Files
1818web-hoduan/广场作品TaskId查询功能说明.md
2025-11-14 17:41:15 +08:00

20 KiB
Raw Blame History

广场作品TaskId查询功能说明

📋 功能概述

新增了通过任务编号taskId/taskNo查询作品详情的功能具有以下特点

  • 使用taskId查询时不会增加浏览数,避免对作品热度数据产生影响
  • 支持查询未发布的任务,即使任务还没发布到广场也能查看详情
  • 自动大小写转换,兼容大小写输入

🎯 业务需求

在某些场景下(如用户从自己的任务列表查看作品),需要通过任务编号直接查看详情,包括:

  1. 已发布作品:查看广场作品的完整信息(包含浏览数、点赞数等社交数据)
  2. 未发布任务:查看任务的基本信息(不包含社交数据)

此时不应计入公开浏览数,以保证统计数据的准确性。


🔧 实现方案

1. 接口修改

接口路径: GET /user/plaza/works/{identifier}

新增查询参数: queryType

参数名 类型 必填 默认值 说明
queryType String workNo 查询类型:
- workNo: 通过作品编号查询(记录浏览)
- taskId: 通过任务编号查询(不记录浏览)

📡 接口使用示例

示例1通过作品编号查询默认方式记录浏览

curl -X 'GET' \
  'http://116.62.4.26:8081/user/plaza/works/WORK-1762440663366-3093' \
  -H 'accept: */*'

或明确指定:

curl -X 'GET' \
  'http://116.62.4.26:8081/user/plaza/works/WORK-1762440663366-3093?queryType=workNo' \
  -H 'accept: */*'

行为:

  • 返回作品详情
  • 增加浏览数 +1

示例2通过任务编号查询不记录浏览

curl -X 'GET' \
  'http://116.62.4.26:8081/user/plaza/works/TASK-20251026183750127-8554?queryType=taskId' \
  -H 'accept: */*'

行为:

  • 返回作品详情(已发布)或任务详情(未发布)
  • 不增加浏览数(保持原有数据)

返回数据区别:

  • 如果已发布到广场:返回完整作品信息,包含标题、描述、标签、浏览数、点赞数等
  • 如果未发布返回任务基本信息社交数据浏览数、点赞数等为0标题/描述/标签为空

🔄 业务流程对比

通过workNo查询原有逻辑

用户请求 GET /plaza/works/{workNo}
    ↓
提取当前用户ID
    ↓
【记录浏览】增加view_count +1
    ↓
查询作品详情(联表查询)
    ↓
检查点赞状态
    ↓
返回作品详情

通过taskId查询新增逻辑

用户请求 GET /plaza/works/{taskNo}?queryType=taskId
    ↓
提取当前用户ID
    ↓
【跳过浏览记录】不增加view_count
    ↓
【步骤1】查询广场作品表plaza_work
    ├── 找到 → 返回完整作品详情(含社交数据)
    └── 未找到 → 继续下一步
    ↓
【步骤2】查询AI任务表ai_task
    ├── 找到 → 返回任务基本信息社交数据为0
    └── 未找到 → 抛出"任务不存在"异常
    ↓
返回详情

💻 代码实现

1. 控制器层修改

文件: PlazaController.java

修改位置: getWorkDetail 方法

@GetMapping("/works/{workNo}")
public Result<WorkDetailResponse> getWorkDetail(
        @PathVariable String workNo,
        @RequestParam(defaultValue = "workNo") String queryType) {
    
    try {
        Long userId = SecurityUtil.getCurrentUserIdOrNull();
        WorkDetailResponse response;
        
        // 根据查询类型处理
        if ("taskId".equalsIgnoreCase(queryType)) {
            // 通过taskId查询不记录浏览
            log.info("通过taskId查询作品详情taskNo: {}", workNo);
            response = plazaService.getWorkDetailByTaskNo(userId, workNo);
        } else {
            // 通过workNo查询记录浏览
            log.info("通过workNo查询作品详情workNo: {}", workNo);
            plazaService.recordView(workNo, userId);
            response = plazaService.getWorkDetail(userId, workNo);
        }
        
        return Result.success(response);
    } catch (Exception e) {
        log.error("查询作品详情失败identifier: {}, queryType: {}", workNo, queryType, e);
        return Result.error(e.getMessage());
    }
}

