策略模式的應用

前言

系統有一個需求就是采購員審批注冊供應商的信息時,會生成一個供應商的賬號,此時需要發送供應商的賬號信息(賬號、密碼)到注冊填寫的郵箱中,通知供應商賬號信息,當時很快就寫好了一個工具類,用來發送普通的文本郵件信息。但是隨著系統的迭代,后面又新增了一些需求,比如一些單據需要在供應商確認時,發送一條站內信到首頁,這樣采購員登錄時就可以看到最新的單據信息,進行相應的處理;或者采購員創建一些單據時,需要發送站內信到首頁,然后供應商登錄系統時,可以看到最新單據信息并進行處理,因此,我在原有的工具類基礎上,修改發送郵件信息的方法,加入了消息類型參數,并根據消息類型,調用相應的方法處理;過了一段時間,業務又找了過來,說當用戶修改密碼時,需要發送一個短信驗證碼,驗證碼輸對了才給他修改,接著我又在工具類里面,加入了處理短信的發送邏輯。

偽代碼如下:

@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.pswp.cn/web/40895.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/40895.shtml
英文地址,請注明出處:http://en.pswp.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…

HashMap的底層實現原理詳解

HashMap是Java中最常用的集合類之一&#xff0c;其基于哈希表的Map接口實現&#xff0c;提供了快速的鍵值對存儲和檢索功能。深入理解HashMap的底層實現原理&#xff0c;對于提升編程技能、應對技術面試以及優化程序性能都具有重要意義。以下從技術難點、面試官關注點、回答吸引…

數據庫作業day3

創建一個student表用于存儲學生信息 CREATE TABLE student( id INT PRIMARY KEY, name VARCHAR(20) NOT NULL, grade FLOAT ); 向student表中添加一條新記錄 記錄中id字段的值為1&#xff0c;name字段的值為"monkey"&#xff0c;grade字段的值為98.5 insert into …

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

VR技術自誕生以來不斷發展&#xff0c;已經廣泛應用于教育、醫療、工程、軍事、航空、航海、影視、娛樂等方面&#xff0c;譬如&#xff0c;大型工程或軍事活動VR預演可以大幅度減少人力物力投入&#xff1b;在航空領域&#xff0c;航天飛行員在訓練艙中面對屏幕進行各種駕駛操…

mysql修改密碼失敗報錯無法登錄解決辦法

mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 1045 (28000): Access denied for user root@localhost (using password: YES) 這個問題是因為在嘗試使用命令行連接MySQL時,使用了明文密碼,這是不安全的。同時,由于某種原因,您…

Kylin中的查詢引擎:大數據查詢加速的引擎解析

Kylin中的查詢引擎&#xff1a;大數據查詢加速的引擎解析 Apache Kylin是一個開源的分布式分析引擎&#xff0c;專為大規模數據集提供快速的SQL查詢和多維分析&#xff08;OLAP&#xff09;能力。在Kylin的架構中&#xff0c;查詢引擎&#xff08;Query Engine&#xff09;扮演…

【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])…

py基礎語法簡述

py基礎&#xff0c;常用sdk 一些要點 python中是沒有常量的關鍵字的&#xff0c;只是我們常常約定使用大寫字符組合的變量名表示常量&#xff0c;也有“不要對其進行賦值”的提醒操作 PI 3.14python3中有六個標準的數據類型&#xff1a; Number(數字)、String(字符串)、Boo…

基于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…

MSPM0G3507——解決printf重定向在其他位置不能用的問題(printf重定向的補充)

除了之前發的文章的printf重定向的代碼之外&#xff0c;還要加上這樣一段代碼即可 int puts(const char *_ptr) {int count fputs(_ptr,stdout);count fputs("\n",stdout);return count;} 完整的重定向&#xff1a; int fputc(int c, FILE* stream) {DL_UART_Main_…

昇思25天學習打卡營第2天|MindSpore快速入門

打卡 目錄 打卡 快速入門案例&#xff1a;minist圖像數據識別任務 案例任務說明 流程 1 加載并處理數據集 2 模型網絡構建與定義 3 模型約束定義 4 模型訓練 5 模型保存 6 模型推理 相關參考文檔入門理解 MindSpore數據處理引擎 模型網絡參數初始化 模型優化器 …

一個字符串的全部子序列和全排列

在計算機科學中&#xff0c;字符串的子序列和全排列是兩個重要的概念。 1. 子序列 子序列是從一個序列中刪除一些&#xff08;或不刪除&#xff09;元素而不改變剩余元素的順序形成的新序列。 例如&#xff0c;字符串 “abc” 的子序列包括&#xff1a; “”&#xff08;空…