Merge branch 'dev-tw'

This commit is contained in:
tangwei 2026-05-15 14:18:52 +08:00
commit f2767bb5b5
49 changed files with 2826 additions and 479 deletions

View File

@ -11,6 +11,7 @@ import com.yfd.platform.constant.Constant;
import com.yfd.platform.system.domain.LoginUser; import com.yfd.platform.system.domain.LoginUser;
import com.yfd.platform.system.domain.Message; import com.yfd.platform.system.domain.Message;
import com.yfd.platform.system.service.IMessageService; import com.yfd.platform.system.service.IMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -25,6 +26,7 @@ import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
@Component @Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired @Autowired
@ -36,6 +38,9 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
FilterChain filterChain) throws ServletException, IOException { FilterChain filterChain) throws ServletException, IOException {
//获取token //获取token
String uri = httpServletRequest.getRequestURI(); String uri = httpServletRequest.getRequestURI();
if(uri.contains("/data/fishDraft/importZip")){
log.info("请求地址:{}", uri);
}
String token = httpServletRequest.getHeader("token"); String token = httpServletRequest.getHeader("token");
if (StrUtil.isEmpty(token) || "/user/login".equals(uri)) { if (StrUtil.isEmpty(token) || "/user/login".equals(uri)) {
filterChain.doFilter(httpServletRequest, httpServletResponse); filterChain.doFilter(httpServletRequest, httpServletResponse);

View File

@ -1,44 +0,0 @@
package com.yfd.platform.config;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 将以 /prod-api/ 开头的请求转发到去掉前缀的真实后端接口路径
* 例如/prod-api/user/code -> /user/code
* 这样可以兼容前端生产环境仍使用 /prod-api 作为网关前缀的情况
*/
@WebFilter(urlPatterns = "/prod-api/*", filterName = "prodApiPrefixFilter")
public class ProdApiPrefixFilter implements Filter {
private static final String PREFIX = "/prod-api";
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
if (!(req instanceof HttpServletRequest) || !(res instanceof HttpServletResponse)) {
chain.doFilter(req, res);
return;
}
HttpServletRequest request = (HttpServletRequest) req;
String uri = request.getRequestURI();
// 仅拦截 /prod-api/* 的接口请求并进行内部 forward
if (uri.startsWith(PREFIX + "/")) {
String forwardUri = uri.substring(PREFIX.length());
RequestDispatcher dispatcher = request.getRequestDispatcher(forwardUri);
dispatcher.forward(req, res);
return;
}
chain.doFilter(req, res);
}
}

View File

@ -1,4 +1,5 @@
package com.yfd.platform.config; package com.yfd.platform.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springdoc.core.models.GroupedOpenApi; import org.springdoc.core.models.GroupedOpenApi;
@ -6,9 +7,6 @@ import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Contact;
/**
* Springdoc OpenAPI 配置
*/
@Configuration @Configuration
public class SwaggerConfig { public class SwaggerConfig {
@ -23,37 +21,51 @@ public class SwaggerConfig {
); );
} }
@Bean // @Bean
public GroupedOpenApi groupWebsiteApi() { // public GroupedOpenApi groupPlatformApi() {
return GroupedOpenApi.builder() // return GroupedOpenApi.builder()
.group("1. 平台模块") // .group("1. 平台模块")
.packagesToScan("com.yfd.platform.modules.platformdb.controller") // .packagesToScan("com.yfd.platform.modules.platformdb.controller")
.build(); // .build();
} // }
@Bean // @Bean
public GroupedOpenApi groupQuartzApi() { // public GroupedOpenApi groupQuartzApi() {
return GroupedOpenApi.builder() // return GroupedOpenApi.builder()
.group("2. 定时任务") // .group("2. 定时任务")
.packagesToScan("com.yfd.platform.modules.quartz.controller") // .packagesToScan("com.yfd.platform.modules.quartz.controller")
.build(); // .build();
} // }
@Bean @Bean
public GroupedOpenApi groupSystemApi() { public GroupedOpenApi groupSystemApi() {
return GroupedOpenApi.builder() return GroupedOpenApi.builder()
.group("3. 系统管理") .group("1. 系统管理")
.packagesToScan("com.yfd.platform.system.controller") .packagesToScan("com.yfd.platform.system.controller")
.build(); .build();
} }
@Bean
public GroupedOpenApi groupDataEnvApi() {
return GroupedOpenApi.builder()
.group("2. 过鱼数据模块")
.packagesToScan("com.yfd.platform.data.controller")
.build();
}
@Bean @Bean
public GroupedOpenApi groupEnvApi() { public GroupedOpenApi groupEnvApi() {
return GroupedOpenApi.builder() return GroupedOpenApi.builder()
.group("4. 环境/基地/流域管理") .group("3. 全过程-生态环保数据服务")
.packagesToScan("com.yfd.platform.env.controller") .packagesToScan("com.yfd.platform.env.controller")
.build(); .build();
} }
@Bean
} public GroupedOpenApi groupEngApi() {
return GroupedOpenApi.builder()
.group("4. 全过程-常规水电数据服务")
.packagesToScan("com.yfd.platform.eng.controller")
.build();
}
}

View File

@ -13,6 +13,7 @@ import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* <p> * <p>
@ -116,4 +117,11 @@ public class ApprovalMainController {
boolean result = approvalMainService.removeById(id); boolean result = approvalMainService.removeById(id);
return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败");
} }
@PostMapping("/batchDelete")
@Operation(summary = "删除审批")
public ResponseResult delete(@RequestBody List<String> ids) {
boolean result = approvalMainService.removeBatchByIds(ids);
return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败");
}
} }

View File

@ -3,7 +3,9 @@ package com.yfd.platform.data.controller;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -11,8 +13,12 @@ import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import cn.hutool.core.io.FileUtil; 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.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@ -20,12 +26,15 @@ import com.yfd.platform.common.DataSourceRequest;
import com.yfd.platform.config.ResponseResult; import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.data.domain.*; import com.yfd.platform.data.domain.*;
import com.yfd.platform.data.domain.vo.FishDraftDataVO; import com.yfd.platform.data.domain.vo.FishDraftDataVO;
import com.yfd.platform.data.domain.vo.FishStatisticsVO;
import com.yfd.platform.data.service.AttachmentUploadService; import com.yfd.platform.data.service.AttachmentUploadService;
import com.yfd.platform.data.service.IFishDraftDataService; import com.yfd.platform.data.service.IFishDraftDataService;
import com.yfd.platform.data.service.IFishImportService; import com.yfd.platform.data.service.IFishImportService;
import com.yfd.platform.data.service.IFishStatisticsService;
import com.yfd.platform.data.service.IImportTaskService; import com.yfd.platform.data.service.IImportTaskService;
import com.yfd.platform.data.utils.ZipFileUtil; import com.yfd.platform.data.utils.ZipFileUtil;
import com.yfd.platform.utils.KendoUtil; import com.yfd.platform.utils.KendoUtil;
import com.yfd.platform.utils.MultipartStreamParser;
import com.yfd.platform.utils.SecurityUtils; import com.yfd.platform.utils.SecurityUtils;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -33,12 +42,12 @@ import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@ -69,6 +78,18 @@ public class FishDraftDataController {
@Resource @Resource
private AttachmentUploadService attachmentUploadService; private AttachmentUploadService attachmentUploadService;
@Resource
private IFishStatisticsService fishStatisticsService;
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
private final ExecutorService attachmentDeleteExecutor = Executors.newFixedThreadPool(4, r -> {
Thread t = new Thread(r, "attachment-delete");
t.setDaemon(true);
return t;
});
@PostMapping("/page") @PostMapping("/page")
@Operation(summary = "分页查询过鱼数据(关联电站和设施)") @Operation(summary = "分页查询过鱼数据(关联电站和设施)")
public ResponseResult queryPageList(@RequestBody DataSourceRequest dataSourceRequest) { public ResponseResult queryPageList(@RequestBody DataSourceRequest dataSourceRequest) {
@ -83,6 +104,13 @@ public class FishDraftDataController {
return ResponseResult.successData(list); return ResponseResult.successData(list);
} }
@PostMapping("/statistics")
@Operation(summary = "过鱼到数统计(按用户月度汇总,支持流域/电站多选过滤)")
public ResponseResult statistics(@RequestBody DataSourceRequest dataSourceRequest) {
Page<FishStatisticsVO> result = fishStatisticsService.queryPage(dataSourceRequest);
return ResponseResult.successData(result);
}
@GetMapping("/getById") @GetMapping("/getById")
@Operation(summary = "根据ID查询") @Operation(summary = "根据ID查询")
public ResponseResult getById(@RequestParam String id) { public ResponseResult getById(@RequestParam String id) {
@ -133,23 +161,10 @@ public class FishDraftDataController {
@PostMapping("/batchSaveDraft") @PostMapping("/batchSaveDraft")
@Operation(summary = "批量保存草稿") @Operation(summary = "批量保存草稿")
@Transactional(rollbackFor = Exception.class)
public ResponseResult saveDraft(@RequestBody FishImportRowRequest request) { public ResponseResult saveDraft(@RequestBody FishImportRowRequest request) {
String taskId = request.getTaskId(); String taskId = request.getTaskId();
ImportTask importTask = importTaskService.getById(taskId); ImportTask importTask = importTaskService.getById(taskId);
String resultJson = importTask.getResultJson(); FishImportResult importResult = importTaskService.buildImportResult(taskId);
FishImportResult importResult = null;
Map<String, String> imageFiles = null;
Map<String, String> videoFiles = null;
if (resultJson != null && !resultJson.isEmpty()) {
try {
importResult = objectMapper.readValue(resultJson, FishImportResult.class);
imageFiles = importResult.getImageFiles();
videoFiles = importResult.getVideoFiles();
} catch (Exception e) {
e.printStackTrace();
}
}
if (importResult == null || importResult.getRows() == null) { if (importResult == null || importResult.getRows() == null) {
return ResponseResult.error("导入数据不存在"); return ResponseResult.error("导入数据不存在");
@ -164,10 +179,12 @@ public class FishDraftDataController {
data.setDeletedFlag(0); data.setDeletedFlag(0);
data.setLockFlag(0); data.setLockFlag(0);
data.setTm(date); data.setTm(date);
data.setVdpthList(row.getVdpthList());
data.setPicpthList(row.getPicpthList());
fishDraftDataList.add(data); fishDraftDataList.add(data);
} }
boolean result = fishDraftDataService.saveBatch(fishDraftDataList); boolean result = fishDraftDataService.batchSaveDraft(fishDraftDataList);
fishImportService.processAttachmentsAsync(fishDraftDataList, imageFiles, videoFiles,importTask.getTempDir()); fishImportService.processAttachmentsAsync(fishDraftDataList, importTask.getTempDir());
importTaskService.markSuccess(taskId); importTaskService.markSuccess(taskId);
return result ? ResponseResult.success("保存成功") : ResponseResult.error("保存失败"); return result ? ResponseResult.success("保存成功") : ResponseResult.error("保存失败");
} }
@ -180,11 +197,10 @@ public class FishDraftDataController {
if (importTask == null) { if (importTask == null) {
return ResponseResult.error("导入任务不存在"); return ResponseResult.error("导入任务不存在");
} }
String resultJson = importTask.getResultJson(); FishImportResult importResult = importTaskService.buildImportResult(taskId);
List<FishDraftData> fishDraftDataList = batchSaveDraftRequest.getFishDraftDataList(); List<FishDraftData> fishDraftDataList = batchSaveDraftRequest.getFishDraftDataList();
if (resultJson != null && !resultJson.isEmpty()) { if (importResult != null) {
try { try {
FishImportResult importResult = objectMapper.readValue(resultJson, FishImportResult.class);
ZipFileUtil.ZipContent content = new ZipFileUtil.ZipContent(); ZipFileUtil.ZipContent content = new ZipFileUtil.ZipContent();
content.images = importResult.getImageFiles(); content.images = importResult.getImageFiles();
content.videos = importResult.getVideoFiles(); content.videos = importResult.getVideoFiles();
@ -203,7 +219,6 @@ public class FishDraftDataController {
}); });
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
// ignore parse error
} }
} }
@ -226,10 +241,90 @@ public class FishDraftDataController {
return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败");
} }
// @PostMapping("/approvalIdRemoveDraft")
// @Operation(summary = "根据批次号批量删除草稿(软删除)")
// public ResponseResult approvalIdRemoveDraft(@RequestBody BatchApproveRequest request) {
// List<FishDraftData> draft = fishDraftDataService.list(new LambdaQueryWrapper<FishDraftData>().eq(FishDraftData::getDeletedFlag, 0).in(FishDraftData::getApprovalId, request.getApprovalIds()).eq(FishDraftData::getStatus, "REJECTED").select(FishDraftData::getId));
// List<String> ids = draft.stream().map(FishDraftData::getId).toList();
// boolean result = fishDraftDataService.batchRemoveDraft(ids);
// if(result){
// List<FishDraftData> list = fishDraftDataService.list(new LambdaQueryWrapper<FishDraftData>().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<String> split = StrUtil.split(picpth, StrUtil.C_COMMA);
// for (String fileId : split) {
// if (StrUtil.isNotBlank(fileId)) {
// attachmentUploadService.deleteFile(fileId.trim());
// }
// }
// }
// if (StrUtil.isNotBlank(vdpth)) {
// List<String> 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);
// }
// }
// }, attachmentDeleteExecutor).exceptionally(ex -> {
// log.error("异步删除任务执行异常", ex);
// return null;
// });
// }
// return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败");
// }
@PostMapping("/batchRemoveDraft") @PostMapping("/batchRemoveDraft")
@Operation(summary = "批量删除草稿(软删除)") @Operation(summary = "批量删除草稿(软删除)")
public ResponseResult batchRemoveDraft(@RequestBody List<String> ids) { public ResponseResult batchRemoveDraft(@RequestBody List<String> ids) {
boolean result = fishDraftDataService.batchRemoveDraft(ids); boolean result = fishDraftDataService.batchRemoveDraft(ids);
if(result){
List<FishDraftData> list = fishDraftDataService.list(new LambdaQueryWrapper<FishDraftData>().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<String> split = StrUtil.split(picpth, StrUtil.C_COMMA);
for (String fileId : split) {
if (StrUtil.isNotBlank(fileId)) {
attachmentUploadService.deleteFile(fileId.trim());
}
}
}
if (StrUtil.isNotBlank(vdpth)) {
List<String> 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);
}
}
}, attachmentDeleteExecutor).exceptionally(ex -> {
log.error("异步删除任务执行异常", ex);
return null;
});
}
return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败");
} }
@ -353,16 +448,11 @@ public class FishDraftDataController {
@PostMapping("/importZip") @PostMapping("/importZip")
@Operation(summary = "导入ZIP过鱼数据每个用户同时只能进行一次导入") @Operation(summary = "导入ZIP过鱼数据每个用户同时只能进行一次导入")
public ResponseResult importZip(@RequestParam("file") MultipartFile file) { public ResponseResult importZip(HttpServletRequest request) {
log.info("开始导入ZIP文件"); log.info("开始导入ZIP文件");
if (file == null || file.isEmpty()) {
return ResponseResult.error("请上传文件");
}
String fileName = file.getOriginalFilename();
if (fileName == null || (!fileName.endsWith(".zip"))) {
return ResponseResult.error("请上传ZIP文件(.zip)");
}
String uploadUserId = SecurityUtils.getUserId(); String uploadUserId = SecurityUtils.getUserId();
if (importTaskService.hasImportingTask(uploadUserId)) { if (importTaskService.hasImportingTask(uploadUserId)) {
return ResponseResult.error("您有正在进行的导入任务,请等待完成后重试"); return ResponseResult.error("您有正在进行的导入任务,请等待完成后重试");
} }
@ -370,52 +460,72 @@ public class FishDraftDataController {
String importNo = "IMP" + System.currentTimeMillis(); String importNo = "IMP" + System.currentTimeMillis();
String taskId = UUID.randomUUID().toString(); String taskId = UUID.randomUUID().toString();
ImportTask task = null;
Path tempDirPath = null;
try { try {
String baseTempDir = ZipFileUtil.getDefaultTempDir(); String baseTempDir = ZipFileUtil.getDefaultTempDir();
String taskDirName = "zip_" + UUID.randomUUID().toString().substring(0, 8); String taskDirName = "zip_" + UUID.randomUUID().toString().substring(0, 8);
Path tempDirPath = Paths.get(baseTempDir, taskDirName); tempDirPath = Paths.get(baseTempDir, taskDirName);
Files.createDirectories(tempDirPath);
File savedZipFile = new File(tempDirPath.toFile(), "upload.zip");
file.transferTo(savedZipFile);
log.info("ZIP文件已保存到: {}", savedZipFile.getAbsolutePath());
ImportTask task = new ImportTask(); task = new ImportTask();
task.setId(taskId); task.setId(taskId);
task.setImportNo(importNo); task.setImportNo(importNo);
task.setBizType("FISH"); task.setBizType("FISH");
task.setFileName(fileName); task.setStatus("UPLOADING");
task.setFileSize(file.getSize());
task.setStatus("UPLOADED");
task.setUploadUserId(uploadUserId); task.setUploadUserId(uploadUserId);
task.setUploadTime(new Date()); task.setUploadTime(new Date());
task.setTempDir(tempDirPath.toString()); task.setTempDir(tempDirPath.toString());
importTaskService.save(task); importTaskService.save(task);
log.info("导入任务已创建: {}", taskId); log.info("导入任务已预创建: {}, 状态: UPLOADING", taskId);
MultipartStreamParser.StreamedFile streamedFile =
MultipartStreamParser.streamToFile(request, tempDirPath);
String fileName = streamedFile.getOriginalFileName();
if (fileName == null || !fileName.toLowerCase().endsWith(".zip")) {
importTaskService.markFailed(taskId, "文件格式错误: 请上传ZIP文件");
return ResponseResult.error("请上传ZIP文件(.zip)");
}
File savedZipFile = streamedFile.getTargetFile();
long fileSize = streamedFile.getFileSize();
log.info("ZIP文件已保存到: {} ({} bytes)", savedZipFile.getAbsolutePath(), fileSize);
task.setFileName(fileName);
task.setFileSize(fileSize);
task.setStatus("UPLOADED");
importTaskService.updateById(task);
log.info("导入任务已更新: {}, 状态: UPLOADED, 文件名: {}", taskId, fileName);
SecurityContext securityContext = SecurityContextHolder.getContext(); SecurityContext securityContext = SecurityContextHolder.getContext();
Path finalTempDirPath = tempDirPath;
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
try { try {
SecurityContextHolder.setContext(securityContext); SecurityContextHolder.setContext(securityContext);
log.info("异步开始解析ZIP文件, taskId: {}", taskId); log.info("异步开始解析ZIP文件, taskId: {}", taskId);
FishImportResult result = fishImportService.parseAndMapZipFromFile( FishImportResult result = fishImportService.parseAndMapZipFromFile(
savedZipFile, tempDirPath.toString(), uploadUserId); savedZipFile, finalTempDirPath.toString(), uploadUserId);
result.setTaskId(taskId); result.setTaskId(taskId);
String status = "VALIDATED";
if ("1".equals(result.getCode())) { String status = "1".equals(result.getCode()) ? "FAILED" : "VALIDATED";
status = "FAILED";
}
importTaskService.updateStatus(taskId, status, result.getTempDir(), null); importTaskService.updateStatus(taskId, status, result.getTempDir(), null);
importTaskService.updateProgress(taskId, result.getTotalCount(), importTaskService.updateProgress(taskId, result.getTotalCount(),
result.getSuccessCount(), result.getFailedCount()); result.getSuccessCount(), result.getFailedCount());
String resultJson = objectMapper.writeValueAsString(result); importTaskService.saveResultJson(taskId, result);
importTaskService.saveResultJson(taskId, resultJson);
log.info("异步解析完成, taskId: {}, 状态: {}", taskId, status); log.info("异步解析完成, taskId: {}, 状态: {}, 成功: {}, 失败: {}",
taskId, status, result.getSuccessCount(), result.getFailedCount());
} catch (Exception e) { } catch (Exception e) {
log.error("异步解析ZIP失败, taskId: {}", taskId, e); log.error("异步解析ZIP失败, taskId: {}", taskId, e);
importTaskService.markFailed(taskId, "导入失败: " + e.getMessage()); importTaskService.markFailed(taskId, "导入失败: " + e.getMessage());
} finally { } finally {
SecurityContextHolder.clearContext(); SecurityContextHolder.clearContext();
if (savedZipFile.exists()) { if (savedZipFile.exists()) {
savedZipFile.delete(); boolean deleted = savedZipFile.delete();
log.debug("临时ZIP文件删除: {}, 结果: {}", savedZipFile.getAbsolutePath(), deleted);
} }
} }
}); });
@ -425,9 +535,23 @@ public class FishDraftDataController {
response.put("importNo", importNo); response.put("importNo", importNo);
response.put("status", "UPLOADED"); response.put("status", "UPLOADED");
return ResponseResult.successData(response); return ResponseResult.successData(response);
} catch (Exception e) { } catch (Exception e) {
log.error("创建导入任务失败", e); log.error("导入ZIP文件失败, taskId: {}", taskId, e);
importTaskService.markFailed(taskId, "导入失败: " + e.getMessage());
if (task != null) {
importTaskService.markFailed(taskId, "导入失败: " + e.getMessage());
}
if (tempDirPath != null) {
try {
cn.hutool.core.io.FileUtil.del(tempDirPath.toFile());
log.debug("清理临时目录: {}", tempDirPath);
} catch (Exception cleanupEx) {
log.warn("清理临时目录失败: {}", tempDirPath, cleanupEx);
}
}
return ResponseResult.error("导入失败: " + e.getMessage()); return ResponseResult.error("导入失败: " + e.getMessage());
} }
} }
@ -445,13 +569,11 @@ public class FishDraftDataController {
return ResponseResult.error("任务不存在"); return ResponseResult.error("任务不存在");
} }
String resultJson = task.getResultJson(); FishImportResult importResult = importTaskService.buildImportResult(taskId);
if (resultJson == null || resultJson.isEmpty()) { if (importResult == null) {
return ResponseResult.error("任务结果为空"); return ResponseResult.error("任务结果为空");
} }
FishImportResult importResult = objectMapper.readValue(resultJson, FishImportResult.class);
Map<String, Object> previewData = new HashMap<>(); Map<String, Object> previewData = new HashMap<>();
previewData.put("tempDir", importResult.getTempDir()); previewData.put("tempDir", importResult.getTempDir());
previewData.put("excelFileName", importResult.getExcelFileName()); previewData.put("excelFileName", importResult.getExcelFileName());
@ -495,20 +617,14 @@ public class FishDraftDataController {
@GetMapping("/previewFile") @GetMapping("/previewFile")
@Operation(summary = "预览临时文件内容") @Operation(summary = "预览临时文件内容")
public void previewFile(@RequestParam String taskId, @RequestParam String filename, @RequestParam String type, HttpServletRequest request, HttpServletResponse response) { public void previewFile(@RequestParam String taskId, @RequestParam String filename, @RequestParam String type, HttpServletRequest request, HttpServletResponse response) {
String decodedFilename = URLDecoder.decode(filename, StandardCharsets.UTF_8);
log.debug("原始文件名: {}, 解码后: {}", filename, decodedFilename);
ImportTask importTask = importTaskService.getById(taskId); ImportTask importTask = importTaskService.getById(taskId);
String resultJson = importTask.getResultJson();
String filePath = null; String filePath = null;
String dir = "1".equals(type) ? "images" : "videos"; String dir = "1".equals(type) ? "images" : "videos";
if (resultJson != null && !resultJson.isEmpty()) { if (importTask != null && importTask.getTempDir() != null) {
try { filePath = importTask.getTempDir() + File.separator + dir + File.separator + decodedFilename;
FishImportResult importResult = objectMapper.readValue(resultJson, FishImportResult.class);
String tempDir = importResult.getTempDir();
filePath = tempDir + File.separator + dir + File.separator + filename;
} catch (Exception e) {
e.printStackTrace();
// ignore parse error
}
} }
if (filePath == null) { if (filePath == null) {
writeErrorResponse(response, "文件路径不能为空"); writeErrorResponse(response, "文件路径不能为空");
@ -568,13 +684,12 @@ public class FishDraftDataController {
return ResponseResult.error("任务不存在"); return ResponseResult.error("任务不存在");
} }
String resultJson = importTask.getResultJson(); FishImportResult importResult = importTaskService.buildImportResult(taskId);
if (resultJson == null || resultJson.isEmpty()) { if (importResult == null) {
return ResponseResult.error("任务结果为空"); return ResponseResult.error("任务结果为空");
} }
try { try {
FishImportResult importResult = objectMapper.readValue(resultJson, FishImportResult.class);
boolean found = false; boolean found = false;
for (FishImportResult.FishImportRow row : importResult.getRows()) { for (FishImportResult.FishImportRow row : importResult.getRows()) {
@ -632,8 +747,7 @@ public class FishDraftDataController {
return ResponseResult.error("未找到对应的数据行"); return ResponseResult.error("未找到对应的数据行");
} }
String updatedJson = objectMapper.writeValueAsString(importResult); importTaskService.saveResultJson(taskId, importResult);
importTaskService.saveResultJson(taskId, updatedJson);
return ResponseResult.success("删除成功"); return ResponseResult.success("删除成功");
@ -723,7 +837,6 @@ public class FishDraftDataController {
result.put("currentTask", null); result.put("currentTask", null);
return ResponseResult.successData(result); return ResponseResult.successData(result);
} }
String statusText = getStatusText(currentTask.getStatus()); String statusText = getStatusText(currentTask.getStatus());
boolean canImport = isTaskComplete(currentTask.getStatus()); boolean canImport = isTaskComplete(currentTask.getStatus());
@ -752,7 +865,8 @@ public class FishDraftDataController {
} }
private boolean isTaskComplete(String status) { private boolean isTaskComplete(String status) {
return "CONFIRMED".equals(status) || "FAILED".equals(status) || "CANCELLED".equals(status); return "CONFIRMED".equals(status) || "CANCELLED".equals(status);
// return "CONFIRMED".equals(status) || "FAILED".equals(status) || "CANCELLED".equals(status);
} }
@GetMapping("/getLastImportResult") @GetMapping("/getLastImportResult")
@ -768,15 +882,7 @@ public class FishDraftDataController {
)); ));
} }
FishImportResult importResult = null; FishImportResult importResult = importTaskService.buildImportResult(lastTask.getId());
if (lastTask.getResultJson() != null && !lastTask.getResultJson().isEmpty()) {
try {
importResult = objectMapper.readValue(lastTask.getResultJson(), FishImportResult.class);
} catch (Exception e) {
e.printStackTrace();
// ignore parse error
}
}
int totalCount = lastTask.getTotalCount() != null ? lastTask.getTotalCount() : 0; int totalCount = lastTask.getTotalCount() != null ? lastTask.getTotalCount() : 0;
int successCount = lastTask.getSuccessCount() != null ? lastTask.getSuccessCount() : 0; int successCount = lastTask.getSuccessCount() != null ? lastTask.getSuccessCount() : 0;
@ -871,13 +977,11 @@ public class FishDraftDataController {
return ResponseResult.error("任务不存在"); return ResponseResult.error("任务不存在");
} }
String resultJson = task.getResultJson(); FishImportResult importResult = importTaskService.buildImportResult(taskId);
if (resultJson == null || resultJson.isEmpty()) { if (importResult == null) {
return ResponseResult.error("任务结果为空"); return ResponseResult.error("任务结果为空");
} }
FishImportResult importResult = objectMapper.readValue(resultJson, FishImportResult.class);
FishImportResult.FishImportRow targetRow = null; FishImportResult.FishImportRow targetRow = null;
int targetIndex = -1; int targetIndex = -1;
for (int i = 0; i < importResult.getRows().size(); i++) { for (int i = 0; i < importResult.getRows().size(); i++) {
@ -925,8 +1029,8 @@ public class FishDraftDataController {
importResult.setSuccessCount(successCount); importResult.setSuccessCount(successCount);
importResult.setFailedCount(failedCount); importResult.setFailedCount(failedCount);
String updatedJson = objectMapper.writeValueAsString(importResult); // String updatedJson = objectMapper.writeValueAsString(importResult);
importTaskService.saveResultJson(taskId, updatedJson); importTaskService.saveResultJson(taskId, importResult);
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("success", true); map.put("success", true);
@ -961,13 +1065,11 @@ public class FishDraftDataController {
return ResponseResult.error("任务不存在"); return ResponseResult.error("任务不存在");
} }
String resultJson = task.getResultJson(); FishImportResult importResult = importTaskService.buildImportResult(taskId);
if (resultJson == null || resultJson.isEmpty()) { if (importResult == null) {
return ResponseResult.error("任务结果为空"); return ResponseResult.error("任务结果为空");
} }
FishImportResult importResult = objectMapper.readValue(resultJson, FishImportResult.class);
FishImportResult.FishImportRow targetRow = null; FishImportResult.FishImportRow targetRow = null;
int targetIndex = -1; int targetIndex = -1;
for (int i = 0; i < importResult.getRows().size(); i++) { for (int i = 0; i < importResult.getRows().size(); i++) {
@ -983,8 +1085,8 @@ public class FishDraftDataController {
return ResponseResult.error("未找到对应的数据行"); return ResponseResult.error("未找到对应的数据行");
} }
importResult.getRows().remove(targetIndex); importResult.getRows().remove(targetIndex);
String updatedJson = objectMapper.writeValueAsString(importResult); // String updatedJson = objectMapper.writeValueAsString(importResult);
importTaskService.saveResultJson(taskId, updatedJson); importTaskService.saveResultJson(taskId, importResult);
return ResponseResult.success(); return ResponseResult.success();
} catch (Exception e) { } catch (Exception e) {
@ -1122,13 +1224,11 @@ public class FishDraftDataController {
return ResponseResult.error("任务不存在"); return ResponseResult.error("任务不存在");
} }
String resultJson = task.getResultJson(); FishImportResult importResult = importTaskService.buildImportResult(taskId);
if (resultJson == null || resultJson.isEmpty()) { if (importResult == null) {
return ResponseResult.error("任务结果为空"); return ResponseResult.error("任务结果为空");
} }
FishImportResult importResult = objectMapper.readValue(resultJson, FishImportResult.class);
FishImportResult.FishImportRow matchedRow = findMatchingRow(importResult, data.getId()); FishImportResult.FishImportRow matchedRow = findMatchingRow(importResult, data.getId());
FishImportResult.FishImportRow resultRow = new FishImportResult.FishImportRow(); FishImportResult.FishImportRow resultRow = new FishImportResult.FishImportRow();
@ -1214,4 +1314,17 @@ public class FishDraftDataController {
return errors; return errors;
} }
@jakarta.annotation.PreDestroy
public void shutdown() {
attachmentDeleteExecutor.shutdown();
try {
if (!attachmentDeleteExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
attachmentDeleteExecutor.shutdownNow();
}
} catch (InterruptedException e) {
attachmentDeleteExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
} }

View File

@ -8,6 +8,8 @@ import lombok.EqualsAndHashCode;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map;
/** /**
* <p> * <p>
@ -65,11 +67,23 @@ public class FishDraftData implements Serializable {
*/ */
private Date strdt; private Date strdt;
/**
* 开始日期
*/
@TableField(exist = false)
private String strdtStr;
/** /**
* 结束日期 * 结束日期
*/ */
private Date enddt; private Date enddt;
/**
* 结束日期
*/
@TableField(exist = false)
private String enddtStr;
/** /**
* 游向上行/下行/上行折返/下行折返 * 游向上行/下行/上行折返/下行折返
*/ */
@ -254,4 +268,10 @@ public class FishDraftData implements Serializable {
@TableField(exist = false) @TableField(exist = false)
private String ftpName; private String ftpName;
@TableField(exist = false)
private List<Map<String, String>> vdpthList;
@TableField(exist = false)
private List<Map<String, String>> picpthList;
} }

