docs:ai搭子修改,热门模块新增,字段判断基础文档

This commit is contained in:
zerosaturation 2026-05-27 16:49:58 +08:00
parent b0b47e4608
commit 9b0c3ee3a9
5 changed files with 2820 additions and 1149 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
-- AI Chat Service 数据库迁移
-- 创建时间: 2026-05-27
-- 说明: AI Chat Service 所需的数据库表
-- =============================================
-- 1. ai_personas (人设表)
-- =============================================
CREATE TABLE IF NOT EXISTS ai_personas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id BIGINT NOT NULL,
name VARCHAR(64) NOT NULL,
description TEXT,
avatar_url VARCHAR(512),
talk_style VARCHAR(256),
system_prompt TEXT NOT NULL,
is_default BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 索引
CREATE INDEX IF NOT EXISTS idx_ai_personas_user_id ON ai_personas(user_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_personas_user_default ON ai_personas(user_id) WHERE is_default = TRUE;
-- =============================================
-- 2. ai_user_memories (长期记忆表)
-- =============================================
CREATE TABLE IF NOT EXISTS ai_user_memories (
id SERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
content TEXT NOT NULL,
keywords TEXT[],
weight INTEGER DEFAULT 50,
is_core BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 索引
CREATE INDEX IF NOT EXISTS idx_ai_user_memories_user_id ON ai_user_memories(user_id);
CREATE INDEX IF NOT EXISTS idx_ai_user_memories_keywords ON ai_user_memories USING GIN(keywords);
CREATE INDEX IF NOT EXISTS idx_ai_user_memories_weight ON ai_user_memories(weight DESC);
-- =============================================
-- 回滚语句 (如需回滚)
-- =============================================
-- DROP TABLE IF EXISTS ai_user_memories;
-- DROP TABLE IF EXISTS ai_personas;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,476 @@
# 热门推荐模块设计方案
## 一、需求概述
在广场页面顶部新增 4 个热门分类区块,每个分类显示 8 张高点赞作品,支持单个分类刷新和查看更多分页。
### 页面结构
```
┌─────────────────────────────────┐
│ BannerCarousel │
├─────────────────────────────────┤
│ 热门推荐 (8张图) │
│ [刷新] [查看更多 >] │
├─────────────────────────────────┤
│ 热门星卡 (8张图) │
│ [刷新] [查看更多 >] │
├─────────────────────────────────┤
│ 热门吧唧 (8张图) │
│ [刷新] [查看更多 >] │
├─────────────────────────────────┤
│ 热门海报 (8张图) │
│ [刷新] [查看更多 >] │
├─────────────────────────────────┤
│ (CreationGrid 组件 - 不变) │
│ [热门作品] [最新作品] [星卡] ... │
└─────────────────────────────────┘
```
### 分类配置
| 区块 | type值 | 说明 |
|-----|-------|------|
| 热门推荐 | `hot_recommend` | 混合所有类型,高点赞作品 |
| 热门星卡 | `hot_star_card` | 只展示星卡 |
| 热门吧唧 | `hot_badge` | 只展示吧唧 |
| 热门海报 | `hot_poster` | 只展示海报 |
---
## 二、接口设计
### 2.1 接口清单
| 接口 | 方法 | 参数 | 说明 |
|-----|------|------|------|
| `/api/v1/inspiration-flow/hot/batch` | GET | 无 | 页面加载批量获取 |
| `/api/v1/inspiration-flow/hot` | GET | `type` | 单个分类刷新 |
| `/api/v1/inspiration-flow/hot/more` | GET | `type`, `cursor`, `limit` | 查看更多分页 |
| `/api/v1/inspiration-flow` | GET | `type`, `cursor`, `limit`, `direction` | 灵感瀑布流(逻辑调整) |
### 2.2 调用时序
```
页面加载 → 批量请求1次
└── GET /api/v1/inspiration-flow/hot/batch
刷新 → 单个分类刷新(点击哪个刷新请求哪个)
└── GET /api/v1/inspiration-flow/hot?type=hot_star_card
查看更多 → 新页面分页
└── GET /api/v1/inspiration-flow/hot/more?type=hot_star_card&cursor=xxx&limit=20
```
### 2.3 批量获取热门分类
**接口**: `GET /api/v1/inspiration-flow/hot/batch`
**请求参数**: 无
**业务逻辑**:
1. 计算各分类作品的点赞平均值
2. 筛选点赞数 ≥ 平均值的作品
3. 基于时间窗口伪随机排序取 8 条
4. 后端动态返回分类数据,前端根据返回的分类动态渲染
5. **排序说明**:使用随机排序,同一分类每次请求返回的作品可能不同
**响应结构**:
```json
{
"code": 200,
"data": {
"categories": [
{
"type": "hot_recommend",
"title": "热门推荐",
"items": [
{
"asset_id": "xxx",
"cover_url": "https://xxx.jpg",
"owner_nickname": "用户名",
"owner_avatar": "https://xxx.jpg",
"likes": 1234
}
]
},
{
"type": "hot_star_card",
"title": "热门星卡",
"items": [...]
},
{
"type": "hot_badge",
"title": "热门吧唧",
"items": [...]
},
{
"type": "hot_poster",
"title": "热门海报",
"items": [...]
}
]
}
}
```
**空分类处理**: 如果某个分类没有作品或查询失败,该分类不返回。
### 2.4 单个分类刷新
**接口**: `GET /api/v1/inspiration-flow/hot`
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|-----|------|------|------|
| `type` | string | 是 | `hot_recommend` / `hot_star_card` / `hot_badge` / `hot_poster` |
**业务逻辑**:
1. 计算该分类作品的点赞平均值
2. 筛选点赞数 ≥ 平均值的作品
3. 基于时间窗口伪随机排序取 8 条
4. **排序说明**:批量和刷新接口使用随机排序,每次刷新可能看到不同作品
**响应结构**:
```json
{
"code": 200,
"data": {
"type": "hot_star_card",
"title": "热门星卡",
"items": [...]
}
}
```
### 2.5 查看更多分页
**接口**: `GET /api/v1/inspiration-flow/hot/more`
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|-----|------|------|------|
| `type` | string | 是 | 分类类型(`hot_recommend`/`hot_star_card`/`hot_badge`/`hot_poster` |
| `cursor` | string | 否 | 翻页游标Base64 编码的 JSON包含 `like_count``asset_id` |
| `limit` | int | 否 | 每页数量,默认 20 |
**业务逻辑**:
1. 计算该分类作品点赞平均值
2. 筛选点赞数 ≥ 平均值的作品
3. 按点赞数 DESC、asset_id DESC 排序
4. Cursor 分页返回(游标编码 last_like_count 和 last_asset_id
**Cursor 编码格式**:
```json
// Base64 编码 {"like_count": 1234, "asset_id": 5678}
```
**响应结构**:
```json
{
"code": 200,
"data": {
"items": [
{
"asset_id": "xxx",
"cover_url": "https://xxx.jpg",
"owner_nickname": "用户名",
"owner_avatar": "https://xxx.jpg",
"likes": 1234,
"material_type": "star_card",
"sub_type": "raster"
}
],
"cursor": "xxx",
"has_more": true
}
}
```
**注意**`material_type` 表示素材类型(`star_card`/`badge`/`poster`),不是热门分类类型。
### 2.6 灵感瀑布流(逻辑调整)
**接口**: `GET /api/v1/inspiration-flow`
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|-----|------|------|------|
| `type` | string | 否 | 素材类型过滤:`badge`/`poster`/`original`/`all`,默认 `all` |
| `cursor` | string | 否 | 翻页游标 |
| `direction` | string | 否 | 滚动方向:`right`(加载新数据)/ `left`(加载历史) |
| `limit` | int | 否 | 每页数量,默认 20 |
| `session_id` | string | 否 | 会话 ID用于去重排除已展示的作品 |
**业务逻辑(调整后)**:
1. 计算该分类作品的点赞平均值(基于展出中的作品 JOIN assets
2. **第一段**:点赞 > 平均值的作品,基于时间窗口伪随机排列,取前 20 条
3. **第二段**:如果不够 20 条,从点赞 ≥ 平均值的作品中(排除第一段已选的)补充,伪随机排列
4. 两段组合返回
5. 通过 `session_id` 排除已展示的作品(`excludeIDs`
**响应结构**:
```json
{
"code": 200,
"data": {
"items": [...],
"cursor": "xxx",
"has_more": true
}
}
```
**注意**:灵感瀑布的 `type` 参数与热门推荐接口的 `type` 参数含义完全不同:
- 灵感瀑布:`badge`/`poster`/`original`/`all`(素材类型过滤)
- 热门推荐:`hot_recommend`/`hot_star_card`/`hot_badge`/`hot_poster`(分类类型)
### 2.7 热门分类 type 与资产类型映射
| 热门分类 type | 对应 assetType | 说明 |
|-------------|---------------|------|
| `hot_recommend` | 空字符串 `""` | 混合所有类型,不过滤 |
| `hot_star_card` | `star_card` | 只展示星卡 |
| `hot_badge` | `badge` | 只展示吧唧 |
| `hot_poster` | `poster` | 只展示海报 |
---
## 三、数据模型
### 3.1 数据库表结构
参考现有 `assets` 表,无需新建表。
### 3.2 伪 SQL以 star_card 为例)
**说明**:热门作品基于**展出中的作品**筛选,只有在 `exhibitions` 表中有有效展出记录且未过期的作品才能参与热门排名。
```sql
-- 计算该分类作品的点赞平均值(基于展出中的作品)
AVG_LIKES := SELECT AVG(a.like_count)
FROM exhibitions e
JOIN assets a ON a.id = e.asset_id
WHERE e.occupier_star_id = :star_id
AND e.expire_at > :now
AND e.deleted_at IS NULL
AND a.status = 1 AND a.is_active = true
AND a.asset_type = 'star_card';
-- 随机取8条基于时间窗口的伪随机避免 ORDER BY RANDOM()
WINDOW_SEED := :now / 30000 % 10; -- 每30秒变化一次
SELECT e.id as exhibition_id, e.asset_id, a.name, a.cover_url, a.like_count,
fp.nickname as owner_nickname, fp.avatar_url as owner_avatar
FROM exhibitions e
JOIN assets a ON a.id = e.asset_id
JOIN fan_profiles fp ON e.occupier_uid = fp.user_id AND e.occupier_star_id = fp.star_id
WHERE e.occupier_star_id = :star_id
AND e.expire_at > :now
AND e.deleted_at IS NULL
AND a.status = 1 AND a.is_active = true
AND a.asset_type = 'star_card'
AND a.like_count >= :AVG_LIKES
AND e.id % 10 = :WINDOW_SEED
ORDER BY e.id
LIMIT 8;
```
### 3.3 随机算法说明
避免 `ORDER BY RANDOM()`(大表性能差),改用基于时间窗口的伪随机:
- `WINDOW_SEED = now / 30000 % 10` — 每 30 秒变化一次
- 通过 `exhibition_id % 10 = WINDOW_SEED` 筛选作品
- 同一时间窗口内结果固定,不同时间窗口返回不同作品
- 如果数量不够,再补充查询(不加窗口过滤)
---
## 四、后端实现
### 4.1 文件改动
| 文件 | 改动内容 |
|-----|---------|
| `backend/proto/gallery.proto` | 新增 Proto 消息定义 |
| `backend/pkg/proto/gallery/gallery.pb.go` | 重新生成 Proto 代码 |
| `backend/gateway/controller/gallery_controller.go` | 新增 `GetHotInspirationFlowBatch`、`GetHotInspirationFlow`、`GetHotInspirationFlowMore` 方法 |
| `backend/gateway/router/router.go` | 新增 `/inspiration-flow/hot/*` 路由注册 |
| `backend/gateway/dto/gallery_dto.go` | 新增 DTO 结构体 |
| `backend/gateway/dto/gallery_converter.go` | 新增 DTO 转换函数 |
| `backend/services/galleryService/service/gallery_service.go` | 新增 `GetHotInspirationFlowBatch`、`GetHotInspirationFlow`、`GetHotInspirationFlowMore` Service 方法 |
| `backend/services/galleryService/repository/gallery_repository.go` | 新增 Repository 方法 |
| `backend/pkg/database/redis.go` | 新增热门推荐缓存辅助函数 |
### 4.2 性能优化
1. **并行查询** — 批量接口用 goroutine 并行查 4 个分类
2. **缓存平均值** — 每个分类的点赞均值独立缓存TTL: 5 分钟),避免重复计算
3. **结果缓存** — 热门列表缓存 30-60 秒,降低数据库压力
4. **随机优化** — 避免 `ORDER BY RANDOM()`,使用基于时间窗口的伪随机
### 4.3 缓存策略
| 缓存项 | TTL | 说明 |
|-------|-----|------|
| 分类点赞均值 | 5 分钟 | 每个分类单独缓存,减少重复计算平均值 |
| 热门列表(批量) | 30 秒 | `/hot/batch` 结果缓存,降低数据库压力 |
| 热门列表(单个刷新) | **不缓存** | 用户主动刷新应获取新数据,只缓存点赞均值 |
---
## 五、前端实现
### 5.1 文件结构
```
frontend/pages/square/
├── square.vue # 修改集成4个热门分类区块
├── components/
│ └── HotCategoryBlock.vue # 新增:单个热门分类区块组件
└── hot-category-more.vue # 新增:热门分类查看更多页面
```
### 5.2 新增文件
| 文件 | 说明 |
|-----|------|
| `pages/square/components/HotCategoryBlock.vue` | 单个热门分类区块组件 |
| `pages/square/hot-category-more.vue` | 热门分类查看更多页面 |
### 5.3 修改文件
| 文件 | 改动内容 |
|-----|---------|
| `frontend/pages/square/square.vue` | 顶部新增 4 个 HotCategoryBlock |
| `frontend/utils/api.js` | 新增批量获取、单个刷新、查看更多 API |
### 5.4 HotCategoryBlock 组件
```
HotCategoryBlock.vue
├── props: { categoryType } // 传入后端返回的 type如 "hot_star_card"
├── 状态: items, loading, refreshing, title
├── 方法: handleRefresh(), handleViewMore()
└── 模板:
├── 标题 (title)
├── 4x2 网格 (items) + loading 骨架屏
├── 刷新按钮 (loading 状态)
└── 查看更多按钮
```
**说明**
- `categoryType` 直接使用后端返回的 type`hot_star_card`
- 刷新时调用 `getHotInspirationFlowApi(categoryType)`
- 查看更多时调用 `getHotInspirationFlowMoreApi(categoryType, ...)`
**刷新交互**:
- 整个区块显示骨架屏 loading
- 刷新按钮显示 loading 状态
- 刷新完成后正常显示
### 5.5 hot-category-more.vue 页面
```
hot-category-more.vue
├── onLoad: 获取 type 参数
├── 状态: items, cursor, loading, hasMore, title
├── 方法: loadMore(), loadData()
└── 模板:
├── 返回按钮 + 标题
└── 分页网格列表
```
**星卡子标签**(仅星卡分类显示):
| 子标签 | value | 说明 |
|-------|-------|------|
| 全部 | `all` | 全部星卡 |
| 光栅卡 | `raster` | 光栅卡类型 |
| 镭射卡 | `holographic` | 镭射卡类型 |
| 撕拉卡 | `tear_off` | 撕拉卡类型 |
| 拍立得 | `polaroid` | 拍立得类型 |
### 5.6 API 封装
```javascript
// 批量获取热门分类(页面加载用)
export function getHotInspirationFlowBatchApi() {
return request({
url: '/api/v1/inspiration-flow/hot/batch',
method: 'GET'
})
}
// 单个分类刷新
export function getHotInspirationFlowApi(type) {
return request({
url: '/api/v1/inspiration-flow/hot',
method: 'GET',
data: { type }
})
}
// 查看更多分页
export function getHotInspirationFlowMoreApi(type, cursor = '', limit = 20) {
return request({
url: '/api/v1/inspiration-flow/hot/more',
method: 'GET',
data: { type, cursor, limit }
})
}
```
**调用示例**
```javascript
// 批量加载(页面初始化)
const res = await getHotInspirationFlowBatchApi()
res.data.categories.forEach(cat => {
// cat.type: "hot_recommend", cat.title: "热门推荐", cat.items: [...]
console.log(cat.type, cat.items.length)
})
// 单个刷新(点击刷新按钮)
const refreshRes = await getHotInspirationFlowApi('hot_star_card')
// 查看更多(跳转 hot-category-more.vue
uni.navigateTo({
url: `/pages/square/hot-category-more?type=${type}&title=${encodeURIComponent(title)}`
})
```
---
## 六、实现步骤
### 后端
- [ ] **Phase 1**: 新增 `/api/v1/inspiration-flow/hot/batch` 批量接口(并行查询 + 缓存)
- [ ] **Phase 2**: 新增 `/api/v1/inspiration-flow/hot` 单个分类接口
- [ ] **Phase 3**: 新增 `/api/v1/inspiration-flow/hot/more` 查看更多接口
- [ ] **Phase 4**: 修改 `/api/v1/inspiration-flow` 逻辑(分段填充)
### 前端
- [ ] **Phase 5**: 新增 `utils/api.js` API 方法
- [ ] **Phase 6**: 新增 `HotCategoryBlock.vue` 组件
- [ ] **Phase 7**: 新增 `hot-category-more.vue` 页面
- [ ] **Phase 8**: 修改 `square.vue` 集成 4 个热门分类区块
- [ ] **Phase 9**: CreationGrid 保持不变
### 联调
- [ ] **Phase 10**: 前后端联调测试
---
## 七、注意事项
1. **后端动态返回分类** — 前端无需硬编码分类,根据接口返回的 `categories` 动态渲染
2. **空分类不显示** — 如果某个分类没有作品,该分类不返回或前端忽略
3. **扩展性** — 后续增加新分类(如"热门贴纸"),只需后端调整,前端无需改动
4. **性能优先** — 后端必须做好并行查询和缓存优化,确保批量接口响应时间 < 200ms