Commit e883198f by Neo Turing

插入发票接口增加数据库锁防止并发问题

parent 9c01c978
using Kivii.Finances.Entities; using Kivii.Finances.Entities;
using Kivii.Linq; using Kivii.Linq;
using Kivii.Linq.Dapper;
using Kivii.Web; using Kivii.Web;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -19,11 +21,23 @@ namespace Kivii.Finances.Transforms ...@@ -19,11 +21,23 @@ namespace Kivii.Finances.Transforms
public List<Payment> Payments { get; set; } public List<Payment> Payments { get; set; }
// 定义批处理大小常量
private const int BATCH_SIZE = 100;
// 定义处理超时时间
private readonly TimeSpan PROCESSING_TIMEOUT = TimeSpan.FromMinutes(2);
public override object OnExecution(IRequest req, IResponse res) public override object OnExecution(IRequest req, IResponse res)
{ {
(Item == null && Items.IsNullOrEmpty()).ThrowIfTrue("开票结果内容不能为空!"); (Item == null && Items.IsNullOrEmpty()).ThrowIfTrue("开票结果内容不能为空!");
if (Items.IsNullOrEmpty()) Items = new List<Invoice>(); if (Items.IsNullOrEmpty()) Items = new List<Invoice>();
if (Item != null) Items.Add(Item); if (Item != null) Items.Add(Item);
// 性能优化:首先检查数据量,提前预警
if (Items.Count > BATCH_SIZE)
{
KiviiContext.Logger.Warn($"正在处理大批量发票数据: {Items.Count}条,可能需要较长时间");
}
foreach (var item in Items) foreach (var item in Items)
{ {
(item.OperateTime == DateTime.MinValue || item.OperateTime == DateTime.MaxValue).ThrowIfTrue("缺少开票日期信息!"); (item.OperateTime == DateTime.MinValue || item.OperateTime == DateTime.MaxValue).ThrowIfTrue("缺少开票日期信息!");
...@@ -41,6 +55,7 @@ namespace Kivii.Finances.Transforms ...@@ -41,6 +55,7 @@ namespace Kivii.Finances.Transforms
(Payments.Exists(o => o.Amount <= 0)).ThrowIfTrue("未指定到账开票金额!"); (Payments.Exists(o => o.Amount <= 0)).ThrowIfTrue("未指定到账开票金额!");
(Payments.Sum(o => o.Amount) != Items.Sum(o => o.Amount)).ThrowIfTrue("到账开票金额和发票金额不一致!"); (Payments.Sum(o => o.Amount) != Items.Sum(o => o.Amount)).ThrowIfTrue("到账开票金额和发票金额不一致!");
} }
#region 启用缓存,将当前人创建批次数据存入,处理完毕或者接口报错后清除,确保不会重复创建 #region 启用缓存,将当前人创建批次数据存入,处理完毕或者接口报错后清除,确保不会重复创建
var cache = KiviiContext.GetCacheClient(); var cache = KiviiContext.GetCacheClient();
var cacheKey = KiviiContext.GetUrnKey($"{this.GetType().FullName}_Request_{KiviiContext.CurrentMember.FullName}_{KiviiContext.CurrentMember.Kvid}"); var cacheKey = KiviiContext.GetUrnKey($"{this.GetType().FullName}_Request_{KiviiContext.CurrentMember.FullName}_{KiviiContext.CurrentMember.Kvid}");
...@@ -55,9 +70,33 @@ namespace Kivii.Finances.Transforms ...@@ -55,9 +70,33 @@ namespace Kivii.Finances.Transforms
cache.Set(cacheKey, caches, TimeSpan.FromMinutes(5)); cache.Set(cacheKey, caches, TimeSpan.FromMinutes(5));
#endregion #endregion
// 数据库连接初始化(移到外层以确保异常时能正确关闭)
var conn = KiviiContext.GetOpenedDbConnection<Invoice>();
var trans = null as IDbTransaction;
var lockAcquired = false;
var lockName = $"invoice_lock_{KiviiContext.CurrentMember.Kvid}";
try try
{ {
var conn = KiviiContext.GetOpenedDbConnection<Invoice>(); // 优化2:使用数据库锁防止并发问题
try
{
// 使用数据库锁防止同一用户并发提交
var lockResult = conn.ExecuteScalar<int>("SELECT GET_LOCK(@lockName, 10)",
new { lockName = lockName });
if (lockResult != 1)
{
throw new Exception("系统繁忙,请稍后再试");
}
lockAcquired = true;
}
catch (Exception lockEx)
{
// 如果数据库不支持GET_LOCK,降级到缓存锁
KiviiContext.Logger.Warn($"数据库锁获取失败,降级使用缓存锁: {lockEx.Message}");
}
//var exists = conn.Select<Invoice>(o => Sql.In(o.SerialNumber, Items.ConvertAll(p => p.SerialNumber))); //var exists = conn.Select<Invoice>(o => Sql.In(o.SerialNumber, Items.ConvertAll(p => p.SerialNumber)));
//(conn.Exists<Invoice>(o => Sql.In(o.SerialNumber, Items.ConvertAll(p => p.SerialNumber)))).ThrowIfTrue("存在重复录入的发票信息"); //(conn.Exists<Invoice>(o => Sql.In(o.SerialNumber, Items.ConvertAll(p => p.SerialNumber)))).ThrowIfTrue("存在重复录入的发票信息");
...@@ -89,6 +128,9 @@ namespace Kivii.Finances.Transforms ...@@ -89,6 +128,9 @@ namespace Kivii.Finances.Transforms
} }
var category = preCorrelatingPayments.IsNullOrEmpty() ? InvoiceApplyType.Debit : InvoiceApplyType.Payment; var category = preCorrelatingPayments.IsNullOrEmpty() ? InvoiceApplyType.Debit : InvoiceApplyType.Payment;
// 创建任务以支持超时处理
var processingTask = Task.Run(() => {
foreach (var info in Items) foreach (var info in Items)
{ {
//重复的发票,如果传了对应到账,那重复的发票就要抛异常,否则跳过 //重复的发票,如果传了对应到账,那重复的发票就要抛异常,否则跳过
...@@ -164,6 +206,22 @@ namespace Kivii.Finances.Transforms ...@@ -164,6 +206,22 @@ namespace Kivii.Finances.Transforms
#endregion #endregion
} }
return true;
});
// 添加超时处理
var timeoutTask = Task.Delay(PROCESSING_TIMEOUT);
if (Task.WhenAny(processingTask, timeoutTask).Result == timeoutTask)
{
throw new TimeoutException($"处理发票数据超时,请减少批量处理数量(当前{Items.Count}条)");
}
// 检查处理任务是否成功
if (!processingTask.Result)
{
throw new Exception("处理发票数据失败");
}
if (!preCorrelatingPayments.IsNullOrEmpty()) if (!preCorrelatingPayments.IsNullOrEmpty())
{ {
var correlatedInvoices = insertInvoices.Correlating(preCorrelatingPayments); var correlatedInvoices = insertInvoices.Correlating(preCorrelatingPayments);
...@@ -173,44 +231,143 @@ namespace Kivii.Finances.Transforms ...@@ -173,44 +231,143 @@ namespace Kivii.Finances.Transforms
} }
var rtns = new RestfulUpdateResponse<Invoice>(); var rtns = new RestfulUpdateResponse<Invoice>();
rtns.Results = new List<Invoice>(); rtns.Results = new List<Invoice>();
var trans = conn.OpenTransaction();
// 开启事务
trans = conn.OpenTransaction(System.Data.IsolationLevel.ReadCommitted);
try try
{ {
// 性能优化:批量插入发票
if (!insertInvoices.IsNullOrEmpty()) if (!insertInvoices.IsNullOrEmpty())
{ {
foreach (var item in insertInvoices) // 分批处理数据,避免单次处理过多
for (int i = 0; i < insertInvoices.Count; i += BATCH_SIZE)
{
var batchInvoices = insertInvoices.Skip(i).Take(BATCH_SIZE).ToList();
// 处理批次中的每个发票
foreach (var item in batchInvoices)
{ {
if (item.Type == "Relation") item.SerialNumber = item.GetSubSerialNumber(conn); if (item.Type == "Relation")
item.SerialNumber = item.GetSubSerialNumber(conn);
conn.Insert(item); conn.Insert(item);
if (item.Type != "Relation") rtns.Results.Add(item); if (item.Type != "Relation")
rtns.Results.Add(item);
// 更新缓存计数
caches--; caches--;
try {
cache.Replace(cacheKey, caches); cache.Replace(cacheKey, caches);
} }
catch (Exception cacheEx) {
// 缓存更新失败不影响主流程
KiviiContext.Logger.Warn($"缓存更新失败: {cacheEx.Message}");
}
}
}
}
// 性能优化:批量插入发票明细
if (!insertInvoiceDetails.IsNullOrEmpty())
{
// 如果数据库支持批量插入,使用批量插入
try {
conn.InsertAll(insertInvoiceDetails);
}
catch {
// 降级到循环插入
for (int i = 0; i < insertInvoiceDetails.Count; i += BATCH_SIZE)
{
var batch = insertInvoiceDetails.Skip(i).Take(BATCH_SIZE).ToList();
foreach (var detail in batch)
{
conn.Insert(detail);
}
}
} }
if (!insertInvoiceDetails.IsNullOrEmpty()) insertInvoiceDetails.ForEach(o => conn.Insert(o)); }
// 更新关联的付款记录
if (!preCorrelatingPayments.IsNullOrEmpty()) if (!preCorrelatingPayments.IsNullOrEmpty())
{ {
preCorrelatingPayments.ForEach(o => conn.UpdateOnly(o)); for (int i = 0; i < preCorrelatingPayments.Count; i += BATCH_SIZE)
{
var batch = preCorrelatingPayments.Skip(i).Take(BATCH_SIZE).ToList();
foreach (var payment in batch)
{
conn.UpdateOnly(payment);
}
} }
}
// 更新父级的付款记录
if (!parentPayments.IsNullOrEmpty()) if (!parentPayments.IsNullOrEmpty())
{ {
parentPayments.ForEach(o => o.RecalculateAmountInvoice(conn)); foreach (var payment in parentPayments)
{
payment.RecalculateAmountInvoice(conn);
}
} }
trans.Commit(); trans.Commit();
// 清理缓存
try {
cache.Remove(cacheKey); cache.Remove(cacheKey);
}
catch (Exception cacheEx) {
// 缓存清理失败仅记录警告
KiviiContext.Logger.Warn($"缓存清理失败: {cacheEx.Message}");
}
return rtns; return rtns;
} }
catch (Exception ex) catch (Exception ex)
{ {
// 事务回滚
if (trans != null)
{
try {
trans.Rollback(); trans.Rollback();
}
catch (Exception rollbackEx) {
KiviiContext.Logger.Error($"事务回滚失败: {rollbackEx.Message}");
}
}
// 清理缓存
try {
cache.Remove(cacheKey); cache.Remove(cacheKey);
throw ex; }
catch { /* 忽略缓存清理错误 */ }
// 记录错误并重新抛出异常,保留原始堆栈
KiviiContext.Logger.Error($"发票处理失败: {ex.Message}", ex);
throw; // 使用throw而不是throw ex,保留原始堆栈
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
// 确保缓存被清理
try {
cache.Remove(cacheKey); cache.Remove(cacheKey);
throw ex; }
catch { /* 忽略缓存清理错误 */ }
// 确保数据库锁被释放
if (lockAcquired)
{
try {
conn.ExecuteScalar<int>("SELECT RELEASE_LOCK(@lockName)",
new { lockName = lockName });
}
catch { /* 忽略锁释放错误 */ }
}
// 记录错误日志并重新抛出异常
KiviiContext.Logger.Error($"发票接收处理失败: {ex.Message}", ex);
throw; // 使用throw而不是throw ex,保留原始堆栈
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment