Files
1818web-hoduan/广场功能实现方案.md
2025-11-14 17:41:15 +08:00

15 KiB
Raw Blame History

广场功能完整实现方案

一、功能概述

用户可以将AI生成的作品发布到广场其他用户可以浏览、点赞、按类型筛选查询作品。

核心功能

  1. 发布作品 - 用户将任务结果发布到广场
  2. 浏览广场 - 按类型、热度、时间查询作品
  3. 作品详情 - 查看单个作品详细信息
  4. 点赞/取消点赞 - 用户可以对作品点赞
  5. 统计数据 - 浏览量、点赞数、分享数统计
  6. 个人作品 - 查看自己发布的作品
  7. 删除作品 - 用户可以删除自己的作品

二、数据库设计

已在 V10__add_plaza_feature.sql 中定义了以下表:

1. plaza_work广场作品表

- 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点赞表

- id: 主键
- work_id: 作品ID
- user_id: 点赞用户ID
- create_time: 点赞时间
- 唯一索引:(work_id, user_id)

3. plaza_work_view浏览记录表

- id: 主键
- work_id: 作品ID
- user_id: 浏览用户ID(可空)
- ip_address: IP地址
- view_time: 浏览时间

三、API 接口设计

基础路径

所有接口以 /user/plaza 开头

1. 发布作品

POST /user/plaza/works/publish

请求参数:

{
  "taskNo": "TASK-20251026183750127-8554",
  "title": "战场气氛短视频",
  "description": "根据参考图生成的恢弘战场场景",
  "tags": ["视频", "战争", "特效"],
  "isPublic": true
}

响应示例:

{
  "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

响应示例:

{
  "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}

响应示例:

{
  "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

响应示例:

{
  "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}

响应示例:

{
  "code": 200,
  "message": "删除成功",
  "data": null
}

7. 统计数据

GET /user/plaza/stats

响应示例:

{
  "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脚本

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. 发布作品到广场

// 假设用户刚完成了一个任务,想发布到广场
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. 浏览广场(按类型筛选)

// 查询所有文生图作品
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. 查看作品详情

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. 点赞作品

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. 查看我的作品

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 完整示例

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