RabbitMQ保證消息被成功發送和消費

一 : 在使用 RabbitMQ 作為消息隊列時,保證消息被成功發送和消費是一個非常重要的問題。以下是一些關鍵點和最佳實踐,以確保消息的可靠傳輸和處理。*
配置方式:

保證消息被成功發送
確認模式(Confirm Mode):生產者可以啟用確認模式,確保消息成功到達交換機。
使用 channel.confirmSelect() 啟用確認模式。
使用 channel.waitForConfirms() 或 channel.addConfirmListener() 來處理確認消息。
事務模式(Transaction Mode):生產者可以使用事務模式,確保消息成功到達隊列。
使用 channel.txSelect() 開啟事務,channel.txCommit() 提交事務,channel.txRollback() 回滾事務。
處理發送失敗:實現重試機制,可以在發送失敗時重試。
使用死信交換機(Dead Letter Exchange, DLX)來存儲處理失敗的消息。
保證消息被成功消費
手動確認(Manual Acknowledgment):消費者應該使用手動確認模式,確保消息被成功處理后再確認。
使用 channel.basicConsume(queue, false, consumer) 開啟手動確認模式。
在消息處理成功后,調用 channel.basicAck(deliveryTag, false) 確認消息。
處理消費失敗:實現消費失敗的重試機制。
使用死信交換機(DLX)來存儲處理失敗的消息。
冪等性:確保消費者處理消息的冪等性,避免重復消費導致的問題。

二:通過記錄消息到數據庫中,采用定時任務輪詢方式:

1 這是一個 Spring 組件,用于構建和發布返利消息事件。

//topic 字段從配置文件中獲取,表示消息隊列的 topic。
//buildEventMessage 方法用于構建 EventMessage 對象,包含隨機生成的 ID、當前時間戳和數據。
//topic 方法返回消息隊列的 topic。
//RebateMessage 是一個內部類,定義了返利消息的結構@Component
public class SendRebateMessageEvent extends BaseEvent<SendRebateMessageEvent.RebateMessage> {@Value("${spring.rabbitmq.topic.send_rebate}")private String topic;@Overridepublic EventMessage<RebateMessage> buildEventMessage(RebateMessage data) {return EventMessage.<SendRebateMessageEvent.RebateMessage>builder().id(RandomStringUtils.randomNumeric(11)).timestamp(new Date()).data(data).build();}@Overridepublic String topic() {return topic;}@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic static class RebateMessage {private String userId;private String rebateDesc;private String rebateType;private String rebateConfig;private String bizId;}
}@Data
public abstract class BaseEvent<T> {public abstract EventMessage<T> buildEventMessage(T data);public abstract String topic();@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic static class EventMessage<T> {private String id;private Date timestamp;private T data;}}

2 生產者示例

