新增订单推荐人和邀请码功能,优化支付流程中的订单插入逻辑,确保订单记录准确。更新小程序支付请求,支持传递邀请码以便于分销归属和对账。同时,调整数据库结构以支持新字段,提升系统的稳定性和用户体验。
This commit is contained in:
@@ -1,367 +1,137 @@
|
||||
/**
|
||||
* 系统配置API
|
||||
* 优先读取数据库配置,失败时读取本地默认配置
|
||||
* 支持配置的增删改查
|
||||
* 优先 Prisma,失败时回退到 lib/db(避免 Prisma 连接池超时导致 500)
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { query, getConfig, setConfig } from '@/lib/db'
|
||||
|
||||
// 本地默认配置(作为数据库备份)
|
||||
const DEFAULT_CONFIGS: Record<string, any> = {
|
||||
// 站点配置
|
||||
site_config: {
|
||||
siteName: 'Soul创业派对',
|
||||
siteDescription: '来自派对房的真实商业故事',
|
||||
logo: '/icon.svg',
|
||||
keywords: ['创业', 'Soul', '私域运营', '商业案例'],
|
||||
icp: '',
|
||||
analytics: ''
|
||||
},
|
||||
|
||||
// 匹配功能配置
|
||||
match_config: {
|
||||
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
|
||||
}
|
||||
},
|
||||
|
||||
// 分销配置
|
||||
referral_config: {
|
||||
distributorShare: 90,
|
||||
minWithdrawAmount: 10,
|
||||
bindingDays: 30,
|
||||
userDiscount: 5,
|
||||
enableAutoWithdraw: false
|
||||
},
|
||||
|
||||
// 价格配置
|
||||
price_config: {
|
||||
sectionPrice: 1,
|
||||
fullBookPrice: 9.9,
|
||||
premiumBookPrice: 19.9,
|
||||
matchPrice: 1
|
||||
},
|
||||
|
||||
// 支付配置
|
||||
payment_config: {
|
||||
wechat: {
|
||||
enabled: true,
|
||||
appId: 'wx432c93e275548671',
|
||||
mchId: '1318592501'
|
||||
},
|
||||
alipay: {
|
||||
enabled: true,
|
||||
pid: '2088511801157159'
|
||||
},
|
||||
wechatGroupUrl: '' // 支付成功后跳转的微信群链接
|
||||
},
|
||||
|
||||
// 书籍配置
|
||||
book_config: {
|
||||
totalSections: 62,
|
||||
freeSections: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3'],
|
||||
latestSectionId: '9.14'
|
||||
},
|
||||
|
||||
// 功能开关配置
|
||||
feature_config: {
|
||||
matchEnabled: true, // 找伙伴功能开关(默认开启)
|
||||
referralEnabled: true, // 推广功能开关
|
||||
searchEnabled: true, // 搜索功能开关
|
||||
aboutEnabled: true // 关于页面开关
|
||||
}
|
||||
}
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { getPrismaConfig, setPrismaConfig } from '@/lib/prisma-helpers'
|
||||
import { query } from '@/lib/db'
|
||||
|
||||
/**
|
||||
* GET - 获取配置
|
||||
* 参数: key - 配置键名,不传则返回所有配置
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const key = searchParams.get('key')
|
||||
const forceLocal = searchParams.get('forceLocal') === 'true'
|
||||
|
||||
try {
|
||||
if (key) {
|
||||
// 获取单个配置
|
||||
let config = null
|
||||
|
||||
if (!forceLocal) {
|
||||
// 优先从数据库读取
|
||||
try {
|
||||
config = await getConfig(key)
|
||||
} catch (e) {
|
||||
console.log(`[Config API] 数据库读取${key}失败,使用本地配置`)
|
||||
}
|
||||
let config = await getPrismaConfig(key)
|
||||
if (config == null) {
|
||||
const rows = await query(
|
||||
'SELECT config_value FROM system_config WHERE config_key = ?',
|
||||
[key]
|
||||
) as any[]
|
||||
config = rows[0]?.config_value ?? null
|
||||
}
|
||||
|
||||
// 数据库没有则使用本地默认
|
||||
if (!config) {
|
||||
config = DEFAULT_CONFIGS[key] || null
|
||||
}
|
||||
|
||||
if (config) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
key,
|
||||
config,
|
||||
source: config === DEFAULT_CONFIGS[key] ? 'local' : 'database'
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '配置不存在'
|
||||
}, { status: 404 })
|
||||
success: true,
|
||||
data: config
|
||||
})
|
||||
}
|
||||
|
||||
// 获取所有配置
|
||||
const allConfigs: Record<string, any> = {}
|
||||
const sources: Record<string, string> = {}
|
||||
|
||||
for (const configKey of Object.keys(DEFAULT_CONFIGS)) {
|
||||
let config = null
|
||||
|
||||
if (!forceLocal) {
|
||||
try {
|
||||
config = await getConfig(configKey)
|
||||
} catch (e) {
|
||||
// 忽略数据库错误
|
||||
}
|
||||
}
|
||||
|
||||
if (config) {
|
||||
allConfigs[configKey] = config
|
||||
sources[configKey] = 'database'
|
||||
} else {
|
||||
allConfigs[configKey] = DEFAULT_CONFIGS[configKey]
|
||||
sources[configKey] = 'local'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取小程序配置
|
||||
let mpConfig = null
|
||||
let configs: any[]
|
||||
try {
|
||||
mpConfig = await getConfig('mp_config')
|
||||
} catch (e) {}
|
||||
|
||||
// 提取前端需要的格式
|
||||
const bookConfig = allConfigs.book_config || DEFAULT_CONFIGS.book_config
|
||||
const featureConfig = allConfigs.feature_config || DEFAULT_CONFIGS.feature_config
|
||||
configs = await prisma.system_config.findMany({
|
||||
orderBy: { config_key: 'asc' }
|
||||
})
|
||||
} catch (e) {
|
||||
const rows = await query(
|
||||
'SELECT id, config_key, config_value, description, created_at, updated_at FROM system_config ORDER BY config_key ASC'
|
||||
) as any[]
|
||||
configs = rows || []
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
configs: allConfigs,
|
||||
sources,
|
||||
// 前端直接使用的格式
|
||||
freeChapters: bookConfig.freeSections || DEFAULT_CONFIGS.book_config.freeSections,
|
||||
features: featureConfig, // 功能开关
|
||||
mpConfig: mpConfig || {
|
||||
appId: 'wxb8bbb2b10dec74aa',
|
||||
apiDomain: 'https://soul.quwanzhi.com',
|
||||
buyerDiscount: 5,
|
||||
referralBindDays: 30,
|
||||
minWithdraw: 10
|
||||
}
|
||||
data: configs
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Config API] GET错误:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '获取配置失败: ' + (error as Error).message
|
||||
error: '获取配置失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST - 保存配置到数据库
|
||||
* 支持两种格式:
|
||||
* 1. { key, config } - 单个配置
|
||||
* 2. { freeChapters, mpConfig } - 批量配置
|
||||
* POST - 创建/更新配置
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { key, value, description } = body
|
||||
|
||||
// 支持批量配置格式
|
||||
if (body.freeChapters || body.mpConfig) {
|
||||
let successCount = 0
|
||||
|
||||
// 保存免费章节配置
|
||||
if (body.freeChapters) {
|
||||
const bookConfig = {
|
||||
...DEFAULT_CONFIGS.book_config,
|
||||
freeSections: body.freeChapters
|
||||
}
|
||||
const success = await setConfig('book_config', bookConfig, '书籍配置-免费章节')
|
||||
if (success) successCount++
|
||||
}
|
||||
|
||||
// 保存小程序配置
|
||||
if (body.mpConfig) {
|
||||
const success = await setConfig('mp_config', body.mpConfig, '小程序配置')
|
||||
if (success) successCount++
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `配置保存成功 (${successCount}项)`,
|
||||
successCount
|
||||
})
|
||||
}
|
||||
|
||||
// 原有的单配置格式
|
||||
const { key, config, description } = body
|
||||
|
||||
if (!key || !config) {
|
||||
if (!key) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '配置键名和配置值不能为空'
|
||||
error: '配置键不能为空'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
console.log(`[Config API] 保存配置 ${key}:`, config)
|
||||
|
||||
// 保存到数据库
|
||||
const success = await setConfig(key, config, description)
|
||||
|
||||
if (success) {
|
||||
// 验证保存结果
|
||||
const saved = await getConfig(key)
|
||||
console.log(`[Config API] 验证保存结果 ${key}:`, saved)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '配置保存成功',
|
||||
key,
|
||||
savedConfig: saved // 返回实际保存的配置
|
||||
})
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '配置保存失败'
|
||||
}, { status: 500 })
|
||||
const valueStr = typeof value === 'string' ? value : JSON.stringify(value ?? null)
|
||||
try {
|
||||
await setPrismaConfig(key, value, description)
|
||||
} catch (e) {
|
||||
await query(
|
||||
`INSERT INTO system_config (config_key, config_value, description, updated_at) VALUES (?, ?, ?, NOW())
|
||||
ON DUPLICATE KEY UPDATE config_value = ?, description = ?, updated_at = NOW()`,
|
||||
[key, valueStr, description || null, valueStr, description || null]
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '配置保存成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[Config API] POST错误:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '保存配置失败: ' + (error as Error).message
|
||||
error: '保存配置失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT - 批量更新配置
|
||||
* DELETE - 删除配置
|
||||
*/
|
||||
export async function PUT(request: NextRequest) {
|
||||
export async function DELETE(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { configs } = body
|
||||
const { searchParams } = new URL(request.url)
|
||||
const key = searchParams.get('key')
|
||||
|
||||
if (!configs || typeof configs !== 'object') {
|
||||
if (!key) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '配置数据格式错误'
|
||||
error: '配置键不能为空'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
let successCount = 0
|
||||
let failedCount = 0
|
||||
|
||||
for (const [key, config] of Object.entries(configs)) {
|
||||
try {
|
||||
const success = await setConfig(key, config)
|
||||
if (success) {
|
||||
successCount++
|
||||
} else {
|
||||
failedCount++
|
||||
}
|
||||
} catch (e) {
|
||||
failedCount++
|
||||
try {
|
||||
await prisma.system_config.delete({
|
||||
where: { config_key: key }
|
||||
})
|
||||
} catch (e: any) {
|
||||
if (e.code === 'P2025') {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '配置不存在'
|
||||
}, { status: 404 })
|
||||
}
|
||||
await query('DELETE FROM system_config WHERE config_key = ?', [key])
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `配置更新完成:成功${successCount}个,失败${failedCount}个`,
|
||||
successCount,
|
||||
failedCount
|
||||
message: '配置删除成功'
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Config API] PUT错误:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '更新配置失败: ' + (error as Error).message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE - 删除配置(恢复为本地默认)
|
||||
*/
|
||||
export async function DELETE(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const key = searchParams.get('key')
|
||||
|
||||
if (!key) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '配置键名不能为空'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
try {
|
||||
await query('DELETE FROM system_config WHERE config_key = ?', [key])
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '配置已删除,将使用本地默认值',
|
||||
key
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('[Config API] DELETE错误:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '删除配置失败: ' + (error as Error).message
|
||||
error: '删除配置失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化:将本地配置同步到数据库
|
||||
*/
|
||||
export async function syncLocalToDatabase() {
|
||||
console.log('[Config] 开始同步本地配置到数据库...')
|
||||
|
||||
for (const [key, config] of Object.entries(DEFAULT_CONFIGS)) {
|
||||
try {
|
||||
// 检查数据库是否已有该配置
|
||||
const existing = await getConfig(key)
|
||||
if (!existing) {
|
||||
// 数据库没有,则写入
|
||||
await setConfig(key, config, `默认${key}配置`)
|
||||
console.log(`[Config] 同步配置: ${key}`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[Config] 同步${key}失败:`, e)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[Config] 配置同步完成')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user