关键逻辑:

  • 通过 queryType 参数判断查询类型
  • taskId 模式:直接调用 getWorkDetailByTaskNo,跳过 recordView
  • workNo 模式:保持原有逻辑,先记录浏览再查询

2. 服务接口层

文件: PlazaService.java

新增方法:

/**
 * 通过任务编号查询作品详情(不记录浏览)
 * 
 * @param userId 当前用户ID可为null
 * @param taskNo 任务编号
 * @return 作品详情
 */
WorkDetailResponse getWorkDetailByTaskNo(Long userId, String taskNo);

3. 服务实现层

文件: PlazaServiceImpl.java

新增实现:

@Override
public WorkDetailResponse getWorkDetailByTaskNo(Long userId, String taskNo) {
    log.info("通过任务编号查询作品详情不记录浏览taskNo: {}", taskNo);
    
    // 1. 统一转换为大写(兼容大小写输入)
    String normalizedTaskNo = taskNo.toUpperCase();
    
    // 2. 先查询广场作品(已发布)
    Map<String, Object> workMap = plazaWorkMapper.findWorkDetailByTaskNo(normalizedTaskNo);
    
    if (workMap != null) {
        // 2.1 找到广场作品,检查是否点赞
        boolean isLiked = false;
        if (userId != null) {
            Long workId = (Long) workMap.get("id");
            isLiked = plazaWorkLikeMapper.existsByWorkIdAndUserId(workId, userId) > 0;
        }
        return convertToDetailResponse(workMap, isLiked);
    }
    
    // 3. 广场中没找到查询AI任务表未发布的任务
    log.info("广场中未找到作品,尝试从任务表查询 - taskNo: {}", normalizedTaskNo);
    Map<String, Object> taskMap = aiTaskMapper.findTaskDetailByTaskNo(normalizedTaskNo);
    
    if (taskMap == null) {
        throw new IllegalArgumentException("该任务不存在: " + taskNo);
    }
    
    // 4. 从任务数据构建作品详情响应(未发布状态)
    return convertTaskToWorkDetail(taskMap);
}

特点:

  • 两级查询:优先查询广场作品,未找到则查询任务表
  • 大小写兼容:自动转换为大写,兼容小写输入
  • 完整回退:未发布的任务也能正常返回
  • 不记录浏览:保证浏览数不变

4. 数据访问层

PlazaWorkMapper查询广场作品

文件: PlazaWorkMapper.java

新增SQL查询:

/**
 * 通过任务编号查询作品详情(包含作者信息)
 */
@Select("SELECT pw.id, pw.work_no, pw.user_id, pw.task_no, pw.task_type, pw.model_name, " +
        "       pw.prompt, pw.result_url, pw.image_url, pw.aspect_ratio, pw.title, " +
        "       pw.description, pw.tags, pw.view_count, pw.like_count, pw.share_count, " +
        "       pw.comment_count, pw.is_public, pw.status, pw.create_time, pw.update_time, " +
        "       u.username as nickname, u.avatar_url " +
        "FROM plaza_work pw " +
        "LEFT JOIN user u ON pw.user_id = u.id " +
        "WHERE pw.task_no = #{taskNo} AND pw.is_deleted = 0")
Map<String, Object> findWorkDetailByTaskNo(@Param("taskNo") String taskNo);

AiTaskMapper查询AI任务

文件: AiTaskMapper.javaAiTaskMapper.xml

新增方法:

/**
 * 通过任务编号查询任务详情(包含作者信息)
 */
java.util.Map<String, Object> findTaskDetailByTaskNo(@Param("taskNo") String taskNo);

XML实现:

<!-- 通过任务编号查询任务详情(包含作者信息) -->
<select id="findTaskDetailByTaskNo" resultType="java.util.HashMap">
    SELECT 
        at.id,
        at.task_no,
        at.user_id,
        at.model_name,
        at.task_type,
        at.prompt,
        at.result_url,
        at.image_url,
        at.aspect_ratio,
        at.status,
        at.progress,
        at.error_message,
        at.create_time,
        at.update_time,
        u.username as nickname,
        u.avatar_url
    FROM ai_task at
    LEFT JOIN user u ON at.user_id = u.id
    WHERE at.task_no = #{taskNo} AND at.is_deleted = 0
