220 lines
7.9 KiB
TypeScript
220 lines
7.9 KiB
TypeScript
|
|
/**
|
|||
|
|
* 数据库迁移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)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 用户标签定义表
|
|||
|
|
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<string, boolean> = {}
|
|||
|
|
|
|||
|
|
// 检查各表是否存在
|
|||
|
|
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<string, boolean> = {}
|
|||
|
|
const checkFields = ['ckb_user_id', 'ckb_synced_at', 'ckb_tags', 'tags', 'merged_tags']
|
|||
|
|
|
|||
|
|
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 })
|
|||
|
|
}
|
|||
|
|
}
|