From ca15a527ef824a7320b6992ca631d5a6dbbf00fe Mon Sep 17 00:00:00 2001 From: qian Date: Fri, 14 Aug 2026 00:19:49 +0800 Subject: [PATCH 1/2] =?UTF-8?q?improve(ui):=E4=BC=98=E5=8C=96=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=90=8E=E5=8F=B0=E7=9A=84=E6=97=B6=E9=97=B4=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/admin/src/composables/useDateRange.ts | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 web/admin/src/composables/useDateRange.ts diff --git a/web/admin/src/composables/useDateRange.ts b/web/admin/src/composables/useDateRange.ts new file mode 100644 index 0000000..0c33ac6 --- /dev/null +++ b/web/admin/src/composables/useDateRange.ts @@ -0,0 +1,43 @@ +import type { ShortcutType } from '@arco-design/web-vue' + +// 日期辅助(native,避免引入 dayjs 依赖) +function pad2(n: number): string { + return String(n).padStart(2, '0') +} +function toDateTimeStr(d: Date): string { + return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}` +} +// 默认查询当天:起始时间为当天 0 点,截止时间留空(后端按「到现在」实时处理)。 +// 日期选择器里的「现在」仅用于展示;查询时截止仍为默认「现在」则不传 end_date,避免固定截止时间漏掉后续新记录。 +// 该值在模块加载时固定一次,页面间共享,保证「截止等于默认现在则不下发 end_date」的判据一致。 +const defaultEnd = toDateTimeStr(new Date()) + +function defaultTodayRange(): string[] { + const d = new Date() + return [`${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} 00:00:00`, defaultEnd] +} + +// 指定偏移天数的当天 0 点 / 23:59:59(offsetDays=0 表示今天) +function dateStartStr(offsetDays: number): string { + const d = new Date() + d.setDate(d.getDate() - offsetDays) + return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} 00:00:00` +} +function dateEndStr(offsetDays: number): string { + const d = new Date() + d.setDate(d.getDate() - offsetDays) + return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} 23:59:59` +} + +// 日期时间选择器底部快捷选项:今天 / 昨天 / 近三天 / 近一周。 +// 截止「现在」的选项复用 defaultEnd,保持「截止不传 → 后端按到现在实时查询」的语义。 +const quickDateRanges: ShortcutType[] = [ + { label: '今天', value: () => defaultTodayRange() }, + { label: '昨天', value: () => [dateStartStr(1), dateEndStr(1)] }, + { label: '近三天', value: () => [dateStartStr(2), defaultEnd] }, + { label: '近一周', value: () => [dateStartStr(6), defaultEnd] }, +] + +export function useDateRange() { + return { defaultEnd, defaultTodayRange, quickDateRanges } +} From 0e8280e42c0b5c7d924d3278ef8f6396ddae9219 Mon Sep 17 00:00:00 2001 From: chengqian Date: Fri, 14 Aug 2026 14:31:39 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(log):=E4=BF=AE=E5=A4=8D=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E6=97=A5=E5=BF=97=E5=9C=A8=E9=85=8D=E7=BD=AE=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E6=95=B0=E6=8D=AE=E5=BA=93=E7=9A=84=E6=83=85=E5=86=B5?= =?UTF-8?q?=E4=B8=8B=E8=A2=AB=E6=88=AA=E6=96=AD=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/logic/relay/provider.go | 23 ++++++++++++++++++----- internal/logic/relay/provider_test.go | 17 +++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/internal/logic/relay/provider.go b/internal/logic/relay/provider.go index 6690acd..ff18f0c 100644 --- a/internal/logic/relay/provider.go +++ b/internal/logic/relay/provider.go @@ -421,10 +421,21 @@ func buildUsageLogDO(record *common.UsageRecord) do.BilUsageLogs { } } -// maxAuditBodyLen 审计日志请求体/响应体/任务结果的字节截断上限(2 KiB)。 +// maxAuditBodyLen 未配置独立审计库时,审计日志请求体/响应体/任务结果的字节截断上限(2 KiB)。 // 落库前由 truncateBody 统一截断;流式响应保留首部 + 末尾若干 SSE 消息。 +// 配置了独立审计库(database.audit)时 auditBodyMaxLen 返回 0,不截断、按审计规则完整记录。 const maxAuditBodyLen = 2048 +// auditBodyMaxLen 返回审计请求体/响应体/任务结果的落库截断上限。 +// 配置了独立审计库时返回 0(truncateBody 视为不截断),此时有充足存储空间按审计规则完整记录; +// 未配置时返回 maxAuditBodyLen,防止与业务数据共用主库导致库膨胀。 +func auditBodyMaxLen() int { + if lcommon.IsAuditDBConfigured() { + return 0 + } + return maxAuditBodyLen +} + // RecordAudit 实现 DataProvider.RecordAudit // 异步写入请求审计日志,同时按系统级别和租户级别分别处理请求/响应体 func (p *DataProviderImpl) RecordAudit(ctx context.Context, record *common.AuditRecord) { @@ -453,8 +464,9 @@ func (p *DataProviderImpl) RecordAudit(ctx context.Context, record *common.Audit // 按租户级别处理 tntReq, tntResp := lcommon.ApplyAuditLevel(tenantLevel, record.RequestBody, record.ResponseBody, record.IsStream, record.Path) - // 截断过长的内容 - maxBodyLen := maxAuditBodyLen + // 截断过长的内容:配置了独立审计库时完整记录(auditBodyMaxLen=0 不截断), + // 否则截断到 2 KiB,防止与业务数据共用主库导致库膨胀。 + maxBodyLen := auditBodyMaxLen() sysReq, sysResp = truncateBody(sysReq, maxBodyLen), truncateBody(sysResp, maxBodyLen) tntReq, tntResp = truncateBody(tntReq, maxBodyLen), truncateBody(tntResp, maxBodyLen) @@ -556,7 +568,7 @@ func (p *DataProviderImpl) UpdateTaskAudit(ctx context.Context, record *common.A } else if globalLevel == lcommon.AuditLevelMasked { taskResult = lcommon.MaskSensitiveData(taskResult) } - taskResult = truncateBody(taskResult, maxAuditBodyLen) + taskResult = truncateBody(taskResult, auditBodyMaxLen()) // 上游响应头仅在 full 级别记录 upstreamHeadersJSON := "null" @@ -615,7 +627,8 @@ func (p *DataProviderImpl) UpdateTaskAudit(ctx context.Context, record *common.A } func truncateBody(s string, maxLen int) string { - if len(s) <= maxLen { + // maxLen <= 0 表示不截断(配置了独立审计库时完整记录) + if maxLen <= 0 || len(s) <= maxLen { return s } // 流式响应:保留首部 + 最后 20 条消息,截断中间 diff --git a/internal/logic/relay/provider_test.go b/internal/logic/relay/provider_test.go index 4bfc872..45c3f77 100644 --- a/internal/logic/relay/provider_test.go +++ b/internal/logic/relay/provider_test.go @@ -84,6 +84,23 @@ func TestTruncateBody_LongNonStreamTruncated(t *testing.T) { } } +func TestTruncateBody_ZeroMaxLenDisablesTruncation(t *testing.T) { + // 配置了独立审计库时 auditBodyMaxLen 返回 0,truncateBody 应原样返回不截断。 + s := strings.Repeat("x", 10000) + if got := truncateBody(s, 0); got != s { + t.Errorf("maxLen=0 should disable truncation and return input verbatim") + } + // 负数 maxLen 同样视为不截断(防御性) + if got := truncateBody(s, -1); got != s { + t.Errorf("negative maxLen should disable truncation and return input verbatim") + } + // 流式输入在 maxLen=0 时同样完整保留,不进入流式截断分支 + stream := "data: first\ndata: second\ndata: [DONE]" + if got := truncateBody(stream, 0); got != stream { + t.Errorf("stream body with maxLen=0 should be returned verbatim") + } +} + func TestTruncateStreamBody_KeepsHeadAndTailDropsMiddle(t *testing.T) { // 40 行短 SSE,每行 "data: Lxx"(9 字节)。尾部 20 行 = 199 字节。 // maxLen=250 留出 headBudget>0,结果形如 head + marker + tail。