修改死锁
This commit is contained in:
@@ -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;
|
||||
|
||||
+79
-3
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user