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

SpringBoot+Redis+RabbitMQ完成增删改查

各部分分工职责

RabbitMQ负责添加、修改、删除的异步操作
Redis负责数据的缓存

RabbitMQ里面角色职责简单描述

RabbitMQ里面有几个角色要先分清以及他们的对应关系:

交换机、队列、路由键
交换机和队列是一对多
队列和路由键是多对多

然后就是消息的发送者(生产者)和消息的接受者(消费者)

此案例中,添加修改删除要从生产者发送到消费者,也就是说,消费者才是具体干活的角色,消息生产者只需要把消息发送到对应的队列中,由交换机根据路由键发送到对应的队列中

Redis职责简单描述

Redis只需要把要看的数据以及新添加的数据,添加到缓冲中即可,如果缓冲中没有,就从数据库查,再添加到缓存中,所以此次数据类型用的Hash

pom.xml文件坐标引入

        <!-- redis工具 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- JSON工具 https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version></dependency><!--spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency><!-- Spring Boot Starter AMQP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>

SpringBoot的配置文件

# RabbitMQ:配置,服务器地址,端口,用户名,密码
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest# 使用 Redis 作为缓存存储,具体配置:服务器地址,端口,密码
spring.cache.type=redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=root
spring.redis.password=
# 连接工厂使用的数据库索引,redis默认有16个db,索引0-15
spring.redis.database=0
#spring.redis.timeout=0
# 连接池最大连接数(使用负值表示没有限制) 这个值决定了同时可以有多少个活动的连接
spring.redis.lettuce.pool.max-active=8
## 连接池最大阻塞等待时间(-1表示没有限制) 当连接池中的所有连接都被占用时,新的请求会等待一段时间
spring.redis.lettuce.pool.max-wait=-1
## 连接池中的最大空闲连接,连接池中最多可以有多少个空闲的连接
spring.redis.lettuce.pool.max-idle=8
## 连接池中的最小空闲连接,连接池中至少要有多少个空闲的连接
spring.redis.lettuce.pool.min-idle=0

Redis配置类

@EnableCaching      //开启缓存
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 Redis 的值(Value)GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();// 使用 StringRedisSerializer 来序列化和反序列化 Redis 的键(Key)StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// 设置键(key)的序列化器template.setKeySerializer(stringRedisSerializer);// 设置值(value)的序列化器template.setValueSerializer(jackson2JsonRedisSerializer);// 设置 Hash 键(key)的序列化器template.setHashKeySerializer(stringRedisSerializer);// 设置 Hash 值(value)的序列化器template.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置序列化(解决乱码的问题),过期时间600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600))  //设置数据过期时间600秒.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}

RabbitMQ配置类

如果没有配置logback,就把log.info()的代码全部删了,不影响运行,后面有的话也都删了

@Configuration
public class RabbitMQConfig {public static final String EXCHANGE_NAME = "bill.exchange"; // 交换机名称public static final String QUEUE_SAVE_UPDATE = "bill.saveupdate"; // 保存修改队列public static final String QUEUE_DELETE = "bill.delete"; // 删除队列public static final String ROUTING_SAVE_UPDATE_KEY = "bill.saveupdatekey"; // 保存修改路由键public static final String ROUTING_DELETE_KEY = "bill.deletekey"; // 删除路由键/*** 添加/修改定义队列* @return 队列对象*/@Beanpublic Queue queueSaveUpdate() {log.info(QUEUE_SAVE_UPDATE + ":RabbitMQ队列初始化成功:" + LocalDateTime.now());return new Queue(QUEUE_SAVE_UPDATE, true); // durable: 是否持久化}/*** 删除定义队列* @return 队列对象*/@Beanpublic Queue queueDelete() {log.info(QUEUE_DELETE + ":RabbitMQ队列初始化成功:" + LocalDateTime.now());return new Queue(QUEUE_DELETE, true); // durable: 是否持久化}/*** 定义交换机* @return 交换机对象*/@Beanpublic TopicExchange exchange() {log.info(EXCHANGE_NAME + ":RabbitMQ交换机初始化成功:" + LocalDateTime.now());return new TopicExchange(EXCHANGE_NAME);}/*** 绑定队列和交换机* @return 绑定对象*/@Beanpublic Binding bindingSaveUpdate() {log.info(ROUTING_SAVE_UPDATE_KEY + ":RabbitMQ绑定队列和交换机成功:" + LocalDateTime.now());return BindingBuilder.bind(queueSaveUpdate()).to(exchange()).with(ROUTING_SAVE_UPDATE_KEY);}/*** 绑定队列和交换机* @return 绑定对象*/@Beanpublic Binding bindingDelete() {log.info(ROUTING_DELETE_KEY + ":RabbitMQ绑定队列和交换机成功:" + LocalDateTime.now());return BindingBuilder.bind(queueDelete()).to(exchange()).with(ROUTING_DELETE_KEY);}
}

