第二章 前端工程化与签名校验闭环实战

1 阅读10分钟

前端工程化与签名校验闭环实战(feature/fontend-build 分支新增内容总结)

本文档承接《第一节:从零构建一个 Koa 企业级框架——Elpis 架构设计实战》(elpis-architecture-guide.md),专门总结本分支新增的前端工程化体系、boot 统一启动、通用工具库、中间件优先级自动排序、API 签名统一卡点以及前后端交互闭环验证的完整实现。


2.1 本节新增内容总览

模块核心产出说明
前端构建app/webpack/config/*.js + webpack.config.js三套 Webpack 配置(base/dev/prod)+ 根目录桥接文件
开发模式app/middleware/webpackDev.js + config/webpack.dev.jsExpress 子服务 + webpack-dev-middleware(内存编译)+ webpack-hot-middleware(HMR)桥接 Koa
生产打包app/webpack/prod.js splitChunks + TerserPlugin 约束分包策略、压缩保留 Vue _ 下划线属性、排除 element-plus/vue 避免 ES2020+ 问题
页面模板app/view/entry.tpl(统一骨架)所有页面共享一套 .tpl,不再逐页创建 .tpl 文件
页面启动app/pages/boot.js + 各页 entry.page.js统一创建 Vue 实例、挂载 Pinia/Element Plus/Vue Router,支持 libs 数组注入
通用库app/pages/common/utils.js + curl.jsRFC 1321 MD5、签名生成函数、fetch 封装(自动签名 + 响应解包 + 拦截器 + 超时)
中间件架构app/middleware/*.js 新格式 + elpis-core/loader/middleware.js每个中间件导出 { name, priority, middleware },Loader 按 priority 自动排序,不再硬编码顺序
签名卡点app/middleware/api-sign-verify.js priority 70/api/* 统一 MD5 签名校验(timestamp + nonce + signature),支持 excludePaths 白名单
示例页面app/pages/page1/page1.vue三个场景完整验证:带签名 GET / POST / 无签名对照(预期 401)
文档docs/directory-structure.md + 本文目录职责说明 + 本节总结
工程规范package.json ghooks + validate-commit-msgpre-commit 跑 ESLint,commit-msg 强制 Angular Conventional Commits 规范

2.2 前端工程化体系(Webpack + Express 子服务)

2.2.1 三套 Webpack 配置 + 根目录桥接

elpis/
├── app/webpack/
│   ├── config/
│   │   ├── webpack.base.js    # 公共配置:entry / output / loader / DefinePlugin / 别名
│   │   ├── webpack.dev.js     # 开发配置:devtool、HMR entry
│   │   └── webpack.prod.js    # 生产配置:splitChunks、压缩、缓存
│   ├── dev.js                 # 开发模式入口(Express 子服务工厂)
│   ├── prod.js                # 生产模式入口
│   └── build.js               # 通用产物输出逻辑
└── webpack.config.js          # 根目录桥接:根据 NODE_ENV 返回对应模式
  • 全局变量:webpack.DefinePlugin 必须显式定义
    __VUE_OPTIONS_API__: true,
    __VUE_PROD_DEVTOOLS__: false,
    __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,  // Vue 3.4+ 用于 hydration 调试
    
  • 路径约定:所有 Webpack 配置使用 __dirname 定位资源,禁止使用 process.cwd()(否则从非项目根目录执行构建会路径错误)
  • 输出目录:app/public/dist/(Koa static 中间件服务此路径,线上直接访问 /js/、/css/)

2.2.2 开发模式:Express 子服务 + Koa 桥接

核心思路:webpack-dev-middleware 和 webpack-hot-middleware 是 Express 生态,Koa 直接套会因 ctx.body 覆盖 Express 的 res 导致 HMR 失败。故采用子服务 + 桥接方案。

实现要点:

  1. Express 子服务:挂载 webpack-dev-middleware(内存编译 + 文件监听,不落盘)+ webpack-hot-middleware(WebSocket 端点 /__webpack_hmr,推送 .hot-update.js/.hot-update.json)
  2. Koa 桥接(app/middleware/webpackDev.js,priority 100):对命中的路径设置 ctx.respond = false,透传 req/res 给 Express,避免 Koa 覆盖响应
  3. 启动清理:开发模式启动时自动清理 app/public/dist/ 下旧的 .tpl 模板和 hashed JS,避免内存文件与落盘文件混用导致 404
  4. --max-old-space-size=8192:通过 NODE_OPTIONS 环境变量传递,不直接作为 webpack 参数(否则 webpack CLI 会报 unknown option)

2.2.3 生产模式:分包 + 压缩 + 缓存 + 关键约束

splitChunks(仅 webpack.prod.js 存在,开发模式禁用以保证构建速度):

cacheGroups: {
  elementPlus: { test: /[\\/]node_modules[\\/]element-plus[\\/]/, priority: 50, name: 'chunk-element-plus' },
  vue:         { test: /[\\/]node_modules[\\/]vue(.*)?[\\/]/,   priority: 40, name: 'chunk-vue' },
  lodash:      { test: /[\\/]node_modules[\\/]lodash[\\/]/,     priority: 30, name: 'chunk-lodash' },
  vendors:     { test: /[\\/]node_modules[\\/]/,                priority: 10, name: 'chunk-vendors' }
}

priority 值越高越先匹配,element-plus 和 vue 独立打包,长期缓存命中率高。

TerserPlugin 三条硬约束(否则 Vue 3 线上白屏):

new TerserPlugin({
  test: /\.js(\?.*)?$/i,
  exclude: [/chunk-(element-plus|vue)/],  // ① 排除 ES2020+ 语法的 chunk,避免压缩器报错
  terserOptions: {
    toplevel: false,                       // ② 不启用顶级变量重命名,减少引用断裂风险
    mangle: {
      properties: { regex: /^_/ }          // ③ !!! 关键:不混淆下划线开头的属性
    }                                      //    Vue 3 响应式系统大量依赖 __v_*、_watchEffect 等内部字段
  }
})

其他优化:output.clean: true 构建前清空 dist,CssMinimizerPlugin 压缩 CSS,contenthash 文件名配合长期缓存。

2.2.4 路由架构:MPA + SPA 混合

浏览器 GET http://localhost:8080/page/page1
    │
    ├─ Koa Controller: app/controller/page.js → ctx.render('entry.page1')
    │     └─ 返回 Nunjucks 渲染的 entry.page1.tpl(已注入 hashed JS/CSS)
    │
    └─ 浏览器加载 entry.page1.*.js → boot() 启动 → createAppRouter(routerBase='/page/')
          └─ vue-router 接管后续客户端导航(/page/page1 → /page/page2 等不再刷服务端)

多页面入口配置在 webpack.base.js:entry.page1: ['./app/pages/page1/entry.page.js']、entry.page2 ……,Webpack 为每个 entry 产出一个 entry.page*.tpl 和对应的 JS 资源。

2.2.5 babel-loader 额外排除

exclude: /node_modules\/(?!(element-plus)\/).*/,
// 显式追加:vue-router 用 ES2020+ 语法,babel 处理会报错
exclude: [..., /vue-router/]

