feat(emergency): 急救宣教资源管理模块多项优化

- 新增版本历史isCurrent标记,编辑/回滚时正确维护当前版本
- 审核记录独立表(QH_EME_FIRST_AID_RESOURCE_AUDIT),保留完整审核轨迹
- 审计表auditStatus沿用10/11/12,资源表保留冗余快照
- 标签挂载到分类下(parentId),同分类内名称唯一
- 分类/标签编辑时校验name唯一性
- 导出状态改用文本(statusText),审核通过+已下架正确显示
- 审核状态/驳回原因支持null更新(FieldStrategy.IGNORED)
- Controller POST接口统一使用@RequestBody接收参数
This commit is contained in:
2026-06-24 17:36:44 +08:00
parent c6d12c9c68
commit 532041ff8d
31 changed files with 1033 additions and 112 deletions
@@ -32,6 +32,7 @@ public interface FirstAidConstants {
/** 已驳回 */
Integer AUDIT_STATUS_REJECTED = 12;
// ========== 发布状态 ==========
/** 草稿 */
Integer STATUS_DRAFT = 0;
@@ -0,0 +1,16 @@
package com.renkang.emergency.dto;
import lombok.Data;
/**
* 通用ID入参DTO
* 用于单参数POST操作接口(删除/审核/上下架/收藏/回滚等)
*
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Data
public class IdDTO {
private String id;
}
@@ -0,0 +1,24 @@
package com.renkang.emergency.dto;
import lombok.Data;
import java.util.List;
/**
* 批量推送入参DTO
*
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Data
public class PushBatchSendDTO {
/** 工单ID(必填) */
private String orderId;
/** 资源ID列表(方式一:指定ID) */
private List<String> resourceIds;
/** 筛选条件(方式二:按条件批量推送),与 resourceIds 二选一 */
private PushFilterDTO filterParams;
}
@@ -0,0 +1,19 @@
package com.renkang.emergency.dto;
import lombok.Data;
/**
* 推送资源筛选条件DTO
*
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Data
public class PushFilterDTO {
private String keyword;
private String categoryId;
private String contentType;
}
@@ -0,0 +1,17 @@
package com.renkang.emergency.dto;
import lombok.Data;
/**
* 审核驳回入参DTO
*
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Data
public class RejectDTO {
private String id;
private String reason;
}
@@ -0,0 +1,58 @@
package com.renkang.emergency.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
* @Description: 急救宣教推送记录
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Data
@TableName("QH_EME_FIRST_AID_PUSH_RECORD")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(title = "first_aid_push_record对象", description = "急救宣教推送记录")
public class FirstAidPushRecord implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
@Schema(title = "id")
private String id;
@Schema(title = "资源ID")
private String resourceId;
@Schema(title = "资源标题")
private String resourceTitle;
@Schema(title = "关联应急工单ID")
private String orderId;
@Schema(title = "推送目标用户ID")
private String targetUserId;
@Schema(title = "推送人")
private String pushBy;
@Schema(title = "推送人ID")
private String pushById;
@Schema(title = "推送时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date pushTime;
@Schema(title = "租户ID")
private String tenantId;
}
@@ -92,17 +92,23 @@ public class FirstAidResource implements Serializable {
/** 审核状态(null=无审核,10=待审核,11=已通过,12=已驳回) */
@Excel(name = "审核状态", width = 15, dicCode = "first_aid_audit_status")
@Schema(title = "审核状态(null=无审核,10=待审核,11=已通过,12=已驳回)")
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Integer auditStatus;
/** 驳回原因 */
@Excel(name = "驳回原因", width = 30)
@Schema(title = "驳回原因")
@TableField(updateStrategy = FieldStrategy.IGNORED)
private String auditRejectReason;
/** 审核人 */
@Schema(title = "审核人")
private String auditBy;
/** 审核人ID */
@Schema(title = "审核人ID")
private String auditById;
/** 审核时间 */
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@@ -174,4 +180,9 @@ public class FirstAidResource implements Serializable {
@TableField(exist = false)
@Schema(title = "复合状态(0=草稿,1=已发布,2=已下架,10=待审核,11=已通过,12=已驳回)")
private Integer combinedStatus;
/** 当前工单下是否已推送(非持久字段) */
@TableField(exist = false)
@Schema(title = "当前工单下是否已推送")
private Boolean pushed;
}
@@ -0,0 +1,72 @@
package com.renkang.emergency.entity;
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.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
* @Description: 急救宣教资源审核记录
* @Author: jeecg-boot
* @Date: 2026-06-24
* @Version: V1.0
*/
@Data
@TableName("QH_EME_FIRST_AID_RESOURCE_AUDIT")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(title = "first_aid_resource_audit对象", description = "急救宣教资源审核记录")
public class FirstAidResourceAudit implements Serializable {
private static final long serialVersionUID = 1L;
/** id */
@TableId(type = IdType.ASSIGN_ID)
@Schema(title = "id")
private String id;
/** 资源ID */
@Schema(title = "资源ID")
private String resourceId;
/** 审核状态(10=待审核, 11=已通过, 12=已驳回) */
@Schema(title = "审核状态")
private Integer auditStatus;
/** 驳回原因 */
@Schema(title = "驳回原因")
private String auditRejectReason;
/** 审核人 */
@Schema(title = "审核人")
private String auditBy;
/** 审核人ID */
@Schema(title = "审核人ID")
private String auditById;
/** 审核时间 */
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(title = "审核时间")
private Date auditTime;
/** 创建时间 */
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(title = "创建时间")
private Date createTime;
/** 资源标题(非持久字段,查询时填充) */
@TableField(exist = false)
@Schema(title = "资源标题")
private String resourceTitle;
}
@@ -58,4 +58,8 @@ public class FirstAidResourceVersion implements Serializable {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(title = "创建时间")
private java.util.Date createTime;
/** 是否当前版本(true=当前版本, false=历史快照) */
@Schema(title = "是否当前版本")
private Boolean isCurrent;
}
@@ -0,0 +1,30 @@
package com.renkang.emergency.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.renkang.emergency.dto.PushBatchSendDTO;
import com.renkang.emergency.dto.PushFilterDTO;
import com.renkang.emergency.entity.FirstAidResource;
/**
* @Description: 急救宣教推送Service
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
public interface IFirstAidPushService {
/**
* 查询可推送资源列表(已发布+已审核),并标记当前工单下是否已推送
*/
IPage<FirstAidResource> queryResourceList(String orderId, PushFilterDTO filter, Page<FirstAidResource> page);
/**
* 推送单条资源
*/
void send(String resourceId, String orderId);
/**
* 批量推送
*/
int batchSend(PushBatchSendDTO dto);
}
@@ -0,0 +1,39 @@
package com.renkang.emergency.vo;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
import java.util.Date;
/**
* @Description: 急救宣教资源-导出VO
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Data
public class FirstAidResourceExportVO {
@Excel(name = "标题", width = 30)
private String title;
@Excel(name = "摘要", width = 30)
private String summary;
@Excel(name = "分类", width = 15)
private String categoryName;
@Excel(name = "类型", width = 15, dicCode = "first_aid_content_type")
private String contentType;
@Excel(name = "标签", width = 30)
private String tagNames;
@Excel(name = "状态", width = 15)
private String statusText;
@Excel(name = "阅读/收藏", width = 15)
private String viewFavorite;
@Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}
@@ -0,0 +1,99 @@
package com.renkang.emergency.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.renkang.emergency.dto.PushBatchSendDTO;
import com.renkang.emergency.dto.PushFilterDTO;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.service.IFirstAidPushService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 急救宣教资源-推送管理
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Tag(name = "急救宣教资源-推送管理")
@RestController
@RequestMapping("/emergency/firstAid/push")
@Slf4j
public class FirstAidPushController {
@Autowired
private IFirstAidPushService pushService;
/**
* 可推送资源列表(已发布+已审核通过,含当前工单推送状态)
*/
@Operation(summary = "可推送资源列表", description = "查询已发布且审核通过、当前有效的资源,标记当前工单下是否已推送")
@GetMapping(value = "/resourceList")
public Result<IPage<FirstAidResource>> resourceList(
@RequestParam(name = "orderId", required = false) String orderId,
@RequestParam(name = "keyword", required = false) String keyword,
@RequestParam(name = "categoryId", required = false) String categoryId,
@RequestParam(name = "contentType", required = false) String contentType,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "20") Integer pageSize) {
PushFilterDTO filter = new PushFilterDTO();
filter.setKeyword(keyword);
filter.setCategoryId(categoryId);
filter.setContentType(contentType);
Page<FirstAidResource> page = new Page<>(pageNo, pageSize);
IPage<FirstAidResource> result = pushService.queryResourceList(orderId, filter, page);
return Result.OK(result);
}
/**
* 推送单条资源
*/
@AutoLog(value = "急救宣教资源-推送单条")
@Operation(summary = "推送单条资源", description = "将指定资源推送给工单关联的求助者")
@PostMapping(value = "/send")
public Result<String> send(@RequestBody Map<String, String> params) {
String resourceId = params.get("resourceId");
String orderId = params.get("orderId");
if (resourceId == null || resourceId.isEmpty()) {
return Result.error("资源ID不能为空");
}
if (orderId == null || orderId.isEmpty()) {
return Result.error("工单ID不能为空");
}
try {
pushService.send(resourceId, orderId);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
return Result.OK("推送成功");
}
/**
* 一键推送(批量)
*/
@AutoLog(value = "急救宣教资源-批量推送")
@Operation(summary = "一键推送", description = "批量推送资源,支持指定ID列表或按筛选条件全量推送")
@PostMapping(value = "/batchSend")
public Result<Map<String, Object>> batchSend(@RequestBody PushBatchSendDTO dto) {
if (dto.getOrderId() == null || dto.getOrderId().isEmpty()) {
return Result.error("工单ID不能为空");
}
try {
int count = pushService.batchSend(dto);
Map<String, Object> result = new HashMap<>();
result.put("count", count);
return Result.OK("已推送 " + count + "", result);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
}
}
@@ -1,10 +1,15 @@
package com.renkang.emergency.controller;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.renkang.emergency.constant.FirstAidConstants;
import com.renkang.emergency.dto.IdDTO;
import com.renkang.emergency.dto.RejectDTO;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.entity.FirstAidResourceAudit;
import com.renkang.emergency.service.IFirstAidResourceAuditService;
import com.renkang.emergency.service.IFirstAidResourceService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
@@ -14,6 +19,10 @@ import org.jeecg.common.aspect.annotation.AutoLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @Description: 急救宣教资源-审核管理
* @Author: jeecg-boot
@@ -29,6 +38,9 @@ public class FirstAidResourceAuditController {
@Autowired
private IFirstAidResourceService resourceService;
@Autowired
private IFirstAidResourceAuditService auditService;
/**
* 待审核列表(支持标题/分类/内容类型筛选)
*/
@@ -75,9 +87,9 @@ public class FirstAidResourceAuditController {
//@RequiresPermissions("emergency:firstAidAudit:approve")
@Operation(summary = "审核通过")
@PostMapping(value = "/approve")
public Result<String> approve(@RequestParam(name = "id", required = true) String id) {
public Result<String> approve(@RequestBody IdDTO dto) {
try {
resourceService.approve(id);
resourceService.approve(dto.getId());
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
@@ -91,13 +103,12 @@ public class FirstAidResourceAuditController {
//@RequiresPermissions("emergency:firstAidAudit:reject")
@Operation(summary = "审核驳回")
@PostMapping(value = "/reject")
public Result<String> reject(@RequestParam(name = "id", required = true) String id,
@RequestParam(name = "reason", required = true) String reason) {
if (StrUtil.isBlank(reason)) {
public Result<String> reject(@RequestBody RejectDTO dto) {
if (StrUtil.isBlank(dto.getReason())) {
return Result.error("驳回原因不能为空");
}
try {
resourceService.reject(id, reason);
resourceService.reject(dto.getId(), dto.getReason());
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
@@ -105,20 +116,29 @@ public class FirstAidResourceAuditController {
}
/**
* 审核记录列表(合并待审核/已通过/已驳回的全部审核操作日志
* 审核记录列表(从审计表查询,支持按审核动作和日期范围筛选
*/
@Operation(summary = "审核记录列表", description = "查询所有已审核的资源记录,支持按操作类型和日期范围筛选")
@Operation(summary = "审核记录列表", description = "查询审核记录,支持按审核状态和日期范围筛选")
@GetMapping(value = "/records")
public Result<IPage<FirstAidResource>> records(FirstAidResource entity,
public Result<IPage<FirstAidResourceAudit>> records(
@RequestParam(name = "resourceTitle", required = false) String resourceTitle,
@RequestParam(name = "auditStatus", required = false) Integer auditStatus,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(name = "startDate", required = false) String startDate,
@RequestParam(name = "endDate", required = false) String endDate) {
Page<FirstAidResource> page = new Page<>(pageNo, pageSize);
// 不传auditStatus时默认查所有已审核的(>=10),传了则按指定类型查
if (entity.getAuditStatus() == null) {
entity.setAuditStatus(10); // 查所有 >=10 的已审核记录: 10=待审核, 11=已通过, 12=已驳回
Page<FirstAidResourceAudit> page = new Page<>(pageNo, pageSize);
IPage<FirstAidResourceAudit> result = auditService.queryAuditRecords(page, resourceTitle, auditStatus, startDate, endDate);
// 批量填充资源标题
if (CollectionUtil.isNotEmpty(result.getRecords())) {
List<String> resourceIds = result.getRecords().stream()
.map(FirstAidResourceAudit::getResourceId)
.distinct()
.collect(Collectors.toList());
Map<String, String> titleMap = resourceService.listByIds(resourceIds).stream()
.collect(Collectors.toMap(FirstAidResource::getId, FirstAidResource::getTitle, (a, b) -> a));
result.getRecords().forEach(audit -> audit.setResourceTitle(titleMap.get(audit.getResourceId())));
}
return Result.OK(resourceService.queryAuditRecords(entity, page, startDate, endDate));
return Result.OK(result);
}
}
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.renkang.emergency.constant.FirstAidConstants;
import com.renkang.emergency.dto.IdDTO;
import com.renkang.emergency.entity.FirstAidResourceCategory;
import com.renkang.emergency.service.IFirstAidResourceCategoryService;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -76,6 +77,11 @@ public class FirstAidResourceCategoryController extends JeecgController<FirstAid
entity.setType(FirstAidConstants.TYPE_CATEGORY);
if (entity.getSortNo() == null) entity.setSortNo(0);
if (entity.getStatus() == null) entity.setStatus(FirstAidConstants.NORMAL);
if (categoryService.count(new QueryWrapper<FirstAidResourceCategory>()
.eq("name", entity.getName())
.eq("type", FirstAidConstants.TYPE_CATEGORY)) > 0) {
return Result.error("分类名称已存在");
}
categoryService.save(entity);
return Result.OK("添加成功!");
}
@@ -87,6 +93,12 @@ public class FirstAidResourceCategoryController extends JeecgController<FirstAid
public Result<String> edit(@RequestBody FirstAidResourceCategory entity) {
FirstAidResourceCategory old = categoryService.getById(entity.getId());
if (old == null) return Result.error("数据不存在");
if (categoryService.count(new QueryWrapper<FirstAidResourceCategory>()
.eq("name", entity.getName())
.eq("type", FirstAidConstants.TYPE_CATEGORY)
.ne("id", entity.getId())) > 0) {
return Result.error("分类名称已存在");
}
entity.setType(old.getType());
categoryService.updateById(entity);
return Result.OK("编辑成功!");
@@ -96,9 +108,9 @@ public class FirstAidResourceCategoryController extends JeecgController<FirstAid
@AutoLog(value = "急救宣教资源分类-删除")
//@RequiresPermissions("emergency:firstAidCategory:delete")
@PostMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id") String id) {
public Result<String> delete(@RequestBody IdDTO dto) {
try {
categoryService.deleteWithCheck(id);
categoryService.deleteWithCheck(dto.getId());
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
@@ -1,23 +1,33 @@
package com.renkang.emergency.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.renkang.emergency.dto.IdDTO;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.service.IFirstAidResourceService;
import com.renkang.emergency.vo.FirstAidResourceExportVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.global.GlobalUtils;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
@Tag(name = "急救宣教资源管理")
@@ -31,10 +41,25 @@ public class FirstAidResourceController extends JeecgController<FirstAidResource
@Operation(summary = "急救宣教资源-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<FirstAidResource>> queryPageList(FirstAidResource entity,
public Result<IPage<FirstAidResource>> queryPageList(
@RequestParam(name = "title", required = false) String title,
@RequestParam(name = "categoryId", required = false) String categoryId,
@RequestParam(name = "contentType", required = false) String contentType,
@RequestParam(name = "combinedStatus", required = false) Integer combinedStatus,
@RequestParam(name = "auditStatus", required = false) Integer auditStatus,
@RequestParam(name = "status", required = false) Integer status,
@RequestParam(name = "tags", required = false) String tags,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
FirstAidResource entity = new FirstAidResource();
entity.setTitle(title);
entity.setCategoryId(categoryId);
entity.setContentType(contentType);
entity.setCombinedStatus(combinedStatus);
entity.setAuditStatus(auditStatus);
entity.setStatus(status);
entity.setTags(tags);
Page<FirstAidResource> page = new Page<>(pageNo, pageSize);
String userId = GlobalUtils.getLoginUser().getId();
return Result.OK(resourceService.queryPageList(entity, page, userId));
@@ -65,8 +90,8 @@ public class FirstAidResourceController extends JeecgController<FirstAidResource
@Operation(summary = "急救宣教资源-删除")
@AutoLog(value = "急救宣教资源-删除")
@PostMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id") String id) {
resourceService.removeById(id);
public Result<String> delete(@RequestBody IdDTO dto) {
resourceService.removeById(dto.getId());
return Result.OK("删除成功!");
}
@@ -97,9 +122,9 @@ public class FirstAidResourceController extends JeecgController<FirstAidResource
@Operation(summary = "急救宣教资源-提交审核")
@AutoLog(value = "急救宣教资源-提交审核")
@PostMapping(value = "/submitForReview")
public Result<String> submitForReview(@RequestParam(name = "id") String id) {
public Result<String> submitForReview(@RequestBody IdDTO dto) {
try {
resourceService.submitForReview(id);
resourceService.submitForReview(dto.getId());
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
@@ -109,9 +134,9 @@ public class FirstAidResourceController extends JeecgController<FirstAidResource
@Operation(summary = "急救宣教资源-下架")
@AutoLog(value = "急救宣教资源-下架")
@PostMapping(value = "/takeDown")
public Result<String> takeDown(@RequestParam(name = "id") String id) {
public Result<String> takeDown(@RequestBody IdDTO dto) {
try {
resourceService.takeDown(id);
resourceService.takeDown(dto.getId());
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
@@ -121,9 +146,9 @@ public class FirstAidResourceController extends JeecgController<FirstAidResource
@Operation(summary = "急救宣教资源-上架")
@AutoLog(value = "急救宣教资源-上架")
@PostMapping(value = "/publish")
public Result<String> publish(@RequestParam(name = "id") String id) {
public Result<String> publish(@RequestBody IdDTO dto) {
try {
resourceService.publish(id);
resourceService.publish(dto.getId());
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
@@ -141,18 +166,18 @@ public class FirstAidResourceController extends JeecgController<FirstAidResource
@Operation(summary = "急救宣教资源-记录阅读")
@AutoLog(value = "急救宣教资源-记录阅读")
@PostMapping(value = "/incrementView")
public Result<String> incrementView(@RequestParam(name = "id") String id) {
public Result<String> incrementView(@RequestBody IdDTO dto) {
String userId = GlobalUtils.getLoginUser().getId();
boolean firstRead = resourceService.incrementViewCount(id, userId);
boolean firstRead = resourceService.incrementViewCount(dto.getId(), userId);
return firstRead ? Result.OK("阅读记录成功") : Result.OK("已阅读过");
}
@Operation(summary = "急救宣教资源-复制资源")
@AutoLog(value = "急救宣教资源-复制")
@PostMapping(value = "/copy")
public Result<FirstAidResource> copy(@RequestParam(name = "id") String id) {
public Result<FirstAidResource> copy(@RequestBody IdDTO dto) {
try {
return Result.OK("复制成功,已保存为草稿", resourceService.copyResource(id));
return Result.OK("复制成功,已保存为草稿", resourceService.copyResource(dto.getId()));
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
@@ -161,7 +186,25 @@ public class FirstAidResourceController extends JeecgController<FirstAidResource
@Operation(summary = "急救宣教资源-导出excel")
@GetMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, FirstAidResource entity) {
return super.exportXls(request, entity, FirstAidResource.class, "急救宣教资源");
QueryWrapper<FirstAidResource> queryWrapper = QueryGenerator.initQueryWrapper(entity, request.getParameterMap());
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String selections = request.getParameter("selections");
if (StrUtil.isNotBlank(selections)) {
queryWrapper.in("id", Arrays.asList(selections.split(",")));
}
queryWrapper.orderByDesc("create_time");
List<FirstAidResource> list = resourceService.list(queryWrapper);
List<FirstAidResourceExportVO> exportList = resourceService.convertToExportVO(list);
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
mv.addObject(NormalExcelConstants.FILE_NAME, "急救宣教资源");
ExportParams exportParams = new ExportParams("急救宣教资源报表", "导出人:" + sysUser.getRealname(), "急救宣教资源");
mv.addObject(NormalExcelConstants.PARAMS, exportParams);
mv.addObject(NormalExcelConstants.CLASS, FirstAidResourceExportVO.class);
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
return mv;
}
@Operation(summary = "急救宣教资源-导入excel")
@@ -3,6 +3,7 @@ package com.renkang.emergency.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.renkang.emergency.dto.IdDTO;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.service.IFirstAidResourceUserActionService;
import io.swagger.v3.oas.annotations.Operation;
@@ -38,12 +39,12 @@ public class FirstAidResourceFavoriteController {
@AutoLog(value = "急救宣教资源-切换收藏")
@Operation(summary = "切换收藏状态", description = "已收藏则取消,未收藏则收藏")
@PostMapping(value = "/toggle")
public Result<Map<String, Object>> toggle(@RequestParam(name = "resourceId", required = true) String resourceId) {
if (StrUtil.isBlank(resourceId)) {
public Result<Map<String, Object>> toggle(@RequestBody IdDTO dto) {
if (StrUtil.isBlank(dto.getId())) {
return Result.error("资源ID不能为空");
}
String userId = GlobalUtils.getLoginUser().getId();
boolean favorited = userActionService.toggleFavorite(resourceId, userId);
boolean favorited = userActionService.toggleFavorite(dto.getId(), userId);
Map<String, Object> result = new HashMap<>();
result.put("favorited", favorited);
@@ -1,9 +1,11 @@
package com.renkang.emergency.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.renkang.emergency.constant.FirstAidConstants;
import com.renkang.emergency.dto.IdDTO;
import com.renkang.emergency.entity.FirstAidResourceCategory;
import com.renkang.emergency.service.IFirstAidResourceCategoryService;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -42,10 +44,11 @@ public class FirstAidResourceTagController extends JeecgController<FirstAidResou
return Result.OK(categoryService.page(page, queryWrapper));
}
@Operation(summary = "急救宣教资源标签-全部列表")
@Operation(summary = "急救宣教资源标签-全部列表(可按分类筛选)")
@GetMapping(value = "/listAll")
public Result<List<FirstAidResourceCategory>> listAll() {
return Result.OK(categoryService.listTags());
public Result<List<FirstAidResourceCategory>> listAll(
@RequestParam(name = "parentId", required = false) String parentId) {
return Result.OK(categoryService.listTags(parentId));
}
@Operation(summary = "急救宣教资源标签-添加")
@@ -56,6 +59,23 @@ public class FirstAidResourceTagController extends JeecgController<FirstAidResou
entity.setType(FirstAidConstants.TYPE_TAG);
if (entity.getSortNo() == null) entity.setSortNo(0);
if (entity.getStatus() == null) entity.setStatus(FirstAidConstants.NORMAL);
// 校验所属分类是否存在
if (StrUtil.isNotBlank(entity.getParentId())) {
FirstAidResourceCategory parent = categoryService.getById(entity.getParentId());
if (parent == null || !FirstAidConstants.TYPE_CATEGORY.equals(parent.getType())) {
return Result.error("所属分类不存在");
}
}
// 同分类下标签名称不能重复
QueryWrapper<FirstAidResourceCategory> checkWrapper = new QueryWrapper<FirstAidResourceCategory>()
.eq("name", entity.getName())
.eq("type", FirstAidConstants.TYPE_TAG);
if (StrUtil.isNotBlank(entity.getParentId())) {
checkWrapper.eq("parent_id", entity.getParentId());
}
if (categoryService.count(checkWrapper) > 0) {
return Result.error("标签名称已存在");
}
categoryService.save(entity);
return Result.OK("添加成功!");
}
@@ -68,6 +88,24 @@ public class FirstAidResourceTagController extends JeecgController<FirstAidResou
FirstAidResourceCategory old = categoryService.getById(entity.getId());
if (old == null) return Result.error("数据不存在");
if (!FirstAidConstants.TYPE_TAG.equals(old.getType())) return Result.error("该记录不是标签");
// 校验所属分类是否存在
if (StrUtil.isNotBlank(entity.getParentId())) {
FirstAidResourceCategory parent = categoryService.getById(entity.getParentId());
if (parent == null || !FirstAidConstants.TYPE_CATEGORY.equals(parent.getType())) {
return Result.error("所属分类不存在");
}
}
// 同分类下标签名称不能重复(排除自身)
QueryWrapper<FirstAidResourceCategory> checkWrapper = new QueryWrapper<FirstAidResourceCategory>()
.eq("name", entity.getName())
.eq("type", FirstAidConstants.TYPE_TAG)
.ne("id", entity.getId());
if (StrUtil.isNotBlank(entity.getParentId())) {
checkWrapper.eq("parent_id", entity.getParentId());
}
if (categoryService.count(checkWrapper) > 0) {
return Result.error("标签名称已存在");
}
entity.setType(old.getType());
categoryService.updateById(entity);
return Result.OK("编辑成功!");
@@ -77,9 +115,9 @@ public class FirstAidResourceTagController extends JeecgController<FirstAidResou
@AutoLog(value = "急救宣教资源标签-删除")
//@RequiresPermissions("emergency:firstAidTag:delete")
@PostMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id") String id) {
public Result<String> delete(@RequestBody IdDTO dto) {
try {
categoryService.deleteWithCheck(id);
categoryService.deleteWithCheck(dto.getId());
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
@@ -105,7 +143,13 @@ public class FirstAidResourceTagController extends JeecgController<FirstAidResou
@GetMapping(value = "/queryById")
public Result<FirstAidResourceCategory> queryById(@RequestParam(name = "id") String id) {
FirstAidResourceCategory entity = categoryService.getById(id);
return entity == null ? Result.error("未找到对应数据") : Result.OK(entity);
if (entity == null) return Result.error("未找到对应数据");
// 填充所属分类名称
if (StrUtil.isNotBlank(entity.getParentId())) {
FirstAidResourceCategory parent = categoryService.getById(entity.getParentId());
if (parent != null) entity.setParentName(parent.getName());
}
return Result.OK(entity);
}
@Operation(summary = "急救宣教资源标签-导出excel")
@@ -1,5 +1,6 @@
package com.renkang.emergency.controller;
import com.renkang.emergency.dto.IdDTO;
import com.renkang.emergency.entity.FirstAidResourceVersion;
import com.renkang.emergency.service.IFirstAidResourceVersionService;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -46,9 +47,9 @@ public class FirstAidResourceVersionController {
//@RequiresPermissions("emergency:firstAidResource:edit")
@Operation(summary = "回滚到指定版本", description = "将指定版本的内容恢复到资源表")
@PostMapping(value = "/rollback")
public Result<String> rollback(@RequestParam(name = "versionId", required = true) String versionId) {
public Result<String> rollback(@RequestBody IdDTO dto) {
try {
versionService.rollback(versionId);
versionService.rollback(dto.getId());
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
@@ -0,0 +1,12 @@
package com.renkang.emergency.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.renkang.emergency.entity.FirstAidPushRecord;
/**
* @Description: 急救宣教推送记录Mapper
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
public interface FirstAidPushRecordMapper extends BaseMapper<FirstAidPushRecord> {
}
@@ -0,0 +1,14 @@
package com.renkang.emergency.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.renkang.emergency.entity.FirstAidResourceAudit;
import org.apache.ibatis.annotations.Mapper;
/**
* @Description: 急救宣教资源审核记录
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Mapper
public interface FirstAidResourceAuditMapper extends BaseMapper<FirstAidResourceAudit> {
}
@@ -41,6 +41,16 @@ public interface FirstAidResourceCategoryMapper extends BaseMapper<FirstAidResou
@Select("SELECT * FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE TYPE = #{type} AND STATUS = 1 AND DEL_FLAG = 0 ORDER BY SORT_NO ASC, CREATE_TIME DESC")
List<FirstAidResourceCategory> selectByType(@Param("type") Integer type);
/**
* 按类型+父分类查询标签列表
*/
@Select("<script>" +
"SELECT * FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE TYPE = #{type} AND STATUS = 1 AND DEL_FLAG = 0" +
"<if test='parentId != null and parentId != \"\"'> AND PARENT_ID = #{parentId}</if>" +
" ORDER BY SORT_NO ASC, CREATE_TIME DESC" +
"</script>")
List<FirstAidResourceCategory> selectByTypeAndParent(@Param("type") Integer type, @Param("parentId") String parentId);
/**
* 按父分类ID查询子分类列表(树形结构用)
*/
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.renkang.emergency.mapper.FirstAidResourceAuditMapper">
</mapper>
@@ -0,0 +1,28 @@
package com.renkang.emergency.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.renkang.emergency.entity.FirstAidResourceAudit;
/**
* @Description: 急救宣教资源审核记录
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
public interface IFirstAidResourceAuditService extends IService<FirstAidResourceAudit> {
/**
* 保存审核记录
*/
void saveAudit(String resourceId, Integer auditStatus, String rejectReason);
/**
* 查询审核记录分页
*/
IPage<FirstAidResourceAudit> queryAuditRecords(Page<FirstAidResourceAudit> page,
String resourceTitle,
Integer auditStatus,
String startDate,
String endDate);
}
@@ -29,9 +29,9 @@ public interface IFirstAidResourceCategoryService extends IService<FirstAidResou
List<FirstAidResourceCategory> listChildrenByParentId(String parentId);
/**
* 查询所有有效标签列表
* 查询所有有效标签列表(可按分类筛选)
*/
List<FirstAidResourceCategory> listTags();
List<FirstAidResourceCategory> listTags(String parentId);
/**
* 删除前校验(分类下有子分类/资源则拒绝;标签有资源引用则拒绝)
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.vo.FirstAidResourceExportVO;
import java.util.List;
@@ -44,10 +45,10 @@ public interface IFirstAidResourceService extends IService<FirstAidResource> {
IPage<FirstAidResource> queryPageList(FirstAidResource resource, Page<FirstAidResource> page, String userId);
/** 审核记录分页列表(支持时间范围筛选) */
IPage<FirstAidResource> queryAuditRecords(FirstAidResource resource, Page<FirstAidResource> page, String startDate, String endDate);
boolean incrementViewCount(String id, String userId);
FirstAidResource getDetail(String id, String userId);
/** 批量转换实体为导出VO(含分类、标签翻译) */
List<FirstAidResourceExportVO> convertToExportVO(List<FirstAidResource> resources);
}
@@ -15,14 +15,20 @@ public interface IFirstAidResourceVersionService extends IService<FirstAidResour
/**
* 保存版本快照
* @param isCurrent 是否标记为当前版本
*/
void saveVersion(String resourceId, String title, String content, String changeDescription);
void saveVersion(String resourceId, String title, String content, String changeDescription, boolean isCurrent);
/**
* 查询某资源的所有版本
*/
List<FirstAidResourceVersion> listVersions(String resourceId);
/**
* 清除指定资源所有版本的当前标记(内容已变更,不再匹配任何版本)
*/
void clearCurrent(String resourceId);
/**
* 回滚到指定版本(将版本内容恢复到资源表)
*/
@@ -0,0 +1,202 @@
package com.renkang.emergency.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.renkang.emergency.constant.FirstAidConstants;
import com.renkang.emergency.dto.PushBatchSendDTO;
import com.renkang.emergency.dto.PushFilterDTO;
import com.renkang.emergency.entity.FirstAidPushRecord;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.entity.FirstAidResourceCategory;
import com.renkang.emergency.entity.Order;
import com.renkang.emergency.mapper.FirstAidPushRecordMapper;
import com.renkang.emergency.mapper.FirstAidResourceMapper;
import com.renkang.emergency.mapper.FirstAidResourceCategoryMapper;
import com.renkang.emergency.mapper.OrderMapper;
import com.renkang.emergency.service.IFirstAidPushService;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.global.GlobalUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
/**
* @Description: 急救宣教推送Service实现
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Slf4j
@Service
public class FirstAidPushServiceImpl implements IFirstAidPushService {
@Autowired
private FirstAidResourceMapper resourceMapper;
@Autowired
private FirstAidPushRecordMapper pushRecordMapper;
@Autowired
private FirstAidResourceCategoryMapper categoryMapper;
@Autowired
private OrderMapper orderMapper;
@Lazy
@Autowired
private FirstAidPushServiceImpl self;
@Override
public IPage<FirstAidResource> queryResourceList(String orderId, PushFilterDTO filter, Page<FirstAidResource> page) {
LambdaQueryWrapper<FirstAidResource> wrapper = buildFilterWrapper(filter);
wrapper.orderByDesc(FirstAidResource::getCreateTime);
IPage<FirstAidResource> result = resourceMapper.selectPage(page, wrapper);
// 查询当前工单已推送的资源ID集合
Set<String> pushedIds = getPushedResourceIds(orderId);
// 填充推送状态 + 分类名称
for (FirstAidResource resource : result.getRecords()) {
resource.setPushed(pushedIds.contains(resource.getId()));
if (StrUtil.isNotBlank(resource.getCategoryId())) {
FirstAidResourceCategory category = categoryMapper.selectById(resource.getCategoryId());
if (category != null) {
resource.setCategoryName(category.getName());
}
}
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void send(String resourceId, String orderId) {
// 1. 校验资源
FirstAidResource resource = resourceMapper.selectById(resourceId);
if (resource == null) {
throw new RuntimeException("资源不存在");
}
if (!FirstAidConstants.STATUS_PUBLISHED.equals(resource.getStatus())
|| !FirstAidConstants.AUDIT_STATUS_APPROVED.equals(resource.getAuditStatus())) {
throw new RuntimeException("仅已发布且审核通过的资源可推送");
}
// 2. 查重
LambdaQueryWrapper<FirstAidPushRecord> existWrapper = new LambdaQueryWrapper<>();
existWrapper.eq(FirstAidPushRecord::getOrderId, orderId)
.eq(FirstAidPushRecord::getResourceId, resourceId);
if (pushRecordMapper.selectCount(existWrapper) > 0) {
throw new RuntimeException("该资源已推送,请勿重复操作");
}
// 3. 获取推送目标
String targetUserId = null;
if (StrUtil.isNotBlank(orderId)) {
Order order = orderMapper.selectById(orderId);
if (order != null) {
targetUserId = order.getSalvageUserId();
}
}
// 4. 保存推送记录
FirstAidPushRecord record = new FirstAidPushRecord();
record.setResourceId(resourceId);
record.setResourceTitle(resource.getTitle());
record.setOrderId(orderId);
record.setTargetUserId(targetUserId);
record.setPushBy(GlobalUtils.getLoginUser().getRealname());
record.setPushById(GlobalUtils.getLoginUser().getId());
record.setPushTime(new Date());
pushRecordMapper.insert(record);
// 5. 推送通知(预留,当前记录日志)
log.info("急救宣教推送: resourceId={}, title={}, orderId={}, targetUserId={}, pushById={}, pushByName={}",
resourceId, resource.getTitle(), orderId, targetUserId,
GlobalUtils.getLoginUser().getId(), GlobalUtils.getLoginUser().getRealname());
}
@Override
@Transactional(rollbackFor = Exception.class)
public int batchSend(PushBatchSendDTO dto) {
List<String> resourceIds = dto.getResourceIds();
// 方式二:按筛选条件查询
if (CollectionUtil.isEmpty(resourceIds) && dto.getFilterParams() != null) {
LambdaQueryWrapper<FirstAidResource> wrapper = buildFilterWrapper(dto.getFilterParams());
List<FirstAidResource> resources = resourceMapper.selectList(wrapper);
resourceIds = resources.stream().map(FirstAidResource::getId).collect(Collectors.toList());
}
if (CollectionUtil.isEmpty(resourceIds)) {
throw new RuntimeException("无可推送的资源");
}
int successCount = 0;
List<String> errors = new ArrayList<>();
for (String resourceId : resourceIds) {
try {
// 通过 self 代理调用,确保每条 send 拥有独立事务
self.send(resourceId, dto.getOrderId());
successCount++;
} catch (RuntimeException e) {
errors.add(resourceId + "" + e.getMessage());
}
}
if (!errors.isEmpty()) {
log.warn("批量推送部分失败: {}/{} 条, 失败原因: {}",
successCount, resourceIds.size(), String.join("; ", errors));
}
return successCount;
}
private Set<String> getPushedResourceIds(String orderId) {
LambdaQueryWrapper<FirstAidPushRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FirstAidPushRecord::getOrderId, orderId)
.select(FirstAidPushRecord::getResourceId);
List<FirstAidPushRecord> records = pushRecordMapper.selectList(wrapper);
if (CollectionUtil.isEmpty(records)) {
return Collections.emptySet();
}
return records.stream().map(FirstAidPushRecord::getResourceId).collect(Collectors.toSet());
}
private LambdaQueryWrapper<FirstAidResource> buildFilterWrapper(PushFilterDTO filter) {
LambdaQueryWrapper<FirstAidResource> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FirstAidResource::getStatus, FirstAidConstants.STATUS_PUBLISHED)
.eq(FirstAidResource::getAuditStatus, FirstAidConstants.AUDIT_STATUS_APPROVED)
.eq(FirstAidResource::getDelFlag, CommonConstant.DEL_FLAG_0);
// 仅返回当前有效资源:已生效(null视为立即生效) 且 未过期(null视为永不过期)
Date now = new Date();
wrapper.and(w -> w.isNull(FirstAidResource::getEffectiveTime)
.or().le(FirstAidResource::getEffectiveTime, now));
wrapper.and(w -> w.isNull(FirstAidResource::getExpiryTime)
.or().ge(FirstAidResource::getExpiryTime, now));
if (filter != null) {
if (StrUtil.isNotBlank(filter.getKeyword())) {
wrapper.and(w -> w.like(FirstAidResource::getTitle, filter.getKeyword())
.or().like(FirstAidResource::getContent, filter.getKeyword()));
}
if (StrUtil.isNotBlank(filter.getCategoryId())) {
wrapper.eq(FirstAidResource::getCategoryId, filter.getCategoryId());
}
if (StrUtil.isNotBlank(filter.getContentType())) {
wrapper.eq(FirstAidResource::getContentType, filter.getContentType());
}
}
return wrapper;
}
}
@@ -0,0 +1,82 @@
package com.renkang.emergency.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.entity.FirstAidResourceAudit;
import com.renkang.emergency.mapper.FirstAidResourceAuditMapper;
import com.renkang.emergency.mapper.FirstAidResourceMapper;
import com.renkang.emergency.service.IFirstAidResourceAuditService;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.global.GlobalUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Description: 急救宣教资源审核记录
* @Author: jeecg-boot
* @Date: 2026-06-24
*/
@Service
public class FirstAidResourceAuditServiceImpl extends ServiceImpl<FirstAidResourceAuditMapper, FirstAidResourceAudit> implements IFirstAidResourceAuditService {
@Autowired
private FirstAidResourceMapper resourceMapper;
@Override
public void saveAudit(String resourceId, Integer auditStatus, String rejectReason) {
FirstAidResourceAudit audit = new FirstAidResourceAudit();
audit.setResourceId(resourceId);
audit.setAuditStatus(auditStatus);
audit.setAuditRejectReason(rejectReason);
audit.setAuditBy(GlobalUtils.getLoginUser().getRealname());
audit.setAuditById(GlobalUtils.getLoginUser().getId());
audit.setAuditTime(new Date());
save(audit);
}
@Override
public IPage<FirstAidResourceAudit> queryAuditRecords(Page<FirstAidResourceAudit> page,
String resourceTitle,
Integer auditStatus,
String startDate,
String endDate) {
LambdaQueryWrapper<FirstAidResourceAudit> queryWrapper = new LambdaQueryWrapper<>();
// 按资源标题筛选:先查匹配的资源ID,再过滤审计记录
if (StrUtil.isNotBlank(resourceTitle)) {
LambdaQueryWrapper<FirstAidResource> resourceWrapper = new LambdaQueryWrapper<>();
resourceWrapper.like(FirstAidResource::getTitle, resourceTitle);
resourceWrapper.select(FirstAidResource::getId);
List<String> matchedIds = resourceMapper.selectList(resourceWrapper).stream()
.map(FirstAidResource::getId)
.collect(Collectors.toList());
if (CollectionUtil.isEmpty(matchedIds)) {
return page;
}
queryWrapper.in(FirstAidResourceAudit::getResourceId, matchedIds);
}
if (auditStatus != null) {
queryWrapper.eq(FirstAidResourceAudit::getAuditStatus, auditStatus);
}
if (StrUtil.isNotBlank(startDate)) {
queryWrapper.ge(FirstAidResourceAudit::getAuditTime, startDate + " 00:00:00");
}
if (StrUtil.isNotBlank(endDate)) {
queryWrapper.le(FirstAidResourceAudit::getAuditTime, endDate + " 23:59:59");
}
queryWrapper.orderByDesc(FirstAidResourceAudit::getAuditTime);
return page(page, queryWrapper);
}
}
@@ -48,8 +48,8 @@ public class FirstAidResourceCategoryServiceImpl extends ServiceImpl<FirstAidRes
}
@Override
public List<FirstAidResourceCategory> listTags() {
List<FirstAidResourceCategory> list = categoryMapper.selectByType(FirstAidConstants.TYPE_TAG);
public List<FirstAidResourceCategory> listTags(String parentId) {
List<FirstAidResourceCategory> list = categoryMapper.selectByTypeAndParent(FirstAidConstants.TYPE_TAG, parentId);
for (FirstAidResourceCategory tag : list) {
Integer count = categoryMapper.countResourcesByTagId(tag.getId());
tag.setResourceCount(count != null ? count : 0);
@@ -12,8 +12,10 @@ import com.renkang.emergency.entity.FirstAidResourceCategory;
import com.renkang.emergency.mapper.FirstAidResourceCategoryMapper;
import com.renkang.emergency.mapper.FirstAidResourceMapper;
import com.renkang.emergency.service.IFirstAidResourceService;
import com.renkang.emergency.service.IFirstAidResourceAuditService;
import com.renkang.emergency.service.IFirstAidResourceUserActionService;
import com.renkang.emergency.service.IFirstAidResourceVersionService;
import com.renkang.emergency.vo.FirstAidResourceExportVO;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.global.GlobalUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -47,6 +49,9 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
@Autowired
private IFirstAidResourceVersionService versionService;
@Autowired
private IFirstAidResourceAuditService auditService;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveResource(FirstAidResource resource) {
@@ -59,6 +64,8 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
resource.setStatus(FirstAidConstants.STATUS_DRAFT);
resource.setAuditStatus(null);
save(resource);
// 保存初始版本,标记为当前版本
versionService.saveVersion(resource.getId(), resource.getTitle(), resource.getContent(), "初始版本", true);
}
@Override
@@ -68,12 +75,15 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
if (old == null) {
throw new RuntimeException("资源不存在");
}
versionService.saveVersion(old.getId(), old.getTitle(), old.getContent(), "编辑更新");
// 保存旧内容到版本历史
versionService.saveVersion(old.getId(), old.getTitle(), old.getContent(), "编辑前版本", false);
// 编辑后自动退回草稿,清除审核状态(方案A)
resource.setStatus(FirstAidConstants.STATUS_DRAFT);
resource.setAuditStatus(null);
resource.setAuditRejectReason(null);
updateById(resource);
// 保存编辑后的新内容为当前版本
versionService.saveVersion(resource.getId(), resource.getTitle(), resource.getContent(), "编辑更新", true);
}
@Override
@@ -135,8 +145,11 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_APPROVED);
resource.setAuditRejectReason(null);
resource.setAuditBy(GlobalUtils.getLoginUser().getRealname());
resource.setAuditById(GlobalUtils.getLoginUser().getId());
resource.setAuditTime(new Date());
updateById(resource);
// 记录审核日志(直接发布视为审核通过)
auditService.saveAudit(id, FirstAidConstants.AUDIT_STATUS_APPROVED, null);
}
@Override
@@ -150,8 +163,11 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
resource.setAuditRejectReason(null);
resource.setStatus(FirstAidConstants.STATUS_PUBLISHED);
resource.setAuditBy(GlobalUtils.getLoginUser().getRealname());
resource.setAuditById(GlobalUtils.getLoginUser().getId());
resource.setAuditTime(new Date());
updateById(resource);
// 记录审核日志
auditService.saveAudit(id, FirstAidConstants.AUDIT_STATUS_APPROVED, null);
}
@Override
@@ -164,8 +180,11 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_REJECTED);
resource.setAuditRejectReason(rejectReason);
resource.setAuditBy(GlobalUtils.getLoginUser().getRealname());
resource.setAuditById(GlobalUtils.getLoginUser().getId());
resource.setAuditTime(new Date());
updateById(resource);
// 记录审核日志
auditService.saveAudit(id, FirstAidConstants.AUDIT_STATUS_REJECTED, rejectReason);
}
@Override
@@ -173,12 +192,14 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
public void batchPublish(List<String> ids) {
Date now = new Date();
String auditBy = GlobalUtils.getLoginUser().getRealname();
String auditById = GlobalUtils.getLoginUser().getId();
for (String id : ids) {
FirstAidResource resource = getById(id);
if (resource != null) {
resource.setStatus(FirstAidConstants.STATUS_PUBLISHED);
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_APPROVED);
resource.setAuditBy(auditBy);
resource.setAuditById(auditById);
resource.setAuditTime(now);
resource.setAuditRejectReason(null);
updateById(resource);
@@ -202,10 +223,9 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
public IPage<FirstAidResource> queryPageList(FirstAidResource resource, Page<FirstAidResource> page, String userId) {
LambdaQueryWrapper<FirstAidResource> queryWrapper = new LambdaQueryWrapper<>();
// 标题 + 正文 模糊搜索
// 标题模糊搜索
if (StrUtil.isNotBlank(resource.getTitle())) {
queryWrapper.and(w -> w.like(FirstAidResource::getTitle, resource.getTitle())
.or().like(FirstAidResource::getContent, resource.getTitle()));
queryWrapper.like(FirstAidResource::getTitle, resource.getTitle());
}
if (StrUtil.isNotBlank(resource.getCategoryId())) {
queryWrapper.eq(FirstAidResource::getCategoryId, resource.getCategoryId());
@@ -213,19 +233,21 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
if (StrUtil.isNotBlank(resource.getContentType())) {
queryWrapper.eq(FirstAidResource::getContentType, resource.getContentType());
}
if (resource.getAuditStatus() != null) {
queryWrapper.eq(FirstAidResource::getAuditStatus, resource.getAuditStatus());
}
if (resource.getStatus() != null) {
queryWrapper.eq(FirstAidResource::getStatus, resource.getStatus());
}
// 前端复合状态筛选:≤9 查status,≥10 查auditStatus
if (resource.getCombinedStatus() != null) {
// combinedStatus 优先:≤9 查status,≥10 查auditStatus
if (resource.getCombinedStatus() < 10) {
queryWrapper.eq(FirstAidResource::getStatus, resource.getCombinedStatus());
} else {
queryWrapper.eq(FirstAidResource::getAuditStatus, resource.getCombinedStatus());
}
} else {
// 未传 combinedStatus 时兼容直接传 status 或 auditStatus
if (resource.getStatus() != null) {
queryWrapper.eq(FirstAidResource::getStatus, resource.getStatus());
}
if (resource.getAuditStatus() != null) {
queryWrapper.eq(FirstAidResource::getAuditStatus, resource.getAuditStatus());
}
}
// 标签过滤
if (StrUtil.isNotBlank(resource.getTags())) {
@@ -245,47 +267,6 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
return result;
}
@Override
public IPage<FirstAidResource> queryAuditRecords(FirstAidResource resource, Page<FirstAidResource> page, String startDate, String endDate) {
LambdaQueryWrapper<FirstAidResource> queryWrapper = new LambdaQueryWrapper<>();
// 标题 + 正文 模糊搜索
if (StrUtil.isNotBlank(resource.getTitle())) {
queryWrapper.and(w -> w.like(FirstAidResource::getTitle, resource.getTitle())
.or().like(FirstAidResource::getContent, resource.getTitle()));
}
if (StrUtil.isNotBlank(resource.getCategoryId())) {
queryWrapper.eq(FirstAidResource::getCategoryId, resource.getCategoryId());
}
if (StrUtil.isNotBlank(resource.getContentType())) {
queryWrapper.eq(FirstAidResource::getContentType, resource.getContentType());
}
// 审核状态筛选:auditStatus=10 时查询所有已审核(>=10),否则精确匹配
if (resource.getAuditStatus() != null) {
if (resource.getAuditStatus() == 10) {
queryWrapper.ge(FirstAidResource::getAuditStatus, 10);
} else {
queryWrapper.eq(FirstAidResource::getAuditStatus, resource.getAuditStatus());
}
}
// 审核时间范围筛选
if (StrUtil.isNotBlank(startDate)) {
queryWrapper.ge(FirstAidResource::getAuditTime, startDate + " 00:00:00");
}
if (StrUtil.isNotBlank(endDate)) {
queryWrapper.le(FirstAidResource::getAuditTime, endDate + " 23:59:59");
}
queryWrapper.eq(FirstAidResource::getDelFlag, CommonConstant.DEL_FLAG_0);
queryWrapper.orderByDesc(FirstAidResource::getAuditTime);
IPage<FirstAidResource> result = page(page, queryWrapper);
fillResourceExtras(result.getRecords(), null);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean incrementViewCount(String id, String userId) {
@@ -301,6 +282,24 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
return resource;
}
@Override
public List<FirstAidResourceExportVO> convertToExportVO(List<FirstAidResource> resources) {
fillResourceExtras(resources, null);
return resources.stream().map(r -> {
FirstAidResourceExportVO vo = new FirstAidResourceExportVO();
vo.setTitle(r.getTitle());
vo.setSummary(r.getSummary());
vo.setCategoryName(r.getCategoryName());
vo.setContentType(r.getContentType());
vo.setTagNames(r.getTagNameList() != null && !r.getTagNameList().isEmpty()
? String.join("", r.getTagNameList()) : "");
vo.setStatusText(toStatusText(r.getAuditStatus(), r.getStatus()));
vo.setViewFavorite(r.getViewCount() + "/" + r.getFavoriteCount());
vo.setCreateTime(r.getCreateTime());
return vo;
}).collect(Collectors.toList());
}
private void fillResourceExtras(List<FirstAidResource> resources, String userId) {
if (CollectionUtil.isEmpty(resources)) {
return;
@@ -335,4 +334,34 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
resource.setIsFavorited(favoritedIds.contains(resource.getId()));
}
}
/**
* 计算导出用的状态文本(审核状态优先于发布状态)
*/
private String toStatusText(Integer auditStatus, Integer status) {
if (auditStatus != null) {
if (FirstAidConstants.AUDIT_STATUS_PENDING.equals(auditStatus)) {
return "待审核";
}
if (FirstAidConstants.AUDIT_STATUS_APPROVED.equals(auditStatus)) {
if (FirstAidConstants.STATUS_DOWN.equals(status)) {
return "已下架";
}
return "已发布";
}
if (FirstAidConstants.AUDIT_STATUS_REJECTED.equals(auditStatus)) {
return "已驳回";
}
}
if (FirstAidConstants.STATUS_DRAFT.equals(status)) {
return "草稿";
}
if (FirstAidConstants.STATUS_PUBLISHED.equals(status)) {
return "已发布";
}
if (FirstAidConstants.STATUS_DOWN.equals(status)) {
return "已下架";
}
return "未知";
}
}
@@ -1,5 +1,6 @@
package com.renkang.emergency.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.renkang.emergency.constant.FirstAidConstants;
import com.renkang.emergency.entity.FirstAidResource;
@@ -30,7 +31,16 @@ public class FirstAidResourceVersionServiceImpl extends ServiceImpl<FirstAidReso
@Override
@Transactional(rollbackFor = Exception.class)
public void saveVersion(String resourceId, String title, String content, String changeDescription) {
public void saveVersion(String resourceId, String title, String content, String changeDescription, boolean isCurrent) {
// 如果标记为当前版本,先清除该资源其他版本的当前标记
if (isCurrent) {
LambdaUpdateWrapper<FirstAidResourceVersion> clearWrapper = new LambdaUpdateWrapper<>();
clearWrapper.eq(FirstAidResourceVersion::getResourceId, resourceId);
clearWrapper.eq(FirstAidResourceVersion::getIsCurrent, true);
clearWrapper.set(FirstAidResourceVersion::getIsCurrent, false);
update(clearWrapper);
}
Integer maxNo = versionMapper.selectMaxVersionNo(resourceId);
int nextVersion = (maxNo == null) ? 1 : maxNo + 1;
@@ -40,9 +50,19 @@ public class FirstAidResourceVersionServiceImpl extends ServiceImpl<FirstAidReso
version.setTitle(title);
version.setContent(content);
version.setChangeDescription(changeDescription);
version.setIsCurrent(isCurrent);
save(version);
}
@Override
public void clearCurrent(String resourceId) {
LambdaUpdateWrapper<FirstAidResourceVersion> clearWrapper = new LambdaUpdateWrapper<>();
clearWrapper.eq(FirstAidResourceVersion::getResourceId, resourceId);
clearWrapper.eq(FirstAidResourceVersion::getIsCurrent, true);
clearWrapper.set(FirstAidResourceVersion::getIsCurrent, false);
update(clearWrapper);
}
@Override
public List<FirstAidResourceVersion> listVersions(String resourceId) {
return versionMapper.selectByResourceId(resourceId);
@@ -61,9 +81,6 @@ public class FirstAidResourceVersionServiceImpl extends ServiceImpl<FirstAidReso
throw new RuntimeException("资源不存在");
}
// 回滚前先保存当前状态为版本快照(防止回滚操作不可逆)
saveVersion(resource.getId(), resource.getTitle(), resource.getContent(), "回滚前自动保存");
// 恢复标题和内容,并退回草稿,清除审核状态(与编辑行为一致)
resource.setTitle(version.getTitle());
resource.setContent(version.getContent());
@@ -71,5 +88,9 @@ public class FirstAidResourceVersionServiceImpl extends ServiceImpl<FirstAidReso
resource.setAuditStatus(null);
resource.setAuditRejectReason(null);
resourceMapper.updateById(resource);
// 基于目标版本创建新版本记录(追加,不修改历史)
saveVersion(resource.getId(), version.getTitle(), version.getContent(),
"回滚到版本" + version.getVersionNo(), true);
}
}