RabbitMQ配置消息发送者(生产者)

也就是说,在需要异步调用的地方,注入BillMessageSender,然后,调对应的方法就可以了

@Service
public class BillMessageSender {@Autowiredprivate RabbitTemplate rabbitTemplate;/*** 发送用户添加修改消息* @param bill 参数对象*/public void sendBillSaveUpdateMessage(Bill bill) {rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.ROUTING_SAVE_UPDATE_KEY, bill);}/*** 发送用户删除消息* @param ids 参数列表*/public void sendBillDeleteMessage(List<Long> ids) {rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.ROUTING_DELETE_KEY, ids);}
}

异步发送消息

这是在Service层,
所以把ApiResult()这个自定义返回类换成你们的就行了
ObjectMapper这个工具主要是用来处理JSON数据的,这里我用是因为为了方便实体类和Map之间相互转换的,BillMapper是我自己用到的,这个可以换成你们自己的,不影响,剩下的就基本上没啥了,有不懂的可以评论区问,看到会回复

@Slf4j
@Service
public class BillRedisService {@Resourceprivate BillMapper billMapper;@Resourceprivate ObjectMapper objectMapper;@Resourceprivate BillMessageSender billMessageSender;@Resourceprivate RedisTemplate<String, Object> redisTemplate;// redis 中 bill的键private static final String BILL_REDIS_KEY = "bill:info";/*** 保存或者修改信息* @param bill* @return*/@Transactionalpublic ApiResult saveUpdateByRedis(Bill bill){if(bill.getId() == null){billMessageSender.sendBillSaveUpdateMessage(bill);   //把要添加的信息放入消息队列}else {log.info("[ " + bill.getId() + " ] 修改缓存中的数据");String key = BILL_REDIS_KEY + bill.getId();          //找到要修改值对应的redis的keyMap map = objectMapper.convertValue(bill, Map.class);//把对象转换成mapredisTemplate.opsForHash().putAll(key, map);         //更新数据到缓存中billMessageSender.sendBillSaveUpdateMessage(bill);   //把要修改的信息放入消息队列}return ApiResult.success();}/*** 从缓存取数据* @param id* @return*/@Transactionalpublic Bill selectPrimaryKeyByRedis(Long id){Bill bill;String key = BILL_REDIS_KEY + id;//有这个键就取数据,不然就查数据库if (redisTemplate.hasKey(key)) {log.info("[ " + id + " ] 从缓存中取数据");Map<Object, Object> map = redisTemplate.opsForHash().entries(key);bill = objectMapper.convertValue(map, Bill.class);}else{log.info("[ " + id + " ] 缓存中没有,向数据库中查询数据");bill = billMapper.selectByPrimaryKey(id);String putKey = BILL_REDIS_KEY + bill.getId();          //找到要修改值对应的redis的keyMap map = objectMapper.convertValue(bill, Map.class);   //把对象转换成mapredisTemplate.opsForHash().putAll(putKey, map);         //更新数据到缓存中}return bill;}/*** 删除方法* @param ids* @return*/@Transactionalpublic ApiResult delByRedis(List<Long> ids) {log.info("[ " + Arrays.toString(ids.toArray()) + " ] 以上数据要被删除");for (Long id : ids) {String key = BILL_REDIS_KEY + id;redisTemplate.delete(key);  //删除缓存中的数据}//数据库信息交给消息队列删除billMessageSender.sendBillDeleteMessage(ids);return ApiResult.success();}
}

