新功能: 还原 v1/public/user/invite_sales 接口 + promo schema 修复迁移
Build docker and publish / build (20.15.1) (push) Failing after 13m47s
Build docker and publish / build (20.15.1) (pull_request) Failing after 15m7s

- 还原 /v1/public/user/invite_sales 接口及 /invite/sales 别名(与 invite_records 并存)
  逻辑/handler/types 与 197fed7d 删除前版本完全一致,按 fefbd4f5 新 routes 结构注册
- 新增迁移 02155_promo_schema_fix: 幂等修复 subscribe_promo / order 列类型与索引偏差
- 同步 etc/ppanel.yaml 数据库连接配置
- 补齐相关需求与设计文档
This commit is contained in:
2026-05-28 20:46:37 -07:00
parent fefbd4f56a
commit 3e265bd837
12 changed files with 2418 additions and 2 deletions
@@ -0,0 +1,559 @@
# App 邀请列表与商品套餐折扣需求梳理
本文基于当前 `ppanel-server` 代码现状,对以下两个需求做整理:
1. App 需要一个“邀请列表/邀请记录”接口。
2. 商品套餐需要补充“折扣信息”,当存在折扣时,App 需要做样式展示,并支持用户继续下单购买。
目标是帮助产品、前端、后端快速统一口径,明确:
- 现在已有哪些接口可以复用
- 哪些地方确实需要新增
- “接口增加三个字段”更适合加在哪一层
---
## 1. 需求结论
### 1.1 邀请列表
当前公开侧已经有一部分邀请能力,但**没有一个完全匹配“App 邀请记录列表”语义的公开接口**。
现状:
- 已有 `GET /v1/public/user/affiliate/list`
- 能返回“我邀请了哪些用户”
- 但字段较少,只包含基础信息
- 已有 `GET /v1/public/user/invite_sales`
- 返回的是“被邀请用户的成交订单记录”
- 不是“邀请用户记录列表”
- 已有 `GET /v1/public/user/invite_stats`
- 返回邀请统计
- 不是列表
结论:
- 如果 App 只是要展示“我邀请了哪些人”,`/affiliate/list` 可以复用。
- 如果 App 需要展示“邀请时间、是否购买、购买次数、带来的佣金/赠送天数”等完整邀请记录,则**建议新增一个公开接口**。
### 1.2 商品套餐折扣
当前公开侧套餐列表接口 `GET /v1/public/subscribe/list` **已经返回 `discount` 字段**,下单预览接口 `POST /v1/public/order/pre` 也已经支持折扣计算。
现状:
- 套餐列表已有折扣规则数组 `discount`
- 预下单接口已有:
- 原价 `price`
- 实付 `amount`
- 折扣金额 `discount`
- 活动优惠 `promo_discount`
- 优惠券减免 `coupon_discount`
- 手续费 `fee_amount`
结论:
- 后端**不是完全没有折扣能力**,而是“已经有计算能力,但 App 展示层使用起来不够直接”。
- 如果需求明确要求“接口增加三个字段”,**更推荐补在套餐列表返回的 `discount[]` 子项里**,而不是直接加在下单接口里。
---
## 2. 当前代码现状
## 2.1 邀请相关
### 2.1.1 已有公开接口
#### A. 邀请基础列表
接口:
- `GET /v1/public/user/affiliate/list`
请求:
- `page`
- `size`
返回结构:
- `total`
- `list[]`
- `identifier`
- `avatar`
- `registered_at`
- `enable`
特点:
- 能表达“我邀请了谁”
- 不能表达“是否购买 / 购买次数 / 给我带来多少收益”
对应代码:
- `apis/public/user.api`
- `internal/logic/public/user/queryUserAffiliateListLogic.go`
#### B. 邀请成交记录
接口:
- `GET /v1/public/user/invite_sales`
返回结构:
- `total`
- `list[]`
- `amount`
- `updated_at`
- `user_hash`
- `product_name`
特点:
- 更像“邀请带来的订单流水”
- 不是邀请用户列表
对应代码:
- `internal/logic/public/user/getInviteSalesLogic.go`
#### C. 邀请统计
接口:
- `GET /v1/public/user/invite_stats`
返回结构:
- `friendly_count`
- `history_count`
特点:
- 只适合头部统计卡片
- 不适合列表页
对应代码:
- `internal/logic/public/user/getUserInviteStatsLogic.go`
### 2.1.2 已有后台接口
后台已经有更完整的邀请记录能力,可以直接参考:
- `GetAdminUserInviteList`
- `GetInviteManageList`
这些接口已经能返回:
- 邀请时间
- 是否购买
- 购买次数
- 邀请人佣金
- 邀请人赠送天数
- 被邀请人赠送天数
对应代码:
- `internal/logic/admin/user/getAdminUserInviteListLogic.go`
- `internal/logic/admin/invite/getInviteManageListLogic.go`
结论:
- 邀请记录的统计逻辑后端已经有现成实现思路。
- 新增 App 公开接口时,建议复用这部分逻辑,不要从零再写一套。
---
## 2.2 套餐折扣相关
### 2.2.1 套餐列表接口已有折扣规则
接口:
- `GET /v1/public/subscribe/list`
当前返回的套餐结构 `Subscribe` 中已包含:
- `unit_price`
- `discount []SubscribeDiscount`
其中 `SubscribeDiscount` 当前字段为:
- `quantity`
- `discount`
- `new_user_only`
- `map_apple`
- `promo`
对应代码:
- `apis/public/subscribe.api`
- `internal/logic/public/subscribe/querySubscribeListLogic.go`
- `internal/types/types.go`
说明:
- `discount` 是按购买数量 `quantity` 生效的阶梯折扣
- 不是单个套餐固定只有一个折扣值
### 2.2.2 预下单接口已有价格计算结果
接口:
- `POST /v1/public/order/pre`
当前已返回:
- `price`:原价
- `amount`:最终应付
- `discount`:折扣减免金额
- `promo_discount`:活动优惠金额
- `gift_amount`:礼品余额抵扣
- `coupon_discount`:优惠券减免
- `fee_amount`:手续费
对应代码:
- `apis/public/order.api`
- `internal/logic/public/order/preCreateOrderLogic.go`
说明:
- 只要 App 知道 `subscribe_id + quantity [+ coupon] [+ payment]`,就已经能拿到准确的下单金额
- 所以“下单购买”这件事本身,后端主链路已经具备
---
## 3. 差距分析
## 3.1 邀请列表的真实缺口
如果产品要的是“邀请记录页”,通常至少会关心以下内容:
- 被邀请用户
- 邀请时间
- 是否已购买
- 购买次数
- 给邀请人带来的佣金
- 双方赠送天数
而当前:
- `/affiliate/list` 只有基础用户列表
- `/invite_sales` 是订单成交记录
- `/invite_stats` 是统计值
所以当前公开侧缺一个“**邀请关系维度的邀请记录列表**”。
## 3.2 套餐折扣的真实缺口
后端目前的主要问题不是“不会算折扣”,而是:
- `discount[]` 更偏规则定义
- App 如果只想直接展示“折后价 / 优惠金额 / 折扣标签”,还需要自己再算一层
- 这会增加前端理解成本,也容易和后端口径不一致
所以当前更合理的改法是:
- 保留现有折扣规则
- 再额外补充几个**面向展示的字段**
---
## 4. 推荐方案
## 4.1 邀请记录接口
### 方案建议
新增一个公开接口,例如:
- `GET /v1/public/user/invite_records`
说明:
- 从登录态中取当前用户 ID
- 不从前端传 `user_id`
- 只查“当前用户邀请的记录”
### 请求参数建议
```json
{
"page": 1,
"size": 10
}
```
### 返回字段建议
```json
{
"total": 2,
"list": [
{
"invitee_id": 1001,
"invitee_identifier": "138****8888",
"invitee_avatar": "https://...",
"invitee_enable": true,
"invited_at": 1716800000,
"order_count": 3,
"has_purchased": true,
"inviter_commission": 1200,
"inviter_gift_days": 30,
"invitee_gift_days": 7
}
]
}
```
### 字段说明
- `invitee_id`:被邀请用户 ID
- `invitee_identifier`:被邀请用户展示账号
- `invitee_avatar`:头像
- `invitee_enable`:是否启用
- `invited_at`:邀请时间
- `order_count`:该被邀请用户产生的有效订单数
- `has_purchased`:是否已购买
- `inviter_commission`:给邀请人带来的佣金,单位建议继续沿用分
- `inviter_gift_days`:邀请人获赠天数
- `invitee_gift_days`:被邀请人获赠天数
### 实现建议
优先复用现有后台逻辑思路:
- 参考 `internal/logic/admin/user/getAdminUserInviteListLogic.go`
- 或参考 `internal/logic/admin/invite/getInviteManageListLogic.go`
公开接口与后台接口的主要差异只有两点:
- 公开接口不允许前端指定 `user_id`
- 公开接口按当前登录用户本人维度返回
### 是否可以不新增接口
可以,但前提是 App 接受以下拆分:
- 列表页用 `/affiliate/list`
- 顶部统计用 `/invite_stats`
- 订单流水页用 `/invite_sales`
如果产品要的是一个完整“邀请记录页”,不建议这样拆三次请求,前端维护成本偏高。
---
## 4.2 套餐折扣字段建议
### 核心建议
“接口增加三个字段”建议**加在 `SubscribeDiscount` 子项里**,不要直接加在 `Subscribe` 顶层。
原因:
- 折扣是按 `quantity` 生效的
- 一个套餐可能有多个折扣档位
- 如果加在套餐顶层,很难表达“买 1 个月”和“买 12 个月”对应不同折扣
### 推荐新增字段
建议在 `SubscribeDiscount` 中增加以下三个展示字段:
- `discount_price`
- `discount_amount`
- `discount_desc`
推荐结构如下:
```json
{
"quantity": 12,
"discount": 80,
"new_user_only": false,
"map_apple": "",
"promo": null,
"discount_price": 9600,
"discount_amount": 2400,
"discount_desc": "年付8折"
}
```
### 三个字段的含义
#### 1. `discount_price`
- 含义:该档位折后总价
- 计算建议:`unit_price * quantity * discount / 100`
- 单位:分
作用:
- App 可直接展示“折后价”
- 下单时直接把该项的 `quantity` 带入 `/order/pre``/order/purchase`
#### 2. `discount_amount`
- 含义:该档位比原价便宜多少钱
- 计算建议:`unit_price * quantity - discount_price`
- 单位:分
作用:
- App 可直接展示“立省 xx”
#### 3. `discount_desc`
- 含义:折扣展示文案
- 示例:
- `年付8折`
- `季付9折`
- `新用户首单8折`
作用:
- App 可直接做角标、标签、促销文案展示
### 为什么不推荐这三个字段加在下单接口
因为下单接口本来就是“结果型接口”,它已经能返回:
- 原价
- 折扣金额
- 实付金额
如果只是为了 App 卡片展示,再去每个套餐都调一次 `/order/pre`,成本会比较高:
- 请求次数多
- 页面首屏会更慢
- 前端链路更复杂
更合适的做法是:
- 套餐列表接口负责“展示友好”
- 预下单接口负责“结算准确”
---
## 5. 推荐改动清单
## 5.1 邀请列表
建议新增:
- 新接口:`GET /v1/public/user/invite_records`
建议新增类型:
- `GetUserInviteRecordsRequest`
- `UserInviteRecord`
- `GetUserInviteRecordsResponse`
建议实现位置:
- `apis/public/user.api`
- `internal/types/types.go`
- `internal/handler/public/user/`
- `internal/logic/public/user/`
## 5.2 套餐折扣
建议调整:
- `SubscribeDiscount` 增加 3 个字段:
- `discount_price`
- `discount_amount`
- `discount_desc`
建议实现位置:
- `apis/types.api`
- `internal/types/types.go`
- `internal/logic/public/subscribe/querySubscribeListLogic.go`
---
## 6. 前后端协作建议
## 6.1 App 侧调用建议
邀请页建议:
- 头部统计:`/v1/public/user/invite_stats`
- 邀请记录列表:`/v1/public/user/invite_records`
- 如果还要看成交流水:`/v1/public/user/invite_sales`
套餐页建议:
- 先调 `/v1/public/subscribe/list` 渲染套餐和折扣标签
- 用户点某个折扣档位时,带 `subscribe_id + quantity``/v1/public/order/pre`
- 用户确认后再调 `/v1/public/order/purchase`
## 6.2 单位口径建议
建议继续保持后端金额统一为“分”:
- `unit_price`
- `discount_price`
- `discount_amount`
- `amount`
- `coupon_discount`
这样可以避免前后端出现小数精度问题。
---
## 7. 最终建议
### 建议一
如果你们只是要“邀请用户名单”,可直接复用:
- `GET /v1/public/user/affiliate/list`
### 建议二
如果你们要的是完整“邀请记录”,建议新增:
- `GET /v1/public/user/invite_records`
这是本次需求里更合理的新增接口。
### 建议三
商品套餐“折扣信息”不建议重新设计一整套下单逻辑。
当前后端已经具备:
- 套餐折扣规则
- 预下单价格计算
- 正式下单购买
更推荐做法是:
-`SubscribeDiscount` 里补 3 个展示字段:
- `discount_price`
- `discount_amount`
- `discount_desc`
这样改动最小,也最贴近 App 展示场景。
---
## 8. 相关代码位置
- `internal/logic/public/user/queryUserAffiliateListLogic.go`
- `internal/logic/public/user/getInviteSalesLogic.go`
- `internal/logic/public/user/getUserInviteStatsLogic.go`
- `internal/logic/admin/user/getAdminUserInviteListLogic.go`
- `internal/logic/admin/invite/getInviteManageListLogic.go`
- `internal/logic/public/subscribe/querySubscribeListLogic.go`
- `internal/logic/public/order/preCreateOrderLogic.go`
- `internal/logic/public/order/purchaseLogic.go`
- `internal/types/types.go`
- `apis/public/user.api`
- `apis/public/subscribe.api`
- `apis/public/order.api`