Reference Card

环境变量键:兜底、覆盖,还是永远为空

配置来自三个地方,而且会互相覆盖。搞不清哪个值最终生效, 就会出现「我改了配置但没生效」和「我以为连的是测试服」。

基线:2026-08-12 的 gameboxclient 工作区快照。 统计口径:.env* 侧用 grep -E '^\s*VITE_[A-Z0-9_]+\s*='注意等号前有空格, 写成 ^VITE_[A-Z_]*= 会大量漏匹配);源码侧用 grep -rhoE 'VITE_[A-Z0-9_]+' src/ electron/

一句话版本

.env 里已经有一整套能跑的配置(42 个键, 含 GM 地址、pcode、渠道、SVN 仓库地址、Jenkins 地址凭据和 11 个 job 名)。 登录做的是覆盖,不是填充 —— initEnv() 覆盖的 14 个键,在 .env全都已有条目

真正的差别在值是不是空的: SVN 账号密码在 .env 里是空的,必须靠登录; Jenkins 和 GM 有真值,不登录也能连上,只是连的是 .env 里那一套

三个来源,谁覆盖谁

① 构建期 · .env* —— Vite 打进包里。 主进程在 ready.ts:11-13 把它们灌进 process.env

② 启动期 · 主进程算路径 —— ready.ts:14-19 算出 DIST / DIST_EXTEND 等,追加到 process.env
随后 App.vue:40gameEnv.init(process.env) 把整个 process.env 收进 Pinia。

③ 登录期 · initEnv() 覆盖 —— utils/extend/envOption.ts:8/user/info 返回的 userInfo.game 覆盖其中 14 个键

落盘 —— GameEvn.ts:44-49 的深度 watch 把 ENV 整体写进 localStorage 的 GameENV。 下次启动 gameEnv.init() 优先读它, 而不是 process.env

⚠️ 这条最容易踩

改了 .env 却不生效,多半是被 localStorage 的旧 GameENV 挡住了。 GameEvn.init()store/modules/GameEvn.ts:17-22) 只要发现 localStorage 里有 GameENV,就整个用它, 根本不看传进来的 process.env

怎么办:改完 .env 后在控制台 localStorage.removeItem('GameENV') 再刷新。

⚠️ 兜底配置有个陷阱:名字写着 Test,请求打到正式服

.env 里三个 pcode 键的值都是 'boxTestLS'。名字里有 Test,看起来是测试环境。

getBaseURL()src/config/axios/service.ts:79-84)是这样判的:

function getBaseURL(pcode: string): string {
  if (pcode.includes('_dev')) {
    return 'http://common-gm-460qntest.3975app.com/n1000y-api/'   // 测试
  }
  return 'http://common-gm-460qnprod.3975app.com/n1000y-api/'     // 正式
}

'boxTestLS' 不含 _dev → 落到 return 那一行 → 打正式 GM

❌ 别学

用子串匹配判环境,而且默认分支是生产。 这里叠了两个问题:判定依据是 _dev 这个后缀, 而兜底值 boxTestLS 的命名暗示的却是「测试」—— 命名和实际路由是矛盾的

后果:登录失败、或登录成功但服务端没下发 pcode 时, 请求会安静地打到正式 GM,而配置文件看起来一切正常。

规范做法:用显式的环境字段(env: 'test' | 'prod'), 不要靠子串;不确定时默认落最安全的环境;地址走配置下发而不是硬编码。

动手前必查:控制台敲 localStorage.getItem('pCode')JSON.parse(localStorage.getItem('GameENV')).VITE_API_PCODE, 看里面有没有 _dev没有就是在打正式服。

A. .env 的 42 个键(真正的配置在这)

35 个有值,7 个是空的。下表按用途分组, 「登录覆盖」列标出 initEnv() 会不会改写它。

