fix: 水温站导入功能

This commit is contained in:
tangwei 2026-09-09 12:04:58 +08:00
parent 8e5a76e644
commit e62af4f460
4 changed files with 242 additions and 0 deletions

View File

@ -13,8 +13,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
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.Map;
@RestController @RestController
@RequestMapping("/base/wt") @RequestMapping("/base/wt")
@ -62,4 +64,12 @@ public class SdWtBHController {
Page<SdWtBH> page = service.queryPageList(request); Page<SdWtBH> page = service.queryPageList(request);
new ExcelUtil<>(SdWtBH.class).exportExcel(response, page.getRecords(), "水温站数据", "水温站导出"); new ExcelUtil<>(SdWtBH.class).exportExcel(response, page.getRecords(), "水温站数据", "水温站导出");
} }
@PostMapping("/import")
@Operation(summary = "水温站导入")
public ResponseResult importData(@RequestParam("file") MultipartFile file,
@RequestParam(value = "source", required = false) String source) throws Exception {
Map<String, Object> result = service.importData(file, source);
return ResponseResult.successData(result);
}
} }

View File

@ -4,6 +4,7 @@ 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.common.DataSourceRequest; import com.yfd.platform.common.DataSourceRequest;
import com.yfd.platform.qgc_base.domain.SdWtBH; import com.yfd.platform.qgc_base.domain.SdWtBH;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -14,4 +15,12 @@ public interface ISdWtBHService extends IService<SdWtBH> {
boolean update(SdWtBH entity, String source); boolean update(SdWtBH entity, String source);
boolean update(Map<String, Object> engInfoPatch, String source) throws Exception; boolean update(Map<String, Object> engInfoPatch, String source) throws Exception;
boolean delete(List<String> stcds, String source); boolean delete(List<String> stcds, String source);
/**
* Excel 文件批量导入水温站数据逐条走 add()含站码唯一校验与操作日志
*
* @param file 上传的 Excel 文件
* @param source 数据来源
* @return 导入结果汇总total/success/fail/errors
*/
Map<String, Object> importData(MultipartFile file, String source) throws Exception;
} }

View File

@ -15,6 +15,7 @@ import com.yfd.platform.utils.*;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
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.web.multipart.MultipartFile;
import java.util.*; import java.util.*;
@ -130,4 +131,34 @@ public class SdWtBHServiceImpl extends ServiceImpl<SdWtBHMapper, SdWtBH> impleme
} }
return count > 0; return count > 0;
} }
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> importData(MultipartFile file, String source) throws Exception {
List<SdWtBH> list = new ExcelUtil<>(SdWtBH.class).importExcel(file);
Map<String, Object> result = new HashMap<>();
result.put("total", list.size());
int batchSize = 500;
// 过滤无站码的行stcd 为主键不能为空
List<SdWtBH> validList = new ArrayList<>();
List<String> errors = new ArrayList<>();
for (SdWtBH entity : list) {
if (StrUtil.isBlank(entity.getStcd())) {
errors.add("存在缺少站码的行,已跳过");
continue;
}
validList.add(entity);
}
int success = 0;
for (int i = 0; i < validList.size(); i += batchSize) {
List<SdWtBH> sub = validList.subList(i, Math.min(i + batchSize, validList.size()));
if (this.saveBatch(sub, batchSize)) {
success += sub.size();
}
}
result.put("success", success);
result.put("fail", list.size() - success);
result.put("errors", errors);
return result;
}
} }

View File

