Browse Source

通知公告功能复制

徐展城 1 day ago
parent
commit
fd8f2925c4
19 changed files with 1567 additions and 0 deletions
  1. 103 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/announcement/AnnouncementController.java
  2. 52 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/announcement/vo/AnnouncementPageReqVO.java
  3. 79 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/announcement/vo/AnnouncementRespVO.java
  4. 36 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/announcement/vo/AnnouncementSaveReqVO.java
  5. 89 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/ReadConfirmController.java
  6. 53 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/vo/ReadConfirmPageReqVO.java
  7. 21 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/vo/ReadConfirmPageRespVO.java
  8. 51 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/vo/ReadConfirmRespVO.java
  9. 31 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/vo/ReadConfirmSaveReqVO.java
  10. 69 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/dal/dataobject/announcement/AnnouncementDO.java
  11. 56 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/dal/dataobject/readconfirm/ReadConfirmDO.java
  12. 33 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/dal/mysql/announcement/AnnouncementMapper.java
  13. 50 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/dal/mysql/readconfirm/ReadConfirmMapper.java
  14. 55 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/service/announcement/AnnouncementService.java
  15. 469 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/service/announcement/AnnouncementServiceImpl.java
  16. 67 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/service/readconfirm/ReadConfirmService.java
  17. 195 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/service/readconfirm/ReadConfirmServiceImpl.java
  18. 11 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/resources/mapper/announcement/AnnouncementMapper.xml
  19. 47 0
      tz-module-pressure2/tz-module-pressure2-biz/src/main/resources/mapper/readconfirm/ReadConfirmMapper.xml

+ 103 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/announcement/AnnouncementController.java

