ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceDocumentfilesController.java
@@ -1,5 +1,6 @@ package com.ruoyi.web.controller.project; import java.io.File; import java.util.Arrays; import java.util.List; @@ -23,8 +24,13 @@ import com.ruoyi.common.enums.BusinessType; import com.ruoyi.project.domain.ServiceDocumentfiles; import com.ruoyi.project.service.IServiceDocumentfilesService; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.framework.config.ServerConfig; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; /** * 捐献附件Controller @@ -40,6 +46,72 @@ @Autowired private IServiceDocumentfilesService serviceDocumentfilesService; @Autowired private ServerConfig serverConfig; /** * 政策文件上传(**不限文件类型**)。 * * <p>2026-10-09 业务要求:政策文件模块允许上传/下载任意文件类型。 * * <p>为什么不直接用 {@code /common/upload}:通用上传走 * {@code FileUploadUtils.upload(baseDir, file)},会按 {@code MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION} * 白名单校验扩展名,非白名单类型(如 dwg、7z、ofd、eml 等)会被拒绝。 * 这里把 allowedExtension 显式传 {@code null} —— {@code assertAllowed} 的判断是 * {@code allowedExtension != null && !isAllowedExtension(...)},传 null 即跳过扩展名校验, * 同时**仍保留** 500MB 大小限制、文件名长度限制与存储路径安全处理。 * * <p>仅政策文件模块使用本接口;{@code /common/upload} 与其他模块的白名单保持不变。 * * @param file 上传文件 * @return 与 {@code /common/upload} 一致的返回体(code/msg/fileName/url) */ @ApiOperation("政策文件上传(不限文件类型)") @PostMapping("/upload") public AjaxResult uploadFile(MultipartFile file) { String originalFilename = file == null ? null : file.getOriginalFilename(); try { if (file == null || file.isEmpty()) { return AjaxResult.error("上传文件为空,请重新选择文件"); } // 上传文件路径 String filePath = RuoYiConfig.getUploadPath(); // 同名文件处理:加时间戳重命名,避免覆盖已上传文件(与通用上传保持一致) String filename = originalFilename == null ? "" : originalFilename.trim(); String pathFileName = FileUploadUtils.extractFilename(file); File target = new File(filePath + "/" + pathFileName); if (target.exists()) { int i = filename.lastIndexOf("."); if (i > -1) { String startStr = filename.substring(0, i); String endStr = filename.substring(i); String newFilename = startStr + System.currentTimeMillis() + endStr; file = new MockMultipartFile(newFilename, newFilename, file.getContentType(), file.getBytes()); } } // allowedExtension 传 null:不做扩展名校验,允许任意文件类型 String relativePath = FileUploadUtils.upload(filePath, file, null); // 与通用上传一致的 url 处理(部分部署下 8032 需要带 /prod-api 前缀) String url = serverConfig.getUrl(); if (url != null && url.contains("8032")) { url = url.replace("8032", "8032/prod-api"); } log.info("政策文件上传成功:原始文件名={},存储路径={}", filename, relativePath); AjaxResult ajax = AjaxResult.success(); ajax.put("fileName", relativePath); ajax.put("url", url + relativePath); return ajax; } catch (Exception e) { // 返回 AjaxResult(HTTP 200 + code=500),前端 uploadSccess 的 else 分支会把原因弹出来 log.error("政策文件上传失败:原始文件名={}", originalFilename, e); return AjaxResult.error("上传失败:" + e.getMessage()); } } /** * 查询捐献附件列表 */ ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceFundServiceImpl.java
@@ -609,8 +609,16 @@ sysUsers = sysUserMapper.selectUserByUserName(user.getUserName()); log.info("费用sysUsers返回的值为:{}", sysUsers); } if (checkFundVO.getFlowconclusion() == 1 && sysUsers != null && sysUsers.getRoles() != null && sysUsers.getRoles().stream().anyMatch(role -> "officedirector".equals(role.getRoleKey()))) { serviceFund.setOfficedirector(user.getNickName()); // 2026-10-09:推送条件由"当前审核人有 officedirector 角色"改为"本次审批后到达最后一级"。 // 原写法在"最后一级审核人没有该角色"时永远不会推送,而"非最后一级的 officedirector"会提前推送。 boolean uploadAtLastLevel = checkFundVO.getFlowconclusion() == 1 && TotalLevel != null && TotalLevel != 0 && TotalLevel == OriginalFlowLevel + 1; if (uploadAtLastLevel) { // 仍只在该角色审批时记录办公室主任,保持字段语义不变 if (sysUsers != null && sysUsers.getRoles() != null && sysUsers.getRoles().stream().anyMatch(role -> "officedirector".equals(role.getRoleKey()))) { serviceFund.setOfficedirector(user.getNickName()); } serviceFund.setUploadStates(1); //数据发送 Boolean aBoolean1 = null; @@ -709,8 +717,12 @@ } private Boolean doSendData(ServiceFund serviceFund) { // fund和funddetail的数据获取 addFundSharedInfo(serviceFund.getId()); // 2026-10-09:接住 addFundSharedInfo 的失败信号(附件上传失败时它会回滚分享表并返回 false) Boolean sharedOk = addFundSharedInfo(serviceFund.getId()); if (sharedOk == null || !sharedOk) { log.error("费用报销单上传失败:分享表/附件环节失败,审批不生效,fundId={}", serviceFund.getId()); return false; } List<ServiceFundShared> fundShareInfos = serviceFundSharedMapper.getFundShareInfoById(serviceFund.getId()); if (CollectionUtils.isEmpty(fundShareInfos)) return false; @@ -1011,11 +1023,10 @@ try { if (isUploadOaEnabled()) uploadOAFileAndUpdateDb(serviceFunddetailShareds.get(i)); } catch (Exception e) { e.printStackTrace(); log.error("Exception中入参的ID为:{},异常信息为 : {}", id, e.getMessage()); log.error("费用附件上传医院协友系统失败:单据id={}(本次报销单上传整体失败,审批不生效)", id, e); if (id != null) { Boolean aBoolean = sharedService.delfundsharedInfoById(null, id); log.error("fund2分享表里的数据是否删除成功:{}", aBoolean); log.error("已回滚分享表数据:{}", aBoolean); } return false; } @@ -1860,10 +1871,18 @@ log.info("filePath的修改:{}", filePath); String url = rbDetailFile.getUrl(); //file用的是绝对位置 String substring = url.substring(url.indexOf("/profile/upload") + "/profile/upload".length()); // 2026-10-09:附件地址解析加固,避免 url 异常/文件缺失时抛 NPE 导致看不到失败原因 int profileIdx = url == null ? -1 : url.indexOf("/profile/upload"); if (profileIdx < 0) { throw new ServiceException("附件地址格式不正确(缺少 /profile/upload),无法上传协友:" + url); } String substring = url.substring(profileIdx + "/profile/upload".length()); // String strFile = rbDetailFile.getUrl().replace(filePath, "/profile/upload"); System.out.println("fund服务的filePath + substring: " + filePath + substring); File filetest = new File(filePath + substring); if (!filetest.exists()) { throw new ServiceException("附件文件在服务器上不存在,无法上传协友:" + filetest.getAbsolutePath()); } String struploadResult = HttpClientKit.sendPostWithFile(filetest, strFUrl); log.info("第三方传回的数据: {}", struploadResult); ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceReimbursementServiceImpl.java
@@ -293,12 +293,12 @@ //如果是本地环境,就不调这个了 if (isUploadOaEnabled()) uploadOAFileAndUpdateDb(serviceReimbursementdetailShareds.get(i)); } catch (Exception e) { log.error("Exception中reimShare的入参id为:{}", serviceReimbursementShared.getId()); log.error("差旅附件上传医院协友系统失败:分享id={},明细={}(本次报销单上传整体失败,审批不生效)", serviceReimbursementShared.getId(), serviceReimbursementdetailShareds.get(i), e); if (serviceReimbursementShared.getId() != null) { Boolean aBoolean = sharedService.delResharedInfoById(serviceReimbursementShared.getId()); log.error("文件上传失败,将分享表数据回滚:{}", aBoolean); log.error("已回滚分享表数据:{}", aBoolean); } e.getMessage(); return false; } @@ -543,8 +543,16 @@ log.info("差旅sysUsers返回的值为:{}", sysUser); } if (checkFundVO.getFlowconclusion() == 1 && Objects.nonNull(sysUser) && sysUser.getRoles() != null && sysUser.getRoles().stream().anyMatch(role -> "officedirector".equals(role.getRoleKey()))) { serviceReimbursement.setOfficedirector(user.getNickName()); // 2026-10-09:推送条件由"当前审核人有 officedirector 角色"改为"本次审批后到达最后一级"。 // 原写法在"最后一级审核人没有该角色"时永远不会推送,而"非最后一级的 officedirector"会提前推送。 boolean uploadAtLastLevel = checkFundVO.getFlowconclusion() == 1 && totalLevel != null && totalLevel != 0 && totalLevel == OriginalFlowLevel + 1; if (uploadAtLastLevel) { // 仍只在该角色审批时记录办公室主任,保持字段语义不变 if (Objects.nonNull(sysUser) && sysUser.getRoles() != null && sysUser.getRoles().stream().anyMatch(role -> "officedirector".equals(role.getRoleKey()))) { serviceReimbursement.setOfficedirector(user.getNickName()); } serviceReimbursement.setUploadStates(1); Boolean aBoolean1 = null; @@ -648,9 +656,17 @@ private Boolean doSendData(ServiceReimbursement serviceReimbursement) { // fund和funddetail的数据获取 addSharedData(serviceReimbursement.getId()); // 2026-10-09:接住 addSharedData 的失败信号(附件上传失败时它会回滚分享表并返回 false) Boolean sharedOk = addSharedData(serviceReimbursement.getId()); if (sharedOk == null || !sharedOk) { log.error("差旅报销单上传失败:分享表/附件环节失败,审批不生效,id={}", serviceReimbursement.getId()); return false; } List<ServiceReimbursementShared> serviceReimbursementSharedList = sharedService.getRemShareInfoByRemId(serviceReimbursement.getId()); if (org.springframework.util.CollectionUtils.isEmpty(serviceReimbursementSharedList)) return false; if (org.springframework.util.CollectionUtils.isEmpty(serviceReimbursementSharedList)) { log.error("差旅报销单上传失败:分享表为空(附件或组装环节异常),审批不生效,id={}", serviceReimbursement.getId()); return false; } log.info("serviceReimbursementSharedList的数据量为:{}", serviceReimbursementSharedList.size()); //detail详情 @@ -974,9 +990,17 @@ log.info("filePath的修改:{}", filePath); String url = rbDetailFile.getUrl(); //file用的是绝对位置 String fileName = url.substring(url.indexOf("/profile/upload") + "/profile/upload".length()); // 2026-10-09:附件地址解析加固,避免 url 异常/文件缺失时抛 NPE 导致看不到失败原因 int profileIdx = url == null ? -1 : url.indexOf("/profile/upload"); if (profileIdx < 0) { throw new ServiceException("附件地址格式不正确(缺少 /profile/upload),无法上传协友:" + url); } String fileName = url.substring(profileIdx + "/profile/upload".length()); String strFile = rbDetailFile.getUrl().replace(filePath, "/profile/upload"); File fileUpload = new File(filePath + fileName); if (!fileUpload.exists()) { throw new ServiceException("附件文件在服务器上不存在,无法上传协友:" + fileUpload.getAbsolutePath()); } log.info("请求第三方的入参strFile : {}, strFUrl : {}", strFile, filePath); String struploadResult = HttpClientKit.sendPostWithFile(fileUpload, strFUrl); log.info("第三方传回的数据: {}", struploadResult);