/** * 数据库迁移API * 用于升级数据库结构 */ import { NextRequest, NextResponse } from 'next/server' import { query } from '@/lib/db' /** * POST - 执行数据库迁移 */ export async function POST(request: NextRequest) { try { const body = await request.json().catch(() => ({})) const { migration } = body const results: string[] = [] // 用户表扩展字段(存客宝同步和标签) if (!migration || migration === 'user_ckb_fields') { const userFields = [ { name: 'ckb_user_id', def: "VARCHAR(100) DEFAULT NULL COMMENT '存客宝用户ID'" }, { name: 'ckb_synced_at', def: "DATETIME DEFAULT NULL COMMENT '最后同步时间'" }, { name: 'ckb_tags', def: "JSON DEFAULT NULL COMMENT '存客宝标签'" }, { name: 'tags', def: "JSON DEFAULT NULL COMMENT '系统标签'" }, { name: 'source_tags', def: "JSON DEFAULT NULL COMMENT '来源标签'" }, { name: 'merged_tags', def: "JSON DEFAULT NULL COMMENT '合并后的标签'" }, { name: 'source', def: "VARCHAR(50) DEFAULT NULL COMMENT '用户来源'" }, { name: 'created_by', def: "VARCHAR(100) DEFAULT NULL COMMENT '创建人'" }, { name: 'matched_by', def: "VARCHAR(100) DEFAULT NULL COMMENT '匹配人'" } ] let addedCount = 0 let existCount = 0 for (const field of userFields) { try { // 检查字段是否存在 await query(`SELECT ${field.name} FROM users LIMIT 1`) existCount++ } catch { // 字段不存在,添加它 try { await query(`ALTER TABLE users ADD COLUMN ${field.name} ${field.def}`) addedCount++ } catch (e: any) { if (e.code !== 'ER_DUP_FIELDNAME') { results.push(`⚠️ 添加字段 ${field.name} 失败: ${e.message}`) } } } } if (addedCount > 0) { results.push(`✅ 用户表新增 ${addedCount} 个字段`) } if (existCount > 0) { results.push(`ℹ️ 用户表已有 ${existCount} 个字段存在`) } } // 用户行为轨迹表 if (!migration || migration === 'user_tracks') { try { await query(` CREATE TABLE IF NOT EXISTS user_tracks ( id VARCHAR(50) PRIMARY KEY, user_id VARCHAR(100) NOT NULL COMMENT '用户ID', action VARCHAR(50) NOT NULL COMMENT '行为类型', chapter_id VARCHAR(100) DEFAULT NULL COMMENT '章节ID', target VARCHAR(200) DEFAULT NULL COMMENT '目标对象', extra_data JSON DEFAULT NULL COMMENT '额外数据', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', INDEX idx_user_id (user_id), INDEX idx_action (action), INDEX idx_created_at (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户行为轨迹表' `) results.push('✅ 用户行为轨迹表创建成功') } catch (e: any) { if (e.code === 'ER_TABLE_EXISTS_ERROR') { results.push('ℹ️ 用户行为轨迹表已存在') } else { results.push('⚠️ 用户行为轨迹表创建失败: ' + e.message) } } } // 存客宝同步记录表 if (!migration || migration === 'ckb_sync_logs') { try { await query(` CREATE TABLE IF NOT EXISTS ckb_sync_logs ( id VARCHAR(50) PRIMARY KEY, user_id VARCHAR(100) NOT NULL COMMENT '用户ID', phone VARCHAR(20) NOT NULL COMMENT '手机号', action VARCHAR(50) NOT NULL COMMENT '同步动作: pull/push/full', status VARCHAR(20) NOT NULL COMMENT '状态: success/failed', request_data JSON DEFAULT NULL COMMENT '请求数据', response_data JSON DEFAULT NULL COMMENT '响应数据', error_msg TEXT DEFAULT NULL COMMENT '错误信息', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', INDEX idx_user_id (user_id), INDEX idx_phone (phone), INDEX idx_created_at (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存客宝同步日志表' `) results.push('✅ 存客宝同步日志表创建成功') } catch (e: any) { if (e.code === 'ER_TABLE_EXISTS_ERROR') { results.push('ℹ️ 存客宝同步日志表已存在') } else { results.push('⚠️ 存客宝同步日志表创建失败: ' + e.message) } } } // VIP会员字段 if (!migration || migration === 'vip_fields') { const vipFields = [ { name: 'is_vip', def: "BOOLEAN DEFAULT FALSE COMMENT 'VIP会员'" }, { name: 'vip_expire_date', def: "TIMESTAMP NULL COMMENT 'VIP到期时间'" }, { name: 'vip_name', def: "VARCHAR(100) COMMENT '会员真实姓名'" }, { name: 'vip_project', def: "VARCHAR(200) COMMENT '会员项目名称'" }, { name: 'vip_contact', def: "VARCHAR(100) COMMENT '会员联系方式'" }, { name: 'vip_avatar', def: "VARCHAR(500) COMMENT '会员展示头像'" }, { name: 'vip_bio', def: "VARCHAR(500) COMMENT '会员简介'" }, ] let addedCount = 0 let existCount = 0 for (const field of vipFields) { try { await query(`SELECT ${field.name} FROM users LIMIT 1`) existCount++ } catch { try { await query(`ALTER TABLE users ADD COLUMN ${field.name} ${field.def}`) addedCount++ } catch (e: any) { if (e.code !== 'ER_DUP_FIELDNAME') { results.push(`⚠️ 添加VIP字段 ${field.name} 失败: ${e.message}`) } } } } // 扩展 orders.product_type 支持 vip try { await query(`ALTER TABLE orders MODIFY COLUMN product_type ENUM('section', 'fullbook', 'match', 'vip') NOT NULL`) results.push('✅ orders.product_type 已支持 vip') } catch (e: any) { results.push('ℹ️ orders.product_type 更新跳过: ' + e.message) } if (addedCount > 0) results.push(`✅ VIP字段新增 ${addedCount} 个`) if (existCount > 0) results.push(`ℹ️ VIP字段已有 ${existCount} 个存在`) } // 用户标签定义表 if (!migration || migration === 'user_tag_definitions') { try { await query(` CREATE TABLE IF NOT EXISTS user_tag_definitions ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE COMMENT '标签名称', category VARCHAR(50) NOT NULL COMMENT '标签分类: system/ckb/behavior/source', color VARCHAR(20) DEFAULT '#38bdac' COMMENT '标签颜色', description VARCHAR(200) DEFAULT NULL COMMENT '标签描述', is_active BOOLEAN DEFAULT TRUE COMMENT '是否启用', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_category (category) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户标签定义表' `) // 插入默认标签 await query(` INSERT IGNORE INTO user_tag_definitions (name, category, color, description) VALUES ('已购全书', 'system', '#22c55e', '购买了完整书籍'), ('VIP用户', 'system', '#eab308', 'VIP会员'), ('活跃用户', 'behavior', '#38bdac', '最近7天有访问'), ('高价值用户', 'behavior', '#f59e0b', '消费超过100元'), ('推广达人', 'behavior', '#8b5cf6', '成功推荐5人以上'), ('微信用户', 'source', '#07c160', '通过微信授权登录'), ('手动创建', 'source', '#6b7280', '后台手动创建') `) results.push('✅ 用户标签定义表创建成功') } catch (e: any) { if (e.code === 'ER_TABLE_EXISTS_ERROR') { results.push('ℹ️ 用户标签定义表已存在') } else { results.push('⚠️ 用户标签定义表创建失败: ' + e.message) } } } return NextResponse.json({ success: true, results, message: '数据库迁移完成' }) } catch (error) { console.error('[DB Migrate] Error:', error) return NextResponse.json({ success: false, error: '数据库迁移失败: ' + (error as Error).message }, { status: 500 }) } } /** * GET - 获取迁移状态 */ export async function GET() { try { const tables: Record = {} // 检查各表是否存在 const checkTables = ['user_tracks', 'ckb_sync_logs', 'user_tag_definitions'] for (const table of checkTables) { try { await query(`SELECT 1 FROM ${table} LIMIT 1`) tables[table] = true } catch { tables[table] = false } } // 检查用户表字段 const userFields: Record = {} const checkFields = ['ckb_user_id', 'ckb_synced_at', 'ckb_tags', 'tags', 'merged_tags', 'is_vip', 'vip_expire_date', 'vip_name'] for (const field of checkFields) { try { await query(`SELECT ${field} FROM users LIMIT 1`) userFields[field] = true } catch { userFields[field] = false } } return NextResponse.json({ success: true, status: { tables, userFields, allReady: Object.values(tables).every(v => v) && Object.values(userFields).every(v => v) } }) } catch (error) { console.error('[DB Migrate] GET Error:', error) return NextResponse.json({ success: false, error: '获取迁移状态失败' }, { status: 500 }) } }