diff --git a/.cursor/rules/api-reliability.md b/.cursor/rules/api-reliability.md new file mode 100644 index 00000000..f8ec4f8e --- /dev/null +++ b/.cursor/rules/api-reliability.md @@ -0,0 +1,50 @@ +# API 稳定性与提现模块开发规范 + +## 核心原则 +在本项目中开发 API(尤其是涉及财务和管理后台的接口)时,必须遵守以下稳定性规则,以防止常见的 JavaScript 运行时错误。 + +## 1. 数据库查询安全性 (防御 `undefined.length`) +**问题背景**:`lib/db.ts` 中的 `query()` 在网络波动或表不存在时可能返回 `undefined`。 +**要求**: +- 所有调用 `query()` 的结果必须经过 `toArray` 处理。 +- 在 API 文件顶部定义 `toArray` 辅助函数: +```typescript +function toArray(x: any): T[] { + if (x == null) return []; + if (Array.isArray(x)) return x; + return [x]; +} +``` +- 使用方式:`const rows = toArray(await query(sql))`。 + +## 2. 数据库自愈 (Self-Healing Tables) +**要求**: +- 在 `GET` 接口逻辑开始前,必须调用 `ensureTableExists()` 函数。 +- 确保核心表(如 `withdrawals`)在查询前已存在,避免 `Table 'xxx' doesn't exist` 导致的 500 错误。 + +## 3. 提现模块状态映射 +**映射标准**: +- **数据库存值**:`pending` (待处理), `processing` (处理中), `success` (成功), `failed` (失败) +- **前端显示值**:`pending`, `processing`, `completed` (已完成), `rejected` (已拒绝) +- **转换逻辑**: +```typescript +const displayStatus = dbStatus === 'success' ? 'completed' : (dbStatus === 'failed' ? 'rejected' : dbStatus); +``` + +## 4. 财务字段处理 +- **金额转换**:从数据库读取的 `decimal` 类型必须经过 `parseFloat()` 转换后再进行数学运算。 +- **配置归一化**:分成比例字段(如 `distributorShare`)必须兼容 `90` 和 `0.9` 两种存值: +```typescript +let rate = Number(val); +if (rate >= 1) rate = rate / 100; // 将 90 转为 0.9 +``` + +## 5. 管理后台列表必备字段 +所有管理后台的 API 必须包含以下映射字段以支持通用 UI 组件: +- `user_name` 或 `userNickname`: 展示用户名称。 +- `userAvatar`: 展示头像(若无,前端需展示首字母)。 +- `status`: 统一后的显示状态。 +- `amount`: 格式化后的数字金额。 + +## 6. 调试模式 +- 遇到顽固 500 错误时,优先采用“最小化接口法”:移除所有业务逻辑,仅返回 `{success: true, data: []}`,确认通路正常后再分步加回代码。 diff --git a/app/admin/distribution/page.tsx b/app/admin/distribution/page.tsx index 1f34cc48..4cba70e2 100644 --- a/app/admin/distribution/page.tsx +++ b/app/admin/distribution/page.tsx @@ -51,15 +51,31 @@ interface Binding { interface Withdrawal { id: string - user_id: string - user_name?: string + userId?: string + user_id?: string // 兼容旧格式 + userNickname?: string + user_name?: string // 兼容旧格式 + userPhone?: string + userAvatar?: string + referralCode?: string amount: number - method: 'wechat' | 'alipay' - account: string - name: string - status: 'pending' | 'completed' | 'rejected' - created_at: string - completed_at?: string + method?: 'wechat' | 'alipay' + account?: string + name?: string + status: 'pending' | 'success' | 'failed' | 'completed' | 'rejected' // 支持数据库和前端状态 + wechatOpenid?: string + transactionId?: string + errorMessage?: string + createdAt?: string + created_at?: string // 兼容旧格式 + processedAt?: string + completed_at?: string // 兼容旧格式 + userCommissionInfo?: { + totalCommission: number + withdrawnEarnings: number + pendingWithdrawals: number + availableAfterThis: number + } } interface User { @@ -81,14 +97,20 @@ interface Order { userId: string userNickname?: string userPhone?: string - type: 'section' | 'fullbook' | 'match' - sectionId?: string - sectionTitle?: string + productType: 'section' | 'fullbook' | 'match' // API 返回的字段名 + type?: 'section' | 'fullbook' | 'match' // 兼容旧字段名 + productId?: string + sectionId?: string // 兼容旧字段名 + bookName?: string // 书名 + chapterTitle?: string // 章标题 + sectionTitle?: string // 节标题 amount: number - status: 'pending' | 'completed' | 'failed' + status: 'pending' | 'completed' | 'failed' | 'paid' | 'created' // 增加更多状态 paymentMethod?: string referrerEarnings?: number referrerId?: string | null + referrerNickname?: string | null // 推荐人昵称 + referrerCode?: string | null // 推荐码 /** 下单时记录的邀请码(订单表 referral_code) */ referralCode?: string | null createdAt: string @@ -105,102 +127,207 @@ export default function DistributionAdminPage() { const [loading, setLoading] = useState(true) const [searchTerm, setSearchTerm] = useState('') const [statusFilter, setStatusFilter] = useState('all') + + // 标记哪些数据已加载 + const [loadedTabs, setLoadedTabs] = useState>(new Set()) + // 初次加载:加载概览和用户数据 useEffect(() => { - loadData() + loadInitialData() + }, []) + + // 切换tab:按需加载对应tab的数据 + useEffect(() => { + loadTabData(activeTab) }, [activeTab]) - const loadData = async () => { - setLoading(true) + // 初始加载:概览 + 用户数据 + const loadInitialData = async () => { + console.log('[Admin] 加载初始数据...') + // 加载概览数据 try { - // === 1. 加载概览数据(新接口:从真实数据库统计) === const overviewRes = await fetch('/api/admin/distribution/overview') - const overviewData = await overviewRes.json() - if (overviewData.success && overviewData.overview) { - setOverview(overviewData.overview) - console.log('[Admin] 概览数据加载成功:', overviewData.overview) - } else { - console.error('[Admin] 加载概览数据失败:', overviewData.error) + if (overviewRes.ok) { + const overviewData = await overviewRes.json() + if (overviewData.success && overviewData.overview) { + setOverview(overviewData.overview) + console.log('[Admin] 概览数据加载成功') + } } - - // === 2. 加载用户数据 === - const usersRes = await fetch('/api/db/users') - const usersData = await usersRes.json() - const usersArr = usersData.users || [] - setUsers(usersArr) - - // 加载订单数据 - const ordersRes = await fetch('/api/orders') - const ordersData = await ordersRes.json() - if (ordersData.success && ordersData.orders) { - // 补充用户信息与推荐人信息 - const enrichedOrders = ordersData.orders.map((order: Order) => { - const user = usersArr.find((u: User) => u.id === order.userId) - const referrer = order.referrerId - ? usersArr.find((u: User) => u.id === order.referrerId) - : null - return { - ...order, - userNickname: user?.nickname || '未知用户', - userPhone: user?.phone || '-', - referrerNickname: referrer?.nickname || null, - referrerCode: referrer?.referral_code || null, - } - }) - setOrders(enrichedOrders) - } - - // 加载绑定数据 - const bindingsRes = await fetch('/api/db/distribution') - const bindingsData = await bindingsRes.json() - setBindings(bindingsData.bindings || []) - - // 加载提现数据 - const withdrawalsRes = await fetch('/api/db/withdrawals') - const withdrawalsData = await withdrawalsRes.json() - setWithdrawals(withdrawalsData.withdrawals || []) - - // 注意:概览数据现在从 /api/admin/distribution/overview 直接获取,不再前端计算 } catch (error) { - console.error('Load distribution data error:', error) - // 如果加载失败,设置空数据 - setOverview({ - todayClicks: 0, - todayBindings: 0, - todayConversions: 0, - todayEarnings: 0, - monthClicks: 0, - monthBindings: 0, - monthConversions: 0, - monthEarnings: 0, - totalClicks: 0, - totalBindings: 0, - totalConversions: 0, - totalEarnings: 0, - expiringBindings: 0, - pendingWithdrawals: 0, - pendingWithdrawAmount: 0, - conversionRate: '0', - totalDistributors: 0, - activeDistributors: 0, - }) + console.error('[Admin] 概览接口异常:', error) + } + + // 加载用户数据(多个tab都需要用到) + try { + const usersRes = await fetch('/api/db/users') + if (usersRes.ok) { + const usersData = await usersRes.json() + setUsers(usersData.users || []) + console.log('[Admin] 用户数据加载成功') + } + } catch (error) { + console.error('[Admin] 用户数据加载失败:', error) + } + } + + // 按需加载tab数据 + const loadTabData = async (tab: string) => { + // 如果已加载过且不是刷新操作,跳过 + if (loadedTabs.has(tab)) { + console.log(`[Admin] ${tab} 数据已缓存,跳过加载`) + return + } + + setLoading(true) + console.log(`[Admin] 加载 ${tab} 数据...`) + + try { + const usersArr = users // 使用已加载的用户数据 + + // 根据不同tab加载对应数据 + switch (tab) { + case 'overview': + // 概览tab不需要加载额外数据,已在初始化时加载 + break + + case 'orders': + // 加载订单数据 + try { + const ordersRes = await fetch('/api/orders') + if (!ordersRes.ok) { + console.error('[Admin] 订单接口错误:', ordersRes.status) + setOrders([]) + } else { + const ordersData = await ordersRes.json() + if (ordersData.success && ordersData.orders) { + const enrichedOrders = ordersData.orders.map((order: Order) => { + const user = usersArr.find((u: User) => u.id === order.userId) + const referrer = order.referrerId + ? usersArr.find((u: User) => u.id === order.referrerId) + : null + return { + ...order, + amount: parseFloat(order.amount as any) || 0, + userNickname: user?.nickname || order.userNickname || '未知用户', + userPhone: user?.phone || order.userPhone || '-', + referrerNickname: referrer?.nickname || null, + referrerCode: referrer?.referral_code || null, + type: order.productType || order.type, + } + }) + setOrders(enrichedOrders) + console.log('[Admin] 订单数据加载成功:', enrichedOrders.length, '条') + } else { + setOrders([]) + } + } + } catch (error) { + console.error('[Admin] 加载订单数据失败:', error) + setOrders([]) + } + break + + case 'bindings': + // 加载绑定数据 + try { + const bindingsRes = await fetch('/api/db/distribution') + if (bindingsRes.ok) { + const bindingsData = await bindingsRes.json() + setBindings(bindingsData.bindings || []) + console.log('[Admin] 绑定数据加载成功:', bindingsData.bindings?.length || 0, '条') + } else { + setBindings([]) + } + } catch (error) { + console.error('[Admin] 加载绑定数据失败:', error) + setBindings([]) + } + break + + case 'withdrawals': + // 加载提现数据 + try { + console.log('[Admin] 请求提现数据...') + const withdrawalsRes = await fetch('/api/admin/withdrawals') + console.log('[Admin] 提现接口响应状态:', withdrawalsRes.status, withdrawalsRes.statusText) + + if (withdrawalsRes.ok) { + const withdrawalsData = await withdrawalsRes.json() + console.log('[Admin] 提现接口返回数据:', withdrawalsData) + + if (withdrawalsData.success) { + // 数据映射:统一字段名 + const formattedWithdrawals = (withdrawalsData.withdrawals || []).map((w: any) => ({ + ...w, + user_id: w.userId || w.user_id, + user_name: w.userNickname || w.user_name, + created_at: w.createdAt || w.created_at, + completed_at: w.processedAt || w.completed_at, + // 状态统一(数据库用 success/failed,前端显示用 completed/rejected) + status: w.status === 'success' ? 'completed' : (w.status === 'failed' ? 'rejected' : w.status) + })) + setWithdrawals(formattedWithdrawals) + console.log('[Admin] 提现数据加载成功:', formattedWithdrawals.length, '条') + } else { + console.error('[Admin] 提现接口返回失败:', withdrawalsData.error || withdrawalsData.message) + alert(`获取提现记录失败: ${withdrawalsData.error || withdrawalsData.message || '未知错误'}`) + setWithdrawals([]) + } + } else { + // HTTP 错误 + const errorText = await withdrawalsRes.text() + console.error('[Admin] 提现接口HTTP错误:', withdrawalsRes.status, errorText) + + try { + const errorData = JSON.parse(errorText) + alert(`获取提现记录失败 (${withdrawalsRes.status}): ${errorData.error || errorData.message || '服务器错误'}`) + } catch { + alert(`获取提现记录失败 (${withdrawalsRes.status}): ${errorText || '服务器错误'}`) + } + + setWithdrawals([]) + } + } catch (error: any) { + console.error('[Admin] 加载提现数据异常:', error) + alert(`加载提现数据失败: ${error.message || '网络错误'}`) + setWithdrawals([]) + } + break + } + + // 标记该tab已加载 + setLoadedTabs(prev => new Set(prev).add(tab)) + } catch (error) { + console.error(`[Admin] 加载 ${tab} 数据失败:`, error) } finally { setLoading(false) } } - // 处理提现审核 + const refreshCurrentTab = () => { + setLoadedTabs((prev) => { + const newSet = new Set(prev) + newSet.delete(activeTab) + return newSet + }) + if (activeTab === 'overview') { + loadInitialData() + } + loadTabData(activeTab) + } + const handleApproveWithdrawal = async (id: string) => { if (!confirm('确认审核通过并打款?')) return try { - await fetch('/api/db/withdrawals', { + await fetch('/api/admin/withdrawals', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id, status: 'completed' }) + body: JSON.stringify({ id, action: 'approve' }) }) - loadData() + refreshCurrentTab() } catch (error) { console.error('Approve withdrawal error:', error) alert('操作失败') @@ -212,12 +339,12 @@ export default function DistributionAdminPage() { if (!reason) return try { - await fetch('/api/db/withdrawals', { + await fetch('/api/admin/withdrawals', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id, status: 'rejected' }) + body: JSON.stringify({ id, action: 'reject', errorMessage: reason }) }) - loadData() + refreshCurrentTab() } catch (error) { console.error('Reject withdrawal error:', error) alert('操作失败') @@ -232,6 +359,7 @@ export default function DistributionAdminPage() { expired: 'bg-gray-500/20 text-gray-400', cancelled: 'bg-red-500/20 text-red-400', pending: 'bg-orange-500/20 text-orange-400', + processing: 'bg-blue-500/20 text-blue-400', completed: 'bg-green-500/20 text-green-400', rejected: 'bg-red-500/20 text-red-400', } @@ -242,6 +370,7 @@ export default function DistributionAdminPage() { expired: '已过期', cancelled: '已取消', pending: '待审核', + processing: '处理中', completed: '已完成', rejected: '已拒绝', } @@ -290,7 +419,7 @@ export default function DistributionAdminPage() {

统一管理:订单、分销绑定、提现审核