</select>

SQL特点:

  • 查询AI任务表的基本字段
  • 通过LEFT JOIN关联用户信息
  • 包含任务状态、进度、错误信息等

📊 响应数据结构

情况1查询已发布到广场的作品

{
  "code": 200,
  "message": "success",
  "data": {
    "workNo": "WORK-1762440663366-3093",
    "taskNo": "TASK-20251026183750127-8554",
    "taskType": "text_to_video",
    "modelName": "minimax-video-01",
    "prompt": "战争场景,恢弘气势...",
    "resultUrl": "https://oss.example.com/videos/xxx.mp4",
    "imageUrl": "https://oss.example.com/images/ref.jpg",
    "aspectRatio": "16:9",
    "title": "战场气氛短视频",
    "description": "根据参考图生成的恢弘战场场景",
    "tags": ["视频", "战争", "特效"],
    "viewCount": 125,
    "likeCount": 38,
    "shareCount": 5,
    "isLiked": true,
    "author": {
      "userId": 17563793187762127,
      "nickname": "用户123456",
      "avatarUrl": "https://img.remit.ee/api/file/xxx.png"
    },
    "createTime": "2024-11-06T10:30:00",
    "updateTime": "2024-11-07T15:45:00"
  }
}

情况2查询未发布的任务

2.1 任务已完成

{
  "code": 200,
  "message": "success",
  "data": {
    "workNo": null,  // 未发布,无作品编号
    "taskNo": "TASK-20251106195754218-9940",
    "taskType": "text_to_video",
    "modelName": "minimax-video-01",
    "prompt": "战争场景,恢弘气势...",
    "resultUrl": "https://oss.example.com/videos/xxx.mp4",  // ✅ 已完成,有结果
    "imageUrl": null,  // text_to_video 任务通常无参考图
    "aspectRatio": "16:9",
    "title": null,  // 未发布,无标题
    "description": null,  // 未发布,无描述
    "tags": [],  // 未发布,无标签
    "viewCount": 0,  // 未发布浏览数为0
    "likeCount": 0,  // 未发布点赞数为0
    "shareCount": 0,  // 未发布分享数为0
    "isLiked": false,  // 未发布,不可点赞
    "author": {
      "userId": 17563793187762127,
      "nickname": "用户123456",
      "avatarUrl": "https://img.remit.ee/api/file/xxx.png"
    },
    "createTime": "2024-11-06T19:57:54",
    "updateTime": "2024-11-06T20:05:30",
    "taskStatus": "completed",  // ✅ 任务状态:已完成
    "taskProgress": 100,  // ✅ 任务进度100%
    "errorMessage": null  // ✅ 无错误
  }
}

2.2 任务处理中

{
  "code": 200,
  "message": "success",
  "data": {
    "workNo": null,
    "taskNo": "TASK-20251106195754218-9940",
    "taskType": "text_to_video",
    "modelName": "minimax-video-01",
    "prompt": "战争场景,恢弘气势...",
    "resultUrl": null,  // ⏳ 处理中,暂无结果
    "imageUrl": null,
    "aspectRatio": "16:9",
    "title": null,
    "description": null,
    "tags": [],
    "viewCount": 0,
    "likeCount": 0,
    "shareCount": 0,
    "isLiked": false,
    "author": {
      "userId": 17563793187762127,
      "nickname": "用户123456",
      "avatarUrl": "https://img.remit.ee/api/file/xxx.png"
    },
    "createTime": "2024-11-06T19:57:54",
    "updateTime": "2024-11-06T19:58:55",
    "taskStatus": "processing",  // ⏳ 任务状态:处理中
    "taskProgress": 45,  // ⏳ 任务进度45%
    "errorMessage": null
  }
}

2.3 任务失败

