【Java】项目采用的设计模式案例
先说一下业务需要:
做电竞酒店后台系统,第一期功能有一个服务申请的消息通知功能
就是酒店用户在小程序点击服务功能,可以在后台这边查到用户的服务需要
原本设计是只需要一张表存储这些消息,但是考虑设计是SAAS结构(所有酒店数据归于一张表,根据商家ID区分各自家的数据)
数据量可能过大,组长决定把表拆分,一个服务类型一张表
这里有7个类型,就有七张消息通知表
-- aisw_e_service_renewal_msg CREATE TABLE `aisw_e_service_renewal_msg` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `ROOM_NO` varchar(10) DEFAULT NULL COMMENT '房间号', `MERCHANT_ID` int(11) DEFAULT NULL COMMENT '商家ID', `SERVICE_TYPE` varchar(50) DEFAULT NULL COMMENT '服务类型(与字典表AISW_MERCHANT_DICT表关联)', `USER_ID` int(11) DEFAULT NULL COMMENT '用户ID(与AISW_USER表关联)', `RENEWAL_TYPE` tinyint(1) DEFAULT NULL COMMENT '续房类型(1-续住此房间 2-换房续住)', `RENEWAL_DAY` int(11) DEFAULT NULL COMMENT '续住天数', `ACCEPT_STATUS` tinyint(1) DEFAULT '0' COMMENT '受理状态(0-未受理 1-已受理)', `STATUS` tinyint(1) DEFAULT '1' COMMENT '状态( 0-无效 1-有效)', `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间', `CREATE_BY` varchar(50) DEFAULT NULL COMMENT '创建人', `UPDATE_BY` varchar(50) DEFAULT NULL COMMENT '更新人', `REMARKS` text COMMENT '备注', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='电竞酒店续住服务通知表'; -- aisw_e_service_clean_msg CREATE TABLE `aisw_e_service_clean_msg` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `ROOM_NO` varchar(10) DEFAULT NULL COMMENT '房间号', `MERCHANT_ID` int(11) DEFAULT NULL COMMENT '商家ID', `SERVICE_TYPE` varchar(50) DEFAULT NULL COMMENT '服务类型(与字典表AISW_MERCHANT_DICT表关联)', `USER_ID` int(11) DEFAULT NULL COMMENT '用户ID(与AISW_USER表关联)', `CLEAN_DATE` varchar(50) DEFAULT NULL COMMENT '清扫时间', `ACCEPT_STATUS` tinyint(1) DEFAULT '0' COMMENT '受理状态(0-未受理 1-已受理)', `STATUS` tinyint(1) DEFAULT '1' COMMENT '状态(0-无效 1-有效)', `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间', `CREATE_BY` varchar(50) DEFAULT NULL COMMENT '创建人', `UPDATE_BY` varchar(50) DEFAULT NULL COMMENT '更新人', `REMARKS` text COMMENT '备注', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='电竞酒店清扫服务通知表'; -- aisw_e_service_delivery_msg CREATE TABLE `aisw_e_service_delivery_msg` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `ROOM_NO` varchar(10) DEFAULT NULL COMMENT '房间号', `MERCHANT_ID` int(11) DEFAULT NULL COMMENT '商家ID', `SERVICE_TYPE` varchar(50) DEFAULT NULL COMMENT '服务类型(与字典表AISW_MERCHANT_DICT表关联)', `USER_ID` int(11) DEFAULT NULL COMMENT '用户ID(与AISW_USER表关联)', `DELIVERY_TYPE` text COMMENT '配送物品类型(与AISW_MERCHANT_DICT表关联),多个以逗号隔开', `DELIVERY_NUMS` text COMMENT '配送物品数量(以逗号分隔)', `ACCEPT_STATUS` tinyint(1) DEFAULT '0' COMMENT '受理状态(0-未受理 1-已受理)', `STATUS` tinyint(1) DEFAULT '1' COMMENT '状态(0-无效 1-有效)', `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间', `CREATE_BY` varchar(50) DEFAULT NULL COMMENT '创建人', `UPDATE_BY` varchar(50) DEFAULT NULL COMMENT '更新人', `REMARKS` text COMMENT '备注', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='电竞酒店物品配送服务通知表'; -- aisw_e_service_fault_msg CREATE TABLE `aisw_e_service_fault_msg` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `ROOM_NO` varchar(10) DEFAULT NULL COMMENT '房间号', `MERCHANT_ID` int(11) DEFAULT NULL COMMENT '商家ID', `SERVICE_TYPE` varchar(50) DEFAULT NULL COMMENT '服务类型(与字典表AISW_MERCHANT_DICT表关联)', `USER_ID` int(11) DEFAULT NULL COMMENT '用户ID(与AISW_USER表关联)', `FAULT_TYPE` text COMMENT '保修物品(与AISW_MERCHANT_DICT表关联),多个以逗号隔开', `ACCEPT_STATUS` tinyint(1) DEFAULT '0' COMMENT '受理状态(0-未受理 1-已受理)', `STATUS` tinyint(1) DEFAULT '1' COMMENT '状态(0-无效 1-有效)', `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间', `CREATE_BY` varchar(50) DEFAULT NULL COMMENT '创建人', `UPDATE_BY` varchar(50) DEFAULT NULL COMMENT '更新人', `REMARKS` text COMMENT '备注', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='电竞酒店故障保修服务通知表'; -- aisw_e_service_invoice_msg CREATE TABLE `aisw_e_service_invoice_msg` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `ROOM_NO` varchar(10) DEFAULT NULL COMMENT '房间号', `MERCHANT_ID` int(11) DEFAULT NULL COMMENT '商家ID', `SERVICE_TYPE` varchar(50) DEFAULT NULL COMMENT '服务类型(与字典表AISW_MERCHANT_DICT表关联)', `USER_ID` int(11) DEFAULT NULL COMMENT '用户ID(与AISW_USER表关联)', `INVOICE_TYPE` tinyint(1) DEFAULT NULL COMMENT '发票类型(1-单位 2-个人)', `INVOICE_TITLE` varchar(100) DEFAULT NULL COMMENT '发票抬头', `INVOICE_TAX_NUMBER` varchar(50) DEFAULT NULL COMMENT '发票税号', `INVOICE_COMPANY_ADDRESS` varchar(100) DEFAULT NULL COMMENT '单位地址', `INVOICE_TELEPHONE` varchar(11) DEFAULT NULL COMMENT '电话号码', `INVOICE_BANK_ACCOUNT` varchar(50) DEFAULT NULL COMMENT '开户银行账户', `INVOICE_BANK_NAME` varchar(50) DEFAULT NULL COMMENT '开户银行名称', `ACCEPT_STATUS` tinyint(1) DEFAULT '0' COMMENT '受理状态(0-未受理 1-已受理)', `STATUS` tinyint(1) DEFAULT '1' COMMENT '状态( 0-无效 1-有效)', `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间', `CREATE_BY` varchar(50) DEFAULT NULL COMMENT '创建人', `UPDATE_BY` varchar(50) DEFAULT NULL COMMENT '更新人', `REMARKS` text COMMENT '备注', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='电竞酒店发票服务通知表'; -- aisw_e_service_one_checkout_msg CREATE TABLE `aisw_e_service_one_checkout_msg` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `ROOM_NO` varchar(10) DEFAULT NULL COMMENT '房间号', `MERCHANT_ID` int(11) DEFAULT NULL COMMENT '商家ID', `SERVICE_TYPE` varchar(50) DEFAULT NULL COMMENT '服务类型(与字典表AISW_MERCHANT_DICT表关联)', `USER_ID` int(11) DEFAULT NULL COMMENT '用户ID(与AISW_USER表关联)', `ACCEPT_STATUS` tinyint(1) DEFAULT '0' COMMENT '受理状态(0-未受理 1-已受理)', `STATUS` tinyint(1) DEFAULT '1' COMMENT '状态(0-无效 1-有效)', `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间', `CREATE_BY` varchar(50) DEFAULT NULL COMMENT '创建人', `UPDATE_BY` varchar(50) DEFAULT NULL COMMENT '更新人', `REMARKS` text COMMENT '备注', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='电竞酒店一键退房服务通知表'; -- aisw_e_service_wake_msg CREATE TABLE `aisw_e_service_wake_msg` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `MERCHANT_ID` int(11) DEFAULT NULL COMMENT '商家ID', `ROOM_NO` varchar(10) DEFAULT NULL COMMENT '房间号', `SERVICE_TYPE` varchar(50) DEFAULT NULL COMMENT '服务类型(与字典表AISW_MERCHANT_DICT表关联)', `USER_ID` int(11) DEFAULT NULL COMMENT '用户ID(与AISW_USER表关联)', `WAKE_DATE` varchar(50) DEFAULT NULL COMMENT '叫醒时间', `WAKE_PHONE` int(11) DEFAULT NULL COMMENT '叫醒手机号', `IS_KNOCK` tinyint(1) DEFAULT NULL COMMENT '是否敲门叫醒(0-否 1-是)', `ACCEPT_STATUS` tinyint(1) DEFAULT '0' COMMENT '受理状态(0-未受理 1-已受理)', `STATUS` tinyint(1) DEFAULT '1' COMMENT '状态(0-无效 1-有效)', `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间', `CREATE_BY` varchar(50) DEFAULT NULL COMMENT '创建人', `UPDATE_BY` varchar(50) DEFAULT NULL COMMENT '更新人', `REMARKS` text COMMENT '备注', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='电竞酒店叫醒服务通知表';
那我写后台就需要对应的写7个接口 和下面这些文件
Controller -> ServiceInterface -> ServiceImpl -> DaoMapper -> MapperXML
组长想了一个策略模式的办法:
只需要一个Controller 和 一个策略接口,但是后面的服务还是需要各自实现
首先看组长写的策略接口:
该接口只定义规范,7个资源共同遵守这个规范来获取资源
package cn.ymcd.aisw.common.strategy; import cn.ymcd.aisw.common.strategy.dto.RoomDTO; import com.baomidou.mybatisplus.core.metadata.IPage; import java.util.List; /** * 策略接口 * * @author wangkun * @version 1.0 * @projectName aisw-api * @date 2022年3月16日 11:27 */ public interface Strategy { /** * 新增服务 * * @param jsonParam 服务消息新增传入参数 * @return * @author wangkun * @createTime 2020/9/18 11:28 */ boolean doServiceDataAdd(String jsonParam); /** * 查询服务翻页 * @param jsonParam * @return com.baomidou.mybatisplus.core.metadata.IPage<java.lang.Object> * @author cloud9 * @createTime 2022/3/17 14:29 * */ IPage<? extends Object> doServiceDataPage(String jsonParam); /** * 消息受理更新 * @param jsonParam * @return boolean * @author cloud9 * @createTime 2022/3/23 19:18 * */ boolean doServiceDataUpdate(String jsonParam); }
然后组长对7个服务进行了枚举,绑定好实现类的名称
只有Key 和 Value
package cn.ymcd.aisw.common.strategy; /** * 策略枚举 * * @author wangkun * @version 1.0 * @projectName aisw-api * @date 2022年3月16日 11:27 */ public enum StrategyEnum { OPERATE_MERCHANT_SERVICE_CONNECT_IMPL("eServiceConnectService", "连接wifi服务实现类"), OPERATE_MERCHANT_SERVICE_CLEAN_IMPL("eServiceCleanMsgService", "清洁服务实现类"), OPERATE_MERCHANT_SERVICE_DELIVERY_IMPL("eServiceDeliveryMsgService", "商品配送服务实现类"), OPERATE_MERCHANT_SERVICE_FAULT_IMPL("eServiceFaultService", "故障保修服务实现类"), OPERATE_MERCHANT_SERVICE_INVOICE_IMPL("eServiceInvoiceService", "发票服务实现类"), OPERATE_MERCHANT_SERVICE_RENEWAL_IMPL("eServiceRenewalService", "续住服务实现类"), OPERATE_MERCHANT_SERVICE_WAKE_IMPL("eServiceWakeService", "唤醒服务实现类"), OPERATE_MERCHANT_SERVICE_ONE_CHECKOUT_IMPL("eServiceOneCheckoutService", "一键退房服务实现类"), TEST_IMPL("testServiceImpl", "测试实现类别名"), ; private String value; private String msg; StrategyEnum(String value, String msg) { this.value = value; this.msg = msg; } public String getValue() { return value; } public String getMsg() { return msg; } }
然后是一个策略装饰器?这个类叫StrategyFacade
最核心的调用在这里实现:
1、内部成员声明一个策略接口的Map容器
2、只存在一个这样的带参构造器,且该参数注解了自动装配?
所有业务实现类注解@Service会存在Bean实例,这个注解的意思像是会把业务Bean都装进来
Key 就是 业务Bean的名字 Value就是Bean的实例
3、获取策略类型,根据提供的channelCode,返回对应的Bean名字
再经过Map容器获取就能得到对应的Bean实例,再调用对应的业务实现类的方法
4、考虑翻页返回的是各个自己的业务PO,所以泛型规约成通用的Object,更新一类的操作则完全可以使用Boolean统一
package cn.ymcd.aisw.common.strategy; import com.baomidou.mybatisplus.core.metadata.IPage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * create by wangkun * Date 2018/06/25 */ @Service public class StrategyFacade { private final Map<String, Strategy> strategyMap = new ConcurrentHashMap<>(); /** * 注入所有实现了Strategy接口的Bean * * @param strategyMap */ @Autowired public StrategyFacade(Map<String, Strategy> strategyMap) { this.strategyMap.clear(); strategyMap.forEach((k, v) -> this.strategyMap.put(k, v)); } /** * 添加服务信息策略 * * @param channelCode 渠道code clean-清洁服务 delivery-商品配送服务 fault-故障保修服务 invoice-发票服务 renewal-续住服务 wake-唤醒服务 onecheckout-一键退房 * @param jsonParam 服务消息传入参数 * @return * @author wangkun * @createTime 2020/9/18 13:58 */ public boolean doServiceDataAdd(String channelCode, String jsonParam) { return strategyMap.get(getStrategyType(channelCode)).doServiceDataAdd(jsonParam); } /** * 查询服务信息策略 * * @param channelCode 渠道code clean-清洁服务 delivery-商品配送服务 fault-故障保修服务 invoice-发票服务 renewal-续住服务 wake-唤醒服务 onecheckout-一键退房 * @param jsonParam 服务消息传入参数 * @return * @author wangkun * @createTime 2020/9/18 13:58 */ public IPage<? extends Object> doServiceDataPage(String channelCode, String jsonParam) { return strategyMap.get(getStrategyType(channelCode)).doServiceDataPage(jsonParam); } /*** * 消息受理更新 * @param channelCode * @param jsonParam * @return boolean * @author 戴知舟 * @createTime 2022/3/23 19:17 * */ public boolean doServiceDataUpdate(String channelCode, String jsonParam) { return strategyMap.get(getStrategyType(channelCode)).doServiceDataUpdate(jsonParam); } /** * 根据不同条件生成不同的策略(使用策略场景变化是需要修改) * * @param channelCode 渠道code * @return * @author wangkun * @createTime 2020/9/18 14:00 */ private static String getStrategyType(String channelCode) { switch (channelCode) { case "connect": //连接WIFI服务信息处理 return StrategyEnum.OPERATE_MERCHANT_SERVICE_CONNECT_IMPL.getValue(); case "clean": //清洁服务信息处理 return StrategyEnum.OPERATE_MERCHANT_SERVICE_CLEAN_IMPL.getValue(); case "delivery": //商品配送服务信息处理 return StrategyEnum.OPERATE_MERCHANT_SERVICE_DELIVERY_IMPL.getValue(); case "fault": //故障保修服务信息处理 return StrategyEnum.OPERATE_MERCHANT_SERVICE_FAULT_IMPL.getValue(); case "invoice": //发票服务信息处理 return StrategyEnum.OPERATE_MERCHANT_SERVICE_INVOICE_IMPL.getValue(); case "renewal": //续住服务信息处理 return StrategyEnum.OPERATE_MERCHANT_SERVICE_RENEWAL_IMPL.getValue(); case "wake": //唤醒服务信息处理 return StrategyEnum.OPERATE_MERCHANT_SERVICE_WAKE_IMPL.getValue(); case "oneCheckout": //一键退房服务信息处理 return StrategyEnum.OPERATE_MERCHANT_SERVICE_ONE_CHECKOUT_IMPL.getValue(); default: //测试类 return StrategyEnum.TEST_IMPL.getValue(); } } }
在Controller中的调用是这样的:
根据约定好的channelCode来完成不同业务的调用
package cn.ymcd.aisw.room.controller; import cn.ymcd.aisw.common.Constant; import cn.ymcd.aisw.common.strategy.StrategyFacade; import cn.ymcd.aisw.common.strategy.dao.EServiceBasicDAO; import cn.ymcd.aisw.room.dto.EServiceMsgDTO; import cn.ymcd.comm.base.BaseController; import cn.ymcd.wss.util.json.JacksonUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.http.util.Asserts; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; /** * aisw_e_service_msg 电竞酒店服务通知表 前端控制器 * * @projectName: * @author:daizhizhou * @date:2022-03-14 * @version 1.0 */ @Api(tags = "客房服务 - 服务申请") @RestController @RequestMapping("${sys.path}/room/message") public class EServiceMsgController extends BaseController { @Autowired private StrategyFacade strategyFacade; @Autowired private EServiceBasicDAO eServiceBasicDAO; /** * /sys/room/message/page * @param json * @return com.baomidou.mybatisplus.core.metadata.IPage<cn.ymcd.aisw.room.dto.EServiceMsgDTO> * @author cloud9 * @createTime 2022/3/17 15:09 * { * "channelCode":"wake", * "jsonParam":{ * "merchantId":"1001", * "serviceType":"360000002", * "unionId":"o9K950jpDrqIRiR_4pNqT89RgEhs", * "cleanDate":"12:00", * "page": { * "size": 10, * "current: 1 * } * } */ @ApiOperation(value = "消息通知查询", notes = "消息通知查询") @PostMapping("/page") public IPage<Object> doServiceDataPage(@RequestBody String json) { Asserts.notEmpty(json, "传入参数为空"); Map<String, Object> map = JacksonUtil.jsonToMap(json); String channelCode = map.get("channelCode").toString(); String jsonParam = map.get("jsonParam").toString(); return (IPage<Object>)strategyFacade.doServiceDataPage(channelCode, jsonParam); } }
其中的一个策略实现类,就是业务实现类:
注意@Service注解的名称,通过这个Bean名称决定Spring是否装配进Map容器
package cn.ymcd.aisw.common.strategy.impl; import cn.ymcd.aisw.common.CommonExtendUtils; import cn.ymcd.aisw.common.strategy.Strategy; import cn.ymcd.aisw.common.strategy.dao.EServiceBasicDAO; import cn.ymcd.aisw.common.strategy.dao.EServiceCleanMsgDAO; import cn.ymcd.aisw.common.strategy.dao.UserDAO; import cn.ymcd.aisw.common.strategy.dto.EServiceCleanMsgDTO; import cn.ymcd.aisw.common.strategy.dto.UserDTO; import cn.ymcd.wss.util.json.JacksonUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * 清洁服务策略 * * @author wangkun * @version 1.0 * @projectName aisw-api * @date 2022年03月16日 10:47 */ @Service("eServiceCleanMsgService") public class EServiceCleanMsgServiceImpl implements Strategy { @Resource private EServiceCleanMsgDAO eServiceCleanMsgDAO; @Resource private UserDAO userDAO; @Resource private EServiceBasicDAO eServiceBasicDAO; /** * * @param jsonParam * @return com.baomidou.mybatisplus.core.metadata.IPage<cn.ymcd.aisw.common.strategy.dto.EServiceCleanMsgDTO> * @author cloud9 * @createTime 2022/3/17 15:01 * * { * "channelCode":"clean", * "jsonParam":{ * "merchantId":"1001", * "serviceType":"360000002", * "unionId":"o9K950jpDrqIRiR_4pNqT89RgEhs", * "cleanDate":"12:00", * "page": { * "size": 10, * "current: 1 * } * } * } * */ @Override public IPage<EServiceCleanMsgDTO> doServiceDataPage(String jsonParam) { EServiceCleanMsgDTO cleanMsgDTO = JacksonUtil.jsonToObject(jsonParam, EServiceCleanMsgDTO.class); // cleanMsgDTO.setMerchantId(merchantBasicDAO.getMerchantIdBySysUserId(LoginUserContext.getUser().getId())); cleanMsgDTO.setMerchantId(eServiceBasicDAO.getMerchantIdBySysUserId("user1001")); return eServiceCleanMsgDAO.cleanServiceMsgPage(cleanMsgDTO.getPage(), cleanMsgDTO); } }
最后是接口测试
里面存在的一些问题:
有些业务操作是在小程序端的
小程序不需要翻页和消息查询,只是提供给酒店用户这些服务功能
但是这个业务所在的实现类必须也要按照策略接口实现方法,实现,但是不需要这个资源
有那么一点点资源浪费