策略模式的应用

前言

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

伪代码如下:

@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远程调用,对于调用者来说还是同步调用,需要等待其发送消息完成,有一定的性能消耗,后续可以采用消息队列进行优化,调用者将参数发送到消息队列就返回客户端提升用户体验,然后环境类监听主题消费即可。当然引入消息队列,还得考虑其中的常见问题(消息丢失、消息重复消费等等)。

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

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

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

相关文章

Python 学习中什么是字典,如何操作字典?

什么是字典 字典&#xff08;Dictionary&#xff09;是Python中的一种内置数据结构&#xff0c;用于存储键值对&#xff08;key-value pair&#xff09;。字典的特点是通过键来快速查找值&#xff0c;键必须是唯一的&#xff0c;而值可以是任何数据类型。字典在其他编程语言中…

vue实现搜索文章关键字,滑到指定位置并且高亮

1、输入搜索条件&#xff0c;点击搜索按钮 2、滑到定位到指定的搜索条件。 <template><div><div class"search_form"><el-inputv-model"searchVal"placeholder"请输入关键字查询"clearablesize"small"style&quo…

对于老百姓而言VR到底能做什么?

VR技术自诞生以来不断发展&#xff0c;已经广泛应用于教育、医疗、工程、军事、航空、航海、影视、娱乐等方面&#xff0c;譬如&#xff0c;大型工程或军事活动VR预演可以大幅度减少人力物力投入&#xff1b;在航空领域&#xff0c;航天飞行员在训练舱中面对屏幕进行各种驾驶操…

【Linux进阶】文件系统4——文件系统特性

1.磁盘组成与分区的复习 首先说明一下磁盘的物理组成&#xff0c;整块磁盘的组成主要有&#xff1a; 圆形的碟片&#xff08;主要记录数据的部分&#xff09;&#xff1b;机械手臂&#xff0c;与在机械手臂上的磁头&#xff08;可擦写碟片上的数据);主轴马达&#xff0c;可以…

打开浏览器控制台,点击应用,浏览器崩溃

调试的时候&#xff0c;打开控制台&#xff0c;点击 “应用” 立马浏览器奔溃&#xff0c;但是点击别的没问题 调查发现是因为manifest.json这个文件引起的 manifest.json 最主要的原因是因为没有设置这个sizes字段 Google浏览器更新大概到126之后的版本会有问题&#xff0c;之…

AI多模态教程:Qwen-VL多模态大模型实践指南

一、模型介绍 Qwen-VL&#xff0c;由阿里云研发的大规模视觉语言模型&#xff08;Large Vision Language Model, LVLM&#xff09;&#xff0c;代表了人工智能领域的一个重大突破。该模型具有处理和关联图像、文本、检测框等多种类型数据的能力&#xff0c;其输出形式同样多样…

代码随想录Day69(图论Part05)

并查集 // 1.初始化 int fa[MAXN]; void init(int n) {for (int i1;i<n;i)fa[i]i; }// 2.查询 找到的祖先直接返回&#xff0c;未进行路径压缩 int.find(int i){if(fa[i] i)return i;// 递归出口&#xff0c;当到达了祖先位置&#xff0c;就返回祖先elsereturn find(fa[i])…

基于Python爬虫的城市二手房数据分析可视化

基于Python爬虫的城市二手房数据分析可视化 一、前言二、数据采集(爬虫,附完整代码)三、数据可视化(附完整代码)3.1 房源面积-总价散点图3.2 各行政区均价3.3 均价最高的10个小区3.4 均价最高的10个地段3.5 户型分布3.6 词云图四、如何更换城市一、前言 二手房具有价格普…

CSS position属性之relative和absolute

目录 1 参考文章2 五个属性值3 position:static4 position:relative&#xff08;相对&#xff09;5 position:absolute&#xff08;绝对&#xff09; 1 参考文章 https://blog.csdn.net/lalala_dxf/article/details/123566909 https://blog.csdn.net/WangMinGirl/article/deta…

最灵活且易用的C++开源串口通信调试软件

这款C开源串口调试软件&#xff0c;集成了丰富的功能&#xff0c;为用户提供高效、便捷的串口通信调试体验。以下是其核心功能亮点&#xff1a; 基础功能&#xff1a; 数据交互自如&#xff1a;支持串口数据的轻松读取与发送&#xff0c;让数据交换变得简单直接。 灵活配置参…

