面向患者的医院门户网站,当前服务于眼科门诊。
┌──────────────────┐ HTTPS/fetch ┌─────────────────────┐ MyBatis ┌───────────┐
│ Vue 3 SPA │ ──────────────────▶ │ Spring Boot API │ ──────────▶ │ MySQL │
│ (Vite :5173) │ │ (HTTPS :8080) │ │ his_db │
└──────────────────┘ └─────────────────────┘ └───────────┘
│ │ (port 3306)
│ Vite 代理 /auth /patients │ 依赖
│ /appointments → :8080 ┌──────────┴──────────┐
│ │ UidProvider (1.3) │
│ Mock 模式可用: └─────────────────────┘
│ VITE_DATA_SOURCE=mock 发布至本地 Maven 仓库
│ 完全绕过后端 (G:/CustomMavenRepo)
Mock 模式:
npm run dev:mock使用内存 Mock 数据,完全脱离后端。启动后自动注入硬编码用户(赵六,uid:P7V455QZ2EC6S772621)和生成的 JWT。
| 层级 | 技术 | 版本 |
|---|---|---|
| 前端框架 | Vue 3 + Composition API | 3.5 |
| 前端语言 | TypeScript | 6.0 |
| 构建工具 | Vite | 8.0 |
| UI 组件库 | Naive UI | 2.44 |
| CSS 框架 | Tailwind CSS | 4.3 |
| 状态管理 | Pinia | 3.0 |
| 路由 | Vue Router | 5.1 |
| 后端框架 | Spring Boot | 4.0.5 |
| 后端语言 | Java | 25 |
| ORM | MyBatis(纯注解,无 XML) | 4.0 |
| 认证 | 自研 JWT Filter + @CurrentUserUid | jose4j 0.9.6 |
| 密码校验 | Passay | 1.6 |
| 工具库 | Lombok、Hutool、dayjs | — |
| 数据库 | MySQL | 8.0+ |
- JDK 25、Maven 3.9+、Node.js 18+、MySQL 8.0+
- 在系统
hosts文件中添加127.0.0.1 dev.test - 复制
Server/src/main/resources/application.yaml.template为application.yaml,填入数据库用户名密码
mysql -u root -p < Db/CreateDb.sql # 创建数据库与表
mysql -u root -p his_db < Db/DbViews.sql # 创建视图
mysql -u root -p his_db < Db/DataGen.sql # 导入种子数据(5名医生、5个诊室)注意:
Db/DataGen.sql当前使用旧版表结构(consulting_room_info、doctor_profile),在新版CreateDb.sql(consulting_rooms、doctors)下可能报错,需要手动适配或使用新版种子数据。
cd UidProvider && mvn clean install # UID 生成器
cd mqc_provider && mvn clean install # 医师执业证号生成器(如果存在)cd Server
mvn clean package # 构建 Fat JAR(版本 0.1.6)
mvn spring-boot:run # 开发服务器(热重载,端口 8080)后端测试:
mvn test # 运行全部测试
mvn test -Dtest=LoginControllerTest # 运行单个测试类cd Web/frontend
npm install
npm run dev # 连接真实后端(HTTPS,端口 5173)
# 或
npm run dev:mock # Mock 模式,无需后端即可完整开发浏览器访问 https://dev.test:5173。
| 命令 | 说明 |
|---|---|
cd Server && mvn clean package |
构建后端 fat JAR |
cd Server && mvn spring-boot:run |
启动后端开发服务器(热重载) |
cd Server && mvn test |
运行全部后端测试 |
cd Server && mvn test -Dtest=ClassName |
运行指定测试类 |
cd Web/frontend && npm run dev |
启动前端(连接真实后端) |
cd Web/frontend && npm run dev:mock |
启动前端(Mock 数据,无需后端) |
cd Web/frontend && npm run build |
生产构建(类型检查 + 代码压缩) |
cd Web/frontend && npm run build:local |
开发构建(不压缩,开启 sourcemap) |
cd Web/frontend && npm run preview |
预览生产构建 |
cd UidProvider && mvn clean install |
构建共享库并发布到本地 Maven 仓库 |
| 方法 | 路径 | 认证 | 说明 |
|---|---|---|---|
| POST | /auth/patients |
— | 注册患者,自动登录 → JWT |
| POST | /auth/tokens |
— | 邮箱 + 密码登录 → JWT |
| GET | /patients/profile |
JWT | 患者档案(姓名、脱敏身份证、邮箱) |
| PUT | /patients/profile |
JWT | 更新患者档案(邮箱) |
| GET | /patients/doctors |
— | 可挂号医师列表(公开) |
| GET | /patients/appointments?type=active|history |
JWT | 预约列表 |
| GET | /patients/appointments/:ticketNo |
JWT | 预约详情 |
| POST | /patients/appointments |
JWT | 创建预约(锁定号源) |
标注「JWT」的端点需在请求头携带
Authorization: Bearer <token>。部分端点(挂号、预约)后端尚未完整实现,当前在前端 Mock 模式下可使用。
登录:
POST /auth/tokens
{ "email": "zhaoliu@test.cn", "password": "Test123456,." }
→ {
"code": 200,
"message": "登录成功",
"data": {
"uid": "P7V455QZ2EC6S772621",
"role": 0,
"token": "eyJhbGciOiJIUzI1NiJ9...",
"expiresAt": "2026-05-19T14:30:00",
"name": "赵六"
}
}注册:
POST /auth/patients
{ "name": "张三", "identityCard": "110101199001011234",
"email": "zhangsan@test.cn", "password": "Test123456,.", "role": 0 }
→ {
"code": 200,
"message": "注册成功",
"data": {
"uid": "PAAAAAAAABBBBCCCC",
"role": 0,
"token": "eyJhbGciOiJIUzI1NiJ9...",
"expiresAt": "2026-05-19T15:00:00",
"name": "张三"
}
}患者档案:
GET /patients/profile
Authorization: Bearer <token>
→ {
"code": 200,
"data": {
"name": "赵六",
"maskedIdentityCard": "450802********7217",
"email": "zhaoliu@test.cn"
}
}HospitalInformationSystem/
├── Server/ ← [子模块] Spring Boot 后端
│ └── src/main/java/cn/snowdreamsblog/server/
│ ├── ServerApplication.java
│ ├── auth/ ← 认证模块(注册 + 登录)
│ │ ├── controller/AuthController.java
│ │ ├── service/AuthService.java
│ │ ├── mapper/AuthMapper.java
│ │ └── dto/ ← AuthCredentialData、RegisterRequest
│ ├── common/
│ │ ├── annotation/CurrentUserUid.java ← 自定义参数注解
│ │ ├── resolver/CurrentUserUidResolver.java ← 参数解析器(从 Request 提取 uid)
│ │ ├── exception/ ← ApiException + GlobalExceptionHandler
│ │ └── result/ ← CommonResult<T> + ResultCode
│ ├── config/WebMvcConfig.java ← 统一 CORS + 参数解析器注册
│ ├── entity/ ← Users、Patients、RegistrationRecord
│ ├── filter/JwtAuthenticationFilter.java ← JWT 认证过滤器(手动白名单)
│ └── utils/jwt/ ← JWT 生成/解析(jose4j)+ 密码校验(Passay)
│
├── Web/ ← [子模块] 前端项目
│ └── frontend/
│ ├── src/
│ │ ├── main.ts ← 入口:Pinia + Router beforeEach 守卫
│ │ ├── App.vue ← 根组件(NConfigProvider + 暖棕色主题)
│ │ ├── api/ ← fetch 封装(JWT 注入、401 登出、Mock 切换)
│ │ │ ├── index.ts ← request<T>() 核心请求函数
│ │ │ ├── auth.ts ← 注册/登录
│ │ │ ├── patient.ts ← 档案/预约列表/预约详情
│ │ │ ├── appointment.ts ← 医生列表/创建预约
│ │ │ └── _mock/ ← Mock 实现(mock 模式下使用)
│ │ ├── types/api.ts ← TypeScript 接口定义
│ │ ├── router/index.ts ← 7 条嵌套路由 + beforeEnter 预约守卫
│ │ ├── stores/ ← Pinia 状态管理
│ │ │ ├── user.ts ← 认证状态 + 档案 + Mock 会话注入
│ │ │ └── appointment.ts ← 预约流程 selectedDoctor
│ │ ├── views/ ← 7 个页面组件
│ │ ├── components/ ← Header/Navbar/Title/Footer
│ │ └── utils/ ← isTokenExpired、naiveDiscreteApi
│ ├── env/ ← 环境变量(.development / .mock / .production)
│ └── vite.config.ts ← HTTPS + 代理 + 自动导入 + Tailwind v4
│
├── Db/ ← [子模块] 数据库脚本
│ ├── CreateDb.sql ← 建库建表(新版)
│ ├── DbViews.sql ← 视图(需适配新版表结构)
│ └── DataGen.sql ← 种子数据(使用旧版表结构)
│
├── UidProvider/ ← UID 生成共享库
├── mqc_provider/ ← 医师资格证编码生成库
└── Db_data/ ← CSV 格式种子数据
基础包:cn.snowdreamsblog.server
auth/ ← 认证模块(注册 + 登录合一)
controller/AuthController.java ← POST /auth/patients、POST /auth/tokens
service/AuthService.java ← 合并注册与登录逻辑
mapper/AuthMapper.java ← users + patients 表 CRUD(纯注解 SQL)
dto/ ← AuthCredentialData、RegisterRequest
common/
annotation/CurrentUserUid.java ← @CurrentUserUid 自定义参数注解
resolver/CurrentUserUidResolver ← HandlerMethodArgumentResolver,从 Request 属性提取 uid
exception/ ← ApiException(code + message)、GlobalExceptionHandler
result/ ← CommonResult<T> 统一响应 + ResultCode 状态码枚举
config/WebMvcConfig.java ← 统一 CORS 配置 + CurrentUserUidResolver 注册
entity/
Users.java ← 核心用户表(id BIGINT 内部主键、uid 业务 ID、email、password、role)
Patients.java ← 患者扩展表(user_id → users.id,CASCADE 删除)
RegistrationRecord.java ← 挂号记录表(含 uid、ticket_no、快照字段)
filter/JwtAuthenticationFilter ← 手动 JWT 过滤器(实现 Filter 接口,白名单放行 /auth/** 和 /patients/doctors)
utils/jwt/ ← JWT 签发/校验(jose4j HMAC-SHA256)、JwtPayloadSettings
utils/pwd/ ← Passay 密码强度规则(8-16 字符,大写+小写+数字+特殊字符)
1. 统一响应
所有 API 返回 CommonResult<T>,结构为 {code, message, data}。GlobalExceptionHandler 统一捕获 ApiException 和通用 Exception。
2. JWT 认证流程(非 Spring Security)
项目已从 Spring Security 重构为手动 Filter 方案:
JwtAuthenticationFilter实现Filter接口,在doFilter中手动校验白名单(/auth/**、/error、/patients/doctors直接放行)- 有效 Token 的 uid 和 role 通过
request.setAttribute()存入 Request - Controller 通过
@CurrentUserUid String uid注解(CurrentUserUidResolver自动注入)获取当前用户 - JWT 有效期 3600 秒(60 分钟),通过 jose4j HMAC-SHA256 签名
3. UID 生成策略
内部 UidGenerator 库生成 32 字符唯一 ID:先插入临时 UID 获取自增主键序号 → 用序号 + 时间戳 + 密钥生成最终 UID → 回写。
4. 按业务域分包
认证模块(auth/)内聚 Controller、Service、Mapper、DTO,避免按技术层横向拆分。
5. MyBatis 纯注解 SQL
无 XML Mapper 文件,所有 SQL 通过 @Select、@Insert、@Update 注解直接写在 Mapper 接口中。
6. DTO 使用 POJO
AuthCredentialData 和 RegisterRequest 使用 Lombok @Data 注解的普通 POJO(非 Java record),请求体通过 @Validated 校验。
| 模式 | VITE_DATA_SOURCE |
行为 |
|---|---|---|
dev(默认) |
api |
Vite 代理 /auth、/patients 到后端 :8080 |
mock |
mock |
使用内存 Mock 数据,__USE_MOCK_API__ 编译时常量切换 |
production |
api |
直接请求 VITE_API_BASE_URL(无代理) |
request<T>() 核心函数:
- 自动注入
Authorization: Bearer <token>请求头 - 解析
CommonResult<T>→ 自动解包data字段 - 401 → 自动登出;403 → 区分代理层/应用层错误
- 编译时常量
__USE_MOCK_API__控制 mock/api 切换(零运行时开销)
嵌套路由:根路径 / 使用 home/index.vue 作为布局外壳(Header + <RouterView/> + Footer),子路由渲染在 <RouterView/> 中。
| 路径 | 组件 | 需登录 | 说明 |
|---|---|---|---|
/home |
MainPage.vue | — | 仪表盘:公告、快捷链接、排班预览 |
/register |
Register.vue | — | 建档(含密码强度指示器) |
/login |
Login.vue | — | 登录 |
/appointment |
Appointment.vue | ✓ | 选择医生 + 号源 |
/appointment/confirm |
AppointmentConfirm.vue | ✓ | 确认预约(beforeEnter 守卫) |
/appointments/:ticketNo |
AppointmentDetail.vue | ✓ | 预约详情(以 ticketNo 为参数) |
/profile |
Profile.vue | ✓ | 电子档案 |
路由守卫:
beforeEach:token 过期检查、登录后自动加载档案、requiresAuth鉴权beforeEnter(confirm):检查selectedDoctor(Pinia)或sessionStorage(刷新恢复)
- user Store:token/uid/name 持久化于 localStorage、登录/注册/登出、档案加载与刷新、Mock 会话注入
- appointment Store:多步骤预约流程的
selectedDoctor状态、doctorAvatarInitial计算属性
- Naive UI:通过
unplugin-vue-components自动按需导入,unplugin-auto-import自动导入 Vue API - Tailwind CSS v4:通过
@tailwindcss/vite插件集成,无 tailwind.config 文件 - dayjs:日期格式化与计算
- password-validator:前端密码强度实时校验
数据库:his_db,字符集 utf8mb4,引擎 InnoDB。
| 表名 | 主键 | 说明 |
|---|---|---|
users |
id BIGINT 自增 |
核心用户认证:uid(业务 ID,UNIQUE)、uid_salt、email、password、role(0=患者,1=医生,2=管理员)、created_at、updated_at |
patients |
user_id BIGINT |
患者档案扩展:关联 users.id(CASCADE 删除)、name、gender、identity_card(UNIQUE)、birthday、last_login_at |
doctors |
user_id BIGINT |
医生档案扩展:关联 users.id(CASCADE 删除)、doctor_code(执业证号,UNIQUE)、name、title、consulting_room_id、reception_limit、outcall_start_time、outcall_end_time、entry_date |
consulting_rooms |
id BIGINT 自增 |
诊室信息:uid(业务 ID)、code(字母编号,如 "A")、name、position |
doctor_daily_sequences |
(doctor_id, visit_date) 联合主键 |
医生每日排队序号计数器:current_seq |
registration_records |
id BIGINT 自增 |
挂号记录:uid(业务 ID)、ticket_no(业务单号)、patient_id、doctor_id、visit_date、sequence_num、快照字段(doctor/room name/title/position)、fee_amount、visit_status(0-3)、arrive_time |
- 内部 ID 与业务 ID 分离:所有表使用 BIGINT
id作为内部主键,uid(VARCHAR(32))作为对外业务标识 - users 统一用户表:替代旧
auth表,通过role区分患者/医生/管理员 - 快照冗余:挂号记录保留预约时刻的医生姓名、职称、诊室名称等快照,防止后续关联表变更导致历史数据失真
- 号源计数器:
doctor_daily_sequences按(医生, 日期)维度独立计数,替代旧的单行cumulative_sequence_number_controller
| 视图名 | 说明 |
|---|---|
slot |
医师 + 诊室联查(⚠ 引用旧版表名 doctor_profile、consulting_room_info,需适配) |
users.id ──(CASCADE)──▶ patients.user_id
users.id ──(CASCADE)──▶ doctors.user_id
users.id ─────────────▶ registration_records.patient_id
users.id ─────────────▶ registration_records.doctor_id
consulting_rooms.id ──▶ doctors.consulting_room_id(SET NULL)
consulting_rooms.id ──▶ registration_records.room_id_snapshot
doctors.user_id ──────▶ doctor_daily_sequences.doctor_id(CASCADE)
- 医生(5名):李伟(A 诊室,08:00-12:00,限 15 号)、王芳(B 诊室,14:00-18:00,限 20 号)、刘强(C 诊室,09:00-17:00,限 25 号)、陈静(D 诊室,08:30-12:30,限 18 号)、赵敏(E 诊室,不限时间,限 30 号)
- 患者(6名):测试密码均为
Test123456,.,邮箱:zhangsan@test.cn、lisi@test.cn、wangwu@test.cn、zhaoliu@test.cn、zhaoqi@test.cn、zhaoba@test.cn Db_data/目录中另有 CSV 格式种子数据
⚠
DataGen.sql仍使用旧版表结构,暂未适配新版users/doctors/consulting_rooms表。
三个核心目录均为 Git 子模块(Server/、Web/、Db/)。对子模块的更改必须先在子模块内提交,然后更新父仓库指向新的提交。
git submodule update --init --recursive # 拉取所有子模块
cd Server && git pull origin main # 更新单个子模块| 功能 | 状态 |
|---|---|
| 患者注册/建档 | ✅ 已完成 |
| 患者登录 | ✅ 已完成 |
| 患者档案查询 + 更新 | ✅ 已完成 |
| 医师列表查询 | ✅ 已完成 |
| 预约挂号(创建预约、锁定号源) | 🔄 后端部分完成 |
| 预约列表 + 预约详情 | 🔄 后端部分完成 |
| Mock 开发模式 | ✅ 已完成 |
| 门诊缴费 | ⏳ 开发中 |
| 报告查询 | ⏳ 开发中 |
| 密码加密哈希 | ❌ 明文存储(已列入 TODO) |
slot 视图适配新版表 |
⚠ 仍引用旧版表名 |
DataGen.sql 适配新版表 |
⚠ 仍使用旧版表结构 |
| 电子档案页 | 🔄 使用 Mock 数据,未完全接入后端 |
| 首页排班速览 | 🔄 硬编码数据 |
- 密码哈希为 TODO:注册服务以明文存储密码,
users表无 salt 字段用于密码加密(uid_salt仅用于 UID 生成) - SSL 当前关闭:
application.yaml中server.ssl.enabled: false;模板中为true,生产环境需启用 - 共享库:
UidProvider必须先mvn clean install到G:/CustomMavenRepo,Server 才能编译 - 非子模块目录:
passay-demo/、totpGenerator/、zxcvbn-demo/、Prototypes/、Gemini对话记录/、Web_Gemini/为探索/演示项目,不属于核心应用 Server使用distributionManagement发布到file://G:/CustomMavenRepo/HIS-System
本节记录了项目从 v1 到 v2 的主要架构变更,供了解演进历史。
| 维度 | v1(旧) | v2(新) |
|---|---|---|
| 用户表 | auth(uid VARCHAR 主键) |
users(id BIGINT 自增主键,uid 为 UNIQUE 业务列) |
| 患者表 | patients(uid VARCHAR 主键,外键→auth.uid) |
patients(user_id BIGINT 主键,外键→users.id) |
| 医生表 | doctor_profile(uid 主键,doctorId 业务列) |
doctors(user_id 主键,doctor_code 业务列,新增 title/entry_date) |
| 诊室表 | consulting_room_info(id VARCHAR(9) 主键) |
consulting_rooms(id BIGINT 自增 + uid VARCHAR(32) 业务列 + code 字母编号) |
| 挂号表 | registration_record(orderId 订单号) |
registration_records(uid 业务 ID + ticket_no 单号 + 快照字段 + fee_amount) |
| 序号器 | cumulative_sequence_number_controller(单行全局) |
doctor_daily_sequences(按医生+日期独立计数) |
动机:将数据库内部自增 ID 与对外暴露的业务 UID 彻底解耦,引入快照字段保证历史数据不受关联表变更影响。
| 维度 | v1(旧) | v2(新) |
|---|---|---|
| 认证框架 | Spring Security + OncePerRequestFilter | 手动 Filter 接口实现 + 白名单 |
| 获取用户 | SecurityContextHolder |
request.setAttribute("currentUserUid") + @CurrentUserUid 注解 |
| 参数注入 | 手动从 Request Header 提取 Token 并解析 | CurrentUserUidResolver 自动注入 |
| CORS 配置 | 独立 CorsConfig.java |
统一 WebMvcConfig.java(CORS + 参数解析器) |
| 异常处理 | 携带 ResultCode 枚举 |
简化为 code(int)+ message,401 单独处理 |
动机:降低 Spring Security 的配置复杂度,减少依赖,使认证流程更透明可控。
| 维度 | v1(旧) | v2(新) |
|---|---|---|
| 包结构 | patient/auth/login/ + patient/auth/register/ 独立分包 |
auth/ 统一模块 |
| Controller | LoginController + RegisterController 两个类 |
AuthController 单一类 |
| Service | LoginService + RegisterService 两个类 |
AuthService 单一类 |
| 响应 DTO | LoginResponse + RegisterResponse(Java record) |
AuthCredentialData(Lombok POJO,含 role 字段) |
| 异常处理 | 登录抛异常,注册返回 null | 统一抛 ApiException |
动机:消除重复代码,统一错误处理策略,简化包结构。
| v1 | v2 |
|---|---|
Auth.java(uid/password/email/salt/role) |
Users.java(id/uid/uidSalt/email/password/role/createdAt/updatedAt) |
UserProfile.java 抽象基类(已移除) |
— |
Patients.java 继承 UserProfile,uid 主键 |
Patients.java 独立 POJO,userId 主键 |
DoctorProfile.java、ConsultingRoomInfo.java、PatientsInsurance.java |
暂未迁移至新版 entity |
| 配置项 | v1 | v2 |
|---|---|---|
| 过期时间 | 1800 秒(30 分钟) | 3600 秒(60 分钟) |
| SSL | 强制启用 | application.yaml 默认关闭(模板仍为启用) |
| 维度 | v1 | v2 |
|---|---|---|
| 预约详情路由 | /appointments/:orderId |
/appointments/:ticketNo(使用 ticketNo 作为标识) |
| 类型系统 | 旧版接口(缺少 doctor title/roomInfo/visitDate 等) | 新版 DoctorInfo(含 title/roomCode/period/remainingSlots/visitDate) |
| 预约类型 | appointment store 缺少 doctorAvatarInitial |
新增 doctorAvatarInitial 计算属性 |
| 路由守卫 | 预约确认仅检查 Pinia | 增加 sessionStorage 兜底(刷新页面后恢复) |
| 新环境变量 | 无 | VITE_ENABLE_ROUTE_GUARD 控制是否启用全局鉴权守卫 |
| 档案 API | 仅 getProfile() |
新增 updateProfile()、getAppointments()、getAppointmentDetail() |
| 依赖 | v1 | v2 |
|---|---|---|
| TypeScript | ~5.x | 6.0.3 |
| Vue Router | 4.x | 5.1.0 |
| Pinia | 2.x | 3.0.4 |
| Vite | 5.x | 8.0.14 |
| Tailwind CSS | 3.x | 4.3(@tailwindcss/vite 插件) |
| 新增依赖 | — | dayjs、password-validator |
| 变量名 | 默认值 | 说明 |
|---|---|---|
VITE_ENABLE_ROUTE_GUARD |
true |
设为 false 可关闭路由鉴权(调试用) |
__USE_MOCK_API__ |
编译时常量 | 由 VITE_DATA_SOURCE 编译时注入,控制 Mock/API 模式 |