From 2574dee457346aaceb4329dd2f2992c9653c3d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Fri, 31 Jul 2026 13:11:51 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=95=86=E4=B8=9A=E5=8C=96=E5=9B=9B?= =?UTF-8?q?=E6=94=AF=E6=9F=B1=E8=AE=BE=E8=AE=A1=EF=BC=88=E4=BC=9A=E5=91=98?= =?UTF-8?q?=E6=94=AF=E4=BB=98/=E5=B9=BF=E5=91=8A=E6=BF=80=E5=8A=B1/CPS=20?= =?UTF-8?q?=E5=BC=95=E6=93=8E=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2026-07-31-commerce-monetization-design.md | 293 ++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-31-commerce-monetization-design.md diff --git a/docs/superpowers/specs/2026-07-31-commerce-monetization-design.md b/docs/superpowers/specs/2026-07-31-commerce-monetization-design.md new file mode 100644 index 0000000..e7528ee --- /dev/null +++ b/docs/superpowers/specs/2026-07-31-commerce-monetization-design.md @@ -0,0 +1,293 @@ +# 商业化四支柱设计(后端)· slogan-agent + +> **目标:** 以「个人形象设计」为主题业务,落地四支柱收入:VIP 会员充值、穿山甲广告、线下门店引流(OTA 联盟)、线上商品(电商联盟 CPS)。 +> **核心原则:** 商业化从「方案/单品」长出,不做泛化场景广场。所有推荐由方案已有字段驱动,**零新增 LLM 调用**。 + +## 1. 总体架构 + +``` +App(slogan-app) + │ 会员中心/方案详情商业化入口/衣橱升级款/广告位 + ▼ +slogan-agent 新增模块 + ├─ 会员模块 member_plan / payment_order / user_member / pay_notify_log + ├─ 广告激励 ad_reward_log + 发放权益 + ├─ CPS 统一引擎 cps_category / cps_product / cps_click_log / scene_category_map + │ └─ 适配器:美团联盟(OTA 到店) / 京东联盟(电商) / 淘宝客(美妆配饰) + └─ 配置 config.yml(cps.payment.ad 配置段,Key 默认空 → 模块自动降级) + │ + ├─▶ 虎皮椒聚合支付(微信/支付宝收银台,iOS WebView) + ├─▶ 美团联盟 API(选品 + 转链,pid 归因) + ├─▶ 京东联盟 API(选品 + 转链) + └─▶ 淘宝客 API(选品 + 淘口令) +``` + +**模块降级原则**:与现有 `llm/weather/geo` 配置段同模式 —— 支付/CPS 相关 key 未配置时,接口返回明确错误信息(如"支付未开通,请在 config.yml 配置"),App 端隐藏对应入口,不影响主功能闭环。 + +## 2. 支柱 A:VIP 会员与聚合支付 + +### 2.1 支付服务商:虎皮棋(xunhupay) + +- 个人可开通、无营业执照门槛、微信+支付宝双通道、收银台 URL 模式(App WebView 打开) +- 下单:`POST /v1/payment`(RSA 签名请求);回调:`POST notify_url`(验签后解析) +- **签名/验签细节以官方最新文档为准**,实现时封装在 `payment/gateway.go` 适配器内,与业务解耦 +- 金额一律以「分」为单位存库,避免浮点误差 + +### 2.2 数据模型(dao init 自动建表,沿用 SQLite 规范) + +```sql +CREATE TABLE IF NOT EXISTS member_plan ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL DEFAULT '', + price_fen INTEGER NOT NULL DEFAULT 0, -- 金额(分) + duration_days INTEGER NOT NULL DEFAULT 30, -- 时长(天) + features TEXT NOT NULL DEFAULT '[]', -- 权益 JSON:["effect_unlimited","ai_priority","cps_commission_x15","store_discount"] + sort INTEGER NOT NULL DEFAULT 0, + status INTEGER NOT NULL DEFAULT 1, + created_at DATETIME DEFAULT (datetime('now','localtime')) +); + +CREATE TABLE IF NOT EXISTS payment_order ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_no TEXT NOT NULL UNIQUE, -- 业务订单号 + user_id INTEGER NOT NULL DEFAULT 0, + plan_id INTEGER NOT NULL DEFAULT 0, + amount_fen INTEGER NOT NULL DEFAULT 0, + channel TEXT NOT NULL DEFAULT '', -- alipay | wechat + status TEXT NOT NULL DEFAULT 'pending', -- pending | paid | closed + trade_no TEXT NOT NULL DEFAULT '', -- 第三方交易号 + notify_raw TEXT NOT NULL DEFAULT '', -- 回调原文(审计) + paid_at DATETIME, + created_at DATETIME DEFAULT (datetime('now','localtime')) +); +CREATE INDEX IF NOT EXISTS idx_payment_order_user ON payment_order(user_id, created_at); + +CREATE TABLE IF NOT EXISTS user_member ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL UNIQUE, + plan_id INTEGER NOT NULL DEFAULT 0, + expire_at DATETIME, + source TEXT NOT NULL DEFAULT 'vip_pay', -- vip_pay | ad_trial | gift + created_at DATETIME DEFAULT (datetime('now','localtime')), + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS pay_notify_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_no TEXT NOT NULL DEFAULT '', + body TEXT NOT NULL DEFAULT '', + sign TEXT NOT NULL DEFAULT '', + remote_ip TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'ok', -- ok | bad_sign | duplicate | no_order + created_at DATETIME DEFAULT (datetime('now','localtime')) +); +``` + +### 2.3 接口(RouteRegister 2 参 handler,`common.GetUserId(g.RequestFromCtx(ctx))` 取用户) + +| 路径 | 方法 | 请求 | 响应 | 说明 | +|---|---|---|---|---| +| `/member/plan/list` | GET | - | `{list: [member_plan]}` | 上架套餐 | +| `/member/status` | GET | - | `{member: {...}, is_vip, expire_at}` | 我的会员状态 | +| `/member/order/create` | POST | `{plan_id}` | `{order_no, pay_url}` | 下单 → 虎皮棋收银台 URL | +| `/member/order/notify` | POST | 表单回调 | `"success"` | **publicPaths 放行**;验签 → 幂等 → 订单 paid → 开通/续期会员 | +| `/member/order/status` | GET | `{order_no}` | `{status}` | App 轮询 | + +**支付时序**: +``` +App → POST /member/order/create → 后端生成订单 + 调虎皮棋下单 → 返回 pay_url +App → WebView 打开 pay_url(用户完成支付) +虎皮棋 → POST /member/order/notify(RSA 验签) +后端 → 幂等校验(order_no 状态机 pending→paid,重复回调忽略并记 pay_notify_log) +后端 → 更新 user_member(续费:expire_at 在原有效期上叠加,min 逻辑;过期则从现在起算) +App → GET /member/order/status 轮询(间隔 2s,超时 60s)→ 展示开通成功 +``` + +**幂等与安全**:回调必须验签(失败记 `bad_sign` 并返回非 success);`order_no` 唯一 + 状态机保证只开通一次;回调日志全量入库审计;退款 MVP 阶段客服手动处理(标记 order closed + 人工延退会员)。 + +## 3. 支柱 B:广告激励 + +### 3.1 数据模型 + +```sql +CREATE TABLE IF NOT EXISTS ad_reward_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL DEFAULT 0, + ad_type TEXT NOT NULL DEFAULT '', -- effect_extra(效果图+1) | vip_trial(体验会员1天) + reward_key TEXT NOT NULL DEFAULT '', -- "2026-07-31:effect_extra" 自然日去重粒度 + status TEXT NOT NULL DEFAULT 'ok', + created_at DATETIME DEFAULT (datetime('now','localtime')) +); +CREATE UNIQUE INDEX IF NOT EXISTS idx_ad_reward_unique ON ad_reward_log(user_id, reward_key); +``` + +### 3.2 接口 + +| 路径 | 方法 | 请求 | 响应 | 说明 | +|---|---|---|---|---| +| `/ad/reward/claim` | POST | `{ad_type}` | `{reward: {...}}` | 发放权益(限频见下) | + +**风控**(防刷,纯服务端计数,不信任客户端): +- `ad_type=effect_extra`:每日每用户限 **2 次**(`reward_key` 唯一索引 + 计数),发放后效果图当日额外 +1 次 +- `ad_type=vip_trial`:每日每用户限 **1 次**,发放 1 天体验会员(写 user_member,source=ad_trial,到期自动失效) +- 效果图限额判定逻辑改造:`EffectImageService.GenerateForPlan` 的 `CountByUserToday` 判断改为 `当日已用 ≤ 基础额度(3) + 额外次数(ad_reward_log 当日 count)`;额外次数次日归零(不落独立表,按日查询即可) + +## 4. 支柱 C/D:统一 CPS 引擎 + +### 4.1 核心抽象 + +```go +// cps/provider.go —— 数据源适配器接口(包级单例:cps.Providers 注册表) +type Provider interface { + Source() string // meituan_ota | jd_ecom | tb_ecom + SyncProducts(ctx, city string, catCode string) ([]CpsProduct, error) // 定时选品池同步 + Search(ctx, keyword string, catCode string, page int) ([]CpsProduct, error) // 实时搜索兜底 + GetLink(ctx, outerId string) (string, error) // 转链(带 pid),结果按 outerId 缓存 24h +} +``` + +- 统一 `cps_product` 选品池:联盟商品定时同步入库,列表读库(不实时调联盟);搜索接口实时兜底 +- 转链结果缓存(与 imagegen cache 同模式),点击时写 `cps_click_log` +- 未配置某联盟 key → 该 source 降级(列表为空 + App 隐藏入口) + +### 4.2 数据模型 + +```sql +CREATE TABLE IF NOT EXISTS cps_category ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + code TEXT NOT NULL UNIQUE, -- haircut / clothing / beauty / food / hotel / ticket / transport / digital ... + name TEXT NOT NULL DEFAULT '', + parent_code TEXT NOT NULL DEFAULT '', + source TEXT NOT NULL DEFAULT '', -- meituan_ota / jd_ecom / tb_ecom + source_cat_id TEXT NOT NULL DEFAULT '', -- 联盟侧类目 ID + sort INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS cps_product ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + source TEXT NOT NULL DEFAULT '', + outer_id TEXT NOT NULL DEFAULT '', -- 联盟商品 ID + category_code TEXT NOT NULL DEFAULT '', + name TEXT NOT NULL DEFAULT '', + cover_url TEXT NOT NULL DEFAULT '', + price_fen INTEGER NOT NULL DEFAULT 0, + shop_name TEXT NOT NULL DEFAULT '', + commission_rate INTEGER NOT NULL DEFAULT 0, -- 万分比 + city TEXT NOT NULL DEFAULT '', -- OTA 到店类目按城市 + scene_tags TEXT NOT NULL DEFAULT '[]', -- 场合标签 ["通勤","约会","旅行"] + raw TEXT NOT NULL DEFAULT '', -- 联盟原始数据 JSON + status INTEGER NOT NULL DEFAULT 1, + sync_at DATETIME, + created_at DATETIME DEFAULT (datetime('now','localtime')) +); +CREATE INDEX IF NOT EXISTS idx_cps_product_cat ON cps_product(source, category_code, status); + +CREATE TABLE IF NOT EXISTS cps_click_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL DEFAULT 0, + source TEXT NOT NULL DEFAULT '', + outer_id TEXT NOT NULL DEFAULT '', + scene TEXT NOT NULL DEFAULT '', -- plan_haircut / plan_item / plan_occasion / wardrobe_upgrade / member_benefit + plan_id INTEGER NOT NULL DEFAULT 0, + category_code TEXT NOT NULL DEFAULT '', + deeplink TEXT NOT NULL DEFAULT '', + ip TEXT NOT NULL DEFAULT '', + created_at DATETIME DEFAULT (datetime('now','localtime')) +); +CREATE INDEX IF NOT EXISTS idx_cps_click_user ON cps_click_log(user_id, created_at); + +CREATE TABLE IF NOT EXISTS scene_category_map ( -- 方案字段 → 联盟类目映射(零 LLM 推荐核心) + id INTEGER PRIMARY KEY AUTOINCREMENT, + scene_type TEXT NOT NULL DEFAULT '', -- haircut / item_buy / item_upgrade / occasion + occasion TEXT NOT NULL DEFAULT '', -- 通勤/约会/旅行/运动/商务(occasion 场景) + source TEXT NOT NULL DEFAULT '', + category_code TEXT NOT NULL DEFAULT '', + priority INTEGER NOT NULL DEFAULT 0 +); +``` + +### 4.3 方案驱动推荐(核心:用已有方案字段,零新增 LLM 调用) + +| 入口 | 方案字段 | 映射 | 推荐内容 | +|---|---|---|---| +| 发型卡「做同款发型」 | 发型名 + 城市 | scene_type=haircut → 丽人类目 | 理发/造型店联盟券(美团) | +| 穿衣清单「买同款」 | 单品 name/Desc | 京东联盟搜索关键词 | 电商同款卡片 | +| 穿衣清单「到店试穿」 | 单品风格 tags + 城市 | scene_type=item_upgrade → 服装类目 | 服装店联盟券(美团) | +| 场合卡「延伸优惠」 | occasion + 地点 | scene_type=occasion 映射表 | 约会→餐厅+丽人;旅行→酒店/车票/当地丽人 | +| 衣橱「找升级款」 | 旧款 category + style_tags | 京东搜索相似款 | 电商升级款 | + +### 4.4 接口 + +| 路径 | 方法 | 请求 | 响应 | 说明 | +|---|---|---|---|---| +| `/cps/category/list` | GET | - | `{list: [cps_category]}` | 统一分类树 | +| `/cps/product/list` | GET | `{source, category_code, city, page}` | `{list, has_more}` | 选品池分页 | +| `/cps/product/link` | POST | `{product_id, scene, plan_id}` | `{deeplink}` | 转链(缓存 24h)+ 记点击日志 | +| `/cps/plan/recommend` | GET | `{plan_id, scene}` | `{list: [推荐项]}` | 方案驱动推荐(发型卡/单品/场合) | +| `/cps/wardrobe/upgrade` | GET | `{item_id}` | `{list}` | 衣橱旧款升级款 | +| `/cps/my/recent` | GET | - | `{list: [点击记录]}` | 我的优惠记录(含返现状态占位) | + +**归因**:转链 URL 内嵌联盟 pid(下单时由适配器生成),联盟侧自动归因;`cps_click_log` 用于转化分析,结算数据以联盟后台为准。 + +## 5. 会员权益实现 + +- `effect_unlimited`:效果图限额判定跳过(`EffectImageService` 加 `IsVip(userId)` 查询) +- `ai_priority`:`outfit_service.Generate` 任务插入优先级字段(MVP 可用简单 FIFO + vip 优先标记,或仅权益展示占位) +- `cps_commission_x15`:VIP 购买 CPS 佣金 ×1.5 —— 结算在联盟后台,**MVP 仅权益展示**(文案"返现加成 1.5x"),真实返现二期(需联盟侧对账) +- `store_discount`:品牌合作门店(partner_store)展示"会员价"标识,到店出示会员状态(App 会员码页),自营核销二期 + +## 6. 配置(config.yml 新增段,Key 默认空) + +```yaml +payment: + xunhu_appid: "" + xunhu_appsecret: "" + notify_url: "http://<公网>/member/order/notify" # 回调需公网可达 + channel: "alipay,wechat" + +ad: + limit_effect_extra: 2 # 每日激励视频次数(效果图) + limit_vip_trial: 1 + +cps: + meituan_appkey: "" + meituan_pid: "" + meituan_shop_id: "" + jd_appkey: "" + jd_secret: "" + jd_pid: "" + tb_appkey: "" + tb_secret: "" + tb_pid: "" + sync_cron: "0 4 * * *" # 选品池定时同步 +``` + +## 7. 合规与风控 + +- **支付**:金额单位分;回调幂等 + 验签;`pay_notify_log` 全量审计;退款人工处理(记录到订单) +- **iOS 合规**:iOS 端 WebView 支付为国内惯例做法,需在 App Store 审核时注意(虚拟商品 IAP 政策风险,上线策略:iOS 端主推激励广告+门店引流,充值入口弱化或按要求接 IAP) +- **广告**:隐私政策披露第三方 SDK 收集信息;提供个性化广告关闭入口(穿山甲 SDK 提供) +- **CPS**:各联盟 API 需个人/企业账号申请(美团联盟、京东联盟、淘宝客均可个人申请);跳转链接遵守联盟推广规范(不得截流/改链接);禁用敏感类目(医疗、成人等) +- **激励防刷**:`ad_reward_log` 唯一索引 + 自然日限频;异常用户(同设备多账号)风控日志记录 + +## 8. 分期实施与成本 + +| 分期 | 内容 | 后端工作量 | 依赖 | +|---|---|---|---| +| **P0** | 会员全链路(4 表 + 5 接口 + 虎皮棋适配器 + 回调验签)+ 广告激励(1 表 + 1 接口 + 效果图限额改造) | ~2 人日 | 虎皮棋账号 | +| **P1** | CPS 引擎(4 表 + 6 接口 + 美团适配器 + 方案驱动推荐)+ 转链缓存 + 点击日志 | ~2.5 人日 | 美团联盟账号 | +| **P2** | 京东/淘宝适配器 + 会员返现加成 + 收益看板 + 风控报表 | ~2 人日 | 京东/淘宝联盟账号 | + +- **服务器成本**:零新增基础设施(SQLite 表均小体量,选品池定时同步 + 转链缓存) +- **模型成本**:零新增 LLM 调用(类目映射 + 关键词匹配) +- **维护成本**:联盟 API 变更由适配器隔离;第三方故障 → 接口降级返回错误,App 隐藏入口 + +## 9. 开发规范约束(沿用 video-factory 规范) + +- Controller→Service→DAO 三层,包级单例(`var MemberService = new(memberService)`) +- RouteRegister 反射路由,**handler 必须 2 参** `func(ctx context.Context, req *BizReq) (*BizRes, error)`;struct 名 kebab-case(`member_plan` → `/member/plan`) +- 用户 ID 一律 `common.GetUserId(g.RequestFromCtx(ctx))` +- 每表一 DAO(`dao/member_plan_dao.go` 等),`init()` 内 `CREATE TABLE IF NOT EXISTS` + seed +- 统一响应 `{"code":0,"message":"OK","data":...}`;`/member/order/notify` 加入 publicPaths +- 外部服务(支付/联盟)全部走包内适配器(`payment/`、`cps/`),业务层不直接感知 +- 配置默认空 → 降级不 panic(与 llm/weather 同模式)