2.3 统一页面启动:app/pages/boot.js

2.3.1 设计动机

之前每页 entry.page.js 都要重复写 createApp、use(Pinia)、use(ElementPlus)、挂载路由……代码高度重复、升级三方包要改 N 个文件。统一抽取为 boot() 函数,每页只传 Component 即可。

2.3.2 API

import { boot } from './boot'
boot(Component, {
  mountSelector = '#root',   // 挂载点
  enableRouter  = true,      // 是否启用 vue-router
  routerBase    = '/page/',  // vue-router base 路径
  libs          = []         // 三方插件数组,boot 内部会 app.use(lib) 逐个加载
})
// 返回:{ app, pinia, router }

2.3.3 各页 entry.page.js 模板

import page1 from './page1.vue'
import { boot } from '../boot'
// import Echarts from 'echarts'  // 如需额外 libs
boot(page1, {
  libs: [ /* Echarts, VueJsonViewer */ ]
})

2.3.4 Router 创建规则

vue-router 统一由 createAppRouter(routerBase) 创建:

  • mode:createWebHistory(routerBase),base 路径为 /page/ 与后端 /page/* 对齐
  • routes:扫描 app/pages/**/index.route.js(或手动声明),支持懒加载

2.4 前端通用库:app/pages/common/

2.4.1 utils.js —— 工具集