{
  "code": 200,
  "message": "success",
  "data": {
    "workNo": null,
    "taskNo": "TASK-20251106195754218-9940",
    "taskType": "text_to_video",
    "modelName": "minimax-video-01",
    "prompt": "战争场景,恢弘气势...",
    "resultUrl": null,  // ❌ 失败,无结果
    "imageUrl": null,
    "aspectRatio": "16:9",
    "title": null,
    "description": null,
    "tags": [],
    "viewCount": 0,
    "likeCount": 0,
    "shareCount": 0,
    "isLiked": false,
    "author": {
      "userId": 17563793187762127,
      "nickname": "用户123456",
      "avatarUrl": "https://img.remit.ee/api/file/xxx.png"
    },
    "createTime": "2024-11-06T19:57:54",
    "updateTime": "2024-11-06T20:10:30",
    "taskStatus": "failed",  // ❌ 任务状态:失败
    "taskProgress": 0,  // ❌ 任务进度0%
    "errorMessage": "生成超时或服务异常"  // ❌ 错误信息
  }
}

关键区别:

字段 已发布作品 未发布任务
workNo 有值 null
title 用户设置的标题 null
description 用户设置的描述 null
tags 用户设置的标签数组 [] 空数组
viewCount 真实浏览数 0
likeCount 真实点赞数 0
shareCount 真实分享数 0
isLiked 真实点赞状态 false
taskStatus null 任务状态queued/processing/completed/failed
taskProgress null 任务进度0-100
errorMessage null 错误信息(失败时才有)
resultUrl 总是有值 仅completed时有值
imageUrl 可能有值 text_to_video通常为null

🔍 任务状态说明

任务状态taskStatus

状态 说明 resultUrl 前端建议
queued 排队中 null 显示"排队中..."
processing 处理中 null 显示进度条使用taskProgress
completed 已完成 有值 显示视频播放器
failed 失败 null 显示错误提示使用errorMessage

为什么resultUrl为null

  1. 任务未完成taskStatusqueuedprocessing

    • 解决方案:轮询查询,等待任务完成
  2. 任务失败taskStatusfailed

    • 解决方案:显示 errorMessage 给用户
  3. 任务刚完成:数据库更新延迟

    • 解决方案:刷新重试

为什么imageUrl为null

  1. text_to_video任务:文本生成视频,不需要参考图 正常
  2. image_to_video任务图生视频应该有参考图URL

🎯 使用场景

场景1公开广场浏览使用workNo

场景描述: 用户在广场浏览其他人的作品

调用方式: GET /plaza/works/{workNo}?queryType=workNo

预期行为:

  • 记录浏览数
  • 用于热度排序
  • 统计作品受欢迎程度

场景2用户查看自己的作品使用taskId

场景描述: 用户从"我的任务"列表点击查看已发布的作品

调用方式: GET /plaza/works/{taskNo}?queryType=taskId

预期行为:

  • 不记录浏览数(避免刷数据)
  • 查看真实的浏览统计
  • 预览作品效果

场景3外部分享链接使用workNo

场景描述: 通过分享链接访问作品

调用方式: GET /plaza/works/{workNo}

预期行为:

  • 记录浏览数
  • 统计分享传播效果

🔒 安全性考虑

1. 权限控制

  • 两种查询方式都支持未登录用户访问
  • 已登录用户可查看个性化点赞状态
  • 查询条件都包含 is_deleted = 0,过滤已删除作品

2. 数据一致性

  • task_nowork_no 在数据库中都有索引(建议)
  • 一个任务最多对应一个作品(发布时已验证)
  • 查询结果经过软删除过滤

3. 异常处理

// taskId不存在时的错误提示
throw new IllegalArgumentException("该任务对应的作品不存在: " + taskNo);

// workNo不存在时的错误提示
throw new IllegalArgumentException("作品不存在: " + workNo);

📈 数据库优化建议

推荐索引

-- 任务编号索引(如果尚未创建)
CREATE INDEX idx_task_no ON plaza_work(task_no);

-- 作品编号索引(应该已存在)
CREATE UNIQUE INDEX uk_work_no ON plaza_work(work_no);

-- 复合索引(优化查询性能)
CREATE INDEX idx_task_no_deleted ON plaza_work(task_no, is_deleted);

🧪 测试建议

测试用例1: 通过workNo查询记录浏览

