From 3371905b4aeebc872b969af0b98c4d3adb6b4699 Mon Sep 17 00:00:00 2001 From: tangwei Date: Sat, 9 May 2026 19:10:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ApprovalMainController.java | 8 + .../controller/FishDraftDataController.java | 45 ++++ .../service/impl/FishImportServiceImpl.java | 213 +++++++++++++++--- .../service/impl/ImportTaskServiceImpl.java | 13 +- .../platform/system/mapper/SysRoleMapper.java | 6 + .../system/service/impl/UserServiceImpl.java | 4 + .../resources/mapper/system/SysRoleMapper.xml | 10 + 7 files changed, 261 insertions(+), 38 deletions(-) diff --git a/backend/src/main/java/com/yfd/platform/data/controller/ApprovalMainController.java b/backend/src/main/java/com/yfd/platform/data/controller/ApprovalMainController.java index 53d927d..c0db986 100644 --- a/backend/src/main/java/com/yfd/platform/data/controller/ApprovalMainController.java +++ b/backend/src/main/java/com/yfd/platform/data/controller/ApprovalMainController.java @@ -13,6 +13,7 @@ import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.*; import java.util.Date; +import java.util.List; /** *

@@ -116,4 +117,11 @@ public class ApprovalMainController { boolean result = approvalMainService.removeById(id); return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); } + + @PostMapping("/batchDelete") + @Operation(summary = "删除审批") + public ResponseResult delete(@RequestBody List ids) { + boolean result = approvalMainService.removeBatchByIds(ids); + return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/data/controller/FishDraftDataController.java b/backend/src/main/java/com/yfd/platform/data/controller/FishDraftDataController.java index 6415ee3..abfb880 100644 --- a/backend/src/main/java/com/yfd/platform/data/controller/FishDraftDataController.java +++ b/backend/src/main/java/com/yfd/platform/data/controller/FishDraftDataController.java @@ -234,6 +234,51 @@ public class FishDraftDataController { return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); } + + +// @PostMapping("/approvalIdRemoveDraft") +// @Operation(summary = "根据批次号批量删除草稿(软删除)") +// public ResponseResult approvalIdRemoveDraft(@RequestBody BatchApproveRequest request) { +// List draft = fishDraftDataService.list(new LambdaQueryWrapper().eq(FishDraftData::getDeletedFlag, 0).in(FishDraftData::getApprovalId, request.getApprovalIds()).eq(FishDraftData::getStatus, "REJECTED").select(FishDraftData::getId)); +// List ids = draft.stream().map(FishDraftData::getId).toList(); +// boolean result = fishDraftDataService.batchRemoveDraft(ids); +// if(result){ +// List list = fishDraftDataService.list(new LambdaQueryWrapper().in(FishDraftData::getId, ids).select(FishDraftData::getPicpth, FishDraftData::getVdpth, FishDraftData::getId)); +// // 异步删除附件 +// CompletableFuture.runAsync(() -> { +// for (FishDraftData fishDraftData : list) { +// String picpth = fishDraftData.getPicpth(); +// String vdpth = fishDraftData.getVdpth(); +// +// try { +// if (StrUtil.isNotBlank(picpth)) { +// // 假设 picpth 是分号或逗号分隔的文件ID/路径 +// List split = StrUtil.split(picpth, StrUtil.C_COMMA); +// for (String fileId : split) { +// if (StrUtil.isNotBlank(fileId)) { +// attachmentUploadService.deleteFile(fileId.trim()); +// } +// } +// } +// if (StrUtil.isNotBlank(vdpth)) { +// List split = StrUtil.split(vdpth, StrUtil.C_COMMA); +// for (String fileId : split) { +// if (StrUtil.isNotBlank(fileId)) { +// attachmentUploadService.deleteFile(fileId.trim()); +// } +// } +// } +// } catch (Exception e) { +// log.error("异步删除附件失败, dataId: {}", fishDraftData.getId(), e); +// } +// } +// }, taskExecutor).exceptionally(ex -> { +// log.error("异步删除任务执行异常", ex); +// return null; +// }); +// } +// return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); +// } @PostMapping("/batchRemoveDraft") @Operation(summary = "批量删除草稿(软删除)") public ResponseResult batchRemoveDraft(@RequestBody List ids) { diff --git a/backend/src/main/java/com/yfd/platform/data/service/impl/FishImportServiceImpl.java b/backend/src/main/java/com/yfd/platform/data/service/impl/FishImportServiceImpl.java index 0cfe76f..17f5f5d 100644 --- a/backend/src/main/java/com/yfd/platform/data/service/impl/FishImportServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/data/service/impl/FishImportServiceImpl.java @@ -3,6 +3,7 @@ package com.yfd.platform.data.service.impl; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.yfd.platform.data.domain.FishDraftData; import com.yfd.platform.data.domain.FishImportRequest; import com.yfd.platform.data.domain.FishImportResult; @@ -19,6 +20,7 @@ import com.yfd.platform.system.service.ISysDictionaryService; import com.yfd.platform.utils.SecurityUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.apache.poi.EncryptedDocumentException; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.security.core.context.SecurityContext; @@ -33,6 +35,7 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.text.ParseException; +import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -370,7 +373,10 @@ public class FishImportServiceImpl implements IFishImportService { } break; case "fsz": - data.setFsz(cellValue.trim()); + if (StringUtils.hasText(cellValue)) { + String parsedFsz = parseFishSizeRange(cellValue.trim()); + data.setFsz(parsedFsz); + } break; case "fcnt": if (!StringUtils.hasText(cellValue)) { @@ -403,14 +409,14 @@ public class FishImportServiceImpl implements IFishImportService { break; case "strdt": if (!StringUtils.hasText(cellValue)) { - importRow.getWarnings().add(fieldName); + importRow.getWarnings().add("strdtStr"); data.setStrdtStr(cellValue); } else { Date strdt = parseDate(cellValue); if (strdt == null) { - importRow.getWarnings().add(fieldName); + importRow.getWarnings().add("strdtStr"); data.setStrdt(null); - data.setStrdtStr(cellValue); + data.setStrdtStr(cellValue.replaceAll("T", " ")); }else{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = sdf.format(strdt); @@ -443,6 +449,9 @@ public class FishImportServiceImpl implements IFishImportService { Map videoFiles = result.getVideoFiles(); for (String fileName : vdpth.split(";")) { + if(StrUtil.isBlank(fileName)){ + continue; + } for (String entryName : videoFiles.keySet()) { if (entryName.equals(fileName) || entryName.endsWith("/" + fileName) || entryName.endsWith("\\" + fileName)) { Map objectObjectHashMap = new HashMap<>(); @@ -450,7 +459,7 @@ public class FishImportServiceImpl implements IFishImportService { objectObjectHashMap.put("value", fileName); importRow.getVdpthList().add(objectObjectHashMap); vdpthList.add(fileName); - } else if (com.yfd.platform.utils.FileUtil.isVideoFileName(fileName)) { + } else if (!com.yfd.platform.utils.FileUtil.isVideoFileName(fileName)) { Map objectObjectHashMap = new HashMap<>(); objectObjectHashMap.put("name", fileName); objectObjectHashMap.put("value", fileName); @@ -472,6 +481,9 @@ public class FishImportServiceImpl implements IFishImportService { Map imageFiles = result.getImageFiles(); for (String fileName : picpth.split(";")) { + if(StrUtil.isBlank(fileName)){ + continue; + } for (String entryName : imageFiles.keySet()) { if (entryName.equals(fileName) || entryName.endsWith("/" + fileName) || entryName.endsWith("\\" + fileName)) { Map objectObjectHashMap = new HashMap<>(); @@ -536,6 +548,67 @@ public class FishImportServiceImpl implements IFishImportService { } + /** + * 解析鱼类体长范围 + * 从混乱的字符串中提取所有数字(支持小数),返回 "最小值~最大值" 格式 + * 例如: "123123&234.dey76fd78" -> 提取出 123123, 234., 76, 78 -> "76~123123" + * + * @param input 原始字符串 + * @return 格式化后的范围字符串,如果没有有效数字则返回原字符串 + */ + private String parseFishSizeRange(String input) { + if (!StringUtils.hasText(input)) { + return input; + } + + // 使用正则表达式提取所有数字(包括整数和小数) + // 解释: \d+ 匹配一个或多个数字, (\.\d+)? 匹配可选的小数部分 + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\d+(?:\\.\\d+)?"); + java.util.regex.Matcher matcher = pattern.matcher(input); + + List numbers = new ArrayList<>(); + while (matcher.find()) { + try { + String numStr = matcher.group(); + // 排除单独的点或无效格式 + if (numStr != null && !numStr.isEmpty()) { + numbers.add(Double.parseDouble(numStr)); + } + } catch (NumberFormatException e) { + // 忽略无法解析的数字 + } + } + + // 如果没有提取到任何数字,返回原字符串或空 + if (numbers.isEmpty()) { + log.warn("鱼类体长字段未提取到有效数字: {}", input); + return input; + } + + // 找出最小值和最大值 + double min = numbers.stream().mapToDouble(Double::doubleValue).min().getAsDouble(); + double max = numbers.stream().mapToDouble(Double::doubleValue).max().getAsDouble(); + + // 格式化结果:如果是整数则不显示小数点,否则保留原有精度 + String minStr = formatNumber(min); + String maxStr = formatNumber(max); + + return minStr + "~" + maxStr; + } + + /** + * 格式化数字:如果是整数则去掉 .0,否则保留小数 + */ + private String formatNumber(double value) { + if (value == Math.floor(value) && !Double.isInfinite(value)) { + return String.valueOf((long) value); + } else { + // 去除末尾多余的 0,例如 12.50 -> 12.5 + return String.valueOf(value).replaceAll("\\.?0+$", ""); + } + } + + private void validateStationFpssRelation(FishDraftData data, FishImportResult.FishImportRow importRow) { if (importRow.getWarnings().contains("hbrvcd")) { @@ -1141,54 +1214,103 @@ public class FishImportServiceImpl implements IFishImportService { return true; } - /** - * 解析日期字符串,支持多种格式 - * @param dateStr 日期字符串 - * @return 解析后的 Date 对象,如果解析失败返回 null - */ - private Date parseDate(String dateStr) { + + private static Date parseDate(String dateStr) { if (!StringUtils.hasText(dateStr)) { return null; } - - // 去除首尾空格 dateStr = dateStr.trim(); // 支持的日期格式列表(按常用程度排序) String[] patterns = { "yyyy-MM-dd HH:mm:ss", // 2024-01-15 14:30:00 - "yyyy-MM-dd", // 2024-01-15 + "yyyy-MM-dd'T'HH:mm:ss", // ⭐ 新增:支持 2024-01-15T14:30:00 (ISO格式) + "yyyy-MM-dd HH:mm", // 2024-01-15 14:30 + "yyyy-MM-dd'T'HH:mm", // ⭐ 确保这一行存在且正确:支持 2024-01-15T14:30 + "yyyy-MM-dd", // 2024-01-15 "yyyy/MM/dd HH:mm:ss", // 2024/01/15 14:30:00 - "yyyy/MM/dd", // 2024/01/15 + "yyyy/MM/dd HH:mm", // 2024/01/15 14:30 + "yyyy/MM/dd", // 2024/01/15 (标准双位) + "yyyy/M/d HH:mm:ss", // 支持 2024/1/1 14:30:00 + "yyyy/M/d HH:mm", // 支持 2024/1/1 14:30 + "yyyy/M/d", // 支持 2024/1/1 (单位数) "yyyy.MM.dd HH:mm:ss", // 2024.01.15 14:30:00 - "yyyy.MM.dd", // 2024.01.15 + "yyyy.MM.dd", // 2024.01.15 + "yyyy.M.d", // 支持 2024.1.1 "yyyyMMdd HHmmss", // 20240115 143000 - "yyyyMMdd", // 20240115 + "yyyyMMdd", // 20240115 "yyyy年MM月dd日", // 2024年01月15日 + "yyyy年M月d日", // 支持 2024年1月1日 "yyyy年MM月dd日HH时mm分ss秒" // 2024年01月15日14时30分00秒 }; for (String pattern : patterns) { try { SimpleDateFormat sdf = new SimpleDateFormat(pattern); - sdf.setLenient(false); // 严格模式,不允许非法日期 - Date parsedDate = sdf.parse(dateStr); - - // 验证解析后的日期是否合理(例如年份不能是 0001) - Calendar cal = Calendar.getInstance(); - cal.setTime(parsedDate); - int year = cal.get(Calendar.YEAR); - if (year >= 1900 && year <= 2100) { - return parsedDate; + sdf.setLenient(false); + ParsePosition pos = new ParsePosition(0); + Date parsedDate = sdf.parse(dateStr, pos); + if (parsedDate != null && pos.getIndex() == dateStr.length()) { + // 整个字符串都被成功解析 + Calendar cal = Calendar.getInstance(); + cal.setTime(parsedDate); + int year = cal.get(Calendar.YEAR); + if (year >= 1900 && year <= 2100) { + return parsedDate; + } } - } catch (ParseException e) { - // 尝试下一个格式 + } catch (Exception e) { + // ignore } } - log.debug("无法解析日期: '{}'", dateStr); return null; } + + +// private Date parseDate(String dateStr) { +// if (!StringUtils.hasText(dateStr)) { +// return null; +// } +// +// // 去除首尾空格 +// dateStr = dateStr.trim(); +// +// // 支持的日期格式列表(按常用程度排序) +// String[] patterns = { +// "yyyy-MM-dd HH:mm:ss", // 2024-01-15 14:30:00 +// "yyyy-MM-dd", // 2024-01-15 +// "yyyy/MM/dd HH:mm:ss", // 2024/01/15 14:30:00 +// "yyyy/MM/dd", // 2024/01/15 +// "yyyy.MM.dd HH:mm:ss", // 2024.01.15 14:30:00 +// "yyyy.MM.dd", // 2024.01.15 +// "yyyyMMdd HHmmss", // 20240115 143000 +// "yyyyMMdd", // 20240115 +// "yyyy年MM月dd日", // 2024年01月15日 +// "yyyy年MM月dd日HH时mm分ss秒" // 2024年01月15日14时30分00秒 +// }; +// +// for (String pattern : patterns) { +// try { +// SimpleDateFormat sdf = new SimpleDateFormat(pattern); +// sdf.setLenient(false); // 严格模式,不允许非法日期 +// Date parsedDate = sdf.parse(dateStr); +// +// // 验证解析后的日期是否合理(例如年份不能是 0001) +// Calendar cal = Calendar.getInstance(); +// cal.setTime(parsedDate); +// int year = cal.get(Calendar.YEAR); +// if (year >= 1900 && year <= 2100) { +// return parsedDate; +// } +// } catch (ParseException e) { +// // 尝试下一个格式 +// } +// } +// +// log.debug("无法解析日期: '{}'", dateStr); +// return null; +// } private Integer parseInteger(String value) { if (!StringUtils.hasText(value)) { return null; @@ -1295,9 +1417,40 @@ public class FishImportServiceImpl implements IFishImportService { result.setTempDir(zipContent.tempDir); result.setImageFiles(zipContent.images); result.setVideoFiles(zipContent.videos); - try (Workbook workbook = new XSSFWorkbook(new FileInputStream(zipContent.excelFilePath))) { + Workbook workbook = null; + // 1. 验证文件是否存在且有效 + File excelFile = new File(zipContent.excelFilePath); + if (!excelFile.exists() || excelFile.length() == 0) { + log.error("Excel文件不存在或为空: {}", zipContent.excelFilePath); + throw new RuntimeException("Excel文件不存在或已损坏"); + } + + try (InputStream fis = new FileInputStream(excelFile)) { + // 2. 使用 WorkbookFactory 自动识别 .xls 和 .xlsx 格式 + // 这样可以避免因为格式不匹配导致的 NotOfficeXmlFileException + workbook = WorkbookFactory.create(fis); + Sheet sheet = workbook.getSheetAt(0); result = parseSheet(sheet, result, uploadUserId); + + } catch (EncryptedDocumentException e) { + log.error("Excel文件已加密,无法解析: {}", excelFile.getName(), e); + throw new RuntimeException("Excel文件已设置密码,请移除密码后重新上传"); + } catch (InvalidFormatException e) { + log.error("Excel文件格式无效: {}", excelFile.getName(), e); + throw new RuntimeException("Excel文件格式无效或已损坏"); + } catch (IOException e) { + log.error("读取Excel文件IO异常: {}", excelFile.getName(), e); + throw new RuntimeException("读取Excel文件失败"); + } finally { + // 3. 确保 Workbook 被正确关闭,释放资源 + if (workbook != null) { + try { + workbook.close(); + } catch (IOException e) { + log.warn("关闭Workbook失败", e); + } + } } result.setExcelFileName(zipContent.excelFileName); result.setExcelFilePath(zipContent.excelFilePath); diff --git a/backend/src/main/java/com/yfd/platform/data/service/impl/ImportTaskServiceImpl.java b/backend/src/main/java/com/yfd/platform/data/service/impl/ImportTaskServiceImpl.java index 1ffcd3b..c576f07 100644 --- a/backend/src/main/java/com/yfd/platform/data/service/impl/ImportTaskServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/data/service/impl/ImportTaskServiceImpl.java @@ -3,6 +3,7 @@ package com.yfd.platform.data.service.impl; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fasterxml.jackson.databind.ObjectMapper; @@ -133,14 +134,10 @@ public class ImportTaskServiceImpl extends ServiceImpl() + .eq(ImportTask::getId, id) + .set(ImportTask::getStatus, "CONFIRMED") + .set(ImportTask::getUpdatedAt, new Date())); // 如果没有配置自动填充,建议保留此行 } @Override diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysRoleMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysRoleMapper.java index 09201a1..4852da3 100644 --- a/backend/src/main/java/com/yfd/platform/system/mapper/SysRoleMapper.java +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysRoleMapper.java @@ -63,6 +63,12 @@ public interface SysRoleMapper extends BaseMapper { ***********************************/ List getRoleByUserId(String id); + + /** + * 批量获取用户角色 + */ + List getRolesByUserIds(@Param("userIds") List userIds); + /********************************** * 用途说明: 根据角色ID删除菜单与角色关联信息 * 参数说明 id 角色id diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java index e2bc0c9..112e8c0 100644 --- a/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java @@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.data.mapper.SysUserDataScopeMapper; import com.yfd.platform.system.domain.LoginUser; import com.yfd.platform.system.domain.SysRole; import com.yfd.platform.system.domain.SysUser; @@ -61,6 +62,9 @@ public class UserServiceImpl extends ServiceImpl impleme @Resource private FileSpaceProperties fileSpaceProperties; + @Resource + private SysUserDataScopeMapper sysUserDataScopeMapper; + /********************************** * 用途说明:获取当前用户账号及名称 * 参数说明 diff --git a/backend/src/main/resources/mapper/system/SysRoleMapper.xml b/backend/src/main/resources/mapper/system/SysRoleMapper.xml index c2c0d97..9d3f206 100644 --- a/backend/src/main/resources/mapper/system/SysRoleMapper.xml +++ b/backend/src/main/resources/mapper/system/SysRoleMapper.xml @@ -133,6 +133,16 @@ ORDER BY r."LEVEL" ASC, lastmodifydate ASC + + delete from sys_role_users where userid !=(select u.id from sys_user u where u.account="admin") and roleid=#{roleid} and userid=#{urserid}