--- description: Node Core(Koa2+Sequelize)框架集成、启动与控制器约定;若项目含 framework/node-core-framework.js 则遵循本规范 alwaysApply: true --- # Node Core 规范 若项目根目录(或约定路径)存在 **`framework/node-core-framework.js`**(或等价 Node Core 打包产物),后端启动与路由须按本节执行。 ## 本仓库路径对照 | 规范表述 | 本仓库 | |---------|--------| | 框架入口 | `./framework/node-core-framework.js` | | 框架配置 | `./config/framework.config.js` | | 业务后台控制器目录 | `api/controller_admin`(前缀 `/admin_api`,勿在键名里重复写前缀) | | 小程序/开放端控制器 | `api/controller_front`(前缀 `/api`) | | 手动注册/代理等自定义 | `api/controller_custom`(仅 `app.js` 的 `beforeInitApi` 内 `addRoutes`) | | 模型关联 | `./config/model.associations.js` | | 白名单 | `./config/config.js` → `allowUrls` | | `beforeInitApi` 实现体 | `./config/before_init_api.js`(由 `framework.config.js` 挂到 `init` 参数) | | 管理端通用工具(非业务域) | `api/utils/query_helpers.js`(分页/筛选/导出)、`api/utils/biz_audit.js`(审计) | | 跨端/复杂业务 | `api/service/*`(如 `biz_auth_verify`、`biz_proxy_service`、`biz_subscription_logic`、`biz_token_logic`) | --- # Framework 启动(`app.js`) ## 入口流程 1. `require('./framework/node-core-framework.js')` → `Framework.init({ ... })` 2. **配置**:展开 `require('./config/framework.config.js')`,勿在 `app.js` 内重复写数据库、端口等业务配置。 3. **模型关联**:传入 `businessAssociations: require('./config/model.associations.js')`。 4. **`beforeInitApi`**:在 `Framework.init` **resolve 之前**执行;本仓库逻辑在 **`config/before_init_api.js`**,由 **`config/framework.config.js`** 导出并随 `...config` 传入 `init`,**`app.js` 只负责 `Framework.init({ ...config, businessAssociations })`**。 - 动态转发路由:在 `beforeInitApi` 内 `require('./api/controller_custom/proxy_api')` 的 `buildProxyRoutes()`,再注册两套前缀: - `framework.addRoutes('', proxyRoutes)` — 与 OpenAPI/Swagger 文档路径一致(如 `/admin/...`) - `framework.addRoutes('/api', proxyRoutes)` — 兼容 `/api/admin/...` - **不要**把代理路由写在 `api/controller_front`;自定义注册放在 `api/controller_custom`。 5. **`await framework.start(config.port.node)`**:真正监听 HTTP。 6. **定时任务**等依赖框架就绪的逻辑放在 `start()` 里、且 **`framework.start` 完成之后**再 `require`/`init`,例如 `middleware/schedule.js`。 ## 错误处理 - `start()` 使用 `try/catch`;失败时打印错误并 `process.exit(1)`,避免进程无监听挂起。 ## 修改启动行为时注意 - 新增 `addRoutes`:键格式与框架约定一致,如 `'POST /path': async (ctx) => { ... }`。 - 白名单路径在 `config/config.js` 的 `allowUrls`(框架层免 token)与业务层(如转发 Token 鉴权)是两段逻辑,改路由前缀时需同步 `allowUrls`。 --- ## 路由体系 - **约定式控制器**:`framework.config.js` 的 `apiPaths` 指向目录,扫描导出;单条路径 = `prefix` + 控制器内声明的路径。 - **内置系统管理**:后台接口固定前缀 **`/admin_api`**(由框架注册系统控制器;本仓库业务管理接口在 `api/controller_admin`,同前缀)。 - **手动路由**:仅在 **`beforeInitApi`** 内调用 `framework.addRoutes(prefix, routes)`;`routes` 键名格式与控制器相同。不要在 `Framework.init()` 已结束后再注册同类路由(会晚于静态路由流水线,易 404)。 ## 鉴权与白名单 - `allowUrls`:路径**子串**匹配则免框架层 admin/applet token;另有默认放行段(以框架 `middleware` 为准)。 - `apiPaths` 项可设 `authType`:`admin` → 校验 **`admin-token`**(`ctx.getAdminUserId()`);`applet` → **`applet-token`**(`ctx.getPappletUserId()`)。 ## 中间件时机 - `beforeInitApi(framework)`:在框架完成路由装配流水线中的**早阶段**执行,适合 `framework.addRoutes` 或与路由顺序相关的 `use`。 - `init` **之后**再往 `app` 追加的中间件通常位于路由之后,多数请求已被消费,一般仅兜底。 ## 接口与命名(业务侧) - 语义化 URL,**优先 POST** 做变更类接口;路径片段可与项目统一为 **snake_case**(如 `/order/create`)。 - JSON 响应优先用 Context 扩展:`ctx.success` / `ctx.fail` / `ctx.tokenFail`;参数可用 `ctx.get`(query+body 合并,body 覆盖 query,以框架实现为准)。 ## 修改核心 / 打包注意 - 路由注册顺序以框架 `init` 内部为准;新增动态加载路径时若存在 webpack 打包,需与框架内 `__non_webpack_require__` 等行为一致。 ## 控制器约定 ### 导出形状 - 每个文件 `module.exports = { 'METHOD /path': handler, ... }`。 - **METHOD** 仅为 **`GET` 或 `POST`**,后接**一个空格**再写路径,例如:`POST /goods/create`、`GET /goods/detail/:id`。 - `handler` 建议为 **async** `(ctx, next) => { ... }`;需继续管道时 `await next()`。 ### 管理端(本仓库) - 文件在 **`api/controller_admin`**;对外完整路径 = **`/admin_api` + 键名中的路径**,键名里**不要**再写 `/admin_api`。 - 需登录:请求头 `admin-token: `。 ### 业务/开放端(本仓库) - `api/controller_front`,`apiPaths` 中 `prefix: '/api'`,`authType: 'applet'`。 ### 常见 Context API(以框架为准) - 参数:`ctx.get('id')`、`ctx.getBody()`、`ctx.getQuery()`、`ctx.getPageSize()` 等。 - 响应:`ctx.success(data, msg)`、`ctx.fail(msg)`、`ctx.tokenFail()`、`ctx.json(code, message, data)`。 - 鉴权:`ctx.getAdminUserId()`、`ctx.getPappletUserId()`。 ### 避免 - 不要用 **`PUT` / `DELETE`** 等作为导出键(框架只按 **GET/POST** 注册)。 - 不要把 **`addRoutes`** 散落到 `init` 完成后的随意位置;统一放 **`beforeInitApi`**。