设计模式(实战项目)-状态模式

需求背景:存在状态流转的预约单

一.数据库设计

sql 复制代码
CREATE TABLE `appointment` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `appoint_type` int(11) NOT NULL COMMENT '预约类型(0:线下查房...)',
  `appoint_user_id` bigint(20) NOT NULL COMMENT '预约人userId',
  `appoint_store_id` bigint(20) NOT NULL COMMENT '预约门店',
  `appoint_service_type` int(11) DEFAULT NULL COMMENT '预约服务类型(9:儿科查房 7:产科查房 8:中医查房)',
  `appoint_doctor_id` bigint(11) DEFAULT NULL COMMENT '预约医生id',
  `appoint_date` date DEFAULT NULL COMMENT '预约日期(精确到日)',
  `appoint_time_start` time NOT NULL COMMENT '预约开始时间',
  `appoint_time_end` time NOT NULL COMMENT '预约结束时间',
  `status` int(11) NOT NULL COMMENT '状态(-1:已取消 0:待接单 1:待分配(已拒绝) 2:待查房 3:待小结 4:待签名 100:已完成 )',
  `drive_appointment_id` bigint(20) DEFAULT NULL COMMENT '驱动预约单id(null:代表驱动预约单)',
  `appointment_setting_id` bigint(20) DEFAULT NULL COMMENT '预约单-配置id',
  `create_id` bigint(20) DEFAULT NULL COMMENT '创建人id',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modify_id` bigint(20) DEFAULT NULL COMMENT '修改人',
  `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '删除标记;0-正常 ;1-删除',
  PRIMARY KEY (`id`),
  KEY `idx_drive_appointment_id` (`drive_appointment_id`) USING BTREE,
  KEY `idx_appointment_setting_id` (`appointment_setting_id`) USING BTREE,
  KEY `idx_appoint_user_id` (`appoint_user_id`) USING BTREE
)  AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4  COMMENT='预约单';




-- 预约单-状态流转表
 CREATE TABLE `stbella-his`.appointment_status_log
( 
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
	appointment_id    bigint(20)						not null COMMENT '预约单id',
	before_status 	  int(11)							  NOT null COMMENT '前状态',
  after_status 	    int(11)							  NOT null COMMENT '后状态',
	handle_type       int(11)								not null COMMENT '操作类型',
	handle_user_type    int(11)					    not null COMMENT '操作人类型',
  create_id bigint(20) DEFAULT NULL COMMENT '创建人id',
	gmt_create datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
	modify_id bigint(20) DEFAULT NULL COMMENT '修改人',
	gmt_modified datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
	deleted tinyint(1) DEFAULT '0' COMMENT '删除标记;0-正常 ;1-删除',
	 PRIMARY KEY (`id`),
	 KEY `idx_appointment_id` (`appointment_id`) USING BTREE
)  
   comment '预约单-状态流转表'; 

二.状态枚举类

java 复制代码
@Getter
@AllArgsConstructor
public enum AppointStatusEnum {

	

	INIT(-100, "初始化"),
	CANCEL(-1, "已取消"),
	WAIT_RECEIVE(0, "待接单"),
	WAIT_DISTRIBUTE(1, "待分配"),
	WAIT_CHECK_ROOM(2, "待查房"),
	WAIT_SUMMARY(3, "待小结"),
	WAIT_SIGN(4, "待签名"),
	COMPLETE(100, "已完成");


	private final int code;

	private final String name;

	public static AppointStatusEnum getEnum(int code) {
		for (AppointStatusEnum statusEnum : AppointStatusEnum.values()) {
			if (statusEnum.getCode() == code) {
				return statusEnum;
			}
		}
		return null;
	}

}

三. 上下文参数类:参数传递

java 复制代码
@Builder
@Data
public class AppointContext implements Serializable {

	private static final long serialVersionUID = 3542771730176821092L;

	private UserTokenInfoDTO userTokenInfoDTO;

	private ClientEnum clientEnum;

	private AppointPO appointPO;

}

四.状态机流转上下文类:所有要执行的动作都在这里记录

java 复制代码
@Data
public class AppointHandleContext implements Serializable {
	private static final long serialVersionUID = 1658366511210864400L;

	private IAppointStatusHandler statusHandler = AppointStatusHandlerFactory.getStatusHandler(AppointStatusEnum.INIT);

