掘金 后端 ( ) • 2024-04-18 09:56

theme: smartblue

背景

最近公司有一个播报打印的功能,需要上线,功能更是比较简单,就是根据不同的订单类型进行一个报文的组合,最后将报文推送到阿里云平台。细细思索一番,其实能感觉到,不论播报还是打印其实都是一个推送的行为,所有只要将推送的步骤拆分出来,由子类或者父类去实现,并按顺序去执行每个步骤。就可以很方便的维护和扩展,所以就考虑使用模板方法进行设计

模式介绍

模板方法

通常定义就是一个算法的步骤,并允许子类为一个或多个步骤进行实现。通俗讲,就是定义好一个行为的轮廓和骨架,有自由选择是否对其重写

实战

首先思考,播报打印的一个流程大概有几个步骤,基于我们现在的业务,大概可以分为 1.获取设备列表 2.解析报文 3.推送到阿里云

@Slf4j
public abstract class IotTemplate {
    ...对象引入
    List<GetDeviceDTO> findDeviceList(Integer orderType, Integer stationId, String userId){
        if (Objects.isNull(stationId)) {
            throw OilCommonException.INVALID_PARAM_ERROR.newInstance("油站id不存在");
        }
        // 根据收银员id和油站id获取设备sn
        List<GetDeviceDTO> allDevice = oilEquipmentSnDAO.getDeviceByStationId(orderType,stationId);
        if (!StringUtils.isBlank(userId)) {
            List<GetDeviceDTO> device = oilEquipmentSnDAO.getDevice(userId, orderType,stationId);
            allDevice.addAll(device);
        }
        return allDevice;
    }
    
    /**
     * 设备推送
     * @param content 文本
     * @param getDeviceDTO 设备信息
     */
    public abstract AliyunMqttPushModel aliyunPush(String logo, String content, GetDeviceDTO getDeviceDTO);

    /**
     * 解析文本
     * @return 打印文本
     */
    public abstract String explainContent(ExplainContentForm explainContentForm);
}

由于获取设备是个比较通用的逻辑,所以可以选择直接实现,子类只需要继承即可,有了算法行为,我们还需要一个模板方法,将所有行为按一定的顺序进行执行

    public void push(IotTemplateForm iotTemplateForm){
        LogUtil.info(log,"IotTemplate.push >> param:{}",iotTemplateForm);
        Integer stationId = iotTemplateForm.getStationId();
        String orderSn = iotTemplateForm.getOrderSn();
        String userId = iotTemplateForm.getUserId();
        Integer orderType = iotTemplateForm.getOrderType();
        String requestId = OilIdUtil.createOnlyId(IdRuleEnum.ALI_MSG.getPrefix());
        if (Objects.isNull(stationId)) {
            throw OilCommonException.INVALID_PARAM_ERROR.newInstance("油站id不存在");
        }
        OilOrderDO order = getOrder(orderSn);
        // 获取设备列表
        List<GetDeviceDTO> allDevice = findDeviceList(orderType,stationId,userId);
        // 钩子函数
        filterDevice(allDevice);
        allDevice.forEach(d -> {
            JSONObject orderJson = JSONObject.parseObject(JSONObject.toJSONString(order));
            ExplainContentForm explainContentForm = new ExplainContentForm();
            explainContentForm.setContent(d.getContent());
            explainContentForm.setOrderJson(orderJson);
            explainContentForm.setTicketModelList(iotTemplateForm.getTicketModelList());
            // 文本解析
            String content = explainContent(explainContentForm);
            LogUtil.info(log, "IotTemplate.push >> 开始推送 >> content = {} device = {}", content,d);
            // 异步推送
            push(orderSn, requestId, d, content,iotTemplateForm.getLogo());
        });
    }
    
    @Async(value = "aliyunPushAsyncExecutor")
    public void push(String orderSn, String requestId, GetDeviceDTO d, String content,String logo) {
        // 插入推送消息记录
        insertMessageInfo(orderSn, BizTypeEnum.VOICEDOWNLOAD.getValue(), d, requestId, content);    
        // 阿里云推送
        AliyunMqttPushModel aliyunMqttPushModel = aliyunPush(logo,content, d);
        // 记录数据推送状态
        updateMessageInfo(aliyunMqttPushModel, requestId);
    }    
    
    /**
     *  钩子函数 过滤设备
     * @param allDevice 设备列表
     */
    public abstract void filterDevice(List<GetDeviceDTO> allDevice);    

这样子就完成了一个简易的算法模板类,之后只需要继承这个类,重写需要实现的算法行为,就可以很快速的完成整个推送的流程

/**
 * 打印模板
 *
 */