RabbitMQ配置消息接收者(消费者)

添加上这个注解@RabbitListener(queues = RabbitMQConfig.QUEUE_SAVE_UPDATE),
并指明监听的队列queues = RabbitMQConfig.QUEUE_SAVE_UPDATE
就能获取到消息发送者发送过来的任务以及任务参数了,就可以在这里写处理逻辑了,如果没有配置logback,可以把@Slf4j,以及log.info(),这两个代码删除了

@Slf4j
@Component
public class BillMessageReceiver {@Autowiredprivate BillMapper billMapper;@Autowiredprivate ObjectMapper objectMapper;@Resourceprivate RedisTemplate<String, Object> redisTemplate;// redis 中 bill的键private static final String BILL_REDIS_KEY = "bill:info";/*** 处理添加和修改操作* @param bill 参数对象*/@Transactional@RabbitListener(queues = RabbitMQConfig.QUEUE_SAVE_UPDATE)public void receiveBillSaveUpdateMessage(Bill bill) {log.info(RabbitMQConfig.QUEUE_SAVE_UPDATE + " 队列获取到数据:" + bill.toString());if (bill == null) {return;}if (bill.getId() == null) {bill.setDeleted(0);bill.setCreateTime(new Date());billMapper.insertSelective(bill);} else {bill.setUpdateTime(new Date());billMapper.updateByPrimaryKeySelective(bill);}String key = BILL_REDIS_KEY + bill.getId();          //添加后就有主键了,拼接成redis的keyMap map = objectMapper.convertValue(bill, Map.class);//把对象转换成mapredisTemplate.opsForHash().putAll(key, map);         //把添加的数据放到缓存中}/*** 删除数据* @param ids 参数列表*/@Transactional@RabbitListener(queues = RabbitMQConfig.QUEUE_DELETE)public void receiveBillDeleteMessage(List<Long> ids) {log.info(RabbitMQConfig.QUEUE_DELETE + " 队列获取到数据:" + Arrays.toString(ids.toArray()));Bill bill = new Bill();bill.setDeleted(1);Example example = new Example(Bill.class);Example.Criteria criteria = example.createCriteria();criteria.andIn("id",ids);billMapper.updateByExampleSelective(bill,example);}}

总结

再把逻辑捋一下

添加修改删除,这些操作统一发送给RabbitMQ,由RabbitMQ的消费者处理后续操作

查看详情,添加和更新的数据,交给Redis缓存,缓存没有,就查数据库,然后再缓存到Redis中,就第一遍查数据库,后续走的都是缓存

以上代码实现的功能就是,

全部数据查询还是走的数据库(数据量不多),但是单个查询,查询详情,先查缓存,缓存没有再查数据库,然后再添加到缓存中,下次查询就不走数据库了

添加修改删除统一发送给RabbitMQ消息队列,由消息队列异步完成后续的任务,并更新或者删除对应的缓存

这比之前单独的对数据库操作,多了2层逻辑,RabbitMQ和缓存的处理,这个例子就是简单的使用RabbitMQ和Redis,算是个小入门,如果有其他好的建议,可以评论一下,十分感谢!

相关文章:

SpringBoot+Redis+RabbitMQ完成增删改查

各部分分工职责 RabbitMQ负责添加、修改、删除的异步操作 Redis负责数据的缓存 RabbitMQ里面角色职责简单描述 RabbitMQ里面有几个角色要先分清以及他们的对应关系&#xff1a; 交换机、队列、路由键 交换机和队列是一对多 队列和路由键是多对多 然后就是消息的发送者&…...

【系统集成中级】线上直播平台开发项目质量管理案例分析

【系统集成中级】线上直播平台开发项目质量管理案例分析 一、案例二、小林在项目质量管理中存在的问题&#xff08;一&#xff09;计划阶段缺失&#xff08;二&#xff09;测试用例编制与执行问题&#xff08;三&#xff09;质量管理流程问题&#xff08;四&#xff09;质量保证…...

浪潮信息领航边缘计算,推动AI与各行业深度融合

在9月20日于安徽盛大召开的浪潮信息边缘计算合作伙伴大会上&#xff0c;浪潮信息指出&#xff0c;未来的计算领域将全面融入AI技术&#xff0c;特别是在企业边缘侧&#xff0c;智能应用特别是生成式人工智能应用正在迅速普及&#xff0c;这一趋势正引领边缘计算向边缘智算的方向…...

Koa2项目实战3 (koa-body,用于处理 HTTP 请求中的请求体)

以用户注册接口为例&#xff0c;需要在请求里携带2个参数&#xff1a;用户名&#xff08;user_name&#xff09;和密码&#xff08;password&#xff09;。 开发者需要在接口端&#xff0c;解析出user_name 、password。 在使用Koa开发的接口中&#xff0c;如何解析出请求携带…...

复盘20241012

1、 classpath "com.android.tools.build:gradle:8.5.1" 的版本 与distributionUrlhttps\://services.gradle.org/distributions/gradle-8.9-bin.zip的对应规则&#xff1a; Execution failed for task :app:compileDebugKotlin. 解决方案 切换 setting --> ot…...

泊松流负载均衡控制

目录 泊松流负载均衡控制 一、到达率λ 二、服务率μ 三、泊松流负载均衡控制 泊松流负载均衡控制 在探讨泊松流负载均衡控制时,我们主要关注的是到达率λ和服务率μ这两个核心参数。以下是对这两个参数及其在泊松流负载均衡控制中作用的详细解释: 一、到达率λ 定义:…...

3D打印矫形器市场报告:未来几年年复合增长率CAGR为10.8%

3D 打印矫形器是指使用 3D 打印技术制作的定制外部支撑装置。它们有助于稳定、引导、缓解或纠正肌肉骨骼状况&#xff0c;并根据个体患者的解剖结构进行设计&#xff0c;通常使用 3D 扫描和建模技术。3D 打印在矫形器方面的主要优势是能够生产精确适合患者解剖结构的定制装置&a…...

Richtek立锜科技线性稳压器 (LDO) 选型

一、什么是LDO? LDO也可称为低压差线性稳压器&#xff0c;适合从较高的输入电压转换成较低输出电压的应用&#xff0c;这种应用的功率消耗通常不是很大&#xff0c;尤其适用于要求低杂讯、低电流和输入、输出电压差很小的应用环境。 二、LDO的特性 LDO透过控制线性区调整管…...

Leetcode 前 k 个高频元素

使用最小堆算法来解决这道题目&#xff1a;相当于有一个容量固定为K的教室&#xff0c;只能容纳 K 个人&#xff0c;学生们逐个逐个进入该教室&#xff0c;当教室容量达到K人之后&#xff0c;每次进入一个新的学生后&#xff0c;我们将分数最低的学生(类似本题中的频率最低元素…...

[LeetCode] 面试题01.02 判定是否互为字符重拍

题目描述&#xff1a; 给定两个由小写字母组成的字符串 s1 和 s2&#xff0c;请编写一个程序&#xff0c;确定其中一个字符串的字符重新排列后&#xff0c;能否变成另一个字符串。 示例 1&#xff1a; 输入: s1 "abc", s2 "bca" 输出: true 示例 2&am…...

数据结构-4.5.KMP算法(旧版上)-朴素模式匹配算法的优化

朴素模式匹配算法最坏的情况&#xff1a; 一.实例&#xff1a; 第一轮匹配失败&#xff0c;开始下一轮的匹配&#xff1a; 不断的操作&#xff0c;最终匹配成功&#xff1a; 如上述图片所述&#xff0c;朴素模式匹配算法会导致时间开销增加&#xff0c; 优化思路&#xff1a;主…...

STM32 QSPI接口驱动GD/W25Qxx配置简要

STM32 QSPI接口GD/W25Qxx配置简要 &#x1f4dd;本篇会具体涉及介绍Winbond&#xff08;华邦&#xff09;和GD(兆易创新) NOR flash相关型号指令差异。由于网络上可以搜索到很多相关QSPI相关知识内容&#xff0c;不对QSPI通讯协议做深度解析。 &#x1f516;首先确保所使用的ST…...

UCI-HAR数据集深度剖析:训练仿真与可视化解读

在本篇文章中&#xff0c;我们将深入探讨如何使用Python对UCI人类活动识别&#xff08;HAR&#xff09;数据集进行分割和预处理&#xff0c;以及运用模型网络CNN对数据集进行训练仿真和可视化解读。 一、UCI-HAR数据集分析及介绍 UCI-HAR数据集是一个公开的数据集&#xff0c…...

牛客SQL练习详解 06:综合练习

牛客SQL练习详解 06&#xff1a;综合练习 SQL34 统计复旦用户8月练题情况SQL35 浙大不同难度题目的正确率SQL39 21年8月份练题总数 叮嘟&#xff01;这里是小啊呜的学习课程资料整理。好记性不如烂笔头&#xff0c;今天也是努力进步的一天。一起加油进阶吧&#xff01; SQL34 统…...

k8s apiserver高可用方案

目前官方推荐有 2 种方式部署k8s apiserver 高可用 keepalived and haproxy 部署有2种方式&#xff0c;一种是systemd管理的&#xff0c;另一种是pod形式&#xff0c;使用那种可以根据实际情况选择 服务部署 systemd方式 可以通过包管理工具安装&#xff0c;正常启动之后&…...

服务器数据恢复—硬盘坏扇区导致Linux系统服务器数据丢失的数据恢复案例

服务器数据恢复环境&#xff1a; 一台linux操作系统网站服务器&#xff0c;该服务器上部署了几十个网站&#xff0c;使用一块SATA硬盘。 服务器故障&原因&#xff1a; 服务器在工作过程中突然宕机。管理员尝试重新启动服务器失败&#xff0c;于是将服务器上的硬盘拆下检测…...

【多线程】多线程(12):多线程环境下使用哈希表

【多线程环境下使用哈希表&#xff08;重点掌握&#xff09;】 可以使用类&#xff1a;“ConcurrentHashMap” ★ConcurrentHashMap对比HashMap和Hashtable的优化点 1.优化了锁的粒度【最核心】 //Hashtable的加锁&#xff0c;就是直接给put&#xff0c;get等方法加上synch…...

轻量服务器和云服务器ecs哪个好用一些?

轻量服务器和云服务器ecs哪个好用一些&#xff1f;轻量服务器与云服务器ECS在多方面存在显著差异&#xff0c;对于需要高性能计算和大规模数据处理的用户来说&#xff0c;ECS可能是更好的选择&#xff1b;而对于预算有限且需求较为简单的用户来说&#xff0c;轻量服务器可能更为…...

【交通标志识别系统】Python+卷积神经网络算法+人工智能+深度学习+机器学习+算法模型

一、介绍 交通标志识别系统。本系统使用Python作为主要编程语言&#xff0c;在交通标志图像识别功能实现中&#xff0c;基于TensorFlow搭建卷积神经网络算法模型&#xff0c;通过对收集到的58种常见的交通标志图像作为数据集&#xff0c;进行迭代训练最后得到一个识别精度较高…...

特种设备作业叉车司机试题附答案

1.发生事故要本着"&#xff08; &#xff09;不放过"的原则,查明原因、分清责任、严肃处理。 A.三 B.四 C.五 答案:B 2.柴油发动机在压缩行程终了时气体的温度和压力都比汽油机&#xff08; &#xff09;。 A.低 B.高 C.相同 答案:B 3.柴油发动机的压缩比比汽…...

EmbeddingGemma-300m部署指南:Ollama镜像+Prometheus监控+日志追踪一体化

EmbeddingGemma-300m部署指南&#xff1a;Ollama镜像Prometheus监控日志追踪一体化 想快速搭建一个功能强大、易于管理的文本向量化服务吗&#xff1f;EmbeddingGemma-300m作为谷歌推出的轻量级嵌入模型&#xff0c;凭借其3亿参数和出色的性能&#xff0c;是构建本地语义搜索、…...

YOLO12快速部署指南:Gradio界面已配好,启动就能用

YOLO12快速部署指南&#xff1a;Gradio界面已配好&#xff0c;启动就能用 1. 为什么选择YOLO12镜像 YOLO12作为2025年最新发布的目标检测模型&#xff0c;带来了革命性的注意力为中心架构。这个预配置好的镜像让您无需任何复杂操作&#xff0c;就能立即体验最先进的目标检测技…...

保姆级避坑指南:在Ubuntu 22.04上为ROS2 Humble编译OpenCV 4.2.0和cv_bridge

深度解析&#xff1a;Ubuntu 22.04下ROS2 Humble与OpenCV 4.2.0的精准版本匹配实战 当视觉SLAM遇上ROS2生态&#xff0c;版本依赖就像一场精密的外科手术。本文将带你穿透ORB-SLAM3等视觉算法与ROS2 Humble环境整合时的核心痛点——特别是OpenCV 4.2.0与cv_bridge的版本锁定机…...

Excel也能搞定GRR!不用买昂贵软件,这份保姆级模板和计算指南请收好

Excel也能搞定GRR&#xff01;不用买昂贵软件&#xff0c;这份保姆级模板和计算指南请收好 在制造业质量管理中&#xff0c;测量系统分析&#xff08;MSA&#xff09;是确保数据可靠性的基石。但现实情况是&#xff0c;许多中小企业和初创团队面对动辄上万元的专业统计软件只能…...

EasyAnimateV5-7b-zh-InP在AI艺术创作中的算法优化实践

EasyAnimateV5-7b-zh-InP在AI艺术创作中的算法优化实践 1. 引言 作为一名数字艺术创作者&#xff0c;我一直在寻找能够提升创作效率和质量的技术工具。最近在尝试使用EasyAnimateV5-7b-zh-InP进行艺术创作时&#xff0c;发现这个模型在图像到视频的转换方面表现出色&#xff…...

避开这些坑!Mapbox图层管理实战:动态加载GeoJSON数据的正确姿势

Mapbox高级图层管理实战&#xff1a;GeoJSON动态加载与性能优化全解析 当处理省级以上GIS数据可视化时&#xff0c;Mapbox的图层管理能力直接决定了应用的流畅度和用户体验。许多开发者在使用GeoJSON数据源时&#xff0c;常遇到内存泄漏、渲染卡顿、交互延迟等问题。本文将深入…...

企业微信考勤自动化解决方案:基于EasyWeChat的实战指南

企业微信考勤自动化解决方案&#xff1a;基于EasyWeChat的实战指南 【免费下载链接】easywechat &#x1f4e6; 一个 PHP 微信 SDK 项目地址: https://gitcode.com/gh_mirrors/ea/easywechat 在数字化办公普及的今天&#xff0c;企业考勤管理面临着数据采集繁琐、统计分…...

DevOps实践:如何让开发、测试、运维不再“打架”?

质量不再是孤岛在追求快速迭代的现代软件开发中&#xff0c;开发、测试与运维团队之间的隔阂与摩擦&#xff0c;常常被戏称为“部门战争”。开发团队渴望快速交付新功能&#xff0c;测试团队需要足够的时间来保障质量&#xff0c;而运维团队则首要追求系统的稳定与可靠。当发布…...

通信确定性可视化冗余现场总线技术开发白皮书(能源化工交通高可靠行业 Profibus DP CAN PROFINET EtherNet/IP SPE APL)

1.前言现场总线是工业物联网的核心支撑技术&#xff0c;但当前国际主流方案在国内应用中普遍存在开发门槛高、硬件成本高、调试维护复杂、冗余配置昂贵等问题&#xff0c;难以满足中小型自动化项目及国产控制系统对高性价比、高可靠性通信的需求。CANWeb现场总线深度融合CAN的高…...

Qwen3.5-2B多场景教程:农业技术人员上传病虫害图→识别种类→推荐药剂

Qwen3.5-2B多场景教程&#xff1a;农业技术人员上传病虫害图→识别种类→推荐药剂 1. 引言&#xff1a;农业病虫害识别的技术痛点 在农业生产中&#xff0c;病虫害防治一直是困扰农户的核心问题。传统识别方式存在三大痛点&#xff1a; 识别门槛高&#xff1a;需要专业农技人…...