From 0417cb3a49c85eb26b058298f5f94a5b751b8320 Mon Sep 17 00:00:00 2001 From: ProcyonNAN <3189960265@qq.com> Date: Tue, 2 Jun 2026 20:33:17 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(extension-usage):=20=E6=94=B6=E7=B4=A7?= =?UTF-8?q?=E7=94=A8=E9=87=8F=E7=9C=8B=E6=9D=BF=E8=AE=BF=E9=97=AE=E6=9D=83?= =?UTF-8?q?=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 chatluna-usage 控制台数据服务增加 authority 限制 在服务端 get() 中校验 console client 的 auth、过期时间与权限等级,避免无 auth 拦截时推送用量数据 将 usage 看板入口和 RPC listener 限制为 console 与 auth 同时可用时注册 在前端根据登录状态隐藏 home slot,并在失权时清理用量状态与废弃未完成请求 在插件服务声明中补充可选 auth 依赖 --- packages/extension-usage/client/index.ts | 4 +- packages/extension-usage/client/state.ts | 54 +++++++++++++++++++++--- packages/extension-usage/package.json | 3 +- packages/extension-usage/src/index.ts | 51 ++++++++++++++++------ 4 files changed, 92 insertions(+), 20 deletions(-) diff --git a/packages/extension-usage/client/index.ts b/packages/extension-usage/client/index.ts index 3257719f8..089a37fdb 100644 --- a/packages/extension-usage/client/index.ts +++ b/packages/extension-usage/client/index.ts @@ -2,6 +2,7 @@ import { Context } from '@koishijs/client' import type {} from 'koishi-plugin-chatluna-usage' import charts from './charts' import home from './home.vue' +import { loggedIn } from './state' export default (ctx: Context) => { ctx.plugin(charts) @@ -9,6 +10,7 @@ export default (ctx: Context) => { ctx.slot({ type: 'home', component: home, - order: -1000 + order: -1000, + disabled: () => !loggedIn.value }) } diff --git a/packages/extension-usage/client/state.ts b/packages/extension-usage/client/state.ts index 77f0b4c60..282ad6dcc 100644 --- a/packages/extension-usage/client/state.ts +++ b/packages/extension-usage/client/state.ts @@ -3,6 +3,10 @@ import { send, store } from '@koishijs/client' import { ElMessage, ElMessageBox } from 'element-plus' import type { ChatLunaUsage } from 'koishi-plugin-chatluna-usage' +type AuthStore = typeof store & { + user?: { authority: number; expiredAt: number } +} + export type Scope = 'all' | 'year' | 'month' | 'week' | 'day' export const scopes: { label: string; value: Scope }[] = [ @@ -32,6 +36,12 @@ export const list = ref() let reqId = 0 let listReqId = 0 +const authStore = store as AuthStore + +export const loggedIn = computed(() => { + const user = authStore.user + return !!user && user.expiredAt > Date.now() && user.authority >= 1 +}) function scopeRange(value: Scope): [Date, Date] | undefined { if (value === 'all') return @@ -56,7 +66,9 @@ export function setScope(value: Scope) { } } -export const usage = computed(() => store.chatluna_usage) +export const usage = computed(() => + loggedIn.value ? store.chatluna_usage : undefined +) let timer: ReturnType | undefined let listTimer: ReturnType | undefined @@ -78,6 +90,7 @@ function refreshListSoon() { } export async function refresh() { + if (!loggedIn.value) return if (timer) { clearTimeout(timer) timer = undefined @@ -96,6 +109,7 @@ export async function refresh() { } export async function refreshList() { + if (!loggedIn.value) return if (listTimer) { clearTimeout(listTimer) listTimer = undefined @@ -143,6 +157,8 @@ export function resetFilters() { } export async function clearHistory() { + if (!loggedIn.value) return + try { await ElMessageBox.confirm( '这会删除所有 ChatLuna 用量历史数据,无法撤销。', @@ -158,6 +174,8 @@ export async function clearHistory() { return } + if (!loggedIn.value) return + const id = ++reqId const listId = ++listReqId try { @@ -203,6 +221,34 @@ export function time(value?: string | Date) { return value ? new Date(value).toLocaleString() : '-' } +watch( + loggedIn, + (ok) => { + if (ok) { + refreshSoon() + refreshListSoon() + } else { + if (timer) { + clearTimeout(timer) + timer = undefined + } + if (listTimer) { + clearTimeout(listTimer) + listTimer = undefined + } + // Invalidate any in-flight requests so their late responses are + // discarded by the reqId checks instead of writing stale data back. + reqId += 1 + listReqId += 1 + store.chatluna_usage = null + list.value = undefined + loading.value = false + listLoading.value = false + } + }, + { immediate: true } +) + watch( () => [ query.period, @@ -212,8 +258,7 @@ watch( query.start, query.end ], - () => refreshSoon(), - { immediate: true } + () => refreshSoon() ) watch( @@ -236,8 +281,7 @@ watch( () => { listQuery.page = 1 refreshListSoon() - }, - { immediate: true } + } ) watch( diff --git a/packages/extension-usage/package.json b/packages/extension-usage/package.json index 7e3fe72d3..2e503e828 100644 --- a/packages/extension-usage/package.json +++ b/packages/extension-usage/package.json @@ -72,7 +72,8 @@ "database" ], "optional": [ - "console" + "console", + "auth" ] } } diff --git a/packages/extension-usage/src/index.ts b/packages/extension-usage/src/index.ts index ca3e5bbc5..f6ced5425 100644 --- a/packages/extension-usage/src/index.ts +++ b/packages/extension-usage/src/index.ts @@ -1,18 +1,27 @@ -import { Context, Logger, Schema, Time } from 'koishi' -import { DataService } from '@koishijs/plugin-console' -import type { UsageMetadata } from '@langchain/core/messages' import { resolve } from 'path' +import type { UsageMetadata } from '@langchain/core/messages' +import { DataService } from '@koishijs/plugin-console' +import type { Client } from '@koishijs/plugin-console' +import { Context, Logger, Schema, Time } from 'koishi' import type { ModelUsageCallType } from 'koishi-plugin-chatluna/llm-core/platform/usage' const logger = new Logger('chatluna-usage') -class ChatLunaUsage extends DataService { +type AuthClient = Client & { + auth?: { + authority: number + expiredAt: number + } +} + +class ChatLunaUsage extends DataService { constructor( ctx: Context, public config: ChatLunaUsage.Config ) { super(ctx, 'chatluna_usage', { - immediate: true + immediate: true, + authority: 1 }) ctx.database.extend( @@ -73,13 +82,17 @@ class ChatLunaUsage extends DataService { if (!config.webui) return - ctx.inject(['console'], (ctx) => { - ctx.console.addListener('chatluna-usage/query', async (input) => - this.query(input) + ctx.inject(['console', 'auth'], (ctx) => { + ctx.console.addListener( + 'chatluna-usage/query', + async (input) => this.query(input), + { authority: 1 } ) - ctx.console.addListener('chatluna-usage/list', async (input) => - this.list(input) + ctx.console.addListener( + 'chatluna-usage/list', + async (input) => this.list(input), + { authority: 1 } ) ctx.console.addListener( @@ -88,7 +101,8 @@ class ChatLunaUsage extends DataService { await this.cleanup(before ? new Date(before) : undefined) await this.refresh() return { success: true } - } + }, + { authority: 1 } ) ctx.console.addEntry({ @@ -98,7 +112,18 @@ class ChatLunaUsage extends DataService { }) } - async get() { + async get(forced?: boolean, client?: Client) { + if (client) { + const auth = (client as AuthClient).auth + if ( + !this.config.webui || + !auth || + auth.expiredAt <= Date.now() || + auth.authority < 1 + ) { + return null + } + } return await this.query() } @@ -618,7 +643,7 @@ export const Config = ChatLunaUsage.Config export const inject = { required: ['chatluna', 'database'], - optional: ['console'] + optional: ['console', 'auth'] } export const name = 'chatluna-usage' From 3b7704c79d4aabcc99f387c0e9b485127c27fa31 Mon Sep 17 00:00:00 2001 From: ProcyonNAN <3189960265@qq.com> Date: Sun, 7 Jun 2026 17:16:47 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(extension-usage):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=94=A8=E9=87=8F=E9=9D=A2=E6=9D=BF=E6=9D=83=E9=99=90=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整 ChatLuna Usage WebUI 的权限控制实现。 在 DataService 与 console listener 上使用 authority: 1,由 Koishi console 负责权限拦截。 在客户端 home slot 上使用 disabled 根据 chatluna_usage store 控制渲染,避免未授权用户加载用量面板。 移除前端手动 auth 状态判断,并撤回 package.json 中对 auth 可选服务的声明。 --- packages/extension-usage/client/index.ts | 19 +++++---- packages/extension-usage/client/state.ts | 54 +++--------------------- packages/extension-usage/package.json | 3 +- packages/extension-usage/src/index.ts | 33 +++------------ 4 files changed, 24 insertions(+), 85 deletions(-) diff --git a/packages/extension-usage/client/index.ts b/packages/extension-usage/client/index.ts index 089a37fdb..c7d4d5936 100644 --- a/packages/extension-usage/client/index.ts +++ b/packages/extension-usage/client/index.ts @@ -1,16 +1,19 @@ -import { Context } from '@koishijs/client' +import { defineAsyncComponent } from 'vue' +import { Context, store } from '@koishijs/client' import type {} from 'koishi-plugin-chatluna-usage' -import charts from './charts' -import home from './home.vue' -import { loggedIn } from './state' export default (ctx: Context) => { - ctx.plugin(charts) - ctx.slot({ type: 'home', - component: home, + component: defineAsyncComponent(async () => { + const [home, charts] = await Promise.all([ + import('./home.vue'), + import('./charts') + ]) + ctx.plugin(charts.default) + return home.default + }), order: -1000, - disabled: () => !loggedIn.value + disabled: () => !store.chatluna_usage }) } diff --git a/packages/extension-usage/client/state.ts b/packages/extension-usage/client/state.ts index 282ad6dcc..77f0b4c60 100644 --- a/packages/extension-usage/client/state.ts +++ b/packages/extension-usage/client/state.ts @@ -3,10 +3,6 @@ import { send, store } from '@koishijs/client' import { ElMessage, ElMessageBox } from 'element-plus' import type { ChatLunaUsage } from 'koishi-plugin-chatluna-usage' -type AuthStore = typeof store & { - user?: { authority: number; expiredAt: number } -} - export type Scope = 'all' | 'year' | 'month' | 'week' | 'day' export const scopes: { label: string; value: Scope }[] = [ @@ -36,12 +32,6 @@ export const list = ref() let reqId = 0 let listReqId = 0 -const authStore = store as AuthStore - -export const loggedIn = computed(() => { - const user = authStore.user - return !!user && user.expiredAt > Date.now() && user.authority >= 1 -}) function scopeRange(value: Scope): [Date, Date] | undefined { if (value === 'all') return @@ -66,9 +56,7 @@ export function setScope(value: Scope) { } } -export const usage = computed(() => - loggedIn.value ? store.chatluna_usage : undefined -) +export const usage = computed(() => store.chatluna_usage) let timer: ReturnType | undefined let listTimer: ReturnType | undefined @@ -90,7 +78,6 @@ function refreshListSoon() { } export async function refresh() { - if (!loggedIn.value) return if (timer) { clearTimeout(timer) timer = undefined @@ -109,7 +96,6 @@ export async function refresh() { } export async function refreshList() { - if (!loggedIn.value) return if (listTimer) { clearTimeout(listTimer) listTimer = undefined @@ -157,8 +143,6 @@ export function resetFilters() { } export async function clearHistory() { - if (!loggedIn.value) return - try { await ElMessageBox.confirm( '这会删除所有 ChatLuna 用量历史数据,无法撤销。', @@ -174,8 +158,6 @@ export async function clearHistory() { return } - if (!loggedIn.value) return - const id = ++reqId const listId = ++listReqId try { @@ -221,34 +203,6 @@ export function time(value?: string | Date) { return value ? new Date(value).toLocaleString() : '-' } -watch( - loggedIn, - (ok) => { - if (ok) { - refreshSoon() - refreshListSoon() - } else { - if (timer) { - clearTimeout(timer) - timer = undefined - } - if (listTimer) { - clearTimeout(listTimer) - listTimer = undefined - } - // Invalidate any in-flight requests so their late responses are - // discarded by the reqId checks instead of writing stale data back. - reqId += 1 - listReqId += 1 - store.chatluna_usage = null - list.value = undefined - loading.value = false - listLoading.value = false - } - }, - { immediate: true } -) - watch( () => [ query.period, @@ -258,7 +212,8 @@ watch( query.start, query.end ], - () => refreshSoon() + () => refreshSoon(), + { immediate: true } ) watch( @@ -281,7 +236,8 @@ watch( () => { listQuery.page = 1 refreshListSoon() - } + }, + { immediate: true } ) watch( diff --git a/packages/extension-usage/package.json b/packages/extension-usage/package.json index 2e503e828..7e3fe72d3 100644 --- a/packages/extension-usage/package.json +++ b/packages/extension-usage/package.json @@ -72,8 +72,7 @@ "database" ], "optional": [ - "console", - "auth" + "console" ] } } diff --git a/packages/extension-usage/src/index.ts b/packages/extension-usage/src/index.ts index f6ced5425..0bfc0cc3f 100644 --- a/packages/extension-usage/src/index.ts +++ b/packages/extension-usage/src/index.ts @@ -1,20 +1,12 @@ -import { resolve } from 'path' -import type { UsageMetadata } from '@langchain/core/messages' -import { DataService } from '@koishijs/plugin-console' -import type { Client } from '@koishijs/plugin-console' import { Context, Logger, Schema, Time } from 'koishi' +import { DataService } from '@koishijs/plugin-console' +import type { UsageMetadata } from '@langchain/core/messages' +import { resolve } from 'path' import type { ModelUsageCallType } from 'koishi-plugin-chatluna/llm-core/platform/usage' const logger = new Logger('chatluna-usage') -type AuthClient = Client & { - auth?: { - authority: number - expiredAt: number - } -} - -class ChatLunaUsage extends DataService { +class ChatLunaUsage extends DataService { constructor( ctx: Context, public config: ChatLunaUsage.Config @@ -82,7 +74,7 @@ class ChatLunaUsage extends DataService { if (!config.webui) return - ctx.inject(['console', 'auth'], (ctx) => { + ctx.inject(['console'], (ctx) => { ctx.console.addListener( 'chatluna-usage/query', async (input) => this.query(input), @@ -112,18 +104,7 @@ class ChatLunaUsage extends DataService { }) } - async get(forced?: boolean, client?: Client) { - if (client) { - const auth = (client as AuthClient).auth - if ( - !this.config.webui || - !auth || - auth.expiredAt <= Date.now() || - auth.authority < 1 - ) { - return null - } - } + async get() { return await this.query() } @@ -643,7 +624,7 @@ export const Config = ChatLunaUsage.Config export const inject = { required: ['chatluna', 'database'], - optional: ['console', 'auth'] + optional: ['console'] } export const name = 'chatluna-usage'