导出说明
md5(str)Joseph Myers 实现的 RFC 1321 MD5(与 Node crypto.createHash('md5').digest('hex') 结果完全一致)
generateSign(params, secret)核心签名函数:过滤 signature/sign → 按 key 字典序排序 → 对象值 JSON.stringify → & 拼接 → 末尾追加 secret → MD5 → 返回 { signature, timestamp, nonce, signedParams }
stringifyParams(obj)URL query 序列化(不编码问号)
formatDate(date, fmt)日期格式化
debounce(fn, wait) / throttle(fn, wait)防抖 / 节流
deepClone(obj)深拷贝
localStorage 封装get(key) / set(key, val) / remove(key) / clear()

2.4.2 curl.js —— fetch 封装 + 自动签名

默认配置:

{
  baseURL: '',
  timeout: 15000,
  sign: {
    secret: 'elpis-dev-secret-key',  // 与后端 config.apiSign.secret 对齐
    enabled: true,
    mode: 'header',                  // 'header' | 'params'
    headerNames: { signature: 'x-signature', timestamp: 'x-timestamp', nonce: 'x-nonce' }
  },
  responseUnwrap: true              // 自动解包 { code, data, message }
}

使用示例:

import curl from '../common/curl'

// ① 自动带签名(header 模式:x-signature / x-timestamp / x-nonce)
await curl.get('/api/projects', { page: 1, pageSize: 10 })  // → 返回 data.items
await curl.post('/api/sign-test', { userId: 123 })

// ② 单次临时关闭签名(例如登录接口,还没有签名上下文)
await curl.post('/api/login', { account, password }, { sign: { enabled: false } })

// ③ 拦截器
curl.setRequestInterceptor(cfg => { /* 可以追加 token header */ return cfg })
curl.setResponseInterceptor((resp, cfg) => resp)
curl.setErrorInterceptor((err, cfg) => Promise.reject(err))

// ④ 响应自动解包:后端返回 { code: 200, data: {...}, message: 'ok' } → code 为 0 或 200 时 resolve(data),其他 code 走 errorInterceptor

自动签名实现要点:

  • 签名目标参数 = query params + body 对象(GET 取 query;POST/PUT 合并 body)
  • 生成 timestamp(秒)+ nonce(随机 10 位字符串)参与签名
  • header 模式:x-signature / x-timestamp / x-nonce 写入 header,同时回写到 GET 的 params 或 POST 的 body,方便后端 { ...ctx.query, ...ctx.request.body } 直接合并校验

2.5 中间件架构升级:priority 自动排序

2.5.1 旧方案的问题

之前 elpis-core/index.js 里硬编码了一份 middlewareOrder 数组,新增/删除中间件都要改 core 代码,违反开闭原则。

2.5.2 新方案:priority 驱动

每个中间件文件(app/middleware/*.js)统一导出:

module.exports = {
  name: 'apiSignVerify',
  priority: 70,        // 数字越小越先执行
  middleware: async (ctx, next) => { /* 具体逻辑 */ }
}

elpis-core/loader/middleware.js 加载所有中间件后,执行:

const list.sort((a, b) => a.priority - b.priority).forEach(item => app.use(item.middleware))

2.5.3 当前完整中间件顺序(12 个,升序)

