fix: 导入过鱼数据初版

This commit is contained in:
tangwei 2026-04-22 15:49:09 +08:00
parent 5854c1b65b
commit bac8bd9b25
11 changed files with 441 additions and 48 deletions

View File

@ -7,8 +7,10 @@ import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.data.domain.FishDraftData;
import com.yfd.platform.data.domain.FishImportRequest;
import com.yfd.platform.data.domain.FishImportResult;
import com.yfd.platform.data.domain.ImportTask;
import com.yfd.platform.data.service.IFishDraftDataService;
import com.yfd.platform.data.service.IFishImportService;
import com.yfd.platform.data.service.IImportTaskService;
import com.yfd.platform.utils.KendoUtil;
import com.yfd.platform.utils.QgcQueryWrapperUtil;
import io.swagger.v3.oas.annotations.Operation;
@ -20,6 +22,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
* <p>
@ -37,18 +40,8 @@ public class FishDraftDataController {
@Resource
private IFishImportService fishImportService;
// @GetMapping("/page")
// @Operation(summary = "分页查询过鱼数据")
// public ResponseResult queryPageList(
// @RequestParam(defaultValue = "1") Integer current,
// @RequestParam(defaultValue = "10") Integer size,
// @RequestParam(required = false) String stcd,
// @RequestParam(required = false) String status,
// @RequestParam(required = false) String ftp) {
// Page<FishDraftData> page = new Page<>(current, size);
// Page<FishDraftData> result = fishDraftDataService.queryPageList(page, stcd, status, ftp);
// return ResponseResult.successData(result);
// }
@Resource
private IImportTaskService importTaskService;
@PostMapping("/page")
@Operation(summary = "分页查询过鱼数据")
@ -71,7 +64,7 @@ public class FishDraftDataController {
@GetMapping("/getById")
@Operation(summary = "根据ID查询")
public ResponseResult getById(@RequestParam Long id) {
public ResponseResult getById(@RequestParam String id) {
FishDraftData fishDraftData = fishDraftDataService.getById(id);
return ResponseResult.successData(fishDraftData);
}
@ -177,38 +170,82 @@ public class FishDraftDataController {
return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败");
}
@PostMapping("/importExcel")
@Operation(summary = "导入Excel过鱼数据")
public ResponseResult importExcel(@RequestParam("file") MultipartFile file,
@RequestParam(required = false) String importNo,
@RequestParam(required = false) Long uploadUserId) {
@PostMapping("/importZip")
@Operation(summary = "导入ZIP过鱼数据每个用户同时只能进行一次导入")
public ResponseResult importZip(@RequestParam("file") MultipartFile file,
@RequestParam String uploadUserId) {
if (file == null || file.isEmpty()) {
return ResponseResult.error("请上传文件");
}
String fileName = file.getOriginalFilename();
if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
return ResponseResult.error("请上传Excel文件(.xlsx或.xls)");
if (fileName == null || (!fileName.endsWith(".zip"))) {
return ResponseResult.error("请上传ZIP文件(.zip)");
}
if (importTaskService.hasImportingTask(uploadUserId)) {
return ResponseResult.error("您有正在进行的导入任务,请等待完成后重试");
}
String importNo = "IMP" + System.currentTimeMillis();
String taskId = UUID.randomUUID().toString();
try {
ImportTask task = new ImportTask();
task.setId(taskId);
task.setImportNo(importNo);
task.setBizType("FISH");
task.setFileName(fileName);
task.setFileSize(file.getSize());
task.setStatus("UPLOADED");
task.setUploadUserId(uploadUserId);
task.setUploadTime(new Date());
importTaskService.save(task);
FishImportRequest request = new FishImportRequest();
request.setFilePath(file.getOriginalFilename());
request.setImportNo(importNo);
request.setUploadUserId(uploadUserId);
request.setBizType("FISH");
FishImportResult result = fishImportService.parseAndMapExcel(request, file.getInputStream());
FishImportResult result = fishImportService.parseAndMapZip(file, uploadUserId);
importTaskService.updateStatus(taskId, "VALIDATED", null);
importTaskService.updateProgress(taskId, result.getTotalCount(), result.getSuccessCount(), result.getFailedCount());
return ResponseResult.successData(result);
} catch (IOException e) {
return ResponseResult.error("文件读取失败: " + e.getMessage());
} catch (Exception e) {
importTaskService.markFailed(taskId, "导入失败: " + e.getMessage());
return ResponseResult.error("导入失败: " + e.getMessage());
}
}
@PostMapping("/importExcelFromPath")
@Operation(summary = "从文件路径导入Excel过鱼数据")
public ResponseResult importExcelFromPath(@RequestBody FishImportRequest request) {
if (request.getFilePath() == null || request.getFilePath().isEmpty()) {
return ResponseResult.error("请提供文件路径");
@PostMapping("/cancelImport")
@Operation(summary = "取消导入任务")
public ResponseResult cancelImport(@RequestParam String taskId,
@RequestParam String operatorId) {
boolean result = importTaskService.cancelTask(taskId, operatorId);
return result ? ResponseResult.success("取消成功") : ResponseResult.error("取消失败");
}
@GetMapping("/checkImportStatus")
@Operation(summary = "检查用户导入状态")
public ResponseResult checkImportStatus(@RequestParam String uploadUserId) {
boolean hasTask = importTaskService.hasImportingTask(uploadUserId);
ImportTask currentTask = importTaskService.getCurrentTaskByUserId(uploadUserId);
return ResponseResult.successData(java.util.Map.of(
"hasImportingTask", hasTask,
"currentTask", currentTask
));
}
@PostMapping("/cleanupTemp")
@Operation(summary = "清理临时文件")
public ResponseResult cleanupTemp(@RequestParam String tempDir) {
try {
com.yfd.platform.data.utils.ZipFileUtil.cleanupTempDir(tempDir);
return ResponseResult.success("清理成功");
} catch (Exception e) {
return ResponseResult.error("清理失败: " + e.getMessage());
}
FishImportResult result = fishImportService.parseAndMapExcelFromPath(request);
return ResponseResult.successData(result);
}
}

View File

@ -14,7 +14,7 @@ public class FishImportRequest implements Serializable {
private String bizType = "FISH";
private Long uploadUserId;
private String uploadUserId;
private Map<String, String> columnMapping;
}

View File

@ -3,7 +3,9 @@ package com.yfd.platform.data.domain;
import lombok.Data;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Data
public class FishImportResult {
@ -11,6 +13,11 @@ public class FishImportResult {
private List<FishImportRow> successRows;
private List<FishImportRow> failedRows;
private List<String> unrecognizedFields;
private Map<String, String> imageFiles;
private Map<String, String> videoFiles;
private String tempDir;
private String excelFileName;
private String excelFilePath;
private int totalCount;
private int successCount;
private int failedCount;
@ -20,6 +27,8 @@ public class FishImportResult {
this.successRows = new ArrayList<>();
this.failedRows = new ArrayList<>();
this.unrecognizedFields = new ArrayList<>();
this.imageFiles = new LinkedHashMap<>();
this.videoFiles = new LinkedHashMap<>();
}
@Data

View File

@ -36,7 +36,17 @@ public interface ImportTaskMapper extends BaseMapper<ImportTask> {
* 根据上传人查询
*/
@Select("SELECT * FROM IMPORT_TASK WHERE UPLOAD_USER_ID = #{uploadUserId} ORDER BY CREATED_AT DESC")
List<ImportTask> selectByUploadUserId(@Param("uploadUserId") Long uploadUserId);
List<ImportTask> selectByUploadUserId(@Param("uploadUserId") String uploadUserId);
@Select("<script>" +
"SELECT * FROM IMPORT_TASK WHERE UPLOAD_USER_ID = #{uploadUserId} AND STATUS IN " +
"<foreach item='status' collection='statuses' open='(' separator=',' close=')'>" +
"#{status}" +
"</foreach>" +
" ORDER BY CREATED_AT DESC" +
"</script>")
List<ImportTask> selectByUserIdAndStatuses(@Param("uploadUserId") String uploadUserId,
@Param("statuses") List<String> statuses);
/**
* 查询过期的任务

View File

@ -2,6 +2,7 @@ package com.yfd.platform.data.service;
import com.yfd.platform.data.domain.FishImportRequest;
import com.yfd.platform.data.domain.FishImportResult;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
@ -12,20 +13,9 @@ import java.io.InputStream;
*/
public interface IFishImportService {
/**
* 解析过鱼数据Excel文件并映射字段
*
* @param request 导入请求
* @param inputStream Excel文件输入流
* @return 解析结果
*/
FishImportResult parseAndMapExcel(FishImportRequest request, InputStream inputStream);
/**
* 解析过鱼数据Excel文件从文件路径
*
* @param request 导入请求
* @return 解析结果
*/
FishImportResult parseAndMapExcelFromPath(FishImportRequest request);
FishImportResult parseAndMapZip(MultipartFile file, String uploadUserId);
}

View File

@ -0,0 +1,40 @@
package com.yfd.platform.data.service;
import com.yfd.platform.data.domain.FishImportResult;
import java.io.InputStream;
/**
* <p>
* 过鱼数据ZIP导入服务接口
* </p>
*/
public interface IFishZipImportService {
/**
* 解析过鱼数据ZIP文件并映射字段
*
* @param importTaskId 导入任务ID
* @param inputStream ZIP文件输入流
* @param uploadUserId 上传用户ID
* @return 解析结果
*/
FishImportResult parseAndMapZip(String importTaskId, InputStream inputStream, String uploadUserId);
/**
* 检查用户是否有正在进行的导入任务
*
* @param uploadUserId 上传用户ID
* @return true表示有任务在进行false表示可以开始新任务
*/
boolean hasImportingTask(String uploadUserId);
/**
* 取消导入任务
*
* @param importTaskId 任务ID
* @param operatorId 操作人ID
* @return 是否成功
*/
boolean cancelImportTask(String importTaskId, String operatorId);
}

View File

@ -62,4 +62,19 @@ public interface IImportTaskService extends IService<ImportTask> {
* 删除过期任务
*/
boolean deleteExpiredTasks();
/**
* 检查用户是否有正在进行的导入任务
*/
boolean hasImportingTask(String uploadUserId);
/**
* 取消导入任务
*/
boolean cancelTask(String taskId, String operatorId);
/**
* 获取用户当前正在进行的导入任务
*/
ImportTask getCurrentTaskByUserId(String uploadUserId);
}

View File

@ -4,6 +4,7 @@ import com.yfd.platform.data.domain.FishDraftData;
import com.yfd.platform.data.domain.FishImportRequest;
import com.yfd.platform.data.domain.FishImportResult;
import com.yfd.platform.data.service.IFishImportService;
import com.yfd.platform.data.utils.ZipFileUtil;
import com.yfd.platform.env.domain.SdEngInfoBH;
import com.yfd.platform.env.domain.SdHydrobase;
import com.yfd.platform.env.mapper.SdEngInfoBHMapper;
@ -13,7 +14,10 @@ import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
@ -400,4 +404,39 @@ public class FishImportServiceImpl implements IFishImportService {
}
return "IMPORT";
}
@Override
public FishImportResult parseAndMapZip(MultipartFile file, String uploadUserId) {
FishImportResult result = new FishImportResult();
try {
ZipFileUtil.ZipContent zipContent = ZipFileUtil.extractZipToTemp(file);
if (zipContent.excelFilePath == null) {
result.setSummary("ZIP文件中未找到Excel文件");
ZipFileUtil.cleanupTempDir(zipContent.tempDir);
return result;
}
try (Workbook workbook = new XSSFWorkbook(new FileInputStream(zipContent.excelFilePath))) {
Sheet sheet = workbook.getSheetAt(0);
result = parseSheet(sheet, result);
}
result.setImageFiles(zipContent.images);
result.setVideoFiles(zipContent.videos);
result.setTempDir(zipContent.tempDir);
result.setExcelFileName(zipContent.excelFileName);
result.setExcelFilePath(zipContent.excelFilePath);
result.setSummary(result.getSummary() + String.format("\nZIP内容: 发现%d张图片, %d个视频, 临时目录: %s",
zipContent.images.size(), zipContent.videos.size(), zipContent.tempDir));
return result;
} catch (Exception e) {
result.setSummary("ZIP文件解析失败: " + e.getMessage());
return result;
}
}
}

View File

@ -137,4 +137,38 @@ public class ImportTaskServiceImpl extends ServiceImpl<ImportTaskMapper, ImportT
List<String> ids = expiredTasks.stream().map(ImportTask::getId).toList();
return this.removeByIds(ids);
}
@Override
public boolean hasImportingTask(String uploadUserId) {
List<String> activeStatuses = List.of("UPLOADED", "PARSING", "VALIDATING");
List<ImportTask> tasks = importTaskMapper.selectByUserIdAndStatuses(uploadUserId, activeStatuses);
return tasks != null && !tasks.isEmpty();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean cancelTask(String taskId, String operatorId) {
ImportTask importTask = this.getById(taskId);
if (importTask == null) {
return false;
}
String currentStatus = importTask.getStatus();
if ("CONFIRMED".equals(currentStatus) || "FAILED".equals(currentStatus) || "CANCELLED".equals(currentStatus)) {
return false;
}
importTask.setStatus("CANCELLED");
importTask.setErrorMsg("用户取消: " + operatorId);
importTask.setUpdatedAt(new Date());
return this.updateById(importTask);
}
@Override
public ImportTask getCurrentTaskByUserId(String uploadUserId) {
List<String> activeStatuses = List.of("UPLOADED", "PARSING", "VALIDATING");
List<ImportTask> tasks = importTaskMapper.selectByUserIdAndStatuses(uploadUserId, activeStatuses);
if (tasks == null || tasks.isEmpty()) {
return null;
}
return tasks.get(0);
}
}

View File

@ -0,0 +1,219 @@
package com.yfd.platform.data.utils;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
public class ZipFileUtil {
public static final String DEFAULT_TEMP_BASE = "D:\\zip_import_temp";
public static class ZipContent {
public String excelFileName;
public String excelFilePath;
public Map<String, String> images;
public Map<String, String> videos;
public Map<String, String> otherFiles;
public String tempDir;
public ZipContent() {
this.images = new HashMap<>();
this.videos = new HashMap<>();
this.otherFiles = new HashMap<>();
}
public InputStream getExcelStream() throws IOException {
if (excelFilePath == null) {
return null;
}
return new FileInputStream(excelFilePath);
}
}
public static String getDefaultTempDir() {
return DEFAULT_TEMP_BASE;
}
public static ZipContent extractZipToTemp(MultipartFile file) throws IOException {
return extractZipToTemp(file, DEFAULT_TEMP_BASE);
}
public static ZipContent extractZipToTemp(MultipartFile file, String baseTempDir) throws IOException {
ZipContent content = new ZipContent();
String taskId = UUID.randomUUID().toString().substring(0, 8);
Path tempDirPath = Paths.get(baseTempDir, "zip_" + taskId);
Files.createDirectories(tempDirPath);
content.tempDir = tempDirPath.toString();
File zipFile = new File(tempDirPath.toFile(), "upload.zip");
file.transferTo(zipFile);
try {
if (!isValidZipFile(zipFile)) {
throw new IOException("文件不是有效的ZIP格式或已损坏");
}
IOException lastException = null;
Charset[] charsets = new Charset[]{
StandardCharsets.UTF_8,
Charset.forName("GBK"),
StandardCharsets.ISO_8859_1,
Charset.forName("GB18030")
};
for (Charset charset : charsets) {
try {
content = extractFromZipFile(zipFile, tempDirPath.toFile(), charset);
content.tempDir = tempDirPath.toString();
return content;
} catch (IOException e) {
lastException = e;
content = new ZipContent();
content.tempDir = tempDirPath.toString();
}
}
throw lastException != null ? lastException : new IOException("无法解析ZIP文件");
} finally {
if (zipFile.exists()) {
zipFile.delete();
}
}
}
private static boolean isValidZipFile(File file) {
if (file == null || file.length() < 4) {
return false;
}
try (FileInputStream fis = new FileInputStream(file)) {
byte[] signature = new byte[4];
int read = fis.read(signature);
if (read != 4) {
return false;
}
return signature[0] == 0x50 && signature[1] == 0x4B &&
signature[2] == 0x03 && signature[3] == 0x04;
} catch (IOException e) {
return false;
}
}
private static ZipContent extractFromZipFile(File zipFile, File tempDir, Charset charset) throws IOException {
ZipContent content = new ZipContent();
try (ZipFile zip = new ZipFile(zipFile, charset)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entry.isDirectory()) {
continue;
}
try (InputStream is = zip.getInputStream(entry)) {
String lowerName = entryName.toLowerCase();
if (lowerName.endsWith(".xlsx") || lowerName.endsWith(".xls")) {
String excelPath = saveFileToDir(is, tempDir, "excel", getFileName(entryName));
content.excelFileName = getFileName(entryName);
content.excelFilePath = excelPath;
} else if (isImageFile(lowerName)) {
String folder = "images";
String path = saveFileToDir(is, tempDir, folder, getFileName(entryName));
content.images.put(entryName, path);
} else if (isVideoFile(lowerName)) {
String folder = "videos";
String path = saveFileToDir(is, tempDir, folder, getFileName(entryName));
content.videos.put(entryName, path);
} else {
String folder = "others";
String path = saveFileToDir(is, tempDir, folder, getFileName(entryName));
content.otherFiles.put(entryName, path);
}
}
}
}
return content;
}
private static String getFileName(String entryName) {
if (entryName == null) return "";
int lastSep = Math.max(entryName.lastIndexOf('/'), entryName.lastIndexOf('\\'));
return lastSep >= 0 ? entryName.substring(lastSep + 1) : entryName;
}
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) {
return fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") ||
fileName.endsWith(".png") || fileName.endsWith(".gif") ||
fileName.endsWith(".bmp") || fileName.endsWith(".webp");
}
private static boolean isVideoFile(String fileName) {
return fileName.endsWith(".mp4") || fileName.endsWith(".avi") ||
fileName.endsWith(".mov") || fileName.endsWith(".wmv") ||
fileName.endsWith(".flv") || fileName.endsWith(".mkv");
}
public static void cleanupTempDir(String tempDir) {
if (tempDir == null || tempDir.isEmpty()) {
return;
}
File dir = new File(tempDir);
if (dir.exists()) {
deleteDirectory(dir);
}
}
private static void deleteDirectory(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDirectory(file);
} else {
file.delete();
}
}
}
dir.delete();
}
public static String getFileExtension(String fileName) {
if (fileName == null) return "";
int lastDot = fileName.lastIndexOf('.');
return lastDot >= 0 ? fileName.substring(lastDot + 1).toLowerCase() : "";
}
}

View File

@ -58,8 +58,8 @@ mybatis-plus:
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
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 登录相关配置