From bdaca758c267d69f660fa735738f62265e487b3e Mon Sep 17 00:00:00 2001 From: cst61 Date: Sat, 25 Apr 2026 15:21:55 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=AF=BC=E5=85=A5=E6=97=B6?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LbDailyUserTradeController.java | 6 +- .../rj/service/ILbDailyUserTradeService.java | 2 +- .../impl/LbDailyUserTradeServiceImpl.java | 237 +++++++++++++++++- 3 files changed, 229 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/rj/controller/LbDailyUserTradeController.java b/src/main/java/com/rj/controller/LbDailyUserTradeController.java index 4dcf8c9..b018c03 100644 --- a/src/main/java/com/rj/controller/LbDailyUserTradeController.java +++ b/src/main/java/com/rj/controller/LbDailyUserTradeController.java @@ -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 result = lbDailyUserTradeService.importFromExcel(file, defaultReportDate); + Map result = lbDailyUserTradeService.importFromExcel(file, defaultReportDate, tenantId); Boolean success = (Boolean) result.get("success"); if (Boolean.TRUE.equals(success)) { return ResponseEntity.ok(result); diff --git a/src/main/java/com/rj/service/ILbDailyUserTradeService.java b/src/main/java/com/rj/service/ILbDailyUserTradeService.java index 494ace9..17f16ef 100644 --- a/src/main/java/com/rj/service/ILbDailyUserTradeService.java +++ b/src/main/java/com/rj/service/ILbDailyUserTradeService.java @@ -13,5 +13,5 @@ import java.util.Map; */ public interface ILbDailyUserTradeService extends IService { - Map importFromExcel(MultipartFile file, String defaultReportDate); + Map importFromExcel(MultipartFile file, String defaultReportDate, String tenantId); } diff --git a/src/main/java/com/rj/service/impl/LbDailyUserTradeServiceImpl.java b/src/main/java/com/rj/service/impl/LbDailyUserTradeServiceImpl.java index 032a2c8..0ed45b4 100644 --- a/src/main/java/com/rj/service/impl/LbDailyUserTradeServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbDailyUserTradeServiceImpl.java @@ -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 implements ILbDailyUserTradeService { + private static final Logger log = LoggerFactory.getLogger(LbDailyUserTradeServiceImpl.class); @Override - public Map importFromExcel(MultipartFile file, String defaultReportDate) { + public Map importFromExcel(MultipartFile file, String defaultReportDate, String tenantId) { Map 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 importList = new ArrayList<>(); List skippedRows = new ArrayList<>(); DataFormatter dataFormatter = new DataFormatter(); - try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) { + log.info("开始导入Excel,fileName={}, 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 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 headerMap, DataFormatter formatter, LocalDate fallbackReportDate) { - String tenantId = readString(row, headerMap.get("tenantId"), formatter); + private LbDailyUserTrade parseTradeRow(Row row, + Map 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 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> parsedRows = new ArrayList<>(); + int maxColumns = 0; + for (String line : lines) { + List 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 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 parseDelimitedLine(String line, char delimiter) { + List 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(); + } + }