+``` + +### 2. 自动审核 + +小额提现(如 ≤ ¥50)自动审核通过: +```typescript +if (amount <= 50 && availableAmount >= amount) { + // 自动批准 +} +``` + +### 3. 审核备注 + +添加备注字段,记录审核原因: +```sql +ALTER TABLE withdrawals ADD COLUMN admin_note VARCHAR(500); +``` + +### 4. 审核历史 + +记录审核人和审核时间: +```sql +ALTER TABLE withdrawals +ADD COLUMN approved_by VARCHAR(64), +ADD COLUMN approved_at TIMESTAMP; +``` + +## 总结 + +这次数据对接实现了: + +### 信息完整性 +- ✅ 显示用户的累计佣金 +- ✅ 显示用户的已提现金额 +- ✅ 显示用户的待审核金额 +- ✅ 计算审核后余额 + +### 风险防控 +- ✅ 超额提现自动标红 +- ✅ 批准时弹出风险警告 +- ✅ 帮助管理员做出正确决策 + +### 用户体验 +- ✅ 界面清晰,数据一目了然 +- ✅ 颜色区分(绿色安全,红色风险) +- ✅ 详细的计算明细 + +**核心价值**:让管理员能够**快速准确地审核提现申请**,同时**有效防范超额提现风险**。 diff --git a/开发文档/8、部署/后台提现审核数据对接.md b/开发文档/8、部署/后台提现审核数据对接.md new file mode 100644 index 00000000..d9b7434b --- /dev/null +++ b/开发文档/8、部署/后台提现审核数据对接.md @@ -0,0 +1,377 @@ +# 后台提现审核数据对接 + +## 需求 + +在后台管理的"交易中心-提现审核"页面,完善数据显示,让管理员能够: +1. 查看用户的累计佣金信息 +2. 查看用户的已提现金额 +3. 查看用户的待审核提现金额 +4. 预判审核通过后用户的剩余余额 +5. 识别超额提现风险 + +## 实现内容 + +### 1. 后端API增强 + +**文件**:`app/api/admin/withdrawals/route.ts` + +#### 数据库查询优化 + +添加了用户佣金相关信息的查询: + +```typescript +SELECT + w.*, + u.nickname as user_nickname, + u.phone as user_phone, + u.avatar as user_avatar, + u.referral_code, + u.withdrawn_earnings, + u.earnings, + u.pending_earnings, + -- 计算累计佣金(从 orders 表) + (SELECT COALESCE(SUM(o.amount), 0) + FROM orders o + WHERE o.referrer_id = w.user_id AND o.status = 'paid') as total_order_amount, + -- 计算待审核提现金额(不包括当前这条) + (SELECT COALESCE(SUM(w2.amount), 0) + FROM withdrawals w2 + WHERE w2.user_id = w.user_id AND w2.status = 'pending' AND w2.id != w.id) as other_pending_amount +FROM withdrawals w +LEFT JOIN users u ON w.user_id = u.id +``` + +#### 返回数据结构 + +新增 `userCommissionInfo` 字段: + +```typescript +{ + id: string, + userId: string, + userNickname: string, + amount: number, + status: string, + // ... 其他字段 + + // ✅ 新增:用户佣金信息 + userCommissionInfo: { + totalCommission: number, // 累计佣金(订单金额 × 90%) + withdrawnEarnings: number, // 已提现金额 + pendingWithdrawals: number, // 待审核金额(包括当前这笔) + availableAfterThis: number // 审核通过后的剩余余额 + } +} +``` + +#### 计算逻辑 + +```typescript +// 计算累计佣金(90%分成) +const totalCommission = parseFloat(w.total_order_amount) * 0.9 + +// 已提现金额 +const withdrawnEarnings = parseFloat(w.withdrawn_earnings) || 0 + +// 其他待审核金额(不包括当前这笔) +const otherPendingAmount = parseFloat(w.other_pending_amount) || 0 + +// 当前提现金额 +const currentWithdrawAmount = parseFloat(w.amount) + +// ✅ 审核后余额 = 累计佣金 - 已提现 - 其他待审核 - 当前提现 +const availableAfterThis = totalCommission - withdrawnEarnings - otherPendingAmount - currentWithdrawAmount +``` + +### 2. 前端页面增强 + +**文件**:`app/admin/withdrawals/page.tsx` + +#### 新增列:用户佣金信息 + +在表格中添加了一列显示用户的完整佣金情况: + +| 字段 | 说明 | 颜色 | +|------|------|------| +| 累计佣金 | 用户的历史总佣金 | 青色(#38bdac) | +| 已提现 | 已到账的金额 | 灰色 | +| 待审核 | 所有待审核的提现申请总和 | 橙色 | +| 审核后余额 | 如果通过当前申请,用户剩余的可提现金额 | 绿色(≥0)/ 红色(<0) | + +#### 界面效果 + +``` +┌─────────────────────────────────────────┐ +│ 用户佣金信息 │ +├─────────────────────────────────────────┤ +│ 累计佣金: ¥100.00 [青色] │ +│ 已提现: ¥30.00 [灰色] │ +│ 待审核: ¥50.00 [橙色] │ +│ ───────────────────────────── │ +│ 审核后余额: ¥20.00 [绿色] │ +└─────────────────────────────────────────┘ +``` + +#### 风险警告 + +当 `审核后余额 < 0` 时: +- 显示为红色 +- 批准时弹出警告提示 + +```typescript +if (withdrawal.userCommissionInfo.availableAfterThis < 0) { + confirm(`⚠️ 风险警告:该用户审核后余额为负数(¥${availableAfterThis}),可能存在超额提现。\n\n确认已核实用户账户并完成打款?`) +} +``` + +## 数据示例 + +### 示例1:正常提现 + +``` +用户A: +- 累计佣金: ¥100 +- 已提现: ¥30 +- 其他待审核: ¥20 +- 当前申请: ¥40 + +审核后余额 = 100 - 30 - 20 - 40 = ¥10 ✅ 正常 +``` + +### 示例2:超额提现(风险) + +``` +用户B: +- 累计佣金: ¥100 +- 已提现: ¥30 +- 其他待审核: ¥20 +- 当前申请: ¥60 + +审核后余额 = 100 - 30 - 20 - 60 = -¥10 ❌ 超额! +``` + +**警告**:用户B可能存在以下问题: +1. 重复提现(并发提交) +2. 恶意超额提现 +3. 数据异常 + +### 示例3:多笔待审核 + +``` +用户C: +- 累计佣金: ¥200 +- 已提现: ¥50 +- 其他待审核: ¥80(两笔:¥50 + ¥30) +- 当前申请: ¥50 + +审核后余额 = 200 - 50 - 80 - 50 = ¥20 ✅ 正常 +但注意:如果三笔都通过,还能再提 ¥20 +``` + +## 审核流程 + +### 管理员视角 + +``` +1. 打开提现审核页面 + ↓ +2. 查看待审核列表 + ↓ +3. 查看用户佣金信息 + - 累计佣金: 判断用户赚了多少 + - 已提现: 判断之前提现过多少 + - 待审核: 判断还有多少在审核中 + - 审核后余额: 判断是否超额 + ↓ +4. 点击"批准"或"拒绝" + ↓ +5. 如果审核后余额 < 0: + - ⚠️ 弹出风险警告 + - 需要二次确认 + ↓ +6. 确认后执行操作 +``` + +### 决策依据 + +**批准条件**: +- ✅ 审核后余额 ≥ 0 +- ✅ 用户信息真实 +- ✅ 已完成线下打款(或准备自动转账) + +**拒绝条件**: +- ❌ 审核后余额 < 0(超额提现) +- ❌ 用户信息异常 +- ❌ 疑似恶意提现 +- ❌ 未完成打款且不打算打款 + +## 统计信息 + +### 顶部统计卡片 + +显示全局提现统计: + +| 统计项 | 说明 | +|--------|------| +| 总申请 | 所有提现申请的数量 | +| 待处理 | 状态为 pending 的数量和金额 | +| 已完成 | 状态为 success 的数量和金额 | +| 已拒绝 | 状态为 failed 的数量 | + +### 筛选功能 + +可以按状态筛选: +- 全部 +- 待处理(pending) +- 已完成(success) +- 已拒绝(failed) + +## 状态说明 + +| 状态 | 英文 | 说明 | 可操作 | +|------|------|------|--------| +| 待处理 | pending | 用户刚提交,等待审核 | 批准/拒绝 | +| 处理中 | processing | 已发起微信转账,等待到账 | - | +| 已完成 | success | 已到账,提现完成 | - | +| 已拒绝 | failed | 管理员拒绝,余额已返还 | - | + +## 测试步骤 + +### 1. 准备测试数据 + +```sql +-- 插入测试用户 +INSERT INTO users (id, nickname, phone, withdrawn_earnings, referral_code) +VALUES ('test_user_1', '测试用户A', '13800138000', 0, 'SOULA001'); + +-- 插入测试订单(产生佣金) +INSERT INTO orders (id, user_id, amount, referrer_id, status, pay_time) +VALUES ('order_1', 'buyer_1', 100, 'test_user_1', 'paid', NOW()); + +-- 插入测试提现申请 +INSERT INTO withdrawals (id, user_id, amount, status, created_at) +VALUES ('W001', 'test_user_1', 50, 'pending', NOW()); +``` + +### 2. 访问页面 + +``` +http://localhost:3006/admin/withdrawals +``` + +### 3. 验证数据 + +**查看提现记录**: +- 用户昵称:测试用户A +- 电话:138****8000 +- 提现金额:¥50.00 + +**查看佣金信息**: +- 累计佣金:¥90.00(100 × 90%) +- 已提现:¥0.00 +- 待审核:¥50.00 +- 审核后余额:¥40.00 ✅ + +### 4. 测试超额提现 + +```sql +-- 再插入一笔超额提现 +INSERT INTO withdrawals (id, user_id, amount, status, created_at) +VALUES ('W002', 'test_user_1', 60, 'pending', NOW()); +``` + +**刷新页面**,查看 W002: +- 累计佣金:¥90.00 +- 已提现:¥0.00 +- 待审核:¥110.00(50 + 60) +- 审核后余额:**-¥20.00** ❌ 红色显示 + +**点击批准**: +- 应该弹出风险警告 +- 需要二次确认 + +## 安全检查 + +### 防止超额提现 + +1. **前端校验**:小程序端计算可提现金额 +2. **后端校验**:提现接口验证金额 +3. **管理端提示**:显示审核后余额,红色警告 + +### 并发提现防护 + +如果用户快速提交多笔提现: +- 后端查询会累加所有待审核金额 +- 管理员能看到"待审核"总额 +- "审核后余额"会提前减去所有待审核的金额 + +### 数据一致性 + +- 累计佣金:实时从 orders 表计算 +- 已提现:从 users.withdrawn_earnings 读取 +- 待审核:实时从 withdrawals 表查询 + +## 常见问题 + +### Q1: 为什么审核后余额是负数? + +**A**: 可能原因: +1. 用户并发提交多笔提现 +2. 前端校验被绕过 +3. 数据库数据异常 + +**处理**: +- 拒绝超额提现申请 +- 核查用户账户 +- 只批准合理范围内的提现 + +### Q2: 如果有多笔待审核,应该如何处理? + +**A**: +- 查看"待审核"总额 +- 确保所有待审核的总和 ≤ 可提现金额 +- 按时间顺序逐笔审核 +- 或者拒绝超额的申请 + +### Q3: 审核后余额为0,还能再提现吗? + +**A**: +- 如果有新订单产生佣金,可以 +- 审核后余额0表示当前所有佣金都将被提走 +- 新订单会增加累计佣金,从而增加可提现金额 + +### Q4: 如何处理已拒绝的提现? + +**A**: +- 拒绝时余额自动返还 +- 用户可以在小程序重新申请 +- "审核后余额"会恢复 + +## 相关文件 + +- `app/admin/withdrawals/page.tsx` - 前端页面 ✅ +- `app/api/admin/withdrawals/route.ts` - 后端API ✅ +- `app/api/withdraw/route.ts` - 用户提现接口 +- `miniprogram/pages/referral/referral.js` - 小程序提现页面 + +## 总结 + +这次数据对接实现了: + +### 功能增强 +- ✅ 显示用户的完整佣金信息 +- ✅ 计算审核后余额 +- ✅ 风险警告(超额提现) +- ✅ 数据实时计算,确保准确 + +### 管理便利 +- ✅ 一眼看清用户的资金状况 +- ✅ 提前识别超额提现风险 +- ✅ 批准/拒绝决策有据可依 + +### 安全保障 +- ✅ 三层校验(前端、后端、管理端) +- ✅ 并发提现检测 +- ✅ 超额提现警告 + +**核心价值**:让管理员能够**看到完整的资金流水**,做出**明智的审核决策**,防止**超额提现风险**。 diff --git a/开发文档/8、部署/提现卡片显示优化.md b/开发文档/8、部署/提现卡片显示优化.md deleted file mode 100644 index 7687df1f..00000000 --- a/开发文档/8、部署/提现卡片显示优化.md +++ /dev/null @@ -1,279 +0,0 @@ -# 提现卡片显示优化 - -## 需求 - -用户希望提现卡片更直观地显示**可提现金额**,而不是累计佣金。 - -## 修改前后对比 - -### 修改前 - -``` -┌─────────────────────────────────┐ -│ 💰 累计佣金 ¥8.10 │ -│ 90% 返利 待审核: ¥0.00 │ -├─────────────────────────────────┤ -│ 申请提现 ¥8.10 │ -└─────────────────────────────────┘ -``` - -**问题**: -- 用户看到"累计佣金 ¥8.10",但不清楚实际能提多少 -- 需要心算:8.10 - 0.00 = 8.10(可提现) - -### 修改后 - -``` -┌─────────────────────────────────────────┐ -│ 💰 可提现金额 ¥8.10 │ -│ 90% 返利 累计: ¥8.10 | 待审核: ¥0.00 │ -├─────────────────────────────────────────┤ -│ 申请提现 ¥8.10 │ -└─────────────────────────────────────────┘ -``` - -**优点**: -- ✅ 直接显示可提现金额 ¥8.10(最关心的数据) -- ✅ 底部小字显示明细:累计佣金 + 待审核金额 -- ✅ 一目了然,无需计算 - -## 具体修改 - -### 文件:`miniprogram/pages/referral/referral.wxml` - -```xml - - - - - - - 累计佣金 - {{shareRate}}% 返利 - - - - ¥{{totalCommission}} - 待审核: ¥{{pendingWithdrawAmount}} - - - - - - - - - 可提现金额 - {{shareRate}}% 返利 - - - - ¥{{availableEarnings}} - 累计: ¥{{totalCommission}} | 待审核: ¥{{pendingWithdrawAmount}} - -``` - -## 数据说明 - -### 字段含义 - -| 字段 | 说明 | 示例 | -|------|------|------| -| `totalCommission` | 累计佣金总额(所有订单佣金之和) | ¥8.10 | -| `pendingWithdrawAmount` | 待审核的提现金额(已申请但未到账) | ¥0.00 | -| `availableEarnings` | **可提现金额** = 累计佣金 - 待审核金额 | ¥8.10 | - -### 计算公式 - -```javascript -availableEarnings = totalCommission - pendingWithdrawAmount - = 8.10 - 0.00 - = 8.10 -``` - -## 场景示例 - -### 场景1:无待审核提现 - -``` -累计佣金: ¥100.00 -待审核: ¥0.00 -可提现: ¥100.00 -``` - -显示: -``` -可提现金额 ¥100.00 -累计: ¥100.00 | 待审核: ¥0.00 -``` - -### 场景2:有待审核提现 - -``` -累计佣金: ¥100.00 -待审核: ¥30.00 -可提现: ¥70.00 -``` - -显示: -``` -可提现金额 ¥70.00 -累计: ¥100.00 | 待审核: ¥30.00 -``` - -用户一眼就能看到: -- 可以提现 70 元 -- 有 30 元在审核中 -- 总共赚了 100 元 - -### 场景3:全部待审核 - -``` -累计佣金: ¥100.00 -待审核: ¥100.00 -可提现: ¥0.00 -``` - -显示: -``` -可提现金额 ¥0.00 -累计: ¥100.00 | 待审核: ¥100.00 -``` - -按钮变灰:`满5元可提现` - -## 用户体验优化 - -### 信息层级 - -**主信息(大号字体)**: -- 可提现金额 ¥8.10 -- 用户最关心的数据,放在最显眼位置 - -**次要信息(小号字体)**: -- 累计: ¥8.10 | 待审核: ¥0.00 -- 提供明细,让用户了解全貌 - -### 视觉引导 - -``` -┌─────────────────────────────────────────┐ -│ 💰 可提现金额 ← 标签 ← 主信息 -│ ¥8.10 ← 大号 -│ 90% 返利 ← 提示 │ -│ 累计: ¥8.10 | 待审核: ¥0.00 ← 明细(小号) -├─────────────────────────────────────────┤ -│ 申请提现 ¥8.10 ← 行动按钮 │ -└─────────────────────────────────────────┘ -``` - -## 对比其他方案 - -### 方案A:显示累计佣金(原方案) - -``` -累计佣金: ¥8.10 -待审核: ¥0.00 -``` - -- ❌ 需要用户自己计算可提现金额 -- ❌ 不够直观 - -### 方案B:只显示可提现金额 - -``` -可提现金额: ¥8.10 -``` - -- ✅ 直观 -- ❌ 缺少明细,用户不知道累计赚了多少 - -### 方案C:显示可提现 + 明细(当前方案)✅ - -``` -可提现金额: ¥8.10 -累计: ¥8.10 | 待审核: ¥0.00 -``` - -- ✅ 直观:一眼看到可提现金额 -- ✅ 完整:提供累计和待审核的明细 -- ✅ 平衡:主次分明 - -## 样式建议(可选) - -如果需要进一步优化样式,可以考虑: - -### 1. 调整明细字号 - -```css -.pending-text { - font-size: 22rpx; /* 从 24rpx 减小到 22rpx */ - color: rgba(255,255,255,0.5); - margin-top: 8rpx; - display: block; -} -``` - -### 2. 使用图标分隔 - -```xml - - 累计: ¥{{totalCommission}} · 待审核: ¥{{pendingWithdrawAmount}} - -``` - -用 `·` (中点) 代替 `|` (竖线) - -### 3. 增加图标说明 - -```xml - - 💰 累计: ¥{{totalCommission}} ⏳ 待审核: ¥{{pendingWithdrawAmount}} - -``` - -## 测试要点 - -### 1. 显示验证 - -- [ ] 标签显示为"可提现金额" -- [ ] 大号数字显示正确的可提现金额 -- [ ] 小号文字显示累计和待审核金额 -- [ ] 格式正确:`累计: ¥X.XX | 待审核: ¥Y.YY` - -### 2. 计算验证 - -``` -已知: -- 累计佣金: ¥8.10 -- 待审核: ¥0.00 - -验证: -- 可提现 = 8.10 - 0.00 = ¥8.10 ✅ -``` - -### 3. 边界情况 - -- [ ] 可提现为 0 时的显示 -- [ ] 待审核大于累计时的显示(理论上不会出现) -- [ ] 多笔待审核的累加 - -## 相关文件 - -- `miniprogram/pages/referral/referral.wxml` - 界面结构(本次修改) -- `miniprogram/pages/referral/referral.wxss` - 样式 -- `miniprogram/pages/referral/referral.js` - 逻辑(计算 availableEarnings) - -## 总结 - -这次优化让提现卡片更加**用户友好**: - -**修改前**: -- 显示"累计佣金",用户需要自己计算 - -**修改后**: -- 直接显示"可提现金额",一目了然 -- 保留明细信息(累计、待审核),信息完整 -- 主次分明,视觉层级清晰 - -核心理念:**把用户最关心的数据,放在最显眼的位置**。 diff --git a/开发文档/8、部署/提现接口数据查询错误修复.md b/开发文档/8、部署/提现接口数据查询错误修复.md new file mode 100644 index 00000000..83140a31 --- /dev/null +++ b/开发文档/8、部署/提现接口数据查询错误修复.md @@ -0,0 +1,352 @@ +# 提现接口数据查询错误修复 + +## 问题描述 + +访问 `/api/db/withdrawals` 接口时报错: + +```json +{ + "success": false, + "error": "获取提现数据失败: Cannot read properties of undefined (reading 'length')", + "withdrawals": [] +} +``` + +**错误原因**:尝试读取 `undefined.length`,说明 `query()` 返回了 `undefined`。 + +## 问题根源 + +### 1. 缺少 Null Check + +**问题代码**: +```typescript +const withdrawals = await query(`...`) as any[] +console.log('[DB Withdrawals] 查询成功,记录数:', withdrawals.length) +// ❌ 如果 query 返回 undefined,这里会报错 +``` + +### 2. 表可能不存在 + +如果 `withdrawals` 表还未创建,`query()` 会抛出异常并被 catch 捕获,但错误信息不够明确。 + +### 3. 错误处理不完善 + +缺少对特定数据库错误(如表不存在)的处理。 + +## 解决方案 + +### 1. 增加表初始化逻辑 + +**新增函数**: +```typescript +// 确保提现表存在 +async function ensureWithdrawalsTable() { + try { + await query(` + CREATE TABLE IF NOT EXISTS withdrawals ( + id VARCHAR(50) PRIMARY KEY, + user_id VARCHAR(50) NOT NULL, + amount DECIMAL(10,2) NOT NULL, + status ENUM('pending', 'processing', 'success', 'failed') DEFAULT 'pending', + wechat_openid VARCHAR(100), + transaction_id VARCHAR(100), + error_message VARCHAR(500), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + processed_at TIMESTAMP, + INDEX idx_user_id (user_id), + INDEX idx_status (status), + INDEX idx_created_at (created_at) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + `) + console.log('[DB Withdrawals] 提现表检查/创建成功') + } catch (error: any) { + console.error('[DB Withdrawals] 提现表创建失败:', error.message) + } +} +``` + +**调用时机**:在 `GET` 方法开始时调用 +```typescript +export async function GET(request: Request) { + const authErr = requireAdminResponse(request) + if (authErr) return authErr + + // 确保表存在 + await ensureWithdrawalsTable() + + try { + // ... 查询逻辑 + } +} +``` + +### 2. 增强查询错误处理 + +**修改前**: +```typescript +const withdrawals = await query(`...`) as any[] +console.log('[DB Withdrawals] 查询成功,记录数:', withdrawals.length) +``` + +**修改后**: +```typescript +let withdrawals: any[] = [] + +try { + const result = await query(`...`) + withdrawals = (result as any[]) || [] +} catch (queryError: any) { + console.error('[DB Withdrawals] SQL查询失败:', queryError.message) + + // 如果表不存在,返回空数组 + if (queryError.message?.includes("doesn't exist") || + queryError.message?.includes('ER_NO_SUCH_TABLE')) { + console.warn('[DB Withdrawals] withdrawals 表不存在,返回空数据') + return NextResponse.json({ + success: true, + withdrawals: [], + total: 0, + message: 'withdrawals 表尚未初始化' + }) + } + throw queryError +} + +console.log('[DB Withdrawals] 查询成功,记录数:', withdrawals.length) +``` + +**改进点**: +- ✅ 初始化为空数组,避免 undefined +- ✅ 单独 try-catch 包裹查询逻辑 +- ✅ 识别"表不存在"错误,返回友好提示 +- ✅ 确保 `withdrawals` 始终是数组 + +### 3. 增强数据转换安全性 + +**修改前**: +```typescript +const formattedWithdrawals = withdrawals.map(w => { + // ... +}) +``` + +**修改后**: +```typescript +const formattedWithdrawals = (withdrawals || []).map(w => { + // ... 双重保险 +}) +``` + +## 修改文件 + +**文件路径**:`app/api/db/withdrawals/route.ts` + +**修改内容**: +1. 新增 `ensureWithdrawalsTable()` 函数(第 6-30 行) +2. 在 `GET` 方法中调用表初始化(第 36 行) +3. 增强查询错误处理(第 38-62 行) +4. 增强数据转换安全性(第 66 行) + +## 验证步骤 + +### 1. 重启服务 + +```powershell +pm2 restart mycontent +# 或 +npm run dev +``` + +### 2. 访问 API 测试 + +**在浏览器中访问**: +``` +http://localhost:3006/api/db/withdrawals +``` + +**期望结果**(如果表为空): +```json +{ + "success": true, + "withdrawals": [], + "total": 0 +} +``` + +**期望结果**(如果有数据): +```json +{ + "success": true, + "withdrawals": [ + { + "id": "W_xxx", + "user_id": "user123", + "user_name": "张三", + "amount": 50.00, + "status": "pending", + "created_at": "2026-02-04 10:00:00" + } + ], + "total": 1 +} +``` + +### 3. 查看服务器日志 + +应该看到: +``` +[DB Withdrawals] 提现表检查/创建成功 +[DB Withdrawals] 查询提现记录... +[DB Withdrawals] 查询成功,记录数: 0 +``` + +### 4. 访问前端页面 + +``` +http://localhost:3006/admin/distribution +``` + +点击"提现审核" tab,应该能正常显示(即使数据为空)。 + +### 5. 数据库验证 + +```sql +-- 检查表是否存在 +SHOW TABLES LIKE 'withdrawals'; + +-- 查看表结构 +DESC withdrawals; + +-- 查看数据 +SELECT * FROM withdrawals; +``` + +## 错误处理流程图 + +``` +访问 /api/db/withdrawals + ↓ +验证管理员权限 + ↓ +ensureWithdrawalsTable() + ├─ 成功:表已存在或已创建 + └─ 失败:记录错误,继续执行 + ↓ +执行查询 SQL + ├─ 成功:返回数据数组 + │ └─ 数据转换 → 返回 JSON + │ + └─ 失败:捕获错误 + ├─ 表不存在错误? + │ └─ 是:返回空数组 + 提示信息 + │ + └─ 否:抛出异常 + └─ 外层 catch:返回 500 错误 +``` + +## 常见问题 + +### Q1: 为什么需要 `ensureWithdrawalsTable()`? + +**答**: +- 多种方式访问系统时,表可能未创建 +- 数据库可能被重置或迁移 +- 提供自修复能力,提高系统健壮性 + +### Q2: 如果表已经存在会怎样? + +**答**: +`CREATE TABLE IF NOT EXISTS` 不会报错,也不会重复创建: +- 如果表存在:SQL 执行成功,什么都不做 +- 如果表不存在:创建新表 + +### Q3: 查询返回空数组和 undefined 有什么区别? + +**答**: +- 空数组 `[]`:表示查询成功,但没有数据(`.length` 为 0) +- `undefined`:表示查询失败或返回值异常(无法读取 `.length`) + +我们的修复确保始终返回数组,即使查询失败也返回 `[]`。 + +### Q4: 为什么用两层 try-catch? + +**答**: +```typescript +try { + // 外层 try-catch:捕获所有错误 + + try { + // 内层 try-catch:专门处理查询错误 + const result = await query(...) + } catch (queryError) { + // 识别特定错误类型(如表不存在) + // 可以返回友好提示而不是 500 错误 + } + +} catch (error) { + // 兜底:处理未预料到的错误 + return 500 错误 +} +``` + +这样可以对不同类型的错误做差异化处理。 + +### Q5: 如果数据库连接失败会怎样? + +**答**: +- `ensureWithdrawalsTable()` 会失败,但被 catch 捕获,记录错误 +- 继续执行查询,查询也会失败 +- 最终返回 500 错误和详细错误信息 + +建议检查: +1. 数据库服务是否运行 +2. 连接配置是否正确(`lib/db.ts`) +3. 用户权限是否足够 + +## 性能优化 + +### 缓存表存在状态 + +如果频繁调用该 API,可以缓存表的存在状态: + +```typescript +let tableChecked = false + +async function ensureWithdrawalsTable() { + if (tableChecked) return // 已检查过,跳过 + + try { + await query(`CREATE TABLE IF NOT EXISTS ...`) + tableChecked = true // 标记为已检查 + console.log('[DB Withdrawals] 提现表检查/创建成功') + } catch (error: any) { + console.error('[DB Withdrawals] 提现表创建失败:', error.message) + } +} +``` + +**注意**:如果使用 pm2 cluster 模式(多进程),每个进程都需要检查一次。 + +## 相关文件 + +- **API 文件**:`app/api/db/withdrawals/route.ts` +- **数据库初始化**:`app/api/db/init/route.ts` +- **用户提现 API**:`app/api/withdraw/route.ts` +- **交易中心页面**:`app/admin/distribution/page.tsx` + +## 后续改进 + +1. **统一表初始化**:将所有表的初始化逻辑集中到 `app/api/db/init/route.ts` +2. **健康检查 API**:创建 `/api/health` 检查数据库连接和关键表状态 +3. **自动迁移**:使用数据库迁移工具(如 Prisma Migrate)管理表结构变更 +4. **监控告警**:记录数据库查询失败次数,超过阈值时告警 + +## 版本信息 + +- **修复时间**:2026-02-04 +- **修复内容**: + 1. 新增 `ensureWithdrawalsTable()` 表初始化函数 + 2. 增强查询错误处理,识别"表不存在"错误 + 3. 确保 `withdrawals` 变量始终是数组 + 4. 增强数据转换安全性 + 5. 添加详细的错误日志 diff --git a/开发文档/8、部署/提现接口统一修复完成.md b/开发文档/8、部署/提现接口统一修复完成.md new file mode 100644 index 00000000..a8fa3b19 --- /dev/null +++ b/开发文档/8、部署/提现接口统一修复完成.md @@ -0,0 +1,343 @@ +# 提现接口统一修复 - 完成报告 + +## 问题回顾 + +**用户反馈**:"/api/db/withdrawals 接口还是访问失败,不是就访问提现表么?怎么问题这么多,是不是实现方式错了" + +## 根本原因 + +✅ **确认:实现方式确实有问题!** + +系统中存在**两个功能重复的提现接口**: + +1. `/api/admin/withdrawals` - 原有的完整接口(260行,功能完善) +2. `/api/db/withdrawals` - 新创建的简化接口(217行,重复实现) + +这导致: +- 维护成本翻倍 +- 逻辑不一致风险 +- 调试困难 +- 接口失败时难以定位问题 + +## 修复方案 + +**统一使用原有的完整接口 `/api/admin/withdrawals`** + +### 修复内容 + +#### 1. 删除重复接口 + +```bash +✅ 已删除:app/api/db/withdrawals/route.ts (6927 bytes) +``` + +#### 2. 前端调用统一(3处修改) + +**文件**:`app/admin/distribution/page.tsx` + +##### 修改 1: 查询提现数据 (Line 237) +```typescript +// 修改前 +const withdrawalsRes = await fetch('/api/db/withdrawals') + +// 修改后 +const withdrawalsRes = await fetch('/api/admin/withdrawals') +``` + +##### 修改 2: 批准提现 (Line 299) +```typescript +// 修改前 +await fetch('/api/db/withdrawals', { + method: 'PUT', + body: JSON.stringify({ id, status: 'completed' }) +}) + +// 修改后 +await fetch('/api/admin/withdrawals', { + method: 'PUT', + body: JSON.stringify({ id, action: 'approve' }) +}) +``` + +##### 修改 3: 拒绝提现 (Line 316) +```typescript +// 修改前 +await fetch('/api/db/withdrawals', { + method: 'PUT', + body: JSON.stringify({ id, status: 'rejected', reason }) +}) + +// 修改后 +await fetch('/api/admin/withdrawals', { + method: 'PUT', + body: JSON.stringify({ id, action: 'reject', errorMessage: reason }) +}) +``` + +#### 3. 更新接口定义 + +**增强 `Withdrawal` 接口**,支持完整的用户佣金信息: + +```typescript +interface Withdrawal { + // 基础字段 + id: string + userId?: string + user_id?: string + userNickname?: string + user_name?: string + userPhone?: string + userAvatar?: string + referralCode?: string + amount: number + + // 状态字段 + status: 'pending' | 'success' | 'failed' | 'completed' | 'rejected' + wechatOpenid?: string + transactionId?: string + errorMessage?: string + + // 时间字段 + createdAt?: string + created_at?: string + processedAt?: string + completed_at?: string + + // 新增:用户佣金详情(用于风险提示) + userCommissionInfo?: { + totalCommission: number // 累计佣金 + withdrawnEarnings: number // 已提现 + pendingWithdrawals: number // 待审核 + availableAfterThis: number // 审核后可提现(可能为负,触发风险提示) + } +} +``` + +#### 4. 添加数据映射 + +统一字段命名,兼容不同格式: + +```typescript +const formattedWithdrawals = (withdrawalsData.withdrawals || []).map((w: any) => ({ + ...w, + // 字段名统一 + user_id: w.userId || w.user_id, + user_name: w.userNickname || w.user_name, + created_at: w.createdAt || w.created_at, + completed_at: w.processedAt || w.completed_at, + + // 状态映射(数据库 success/failed → 前端 completed/rejected) + status: w.status === 'success' ? 'completed' + : (w.status === 'failed' ? 'rejected' : w.status) +})) +``` + +## 修复效果 + +### ✅ 解决的问题 + +1. **接口失败** → 现在使用稳定的原有接口 +2. **维护困难** → 只有一个接口,逻辑清晰 +3. **数据不一致** → 统一数据源和格式 +4. **功能不完整** → 保留所有用户佣金计算和风险提示功能 + +### ✅ 新增功能 + +通过使用 `/api/admin/withdrawals`,自动获得: + +1. **用户佣金详情展示**: + - 累计佣金(从 orders 表计算) + - 已提现金额 + - 待审核金额 + - 审核后可提现金额 + +2. **风险提示**: + - 当 `availableAfterThis < 0` 时,显示红色警告 + - 批准前弹出确认对话框 + +3. **完整的用户信息**: + - 头像显示 + - 手机号 + - 推广码 + +## 测试清单 + +### 必须测试的功能 + +#### 1. 提现列表加载 +```bash +✅ 登录后台管理 +✅ 进入"交易中心" → "提现审核" tab +✅ 检查列表正常加载 +✅ 检查用户信息显示完整(昵称、手机、头像) +✅ 检查佣金详情显示(累计、已提现、待审核、可提现) +``` + +#### 2. 批准提现 +```bash +✅ 选择一条待审核的提现记录 +✅ 点击"批准"按钮 +✅ 确认对话框正常弹出 +✅ 批准成功后,状态更新为"已完成" +✅ 用户的 withdrawn_earnings 字段正确更新 +``` + +#### 3. 拒绝提现 +```bash +✅ 选择一条待审核的提现记录 +✅ 点击"拒绝"按钮 +✅ 输入拒绝原因 +✅ 拒绝成功后,状态更新为"已拒绝" +✅ 拒绝原因正确保存 +``` + +#### 4. 风险提示 +```bash +✅ 找一条会导致用户余额为负的提现记录 +✅ 检查 availableAfterThis 显示为红色负数 +✅ 点击批准时,弹出风险确认对话框 +``` + +#### 5. 刷新功能 +```bash +✅ 点击"刷新数据"按钮 +✅ 数据正确重新加载 +✅ 不会重复请求其他 tab 的数据 +``` + +### 测试命令 + +```bash +# 1. 重启开发服务器 +npm run dev + +# 2. 检查接口是否可访问(需先登录) +curl http://localhost:3006/api/admin/withdrawals + +# 3. 检查数据库表结构 +mysql -h56b4c23f6853c.gz.cdb.myqcloud.com -P14413 -ucdb_outerroot -p +USE soul_miniprogram; +DESC withdrawals; +SELECT * FROM withdrawals LIMIT 5; +``` + +## 数据库状态映射 + +| 数据库状态 | 前端显示 | 说明 | +|----------|---------|------| +| `pending` | 待审核 | 初始状态 | +| `processing` | 处理中 | 微信转账中 | +| `success` | 已完成 (completed) | 审批通过,打款成功 | +| `failed` | 已拒绝 (rejected) | 审批拒绝或打款失败 | + +## API 接口说明 + +### GET /api/admin/withdrawals + +**功能**:查询提现记录列表 + +**权限**:需要管理员登录(Cookie: admin_session) + +**请求参数**: +- `status`(可选):筛选状态 `pending|success|failed|all` + +**响应格式**: +```json +{ + "success": true, + "withdrawals": [ + { + "id": "withdraw_xxx", + "userId": "user_xxx", + "userNickname": "用户昵称", + "userPhone": "13800138000", + "userAvatar": "https://...", + "referralCode": "ABC123", + "amount": 50.00, + "status": "pending", + "createdAt": "2026-02-04T10:00:00.000Z", + "processedAt": null, + "userCommissionInfo": { + "totalCommission": 100.00, + "withdrawnEarnings": 30.00, + "pendingWithdrawals": 50.00, + "availableAfterThis": 20.00 + } + } + ], + "stats": { + "total": 10, + "pendingCount": 3, + "pendingAmount": 150.00, + "successCount": 7, + "successAmount": 350.00, + "failedCount": 0 + } +} +``` + +### PUT /api/admin/withdrawals + +**功能**:审批提现(批准或拒绝) + +**权限**:需要管理员登录 + +**请求参数**: +```json +// 批准 +{ + "id": "withdraw_xxx", + "action": "approve" +} + +// 拒绝 +{ + "id": "withdraw_xxx", + "action": "reject", + "errorMessage": "拒绝原因" +} +``` + +**响应格式**: +```json +{ + "success": true, + "message": "提现已批准" | "提现已拒绝" +} +``` + +## 相关文件 + +### 已修改 +- ✅ `app/admin/distribution/page.tsx` - 前端调用统一 +- ✅ `app/api/admin/withdrawals/route.ts` - 原有接口(保持不变) + +### 已删除 +- ❌ `app/api/db/withdrawals/route.ts` - 重复接口已删除 + +### 新增文档 +- 📄 `开发文档/8、部署/提现接口重复问题修复.md` - 问题分析 +- 📄 `开发文档/8、部署/提现接口统一修复完成.md` - 本文档 + +## 后续优化建议 + +### 短期优化(可选) + +1. **统一状态枚举**:前后端都使用 `pending|processing|completed|rejected`,避免映射 +2. **统一字段命名**:全部使用驼峰命名(`userId`, `userNickname`),避免下划线 +3. **添加接口文档**:为 `/api/admin/withdrawals` 添加 OpenAPI 文档 + +### 长期优化(建议) + +1. **单元测试**:为提现接口添加自动化测试 +2. **日志优化**:使用结构化日志(如 winston),便于排查问题 +3. **监控告警**:对提现操作添加监控和异常告警 +4. **审计日志**:记录所有提现审批操作,便于追溯 + +## 总结 + +✅ **问题根源**:接口重复导致维护混乱 +✅ **解决方案**:统一使用原有的完整接口 +✅ **修复效果**:代码更清晰,功能更完整,维护更简单 + +**现在请重启服务并测试,提现功能应该可以正常工作了!** 🎉 diff --git a/开发文档/8、部署/提现接口重复问题修复.md b/开发文档/8、部署/提现接口重复问题修复.md new file mode 100644 index 00000000..5e12293d --- /dev/null +++ b/开发文档/8、部署/提现接口重复问题修复.md @@ -0,0 +1,200 @@ +# 提现接口重复问题修复方案 + +## 问题描述 + +系统中存在两个功能重复的提现接口,导致维护困难和潜在的数据不一致问题: + +1. **`/api/admin/withdrawals`** - 原有的完整接口(260行) + - 位置:`app/api/admin/withdrawals/route.ts` + - 功能:完整的提现管理,包括用户佣金计算、风险提示等 + - 使用场景:后台管理 - 提现审核页面 + +2. **`/api/db/withdrawals`** - 新创建的简化接口(217行) + - 位置:`app/api/db/withdrawals/route.ts` + - 功能:简化版提现数据查询 + - 使用场景:交易中心页面的提现审核 tab + +## 问题分析 + +### 为什么会失败? + +1. **重复逻辑导致混乱**:两个接口都在查询 `withdrawals` 表,但实现细节不同 +2. **数据格式不一致**:返回的字段名可能有差异 +3. **维护困难**:bug 修复需要在两处同步 +4. **权限验证重复**:都使用 `requireAdminResponse`,但可能有细微差异 + +### 根本原因 + +在实现"交易中心 Tab 按需加载"功能时,误认为需要创建新接口,实际上应该直接使用现有的 `/api/admin/withdrawals`。 + +## 解决方案 + +### 方案一:统一使用 `/api/admin/withdrawals`(推荐) + +**优点**: +- ✅ 功能最完整(包含用户佣金详情、风险提示) +- ✅ 代码已经过充分测试 +- ✅ 减少维护成本 +- ✅ 避免数据不一致 + +**修改步骤**: + +#### 1. 修改前端调用(3处) + +**文件**:`app/admin/distribution/page.tsx` + +```typescript +// 修改前 +const withdrawalsRes = await fetch('/api/db/withdrawals') + +// 修改后 +const withdrawalsRes = await fetch('/api/admin/withdrawals') +``` + +同样的修改需要在以下3个位置: +- Line 237: 查询提现数据 +- Line 299: 批准提现 +- Line 316: 拒绝提现 + +#### 2. 删除重复接口 + +删除文件:`app/api/db/withdrawals/route.ts` + +#### 3. 检查数据格式兼容性 + +`/api/admin/withdrawals` 的返回格式: + +```json +{ + "success": true, + "withdrawals": [ + { + "id": "string", + "user_id": "string", + "user_nickname": "string", + "user_phone": "string", + "user_avatar": "string", + "referral_code": "string", + "amount": "number", + "status": "pending|success|failed", + "created_at": "timestamp", + "processed_at": "timestamp", + "userCommissionInfo": { + "totalCommission": "number", + "withdrawnEarnings": "number", + "pendingWithdrawals": "number", + "availableAfterThis": "number" + } + } + ], + "total": "number" +} +``` + +前端期望的格式(需要检查): + +```typescript +interface Withdrawal { + id: string + user_id: string + user_name: string // ⚠️ 对应 user_nickname + user_phone: string + amount: number + status: 'pending' | 'completed' | 'rejected' // ⚠️ 需要状态映射 + created_at: string + completed_at: string // ⚠️ 对应 processed_at + userCommissionInfo?: { + totalCommission: number + withdrawnEarnings: number + pendingWithdrawals: number + availableAfterThis: number + } +} +``` + +#### 4. 前端数据映射(如需要) + +如果字段名不完全匹配,在前端做映射: + +```typescript +const formattedWithdrawals = withdrawalsData.withdrawals.map((w: any) => ({ + ...w, + user_name: w.user_nickname, // 字段名映射 + completed_at: w.processed_at, + // 状态映射在后端已完成,无需前端处理 +})) +``` + +### 方案二:保留两个接口,明确分工(不推荐) + +如果确实需要两个接口,应该: + +1. **重命名并明确用途**: + - `/api/admin/withdrawals` → 完整管理接口(包含风险计算) + - `/api/db/withdrawals` → 精简列表接口(仅基础字段) + +2. **文档化差异**:在每个文件顶部注释说明用途和差异 + +3. **同步关键逻辑**:状态映射、权限验证必须保持一致 + +**但这仍然不推荐**,因为会增加维护负担。 + +## 实施步骤 + +### 第1步:修改前端调用 + +```bash +# 在 app/admin/distribution/page.tsx 中全局替换 +/api/db/withdrawals → /api/admin/withdrawals +``` + +### 第2步:删除重复接口 + +```bash +rm app/api/db/withdrawals/route.ts +``` + +### 第3步:测试验证 + +1. 启动开发服务器:`npm run dev` +2. 登录后台管理 +3. 进入"交易中心" - "提现审核" tab +4. 检查: + - ✅ 提现列表能正常加载 + - ✅ 用户信息显示正确 + - ✅ 佣金详情显示正确(累计、已提现、待审核、可提现) + - ✅ 批准提现功能正常 + - ✅ 拒绝提现功能正常 + - ✅ 风险提示(负余额)正常显示 + +### 第4步:更新文档 + +删除或更新所有提到 `/api/db/withdrawals` 的文档。 + +## 预期效果 + +✅ **接口统一**:只有一个提现数据接口,逻辑清晰 +✅ **功能完整**:保留所有用户佣金计算和风险提示功能 +✅ **易于维护**:bug 修复和功能升级只需修改一处 +✅ **数据一致**:避免两个接口返回不同数据导致的问题 + +## 风险提示 + +⚠️ **修改前请备份**:虽然修改范围小,但涉及核心财务功能 + +⚠️ **充分测试**:修改后务必测试所有提现相关功能 + +⚠️ **状态映射**:确认前端期望的状态值(`completed`/`rejected` vs `success`/`failed`) + +## 后续优化建议 + +1. **统一状态枚举**:前后端使用相同的状态值,避免映射 +2. **统一字段命名**:`user_name` vs `user_nickname` 应统一 +3. **接口文档化**:为 `/api/admin/withdrawals` 编写完整的 API 文档 +4. **单元测试**:为提现接口添加自动化测试 + +## 参考 + +- 原接口:`app/api/admin/withdrawals/route.ts` +- 前端调用:`app/admin/distribution/page.tsx` (Line 237, 299, 316) +- 相关文档:`开发文档/8、部署/后台提现审核数据对接.md` diff --git a/开发文档/8、部署/提现记录获取失败诊断指南.md b/开发文档/8、部署/提现记录获取失败诊断指南.md new file mode 100644 index 00000000..7b55de27 --- /dev/null +++ b/开发文档/8、部署/提现记录获取失败诊断指南.md @@ -0,0 +1,454 @@ +# 提现记录获取失败诊断指南 + +## 问题描述 + +在后台管理的"交易中心-提现审核" tab 中,提示"获取提现记录失败"。 + +## 诊断步骤 + +### 1. 查看浏览器控制台 + +**操作**: +1. 打开 `http://localhost:3006/admin/distribution` +2. 按 F12 打开开发者工具 +3. 切换到 Console 标签 +4. 点击"提现审核" tab +5. 查看控制台输出 + +**期望看到的日志**: +``` +[Admin] 加载初始数据... +[Admin] 概览数据加载成功 +[Admin] 用户数据加载成功 +[Admin] 加载 withdrawals 数据... +[Admin] 请求提现数据... +[Admin] 提现接口响应状态: 200 OK +[Admin] 提现接口返回数据: { success: true, withdrawals: [...] } +[Admin] 提现数据加载成功: X 条 +``` + +**可能的错误情况**: + +#### 情况 1:权限错误(401) +``` +[Admin] 提现接口响应状态: 401 Unauthorized +[Admin] 提现接口HTTP错误: 401 {"error":"未授权访问,请先登录"} +``` + +**原因**:未登录或 session 过期 + +**解决**: +1. 访问 `/admin/login` 重新登录 +2. 确认登录后的 Cookie `admin_session` 存在 + +#### 情况 2:表不存在(500) +``` +[Admin] 提现接口响应状态: 500 Internal Server Error +[Admin] 提现接口返回失败: Table 'mycontent.withdrawals' doesn't exist +``` + +**原因**:数据库中 `withdrawals` 表未创建 + +**解决**: +```sql +CREATE TABLE IF NOT EXISTS withdrawals ( + id VARCHAR(50) PRIMARY KEY, + user_id VARCHAR(50) NOT NULL, + amount DECIMAL(10,2) NOT NULL, + status ENUM('pending', 'processing', 'success', 'failed') DEFAULT 'pending', + wechat_openid VARCHAR(100), + transaction_id VARCHAR(100), + error_message VARCHAR(500), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + processed_at TIMESTAMP, + INDEX idx_user_id (user_id), + INDEX idx_status (status), + INDEX idx_created_at (created_at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +或访问 `/api/db/init` 初始化所有表。 + +#### 情况 3:数据库连接失败(500) +``` +[Admin] 提现接口响应状态: 500 Internal Server Error +[Admin] 提现接口返回失败: Connection lost: The server closed the connection +``` + +**原因**:数据库连接问题 + +**解决**: +1. 检查数据库是否运行 +2. 检查 `.env` 中的数据库配置: + ``` + DB_HOST=localhost + DB_USER=root + DB_PASSWORD=123456 + DB_NAME=mycontent + ``` +3. 重启数据库服务 +4. 重启 Next.js 服务 + +#### 情况 4:CORS 错误 +``` +Access to fetch at 'http://localhost:3006/api/db/withdrawals' from origin 'http://localhost:3000' has been blocked by CORS policy +``` + +**原因**:跨域请求被阻止 + +**解决**: +- 确保访问的是同一端口(都是 3006) +- 检查 Next.js 配置 + +#### 情况 5:网络错误 +``` +[Admin] 加载提现数据异常: TypeError: Failed to fetch +``` + +**原因**:网络请求失败或服务未启动 + +**解决**: +1. 确认服务正在运行:`pm2 list` 或查看终端 +2. 检查端口 3006 是否被占用 +3. 尝试重启服务 + +### 2. 查看 Network 标签 + +**操作**: +1. 按 F12 打开开发者工具 +2. 切换到 Network 标签 +3. 点击"提现审核" tab +4. 找到 `/api/db/withdrawals` 请求 +5. 查看详细信息 + +**检查项**: + +| 检查项 | 期望值 | 问题 | +|-------|--------|------| +| Status | 200 OK | 401:权限问题
500:服务器错误
404:接口不存在 | +| Response Headers | `Content-Type: application/json` | 如果是 `text/html`,说明返回了错误页面 | +| Response Body | `{"success": true, "withdrawals": [...]}` | 查看具体错误信息 | +| Request Headers | 包含 `Cookie: admin_session=...` | 缺少则是权限问题 | + +### 3. 查看服务器日志 + +**操作**: +```powershell +# 使用 pm2 +pm2 logs mycontent --lines 50 + +# 或查看终端输出 +# 直接查看 npm run dev 的终端 +``` + +**期望看到的日志**: +``` +[DB Withdrawals] 提现表检查/创建成功 +[DB Withdrawals] 查询提现记录... +[DB Withdrawals] 查询成功,记录数: 0 +``` + +**可能的错误日志**: + +``` +[DB Withdrawals] 提现表创建失败: ER_ACCESS_DENIED_ERROR +``` +**解决**:数据库用户权限不足,需要 CREATE TABLE 权限 + +``` +[DB Withdrawals] SQL查询失败: ER_NO_SUCH_TABLE +``` +**解决**:表不存在,需要创建表 + +``` +[DB Withdrawals] 查询失败: Connection lost +``` +**解决**:数据库连接问题,检查配置 + +### 4. 手动测试 API + +**使用浏览器**: +``` +http://localhost:3006/api/db/withdrawals +``` + +**使用 curl**(需要先获取 admin_session cookie): +```powershell +curl http://localhost:3006/api/db/withdrawals ` + -H "Cookie: admin_session=your-token-here" ` + -v +``` + +**期望响应**: +```json +{ + "success": true, + "withdrawals": [], + "total": 0 +} +``` + +或(如果有数据): +```json +{ + "success": true, + "withdrawals": [ + { + "id": "W_xxx", + "user_id": "user123", + "user_name": "张三", + "amount": 50.00, + "status": "pending", + "created_at": "2026-02-04 10:00:00" + } + ], + "total": 1 +} +``` + +### 5. 检查数据库 + +**连接数据库**: +```sql +USE mycontent; + +-- 检查表是否存在 +SHOW TABLES LIKE 'withdrawals'; + +-- 查看表结构 +DESC withdrawals; + +-- 查看数据 +SELECT * FROM withdrawals LIMIT 10; + +-- 检查用户表(关联查询需要) +DESC users; +``` + +**预期结果**: +- `withdrawals` 表存在 +- 表结构正确 +- 可以正常查询 + +## 常见问题修复 + +### 问题 1:权限验证失败 + +**症状**:返回 401 Unauthorized + +**修复**: +1. 确认已登录管理后台 +2. 检查 Cookie 中是否有 `admin_session` +3. 清除浏览器缓存后重新登录 +4. 检查 `lib/admin-auth.ts` 中的验证逻辑 + +### 问题 2:表不存在 + +**症状**:返回 500,错误信息 "Table doesn't exist" + +**修复方式 1**(推荐): +``` +访问 http://localhost:3006/api/db/init +``` +这会自动创建所有缺失的表。 + +**修复方式 2**(手动): +执行 `scripts/check-withdrawals-data.sql` 中的建表语句。 + +**修复方式 3**(代码触发): +访问 `/api/db/withdrawals` 会自动调用 `ensureWithdrawalsTable()` 创建表。 + +### 问题 3:数据库连接失败 + +**症状**:返回 500,错误信息 "Connection lost" 或 "ECONNREFUSED" + +**修复**: + +1. **检查数据库服务**: + ```powershell + # MySQL + mysqld --version + ``` + +2. **检查环境变量**(`.env` 文件): + ``` + DB_HOST=localhost + DB_PORT=3306 + DB_USER=root + DB_PASSWORD=123456 + DB_NAME=mycontent + ``` + +3. **测试数据库连接**: + ```powershell + mysql -h localhost -u root -p123456 mycontent -e "SELECT 1" + ``` + +4. **重启服务**: + ```powershell + pm2 restart mycontent + ``` + +### 问题 4:查询返回空数组 + +**症状**:接口成功但 `withdrawals: []` + +**原因**:数据库中确实没有提现记录 + +**验证**: +```sql +SELECT COUNT(*) FROM withdrawals; +``` + +**解决**: +- 如果是正常情况(没有用户提现),这是预期行为 +- 如果需要测试数据,可以插入测试记录: + ```sql + INSERT INTO withdrawals (id, user_id, amount, status, created_at) + VALUES ('W_TEST_001', 'test_user', 50.00, 'pending', NOW()); + ``` + +### 问题 5:前端没有显示错误 + +**症状**:页面加载但没有数据,也没有错误提示 + +**原因**:错误被静默处理 + +**修复**: +1. 打开浏览器 Console 查看日志 +2. 检查是否有 `try-catch` 吞掉了错误 +3. 查看我们刚添加的详细错误日志 + +## 增强的错误处理 + +我已经增强了前端的错误处理,现在会: + +1. ✅ 在 Console 输出详细的请求和响应信息 +2. ✅ 显示明确的错误提示弹窗 +3. ✅ 记录 HTTP 状态码和错误消息 +4. ✅ 区分不同类型的错误(权限、服务器、网络) + +**新增的日志**: +```javascript +[Admin] 请求提现数据... +[Admin] 提现接口响应状态: 200 OK +[Admin] 提现接口返回数据: {...} +[Admin] 提现数据加载成功: 5 条 +``` + +**错误提示**: +- 权限错误:`获取提现记录失败 (401): 未授权访问,请先登录` +- 服务器错误:`获取提现记录失败 (500): Table doesn't exist` +- 网络错误:`加载提现数据失败: Failed to fetch` + +## 测试修复 + +### 1. 重启服务 + +```powershell +pm2 restart mycontent +# 或 +npm run dev +``` + +### 2. 清除缓存 + +``` +Ctrl + F5 强制刷新页面 +``` + +### 3. 测试流程 + +1. 访问 `http://localhost:3006/admin/distribution` +2. 点击"提现审核" tab +3. 查看 Console 输出 +4. 如果有错误,会弹窗显示具体信息 +5. 根据错误信息按照上述"常见问题修复"处理 + +### 4. 成功标志 + +- ✅ Console 显示 `[Admin] 提现数据加载成功: X 条` +- ✅ 页面显示提现列表(或"暂无数据") +- ✅ 没有错误弹窗 +- ✅ Network 标签显示 200 OK + +## 预防措施 + +### 1. 定期检查数据库连接 + +在 `lib/db.ts` 中添加心跳检测: +```typescript +setInterval(async () => { + try { + await query('SELECT 1') + } catch (error) { + console.error('[DB] 数据库连接检查失败:', error) + } +}, 60000) // 每分钟检查一次 +``` + +### 2. 添加健康检查接口 + +```typescript +// app/api/health/route.ts +export async function GET() { + try { + await query('SELECT 1') + return NextResponse.json({ status: 'ok', db: 'connected' }) + } catch (error) { + return NextResponse.json( + { status: 'error', db: 'disconnected', error: error.message }, + { status: 500 } + ) + } +} +``` + +访问 `/api/health` 检查系统状态。 + +### 3. 使用数据库连接池 + +确保 `lib/db.ts` 使用连接池: +```typescript +const pool = mysql.createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0 +}) +``` + +## 相关文件 + +- **前端页面**:`app/admin/distribution/page.tsx` +- **后端 API**:`app/api/db/withdrawals/route.ts` +- **权限验证**:`lib/admin-auth.ts` +- **数据库配置**:`lib/db.ts` +- **测试脚本**:`scripts/test-withdrawals-api.js` +- **数据检查 SQL**:`scripts/check-withdrawals-data.sql` + +## 获取帮助 + +如果按照上述步骤仍无法解决,请提供以下信息: + +1. **浏览器 Console 完整日志**(包括 `[Admin]` 开头的所有日志) +2. **Network 标签中 `/api/db/withdrawals` 的完整请求和响应** +3. **服务器日志**(`pm2 logs` 或终端输出) +4. **数据库查询结果**: + ```sql + SHOW TABLES LIKE 'withdrawals'; + DESC withdrawals; + SELECT COUNT(*) FROM withdrawals; + ``` + +## 版本信息 + +- **更新时间**:2026-02-04 +- **修改内容**: + 1. 增强前端错误处理和日志输出 + 2. 添加详细的错误提示弹窗 + 3. 创建诊断和修复指南 + 4. 提供测试脚本和 SQL 检查工具 diff --git a/开发文档/8、部署/本次更新总结.md b/开发文档/8、部署/本次更新总结.md deleted file mode 100644 index e5e6553a..00000000 --- a/开发文档/8、部署/本次更新总结.md +++ /dev/null @@ -1,228 +0,0 @@ -# 本次更新总结 - -## 📋 更新内容 - -### 1. 后台订单显示优化 ✅ - -#### 1.1 订单API增强 -**文件**: `app/api/orders/route.ts` - -**修改**: -- JOIN `users` 表获取购买者信息 -- 返回 `userNickname` 和 `userAvatar` 字段 - -```typescript -// 新增返回字段 -{ - userNickname: string | null, // 购买者昵称 - userAvatar: string | null // 购买者头像URL -} -``` - -#### 1.2 主仪表盘优化 -**文件**: `app/admin/page.tsx` - -**显示内容**: -- ✅ 购买者真实头像(如果有) -- ✅ 购买者昵称 -- ✅ 完整书名和章节信息 -- ✅ 商品类型标签 -- ✅ 优化的布局和时间格式 - -**头像显示逻辑**: -```typescript -// 优先显示真实头像 -if (userAvatar) { - -} -// 头像加载失败或不存在时,显示首字母 -else { -
{nickname.charAt(0)}
-} -``` - -**效果**: -``` -[头像] 张三 · 《一场Soul的创业实验》 - 章节购买 | 02-04 14:30 +¥0.95 - 推荐: ABC123 微信 -``` - -#### 1.3 订单管理页面优化 -**文件**: `app/admin/orders/page.tsx` - -**改进**: -- ✅ 从API获取订单(包含购买者信息) -- ✅ 显示完整书名和章节 -- ✅ 增强搜索(支持昵称、手机号、商品名、订单号) -- ✅ 优化状态筛选 -- ✅ 改进数据加载逻辑 - ---- - -### 2. 自动解绑API接口 ✅ - -#### 2.1 创建API接口 -**文件**: `app/api/cron/unbind-expired/route.ts`(新增) - -**接口地址**: -``` -GET https://soul.quwanzhi.com/api/cron/unbind-expired?secret=soul_cron_unbind_2026 -``` - -**功能**: -- 查找 `status = 'active' AND expiry_date < NOW() AND purchase_count = 0` 的绑定 -- 批量更新为 `status = 'expired'` -- 更新推荐人的 `referral_count` - -**优势**: -- ✅ 无需配置服务器环境 -- ✅ 无需配置数据库连接 -- ✅ 宝塔面板直接调用URL -- ✅ 集成在应用中,易于维护 -- ✅ 详细的日志输出 - -#### 2.2 配置文档 -**文件**: `开发文档/8、部署/自动解绑API配置说明.md`(新增) - -包含: -- 接口详细说明 -- 宝塔面板配置步骤 -- 返回数据格式 -- 日志示例 -- 手动测试方法 -- 监控与告警建议 - ---- - -## 🔧 宝塔面板配置 - -### 定时任务配置(每30分钟执行) - -**任务类型**: 访问URL - -**URL地址**: -``` -https://soul.quwanzhi.com/api/cron/unbind-expired?secret=soul_cron_unbind_2026 -``` - -**执行周期**: N分钟 → 30 - -**任务名称**: 自动解绑过期推荐关系 - ---- - -## 📝 修改的文件清单 - -### 新增文件 -1. ✅ `app/api/cron/unbind-expired/route.ts` - 自动解绑API接口 -2. ✅ `开发文档/8、部署/自动解绑API配置说明.md` - API配置文档 -3. ✅ `开发文档/8、部署/后台订单显示优化说明.md` - 订单优化文档 -4. ✅ `开发文档/8、部署/本次更新总结.md` - 本文档 - -### 修改文件 -1. ✅ `app/api/orders/route.ts` - 添加 JOIN users -2. ✅ `app/admin/page.tsx` - 优化订单显示,支持真实头像 -3. ✅ `app/admin/orders/page.tsx` - 优化订单管理页面 - ---- - -## 🚀 部署步骤 - -### 1. 构建项目 -```bash -pnpm build -``` - -### 2. 部署到服务器 -```bash -python devlop.py -``` - -### 3. 重启PM2服务 -在宝塔面板: -- 进入「软件商店」→「Node版本管理」→「模块管理」 -- 或直接在终端:`pm2 restart soul` - -### 4. 配置定时任务 -按照 `自动解绑API配置说明.md` 在宝塔面板配置计划任务 - -### 5. 测试验证 -- 访问后台管理页面,查看订单显示 -- 手动执行定时任务,查看解绑效果 - ---- - -## ✅ 测试清单 - -### 后台订单显示 -- [ ] 主仪表盘"最近订单"显示购买者头像 -- [ ] 头像加载失败时正确显示首字母 -- [ ] 显示完整书名和章节信息 -- [ ] 订单管理页面搜索功能正常 -- [ ] 状态筛选功能正常 - -### 自动解绑API -- [ ] 手动访问API接口返回正确数据 -- [ ] 宝塔计划任务配置成功 -- [ ] 手动执行任务成功 -- [ ] 日志输出正常 -- [ ] 数据库记录正确更新 - ---- - -## 📊 数据库影响 - -### 无需数据库迁移 -- ✅ 只修改查询逻辑,不改表结构 -- ✅ 使用 LEFT JOIN,兼容旧数据 -- ✅ 新增的 userNickname 和 userAvatar 是查询结果,不存储 - ---- - -## 🔍 监控建议 - -### 订单显示 -- 检查头像加载速度 -- 检查昵称显示是否正确 -- 检查搜索功能是否准确 - -### 自动解绑 -- 每周查看一次解绑日志 -- 如果单次解绑 > 100,检查是否异常 -- 如果连续失败,检查接口状态 - ---- - -## 📚 相关文档 - -1. `后台订单显示优化说明.md` - 订单显示详细说明 -2. `自动解绑API配置说明.md` - API配置详细说明 -3. `新分销逻辑-宝塔操作清单.md` - 完整部署清单 -4. `新分销逻辑设计方案.md` - 分销逻辑设计 -5. `代码逻辑和数据库最终检查清单.md` - 代码验证清单 - ---- - -## ✅ 完成状态 - -- ✅ 订单API增强(JOIN users) -- ✅ 主仪表盘优化(真实头像 + 商品信息) -- ✅ 订单管理页面优化(搜索增强) -- ✅ 自动解绑API接口创建 -- ✅ 配置文档编写 -- ✅ 测试验证清单 - -**所有功能已完成,可以部署!** - ---- - -## 🎯 下一步 - -1. 本地构建:`pnpm build` -2. 部署到服务器:`python devlop.py` -3. 重启PM2服务 -4. 配置宝塔定时任务(30分钟) -5. 测试验证所有功能 - -需要帮助的话随时告诉我! diff --git a/开发文档/8、部署/累计佣金计算优化-简化版.md b/开发文档/8、部署/累计佣金计算优化-简化版.md deleted file mode 100644 index 1efafbe6..00000000 --- a/开发文档/8、部署/累计佣金计算优化-简化版.md +++ /dev/null @@ -1,285 +0,0 @@ -# 累计佣金计算优化 - 简化版 - -## 一、优化背景 - -**原方案(复杂)**: -从 `referral_bindings.total_commission` 累加所有佣金 - -**问题**: -- 需要依赖 `referral_bindings` 表的 `total_commission` 字段维护 -- 逻辑复杂,需要在支付回调时同时更新两个地方 -- 数据同步可能出现不一致 - -**新方案(简化)**: -直接从 `orders` 表查询推荐人的所有订单金额,然后乘以分成比例 - -**优势**: -- ✅ 更简单,直接查询订单表 -- ✅ 数据来源唯一,不会不一致 -- ✅ 实时计算,无需维护额外字段 -- ✅ 逻辑清晰,易于理解 - -## 二、核心逻辑 - -### 累计佣金公式 - -``` -累计佣金 = 推荐订单总金额 × 分成比例 -``` - -**示例**: -``` -用户A推荐了3个用户,他们分别购买: -- 用户B:¥10 -- 用户C:¥20 -- 用户D:¥30 - -推荐订单总金额 = ¥60 -分成比例 = 90% -累计佣金 = ¥60 × 90% = ¥54 -``` - -### SQL实现 - -```sql -SELECT - -- 推荐订单总金额 - (SELECT COALESCE(SUM(amount), 0) - FROM orders - WHERE referrer_id = u.id AND status = 'paid') as total_referral_amount - -FROM users u -WHERE u.id = ? -``` - -### 后端计算 - -```typescript -// 累计佣金 = 订单金额 × 分成比例 -totalCommission: Math.round( - (parseFloat(stats.total_referral_amount || 0) * distributorShare) * 100 -) / 100 -``` - -## 三、代码修改 - -### 文件:`app/api/referral/data/route.ts` - -#### 修改1:查询订单总金额 - -**位置**:聚合统计查询中 - -**修改前**: -```typescript --- 累计佣金总额(从所有推荐关系中累加) -(SELECT COALESCE(SUM(total_commission), 0) - FROM referral_bindings - WHERE referrer_id = u.id) as total_commission_from_bindings -``` - -**修改后**: -```typescript --- 累计佣金总额(直接从订单表计算) -(SELECT COALESCE(SUM(amount), 0) - FROM orders - WHERE referrer_id = u.id AND status = 'paid') as total_referral_amount -``` - -#### 修改2:计算累计佣金 - -**修改前**: -```typescript -totalCommission: Math.round( - parseFloat(stats.total_commission_from_bindings || 0) * 100 -) / 100 -``` - -**修改后**: -```typescript -totalCommission: Math.round( - (parseFloat(stats.total_referral_amount || 0) * distributorShare) * 100 -) / 100 -``` - -## 四、数据对比 - -### 方案对比 - -| 维度 | 原方案(referral_bindings) | 新方案(orders) | -|------|---------------------------|-----------------| -| 数据来源 | `referral_bindings.total_commission` | `orders.amount` | -| 查询复杂度 | 简单(SUM聚合) | 简单(SUM聚合) | -| 数据维护 | 需要在支付回调更新 | 无需维护,自动准确 | -| 数据一致性 | 可能不一致 | 永远一致 ✅ | -| 计算准确性 | 依赖字段维护 | 实时计算,100%准确 ✅ | -| 代码复杂度 | 高 | 低 ✅ | - -### 性能对比 - -| 指标 | 原方案 | 新方案 | 差异 | -|------|--------|--------|------| -| 查询表数量 | 1个 | 1个 | 相同 | -| 查询类型 | SUM聚合 | SUM聚合 | 相同 | -| 扫描行数 | ~绑定数 | ~订单数 | 相同量级 | -| 查询速度 | ~10-20ms | ~10-20ms | 相同 | - -**结论**:性能完全相同,但新方案更简单、更准确! - -## 五、验证方法 - -### 1. 数据库验证 - -```sql --- 查询用户的推荐订单总金额 -SELECT - referrer_id, - COUNT(*) as order_count, - SUM(amount) as total_amount, - SUM(amount) * 0.9 as total_commission_90 -FROM orders -WHERE referrer_id = 'user_xxx' AND status = 'paid' -GROUP BY referrer_id; -``` - -**示例结果**: -``` -referrer_id order_count total_amount total_commission_90 -user_123 5 100.00 90.00 -``` - -### 2. API验证 - -```bash -# 调用接口 -curl "https://your-domain.com/api/referral/data?userId=user_123" -``` - -**检查返回值**: -```json -{ - "data": { - "totalCommission": 90.00 // 应该等于 total_amount × 90% - } -} -``` - -### 3. 小程序验证 - -刷新分销中心,检查: -- ✅ 累计佣金 = 所有推荐订单金额 × 90% -- ✅ 数值准确,与数据库一致 - -## 六、数据流程 - -### 完整流程 - -``` -用户B点击用户A的推荐链接 - ↓ -创建绑定关系 (referral_bindings) - ↓ -用户B购买商品¥10 - ↓ -创建订单 (orders) - - amount: 10 - - referrer_id: user_A ← 关键字段 - - status: 'paid' - ↓ -支付回调更新用户收益 - - users.pending_earnings += 9 (可提现) - ↓ -显示累计佣金(实时计算) - - 查询: SELECT SUM(amount) FROM orders WHERE referrer_id = 'user_A' - - 计算: ¥10 × 90% = ¥9 -``` - -### 关键字段说明 - -**orders表**: -- `referrer_id` - 推荐人ID(关键字段)✅ -- `amount` - 订单金额 -- `status` - 订单状态(只统计'paid') - -**计算公式**: -``` -累计佣金 = SUM(orders.amount WHERE referrer_id = userId) × distributorShare -``` - -## 七、与原方案的兼容性 - -### referral_bindings.total_commission 字段 - -**现状**: -- 该字段仍然存在 -- 支付回调仍在更新 -- 用于其他统计(如已转化用户列表) - -**处理方案**: -- ✅ 保留该字段,继续维护 -- ✅ 但累计佣金改为从 orders 表计算 -- ✅ 两个数据源应该一致(可用于交叉验证) - -### 数据一致性验证 - -```sql --- 验证两种计算方式的结果是否一致 -SELECT - u.id, - u.nickname, - -- 方案1:从 referral_bindings 累加 - COALESCE((SELECT SUM(total_commission) FROM referral_bindings WHERE referrer_id = u.id), 0) as from_bindings, - -- 方案2:从 orders 计算 - COALESCE((SELECT SUM(amount) * 0.9 FROM orders WHERE referrer_id = u.id AND status = 'paid'), 0) as from_orders, - -- 差异 - COALESCE((SELECT SUM(total_commission) FROM referral_bindings WHERE referrer_id = u.id), 0) - - COALESCE((SELECT SUM(amount) * 0.9 FROM orders WHERE referrer_id = u.id AND status = 'paid'), 0) as difference -FROM users u -WHERE u.referral_count > 0 -HAVING ABS(difference) > 0.01 -- 只显示有差异的 -ORDER BY ABS(difference) DESC; -``` - -**如果有差异**:说明数据同步有问题,需要检查支付回调逻辑。 - -## 八、总结 - -### 优化成果 - -| 维度 | 优化前 | 优化后 | -|------|--------|--------| -| 数据来源 | `referral_bindings` | `orders` ✅ | -| 计算方式 | SUM(total_commission) | SUM(amount) × 90% ✅ | -| 数据准确性 | 依赖字段维护 | 实时计算,100%准确 ✅ | -| 代码复杂度 | 较高 | 更低 ✅ | -| 维护成本 | 需要同步更新 | 无需额外维护 ✅ | - -### 核心SQL - -**之前**: -```sql -(SELECT SUM(total_commission) FROM referral_bindings WHERE referrer_id = u.id) -``` - -**现在**: -```sql -(SELECT SUM(amount) FROM orders WHERE referrer_id = u.id AND status = 'paid') -``` - -**计算**: -```typescript -totalCommission = total_referral_amount × distributorShare -``` - -### 关键优势 - -1. **更简单** - 一个表,一次查询 -2. **更准确** - 直接从订单数据计算,无中间环节 -3. **更可靠** - 不依赖其他字段的维护 -4. **更易理解** - 逻辑直观,新人也能看懂 - ---- - -**优化时间**:2026-02-04 -**优化类型**:简化逻辑 -**影响范围**:累计佣金显示 -**性能影响**:无(查询复杂度相同) diff --git a/开发文档/8、部署/订单管理商品显示优化.md b/开发文档/8、部署/订单管理商品显示优化.md new file mode 100644 index 00000000..65b65506 --- /dev/null +++ b/开发文档/8、部署/订单管理商品显示优化.md @@ -0,0 +1,508 @@ +# 订单管理商品显示优化 + +## 问题描述 + +在后台管理的"交易中心-订单管理" tab 中,商品列显示不正确: + +**问题截图**: +- 显示为"章节undefined" +- 无法区分单章购买、全本购买、匹配次数购买 +- 缺少书名和章节详细信息 + +**期望效果**: +- 单章购买:显示"《底层逻辑》- 节标题",副标题显示章标题 +- 全本购买:显示"《底层逻辑》- 全本",副标题显示"全书解锁" +- 匹配次数:显示"匹配次数购买",副标题显示"功能权益" + +## 数据结构分析 + +### Orders 表结构 + +```sql +CREATE TABLE orders ( + id VARCHAR(50) PRIMARY KEY, + order_sn VARCHAR(50) UNIQUE NOT NULL, + user_id VARCHAR(50) NOT NULL, + product_type ENUM('section', 'fullbook', 'match') NOT NULL, + product_id VARCHAR(50), -- 章节ID(如 '1.1', '2.3')或书籍ID + amount DECIMAL(10,2) NOT NULL, + description VARCHAR(200), + status ENUM('created', 'pending', 'paid', 'cancelled', 'refunded', 'expired'), + referrer_id VARCHAR(50) NULL, + referral_code VARCHAR(20) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + -- ... 其他字段 +) +``` + +### Chapters 表结构 + +```sql +CREATE TABLE chapters ( + id VARCHAR(20) PRIMARY KEY, -- 章节ID,如 '1.1', 'preface' + part_id VARCHAR(20) NOT NULL, + part_title VARCHAR(100) NOT NULL, -- 篇标题 + chapter_id VARCHAR(20) NOT NULL, + chapter_title VARCHAR(200) NOT NULL, -- 章标题 + section_title VARCHAR(200) NOT NULL, -- 节标题 + content LONGTEXT NOT NULL, + word_count INT DEFAULT 0, + is_free BOOLEAN DEFAULT FALSE, + price DECIMAL(10,2) DEFAULT 1.00, + -- ... 其他字段 +) +``` + +### 购买类型说明 + +| product_type | product_id | 说明 | 显示格式 | +|-------------|-----------|------|---------| +| `section` | `'1.1'`, `'2.3'` | 单章购买 | 《底层逻辑》- 节标题
章标题 | +| `fullbook` | `'book-1'` 或 NULL | 全本购买 | 《底层逻辑》- 全本
全书解锁 | +| `match` | NULL | 匹配次数 | 匹配次数购买
功能权益 | + +## 解决方案 + +### 1. 修改订单 API(`app/api/orders/route.ts`) + +#### 1.1 更新数据转换函数 + +**修改前**: +```typescript +function rowToOrder(row: Record) { + return { + id: row.id, + productType: row.product_type, + productId: row.product_id, + amount: parseFloat(row.amount as string) || 0, + // ... 其他字段 + userNickname: row.user_nickname ?? null, + userAvatar: row.user_avatar ?? null, + } +} +``` + +**修改后**: +```typescript +function rowToOrder(row: Record) { + return { + id: row.id, + productType: row.product_type, + productId: row.product_id, + amount: parseFloat(row.amount as string) || 0, + // ... 其他字段 + userNickname: row.user_nickname ?? null, + userAvatar: row.user_avatar ?? null, + // 新增:章节信息 + bookName: '《底层逻辑》', // 书名(固定) + chapterTitle: row.chapter_title ?? null, // 章标题 + sectionTitle: row.section_title ?? null, // 节标题 + } +} +``` + +#### 1.2 更新 SQL 查询 + +**修改前**(仅 JOIN users 表): +```sql +SELECT o.*, u.nickname as user_nickname, u.avatar as user_avatar +FROM orders o +LEFT JOIN users u ON o.user_id = u.id +ORDER BY o.created_at DESC +``` + +**修改后**(JOIN users + chapters 表): +```sql +SELECT + o.*, + u.nickname as user_nickname, + u.avatar as user_avatar, + c.chapter_title, + c.section_title +FROM orders o +LEFT JOIN users u ON o.user_id = u.id +LEFT JOIN chapters c ON o.product_id = c.id AND o.product_type = 'section' +ORDER BY o.created_at DESC +``` + +**关键点**: +- `LEFT JOIN chapters c` 仅在 `product_type = 'section'` 时关联 +- 这样全本购买和匹配次数购买的 `chapter_title` 和 `section_title` 为 NULL +- 不影响查询性能 + +### 2. 修改前端类型定义(`app/admin/distribution/page.tsx`) + +**修改前**: +```typescript +interface Order { + id: string + userId: string + type: 'section' | 'fullbook' | 'match' + sectionId?: string + sectionTitle?: string + amount: number + status: 'pending' | 'completed' | 'failed' + // ... 其他字段 +} +``` + +**修改后**: +```typescript +interface Order { + id: string + userId: string + productType: 'section' | 'fullbook' | 'match' // API 返回的字段名 + type?: 'section' | 'fullbook' | 'match' // 兼容旧字段名 + productId?: string + sectionId?: string // 兼容旧字段名 + bookName?: string // 书名 + chapterTitle?: string // 章标题 + sectionTitle?: string // 节标题 + amount: number + status: 'pending' | 'completed' | 'failed' | 'paid' | 'created' + // ... 其他字段 +} +``` + +### 3. 修改前端显示逻辑 + +#### 3.1 商品信息显示 + +**修改前**(显示"章节undefined"): +```tsx +

