diff --git a/README.md b/README.md index 1a23634..88e02f7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ npm run dev:h5 | `weather.geo` | 和风天气 v7 Key + 高德地理编码 Key,未配置时穿搭生成跳过天气/地理推荐 | | `payment` | 虎皮棋聚合支付(`xunhu_appid/appsecret` 为空则支付功能降级关闭) | | `avatar` | Tripo 3D 生成(`tripo_api_key` 为空则 /avatar/build 返回失败提示) | -| `render` | 化身帧序列预渲染(Node + headless-gl) | | `cps` | 美团/京东/淘宝联盟(key 全空则联盟入口优雅降级隐藏) | | `ad` | 广告激励限频(自然日):`effect_extra` 2 次 / `vip_trial` 1 次 | @@ -57,7 +56,7 @@ npm run dev:h5 | slogan_user_photo | 用户照片(type: 1 大头照 / 2 全身正面 / 3 全身侧面 / 4 全身背面) | | slogan_wardrobe_item | 衣橱单品(分类/季节/风格标签/颜色) | | slogan_body_measurement | 身形参数(身高/体重/三围/肩宽/肤色) | -| slogan_avatar_model | 3D 化身(Tripo 构建状态 + GLB/帧序列 URL) | +| slogan_avatar_model | 3D 化身(Tripo 构建状态 + GLB URL) | | slogan_scoring_rule | 方案评分规则(5 维权重,可配置) | | slogan_partner_store | 合作门店(形象设计/服装门店) | @@ -108,7 +107,7 @@ npm run dev:h5 | 身形 | `POST /body-measurement/save` | 保存身形参数(身高/体重/三围/肩宽/肤色) | | | 身形 | `GET /body-measurement/get` | 查询身形参数 | | | 化身 | `POST /avatar/build` | 构建 3D 化身(Tripo 图像转 3D,异步) | | -| 化身 | `GET /avatar/get` | 我的化身(GLB/帧序列/构建状态) | | +| 化身 | `GET /avatar/get` | 我的化身(GLB/构建状态) | | | 发型 | `GET /hairstyle/list` | 发型资产库 | 是 | | 穿搭 | `POST /outfit/generate` | 生成穿搭方案(异步任务) | | | 穿搭 | `GET /outfit/task/status` | 任务状态 | | @@ -148,8 +147,8 @@ pending → planning(天气获取 → 规则预筛 3 套候选 → LLM 规划 - 效果图次数可通过广告激励补充(`/ad/reward/claim`,`effect_extra` 自然日 2 次) ### 3D 化身 -- 基于三视角全身照(正面/侧面/背面)→ Tripo 图像转 3D 生成 GLB → 本地 Node(headless-gl)渲染 36 帧旋转预览 -- 构建为异步任务,`GET /avatar/get` 轮询状态;服务重启未完成任务标记失败 +- 基于三视角全身照(正面/侧面/背面)→ Tripo 图像转 3D 生成 GLB(异步任务,`GET /avatar/get` 轮询状态;服务重启未完成任务标记失败) +- 展示端为前端 three.js 渲染(H5):GLTFLoader 加载 GLB,包围盒居中 + 相机自适应,空闲自动旋转,触摸/鼠标拖拽全角度旋转;非 H5 端提示"数字人仅支持 H5 查看" ### 会员支付 - 套餐下单 → 虎皮棋聚合支付(alipay/wechat)→ `pay_url` 拉起支付 → 前端轮询订单状态 → 回调 `/member/order/notify` 幂等更新会员 @@ -175,11 +174,10 @@ server/ agent/ LLM 调用(OpenAI 兼容)+ 方案规划/兜底 + 万相出图 scoring/ 规则评分引擎(零 LLM 成本) weather/ 和风天气 + 高德地理编码 - avatar/ Tripo 3D 客户端 + GLB 帧渲染 + avatar/ Tripo 3D 客户端(图像转 3D + GLB 下载) cps/ 三联盟客户端(美团/京东/淘宝) consts/ 常量(表名/状态/数据库组) - scripts/ avatar-render(GLB 帧渲染,Dockerfile 依赖) -app-uni/ uni-app (Vue3) 多端工程 +app-uni/ uni-app (Vue3) 多端工程(three.js 化身 3D 展示) ``` ## 部署 diff --git a/server/data/slogan.db b/server/data/slogan.db index ff9687d..59ee46d 100644 Binary files a/server/data/slogan.db and b/server/data/slogan.db differ diff --git a/server/data/slogan_cps.db b/server/data/slogan_cps.db index 75760b1..35ba483 100644 Binary files a/server/data/slogan_cps.db and b/server/data/slogan_cps.db differ diff --git a/server/styleagent/dao/avatar_model_dao.go b/server/styleagent/dao/avatar_model_dao.go index a98fe69..1eb7fbb 100644 --- a/server/styleagent/dao/avatar_model_dao.go +++ b/server/styleagent/dao/avatar_model_dao.go @@ -5,7 +5,6 @@ import ( "slogan-agent/common" "slogan-agent/styleagent/consts" "slogan-agent/styleagent/model/entity" - "strings" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" @@ -34,12 +33,6 @@ func init() { if err != nil { g.Log().Warningf(ctx, "create avatar_model table failed: %v", err) } - // 旧库补列(CREATE TABLE IF NOT EXISTS 不给已存在表加列,忽略 duplicate column 错误) - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameAvatarModel+" ADD COLUMN frames_url TEXT NOT NULL DEFAULT ''"); err != nil { - if !strings.Contains(err.Error(), "duplicate column") { - g.Log().Warningf(ctx, "alter avatar_model add frames_url failed: %v", err) - } - } } func (d *avatarModelDao) Insert(ctx context.Context, data *entity.AvatarModel) (int64, error) { diff --git a/server/styleagent/model/dto/avatar_model_dto.go b/server/styleagent/model/dto/avatar_model_dto.go index e4eb805..150a7aa 100644 --- a/server/styleagent/model/dto/avatar_model_dto.go +++ b/server/styleagent/model/dto/avatar_model_dto.go @@ -22,7 +22,6 @@ type AvatarGetRes struct { BodyTemplateId int `json:"body_template_id"` SkinToneIndex int `json:"skin_tone_index"` GlbUrl string `json:"glb_url"` - FramesUrl string `json:"frames_url"` BuildStatus string `json:"build_status"` Error string `json:"error"` } diff --git a/server/styleagent/model/entity/avatar_model.go b/server/styleagent/model/entity/avatar_model.go index a0f8a7b..8e7ea82 100644 --- a/server/styleagent/model/entity/avatar_model.go +++ b/server/styleagent/model/entity/avatar_model.go @@ -10,7 +10,6 @@ type AvatarModel struct { SkinToneIndex int `orm:"skin_tone_index" json:"skin_tone_index"` FaceTextureUrl string `orm:"face_texture_url" json:"face_texture_url"` GlbUrl string `orm:"glb_url" json:"glb_url"` - FramesUrl string `orm:"frames_url" json:"frames_url"` BuildStatus string `orm:"build_status" json:"build_status"` Error string `orm:"error" json:"error"` ParamsSnapshot string `orm:"params_snapshot" json:"params_snapshot"` diff --git a/server/styleagent/service/avatar_model_service.go b/server/styleagent/service/avatar_model_service.go index d91b1f5..a651e6c 100644 --- a/server/styleagent/service/avatar_model_service.go +++ b/server/styleagent/service/avatar_model_service.go @@ -67,7 +67,7 @@ func (s *avatarService) Build(ctx context.Context, userId int64) (*dto.AvatarBui } if err := dao.AvatarModel.Update(ctx, existing.Id, map[string]any{ "face_template_id": 0, "body_template_id": 0, "skin_tone_index": 0, - "glb_url": "", "frames_url": "", + "glb_url": "", "build_status": consts.AvatarBuildProcessing, "error": "", "params_snapshot": snapshot, }); err != nil { @@ -202,7 +202,7 @@ func (s *avatarService) Get(ctx context.Context, userId int64) (*dto.AvatarGetRe } return &dto.AvatarGetRes{ FaceTemplateId: a.FaceTemplateId, BodyTemplateId: a.BodyTemplateId, - SkinToneIndex: a.SkinToneIndex, GlbUrl: a.GlbUrl, FramesUrl: a.FramesUrl, + SkinToneIndex: a.SkinToneIndex, GlbUrl: a.GlbUrl, BuildStatus: a.BuildStatus, Error: a.Error, }, nil } diff --git a/技术设计.md b/技术设计.md index 63055d2..6127520 100644 --- a/技术设计.md +++ b/技术设计.md @@ -42,7 +42,7 @@ SQLite 无 WAL 并发写,写一律回主 goroutine;跨表数据拆多条单 | 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/frames_url/build_status/params_snapshot | 每用户一行;params_snapshot 存构建参数 JSON | +| slogan_avatar_model | user_id/glb_url/build_status/params_snapshot | 每用户一行;params_snapshot 存构建参数 JSON;GLB 由前端 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 家 | @@ -112,13 +112,14 @@ POST /outfit/plan/select-main → 置 main_flag → 触发 front/side/back 三 ``` POST /avatar/build(用户维度 WithLock 防重复构建) → 校验三视角照片齐全(type 2/3/4)+ config 已配 tripo_api_key - → 落库 build_status=pending → 提交协程池 + → 落库 build_status=processing → 提交协程池 → Tripo 图像转 3D(v2.5-20250123,多视角图 → GLB)→ 轮询任务(5s,上限 900s) - → 成功后本地渲染 36 帧旋转预览(Node + headless-gl,render.node_bin)→ frames_url → done + → 下载 GLB 到 workspace/avatar/{user}/avatar.glb → done ``` - 构建结果经 `GET /avatar/get` 轮询(前端 2s × 30);重启未完成任务标记 failed -- 帧序列目录:`workspace/avatar_frames/{user}/frame_001.png...`;GLB:`workspace/avatar/{user}.glb` +- **展示端为前端 three.js 渲染**(H5):GLTFLoader 加载 GLB,包围盒居中 + 相机自适应,空闲自动旋转,触摸/鼠标拖拽全角度旋转;非 H5 端提示"数字人仅支持 H5 查看" +- GLB 路径:`workspace/avatar/{user}/avatar.glb` ### 4.4 会员支付(虎皮棋聚合支付)