feat(content): 新增视频审核功能并优化状态管理

- 新增 AuditVlogBO 类用于视频审核请求参数
- 在 VlogController 中增加视频审核状态变更接口
- 修改 VlogService 接口与实现类,支持通过 BO 对象更新审核状态-为 Vlog 实体类添加 Lombok 注解以简化构建
- 调整视频列表查询逻辑,支持按是否私密和状态筛选
- 更新 VlogMapperCustom.xml 查询条件动态 SQL 结构- 设置默认视频状态为“待审核”
- 移除冗余的用户 ID 参数校验,改用 BO 校验方式
- 引入枚举 VlogStatusEnum 的 Getter 方法便于获取状态值
This commit is contained in:
huk 2025-09-29 16:10:49 +08:00
parent eeab201625
commit 4c1e123214
9 changed files with 141 additions and 123 deletions

View File

@ -3,6 +3,8 @@ package org.dromara.app;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wzj.soopin.content.domain.bo.*;
import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.enums.VlogStatusEnum;
import com.wzj.soopin.content.enums.YesOrNo;
import com.wzj.soopin.content.service.IVlogPullService;
import com.wzj.soopin.content.service.VlogService;
import com.wzj.soopin.content.service.VlogUploadService;
@ -160,11 +162,11 @@ public class AppVlogController {
@Operation(summary = "视频列表")
@PostMapping("/page")
public R<Page<IndexVlogVO>> page(@RequestBody VlogBO vlogBO) {
LoginUser loginUser = LoginHelper.getLoginUser();
if(vlogBO.getMemberId()==null){
throw new ServiceException("用户id不能为空");
}
vlogBO.setStatus(VlogStatusEnum.APPROVED.type);
vlogBO.setIsPrivate(YesOrNo.NO.type);
return R.ok(vlogService.getIndexVlogList(vlogBO,vlogBO.getPage()));
}

View File

@ -3,7 +3,7 @@ package com.wzj.soopin.content.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.common.core.constant.BaseInfoProperties;
import com.wzj.soopin.content.domain.bo.AuditVlogBO;
import com.wzj.soopin.content.domain.bo.VlogBO;
import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.enums.YesOrNo;
@ -13,19 +13,23 @@ import com.wzj.soopin.content.service.VlogService;
import com.wzj.soopin.content.utils.RedisOperator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.BaseInfoProperties;
import org.dromara.common.core.domain.R;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Slf4j
@Tag(name = "短视频相关业务功能的接口")
@Tag(name = "短视频管理")
@RequestMapping("/cms/vlog")
@RestController
@AllArgsConstructor
@Validated
public class VlogController extends BaseInfoProperties {
private final VlogService vlogService;
public final RedisOperator redis;
@ -35,9 +39,10 @@ public class VlogController extends BaseInfoProperties {
@Operation(summary = "查询列表")
@PostMapping("/page")
public R<IPage<IndexVlogVO>> page(@RequestBody VlogBO bo, @RequestBody Page page) {
Page<IndexVlogVO> pages = vlogService.getIndexVlogList(bo ,page);
Page<IndexVlogVO> pages = vlogService.getIndexVlogList(bo, page);
return R.ok(pages);
}
@PostMapping("/vodCallBack")
public R<Void> vodCallBack(@RequestBody Map<String, Object> callbackData) {
try {
@ -59,32 +64,32 @@ public class VlogController extends BaseInfoProperties {
if ("FINISH".equals(status)) {
// 获取 MediaProcessResultSet可能包含任务流信息
List<Map<String, Object>> mediaProcessResultSet =
(List<Map<String, Object>>) procedureStateChangeEvent.get("MediaProcessResultSet");
(List<Map<String, Object>>) procedureStateChangeEvent.get("MediaProcessResultSet");
if (mediaProcessResultSet != null) {
String coverUrl=null; // 首帧图
String transVdUrl=null; // 转码后的视频地址
String coverUrl = null; // 首帧图
String transVdUrl = null; // 转码后的视频地址
for (Map<String, Object> processResult : mediaProcessResultSet) {
String type = (String) processResult.get("Type");
if ("CoverBySnapshot".equals(type)) {
// 任务流首帧截图
Map<String, Object> coverBySnapshotTask =
(Map<String, Object>) processResult.get("CoverBySnapshotTask");
(Map<String, Object>) processResult.get("CoverBySnapshotTask");
Map<String, Object> output = (Map<String, Object>) coverBySnapshotTask.get("Output");
coverUrl = (String) output.get("CoverUrl"); // 正确获取 CoverUrl
}
if("Transcode".equals(type)){
if ("Transcode".equals(type)) {
// 拿到output中的url
Map<String, Object> TranscodeTask =
(Map<String, Object>) processResult.get("TranscodeTask");
(Map<String, Object>) processResult.get("TranscodeTask");
Map<String, Object> output = (Map<String, Object>) TranscodeTask.get("Output");
transVdUrl = (String) output.get("Url");
}
}
log.info("任务流 [截取首帧,视频转码] 完成, FileId: {}, 封面图 URL: {},视频地址: {}", fileId, coverUrl,transVdUrl);
log.info("任务流 [截取首帧,视频转码] 完成, FileId: {}, 封面图 URL: {},视频地址: {}", fileId, coverUrl, transVdUrl);
// TODO: 更新数据库存储首帧图,设置云端地址删除minio视频文件
vlogService.updateVlogFirstImg(fileId,coverUrl,transVdUrl);
vlogService.updateVlogFirstImg(fileId, coverUrl, transVdUrl);
} else {
log.warn("MediaProcessResultSet 为空,回调数据: {}", callbackData);
@ -95,7 +100,7 @@ public class VlogController extends BaseInfoProperties {
log.warn("ProcedureStateChangeEvent 为空或格式错误,回调数据: {}", callbackData);
}
}
if("ReviewAudioVideoComplete".equals(eventType)){
if ("ReviewAudioVideoComplete".equals(eventType)) {
//处理审核结果
Map<String, Object> reviewEvent = (Map<String, Object>) callbackData.get("ReviewAudioVideoCompleteEvent");
if (reviewEvent != null) {
@ -120,20 +125,20 @@ public class VlogController extends BaseInfoProperties {
String subLabel = (String) output.get("SubLabel"); // 违规子标签
log.info("视频文件 {} 审核驳回, 原因: {} - {}", fileId, label, subLabel);
// TODO: 更新数据库,发送站内信
vlogService.updateVlogStatus(fileId,2,label);
vlogService.updateVlogStatus(fileId, 2, label);
} else if ("pass".equals(suggestion)) {
// 审核通过处理通过逻辑,修改视频状态为1发送站内消息告知审核结果
log.info("视频文件 {} 审核通过", fileId);
// TODO: 更新数据库发送站内信
vlogService.updateVlogStatus(fileId,1,"通过");
vlogService.updateVlogStatus(fileId, 1, "通过");
} else if ("review".equals(suggestion)){
} else if ("review".equals(suggestion)) {
// 建议复审修改状态为3发送站内消息告知审核结果
String label = (String) output.get("Label"); // 违规内容
String subLabel = (String) output.get("SubLabel"); // 违规子标签
log.info("视频文件 {} 建议复审, 原因: {} - {}", fileId, label, subLabel);
// TODO: 更新数据库发送站内信
vlogService.updateVlogStatus(fileId,3,"等待复审");
vlogService.updateVlogStatus(fileId, 3, "等待复审");
}
}
@ -148,36 +153,24 @@ public class VlogController extends BaseInfoProperties {
}
@Operation(summary = "修改视频状态")
@PostMapping("/changeVlogStatus")
public R<Void> changeVlogStatus(@RequestParam String userId,
@RequestParam String vlogId,
@RequestParam Integer status) {
vlogService.changeVlogStatus(userId, vlogId,status);
public R<Void> changeVlogStatus(@RequestBody @Valid AuditVlogBO bo) {
vlogService.changeVlogStatus(bo);
return R.ok();
}
@Operation(summary = "修改视频为私密")
@PostMapping("/changeToPrivate")
public R<Void> changeToPrivate(@RequestParam String userId,
@RequestParam String vlogId) {
vlogService.changeToPrivateOrPublic(userId,
vlogId,
YesOrNo.YES.type);
public R<Void> changeToPrivate(@RequestParam String vlogId) {
vlogService.changeToPrivateOrPublic(vlogId, YesOrNo.YES.type);
return R.ok();
}
@Operation(summary = "修改视频为公开")
@PostMapping("/changeToPublic")
public R<Void> changeToPublic(@RequestParam String userId,
@RequestParam String vlogId) {
vlogService.changeToPrivateOrPublic(userId,
vlogId,
YesOrNo.NO.type);
public R<Void> changeToPublic(@RequestParam String vlogId) {
vlogService.changeToPrivateOrPublic(vlogId, YesOrNo.NO.type);
return R.ok();
}
@ -189,8 +182,8 @@ public class VlogController extends BaseInfoProperties {
@Operation(summary = "推送视频")
@GetMapping("/push")
public R pushMember(@RequestParam int count,@RequestParam String tag) {
return R.ok(vlogPushService.pushVlogToMq(count,tag));
public R pushMember(@RequestParam int count, @RequestParam String tag) {
return R.ok(vlogPushService.pushVlogToMq(count, tag));
}
@Operation(summary = "同步视频")

View File

@ -0,0 +1,35 @@
package com.wzj.soopin.content.domain.bo;
import com.wzj.soopin.content.enums.VlogStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 视频审核BO
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuditVlogBO {
@Schema(description = "视频id")
@NotBlank(message = "视频id不能为空")
private String vlogId;
/**
* 状态
* @see VlogStatusEnum#getType()
*/
@NotNull(message = "状态不能为空")
@Schema(description = "审核状态", implementation = VlogStatusEnum.class)
private Integer status;
@Schema(description = "未通过原因")
private String reason;
}

View File

@ -54,6 +54,9 @@ public class VlogBO extends BaseBO {
@Schema(description = "视频标题(模糊查询)")
private String titleQuery;
@Schema(description = "是否私密")
private Integer isPrivate;
@Schema(description = "视频状态(精确查询)")
private Integer status;

View File

@ -1,15 +1,16 @@
package com.wzj.soopin.content.domain.po;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.*;
import org.apache.ibatis.type.JdbcType;
import org.dromara.common.core.domain.model.BaseAudit;
@TableName(value = "cont_vlog", autoResultMap = true)
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Vlog extends BaseAudit {
@TableId
private String id;

View File

@ -1,8 +1,10 @@
package com.wzj.soopin.content.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum VlogStatusEnum {
AUDITING(0, "待审核"),
APPROVED(1, "通过"),

View File

@ -3,10 +3,12 @@ package com.wzj.soopin.content.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wzj.soopin.content.domain.bo.*;
import com.wzj.soopin.content.domain.bo.AuditVlogBO;
import com.wzj.soopin.content.domain.bo.MyListBO;
import com.wzj.soopin.content.domain.bo.SearchBO;
import com.wzj.soopin.content.domain.bo.VlogBO;
import com.wzj.soopin.content.domain.po.Vlog;
import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.utils.PagedGridResult;
import java.util.List;
import java.util.Map;
@ -34,13 +36,9 @@ public interface VlogService extends IService<Vlog> {
/**
* 用户把视频改为公开/私密的视频
*/
public void changeToPrivateOrPublic(String userId,
String vlogId,
Integer yesOrNo);
public void changeToPrivateOrPublic(String vlogId, Integer yesOrNo);
public void changeVlogStatus(String userId,
String vlogId,
Integer status);
public void changeVlogStatus(AuditVlogBO auditVlogBO);
/**
* 查询用的公开/私密的视频列表
*/

View File

@ -8,32 +8,36 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wzj.soopin.content.domain.bo.*;
import com.wzj.soopin.content.convert.VlogConvert;
import com.wzj.soopin.content.domain.bo.AuditVlogBO;
import com.wzj.soopin.content.domain.bo.MyListBO;
import com.wzj.soopin.content.domain.bo.SearchBO;
import com.wzj.soopin.content.domain.bo.VlogBO;
import com.wzj.soopin.content.domain.po.MyLikedVlog;
import com.wzj.soopin.content.domain.po.Vlog;
import com.wzj.soopin.content.enums.VlogStatusEnum;
import com.wzj.soopin.member.domain.po.Member;
import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.enums.VlogStatusEnum;
import com.wzj.soopin.content.enums.YesOrNo;
import com.wzj.soopin.content.mapper.CommentMapper;
import com.wzj.soopin.content.mapper.MyLikedVlogMapper;
import com.wzj.soopin.member.domain.vo.MemberVO;
import com.wzj.soopin.member.mapper.MemberMapper;
import com.wzj.soopin.content.mapper.VlogMapper;
import com.wzj.soopin.content.mapper.VlogMapperCustom;
import com.wzj.soopin.content.service.MsgService;
import com.wzj.soopin.content.service.VlogService;
import com.wzj.soopin.content.utils.RedisOperator;
import com.wzj.soopin.content.utils.Sid;
import com.wzj.soopin.member.domain.po.Member;
import com.wzj.soopin.member.domain.vo.MemberVO;
import com.wzj.soopin.member.mapper.MemberMapper;
import com.wzj.soopin.member.service.IFansService;
import com.wzj.soopin.content.convert.VlogConvert;
import com.wzj.soopin.member.service.IMemberService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.mq.config.RocketMQConfig;
import org.dromara.common.mq.domain.MQMessage;
import org.dromara.common.mq.enums.MQMessageType;
@ -46,16 +50,12 @@ import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.dromara.common.core.utils.MapstructUtils;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;
import java.util.ArrayList;
import lombok.extern.slf4j.Slf4j;
import static org.dromara.common.core.constant.BaseInfoProperties.*;
@ -141,7 +141,7 @@ public class VlogServiceImpl extends ServiceImpl<VlogMapper, Vlog> implements Vl
vlog.setId(vid);
vlog.setLikeCounts(0);
vlog.setCommentsCounts(0);
vlog.setStatus(0);
vlog.setStatus(VlogStatusEnum.AUDITING.type);
vlog.setIsPrivate(YesOrNo.NO.type);
vlog.setMemberId(loginUser.getUserId()+"");
@ -213,25 +213,16 @@ public class VlogServiceImpl extends ServiceImpl<VlogMapper, Vlog> implements Vl
@Transactional(rollbackFor = Exception.class)
@Override
@CacheEvict(value = GlobalConstants.VLOG_KEY, key = "#vlogId")
public void changeToPrivateOrPublic(String userId, String vlogId, Integer yesOrNo) {
LambdaUpdateWrapper<Vlog> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Vlog::getId, vlogId)
.eq(Vlog::getMemberId, userId)
.set(Vlog::getIsPrivate, yesOrNo);
vlogMapper.update(null, updateWrapper);
public void changeToPrivateOrPublic(String vlogId, Integer yesOrNo) {
vlogMapper.updateById(Vlog.builder().id(vlogId).isPrivate(yesOrNo).build());
}
@Transactional(rollbackFor = Exception.class)
@Override
@CacheEvict(value = GlobalConstants.VLOG_KEY, key = "#vlogId")
public void changeVlogStatus(String userId, String vlogId, Integer status) {
LambdaUpdateWrapper<Vlog> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Vlog::getId, vlogId)
.eq(Vlog::getMemberId, userId)
.set(Vlog::getStatus, status);
vlogMapper.update(null, updateWrapper);
@CacheEvict(value = GlobalConstants.VLOG_KEY, key = "#bo.vlogId")
public void changeVlogStatus(AuditVlogBO bo) {
vlogMapper.updateById(Vlog.builder().id(bo.getVlogId()).status(bo.getStatus()).reason(bo.getReason()).build());
}
@Override

View File

@ -3,57 +3,51 @@
<mapper namespace="com.wzj.soopin.content.mapper.VlogMapperCustom" >
<select id="getIndexVlogList" parameterType="map" resultType="com.wzj.soopin.content.domain.vo.IndexVlogVO">
SELECT
v.id,
v.member_id,
m.avatar,
m.nickname,
m.phone_encrypted as phone,
v.create_time,
v.title as content,
v.url as url,
v.cover as cover,
v.title,
v.width as width,
v.height as height,
v.like_counts as likeCounts,
v.comments_counts as commentsCounts,
v.is_private as isPrivate,
v.city_code as cityCode,
v.reason as reason,
v.file_id as fileId,
v.status as status,
v.first_frame_img as firstFrameImg
FROM
cont_vlog v
LEFT JOIN
ums_member m
ON
v.member_id = m.id
WHERE
v.is_private = 0
<choose>
<when test="bo.status != null and bo.status!=''">
AND v.status = #{bo.status}
</when>
<otherwise>
AND v.status = 1
</otherwise>
</choose>
AND v.first_frame_img IS NOT NULL
<if test="bo.cityCode != null and bo.cityCode != ''">
SELECT v.id,
v.member_id,
m.avatar,
m.nickname,
m.phone_encrypted as phone,
v.create_time,
v.title as content,
v.url as url,
v.cover as cover,
v.title,
v.width as width,
v.height as height,
v.like_counts as likeCounts,
v.comments_counts as commentsCounts,
v.is_private as isPrivate,
v.city_code as cityCode,
v.reason as reason,
v.file_id as fileId,
v.status as status,
v.first_frame_img as firstFrameImg
FROM cont_vlog v
LEFT JOIN
ums_member m
ON
v.member_id = m.id
WHERE v.first_frame_img IS NOT NULL
<if test="bo.isPrivate != null">
AND v.is_private = #{bo.isPrivate}
</if>
<if test="bo.status != null">
AND v.status = #{bo.status}
</if>
<if test="bo.cityCode != null and bo.cityCode != ''">
AND v.city_code = #{bo.cityCode}
</if>
</if>
<if test="bo.memberId != null and bo.memberId != ''">
AND v.member_id = #{bo.memberId}
</if>
<if test="bo.title != null and bo.title != ''">
<if test="bo.title != null and bo.title != ''">
AND v.title like '%${bo.title}%'
</if>
<if test="bo.startTime != null ">
</if>
<if test="bo.startTime != null">
AND v.create_time >= #{bo.startTime}
</if>
<if test="bo.endTime != null ">
<if test="bo.endTime != null">
AND #{bo.endTime} >= v.create_time
</if>
<if test="bo.mobile != null and bo.mobile != ''">
@ -72,14 +66,13 @@
</foreach>
</if>
<if test="bo.ids != null and bo.ids.size() > 0">
AND v.id IN
AND v.id IN
<foreach collection="bo.ids" item="vlogId" open="(" separator="," close=")">
#{vlogId}
</foreach>
</if>
ORDER BY
v.create_time
DESC
ORDER BY v.create_time
DESC
</select>