feat: 完善鉴权
This commit is contained in:
@@ -8,9 +8,6 @@ MAX_TOKENS=500
|
|||||||
TEMPERATURE=0.7
|
TEMPERATURE=0.7
|
||||||
OPENAI_STREAM_MAX_SSE_LINE_BYTES=1048576
|
OPENAI_STREAM_MAX_SSE_LINE_BYTES=1048576
|
||||||
|
|
||||||
# 安全设置,可选
|
|
||||||
# ACCESS_TOKEN=your-access-token
|
|
||||||
|
|
||||||
# 缓存配置
|
# 缓存配置
|
||||||
ENABLE_CACHE=true
|
ENABLE_CACHE=true
|
||||||
CACHE_EXPIRATION=86400
|
CACHE_EXPIRATION=86400
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- app/components/CopyCom.vue - 通用复制按钮,带图标切换动画 -->
|
<!-- app/components/CopyCom.vue - 通用复制按钮 -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Check, Copy } from "lucide-vue-next";
|
import { Check, Copy } from "lucide-vue-next";
|
||||||
import type { HTMLAttributes } from "vue";
|
import type { HTMLAttributes } from "vue";
|
||||||
@@ -14,10 +14,12 @@ const {
|
|||||||
variant = "outline",
|
variant = "outline",
|
||||||
size = "sm",
|
size = "sm",
|
||||||
timeout = 2000,
|
timeout = 2000,
|
||||||
disableDefaultCopy = false
|
disableDefaultCopy = false,
|
||||||
|
disabled = false
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
/** 要复制的文本 */
|
/** 要复制的文本 */
|
||||||
text?: string;
|
text?: string;
|
||||||
|
// eslint-disable-next-line vue/require-default-prop
|
||||||
class?: HTMLAttributes["class"] | undefined;
|
class?: HTMLAttributes["class"] | undefined;
|
||||||
variant?: ButtonVariants["variant"];
|
variant?: ButtonVariants["variant"];
|
||||||
size?: ButtonVariants["size"];
|
size?: ButtonVariants["size"];
|
||||||
@@ -25,6 +27,8 @@ const {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
/** 禁用内置复制行为,只触发状态变化(外部自行处理复制) */
|
/** 禁用内置复制行为,只触发状态变化(外部自行处理复制) */
|
||||||
disableDefaultCopy?: boolean;
|
disableDefaultCopy?: boolean;
|
||||||
|
/** 禁用按鈕,不可点击且展示禁用样式 */
|
||||||
|
disabled?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -34,6 +38,7 @@ const emit = defineEmits<{
|
|||||||
const isCopied = ref(false);
|
const isCopied = ref(false);
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
|
if (disabled) return;
|
||||||
if (!disableDefaultCopy) {
|
if (!disableDefaultCopy) {
|
||||||
copy(text);
|
copy(text);
|
||||||
}
|
}
|
||||||
@@ -52,6 +57,7 @@ defineExpose({ isCopied });
|
|||||||
type="button"
|
type="button"
|
||||||
:variant="variant"
|
:variant="variant"
|
||||||
:size="size"
|
:size="size"
|
||||||
|
:disabled="disabled"
|
||||||
:class="cn('relative overflow-hidden pl-8', className)"
|
:class="cn('relative overflow-hidden pl-8', className)"
|
||||||
@click.prevent.stop="handleCopy"
|
@click.prevent.stop="handleCopy"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<!-- app/pages/dashboard.vue - 运行状态与最近问答记录页面 -->
|
<!-- app/pages/dashboard.vue - 运行状态与最近问答记录页面 -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ArrowPathIcon } from "@heroicons/vue/24/outline";
|
import { RefreshCw } from "lucide-vue-next";
|
||||||
|
|
||||||
import QaRecordDetail from "@/components/dashboard/QaRecordDetail.vue";
|
import QaRecordDetail from "@/components/dashboard/QaRecordDetail.vue";
|
||||||
import QaRecordsTable from "@/components/dashboard/QaRecordsTable.vue";
|
import QaRecordsTable from "@/components/dashboard/QaRecordsTable.vue";
|
||||||
@@ -64,17 +64,17 @@ const handleRefreshToken = async () => {
|
|||||||
>
|
>
|
||||||
{{ authStore.user.apiToken }}
|
{{ authStore.user.apiToken }}
|
||||||
</code>
|
</code>
|
||||||
<CopyCom :text="authStore.user.apiToken" />
|
<CopyCom :text="authStore.user.apiToken" :disabled="refreshing" />
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
:disabled="refreshing"
|
:disabled="refreshing"
|
||||||
title="刷新 Token"
|
title="刷新 Token"
|
||||||
|
class="flex items-center gap-1 w-fit px-2.5"
|
||||||
@click="handleRefreshToken"
|
@click="handleRefreshToken"
|
||||||
>
|
>
|
||||||
<ArrowPathIcon
|
<RefreshCw :class="['size-4', refreshing ? 'animate-spin' : '']" />
|
||||||
:class="['size-4', refreshing ? 'animate-spin' : '']"
|
<span>刷新 Token</span>
|
||||||
/>
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class AnswerService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 读取运行统计;配置 ACCESS_TOKEN 时由服务端校验 */
|
/** 读取运行统计(需登录) */
|
||||||
public static getStats() {
|
public static getStats() {
|
||||||
return BaseClientService.get<IStatsResponse>(
|
return BaseClientService.get<IStatsResponse>(
|
||||||
`${AnswerService.basePath}/stats`
|
`${AnswerService.basePath}/stats`
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export default withNuxt(
|
|||||||
'vue/html-self-closing': ['error', { html: { void: 'any' } }],
|
'vue/html-self-closing': ['error', { html: { void: 'any' } }],
|
||||||
'no-unused-vars': 'warn',
|
'no-unused-vars': 'warn',
|
||||||
'@typescript-eslint/no-unused-vars': 'warn',
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
|
"@typescript-eslint/no-extraneous-class": "off",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Vendored
+1
-17
@@ -1,10 +1,4 @@
|
|||||||
// server/api/cache/clear.post.ts - 清空答题缓存接口
|
// server/api/cache/clear.post.ts - 清空答题缓存接口
|
||||||
import { setResponseStatus } from "h3";
|
|
||||||
|
|
||||||
import {
|
|
||||||
invalidAccessTokenResponse,
|
|
||||||
verifyAccessToken
|
|
||||||
} from "~~/server/utils/accessToken";
|
|
||||||
import { answerCache } from "~~/server/utils/cache";
|
import { answerCache } from "~~/server/utils/cache";
|
||||||
import { serverEnv } from "~~/server/utils/env";
|
import { serverEnv } from "~~/server/utils/env";
|
||||||
import { createApiLogger } from "~~/server/utils/logging";
|
import { createApiLogger } from "~~/server/utils/logging";
|
||||||
@@ -12,22 +6,12 @@ import { createApiLogger } from "~~/server/utils/logging";
|
|||||||
/**
|
/**
|
||||||
* 清空内存缓存
|
* 清空内存缓存
|
||||||
*
|
*
|
||||||
* 这是一个管理类接口,配置 ACCESS_TOKEN 时必须携带正确 token
|
* 这是一个管理类接口,必须携带有效 session cookie(由 api-auth middleware 统一鉴权)
|
||||||
* 清理的是当前 Nuxt 进程内缓存;多实例部署时,每个实例都有自己的内存缓存
|
* 清理的是当前 Nuxt 进程内缓存;多实例部署时,每个实例都有自己的内存缓存
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler((event) => {
|
export default defineEventHandler((event) => {
|
||||||
const logger = createApiLogger(event, "api.cache.clear");
|
const logger = createApiLogger(event, "api.cache.clear");
|
||||||
|
|
||||||
// 没配置 token 时保持旧服务的开放行为;配置后用于简单保护管理接口
|
|
||||||
if (!verifyAccessToken(event)) {
|
|
||||||
setResponseStatus(event, 403);
|
|
||||||
logger.warn("invalid_access_token");
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: invalidAccessTokenResponse().msg
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存被关闭时不报错,只明确告诉调用方当前没有缓存可清
|
// 缓存被关闭时不报错,只明确告诉调用方当前没有缓存可清
|
||||||
if (!serverEnv.enableCache || !answerCache) {
|
if (!serverEnv.enableCache || !answerCache) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// server/api/records.get.ts - 用户问答记录接口,供 Nuxt Dashboard 表格展示
|
// server/api/records.get.ts - 用户问答记录接口,供 Nuxt Dashboard 表格展示
|
||||||
import { getQuery, setResponseStatus } from "h3";
|
import { getQuery } from "h3";
|
||||||
|
|
||||||
import { getAuthSession } from "~~/server/utils/auth";
|
|
||||||
import { createApiLogger } from "~~/server/utils/logging";
|
import { createApiLogger } from "~~/server/utils/logging";
|
||||||
import { getQaRecords } from "~~/server/utils/runtimeState";
|
import { getQaRecords } from "~~/server/utils/runtimeState";
|
||||||
|
|
||||||
@@ -9,27 +8,13 @@ import { getQaRecords } from "~~/server/utils/runtimeState";
|
|||||||
* 用户问答记录接口(分页,最新在前)
|
* 用户问答记录接口(分页,最新在前)
|
||||||
*
|
*
|
||||||
* 安全边界:
|
* 安全边界:
|
||||||
* - 必须携带有效 session cookie(登录用户)
|
* - 必须携带有效 session cookie(由 api-auth middleware 统一鉴权)
|
||||||
* - 只返回当前登录用户自己的记录,用 userId 严格隔离
|
* - 只返回当前登录用户自己的记录,用 userId 严格隔离
|
||||||
* - 不包含 OpenAI 响应体、API Key 或内部错误
|
* - 不包含 OpenAI 响应体、API Key 或内部错误
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const logger = createApiLogger(event, "api.records");
|
const logger = createApiLogger(event, "api.records");
|
||||||
|
|
||||||
const session = await getAuthSession(event);
|
|
||||||
if (!session) {
|
|
||||||
setResponseStatus(event, 401);
|
|
||||||
logger.warn("unauthorized");
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: "未登录",
|
|
||||||
records: [],
|
|
||||||
page: 1,
|
|
||||||
size: 10,
|
|
||||||
total: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = getQuery(event);
|
const query = getQuery(event);
|
||||||
const rawPage = Number.parseInt(query.page?.toString() || "1", 10);
|
const rawPage = Number.parseInt(query.page?.toString() || "1", 10);
|
||||||
const rawSize = Number.parseInt(query.size?.toString() || "10", 10);
|
const rawSize = Number.parseInt(query.size?.toString() || "10", 10);
|
||||||
@@ -38,7 +23,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
: 10;
|
: 10;
|
||||||
const page = Number.isFinite(rawPage) ? Math.max(rawPage, 1) : 1;
|
const page = Number.isFinite(rawPage) ? Math.max(rawPage, 1) : 1;
|
||||||
|
|
||||||
const { records, total } = await getQaRecords(session.user.id, {
|
const { records, total } = await getQaRecords(event.context.auth!.user.id, {
|
||||||
page,
|
page,
|
||||||
size
|
size
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-18
@@ -1,10 +1,4 @@
|
|||||||
// server/api/stats.get.ts - 服务运行统计接口
|
// server/api/stats.get.ts - 服务运行统计接口
|
||||||
import { setResponseStatus } from "h3";
|
|
||||||
|
|
||||||
import {
|
|
||||||
invalidAccessTokenResponse,
|
|
||||||
verifyAccessToken
|
|
||||||
} from "~~/server/utils/accessToken";
|
|
||||||
import { createApiLogger } from "~~/server/utils/logging";
|
import { createApiLogger } from "~~/server/utils/logging";
|
||||||
import { getRuntimeStats } from "~~/server/utils/runtimeState";
|
import { getRuntimeStats } from "~~/server/utils/runtimeState";
|
||||||
|
|
||||||
@@ -12,21 +6,10 @@ import { getRuntimeStats } from "~~/server/utils/runtimeState";
|
|||||||
* 运行统计接口
|
* 运行统计接口
|
||||||
*
|
*
|
||||||
* 统计信息包含 uptime、模型名、缓存数量、最近问答记录数量
|
* 统计信息包含 uptime、模型名、缓存数量、最近问答记录数量
|
||||||
* 因为它能反映服务使用情况,所以和旧 Python 服务一样走可选 ACCESS_TOKEN 校验
|
* 必须携带有效 session cookie(由 api-auth middleware 统一鉴权)
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler((event) => {
|
export default defineEventHandler((event) => {
|
||||||
const logger = createApiLogger(event, "api.stats");
|
const logger = createApiLogger(event, "api.stats");
|
||||||
|
|
||||||
// 未配置 ACCESS_TOKEN 时 verifyAccessToken 会放行;配置后必须携带正确 token
|
|
||||||
if (!verifyAccessToken(event)) {
|
|
||||||
setResponseStatus(event, 403);
|
|
||||||
logger.warn("invalid_access_token");
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: invalidAccessTokenResponse().msg
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRuntimeStats 会实时清理过期缓存,再返回有效缓存数量
|
// getRuntimeStats 会实时清理过期缓存,再返回有效缓存数量
|
||||||
logger.info("finish_success");
|
logger.info("finish_success");
|
||||||
return getRuntimeStats();
|
return getRuntimeStats();
|
||||||
|
|||||||
@@ -4,21 +4,13 @@ import { randomBytes } from "node:crypto";
|
|||||||
|
|
||||||
import { setResponseStatus } from "h3";
|
import { setResponseStatus } from "h3";
|
||||||
|
|
||||||
import { getAuthSession } from "~~/server/utils/auth";
|
|
||||||
import { prisma } from "~~/server/utils/db";
|
import { prisma } from "~~/server/utils/db";
|
||||||
import { createApiLogger } from "~~/server/utils/logging";
|
import { createApiLogger } from "~~/server/utils/logging";
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const logger = createApiLogger(event, "api.user.refresh-token");
|
const logger = createApiLogger(event, "api.user.refresh-token");
|
||||||
|
|
||||||
const session = await getAuthSession(event);
|
const userId = event.context.auth!.user.id;
|
||||||
if (!session) {
|
|
||||||
setResponseStatus(event, 401);
|
|
||||||
logger.warn("unauthorized");
|
|
||||||
return { success: false, msg: "未登录" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const userId = session.user.id;
|
|
||||||
const newToken = randomBytes(16).toString("hex");
|
const newToken = randomBytes(16).toString("hex");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// server/middleware/api-auth.ts - 统一 API 鉴权层,所有非公开 /api/* 请求都必须通过此处
|
||||||
|
import { createError, getRequestURL } from "h3";
|
||||||
|
|
||||||
|
import { isPublicApiRoute } from "~~/server/utils/api-auth-rules";
|
||||||
|
import { getAuthSession } from "~~/server/utils/auth";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一 API 鉴权 middleware
|
||||||
|
*
|
||||||
|
* 策略:默认所有 /api/* 需要登录,只有 publicApiRoutes 里的路由可以公开访问。
|
||||||
|
* 鉴权通过后将 session 挂到 event.context.auth,handler 直接取用,无需重复查询。
|
||||||
|
*
|
||||||
|
* 不影响页面渲染、静态资源或 Nuxt 内部请求。
|
||||||
|
*/
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const { pathname } = getRequestURL(event);
|
||||||
|
const method = event.method;
|
||||||
|
|
||||||
|
// 只处理 /api/ 路径
|
||||||
|
if (!pathname.startsWith("/api/")) return;
|
||||||
|
|
||||||
|
// OPTIONS 预检请求不需要鉴权,CORS headers 由 nuxt.config routeRules 统一设置
|
||||||
|
if (method === "OPTIONS") return;
|
||||||
|
|
||||||
|
// 公开路由直接放行
|
||||||
|
if (isPublicApiRoute(pathname, method)) return;
|
||||||
|
|
||||||
|
const session = await getAuthSession(event);
|
||||||
|
if (!session) {
|
||||||
|
throw createError({ statusCode: 401, statusMessage: "未登录" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 挂到 event.context,后续 handler 直接读取,不再重复查询 session
|
||||||
|
event.context.auth = session;
|
||||||
|
});
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// server/types.d.ts - H3EventContext 扩展,供所有 server handler 使用
|
||||||
|
import type { auth } from "~~/server/utils/auth";
|
||||||
|
|
||||||
|
type BetterAuthSession = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof auth.api.getSession>>
|
||||||
|
>;
|
||||||
|
|
||||||
|
declare module "h3" {
|
||||||
|
interface H3EventContext {
|
||||||
|
/**
|
||||||
|
* Better Auth session,由 api-auth middleware 在非公开路由上设置。
|
||||||
|
* 公开路由(publicApiRoutes)不会被赋值,业务接口可安全断言为非空。
|
||||||
|
*/
|
||||||
|
auth?: BetterAuthSession;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// server/utils/accessToken.ts - OCS API 可选访问令牌校验(用于 stats/cache 等管理接口)
|
|
||||||
import { getQuery, type H3Event } from "h3";
|
|
||||||
|
|
||||||
import { serverEnv } from "~~/server/utils/env";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* H3 的 query 值可能是字符串、数组或 undefined
|
|
||||||
* 访问令牌只接受第一个值,和多数 Web 框架读取 query 的行为保持一致
|
|
||||||
*/
|
|
||||||
const firstQueryValue = (value: unknown) => {
|
|
||||||
if (Array.isArray(value)) return value[0]?.toString() || "";
|
|
||||||
return value?.toString() || "";
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验可选访问令牌
|
|
||||||
*
|
|
||||||
* - 没配置 `ACCESS_TOKEN` 时,服务保持旧 Python 项目的开放行为
|
|
||||||
* - 配置后,兼容两种传递方式:`X-Access-Token` 请求头或 `?token=...`
|
|
||||||
*/
|
|
||||||
export const verifyAccessToken = (event: H3Event) => {
|
|
||||||
if (!serverEnv.accessToken) return true;
|
|
||||||
|
|
||||||
const headerToken = event.node.req.headers["x-access-token"]?.toString();
|
|
||||||
const queryToken = firstQueryValue(getQuery(event).token);
|
|
||||||
|
|
||||||
return (
|
|
||||||
headerToken === serverEnv.accessToken ||
|
|
||||||
queryToken === serverEnv.accessToken
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OCS 搜索接口使用 `{ code, msg }`,而 stats/cache 使用 `{ success, message }`
|
|
||||||
* 这里先返回最基础的 OCS 形状,其他接口复用其中的 msg 文案
|
|
||||||
*/
|
|
||||||
export const invalidAccessTokenResponse = () => {
|
|
||||||
return {
|
|
||||||
code: 0,
|
|
||||||
msg: "无效的访问令牌"
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// server/utils/api-auth-rules.ts - API 鉴权路由规则表:声明公开路由,其余默认需要登录
|
||||||
|
|
||||||
|
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS";
|
||||||
|
|
||||||
|
type ApiRouteRule = {
|
||||||
|
path: string | RegExp;
|
||||||
|
method?: HttpMethod | HttpMethod[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公开 API 路由规则表
|
||||||
|
*
|
||||||
|
* 新增接口默认需要登录,无需任何改动;
|
||||||
|
* 只有明确需要公开访问的路由才加到这里。
|
||||||
|
*/
|
||||||
|
export const publicApiRoutes: ApiRouteRule[] = [
|
||||||
|
// Better Auth 自己的登录、注册、退出、get-session 等接口必须放行
|
||||||
|
{ path: "/api/auth/**" },
|
||||||
|
// 服务健康检查,供部署平台和 Docker healthcheck 使用,不含敏感信息
|
||||||
|
{ method: "GET", path: "/api/health" },
|
||||||
|
// OCS 油猴脚本跨域无法携带 cookie,搜索接口有自己的双通道鉴权(session 或 apiToken)
|
||||||
|
{ path: "/api/search" }
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 路径匹配:支持精确匹配、`/**` 前缀通配和 RegExp */
|
||||||
|
function matchPath(rulePath: string | RegExp, pathname: string): boolean {
|
||||||
|
if (rulePath instanceof RegExp) return rulePath.test(pathname);
|
||||||
|
if (rulePath.endsWith("/**")) {
|
||||||
|
const prefix = rulePath.slice(0, -3);
|
||||||
|
return pathname === prefix || pathname.startsWith(`${prefix}/`);
|
||||||
|
}
|
||||||
|
return pathname === rulePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 判断当前请求是否命中公开路由规则 */
|
||||||
|
export function isPublicApiRoute(pathname: string, method: string): boolean {
|
||||||
|
return publicApiRoutes.some((rule) => {
|
||||||
|
const methods = Array.isArray(rule.method)
|
||||||
|
? rule.method
|
||||||
|
: rule.method
|
||||||
|
? [rule.method]
|
||||||
|
: null;
|
||||||
|
const methodMatched = !methods || methods.includes(method as HttpMethod);
|
||||||
|
return methodMatched && matchPath(rule.path, pathname);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -73,8 +73,6 @@ export const serverEnv = {
|
|||||||
maxTokens: readInteger("MAX_TOKENS", 500),
|
maxTokens: readInteger("MAX_TOKENS", 500),
|
||||||
/** 模型采样温度,越低越稳定 */
|
/** 模型采样温度,越低越稳定 */
|
||||||
temperature: readNumber("TEMPERATURE", 0.7),
|
temperature: readNumber("TEMPERATURE", 0.7),
|
||||||
/** 可选访问令牌;配置后 API 需要 header 或 query 携带 token */
|
|
||||||
accessToken: readOptionalString("ACCESS_TOKEN"),
|
|
||||||
/** 是否启用内存缓存 */
|
/** 是否启用内存缓存 */
|
||||||
enableCache: readBoolean("ENABLE_CACHE", true),
|
enableCache: readBoolean("ENABLE_CACHE", true),
|
||||||
/** 缓存过期时间,单位秒 */
|
/** 缓存过期时间,单位秒 */
|
||||||
|
|||||||
Reference in New Issue
Block a user