用途 .env 有值? 登录覆盖?
GM 接口 VITE_API_PCODE · VITE_API_BASE_PCODE · VITE_API_GM_PCODE ✅ 均为 boxTestLS ✅ 前两个会(GM_PCODE 由选服页另算)
VITE_API_BASEPATH · VITE_API_BASEPATH_DEV · VITE_API_PATH
VITE_BASEPATH ✅ axios 的 baseURL 初始值
渠道 VITE_API_CHANNEL · VITE_API_CHANNEL_SVN
SVN VITE_SVN_URL · VITE_SVN_EXCEL_URL
VITE_SVN_USER · VITE_SVN_PASSWORD ❌ 空 只能靠登录
VITE_SVN_ASSETSURL ❌ 空 源码里零引用,疑似遗留
Jenkins VITE_JENKINS_URL · VITE_JENKINS_USER · VITE_JENKINS_TOKEN ✅ 有真值
11 个 job 名(RES / DEV / ALPHA / PROD / APP / PUBLISH_* ❌ 不覆盖值,但 initEnv() 会把里面的 channel 字样替换成实际渠道
腾讯云 COS VITE_COS_ACCESSKEYID · VITE_COS_ACCESSKEYSECRET ❌ 空 ❌ 见下方红框
VITE_COS_BUCKET · VITE_COS_REGION · VITE_ENDPOINT
阿里云 OSS VITE_OSS_ACCESSKEYID · SECRET · BUCKET · REGION 两个密钥,其余有值
网页版地址 VITE_DEV_WEB · VITE_ALPHA_WEB · VITE_PROD_WEB · VITE_STORE
其他 VITE_DEBUG

❌ COS 密钥在当前代码里没有任何写入路径

三条事实叠在一起:

所以 COS_Handler.init() 拿到的必然是两个空字符串。 而它内部是 try/catch + return false,没人检查返回值, 所以这件事在界面上完全不可见。

需要实测确认的是:带空密钥的 COS 请求在服务端是直接 401, 还是某些只读操作仍可用。这是「必须问人 / 必须实测」清单里的一条。

B. .env.base / .env.dev / .env.pro:只有构建开关

这三个文件不含任何业务配置,加起来只有 9 个不同的键, 全是构建期开关:

作用
NODE_ENV 决定主进程要不要初始化 electron-logready.ts:24-26
VITE_APP_TITLE 窗口标题,经 EJS 插件注入 index.html
VITE_BASE_PATH Vite 的 base,资源路径前缀。 注意和 .env 里的 VITE_BASEPATH 只差一个下划线,含义完全不同
VITE_BASEPATH 接口 baseURL(只有 .env.pro 覆盖它)
VITE_OUT_DIR · VITE_SOURCEMAP 构建输出目录、要不要出 sourcemap
VITE_DROP_CONSOLE · VITE_DROP_DEBUGGER terser 压缩时去不去掉 console / debugger
VITE_DEBUG 主进程在 onAppReady.ts 里打日志用
VITE_SHOW_UI ⚠️ 在 .env.base / .env.dev 里定义了,但 src/electron/零引用,疑似遗留

❌ 别学

VITE_BASE_PATH(资源前缀)和 VITE_BASEPATH(接口 baseURL)只差一个下划线。 改配置时看漏一个,症状会是「资源 404」或「所有接口打到根路径」—— 两个症状都跟你以为的原因无关。

规范做法:命名要拉开距离,比如 VITE_ASSET_BASEVITE_API_BASE_URL

C. 主进程算出来的路径键

算法 谁用
DIST_ELECTRON join(__dirname, '..') 下面几个的基准
DIST DIST_ELECTRON/../dist 打包后加载 index.html 的位置
DIST_EXTEND DIST_ELECTRON/../extend 所有外挂工具的根目录
PUBLIC 打包后 = DIST,开发时 = ../public 图标等静态资源
VITE_DIST_EXTEND utils/extend/electron.ts:4DIST_EXTEND 里的 app.asar 替换成 app.asar.unpacked SVN / Python / 7-Zip 全靠它拼路径
VITE_DEV_SERVER_URL vite-plugin-electron 注入 开发时窗口加载哪个地址;打包后为空
为什么要做 asar 替换:Electron 打包把代码塞进 app.asar 归档,归档内的文件不能被当作可执行程序运行electron-builder.json5asarUnpackextend 解出来放在 app.asar.unpacked,代码再把路径替换过去。 这是打包后「找不到 svn / python」类问题的根源所在。

D. 源码用了、但 .env* 里没有的 8 个

从哪来
VITE_DIST_EXTEND · VITE_DEV_SERVER_URL 主进程 / 构建插件注入(见上一节)
VITE_API_SCRIPT_PCODE 脚本管理页按当前版本现算 (views/GameEditor/NewScript/index.vue:1092-1101
VITE_API_SECTCONFIG_PCODE · VITE_API_SECTCONFIG_PATH 全局数据页现算(views/GameEditor/GlobalData/index.vue:143-154
VITE_JENKINS_STOP · VITE_JENKINS_AUTO · VITE_JENKINS_CHECK 不是真配置键。它们是 jenkins/config.tsJOB_TYPES 里的动作标记, 值写死为 'STOP' / 'ATUO' / 'CHECK'ATUOAUTO 的拼写错误)

E. 定义了但源码没用的 7 个

VITE_BASE_PATH · VITE_DROP_CONSOLE · VITE_DROP_DEBUGGER · VITE_OUT_DIR · VITE_SOURCEMAP —— 这五个是给 vite.config.ts 用的,不出现在 src/ 里,属正常。

VITE_SHOW_UIVITE_SVN_ASSETSURL 则是 vite.config.ts 也不用的真遗留键。

排查流程

JSON.parse(localStorage.getItem('GameENV')) —— 看整个包,以及目标键的当前值

值不对 → 是不是被旧的 localStorage 挡住了? localStorage.removeItem('GameENV') 再刷新试试

值是空的 → 看它属于哪一类: SVN 账号密码空 = 登录没下发,去问后端 /user/info 返回了什么;COS 密钥空 = 当前代码本来就没有写入路径(见上文红框)

pcode 类问题额外查 localStorage.getItem('pCode') —— getPcode()优先用它, 优先级高于选服时写进 GameEvn 的值

确认最终打到哪台服:看 Network 里 Request URL 的域名是 qntest 还是 qnprod,并和 pcode 里有没有 _dev 对上

站点版本 319ca82 · 2026-08-13 内容基线 gameboxclient @ 2026-08-12