当前位置: 首页 > news >正文

策略模式的应用

前言

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

伪代码如下:

@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 {@Resourceprivate 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);@Autowiredprivate ISendEmailService sendEmailService;@Autowiredprivate MsgConfigurationMapper msgConfigurationMapper;/*** 是否支持邮件发送** @param type 消息类型* @return*/@Overridepublic boolean match(String type) {if (!String.valueOf(SendTypeEnum.EMAIL.getItem()).equals(type)) {return false;}return true;}/*** 处理消息发送** @param map 相关参数* @return*/@Overridepublic 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*/@Overridepublic boolean match(String type) {if (!String.valueOf(SendTypeEnum.STATION.getItem()).equals(type)) {return false;}return true;}/*** 处理消息发送** @param map 相关参数* @return*/@Overridepublic 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); @Autowiredprivate ISendSmsService sendSmsService;@Autowiredprivate MsgConfigurationMapper msgConfigurationMapper;@Overridepublic boolean match(String type) {if(!String.valueOf(SendTypeEnum.SMS.getItem()).equals(type)){return false;}return true;}@Overridepublic 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;@Resourceprivate NoticeConvertUtils noticeConvertUtils;@Resourceprivate MsgPublicTemplateMapper msgPublicTemplateMapper;public NoticeServiceImpl(){// 创建线程池Integer availableProcessors = Runtime.getRuntime().availableProcessors();Integer numOfThreads = availableProcessors * 2;executorService = new ThreadPoolExecutor(availableProcessors,numOfThreads,100, TimeUnit.SECONDS,new LinkedBlockingDeque<>());}/*** 当前Bean初始化之前会执行当前方法,获取所有的消息策略类*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {// 获取实现了NoticeExchanger接口的所有beanMap<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 {// 将参数转换成mapnotice = 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;}@Overridepublic Boolean call() throws Exception {log.info("============发送消息任务开始=============");return noticeExchanger.exchanger(notice);}
}

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

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

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

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

相关文章:

策略模式的应用

前言 系统有一个需求就是采购员审批注册供应商的信息时&#xff0c;会生成一个供应商的账号&#xff0c;此时需要发送供应商的账号信息&#xff08;账号、密码&#xff09;到注册填写的邮箱中&#xff0c;通知供应商账号信息&#xff0c;当时很快就写好了一个工具类&#xff0…...

如何使用uer做多分类任务

如何使用uer做多分类任务 语料集下载 找到这里点击即可 里面是这有json文件的 因此我们对此要做一些处理&#xff0c;将其转为tsv格式 # -*- coding: utf-8 -*- import json import csv import chardet# 检测文件编码 def detect_encoding(file_path):with open(file_path,…...

【HICE】转发服务器实验

1.在本地主机上操作 2.在客户端操作设置主机的IP地址为dns 3.测试,客户机是否能ping通...

MATLAB-分类CPO-RF-Adaboost冠豪猪优化器(CPO)优化RF随机森林结合Adaboost分类预测(二分类及多分类)

MATLAB-分类CPO-RF-Adaboost冠豪猪优化器&#xff08;CPO&#xff09;优化RF随机森林结合Adaboost分类预测&#xff08;二分类及多分类&#xff09; 分类CPO-RF-Adaboost冠豪猪优化器&#xff08;CPO&#xff09;优化RF随机森林结合Adaboost分类预测&#xff08;二分类及多分类…...

绝区贰--及时优化降低 LLM 成本和延迟

前言 大型语言模型 (LLM) 为各行各业带来了变革性功能&#xff0c;让用户能够利用尖端的自然语言处理技术处理各种应用。然而&#xff0c;这些强大的 AI 系统的便利性是有代价的 — 确实如此。随着 LLM 变得越来越普及&#xff0c;其计算成本和延迟可能会迅速增加&#xff0c;…...

JDBC【封装工具类、SQL注入问题】

day54 JDBC 封装工具类01 创建配置文件 DBConfig.properties driverNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/qnz01?characterEncodingutf8&serverTimezoneUTC usernameroot passwordroot新建配置文件&#xff0c;不用写后缀名 创建工具类 将变…...

Windows打开redis以及Springboot整合redis

