diff --git a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/DictTypeConstants.java b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/DictTypeConstants.java index d04331d..0720a39 100644 --- a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/DictTypeConstants.java +++ b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/DictTypeConstants.java @@ -42,4 +42,9 @@ public interface DictTypeConstants { */ String RDMS_OVERTIME_DURATION = "rdms_overtime_duration"; + /** 意见反馈分类 */ + String FEEDBACK_TYPE = "feedback_type"; + /** 意见反馈处理状态 */ + String FEEDBACK_STATUS = "feedback_status"; + } diff --git a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java index 7be0862..34d70bd 100644 --- a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java +++ b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java @@ -99,6 +99,9 @@ public interface ErrorCodeConstants { // ========== 通知公告 1-002-008-000 ========== ErrorCode NOTICE_NOT_FOUND = new ErrorCode(1_002_008_001, "当前通知公告不存在"); + // ========== 用户意见反馈 1-002-009-000 ========== + ErrorCode FEEDBACK_NOT_FOUND = new ErrorCode(1_002_009_001, "用户意见反馈不存在"); + // ========= 文件相关 1-001-003-000 ================= ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在"); ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在"); diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/FeedbackController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/FeedbackController.java new file mode 100644 index 0000000..26403bb --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/FeedbackController.java @@ -0,0 +1,83 @@ +package com.njcn.rdms.module.system.controller.admin.feedback; + +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackPageReqVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackRespVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackSaveReqVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackStatRespVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackStatusReqVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackUpdateReqVO; +import com.njcn.rdms.module.system.service.feedback.FeedbackService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.njcn.rdms.framework.common.pojo.CommonResult.success; + +// 临时功能:意见反馈过测试阶段即下线。全部接口均「有意」不挂后端鉴权(仅过 token,需登录), +// 按钮可见性(仅 admin 可见)由前端控制;并非漏挂。如需恢复鉴权,给对应接口加回 +// @PreAuthorize("@ss.hasPermission('system:feedback:xxx')") 并配 system_menu 权限码 + 角色授权。 +@Tag(name = "管理后台 - 用户意见反馈") +@RestController +@RequestMapping("/system/feedback") +@Validated +public class FeedbackController { + + @Resource + private FeedbackService feedbackService; + + @PostMapping("/create") + @Operation(summary = "提交意见反馈") + public CommonResult createFeedback(@Valid @RequestBody FeedbackSaveReqVO reqVO) { + return success(feedbackService.createFeedback(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改意见反馈") + public CommonResult updateFeedback(@Valid @RequestBody FeedbackUpdateReqVO reqVO) { + feedbackService.updateFeedback(reqVO); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得意见反馈分页(全量)") + public CommonResult> getFeedbackPage(@Valid FeedbackPageReqVO pageReqVO) { + return success(feedbackService.getFeedbackPage(pageReqVO)); + } + + @GetMapping("/stat") + @Operation(summary = "意见反馈统计(全量:总数 + 按分类 / 按状态分组计数)") + public CommonResult getFeedbackStat() { + return success(feedbackService.getFeedbackStat()); + } + + @GetMapping("/get") + @Operation(summary = "获得意见反馈详情") + @Parameter(name = "id", description = "编号", required = true) + public CommonResult getFeedback(@RequestParam("id") Long id) { + return success(feedbackService.getFeedback(id)); + } + + @PutMapping("/{id}/status") + @Operation(summary = "修改意见反馈处理状态") + @Parameter(name = "id", description = "编号", required = true) + public CommonResult updateFeedbackStatus(@PathVariable("id") Long id, + @Valid @RequestBody FeedbackStatusReqVO reqVO) { + feedbackService.updateFeedbackStatus(id, reqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除意见反馈") + @Parameter(name = "id", description = "编号", required = true) + public CommonResult deleteFeedback(@RequestParam("id") Long id) { + feedbackService.deleteFeedback(id); + return success(true); + } + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackPageReqVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackPageReqVO.java new file mode 100644 index 0000000..3e51c03 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackPageReqVO.java @@ -0,0 +1,22 @@ +package com.njcn.rdms.module.system.controller.admin.feedback.vo; + +import com.njcn.rdms.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 用户意见反馈分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class FeedbackPageReqVO extends PageParam { + + @Schema(description = "反馈分类(字典 feedback_type)") + private Integer type; + + @Schema(description = "处理状态(字典 feedback_status)") + private Integer status; + + @Schema(description = "标题关键词") + private String title; + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackRespVO.java new file mode 100644 index 0000000..164cdee --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackRespVO.java @@ -0,0 +1,44 @@ +package com.njcn.rdms.module.system.controller.admin.feedback.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 用户意见反馈 Response VO") +@Data +public class FeedbackRespVO { + + @Schema(description = "主键 ID") + private Long id; + + @Schema(description = "反馈分类(字典 feedback_type)") + private Integer type; + + @Schema(description = "标题") + private String title; + + @Schema(description = "详细描述") + private String content; + + @Schema(description = "附件 URL 列表(JSON 数组字符串)") + private String attachmentUrls; + + @Schema(description = "联系方式") + private String contact; + + @Schema(description = "处理状态(字典 feedback_status)") + private Integer status; + + @Schema(description = "提交人 user id") + private String creator; + + @Schema(description = "提交人姓名(后端按 creator 翻译,查不到为空串)") + private String creatorName; + + @Schema(description = "提交时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 本字段按可读字符串返回前端;全局默认是 Long 时间戳,此处显式覆盖 + private LocalDateTime createTime; + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackSaveReqVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackSaveReqVO.java new file mode 100644 index 0000000..48eb267 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackSaveReqVO.java @@ -0,0 +1,30 @@ +package com.njcn.rdms.module.system.controller.admin.feedback.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 用户意见反馈提交 Request VO") +@Data +public class FeedbackSaveReqVO { + + @Schema(description = "反馈分类(字典 feedback_type)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "反馈分类不能为空") + private Integer type; + + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "导出按钮点了没反应") + @NotBlank(message = "标题不能为空") + private String title; + + @Schema(description = "详细描述", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "详细描述不能为空") + private String content; + + @Schema(description = "附件 URL 列表(JSON 数组字符串)", example = "[\"https://.../a.png\"]") + private String attachmentUrls; + + @Schema(description = "联系方式", example = "微信 xxx") + private String contact; + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackStatRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackStatRespVO.java new file mode 100644 index 0000000..2290fe9 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackStatRespVO.java @@ -0,0 +1,51 @@ +package com.njcn.rdms.module.system.controller.admin.feedback.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Schema(description = "管理后台 - 用户意见反馈统计 Response VO") +@Data +public class FeedbackStatRespVO { + + @Schema(description = "全部未软删反馈总数") + private Integer total; + + @Schema(description = "按分类分组的计数(覆盖字典 feedback_type 全部码值,无数据补 0;顺序按字典 sort)") + private List typeCounts; + + @Schema(description = "按处理状态分组的计数(覆盖字典 feedback_status 全部码值,无数据补 0;顺序按字典 sort)") + private List statusCounts; + + @Schema(description = "分类计数项") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class TypeCount { + + @Schema(description = "分类码值(字典 feedback_type)") + private Integer type; + + @Schema(description = "该分类下未软删反馈数") + private Integer count; + + } + + @Schema(description = "状态计数项") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class StatusCount { + + @Schema(description = "状态码值(字典 feedback_status)") + private Integer status; + + @Schema(description = "该状态下未软删反馈数") + private Integer count; + + } + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackStatusReqVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackStatusReqVO.java new file mode 100644 index 0000000..5d00521 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackStatusReqVO.java @@ -0,0 +1,15 @@ +package com.njcn.rdms.module.system.controller.admin.feedback.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 修改意见反馈处理状态 Request VO") +@Data +public class FeedbackStatusReqVO { + + @Schema(description = "目标状态(字典 feedback_status)", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") + @NotNull(message = "目标状态不能为空") + private Integer status; + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackUpdateReqVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackUpdateReqVO.java new file mode 100644 index 0000000..3890771 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/feedback/vo/FeedbackUpdateReqVO.java @@ -0,0 +1,17 @@ +package com.njcn.rdms.module.system.controller.admin.feedback.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 用户意见反馈更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class FeedbackUpdateReqVO extends FeedbackSaveReqVO { + + @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "反馈编号不能为空") + private Long id; + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/feedback/FeedbackDO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/feedback/FeedbackDO.java new file mode 100644 index 0000000..d6cf21f --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/feedback/FeedbackDO.java @@ -0,0 +1,36 @@ +package com.njcn.rdms.module.system.dal.dataobject.feedback; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户意见反馈 DO + */ +@TableName("system_feedback") +@Data +@EqualsAndHashCode(callSuper = true) +public class FeedbackDO extends BaseDO { + + @TableId + private Long id; + /** 反馈分类:字典 {@link com.njcn.rdms.module.system.enums.DictTypeConstants#FEEDBACK_TYPE}(1缺陷 2体验问题 3功能建议) */ + private Integer type; + /** 标题 */ + private String title; + /** 详细描述 */ + private String content; + /** 附件列表(JSON 数组字符串),可空。PUT 全量替换语义下前端传 null 需真正清空,故用 ALWAYS 跳过全局 NOT_NULL 策略(见 CLAUDE.md「接口语义」节) */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String attachmentUrls; + /** 联系方式,可空。同上:PUT 传 null 需落库清空 */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String contact; + /** 处理状态:字典 {@link com.njcn.rdms.module.system.enums.DictTypeConstants#FEEDBACK_STATUS}(1待处理 2处理中 3已处理 4已忽略) */ + private Integer status; + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/feedback/FeedbackMapper.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/feedback/FeedbackMapper.java new file mode 100644 index 0000000..fc393be --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/feedback/FeedbackMapper.java @@ -0,0 +1,21 @@ +package com.njcn.rdms.module.system.dal.mysql.feedback; + +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackPageReqVO; +import com.njcn.rdms.module.system.dal.dataobject.feedback.FeedbackDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface FeedbackMapper extends BaseMapperX { + + default PageResult selectPage(FeedbackPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(FeedbackDO::getType, reqVO.getType()) + .eqIfPresent(FeedbackDO::getStatus, reqVO.getStatus()) + .likeIfPresent(FeedbackDO::getTitle, reqVO.getTitle()) + .orderByDesc(FeedbackDO::getId)); + } + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/feedback/FeedbackService.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/feedback/FeedbackService.java new file mode 100644 index 0000000..b711d41 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/feedback/FeedbackService.java @@ -0,0 +1,37 @@ +package com.njcn.rdms.module.system.service.feedback; + +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackPageReqVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackRespVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackSaveReqVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackStatRespVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackStatusReqVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackUpdateReqVO; + +public interface FeedbackService { + + /** 提交意见反馈,返回新建 id */ + Long createFeedback(FeedbackSaveReqVO reqVO); + + /** 修改意见反馈内容(PUT 全量替换;不改 status/creator)。临时功能,后端不鉴权、仅过 token;编辑权限由前端按钮可见性控制 */ + void updateFeedback(FeedbackUpdateReqVO reqVO); + + /** 分页查询(全量,不按提交人过滤);creatorName 已按 creator 翻译为提交人姓名 */ + PageResult getFeedbackPage(FeedbackPageReqVO reqVO); + + /** 查询详情,不存在抛 FEEDBACK_NOT_FOUND;creatorName 已翻译 */ + FeedbackRespVO getFeedback(Long id); + + /** 修改处理状态(临时功能,后端不鉴权、仅过 token;按钮可见性由前端控制) */ + void updateFeedbackStatus(Long id, FeedbackStatusReqVO reqVO); + + /** 删除(临时功能,后端不鉴权、仅过 token;删除权限由前端按钮可见性控制) */ + void deleteFeedback(Long id); + + /** + * 全量统计:总数 + 按分类(feedback_type)/ 按状态(feedback_status)分组计数。 + * 排除软删;两个维度均按字典全集补零(无数据的码值 count=0),顺序按字典 sort。 + */ + FeedbackStatRespVO getFeedbackStat(); + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/feedback/FeedbackServiceImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/feedback/FeedbackServiceImpl.java new file mode 100644 index 0000000..d4d0ecd --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/feedback/FeedbackServiceImpl.java @@ -0,0 +1,179 @@ +package com.njcn.rdms.module.system.service.feedback; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.google.common.annotations.VisibleForTesting; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.common.util.object.BeanUtils; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackPageReqVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackRespVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackSaveReqVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackStatRespVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackStatusReqVO; +import com.njcn.rdms.module.system.controller.admin.feedback.vo.FeedbackUpdateReqVO; +import com.njcn.rdms.module.system.dal.dataobject.feedback.FeedbackDO; +import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO; +import com.njcn.rdms.module.system.dal.mysql.feedback.FeedbackMapper; +import com.njcn.rdms.module.system.enums.DictTypeConstants; +import com.njcn.rdms.module.system.service.dict.DictDataService; +import com.njcn.rdms.module.system.service.user.AdminUserService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.FEEDBACK_NOT_FOUND; + +/** + * 用户意见反馈 Service 实现类 + */ +@Service +public class FeedbackServiceImpl implements FeedbackService { + + @Resource + private FeedbackMapper feedbackMapper; + @Resource + private AdminUserService adminUserService; + @Resource + private DictDataService dictDataService; + + @Override + public Long createFeedback(FeedbackSaveReqVO reqVO) { + FeedbackDO feedback = BeanUtils.toBean(reqVO, FeedbackDO.class); + feedback.setStatus(1); // 默认「待处理」,不信任前端传入 + feedbackMapper.insert(feedback); + return feedback.getId(); + } + + @Override + public void updateFeedback(FeedbackUpdateReqVO reqVO) { + // 临时功能,后端不鉴权(仅过 token);编辑权限由前端按钮可见性控制 + validateFeedbackExists(reqVO.getId()); + // 只更新内容字段:toBean 不带 status/creator,updateById 不会动它们(status 走专用接口、creator 为审计字段); + // attachmentUrls/contact 传 null 也会落库清空(DO 上 FieldStrategy.ALWAYS) + FeedbackDO update = BeanUtils.toBean(reqVO, FeedbackDO.class); + feedbackMapper.updateById(update); + } + + @Override + public PageResult getFeedbackPage(FeedbackPageReqVO reqVO) { + PageResult pageResult = feedbackMapper.selectPage(reqVO); + return new PageResult<>(buildRespList(pageResult.getList()), pageResult.getTotal()); + } + + @Override + public FeedbackRespVO getFeedback(Long id) { + FeedbackDO feedback = validateFeedbackExists(id); + return buildRespList(Collections.singletonList(feedback)).get(0); + } + + @Override + public void updateFeedbackStatus(Long id, FeedbackStatusReqVO reqVO) { + validateFeedbackExists(id); + FeedbackDO update = new FeedbackDO(); + update.setId(id); + update.setStatus(reqVO.getStatus()); + feedbackMapper.updateById(update); + } + + @Override + public void deleteFeedback(Long id) { + // 临时功能,后端不鉴权(仅过 token);删除权限由前端按钮可见性控制 + validateFeedbackExists(id); + feedbackMapper.deleteById(id); + } + + @Override + public FeedbackStatRespVO getFeedbackStat() { + // 一次性全量加载(软删自动排除),内存按两个维度聚合:同一数据快照天然保证 total 与各维度计数之和一致 + List all = feedbackMapper.selectList(); + Map typeCountMap = all.stream() + .filter(item -> item.getType() != null) + .collect(Collectors.groupingBy(FeedbackDO::getType, Collectors.counting())); + Map statusCountMap = all.stream() + .filter(item -> item.getStatus() != null) + .collect(Collectors.groupingBy(FeedbackDO::getStatus, Collectors.counting())); + + FeedbackStatRespVO stat = new FeedbackStatRespVO(); + stat.setTotal(all.size()); + // 按字典全集补零:无数据的码值也返回 count=0,保证消费端结构稳定,无需自己补零 + stat.setTypeCounts(dictCodes(DictTypeConstants.FEEDBACK_TYPE).stream() + .map(code -> new FeedbackStatRespVO.TypeCount(code, countOf(typeCountMap, code))) + .collect(Collectors.toList())); + stat.setStatusCounts(dictCodes(DictTypeConstants.FEEDBACK_STATUS).stream() + .map(code -> new FeedbackStatRespVO.StatusCount(code, countOf(statusCountMap, code))) + .collect(Collectors.toList())); + return stat; + } + + /** 取字典某类型下全部码值(含停用项),顺序按字典 sort;非数字 value 跳过 */ + private List dictCodes(String dictType) { + return dictDataService.getDictDataListByDictType(dictType).stream() + .map(dict -> parseDictValue(dict.getValue())) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + } + + private Integer countOf(Map countMap, Integer code) { + return countMap.getOrDefault(code, 0L).intValue(); + } + + /** 字典 value 解析为整数码值;空 / 非数字返回 null,避免脏字典数据炸接口 */ + private Integer parseDictValue(String value) { + if (StrUtil.isBlank(value)) { + return null; + } + try { + return Integer.valueOf(value.trim()); + } catch (NumberFormatException e) { + return null; + } + } + + @VisibleForTesting + public FeedbackDO validateFeedbackExists(Long id) { + FeedbackDO feedback = feedbackMapper.selectById(id); + if (feedback == null) { + throw exception(FEEDBACK_NOT_FOUND); + } + return feedback; + } + + /** DO 列表转 RespVO,并把 creator(user id 字符串)批量翻译成提交人姓名,避免逐行查库 */ + private List buildRespList(List list) { + if (CollUtil.isEmpty(list)) { + return new ArrayList<>(); + } + Set userIds = list.stream() + .map(feedback -> parseUserId(feedback.getCreator())) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Map userMap = adminUserService.getUserMap(userIds); + return BeanUtils.toBean(list, FeedbackRespVO.class, resp -> { + Long uid = parseUserId(resp.getCreator()); + AdminUserDO user = uid == null ? null : userMap.get(uid); + resp.setCreatorName(user != null ? user.getNickname() : ""); + }); + } + + /** creator 是 String 形态的 user id,解析为 Long;空/非数字返回 null */ + private Long parseUserId(String creator) { + if (StrUtil.isBlank(creator)) { + return null; + } + try { + return Long.valueOf(creator.trim()); + } catch (NumberFormatException e) { + return null; + } + } + +}