|
@@ -8,13 +8,13 @@
|
|
|
|
|
|
|
|
### 2.1 复诊记录表 (t_follow_up)
|
|
### 2.1 复诊记录表 (t_follow_up)
|
|
|
|
|
|
|
|
-根据项目数据库表设计规范,创建复诊记录表:
|
|
|
|
|
|
|
+根据项目数据库表设计规范(参见 `docs/OLD/DevRule/07-数据库规范.md`),创建复诊记录表:
|
|
|
|
|
|
|
|
| 字段名 | 类型 | 描述 |
|
|
| 字段名 | 类型 | 描述 |
|
|
|
|--------|------|------|
|
|
|--------|------|------|
|
|
|
-| id | BIGINT | 主键ID |
|
|
|
|
|
-| patient_user_id | BIGINT | 患者用户ID |
|
|
|
|
|
-| doctor_user_id | BIGINT | 医生用户ID |
|
|
|
|
|
|
|
+| id | BIGINT | 主键ID,使用雪花算法(MyBatis-Plus `ASSIGN_ID`) |
|
|
|
|
|
+| patient_user_id | BIGINT | 患者用户ID,外键(可选) |
|
|
|
|
|
+| doctor_user_id | BIGINT | 医生用户ID,外键(可选) |
|
|
|
| appointment_time | DATETIME | 预约时间 |
|
|
| appointment_time | DATETIME | 预约时间 |
|
|
|
| actual_time | DATETIME | 实际就诊时间 |
|
|
| actual_time | DATETIME | 实际就诊时间 |
|
|
|
| status | VARCHAR(20) | 复诊状态 (PENDING-待确认, CONFIRMED-已确认, CANCELLED-已取消, COMPLETED-已完成) |
|
|
| status | VARCHAR(20) | 复诊状态 (PENDING-待确认, CONFIRMED-已确认, CANCELLED-已取消, COMPLETED-已完成) |
|
|
@@ -27,6 +27,39 @@
|
|
|
| version | INT | 版本号(乐观锁) |
|
|
| version | INT | 版本号(乐观锁) |
|
|
|
| remark | VARCHAR(255) | 备注 |
|
|
| remark | VARCHAR(255) | 备注 |
|
|
|
|
|
|
|
|
|
|
+遵循的数据库规范要点:
|
|
|
|
|
+- 使用 `BIGINT` 主键并通过雪花算法生成(MyBatis-Plus `ASSIGN_ID`)。
|
|
|
|
|
+- 公共字段(`version`、`create_user`、`create_time`、`update_user`、`update_time`)由 `BaseEntity` 与 `CustomMetaObjectHandler` 自动填充。
|
|
|
|
|
+- 建议添加索引:`patient_user_id`、`doctor_user_id`、`appointment_time`、`create_time`。如有按状态频繁查询,可加 `status` 的组合索引。
|
|
|
|
|
+- 根据需要添加外键约束(注意性能影响)或在应用层校验关联数据完整性。
|
|
|
|
|
+- 数据迁移建议使用 Flyway/Liquibase(脚本命名遵循 `V{version}__{description}.sql`)。
|
|
|
|
|
+
|
|
|
|
|
+示例建表(参考):
|
|
|
|
|
+```
|
|
|
|
|
+CREATE TABLE t_follow_up (
|
|
|
|
|
+ id BIGINT PRIMARY KEY,
|
|
|
|
|
+ patient_user_id BIGINT NOT NULL,
|
|
|
|
|
+ doctor_user_id BIGINT NOT NULL,
|
|
|
|
|
+ appointment_time DATETIME NOT NULL,
|
|
|
|
|
+ actual_time DATETIME,
|
|
|
|
|
+ status VARCHAR(20) NOT NULL,
|
|
|
|
|
+ reason TEXT,
|
|
|
|
|
+ notes TEXT,
|
|
|
|
|
+ create_user BIGINT,
|
|
|
|
|
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
+ update_user BIGINT,
|
|
|
|
|
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
|
+ version INT DEFAULT 0,
|
|
|
|
|
+ remark VARCHAR(255)
|
|
|
|
|
+);
|
|
|
|
|
+CREATE INDEX idx_followup_patient ON t_follow_up(patient_user_id);
|
|
|
|
|
+CREATE INDEX idx_followup_doctor ON t_follow_up(doctor_user_id);
|
|
|
|
|
+CREATE INDEX idx_followup_appointment ON t_follow_up(appointment_time);
|
|
|
|
|
+CREATE INDEX idx_followup_create ON t_follow_up(create_time);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+备注:对外响应的 `id` 建议以字符串形式返回以避免前端 JS 精度丢失(参见 `docs/OLD/DevRule/03-API设计规范.md` 的长整型 ID 传输策略)。
|
|
|
|
|
+
|
|
|
## 3. 实体类设计
|
|
## 3. 实体类设计
|
|
|
|
|
|
|
|
### 3.1 PO实体类 (FollowUp.java)
|
|
### 3.1 PO实体类 (FollowUp.java)
|
|
@@ -550,9 +583,11 @@ public class FollowUpController {
|
|
|
content = @Content(mediaType = "application/json",
|
|
content = @Content(mediaType = "application/json",
|
|
|
schema = @Schema(implementation = Void.class)))
|
|
schema = @Schema(implementation = Void.class)))
|
|
|
})
|
|
})
|
|
|
- @PostMapping(path = "/update", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
- public R<?> update(@RequestBody UpdateFollowUpRequest req) {
|
|
|
|
|
|
|
+ @PutMapping(path = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
+ public R<?> update(@PathVariable Long id, @RequestBody UpdateFollowUpRequest req) {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ // 将路径ID赋值到请求对象,保证一致性
|
|
|
|
|
+ req.setId(id);
|
|
|
followUpService.updateFollowUp(req);
|
|
followUpService.updateFollowUp(req);
|
|
|
return R.success(200, "复诊记录更新成功");
|
|
return R.success(200, "复诊记录更新成功");
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
@@ -597,7 +632,7 @@ public class FollowUpController {
|
|
|
schema = @Schema(implementation = Void.class)))
|
|
schema = @Schema(implementation = Void.class)))
|
|
|
})
|
|
})
|
|
|
@PostMapping(path = "/list-by-patient", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
@PostMapping(path = "/list-by-patient", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
- public R<?> listByPatient(Long patientUserId, @RequestBody BaseQueryRequest req) {
|
|
|
|
|
|
|
+ public R<?> listByPatient(@RequestParam("patientUserId") Long patientUserId, @RequestBody BaseQueryRequest req) {
|
|
|
try {
|
|
try {
|
|
|
Page<FollowUpResponse> page = followUpService.listFollowUpsByPatient(patientUserId, req);
|
|
Page<FollowUpResponse> page = followUpService.listFollowUpsByPatient(patientUserId, req);
|
|
|
work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse vo = new work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse();
|
|
work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse vo = new work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse();
|
|
@@ -622,10 +657,10 @@ public class FollowUpController {
|
|
|
content = @Content(mediaType = "application/json",
|
|
content = @Content(mediaType = "application/json",
|
|
|
schema = @Schema(implementation = Void.class)))
|
|
schema = @Schema(implementation = Void.class)))
|
|
|
})
|
|
})
|
|
|
- @PostMapping(path = "/delete", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
- public R<?> delete(@RequestBody work.baiyun.chronicdiseaseapp.model.vo.DeleteFollowUpRequest req) {
|
|
|
|
|
|
|
+ @DeleteMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
+ public R<?> delete(@PathVariable Long id) {
|
|
|
try {
|
|
try {
|
|
|
- followUpService.deleteFollowUp(req.getId());
|
|
|
|
|
|
|
+ followUpService.deleteFollowUp(id);
|
|
|
return R.success(200, "复诊记录删除成功");
|
|
return R.success(200, "复诊记录删除成功");
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
logger.error("delete follow up record failed", e);
|
|
logger.error("delete follow up record failed", e);
|
|
@@ -724,7 +759,35 @@ CANCELLED(已取消)
|
|
|
|
|
|
|
|
## 12. 安全考虑
|
|
## 12. 安全考虑
|
|
|
|
|
|
|
|
-1. 数据访问权限控制
|
|
|
|
|
-2. 参数验证和输入检查
|
|
|
|
|
-3. 防止SQL注入
|
|
|
|
|
-4. 日志记录和审计追踪
|
|
|
|
|
|
|
+本项目安全实现应遵循 `docs/OLD/DevRule/08-安全规范.md` 中的已有约定,以下为与复诊管理功能相关的要点(需在实现时落地):
|
|
|
|
|
+
|
|
|
|
|
+1. **认证与授权**:
|
|
|
|
|
+ - 使用 Token(优先 `Authorization: Bearer <token>`,兼容 `X-Token` / `token`)并通过 `AuthInterceptor` 校验,将 `currentUserId`/`currentUserRole` 注入请求上下文。
|
|
|
|
|
+ - 仅允许符合角色与绑定关系的用户访问资源(患者仅访问本人,医生访问分配给自己的或其患者,系统管理员可查看所有记录)。
|
|
|
|
|
+
|
|
|
|
|
+2. **输入校验**:
|
|
|
|
|
+ - 使用 Bean Validation 注解(`@NotNull`、`@Future` 等)对请求参数进行校验。
|
|
|
|
|
+ - 对时间类型(`appointmentTime`)增加业务校验:不可为过去时间、合法时间窗口、与已有预约冲突的检测。
|
|
|
|
|
+
|
|
|
|
|
+3. **异常与统一错误响应**:
|
|
|
|
|
+ - 使用全局异常处理器(`@RestControllerAdvice`/`CustomExceptionHandler`)统一映射 `CustomException`、校验异常、未知异常到 `R.fail(code, message)`,并返回合适的 HTTP 状态码(如 400/403/404/500)。
|
|
|
|
|
+ - 业务异常(如 `FOLLOW_UP_NOT_FOUND` / `FOLLOW_UP_ACCESS_DENIED`)应使用明确错误码并在 `ErrorCode` 中定义。
|
|
|
|
|
+
|
|
|
|
|
+4. **日志与敏感信息**:
|
|
|
|
|
+ - 在日志中避免记录完整 Token 或敏感字段;若需记录,脱敏或仅记录前 8 位。
|
|
|
|
|
+ - 记录关键审计事件:创建、更新、取消、完成、删除等操作的用户 ID 与时间戳。
|
|
|
|
|
+
|
|
|
|
|
+5. **SQL 注入与 ORM 使用**:
|
|
|
|
|
+ - 使用 MyBatis-Plus 提供的预编译与条件构造器,禁止使用字符串拼接构建 SQL。
|
|
|
|
|
+
|
|
|
|
|
+6. **会话安全与 Token 管理**:
|
|
|
|
|
+ - Token 存储与过期策略遵循全局规范(存 `t_user_token`,有效期 72 小时,低于阈值自动延长)。
|
|
|
|
|
+
|
|
|
|
|
+7. **部署/传输**:
|
|
|
|
|
+ - 建议生产环境使用 HTTPS,证书放置在 `classpath:cert/` 并在启动时加载。
|
|
|
|
|
+
|
|
|
|
|
+8. **最小权限原则**:
|
|
|
|
|
+ - 服务端对每个接口、数据读取与修改操作都进行基于角色的授权校验,不信任前端传入的角色或用户 ID。
|
|
|
|
|
+
|
|
|
|
|
+9. **审计与监控**:
|
|
|
|
|
+ - 对认证失败、权限拒绝、异常错误等关键安全事件进行集中日志与告警(便于安全分析)。
|