Files
slogan/技术设计.md
2026-08-17 13:38:41 +08:00

12 KiB
Raw Permalink Blame History

技术设计

「我的形象穿搭」后端实现细节与技术决策。功能与接口清单见 README.md,开发规范见 CLAUDE.md。

1. 总体架构

前后端一体单端口部署(默认 :8080):GoFrame 托管 API + H5 静态产物 + workspace 文件服务。

┌─────────────┐   H5 静态托管    ┌───────────────────────────┐
│  前端 app-uni │ ───────────────▶│  server (GoFrame :8080)   │
└─────────────┘                  │  RouteRegister 反射路由    │
        │  REST/JSON             │  Auth 中间件(JWT)          │
        └───────────────────────▶│  workspace/* 文件服务      │
                                 └───────────┬───────────────┘
                                             │ 4 组 SQLitedata/
                          ┌──────────────────┼──────────────────┐
                          ▼                  ▼                  ▼
                   default(slogan.db)  plan(slogan_plan.db)  pay(slogan_pay.db) / cps(slogan_cps.db)

外部依赖(均可配置,未配置时对应功能降级):大模型(LLM)、通义万相(出图)、Tripo(3D)、和风天气 + 高德地理、虎皮棋聚合支付、美团/京东/淘宝联盟。

2. 分层契约

输入 输出 约束
controller *dto.XxxReqv tag 自动校验) *dto.XxxRes, error 只透传,禁止调 dao、禁止字段搬运
service *dto.XxxReq 直传 *dto.XxxRes, error 组装/事务/校验/跨表;事务唯一入口
dao entity/单值 entity/单值 单表 SQLRecord→entity 转换在 dao 内

路由由 dto g.Meta 声明,common.RouteRegister 按 controller 结构体名反射为 kebab-case 组前缀统一注册;新增接口 = 写 dto + controller 方法,不手工注册。例外:支付回调 /member/order/notify 需裸文本响应 "success",由 controller 直接写响应体(HTTP 协议职责),经 Auth 白名单放行。

3. 数据库设计(4 库 22 表)

SQLite 无 WAL 并发写,写一律回主 goroutine;跨表数据拆多条单表 SQL + 应用层内存组装(禁 JOIN/子查询,IN 按 ≤100 分批)。金额字段一律整数分 int64。查询走 gdb.CacheOption 缓存(TTL 60sCacheName 用表名前缀),写操作后按表前缀 ClearCache

default(用户域)

关键字段 说明
slogan_user username/phone/password/role 账号;username 唯一索引
slogan_user_photo user_id/type/url/status type: 1 大头照 2 正面 3 侧面 4 背面;(user_id,type) 唯一,重复上传覆盖
slogan_wardrobe_item user_id/photo_url/name/category/season/style_tags/color_info category: 上衣/下装/鞋/配饰
slogan_body_measurement user_id/height/weight/bust/waist/hip/shoulder/skin_tone/fit_params 每用户一行
slogan_avatar_model user_id/glb_url/build_status/params_snapshot 每用户一行;params_snapshot 存构建参数 JSONGLB 由前端 three.js 直接加载
slogan_scoring_rule dimension/rule_type/rules_json/enabled 评分规则(5 维权重、效果图限次),seed 数据
slogan_partner_store name/type/lat/lng/address/commission_policy 合作门店,seed 4 家

plan(穿搭域)

关键字段 说明
slogan_hairstyle_asset name/style_tag/glb_url/thumb_url/applicable_face 发型资产,seed 8 款,公开接口
slogan_outfit_generation_task user_id/start_date/end_date/location/weather_snapshot/status/error/model_name 状态机:pending→planning→scoring→rendering→done/failed
slogan_outfit_plan task_id/user_id/date_range/title/source/score/main_flag/hairstyle_id/occasion source: wardrobe/recommend;每任务多方案
slogan_plan_outfit_item plan_id/slot/source/wardrobe_item_id/product_name/name/desc slot: top/bottom/shoes/accessory 等
slogan_plan_effect_image plan_id/angle/url/status/prompt_snapshot angle: front/side/back
slogan_plan_review plan_id/user_id/action/note action: fav/unfav

pay(支付域)

关键字段 说明
slogan_member_plan name/price_fen/duration_days/features/status 套餐配置;金额分 int64
slogan_user_member user_id/plan_id/expire_at/source 每用户一行会员态
slogan_payment_order order_no/user_id/plan_id/amount_fen/channel/status/trade_no/notify_raw status: pending/paid/failedorder_no 唯一
slogan_pay_notify_log order_no/body/sign/status 回调日志(记录类豁免分层,由支付 service 事务内直写)
slogan_ad_reward_log user_id/ad_type/reward_key/status 广告激励领取流水,限频依据

cps(联盟域)

关键字段 说明
slogan_cps_category code/name/parent_code/source/source_cat_id/sort 三源归一分类树
slogan_cps_product source/outer_id/category_code/name/cover_url/price_fen/shop_name/commission_rate/city/scene_tags/raw/status (source,outer_id) 唯一;raw 存原始报文
slogan_cps_click_log user_id/source/outer_id/scene/plan_id/category_code/deeplink/ip 点击流水
slogan_scene_category_map scene_type/occasion/source/category_code/priority 业务场景→联盟分类映射

豁免分层规则(与 CLAUDE.md 一致):

  • 完全豁免(无任何分层文件)pay_notify_log(记录类,建表归 payment_order_dao init(),读写由支付 service 事务内直写)
  • 豁免 controller/dto(保留 entity/dao/service,无独立 HTTP 出入口)plan_effect_imageplan_outfit_item(方案详情聚合透出)、scoring_rule(纯内部配置)、user_membermember/status 状态聚合透出)——禁止造无路由的空壳 controller/dto 门面
  • 其余 17 表五层齐全,每层目录文件数 = 分层表数(21),非表文件不进业务分层目录

4. 核心流程设计

4.1 穿搭生成(slogan_outfit_generation_task 状态机)

POST /outfit/generate
  → 校验(日期不倒挂、衣橱 ≥3 件、task 唯一性)→ 落任务(pending) → 提交协程池
  → planning:和风天气(7天预报) + 高德地理编码(location→adcode) → 规则预筛 3 套候选(衣橱季节/风格匹配)
      → LLM 规划 1 次调用(OpenAI 兼容,temperature 0.8,超时 300s,重试 3 次)
  → scoring:规则引擎 5 维评分(天气 25 / 场合 25 / 色彩 20 / 完整度 20 / 风格 10,阈值 75)
  → 全低分 → LLM 兜底创作 1 次(recommend 来源方案)
  → 落库 outfit_plan + plan_outfit_item → done
  • 任务状态经 GET /outfit/task/status 轮询(前端 2s × 90);任何失败置 failed + 明确错误文案
  • 服务重启时未完成任务标记 failed(main.go StartWorker 恢复,避免重复消耗 LLM 费用)
  • 并行调度:任务提交走 common 协程池(大小来自 config pool 节点,consts 提供默认值),禁止裸 go

4.2 效果图(万相异步任务)

POST /outfit/plan/select-main → 置 main_flag → 触发 front/side/back 三张效果图任务(提交协程池)
  → 万相 wan2.7-image-pro 提交异步任务 → 轮询任务状态(5s × 60)→ 下载落盘 workspace/plan_effect/{user}/{plan}/{angle}.png → done
  • 内容 hash 缓存 24h(同方案不重复出图)
  • 每日限 3 次(scoring_ruleeffect_limit 维度),超限返回明确错误;广告激励可补充次数(见 4.5)
  • prompt 由方案 item 描述组装(含发型/肤色/场景),prompt_snapshot 落库便于回溯

4.3 3D 化身(Tripo 图像转 3D

POST /avatar/build(用户维度 WithLock 防重复构建)
  → 校验三视角照片齐全(type 2/3/4+ config 已配 tripo_api_key
  → 落库 build_status=processing → 提交协程池
  → Tripo 图像转 3Dv2.5-20250123,多视角图 → GLB)→ 轮询任务(5s,上限 900s
  → 下载 GLB 到 workspace/avatar/{user}/avatar.glb → done
  • 构建结果经 GET /avatar/get 轮询(前端 2s × 30);重启未完成任务标记 failed
  • 展示端为前端 three.js 渲染H5):GLTFLoader 加载 GLB,包围盒居中 + 相机自适应,空闲自动旋转,触摸/鼠标拖拽全角度旋转;非 H5 端提示"数字人仅支持 H5 查看"
  • GLB 路径:workspace/avatar/{user}/avatar.glb

4.4 会员支付(虎皮棋聚合支付)

POST /member/order/create → 生成 order_no + 调 xunhu 下单(amount 分→元仅此对接处,协议要求)→ 返回 pay_url
前端打开 pay_url → 轮询 GET /member/order/status2s × 30
POST /member/order/notify(公开,裸文本 "success")→ 验签 → 按 order_no 幂等(已 paid 直接 success
  → 事务:payment_order 置 paid + user_member upsert 会员态 + pay_notify_log 落日志
  • xunhu_appid/appsecret 未配置 → 支付接口返回"支付功能未配置",前端降级隐藏充值入口
  • 回调重复投递幂等:以 order_no 为唯一键查重,重复直接返回 success

4.5 广告激励(限频)

POST /ad/reward/claimad_type: effect_extra / vip_trial
  → 自然日限频:effect_extra 2 次 / vip_trial 1 次(ad_reward_log 当日计数)
  → 超限返回明确错误;effect_extra 增加当日效果图额度,vip_trial 发放体验会员

4.6 CPS 联盟

  • 定时同步:cps.sync_cron(默认 0 4 * * *)拉取美团/京东/淘宝选品池,按 (source,outer_id) upsert,分类归一到 slogan_cps_category
  • 推荐链路:场景(发型卡/买同款/到店试穿/延伸优惠/找升级款/会员权益)→ scene_category_map → 分类 → 商品池筛选(city/scene_tags
  • 转链:POST /cps/product/link 调联盟转链接口 + cps_click_log 落点击流水(按 (user,outer_id,scene) 去重)
  • key 全空 → 联盟入口优雅降级隐藏(前端不渲染)

5. 关键技术决策

决策 方案 理由
存储 SQLite 4 库分域 单机部署零运维;分域隔离写锁争用;文件即备份
金额 整数分 int64common.RoundInt 四舍五入 杜绝浮点误差;前端 ÷100 展示
查询缓存 gdb.CacheOptionTTL 60sCacheName=表名前缀+参数),写后 ClearCache(表前缀) 命中率高、清理精确;防"库里已改、查询旧值"
并发 并行点三处落地:common 池封装 → consts 默认大小 → config pool 节点;用户维度互斥用 common.WithLock 统一入口、可配置、防裸 go 失控
common.WithLock[T] 泛型:配置 redis → redis 锁(SET NX EX + token 删除),否则 gcache 内存锁;ErrLockHeld 重试,defer 释放 单机/多实例部署形态自适应
任务恢复 启动时未完成任务置 failed 防重启后重复消耗外部服务费用
回调幂等 业务唯一键先查重再落库 支付回调/CPS 同步都可能重复投递
图片 URL 相对路径 /workspace/...,前端 resolveUrl() 拼 BASE_URL 部署地址变化前端一处配置

6. 风险与备选

  • LLM 输出不稳定:评分引擎兜底 + 全低分走 recommend 创作;prompt 模板在 agent/ 集中维护
  • 万相/Tripo 任务长耗时:异步任务 + 轮询;超时置 failed 可重试
  • SQLite 并发写锁:写回主 goroutine 串行;分库降低跨域锁争用;必要时可切 WAL 或迁移 PG
  • 缓存脏读:写后按表前缀清缓存全覆盖(含 Insert);冒烟回归验证改后读新值