目录 前言Windows系统打开redisSpringboot整合redis依赖实体类yml配置文件config配置各个数据存储类型分别说明记录string数据写入redis&#xff0c;并查询通过命令行查询 list插入数据到redis中从redis中读取命令读取数据 hash向redis中逐个添加map键值对获取key对应的map中所…...

MySQL使用LIKE索引是否失效的验证

1、简单的示例展示 在MySQL中&#xff0c;LIKE查询可以通过一些方法来使得LIKE查询能够使用索引。以下是一些可以使用的方法&#xff1a; 使用前导通配符&#xff08;%&#xff09;&#xff0c;但确保它紧跟着一个固定的字符。 避免使用后置通配符&#xff08;%&#xff09;&…...

封装日历uniapp,只显示年月不显示日

默认展示最新日期 子组件 <template><view class"date-picker"><picker mode"date" fields"month" change"onDateChange" :value"selectedDate"><view class"picker">{{ selectedDate…...

golang线程池ants-实现架构

1、总体架构 ants协程池&#xff0c;在使用上有多种方式(使用方式参考这篇文章&#xff1a;golang线程池ants-四种使用方法)&#xff0c;但是在实现的核心就一个&#xff0c;如下架构图&#xff1a; 总的来说&#xff0c;就是三个数据结构&#xff1a; Pool、WorkerStack、goW…...

Mysql面试合集

概念 是一个开源的关系型数据库。 数据库事务及其特性 事务&#xff1a;是一系列的数据库操作&#xff0c;是数据库应用的基本逻辑单位。 事务特性&#xff1a; &#xff08;1&#xff09;原子性&#xff1a;即不可分割性&#xff0c;事务要么全部被执行&#xff0c;要么就…...

Android Gradle 开发与应用 (五): 构建变体与自定义任务

目录 1. 概述 2. 构建变体 2.1 构建变体的概念 2.2 构建类型 2.3 产品风味 2.4 构建变体的使用 3. 自定义任务 3.1 自定义任务的概念 3.2 创建自定义任务 3.3 配置任务依赖 3.4 任务类型 3.5 动态任务 3.6 自定义任务执行顺序 4. 案例 4.1 多渠道打包 4.2 自动…...

Django学习第六天

启动项目命令 python manage.py runserver 取消模态框功能 js实现列表数据删除 第二种实现思路 使用jquery修改模态框标题 编辑页面拿到数据库数据显示默认数据功能实现 想要去数据库中获取数据时&#xff1a;对象/字典 三种不同的数据类型 使用Ajax传入数据实现表单编辑&…...

docker部署mycat,连接上面一篇的一主二从mysql

一、docker下载mycat镜像 查看安装结果 这个名称太长&#xff0c;在安装容器时不方便操作&#xff0c;设置标签为mycat docker tag longhronshens/mycat-docker mycat 二、安装容器 先安装一个&#xff0c;主要目的是获得配置文件 docker run -it -d --name mycat -p 8066:…...

VUE2拖拽组件:vue-draggable-resizable-gorkys

vue-draggable-resizable-gorkys组件基于vue-draggable-resizable进行二次开发, 用于可调整大小和可拖动元素的组件并支持冲突检测、元素吸附、元素对齐、辅助线 安装: npm install --save vue-draggable-resizable-gorkys 全局引用: import Vue from vue import vdr fro…...

容器:stack

以下是关于stack容器的一些总结&#xff1a; stack容器比较简单&#xff0c;主要包括&#xff1a; 1、构造函数&#xff1a;stack [staName] 2、添加、删除元素: push() 、pop() 3、获取栈顶元素&#xff1a;top() 4、获取栈的大小&#xff1a;size() 5、判断栈是否为空&#x…...

跨平台Ribbon UI组件QtitanRibbon全新发布v6.7.0——支持Qt 6.6.3

没有Microsoft在其办公解决方案中提供的界面&#xff0c;就无法想象现代应用程序&#xff0c;这个概念称为Ribbon UI&#xff0c;目前它是使应用程序与时俱进的主要属性。QtitanRibbon是一款遵循Microsoft Ribbon UI Paradigm for Qt技术的Ribbon UI组件&#xff0c;QtitanRibb…...

(6) 深入探索Python-Pandas库的核心数据结构:DataFrame全面解析

目录 前言1. DataFrame 简介2. DataFrame的特点3. DataFrame的创建3.1 使用字典创建DataFrame3.2 使用列表的列表&#xff08;或元组&#xff09;创建DataFrame3.3 使用NumPy数组创建DataFrame3.4 使用Series构成的字典创建DataFrame3.5 使用字典构成的字典创建DataFrame 4. 从…...

在 Azure 云中开始使用适用于 Ubuntu 的 Grafana

介绍 Grafana 是一款开源工具&#xff0c;可用于可视化和分析数据。它特别适合跟踪计算机系统的运行情况。在构建微服务或其他类型的应用程序时&#xff0c;您可能需要分析日志数据、轻松可视化数据或设置特殊警报以接收有关系统中发生的某些事件的通知。 这就是为什么你可能…...

1.Python学习笔记

一、环境配置 1.Python解释器 把程序员用编程语言编写的程序&#xff0c;翻译成计算机可以执行的机器语言 安装&#xff1a; 双击Python3.7.0-选择自定义安装【Customize installation】-勾选配置环境变量 如果没有勾选配置环境变量&#xff0c;输入python就会提示找不到命令…...

中英双语介绍百老汇著名歌剧:《猫》(Cats)和《剧院魅影》(The Phantom of the Opera)

中文版 百老汇著名歌剧 百老汇&#xff08;Broadway&#xff09;是世界著名的剧院区&#xff0c;位于美国纽约市曼哈顿。这里汇集了许多著名的音乐剧和歌剧&#xff0c;吸引了全球各地的观众。以下是两部百老汇的经典音乐剧&#xff1a;《猫》和《剧院魅影》的详细介绍。 1.…...

RpcChannel的调用过程

目录 1. RPC调用方&#xff08;caller&#xff09;的调用(消费)过程 2.在caller下创建文件&#xff1a;calluserservice.cc 3.在src的include下创建文件&#xff1a;mprpcchannel.h 4.在src下创建mprpcchannel.cc 1. RPC调用方&#xff08;caller&#xff09;的调用(消费)过…...

东芝TB6560AHQ/AFG步进电机驱动IC:解锁卓越的电机控制性能

作为一名工程师&#xff0c;一直在寻找可靠且高效的组件来应用于你的项目中。东芝的TB6560AHQ/AFG步进电机驱动IC能够提供精准且多功能的电机控制&#xff0c;完全符合现代应用的高要求&#xff0c;保证高性能和易用性。在这篇文章中&#xff0c;我们将探讨TB6560AHQ/AFG的主要…...

免杀笔记 ----> DLL注入

这段时间我们暂时没什么事情干的话我们就继续更新我们的免杀笔记力&#xff01;&#xff01;&#xff01; &#xff1a;今天我们讲DLL注入 目录 1.DLL注入 2.直接加载DLL&#xff1f; 3.远程线程注入 获取Handle 远程申请内存空间 将我们的CS的DLL加载入内存 创建远程线…...

奇迹MU 骷髅战士在哪

BOSS分布图介绍 我为大家带来各地区怪物分布图。在游戏前期&#xff0c;很多玩家可能会不知道该去哪里寻找怪物&#xff0c;也不知道哪些怪物值得打。如果选择了太强的怪物&#xff0c;弱小的玩家可能会无法抵御攻击。如果选择了低等级的boss&#xff0c;收益可能并不理想。所…...

leetcode力扣_贪心思想

455.分发饼干&#xff08;easy-自己想得出来并写好&#xff09; 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺…...

Vue中Class数据绑定

Class数据绑定 数据绑定的一个常见需求场景是操作CSS class列表&#xff0c;因为class是attribute&#xff08;属性&#xff09;&#xff0c;我们可以和其他attribute一样使用v-bind 将它们和动态的字符串绑定。但是&#xff0c;在处理比较复杂的绑定时&#xff0c;通过拼接生…...

Python数据分析案例49——基于机器学习的垃圾邮件分类系统构建(朴素贝叶斯,支持向量机)

案例背景 trec06c是非常经典的邮件分类的数据&#xff0c;还是难能可贵的中文数据集。 这个数据集从一堆txt压缩包里面提取出来整理为excel文件还真不容不易&#xff0c;肯定要做一下文本分类。 虽然现在文本分类基本都是深度学习了&#xff0c;但是传统的机器学习也能做。本案…...

贪心算法-以学籍管理系统为例

1.贪心算法介绍 1.算法思路 贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行&#xff0c;根据某个优化测度&#xff0c;每一 步都要确保能获得局部最优解。每一步只考虑一 个数据&#xff0c;其选取应该满足局部优化的条件。若下 一个数据和部分最优解连在一起…...

PyCharm 安装

PyCharm是一种流行的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;由JetBrains公司开发。它提供了丰富的功能&#xff0c;如智能代码补全、实时错误检查、项目导航、调试工具以及版本控制等&#xff0c;极大地提高了Python开发人员的工作效率。以下是PyCharm安装…...

C++:对象指针访问成员函数

使用箭头操作符 (->)&#xff1a;ptr->function() 是最常用和推荐的方式&#xff0c;因为它更简洁、更直观。箭头操作符 (->) 被设计为与点操作符 (.) 配合指针一起使用&#xff0c;以便通过指针访问对象的成员。 先解引用指针&#xff0c;然后使用点操作符 (.)&…...

Linux 防火墙配置指南:firewalld 端口管理应用案例(二十个实列)

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;&#x1f427;Linux高级管理专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️…...

推荐Bulk Image Downloader插件下载网页中图片链接很好用

推荐&#xff1a;Bulk Image Downloader chome浏览器插件下载图片链接&#xff0c;很好用。 有个网页&#xff0c;上面放了数千的gif的电路图&#xff0c;手工下载会累瘫了不可。想找一个工具分析它的静态链接并下载&#xff0c;找了很多推荐的下载工具&#xff0c;都是不能分…...

详解前缀码与前缀编码

前缀编码是一种数据压缩技术&#xff0c;也被称为可变长度编码。它的基本原理是将频繁出现的字符或字符序列用较短的编码表示&#xff0c;而较少出现的字符或字符序列用较长的编码表示&#xff0c;从而达到压缩数据的目的。 概念定义 前缀码&#xff1a;给定一个编码序列的集合…...

数据库管理工具 -- Navicat Premium v17.0.8 特别版

软件简介 Navicat Premium 是一款功能强大的数据库管理工具&#xff0c;适用于Windows、Mac和Linux平台。它支持多种数据库&#xff0c;包括MySQL、MariaDB、SQL Server、PostgreSQL、Oracle、SQLite等。用户可以通过Navicat Premium轻松地连接到各种数据库服务器&#xff0c;…...

【Linux】进程创建和终止 | slab分配器

进程创建 fork 1.fork 之后发生了什么 将给子进程分配新的内存块和内核数据结构&#xff08;形成了新的页表映射&#xff09;将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表当中fork 返回&#xff0c;开始调度器调度 这样就可以回答之前返回两个值&#xff1f…...

计算机网络--网络层

一、网络层的服务和功能 网络层主要为应用层提供端对端的数据传输服务 网络层接受运输层的报文段&#xff0c;添加自己的首部&#xff0c;形成网络层分组。分组是网络层的传输单元。网络层分组在各个站点的网络层之间传输&#xff0c;最终到达接收方的网络层。接收方网络层将运…...

【CSS】如何实现分栏布局

在CSS分栏布局中&#xff0c;设置宽度和样式是一个基本且重要的步骤。这可以通过直接应用样式到列元素&#xff08;通常是div元素&#xff09;上来实现。以下是一些常用的方法来设置分栏布局的宽度和样式&#xff1a; 1. 使用百分比宽度 使用百分比宽度可以使列的大小相对于其…...

2025湖北武汉智慧教育装备信息化展/智慧校园展/湖北高博会

2025武汉教育装备展,2025武汉智慧教育展,2025武汉智慧校园展,2025武汉教育信息化展,2025武汉智慧教室展,湖北智慧校园展,湖北智慧教室展,武汉教学设备展,湖北高教会,湖北高博会 2025湖北武汉智慧教育装备信息化展/智慧校园展/湖北高博会 2025第10届武汉国际教育装备及智慧校园…...

Android Studio Run窗口中文乱码解决办法

Android Studio Run窗口中文乱码解决办法 问题描述&#xff1a; AndroidStudio 编译项目时Run窗口中文乱码&#xff0c;如图&#xff1a; 解决方法&#xff1a; 依次打开菜单&#xff1a;Help--Edit Custom VM Options&#xff0c;打开studio64.exe.vmoptions编辑框&#xf…...

代码随想录——划分字母区间(Leetcode763)

题目链接 贪心 class Solution {public List<Integer> partitionLabels(String s) {int[] count new int[27];Arrays.fill(count,0);// 统计元素最后一次出现的位置for(int i 0; i < s.length(); i){count[s.charAt(i) - a] i;}List<Integer> res new Ar…...

SQL注入方法

文章目录 前言如何测试与利用注入点手工注入思路工具sqlmap-r-u-m--level--risk-v-p--threads-batch-smart--os-shell--mobiletamper插件获取数据的相关参数 前言 记录一些注入思路和经常使用的工具&#xff0c;后续有用到新的工具和总结新的方法再继续补充。 如何测试与利用注…...

Vue表单输入绑定v-model

表单输入绑定 在前端处理表单时&#xff0c;我们常常需要将表单输入框的内容同步给Javascript中相应的变量。手动连接绑定和更改事件监听器可能会很麻&#xff0c;v-model 指令帮我们简化了这一步骤。 <template><h3>表单输入绑定</h3><hr> <inpu…...

【分布式系统】ELK 企业级日志分析系统

目录 一.ELK概述 1.简介 1.1.可以添加的其他组件 1.2.filebeat 结合 logstash 带来好处 2.为什么使用ELK 3.完整日志系统基本特征 4.工作原理 二.部署ELK日志分析系统 1.初始化环境 2.完成JAVA部署 三. ELK Elasticsearch 集群部署 1.安装 2.修改配置文件 3.es 性…...

vs2019 无法打开项目文件

vs2019 无法打开项目文件&#xff0c;无法找到 .NET SDK。请检查确保已安装此项且 global.json 中指定的版本(如有)与所安装的版本相匹配 原因&#xff1a;缺少组件 解决方案&#xff1a;选择需要的组件进行安装完成...

Elasticsearch:Painless scripting 语言(一)

Painless 是一种高性能、安全的脚本语言&#xff0c;专为 Elasticsearch 设计。你可以使用 Painless 在 Elasticsearch 支持脚本的任何地方安全地编写内联和存储脚本。 Painless 提供众多功能&#xff0c;这些功能围绕以下核心原则&#xff1a; 安全性&#xff1a;确保集群的…...

SpringBoot项目练习

文章目录 SpringBootVue后台管理系统所需软件下载、安装、版本查询Vue搭建一个简单的Vue项目 Spring项目1项目架构 SpringBootVue后台管理系统 学习视频&#xff1a; https://www.bilibili.com/video/BV1U44y1W77D/?spm_id_from333.337.search-card.all.click&vd_sourcec…...

Android Gradle 开发与应用 (七): Gradle 插件开发与发布

目录 一、概述 二、Gradle插件的基础知识 2.1 Gradle插件的定义 2.2 Gradle插件的种类 2.3 Gradle插件的生命周期 三、开发一个Gradle插件 3.1 创建Gradle插件项目 3.2 编写插件实现 3.3 配置插件元数据 3.4 构建和测试插件 3.5 在项目中应用插件 四、发布Gradle插…...

方法引用详解

什么是方法引用&#xff1f;&#xff1a;针对于函数式接口中的抽象方法 为什么用方法引用&#xff1f;&#xff1a;避免代码的重复&#xff0c;简便书写&#xff0c;提高效率 在使用Lambda表达式的时候&#xff0c;我们实际上传递进去的代码就是一种解决方案&#xff1a;拿参数…...

Apache Seata 高可用部署实践

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Apache Seata 高可用部署实践 Seata 高可用部署实践 使用配置中心和数据库来实现 Seata 的高…...