	public AppointDetailVO detail(AppointHandleContext handleContext, 
	public Boolean cancel(AppointHandleContext handleContext, AppointContext context) {
		return statusHandler.cancel(handleContext, context);
	}

	public String add(AppointHandleContext handleContext, AppointContext context) {
		return statusHandler.add(handleContext, context);
	}

	public Boolean update(AppointHandleContext handleContext, AppointContext context) {
		return statusHandler.update(handleContext, context);
	}

	

}

五.状态处理器工厂类

java 复制代码
@Component
public class AppointStatusHandlerFactory implements ApplicationContextAware {

	private static final Map<AppointStatusEnum, IAppointStatusHandler> MAP = Maps.newHashMapWithExpectedSize(AppointStatusEnum.values().length);

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		Map<String, IAppointStatusHandler> beansOfType = applicationContext.getBeansOfType(IAppointStatusHandler.class);
		if (CollectionUtil.isEmpty(beansOfType)) {
			return;
		}
		beansOfType.forEach((key, statusHandler) -> MAP.put(statusHandler.getStatus(), statusHandler));
	}


	public static IAppointStatusHandler getStatusHandler(AppointStatusEnum appointStatusEnum) {
		return Optional.ofNullable(MAP.get(appointStatusEnum))
				.orElseThrow(() -> new BusinessException(ResultEnum.PARAM_ERROR, "预约单状态异常"));
	}

}

六.状态接口类

java 复制代码
public interface IAppointStatusHandler {

	AppointStatusEnum getStatus();

	AppointDetailVO detail(AppointHandleContext handleContext, AppointContext context);

	Boolean cancel(AppointHandleContext handleContext, AppointContext context);

	String add(AppointHandleContext handleContext, AppointContext context);

	Boolean update(AppointHandleContext handleContext, AppointContext context);

」

七.状态抽象实现类

java 复制代码
@Slf4j
@Component
public abstract class AbstractAppointStatusHandler implements IAppointStatusHandler {

	private static final String LOG_PRE = "预约单状态流转异常,method:{},handleContext:{},context:{}";


	@Override
	public AppointDetailVO detail(AppointHandleContext handleContext, AppointContext context) {
		log.error(LOG_PRE, "detail", JSONUtil.toJsonStr(handleContext), JSONUtil.toJsonStr(context));
		throw new BusinessException(ResultEnum.PARAM_ERROR, "当前状态不允许查询");
	}

	@Override
	public Boolean cancel(AppointHandleContext handleContext, AppointContext context) {
		log.error(LOG_PRE, "cancel", JSONUtil.toJsonStr(handleContext), JSONUtil.toJsonStr(context));
		throw new BusinessException(ResultEnum.PARAM_ERROR, "当前状态不允许取消");
	}

	@Override
	public String add(AppointHandleContext handleContext, AppointContext context) {
		log.error(LOG_PRE, "add", JSONUtil.toJsonStr(handleContext), JSONUtil.toJsonStr(context));
		throw new BusinessException(ResultEnum.PARAM_ERROR, "当前状态不允许新增");
	}

	@Override
	public Boolean update(AppointHandleContext handleContext, AppointContext context) {
		log.error(LOG_PRE, "update", JSONUtil.toJsonStr(handleContext), JSONUtil.toJsonStr(context));
		throw new BusinessException(ResultEnum.PARAM_ERROR, "当前状态不允许修改");
	}
}

八.每个状态节点实现类

java 复制代码
@Slf4j
@Component
public class AppointInitHandler extends AbstractAppointStatusHandler {

	

	@Override
	public AppointStatusEnum getStatus() {
		return AppointStatusEnum.INIT;
	}

	@Override
	public AppointDetailVO detail(AppointHandleContext handleContext, AppointContext context) {
		return appointSupport.detail(context);
	}

	@Override
	public String add(AppointHandleContext handleContext, AppointContext context) 
    {
		
	}

}
java 复制代码
@Slf4j
@Component
public class AppointWaitCheckRoomHandler extends AbstractAppointStatusHandler {
	@Override
	public AppointStatusEnum getStatus() {
		return AppointStatusEnum.WAIT_CHECK_ROOM;
	}

	@Resource
	private AppointSupport appointSupport;