priorityname职责
10corskoa2-cors,跨域头
20bodyParserkoa-bodyparser,解析 JSON body
30loggerlog4js 注入
40errorHandler洋葱外层 try/catch,template not found → 302,重定向状态码不覆盖 body
50accessLogger记录 method/url/status/耗时/ip 的 access log
60auth用户鉴权占位(JWT/Session)
70apiSignVerifyAPI 签名统一卡点(本节核心)
80validatorAJV 参数校验(基于 router-schema/*.js)
90requestLogger开发环境详细请求体日志
100webpackDevExpress + webpack-dev-middleware + HMR 桥接(仅开发环境启用)
110statickoa-static,服务 app/public/ 和 app/public/dist/ 下的静态资源
120nunjuckskoa-nunjucks-2,注入 ctx.render(),.tpl 模板引擎

签名卡点的位置:priority 70,在 cors/bodyParser/logger/errorHandler 之后(保证 body 已解析、日志可记录),在 auth/validator 之前(没有合法签名的请求直接挡在业务逻辑之外)。静态资源和 HMR 路径在 shouldVerify 返回 false,不会走到签名检查逻辑。


2.6 API 签名校验统一卡点:app/middleware/api-sign-verify.js

2.6.1 配置项(config/config.default.js → apiSign)

apiSign: {
  enabled: true,                                        // 全局开关,dev 可临时关闭排查
  secret: process.env.API_SIGN_SECRET || 'elpis-dev-secret-key',
  maxAge: process.env.API_SIGN_MAX_AGE || 300,          // 请求有效期(秒)= 5 分钟
  excludePaths: [                                        // 白名单前缀(命中则不校验)
    '/page', '/view',
    '/api/public', '/api/login', '/api/register',
    '/assets', '/public', '/static', '/js', '/css',
    '/images', '/fonts', '/media', '/__webpack_hmr'
  ]
}

2.6.2 拦截范围规则(shouldVerify)

① 默认排除(永远不签名):
   ├── /page/* /view/*                   页面路由
   ├── /api/public/* /api/login /register 公开/登录
   ├── /assets /public /static /js /css /images /fonts /media   静态资源
   └── /__webpack_hmr 及 路径含 .hot-update.    HMR 热更新

② 其他所有路径(包括 /api/*、未知接口、未知资源)全部走签名检查。

设计原则:白名单模式。宁可多拦截,也不能漏掉 API。新增的接口只要路径没命中白名单就天然受签名保护。

2.6.3 完整校验流程

请求进入 → priority 70 触发 middleware
  │
  ├─ apiSign.enabled? 否则 → await next()
  ├─ shouldVerify(ctx.path)? 否则 → await next()
  │
  ├─ params = { ...ctx.query, ...ctx.request.body }
  ├─ signature = params.signature/params.sign 或 ctx.get('x-signature')
  ├─ timestamp = Number(params.timestamp || ctx.get('x-timestamp'))
  ├─ nonce     = params.nonce || ctx.get('x-nonce')
  │
  ├─ 缺 signature → 401 缺少签名参数
  ├─ 缺 timestamp → 401 缺少时间戳参数
  ├─ 缺 nonce    → 401 缺少nonce参数
  │
  ├─ verifyTimestamp(timestamp, maxAge=300s) 失败 → 401 请求已过期
  │
  ├─ verifyNonce(nonce, app.nonceCache)
  │     └─ nonce 已使用过 → 401 nonce已使用,可能是重放攻击
  │     └─ 记录 nonce 并清理 5 分钟前的旧 nonce
  │
  ├─ verifySign(params, secret, signature)
  │     └─ 过滤 sign/signature → 按 key 排序 → JSON.stringify 对象 → &拼接 + secret → MD5
  │     └─ 与收到的 signature 不相等 → 401 签名验证失败
  │
  └─ 全部通过 → await next() 进入业务

失败响应统一格式:

{ "code": 401, "message": "缺少签名参数|请求已过期|签名验证失败|nonce已使用…", "data": null }

2.6.4 nonce 缓存策略

简单实现使用 app.nonceCache = { [nonce]: expireTime },每次请求清理 maxAge 前的旧条目。生产环境应替换为 Redis,保证多实例/多机部署下重放防护依然有效。


2.7 签名算法对齐:前端 generateSign ↔ 后端 verifySign

签名算法前后端必须一字不差,否则即使合法请求也会 401。

2.7.1 算法步骤(两端一致)

输入:params(对象,可含 query+body)、secret(字符串)

1. 过滤:删除 params 中 key 为 'signature' 或 'sign' 的项
2. 排序:Object.keys(filtered).sort() 字典序
3. 拼接:sortedKeys.map(key => {
          v = filtered[key]
          if typeof v === 'object' → `${key}=${JSON.stringify(v)}`
          else                     → `${key}=${v}`
       }).join('&')
4. 加盐:signStr = 拼接结果 + secret(末尾直接追加,无 &)
5. MD5:signature = md5_hex(signStr)  ← 两端必须同一个 MD5 实现
6. 附带:timestamp = Math.floor(Date.now()/1000),nonce = Math.random().toString(36).slice(2)

2.7.2 历史踩坑:MD5 实现不一致

早期前端使用了一个简化版 JS MD5,对于含有中文或特殊字符的参数,计算结果与 Node crypto 不一致,导致合法请求 签名验证失败 401。

解决方案:前端 utils.js 替换为 Joseph Myers RFC 1321 标准实现,并使用真实密钥 + 参数做过端到端对比(curl 生成的签名与 Node crypto 逐字节一致)。

2.7.3 传输模式两种(任选其一,前后端约定好即可)

项目默认 mode: 'header'(签名放在 header,URL 干净):

  • 前端 curl.js:在 config.headers 写入 x-signature / x-timestamp / x-nonce,同时把 timestamp/nonce 回写进 query 或 body,便于后端 { ...query, ...body } 合并时参数完整。
  • 后端 api-sign-verify.js:优先读 params,取不到再读 header。

备用 mode: 'params':signature/timestamp/nonce 直接放在 query 或 body,适合调试(URL 上就能看到签名串)。


2.8 闭环验证:app/pages/page1/page1.vue 三场景

2.8.1 页面内容结构

  1. Section 1:卡点范围和规则说明(UI 级文档,直接给使用者看)
  2. Section 2:curl.get('/api/projects', { page:1, pageSize:10 }) 正常带签名调用
  3. Section 3:curl.post('/api/sign-test', { userId:123 }) POST 带签名
  4. Section 4:curl.get('/api/sign-test', {}, { sign:{ enabled:false } }) 临时关闭签名(对照实验)

2.8.2 验证结果(✅ 均通过)

场景HTTP 方法URL签名预期结果实际结果
Section 2 onMountedGET/api/projects?page=1&pageSize=10&timestamp=&nonce=✅ 自动附加 header+query200 返回 mock 项目列表✅ 加载到 3 个项目,表格渲染正常
Section 3 点击按钮POST/api/sign-test body: {userId,action,timestamp,nonce}✅ header 附加200 返回 {message/timestamp/path/method}✅ ElMessage 提示「POST 带签名请求成功」
Section 4 对照实验GET/api/sign-test 无附加❌ 临时关闭401 缺少签名参数✅ ElMessage 提示「未签名返回 401(符合预期):[401] 缺少签名参数」

2.8.3 ProjectService mock 数据设计

app/service/project.js 内置 3 条项目 + 4 条项目成员,完全内存数组,不依赖 MySQL。任何环境只要启动服务就能演示闭环,省去 DB 搭建:

this.projects = [
  { id:1, name:'Elpis框架开发',   status:'active',    memberCount: 2 },
  { id:2, name:'前端组件库',       status:'active',    memberCount: 2 },
  { id:3, name:'移动应用开发',     status:'completed', memberCount: 1 }
]

2.8.4 后端日志对应关系(证据链)

[apiSignVerify] 检查路径: /api/projects, 配置enabled: true → status 200 ✓
[apiSignVerify] 检查路径: /api/sign-test POST → status 200 ✓
[apiSignVerify] 检查路径: /api/sign-test GET  → status 401 ✓(duration 0ms → 卡点立即返回,没进入 controller)

2.9 工程规范:ESLint + Commit Message Hooks

package.json 中的 ghooks 配置:

"config": {
  "ghooks": {
    "pre-commit": "npm run lint",
    "commit-msg": "validate-commit-msg"
  }
}

2.9.1 pre-commit

自动执行 eslint --quiet --ext .js,.jsx,.vue(--quiet 只报 error,忽略 warning),通过才能继续。

2.9.2 commit-msg(Angular Conventional Commits)

格式要求:

<type>(<scope>): <subject>
  │      │        └─ 简短描述,冒号和描述间必须有空格,末尾不加句号(。/.)
  │      └─ 影响范围括号里,如 frontend/core/build/page1(可省略但建议写)
  └─ feat / fix / docs / style / refactor / perf / test / build / ci / chore / revert

本次失败案例 vs 正确写法:

❌ INVALID:  feat:完成前端工程化与基础建设。
                ↑冒号后没空格              ↑末尾中文句号

✅ VALID:    feat(frontend): 完成前端工程化与基础建设
✅ VALID:    build(webpack): 配置 splitChunks 与 Terser 压缩约束
✅ VALID:    fix(sign): 修正前端 MD5 实现与后端 crypto 不一致

2.9.3 已移除的 hook

pre-push hook 原本执行 npm test(mocha test/**/*.test.js --exit),因当前仓库无测试文件导致 push 被拒,已移除,后续补齐单测再恢复。


