Project Map · Architecture
每张图都能逐步播放。先看全景,再点「下一步」跟着数据走一遍。
gameboxclient 工作区快照(未提交状态,git HEAD
仍是旧基线)。所有
file:line
引用当时逐条核对过;一旦有人提交或改动,行号会漂移,
文件名和函数名仍然有效。
GameBox 是一个装在电脑上的桌面程序,策划和美术每天用它改游戏里的 NPC、物品、掉落、任务、地图,改完点发布,资源就上到测试服或正式服。
它不是网页,但它里面装着一个网页。Electron 干的就是这件事: 给你一个浏览器内核负责画界面,同时给你一个 Node.js 进程负责干浏览器干不了的活 —— 读写本地文件、调用 SVN 命令行、fork 子进程解压几个 G 的资源包。
所以看这个项目,第一件要分清的事永远是:我现在看的这段代码,跑在哪个进程里? 这决定了它能用什么 API、出错时去哪看日志、以及为什么有些代码在浏览器里根本没法调试。
这张图最该记住的是那个虚线空框:本项目没有 preload。 标准 Electron 应用靠 preload 脚本在两个进程之间开一道窄门, 只把明确列出的方法暴露给页面。这里没有,取而代之的是把整个 Node 环境直接交给了渲染层。
electron/preload/index.ts
这个文件是存在的,里面还老老实实写着
contextBridge.exposeInMainWorld。但它从来没被构建过 ——
vite.config.ts:58-72 把整个 preload 构建块注释掉了,
electron/main/config/options.ts 的
webPreferences 里也没有 preload 这个键。
结果是:想在渲染层用 Node,唯一的路就是直接 import。
所以你会在 .vue 文件里看到
import { ipcRenderer } from 'electron'、
import Fs from 'fs'、const SVN = require('node-svn-ultimate')
这些在正常 Web
项目里根本不该出现的东西。它们不是写错了,是这个项目的既定架构。
⚠️ 隔离
渲染层全开 Node 权限(options.ts:6-8)。
这是架构级决定,几百个文件都建立在它之上,你接手第一周绝不该动它。
但要清楚代价:渲染层加载的任何第三方内容都拥有读写整台机器的能力,
webSecurity: false 还关掉了同源策略。
规范做法是保持 contextIsolation: true, 用
preload + contextBridge 只暴露必要方法。见
Electron · Context Isolation
与
Electron · Security。
你现在该做的:新写的代码不要继续扩散
@electron/remote。要跟主进程说话就老实加一个 IPC 通道,
至少让边界是显式的、可数的。
从双击图标到看见登录框,中间有八步。搞清楚这八步, 你才知道「界面白屏」这类问题该往哪一步查。
排障用法:白屏且没报错,先看是卡在 3–4 步(窗口起了但页面没加载) 还是 5–6 步(页面加载了但 Vue 没装配)。前者去主进程日志找, 后者按 F12 看渲染层控制台。
⚠️ 隔离
generateRoutes() 名不副实。
src/store/modules/permission.ts:37-56 里它只做了
[...DynamicRouterMap] 加一条 404 兜底,然后全部
router.addRoute()。函数名和文件名都写着「权限」,
但没有任何一行按用户角色过滤。
这意味着:菜单里藏起来的页面(meta.hidden)
并不是「无权访问」,只是「不显示入口」。真正的授权只可能在服务端。
排障时如果有人说「他不该能进那个页面」,答案是前端从来没拦过。
这是整个项目最反直觉、也最值得先搞懂的一条链路。
正常项目里,SVN 地址、Jenkins 地址、对象存储密钥这些东西写在
.env 文件里,构建时打进去。这个项目不是。
源码里用到了约 50 个 VITE_* 开头的配置键,但
.env / .env.base / .env.dev /
.env.pro 四个文件加起来只定义了 11 个。剩下的 ——
SVN 账号密码、Jenkins 地址账号 token、GM 接口地址、项目编码 pcode ——
全部是登录接口下发的,登录成功那一刻才被塞进内存。
排障用法:「点了发布没反应」「SVN
提交报没权限」这类问题, 第一步永远是打开 DevTools 控制台敲
JSON.parse(localStorage.getItem('GameENV')),
看该有的键是不是空的。空的就不是发布代码的问题,是登录没成功或服务端没下发。
❌ 别学
SVN 账号密码和 Jenkins token 明文写进 localStorage。
src/store/modules/GameEvn.ts:44-49 挂了一个深度
watch, ENV 一变就整体
setStorage('GameENV', nv), 而 ENV 里装着
svn_password 和 jenkins_token。 localStorage
没有加密、没有过期、退出登录前一直留在磁盘上, 而渲染层又是
webSecurity: false 的全 Node 环境。
规范做法是凭据只留在主进程,渲染层要用就通过 IPC 请求主进程代为执行(「帮我提交这些文件」),而不是把密码交给渲染层自己拼命令行。 主进程侧可用 Electron safeStorage 走系统钥匙串加密。
你现在该做的:先别改,但把它记进风险清单 —— 这是将来跟安全或运维对话时你必须能说清楚的一条。
策划打开「NPC」页,搜一个 NPC,点进去改「刷新时间」,点保存。 这个动作在代码里要穿过两条并行的链路,最后在表单组件里汇合。
数据链负责「这个 NPC 现在的值是多少」,走网络。
描述链负责「这张表有哪些字段、每个字段该用什么控件、选项有哪些」, 走本地的
schema 文件。
分清这两条是理解本项目的关键:绝大多数「加个字段」「改个下拉选项」的需求,只动描述链,碰都不用碰页面组件。
最实用的一条:策划说「NPC 表里给我加个字段」,
你要改的是 src/store/config/Npc.ts(加字段定义)和
src/views/GameEditor/Npc/view.ts(决定它显示在哪个标签页), 不是
NpcEdit.vue。页面组件是通用的,它只负责按 schema 渲染。
这个后端不是 REST
别按 REST 的直觉找接口。这个后端只有极少数几个 URL,业务动作全塞在
cmd 字段里:查 NPC 是
cmd: 'npc_search_ByMapId', 查表字段是
cmd: 'excel_search_name',改数据是
cmd: 'excel_modify'。
后果是:你在浏览器 Network 面板里看到的请求全长一样
(都是 POST /1000y/engine/cmd),想知道某个请求在干什么,
必须展开 Payload 看 cmd。这是排障时最容易卡住新人的一点。
发布页在 /expert/release(专家模式 → 发布,组件是
src/views/Option/index.vue)。页面上是一排卡片,
但卡片背后其实只有两种任务类型,走完全不同的代码路径。
互斥规则只有一条,而且是全局的:只要 Jenkins
上有任何一个 job 在跑,所有发布卡片都点不动 —— 包括纯本地的 SVN
提交。
闸门在分叉之前(Option/index.vue:89-111),
所以「我只是想提交几个文件,为什么提示有打包任务」是符合预期的,不是
bug。
⚠️ 隔离
轮询用的是裸 setInterval,没有超时上限。
src/api/login/index.ts:105-107 起一个定时器反复查构建状态,
只在拿到明确结束态时才 clearInterval。 如果 Jenkins
中途不可达、返回结构变了、或者 job 被人在 Jenkins 侧删了,
这个定时器会一直转下去,界面上的任务永远停在进行中。
规范做法是给轮询加最大次数或截止时间,超时后主动置为「状态未知」 并给用户一个刷新入口,而不是无限等。
排障提示:用户报「发布卡住不动了」,先问 Jenkins
页面上那个 job 到底是什么状态。八成 Jenkins
那边早就结束了,是这边的轮询没收到。 重新登录会重置
taskStore,是最快的自救手段。