From 9d6e6eb189652c9e8eddcf91ef96e74d265bdcd3 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 20 Aug 2026 08:39:10 +0800 Subject: [PATCH] commit --- .../ClubSchoolAuditReportController.java | 148 +++++++++++++++++- .../controller/AssetManageController.java | 16 +- .../infoManage/schoolAuditReport/index.html | 4 + 3 files changed, 162 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/budwk/app/zhgh/club/controller/infoManage/ClubSchoolAuditReportController.java b/src/main/java/com/budwk/app/zhgh/club/controller/infoManage/ClubSchoolAuditReportController.java index e302329e..99110ba4 100644 --- a/src/main/java/com/budwk/app/zhgh/club/controller/infoManage/ClubSchoolAuditReportController.java +++ b/src/main/java/com/budwk/app/zhgh/club/controller/infoManage/ClubSchoolAuditReportController.java @@ -2,10 +2,14 @@ package com.budwk.app.zhgh.club.controller.infoManage; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.json.JSONObject; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.result.Result; import com.budwk.app.base.utils.PageUtil; import com.budwk.app.flow.enums.ProcessTaskStateEnum; +import com.budwk.app.sys.models.Sys_file; +import com.budwk.app.sys.utils.SysFileMinIoUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.club.param.ClubUserPageForm; import com.budwk.app.zhgh.club.service.SysClubInfoManageService; @@ -14,12 +18,24 @@ import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.json.Json; +import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** * @Author: JyuHsin @@ -32,6 +48,8 @@ import java.util.List; @At("/platform/club/infoManage/schoolAuditReport") public class ClubSchoolAuditReportController { + private static final Logger log = LoggerFactory.getLogger(ClubSchoolAuditReportController.class); + @Inject private SysClubInfoManageService clubInfoManageService; @@ -44,6 +62,71 @@ public class ClubSchoolAuditReportController { @SaCheckPermission("club.infoManage.schoolAuditReport") public Result pageData(@Valid ClubUserPageForm pageForm, @Param(value = "approval") Boolean approval) { + Sql sql = buildPageDataSql(pageForm, approval); + Pagination pagination = clubInfoManageService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + return Result.success(pagination); + } + + /** + * 导出当前筛选范围内的换届报告附件压缩包。 + * + * @param pageForm 页面查询条件 + * @param approval 审核状态,与列表页保持一致 + * @param response HTTP 下载响应 + */ + @At + @Ok("void") + @SaCheckPermission("club.infoManage.schoolAuditReport") + public void exportRefreshReportZip(@Valid ClubUserPageForm pageForm, + @Param(value = "approval") Boolean approval, + HttpServletResponse response) throws IOException { + List reportList = clubInfoManageService.listMap(buildPageDataSql(pageForm, approval)); + response.setContentType("application/zip"); + response.setHeader("Content-Disposition", "attachment;filename=" + URLUtil.encode("换届报告.zip")); + + // 显式指定 UTF-8,确保压缩包内中文目录和文件名正常显示。 + try (ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()), StandardCharsets.UTF_8)) { + Set entryNames = new HashSet<>(); + for (NutMap report : reportList) { + String filesJson = report.getString("files"); + if (StrUtil.isBlank(filesJson)) { + continue; + } + List files = Json.fromJsonAsList(JSONObject.class, filesJson); + String folderName = buildFolderName(report); + for (int index = 0; index < files.size(); index++) { + JSONObject fileInfo = files.get(index); + Sys_file sysFile = findSysFile(fileInfo); + if (sysFile == null) { + log.warn("换届报告附件不存在,报告ID:{},附件:{}", report.getString("id"), fileInfo); + continue; + } + byte[] fileBytes = SysFileMinIoUtil.getFileBytes(sysFile.getBucket(), sysFile.getStoragePath()); + if (fileBytes == null || fileBytes.length == 0) { + log.warn("换届报告附件内容为空,报告ID:{},附件ID:{}", report.getString("id"), sysFile.getId()); + continue; + } + String fileName = StrUtil.blankToDefault(fileInfo.getStr("name"), fileInfo.getStr("filename")); + if (StrUtil.isBlank(fileName)) { + fileName = sysFile.getName(); + } + String entryName = buildUniqueEntryName(folderName + "/" + sanitizeFileName(fileName), entryNames); + zipOutputStream.putNextEntry(new ZipEntry(entryName)); + zipOutputStream.write(fileBytes); + zipOutputStream.closeEntry(); + } + } + } + } + + /** + * 构建列表与导出共用的换届报告查询,确保“查什么导什么”。 + * + * @param pageForm 页面查询条件 + * @param approval 审核状态 + * @return 已绑定筛选条件的查询对象 + */ + private Sql buildPageDataSql(ClubUserPageForm pageForm, Boolean approval) { Sql sql = Sqls.create(""" SELECT info.*, @@ -79,7 +162,7 @@ public class ClubSchoolAuditReportController { cnd.and("t.taskName", "=", "d4323546-8d09-419e-88d4-7b15862ca29d"); cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId())); - if (approval) { + if (Boolean.TRUE.equals(approval)) { cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode())); } else { cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode()); @@ -96,8 +179,67 @@ public class ClubSchoolAuditReportController { cnd.groupBy("t.id"); sql.setCondition(cnd); + return sql; + } - Pagination pagination = clubInfoManageService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); - return Result.success(pagination); + /** + * 兼容新旧附件 JSON 结构,按附件 ID 或下载地址查找文件元数据。 + * + * @param fileInfo 附件 JSON 信息 + * @return 系统文件记录;未找到时返回 null + */ + private Sys_file findSysFile(JSONObject fileInfo) { + String fileId = fileInfo.getStr("id"); + if (StrUtil.isNotBlank(fileId)) { + return clubInfoManageService.dao().fetch(Sys_file.class, fileId); + } + String fileUrl = fileInfo.getStr("url"); + if (StrUtil.isBlank(fileUrl)) { + return null; + } + return clubInfoManageService.dao().fetch(Sys_file.class, Cnd.where(Sys_file::getDownloadPath, "=", fileUrl)); + } + + /** + * 使用社团名称建立目录,方便按换届报告来源查阅。 + * + * @param report 换届报告记录 + * @return Zip 内目录名称 + */ + private String buildFolderName(NutMap report) { + return sanitizeFileName(report.getString("clubName")); + } + + /** + * 保留附件原始文件名;同一社团目录存在同名文件时追加序号,避免覆盖。 + * + * @param entryName 原始 Zip 条目名称 + * @param entryNames 已写入的 Zip 条目名称 + * @return 唯一的 Zip 条目名称 + */ + private String buildUniqueEntryName(String entryName, Set entryNames) { + if (entryNames.add(entryName)) { + return entryName; + } + int extensionIndex = entryName.lastIndexOf('.'); + String name = extensionIndex > entryName.lastIndexOf('/') ? entryName.substring(0, extensionIndex) : entryName; + String extension = extensionIndex > entryName.lastIndexOf('/') ? entryName.substring(extensionIndex) : ""; + int index = 2; + String uniqueEntryName; + do { + uniqueEntryName = name + "(" + index + ")" + extension; + index++; + } while (!entryNames.add(uniqueEntryName)); + return uniqueEntryName; + } + + /** + * 去除 Zip 条目路径分隔符,避免附件名影响压缩包目录结构。 + * + * @param fileName 原始文件名 + * @return 可安全写入 Zip 的文件名 + */ + private String sanitizeFileName(String fileName) { + return StrUtil.blankToDefault(fileName, "未命名文件").replace("/", "_").replace("\\", "_"); } } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java index 3d768e1c..6aa6a0c9 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java @@ -9,9 +9,11 @@ import cn.dev33.satoken.annotation.SaMode; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.annotation.SLog; +import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.result.Result; import com.budwk.app.base.utils.CommonDownloadUtil; +import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.dayofficework.asset.model.Asset; import com.budwk.app.zhgh.dayofficework.asset.model.AssetDepreciationRecord; @@ -27,6 +29,7 @@ import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; +import org.nutz.dao.util.cri.SqlExpressionGroup; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -157,9 +160,16 @@ public class AssetManageController { @SaCheckPermission(value = {"asset.manage", "asset.stocktaking", "h5.asset.stocktaking"}, mode = SaMode.OR) public Result searchAssetUseUser(String query) { Cnd cnd = Cnd.NEW(); - cnd.where().orLike("username", query); - cnd.where().orLike("loginname", query); - cnd.where().orLike("id", query); + // 三个关键词条件必须整体分组,避免 OR 条件绕过分工会数据范围。 + SqlExpressionGroup searchGroup = new SqlExpressionGroup(); + searchGroup.orLike("username", query); + searchGroup.orLike("loginname", query); + searchGroup.orLike("id", query); + cnd.and(searchGroup); + // 分工会侧盘点人员只能选择本分工会成员,校工会和系统管理员保留全校查询范围。 + if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { + cnd.and("unionId", "=", SecurityUtil.getUnionId()); + } Sql sql = Sqls.create(""" SELECT id, diff --git a/src/main/resources/views/platform/zhgh/club/infoManage/schoolAuditReport/index.html b/src/main/resources/views/platform/zhgh/club/infoManage/schoolAuditReport/index.html index 3bbe6171..17ffcfd2 100644 --- a/src/main/resources/views/platform/zhgh/club/infoManage/schoolAuditReport/index.html +++ b/src/main/resources/views/platform/zhgh/club/infoManage/schoolAuditReport/index.html @@ -32,6 +32,7 @@ layout("/layouts/platform.html"){ + 导出换届报告Zip 已审核 未审核 @@ -158,6 +159,9 @@ layout("/layouts/platform.html"){ }) }) }, + exportRefreshReportZip() { + this.$downLoad("/platform/club/infoManage/schoolAuditReport/exportRefreshReportZip", this.pageForm) + }, async pageData() { const resp = await this.$axios.post("/platform/club/infoManage/schoolAuditReport/pageData", this.pageForm) if (resp.code === 0) {