refactor: 优化代码结构,添加中文注释,完善 README 文档

- Hooks/组件添加 useMemo 优化,减少不必要的重计算
- 简化 TokenUsageDashboard 的 Suspense 嵌套层级
- 完善 README: 技术栈、构建产物位置、架构说明
This commit is contained in:
2026-01-21 20:15:34 +08:00
parent a77fcdd3dc
commit 13a873ec76
17 changed files with 944 additions and 365 deletions

View File

@@ -1,11 +1,72 @@
/**
* 环境变量配置模块
*
* 使用 @t3-oss/env-core 进行类型安全的环境变量验证。
* 支持从同目录的 .env 文件加载配置(优先级低于系统环境变量)。
*/
import { existsSync, readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { createEnv } from '@t3-oss/env-core'
import { z } from 'zod'
/** 默认的 TOKEN_USAGE_URL */
/** Token 使用量 API 的默认地址 */
const DEFAULT_TOKEN_USAGE_URL = 'http://10.0.1.1:8318/usage'
/**
* 判断当前是否为打包后的可执行文件运行环境
*
* @returns 是否为打包后的二进制文件
*/
const isBundledExec = (): boolean => {
const execPath = process.execPath
return !execPath.includes('node') && !execPath.includes('bun')
}
/**
* 获取配置文件的基础目录
*
* - 打包后的 sidecar: 使用可执行文件所在目录
* - 开发模式: 使用项目根目录
*
* @returns 基础目录路径
*/
const getBaseDir = (): string =>
isBundledExec() ? dirname(process.execPath) : process.cwd()
/**
* 解析 .env 文件内容
*
* 支持:
* - 空行和 # 开头的注释
* - KEY=value 格式
* - 只设置系统环境变量中不存在的变量
*
* @param content - .env 文件内容
* @returns 解析后的环境变量对象
*/
const parseEnvContent = (content: string): Record<string, string> => {
const result: Record<string, string> = {}
for (const line of content.split('\n')) {
const trimmed = line.trim()
// 跳过空行和注释
if (!trimmed || trimmed.startsWith('#')) continue
const eqIndex = trimmed.indexOf('=')
if (eqIndex <= 0) continue
const key = trimmed.slice(0, eqIndex).trim()
const value = trimmed.slice(eqIndex + 1).trim()
// 只设置系统环境变量中不存在的变量
if (!process.env[key]) {
result[key] = value
}
}
return result
}
/**
* 从同目录的 .env 配置文件读取环境变量
*
@@ -13,63 +74,54 @@ const DEFAULT_TOKEN_USAGE_URL = 'http://10.0.1.1:8318/usage'
* 1. 系统环境变量 (process.env)
* 2. 可执行文件同目录的 .env 文件
* 3. 默认值
*
* @returns 从文件解析的环境变量
*/
function loadEnvFromFile(): Record<string, string> {
const result: Record<string, string> = {}
const loadEnvFromFile = (): Record<string, string> => {
const envPath = join(getBaseDir(), '.env')
// 确定可执行文件所在目录
const execPath = process.execPath
const isBundled = !execPath.includes('node') && !execPath.includes('bun')
const baseDir = isBundled ? dirname(execPath) : process.cwd()
const envPath = join(baseDir, '.env')
if (!existsSync(envPath)) return {}
// 如果 .env 文件存在,解析它
if (existsSync(envPath)) {
try {
const content = readFileSync(envPath, 'utf-8')
for (const line of content.split('\n')) {
const trimmed = line.trim()
// 跳过空行和注释
if (!trimmed || trimmed.startsWith('#')) continue
try {
const content = readFileSync(envPath, 'utf-8')
return parseEnvContent(content)
} catch {
// 忽略读取错误(权限问题等)
return {}
}
}
const eqIndex = trimmed.indexOf('=')
if (eqIndex > 0) {
const key = trimmed.slice(0, eqIndex).trim()
const value = trimmed.slice(eqIndex + 1).trim()
// 只设置不存在的环境变量
if (!process.env[key]) {
result[key] = value
}
}
}
} catch {
// 忽略读取错误
/**
* 构建合并后的环境变量对象
*
* 合并顺序: process.env > fileEnv > 默认值
*/
const buildMergedEnv = (): Record<string, string | undefined> => {
const fileEnv = loadEnvFromFile()
const merged: Record<string, string | undefined> = { ...process.env }
// 从文件填充缺失的变量
for (const [key, value] of Object.entries(fileEnv)) {
if (!merged[key]) {
merged[key] = value
}
}
return result
}
// 加载配置文件中的环境变量
const fileEnv = loadEnvFromFile()
// 合并环境变量: process.env > fileEnv > 默认值
const mergedEnv: Record<string, string | undefined> = {
...process.env,
}
// 从文件填充缺失的变量
for (const [key, value] of Object.entries(fileEnv)) {
if (!mergedEnv[key]) {
mergedEnv[key] = value
}
}
// 如果仍然没有 TOKEN_USAGE_URL使用默认值
if (!mergedEnv.TOKEN_USAGE_URL) {
mergedEnv.TOKEN_USAGE_URL = DEFAULT_TOKEN_USAGE_URL
// 设置默认值
merged.TOKEN_USAGE_URL ??= DEFAULT_TOKEN_USAGE_URL
return merged
}
/**
* 类型安全的环境变量配置
*
* 服务端变量:
* - TOKEN_USAGE_URL: Token 使用量 API 地址
*
* 客户端变量 (VITE_ 前缀):
* - VITE_APP_TITLE: 应用标题 (可选)
*/
export const env = createEnv({
server: {
TOKEN_USAGE_URL: z.string().url(),
@@ -78,6 +130,6 @@ export const env = createEnv({
client: {
VITE_APP_TITLE: z.string().min(1).optional(),
},
runtimeEnv: mergedEnv,
runtimeEnv: buildMergedEnv(),
emptyStringAsUndefined: true,
})