From 81cb095a64d668c1963a178126c8b14b90e29267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 10 Aug 2026 10:32:41 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix(bond):=20=E4=BF=AE=E5=A4=8D=E5=80=BA?= =?UTF-8?q?=E5=88=B8=E8=AE=A1=E7=AE=97=E4=B8=AD=E7=9A=84=E4=BB=B7=E6=A0=BC?= =?UTF-8?q?=E5=80=8D=E6=95=B0=E5=BA=94=E7=94=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在BondCalcHelper调用中添加了bondShowPriceMultiple倍数转换 - 确保deal_full_price_avg在传递给债券计算器之前进行正确的价格倍数调整 - 保持原有的空值检查逻辑以确保计算安全性 --- YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs b/YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs index 54e9a178..d8347cbc 100644 --- a/YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs +++ b/YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs @@ -745,7 +745,7 @@ namespace YLErp.BLL.Eod #region 新互换实时持仓私有方法 private static void BondCalcApi(ClientPosition clientPosition) { - var resp = BondCalcHepler.BondCalc(clientPosition.security_id, clientPosition.deal_full_price_avg ?? 0, "DP"); + var resp = BondCalcHepler.BondCalc(clientPosition.security_id, clientPosition.deal_full_price_avg * ConsGlobal.bondShowPriceMultiple ?? 0, "DP"); if (resp != null) { clientPosition.deal_yield_avg = resp.ytm * ConsGlobal.bondPriceMultiple; From 2e75e62bbcd27969d2490c6861cfd1e9313dfc30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Mon, 10 Aug 2026 17:21:46 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(exception):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E4=B8=AD=E9=97=B4=E4=BB=B6=E7=9A=84=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AE=B0=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了对 Swap Trade 编辑请求的特殊处理逻辑 - 实现了针对掉期交易的诊断功能,包括 JSON 解析和验证 - 添加了专门用于检测无效利率的诊断方法 - 优化了异常日志的输出格式和内容 - 集成了缓冲区管理和流位置重置功能 - 增强了错误信息的详细程度和可读性 --- YLErpWeb/App/ExceptionMiddleware.cs | 123 +++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 4 deletions(-) diff --git a/YLErpWeb/App/ExceptionMiddleware.cs b/YLErpWeb/App/ExceptionMiddleware.cs index a0c469d2..86b10b6c 100644 --- a/YLErpWeb/App/ExceptionMiddleware.cs +++ b/YLErpWeb/App/ExceptionMiddleware.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Http; using System.Buffers; +using System.Text; +using System.Text.Json; namespace YLErp.Web.App { @@ -17,6 +19,11 @@ namespace YLErp.Web.App public async Task Invoke(HttpContext context) { + if (IsSwapTradeEditRequest(context.Request)) + { + context.Request.EnableBuffering(); + } + try { await _next.Invoke(context); @@ -41,9 +48,17 @@ namespace YLErp.Web.App if (serviceExpcetion == null || serviceExpcetion.IsFaultError) { - var result = await request.BodyReader.ReadAsync(); - var reqBody = ConvertBufferToString(result.Buffer); - LogFactory.GetLogger(context.Request.Path.Value).Error(serviceExpcetion ?? exception, $"[query]:{request.QueryString.Value};[body]:{reqBody}"); + if (IsSwapTradeEditRequest(request)) + { + var diagnostic = await GetSwapIntervalDiagnosticAsync(request); + LogFactory.GetLogger(context.Request.Path.Value).Error(serviceExpcetion ?? exception, $"[query]:{request.QueryString.Value};{diagnostic}"); + } + else + { + var result = await request.BodyReader.ReadAsync(); + var reqBody = ConvertBufferToString(result.Buffer); + LogFactory.GetLogger(context.Request.Path.Value).Error(serviceExpcetion ?? exception, $"[query]:{request.QueryString.Value};[body]:{reqBody}"); + } } } catch (Exception ex) @@ -78,6 +93,106 @@ namespace YLErp.Web.App return System.Text.Encoding.UTF8.GetString(span); } + private static bool IsSwapTradeEditRequest(HttpRequest request) + { + return string.Equals(request.Path.Value, "/swaptrade2/tradeEditJson", StringComparison.OrdinalIgnoreCase); + } + + private static async Task GetSwapIntervalDiagnosticAsync(HttpRequest request) + { + if (!request.Body.CanSeek) + { + return "[swap-interval-diagnostic]:request-body-unavailable"; + } + + request.Body.Position = 0; + using var reader = new StreamReader(request.Body, Encoding.UTF8, false, 1024, leaveOpen: true); + var requestBody = await reader.ReadToEndAsync(); + request.Body.Position = 0; + + if (string.IsNullOrWhiteSpace(requestBody)) + { + return "[swap-interval-diagnostic]:request-body-empty"; + } + + try + { + using var document = JsonDocument.Parse(requestBody); + if (!document.RootElement.TryGetProperty("swap_positions", out var positions) || positions.ValueKind != JsonValueKind.Array) + { + return "[swap-interval-diagnostic]:swap_positions-missing"; + } + + var invalidRates = new List(); + var positionIndex = 0; + foreach (var position in positions.EnumerateArray()) + { + var positionId = position.TryGetProperty("id", out var id) ? id.ToString() : "missing"; + AddInvalidRateDiagnostics(position, "SwapIntervalList", false, positionIndex, positionId, invalidRates); + AddInvalidRateDiagnostics(position, "InterestSwapInterval", true, positionIndex, positionId, invalidRates); + if (position.TryGetProperty("Obervation", out var observation)) + { + AddInvalidRateDiagnostics(observation, "Obervation.ObservationInterval", true, positionIndex, positionId, invalidRates); + } + if (invalidRates.Count >= 10) + { + break; + } + positionIndex++; + } + + return invalidRates.Count == 0 + ? "[swap-interval-diagnostic]:no-invalid-rate-in-payload" + : $"[swap-interval-diagnostic]:{string.Join(";", invalidRates)}"; + } + catch (JsonException) + { + return "[swap-interval-diagnostic]:request-json-invalid"; + } + } + + private static void AddInvalidRateDiagnostics(JsonElement position, string source, bool serializedJson, int positionIndex, string positionId, List invalidRates) + { + if (!position.TryGetProperty(source, out var intervals)) + { + return; + } + + if (serializedJson) + { + if (intervals.ValueKind != JsonValueKind.String) + { + return; + } + + try + { + using var document = JsonDocument.Parse(intervals.GetString()); + intervals = document.RootElement.Clone(); + } + catch (JsonException) + { + invalidRates.Add($"positionIndex={positionIndex},positionId={positionId},source={source},interval-json-invalid"); + return; + } + } + + if (intervals.ValueKind != JsonValueKind.Array) + { + return; + } + + var intervalIndex = 0; + foreach (var interval in intervals.EnumerateArray()) + { + if ((!interval.TryGetProperty("Rate", out var rate) || rate.ValueKind == JsonValueKind.Null) && invalidRates.Count < 10) + { + invalidRates.Add($"positionIndex={positionIndex},positionId={positionId},source={source},intervalIndex={intervalIndex},rate={(rate.ValueKind == JsonValueKind.Null ? "null" : "missing")}"); + } + intervalIndex++; + } + } + private static string GetInnerExceptionMessage(Exception ex) { var exceptionStr = ex.Message; @@ -89,4 +204,4 @@ namespace YLErp.Web.App return exceptionStr; } } -} \ No newline at end of file +} From 0b5a64f42c27250ec63867b90e95f63ebcba3abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AC=B4=E6=94=BF=20=E6=97=B6?= Date: Tue, 11 Aug 2026 11:35:19 +0800 Subject: [PATCH 3/4] =?UTF-8?q?#EQD-7017=20=E5=9B=BD=E8=81=94=E6=B0=91?= =?UTF-8?q?=E7=94=9F-=E4=BA=A4=E6=98=93=E7=A1=AE=E8=AE=A4=E4=B9=A6?= =?UTF-8?q?=E4=B8=AD=E5=9F=BA=E6=9C=AC=E8=B4=B9=E7=8E=87=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E4=BF=9D=E7=95=994=E4=BD=8D=E5=B0=8F=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DocumentGenerator/TradeConfirmationGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs index ade5d22a..a4047271 100644 --- a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs +++ b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs @@ -471,7 +471,7 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator var notional = trade.OriginalStockEqvNotional ?? 0; var tradingFee = (double)swapPosition.PosiTradingFeePending; var basicFeeRate = notional == 0 ? 0 : tradingFee / notional * 100; - dic["基本费率"] = basicFeeRate.ToString("0.##"); + dic["基本费率"] = basicFeeRate.ToString("0.####"); // 期初现金交换比例和金额(使用初始预付金数据) dic["期初现金交换比例"] = initialMarginPosition != null From b0cdf0bcbc7fca51e2257fc0b4ab9279d32f9eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Tue, 11 Aug 2026 12:33:34 +0800 Subject: [PATCH 4/4] =?UTF-8?q?style(SwapModule):=20=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 SwapDealService.cs 中标准化了多行参数的缩进和换行 - 修复了 SwapTradeBaseService.cs 中的条件运算符周围的空格 - 统一了方法调用中参数的排列格式 - 改进了代码的可读性和一致性 - 遵循了团队的代码风格规范 --- YLErpDAL/Modules/SwapModule/SwapDealService.cs | 14 ++++++++++---- .../Modules/SwapModule/SwapTradeBaseService.cs | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index c9605a21..5598d46b 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -862,7 +862,9 @@ namespace YLErp.Modules.SwapModule var consumedInterest = position.InterestType == (int)InterestTypeEnum.复利 ? GetConsumedInterest(td.id, position.id, endDate) : 0m; - interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, calcLast||newCalcLast, consumedInterest)); + interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, + closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, + calcLast||newCalcLast, consumedInterest)); } } //当日有平仓或互换记录时,避免重复结算 @@ -1132,11 +1134,13 @@ namespace YLErp.Modules.SwapModule preEod.ValueDate = td.StartDate.Value; if (calcFirst) { - preEod.ValueDate= preEod.ValueDate.AddDays(-1); + preEod.ValueDate = preEod.ValueDate.AddDays(-1); } } - return InitSwapDealInterest(td, valueDate, endDate, rate, position, add, swap, posiPrincipal, closePrincipal, closePercent, annualDays, eventType, preEod, false, orginPv, calcFirst, calcLast, consumedInterest); + return InitSwapDealInterest(td, valueDate, endDate, rate, position, add, swap, posiPrincipal, + closePrincipal, closePercent, annualDays, eventType, preEod, false, + orginPv, calcFirst, calcLast, consumedInterest); } /// /// 初始化利息腿信息 @@ -1324,7 +1328,9 @@ namespace YLErp.Modules.SwapModule /// 是否年化 /// 年化天数 /// - public void CalcDailyCompoundInterest(DateTime endDate, swap_position position, decimal principal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m, decimal resetCarryInterest = 0m) + public void CalcDailyCompoundInterest(DateTime endDate, swap_position position, decimal principal, swap_flow_event flowEvent, + int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, + ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m, decimal resetCarryInterest = 0m) { var startDate = position.PosiStartDate; decimal interestProfitSum = 0; diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeBaseService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeBaseService.cs index bfc2e45d..8f2d3ed4 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeBaseService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeBaseService.cs @@ -369,7 +369,7 @@ namespace YLErp.Modules.SwapModule { interestStart = td.StartDate.Value; var exerciseDate = td.ExerciseDate.Value; - interestEnd = valueDate> exerciseDate? exerciseDate : valueDate; + interestEnd = valueDate > exerciseDate ? exerciseDate : valueDate; bool calcFirst = true; bool calcLast = true;