# Node Core 框架 API 文档2 本文档与仓库源码一致,说明 **HTTP 接口**、**业务控制器约定**、**Koa Context 扩展**、**框架模块导出(`init` / `getModels` 等)** 及 **内置服务**。 --- ## 目录 1. [基础约定](#1-基础约定) 2. [认证与白名单](#2-认证与白名单) 3. [Swagger 与 OpenAPI](#3-swagger-与-openapi) 4. [内置管理端 REST API(`/admin_api`)](#4-内置管理端-rest-apiadmin_api) 5. [自定义业务控制器](#5-自定义业务控制器)(含 [手动添加路由](#54-手动添加路由addroutes)) 6. [Context 方法参考](#6-context-方法参考) 7. [框架 Node 模块 API](#7-框架-node-模块-api) 8. [内置服务(`getServices()`)](#8-内置服务getservices) 9. [业务项目集成与配置](#9-业务项目集成与配置) 10. [内部方法与外部注入详解](#10-内部方法与外部注入详解) 11. [注意事项](#11-注意事项) 12. [常见问题排查(FAQ)](#12-常见问题排查faq) --- ## 1. 基础约定 ### 1.1 完整 URL 业务项目在配置里通过 `apiPaths` 为「控制器目录」指定路由前缀 `prefix`。最终地址为: ```text http(s)://:<控制器内定义的路径> ``` 框架**内置系统管理控制器**固定注册在前缀 **`/admin_api`** 下(见 `framework.js` 中 `_registerSystemControllers`)。例如控制器键名为 `"POST /sys_user/login"` 时,完整路径为: ```text POST /admin_api/sys_user/login ``` 业务项目自己的控制器由 `apiPaths[].path` 加载,键名同样为 `"GET /xxx"` / `"POST /xxx"`,再拼上所配置的 `prefix`。 ### 1.2 统一 JSON 响应 成功与业务失败均可能返回下列结构(`middleware/baseRequest.js` 中 `ctx.json` / `ctx.success` / `ctx.fail` / `ctx.tokenFail`): | 字段 | 类型 | 说明 | |------|------|------| | `code` | `number` | `0` 成功;`-1` 业务失败;`-2` 未登录或 token 无效 | | `message` | `string` | 提示文案 | | `data` | `any` | 载荷;失败时可能为 `{}` | 示例: ```json { "code": 0, "message": "请求成功", "data": {} } ``` **特例:** 部分接口直接输出文件流(如用户导出 CSV),不返回上述 JSON。 ### 1.3 请求参数合并规则 `ctx.get(name)` 将 **Query** 与 **Body** 合并后按字段名读取(实现为 `Object.assign({}, query, body)`)。同名字段以 **body 覆盖 query**。 ### 1.4 HTTP 方法 控制器导出对象的键必须为 **`GET`** 或 **`POST`**,格式: ```text <空格> <路径> ``` 路径中可含动态段(由 `koa-router` 解析)。删除、更新类操作在业务上常用 `POST`。 --- ## 2. 认证与白名单 ### 2.1 默认免登录路径(子串匹配) 请求 **path** 任意位置包含下列子串之一时,**不校验** token(`baseRequest.js`): - `/login` - `/register` - `/health` - `/docs` - `/swagger.json` ### 2.2 配置 `allowUrls` 配置项 `allowUrls: ['/your/public', ...]` 与上述默认列表合并;命中规则同样是 **path 包含子串**。 ### 2.3 `apiPaths` 与 `authType` `apiPaths` 每一项为**字符串**(仅作前缀,旧用法)或**对象**: ```js { path: './controller', prefix: '/admin_api', authType: 'admin' // 可选:'admin' | 省略时默认 'applet' } ``` - **`authType === 'admin'`**:从请求头 **`admin-token`** 解析 JWT,得到后台用户;`ctx.getAdminUserId()` 返回用户 id,无效则为 `0`。 - **默认 `applet`**:从 **`applet-token`** 解析;`ctx.getPappletUserId()` 返回用户 id。开发时若 query 带 **`userIdTest45`**,会优先返回该值(便于联调)。 未命中白名单且未通过对应前缀的鉴权时,返回 **`code: -2`**,`message: "非法请求,或登录已超时"`(`ctx.tokenFail`)。 **管理端请求头示例:** ```http admin-token: <登录返回的 token> Content-Type: application/json ``` --- ## 3. Swagger 与 OpenAPI | 路径 | 说明 | |------|------| | `GET /api/docs` | Swagger UI 页面(HTML) | | `GET /api/swagger.json` | OpenAPI JSON(由 `SwaggerService.generateSpecs` 生成) | **Swagger 页相关辅助接口**(`lib/swaggerUIManager.js`,响应格式与业务 JSON 约定独立): | 路径 | 方法 | 说明 | |------|------|------| | `/api/swagger/security-config` | GET | 读取 Cookie `swagger_security_config` 中的安全配置 | | `/api/swagger/security-config` | POST | Body:`{ securityConfig: Array }`,写入 Cookie | | `/api/swagger/security-config` | DELETE | 清除该 Cookie | --- ## 4. 内置管理端 REST API(`/admin_api`) 下文 **`BASE`** = `http(s)://:`。系统接口完整路径为 **`BASE + /admin_api + 下表路径`**。 **通用请求约定:** - 需登录接口:请求头带 **`admin-token`**。 - 参数可通过 **Query** 或 **JSON Body** 传递(见 §1.3)。 - 成功时 `code === 0`,具体数据在 **`data`**(除非另有说明)。 --- ### 4.1 用户模块 `sys_user` 源码:`controller/admin/sys_user.js`。 | 路径 | 方法 | 认证 | 说明 | |------|------|------|------| | `/sys_user/index` | GET | admin | 用户列表。当前登录租户为平台租户时看全部;普通租户仅看本租户。 | | `/sys_user/login` | POST | 免登录 | 登录:`name`、`password`,可选 `tenant_code`。成功返回 `token`、`user`、`authorityMenus`、`tenant`。 | | `/sys_user/export` | POST | admin | **CSV 文件流**:列为模型字段注释或字段名,行为未删除用户;非 JSON。 | | `/sys_user/authorityMenus` | POST | admin | 当前登录用户可见菜单。`role.type=0` 按 `menus` 过滤;`role.type=1` 返回全部未删菜单。 | | `/sys_user/add` | POST | admin | 新增用户。平台租户可指定 `tenant_id`;普通租户固定写入当前租户。 | | `/sys_user/edit` | POST | admin | 编辑用户。支持 `name`、`roleId`、`password`、`tenant_id(平台租户)`。 | | `/sys_user/del` | POST | admin | 软删除:`id`,`is_delete` 置 1。 | **`POST /sys_user/login` 请求 body(完整示例):** ```json { "name": "admin", "password": "123456", "tenant_code": "default" } ``` **`POST /sys_user/login` 响应 `data`(成功示例):** ```json { "token": "", "user": { "id": 1, "name": "admin", "roleId": 1, "tenant_id": 1 }, "authorityMenus": [1, 2, 3, 10], "tenant": { "id": 1, "name": "", "code": "default", "is_platform": 1 } } ``` **`POST /sys_user/add` 请求 body(完整示例):** ```json { "name": "ops_user", "password": "123456", "roleId": 2, "tenant_id": 2 } ``` **`POST /sys_user/edit` 请求 body(完整示例):** ```json { "id": 12, "name": "ops_user_2", "password": "12345678", "roleId": 3, "tenant_id": 2 } ``` **`POST /sys_user/del` 请求 body(完整示例):** ```json { "id": 12 } ``` **`POST /sys_user/authorityMenus` 请求 body(完整示例):** ```json {} ``` **`POST /sys_user/export` 请求 body(完整示例):** ```json {} ``` **响应示例(成功):** ```json { "/sys_user/index": { "code": 0, "message": "请求成功", "data": [{ "id": 1, "name": "admin", "role": { "id": 1, "name": "管理员" }, "tenant": { "id": 1, "code": "default" } }] }, "/sys_user/authorityMenus": { "code": 0, "message": "请求成功", "data": [{ "id": 10, "name": "用户管理", "path": "/sys/user" }] }, "/sys_user/export": "CSV 文件流(非 JSON,Content-Disposition: attachment)", "/sys_user/add": { "code": 0, "message": "请求成功", "data": { "id": 12, "name": "ops_user", "roleId": 2, "tenant_id": 2 } }, "/sys_user/edit": { "code": 0, "message": "请求成功", "data": [1] }, "/sys_user/del": { "code": 0, "message": "请求成功", "data": [1] } } ``` **curl 示例:** ```bash curl -X POST "$BASE/admin_api/sys_user/login" -H "Content-Type: application/json" \ -d "{\"name\":\"admin\",\"password\":\"your_password\",\"tenant_code\":\"default\"}" curl "$BASE/admin_api/sys_user/index" -H "admin-token: " ``` --- ### 4.2 菜单与低代码 `sys_menu` / `model` / `form` 源码:`controller/admin/sys_menu.js`,依赖 `platformProjectService`(远程平台与 `config.project_key`)。 | 路径 | 方法 | 认证 | 说明 | |------|------|------|------| | `/sys_menu/index` | GET | admin | 菜单列表,`sort` 升序;每条增加 `parent_node_ids`。 | | `/sys_menu/add` | POST | admin | 创建菜单,并尝试触发表单创建/代码下载。 | | `/sys_menu/edit` | POST | admin | `id` + `body` 更新字段。 | | `/sys_menu/del` | POST | admin | 软删除:`id`。 | | `/model/all` | POST | admin | 平台模型列表(内部 `projectKey`);失败返回 `[]`。 | | `/form/generate` | POST | admin | 参数 **`id`**(菜单 id)。按菜单关联表单更新并生成,下载资源;成功 `frontApiUrl`、`frontVueUrl`;失败「生成失败」。 | | `/model/generate` | POST | admin | 参数 **`id`**(菜单 id)。按关联 `model_id` 生成后端代码并下载;成功返回 `controllerPath`、`modelPath` 等;失败「生成失败」。 | | `/model/interface` | POST | admin | Body:`model_key`(模型名,须已在 `getModels()` 中存在)、`map_option: { key, value }`。返回 `[{ key, value }, ...]`;模型不存在则业务失败「数据模型不存在」。 | **`POST /sys_menu/add` 请求 body(完整示例):** ```json { "name": "订单管理", "parent_id": 0, "icon": "ios-list-box", "path": "/order/index", "type": "页面", "model_id": 5, "form_id": 0, "component": "views/order/index.vue", "api_path": "/api/order", "is_show_menu": 1, "is_show": 1, "sort": 10 } ``` **`POST /sys_menu/edit` 请求 body(完整示例):** ```json { "id": 20, "name": "订单中心", "icon": "ios-paper", "path": "/order/list", "sort": 11 } ``` **`POST /sys_menu/del` / `/form/generate` / `/model/generate` 请求 body(完整示例):** ```json { "id": 20 } ``` **`POST /model/all` 请求 body(完整示例):** ```json {} ``` **`POST /model/interface` 请求 body(完整示例):** ```json { "model_key": "sys_role", "map_option": { "key": "id", "value": "name" } } ``` **响应示例(成功):** ```json { "/sys_menu/index": { "code": 0, "message": "请求成功", "data": [{ "id": 20, "name": "订单管理", "parent_node_ids": [0] }] }, "/sys_menu/add": { "code": 0, "message": "请求成功", "data": { "frontApiUrl": "https://xx/api.js", "frontVueUrl": "https://xx/index.vue" } }, "/sys_menu/edit": { "code": 0, "message": "请求成功", "data": [1] }, "/sys_menu/del": { "code": 0, "message": "请求成功", "data": [1] }, "/model/all": { "code": 0, "message": "请求成功", "data": [{ "id": 5, "name": "order" }] }, "/form/generate": { "code": 0, "message": "请求成功", "data": { "frontApiUrl": "https://xx/api.js", "frontVueUrl": "https://xx/index.vue" } }, "/model/generate": { "code": 0, "message": "请求成功", "data": { "controllerPath": "https://xx/controller.js", "modelPath": "https://xx/model.js" } }, "/model/interface": { "code": 0, "message": "请求成功", "data": [{ "key": 1, "value": "管理员" }] } } ``` --- ### 4.3 角色模块 `sys_role` 源码:`controller/admin/sys_role.js`。 | 路径 | 方法 | 认证 | 说明 | |------|------|------|------| | `/sys_role/index` | GET | admin | 全部未删除角色。 | | `/sys_role/add` | POST | admin | 新增角色,至少传 `name`。 | | `/sys_role/edit` | POST | admin | 编辑角色,传 `id` 和更新字段。 | | `/sys_role/del` | POST | admin | 软删除:`id`。 | **`POST /sys_role/add` 请求 body(完整示例):** ```json { "name": "运营角色", "menus": [1, 2, 3, 20] } ``` **`POST /sys_role/edit` 请求 body(完整示例):** ```json { "id": 3, "name": "运营管理员", "menus": [1, 2, 3, 4, 20] } ``` **`POST /sys_role/del` 请求 body(完整示例):** ```json { "id": 3 } ``` **响应示例(成功):** ```json { "/sys_role/index": { "code": 0, "message": "请求成功", "data": [{ "id": 1, "name": "管理员", "menus": [1, 2, 3] }] }, "/sys_role/add": { "code": 0, "message": "请求成功", "data": { "id": 3, "name": "运营角色" } }, "/sys_role/edit": { "code": 0, "message": "请求成功", "data": [1] }, "/sys_role/del": { "code": 0, "message": "请求成功", "data": [1] } } ``` --- ### 4.4 系统参数 `sys_parameter` 源码:`controller/admin/sys_parameter.js`。 | 路径 | 方法 | 认证 | 说明 | |------|------|------|------| | `/sys_parameter/index` | GET | admin | `is_modified=0` 且 `is_delete=0` 的参数列表。 | | `/sys_parameter/key` | GET | admin | 查询参数 **`key`**,返回单条或未命中。 | | `/sys_parameter/setSysConfig` | POST | admin | Body:`title`、`logoUrl`,分别更新 `key=sys_title`、`key=sys_logo` 的 `value`。 | | `/sys_parameter/add` | POST | admin | 新增:整表 `body`。 | | `/sys_parameter/edit` | POST | admin | `id` + `body` 更新。 | | `/sys_parameter/del` | POST | admin | 软删除:`id`。 | **`POST /sys_parameter/setSysConfig` 请求 body(完整示例):** ```json { "title": "Node Core 管理后台", "logoUrl": "https://cdn.example.com/logo.png" } ``` **`POST /sys_parameter/add` 请求 body(完整示例):** ```json { "key": "sys_theme", "value": "dark", "name": "系统主题", "is_modified": 0 } ``` **`POST /sys_parameter/edit` 请求 body(完整示例):** ```json { "id": 8, "value": "light", "name": "系统主题(浅色)" } ``` **`POST /sys_parameter/del` 请求 body(完整示例):** ```json { "id": 8 } ``` **响应示例(成功):** ```json { "/sys_parameter/index": { "code": 0, "message": "请求成功", "data": [{ "id": 1, "key": "sys_title", "value": "管理后台" }] }, "/sys_parameter/key": { "code": 0, "message": "请求成功", "data": { "id": 1, "key": "sys_title", "value": "管理后台" } }, "/sys_parameter/setSysConfig": { "code": 0, "message": "请求成功", "data": {} }, "/sys_parameter/add": { "code": 0, "message": "请求成功", "data": { "id": 8, "key": "sys_theme", "value": "dark" } }, "/sys_parameter/edit": { "code": 0, "message": "请求成功", "data": [1] }, "/sys_parameter/del": { "code": 0, "message": "请求成功", "data": [1] } } ``` --- ### 4.5 日志模块 `sys_log` 源码:`controller/admin/sys_log.js`。 | 路径 | 方法 | 认证 | 说明 | |------|------|------|------| | `/sys_log/all` | GET | admin | 日志目录下文件名列表(倒序),项为 `{ title: 文件名 }`。 | | `/sys_log/detail` | GET | admin | Query **`title`**:文件名,返回文件文本内容。 | | `/sys_log/delete` | GET | admin | 删除单个文件:`title`。 | | `/sys_log/delete_all` | GET | admin | 清空日志目录下所有文件(慎用)。 | | `/sys_log/operates` | GET | admin | 数据库操作记录分页:`ctx.getPageSize()`,需传 **`pageOption`**(JSON 字符串或对象),如 `{"page":1,"pageSize":20}`。返回 Sequelize `findAndCountAll` 结果(含 `rows`、`count`)。 | **操作记录写入规则**:`database/db.js` 在 `define` 模型的 Sequelize 钩子中,对符合条件的表在**新增、修改、删除**时写入 `sys_log`。`sys_log` 表本身**从不**记操作日志。若需在业务库中排除其它表(如高频临时表、会话表),在 **`db.operateLogExcludeTableNames`** 中传入与模型 **`tableName`** 一致的字符串数组即可,命中表名将不写入操作日志(修改时仍会更新 `last_modify_time`)。详见 [§9.1.1](#911-db-可选字段扩展)。 **GET 请求示例:** ```bash curl "$BASE/admin_api/sys_log/all" -H "admin-token: " curl "$BASE/admin_api/sys_log/detail?title=app-2026-04-15.log" -H "admin-token: " curl "$BASE/admin_api/sys_log/delete?title=app-2026-04-15.log" -H "admin-token: " curl "$BASE/admin_api/sys_log/delete_all" -H "admin-token: " curl "$BASE/admin_api/sys_log/operates?pageOption=%7B%22page%22%3A1%2C%22pageSize%22%3A20%7D" -H "admin-token: " ``` **响应示例(成功):** ```json { "/sys_log/all": { "code": 0, "message": "请求成功", "data": [{ "title": "app-2026-04-15.log" }] }, "/sys_log/detail": { "code": 0, "message": "请求成功", "data": "日志文件文本内容..." }, "/sys_log/delete": { "code": 0, "message": "请求成功", "data": {} }, "/sys_log/delete_all": { "code": 0, "message": "请求成功", "data": {} }, "/sys_log/operates": { "code": 0, "message": "请求成功", "data": { "count": 100, "rows": [{ "id": 1, "table_name": "sys_user", "operate": "登录" }] } } } ``` --- ### 4.6 控件类型 `sys_control_type` 源码:`controller/admin/sys_control_type.js`。 | 路径 | 方法 | 认证 | 说明 | |------|------|------|------| | `/sys_control_type/all` | GET | admin | 全部未删除记录。 | | `/sys_control_type/page` | POST | admin | Body 需含 **`seachOption`**:`{ key, value }` 时对 `key` 字段 `LIKE`;分页 **`pageOption`** 经 `getPageSize()` 解析;排序 `id DESC`。 | | `/sys_control_type/add` | POST | admin | 新增。 | | `/sys_control_type/edit` | POST | admin | `id` + 更新字段。 | | `/sys_control_type/del` | POST | admin | 软删除:`id`。 | **`POST /sys_control_type/page` 请求 body(完整示例):** ```json { "seachOption": { "key": "name", "value": "输入" }, "pageOption": { "page": 1, "pageSize": 20 } } ``` **`POST /sys_control_type/add` 请求 body(完整示例):** ```json { "name": "上传组件", "code": "upload", "desc": "文件上传控件" } ``` **`POST /sys_control_type/edit` 请求 body(完整示例):** ```json { "id": 5, "name": "上传组件(单文件)", "desc": "限制单文件上传" } ``` **`POST /sys_control_type/del` 请求 body(完整示例):** ```json { "id": 5 } ``` **响应示例(成功):** ```json { "/sys_control_type/all": { "code": 0, "message": "请求成功", "data": [{ "id": 5, "name": "上传组件" }] }, "/sys_control_type/page": { "code": 0, "message": "请求成功", "data": { "count": 1, "rows": [{ "id": 5, "name": "上传组件" }] } }, "/sys_control_type/add": { "code": 0, "message": "请求成功", "data": { "id": 5, "name": "上传组件" } }, "/sys_control_type/edit": { "code": 0, "message": "请求成功", "data": [1] }, "/sys_control_type/del": { "code": 0, "message": "请求成功", "data": [1] } } ``` --- ### 4.7 租户模块 `sys_tenant` 源码:`controller/admin/sys_tenant.js`。 | 路径 | 方法 | 认证 | 说明 | |------|------|------|------| | `/sys_tenant/index` | GET | admin | 平台租户看全部租户;普通租户仅看自身。 | | `/sys_tenant/add` | POST | admin | 仅平台租户可新增。`code` 可不传,不传时自动生成。 | | `/sys_tenant/edit` | POST | admin | 仅平台租户可编辑。 | | `/sys_tenant/del` | POST | admin | 仅平台租户可删除,且禁止删除默认租户(`id=1`)。 | **`POST /sys_tenant/add` 请求 body(完整示例):** ```json { "name": "华东租户A", "code": "tenant_east_a", "remark": "华东区域项目", "status": 1 } ``` **`POST /sys_tenant/edit` 请求 body(完整示例):** ```json { "id": 2, "name": "华东租户A-正式", "code": "tenant_east_a_prod", "remark": "正式环境", "status": 1 } ``` **`POST /sys_tenant/del` 请求 body(完整示例):** ```json { "id": 2 } ``` **响应示例(成功):** ```json { "/sys_tenant/index": { "code": 0, "message": "请求成功", "data": [{ "id": 1, "name": "默认租户", "code": "default", "status": 1 }] }, "/sys_tenant/add": { "code": 0, "message": "请求成功", "data": { "id": 2, "name": "华东租户A", "code": "tenant_east_a" } }, "/sys_tenant/edit": { "code": 0, "message": "请求成功", "data": 1 }, "/sys_tenant/del": { "code": 0, "message": "请求成功", "data": [1] } } ``` --- ## 5. 自定义业务控制器 ### 5.1 路由约定 - 目录内 `*.js` 被加载;导出对象为 **`"GET /path"` / `"POST /path"`** → 处理器 **`async (ctx, next) => {}`**。 - 完整路径 = `prefix + 路径`(`router/baseController.js`)。 - 在处理器中通过 **`Framework.getModels()`**(需在 `init` 完成 DB 后)访问模型;通过 **`Framework.getServices()`** 访问内置服务。 ### 5.2 与内置 `/admin_api` 的关系 系统管理控制器由核心包**内置注册**,不依赖业务 `apiPaths`。业务可再配置其它 `prefix`(如 `/app_api`)挂载自己的控制器。 ### 5.3 控制器文件示例 ```js const Framework = require('@project/core'); // 或实际包名 / 相对路径 module.exports = { 'GET /goods/list': async (ctx) => { const models = Framework.getModels(); const rows = await models.goods.findAll({ where: { is_delete: 0 } }); return ctx.success(rows); }, 'POST /goods/save': async (ctx) => { const row = ctx.getBody(); const id = ctx.get('id'); return ctx.success({ id }); } }; ``` ### 5.4 手动添加路由(`addRoutes`) 除了通过 `apiPaths` 配置控制器目录自动扫描之外,还可以在 `beforeInitApi` 回调中调用 `framework.addRoutes(prefix, routes)` **手动注册路由**。 手动注册的路由与控制器目录扫描的路由**统一注册到 `koa-router`**,支持路径参数(`:id`),并会出现在启动时的路由列表打印中。 **方法签名:** ```js framework.addRoutes(prefix, routes) ``` | 参数 | 类型 | 说明 | |------|------|------| | `prefix` | `string` | 路由前缀,如 `'/api'`;不需要前缀时传 `''` | | `routes` | `object` | 路由定义对象,键名格式 `"GET /path"` 或 `"POST /path"`,值为 `async (ctx) => {}` 处理函数 | **使用示例:** ```js const framework = await Framework.init({ // ...其他配置 beforeInitApi: async (framework) => { // 手动添加带前缀的路由 framework.addRoutes('/api', { 'GET /custom/list': async (ctx) => { ctx.success([]); }, 'POST /custom/create': async (ctx) => { const { name } = ctx.request.body; ctx.success({ id: 1, name }); } }); // 不带前缀的路由 framework.addRoutes('', { 'GET /health': async (ctx) => { ctx.body = { status: 'ok', timestamp: new Date().toISOString() }; } }); } }); ``` 上述示例将注册以下路由: | 方法 | 路径 | |------|------| | GET | `/api/custom/list` | | POST | `/api/custom/create` | | GET | `/health` | > **注意:** `addRoutes` 必须在 `beforeInitApi` 回调中调用,不能在 `init()` 完成之后调用,否则路由不会被注册。 --- ## 6. Context 方法参考 由 `middleware/baseRequest.js` 注入(业务控制器与系统控制器均可使用): | 方法 | 说明 | |------|------| | `ctx.get(name)` | 合并 query + body 后取字段;缺失返回 `""`。 | | `ctx.getQuery()` | 仅 query 对象。 | | `ctx.getBody()` | body(与部分兼容字段合并)。 | | `ctx.getIp()` | 客户端 IP,支持 `x-forwarded-for` 第一段。 | | `ctx.getAdminUserId()` | 解析 `admin-token`,返回用户 id 或 `0`。 | | `ctx.getPappletUserId()` | 解析 `applet-token` 或调试 query `userIdTest45`。 | | `ctx.getPageSize()` | 从 `pageOption` 解析 `limit`、`offset`(默认 `page=1`,`pageSize=20`)。 | | `ctx.getOrder(key)` | 默认从 `order` 字段解析排序数组(支持字符串 eval 解析,异常则 `[]`)。 | | `ctx.downFile({ title, rows, cols })` | 导出 CSV,UTF-8 BOM,`cols[].caption` 为表头。 | | `ctx.json(code, message, data)` | 原始 JSON 输出。 | | `ctx.success(data, msg)` | `code: 0`。 | | `ctx.fail(msg)` | `code: -1`;若存在 `ctx.errorCallback` 会调用。 | | `ctx.tokenFail(data)` | `code: -2`,默认提示「非法请求,或登录已超时」。 | --- ## 7. 框架 Node 模块 API 入口:`index.js` → 导出 `framework.js` 的聚合对象。 ### 7.1 顶层导出 | 导出 | 说明 | |------|------| | `init(config)` | **异步**:校验配置 → `_validatePackages` → `initDb` → `initServices` → `initApi`;返回 **Framework 实例**。 | | `getInstance()` | 当前全局 Framework 实例(`init` 之后)。 | | `getModels()` | 等价于实例 `getModels()`;未初始化则为 `null`。 | | `getSequelize()` | Sequelize 实例。 | | `getDB()` | 与实例 `getDB()` 一致(见下)。 | | `getServices()` | `initServices` 返回的对象(含 `tokenService`、`logsService` 等)。 | | `getPlatformProjectService()` | `platformProjectService` 快捷方式。 | | `Framework` | 类,可 `new Framework(config)`(一般业务用 `init`)。 | ### 7.2 Framework 实例方法 | 方法 | 说明 | |------|------| | `start(port)` | **异步**:监听端口;**必须先** `initApi` 完成(即通过 `init()` 一次性跑完)。 | | `addRoutes(prefix, routes)` | 手动注册路由(须在 `beforeInitApi` 中调用),详见 [§5.4](#54-手动添加路由addroutes)。 | | `use(middleware)` | 向 Koa `app` 追加中间件。 | | `getApp()` | Koa 应用实例。 | | `getModels()` | 合并后的模型对象(系统表 + 业务表)。 | | `getSequelize()` | Sequelize 实例。 | | `getDB()` | 当前 `framework.js` 实现返回 `this.db`,但构造函数未给 `this.db` 赋值,**可能为 `undefined`**;请优先使用 **`getSequelize()`** 或 **`getModels()`**,或在业务层封装修复后再用 `getDB()`。 | | `getServices()` | 见 §8。 | | `getPlatformProjectService()` | 平台项目服务实例。 | | `query(sql)` | 执行原始 SQL(`modelManager.core.querySql`)。 | | `syncDatabase()` | 同步数据库结构(慎用生产环境)。 | **推荐启动顺序(与 `examples/index.js` 一致):** ```js const core = require('@project/core'); // 包名以实际为准 const framework = await core.init({ db: { /* host, username, password, database, dialect */ // operateLogExcludeTableNames: ['cache_row', 'user_session'], // 可选:这些 tableName 不写 sys_log 操作记录 }, baseUrl: 'http://localhost:3000', apiPaths: [{ path: require('path').join(__dirname, 'controller'), prefix: '/api', authType: 'admin' }], allowUrls: ['/health'], licensePath: '...', modelPaths: '...', beforeInitApi: async (fw) => { /* 在路由前挂中间件 */ } }); await framework.start(3000); ``` --- ## 8. 内置服务(`getServices()`) `initServices()` 完成后,`getServices()` 返回对象包含(`services/index.js`): | 键 | 类型 | 说明 | |----|------|------| | `tokenService` | `TokenService` | JWT 签发/解析、`getMd5`、`decryptWxData` 等。 | | `logsService` | `LogsService` | 文件日志目录、错误记录等。 | | `redisService` | `RedisService` | 未配置 `redis` 时可能未完整初始化。 | | `platformProjectService` | `PlatformProjectService` | 低代码平台 HTTP 调用、文件下载。 | | `createSwaggerService` | `function` | `createSwaggerService(models)` → Swagger 文档服务实例。 | **TokenService(`services/token.js`)常用方法:** | 方法 | 说明 | |------|------| | `getMd5(str)` | MD5 十六进制字符串。 | | `create(userInfo)` | `jwt.sign`,载荷写入 token。 | | `parse(token)` | `jwt.verify`,失败返回 `null`。 | | `verify(token)` | 布尔是否有效。 | ### 8.1 LogsService 用法(`services/logs.js`) | 方法 | 说明 | 示例 | |------|------|------| | `log(msg, data?)` | 记录普通日志 | `logsService.log('用户登录', { id: 1 })` | | `error(msg, err?)` | 记录错误日志 | `logsService.error('下单失败', error)` | | `ctxError(error, ctx)` | 记录请求上下文错误 | `logsService.ctxError(error, ctx)` | | `writeSlowSQL(sql, timing)` | 记录慢 SQL | `logsService.writeSlowSQL(sql, 850)` | 日志文件说明: - 普通日志:`log_YYYY.MM.DD.log` - 错误日志:`logError_YYYY.MM.DD.log` - 慢 SQL:`slowSQL_YYYY.MM.DD.log` - 单文件超过 5MB 自动滚动为 `_1.log`、`_2.log`... ### 8.2 RedisService 用法(`services/redis.js`) | 方法 | 返回 | 说明 | |------|------|------| | `await init()` | `boolean` | 初始化并连接 Redis | | `await set(key, value, expire?)` | `void` | 设置缓存(默认 12 小时) | | `await get(key)` | `string \| null` | 读取缓存 | | `await del(key)` | `number` | 删除缓存 | | `await setIfAbsent(key, value, expire?)` | `boolean` | 原子写入(NX + EX) | | `await waitForConnection(timeout?)` | `boolean` | 等待连接就绪 | | `close()` | `void` | 关闭连接 | 示例: ```js const { redisService } = Framework.getServices(); await redisService.set('order_lock_1001', '1', 30); const locked = await redisService.get('order_lock_1001'); if (locked) { // ... 业务逻辑 } await redisService.del('order_lock_1001'); ``` ### 8.3 PlatformProjectService 用法(`services/platformProject.js`) | 方法 | 说明 | |------|------| | `modelAll()` | 获取平台模型列表 | | `createForm(row)` / `updateForm(row)` | 创建/更新表单 | | `formGenerate({ id })` | 生成前端资源地址 | | `modelGenerate({ id })` | 生成后端资源地址 | | `downloadPlatformFile(url)` | 下载平台文件到本地项目 | 示例: ```js const { platformProjectService } = Framework.getServices(); const models = await platformProjectService.modelAll(); const form = await platformProjectService.createForm({ name: '订单管理', model_id: 5, api_path: '/api/order', component: 'views/order/index.vue', }); const { frontApiUrl, frontVueUrl } = await platformProjectService.formGenerate({ id: form.id }); await platformProjectService.downloadPlatformFile(frontApiUrl); await platformProjectService.downloadPlatformFile(frontVueUrl); ``` ### 8.4 Swagger 服务用法(`createSwaggerService`) `getServices().createSwaggerService(models)` 可手动创建 SwaggerService(通常框架自动创建即可): ```js const services = Framework.getServices(); const models = Framework.getModels(); const swaggerService = services.createSwaggerService(models); const specs = swaggerService.generateSpecs(); ``` ### 8.5 服务层注意事项 - `ServiceManager.getServices()` 在未完成初始化时会触发异步初始化,业务不要在 `init()` 前调用。 - Redis 配置存在但连接失败时: - `servicesRequired !== false`:启动失败; - `servicesRequired === false`:仅告警并继续启动。 - `TokenService.secret` 目前是代码内常量,生产建议在你们业务层封装替换(环境变量注入)。 --- ## 9. 业务项目集成与配置 ### 9.1 必填与常用配置 | 配置项 | 说明 | |--------|------| | `db` | **必填**:`host`、`username`、`password`、`database`、`dialect` 等;可选扩展字段见 [§9.1.1](#911-db-可选字段扩展)。 | | `apiPaths` | **必填**:非空数组;每项为字符串或 `{ path, prefix?, authType? }`。 | | `baseUrl` | 强烈建议:Swagger 等依赖。 | | `allowUrls` | 白名单子串数组;未配置时非白名单路径均需登录(见中间件逻辑)。 | | `logPath` | 日志服务目录。 | | `redis` | 可选;不配则跳过 Redis 初始化(`initServices` 内不抛错则继续)。 | | `licensePath` | 授权文件路径;`initDb` 时会做离线授权校验。 | | `modelPaths` | 业务模型目录。 | | `businessAssociations` | `(models) => void`,业务模型关联。 | | `beforeInitApi` | `(framework) => Promise`,在路由注册前执行(适合挂 `/health`、额外中间件)。 | | `customSchemas` | 传入 Swagger 服务,扩展 Schema。 | | `project_key` | 低代码平台项目标识。 | | `skipPackageValidation` | `true` 跳过依赖版本校验。 | | `strictPackageValidation` | `true` 校验失败则中止启动。 | | `servicesRequired` | 默认 `true`:`initServices` 失败会中止;`false` 时仅警告。 | | `env` | 如 `development` / `production`,影响部分日志与路由打印行为。 | #### 9.1.1 db 可选字段扩展 除连接与 Sequelize 常用项外,框架会读取: | 字段 | 类型 | 说明 | |------|------|------| | `slowSQLThreshold` | `number` | 慢 SQL 阈值(毫秒),默认 `300`;超过则写入文件日志。 | | `logging` | `boolean` \| `function` | SQL 控制台输出;为函数时可自定义并仍参与慢 SQL 检测。 | | `pool` | `object` | 连接池,会与内置默认合并。 | | `operateLogExcludeTableNames` | `string[]` | **操作日志排除表**:元素为模型的 **`tableName`**(与 `db.define` 时一致,默认常为模型名)。命中则**不**向 `sys_log` 写入新增/修改/删除操作记录;`sys_log` 本身始终排除。更新钩子仍会维护 `last_modify_time`。 | 示例: ```js db: { host: '127.0.0.1', username: 'root', password: '***', database: 'app', dialect: 'mysql', operateLogExcludeTableNames: ['user_session', 'api_rate_limit'], }, ``` ### 9.2 静态与上传 `framework.js` 中 `koa-static` 指向 **`path.resolve(__dirname, '../upload')`**(相对框架包内 `framework.js` 所在目录),用于静态资源;上传目录以部署为准。 ### 9.3 前端调用流程简述 1. `POST /admin_api/sys_user/login` 取 `data.token`。 2. 后续请求头:`admin-token: `。 3. 小程序接口:配置 `authType: 'applet'`,使用 `applet-token`。 ### 9.4 配置模板(开发 / 生产) **开发环境模板:** ```js { env: 'development', db: { host: '127.0.0.1', port: 3306, username: 'root', password: '***', database: 'demo_dev', dialect: 'mysql', logging: true, slowSQLThreshold: 300, }, redis: { host: '127.0.0.1', port: 6379, pwd: '' }, baseUrl: 'http://127.0.0.1:3000', apiPaths: [{ path: './controller', prefix: '/api', authType: 'admin' }], allowUrls: ['/health'], servicesRequired: true, } ``` **生产环境模板:** ```js { env: 'production', db: { host: process.env.DB_HOST, port: Number(process.env.DB_PORT || 3306), username: process.env.DB_USER, password: process.env.DB_PWD, database: process.env.DB_NAME, dialect: 'mysql', logging: false, slowSQLThreshold: 500, operateLogExcludeTableNames: ['sys_log', 'sys_session'], }, redis: { host: process.env.REDIS_HOST, port: Number(process.env.REDIS_PORT || 6379), pwd: process.env.REDIS_PWD, }, baseUrl: process.env.BASE_URL, apiPaths: [{ path: './controller', prefix: '/api', authType: 'admin' }], allowUrls: ['/health', '/api/public'], servicesRequired: true, strictPackageValidation: true, } ``` ### 9.5 `apiPaths` 组合示例(多前缀) ```js apiPaths: [ { path: path.join(__dirname, 'controller/admin'), prefix: '/admin_api', authType: 'admin' }, { path: path.join(__dirname, 'controller/applet'), prefix: '/api', authType: 'applet' }, ] ``` ### 9.6 业务注入最小模板(模型 + 关联 + 路由) ```js { modelPaths: path.join(__dirname, 'models'), businessAssociations: (models) => { if (models.order && models.user) { models.order.belongsTo(models.user, { foreignKey: 'user_id', as: 'creator' }); } }, beforeInitApi: async (fw) => { fw.addRoutes('/api', { 'GET /health': async (ctx) => ctx.success({ ok: true }), }); }, } ``` --- ## 10. 内部方法与外部注入详解 本节聚焦两个问题: 1. 框架内部方法是按什么顺序调用、各自负责什么。 2. 业务项目可以从哪些入口注入自定义逻辑(模型、关联、路由、中间件、Swagger 扩展等)。 ### 10.1 `init(config)` 内部执行顺序 `index.js` 导出 `framework.init(config)`,其内部固定按以下顺序执行: 1. `new Framework(config)`:校验配置、构造 Koa、初始化 `ServiceManager` 与 `RequestManager`。 2. `_validatePackages()`:依赖版本检查(可被 `skipPackageValidation` 跳过)。 3. `initDb(modelPaths, businessAssociations)`:校验授权、初始化 DB、加载系统模型、加载业务模型、合并模型并执行关联注入。 4. `initServices()`:初始化 `logs/token/redis/platformProject`。 5. `initApi(beforeInitApi)`:安装中间件,执行外部注入回调,注册 Swagger 与路由。 最小可运行示例: ```js const path = require('path'); const core = require('@project/core'); async function bootstrap() { const framework = await core.init({ db: { host: '127.0.0.1', port: 3306, username: 'root', password: '***', database: 'demo', dialect: 'mysql', }, baseUrl: 'http://127.0.0.1:3000', apiPaths: [{ path: path.join(__dirname, 'controller'), prefix: '/api', authType: 'admin' }], modelPaths: path.join(__dirname, 'models'), allowUrls: ['/health'], }); await framework.start(3000); } ``` ### 10.2 Framework 实例核心方法(内部职责 + 推荐用法) | 方法 | 内部职责 | 业务侧何时用 | |------|----------|--------------| | `start(port)` | `app.listen(port)` 启动 HTTP 服务 | `init` 成功后调用一次 | | `addRoutes(prefix, routes)` | 缓存手动路由,最终在 `_setupRoutes` 注册到 `koa-router` | 仅在 `beforeInitApi` 里动态加路由 | | `use(middleware)` | 对 Koa `app` 执行 `app.use` | 优先在 `beforeInitApi` 中注入全局中间件 | | `getApp()` | 返回 Koa 实例 | 需要访问底层 Koa 能力时 | | `getModels()` | 返回合并后的模型对象(系统 + 业务) | 控制器 / Service 查询数据库 | | `getServices()` | 返回 `ServiceFactory.initServices()` 产物 | 获取 `tokenService`、`logsService` 等 | | `getPlatformProjectService()` | 返回低代码平台服务 | 需要调用平台模型/表单生成能力 | | `query(sql)` | 原生 SQL 查询(通过 ModelManager) | 临时查询或复杂 SQL | | `syncDatabase()` | 触发结构同步 | 仅开发期使用,生产慎用 | > `getDB()`、`getSequelize()` 当前源码存在不一致实现,业务建议优先使用 `getModels()` 与 `getServices()`,必要时自行封装。 ### 10.3 外部注入点总览(最重要) | 注入点 | 配置项 / 方法 | 触发时机 | 适合注入什么 | |--------|----------------|----------|--------------| | 业务模型目录 | `config.modelPaths` | `initDb` 中 `_loadBusinessModels` | 业务 Sequelize 模型 | | 业务模型关联 | `config.businessAssociations(models)` | 系统模型与业务模型合并后 | `belongsTo/hasMany/belongsToMany` | | API 前置注入 | `config.beforeInitApi(framework)` | `_setupMiddleware` 后、`_setupRoutes` 前 | 手动路由、全局中间件、桥接逻辑 | | 手动路由 | `framework.addRoutes(prefix, routes)` | `beforeInitApi` 内调用 | 动态生成路由、代理路由 | | 中间件 | `framework.use(middleware)` 或 `framework.app.use` | 推荐在 `beforeInitApi` | 请求日志、租户注入、灰度开关 | | Swagger 扩展 | `config.customSchemas` | `_setupSwagger` 时 | 业务模型额外 schema | ### 10.4 业务模型注入:`modelPaths` 模型文件建议导出工厂函数,框架会调用 `model(db)`: ```js // models/order.js const Sequelize = require('sequelize'); module.exports = (db) => { return db.define('order', { title: { type: Sequelize.STRING(100), allowNull: false, defaultValue: '' }, amount: { type: Sequelize.DECIMAL(10, 2), allowNull: false, defaultValue: 0 }, }); }; ``` 配置: ```js modelPaths: path.join(__dirname, 'models') ``` ### 10.5 关联注入:`businessAssociations(models)` 框架会在模型合并后调用 `businessAssociations`,这里写跨模型关联最稳妥: ```js businessAssociations: (models) => { const { order, user } = models; if (order && user) { order.belongsTo(user, { foreignKey: 'user_id', targetKey: 'id', as: 'creator' }); user.hasMany(order, { foreignKey: 'user_id', sourceKey: 'id', as: 'orders' }); } } ``` ### 10.6 路由注入:`beforeInitApi` + `addRoutes` `beforeInitApi` 是最关键的扩展点,执行时机早于路由扫描注册,可同时注入中间件与手动路由: ```js beforeInitApi: async (framework) => { // 1) 注入中间件 framework.use(async (ctx, next) => { ctx.set('x-request-source', 'node-core'); await next(); }); // 2) 注入手动路由 framework.addRoutes('/api', { 'GET /health': async (ctx) => ctx.success({ ok: true }), 'POST /tenant/switch': async (ctx) => { const tenantId = ctx.get('tenant_id'); return ctx.success({ tenant_id: tenantId }); }, }); } ``` ### 10.7 Swagger 扩展注入:`customSchemas` 业务侧可把自定义 schema 注入 Swagger 生成器: ```js customSchemas: { OrderCreateBody: { type: 'object', properties: { title: { type: 'string' }, amount: { type: 'number' }, }, required: ['title', 'amount'], }, } ``` 注入后可在业务 OpenAPI 注释或生成逻辑中引用该 schema。 ### 10.8 服务注入关系(内部) `ServiceFactory.initServices()` 内部依赖顺序如下: 1. `logsService` 2. `tokenService` 3. `redisService`(有配置才连接) 4. `platformProjectService` 5. `tokenService.setLogsService(logsService)`(日志依赖回注) 业务侧通常只需: ```js const { tokenService, redisService, logsService } = Framework.getServices(); ``` ### 10.9 内部模块分层说明(源码导读) 这一节按「启动层 -> 数据层 -> 请求层 -> 服务层」说明内部模块,方便你二开时快速定位。 #### 10.9.1 启动编排层:`framework.js` **定位**:框架总调度器,负责把 DB、Service、API 串起来。 **关键职责:** - 配置校验:`_validateConfig(config)` 检查 `db`、`apiPaths` 等必填项。 - 启动顺序:`init -> initDb -> initServices -> initApi -> start`。 - 系统控制器注册:`_registerSystemControllers()` 固定挂载在 `/admin_api`。 - 手动路由缓存:`addRoutes()` 先存入 `customRoutes`,最后在 `_setupRoutes()` 统一注册。 **关键扩展点:** - `beforeInitApi(framework)`:最推荐的业务注入位置。 - `addRoutes(prefix, routes)`:动态路由注入。 - `use(middleware)`:追加 Koa 中间件。 #### 10.9.2 数据模型层:`lib/modelManager.js` + `database/db.js` **`ModelManager`(模型编排):** - `initSysModels()`:初始化系统模型(`sys_user/sys_role/...`)。 - `_loadBusinessModels`(在 `framework.js` 内调用):按 `modelPaths` 加载业务模型。 - `mergeModels()`:合并系统模型与业务模型,顺序为: - `allModels = { ...sysModels, ...businessModels }` - **同名时业务模型覆盖系统模型**(优先级更高)。 - `setupBusinessModelAssociations(models => {})`:注入业务关联关系。 **`DatabaseManager`(Sequelize 封装):** - `initDatabase(config)`:连接池、SQL 日志、慢 SQL 记录、`operateLogExcludeTableNames` 解析。 - `define(name, attrs)`:统一追加 `create_time/last_modify_time/is_delete` 字段。 - 钩子日志:`afterCreate/beforeUpdate/afterDestroy` 自动写 `sys_log`(可按表名排除)。 #### 10.9.3 请求路由层:`lib/requestManager.js` + `middleware/baseRequest.js` + `router/baseController.js` **`RequestManager`:** - `getBaseRequest()`:构建基础中间件(鉴权 + Context 扩展)。 - `getBaseController()`:构建/复用路由加载器。 **`baseRequest`(全局中间件)做了三件事:** 1. 注入 `ctx.get/ctx.getBody/ctx.success/ctx.fail/...` 等便捷 API。 2. 执行白名单 + token 鉴权(`admin-token` / `applet-token`)。 3. 统一异常捕获并写错误日志。 **`BaseController`(控制器解析):** - 扫描 `apiPaths[].path` 下的控制器文件。 - 仅识别导出键格式:`GET /path`、`POST /path`。 - 合并前缀后注册到 `koa-router`,并打印路由清单。 #### 10.9.4 服务层:`lib/serviceManager.js` + `services/*` **初始化入口**:`ServiceFactory.initServices()`,顺序固定: 1. `LogsService` 2. `TokenService` 3. `RedisService`(配置了 `redis` 才连接) 4. `PlatformProjectService` 5. `tokenService.setLogsService(logsService)` **核心服务职责:** - `services/token.js` - `getMd5(str)`、`create(payload)`、`parse(token)`、`verify(token)`。 - `services/logs.js` - 统一日志写入,按类型分文件(普通/错误/慢 SQL),支持文件超过 5MB 自动滚动后缀。 - `services/redis.js` - 封装 `set/get/del/setIfAbsent`,带重连和连接状态管理。 - `services/platformProject.js` - 对接低代码平台:`modelAll/createForm/updateForm/formGenerate/modelGenerate`。 - `services/swagger.js` + `lib/swaggerUIManager.js` - 生成 OpenAPI 文档、挂载 `/api/docs` `/api/swagger.json`,并支持安全配置 Cookie 持久化。 #### 10.9.5 模块调用关系(简图) ```text Framework.init -> ModelManager (db + models) -> ServiceManager (logs/token/redis/platform/swagger) -> RequestManager -> baseRequest (ctx扩展 + 鉴权) -> BaseController (控制器解析注册) -> Koa app.listen ``` #### 10.9.6 二开建议(内部模块维度) - 需要改鉴权策略:优先改 `middleware/baseRequest.js`。 - 需要改控制器扫描规则:改 `router/baseController.js` 的 `_parseController` / `loadControllers`。 - 需要改 DB 公共字段与日志策略:改 `database/db.js` 的 `define` 与 hooks。 - 需要新增通用服务:放到 `services/`,并在 `services/index.js` + `lib/serviceManager.js` 暴露。 ### 10.10 建议的集成模板(可直接复制) ```js const path = require('path'); const Framework = require('@project/core'); async function start() { const framework = await Framework.init({ env: process.env.NODE_ENV || 'development', db: { host: '127.0.0.1', port: 3306, username: 'root', password: '***', database: 'demo', dialect: 'mysql', operateLogExcludeTableNames: ['sys_log', 'user_session'], }, redis: { host: '127.0.0.1', port: 6379, pwd: '' }, baseUrl: 'http://127.0.0.1:3000', allowUrls: ['/health', '/api/public'], apiPaths: [{ path: path.join(__dirname, 'controller'), prefix: '/api', authType: 'admin' }], modelPaths: path.join(__dirname, 'models'), businessAssociations: (models) => { // 写业务模型关联 }, beforeInitApi: async (fw) => { // 写中间件与手动路由 }, }); await framework.start(3000); } start().catch((error) => { console.error('启动失败:', error); process.exit(1); }); ``` --- ## 11. 注意事项 1. **白名单**为子串匹配,自定义公开接口时避免与 `/login` 等敏感片段无意冲突。 2. **系统管理接口**路径前缀为 **`/admin_api`**;业务前缀在 `apiPaths` 中单独配置。 3. **`sys_user/edit`** 源码对 `password` 直接取长度,调用时请始终传入 `password`(或按你们分支修复后再依赖可选密码)。 4. **`sys_control_type/page`** 需传 `seachOption` 对象,否则可能运行时报错。 5. **生产环境**授权、依赖版本与数据库同步策略以部署与发行说明为准。 6. 更完整的 OpenAPI 可通过 **`GET /api/swagger.json`** 与 Swagger UI 查看(与手写路由并行存在时,以生成内容为准)。 如需扩展接口文档,可在业务侧增加 swagger-jsdoc 注释或维护独立 OpenAPI 文件,并按项目约定合并到 `customSchemas`。 --- ## 12. 常见问题排查(FAQ) ### 12.1 启动时报 “配置验证失败” 优先检查: - `db.host/username/password/database/dialect` 是否完整; - `apiPaths` 是否为非空数组,且对象项有 `path`; - `baseUrl` 建议填写,否则 Swagger 相关能力受影响。 ### 12.2 接口总是返回 `code: -2` 排查顺序: 1. 请求路径是否命中 `allowUrls` 或默认白名单; 2. 路径前缀是否与 `apiPaths[].prefix` 匹配; 3. `authType` 对应请求头是否正确: - `admin` -> `admin-token` - `applet` -> `applet-token` ### 12.3 `getModels()` 里模型找不到 - 确认 `modelPaths` 指向真实目录; - 模型文件是否导出工厂函数 `module.exports = (db) => db.define(...)`; - 文件后缀是否为 `.js`; - 初始化顺序必须是先 `init` 再在业务代码中调用 `Framework.getModels()`。 ### 12.4 Redis 连接失败导致启动中断 - 如果希望 Redis 故障不阻断服务,把 `servicesRequired` 设为 `false`; - 检查 Redis 地址、端口、密码、网络策略; - 查看 `logsService` 与控制台日志中的 `RedisService` 错误信息。 ### 12.5 Swagger 页面可打开但接口调试失败 - 检查 `baseUrl` 是否正确; - 检查 `/api/swagger.json` 是否可访问; - 在 `/api/docs` 里确认安全配置(`admin-token` / `applet-token`)是否已填写。 ### 12.6 业务模型与系统模型同名时用哪个? `mergeModels()` 顺序是 `{ ...sysModels, ...businessModels }`,所以**业务模型优先级更高**(会覆盖系统同名模型)。