Files
soul/lib/db.ts
卡若 b60edb3d47 feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

2. 修复所有卡片对齐和宽度问题
   - 目录页附录卡片居中
   - 首页阅读进度卡片满宽度
   - 我的页面菜单卡片对齐
   - 推广中心分享卡片统一宽度

3. 修复目录页图标和文字对齐
   - section-icon固定40rpx宽高
   - section-title与图标垂直居中

4. 更新真实完整文章标题(62篇)
   - 从book目录读取真实markdown文件名
   - 替换之前的简化标题

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
2026-01-21 15:49:12 +08:00

512 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 数据库连接配置
// 使用腾讯云数据库
import mysql from 'mysql2/promise'
// 数据库配置不含database用于创建数据库
const dbConfigWithoutDB = {
host: '56b4c23f6853c.gz.cdb.myqcloud.com',
port: 14413,
user: 'cdb_outerroot',
password: 'Zhiqun1984',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
}
// 数据库配置含database
const dbConfig = {
...dbConfigWithoutDB,
database: 'soul_experiment',
}
// 创建连接池
let pool: mysql.Pool | null = null
export function getPool() {
if (!pool) {
pool = mysql.createPool(dbConfig)
}
return pool
}
// 创建数据库(如果不存在)
export async function createDatabaseIfNotExists() {
const conn = await mysql.createConnection(dbConfigWithoutDB)
try {
await conn.execute('CREATE DATABASE IF NOT EXISTS soul_experiment CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci')
console.log('Database soul_experiment created or already exists')
return true
} catch (error) {
console.error('Error creating database:', error)
throw error
} finally {
await conn.end()
}
}
// 执行查询
export async function query<T = any>(sql: string, params?: any[]): Promise<T[]> {
const pool = getPool()
const [rows] = await pool.execute(sql, params)
return rows as T[]
}
// 执行单条插入/更新/删除
export async function execute(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
const pool = getPool()
const [result] = await pool.execute(sql, params)
return result as mysql.ResultSetHeader
}
// 用户相关操作
export const userDB = {
// 获取所有用户
async getAll() {
return query(`SELECT * FROM users ORDER BY created_at DESC`)
},
// 根据ID获取用户
async getById(id: string) {
const rows = await query(`SELECT * FROM users WHERE id = ?`, [id])
return rows[0] || null
},
// 根据手机号获取用户
async getByPhone(phone: string) {
const rows = await query(`SELECT * FROM users WHERE phone = ?`, [phone])
return rows[0] || null
},
// 创建用户
async create(user: {
id: string
phone: string
nickname: string
password?: string
is_admin?: boolean
referral_code: string
referred_by?: string
}) {
await execute(
`INSERT INTO users (id, phone, nickname, password, is_admin, referral_code, referred_by, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`,
[user.id, user.phone, user.nickname, user.password || '', user.is_admin || false, user.referral_code, user.referred_by || null]
)
return user
},
// 更新用户
async update(id: string, updates: Partial<{
nickname: string
password: string
is_admin: boolean
has_full_book: boolean
earnings: number
pending_earnings: number
withdrawn_earnings: number
referral_count: number
match_count_today: number
last_match_date: string
}>) {
const fields: string[] = []
const values: any[] = []
Object.entries(updates).forEach(([key, value]) => {
if (value !== undefined) {
fields.push(`${key} = ?`)
values.push(value)
}
})
if (fields.length === 0) return
values.push(id)
await execute(`UPDATE users SET ${fields.join(', ')} WHERE id = ?`, values)
},
// 删除用户
async delete(id: string) {
await execute(`DELETE FROM users WHERE id = ?`, [id])
},
// 验证密码
async verifyPassword(phone: string, password: string) {
const rows = await query(`SELECT * FROM users WHERE phone = ? AND password = ?`, [phone, password])
return rows[0] || null
},
// 更新匹配次数
async updateMatchCount(userId: string) {
const today = new Date().toISOString().split('T')[0]
const user = await this.getById(userId)
if (user?.last_match_date === today) {
await execute(
`UPDATE users SET match_count_today = match_count_today + 1 WHERE id = ?`,
[userId]
)
} else {
await execute(
`UPDATE users SET match_count_today = 1, last_match_date = ? WHERE id = ?`,
[today, userId]
)
}
},
// 获取今日匹配次数
async getMatchCount(userId: string) {
const today = new Date().toISOString().split('T')[0]
const rows = await query(
`SELECT match_count_today FROM users WHERE id = ? AND last_match_date = ?`,
[userId, today]
)
return rows[0]?.match_count_today || 0
}
}
// 购买记录相关操作
export const purchaseDB = {
async getAll() {
return query(`SELECT * FROM purchases ORDER BY created_at DESC`)
},
async getByUserId(userId: string) {
return query(`SELECT * FROM purchases WHERE user_id = ? ORDER BY created_at DESC`, [userId])
},
async create(purchase: {
id: string
user_id: string
type: 'section' | 'fullbook' | 'match'
section_id?: string
section_title?: string
amount: number
payment_method?: string
referral_code?: string
referrer_earnings?: number
status: string
}) {
await execute(
`INSERT INTO purchases (id, user_id, type, section_id, section_title, amount, payment_method, referral_code, referrer_earnings, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`,
[purchase.id, purchase.user_id, purchase.type, purchase.section_id || null, purchase.section_title || null,
purchase.amount, purchase.payment_method || null, purchase.referral_code || null, purchase.referrer_earnings || 0, purchase.status]
)
return purchase
}
}
// 分销绑定相关操作
export const distributionDB = {
async getAllBindings() {
return query(`SELECT * FROM referral_bindings ORDER BY bound_at DESC`)
},
async getBindingsByReferrer(referrerId: string) {
return query(`SELECT * FROM referral_bindings WHERE referrer_id = ? ORDER BY bound_at DESC`, [referrerId])
},
async createBinding(binding: {
id: string
referrer_id: string
referee_id: string
referrer_code: string
bound_at: string
expires_at: string
status: string
}) {
await execute(
`INSERT INTO referral_bindings (id, referrer_id, referee_id, referrer_code, bound_at, expires_at, status)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[binding.id, binding.referrer_id, binding.referee_id, binding.referrer_code, binding.bound_at, binding.expires_at, binding.status]
)
return binding
},
async updateBindingStatus(id: string, status: string) {
await execute(`UPDATE referral_bindings SET status = ? WHERE id = ?`, [status, id])
},
async getActiveBindingByReferee(refereeId: string) {
const rows = await query(
`SELECT * FROM referral_bindings WHERE referee_id = ? AND status = 'active' AND expires_at > NOW()`,
[refereeId]
)
return rows[0] || null
},
// 佣金记录
async getAllCommissions() {
return query(`SELECT * FROM distribution_commissions ORDER BY created_at DESC`)
},
async createCommission(commission: {
id: string
binding_id: string
referrer_id: string
referee_id: string
order_id: string
amount: number
commission_rate: number
commission_amount: number
status: string
}) {
await execute(
`INSERT INTO distribution_commissions (id, binding_id, referrer_id, referee_id, order_id, amount, commission_rate, commission_amount, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`,
[commission.id, commission.binding_id, commission.referrer_id, commission.referee_id, commission.order_id,
commission.amount, commission.commission_rate, commission.commission_amount, commission.status]
)
return commission
}
}
// 提现记录相关操作
export const withdrawalDB = {
async getAll() {
return query(`SELECT * FROM withdrawals ORDER BY created_at DESC`)
},
async getByUserId(userId: string) {
return query(`SELECT * FROM withdrawals WHERE user_id = ? ORDER BY created_at DESC`, [userId])
},
async create(withdrawal: {
id: string
user_id: string
amount: number
method: string
account: string
name: string
status: string
}) {
await execute(
`INSERT INTO withdrawals (id, user_id, amount, method, account, name, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`,
[withdrawal.id, withdrawal.user_id, withdrawal.amount, withdrawal.method, withdrawal.account, withdrawal.name, withdrawal.status]
)
return withdrawal
},
async updateStatus(id: string, status: string) {
const completedAt = status === 'completed' ? ', completed_at = NOW()' : ''
await execute(`UPDATE withdrawals SET status = ?${completedAt} WHERE id = ?`, [status, id])
}
}
// 系统设置相关操作
export const settingsDB = {
async get() {
const rows = await query(`SELECT * FROM settings WHERE id = 1`)
return rows[0] || null
},
async update(settings: Record<string, any>) {
const json = JSON.stringify(settings)
await execute(
`INSERT INTO settings (id, data, updated_at) VALUES (1, ?, NOW())
ON DUPLICATE KEY UPDATE data = ?, updated_at = NOW()`,
[json, json]
)
}
}
// book内容相关操作
export const bookDB = {
async getAllSections() {
return query(`SELECT * FROM book_sections ORDER BY sort_order ASC`)
},
async getSection(id: string) {
const rows = await query(`SELECT * FROM book_sections WHERE id = ?`, [id])
return rows[0] || null
},
async updateSection(id: string, updates: { title?: string; content?: string; price?: number; is_free?: boolean }) {
const fields: string[] = []
const values: any[] = []
Object.entries(updates).forEach(([key, value]) => {
if (value !== undefined) {
fields.push(`${key} = ?`)
values.push(value)
}
})
if (fields.length === 0) return
fields.push('updated_at = NOW()')
values.push(id)
await execute(`UPDATE book_sections SET ${fields.join(', ')} WHERE id = ?`, values)
},
async createSection(section: {
id: string
part_id: string
chapter_id: string
title: string
content: string
price: number
is_free: boolean
sort_order: number
}) {
await execute(
`INSERT INTO book_sections (id, part_id, chapter_id, title, content, price, is_free, sort_order, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
[section.id, section.part_id, section.chapter_id, section.title, section.content, section.price, section.is_free, section.sort_order]
)
return section
},
// 导出所有章节
async exportAll() {
const sections = await this.getAllSections()
return JSON.stringify(sections, null, 2)
},
// 导入章节
async importSections(sectionsJson: string) {
const sections = JSON.parse(sectionsJson)
for (const section of sections) {
const existing = await this.getSection(section.id)
if (existing) {
await this.updateSection(section.id, section)
} else {
await this.createSection(section)
}
}
return sections.length
}
}
// 初始化数据库表
export async function initDatabase() {
// 先创建数据库
await createDatabaseIfNotExists()
const pool = getPool()
// 用户表
await pool.execute(`
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(50) PRIMARY KEY,
phone VARCHAR(20) UNIQUE NOT NULL,
nickname VARCHAR(100) NOT NULL,
password VARCHAR(100) DEFAULT '',
is_admin BOOLEAN DEFAULT FALSE,
has_full_book BOOLEAN DEFAULT FALSE,
referral_code VARCHAR(20) UNIQUE,
referred_by VARCHAR(20),
earnings DECIMAL(10,2) DEFAULT 0,
pending_earnings DECIMAL(10,2) DEFAULT 0,
withdrawn_earnings DECIMAL(10,2) DEFAULT 0,
referral_count INT DEFAULT 0,
match_count_today INT DEFAULT 0,
last_match_date DATE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_phone (phone),
INDEX idx_referral_code (referral_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
// 购买记录表
await pool.execute(`
CREATE TABLE IF NOT EXISTS purchases (
id VARCHAR(50) PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
type ENUM('section', 'fullbook', 'match') NOT NULL,
section_id VARCHAR(20),
section_title VARCHAR(200),
amount DECIMAL(10,2) NOT NULL,
payment_method VARCHAR(20),
referral_code VARCHAR(20),
referrer_earnings DECIMAL(10,2) DEFAULT 0,
status ENUM('pending', 'completed', 'refunded') DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
// 分销绑定表
await pool.execute(`
CREATE TABLE IF NOT EXISTS referral_bindings (
id VARCHAR(50) PRIMARY KEY,
referrer_id VARCHAR(50) NOT NULL,
referee_id VARCHAR(50) NOT NULL,
referrer_code VARCHAR(20) NOT NULL,
bound_at DATETIME NOT NULL,
expires_at DATETIME NOT NULL,
status ENUM('active', 'converted', 'expired') DEFAULT 'active',
INDEX idx_referrer (referrer_id),
INDEX idx_referee (referee_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
// 分销佣金表
await pool.execute(`
CREATE TABLE IF NOT EXISTS distribution_commissions (
id VARCHAR(50) PRIMARY KEY,
binding_id VARCHAR(50) NOT NULL,
referrer_id VARCHAR(50) NOT NULL,
referee_id VARCHAR(50) NOT NULL,
order_id VARCHAR(50) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
commission_rate DECIMAL(5,2) NOT NULL,
commission_amount DECIMAL(10,2) NOT NULL,
status ENUM('pending', 'paid', 'cancelled') DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
paid_at DATETIME,
INDEX idx_referrer (referrer_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
// 提现记录表
await pool.execute(`
CREATE TABLE IF NOT EXISTS withdrawals (
id VARCHAR(50) PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
method ENUM('wechat', 'alipay') NOT NULL,
account VARCHAR(100) NOT NULL,
name VARCHAR(50) NOT NULL,
status ENUM('pending', 'completed', 'rejected') DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
completed_at DATETIME,
INDEX idx_user_id (user_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
// 系统设置表
await pool.execute(`
CREATE TABLE IF NOT EXISTS settings (
id INT PRIMARY KEY,
data JSON,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
// book章节表
await pool.execute(`
CREATE TABLE IF NOT EXISTS book_sections (
id VARCHAR(20) PRIMARY KEY,
part_id VARCHAR(20) NOT NULL,
chapter_id VARCHAR(20) NOT NULL,
title VARCHAR(200) NOT NULL,
content LONGTEXT,
price DECIMAL(10,2) DEFAULT 1,
is_free BOOLEAN DEFAULT FALSE,
sort_order INT DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_part (part_id),
INDEX idx_chapter (chapter_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
console.log('Database tables initialized successfully')
}