修正重复导入的问题

This commit is contained in:
2026-05-12 11:52:45 +08:00
parent c5d24afdb4
commit 97b9048ee7

View File

@@ -43,6 +43,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -70,6 +72,8 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
private static final int DB_PROMOTER_NAME_MAX = 100;
private static final int DB_DESC_MAX = 512;
private static final int ID_IN_QUERY_BATCH = 500;
/** 同一租户同时只允许一个 Excel 导入,避免并发请求或连点导致重复插入 */
private static final ConcurrentHashMap<String, ReentrantLock> TENANT_LB_DAILY_TRADE_IMPORT_LOCKS = new ConcurrentHashMap<>();
@Autowired
private ILbDailyUserTradeReportService lbDailyUserTradeReportService;
@@ -197,6 +201,13 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
return result;
}
ReentrantLock tenantImportLock = TENANT_LB_DAILY_TRADE_IMPORT_LOCKS.computeIfAbsent(
normalizedTenantId, k -> new ReentrantLock());
if (!tenantImportLock.tryLock()) {
result.put("success", false);
result.put("message", "该租户已有导入任务正在执行,请等待完成后再试,避免重复写入数据");
return result;
}
String previousTenantId = TenantContextHolder.getTenantId();
TenantContextHolder.setTenantId(normalizedTenantId);
try {
@@ -223,6 +234,8 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
List<LbDailyUserTrade> importList = new ArrayList<>();
List<String> skippedRows = new ArrayList<>();
int skippedHiddenRows = 0;
/** 表头以下:非空行且非隐藏行,即参与解析的 Excel 行数(含解析失败/跳过的行) */
int excelTableRowCount = 0;
DataFormatter dataFormatter = new DataFormatter();
log.info("开始导入ExcelfileName={}, size={} bytes, defaultReportDate={}",
originalFilename, file.getSize(), defaultReportDate);
@@ -247,12 +260,12 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
if (row == null || isEmptyRow(row, dataFormatter)) {
continue;
}
// 隐藏行(行高为 0在部分导出/复制模板中会与可见行重复存一份数据;
// POI 仍会遍历到这些行,若不跳过会导致导入条数约为可见行的整数倍。
// 部分导出模板会把同一笔数据再存一份「行高为 0」的隐藏行与业务是否重复无关仅排除此类重复物理行。
if (row.getZeroHeight()) {
skippedHiddenRows++;
continue;
}
excelTableRowCount++;
try {
LbDailyUserTrade trade = parseTradeRow(row, headerIndexMap, dataFormatter, fallbackReportDate, normalizedTenantId);
if (trade == null) {
@@ -327,9 +340,12 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
result.put("parsedCount", total);
result.put("skipCount", skippedRows.size());
result.put("skippedRows", skippedRows);
result.put("skippedHiddenRowCount", skippedHiddenRows);
return result;
}
int dbInsertedRowCount = importList.size();
log.info("lb_daily_user_trade 导入落库完成Excel表格行数={},插入数据库行数={}", excelTableRowCount, dbInsertedRowCount);
log.info("导入成功fileName={}, importCount={}, skipCount={}, skippedHiddenRows={}",
originalFilename, importList.size(), skippedRows.size(), skippedHiddenRows);
result.put("success", true);
@@ -345,6 +361,7 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
} else {
TenantContextHolder.clear();
}
tenantImportLock.unlock();
}
}
@@ -516,13 +533,7 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
return null;
}
LbDailyUserTrade matchedTrade = null;
if (StringUtils.isBlank(userId) && StringUtils.isNotBlank(nickname)) {
matchedTrade = findLatestTradeByNickname(tenantId, nickname);
if (matchedTrade != null && StringUtils.isNotBlank(matchedTrade.getUserId())) {
userId = matchedTrade.getUserId();
}
}
// 严格按 Excel 行写入:不查库合并/补全 userId 或推广人;与库内是否重复无关,每行一条 INSERT。
if (userId.isEmpty()) {
userId = "AUTO_USERID_" + UUID.randomUUID().toString().replace("-", "");
}
@@ -534,18 +545,8 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
trade.setNickname(nickname);
trade.setDailySellAmt(readDecimal(row, headerMap.get("dailySellAmt"), formatter));
trade.setDailyBuyAmt(readDecimal(row, headerMap.get("dailyBuyAmt"), formatter));
String promoterId = readString(row, headerMap.get("promoterId"), formatter);
String promoterName = readString(row, headerMap.get("promoterName"), formatter);
if (matchedTrade != null) {
if (StringUtils.isBlank(promoterId) && StringUtils.isNotBlank(matchedTrade.getPromoterId())) {
promoterId = matchedTrade.getPromoterId();
}
if (StringUtils.isBlank(promoterName) && StringUtils.isNotBlank(matchedTrade.getPromoterName())) {
promoterName = matchedTrade.getPromoterName();
}
}
trade.setPromoterId(promoterId);
trade.setPromoterName(promoterName);
trade.setPromoterId(readString(row, headerMap.get("promoterId"), formatter));
trade.setPromoterName(readString(row, headerMap.get("promoterName"), formatter));
trade.setDescContent(readString(row, headerMap.get("descContent"), formatter));
BigDecimal sell = trade.getDailySellAmt() == null ? BigDecimal.ZERO : trade.getDailySellAmt();
@@ -562,15 +563,6 @@ public class LbDailyUserTradeServiceImpl extends ServiceImpl<LbDailyUserTradeMap
return trade;
}
private LbDailyUserTrade findLatestTradeByNickname(String tenantId, String nickname) {
LambdaQueryWrapper<LbDailyUserTrade> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LbDailyUserTrade::getTenantId, tenantId)
.eq(LbDailyUserTrade::getNickname, nickname.trim())
.orderByDesc(LbDailyUserTrade::getUpdatedAt)
.last("limit 1");
return this.getOne(wrapper, false);
}
private boolean isEmptyRow(Row row, DataFormatter formatter) {
if (row == null) {
return true;