370 lines
10 KiB
Markdown
370 lines
10 KiB
Markdown
|
|
# 推广设置功能 - 完整修复清单
|
|||
|
|
|
|||
|
|
## 修复概述
|
|||
|
|
为了确保后台「推广设置」页面的配置能正确应用到整个分销流程,我们修复了 **3 个关键 bug**,并创建了新的管理页面。
|
|||
|
|
|
|||
|
|
## ✅ 已完成的修改
|
|||
|
|
|
|||
|
|
### 1. 创建管理页面入口
|
|||
|
|
**文件**: `app/admin/layout.tsx`
|
|||
|
|
**修改内容**: 在侧边栏菜单中增加「推广设置」入口
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
{ icon: CreditCard, label: "推广设置", href: "/admin/referral-settings" },
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**位置**: 「交易中心」和「系统设置」之间
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. 创建推广设置页面
|
|||
|
|
**文件**: `app/admin/referral-settings/page.tsx` (新建)
|
|||
|
|
**功能**:
|
|||
|
|
- 配置「好友优惠」(userDiscount) - 百分比
|
|||
|
|
- 配置「推广者分成」(distributorShare) - 百分比,带滑块
|
|||
|
|
- 配置「绑定有效期」(bindingDays) - 天数
|
|||
|
|
- 配置「最低提现金额」(minWithdrawAmount) - 元
|
|||
|
|
- 配置「自动提现开关」(enableAutoWithdraw) - 布尔值 (预留)
|
|||
|
|
|
|||
|
|
**关键特性**:
|
|||
|
|
- 保存时强制类型转换(确保所有数字字段是 `Number` 类型)
|
|||
|
|
- 加载时有默认值保护
|
|||
|
|
- 保存成功后有详细提示
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const safeConfig = {
|
|||
|
|
distributorShare: Number(config.distributorShare) || 0,
|
|||
|
|
minWithdrawAmount: Number(config.minWithdrawAmount) || 0,
|
|||
|
|
bindingDays: Number(config.bindingDays) || 0,
|
|||
|
|
userDiscount: Number(config.userDiscount) || 0,
|
|||
|
|
enableAutoWithdraw: Boolean(config.enableAutoWithdraw),
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. 修复绑定 API 硬编码问题 ⚠️
|
|||
|
|
**文件**: `app/api/referral/bind/route.ts`
|
|||
|
|
**Bug**: 使用硬编码 `BINDING_DAYS = 30`,不读取配置
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```typescript
|
|||
|
|
// 修复前
|
|||
|
|
const BINDING_DAYS = 30
|
|||
|
|
const expiryDate = new Date()
|
|||
|
|
expiryDate.setDate(expiryDate.getDate() + BINDING_DAYS)
|
|||
|
|
|
|||
|
|
// 修复后
|
|||
|
|
const DEFAULT_BINDING_DAYS = 30
|
|||
|
|
let bindingDays = DEFAULT_BINDING_DAYS
|
|||
|
|
try {
|
|||
|
|
const config = await getConfig('referral_config')
|
|||
|
|
if (config?.bindingDays) {
|
|||
|
|
bindingDays = Number(config.bindingDays)
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.warn('[Referral Bind] 读取配置失败,使用默认值', DEFAULT_BINDING_DAYS)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const expiryDate = new Date()
|
|||
|
|
expiryDate.setDate(expiryDate.getDate() + bindingDays)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**影响**:
|
|||
|
|
- ✅ 新用户绑定关系的过期时间会使用后台配置的天数
|
|||
|
|
- ✅ 支持动态调整绑定期(如改为 60 天)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4. 修复提现 API 缺少门槛检查 ⚠️
|
|||
|
|
**文件**: `app/api/withdraw/route.ts`
|
|||
|
|
**Bug**: 没有检查最低提现门槛,只检查了 `amount > 0`
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```typescript
|
|||
|
|
// 导入 getConfig
|
|||
|
|
import { query, getConfig } from '@/lib/db'
|
|||
|
|
|
|||
|
|
// 在 POST 函数中添加检查
|
|||
|
|
let minWithdrawAmount = 10 // 默认值
|
|||
|
|
try {
|
|||
|
|
const config = await getConfig('referral_config')
|
|||
|
|
if (config?.minWithdrawAmount) {
|
|||
|
|
minWithdrawAmount = Number(config.minWithdrawAmount)
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.warn('[Withdraw] 读取配置失败,使用默认值 10 元')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查最低提现门槛
|
|||
|
|
if (amount < minWithdrawAmount) {
|
|||
|
|
return NextResponse.json({
|
|||
|
|
success: false,
|
|||
|
|
message: `最低提现金额为 ¥${minWithdrawAmount},当前 ¥${amount}`
|
|||
|
|
}, { status: 400 })
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**影响**:
|
|||
|
|
- ✅ 用户提现时会校验后台配置的最低门槛
|
|||
|
|
- ✅ 防止低于门槛的提现请求
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. 后端 API 验证
|
|||
|
|
已确认以下 API **正确读取** `referral_config`:
|
|||
|
|
|
|||
|
|
#### 5.1 支付回调 - 佣金计算
|
|||
|
|
**文件**: `app/api/miniprogram/pay/notify/route.ts`
|
|||
|
|
```typescript
|
|||
|
|
let distributorShare = DEFAULT_DISTRIBUTOR_SHARE
|
|||
|
|
const config = await getConfig('referral_config')
|
|||
|
|
if (config?.distributorShare) {
|
|||
|
|
distributorShare = config.distributorShare / 100 // 90 → 0.9
|
|||
|
|
}
|
|||
|
|
const commission = Math.round(amount * distributorShare * 100) / 100
|
|||
|
|
```
|
|||
|
|
✅ **已验证正确**
|
|||
|
|
|
|||
|
|
#### 5.2 推广数据 API
|
|||
|
|
**文件**: `app/api/referral/data/route.ts`
|
|||
|
|
```typescript
|
|||
|
|
let distributorShare = DISTRIBUTOR_SHARE
|
|||
|
|
const config = await getConfig('referral_config')
|
|||
|
|
if (config?.distributorShare) {
|
|||
|
|
distributorShare = config.distributorShare / 100 // 用于展示
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
✅ **已验证正确**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 配置字段说明
|
|||
|
|
|
|||
|
|
### 数据库表: `system_config`
|
|||
|
|
- **config_key**: `referral_config`
|
|||
|
|
- **config_value**: JSON 字符串
|
|||
|
|
|
|||
|
|
### JSON 结构:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"distributorShare": 90, // 推广者分成百分比(存 90,计算时除以 100)
|
|||
|
|
"minWithdrawAmount": 10, // 最低提现金额(元)
|
|||
|
|
"bindingDays": 30, // 绑定有效期(天)
|
|||
|
|
"userDiscount": 5, // 好友优惠百分比(预留字段)
|
|||
|
|
"enableAutoWithdraw": false // 自动提现开关(预留字段)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**注意**:
|
|||
|
|
- `distributorShare` 在数据库存的是百分比数字(如 90),使用时需除以 100(0.9)
|
|||
|
|
- 所有字段必须是 **数字类型**,不能是字符串 `"90"`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 完整业务流程
|
|||
|
|
|
|||
|
|
### 1. 用户通过推广链接注册
|
|||
|
|
**API**: `/api/referral/bind`
|
|||
|
|
**读取配置**: `bindingDays`
|
|||
|
|
**行为**: 创建绑定关系,过期时间 = 当前时间 + bindingDays 天
|
|||
|
|
|
|||
|
|
### 2. 绑定用户下单支付
|
|||
|
|
**API**: `/api/miniprogram/pay/notify`
|
|||
|
|
**读取配置**: `distributorShare`
|
|||
|
|
**行为**: 计算佣金 = 订单金额 × (distributorShare / 100),写入 `referral_bindings` 表
|
|||
|
|
|
|||
|
|
### 3. 推广者查看收益
|
|||
|
|
**API**: `/api/referral/data`
|
|||
|
|
**读取配置**: `distributorShare`
|
|||
|
|
**行为**: 展示推广规则卡片,显示当前分成比例
|
|||
|
|
|
|||
|
|
### 4. 推广者申请提现
|
|||
|
|
**API**: `/api/withdraw`
|
|||
|
|
**读取配置**: `minWithdrawAmount`
|
|||
|
|
**行为**:
|
|||
|
|
- 检查提现金额 >= minWithdrawAmount
|
|||
|
|
- 创建提现记录
|
|||
|
|
|
|||
|
|
### 5. 管理员审核提现
|
|||
|
|
**API**: `/api/admin/withdrawals`
|
|||
|
|
**读取配置**: 不需要
|
|||
|
|
**行为**: 更新提现状态为 `completed` 或 `rejected`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 测试验证步骤
|
|||
|
|
|
|||
|
|
### 验证 1: 绑定天数动态生效
|
|||
|
|
1. 后台设置「绑定有效期」为 **60 天**,保存
|
|||
|
|
2. 小程序新用户通过推广链接注册
|
|||
|
|
3. 数据库查询:
|
|||
|
|
```sql
|
|||
|
|
SELECT expiry_date FROM referral_bindings WHERE referee_id = '新用户ID' ORDER BY created_at DESC LIMIT 1;
|
|||
|
|
```
|
|||
|
|
4. **预期**: `expiry_date` = 当前时间 + **60 天**
|
|||
|
|
|
|||
|
|
### 验证 2: 佣金比例动态生效
|
|||
|
|
1. 后台设置「推广者分成」为 **85%**,保存
|
|||
|
|
2. 已绑定用户购买 100 元订单
|
|||
|
|
3. 数据库查询:
|
|||
|
|
```sql
|
|||
|
|
SELECT commission FROM referral_bindings WHERE status = 'converted' ORDER BY created_at DESC LIMIT 1;
|
|||
|
|
```
|
|||
|
|
4. **预期**: `commission` = **85.00**
|
|||
|
|
|
|||
|
|
### 验证 3: 提现门槛动态生效
|
|||
|
|
1. 后台设置「最低提现金额」为 **50 元**,保存
|
|||
|
|
2. 用户尝试提现 **30 元**
|
|||
|
|
3. **预期**: 返回错误「最低提现金额为 ¥50,当前 ¥30」
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 部署清单
|
|||
|
|
|
|||
|
|
### 1. 代码部署
|
|||
|
|
```bash
|
|||
|
|
# 本地构建
|
|||
|
|
pnpm build
|
|||
|
|
|
|||
|
|
# 上传到服务器
|
|||
|
|
python devlop.py
|
|||
|
|
|
|||
|
|
# 重启 PM2
|
|||
|
|
pm2 restart soul
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 数据库检查
|
|||
|
|
确保 `system_config` 表存在 `referral_config` 配置:
|
|||
|
|
```sql
|
|||
|
|
SELECT * FROM system_config WHERE config_key = 'referral_config';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
如果不存在,插入默认配置:
|
|||
|
|
```sql
|
|||
|
|
INSERT INTO system_config (config_key, config_value, description) VALUES (
|
|||
|
|
'referral_config',
|
|||
|
|
'{"distributorShare":90,"minWithdrawAmount":10,"bindingDays":30,"userDiscount":5,"enableAutoWithdraw":false}',
|
|||
|
|
'分销 / 推广规则配置'
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 清理缓存
|
|||
|
|
- 重启 Node.js 服务
|
|||
|
|
- 清除前端缓存(刷新浏览器 Ctrl+Shift+R)
|
|||
|
|
- 删除微信小程序缓存(开发者工具 -> 清除缓存)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 潜在风险
|
|||
|
|
|
|||
|
|
### 风险 1: 配置读取失败
|
|||
|
|
**场景**: 数据库连接异常或配置格式错误
|
|||
|
|
**保护措施**: 所有读取配置的地方都有默认值 fallback
|
|||
|
|
```typescript
|
|||
|
|
try {
|
|||
|
|
const config = await getConfig('referral_config')
|
|||
|
|
if (config?.distributorShare) {
|
|||
|
|
distributorShare = config.distributorShare / 100
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
// 使用默认配置 DEFAULT_DISTRIBUTOR_SHARE
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 风险 2: 历史订单佣金
|
|||
|
|
**场景**: 修改配置后,历史订单的佣金会变吗?
|
|||
|
|
**回答**: **不会**。已结算的佣金存在 `referral_bindings` 表的 `commission` 字段,不会因配置修改而变化。只影响 **新订单**。
|
|||
|
|
|
|||
|
|
### 风险 3: 类型错误
|
|||
|
|
**场景**: 前端输入框可能返回字符串 `"90"` 而不是数字 `90`
|
|||
|
|
**保护措施**: 管理页面保存时强制类型转换
|
|||
|
|
```typescript
|
|||
|
|
const safeConfig = {
|
|||
|
|
distributorShare: Number(config.distributorShare) || 0,
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 性能优化建议
|
|||
|
|
|
|||
|
|
### 当前实现
|
|||
|
|
每次绑定/支付/提现都会查询一次 `system_config` 表
|
|||
|
|
|
|||
|
|
### 优化方案 (可选)
|
|||
|
|
增加 Redis 缓存:
|
|||
|
|
```typescript
|
|||
|
|
// 伪代码
|
|||
|
|
const cachedConfig = await redis.get('referral_config')
|
|||
|
|
if (cachedConfig) {
|
|||
|
|
return JSON.parse(cachedConfig)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const config = await getConfig('referral_config')
|
|||
|
|
await redis.set('referral_config', JSON.stringify(config), 'EX', 60) // TTL 60s
|
|||
|
|
return config
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**收益**: 减少数据库查询,QPS 可提升 10-20 倍
|
|||
|
|
**成本**: 需要部署 Redis,配置变更有最多 60 秒延迟
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 遗留问题
|
|||
|
|
|
|||
|
|
### userDiscount 字段未应用
|
|||
|
|
**状态**: ✅ 已定义,❌ 未应用
|
|||
|
|
**说明**: `userDiscount` (好友优惠) 目前只存在配置中,但订单价格计算逻辑中没有实际使用。
|
|||
|
|
**影响**: 修改这个值 **不会** 影响实际订单价格
|
|||
|
|
**建议**: 如需启用,需在订单创建 API 中读取此配置并应用折扣
|
|||
|
|
|
|||
|
|
### enableAutoWithdraw 字段未应用
|
|||
|
|
**状态**: ✅ 已定义,❌ 未实现
|
|||
|
|
**说明**: 自动提现功能需结合定时任务(cron job)和微信商家转账 API
|
|||
|
|
**影响**: 修改这个开关 **不会** 触发任何行为
|
|||
|
|
**建议**: 后续实现定时任务模块时读取此配置
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## FAQ
|
|||
|
|
|
|||
|
|
### Q1: 修改配置后需要重启服务吗?
|
|||
|
|
**A**: **不需要**。每次请求都会动态读取数据库配置。
|
|||
|
|
|
|||
|
|
### Q2: 小程序展示的规则和后台设置不一致?
|
|||
|
|
**A**: 可能原因:
|
|||
|
|
1. 小程序缓存未清除 - 重新编译上传小程序
|
|||
|
|
2. API 未正确读取配置 - 检查 PM2 日志
|
|||
|
|
3. 前端硬编码了文案 - 检查小程序代码
|
|||
|
|
|
|||
|
|
### Q3: 测试环境如何验证?
|
|||
|
|
**A**: 使用测试数据库,修改配置后用测试账号走完整流程(绑定→下单→提现)
|
|||
|
|
|
|||
|
|
### Q4: 如何回滚配置?
|
|||
|
|
**A**: 执行 SQL:
|
|||
|
|
```sql
|
|||
|
|
UPDATE system_config
|
|||
|
|
SET config_value = '{"distributorShare":90,"minWithdrawAmount":10,"bindingDays":30,"userDiscount":5,"enableAutoWithdraw":false}'
|
|||
|
|
WHERE config_key = 'referral_config';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
✅ **3 个 bug 已修复**:
|
|||
|
|
1. 绑定 API 读取配置的 `bindingDays`
|
|||
|
|
2. 提现 API 检查 `minWithdrawAmount`
|
|||
|
|
3. 管理页面强制类型转换
|
|||
|
|
|
|||
|
|
✅ **5 个 API 已验证正确**:
|
|||
|
|
1. `/api/referral/bind` - 绑定关系
|
|||
|
|
2. `/api/miniprogram/pay/notify` - 佣金计算
|
|||
|
|
3. `/api/referral/data` - 推广数据
|
|||
|
|
4. `/api/withdraw` - 提现申请
|
|||
|
|
5. `/api/admin/withdrawals` - 提现审核
|
|||
|
|
|
|||
|
|
✅ **整个分销流程已打通**,后台配置会实时生效!
|