解决导入时报错的问题

This commit is contained in:
2026-04-25 15:21:55 +08:00
parent 4ce1cf326e
commit bdaca758c2
3 changed files with 229 additions and 16 deletions

View File

@@ -250,9 +250,11 @@ public class LbDailyUserTradeController {
@Parameter(description = "Excel文件", required = true)
@RequestParam("file") MultipartFile file,
@Parameter(description = "默认报表日期格式yyyy-MM-dd当Excel行里无报表日期时生效")
@RequestParam(required = false) String defaultReportDate) {
@RequestParam(required = false) String defaultReportDate,
@Parameter(description = "租户ID必填导入时写入每行记录", required = true)
@RequestParam String tenantId) {
try {
Map<String, Object> result = lbDailyUserTradeService.importFromExcel(file, defaultReportDate);
Map<String, Object> result = lbDailyUserTradeService.importFromExcel(file, defaultReportDate, tenantId);
Boolean success = (Boolean) result.get("success");
if (Boolean.TRUE.equals(success)) {
return ResponseEntity.ok(result);

View File

@@ -13,5 +13,5 @@ import java.util.Map;
*/
public interface ILbDailyUserTradeService extends IService<LbDailyUserTrade> {
Map<String, Object> importFromExcel(MultipartFile file, String defaultReportDate);
Map<String, Object> importFromExcel(MultipartFile file, String defaultReportDate, String tenantId);
}

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rj.entity.LbDailyUserTrade;
import com.rj.mapper.LbDailyUserTradeMapper;
import com.rj.service.ILbDailyUserTradeService;
import org.apache.poi.poifs.filesystem.FileMagic;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormatter;
@@ -12,12 +13,19 @@ import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -35,19 +43,26 @@ import java.util.UUID;
*/
@Service
public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMapper, LbDailyUserTrade> implements ILbDailyUserTradeService {
private static final Logger log = LoggerFactory.getLogger(LbDailyUserTradeServiceImpl.class);
@Override
public Map<String, Object> importFromExcel(MultipartFile file, String defaultReportDate) {
public Map<String, Object> importFromExcel(MultipartFile file, String defaultReportDate, String tenantId) {
Map<String, Object> result = new HashMap<>();
if (file == null || file.isEmpty()) {
result.put("success", false);
result.put("message", "上传文件不能为空");
return result;
}
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".xlsx")) {
if (StringUtils.isBlank(tenantId)) {
result.put("success", false);
result.put("message", "仅支持 .xlsx 文件");
result.put("message", "tenantId不能为空");
return result;
}
String normalizedTenantId = tenantId.trim();
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || !isSupportedExcelSuffix(originalFilename)) {
result.put("success", false);
result.put("message", "仅支持 .xlsx 或 .xls 文件");
return result;
}
@@ -65,7 +80,9 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
List<LbDailyUserTrade> importList = new ArrayList<>();
List<String> skippedRows = new ArrayList<>();
DataFormatter dataFormatter = new DataFormatter();
try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) {
log.info("开始导入ExcelfileName={}, size={} bytes, defaultReportDate={}",
originalFilename, file.getSize(), defaultReportDate);
try (Workbook workbook = openWorkbook(file)) {
Sheet sheet = workbook.getSheetAt(0);
if (sheet == null) {
result.put("success", false);
@@ -76,7 +93,7 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
int headerRowNum = findHeaderRow(sheet, dataFormatter);
if (headerRowNum < 0) {
result.put("success", false);
result.put("message", "未识别到表头,请确认模板包含:租户ID、用户ID、单日卖出金额、当日买入金额等字段");
result.put("message", "未识别到表头,请确认模板至少包含用户ID、单日卖出金额、当日买入金额等字段");
return result;
}
@@ -87,20 +104,38 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
continue;
}
try {
LbDailyUserTrade trade = parseTradeRow(row, headerIndexMap, dataFormatter, fallbackReportDate);
LbDailyUserTrade trade = parseTradeRow(row, headerIndexMap, dataFormatter, fallbackReportDate, normalizedTenantId);
if (trade == null) {
skippedRows.add(String.valueOf(i + 1));
continue;
}
importList.add(trade);
} catch (Exception ex) {
log.warn("解析Excel行失败rowNum={}, message={}", i + 1, ex.getMessage(), ex);
skippedRows.add((i + 1) + "(" + ex.getMessage() + ")");
}
}
log.info("Excel解析完成fileName={}, parsedCount={}, skippedCount={}",
originalFilename, importList.size(), skippedRows.size());
if (importList.isEmpty()) {
log.warn("Excel解析后无有效数据fileName={}, tenantId={}, skippedRows={}",
originalFilename, normalizedTenantId, skippedRows);
}
} catch (IOException e) {
log.error("读取Excel失败fileName={}, message={}", originalFilename, e.getMessage(), e);
result.put("success", false);
result.put("message", "读取Excel失败" + e.getMessage());
return result;
} catch (IllegalArgumentException e) {
log.error("Excel格式校验失败fileName={}, message={}", originalFilename, e.getMessage(), e);
result.put("success", false);
result.put("message", e.getMessage());
return result;
} catch (Exception e) {
log.error("解析Excel异常fileName={}, message={}", originalFilename, e.getMessage(), e);
result.put("success", false);
result.put("message", "解析Excel异常" + e.getMessage());
return result;
}
if (importList.isEmpty()) {
@@ -112,11 +147,14 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
boolean saved = this.saveBatch(importList);
if (!saved) {
log.error("导入数据保存失败fileName={}, importCount={}", originalFilename, importList.size());
result.put("success", false);
result.put("message", "导入失败,数据库保存异常");
return result;
}
log.info("导入成功fileName={}, importCount={}, skipCount={}",
originalFilename, importList.size(), skippedRows.size());
result.put("success", true);
result.put("message", "导入成功");
result.put("importCount", importList.size());
@@ -133,7 +171,7 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
continue;
}
Map<String, Integer> headerMap = buildHeaderIndexMap(row, formatter);
if (headerMap.containsKey("tenantId") && headerMap.containsKey("userId")) {
if (headerMap.containsKey("userId")) {
return i;
}
}
@@ -179,10 +217,21 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
return null;
}
private LbDailyUserTrade parseTradeRow(Row row, Map<String, Integer> headerMap, DataFormatter formatter, LocalDate fallbackReportDate) {
String tenantId = readString(row, headerMap.get("tenantId"), formatter);
private LbDailyUserTrade parseTradeRow(Row row,
Map<String, Integer> headerMap,
DataFormatter formatter,
LocalDate fallbackReportDate,
String defaultTenantId) {
String tenantId = defaultTenantId == null ? "" : defaultTenantId;
String userId = readString(row, headerMap.get("userId"), formatter);
if (tenantId.isEmpty() || userId.isEmpty()) {
String nickname = readString(row, headerMap.get("nickname"), formatter);
if (userId.isEmpty()) {
userId = nickname;
}
if (userId.isEmpty()) {
userId = "AUTO_" + UUID.randomUUID().toString().replace("-", "");
}
if (tenantId.isEmpty()) {
return null;
}
@@ -190,7 +239,7 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
trade.setId(UUID.randomUUID().toString());
trade.setTenantId(tenantId);
trade.setUserId(userId);
trade.setNickname(readString(row, headerMap.get("nickname"), formatter));
trade.setNickname(nickname);
trade.setDailySellAmt(readDecimal(row, headerMap.get("dailySellAmt"), formatter));
trade.setDailyBuyAmt(readDecimal(row, headerMap.get("dailyBuyAmt"), formatter));
trade.setPromoterId(readString(row, headerMap.get("promoterId"), formatter));
@@ -277,4 +326,166 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
private String normalizeHeader(String header) {
return header == null ? "" : header.toLowerCase().replace(" ", "").replace(" ", "").trim();
}
private boolean isSupportedExcelSuffix(String fileName) {
String lowerCaseName = fileName.toLowerCase();
return lowerCaseName.endsWith(".xlsx") || lowerCaseName.endsWith(".xls");
}
private Workbook openWorkbook(MultipartFile file) throws IOException {
byte[] fileBytes = file.getBytes();
FileMagic fileMagic = detectFileMagic(fileBytes);
try {
return WorkbookFactory.create(new ByteArrayInputStream(fileBytes));
} catch (Exception e) {
log.warn("标准Excel解析失败尝试文本格式兜底fileName={}, fileMagic={}, contentType={}, headHex={}",
file.getOriginalFilename(), fileMagic, file.getContentType(), toHexPreview(fileBytes, 16));
Workbook textWorkbook = tryCreateWorkbookFromText(fileBytes, file.getOriginalFilename());
if (textWorkbook != null) {
log.info("文本格式兜底解析成功fileName={}", file.getOriginalFilename());
return textWorkbook;
}
log.error("文本格式兜底也失败fileName={}, fileMagic={}, contentType={}",
file.getOriginalFilename(), fileMagic, file.getContentType(), e);
throw new IllegalArgumentException(
"上传文件内容不是有效的Excel文件请确认文件未损坏且不要仅修改后缀名。检测到文件签名" + fileMagic, e);
}
}
private FileMagic detectFileMagic(byte[] fileBytes) throws IOException {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(fileBytes)) {
return FileMagic.valueOf(FileMagic.prepareToCheckMagic(inputStream));
}
}
private Workbook tryCreateWorkbookFromText(byte[] fileBytes, String fileName) {
for (Charset charset : List.of(StandardCharsets.UTF_8, Charset.forName("GBK"))) {
try {
Workbook workbook = createWorkbookFromDelimitedText(fileBytes, charset);
if (workbook != null) {
log.info("文本文件按分隔格式解析成功fileName={}, charset={}", fileName, charset.name());
return workbook;
}
} catch (Exception ex) {
log.debug("文本格式兜底解析失败fileName={}, charset={}, message={}",
fileName, charset.name(), ex.getMessage());
}
}
return null;
}
private Workbook createWorkbookFromDelimitedText(byte[] fileBytes, Charset charset) throws IOException {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(fileBytes), charset))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty()) {
lines.add(removeUtf8Bom(line));
}
}
}
if (lines.isEmpty()) {
return null;
}
char delimiter = detectDelimiter(lines.get(0));
if (delimiter == 0) {
return null;
}
List<List<String>> parsedRows = new ArrayList<>();
int maxColumns = 0;
for (String line : lines) {
List<String> cells = parseDelimitedLine(line, delimiter);
if (cells.size() > 1) {
parsedRows.add(cells);
maxColumns = Math.max(maxColumns, cells.size());
}
}
if (parsedRows.isEmpty() || maxColumns < 2) {
return null;
}
Workbook workbook = WorkbookFactory.create(true);
Sheet sheet = workbook.createSheet("Sheet1");
for (int rowIndex = 0; rowIndex < parsedRows.size(); rowIndex++) {
Row row = sheet.createRow(rowIndex);
List<String> cells = parsedRows.get(rowIndex);
for (int colIndex = 0; colIndex < cells.size(); colIndex++) {
row.createCell(colIndex).setCellValue(cells.get(colIndex));
}
}
return workbook;
}
private char detectDelimiter(String line) {
int tabCount = countChar(line, '\t');
int commaCount = countChar(line, ',');
int semicolonCount = countChar(line, ';');
if (tabCount > 0 && tabCount >= commaCount && tabCount >= semicolonCount) {
return '\t';
}
if (commaCount > 0 && commaCount >= semicolonCount) {
return ',';
}
if (semicolonCount > 0) {
return ';';
}
return 0;
}
private int countChar(String text, char ch) {
int count = 0;
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) == ch) {
count++;
}
}
return count;
}
private List<String> parseDelimitedLine(String line, char delimiter) {
List<String> cells = new ArrayList<>();
StringBuilder current = new StringBuilder();
boolean inQuotes = false;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (c == '"') {
if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') {
current.append('"');
i++;
} else {
inQuotes = !inQuotes;
}
continue;
}
if (c == delimiter && !inQuotes) {
cells.add(current.toString().trim());
current.setLength(0);
continue;
}
current.append(c);
}
cells.add(current.toString().trim());
return cells;
}
private String removeUtf8Bom(String line) {
if (!line.isEmpty() && line.charAt(0) == '\uFEFF') {
return line.substring(1);
}
return line;
}
private String toHexPreview(byte[] bytes, int len) {
int size = Math.min(bytes.length, len);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
sb.append(String.format("%02X", bytes[i]));
if (i < size - 1) {
sb.append(" ");
}
}
return sb.toString();
}
}