topfans/backend/docs/资产铸造AI生成流程设计文档.md
2026-04-07 22:29:48 +08:00

821 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 资产铸造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 前端: {<br/> "name": "资产名称",<br/> "material_url": "...",<br/> "description": "描述"<br/>}
网关->>资产服务: 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 = <OSS地址>`
- `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`: 订单 IDUUID 格式)
**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 服务 APIOpenAI 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 的核心功能实现,优先完成前后端完整对接流程。