2026-01-23 16:31:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 数据库连接配置
|
|
|
|
|
|
* 使用MySQL数据库存储用户、订单、推广关系等数据
|
2026-02-20 18:50:16 +08:00
|
|
|
|
* 优先从环境变量读取,便于本地/部署分离;未设置时使用默认值
|
2026-01-23 16:31:54 +08:00
|
|
|
|
*/
|
2026-01-21 15:49:12 +08:00
|
|
|
|
|
|
|
|
|
|
import mysql from 'mysql2/promise'
|
|
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
const DB_CONFIG = {
|
2026-02-20 18:50:16 +08:00
|
|
|
|
host: process.env.MYSQL_HOST || '56b4c23f6853c.gz.cdb.myqcloud.com',
|
|
|
|
|
|
port: Number(process.env.MYSQL_PORT || '14413'),
|
|
|
|
|
|
user: process.env.MYSQL_USER || 'cdb_outerroot',
|
|
|
|
|
|
password: process.env.MYSQL_PASSWORD || 'Zhiqun1984',
|
|
|
|
|
|
database: process.env.MYSQL_DATABASE || 'soul_miniprogram',
|
2026-01-23 16:31:54 +08:00
|
|
|
|
charset: 'utf8mb4',
|
|
|
|
|
|
timezone: '+08:00',
|
2026-02-20 18:50:16 +08:00
|
|
|
|
connectTimeout: 10000, // 10 秒,连接不可达时快速失败,避免长时间挂起
|
|
|
|
|
|
acquireTimeout: 15000,
|
2026-01-23 16:31:54 +08:00
|
|
|
|
reconnect: true
|
2026-01-21 15:49:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-20 18:50:16 +08:00
|
|
|
|
// 本地无数据库时可通过 SKIP_DB=1 跳过连接,接口将使用默认配置
|
|
|
|
|
|
const SKIP_DB = process.env.SKIP_DB === '1' || process.env.SKIP_DB === 'true'
|
|
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
// 连接池
|
2026-01-21 15:49:12 +08:00
|
|
|
|
let pool: mysql.Pool | null = null
|
2026-02-20 18:50:16 +08:00
|
|
|
|
// 连接类错误只打一次日志,避免刷屏
|
|
|
|
|
|
let connectionErrorLogged = false
|
|
|
|
|
|
|
|
|
|
|
|
function isConnectionError(err: unknown): boolean {
|
|
|
|
|
|
const code = (err as NodeJS.ErrnoException)?.code
|
|
|
|
|
|
return (
|
|
|
|
|
|
code === 'ETIMEDOUT' ||
|
|
|
|
|
|
code === 'ECONNREFUSED' ||
|
|
|
|
|
|
code === 'PROTOCOL_CONNECTION_LOST' ||
|
|
|
|
|
|
code === 'ENOTFOUND'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2026-01-21 15:49:12 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
/**
|
2026-02-20 18:50:16 +08:00
|
|
|
|
* 获取数据库连接池(SKIP_DB 时不创建)
|
2026-01-23 16:31:54 +08:00
|
|
|
|
*/
|
2026-02-20 18:50:16 +08:00
|
|
|
|
export function getPool(): mysql.Pool | null {
|
|
|
|
|
|
if (SKIP_DB) return null
|
2026-01-21 15:49:12 +08:00
|
|
|
|
if (!pool) {
|
2026-01-23 16:31:54 +08:00
|
|
|
|
pool = mysql.createPool({
|
|
|
|
|
|
...DB_CONFIG,
|
|
|
|
|
|
waitForConnections: true,
|
|
|
|
|
|
connectionLimit: 10,
|
|
|
|
|
|
queueLimit: 0
|
|
|
|
|
|
})
|
2026-01-21 15:49:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
return pool
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 执行SQL查询
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function query(sql: string, params?: any[]) {
|
2026-02-20 18:50:16 +08:00
|
|
|
|
const connection = getPool()
|
|
|
|
|
|
if (!connection) {
|
|
|
|
|
|
throw new Error('数据库未配置或已跳过 (SKIP_DB)')
|
|
|
|
|
|
}
|
|
|
|
|
|
// mysql2 内部会读 params.length,不能传 undefined
|
|
|
|
|
|
const safeParams = Array.isArray(params) ? params : []
|
2026-01-21 15:49:12 +08:00
|
|
|
|
try {
|
2026-02-20 18:50:16 +08:00
|
|
|
|
const [results] = await connection.execute(sql, safeParams)
|
|
|
|
|
|
// 确保调用方拿到的始终是数组,避免 undefined.length 报错
|
|
|
|
|
|
if (Array.isArray(results)) return results
|
|
|
|
|
|
if (results != null) return [results]
|
|
|
|
|
|
return []
|
2026-01-21 15:49:12 +08:00
|
|
|
|
} catch (error) {
|
2026-02-20 18:50:16 +08:00
|
|
|
|
if (isConnectionError(error)) {
|
|
|
|
|
|
if (!connectionErrorLogged) {
|
|
|
|
|
|
connectionErrorLogged = true
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
'[DB] 数据库连接不可用,将使用本地默认配置。',
|
|
|
|
|
|
(error as Error).message
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('数据库查询错误:', error)
|
|
|
|
|
|
}
|
2026-01-21 15:49:12 +08:00
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 初始化数据库表结构
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function initDatabase() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('开始初始化数据库表结构...')
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-25 19:37:59 +08:00
|
|
|
|
// 用户表(完整字段)
|
2026-01-23 16:31:54 +08:00
|
|
|
|
await query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
|
|
|
|
id VARCHAR(50) PRIMARY KEY,
|
2026-01-25 19:37:59 +08:00
|
|
|
|
open_id VARCHAR(100) UNIQUE,
|
|
|
|
|
|
session_key VARCHAR(100) COMMENT '微信session_key',
|
2026-01-23 16:31:54 +08:00
|
|
|
|
nickname VARCHAR(100),
|
|
|
|
|
|
avatar VARCHAR(500),
|
|
|
|
|
|
phone VARCHAR(20),
|
2026-01-25 19:37:59 +08:00
|
|
|
|
password VARCHAR(100) COMMENT '密码(可选)',
|
|
|
|
|
|
wechat_id VARCHAR(100) COMMENT '用户填写的微信号',
|
2026-01-23 16:31:54 +08:00
|
|
|
|
referral_code VARCHAR(20) UNIQUE,
|
2026-01-25 19:37:59 +08:00
|
|
|
|
referred_by VARCHAR(50) COMMENT '推荐人ID',
|
|
|
|
|
|
purchased_sections JSON DEFAULT '[]',
|
2026-01-23 16:31:54 +08:00
|
|
|
|
has_full_book BOOLEAN DEFAULT FALSE,
|
2026-01-25 19:37:59 +08:00
|
|
|
|
is_admin BOOLEAN DEFAULT FALSE COMMENT '是否管理员',
|
|
|
|
|
|
earnings DECIMAL(10,2) DEFAULT 0 COMMENT '已提现收益',
|
|
|
|
|
|
pending_earnings DECIMAL(10,2) DEFAULT 0 COMMENT '待提现收益',
|
|
|
|
|
|
withdrawn_earnings DECIMAL(10,2) DEFAULT 0 COMMENT '累计已提现',
|
|
|
|
|
|
referral_count INT DEFAULT 0 COMMENT '推广人数',
|
|
|
|
|
|
match_count_today INT DEFAULT 0 COMMENT '今日匹配次数',
|
|
|
|
|
|
last_match_date DATE COMMENT '最后匹配日期',
|
2026-01-23 16:31:54 +08:00
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
|
|
INDEX idx_open_id (open_id),
|
2026-01-25 19:37:59 +08:00
|
|
|
|
INDEX idx_phone (phone),
|
|
|
|
|
|
INDEX idx_referral_code (referral_code),
|
|
|
|
|
|
INDEX idx_referred_by (referred_by)
|
2026-01-23 16:31:54 +08:00
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
|
|
|
|
`)
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-25 19:37:59 +08:00
|
|
|
|
// 尝试添加可能缺失的字段(用于升级已有数据库)
|
2026-02-20 18:50:16 +08:00
|
|
|
|
// 兼容 MySQL 5.7:IF NOT EXISTS 在 5.7 不支持,先检查列是否存在
|
|
|
|
|
|
const addColumnIfMissing = async (colName: string, colDef: string) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const rows = await query(
|
|
|
|
|
|
"SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = ?",
|
|
|
|
|
|
[colName]
|
|
|
|
|
|
) as any[]
|
|
|
|
|
|
if (!rows?.length) {
|
|
|
|
|
|
await query(`ALTER TABLE users ADD COLUMN ${colName} ${colDef}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) { /* 忽略 */ }
|
|
|
|
|
|
}
|
|
|
|
|
|
await addColumnIfMissing('session_key', 'VARCHAR(100)')
|
|
|
|
|
|
await addColumnIfMissing('password', 'VARCHAR(100)')
|
|
|
|
|
|
await addColumnIfMissing('referred_by', 'VARCHAR(50)')
|
|
|
|
|
|
await addColumnIfMissing('is_admin', 'BOOLEAN DEFAULT FALSE')
|
|
|
|
|
|
await addColumnIfMissing('match_count_today', 'INT DEFAULT 0')
|
|
|
|
|
|
await addColumnIfMissing('last_match_date', 'DATE')
|
|
|
|
|
|
await addColumnIfMissing('withdrawn_earnings', 'DECIMAL(10,2) DEFAULT 0')
|
2026-02-23 14:07:41 +08:00
|
|
|
|
await addColumnIfMissing('is_vip', "BOOLEAN DEFAULT FALSE COMMENT 'VIP会员'")
|
|
|
|
|
|
await addColumnIfMissing('vip_expire_date', "TIMESTAMP NULL COMMENT 'VIP到期时间'")
|
|
|
|
|
|
await addColumnIfMissing('vip_name', "VARCHAR(100) COMMENT '会员真实姓名'")
|
|
|
|
|
|
await addColumnIfMissing('vip_project', "VARCHAR(200) COMMENT '会员项目名称'")
|
|
|
|
|
|
await addColumnIfMissing('vip_contact', "VARCHAR(100) COMMENT '会员联系方式'")
|
|
|
|
|
|
await addColumnIfMissing('vip_avatar', "VARCHAR(500) COMMENT '会员展示头像'")
|
|
|
|
|
|
await addColumnIfMissing('vip_bio', "VARCHAR(500) COMMENT '会员简介'")
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-25 19:37:59 +08:00
|
|
|
|
console.log('用户表初始化完成')
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
|
|
|
|
|
// 订单表(含 referrer_id/referral_code、status 含 created/expired)
|
2026-01-23 16:31:54 +08:00
|
|
|
|
await query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS orders (
|
|
|
|
|
|
id VARCHAR(50) PRIMARY KEY,
|
|
|
|
|
|
order_sn VARCHAR(50) UNIQUE NOT NULL,
|
|
|
|
|
|
user_id VARCHAR(50) NOT NULL,
|
|
|
|
|
|
open_id VARCHAR(100) NOT NULL,
|
2026-02-23 14:07:41 +08:00
|
|
|
|
product_type ENUM('section', 'fullbook', 'match', 'vip') NOT NULL,
|
2026-01-23 16:31:54 +08:00
|
|
|
|
product_id VARCHAR(50),
|
|
|
|
|
|
amount DECIMAL(10,2) NOT NULL,
|
|
|
|
|
|
description VARCHAR(200),
|
2026-02-20 18:50:16 +08:00
|
|
|
|
status ENUM('created', 'pending', 'paid', 'cancelled', 'refunded', 'expired') DEFAULT 'created',
|
2026-01-23 16:31:54 +08:00
|
|
|
|
transaction_id VARCHAR(100),
|
|
|
|
|
|
pay_time TIMESTAMP NULL,
|
2026-02-20 18:50:16 +08:00
|
|
|
|
referrer_id VARCHAR(50) NULL COMMENT '推荐人用户ID,用于分销归属',
|
|
|
|
|
|
referral_code VARCHAR(20) NULL COMMENT '下单时使用的邀请码,便于对账与展示',
|
2026-01-23 16:31:54 +08:00
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
|
|
FOREIGN KEY (user_id) REFERENCES users(id),
|
|
|
|
|
|
INDEX idx_order_sn (order_sn),
|
|
|
|
|
|
INDEX idx_user_id (user_id),
|
|
|
|
|
|
INDEX idx_status (status)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
|
|
|
|
`)
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
// 推广绑定关系表
|
|
|
|
|
|
await query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS referral_bindings (
|
|
|
|
|
|
id VARCHAR(50) PRIMARY KEY,
|
|
|
|
|
|
referrer_id VARCHAR(50) NOT NULL,
|
|
|
|
|
|
referee_id VARCHAR(50) NOT NULL,
|
|
|
|
|
|
referral_code VARCHAR(20) NOT NULL,
|
|
|
|
|
|
status ENUM('active', 'converted', 'expired') DEFAULT 'active',
|
|
|
|
|
|
binding_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
expiry_date TIMESTAMP NOT NULL,
|
|
|
|
|
|
conversion_date TIMESTAMP NULL,
|
|
|
|
|
|
commission_amount DECIMAL(10,2) DEFAULT 0,
|
|
|
|
|
|
order_id VARCHAR(50) NULL,
|
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
|
|
FOREIGN KEY (referrer_id) REFERENCES users(id),
|
|
|
|
|
|
FOREIGN KEY (referee_id) REFERENCES users(id),
|
|
|
|
|
|
FOREIGN KEY (order_id) REFERENCES orders(id),
|
|
|
|
|
|
UNIQUE KEY unique_referrer_referee (referrer_id, referee_id),
|
|
|
|
|
|
INDEX idx_referrer_id (referrer_id),
|
|
|
|
|
|
INDEX idx_referee_id (referee_id),
|
|
|
|
|
|
INDEX idx_status (status),
|
|
|
|
|
|
INDEX idx_expiry_date (expiry_date)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
|
|
|
|
`)
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
// 匹配记录表
|
|
|
|
|
|
await query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS match_records (
|
|
|
|
|
|
id VARCHAR(50) PRIMARY KEY,
|
|
|
|
|
|
user_id VARCHAR(50) NOT NULL,
|
|
|
|
|
|
match_type ENUM('partner', 'investor', 'mentor', 'team') NOT NULL,
|
|
|
|
|
|
phone VARCHAR(20),
|
|
|
|
|
|
wechat_id VARCHAR(100),
|
|
|
|
|
|
matched_user_id VARCHAR(50),
|
|
|
|
|
|
match_score INT,
|
|
|
|
|
|
status ENUM('pending', 'matched', 'contacted') DEFAULT 'pending',
|
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
FOREIGN KEY (user_id) REFERENCES users(id),
|
|
|
|
|
|
INDEX idx_user_id (user_id),
|
|
|
|
|
|
INDEX idx_match_type (match_type),
|
|
|
|
|
|
INDEX idx_status (status)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
2026-01-29 09:47:04 +08:00
|
|
|
|
`)
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 推广访问记录表(用于统计「通过链接进的人数」)
|
|
|
|
|
|
await query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS referral_visits (
|
|
|
|
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
|
referrer_id VARCHAR(50) NOT NULL COMMENT '推广者ID',
|
|
|
|
|
|
visitor_id VARCHAR(50) COMMENT '访客ID(可能为空)',
|
|
|
|
|
|
visitor_openid VARCHAR(100) COMMENT '访客openId',
|
|
|
|
|
|
source VARCHAR(50) DEFAULT 'miniprogram' COMMENT '来源:miniprogram/web/share',
|
|
|
|
|
|
page VARCHAR(200) COMMENT '落地页路径',
|
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
INDEX idx_referrer_id (referrer_id),
|
|
|
|
|
|
INDEX idx_visitor_id (visitor_id),
|
|
|
|
|
|
INDEX idx_created_at (created_at)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
2026-01-23 16:31:54 +08:00
|
|
|
|
`)
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
// 系统配置表
|
|
|
|
|
|
await query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS system_config (
|
|
|
|
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
|
config_key VARCHAR(100) UNIQUE NOT NULL,
|
|
|
|
|
|
config_value JSON NOT NULL,
|
|
|
|
|
|
description VARCHAR(200),
|
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
|
|
INDEX idx_config_key (config_key)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
|
|
|
|
`)
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-25 09:57:21 +08:00
|
|
|
|
// 章节内容表 - 存储书籍所有章节
|
|
|
|
|
|
await query(`
|
|
|
|
|
|
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
|
|
|
|
|
|
`)
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
console.log('数据库表结构初始化完成')
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
// 插入默认配置
|
|
|
|
|
|
await initDefaultConfig()
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('初始化数据库失败:', error)
|
|
|
|
|
|
throw error
|
2026-01-21 15:49:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 初始化默认配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function initDefaultConfig() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 匹配类型配置
|
|
|
|
|
|
const matchConfig = {
|
|
|
|
|
|
matchTypes: [
|
|
|
|
|
|
{ id: 'partner', label: '创业合伙', matchLabel: '创业伙伴', icon: '⭐', matchFromDB: true, showJoinAfterMatch: false, enabled: true },
|
|
|
|
|
|
{ id: 'investor', label: '资源对接', matchLabel: '资源对接', icon: '👥', matchFromDB: false, showJoinAfterMatch: true, enabled: true },
|
|
|
|
|
|
{ id: 'mentor', label: '导师顾问', matchLabel: '商业顾问', icon: '❤️', matchFromDB: false, showJoinAfterMatch: true, enabled: true },
|
|
|
|
|
|
{ id: 'team', label: '团队招募', matchLabel: '加入项目', icon: '🎮', matchFromDB: false, showJoinAfterMatch: true, enabled: true }
|
|
|
|
|
|
],
|
|
|
|
|
|
freeMatchLimit: 3,
|
|
|
|
|
|
matchPrice: 1,
|
|
|
|
|
|
settings: {
|
|
|
|
|
|
enableFreeMatches: true,
|
|
|
|
|
|
enablePaidMatches: true,
|
|
|
|
|
|
maxMatchesPerDay: 10
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
await query(`
|
2026-02-20 18:50:16 +08:00
|
|
|
|
INSERT INTO system_config (config_key, config_value, description)
|
|
|
|
|
|
VALUES (?, ?, ?)
|
2026-01-23 16:31:54 +08:00
|
|
|
|
ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)
|
|
|
|
|
|
`, ['match_config', JSON.stringify(matchConfig), '匹配功能配置'])
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
// 推广配置
|
|
|
|
|
|
const referralConfig = {
|
|
|
|
|
|
distributorShare: 90, // 推广者分成比例
|
|
|
|
|
|
minWithdrawAmount: 10, // 最小提现金额
|
|
|
|
|
|
bindingDays: 30, // 绑定有效期(天)
|
|
|
|
|
|
userDiscount: 5 // 用户优惠比例
|
|
|
|
|
|
}
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
await query(`
|
2026-02-20 18:50:16 +08:00
|
|
|
|
INSERT INTO system_config (config_key, config_value, description)
|
|
|
|
|
|
VALUES (?, ?, ?)
|
2026-01-23 16:31:54 +08:00
|
|
|
|
ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)
|
|
|
|
|
|
`, ['referral_config', JSON.stringify(referralConfig), '推广功能配置'])
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
console.log('默认配置初始化完成')
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('初始化默认配置失败:', error)
|
2026-01-21 15:49:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取系统配置
|
2026-02-20 18:50:16 +08:00
|
|
|
|
* 连接不可达时返回 null,由上层使用本地默认配置,不重复打日志
|
2026-01-23 16:31:54 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export async function getConfig(key: string) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const results = await query(
|
|
|
|
|
|
'SELECT config_value FROM system_config WHERE config_key = ?',
|
|
|
|
|
|
[key]
|
2026-02-20 18:50:16 +08:00
|
|
|
|
)
|
|
|
|
|
|
const rows = Array.isArray(results) ? results : (results != null ? [results] : [])
|
|
|
|
|
|
if (rows != null && rows.length > 0) {
|
|
|
|
|
|
return (rows[0] as any)?.config_value ?? null
|
2026-01-23 16:31:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
return null
|
|
|
|
|
|
} catch (error) {
|
2026-02-20 18:50:16 +08:00
|
|
|
|
if (!isConnectionError(error)) {
|
|
|
|
|
|
console.error('获取配置失败:', error)
|
|
|
|
|
|
}
|
2026-01-23 16:31:54 +08:00
|
|
|
|
return null
|
2026-01-21 15:49:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 设置系统配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function setConfig(key: string, value: any, description?: string) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await query(`
|
2026-02-20 18:50:16 +08:00
|
|
|
|
INSERT INTO system_config (config_key, config_value, description)
|
|
|
|
|
|
VALUES (?, ?, ?)
|
|
|
|
|
|
ON DUPLICATE KEY UPDATE
|
2026-01-23 16:31:54 +08:00
|
|
|
|
config_value = VALUES(config_value),
|
|
|
|
|
|
description = COALESCE(VALUES(description), description)
|
|
|
|
|
|
`, [key, JSON.stringify(value), description])
|
2026-02-20 18:50:16 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
return true
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('设置配置失败:', error)
|
|
|
|
|
|
return false
|
2026-01-21 15:49:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
// 导出数据库实例
|
2026-02-20 18:50:16 +08:00
|
|
|
|
export default { getPool, query, initDatabase, getConfig, setConfig }
|