//topic 字段從配置文件中獲取,表示消息隊列的 topic。
//listener 方法使用 @RabbitListener 注解監聽指定隊列的消息。
//消息到達后,解析消息內容,根據 rebateType 字段的不同,調用相應的服務方法處理消息。
//異常處理機制確保了消息處理的健壯性。@Component
public class RebateMessageCustomer {@Value("${spring.rabbitmq.topic.send_rebate}")private String topic;@Resourceprivate IRaffleActivityAccountQuotaService raffleActivityAccountQuotaService;@Resourceprivate ICreditAdjustService creditAdjustService;@RabbitListener(queuesToDeclare = @Queue(value = "${spring.rabbitmq.topic.send_rebate}"))public void listener(String message) {try {log.info("監聽用戶行為返利消息 topic: {} message: {}", topic, message);BaseEvent.EventMessage<SendRebateMessageEvent.RebateMessage> eventMessage = JSON.parseObject(message, new TypeReference<BaseEvent.EventMessage<SendRebateMessageEvent.RebateMessage>>() {}.getType());SendRebateMessageEvent.RebateMessage rebateMessage = eventMessage.getData();switch (rebateMessage.getRebateType()) {case "sku":SkuRechargeEntity skuRechargeEntity = new SkuRechargeEntity();skuRechargeEntity.setUserId(rebateMessage.getUserId());skuRechargeEntity.setSku(Long.valueOf(rebateMessage.getRebateConfig()));skuRechargeEntity.setOutBusinessNo(rebateMessage.getBizId());skuRechargeEntity.setOrderTradeType(OrderTradeTypeVO.rebate_no_pay_trade);raffleActivityAccountQuotaService.createOrder(skuRechargeEntity);break;case "integral":TradeEntity tradeEntity = new TradeEntity();tradeEntity.setUserId(rebateMessage.getUserId());tradeEntity.setTradeName(TradeNameVO.REBATE);tradeEntity.setTradeType(TradeTypeVO.FORWARD);tradeEntity.setAmount(new BigDecimal(rebateMessage.getRebateConfig()));tradeEntity.setOutBusinessNo(rebateMessage.getBizId());creditAdjustService.createOrder(tradeEntity);break;}} catch (AppException e) {if (ResponseCode.INDEX_DUP.getCode().equals(e.getCode())) {log.warn("監聽用戶行為返利消息,消費重復 topic: {} message: {}", topic, message, e);return;}throw e;} catch (Exception e) {log.error("監聽用戶行為返利消息,消費失敗 topic: {} message: {}", topic, message, e);throw e;}}
}

3 消費者示例