	@Override
	public AppointDetailVO detail(AppointHandleContext handleContext, AppointContext context) {
		AppointDetailVO detail = appointSupport.detail(context);
		ClientEnum clientEnum = context.getClientEnum();
		AppointPO appointPO = context.getAppointPO();

		switch (clientEnum) {
			case HIS_NURSE:
				this.nurseDetail(detail, appointPO);
				break;
			case HIS_DOCTOR:
				this.doctorDetail(detail, appointPO);
				break;
			default:
				break;
		}
		return detail;
	}

	@Override
	public Boolean cancel(AppointHandleContext handleContext, AppointContext context) {
		return appointSupport.handleOnlyStatus(context, AppointStatusEnum.CANCEL, AppointHandleTypeEnum.CANCEL);
	}


	@Override
	public Boolean startCheckRoom(AppointHandleContext handleContext, AppointContext context) {
		return appointSupport.handleOnlyStatus(context, AppointStatusEnum.WAIT_SUMMARY, AppointHandleTypeEnum.START_CHECK_ROOM);
	}
java 复制代码
@Slf4j
@Component
public class AppointCompleteHandler extends AbstractAppointStatusHandler {

	@Override
	public AppointStatusEnum getStatus() {
		return AppointStatusEnum.COMPLETE;
	}


	@Override
	public AppointDetailVO detail(AppointHandleContext handleContext, AppointContext context) {
		AppointDetailVO detail = appointSupport.detail(context);
		ClientEnum clientEnum = context.getClientEnum();
		AppointPO appointPO = context.getAppointPO();

		switch (clientEnum) {
			case HIS_NURSE:
				this.nurseDetail(detail, appointPO);
				break;
			case HIS_DOCTOR:
				this.doctorDetail(detail, appointPO);
				break;
			default:
				break;
		}
		return detail;
	}



	@Override
	public AppointCheckRoomSignNotifyVO getAppointCheckRoomSignNotifyVO(AppointHandleContext handleContext, AppointContext context) {
		
	}
}

九.具体行为调用代码类

java 复制代码
@Override
	public void bindPicpCustomer(AppointClinicBindReq req, UserTokenInfoDTO userTokenInfoDTO) {
		AppointPO appointPO = Optional.ofNullable(appointRepository.getOnePOById(req.getAppointmentId())).orElseThrow(() ->
				new BusinessException(ResultEnum.PARAM_ERROR, "预约单数据不存在或已被删除"));

		AppointHandleContext handleContext = new AppointHandleContext();
		handleContext.setStatusHandler(AppointStatusHandlerFactory.getStatusHandler(AppointStatusEnum.getEnum(appointPO.getStatus())));
		handleContext.bindPicpCustomer(handleContext, AppointContext.builder()
				.appointPO(appointPO)
				.clientEnum(appointSupport.getLoginClientEnum(userTokenInfoDTO))
				.userTokenInfoDTO(userTokenInfoDTO)
				.picpUsers(req.getPicpUsers())
				.build());
	}
相关推荐
写了20年代码的老程序员4 分钟前
Java Lambda 类型安全查询是怎么实现的?从字节码层面拆一遍
java·开源
唐青枫15 分钟前
Java RabbitMQ 实战指南:从交换机、队列到可靠消息处理
java
卓怡学长20 分钟前
w272基于springboot便民医疗服务小程序
java·spring boot·spring·小程序·intellij-idea
一方~22 分钟前
权限系统设计
java
蜗牛~turbo23 分钟前
金蝶云星空的网络控制设置
开发语言·c#·金蝶·erp·云星空·k3 cloud
盐焗鹌鹑蛋28 分钟前
【C++】多态
java·jvm·c++
rebibabo28 分钟前
Java高并发底层原理(七)volatile 到底解决了什么问题
java·学习笔记·volatile·可见性·有序性·指令重排
Reload.43 分钟前
GJ航司,验证码网易易盾 JS逆向 纯算轨迹
开发语言·前端·javascript
名字还没想好☜1 小时前
Go 并发实战:用 channel 实现 worker pool
java·数据库·后端·golang·go
人道领域1 小时前
【LeetCode刷题日记】贪心算法理论与实战:455.分发饼干最优解
java·开发语言·数据结构·算法·leetcode·贪心算法