策略模式的应用

news2025/2/25 17:13:45

前言

系统有一个需求就是采购员审批注册供应商的信息时,会生成一个供应商的账号,此时需要发送供应商的账号信息(账号、密码)到注册填写的邮箱中,通知供应商账号信息,当时很快就写好了一个工具类,用来发送普通的文本邮件信息。但是随着系统的迭代,后面又新增了一些需求,比如一些单据需要在供应商确认时,发送一条站内信到首页,这样采购员登录时就可以看到最新的单据信息,进行相应的处理;或者采购员创建一些单据时,需要发送站内信到首页,然后供应商登录系统时,可以看到最新单据信息并进行处理,因此,我在原有的工具类基础上,修改发送邮件信息的方法,加入了消息类型参数,并根据消息类型,调用相应的方法处理;过了一段时间,业务又找了过来,说当用户修改密码时,需要发送一个短信验证码,验证码输对了才给他修改,接着我又在工具类里面,加入了处理短信的发送逻辑。

伪代码如下:

@Component
public class NoticeSendUtils {
    // 省略其他配置

    /**
     * 发送消息
     *
     * @param params 参数
     * @param type 消息类型(0-邮件消息,1-站内信消息,2-短信消息)
     * @param content 消息内容
     */
    public void sendMessage(Object params, Integer type, String content) {
        if (type.equals(0)) {
            this.sendMailMessage(params, content);
        } else if (type.equals(1)) {
            this.sendStationMessage(params, content);
        } else {
            this.sendPhoneMessage(params, content);
        }
    }


    /**
     * 发送邮件消息
     * 
     * @param params
     * @param content
     */
    private void sendMailMessage(Object params, String content) {
        // 处理邮件消息
    }

    /**
     * 发送站内信消息
     * 
     * @param params
     * @param content
     */
    private void sendStationMessage(Object params, String content) {
        // 处理站内信消息
    }

    /**
     * 发送短信消息
     * 
     * @param params
     * @param content
     */
    private void sendPhoneMessage(Object params, String content) {
        // 处理短信消息
    }

存在问题

  • 当需要新增一种消息发送类型时,需要修改该工具类加上if-else逻辑,处理新的消息类型发送,这违背了开放封闭原则(软件实体应该对扩展开放,对修改封闭。这意味着当软件需要适应新的需求时,应该通过添加新的代码来扩展系统的行为,而不是修改已有的代码),新增一种消息类型,就要修改该类原有的方法
  • 调用者调用时,需要指定消息类型和内容,系统就会存在大量这样的调用代码,如果需要在发送消息的方法新增参数,那么所有调用者都需要改变新增参数,系统后期就会非常难维护
  • 没有对消息发送过程产生的异常进行处理,无法知晓消息有没有发送成功

因此,趁着最近没有什么需求,对消息发送功能采用策略模式进行了重构,由消息模板的类型决定调用相应的消息类型处理类处理消息发送,独立维护了一个消息中心模块,也提供页面管理功能,可对消息发送模板进行配置,并且存储了消息发送记录,这样可以知晓消息有没有发送成功,对原有的消息发送功能进行了解耦。

以下仅提供部分核心代码和相关表设计,关键的是其中的设计思想

使用

消息规则配置

主要配置发送方的邮箱配置和短信功能账号配置,系统采用了阿里云的短信服务,所以配置了阿里云的短信服务的账号和密码;在发送消息时,先查一下这里面的配置,比如发送邮箱消息,则查询规则类型为邮箱的信息,查询到了就调用相应的方法发送消息

如图所示

在这里插入图片描述

消息模板配置

主要配置消息模板,每个消息模板都有唯一的模板编码,一个消息模板可以有多个适用规则,比如一个模板有短信和站内信的适用规则,那么当调用者使用这个模板时,会同时发送一个站内信(首页待办消息展示)和一封邮件信息

列表页面如图所示

在这里插入图片描述

修改页面如图所示

