# 认证与系统基础接口
**本文引用的文件**
- [AuthController.java](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/AuthController.java)
- [UserController.java](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/UserController.java)
- [SystemController.java](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/SystemController.java)
- [CaseInfoController.java](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/CaseInfoController.java)
- [TableInfoController.java](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TableInfoController.java)
- [TowerController.java](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TowerController.java)
- [SysUserService.java](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java)
- [LoginDTO.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/dto/LoginDTO.java)
- [OpenCaseDTO.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/dto/OpenCaseDTO.java)
- [UpdateCasePwdDTO.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/dto/UpdateCasePwdDTO.java)
- [LoginVO.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/vo/LoginVO.java)
- [UserVO.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/vo/UserVO.java)
- [Result.java](file://ai-server/src/main/java/com/zsjz/ai/common/config/Result.java)
- [AccountInUseException.java](file://ai-server/src/main/java/com/zsjz/ai/common/exception/AccountInUseException.java)
- [GlobalExceptionHandler.java](file://ai-server/src/main/java/com/zsjz/ai/common/exception/GlobalExceptionHandler.java)
- [SysUser.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/entity/SysUser.java)
- [CaseInfo.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/entity/CaseInfo.java)
- [TableInfo.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/entity/TableInfo.java)
- [SystemInfo.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/entity/SystemInfo.java)
- [authApi.ts](file://ai-electron/frontend/src/core/api/auth/authApi.ts)
- [account.ts](file://ai-electron/frontend/src/core/store/modules/account.ts)
- [user.sql](file://sql/user.sql)
- [plat.sql](file://sql/plat.sql)
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细接口说明](#详细接口说明)
6. [依赖关系分析](#依赖关系分析)
7. [性能与分页特性](#性能与分页特性)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
## 简介
本文面向前端和后端维护者,系统性梳理 plat 模块下 `/auth`、`/user`、`/sys`、`/case`、`/tpl`、`/tower` 等控制器的 API 契约。重点包括:
- 登录流程:前端如何调用 `/auth/login` 获取 token,后续请求如何携带 `x-token`,Sa-Token 与会话活跃检测如何配合完成“账号占用确认”“强制登录”“顶下线”。
- 用户信息、授权单元、系统信息、案件数据源、表模板、塔台基站的 HTTP 方法、URL、请求参数、响应字段、分页格式和错误码。
- 登录失效、账号被顶下线、管理员重置密码或强制下线时,前端缓存清理和状态恢复行为。
- curl 与前端调用示例,以及 401、403、503 等错误的排查步骤。
## 项目结构
plat 模块采用典型的 Spring MVC 分层:控制器负责路由和统一响应包装,服务层封装认证、用户管理、案件、系统和塔台业务,实体类描述数据库模型,DTO/VO 描述接口入参出参。
```mermaid
graph TB
Client["前端页面
浏览器 / Electron"] --> Gateway["网关 / 反向代理"]
Gateway --> AuthCtl["/auth 认证控制器"]
Gateway --> UserCtl["/user 用户管理控制器"]
Gateway --> SysCtl["/sys 系统控制器"]
Gateway --> CaseCtl["/case 案件控制器"]
Gateway --> TplCtl["/tpl 表模板控制器"]
Gateway --> TowerCtl["/tower 塔台控制器"]
AuthCtl --> SysSvc["SysUserService
认证 + 用户管理"]
UserCtl --> SysSvc
CaseCtl --> CaseSvc["CaseInfoService"]
TplCtl --> TableSvc["TableInfoService"]
TowerCtl --> TowerSvc["TowerService"]
SysCtl --> SystemSvc["SystemService"]
SysCtl --> HealthSvc["SystemHealthService"]
SysSvc --> Stp["Sa-Token
会话与 Token"]
SysSvc --> DsReg["CaseDataSourceRegistry
案件数据源注册表"]
SysSvc --> Cache["CaseDataCache
案件数据缓存"]
```
**图示来源**
- [AuthController.java:1-82](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/AuthController.java#L1-L82)
- [UserController.java:1-89](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/UserController.java#L1-L89)
- [SystemController.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/SystemController.java#L1-L200)
- [CaseInfoController.java:1-117](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/CaseInfoController.java#L1-L117)
- [TableInfoController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TableInfoController.java#L1-L91)
- [TowerController.java:1-54](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TowerController.java#L1-L54)
- [SysUserService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L1-L200)
**章节来源**
- [AuthController.java:1-82](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/AuthController.java#L1-L82)
- [UserController.java:1-89](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/UserController.java#L1-L89)
- [SystemController.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/SystemController.java#L1-L200)
- [CaseInfoController.java:1-117](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/CaseInfoController.java#L1-L117)
- [TableInfoController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TableInfoController.java#L1-L91)
- [TowerController.java:1-54](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TowerController.java#L1-L54)
## 核心组件
- **认证控制器 `/auth`**:提供登录、登出、当前用户信息、修改密码、校验密码。其中 `/auth/login` 在 Sa-Token 拦截器白名单中,其余接口需要登录。
- **用户管理控制器 `/user`**:用户分页列表、创建、更新、重置密码、删除、强制下线。所有接口要求当前用户是管理员。
- **系统控制器 `/sys`**:健康检查、系统服务探测、授权信息、机器码、离线授权激活、缓存清理、升级包上传、文件下载、本地文件打开、离线地图瓦片与样式等。
- **案件控制器 `/case`**:查询自己的案件列表、新建或更新案件、删除、更新案件密码、开启案件、退出案件、查询当前已打开案件。
- **表模板控制器 `/tpl`**:模板分页查询、名称重复性检查、分类枚举、详情、创建、更新、删除。
- **塔台控制器 `/tower`**:基站查询、上传基站 xls/xlsx 转换、下载转换模板。
- **统一响应体 `Result`**:所有业务接口返回 `{code, message, data}`;成功默认 code 为 200,失败默认 code 为 400。
- **登录异常 `AccountInUseException`**:账号正在其他设备使用时返回 HTTP 200 + body.code=40901,前端据此弹出是否继续登录的确认框。
**章节来源**
- [AuthController.java:1-82](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/AuthController.java#L1-L82)
- [UserController.java:1-89](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/UserController.java#L1-L89)
- [SystemController.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/SystemController.java#L1-L200)
- [CaseInfoController.java:1-117](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/CaseInfoController.java#L1-L117)
- [TableInfoController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TableInfoController.java#L1-L91)
- [TowerController.java:1-54](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TowerController.java#L1-L54)
- [Result.java:1-114](file://ai-server/src/main/java/com/zsjz/ai/common/config/Result.java#L1-L114)
- [AccountInUseException.java:1-19](file://ai-server/src/main/java/com/zsjz/ai/common/exception/AccountInUseException.java#L1-L19)
## 架构总览
认证与系统基础接口的关键交互如下:
```mermaid
sequenceDiagram
participant FE as "前端"
participant GW as "网关 / 反向代理"
participant AC as "认证控制器"
participant SVC as "SysUserService"
participant ST as "Sa-Token"
participant ACT as "登录活跃服务"
participant DS as "案件数据源注册表"
participant CACHE as "案件数据缓存"
FE->>GW: POST /auth/login {username,password,force}
GW->>AC: 转发登录请求
AC->>SVC: login(LoginDTO)
SVC->>ACT: isInUse(userId)
alt 账号正在使用且未 force
SVC-->>AC: 抛出 AccountInUseException
AC-->>FE: HTTP 200 {code:40901,message}
FE->>FE: 弹出确认框
FE->>GW: POST /auth/login {...,force:true}
GW->>AC: 转发强制登录
AC->>SVC: login(force=true)
SVC->>ST: kickout(userId)
SVC->>DS: 释放旧会话打开的案件
SVC->>CACHE: 清理旧会话案件缓存
SVC->>ST: login(userId)
SVC-->>AC: LoginVO{token,...}
AC-->>FE: {code:200,data:LoginVO}
else 正常登录
SVC->>ST: login(userId)
SVC-->>AC: LoginVO{token,...}
AC-->>FE: {code:200,data:LoginVO}
end
Note over FE,GW: 后续请求通过 x-token 携带令牌
```
**图示来源**
- [AuthController.java:28-48](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/AuthController.java#L28-L48)
- [SysUserService.java:63-118](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L63-L118)
- [AccountInUseException.java:11-19](file://ai-server/src/main/java/com/zsjz/ai/common/exception/AccountInUseException.java#L11-L19)
- [GlobalExceptionHandler.java:41-52](file://ai-server/src/main/java/com/zsjz/ai/common/exception/GlobalExceptionHandler.java#L41-L52)
- [authApi.ts:77-85](file://ai-electron/frontend/src/core/api/auth/authApi.ts#L77-L85)
- [account.ts:57-65](file://ai-electron/frontend/src/core/store/modules/account.ts#L57-L65)
## 详细接口说明
### 认证接口 `/auth`
| HTTP 方法 | URL | 鉴权 | 请求体 | 成功响应 | 说明 |
|---|---|---|---|---|---|
| POST | `/auth/login` | 无需登录 | `{username,password,force?}` | `{code:200,data:{token,userId,username,displayName,isAdmin}}` | 登录;账号被占用且未 force 时返回 HTTP 200 + code=40901 |
| POST | `/auth/logout` | 可选(401 也允许) | 无 | `{code:200}` | 登出并释放该 token 打开的案件 |
| GET | `/auth/info` | 需登录 | 无 | `{code:200,data:{id,username,displayName,isAdmin,status,createTime,lastLoginTime}}` | 刷新页面后恢复用户态 |
| POST | `/auth/changePwd` | 需登录 | `{oldPwd,newPwd}` | `{code:200}` | 自助修改密码 |
| POST | `/auth/verifyPwd` | 需登录 | `{password?}` | `{code:200,data:boolean}` | 锁屏解锁;密码错返回 false 而非 401 |
**登录请求字段**
- `username`:登录名,必填。
- `password`:明文密码,服务端用 BCrypt 比对,不落库。
- `force`:可选布尔值;当账号正在其他设备使用时首次登录返回 40901,用户确认后带 `force=true` 重新提交以接管旧会话。
**登录成功返回字段**
- `token`:Sa-Token 登录凭据,前端写入缓存后由请求拦截器注入 `x-token` 请求头。
- `userId`:用户 ID。
- `username`:登录名。
- `displayName`:显示名。
- `isAdmin`:1 为管理员,0 为普通用户。
**错误行为**
- 用户名或密码为空:HTTP 400 + code=400 + message。
- 用户名或密码错误:HTTP 400 + code=400 + message。
- 账号已被停用:HTTP 400 + code=400 + message。
- 账号正在其他设备使用:HTTP 200 + code=40901 + message,前端弹确认框。
**章节来源**
- [AuthController.java:28-81](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/AuthController.java#L28-L81)
- [LoginDTO.java:1-25](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/dto/LoginDTO.java#L1-L25)
- [LoginVO.java:1-24](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/vo/LoginVO.java#L1-L24)
- [SysUserService.java:63-118](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L63-L118)
- [AccountInUseException.java:11-19](file://ai-server/src/main/java/com/zsjz/ai/common/exception/AccountInUseException.java#L11-L19)
- [GlobalExceptionHandler.java:41-52](file://ai-server/src/main/java/com/zsjz/ai/common/exception/GlobalExceptionHandler.java#L41-L52)
#### 前端登录调用示例
- **curl**
- 正常登录:`curl -X POST http://localhost:端口/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"admin123"}'`
- 强制登录:`curl -X POST http://localhost:端口/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"admin123","force":true}'`
- **前端调用**
- 调用 `/auth/login`,设置 `isTransformResponse:false`,因为 40901 是正常分支。
- 成功后从响应体取 `data.token`,写入 localStorage,并在后续请求中通过 `x-token` 请求头携带。
- 若返回 `code===40901`,弹出“是否确定登录”确认框;确认后再次调用同一接口并传入 `force:true`。
**章节来源**
- [authApi.ts:24-30](file://ai-electron/frontend/src/core/api/auth/authApi.ts#L24-L30)
- [authApi.ts:77-85](file://ai-electron/frontend/src/core/api/auth/authApi.ts#L77-L85)
- [account.ts:57-65](file://ai-electron/frontend/src/core/store/modules/account.ts#L57-L65)
### 用户管理接口 `/user`
| HTTP 方法 | URL | 鉴权 | 请求体 | 成功响应 | 说明 |
|---|---|---|---|---|---|
| GET | `/user/list` | 管理员 | `keyword?`, `page?`, `size?` | `{code:200,data:Page}` | 按登录名或显示名模糊匹配分页 |
| POST | `/user/create` | 管理员 | `{username,password,displayName?,isAdmin?,status?}` | `{code:200}` | 新建用户 |
| POST | `/user/update` | 管理员 | `{id,displayName?,isAdmin?,status?}` | `{code:200}` | 修改显示名、管理员标志、启用状态 |
| POST | `/user/resetPwd` | 管理员 | `{id,newPassword}` | `{code:200}` | 重置密码并强制该用户下线 |
| POST | `/user/delete` | 管理员 | `{id}` | `{code:200}` | 删除用户 |
| POST | `/user/kickout` | 管理员 | `{id}` | `{code:200}` | 强制指定用户下线 |
**分页字段**
- `keyword`:可选,模糊匹配登录名或显示名。
- `page`:可选,默认 1。
- `size`:可选,默认 20。
- 返回的 `data` 是 MyBatis-Plus `Page` 对象,包含 `current`、`size`、`total`、`records`。
**用户返回字段**
- `id`:用户 ID。
- `username`:登录名。
- `displayName`:显示名。
- `isAdmin`:1 为管理员,0 为普通用户。
- `status`:1 为启用,0 为停用。
- `createTime`:创建时间。
- `lastLoginTime`:最后登录时间。
**错误行为**
- 非管理员调用:HTTP 403 + code=403 + message。
- 用户名或新密码为空:HTTP 400 + code=400 + message。
- 用户不存在:HTTP 400 + code=400 + message。
- 尝试删除最后一个管理员:HTTP 400 + code=400 + message。
- 用户名下还有案件:HTTP 400 + code=400 + message。
- 密码长度少于 6 位:HTTP 400 + code=400 + message。
**章节来源**
- [UserController.java:18-88](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/UserController.java#L18-L88)
- [SysUserService.java:138-429](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L138-L429)
- [UserVO.java:1-28](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/vo/UserVO.java#L1-L28)
- [SysUser.java:1-61](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/entity/SysUser.java#L1-L61)
### 系统接口 `/sys`
| HTTP 方法 | URL | 鉴权 | 请求体 | 成功响应 | 说明 |
|---|---|---|---|---|---|
| GET | `/sys/health` | 通常无需登录 | 无 | `{code:200}` | 健康检查 |
| GET | `/sys/serviceHealth` | 通常无需登录 | 无 | `{code:200,data:[ServiceHealthDTO]}` | 后端、PostgreSQL、Redis、Elasticsearch 可用性与耗时 |
| GET | `/sys/info` | 通常无需登录 | 无 | `{code:200,data:SystemInfo}` | 系统授权信息 |
| GET | `/sys/code` | 通常无需登录 | 无 | `{code:200,data:string}` | 服务器机器码 |
| POST | `/sys/noNet` | 通常无需登录 | multipart/form-data `file=.xlts` | `{code:200}` | 离线授权激活 |
| GET | `/sys/cleanCache` | 通常无需登录 | 无 | `{code:200}` | 清理系统缓存 |
| POST | `/sys/uploadUpgrade` | 通常无需登录 | multipart/form-data `file=zip` | `{code:200}` | 上传升级包并执行升级 |
| GET | `/sys/downloadFile` | 通常无需登录 | query `fid=file_info.id` | 文件流 | 按文件 ID 下载附件,不返回 Result |
| GET | `/sys/map/pmtiles` | 通常无需登录 | header `Range?` | 瓦片字节流 | 离线地图 PMTiles,支持 Range |
| GET | `/sys/map/style` | 通常无需登录 | 无 | 样式 JSON 或 404 | 离线地图自定义样式 |
**系统信息字段**
- `machinecode`:机器码。
- `clientNo`:客户编号。
- `unit`:单位。
- `phone`:手机号。
- `authdatetime`:授权时间。
- `overdatetime`:过期时间。
- `activeDateTime`:激活时间。
- `remainingDate`:剩余天数,非数据库字段。
**错误行为**
- 健康检查失败或外部服务不可用时,`serviceHealth` 会返回各服务的探测结果;整体仍可能走 200,但前端应读取 `data` 中的状态字段判断可用性。
- 文件下载或离线地图资源缺失时,可能返回 404,前端应按业务逻辑处理回退。
**章节来源**
- [SystemController.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/SystemController.java#L1-L200)
- [SystemInfo.java:1-71](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/entity/SystemInfo.java#L1-L71)
- [plat.sql:1-154](file://sql/plat.sql#L1-L154)
### 案件接口 `/case`
| HTTP 方法 | URL | 鉴权 | 请求体 | 成功响应 | 说明 |
|---|---|---|---|---|---|
| GET | `/case/list` | 需登录 | 无 | `{code:200,data:[CaseInfo]}` | 仅返回当前登录用户自己的案件 |
| POST | `/case/create` | 需登录 | `CaseInfo` JSON | `{code:200}` | 带 id 为更新,不带 id 为新建 |
| GET | `/case/delete` | 需登录 | query `id` | `{code:200}` | 删除案件 |
| POST | `/case/updatePwd` | 需登录 | `{id,newPwd}` | `{code:200}` | 更新案件密码 |
| POST | `/case/open` | 需登录 | `{id,pwd?}` | `{code:200,data:CaseInfo}` | 开启案件,返回工作空间路径 |
| GET | `/case/exit` | 需登录 | 无 | `{code:200}` | 退出案件 |
| GET | `/case/current` | 需登录 | 无 | `{code:200,data:CaseInfo|null}` | 当前已打开案件 |
**案件实体字段**
- `id`:案件 ID。
- `name`:案件名称。
- `pwd`:案件密码,写保护,不返回给前端。
- `db`:数据库名称,忽略序列化。
- `dbPath`:数据库路径,忽略序列化。
- `type`:案件类型。
- `remark`:备注。
- `createTime`:创建时间。
- `init`:是否初始化。
- `ownerId`:归属用户 ID。
- `workspacePath`:工作空间目录,非数据库字段,由服务端补回。
**错误行为**
- 未登录:Sa-Token 拦截器统一处理,通常为 401。
- 参数绑定失败:若前端用表单参数而非 JSON body 调用 create/open/updatePwd,会抛 500。
- 越权访问他人案件:服务层按 owner_id 隔离,越权请求会被拒绝。
**章节来源**
- [CaseInfoController.java:18-116](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/CaseInfoController.java#L18-L116)
- [CaseInfo.java:1-101](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/entity/CaseInfo.java#L1-L101)
- [OpenCaseDTO.java:1-20](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/dto/OpenCaseDTO.java#L1-L20)
- [UpdateCasePwdDTO.java:1-12](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/dto/UpdateCasePwdDTO.java#L1-L12)
- [user.sql:1-75](file://sql/user.sql#L1-L75)
### 表模板接口 `/tpl`
| HTTP 方法 | URL | 鉴权 | 请求体 | 成功响应 | 说明 |
|---|---|---|---|---|---|
| GET | `/tpl/page` | 需登录 | `table_name_cn?`, `has_inner?`, `classify?`, `page`, `limit` | `{code:200,data:Page}` | 模板分页查询 |
| GET | `/tpl/checkName` | 需登录 | `old_table_name_cn?`, `name` | `{code:200,data:"Y"|"N"}` | 名称重复性检查 |
| GET | `/tpl/cag` | 需登录 | 无 | `{code:200,data:[string]}` | 分类枚举 |
| GET | `/tpl/detail` | 需登录 | `id` | `{code:200,data:TableInfo}` | 模板详情 |
| POST | `/tpl/create` | 需登录 | `TableInfo` JSON | `{code:200}` | 创建模板 |
| POST | `/tpl/update` | 需登录 | `TableInfo` JSON | `{code:200}` | 更新模板 |
| GET | `/tpl/delete` | 需登录 | `id` | `{code:200}` | 删除模板 |
**模板分页字段**
- `table_name_cn`:可选,中文表名模糊匹配。
- `has_inner`:可选,1 表示有内嵌,0 表示无内嵌。
- `classify`:可选,按类别过滤。
- `page`、`limit`:分页页码与每页条数。
**模板实体字段**
- `id`:主模版 ID。
- `mainId`:主模版 ID。
- `tableNameCn`:表名中文。
- `tableNameEn`:表名英文。
- `headers`:表头。
- `classify`:类别。
- `hasInner`:是否有内嵌。
- `hasTable`:是否有表。
- `createTime`:创建时间。
- `funcRegx`:模板函数正则。
- `lineNo`:表头行号。
- `tableFields`:表字段,非数据库字段。
**错误行为**
- 未登录:401。
- 权限不足:本项目 `/tpl` 没有独立权限体系,但受全局登录拦截影响。
**章节来源**
- [TableInfoController.java:15-90](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TableInfoController.java#L15-L90)
- [TableInfo.java:1-88](file://ai-server/src/main/java/com/zsjz/ai/common/model/plat/entity/TableInfo.java#L1-L88)
- [plat.sql:1-154](file://sql/plat.sql#L1-L154)
### 塔台接口 `/tower`
| HTTP 方法 | URL | 鉴权 | 请求体 | 成功响应 | 说明 |
|---|---|---|---|---|---|
| GET | `/tower/qry` | 需登录 | `mnc`,`lac`,`ci` | `{code:200,data:[string]}` | 基站查询 |
| POST | `/tower/covFile` | 需登录 | multipart/form-data `file=xls/xlsx` | 文件流 | 上传基站 xls 并转换,返回 xlsx 文件流 |
| GET | `/tower/template` | 需登录 | 无 | 文件流 | 下载基站转换模板 |
**错误行为**
- 未登录:401。
- 文件格式错误或模板不匹配:由服务层抛出具体异常,前端按通用错误提示。
**章节来源**
- [TowerController.java:18-53](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TowerController.java#L18-L53)
## 依赖关系分析
```mermaid
classDiagram
class AuthController {
+login(dto)
+logout()
+info()
+changePwd(dto)
+verifyPwd(dto)
}
class UserController {
+list(keyword,page,size)
+create(dto)
+update(dto)
+resetPwd(dto)
+delete(dto)
+kickout(dto)
}
class SystemController {
+health()
+serviceHealth()
+info()
+machineCode()
+noNet(file)
+cleanCache()
+uploadUpgrade(file)
+downloadFile(fid)
+mapPmtiles(range)
+mapStyle()
}
class CaseInfoController {
+list()
+create(caseInfo)
+delete(id)
+updatePwd(dto)
+open(dto)
+exit()
+current()
}
class TableInfoController {
+page(query)
+checkName(old,name)
+cag()
+detail(id)
+create(tableInfo)
+update(tableInfo)
+delete(id)
}
class TowerController {
+qry(mnc,lac,ci)
+covFile(file)
+template()
}
class SysUserService {
+login(dto)
+logout()
+info()
+changePwd(dto)
+verifyPwd(password)
+list(keyword,page,size)
+create(dto)
+update(dto)
+resetPwd(dto)
+delete(id)
+kickout(id)
}
AuthController --> SysUserService : "依赖"
UserController --> SysUserService : "依赖"
CaseInfoController --> CaseInfoService : "依赖"
TableInfoController --> TableInfoService : "依赖"
TowerController --> TowerService : "依赖"
SystemController --> SystemService : "依赖"
SystemController --> SystemHealthService : "依赖"
```
**图示来源**
- [AuthController.java:1-82](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/AuthController.java#L1-L82)
- [UserController.java:1-89](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/UserController.java#L1-L89)
- [SystemController.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/SystemController.java#L1-L200)
- [CaseInfoController.java:1-117](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/CaseInfoController.java#L1-L117)
- [TableInfoController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TableInfoController.java#L1-L91)
- [TowerController.java:1-54](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TowerController.java#L1-L54)
- [SysUserService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L1-L200)
**章节来源**
- [AuthController.java:1-82](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/AuthController.java#L1-L82)
- [UserController.java:1-89](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/UserController.java#L1-L89)
- [SystemController.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/SystemController.java#L1-L200)
- [CaseInfoController.java:1-117](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/CaseInfoController.java#L1-L117)
- [TableInfoController.java:1-91](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TableInfoController.java#L1-L91)
- [TowerController.java:1-54](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TowerController.java#L1-L54)
- [SysUserService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L1-L200)
## 性能与分页特性
- 用户列表分页使用 MyBatis-Plus `Page`,默认 page=1、size=20;返回对象包含 `current`、`size`、`total`、`records`。
- 表模板分页由 `TableInfoQuery` 承载 `page` 和 `limit`,同样返回 MyBatis-Plus `Page`。
- 登出时会关闭当前 token 打开的案件数据源,避免反复登录登出累积 DuckDB 实例占用内存预算。
- 管理员禁用用户、重置密码、删除用户或强制下线时,会调用 `StpUtil.kickout` 并释放对应用户的案件数据源及缓存。
- 系统升级接口 `/sys/uploadUpgrade` 会执行版本校验、解压、覆盖数据库、执行升级 SQL,属于长耗时操作,前端应提供进度或超时提示。
**章节来源**
- [SysUserService.java:118-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L118-L200)
- [SysUserService.java:201-429](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L201-L429)
- [TableInfoController.java:23-34](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/TableInfoController.java#L23-L34)
## 故障排查指南
### 登录相关
- **401 未登录**
- 原因:Sa-Token 检测到 token 无效、过期或被替换。
- 排查:检查请求是否携带 `x-token`;检查前端是否在登录成功后写入 token;检查网关是否正确透传请求头。
- 处理:清空前端登录缓存,跳转到登录页重新登录。
- **40901 账号正在使用**
- 原因:该账号存在有效会话且半小时内活跃过。
- 排查:查看后端日志是否打印“登录被拦截(账号正在使用)”。
- 处理:前端弹出确认框;用户确认后带 `force=true` 重新提交;服务端踢掉旧会话并让新会话登录。
- **400 用户名或密码错误**
- 原因:用户名或密码为空、用户名不存在、密码不匹配、账号停用。
- 排查:检查请求体是否为 JSON,字段名是否正确。
- 处理:提示用户重试或联系管理员启用账号。
**章节来源**
- [AccountInUseException.java:11-19](file://ai-server/src/main/java/com/zsjz/ai/common/exception/AccountInUseException.java#L11-L19)
- [GlobalExceptionHandler.java:41-52](file://ai-server/src/main/java/com/zsjz/ai/common/exception/GlobalExceptionHandler.java#L41-L52)
- [SysUserService.java:63-118](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L63-L118)
### 权限相关
- **403 无权限**
- 原因:调用 `/user/**` 的用户不是管理员。
- 排查:检查当前登录用户 `isAdmin` 是否为 1;检查前端是否误把普通用户引导到用户管理入口。
- 处理:切换到管理员账号或移除普通用户对用户管理的访问。
**章节来源**
- [SysUserService.java:389-398](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L389-L398)
- [UserController.java:18-88](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/UserController.java#L18-L88)
### 系统与服务
- **503 或服务不可用**
- 原因:网关不可达、后端进程未启动、数据库或 Redis/Elasticsearch 不可用。
- 排查:先调 `/sys/health`;再调 `/sys/serviceHealth` 查看各子服务状态;检查部署环境配置。
- 处理:重启后端服务、修复数据库连接、检查网关路由。
- **404 文件下载或地图样式缺失**
- 原因:文件 ID 不存在、离线地图样式未配置。
- 排查:确认 `fid` 是否正确;检查系统配置的离线地图目录是否存在样式文件。
- 处理:前端回退到内置样式或提示资源缺失。
**章节来源**
- [SystemController.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/controller/SystemController.java#L1-L200)
### 登录失效与缓存清理
- **登录失效**
- 现象:后续请求返回 401。
- 前端行为:清除 localStorage 中的 token、用户信息、当前案件缓存,跳转到登录页。
- **账号被顶下线**
- 触发条件:生产默认单端登录;新登录会替换旧 token。
- 后端行为:旧 token 下次请求抛未登录异常;若因强制登录接管,旧会话打开的案件会被释放。
- 前端行为:收到 401 后清理缓存并提示重新登录。
- **管理员重置密码或强制下线**
- 后端行为:调用 `StpUtil.kickout`,释放该用户打开的案件数据源和缓存。
- 前端行为:后续请求 401,清理缓存并跳转登录。
**章节来源**
- [SysUserService.java:118-200](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L118-L200)
- [SysUserService.java:201-429](file://ai-server/src/main/java/com/zsjz/ai/module/plat/service/SysUserService.java#L201-L429)
- [GlobalExceptionHandler.java:54-70](file://ai-server/src/main/java/com/zsjz/ai/common/exception/GlobalExceptionHandler.java#L54-L70)
## 结论
plat 模块的认证与系统基础接口以 Sa-Token 为核心,结合登录活跃检测实现“账号占用确认”和“强制登录”,并通过 `x-token` 请求头维持会话。用户管理接口仅限管理员,案件接口按 owner_id 隔离,表模板和塔台接口分别承担数据建模与基站处理能力。前端应在登录后持久化 token,在 401、403、40901 等场景下正确处理缓存清理和用户引导。运维侧可通过 `/sys/health` 和 `/sys/serviceHealth` 快速定位后端及依赖服务状态。