feat:风控引擎增加超时机制:在进行超时校验的时候,如果超时,不但重置新风控重新校验,老风控的二次校验也不允许直接放行,需要重新校验。

This commit is contained in:
ruisu
2026-07-09 10:26:26 +08:00
parent b8fb8bb148
commit d14bdc18e7
4 changed files with 70 additions and 3 deletions
@@ -4929,6 +4929,22 @@ namespace YLErp.Modules.RiskModule
return result;
}
/// <summary>
/// 按 quotaTrial.id 校验试算结果是否已超过配置时效
/// </summary>
/// <param name="trialDataId">quotaTrial.id</param>
/// <param name="expireSeconds">有效时长(秒)</param>
/// <returns>查不到记录或已超时返回 true,否则返回 false</returns>
public bool IsQuotaTrialExpired(int trialDataId, int expireSeconds)
{
var quotaTrial = QueryQuotaTrial(trialDataId);
if (quotaTrial == null || !quotaTrial.OptDate.HasValue)
{
return true;
}
return DateTime.Now - quotaTrial.OptDate.Value > TimeSpan.FromSeconds(expireSeconds);
}
/// <summary>
/// 查询试算历史
/// </summary>
@@ -149,6 +149,26 @@ namespace YLErp.Modules.RiskModule
DbContext.SaveChanges();
}
/// <summary>
/// 查询指定交易最近一次新风控“确认通过”的处理时间,用于控制二次审批放行仅在短时间内有效。
/// </summary>
public DateTime? QueryLatestRiskWarningDecisionTime(int tradeId)
{
var tradeNumber = DbContext.trade.Where(x => x.id == tradeId).Select(x => x.TradeNumber).FirstOrDefault();
if (string.IsNullOrWhiteSpace(tradeNumber))
{
return null;
}
return DbContext.trade_risk_check_log
.Where(x => x.trade_number == tradeNumber
&& x.trial_result == (int)QuotaTrialStatusEnum.RiskWarning
&& x.remark.Contains("风控预警处理结果:确认通过"))
.OrderByDescending(x => x.create_time)
.Select(x => (DateTime?)x.create_time)
.FirstOrDefault();
}
public void AddWarningDecisionLog(int quotaTrialId, string decision)
{
var quotaTrial = DbContext.quotaTrial.FirstOrDefault(x => x.id == quotaTrialId);
+28 -1
View File
@@ -2447,7 +2447,8 @@ namespace YLErp.Web.Controllers
}
public JsonResult tradeConfirm(string tradeids, string additionalProcessing, string ignoreRiskRuleIds, bool isSkipCheck = false)
// trialDataId: 二次确认时回传的 quotaTrial.id,用于超时校验;首次确认不传,为 null 时不做超时判断
public JsonResult tradeConfirm(string tradeids, string additionalProcessing, string ignoreRiskRuleIds, int? trialDataId, bool isSkipCheck = false)
{
var tradeidArr = DataConvert.ConvertCommaValuesToInt32Array(tradeids);
if (tradeids is null || !tradeids.Any(n => n > 0))
@@ -2455,9 +2456,35 @@ namespace YLErp.Web.Controllers
return JsonError("请至少选择一项要生成确认书的交易,参数tradeids:" + tradeids);
}
var config = valuedateBLL.SystemDate;
var riskWarningConfirmExpireSeconds = 300;
var riskWarningConfirmExpireSecondsConfig = AppManager.GetAppConfigValue("ProjectConfig", "Trade.RiskWarningConfirmExpireSeconds");
if (!string.IsNullOrWhiteSpace(riskWarningConfirmExpireSecondsConfig)
&& int.TryParse(riskWarningConfirmExpireSecondsConfig, out var configuredRiskWarningConfirmExpireSeconds)
&& configuredRiskWarningConfirmExpireSeconds > 0)
{
riskWarningConfirmExpireSeconds = configuredRiskWarningConfirmExpireSeconds;
}
var ignoreMoneyCheck = tradeidArr.Count() == 1 && additionalProcessing == tradeBLL.LackOfMoney;
var ignoreRiskWarning = tradeidArr.Count() == 1 && additionalProcessing == tradeBLL.RiskWarningConfirm;
var ignoreRiskRuleIdArr = StringHelper.ConvertCommaValuesToStringArray(ignoreRiskRuleIds);
// 老风控交易特批与新风控二次审批统一基于 quotaTrial 做超时校验,超时后清空放行标记并重新校验。
// 仅 swapTradeView.js 会回传 trialDataId 并触发 RiskWarningConfirm,按 quotaTrial.OptDate 精确校验;
// tradeview.js / tradeConfirmList.js / swapTradeConfirmList.js / quotaMonitor.js 只发 LackOfMoney,不传 trialDataId,不进入超时校验。
if (ignoreMoneyCheck || ignoreRiskWarning)
{
var trialExpired = false;
if (trialDataId.HasValue)
{
// swapTradeView.js:按 quotaTrial.OptDate 校验,查不到记录也视为过期
trialExpired = new QuotaMonitorService(CurUser).IsQuotaTrialExpired(trialDataId.Value, riskWarningConfirmExpireSeconds);
}
if (trialExpired)
{
ignoreMoneyCheck = false;
ignoreRiskWarning = false;
ignoreRiskRuleIdArr = Array.Empty<string>();
}
}
var isSkipApproval = false;
if (CurUser._特批批量确认 && isSkipCheck)
{
@@ -49,7 +49,7 @@ function showSwapRate(timeRate, isMarginLeg) {
$("#swapIntervalModal").modal("show");
}
var layerIndex = -1;
var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds) {
var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds, trialDataId) {
var pData = { tradeids: id };
if (!main.isEmpty(additionalProcessing)) {
pData.additionalProcessing = additionalProcessing;
@@ -57,6 +57,9 @@ var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds) {
if (ignoreRiskRuleIds && ignoreRiskRuleIds.length > 0) {
pData.ignoreRiskRuleIds = ignoreRiskRuleIds.join(',');
}
if (trialDataId) {
pData.trialDataId = trialDataId;
}
main.post("/trade/tradeConfirm", pData).done(function (data) {
if (!data.success) {
main.message(data.msg);
@@ -103,6 +106,7 @@ var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds) {
var additionalProcessingType = isRiskWarningConfirm ? "RiskWarningConfirm" : "LackOfMoney";
// 新风控需审批时由后端返回本次命中的审批规则 id,前端二次确认时原样透传。
var currentIgnoreRiskRuleIds = data.obj.ignoreRiskRuleIds || [];
var currentTrialDataId = data.obj.TrialDataId;
var saveQuotaTrial = function (obj) {
var saveSuccess = true;
if (obj.Data.Remark && obj.Data.Remark.length > 0) {
@@ -150,7 +154,7 @@ var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds) {
obj.Data.Remark = (obj.Data.Remark || "").trim();
}
layer.close(layerIndex);
confirmFunc(id, additionalProcessingType, currentIgnoreRiskRuleIds);
confirmFunc(id, additionalProcessingType, currentIgnoreRiskRuleIds, currentTrialDataId);
},
cancel: function () {
var iframeWindow = window["layui-layer-iframe" + this.index];