@@ -0,0 +1,103 @@
+package cn.start.tz.module.pressure2.controller.admin.announcement;
+
+import cn.start.tz.framework.apilog.core.annotation.ApiAccessLog;
+import cn.start.tz.framework.common.pojo.CommonResult;
+import cn.start.tz.framework.common.pojo.PageParam;
+import cn.start.tz.framework.common.pojo.PageResult;
+import cn.start.tz.framework.common.util.object.BeanUtils;
+import cn.start.tz.framework.datapermission.core.annotation.DataPermission;
+import cn.start.tz.framework.excel.core.util.ExcelUtils;
+import cn.start.tz.module.pressure2.controller.admin.announcement.vo.AnnouncementPageReqVO;
+import cn.start.tz.module.pressure2.controller.admin.announcement.vo.AnnouncementRespVO;
+import cn.start.tz.module.pressure2.controller.admin.announcement.vo.AnnouncementSaveReqVO;
+import cn.start.tz.module.pressure2.dal.dataobject.announcement.AnnouncementDO;
+import cn.start.tz.module.pressure2.service.announcement.AnnouncementService;
+import cn.start.tz.module.system.api.user.AdminUserApi;
+import cn.start.tz.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+import static cn.start.tz.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.start.tz.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 公告")
+@RestController
+@RequestMapping("/pressure2/announcement")
+@Validated
+public class AnnouncementController {
+
+    @Resource
+    private AnnouncementService announcementService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建公告")
+//
+    public CommonResult<String> createAnnouncement(@Valid @RequestBody AnnouncementSaveReqVO createReqVO) {
+        return success(announcementService.createAnnouncement(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新公告")
+//
+    public CommonResult<Boolean> updateAnnouncement(@Valid @RequestBody AnnouncementSaveReqVO updateReqVO) {
+        announcementService.updateAnnouncement(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除公告")
+    @Parameter(name = "id", description = "编号", required = true)
+//
+    public CommonResult<Boolean> deleteAnnouncement(@RequestParam("id") String id) {
+        announcementService.deleteAnnouncement(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得公告")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+//
+    public CommonResult<AnnouncementRespVO> getAnnouncement(@RequestParam("id") String id) {
+        AnnouncementDO announcement = announcementService.getAnnouncement(id);
+        CommonResult<AdminUserRespDTO> user = adminUserApi.getUser(announcement.getCreator());
+        AdminUserRespDTO checkedData = user.getCheckedData();
+        announcement.setAvatar(checkedData.getAvatar());
+
+        return success(BeanUtils.toBean(announcement, AnnouncementRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得公告分页")
+    @DataPermission(enable = false) // 关闭数据权限,避免只查看自己时,查询不到部门。
+//
+    public CommonResult<PageResult<AnnouncementRespVO>> getAnnouncementPage(@Valid AnnouncementPageReqVO pageReqVO) {
+        PageResult<AnnouncementDO> pageResult = announcementService.getAnnouncementPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AnnouncementRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出公告 Excel")
+//
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportAnnouncementExcel(@Valid AnnouncementPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<AnnouncementDO> list = announcementService.getAnnouncementPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "公告.xls", "数据", AnnouncementRespVO.class,
+                        BeanUtils.toBean(list, AnnouncementRespVO.class));
+    }
+
+}

+ 52 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/announcement/vo/AnnouncementPageReqVO.java

@@ -0,0 +1,52 @@
+package cn.start.tz.module.pressure2.controller.admin.announcement.vo;
+
+import cn.start.tz.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.start.tz.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 公告分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AnnouncementPageReqVO extends PageParam {
+
+    @Schema(description = "部门ID", example = "19044")
+    private String deptId;
+
+    @Schema(description = "公告标题")
+    private String title;
+
+    @Schema(description = "公告类型", example = "1")
+    private Integer type;
+
+    @Schema(description = "公告内容")
+    private String content;
+
+    @Schema(description = "附件地址", example = "https://www.iocoder.cn")
+    private String fileUrl;
+
+    @Schema(description = "附件名称", example = "张三")
+    private String fileName;
+
+    @Schema(description = "创建人")
+    private String creator;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "更新者")
+    private String updater;
+
+    @Schema(description = "更新时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] updateTime;
+
+}

+ 79 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/announcement/vo/AnnouncementRespVO.java

@@ -0,0 +1,79 @@
+package cn.start.tz.module.pressure2.controller.admin.announcement.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 公告 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class AnnouncementRespVO {
+
+    @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28994")
+    @ExcelProperty("主键ID")
+    private String id;
+
+    @Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "19044")
+    @ExcelProperty("部门ID")
+    private String deptId;
+
+    @Schema(description = "公告标题", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("公告标题")
+    private String title;
+
+    @Schema(description = "公告类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("公告类型")
+    private Integer type;
+
+    @Schema(description = "公告内容")
+    @ExcelProperty("公告内容")
+    private String content;
+
+    @Schema(description = "附件地址", example = "https://www.iocoder.cn")
+    @ExcelProperty("附件地址")
+    private String fileUrl;
+
+    @Schema(description = "附件名称", example = "张三")
+    @ExcelProperty("附件名称")
+    private String fileName;
+
+    @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建人")
+    private String creator;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新者")
+    @ExcelProperty("更新者")
+    private String updater;
+
+    @Schema(description = "更新时间")
+    @ExcelProperty("更新时间")
+    private LocalDateTime updateTime;
+
+    /**
+     * 阅读状态(0-未读 1-已读)
+     */
+    @Schema(description = "阅读状态")
+    @ExcelProperty("阅读状态")
+    private Integer readStatus;
+
+    /**
+     * 创建人名称(非数据库字段)
+     */
+    @Schema(description = "创建人名称")
+    @ExcelProperty("创建人名称")
+    private String creatorName;
+    /**
+     * 创建人头像(非数据库字段)
+     */
+    @Schema(description = "创建人头像")
+    @ExcelProperty("创建人头像")
+    private String avatar;
+
+}

+ 36 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/announcement/vo/AnnouncementSaveReqVO.java

@@ -0,0 +1,36 @@
+package cn.start.tz.module.pressure2.controller.admin.announcement.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 公告新增/修改 Request VO")
+@Data
+public class AnnouncementSaveReqVO {
+
+    @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28994")
+    private String id;
+
+    @Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "19044")
+    @NotEmpty(message = "部门ID不能为空")
+    private String deptId;
+
+    @Schema(description = "公告标题", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "公告标题不能为空")
+    private String title;
+
+    @Schema(description = "公告类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "公告类型不能为空")
+    private Integer type;
+
+    @Schema(description = "公告内容")
+    private String content;
+
+    @Schema(description = "附件地址", example = "https://www.iocoder.cn")
+    private String fileUrl;
+
+    @Schema(description = "附件名称", example = "张三")
+    private String fileName;
+
+}

+ 89 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/ReadConfirmController.java

@@ -0,0 +1,89 @@
+package cn.start.tz.module.pressure2.controller.admin.readconfirm;
+
+import cn.start.tz.framework.apilog.core.annotation.ApiAccessLog;
+import cn.start.tz.framework.common.pojo.CommonResult;
+import cn.start.tz.framework.common.util.object.BeanUtils;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmPageReqVO;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmPageRespVO;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmRespVO;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmSaveReqVO;
+import cn.start.tz.module.pressure2.dal.dataobject.readconfirm.ReadConfirmDO;
+import cn.start.tz.module.pressure2.service.readconfirm.ReadConfirmService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+
+import static cn.start.tz.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.start.tz.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 已读确认")
+@RestController
+@RequestMapping("/pressure2/read-confirm")
+@Validated
+public class ReadConfirmController {
+
+    @Resource
+    private ReadConfirmService readConfirmService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建已读确认")
+//
+    public CommonResult<String> createReadConfirm(@Valid @RequestBody ReadConfirmSaveReqVO createReqVO) {
+        return success(readConfirmService.createReadConfirm(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新已读确认")
+//
+    public CommonResult<Boolean> updateReadConfirm(@Valid @RequestBody ReadConfirmSaveReqVO updateReqVO) {
+        readConfirmService.updateReadConfirm(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除已读确认")
+    @Parameter(name = "id", description = "编号", required = true)
+//
+    public CommonResult<Boolean> deleteReadConfirm(@RequestParam("id") String id) {
+        readConfirmService.deleteReadConfirm(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得已读确认")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+//
+    public CommonResult<ReadConfirmRespVO> getReadConfirm(@RequestParam("id") String id) {
+        ReadConfirmDO readConfirm = readConfirmService.getReadConfirm(id);
+        return success(BeanUtils.toBean(readConfirm, ReadConfirmRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得已读确认分页")
+//
+    public CommonResult<ReadConfirmPageRespVO> getReadConfirmPage(@Valid ReadConfirmPageReqVO pageReqVO) {
+        ReadConfirmPageRespVO pageResult = readConfirmService.getReadConfirmPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, ReadConfirmPageRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出已读确认 Excel")
+//
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportReadConfirmExcel(@Valid ReadConfirmPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+//        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+//        List<ReadConfirmDO> list = readConfirmService.getReadConfirmPage(pageReqVO).getList();
+//        // 导出 Excel
+//        ExcelUtils.write(response, "已读确认.xls", "数据", ReadConfirmRespVO.class,
+//                        BeanUtils.toBean(list, ReadConfirmRespVO.class));
+    }
+
+}

+ 53 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/vo/ReadConfirmPageReqVO.java

@@ -0,0 +1,53 @@
+package cn.start.tz.module.pressure2.controller.admin.readconfirm.vo;
+
+import cn.start.tz.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.start.tz.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 已读确认分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ReadConfirmPageReqVO extends PageParam {
+
+    @Schema(description = "用户ID", example = "23829")
+    private String userId;
+
+    @Schema(description = "公告表ID", example = "17610")
+    private String laboratoryAnnouncementId;
+
+    // 新增公告表的查询条件
+    @Schema(description = "公告标题")
+    private String title;
+
+    @Schema(description = "公告类型", example = "1")
+    private Integer type;
+
+    @Schema(description = "阅读状态(0-未读 1-已读)", example = "1")
+    private Integer readStatus;
+
+    @Schema(description = "部门ID", example = "5314")
+    private String deptId;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "创建人")
+    private String creator;
+
+    @Schema(description = "更新者")
+    private String updater;
+
+    @Schema(description = "更新时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] updateTime;
+
+}

+ 21 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/vo/ReadConfirmPageRespVO.java

@@ -0,0 +1,21 @@
+package cn.start.tz.module.pressure2.controller.admin.readconfirm.vo;
+
+import cn.start.tz.framework.common.pojo.PageResult;
+import cn.start.tz.module.pressure2.dal.dataobject.readconfirm.ReadConfirmDO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class ReadConfirmPageRespVO {
+
+    @Schema(description = "分页数据")
+    private PageResult<ReadConfirmDO> pageData;
+
+    @Schema(description = "已读数量")
+    private Long readCount;
+
+    public ReadConfirmPageRespVO(PageResult<ReadConfirmDO> pageData, Long readCount) {
+        this.pageData = pageData;
+        this.readCount = readCount;
+    }
+}

+ 51 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/vo/ReadConfirmRespVO.java

@@ -0,0 +1,51 @@
+package cn.start.tz.module.pressure2.controller.admin.readconfirm.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 已读确认 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ReadConfirmRespVO {
+
+    @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "17741")
+    @ExcelProperty("主键ID")
+    private String id;
+
+    @Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23829")
+    @ExcelProperty("用户ID")
+    private String userId;
+
+    @Schema(description = "公告表ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "17610")
+    @ExcelProperty("公告表ID")
+    private String laboratoryAnnouncementId;
+
+    @Schema(description = "阅读状态(0-未读 1-已读)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("阅读状态(0-未读 1-已读)")
+    private Integer readStatus;
+
+    @Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "5314")
+    @ExcelProperty("部门ID")
+    private String deptId;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "创建人")
+    @ExcelProperty("创建人")
+    private String creator;
+
+    @Schema(description = "更新者")
+    @ExcelProperty("更新者")
+    private String updater;
+
+    @Schema(description = "更新时间")
+    @ExcelProperty("更新时间")
+    private LocalDateTime updateTime;
+
+}

+ 31 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/controller/admin/readconfirm/vo/ReadConfirmSaveReqVO.java

@@ -0,0 +1,31 @@
+package cn.start.tz.module.pressure2.controller.admin.readconfirm.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 已读确认新增/修改 Request VO")
+@Data
+public class ReadConfirmSaveReqVO {
+
+    @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "17741")
+    private String id;
+
+    @Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23829")
+//    @NotEmpty(message = "用户ID不能为空")
+    private String userId;
+
+    @Schema(description = "公告表ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "17610")
+    @NotEmpty(message = "公告表ID不能为空")
+    private String laboratoryAnnouncementId;
+
+    @Schema(description = "阅读状态(0-未读 1-已读)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "阅读状态(0-未读 1-已读)不能为空")
+    private Integer readStatus;
+
+    @Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "5314")
+//    @NotEmpty(message = "部门ID不能为空")
+    private String deptId;
+
+}

+ 69 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/dal/dataobject/announcement/AnnouncementDO.java

@@ -0,0 +1,69 @@
+package cn.start.tz.module.pressure2.dal.dataobject.announcement;
+
+import cn.start.tz.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.*;
+
+/**
+ * 公告 DO
+ *
+ * @author 特种管理员
+ */
+@TableName("laboratory_announcement")
+@KeySequence("laboratory_announcement_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AnnouncementDO extends BaseDO {
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_UUID)
+    private String id;
+    /**
+     * 部门ID
+     */
+    private String deptId;
+    /**
+     * 公告标题
+     */
+    private String title;
+    /**
+     * 公告类型
+     */
+    private Integer type;
+    /**
+     * 公告内容
+     */
+    private String content;
+    /**
+     * 附件地址
+     */
+    private String fileUrl;
+    /**
+     * 附件名称
+     */
+    private String fileName;
+
+    /**
+     * 阅读状态(0-未读 1-已读)
+     */
+    @TableField(exist = false)
+    private Integer readStatus;
+
+    /**
+     * 创建人名称(非数据库字段)
+     */
+    @TableField(exist = false)
+    private String creatorName;
+    /**
+     * 创建人头像(非数据库字段)
+     */
+    @TableField(exist = false)
+    private String avatar;
+
+}

+ 56 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/dal/dataobject/readconfirm/ReadConfirmDO.java

@@ -0,0 +1,56 @@
+package cn.start.tz.module.pressure2.dal.dataobject.readconfirm;
+
+import cn.start.tz.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.*;
+
+/**
+ * 已读确认 DO
+ *
+ * @author 特种管理员
+ */
+@TableName("laboratory_read_confirm")
+@KeySequence("laboratory_read_confirm_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ReadConfirmDO extends BaseDO {
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_UUID)
+    private String id;
+    /**
+     * 用户ID
+     */
+    private String userId;
+    /**
+     * 公告表ID
+     */
+    private String laboratoryAnnouncementId;
+    /**
+     * 阅读状态(0-未读 1-已读)
+     */
+    private Integer readStatus;
+    /**
+     * 部门ID
+     */
+    private String deptId;
+
+    /**
+     * 用户名称(非数据库字段)
+     */
+    @TableField(exist = false)
+    private String userName;
+
+    /**
+     * 部门名称(非数据库字段)
+     */
+    @TableField(exist = false)
+    private String deptName;
+
+}

+ 33 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/dal/mysql/announcement/AnnouncementMapper.java

@@ -0,0 +1,33 @@
+package cn.start.tz.module.pressure2.dal.mysql.announcement;
+
+import cn.start.tz.framework.common.pojo.PageResult;
+import cn.start.tz.framework.mybatis.core.mapper.BaseMapperX;
+import cn.start.tz.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.start.tz.module.pressure2.controller.admin.announcement.vo.AnnouncementPageReqVO;
+import cn.start.tz.module.pressure2.dal.dataobject.announcement.AnnouncementDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 公告 Mapper
+ *
+ * @author 特种管理员
+ */
+@Mapper
+public interface AnnouncementMapper extends BaseMapperX<AnnouncementDO> {
+
+    default PageResult<AnnouncementDO> selectPage(AnnouncementPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AnnouncementDO>()
+                .eqIfPresent(AnnouncementDO::getDeptId, reqVO.getDeptId())
+                .likeIfPresent(AnnouncementDO::getTitle, reqVO.getTitle())
+                .eqIfPresent(AnnouncementDO::getType, reqVO.getType())
+                .likeIfPresent(AnnouncementDO::getContent, reqVO.getContent())
+                .eqIfPresent(AnnouncementDO::getFileUrl, reqVO.getFileUrl())
+                .likeIfPresent(AnnouncementDO::getFileName, reqVO.getFileName())
+                .eqIfPresent(AnnouncementDO::getCreator, reqVO.getCreator())
+                .betweenIfPresent(AnnouncementDO::getCreateTime, reqVO.getCreateTime())
+                .eqIfPresent(AnnouncementDO::getUpdater, reqVO.getUpdater())
+                .betweenIfPresent(AnnouncementDO::getUpdateTime, reqVO.getUpdateTime())
+                .orderByDesc(AnnouncementDO::getCreateTime));
+    }
+
+}

+ 50 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/dal/mysql/readconfirm/ReadConfirmMapper.java

@@ -0,0 +1,50 @@
+package cn.start.tz.module.pressure2.dal.mysql.readconfirm;
+
+import cn.start.tz.framework.common.pojo.PageResult;
+import cn.start.tz.framework.mybatis.core.mapper.BaseMapperX;
+import cn.start.tz.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmPageReqVO;
+import cn.start.tz.module.pressure2.dal.dataobject.readconfirm.ReadConfirmDO;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.data.repository.query.Param;
+
+/**
+ * 已读确认 Mapper
+ *
+ * @author 特种管理员
+ */
+@Mapper
+public interface ReadConfirmMapper extends BaseMapperX<ReadConfirmDO> {
+
+    default PageResult<ReadConfirmDO> selectPage(ReadConfirmPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ReadConfirmDO>()
+                .eqIfPresent(ReadConfirmDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(ReadConfirmDO::getLaboratoryAnnouncementId, reqVO.getLaboratoryAnnouncementId())
+                .eqIfPresent(ReadConfirmDO::getReadStatus, reqVO.getReadStatus())
+                .eqIfPresent(ReadConfirmDO::getDeptId, reqVO.getDeptId())
+                .betweenIfPresent(ReadConfirmDO::getCreateTime, reqVO.getCreateTime())
+                .eqIfPresent(ReadConfirmDO::getCreator, reqVO.getCreator())
+                .eqIfPresent(ReadConfirmDO::getUpdater, reqVO.getUpdater())
+                .betweenIfPresent(ReadConfirmDO::getUpdateTime, reqVO.getUpdateTime())
+                .orderByDesc(ReadConfirmDO::getReadStatus));
+    }
+    default PageResult<ReadConfirmDO> selectPage2(ReadConfirmPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ReadConfirmDO>()
+                .eqIfPresent(ReadConfirmDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(ReadConfirmDO::getLaboratoryAnnouncementId, reqVO.getLaboratoryAnnouncementId())
+                .eqIfPresent(ReadConfirmDO::getReadStatus, reqVO.getReadStatus())
+                .eqIfPresent(ReadConfirmDO::getDeptId, reqVO.getDeptId())
+                .betweenIfPresent(ReadConfirmDO::getCreateTime, reqVO.getCreateTime())
+                .eqIfPresent(ReadConfirmDO::getCreator, reqVO.getCreator())
+                .eqIfPresent(ReadConfirmDO::getUpdater, reqVO.getUpdater())
+                .betweenIfPresent(ReadConfirmDO::getUpdateTime, reqVO.getUpdateTime())
+                .orderByAsc(ReadConfirmDO::getReadStatus)
+                .orderByDesc(ReadConfirmDO::getCreateTime)); // 创建时间降序(新的在前)
+    }
+
+    // 新增联表查询方法
+    // 修改联表查询方法,返回 IPage 而不是 PageResult
+    IPage<ReadConfirmDO> selectPageWithAnnouncement(IPage<ReadConfirmDO> page, @Param("reqVO") ReadConfirmPageReqVO reqVO);
+
+}

+ 55 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/service/announcement/AnnouncementService.java

@@ -0,0 +1,55 @@
+package cn.start.tz.module.pressure2.service.announcement;
+
+import cn.start.tz.framework.common.pojo.PageResult;
+import cn.start.tz.module.pressure2.controller.admin.announcement.vo.AnnouncementPageReqVO;
+import cn.start.tz.module.pressure2.controller.admin.announcement.vo.AnnouncementSaveReqVO;
+import cn.start.tz.module.pressure2.dal.dataobject.announcement.AnnouncementDO;
+import com.baomidou.mybatisplus.extension.service.IService;
+import jakarta.validation.Valid;
+
+/**
+ * 公告 Service 接口
+ *
+ * @author 特种管理员
+ */
+public interface AnnouncementService extends IService<AnnouncementDO>  {
+
+    /**
+     * 创建公告
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    String createAnnouncement(@Valid AnnouncementSaveReqVO createReqVO);
+
+    /**
+     * 更新公告
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateAnnouncement(@Valid AnnouncementSaveReqVO updateReqVO);
+
+    /**
+     * 删除公告
+     *
+     * @param id 编号
+     */
+    void deleteAnnouncement(String id);
+
+    /**
+     * 获得公告
+     *
+     * @param id 编号
+     * @return 公告
+     */
+    AnnouncementDO getAnnouncement(String id);
+
+    /**
+     * 获得公告分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 公告分页
+     */
+    PageResult<AnnouncementDO> getAnnouncementPage(AnnouncementPageReqVO pageReqVO);
+
+}

+ 469 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/service/announcement/AnnouncementServiceImpl.java

@@ -0,0 +1,469 @@
+package cn.start.tz.module.pressure2.service.announcement;
+
+import cn.start.tz.framework.common.pojo.CommonResult;
+import cn.start.tz.framework.common.pojo.PageResult;
+import cn.start.tz.framework.common.util.object.BeanUtils;
+import cn.start.tz.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.start.tz.module.pressure2.controller.admin.announcement.vo.AnnouncementPageReqVO;
+import cn.start.tz.module.pressure2.controller.admin.announcement.vo.AnnouncementSaveReqVO;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmPageReqVO;
+import cn.start.tz.module.pressure2.dal.dataobject.announcement.AnnouncementDO;
+import cn.start.tz.module.pressure2.dal.dataobject.readconfirm.ReadConfirmDO;
+import cn.start.tz.module.pressure2.dal.mysql.announcement.AnnouncementMapper;
+import cn.start.tz.module.pressure2.dal.mysql.readconfirm.ReadConfirmMapper;
+import cn.start.tz.module.pressure2.service.readconfirm.ReadConfirmService;
+import cn.start.tz.module.system.api.dept.DeptApi;
+import cn.start.tz.module.system.api.dept.dto.DeptRespDTO;
+import cn.start.tz.module.system.api.user.AdminUserApi;
+import cn.start.tz.module.system.api.user.dto.AdminUserRespDTO;
+import com.alibaba.cloud.commons.lang.StringUtils;
+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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.start.tz.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.start.tz.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
+import static cn.start.tz.module.pressure2.enums.ErrorCodeConstants.*;
+import static cn.start.tz.module.system.enums.ErrorCodeConstants.ANNOUNCEMENT_NOT_EXISTS;
+
+/**
+ * 公告 Service 实现类
+ *
+ * @author 特种管理员
+ */
+@Slf4j
+@Service
+@Validated
+public class AnnouncementServiceImpl extends ServiceImpl<AnnouncementMapper, AnnouncementDO> implements AnnouncementService {
+
+    @Resource
+    private AnnouncementMapper announcementMapper;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @Resource
+    private DeptApi deptApi;
+
+    @Resource
+    private ReadConfirmService readConfirmService; // 新增注入
+
+    @Resource
+    private ReadConfirmMapper readConfirmMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String createAnnouncement(AnnouncementSaveReqVO createReqVO) {
+        try {
+            log.info("开始创建公告: {}", createReqVO.getTitle());
+
+            // 插入公告,如果是多部门,前端传递的值就应该是 100,101这样
+            AnnouncementDO announcement = BeanUtils.toBean(createReqVO, AnnouncementDO.class);
+            announcementMapper.insert(announcement);
+
+            String announcementId = announcement.getId();
+            log.info("公告创建成功,ID: {}", announcementId);
+
+            // 获取部门下的所有用户
+            List<AdminUserRespDTO> users = getDistinctUsersByDepartmentAndSubDepartments(createReqVO.getDeptId());
+
+            if (users != null && !users.isEmpty()) {
+                log.info("找到 {} 个用户需要创建已读记录", users.size());
+
+                List<ReadConfirmDO> readConfirmList = new ArrayList<>();
+                for (AdminUserRespDTO user : users) {
+                    ReadConfirmDO readConfirm = ReadConfirmDO.builder()
+                            .userId(user.getId())
+                            .laboratoryAnnouncementId(announcementId)
+                            .readStatus(0)
+                            .deptId(user.getDeptId())
+                            .build();
+
+                    readConfirmList.add(readConfirm);
+                }
+
+                // 批量保存
+                if (!readConfirmList.isEmpty()) {
+                    boolean saveResult = readConfirmService.saveBatch(readConfirmList);
+                    if (!saveResult) {
+                        log.error("保存已读确认记录失败");
+                        throw new RuntimeException("保存已读确认记录失败");
+                    }
+                    log.info("成功创建 {} 条已读确认记录", readConfirmList.size());
+                }
+            } else {
+                log.warn("未找到部门 {} 下的用户", createReqVO.getDeptId());
+            }
+
+            return announcementId;
+
+        } catch (Exception e) {
+            log.error("创建公告失败: {}", e.getMessage(), e);
+            throw e; // 抛出异常触发事务回滚
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateAnnouncement(AnnouncementSaveReqVO updateReqVO) {
+        try {
+            log.info("开始更新公告: {}", updateReqVO.getId());
+
+            // 校验存在并获取原始公告信息
+            AnnouncementDO originalAnnouncement = Optional.ofNullable(announcementMapper.selectById(updateReqVO.getId()))
+                    .orElseThrow(() -> exception(ANNOUNCEMENT_NOT_EXISTS));
+
+            // 检查部门是否发生变化
+            boolean deptChanged = !originalAnnouncement.getDeptId().equals(updateReqVO.getDeptId());
+
+            if (deptChanged) {
+                log.info("公告部门发生变化,原部门: {} -> 新部门: {}",
+                        originalAnnouncement.getDeptId(), updateReqVO.getDeptId());
+
+                // 删除原部门的已读确认记录
+                deleteReadConfirmByAnnouncementId(updateReqVO.getId());
+
+                // 获取新部门及其子部门的所有用户
+                List<AdminUserRespDTO> newDeptUsers = getDistinctUsersByDepartmentAndSubDepartments(updateReqVO.getDeptId());
+
+                // 为新部门用户创建已读确认记录
+                if (newDeptUsers != null && !newDeptUsers.isEmpty()) {
+                    createReadConfirmRecords(updateReqVO.getId(), newDeptUsers);
+                }
+            }
+
+            // 更新公告
+            AnnouncementDO updateObj = BeanUtils.toBean(updateReqVO, AnnouncementDO.class);
+            announcementMapper.updateById(updateObj);
+
+            log.info("公告更新成功,ID: {}", updateReqVO.getId());
+
+        } catch (Exception e) {
+            log.error("更新公告失败: {}", e.getMessage(), e);
+            throw e; // 抛出异常触发事务回滚
+        }
+    }
+
+    /**
+     * 删除指定公告的已读确认记录
+     */
+    private boolean deleteReadConfirmByAnnouncementId(String announcementId) {
+        return Optional.ofNullable(announcementId)
+                .filter(StringUtils::isNotBlank)
+                .map(id -> {
+                    QueryWrapper<ReadConfirmDO> queryWrapper = new QueryWrapper<>();
+                    queryWrapper.lambda().eq(ReadConfirmDO::getLaboratoryAnnouncementId, id);
+                    int deleteCount = readConfirmService.remove(queryWrapper) ? 1 : 0;
+                    log.info("删除公告 {} 的已读确认记录,删除数量: {}", id, deleteCount);
+                    return deleteCount > 0;
+                })
+                .orElse(false);
+    }
+
+    /**
+     * 为指定公告创建已读确认记录
+     */
+    private void createReadConfirmRecords(String announcementId, List<AdminUserRespDTO> users) {
+        List<ReadConfirmDO> readConfirmList = users.stream()
+                .filter(Objects::nonNull)
+                .map(user -> ReadConfirmDO.builder()
+                        .userId(user.getId())
+                        .laboratoryAnnouncementId(announcementId)
+                        .readStatus(0)
+                        .deptId(user.getDeptId())
+                        .build())
+                .collect(Collectors.toList());
+
+        if (!readConfirmList.isEmpty()) {
+            boolean saveResult = readConfirmService.saveBatch(readConfirmList);
+            if (!saveResult) {
+                throw new RuntimeException("创建已读确认记录失败");
+            }
+            log.info("为公告 {} 创建了 {} 条已读确认记录", announcementId, readConfirmList.size());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteAnnouncement(String id) {
+        // 校验ID不为空
+        Optional.ofNullable(id)
+                .orElseThrow(() -> new IllegalArgumentException("公告ID不能为空"));
+
+        // 校验公告是否存在
+        AnnouncementDO announcement = Optional.ofNullable(announcementMapper.selectById(id))
+                .orElseThrow(() -> exception(ANNOUNCEMENT_NOT_EXISTS));
+
+        // 使用Lambda删除关联的已读确认记录
+        QueryWrapper<ReadConfirmDO> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(ReadConfirmDO::getLaboratoryAnnouncementId, id);
+        readConfirmService.remove(queryWrapper);
+
+        // 删除公告
+        int deleteCount = announcementMapper.deleteById(id);
+        if (deleteCount == 0) {
+            throw new RuntimeException("删除公告失败");
+        }
+
+        log.info("成功删除公告 {} 及其关联数据", id);
+    }
+
+    private void validateAnnouncementExists(String id) {
+        if (announcementMapper.selectById(id) == null) {
+            throw exception(ANNOUNCEMENT_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public AnnouncementDO getAnnouncement(String id) {
+        return announcementMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<AnnouncementDO> getAnnouncementPage(AnnouncementPageReqVO pageReqVO) {
+        String userId = getLoginUserId();
+
+        if (userId == null) {
+            // 用户未登录,返回空公告列表
+            return new PageResult<>(Collections.emptyList(), 0L);
+        }
+
+        // 构建 ReadConfirmPageReqVO,并设置公告表条件
+        ReadConfirmPageReqVO readConfirmPageReqVO = new ReadConfirmPageReqVO();
+        readConfirmPageReqVO.setPageNo(pageReqVO.getPageNo());
+        readConfirmPageReqVO.setPageSize(pageReqVO.getPageSize());
+        readConfirmPageReqVO.setUserId(userId);
+
+        // 设置公告表条件
+        readConfirmPageReqVO.setTitle(pageReqVO.getTitle());
+        readConfirmPageReqVO.setType(pageReqVO.getType());
+//        readConfirmPageReqVO.setAnnouncementDeptId(pageReqVO.getDeptId()); // 注意:AnnouncementPageReqVO 的 deptId 是公告表的部门ID
+//        readConfirmPageReqVO.setContent(pageReqVO.getContent());
+//        readConfirmPageReqVO.setFileUrl(pageReqVO.getFileUrl());
+//        readConfirmPageReqVO.setFileName(pageReqVO.getFileName());
+
+        // 创建分页对象
+        Page<ReadConfirmDO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
+
+        // 调用联表查询方法
+        IPage<ReadConfirmDO> readConfirmDOPage = readConfirmMapper.selectPageWithAnnouncement(page, readConfirmPageReqVO);
+
+        // 转换为 PageResult
+        PageResult<ReadConfirmDO> readConfirmDOPageResult = new PageResult<>(
+                readConfirmDOPage.getRecords(),
+                readConfirmDOPage.getTotal()
+        );
+        if (readConfirmDOPageResult.getList().isEmpty()) {
+            return new PageResult<>(Collections.emptyList(), readConfirmDOPageResult.getTotal());
+        }
+
+        // 提取公告ID列表
+        List<String> announcementIds = readConfirmDOPageResult.getList().stream()
+                .map(ReadConfirmDO::getLaboratoryAnnouncementId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (announcementIds.isEmpty()) {
+            return new PageResult<>(Collections.emptyList(), readConfirmDOPageResult.getTotal());
+        }
+
+        // 根据公告ID查询公告详细信息
+        List<AnnouncementDO> announcements = announcementMapper.selectList(
+                new LambdaQueryWrapperX<AnnouncementDO>()
+                        .in(AnnouncementDO::getId, announcementIds)
+        );
+
+        // 创建阅读状态映射(公告ID -> 阅读状态)
+        Map<String, Integer> readStatusMap = readConfirmDOPageResult.getList().stream()
+                .collect(Collectors.toMap(
+                        ReadConfirmDO::getLaboratoryAnnouncementId,
+                        ReadConfirmDO::getReadStatus,
+                        (existing, replacement) -> existing
+                ));
+
+        // 设置阅读状态并保持原始顺序
+        List<AnnouncementDO> sortedAnnouncements = new ArrayList<>();
+        for (String announcementId : announcementIds) {
+            announcements.stream()
+                    .filter(announcement -> announcementId.equals(announcement.getId()))
+                    .findFirst()
+                    .ifPresent(announcement -> {
+                        announcement.setReadStatus(readStatusMap.get(announcementId));
+                        sortedAnnouncements.add(announcement);
+                    });
+        }
+
+        // 批量设置创建人名称
+        setCreatorNamesInBatch(sortedAnnouncements);
+
+        return new PageResult<>(sortedAnnouncements, readConfirmDOPageResult.getTotal());
+    }
+
+    /**
+     * 批量设置创建人名称
+     */
+    private void setCreatorNamesInBatch(List<AnnouncementDO> announcements) {
+        if (announcements.isEmpty()) {
+            return;
+        }
+
+        // 提取创建人ID(去重)
+        Set<String> creatorIds = announcements.stream()
+                .map(AnnouncementDO::getCreator)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        if (creatorIds.isEmpty()) {
+            return;
+        }
+
+        // 批量查询用户信息
+        Map<String, AdminUserRespDTO> userMap = adminUserApi.getUserMap(creatorIds);
+
+        // 设置创建人名称到公告对象中
+        announcements.forEach(announcement -> {
+            if (announcement.getCreator() != null && userMap.containsKey(announcement.getCreator())) {
+                AdminUserRespDTO user = userMap.get(announcement.getCreator());
+                // 这里需要为 AnnouncementDO 添加 creatorName 字段
+                announcement.setCreatorName(user.getNickname());
+            }
+        });
+    }
+
+    private void setReadStatusInBatch(List<AnnouncementDO> announcements, String userId) {
+        if (announcements.isEmpty()) {
+            return;
+        }
+
+        // 提取公告ID
+        List<String> announcementIds = announcements.stream()
+                .map(AnnouncementDO::getId)
+                .collect(Collectors.toList());
+
+        // 批量查询阅读状态
+        Map<String, Integer> statusMap = readConfirmService.selectReadStatusBatch(userId, announcementIds);
+
+        // 设置阅读状态
+        announcements.forEach(announcement -> {
+            Integer status = statusMap.get(announcement.getId());
+            announcement.setReadStatus(status != null ? status : 0); // 0=未读
+        });
+    }
+
+    /**
+     * 获取指定部门及其所有子部门的所有用户
+     *
+     * @param deptId 部门ID
+     * @return 用户列表
+     */
+    public List<AdminUserRespDTO> getUsersByDepartmentAndSubDepartments(String deptId) {
+        // 1. 获取所有部门ID(包括当前部门和所有子部门)
+        Set<String> allDeptIds = getAllDepartmentIdsIncludingChildren(deptId);
+
+        // 2. 如果没有找到任何部门,返回空列表
+        if (allDeptIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 3. 获取这些部门的所有用户
+        CommonResult<List<AdminUserRespDTO>> result = adminUserApi.getUserListByDeptIds(allDeptIds);
+
+        // 系统管理员虽然属于部门,但是需要单独增加。
+        CommonResult<AdminUserRespDTO> user = adminUserApi.getUser("1");
+        if (result.getCheckedData() != null && user.getCheckedData() != null) {
+            result.getCheckedData().add(user.getCheckedData());
+        }
+        // 4. 返回用户列表,如果结果为空则返回空列表
+        return result.getCheckedData() != null ? result.getCheckedData() : Collections.emptyList();
+    }
+
+    /**
+     获取指定部门及其所有子部门的ID集合
+     @param deptIds 部门ID字符串,多个ID用逗号分隔
+     @return 去重后的部门ID集合
+     */
+    private Set<String> getAllDepartmentIdsIncludingChildren(String deptIds) {
+        Set<String> allDeptIds = new HashSet<>();
+        if (StringUtils.isBlank(deptIds)) {
+            return allDeptIds;
+        }
+        // 按逗号分割部门ID
+        String[] deptIdArray = deptIds.split(",");
+
+        for (String deptId : deptIdArray) {
+            if (StringUtils.isBlank(deptId)) {
+                continue;
+            }
+            deptId = deptId.trim();
+
+            // 调用API获取所有子部门(包括递归的子部门)
+            CommonResult<List<DeptRespDTO>> childDeptsResult = deptApi.getChildDeptList(deptId);
+            List<DeptRespDTO> childDepts = childDeptsResult.getCheckedData();
+
+            if (childDepts != null && !childDepts.isEmpty()) {
+                // 提取所有子部门ID
+                Set<String> childDeptIds = childDepts.stream()
+                        .map(DeptRespDTO::getId)
+                        .collect(Collectors.toSet());
+                allDeptIds.addAll(childDeptIds);
+            }
+
+            // 添加当前部门ID
+            allDeptIds.add(deptId);
+        }
+
+        return allDeptIds;
+    }
+
+    /**
+     * 获取指定部门及其所有子部门的所有用户(带去重)
+     *
+     * @param deptId 部门ID
+     * @return 去重的用户列表
+     */
+    public List<AdminUserRespDTO> getDistinctUsersByDepartmentAndSubDepartments(String deptId) {
+        List<AdminUserRespDTO> users = getUsersByDepartmentAndSubDepartments(deptId);
+
+        // 根据用户ID去重
+        return users.stream()
+                .filter(Objects::nonNull)
+                .collect(Collectors.collectingAndThen(
+                        Collectors.toMap(AdminUserRespDTO::getId, user -> user, (existing, replacement) -> existing),
+                        map -> new ArrayList<>(map.values())
+                ));
+    }
+
+    /**
+     * 获取指定部门及其所有子部门的用户数量
+     *
+     * @param deptId 部门ID
+     * @return 用户数量
+     */
+    public int getUserCountByDepartmentAndSubDepartments(String deptId) {
+        List<AdminUserRespDTO> users = getUsersByDepartmentAndSubDepartments(deptId);
+        return users.size();
+    }
+
+    /**
+     * 获取指定部门及其所有子部门的用户Map(按部门分组)
+     *
+     * @param deptId 部门ID
+     * @return 按部门分组的用户Map
+     */
+    public Map<String, List<AdminUserRespDTO>> getUsersGroupedByDepartment(String deptId) {
+        List<AdminUserRespDTO> users = getUsersByDepartmentAndSubDepartments(deptId);
+
+        return users.stream()
+                .filter(Objects::nonNull)
+                .collect(Collectors.groupingBy(AdminUserRespDTO::getDeptId));
+    }
+}

+ 67 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/service/readconfirm/ReadConfirmService.java

@@ -0,0 +1,67 @@
+package cn.start.tz.module.pressure2.service.readconfirm;
+
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmPageReqVO;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmPageRespVO;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmSaveReqVO;
+import cn.start.tz.module.pressure2.dal.dataobject.readconfirm.ReadConfirmDO;
+import com.baomidou.mybatisplus.extension.service.IService;
+import jakarta.validation.Valid;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 已读确认 Service 接口
+ *
+ * @author 特种管理员
+ */
+public interface ReadConfirmService extends IService<ReadConfirmDO>  {
+
+
+    /**
+     * 批量查询用户对多个公告的阅读状态
+     * @param userId 用户ID
+     * @param announcementIds 公告ID列表
+     * @return Map<公告ID, 阅读状态>
+     */
+    Map<String, Integer> selectReadStatusBatch(String userId, List<String> announcementIds);
+
+    /**
+     * 创建已读确认
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    String createReadConfirm(@Valid ReadConfirmSaveReqVO createReqVO);
+
+    /**
+     * 更新已读确认
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateReadConfirm(@Valid ReadConfirmSaveReqVO updateReqVO);
+
+    /**
+     * 删除已读确认
+     *
+     * @param id 编号
+     */
+    void deleteReadConfirm(String id);
+
+    /**
+     * 获得已读确认
+     *
+     * @param id 编号
+     * @return 已读确认
+     */
+    ReadConfirmDO getReadConfirm(String id);
+
+    /**
+     * 获得已读确认分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 已读确认分页
+     */
+    ReadConfirmPageRespVO getReadConfirmPage(ReadConfirmPageReqVO pageReqVO);
+
+}

+ 195 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/java/cn/start/tz/module/pressure2/service/readconfirm/ReadConfirmServiceImpl.java

@@ -0,0 +1,195 @@
+package cn.start.tz.module.pressure2.service.readconfirm;
+
+import cn.start.tz.framework.common.exception.ErrorCode;
+import cn.start.tz.framework.common.pojo.PageResult;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmPageReqVO;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmPageRespVO;
+import cn.start.tz.module.pressure2.controller.admin.readconfirm.vo.ReadConfirmSaveReqVO;
+import cn.start.tz.module.pressure2.dal.dataobject.readconfirm.ReadConfirmDO;
+import cn.start.tz.module.pressure2.dal.mysql.readconfirm.ReadConfirmMapper;
+import cn.start.tz.module.system.api.dept.DeptApi;
+import cn.start.tz.module.system.api.dept.dto.DeptRespDTO;
+import cn.start.tz.module.system.api.user.AdminUserApi;
+import cn.start.tz.module.system.api.user.dto.AdminUserRespDTO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.start.tz.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.start.tz.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
+import static cn.start.tz.module.pressure2.enums.ErrorCodeConstants.*;
+
+/**
+ * 已读确认 Service 实现类
+ *
+ * @author 特种管理员
+ */
+@Service
+@Validated
+public class ReadConfirmServiceImpl extends ServiceImpl<ReadConfirmMapper, ReadConfirmDO> implements ReadConfirmService {
+
+    @Resource
+    private ReadConfirmMapper readConfirmMapper;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @Resource
+    private DeptApi deptApi;
+
+    @Override
+    public Map<String, Integer> selectReadStatusBatch(String userId, List<String> announcementIds) {
+        if (userId == null || announcementIds == null || announcementIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+
+        // 批量查询阅读状态
+        List<ReadConfirmDO> readConfirmList = readConfirmMapper.selectList(
+                new LambdaQueryWrapper<ReadConfirmDO>()
+                        .eq(ReadConfirmDO::getUserId, userId)
+                        .in(ReadConfirmDO::getLaboratoryAnnouncementId, announcementIds)
+                        .select(ReadConfirmDO::getLaboratoryAnnouncementId, ReadConfirmDO::getReadStatus)
+        );
+
+        // 转换为 Map: key=公告ID, value=阅读状态
+        return readConfirmList.stream()
+                .collect(Collectors.toMap(
+                        ReadConfirmDO::getLaboratoryAnnouncementId,
+                        ReadConfirmDO::getReadStatus
+                ));
+    }
+
+    @Override
+    public String createReadConfirm(ReadConfirmSaveReqVO createReqVO) {
+//        AnnouncementDO announcement = announcementService.getAnnouncement(createReqVO.getLaboratoryAnnouncementId());
+//        if (announcement == null) {
+//            throw exception(ANNOUNCEMENT_NOT_EXISTS);
+//        }
+//        // 插入
+//        ReadConfirmDO readConfirm = BeanUtils.toBean(createReqVO, ReadConfirmDO.class);
+//        readConfirm.setReadStatus(createReqVO.getReadStatus());
+//        readConfirm.setDeptId(announcement.getDeptId());
+//        readConfirm.setUserId(getLoginUserId());
+//        readConfirm.setLaboratoryAnnouncementId(createReqVO.getLaboratoryAnnouncementId());
+//        readConfirmMapper.insert(readConfirm);
+        // 返回
+        return null;
+    }
+
+    @Override
+    public void updateReadConfirm(ReadConfirmSaveReqVO updateReqVO) {
+        // 获取当前用户Id
+        String loginUserId = getLoginUserId();
+
+        // 构建查询条件:通过公告ID和用户ID来确定记录
+        LambdaQueryWrapper<ReadConfirmDO> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ReadConfirmDO::getLaboratoryAnnouncementId, updateReqVO.getLaboratoryAnnouncementId())
+                .eq(ReadConfirmDO::getUserId, loginUserId);
+
+        // 查询记录
+        ReadConfirmDO existing = readConfirmMapper.selectOne(queryWrapper);
+        if (existing == null) {
+            throw exception(new ErrorCode(100030 , "已读确认不存在"));
+        }
+
+        // 使用查询出来的记录ID进行更新
+        LambdaUpdateWrapper<ReadConfirmDO> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(ReadConfirmDO::getId, existing.getId());
+
+        // 只更新阅读状态
+        if (updateReqVO.getReadStatus() != null) {
+            updateWrapper.set(ReadConfirmDO::getReadStatus, updateReqVO.getReadStatus());
+            updateWrapper.set(ReadConfirmDO::getUpdateTime, new Date());
+            updateWrapper.set(ReadConfirmDO::getUpdater, loginUserId);
+        }
+
+        // 执行更新
+        readConfirmMapper.update(null, updateWrapper);
+    }
+
+    @Override
+    public void deleteReadConfirm(String id) {
+        // 校验存在
+        validateReadConfirmExists(id);
+        // 删除
+        readConfirmMapper.deleteById(id);
+    }
+
+    private void validateReadConfirmExists(String id) {
+        if (readConfirmMapper.selectById(id) == null) {
+            throw exception(new ErrorCode(100030 , "已读确认不存在"));
+        }
+    }
+
+    @Override
+    public ReadConfirmDO getReadConfirm(String id) {
+        return readConfirmMapper.selectById(id);
+    }
+
+    @Override
+    public ReadConfirmPageRespVO getReadConfirmPage(ReadConfirmPageReqVO pageReqVO) {
+        // 1. 执行原始分页查询
+        PageResult<ReadConfirmDO> pageResult = readConfirmMapper.selectPage(pageReqVO);
+
+        if (!pageResult.getList().isEmpty()) {
+            // 2. 收集用户ID和部门ID
+            Set<String> userIds = new HashSet<>();
+            Set<String> deptIds = new HashSet<>();
+
+            for (ReadConfirmDO item : pageResult.getList()) {
+                if (item.getUserId() != null) {
+                    userIds.add(item.getUserId());
+                }
+                if (item.getDeptId() != null) {
+                    deptIds.add(item.getDeptId());
+                }
+            }
+
+            // 3. 批量查询用户信息和部门信息
+            Map<String, AdminUserRespDTO> userMap = userIds.isEmpty() ? new HashMap<>() : adminUserApi.getUserMap(userIds);
+            Map<String, DeptRespDTO> deptMap = deptIds.isEmpty() ? new HashMap<>() : deptApi.getDeptMap(deptIds);
+
+            // 4. 设置用户名称和部门名称到每条结果中
+            for (ReadConfirmDO item : pageResult.getList()) {
+                if (item.getUserId() != null && userMap.containsKey(item.getUserId())) {
+                    AdminUserRespDTO user = userMap.get(item.getUserId());
+                    item.setUserName(user.getNickname());
+                }
+
+                if (item.getDeptId() != null && deptMap.containsKey(item.getDeptId())) {
+                    DeptRespDTO dept = deptMap.get(item.getDeptId());
+                    item.setDeptName(dept.getName());
+                }
+            }
+        }
+
+        // 5. 查询已读数量
+        Long readCount = getReadCount(pageReqVO.getLaboratoryAnnouncementId());
+
+        // 6. 返回包装结果
+        return new ReadConfirmPageRespVO(pageResult, readCount);
+    }
+
+    /**
+     * 根据 laboratoryAnnouncementId 查询已读数量
+     */
+    private Long getReadCount(String laboratoryAnnouncementId) {
+        LambdaQueryWrapper<ReadConfirmDO> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ReadConfirmDO::getReadStatus, 1); // 只查询已读状态
+
+        if (laboratoryAnnouncementId != null) {
+            queryWrapper.eq(ReadConfirmDO::getLaboratoryAnnouncementId, laboratoryAnnouncementId);
+        }
+
+        return readConfirmMapper.selectCount(queryWrapper);
+    }
+
+
+
+}

+ 11 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/resources/mapper/announcement/AnnouncementMapper.xml

@@ -0,0 +1,11 @@
+<?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="cn.start.tz.module.pressure2.dal.mysql.announcement.AnnouncementMapper">
+
+    <!--
+        一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
+        无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
+        代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
+        文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
+     -->
+</mapper>

+ 47 - 0
tz-module-pressure2/tz-module-pressure2-biz/src/main/resources/mapper/readconfirm/ReadConfirmMapper.xml

@@ -0,0 +1,47 @@
+<?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="cn.start.tz.module.pressure2.dal.mysql.readconfirm.ReadConfirmMapper">
+
+    <!--
+        一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
+        无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
+        代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
+        文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
+     --><select id="selectPageWithAnnouncement" resultType="cn.start.tz.module.pressure2.dal.dataobject.readconfirm.ReadConfirmDO">
+    SELECT rc.*
+    FROM laboratory_read_confirm rc
+    LEFT JOIN laboratory_announcement la ON rc.laboratory_announcement_id = la.id
+    <where>
+        <if test="reqVO.userId != null">
+            AND rc.user_id = #{reqVO.userId}
+        </if>
+        <if test="reqVO.laboratoryAnnouncementId != null">
+            AND rc.laboratory_announcement_id = #{reqVO.laboratoryAnnouncementId}
+        </if>
+        <if test="reqVO.readStatus != null">
+            AND rc.read_status = #{reqVO.readStatus}
+        </if>
+        <if test="reqVO.deptId != null">
+            AND rc.dept_id = #{reqVO.deptId}
+        </if>
+        <if test="reqVO.createTime != null">
+            AND rc.create_time BETWEEN #{reqVO.createTime[0]} AND #{reqVO.createTime[1]}
+        </if>
+        <if test="reqVO.creator != null">
+            AND rc.creator = #{reqVO.creator}
+        </if>
+        <if test="reqVO.updater != null">
+            AND rc.updater = #{reqVO.updater}
+        </if>
+        <!-- 公告表条件 -->
+        <if test="reqVO.title != null and reqVO.title != ''">
+            AND la.title LIKE CONCAT('%', #{reqVO.title}, '%')
+        </if>
+        <if test="reqVO.type != null">
+            AND la.type = #{reqVO.type}
+        </if>
+    </where>
+    ORDER BY rc.read_status ASC, rc.create_time DESC
+</select>
+
+</mapper>