Add 'server/' from commit 'e64421295fff83acbb6d6ab3d3b27f3ef8368f00'
git-subtree-dir: server git-subtree-mainline:c4e617ada7git-subtree-split:e64421295f
This commit is contained in:
@@ -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 同模式)
|
||||
@@ -0,0 +1,301 @@
|
||||
# slogan-agent 服务端设计方案
|
||||
|
||||
> 日期:2026-07-31
|
||||
> 关联:slogan-app 设计方案(App 端)见 slogan-app 仓库对应文档
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
slogan 是一个"人形象设计"应用:用户上传大头照和全身多角度照片、维护个人服装资产(衣橱),指定日期范围和地点后一键生成最适合的穿搭方案(含发型、发色、服装穿搭),方案以 3D 化身 + 2D 效果图双形态呈现。
|
||||
|
||||
本仓库为服务端(slogan-agent),提供:用户/照片/衣橱/身形管理、3D 化身构建、穿搭方案生成(规则评分 + Agent)、效果图生成、天气服务、商业化渠道(CPS 电商/门店导流/订阅)。
|
||||
|
||||
## 2. 开发规范约束(严格遵守 video-factory)
|
||||
|
||||
本服务端**架构与代码规范严格遵守** `/Users/zhangbin/Desktop/d盘/work/video-factory/video-factory` 的既有规范:
|
||||
|
||||
| 规范点 | 约束 |
|
||||
|--------|------|
|
||||
| 技术栈 | Go 1.22+ / GoFrame v2 (github.com/gogf/gf/v2) / SQLite(GoFrame ORM 驱动) |
|
||||
| 认证 | JWT (golang-jwt/jwt/v5),`/user/login` 公开,其余全部经 auth 中间件,7 天过期,bcrypt 密码 |
|
||||
| 分层 | Controller → Service → DAO → SQLite;每一层独立包,包级变量单例(`var XxxService = new(xxxService)`) |
|
||||
| 路由 | `RouteRegister`(common/http/http.go)反射注册,kebab-case 前缀,如 `/outfit/generate` |
|
||||
| 响应 | 统一 JSON `{"code":0,"message":"OK","data":...}` |
|
||||
| DAO | 每张表一个 DAO,`init()` 自动建表 + ALTER TABLE 兼容迁移 |
|
||||
| 模型 | `model/entity/`(表实体)+ `model/dto/`(请求响应,含 g.Meta 路由)+ `model/domain/` |
|
||||
| Agent | 复用 video-factory ReAct 引擎模式:chat_model.go(OpenAI 兼容 API,指数退避重试)+ react_agent.go + tools.go + context.go |
|
||||
| 模型配置 | 系统配置 + 用户配置 → MergedModelConfig(复用 model_config / user_model_config 表模式) |
|
||||
| 异步任务 | 生成任务表 + 后台轮询(复用 GenerationService.StartPoller 模式,15s 间隔) |
|
||||
| 文件存储 | `workspace/` 目录 + JWT 鉴权静态文件服务(BindHandler 方式,防路径穿越) |
|
||||
| 参数校验 | gvalid(main.go 注册自定义规则) |
|
||||
| 部署 | 单体服务,Docker(复用 video-factory Dockerfile 模式),端口 3006 规则下自定 |
|
||||
|
||||
**新增加固规则**(本项目的领域约束):
|
||||
- 所有涉及 LLM / 图像生成的调用必须经过"供应商适配层"(chat_model / imagegen),禁止业务代码直连第三方 SDK
|
||||
- 所有外部 API(人脸/天气/地理编码)必须封装为 service 层适配器,Key 存配置表不入代码
|
||||
- 费用敏感:LLM/图像调用全部走任务表异步化 + 缓存,禁止同步阻塞式出图
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
```
|
||||
Flutter App (slogan-app)
|
||||
│ HTTPS + JWT
|
||||
▼
|
||||
slogan-agent (Go 单体)
|
||||
├── controller → service → dao → SQLite
|
||||
├── avatar/ 3D 化身管线(预烘焙模板匹配 + 贴图合成 + GLB 输出)
|
||||
├── scoring/ 规则引擎评分(零 LLM 成本)
|
||||
├── agent/ 轻量 Agent(方案规划 / 兜底创作)
|
||||
├── imagegen/ 效果图客户端(多供应商适配 + 缓存)
|
||||
├── weather/ 天气适配(和风天气 + 缓存)
|
||||
├── commercial/ CPS 商品 / 门店 / 导流 / 订阅
|
||||
├── assets/avatar-templates/ 预烘焙模板库(构建期产物,运行时只读)
|
||||
└── workspace/ 用户照片 / GLB / 效果图
|
||||
```
|
||||
|
||||
## 4. 项目结构
|
||||
|
||||
```
|
||||
slogan-agent/
|
||||
├── main.go # 入口:RouteRegister + workspace 鉴权文件服务 + 后台轮询
|
||||
├── common/ # 复用 video-factory(auth / cache / http / base_dao)
|
||||
├── styleagent/ # 业务模块(对应 shortdrama)
|
||||
│ ├── controller/ # user / user-photo / wardrobe / body-measurement /
|
||||
│ │ # avatar / outfit / hairstyle / product-recommend /
|
||||
│ │ # partner-store / store-lead / subscription
|
||||
│ ├── service/ # 对应业务逻辑(每域一个)
|
||||
│ ├── dao/ # 每表一个
|
||||
│ ├── model/
|
||||
│ │ ├── entity/ # 表实体
|
||||
│ │ ├── dto/ # 请求/响应 + g.Meta 路由
|
||||
│ │ └── domain/
|
||||
│ │ ├── outfit_plan.go # 方案领域模型 + JSON 解析校验
|
||||
│ │ └── avatar_profile.go # 化身参数配置
|
||||
│ ├── avatar/ # 3D 化身管线
|
||||
│ │ ├── template_matcher.go # 特征 → 模板匹配
|
||||
│ │ ├── texture_composer.go # 面部照片贴图合成
|
||||
│ │ ├── glb_packer.go # 头部/身体/发型 GLB 组合打包
|
||||
│ │ └── template_builder/ # 构建期烘焙脚本(MakeHuman/MPFB+Blender,CI 运行,不入运行时)
|
||||
│ ├── scoring/ # 规则引擎评分
|
||||
│ │ ├── rules.go # 规则定义与配置加载
|
||||
│ │ ├── weather_rule.go # 天气适宜度
|
||||
│ │ ├── occasion_rule.go # 场合匹配
|
||||
│ │ ├── color_rule.go # 色彩和谐
|
||||
│ │ └── completeness_rule.go # 层次完整度
|
||||
│ ├── agent/ # 轻量 Agent
|
||||
│ │ ├── chat_model.go # OpenAI 兼容调用(含重试/限流,复用模式)
|
||||
│ │ ├── outfit_agent.go # 方案规划 / 兜底创作
|
||||
│ │ ├── tools.go # get_weather / list_wardrobe / score_outfit / create_plan
|
||||
│ │ └── output.go # 输出 JSON Schema 校验
|
||||
│ ├── imagegen/
|
||||
│ │ ├── client.go # ImageGenClient 接口
|
||||
│ │ ├── wanx_client.go # 通义万相
|
||||
│ │ ├── jimeng_client.go # 即梦
|
||||
│ │ └── cache.go # 按快照 hash 缓存
|
||||
│ ├── weather/
|
||||
│ │ ├── qweather.go # 和风天气适配
|
||||
│ │ └── geo.go # 地点 → 城市编码(高德)
|
||||
│ ├── commercial/
|
||||
│ │ ├── cps.go # CPS 商品检索
|
||||
│ │ ├── store.go # 合作门店 LBS
|
||||
│ │ └── subscription.go # 订阅权益
|
||||
│ └── consts/
|
||||
│ ├── public/table_name.go # 表名常量
|
||||
│ ├── public/content_type.go # 照片类型/方案来源/任务状态
|
||||
│ └── status.go # 任务状态常量
|
||||
├── assets/avatar-templates/ # 预烘焙模板(20 头部 GLB + 6 身体 GLB + 5 档皮肤贴图 + 发型 GLB)
|
||||
└── workspace/ # 用户数据(照片/GLB/效果图)
|
||||
```
|
||||
|
||||
## 5. 数据库设计(每表一个 DAO/Service/Controller)
|
||||
|
||||
### 用户域
|
||||
|
||||
| 表 | 字段要点 | 说明 |
|
||||
|----|---------|------|
|
||||
| `user` | 复用 video-factory 用户模型(role 扩展:user) | 账号密码登录 v1,手机号绑定留扩展 |
|
||||
| `user_photo` | id / user_id / type(1大头照 2全身正面 3全身侧面 4全身背面) / url / status | 3D 构建用原图 |
|
||||
| `wardrobe_item` | id / user_id / photo_url / category(上衣/下装/鞋/配饰) / season / style_tags / color_info / status | 服装资产 |
|
||||
| `body_measurement` | id / user_id / height / weight / skin_tone / fit_params(JSON) | 用户填写 + 照片估算合并 |
|
||||
|
||||
### 化身域
|
||||
|
||||
| 表 | 字段要点 | 说明 |
|
||||
|----|---------|------|
|
||||
| `avatar_model` | id / user_id / face_template_id / body_template_id / skin_tone_index / face_texture_url / glb_url / build_status / params_snapshot(JSON) | 3D 化身 |
|
||||
| `hairstyle_asset` | id / name / style_tag / glb_url / thumb_url / applicable_face / sort | 发型资产库(静态维护) |
|
||||
| `outfit_asset` | id / name / style_tag / season / glb_url / cc0_source | 服装简模资产库(少量 CC0) |
|
||||
|
||||
### 生成域
|
||||
|
||||
| 表 | 字段要点 | 说明 |
|
||||
|----|---------|------|
|
||||
| `outfit_generation_task` | id / user_id / start_date / end_date / location / weather_snapshot(JSON) / status(planning→scored→rendering→done/failed) / model_name / error | 生成任务(轮询) |
|
||||
| `outfit_plan` | id / task_id / user_id / date_range / location / source(wardrobe/recommend) / score / main_flag / hairstyle_id / hair_color / weather_ref(JSON) | 穿搭方案 |
|
||||
| `plan_outfit_item` | id / plan_id / slot(发型/上衣/下装/鞋/配饰) / source(wardrobe/recommend) / wardrobe_item_id(可空) / product_recommend_id(可空) / name / desc | 方案条目 |
|
||||
| `plan_effect_image` | id / plan_id / angle(正面/侧面/背面) / url / status / prompt_snapshot | 2D 效果图 |
|
||||
| `plan_review` | id / plan_id / user_id / action(fav/unfav) / note | 用户反馈 → 回流 Agent |
|
||||
|
||||
### 商业域
|
||||
|
||||
| 表 | 字段要点 | 说明 |
|
||||
|----|---------|------|
|
||||
| `product_recommend` | id / plan_id(可空,全局备选) / product_name / channel(淘宝/京东/抖音/拼多多) / cps_url / price / commission_rate / image_url / status | CPS 商品 |
|
||||
| `partner_store` | id / name / type(1形象设计 2服装门店) / lat / lng / address / commission_policy(JSON) / status | 合作门店 |
|
||||
| `store_lead` | id / user_id / plan_id / store_id / status(created→visited→settled/cancelled) / create_time | 导流订单 |
|
||||
| `subscription` | id / user_id / plan_type(standard/pro) / start_time / end_time / status | 会员订阅 |
|
||||
| `model_config` / `user_model_config` | 复用 video-factory 表结构 | 模型配置 |
|
||||
| `imagegen_config` | id / supplier / api_key / model_name / price_tier / enabled | 图像生成供应商配置 |
|
||||
| `scoring_rule` | id / dimension / rule_type / rules_json / enabled / version | 评分规则配置(第 7 节),内置默认值 + 可配置 |
|
||||
|
||||
## 6. 3D 化身管线(预烘焙模板 + 运行时匹配)
|
||||
|
||||
### 核心理念
|
||||
|
||||
所有"昂贵且不稳定"的环节在**构建期**完成;运行时只做轻量匹配与合成,服务器成本趋近于零。
|
||||
|
||||
### 构建期(CI 或发布流水线,一次性执行)
|
||||
|
||||
1. MakeHuman(CC0 资产,官方导出可商用)生成参数化角色基底
|
||||
2. MPFB + Blender headless 脚本烘焙:
|
||||
- 20 个头部 GLB(脸型差异,PBR 材质)
|
||||
- 6 个身体 GLB(体型差异:身高×胖瘦组合)
|
||||
- 5 档皮肤贴图(肤色深浅)
|
||||
- 10-15 个发型 GLB(CC0/自建,含发色可调材质)
|
||||
3. glTF-Transform 压缩优化,产物提交 `assets/avatar-templates/`
|
||||
|
||||
### 运行时(用户触发 build)
|
||||
|
||||
```
|
||||
用户照片(大头照+全身) + 身形参数
|
||||
→ ① 特征提取:国内人脸 API(腾讯/阿里,免费额度)→ 脸型/五官特征向量
|
||||
→ ② 模板匹配:特征向量 → 最近脸型模板(余弦距离,阈值外降级到用户滑杆微调)
|
||||
→ ③ 贴图合成:大头照人脸区域 → 面部贴图(对齐模板 UV,肤色按色阶匹配 5 档)
|
||||
→ ④ 打包:组合 头部模板 + 身体模板 + 皮肤贴图 → avatar GLB(头部/身体/发型分离存储,App 端组合换装)
|
||||
→ ⑤ 保存 avatar_model 记录(build 任务异步,状态机 pending→processing→done/failed)
|
||||
```
|
||||
|
||||
### v1 边界声明
|
||||
|
||||
- 化身定位"高相似度虚拟形象"(脸型/肤色/身形贴近),非照片级真人重建
|
||||
- 发型为资产库切换,不做 AI 重建用户真实发型
|
||||
- 用户可在 App 端用滑杆微调身形/肤色(参数化信息与照片估算合并),滑杆调整即时反映在 GLB 缩放参数上(运行时零渲染成本)
|
||||
|
||||
## 7. 规则引擎评分(零 LLM 成本)
|
||||
|
||||
每个候选方案多维度打分,总分 100:
|
||||
|
||||
| 维度 | 权重 | 规则来源 |
|
||||
|------|------|---------|
|
||||
| 天气适宜度 | 25 | 温度区间 × 服装厚度匹配表(如 <10°C 需外套;25-32°C 短袖) |
|
||||
| 场合匹配 | 25 | 日期类型(工作日/周末/节假日)→ 场合(通勤/约会/聚会)→ 服装类别规则表 |
|
||||
| 色彩和谐 | 20 | 色相环配色表(同类色/邻近色/对比色得分) |
|
||||
| 层次完整度 | 20 | 上衣/下装/鞋/配饰齐全度 + 可穿性(衣橱库存覆盖) |
|
||||
| 风格一致性 | 10 | 服装 style_tags 与用户画像(历史收藏偏好)匹配度 |
|
||||
|
||||
- 规则表配置存库(`scoring_rule` 可配置,后台可调,v1 内置默认值常量 + 配置表扩展)
|
||||
- 阈值 75 分可配置
|
||||
- 全部低于阈值 → 判定"无合格衣橱方案",触发 Agent 兜底创作
|
||||
- 免费用户效果图次数:每日 N 次(默认 3 次,配置可调);pro 订阅不限
|
||||
|
||||
## 8. 穿搭生成流程(Agent + 评分 + 兜底)
|
||||
|
||||
```
|
||||
POST /outfit/generate {start_date, end_date, location}
|
||||
→ ① 天气获取(和风 API,按 城市+日期 缓存 6h;地点经高德地理编码)
|
||||
→ ② 规则引擎预筛:衣橱 × 天气 × 场合 → 3 套候选组合(零 LLM)
|
||||
→ ③ Agent 规划(1 次 LLM 调用):
|
||||
│ 工具:get_weather / list_wardrobe / score_outfit(规则引擎) / create_plan
|
||||
│ 输出:3 套方案结构化 JSON(每套含发型建议/发色/服装条目)
|
||||
→ ④ 规则评分:≥75 → source=wardrobe;3 套全 <75 → LLM 兜底创作(1 次调用):
|
||||
│ 输入:用户画像 + 天气 + 场合 + 衣橱摘要
|
||||
│ 输出:高分方案 JSON(含 1-3 件新服装推荐,带品类/风格/价格带)
|
||||
│ 方案标记 source=recommend,新服装关联 CPS 商品检索
|
||||
→ ⑤ 保存方案(outfit_plan + plan_outfit_item),任务状态 → done
|
||||
→ ⑥ App 端 3D 即时呈现 3 套方案(无额外成本);用户选定主方案后:
|
||||
→ ⑦ 效果图按需生成(见下节),缓存命中则免费
|
||||
```
|
||||
|
||||
**成本控制**:
|
||||
- 每次生成 LLM 调用 ≤ 2 次(规划 + 兜底,兜底仅全低分时触发)
|
||||
- 评分 100% 规则引擎
|
||||
- 工具调用控制在 3-5 次内(ReAct 最大步数 8)
|
||||
|
||||
## 9. 效果图生成(按需 + 缓存 + 多供应商)
|
||||
|
||||
- 供应商适配器:`ImageGenClient` 接口,实现 通义万相(人像写真类 API)/ 即梦,配置表切换
|
||||
- 输入:用户全身照 + 方案条目描述 + 人像一致性参数 + 视角(正面/侧面/背面)
|
||||
- 触发:用户选定主方案后自动生成 3 视角;其余方案需用户主动请求(免费次数内/订阅权益检查)
|
||||
- 缓存:key = md5(user_id + wardrobe_snapshot + plan_content),命中直接返回已生成图
|
||||
- 异步:任务表 + 轮询(复用 StartPoller 模式)
|
||||
- 失败重试 1 次,仍失败则标记 failed 并降级提示(3D 方案仍可用)
|
||||
|
||||
## 10. 商业化模块
|
||||
|
||||
| 渠道 | 实现 |
|
||||
|------|------|
|
||||
| 服装电商 CPS | `product_recommend` 表;兜底方案新服装检索 CPS 商品(淘宝联盟/京东联盟/抖音电商),App 端展示跳转,按成交佣金分成 |
|
||||
| 形象设计门店 | `partner_store` type=1;发型/造型方案 LBS 推荐附近合作店(理发/造型师),`store_lead` 导流 + 到店核销 |
|
||||
| 服装门店渠道 | `partner_store` type=2;本地服装门店展示 + 方案一键到店 |
|
||||
| 会员订阅 | `subscription`:standard(免费基础)/ pro(无限生成/高清效果图/方案全量效果图解锁) |
|
||||
|
||||
## 11. API 路由表(所有请求 JWT 鉴权,除 /user/login)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | `/user/login` | 登录(公开) |
|
||||
| POST | `/user/change-password` | 修改密码 |
|
||||
| GET | `/user/profile` | 个人资料 |
|
||||
| POST | `/user-photo/upload` | 上传照片(type:大头照/全身正面/侧面/背面) |
|
||||
| GET | `/user-photo/list` | 照片列表 |
|
||||
| POST | `/user-photo/delete` | 删除照片 |
|
||||
| POST | `/wardrobe/upload` | 上传服装(分类/季节/风格标签) |
|
||||
| GET | `/wardrobe/list` | 衣橱列表 |
|
||||
| POST | `/wardrobe/update` | 更新服装信息 |
|
||||
| POST | `/wardrobe/delete` | 删除服装 |
|
||||
| POST | `/body-measurement/save` | 保存身形参数 |
|
||||
| GET | `/body-measurement/get` | 获取身形参数 |
|
||||
| POST | `/avatar/build` | 触发化身构建任务 |
|
||||
| GET | `/avatar/get` | 化身信息(GLB 地址/状态) |
|
||||
| POST | `/avatar/rebuild` | 重新构建化身 |
|
||||
| GET | `/hairstyle/list` | 发型资产列表 |
|
||||
| POST | `/outfit/generate` | 生成穿搭方案(日期范围+地点) |
|
||||
| GET | `/outfit/task/status` | 生成任务状态轮询 |
|
||||
| GET | `/outfit/plan/list` | 方案列表(历史) |
|
||||
| GET | `/outfit/plan/detail` | 方案详情(3D 配置 + 条目 + 商品/门店) |
|
||||
| POST | `/outfit/plan/select-main` | 选定主方案(触发效果图生成) |
|
||||
| POST | `/outfit/plan/effect-image/generate` | 补生成某方案效果图(权益检查) |
|
||||
| POST | `/outfit/plan/review` | 方案反馈(收藏/点赞/备注) |
|
||||
| GET | `/product-recommend/list` | 方案关联 CPS 商品 |
|
||||
| GET | `/partner-store/list` | 附近合作门店(lat/lng) |
|
||||
| POST | `/store-lead/create` | 创建导流订单 |
|
||||
| POST | `/store-lead/confirm` | 到店核销 |
|
||||
| POST | `/subscription/create` | 创建订阅 |
|
||||
| GET | `/subscription/status` | 订阅状态 |
|
||||
|
||||
## 12. 错误处理与异步任务
|
||||
|
||||
- 任务状态机:`pending → processing → done / failed`,失败写 `error` 字段,App 轮询展示
|
||||
- 外部 API(人脸/天气/LLM/图像)统一超时与指数退避重试;Key 失效/欠费返回明确错误码
|
||||
- 图片上传限制:单张 ≤ 10MB,格式 jpg/png/webp,服务端校验 + 压缩(宽边 ≤ 2048)
|
||||
- 路径安全:workspace 文件服务防 `..` 穿越(复用 video-factory BindHandler 实现)
|
||||
|
||||
## 13. 测试策略
|
||||
|
||||
- DAO/Service:表驱动单测(SQLite 内存库),覆盖评分规则各维度边界(温度档位/色彩组合/阈值判定)
|
||||
- Agent:输出 JSON Schema 校验测试 + 工具 mock(chat_model 接口化)
|
||||
- 化身管线:模板匹配单元测试(特征向量 → 模板索引)+ 贴图合成冒烟
|
||||
- Controller:路由注册冒烟 + 鉴权中间件测试
|
||||
- 关键流程集成测试:generate → 评分 → 兜底 → 出图(全 mock 外部 API)
|
||||
|
||||
## 14. 成本估算与部署(初期 1 万次生成/月)
|
||||
|
||||
| 项目 | 月成本 | 说明 |
|
||||
|------|--------|------|
|
||||
| 服务器 | ~¥150 | 2C4G 轻量云,Docker 部署单体 |
|
||||
| LLM | ~¥1000 | DeepSeek/Qwen,~¥0.1/次(≤2 次调用 + 工具) |
|
||||
| 图像生成 | ~¥7000 | 主方案 3 视角 ≈ ¥0.7/次;pro 订阅用户分摊成本 |
|
||||
| 人脸 API / 天气 | 免费额度内 | 缓存 + 免费版 |
|
||||
| **单次生成总成本** | **~¥0.8** | 其中图像生成占大头,已按最优策略控制 |
|
||||
|
||||
- 存储 v1 本地 workspace(可迁 OSS/COS,存储接口抽象预留)
|
||||
- 规模化信号:存储 > 50GB 或单机 CPU 持续 >70% → 迁对象存储 + 拆分轮询 worker
|
||||
Reference in New Issue
Block a user