Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
Kivii.Biz.Finances.V2.5
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
陶然
Kivii.Biz.Finances.V2.5
Commits
e883198f
Commit
e883198f
authored
Apr 25, 2025
by
Neo Turing
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
插入发票接口增加数据库锁防止并发问题
parent
9c01c978
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
167 additions
and
10 deletions
+167
-10
RestfulInvoice.cs
Src/Transforms/RestfulInvoice.cs
+167
-10
No files found.
Src/Transforms/RestfulInvoice.cs
View file @
e883198f
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,保留原始堆栈
}
}
}
}
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment