diff --git a/CLAUDE.md b/CLAUDE.md index b17cd63..a517304 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -145,6 +145,27 @@ cd server && npm run db:init - MySQL: 本地 3306 端口 - 微信小程序上线需配置 HTTPS 域名 (替换 `request.ts` 中的 `BASE_URL`) +### 手动部署步骤 + +```bash +# 1. 本地构建后端 +cd server && npm run build + +# 2. 复制 dist 目录到服务器 +scp -r server/dist/* root@106.14.208.43:/var/www/xiaocai/ + +# 3. SSH 到服务器 +ssh root@106.14.208.43 + +# 4. 安装依赖(如 node_modules 已存在可跳过) +cd /var/www/xiaocai && npm install --production + +# 5. 重启服务 +pm2 restart xiaocai-server +# 或直接运行 +node dist/index.js +``` + ## 开发 @DEV.md diff --git a/deploy/backup.sh b/deploy/backup.sh deleted file mode 100644 index b52bdef..0000000 --- a/deploy/backup.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# 数据库备份脚本 (每天执行) - -BACKUP_DIR="/var/backups/xiaocai" -DATE=$(date +%Y%m%d_%H%M%S) - -mkdir -p "$BACKUP_DIR" - -mysqldump -u xiaocai -pxiaocai123 xiaocai | gzip > "$BACKUP_DIR/xiaocai_$DATE.sql.gz" - -# 保留最近 7 天的备份 -find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete - -echo "备份完成: $BACKUP_DIR/xiaocai_$DATE.sql.gz" diff --git a/deploy/deploy.sh b/deploy/deploy.sh deleted file mode 100644 index 72f84c5..0000000 --- a/deploy/deploy.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash -# 小菜记账部署脚本 - -set -e - -SERVER_DIR="/var/www/xiaocai" -LOG_DIR="/var/log/xiaocai" - -echo "=== 开始部署 ===" - -# 进入项目目录 -cd "$(dirname "$0")/.." - -# 构建服务端 -echo "1/4 构建服务端..." -cd server -npm install -npm run build -cd .. - -# 复制文件到服务器目录 -echo "2/4 复制文件..." -cp -r server/dist "$SERVER_DIR/" -cp -r server/node_modules "$SERVER_DIR/" -cp server/package.json "$SERVER_DIR/" -cp deploy/ecosystem.config.js "$SERVER_DIR/" - -# 初始化数据库 -echo "3/4 初始化数据库..." -cd "$SERVER_DIR" -mysql -u xiaocai -pxiaocai123 xiaocai < dist/db/schema.sql -mysql -u xiaocai -pxiaocai123 xiaocai < dist/db/seed.sql 2>/dev/null || true -cd - - -# 配置 Nginx -echo "4/4 配置 Nginx..." -sudo cp deploy/nginx.conf /etc/nginx/sites-available/xiaocai -sudo ln -sf /etc/nginx/sites-available/xiaocai /etc/nginx/sites-enabled/ -sudo rm -f /etc/nginx/sites-enabled/default -sudo nginx -t -sudo systemctl reload nginx - -# 重启服务 -echo "重启 PM2 服务..." -cd "$SERVER_DIR" -pm2 delete xiaocai-server 2>/dev/null || true -pm2 start ecosystem.config.js -pm2 save - -echo "" -echo "=== 部署完成 ===" -echo "服务地址: http://your-domain.com" -echo "API 健康检查: http://your-domain.com/api/health" -echo "" -echo "查看日志: pm2 logs xiaocai-server" -echo "查看状态: pm2 status" diff --git a/deploy/ecosystem.config.js b/deploy/ecosystem.config.js deleted file mode 100644 index 0e8ac9d..0000000 --- a/deploy/ecosystem.config.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - apps: [ - { - name: 'xiaocai-server', - script: './dist/index.js', - instances: 1, - autorestart: true, - watch: false, - max_memory_restart: '300M', - env: { - NODE_ENV: 'production', - PORT: 3000 - }, - error_file: '/var/log/xiaocai/error.log', - out_file: '/var/log/xiaocai/out.log', - log_date_format: 'YYYY-MM-DD HH:mm:ss' - } - ] -} diff --git a/deploy/nginx.conf b/deploy/nginx.conf deleted file mode 100644 index 5e29211..0000000 --- a/deploy/nginx.conf +++ /dev/null @@ -1,26 +0,0 @@ -server { - listen 80; - server_name your-domain.com; - - # 小程序 API - location /api/ { - proxy_pass http://127.0.0.1:3000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - # 健康检查 - location /health { - proxy_pass http://127.0.0.1:3000; - } - - # SSL 配置 (上线前取消注释) - # listen 443 ssl; - # ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; - # ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; - - # HTTP 重定向到 HTTPS - # return 301 https://$host$request_uri; -} diff --git a/deploy/setup.sh b/deploy/setup.sh deleted file mode 100644 index 5948866..0000000 --- a/deploy/setup.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -# 小菜记账服务器初始化脚本 -# 适用于 Ubuntu 22.04 LTS + 阿里云 2H2G - -set -e - -echo "=== 小菜记账服务器初始化 ===" - -# 更新系统 -echo "1/8 更新系统包..." -sudo apt update && sudo apt upgrade -y - -# 安装 Node.js 20 -echo "2/8 安装 Node.js 20..." -curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - -sudo apt install -y nodejs - -# 安装 MySQL 8.0 -echo "3/8 安装 MySQL 8.0..." -sudo apt install -y mysql-server - -# 安全配置 MySQL -echo "4/8 配置 MySQL..." -sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root_password';" -sudo mysql -e "CREATE DATABASE IF NOT EXISTS xiaocai DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" -sudo mysql -e "CREATE USER IF NOT EXISTS 'xiaocai'@'localhost' IDENTIFIED BY 'xiaocai123';" -sudo mysql -e "GRANT ALL PRIVILEGES ON xiaocai.* TO 'xiaocai'@'localhost';" -sudo mysql -e "FLUSH PRIVILEGES;" - -# 安装 Nginx -echo "5/8 安装 Nginx..." -sudo apt install -y nginx - -# 安装 PM2 -echo "6/8 安装 PM2..." -sudo npm install -g pm2 - -# 创建部署目录 -echo "7/8 创建部署目录..." -sudo mkdir -p /var/www/xiaocai -sudo mkdir -p /var/log/xiaocai -sudo chown -R $USER:$USER /var/www/xiaocai - -# 配置防火墙 -echo "8/8 配置防火墙..." -sudo ufw allow 22/tcp -sudo ufw allow 80/tcp -sudo ufw allow 443/tcp -sudo ufw --force enable - -echo "" -echo "=== 服务器初始化完成 ===" -echo "MySQL root 密码: root_password" -echo "MySQL xiaocai 密码: xiaocai123" -echo "请修改 deploy/ecosystem.config.js 中的数据库密码" -echo "" -echo "下一步: ./deploy.sh" diff --git a/server/src/db/init.ts b/server/src/db/init.ts index 01824e5..e5c02af 100644 --- a/server/src/db/init.ts +++ b/server/src/db/init.ts @@ -3,6 +3,31 @@ import pool from './connection' import { readFileSync } from 'fs' import { join } from 'path' +async function columnExists(conn: mysql.Connection, table: string, column: string): Promise { + const [rows] = await conn.query( + `SELECT COUNT(*) as cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?`, + [table, column] + ) + return (rows as any[])[0].cnt > 0 +} + +async function runMigrations(conn: mysql.Connection) { + // transactions 表添加 user_id + if (!await columnExists(conn, 'transactions', 'user_id')) { + console.log('[DB] Migrating: adding user_id to transactions') + await conn.query('ALTER TABLE transactions ADD COLUMN user_id INT NOT NULL DEFAULT 1 AFTER id') + await conn.query('ALTER TABLE transactions ADD INDEX idx_user_date (user_id, date)') + await conn.query('ALTER TABLE transactions ADD INDEX idx_user_type_date (user_id, type, date)') + } + + // budgets 表添加 user_id + if (!await columnExists(conn, 'budgets', 'user_id')) { + console.log('[DB] Migrating: adding user_id to budgets') + await conn.query('ALTER TABLE budgets ADD COLUMN user_id INT NOT NULL DEFAULT 1 AFTER id') + await conn.query('ALTER TABLE budgets ADD UNIQUE KEY uk_user_month (user_id, month)') + } +} + export async function initDatabase() { const dbName = process.env.DB_NAME || 'xiaocai' @@ -38,10 +63,14 @@ export async function initDatabase() { const seed = readFileSync(seedPath, 'utf-8') await initConn.query(seed) + // 运行迁移(添加缺失列) + await runMigrations(initConn) + console.log('[DB] Database initialized') } catch (err: any) { if (err.code === 'ER_DUP_ENTRY' || err.errno === 1062) { console.log('[DB] Already initialized, skipping seed') + await runMigrations(initConn) } else { console.error('[DB] Init failed:', err.message) } diff --git a/server/src/db/migrate.sql b/server/src/db/migrate.sql new file mode 100644 index 0000000..cdcecc3 --- /dev/null +++ b/server/src/db/migrate.sql @@ -0,0 +1,16 @@ +-- 迁移脚本:为缺少 user_id 的表添加该列 +-- 适用于旧版数据库升级 + +-- transactions 表添加 user_id +ALTER TABLE transactions ADD COLUMN user_id INT NOT NULL DEFAULT 1 AFTER id; +ALTER TABLE transactions ADD INDEX idx_user_date (user_id, date); +ALTER TABLE transactions ADD INDEX idx_user_type_date (user_id, type, date); +ALTER TABLE transactions ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + +-- budgets 表添加 user_id +ALTER TABLE budgets ADD COLUMN user_id INT NOT NULL DEFAULT 1 AFTER id; +ALTER TABLE budgets ADD UNIQUE KEY uk_user_month (user_id, month); +ALTER TABLE budgets ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + +-- categories 表确认 user_id 存在(旧版可能也没有) +ALTER TABLE categories ADD COLUMN IF NOT EXISTS user_id INT DEFAULT 0 COMMENT '0=默认分类, 其他=用户自定义' AFTER id;