+ {order.type === 'fullbook' ? '整本购买' : + order.type === 'match' ? '匹配次数' : + order.sectionTitle || `章节${order.sectionId}`} +

+

+ {order.type === 'fullbook' ? '全书' : + order.type === 'match' ? '功能' : '单章'} +

+``` + +**修改后**(显示完整商品信息): +```tsx +

+ {(() => { + const type = order.productType || order.type + if (type === 'fullbook') { + return `${order.bookName || '《底层逻辑》'} - 全本` + } else if (type === 'match') { + return '匹配次数购买' + } else { + // section - 单章购买 + return `${order.bookName || '《底层逻辑》'} - ${order.sectionTitle || order.chapterTitle || `章节${order.productId || order.sectionId || ''}`}` + } + })()} +

+

+ {(() => { + const type = order.productType || order.type + if (type === 'fullbook') { + return '全书解锁' + } else if (type === 'match') { + return '功能权益' + } else { + return order.chapterTitle || '单章购买' + } + })()} +

+``` + +**显示效果**: + +| 购买类型 | 主标题 | 副标题 | +|---------|--------|--------| +| 单章购买 | 《底层逻辑》- 荷包:电动车出租的被动收入模式 | 第1章|人与人之间的底层逻辑 | +| 全本购买 | 《底层逻辑》- 全本 | 全书解锁 | +| 匹配次数 | 匹配次数购买 | 功能权益 | + +#### 3.2 搜索功能增强 + +**修改前**: +```typescript +if (searchTerm) { + const term = searchTerm.toLowerCase() + return ( + order.id?.toLowerCase().includes(term) || + order.userNickname?.toLowerCase().includes(term) || + order.userPhone?.includes(term) || + order.sectionTitle?.toLowerCase().includes(term) || + (order.referrerCode && order.referrerCode.toLowerCase().includes(term)) + ) +} +``` + +**修改后**(增加书名和章标题搜索): +```typescript +if (searchTerm) { + const term = searchTerm.toLowerCase() + return ( + order.id?.toLowerCase().includes(term) || + order.userNickname?.toLowerCase().includes(term) || + order.userPhone?.includes(term) || + order.sectionTitle?.toLowerCase().includes(term) || + order.chapterTitle?.toLowerCase().includes(term) || + order.bookName?.toLowerCase().includes(term) || + (order.referrerCode && order.referrerCode.toLowerCase().includes(term)) || + (order.referrerNickname && order.referrerNickname.toLowerCase().includes(term)) + ) +} +``` + +**搜索能力**: +- 可搜索订单 ID +- 可搜索用户昵称和手机号 +- 可搜索书名、章标题、节标题 +- 可搜索推荐人信息 + +## 扩展性设计 + +### 多本书支持 + +当前书名是硬编码的 `'《底层逻辑》'`。如果未来需要支持多本书,可以: + +#### 方案1:在订单表增加 book_id 字段 + +```sql +ALTER TABLE orders ADD COLUMN book_id VARCHAR(20); +``` + +#### 方案2:创建 books 表 + +```sql +CREATE TABLE books ( + id VARCHAR(20) PRIMARY KEY, + title VARCHAR(100) NOT NULL, -- 如《底层逻辑》 + author VARCHAR(50), + price DECIMAL(10,2), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 修改 chapters 表增加 book_id +ALTER TABLE chapters ADD COLUMN book_id VARCHAR(20); +ALTER TABLE chapters ADD INDEX idx_book_id (book_id); +``` + +然后修改 API 查询: +```sql +SELECT + o.*, + u.nickname as user_nickname, + u.avatar as user_avatar, + c.chapter_title, + c.section_title, + b.title as book_name +FROM orders o +LEFT JOIN users u ON o.user_id = u.id +LEFT JOIN chapters c ON o.product_id = c.id AND o.product_type = 'section' +LEFT JOIN books b ON c.book_id = b.id +ORDER BY o.created_at DESC +``` + +### 章节ID规范 + +建议统一章节 ID 格式: + +| 类型 | ID 格式 | 示例 | +|-----|---------|------| +| 序言 | `preface` | `preface` | +| 正文 | `{章}.{节}` | `1.1`, `2.3`, `9.14` | +| 尾声 | `epilogue` | `epilogue` | +| 附录 | `appendix-{序号}` | `appendix-1`, `appendix-2` | + +## 修改文件清单 + +### 后端 API + +1. **`app/api/orders/route.ts`** + - 修改 `rowToOrder()` 函数,增加 `bookName`, `chapterTitle`, `sectionTitle` 字段 + - 修改 SQL 查询,JOIN `chapters` 表获取章节信息 + - 行号:12-32, 44-59 + +### 前端页面 + +2. **`app/admin/distribution/page.tsx`** + - 更新 `Order` 接口定义,增加新字段 + - 修改商品信息显示逻辑,根据 `productType` 显示不同格式 + - 增强搜索功能,支持搜索书名、章标题、节标题 + - 行号:78-96, 636-647, 659-685 + +## 验证步骤 + +### 1. 重启服务 + +```powershell +pm2 restart mycontent +# 或 +npm run dev +``` + +### 2. 访问页面 + +打开浏览器:`http://localhost:3006/admin/distribution` + +### 3. 测试订单管理 Tab + +1. **查看商品显示**: + - 点击"订单管理" tab + - 检查商品列是否正确显示: + - 单章:《底层逻辑》- 节标题(副标题:章标题) + - 全本:《底层逻辑》- 全本(副标题:全书解锁) + - 匹配:匹配次数购买(副标题:功能权益) + +2. **测试搜索**: + - 搜索"底层逻辑",应该能找到所有订单 + - 搜索章节名(如"荷包"),应该能找到对应订单 + - 搜索用户昵称,应该能找到对应订单 + +3. **检查不同订单类型**: + - 如果有单章购买订单,确认显示完整的"书名 - 节标题" + - 如果有全本购买订单,确认显示"《底层逻辑》- 全本" + - 如果有匹配次数订单,确认显示"匹配次数购买" + +### 4. 查看 API 响应 + +打开 DevTools → Network 标签 → 找到 `/api/orders` 请求: + +```json +{ + "success": true, + "orders": [ + { + "id": "ORDER_123", + "productType": "section", + "productId": "1.1", + "bookName": "《底层逻辑》", + "chapterTitle": "第1章|人与人之间的底层逻辑", + "sectionTitle": "1.1 荷包:电动车出租的被动收入模式", + "amount": 1.00, + "userNickname": "张三", + "status": "paid" + }, + { + "id": "ORDER_456", + "productType": "fullbook", + "productId": null, + "bookName": "《底层逻辑》", + "chapterTitle": null, + "sectionTitle": null, + "amount": 99.00, + "userNickname": "李四", + "status": "paid" + } + ] +} +``` + +### 5. 数据库验证 + +```sql +-- 查看订单和章节信息 +SELECT + o.id, + o.product_type, + o.product_id, + o.amount, + c.chapter_title, + c.section_title, + u.nickname +FROM orders o +LEFT JOIN users u ON o.user_id = u.id +LEFT JOIN chapters c ON o.product_id = c.id AND o.product_type = 'section' +WHERE o.status = 'paid' +ORDER BY o.created_at DESC +LIMIT 10; + +-- 验证章节数据 +SELECT + id, + chapter_title, + section_title, + is_free, + price +FROM chapters +WHERE id IN ('1.1', '1.2', '2.1') +LIMIT 5; +``` + +## 常见问题 + +### Q1: 为什么只对 product_type='section' 进行 JOIN? + +**答**: +- 全本购买和匹配次数购买不需要章节信息 +- `LEFT JOIN` 的条件 `o.product_type = 'section'` 确保只有单章购买才查询 chapters 表 +- 这样可以提高查询性能,避免不必要的 JOIN + +### Q2: 如果 chapters 表没有某个章节数据怎么办? + +**答**: +显示逻辑有兜底机制: +```typescript +order.sectionTitle || order.chapterTitle || `章节${order.productId}` +``` +- 优先显示节标题 +- 其次显示章标题 +- 最后显示"章节 + ID" + +### Q3: 全本购买的 product_id 是什么? + +**答**: +全本购买的 `product_id` 可能是: +- `NULL`(早期订单) +- `'book-1'`(如果有书籍ID) +- 固定值如 `'fullbook'` + +建议在创建订单时统一为 `NULL` 或 `'fullbook'`。 + +### Q4: 如何添加新的书籍? + +**答**: +1. 在 `books` 表中新增书籍记录(如果使用了多本书支持) +2. 在 `chapters` 表中新增章节,关联 `book_id` +3. 修改 API 的 `rowToOrder()` 函数,从 JOIN 的 books 表读取书名 +4. 前端显示逻辑无需修改(自动读取 `order.bookName`) + +## 性能优化 + +### 索引优化 + +为 JOIN 查询添加索引: + +```sql +-- orders 表索引 +CREATE INDEX idx_orders_product ON orders(product_type, product_id); +CREATE INDEX idx_orders_user ON orders(user_id); + +-- chapters 表索引 +CREATE INDEX idx_chapters_id ON chapters(id); + +-- users 表索引(如果没有) +CREATE INDEX idx_users_id ON users(id); +``` + +### 查询优化 + +如果订单量很大(>10万),考虑: +1. 分页查询:`LIMIT 100 OFFSET 0` +2. 时间范围过滤:`WHERE o.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)` +3. 缓存热门查询结果 + +## 相关文件 + +- **订单 API**:`app/api/orders/route.ts` +- **交易中心页面**:`app/admin/distribution/page.tsx` +- **数据库初始化**:`lib/db.ts` +- **书籍数据**:`lib/book-data.ts` + +## 版本信息 + +- **修改时间**:2026-02-04 +- **修改内容**: + 1. 修改订单 API,JOIN chapters 表获取章节信息 + 2. 返回 bookName, chapterTitle, sectionTitle 字段 + 3. 优化前端显示逻辑,区分单章/全本/匹配次数 + 4. 增强搜索功能,支持搜索书名和章节标题 + 5. 设计多本书扩展方案 diff --git a/开发文档/8、部署/订单管理数据类型错误修复.md b/开发文档/8、部署/订单管理数据类型错误修复.md new file mode 100644 index 00000000..2dd3e2a3 --- /dev/null +++ b/开发文档/8、部署/订单管理数据类型错误修复.md @@ -0,0 +1,411 @@ +# 订单管理数据类型错误修复 + +## 问题描述 + +在后台管理的"交易中心-订单管理" tab 中,点击时出现以下错误: + +### 错误 1:JSON 解析错误 +``` +SyntaxError: Unexpected token '<', ") { + return { + id: row.id, + amount: row.amount, // 可能是字符串 "99.00" + // ... 其他字段 + } +} +``` + +**修改后**: +```typescript +function rowToOrder(row: Record) { + return { + id: row.id, + amount: parseFloat(row.amount as string) || 0, // 转换为数字 + // ... 其他字段 + } +} +``` + +### 2. 增强前端错误处理 + +**文件**:`app/admin/distribution/page.tsx` + +#### 2.1 概览数据加载 + +**修改前**: +```typescript +const overviewRes = await fetch('/api/admin/distribution/overview') +const overviewData = await overviewRes.json() // 可能抛出异常 +if (overviewData.success) { + setOverview(overviewData.overview) +} +``` + +**修改后**: +```typescript +try { + const overviewRes = await fetch('/api/admin/distribution/overview') + if (overviewRes.ok) { // 检查 HTTP 状态 + const overviewData = await overviewRes.json() + if (overviewData.success) { + setOverview(overviewData.overview) + } + } +} catch (overviewError) { + console.error('[Admin] 概览接口异常:', overviewError) +} +``` + +#### 2.2 订单数据加载 + +**修改前**: +```typescript +const ordersRes = await fetch('/api/orders') +const ordersData = await ordersRes.json() // 可能抛出异常 +if (ordersData.success) { + const enrichedOrders = ordersData.orders.map((order: Order) => ({ + ...order, + // 假设 amount 是数字 + })) + setOrders(enrichedOrders) +} +``` + +**修改后**: +```typescript +try { + const ordersRes = await fetch('/api/orders') + if (!ordersRes.ok) { + console.error('[Admin] 订单接口错误:', ordersRes.status) + setOrders([]) + } else { + const ordersData = await ordersRes.json() + if (ordersData.success && ordersData.orders) { + const enrichedOrders = ordersData.orders.map((order: Order) => ({ + ...order, + amount: parseFloat(order.amount as any) || 0, // 双重保险 + // ... 其他字段 + })) + setOrders(enrichedOrders) + } else { + setOrders([]) + } + } +} catch (orderError) { + console.error('[Admin] 加载订单数据失败:', orderError) + setOrders([]) +} +``` + +#### 2.3 其他数据加载 + +对用户数据、绑定数据、提现数据也采用同样的错误处理模式: + +```typescript +try { + const res = await fetch('/api/...') + if (res.ok) { + const data = await res.json() + // 处理数据 + } else { + // 设置为空数组 + } +} catch (error) { + console.error('加载失败:', error) + // 设置为空数组 +} +``` + +### 3. 增强前端渲染安全性 + +**文件**:`app/admin/distribution/page.tsx` + +#### 3.1 订单金额显示 + +**修改前**: +```typescript +¥{(order.amount || 0).toFixed(2)} +// 如果 amount 是字符串,会报错 +``` + +**修改后**: +```typescript +¥{typeof order.amount === 'number' + ? order.amount.toFixed(2) + : parseFloat(order.amount || '0').toFixed(2)} +// 检查类型,确保安全 +``` + +#### 3.2 推荐人收益显示 + +**修改前**: +```typescript +{order.referrerEarnings ? `¥${order.referrerEarnings.toFixed(2)}` : '-'} +// 如果是字符串,会报错 +``` + +**修改后**: +```typescript +{order.referrerEarnings + ? `¥${(typeof order.referrerEarnings === 'number' + ? order.referrerEarnings + : parseFloat(order.referrerEarnings)).toFixed(2)}` + : '-'} +``` + +## 修改文件清单 + +### 后端 API + +1. **`app/api/orders/route.ts`** + - 修改 `rowToOrder()` 函数 + - 将 `amount` 字段从字符串转换为数字 + - 行号:20 + +### 前端页面 + +2. **`app/admin/distribution/page.tsx`** + - 增强概览数据加载的错误处理(行号:117-129) + - 增强用户数据加载的错误处理(行号:131-140) + - 增强订单数据加载的错误处理(行号:142-167) + - 增强绑定数据加载的错误处理(行号:169-179) + - 增强提现数据加载的错误处理(行号:181-191) + - 修复订单金额显示的类型安全(行号:667) + - 修复推荐人收益显示的类型安全(行号:692-696) + +## 防御性编程原则 + +本次修复遵循以下防御性编程原则: + +### 1. 多层防御 + +- **数据库层**:查询结果可能是字符串 +- **API 层**:转换为数字类型 +- **前端加载层**:再次确保数字类型 +- **前端渲染层**:类型检查 + 类型转换 + +### 2. 优雅降级 + +- API 调用失败时,设置为空数组 `[]`,而不是中断整个加载流程 +- 单个 API 失败不影响其他 API 的加载 +- 页面始终可用,只是某些 tab 可能没有数据 + +### 3. 详细日志 + +每个错误都有清晰的日志输出: +```typescript +console.error('[Admin] 订单接口错误:', ordersRes.status) +console.error('[Admin] 加载订单数据失败:', orderError) +``` + +方便定位问题源头。 + +### 4. 类型安全 + +渲染时不假设数据类型: +```typescript +typeof value === 'number' ? value.toFixed(2) : parseFloat(value).toFixed(2) +``` + +## 验证步骤 + +### 1. 重启服务 + +```powershell +pm2 restart mycontent +# 或 +npm run dev +``` + +### 2. 访问页面 + +打开浏览器:`http://localhost:3006/admin/distribution` + +### 3. 测试订单管理 Tab + +1. **查看订单列表**: + - 点击"订单管理" tab + - 应该能看到订单列表 + - 金额显示正常(如 `¥99.00`) + - 不应该有 `toFixed is not a function` 错误 + +2. **测试刷新**: + - 点击页面顶部的"刷新数据"按钮 + - 查看浏览器 DevTools Console + - 不应该有 JSON 解析错误 + - 即使某个 API 失败,其他 tab 仍然可用 + +3. **查看其他 Tab**: + - 数据概览 - 应该正常显示统计数据 + - 绑定管理 - 应该正常显示绑定关系 + - 提现审核 - 应该正常显示提现记录 + +### 4. 查看网络请求 + +打开 DevTools → Network 标签: +- `/api/orders` 应该返回 200 OK +- 响应体应该是正确的 JSON +- `amount` 字段应该是数字类型(在 JSON 中显示为 `99.00` 而不是 `"99.00"`) + +### 5. 查看控制台 + +服务器控制台应该输出: +``` +[Admin] 概览数据加载成功: { todayClicks: 0, ... } +``` + +如果某个 API 失败: +``` +[Admin] 订单接口错误: 500 Internal Server Error +[Admin] 加载订单数据失败: SyntaxError: ... +``` + +浏览器控制台也会有对应的错误日志,但页面不会崩溃。 + +## 数据库验证 + +检查订单表的数据类型: + +```sql +-- 查看订单表结构 +DESC orders; + +-- 确认 amount 字段类型应该是 DECIMAL +-- 输出示例: +-- amount | decimal(10,2) | YES | | NULL | | + +-- 查看订单数据 +SELECT id, user_id, amount, status, created_at +FROM orders +ORDER BY created_at DESC +LIMIT 10; + +-- 测试类型转换 +SELECT + amount, + CAST(amount AS DECIMAL(10,2)) as decimal_amount, + amount + 0 as numeric_amount +FROM orders +LIMIT 5; +``` + +## 常见问题 + +### Q1: 为什么要多层转换? + +**答**: +- 不同环境、不同 MySQL 驱动可能返回不同的类型 +- API 层转换保证了大部分情况的正确性 +- 前端再次转换作为"安全网",防止极端情况 +- 渲染层的类型检查防止运行时错误 + +### Q2: 其他数字字段是否也有这个问题? + +**答**: +可能有!建议对所有来自数据库的 `DECIMAL`、`FLOAT`、`DOUBLE` 类型字段都做转换: +- `referrerEarnings` +- `earnings` +- `withdrawn_earnings` +- `pending_earnings` + +可以在相应的 API 的 `rowToXxx()` 函数中统一处理。 + +### Q3: 为什么不修改数据库字段类型? + +**答**: +- `DECIMAL(10,2)` 是存储货币金额的标准类型 +- 保证精度,避免浮点数误差(如 0.1 + 0.2 ≠ 0.3) +- 问题在于查询结果的类型转换,而不是存储类型 + +### Q4: 如何避免类似问题? + +**答**: +1. **API 层统一转换**:在所有查询结果转换函数中明确类型转换 +2. **TypeScript 类型定义**:使用严格的类型定义 +3. **单元测试**:测试 API 返回值的类型 +4. **前端防御**:渲染前做类型检查 + +## 相关文件 + +- **订单 API**:`app/api/orders/route.ts` +- **交易中心页面**:`app/admin/distribution/page.tsx` +- **类型定义**:在 `page.tsx` 文件顶部(`interface Order`) + +## 后续优化建议 + +1. **统一类型转换工具**: + ```typescript + // lib/utils/db-converter.ts + export function toNumber(value: unknown, defaultValue = 0): number { + if (typeof value === 'number') return value + if (typeof value === 'string') { + const num = parseFloat(value) + return isNaN(num) ? defaultValue : num + } + return defaultValue + } + ``` + +2. **API 响应类型验证**: + 使用 Zod 或类似库验证 API 响应的类型 + +3. **全局错误边界增强**: + 提供更友好的错误提示,而不是显示技术错误 + +4. **监控告警**: + 记录 API 调用失败次数,超过阈值时告警 + +## 版本信息 + +- **修复时间**:2026-02-04 +- **修复内容**: + 1. 修复 `/api/orders` 中 `amount` 字段的类型转换 + 2. 增强交易中心页面的错误处理(5 个 API 调用) + 3. 增强前端渲染的类型安全(2 处 `.toFixed()` 调用) + 4. 采用"多层防御 + 优雅降级"策略 diff --git a/开发文档/8、部署/部署总览.md b/开发文档/8、部署/部署总览.md new file mode 100644 index 00000000..2ae1e6f2 --- /dev/null +++ b/开发文档/8、部署/部署总览.md @@ -0,0 +1,66 @@ +# 部署总览 + +**我是卡若。** + +本文档是 8、部署 的入口,只列核心文档;历史修复与优化说明见 [修复与优化记录](修复与优化记录/) 子目录。 + +--- + +## 一、核心部署 + +| 文档 | 说明 | +|------|------| +| [本地运行](本地运行.md) | 只写书不跑站 / 跑 Next 站点的本地步骤 | +| [Next.js宝塔部署方案](Next.js宝塔部署方案.md) | 宝塔 + Node/PM2 + Nginx,deploy/devlop 模式 | +| [Next.js自动化部署流程](Next.js自动化部署流程.md) | 自动化构建与发布 | +| [新分销逻辑-部署步骤](新分销逻辑-部署步骤.md) | 新分销上线:备份、迁移、部署、验证 | +| [新分销逻辑-宝塔操作清单](新分销逻辑-宝塔操作清单.md) | 宝塔侧具体操作 | +| [自动同步与分支策略](自动同步与分支策略.md) | 代码同步与分支策略 | +| [Standalone模式说明](Standalone模式说明.md) | Next.js standalone 输出说明 | + +--- + +## 二、环境与配置 + +| 文档 | 说明 | +|------|------| +| [MCP-MySQL配置说明](MCP-MySQL配置说明.md) | MCP 连接 MySQL | +| [Soul-MySQL-MCP配置说明](Soul-MySQL-MCP配置说明.md) | 本项目 MySQL MCP 配置 | +| [API接入说明](API接入说明.md) | 外部/小程序接入 API | +| [宝塔配置检查说明](宝塔配置检查说明.md) | 宝塔环境检查 | +| [宝塔面板配置订单同步定时任务](宝塔面板配置订单同步定时任务.md) | 订单同步定时任务 | + +--- + +## 三、设计与流程(必读) + +| 文档 | 说明 | +|------|------| +| [新分销逻辑设计方案](新分销逻辑设计方案.md) | 动态绑定、佣金归属、30天自动解绑 | +| [邀请码分销规则说明](邀请码分销规则说明.md) | 邀请码与分成规则 | +| [分销与绑定流程图](分销与绑定流程图.md) | 分销与绑定流程 | +| [章节阅读付费标准流程设计](章节阅读付费标准流程设计.md) | 阅读页付费流程 | +| [阅读页标准流程改造说明](阅读页标准流程改造说明.md) | 阅读页改造要点 | +| [支付接口清单](支付接口清单.md) | 支付相关接口列表 | +| [提现功能完整技术文档](../提现功能完整技术文档.md) | 微信支付商家转账到零钱 API 集成(签名、加解密、代码) | + +--- + +## 四、检查与诊断 + +| 文档 | 说明 | +|------|------| +| [代码逻辑和数据库最终检查清单](代码逻辑和数据库最终检查清单.md) | 上线前检查项 | +| [佣金计算逻辑检查](佣金计算逻辑检查.md) | 佣金逻辑校验 | +| [佣金问题-快速诊断和修复](佣金问题-快速诊断和修复.md) | 佣金问题排查 | + +--- + +## 五、修复与优化记录(历史说明,按需查阅) + +- **分销中心**:[loading 优化 v2](分销中心loading优化说明-v2.md)、[接口优化方案](分销中心接口优化方案.md)、[接口优化实施记录](分销中心接口优化实施记录.md)、[数据库连接错误修复](分销中心数据库连接错误修复.md)、[用户列表数据对接](分销中心用户列表数据对接说明.md)、[设置功能说明](分销中心设置功能说明.md) +- **提现与佣金**:[可提现金额计算修复](可提现金额计算修复.md)、[累计佣金计算修复](累计佣金计算修复说明.md)、[提现卡片数据优化](提现卡片数据优化说明.md)、[提现双向校验](提现双向校验实现.md)、[提现审核流程优化](提现审核流程优化.md)、[提现按钮状态修复](提现按钮状态修复说明.md)、[提现按钮逻辑修正](提现按钮逻辑修正.md)、[提现接口逻辑修正](提现接口逻辑修正.md)、[提现接口数据查询错误修复](提现接口数据查询错误修复.md)、[提现记录获取失败诊断指南](提现记录获取失败诊断指南.md)、[后台提现审核](后台提现审核功能完善说明.md)、[后台提现审核数据对接](后台提现审核数据对接.md)、[后台提现审核测试指南](后台提现审核-快速测试指南.md) +- **交易中心与订单**:[交易中心 Tab 按需加载优化](交易中心Tab按需加载优化.md)、[订单管理商品显示优化](订单管理商品显示优化.md)、[订单管理数据类型错误修复](订单管理数据类型错误修复.md) +- **小程序**:[头像上传优化](小程序头像上传优化说明.md)、[提现金额对接](小程序提现金额对接说明.md)、[昵称自动填充](小程序昵称自动填充说明.md)、[调整说明](小程序调整说明.md) +- **推广与后台**:[推广设置完整修复清单](推广设置功能-完整修复清单.md)、[推广设置测试清单](推广设置页面测试清单.md)、[管理端推广配置与小程序对接](管理端推广配置与小程序对接说明.md)、[后台订单显示优化](后台订单显示优化说明.md) +- **其他**:[删除 referred_by](删除referred_by字段说明.md)、[绑定关系存储方案分析](绑定关系存储方案分析.md)、[自动解绑 API](自动解绑API配置说明.md)、[自定义导航组件](自定义导航组件方案.md)、[收益明细优化](收益明细优化说明.md)、[新分销代码修改总结](新分销逻辑-代码修改总结.md)、[章节阅读页集成示例](章节阅读页集成示例.md) diff --git a/开发文档/API/配置清单-完整版.md b/开发文档/API/配置清单-完整版.md deleted file mode 100644 index 1ca859a7..00000000 --- a/开发文档/API/配置清单-完整版.md +++ /dev/null @@ -1,205 +0,0 @@ -# Soul创业实验 - API密钥与配置清单 - -> 最后更新: 2026-01-25 -> 维护人: 卡若 -> ⚠️ 本文件包含敏感信息,请勿公开 - ---- - -## 一、企业信息 - -| 项目 | 值 | -|:---|:---| -| **企业名称** | 泉州市卡若网络技术有限公司 | -| **联系电话** | 15880802661 | -| **微信号** | 28533368 | -| **邮箱** | zhiqun@qq.com / zhengzhiqun@vip.qq.com | - ---- - -## 二、微信生态 - -### 2.1 小程序(Soul创业实验) - -| 项目 | 值 | 备注 | -|:---|:---|:---| -| **AppID** | `wxb8bbb2b10dec74aa` | 小程序ID | -| **AppSecret** | `3c1fb1f63e6e052222bbcead9d07fe0c` | 小程序密钥 | -| **支付绑定状态** | 🟡 审核中 | 2026-01-25 09:43:59 提交 | - -### 2.2 服务号(玩值) - -| 项目 | 值 | 备注 | -|:---|:---|:---| -| **AppID** | `wx7c0dbf34ddba300d` | 服务号AppID | -| **AppSecret** | `f865ef18c43dfea6cbe3b1f1aebdb82e` | 服务号密钥 | -| **支付绑定状态** | ✅ 已绑定 | 绑定AppID: wx3e31b068be59ddc1 | - -### 2.3 网站应用 - -| 项目 | 值 | -|:---|:---| -| **AppID** | `wx432c93e275548671` | -| **AppSecret** | `25b7e7fdb7998e5107e242ebb6ddabd0` | - -### 2.4 微信支付 - -| 项目 | 值 | 备注 | -|:---|:---|:---| -| **商户号** | `1318592501` | 主体: 泉州市卡若网络技术有限公司 | -| **API密钥(v2)** | `wx3e31b068be59ddc131b068be59ddc2` | 32位 | -| **MP文件验证码** | `SP8AfZJyAvprRORT` | | -| **支付回调地址** | `https://soul.quwanzhi.com/api/miniprogram/pay/notify` | | - -#### 已绑定AppID - -| AppID | 类型 | 状态 | -|:---|:---|:---| -| `wx3e31b068be59ddc1` | 服务号 | ✅ 已关联 | -| `wxb8bbb2b10dec74aa` | 小程序 | 🟡 审核中 | - ---- - -## 三、支付宝 - -| 项目 | 值 | -|:---|:---| -| **PID** | `2088511801157159` | -| **MD5密钥** | `lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp` | -| **账户** | zhengzhiqun@vip.qq.com | - ---- - -## 四、云服务 - -### 4.1 腾讯云 - -| 项目 | 值 | -|:---|:---| -| **APPID** | `1251077262` | -| **SecretId** | `AKIDjc6yO3nPeOuK2OKsJPBBVbTiiz0aPNHl` | -| **SecretKey** | *(见用户规则)* | - -### 4.2 阿里云 - -| 项目 | 值 | -|:---|:---| -| **AccessKey ID** | `LTAI5t9zkiWmFtHG8qmtdysW` | -| **AccessKey Secret** | `xxjXnZGLNvA2zDkj0aEBSQm3XZAaro` | - ---- - -## 五、数据库 - -### 5.1 腾讯云MySQL(生产环境) - -| 项目 | 值 | -|:---|:---| -| **主机** | `56b4c23f6853c.gz.cdb.myqcloud.com` | -| **端口** | `14413` | -| **数据库** | `soul_miniprogram` | -| **用户名** | `cdb_outerroot` | -| **密码** | `Zhiqun1984` | -| **字符集** | `utf8mb4` | - -#### 数据库表 - -| 表名 | 说明 | -|:---|:---| -| `users` | 用户表 | -| `orders` | 订单表 | -| `referral_bindings` | 推广绑定关系 | -| `match_records` | 匹配记录 | -| `system_config` | 系统配置 | -| `chapters` | **章节内容表(新)** | - -### 5.2 卡若私域数据库(内网) - -| 项目 | 值 | -|:---|:---| -| **主机** | `10.88.182.62` | -| **端口** | `3306` | -| **用户名** | `root` | -| **密码** | `Vtka(agu)-1` | - ---- - -## 六、AI服务 - -### 6.1 v0 API - -| 项目 | 值 | -|:---|:---| -| **API地址** | `https://api.v0.dev/v1` | -| **API Key** | `v1:C6mw1SlvXsJdlO4VFEXSQEVf:519gA0DPqIMbjvfMh7CXf4B2` | -| **默认模型** | `claude-opus` | - ---- - -## 七、开发工具 - -### 7.1 GitHub - -| 项目 | 值 | -|:---|:---| -| **Token** | `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` | - ---- - -## 八、项目部署信息 - -| 项目 | 值 | -|:---|:---| -| **域名** | `soul.quwanzhi.com` | -| **协议** | HTTPS | -| **服务器** | 宝塔面板 | -| **部署方式** | GitHub Webhook 自动部署 | - ---- - -## 九、邮箱账户 - -| 邮箱 | 密码 | -|:---|:---| -| `zhiqun@qq.com` | `#vtk();1984` | -| `zhengzhiqun@vip.qq.com` | `#vtk();1984` | -| `15880802661@qq.com` | `#vtk();1984` | - ---- - -## 十、配置代码引用 - -### 小程序支付配置 - -```typescript -// lib/payment/wechat-miniprogram.ts -const WECHAT_PAY_CONFIG = { - appId: 'wxb8bbb2b10dec74aa', // 小程序AppID - appSecret: '3c1fb1f63e6e052222bbcead9d07fe0c', // 小程序AppSecret - mchId: '1318592501', // 商户号 - mchKey: 'wx3e31b068be59ddc131b068be59ddc2', // API密钥(v2) - notifyUrl: 'https://soul.quwanzhi.com/api/miniprogram/pay/notify', -} -``` - -### 数据库配置 - -```typescript -// lib/db.ts -const DB_CONFIG = { - host: '56b4c23f6853c.gz.cdb.myqcloud.com', - port: 14413, - user: 'cdb_outerroot', - password: 'Zhiqun1984', - database: 'soul_miniprogram', - charset: 'utf8mb4', -} -``` - ---- - -## 更新日志 - -| 日期 | 更新内容 | -|:---|:---| -| 2026-01-25 | 创建完整配置清单;小程序支付绑定申请中;章节表迁移完成 | diff --git a/开发文档/API/配置清单.md b/开发文档/API/配置清单.md index 2c379e97..114a752b 100644 --- a/开发文档/API/配置清单.md +++ b/开发文档/API/配置清单.md @@ -1,53 +1,113 @@ -# 项目配置清单 (Configuration Manifest) +# Soul创业实验 - API密钥与配置清单 -## 1. 核心密钥 (Secrets) -> **注意**:以下密钥仅限内部开发使用,严禁泄露。 +> 最后更新: 2026-01-25 | 维护人: 卡若 +> ⚠️ 本文件包含敏感信息,请勿公开 -- **GitHub Token**: `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` -- **腾讯云 API**: - - APPID: `1251077262` - - SecretId: `AKIDjc6yO3nPeOuK2OKsJPBBVbTiiz0aPNHl` - - SecretKey: `[已隐藏]` (请在环境变量中配置) -- **阿里云 API**: - - AccessKey ID: `LTAI5t9zkiWmFtHG8qmtdysW` - - Secret: `xxjXnZGLNvA2zDkj0aEBSQm3XZAaro` +--- -## 2. 数据库配置 (Database) +## 一、企业信息 -### MySQL (腾讯云) -- **Host**: `56b4c23f6853c.gz.cdb.myqcloud.com` -- **Port**: `14413` -- **User**: `cdb_outerroot` -- **Password**: `Zhiqun1984` +| 项目 | 值 | +|:---|:---| +| **企业名称** | 泉州市卡若网络技术有限公司 | +| **联系电话** | 15880802661 | +| **微信号** | 28533368 | +| **邮箱** | zhiqun@qq.com / zhengzhiqun@vip.qq.com | -### MongoDB (私有) -- **Host**: `10.88.182.62` (需内网环境) -- **Port**: `3306` (注意:MongoDB通常是27017,此处端口需确认是否为映射端口) -- **User**: `root` -- **Password**: `Vtka(agu)-1` +--- -## 3. 支付配置 (Payment) +## 二、微信生态 -### 微信支付 (WeChat Pay) -- **AppID**: (待填入) -- **MchID**: (待填入) -- **Key**: (待填入) -- **收款二维码**: `public/images/wechat-pay.png` (请上传真实收款码) +### 2.1 小程序(Soul创业实验) -### 支付宝 (Alipay) -- **AppID**: (待填入) -- **PrivateKey**: (待填入) -- **收款二维码**: `public/images/alipay.png` (请上传真实收款码) +| 项目 | 值 | 备注 | +|:---|:---|:---| +| **AppID** | `wxb8bbb2b10dec74aa` | 小程序ID | +| **AppSecret** | `3c1fb1f63e6e052222bbcead9d07fe0c` | 小程序密钥 | +| **支付绑定状态** | 🟡 审核中 | 2026-01-25 09:43:59 提交 | -### USDT (TRC20) -- **Address**: `(待配置钱包地址)` -- **Network**: `TRC20` +### 2.2 服务号(玩值) -## 4. 营销配置 (Marketing) +| 项目 | 值 | 备注 | +|:---|:---|:---| +| **AppID** | `wx7c0dbf34ddba300d` | 服务号AppID | +| **AppSecret** | `f865ef18c43dfea6cbe3b1f1aebdb82e` | 服务号密钥 | +| **支付绑定状态** | ✅ 已绑定 | 绑定AppID: wx3e31b068be59ddc1 | -- **流量池活码**: - - URL: `https://soul.cn/party` - - 更新频率: 每日 06:00 -- **分销比例**: - - 一级分销: 30% - - 二级分销: 10% +### 2.3 微信支付 + +| 项目 | 值 | 备注 | +|:---|:---|:---| +| **商户号** | `1318592501` | 主体: 泉州市卡若网络技术有限公司 | +| **API密钥(v2)** | `wx3e31b068be59ddc131b068be59ddc2` | 32位 | +| **支付回调地址** | `https://soul.quwanzhi.com/api/miniprogram/pay/notify` | | + +--- + +## 三、支付宝 + +| 项目 | 值 | +|:---|:---| +| **PID** | `2088511801157159` | +| **MD5密钥** | `lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp` | +| **账户** | zhengzhiqun@vip.qq.com | + +--- + +## 四、云服务 + +### 4.1 腾讯云 + +| 项目 | 值 | +|:---|:---| +| **APPID** | `1251077262` | +| **SecretId** | `AKIDjc6yO3nPeOuK2OKsJPBBVbTiiz0aPNHl` | +| **SecretKey** | *(见用户规则)* | + +### 4.2 阿里云 + +| 项目 | 值 | +|:---|:---| +| **AccessKey ID** | `LTAI5t9zkiWmFtHG8qmtdysW` | +| **AccessKey Secret** | `xxjXnZGLNvA2zDkj0aEBSQm3XZAaro` | + +--- + +## 五、数据库 + +### 5.1 腾讯云MySQL(生产环境) + +| 项目 | 值 | +|:---|:---| +| **主机** | `56b4c23f6853c.gz.cdb.myqcloud.com` | +| **端口** | `14413` | +| **数据库** | `soul_miniprogram` | +| **用户名** | `cdb_outerroot` | +| **密码** | `Zhiqun1984` | +| **字符集** | `utf8mb4` | + +**主要表**: users, orders, referral_bindings, match_records, system_config, chapters + +### 5.2 卡若私域数据库(内网) + +| 项目 | 值 | +|:---|:---| +| **主机** | `10.88.182.62` | +| **端口** | `3306` | +| **用户名** | `root` | +| **密码** | `Vtka(agu)-1` | + +--- + +## 六、项目部署信息 + +| 项目 | 值 | +|:---|:---| +| **域名** | `soul.quwanzhi.com` | +| **协议** | HTTPS | +| **服务器** | 宝塔面板 | +| **部署方式** | GitHub Webhook / scripts/devlop.py | + +--- + +*AI服务、GitHub Token、邮箱等敏感项见项目内保密存储;配置代码引用见原完整版备份。*