View File

@ -21,8 +21,6 @@ public class FishImportResult {
private List<String> unrecognizedFields; private List<String> unrecognizedFields;
private Map<String, String> imageFiles; private Map<String, String> imageFiles;
private Map<String, String> videoFiles; private Map<String, String> videoFiles;
// public Map<String, String> images;
// public Map<String, String> videos;
private String tempDir; private String tempDir;
private String excelFileName; private String excelFileName;
private String excelFilePath; private String excelFilePath;

View File

@ -96,9 +96,34 @@ public class ImportTask implements Serializable {
private Date expireTime; private Date expireTime;
/** /**
* 导入结果JSON存储校验后的数据 * 解析结果摘要
*/ */
private String resultJson; private String summary;
/**
* 结果代码0成功 1失败
*/
private String code;
/**
* 结果消息
*/
private String message;
/**
* 未识别字段逗号分隔
*/
private String unrecognizedFields;
// /**
// * 图片文件映射JSON
// */
// private String imageFilesJson;
//
// /**
// * 视频文件映射JSON
// */
// private String videoFilesJson;
/** /**
* 创建时间 * 创建时间

View File

@ -0,0 +1,104 @@
package com.yfd.platform.data.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* <p>
* 导入任务行数据表DATA_JSON 拆为独立字段彻底消除 CLOB
* </p>
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("IMPORT_TASK_ROW")
public class ImportTaskRow implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String taskId;
private Integer rowIndex;
private String status;
// ========== FishDraftData 字段 DATA_JSON ==========
private String dataId;
private String stcd;
private String stnm;
private String rstcd;
private String ennm;
private String hbrvcd;
private String hbrvnm;
private String rvcd;
private String baseId;
private String baseName;
private Date strdt;
private String strdtStr;
private Date enddt;
private String ftp;
private String ftpName;
private Integer isfs;
private String direction;
private Integer fcnt;
private String fsz;
private String fwet;
private BigDecimal wt;
private String picpth;
private String vdpth;
private Date tm;
private String sourceType;
private Integer mouth;
private Integer yr;
// ========== 校验相关 ==========
private String warnings;
private String unrecognizedFields;
private String vdpthListJson;
private String picpthListJson;
private String vdpthWarnings;
private String picpthWarnings;
@TableField(fill = FieldFill.INSERT)
private Date createdAt;
}

View File

@ -49,6 +49,12 @@ public class SysUserDataScope implements Serializable {
*/ */
private String orgId; private String orgId;
/**
* 资源名称根据orgType关联查询得出
*/
@TableField(exist = false)
private String orgName;
/** /**
* 上级资源编码可选用于层级追溯 * 上级资源编码可选用于层级追溯
*/ */

View File

@ -0,0 +1,38 @@
package com.yfd.platform.data.domain.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class FishStatisticsVO implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;
private String realName;
private String phone;
private String contact;
private String basinNames;
private String stationNames;
private String basinCode;
private String stationCode;
private String reportMonth;
private Date minStrdt;
private Date maxEnddt;
private Integer totalFcnt;
private Integer hasData;
}

View File

@ -0,0 +1,17 @@
package com.yfd.platform.data.mapper;
import com.yfd.platform.data.domain.vo.FishStatisticsVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface FishStatisticsMapper {
List<FishStatisticsVO> queryStatistics(@Param("basinCode") String basinCode,
@Param("stationCode") String stationCode,
@Param("startRow") int startRow,
@Param("endRow") int endRow);
int countStatistics(@Param("basinCode") String basinCode,
@Param("stationCode") String stationCode);
}

View File

@ -40,11 +40,10 @@ public interface ImportTaskMapper extends BaseMapper<ImportTask> {
@Select("SELECT * FROM (" + @Select("SELECT * FROM (" +
"SELECT ID, IMPORT_NO, BIZ_TYPE, FILE_NAME, FILE_SIZE, FILE_PATH, TEMP_DIR, " + "SELECT ID, IMPORT_NO, BIZ_TYPE, FILE_NAME, FILE_SIZE, FILE_PATH, TEMP_DIR, " +
"TOTAL_COUNT, SUCCESS_COUNT, FAIL_COUNT, STATUS, ERROR_MSG, " + "TOTAL_COUNT, SUCCESS_COUNT, FAIL_COUNT, STATUS, ERROR_MSG " +
"UPLOAD_USER_ID, UPLOAD_TIME, EXPIRE_TIME, CREATED_AT, UPDATED_AT " +
"FROM IMPORT_TASK " + "FROM IMPORT_TASK " +
"WHERE UPLOAD_USER_ID = #{uploadUserId} " + "WHERE UPLOAD_USER_ID = #{uploadUserId} " +
"AND STATUS IN ('UPLOADED', 'PARSING', 'VALIDATED') " + "AND STATUS IN ('UPLOADED', 'PARSING', 'VALIDATED','FAILED') " +
"ORDER BY CREATED_AT DESC" + "ORDER BY CREATED_AT DESC" +
") WHERE ROWNUM = 1") ") WHERE ROWNUM = 1")
List<ImportTask> selectByUserIdAndStatuses(@Param("uploadUserId") String uploadUserId); List<ImportTask> selectByUserIdAndStatuses(@Param("uploadUserId") String uploadUserId);

View File

@ -0,0 +1,21 @@
package com.yfd.platform.data.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yfd.platform.data.domain.ImportTaskRow;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* <p>
* 导入任务行数据表 Mapper 接口
* </p>
*/
public interface ImportTaskRowMapper extends BaseMapper<ImportTaskRow> {
List<ImportTaskRow> selectByTaskId(@Param("taskId") String taskId);
List<ImportTaskRow> selectByTaskIdAndStatus(@Param("taskId") String taskId, @Param("status") String status);
int deleteByTaskId(@Param("taskId") String taskId);
}

View File

@ -43,4 +43,18 @@ public interface SysUserDataScopeMapper extends BaseMapper<SysUserDataScope> {
* 查询有效权限状态=1且在有效期内的 * 查询有效权限状态=1且在有效期内的
*/ */
List<SysUserDataScope> selectValidPermissions(@Param("userId") String userId); List<SysUserDataScope> selectValidPermissions(@Param("userId") String userId);
/**
* 根据用户ID查询权限列表含关联资源名称
* orgType=STATION时关联SD_ENGINFO_B_H获取ennm工程名称
* orgType=HBRVCD时关联SD_HBRV_DIC获取hbrvnm基地流域名称
* orgType=BASE时关联SD_HYDROBASE获取basename基地名称
* orgType=RVCD时关联SD_RVCD_DIC获取rvnm流域名称
*/
List<SysUserDataScope> selectValidPermissionsWithName(@Param("userId") String userId);
/**
* 批量根据用户ID查询权限列表含关联资源名称
*/
List<SysUserDataScope> selectValidPermissionsWithNameByUserIds(@Param("userIds") List<String> userIds);
} }

View File

@ -96,4 +96,9 @@ public interface IFishDraftDataService extends IService<FishDraftData> {
* 审批人修改数据并记录变更日志 * 审批人修改数据并记录变更日志
*/ */
boolean updateByIdWithLog(FishDraftData fishDraftData); boolean updateByIdWithLog(FishDraftData fishDraftData);
/**
* 批量保存草稿带事务仅包裹 INSERT
*/
boolean batchSaveDraft(List<FishDraftData> fishDraftDataList);
} }

View File

@ -1,5 +1,6 @@
package com.yfd.platform.data.service; package com.yfd.platform.data.service;
import com.yfd.platform.data.domain.FishDraftData;
import com.yfd.platform.data.domain.FishImportRequest; import com.yfd.platform.data.domain.FishImportRequest;
import com.yfd.platform.data.domain.FishImportResult; import com.yfd.platform.data.domain.FishImportResult;
import com.yfd.platform.data.utils.ZipFileUtil; import com.yfd.platform.data.utils.ZipFileUtil;
@ -7,6 +8,8 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import java.util.Map;
/** /**
* <p> * <p>
@ -52,8 +55,6 @@ public interface IFishImportService {
void processAttachments(FishImportResult result, ZipFileUtil.ZipContent zipContent); void processAttachments(FishImportResult result, ZipFileUtil.ZipContent zipContent);
void processAttachmentsAsync(java.util.List<com.yfd.platform.data.domain.FishDraftData> dataList, void processAttachmentsAsync(List<FishDraftData> dataList,
java.util.Map<String, String> imageFiles,
java.util.Map<String, String> videoFiles,
String tempDir); String tempDir);
} }

View File

@ -0,0 +1,10 @@
package com.yfd.platform.data.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.platform.common.DataSourceRequest;
import com.yfd.platform.data.domain.vo.FishStatisticsVO;
public interface IFishStatisticsService {
Page<FishStatisticsVO> queryPage(DataSourceRequest dataSourceRequest);
}

View File

@ -2,7 +2,9 @@ package com.yfd.platform.data.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.yfd.platform.data.domain.FishImportResult;
import com.yfd.platform.data.domain.ImportTask; import com.yfd.platform.data.domain.ImportTask;
import com.yfd.platform.data.domain.ImportTaskRow;
import java.util.List; import java.util.List;
@ -79,12 +81,27 @@ public interface IImportTaskService extends IService<ImportTask> {
ImportTask getCurrentTaskByUserId(String uploadUserId); ImportTask getCurrentTaskByUserId(String uploadUserId);
/** /**
* 保存导入结果JSON * 保存导入结果同时写入 IMPORT_TASK 汇总字段和 IMPORT_TASK_ROW 行数据
*/ */
boolean saveResultJson(String taskId, String resultJson); boolean saveResultJson(String taskId, FishImportResult result);
/** /**
* 获取用户最后一次导入结果用于断点续传或查看历史 * 获取用户最后一次导入结果用于断点续传或查看历史
*/ */
ImportTask getLastImportResult(String uploadUserId); ImportTask getLastImportResult(String uploadUserId);
/**
* 根据任务ID查询行数据列表
*/
List<ImportTaskRow> getRowsByTaskId(String taskId);
/**
* 根据任务ID和状态查询行数据列表
*/
List<ImportTaskRow> getRowsByTaskIdAndStatus(String taskId, String status);
/**
* 根据任务ID重建 FishImportResult 对象从行表组装
*/
FishImportResult buildImportResult(String taskId);
} }

View File

