diff --git a/src/main/java/com/ycwl/basic/controller/VideoReviewController.java b/src/main/java/com/ycwl/basic/controller/VideoReviewController.java index 6431a98e..cfa0e1cd 100644 --- a/src/main/java/com/ycwl/basic/controller/VideoReviewController.java +++ b/src/main/java/com/ycwl/basic/controller/VideoReviewController.java @@ -1,6 +1,8 @@ package com.ycwl.basic.controller; import com.github.pagehelper.PageInfo; +import com.ycwl.basic.model.pc.videoreview.dto.AdminVideoReviewLogReqDTO; +import com.ycwl.basic.model.pc.videoreview.dto.AdminVideoReviewLogRespDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoPurchaseCheckReqDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoPurchaseCheckRespDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewAddReqDTO; @@ -55,6 +57,20 @@ public class VideoReviewController { return ApiResponse.success(pageInfo); } + /** + * 管理后台分页查询评价日志 + * 提供更详细的管理信息,包括评价人账号、机位评价统计等 + * + * @param reqDTO 查询条件 + * @return 分页结果 + */ + @GetMapping("/admin/logs") + public ApiResponse> getAdminReviewLogList(AdminVideoReviewLogReqDTO reqDTO) { + log.info("管理后台查询评价日志,pageNum: {}, pageSize: {}", reqDTO.getPageNum(), reqDTO.getPageSize()); + PageInfo pageInfo = videoReviewService.getAdminReviewLogList(reqDTO); + return ApiResponse.success(pageInfo); + } + /** * 获取评价统计数据 * diff --git a/src/main/java/com/ycwl/basic/enums/VideoReviewSourceEnum.java b/src/main/java/com/ycwl/basic/enums/VideoReviewSourceEnum.java new file mode 100644 index 00000000..3472f98c --- /dev/null +++ b/src/main/java/com/ycwl/basic/enums/VideoReviewSourceEnum.java @@ -0,0 +1,63 @@ +package com.ycwl.basic.enums; + +import lombok.Getter; + +/** + * 视频评价来源枚举 + */ +@Getter +public enum VideoReviewSourceEnum { + + /** + * 订单 + */ + ORDER("ORDER", "订单"), + + /** + * 渲染 + */ + RENDER("RENDER", "渲染"); + + /** + * 枚举代码 + */ + private final String code; + + /** + * 枚举描述 + */ + private final String description; + + VideoReviewSourceEnum(String code, String description) { + this.code = code; + this.description = description; + } + + /** + * 根据code获取枚举 + * + * @param code 枚举代码 + * @return 枚举对象,不存在则返回null + */ + public static VideoReviewSourceEnum fromCode(String code) { + if (code == null || code.isEmpty()) { + return null; + } + for (VideoReviewSourceEnum value : VideoReviewSourceEnum.values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return null; + } + + /** + * 验证code是否有效 + * + * @param code 枚举代码 + * @return true-有效, false-无效 + */ + public static boolean isValid(String code) { + return fromCode(code) != null; + } +} diff --git a/src/main/java/com/ycwl/basic/handler/LongListTypeHandler.java b/src/main/java/com/ycwl/basic/handler/LongListTypeHandler.java new file mode 100644 index 00000000..8f361464 --- /dev/null +++ b/src/main/java/com/ycwl/basic/handler/LongListTypeHandler.java @@ -0,0 +1,80 @@ +package com.ycwl.basic.handler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Long类型列表的TypeHandler + * 用于处理数据库JSON字段与Java List之间的转换 + */ +@Slf4j +@MappedTypes(List.class) +@MappedJdbcTypes(JdbcType.VARCHAR) +public class LongListTypeHandler extends BaseTypeHandler> { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final TypeReference> typeReference = new TypeReference>() {}; + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) throws SQLException { + try { + String json = objectMapper.writeValueAsString(parameter); + ps.setString(i, json); + log.debug("序列化Long列表: {}", json); + } catch (JsonProcessingException e) { + log.error("序列化Long列表失败", e); + throw new SQLException("序列化Long列表失败", e); + } + } + + @Override + public List getNullableResult(ResultSet rs, String columnName) throws SQLException { + String json = rs.getString(columnName); + return parseJson(json, columnName); + } + + @Override + public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + String json = rs.getString(columnIndex); + return parseJson(json, "columnIndex:" + columnIndex); + } + + @Override + public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + String json = cs.getString(columnIndex); + return parseJson(json, "columnIndex:" + columnIndex); + } + + private List parseJson(String json, String source) { + if (json == null || json.trim().isEmpty() || "null".equals(json)) { + log.debug("从{}获取的JSON为空,返回空列表", source); + return new ArrayList<>(); + } + + try { + List result = objectMapper.readValue(json, typeReference); + if (result == null) { + log.debug("从{}反序列化得到null,返回空列表", source); + return new ArrayList<>(); + } + log.debug("从{}反序列化Long列表成功,数量: {}", source, result.size()); + return result; + } catch (JsonProcessingException e) { + log.error("从{}反序列化Long列表失败,JSON: {}", source, json, e); + return new ArrayList<>(); + } + } +} diff --git a/src/main/java/com/ycwl/basic/handler/StringListTypeHandler.java b/src/main/java/com/ycwl/basic/handler/StringListTypeHandler.java new file mode 100644 index 00000000..eff75ee8 --- /dev/null +++ b/src/main/java/com/ycwl/basic/handler/StringListTypeHandler.java @@ -0,0 +1,74 @@ +package com.ycwl.basic.handler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * String列表类型处理器 + * 用于将数据库中的JSON字符串转换为Java的List对象 + */ +@Slf4j +@MappedTypes(List.class) +@MappedJdbcTypes(JdbcType.VARCHAR) +public class StringListTypeHandler extends BaseTypeHandler> { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final TypeReference> typeReference = new TypeReference>() {}; + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) throws SQLException { + try { + String json = objectMapper.writeValueAsString(parameter); + ps.setString(i, json); + log.debug("序列化String列表: {}", json); + } catch (JsonProcessingException e) { + log.error("序列化String列表失败", e); + throw new SQLException("序列化String列表失败", e); + } + } + + @Override + public List getNullableResult(ResultSet rs, String columnName) throws SQLException { + return parseJson(rs.getString(columnName)); + } + + @Override + public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + return parseJson(rs.getString(columnIndex)); + } + + @Override + public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + return parseJson(cs.getString(columnIndex)); + } + + /** + * 解析JSON字符串为List + */ + private List parseJson(String json) { + if (json == null || json.isEmpty() || "null".equals(json)) { + return new ArrayList<>(); + } + try { + List result = objectMapper.readValue(json, typeReference); + log.debug("反序列化String列表: {}", result); + return result; + } catch (JsonProcessingException e) { + log.error("反序列化String列表失败: {}", json, e); + return new ArrayList<>(); + } + } +} diff --git a/src/main/java/com/ycwl/basic/mapper/VideoReviewMapper.java b/src/main/java/com/ycwl/basic/mapper/VideoReviewMapper.java index 2339971b..647b4f94 100644 --- a/src/main/java/com/ycwl/basic/mapper/VideoReviewMapper.java +++ b/src/main/java/com/ycwl/basic/mapper/VideoReviewMapper.java @@ -1,6 +1,8 @@ package com.ycwl.basic.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.model.pc.videoreview.dto.AdminVideoReviewLogReqDTO; +import com.ycwl.basic.model.pc.videoreview.dto.AdminVideoReviewLogRespDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewListReqDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewRespDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewStatisticsRespDTO; @@ -25,6 +27,14 @@ public interface VideoReviewMapper extends BaseMapper { */ List selectReviewList(VideoReviewListReqDTO reqDTO); + /** + * 管理后台分页查询评价日志(带更详细的管理信息) + * + * @param reqDTO 查询条件 + * @return 评价日志列表 + */ + List selectAdminReviewLogList(AdminVideoReviewLogReqDTO reqDTO); + /** * 统计总评价数 * @@ -63,9 +73,9 @@ public interface VideoReviewMapper extends BaseMapper { List countScenicRank(@Param("limit") int limit); /** - * 查询所有机位评价数据(用于后端计算平均值) + * 查询所有问题机位ID列表(用于后端统计问题机位) * - * @return 机位评价列表(Map结构: 机位ID -> 评分) + * @return 问题机位ID列表 */ - List> selectAllCameraPositionRatings(); + List> selectAllProblemDeviceIds(); } diff --git a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/AdminVideoReviewLogReqDTO.java b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/AdminVideoReviewLogReqDTO.java new file mode 100644 index 00000000..4cf5b28b --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/AdminVideoReviewLogReqDTO.java @@ -0,0 +1,123 @@ +package com.ycwl.basic.model.pc.videoreview.dto; + +import lombok.Data; + +/** + * 管理后台视频评价日志查询请求DTO + */ +@Data +public class AdminVideoReviewLogReqDTO { + + /** + * 评价ID(可选,精确查询) + */ + private Long id; + + /** + * 视频ID(可选) + */ + private Long videoId; + + /** + * 景区ID(可选) + */ + private Long scenicId; + + /** + * 评价人ID(可选) + */ + private Long creator; + + /** + * 评价人名称(可选,模糊查询) + */ + private String creatorName; + + /** + * 评分(可选,精确匹配) + */ + private Integer rating; + + /** + * 最小评分(可选,范围查询) + */ + private Integer minRating; + + /** + * 最大评分(可选,范围查询) + */ + private Integer maxRating; + + /** + * 开始时间(可选,格式: yyyy-MM-dd HH:mm:ss) + */ + private String startTime; + + /** + * 结束时间(可选,格式: yyyy-MM-dd HH:mm:ss) + */ + private String endTime; + + /** + * 关键词搜索(可选,搜索评价内容、景区名称、模板名称) + */ + private String keyword; + + /** + * 模板ID(可选) + */ + private Long templateId; + + /** + * 模板名称(可选,模糊查询) + */ + private String templateName; + + /** + * 是否有机位评价(可选) + * true: 仅查询有机位评价的记录 + * false: 仅查询无机位评价的记录 + * null: 不限制 + */ + private Boolean hasCameraRating; + + /** + * 问题机位ID(可选,筛选包含该机位ID的评价) + * 任意一个问题机位匹配即可 + */ + private Long problemDeviceId; + + /** + * 问题标签(可选,筛选包含该标签的评价) + * 任意一个标签匹配即可 + */ + private String problemTag; + + /** + * 来源(可选,筛选指定来源的评价) + * 固定值: ORDER(订单), RENDER(渲染) + */ + private String source; + + /** + * 页码(必填,默认1) + */ + private Integer pageNum = 1; + + /** + * 每页数量(必填,默认20) + */ + private Integer pageSize = 20; + + /** + * 排序字段(可选,默认create_time) + * 可选值: create_time, rating, update_time, id + */ + private String orderBy = "create_time"; + + /** + * 排序方向(可选,默认DESC) + * 可选值: ASC, DESC + */ + private String orderDirection = "DESC"; +} diff --git a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/AdminVideoReviewLogRespDTO.java b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/AdminVideoReviewLogRespDTO.java new file mode 100644 index 00000000..49c9c015 --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/AdminVideoReviewLogRespDTO.java @@ -0,0 +1,120 @@ +package com.ycwl.basic.model.pc.videoreview.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * 管理后台视频评价日志响应DTO + */ +@Data +public class AdminVideoReviewLogRespDTO { + + /** + * 评价ID + */ + private Long id; + + /** + * 视频ID + */ + private Long videoId; + + /** + * 视频URL(关联查询) + */ + private String videoUrl; + + /** + * 模板ID(关联查询video表) + */ + private Long templateId; + + /** + * 模板名称(关联查询) + */ + private String templateName; + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 景区名称(关联查询) + */ + private String scenicName; + + /** + * 评价人ID(管理员ID) + */ + private Long creator; + + /** + * 评价人名称(关联查询) + */ + private String creatorName; + + /** + * 评价人账号(关联查询,管理后台显示) + */ + private String creatorAccount; + + /** + * 购买评分 1-5 + */ + private Integer rating; + + /** + * 文字评价内容 + */ + private String content; + + /** + * 有问题的机位ID列表 + * 格式: [12345, 12346, 12347] + */ + private List problemDeviceIds; + + /** + * 问题机位数量(方便前端展示) + */ + private Integer problemDeviceCount; + + /** + * 问题标签列表 + * 格式: ["画面模糊", "抖动严重", "色彩异常"] + */ + private List problemTags; + + /** + * 来源 + * 固定值: ORDER(订单), RENDER(渲染) + */ + private String source; + + /** + * 来源ID + * 用于溯源,关联订单ID或渲染任务ID等 + */ + private Long sourceId; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createTime; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date updateTime; + + /** + * 操作时长(创建到更新的时间差,单位:秒) + */ + private Long operationDuration; +} diff --git a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewAddReqDTO.java b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewAddReqDTO.java index 5beca3d8..74019af1 100644 --- a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewAddReqDTO.java +++ b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewAddReqDTO.java @@ -2,7 +2,7 @@ package com.ycwl.basic.model.pc.videoreview.dto; import lombok.Data; -import java.util.Map; +import java.util.List; /** * 新增视频评价请求DTO @@ -26,9 +26,28 @@ public class VideoReviewAddReqDTO { private String content; /** - * 机位评价JSON(可选) - * 格式: {"12345": 5, "12346": 4} - * key为机位ID,value为该机位的评分(1-5) + * 有问题的机位ID列表(可选) + * 格式: [12345, 12346, 12347] + * 选择有问题的机位ID */ - private Map cameraPositionRating; + private List problemDeviceIds; + + /** + * 问题标签列表(可选) + * 格式: ["画面模糊", "抖动严重", "色彩异常"] + * 可多选问题标签 + */ + private List problemTags; + + /** + * 来源(必填) + * 固定值: ORDER(订单), RENDER(渲染) + */ + private String source; + + /** + * 来源ID(可选) + * 用于溯源,关联订单ID或渲染任务ID等 + */ + private Long sourceId; } diff --git a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewListReqDTO.java b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewListReqDTO.java index 6dc0e6d2..b36923fd 100644 --- a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewListReqDTO.java +++ b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewListReqDTO.java @@ -53,6 +53,24 @@ public class VideoReviewListReqDTO { */ private String keyword; + /** + * 问题机位ID(可选,筛选包含该机位ID的评价) + * 任意一个问题机位匹配即可 + */ + private Long problemDeviceId; + + /** + * 问题标签(可选,筛选包含该标签的评价) + * 任意一个标签匹配即可 + */ + private String problemTag; + + /** + * 来源(可选,筛选指定来源的评价) + * 固定值: ORDER(订单), RENDER(渲染) + */ + private String source; + /** * 页码(必填,默认1) */ diff --git a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewRespDTO.java b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewRespDTO.java index d4f0470c..9ba5425f 100644 --- a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewRespDTO.java +++ b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewRespDTO.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.util.Date; -import java.util.Map; +import java.util.List; /** * 视频评价详情响应DTO @@ -68,11 +68,28 @@ public class VideoReviewRespDTO { private String content; /** - * 机位评价JSON - * 格式: {"12345": 5, "12346": 4} - * key为机位ID,value为该机位的评分(1-5) + * 有问题的机位ID列表 + * 格式: [12345, 12346, 12347] */ - private Map cameraPositionRating; + private List problemDeviceIds; + + /** + * 问题标签列表 + * 格式: ["画面模糊", "抖动严重", "色彩异常"] + */ + private List problemTags; + + /** + * 来源 + * 固定值: ORDER(订单), RENDER(渲染) + */ + private String source; + + /** + * 来源ID + * 用于溯源,关联订单ID或渲染任务ID等 + */ + private Long sourceId; /** * 创建时间 diff --git a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewStatisticsRespDTO.java b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewStatisticsRespDTO.java index e58d0e82..7a4c3e32 100644 --- a/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewStatisticsRespDTO.java +++ b/src/main/java/com/ycwl/basic/model/pc/videoreview/dto/VideoReviewStatisticsRespDTO.java @@ -40,10 +40,10 @@ public class VideoReviewStatisticsRespDTO { private List scenicRankList; /** - * 机位评价统计 - * key: 机位ID, value: 该机位的平均评分 + * 问题机位统计 + * key: 机位ID, value: 该机位被标记为问题的次数 */ - private Map cameraPositionAverage; + private Map problemDeviceStatistics; /** * 景区评价排行内部类 diff --git a/src/main/java/com/ycwl/basic/model/pc/videoreview/entity/VideoReviewEntity.java b/src/main/java/com/ycwl/basic/model/pc/videoreview/entity/VideoReviewEntity.java index b029492d..3fb22f9c 100644 --- a/src/main/java/com/ycwl/basic/model/pc/videoreview/entity/VideoReviewEntity.java +++ b/src/main/java/com/ycwl/basic/model/pc/videoreview/entity/VideoReviewEntity.java @@ -4,12 +4,13 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import com.ycwl.basic.handler.MapTypeHandler; +import com.ycwl.basic.handler.LongListTypeHandler; +import com.ycwl.basic.handler.StringListTypeHandler; import lombok.Data; import org.apache.ibatis.type.JdbcType; import java.util.Date; -import java.util.Map; +import java.util.List; /** * 视频评价实体类 @@ -50,12 +51,32 @@ public class VideoReviewEntity { private String content; /** - * 机位评价JSON - * 格式: {"12345": 5, "12346": 4} - * key为机位ID,value为该机位的评分(1-5) + * 有问题的机位ID列表 + * 格式: [12345, 12346, 12347] + * 存储被标记为有问题的机位ID */ - @TableField(typeHandler = MapTypeHandler.class, jdbcType = JdbcType.VARCHAR) - private Map cameraPositionRating; + @TableField(typeHandler = LongListTypeHandler.class, jdbcType = JdbcType.VARCHAR) + private List problemDeviceIds; + + /** + * 问题标签列表 + * 格式: ["画面模糊", "抖动严重", "色彩异常"] + * 存储视频或机位的问题标签,可多选 + */ + @TableField(typeHandler = StringListTypeHandler.class, jdbcType = JdbcType.VARCHAR) + private List problemTags; + + /** + * 来源 + * 固定值: ORDER(订单), RENDER(渲染) + */ + private String source; + + /** + * 来源ID + * 用于溯源,关联订单ID或渲染任务ID等 + */ + private Long sourceId; /** * 创建时间 diff --git a/src/main/java/com/ycwl/basic/service/VideoReviewService.java b/src/main/java/com/ycwl/basic/service/VideoReviewService.java index fd8b2852..3f98ba63 100644 --- a/src/main/java/com/ycwl/basic/service/VideoReviewService.java +++ b/src/main/java/com/ycwl/basic/service/VideoReviewService.java @@ -1,6 +1,8 @@ package com.ycwl.basic.service; import com.github.pagehelper.PageInfo; +import com.ycwl.basic.model.pc.videoreview.dto.AdminVideoReviewLogReqDTO; +import com.ycwl.basic.model.pc.videoreview.dto.AdminVideoReviewLogRespDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoPurchaseCheckReqDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoPurchaseCheckRespDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewAddReqDTO; @@ -32,6 +34,14 @@ public interface VideoReviewService { */ PageInfo getReviewList(VideoReviewListReqDTO reqDTO); + /** + * 管理后台分页查询评价日志 + * + * @param reqDTO 查询条件 + * @return 分页结果 + */ + PageInfo getAdminReviewLogList(AdminVideoReviewLogReqDTO reqDTO); + /** * 获取评价统计数据 * diff --git a/src/main/java/com/ycwl/basic/service/impl/VideoReviewServiceImpl.java b/src/main/java/com/ycwl/basic/service/impl/VideoReviewServiceImpl.java index 59b960f7..c6a0ffc9 100644 --- a/src/main/java/com/ycwl/basic/service/impl/VideoReviewServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/impl/VideoReviewServiceImpl.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.ycwl.basic.constant.BaseContextHandler; +import com.ycwl.basic.enums.VideoReviewSourceEnum; import com.ycwl.basic.exception.BaseException; import com.ycwl.basic.exception.BizException; import com.ycwl.basic.mapper.OrderMapper; @@ -14,6 +15,8 @@ import com.ycwl.basic.mapper.VideoReviewMapper; import com.ycwl.basic.model.pc.order.entity.OrderEntity; import com.ycwl.basic.model.pc.task.entity.TaskEntity; import com.ycwl.basic.model.pc.video.entity.VideoEntity; +import com.ycwl.basic.model.pc.videoreview.dto.AdminVideoReviewLogReqDTO; +import com.ycwl.basic.model.pc.videoreview.dto.AdminVideoReviewLogRespDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoPurchaseCheckReqDTO; import com.ycwl.basic.model.pc.videoreview.dto.VideoPurchaseCheckRespDTO; import com.ycwl.basic.repository.DeviceRepository; @@ -72,6 +75,12 @@ public class VideoReviewServiceImpl implements VideoReviewService { if (reqDTO.getRating() == null || reqDTO.getRating() < 1 || reqDTO.getRating() > 5) { throw new BaseException("评分必须在1-5之间"); } + if (reqDTO.getSource() == null || reqDTO.getSource().isEmpty()) { + throw new BaseException("来源不能为空"); + } + if (!VideoReviewSourceEnum.isValid(reqDTO.getSource())) { + throw new BaseException("来源值无效,仅支持: ORDER(订单), RENDER(渲染)"); + } // 2. 查询视频信息,获取景区ID VideoEntity video = videoMapper.getEntity(reqDTO.getVideoId()); @@ -93,12 +102,16 @@ public class VideoReviewServiceImpl implements VideoReviewService { entity.setCreator(creator); entity.setRating(reqDTO.getRating()); entity.setContent(reqDTO.getContent()); - entity.setCameraPositionRating(reqDTO.getCameraPositionRating()); + entity.setProblemDeviceIds(reqDTO.getProblemDeviceIds()); + entity.setProblemTags(reqDTO.getProblemTags()); + entity.setSource(reqDTO.getSource()); + entity.setSourceId(reqDTO.getSourceId()); // 5. 插入数据库 videoReviewMapper.insert(entity); - log.info("管理员[{}]对视频[{}]添加评价成功,评价ID: {}", creator, reqDTO.getVideoId(), entity.getId()); + log.info("管理员[{}]对视频[{}]添加评价成功,评价ID: {}, 来源: {}, 来源ID: {}", + creator, reqDTO.getVideoId(), entity.getId(), reqDTO.getSource(), reqDTO.getSourceId()); return entity.getId(); } @@ -114,6 +127,18 @@ public class VideoReviewServiceImpl implements VideoReviewService { return new PageInfo<>(list); } + @Override + public PageInfo getAdminReviewLogList(AdminVideoReviewLogReqDTO reqDTO) { + // 设置分页参数 + PageHelper.startPage(reqDTO.getPageNum(), reqDTO.getPageSize()); + + // 查询列表 + List list = videoReviewMapper.selectAdminReviewLogList(reqDTO); + + // 封装分页结果 + return new PageInfo<>(list); + } + @Override public VideoReviewStatisticsRespDTO getStatistics() { VideoReviewStatisticsRespDTO statistics = new VideoReviewStatisticsRespDTO(); @@ -154,9 +179,9 @@ public class VideoReviewServiceImpl implements VideoReviewService { List scenicRankList = videoReviewMapper.countScenicRank(10); statistics.setScenicRankList(scenicRankList); - // 6. 机位评价维度平均值 - Map cameraPositionAverage = calculateCameraPositionAverage(); - statistics.setCameraPositionAverage(cameraPositionAverage); + // 6. 问题机位统计 + Map problemDeviceStatistics = calculateProblemDeviceStatistics(); + statistics.setProblemDeviceStatistics(problemDeviceStatistics); return statistics; } @@ -168,37 +193,11 @@ public class VideoReviewServiceImpl implements VideoReviewService { reqDTO.setPageSize(Integer.MAX_VALUE); List list = videoReviewMapper.selectReviewList(reqDTO); - // 2. 收集所有机位ID并批量查询机位名称 - Set allDeviceIds = new LinkedHashSet<>(); - for (VideoReviewRespDTO review : list) { - Map cameraRating = review.getCameraPositionRating(); - if (cameraRating != null && !cameraRating.isEmpty()) { - // 收集机位ID (按顺序) - for (String deviceIdStr : cameraRating.keySet()) { - try { - allDeviceIds.add(Long.valueOf(deviceIdStr)); - } catch (NumberFormatException e) { - log.warn("无效的机位ID: {}", deviceIdStr); - } - } - } - } - - // 批量查询机位名称 - Map deviceNames = new HashMap<>(); - if (!allDeviceIds.isEmpty()) { - deviceNames = deviceRepository.batchGetDeviceNames(new ArrayList<>(allDeviceIds)); - } - - // 对机位ID按ID排序,保证表头顺序一致 - List sortedDeviceIds = new ArrayList<>(allDeviceIds); - sortedDeviceIds.sort(Long::compareTo); - - // 3. 创建Excel工作簿 + // 2. 创建Excel工作簿 Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("视频评价数据"); - // 4. 创建标题行样式 + // 3. 创建标题行样式 CellStyle headerStyle = workbook.createCellStyle(); Font headerFont = workbook.createFont(); headerFont.setBold(true); @@ -206,7 +205,7 @@ public class VideoReviewServiceImpl implements VideoReviewService { headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); - // 5. 生成动态表头 - 使用机位名称作为表头 + // 4. 生成表头 Row headerRow = sheet.createRow(0); List headerList = new ArrayList<>(); headerList.add("评价ID"); @@ -216,14 +215,8 @@ public class VideoReviewServiceImpl implements VideoReviewService { headerList.add("评价人名称"); headerList.add("评分"); headerList.add("文字评价"); - - // 添加机位列 - 表头直接使用机位名称 - Map finalDeviceNames = deviceNames; - for (Long deviceId : sortedDeviceIds) { - String deviceName = finalDeviceNames.getOrDefault(deviceId, "未知设备(ID:" + deviceId + ")"); - headerList.add(deviceName); - } - + headerList.add("问题机位ID列表"); + headerList.add("问题标签"); headerList.add("创建时间"); headerList.add("更新时间"); @@ -234,7 +227,7 @@ public class VideoReviewServiceImpl implements VideoReviewService { cell.setCellStyle(headerStyle); } - // 6. 填充数据 + // 5. 填充数据 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); int rowNum = 1; @@ -249,41 +242,37 @@ public class VideoReviewServiceImpl implements VideoReviewService { row.createCell(colIndex++).setCellValue(review.getScenicName()); row.createCell(colIndex++).setCellValue(review.getCreatorName()); row.createCell(colIndex++).setCellValue(review.getRating()); - row.createCell(colIndex++).setCellValue(review.getContent()); + row.createCell(colIndex++).setCellValue(review.getContent() != null ? review.getContent() : ""); - // 机位评价列 - 按表头顺序填充 - Map cameraRating = review.getCameraPositionRating(); - for (Long deviceId : sortedDeviceIds) { - String deviceIdStr = String.valueOf(deviceId); - Integer rating = null; + // 问题机位ID列表 + List problemDeviceIds = review.getProblemDeviceIds(); + String problemDeviceIdsStr = (problemDeviceIds != null && !problemDeviceIds.isEmpty()) + ? problemDeviceIds.toString() + : ""; + row.createCell(colIndex++).setCellValue(problemDeviceIdsStr); - if (cameraRating != null && cameraRating.containsKey(deviceIdStr)) { - rating = cameraRating.get(deviceIdStr); - } - - Cell cell = row.createCell(colIndex++); - if (rating != null) { - cell.setCellValue(rating); - } else { - cell.setCellValue(""); - } - } + // 问题标签 + List problemTags = review.getProblemTags(); + String problemTagsStr = (problemTags != null && !problemTags.isEmpty()) + ? String.join(", ", problemTags) + : ""; + row.createCell(colIndex++).setCellValue(problemTagsStr); // 时间列 row.createCell(colIndex++).setCellValue(review.getCreateTime() != null ? sdf.format(review.getCreateTime()) : ""); row.createCell(colIndex).setCellValue(review.getUpdateTime() != null ? sdf.format(review.getUpdateTime()) : ""); } - // 7. 自动调整列宽 + // 6. 自动调整列宽 for (int i = 0; i < headerList.size(); i++) { sheet.autoSizeColumn(i); } - // 8. 写入输出流 + // 7. 写入输出流 workbook.write(outputStream); workbook.close(); - log.info("导出视频评价数据成功,共{}条,机位数:{}", list.size(), sortedDeviceIds.size()); + log.info("导出视频评价数据成功,共{}条", list.size()); } @Override @@ -338,32 +327,25 @@ public class VideoReviewServiceImpl implements VideoReviewService { } /** - * 计算各机位的平均评分 + * 统计问题机位 + * 统计每个机位被标记为问题的次数 */ - private Map calculateCameraPositionAverage() { - List> allRatings = videoReviewMapper.selectAllCameraPositionRatings(); + private Map calculateProblemDeviceStatistics() { + List> allProblemDeviceIds = videoReviewMapper.selectAllProblemDeviceIds(); - if (allRatings == null || allRatings.isEmpty()) { + if (allProblemDeviceIds == null || allProblemDeviceIds.isEmpty()) { return new HashMap<>(); } - // 统计各机位的总分和次数 - Map> deviceScores = new HashMap<>(); - for (Map rating : allRatings) { - if (rating == null) continue; - // 遍历每个机位的评分 - for (Map.Entry entry : rating.entrySet()) { - deviceScores.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(entry.getValue()); + // 统计各机位被标记为问题的次数 + Map deviceProblemCount = new HashMap<>(); + for (List problemDeviceIds : allProblemDeviceIds) { + if (problemDeviceIds == null || problemDeviceIds.isEmpty()) continue; + for (Long deviceId : problemDeviceIds) { + deviceProblemCount.put(deviceId, deviceProblemCount.getOrDefault(deviceId, 0L) + 1); } } - // 计算平均值 - Map result = new HashMap<>(); - for (Map.Entry> entry : deviceScores.entrySet()) { - double avg = entry.getValue().stream().mapToInt(Integer::intValue).average().orElse(0.0); - result.put(entry.getKey(), BigDecimal.valueOf(avg).setScale(2, RoundingMode.HALF_UP)); - } - - return result; + return deviceProblemCount; } } diff --git a/src/main/resources/mapper/VideoReviewMapper.xml b/src/main/resources/mapper/VideoReviewMapper.xml index e056e690..a1f6bf49 100644 --- a/src/main/resources/mapper/VideoReviewMapper.xml +++ b/src/main/resources/mapper/VideoReviewMapper.xml @@ -15,8 +15,12 @@ - + + + + @@ -31,7 +35,10 @@ vr.creator, vr.rating, vr.content, - vr.camera_position_rating, + vr.problem_device_ids, + vr.problem_tags, + vr.source, + vr.source_id, vr.create_time, vr.update_time, v.video_url, @@ -72,6 +79,15 @@ AND vr.content LIKE CONCAT('%', #{keyword}, '%') + + AND JSON_CONTAINS(vr.problem_device_ids, CAST(#{problemDeviceId} AS CHAR), '$') + + + AND JSON_CONTAINS(vr.problem_tags, JSON_QUOTE(#{problemTag}), '$') + + + AND vr.source = #{source} + ORDER BY @@ -140,11 +156,156 @@ LIMIT #{limit} - - + SELECT problem_device_ids FROM video_review - WHERE camera_position_rating IS NOT NULL AND camera_position_rating != '' + WHERE problem_device_ids IS NOT NULL + AND problem_device_ids != '' + AND problem_device_ids != '[]' + + + + + + + + + + + + + + + + + + + + + + + + + + + +