topfans/backend/docs/铸爱创作模块技术设计文档.md
2026-04-10 16:17:45 +08:00

1541 lines
48 KiB
Markdown
Raw 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.

# 铸爱创作模块 技术设计文档
> **文档版本:** V1.0
> **创建日期:** 2026-04-09
> **关联产品文档:** 铸爱创作模块PRD.md
> **状态:** 初稿
---
## 1. 产品需求概述
### 1.1 核心功能
| 功能模块 | 说明 |
|----------|------|
| 顶部运营轮播图 | 展示运营活动点击跳转H5或站内页面 |
| 主Tab星卡/吧唧/海报) | 三大创作分类,点击直接进入铸造页面 |
| 分类标签 | 筛选用户已铸造的藏品(热门/最新/各分类) |
| 创作网格列表 | 双列瀑布流展示藏品卡片 |
| 铸造页面 | 选择素材 → 输入描述词 → AI生成 |
| AI处理中页面 | 展示创作进度 |
| AI创作结果预览 | 预览结果 → 重新生成/确认铸造 |
| 创作详情页 | 展示藏品信息、用户名、点赞、身份证编号 |
| 身份证编号(上链) | 每份藏品全球唯一编号,用于确权追溯 |
### 1.2 现有系统能力复用
| 现有服务 | 可复用能力 |
|----------|-----------|
| AssetService | 数字藏品铸造、资产查询、OSS存储、上链 |
| ActivityService | 运营活动管理(轮播图配置) |
| GalleryService | 展馆展示(可参考布局) |
| UserService | 用户信息、身份验证 |
| SocialService | 点赞功能 |
---
## 2. 技术架构设计
### 2.1 服务职责划分
```
┌─────────────────────────────────────────────────────────────┐
│ API Gateway (8080) │
│ 统一入口 / 认证 / 协议转换 / 请求路由 │
└─────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ AssetService │ │ ActivityService │ │ SocialService │
│ (tri:20003) │ │ (tri:20005?) │ │ (tri:20001) │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ 铸造订单管理 │ │ 运营活动配置 │ │ 藏品点赞 │
│ 资产CRUD │ │ 轮播图管理 │ │ 评论(扩展) │
│ AI任务调度 │ │ 活动跳转配置 │ │ │
│ 上链状态 │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### 2.2 新增/扩展接口
| 接口 | 方法 | 归属服务 | 说明 | 状态 |
|------|------|----------|------|------|
| `GET /api/v1/activities/banners` | GET | ActivityService | 获取轮播图列表 | **新增** |
| `GET /api/v1/assets/cast/items` | GET | AssetService | 获取铸爱创作列表(按分类筛选) | **新增** |
| `GET /api/v1/assets/cast/options` | GET | AssetService | 获取铸爱类型选项(吧唧装饰/海报风格) | **新增** |
| `GET /api/v1/assets/:asset_id` | GET | AssetService | 获取藏品详情(**复用现有接口** | 已存在 |
| `POST /api/v1/social/assets/:asset_id/like` | POST | SocialService | 藏品点赞(**复用现有接口** | 已存在 |
| `POST /api/v1/assets/cast/mints` | POST | AssetService | 创建铸爱铸造订单 | **新增** |
| `GET /api/v1/assets/cast/mints/:order_id` | GET | AssetService | 查询铸造订单状态 | **新增** |
**接口复用说明:**
- `GET /api/v1/assets/:asset_id` - 现有接口,可查询藏品详情
- `POST /api/v1/social/assets/:asset_id/like` - 现有接口,可用于点赞
---
## 3. 数据模型设计
### 3.1 铸爱藏品表(新建)
**表名:** `cast_assets`
```sql
CREATE TABLE IF NOT EXISTS cast_assets (
id BIGSERIAL PRIMARY KEY,
owner_uid BIGINT NOT NULL COMMENT '所有者ID',
star_id BIGINT NOT NULL COMMENT '明星ID用于数据隔离',
name VARCHAR(255) NOT NULL COMMENT '藏品名称',
material_url TEXT COMMENT '原始素材URL用户相册图片',
cover_url TEXT COMMENT 'AI生成结果图URL',
description TEXT COMMENT '藏品描述',
cast_type INT NOT NULL DEFAULT 1 COMMENT '藏品类型1=星卡, 2=吧唧, 3=海报',
identity_no VARCHAR(50) UNIQUE COMMENT '身份证编号(全局唯一)',
tx_hash VARCHAR(100) COMMENT '上链交易哈希',
block_number BIGINT COMMENT '区块高度',
status INT NOT NULL DEFAULT 0 COMMENT '状态0=处理中, 1=成功, 2=失败',
like_count INT NOT NULL DEFAULT 0 COMMENT '点赞数',
user_id BIGINT NOT NULL COMMENT '创作者ID',
minted_at BIGINT COMMENT '上链时间(毫秒)',
created_at BIGINT NOT NULL DEFAULT 0,
updated_at BIGINT NOT NULL DEFAULT 0,
-- 索引
INDEX idx_cast_assets_star_id (star_id),
INDEX idx_cast_assets_owner_uid_star_id (owner_uid, star_id),
INDEX idx_cast_assets_cast_type (cast_type),
INDEX idx_cast_assets_identity_no (identity_no),
INDEX idx_cast_assets_user_id (user_id),
INDEX idx_cast_assets_status (status),
INDEX idx_cast_assets_like_count (like_count),
INDEX idx_cast_assets_created_at (created_at)
) COMMENT '铸爱藏品表';
```
### 3.2 铸爱铸造订单表(新建)
**表名:** `cast_mint_orders`
```sql
CREATE TABLE IF NOT EXISTS cast_mint_orders (
order_id VARCHAR(50) PRIMARY KEY COMMENT '订单IDUUID',
user_id BIGINT NOT NULL COMMENT '用户ID',
star_id BIGINT NOT NULL COMMENT '明星ID',
asset_id BIGINT NOT NULL COMMENT '关联藏品ID',
cast_type INT NOT NULL COMMENT '藏品类型1=星卡, 2=吧唧, 3=海报',
prompt_text TEXT COMMENT '用户输入的描述词',
material_url TEXT COMMENT '原始素材URL',
cover_url TEXT COMMENT 'AI生成结果URL完成后回填',
identity_no VARCHAR(50) COMMENT '身份证编号',
tx_hash VARCHAR(100) COMMENT '上链哈希',
status VARCHAR(20) NOT NULL DEFAULT 'PROCESSING' COMMENT 'PROCESSING/SUCCESS/FAILED',
error_message TEXT COMMENT '失败原因',
cost_crystal BIGINT DEFAULT 0 COMMENT '消耗水晶',
retry_count INT DEFAULT 0 COMMENT '重试次数',
-- 吧唧专属参数
decoration_type VARCHAR(50) COMMENT '吧唧装饰类型cream_glue/metal_frame/shaped',
-- 海报专属参数
aspect_ratio VARCHAR(20) COMMENT '海报比例landscape/portrait/square',
style_options TEXT COMMENT '海报风格选项JSON数组bg_replace/style_transfer/lighting',
created_at BIGINT NOT NULL DEFAULT 0,
updated_at BIGINT NOT NULL DEFAULT 0,
minted_at BIGINT COMMENT '完成时间',
-- 索引
INDEX idx_cast_mint_orders_user_star (user_id, star_id),
INDEX idx_cast_mint_orders_status (status),
INDEX idx_cast_mint_orders_asset_id (asset_id),
INDEX idx_cast_mint_orders_created_at (created_at)
) COMMENT '铸爱铸造订单表';
```
### 3.3 铸爱点赞表(扩展现有 asset_likes
**扩展现有表,新增字段:**
```sql
ALTER TABLE asset_likes ADD COLUMN IF NOT EXISTS asset_type INT DEFAULT 1 COMMENT '资产类型1=原有资产, 2=铸爱藏品';
ALTER TABLE asset_likes ADD COLUMN IF NOT EXISTS cast_asset_id BIGINT COMMENT '铸爱藏品IDasset_type=2时使用';
-- 添加联合唯一索引,防止重复点赞
ALTER TABLE asset_likes ADD CONSTRAINT uk_asset_like UNIQUE (user_id, asset_id, asset_type);
```
### 3.4 铸爱类型选项配置表(新建)
**表名:** `cast_type_options`
**说明:** 运维后台上传和管理各类型的可选项吧唧装饰类型、海报风格选项等前端下发给用户选择同时传给AI生成。
```sql
CREATE TABLE IF NOT EXISTS cast_type_options (
id BIGSERIAL PRIMARY KEY,
cast_type INT NOT NULL COMMENT '藏品类型1=星卡, 2=吧唧, 3=海报',
option_key VARCHAR(50) NOT NULL COMMENT '选项标识,如 cream_glue / metal_frame / bg_replace',
option_name VARCHAR(100) NOT NULL COMMENT '选项显示名称,如 奶油胶 / 金属边框 / 背景替换',
option_type VARCHAR(30) NOT NULL COMMENT '选项类别decoration / style',
icon_url TEXT COMMENT '选项图标URL',
sort_order INT DEFAULT 0 COMMENT '排序',
status INT DEFAULT 1 COMMENT '状态0=禁用, 1=启用',
created_at BIGINT NOT NULL DEFAULT 0,
updated_at BIGINT NOT NULL DEFAULT 0,
-- 索引
INDEX idx_cast_type_options_type (cast_type),
INDEX idx_cast_type_options_key (cast_type, option_key),
UNIQUE KEY uk_cast_type_option (cast_type, option_key)
) COMMENT '铸爱类型选项配置表';
```
**示例数据:**
| cast_type | option_key | option_name | option_type |
|-----------|------------|-------------|-------------|
| 2 (吧唧) | cream_glue | 奶油胶 | decoration |
| 2 (吧唧) | metal_frame | 金属边框 | decoration |
| 2 (吧唧) | shaped | 异形裁切 | decoration |
| 3 (海报) | bg_replace | 背景替换 | style |
| 3 (海报) | style_transfer | 风格迁移 | style |
| 3 (海报) | lighting | 光影调整 | style |
**运维后台功能:**
- 新增/编辑/删除选项
- 上传选项图标
- 调整排序
---
## 4. 接口详细设计与代码实现
### 4.1 获取轮播图列表(新增接口)
**Endpoint** `GET /api/v1/activities/banners`
**归属服务:** ActivityService
#### 4.1.1 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| position | string | 否 | 位置标识,默认 "cast_home" |
#### 4.1.2 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"items": [
{
"id": 1,
"image_url": "https://xxx/banner1.jpg",
"title": "铸爱联名活动",
"description": "参与铸爱创作赢好礼",
"link_type": "h5",
"link_value": "https://xxx/activity"
}
]
}
}
```
#### 4.1.3 DTO 定义
```go
// BannerDTO 轮播图响应
type BannerDTO struct {
ID int64 `json:"id"`
ImageURL string `json:"image_url"`
Title string `json:"title"`
Description string `json:"description"`
LinkType string `json:"link_type"` // h5 / activity / internal
LinkValue string `json:"link_value"` // 跳转目标
}
// BannerListResponse 轮播图列表响应
type BannerListResponse struct {
Items []BannerDTO `json:"items"`
}
```
#### 4.1.4 Service 层实现
```go
// ActivityService/service/activity_service.go
// GetBanners 获取轮播图列表
func (s *ActivityService) GetBanners(ctx context.Context, position string) ([]*BannerDTO, error) {
// 默认位置
if position == "" {
position = "cast_home"
}
// 查询数据库
banners, err := s.bannerRepo.FindByPosition(ctx, position)
if err != nil {
return nil, err
}
// 转换为DTO
dtos := make([]*BannerDTO, 0, len(banners))
for _, banner := range banners {
dtos = append(dtos, &BannerDTO{
ID: banner.ID,
ImageURL: banner.ImageURL,
Title: banner.Title,
Description: banner.Description,
LinkType: banner.LinkType,
LinkValue: banner.LinkValue,
})
}
return dtos, nil
}
```
#### 4.1.5 Controller 层实现
```go
// Gateway/controller/activity_controller.go
// GetBanners 获取轮播图列表
// @Summary 获取轮播图列表
// @Tags activity
// @Param position query string false "位置标识"
// @Success 200 {object} BannerListResponse
// @Router /api/v1/activities/banners [get]
func (c *ActivityController) GetBanners(ctx *gin.Context) {
position := ctx.DefaultQuery("position", "cast_home")
banners, err := c.activityClient.GetBanners(ctx, position)
if err != nil {
response.Fail(ctx, 500, "获取轮播图失败")
return
}
response.Success(ctx, gin.H{
"items": banners,
})
}
```
---
### 4.2 获取铸爱创作列表(新增接口)
**Endpoint** `GET /api/v1/assets/cast/items`
**归属服务:** AssetService
#### 4.2.1 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| cast_type | int32 | 否 | 藏品类型1=星卡, 2=吧唧, 3=海报 |
| sort_by | string | 否 | 排序方式hot(热门) / latest(最新)默认hot |
| page | int32 | 否 | 页码默认1 |
| page_size | int32 | 否 | 每页数量默认20 |
#### 4.2.2 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"total": 100,
"page": 1,
"page_size": 20,
"items": [
{
"asset_id": 12345,
"name": "我的铸爱作品",
"cover_url": "https://xxx/cover.jpg",
"cover_url_signed": "https://xxx/cover.jpg?Expires=...&Signature=...",
"identity_no": "ZUA-20260409-00001",
"cast_type": 1,
"user_id": 10001,
"user_nickname": "用户昵称",
"user_avatar": "https://xxx/avatar.jpg",
"like_count": 999,
"is_liked": false,
"created_at": 1705747200000
}
]
}
}
```
#### 4.2.3 DTO 定义
```go
// CastAssetItemDTO 铸爱藏品列表项
type CastAssetItemDTO struct {
AssetID int64 `json:"asset_id"`
Name string `json:"name"`
CoverURL string `json:"cover_url"`
CoverURLSigned string `json:"cover_url_signed"`
IdentityNo string `json:"identity_no"`
CastType int32 `json:"cast_type"`
UserID int64 `json:"user_id"`
UserNickname string `json:"user_nickname"`
UserAvatar string `json:"user_avatar"`
LikeCount int32 `json:"like_count"`
IsLiked bool `json:"is_liked"`
CreatedAt int64 `json:"created_at"`
}
// CastAssetListResponse 铸爱藏品列表响应
type CastAssetListResponse struct {
Total int64 `json:"total"`
Page int32 `json:"page"`
PageSize int32 `json:"page_size"`
Items []CastAssetItemDTO `json:"items"`
}
```
#### 4.2.4 Service 层实现
```go
// AssetService/service/cast_service.go
// GetCastItems 获取铸爱创作列表
func (s *CastService) GetCastItems(ctx context.Context, req *GetCastItemsRequest) (*CastAssetListResponse, error) {
// 参数校验与默认值
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 || req.PageSize > 100 {
req.PageSize = 20
}
if req.SortBy == "" {
req.SortBy = "hot"
}
// 构建查询条件
query := &CastAssetQuery{
StarID: req.StarID,
CastType: req.CastType,
SortBy: req.SortBy,
Page: req.Page,
PageSize: req.PageSize,
Status: 1, // 只查询成功的
}
// 查询列表
assets, total, err := s.castRepo.FindList(ctx, query)
if err != nil {
return nil, err
}
// 获取当前用户已点赞的藏品ID列表
likedMap, err := s.getUserLikedMap(ctx, req.UserID, assets)
if err != nil {
return nil, err
}
// 转换为DTO
items := make([]CastAssetItemDTO, 0, len(assets))
for _, asset := range assets {
// 生成预签名URL
coverURLSigned, _ := s.generatePresignedURL(asset.CoverURL, 3600)
// 获取用户信息
user, _ := s.userClient.GetUserInfo(ctx, asset.UserID)
items = append(items, CastAssetItemDTO{
AssetID: asset.ID,
Name: asset.Name,
CoverURL: asset.CoverURL,
CoverURLSigned: coverURLSigned,
IdentityNo: asset.IdentityNo,
CastType: asset.CastType,
UserID: asset.UserID,
UserNickname: user.Nickname,
UserAvatar: user.Avatar,
LikeCount: asset.LikeCount,
IsLiked: likedMap[asset.ID],
CreatedAt: asset.CreatedAt,
})
}
return &CastAssetListResponse{
Total: total,
Page: req.Page,
PageSize: req.PageSize,
Items: items,
}, nil
}
// generatePresignedURL 生成预签名URL
func (s *CastService) generatePresignedURL(url string, expireSeconds int64) (string, error) {
if url == "" {
return "", nil
}
// 调用OSS生成预签名URL
return s.ossClient.GetPresignedURL(ctx, url, expireSeconds)
}
// getUserLikedMap 获取用户已点赞的藏品Map
func (s *CastService) getUserLikedMap(ctx context.Context, userID int64, assets []*CastAsset) (map[int64]bool, error) {
assetIDs := make([]int64, 0, len(assets))
for _, asset := range assets {
assetIDs = append(assetIDs, asset.ID)
}
likedAssetIDs, err := s.socialClient.GetUserLikedCastAssets(ctx, userID, assetIDs)
if err != nil {
return nil, err
}
likedMap := make(map[int64]bool)
for _, id := range likedAssetIDs {
likedMap[id] = true
}
return likedMap, nil
}
```
#### 4.2.5 Repository 层实现
```go
// AssetService/repository/cast_asset_repo.go
// FindList 查询铸爱藏品列表
func (r *CastAssetRepository) FindList(ctx context.Context, query *CastAssetQuery) ([]*CastAsset, int64, error) {
db := r.db.WithContext(ctx)
// 条件构建
if query.StarID > 0 {
db = db.Where("star_id = ?", query.StarID)
}
if query.CastType > 0 {
db = db.Where("cast_type = ?", query.CastType)
}
if query.Status > 0 {
db = db.Where("status = ?", query.Status)
}
// 排序
switch query.SortBy {
case "latest":
db = db.Order("created_at DESC")
case "hot":
db = db.Order("like_count DESC, created_at DESC")
default:
db = db.Order("like_count DESC, created_at DESC")
}
// 查询总数
var total int64
if err := db.Model(&CastAsset{}).Count(&total).Error; err != nil {
return nil, 0, err
}
// 分页
offset := (query.Page - 1) * query.PageSize
db = db.Offset(offset).Limit(query.PageSize)
// 执行查询
var assets []*CastAsset
if err := db.Find(&assets).Error; err != nil {
return nil, 0, err
}
return assets, total, nil
}
```
---
### 4.3 获取铸爱藏品详情(复用现有接口)
**Endpoint** `GET /api/v1/assets/:asset_id`
**归属服务:** AssetService
**接口说明:** 复用现有的 `GetAsset` 接口,无需新增。铸爱藏品使用 `cast_assets` 表,扩展 `AssetService.GetAsset` 方法支持查询铸爱藏品。
#### 4.3.1 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"asset_id": 12345,
"name": "我的铸爱作品",
"cover_url": "https://xxx/cover.jpg",
"cover_url_signed": "https://xxx/cover.jpg?Expires=...&Signature=...",
"identity_no": "ZUA-20260409-00001",
"identity_no_chain_url": "https://xxx/chain/query?no=ZUA-20260409-00001",
"cast_type": 1,
"description": "藏品描述",
"user_id": 10001,
"user_nickname": "用户昵称",
"user_avatar": "https://xxx/avatar.jpg",
"like_count": 999,
"is_liked": false,
"created_at": 1705747200000
}
}
```
#### 4.3.2 DTO 定义
```go
// CastAssetDetailDTO 铸爱藏品详情
type CastAssetDetailDTO struct {
AssetID int64 `json:"asset_id"`
Name string `json:"name"`
CoverURL string `json:"cover_url"`
CoverURLSigned string `json:"cover_url_signed"`
IdentityNo string `json:"identity_no"`
IdentityNoChainURL string `json:"identity_no_chain_url"`
CastType int32 `json:"cast_type"`
Description string `json:"description"`
UserID int64 `json:"user_id"`
UserNickname string `json:"user_nickname"`
UserAvatar string `json:"user_avatar"`
LikeCount int32 `json:"like_count"`
IsLiked bool `json:"is_liked"`
CreatedAt int64 `json:"created_at"`
}
```
#### 4.3.3 Service 层实现
```go
// GetCastAssetDetail 获取铸爱藏品详情
func (s *CastService) GetCastAssetDetail(ctx context.Context, assetID, userID int64) (*CastAssetDetailDTO, error) {
// 查询藏品
asset, err := s.castRepo.FindByID(ctx, assetID)
if err != nil {
return nil, err
}
if asset == nil {
return nil, errors.New("藏品不存在")
}
// 生成预签名URL
coverURLSigned, _ := s.generatePresignedURL(asset.CoverURL, 3600)
// 获取用户信息
user, _ := s.userClient.GetUserInfo(ctx, asset.UserID)
// 查询用户是否已点赞
isLiked, _ := s.socialClient.IsUserLikedCastAsset(ctx, userID, assetID)
// 链上查询URL预留
chainURL := s.buildChainQueryURL(asset.IdentityNo)
return &CastAssetDetailDTO{
AssetID: asset.ID,
Name: asset.Name,
CoverURL: asset.CoverURL,
CoverURLSigned: coverURLSigned,
IdentityNo: asset.IdentityNo,
IdentityNoChainURL: chainURL,
CastType: asset.CastType,
Description: asset.Description,
UserID: asset.UserID,
UserNickname: user.Nickname,
UserAvatar: user.Avatar,
LikeCount: asset.LikeCount,
IsLiked: isLiked,
CreatedAt: asset.CreatedAt,
}, nil
}
// buildChainQueryURL 构建链上查询URL
func (s *CastService) buildChainQueryURL(identityNo string) string {
// 预留链上查询功能
return fmt.Sprintf("https://xxx/chain/query?no=%s", identityNo)
}
```
---
### 4.4 创建铸爱铸造订单(新增接口)
**Endpoint** `POST /api/v1/assets/cast/mints`
**归属服务:** AssetService
**页面说明:**
- 用户点击主Tab星卡/吧唧/海报)后进入对应类型的铸造页面
- 铸造页面是同一个页面,通过 `cast_type` 参数区分类型
-铸造流程:选择素材 → 输入描述词 → 发送 → AI处理中 → 结果预览
**三种铸造类型:**
| cast_type | 类型 | 素材形态 | 尺寸规格 | 创作特点 | 专属参数 |
|-----------|------|----------|----------|----------|----------|
| 1 | 星卡 | 卡片形态,类似明星小卡 | 1:1 或 3:4 | 适合人物特写、半身照 | 无 |
| 2 | 吧唧 | 徽章/贴纸形态 | 圆形或异形 | 适合可爱风格、二次元、奶油胶/金属边框/异形裁切 | decoration_type |
| 3 | 海报 | 大尺寸图片 | 16:9、4:3 或 2:3 | 适合全身照、场景图、背景替换/风格迁移/光影调整 | aspect_ratio, style_options |
#### 4.4.1 请求格式
```json
{
"name": "我的铸爱作品",
"material_url": "https://xxx/material.jpg",
"description": "藏品描述",
"cast_type": 1,
"prompt_text": "请生成一张温暖风格的图片",
"decoration_type": "",
"aspect_ratio": "",
"style_options": []
}
```
**请求参数说明:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 藏品名称 |
| material_url | string | 是 | 用户选择的相册图片URL |
| description | string | 否 | 藏品描述 |
| cast_type | int32 | 是 | 藏品类型1=星卡, 2=吧唧, 3=海报 |
| prompt_text | string | 是 | AI描述词 |
| decoration_type | string | 否 | **吧唧专属**:装饰类型,记录用户选择,从 `/cast/options` 获取,如 cream_glue/metal_frame/shaped可组合逗号分隔 |
| aspect_ratio | string | 否 | **海报专属**比例landscape(横向16:9) / portrait(竖向2:3) / square(方形4:3) |
| style_options | string[] | 否 | **海报专属**:风格选项,记录用户选择,从 `/cast/options` 获取,如 bg_replace/style_transfer/lighting可多选 |
**参数说明:**
- `decoration_type``style_options` 仅记录用户选择,**不参与AI生成**AI会根据cast_type自动生成对应效果
- `aspect_ratio` 为海报比例参数AI生成时使用
**不同类型的参数组合:**
| cast_type | 必须参数 | 可选参数 |
|-----------|----------|----------|
| 1 (星卡) | name, material_url, cast_type, prompt_text | description |
| 2 (吧唧) | name, material_url, cast_type, prompt_text | description, decoration_type |
| 3 (海报) | name, material_url, cast_type, prompt_text | description, aspect_ratio, style_options |
#### 4.4.2 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"asset_id": 12345,
"status": "PROCESSING",
"identity_no": "ZUA-20260409-00002",
"created_at": 1705747200000
}
}
```
#### 4.4.3 DTO 定义
```go
// CreateCastMintRequest 创建铸造订单请求
type CreateCastMintRequest struct {
Name string `json:"name" binding:"required"`
MaterialURL string `json:"material_url" binding:"required,url"`
Description string `json:"description"`
CastType int32 `json:"cast_type" binding:"required,oneof=1 2 3"`
PromptText string `json:"prompt_text" binding:"required"`
// 吧唧专属参数
DecorationType string `json:"decoration_type"` // cream_glue, metal_frame, shaped
// 海报专属参数
AspectRatio string `json:"aspect_ratio"` // landscape, portrait, square
StyleOptions []string `json:"style_options"` // bg_replace, style_transfer, lighting
}
// CreateCastMintResponse 创建铸造订单响应
type CreateCastMintResponse struct {
OrderID string `json:"order_id"`
AssetID int64 `json:"asset_id"`
Status string `json:"status"`
IdentityNo string `json:"identity_no"`
CreatedAt int64 `json:"created_at"`
}
```
#### 4.4.4 Service 层实现(核心流程)
```go
// CreateCastMint 创建铸爱铸造订单
func (s *CastService) CreateCastMint(ctx context.Context, userID, starID int64, req *CreateCastMintRequest) (*CreateCastMintResponse, error) {
// 1. 参数校验
if req.Name == "" || req.MaterialURL == "" || req.PromptText == "" {
return nil, errors.New("参数不完整")
}
if req.CastType < 1 || req.CastType > 3 {
return nil, errors.New("无效的藏品类型")
}
// 2. 生成唯一订单ID
orderID := uuid.New().String()
// 3. 生成唯一身份证编号
identityNo, err := s.generateIdentityNo(ctx)
if err != nil {
return nil, fmt.Errorf("生成身份证编号失败: %w", err)
}
// 4. 创建藏品记录(初始状态:处理中)
asset := &CastAsset{
OwnerUID: userID,
StarID: starID,
Name: req.Name,
MaterialURL: req.MaterialURL,
Description: req.Description,
CastType: req.CastType,
IdentityNo: identityNo,
Status: 0, // 处理中
UserID: userID,
CreatedAt: time.Now().UnixMilli(),
UpdatedAt: time.Now().UnixMilli(),
}
if err := s.castRepo.Create(ctx, asset); err != nil {
return nil, fmt.Errorf("创建藏品记录失败: %w", err)
}
// 5. 创建订单记录
order := &CastMintOrder{
OrderID: orderID,
UserID: userID,
StarID: starID,
AssetID: asset.ID,
CastType: req.CastType,
PromptText: req.PromptText,
MaterialURL: req.MaterialURL,
IdentityNo: identityNo,
Status: "PROCESSING",
DecorationType: req.DecorationType, // 吧唧专属
AspectRatio: req.AspectRatio, // 海报专属
StyleOptions: req.StyleOptions, // 海报专属
CreatedAt: time.Now().UnixMilli(),
UpdatedAt: time.Now().UnixMilli(),
}
if err := s.mintOrderRepo.Create(ctx, order); err != nil {
return nil, fmt.Errorf("创建订单记录失败: %w", err)
}
// 6. 启动异步AI生成任务decoration_type/style_options仅记录不传给AI
go s.processCastAIGeneration(orderID, asset.ID, req.CastType, req.PromptText, req.MaterialURL, req.AspectRatio)
// 7. 返回响应
return &CreateCastMintResponse{
OrderID: orderID,
AssetID: asset.ID,
Status: "PROCESSING",
IdentityNo: identityNo,
CreatedAt: asset.CreatedAt,
}, nil
}
```
#### 4.4.5 异步AI生成任务核心代码
```go
// processCastAIGeneration 异步处理AI生成
func (s *CastService) processCastAIGeneration(orderID string, assetID int64, castType int32, promptText, materialURL, aspectRatio string) {
// 创建独立的context避免请求超时
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
logger.Info("开始处理铸爱AI生成",
zap.String("order_id", orderID),
zap.Int64("asset_id", assetID),
zap.Int32("cast_type", castType),
zap.String("aspect_ratio", aspectRatio),
zap.String("prompt", promptText),
)
// 1. 构建AI请求参数
aiReq := &AIGenerateRequest{
Model: "minimax",
InputImage: materialURL,
Prompt: promptText,
AspectRatio: aspectRatio,
}
// 2. 根据cast_type设置AI生成参数
// decoration_type和style_options仅记录到订单不参与AI生成
switch castType {
case 1: // 星卡
aiReq.AspectRatio = "1:1"
case 2: // 吧唧
// AI根据cast_type自动生成吧唧装饰效果
case 3: // 海报
// AI根据aspectRatio确定输出图片的尺寸比例
}
// 3. 调用MiniMax AI服务进行图生图
aiResult, err := s.aiClient.GenerateImage(ctx, aiReq)
if err != nil {
s.handleAIGenerationFailed(ctx, orderID, assetID, "AI服务调用失败: "+err.Error())
return
}
// 5. 下载AI生成的图片
generatedImage, err := s.downloadImage(ctx, aiResult.ImageURL)
if err != nil {
s.handleAIGenerationFailed(ctx, orderID, assetID, "下载AI图片失败: "+err.Error())
return
}
// 6. 上传到OSS
coverURL, err := s.uploadToOSS(ctx, generatedImage, assetID)
if err != nil {
s.handleAIGenerationFailed(ctx, orderID, assetID, "上传OSS失败: "+err.Error())
return
}
// 7. 生成模拟上链信息
txHash := s.generateMockTxHash()
blockNumber := s.generateMockBlockNumber()
mintedAt := time.Now().UnixMilli()
// 8. 更新藏品状态
if err := s.updateAssetSuccess(ctx, assetID, coverURL, txHash, blockNumber, mintedAt); err != nil {
s.handleAIGenerationFailed(ctx, orderID, assetID, "更新藏品状态失败: "+err.Error())
return
}
// 9. 更新订单状态
if err := s.updateOrderSuccess(ctx, orderID, coverURL, txHash); err != nil {
logger.Error("更新订单状态失败", zap.String("order_id", orderID), zap.Error(err))
return
}
logger.Info("铸爱AI生成完成",
zap.String("order_id", orderID),
zap.Int64("asset_id", assetID),
zap.String("cover_url", coverURL),
zap.String("tx_hash", txHash),
)
}
// handleAIGenerationFailed 处理AI生成失败
func (s *CastService) handleAIGenerationFailed(ctx context.Context, orderID string, assetID int64, errorMsg string) {
logger.Error("铸爱AI生成失败",
zap.String("order_id", orderID),
zap.Int64("asset_id", assetID),
zap.String("error", errorMsg),
)
// 1. 更新订单状态为失败
order, _ := s.mintOrderRepo.FindByOrderID(ctx, orderID)
if order != nil {
order.Status = "FAILED"
order.ErrorMessage = errorMsg
order.UpdatedAt = time.Now().UnixMilli()
s.mintOrderRepo.Update(ctx, order)
// 2. 退回水晶费用
if order.CostCrystal > 0 {
s.refundCrystalBalance(ctx, order.UserID, order.StarID, order.CostCrystal)
}
}
// 3. 更新藏品状态
s.castRepo.UpdateStatus(ctx, assetID, 2) // 2=失败
}
```
#### 4.4.6 身份证编号生成
```go
// generateIdentityNo 生成唯一身份证编号
// 格式: ZUA-YYYYMMDD-XXXXX
func (s *CastService) generateIdentityNo(ctx context.Context) (string, error) {
// 获取当前日期
dateStr := time.Now().Format("20060102")
// 使用Redis INCR获取当日序号按日期key
key := fmt.Sprintf("cast:identity:seq:%s", dateStr)
seq, err := s.redisClient.Incr(ctx, key)
if err != nil {
return "", err
}
// 设置过期时间(次日凌晨)
s.redisClient.ExpireAt(ctx, key, s.getNextDayMidnight())
// 格式化为5位序号
seqStr := fmt.Sprintf("%05d", seq)
return fmt.Sprintf("ZUA-%s-%s", dateStr, seqStr), nil
}
// getNextDayMidnight 获取次日凌晨时间
func (s *CastService) getNextDayMidnight() time.Time {
tomorrow := time.Now().AddDate(0, 0, 1)
return time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 0, 0, 0, 0, tomorrow.Location())
}
```
---
### 4.5 查询铸造订单状态(新增接口)
**Endpoint** `GET /api/v1/assets/cast/mints/:order_id`
**归属服务:** AssetService
#### 4.5.1 响应格式
```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,
"identity_no": "ZUA-20260409-00002",
"tx_hash": null,
"error_message": null,
"created_at": 1705747200000,
"updated_at": 1705747200000
}
}
```
#### 4.5.2 DTO 定义
```go
// MintOrderStatusDTO 铸造订单状态
type MintOrderStatusDTO struct {
OrderID string `json:"order_id"`
AssetID int64 `json:"asset_id"`
Status string `json:"status"` // PROCESSING / SUCCESS / FAILED
CoverURL string `json:"cover_url"`
CoverURLSigned string `json:"cover_url_signed"`
IdentityNo string `json:"identity_no"`
TxHash string `json:"tx_hash"`
ErrorMessage string `json:"error_message,omitempty"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
```
#### 4.5.3 Service 层实现
```go
// GetMintOrderStatus 查询铸造订单状态
func (s *CastService) GetMintOrderStatus(ctx context.Context, orderID string) (*MintOrderStatusDTO, error) {
// 查询订单
order, err := s.mintOrderRepo.FindByOrderID(ctx, orderID)
if err != nil {
return nil, err
}
if order == nil {
return nil, errors.New("订单不存在")
}
dto := &MintOrderStatusDTO{
OrderID: order.OrderID,
AssetID: order.AssetID,
Status: order.Status,
IdentityNo: order.IdentityNo,
ErrorMessage: order.ErrorMessage,
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
}
// 如果成功填充图片URL
if order.Status == "SUCCESS" && order.CoverURL != "" {
dto.CoverURL = order.CoverURL
dto.CoverURLSigned, _ = s.generatePresignedURL(order.CoverURL, 3600)
dto.TxHash = order.TxHash
}
return dto, nil
}
```
---
### 4.6 铸爱藏品点赞(复用现有接口)
**Endpoint** `POST /api/v1/social/assets/:asset_id/like`
**归属服务:** SocialService
**接口说明:** 复用现有的 `LikeAsset` 接口。扩展 `asset_likes` 表的 `asset_type` 字段,支持区分原有资产(1)和铸爱藏品(2)。
#### 4.6.1 请求格式
```json
{
"asset_id": 12345
}
```
#### 4.6.2 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"asset_id": 12345,
"is_liked": true,
"like_count": 1000
}
}
```
#### 4.6.3 DTO 定义
```go
// LikeCastAssetRequest 点赞请求
type LikeCastAssetRequest struct {
AssetID int64 `json:"asset_id" binding:"required"`
}
// LikeCastAssetResponse 点赞响应
type LikeCastAssetResponse struct {
AssetID int64 `json:"asset_id"`
IsLiked bool `json:"is_liked"`
LikeCount int32 `json:"like_count"`
}
```
#### 4.6.4 Service 层实现
```go
// LikeCastAsset 铸爱藏品点赞/取消点赞
func (s *SocialService) LikeCastAsset(ctx context.Context, userID, assetID int64) (*LikeCastAssetResponse, error) {
// 查询是否已点赞
existing, err := s.assetLikeRepo.FindByUserAndAsset(ctx, userID, assetID, 2) // asset_type=2
if err != nil {
return nil, err
}
var isLiked bool
if existing != nil {
// 取消点赞
if err := s.assetLikeRepo.Delete(ctx, existing.ID); err != nil {
return nil, err
}
// 减少点赞数
s.castAssetRepo.DecrLikeCount(ctx, assetID)
isLiked = false
} else {
// 添加点赞
like := &AssetLike{
UserID: userID,
AssetID: assetID,
AssetType: 2, // 铸爱藏品
CastAssetID: assetID,
CreatedAt: time.Now().UnixMilli(),
}
if err := s.assetLikeRepo.Create(ctx, like); err != nil {
return nil, err
}
// 增加点赞数
s.castAssetRepo.IncrLikeCount(ctx, assetID)
isLiked = true
}
// 获取最新点赞数
likeCount, _ := s.castAssetRepo.GetLikeCount(ctx, assetID)
return &LikeCastAssetResponse{
AssetID: assetID,
IsLiked: isLiked,
LikeCount: likeCount,
}, nil
}
```
---
### 4.7 获取铸爱类型选项(新增接口)
**Endpoint** `GET /api/v1/assets/cast/options`
**归属服务:** AssetService
**接口说明:** 获取各类型的可选项(吧唧装饰类型、海报风格选项等),供前端下发给用户选择。
#### 4.7.1 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| cast_type | int32 | 是 | 藏品类型1=星卡, 2=吧唧, 3=海报 |
#### 4.7.2 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"cast_type": 2,
"options": [
{
"key": "cream_glue",
"name": "奶油胶",
"type": "decoration",
"icon_url": "https://xxx/icons/cream_glue.png"
},
{
"key": "metal_frame",
"name": "金属边框",
"type": "decoration",
"icon_url": "https://xxx/icons/metal_frame.png"
},
{
"key": "shaped",
"name": "异形裁切",
"type": "decoration",
"icon_url": "https://xxx/icons/shaped.png"
}
]
}
}
```
#### 4.7.3 DTO 定义
```go
// CastTypeOptionDTO 铸爱类型选项
type CastTypeOptionDTO struct {
Key string `json:"key"` // 选项标识
Name string `json:"name"` // 显示名称
Type string `json:"type"` // decoration / style
IconURL string `json:"icon_url"` // 图标
}
// CastTypeOptionsResponse 铸爱类型选项响应
type CastTypeOptionsResponse struct {
CastType int32 `json:"cast_type"`
Options []CastTypeOptionDTO `json:"options"`
}
```
#### 4.7.4 Service 层实现
```go
// GetCastTypeOptions 获取铸爱类型选项
func (s *CastService) GetCastTypeOptions(ctx context.Context, castType int32) (*CastTypeOptionsResponse, error) {
// 查询启用的选项
options, err := s.castTypeOptionRepo.FindByCastType(ctx, castType, 1)
if err != nil {
return nil, err
}
// 转换为DTO
dtos := make([]CastTypeOptionDTO, 0, len(options))
for _, opt := range options {
dtos = append(dtos, CastTypeOptionDTO{
Key: opt.OptionKey,
Name: opt.OptionName,
Type: opt.OptionType,
IconURL: opt.IconURL,
})
}
return &CastTypeOptionsResponse{
CastType: castType,
Options: dtos,
}, nil
}
```
#### 4.7.5 运维后台接口(新增)
**Endpoint** `GET /api/v1/admin/cast/options`
| 接口 | 方法 | 说明 |
|------|------|------|
| `GET /api/v1/admin/cast/options` | GET | 获取选项列表支持筛选cast_type |
| `POST /api/v1/admin/cast/options` | POST | 新增选项 |
| `PUT /api/v1/admin/cast/options/:id` | PUT | 更新选项 |
| `DELETE /api/v1/admin/cast/options/:id` | DELETE | 删除选项 |
---
## 5. AI 服务对接
### 5.1 MiniMax AI 服务对接
#### 5.1.1 AI 客户端接口
```go
// AI客户端接口
type AIClient interface {
GenerateImage(ctx context.Context, req *AIGenerateRequest) (*AIGenerateResponse, error)
}
// AIGenerateRequest AI生成请求
type AIGenerateRequest struct {
Model string // 模型标识: minimax / openai / sd
InputImage string // 输入图片URL
Prompt string // 描述词
AspectRatio string // 输出尺寸比例1:1(星卡) / landscape(海报16:9) / portrait(海报2:3) / square(海报4:3)
}
// AIGenerateResponse AI生成响应
type AIGenerateResponse struct {
ImageURL string // 生成的图片URL
RequestID string // 请求ID
}
```
#### 5.1.2 MiniMax 实现
```go
// MiniMaxAIClient MiniMax AI客户端
type MiniMaxAIClient struct {
apiKey string
apiURL string
httpClient *http.Client
}
// GenerateImage 调用MiniMax图生图API
func (c *MiniMaxAIClient) GenerateImage(ctx context.Context, req *AIGenerateRequest) (*AIGenerateResponse, error) {
// 构造请求
payload := map[string]interface{}{
"model": "minimax-vl-01",
"image_url": req.InputImage,
"prompt": req.Prompt,
"num_images": 1,
"aspect_ratio": req.AspectRatio, // 1:1(星卡) / landscape(海报16:9) / portrait(海报2:3) / square(海报4:3)
}
httpReq, _ := http.NewRequestWithContext(ctx, "POST", c.apiURL+"/v1/image_generation", bytes.NewReader(mustMarshal(payload)))
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+c.apiKey)
// 发送请求
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("MiniMax API error: %d - %s", resp.StatusCode, string(body))
}
// 解析响应
var result struct {
Data []struct {
ImageURL string `json:"image_url"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if len(result.Data) == 0 {
return nil, errors.New("AI返回数据为空")
}
return &AIGenerateResponse{
ImageURL: result.Data[0].ImageURL,
RequestID: resp.Header.Get("X-Request-ID"),
}, nil
}
```
### 5.2 多模型切换策略(后续扩展)
```go
// MultiModelAIClient 多模型AI客户端后续扩展
type MultiModelAIClient struct {
clients map[string]AIClient // 模型客户端Map
defaultModel string
}
// GenerateImage 根据模型生成图片
func (c *MultiModelAIClient) GenerateImage(ctx context.Context, req *AIGenerateRequest) (*AIGenerateResponse, error) {
model := req.Model
if model == "" {
model = c.defaultModel
}
client, ok := c.clients[model]
if !ok {
return nil, fmt.Errorf("不支持的AI模型: %s", model)
}
return client.GenerateImage(ctx, req)
}
```
---
## 6. 服务间调用关系
### 6.1 调用链
```
API Gateway
├── ActivityService (gRPC)
│ └── GetBanners(position) → BannerDTO[]
├── AssetService (gRPC)
│ ├── GetCastItems() → CastAssetListResponse
│ ├── GetCastAssetDetail() → CastAssetDetailDTO
│ ├── CreateCastMint() → CreateCastMintResponse
│ │ │
│ │ ├── UserService.UpdateCrystalBalance() 扣水晶
│ │ └── 启动异步任务 processCastAIGeneration()
│ └── GetMintOrderStatus() → MintOrderStatusDTO
└── SocialService (gRPC)
├── GetUserLikedCastAssets() → assetID[]
├── IsUserLikedCastAsset() → bool
└── LikeCastAsset() → LikeCastAssetResponse
```
### 6.2 gRPC Proto 定义
```protobuf
// AssetService Proto
service AssetService {
// 获取铸爱创作列表
rpc GetCastItems(GetCastItemsRequest) returns (CastAssetListResponse);
// 获取铸爱类型选项(吧唧装饰/海报风格)
rpc GetCastTypeOptions(GetCastTypeOptionsRequest) returns (CastTypeOptionsResponse);
// 获取铸爱藏品详情
rpc GetCastAssetDetail(GetCastAssetDetailRequest) returns (CastAssetDetailDTO);
// 创建铸爱铸造订单
rpc CreateCastMint(CreateCastMintRequest) returns (CreateCastMintResponse);
// 查询铸造订单状态
rpc GetMintOrderStatus(GetMintOrderStatusRequest) returns (MintOrderStatusDTO);
}
// SocialService Proto
service SocialService {
// 铸爱藏品点赞
rpc LikeCastAsset(LikeCastAssetRequest) returns (LikeCastAssetResponse);
}
```
---
## 7. 前端对接说明
### 7.1 页面路由
| 页面 | 路由 | 对应接口 |
|------|------|----------|
| 铸爱首页 | `/cast` | `GetBanners`, `GetCastItems` |
| 铸造页面 | `/cast/create/:type` | `CreateCastMintOrder`, `GetCastMintStatus` |
| AI处理中 | `/cast/processing/:order_id` | `GetMintOrderStatus`(轮询) |
| 结果预览 | `/cast/result/:order_id` | `GetMintOrderStatus` |
| 创作详情 | `/cast/detail/:asset_id` | `GetCastAssetDetail`, `LikeCastAsset` |
### 7.2 前端轮询策略
```javascript
// 铸造订单状态轮询
async function pollMintStatus(orderId) {
const maxAttempts = 60; // 最多60次
const interval = 3000; // 3秒
for (let i = 0; i < maxAttempts; i++) {
const resp = await api.getCastMintStatus(orderId);
const { status, cover_url, error_message } = resp.data;
if (status === 'SUCCESS') {
// 铸造成功,跳转结果页
router.push(`/cast/result/${orderId}`);
return;
}
if (status === 'FAILED') {
// 铸造失败,显示错误
showToast(error_message || '铸造失败');
return;
}
// 继续等待
await sleep(interval);
}
// 超时
showToast('铸造超时,请稍后重试');
}
```
---
## 8. 待确认事项
| 问题 | 说明 | 状态 |
|------|------|------|
| 铸造费用 | 铸爱创作是否需要消耗水晶?收费标准? | 待确认 |
| AI 模型 | 初期 MiniMax后续多模型切换 | 已确认 |
| 多模型切换 | 后续扩展时,模型切换策略(用户可选/后台配置)? | 待确认 |
| 评论功能 | 是否需要评论功能? | 待确认 |
| 分享功能 | 分享到外部平台的规则? | 待确认 |
---
## 9. 实现计划
### Phase 1基础设施1-2天
- [ ] 创建数据库表 `cast_assets`、`cast_mint_orders`
- [ ] 在 AssetService 中新增 Cast 相关方法
- [ ] 在 ActivityService 中新增 Banner 轮播接口
- [ ] 扩展 asset_likes 表支持 asset_type 字段
### Phase 2核心功能3-5天
- [x] 实现 `GET /api/v1/activities/banners`**新增**
- [x] 实现 `GET /api/v1/assets/cast/items`**新增**
- [ ] 实现 `GET /api/v1/assets/:asset_id`**复用现有接口**,扩展支持铸爱藏品查询
- [x] 实现 `POST /api/v1/assets/cast/mints`**新增**
- [x] 实现 `GET /api/v1/assets/cast/mints/:order_id`**新增**
- [x] 实现 AI 异步生成任务MiniMax 图生图)← **新增**
### Phase 3社交功能1-2天
- [ ] 实现 `POST /api/v1/social/assets/:asset_id/like`**复用现有接口**,扩展支持铸爱藏品点赞
### Phase 4联调测试2-3天
- [ ] 前端页面联调
- [ ] 全流程测试
- [ ] 性能测试
---
**接口状态汇总:**
| 接口 | 状态 | 说明 |
|------|------|------|
| `GET /api/v1/activities/banners` | 新增 | 轮播图列表 |
| `GET /api/v1/assets/cast/items` | 新增 | 铸爱创作列表 |
| `GET /api/v1/assets/cast/options` | 新增 | 铸爱类型选项 |
| `GET /api/v1/assets/:asset_id` | 复用 | 藏品详情(需扩展) |
| `POST /api/v1/assets/cast/mints` | 新增 | 创建铸造订单 |
| `GET /api/v1/assets/cast/mints/:order_id` | 新增 | 查询订单状态 |
| `POST /api/v1/social/assets/:asset_id/like` | 复用 | 点赞(需扩展) |
---
## 10. 参考文档
| 文档 | 说明 |
|------|------|
| `docs/资产铸造AI生成流程设计文档.md` | 现有AI铸造流程设计参考 |
| `PROJECT_SUMMARY.md` | 项目整体架构说明 |
| `docs/资产服务设计文档.md` | AssetService 设计说明 |
---
> **下一步行动:** 请确认以上设计决策特别是铸造费用和AI模型对接方案。