@ -198,6 +198,7 @@ public class FishDraftDataServiceImpl extends ServiceImpl<FishDraftDataMapper, F
public boolean saveDraft(FishDraftData fishDraftData) { public boolean saveDraft(FishDraftData fishDraftData) {
fishDraftData.setStatus("DRAFT"); fishDraftData.setStatus("DRAFT");
fishDraftData.setDeletedFlag(0); fishDraftData.setDeletedFlag(0);
fishDraftData.setEnddt(fishDraftData.getStrdt());
fishDraftData.setLockFlag(0); fishDraftData.setLockFlag(0);
return this.save(fishDraftData); return this.save(fishDraftData);
} }
@ -209,6 +210,7 @@ public class FishDraftDataServiceImpl extends ServiceImpl<FishDraftDataMapper, F
if (existing == null || existing.getLockFlag() == 1) { if (existing == null || existing.getLockFlag() == 1) {
return false; return false;
} }
fishDraftData.setEnddt(fishDraftData.getStrdt());
boolean isSubmitted = existing.getApprovalId() != null && StrUtil.isNotBlank(existing.getApprovalId()); boolean isSubmitted = existing.getApprovalId() != null && StrUtil.isNotBlank(existing.getApprovalId());
try { try {
String beforeJson = objectMapper.writeValueAsString(existing); String beforeJson = objectMapper.writeValueAsString(existing);
@ -655,5 +657,15 @@ public class FishDraftDataServiceImpl extends ServiceImpl<FishDraftDataMapper, F
} }
} }
@Override
@Transactional(rollbackFor = Exception.class)
public boolean batchSaveDraft(List<FishDraftData> fishDraftDataList) {
if (fishDraftDataList == null || fishDraftDataList.isEmpty()) {
return false;
}
long start = System.currentTimeMillis();
boolean result = this.saveBatch(fishDraftDataList, 500);
log.info("批量保存草稿完成, 共{}条, 耗时{}ms", fishDraftDataList.size(), System.currentTimeMillis() - start);
return result;
}
} }

View File

