From 431c6de8d2c731f33577903d1082c0ab862dd310 Mon Sep 17 00:00:00 2001 From: fawney19 Date: Mon, 5 Jan 2026 19:32:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E7=94=A8=E9=87=8F?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=94=AF=E6=8C=81=E5=88=86=E9=A1=B5=E3=80=81?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=92=8C=E5=AF=86=E9=92=A5=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 用户用量API增加search参数支持密钥名、模型名搜索 - 用户用量API返回api_key信息(id、name、display) - 用户页面记录表格增加密钥列显示 - 前端统一管理员和用户页面的分页/搜索逻辑 - 后端LIKE查询增加特殊字符转义防止SQL注入 - 添加escape_like_pattern和safe_truncate_escaped工具函数 --- frontend/package-lock.json | 19 ++++- frontend/src/api/me.ts | 6 ++ frontend/src/api/usage.ts | 2 +- .../usage/components/UsageRecordsTable.vue | 79 ++++++++++++------- .../usage/composables/useUsageData.ts | 50 ++++++------ frontend/src/mocks/handler.ts | 17 +++- frontend/src/views/shared/Usage.vue | 51 +++++------- src/api/admin/usage/routes.py | 51 +++++++++--- src/api/user_me/routes.py | 43 +++++++++- src/utils/database_helpers.py | 53 +++++++++++++ 10 files changed, 262 insertions(+), 109 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 539af91..a97141a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -262,6 +262,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -305,6 +306,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1598,6 +1600,7 @@ "integrity": "sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -1676,6 +1679,7 @@ "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", @@ -2004,6 +2008,7 @@ "integrity": "sha512-oWtNM89Np+YsQO3ttT5i1Aer/0xbzQzp66NzuJn/U16bB7MnvSzdLKXgk1kkMLYyKSSzA2ajzqMkYheaE9opuQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "4.0.10", "fflate": "^0.8.2", @@ -2301,6 +2306,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2602,6 +2608,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.2", "caniuse-lite": "^1.0.30001741", @@ -2718,6 +2725,7 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", "license": "MIT", + "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -2940,6 +2948,7 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -3192,6 +3201,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4091,6 +4101,7 @@ "integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@acemir/cssom": "^0.9.23", "@asamuzakjp/dom-selector": "^6.7.4", @@ -4655,6 +4666,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4722,6 +4734,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5752,6 +5765,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5845,6 +5859,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -5920,6 +5935,7 @@ "integrity": "sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.10", "@vitest/mocker": "4.0.10", @@ -6004,6 +6020,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz", "integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.21", "@vue/compiler-sfc": "3.5.21", @@ -6036,7 +6053,6 @@ "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0", @@ -6061,7 +6077,6 @@ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, diff --git a/frontend/src/api/me.ts b/frontend/src/api/me.ts index 0534b33..aa4fb63 100644 --- a/frontend/src/api/me.ts +++ b/frontend/src/api/me.ts @@ -62,6 +62,11 @@ export interface UsageRecordDetail { cache_creation_price_per_1m?: number cache_read_price_per_1m?: number price_per_request?: number // 按次计费价格 + api_key?: { + id: string + name: string + display: string + } } // 模型统计接口 @@ -192,6 +197,7 @@ export const meApi = { async getUsage(params?: { start_date?: string end_date?: string + search?: string // 通用搜索:密钥名、模型名 limit?: number offset?: number }): Promise { diff --git a/frontend/src/api/usage.ts b/frontend/src/api/usage.ts index 85d543d..8865a0d 100644 --- a/frontend/src/api/usage.ts +++ b/frontend/src/api/usage.ts @@ -164,9 +164,9 @@ export const usageApi = { async getAllUsageRecords(params?: { start_date?: string end_date?: string + search?: string // 通用搜索:用户名、密钥名、模型名、提供商名 user_id?: string // UUID username?: string - user_api_key_name?: string model?: string provider?: string status?: string // 'stream' | 'standard' | 'error' diff --git a/frontend/src/features/usage/components/UsageRecordsTable.vue b/frontend/src/features/usage/components/UsageRecordsTable.vue index c1d6a61..e6057c8 100644 --- a/frontend/src/features/usage/components/UsageRecordsTable.vue +++ b/frontend/src/features/usage/components/UsageRecordsTable.vue @@ -32,6 +32,17 @@