基于顺序表的通讯录实现

一、前言 基于已经学过的顺序表&#xff0c;可以实现一个简单的通讯录。 二、通讯录相关头文件 //Contact.h #pragma once#define NAME_MAX 20 #define TEL_MAX 20 #define ADDR_MAX 20 #define GENDER_MAX 20typedef struct PersonInfo {char name[NAME_MAX];char gender[G…

Python的招聘数据分析与可视化管理系统-计算机毕业设计源码55218

摘要 随着互联网的迅速发展&#xff0c;招聘数据在规模和复杂性上呈现爆炸式增长&#xff0c;对数据的深入分析和有效可视化成为招聘决策和招聘管理的重要手段。本论文旨在构建一个基于Python的招聘数据分析与可视化管理系统。 该平台以主流招聘平台为数据源&#xff0c;利用Py…

昇思25天学习打卡营第2天|MindSpore快速入门

打卡 目录 打卡 快速入门案例&#xff1a;minist图像数据识别任务 案例任务说明 流程 1 加载并处理数据集 2 模型网络构建与定义 3 模型约束定义 4 模型训练 5 模型保存 6 模型推理 相关参考文档入门理解 MindSpore数据处理引擎 模型网络参数初始化 模型优化器 …

如何选择TikTok菲律宾直播网络?

为了满足用户对于实时互动的需求&#xff0c;TikTok推出了直播功能&#xff0c;让用户能够与粉丝即时交流。本文将探讨如何选择适合的TikTok菲律宾直播网络&#xff0c;并分析OgLive是否是值得信赖的选择。 TikTok菲律宾直播网络面临的挑战 作为全球领先的短视频平台&#xff…

Python + OpenCV 开启图片、写入储存图片

这篇教学会介绍OpenCV 里imread()、imshow()、waitKey() 方法&#xff0c;透过这些方法&#xff0c;在电脑中使用不同的色彩模式开启图片并显示图片。 imread() 开启图片 使用imread() 方法&#xff0c;可以开启图片&#xff0c;imread() 有两个参数&#xff0c;第一个参数为档…

Google Play上架:恶意软件、移动垃圾软件和行为透明度详细解析和解决办法 (一)

近期整理了许多开发者的拒审邮件和内容,也发现了许多问题,今天来说一下关于恶意软件这类拒审的问题。 目标邮件如下: 首先说一下各位小伙伴留言私信的一个方法,提供你的拒审邮件和时间,尽可能的详细,这样会帮助我们的团队了解你们的问题,去帮助小伙伴么解决问题。由于前…

集成学习(一)Bagging

前边学习了&#xff1a;十大集成学习模型&#xff08;简单版&#xff09;-CSDN博客 Bagging又称为“装袋法”&#xff0c;它是所有集成学习方法当中最为著名、最为简单、也最为有效的操作之一。 在Bagging集成当中&#xff0c;我们并行建立多个弱评估器&#xff08;通常是决策…

排序——数据结构与算法 总结8

目录 8.1 排序相关概念 8.2 插入排序 8.2.1 直接插入排序&#xff1a; 8.2.2 折半插入排序&#xff1a; 8.2.3 希尔排序&#xff1a; 8.3 交换排序 8.3.1 冒泡排序&#xff1a; 8.3.2 快速排序&#xff1a; 8.4 选择排序 8.4.1 简单选择排序 8.4.2 堆排序 8.5 归并…

磁盘就是一个超大的Byte数组,操作系统是如何管理的?

磁盘在操作系统的维度看&#xff0c;就是一个“超大的Byte数组”。 那么操作系统是如何对这块“超大的Byte数组”做管理的呢&#xff1f; 我们知道在逻辑上&#xff0c;上帝说是用“文件”的概念来进行管理的。于是&#xff0c;便有了“文件系统”。那么&#xff0c;文件系统…

最新版情侣飞行棋dofm,已解锁高阶私密模式,单身狗务必绕道!(附深夜学习资源)

今天阿星要跟大家聊一款让阿星这个大老爷们儿面红耳赤的神奇游戏——情侣飞行棋。它的神奇之处就在于专为情侣设计&#xff0c;能让情侣之间感情迅速升温&#xff0c;但单身狗们请自觉绕道&#xff0c;不然后果自负哦&#xff01; 打开游戏&#xff0c;界面清新&#xff0c;操…