diff --git a/app/api/book/chapter/[id]/route.ts b/app/api/book/chapter/[id]/route.ts index 80f0080..61bb636 100644 --- a/app/api/book/chapter/[id]/route.ts +++ b/app/api/book/chapter/[id]/route.ts @@ -10,10 +10,10 @@ const FREE_CHAPTERS = ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', export async function GET( req: NextRequest, - { params }: { params: { id: string } } + { params }: { params: Promise<{ id: string }> } ) { try { - const chapterId = params.id + const { id: chapterId } = await params console.log('[Chapter API] 请求章节:', chapterId) // 从数据库查询章节 diff --git a/app/api/db/book/route.ts b/app/api/db/book/route.ts deleted file mode 100644 index d3df69b..0000000 --- a/app/api/db/book/route.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { bookDB } from '@/lib/db' -import { bookData } from '@/lib/book-data' -import fs from 'fs/promises' -import path from 'path' - -// 获取章节 -export async function GET(req: NextRequest) { - try { - const { searchParams } = new URL(req.url) - const id = searchParams.get('id') - const action = searchParams.get('action') - - // 导出所有章节 - if (action === 'export') { - const data = await bookDB.exportAll() - return new NextResponse(data, { - headers: { - 'Content-Type': 'application/json', - 'Content-Disposition': 'attachment; filename=book_sections.json' - } - }) - } - - // 从文件系统读取章节内容 - if (action === 'read' && id) { - // 查找章节文件路径 - let filePath = '' - for (const part of bookData) { - for (const chapter of part.chapters) { - const section = chapter.sections.find(s => s.id === id) - if (section) { - filePath = section.filePath - break - } - } - if (filePath) break - } - - if (!filePath) { - return NextResponse.json({ - success: false, - error: '章节不存在' - }, { status: 404 }) - } - - const fullPath = path.join(process.cwd(), filePath) - const content = await fs.readFile(fullPath, 'utf-8') - - return NextResponse.json({ - success: true, - section: { id, filePath, content } - }) - } - - if (id) { - const section = await bookDB.getSection(id) - return NextResponse.json({ success: true, section }) - } - - const sections = await bookDB.getAllSections() - return NextResponse.json({ success: true, sections }) - } catch (error: any) { - console.error('Get book sections error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 创建或更新章节 -export async function POST(req: NextRequest) { - try { - const body = await req.json() - const { action, data } = body - - // 导入章节 - if (action === 'import') { - const count = await bookDB.importSections(JSON.stringify(data)) - return NextResponse.json({ - success: true, - message: `成功导入 ${count} 个章节` - }) - } - - // 同步book-data到数据库 - if (action === 'sync') { - let count = 0 - let sortOrder = 0 - - for (const part of bookData) { - for (const chapter of part.chapters) { - for (const section of chapter.sections) { - sortOrder++ - const existing = await bookDB.getSection(section.id) - - // 读取文件内容 - let content = '' - try { - const fullPath = path.join(process.cwd(), section.filePath) - content = await fs.readFile(fullPath, 'utf-8') - } catch (e) { - console.warn(`Cannot read file: ${section.filePath}`) - } - - if (existing) { - await bookDB.updateSection(section.id, { - title: section.title, - content, - price: section.price, - is_free: section.isFree - }) - } else { - await bookDB.createSection({ - id: section.id, - part_id: part.id, - chapter_id: chapter.id, - title: section.title, - content, - price: section.price, - is_free: section.isFree, - sort_order: sortOrder - }) - } - count++ - } - } - } - - return NextResponse.json({ - success: true, - message: `成功同步 ${count} 个章节到数据库` - }) - } - - // 创建单个章节 - const section = await bookDB.createSection(data) - return NextResponse.json({ success: true, section }) - } catch (error: any) { - console.error('Create/Import book section error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 更新章节 -export async function PUT(req: NextRequest) { - try { - const body = await req.json() - const { id, ...updates } = body - - if (!id) { - return NextResponse.json({ - success: false, - error: '缺少章节ID' - }, { status: 400 }) - } - - // 如果要保存到文件系统 - if (updates.content && updates.saveToFile) { - // 查找章节文件路径 - let filePath = '' - for (const part of bookData) { - for (const chapter of part.chapters) { - const section = chapter.sections.find(s => s.id === id) - if (section) { - filePath = section.filePath - break - } - } - if (filePath) break - } - - if (filePath) { - const fullPath = path.join(process.cwd(), filePath) - await fs.writeFile(fullPath, updates.content, 'utf-8') - } - } - - await bookDB.updateSection(id, updates) - const section = await bookDB.getSection(id) - - return NextResponse.json({ success: true, section }) - } catch (error: any) { - console.error('Update book section error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} diff --git a/app/api/db/distribution/route.ts b/app/api/db/distribution/route.ts deleted file mode 100644 index e04c998..0000000 --- a/app/api/db/distribution/route.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { distributionDB, purchaseDB } from '@/lib/db' - -// 获取分销数据 -export async function GET(req: NextRequest) { - try { - const { searchParams } = new URL(req.url) - const type = searchParams.get('type') - const referrerId = searchParams.get('referrer_id') - - // 获取佣金记录 - if (type === 'commissions') { - const commissions = await distributionDB.getAllCommissions() - return NextResponse.json({ success: true, commissions }) - } - - // 获取指定推荐人的绑定 - if (referrerId) { - const bindings = await distributionDB.getBindingsByReferrer(referrerId) - return NextResponse.json({ success: true, bindings }) - } - - // 获取所有绑定关系 - const bindings = await distributionDB.getAllBindings() - return NextResponse.json({ success: true, bindings }) - } catch (error: any) { - console.error('Get distribution data error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 创建绑定或佣金记录 -export async function POST(req: NextRequest) { - try { - const body = await req.json() - const { type, data } = body - - if (type === 'binding') { - const binding = await distributionDB.createBinding({ - id: `binding_${Date.now()}`, - ...data, - bound_at: new Date().toISOString(), - expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30天有效期 - status: 'active' - }) - return NextResponse.json({ success: true, binding }) - } - - if (type === 'commission') { - const commission = await distributionDB.createCommission({ - id: `commission_${Date.now()}`, - ...data, - status: 'pending' - }) - return NextResponse.json({ success: true, commission }) - } - - return NextResponse.json({ - success: false, - error: '未知操作类型' - }, { status: 400 }) - } catch (error: any) { - console.error('Create distribution record error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 更新绑定状态 -export async function PUT(req: NextRequest) { - try { - const body = await req.json() - const { id, status } = body - - if (!id) { - return NextResponse.json({ - success: false, - error: '缺少记录ID' - }, { status: 400 }) - } - - await distributionDB.updateBindingStatus(id, status) - return NextResponse.json({ success: true }) - } catch (error: any) { - console.error('Update distribution status error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} diff --git a/app/api/db/purchases/route.ts b/app/api/db/purchases/route.ts deleted file mode 100644 index 3b684c0..0000000 --- a/app/api/db/purchases/route.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { purchaseDB, userDB, distributionDB } from '@/lib/db' - -// 获取购买记录 -export async function GET(req: NextRequest) { - try { - const { searchParams } = new URL(req.url) - const userId = searchParams.get('user_id') - - if (userId) { - const purchases = await purchaseDB.getByUserId(userId) - return NextResponse.json({ success: true, purchases }) - } - - const purchases = await purchaseDB.getAll() - return NextResponse.json({ success: true, purchases }) - } catch (error: any) { - console.error('Get purchases error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 创建购买记录 -export async function POST(req: NextRequest) { - try { - const body = await req.json() - const { - user_id, - type, - section_id, - section_title, - amount, - payment_method, - referral_code - } = body - - // 创建购买记录 - const purchase = await purchaseDB.create({ - id: `purchase_${Date.now()}`, - user_id, - type, - section_id, - section_title, - amount, - payment_method, - referral_code, - referrer_earnings: 0, - status: 'completed' - }) - - // 更新用户购买状态 - if (type === 'fullbook') { - await userDB.update(user_id, { has_full_book: true }) - } - - // 处理分销佣金 - if (referral_code) { - // 查找推荐人 - const users = await userDB.getAll() - const referrer = users.find((u: any) => u.referral_code === referral_code) - - if (referrer) { - const commissionRate = 0.9 // 90% 佣金 - const commissionAmount = amount * commissionRate - - // 查找有效的绑定关系 - const binding = await distributionDB.getActiveBindingByReferee(user_id) - - if (binding) { - // 创建佣金记录 - await distributionDB.createCommission({ - id: `commission_${Date.now()}`, - binding_id: binding.id, - referrer_id: referrer.id, - referee_id: user_id, - order_id: purchase.id, - amount, - commission_rate: commissionRate * 100, - commission_amount: commissionAmount, - status: 'pending' - }) - - // 更新推荐人收益 - await userDB.update(referrer.id, { - earnings: (referrer.earnings || 0) + commissionAmount, - pending_earnings: (referrer.pending_earnings || 0) + commissionAmount - }) - - // 更新购买记录的推荐人收益 - purchase.referrer_earnings = commissionAmount - } - } - } - - return NextResponse.json({ success: true, purchase }) - } catch (error: any) { - console.error('Create purchase error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} diff --git a/app/api/db/settings/route.ts b/app/api/db/settings/route.ts deleted file mode 100644 index eb04736..0000000 --- a/app/api/db/settings/route.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { settingsDB } from '@/lib/db' - -// 获取系统设置 -export async function GET() { - try { - const settings = await settingsDB.get() - return NextResponse.json({ success: true, settings: settings?.data || null }) - } catch (error: any) { - console.error('Get settings error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 保存系统设置 -export async function POST(req: NextRequest) { - try { - const body = await req.json() - await settingsDB.update(body) - return NextResponse.json({ success: true, message: '设置已保存' }) - } catch (error: any) { - console.error('Save settings error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} diff --git a/app/api/db/users/route.ts b/app/api/db/users/route.ts deleted file mode 100644 index 06aa97d..0000000 --- a/app/api/db/users/route.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { userDB } from '@/lib/db' - -// 获取所有用户 -export async function GET(req: NextRequest) { - try { - const { searchParams } = new URL(req.url) - const id = searchParams.get('id') - const phone = searchParams.get('phone') - - if (id) { - const user = await userDB.getById(id) - return NextResponse.json({ success: true, user }) - } - - if (phone) { - const user = await userDB.getByPhone(phone) - return NextResponse.json({ success: true, user }) - } - - const users = await userDB.getAll() - return NextResponse.json({ success: true, users }) - } catch (error: any) { - console.error('Get users error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 创建用户 -export async function POST(req: NextRequest) { - try { - const body = await req.json() - const { phone, nickname, password, referral_code, referred_by } = body - - // 检查手机号是否已存在 - const existing = await userDB.getByPhone(phone) - if (existing) { - return NextResponse.json({ - success: false, - error: '该手机号已注册' - }, { status: 400 }) - } - - const user = await userDB.create({ - id: `user_${Date.now()}`, - phone, - nickname, - password, - referral_code: referral_code || `REF${Date.now().toString(36).toUpperCase()}`, - referred_by - }) - - return NextResponse.json({ success: true, user }) - } catch (error: any) { - console.error('Create user error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 更新用户 -export async function PUT(req: NextRequest) { - try { - const body = await req.json() - const { id, ...updates } = body - - if (!id) { - return NextResponse.json({ - success: false, - error: '缺少用户ID' - }, { status: 400 }) - } - - await userDB.update(id, updates) - const user = await userDB.getById(id) - - return NextResponse.json({ success: true, user }) - } catch (error: any) { - console.error('Update user error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 删除用户 -export async function DELETE(req: NextRequest) { - try { - const { searchParams } = new URL(req.url) - const id = searchParams.get('id') - - if (!id) { - return NextResponse.json({ - success: false, - error: '缺少用户ID' - }, { status: 400 }) - } - - await userDB.delete(id) - return NextResponse.json({ success: true }) - } catch (error: any) { - console.error('Delete user error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} diff --git a/app/api/db/withdrawals/route.ts b/app/api/db/withdrawals/route.ts deleted file mode 100644 index a7b0f68..0000000 --- a/app/api/db/withdrawals/route.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { withdrawalDB, userDB } from '@/lib/db' - -// 获取提现记录 -export async function GET(req: NextRequest) { - try { - const { searchParams } = new URL(req.url) - const userId = searchParams.get('user_id') - - if (userId) { - const withdrawals = await withdrawalDB.getByUserId(userId) - return NextResponse.json({ success: true, withdrawals }) - } - - const withdrawals = await withdrawalDB.getAll() - return NextResponse.json({ success: true, withdrawals }) - } catch (error: any) { - console.error('Get withdrawals error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 创建提现申请 -export async function POST(req: NextRequest) { - try { - const body = await req.json() - const { user_id, amount, method, account, name } = body - - // 验证用户余额 - const user = await userDB.getById(user_id) - if (!user) { - return NextResponse.json({ - success: false, - error: '用户不存在' - }, { status: 404 }) - } - - if ((user.earnings || 0) < amount) { - return NextResponse.json({ - success: false, - error: '余额不足' - }, { status: 400 }) - } - - // 创建提现记录 - const withdrawal = await withdrawalDB.create({ - id: `withdrawal_${Date.now()}`, - user_id, - amount, - method, - account, - name, - status: 'pending' - }) - - // 扣除用户余额,增加待提现金额 - await userDB.update(user_id, { - earnings: (user.earnings || 0) - amount, - pending_earnings: (user.pending_earnings || 0) + amount - }) - - return NextResponse.json({ success: true, withdrawal }) - } catch (error: any) { - console.error('Create withdrawal error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} - -// 更新提现状态 -export async function PUT(req: NextRequest) { - try { - const body = await req.json() - const { id, status } = body - - if (!id) { - return NextResponse.json({ - success: false, - error: '缺少提现记录ID' - }, { status: 400 }) - } - - await withdrawalDB.updateStatus(id, status) - - // 如果状态是已完成,更新用户的已提现金额 - if (status === 'completed') { - const withdrawals = await withdrawalDB.getAll() - const withdrawal = withdrawals.find((w: any) => w.id === id) - if (withdrawal) { - const user = await userDB.getById(withdrawal.user_id) - if (user) { - await userDB.update(user.id, { - pending_earnings: (user.pending_earnings || 0) - withdrawal.amount, - withdrawn_earnings: (user.withdrawn_earnings || 0) + withdrawal.amount - }) - } - } - } - - return NextResponse.json({ success: true }) - } catch (error: any) { - console.error('Update withdrawal status error:', error) - return NextResponse.json({ - success: false, - error: error.message - }, { status: 500 }) - } -} diff --git a/scripts/deploy-to-server.sh b/scripts/deploy-to-server.sh new file mode 100755 index 0000000..b67a6ca --- /dev/null +++ b/scripts/deploy-to-server.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# Soul项目一键部署到宝塔服务器 +# 使用方法: ./deploy-to-server.sh [SSH密码] + +# 服务器配置 +SERVER_IP="42.194.232.22" +SERVER_USER="root" +PROJECT_PATH="/www/wwwroot/soul" +BRANCH="soul-content" + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo "=======================================" +echo " Soul项目 - 宝塔服务器一键部署" +echo "=======================================" +echo "" + +# 检查sshpass是否安装 +if ! command -v sshpass &> /dev/null; then + echo -e "${YELLOW}正在安装sshpass...${NC}" + brew install hudochenkov/sshpass/sshpass 2>/dev/null || { + echo -e "${RED}请手动安装sshpass: brew install hudochenkov/sshpass/sshpass${NC}" + exit 1 + } +fi + +# 获取SSH密码 +if [ -z "$1" ]; then + echo -n "请输入SSH密码: " + read -s SSH_PASSWORD + echo "" +else + SSH_PASSWORD="$1" +fi + +echo "" +echo -e "${GREEN}[1/5]${NC} 连接服务器..." + +# 测试连接 +sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 $SERVER_USER@$SERVER_IP "echo '连接成功'" 2>/dev/null +if [ $? -ne 0 ]; then + echo -e "${RED}连接失败,请检查密码是否正确${NC}" + exit 1 +fi + +echo -e "${GREEN}[2/5]${NC} 拉取最新代码..." +sshpass -p "$SSH_PASSWORD" ssh $SERVER_USER@$SERVER_IP "cd $PROJECT_PATH && git fetch origin && git reset --hard origin/$BRANCH" + +echo -e "${GREEN}[3/5]${NC} 安装依赖..." +sshpass -p "$SSH_PASSWORD" ssh $SERVER_USER@$SERVER_IP "cd $PROJECT_PATH && pnpm install --frozen-lockfile 2>/dev/null || npm install" + +echo -e "${GREEN}[4/5]${NC} 构建项目..." +sshpass -p "$SSH_PASSWORD" ssh $SERVER_USER@$SERVER_IP "cd $PROJECT_PATH && pnpm build 2>/dev/null || npm run build" + +echo -e "${GREEN}[5/5]${NC} 重启服务..." +# 使用www用户的PM2(宝塔方式) +sshpass -p "$SSH_PASSWORD" ssh $SERVER_USER@$SERVER_IP "sudo -u www /www/server/nvm/versions/node/*/bin/pm2 restart soul 2>/dev/null || pm2 restart soul" + +echo "" +echo "=======================================" +echo -e "${GREEN}✅ 部署完成!${NC}" +echo "=======================================" +echo "" +echo "访问地址: https://soul.quwanzhi.com" +echo "" + +# 测试API +echo "正在验证部署..." +sleep 3 +curl -s "https://soul.quwanzhi.com/api/book/chapter/1.1" | head -100 diff --git a/scripts/migrate-chapters-to-db.ts b/scripts/migrate-chapters-to-db.ts new file mode 100644 index 0000000..a72846f --- /dev/null +++ b/scripts/migrate-chapters-to-db.ts @@ -0,0 +1,224 @@ +/** + * 章节迁移脚本 - 将book文件夹的Markdown文件导入数据库 + * 运行方式: npx ts-node scripts/migrate-chapters-to-db.ts + * + * 开发: 卡若 + * 日期: 2026-01-25 + */ + +import fs from 'fs' +import path from 'path' +import mysql from 'mysql2/promise' + +// 数据库配置 +const DB_CONFIG = { + host: '56b4c23f6853c.gz.cdb.myqcloud.com', + port: 14413, + user: 'cdb_outerroot', + password: 'Zhiqun1984', + database: 'soul_miniprogram', + charset: 'utf8mb4', +} + +// Book目录 +const BOOK_DIR = path.join(process.cwd(), 'book') + +// 免费章节列表 +const FREE_CHAPTERS = ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3'] + +// 章节映射配置 +interface ChapterConfig { + id: string + partId: string + partTitle: string + chapterId: string + chapterTitle: string + sectionTitle: string + dir: string + file: string + sortOrder: number +} + +// 所有章节配置 +const CHAPTERS_CONFIG: ChapterConfig[] = [ + // 序言 + { id: 'preface', partId: 'intro', partTitle: '序言', chapterId: 'preface', chapterTitle: '序言', sectionTitle: '为什么我每天早上6点在Soul开播?', dir: '', file: '序言|为什么我每天早上6点在Soul开播?.md', sortOrder: 0 }, + + // 第一篇 真实的人 + { id: '1.1', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-1', chapterTitle: '第1章|人与人之间的底层逻辑', sectionTitle: '荷包:电动车出租的被动收入模式', dir: '第一篇|真实的人/第1章|人与人之间的底层逻辑', file: '1.1 荷包:电动车出租的被动收入模式.md', sortOrder: 1 }, + { id: '1.2', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-1', chapterTitle: '第1章|人与人之间的底层逻辑', sectionTitle: '老墨:资源整合高手的社交方法', dir: '第一篇|真实的人/第1章|人与人之间的底层逻辑', file: '1.2 老墨:资源整合高手的社交方法.md', sortOrder: 2 }, + { id: '1.3', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-1', chapterTitle: '第1章|人与人之间的底层逻辑', sectionTitle: '笑声背后的MBTI:为什么ENTJ适合做资源,INTP适合做系统', dir: '第一篇|真实的人/第1章|人与人之间的底层逻辑', file: '1.3 笑声背后的MBTI:为什么ENTJ适合做资源,INTP适合做系统.md', sortOrder: 3 }, + { id: '1.4', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-1', chapterTitle: '第1章|人与人之间的底层逻辑', sectionTitle: '人性的三角结构:利益、情感、价值观', dir: '第一篇|真实的人/第1章|人与人之间的底层逻辑', file: '1.4 人性的三角结构:利益、情感、价值观.md', sortOrder: 4 }, + { id: '1.5', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-1', chapterTitle: '第1章|人与人之间的底层逻辑', sectionTitle: '沟通差的问题:为什么你说的别人听不懂', dir: '第一篇|真实的人/第1章|人与人之间的底层逻辑', file: '1.5 沟通差的问题:为什么你说的别人听不懂.md', sortOrder: 5 }, + + { id: '2.1', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-2', chapterTitle: '第2章|人性困境案例', sectionTitle: '相亲故事:你以为找的是人,实际是在找模式', dir: '第一篇|真实的人/第2章|人性困境案例', file: '2.1 相亲故事:你以为找的是人,实际是在找模式.md', sortOrder: 6 }, + { id: '2.2', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-2', chapterTitle: '第2章|人性困境案例', sectionTitle: '找工作迷茫者:为什么简历解决不了人生', dir: '第一篇|真实的人/第2章|人性困境案例', file: '2.2 找工作迷茫者:为什么简历解决不了人生.md', sortOrder: 7 }, + { id: '2.3', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-2', chapterTitle: '第2章|人性困境案例', sectionTitle: '撸运费险:小钱困住大脑的真实心理', dir: '第一篇|真实的人/第2章|人性困境案例', file: '2.3 撸运费险:小钱困住大脑的真实心理.md', sortOrder: 8 }, + { id: '2.4', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-2', chapterTitle: '第2章|人性困境案例', sectionTitle: '游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力', dir: '第一篇|真实的人/第2章|人性困境案例', file: '2.4 游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力.md', sortOrder: 9 }, + { id: '2.5', partId: 'part-1', partTitle: '第一篇|真实的人', chapterId: 'chapter-2', chapterTitle: '第2章|人性困境案例', sectionTitle: '健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒', dir: '第一篇|真实的人/第2章|人性困境案例', file: '2.5 健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒.md', sortOrder: 10 }, + + // 第二篇 真实的行业 + { id: '3.1', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-3', chapterTitle: '第3章|电商篇', sectionTitle: '3000万流水如何跑出来(退税模式解析)', dir: '第二篇|真实的行业/第3章|电商篇', file: '3.1 3000万流水如何跑出来(退税模式解析).md', sortOrder: 11 }, + { id: '3.2', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-3', chapterTitle: '第3章|电商篇', sectionTitle: '供应链之王 vs 打工人:利润不在前端', dir: '第二篇|真实的行业/第3章|电商篇', file: '3.2 供应链之王 vs 打工人:利润不在前端.md', sortOrder: 12 }, + { id: '3.3', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-3', chapterTitle: '第3章|电商篇', sectionTitle: '社区团购的底层逻辑', dir: '第二篇|真实的行业/第3章|电商篇', file: '3.3 社区团购的底层逻辑.md', sortOrder: 13 }, + { id: '3.4', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-3', chapterTitle: '第3章|电商篇', sectionTitle: '跨境电商与退税套利', dir: '第二篇|真实的行业/第3章|电商篇', file: '3.4 跨境电商与退税套利.md', sortOrder: 14 }, + + { id: '4.1', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-4', chapterTitle: '第4章|内容商业篇', sectionTitle: '旅游号:30天10万粉的真实逻辑', dir: '第二篇|真实的行业/第4章|内容商业篇', file: '4.1 旅游号:30天10万粉的真实逻辑.md', sortOrder: 15 }, + { id: '4.2', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-4', chapterTitle: '第4章|内容商业篇', sectionTitle: '做号工厂:如何让一个号变成一个机器', dir: '第二篇|真实的行业/第4章|内容商业篇', file: '4.2 做号工厂:如何让一个号变成一个机器.md', sortOrder: 16 }, + { id: '4.3', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-4', chapterTitle: '第4章|内容商业篇', sectionTitle: '情绪内容为什么比专业内容更赚钱', dir: '第二篇|真实的行业/第4章|内容商业篇', file: '4.3 情绪内容为什么比专业内容更赚钱.md', sortOrder: 17 }, + { id: '4.4', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-4', chapterTitle: '第4章|内容商业篇', sectionTitle: '猫与宠物号:为什么宠物赛道永不过时', dir: '第二篇|真实的行业/第4章|内容商业篇', file: '4.4 猫与宠物号:为什么宠物赛道永不过时.md', sortOrder: 18 }, + { id: '4.5', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-4', chapterTitle: '第4章|内容商业篇', sectionTitle: '直播间里的三种人:演员、技术工、系统流', dir: '第二篇|真实的行业/第4章|内容商业篇', file: '4.5 直播间里的三种人:演员、技术工、系统流.md', sortOrder: 19 }, + + { id: '5.1', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-5', chapterTitle: '第5章|传统行业篇', sectionTitle: '拍卖行抱朴:一天240万的摇号生意', dir: '第二篇|真实的行业/第5章|传统行业篇', file: '5.1 拍卖行抱朴:一天240万的摇号生意.md', sortOrder: 20 }, + { id: '5.2', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-5', chapterTitle: '第5章|传统行业篇', sectionTitle: '土地拍卖:招拍挂背后的游戏规则', dir: '第二篇|真实的行业/第5章|传统行业篇', file: '5.2 土地拍卖:招拍挂背后的游戏规则.md', sortOrder: 21 }, + { id: '5.3', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-5', chapterTitle: '第5章|传统行业篇', sectionTitle: '地摊经济数字化:一个月900块的餐车生意', dir: '第二篇|真实的行业/第5章|传统行业篇', file: '5.3 地摊经济数字化:一个月900块的餐车生意.md', sortOrder: 22 }, + { id: '5.4', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-5', chapterTitle: '第5章|传统行业篇', sectionTitle: '不良资产拍卖:我错过的一个亿佣金', dir: '第二篇|真实的行业/第5章|传统行业篇', file: '5.4 不良资产拍卖:我错过的一个亿佣金.md', sortOrder: 23 }, + { id: '5.5', partId: 'part-2', partTitle: '第二篇|真实的行业', chapterId: 'chapter-5', chapterTitle: '第5章|传统行业篇', sectionTitle: '桶装水李总:跟物业合作的轻资产模式', dir: '第二篇|真实的行业/第5章|传统行业篇', file: '5.5 桶装水李总:跟物业合作的轻资产模式.md', sortOrder: 24 }, + + // 第三篇 真实的错误 + { id: '6.1', partId: 'part-3', partTitle: '第三篇|真实的错误', chapterId: 'chapter-6', chapterTitle: '第6章|我人生错过的4件大钱', sectionTitle: '电商财税窗口:2016年的千万级机会', dir: '第三篇|真实的错误/第6章|我人生错过的4件大钱', file: '6.1 电商财税窗口:2016年的千万级机会.md', sortOrder: 25 }, + { id: '6.2', partId: 'part-3', partTitle: '第三篇|真实的错误', chapterId: 'chapter-6', chapterTitle: '第6章|我人生错过的4件大钱', sectionTitle: '供应链金融:我不懂的杠杆游戏', dir: '第三篇|真实的错误/第6章|我人生错过的4件大钱', file: '6.2 供应链金融:我不懂的杠杆游戏.md', sortOrder: 26 }, + { id: '6.3', partId: 'part-3', partTitle: '第三篇|真实的错误', chapterId: 'chapter-6', chapterTitle: '第6章|我人生错过的4件大钱', sectionTitle: '内容红利:2019年我为什么没做抖音', dir: '第三篇|真实的错误/第6章|我人生错过的4件大钱', file: '6.3 内容红利:2019年我为什么没做抖音.md', sortOrder: 27 }, + { id: '6.4', partId: 'part-3', partTitle: '第三篇|真实的错误', chapterId: 'chapter-6', chapterTitle: '第6章|我人生错过的4件大钱', sectionTitle: '数据资产化:我还在观望的未来机会', dir: '第三篇|真实的错误/第6章|我人生错过的4件大钱', file: '6.4 数据资产化:我还在观望的未来机会.md', sortOrder: 28 }, + + { id: '7.1', partId: 'part-3', partTitle: '第三篇|真实的错误', chapterId: 'chapter-7', chapterTitle: '第7章|别人犯的错误', sectionTitle: '投资房年轻人的迷茫:资金 vs 能力', dir: '第三篇|真实的错误/第7章|别人犯的错误', file: '7.1 投资房年轻人的迷茫:资金 vs 能力.md', sortOrder: 29 }, + { id: '7.2', partId: 'part-3', partTitle: '第三篇|真实的错误', chapterId: 'chapter-7', chapterTitle: '第7章|别人犯的错误', sectionTitle: '信息差骗局:永远有人靠卖学习赚钱', dir: '第三篇|真实的错误/第7章|别人犯的错误', file: '7.2 信息差骗局:永远有人靠卖学习赚钱.md', sortOrder: 30 }, + { id: '7.3', partId: 'part-3', partTitle: '第三篇|真实的错误', chapterId: 'chapter-7', chapterTitle: '第7章|别人犯的错误', sectionTitle: '在Soul找恋爱但想赚钱的人', dir: '第三篇|真实的错误/第7章|别人犯的错误', file: '7.3 在Soul找恋爱但想赚钱的人.md', sortOrder: 31 }, + { id: '7.4', partId: 'part-3', partTitle: '第三篇|真实的错误', chapterId: 'chapter-7', chapterTitle: '第7章|别人犯的错误', sectionTitle: '创业者的三种死法:冲动、轻信、没结构', dir: '第三篇|真实的错误/第7章|别人犯的错误', file: '7.4 创业者的三种死法:冲动、轻信、没结构.md', sortOrder: 32 }, + { id: '7.5', partId: 'part-3', partTitle: '第三篇|真实的错误', chapterId: 'chapter-7', chapterTitle: '第7章|别人犯的错误', sectionTitle: '人情生意的终点:关系越多亏得越多', dir: '第三篇|真实的错误/第7章|别人犯的错误', file: '7.5 人情生意的终点:关系越多亏得越多.md', sortOrder: 33 }, + + // 第四篇 真实的赚钱 + { id: '8.1', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-8', chapterTitle: '第8章|底层结构', sectionTitle: '流量杠杆:抖音、Soul、飞书', dir: '第四篇|真实的赚钱/第8章|底层结构', file: '8.1 流量杠杆:抖音、Soul、飞书.md', sortOrder: 34 }, + { id: '8.2', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-8', chapterTitle: '第8章|底层结构', sectionTitle: '价格杠杆:供应链与信息差', dir: '第四篇|真实的赚钱/第8章|底层结构', file: '8.2 价格杠杆:供应链与信息差.md', sortOrder: 35 }, + { id: '8.3', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-8', chapterTitle: '第8章|底层结构', sectionTitle: '时间杠杆:自动化 + AI', dir: '第四篇|真实的赚钱/第8章|底层结构', file: '8.3 时间杠杆:自动化 + AI.md', sortOrder: 36 }, + { id: '8.4', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-8', chapterTitle: '第8章|底层结构', sectionTitle: '情绪杠杆:咨询、婚恋、生意场', dir: '第四篇|真实的赚钱/第8章|底层结构', file: '8.4 情绪杠杆:咨询、婚恋、生意场.md', sortOrder: 37 }, + { id: '8.5', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-8', chapterTitle: '第8章|底层结构', sectionTitle: '社交杠杆:认识谁比你会什么更重要', dir: '第四篇|真实的赚钱/第8章|底层结构', file: '8.5 社交杠杆:认识谁比你会什么更重要.md', sortOrder: 38 }, + { id: '8.6', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-8', chapterTitle: '第8章|底层结构', sectionTitle: '云阿米巴:分不属于自己的钱', dir: '第四篇|真实的赚钱/第8章|底层结构', file: '8.6 云阿米巴:分不属于自己的钱.md', sortOrder: 39 }, + + { id: '9.1', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '游戏账号私域:账号即资产', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.1 游戏账号私域:账号即资产.md', sortOrder: 40 }, + { id: '9.2', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '健康包模式:高复购、高毛利', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.2 健康包模式:高复购、高毛利.md', sortOrder: 41 }, + { id: '9.3', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '药物私域:长期关系赛道', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.3 药物私域:长期关系赛道.md', sortOrder: 42 }, + { id: '9.4', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '残疾机构合作:退税 × AI × 人力成本', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.4 残疾机构合作:退税 × AI × 人力成本.md', sortOrder: 43 }, + { id: '9.5', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '私域银行:粉丝即小股东', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.5 私域银行:粉丝即小股东.md', sortOrder: 44 }, + { id: '9.6', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: 'Soul派对房:陌生人成交的最快场景', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.6 Soul派对房:陌生人成交的最快场景.md', sortOrder: 45 }, + { id: '9.7', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '飞书中台:从聊天到成交的流程化体系', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.7 飞书中台:从聊天到成交的流程化体系.md', sortOrder: 46 }, + { id: '9.8', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '餐饮女孩:6万营收、1万利润的死撑生意', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.8 餐饮女孩:6万营收、1万利润的死撑生意.md', sortOrder: 47 }, + { id: '9.9', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '电竞生态:从陪玩到签约到酒店的完整链条', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.9 电竞生态:从陪玩到签约到酒店的完整链条.md', sortOrder: 48 }, + { id: '9.10', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '淘客大佬:损耗30%的白色通道', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.10 淘客大佬:损耗30%的白色通道.md', sortOrder: 49 }, + { id: '9.11', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '蔬菜供应链:农户才是最赚钱的人', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.11 蔬菜供应链:农户才是最赚钱的人.md', sortOrder: 50 }, + { id: '9.12', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '美业整合:一个人的公司如何月入十万', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.12 美业整合:一个人的公司如何月入十万.md', sortOrder: 51 }, + { id: '9.13', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: 'AI工具推广:一个隐藏的高利润赛道', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.13 AI工具推广:一个隐藏的高利润赛道.md', sortOrder: 52 }, + { id: '9.14', partId: 'part-4', partTitle: '第四篇|真实的赚钱', chapterId: 'chapter-9', chapterTitle: '第9章|我在Soul上亲访的赚钱案例', sectionTitle: '大健康私域:一个月150万的70后', dir: '第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例', file: '9.14 大健康私域:一个月150万的70后.md', sortOrder: 53 }, + + // 第五篇 真实的社会 + { id: '10.1', partId: 'part-5', partTitle: '第五篇|真实的社会', chapterId: 'chapter-10', chapterTitle: '第10章|未来职业的变化趋势', sectionTitle: 'AI时代:哪些工作会消失,哪些会崛起', dir: '第五篇|真实的社会/第10章|未来职业的变化趋势', file: '10.1 AI时代:哪些工作会消失,哪些会崛起.md', sortOrder: 54 }, + { id: '10.2', partId: 'part-5', partTitle: '第五篇|真实的社会', chapterId: 'chapter-10', chapterTitle: '第10章|未来职业的变化趋势', sectionTitle: '一人公司:为什么越来越多人选择单干', dir: '第五篇|真实的社会/第10章|未来职业的变化趋势', file: '10.2 一人公司:为什么越来越多人选择单干.md', sortOrder: 55 }, + { id: '10.3', partId: 'part-5', partTitle: '第五篇|真实的社会', chapterId: 'chapter-10', chapterTitle: '第10章|未来职业的变化趋势', sectionTitle: '为什么链接能力会成为第一价值', dir: '第五篇|真实的社会/第10章|未来职业的变化趋势', file: '10.3 为什么链接能力会成为第一价值.md', sortOrder: 56 }, + { id: '10.4', partId: 'part-5', partTitle: '第五篇|真实的社会', chapterId: 'chapter-10', chapterTitle: '第10章|未来职业的变化趋势', sectionTitle: '新型公司:Soul-飞书-线下的三位一体', dir: '第五篇|真实的社会/第10章|未来职业的变化趋势', file: '10.4 新型公司:Soul-飞书-线下的三位一体.md', sortOrder: 57 }, + + { id: '11.1', partId: 'part-5', partTitle: '第五篇|真实的社会', chapterId: 'chapter-11', chapterTitle: '第11章|中国社会商业生态的未来', sectionTitle: '私域经济:为什么流量越来越贵', dir: '第五篇|真实的社会/第11章|中国社会商业生态的未来', file: '11.1 私域经济:为什么流量越来越贵.md', sortOrder: 58 }, + { id: '11.2', partId: 'part-5', partTitle: '第五篇|真实的社会', chapterId: 'chapter-11', chapterTitle: '第11章|中国社会商业生态的未来', sectionTitle: '银发经济与孤独经济:两个被忽视的万亿市场', dir: '第五篇|真实的社会/第11章|中国社会商业生态的未来', file: '11.2 银发经济与孤独经济:两个被忽视的万亿市场.md', sortOrder: 59 }, + { id: '11.3', partId: 'part-5', partTitle: '第五篇|真实的社会', chapterId: 'chapter-11', chapterTitle: '第11章|中国社会商业生态的未来', sectionTitle: '流量红利的终局', dir: '第五篇|真实的社会/第11章|中国社会商业生态的未来', file: '11.3 流量红利的终局.md', sortOrder: 60 }, + { id: '11.4', partId: 'part-5', partTitle: '第五篇|真实的社会', chapterId: 'chapter-11', chapterTitle: '第11章|中国社会商业生态的未来', sectionTitle: '大模型 + 供应链的组合拳', dir: '第五篇|真实的社会/第11章|中国社会商业生态的未来', file: '11.4 大模型 + 供应链的组合拳.md', sortOrder: 61 }, + { id: '11.5', partId: 'part-5', partTitle: '第五篇|真实的社会', chapterId: 'chapter-11', chapterTitle: '第11章|中国社会商业生态的未来', sectionTitle: '社会分层的最终逻辑', dir: '第五篇|真实的社会/第11章|中国社会商业生态的未来', file: '11.5 社会分层的最终逻辑.md', sortOrder: 62 }, + + // 尾声 + { id: 'epilogue', partId: 'outro', partTitle: '尾声', chapterId: 'epilogue', chapterTitle: '尾声', sectionTitle: '这本书的真实目的', dir: '', file: '尾声|这本书的真实目的.md', sortOrder: 63 }, + + // 附录 + { id: 'appendix-1', partId: 'appendix', partTitle: '附录', chapterId: 'appendix', chapterTitle: '附录', sectionTitle: 'Soul派对房精选对话', dir: '附录', file: '附录1|Soul派对房精选对话.md', sortOrder: 64 }, + { id: 'appendix-2', partId: 'appendix', partTitle: '附录', chapterId: 'appendix', chapterTitle: '附录', sectionTitle: '创业者自检清单', dir: '附录', file: '附录2|创业者自检清单.md', sortOrder: 65 }, + { id: 'appendix-3', partId: 'appendix', partTitle: '附录', chapterId: 'appendix', chapterTitle: '附录', sectionTitle: '本书提到的工具和资源', dir: '附录', file: '附录3|本书提到的工具和资源.md', sortOrder: 66 }, +] + +async function main() { + console.log('🚀 开始迁移章节内容到数据库...') + console.log(`📁 Book目录: ${BOOK_DIR}`) + console.log(`📊 共${CHAPTERS_CONFIG.length}个章节\n`) + + // 连接数据库 + const connection = await mysql.createConnection(DB_CONFIG) + console.log('✅ 数据库连接成功\n') + + // 创建表(如果不存在) + await connection.execute(` + CREATE TABLE IF NOT EXISTS chapters ( + id VARCHAR(20) PRIMARY KEY COMMENT '章节ID,如1.1、preface等', + part_id VARCHAR(20) NOT NULL COMMENT '所属篇ID,如part-1', + part_title VARCHAR(100) NOT NULL COMMENT '篇标题,如第一篇|真实的人', + chapter_id VARCHAR(20) NOT NULL COMMENT '所属章ID,如chapter-1', + chapter_title VARCHAR(200) NOT NULL COMMENT '章标题,如第1章|人与人之间的底层逻辑', + section_title VARCHAR(200) NOT NULL COMMENT '节标题', + content LONGTEXT NOT NULL COMMENT '章节正文内容(Markdown格式)', + word_count INT DEFAULT 0 COMMENT '字数统计', + is_free BOOLEAN DEFAULT FALSE COMMENT '是否免费章节', + price DECIMAL(10,2) DEFAULT 1.00 COMMENT '单章价格', + sort_order INT DEFAULT 0 COMMENT '排序顺序', + status ENUM('draft', 'published', 'archived') DEFAULT 'published' COMMENT '状态', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_part_id (part_id), + INDEX idx_chapter_id (chapter_id), + INDEX idx_status (status), + INDEX idx_sort_order (sort_order) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `) + console.log('✅ chapters表创建/确认成功\n') + + // 迁移章节 + let successCount = 0 + let errorCount = 0 + + for (const config of CHAPTERS_CONFIG) { + const filePath = path.join(BOOK_DIR, config.dir, config.file) + + try { + if (!fs.existsSync(filePath)) { + console.log(`⚠️ 文件不存在: ${config.id} - ${config.file}`) + errorCount++ + continue + } + + const content = fs.readFileSync(filePath, 'utf-8') + const wordCount = content.replace(/\s/g, '').length + const isFree = FREE_CHAPTERS.includes(config.id) + + // 使用 REPLACE INTO 实现更新或插入 + await connection.execute(` + REPLACE INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, is_free, price, sort_order, status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'published') + `, [ + config.id, + config.partId, + config.partTitle, + config.chapterId, + config.chapterTitle, + config.sectionTitle, + content, + wordCount, + isFree, + isFree ? 0 : 1, + config.sortOrder + ]) + + console.log(`✅ ${config.id}: ${config.sectionTitle} (${wordCount}字${isFree ? ' 免费' : ''})`) + successCount++ + } catch (error) { + console.log(`❌ ${config.id}: ${error}`) + errorCount++ + } + } + + console.log('\n' + '='.repeat(50)) + console.log(`📊 迁移完成: 成功 ${successCount} 个, 失败 ${errorCount} 个`) + console.log('='.repeat(50)) + + // 查询验证 + const [rows] = await connection.execute('SELECT COUNT(*) as count FROM chapters') + console.log(`\n📚 数据库中共有 ${(rows as any)[0].count} 个章节`) + + await connection.end() + console.log('\n✅ 数据库连接已关闭') +} + +main().catch(console.error) diff --git a/开发文档/6、后端/image.png b/开发文档/6、后端/image.png new file mode 100644 index 0000000..b26ffbf Binary files /dev/null and b/开发文档/6、后端/image.png differ diff --git a/开发文档/6、后端/小程序支付参数.png b/开发文档/6、后端/小程序支付参数.png new file mode 100644 index 0000000..16d1881 Binary files /dev/null and b/开发文档/6、后端/小程序支付参数.png differ