  • 短信相关配置只有适用规则为短信才必填
  • PC-地址主要是为了站内信实现点击消息时,跳转到对应页面
  • 短信模板编码由阿里云短信服务提供
  • 调用者的参数字段名称需要和模板内容的${}表达式中的名称一致(使用了freemarker进行模板渲染)

在这里插入图片描述

消息发送记录

主要查看消息有没有发送成功

在这里插入图片描述

消息接收中心

主要显示站内信发送情况

在这里插入图片描述

设计

消息规则配置表

CREATE TABLE `msg_configuration` (
  `id` varchar(36) NOT NULL COMMENT '主键',
  `code` varchar(255) DEFAULT NULL COMMENT '编码',
  `ip` varchar(255) DEFAULT NULL COMMENT 'ip',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `port` varchar(50) DEFAULT NULL COMMENT '端口',
  `protocol` varchar(100) DEFAULT NULL COMMENT '协议名称',
  `type` int(11) DEFAULT NULL COMMENT '0邮箱 1短信',
  `username` varchar(255) DEFAULT NULL COMMENT '用户名',
  `enable` int(11) DEFAULT NULL COMMENT '0未启用 1启用',
  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
  `creator` varchar(36) DEFAULT NULL COMMENT '创建人',
  `update_date` datetime DEFAULT NULL COMMENT '修改时间',
  `modifier` varchar(36) DEFAULT NULL COMMENT '最后修改人',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='消息规则配置表';

消息模板配置表

CREATE TABLE `msg_public_template` (
  `id` varchar(36) NOT NULL COMMENT '主键',
  `code` varchar(200) DEFAULT NULL COMMENT '模板编号',
  `sys_notice_content` mediumtext COMMENT '模板内容',
  `message_code` varchar(100) DEFAULT NULL COMMENT '短信编码',
  `message_type_code` varchar(200) DEFAULT NULL COMMENT '消息类型  1站内信 2邮件 3短信',
  `name` varchar(200) DEFAULT NULL COMMENT '模板名称',
  `notice_type_code` tinyint(2) DEFAULT NULL COMMENT '通知类型快码',
  `service_module_code` varchar(100) DEFAULT NULL COMMENT '业务模块快照编码',
  `template_type_code` tinyint(7) DEFAULT NULL COMMENT '模板类型快照编码',
  `title` varchar(200) DEFAULT NULL COMMENT '消息模板标题',
  `pc_url` varchar(255) DEFAULT NULL COMMENT 'PC-跳转地址',
  `business_obj_id` varchar(36) DEFAULT NULL COMMENT '业务对象',
  `notice_enabled_flag` tinyint(2) DEFAULT NULL COMMENT '通知是否启用(1.启用/0.不启用)',
  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
  `creator` varchar(36) DEFAULT NULL COMMENT '创建人',
  `update_date` datetime DEFAULT NULL COMMENT '修改时间',
  `modifier` varchar(36) DEFAULT NULL COMMENT '最后修改人',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='消息模板配置表';

消息发送记录表

CREATE TABLE `msg_send_record` (
  `id` varchar(36) NOT NULL,
  `content` mediumtext COMMENT '发送内容',
  `msg_public_template_id` varchar(200) DEFAULT NULL COMMENT '消息模板Id',
  `read_flag` tinyint(2) DEFAULT NULL COMMENT '已读状态(1.已读/0.未读)',
  `receiver_name` varchar(100) DEFAULT NULL COMMENT '接收人姓名',
  `receiver_uid` varchar(36) DEFAULT NULL COMMENT '接收人主键',
  `send_time` datetime DEFAULT NULL COMMENT '发送时间',
  `send_type` tinyint(2) DEFAULT NULL COMMENT '通知渠道 1站内信 2邮件 3短信',
  `status` tinyint(2) DEFAULT NULL COMMENT '发送状态(1.发送中/2.发送成功/3.发送失败)',
  `title` varchar(200) DEFAULT NULL COMMENT '发送主题',
  `business_id` varchar(36) DEFAULT NULL COMMENT '业务id(跳转页面链接可以拼接相关id跳转)',
  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
  `creator` varchar(36) DEFAULT NULL COMMENT '创建人',
  `update_date` datetime DEFAULT NULL COMMENT '修改时间',
  `modifier` varchar(36) DEFAULT NULL COMMENT '最后修改人',
  `error_msg` varchar(1000) DEFAULT NULL COMMENT '错误信息',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='消息发送记录表';

实现

如图所示,经过策略模式设计如下,后续有新的消息类型增加,只需要新增一个具体策略类实现相关发送逻辑即可,无需修改原有的代码,没有违背开放封闭原则

  • 消息发送类型的抽象策略类NoticeExchanger,规定了具体策略类必须重写的抽象方法match(是否支持当前消息类型发送)、exchanger(处理消息发送),以及自己实现的saveMessageRecord方法(保存消息发送记录)、parseMessage方法(解析模板内容和标题)
  • 具体策略类EmailNoticeExchanger,负责邮件消息的发送
  • 具体策略类StationNoticeExchanger,负责站内信的发送
  • 具体策略类SmsNoticeExchanger,负责短信消息的发送
  • 环境类NoticeServiceImpl,维护一个策略对象的引用集合,负责将消息发送请求委派给具体的策略对象执行
在这里插入图片描述
抽象策略类NoticeExchanger
public abstract class NoticeExchanger {

    @Resource
    private MsgSendRecordService msgSendRecordService;


    /**
     * 是否支持当前消息类型发送(true-支持,false-不支持)
	 
     * @param type 消息类型
     * @return
     */
    public abstract boolean match(String type);

    /**
     * 处理消息发送
	 *
     * @param map 相关参数
     * @return
     */
    public abstract boolean exchanger(Map<String, Object> map) throws Exception;


    /**
     * 使用Freemarker解析模板内容和标题
	 *
     * @param notice 相关参数
     * @return
     */
    public Map<String, Object> parseMessage(Map<String, Object> notice){
        MsgPublicTemplate msgPublicTemplate = notice.get("msgPublicTemplate");
        if(msgPublicTemplate==null){
            throw new CommonException(ExceptionDefinition.TEMPLATE_NOT_FOUND);
        }
        if(msgPublicTemplate.getNoticeEnabledFlag().intValue()==0){
            throw new CommonException(ExceptionDefinition.TEMPLATE_NOT_ENABLED);
        }
        //freemarker解析模板,填充模板内容
        //标题
        String title=msgPublicTemplate.getTitle();
        //内容
        String sysNoticeContent = msgPublicTemplate.getContent();
        Map<String, Object> params = notice.get("params");
        try {
            title= FreemarkerUtils.generateContent(params,title);
            sysNoticeContent=FreemarkerUtils.generateContent(params,sysNoticeContent);
        } catch (Exception e) {
            throw new CommonException(ExceptionDefinition.TRANSFORMATION_OF_THE_TEMPLATE);
        }
		Map<String, Object> result = new HashMap<>();
		result.put("title", title);
		result.put("sysNoticeContent", sysNoticeContent);
        return result;
    }

    /**
     * 保存消息发送记录
	 *
     * @param msgSendRecordDto 相关参数
     * @return
     */
    public void saveSendMessage(MsgSendRecordDto msgSendRecordDto){
        // ...参数校验
        String [] ids = msgSendRecordDto.getUserId().split(",");
        String [] names = msgSendRecordDto.getUserName().split(",");
        // ...参数填充
        //是否多个用户
        if (ids.length == 0) {
            msgSendRecord.setReceiverName(msgSendRecordDto.getUserName())
                    .setReceiverUid(msgSendRecordDto.getUserId());
            msgSendRecordService.save(msgSendRecord);
        }
        if (ids.length > 0) {
            List<MsgSendRecord> msgSendRecordList = Lists.newArrayList();
            for (int i = 0; i < ids.length; i++) {
                MsgSendRecord data = BeanUtils.copyProperties(msgSendRecordDto, MsgSendRecord.class);
                data.setReceiverUid(ids[i]);
                data.setReceiverName(names[i]);
                msgSendRecordList.add(data);
            }
            msgSendRecordService.saveBatch(msgSendRecordList);
        } 
    }
}
具体策略类EmailNoticeExchanger

发送邮件

@Component
public class EmailNoticeExchanger extends NoticeExchanger {
    private Logger logger = LoggerFactory.getLogger(EmailNoticeExchanger.class);
    @Autowired
    private ISendEmailService sendEmailService;
    @Autowired
    private MsgConfigurationMapper msgConfigurationMapper;

    /**
     * 是否支持邮件发送
     *
     * @param type 消息类型
     * @return
     */
    @Override
    public boolean match(String type) {
        if (!String.valueOf(SendTypeEnum.EMAIL.getItem()).equals(type)) {
            return false;
        }
        return true;
    }

     /**
     * 处理消息发送
	 *
     * @param map 相关参数
     * @return
     */
    @Override
    public boolean exchanger(Map<String, Object> map) throws Exception {
        EmailNotice notice = new EmailNotice();
        BeanUtils.populate(notice, map);
        String code = notice.getCode();
        Map<String, Object> params = notice.getParams();
		// 解析模板内容和标题
        Map<String, Object> objectMap = parseMessage(map);
		String title = objectMap.get("title") == null ? "" : objectMap.get("title").toString();
		String sysNoticeContent = objectMap.get("sysNoticeContent") == null ? "" : objectMap.get("sysNoticeContent").toString();
		try {
			// 查询邮箱配置
			MsgConfiguration msgConfiguration = 省略...
			if (msgConfiguration == null) {
				throw new CommonException(ExceptionDefinition.NO_LAUNCH_CONFIGURATION);
			}
			// 组装参数发送邮件
			EmailConfig emailConfig = new EmailConfig();
			emailConfig.setUsername(msgConfiguration.getUsername());
			emailConfig.setPassword(msgConfiguration.getPassword());
			emailConfig.setMailServerHost(msgConfiguration.getIp());
			emailConfig.setMailServerPort(msgConfiguration.getPort());
			emailConfig.setProtocol(msgConfiguration.getProtocol());
			emailConfig.setFromAddress(msgConfiguration.getUsername());

			MailData mailData = new MailData();
			mailData.setSubject(title);
			mailData.setContent(sysNoticeContent);
			mailData.setToAddresss(notice.getToAddress());
			mailData.setCcAddresss(notice.getCcAddress());
			//发送邮件
			sendEmailService.sendMail(mailData, emailConfig);
			// 省略组装参数...
			// 保存发送记录
			saveSendMessage(msgSendRecordDto);
			logger.info("send email success!");
			return true;
		} catch (Exception e) {
			logger.error(e.getMessage());
			// 省略组装参数...
			// 保存发送记录
			saveSendMessage(msgSendRecordDto);
			return false;
		}
        return false;
    }
}
具体策略类StationNoticeExchanger

发送站内信

@Component
public class StationNoticeExchanger extends NoticeExchanger {
    private Logger logger = LoggerFactory.getLogger(StationNoticeExchanger.class); 

    /**
     * 是否支持站内信发送
     *
     * @param type 消息类型
     * @return
     */
    @Override
    public boolean match(String type) {
        if (!String.valueOf(SendTypeEnum.STATION.getItem()).equals(type)) {
            return false;
        }
        return true;

    }

     /**
     * 处理消息发送
	 *
     * @param map 相关参数
     * @return
     */
    @Override
    public boolean exchanger(Map<String, Object> map) throws Exception {
        logger.info("=========== send station begin !========================");
        StationNotice notice = new StationNotice();
        BeanUtils.populate(notice, map);
		// 解析模板内容和标题
        Map<String, Object> objectMap = parseMessage(map);
		String title = objectMap.get("title") == null ? "" : objectMap.get("title").toString();
		String sysNoticeContent = objectMap.get("sysNoticeContent") == null ? "" : objectMap.get("sysNoticeContent").toString();
		// 发送站内信即保存发送记录即可,记录类型为站内信
		MsgSendRecordDto msgSendRecordDto = new MsgSendRecordDto();
		// 省略组装参数...
	    // 保存发送记录
	    saveSendMessage(msgSendRecordDto);
		logger.info("=================send station success!==========================");
        return true;
    }
}
具体策略类SmsNoticeExchanger

发送短信

@Component
public class SmsNoticeExchanger extends NoticeExchanger{
	
    private Logger logger = LoggerFactory.getLogger(SmsNoticeExchanger.class); 

    @Autowired
    private ISendSmsService sendSmsService;

    @Autowired
    private MsgConfigurationMapper msgConfigurationMapper;


    @Override
    public boolean match(String type) {
        if(!String.valueOf(SendTypeEnum.SMS.getItem()).equals(type)){
            return false;
        }
        return true;
    }

    @Override
    public boolean exchanger(Map<String, Object> map) {
        SmsNotice notice = new SmsNotice();
        BeanUtils.populate(notice, map);
        // 解析模板内容和标题,这里的模板内容和标题只在发送记录使用,短信的模板内容配置在了阿里云短信服务
        Map<String, Object> objectMap = parseMessage(map);
		String title = objectMap.get("title") == null ? "" : objectMap.get("title").toString();
		String sysNoticeContent = objectMap.get("sysNoticeContent") == null ? "" : objectMap.get("sysNoticeContent").toString();
		try {
			// 查询短信配置
			MsgConfiguration msgConfiguration = 省略...
			if(msgMailConfiguration == null){
				throw new CommonException(ExceptionDefinition.SEND_CHANNELS);
			}
			logger.info("send sms success begin !");
			//发送短信,填充阿里云用户名、密码、短信模板编码、参数等等,调用阿里云api发送短信
			sendSmsService.sendSms(notice, msgConfiguration);
			// 省略组装参数...
			// 保存发送记录
			saveSendMessage(msgSendRecordDto);
			logger.info("send sms success!");
		} catch (Exception e) {
			// 省略组装参数...
			// 保存发送记录
			saveSendMessage(msgSendRecordDto);
			logger.error(e.getMessage());
			throw new CommonException(ExceptionDefinition.SEND_SMS_EXCEPTIONS);
		}
        return true;
    }
}
消息类型枚举类
public enum SendTypeEnum {
    /**
     * 通知渠道类型
     */
    STATION(1,"站内信"),
    EMAIL(2,"邮件"),
    SMS(3,"短信");


    private int item;

    private String itemName;

    SendTypeEnum(int item, String itemName) {
        this.item = item;
        this.itemName = itemName;
    }

    public int getItem() {
        return item;
    }

    public void setItem(int item) {
        this.item = item;
    }

    public String getItemName() {
        return itemName;
    }

    public void setItemName(String itemName) {
        this.itemName = itemName;
    }

    public static String getItemName(int item){
        for (SendTypeEnum es : SendTypeEnum.values()){
            if(item == es.getItem()){
                return es.getItemName();
            }
        }
        return "";
    }
}
环境类NoticeServiceImpl

负责将消息发送请求委派给具体的策略对象执行

@Service
@Slf4j
public class NoticeServiceImpl implements NoticeService,ApplicationContextAware {
    
	// 保存所有的消息策略类
    private Collection<NoticeExchanger> exchangers;

    // 线程池,异步发送消息
    private ExecutorService executorService;
	
	
	@Resource
    private NoticeConvertUtils noticeConvertUtils;

    @Resource
    private MsgPublicTemplateMapper msgPublicTemplateMapper;

    public NoticeServiceImpl(){
		// 创建线程池
        Integer availableProcessors = Runtime.getRuntime().availableProcessors();
        Integer numOfThreads = availableProcessors * 2;
        executorService = new ThreadPoolExecutor(availableProcessors,numOfThreads,100, TimeUnit.SECONDS,new LinkedBlockingDeque<>());

    }
	
	/**
     * 当前Bean初始化之前会执行当前方法,获取所有的消息策略类
     */
	@Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		// 获取实现了NoticeExchanger接口的所有bean
        Map<String, NoticeExchanger> beansOfType = applicationContext.getBeansOfType(NoticeExchanger.class);
        this.exchangers=beansOfType.values();
    }
	
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void sendMessage(NoticeParamDto noticeParamDto) {
        Map<String, Object> notice = null;
        try {
			// 将参数转换成map
            notice = noticeConvertUtils.sendMessageIsParam(notice);
        } catch (Exception e) {
            log.error("消息发送失败,消息模板内容转换失败", e);
            throw new CommonException("消息发送失败,消息模板内容转换失败", 999);
        }
		
        if(notice.get("code") == null){
            throw new CustomException(CommonCode.NO_TEMPLATE);
        }
        QueryWrapper<MsgPublicTemplate> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("code", notice.get("code").toString());
        MsgPublicTemplate msgPublicTemplate  = msgPublicTemplateMapper.selectOne(queryWrapper);
        notice.put("msgPublicTemplate",msgPublicTemplate);
        if(msgPublicTemplate.getMessageTypeCode() == null){
            throw new CustomException(CommonCode.NO_TEMPLATE);
        }
		// 获取当前消息模板的类型,以逗号隔开,由所有的消息策略类去匹配类型,匹配成功则提交任务给线程池异步执行
        String[] array = msgPublicTemplate.getMessageTypeCode().split(",");
        for(int i = 0; i < array.length; i++){
            if(StringUtils.isNotBlank(array[i])){
                exchangers.forEach(item->{
                    if(item.match(array[i)){
                        //开启线程池处理
                        log.info("发送站内信任务提交");
                        executorService.submit(new NoticeTask(item,notice));
                        log.info("发送站内信任务提交完成");
                    }
                });
            }
        }
    }
}

noticeConvertUtils的sendMessageIsParam(notice)逻辑,主要将传递过来的参数转成Map对象

public Map<String, Object> sendMessageIsParam(NoticeParamDto notice) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map = this.convertToMap(notice.getParams(), map);
        Map<String, Object> returnMap = new HashMap<>();
        returnMap.put("businessId", notice.getBusinessId());
        returnMap.put("code", notice.getSendMessageCode());
        returnMap.put("phones", notice.getPhones());
        returnMap.put("toAddress", notice.getToAddress());
        if (StringUtils.isEmpty(notice.getCcAddress())) {
            returnMap.put("ccAddress", notice.getCcAddress());
        }
        returnMap.put("userId", notice.getUserId());
        returnMap.put("userName", notice.getUserName());
        returnMap.put("params", map);
        return returnMap;
    }
参数类NoticeParamDto
@Data
@Accessors(chain = true)
public class NoticeParamDto {

    /**
     * 消息id
     */
    private String id;

    /**
     * 业务单据id
     */
    private String businessId;

    /**
     * 消息模板编码
     */
    private String sendMessageCode;

    /**
     * 接收人手机号
     */
    private String phones;

    /**
     * 接收人邮箱,多个以英文逗号分割
     */
    private String toAddress;

    /**
     * 抄送人邮箱
     */
    private String ccAddress;

    /**
     * 接收人账号
     */
    private String userId;

    /**
     * 接收人姓名
     */
    private String userName;

    /**
     * 传递参数,字段名称需和模板内容、标题一样,否则解析模板内容、标题失败
     */
    private Object params;
}
任务类NoticeTask
@Slf4j
public class NoticeTask implements Callable<Boolean>{
    private NoticeExchanger noticeExchanger;
    private Map<String, Object> notice;

    public NoticeTask(NoticeExchanger noticeExchanger, Map<String, Object> notice){
        this.noticeExchanger=noticeExchanger;
        this.notice=notice;
    }
    @Override
    public Boolean call() throws Exception {
        log.info("============发送消息任务开始=============");
        return noticeExchanger.exchanger(notice);
    }
}

至此,核心代码已经介绍完成。由于这是一个独立的服务,所以我写了一个接口来调用NoticeServiceImpl的发送消息方法,然后再写一个Feign接口提供给其他服务使用,调用者调用时只需要传递消息模板编码、接收人消息等参数即可,无需在原来的代码上写上大量的内容拼接参数处理,实现了解耦,后续也更好维护。

当后续需要新增消息发送类型,比如要发送微信公众号消息,接着扩展即可,新增一个微信公众号消息发送的策略类和枚举类型,写对应逻辑即可,其他不需要变化,这样就非常灵活,也变得易扩展、易维护了。

当然这里还可以优化,比如我上面代码用了大量的map操作,有时候这些参数看着一头雾水,应该封装成实体类;再比如,我这里采用的是feign远程调用,对于调用者来说还是同步调用,需要等待其发送消息完成,有一定的性能消耗,后续可以采用消息队列进行优化,调用者将参数发送到消息队列就返回客户端提升用户体验,然后环境类监听主题消费即可。当然引入消息队列,还得考虑其中的常见问题(消息丢失、消息重复消费等等)。

好了,今天就讲这么多了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1901920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

容器:queue(队列)

以下是关于queue容器的总结 1、构造函数&#xff1a;queue [queueName] 2、添加、删除元素: push() 、pop() 3、获取队头/队尾元素&#xff1a;front()、back() 4、获取栈的大小&#xff1a;size() 5、判断栈是否为空&#xff1a;empty() #include <iostream> #include …

论文辅助笔记:ST-LLM

1 时间嵌入 2 PFA&#xff08;Partial Frozen Architecture&#xff09; 3 ST_LLM 3.1 初始化 3.2 forward

[FreeRTOS 内部实现] 事件组

文章目录 事件组结构体创建事件组事件组等待位事件组设置位 事件组结构体 // 路径&#xff1a;Source/event_groups.c typedef struct xEventGroupDefinition {EventBits_t uxEventBits;List_t xTasksWaitingForBits; } EventGroup_t;uxEventBits 中的每一位表示某个事件是否…

CDN节点是什么

CDN 节点是什么 CDN 主要依靠部署在各地的边缘服务器&#xff0c;利用全局负载技术将用户的访问指向距离最近且正常工作的缓存服务器上&#xff0c;用户访问网站时由缓存服务器直接响应用户请求。CDN 节点作为用来缓存数据的服务器&#xff0c;会将用户请求自动指向距离最近的…

微服务: Nacos部署安装与properties配置

Nacos 是阿里巴巴开源的一款用于动态服务发现、配置管理和服务管理的基础设施。Nacos 这个名称源自于 “Dynamic Naming and Configuration Service”。它主要是用于解决微服务架构中服务发现和配置管理的问题。 Nacos 单机模式的部署安装 1. 安装(Windows环境) Nacos是Java…

使用echarts绘制中国地图根据不同的省份划分到指定区域里面中

需求&#xff1a;我们在开发过程中会遇到使用中国地图来划分不同区域省份下面的数量统计情况&#xff0c;但是有时候使用Echarts里面地图功能和我们实际业务需求不匹配的&#xff0c;这个时候就需要我们手动自定义进行划分不同区域下面的省份数据。例如大区1下面有哪些省份&…

Redis---10---SpringBoot集成Redis

SpringBoot集成Redis 总体概述jedis-lettuce-RedisTemplate三者的联系 本地Java连接Redis常见问题&#xff0c;注意 bind配置请注释掉​ 保护模式设置为no​ Linux系统的防火墙设置​ redis服务器的IP地址和密码是否正确​ 忘记写访问redis的服务端口号和auth密码集成Jedis …

如何利用算法优化广告效果

效果广告以超过67%的占比&#xff0c;成为了中国互联网广告预算的大头。在BAT、字节等大的媒体平台上&#xff0c;效果广告以CPC实时竞价广告为主。在这种广告产品的投放中&#xff0c;广告主或其代理公司通过针对每个广告点击出价&#xff0c;系统自动把这些点击出价换算成eCP…

【C++干货基地】C++模板深度解析:进阶技巧与高级特性掌握(按需实例化、全特化与偏特化)文末送书

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

运维锅总详解CPU

本文从CPU简介、衡量CPU性能指标、单核及多核CPU工作流程、如何平衡 CPU 性能和防止CPU过载、为什么计算密集型任务要选择高频率CPU、超线程技术、CPU历史演进及摩尔定律等方面对CPU进行详细分析。希望对您有所帮助&#xff01; 一、CPU简介 CPU&#xff08;中央处理器&#…

亚马逊自养号实操:搭建高效防砍单IP环境的步骤与技巧

在亚马逊自养号下单的实践中&#xff0c;面对砍单与封号的风险&#xff0c;构建一个稳固且安全的IP网络环境成为了至关重要的一环。以下是一系列精心设计的步骤与策略&#xff0c;主要帮助卖家减少这些风险&#xff0c;确保账号的稳定运行&#xff1a; 一、构建稳固的IP网络环境…

实例演示kafka stream消息流式处理流程及原理

以下结合案例&#xff1a;统计消息中单词出现次数&#xff0c;来测试并说明kafka消息流式处理的执行流程 Maven依赖 <dependencies><dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-streams</artifactId><exclusio…

jmeter-beanshell学习3-beanshell获取请求报文和响应报文

前后两个报文&#xff0c;后面报文要用前面报文的响应结果&#xff0c;这个简单&#xff0c;正则表达式或者json提取器&#xff0c;都能实现。但是如果后面报文要用前面请求报文的内容&#xff0c;感觉有点难。最早时候把随机数写在自定义变量&#xff0c;前后两个接口都用这个…

如何处理 PostgreSQL 中由于表连接顺序不当导致的性能问题?

文章目录 一、理解表连接和连接顺序二、识别由于表连接顺序不当导致的性能问题三、影响表连接顺序的因素四、解决方案手动调整连接顺序创建合适的索引分析数据分布和优化查询逻辑 五、示例分析手动调整连接顺序创建索引优化查询逻辑 六、总结 在 PostgreSQL 中&#xff0c;表连…

浪潮信息携手算力企业为华东产业集群布局提供高质量算力支撑

随着信息技术的飞速发展&#xff0c;算力已成为推动数字经济发展的核心力量。近日&#xff0c;浪潮信息与五家领先的算力运营公司在南京正式签署战略合作协议&#xff0c;共同加速华东地区智算基础设施布局&#xff0c;为区域经济发展注入新动力。 进击的算力 江苏持续加码智算…

使用LoFTR模型进行图像配准、重叠区提取

LoFTR模型源自2021年CVPR提出的一篇论文LoFTR: Detector-Free Local Feature Matching with Transformers&#xff0c;其基于pytorch实现图像配准&#xff0c;与基于superpointsuperglue的方法不同&#xff0c; 是一个端到端的图像配准方法。与LoFTR官方库相关的有loftr2onnx库…

推荐好玩的工具之OhMyPosh使用

解除禁止脚本 Set-ExecutionPolicy RemoteSigned 下载Oh My Posh winget install oh-my-posh 或者 Install-Module oh-my-posh -Scope AllUsers 下载Git提示 Install-Module posh-git -Scope CurrentUser 或者 Install-Module posh-git -Scope AllUser 下载命令提示 Install-Mo…

搜索旋转数组

题目链接 搜索旋转数组 题目描述 注意点 数组已被旋转过很多次数组元素原先是按升序排列的若有多个相同元素&#xff0c;返回索引值最小的一个 解答思路 首先需要知道的是&#xff0c;本题数组中的旋转多次只是将头部的某些元素移动到尾部&#xff0c;所以不论怎么旋转&am…

欢迎加入国家智能网联汽车创新中心OS开发训练营大家庭

欢迎加入国家智能网联汽车创新中心OS开发训练营大家庭。&#x1f680; 导学阶段启动 在正式开营之前&#xff0c;我们特别设置了导学阶段&#xff0c;旨在帮助大家更好地迎接颇具挑战性的项目实战。导学阶段包括一系列精心准备的视频课程和配套习题。github链接&#xff1a;htt…

20K Stars!一个轻量级的 JS 库

大家好,我是CodeQi! 一位热衷于技术分享的码仔。 Driver.js 是一个轻量级的 JavaScript 库,旨在帮助开发人员创建网站或应用程序的引导和教程。通过 Driver.js,您可以引导用户了解网站的各个功能和使用方式。 Driver.js 提供了高度可定制的功能,使其能够适应各种需求和…