diff --git a/health-emergency/health-emergency-api/src/main/java/com/renkang/emergency/service/IFirstAidPushService.java b/health-emergency/health-emergency-api/src/main/java/com/renkang/emergency/service/IFirstAidPushService.java index 2114106..c5e915d 100644 --- a/health-emergency/health-emergency-api/src/main/java/com/renkang/emergency/service/IFirstAidPushService.java +++ b/health-emergency/health-emergency-api/src/main/java/com/renkang/emergency/service/IFirstAidPushService.java @@ -6,6 +6,8 @@ import com.renkang.emergency.dto.PushBatchSendDTO; import com.renkang.emergency.dto.PushFilterDTO; import com.renkang.emergency.entity.FirstAidResource; +import java.util.Map; + /** * @Description: 急救宣教推送Service * @Author: jeecg-boot @@ -24,7 +26,7 @@ public interface IFirstAidPushService { void send(String resourceId, String orderId); /** - * 批量推送 + * 批量推送,返回 {total, success, failed} 结构 */ - int batchSend(PushBatchSendDTO dto); + Map batchSend(PushBatchSendDTO dto); } diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidPushController.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidPushController.java index eeedf20..12d2128 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidPushController.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidPushController.java @@ -14,7 +14,6 @@ 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; /** @@ -81,17 +80,15 @@ public class FirstAidPushController { * 一键推送(批量) */ @AutoLog(value = "急救宣教资源-批量推送") - @Operation(summary = "一键推送", description = "批量推送资源,支持指定ID列表或按筛选条件全量推送") + @Operation(summary = "一键推送", description = "批量推送资源,支持指定ID列表或按筛选条件全量推送,返回成功/失败明细") @PostMapping(value = "/batchSend") public Result> batchSend(@RequestBody PushBatchSendDTO dto) { if (dto.getOrderId() == null || dto.getOrderId().isEmpty()) { return Result.error("工单ID不能为空"); } try { - int count = pushService.batchSend(dto); - Map result = new HashMap<>(); - result.put("count", count); - return Result.OK("已推送 " + count + " 条", result); + Map result = pushService.batchSend(dto); + return Result.OK(result); } catch (RuntimeException e) { return Result.error(e.getMessage()); } diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceCategoryController.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceCategoryController.java index 29cfd2b..9a27bd8 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceCategoryController.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceCategoryController.java @@ -58,13 +58,13 @@ public class FirstAidResourceCategoryController extends JeecgController> listRoot() { return Result.OK(categoryService.listRootCategories()); } - @Operation(summary = "急救宣教资源分类-子分类列表") + @Operation(summary = "急救宣教资源分类-标签列表(按分类)") @GetMapping(value = "/listChildren") public Result> listChildren(@RequestParam(name = "parentId") String parentId) { return Result.OK(categoryService.listChildrenByParentId(parentId)); @@ -128,13 +128,10 @@ public class FirstAidResourceCategoryController extends JeecgController deleteBatch(@RequestBody List ids) { for (String id : ids) { - try { - categoryService.deleteWithCheck(id); - } catch (RuntimeException e) { - return Result.error("ID[" + id + "]:" + e.getMessage()); - } + categoryService.deleteWithCheck(id); } return Result.OK("批量删除成功!"); } diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceController.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceController.java index 516e1e0..4d23bfb 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceController.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceController.java @@ -61,7 +61,8 @@ public class FirstAidResourceController extends JeecgController page = new Page<>(pageNo, pageSize); - String userId = GlobalUtils.getLoginUser().getId(); + LoginUser loginUser = GlobalUtils.getLoginUser(); + String userId = loginUser != null ? loginUser.getId() : null; return Result.OK(resourceService.queryPageList(entity, page, userId)); } @@ -91,7 +92,11 @@ public class FirstAidResourceController extends JeecgController delete(@RequestBody IdDTO dto) { - resourceService.removeById(dto.getId()); + try { + resourceService.deleteResource(dto.getId()); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } return Result.OK("删除成功!"); } @@ -99,7 +104,11 @@ public class FirstAidResourceController extends JeecgController deleteBatch(@RequestBody List ids) { - resourceService.removeByIds(ids); + try { + resourceService.batchDeleteResources(ids); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } return Result.OK("批量删除成功!"); } @@ -107,7 +116,11 @@ public class FirstAidResourceController extends JeecgController batchPublish(@RequestBody List ids) { - resourceService.batchPublish(ids); + try { + resourceService.batchPublish(ids); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } return Result.OK("批量发布成功!"); } @@ -115,7 +128,11 @@ public class FirstAidResourceController extends JeecgController batchTakeDown(@RequestBody List ids) { - resourceService.batchTakeDown(ids); + try { + resourceService.batchTakeDown(ids); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } return Result.OK("批量下架成功!"); } @@ -158,7 +175,8 @@ public class FirstAidResourceController extends JeecgController queryById(@RequestParam(name = "id") String id) { - String userId = GlobalUtils.getLoginUser().getId(); + LoginUser loginUser = GlobalUtils.getLoginUser(); + String userId = loginUser != null ? loginUser.getId() : null; FirstAidResource entity = resourceService.getDetail(id, userId); return entity == null ? Result.error("未找到对应数据") : Result.OK(entity); } @@ -167,7 +185,8 @@ public class FirstAidResourceController extends JeecgController incrementView(@RequestBody IdDTO dto) { - String userId = GlobalUtils.getLoginUser().getId(); + LoginUser loginUser = GlobalUtils.getLoginUser(); + String userId = loginUser != null ? loginUser.getId() : null; boolean firstRead = resourceService.incrementViewCount(dto.getId(), userId); return firstRead ? Result.OK("阅读记录成功") : Result.OK("已阅读过"); } diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceTagController.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceTagController.java index 3723283..065bbc0 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceTagController.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/controller/FirstAidResourceTagController.java @@ -128,13 +128,10 @@ public class FirstAidResourceTagController extends JeecgController deleteBatch(@RequestBody List ids) { for (String id : ids) { - try { - categoryService.deleteWithCheck(id); - } catch (RuntimeException e) { - return Result.error("ID[" + id + "]:" + e.getMessage()); - } + categoryService.deleteWithCheck(id); } return Result.OK("批量删除成功!"); } diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/mapper/FirstAidResourceCategoryMapper.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/mapper/FirstAidResourceCategoryMapper.java index 607c0ea..47244eb 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/mapper/FirstAidResourceCategoryMapper.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/mapper/FirstAidResourceCategoryMapper.java @@ -18,9 +18,9 @@ import java.util.List; public interface FirstAidResourceCategoryMapper extends BaseMapper { /** - * 查询某分类下的子分类/标签数量(级联删除校验用,含TYPE=1子分类和TYPE=2标签) + * 查询某分类下的标签数量(级联删除校验用,TYPE=2) */ - @Select("SELECT COUNT(*) FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE PARENT_ID = #{parentId} AND DEL_FLAG = 0") + @Select("SELECT COUNT(*) FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE PARENT_ID = #{parentId} AND TYPE = 2 AND DEL_FLAG = 0") Integer countChildrenByParentId(@Param("parentId") String parentId); /** @@ -32,19 +32,13 @@ public interface FirstAidResourceCategoryMapper extends BaseMapper selectAllCategoriesAndTags(); - - /** - * 按父ID查询子节点(含子分类TYPE=1和标签TYPE=2) - */ - @Select("SELECT * FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE PARENT_ID = #{parentId} AND STATUS = 1 AND DEL_FLAG = 0 ORDER BY TYPE ASC, SORT_NO ASC") + @Select("SELECT * FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE PARENT_ID = #{parentId} AND TYPE = 2 AND STATUS = 1 AND DEL_FLAG = 0 ORDER BY SORT_NO ASC") List selectByParentId(@Param("parentId") String parentId); /** @@ -63,12 +57,6 @@ public interface FirstAidResourceCategoryMapper extends BaseMapper") List selectByTypeAndParent(@Param("type") Integer type, @Param("parentId") String parentId); - /** - * 按父分类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 selectChildrenByParentId(@Param("parentId") String parentId); - /** * 查询所有根分类(PARENT_ID IS NULL) */ diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/mapper/FirstAidResourceVersionMapper.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/mapper/FirstAidResourceVersionMapper.java index 1e74488..d028f30 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/mapper/FirstAidResourceVersionMapper.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/mapper/FirstAidResourceVersionMapper.java @@ -28,4 +28,10 @@ public interface FirstAidResourceVersionMapper extends BaseMapper selectByResourceId(@Param("resourceId") String resourceId); + + /** + * 查询某资源的所有非当前版本ID(按版本号降序),用于清理旧版本 + */ + @Select("SELECT ID FROM QH_EME_FIRST_AID_RESOURCE_VERSION WHERE RESOURCE_ID = #{resourceId} AND IS_CURRENT = 0 ORDER BY VERSION_NO DESC") + List selectNonCurrentVersionIds(@Param("resourceId") String resourceId); } diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/IFirstAidResourceCategoryService.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/IFirstAidResourceCategoryService.java index 47b6e3f..f098af5 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/IFirstAidResourceCategoryService.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/IFirstAidResourceCategoryService.java @@ -14,27 +14,17 @@ import java.util.List; public interface IFirstAidResourceCategoryService extends IService { /** - * 查询所有有效分类(扁平,一级分类) - */ - List listCategories(); - - /** - * 查询所有分类和标签(扁平,通过parentId组装树) - */ - List listAllCategoriesAndTags(); - - /** - * 按父ID查询子节点(含子分类和标签,树形展开用) + * 按父分类ID查询标签列表(TYPE=2) */ List listByParentId(String parentId); /** - * 查询根分类列表(树形结构顶层) + * 查询所有分类(扁平,无子分类) */ List listRootCategories(); /** - * 按父分类ID查询子分类 + * 按父分类ID查询标签(树形展开用) */ List listChildrenByParentId(String parentId); diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/IFirstAidResourceService.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/IFirstAidResourceService.java index 314a8d5..2af1b00 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/IFirstAidResourceService.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/IFirstAidResourceService.java @@ -37,10 +37,10 @@ public interface IFirstAidResourceService extends IService { /** 审核驳回 */ void reject(String id, String rejectReason); - /** 批量发布 */ + /** 批量发布(前置校验:全部审核通过才执行,否则抛异常) */ void batchPublish(List ids); - /** 批量下架 */ + /** 批量下架(前置校验:全部已发布才执行,否则抛异常) */ void batchTakeDown(List ids); IPage queryPageList(FirstAidResource resource, Page page, String userId); @@ -49,6 +49,12 @@ public interface IFirstAidResourceService extends IService { FirstAidResource getDetail(String id, String userId); + /** 删除资源(已发布状态不可删除) */ + void deleteResource(String id); + + /** 批量删除资源(前置校验:全部非已发布才执行,否则抛异常) */ + void batchDeleteResources(List ids); + /** 批量转换实体为导出VO(含分类、标签翻译) */ List convertToExportVO(List resources); } diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidPushServiceImpl.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidPushServiceImpl.java index 79d2c30..6e9e0c6 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidPushServiceImpl.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidPushServiceImpl.java @@ -19,10 +19,12 @@ 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.common.system.vo.LoginUser; 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.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.*; @@ -63,14 +65,22 @@ public class FirstAidPushServiceImpl implements IFirstAidPushService { // 查询当前工单已推送的资源ID集合 Set pushedIds = getPushedResourceIds(orderId); + // 批量查询分类名称,避免 N+1 + List categoryIds = result.getRecords().stream() + .map(FirstAidResource::getCategoryId) + .filter(StrUtil::isNotBlank) + .distinct() + .collect(Collectors.toList()); + java.util.Map categoryNameMap = CollectionUtil.isEmpty(categoryIds) + ? Collections.emptyMap() + : categoryMapper.selectBatchIds(categoryIds).stream() + .collect(Collectors.toMap(FirstAidResourceCategory::getId, FirstAidResourceCategory::getName, (a, b) -> a)); + // 填充推送状态 + 分类名称 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()); - } + resource.setCategoryName(categoryNameMap.get(resource.getCategoryId())); } } @@ -78,7 +88,7 @@ public class FirstAidPushServiceImpl implements IFirstAidPushService { } @Override - @Transactional(rollbackFor = Exception.class) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void send(String resourceId, String orderId) { // 1. 校验资源 FirstAidResource resource = resourceMapper.selectById(resourceId); @@ -108,25 +118,29 @@ public class FirstAidPushServiceImpl implements IFirstAidPushService { } // 4. 保存推送记录 + LoginUser loginUser = GlobalUtils.getLoginUser(); 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()); + if (loginUser != null) { + record.setPushBy(loginUser.getRealname()); + record.setPushById(loginUser.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()); + loginUser != null ? loginUser.getId() : null, + loginUser != null ? loginUser.getRealname() : null); } @Override @Transactional(rollbackFor = Exception.class) - public int batchSend(PushBatchSendDTO dto) { + public Map batchSend(PushBatchSendDTO dto) { List resourceIds = dto.getResourceIds(); // 方式二:按筛选条件查询 @@ -141,7 +155,7 @@ public class FirstAidPushServiceImpl implements IFirstAidPushService { } int successCount = 0; - List errors = new ArrayList<>(); + List> errors = new ArrayList<>(); for (String resourceId : resourceIds) { try { @@ -149,19 +163,29 @@ public class FirstAidPushServiceImpl implements IFirstAidPushService { self.send(resourceId, dto.getOrderId()); successCount++; } catch (RuntimeException e) { - errors.add(resourceId + ":" + e.getMessage()); + Map err = new HashMap<>(); + err.put("resourceId", resourceId); + err.put("message", e.getMessage()); + errors.add(err); } } if (!errors.isEmpty()) { log.warn("批量推送部分失败: {}/{} 条, 失败原因: {}", - successCount, resourceIds.size(), String.join("; ", errors)); + successCount, resourceIds.size(), errors); } - return successCount; + Map result = new HashMap<>(); + result.put("total", resourceIds.size()); + result.put("success", successCount); + result.put("failed", errors); + return result; } private Set getPushedResourceIds(String orderId) { + if (StrUtil.isBlank(orderId)) { + return Collections.emptySet(); + } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(FirstAidPushRecord::getOrderId, orderId) .select(FirstAidPushRecord::getResourceId); diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceCategoryServiceImpl.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceCategoryServiceImpl.java index 3631c03..594a0dc 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceCategoryServiceImpl.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceCategoryServiceImpl.java @@ -2,6 +2,7 @@ package com.renkang.emergency.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.renkang.emergency.constant.FirstAidConstants; +import org.springframework.transaction.annotation.Transactional; import com.renkang.emergency.entity.FirstAidResourceCategory; import com.renkang.emergency.mapper.FirstAidResourceCategoryMapper; import com.renkang.emergency.mapper.FirstAidResourceMapper; @@ -26,39 +27,13 @@ public class FirstAidResourceCategoryServiceImpl extends ServiceImpl listCategories() { - List list = categoryMapper.selectByType(FirstAidConstants.TYPE_CATEGORY); - fillResourceCounts(list); - return list; - } - - @Override - public List listAllCategoriesAndTags() { - List list = categoryMapper.selectAllCategoriesAndTags(); - for (FirstAidResourceCategory item : list) { - if (FirstAidConstants.TYPE_CATEGORY.equals(item.getType())) { - Integer count = resourceMapper.countByCategoryId(item.getId()); - item.setResourceCount(count != null ? count : 0); - } else { - Integer count = categoryMapper.countResourcesByTagId(item.getId()); - item.setResourceCount(count != null ? count : 0); - } - } - return list; - } - @Override public List listByParentId(String parentId) { + // selectByParentId 仅查 TYPE=2(标签),无子分类 List list = categoryMapper.selectByParentId(parentId); - for (FirstAidResourceCategory item : list) { - if (FirstAidConstants.TYPE_CATEGORY.equals(item.getType())) { - Integer count = resourceMapper.countByCategoryId(item.getId()); - item.setResourceCount(count != null ? count : 0); - } else { - Integer count = categoryMapper.countResourcesByTagId(item.getId()); - item.setResourceCount(count != null ? count : 0); - } + for (FirstAidResourceCategory tag : list) { + Integer count = categoryMapper.countResourcesByTagId(tag.getId()); + tag.setResourceCount(count != null ? count : 0); } return list; } @@ -117,6 +92,7 @@ public class FirstAidResourceCategoryServiceImpl extends ServiceImpl 0) { + Integer tagCount = categoryMapper.countChildrenByParentId(id); + if (tagCount != null && tagCount > 0) { throw new RuntimeException("该分类下存在标签,无法删除"); } // 分类:检查是否有资源 diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceServiceImpl.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceServiceImpl.java index e698aa5..b9e4220 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceServiceImpl.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceServiceImpl.java @@ -17,15 +17,19 @@ 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.common.system.vo.LoginUser; 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.Arrays; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** @@ -63,6 +67,7 @@ public class FirstAidResourceServiceImpl extends ServiceImpl ids) { + // 前置校验:全部审核通过才允许批量发布 for (String id : ids) { FirstAidResource resource = getById(id); if (resource == null) { - continue; + throw new RuntimeException("资源[" + id + "]不存在"); } if (!FirstAidConstants.AUDIT_STATUS_APPROVED.equals(resource.getAuditStatus())) { - continue; + throw new RuntimeException("资源[" + resource.getTitle() + "]尚未通过审核,无法发布"); } + } + for (String id : ids) { + FirstAidResource resource = getById(id); resource.setStatus(FirstAidConstants.STATUS_PUBLISHED); updateById(resource); } @@ -201,12 +231,51 @@ public class FirstAidResourceServiceImpl extends ServiceImpl ids) { + // 前置校验:全部已发布才允许批量下架 for (String id : ids) { FirstAidResource resource = getById(id); - if (resource != null) { - resource.setStatus(FirstAidConstants.STATUS_DOWN); - updateById(resource); + if (resource == null) { + throw new RuntimeException("资源[" + id + "]不存在"); } + if (!FirstAidConstants.STATUS_PUBLISHED.equals(resource.getStatus())) { + throw new RuntimeException("资源[" + resource.getTitle() + "]非已发布状态,无法下架"); + } + } + for (String id : ids) { + FirstAidResource resource = getById(id); + resource.setStatus(FirstAidConstants.STATUS_DOWN); + updateById(resource); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteResource(String id) { + FirstAidResource resource = getById(id); + if (resource == null) { + throw new RuntimeException("资源不存在"); + } + if (FirstAidConstants.STATUS_PUBLISHED.equals(resource.getStatus())) { + throw new RuntimeException("已发布的资源不可删除,请先下架"); + } + removeById(id); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void batchDeleteResources(List ids) { + // 前置校验:全部非已发布才允许删除 + for (String id : ids) { + FirstAidResource resource = getById(id); + if (resource == null) { + throw new RuntimeException("资源[" + id + "]不存在"); + } + if (FirstAidConstants.STATUS_PUBLISHED.equals(resource.getStatus())) { + throw new RuntimeException("资源[" + resource.getTitle() + "]已发布,请先下架再删除"); + } + } + for (String id : ids) { + removeById(id); } } @@ -240,12 +309,12 @@ public class FirstAidResourceServiceImpl extends ServiceImpl allCategoryIds = new ArrayList<>(); for (FirstAidResource resource : resources) { if (StrUtil.isNotBlank(resource.getCategoryId())) { - FirstAidResourceCategory category = categoryMapper.selectById(resource.getCategoryId()); - if (category != null) { - resource.setCategoryName(category.getName()); + allCategoryIds.add(resource.getCategoryId()); + } + if (StrUtil.isNotBlank(resource.getTags())) { + for (String tagId : resource.getTags().split(",")) { + String trimmed = tagId.trim(); + if (StrUtil.isNotBlank(trimmed)) { + allCategoryIds.add(trimmed); + } } } + } + // 批量查询,避免 N+1 + java.util.Map nameMap = CollectionUtil.isEmpty(allCategoryIds) + ? Collections.emptyMap() + : categoryMapper.selectBatchIds(allCategoryIds).stream() + .collect(Collectors.toMap(FirstAidResourceCategory::getId, FirstAidResourceCategory::getName, (a, b) -> a)); + + for (FirstAidResource resource : resources) { + if (StrUtil.isNotBlank(resource.getCategoryId())) { + resource.setCategoryName(nameMap.get(resource.getCategoryId())); + } if (StrUtil.isNotBlank(resource.getTags())) { List 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()); + for (String tagId : resource.getTags().split(",")) { + String trimmed = tagId.trim(); + if (StrUtil.isNotBlank(trimmed)) { + String tagName = nameMap.get(trimmed); + if (tagName != null) { + tagNames.add(tagName); } } } @@ -327,6 +413,53 @@ public class FirstAidResourceServiceImpl extends ServiceImpl VALID_CONTENT_TYPES = new HashSet<>(Arrays.asList( + FirstAidConstants.CONTENT_TYPE_IMAGE_TEXT, + FirstAidConstants.CONTENT_TYPE_VIDEO, + FirstAidConstants.CONTENT_TYPE_AUDIO, + FirstAidConstants.CONTENT_TYPE_PDF + )); + + /** + * 校验资源的分类和标签引用合法性 + */ + private void validateResource(FirstAidResource resource) { + // 校验内容类型 + if (StrUtil.isNotBlank(resource.getContentType()) + && !VALID_CONTENT_TYPES.contains(resource.getContentType())) { + throw new RuntimeException("无效的内容类型: " + resource.getContentType()); + } + // 校验分类存在且为分类类型 + if (StrUtil.isNotBlank(resource.getCategoryId())) { + FirstAidResourceCategory category = categoryMapper.selectById(resource.getCategoryId()); + if (category == null || !FirstAidConstants.TYPE_CATEGORY.equals(category.getType())) { + throw new RuntimeException("所选分类不存在"); + } + } + // 校验标签存在且为标签类型 + if (StrUtil.isNotBlank(resource.getTags())) { + String[] parts = resource.getTags().split(","); + Set tagIdSet = new HashSet<>(); + for (String p : parts) { + String trimmed = p.trim(); + if (StrUtil.isNotBlank(trimmed)) { + tagIdSet.add(trimmed); + } + } + if (CollectionUtil.isNotEmpty(tagIdSet)) { + List tags = categoryMapper.selectBatchIds(new ArrayList<>(tagIdSet)); + if (tags.size() != tagIdSet.size()) { + throw new RuntimeException("所选标签不存在"); + } + for (FirstAidResourceCategory tag : tags) { + if (!FirstAidConstants.TYPE_TAG.equals(tag.getType())) { + throw new RuntimeException("标签[" + tag.getName() + "]类型不正确"); + } + } + } + } + } + /** * 计算导出用的状态文本(审核状态优先于发布状态) */ diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceUserActionServiceImpl.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceUserActionServiceImpl.java index af8bd71..e53d373 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceUserActionServiceImpl.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceUserActionServiceImpl.java @@ -37,23 +37,15 @@ public class FirstAidResourceUserActionServiceImpl extends ServiceImpl 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 + try { + save(action); + } catch (org.springframework.dao.DuplicateKeyException e) { + return false; // 已有阅读记录 + } resourceMapper.incrementViewCount(resourceId); return true; } @@ -61,27 +53,23 @@ public class FirstAidResourceUserActionServiceImpl extends ServiceImpl 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); + FirstAidResourceUserAction action = new FirstAidResourceUserAction(); + action.setResourceId(resourceId); + action.setUserId(userId); + action.setType(FirstAidConstants.ACTION_TYPE_FAVORITE); + try { save(action); resourceMapper.incrementFavoriteCount(resourceId); return true; + } catch (org.springframework.dao.DuplicateKeyException e) { + // 已收藏 → 取消收藏 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FirstAidResourceUserAction::getResourceId, resourceId) + .eq(FirstAidResourceUserAction::getUserId, userId) + .eq(FirstAidResourceUserAction::getType, FirstAidConstants.ACTION_TYPE_FAVORITE); + remove(queryWrapper); + resourceMapper.decrementFavoriteCount(resourceId); + return false; } } diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceVersionServiceImpl.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceVersionServiceImpl.java index ac17f0c..d98a46b 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceVersionServiceImpl.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/service/impl/FirstAidResourceVersionServiceImpl.java @@ -1,5 +1,6 @@ package com.renkang.emergency.service.impl; +import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.renkang.emergency.constant.FirstAidConstants; @@ -23,6 +24,9 @@ import java.util.List; @Service public class FirstAidResourceVersionServiceImpl extends ServiceImpl implements IFirstAidResourceVersionService { + /** 每个资源最多保留的版本数(含当前版本) */ + private static final int MAX_VERSIONS = 20; + @Autowired private FirstAidResourceVersionMapper versionMapper; @@ -52,6 +56,24 @@ public class FirstAidResourceVersionServiceImpl extends ServiceImpl nonCurrentIds = versionMapper.selectNonCurrentVersionIds(resourceId); + if (nonCurrentIds.size() <= MAX_VERSIONS - 1) { + return; // 未超上限,无需清理 + } + // 保留最近 MAX_VERSIONS-1 条非当前版本,删除其余 + List toDelete = nonCurrentIds.subList(MAX_VERSIONS - 1, nonCurrentIds.size()); + if (CollUtil.isNotEmpty(toDelete)) { + removeByIds(toDelete); + } } @Override