|
@@ -5,11 +5,15 @@ import org.apache.commons.csv.CSVParser;
|
|
|
import org.apache.commons.csv.CSVRecord;
|
|
import org.apache.commons.csv.CSVRecord;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
|
|
import javax.annotation.PostConstruct;
|
|
import javax.annotation.PostConstruct;
|
|
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
import java.io.File;
|
|
import java.io.File;
|
|
|
import java.io.FileReader;
|
|
import java.io.FileReader;
|
|
|
|
|
+import java.io.InputStreamReader;
|
|
|
import java.io.Reader;
|
|
import java.io.Reader;
|
|
|
|
|
+import java.nio.charset.Charset;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
import java.util.Collections;
|
|
@@ -23,6 +27,7 @@ public class ShopSalesDataStore {
|
|
|
|
|
|
|
|
private volatile List<ShopSalesRecord> salesRecords = Collections.emptyList();
|
|
private volatile List<ShopSalesRecord> salesRecords = Collections.emptyList();
|
|
|
private volatile long lastLoadCount = 0;
|
|
private volatile long lastLoadCount = 0;
|
|
|
|
|
+ private volatile String lastUploadDebug = "";
|
|
|
|
|
|
|
|
@PostConstruct
|
|
@PostConstruct
|
|
|
public void loadOnStartup() {
|
|
public void loadOnStartup() {
|
|
@@ -91,6 +96,49 @@ public class ShopSalesDataStore {
|
|
|
return loaded.size();
|
|
return loaded.size();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ public int reloadFromUploads(MultipartFile[] files) {
|
|
|
|
|
+ List<ShopSalesRecord> loaded = new ArrayList<>();
|
|
|
|
|
+ StringBuilder debug = new StringBuilder();
|
|
|
|
|
+ if (files == null || files.length == 0) {
|
|
|
|
|
+ salesRecords = Collections.emptyList();
|
|
|
|
|
+ lastLoadCount = 0;
|
|
|
|
|
+ lastUploadDebug = "未收到上传文件。";
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int received = 0;
|
|
|
|
|
+ int csvCount = 0;
|
|
|
|
|
+ for (MultipartFile file : files) {
|
|
|
|
|
+ if (file == null || file.isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ received++;
|
|
|
|
|
+ String filename = file.getOriginalFilename();
|
|
|
|
|
+ if (filename == null || !filename.toLowerCase().endsWith(".csv")) {
|
|
|
|
|
+ debug.append("跳过非CSV文件: ").append(filename).append("\n");
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ csvCount++;
|
|
|
|
|
+ debug.append("文件: ").append(filename).append(", 大小=").append(file.getSize()).append("\n");
|
|
|
|
|
+ try {
|
|
|
|
|
+ byte[] bytes = file.getBytes();
|
|
|
|
|
+ int before = loaded.size();
|
|
|
|
|
+ int added = parseCsvBytes(bytes, loaded, debug);
|
|
|
|
|
+ debug.append("导入行数: ").append(added).append("\n");
|
|
|
|
|
+ if (loaded.size() == before) {
|
|
|
|
|
+ debug.append("提示: 该文件未解析到有效数据,可能是编码/分隔符/列格式不匹配。\n");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ debug.append("读取文件异常: ").append(e.getMessage()).append("\n");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ salesRecords = Collections.unmodifiableList(loaded);
|
|
|
|
|
+ lastLoadCount = loaded.size();
|
|
|
|
|
+ lastUploadDebug = "收到文件数=" + received + ", CSV文件数=" + csvCount + ", 总导入行数=" + loaded.size() + "\n" + debug;
|
|
|
|
|
+ return loaded.size();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public List<ShopSalesRecord> getSalesRecords() {
|
|
public List<ShopSalesRecord> getSalesRecords() {
|
|
|
return salesRecords;
|
|
return salesRecords;
|
|
|
}
|
|
}
|
|
@@ -98,6 +146,73 @@ public class ShopSalesDataStore {
|
|
|
public long getLastLoadCount() {
|
|
public long getLastLoadCount() {
|
|
|
return lastLoadCount;
|
|
return lastLoadCount;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ public String getLastUploadDebug() {
|
|
|
|
|
+ return lastUploadDebug;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int parseCsvBytes(byte[] bytes, List<ShopSalesRecord> loaded, StringBuilder debug) {
|
|
|
|
|
+ int added = parseCsvBytesWithCharset(bytes, loaded, StandardCharsets.UTF_8, debug);
|
|
|
|
|
+ if (added > 0) {
|
|
|
|
|
+ return added;
|
|
|
|
|
+ }
|
|
|
|
|
+ return parseCsvBytesWithCharset(bytes, loaded, Charset.forName("GBK"), debug);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int parseCsvBytesWithCharset(byte[] bytes, List<ShopSalesRecord> loaded, Charset charset, StringBuilder debug) {
|
|
|
|
|
+ try (Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes), charset);
|
|
|
|
|
+ CSVParser csvParser = new CSVParser(reader, CSVFormat.EXCEL.builder().setTrim(true).build())) {
|
|
|
|
|
+ int added = appendCsvRecords(csvParser, loaded);
|
|
|
|
|
+ debug.append("编码 ").append(charset.displayName()).append(" 解析行数: ").append(added).append("\n");
|
|
|
|
|
+ return added;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ debug.append("编码 ").append(charset.displayName()).append(" 解析异常: ").append(e.getMessage()).append("\n");
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int appendCsvRecords(CSVParser csvParser, List<ShopSalesRecord> loaded) {
|
|
|
|
|
+ Iterator<CSVRecord> csvIterator = csvParser.iterator();
|
|
|
|
|
+ if (csvIterator.hasNext()) {
|
|
|
|
|
+ csvIterator.next();
|
|
|
|
|
+ }
|
|
|
|
|
+ int added = 0;
|
|
|
|
|
+ while (csvIterator.hasNext()) {
|
|
|
|
|
+ CSVRecord csvRecord = csvIterator.next();
|
|
|
|
|
+ if (csvRecord.size() < 10) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ int year = Integer.parseInt(csvRecord.get(0));
|
|
|
|
|
+ int month = Integer.parseInt(csvRecord.get(1));
|
|
|
|
|
+ int day = Integer.parseInt(csvRecord.get(2));
|
|
|
|
|
+ String businessUnitName = csvRecord.get(3);
|
|
|
|
|
+ String channelName = csvRecord.get(4);
|
|
|
|
|
+ String platformName = csvRecord.get(5);
|
|
|
|
|
+ String productCode = csvRecord.get(6);
|
|
|
|
|
+ double quantity = Double.parseDouble(csvRecord.get(7));
|
|
|
|
|
+ double unitPrice = Double.parseDouble(csvRecord.get(8));
|
|
|
|
|
+ double salesAmount = Double.parseDouble(csvRecord.get(9));
|
|
|
|
|
+
|
|
|
|
|
+ loaded.add(new ShopSalesRecord(
|
|
|
|
|
+ year,
|
|
|
|
|
+ month,
|
|
|
|
|
+ day,
|
|
|
|
|
+ businessUnitName,
|
|
|
|
|
+ channelName,
|
|
|
|
|
+ platformName,
|
|
|
|
|
+ productCode,
|
|
|
|
|
+ quantity,
|
|
|
|
|
+ unitPrice,
|
|
|
|
|
+ salesAmount
|
|
|
|
|
+ ));
|
|
|
|
|
+ added++;
|
|
|
|
|
+ } catch (Exception ignored) {
|
|
|
|
|
+ // Skip bad rows.
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return added;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|