# 第一次查询
curl 'http://116.62.4.26:8081/user/plaza/works/WORK-xxx'
# 预期: viewCount = N

# 第二次查询
curl 'http://116.62.4.26:8081/user/plaza/works/WORK-xxx'
# 预期: viewCount = N + 1

测试用例2: 通过taskId查询不记录浏览

# 查询前记录当前浏览数
curl 'http://116.62.4.26:8081/user/plaza/works/WORK-xxx'
# 假设 viewCount = 100

# 通过taskId查询多次
curl 'http://116.62.4.26:8081/user/plaza/works/TASK-xxx?queryType=taskId'
curl 'http://116.62.4.26:8081/user/plaza/works/TASK-xxx?queryType=taskId'
curl 'http://116.62.4.26:8081/user/plaza/works/TASK-xxx?queryType=taskId'

# 再次通过workNo查询确认
curl 'http://116.62.4.26:8081/user/plaza/works/WORK-xxx'
# 预期: viewCount 仍然为 100未增加

测试用例3: 错误的taskId

curl 'http://116.62.4.26:8081/user/plaza/works/TASK-invalid?queryType=taskId'

# 预期响应:
{
  "code": 400,
  "message": "该任务对应的作品不存在: TASK-invalid",
  "data": null
}

测试用例4: 未发布作品的taskId

# 对于未发布到广场的任务
curl 'http://116.62.4.26:8081/user/plaza/works/TASK-未发布?queryType=taskId'

# 预期响应:
{
  "code": 400,
  "message": "该任务对应的作品不存在: TASK-未发布",
  "data": null
}

📝 日志记录

通过workNo查询的日志

INFO - 通过workNo查询作品详情workNo: WORK-1762440663366-3093
DEBUG - 记录作品浏览workNo: WORK-1762440663366-3093, userId: 17563793187762127
INFO - 查询作品详情workNo: WORK-1762440663366-3093

通过taskId查询的日志

INFO - 通过taskId查询作品详情taskNo: TASK-20251026183750127-8554
INFO - 通过任务编号查询作品详情不记录浏览taskNo: TASK-20251026183750127-8554

修改文件清单

文件路径 修改内容
PlazaController.java 新增 queryType 参数,根据类型分发请求
PlazaService.java 新增 getWorkDetailByTaskNo() 接口方法
PlazaServiceImpl.java 实现两级查询逻辑、大小写转换、convertTaskToWorkDetail() 转换方法
PlazaWorkMapper.java 新增 findWorkDetailByTaskNo() SQL查询
AiTaskMapper.java 新增 findTaskDetailByTaskNo() 接口方法
AiTaskMapper.xml 新增 findTaskDetailByTaskNo SQL实现关联user表

🎬 总结

向后兼容 - 原有接口调用方式不受影响
灵活查询 - 支持通过作品编号或任务编号查询
完整回退 - 未发布任务也能正常查询
大小写兼容 - 自动转换大小写,提升用户体验
数据准确 - taskId查询不影响浏览统计
完善日志 - 详细记录查询类型和来源
异常处理 - 明确的错误提示信息

该功能完美满足了在不同场景下查询作品详情的需求,包括已发布作品和未发布任务,同时保证了统计数据的准确性!


🔍 Bug修复记录

Bug 1: 大小写敏感问题

现象: 传入小写的taskIdtask-xxx)查询失败

原因: 数据库中存储的是大写格式(如 TASK-xxx

解决: 在所有查询方法中添加 .toUpperCase() 转换

影响范围: getWorkDetail, getWorkDetailByTaskNo, likeWork, unlikeWork, deleteWork, recordView

Bug 2: 未发布任务无法查询

现象: 传入未发布任务的taskId返回"作品不存在"

原因: 只查询了plaza_work表未发布的任务不在该表中

解决: 实现两级查询先查plaza_work未找到再查ai_task表

新增方法: AiTaskMapper.findTaskDetailByTaskNo(), PlazaServiceImpl.convertTaskToWorkDetail()


📞 技术支持

如有问题,请查看日志文件或联系技术团队。

日志关键字:

  • 通过taskId查询作品详情
  • 通过workNo查询作品详情
  • 该任务对应的作品不存在