# 服务层
**本文引用的文件**
- [http.js](file://fms-vue/src/services/http.js)
- [api.js](file://fms-vue/src/services/api.js)
- [fileService.js](file://fms-vue/src/services/fileService.js)
- [auth.js](file://fms-vue/src/stores/auth.js)
- [FmsFile.vue](file://fms-vue/src/components/fms-file/FmsFile.vue)
- [http.spec.js](file://fms-vue/tests/services/http.spec.js)
- [api.spec.js](file://fms-vue/tests/services/api.spec.js)
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能与并发](#性能与并发)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向 FMS 前端服务层,系统性说明 HTTP 请求封装、API 接口组织、文件上传下载服务、错误处理统一机制、日志记录方案、缓存策略与并发控制,以及 API 版本管理与向后兼容建议。文档以代码为依据,结合测试用例,帮助读者快速理解并安全扩展服务层能力。
## 项目结构
服务层位于 fms-vue/src/services 下,包含:
- http.js:基于 axios 的 HTTP 客户端封装,提供请求/响应拦截器、统一错误处理、登录态注入与跳转。
- api.js:业务 API 函数集合,按功能分组暴露方法,内部调用 http.post。
- fileService.js:文件服务,支持本地直传与阿里云 OSS 直传,含凭证缓存、批量上传、预览与删除。
```mermaid
graph TB
subgraph "服务层"
A["http.js
axios实例/拦截器"]
B["api.js
业务API集合"]
C["fileService.js
文件上传/下载/删除"]
end
subgraph "状态管理"
D["auth.js
登录态/会话"]
end
subgraph "UI组件"
E["FmsFile.vue
文件面板"]
end
B --> A
C --> A
C --> B
A --> D
E --> B
E --> C
```
图表来源
- [http.js:8-15](file://fms-vue/src/services/http.js#L8-L15)
- [api.js:1-81](file://fms-vue/src/services/api.js#L1-L81)
- [fileService.js:1-149](file://fms-vue/src/services/fileService.js#L1-L149)
- [auth.js:1-49](file://fms-vue/src/stores/auth.js#L1-L49)
- [FmsFile.vue:1-262](file://fms-vue/src/components/fms-file/FmsFile.vue#L1-L262)
章节来源
- [http.js:8-15](file://fms-vue/src/services/http.js#L8-L15)
- [api.js:1-81](file://fms-vue/src/services/api.js#L1-L81)
- [fileService.js:1-149](file://fms-vue/src/services/fileService.js#L1-L149)
- [auth.js:1-49](file://fms-vue/src/stores/auth.js#L1-L49)
- [FmsFile.vue:1-262](file://fms-vue/src/components/fms-file/FmsFile.vue#L1-L262)
## 核心组件
- HTTP 客户端(http.js)
- 创建 axios 实例,设置 baseURL、超时、默认头。
- 请求拦截器:从 Pinia 读取 token,自动注入 Authorization 头。
- 响应拦截器:统一包装 ApiResult;对认证失败(401/40101)清理会话并跳转登录;网络异常时给出友好提示并标记 tosted 避免重复弹窗。
- 导出 post(path, data, config) 简化调用。
- API 接口(api.js)
- 按功能分组:认证、数据加载、分页、保存、ID/编号生成、表结构描述等。
- 所有接口通过 http.post 发起,参数映射到后端约定字段。
- 文件服务(fileService.js)
- initFileConfig/getStorageType:初始化存储模式(local 或 aliyun-oss)。
- upload:根据模式选择本地打包上传或 OSS 直传;OSS 路径下逐个回调 save-record 写入元数据。
- 凭证缓存:getOssCredential 带过期缓冲刷新。
- 工具:预览地址获取、删除文件、扩展名解析。
章节来源
- [http.js:8-110](file://fms-vue/src/services/http.js#L8-L110)
- [api.js:1-81](file://fms-vue/src/services/api.js#L1-L81)
- [fileService.js:1-149](file://fms-vue/src/services/fileService.js#L1-L149)
## 架构总览
下图展示一次典型的数据查询流程:页面调用 api.js 中的接口,经 http.js 的请求拦截器注入鉴权头,到达服务端后由响应拦截器统一处理成功/失败/认证失效分支。
```mermaid
sequenceDiagram
participant UI as "页面/组件"
participant API as "api.js"
participant HTTP as "http.js"
participant AUTH as "auth.js"
participant S as "后端服务"
UI->>API : 调用 loadDataApi(...)
API->>HTTP : post('/data/loaddata', params)
HTTP->>AUTH : 读取token
AUTH-->>HTTP : token
HTTP->>S : 发送请求(携带Authorization)
S-->>HTTP : 返回{code,data,message}
alt 认证成功且业务成功
HTTP-->>API : 透传response.data
API-->>UI : 数据
else 业务错误
HTTP-->>API : 抛出Error(code/message/data/success=false)
API-->>UI : 捕获并处理
else 认证失败(401/40101)
HTTP->>AUTH : clearSession()
HTTP-->>UI : 抛错(可被上层捕获)
Note over HTTP,AUTH : 同时触发跳转登录页
end
```
图表来源
- [api.js:17-24](file://fms-vue/src/services/api.js#L17-L24)
- [http.js:41-104](file://fms-vue/src/services/http.js#L41-L104)
- [auth.js:14-25](file://fms-vue/src/stores/auth.js#L14-L25)
## 详细组件分析
### HTTP 客户端(http.js)
- 实例配置
- baseURL 来自环境变量或默认 /api;timeout 15s;默认 JSON 头。
- 请求拦截器
- 动态读取 auth store 的 token,若存在则注入 Authorization: Bearer 。
- 响应拦截器
- 无 code 字段时按成功透传并包装为 {success:true, code:0, message:'', data}。
- code===0 视为成功,直接返回原 response。
- 业务错误:构造 Error 并附加 code、data、success=false。
- 认证错误:401/40101 时清除会话并跳转登录页,附带 reason=expired 或 session-replaced。
- 网络异常:区分超时与一般网络错误;当无 status 或 status>=500 时统一弹出全局 Message 并标记 tosted=true,避免与已有消息重复。
- 便捷方法
- post(path, data, config) 返回 response.data,简化调用方使用。
```mermaid
flowchart TD
Start(["进入响应拦截器"]) --> CheckCode{"响应体是否有code?"}
CheckCode --> |否| WrapSuccess["包装为ApiResult(success=true)"]
CheckCode --> |是| SuccessCheck{"code===0 ?"}
SuccessCheck --> |是| ReturnResp["返回原response"]
SuccessCheck --> |否| AuthCheck{"是否认证错误(401/40101)?"}
AuthCheck --> |是| HandleAuth["clearSession + 跳转登录"]
AuthCheck --> |否| ThrowBizErr["抛业务错误Error(code/message/data/success=false)"]
ReturnResp --> End(["结束"])
ThrowBizErr --> End
HandleAuth --> End
```
图表来源
- [http.js:49-104](file://fms-vue/src/services/http.js#L49-L104)
章节来源
- [http.js:8-110](file://fms-vue/src/services/http.js#L8-L110)
- [http.spec.js:33-200](file://fms-vue/tests/services/http.spec.js#L33-L200)
### API 接口(api.js)
- 模块化设计
- 认证:loginApi
- 数据访问:loadDataApi、loadDataBySqlApi、pageDataApi
- 数据变更:saveObjectApi
- 标识与编码:nextIdApi、nextCodeApi
- 元信息:describeApi
- 参数约定
- 统一通过 http.post 发送 JSON;分页默认 page_no=1、page_size=20。
- 模块编号 moduleId 在 nextCodeApi 中转为字符串。
- 测试覆盖
- 针对每个接口的请求路径与参数进行断言,确保契约稳定。
```mermaid
graph LR
P["页面/组件"] --> L["api.js"]
L --> H["http.post"]
H --> S["后端接口"]
```
图表来源
- [api.js:1-81](file://fms-vue/src/services/api.js#L1-L81)
- [http.js:106-108](file://fms-vue/src/services/http.js#L106-L108)
章节来源
- [api.js:1-81](file://fms-vue/src/services/api.js#L1-L81)
- [api.spec.js:24-115](file://fms-vue/tests/services/api.spec.js#L24-L115)
### 文件服务(fileService.js)
- 存储模式
- initFileConfig 调用 /file/config 获取 storageType(local 或 aliyun-oss),并在模块级缓存。
- 上传流程
- Local:一次性 FormData 打包多个文件,POST /file/upload,后端写磁盘并插入记录。
- OSS:共享凭证 → 逐个 XHR 直传到 OSS host → 逐个 POST /file/save-record 写入元数据。
- 凭证缓存
- getOssCredential 缓存最近一次凭证,credExpiring 根据 expire/Expiration 或 fetchedAt 计算是否过期,提前 5 分钟刷新。
- 其他能力
- getPreviewUrl:返回 mx_mapfilename。
- removeFile:POST /file/delete。
- extname:提取扩展名用于对象命名。
```mermaid
sequenceDiagram
participant UI as "文件组件"
participant FS as "fileService.js"
participant API as "api.js"
participant HTTP as "http.js"
participant OSS as "OSS直传"
UI->>FS : upload({files,father,moduleId,cateId})
FS->>FS : initFileConfig()
alt storageType === 'aliyun-oss'
FS->>FS : getOssCredential()
loop 逐个文件
FS->>API : nextIdApi()
API->>HTTP : post('/data/nextid')
HTTP-->>API : id
API-->>FS : id
FS->>OSS : XHR直传(key,policy,signature,file)
OSS-->>FS : 返回URL
FS->>API : post('/file/save-record', record)
API->>HTTP : post('/file/save-record')
HTTP-->>API : 成功
API-->>FS : 成功
end
else storageType === 'local'
FS->>API : post('/file/upload', FormData)
API->>HTTP : post('/file/upload')
HTTP-->>API : List