Files
soul/app/api/db/migrate/route.ts

220 lines
7.9 KiB
TypeScript
Raw Normal View History

/**
* 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 })
}
}