refactor: 优化代码结构,添加中文注释,完善 README 文档
- Hooks/组件添加 useMemo 优化,减少不必要的重计算 - 简化 TokenUsageDashboard 的 Suspense 嵌套层级 - 完善 README: 技术栈、构建产物位置、架构说明
This commit is contained in:
152
src/env.ts
152
src/env.ts
@@ -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,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user