修改死锁

This commit is contained in:
wuweihua
2026-10-21 10:34:45 +08:00
parent d7f7ebd981
commit dd2efef456
3 changed files with 109 additions and 19 deletions
@@ -22,6 +22,8 @@ public class LocalRebillRequest implements Serializable
private List<String> orderIdentifiers;
private List<String> customerCodes;
private List<String> productCodes;
private List<Integer> orderStatuses;
private List<Integer> billingFailStatuses;
private String countryCode;
private String creditArrearsStatus;
private String beginBizDate;
@@ -12,6 +12,8 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -89,6 +91,8 @@ import com.bms.price.task.PrcCreditArrearsClearAsyncRunner;
@Service
public class PrcOrderBillServiceImpl implements IPrcOrderBillService
{
private static final Logger log = LoggerFactory.getLogger(PrcOrderBillServiceImpl.class);
private static final String DICT_FEE_TYPE = "bms_fee_type";
private static final String FEE_TYPE_REFUND = "REFUND";
@@ -701,8 +705,14 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
persistProductOrRedlineOnly(bill, result, productMode);
}
private static final int LOCAL_REBILL_DB_RETRY_MAX = 3;
private static final long LOCAL_REBILL_DB_RETRY_BASE_MS = 80L;
/**
* Local rebill without outer txn: each {@link #bill} starts its own transaction so
* MySQL deadlock can be retried cleanly (same idea as billing-task billWithDbRetry).
*/
@Override
@Transactional(rollbackFor = Exception.class)
public TmsPriceQuoteResult rebillLocalByBillId(String billId)
{
if (StringUtils.isEmpty(billId))
@@ -723,8 +733,72 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
throw new ServiceException(MessageUtils.message("price.bill.bizDateRequired"));
}
TmsPriceBillRequest req = buildBillRequestFromBill(bill);
// Full bill() path: AR guard, weight rules, quote, persist (local snapshot only).
return self.bill(req);
return billLocalWithDbRetry(billId.trim(), req);
}
private TmsPriceQuoteResult billLocalWithDbRetry(String billId, TmsPriceBillRequest billReq)
{
RuntimeException last = null;
for (int attempt = 1; attempt <= LOCAL_REBILL_DB_RETRY_MAX; attempt++)
{
try
{
return self.bill(billReq);
}
catch (RuntimeException e)
{
last = e;
if (attempt < LOCAL_REBILL_DB_RETRY_MAX && isRetryableDbConflict(e))
{
long sleepMs = LOCAL_REBILL_DB_RETRY_BASE_MS * attempt
+ (long) (Math.random() * LOCAL_REBILL_DB_RETRY_BASE_MS);
log.warn("local rebill db conflict, retry billId={} attempt={}/{} sleepMs={} msg={}",
billId, Integer.valueOf(attempt), Integer.valueOf(LOCAL_REBILL_DB_RETRY_MAX),
Long.valueOf(sleepMs), StringUtils.substring(e.getMessage(), 0, 200));
try
{
Thread.sleep(sleepMs);
}
catch (InterruptedException ie)
{
Thread.currentThread().interrupt();
throw e;
}
continue;
}
throw e;
}
}
throw last != null ? last : new ServiceException("local rebill retry exhausted");
}
/** Deadlock / lock wait — safe to retry whole bill txn (parallel local rebill). */
private static boolean isRetryableDbConflict(Throwable e)
{
Throwable t = e;
while (t != null)
{
String name = t.getClass().getName();
if (name.indexOf("DeadlockLoser") >= 0
|| name.indexOf("CannotAcquireLock") >= 0
|| name.indexOf("MySQLTransactionRollback") >= 0)
{
return true;
}
String msg = t.getMessage();
if (msg != null)
{
String m = msg.toLowerCase();
if (m.indexOf("deadlock") >= 0
|| m.indexOf("try restarting transaction") >= 0
|| m.indexOf("lock wait timeout") >= 0)
{
return true;
}
}
t = t.getCause();
}
return false;
}
@Override
@@ -839,6 +913,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
query.setOrderIdentifiers(orderIdentifiers);
query.setCustomerCodes(customerCodes);
query.setProductCodes(productCodes);
query.setOrderStatuses(request.getOrderStatuses());
query.setBillingFailStatuses(request.getBillingFailStatuses());
query.setCreditArrearsStatus(creditArrearsStatus);
query.setCountryCode(countryCode);
if (beginBizDate != null)
+28 -16
View File
@@ -295,14 +295,27 @@ function currentIdentifiers() {
}
function buildRebillFilter() {
const q = tableRef.value?.getQueryParams?.() || {}
const filter = { orderIdentifiers: currentIdentifiers() }
if (Array.isArray(q.customerCodes) && q.customerCodes.length) {
filter.customerCodes = q.customerCodes
const q = applySearchRanges(tableRef.value?.getQueryParams?.() || {})
const filter = {
scopeMode: 'ALL',
orderIdentifiers: currentIdentifiers(),
customerCodes: Array.isArray(q.customerCodes) ? q.customerCodes : [],
productCodes: Array.isArray(q.productCodes) ? q.productCodes : [],
orderStatuses: Array.isArray(q.orderStatuses) ? q.orderStatuses : [],
billingFailStatuses: Array.isArray(q.billingFailStatuses) ? q.billingFailStatuses : [],
beginBizDate: q.params?.beginBizDate || null,
endBizDate: q.params?.endBizDate || null
}
return filter
}
function hasRebillFilter(filter) {
const ids = filter.orderIdentifiers || []
if (ids.length) return true
const hasCustomer = Array.isArray(filter.customerCodes) && filter.customerCodes.length > 0
const hasBizDate = !!(filter.beginBizDate && filter.endBizDate)
return hasCustomer && hasBizDate
}
function buildCheckQuery() {
const q = tableRef.value?.getQueryParams?.() || {}
@@ -383,24 +396,23 @@ async function runLocalRebill(payload, confirmText) {
}
function openFilterRebill() {
const identifiers = currentIdentifiers()
if (!identifiers.length) {
ElMessage.warning('请输入单号后再重新计费,或勾选订单后点重新计费选中')
const filter = buildRebillFilter()
const identifiers = filter.orderIdentifiers || []
if (!hasRebillFilter(filter)) {
ElMessage.warning('请输入单号,或选择客户并选择计费时间后再重新计费;也可勾选订单后点重新计费选中')
return
}
if (identifiers.length > MAX_REBILL) {
ElMessage.warning('单次最多重新计费 ' + MAX_REBILL + ' 个单号')
return
}
const filter = buildRebillFilter()
runLocalRebill(
{
scopeMode: 'ALL',
orderIdentifiers: filter.orderIdentifiers || identifiers,
customerCodes: filter.customerCodes || []
},
'将按 ' + identifiers.length + ' 个单号从本地订单表重新计费(不查上游),确认继续?'
)
let confirmText
if (identifiers.length) {
confirmText = '将按 ' + identifiers.length + ' 个单号从本地订单表重新计费(不查上游),确认继续?'
} else {
confirmText = '将按当前列表筛选(客户 + 计费时间等)从本地订单表重新计费(不查上游),确认继续?'
}
runLocalRebill(filter, confirmText)
}
function handleBatchRebill(selection) {