fix: 新增过鱼文件导入接口

This commit is contained in:
tangwei 2026-04-22 10:35:07 +08:00
parent 37e17b6755
commit 10946f8476
9 changed files with 562 additions and 4 deletions

View File

@ -1,6 +1,7 @@
package com.yfd.platform.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.yfd.platform.utils.SecurityUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
@ -13,6 +14,7 @@ import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时自动填充
*/
@ -25,6 +27,11 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
// 自动填充更新时间
this.strictInsertFill(metaObject, "updatedAt", Date.class, now);
// 自动填充更新时间
this.strictInsertFill(metaObject, "createdBy", String.class, SecurityUtils.getUserId());
// 自动填充更新时间
this.strictInsertFill(metaObject, "updatedBy", String.class, SecurityUtils.getUserId());
}
/**
@ -34,5 +41,7 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
public void updateFill(MetaObject metaObject) {
// 自动填充更新时间
this.strictUpdateFill(metaObject, "updatedAt", Date.class, new Date());
// 自动填充更新人
this.strictInsertFill(metaObject, "updatedBy", String.class, SecurityUtils.getUserId());
}
}

View File

@ -5,14 +5,19 @@ import com.yfd.platform.common.DataSourceLoadOptionsBase;
import com.yfd.platform.common.DataSourceRequest;
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.service.IFishDraftDataService;
import com.yfd.platform.data.service.IFishImportService;
import com.yfd.platform.utils.KendoUtil;
import com.yfd.platform.utils.QgcQueryWrapperUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Date;
import java.util.List;
@ -29,6 +34,9 @@ public class FishDraftDataController {
@Resource
private IFishDraftDataService fishDraftDataService;
@Resource
private IFishImportService fishImportService;
// @GetMapping("/page")
// @Operation(summary = "分页查询过鱼数据")
// public ResponseResult queryPageList(
@ -144,8 +152,6 @@ public class FishDraftDataController {
@PostMapping("/add")
@Operation(summary = "新增过鱼数据")
public ResponseResult add(@RequestBody FishDraftData fishDraftData) {
fishDraftData.setCreatedAt(new Date());
fishDraftData.setUpdatedAt(new Date());
boolean result = fishDraftDataService.save(fishDraftData);
return result ? ResponseResult.success("新增成功") : ResponseResult.error("新增失败");
}
@ -153,7 +159,6 @@ public class FishDraftDataController {
@PostMapping("/update")
@Operation(summary = "修改过鱼数据")
public ResponseResult update(@RequestBody FishDraftData fishDraftData) {
fishDraftData.setUpdatedAt(new Date());
boolean result = fishDraftDataService.updateById(fishDraftData);
return result ? ResponseResult.success("修改成功") : ResponseResult.error("修改失败");
}
@ -164,4 +169,39 @@ public class FishDraftDataController {
boolean result = fishDraftDataService.removeById(id);
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) {
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)");
}
try {
FishImportRequest request = new FishImportRequest();
request.setFilePath(file.getOriginalFilename());
request.setImportNo(importNo);
request.setUploadUserId(uploadUserId);
request.setBizType("FISH");
FishImportResult result = fishImportService.parseAndMapExcel(request, file.getInputStream());
return ResponseResult.successData(result);
} catch (IOException e) {
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("请提供文件路径");
}
FishImportResult result = fishImportService.parseAndMapExcelFromPath(request);
return ResponseResult.successData(result);
}
}

View File

@ -31,6 +31,11 @@ public class FishDraftData implements Serializable {
*/
private String stcd;
/**
* 所属基地编码
*/
private String baseId;
/**
* 填报时间
*/
@ -177,6 +182,7 @@ public class FishDraftData implements Serializable {
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
private String createdBy;
/**
@ -188,5 +194,6 @@ public class FishDraftData implements Serializable {
/**
* 更新人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.data.domain;
import lombok.Data;
import java.io.Serializable;
import java.util.Map;
@Data
public class FishImportRequest implements Serializable {
private String filePath;
private String importNo;
private String bizType = "FISH";
private Long uploadUserId;
private Map<String, String> columnMapping;
}

View File

@ -0,0 +1,38 @@
package com.yfd.platform.data.domain;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class FishImportResult {
private List<FishImportRow> successRows;
private List<FishImportRow> failedRows;
private List<String> unrecognizedFields;
private int totalCount;
private int successCount;
private int failedCount;
private String summary;
public FishImportResult() {
this.successRows = new ArrayList<>();
this.failedRows = new ArrayList<>();
this.unrecognizedFields = new ArrayList<>();
}
@Data
public static class FishImportRow {
private int rowIndex;
private FishDraftData data;
private List<String> unrecognizedFields;
private List<String> warnings;
public FishImportRow(int rowIndex) {
this.rowIndex = rowIndex;
this.unrecognizedFields = new ArrayList<>();
this.warnings = new ArrayList<>();
}
}
}

View File

@ -83,7 +83,7 @@ public class ImportTask implements Serializable {
/**
* 上传人ID
*/
private Long uploadUserId;
private String uploadUserId;
/**
* 上传时间

View File

@ -0,0 +1,31 @@
package com.yfd.platform.data.service;
import com.yfd.platform.data.domain.FishImportRequest;
import com.yfd.platform.data.domain.FishImportResult;
import java.io.InputStream;
/**
* <p>
* 过鱼数据Excel导入服务接口
* </p>
*/
public interface IFishImportService {
/**
* 解析过鱼数据Excel文件并映射字段
*
* @param request 导入请求
* @param inputStream Excel文件输入流
* @return 解析结果
*/
FishImportResult parseAndMapExcel(FishImportRequest request, InputStream inputStream);
/**
* 解析过鱼数据Excel文件从文件路径
*
* @param request 导入请求
* @return 解析结果
*/
FishImportResult parseAndMapExcelFromPath(FishImportRequest request);
}

View File

@ -0,0 +1,403 @@
package com.yfd.platform.data.service.impl;
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.env.domain.SdEngInfoBH;
import com.yfd.platform.env.domain.SdHydrobase;
import com.yfd.platform.env.mapper.SdEngInfoBHMapper;
import com.yfd.platform.env.mapper.SdHydrobaseMapper;
import jakarta.annotation.Resource;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class FishImportServiceImpl implements IFishImportService {
@Resource
private SdEngInfoBHMapper engInfoBHMapper;
@Resource
private SdHydrobaseMapper hydrobaseMapper;
private static final Map<String, String> EXCEL_COLUMN_MAPPING = new LinkedHashMap<>();
private static final Map<String, String> STATION_NAME_CACHE = new HashMap<>();
private static final Map<String, String> STATION_CODE_CACHE = new HashMap<>();
private static final Map<String, String> BASE_NAME_CACHE = new HashMap<>();
private static final Map<String, String> BASE_CODE_CACHE = new HashMap<>();
static {
EXCEL_COLUMN_MAPPING.put("电站名称", "stcd");
EXCEL_COLUMN_MAPPING.put("基地名称", "baseId");
EXCEL_COLUMN_MAPPING.put("数据时间", "tm");
EXCEL_COLUMN_MAPPING.put("鱼类", "ftp");
EXCEL_COLUMN_MAPPING.put("鱼类全长", "fsz");
EXCEL_COLUMN_MAPPING.put("过鱼数量", "fcnt");
EXCEL_COLUMN_MAPPING.put("平均体重", "fwet");
EXCEL_COLUMN_MAPPING.put("开始日期", "strdt");
EXCEL_COLUMN_MAPPING.put("结束日期", "enddt");
EXCEL_COLUMN_MAPPING.put("游向", "direction");
EXCEL_COLUMN_MAPPING.put("年份", "yr");
EXCEL_COLUMN_MAPPING.put("月份", "mouth");
EXCEL_COLUMN_MAPPING.put("过鱼视频文件路径", "vdpth");
EXCEL_COLUMN_MAPPING.put("图片文件路径", "picpth");
EXCEL_COLUMN_MAPPING.put("是否鱼苗", "isfs");
EXCEL_COLUMN_MAPPING.put("数据来源", "sourceType");
}
@Override
public FishImportResult parseAndMapExcel(FishImportRequest request, InputStream inputStream) {
FishImportResult result = new FishImportResult();
try (Workbook workbook = new XSSFWorkbook(inputStream)) {
Sheet sheet = workbook.getSheetAt(0);
return parseSheet(sheet, result);
} catch (IOException e) {
result.setSummary("Excel文件解析失败: " + e.getMessage());
return result;
}
}
@Override
public FishImportResult parseAndMapExcelFromPath(FishImportRequest request) {
FishImportResult result = new FishImportResult();
try (InputStream inputStream = request.getFilePath().startsWith("http")
? new java.net.URL(request.getFilePath()).openStream()
: new java.io.FileInputStream(request.getFilePath())) {
return parseAndMapExcel(request, inputStream);
} catch (IOException e) {
result.setSummary("读取文件失败: " + e.getMessage());
return result;
}
}
private FishImportResult parseSheet(Sheet sheet, FishImportResult result) {
loadStationAndBaseCache();
Row headerRow = sheet.getRow(0);
if (headerRow == null) {
result.setSummary("Excel文件没有表头");
return result;
}
Map<Integer, String> columnIndexMap = new HashMap<>();
List<String> unrecognizedHeaders = new ArrayList<>();
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
Cell cell = headerRow.getCell(i);
if (cell != null) {
String headerName = getCellStringValue(cell).trim();
String mappedField = EXCEL_COLUMN_MAPPING.get(headerName);
if (mappedField != null) {
columnIndexMap.put(i, mappedField);
} else {
unrecognizedHeaders.add(headerName);
}
}
}
if (!unrecognizedHeaders.isEmpty()) {
result.setUnrecognizedFields(unrecognizedHeaders);
}
int totalRows = sheet.getLastRowNum();
result.setTotalCount(totalRows);
for (int i = 1; i <= totalRows; i++) {
Row row = sheet.getRow(i);
if (row == null || isRowEmpty(row)) {
continue;
}
FishImportResult.FishImportRow importRow = parseRow(row, columnIndexMap, i);
if (importRow.getData() != null && importRow.getUnrecognizedFields().isEmpty()) {
result.getSuccessRows().add(importRow);
result.setSuccessCount(result.getSuccessCount() + 1);
} else {
result.getFailedRows().add(importRow);
result.setFailedCount(result.getFailedCount() + 1);
}
}
result.setSummary(String.format("共解析%d条数据成功%d条失败%d条未识别字段%s",
result.getTotalCount(), result.getSuccessCount(), result.getFailedCount(),
unrecognizedHeaders.isEmpty() ? "" : String.join(", ", unrecognizedHeaders)));
return result;
}
private FishImportResult.FishImportRow parseRow(Row row, Map<Integer, String> columnIndexMap, int rowIndex) {
FishImportResult.FishImportRow importRow = new FishImportResult.FishImportRow(rowIndex);
FishDraftData data = new FishDraftData();
data.setId(UUID.randomUUID().toString());
List<String> unrecognizedFields = new ArrayList<>();
for (Map.Entry<Integer, String> entry : columnIndexMap.entrySet()) {
Integer columnIndex = entry.getKey();
String fieldName = entry.getValue();
Cell cell = row.getCell(columnIndex);
String cellValue = getCellStringValue(cell);
if (!StringUtils.hasText(cellValue)) {
continue;
}
try {
switch (fieldName) {
case "stcd":
String stcd = resolveStationCode(cellValue.trim());
if (stcd == null) {
importRow.getWarnings().add("无法识别的电站: " + cellValue);
}
data.setStcd(stcd);
break;
case "baseId":
String baseId = resolveBaseCode(cellValue.trim());
if (baseId == null) {
importRow.getWarnings().add("无法识别的基地: " + cellValue);
}
data.setBaseId(baseId);
break;
case "tm":
data.setTm(parseDate(cellValue));
break;
case "ftp":
data.setFtp(cellValue.trim());
break;
case "fsz":
data.setFsz(cellValue.trim());
break;
case "fcnt":
data.setFcnt(parseInteger(cellValue));
break;
case "fwet":
data.setFwet(cellValue.trim());
break;
case "strdt":
data.setStrdt(parseDate(cellValue));
break;
case "enddt":
data.setEnddt(parseDate(cellValue));
break;
case "direction":
data.setDirection(parseDirection(cellValue.trim()));
break;
case "yr":
data.setYr(parseInteger(cellValue));
break;
case "mouth":
data.setMouth(parseInteger(cellValue));
break;
case "vdpth":
data.setVdpth(cellValue.trim());
break;
case "picpth":
data.setPicpth(cellValue.trim());
break;
case "isfs":
data.setIsfs(parseIsfs(cellValue));
break;
case "sourceType":
data.setSourceType(parseSourceType(cellValue.trim()));
break;
default:
break;
}
} catch (Exception e) {
importRow.getWarnings().add("字段[" + fieldName + "]解析异常: " + e.getMessage());
}
}
importRow.setData(data);
importRow.setUnrecognizedFields(unrecognizedFields);
return importRow;
}
private void loadStationAndBaseCache() {
if (STATION_NAME_CACHE.isEmpty()) {
List<SdEngInfoBH> stationList = engInfoBHMapper.selectList(null);
for (SdEngInfoBH station : stationList) {
if (StringUtils.hasText(station.getEnnm())) {
STATION_NAME_CACHE.put(station.getEnnm().trim().toLowerCase(), station.getStcd());
}
if (StringUtils.hasText(station.getStcd())) {
STATION_CODE_CACHE.put(station.getStcd().trim().toLowerCase(), station.getStcd());
}
}
}
if (BASE_NAME_CACHE.isEmpty()) {
List<SdHydrobase> baseList = hydrobaseMapper.selectList(null);
for (SdHydrobase base : baseList) {
if (StringUtils.hasText(base.getBasename())) {
BASE_NAME_CACHE.put(base.getBasename().trim().toLowerCase(), base.getBaseid());
}
if (StringUtils.hasText(base.getBaseid())) {
BASE_CODE_CACHE.put(base.getBaseid().trim().toLowerCase(), base.getBaseid());
}
}
}
}
private String resolveStationCode(String stationName) {
if (stationName == null) {
return null;
}
String lowerName = stationName.toLowerCase().trim();
if (STATION_NAME_CACHE.containsKey(lowerName)) {
return STATION_NAME_CACHE.get(lowerName);
}
for (Map.Entry<String, String> entry : STATION_NAME_CACHE.entrySet()) {
if (entry.getKey().contains(lowerName) || lowerName.contains(entry.getKey())) {
return entry.getValue();
}
}
return null;
}
private String resolveBaseCode(String baseName) {
if (baseName == null) {
return null;
}
String lowerName = baseName.toLowerCase().trim();
if (BASE_NAME_CACHE.containsKey(lowerName)) {
return BASE_NAME_CACHE.get(lowerName);
}
for (Map.Entry<String, String> entry : BASE_NAME_CACHE.entrySet()) {
if (entry.getKey().contains(lowerName) || lowerName.contains(entry.getKey())) {
return entry.getValue();
}
}
return null;
}
private String getCellStringValue(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getLocalDateTimeCellValue().toString();
}
return String.valueOf((long) cell.getNumericCellValue());
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
try {
return cell.getStringCellValue();
} catch (Exception e) {
return String.valueOf(cell.getNumericCellValue());
}
default:
return "";
}
}
private boolean isRowEmpty(Row row) {
for (int i = 0; i < row.getLastCellNum(); i++) {
Cell cell = row.getCell(i);
if (cell != null && StringUtils.hasText(getCellStringValue(cell))) {
return false;
}
}
return true;
}
private Date parseDate(String dateStr) {
if (!StringUtils.hasText(dateStr)) {
return null;
}
String[] patterns = {
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd",
"yyyy/MM/dd",
"yyyy.MM.dd",
"yyyyMMdd"
};
for (String pattern : patterns) {
try {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setLenient(false);
return sdf.parse(dateStr);
} catch (ParseException ignored) {
}
}
return null;
}
private Integer parseInteger(String value) {
if (!StringUtils.hasText(value)) {
return null;
}
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
try {
return (int) Double.parseDouble(value.trim());
} catch (NumberFormatException ex) {
return null;
}
}
}
private String parseDirection(String direction) {
if (!StringUtils.hasText(direction)) {
return null;
}
String d = direction.toLowerCase();
if (d.contains("上行") || d.contains("up")) {
return "上行";
} else if (d.contains("下行") || d.contains("down")) {
return "下行";
} else if (d.contains("折返")) {
if (d.contains("上行")) {
return "上行折返";
} else if (d.contains("下行")) {
return "下行折返";
}
return "折返";
}
return direction;
}
private Integer parseIsfs(String value) {
if (!StringUtils.hasText(value)) {
return 0;
}
String v = value.toLowerCase().trim();
if (v.equals("1") || v.equals("") || v.equals("yes") || v.equals("鱼苗")) {
return 1;
}
return 0;
}
private String parseSourceType(String value) {
if (!StringUtils.hasText(value)) {
return "IMPORT";
}
String v = value.toLowerCase();
if (v.contains("手工") || v.contains("manual") || v.contains("录入")) {
return "MANUAL";
} else if (v.contains("自动") || v.contains("auto")) {
return "AUTO";
} else if (v.contains("导入") || v.contains("import")) {
return "IMPORT";
}
return "IMPORT";
}
}

View File

@ -71,6 +71,16 @@ public class SecurityUtils {
return new JSONObject(new JSONObject(userDetails).get("user")).get("id", Long.class);
}
/**
* 获取系统用户ID
* @return 系统用户ID
*/
public static String getUserId() {
UserDetails userDetails = getCurrentUser();
return new JSONObject(new JSONObject(userDetails).get("user")).get("id", String.class);
}
/**
* 获取当前用户的数据权限
* @return /