fix: 启动时自动迁移缺失的数据库列
问题:旧版数据库缺少 user_id 列导致 500 错误 修复: - init.ts 启动时自动检测并添加缺失列 - 删除 deploy/ 目录,改为手动部署 - 添加 migrate.sql 迁移脚本 - 更新 CLAUDE.md 部署说明
This commit is contained in:
21
CLAUDE.md
21
CLAUDE.md
@@ -145,6 +145,27 @@ cd server && npm run db:init
|
|||||||
- MySQL: 本地 3306 端口
|
- MySQL: 本地 3306 端口
|
||||||
- 微信小程序上线需配置 HTTPS 域名 (替换 `request.ts` 中的 `BASE_URL`)
|
- 微信小程序上线需配置 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
|
@DEV.md
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -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"
|
|
||||||
@@ -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'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
@@ -3,6 +3,31 @@ import pool from './connection'
|
|||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
|
async function columnExists(conn: mysql.Connection, table: string, column: string): Promise<boolean> {
|
||||||
|
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() {
|
export async function initDatabase() {
|
||||||
const dbName = process.env.DB_NAME || 'xiaocai'
|
const dbName = process.env.DB_NAME || 'xiaocai'
|
||||||
|
|
||||||
@@ -38,10 +63,14 @@ export async function initDatabase() {
|
|||||||
const seed = readFileSync(seedPath, 'utf-8')
|
const seed = readFileSync(seedPath, 'utf-8')
|
||||||
await initConn.query(seed)
|
await initConn.query(seed)
|
||||||
|
|
||||||
|
// 运行迁移(添加缺失列)
|
||||||
|
await runMigrations(initConn)
|
||||||
|
|
||||||
console.log('[DB] Database initialized')
|
console.log('[DB] Database initialized')
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.code === 'ER_DUP_ENTRY' || err.errno === 1062) {
|
if (err.code === 'ER_DUP_ENTRY' || err.errno === 1062) {
|
||||||
console.log('[DB] Already initialized, skipping seed')
|
console.log('[DB] Already initialized, skipping seed')
|
||||||
|
await runMigrations(initConn)
|
||||||
} else {
|
} else {
|
||||||
console.error('[DB] Init failed:', err.message)
|
console.error('[DB] Init failed:', err.message)
|
||||||
}
|
}
|
||||||
|
|||||||
16
server/src/db/migrate.sql
Normal file
16
server/src/db/migrate.sql
Normal file
@@ -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;
|
||||||
Reference in New Issue
Block a user