topfans/docs/specs/2026-05-25-laser-card-refactor-design.md

61 KiB
Raw Blame History

镭射卡功能重构技术方案(总方案)

文档版本: v3.3
状态: 待评审
创建日期: 2026-05-25
更新日期: 2026-05-27
所属项目: TopFans 铸爱 — 镭射卡Laser Card
关联服务: assetServiceDubbo-gogatewayGinfrontenduni-app

文档定位: 本文档为镭射卡重构唯一总方案。实施、评审、DDL、接口均以本文为准。
辅助材料(非总方案,不重复维护表结构):

  • docs/specs/2026-05-15-lenticular-card-multi-material-architecture-design.md — 光栅多素材规范基线(只读参考)
  • docs/specs/2026-05-24-rembg-segmentation-feasibility-study.md — 通用抠图后续调研(本期不实施)
  • docs/specs/2026-05-18-laser-card-refactor-design.md已废止,仅作历史 diff

文档目录

章节 内容
§一~二 背景、现状与目标路径
§三 魔搭抠图、Dify、aiWorkflowService、MiniMax 边界
§四 整体逻辑架构
§五 前端目录、组件隔离五图生成(无后端)
§六 数据库完整设计(含 §6.3.1 tags 约定,不新增列
§七 后端 API、铸造、页面职责、死代码
§八~十 计划、风险、验收
§十一 附录(预设种子、代码索引、组件迁移步骤

版本变更摘要

v3.3(相对 v3.2

变更项 说明
品类 / 工艺标识 不在 assets / asset_registry 新增列;铸爱大类(星卡/吧唧/海报)与工艺小类(镭射/光栅等)统一写入既有 assets.tagsJSONB 数组) 约定字符串,见 §6.3.1
镭射铸造 tags 铸造成功时 tags 必须含 cast:star_card + craft:laser(与光栅 craft:lenticular 对称)
前端常量 castloveMintForm.js 增加 CAST_TAG_* / CRAFT_TAG_LASERbuildCastloveFormSnapshot / useLaserMint 按工艺写入
Phase 1 进度 前端已落地 laser-thinkinglaser-resultLaserVariantPyramiduseLaserBatchGenerate 等;tags 双写与多素材铸造仍待 §十一附录 C 步 6 收尾

v3.2(相对 v3.1

变更项 说明
前端组件隔离 新增 §5.4 组件目录、职责、数据流、迁移步骤
五图生成说明 新增 §5.5无后端接口、JPG 格式、Storage 协议
AI 平台服务 新增 §3.5 aiWorkflowServiceDify/魔搭统一编排,供多模块接入)
Dify 工作流 新增 §3.6 镭射五图工作流 laser_card_variants_v1 节点级设计

v3.0 相对 v2.0 的核心变更

变更项 v2.0 v3.0(本版)
用户主路径 create镭射工坊 laser-card-studio → 保存铸造 createThinking五图选卡 → 铸造(与产品截图一致)
五图生成 计划废弃 laserBatchExport 保留并强化五图批量合成Phase 2 可迁 Dify 工作流
抠图 阿里云 IVPD 魔搭社区 RMBG-2.0(服务端代理)
生成编排 未定义 Dify 工作流Phase 2 服务端五图生成Phase 1 仍用客户端合成)
铸造 craftMintSubmit 多素材 同左;选卡后与光栅卡一致走 estimateMintCost + ConfirmModal
草稿 / 转赠 已舍弃 继续舍弃
前端目录 pages/castlove/laser/laser-studio.vue pages/castlove/laser/laser-thinking.vue + laser-result.vue(对齐光栅 lenticular/*

一、背景与目标

1.1 项目背景

当前镭射卡存在以下问题:

问题 说明
双轨入口混乱 线上同时存在「工坊保存」与「生成五图选卡」两套入口,产品只保留后者,前者造成维护与安全风险
铸造链路落后 选卡结果页仍走单图 material_url + createMintOrderApi,未对齐光栅卡多素材模型
抠图不安全 客户端直连阿里云 IVPD存在 AK 泄露风险
目录不规范 镭射逻辑散落在 discover/generation-*laser-card-studio,未与光栅 lenticular/* 对仗
服务端无编排 五图合成纯客户端 Canvas无法统一审计、限流与 A/B 预设

1.2 业务目标

  • 用户路径与产品稿一致:上传照片 → Thinking → 五图金字塔选卡 → 确认铸造
  • 铸造后资产可回溯 source / cutout / backdrop / composite 分层素材
  • 与光栅卡体验一致:同一套「生成 → 选卡 → 费用确认 → 铸造成功」心智
  • 为吧唧生成器等后续场景预留 scene 路由(魔搭抠图)

1.3 技术目标

目标 说明
路径收敛 下线镭射工坊;create.vue 镭射仅走 startCraftGenerationFlow(STUDIO_LASER)
规范对齐 页面 pages/castlove/laser/*、组件 components/laser/*、铸造走 craftMintSubmit.js
魔搭抠图 服务端代理魔搭 RMBG-2.0,客户端无密钥
Dify 编排 Phase 2 将五图生成迁入 Dify WorkflowPhase 1 客户端 laserBatchExport 快速上线
2 周交付 Phase 1 优先打通选卡 + 多素材铸造 + 魔搭抠图

1.4 本期明确不做

  • 镭射工坊(laser-card-studio.vue 及入口 handleSingleCraftLaserEntry
  • 相册导出:不提供「保存到相册 / 下载到本地」;禁止 uni.saveImageToPhotosAlbum 及任何面向用户的导出能力(现工坊 laser-card-studio 中的保存相册逻辑一并删除)
  • 草稿实例、转赠、领取中心、管理端模板 CRUD
  • rembg 自建部署(见独立调研文档)
  • 阿里云 IVPD 直连(客户端 AK 全部删除)
  • 陀螺仪驱动镭射预览(晃动手机变光效)

仍允许(非「相册导出」):

能力 说明
从相册选图 createchooseImage 选用户照片作为输入,属于上传源,不是导出
离屏 Canvas 临时文件 laserBatchExportcanvasToTempFilePath 仅用于五图候选与 OSS 上传 / 铸造,不对用户暴露「保存」
WebGL 屏上动画 LaserPreviewCanvas 仅页面内实时预览,不落相册

二、现状与目标路径对比

2.1 现状As-Is

flowchart TB
  subgraph online [线上实际可触达]
    A[create.vue 点生成] --> B[startCraftGenerationFlow laser]
    B --> C[generation-loading.vue]
    C --> D[laserBatchExport 客户端5图]
    D --> E[generation-result.vue 金字塔选卡]
    E --> F[单图 OSS + createMintOrderApi]
  end
  subgraph dead [应下线]
    G[create 或旧入口] --> H[laser-card-studio.vue 3600行]
    H --> I[castloveAfterLaserMint 单图]
  end
路径 入口 行为 处置
A. 五图选卡(保留) createstartCraftGenerationFlow 客户端批量 5 预设 → generation-result 主路径,重构增强
B. 镭射工坊(废弃) handleSingleCraftLaserEntrylaser-card-studio WebGL 预览 + 单图铸造 删除

2.2 目标To-Be

sequenceDiagram
  participant U as 用户
  participant C as create.vue
  participant T as laser-thinking.vue
  participant R as laser-result.vue
  participant API as Gateway
  participant MS as 魔搭 RMBG
  participant OSS as OSS
  participant Mint as craftMintSubmit

  U->>C: 上传照片 + 藏品信息 + 点「生成」
  C->>T: startCraftGenerationFlow(STUDIO_LASER)
  opt 智能抠图 Phase1可选
    T->>API: POST /api/v1/segment scene=portrait
    API->>MS: RMBG-2.0
    MS-->>T: cutout_url
  end
  T->>T: 生成5张镭射变体 Phase1客户端 / Phase2 Dify
  T->>R: 金字塔选卡 UI
  U->>R: 选一张 + 开始铸造
  R->>API: estimateMintCost
  R->>U: ConfirmModal 水晶确认
  U->>R: 确认
  R->>OSS: 上传 source/cutout/backdrop/composite
  R->>API: POST /laser/instances
  R->>Mint: submitCraftMintFromPath 多素材
  Mint-->>U: success 页

三、Dify + 魔搭 技术架构

3.1 职责划分

平台 职责 本期
魔搭社区 ModelScope 人像抠图RMBG-2.0 等),输入 HTTP 图 URL输出透明 PNG Phase 1 落地POST /api/v1/segment
Dify 镭射卡五图生成工作流编排:接收用户图 + 预设参数 → 调用合成/风格节点 → 输出 5 张图 OSS URL Phase 2 落地Phase 1 用客户端 laserBatchExport
TopFans Gateway 鉴权、限流、审计、统一错误码;向客户端暴露魔搭/Dify 密钥 Phase 1
assetService 实例 CRUD、铸造编排、操作流水 Phase 1

3.2 魔搭抠图Segment Service

接口: POST /api/v1/segment
本期仅实现: scene=portrait → 魔搭 RMBG-2.0(或团队已申请的同系列模型)

推荐调用链:

客户端 multipart 上传
  → Gateway segment_controller
    → 临时上传 OSS或内存限制 3MB 直传魔搭)
    → ModelScope Inference API服务端 AK
    → 结果写入用户 OSSlaser-card/{star_id}/{user_id}/{date}/{uuid}_cutout.png
    → 返回 cutout_oss_key + cutout_url_signed

降级: 魔搭失败 / 超时 → LC_SEGMENT_FAILED → 前端椭圆 mask与现 laser-card-studio 注释一致,逻辑迁到 laser-thinking

限流: 单用户 10 次/分钟;单 IP 30 次/分钟

scene 预留(本期返回 400 bajis / sticker / portrait_hd — 见 rembg 调研文档

3.3 Dify 镭射五图生成工作流Phase 2

目标:laserBatchExport.js 的 5 预设合成迁到服务端,便于统一预设、监控与后续 AI 增强。

建议工作流Dify Workflow

flowchart LR
  IN[输入: image_url, preset_codes, user_id] --> SEG{需要抠图?}
  SEG -->|是| MS[HTTP: 魔搭 RMBG]
  SEG -->|否| COMP[合成节点 x5]
  MS --> COMP
  COMP --> OSS[上传 OSS x5]
  OSS --> OUT[输出: variant_urls 数组]
节点 说明
开始 image_urlpreset_codes(默认 5 个)、use_cutoutbool
条件分支 use_cutout=true 时调魔搭(与 Segment 服务复用同一 HTTP 封装)
代码/HTTP 节点 调用 TopFans 内部 laser-compositor 服务Go 复刻 PRESET_VARIANTS)或 GPU 容器
结束 variants: [{ preset_id, oss_key, signed_url, width, height }]

对外 APIPhase 2 新增):

方法 路径 说明
POST /api/v1/laser/generate 创建五图生成任务(异步)
GET /api/v1/laser/generate/{job_id} 轮询状态;完成返回 5 张图 URL

Phase 1 不实现上述 APIlaser-thinking 继续调用 generateLaserVariantBatch(imagePath, canvasId),与现网一致。

3.4 与现有 MiniMax 图生图接口的关系

接口 用途 镭射卡是否使用
POST /api/v1/assets/mints/image/generation 星卡等 AI 文生图/图生图 四宫格
镭射五图 本地 Canvas 合成Phase 1或 DifyPhase 2

镭射卡走 MiniMax imageGenerationApi,避免与星卡共用 Loading 页时逻辑混淆Phase 1 镭射不再进入 discover/generation-loading(见 §5.4)。

3.5 AI 编排服务aiWorkflowServicePhase 2 起)

定位: 平台级 AI 能力微服务,与 laserService(镭射业务实例)、assetService(铸造/素材分离。Dify、魔搭等密钥在此服务内;业务方只认 workflow_key + run_id

职责 说明
工作流注册 workflow_key → Dify app_id / 版本 / 默认 inputs
异步运行 CreateRun / GetRun / CancelRun
Provider 适配 Dify编排、魔搭Segment / 可选图生图)、后续可收敛 MiniMax
统一限流与审计 user_idworkflow_key 配额

不负责: 镭射实例 CRUD、水晶扣费、materials 写入(仍属 laserService / assetService)。

Gateway 对外(建议):

方法 路径 Phase
POST /api/v1/ai/runs P2
GET /api/v1/ai/runs/:run_id P2
POST /api/v1/segment P1实现可暂放 Gateway逻辑归属 ai 服务)

镭射五图 Phase 2 workflow_key = laser_card_variants_v1,由 useLaserBatchGenerate 在开关打开时改为轮询 /ai/runs,关闭时仍走 §5.5 客户端 batch。

flowchart LR
  subgraph biz [业务]
    LT[laser-thinking]
    LS[laserService]
    AS[assetService]
  end
  subgraph ai [aiWorkflowService]
    RUN[workflow_runs]
    DIFY[Dify Adapter]
    MS[ModelScope Adapter]
  end
  LT -->|P2 CreateRun| ai
  LT -->|P1 batch 本地| LT
  LS --> AS
  ai --> DIFY
  ai --> MS

3.6 Dify 工作流详设:laser_card_variants_v1Phase 2

原则: Dify 只做编排;像素级镭射合成调内部 laser-compositor HTTPGo 复刻 laserBatchExport不用魔搭通用图生图替代 compositor效果不可控见评审结论

工作流类型: WorkflowAPI 触发,非 Chatbot

开始节点 — 输入变量:

变量 类型 必填 说明
source_image_url string 用户原图可访问 URLOSS 签名)
use_cutout boolean 是否人像抠图
preset_codes array[string] 默认 ["dream","classic","holoFull","ice","sunset"]
star_id / user_id number 隔离与审计
export_width / export_height number 默认 1080 / 1440

节点链路:

flowchart TB
  START[开始] --> NORM[代码: 补全 preset + render_config]
  NORM --> BR{use_cutout?}
  BR -->|是| SEG[HTTP POST TopFans /segment]
  BR -->|否| LOOP[循环 preset_codes]
  SEG --> SEGF{成功?}
  SEGF -->|否| WARN[warnings += SEGMENT_FAILED]
  SEGF -->|是| LOOP
  WARN --> LOOP
  LOOP --> COMP[HTTP POST laser-compositor /compose]
  COMP --> AGG[代码: 聚合 variants]
  AGG --> END[结束 JSON]
步骤 节点 说明
1 代码 preset_codes 为空则填默认 5 个;展开为带 render_config 的列表(与 laserPresets.js / DB 模板一致)
2 条件 use_cutout=true → HTTP POST /api/v1/segmentscene=portrait),失败不中断cutout_url="" 并记 warning
3 循环 对每个 preset 调 POST /v1/compose(或一次 POST /v1/compose/batch
4 结束 输出统一 JSON见下

结束节点 — 输出 JSON

{
  "workflow_key": "laser_card_variants_v1",
  "status": "succeeded",
  "cutout_oss_key": "",
  "variants": [
    {
      "preset_id": "holoFull",
      "oss_key": "laser-card/.../variant_holoFull.jpg",
      "signed_url": "https://...",
      "width": 1080,
      "height": 1440
    }
  ],
  "warnings": [],
  "engine_version": "compositor-1.0.0"
}

aiWorkflowService 将 outputs 写入 ai_workflow_runslaser-thinking / useLaserBatchGenerate 轮询完成后写入 GENERATED_IMAGES_KEYURL 数组,与 Phase 1 本地 path 数组对前端透明)。


四、整体逻辑架构

┌─────────────────────────────────────────────────────────────┐
│ 客户端 (uni-app)                                              │
│  create.vue ──► laser-thinking.vue ──► laser-result.vue      │
│       │              │                      │                 │
│       │         laserBatchExport (P1)       │ craftMintSubmit │
│       │         Dify job poll (P2)          │                 │
└───────┼──────────────┼──────────────────────┼─────────────────┘
        │              │                      │
        ▼              ▼                      ▼
┌─────────────────────────────────────────────────────────────┐
│ API Gateway (Gin)                                            │
│  /api/v1/segment          魔搭抠图代理                         │
│  /api/v1/laser/templates  模板/预设只读                        │
│  /api/v1/laser/instances  实例创建/查询                       │
│  /api/v1/laser/instances/:id/mint                           │
│  /api/v1/assets/*         复用 OSS 签名、素材、铸造估价          │
└───────────────────────────────┬─────────────────────────────┘
                                │ Dubbo
┌───────────────────────────────▼─────────────────────────────┐
│ assetService                                                 │
│  LaserCardService │ MaterialService │ MintService           │
└───────────────────────────────┬─────────────────────────────┘
                                │
        ┌───────────────┬───────────────┬───────────────────┐
        ▼               ▼               ▼
   PostgreSQL      阿里云 OSS     aiWorkflowService (P2)
   laser_* 三表     素材与成品图    Dify + 魔搭

五、前端架构(目录、组件隔离、五图生成)

5.1 目录对仗(页面 + 组件 + composable

光栅卡(基线) 镭射卡(目标)
pages/castlove/create.vue(分流) 同左,studioKind=laserlaser-thinking
pages/castlove/lenticular/lenticular-thinking.vue pages/castlove/laser/laser-thinking.vue
pages/castlove/lenticular/lenticular-result.vue pages/castlove/laser/laser-result.vue
components/lenticular/LenticularCard.vue components/laser/LaserPreviewCanvas.vue(动态预览)
components/laser/LaserVariantPyramid.vue(五图金字塔)
components/laser/LaserVariantThumb.vue(静态缩略图)
components/laser/LaserBatchCanvasHost.vue(离屏 batch canvas
composables/useLenticularCraftTiltPreview.js composables/useLaserPreview.jsWebGL/2D 动画)
composables/useLaserBatchGenerate.js(五图 batch + Storage
composables/useLaserResultSelection.js(选中 ↔ preset ↔ 铸造 path
composables/useLaserMint.js(估价 + ConfirmModal + craftMintSubmit
composables/useLaserSegment.js魔搭抠图P1 可选)
utils/craftMintSubmit.js 直接复用,扩展 material_type
utils/lenticular-engine.js utils/laser-card/laserBatchExport.jslaserPreviewWebgl.jslaserPresets.js

5.2 路由与 Storage 约定

Key / 常量 说明
GENERATION_FLOW_KEY + FLOW_MODE_LASER 不变;navigateToStudioLoading 改为跳转 /pages/castlove/laser/laser-thinking
GENERATED_IMAGES_KEY 存 5 张变体本地路径或 URL
GENERATION_RESULT_META_KEY { displayMode: 'laser', imageCount: 5, selectedPresetId }
CASTLOVE_FORM_KEY 与光栅共用表单快照
CASTLOVE_LASER_ENTRY_KEY 删除(工坊专用)

5.3 强制原则

  1. 镭射禁止新增 laser-card-studio.vue 路由与 pages.json 注册
  2. discover/generation-loadinggeneration-result 不得再含镭射 v-if 分支;镭射仅走 pages/castlove/laser/*
  3. 页面薄编排laser-thinking / laser-result< 200 行,业务在 components/laser + composable
  4. 素材绑定必须uploadMaterialApi + bindAssetMaterialsApi
  5. 选卡页铸造必须与光栅一致:estimateMintCostApiConfirmModalsubmitCraftMintFromPath
  6. 禁止 uni.saveImageToPhotosAlbum(见 §1.4
  7. 铸造时 assets.tags 写入 cast:star_card(铸爱大类)+ craft:laser(工艺小类);禁止为品类/工艺新增 cast_category / craft_type 列(见 §6.3.1
  8. material_type 仍为表单「素材类型」文案(如粉丝自制),不等于 工艺;工艺只认 tagscraft:*

5.4 前端组件隔离设计

5.4.1 隔离原则

原则 做法
按工艺分包 镭射 UI 仅在 components/laser/,不 import components/lenticular/*
引擎与 UI 分离 Canvas/WebGL 在 utils/laser-card/;组件只通过 composable 调用
预览动 / 铸造静 动画仅 LaserPreviewCanvas;铸造用 batch 静态 JPG§5.5
与 discover 脱钩 星卡/AI 四图仍用 generation-loading;镭射已迁出

5.4.2 组件与 composable 职责

模块 职责 禁止包含
LaserBatchCanvasHost 隐藏 canvas-id="laserBatchCanvas" 业务逻辑、进度 UI
useLaserBatchGenerate generateLaserVariantBatchpersistLaserPreviewImages HTTP 五图接口P1 无)
LaserPreviewCanvas 单 WebGL/2D 实例,u_time 驱动动画 陀螺仪、保存相册
useLaserPreview RAF 生命周期、preset 切换、降级 铸造、OSS
LaserVariantPyramid 金字塔布局、getCardStyle、选中态;中央 slot 放 Preview batch 合成
LaserVariantThumb 单张静态 <image> 动画
useLaserResultSelection selectedIndexpresetIdpaths[i] 扣费
useLaserMint estimateMintCost + ConfirmModal + submitCraftMintFromPath 五图生成
useLaserSegment POST /segment(可选) 实例写库

5.4.3 页面编排(薄层)

laser-thinking.vue

LaserThinkingShell礼盒 + 进度 + "Thinking",从 generation-loading 抽取)
  └── LaserBatchCanvasHost
onMounted → useLaserBatchGenerate().run() → navigateTo laser-result

laser-result.vue

LaserCraftRewardBanner可选
LaserVariantPyramid(:paths :selected @select)
  └── #hero → LaserPreviewCanvas(:source-path :preset :paused)
底部:重新生成 | 选择作品
ConfirmModal ← useLaserMint

5.4.4 组件数据流

flowchart TB
  subgraph pages [pages/castlove/laser]
    TH[laser-thinking]
    RS[laser-result]
  end
  subgraph comp [components/laser]
    BH[LaserBatchCanvasHost]
    PV[LaserPreviewCanvas]
    PY[LaserVariantPyramid]
  end
  subgraph hooks [composables]
    BG[useLaserBatchGenerate]
    LP[useLaserPreview]
    SEL[useLaserResultSelection]
    MT[useLaserMint]
  end
  subgraph util [utils/laser-card]
    BE[laserBatchExport]
    WG[laserPreviewWebgl]
  end

  TH --> BG --> BE
  TH --> BH
  RS --> PY --> PV
  PV --> LP --> WG
  RS --> SEL --> MT

5.4.5 与共享模块边界

模块 关系
castloveGenerationFlow.js navigateToStudioLoading('laser')/pages/castlove/laser/laser-thinking
create.vue startCraftGenerationFlow(STUDIO_LASER),不 import components/laser
craftMintSubmit.js 扩展多素材;由 useLaserMint 调用

5.5 五图生成Phase 1无后端接口

结论: 五图不调用 TopFans 后端生成接口;与星卡 POST /api/v1/assets/mints/image/generationMiniMax完全分离。

5.5.1 调用链

create.vue → startCraftGenerationFlow(STUDIO_LASER)
  → CASTLOVE_FORM_KEY + GENERATION_FLOW_KEY { mode: 'laser' }
  → laser-thinking.vue
  → useLaserBatchGenerate.run()
       → generateLaserVariantBatch(imagePath, 'laserBatchCanvas')
       → persistLaserPreviewImages(paths)
  → laser-result.vue 读取 GENERATED_IMAGES_KEY

输入: formData.imageformData.uploadedImagechooseImage 本地路径,非 URL

核心实现: frontend/utils/laser-card/laserBatchExport.js — 循环 5 次 PRESET_VARIANTS2D Canvas 合成(底纹 + 人像 + 镭射叠层)

5.5.2 输出格式

API ;仅 uni.canvasToTempFilePath
文件类型 JPEGfileType: 'jpg', quality: 0.96
逻辑尺寸 450 × 600
输出像素 900 × 1200destWidth/Height = 2×
返回类型 string[] 本地临时路径

5.5.3 Storage 协议

Key 内容
generated_images JSON.stringify(["path0","path1",...])
generation_result_meta {"displayMode":"laser","imageCount":5}
castlove_form_data 铸造前保留;含 image / uploadedImage

五图阶段不上传 OSS;选卡确认铸造后再 getOssSignatureApi 上传。

5.5.4 与动画预览的关系

用途 实现
用户看到的「会动的镭射」 LaserPreviewCanvasWebGLlaserPreviewWebgl.js
选卡/铸造用的图 batch 生成的 静态 JPGpaths[selectedIndex]

切换缩略图时:更新 Preview 的 preset(光效变),铸造仍绑定对应静态文件。

5.5.5 Phase 2 切换点

useLaserBatchGenerate 内根据 feature_laser_dify_compose

  • false默认§5.5.1 客户端 batch
  • truePOST /api/v1/ai/runs + 轮询 → variants[].signed_url 写入 GENERATED_IMAGES_KEY

六、数据库设计(完整)

设计原则

  1. 本期仅新增 3 张镭射业务表;素材主数据与资产绑定完全复用既有 materials + asset_material_relations
  2. 禁止新增 laser_card_instance_materials 中间表;铸造前临时素材仅存 laser_card_instances.materials_snapshotJSONB
  3. 禁止laser_card_service 内私写 materials / asset_material_relations SQL铸造成功后统一走既有 MaterialService.UploadMaterial + BindAssetMaterials RPC。
  4. 旧版单图镭射资产仍可读 assets.material_url;新资产以 asset_material_relations 为准。
  5. 品类(星卡/吧唧/海报)与工艺(镭射/光栅等)本期不新增 DDL 列;统一用既有 assets.tagsJSONB 约定前缀 cast:* / craft:*§6.3.1)。若未来按大类高频筛选成为瓶颈,再单独立项增加 cast_category 列并自 tags 回填。

6.1 表清单与职责

表名 类型 职责
laser_card_templates 新增 镭射预设模板5 条种子,只读)
laser_card_instances 新增 用户一次「选卡→铸造」业务实例
laser_card_operation_logs 新增 实例写操作审计流水
materials 复用 素材文件元数据OSS key、hash、尺寸
asset_material_relations 复用 资产 ↔ 素材角色绑定(main/source/cutout/backdrop
assets 复用 铸造产出的藏品主表
mint_orders 复用 铸造订单、水晶扣费
users 复用 owner_user_id 逻辑关联(库内可不建物理 FK
stars 复用 star_id 多星隔离

明确不建表: laser_card_instance_materialslaser_card_draftslaser_card_transfers

6.2 全库 ER 关系

erDiagram
    laser_card_templates ||--o{ laser_card_instances : "1:N template_id"
    laser_card_instances ||--o{ laser_card_operation_logs : "1:N instance_id"
    laser_card_instances }o--o| assets : "0..1 asset_id"
    laser_card_instances }o--o| mint_orders : "0..1 mint_order_id"
    assets ||--o{ asset_material_relations : "1:N asset_id"
    materials ||--o{ asset_material_relations : "1:N material_id"

    laser_card_templates {
        bigint id PK
        varchar template_code UK
        jsonb render_config
        jsonb backdrop_options
    }
    laser_card_instances {
        bigint id PK
        varchar instance_no UK
        varchar instance_ulid UK
        bigint template_id FK
        bigint owner_user_id
        bigint star_id
        varchar status
        jsonb materials_snapshot
        bigint asset_id FK
        varchar mint_order_id FK
    }
    laser_card_operation_logs {
        bigint id PK
        bigint instance_id FK
        varchar action
    }
    assets {
        bigint id PK
        bigint owner_uid
        varchar cover_url
        varchar material_url
        jsonb tags
    }
    materials {
        bigint id PK
        varchar oss_key UK
        varchar hash
    }
    asset_material_relations {
        bigint id PK
        bigint asset_id FK
        bigint material_id FK
        varchar material_type UK
        int layer_order UK
    }
    mint_orders {
        varchar order_id PK
        bigint user_id
        bigint asset_id
    }

6.3 表间关系说明(基数 + 关联键 + 时机)

关系 基数 关联字段 写入时机 说明
templatesinstances 1 : N instances.template_idtemplates.id POST /laser/instances 用户选中的预设;template_code / template_version 冗余便于列表查询
instancesoperation_logs 1 : N logs.instance_idinstances.id 每次写实例 只追加,不更新
instancesassets N : 0..1 instances.asset_idassets.id POST .../mint 成功 铸造前 asset_id 为 NULL
instancesmint_orders N : 0..1 instances.mint_order_idmint_orders.order_id 发起铸造时 与光栅共用 InitMintOrder 返回的 order_id
assetsasset_material_relations 1 : N amr.asset_idassets.id 铸造成功事务末 status=minted 后写入
materialsasset_material_relations 1 : N amr.material_idmaterials.id 同上 同一 oss_key 可对应一条 materials
instances.materials_snapshot JSONB 内 oss_key POST /instances 铸造前唯一素材暂存;不指向 materials.id
instancesusers N : 1 owner_user_id 创建实例 鉴权:owner_user_id = JWT user_id
instancesstars N : 1 star_id 创建实例 与 Attachment star_id 一致

与旧字段兼容:

字段 新镭射资产 旧镭射资产(单图)
assets.cover_url = 选中 composite 的 CDN URL
assets.material_url 可写 composite URL 作降级 唯一素材来源
assets.tags cast:star_card + craft:laser 可能仅 craft:laser 或为空;读路径需兼容
GetAssetMaterials 4 条 amr 空则降级读 material_url

6.3.1 assets.tags 品类与工艺约定(本期不新增列)

决策v3.3 铸爱「大类」与「工艺小类」均写入 assets.tags,与现网光栅 craft:lenticular 一致;assets / asset_registry 增加 cast_categorycraft_type 等列。

6.3.1.1 与 star_id / material_type 的区分

字段 / 概念 含义 镭射卡示例
assets.star_id 所属明星/星球(多星隔离) 1001
assets.material_type(可选列) 表单「素材类型」文案(粉丝自制等) 粉丝自制
tagscast:* 铸爱大类:星卡 / 吧唧 / 海报 cast:star_card
tagscraft:* 工艺小类:镭射 / 光栅 / 拍立得等 craft:laser
laser_card_instances.template_code 五套预设dream/classic/…) holoFull

6.3.1.2 约定枚举(常量须在前后端统一定义)

大类 cast:*(与 craft-select.vue 左侧分类对齐):

tag 值 产品名
cast:star_card 星卡
cast:badge 吧唧
cast:poster 海报

工艺 craft:*(与工艺卡片对齐):

tag 值 产品名 现网写入情况
craft:laser 镭射卡 Phase 1 铸造时必须写入
craft:lenticular 光栅卡 已写入(castloveMintForm.js
craft:polaroid 拍立得 待产品开通时写入
craft:tear_off 撕拉片 待产品开通时写入
craft:plain 普通单图星卡(无特殊工艺) 可选

镭射卡铸造成功时 tags 示例:

["cast:star_card", "craft:laser"]

光栅卡对照:

["cast:star_card", "craft:lenticular"]

6.3.1.3 写入时机与链路

阶段 写入位置 说明
create / 选卡页 CASTLOVE_FORM_KEY 快照字段 tags: string[] buildCastloveFormSnapshot 或镭射专用快照按 pageName / 工艺注入
POST /api/v1/assets/mints CreateMintOrderRequest.tagsmint_serviceassets.tags 与光栅相同,铸造事务内落库
laser_card_instances 不重复存 tags 工艺由业务表 + 关联 assets 表达;实例表只存 template_codematerials_snapshot

前端常量(frontend/utils/castloveMintForm.js

export const CAST_TAG_STAR_CARD = 'cast:star_card'
export const CAST_TAG_BADGE = 'cast:badge'
export const CAST_TAG_POSTER = 'cast:poster'
export const CRAFT_TAG_LASER = 'craft:laser'
// 已有export const CRAFT_TAG_LENTICULAR = 'craft:lenticular'

buildCastloveFormSnapshot 规则(摘要):

  • craft-select 进入且大类为星卡、工艺为镭射:tags = [CAST_TAG_STAR_CARD, CRAFT_TAG_LASER]
  • 光栅:tags = [CAST_TAG_STAR_CARD, CRAFT_TAG_LENTICULAR](在现有仅 craft:lenticular 基础上 cast:star_card
  • 禁止把 craft_name 中文名(如「镭射卡」)直接写入 tags

6.3.1.4 查询与索引

推荐 SQL与画廊光栅判断一致

-- 是否镭射工艺
(a.tags @> '["craft:laser"]') AS is_laser

-- 是否星卡大类下的藏品(可选,用于广场/星册筛大类)
(a.tags @> '["cast:star_card"]') AS is_star_card_cast

本期不强制tags 建 GIN 索引;若 cast:* / craft:* 筛选 QPS 升高,再评估:

CREATE INDEX idx_assets_tags_gin ON assets USING GIN (tags) WHERE deleted_at IS NULL;

明确不做:

  • 不在 asset_registry 冗余 cast_category(避免双写);星册按大类筛时 JOIN assets 或读 API 层聚合。
  • 不用 tags 存五图预设 id预设走 laser_card_instances.template_code / GENERATION_RESULT_META_KEY.selectedPresetId)。

6.3.1.5 历史数据与兼容

数据 读逻辑
仅有 craft:lenticular 视为工艺光栅;大类可推断为 cast:star_card(仅展示层,不回填库表除非做迁移脚本)
tags 为空的老镭射单图 GetAssetMaterials 空则降级 material_url根据 tags 阻断展示
回填(可选运维脚本) UPDATE assets SET tags = tags || '"cast:star_card"'::jsonb WHERE ... 需谨慎去重

6.4 复用表结构(实施须对齐现网)

6.4.0 assets.tags(已上线,本期扩展约定即可)

-- 来源docker/init-db.sql
tags jsonb   -- 字符串数组,如 ["cast:star_card","craft:laser"]
属性 说明
类型 JSONB元素为 string
本期变更 无 DDL仅扩展写入约定§6.3.1
与迁移关系 migrate_laser_card_v3_tables.sql 不包含 ALTER TABLE assets ADD COLUMN

6.4.1 materials(素材主表,已上线)

-- 来源docker/init-db.sql V6 / supabase/migrations/20260515_*
CREATE TABLE materials (
    id              BIGSERIAL PRIMARY KEY,
    oss_key         VARCHAR(255) NOT NULL,
    original_name   VARCHAR(255) NOT NULL,
    file_size       BIGINT NOT NULL,
    mime_type       VARCHAR(100) NOT NULL,
    width           INT,
    height          INT,
    hash            VARCHAR(64) NOT NULL,
    created_by      BIGINT NOT NULL,
    star_id         BIGINT NOT NULL DEFAULT 0,
    created_at      BIGINT NOT NULL,
    updated_at      BIGINT NOT NULL,
    deleted_at      BIGINT
);
CREATE UNIQUE INDEX uk_materials_oss_key ON materials(oss_key);
CREATE INDEX idx_materials_hash ON materials(hash);
CREATE INDEX idx_materials_created_by ON materials(created_by);
CREATE INDEX idx_materials_star_id ON materials(star_id);
字段 类型 镭射卡用途
oss_key VARCHAR(255) UK laser-card/{star_id}/{user_id}/{date}/{uuid}_{role}.ext
hash VARCHAR(64) 客户端 SHA256用于去重与幂等
created_by BIGINT = owner_user_id
star_id BIGINT 多星隔离

6.4.2 asset_material_relations(资产-素材关联,已上线)

CREATE TABLE asset_material_relations (
    id              BIGSERIAL PRIMARY KEY,
    asset_id        BIGINT NOT NULL,
    material_id     BIGINT NOT NULL,
    material_type   VARCHAR(50) NOT NULL,
    layer_order     INT NOT NULL DEFAULT 0,
    pos_x           DOUBLE PRECISION,
    pos_y           DOUBLE PRECISION,
    opacity         DOUBLE PRECISION DEFAULT 1.0,
    rotation        DOUBLE PRECISION DEFAULT 0,
    scale_x         DOUBLE PRECISION DEFAULT 1.0,
    scale_y         DOUBLE PRECISION DEFAULT 1.0,
    version         INT NOT NULL DEFAULT 1,
    created_at      BIGINT NOT NULL,
    updated_at      BIGINT NOT NULL,
    deleted_at      BIGINT
);
ALTER TABLE asset_material_relations
    ADD CONSTRAINT fk_amr_material
    FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE RESTRICT;
-- asset_id → assets(id) ON DELETE CASCADE 以现网 migration 为准

CREATE INDEX idx_amr_asset_id ON asset_material_relations(asset_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_amr_material_id ON asset_material_relations(material_id) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX uk_amr_asset_type_active
    ON asset_material_relations(asset_id, material_type) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX uk_amr_asset_layer_active
    ON asset_material_relations(asset_id, layer_order) WHERE deleted_at IS NULL;

镭射卡 material_typelayer_order(铸造成功后写入):

material_type layer_order 对应 snapshot.role 说明
backdrop 0 backdrop 最底层:液态底纹
source 1 source 用户原图(可选展示)
cutout 2 cutout 魔搭抠图 PNG无抠图时可省略该行
main 3 composite 用户选中的五图之一,封面

光栅卡使用 main(1) + bg(0);镭射与光栅 material_type 不混用于同一资产。
常量须定义在 backend/pkg/models/material.go,禁止业务层硬编码字符串。

6.4.3 assets / mint_orders(逻辑关联)

镭射关联字段 说明
assets laser_card_instances.asset_id 铸造成功后回填;cover_url = composite 签名 URL
mint_orders laser_card_instances.mint_order_id = order_id status: PENDING → PROCESSING → SUCCESS/FAILED

6.5 新增表:laser_card_templates

职责: 内置 5 套预设(与 laserBatchExport.jsPRESET_VARIANTS 一一对应),本期仅种子数据,无管理端 CRUD。

CREATE TABLE laser_card_templates (
    id                  BIGSERIAL PRIMARY KEY,
    template_code       VARCHAR(64)  NOT NULL,
    name                VARCHAR(100) NOT NULL,
    description         TEXT,
    status              VARCHAR(20)  NOT NULL DEFAULT 'published',
    version             INT          NOT NULL DEFAULT 1,
    thumbnail_oss_key   VARCHAR(255),
    backdrop_options    JSONB        NOT NULL DEFAULT '[]',
    render_config       JSONB        NOT NULL,
    engine_min_version  VARCHAR(32)  NOT NULL DEFAULT 'compositor-1.0.0',
    sort_order          INT          NOT NULL DEFAULT 0,
    star_id             BIGINT       NOT NULL DEFAULT 0,
    created_by          BIGINT       NOT NULL DEFAULT 0,
    created_at          BIGINT       NOT NULL,
    updated_at          BIGINT       NOT NULL,
    deleted_at          BIGINT
);

CREATE UNIQUE INDEX uk_lct_code_version
    ON laser_card_templates(template_code, version)
    WHERE deleted_at IS NULL;
CREATE INDEX idx_lct_status_star
    ON laser_card_templates(status, star_id)
    WHERE deleted_at IS NULL;
字段 类型 说明
id BIGSERIAL N 主键
template_code VARCHAR(64) N 业务编码:dream/classic/holoFull/ice/sunset
name VARCHAR(100) N 展示名
description TEXT Y 描述
status VARCHAR(20) N published | archived
version INT N 模板版本;与 uk_lct_code_version 联合唯一
thumbnail_oss_key VARCHAR(255) Y 列表缩略图 OSS
backdrop_options JSONB N [{ "id": "liquidBlue", "label": "...", "oss_key": "static/laser-bg/..." }]
render_config JSONB N 默认滑杆、beam、style_preset_id 等,< 16KB
engine_min_version VARCHAR(32) N 最低合成器版本
sort_order INT N 列表排序
star_id BIGINT N 0 = 全站通用
created_by BIGINT N 种子填 0
created_at / updated_at BIGINT N 毫秒时间戳
deleted_at BIGINT Y 软删除

种子数据5 条): 见 §十附录 A。

6.6 新增表:laser_card_instances

职责: 一次「上传 → 五图选卡 → 铸造」的业务实体;铸造前素材在 materials_snapshot,铸造后回填 asset_id 并写 asset_material_relations

CREATE TABLE laser_card_instances (
    id                      BIGSERIAL PRIMARY KEY,
    instance_no             VARCHAR(32)  NOT NULL,
    instance_ulid           VARCHAR(40)  NOT NULL,
    template_id             BIGINT       NOT NULL,
    template_code           VARCHAR(64)  NOT NULL,
    template_version        INT          NOT NULL,
    owner_user_id           BIGINT       NOT NULL,
    star_id                 BIGINT       NOT NULL,
    status                  VARCHAR(20)  NOT NULL DEFAULT 'rendered',
    client_request_id       VARCHAR(64),
    render_config           JSONB,
    materials_snapshot      JSONB        NOT NULL DEFAULT '[]',
    composite_oss_key       VARCHAR(255),
    composite_material_id   BIGINT,
    asset_id                BIGINT,
    mint_order_id           VARCHAR(100),
    idempotency_key         VARCHAR(64),
    version                 INT          NOT NULL DEFAULT 1,
    created_at              BIGINT       NOT NULL,
    updated_at              BIGINT       NOT NULL,
    deleted_at              BIGINT,

    CONSTRAINT fk_lci_template
        FOREIGN KEY (template_id) REFERENCES laser_card_templates(id),
    CONSTRAINT fk_lci_asset
        FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE SET NULL,
    CONSTRAINT fk_lci_mint_order
        FOREIGN KEY (mint_order_id) REFERENCES mint_orders(order_id) ON DELETE SET NULL,
    CONSTRAINT fk_lci_composite_material
        FOREIGN KEY (composite_material_id) REFERENCES materials(id) ON DELETE SET NULL,
    CONSTRAINT chk_lci_status
        CHECK (status IN ('rendered', 'minting', 'minted'))
);

CREATE UNIQUE INDEX uk_lci_instance_no ON laser_card_instances(instance_no);
CREATE UNIQUE INDEX uk_lci_instance_ulid ON laser_card_instances(instance_ulid);
CREATE UNIQUE INDEX uk_lci_user_client_req
    ON laser_card_instances(owner_user_id, client_request_id)
    WHERE deleted_at IS NULL AND client_request_id IS NOT NULL;
CREATE INDEX idx_lci_owner_status
    ON laser_card_instances(owner_user_id, status)
    WHERE deleted_at IS NULL;
CREATE INDEX idx_lci_asset
    ON laser_card_instances(asset_id)
    WHERE asset_id IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_lci_star_created
    ON laser_card_instances(star_id, created_at DESC)
    WHERE deleted_at IS NULL;
字段 类型 说明
id BIGSERIAL N 主键;对外可用 instance_ulid
instance_no VARCHAR(32) N LC + yyyyMMddHHmmss + 6 位随机UK
instance_ulid VARCHAR(40) N 对外 IDlc_inst_01HXYZ...UK
template_id BIGINT N FK → laser_card_templates.id
template_code VARCHAR(64) N 冗余,避免 JOIN
template_version INT N 创建时模板版本快照
owner_user_id BIGINT N 持有者
star_id BIGINT N 多星隔离
status VARCHAR(20) N 见下表状态机
client_request_id VARCHAR(64) Y 客户端 UUIDowner_user_id 联合幂等
render_config JSONB Y 用户最终渲染参数快照
materials_snapshot JSONB N 铸造前素材 OSS 清单(不存 material_id
composite_oss_key VARCHAR(255) Y 选中合成图 OSS key= snapshot 中 composite
composite_material_id BIGINT Y 铸造后回填 materials.idmain 层)
asset_id BIGINT Y 铸造成功后回填
mint_order_id VARCHAR(100) Y FK → mint_orders.order_id
idempotency_key VARCHAR(64) Y 最后一次写操作幂等键
version INT N 乐观锁
created_at / updated_at / deleted_at BIGINT 审计

状态机:

status 含义 允许操作
rendered 已选卡、素材已上传、实例已创建,未铸造 POST .../mint
minting 铸造进行中 幂等查询;失败可回到 rendered
minted 铸造完成,asset_id 已回填 只读
stateDiagram-v2
    [*] --> rendered: POST /laser/instances
    rendered --> minting: POST /mint
    minting --> minted: CreateMintOrder SUCCESS
    minting --> rendered: CreateMintOrder FAILED

6.7 新增表:laser_card_operation_logs

CREATE TABLE laser_card_operation_logs (
    id                  BIGSERIAL PRIMARY KEY,
    instance_id         BIGINT       NOT NULL,
    instance_no         VARCHAR(32)  NOT NULL,
    operator_user_id    BIGINT       NOT NULL,
    action              VARCHAR(50)  NOT NULL,
    status_before       VARCHAR(20),
    status_after        VARCHAR(20),
    request_id          VARCHAR(64),
    payload_json        JSONB,
    result_json         JSONB,
    ip_address          VARCHAR(45),
    user_agent          VARCHAR(255),
    latency_ms          INT,
    err_code            VARCHAR(32),
    created_at          BIGINT       NOT NULL,

    CONSTRAINT fk_lclog_instance
        FOREIGN KEY (instance_id) REFERENCES laser_card_instances(id) ON DELETE CASCADE
);

CREATE INDEX idx_lclog_instance_time
    ON laser_card_operation_logs(instance_id, created_at DESC);
CREATE INDEX idx_lclog_operator_time
    ON laser_card_operation_logs(operator_user_id, created_at DESC);
CREATE INDEX idx_lclog_action_time
    ON laser_card_operation_logs(action, created_at DESC);
action 触发接口 status 变化
create_instance POST /laser/instances rendered
generate_variants Thinking 页五图完成(可选记日志)
mint_start POST .../mint renderedminting
mint_success 铸造事务提交成功 mintingminted
mint_fail 铸造失败 mintingrendered

6.8 materials_snapshot JSON Schema

铸造前写入 laser_card_instances.materials_snapshot数组长度 34(无抠图时无 cutout 项):

[
  {
    "role": "source",
    "oss_key": "laser-card/1001/42/20260525/uuid_source.jpg",
    "hash": "sha256...",
    "width": 1080,
    "height": 1440,
    "mime_type": "image/jpeg",
    "file_size": 245760,
    "preset_id": null
  },
  {
    "role": "cutout",
    "oss_key": "laser-card/1001/42/20260525/uuid_cutout.png",
    "hash": "sha256...",
    "width": 1080,
    "height": 1440,
    "mime_type": "image/png",
    "file_size": 180000,
    "preset_id": null
  },
  {
    "role": "backdrop",
    "oss_key": "static/laser-bg/laser-bg-1.png",
    "hash": "static...",
    "preset_id": "liquidBlue"
  },
  {
    "role": "composite",
    "oss_key": "laser-card/1001/42/20260525/uuid_variant_holoFull.jpg",
    "hash": "sha256...",
    "width": 1080,
    "height": 1440,
    "preset_id": "holoFull"
  }
]
snapshot 字段 必填 说明
role Y source | cutout | backdrop | composite
oss_key Y OSS 对象键
hash Y 文件哈希
width / height 图必填 像素尺寸
preset_id N backdrop / composite 关联模板预设

6.9 跨表生命周期(数据落库顺序)

sequenceDiagram
    participant FE as 前端
    participant OSS as OSS
    participant LCI as laser_card_instances
    participant MAT as materials
    participant AMR as asset_material_relations
    participant AST as assets
    participant MO as mint_orders

    FE->>OSS: 上传 source/cutout/backdrop/composite
    FE->>LCI: INSERT status=rendered, materials_snapshot=[...]
    Note over LCI: materials 表尚无记录
    FE->>MO: InitMintOrder (既有)
    FE->>LCI: UPDATE status=minting, mint_order_id
    FE->>AST: CreateMintOrder (既有)
    loop 每条 snapshot.role
        FE->>MAT: UploadMaterial(oss_key) → material_id
        FE->>AMR: BindAssetMaterials(asset_id, type, layer_order)
    end
    FE->>LCI: UPDATE status=minted, asset_id, composite_material_id
阶段 写入表 关键字段
选卡确认 laser_card_instances status=rendered, materials_snapshot, composite_oss_key, render_config
发起铸造 laser_card_instances + mint_orders status=minting, mint_order_id
铸造成功 assets + materials × N + asset_material_relations × N instances.asset_id, instances.composite_material_id
完成 laser_card_instances status=minted
全程 laser_card_operation_logs 每个写操作追加一行

6.10 OSS 路径与表字段映射

文件角色 OSS 路径模式 写入 snapshot.role 铸造后 material_type
用户原图 laser-card/{star_id}/{user_id}/{yyyyMMdd}/{uuid}_source.jpg source source
抠图 PNG laser-card/.../{uuid}_cutout.png cutout cutout
内置底纹 static/laser-bg/laser-bg-{n}.png backdrop backdrop
选中合成图 laser-card/.../{uuid}_composite.jpg composite main
模板缩略图 laser-card/templates/{code}/thumb.jpg

禁止: 在 PostgreSQL 存 BLOB / base64payload_json 禁止存大图。

6.11 迁移脚本与索引汇总

迁移文件(已落库):

文件 用途
backend/scripts/migrations/migrate_laser_card_v3_tables.sql 三表 DDL + 全量 COMMENT + 5 条模板种子
backend/scripts/migrations/migrate_laser_card_v3_comments.sql 仅备注片段(被主迁移 \ir 引用)
backend/scripts/migrations/migrate_laser_card_v3_comments_only.sql 已建表时单独补备注
backend/scripts/migrations/migrate_laser_card_tags_optional.sql 可选 历史 assets.tagscast:star_card(无 DDL
-- 1. laser_card_templates
-- 2. laser_card_instances (+ FK + CHECK)
-- 3. laser_card_operation_logs (+ FK)
-- 4. INSERT 5 rows INTO laser_card_templates (附录 A)
-- 5. pkg/models 新增 MaterialTypeSource / Cutout / Backdrop 常量
-- 注意:本期不对 assets / asset_registry 做 ALTER品类与工艺走 tags 约定§6.3.1

本期新增索引一览:

索引名 类型
laser_card_templates uk_lct_code_version template_code, version UNIQUE (partial)
laser_card_templates idx_lct_status_star status, star_id INDEX (partial)
laser_card_instances uk_lci_instance_no instance_no UNIQUE
laser_card_instances uk_lci_instance_ulid instance_ulid UNIQUE
laser_card_instances uk_lci_user_client_req owner_user_id, client_request_id UNIQUE (partial)
laser_card_instances idx_lci_owner_status owner_user_id, status INDEX (partial)
laser_card_instances idx_lci_asset asset_id INDEX (partial)
laser_card_operation_logs idx_lclog_instance_time instance_id, created_at DESC INDEX

6.12 Phase 2 可选表Dify 五图任务,本期不建)

若服务端生成五图上线,可另立迁移增加 laser_card_generate_jobsjob_idowner_user_idstatusvariant_urls JSONB),与 instances 通过 generate_job_id 关联。本期不实施,不写入迁移脚本。


七、后端 API 与其它详细设计

7.1 接口一览6 个Phase 1

方法 路径 说明
GET /api/v1/laser/templates 预设列表(含 render_config
GET /api/v1/laser/templates/:code 预设详情
POST /api/v1/laser/instances 选卡并确认素材后创建实例
POST /api/v1/laser/instances/:id/mint 铸造
GET /api/v1/laser/instances/:id 实例详情
POST /api/v1/segment 魔搭抠图代理

Phase 2 追加: POST/GET /api/v1/laser/generate[/{job_id}]Dify 五图任务)

7.2 选卡页铸造流程(对齐光栅 lenticular-result

sequenceDiagram
  participant R as laser-result.vue
  participant API as Gateway
  participant Mint as craftMintSubmit

  R->>R: 用户选中 variant[index]
  R->>API: estimateMintCostApi
  R->>R: ConfirmModal 展示水晶
  R->>R: 上传 source + cutout + backdrop + composite
  R->>API: POST /laser/instances
  R->>Mint: submitCraftMintFromPath 扩展多素材
  Note over Mint: main=composite<br/>source/cutout/backdrop 额外 bind
  Mint->>API: createMintOrder + bindAssetMaterials

submitCraftMintFromPath 扩展要点:

  • 新增参数 laserMaterials: [{ role, oss_key, material_id? }]
  • main = 用户选中的合成图
  • 铸造成功后 bindAssetMaterialsApi 绑定 4 层
  • 复用现有水晶扣费与 order_id 幂等
  • createMintOrderApi 请求体 tags:必须含 cast:star_card + craft:laser(来自 CASTLOVE_FORM_KEY 快照§6.3.1mint_service 已支持 req.Tagsasset.Tags无需改表结构

7.3 页面职责摘要(细节见 §5.4、§5.5

页面 职责
laser-thinking.vue useLaserBatchGenerate + LaserBatchCanvasHost;礼盒进度 UI完成后跳 result
laser-result.vue LaserVariantPyramid + LaserPreviewCanvas + useLaserMint删除 createMintOrderApi 单图路径

展示原则§5.4 预览可动WebGL、铸造用静图batch JPG无相册导出无陀螺仪

7.4 死代码清理(实施时必须删除)

处置
pages/castlove/laser-card-studio.vue 删除(含 saveImageToPhotosAlbum 保存相册逻辑)
pages.json 中 laser-card-studio 路由 删除
handleSingleCraftLaserEntryCASTLOVE_LASER_ENTRY_KEY 写入 删除
castloveAfterLaserMint.js Phase 1 末删除(由扩展后的 craftMintSubmit 替代)
aliyunPortraitUni.jssegmentApi.js AK、segmentationCloud.js direct 分支 删除
components/lenticular/HolographicCard.vue 删除
lenticular-thinking.vue 误引 laserBatchExport 删除误引
discover/generation-loading 镭射分支 迁出后删除分支,仅保留 API/星卡模式

八、分阶段实施计划14 天)

阶段 时间 交付
Phase 1 第 110 天 魔搭 Segment 代理;三表 + 实例/铸造 APIlaser-thinking / laser-result 迁目录;craftMintSubmit 多素材 + tags 双写§6.3.1);下线工坊;客户端五图
Phase 2 第 1114 天 Dify 五图工作流 + /laser/generate 接口;灰度切换客户端/服务端生成;死代码清零;监控告警
里程碑 日期
后端接口可联调 D5
选卡 → 多素材铸造 E2E D10
灰度 10% D12
全量 + 删工坊 D14

灰度: feature_flag_laser_v3user_id 尾号;关闭时回退旧五图+单图铸造(仅灰度期,全量后删旧逻辑)。


九、风险与应对

风险 等级 应对
魔搭额度/稳定性 椭圆降级;购买额度包;超时 2s 重试
客户端/服务端五图不一致 Phase 2 Phase 2 灰度对比;验收截图 diff
选卡页改造引入回归 与光栅共用 ConfirmModalE2E 用例
删除工坊误伤入口 全局搜 laser-card-studio;产品确认无工坊入口
Dify 工作流延迟 P2 异步 job + 轮询Loading 最短展示 1.6s

十、验收标准

10.1 功能

  • create 上传 → Thinking → 五图展示 → 选卡 → 费用确认 → 铸造成功
  • 魔搭抠图成功/失败椭圆降级
  • 铸造后 GetAssetMaterials 含 main/source/cutout/backdrop
  • 铸造成功后 assets.tagscast:star_cardcraft:laser
  • laser-card-studio 路由可访问
  • 光栅流程无回归

10.2 性能

  • 五图生成Phase 1 中端机)< 8s
  • 抠图 P95 < 3s
  • 选卡页滑动流畅

10.3 安全

  • 正式包无 AK
  • /segment 鉴权 + 限流生效

10.4 规范

  • 页面位于 pages/castlove/laser/*,单页 < 200 行
  • 镭射逻辑仅在 components/laser/ + composables/useLaser*discover/generation-* 无镭射分支
  • saveImageToPhotosAlbum、无 laser-card-studio 路由
  • 五图 Phase 1 无后端生成接口(仅 canvasToTempFilePath
  • laser_card_instance_materials
  • 无私写 materials SQL
  • 为品类/工艺新增 cast_category / craft_type 列;仅用 tags 约定§6.3.1

十一、附录

A. Phase 1 五图预设与模板种子映射

序号 PRESET_VARIANTS.style template_code 底纹 backdrop
0 dream dream liquidBlue
1 classic classic liquidLavender
2 holoFull holoFull liquidPearl
3 ice ice liquidBlue
4 sunset sunset liquidLavender

种子 SQL 示例(每条需补全 render_config / backdrop_options JSON

INSERT INTO laser_card_templates (
  template_code, name, status, version,
  backdrop_options, render_config, engine_min_version,
  sort_order, star_id, created_by, created_at, updated_at
) VALUES
  ('dream',    '梦幻', 'published', 1, '[{"id":"liquidBlue","oss_key":"static/laser-bg/laser-bg-1.png"}]',    '{}', 'compositor-1.0.0', 0, 0, 0, EXTRACT(EPOCH FROM NOW())*1000, EXTRACT(EPOCH FROM NOW())*1000),
  ('classic',  '经典', 'published', 1, '[{"id":"liquidLavender","oss_key":"static/laser-bg/laser-bg-2.png"}]', '{}', 'compositor-1.0.0', 1, 0, 0, EXTRACT(EPOCH FROM NOW())*1000, EXTRACT(EPOCH FROM NOW())*1000),
  ('holoFull', '全息', 'published', 1, '[{"id":"liquidPearl","oss_key":"static/laser-bg/laser-bg-3.png"}]',   '{}', 'compositor-1.0.0', 2, 0, 0, EXTRACT(EPOCH FROM NOW())*1000, EXTRACT(EPOCH FROM NOW())*1000),
  ('ice',      '冰蓝', 'published', 1, '[{"id":"liquidBlue","oss_key":"static/laser-bg/laser-bg-1.png"}]',    '{}', 'compositor-1.0.0', 3, 0, 0, EXTRACT(EPOCH FROM NOW())*1000, EXTRACT(EPOCH FROM NOW())*1000),
  ('sunset',   '晚霞', 'published', 1, '[{"id":"liquidLavender","oss_key":"static/laser-bg/laser-bg-2.png"}]', '{}', 'compositor-1.0.0', 4, 0, 0, EXTRACT(EPOCH FROM NOW())*1000, EXTRACT(EPOCH FROM NOW())*1000);

B. 参考代码索引

文件 说明
frontend/utils/castloveGenerationFlow.js startCraftGenerationFlowpersistLaserPreviewImages
frontend/utils/laser-card/laserBatchExport.js Phase 1 五图合成§5.5
frontend/utils/laser-card/laserPreviewWebgl.js 动态预览着色器
frontend/pages/discover/generation-loading.vue 迁出 runLaserFlowlaser-thinking
frontend/pages/discover/generation-result.vue 迁出 金字塔 UI → LaserVariantPyramid
frontend/pages/castlove/laser-card-studio.vue 删除WebGL 逻辑迁入 useLaserPreview
frontend/pages/castlove/lenticular/lenticular-result.vue 铸造确认交互基线
frontend/utils/craftMintSubmit.js 多素材铸造基线
frontend/utils/castloveMintForm.js CAST_TAG_* / CRAFT_TAG_*buildCastloveFormSnapshot§6.3.1
frontend/composables/useLaserMint.js 镭射估价 + 铸造;须透传 tags
docs/specs/2026-04-07-minimax-image-generation-design.md 星卡 AI 四图(镭射不用)

C. 前端组件迁移步骤(建议顺序)

任务 产出
1 抽出 utils/laser-card/laserPresets.jsPRESET_VARIANTS 预设单源
2 useLaserBatchGenerate + LaserBatchCanvasHost Thinking 可生成五图
3 新建 laser-thinking.vuepages.jsoncastloveGenerationFlow 改跳转 脱离 discover/loading
4 useLaserPreview + LaserPreviewCanvas(从 studio 迁移 RAF/WebGL 动态预览组件
5 LaserVariantPyramid + useLaserResultSelection 金字塔选卡
6 laser-result.vue + useLaserMint 多素材铸造 E2Etags: cast:star_card + craft:laser§6.3.1
6b castloveMintForm.js:补 CAST_TAG_STAR_CARDCRAFT_TAG_LASER;光栅快照补 cast:star_card tags 双轨对齐
7 删除 generation-* 镭射分支、laser-card-studio、死代码 单轨维护
8 Phase 2aiWorkflowService + useLaserBatchGenerate Dify 分支 可选服务端五图

文档结束。 评审通过后,实施任务可拆至 docs/superpowers/plans/2026-05-25-laser-card-refactor-plan.md