@ -3,6 +3,7 @@ package com.yfd.platform.data.service.impl;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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.FishDraftData;
import com.yfd.platform.data.domain.FishImportRequest; import com.yfd.platform.data.domain.FishImportRequest;
import com.yfd.platform.data.domain.FishImportResult; 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 com.yfd.platform.utils.SecurityUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
@ -33,9 +35,13 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.ParseException; import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Service @Service
@Slf4j @Slf4j
@ -74,6 +80,12 @@ public class FishImportServiceImpl implements IFishImportService {
@Resource @Resource
private SysUserDataScopeMapper userDataScopeMapper; private SysUserDataScopeMapper userDataScopeMapper;
private final ExecutorService attachmentExecutor = Executors.newFixedThreadPool(4, r -> {
Thread t = new Thread(r, "attachment-upload");
t.setDaemon(true);
return t;
});
private static final Map<String, String> EXCEL_COLUMN_MAPPING = new LinkedHashMap<>(); private static final Map<String, String> EXCEL_COLUMN_MAPPING = new LinkedHashMap<>();
private static final Map<Integer, String> EXCEL_COLUMN_INDEX_MAPPING = new LinkedHashMap<>(); private static final Map<Integer, String> EXCEL_COLUMN_INDEX_MAPPING = new LinkedHashMap<>();
@ -176,38 +188,14 @@ public class FishImportServiceImpl implements IFishImportService {
Map<Integer, String> columnIndexMap = new HashMap<>(EXCEL_COLUMN_INDEX_MAPPING); Map<Integer, String> columnIndexMap = new HashMap<>(EXCEL_COLUMN_INDEX_MAPPING);
int totalRows = sheet.getLastRowNum();
for (int i = 1; i <= totalRows; i++) {
Row row = sheet.getRow(i);
if (row == null || isRowEmpty(row)) {
continue;
}
FishImportResult.FishImportRow importRow = parseRow(result, row, columnIndexMap, i, uploadUserId);
result.setTotalCount(result.getTotalCount() + 1);
if (importRow.getData() != null && importRow.getWarnings().isEmpty()) {
result.addSuccessRow(importRow);
result.setSuccessCount(result.getSuccessCount() + 1);
} else {
result.addFailedRow(importRow);
result.setFailedCount(result.getFailedCount() + 1);
}
}
result.setSummary(String.format("共解析%d条数据失败%d条",
result.getSuccessCount(), result.getFailedCount()));
return result;
}
private FishImportResult.FishImportRow parseRow(FishImportResult result, Row row, Map<Integer, String> columnIndexMap, int rowIndex, String userId) {
FishImportResult.FishImportRow importRow = new FishImportResult.FishImportRow(rowIndex);
FishDraftData data = new FishDraftData();
data.setId(UUID.randomUUID().toString());
Set<String> allowedHbrvcdSet = new HashSet<>(); Set<String> allowedHbrvcdSet = new HashSet<>();
Set<String> directStcdSet = new HashSet<>(); Set<String> directStcdSet = new HashSet<>();
Set<String> directBHSet = new HashSet<>();
if (userId != null) { List<SdFpssBH> sdFpssList=new ArrayList<>();
List<SysUserDataScope> permissions = userDataScopeMapper.selectValidPermissions(userId); List<SdHbrvDic> allowedHbrvcdList = new ArrayList<>();
List<SdEngInfoBH> directStcdList = new ArrayList<>();
if (uploadUserId != null) {
List<SysUserDataScope> permissions = userDataScopeMapper.selectValidPermissions(uploadUserId);
if (permissions != null && !permissions.isEmpty()) { if (permissions != null && !permissions.isEmpty()) {
for (SysUserDataScope permission : permissions) { for (SysUserDataScope permission : permissions) {
String orgType = permission.getOrgType(); String orgType = permission.getOrgType();
@ -226,7 +214,6 @@ public class FishImportServiceImpl implements IFishImportService {
} }
} }
// Set<String> allStcdSet = new HashSet<>();
if (!allowedHbrvcdSet.isEmpty() || !directStcdSet.isEmpty()) { if (!allowedHbrvcdSet.isEmpty() || !directStcdSet.isEmpty()) {
if (!allowedHbrvcdSet.isEmpty()) { if (!allowedHbrvcdSet.isEmpty()) {
@ -250,14 +237,52 @@ public class FishImportServiceImpl implements IFishImportService {
} }
} }
} }
if(!allowedHbrvcdSet.isEmpty()){
List<SdHbrvDic> sdHbrvDics = sdHbrvDicMapper.selectList(new LambdaQueryWrapper<SdHbrvDic>().in(SdHbrvDic::getHbrvcd, allowedHbrvcdSet).select(SdHbrvDic::getHbrvcd, SdHbrvDic::getHbrvnm));
allowedHbrvcdList.addAll(sdHbrvDics);
}
if (!directStcdSet.isEmpty()) { if (!directStcdSet.isEmpty()) {
List<SdFpssBH> sdFpssBHS = fpssBHMapper.selectList(new LambdaQueryWrapper<SdFpssBH>().in(SdFpssBH::getRstcd, directStcdSet).select(SdFpssBH::getStcd)); List<SdFpssBH> sdFpssBHS = fpssBHMapper.selectList(new LambdaQueryWrapper<SdFpssBH>().in(SdFpssBH::getRstcd, directStcdSet).select(SdFpssBH::getStcd, SdFpssBH::getStnm));
for (SdFpssBH sdFpssBH : sdFpssBHS) { //设施
if (sdFpssBH.getStcd() != null) { sdFpssList.addAll(sdFpssBHS.stream().filter(sdFpssBH -> sdFpssBH.getStcd() != null).toList());
directBHSet.add(sdFpssBH.getStcd());
} //电站
directStcdList.addAll(engInfoBHMapper.selectList(new LambdaQueryWrapper<SdEngInfoBH>().in(SdEngInfoBH::getStcd, directStcdSet).select(SdEngInfoBH::getStcd, SdEngInfoBH::getEnnm)));
}
int totalRows = sheet.getLastRowNum();
for (int i = 1; i <= totalRows; i++) {
Row row = sheet.getRow(i);
if (row == null || isRowEmpty(row)) {
continue;
}
FishImportResult.FishImportRow importRow = parseRow(result, row, columnIndexMap, i, uploadUserId,allowedHbrvcdList,directStcdList,sdFpssList);
result.setTotalCount(result.getTotalCount() + 1);
if (importRow.getData() != null && importRow.getWarnings().isEmpty()) {
result.addSuccessRow(importRow);
result.setSuccessCount(result.getSuccessCount() + 1);
} else {
result.addFailedRow(importRow);
result.setFailedCount(result.getFailedCount() + 1);
} }
} }
result.setSummary(String.format("共解析%d条数据失败%d条",
result.getSuccessCount(), result.getFailedCount()));
return result;
}
private FishImportResult.FishImportRow parseRow(FishImportResult result, Row row, Map<Integer, String> columnIndexMap, int rowIndex, String userId,List<SdHbrvDic> allowedHbrvcdList,List<SdEngInfoBH> directStcdList,List<SdFpssBH> sdFpssList) {
FishImportResult.FishImportRow importRow = new FishImportResult.FishImportRow(rowIndex);
FishDraftData data = new FishDraftData();
data.setId(UUID.randomUUID().toString());
for (Map.Entry<Integer, String> entry : columnIndexMap.entrySet()) { for (Map.Entry<Integer, String> entry : columnIndexMap.entrySet()) {
Integer columnIndex = entry.getKey(); Integer columnIndex = entry.getKey();
String fieldName = entry.getValue(); String fieldName = entry.getValue();
@ -271,20 +296,14 @@ public class FishImportServiceImpl implements IFishImportService {
data.setEnnm(cellValue.trim()); data.setEnnm(cellValue.trim());
data.setRstcd(cellValue); data.setRstcd(cellValue);
} else { } else {
String stcd = resolveStationCode(cellValue.trim()); String stcd = directStcdList.stream().filter(sdEngInfoBH -> sdEngInfoBH.getEnnm().equals(cellValue.trim())).map(SdEngInfoBH::getStcd).findFirst().orElse( null);
if (stcd == null) { if (StrUtil.isBlank(stcd)) {
importRow.getWarnings().add("rstcd"); importRow.getWarnings().add("rstcd");
data.setEnnm(cellValue.trim()); data.setEnnm(cellValue.trim());
data.setRstcd(cellValue.trim());
} else { } else {
if (directStcdSet.contains(stcd)) { data.setEnnm(cellValue.trim());
data.setEnnm(cellValue.trim()); data.setRstcd(stcd);
data.setRstcd(stcd);
} else {
importRow.getWarnings().add("rstcd");
data.setEnnm(cellValue.trim());
data.setRstcd(cellValue);
}
} }
} }
break; break;
@ -322,33 +341,17 @@ public class FishImportServiceImpl implements IFishImportService {
data.setHbrvcd(cellValue); data.setHbrvcd(cellValue);
data.setHbrvnm(cellValue); data.setHbrvnm(cellValue);
} else { } else {
String hbrvcdCode = resolveHbrvcdCode(cellValue.trim()); String hbrvcdCode = allowedHbrvcdList.stream().filter(sdHbrvDic -> sdHbrvDic.getHbrvnm().equals(cellValue.trim())).map(SdHbrvDic::getHbrvcd).findFirst().orElse( null);
if (hbrvcdCode == null) { if (StrUtil.isBlank(hbrvcdCode)) {
importRow.getWarnings().add("hbrvcd"); importRow.getWarnings().add("hbrvcd");
data.setHbrvcd(cellValue.trim()); data.setHbrvcd(cellValue.trim());
data.setHbrvnm(cellValue.trim()); data.setHbrvnm(cellValue.trim());
} else { } else {
if (allowedHbrvcdSet.contains(hbrvcdCode)) { data.setHbrvcd(hbrvcdCode);
data.setHbrvcd(hbrvcdCode); data.setHbrvnm(cellValue.trim());
data.setHbrvnm(cellValue.trim());
} else {
importRow.getWarnings().add("hbrvcd");
data.setHbrvcd(cellValue.trim());
data.setHbrvnm(cellValue.trim());
}
} }
} }
// if (StringUtils.hasText(cellValue)) { //
// String rvcd = resolveHbrvcdCode(cellValue.trim());
// if (rvcd == null) {
// importRow.getWarnings().add(fieldName);
// data.setRvcd(cellValue);
// } else {
// data.setHbrvcd(rvcd);
// data.setHbrvcd(rvcd);
// }
// }
break; break;
case "tm": case "tm":
if (!StringUtils.hasText(cellValue)) { if (!StringUtils.hasText(cellValue)) {
@ -379,7 +382,10 @@ public class FishImportServiceImpl implements IFishImportService {
} }
break; break;
case "fsz": case "fsz":
data.setFsz(cellValue.trim()); if (StringUtils.hasText(cellValue)) {
String parsedFsz = parseFishSizeRange(cellValue.trim());
data.setFsz(parsedFsz);
}
break; break;
case "fcnt": case "fcnt":
if (!StringUtils.hasText(cellValue)) { if (!StringUtils.hasText(cellValue)) {
@ -412,13 +418,20 @@ public class FishImportServiceImpl implements IFishImportService {
break; break;
case "strdt": case "strdt":
if (!StringUtils.hasText(cellValue)) { if (!StringUtils.hasText(cellValue)) {
importRow.getWarnings().add(fieldName); importRow.getWarnings().add("strdtStr");
data.setStrdtStr(cellValue);
} else { } else {
Date strdt = parseDate(cellValue); Date strdt = parseDate(cellValue);
if (strdt == null) { if (strdt == null) {
importRow.getWarnings().add(fieldName); importRow.getWarnings().add("strdtStr");
data.setStrdt(null);
data.setStrdtStr(cellValue.replaceAll("T", " "));
}else{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = sdf.format(strdt);
data.setStrdtStr(dateString);
data.setStrdt(strdt);
} }
data.setStrdt(strdt);
} }
break; break;
case "enddt": case "enddt":
@ -445,6 +458,9 @@ public class FishImportServiceImpl implements IFishImportService {
Map<String, String> videoFiles = result.getVideoFiles(); Map<String, String> videoFiles = result.getVideoFiles();
for (String fileName : vdpth.split(";")) { for (String fileName : vdpth.split(";")) {
if(StrUtil.isBlank(fileName)){
continue;
}
for (String entryName : videoFiles.keySet()) { for (String entryName : videoFiles.keySet()) {
if (entryName.equals(fileName) || entryName.endsWith("/" + fileName) || entryName.endsWith("\\" + fileName)) { if (entryName.equals(fileName) || entryName.endsWith("/" + fileName) || entryName.endsWith("\\" + fileName)) {
Map<String, String> objectObjectHashMap = new HashMap<>(); Map<String, String> objectObjectHashMap = new HashMap<>();
@ -452,6 +468,13 @@ public class FishImportServiceImpl implements IFishImportService {
objectObjectHashMap.put("value", fileName); objectObjectHashMap.put("value", fileName);
importRow.getVdpthList().add(objectObjectHashMap); importRow.getVdpthList().add(objectObjectHashMap);
vdpthList.add(fileName); vdpthList.add(fileName);
} else if (!com.yfd.platform.utils.FileUtil.isVideoFileName(fileName)) {
Map<String, String> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("name", fileName);
objectObjectHashMap.put("value", fileName);
importRow.getVdpthList().add(objectObjectHashMap);
vdpthList.add(fileName);
importRow.getVdpthsWarnings().add(fileName);
} }
} }
@ -467,6 +490,9 @@ public class FishImportServiceImpl implements IFishImportService {
Map<String, String> imageFiles = result.getImageFiles(); Map<String, String> imageFiles = result.getImageFiles();
for (String fileName : picpth.split(";")) { for (String fileName : picpth.split(";")) {
if(StrUtil.isBlank(fileName)){
continue;
}
for (String entryName : imageFiles.keySet()) { for (String entryName : imageFiles.keySet()) {
if (entryName.equals(fileName) || entryName.endsWith("/" + fileName) || entryName.endsWith("\\" + fileName)) { if (entryName.equals(fileName) || entryName.endsWith("/" + fileName) || entryName.endsWith("\\" + fileName)) {
Map<String, String> objectObjectHashMap = new HashMap<>(); Map<String, String> objectObjectHashMap = new HashMap<>();
@ -474,6 +500,13 @@ public class FishImportServiceImpl implements IFishImportService {
objectObjectHashMap.put("value", fileName); objectObjectHashMap.put("value", fileName);
importRow.getPicpthList().add(objectObjectHashMap); importRow.getPicpthList().add(objectObjectHashMap);
picpthList.add(fileName); picpthList.add(fileName);
} else if (!com.yfd.platform.utils.FileUtil.isImageFileName(fileName)) {
Map<String, String> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("name", fileName);
objectObjectHashMap.put("value", fileName);
importRow.getPicpthList().add(objectObjectHashMap);
picpthList.add(fileName);
importRow.getPicpthsWarnings().add(fileName);
} }
} }
@ -498,21 +531,14 @@ public class FishImportServiceImpl implements IFishImportService {
importRow.getWarnings().add("stcd"); importRow.getWarnings().add("stcd");
data.setStcd(cellValue); data.setStcd(cellValue);
} else { } else {
String stcd = resolveFpssCode(cellValue.trim()); String stcd = sdFpssList.stream().filter(sdFpssBH -> sdFpssBH.getStnm().equals(cellValue.trim())).map(SdFpssBH::getStcd).findFirst().orElse( null);
if (stcd == null) { if (StrUtil.isBlank(stcd)) {
importRow.getWarnings().add("stcd"); importRow.getWarnings().add("stcd");
data.setStcd(cellValue.trim()); data.setStcd(cellValue.trim());
data.setStnm(cellValue.trim()); data.setStnm(cellValue.trim());
} else { } else {
if (directBHSet.contains(stcd)) { data.setStnm(cellValue.trim());
data.setStnm(cellValue.trim()); data.setStcd(stcd);
data.setStcd(stcd);
} else {
importRow.getWarnings().add("stcd");
data.setStcd(cellValue.trim());
data.setStnm(cellValue.trim());
}
} }
} }
break; break;
@ -520,7 +546,8 @@ public class FishImportServiceImpl implements IFishImportService {
break; break;
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error("字段[" + fieldName + "]解析异常: " + e.getMessage());
// e.printStackTrace();
// importRow.getWarnings().add("字段[" + fieldName + "]解析异常: " + e.getMessage()); // importRow.getWarnings().add("字段[" + fieldName + "]解析异常: " + e.getMessage());
} }
} }
@ -529,34 +556,111 @@ public class FishImportServiceImpl implements IFishImportService {
return importRow; return importRow;
} }
/**
* 解析鱼类体长范围
* 从混乱的字符串中提取所有数字支持小数返回 "最小值~最大值" 格式
* 例如: "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<Double> 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) { private void validateStationFpssRelation(FishDraftData data, FishImportResult.FishImportRow importRow) {
loadStationAndBaseCache(); if (importRow.getWarnings().contains("hbrvcd")) {
if (StringUtils.hasText(data.getHbrvcd()) && StringUtils.hasText(data.getRstcd())) { if (!importRow.getWarnings().contains("rstcd")) {
if (!validateStationBelongsToBase(data.getRstcd(), data.getHbrvcd())) { importRow.getWarnings().add("rstcd");
if (!importRow.getWarnings().contains("hbrvcd")) { }
importRow.getWarnings().add("hbrvcd"); if (!importRow.getWarnings().contains("stcd")) {
} importRow.getWarnings().add("stcd");
if (!importRow.getWarnings().contains("rstcd")) {
importRow.getWarnings().add("rstcd");
}
if (!importRow.getWarnings().contains("stcd")) {
importRow.getWarnings().add("stcd");
}
} }
} }
if (StringUtils.hasText(data.getRstcd()) && StringUtils.hasText(data.getStcd())) { if (importRow.getWarnings().contains("rstcd")) {
if (!validateFpssBelongsToStation(data.getStcd(), data.getRstcd())) { if (!importRow.getWarnings().contains("stcd")) {
if (!importRow.getWarnings().contains("hbrvcd")) { importRow.getWarnings().add("stcd");
importRow.getWarnings().add("hbrvcd");
}
if (!importRow.getWarnings().contains("stcd")) {
importRow.getWarnings().add("stcd");
}
if (!importRow.getWarnings().contains("rstcd")) {
importRow.getWarnings().add("rstcd");
}
} }
} }
// loadStationAndBaseCache();
// if (StringUtils.hasText(data.getHbrvcd()) && StringUtils.hasText(data.getRstcd())) {
// if (!validateStationBelongsToBase(data.getRstcd(), data.getHbrvcd())) {
// if (!importRow.getWarnings().contains("hbrvcd")) {
// importRow.getWarnings().add("hbrvcd");
// }
// if (!importRow.getWarnings().contains("rstcd")) {
// importRow.getWarnings().add("rstcd");
// }
// if (!importRow.getWarnings().contains("stcd")) {
// importRow.getWarnings().add("stcd");
// }
// }
// }
// if (StringUtils.hasText(data.getRstcd()) && StringUtils.hasText(data.getStcd())) {
// if (!validateFpssBelongsToStation(data.getStcd(), data.getRstcd())) {
// if (!importRow.getWarnings().contains("hbrvcd")) {
// importRow.getWarnings().add("hbrvcd");
// }
// if (!importRow.getWarnings().contains("stcd")) {
// importRow.getWarnings().add("stcd");
// }
// if (!importRow.getWarnings().contains("rstcd")) {
// importRow.getWarnings().add("rstcd");
// }
// }
// }
} }
private void loadStationAndBaseCache() { private void loadStationAndBaseCache() {
@ -1119,28 +1223,103 @@ public class FishImportServiceImpl implements IFishImportService {
return true; return true;
} }
private Date parseDate(String dateStr) {
private static Date parseDate(String dateStr) {
if (!StringUtils.hasText(dateStr)) { if (!StringUtils.hasText(dateStr)) {
return null; return null;
} }
dateStr = dateStr.trim();
// 支持的日期格式列表按常用程度排序
String[] patterns = { String[] patterns = {
"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss", // 2024-01-15 14:30:00
"yyyy-MM-dd", "yyyy-MM-dd'T'HH:mm:ss", // 新增支持 2024-01-15T14:30:00 (ISO格式)
"yyyy/MM/dd", "yyyy-MM-dd HH:mm", // 2024-01-15 14:30
"yyyy.MM.dd", "yyyy-MM-dd'T'HH:mm", // 确保这一行存在且正确支持 2024-01-15T14:30
"yyyyMMdd" "yyyy-MM-dd", // 2024-01-15
"yyyy/MM/dd HH:mm:ss", // 2024/01/15 14:30:00
"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.M.d", // 支持 2024.1.1
"yyyyMMdd HHmmss", // 20240115 143000
"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) { for (String pattern : patterns) {
try { try {
SimpleDateFormat sdf = new SimpleDateFormat(pattern); SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setLenient(false); sdf.setLenient(false);
return sdf.parse(dateStr); ParsePosition pos = new ParsePosition(0);
} catch (ParseException ignored) { 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 (Exception e) {
// ignore
} }
} }
log.debug("无法解析日期: '{}'", dateStr);
return null; 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) { private Integer parseInteger(String value) {
if (!StringUtils.hasText(value)) { if (!StringUtils.hasText(value)) {
return null; return null;
@ -1247,9 +1426,40 @@ public class FishImportServiceImpl implements IFishImportService {
result.setTempDir(zipContent.tempDir); result.setTempDir(zipContent.tempDir);
result.setImageFiles(zipContent.images); result.setImageFiles(zipContent.images);
result.setVideoFiles(zipContent.videos); 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); Sheet sheet = workbook.getSheetAt(0);
result = parseSheet(sheet, result, uploadUserId); 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.setExcelFileName(zipContent.excelFileName);
result.setExcelFilePath(zipContent.excelFilePath); result.setExcelFilePath(zipContent.excelFilePath);
@ -1293,8 +1503,6 @@ public class FishImportServiceImpl implements IFishImportService {
@Override @Override
public void processAttachmentsAsync(List<FishDraftData> dataList, public void processAttachmentsAsync(List<FishDraftData> dataList,
Map<String, String> imageFiles,
Map<String, String> videoFiles,
String tempDir) { String tempDir) {
if (dataList == null || dataList.isEmpty()) { if (dataList == null || dataList.isEmpty()) {
return; return;
@ -1308,10 +1516,16 @@ public class FishImportServiceImpl implements IFishImportService {
String vdpth = data.getVdpth(); String vdpth = data.getVdpth();
String picpth = data.getPicpth(); String picpth = data.getPicpth();
if(StrUtil.isBlank(vdpth)||StrUtil.isBlank(picpth)){ if (StrUtil.isBlank(vdpth) && StrUtil.isBlank(picpth)) {
log.error("数据不完整, 忽略处理"); log.error("数据不完整, 忽略处理");
return; return;
} }
List<Map<String, String>> vdpthList = data.getVdpthList();
List<Map<String, String>> picpthList = data.getPicpthList();
Map<String, String> videoFiles = buildFileMap(vdpthList, tempDir, "videos");
Map<String, String> imageFiles = buildFileMap(picpthList, tempDir, "images");
if (StringUtils.hasText(vdpth) && videoFiles != null && !videoFiles.isEmpty()) { if (StringUtils.hasText(vdpth) && videoFiles != null && !videoFiles.isEmpty()) {
String uploadedVdpth = uploadVideoFilesAsync(vdpth, videoFiles); String uploadedVdpth = uploadVideoFilesAsync(vdpth, videoFiles);
if (uploadedVdpth != null) { if (uploadedVdpth != null) {
@ -1325,27 +1539,28 @@ public class FishImportServiceImpl implements IFishImportService {
data.setPicpth(uploadedPicpth); data.setPicpth(uploadedPicpth);
} }
} }
fishDraftDataService.updateById(data);
log.info("异步上传附件完成, dataId: {},{},{}", data.getId(), data.getPicpth(), data.getVdpth()); log.info("异步上传附件完成, dataId: {},{},{}", data.getId(), data.getPicpth(), data.getVdpth());
} catch (Exception e) { } catch (Exception e) {
log.error("异步上传附件失败, dataId: {}", data.getId(), e); log.error("异步上传附件失败, dataId: {}", data.getId(), e);
} finally { } finally {
SecurityContextHolder.clearContext(); SecurityContextHolder.clearContext();
} }
}); }, attachmentExecutor);
futures.add(future); futures.add(future);
} }
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> { .thenRun(() -> {
try { try {
// del 方法会递归删除目录及其所有内容 fishDraftDataService.updateBatchById(dataList);
log.info("批量更新附件路径完成, 共{}条", dataList.size());
} catch (Exception e) {
log.error("批量更新附件路径失败", e);
}
try {
FileUtil.del(tempDir); FileUtil.del(tempDir);
} catch (Exception e) { } catch (Exception e) {
log.error("删除临时目录失败{}", e.getMessage()); log.error("删除临时目录失败{}", e.getMessage());
} }
}) })
.exceptionally(ex -> { .exceptionally(ex -> {
log.error("等待异步任务完成时发生异常", ex); log.error("等待异步任务完成时发生异常", ex);
@ -1353,6 +1568,27 @@ public class FishImportServiceImpl implements IFishImportService {
}); });
} }
private Map<String, String> buildFileMap(List<Map<String, String>> fileList, String tempDir, String subDir) {
if (fileList == null || fileList.isEmpty()) {
return null;
}
Map<String, String> fileMap = new HashMap<>();
String baseDir = tempDir + File.separator + subDir + File.separator;
for (Map<String, String> item : fileList) {
String name = item.get("name");
String value = item.get("value");
if (name != null && value != null) {
fileMap.put(name, baseDir + value);
}
}
return fileMap.isEmpty() ? null : fileMap;
}
private String uploadVideoFilesAsync(String videoNames, Map<String, String> videoMap) { private String uploadVideoFilesAsync(String videoNames, Map<String, String> videoMap) {
String[] fileNames = videoNames.split(";"); String[] fileNames = videoNames.split(";");
List<String> attachmentIds = new ArrayList<>(); List<String> attachmentIds = new ArrayList<>();
@ -1508,4 +1744,17 @@ public class FishImportServiceImpl implements IFishImportService {
} }
return null; return null;
} }
@jakarta.annotation.PreDestroy
public void shutdown() {
attachmentExecutor.shutdown();
try {
if (!attachmentExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
attachmentExecutor.shutdownNow();
}
} catch (InterruptedException e) {
attachmentExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
} }

View File

@ -0,0 +1,70 @@
package com.yfd.platform.data.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.platform.common.DataSourceLoadOptionsBase;
import com.yfd.platform.common.DataSourceRequest;
import com.yfd.platform.data.domain.vo.FishStatisticsVO;
import com.yfd.platform.data.mapper.FishStatisticsMapper;
import com.yfd.platform.data.service.IFishStatisticsService;
import com.yfd.platform.utils.KendoUtil;
import com.yfd.platform.utils.QgcQueryWrapperUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
@Slf4j
public class FishStatisticsServiceImpl implements IFishStatisticsService {
@Resource
private FishStatisticsMapper fishStatisticsMapper;
@Override
public Page<FishStatisticsVO> queryPage(DataSourceRequest dataSourceRequest) {
DataSourceLoadOptionsBase loadOptions = dataSourceRequest.toDevRequest();
String basinNamesStr = QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "basinCode");
String stationNamesStr = QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "stationCode");
// List<String> basinNames = parseFilterList(basinNamesStr);
// List<String> stationNames = parseFilterList(stationNamesStr);
int currentPage = dataSourceRequest.getSkip();
int pageSize = dataSourceRequest.getTake();
if (currentPage <= 0) {
currentPage = 1;
}
if (pageSize <= 0) {
pageSize = 20;
}
int startRow = (currentPage - 1) * pageSize;
int endRow = startRow + pageSize;
List<FishStatisticsVO> records = fishStatisticsMapper.queryStatistics(
basinNamesStr, stationNamesStr, startRow, endRow);
int total = fishStatisticsMapper.countStatistics(basinNamesStr, basinNamesStr);
Page<FishStatisticsVO> page = new Page<>();
page.setRecords(records);
page.setTotal(total);
page.setSize(pageSize);
page.setCurrent(currentPage);
return page;
}
private List<String> parseFilterList(String filterValue) {
if (StrUtil.isBlank(filterValue)) {
return new ArrayList<>();
}
return Arrays.asList(filterValue.split(","));
}
}

View File

@ -3,35 +3,45 @@ package com.yfd.platform.data.service.impl;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.yfd.platform.data.domain.FishDraftData;
import com.yfd.platform.data.domain.FishImportResult; import com.yfd.platform.data.domain.FishImportResult;
import com.yfd.platform.data.domain.ImportTask; import com.yfd.platform.data.domain.ImportTask;
import com.yfd.platform.data.domain.ImportTaskRow;
import com.yfd.platform.data.mapper.ImportTaskMapper; import com.yfd.platform.data.mapper.ImportTaskMapper;
import com.yfd.platform.data.mapper.ImportTaskRowMapper;
import com.yfd.platform.data.service.AttachmentUploadService; import com.yfd.platform.data.service.AttachmentUploadService;
import com.yfd.platform.data.service.IImportTaskService; import com.yfd.platform.data.service.IImportTaskService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.Date; import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/** /**
* <p> * <p>
* 导入任务表 服务实现类 * 导入任务表 服务实现类
* </p> * </p>
*/ */
@Slf4j
@Service @Service
public class ImportTaskServiceImpl extends ServiceImpl<ImportTaskMapper, ImportTask> implements IImportTaskService { public class ImportTaskServiceImpl extends ServiceImpl<ImportTaskMapper, ImportTask> implements IImportTaskService {
@Resource @Resource
private ImportTaskMapper importTaskMapper; private ImportTaskMapper importTaskMapper;
@Resource
private ImportTaskRowMapper importTaskRowMapper;
@Resource @Resource
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
@Resource @Resource
@ -133,14 +143,10 @@ public class ImportTaskServiceImpl extends ServiceImpl<ImportTaskMapper, ImportT
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean markSuccess(String id) { public boolean markSuccess(String id) {
ImportTask importTask = this.getById(id); return this.update(new LambdaUpdateWrapper<ImportTask>()
if (importTask == null) { .eq(ImportTask::getId, id)
return false; .set(ImportTask::getStatus, "CONFIRMED")
} .set(ImportTask::getUpdatedAt, new Date())); // 如果没有配置自动填充建议保留此行
importTask.setStatus("CONFIRMED");
importTask.setUpdatedAt(new Date());
boolean b = this.updateById(importTask);
return b;
} }
@Override @Override
@ -151,6 +157,9 @@ public class ImportTaskServiceImpl extends ServiceImpl<ImportTaskMapper, ImportT
return true; return true;
} }
List<String> ids = expiredTasks.stream().map(ImportTask::getId).toList(); List<String> ids = expiredTasks.stream().map(ImportTask::getId).toList();
for (String id : ids) {
importTaskRowMapper.deleteByTaskId(id);
}
return this.removeByIds(ids); return this.removeByIds(ids);
} }
@ -164,34 +173,38 @@ public class ImportTaskServiceImpl extends ServiceImpl<ImportTaskMapper, ImportT
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean cancelTask(String taskId, String operatorId) { public boolean cancelTask(String taskId, String operatorId) {
ImportTask importTask = this.getById(taskId); ImportTask importTask = this.getOne(new LambdaQueryWrapper<ImportTask>().eq(ImportTask::getId, taskId).select(ImportTask::getId,ImportTask::getStatus, ImportTask::getTempDir));
if (importTask == null) { if (importTask == null) {
return false; return false;
} }
String currentStatus = importTask.getStatus(); String currentStatus = importTask.getStatus();
if ("CONFIRMED".equals(currentStatus) || "FAILED".equals(currentStatus) || "CANCELLED".equals(currentStatus)) { // if ("CONFIRMED".equals(currentStatus) || "FAILED".equals(currentStatus) || "CANCELLED".equals(currentStatus)) {
// return false;
// }
if ("CONFIRMED".equals(currentStatus) || "CANCELLED".equals(currentStatus)) {
return false; return false;
} }
String temp = importTask.getTempDir(); String temp = importTask.getTempDir();
if (StrUtil.isNotBlank( temp)) {
FileUtil.del(temp);
}else{
if (importTask.getResultJson() != null && !importTask.getResultJson().isEmpty()) {
try {
FishImportResult importResult = objectMapper.readValue(importTask.getResultJson(), FishImportResult.class);
String tempDir = importResult.getTempDir();
// del 方法会递归删除目录及其所有内容
FileUtil.del(tempDir);
} catch (Exception e) {
e.printStackTrace();
// ignore parse error
}
}
}
importTask.setStatus("CANCELLED"); importTask.setStatus("CANCELLED");
importTask.setErrorMsg("用户取消: " + operatorId); importTask.setErrorMsg("用户取消: " + operatorId);
importTask.setUpdatedAt(new Date()); importTask.setUpdatedAt(new Date());
return this.updateById(importTask); importTaskRowMapper.deleteByTaskId(taskId);
boolean result = this.updateById(importTask);
if (result && StrUtil.isNotBlank(temp)) {
CompletableFuture.runAsync(() -> {
try {
FileUtil.del(temp);
log.info("异步删除临时目录成功: {}", temp);
} catch (Exception e) {
log.error("异步删除临时目录失败: {}", temp, e);
}
});
}
return result;
} }
@Override @Override
@ -206,14 +219,103 @@ public class ImportTaskServiceImpl extends ServiceImpl<ImportTaskMapper, ImportT
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean saveResultJson(String taskId, String resultJson) { public boolean saveResultJson(String taskId, FishImportResult importResult) {
ImportTask importTask = this.getById(taskId); ImportTask importTask = this.getById(taskId);
if (importTask == null) { if (importTask == null) {
return false; return false;
} }
importTask.setResultJson(resultJson);
// importTask.setResultJson(resultJson);
importTask.setUpdatedAt(new Date()); importTask.setUpdatedAt(new Date());
return this.updateById(importTask);
try {
importTask.setSummary(importResult.getSummary());
importTask.setCode(importResult.getCode());
importTask.setMessage(importResult.getMessage());
if (importResult.getUnrecognizedFields() != null && !importResult.getUnrecognizedFields().isEmpty()) {
importTask.setUnrecognizedFields(String.join(",", importResult.getUnrecognizedFields()));
}
// if (importResult.getImageFiles() != null && !importResult.getImageFiles().isEmpty()) {
// importTask.setImageFilesJson(objectMapper.writeValueAsString(importResult.getImageFiles()));
// }
// if (importResult.getVideoFiles() != null && !importResult.getVideoFiles().isEmpty()) {
// importTask.setVideoFilesJson(objectMapper.writeValueAsString(importResult.getVideoFiles()));
// }
this.updateById(importTask);
importTaskRowMapper.deleteByTaskId(taskId);
if (importResult.getRows() != null && !importResult.getRows().isEmpty()) {
List<ImportTaskRow> rows = new ArrayList<>();
for (FishImportResult.FishImportRow row : importResult.getRows()) {
ImportTaskRow taskRow = new ImportTaskRow();
taskRow.setTaskId(taskId);
taskRow.setRowIndex(row.getRowIndex());
taskRow.setStatus(row.getStatus());
FishDraftData data = row.getData();
if (data != null) {
taskRow.setDataId(data.getId());
taskRow.setStcd(data.getStcd());
taskRow.setStnm(data.getStnm());
taskRow.setRstcd(data.getRstcd());
taskRow.setEnnm(data.getEnnm());
taskRow.setHbrvcd(data.getHbrvcd());
taskRow.setHbrvnm(data.getHbrvnm());
taskRow.setRvcd(data.getRvcd());
taskRow.setBaseId(data.getBaseId());
taskRow.setBaseName(data.getBaseName());
taskRow.setStrdt(data.getStrdt());
taskRow.setStrdtStr(data.getStrdtStr());
taskRow.setEnddt(data.getEnddt());
taskRow.setFtp(data.getFtp());
taskRow.setFtpName(data.getFtpName());
taskRow.setIsfs(data.getIsfs());
taskRow.setDirection(data.getDirection());
taskRow.setFcnt(data.getFcnt());
taskRow.setFsz(data.getFsz());
taskRow.setFwet(data.getFwet());
taskRow.setWt(data.getWt());
taskRow.setPicpth(data.getPicpth());
taskRow.setVdpth(data.getVdpth());
taskRow.setTm(data.getTm());
taskRow.setSourceType(data.getSourceType());
taskRow.setMouth(data.getMouth());
taskRow.setYr(data.getYr());
}
if (row.getWarnings() != null && !row.getWarnings().isEmpty()) {
taskRow.setWarnings(String.join(",", row.getWarnings()));
}
if (row.getUnrecognizedFields() != null && !row.getUnrecognizedFields().isEmpty()) {
taskRow.setUnrecognizedFields(String.join(",", row.getUnrecognizedFields()));
}
if (row.getVdpthList() != null && !row.getVdpthList().isEmpty()) {
taskRow.setVdpthListJson(objectMapper.writeValueAsString(row.getVdpthList()));
}
if (row.getPicpthList() != null && !row.getPicpthList().isEmpty()) {
taskRow.setPicpthListJson(objectMapper.writeValueAsString(row.getPicpthList()));
}
if (row.getVdpthsWarnings() != null && !row.getVdpthsWarnings().isEmpty()) {
taskRow.setVdpthWarnings(String.join(",", row.getVdpthsWarnings()));
}
if (row.getPicpthsWarnings() != null && !row.getPicpthsWarnings().isEmpty()) {
taskRow.setPicpthWarnings(String.join(",", row.getPicpthsWarnings()));
}
taskRow.setCreatedAt(new Date());
rows.add(taskRow);
}
importTaskRowMapper.insert(rows);
}
} catch (Exception e) {
log.error("保存导入结果行数据失败, taskId: {}", taskId, e);
importTask.setErrorMsg("保存行数据失败: " + e.getMessage());
this.updateById(importTask);
return false;
}
return true;
} }
@Override @Override
@ -221,4 +323,140 @@ public class ImportTaskServiceImpl extends ServiceImpl<ImportTaskMapper, ImportT
ImportTask importTask = importTaskMapper.selectLastByUserId(uploadUserId); ImportTask importTask = importTaskMapper.selectLastByUserId(uploadUserId);
return importTask; return importTask;
} }
@Override
public List<ImportTaskRow> getRowsByTaskId(String taskId) {
return importTaskRowMapper.selectByTaskId(taskId);
}
@Override
public List<ImportTaskRow> getRowsByTaskIdAndStatus(String taskId, String status) {
return importTaskRowMapper.selectByTaskIdAndStatus(taskId, status);
}
@Override
public FishImportResult buildImportResult(String taskId) {
ImportTask importTask = this.getById(taskId);
if (importTask == null) {
return null;
}
FishImportResult result = new FishImportResult();
result.setTaskId(taskId);
result.setTempDir(importTask.getTempDir());
result.setSummary(importTask.getSummary());
result.setCode(importTask.getCode());
result.setMessage(importTask.getMessage());
result.setTotalCount(importTask.getTotalCount() != null ? importTask.getTotalCount() : 0);
result.setSuccessCount(importTask.getSuccessCount() != null ? importTask.getSuccessCount() : 0);
result.setFailedCount(importTask.getFailCount() != null ? importTask.getFailCount() : 0);
if (importTask.getUnrecognizedFields() != null && !importTask.getUnrecognizedFields().isEmpty()) {
result.setUnrecognizedFields(Arrays.asList(importTask.getUnrecognizedFields().split(",")));
}
List<ImportTaskRow> rows = importTaskRowMapper.selectByTaskId(taskId);
if (rows != null && !rows.isEmpty()) {
for (ImportTaskRow row : rows) {
FishImportResult.FishImportRow importRow = new FishImportResult.FishImportRow(row.getRowIndex());
importRow.setStatus(row.getStatus());
FishDraftData data = new FishDraftData();
data.setId(row.getDataId());
data.setStcd(row.getStcd());
data.setStnm(row.getStnm());
data.setRstcd(row.getRstcd());
data.setEnnm(row.getEnnm());
data.setHbrvcd(row.getHbrvcd());
data.setHbrvnm(row.getHbrvnm());
data.setRvcd(row.getRvcd());
data.setBaseId(row.getBaseId());
data.setBaseName(row.getBaseName());
data.setStrdt(row.getStrdt());
data.setStrdtStr(row.getStrdtStr());
data.setEnddt(row.getEnddt());
data.setFtp(row.getFtp());
data.setFtpName(row.getFtpName());
data.setIsfs(row.getIsfs());
data.setDirection(row.getDirection());
data.setFcnt(row.getFcnt());
data.setFsz(row.getFsz());
data.setFwet(row.getFwet());
data.setWt(row.getWt());
data.setPicpth(row.getPicpth());
data.setVdpth(row.getVdpth());
data.setTm(row.getTm());
data.setSourceType(row.getSourceType());
data.setMouth(row.getMouth());
data.setYr(row.getYr());
importRow.setData(data);
if (row.getWarnings() != null && !row.getWarnings().isEmpty()) {
importRow.setWarnings(Arrays.asList(row.getWarnings().split(",")));
}
if (row.getUnrecognizedFields() != null && !row.getUnrecognizedFields().isEmpty()) {
importRow.setUnrecognizedFields(Arrays.asList(row.getUnrecognizedFields().split(",")));
}
if (row.getVdpthListJson() != null && !row.getVdpthListJson().isEmpty()) {
try {
List<Map<String, String>> vdpthList = objectMapper.readValue(
row.getVdpthListJson(), new TypeReference<>() {
});
importRow.setVdpthList(vdpthList);
} catch (Exception e) {
log.error("解析视频列表JSON失败, rowId: {}", row.getId(), e);
}
}
if (row.getPicpthListJson() != null && !row.getPicpthListJson().isEmpty()) {
try {
List<Map<String, String>> picpthList = objectMapper.readValue(
row.getPicpthListJson(), new TypeReference<List<Map<String, String>>>() {});
importRow.setPicpthList(picpthList);
} catch (Exception e) {
log.error("解析图片列表JSON失败, rowId: {}", row.getId(), e);
}
}
if (row.getVdpthWarnings() != null && !row.getVdpthWarnings().isEmpty()) {
importRow.setVdpthsWarnings(Arrays.asList(row.getVdpthWarnings().split(",")));
}
if (row.getPicpthWarnings() != null && !row.getPicpthWarnings().isEmpty()) {
importRow.setPicpthsWarnings(Arrays.asList(row.getPicpthWarnings().split(",")));
}
if ("SUCCESS".equals(row.getStatus())) {
result.addSuccessRow(importRow);
} else {
result.addFailedRow(importRow);
}
}
Map<String, String> imageFiles = new LinkedHashMap<>();
Map<String, String> videoFiles = new LinkedHashMap<>();
for (FishImportResult.FishImportRow row : result.getRows()) {
if (row.getPicpthList() != null) {
for (Map<String, String> pic : row.getPicpthList()) {
String name = pic.get("name");
String path = pic.get("path");
if (name != null && path != null) {
imageFiles.putIfAbsent(name, path);
}
}
}
if (row.getVdpthList() != null) {
for (Map<String, String> vid : row.getVdpthList()) {
String name = vid.get("name");
String path = vid.get("path");
if (name != null && path != null) {
videoFiles.putIfAbsent(name, path);
}
}
}
}
result.setImageFiles(imageFiles);
result.setVideoFiles(videoFiles);
}
return result;
}
} }

View File

@ -1,5 +1,7 @@
package com.yfd.platform.data.utils; package com.yfd.platform.data.utils;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -364,22 +366,99 @@ public class ZipFileUtil {
} }
private static String saveFileToDir(InputStream is, File tempDir, String subFolder, String fileName) throws IOException { private static String saveFileToDir(InputStream is, File tempDir, String subFolder, String fileName) throws IOException {
// 使用 Hutool 构建文件路径
String safeFileName = sanitizeFileName(fileName);
File folder = new File(tempDir, subFolder); File folder = new File(tempDir, subFolder);
if (!folder.exists()) {
folder.mkdirs(); // 使用 Hutool 创建目录会自动创建父目录
FileUtil.mkdir(folder);
log.info("保存文件: fileName{} -> safeFileName{}->subFolder{}", fileName, safeFileName,subFolder);
// 构建完整文件路径
File file = new File(folder, safeFileName);
// 安全检查防止目录穿越
String canonicalPath = file.getCanonicalPath();
String canonicalDir = folder.getCanonicalPath();
if (!canonicalPath.startsWith(canonicalDir)) {
throw new IOException("非法的文件路径: " + fileName);
} }
File file = new File(folder, fileName); // 使用 Hutool 从流复制到文件
try (FileOutputStream fos = new FileOutputStream(file)) { try {
byte[] buffer = new byte[4096]; FileUtil.writeFromStream(is, file);
int len; log.debug("保存文件: {} -> {}, 大小: {} bytes", fileName, safeFileName, file.length());
while ((len = is.read(buffer)) > 0) { } catch (Exception e) {
fos.write(buffer, 0, len); throw new IOException("保存文件失败: " + fileName, e);
}
} }
return file.getAbsolutePath(); return file.getAbsolutePath();
} }
/**
* 清理和标准化文件名确保在 Linux 上正确显示中文
*/
private static String sanitizeFileName(String fileName) {
if (StrUtil.isBlank(fileName)) {
return "unnamed_" + System.currentTimeMillis();
}
// 使用 Hutool 去除首尾空格
String sanitized = StrUtil.trim(fileName);
// 替换非法字符Linux/Windows 都不允许的字符
sanitized = sanitized.replaceAll("[\\\\/:*?\"<>|]", "_");
// 去除连续的下划线
sanitized = sanitized.replaceAll("_+", "_");
// 如果文件名过长截断Linux 文件名最大 255 字节
byte[] bytes = sanitized.getBytes(StandardCharsets.UTF_8);
if (bytes.length > 200) {
String extension = "";
int dotIndex = sanitized.lastIndexOf('.');
if (dotIndex > 0 && dotIndex < sanitized.length() - 1) {
extension = sanitized.substring(dotIndex);
sanitized = sanitized.substring(0, dotIndex);
}
// 重新计算长度
bytes = sanitized.getBytes(StandardCharsets.UTF_8);
int maxNameLength = 200 - extension.getBytes(StandardCharsets.UTF_8).length;
if (bytes.length > maxNameLength) {
// 安全截断避免切断多字节字符
sanitized = StrUtil.subPre(sanitized, maxNameLength);
}
sanitized = sanitized + extension;
}
// 确保文件名不为空
if (StrUtil.isBlank(sanitized) || ".".equals(sanitized)) {
return "file_" + System.currentTimeMillis();
}
return sanitized;
}
// private static String saveFileToDir(InputStream is, File tempDir, String subFolder, String fileName) throws IOException {
// File folder = new File(tempDir, subFolder);
// if (!folder.exists()) {
// folder.mkdirs();
// }
//
// File file = new File(folder, fileName);
// try (FileOutputStream fos = new FileOutputStream(file)) {
// byte[] buffer = new byte[4096];
// int len;
// while ((len = is.read(buffer)) > 0) {
// fos.write(buffer, 0, len);
// }
// }
// return file.getAbsolutePath();
// }
private static boolean isImageFile(String fileName) { private static boolean isImageFile(String fileName) {
return fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") || return fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") ||
fileName.endsWith(".png") || fileName.endsWith(".gif") || fileName.endsWith(".png") || fileName.endsWith(".gif") ||

View File

@ -0,0 +1,11 @@
package com.yfd.platform.env.domain;
import lombok.Data;
@Data
public class StationBasinInfo {
private String stcd; // 电站编码
private String ennm; // 电站名称
private String hbrvcd; // 流域编码
private String hbrvnm; // 流域名称
}

View File

@ -2,6 +2,7 @@ package com.yfd.platform.env.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yfd.platform.env.domain.SdEngInfoBH; import com.yfd.platform.env.domain.SdEngInfoBH;
import com.yfd.platform.env.domain.StationBasinInfo;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
@ -37,4 +38,7 @@ public interface SdEngInfoBHMapper extends BaseMapper<SdEngInfoBH> {
* 根据基地流域编码列表批量查询电站 * 根据基地流域编码列表批量查询电站
*/ */
List<SdEngInfoBH> selectByHbrvcdList(@Param("hbrvcdList") List<String> hbrvcdList); List<SdEngInfoBH> selectByHbrvcdList(@Param("hbrvcdList") List<String> hbrvcdList);
List<StationBasinInfo> selectStationBasinInfo();
} }

View File

@ -83,16 +83,19 @@ public class SdEngInfoBHServiceImpl extends ServiceImpl<SdEngInfoBHMapper, SdEng
.select(SdEngInfoBH::getStcd, SdEngInfoBH::getEnnm, SdEngInfoBH::getBaseId) .select(SdEngInfoBH::getStcd, SdEngInfoBH::getEnnm, SdEngInfoBH::getBaseId)
.orderByAsc(SdEngInfoBH::getOrderIndex); .orderByAsc(SdEngInfoBH::getOrderIndex);
if("admin".equals(SecurityUtils.getCurrentUsername())){
return this.list(wrapper);
}
Set<String> authorizedStations = getUserAuthorizedStationCodes(); Set<String> authorizedStations = getUserAuthorizedStationCodes();
if (authorizedStations != null && !authorizedStations.isEmpty()) { if (authorizedStations != null && !authorizedStations.isEmpty()) {
wrapper.in(SdEngInfoBH::getStcd, authorizedStations); List<SdEngInfoBH> list = this.list(wrapper);
} else if (!"admin".equals(SecurityUtils.getCurrentUsername())){ return list.stream()
.filter(item -> authorizedStations.contains(item.getStcd()))
.collect(Collectors.toList());
}else{
return new ArrayList<>(); return new ArrayList<>();
} }
return this.list(wrapper);
} }
@Override @Override
public Set<String> getUserAuthorizedStationCodes() { public Set<String> getUserAuthorizedStationCodes() {
String userId = SecurityUtils.getUserId(); String userId = SecurityUtils.getUserId();

View File

@ -67,28 +67,42 @@ public class SdFpssBHServiceImpl extends ServiceImpl<SdFpssBHMapper, SdFpssBH> i
@Override @Override
public List<SdFpssBH> selectForDropdown(String rstcd, String stnm, String baseId) { public List<SdFpssBH> selectForDropdown(String rstcd, String stnm, String baseId) {
// 管理员直接查询无需权限过滤
if ("admin".equals(SecurityUtils.getCurrentUsername())) {
return queryFpssList(rstcd, stnm, baseId);
}
// 获取用户有权限的工程编码
Set<String> authorizedStations = getUserAuthorizedStationCodes(); Set<String> authorizedStations = getUserAuthorizedStationCodes();
List<SdFpssBH> result; // 无权限直接返回空列表
if (authorizedStations == null || authorizedStations.isEmpty()) {
if (StringUtils.hasText(baseId)) { return new ArrayList<>();
result = baseMapper.selectForDropdownWithBaseId(rstcd, stnm, baseId);
} else {
LambdaQueryWrapper<SdFpssBH> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.hasText(rstcd), SdFpssBH::getRstcd, rstcd)
.like(StringUtils.hasText(stnm), SdFpssBH::getStnm, stnm)
.orderByDesc(SdFpssBH::getOrderIndex);
result = list(wrapper);
}
if (authorizedStations.isEmpty()&&"admin".equals(SecurityUtils.getCurrentUsername())) {
return result;
} }
// 查询数据
List<SdFpssBH> result = queryFpssList(rstcd, stnm, baseId);
// 权限过滤
return result.stream() return result.stream()
.filter(fpss -> authorizedStations.contains(fpss.getRstcd())) .filter(fpss -> fpss.getRstcd() != null && authorizedStations.contains(fpss.getRstcd()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* 查询过鱼设施列表公共方法
*/
private List<SdFpssBH> queryFpssList(String rstcd, String stnm, String baseId) {
if (StringUtils.hasText(baseId)) {
return baseMapper.selectForDropdownWithBaseId(rstcd, stnm, baseId);
} else {
LambdaQueryWrapper<SdFpssBH> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.hasText(rstcd), SdFpssBH::getRstcd, rstcd)
.like(StringUtils.hasText(stnm), SdFpssBH::getStnm, stnm)
.orderByDesc(SdFpssBH::getOrderIndex);
return list(wrapper);
}
}
@Override @Override
public Set<String> getUserAuthorizedStationCodes() { public Set<String> getUserAuthorizedStationCodes() {
String userId = SecurityUtils.getUserId(); String userId = SecurityUtils.getUserId();

View File

@ -92,8 +92,16 @@ public class SdHbrvDicServiceImpl extends ServiceImpl<SdHbrvDicMapper, SdHbrvDic
@Override @Override
public List<SdHbrvDic> selectForDropdown(String hbrvnm, String baseid) { public List<SdHbrvDic> selectForDropdown(String hbrvnm, String baseid) {
Set<String> authorizedStations = getUserAuthorizedStationCodes();
LambdaQueryWrapper<SdHbrvDic> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<SdHbrvDic> wrapper = new LambdaQueryWrapper<>();
wrapper.like(hbrvnm != null && !hbrvnm.isEmpty(), SdHbrvDic::getHbrvnm, hbrvnm)
.eq(baseid != null && !baseid.isEmpty(), SdHbrvDic::getBaseid, baseid)
.eq(SdHbrvDic::getEnabled, 1)
.orderByAsc(SdHbrvDic::getOrderIndex);
if("admin".equals(SecurityUtils.getCurrentUsername())){
return this.list(wrapper);
}
Set<String> authorizedStations = getUserAuthorizedStationCodes();
if (authorizedStations != null && !authorizedStations.isEmpty()) { if (authorizedStations != null && !authorizedStations.isEmpty()) {
List<SdEngInfoBH> engInfos = engInfoBHMapper.selectList( List<SdEngInfoBH> engInfos = engInfoBHMapper.selectList(
new LambdaQueryWrapper<SdEngInfoBH>() new LambdaQueryWrapper<SdEngInfoBH>()
@ -104,21 +112,19 @@ public class SdHbrvDicServiceImpl extends ServiceImpl<SdHbrvDicMapper, SdHbrvDic
.map(SdEngInfoBH::getHbrvcd) .map(SdEngInfoBH::getHbrvcd)
.filter(id -> id != null && !id.isEmpty()) .filter(id -> id != null && !id.isEmpty())
.distinct() .distinct()
.collect(Collectors.toList()); .toList();
if (!hbrvcds.isEmpty()) { if (!hbrvcds.isEmpty()) {
wrapper.in(SdHbrvDic::getHbrvcd, hbrvcds); List<SdHbrvDic> list = this.list(wrapper);
return list.stream()
.filter(hbrvDic -> hbrvcds.contains(hbrvDic.getHbrvcd()))
.collect(Collectors.toList());
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }
}else if (!"admin".equals(SecurityUtils.getCurrentUsername())){ }else {
return new ArrayList<>(); return new ArrayList<>();
} }
wrapper.like(hbrvnm != null && !hbrvnm.isEmpty(), SdHbrvDic::getHbrvnm, hbrvnm)
.eq(baseid != null && !baseid.isEmpty(), SdHbrvDic::getBaseid, baseid)
.eq(SdHbrvDic::getEnabled, 1)
.orderByAsc(SdHbrvDic::getOrderIndex);
return this.list(wrapper);
} }
@Override @Override

View File

@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -41,6 +42,7 @@ import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping("/sms") @RequestMapping("/sms")
@Tag(name = "短信验证码管理") @Tag(name = "短信验证码管理")
@Slf4j
public class SmsVerifyCodeController { public class SmsVerifyCodeController {
@Resource @Resource
@ -69,6 +71,7 @@ public class SmsVerifyCodeController {
@Value("${rsa.private_key}") @Value("${rsa.private_key}")
private String privateKey; private String privateKey;
private static final String URGE_CONTENT ="根据生态环境部要求,请贵电站尽快完成过鱼数据的报送工作,感谢支持。";
/** /**
* 发送验证码 * 发送验证码
@ -124,6 +127,53 @@ public class SmsVerifyCodeController {
} }
/**
* 批量发送短信
*/
@PostMapping("/batchSendContent")
@Operation(summary = "批量发送短信")
public ResponseResult batchSendContent(@RequestBody SmsVerifyCodeRequest smsVerifyCodeRequest) {
List<String> phoneList = smsVerifyCodeRequest.getPhoneList();
if(phoneList==null){
return ResponseResult.error("手机号不能为空");
}
smsVerifyCodeService.batchSendContent(phoneList, smsVerifyCodeRequest.getContent());
return ResponseResult.success();
}
/**
* 批量催促
*/
@PostMapping("/batchUrgeContent")
@Operation(summary = "催促")
public ResponseResult batchUrgeContent(@RequestBody SmsVerifyCodeRequest smsVerifyCodeRequest) {
List<String> userIds = smsVerifyCodeRequest.getUserIds();
String content = smsVerifyCodeRequest.getContent();
if ((userIds == null || userIds.isEmpty())) {
return ResponseResult.error("用户ID不能为空");
}
if (content == null || content.isEmpty()) {
content = URGE_CONTENT;
}
try {
int successCount = smsVerifyCodeService.urgeByUserIds(userIds, content);
Map<String, Object> result = new HashMap<>();
// result.put("successCount", successCount);
result.put("totalCount", userIds.size());
return ResponseResult.successData(result);
} catch (Exception e) {
log.error("批量催促短信发送失败", e);
return ResponseResult.error("短信发送失败: " + e.getMessage());
}
}
/** /**
* 注册用户 * 注册用户
*/ */
@ -204,7 +254,7 @@ public class SmsVerifyCodeController {
selectedBasinCodes.addAll(Arrays.asList(hbrvcdCode.split(","))); selectedBasinCodes.addAll(Arrays.asList(hbrvcdCode.split(",")));
} }
Set<String> addedStationCodes = new HashSet<>(); // Set<String> addedStationCodes = new HashSet<>();
for (String basinCode : selectedBasinCodes) { for (String basinCode : selectedBasinCodes) {
if (StringUtils.isEmpty(basinCode)) { if (StringUtils.isEmpty(basinCode)) {
@ -241,7 +291,7 @@ public class SmsVerifyCodeController {
scope.setStatus(1); scope.setStatus(1);
scope.setPermissionType("READ"); scope.setPermissionType("READ");
sysUserDataScopeService.addDataScope(scope); sysUserDataScopeService.addDataScope(scope);
addedStationCodes.add(basinCode); // addedStationCodes.add(basinCode);
} else { } else {
Set<String> stationsInBasinAndSelected = allStationCodesInBasin.stream() Set<String> stationsInBasinAndSelected = allStationCodesInBasin.stream()
.filter(selectedStationCodes::contains) .filter(selectedStationCodes::contains)
@ -255,27 +305,27 @@ public class SmsVerifyCodeController {
scope.setStatus(1); scope.setStatus(1);
scope.setPermissionType("READ"); scope.setPermissionType("READ");
sysUserDataScopeService.addDataScope(scope); sysUserDataScopeService.addDataScope(scope);
addedStationCodes.add(stationCd); // addedStationCodes.add(stationCd);
} }
} }
} }
Set<String> standaloneStations = selectedStationCodes.stream() // Set<String> standaloneStations = selectedStationCodes.stream()
.filter(code -> !addedStationCodes.contains(code)) // .filter(code -> !addedStationCodes.contains(code))
.collect(Collectors.toSet()); // .collect(Collectors.toSet());
//
for (String stationCd : standaloneStations) { // for (String stationCd : standaloneStations) {
if (StringUtils.isEmpty(stationCd)) { // if (StringUtils.isEmpty(stationCd)) {
continue; // continue;
} // }
SysUserDataScope scope = new SysUserDataScope(); // SysUserDataScope scope = new SysUserDataScope();
scope.setUserId(userId); // scope.setUserId(userId);
scope.setOrgType("STATION"); // scope.setOrgType("STATION");
scope.setOrgId(stationCd); // scope.setOrgId(stationCd);
scope.setStatus(1); // scope.setStatus(1);
scope.setPermissionType("READ"); // scope.setPermissionType("READ");
sysUserDataScopeService.addDataScope(scope); // sysUserDataScopeService.addDataScope(scope);
} // }
SysUser user = new SysUser(); SysUser user = new SysUser();
user.setId(userId); user.setId(userId);
userService.updateUserRoles( user,"c13481a486c9ee559cf305284df4d207"); userService.updateUserRoles( user,"c13481a486c9ee559cf305284df4d207");

View File

@ -3,6 +3,8 @@ package com.yfd.platform.system.domain;
import lombok.Data; import lombok.Data;
import java.util.List;
@Data @Data
public class SmsVerifyCodeRequest { public class SmsVerifyCodeRequest {
@ -61,5 +63,14 @@ public class SmsVerifyCodeRequest {
*/ */
private String stationCode; private String stationCode;
private List<String> phoneList;
private String content;
/**
* 用户编号
*/
private List<String> userIds;
} }

View File

@ -163,4 +163,10 @@ public class SysUser implements Serializable {
@TableField(exist = false) @TableField(exist = false)
List<SysRole> roles; List<SysRole> roles;
@TableField(exist = false)
private String basinNames;
@TableField(exist = false)
private String stationNames;
} }

View File

@ -63,6 +63,12 @@ public interface SysRoleMapper extends BaseMapper<SysRole> {
***********************************/ ***********************************/
List<SysRole> getRoleByUserId(String id); List<SysRole> getRoleByUserId(String id);
/**
* 批量获取用户角色含userId映射
*/
List<Map<String, Object>> getUserRolesByUserIds(@Param("userIds") List<String> userIds);
/********************************** /**********************************
* 用途说明: 根据角色ID删除菜单与角色关联信息 * 用途说明: 根据角色ID删除菜单与角色关联信息
* 参数说明 id 角色id * 参数说明 id 角色id

View File

@ -3,6 +3,8 @@ package com.yfd.platform.system.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.yfd.platform.system.domain.SmsVerifyCode; import com.yfd.platform.system.domain.SmsVerifyCode;
import java.util.List;
/** /**
* <p> * <p>
* 短信验证码表 服务类 * 短信验证码表 服务类
@ -46,4 +48,7 @@ public interface ISmsVerifyCodeService extends IService<SmsVerifyCode> {
* @return 是否发送成功 * @return 是否发送成功
*/ */
boolean sendAuditNotify(String phone, String auditStatus, String reason); boolean sendAuditNotify(String phone, String auditStatus, String reason);
void batchSendContent(List<String> phoneList, String content);
int urgeByUserIds(List<String> userIds, String content);
} }

View File

@ -1,17 +1,24 @@
package com.yfd.platform.system.service.impl; package com.yfd.platform.system.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yfd.platform.system.domain.SmsVerifyCode; import com.yfd.platform.system.domain.SmsVerifyCode;
import com.yfd.platform.system.domain.SysUser;
import com.yfd.platform.system.mapper.SmsVerifyCodeMapper; import com.yfd.platform.system.mapper.SmsVerifyCodeMapper;
import com.yfd.platform.system.mapper.SysUserMapper;
import com.yfd.platform.system.service.ISmsVerifyCodeService; import com.yfd.platform.system.service.ISmsVerifyCodeService;
import com.yfd.platform.utils.SmsSender; import com.yfd.platform.utils.SmsSender;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/** /**
* <p> * <p>
@ -19,14 +26,19 @@ import java.util.Random;
* </p> * </p>
*/ */
@Service @Service
@Slf4j
public class SmsVerifyCodeServiceImpl extends ServiceImpl<SmsVerifyCodeMapper, SmsVerifyCode> implements ISmsVerifyCodeService { public class SmsVerifyCodeServiceImpl extends ServiceImpl<SmsVerifyCodeMapper, SmsVerifyCode> implements ISmsVerifyCodeService {
private static final int CODE_VALID_MINUTES = 5; private static final int CODE_VALID_MINUTES = 5;
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
@Resource @Resource
private SmsSender smsSender; private SmsSender smsSender;
@Resource
private SysUserMapper sysUserMapper;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public String sendVerifyCode(String phone, Integer type) { public String sendVerifyCode(String phone, Integer type) {
@ -120,4 +132,69 @@ public class SmsVerifyCodeServiceImpl extends ServiceImpl<SmsVerifyCodeMapper, S
return false; return false;
} }
} }
@Override
public void batchSendContent(List<String> phoneList, String content) {
// for (String phone : phoneList) {
//
// }
String phone = StrUtil.join(",", phoneList);
try {
smsSender.send(phone, content);
} catch (Exception e) {
log.debug("批量发送短信失败"+phone);
}
}
@Override
public int urgeByUserIds(List<String> userIds, String content) {
if (userIds == null || userIds.isEmpty()) {
log.warn("催促短信发送失败用户ID列表为空");
return 0;
}
List<String> ids = userIds.stream().distinct().toList();
List<SysUser> sysUsers = sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>().in(SysUser::getId, ids)
.eq(SysUser::getRegStatus, "APPROVED").eq(SysUser::getStatus,1).select(SysUser::getPhone));
if (sysUsers.isEmpty()) {
log.warn("催促短信发送失败未找到有效的手机号userIds: {}", ids);
return 0;
}
List<String> validPhones = sysUsers.stream().map(SysUser::getPhone).filter(this::isValidPhone).toList();
if (validPhones.isEmpty()) {
log.warn("催促短信发送失败:没有合规的手机号");
return 0;
}
int successCount = 0;
// for (String phone : validPhones) {
//
// }
String phone = StrUtil.join(",", validPhones);
try {
boolean sent = smsSender.send(phone, content);
if (sent) {
successCount++;
log.info("催促短信发送成功: {}", phone);
} else {
log.warn("催促短信发送失败: {}", phone);
}
} catch (Exception e) {
log.error("催促短信发送异常: {}", phone, e);
}
log.info("催促短信发送完成,总数: {}, 成功: {}", validPhones.size(), successCount);
return successCount;
}
private boolean isValidPhone(String phone) {
if (StrUtil.isBlank(phone)) {
return false;
}
return PHONE_PATTERN.matcher(phone).matches();
}
} }

View File

@ -14,6 +14,7 @@ import com.yfd.platform.system.mapper.SysRoleMapper;
import com.yfd.platform.system.service.ISysOrganizationService; import com.yfd.platform.system.service.ISysOrganizationService;
import com.yfd.platform.system.service.IUserService; import com.yfd.platform.system.service.IUserService;
import com.yfd.platform.utils.ObjectConverterUtil; import com.yfd.platform.utils.ObjectConverterUtil;
import com.yfd.platform.utils.SecurityUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -56,21 +57,13 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
@Override @Override
public List<Map<String, Object>> getOrgTree(String parentid, public List<Map<String, Object>> getOrgTree(String parentid,
String params) { String params) {
List<SysOrganization> orgList = new ArrayList<>();
QueryWrapper<SysOrganization> queryWrapper = new QueryWrapper<>();
//根据父级id查询
queryWrapper.eq("parentid", parentid);
if (StrUtil.isNotEmpty(params)) {
queryWrapper.like("orgname", params); // 根据 部门名称
}
SysUser userInfo = userService.getUserInfo(); SysUser userInfo = userService.getUserInfo();
// 构建权限过滤条件
Set<String> allowedOrgIds = new HashSet<>();
if (userInfo.getUsertype() != 0) { if (userInfo.getUsertype() != 0) {
List<SysRole> roleByUserId = List<SysRole> roleByUserId = sysRoleMapper.getRoleByUserId(userInfo.getId());
sysRoleMapper.getRoleByUserId(userInfo.getId());
List<String> ids = new ArrayList<>();
// 循环当前角色
for (SysRole sysRole : roleByUserId) { for (SysRole sysRole : roleByUserId) {
// 获取角色的组织Id
String orgscope = sysRole.getOrgscope(); String orgscope = sysRole.getOrgscope();
if (StrUtil.isBlank(orgscope)) { if (StrUtil.isBlank(orgscope)) {
continue; continue;
@ -78,21 +71,33 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
// 拆分组织Id // 拆分组织Id
String[] split = orgscope.split(","); String[] split = orgscope.split(",");
List<String> stringList = Arrays.asList(split); List<String> stringList = Arrays.asList(split);
Set<String> set = new HashSet<>(); if (!stringList.isEmpty()) {
if (stringList.size() > 0) { allowedOrgIds.addAll(stringList);
List<SysOrganization> list = // 查询这些组织的父级ID
sysOrganizationMapper.selectList(new LambdaQueryWrapper<SysOrganization>().in(SysOrganization::getId, stringList)); List<SysOrganization> list = sysOrganizationMapper.selectList(
list.forEach(l -> set.add(l.getParentid())); new LambdaQueryWrapper<SysOrganization>().in(SysOrganization::getId, stringList));
for (SysOrganization org : list) {
if (org.getParentid() != null) {
allowedOrgIds.add(org.getParentid());
}
}
} }
ids.addAll(stringList);
ids.addAll(set);
} }
queryWrapper.in("id", ids);
} }
orgList = this.list(queryWrapper.orderByAsc("orgcode"));
// 将实体对象转换为 Map确保字段名与实体类一致 // 查询所有组织数据
// 将实体对象转换为 Map确保字段名与实体类一致 QueryWrapper<SysOrganization> queryWrapper = new QueryWrapper<>();
List<Map<String,Object>> listMap = orgList.stream().map(org -> { if (!allowedOrgIds.isEmpty()) {
queryWrapper.in("id", allowedOrgIds);
}
if (StrUtil.isNotEmpty(params)) {
queryWrapper.like("orgname", params);
}
List<SysOrganization> allOrgList = this.list(queryWrapper.orderByAsc("orgcode"));
// 将所有组织数据转换为Map结构
List<Map<String, Object>> allOrgMaps = allOrgList.stream().map(org -> {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("id", org.getId()); map.put("id", org.getId());
map.put("orgtype", org.getOrgtype()); map.put("orgtype", org.getOrgtype());
@ -109,12 +114,39 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
map.put("custom3", org.getCustom3()); map.put("custom3", org.getCustom3());
return map; return map;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
for (Map<String, Object> map : listMap) {
List<Map<String, Object>> childList = child(map.get( // 构建父子关系映射
"id").toString());//查询下一子集 Map<String, List<Map<String, Object>>> parentToChildrenMap = new HashMap<>();
map.put("childList", childList); //添加新列 子集 for (Map<String, Object> orgMap : allOrgMaps) {
String parentId = (String) orgMap.get("parentid");
parentToChildrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(orgMap);
}
// 过滤出指定父级ID的组织作为根节点
List<Map<String, Object>> rootOrgs = parentToChildrenMap.getOrDefault(parentid, new ArrayList<>());
// 构建完整的树形结构
buildTreeStructure(rootOrgs, parentToChildrenMap);
return rootOrgs;
}
/**
* 构建树形结构
* @param orgList 当前层级的组织列表
* @param parentToChildrenMap 父子关系映射
*/
private void buildTreeStructure(List<Map<String, Object>> orgList,
Map<String, List<Map<String, Object>>> parentToChildrenMap) {
for (Map<String, Object> orgMap : orgList) {
String orgId = (String) orgMap.get("id");
List<Map<String, Object>> children = parentToChildrenMap.getOrDefault(orgId, new ArrayList<>());
if (!children.isEmpty()) {
buildTreeStructure(children, parentToChildrenMap); // 递归构建子树
}
orgMap.put("childList", children);
} }
return listMap;
} }
/*********************************** /***********************************
@ -202,35 +234,42 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
@Override @Override
public List<SysOrganization> getOrganizationById(String id, public List<SysOrganization> getOrganizationById(String id,
String orgName) { String orgName) {
LambdaQueryWrapper<SysOrganization> queryWrapper =
new LambdaQueryWrapper<>();
SysUser userInfo = userService.getUserInfo(); SysUser userInfo = userService.getUserInfo();
// 收集所有允许的组织ID
Set<String> allowedOrgIds = new HashSet<>();
if (userInfo.getUsertype() != 0) { if (userInfo.getUsertype() != 0) {
List<SysRole> roleByUserId = List<SysRole> roleByUserId = sysRoleMapper.getRoleByUserId(userInfo.getId());
sysRoleMapper.getRoleByUserId(userInfo.getId());
List<String> ids = new ArrayList<>();
// 循环当前角色
for (SysRole sysRole : roleByUserId) { for (SysRole sysRole : roleByUserId) {
// 获取角色的组织Id
String orgscope = sysRole.getOrgscope(); String orgscope = sysRole.getOrgscope();
if (StrUtil.isBlank(orgscope)) { if (StrUtil.isBlank(orgscope)) {
continue; continue;
} }
// 拆分组织Id // 拆分组织Id并添加到集合中自动去重
String[] split = orgscope.split(","); String[] split = orgscope.split(",");
List<String> stringList = Arrays.asList(split); for (String orgId : split) {
ids.addAll(stringList); if (StrUtil.isNotBlank(orgId)) {
allowedOrgIds.add(orgId.trim());
}
}
} }
if (ObjectUtil.isNotEmpty(ids)) {
queryWrapper.in(SysOrganization::getId, ids);
}
} }
// 构建查询条件
LambdaQueryWrapper<SysOrganization> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysOrganization::getParentid, id);
if (StrUtil.isNotBlank(orgName)) { if (StrUtil.isNotBlank(orgName)) {
queryWrapper.like(SysOrganization::getOrgname, orgName); queryWrapper.like(SysOrganization::getOrgname, orgName);
} }
queryWrapper.eq(SysOrganization::getParentid, id).orderByDesc(SysOrganization::getOrgcode);
// 如果有权限限制添加IN条件
if (!allowedOrgIds.isEmpty()) {
queryWrapper.in(SysOrganization::getId, allowedOrgIds);
}
queryWrapper.orderByDesc(SysOrganization::getOrgcode);
return this.list(queryWrapper); return this.list(queryWrapper);
} }
@ -243,20 +282,34 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
***********************************/ ***********************************/
@Override @Override
public List<Map<String, Object>> getOrgScopeTree(String roleId) { public List<Map<String, Object>> getOrgScopeTree(String roleId) {
LambdaQueryWrapper<SysOrganization> queryWrapper =
new LambdaQueryWrapper<>(); LambdaQueryWrapper<SysOrganization> queryWrapper = new LambdaQueryWrapper<>();
if(!"admin".equals(SecurityUtils.getCurrentUsername())){
String userId = SecurityUtils.getUserId();
List<SysRole> roles = sysRoleMapper.getRoleByUserId(userId);
List<String> ids = new ArrayList<>();
for (SysRole role : roles) {
String orgscope = role.getOrgscope();
if(StrUtil.isNotBlank(orgscope)){
ids.addAll(Arrays.asList(orgscope.split(",")));
}
}
if(ids.isEmpty()){
return new ArrayList<>();
}
queryWrapper.in(SysOrganization::getId, ids);
}
queryWrapper.eq(SysOrganization::getIsvaild, '1'); queryWrapper.eq(SysOrganization::getIsvaild, '1');
queryWrapper.orderByAsc(SysOrganization::getOrgcode); queryWrapper.orderByAsc(SysOrganization::getOrgcode);
List<Map<String, Object>> listMaps = this.listMaps(queryWrapper); List<Map<String, Object>> mapList = this.listMaps(queryWrapper);
List<Map<String, Object>> listMaps = ObjectConverterUtil.convertMapFieldsToEntityFormat(SysOrganization.class, mapList);
// 获取当前角色 // 获取当前角色
SysRole sysRole = sysRoleMapper.selectById(roleId); SysRole sysRole = sysRoleMapper.selectById(roleId);
String orgscope = sysRole.getOrgscope(); String orgscope = sysRole.getOrgscope();
List<String> ids = new ArrayList<>(); List<String> ids = new ArrayList<>();
if (StrUtil.isNotBlank(orgscope)) { if (StrUtil.isNotBlank(orgscope)) {
String[] split = orgscope.split(","); ids.addAll(Arrays.asList(orgscope.split(",")));
ids = Arrays.asList(split);
} }
for (Map<String, Object> map : listMaps) { for (Map<String, Object> map : listMaps) {
String id = (String) map.get("id"); String id = (String) map.get("id");
if (ids.contains(id)) { if (ids.contains(id)) {

View File

@ -10,6 +10,13 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yfd.platform.config.ResponseResult; import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.data.domain.SysUserDataScope;
import com.yfd.platform.data.mapper.SysUserDataScopeMapper;
import com.yfd.platform.env.domain.SdEngInfoBH;
import com.yfd.platform.env.domain.SdHbrvDic;
import com.yfd.platform.env.domain.StationBasinInfo;
import com.yfd.platform.env.mapper.SdEngInfoBHMapper;
import com.yfd.platform.env.mapper.SdHbrvDicMapper;
import com.yfd.platform.system.domain.LoginUser; import com.yfd.platform.system.domain.LoginUser;
import com.yfd.platform.system.domain.SysRole; import com.yfd.platform.system.domain.SysRole;
import com.yfd.platform.system.domain.SysUser; import com.yfd.platform.system.domain.SysUser;
@ -55,12 +62,21 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
@Resource @Resource
private PasswordEncoder passwordEncoder; private PasswordEncoder passwordEncoder;
@Resource
private SdHbrvDicMapper sdHbrvDicMapper;
@Resource
private SdEngInfoBHMapper sdEngInfoBHMapper;
/** /**
* 文件空间配置 * 文件空间配置
*/ */
@Resource @Resource
private FileSpaceProperties fileSpaceProperties; private FileSpaceProperties fileSpaceProperties;
@Resource
private SysUserDataScopeMapper sysUserDataScopeMapper;
/********************************** /**********************************
* 用途说明:获取当前用户账号及名称 * 用途说明:获取当前用户账号及名称
* 参数说明 * 参数说明
@ -224,7 +240,6 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
* 返回值说明: 是否更新成功 * 返回值说明: 是否更新成功
************************************/ ************************************/
// ... existing code ... // ... existing code ...
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Map updateById(SysUser sysUser, String roleids) { public Map updateById(SysUser sysUser, String roleids) {
@ -309,7 +324,8 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
/** /**
* 处理用户角色分配增量更新 * 处理用户角色分配增量更新
* @param userId 用户 ID *
* @param userId 用户 ID
* @param roleIds 角色 ID 字符串逗号分隔 * @param roleIds 角色 ID 字符串逗号分隔
*/ */
private void handleUserRoles(String userId, String roleIds) { private void handleUserRoles(String userId, String roleIds) {
@ -430,9 +446,9 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
//根据当前用户id 查询角色表的级别 currentUser.getUser() 获取当前用户id //根据当前用户id 查询角色表的级别 currentUser.getUser() 获取当前用户id
String level = sysUserMapper.getMaxLevel(id); String level = sysUserMapper.getMaxLevel(id);
//判断是否获取级别 //判断是否获取级别
if ("admin".equals(SecurityUtils.getCurrentUsername())||StrUtil.isNotEmpty(level)) { if ("admin".equals(SecurityUtils.getCurrentUsername()) || StrUtil.isNotEmpty(level)) {
//判断当前用户级别 管理员及以上权限 //判断当前用户级别 管理员及以上权限
if ("admin".equals(SecurityUtils.getCurrentUsername())||Integer.parseInt(level) <= 2) { if ("admin".equals(SecurityUtils.getCurrentUsername()) || Integer.parseInt(level) <= 2) {
SysUser sysUser = sysUserMapper.selectById(id); SysUser sysUser = sysUserMapper.selectById(id);
UpdateWrapper<SysUser> updateWrapper = new UpdateWrapper<>(); UpdateWrapper<SysUser> updateWrapper = new UpdateWrapper<>();
String password = PasswordGenerator.generateRandomPassword(sysUser.getUsername()); String password = PasswordGenerator.generateRandomPassword(sysUser.getUsername());
@ -465,7 +481,7 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
//根据当前用户id 查询角色表的级别 currentUser.getUser() 获取当前用户id //根据当前用户id 查询角色表的级别 currentUser.getUser() 获取当前用户id
String level = sysUserMapper.getMaxLevel(id); String level = sysUserMapper.getMaxLevel(id);
//判断当前用户级别 管理员及以上权限 //判断当前用户级别 管理员及以上权限
if ("admin".equals(SecurityUtils.getCurrentUsername())||Integer.parseInt(level) <= 2) { if ("admin".equals(SecurityUtils.getCurrentUsername()) || Integer.parseInt(level) <= 2) {
UpdateWrapper<SysUser> updateWrapper = new UpdateWrapper<>(); UpdateWrapper<SysUser> updateWrapper = new UpdateWrapper<>();
//根据id修改用户状态最近修改人最近修改时间 //根据id修改用户状态最近修改人最近修改时间
updateWrapper.eq("id", id).set("status", status).set( updateWrapper.eq("id", id).set("status", status).set(
@ -547,8 +563,8 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
@Override @Override
public Page<SysUser> queryUsers(String orgid, public Page<SysUser> queryUsers(String orgid,
String username, String username,
Page<SysUser> page) { Page<SysUser> page) {
Page<SysUser> mapPage = sysUserMapper.queryUsers(orgid, Page<SysUser> mapPage = sysUserMapper.queryUsers(orgid,
username, page); username, page);
mapPage.getRecords().forEach(record -> { mapPage.getRecords().forEach(record -> {
@ -632,10 +648,10 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
} }
@Override @Override
public Page<SysUser> queryPendingAuditUsers(Page<SysUser> page,String name,String regStatus) { public Page<SysUser> queryPendingAuditUsers(Page<SysUser> page, String name, String regStatus) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(SysUser::getRegStatus, "PENDING","APPROVED","REJECTED"); queryWrapper.in(SysUser::getRegStatus, "PENDING", "APPROVED", "REJECTED");
queryWrapper.eq(ObjectUtil.isNotEmpty(regStatus),SysUser::getRegStatus, regStatus); queryWrapper.eq(ObjectUtil.isNotEmpty(regStatus), SysUser::getRegStatus, regStatus);
queryWrapper.and(StrUtil.isNotBlank(name), wrapper -> queryWrapper.and(StrUtil.isNotBlank(name), wrapper ->
wrapper.like(SysUser::getNickname, name) wrapper.like(SysUser::getNickname, name)
.or() .or()
@ -643,11 +659,85 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
); );
queryWrapper.orderByDesc(SysUser::getRegTime); queryWrapper.orderByDesc(SysUser::getRegTime);
Page<SysUser> mapPage = this.page(page, queryWrapper); Page<SysUser> mapPage = this.page(page, queryWrapper);
mapPage.getRecords().forEach(record -> {
String id = record.getId(); List<SysUser> records = mapPage.getRecords();
List<SysRole> sysRoles = sysRoleMapper.getRoleByUserId(id); if (records == null || records.isEmpty()) {
record.setRoles(sysRoles); return mapPage;
}); }
List<String> userIds = records.stream()
.map(SysUser::getId)
.collect(Collectors.toList());
Map<String, List<SysRole>> userRoleMap = new HashMap<>();
List<Map<String, Object>> userRoles = sysRoleMapper.getUserRolesByUserIds(userIds);
if (userRoles != null) {
for (Map<String, Object> row : userRoles) {
String userId = (String) row.get("userId");
SysRole role = new SysRole();
role.setId((String) row.get("id"));
role.setRolecode((String) row.get("rolecode"));
role.setRolename((String) row.get("rolename"));
role.setLevel((String) row.get("level"));
role.setDescription((String) row.get("description"));
role.setIsvaild((String) row.get("isvaild"));
role.setOrgscope((String) row.get("orgscope"));
role.setOptscope((String) row.get("optscope"));
role.setBusscope((String) row.get("busscope"));
userRoleMap.computeIfAbsent(userId, k -> new ArrayList<>()).add(role);
}
}
Map<String, List<SysUserDataScope>> userScopeMap = new HashMap<>();
List<SysUserDataScope> allScopes = sysUserDataScopeMapper.selectValidPermissionsWithNameByUserIds(userIds);
if (allScopes != null) {
for (SysUserDataScope scope : allScopes) {
userScopeMap.computeIfAbsent(scope.getUserId(), k -> new ArrayList<>()).add(scope);
}
}
List<StationBasinInfo> stationBasinInfos = sdEngInfoBHMapper.selectStationBasinInfo();
for (SysUser record : records) {
String userId = record.getId();
List<SysRole> roles = userRoleMap.getOrDefault(userId, Collections.emptyList());
record.setRoles(roles);
List<SysUserDataScope> scopes = userScopeMap.getOrDefault(userId, Collections.emptyList());
Set<String> basinNameSet = new LinkedHashSet<>();
Set<String> stationNameSet = new LinkedHashSet<>();
for (SysUserDataScope scope : scopes) {
String orgType = scope.getOrgType();
String orgId = scope.getOrgId();
String orgName = scope.getOrgName();
if ("HBRVCD".equals(orgType)) {
if (orgName != null) {
basinNameSet.add(orgName);
}
if (orgId != null) {
if (stationBasinInfos != null) {
List<String> stcdList = stationBasinInfos.stream().filter(info -> info.getHbrvcd().equals(orgId)).map(StationBasinInfo::getEnnm).toList();
stationNameSet.addAll(stcdList);
}
}
} else if ("STATION".equals(orgType)) {
if (orgName != null) {
stationNameSet.add(orgName);
}
if (orgId != null) {
if (stationBasinInfos != null) {
String hbrbcd = stationBasinInfos.stream().filter(info -> info.getStcd().equals(orgId)).map(StationBasinInfo::getHbrvnm).findFirst().orElse(null);
basinNameSet.add(hbrbcd);
}
}
}
}
record.setBasinNames(basinNameSet.isEmpty() ? null : String.join(",", basinNameSet));
record.setStationNames(stationNameSet.isEmpty() ? null : String.join(",", stationNameSet));
}
return mapPage; return mapPage;
} }

View File

@ -391,6 +391,42 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
fis.close(); fis.close();
} }
/**
* 判断文件名是否为图片类型
*/
public static boolean isImageFileName(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return false;
}
String lowerName = fileName.toLowerCase();
return lowerName.endsWith(".jpg")
|| lowerName.endsWith(".jpeg")
|| lowerName.endsWith(".png")
|| lowerName.endsWith(".gif")
|| lowerName.endsWith(".bmp")
|| lowerName.endsWith(".webp")
|| lowerName.endsWith(".svg");
}
/**
* 判断文件名是否为视频类型
*/
public static boolean isVideoFileName(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return false;
}
String lowerName = fileName.toLowerCase();
return lowerName.endsWith(".mp4")
|| lowerName.endsWith(".avi")
|| lowerName.endsWith(".mov")
|| lowerName.endsWith(".wmv")
|| lowerName.endsWith(".flv")
|| lowerName.endsWith(".mkv");
}
public static String getMd5(File file) { public static String getMd5(File file) {
return getMd5(getByte(file)); return getMd5(getByte(file));
} }

View File

@ -0,0 +1,334 @@
package com.yfd.platform.utils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* 流式 multipart 文件上传解析器
* <p>
* 绕过 Spring {@code MultipartResolver}它会先把整个请求体解析完才调用 Controller
* 直接从 {@link HttpServletRequest#getInputStream()} 边读边写目标文件
* 80MB+ 的大文件也能在 Controller 方法内立即开始接收
* <p>
* 前提{@code spring.servlet.multipart.resolve-lazily=true}
*/
@Slf4j
public final class MultipartStreamParser {
private static final int BUFFER_SIZE = 8192;
private MultipartStreamParser() {
}
/**
* multipart/form-data 请求中流式提取第一个文件并写入目标目录
*
* @param request HTTP 请求Content-Type: multipart/form-data
* @param targetDir 目标目录文件将保存为 upload.zip
* @return 解析结果原始文件名目标文件文件大小
*/
public static StreamedFile streamToFile(HttpServletRequest request, Path targetDir) throws IOException {
long t0 = System.currentTimeMillis();
String contentType = request.getContentType();
if (contentType == null || !contentType.startsWith("multipart/form-data")) {
throw new IOException("请求不是 multipart/form-data 类型");
}
String boundary = extractBoundary(contentType);
byte[] closingDelim = ("\r\n--" + boundary + "--").getBytes(StandardCharsets.ISO_8859_1);
InputStream rawInput = new BufferedInputStream(request.getInputStream(), BUFFER_SIZE);
long t1 = System.currentTimeMillis();
String originalFileName = readPartHeaders(rawInput, boundary);
long t2 = System.currentTimeMillis();
Files.createDirectories(targetDir);
File targetFile = new File(targetDir.toFile(), "upload.zip");
long fileSize = streamBinaryToFile(rawInput, targetFile, closingDelim);
long t3 = System.currentTimeMillis();
log.info("流式接收完成: {} -> {} ({} bytes), 头解析{}ms, 体写入{}ms, 总耗时{}ms",
originalFileName, targetFile.getAbsolutePath(), fileSize,
t2 - t1, t3 - t2, t3 - t0);
return new StreamedFile(originalFileName, targetFile, fileSize);
}
// ==================== 阶段1 part ====================
/**
* 跳过第一个 boundary 读取该 part HTTP 从中提取 filename
*/
private static String readPartHeaders(InputStream in, String boundary) throws IOException {
String firstBoundary = "--" + boundary;
String fileName = null;
// 跳过第一个 boundary
String line = readLine(in);
if (line == null || !line.equals(firstBoundary)) {
// 可能前面有空行或 boundary 带了 \r
if (line != null && line.startsWith(firstBoundary)) {
// ok
} else {
throw new IOException("未找到 multipart boundary收到: " + line);
}
}
// 读取 part 直到空行
while ((line = readLine(in)) != null) {
if (line.isEmpty()) {
break; // 空行 = 头结束接下来是二进制数据
}
if (line.startsWith("Content-Disposition") || line.startsWith("content-disposition")) {
fileName = extractFileName(line);
}
}
if (fileName == null) {
fileName = "unknown.zip";
}
return fileName;
}
/**
* Content-Disposition 头中提取 filename
* 格式form-data; name="file"; filename="xxx.zip"
*/
static String extractFileName(String headerLine) {
for (String part : headerLine.split(";")) {
part = part.trim();
if (part.startsWith("filename")) {
int eq = part.indexOf('=');
if (eq >= 0) {
String val = part.substring(eq + 1).trim();
if (val.startsWith("\"") && val.endsWith("\"")) {
val = val.substring(1, val.length() - 1);
}
// 先进行 URL 解码处理 %E4%B8%AD%E6%96%87 这种编码
try {
val = java.net.URLDecoder.decode(val, StandardCharsets.UTF_8.name());
} catch (Exception e) {
log.debug("URL解码失败使用原始值: {}", val);
}
// 再进行字符编码转换处理 GBK/UTF-8 乱码
String decodedFileName = decodeFileName(val);
return decodedFileName;
}
}
}
return null;
}
/**
* 尝试修复乱码的文件名支持 GBK UTF-8 编码
*/
private static String decodeFileName(String rawFileName) {
if (rawFileName == null || rawFileName.isEmpty()) {
return rawFileName;
}
try {
byte[] rawBytes = rawFileName.getBytes(StandardCharsets.ISO_8859_1);
// 优先尝试 UTF-8 解码现代浏览器常用
String utf8Decoded = new String(rawBytes, StandardCharsets.UTF_8);
if (isValidChinese(utf8Decoded)) {
return utf8Decoded;
}
// 再尝试 GBK 解码Windows/旧版浏览器常用
String gbkDecoded = new String(rawBytes, "GBK");
if (isValidChinese(gbkDecoded)) {
return gbkDecoded;
}
// 都不包含中文返回 UTF-8 解码结果
return utf8Decoded;
} catch (Exception e) {
log.warn("文件名解码失败,使用原始值: {}", rawFileName, e);
return rawFileName;
}
}
/**
* 简单判断字符串是否包含有效的中文字符
*/
private static boolean isValidChinese(String str) {
if (str == null || str.isEmpty()) {
return false;
}
for (char c : str.toCharArray()) {
// 检查是否在中文 Unicode 范围内
if (c >= '\u4e00' && c <= '\u9fa5') {
return true;
}
// 检查中文标点符号范围
if (c >= '\u3000' && c <= '\u303f') {
return true;
}
}
return false;
}
// ==================== 阶段2流式写二进制 ====================
/**
* 从输入流读取二进制数据写入目标文件遇到 closingDelim 时停止
* <p>
* 使用重叠窗口策略每次读取一个 chunk保留末尾 (delimLen-1) 字节
* 与下一个 chunk 拼接防止 boundary chunk 被截断
*/
private static long streamBinaryToFile(InputStream in, File targetFile, byte[] closingDelim) throws IOException {
int delimLen = closingDelim.length;
int overlap = delimLen - 1;
byte[] buf = new byte[BUFFER_SIZE];
byte[] carry = new byte[overlap]; // 上一轮末尾的重叠字节
int carryLen = 0;
long totalWritten = 0;
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile), BUFFER_SIZE)) {
while (true) {
int n = in.read(buf);
if (n == -1) {
// 流结束写出剩余的重叠字节
if (carryLen > 0) {
out.write(carry, 0, carryLen);
totalWritten += carryLen;
}
break;
}
// 将上一轮的重叠字节 + 本轮数据拼接搜索 closing boundary
byte[] searchBuf = new byte[carryLen + n];
if (carryLen > 0) {
System.arraycopy(carry, 0, searchBuf, 0, carryLen);
}
System.arraycopy(buf, 0, searchBuf, carryLen, n);
int boundaryPos = indexOf(searchBuf, closingDelim);
if (boundaryPos >= 0) {
// 找到 closing boundary写出之前的数据
if (boundaryPos > 0) {
out.write(searchBuf, 0, boundaryPos);
totalWritten += boundaryPos;
}
break;
}
// 未找到 boundary写出安全部分保留末尾 overlap 字节
int safeLen = searchBuf.length - overlap;
if (safeLen > 0) {
out.write(searchBuf, 0, safeLen);
totalWritten += safeLen;
// 保留末尾 overlap 字节到 carry
System.arraycopy(searchBuf, safeLen, carry, 0, overlap);
carryLen = overlap;
} else {
// 数据量不足 overlap全部保留
System.arraycopy(searchBuf, 0, carry, 0, searchBuf.length);
carryLen = searchBuf.length;
}
}
}
return totalWritten;
}
// ==================== 工具方法 ====================
/**
* Content-Type 头中提取 boundary
* 格式multipart/form-data; boundary=----WebKitFormBoundary...
*/
static String extractBoundary(String contentType) {
for (String part : contentType.split(";")) {
part = part.trim();
if (part.startsWith("boundary=")) {
return part.substring("boundary=".length());
}
}
throw new IllegalArgumentException("无法从 Content-Type 提取 boundary: " + contentType);
}
/**
* 按行读取 \r\n \n 结尾返回行内容不含换行符
*/
private static String readLine(InputStream in) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(256);
int prev = -1;
int b;
while ((b = in.read()) != -1) {
if (b == '\n') {
// 去掉末尾的 \r
byte[] bytes = bos.toByteArray();
int len = bytes.length;
if (len > 0 && bytes[len - 1] == '\r') {
len--;
}
return new String(bytes, 0, len, StandardCharsets.ISO_8859_1);
}
bos.write(b);
prev = b;
}
// 流结束
if (bos.size() > 0) {
return bos.toString(StandardCharsets.ISO_8859_1);
}
return null;
}
/**
* byte 数组中查找子数组的位置Boyer-Moore 简化版
*/
private static int indexOf(byte[] haystack, byte[] needle) {
if (needle.length == 0) return 0;
if (haystack.length < needle.length) return -1;
outer:
for (int i = 0; i <= haystack.length - needle.length; i++) {
for (int j = 0; j < needle.length; j++) {
if (haystack[i + j] != needle[j]) {
continue outer;
}
}
return i;
}
return -1;
}
// ==================== 结果 DTO ====================
public static class StreamedFile {
private final String originalFileName;
private final File targetFile;
private final long fileSize;
StreamedFile(String originalFileName, File targetFile, long fileSize) {
this.originalFileName = originalFileName;
this.targetFile = targetFile;
this.fileSize = fileSize;
}
public String getOriginalFileName() {
return originalFileName;
}
public File getTargetFile() {
return targetFile;
}
public long getFileSize() {
return fileSize;
}
}
}

View File

@ -24,8 +24,11 @@ spring:
matching-strategy: ant_path_matcher matching-strategy: ant_path_matcher
servlet: servlet:
multipart: multipart:
max-file-size: 30MB max-file-size: 300MB
max-request-size: 100MB max-request-size: 500MB
file-size-threshold: 1KB
location: /tmp/upload
resolve-lazily: true
logging: logging:
file: file:

View File

@ -1,5 +1,8 @@
server: server:
port: 8093 port: 8093
tomcat:
connection-timeout: 300000
max-swallow-size: 500MB
spring: spring:
#应用名称 #应用名称
@ -13,11 +16,94 @@ spring:
url: "${DB_MASTER_URL:jdbc:oracle:thin:@172.16.21.134:1521/SDLYZ}" url: "${DB_MASTER_URL:jdbc:oracle:thin:@172.16.21.134:1521/SDLYZ}"
username: "${DB_MASTER_USERNAME:QGC_REFA}" username: "${DB_MASTER_USERNAME:QGC_REFA}"
password: "${DB_MASTER_PASSWORD:Y4M4K1oCkL8U}" password: "${DB_MASTER_PASSWORD:Y4M4K1oCkL8U}"
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 30000
async-init: true
keep-alive-between-time-millis: 120000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 180000
max-evictable-idle-time-millis: 300000
phy-timeout-millis: 25200000
validation-query: SELECT 1 FROM DUAL
validation-query-timeout: 3
test-while-idle: true
test-on-borrow: false
test-on-return: false
keep-alive: true
remove-abandoned: true
remove-abandoned-timeout: 1800
log-abandoned: true
break-after-acquire-failure: true
time-between-connect-error-millis: 300000
pool-prepared-statements: true
max-open-prepared-statements: 100
max-pool-prepared-statement-per-connection-size: 100
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
slave: slave:
driverClassName: oracle.jdbc.OracleDriver driverClassName: oracle.jdbc.OracleDriver
url: "${DB_SLAVE_URL:jdbc:oracle:thin:@172.16.21.134:1521/SDLYZ}" url: "${DB_SLAVE_URL:jdbc:oracle:thin:@172.16.21.134:1521/SDLYZ}"
username: "${DB_SLAVE_USERNAME:QGC_REFA}" username: "${DB_SLAVE_USERNAME:QGC_REFA}"
password: "${DB_SLAVE_PASSWORD:Y4M4K1oCkL8U}" password: "${DB_SLAVE_PASSWORD:Y4M4K1oCkL8U}"
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 30000
async-init: true
keep-alive-between-time-millis: 120000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 180000
max-evictable-idle-time-millis: 300000
phy-timeout-millis: 25200000
validation-query: SELECT 1 FROM DUAL
validation-query-timeout: 3
test-while-idle: true
test-on-borrow: false
test-on-return: false
keep-alive: true
remove-abandoned: true
remove-abandoned-timeout: 1800
log-abandoned: true
break-after-acquire-failure: true
time-between-connect-error-millis: 300000
pool-prepared-statements: true
max-open-prepared-statements: 100
max-pool-prepared-statement-per-connection-size: 100
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 3000
merge-sql: true
slf4j:
enabled: true
wall:
enabled: true
log-violation: true
throw-exception: true
config:
select-where-alway-true-check: true
select-having-alway-true-check: true
delete-where-alway-true-check: true
update-where-alay-true-check: true
update-where-alway-true-check: true
update-where-none-check: true
multi-statement-allow: false
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
session-stat-enable: true
principal-session-name: admin
profile-enable: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin
reset-enable: false
jackson: jackson:
date-format: yyyy-MM-dd HH:mm:ss date-format: yyyy-MM-dd HH:mm:ss
@ -29,6 +115,9 @@ spring:
multipart: multipart:
max-file-size: 300MB max-file-size: 300MB
max-request-size: 500MB max-request-size: 500MB
file-size-threshold: 1KB
location: /tmp/upload
resolve-lazily: true
logging: logging:
file: file:
@ -122,6 +211,6 @@ attachment:
token: ${ATTACHMENT_TOKEN:qgcBkod25ngBa4wu8BtfCPYsJ7lQGVDoexH} token: ${ATTACHMENT_TOKEN:qgcBkod25ngBa4wu8BtfCPYsJ7lQGVDoexH}
upload-url: ${ATTACHMENT_UPLOAD_URL:http://172.16.31.185:18200/upload} upload-url: ${ATTACHMENT_UPLOAD_URL:http://172.16.31.185:18200/upload}
video-url: ${ATTACHMENT_VIDEO_URL:http://172.16.31.185:18200/upload} video-url: ${ATTACHMENT_VIDEO_URL:http://172.16.31.185:18200/upload}
delete-url: ${ATTACHMENT_DELETE_URL:http://172.16.31.185:18200/delete} delete-url: ${ATTACHMENT_DELETE_URL:http://172.16.31.185:18200/FileDelete}
# upload-url: ${ATTACHMENT_UPLOAD_URL:https://211.99.26.225:12125/upload} # upload-url: ${ATTACHMENT_UPLOAD_URL:https://211.99.26.225:12125/upload}
# video-url: ${ATTACHMENT_VIDEO_URL:https://211.99.26.225:12125/upload} # video-url: ${ATTACHMENT_VIDEO_URL:https://211.99.26.225:12125/upload}

View File

@ -1,6 +1,8 @@
server: server:
port: 8093 port: 8093
tomcat:
connection-timeout: 300000
max-swallow-size: 500MB
spring: spring:
#应用名称 #应用名称
application: application:
@ -13,12 +15,94 @@ spring:
url: "${DB_MASTER_URL:jdbc:oracle:thin:@172.16.31.190:1521/SDLYZ}" url: "${DB_MASTER_URL:jdbc:oracle:thin:@172.16.31.190:1521/SDLYZ}"
username: "${DB_MASTER_USERNAME:QGC_REFA}" username: "${DB_MASTER_USERNAME:QGC_REFA}"
password: "${DB_MASTER_PASSWORD:Y4M4K1oCkL8U}" password: "${DB_MASTER_PASSWORD:Y4M4K1oCkL8U}"
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 30000
async-init: true
keep-alive-between-time-millis: 120000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 180000
max-evictable-idle-time-millis: 300000
phy-timeout-millis: 25200000
validation-query: SELECT 1 FROM DUAL
validation-query-timeout: 3
test-while-idle: true
test-on-borrow: false
test-on-return: false
keep-alive: true
remove-abandoned: true
remove-abandoned-timeout: 1800
log-abandoned: true
break-after-acquire-failure: true
time-between-connect-error-millis: 300000
pool-prepared-statements: true
max-open-prepared-statements: 100
max-pool-prepared-statement-per-connection-size: 100
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
slave: slave:
driverClassName: oracle.jdbc.OracleDriver driverClassName: oracle.jdbc.OracleDriver
url: "${DB_SLAVE_URL:jdbc:oracle:thin:@172.16.31.190:1521/SDLYZ}" url: "${DB_SLAVE_URL:jdbc:oracle:thin:@172.16.31.190:1521/SDLYZ}"
username: "${DB_SLAVE_USERNAME:QGC_REFA}" username: "${DB_SLAVE_USERNAME:QGC_REFA}"
password: "${DB_SLAVE_PASSWORD:Y4M4K1oCkL8U}" password: "${DB_SLAVE_PASSWORD:Y4M4K1oCkL8U}"
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 30000
async-init: true
keep-alive-between-time-millis: 120000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 180000
max-evictable-idle-time-millis: 300000
phy-timeout-millis: 25200000
validation-query: SELECT 1 FROM DUAL
validation-query-timeout: 3
test-while-idle: true
test-on-borrow: false
test-on-return: false
keep-alive: true
remove-abandoned: true
remove-abandoned-timeout: 1800
log-abandoned: true
break-after-acquire-failure: true
time-between-connect-error-millis: 300000
pool-prepared-statements: true
max-open-prepared-statements: 100
max-pool-prepared-statement-per-connection-size: 100
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 3000
merge-sql: true
slf4j:
enabled: true
wall:
enabled: true
log-violation: true
throw-exception: true
config:
select-where-alway-true-check: true
select-having-alway-true-check: true
delete-where-alway-true-check: true
update-where-alay-true-check: true
update-where-alway-true-check: true
update-where-none-check: true
multi-statement-allow: false
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
session-stat-enable: true
principal-session-name: admin
profile-enable: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin
reset-enable: false
jackson: jackson:
date-format: yyyy-MM-dd HH:mm:ss date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8 time-zone: GMT+8
@ -29,7 +113,9 @@ spring:
multipart: multipart:
max-file-size: 300MB max-file-size: 300MB
max-request-size: 500MB max-request-size: 500MB
file-size-threshold: 1KB
location: /tmp/upload
resolve-lazily: true
logging: logging:
file: file:
name: logs/platform-dev.log name: logs/platform-dev.log

View File

@ -1,5 +1,5 @@
server: server:
port: 8090 port: 8093
spring: spring:
#应用名称 #应用名称
@ -9,44 +9,120 @@ spring:
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
druid: druid:
master: master:
driverClassName: com.mysql.cj.jdbc.Driver driverClassName: oracle.jdbc.OracleDriver
url: "${DB_MASTER_URL:jdbc:mysql://43.138.168.68:3306/frameworkdb2025?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true}" url: "${DB_MASTER_URL:jdbc:oracle:thin:@172.16.21.134:1521/SDLYZ}"
username: "${DB_MASTER_USERNAME:root}" username: "${DB_MASTER_USERNAME:QGC_REFA}"
password: "${DB_MASTER_PASSWORD:}" password: "${DB_MASTER_PASSWORD:Y4M4K1oCkL8U}"
slave:
driverClassName: oracle.jdbc.OracleDriver
url: "${DB_SLAVE_URL:jdbc:oracle:thin:@172.16.21.134:1521/SDLYZ}"
username: "${DB_SLAVE_USERNAME:QGC_REFA}"
password: "${DB_SLAVE_PASSWORD:Y4M4K1oCkL8U}"
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mvc: mvc:
pathmatch: pathmatch:
matching-strategy: ant_path_matcher matching-strategy: ant_path_matcher
servlet: servlet:
multipart: multipart:
max-file-size: 30MB max-file-size: 300MB
max-request-size: 100MB max-request-size: 500MB
file-size-threshold: 1KB
location: /tmp/upload
resolve-lazily: true
logging: logging:
file: file:
name: logs/projectname.log name: logs/platform-dev.log
level: level:
com.genersoft.iot: debug root: info
com.genersoft.iot.vmp.storager.dao: info com.yfd.platform: info
com.genersoft.iot.vmp.gb28181: info # com.yfd.platform.*.mapper: trace
# 在线文档: swagger-ui生产环境建议关闭 # 在线文档: swagger-ui生产环境建议关闭
swagger-ui: swagger-ui:
enabled: false enabled: true
file-space: #项目文档空间 mybatis-plus:
files: D:\demoproject\files\ #单独上传的文件附件 # mapper-locations: classpath*:**/mapper/*Mapper.xml,classpath*:**/mapping/*Mapper.xml
useravatar: D:\demoproject\useravatar\ #用户头像 global-config:
system: D:\demoproject\system\ #系统文档根目录,用于头像等静态资源 banner: false
db-config:
id-type: ASSIGN_ID
insert-strategy: not_null
update-strategy: not_null
select-strategy: not_empty
table-underline: true
logic-delete-value: 1
logic-not-delete-value: 0
logic-delete-field: isDeleted
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 登录相关配置
login:
# 登录缓存
cache-enable: true
# 是否限制单用户登录
single-login: false
# 验证码
login-code:
# 验证码类型配置 查看 LoginProperties 类
code-type: arithmetic
# 启动自动数据库初始化(仅 dev/server # 启动自动数据库初始化(仅 dev/server
app: app:
# ZIP导入临时目录配置
zip-import:
temp-dir: ${ZIP_IMPORT_TEMP_DIR:/qgc-platform/tmp/zip_import_temp}
init: init:
enabled: true enabled: false
schema: classpath:db-init/sql/min-schema.sql schema: classpath:db-init/sql/min-schema.sql
# data 文件可选;为避免复杂 dump 解析问题,先不导入 # data 文件可选;为避免复杂 dump 解析问题,先不导入
# data: # data:
marker-table: sys_user marker-table: sys_user
marker-version: v1.0.0 marker-version: v1.0.0
# 登录图形验证码有效时间/分钟
expiration: 2
# 验证码高度
width: 111
# 验证码宽度
heigth: 36
# 内容长度
length: 2
# 字体名称,为空则使用默认字体
font-name:
# 字体大小
font-size: 25
# IP 本地解析
ip:
local-parsing: true
file-space: #项目文档空间
files: /qgc-platform/files/ #单独上传的文件附件
system: /qgc-platform/system/ #单独上传的文件
task:
pool:
# 核心线程池大小
core-pool-size: 10
# 最大线程数
max-pool-size: 30
# 活跃时间
keep-alive-seconds: 60
# 队列容量
queue-capacity: 50
attachment:
token: ${ATTACHMENT_TOKEN:qgcBkod25ngBa4wu8BtfCPYsJ7lQGVDoexH}
upload-url: ${ATTACHMENT_UPLOAD_URL:http://172.16.31.185:18200/upload}
video-url: ${ATTACHMENT_VIDEO_URL:http://172.16.31.185:18200/upload}
delete-url: ${ATTACHMENT_DELETE_URL:http://172.16.31.185:18200/delete}

View File

@ -38,5 +38,5 @@ springdoc:
swagger-ui: swagger-ui:
enabled: true enabled: true
path: /swagger-ui.html path: /swagger-ui.html
packages-to-scan: com.yfd.platform # packages-to-scan: com.yfd.platform

View File

@ -0,0 +1,195 @@
<?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="com.yfd.platform.data.mapper.FishStatisticsMapper">
<resultMap id="StatisticsResultMap" type="com.yfd.platform.data.domain.vo.FishStatisticsVO">
<result column="USER_ID" property="userId"/>
<result column="REAL_NAME" property="realName"/>
<result column="PHONE" property="phone"/>
<result column="CONTACT" property="contact"/>
<result column="BASIN_NAMES" property="basinNames"/>
<result column="STATION_NAMES" property="stationNames"/>
<result column="BASIN_CODE" property="basinCode"/>
<result column="STATION_CODE" property="stationCode"/>
<result column="REPORT_MONTH" property="reportMonth"/>
<result column="MIN_STRDT" property="minStrdt"/>
<result column="MAX_ENDDT" property="maxEnddt"/>
<result column="TOTAL_FCNT" property="totalFcnt"/>
<result column="HAS_DATA" property="hasData"/>
</resultMap>
<sql id="statisticsQuery">
WITH
user_scope_detail AS (
SELECT DISTINCT
su.ID AS USER_ID,
e.STCD AS STATION_CODE,
h.HBRVCD AS BASIN_CODE,
h.HBRVNM AS BASIN_NAME,
e.ENNM AS STATION_NAME
FROM QGC_REFA.SYS_USER su
JOIN QGC_REFA.SYS_USER_DATA_SCOPE sud
ON su.ID = sud.USER_ID
AND sud.STATUS = 1
AND sud.ORG_TYPE = 'HBRVCD'
JOIN QGC_REFA.SD_HBRV_DIC h
ON sud.ORG_ID = h.HBRVCD
AND h.ENABLED = 1
AND h.IS_DELETED = 0
JOIN QGC_REFA.SD_ENGINFO_B_H e
ON e.HBRVCD = h.HBRVCD
AND e.USFL = 1
WHERE su.STATUS = 1
AND su.REG_STATUS IN ('PENDING', 'APPROVED', 'REJECTED')
UNION
SELECT DISTINCT
su.ID AS USER_ID,
e.STCD AS STATION_CODE,
h.HBRVCD AS BASIN_CODE,
h.HBRVNM AS BASIN_NAME,
e.ENNM AS STATION_NAME
FROM QGC_REFA.SYS_USER su
JOIN QGC_REFA.SYS_USER_DATA_SCOPE sud
ON su.ID = sud.USER_ID
AND sud.STATUS = 1
AND sud.ORG_TYPE = 'STATION'
JOIN QGC_REFA.SD_ENGINFO_B_H e
ON sud.ORG_ID = e.STCD
AND e.USFL = 1
JOIN QGC_REFA.SD_HBRV_DIC h
ON e.HBRVCD = h.HBRVCD
AND h.ENABLED = 1
AND h.IS_DELETED = 0
WHERE su.STATUS = 1
AND su.REG_STATUS IN ('PENDING', 'APPROVED', 'REJECTED')
),
filtered_users AS (
SELECT DISTINCT USER_ID
FROM user_scope_detail
<where>
<if test="basinCode != null and basinCode != ''">
AND BASIN_CODE = #{basinCode}
</if>
<if test="stationCode != null and stationCode != ''">
AND STATION_CODE = #{stationCode}
</if>
</where>
),
distinct_basins AS (
SELECT DISTINCT USER_ID,BASIN_CODE, BASIN_NAME
FROM user_scope_detail
WHERE USER_ID IN (SELECT USER_ID FROM filtered_users)
),
distinct_stations AS (
SELECT DISTINCT USER_ID, STATION_CODE, STATION_NAME
FROM user_scope_detail
WHERE USER_ID IN (SELECT USER_ID FROM filtered_users)
),
basin_agg AS (
SELECT
USER_ID,
RTRIM(
XMLAGG(XMLELEMENT(E, BASIN_NAME || ',') ORDER BY BASIN_NAME)
.EXTRACT('//text()').GETCLOBVAL(),
','
) AS BASIN_NAMES,
MIN(BASIN_CODE) AS MIN_BASIN_CODE -- 辅助排序字段VARCHAR2
FROM distinct_basins
GROUP BY USER_ID
),
station_agg AS (
SELECT
USER_ID,
RTRIM(
XMLAGG(XMLELEMENT(E, STATION_NAME || ',') ORDER BY STATION_NAME)
.EXTRACT('//text()').GETCLOBVAL(),
','
) AS STATION_NAMES,
MIN(STATION_CODE) AS MIN_STATION_CODE -- 辅助排序字段VARCHAR2
FROM distinct_stations
GROUP BY USER_ID
),
fish_monthly AS (
SELECT
CREATED_BY,
TO_CHAR(STRDT, 'YYYY-MM') AS REPORT_MONTH,
MIN(STRDT) AS MIN_STRDT,
MAX(STRDT) AS MAX_ENDDT,
SUM(FCNT) AS TOTAL_FCNT
FROM QGC_REFA.FISH_DRAFT_DATA
WHERE DELETED_FLAG = 0
AND STATUS IN ('PENDING','APPROVED')
GROUP BY CREATED_BY, TO_CHAR(STRDT, 'YYYY-MM')
),
all_users AS (
SELECT USER_ID FROM filtered_users
)
SELECT
u.USER_ID,
su.REAL_NAME,
su.PHONE,
su.NICKNAME || '/' || su.PHONE AS CONTACT,
ba.MIN_BASIN_CODE,
sa.MIN_STATION_CODE,
ba.BASIN_NAMES,
sa.STATION_NAMES,
fm.REPORT_MONTH,
fm.MIN_STRDT,
fm.MAX_ENDDT,
fm.TOTAL_FCNT,
CASE WHEN fm.CREATED_BY IS NOT NULL THEN 1 ELSE 0 END AS HAS_DATA
FROM all_users u
JOIN QGC_REFA.SYS_USER su
ON u.USER_ID = su.ID
LEFT JOIN basin_agg ba
ON u.USER_ID = ba.USER_ID
LEFT JOIN station_agg sa
ON u.USER_ID = sa.USER_ID
LEFT JOIN fish_monthly fm
ON u.USER_ID = fm.CREATED_BY
<!-- 可选排序 -->
<!-- <if test="basinNames != null and basinNames.size() > 0">-->
<!-- AND (-->
<!-- <foreach collection="basinNames" item="name" separator=" OR ">-->
<!-- INSTR(ba.BASIN_NAMES, #{name}) > 0-->
<!-- </foreach>-->
<!-- )-->
<!-- </if>-->
<!-- <if test="stationNames != null and stationNames.size() > 0">-->
<!-- AND (-->
<!-- <foreach collection="stationNames" item="name" separator=" OR ">-->
<!-- INSTR(sa.STATION_NAMES, #{name}) > 0-->
<!-- </foreach>-->
<!-- )-->
<!-- </if>-->
</sql>
<select id="queryStatistics" resultMap="StatisticsResultMap">
SELECT * FROM (
SELECT
t.*,
ROW_NUMBER() OVER (ORDER BY t.MIN_BASIN_CODE,t.MIN_STATION_CODE,t.REPORT_MONTH,t.MIN_STRDT) AS rn
FROM (
<include refid="statisticsQuery"/>
) t ORDER BY t.MIN_BASIN_CODE,t.MIN_STATION_CODE,t.REPORT_MONTH,t.MIN_STRDT
) WHERE rn BETWEEN #{startRow}+1 AND #{endRow}
</select>
<select id="countStatistics" resultType="int">
SELECT COUNT(*) FROM (
<include refid="statisticsQuery"/>
)
</select>
</mapper>

View File

@ -0,0 +1,64 @@
<?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="com.yfd.platform.data.mapper.ImportTaskRowMapper">
<resultMap id="BaseResultMap" type="com.yfd.platform.data.domain.ImportTaskRow">
<id column="ID" property="id"/>
<result column="TASK_ID" property="taskId"/>
<result column="ROW_INDEX" property="rowIndex"/>
<result column="STATUS" property="status"/>
<result column="DATA_ID" property="dataId"/>
<result column="STCD" property="stcd"/>
<result column="STNM" property="stnm"/>
<result column="RSTCD" property="rstcd"/>
<result column="ENNM" property="ennm"/>
<result column="HBRVCD" property="hbrvcd"/>
<result column="HBRVNM" property="hbrvnm"/>
<result column="RVCD" property="rvcd"/>
<result column="BASE_ID" property="baseId"/>
<result column="BASE_NAME" property="baseName"/>
<result column="STRDT" property="strdt"/>
<result column="STRDT_STR" property="strdtStr"/>
<result column="ENDDT" property="enddt"/>
<result column="FTP" property="ftp"/>
<result column="FTP_NAME" property="ftpName"/>
<result column="ISFS" property="isfs"/>
<result column="DIRECTION" property="direction"/>
<result column="FCNT" property="fcnt"/>
<result column="FSZ" property="fsz"/>
<result column="FWET" property="fwet"/>
<result column="WT" property="wt"/>
<result column="PICPTH" property="picpth"/>
<result column="VDPTH" property="vdpth"/>
<result column="TM" property="tm"/>
<result column="SOURCE_TYPE" property="sourceType"/>
<result column="MOUTH" property="mouth"/>
<result column="YR" property="yr"/>
<result column="WARNINGS" property="warnings"/>
<result column="UNRECOGNIZED_FIELDS" property="unrecognizedFields"/>
<result column="VDPTH_LIST_JSON" property="vdpthListJson"/>
<result column="PICPTH_LIST_JSON" property="picpthListJson"/>
<result column="VDPTH_WARNINGS" property="vdpthWarnings"/>
<result column="PICPTH_WARNINGS" property="picpthWarnings"/>
<result column="CREATED_AT" property="createdAt"/>
</resultMap>
<select id="selectByTaskId" resultMap="BaseResultMap">
SELECT * FROM IMPORT_TASK_ROW
WHERE TASK_ID = #{taskId}
ORDER BY ROW_INDEX
</select>
<select id="selectByTaskIdAndStatus" resultMap="BaseResultMap">
SELECT * FROM IMPORT_TASK_ROW
WHERE TASK_ID = #{taskId}
AND STATUS = #{status}
ORDER BY ROW_INDEX
</select>
<delete id="deleteByTaskId">
DELETE FROM IMPORT_TASK_ROW WHERE TASK_ID = #{taskId}
</delete>
</mapper>

View File

@ -72,4 +72,67 @@
ORDER BY CREATED_AT DESC ORDER BY CREATED_AT DESC
</select> </select>
<select id="selectValidPermissionsWithName" resultMap="BaseResultMap">
SELECT
s.ID,
s.USER_ID,
s.ORG_TYPE,
s.ORG_ID,
s.PARENT_ID,
s.ORG_LEVEL,
s.PATH,
s.PERMISSION_TYPE,
s.ROLE_CODE,
s.DATA_SCOPE,
s.STATUS,
s.START_TIME,
s.END_TIME,
s.CREATED_AT,
s.CREATED_BY,
s.UPDATED_AT,
s.UPDATED_BY,
s.REMARK,
COALESCE(e.ennm, h.hbrvnm, b.basename, r.rvnm) AS orgName
FROM SYS_USER_DATA_SCOPE s
LEFT JOIN SD_ENGINFO_B_H e ON s.ORG_TYPE = 'STATION' AND s.ORG_ID = e.stcd
LEFT JOIN SD_HBRV_DIC h ON s.ORG_TYPE = 'HBRVCD' AND s.ORG_ID = h.hbrvcd
LEFT JOIN SD_HYDROBASE b ON s.ORG_TYPE = 'BASE' AND s.ORG_ID = b.baseid
LEFT JOIN SD_RVCD_DIC r ON s.ORG_TYPE = 'RVCD' AND s.ORG_ID = r.rvcd
WHERE s.USER_ID = #{userId}
AND s.STATUS = 1
ORDER BY s.CREATED_AT DESC
</select>
<select id="selectValidPermissionsWithNameByUserIds" resultMap="BaseResultMap">
SELECT
s.ID,
s.USER_ID,
s.ORG_TYPE,
s.ORG_ID,
s.PARENT_ID,
s.ORG_LEVEL,
s.PATH,
s.PERMISSION_TYPE,
s.ROLE_CODE,
s.DATA_SCOPE,
s.STATUS,
s.START_TIME,
s.END_TIME,
s.CREATED_AT,
s.CREATED_BY,
s.UPDATED_AT,
s.UPDATED_BY,
s.REMARK,
COALESCE(e.ENNM, h.HBRVNM) AS orgName
FROM SYS_USER_DATA_SCOPE s
LEFT JOIN SD_ENGINFO_B_H e ON s.ORG_TYPE = 'STATION' AND s.ORG_ID = e.stcd
LEFT JOIN SD_HBRV_DIC h ON s.ORG_TYPE = 'HBRVCD' AND s.ORG_ID = h.hbrvcd
WHERE s.USER_ID IN
<foreach collection="userIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND s.STATUS = 1
ORDER BY s.CREATED_AT DESC
</select>
</mapper> </mapper>

View File

@ -231,6 +231,23 @@
<result column="VLSRTM" property="vlsrTm"/> <result column="VLSRTM" property="vlsrTm"/>
</resultMap> </resultMap>
<resultMap id="StationBasinInfoResultMap" type="com.yfd.platform.env.domain.StationBasinInfo">
<result column="STCD" property="stcd"/>
<result column="ENNM" property="ennm"/>
<result column="HBRVCD" property="hbrvcd"/>
<result column="HBRVNM" property="hbrvnm"/>
</resultMap>
<select id="selectStationBasinInfo" resultMap="StationBasinInfoResultMap">
SELECT
e.STCD,
e.ENNM,
e.HBRVCD,
h.HBRVNM
FROM SD_ENGINFO_B_H e
LEFT JOIN SD_HBRV_DIC h ON e.HBRVCD = h.HBRVCD
</select>
<select id="selectByBaseId" resultMap="BaseResultMap"> <select id="selectByBaseId" resultMap="BaseResultMap">
SELECT * FROM SD_ENGINFO_B_H SELECT * FROM SD_ENGINFO_B_H
WHERE BASE_ID = #{baseId} WHERE BASE_ID = #{baseId}

View File

@ -133,6 +133,37 @@
</where> </where>
ORDER BY r."LEVEL" ASC, lastmodifydate ASC ORDER BY r."LEVEL" ASC, lastmodifydate ASC
</select> </select>
<!-- SysRoleMapper.xml -->
<select id="getRolesByUserIds" resultType="com.yfd.platform.system.domain.SysRole">
SELECT r.*
FROM SYS_ROLE r
INNER JOIN SYS_USER_ROLE ur ON r.ID = ur.ROLE_ID
WHERE ur.USER_ID IN
<foreach collection="userIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<select id="getUserRolesByUserIds" resultType="java.util.HashMap">
SELECT
ur.USERID AS "userId",
r.ID AS "id",
r.ROLECODE AS "rolecode",
r.ROLENAME AS "rolename",
r."LEVEL" AS "level",
r.DESCRIPTION AS "description",
r.ISVAILD AS "isvaild",
r.ORGSCOPE AS "orgscope",
r.OPTSCOPE AS "optscope",
r.BUSSCOPE AS "busscope"
FROM
SYS_ROLE r
INNER JOIN SYS_ROLE_USERS ur ON r.ID = ur.ROLEID
WHERE ur.USERID IN
<foreach collection="userIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!--根据 角色id和用户id 删除系统角色用户对照 admin除外--> <!--根据 角色id和用户id 删除系统角色用户对照 admin除外-->
<delete id="deleteRoleUsers"> <delete id="deleteRoleUsers">
delete from sys_role_users where userid !=(select u.id from sys_user u where u.account="admin") and roleid=#{roleid} and userid=#{urserid} delete from sys_role_users where userid !=(select u.id from sys_user u where u.account="admin") and roleid=#{roleid} and userid=#{urserid}