前端工程化与签名校验闭环实战(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.js | Express 子服务 + 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.js | RFC 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-msg | pre-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/(Koastatic中间件服务此路径,线上直接访问/js/、/css/)
2.2.2 开发模式:Express 子服务 + Koa 桥接
核心思路:webpack-dev-middleware 和 webpack-hot-middleware 是 Express 生态,Koa 直接套会因 ctx.body 覆盖 Express 的 res 导致 HMR 失败。故采用子服务 + 桥接方案。
实现要点:
- Express 子服务:挂载
webpack-dev-middleware(内存编译 + 文件监听,不落盘)+webpack-hot-middleware(WebSocket 端点/__webpack_hmr,推送.hot-update.js/.hot-update.json) - Koa 桥接(
app/middleware/webpackDev.js,priority 100):对命中的路径设置ctx.respond = false,透传 req/res 给 Express,避免 Koa 覆盖响应 - 启动清理:开发模式启动时自动清理
app/public/dist/下旧的.tpl模板和 hashed JS,避免内存文件与落盘文件混用导致 404 --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 个,升序)
| priority | name | 职责 |
|---|---|---|
| 10 | cors | koa2-cors,跨域头 |
| 20 | bodyParser | koa-bodyparser,解析 JSON body |
| 30 | logger | log4js 注入 |
| 40 | errorHandler | 洋葱外层 try/catch,template not found → 302,重定向状态码不覆盖 body |
| 50 | accessLogger | 记录 method/url/status/耗时/ip 的 access log |
| 60 | auth | 用户鉴权占位(JWT/Session) |
| 70 | apiSignVerify | API 签名统一卡点(本节核心) |
| 80 | validator | AJV 参数校验(基于 router-schema/*.js) |
| 90 | requestLogger | 开发环境详细请求体日志 |
| 100 | webpackDev | Express + webpack-dev-middleware + HMR 桥接(仅开发环境启用) |
| 110 | static | koa-static,服务 app/public/ 和 app/public/dist/ 下的静态资源 |
| 120 | nunjucks | koa-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 页面内容结构
- Section 1:卡点范围和规则说明(UI 级文档,直接给使用者看)
- Section 2:
curl.get('/api/projects', { page:1, pageSize:10 })正常带签名调用 - Section 3:
curl.post('/api/sign-test', { userId:123 })POST 带签名 - Section 4:
curl.get('/api/sign-test', {}, { sign:{ enabled:false } })临时关闭签名(对照实验)
2.8.2 验证结果(✅ 均通过)
| 场景 | HTTP 方法 | URL | 签名 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| Section 2 onMounted | GET | /api/projects?page=1&pageSize=10×tamp=&nonce= | ✅ 自动附加 header+query | 200 返回 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)
这些约束是从数十次编译/运行错误中总结的,修改相关代码前必须检查是否违反。
- Loader 执行顺序(
elpis-core/index.js)固定:config → extend → routerSchema → service → controller → middleware → router - 框架核心不反向引用业务:
elpis-core/禁止require('../app/...') app.baseDir = process.cwd()在 core 设置,业务代码访问项目根必须用app.baseDir,禁止再用process.cwd()- 中间件文件放
app/middleware/,一个文件一个中间件,必须新格式导出:module.exports = { name, priority, middleware },priority小的先执行 - Middleware 文件内引用自身资源用
__dirname,不要依赖 cwd - Webpack 配置文件内部定位资源一律用
__dirname,不用process.cwd() - splitChunks 只在
webpack.prod.js配置;dev 环境禁用,保证构建速度和调试体验 - Webpack 输出目录固定
app/public/dist/,不要用 output 或根 dist - Terser 必须保留下划线属性:
properties.regex = /^_/,否则 Vue 3 线上白屏(<!---->) - Terser
toplevel必须 false - Terser 排除
/chunk-(element-plus|vue)/,避免 ES2020+ 语法崩溃 - babel-loader 排除
vue-router,确保它的 ES2020+ 语法不被破坏 - 签名排除路径必须包含:
/static/js/css/images/fonts/media/public/assets/page/view/api/public/api/login/api/register/__webpack_hmr,以及.hot-update.*文件 - errorHandler:对 301/302/307/308 直接 return 不覆盖 body;template not found 走 302 跳首页,但排除
/api/*/static/* - 签名中间件 timestamp 是秒级(
Math.floor(Date.now()/1000)),不是毫秒,否则 5 分钟阈值瞬间过期 - MD5 算法前后端对齐:前端用 RFC 1321 标准实现(Joseph Myers 版),不要使用网上流传的简化版(中文场景哈希不一致)
- 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