@Slf4j
@AllArgsConstructor
@Service
public class PrinterTemplate extends IotTemplate {
    @Override
    public String explainContent(ExplainContentForm explainContentForm) {
        List<TicketModel> ticketModelList1 = explainContentForm.getTicketModelList();
        List<TicketModel> ticketModelList = BeanUtil.copyToList(ticketModelList1, TicketModel.class);
        JSONObject orderJson = explainContentForm.getOrderJson();
        StringBuilder receipt = new StringBuilder();
        TicketModel title = ticketModelList.remove(0);
        receipt.append(ReceiptConstants.logo);
        receipt.append(String.format(ReceiptConstants.title, title.getLabelValue()));
        receipt.append(ReceiptConstants.underline);
        ticketModelList.forEach(ticketModel -> {
            if (CollectionUtils.isEmpty(ticketModel.getChild())) {
                return;
            }
            ticketModel.getChild().forEach(childTicketModel -> {
                if (childTicketModel.getLabelName().contains("二维码")) {
                    receipt.append(String.format(ReceiptConstants.qrCode, explainQrCode(childTicketModel.getLabelValue())));
                    return;
                }
                receipt.append(String.format(ReceiptConstants.content,
                        childTicketModel.getLabelName(), TicketLabelEnum.getColName(childTicketModel.getLabelValue(), orderJson)));
            });
            receipt.append(ReceiptConstants.underline);
        });
        receipt.append(ReceiptConstants.movePaper);
        return receipt.toString();
    }

    @Override
    public void filterDevice(List<GetDeviceDTO> allDevice) {
        List<GetDeviceDTO> collect = allDevice.stream().filter(getDeviceDTO -> getDeviceDTO.getHasPrint() == 2).collect(Collectors.toList());
        allDevice.removeAll(collect);
    }

    private String explainQrCode(String labelValue) {
        byte[] bytes = HttpUtil.downloadBytes(labelValue);
        bytes = bytes == null ? new byte[0] : bytes;
        InputStream inputStream = new ByteArrayInputStream(bytes);
        String decode = QrCodeUtil.decode(inputStream);
        LogUtil.info(log, "PrinterTemplate.explainQrCode  >> labelValue:{},decode:{}", labelValue, decode);
        return decode;
    }

    @Override
    public AliyunMqttPushModel aliyunPush(String logo, String content, GetDeviceDTO device) {
        LogUtil.info(log, "PrinterTemplate.aliyunPush  >> content:{} device:{}", content, device);
        String requestId = OilIdUtil.createOnlyId(IdRuleEnum.ALI_MSG.getPrefix());
        JSONObject resultMap = new JSONObject();
        resultMap.put("platformType", 3);
        resultMap.put("source", 3);
        resultMap.put("orderId", requestId);
        resultMap.put("content", content);
        resultMap.put("uplogo", logo);
        resultMap.put("cn", 1);
        AliyunMqttPushForm form = new AliyunMqttPushForm();
        if (StringUtils.isNotBlank(device.getIotInstanceId())) {
            form.setIotInstanceId(sysConfig.getIotInstanceId());
        }
        form.setProductKey(device.getProductKey());
        form.setDeviceName(device.getDeviceName());
        form.setSn(device.getSn());
        form.setContent(JSONObject.toJSONString(resultMap));
        return aliyunMqttPushManager.pushMessage(form);
    }
}

如果后续还有打印的扩展,甚至可以直接继承PrinterTemplate方法,根据自己的需求添加新的逻辑。

例如我有一个新的补打逻辑,整体打印逻辑与原先的打印逻辑一致,但是有个获取订单的逻辑稍微有点区别,我就可以选择直接继承PrinterTemplate方法,去重写其中获取订单的方法即可

@Slf4j
@AllArgsConstructor
@Service
public class RePrinterTemplate extends PrinterTemplate {
    private OilOrderDAO oilOrderDAO;
    private OilUsersDAO oilUsersDAO;
    private OilStationDAO oilStationDAO;

    @Override
    protected OilOrderDO getOrder(String orderSn) {
        OilOrderDO oilOrderDO = oilOrderDAO.getOilOrderByOrderNo(orderSn);
        //油站名称
        OilStationDO oilStationDO = oilStationDAO.getStationById(oilOrderDO.getStationId());
        if (Objects.nonNull(oilStationDO) && StringUtils.isNotEmpty(oilStationDO.getStationName())) {
            oilOrderDO.setStationName(oilStationDO.getStationName());
        }
        //收银员名称
        if (Objects.isNull(oilOrderDO.getUserId()) || IntegerPool.ZERO.equals(oilOrderDO.getUserId())) {
            oilOrderDO.setUserName("管理员");
        } else {
            OilUsersDO oilUsersDO = oilUsersDAO.getOilUserById(oilOrderDO.getUserId());
            if (Objects.nonNull(oilUsersDO)) {
                if (Objects.equals(0, oilUsersDO.getBelongId())) {
                    oilOrderDO.setUserName("管理员");
                } else if (StringUtils.isNotEmpty(oilUsersDO.getUsername())){
                    oilOrderDO.setUserName(oilUsersDO.getUsername());
                }
            }
        }
        return oilOrderDO;
    }
}

总结

总结来说模板方法主要包含

抽象类:负责给出一个算法的轮廓和骨架,它由一个模板方法和若干个基本方法构成。

模板方法:定义了算法的骨架,按某种顺序调用执行不同基本方法

基本方法:是实现算法各个步骤的方法,可以是抽象方法,也可以是普通方法,再模板方法中进行组合

具体子类:实现抽象类中所定义的抽象方法和钩子方法

其实设计模式更多是一个规范,规范每个人的开发行为,遵循这个规范,可以使得各个开发者的沟通成本快速下降