# 资产铸造(AI 生成版)流程设计文档 ## 📋 文档信息 - **版本**: v1.0 - **创建日期**: 2026-01-20 - **状态**: 待确认 - **作者**: 开发团队 --- ## 📊 文档状态总结 ### ✅ 已确认的设计 - ✅ 采用异步处理机制(AI 生成耗时,不阻塞用户请求) - ✅ AI 生成的图片必须存储到 OSS(确保持久性) - ✅ 使用预签名 URL 机制访问私有 OSS 资源 - ✅ 复用现有的 `Asset` 和 `MintOrder` 数据模型 - ✅ **AI 服务先使用 Mock 版本**,实现完整的前后端对接流程 - ✅ **铸造订单失败时自动退回水晶费用**(无论 AI 问题还是链问题) - ✅ **所有图片 URL 自动生成预签名 URL**(在查询接口中) - ✅ **前端轮询频率为 3 秒** - ✅ **AI 生成失败时,前端查询返回失败消息** - ✅ **暂不支持 WebSocket、进度提示、批量铸造** ### ⚠️ 后续优化项(暂不实现) 1. WebSocket 实时通知(当前使用轮询) 2. AI 生成进度百分比显示 3. 批量铸造功能(一次提交多个素材) --- ## 1. 业务概述 ### 1.1 功能描述 用户通过上传原始图片(素材)并填写元数据信息,发起“铸造”请求。系统将: 1. 扣除用户的水晶余额作为铸造费用 2. 异步调用 AI 服务,基于原始素材生成一张新的艺术图片 3. 将 AI 生成的图片上传到 OSS 存储 4. 模拟区块链上链过程(生成交易哈希和模拟地址) 5. 更新资产状态为“已激活”,用户可在“我的藏品”中查看 ### 1.2 核心特点 - **异步处理**:AI 生成耗时较长(通常 10-60 秒),采用后台异步处理,不阻塞用户请求 - **持久化存储**:AI 生成的图片必须转存到自己的 OSS,避免临时链接失效 - **状态可追踪**:用户可通过 `order_id` 实时查询铸造进度 - **安全访问**:私有 OSS 资源通过预签名 URL 机制访问 --- ## 2. 系统架构 ### 2.1 参与角色 | 角色 | 职责 | | :--- | :--- | | **前端 (FE)** | 用户交互、图片上传、状态展示、结果呈现 | | **网关 (GW)** | 鉴权、限流、请求转发 | | **资产服务 (AssetService)** | 订单管理、资产状态更新、异步任务调度 | | **用户服务 (UserService)** | 水晶余额校验与扣减 | | **AI 代理层 (AI Task)** | 对接 AI 接口、图片转存 OSS、模拟上链 | | **阿里云 OSS** | 存储原始素材图和 AI 生成的目标图 | ### 2.2 数据流向 ``` 用户上传素材 → OSS (material_url) ↓ 创建铸造订单 → 数据库 (Asset + MintOrder) ↓ 异步 AI 处理 → AI 服务 → 下载图片 → 上传 OSS (cover_url) ↓ 更新数据库 → 状态变更 (Pending → Active) ↓ 前端轮询查询 → 返回结果(含预签名 URL) ``` --- ## 3. 详细交互流程 ### 3.1 完整流程图 ```mermaid sequenceDiagram autonumber participant 用户 participant 前端 participant 网关 participant 资产服务 participant 用户服务 participant 异步任务(AI+OSS) participant AI服务 participant OSS存储 Note over 用户, OSS存储: 第一阶段:素材准备 用户->>前端: 选择图片并填写信息 前端->>网关: GET /api/v1/assets/oss/upload-signature?type=asset 网关-->>前端: 返回STS临时凭证 + 目录路径 前端->>OSS存储: 直传素材图 (POST Object) OSS存储-->>前端: 上传成功,返回 material_url Note over 用户, OSS存储: 第二阶段:发起铸造 (同步,立即返回) 前端->>网关: POST /api/v1/assets/mints Note right of 前端: {
"name": "资产名称",
"material_url": "...",
"description": "描述"
} 网关->>资产服务: CreateMintOrder (RPC) 资产服务->>用户服务: 扣除铸造费用 (UpdateCrystalBalance) 用户服务-->>资产服务: 扣费成功 资产服务->>资产服务: DB事务:创建 Asset (Status=0) + Order (Status=PROCESSING) 资产服务-->>网关: 返回 order_id 网关-->>前端: 返回 200 OK { order_id, status: "PROCESSING" } 前端->>用户: 界面显示 "AI创作中,请稍候..." Note over 用户, OSS存储: 第三阶段:异步生成 (后台处理) rect rgb(240, 240, 240) 资产服务->>异步任务(AI+OSS): 启动 Goroutine 处理 alt AI处理成功 异步任务(AI+OSS)->>AI服务: 调用AI接口 (输入: material_url) [Mock版本] AI服务-->>异步任务(AI+OSS): 返回AI生成图片 (临时URL或Base64) 异步任务(AI+OSS)->>异步任务(AI+OSS): 下载AI图片到内存 异步任务(AI+OSS)->>OSS存储: 上传到 OSS (asset/{user_id}/{star_id}/covers/{asset_id}.png) OSS存储-->>异步任务(AI+OSS): 返回 cover_url 异步任务(AI+OSS)->>异步任务(AI+OSS): 生成模拟 TxHash (0x...) 异步任务(AI+OSS)->>资产服务: 更新 DB: Asset (Status=1, CoverURL, TxHash) 资产服务->>资产服务: 更新 Order 为 SUCCESS else AI处理失败 异步任务(AI+OSS)->>资产服务: 更新 Order 为 FAILED + error_message 资产服务->>用户服务: 退回水晶费用 (UpdateCrystalBalance) 用户服务-->>资产服务: 退费成功 end end Note over 用户, OSS存储: 第四阶段:结果获取 (轮询/展示) loop 状态轮询 (每3秒) 前端->>网关: GET /api/v1/assets/mints/:order_id 网关->>资产服务: 查询订单状态 (GetMintOrder) 资产服务-->>前端: 返回 Status=SUCCESS, cover_url=... end alt 状态为SUCCESS 资产服务-->>前端: 返回 Status=SUCCESS, cover_url_signed=... (已自动生成预签名URL) 前端->>用户: 动画展示生成的藏品 & 模拟链上地址 else 状态为FAILED 资产服务-->>前端: 返回 Status=FAILED, error_message="AI生成失败,已退回费用" 前端->>用户: 显示失败提示 & 费用已退回消息 end ``` ### 3.2 阶段说明 #### 阶段一:素材准备(前端完成) 1. 用户在前端选择图片并填写元数据(名称、描述等) 2. 前端调用 `GET /api/v1/assets/oss/upload-signature?type=asset` 获取 OSS 上传凭证 3. 前端使用临时凭证直接上传图片到 OSS(路径:`asset/{user_id}/{star_id}/materials/{filename}`) 4. 前端获得 `material_url`(原始素材的 OSS 地址) #### 阶段二:发起铸造(同步,立即返回) 1. 前端调用 `POST /api/v1/assets/mints`,传入 `material_url` 和元数据 2. 后端在事务中完成: - 扣除用户水晶余额(调用 UserService) - 创建 `Asset` 记录(`Status = 0 (Pending)`,`MaterialURL` 已设置,`CoverURL` 为空) - 创建 `MintOrder` 记录(`Status = PROCESSING`) 3. 立即返回 `order_id`,不等待 AI 处理 #### 阶段三:异步 AI 处理(后台 Goroutine) 1. 启动独立的 Goroutine 处理 AI 生成任务 2. 调用 AI 服务接口(输入:`material_url`) - **Mock 版本**:随机选择预设图片或直接复制 `material_url` 的图片 3. AI 服务返回生成的图片(可能是临时 URL 或 Base64) 4. 下载 AI 生成的图片到内存 5. 上传到 OSS(路径:`asset/{user_id}/{star_id}/covers/{asset_id}_{timestamp}.png`) 6. 生成模拟链上信息: - `TxHash`: `0x` + 64位随机16进制字符串 - `BlockNumber`: 随机生成 - `MintedAt`: 当前时间戳 7. 更新数据库: - `Asset.Status = 1 (Active)` - `Asset.CoverURL = ` - `Asset.TxHash = <模拟哈希>` - `MintOrder.Status = SUCCESS` **失败处理**(AI 超时、AI 调用失败、OSS 上传失败等): 1. 记录错误信息到 `MintOrder.error_message` 2. 更新订单状态为 `FAILED` 3. **自动退回水晶费用**:调用 `UserService.UpdateCrystalBalance`,退回 `cost_crystal` 金额 4. 记录日志,便于后续排查 #### 阶段四:结果获取(前端轮询) 1. 前端每 3 秒轮询一次 `GET /api/v1/assets/mints/:order_id` 2. **成功场景**:当 `status = SUCCESS` 时 - 前端获取到 `cover_url_signed`(后端已自动生成预签名 URL) - 展示生成的藏品图片和模拟链上地址 3. **失败场景**:当 `status = FAILED` 时 - 前端获取到 `error_message` 字段 - 展示失败提示:"AI生成失败,已退回水晶费用" - 用户可重新提交订单 --- ## 4. API 接口定义 ### 4.1 创建铸造订单 **Endpoint**: `POST /api/v1/assets/mints` **认证**: 需要 JWT Token(从 Header 中获取 `user_id` 和 `star_id`) **Request Body**: ```json { "name": "我的数字艺术藏品", "material_url": "https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/7/87/materials/original.jpg", "description": "这是由AI生成的数字艺术品", "rarity": 1, "tags": ["AI", "Art", "Digital"] } ``` **字段说明**: | 字段 | 类型 | 必填 | 说明 | | :--- | :--- | :--- | :--- | | `name` | string | ✅ | 资产名称 | | `material_url` | string | ✅ | 原始素材的 OSS URL(前端已上传到 `asset/{user_id}/{star_id}/materials/` 目录) | | `description` | string | ❌ | 资产描述 | | `rarity` | int32 | ❌ | 稀有度(预留字段) | | `tags` | string[] | ❌ | 标签数组 | **重要说明**: - ✅ `material_url` 是必填字段,前端必须在调用此接口前完成素材上传 - ✅ `cover_url` 不需要前端传入,由后端 AI 处理完成后自动生成 - ✅ 创建订单时,`Asset.CoverURL` 字段为空,等待异步任务完成后回填 **Response (200 OK)**: ```json { "code": 200, "message": "success", "data": { "order_id": "550e8400-e29b-41d4-a716-446655440000", "asset_id": 12345, "status": "PROCESSING", "created_at": 1705747200000 } } ``` **错误响应**: - `400 Bad Request`: 参数错误(缺少必填字段、URL 格式错误等) - `401 Unauthorized`: 未授权(Token 无效) - `402 Payment Required`: 水晶余额不足 - `500 Internal Server Error`: 服务器内部错误 --- ### 4.2 查询铸造订单状态 **Endpoint**: `GET /api/v1/assets/mints/:order_id` **认证**: 需要 JWT Token(只能查询自己的订单) **URL 参数**: - `order_id`: 订单 ID(UUID 格式) **Response (200 OK) - 成功状态**: ```json { "code": 200, "message": "success", "data": { "order_id": "550e8400-e29b-41d4-a716-446655440000", "asset_id": 12345, "status": "SUCCESS", "cover_url": "https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/7/87/covers/12345_1705747200.png", "cover_url_signed": "https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/7/87/covers/12345_1705747200.png?Expires=...&Signature=...", "tx_hash": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", "minted_at": 1705747260000, "created_at": 1705747200000, "updated_at": 1705747260000 } } ``` **Response (200 OK) - 处理中状态**: ```json { "code": 200, "message": "success", "data": { "order_id": "550e8400-e29b-41d4-a716-446655440000", "asset_id": 12345, "status": "PROCESSING", "cover_url": null, "cover_url_signed": null, "tx_hash": null, "minted_at": null, "created_at": 1705747200000, "updated_at": 1705747200000 } } ``` **Response (200 OK) - 失败状态**: ```json { "code": 200, "message": "success", "data": { "order_id": "550e8400-e29b-41d4-a716-446655440000", "asset_id": 12345, "status": "FAILED", "error_message": "AI生成超时,已自动退回水晶费用", "cover_url": null, "cover_url_signed": null, "tx_hash": null, "minted_at": null, "created_at": 1705747200000, "updated_at": 1705747300000 } } ``` **状态值说明**: - `PROCESSING`: AI 生成中(前端继续轮询) - `SUCCESS`: 生成成功(前端展示结果) - `FAILED`: 生成失败(前端显示 `error_message`,提示用户费用已退回) **关键设计点**: - ✅ **自动预签名**:`status = SUCCESS` 时,自动返回 `cover_url_signed` 预签名 URL - ✅ **失败信息返回**:`status = FAILED` 时,返回 `error_message` 字段,前端直接展示 - ✅ **费用已退回**:失败时,后端已自动退回水晶费用,前端无需额外处理 **错误响应**: - `404 Not Found`: 订单不存在或不属于当前用户 - `401 Unauthorized`: 未授权 --- ### 4.3 获取我的藏品列表 **Endpoint**: `GET /api/v1/assets/me` **认证**: 需要 JWT Token **Query 参数**: - `page`: 页码(默认 1) - `page_size`: 每页数量(默认 20,最大 100) - `status`: 筛选状态(可选:`pending`, `active`,默认返回全部) **Response (200 OK)**: ```json { "code": 200, "message": "success", "data": { "total": 50, "page": 1, "page_size": 20, "items": [ { "asset_id": 12345, "name": "我的数字艺术藏品", "cover_url": "https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/7/87/covers/12345_1705747200.png", "cover_url_signed": "https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/7/87/covers/12345_1705747200.png?Expires=...&Signature=...", "status": "active", "tx_hash": "0x1a2b3c4d...", "minted_at": 1705747260000, "like_count": 10, "created_at": 1705747200000 }, { "asset_id": 12346, "name": "另一个藏品", "cover_url": null, "cover_url_signed": null, "status": "pending", "tx_hash": null, "minted_at": null, "like_count": 0, "created_at": 1705747300000 } ] } } ``` **关键设计点**: - ✅ **自动预签名**:后端在返回数据前,自动为所有 `cover_url` 和 `material_url` 生成预签名 URL,放入 `*_signed` 字段 - ✅ **状态区分**:`status = pending` 时,`cover_url` 为 `null`,前端显示“生成中”占位图 - ✅ **状态筛选**:支持按状态筛选,方便用户查看“进行中”或“已完成”的藏品 - ✅ **失败状态展示**:`status = failed` 的藏品也会出现在列表中,`cover_url` 为 `null`,前端可显示“生成失败”提示 --- ### 4.4 获取藏品详情 **Endpoint**: `GET /api/v1/assets/:asset_id` **认证**: 需要 JWT Token(只能查看自己的藏品或公开藏品) **Response (200 OK)**: ```json { "code": 200, "message": "success", "data": { "asset_id": 12345, "name": "我的数字艺术藏品", "description": "这是由AI生成的数字艺术品", "material_url": "https://.../materials/original.jpg", "material_url_signed": "https://...?Expires=...&Signature=...", "cover_url": "https://.../covers/12345_1705747200.png", "cover_url_signed": "https://...?Expires=...&Signature=...", "status": "active", "tx_hash": "0x1a2b3c4d...", "block_number": 12345678, "minted_at": 1705747260000, "like_count": 10, "is_liked": false, "created_at": 1705747200000 } } ``` --- ## 5. 数据模型设计 ### 5.1 Asset 表(资产表) **现有字段复用**: | 字段 | 类型 | 用途 | 说明 | | :--- | :--- | :--- | :--- | | `id` | int64 | 资产ID | 主键 | | `owner_uid` | int64 | 所有者ID | 外键关联 users 表 | | `star_id` | int64 | 明星ID | 用于数据隔离 | | `name` | string | 资产名称 | 用户填写 | | `cover_url` | string | **AI生成图URL** | 异步处理完成后回填 | | `material_url` | string | **原始素材URL** | 前端上传时设置 | | `description` | text | 描述 | 用户填写 | | `status` | int32 | 状态 | `0=Pending`, `1=Active` | | `tx_hash` | string | 交易哈希 | 模拟生成(0x...) | | `block_number` | int64 | 区块号 | 模拟生成 | | `minted_at` | int64 | 上链时间 | 毫秒时间戳 | | `like_count` | int32 | 点赞数 | 默认 0 | | `created_at` | int64 | 创建时间 | 毫秒时间戳 | | `updated_at` | int64 | 更新时间 | 毫秒时间戳 | **关键字段映射**: - ✅ `MaterialURL` → 用户上传的原始素材(前端已上传到 OSS) - ✅ `CoverURL` → AI 生成的最终图片(异步任务完成后回填) - ✅ `Status` → `0` 表示处理中,`1` 表示已完成 ### 5.2 MintOrder 表(铸造订单表) **现有字段复用**: | 字段 | 类型 | 用途 | 说明 | | :--- | :--- | :--- | :--- | | `order_id` | string | 订单ID | UUID,主键 | | `user_id` | int64 | 用户ID | 外键关联 users 表 | | `star_id` | int64 | 明星ID | 用于数据隔离 | | `asset_id` | int64 | 资产ID | 外键关联 assets 表 | | `status` | string | 订单状态 | `PROCESSING`, `SUCCESS`, `FAILED` | | `cost_crystal` | int64 | 消耗水晶 | 铸造费用 | | `error_message` | text | 错误信息 | 失败时记录原因 | | `retry_count` | int32 | 重试次数 | 默认 0 | | `created_at` | int64 | 创建时间 | 毫秒时间戳 | | `updated_at` | int64 | 更新时间 | 毫秒时间戳 | | `minted_at` | int64 | 完成时间 | 毫秒时间戳 | --- ## 6. 状态机设计 ### 6.1 Asset 状态流转 ``` [Pending (0)] ──AI处理成功──> [Active (1)] │ │ └──AI处理失败──> [Pending (0)] ──┘ (保持Pending,记录错误信息) ``` **状态说明**: - **Pending (0)**: 资产创建后,等待 AI 处理或处理失败 - **Active (1)**: AI 处理成功,资产可用(`CoverURL` 已设置) ### 6.2 MintOrder 状态流转 ``` [PROCESSING] ──AI处理成功──> [SUCCESS] │ │ └──AI处理失败──> [FAILED] ────┘ (记录 error_message) ``` **状态说明**: - **PROCESSING**: 订单创建后,AI 处理中 - **SUCCESS**: AI 处理成功,资产已激活 - **FAILED**: AI 处理失败(超时、API 错误等) --- ## 7. 关键设计决策 ### 7.1 异步处理机制 **决策**: 采用 Goroutine 异步处理 AI 生成任务,不阻塞用户请求。 **理由**: - AI 生成通常需要 10-60 秒,同步等待会导致请求超时 - 用户体验更好:用户提交后立即获得 `order_id`,可继续其他操作 - 系统可扩展:后续可引入消息队列(如 RocketMQ)进行任务调度 **实现方式**: ```go // 在 CreateMintOrder 中,创建订单后立即启动异步任务 go s.processAIGeneration(mintOrder.OrderID, asset.ID, req.MaterialUrl) ``` ### 7.2 AI 图片必须存储到 OSS **决策**: AI 服务返回的图片(临时 URL 或 Base64)必须下载并上传到自己的 OSS。 **理由**: - AI 服务的临时 URL 通常有效期仅 1 小时,不转存会导致链接失效 - 统一管理:所有资产图片都在自己的 OSS 中,便于权限控制和 CDN 加速 - 数据安全:避免依赖第三方服务的稳定性 **实现方式**: 1. 调用 AI 服务,获取生成的图片(临时 URL 或 Base64) 2. 下载图片到内存(如果是 URL,使用 HTTP GET;如果是 Base64,解码) 3. 上传到 OSS(路径:`asset/{user_id}/{star_id}/covers/{asset_id}_{timestamp}.png`) 4. 将 OSS URL 回填到 `Asset.CoverURL` ### 7.3 预签名 URL 自动生成 **决策**: 在 `GetMyAssets`、`GetAssetDetail` 和 `GetMintOrder` 接口中,后端自动为**所有图片 URL** 生成预签名 URL。 **理由**: - 减少前端请求次数:前端不需要为每张图片单独调用预签名接口 - 统一管理:后端统一控制访问权限和有效期 - 性能优化:批量生成预签名 URL,减少 OSS API 调用 - 用户体验:前端拿到数据即可直接展示图片,无需额外请求 **实现方式**: ```go // 在 DTO 转换层,自动为所有图片 URL 生成预签名 URL func ToAssetDTO(asset *models.Asset) *dto.AssetDTO { dto := &dto.AssetDTO{ AssetID: asset.ID, CoverURL: asset.CoverURL, MaterialURL: asset.MaterialURL, } // 为 cover_url 生成预签名 URL(如果存在) if asset.CoverURL != "" { signedURL, _ := generatePresignedURL(asset.CoverURL, 3600) dto.CoverURLSigned = signedURL } // 为 material_url 生成预签名 URL(如果存在) if asset.MaterialURL != nil && *asset.MaterialURL != "" { signedURL, _ := generatePresignedURL(*asset.MaterialURL, 3600) dto.MaterialURLSigned = &signedURL } return dto } ``` **适用范围**: - ✅ `GET /api/v1/assets/me` - 藏品列表 - ✅ `GET /api/v1/assets/:asset_id` - 藏品详情 - ✅ `GET /api/v1/assets/mints/:order_id` - 订单状态查询(成功时) ### 7.4 模拟链上信息生成 **决策**: 在前期不引入链端 API 的情况下,后端模拟生成 `TxHash` 和 `BlockNumber`。 **实现方式**: ```go // 生成模拟交易哈希 txHash := fmt.Sprintf("0x%x", rand.Int63()) // 0x + 64位16进制 // 生成模拟区块号 blockNumber := rand.Int63n(1000000) + 1000000 // 设置上链时间 mintedAt := time.Now().UnixMilli() ``` **后续扩展**: 当引入真实区块链时,只需替换此处的模拟逻辑为真实的链端 SDK 调用。 --- ## 8. 错误处理与异常场景 ### 8.1 AI 生成超时 **场景**: AI 服务响应时间超过 60 秒。 **处理**: 1. 设置超时时间(60 秒,可配置) 2. 超时后,将订单状态更新为 `FAILED` 3. 记录错误信息:`error_message = "AI生成超时"` 4. **✅ 自动回退水晶费用**:调用 `UserService.UpdateCrystalBalance`,退回 `cost_crystal` 金额 5. 记录日志,便于后续排查 ### 8.2 AI 服务调用失败 **场景**: AI 服务返回错误(API 限流、服务不可用等)。 **处理**: 1. 记录错误信息到 `MintOrder.error_message`(如:"AI服务调用失败: API限流") 2. 更新订单状态为 `FAILED` 3. **✅ 自动回退水晶费用**:调用 `UserService.UpdateCrystalBalance`,退回 `cost_crystal` 金额 4. **暂不重试**:当前版本不自动重试,失败即退回费用(后续版本可考虑重试机制) ### 8.3 OSS 上传失败 **场景**: AI 图片下载成功,但上传到 OSS 失败。 **处理**: 1. 记录错误信息:`error_message = "OSS上传失败: <具体错误>"` 2. 订单状态更新为 `FAILED` 3. **✅ 自动回退水晶费用**:调用 `UserService.UpdateCrystalBalance`,退回 `cost_crystal` 金额 4. **建议**:保留 AI 服务的临时 URL(如果还在有效期内),记录到日志,便于后续手动重试 ### 8.4 链上模拟失败(预留) **场景**: 模拟链上信息生成失败(当前版本为模拟,此场景理论上不会发生,但预留处理逻辑)。 **处理**: 1. 记录错误信息 2. 订单状态更新为 `FAILED` 3. **✅ 自动回退水晶费用**:调用 `UserService.UpdateCrystalBalance`,退回 `cost_crystal` 金额 ### 8.5 用户余额不足 **场景**: 创建订单时,用户水晶余额不足。 **处理**: 1. 在事务中先检查余额,不足则直接返回错误 2. 不创建 `Asset` 和 `MintOrder` 记录 3. 返回 HTTP 402 状态码,前端提示“余额不足” ### 8.6 费用回退统一处理 **决策**: 所有失败场景(AI 超时、AI 调用失败、OSS 上传失败、链上失败)都必须自动退回水晶费用。 **实现方式**: ```go // 在异步任务失败时,统一调用费用回退 func (s *mintService) refundCrystalBalance(order *models.MintOrder) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 退回水晶费用 _, err := s.userClient.UpdateCrystalBalance(ctx, order.UserID, order.StarID, order.CostCrystal) if err != nil { logger.Logger.Error("Failed to refund crystal balance", zap.String("order_id", order.OrderID), zap.Error(err), ) return err } logger.Logger.Info("Crystal balance refunded", zap.String("order_id", order.OrderID), zap.Int64("amount", order.CostCrystal), ) return nil } ``` **注意事项**: - 费用回退必须在订单状态更新为 `FAILED` 之前完成(确保原子性) - 如果费用回退失败,需要记录告警日志,并可能需要人工介入 --- ## 9. 性能优化建议 ### 9.1 异步任务并发控制 **建议**: 限制同时处理的 AI 任务数量,避免过载。 **实现方式**: ```go // 使用 channel 作为信号量,限制并发数 var semaphore = make(chan struct{}, 10) // 最多10个并发任务 func (s *mintService) processAIGeneration(...) { semaphore <- struct{}{} // 获取信号量 defer func() { <-semaphore }() // 释放信号量 // AI 处理逻辑 } ``` ### 9.2 预签名 URL 批量生成 **建议**: 在 `GetMyAssets` 接口中,批量生成预签名 URL,减少 OSS API 调用。 **实现方式**: ```go // 一次性为所有 cover_url 生成预签名 URL for i := range assets { if assets[i].CoverURL != "" { signedURL, _ := generatePresignedURL(assets[i].CoverURL, 3600) assets[i].CoverURLSigned = signedURL } } ``` ### 9.3 数据库索引优化 **建议**: 确保以下字段已建立索引: - `assets.owner_uid + star_id`(复合索引,用于查询用户藏品) - `assets.status`(用于筛选状态) - `mint_orders.user_id + star_id`(复合索引,用于查询用户订单) - `mint_orders.status`(用于筛选订单状态) --- ## 10. 已确认问题清单 ### ✅ 已确认的设计决策 1. **AI 服务选择** ✅ - ✅ **先使用 Mock 版本**:实现完整的前后端对接流程,AI 服务暂时模拟(返回一张预设图片或随机选择) - ✅ **后续集成真实 AI 服务**:Phase 4 再集成真实的 AI 服务 API - ✅ **Mock 实现方式**:可以随机选择一张预设图片,或直接复制 `material_url` 作为 `cover_url`(仅用于测试) 2. **AI 生成超时与重试** ✅ - ✅ **超时时间**:60 秒(可配置) - ✅ **失败处理**:不自动重试,失败即退回费用(后续版本可考虑重试机制) 3. **费用回退策略** ✅ - ✅ **自动回退**:所有失败场景(AI 超时、AI 调用失败、OSS 上传失败、链上失败)都必须自动退回水晶费用 - ✅ **回退时机**:在订单状态更新为 `FAILED` 时,立即调用 `UserService.UpdateCrystalBalance` 退回费用 - ✅ **回退金额**:退回 `MintOrder.cost_crystal` 的完整金额 4. **藏品列表预签名** ✅ - ✅ **自动生成**:在 `GetMyAssets`、`GetAssetDetail` 和 `GetMintOrder` 接口中,自动为所有图片 URL 生成预签名 URL - ✅ **有效期**:3600 秒(1 小时) - ✅ **适用范围**:`cover_url` 和 `material_url` 都需要生成预签名 URL 5. **状态轮询频率** ✅ - ✅ **轮询频率**:3 秒(前端每 3 秒轮询一次 `GET /api/v1/assets/mints/:order_id`) 6. **错误信息展示** ✅ - ✅ **失败消息返回**:AI 生成失败时,`GET /api/v1/assets/mints/:order_id` 接口返回 `status = FAILED` 和 `error_message` 字段 - ✅ **前端展示**:前端直接展示 `error_message`,提示用户费用已退回 - ✅ **暂不提供重新生成**:当前版本不提供“重新生成”功能,用户需要重新提交订单 ### 🟢 暂不实现的功能(后续优化) 7. **WebSocket 实时通知** ❌ - ❌ **暂不实现**:当前版本使用轮询机制,后续版本可考虑 WebSocket 8. **AI 生成进度** ❌ - ❌ **暂不实现**:当前版本不显示进度百分比,后续版本可考虑(需要 AI 服务支持) 9. **批量铸造** ❌ - ❌ **暂不实现**:当前版本不支持一次提交多个素材,后续版本可考虑 --- ## 11. 实现计划 ### Phase 1: 核心功能实现(预计 3-5 天) - [ ] 修改 `CreateMintOrder` 接口,支持 `material_url` 参数(替换 `cover_url` 为必填) - [ ] 实现异步 AI 处理 Goroutine(**Mock 版本**:随机选择预设图片或复制 `material_url`) - [ ] 实现 AI 图片下载与 OSS 上传逻辑(Mock 版本:直接上传 `material_url` 的图片到 `covers` 目录) - [ ] 实现模拟链上信息生成(`TxHash`、`BlockNumber`、`MintedAt`) - [ ] 实现订单状态查询接口 `GET /api/v1/assets/mints/:order_id` - [ ] 实现费用回退逻辑(所有失败场景自动退回水晶) ### Phase 2: 藏品查询优化(预计 1-2 天) - [ ] 优化 `GetMyAssets` 接口,自动为所有图片 URL 生成预签名 URL - [ ] 实现 `GetAssetDetail` 接口,自动生成预签名 URL - [ ] 优化 `GetMintOrder` 接口,成功时自动生成 `cover_url_signed` - [ ] 添加状态筛选功能(`status` 参数) ### Phase 3: 错误处理与测试(预计 2-3 天) - [ ] 实现超时处理机制(60 秒超时) - [ ] 实现失败场景的统一错误处理(AI 超时、AI 调用失败、OSS 上传失败) - [ ] 实现费用回退的统一处理逻辑 - [ ] 编写单元测试和集成测试 - [ ] 编写 HTTP 完整测试流程文档 ### Phase 4: 真实 AI 服务集成(预计 2-3 天,后续版本) - [ ] 集成真实的 AI 服务 API(OpenAI DALL-E、Stable Diffusion 或其他) - [ ] 配置 AI 服务的 API Key 和 Endpoint - [ ] 实现真实 AI 图片下载与转存逻辑 - [ ] 测试真实场景下的性能与稳定性 - [ ] 替换 Mock 版本的 AI 处理逻辑 --- ## 12. 参考文档 - [资产服务设计文档](./资产服务设计文档.md) - [OSS 预签名 URL 指南](../gateway/OSS_PRESIGNED_URL_GUIDE.md) - [OSS 批量预签名 URL 指南](../gateway/OSS_BATCH_PRESIGNED_URL_GUIDE.md) - [API 测试快速指南](../gateway/API_TEST_QUICK.md) --- ## 13. 更新日志 | 版本 | 日期 | 更新内容 | 作者 | | :--- | :--- | :--- | :--- | | v1.0 | 2026-01-20 | 初始版本,包含完整流程设计和待确认问题 | 开发团队 | | v1.1 | 2026-01-20 | 根据确认结果更新:AI Mock 版本、费用自动回退、预签名 URL 自动生成、轮询频率 3 秒、失败消息返回、暂不实现的功能 | 开发团队 | --- **文档状态**: ✅ 已确认,可开始实现 **下一步**: 开始 Phase 1 的核心功能实现,优先完成前后端完整对接流程。