2.10 架构约束与 Lessons Learned(来源于 project_memory)

这些约束是从数十次编译/运行错误中总结的,修改相关代码前必须检查是否违反。

  1. Loader 执行顺序(elpis-core/index.js)固定:config → extend → routerSchema → service → controller → middleware → router
  2. 框架核心不反向引用业务:elpis-core/ 禁止 require('../app/...')
  3. app.baseDir = process.cwd() 在 core 设置,业务代码访问项目根必须用 app.baseDir,禁止再用 process.cwd()
  4. 中间件文件放 app/middleware/,一个文件一个中间件,必须新格式导出:module.exports = { name, priority, middleware },priority 小的先执行
  5. Middleware 文件内引用自身资源用 __dirname,不要依赖 cwd
  6. Webpack 配置文件内部定位资源一律用 __dirname,不用 process.cwd()
  7. splitChunks 只在 webpack.prod.js 配置;dev 环境禁用,保证构建速度和调试体验
  8. Webpack 输出目录固定 app/public/dist/,不要用 output 或根 dist
  9. Terser 必须保留下划线属性:properties.regex = /^_/,否则 Vue 3 线上白屏(<!---->)
  10. Terser toplevel 必须 false
  11. Terser 排除 /chunk-(element-plus|vue)/,避免 ES2020+ 语法崩溃
  12. babel-loader 排除 vue-router,确保它的 ES2020+ 语法不被破坏
  13. 签名排除路径必须包含:/static /js /css /images /fonts /media /public /assets /page /view /api/public /api/login /api/register /__webpack_hmr,以及 .hot-update.* 文件
  14. errorHandler:对 301/302/307/308 直接 return 不覆盖 body;template not found 走 302 跳首页,但排除 /api/* /static/*
  15. 签名中间件 timestamp 是秒级(Math.floor(Date.now()/1000)),不是毫秒,否则 5 分钟阈值瞬间过期
  16. MD5 算法前后端对齐:前端用 RFC 1321 标准实现(Joseph Myers 版),不要使用网上流传的简化版(中文场景哈希不一致)
  17. Koa + Express 中间件桥接:必须设置 ctx.respond = false 阻止 Koa 覆盖 Express 写回的 res(HMR、dev-middleware 依赖此)

2.11 本分支新增/修改关键文件清单

文件路径新增 / 修改作用
app/webpack/config/webpack.base.js新增Webpack 基础配置(entry、loader、DefinePlugin、别名、vue-loader)
app/webpack/config/webpack.dev.js新增开发配置(devtool、HMR entry、无 splitChunks)
app/webpack/config/webpack.prod.js新增生产配置(splitChunks + Terser 三条硬约束 + CssMinimizer + clean)
app/webpack/dev.js新增Express 子服务工厂:webpack-dev-middleware + webpack-hot-middleware
app/webpack/prod.js新增生产打包入口
app/webpack/build.js新增产物输出通用逻辑
webpack.config.js新增根目录桥接:根据 NODE_ENV 返回 base/merge 结果
config/webpack.dev.js新增Webpack 开发子服务工厂(供 webpackDev 中间件调用)
app/middleware/webpackDev.js新增Express ↔ Koa 桥接中间件,priority 100,开发模式启用
app/middleware/api-sign-verify.js新增/完善API 签名统一卡点,priority 70,shouldVerify 白名单 + nonce 缓存
app/middleware/errorHandler.js改造保护 3xx 不覆盖 body、template not found → 302、排除 /api /static
app/middleware/static.js新增koa-static 多目录服务 app/public 和 app/public/dist
app/middleware/*.js(其余)改造统一导出 { name, priority, middleware } 新格式
elpis-core/loader/middleware.js改造读取新格式字段,按 priority 升序自动排序注册;移除未使用的 sep 变量
elpis-core/index.js改造移除硬编码 middleware 顺序,改用 loader 排序后的列表
app/view/entry.tpl新增统一页面骨架模板,所有页面共享
app/pages/boot.js新建/改造统一 Vue 启动入口:Pinia + Element Plus + Vue Router + libs 数组注入
app/pages/page1/entry.page.js改造改为调用 boot(page1, options) 极简模板
app/pages/page2/entry.page.js改造同上
app/pages/page1/page1.vue改造新增四个 Section:卡点说明 + /api/projects + sign-test POST + sign-test 未签名对照
app/pages/router/index.js新增vue-router routes 声明和懒加载配置
app/pages/store/index.js新增Pinia store 示例
app/pages/common/utils.js新增RFC 1321 MD5 + generateSign + 通用工具函数
app/pages/common/curl.js新增fetch 封装:自动签名、响应解包、拦截器、超时、单次关闭签名
app/controller/signTest.js新增签名验证专用接口控制器 GET/POST 同方法 check()
app/router/sign-test.js新增/api/sign-test GET/POST 路由
app/service/project.js改造内置 mock 项目 + 成员 + 分页,无需 DB 可演示
config/config.default.js改造新增 apiSign 完整配置(enabled/secret/maxAge/excludePaths)
config/config.prod.js改造生产密钥改为环境变量读取,apiSign 强制 enabled
babel.config.js改造支持 element-plus 按需引入与 runtime helpers
.eslintrc保持ESLint + Vue 规则
package.json改造新增 build/dev/build:prod 脚本、ghooks 配置、依赖补全
.gitignore改造加入 app/public/dist/、日志、node_modules
docs/directory-structure.md新增完整目录职责说明文档
本文档新增第二节:本分支新增内容总结

2.12 后续可继续演进方向

  • nonce 缓存接入 Redis:保证多实例部署重放防护
  • JWT Auth 中间件(priority 60 位置已有占位):token 校验 + 用户上下文注入
  • /api/public/ 限流*:公开接口加 rate limit 防刷
  • 单测框架接入:mocha + chai,恢复 pre-push npm test
  • 签名密钥轮换机制:API 版本化 + 双密钥过渡
  • PWA / Service Worker:静态资源离线缓存
  • Dockerfile + docker-compose:Node + MySQL + Redis 一键起环境
  • 日志接入 APM:log4js 输出到 ELK / SkyWalking

文档归属:Elpis 学习项目第二节总结

对应分支:feature/fontend-build

最后更新:2026-08-12