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}