//這個方法用于保存用戶返利記錄,并在事務中插入用戶行為返利訂單和任務對象。
//在事務外,同步發送 MQ 消息。
//發送消息時,調用 eventPublisher.publish 方法發布消息到指定的 topic。public void saveUserRebateRecord(String userId, List<BehaviorRebateAggregate> behaviorRebateAggregates) {try {dbRouter.doRouter(userId);transactionTemplate.execute(status -> {try {for (BehaviorRebateAggregate behaviorRebateAggregate : behaviorRebateAggregates) {BehaviorRebateOrderEntity behaviorRebateOrderEntity = behaviorRebateAggregate.getBehaviorRebateOrderEntity();UserBehaviorRebateOrder userBehaviorRebateOrder = new UserBehaviorRebateOrder();userBehaviorRebateOrder.setUserId(behaviorRebateOrderEntity.getUserId());userBehaviorRebateOrder.setOrderId(behaviorRebateOrderEntity.getOrderId());userBehaviorRebateOrder.setBehaviorType(behaviorRebateOrderEntity.getBehaviorType());userBehaviorRebateOrder.setRebateDesc(behaviorRebateOrderEntity.getRebateDesc());userBehaviorRebateOrder.setRebateType(behaviorRebateOrderEntity.getRebateType());userBehaviorRebateOrder.setRebateConfig(behaviorRebateOrderEntity.getRebateConfig());userBehaviorRebateOrder.setOutBusinessNo(behaviorRebateOrderEntity.getOutBusinessNo());userBehaviorRebateOrder.setBizId(behaviorRebateOrderEntity.getBizId());userBehaviorRebateOrderDao.insert(userBehaviorRebateOrder);TaskEntity taskEntity = behaviorRebateAggregate.getTaskEntity();Task task = new Task();task.setUserId(taskEntity.getUserId());task.setTopic(taskEntity.getTopic());task.setMessageId(taskEntity.getMessageId());task.setMessage(JSON.toJSONString(taskEntity.getMessage()));task.setState(taskEntity.getState().getCode());taskDao.insert(task);}return 1;} catch (DuplicateKeyException e) {status.setRollbackOnly();log.error("寫入返利記錄,唯一索引沖突 userId: {}", userId, e);throw new AppException(ResponseCode.INDEX_DUP.getCode(), ResponseCode.INDEX_DUP.getInfo());}});} finally {dbRouter.clear();}for (BehaviorRebateAggregate behaviorRebateAggregate : behaviorRebateAggregates) {TaskEntity taskEntity = behaviorRebateAggregate.getTaskEntity();Task task = new Task();task.setUserId(taskEntity.getUserId());task.setMessageId(taskEntity.getMessageId());try {eventPublisher.publish(taskEntity.getTopic(), taskEntity.getMessage());taskDao.updateTaskSendMessageCompleted(task);} catch (Exception e) {log.error("寫入返利記錄,發送MQ消息失敗 userId: {} topic: {}", userId, task.getTopic());taskDao.updateTaskSendMessageFail(task);}}
}public List<String> createOrder(BehaviorEntity behaviorEntity) {// 1. 查詢返利配置List<DailyBehaviorRebateVO> dailyBehaviorRebateVOS = behaviorRebateRepository.queryDailyBehaviorRebateConfig(behaviorEntity.getBehaviorTypeVO());if (null == dailyBehaviorRebateVOS || dailyBehaviorRebateVOS.isEmpty()) return new ArrayList<>();// 2. 構建聚合對象List<String> orderIds = new ArrayList<>();List<BehaviorRebateAggregate> behaviorRebateAggregates = new ArrayList<>();for (DailyBehaviorRebateVO dailyBehaviorRebateVO : dailyBehaviorRebateVOS) {// 拼裝業務ID;用戶ID_返利類型_外部透徹業務IDString bizId = behaviorEntity.getUserId() + Constants.UNDERLINE + dailyBehaviorRebateVO.getRebateType() + Constants.UNDERLINE + behaviorEntity.getOutBusinessNo();BehaviorRebateOrderEntity behaviorRebateOrderEntity = BehaviorRebateOrderEntity.builder().userId(behaviorEntity.getUserId()).orderId(RandomStringUtils.randomNumeric(12)).behaviorType(dailyBehaviorRebateVO.getBehaviorType()).rebateDesc(dailyBehaviorRebateVO.getRebateDesc()).rebateType(dailyBehaviorRebateVO.getRebateType()).rebateConfig(dailyBehaviorRebateVO.getRebateConfig()).outBusinessNo(behaviorEntity.getOutBusinessNo()).bizId(bizId).build();orderIds.add(behaviorRebateOrderEntity.getOrderId());// MQ 消息對象SendRebateMessageEvent.RebateMessage rebateMessage = SendRebateMessageEvent.RebateMessage.builder().userId(behaviorEntity.getUserId()).rebateType(dailyBehaviorRebateVO.getRebateType()).rebateConfig(dailyBehaviorRebateVO.getRebateConfig()).bizId(bizId).build();// 構建事件消息BaseEvent.EventMessage<SendRebateMessageEvent.RebateMessage> rebateMessageEventMessage = sendRebateMessageEvent.buildEventMessage(rebateMessage);// 組裝任務對象TaskEntity taskEntity = new TaskEntity();taskEntity.setUserId(behaviorEntity.getUserId());taskEntity.setTopic(sendRebateMessageEvent.topic());taskEntity.setMessageId(rebateMessageEventMessage.getId());taskEntity.setMessage(rebateMessageEventMessage);taskEntity.setState(TaskStateVO.create);BehaviorRebateAggregate behaviorRebateAggregate = BehaviorRebateAggregate.builder().userId(behaviorEntity.getUserId()).behaviorRebateOrderEntity(behaviorRebateOrderEntity).taskEntity(taskEntity).build();behaviorRebateAggregates.add(behaviorRebateAggregate);}// 3. 存儲聚合對象數據behaviorRebateRepository.saveUserRebateRecord(behaviorEntity.getUserId(), behaviorRebateAggregates);// 4. 返回訂單ID集合return orderIds;}@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TaskEntity {/** 活動ID */private String userId;/** 消息主題 */private String topic;/** 消息編號 */private String messageId;/** 消息主體 */private BaseEvent.EventMessage<SendRebateMessageEvent.RebateMessage> message;/** 任務狀態;create-創建、completed-完成、fail-失敗 */private TaskStateVO state;}@Data
public class Task {/** 自增ID */private String id;/** 活動ID */private String userId;/** 消息主題 */private String topic;/** 消息編號 */private String messageId;/** 消息主體 */private String message;/** 任務狀態;create-創建、completed-完成、fail-失敗 */private String state;/** 創建時間 */private Date createTime;/** 更新時間 */private Date updateTime;}

4 定時任務示例

// @Scheduled(cron = "0/5 * * * * ?")
public void exec_db01() {try {// 設置庫表dbRouter.setDBKey(1);dbRouter.setTBKey(0);// 查詢未發送的任務List<TaskEntity> taskEntities = taskService.queryNoSendMessageTaskList();if (taskEntities.isEmpty()) return;// 發送MQ消息for (TaskEntity taskEntity : taskEntities) {try {taskService.sendMessage(taskEntity);taskService.updateTaskSendMessageCompleted(taskEntity.getUserId(), taskEntity.getMessageId());} catch (Exception e) {log.error("定時任務,發送MQ消息失敗 userId: {} topic: {}", taskEntity.getUserId(), taskEntity.getTopic());taskService.updateTaskSendMessageFail(taskEntity.getUserId(), taskEntity.getMessageId());}}} catch (Exception e) {log.error("定時任務,掃描MQ任務表發送消息失敗。", e);} finally {dbRouter.clear();}
}@Data
public class TaskEntity {/** 活動ID */private String userId;/** 消息主題 */private String topic;/** 消息編號 */private String messageId;/** 消息主體 */private String message;}

三: 串聯流程

生產消息:saveUserRebateRecord 方法在事務中插入用戶行為返利訂單和任
務對象。
在事務外,調用 eventPublisher.publish 方法發布消息到指定的 
topic。
消息構建和發布:SendRebateMessageEvent 類構建 EventMessage 對象,包含隨機
生成的 ID、當前時間戳和數據,并返回消息隊列的 topic。
消費消息:RebateMessageCustomer 類監聽指定隊列的消息,解析消息內容,
根據 rebateType 字段的不同,調用相應的服務方法處理消息。
定時任務補償:SendMessageTaskJob 類定時掃描數據庫中的任務表,發送未發送的
消息到 MQ 隊列,并更新任務狀態。如果發送失敗,記錄錯誤日志并
更新任務狀態為發送失敗。

四:配置文件

spring:rabbitmq:addresses: ****port: ***username: **password: **listener:simple:prefetch: 1 # 每次投遞n個消息,消費完在投遞n個topic:send_rebate: send_rebate

五: 消費失敗

消息發送:
生產者在發送消息時,會將消息的相關信息(如消息內容、發送狀態等)記錄到 task 表中。
如果消息發送成功,則更新 task 表中的狀態為“已發送”。
如果消息發送失敗,則更新 task 表中的狀態為“發送失敗”。
定時任務會掃描 task 表,查找狀態為“發送失敗”的消息,并重試發送。消息消費:
消費者在處理消息時,也會將消息的相關信息(如消息內容、處理狀態等)記錄到 task 表中。
如果消息處理成功,則更新 task 表中的狀態為“已處理”。
如果消息處理失敗,則更新 task 表中的狀態為“處理失敗”。
定時任務會掃描 task 表,查找狀態為“處理失敗”的消息,并重試處理。
通過這種方式,可以確保即使消息在發送或消費過程中出現失敗,也能夠通過重試機制最終成功發送或處理。

示例:

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class MessageService {@Autowiredprivate RabbitTemplate rabbitTemplate;@Autowiredprivate TaskRepository taskRepository;@Transactionalpublic void sendMessage(String message) {try {rabbitTemplate.convertAndSend("send_rebate", message);TaskEntity task = new TaskEntity();task.setMessage(message);task.setStatus("SENT");taskRepository.save(task);} catch (Exception e) {TaskEntity task = new TaskEntity();task.setMessage(message);task.setStatus("SEND_FAILED");taskRepository.save(task);}}
}
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class MessageConsumer {@Autowiredprivate TaskRepository taskRepository;@RabbitListener(queues = "${spring.rabbitmq.topic.send_rebate}")public void handleMessage(String message) {try {// 處理消息的邏輯System.out.println("Processing message: " + message);// 假設處理成功TaskEntity task = new TaskEntity();task.setMessage(message);task.setStatus("PROCESSED");taskRepository.save(task);} catch (Exception e) {// 處理失敗的邏輯TaskEntity task = new TaskEntity();task.setMessage(message);task.setStatus("PROCESS_FAILED");taskRepository.save(task);}}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.List;@Component
public class RetryTaskJob {@Autowiredprivate TaskRepository taskRepository;@Autowiredprivate MessageService messageService;@Autowiredprivate MessageConsumer messageConsumer;@Scheduled(fixedRate = 60000) // 每分鐘執行一次public void retryFailedMessages() {// 重試發送失敗的消息List<TaskEntity> sendFailedTasks = taskRepository.findByStatus("SEND_FAILED");for (TaskEntity task : sendFailedTasks) {try {messageService.sendMessage(task.getMessage());task.setStatus("SENT");taskRepository.save(task);} catch (Exception e) {// 記錄日志或進行其他處理}}// 重試處理失敗的消息List<TaskEntity> processFailedTasks = taskRepository.findByStatus("PROCESS_FAILED");for (TaskEntity task : processFailedTasks) {try {messageConsumer.handleMessage(task.getMessage());task.setStatus("PROCESSED");taskRepository.save(task);} catch (Exception e) {// 記錄日志或進行其他處理}}}
}

六: 防止重復消費

保證消息消費的冪等性是確保消息系統可靠性的重要一環。冪等性意
味著無論消息被處理一次還是多次,結果都是相同的。以下是一些常見的策略來保證消息消費的冪等性:唯一標識符:為每條消息生成一個唯一的標識符(如 UUID),并在
處理消息時檢查該標識符是否已經被處理過。如果已經處理過,則忽略該消息。狀態檢查:在處理消息之前,檢查系統的狀態,確保該消息對應的操
作尚未執行。例如,如果消息是要更新某個資源,可以先檢查該資源的狀態,確保更新操作尚未執行。數據庫唯一約束:在數據庫中為消息處理結果創建唯一約束,確保相
同的消息不會被重復處理。冪等API設計:設計冪等的API,確保相同的請求多次執行不會產生不
同的結果。例如,使用“PUT”方法更新資源,而不是“POST”方法。

使用 Redis 來實現消息消費的冪等性是一個非常有效的方法。Redis 是一個高性能的內存數據庫,適合用于存儲臨時狀態信息。
1 消費者處理消息并記錄到 Redis

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component
public class MessageConsumer {@Autowiredprivate StringRedisTemplate redisTemplate;@RabbitListener(queues = "${spring.rabbitmq.topic.send_rebate}")public void handleMessage(String message) {// 假設消息中包含唯一標識符String messageId = extractMessageId(message);// 檢查消息是否已經處理過或正在處理if (redisTemplate.hasKey(messageId)) {System.out.println("Message already processed or being processed: " + messageId);return;}// 將消息ID存入Redis,設置過期時間redisTemplate.opsForValue().set(messageId, "PROCESSING", 60, TimeUnit.SECONDS);try {// 處理消息的邏輯System.out.println("Processing message: " + message);// 假設處理成功redisTemplate.opsForValue().set(messageId, "PROCESSED", 60, TimeUnit.SECONDS);} catch (Exception e) {// 處理失敗的邏輯redisTemplate.delete(messageId); // 刪除Redis中的記錄,以便可以重試}}private String extractMessageId(String message) {// 假設消息中包含唯一標識符,例如 JSON 格式中的 "id" 字段// 這里只是一個示例,實際實現可能需要解析消息內容return message.substring(0, 36); // 假設 UUID 長度為 36}
}
spring:redis:host: localhostport: 6379
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

通過上述方法和配置,您可以確保消息消費的冪等性。消費者在處理消息之前會檢查消息的唯一標識符是否已經存在于 Redis 中,如果存在,則忽略該消息,從而避免重復處理。同時,通過設置過期時間,可以確保在處理過程中出現異常時,Redis 中的記錄會被刪除,從而允許消息重試。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/43581.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/43581.shtml
英文地址,請注明出處:http://en.pswp.cn/web/43581.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在SpringBoot使用AOP防止接口重復提交

前言 防止接口重復提交有跟多種方法&#xff0c;可以在前端做處理。同樣在后端也能處理&#xff0c;而且后端的處理也有很多中方法。最先能想到的就是加鎖&#xff0c;也可以直接在該接口的實現過程中進行處理&#xff08;可以參考防止數據重復提交的6種方法(超簡單)&#xff…

動手學Avalonia:基于硅基流動構建一個文生圖應用(一)

文生圖 文生圖&#xff0c;全稱“文字生成圖像”&#xff08;Text-to-Image&#xff09;&#xff0c;是一種AI技術&#xff0c;能夠根據給定的文本描述生成相應的圖像。這種技術利用深度學習模型&#xff0c;如生成對抗網絡&#xff08;GANs&#xff09;或變換器&#xff08;T…

【Mac】Charles for Mac(HTTP協議抓包工具)及同類型軟件介紹

軟件介紹 Charles for Mac 是一款功能強大的網絡調試工具&#xff0c;主要用于HTTP代理/HTTP監視器。以下是它的一些主要特點和功能&#xff1a; 1.HTTP代理&#xff1a;Charles 可以作為HTTP代理服務器&#xff0c;允許你查看客戶端和服務器之間的所有HTTP和SSL/TLS通信。 …

金航標kinghelm宋仕強在介紹自己公司時說

金航標kinghelm宋仕強在介紹自己公司時說&#xff0c;金航標成立于2007年&#xff0c;成立地點在華強北雷圳大廈803室&#xff0c;后搬到華強北廣業大廈24樓CD室&#xff0c;后搬遷到龍華展滔科技大廈C座C809和C817室&#xff0c;現在的辦公地址為龍崗區坂田街道百瑞達大廈&…

WSL安裝USB驅動

wsl用不了USB盤&#xff0c;需要安裝驅動 1、安裝windows驅動 https://github.com/dorssel/usbipd-win/releases 下載msi&#xff0c;并且安裝 2、linux里面安裝 sudo apt install linux-tools-5.4.0-77-generic hwdata sudo update-alternatives --install /usr/local/bin/usb…

PageDTO<T>,PageQuery,BeanUtils,CollUtils的封裝

一、PageDTO<T> import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.annotation.JsonIgnore; import com.tianji.common.utils.BeanUtils; import com.tianji.common.utils.CollUtils; import com.tianji.common.utils.…

C#中的MD5摘要算法與哈希算法

文章目錄 一、哈希算法基礎二、MD5 算法原理三、MD5摘要算法四、哈希算法五、C#實現示例MD5算法示例哈希算法示例字符串MD5值對比 六、總結 一、哈希算法基礎 哈希算法是一種單向密碼體制&#xff0c;它將任意長度的數據轉換成固定長度的字符串。這種轉換是不可逆的&#xff0…

IDEA中配置代理,解決Codearts Snap登陸不了的問題

問題描述&#xff1a;在mac電腦中的idea中安裝了華為的codearts snap插件&#xff0c;一直登錄不了&#xff0c;賬號是沒問題的&#xff0c;后來我懷疑是我的代理有問題&#xff0c;找到IDEA中的代理設置先是有這個問題“You have JVM property "https.proxyHost" se…

千呼新零售2.0分銷商城視頻介紹

千呼新零售2.0系統是零售行業連鎖店一體化收銀系統&#xff0c;包括線下收銀線上商城連鎖店管理ERP管理商品管理供應商管理會員營銷等功能為一體&#xff0c;線上線下數據全部打通。 適用于商超、便利店、水果、生鮮、母嬰、服裝、零食、百貨、寵物等連鎖店使用。 詳細介紹請…

C語言 將兩個字符串連接起來,不用strcat函數

編一個程序,將兩個字符串連接起來,不要用strcat函數。 #include <stdio.h>void my_strcat(char *s1, const char *s2) {while (*s1) {s1;}while (*s2) {*s1 *s2;s1;s2;}*s1 \0; }int main() {char s1[100] "Hello, ";char s2[] "World!";my_str…

Android初學者書籍推薦

書單 1.《Android應用開發項目式教程》&#xff0c;機械工業出版社&#xff0c;2024年出版2.《第一行代碼Android》第二版3.《第一行代碼Android》第三版4.《瘋狂Android講義》第四版5.《Android移動應用基礎教程&#xff08;Android Studio 第2版&#xff09;》 從學安卓到用安…

uniapp 打包成安卓APP預覽base64pdf實現方法

下載PDF.js 問題描述 在uniapp中預覽base64的PDF&#xff0c;可以使用web-view組件嵌入一個PDF.js的實例。以下是一個簡單的示例&#xff1a; 解決方案&#xff1a; 1.在頁面的.vue文件中添加web-view組件&#xff1a; <template><view style"width: 50%;&qu…

【機器學習】支持向量機與主成分分析在機器學習中的應用

文章目錄 一、支持向量機概述什么是支持向量機&#xff1f;超平面和支持向量大邊距直覺 二、數據預處理與可視化數據集的基本信息導入必要的庫加載數據集數據概況數據可視化特征對的散點圖矩陣類別分布條形圖平均面積與平均光滑度的散點圖變量之間的相關性熱圖 三、模型訓練&am…

JS【詳解】類 class ( ES6 新增語法 )

本質上&#xff0c;類只是一種特殊的函數。 console.log(typeof 某類); //"function"聲明類 class 方式 1 – 類聲明 class Car {constructor(model, year) {this.model model;this.year year;} }方式 2 – 類表達式 匿名式 const Car class {constructor(mod…

在conda的環境中安裝Jupyter及其他軟件包

Pytorch版本、安裝和檢驗 大多數軟件包都是隨Anaconda安裝的&#xff0c;也可以根據需要手動安裝一些其他軟件包。 目錄 創建虛擬環境 進入虛擬環境 安裝Jupyter notebook 安裝matplotlib 安裝 pandas 創建虛擬環境 基于conda包的環境創建、激活、管理與刪除http://t.cs…

podman 替代 docker ? centos Stream 10 已經棄用docker,開始用podman了!

&#x1f468;?&#x1f393;博主簡介 &#x1f3c5;CSDN博客專家 ??&#x1f3c5;云計算領域優質創作者 ??&#x1f3c5;華為云開發者社區專家博主 ??&#x1f3c5;阿里云開發者社區專家博主 &#x1f48a;交流社區&#xff1a;運維交流社區 歡迎大家的加入&#xff01…

淺談React

forwardRef和useImperativeHandle的聯動使用 import React, { useImperativeHandle, useRef } from "react" import { forwardRef } from "react"const CustomInput forwardRef((props, ref) > {const inputRef useRef<HTMLInputElement>(null…

Java中鎖的分類、原理、使用場景、注意事項、優缺點等詳解

Java開發中&#xff0c;鎖是保證多線程安全的重要手段。Java提供了多種類型的鎖來滿足不同的同步需求。在這篇文章中&#xff0c;我將為您介紹以下幾種常見的鎖類型&#xff1a; 偏向鎖/輕量級鎖/重量級鎖 偏向鎖&#xff1a;當一個線程獲取一個對象的鎖時&#xff0c;如果發現…

解決MCM功率電源模塊EMC的關鍵

對MCM功率電源而言&#xff0c;由于其工作在幾百kHz的高頻開關狀態&#xff0c;故易成為干擾源。電磁兼容性EMC&#xff08;Electro Magnetic Compatibility&#xff09;&#xff0c;是指設備或系統在其電磁環境中符合要求運行并不對其環境中的任何設備產生無法忍受的電磁干擾的…

react父調用子的方法,子調用父的方法

父調用子的方法 // 子組件 import React, { useRef, useEffect } from react;const ChildComponent ({ childMethodRef }) > {const childMethod useRef(null);useEffect(() > {childMethodRef.current childMethod;}, []);const someMethod () > {console.log(子…