文档优化:将核心架构与流程字符图转换为Mermaid

This commit is contained in:
wangzhibiao committed 2026-09-18 09:36:39 +08:00
1 parent 8b54dee004
commit 18a3bfb50e
12 files changed
+141 -244

No files matched your search

@@ -77,28 +77,25 @@
当前系统必须在概念模型与后续实现中**严格切分以下三个业务对象**,严禁混为一谈:
```
+-----------------------------------------------------------------------------+
| A. 物理宿舍资源 (Dormitory Resource - train_dormitory_info) |
| - 物理属性:楼栋、楼层、房间号、核定床位容量、适用性别、房间可用/停用状态 |
| - 生命周期:独立于任何培训班和学员,长期相对稳定 |
+-----------------------------------------------------------------------------+
▲
│ 1 间宿舍可容纳多个住宿安排
+-----------------------------------------------------------------------------+
| B. 住宿业务事实 (Accommodation - train_accommodation) |
| - 业务属性:某个学员参训档案 (enrollment_id) 在本次开班中分配到了哪个房间与床位 |
| - 状态流转:预分配 (0) -> 已入住 (1) -> 已退宿 (2) |
| - 事实归属:RailwaySystem 是本业务的绝对 Source of Truth (事实唯一真源) |
+-----------------------------------------------------------------------------+
▲
│ 1 条住宿事实产生门禁授权要求
+-----------------------------------------------------------------------------+
| C. 门锁设备授权 (Door Authorization - 门禁授权模型) |
| - 设备属性:基于该住宿事实,向 DMS 门锁下发的有效时间区间与电子钥匙凭据 (密码/卡片) |
| - 状态流转:待下发 -> 平台已受理 -> 设备执行中 -> 设备生效 -> 撤销中 -> 已注销 |
| - 属性特征:依赖外部网络与硬件状态,可异步重试,失败不推翻住宿业务事实 |
+-----------------------------------------------------------------------------+
```mermaid
flowchart TB
subgraph LayerA["A. 物理宿舍资源 (train_dormitory_info)"]
direction TB
A1["物理属性:楼栋、楼层、房间号、核定床位容量、适用性别、房间可用/停用状态<br/>生命周期:独立于任何培训班和学员,长期相对稳定"]
end
subgraph LayerB["B. 住宿业务事实 (train_accommodation)"]
direction TB
B1["业务属性:学员参训档案 (enrollment_id) 在本次开班中分配到的房间与床位<br/>状态流转:预分配 (0) -> 已入住 (1) -> 已退宿 (2)<br/>事实归属:RailwaySystem 是本业务的绝对 Source of Truth (事实唯一真源)"]
end
subgraph LayerC["C. 门锁设备授权 (门禁授权模型)"]
direction TB
C1["设备属性:向 DMS 门锁下发的有效时间区间与电子钥匙凭据 (密码/卡片)<br/>状态流转:待下发 -> 平台已受理 -> 设备执行中 -> 设备生效 -> 撤销中 -> 已注销<br/>属性特征:依赖外部网络与硬件状态,可异步重试,失败不推翻住宿业务事实"]
end
LayerB -->|"1 间宿舍可容纳多个住宿安排"| LayerA
LayerC -->|"1 条住宿事实产生门禁授权要求"| LayerB
```
---
@@ -113,26 +113,16 @@ sequenceDiagram
当由于外部原因(例如运维人员在第三方控制台手动注销、或 DMS 服务端异常重启导致 Session 丢失)出现 `401 Unauthorized` 时的处理规范:
```
业务请求触发调用
│
▼
使用本地 Token 调用 DMS API
│
├─────────────────────────────────┐
▼ [成功 200 OK] ▼ [失败 401 Unauthorized]
正常返回业务数据 立即令本地缓存的 Token 失效 (invalidate)
通知 TokenManager.touch() 延展寿命 │
▼
单飞排他锁获取新 Token (Single-Flight Login)
│
├─────────────────────────┐
▼ [重登录成功] ▼ [重登录失败]
安全重试原始业务请求 (最多 1 次) 标记操作失败 (FAILED)
│ 记录审计日志
├──────────────┐ 不影响本地住宿业务事实
▼ [重试成功] ▼ [依然401]
成功返回 熔断抛出异常
```mermaid
flowchart TD
Start["业务请求触发调用"] --> CallDms["使用本地 Token 调用 DMS API"]
CallDms -->|"成功 200 OK"| Success1["正常返回业务数据<br/>通知 TokenManager.touch() 延展寿命"]
CallDms -->|"失败 401 Unauthorized"| Invalidate["立即令本地缓存的 Token 失效 (invalidate)"]
Invalidate --> SingleFlight["单飞排他锁获取新 Token (Single-Flight Login)"]
SingleFlight -->|"重登录成功"| Retry["安全重试原始业务请求 (最多 1 次)"]
SingleFlight -->|"重登录失败"| LoginFail["标记操作失败 (FAILED)<br/>记录审计日志<br/>不影响本地住宿业务事实"]
Retry -->|"重试成功"| Success2["成功返回"]
Retry -->|"依然 401"| CircuitBreak["熔断抛出异常"]
```
### 5.1 恢复红线守则
@@ -11,22 +11,11 @@
- 官方文档明示:**“门锁的实际执行结果,务必在调用相关操作后 10 秒查询”**。
- **严重红线**:绝对禁止在 Web 请求线程(Controller/Service)中执行 `Thread.sleep(10000)`!这会迅速耗尽 Tomcat 工作线程池,引发系统雪崩。
- **轮询架构设计**:
```
Web 请求 (如确认入住)
│
▼
本地持久化 Accommodation (已入住) 与 DoorAuthorization (ISSUING)
提交本地数据库事务 (Commit Local DB)
│
▼
异步派发调用 DMS 创建钥匙 -> 返回 operationId (耗时 ~200ms)
本地记录 ExternalOperationRecord (status = ACCEPTED, next_poll_time = now + 10s)
快速向前端返回 HTTP 200 (附带提示:“住宿已分配,门锁授权下发中”)
│
▼
后台调度引擎 (Quartz / Spring Scheduled ThreadPool - 专用IO线程池)
扫描 next_poll_time <= NOW() 的任务 -> 调用 /doorlock/query-operation-status
更新操作记录与授权记录状态 -> 若为 success 则推为 ACTIVE
```mermaid
flowchart TD
Req["Web 请求 (如确认入住)"] --> LocalDB["本地持久化 Accommodation (已入住) 与 DoorAuthorization (ISSUING)<br/>提交本地数据库事务 (Commit Local DB)"]
LocalDB --> AsyncDms["异步派发调用 DMS 创建钥匙 -> 返回 operationId (耗时 ~200ms)<br/>本地记录 ExternalOperationRecord (status = ACCEPTED, next_poll_time = now + 10s)<br/>快速向前端返回 HTTP 200 (附带提示:“住宿已分配,门锁授权下发中”)"]
AsyncDms --> Scheduler["后台调度引擎 (Quartz / Spring Scheduled ThreadPool - 专用IO线程池)<br/>扫描 next_poll_time <= NOW() 的任务 -> 调用 /doorlock/query-operation-status<br/>更新操作记录与授权记录状态 -> 若为 success 则推为 ACTIVE"]
```
### 1.2 门锁 vs 网关轮询参数隔离
@@ -35,20 +35,12 @@
官方文档对在线卡片(`POST /rest/doorlock/online-card/create`)的卡号规定了非常特殊的**小端字节序十六进制转换规则**:
```
[读卡器读取 10 进制原始卡号]
│
▼ 例: 3161082660
[转成标准 16 进制大端串]
│
▼ 例: BC 6A 4B 24
[不足 8 位时高位补零]
│
▼ 例: 09 99 ED 24
[低位在前的字节序逆序转换 (Little-Endian)]
│
▼ 例: 24 4B 6A BC (或 24 ED 99 09)
[送入 DMS API 的 cardNumber 参数]
```mermaid
flowchart TD
Step1["读卡器读取 10 进制原始卡号"] -->|"例: 3161082660"| Step2["转成标准 16 进制大端串"]
Step2 -->|"例: BC 6A 4B 24"| Step3["不足 8 位时高位补零"]
Step3 -->|"例: 09 99 ED 24"| Step4["低位在前的字节序逆序转换 (Little-Endian)"]
Step4 -->|"例: 24 4B 6A BC 或 24 ED 99 09"| Step5["送入 DMS API 的 cardNumber 参数"]
```
### 2.1 架构封装纪律
@@ -96,23 +96,13 @@
## 5. DMS 资源映射与同步管理界面设计 (`Resource Sync UI`)
### 5.1 房源对齐工作流
```
[管理员进入"门锁资源映射"界面]
│
▼
[点击"从DMS拉取房源目录"]
│
▼ 适配器调用 POST /rest/place/room/list
[系统解析出 DMS 的 Building, Floor, Room, placeId, 挂载的 lockId, 硬件SN]
│
▼
[系统依据"楼栋-楼层-房间"名称执行规则预匹配]
│
▼
[管理员在界面核对预配对结果, 确认手动微调后点击"确认持久化绑定"]
│
▼
[写入 train_external_resource_mapping, 完成两套系统物理基线的稳定锁定]
```mermaid
flowchart TD
Step1["管理员进入“门锁资源映射”界面"] --> Step2["点击“从 DMS 拉取房源目录”"]
Step2 -->|"适配器调用 POST /rest/place/room/list"| Step3["系统解析出 DMS 的 Building, Floor, Room, placeId, 挂载的 lockId, 硬件SN"]
Step3 --> Step4["系统依据“楼栋-楼层-房间”名称执行规则预匹配"]
Step4 --> Step5["管理员在界面核对预配对结果,确认手动微调后点击“确认持久化绑定”"]
Step5 --> Step6["写入 train_external_resource_mapping,完成两套系统物理基线的稳定锁定"]
```
- **核心保障**:同步操作仅建立与更新映射表,**绝不自动删除或篡改本地 `train_dormitory_info` 的基础房源台账**。本地房源依然是学员管理系统的独立主数据。
@@ -8,16 +8,16 @@
## 1. 分层测试策略体系 (Testing Strategy)
```
+--------------------------------------------------------------------------------+
| L4. 现场沙箱验收测试 (Live Sandbox E2E - 独立测试账号 + 实验台独立挂载测试锁) |
+--------------------------------------------------------------------------------+
| L3. 契约与故障注入测试 (WireMock Failure Injection - 401/超时/宕机/乱序状态) |
+--------------------------------------------------------------------------------+
| L2. 并发与状态机集成测试 (Concurrency & State Machine Test - 20并发Token竞争/幂等) |
+--------------------------------------------------------------------------------+
| L1. 纯领域单元测试 (Unit Test - CardNumberCodec 编码 / 状态机转换 / DTO 转换) |
+--------------------------------------------------------------------------------+
```mermaid
flowchart BT
L1["L1. 纯领域单元测试<br/>Unit Test - CardNumberCodec 编码 / 状态机转换 / DTO 转换"]
L2["L2. 并发与状态机集成测试<br/>Concurrency & State Machine Test - 20并发Token竞争/幂等"]
L3["L3. 契约与故障注入测试<br/>WireMock Failure Injection - 401/超时/宕机/乱序状态"]
L4["L4. 现场沙箱验收测试<br/>Live Sandbox E2E - 独立测试账号 + 实验台独立挂载测试锁"]
L1 --> L2
L2 --> L3
L3 --> L4
```
---
+14 -17
View File
@@ -75,23 +75,20 @@
## 5. 推荐技术架构设计
```
[前端 Accommodation / 个人中心]
│
▼
[业务领域层: 住宿分配 / 换房补偿 / 报到联动] <--- 绝对 Source of Truth
│
▼ (通过强类型 DTO,完全屏蔽外部厂商细节)
[中立网关接口: DoorAccessGateway (grant / revoke / query)]
│
▼ (SPI 模式)
[DMS 集成适配器: DmsDoorAccessGateway]
├── DmsTokenManager: 进程级单飞排他锁 (防并发覆盖踩踏)
├── 细分 HTTP 客户端: PlaceClient / DoorLockClient / KeyClient / OpClient
└── DmsOperationPoller: 后台 10 秒对账调度器 (解除 HTTP 线程阻塞)
│
▼ (HTTPS / Cookie 传递 token)
[外部 DMS 物联网平台: https://dms.dmsiot.com]
```mermaid
flowchart TD
UI["前端 Accommodation / 个人中心"] --> Domain["业务领域层: 住宿分配 / 换房补偿 / 报到联动<br/>(绝对 Source of Truth)"]
Domain -->|"通过强类型 DTO,屏蔽外部厂商细节"| Gateway["中立网关接口: DoorAccessGateway<br/>(grant / revoke / query)"]
subgraph Adapter["DMS 集成适配器: DmsDoorAccessGateway (SPI 模式)"]
direction TB
TM["DmsTokenManager: 进程级单飞排他锁 (防并发覆盖踩踏)"]
Clients["细分 HTTP 客户端: PlaceClient / DoorLockClient / KeyClient / OpClient"]
Poller["DmsOperationPoller: 后台 10 秒对账调度器 (解除 HTTP 线程阻塞)"]
end
Gateway --> Adapter
Adapter -->|"HTTPS / Cookie 传递 token"| External["外部 DMS 物联网平台: https://dms.dmsiot.com"]
```
---
@@ -10,27 +10,20 @@
在多角色(Admin, Teacher, Student)登录体系中,数据范围安全必须建立在**零信任前端参数**的基石之上:
```
+─────────────────────────────────────────────────────────────────────────────+
| 零信任推导核心防线 (Secure Chain) |
| |
| 客户端 HTTP Request |
| │ (仅携带 Bearer JWT Token,禁止/忽略 query 或 body 中的 identityId) |
| ▼ |
| Spring Security Filter: JwtAuthenticationTokenFilter |
| │ (验签并提取 claims 中的 userId) |
| ▼ |
| SecurityContextHolder.getContext().getAuthentication() |
| │ (获取受信任的 LoginUser.getUserId()) |
| ▼ |
| Service 内部数据推导 (SecurityContextHelper) |
| ├─ 若拥有 train_teacher 角色: |
| │ SELECT t_id FROM train_teacher WHERE user_id = #{userId} |
| ├─ 若拥有 train_student 角色: |
| │ SELECT s_id FROM train_student WHERE user_id = #{userId} |
| └─ 若拥有 admin / train_admin 角色: |
| 按管理员角色规则放行或基于组织机构过滤 |
+─────────────────────────────────────────────────────────────────────────────+
```mermaid
flowchart TD
Req["客户端 HTTP Request<br/>(仅携带 Bearer JWT Token,禁止/忽略 query 或 body 中的 identityId)"]
Filter["Spring Security Filter: JwtAuthenticationTokenFilter<br/>(验签并提取 claims 中的 userId)"]
SecCtx["SecurityContextHolder.getContext().getAuthentication()<br/>(获取受信任的 LoginUser.getUserId())"]
Helper["Service 内部数据推导 (SecurityContextHelper)"]
Req --> Filter
Filter --> SecCtx
SecCtx --> Helper
Helper -->|"拥有 train_teacher 角色"| TeacherRole["SELECT t_id FROM train_teacher WHERE user_id = #{userId}"]
Helper -->|"拥有 train_student 角色"| StudentRole["SELECT s_id FROM train_student WHERE user_id = #{userId}"]
Helper -->|"拥有 admin / train_admin 角色"| AdminRole["按管理员角色规则放行或基于组织机构过滤"]
```
### 1.1 核心铁律
@@ -44,30 +37,16 @@
教师登录后,其可访问的所有教学资源必须严格基于其受聘授课关系动态推导:
```
┌──────────────────────┐
│ sys_user (userId) │
└──────────┬───────────┘
│ WHERE user_id = #{userId}
▼
┌──────────────────────┐
│ train_teacher (t_id)│
└──────────┬───────────┘
│
┌───────────────────────┼────────────────────────┐
│ (排课直接授课) │ (计划课程关联) │ (关联教学班级)
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│timetable_lesson │ │train_plan_course │ │ train_class_plan │
│WHERE teacher_id │ │WHERE teacher_id │ │ WHERE plan_id IN │
│ = #{t_id} │ │ = #{t_id} │ │ (授课班级集合) │
└────────┬─────────┘ └──────────────────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│student_checkin │ │training_enrollment
│(本人课次考勤流水) │ │(我带的班级学员名单)│
└──────────────────┘ └──────────────────┘
```mermaid
flowchart TD
User["sys_user (userId)"] -->|"WHERE user_id = #{userId}"| Teacher["train_teacher (t_id)"]
Teacher -->|"排课直接授课"| Lesson["timetable_lesson<br/>WHERE teacher_id = #{t_id}"]
Teacher -->|"计划课程关联"| PlanCourse["train_plan_course<br/>WHERE teacher_id = #{t_id}"]
Teacher -->|"关联教学班级"| ClassPlan["train_class_plan<br/>WHERE plan_id IN (授课班级集合)"]
Lesson --> Checkin["student_checkin<br/>(本人课次考勤流水)"]
ClassPlan --> Enrollment["training_enrollment<br/>(我带的班级学员名单)"]
```
- **授课课表数据**: 仅能查到 `train_timetable_lesson.teacher_id = #{currentTeacherId}` 的课次。
@@ -80,29 +59,17 @@
学员登录后,进入极简个人培训中心,数据范围完全收敛于“本人”:
```
┌──────────────────────┐
│ sys_user (userId) │
└──────────┬───────────┘
│ WHERE user_id = #{userId}
▼
┌──────────────────────┐
│ train_student (s_id)│
└──────────┬───────────┘
│
┌─────────────┬───────────────┼───────────────┬─────────────┐
▼ ▼ ▼ ▼ ▼
┌───────┐ ┌──────────────┐ ┌───────────┐ ┌──────────────┐ ┌────────────┐
│ 个人 │ │ 参训班级名单 │ │ 个人报到 │ │ 个人考勤记录 │ │ 个人画像/ │
│ 档案 │ │ Enrollment │ │ Report │ │ Checkin │ │ 学时累计 │
│(只读) │ │WHERE s_id=...│ │WHERE s_id │ │WHERE s_id=...│ │WHERE s_id │
└───────┘ └──────┬───────┘ └───────────┘ └──────────────┘ └────────────┘
│
▼
┌──────────────┐
│ 班级参训课表 │
│(仅限正常在训)│
└──────────────┘
```mermaid
flowchart TD
User["sys_user (userId)"] -->|"WHERE user_id = #{userId}"| Student["train_student (s_id)"]
Student --> Profile["个人档案 (只读)"]
Student --> Enrollment["参训班级名单 (Enrollment)<br/>WHERE s_id = ..."]
Student --> Report["个人报到 (Report)<br/>WHERE s_id = ..."]
Student --> Checkin["个人考勤记录 (Checkin)<br/>WHERE s_id = ..."]
Student --> Portrait["个人画像 / 学时累计<br/>WHERE s_id = ..."]
Enrollment --> Timetable["班级参训课表<br/>(仅限正常在训)"]
```
- **学员档案**: 仅能读取与更新本人通讯电话、邮箱。
+13 -20
View File
@@ -35,26 +35,19 @@
系统全员使用同一个登录入口 `http://192.168.139.230:8082/login`。用户验证成功颁发 JWT 后,前端路由守卫(`permission.js`)执行如下分流重定向策略:
```
用户输入账号密码
│
▼
POST /login (200 OK)
│
▼
GET /getInfo (拉取 roles 与 permissions)
│
▼
角色判定 (Role Branching)
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
拥有 admin 或 train_admin 拥有 train_teacher 拥有 train_student
│ │ │
▼ ▼ ▼
Landing 重定向至: Landing 重定向至: Landing 重定向至:
/index /train/teacher/dashboard /train/student/portal
(系统综合驾驶舱) (教师授课工作台) (学员个人培训中心)
```mermaid
flowchart TD
LoginInput["用户输入账号密码"] --> PostLogin["POST /login (200 OK)"]
PostLogin --> GetInfo["GET /getInfo (拉取 roles 与 permissions)"]
GetInfo --> RoleBranch["角色判定 (Role Branching)"]
RoleBranch --> BranchAdmin["拥有 admin 或 train_admin"]
RoleBranch --> BranchTeacher["拥有 train_teacher"]
RoleBranch --> BranchStudent["拥有 train_student"]
BranchAdmin --> LandingAdmin["Landing 重定向至:<br/>/index (系统综合驾驶舱)"]
BranchTeacher --> LandingTeacher["Landing 重定向至:<br/>/train/teacher/dashboard (教师授课工作台)"]
BranchStudent --> LandingStudent["Landing 重定向至:<br/>/train/student/portal (学员个人培训中心)"]
```
- **兜底方案 (Fallback)**: 若用户被赋予复合角色或自定义角色,前端自动跳转至 `asyncRoutes` 生成的动态路由树的第一个叶子节点页面,杜绝 404 或白屏。