[Claude Workbench] Initial commit - preserving existing code
This commit is contained in:
684
广场功能实现方案.md
Normal file
684
广场功能实现方案.md
Normal file
@@ -0,0 +1,684 @@
|
||||
# 广场功能完整实现方案
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
用户可以将AI生成的作品发布到广场,其他用户可以浏览、点赞、按类型筛选查询作品。
|
||||
|
||||
### 核心功能
|
||||
1. **发布作品** - 用户将任务结果发布到广场
|
||||
2. **浏览广场** - 按类型、热度、时间查询作品
|
||||
3. **作品详情** - 查看单个作品详细信息
|
||||
4. **点赞/取消点赞** - 用户可以对作品点赞
|
||||
5. **统计数据** - 浏览量、点赞数、分享数统计
|
||||
6. **个人作品** - 查看自己发布的作品
|
||||
7. **删除作品** - 用户可以删除自己的作品
|
||||
|
||||
---
|
||||
|
||||
## 二、数据库设计
|
||||
|
||||
已在 `V10__add_plaza_feature.sql` 中定义了以下表:
|
||||
|
||||
### 1. plaza_work(广场作品表)
|
||||
```sql
|
||||
- id: 主键
|
||||
- work_no: 作品编号(唯一)
|
||||
- user_id: 发布者ID
|
||||
- task_no: 关联任务编号
|
||||
- task_type: 任务类型(text_to_image/image_to_video等)
|
||||
- model_name: 使用的模型
|
||||
- prompt: 生成提示词
|
||||
- result_url: 作品URL
|
||||
- image_url: 参考图URL(可选)
|
||||
- title: 作品标题
|
||||
- description: 作品描述
|
||||
- tags: 标签(JSON)
|
||||
- view_count, like_count, share_count: 统计数据
|
||||
- is_public: 是否公开
|
||||
- status: 状态(draft/published/hidden)
|
||||
- create_time, update_time, is_deleted
|
||||
```
|
||||
|
||||
### 2. plaza_work_like(点赞表)
|
||||
```sql
|
||||
- id: 主键
|
||||
- work_id: 作品ID
|
||||
- user_id: 点赞用户ID
|
||||
- create_time: 点赞时间
|
||||
- 唯一索引:(work_id, user_id)
|
||||
```
|
||||
|
||||
### 3. plaza_work_view(浏览记录表)
|
||||
```sql
|
||||
- id: 主键
|
||||
- work_id: 作品ID
|
||||
- user_id: 浏览用户ID(可空)
|
||||
- ip_address: IP地址
|
||||
- view_time: 浏览时间
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、API 接口设计
|
||||
|
||||
### 基础路径
|
||||
所有接口以 `/user/plaza` 开头
|
||||
|
||||
### 1. 发布作品
|
||||
```
|
||||
POST /user/plaza/works/publish
|
||||
```
|
||||
|
||||
**请求参数:**
|
||||
```json
|
||||
{
|
||||
"taskNo": "TASK-20251026183750127-8554",
|
||||
"title": "战场气氛短视频",
|
||||
"description": "根据参考图生成的恢弘战场场景",
|
||||
"tags": ["视频", "战争", "特效"],
|
||||
"isPublic": true
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"workNo": "WORK-20251026185630123-4567",
|
||||
"taskNo": "TASK-20251026183750127-8554",
|
||||
"taskType": "image_to_video",
|
||||
"modelName": "sc_sora2_img_landscape_15s_small",
|
||||
"prompt": "根据参考图展现出一个100秒左右的战场短视频...",
|
||||
"resultUrl": "https://oss-1818ai-user-img.oss-cn-hangzhou.aliyuncs.com/result.mp4",
|
||||
"imageUrl": "https://oss-1818ai-user-img.oss-cn-hangzhou.aliyuncs.com/ref.png",
|
||||
"title": "战场气氛短视频",
|
||||
"description": "根据参考图生成的恢弘战场场景",
|
||||
"tags": ["视频", "战争", "特效"],
|
||||
"viewCount": 0,
|
||||
"likeCount": 0,
|
||||
"isLiked": false,
|
||||
"createTime": "2025-10-26T18:56:30"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 查询广场作品列表
|
||||
```
|
||||
GET /user/plaza/works/list
|
||||
```
|
||||
|
||||
**请求参数:**
|
||||
- `page`: 页码(默认1)
|
||||
- `size`: 每页数量(默认20)
|
||||
- `taskType`: 任务类型筛选(可选:text_to_image/image_to_video等)
|
||||
- `sortBy`: 排序方式(可选:latest-最新/hot-最热,默认latest)
|
||||
|
||||
**示例:**
|
||||
```
|
||||
GET /user/plaza/works/list?page=1&size=20&taskType=text_to_image&sortBy=hot
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"total": 156,
|
||||
"list": [
|
||||
{
|
||||
"workNo": "WORK-20251026185630123-4567",
|
||||
"taskType": "text_to_image",
|
||||
"modelName": "sc_soraimg_text_1x1",
|
||||
"prompt": "一只可爱的橘猫在窗台晒太阳",
|
||||
"resultUrl": "https://oss-.../cat.png",
|
||||
"title": "窗台上的橘猫",
|
||||
"tags": ["猫咪", "温馨", "治愈"],
|
||||
"viewCount": 1245,
|
||||
"likeCount": 89,
|
||||
"isLiked": false,
|
||||
"author": {
|
||||
"userId": 17563793187762127,
|
||||
"nickname": "AI创作者",
|
||||
"avatarUrl": "https://oss-.../avatar.jpg"
|
||||
},
|
||||
"createTime": "2025-10-26T18:56:30"
|
||||
}
|
||||
// ... 更多作品
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 查询作品详情
|
||||
```
|
||||
GET /user/plaza/works/{workNo}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"workNo": "WORK-20251026185630123-4567",
|
||||
"taskType": "text_to_image",
|
||||
"modelName": "sc_soraimg_text_1x1",
|
||||
"prompt": "一只可爱的橘猫在窗台晒太阳,温馨的室内场景,柔和的光线",
|
||||
"resultUrl": "https://oss-.../cat.png",
|
||||
"imageUrl": null,
|
||||
"aspectRatio": "1:1",
|
||||
"title": "窗台上的橘猫",
|
||||
"description": "这是一个温馨治愈的作品,展现了橘猫慵懒晒太阳的画面。",
|
||||
"tags": ["猫咪", "温馨", "治愈"],
|
||||
"viewCount": 1246,
|
||||
"likeCount": 89,
|
||||
"shareCount": 12,
|
||||
"isLiked": false,
|
||||
"author": {
|
||||
"userId": 17563793187762127,
|
||||
"nickname": "AI创作者",
|
||||
"avatarUrl": "https://oss-.../avatar.jpg"
|
||||
},
|
||||
"createTime": "2025-10-26T18:56:30",
|
||||
"updateTime": "2025-10-26T19:15:42"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 点赞/取消点赞
|
||||
```
|
||||
POST /user/plaza/works/{workNo}/like
|
||||
DELETE /user/plaza/works/{workNo}/like
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"isLiked": true,
|
||||
"likeCount": 90
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 查询我的作品
|
||||
```
|
||||
GET /user/plaza/my-works
|
||||
```
|
||||
|
||||
**请求参数:**
|
||||
- `page`: 页码
|
||||
- `size`: 每页数量
|
||||
- `status`: 状态筛选(可选:published/draft/hidden)
|
||||
|
||||
**响应格式:** 同广场作品列表
|
||||
|
||||
---
|
||||
|
||||
### 6. 删除作品
|
||||
```
|
||||
DELETE /user/plaza/works/{workNo}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "删除成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. 统计数据
|
||||
```
|
||||
GET /user/plaza/stats
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"totalWorks": 5678,
|
||||
"totalViews": 123456,
|
||||
"totalLikes": 8901,
|
||||
"worksByType": {
|
||||
"text_to_image": 3456,
|
||||
"image_to_video": 1234,
|
||||
"text_to_video": 988
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、实现步骤
|
||||
|
||||
### 步骤1: 执行SQL脚本
|
||||
```bash
|
||||
mysql -u root -p 1818ai < V10__add_plaza_feature.sql
|
||||
```
|
||||
|
||||
### 步骤2: 创建实体类
|
||||
- ✅ `PlazaWork.java` - 已创建
|
||||
- ✅ `PlazaWorkLike.java` - 已创建
|
||||
|
||||
### 步骤3: 创建DTO类
|
||||
需要创建以下DTO:
|
||||
- `PlazaWorkDto.java` - 包含各种请求和响应DTO
|
||||
- `PublishWorkRequest` - 发布作品请求
|
||||
- `WorkQueryRequest` - 查询作品请求
|
||||
- `WorkDetailResponse` - 作品详情响应
|
||||
- `WorkListResponse` - 作品列表响应
|
||||
- `WorkAuthorDto` - 作者信息DTO
|
||||
|
||||
### 步骤4: 创建Mapper接口
|
||||
- `PlazaWorkMapper.java` - 作品数据访问
|
||||
- `PlazaWorkLikeMapper.java` - 点赞数据访问
|
||||
|
||||
### 步骤5: 创建Service层
|
||||
- `PlazaService.java` - 接口
|
||||
- `PlazaServiceImpl.java` - 实现
|
||||
|
||||
### 步骤6: 创建Controller
|
||||
- `PlazaController.java` - 用户端控制器
|
||||
|
||||
---
|
||||
|
||||
## 五、核心业务逻辑
|
||||
|
||||
### 1. 发布作品流程
|
||||
```
|
||||
1. 验证taskNo是否存在且属于当前用户
|
||||
2. 验证任务状态是否为completed
|
||||
3. 检查该任务是否已经发布过
|
||||
4. 生成唯一的workNo
|
||||
5. 从任务表复制数据到广场作品表
|
||||
6. 设置标题、描述、标签等
|
||||
7. 初始化统计数据(浏览、点赞等为0)
|
||||
8. 返回作品信息
|
||||
```
|
||||
|
||||
### 2. 查询列表流程
|
||||
```
|
||||
1. 构建查询条件(任务类型、公开性、状态)
|
||||
2. 根据sortBy排序(latest/hot)
|
||||
3. 分页查询
|
||||
4. 关联查询用户信息(昵称、头像)
|
||||
5. 判断当前用户是否点赞过(如果已登录)
|
||||
6. 返回作品列表
|
||||
```
|
||||
|
||||
### 3. 点赞流程
|
||||
```
|
||||
1. 检查作品是否存在
|
||||
2. 检查是否已经点赞
|
||||
3. 插入点赞记录
|
||||
4. 更新作品点赞数(+1)
|
||||
5. 返回最新点赞状态
|
||||
```
|
||||
|
||||
### 4. 浏览统计流程
|
||||
```
|
||||
1. 记录浏览日志(可选)
|
||||
2. 更新作品浏览数(+1)
|
||||
3. 防止频繁刷新(可加Redis缓存,同一用户1分钟内只计数一次)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、前端调用示例
|
||||
|
||||
### 1. 发布作品到广场
|
||||
```javascript
|
||||
// 假设用户刚完成了一个任务,想发布到广场
|
||||
async function publishToPlaza(taskNo) {
|
||||
const response = await fetch('http://localhost:8081/user/plaza/works/publish', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${YOUR_TOKEN}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
taskNo: taskNo,
|
||||
title: '我的AI创作',
|
||||
description: '这是我用AI生成的作品',
|
||||
tags: ['AI艺术', '创意'],
|
||||
isPublic: true
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log('发布成功:', result.data.workNo);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 浏览广场(按类型筛选)
|
||||
```javascript
|
||||
// 查询所有文生图作品
|
||||
async function browsePlaza() {
|
||||
const response = await fetch(
|
||||
'http://localhost:8081/user/plaza/works/list?page=1&size=20&taskType=text_to_image&sortBy=hot',
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${YOUR_TOKEN}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
console.log('广场作品:', result.data.list);
|
||||
|
||||
// 渲染作品网格
|
||||
renderWorksGrid(result.data.list);
|
||||
}
|
||||
|
||||
function renderWorksGrid(works) {
|
||||
works.forEach(work => {
|
||||
console.log(`
|
||||
作品:${work.title}
|
||||
作者:${work.author.nickname}
|
||||
点赞:${work.likeCount}
|
||||
浏览:${work.viewCount}
|
||||
图片:${work.resultUrl}
|
||||
`);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 查看作品详情
|
||||
```javascript
|
||||
async function viewWorkDetail(workNo) {
|
||||
const response = await fetch(
|
||||
`http://localhost:8081/user/plaza/works/${workNo}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${YOUR_TOKEN}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
console.log('作品详情:', result.data);
|
||||
|
||||
// 显示详情页
|
||||
showDetailModal(result.data);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 点赞作品
|
||||
```javascript
|
||||
async function likeWork(workNo) {
|
||||
const response = await fetch(
|
||||
`http://localhost:8081/user/plaza/works/${workNo}/like`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${YOUR_TOKEN}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
console.log('点赞成功,当前点赞数:', result.data.likeCount);
|
||||
|
||||
// 更新UI
|
||||
updateLikeButton(workNo, result.data.isLiked, result.data.likeCount);
|
||||
}
|
||||
|
||||
async function unlikeWork(workNo) {
|
||||
const response = await fetch(
|
||||
`http://localhost:8081/user/plaza/works/${workNo}/like`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${YOUR_TOKEN}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
console.log('取消点赞');
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 查看我的作品
|
||||
```javascript
|
||||
async function getMyWorks() {
|
||||
const response = await fetch(
|
||||
'http://localhost:8081/user/plaza/my-works?page=1&size=10',
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${YOUR_TOKEN}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
console.log('我的作品:', result.data.list);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、React 完整示例
|
||||
|
||||
```jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
// 广场组件
|
||||
function PlazaPage() {
|
||||
const [works, setWorks] = useState([]);
|
||||
const [taskType, setTaskType] = useState('');
|
||||
const [sortBy, setSortBy] = useState('latest');
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
loadWorks();
|
||||
}, [taskType, sortBy, page]);
|
||||
|
||||
async function loadWorks() {
|
||||
const params = new URLSearchParams({
|
||||
page,
|
||||
size: 20,
|
||||
sortBy,
|
||||
...(taskType && { taskType })
|
||||
});
|
||||
|
||||
const response = await fetch(
|
||||
`http://localhost:8081/user/plaza/works/list?${params}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
setWorks(result.data.list);
|
||||
}
|
||||
|
||||
async function handleLike(workNo, isLiked) {
|
||||
const method = isLiked ? 'DELETE' : 'POST';
|
||||
|
||||
const response = await fetch(
|
||||
`http://localhost:8081/user/plaza/works/${workNo}/like`,
|
||||
{
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
// 刷新列表
|
||||
loadWorks();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="plaza-page">
|
||||
<h1>AI创作广场</h1>
|
||||
|
||||
{/* 筛选器 */}
|
||||
<div className="filters">
|
||||
<select value={taskType} onChange={(e) => setTaskType(e.target.value)}>
|
||||
<option value="">全部类型</option>
|
||||
<option value="text_to_image">文生图</option>
|
||||
<option value="image_to_image">图生图</option>
|
||||
<option value="text_to_video">文生视频</option>
|
||||
<option value="image_to_video">图生视频</option>
|
||||
</select>
|
||||
|
||||
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
|
||||
<option value="latest">最新发布</option>
|
||||
<option value="hot">最热门</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* 作品网格 */}
|
||||
<div className="works-grid">
|
||||
{works.map(work => (
|
||||
<div key={work.workNo} className="work-card">
|
||||
<img src={work.resultUrl} alt={work.title} />
|
||||
<h3>{work.title}</h3>
|
||||
<p className="author">{work.author.nickname}</p>
|
||||
<div className="stats">
|
||||
<span>👁️ {work.viewCount}</span>
|
||||
<button onClick={() => handleLike(work.workNo, work.isLiked)}>
|
||||
{work.isLiked ? '❤️' : '🤍'} {work.likeCount}
|
||||
</button>
|
||||
</div>
|
||||
<div className="tags">
|
||||
{work.tags.map(tag => (
|
||||
<span key={tag} className="tag">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 分页 */}
|
||||
<div className="pagination">
|
||||
<button onClick={() => setPage(p => Math.max(1, p - 1))}>
|
||||
上一页
|
||||
</button>
|
||||
<span>第 {page} 页</span>
|
||||
<button onClick={() => setPage(p => p + 1)}>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlazaPage;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、后续完善功能(可选)
|
||||
|
||||
### 1. 评论功能
|
||||
- 创建 `plaza_work_comment` 表
|
||||
- 支持评论和回复
|
||||
- 评论点赞
|
||||
|
||||
### 2. 标签系统
|
||||
- 创建 `plaza_tag` 标签表
|
||||
- 支持热门标签推荐
|
||||
- 按标签筛选作品
|
||||
|
||||
### 3. 用户关注
|
||||
- 创建 `user_follow` 关注关系表
|
||||
- 查看关注用户的作品
|
||||
- 关注动态推送
|
||||
|
||||
### 4. 举报系统
|
||||
- 创建 `plaza_report` 举报表
|
||||
- 用户可以举报违规作品
|
||||
- 管理员审核机制
|
||||
|
||||
### 5. 作品收藏
|
||||
- 创建 `plaza_work_collect` 收藏表
|
||||
- 用户收藏喜欢的作品
|
||||
- 查看收藏列表
|
||||
|
||||
### 6. 分享功能
|
||||
- 生成分享链接
|
||||
- 分享到社交媒体
|
||||
- 统计分享数据
|
||||
|
||||
---
|
||||
|
||||
## 九、性能优化建议
|
||||
|
||||
### 1. 缓存策略
|
||||
- Redis 缓存热门作品列表
|
||||
- 缓存用户点赞状态
|
||||
- 缓存作品统计数据
|
||||
|
||||
### 2. CDN 加速
|
||||
- 作品图片/视频通过 CDN 分发
|
||||
- 提升加载速度
|
||||
|
||||
### 3. 数据库优化
|
||||
- 对热点字段建立索引
|
||||
- 分表存储历史数据
|
||||
- 读写分离
|
||||
|
||||
### 4. 异步处理
|
||||
- 浏览统计异步入库
|
||||
- 点赞数更新异步化
|
||||
- 使用消息队列
|
||||
|
||||
---
|
||||
|
||||
## 十、安全考虑
|
||||
|
||||
1. **内容审核**
|
||||
- 发布前自动审核(敏感词过滤)
|
||||
- 人工审核机制
|
||||
- 违规内容下架
|
||||
|
||||
2. **防刷机制**
|
||||
- 限制发布频率
|
||||
- 防止点赞刷数据
|
||||
- IP 限流
|
||||
|
||||
3. **权限控制**
|
||||
- 只能删除自己的作品
|
||||
- 未登录用户只能浏览
|
||||
- 隐私作品仅自己可见
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
广场功能的核心是:
|
||||
1. **数据模型** - 作品表、点赞表、浏览记录表
|
||||
2. **API接口** - 发布、查询、点赞、删除等
|
||||
3. **前端展示** - 瀑布流/网格布局、筛选排序、实时统计
|
||||
|
||||
按照本方案实施,可以快速搭建一个功能完善的AI作品广场。
|
||||
|
||||
下一步我会为你生成完整的 Java 代码(Mapper、Service、Controller)。
|
||||
|
||||
Reference in New Issue
Block a user