@ -6,6 +6,8 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.HorizontalAlignment;
@ -13,24 +15,33 @@ import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor; import org.apache.poi.xssf.usermodel.XSSFColor;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -105,6 +116,67 @@ public class ExcelUtil<T> {
} }
} }
/**
* 从上传的 Excelxlsx导入数据读取首个工作表按表头{@link Excel#name()}匹配列
* 逐行转换为实体对象表头列顺序无要求只按列名匹配
*
* @param file 上传的 Excel 文件
* @return 解析出的实体列表
*/
public List<T> importExcel(MultipartFile file) throws Exception {
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("导入文件不能为空");
}
List<T> list = new ArrayList<>();
try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = workbook.getSheetAt(0);
Row headerRow = sheet.getRow(0);
if (headerRow == null) {
throw new IllegalArgumentException("Excel文件没有表头");
}
// 表头名 -> 列号
Map<String, Integer> headerColMap = new HashMap<>();
for (int c = 0; c < headerRow.getLastCellNum(); c++) {
String name = getStringCellValue(headerRow.getCell(c));
if (name != null && !name.isBlank()) {
headerColMap.put(name.trim(), c);
}
}
// 列号 -> 实体字段仅匹配参与导出的 @Excel 字段
Map<Integer, Field> colFieldMap = new HashMap<>();
for (Field field : exportFields) {
Integer col = headerColMap.get(field.getAnnotation(Excel.class).name());
if (col != null) {
colFieldMap.put(col, field);
}
}
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (isRowEmpty(row)) {
continue;
}
T entity = clazz.getDeclaredConstructor().newInstance();
boolean hasValue = false;
for (Map.Entry<Integer, Field> entry : colFieldMap.entrySet()) {
Cell cell = row.getCell(entry.getKey());
if (cell == null) {
continue;
}
Excel excel = entry.getValue().getAnnotation(Excel.class);
Object value = convertFromCell(excel, entry.getValue(), cell);
if (value != null) {
setFieldValue(entry.getValue(), entity, value);
hasValue = true;
}
}
if (hasValue) {
list.add(entity);
}
}
}
return list;
}
// ==================== 内部实现 ==================== // ==================== 内部实现 ====================
private void writeSheet(SXSSFWorkbook workbook, String sheetName, List<T> list) { private void writeSheet(SXSSFWorkbook workbook, String sheetName, List<T> list) {
@ -285,4 +357,124 @@ public class ExcelUtil<T> {
String safe = name == null || name.isBlank() ? "Sheet1" : name.replaceAll("[\\[\\]:*?/\\\\]", "_"); String safe = name == null || name.isBlank() ? "Sheet1" : name.replaceAll("[\\[\\]:*?/\\\\]", "_");
return safe.length() > 31 ? safe.substring(0, 31) : safe; return safe.length() > 31 ? safe.substring(0, 31) : safe;
} }
/**
* 读取单元格文本值空白单元格返回 null
*/
private String getStringCellValue(Cell cell) {
if (cell == null) {
return null;
}
return switch (cell.getCellType()) {
case STRING -> {
String v = cell.getStringCellValue();
yield v == null || v.isBlank() ? null : v.trim();
}
case NUMERIC -> {
if (DateUtil.isCellDateFormatted(cell)) {
yield new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cell.getDateCellValue());
}
double d = cell.getNumericCellValue();
yield d == Math.floor(d) ? String.valueOf((long) d) : String.valueOf(d);
}
case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
case FORMULA -> {
try {
yield cell.getStringCellValue();
} catch (Exception e) {
yield String.valueOf(cell.getNumericCellValue());
}
}
default -> null;
};
}
/**
* 判断整行是否无有效数据
*/
private boolean isRowEmpty(Row row) {
if (row == null) {
return true;
}
for (int i = 0; i < row.getLastCellNum(); i++) {
if (getStringCellValue(row.getCell(i)) != null) {
return false;
}
}
return true;
}
/**
* 将单元格值转换为目标实体字段类型String/数值/日期/Boolean
*/
private Object convertFromCell(Excel excel, Field field, Cell cell) {
if (cell == null) {
return null;
}
Class<?> type = field.getType();
if (type == String.class) {
return getStringCellValue(cell);
}
if (type == BigDecimal.class || type == Integer.class || type == Long.class
|| type == Double.class || type == Float.class || type == Short.class) {
String s = getStringCellValue(cell);
if (s == null) {
return null;
}
try {
if (type == BigDecimal.class) return new BigDecimal(s);
if (type == Integer.class) return Integer.valueOf(s);
if (type == Long.class) return Long.valueOf(s);
if (type == Double.class) return Double.valueOf(s);
if (type == Float.class) return Float.valueOf(s);
if (type == Short.class) return Short.valueOf(s);
} catch (NumberFormatException e) {
log.warn("Excel 导入数值转换失败: col={}, value={}", excel.name(), s);
return null;
}
}
if (type == Date.class || type == LocalDate.class || type == LocalDateTime.class) {
if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
if (type == Date.class) return date;
if (type == LocalDate.class) return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
String s = getStringCellValue(cell);
if (s == null) {
return null;
}
try {
SimpleDateFormat sdf = new SimpleDateFormat(excel.dateFormat());
sdf.setLenient(false);
Date date = sdf.parse(s);
if (type == Date.class) return date;
if (type == LocalDate.class) return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
} catch (ParseException e) {
log.warn("Excel 导入日期转换失败: col={}, value={}, format={}", excel.name(), s, excel.dateFormat());
return null;
}
}
if (type == Boolean.class) {
if (cell.getCellType() == CellType.BOOLEAN) {
return cell.getBooleanCellValue();
}
String s = getStringCellValue(cell);
if (s == null) {
return null;
}
return "1".equals(s) || "true".equalsIgnoreCase(s) || "".equals(s);
}
return null;
}
private void setFieldValue(Field field, Object entity, Object value) {
try {
field.setAccessible(true);
field.set(entity, value);
} catch (IllegalAccessException e) {
log.warn("Excel 导入设置字段值失败: field={}, err={}", field.getName(), e.getMessage());
}
}
} }