feat(emergency): 急救宣教资源管理模块

新增急救宣教资源管理完整功能,包含6个子模块:

- 分类管理:树形结构,CRUD + 级联删除校验
- 标签管理:扁平结构,CRUD + 引用校验
- 资源管理:CRUD + 状态流转(草稿→审核→发布→下架) + 批量操作 + 阅读去重 + 复制
- 审核管理:待审核/已通过/已驳回列表 + 审核操作 + 审核记录
- 收藏管理:收藏/取消切换 + 状态查询 + 我的收藏列表
- 版本管理:编辑自动保存版本快照 + 回滚 + 差异对比

业务规则:
- 编辑/回滚后自动退回草稿,清除审核状态(方案A)
- 阅读去重:同用户重复阅读不增加计数
- 逻辑删除 + 租户隔离

Co-Authored-By: Claude <noreply@anthropic.com>
@
This commit is contained in:
2026-06-23 17:40:30 +08:00
parent 74ebe26a6c
commit 6384021c4d
30 changed files with 2432 additions and 0 deletions
@@ -0,0 +1,124 @@
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.constant.FirstAidConstants;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.service.IFirstAidResourceService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
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.*;
/**
* @Description: 急救宣教资源-审核管理
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Tag(name = "急救宣教资源-审核管理")
@RestController
@RequestMapping("/emergency/firstAid/audit")
@Slf4j
public class FirstAidResourceAuditController {
@Autowired
private IFirstAidResourceService resourceService;
/**
* 待审核列表(支持标题/分类/内容类型筛选)
*/
@Operation(summary = "待审核列表")
@GetMapping(value = "/pending")
public Result<IPage<FirstAidResource>> pending(FirstAidResource entity,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
Page<FirstAidResource> page = new Page<>(pageNo, pageSize);
entity.setAuditStatus(FirstAidConstants.AUDIT_STATUS_PENDING);
return Result.OK(resourceService.queryPageList(entity, page, null));
}
/**
* 已通过列表(支持标题/分类/内容类型筛选)
*/
@Operation(summary = "已通过列表")
@GetMapping(value = "/passed")
public Result<IPage<FirstAidResource>> passed(FirstAidResource entity,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
Page<FirstAidResource> page = new Page<>(pageNo, pageSize);
entity.setAuditStatus(FirstAidConstants.AUDIT_STATUS_APPROVED);
return Result.OK(resourceService.queryPageList(entity, page, null));
}
/**
* 已驳回列表(支持标题/分类/内容类型筛选)
*/
@Operation(summary = "已驳回列表")
@GetMapping(value = "/rejected")
public Result<IPage<FirstAidResource>> rejected(FirstAidResource entity,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
Page<FirstAidResource> page = new Page<>(pageNo, pageSize);
entity.setAuditStatus(FirstAidConstants.AUDIT_STATUS_REJECTED);
return Result.OK(resourceService.queryPageList(entity, page, null));
}
/**
* 审核通过
*/
@AutoLog(value = "急救宣教资源-审核通过")
//@RequiresPermissions("emergency:firstAidAudit:approve")
@Operation(summary = "审核通过")
@PostMapping(value = "/approve")
public Result<String> approve(@RequestParam(name = "id", required = true) String id) {
try {
resourceService.approve(id);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
return Result.OK("审核通过");
}
/**
* 审核驳回
*/
@AutoLog(value = "急救宣教资源-审核驳回")
//@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)) {
return Result.error("驳回原因不能为空");
}
try {
resourceService.reject(id, reason);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
return Result.OK("已驳回");
}
/**
* 审核记录列表(合并待审核/已通过/已驳回的全部审核操作日志)
*/
@Operation(summary = "审核记录列表", description = "查询所有已审核的资源记录,支持按操作类型和日期范围筛选")
@GetMapping(value = "/records")
public Result<IPage<FirstAidResource>> records(FirstAidResource entity,
@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=已驳回
}
return Result.OK(resourceService.queryAuditRecords(entity, page, startDate, endDate));
}
}
@@ -0,0 +1,148 @@
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.entity.FirstAidResourceCategory;
import com.renkang.emergency.service.IFirstAidResourceCategoryService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
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.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.List;
@Tag(name = "急救宣教资源-分类管理")
@RestController
@RequestMapping("/emergency/firstAid/category")
@Slf4j
public class FirstAidResourceCategoryController extends JeecgController<FirstAidResourceCategory, IFirstAidResourceCategoryService> {
@Autowired
private IFirstAidResourceCategoryService categoryService;
@Operation(summary = "急救宣教资源分类-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<FirstAidResourceCategory>> queryPageList(FirstAidResourceCategory entity,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
entity.setType(FirstAidConstants.TYPE_CATEGORY);
QueryWrapper<FirstAidResourceCategory> queryWrapper = QueryGenerator.initQueryWrapper(entity, req.getParameterMap());
Page<FirstAidResourceCategory> page = new Page<>(pageNo, pageSize);
IPage<FirstAidResourceCategory> pageList = categoryService.page(page, queryWrapper);
for (FirstAidResourceCategory record : pageList.getRecords()) {
if (StrUtil.isNotBlank(record.getParentId())) {
FirstAidResourceCategory parent = categoryService.getById(record.getParentId());
if (parent != null) record.setParentName(parent.getName());
}
}
return Result.OK(pageList);
}
@Operation(summary = "急救宣教资源分类-全部列表")
@GetMapping(value = "/listAll")
public Result<List<FirstAidResourceCategory>> listAll() {
return Result.OK(categoryService.listCategories());
}
@Operation(summary = "急救宣教资源分类-根分类列表")
@GetMapping(value = "/listRoot")
public Result<List<FirstAidResourceCategory>> listRoot() {
return Result.OK(categoryService.listRootCategories());
}
@Operation(summary = "急救宣教资源分类-子分类列表")
@GetMapping(value = "/listChildren")
public Result<List<FirstAidResourceCategory>> listChildren(@RequestParam(name = "parentId") String parentId) {
return Result.OK(categoryService.listChildrenByParentId(parentId));
}
@Operation(summary = "急救宣教资源分类-添加")
@AutoLog(value = "急救宣教资源分类-添加")
//@RequiresPermissions("emergency:firstAidCategory:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody FirstAidResourceCategory entity) {
entity.setType(FirstAidConstants.TYPE_CATEGORY);
if (entity.getSortNo() == null) entity.setSortNo(0);
if (entity.getStatus() == null) entity.setStatus(FirstAidConstants.NORMAL);
categoryService.save(entity);
return Result.OK("添加成功!");
}
@Operation(summary = "急救宣教资源分类-编辑")
@AutoLog(value = "急救宣教资源分类-编辑")
//@RequiresPermissions("emergency:firstAidCategory:edit")
@PostMapping(value = "/edit")
public Result<String> edit(@RequestBody FirstAidResourceCategory entity) {
FirstAidResourceCategory old = categoryService.getById(entity.getId());
if (old == null) return Result.error("数据不存在");
entity.setType(old.getType());
categoryService.updateById(entity);
return Result.OK("编辑成功!");
}
@Operation(summary = "急救宣教资源分类-删除")
@AutoLog(value = "急救宣教资源分类-删除")
//@RequiresPermissions("emergency:firstAidCategory:delete")
@PostMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id") String id) {
try {
categoryService.deleteWithCheck(id);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
return Result.OK("删除成功!");
}
@Operation(summary = "急救宣教资源分类-批量删除")
@AutoLog(value = "急救宣教资源分类-批量删除")
//@RequiresPermissions("emergency:firstAidCategory:deleteBatch")
@PostMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestBody List<String> ids) {
for (String id : ids) {
try {
categoryService.deleteWithCheck(id);
} catch (RuntimeException e) {
return Result.error("ID[" + id + "]" + e.getMessage());
}
}
return Result.OK("批量删除成功!");
}
@Operation(summary = "急救宣教资源分类-通过id查询")
@GetMapping(value = "/queryById")
public Result<FirstAidResourceCategory> queryById(@RequestParam(name = "id") String id) {
FirstAidResourceCategory entity = categoryService.getById(id);
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")
//@RequiresPermissions("emergency:firstAidCategory:exportXls")
@GetMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, FirstAidResourceCategory entity) {
return super.exportXls(request, entity, FirstAidResourceCategory.class, "急救宣教资源分类");
}
@Operation(summary = "急救宣教资源分类-导入excel")
//@RequiresPermissions("emergency:firstAidCategory:importExcel")
@PostMapping(value = "/importExcel")
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, FirstAidResourceCategory.class);
}
}
@@ -0,0 +1,172 @@
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.entity.FirstAidResource;
import com.renkang.emergency.service.IFirstAidResourceService;
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.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.global.GlobalUtils;
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.List;
@Tag(name = "急救宣教资源管理")
@RestController
@RequestMapping("/emergency/firstAid/resource")
@Slf4j
public class FirstAidResourceController extends JeecgController<FirstAidResource, IFirstAidResourceService> {
@Autowired
private IFirstAidResourceService resourceService;
@Operation(summary = "急救宣教资源-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<FirstAidResource>> queryPageList(FirstAidResource entity,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
Page<FirstAidResource> page = new Page<>(pageNo, pageSize);
String userId = GlobalUtils.getLoginUser().getId();
return Result.OK(resourceService.queryPageList(entity, page, userId));
}
@Operation(summary = "急救宣教资源-添加")
@AutoLog(value = "急救宣教资源-添加")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody FirstAidResource entity) {
if (StrUtil.isBlank(entity.getTitle())) {
return Result.error("标题不能为空");
}
resourceService.saveResource(entity);
return Result.OK("添加成功!");
}
@Operation(summary = "急救宣教资源-编辑")
@AutoLog(value = "急救宣教资源-编辑")
@PostMapping(value = "/edit")
public Result<String> edit(@RequestBody FirstAidResource entity) {
if (resourceService.getById(entity.getId()) == null) {
return Result.error("数据不存在");
}
resourceService.updateResource(entity);
return Result.OK("编辑成功!");
}
@Operation(summary = "急救宣教资源-删除")
@AutoLog(value = "急救宣教资源-删除")
@PostMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id") String id) {
resourceService.removeById(id);
return Result.OK("删除成功!");
}
@Operation(summary = "急救宣教资源-批量删除")
@AutoLog(value = "急救宣教资源-批量删除")
@PostMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestBody List<String> ids) {
resourceService.removeByIds(ids);
return Result.OK("批量删除成功!");
}
@Operation(summary = "急救宣教资源-批量发布")
@AutoLog(value = "急救宣教资源-批量发布")
@PostMapping(value = "/batchPublish")
public Result<String> batchPublish(@RequestBody List<String> ids) {
resourceService.batchPublish(ids);
return Result.OK("批量发布成功!");
}
@Operation(summary = "急救宣教资源-批量下架")
@AutoLog(value = "急救宣教资源-批量下架")
@PostMapping(value = "/batchTakeDown")
public Result<String> batchTakeDown(@RequestBody List<String> ids) {
resourceService.batchTakeDown(ids);
return Result.OK("批量下架成功!");
}
@Operation(summary = "急救宣教资源-提交审核")
@AutoLog(value = "急救宣教资源-提交审核")
@PostMapping(value = "/submitForReview")
public Result<String> submitForReview(@RequestParam(name = "id") String id) {
try {
resourceService.submitForReview(id);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
return Result.OK("已提交审核");
}
@Operation(summary = "急救宣教资源-下架")
@AutoLog(value = "急救宣教资源-下架")
@PostMapping(value = "/takeDown")
public Result<String> takeDown(@RequestParam(name = "id") String id) {
try {
resourceService.takeDown(id);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
return Result.OK("已下架");
}
@Operation(summary = "急救宣教资源-上架")
@AutoLog(value = "急救宣教资源-上架")
@PostMapping(value = "/publish")
public Result<String> publish(@RequestParam(name = "id") String id) {
try {
resourceService.publish(id);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
return Result.OK("已上架");
}
@Operation(summary = "急救宣教资源-通过id查询详情")
@GetMapping(value = "/queryById")
public Result<FirstAidResource> queryById(@RequestParam(name = "id") String id) {
String userId = GlobalUtils.getLoginUser().getId();
FirstAidResource entity = resourceService.getDetail(id, userId);
return entity == null ? Result.error("未找到对应数据") : Result.OK(entity);
}
@Operation(summary = "急救宣教资源-记录阅读")
@AutoLog(value = "急救宣教资源-记录阅读")
@PostMapping(value = "/incrementView")
public Result<String> incrementView(@RequestParam(name = "id") String id) {
String userId = GlobalUtils.getLoginUser().getId();
boolean firstRead = resourceService.incrementViewCount(id, userId);
return firstRead ? Result.OK("阅读记录成功") : Result.OK("已阅读过");
}
@Operation(summary = "急救宣教资源-复制资源")
@AutoLog(value = "急救宣教资源-复制")
@PostMapping(value = "/copy")
public Result<FirstAidResource> copy(@RequestParam(name = "id") String id) {
try {
return Result.OK("复制成功,已保存为草稿", resourceService.copyResource(id));
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
}
@Operation(summary = "急救宣教资源-导出excel")
@GetMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, FirstAidResource entity) {
return super.exportXls(request, entity, FirstAidResource.class, "急救宣教资源");
}
@Operation(summary = "急救宣教资源-导入excel")
@PostMapping(value = "/importExcel")
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, FirstAidResource.class);
}
}
@@ -0,0 +1,82 @@
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.entity.FirstAidResource;
import com.renkang.emergency.service.IFirstAidResourceUserActionService;
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.jeecg.global.GlobalUtils;
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-22
* @Version: V1.0
*/
@Tag(name = "急救宣教资源-收藏管理")
@RestController
@RequestMapping("/emergency/firstAid/favorite")
@Slf4j
public class FirstAidResourceFavoriteController {
@Autowired
private IFirstAidResourceUserActionService userActionService;
/**
* 切换收藏状态(收藏/取消收藏)
*/
@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)) {
return Result.error("资源ID不能为空");
}
String userId = GlobalUtils.getLoginUser().getId();
boolean favorited = userActionService.toggleFavorite(resourceId, userId);
Map<String, Object> result = new HashMap<>();
result.put("favorited", favorited);
return Result.OK(favorited ? "收藏成功" : "已取消收藏", result);
}
/**
* 判断是否已收藏
*/
@Operation(summary = "判断收藏状态", description = "查询当前用户是否已收藏该资源")
@GetMapping(value = "/isFavorited")
public Result<Map<String, Object>> isFavorited(@RequestParam(name = "resourceId", required = true) String resourceId) {
if (StrUtil.isBlank(resourceId)) {
return Result.error("资源ID不能为空");
}
String userId = GlobalUtils.getLoginUser().getId();
boolean favorited = userActionService.isFavorited(resourceId, userId);
Map<String, Object> result = new HashMap<>();
result.put("favorited", favorited);
return Result.OK(result);
}
/**
* 我的收藏列表
*/
@Operation(summary = "我的收藏列表", description = "查询当前用户的收藏资源列表")
@GetMapping(value = "/myList")
public Result<IPage<FirstAidResource>> myList(@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
String userId = GlobalUtils.getLoginUser().getId();
Page<FirstAidResource> page = new Page<>(pageNo, pageSize);
IPage<FirstAidResource> pageList = userActionService.getMyFavoritePage(page, userId);
return Result.OK(pageList);
}
}
@@ -0,0 +1,124 @@
package com.renkang.emergency.controller;
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.entity.FirstAidResourceCategory;
import com.renkang.emergency.service.IFirstAidResourceCategoryService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
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.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.List;
@Tag(name = "急救宣教资源-标签管理")
@RestController
@RequestMapping("/emergency/firstAid/tag")
@Slf4j
public class FirstAidResourceTagController extends JeecgController<FirstAidResourceCategory, IFirstAidResourceCategoryService> {
@Autowired
private IFirstAidResourceCategoryService categoryService;
@Operation(summary = "急救宣教资源标签-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<FirstAidResourceCategory>> queryPageList(FirstAidResourceCategory entity,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
entity.setType(FirstAidConstants.TYPE_TAG);
QueryWrapper<FirstAidResourceCategory> queryWrapper = QueryGenerator.initQueryWrapper(entity, req.getParameterMap());
Page<FirstAidResourceCategory> page = new Page<>(pageNo, pageSize);
return Result.OK(categoryService.page(page, queryWrapper));
}
@Operation(summary = "急救宣教资源标签-全部列表")
@GetMapping(value = "/listAll")
public Result<List<FirstAidResourceCategory>> listAll() {
return Result.OK(categoryService.listTags());
}
@Operation(summary = "急救宣教资源标签-添加")
@AutoLog(value = "急救宣教资源标签-添加")
//@RequiresPermissions("emergency:firstAidTag:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody FirstAidResourceCategory entity) {
entity.setType(FirstAidConstants.TYPE_TAG);
if (entity.getSortNo() == null) entity.setSortNo(0);
if (entity.getStatus() == null) entity.setStatus(FirstAidConstants.NORMAL);
categoryService.save(entity);
return Result.OK("添加成功!");
}
@Operation(summary = "急救宣教资源标签-编辑")
@AutoLog(value = "急救宣教资源标签-编辑")
//@RequiresPermissions("emergency:firstAidTag:edit")
@PostMapping(value = "/edit")
public Result<String> edit(@RequestBody FirstAidResourceCategory entity) {
FirstAidResourceCategory old = categoryService.getById(entity.getId());
if (old == null) return Result.error("数据不存在");
if (!FirstAidConstants.TYPE_TAG.equals(old.getType())) return Result.error("该记录不是标签");
entity.setType(old.getType());
categoryService.updateById(entity);
return Result.OK("编辑成功!");
}
@Operation(summary = "急救宣教资源标签-删除")
@AutoLog(value = "急救宣教资源标签-删除")
//@RequiresPermissions("emergency:firstAidTag:delete")
@PostMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id") String id) {
try {
categoryService.deleteWithCheck(id);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
return Result.OK("删除成功!");
}
@Operation(summary = "急救宣教资源标签-批量删除")
@AutoLog(value = "急救宣教资源标签-批量删除")
//@RequiresPermissions("emergency:firstAidTag:deleteBatch")
@PostMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestBody List<String> ids) {
for (String id : ids) {
try {
categoryService.deleteWithCheck(id);
} catch (RuntimeException e) {
return Result.error("ID[" + id + "]" + e.getMessage());
}
}
return Result.OK("批量删除成功!");
}
@Operation(summary = "急救宣教资源标签-通过id查询")
@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);
}
@Operation(summary = "急救宣教资源标签-导出excel")
//@RequiresPermissions("emergency:firstAidTag:exportXls")
@GetMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, FirstAidResourceCategory entity) {
return super.exportXls(request, entity, FirstAidResourceCategory.class, "急救宣教资源标签");
}
@Operation(summary = "急救宣教资源标签-导入excel")
//@RequiresPermissions("emergency:firstAidTag:importExcel")
@PostMapping(value = "/importExcel")
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, FirstAidResourceCategory.class);
}
}
@@ -0,0 +1,78 @@
package com.renkang.emergency.controller;
import com.renkang.emergency.entity.FirstAidResourceVersion;
import com.renkang.emergency.service.IFirstAidResourceVersionService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
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.List;
import java.util.Map;
/**
* @Description: 急救宣教资源-版本管理
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Tag(name = "急救宣教资源-版本管理")
@RestController
@RequestMapping("/emergency/firstAid/version")
@Slf4j
public class FirstAidResourceVersionController {
@Autowired
private IFirstAidResourceVersionService versionService;
/**
* 查询某资源的版本历史
*/
@Operation(summary = "版本历史列表", description = "查询某资源的所有版本号,按版本倒序")
@GetMapping(value = "/list")
public Result<List<FirstAidResourceVersion>> list(@RequestParam(name = "resourceId", required = true) String resourceId) {
List<FirstAidResourceVersion> list = versionService.listVersions(resourceId);
return Result.OK(list);
}
/**
* 回滚到指定版本
*/
@AutoLog(value = "急救宣教资源-版本回滚")
//@RequiresPermissions("emergency:firstAidResource:edit")
@Operation(summary = "回滚到指定版本", description = "将指定版本的内容恢复到资源表")
@PostMapping(value = "/rollback")
public Result<String> rollback(@RequestParam(name = "versionId", required = true) String versionId) {
try {
versionService.rollback(versionId);
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
return Result.OK("回滚成功");
}
/**
* 版本差异对比
*/
@Operation(summary = "版本差异对比", description = "对比两个版本的标题和内容差异")
@GetMapping(value = "/diff")
public Result<Map<String, Object>> diff(@RequestParam(name = "versionId1", required = true) String versionId1,
@RequestParam(name = "versionId2", required = true) String versionId2) {
FirstAidResourceVersion v1 = versionService.getById(versionId1);
FirstAidResourceVersion v2 = versionService.getById(versionId2);
if (v1 == null || v2 == null) {
return Result.error("版本记录不存在");
}
if (!v1.getResourceId().equals(v2.getResourceId())) {
return Result.error("两个版本不属于同一资源,无法对比");
}
Map<String, Object> result = new HashMap<>();
result.put("version1", v1);
result.put("version2", v2);
return Result.OK(result);
}
}
@@ -0,0 +1,55 @@
package com.renkang.emergency.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.renkang.emergency.entity.FirstAidResourceCategory;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @Description: 急救宣教资源分类/标签
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Mapper
public interface FirstAidResourceCategoryMapper extends BaseMapper<FirstAidResourceCategory> {
/**
* 查询某分类下的子分类数量(级联删除校验用)
*/
@Select("SELECT COUNT(*) FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE PARENT_ID = #{parentId} AND TYPE = 1 AND DEL_FLAG = 0")
Integer countChildrenByParentId(@Param("parentId") String parentId);
/**
* 查询某分类下的资源数量(级联删除校验用)
*/
@Select("SELECT COUNT(*) FROM QH_EME_FIRST_AID_RESOURCE WHERE CATEGORY_ID = #{categoryId} AND DEL_FLAG = 0")
Integer countResourcesByCategoryId(@Param("categoryId") String categoryId);
/**
* 查询引用某标签的资源数量(标签删除校验用)
*/
@Select("SELECT COUNT(*) FROM QH_EME_FIRST_AID_RESOURCE WHERE DEL_FLAG = 0 AND TAGS LIKE CONCAT('%', #{tagId}, '%')")
Integer countResourcesByTagId(@Param("tagId") String tagId);
/**
* 按类型查询分类/标签列表
*/
@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);
/**
* 按父分类ID查询子分类列表(树形结构用)
*/
@Select("SELECT * FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE PARENT_ID = #{parentId} AND TYPE = 1 AND STATUS = 1 AND DEL_FLAG = 0 ORDER BY SORT_NO ASC")
List<FirstAidResourceCategory> selectChildrenByParentId(@Param("parentId") String parentId);
/**
* 查询所有根分类(PARENT_ID IS NULL
*/
@Select("SELECT * FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE TYPE = 1 AND PARENT_ID IS NULL AND STATUS = 1 AND DEL_FLAG = 0 ORDER BY SORT_NO ASC")
List<FirstAidResourceCategory> selectRootCategories();
}
@@ -0,0 +1,42 @@
package com.renkang.emergency.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.renkang.emergency.entity.FirstAidResource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
/**
* @Description: 急救宣教资源
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Mapper
public interface FirstAidResourceMapper extends BaseMapper<FirstAidResource> {
/**
* 查询某分类下的资源数量(级联删除校验用)
*/
@Select("SELECT COUNT(*) FROM QH_EME_FIRST_AID_RESOURCE WHERE CATEGORY_ID = #{categoryId} AND DEL_FLAG = 0")
Integer countByCategoryId(@Param("categoryId") String categoryId);
/**
* 浏览计数+1
*/
@Update("UPDATE QH_EME_FIRST_AID_RESOURCE SET VIEW_COUNT = VIEW_COUNT + 1 WHERE ID = #{id}")
void incrementViewCount(@Param("id") String id);
/**
* 收藏计数+1
*/
@Update("UPDATE QH_EME_FIRST_AID_RESOURCE SET FAVORITE_COUNT = FAVORITE_COUNT + 1 WHERE ID = #{id}")
void incrementFavoriteCount(@Param("id") String id);
/**
* 收藏计数-1
*/
@Update("UPDATE QH_EME_FIRST_AID_RESOURCE SET FAVORITE_COUNT = FAVORITE_COUNT - 1 WHERE ID = #{id} AND FAVORITE_COUNT > 0")
void decrementFavoriteCount(@Param("id") String id);
}
@@ -0,0 +1,45 @@
package com.renkang.emergency.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.entity.FirstAidResourceUserAction;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @Description: 用户行为记录
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Mapper
public interface FirstAidResourceUserActionMapper extends BaseMapper<FirstAidResourceUserAction> {
/**
* 查询用户对一批资源的收藏状态
*/
@Select("<script>" +
"SELECT RESOURCE_ID FROM QH_EME_FIRST_AID_RESOURCE_USER_ACTION " +
"WHERE USER_ID = #{userId} AND TYPE = 2 " +
"AND RESOURCE_ID IN " +
"<foreach collection='resourceIds' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
List<String> selectFavoritedResourceIds(@Param("userId") String userId,
@Param("resourceIds") List<String> resourceIds);
/**
* 查询用户的收藏资源列表(分页)
*/
@Select("SELECT R.* FROM QH_EME_FIRST_AID_RESOURCE R " +
"INNER JOIN QH_EME_FIRST_AID_RESOURCE_USER_ACTION A ON R.ID = A.RESOURCE_ID " +
"WHERE A.USER_ID = #{userId} AND A.TYPE = 2 AND R.DEL_FLAG = 0 " +
"ORDER BY A.CREATE_TIME DESC")
IPage<FirstAidResource> selectFavoritePage(Page<FirstAidResource> page, @Param("userId") String userId);
}
@@ -0,0 +1,31 @@
package com.renkang.emergency.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.renkang.emergency.entity.FirstAidResourceVersion;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @Description: 资源版本历史
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Mapper
public interface FirstAidResourceVersionMapper extends BaseMapper<FirstAidResourceVersion> {
/**
* 查询某资源的最高版本号
*/
@Select("SELECT MAX(VERSION_NO) FROM QH_EME_FIRST_AID_RESOURCE_VERSION WHERE RESOURCE_ID = #{resourceId}")
Integer selectMaxVersionNo(@Param("resourceId") String resourceId);
/**
* 查询某资源的所有版本历史(按版本号倒序)
*/
@Select("SELECT * FROM QH_EME_FIRST_AID_RESOURCE_VERSION WHERE RESOURCE_ID = #{resourceId} ORDER BY VERSION_NO DESC")
List<FirstAidResourceVersion> selectByResourceId(@Param("resourceId") String resourceId);
}
@@ -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.FirstAidResourceCategoryMapper">
</mapper>
@@ -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.FirstAidResourceMapper">
</mapper>
@@ -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.FirstAidResourceUserActionMapper">
</mapper>
@@ -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.FirstAidResourceVersionMapper">
</mapper>
@@ -0,0 +1,40 @@
package com.renkang.emergency.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.renkang.emergency.entity.FirstAidResourceCategory;
import java.util.List;
/**
* @Description: 急救宣教资源分类/标签
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
public interface IFirstAidResourceCategoryService extends IService<FirstAidResourceCategory> {
/**
* 查询所有有效分类(根分类+子分类)
*/
List<FirstAidResourceCategory> listCategories();
/**
* 查询根分类列表(树形结构顶层)
*/
List<FirstAidResourceCategory> listRootCategories();
/**
* 按父分类ID查询子分类
*/
List<FirstAidResourceCategory> listChildrenByParentId(String parentId);
/**
* 查询所有有效标签列表
*/
List<FirstAidResourceCategory> listTags();
/**
* 删除前校验(分类下有子分类/资源则拒绝;标签有资源引用则拒绝)
*/
void deleteWithCheck(String id);
}
@@ -0,0 +1,53 @@
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.FirstAidResource;
import java.util.List;
/**
* @Description: 急救宣教资源
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
public interface IFirstAidResourceService extends IService<FirstAidResource> {
void saveResource(FirstAidResource resource);
void updateResource(FirstAidResource resource);
FirstAidResource copyResource(String id);
/** 提交审核 */
void submitForReview(String id);
/** 下架 */
void takeDown(String id);
/** 上架(发布) */
void publish(String id);
/** 审核通过 */
void approve(String id);
/** 审核驳回 */
void reject(String id, String rejectReason);
/** 批量发布 */
void batchPublish(List<String> ids);
/** 批量下架 */
void batchTakeDown(List<String> ids);
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);
}
@@ -0,0 +1,49 @@
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.FirstAidResource;
import com.renkang.emergency.entity.FirstAidResourceUserAction;
import java.util.List;
/**
* @Description: 用户行为记录(阅读/收藏)
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
public interface IFirstAidResourceUserActionService extends IService<FirstAidResourceUserAction> {
/**
* 记录阅读行为(去重,已读则忽略)
*
* @return true=首次阅读(已计数), false=已读过(忽略)
*/
boolean recordRead(String resourceId, String userId);
/**
* 切换收藏状态(收藏/取消收藏)
*
* @return true=已收藏, false=已取消
*/
boolean toggleFavorite(String resourceId, String userId);
/**
* 判断用户是否已收藏某资源
*/
boolean isFavorited(String resourceId, String userId);
/**
* 批量查询用户对一批资源的收藏状态
*
* @return 已收藏的资源ID集合
*/
List<String> getFavoritedResourceIds(String userId, List<String> resourceIds);
/**
* 我的收藏分页列表
*/
IPage<FirstAidResource> getMyFavoritePage(Page<FirstAidResource> page, String userId);
}
@@ -0,0 +1,30 @@
package com.renkang.emergency.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.renkang.emergency.entity.FirstAidResourceVersion;
import java.util.List;
/**
* @Description: 资源版本历史
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
public interface IFirstAidResourceVersionService extends IService<FirstAidResourceVersion> {
/**
* 保存版本快照
*/
void saveVersion(String resourceId, String title, String content, String changeDescription);
/**
* 查询某资源的所有版本
*/
List<FirstAidResourceVersion> listVersions(String resourceId);
/**
* 回滚到指定版本(将版本内容恢复到资源表)
*/
void rollback(String versionId);
}
@@ -0,0 +1,98 @@
package com.renkang.emergency.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.renkang.emergency.constant.FirstAidConstants;
import com.renkang.emergency.entity.FirstAidResourceCategory;
import com.renkang.emergency.mapper.FirstAidResourceCategoryMapper;
import com.renkang.emergency.mapper.FirstAidResourceMapper;
import com.renkang.emergency.service.IFirstAidResourceCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Description: 急救宣教资源分类/标签
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Service
public class FirstAidResourceCategoryServiceImpl extends ServiceImpl<FirstAidResourceCategoryMapper, FirstAidResourceCategory> implements IFirstAidResourceCategoryService {
@Autowired
private FirstAidResourceCategoryMapper categoryMapper;
@Autowired
private FirstAidResourceMapper resourceMapper;
@Override
public List<FirstAidResourceCategory> listCategories() {
List<FirstAidResourceCategory> list = categoryMapper.selectByType(FirstAidConstants.TYPE_CATEGORY);
fillResourceCounts(list);
return list;
}
@Override
public List<FirstAidResourceCategory> listRootCategories() {
List<FirstAidResourceCategory> list = categoryMapper.selectRootCategories();
fillResourceCounts(list);
return list;
}
@Override
public List<FirstAidResourceCategory> listChildrenByParentId(String parentId) {
List<FirstAidResourceCategory> list = categoryMapper.selectChildrenByParentId(parentId);
fillResourceCounts(list);
return list;
}
@Override
public List<FirstAidResourceCategory> listTags() {
List<FirstAidResourceCategory> list = categoryMapper.selectByType(FirstAidConstants.TYPE_TAG);
for (FirstAidResourceCategory tag : list) {
Integer count = categoryMapper.countResourcesByTagId(tag.getId());
tag.setResourceCount(count != null ? count : 0);
}
return list;
}
/**
* 填充分类的资源数量(分类直查 categoryId
*/
private void fillResourceCounts(List<FirstAidResourceCategory> categories) {
for (FirstAidResourceCategory cat : categories) {
Integer count = resourceMapper.countByCategoryId(cat.getId());
cat.setResourceCount(count != null ? count : 0);
}
}
@Override
public void deleteWithCheck(String id) {
FirstAidResourceCategory record = getById(id);
if (record == null) {
throw new RuntimeException("数据不存在");
}
if (FirstAidConstants.TYPE_CATEGORY.equals(record.getType())) {
// 分类:检查是否有子分类
Integer childCount = categoryMapper.countChildrenByParentId(id);
if (childCount != null && childCount > 0) {
throw new RuntimeException("该分类下存在子分类,无法删除");
}
// 分类:检查是否有资源
Integer resourceCount = resourceMapper.countByCategoryId(id);
if (resourceCount != null && resourceCount > 0) {
throw new RuntimeException("该分类下存在资源,无法删除");
}
} else if (FirstAidConstants.TYPE_TAG.equals(record.getType())) {
// 标签:检查是否有资源引用
Integer resourceCount = categoryMapper.countResourcesByTagId(id);
if (resourceCount != null && resourceCount > 0) {
throw new RuntimeException("该标签已被资源引用,无法删除");
}
}
removeById(id);
}
}
@@ -0,0 +1,338 @@
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.constant.FirstAidConstants;
import com.renkang.emergency.entity.FirstAidResource;
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.IFirstAidResourceUserActionService;
import com.renkang.emergency.service.IFirstAidResourceVersionService;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.global.GlobalUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Description: 急救宣教资源
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Service
public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMapper, FirstAidResource> implements IFirstAidResourceService {
@Autowired
private FirstAidResourceMapper resourceMapper;
@Autowired
private FirstAidResourceCategoryMapper categoryMapper;
@Autowired
private IFirstAidResourceUserActionService userActionService;
@Autowired
private IFirstAidResourceVersionService versionService;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveResource(FirstAidResource resource) {
if (resource.getViewCount() == null) {
resource.setViewCount(0);
}
if (resource.getFavoriteCount() == null) {
resource.setFavoriteCount(0);
}
resource.setStatus(FirstAidConstants.STATUS_DRAFT);
resource.setAuditStatus(null);
save(resource);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateResource(FirstAidResource resource) {
FirstAidResource old = getById(resource.getId());
if (old == null) {
throw new RuntimeException("资源不存在");
}
versionService.saveVersion(old.getId(), old.getTitle(), old.getContent(), "编辑更新");
// 编辑后自动退回草稿,清除审核状态(方案A)
resource.setStatus(FirstAidConstants.STATUS_DRAFT);
resource.setAuditStatus(null);
resource.setAuditRejectReason(null);
updateById(resource);
}
@Override
@Transactional(rollbackFor = Exception.class)
public FirstAidResource copyResource(String id) {
FirstAidResource source = getById(id);
if (source == null) {
throw new RuntimeException("源资源不存在");
}
FirstAidResource copy = new FirstAidResource();
copy.setTitle(source.getTitle() + " - 副本");
copy.setSummary(source.getSummary());
copy.setContentType(source.getContentType());
copy.setCategoryId(source.getCategoryId());
copy.setApplicableScenario(source.getApplicableScenario());
copy.setTags(source.getTags());
copy.setContent(source.getContent());
copy.setCoverImage(source.getCoverImage());
copy.setFileUrl(source.getFileUrl());
copy.setStatus(FirstAidConstants.STATUS_DRAFT);
copy.setAuditStatus(null);
copy.setViewCount(0);
copy.setFavoriteCount(0);
save(copy);
return copy;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void submitForReview(String id) {
FirstAidResource resource = getById(id);
if (resource == null) {
throw new RuntimeException("资源不存在");
}
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_PENDING);
resource.setAuditRejectReason(null);
updateById(resource);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void takeDown(String id) {
FirstAidResource resource = getById(id);
if (resource == null) {
throw new RuntimeException("资源不存在");
}
resource.setStatus(FirstAidConstants.STATUS_DOWN);
updateById(resource);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void publish(String id) {
FirstAidResource resource = getById(id);
if (resource == null) {
throw new RuntimeException("资源不存在");
}
resource.setStatus(FirstAidConstants.STATUS_PUBLISHED);
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_APPROVED);
resource.setAuditRejectReason(null);
resource.setAuditBy(GlobalUtils.getLoginUser().getRealname());
resource.setAuditTime(new Date());
updateById(resource);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void approve(String id) {
FirstAidResource resource = getById(id);
if (resource == null) {
throw new RuntimeException("资源不存在");
}
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_APPROVED);
resource.setAuditRejectReason(null);
resource.setStatus(FirstAidConstants.STATUS_PUBLISHED);
resource.setAuditBy(GlobalUtils.getLoginUser().getRealname());
resource.setAuditTime(new Date());
updateById(resource);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void reject(String id, String rejectReason) {
FirstAidResource resource = getById(id);
if (resource == null) {
throw new RuntimeException("资源不存在");
}
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_REJECTED);
resource.setAuditRejectReason(rejectReason);
resource.setAuditBy(GlobalUtils.getLoginUser().getRealname());
resource.setAuditTime(new Date());
updateById(resource);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void batchPublish(List<String> ids) {
Date now = new Date();
String auditBy = GlobalUtils.getLoginUser().getRealname();
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.setAuditTime(now);
resource.setAuditRejectReason(null);
updateById(resource);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void batchTakeDown(List<String> ids) {
for (String id : ids) {
FirstAidResource resource = getById(id);
if (resource != null) {
resource.setStatus(FirstAidConstants.STATUS_DOWN);
updateById(resource);
}
}
}
@Override
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()));
}
if (StrUtil.isNotBlank(resource.getCategoryId())) {
queryWrapper.eq(FirstAidResource::getCategoryId, resource.getCategoryId());
}
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) {
if (resource.getCombinedStatus() < 10) {
queryWrapper.eq(FirstAidResource::getStatus, resource.getCombinedStatus());
} else {
queryWrapper.eq(FirstAidResource::getAuditStatus, resource.getCombinedStatus());
}
}
// 标签过滤
if (StrUtil.isNotBlank(resource.getTags())) {
String[] tagIds = resource.getTags().split(",");
for (String tagId : tagIds) {
if (StrUtil.isNotBlank(tagId)) {
queryWrapper.like(FirstAidResource::getTags, tagId.trim());
}
}
}
queryWrapper.eq(FirstAidResource::getDelFlag, CommonConstant.DEL_FLAG_0);
queryWrapper.orderByDesc(FirstAidResource::getCreateTime);
IPage<FirstAidResource> result = page(page, queryWrapper);
fillResourceExtras(result.getRecords(), userId);
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) {
return userActionService.recordRead(id, userId);
}
@Override
public FirstAidResource getDetail(String id, String userId) {
FirstAidResource resource = getById(id);
if (resource != null) {
fillResourceExtras(Collections.singletonList(resource), userId);
}
return resource;
}
private void fillResourceExtras(List<FirstAidResource> resources, String userId) {
if (CollectionUtil.isEmpty(resources)) {
return;
}
List<String> resourceIds = resources.stream().map(FirstAidResource::getId).collect(Collectors.toList());
// 未登录用户无需查询收藏状态
List<String> favoritedIds = StrUtil.isNotBlank(userId)
? userActionService.getFavoritedResourceIds(userId, resourceIds)
: Collections.emptyList();
for (FirstAidResource resource : resources) {
if (StrUtil.isNotBlank(resource.getCategoryId())) {
FirstAidResourceCategory category = categoryMapper.selectById(resource.getCategoryId());
if (category != null) {
resource.setCategoryName(category.getName());
}
}
if (StrUtil.isNotBlank(resource.getTags())) {
List<String> tagNames = new ArrayList<>();
String[] tagIds = resource.getTags().split(",");
for (String tagId : tagIds) {
String trimmedId = tagId.trim();
if (StrUtil.isNotBlank(trimmedId)) {
FirstAidResourceCategory tag = categoryMapper.selectById(trimmedId);
if (tag != null) {
tagNames.add(tag.getName());
}
}
}
resource.setTagNameList(tagNames);
}
resource.setIsFavorited(favoritedIds.contains(resource.getId()));
}
}
}
@@ -0,0 +1,109 @@
package com.renkang.emergency.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
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.constant.FirstAidConstants;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.entity.FirstAidResourceUserAction;
import com.renkang.emergency.mapper.FirstAidResourceMapper;
import com.renkang.emergency.mapper.FirstAidResourceUserActionMapper;
import com.renkang.emergency.service.IFirstAidResourceUserActionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
/**
* @Description: 用户行为记录(阅读/收藏)
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Service
public class FirstAidResourceUserActionServiceImpl extends ServiceImpl<FirstAidResourceUserActionMapper, FirstAidResourceUserAction> implements IFirstAidResourceUserActionService {
@Autowired
private FirstAidResourceUserActionMapper actionMapper;
@Autowired
private FirstAidResourceMapper resourceMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean recordRead(String resourceId, String userId) {
// 检查是否已有阅读记录
LambdaQueryWrapper<FirstAidResourceUserAction> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FirstAidResourceUserAction::getResourceId, resourceId)
.eq(FirstAidResourceUserAction::getUserId, userId)
.eq(FirstAidResourceUserAction::getType, FirstAidConstants.ACTION_TYPE_READ);
if (ObjectUtil.isNotNull(getOne(queryWrapper, false))) {
return false;
}
// 插入阅读记录
FirstAidResourceUserAction action = new FirstAidResourceUserAction();
action.setResourceId(resourceId);
action.setUserId(userId);
action.setType(FirstAidConstants.ACTION_TYPE_READ);
save(action);
// 浏览计数+1
resourceMapper.incrementViewCount(resourceId);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean toggleFavorite(String resourceId, String userId) {
// 查是否已收藏
LambdaQueryWrapper<FirstAidResourceUserAction> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FirstAidResourceUserAction::getResourceId, resourceId)
.eq(FirstAidResourceUserAction::getUserId, userId)
.eq(FirstAidResourceUserAction::getType, FirstAidConstants.ACTION_TYPE_FAVORITE);
FirstAidResourceUserAction existing = getOne(queryWrapper, false);
if (ObjectUtil.isNotNull(existing)) {
// 已收藏 → 取消收藏
removeById(existing.getId());
resourceMapper.decrementFavoriteCount(resourceId);
return false;
} else {
// 未收藏 → 收藏
FirstAidResourceUserAction action = new FirstAidResourceUserAction();
action.setResourceId(resourceId);
action.setUserId(userId);
action.setType(FirstAidConstants.ACTION_TYPE_FAVORITE);
save(action);
resourceMapper.incrementFavoriteCount(resourceId);
return true;
}
}
@Override
public boolean isFavorited(String resourceId, String userId) {
LambdaQueryWrapper<FirstAidResourceUserAction> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FirstAidResourceUserAction::getResourceId, resourceId)
.eq(FirstAidResourceUserAction::getUserId, userId)
.eq(FirstAidResourceUserAction::getType, FirstAidConstants.ACTION_TYPE_FAVORITE);
return ObjectUtil.isNotNull(getOne(queryWrapper, false));
}
@Override
public List<String> getFavoritedResourceIds(String userId, List<String> resourceIds) {
if (CollectionUtil.isEmpty(resourceIds)) {
return Collections.emptyList();
}
return actionMapper.selectFavoritedResourceIds(userId, resourceIds);
}
@Override
public IPage<FirstAidResource> getMyFavoritePage(Page<FirstAidResource> page, String userId) {
return actionMapper.selectFavoritePage(page, userId);
}
}
@@ -0,0 +1,75 @@
package com.renkang.emergency.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.renkang.emergency.constant.FirstAidConstants;
import com.renkang.emergency.entity.FirstAidResource;
import com.renkang.emergency.entity.FirstAidResourceVersion;
import com.renkang.emergency.mapper.FirstAidResourceMapper;
import com.renkang.emergency.mapper.FirstAidResourceVersionMapper;
import com.renkang.emergency.service.IFirstAidResourceVersionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* @Description: 资源版本历史
* @Author: jeecg-boot
* @Date: 2026-06-22
* @Version: V1.0
*/
@Service
public class FirstAidResourceVersionServiceImpl extends ServiceImpl<FirstAidResourceVersionMapper, FirstAidResourceVersion> implements IFirstAidResourceVersionService {
@Autowired
private FirstAidResourceVersionMapper versionMapper;
@Autowired
private FirstAidResourceMapper resourceMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveVersion(String resourceId, String title, String content, String changeDescription) {
Integer maxNo = versionMapper.selectMaxVersionNo(resourceId);
int nextVersion = (maxNo == null) ? 1 : maxNo + 1;
FirstAidResourceVersion version = new FirstAidResourceVersion();
version.setResourceId(resourceId);
version.setVersionNo(nextVersion);
version.setTitle(title);
version.setContent(content);
version.setChangeDescription(changeDescription);
save(version);
}
@Override
public List<FirstAidResourceVersion> listVersions(String resourceId) {
return versionMapper.selectByResourceId(resourceId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void rollback(String versionId) {
FirstAidResourceVersion version = getById(versionId);
if (version == null) {
throw new RuntimeException("版本记录不存在");
}
FirstAidResource resource = resourceMapper.selectById(version.getResourceId());
if (resource == null) {
throw new RuntimeException("资源不存在");
}
// 回滚前先保存当前状态为版本快照(防止回滚操作不可逆)
saveVersion(resource.getId(), resource.getTitle(), resource.getContent(), "回滚前自动保存");
// 恢复标题和内容,并退回草稿,清除审核状态(与编辑行为一致)
resource.setTitle(version.getTitle());
resource.setContent(version.getContent());
resource.setStatus(FirstAidConstants.STATUS_DRAFT);
resource.setAuditStatus(null);
resource.setAuditRejectReason(null);
resourceMapper.updateById(resource);
}
}