复诊管理功能旨在为患者和医生提供一个便捷的复诊预约和管理平台。该功能允许患者发起复诊请求,医生审核并安排复诊时间,双方可以查看和管理复诊记录。
根据项目数据库表设计规范(参见 docs/OLD/DevRule/07-数据库规范.md),创建复诊记录表:
| 字段名 | 类型 | 描述 |
|---|---|---|
| id | BIGINT(20) | 主键ID,使用雪花算法(MyBatis-Plus ASSIGN_ID) |
| patient_user_id | BIGINT(20) | 患者用户ID,外键(可选) |
| doctor_user_id | BIGINT(20) | 医生用户ID,外键(可选) |
| appointment_time | DATETIME | 预约时间 |
| actual_time | DATETIME | 医生主动确认已完成的时间记录 |
| status | VARCHAR(20) | 复诊状态 (PENDING-待确认, CONFIRMED-已确认, CANCELLED-已取消, COMPLETED-已完成) |
| reason | TEXT | 复诊原因 |
| notes | TEXT | 备注 |
| version | INT(11) | 版本号(乐观锁) |
| create_user | BIGINT(20) | 创建者ID |
| create_time | DATETIME | 创建时间,默认值CURRENT_TIMESTAMP |
| update_user | BIGINT(20) | 更新者ID |
| update_time | DATETIME | 更新时间,默认值CURRENT_TIMESTAMP,更新时自动设置为当前时间 |
| remark | VARCHAR(255) | 备注 |
遵循的数据库规范要点:
BIGINT 主键并通过雪花算法生成(MyBatis-Plus ASSIGN_ID)。version、create_user、create_time、update_user、update_time)由 BaseEntity 与 CustomMetaObjectHandler 自动填充。V{version}__{description}.sql)。示例建表(参考):
CREATE TABLE `t_follow_up` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`patient_user_id` bigint(20) NOT NULL COMMENT '患者用户ID',
`doctor_user_id` bigint(20) NOT NULL COMMENT '医生用户ID',
`appointment_time` datetime NOT NULL COMMENT '预约时间',
`actual_time` datetime DEFAULT NULL COMMENT '医生主动确认已完成的时间记录',
`status` varchar(20) NOT NULL COMMENT '复诊状态 (PENDING-待确认, CONFIRMED-已确认, CANCELLED-已取消, COMPLETED-已完成)',
`reason` text COMMENT '复诊原因',
`notes` text COMMENT '备注',
`version` int(11) DEFAULT '0' COMMENT '版本号(乐观锁)',
`create_user` bigint(20) DEFAULT NULL COMMENT '创建者ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_user` bigint(20) DEFAULT NULL COMMENT '更新者ID',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `idx_patient_user_id` (`patient_user_id`),
KEY `idx_doctor_user_id` (`doctor_user_id`),
KEY `idx_appointment_time` (`appointment_time`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='复诊记录表';
备注:对外响应的 id 建议以字符串形式返回以避免前端 JS 精度丢失(参见 docs/OLD/DevRule/03-API设计规范.md 的长整型 ID 传输策略)。
package work.baiyun.chronicdiseaseapp.model.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Schema(description = "复诊记录表")
@TableName("t_follow_up")
@Data
@EqualsAndHashCode(callSuper = false)
public class FollowUp extends BaseEntity {
@Schema(description = "患者用户ID")
@TableField("patient_user_id")
private Long patientUserId;
@Schema(description = "医生用户ID")
@TableField("doctor_user_id")
private Long doctorUserId;
@Schema(description = "预约时间")
@TableField("appointment_time")
private LocalDateTime appointmentTime;
@Schema(description = "医生主动确认已完成的时间记录")
@TableField("actual_time")
private LocalDateTime actualTime;
@Schema(description = "复诊状态 (PENDING-待确认, CONFIRMED-已确认, CANCELLED-已取消, COMPLETED-已完成)")
@TableField("status")
private String status;
@Schema(description = "复诊原因")
@TableField("reason")
private String reason;
@Schema(description = "备注")
@TableField("notes")
private String notes;
}
``java // CreateFollowUpRequest.java package work.baiyun.chronicdiseaseapp.model.vo;
import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data;
import jakarta.validation.constraints.*; import java.time.LocalDateTime;
@Schema(description = "创建复诊请求") @Data public class CreateFollowUpRequest {
@Schema(description = "医生用户ID", required = true)
@NotNull(message = "医生ID不能为空")
private Long doctorUserId;
@Schema(description = "预约时间", required = true)
@NotNull(message = "预约时间不能为空")
private LocalDateTime appointmentTime;
@Schema(description = "复诊原因")
private String reason;
}
// UpdateFollowUpRequest.java package work.baiyun.chronicdiseaseapp.model.vo;
import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data;
import jakarta.validation.constraints.*; import java.time.LocalDateTime;
@Schema(description = "更新复诊请求") @Data public class UpdateFollowUpRequest {
@Schema(description = "复诊记录ID", required = true)
@NotNull(message = "复诊记录ID不能为空")
private Long id;
@Schema(description = "预约时间")
private LocalDateTime appointmentTime;
@Schema(description = "复诊状态")
private String status;
@Schema(description = "备注")
private String notes;
}
#### 3.2.2 响应对象
``java
// FollowUpResponse.java
package work.baiyun.chronicdiseaseapp.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "复诊记录响应")
@Data
public class FollowUpResponse {
@Schema(description = "记录ID")
private String id;
@Schema(description = "患者用户ID")
private Long patientUserId;
@Schema(description = "医生用户ID")
private Long doctorUserId;
@Schema(description = "预约时间")
private LocalDateTime appointmentTime;
@Schema(description = "实际就诊时间")
private LocalDateTime actualTime;
@Schema(description = "复诊状态")
private String status;
@Schema(description = "复诊原因")
private String reason;
@Schema(description = "备注")
private String notes;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "患者昵称")
private String patientNickname;
@Schema(description = "医生昵称")
private String doctorNickname;
}
// FollowUpPageResponse.java
package work.baiyun.chronicdiseaseapp.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "复诊记录分页响应")
@Data
public class FollowUpPageResponse {
@Schema(description = "数据列表")
private List<FollowUpResponse> records;
@Schema(description = "总数")
private long total;
@Schema(description = "每页大小")
private long size;
@Schema(description = "当前页码")
private long current;
@Schema(description = "总页数")
private long pages;
}
``java // FollowUpMapper.java package work.baiyun.chronicdiseaseapp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import work.baiyun.chronicdiseaseapp.model.po.FollowUp; import org.apache.ibatis.annotations.Mapper;
@Mapper public interface FollowUpMapper extends BaseMapper { }
## 5. Service层设计
### 5.1 接口定义
``java
// FollowUpService.java
package work.baiyun.chronicdiseaseapp.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import work.baiyun.chronicdiseaseapp.model.vo.BaseQueryRequest;
import work.baiyun.chronicdiseaseapp.model.vo.FollowUpResponse;
import work.baiyun.chronicdiseaseapp.model.vo.CreateFollowUpRequest;
import work.baiyun.chronicdiseaseapp.model.vo.UpdateFollowUpRequest;
public interface FollowUpService {
/**
* 患者创建复诊请求
*/
void createFollowUp(CreateFollowUpRequest request);
/**
* 更新复诊记录(医生确认、取消或患者修改)
*/
void updateFollowUp(UpdateFollowUpRequest request);
/**
* 分页查询当前用户的复诊记录
*/
Page<FollowUpResponse> listFollowUps(BaseQueryRequest request);
/**
* 医生分页查询患者的复诊记录
*/
Page<FollowUpResponse> listFollowUpsByPatient(Long patientUserId, BaseQueryRequest request);
/**
* 删除复诊记录
*/
void deleteFollowUp(Long id);
}
``java // FollowUpServiceImpl.java package work.baiyun.chronicdiseaseapp.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import work.baiyun.chronicdiseaseapp.mapper.FollowUpMapper; import work.baiyun.chronicdiseaseapp.mapper.UserInfoMapper; import work.baiyun.chronicdiseaseapp.model.po.FollowUp; import work.baiyun.chronicdiseaseapp.model.po.UserInfo; import work.baiyun.chronicdiseaseapp.model.vo.BaseQueryRequest; import work.baiyun.chronicdiseaseapp.model.vo.FollowUpResponse; import work.baiyun.chronicdiseaseapp.model.vo.CreateFollowUpRequest; import work.baiyun.chronicdiseaseapp.model.vo.UpdateFollowUpRequest; import work.baiyun.chronicdiseaseapp.service.FollowUpService; import work.baiyun.chronicdiseaseapp.util.SecurityUtils;
import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors;
@Service public class FollowUpServiceImpl implements FollowUpService {
@Autowired
private FollowUpMapper followUpMapper;
@Autowired
private UserInfoMapper userInfoMapper;
@Override
public void createFollowUp(CreateFollowUpRequest request) {
Long userId = SecurityUtils.getCurrentUserId();
FollowUp followUp = new FollowUp();
BeanUtils.copyProperties(request, followUp);
followUp.setPatientUserId(userId);
followUp.setStatus("PENDING"); // 默认状态为待确认
followUpMapper.insert(followUp);
}
@Override
public void updateFollowUp(UpdateFollowUpRequest request) {
Long userId = SecurityUtils.getCurrentUserId();
FollowUp followUp = followUpMapper.selectById(request.getId());
if (followUp == null) {
throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_NOT_FOUND.getCode(),
"复诊记录不存在");
}
// 权限检查:患者只能修改自己的记录,医生只能修改分配给自己的记录
work.baiyun.chronicdiseaseapp.enums.PermissionGroup role = SecurityUtils.getCurrentUserRole();
if (role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.PATIENT &&
!followUp.getPatientUserId().equals(userId)) {
throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
"无权操作该复诊记录");
}
if ((role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.DOCTOR ||
role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.SYS_ADMIN) &&
!followUp.getDoctorUserId().equals(userId)) {
throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
"无权操作该复诊记录");
}
// 更新字段
if (request.getAppointmentTime() != null) {
followUp.setAppointmentTime(request.getAppointmentTime());
}
if (request.getStatus() != null) {
followUp.setStatus(request.getStatus());
}
if (request.getNotes() != null) {
followUp.setNotes(request.getNotes());
}
// 如果状态更新为已完成,则设置实际就诊时间
if ("COMPLETED".equals(request.getStatus())) {
followUp.setActualTime(java.time.LocalDateTime.now());
}
followUpMapper.updateById(followUp);
}
@Override
public Page<FollowUpResponse> listFollowUps(BaseQueryRequest request) {
Long userId = SecurityUtils.getCurrentUserId();
work.baiyun.chronicdiseaseapp.enums.PermissionGroup role = SecurityUtils.getCurrentUserRole();
Page<FollowUp> page = new Page<>(request.getPageNum(), request.getPageSize());
LambdaQueryWrapper<FollowUp> wrapper = new LambdaQueryWrapper<>();
// 根据用户角色查询不同的数据
if (role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.PATIENT) {
wrapper.eq(FollowUp::getPatientUserId, userId);
} else if (role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.DOCTOR ||
role == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.SYS_ADMIN) {
wrapper.eq(FollowUp::getDoctorUserId, userId);
}
wrapper.ge(request.getStartTime() != null, FollowUp::getCreateTime, request.getStartTime())
.le(request.getEndTime() != null, FollowUp::getCreateTime, request.getEndTime())
.orderByDesc(FollowUp::getCreateTime);
Page<FollowUp> result = followUpMapper.selectPage(page, wrapper);
// 批量查询用户信息
Set<Long> userIds = result.getRecords().stream()
.flatMap(r -> java.util.stream.Stream.of(r.getPatientUserId(), r.getDoctorUserId()))
.filter(id -> id != null)
.collect(Collectors.toSet());
Map<Long, UserInfo> userInfoMap;
if (userIds.isEmpty()) {
userInfoMap = java.util.Collections.emptyMap();
} else {
List<UserInfo> userInfos = userInfoMapper.selectBatchIds(userIds);
userInfoMap = userInfos.stream().collect(Collectors.toMap(UserInfo::getId, u -> u));
}
List<FollowUpResponse> responses = result.getRecords().stream().map(r -> {
FollowUpResponse resp = new FollowUpResponse();
BeanUtils.copyProperties(r, resp);
resp.setId(r.getId().toString());
// 设置用户昵称
UserInfo patient = userInfoMap.get(r.getPatientUserId());
if (patient != null) {
resp.setPatientNickname(patient.getNickname());
}
UserInfo doctor = userInfoMap.get(r.getDoctorUserId());
if (doctor != null) {
resp.setDoctorNickname(doctor.getNickname());
}
return resp;
}).collect(Collectors.toList());
Page<FollowUpResponse> responsePage = new Page<>();
responsePage.setRecords(responses);
responsePage.setCurrent(result.getCurrent());
responsePage.setSize(result.getSize());
responsePage.setTotal(result.getTotal());
responsePage.setPages(result.getPages());
return responsePage;
}
@Override
public Page<FollowUpResponse> listFollowUpsByPatient(Long patientUserId, BaseQueryRequest request) {
// 检查绑定关系
Long userId = SecurityUtils.getCurrentUserId();
work.baiyun.chronicdiseaseapp.enums.PermissionGroup role = SecurityUtils.getCurrentUserRole();
// 只有医生和管理员可以查询其他患者的复诊记录
if (role != work.baiyun.chronicdiseaseapp.enums.PermissionGroup.DOCTOR &&
role != work.baiyun.chronicdiseaseapp.enums.PermissionGroup.SYS_ADMIN) {
throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
"无权查询其他患者的复诊记录");
}
Page<FollowUp> page = new Page<>(request.getPageNum(), request.getPageSize());
LambdaQueryWrapper<FollowUp> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FollowUp::getPatientUserId, patientUserId)
.ge(request.getStartTime() != null, FollowUp::getCreateTime, request.getStartTime())
.le(request.getEndTime() != null, FollowUp::getCreateTime, request.getEndTime())
.orderByDesc(FollowUp::getCreateTime);
Page<FollowUp> result = followUpMapper.selectPage(page, wrapper);
List<FollowUpResponse> responses = result.getRecords().stream().map(r -> {
FollowUpResponse resp = new FollowUpResponse();
BeanUtils.copyProperties(r, resp);
resp.setId(r.getId().toString());
return resp;
}).collect(Collectors.toList());
Page<FollowUpResponse> responsePage = new Page<>();
responsePage.setRecords(responses);
responsePage.setCurrent(result.getCurrent());
responsePage.setSize(result.getSize());
responsePage.setTotal(result.getTotal());
responsePage.setPages(result.getPages());
return responsePage;
}
@Override
public void deleteFollowUp(Long id) {
Long userId = SecurityUtils.getCurrentUserId();
FollowUp followUp = followUpMapper.selectById(id);
if (followUp == null) {
throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_NOT_FOUND.getCode(),
"复诊记录不存在");
}
// 只有患者本人才能删除自己的复诊记录
if (!followUp.getPatientUserId().equals(userId)) {
throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
"无权删除该复诊记录");
}
followUpMapper.deleteById(id);
}
}
## 6. Controller层设计
``java
// FollowUpController.java
package work.baiyun.chronicdiseaseapp.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import work.baiyun.chronicdiseaseapp.common.R;
import work.baiyun.chronicdiseaseapp.model.vo.BaseQueryRequest;
import work.baiyun.chronicdiseaseapp.model.vo.FollowUpResponse;
import work.baiyun.chronicdiseaseapp.model.vo.CreateFollowUpRequest;
import work.baiyun.chronicdiseaseapp.model.vo.UpdateFollowUpRequest;
import work.baiyun.chronicdiseaseapp.service.FollowUpService;
import work.baiyun.chronicdiseaseapp.enums.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
@RequestMapping("/follow-up")
@Tag(name = "复诊管理", description = "复诊预约与管理相关接口")
public class FollowUpController {
private static final Logger logger = LoggerFactory.getLogger(FollowUpController.class);
@Autowired
private FollowUpService followUpService;
@Operation(summary = "创建复诊请求", description = "患者创建复诊预约请求")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "复诊请求创建成功",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class))),
@ApiResponse(responseCode = "500", description = "服务器内部错误",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class)))
})
@PostMapping(path = "/create", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public R<?> create(@RequestBody CreateFollowUpRequest req) {
try {
followUpService.createFollowUp(req);
return R.success(200, "复诊请求创建成功");
} catch (Exception e) {
logger.error("create follow up request failed", e);
return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
}
}
@Operation(summary = "更新复诊记录", description = "更新复诊记录(医生确认、取消或患者修改)")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "复诊记录更新成功",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class))),
@ApiResponse(responseCode = "500", description = "服务器内部错误",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class)))
})
@PutMapping(path = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public R<?> update(@PathVariable Long id, @RequestBody UpdateFollowUpRequest req) {
try {
// 将路径ID赋值到请求对象,保证一致性
req.setId(id);
followUpService.updateFollowUp(req);
return R.success(200, "复诊记录更新成功");
} catch (Exception e) {
logger.error("update follow up request failed", e);
return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
}
}
@Operation(summary = "分页查询复诊记录", description = "根据时间范围和分页参数查询复诊记录")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "成功查询复诊记录列表",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse.class))),
@ApiResponse(responseCode = "500", description = "服务器内部错误",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class)))
})
@PostMapping(path = "/list", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public R<?> list(@RequestBody BaseQueryRequest req) {
try {
Page<FollowUpResponse> page = followUpService.listFollowUps(req);
work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse vo = new work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse();
vo.setRecords(page.getRecords());
vo.setTotal(page.getTotal());
vo.setSize(page.getSize());
vo.setCurrent(page.getCurrent());
vo.setPages(page.getPages());
return R.success(200, "ok", vo);
} catch (Exception e) {
logger.error("list follow up records failed", e);
return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
}
}
@Operation(summary = "医生分页查询患者复诊记录", description = "医生查询患者复诊记录(需有绑定关系或权限)")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "成功查询复诊记录列表",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse.class))),
@ApiResponse(responseCode = "403", description = "无权限访问",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class)))
})
@PostMapping(path = "/list-by-patient", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public R<?> listByPatient(@RequestParam("patientUserId") Long patientUserId, @RequestBody BaseQueryRequest req) {
try {
Page<FollowUpResponse> page = followUpService.listFollowUpsByPatient(patientUserId, req);
work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse vo = new work.baiyun.chronicdiseaseapp.model.vo.FollowUpPageResponse();
vo.setRecords(page.getRecords());
vo.setTotal(page.getTotal());
vo.setSize(page.getSize());
vo.setCurrent(page.getCurrent());
vo.setPages(page.getPages());
return R.success(200, "ok", vo);
} catch (Exception e) {
logger.error("list follow up records by patient failed", e);
return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
}
}
@Operation(summary = "删除复诊记录", description = "根据ID删除复诊记录")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "复诊记录删除成功",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class))),
@ApiResponse(responseCode = "500", description = "服务器内部错误",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class)))
})
@DeleteMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public R<?> delete(@PathVariable Long id) {
try {
followUpService.deleteFollowUp(id);
return R.success(200, "复诊记录删除成功");
} catch (Exception e) {
logger.error("delete follow up record failed", e);
return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
}
}
}
``java // DeleteFollowUpRequest.java package work.baiyun.chronicdiseaseapp.model.vo;
import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data;
@Schema(description = "删除复诊记录请求") @Data public class DeleteFollowUpRequest {
@Schema(description = "复诊记录ID")
private Long id;
}
## 8. 错误码补充
在 ErrorCode 枚举中可能需要添加以下错误码:
``java
// 在 ErrorCode.java 中添加
FOLLOW_UP_NOT_FOUND(6000, "复诊记录不存在"),
FOLLOW_UP_ACCESS_DENIED(6001, "无权访问复诊记录"),
FOLLOW_UP_STATUS_INVALID(6002, "复诊状态无效");
POST /follow-up/create
{
"doctorUserId": 1988172854356631553,
"appointmentTime": "2025-12-01T10:00:00",
"reason": "血糖控制不佳,需要调整治疗方案"
}
POST /follow-up/update
{
"id": 1988502746686595073,
"status": "CONFIRMED",
"notes": "已确认预约"
}
POST /follow-up/list
{
"pageNum": 1,
"pageSize": 10,
"startTime": "2025-11-01T00:00:00",
"endTime": "2025-12-01T23:59:59"
}
患者:
医生:
系统管理员:
PENDING(待确认) -> CONFIRMED(已确认) -> COMPLETED(已完成)
|
v
CANCELLED(已取消)
本项目安全实现应遵循 docs/OLD/DevRule/08-安全规范.md 中的已有约定,以下为与复诊管理功能相关的要点(需在实现时落地):
认证与授权:
Authorization: Bearer <token>,兼容 X-Token / token)并通过 AuthInterceptor 校验,将 currentUserId/currentUserRole 注入请求上下文。输入校验:
@NotNull、@Future 等)对请求参数进行校验。appointmentTime)增加业务校验:不可为过去时间、合法时间窗口、与已有预约冲突的检测。异常与统一错误响应:
@RestControllerAdvice/CustomExceptionHandler)统一映射 CustomException、校验异常、未知异常到 R.fail(code, message),并返回合适的 HTTP 状态码(如 400/403/404/500)。FOLLOW_UP_NOT_FOUND / FOLLOW_UP_ACCESS_DENIED)应使用明确错误码并在 ErrorCode 中定义。日志与敏感信息:
SQL 注入与 ORM 使用:
会话安全与 Token 管理:
t_user_token,有效期 72 小时,低于阈值自动延长)。部署/传输:
classpath:cert/ 并在启动时加载。最小权限原则:
审计与监控: