外观
antispam-cms-web — 审核运营控制台前端(Vue 2.7 SPA,nginx 静态托管)
分层:业务层(浏览器端 SPA)| 部署单元:1 个(仓库根即单元)| 数据库:无(全部经后端
antispam-cms)| base image:private-registry.nis.netease.com/library/nginx:1.28.0(tagbase-250409-pg-youchu)
一、模块定位与架构
它是易盾内容安全审核运营系统的 Web 控制台:运营与审核人员在同一个 SPA 里完成「数据查询 → 人工审核/质检 → 策略与产品配置 → 实时监控与统计 → 预警/公告/消息触达」的闭环。
同一份代码通过多套环境与构建模式同时服务两种形态:
- 公有云 SaaS(
dun.163.com):静态资源与接口按普通路径走; - 私有化部署(邮储 PSBC 等):静态资源、接口、WebSocket 全部走
/ueps{系统简称}{web|api|ws}反代前缀,本项目为/uepscrasweb/、/uepscrasapi、/uepscrasws。
架构分层(单页应用三层)
- 入口层:
src/main.tsx(异步加载./asyncEntry,先loadLanguageAsync()再建 Vue 实例)→src/router/rawRouter.ts/genAuthMenu.ts(按权限生成路由与菜单)→src/views/layout/index.vue(应用外壳,注册全局 WS 与弹窗监听) - 页面业务域层:
src/views/{query,audit,detect,monitor,statics,dashboard,strategy,setting,subscribe,quality,profile,college,train-platform,ad,paypal,aigc,qiyu,layout,login,...}—— 21 个业务域;src/components/audit等 60+ 审核组件 - 通用能力层:
src/helpers(axios / useWebSocket / sendBeacon 封装,自动补私有化前缀)src/api(按业务域拆分为 58 个接口模块,base.ts为公共封装)src/store(vuex 3:userInfo/region/locale/systemInfo)src/composition(62 个 Vue3 style hooks:useGlobalWs、useCommonSpamType、useAudit、useDataQuery等)src/consts(50+ 枚举/常量文件,值以[code, $t(key)]集中定义)src/i18n(语言包,含 8 位 hash key 的中文文案)、src/plugins/storageManager、src/utils、src/styles
关键数据流(典型查询)
views/query/text/index.vue提交表单helpers/axios实例(baseURL 带/uepscrasapi,自动附locale/X-Time-Zone头)api/text.ts→/api/query/text- 后端
antispam-cms做命中类型三级树转换,携带spamLabelTree/machineSpamLabelTree下沉到antispam-business-text/-image的 ES 查询 responseInterceptor统一解包{code,data};401 时置空UPDATE_USER_INFO并跳/login- 全局 WebSocket(
/uepscrasws/webSocket/…)推送未读数、公告、待审提醒、智能运营预警(type=11)驱动站内信弹窗
会话与状态事实源
store的UPDATE_USER_INFOmutation 是会话唯一事实源,收到 401 推送即置空并跳/login;- 区域(
region)与语言(locale)持久化在 localStorage(plugins/storageManager)。
二、可部署服务清单
| 部署单元目录 | artifactId | 镜像名 | 端口 | 服务类型 | 独立 Dockerfile | 是否进 kubernetes.yml |
|---|---|---|---|---|---|---|
仓库根(.,package.json name=antispam-cms-web) | name=antispam-cms-web,version 0.1.0 | private-registry.nis.netease.com/yidun/antispam-cms-web:base-250409-pg-youchu | 80(nginx) | 前端 SPA + nginx 静态托管 | 有(Dockerfile,另 Dockerfile-arm) | 否(kubernetes.yml 内容为 antispam-cms-image-* 复制粘贴残留) |
构建脚本与产物部署方式
buildAll.sh:git pull→GIT_PAGER=cat git log -1→ 取GIT_COMMIT/GIT_BRANCH/BUILD_TIME→ 切 Node(export PATH=/home/appops/ndp-soft/node-v18.16.0-linux-x64/bin:./node_modules/.bin:$PATH)→rm -rf ./node_modules/.vite→npm install→npm run build:private→docker buildx build --platform linux/amd64,linux/arm64 --build-arg GIT_COMMIT/BUILD_TIME/GIT_BRANCH --tag <FULL_IMAGE_PATH> --push→source ~/.zsh_private && bash $k8s_script_path/podupdate.sh antispam-cms-web base-250409-pg-youchuDOCKER_IMAGE_NAME=antispam-cms-web、DOCKER_REPOSITORY=private-registry.nis.netease.com/yidun、DOCKER_IMAGE_TAG=base-250409-pg-youchu、GLOBAL_ENV=private- 另有
buildAll-arm.sh(ARM 专用改写)与deploy.sh - 产物:
vite build --mode private输出到build.outDir——root=true时默认dist;--root false时输出到../api/target/classes/static,供后端内嵌发布 - 镜像内落地:
Dockerfile里COPY dist/ /usr/share/nginx/html/+COPY location.conf /etc/nginx/conf.d/default.conf - 无独立后端进程、无 Java 启动类、无 WAR
三、同模块启动顺序
- 唯一单元,无同模块顺序问题。 但它对外部有硬顺序约束:
antispam-cms的console/api必须先就绪 —— 所有/api/**与登录鉴权都由它提供;前端启动本身只是 nginx 返回静态文件,但用户首个请求即打向它。- 反向代理 / 网关必须先就绪 ——
base='/uepscrasweb/'决定静态资源路径,/uepscrasapi、/uepscrasws的前缀剥离由外层反代完成;反代缺失时页面能开但接口全 404。 - 后端 WebSocket 端口(本设计为 8080)必须先就绪 ——
location.conf里 WS 段硬编码http://127.0.0.1:8080/webSocket/。 - nginx 容器自身无依赖顺序 —— 只要
dist/已构建完成,镜像即可启动。 - 滚动发布期间新旧 Pod 并存 ——
location.conf的静态资源名带构建时间戳(主题 CSS 文件名含timestamp),新旧 Pod 同时在线时不会读到过期主题文件,但也意味着新 Pod 上线前必须完成npm run build:private。
四、逐服务启动逻辑
仓库根(前端工程 + nginx 镜像)
- 无 Java 启动类、无
@SpringBootApplication、无@Enable*、无@Import - 应用入口:
src/main.tsxcreateApp()→await loadLanguageAsync()(按当前语言只加载对应语言包)- →
await import('./asyncEntry')(注释明确:依赖统一通过asyncEntry引入,避免提前引入依赖 i18n 的文件导致展示异常) - → 解析
location.search的isv_token - →
handleAppTransition(store, router)+isOemDomain() - → 计算
currentPath(若window.location.pathname命中PSBC_WEB_BASE则先剥前缀) - → 判定免登路径:
QI_YU_QUERY_PATH(七鱼客服)、/sso-login-error、/error-hint、/login,命中则直接create(App) - → 否则
new LoginManager({ store, router }).checkLogin({ isvToken })后create(App)
- 私有化分支(登录成功后):
import('@/helpers/psbcJwtManager')→startPsbcTokenRefresh(onUnauthorized时stopPsbcTokenRefresh()+clearPsbcToken()+router.push('/login'))→.then(notifyPsbcSessionChanged);再store.dispatch('fetchSystemConfig')→changeTheme(version)、storeManager.set('region', userInfo.area)、addNoReferrerMeta(noReferrer) - 异常分支:
error._code !== RESPONSE_CODE.UN_LOGIN[0]时create(AppError, { error });否则create(App)并提示$t('zidongtiao_0d2197') - 全局构建配置
vite.config.js:base = usecdn ? 'https://yidunfe.nosdn.127.net/<pkg.name>/' : '/uepscrasweb/'(参数来自minimist(process.argv.slice(6))的usecd/root)envPrefix='VUE_';loadEnv(mode, '.', 'VUE_')+VUE_BASE_URL/VUE_BUILD_VITE注入define['process.env']assetsInclude放行.xlsx/.xls/.docx/.doc/.pptx/.ppt/.csvbuild.outDir = root ? 'dist' : '../api/target/classes/static',emptyOutDir: true- 插件:
antdvFix()、htmlPlugin(env)(EJS 编译index.html)、basicSsl()、vue2()、vue2Jsx()、createSvgPlugin()、@yidun/vite-plugin-theme-preprocessor、fix-theme-link-base(自定义,给 theme link 补 base 前缀)、splitVendorChunkPlugin()、usecd时追加CdnUploadVitePlugin - 仅 dev 追加
mockPlugin()(按?__mock__触发)与未启用的ppeffectPlugin()
- dev server:
port=3000、host='cms-dev.dun.163.com'、https=false^/uepscrasapi/api(?!.*__mock__)→http://${process.env.CMS_PROXY || '10.44.45.14:32661'},rewrite去掉/uepscrasapi^/openapi→http://cms-api.test.dun.163.com^/uepscrasws/webSocket→ws://${target}(ws:true、changeOrigin:true)
- 容器启动逻辑(
Dockerfile):FROM private-registry.nis.netease.com/library/nginx:1.28.0,ENV APP_NAME=antispam-cms-web,WORKDIR /usr/share/nginx/html- 单个
RUN内完成:rm -rf /usr/share/nginx/html/*、groupadd -g 600 docker、useradd -u 12028 -g docker -m -s /bin/bash docker、sed -i 's/user nginx;/user docker;/g' /etc/nginx/nginx.conf - 随后
COPY dist/ /usr/share/nginx/html/、COPY location.conf /etc/nginx/conf.d/default.conf - 写
/usr/share/nginx/html/version.txt(git_commit / build_time / git_branch),chown -R 12028:600到/usr/share/nginx/html /var/cache/nginx /var/log/nginx /run,chmod -R 755 EXPOSE 80、CMD ["nginx","-g","daemon off;"]
- nginx 路由(
location.conf):listen 80、root /usr/share/nginx/html、index index.html index.htmlocation ^~ /uepscrasws/webSocket/→proxy_pass http://127.0.0.1:8080/webSocket/,proxy_http_version 1.1+Upgrade/Connection "upgrade"+X-Real-IP/X-Forwarded-*,proxy_read_timeout 86400、proxy_send_timeout 86400location /→try_files $uri $uri/ /index.html(history 模式兜底)location /health/check→access_log off; return 200 'ok'
- 启动钩子:前端无「启动钩子」概念;容器侧即 nginx 前台进程
备用镜像 Dockerfile-arm
FROM private-registry.yidun.internal/arm/library/nginx:1.28.0- 只做:
rm -rf /usr/share/nginx/html/*→COPY dist/→COPY location.conf→ 写version.txt→EXPOSE 80→CMD nginx - 不创建
docker用户、不改 nginx 运行用户、不做 chown/chmod,与主Dockerfile行为不一致(见第七节)
五、启动前置依赖
| 依赖 | 配置键 / 地址 | 阻塞 or 弱依赖 | 配置文件 |
|---|---|---|---|
后端 antispam-cms console/api | /uepscrasapi(反代后指向 console/api:8908);dev 默认 CMS_PROXY 或 10.44.45.14:32661 | 阻塞(登录/鉴权/全部业务接口) | vite.config.js、外层反代 |
| 后端 WebSocket | /uepscrasws/webSocket/ → http://127.0.0.1:8080/webSocket/ | 弱(失败仅未读/预警/站内信失效) | location.conf |
| 外层反向代理 / 网关 | 剥离 /uepscrasweb/、/uepscrasapi、/uepscrasws 前缀 | 阻塞(前缀不匹配则静态资源与接口全 404) | 部署环境,不在本仓 |
| CDN(可选) | usecd=true 时 base 切 https://yidunfe.nosdn.127.net/antispam-cms-web/,CdnUploadVitePlugin 上传(bucket yidunfe、endpoint nos-jd.163yun.com、namespace=pkg.name) | 弱(按开关) | vite.config.js |
| 外部开放接口(dev 代理) | ^/openapi → http://cms-api.test.dun.163.com | 弱(仅 dev) | vite.config.js |
| 私有化 SSO / JWT 网关 | 邮储场景由后端反代提供 SSO 回调与 JWT 续期通道 | 弱(仅私有化) | src/helpers/psbcJwtManager.ts、后端反代 |
| Node 运行时 | volta.node=18.16.0;构建机 export PATH=/home/appops/ndp-soft/node-v18.16.0-linux-x64/bin | 阻塞(构建期) | package.json、buildAll.sh |
| nginx | 镜像内置 nginx:1.28.0,需可写 /run、/var/cache/nginx、/var/log/nginx | 阻塞(容器启动) | Dockerfile |
| 构建产物目录 | dist/(root=true)或 ../api/target/classes/static(root=false) | 阻塞(镜像构建) | vite.config.js |
| 主题变量文件 | ./src/theme(THEME_CONFIG)与多套 less 变量文件 | 弱(构建期) | vite.config.js、src/styles/theme |
六、启动参数与 Profile
- 前端无 Java profile;等价物是
--mode与.env.*文件:dev=vite --mode development→.env.developmentdev:private=vite --mode private-development→.env.private-developmentbuild:private=vite build --mode private --force→.env.private——buildAll.sh实际调用的就是它preview=vite preview --port 4173- 仓库另含
.env.production、.env.test、.env.private-test(antispam-cms/console/web的同名工程还有build:test/build:private-test/build:production三档)
- 其它
package.jsonscripts:lint(eslint --fix)、test(→npm run test:unit)、type-check(vue-tsc --noEmit -p tsconfig.vitest.json --composite false)、prettier:json、i18n:transform/translate/detect/export/import(@yidun/i18n-cli) - 环境变量前缀固定
VUE_(envPrefix='VUE_');VUE_APP_MODE === 'test'才启用代码精准测试插件 - Node 版本:
volta.node = 18.16.0;构建脚本切换/home/appops/ndp-soft/node-v18.16.0-linux-x64 - 包管理:
npm install(非npm ci),构建前rm -rf ./node_modules/.vite - JAVA_OPTS:不适用(无 JVM)
- base image 与仓库:
private-registry.nis.netease.com/library/nginx:1.28.0;产物仓库private-registry.nis.netease.com/yidun,tagbase-250409-pg-youchu;ARM 分支用private-registry.yidun.internal/arm/library/nginx:1.28.0 - 构建平台:
--platform linux/amd64,linux/arm64(双架构一次推送)
七、启动期踩坑
base='/uepscrasweb/'与后端反代前缀强绑定:前缀不符(或缺尾斜杠)时index.html内的 JS/CSS 全部 404,页面白屏;usecd=true还会把 base 换成 CDN,混用时更易错。https:false但basicSsl()仍无条件挂在 plugins 里:dev server 声明不启用 HTTPS,却始终装载 basic-ssl 插件,本地起服务时证书/HMR 行为易出现意料外提示。- WS 段硬编码
proxy_pass http://127.0.0.1:8080/webSocket/:后端换端口或改走 Service 名时全局 WS 断开,表现为未读/预警/站内信静默失效(无报错弹窗)。 - 容器以
uid 12028 / gid 600运行:只读根文件系统、或未挂载/run、/var/cache/nginx时 nginx 启动失败;Dockerfile-arm不同步做这层处理,双架构镜像行为不一致。 Dockerfile只COPY dist/:必须先成功执行npm run build:private;buildAll.sh未实现getopts,即使想跳编译也会打到旧的dist目录。npm install而非npm ci:package-lock.json存在但未锁定安装,依赖漂移会让「同一 commit 打出不同 bundle」。vite.config.js内硬编码 CDN 凭据(accessId='e36babfdcb884368b4e72670e1a08d6a'、secretKey='9d14ac7ba4054498858b572cd9e28c54'),随源码公开。kubernetes.yml与模块无关:内容是antispam-cms-image-facade-dubbo-provider/antispam-business-image-*(tag1.0.0.private),不是本前端工程的部署清单,不能据此判断副本数与资源。main.tsx的免登名单靠路径硬编码(/login、/sso-login-error、/error-hint、QI_YU_QUERY_PATH):新增免登页面必须同步改main.tsx,否则会被LoginManager.checkLogin拦到登录页。src/main.tsx与asyncEntry的拆分约束:依赖 i18n 的模块若被提到asyncEntry之外引入,会出现「文案显示为 key 或空白」,且只在切语言时暴露。process.on('uncaughtException')全局吞异常:文件末尾注册了兜底 handler(注释说明是为拦截 WS 代理断开导致的 dev server 崩溃),除了打印日志外不做任何处理,本地调试时容易「错误消失」。fix-theme-link-base自定义插件依赖base字符串:它用正则给id="theme-link-tag"的 link 补 base 前缀,base一旦含正则特殊字符(如 CDN 域名带-、/)会有替换副作用,改base后需回归主题切换。vite.config.js底部注册了全局uncaughtException兜底(注释说明用于拦截 WS 代理断开导致的 dev server 崩溃),只console.log不做处理:本地调试时「异常被吞」,容易误判为无错误。optimizeDeps.include/exclude写死了模块清单(echarts、lodash、@/helpers/protocolcheck等):升级这些依赖或改别名后若不更新清单,dev 冷启动会报预构建错误。
八、跨模块前置
antispam-cms(后端console/api,端口 8908) —— 硬前置:登录、SSO 回调、全部/api/*、WebSocket 推送均由它提供;/api/subscribe/*(预警配置)、查询、审核、消息中心接口也在此。- 跨项目字段协议:文本/图片「最新命中类型」三级筛选树(
spamLabelTree/machineSpamLabelTree)由 CMS 网关转换成查询条件后下沉给antispam-business-text/antispam-business-image;图片latestOrConditions契约为frontLabelSet/subLabelSet/thirdLabelSet,改动前端结构必须与这两个后端服务同步。 - 私有化客户侧:邮储 PSBC 场景由后端反代决定全部资源前缀并负责 SSO 认证与 JWT 续期(
psbcJwtManager),后端反代规则变更会直接打断前端。 - 基础设施:本模块不直连 DB/Redis/Kafka/ES,全部经后端;唯一直接依赖是 nginx 运行时与 Node 构建链。
- 镜像层协同:与
antispam-cms/console/web(同名前端工程,独立一份package.json)并行存在,两处main.tsx/vite.config.js若不同步,会出现「同域名不同行为」。 - 构建机前置:需要能拉取
private-registry.nis.netease.com/library/nginx:1.28.0、能推private-registry.nis.netease.com/yidun,以及~/.zsh_private(提供k8s_script_path)与 Node 18.16.0 运行环境。