# 后端 API 文档 微前端平台后端服务(Spring Boot 2.7 / Java 8 / SQLite)。本文档列出当前全部 HTTP 接口、鉴权要求与示例。 - Base URL:`http://:8080` - Content-Type:`application/json` - 统一错误响应格式: ```json { "code": "ERROR_CODE", "message": "错误描述" } ``` --- ## 鉴权说明 | 级别 | 说明 | |---|---| | 免鉴权 | 无需 Token,见各接口标注"免鉴权" | | 登录态 | 请求头携带 `Authorization: Bearer ` 即可 | | 角色 | 需 `@RequireRole`,如 `ADMIN` | | 权限点 | 需 `@RequirePermission`,如 `user:list` | 角色/权限随 JWT 下发,**修改角色或权限后需重新登录** 获取新 Token 生效。 默认管理员:`admin / admin123`(由 `sql/init.sql` 一次性灌入)。 --- ## 1. 基础 ### GET /api/hello 免鉴权。连通性验证。 **响应 200** ```json { "message": "Hello from backend", "serverTime": "2026-09-22T14:00:00" } ``` --- ## 2. 数据源 ### GET /api/datasource/info 免鉴权。当前数据源信息与健康检查。 **响应 200** ```json { "type": "sqlite", "ping": true, "serverNow": "2026-09-22 14:00:00" } ``` ### GET /api/datasource/users 登录态。查询演示用户列表(表 `demo_user`)。 **响应 200** ```json [ { "id": 1, "name": "Alice", "created_at": "2026-09-22 14:00:00" } ] ``` ### POST /api/datasource/users 登录态。新增演示用户。 **请求体** ```json { "name": "Alice" } ``` **响应 200** ```json { "affected": 1, "name": "Alice" } ``` ### POST /api/datasource/users/batch 登录态。批量新增演示用户。 **查询参数**:`prefix`(String,前缀)、`count`(int,数量) **响应 200** ```json { "affected": 100 } ``` --- ## 3. 认证 Auth ### POST /api/auth/register 免鉴权。注册,默认分配 `USER` 角色。 **请求体** ```json { "username": "alice", "password": "123456", "nickname": "爱丽丝" } ``` **响应 200**(失败:`USER_EXISTS` 用户名已存在 / `BAD_REQUEST` 参数不合法) ```json { "token": "eyJhbGciOiJIUzI1NiJ9...", "tokenType": "Bearer", "expiresIn": 7200, "id": 2, "username": "alice", "nickname": "爱丽丝", "roles": ["USER"], "permissions": [] } ``` ### POST /api/auth/login 免鉴权。登录。 **请求体** ```json { "username": "admin", "password": "admin123" } ``` **响应 200**(失败:`BAD_CREDENTIALS` 账号或密码错误 / `USER_DISABLED` 账号禁用) ```json { "token": "eyJhbGciOiJIUzI1NiJ9...", "tokenType": "Bearer", "expiresIn": 7200, "id": 1, "username": "admin", "nickname": "管理员", "roles": ["ADMIN"], "permissions": ["user:list", "user:create", "role:assign", "..."] } ``` ### GET /api/auth/me 登录态(`@RequireAuth`)。返回当前登录用户信息。 **响应 200**(未登录:401 `{"code":"UNAUTHORIZED","message":"请先登录"}`) ```json { "id": 1, "username": "admin", "nickname": "管理员", "roles": ["ADMIN"], "permissions": ["..."] } ``` --- ## 4. RBAC 用户权限管理 > 以下接口均需 `ADMIN` 角色;权限收回时返回 403 `{"code":"FORBIDDEN","message":"无角色权限"}` 或 `无操作权限`。 ### 4.1 角色 #### GET /api/rbac/roles 权限点:`role:list`。角色列表(含用户数)。 **响应 200** ```json [ { "id": 1, "code": "ADMIN", "name": "超级管理员", "description": null, "created_at": "...", "user_count": 1 } ] ``` #### POST /api/rbac/roles 权限点:`role:create`。创建角色。 **请求体** ```json { "code": "EDITOR", "name": "编辑", "description": "内容编辑" } ``` **响应 200**(`ROLE_EXISTS`:编码已存在) #### PUT /api/rbac/roles/{id} 权限点:`role:update`。更新角色名称/描述。 **请求体** ```json { "name": "资深编辑", "description": "..." } ``` **响应 200**(`NOT_FOUND`:角色不存在) #### DELETE /api/rbac/roles/{id} 权限点:`role:delete`。删除角色(级联清理关联关系)。 **响应 200** `{ "deleted": 3 }` #### GET /api/rbac/roles/{id}/permissions 权限点:`role:assign`。查询角色拥有的权限列表。 **响应 200** ```json { "role": { "id": 1, "code": "ADMIN", "name": "超级管理员" }, "permissions": [ { "id": 1, "code": "user:list", "name": "user:list" } ] } ``` #### POST /api/rbac/roles/{id}/permissions 权限点:`role:assign`。**覆盖式**设置角色拥有的权限。 **请求体** ```json { "ids": [1, 2, 3] } ``` **响应 200**:同 GET /api/rbac/roles/{id}/permissions ### 4.2 权限 #### GET /api/rbac/permissions 权限点:`permission:list`。权限点列表。 **响应 200** ```json [ { "id": 1, "code": "user:list", "name": "user:list", "description": null, "created_at": "..." } ] ``` #### POST /api/rbac/permissions 权限点:`permission:create`。新增权限点。 **请求体** ```json { "code": "report:export", "name": "导出报表", "description": "..." } ``` **响应 200**(`PERMISSION_EXISTS`:编码已存在) #### DELETE /api/rbac/permissions/{id} 权限点:`permission:delete`。删除权限点(级联清理关联关系)。 **响应 200** `{ "deleted": 5 }` ### 4.3 用户授权 #### POST /api/rbac/users/{id}/roles 权限点:`role:assign`。**覆盖式**设置用户拥有的角色。 **请求体** ```json { "ids": [1, 2] } ``` **响应 200**:同 GET /api/rbac/users/{id}/auth #### GET /api/rbac/users/{id}/auth 权限点:`role:assign`。查询用户的角色与权限。 **响应 200** ```json { "userId": 2, "roles": [ { "id": 2, "code": "USER", "name": "普通用户" } ], "permissions": [ { "id": 6, "code": "data:query", "name": "data:query" } ] } ``` ### 4.4 当前用户 #### GET /api/rbac/me 登录态。当前登录用户的角色与权限(同 /api/auth/me 的 roles/permissions,从库中实时查)。 **响应 200** ```json { "userId": 1, "roles": [ { "id": 1, "code": "ADMIN", "name": "超级管理员" } ], "permissions": [ "..." ] } ``` ### 4.5 用户管理 User > 以下接口均需 `ADMIN` 角色;`@RequirePermission` 权限点由 `sql/init.sql` 预置(`user:list` / `user:update` / `user:delete`)。 > 安全约束: > - 内置超级管理员(`id=1`)不可删除、不可停用; > - 不能删除 / 停用当前登录用户自身; > - 密码以 BCrypt 哈希存储,长度 ≥ 6 位; > - `username` 不可通过本组接口修改。 #### GET /api/users 权限点:`user:list`。用户列表(不含密码字段,含用户的角色信息)。 **响应 200** ```json [ { "id": 1, "username": "admin", "nickname": "管理员", "enabled": 1, "created_at": "2026-09-22 14:00:00", "roles": [ { "user_id": 1, "role_id": 1, "code": "ADMIN", "name": "超级管理员" } ], "roleCodes": [ "ADMIN" ], "roleNames": [ "超级管理员" ] } ] ``` #### PUT /api/users/{id} 权限点:`user:update`。更新用户;字段留空或 `null` 表示不修改该字段。 **请求体**(三个字段均可省略) ```json { "nickname": "新昵称", "password": "newPwd123", "enabled": true } ``` **响应 200**(失败码:`NOT_FOUND` 用户不存在 / `BAD_REQUEST` 密码 < 6 位 / `FORBIDDEN` 禁用 ADMIN 或自身) ```json { "id": 2, "username": "alice", "nickname": "新昵称", "enabled": 1, "created_at": "..." } ``` #### DELETE /api/users/{id} 权限点:`user:delete`。删除用户(级联清理 `sys_user_role`)。 **响应 200**(失败:`FORBIDDEN` 不能删除 ADMIN 或自身 / `NOT_FOUND` 用户不存在) ```json { "deleted": 2 } ``` #### PUT /api/users/{id}/enabled 权限点:`user:update`。启停用户。 **请求体** ```json { "enabled": false } ``` **响应 200**(失败:`FORBIDDEN` 不能停用 ADMIN 或自身 / `NOT_FOUND` 用户不存在) ```json { "id": 2, "username": "alice", "nickname": "爱丽丝", "enabled": 0, "created_at": "..." } ``` --- ## 5. 菜单权限 Menu > 独立菜单权限体系,**与接口权限(sys_permission)完全解耦**:菜单可见性只由“用户的角色 → 角色被授予的菜单”决定(`sys_role_menu`),不依赖任何权限点编码。 > > 因此管理端接口仅校验 `ADMIN` 角色(**不使用** `@RequirePermission`);`/current` 实时查库返回,**修改菜单授权后无需重新登录**即可生效(这与角色/权限需重新登录的机制不同)。 > > 用户可见菜单树 = 其所有角色被授予菜单的并集,下发时自动**向上补全父级**、**剔除空目录**。 ### GET /api/menu/current 登录态(`@RequireAuth`)。当前用户可见菜单树。 **响应 200**(未登录:401) ```json [ { "id": 1, "parent_id": null, "name": "工作台", "type": "menu", "route": "/dashboard", "component": "dashboard/index", "icon": "HomeFilled", "sort": 1, "visible": 1, "status": 1, "created_at": "...", "children": [] }, { "id": 2, "parent_id": null, "name": "系统管理", "type": "dir", "route": null, "component": null, "icon": "Setting", "sort": 1, "visible": 1, "status": 1, "created_at": "...", "children": [ { "id": 3, "parent_id": 2, "name": "用户管理", "type": "menu", "route": "/system/user", "component": "system/user/index", "icon": "User", "sort": 1, "visible": 1, "status": 1, "created_at": "...", "children": [] } ] } ] ``` ### GET /api/menu/tree 角色:`ADMIN`。全量菜单树(管理端,含停用/隐藏项)。 **响应 200**:同上树形结构,返回全部节点。 ### POST /api/menu 角色:`ADMIN`。新建菜单(**自动授权给 ADMIN 角色**)。 **请求体** ```json { "parentId": null, "name": "报表中心", "type": "menu", "route": "/report", "component": "report/index", "icon": "DataAnalysis", "sort": 3, "visible": 1, "status": 1 } ``` **响应 200**:新建后的菜单记录(`NOT_FOUND` 不存在 / `PARAM` 名称缺失)。 ### PUT /api/menu/{id} 角色:`ADMIN`。更新菜单(缺失字段沿用原值)。 **请求体**:同 POST(可只传需改字段)。 **响应 200**:更新后的菜单记录(`NOT_FOUND`:菜单不存在)。 ### DELETE /api/menu/{id} 角色:`ADMIN`。删除菜单,**级联删除其全部子菜单**并清理 `sys_role_menu` 关联。 **响应 200** `{ "deleted": 6 }` ### GET /api/menu/role/{roleId} 角色:`ADMIN`。查询某角色被授予的菜单 id 列表。 **响应 200** ```json [ 1, 3, 7 ] ``` ### POST /api/menu/role/{roleId} 角色:`ADMIN`。**覆盖式**设置某角色拥有的菜单。 **请求体** ```json { "ids": [1, 3, 7] } ``` **响应 200** ```json { "roleId": 3, "menuIds": [1, 3, 7] } ``` --- ## 6. 指标数据 Indicator > 表结构与种子数据见 `sql/init.sql`(DDL + 元数据 51 条)与 `sql/indicator_values_seed.sql`(时序值)。 > 一个指标对应一组 URL:基础 `/api/indicators/{id}` 拿元数据,加 `/values` 拿时序,加 `/latest` 拿最新观测点。 > 51 个指标的 `id` 由种子数据固定,可在 `sql/init.sql` 的 `INSERT OR IGNORE INTO indicator(...)` 中查到。 ### GET /api/indicators 登录态。指标元数据列表。 **查询参数**(均可选): - `category` 一级分类(`GDP` / `投资` / `出口` / `消费` / `通胀` / `供给` / `景气度`) - `subcategory` 二级分类 - `region` 区域(`中国` / `美国`) - `frequency` 频度(`日` / `周` / `月` / `季` / `年`) **响应 200** ```json [ { "id": 1, "category": "GDP", "subcategory": "现价GDP增速", "name": "中国:GDP:现价:当季同比", "region": "中国", "frequency": "季", "unit": "%", "wind_code": "X4608416", "source": "Wind", "data_start": "1993-03", "data_end": "2026-06", "refreshed_at": "2026-07-15", "remark": "同比" } ] ``` ### GET /api/indicators/{id} 登录态。单个指标元数据。 **响应 200**(`NOT_FOUND` 指标不存在) ```json { "id": 1, "category": "GDP", "subcategory": "现价GDP增速", "name": "中国:GDP:现价:当季同比", "region": "中国", "frequency": "季", "unit": "%", "wind_code": "X4608416", "source": "Wind", "data_start": "1993-03", "data_end": "2026-06", "refreshed_at": "2026-07-15", "remark": "同比" } ``` ### GET /api/indicators/{id}/values 登录态。单个指标的时序值。 **查询参数**(均可选): - `start` 起始日期(`yyyy-MM-dd` 或 `yyyy-MM`,按 `obs_date >= start` 过滤) - `end` 截止日期(`obs_date <= end`) - `limit` 返回行数限制(与 `start`/`end` 可组合使用) - `order` 排序方向,`asc`(默认,时间正序)/ `desc` **响应 200**(`NOT_FOUND` 指标不存在) ```json { "indicator": { "id": 1, "category": "GDP", "subcategory": "现价GDP增速", "name": "中国:GDP:现价:当季同比", "region": "中国", "frequency": "季", "unit": "%", "wind_code": "X4608416", "source": "Wind", "data_start": "1993-03", "data_end": "2026-06", "refreshed_at": "2026-07-15", "remark": "同比" }, "count": 2, "values": [ { "obs_date": "2025-12-31", "value": 3.85 }, { "obs_date": "2026-03-31", "value": 4.94 } ] } ``` ### GET /api/indicators/{id}/latest 登录态。单个指标的最新观测点。 **响应 200**(`NOT_FOUND` 指标不存在) ```json { "indicator": { "id": 1, "category": "GDP", "subcategory": "现价GDP增速", "name": "中国:GDP:现价:当季同比", "region": "中国", "frequency": "季", "unit": "%", "wind_code": "X4608416", "source": "Wind", "data_start": "1993-03", "data_end": "2026-06", "refreshed_at": "2026-07-15", "remark": "同比" }, "latest": { "obs_date": "2026-06-30", "value": 5.89 } } ``` ### 6.1 指标管理 > 以下写接口均需 `ADMIN` 角色 + 权限点(`indicator:create` / `indicator:update` / `indicator:delete` / `indicator:value:write`),由 `sql/init.sql` 预置。 > 元数据表 `indicator.id` 为 `INTEGER PRIMARY KEY`(SQLite rowid),缺省时由数据库自动分配;`wind_code` 全局唯一。 > 时序值 `indicator_value` 上有 `UNIQUE(indicator_id, obs_date)` 约束。 #### POST /api/indicators 权限点:`indicator:create`。新增指标元数据。 **请求体**(`category` / `name` / `frequency` 必填;`id` 可选,缺省自动分配) ```json { "id": 100, "category": "GDP", "subcategory": "现价GDP增速", "name": "示例指标", "region": "中国", "frequency": "月", "unit": "%", "windCode": "X9999999", "source": "Wind", "dataStart": "2020-01", "dataEnd": "2026-08", "refreshedAt": "2026-09-15", "remark": "示例" } ``` **响应 200**(失败:`BAD_REQUEST` 必填字段缺失 / `INDICATOR_EXISTS` id 或 wind_code 重复):新建后的完整元数据。 #### PUT /api/indicators/{id} 权限点:`indicator:update`。更新元数据,缺失字段沿用原值。 **请求体**:同 POST(可只传需改字段;`id` / `category` / `name` / `frequency` 缺省时保持原值)。 **响应 200**(`NOT_FOUND` 指标不存在 / `INDICATOR_EXISTS` wind_code 被其它指标占用):更新后的完整元数据。 #### DELETE /api/indicators/{id} 权限点:`indicator:delete`。删除指标元数据,并级联清理其全部时序值(`indicator_value.indicator_id` 外键 `ON DELETE CASCADE`)。 **请求体**:无。 **响应 200** ```json { "id": 12, "deleted": true, "valuesDeleted": 187 } ``` `NOT_FOUND` 指标不存在。 #### POST /api/indicators/{id}/values 权限点:`indicator:value:write`。新增单条或批量时序值;与查询接口同路径,方法为 POST。 **单条请求体** ```json { "obsDate": "2026-07-31", "value": 5.2 } ``` **批量请求体**(数组内每项 `{obsDate, value}`;批量使用 `INSERT OR IGNORE`,已存在的 obs_date 被静默跳过,保证幂等) ```json { "values": [ { "obsDate": "2026-08-31", "value": 5.4 }, { "obsDate": "2026-09-30", "value": 5.6 } ] } ``` **响应 200** - 单条:返回 `{id, obs_date, value}`(重复 obs_date 返回 `VALUE_EXISTS`); - 批量:`{indicatorId, requested, inserted, skipped}`。 #### PUT /api/indicators/{id}/values/{obsDate} 权限点:`indicator:value:write`。按 obs_date upsert 单条值(不存在则创建)。 **请求体** ```json { "value": 5.89 } ``` **响应 200**(`NOT_FOUND` 指标不存在):返回 `{id, obs_date, value}`。 --- ## 7. 文档管理 Document > 通用文档管理:每个文档归属一个 `indicator`(任意 `category` / `subcategory`,不再限制白名单), > 用于把投研文档(央行货政报告、重要会议、产业政策、研究报告、合同模板……)统一上传、分类、检索、下载、摘要。 > > 数据源:`document` + `document_summary`(表结构见 `sql/init.sql` 8 节)。 > 文件落盘目录:`ir.upload.base-path`(默认 `data/uploads/`,部署可改为绝对路径)。 > 落盘子目录格式:`{base-path}/{indicator.category}/{indicator.subcategory}/{period-title-uuid}.{ext}`。 > 文件大小限制:单文件 20MB,请求体 25MB(`spring.servlet.multipart.*`)。 > 允许的扩展名白名单:`pdf, doc, docx, xls, xlsx, ppt, pptx, txt, md, csv, json`(在 `DocumentController.ALLOWED_EXTS` 追加即可扩展)。 > 权限点:`document:create` / `document:update` / `document:delete` / `document:summary:write`(仅 ADMIN 可写)。 > 前端菜单:`数据管理 / 文档管理`(id=31,route=`/document/index`,component=`document/index`)。 ### 7.1 查询 #### GET /api/documents/categories 权限:登录。返回有文档的 `(category, subcategory)` 及文档数、最新期信息(左侧导航/分类树用)。 **响应 200** ```json [ { "category": "政策面", "subcategory": "2.10 央行货币政策例会", "indicator_id": 77, "indicator_name": "央行货币政策委员会例会", "doc_count": 22, "latest_period": "2026Q2", "latest_title": "...", "latest_id": 1 } ] ``` #### GET /api/documents 权限:登录。按 `category` / `subcategory` 过滤;`page`/`size` 不传则返回全部。 **Query**:`category` / `subcategory` / `page` / `size` **响应 200** ```json { "count": 22, "items": [ { "id": 1, "category_id": 77, "period": "2026Q2", "period_year": 2026, "title": "2026Q2-中国货币政策例会", "file_path": "政策面/2.10 央行货币政策例会/2026Q2-2026Q2-中国货币政策例会-abcd1234.pdf", "file_size": 222123, "publish_date": "2026-06-28", "page_count": 8, "is_latest": 1, "sort_order": 1, "created_at": "...", "category": "政策面", "subcategory": "2.10 央行货币政策例会", "indicator_name": "..." } ] } ``` #### GET /api/documents/latest 权限:登录。一次性返回每个 `subcategory` 的 `is_latest=1` 文档 + 摘要(工作台/首页用)。 **响应 200**:数组,每项包含文档字段 + `summary {overall_tone, policy_stance, risk_control, key_points_json, summary_updated_at}`。 #### GET /api/documents/{id} 权限:登录。返回单个文档详情,`summary` 字段可能为 `null`。 #### GET /api/documents/{id}/download 权限:登录。流式返回文件二进制,触发浏览器下载(`Content-Disposition: attachment; filename=".<ext>"`)。 `Content-Type` 按扩展名自动匹配(pdf/doc/xls/ppt/...),未识别时返回 `application/octet-stream`。 #### GET /api/documents/{id}/preview 权限:登录。**内联**返回文件二进制(`Content-Disposition: inline`),供浏览器/`<iframe>` 直接渲染: - `pdf` → 浏览器内置 PDF 阅读器; - `txt` / `md` / `csv` / `json` → 文本展示; - 图片类扩展名当前在白名单外,可先在 `DocumentController.ALLOWED_EXTS` 加上 `png/jpg/jpeg/gif/webp` 后即可预览; - `doc` / `xls` / `ppt` 等 Office 文档浏览器无内置渲染,会回退到下载行为(建议前端降级为 `/download`)。 附带 `X-Content-Type-Options: nosniff` 防止 MIME 嗅探。 ### 7.2 管理 #### POST /api/documents 权限点:`document:create`。上传文件 + 创建元数据。 **请求**(`multipart/form-data`): | 字段 | 必填 | 说明 | |---|---|---| | `file` | 是 | 文件(扩展名必须在白名单内) | | `categoryId` | 是 | 任意 `indicator.id`(不再限制白名单) | | `period` | 是 | 例:`2026Q2` / `2025年` / `2026-07-30` | | `title` | 是 | 例:`2026Q2-中国货币政策例会` | | `periodYear` | 否 | 整数 | | `publishDate` | 否 | `YYYY-MM-DD` | | `pageCount` | 否 | 整数 | | `isLatest` | 否 | 0/1,默认 1;会自动取消同分类其它文档的 latest 标记 | | `sortOrder` | 否 | 整数,默认 0 | **响应 200**:`DOC_EXISTS` 表示 `(category_id, period)` 已存在;返回完整文档对象。 #### PUT /api/documents/{id} 权限点:`document:update`。更新元数据;如同时传 `file` 则替换磁盘文件(旧文件删除)。 **请求**(`multipart/form-data`):字段同 POST;未传的字段保留原值;`file` 不传则不改文件。 #### DELETE /api/documents/{id} 权限点:`document:delete`。删除文档(级联删除 `document_summary`)与磁盘文件。 **响应 200**:`{id, dbDeleted, fileDeleted}`。 #### PUT /api/documents/{id}/summary 权限点:`document:summary:write`。整体替换式 upsert 摘要。 **请求体**(`application/json`) ```json { "overallTone": "坚持稳中求进", "policyStance": "稳健偏宽松", "riskControl": "防范资金空转", "keyPointsJson": "[{\"section\":\"汇率\",\"text\":\"防止超调\",\"change\":\"强调\"}]" } ``` **响应 200**:返回更新后的文档详情。 --- ## 8. 常见错误码 | code | HTTP | 说明 | |---|---|---| | `TOKEN_INVALID` | 200 | Token 无效或已过期 | | `UNAUTHORIZED` | 401 | 未登录(见 /api/auth/me、拦截器) | | `FORBIDDEN` | 403 | 无角色/权限 | | `BAD_CREDENTIALS` | 200 | 用户名或密码错误 | | `USER_EXISTS` | 200 | 用户名已被注册 | | `USER_DISABLED` | 200 | 账号已禁用 | | `USER_NOT_FOUND` | 200 | 用户不存在 | | `ROLE_EXISTS` / `PERMISSION_EXISTS` | 200 | 编码重复 | | `NOT_FOUND` | 200 | 资源不存在 | | `BAD_REQUEST` | 200 | 请求参数不合法 | | `DB_ERROR` | 500 | 数据源异常 | | `INTERNAL_ERROR` | 500 | 服务器内部错误 |