• [技术干货] SpringBoot 动态数据源切换【转】
    手动数据源切换先看下配置类一个是本地的IP一个是我的服务器上docker容器中的mysql数据库。server.port=8080 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl mybatis.mapper-locations=classpath:mapper/*.xml spring.datasource.aa.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.aa.username=root spring.datasource.aa.password=123 spring.datasource.aa.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useSSL=false spring.datasource.bb.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.bb.username=root spring.datasource.bb.password=123 spring.datasource.bb.url=jdbc:mysql://39.1xx.1xx.1xx:3306/test?serverTimezone=UTC&useSSL=false 然后最主要的就是先编写一个DynamicDatasourceConfig类去继承,AbstractRoutingDataSource这个抽象类。至于为什么继承这个类后续会为大家讲解。import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceChange.getDataSourceKey(); }}然后就是这个DynamicDataSourceChange的切换类,这个类负责切换数据源。import java.util.ArrayList;import java.util.List;public class DynamicDataSourceChange {/*** 这个ThreadLocal 是用来储存当前线程中的数据源的key*/ private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() { /** * 将 master 数据源的 key作为默认数据源的 key */ @Override protected String initialValue() { return "aa"; } }; /** * 切换数据源 * @param key */ public static void setDataSourceKey(String key) { contextHolder.set(key); } /** * 获取数据源 * @return */ public static String getDataSourceKey() { return contextHolder.get(); } /** * 重置数据源 */ public static void clearDataSourceKey() { contextHolder.remove(); }}这个类中的ThreadLocal用来存储当前进程中的数据源的key。上面的额initValue 是我自己设置的默认数据源的key。然后就是数据源的注册,这里也是很关键的一步。package com.llq.datasource.config;import com.alibaba.druid.pool.DruidDataSource;import org.mybatis.spring.SqlSessionFactoryBean;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import javax.sql.DataSource;import java.io.IOException;import java.io.InputStream;import java.util.HashMap;import java.util.Properties;@Configurationpublic class DataSourceRegister { private static final Logger logger = LoggerFactory.getLogger(DataSourceRegister.class); @Bean(name = "aa") @Primary public static DataSource registerDataSource() throws IOException { InputStream asStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("application.properties"); Properties properties = new Properties(); properties.load(asStream); String username = properties.getProperty("spring.datasource.aa.username"); String password = properties.getProperty("spring.datasource.aa.password"); String url = properties.getProperty("spring.datasource.aa.url"); String driverClass = properties.getProperty("spring.datasource.aa.driver-class-name"); DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setUrl(url); dataSource.setDriverClassName(driverClass); return dataSource; } @Bean(name = "bb") public static DataSource registerDataSource2() throws IOException { InputStream asStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("application.properties"); Properties properties = new Properties(); properties.load(asStream); String username = properties.getProperty("spring.datasource.bb.username"); String password = properties.getProperty("spring.datasource.bb.password"); String url = properties.getProperty("spring.datasource.bb.url"); String driverClass = properties.getProperty("spring.datasource.bb.driver-class-name"); DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setUrl(url); dataSource.setDriverClassName(driverClass); return dataSource; } @Bean("dynamicDataSource") public DataSource registerAllDataSource() throws IOException { DataSource dataSource1 = registerDataSource(); DataSource dataSource2 = registerDataSource2(); DynamicDataSource dynamicDataSource = new DynamicDataSource(); HashMap<Object, Object> map = new HashMap<>(); map.put("aa",dataSource1); map.put("bb",dataSource2); //这里敲一下黑板,这里必须要指定默认的数据源不然,在注册时候会出现发现两个数据源的异常问题,所以需要去设置默认数据源,也就是setDefalutTargetDataSource(),这个方法是哪儿来的呢?这个方法是因为DynamicDataSource继承了AbstractRoutingDataSource这个类。 dynamicDataSource.setDefaultTargetDataSource(dataSource1); //这里是将所有的数据源放入 dynamicDataSource.setTargetDataSources(map); logger.info("数据源注册成功,一共注册{}个数据源",map.size()); return dynamicDataSource; } @Bean public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception{ SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(registerAllDataSource()); sessionFactory.setTypeAliasesPackage("com.llq.pojo"); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); sessionFactory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); return sessionFactory; }}然后这样就好了,其余的就是自己写代码测试了。还有就是这里再强调一下,在手动切换数据源的时候你需要去先去清除一下数据源,然后再进行设置。 @RequestMapping("getUsersAA") public String shardingCrudAA(){ DynamicDataSourceChange.clearDataSourceKey(); DynamicDataSourceChange.setDataSourceKey("aa"); List<User> allTables = userService.getAllTables(); return allTables.toString(); } @RequestMapping("getUsersBB") public String shardingCrudBB(){ DynamicDataSourceChange.clearDataSourceKey(); DynamicDataSourceChange.setDataSourceKey("bb"); List<User> allTables = userService.getAllTablesbb(); return allTables.toString(); }动态数据源动态数据源的话其实和手动的大差不差,只不过动态的是交给了AOP根据切入点的时机去实现切换数据源。上面的代码可以不用动,先去创建一个枚举类来配置需要用到的数据源的key以及自定义注解。public enum DataSourceKey {     DB_MASTER,     DB_SLAVE1,     DB_SLAVE2,     DB_OTHER } import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target; @Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface TargetDataSource { DataSourceKey dataSourceKey() default DataSourceKey.DB_MASTER;}然后就是AOP切面实现类import com.apedad.example.annotation.TargetDataSource;import org.apache.log4j.Logger;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Aspect@Order(-1)@Componentpublic class DynamicDataSourceAspect { private static final Logger LOG = Logger.getLogger(DynamicDataSourceAspect.class); @Pointcut("execution(* com.apedad.example.service.*.list*(..))") public void pointCut() { } /** * 执行方法前更换数据源 * * @param joinPoint 切点 * @param targetDataSource 动态数据源 */ @Before("@annotation(targetDataSource)") public void doBefore(JoinPoint joinPoint, TargetDataSource targetDataSource) { DataSourceKey dataSourceKey = targetDataSource.dataSourceKey(); if (dataSourceKey == DataSourceKey.DB_OTHER) { LOG.info(String.format("设置数据源为 %s", DataSourceKey.DB_OTHER)); DynamicDataSourceChange.setDataSourceKey(DataSourceKey.DB_OTHER); } else { LOG.info(String.format("使用默认数据源 %s", DataSourceKey.DB_MASTER)); DynamicDataSourceChange.setDataSourceKey(DataSourceKey.DB_MASTER); } } /** * 执行方法后清除数据源设置 * * @param joinPoint 切点 * @param targetDataSource 动态数据源 */ @After("@annotation(targetDataSource)") public void doAfter(JoinPoint joinPoint, TargetDataSource targetDataSource) { LOG.info(String.format("当前数据源 %s 执行清理方法", targetDataSource.dataSourceKey())); DynamicDataSourceChange.clearDataSourceKey(); } @Before(value = "pointCut()") public void doBeforeWithSlave(JoinPoint joinPoint) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); //获取当前切点方法对象 Method method = methodSignature.getMethod(); if (method.getDeclaringClass().isInterface()) {//判断是否为借口方法 try { //获取实际类型的方法对象 method = joinPoint.getTarget().getClass() .getDeclaredMethod(joinPoint.getSignature().getName(), method.getParameterTypes()); } catch (NoSuchMethodException e) { LOG.error("方法不存在!", e); } } if (null == method.getAnnotation(TargetDataSource.class)) { DynamicDataSourceChange.setDataSourceKey(DataSourceKey.DB_MASTER); } }}原文链接:SpringBoot 动态数据源切换 + 手动数据源切换(最有效的)_数据有效性动态数据源-CSDN博客
  • [技术干货] 【SpringBoot篇】使用Spring Cache高效处理缓存数据
     Spring Cache是一个框架,只要简单加一个注解,就能实现缓存功能。Spring Cache是Spring Framework提供的一个模块,它为应用程序添加了缓存支持。通过使用Spring Cache,你可以在方法级别上定义缓存规则,将方法的返回结果缓存起来,以提高方法调用的性能和响应速度。 🌹简述Spring Cache 是一个框架,只要简单加一个注解,就能实现缓存功能 Spring Cache 是 Spring Framework 提供的一个模块,它为应用程序添加了缓存支持。通过使用 Spring Cache,你可以在方法级别上定义缓存规则,将方法的返回结果缓存起来,以提高方法调用的性能和响应速度。  Spring Cache 的主要特点和功能包括:  注解驱动:Spring Cache 基于注解,通过在方法上添加 @Cacheable、@CachePut、@CacheEvict 等注解,来定义缓存规则和行为。  支持多种缓存实现:Spring Cache 支持多种常见的缓存实现,包括 Ehcache、Redis、Caffeine、ConcurrentMap 等,你可以根据自己的需求选择合适的缓存提供者。  灵活的缓存配置:你可以通过配置文件或者 Java 代码来灵活地配置缓存管理器、缓存的过期时间、缓存的键生成策略等。  支持条件化的缓存操作:除了基本的缓存注解外,Spring Cache 还支持条件化的缓存操作,比如通过 SpEL 表达式来定义条件,决定是否执行缓存操作。  总之,Spring Cache 提供了一种便捷的方式来实现方法级别的缓存,使得开发者可以专注于业务逻辑的实现,而不必过多关注缓存的管理和维护。这样可以有效地提升应用程序的性能,并减少对底层缓存实现的耦合。  🏳️‍🌈常用注解  @Cacheable:触发将方法返回结果缓存。 @CacheEvict:触发从缓存中清除一条或多条数据。 @CachePut:触发将方法返回结果更新到缓存。 @Caching:组合多个缓存注解在一个方法上。 @CacheConfig:在类级别共享缓存注解的通用配置。  🌺使用SpringCache 使用SpringCache需要把下面的代码导入到pom文件中    <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-cache</artifactId>             <version>2.7.3</version>         </dependency> 1 2 3 4 5 这样子我们就可以使用SpringCache了  我们打开下面链接里面的文件,我们下面要使用文件中的代码进行讲解 我用夸克网盘分享了「springcache-demo.zip」,点击链接即可保存。打开「夸克APP」 链接:https://pan.quark.cn/s/571a45c464d6  我们首先来启动Redis服务  CacheDemoApplication.java  在启动类上加上这个注解,开启缓存注解功能  @EnableCaching 1  🛸@Cacheable注解 进入controller包 UserController.java  加入下面的注解  @CachePut 1 插入数据的同时,我们还需要把数据保存到Redis中一份  ⭐测试 我们启动项目,输入http://localhost:8888/doc.html打开接口文档进行测试  发现发送成功,我们去查看数据库,发现数据库已经插入了一条数据了  我们打开Redis Desktop Manager,连接成功后,发送数据 发现  user5对象已经进行了序列化  🛸@CacheEvict 🎍一次清理一条数据 如果我们把数据库中的数据给删除掉了,那么对应的缓存数据我们也应该删除,下面我们就来使用@CacheEvict注解来删除缓存数据 我们加上这一段代码  @CacheEvict(cacheNames = "userCache",key = "#id") 1 然后启动项目  输入http://localhost:8888/doc.html打开接口文档进行测试    查询数据库,发现数据库里面对应id=2的字段被删除了 Redis缓存也删除了  🎍一次删除多条数据 我们在deleteAll这个方法中进行操作 仍然加上@CacheEvict注解  @CacheEvict(cacheNames = "userCache",allEntries = true) 1  使用接口文档http://localhost:8888/doc.html发送数据,就可以删除所有缓存了 ———————————————— 版权声明:本文为CSDN博主「在下小吉.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_72853403/article/details/134371174 
  • [技术干货] Spring Boot 整合RabbitMQ-转载
     前言 一般MQ用于系统解耦、削峰使用,常见于微服务、业务活动等场景。 MQ(消息队列)在微服务、业务活动等场景中的应用主要表现为系统解耦和削峰。 系统解耦 场景描述:在微服务架构中,服务与服务之间需要通信。如果采用直接调用方式,服务间会存在强依赖关系,一个服务的改动可能引发连锁反应。 MQ作用:服务间可以通过消息队列进行通信,一个服务将消息放入队列,另一个服务从队列中取出消息进行处理。这种方式下,服务间实现了解耦,降低了相互的依赖。  削峰 场景描述:在业务活动期间,由于用户请求量短时间内剧增,可能导致系统压力过大甚至崩溃。 MQ作用:通过消息队列实现请求的缓冲。在高并发场景下,系统可以将请求放入消息队列,然后异步处理这些请求,从而平滑系统的处理负载,确保系统的稳定性。  综上所述,MQ因其独特的队列属性和消息传递模式,在分布式、微服务架构中发挥着重要的作用,提高了系统的可用性和稳定性。 1、RabbitMQ概念概念 RabbitMQ整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。  1.1、生产者和消费者 Producer:生产者,就是投递消息的一方。消息一般可以包含2个部分:消息体和标签(Label)。消息的标签用来描述这条消息,比如一个交换器的名称和一个路由键。 Consumer:消费者,就是接受消息的一方。消费者连接到RabbitMQ服务器,并订阅到队列上。当消费者消费一条消息时,只是消费消息的消息体(payload) Broker:消息中间件的服务节点。一个RabbitMQ Broker看做一台RabbitMQ服务器  1.2、队列 Queue:队列,是RabbitMQ的内部对象,用于存储消息   1.3、交换机、路由键、绑定 Exchange:交换器。生产者将消息发送到Exchange(交换器,通常也可以用大写的"X"来表示),有交换器将消息路由到一个或者多个队列中。如果路由不到,或许会返回给生产者,或许直接丢弃。  RoutingKey:路由键。生产者将消息发给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则,而这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。 Binding:绑定。RabbitMQ中通过绑定将交换器与队列联合起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样RabbitMQ就知道如何正确地将消息路由到队列了。  1.3.1、交换机类型 Direct Exchange:直连交换机,根据Routing Key(路由键)进行投递到不同队列。  Fanout Exchange:扇形交换机,采用广播模式,根据绑定的交换机,路由到与之对应的所有队列。  Topic Exchange:主题交换机,对路由键进行模式匹配后进行投递,符号#表示一个或多个词,*表示一个词。  Header Exchange:头交换机,不处理路由键。而是根据发送的消息内容中的headers属性进行匹配。  自学参考:https://blog.csdn.net/qq_38550836/article/details/95358353 2、RabbitMQ运转流程 2.1、生产者发送消息流程 生产者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel) 生产者声明一个交换器,并设置相关属性,比如交换器类型、是否持久化等 生产者声明一个队列并设置相关属性,比如是否排他、是否持久化、是否自动删除等 生产者通过路由键将交换器和队列绑定起来 生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息 相应的交换器根据接收到的路由键查找相匹配的队列。 如果找到,则将从生产者发送过来的消息存入相应的队列。 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者 关闭信道 关闭连接 2.2、消费者接收消息的过程 消费者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)。 消费者向RabbitMQ Broker请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作。 等待RabbitMQ Broker回应并投递相应队列中队列的消息,消费者接收消息。 消费者确认(ack)接收到的消息。 RabbitMQ从队列中删除相应已经被确认的消息。 关闭信道 关闭连接 无论是生产者还是消费者,都需要和RabbitMQ Broker建立连接,这个连接就是一条TCP连接,也就是Connection。一旦TCP连接建立起来,客户端紧接着可以创建一个AMQP信道(Channel),每个信道都会被指派一个唯一的ID。信道是建立在Connection之上的虚拟连接,RabbitMQ处理的每条AMQP指令都是通过信道完成的。 2.3、AMQP协议 Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等  Broker:接收和分发消息的应用,RabbitMQ 就是 Message Broker Virtual Host:虚拟 Broker,将多个单元隔离开 Connection:publisher / consumer 和 broker 之间的 tcp 连接 Channel:connection 内部建立的逻辑连接,通常每个线程创建单独的 channel Routing key:路由键,用来指示消息的路由转发,相当于快递的地址 Exchange:交换机,相当于快递的分拨中心 Queue:消息队列,消息最终被送到这里等待 consumer 取走 Binding:exchange 和 queue 之间的虚拟连接,用于 message 的分发依据 3、RabbitMQ windows安装 3.1、下载 https://github.com/erlang/otp/releases/download/OTP-25.2/otp_win64_25.2.exe https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.11.5/rabbitmq-server-3.11.5.exe 3.2、安装 配置环境变量 cd D:\Program Files\RabbitMQ Server\rabbitmq_server-3.11.5\sbin 开启rabbitmq-plugins插件 rabbitmq-plugins enable rabbitmq_management 打开地址 http://127.0.0.1:15672/ 输入用户名/密码:guest/guest 4、Spring Boot 整合RabbitMQ 4.1、在user-service添加依赖 <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-amqp</artifactId> </dependency> 4.2、配置文件添加 spring:   rabbitmq:     host: 127.0.0.1     port: 5672     username: guest     password: guest 4.3、增加RabbitMQ配置类 package com.xxxx.user.config;  import org.springframework.amqp.core.*; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  @Configuration public class RabbitMQConfig {     /******************direct**********************/     /**      * 创建direct队列      * @return      */     @Bean     public Queue directQueue(){         return new Queue("directQueue");     }      /**      * 创建direct交换机      * @return      */     @Bean     public DirectExchange directExchange(){         return new DirectExchange("directExchange");     }      /**      * 把队列和交换机绑定在一起      * @param queue      * @param directExchange      * @return      */     @Bean     public Binding bindingDirect(@Qualifier("directQueue") Queue queue, DirectExchange directExchange){         return BindingBuilder.bind(queue).to(directExchange).with("routingKey");     }      /******************topic**********************/     @Bean     public Queue topicQuerue1(){         return new Queue("topicQuerue1");     }      @Bean     public Queue topicQuerue2(){         return new Queue("topicQuerue2");     }     @Bean     public TopicExchange topicExchange(){         return new TopicExchange("topicExchange");     }     @Bean     public Binding bindingTopic1(@Qualifier("topicQuerue1") Queue queue,@Qualifier("topicExchange") TopicExchange topicExchange){         return BindingBuilder.bind(queue).to(topicExchange).with("topic.key1");     }      /**      * 通配符:* 表示一个词,# 表示零个或多个词      * @param queue      * @param topicExchange      * @return      */     @Bean     public Binding bindingTopic2(@Qualifier("topicQuerue2") Queue queue,@Qualifier("topicExchange") TopicExchange topicExchange){         return BindingBuilder.bind(queue).to(topicExchange).with("topic.#");     }      /******************fanout**********************/     @Bean     public Queue fanoutQueue1(){         return new Queue("fanoutQueue1");     }     @Bean     public Queue fanoutQueue2(){         return new Queue("fanoutQueue2");     }     @Bean     public Queue fanoutQueue3(){         return new Queue("fanoutQueue3");     }     @Bean     public FanoutExchange fanoutExchange(){         return new FanoutExchange("fanoutExchange");     }      @Bean     public Binding bindingFanout1(@Qualifier("fanoutQueue1") Queue queue,@Qualifier("fanoutExchange") FanoutExchange fanoutExchange){         return BindingBuilder.bind(queue).to(fanoutExchange);     }      @Bean     public Binding bindingFanout2(@Qualifier("fanoutQueue2") Queue queue,@Qualifier("fanoutExchange") FanoutExchange fanoutExchange){         return BindingBuilder.bind(queue).to(fanoutExchange);     }      @Bean     public Binding bindingFanout3(@Qualifier("fanoutQueue3") Queue queue,@Qualifier("fanoutExchange") FanoutExchange fanoutExchange){         return BindingBuilder.bind(queue).to(fanoutExchange);     } } 4.4、新增消费监听类 package com.xxxx.user.consumer;  import com.xxxx.drp.common.entity.UserInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component;  @Component @Slf4j @RabbitListener(queues = "directQueue") public class DataDirectReceiver {     @RabbitHandler     public void process(String data){         log.info("收到directQueue队列信息:" + data);     }      @RabbitHandler     public void process(UserInfo data){         log.info("收到directQueue队列信息:" + data);     } } package com.xxxx.user.consumer;  import com.xxxx.common.entity.UserInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component;  @Component @Slf4j @RabbitListener(queues = {"topicQuerue1","topicQuerue2"}) public class DataFanoutReceiver {     @RabbitHandler     public void process(String data){         log.info("收到topicQuerue队列信息:" + data);     }      @RabbitHandler     public void process(UserInfo data){         log.info("收到topicQuerue队列信息:" + data);     } } package com.xxxx.user.consumer;  import com.xxxx.common.entity.UserInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component;  @Component @Slf4j @RabbitListener(queues = {"fanoutQueue1","fanoutQueue2","fanoutQueue3"}) public class DataTopicReceiver {     @RabbitHandler     public void process(String data){         log.info("收到topicQuerue队列信息:" + data);     }      @RabbitHandler     public void process(UserInfo data){         log.info("收到topicQuerue队列信息:" + data);     } } 4.5、消息生产端 package com.xxxx.user;  import com.xxxx.common.entity.UserInfo; import org.junit.jupiter.api.Test; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class DataSender {     @Autowired     private RabbitTemplate rabbitTemplate;      @Test     public void sendDirect(){         UserInfo userInfo = new UserInfo();         userInfo.setUserAccount("tiger");         userInfo.setPassword("12345");         this.rabbitTemplate.convertAndSend("directExchange","routingKey",userInfo);     }     @Test     public void sendTopic(){         this.rabbitTemplate.convertAndSend("topicExchange","topic.key2","Hello world topic");     }      @Test     public void sendFanout(){         this.rabbitTemplate.convertAndSend("fanoutExchange","","Hello world topic");     } } 总结 MQ因其独特的队列属性和消息传递模式,在分布式、微服务架构中发挥着重要的作用,提高了系统的可用性和稳定性。人工智能AI编程知识库 ———————————————— 版权声明:本文为CSDN博主「青花锁」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/s445320/article/details/134228181 
  • [技术干货] MyBatis-Plus如何使用@TableLogic注解实现逻辑删除功能
    @TableLogic是MyBatis-Plus框架提供的注解之一,用于标识逻辑删除功能。该注解可以应用于实体类的字段上,用于标识该字段是否参与逻辑删除。 使用@TableLogic注解时,需要满足以下条件:1.被注解的字段的类型必须是逻辑删除标识的类型,通常是整数数值类型。2.逻辑删除的取值范围要么是1和0,要么是某个特定的非0数值和0。3.被注解的字段必须在对应的数据库表中存在。当一个实体类的字段被标记为@TableLogic时,MyBatis-Plus框架会在进行该实体类的数据库操作时,自动处理逻辑删除的相关逻辑。例如,在执行删除操作时,实际上会将该字段的值更新为标识删除的值,而不是真正地从数据库中删除记录。 以下是一个示例代码,展示了如何在实体类中使用@TableLogic注解:import com.baomidou.mybatisplus.annotation.TableLogic;public class ZuoYang { @TableLogic(value = "0",delval = "1") private Integer isDel;}上述示例中,ZuoYang实体类中的isDel字段使用了@TableLogic注解,表示该字段参与逻辑删除。在进行数据库操作时,MyBatis-Plus框架会根据该注解的定义来处理逻辑删除的相关逻辑。转载自https://www.duidaima.com/Group/Topic/JAVA/18040
  • [技术干货] SpringBoot有几种获取Request对象的方法
    HttpServletRequest 简称 Request,它是一个 Servlet API 提供的对象,用于获取客户端发起的 HTTP 请求信息。例如:获取请求参数、获取请求头、获取 Session 会话信息、获取请求的 IP 地址等信息。那么问题来了,在 Spring Boot 中,获取 Request 对象的方法有哪些?常见的获取 Request 对象的方法有以下三种:通过请求参数中获取 Request 对象;通过 RequestContextHolder 获取 Request 对象;通过自动注入获取 Request 对象。具体实现如下。1.通过请求参数获取实现代码:@RequestMapping("/index") @ResponseBody public void index(HttpServletRequest request){   // do something }该方法实现的原理是 Controller 开始处理请求时,Spring 会将 Request 对象赋值到方法参数中,我们直接设置到参数中即可得到 Request 对象。2.通过 RequestContextHolder 获取在 Spring Boot 中,RequestContextHolder 是 Spring 框架提供的一个工具类,用于在多线程环境中存储和访问与当前线程相关的请求上下文信息。它主要用于将当前请求的信息存储在线程范围内,以便在不同的组件中共享和访问这些信息,特别是在没有直接传递参数的情况下。RequestContextHolder 的主要作用有以下几个:访问请求上下文信息: 在 Web 应用中,每个请求都会触发一个新的线程来处理。RequestContextHolder 允许你在任何地方获取当前请求的上下文信息,比如 HttpServletRequest 对象、会话信息等。跨层传递信息: 在多层架构中,比如控制器、服务层、数据访问层,你可能需要在这些层之间传递一些与请求相关的信息,但不想在每个方法中显式传递。通过 RequestContextHolder,你可以在一处设置请求信息,在其他地方获取并使用。线程安全的上下文共享: RequestContextHolder 使用线程局部变量来存储请求上下文信息,确保在多线程环境下每个线程访问的上下文信息都是独立的,避免了线程安全问题。因此我们可以使用 RequestContextHolde 获取 Request 对象,实现代码如下:@RequestMapping("/index") @ResponseBody public void index(){ ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(); HttpServletRequest request = servletRequestAttributes.getRequest(); // do something }3.通过自动注入获取HttpServletRequest 对象也可以通过自动注入,如属性注入的方式获取,如下代码所示:@Controller public class HomeController{ @Autowired private HttpServletRequest request; // 自动注入 request 对象 // do something }小结Request 对象是获取客户端 HTTP 请求的重要对象,也是 Spring Boot 的重要对象之一,获取此对象的常用方法有:通过请求参数获取、通过 RequestContextHolder 获取,以及通过注入获取。转载自https://www.cnblogs.com/vipstone/p/17629352.html
  • [技术干货] SpringBoot拦截器和动态代理有什么区别?
    在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,所以在很多时候,有人会认为拦截器的底层是通过动态代理实现的,所以本文就来盘点一下他们两的区别,以及拦截器的底层实现。1.拦截器拦截器(Interceptor)准确来说在 Spring MVC 中的一个很重要的组件,用于拦截 Controller 的请求。它的主要作用有以下几个:权限验证:验证用户是否登录、是否有权限访问某个接口。日志记录:记录请求信息的日志,如请求参数,响应信息等。性能监控:监控系统的运行性能,如慢查询接口等。通用行为:插入一些通用的行为,比如开发环境忽略某些请求。典型的使用场景是身份认证、授权检查、请求日志记录等。1.1 拦截器实现在 Spring Boot 中拦截器的实现分为两步:创建一个普通的拦截器,实现 HandlerInterceptor 接口,并重写接口中的相关方法。将上一步创建的拦截器加入到 Spring Boot 的配置文件中,并配置拦截规则。具体实现如下。① 实现自定义拦截器import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class TestInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器:执行 preHandle 方法。"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截器:执行 postHandle 方法。"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("拦截器:执行 afterCompletion 方法。"); } }其中:boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle):在请求方法执行前被调用,也就是调用目标方法之前被调用。比如我们在操作数据之前先要验证用户的登录信息,就可以在此方法中实现,如果验证成功则返回 true,继续执行数据操作业务;否则就返回 false,后续操作数据的业务就不会被执行了。void postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView):调用请求方法之后执行,但它会在 DispatcherServlet 进行渲染视图之前被执行。void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex):会在整个请求结束之后再执行,也就是在 DispatcherServlet 渲染了对应的视图之后再执行。② 配置拦截规则然后,我们再将上面的拦截器注入到项目配置文件中,并设置相应拦截规则,具体实现代码如下:import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class AppConfig implements WebMvcConfigurer { // 注入拦截器 @Autowired private TestInterceptor testInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(testInterceptor) // 添加拦截器 .addPathPatterns("/**"); // 拦截所有地址 .excludePathPatterns("/login"); // 放行接口 } }这样我们的拦截器就实现完了。1.2 拦截器实现原理Spring Boot 拦截器是基于 Java 的 Servlet 规范实现的,通过实现 HandlerInterceptor 接口来实现拦截器功能。在 Spring Boot 框架的执行流程中,拦截器被注册在 DispatcherServlet 的 doDispatch() 方法中,该方法是 Spring Boot 框架的核心方法,用于处理请求和响应。程序每次执行时都会调用 doDispatch() 方法时,并验证拦截器(链),之后再根据拦截器返回的结果,进行下一步的处理。如果返回的是 true,那么继续调用目标方法,反之则会直接返回验证失败给前端。doDispatch 源码实现如下:protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } // 调用预处理【重点】 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 执行 Controller 中的业务 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }从上述源码可以看出在开始执行 Controller 之前,会先调用 预处理方法 applyPreHandle,而 applyPreHandle 方法的实现源码如下:boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) { // 获取项目中使用的拦截器 HandlerInterceptor HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i); if (!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } return true; }从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法,这样就会咱们前面定义的拦截器对应上了此时用户登录权限的验证方法就会执行,这就是拦截器的执行过程。因此,可以得出结论,拦截器的实现主要是依赖 Servlet 或 Spring 执行流程来进行拦截和功能增强的。2.动态代理动态代理是一种设计模式,它是指在运行时提供代理对象,来扩展目标对象的功能。在 Spring 中的,动态代理的实现手段有以下两种:JDK 动态代理:通过反射机制生成代理对象,目标对象必须实现接口。CGLIB 动态代理:通过生成目标类的子类来实现代理,不要求目标对象实现接口。动态代理的主要作用包括:扩展目标对象的功能:如添加日志、验证参数等。控制目标对象的访问:如进行权限控制。延迟加载目标对象:在需要时才实例化目标对象。远程代理:将请求转发到远程的目标对象上。JDK 动态代理和 CGLIB 的区别详见:www.javacn.site/interview/spring/jdk_cglib.html3.拦截器 VS 动态代理因此,我们可以得出结论,拦截器和动态代理虽然都是用来实现功能增强的,但二者完全不同,他们的主要区别体现在以下几点:使用范围不同:拦截器通常用于 Spring MVC 中,主要用于拦截 Controller 请求。动态代理可以使用在 Bean 中,主要用于提供 bean 的代理对象,实现对 bean 方法的拦截。实现原理不同:拦截器是通过 HandlerInterceptor 接口来实现的,主要是通过 afterCompletion、postHandle、preHandle 这三个方法在请求前后进行拦截处理。动态代理主要有 JDK 动态代理和 CGLIB 动态代理,JDK 通过反射生成代理类;CGLIB 通过生成被代理类的子类来实现代理。加入时机不同:拦截器是在运行阶段动态加入的;动态代理是在编译期或运行期生成的代理类。使用难易程度不同:拦截器相对简单,通过实现接口即可使用。动态代理稍微复杂,需要了解动态代理的实现原理,然后通过相应的 api 实现。小结在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,但二者没有任何关联关系,它的区别主要体现在使用范围、实现原理、加入时机和使用的难易程度都是不同的。转载自https://www.cnblogs.com/vipstone/p/17704643.html
  • [技术干货] springboot项目中关于定时器的使用
    本周正在开开心心的撸码(因为终于不用处理数据了)突然接到了朋友的求助(当然我们都是小白)。间隔一周的周二周三执行定时器的corn,改怎么写。看到问题之后我的第一反应就是这特么还用问么,直接搜索引擎搞起啊 ,2分钟没找到答案算我输,哐哐的一顿搜索,直接我蒙了 。怎么验证就是没办法搞定,所以我就想从头开始学习下springboot里面的定时器 。(结果最后说)下面介绍一下再springboot项目中改怎么引入和使用定时器步骤1:引入相关依赖 首先,在你的Spring Boot项目的pom.xml文件中添加以下依赖项:<dependencies>     <!-- 其他依赖项 -->     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter</artifactId>     </dependency>     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-web</artifactId>     </dependency>     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-task</artifactId>     </dependency> </dependencies> 这些依赖将使你能够使用Spring Boot的定时器功能。步骤2:创建定时任务 接下来,创建一个Java类,用于定义你的定时任务。这个类需要使用Spring的@Component注解标记为一个组件,并使用@EnableScheduling注解启用定时任务的支持。 import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;  @Component @EnableScheduling public class MyScheduledTasks {      @Scheduled(fixedRate = 5000) // 每隔5秒执行一次任务     public void myTask() {         // 在这里编写你的任务逻辑         System.out.println("定时任务执行中...");     } } 在上面的例子中,我们创建了一个名为MyScheduledTasks的类,并使用@Scheduled注解将myTask方法标记为一个定时任务。通过设置fixedRate属性为5000,我们让任务每隔5秒执行一次。步骤3:运行Spring Boot应用 现在,你可以运行你的Spring Boot应用程序,并观察控制台输出。你将看到定时任务每隔5秒执行一次,并输出"定时任务执行中..."。你可以根据自己的需求,根据不同的时间表达式来配置任务的执行时间。Spring的定时器支持常见的cron表达式,可以实现更复杂的任务调度。例如,如果你想每天的特定时间点执行任务,你可以修改定时任务的注解为:@Scheduled(cron = "0 0 18 * * ?") // 每天下午6点执行任务 public void myTask() {     // 你的任务逻辑 } 这是根据搜索引擎搜索的东西,在整理的。但是但是没看到最核心的cron啊 。 而且还是没看到怎么写cron。下来就有去整理cron的知识点cron表达式是一种在固定时间间隔执行任务的时间表达式。它由6个字段组成,分别表示秒、分钟、小时、日期、月份和星期几。每个字段可以使用特定的值、通配符(*)、范围(-)、间隔(/)、逗号(,)和?来定义时间规则。下面是各个字段的取值范围和含义:秒(0-59):表示每分钟的哪一秒执行任务。分钟(0-59):表示每小时的哪一分钟执行任务。小时(0-23):表示每天的哪一个小时执行任务。日期(1-31):表示每月的哪一天执行任务。月份(1-12):表示每年的哪一个月执行任务。星期几(0-7或SUN-SAT,其中0和7都表示星期日):表示每周的哪一天执行任务。除了以上的具体取值范围外,还可以使用特殊字符来表示某些情况:*(通配符):表示匹配所有可能的值。例如,*在分钟字段中表示每分钟执行任务。?(问号):在日期和星期几字段中,表示不关心具体值。它与通配符*的作用类似,但是不能同时在一个字段中使用。例如,如果想让任务每月的第5天执行,不管这一天是星期几,可以将日期字段设置为5,星期几字段设置为?。-(范围):表示一个连续的范围。例如,1-3在小时字段中表示1点到3点之间执行任务。,(逗号):用于列出多个值。例如,1,3,5在星期几字段中表示星期一、星期三和星期五执行任务。/(间隔):用于指定一个值的增量。例如,0/5在秒字段中表示从0秒开始,每隔5秒执行任务。然后 我写出了如下cron0 0 10 ? * TUE,WED/7 (执行结果不对)0 0 10 ? * TUE,WED 1/2 (无法执行)0 0 10 ? * (TUE,WED)/7(无法执行)等等 ,直接从尝试到放弃 ,还是没有写出来 ,顺便求一个答案 。然后我就另辟蹊径的搞了下 ,定时器里面封装了方法 ,然后每次方法执行的时候记录日志 。 下次定时器执行前,看下日志时间,如果上一周执行了,这周不执行。这个还在尝试阶段。但是感觉可行,不说了撸码去了 。
  • [技术干货] Spring boot全局统一捕获错误
    前言众所周知,在程序里难以避免会出现错误。在我们写高级语言的时候,遇到错误经常就是try...catch...然而,随着代码量的增大,尤其是在web中,对每个接口写一遍try...catch...会导致业务无关的代码冗余度大大增多,增加我们后期维护的成本统一捕获错误的时机在 Spring Boot 项目中,统一捕获错误通常在以下几种情况下使用:项目模块较多,各个模块有自己的异常处理逻辑:在这种情况下,可以使用统一捕获错误来简化处理,避免在每个模块中都定义一套相同的异常处理逻辑。通过全局统一的异常处理,可以降低代码重复,提高代码复用性。需要自定义错误页面:当项目需要展示自定义的错误页面时,可以通过统一捕获错误来实现。这样可以确保在不同模块出现异常时,都能够按照统一的风格展示错误信息,提升用户体验。需要统一处理业务逻辑异常:在某些情况下,不同模块的业务逻辑可能出现相同的异常,这时可以通过统一捕获错误来处理这些异常。这样可以确保在不同模块中,对这些业务的处理方式是一致的。需要对异常进行日志记录或统计分析:通过统一捕获错误,可以方便地对异常进行日志记录、统计和分析。这对于排查问题和优化系统性能非常有帮助。需要限制异常信息的泄露:在某些安全性要求较高的项目中,可能需要限制异常信息的泄露。通过统一捕获错误,可以自定义异常处理逻辑,从而避免敏感信息泄露。在需要全局统一处理异常情况时,可以使用 Spring Boot 的统一捕获错误功能。这有助于简化代码、提高复用性、提升系统性能和安全性。实现方式在 SpringBoot 中,全局统一捕获错误可以通过以下几种方式实现:继承 BasicErrorController: 创建一个类,继承自 BasicErrorController,并重写相应的处理方法。在这个类中,可以自定义错误页面的跳转以及错误信息的展示。import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.Map; @ControllerAdvice public class GlobalErrorController extends BasicErrorController { @Override public Map<String, Object> getErrorAttributes(Map<String, Object> errorAttributes, boolean includeStackTrace) { // 自定义错误属性 errorAttributes.put("customKey", "customValue"); return errorAttributes; } @Override @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody public Map<String, Object> handleAllException(Exception e) { // 自定义错误页面内容 Map<String, Object> errorAttributes = super.handleAllException(e); errorAttributes.put("errorMessage", "这是自定义的错误信息"); return errorAttributes; } }使用@ControllerAdvice 和@ExceptionHandler: 创建一个统一的异常处理类,使用@ControllerAdvice 注解定义,然后在类中定义@ExceptionHandler 方法,用于处理所有异常。import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.Map; @ControllerAdvice public class GlobalErrorController { @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody public Map<String, Object> handleAllException(Exception e) { // 自定义错误页面内容 Map<String, Object> errorAttributes = new HashMap<>(); errorAttributes.put("errorMessage", "这是自定义的错误信息"); return errorAttributes; } }修改默认错误页面: 在src/main/resources/templates目录下,创建或者修改error.html文件,为自己的错误页面添加自定义内容。以上三种方式可以根据实际需求进行选择,灵活配置,实现 SpringBoot 全局统一捕获错误。
  • [技术干货] Minio入门系列【7】Spring Boot集成Minio
    1 前言 之前介绍了如何使用Minio提供的JAVA SDK进行上传和下载文件,在此基础上,我们可以使用spring boot集成Minio JAVA SDK,添加自动配置、装配、客户端管理等功能,简化开发 2 Spring Boot集成Minio 2.1 环境搭建 首先我们搭建一个spring boot基础工程,引入以下依赖      <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>             <scope>test</scope>         </dependency>         <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>             <optional>true</optional>         </dependency>         <!-- https://mvnrepository.com/artifact/io.minio/minio -->         <dependency>             <groupId>io.minio</groupId>             <artifactId>minio</artifactId>             <version>8.3.1</version>         </dependency>         <dependency>             <groupId>me.tongfei</groupId>             <artifactId>progressbar</artifactId>             <version>0.9.2</version>         </dependency>         <dependency>             <groupId>com.squareup.okhttp3</groupId>             <artifactId>okhttp</artifactId>             <version>4.9.2</version>         </dependency>         <dependency>             <groupId>cn.hutool</groupId>             <artifactId>hutool-all</artifactId>             <version>5.7.13</version>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-configuration-processor</artifactId>         </dependency>         <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->         <dependency>             <groupId>commons-fileupload</groupId>             <artifactId>commons-fileupload</artifactId>             <version>1.4</version>         </dependency> 2.2 操作模板类 在spring中,提供了很多集成第三方的操作模板类,比如RedisTemplate、RestTemplate等等,我们可以参照这些,提供一个minio SDK的集成模板类,这样在使用API时就比较方便了。  首先需要创建一个OSS文件对象,上传文件成功后,我们需要将文件信息返回给前端  import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Configuration;  /**  * @author wuKeFan  * @date 2020/9/10  */ @RefreshScope @Configuration public class OssConfig {      @Value("${biz.oss.endpoint}")     private String endpoint;     @Value("${biz.oss.bucket}")     private String bucket;     @Value("${biz.oss.access-key-id}")     private String accessKeyId;     @Value("${biz.oss.access-key-secret}")     private String accessKeySecret;     @Value("${biz.oss.type}")     private Integer ossType;      /**      * 最大上传长度单位m,默认20M      */     @Value("${biz.oss.maxLength:20}")     private Integer maxLength;      public String getAccessId() {         return accessKeyId;     }      public String getBucket() {         return bucket;     }      public String getEndpoint() {         return endpoint;     }      public Integer getMaxLength() {         return maxLength;     }      public void setEndpoint(String endpoint) {         this.endpoint = endpoint;     }      public void setBucket(String bucket) {         this.bucket = bucket;     }      public String getAccessKeyId() {         return accessKeyId;     }      public void setAccessKeyId(String accessKeyId) {         this.accessKeyId = accessKeyId;     }      public String getAccessKeySecret() {         return accessKeySecret;     }      public void setAccessKeySecret(String accessKeySecret) {         this.accessKeySecret = accessKeySecret;     }      public void setMaxLength(Integer maxLength) {         this.maxLength = maxLength;     }      public Integer getOssType() {         return ossType;     }      public void setOssType(Integer ossType) {         this.ossType = ossType;     } }  import com.wkf.workrecord.tools.minio.config.OssConfig; import io.minio.GetPresignedObjectUrlArgs; import io.minio.MinioClient; import io.minio.PutObjectArgs; import io.minio.RemoveObjectArgs; import io.minio.errors.*; import io.minio.http.Method; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Objects; import java.util.concurrent.TimeUnit;  /**  * @author wuKeFan  * @date 2023-09-21 10:59:24  */ @Slf4j @Component public class MinioTemplate implements InitializingBean {      @Resource     private OssConfig ossConfig;      private MinioClient minioClient;      @Override     public void afterPropertiesSet() {         this.minioClient =  MinioClient.builder().endpoint(ossConfig.getEndpoint())                 .credentials(ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret())                 .build();     }      /**      * 删除文件      *      * @param objectName 文件名称      * @throws Exception 参考https://docs.minio.io/cn/java-client-api-reference.html#removeObject      */     public void removeObject(String objectName) throws Exception {         minioClient.removeObject(RemoveObjectArgs.builder().object(objectName).bucket(ossConfig.getBucket()).build());     }      /**      * 获得上传的URL      * @param objectName 文件路径(对象名)      */     public String getPresignedObjectUrl(String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {         return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(ossConfig.getBucket()).object(objectName).expiry(10, TimeUnit.MINUTES).method(Method.PUT).build());     }      public void uploadMinio(byte[] bytes, String filePath, String contentType) throws IOException {         InputStream input = null;         try {             input = new ByteArrayInputStream(bytes);             minioClient.putObject(                     PutObjectArgs.builder()                             .bucket(ossConfig.getBucket())                             .contentType(contentType)                             .stream(input, input.available(), -1)                             .object(filePath)                             .build()             );         } catch (Exception e) {             log.error("minio上传文件错误:", e);         } finally {             if (Objects.nonNull(input)) {                 input.close();             }         }     } } 2.3 自动配置 在了解了BAT公司提供的对象存储OSS后,发现其API接口标准都是差不多的,从扩展性的角度出发,我们当前服务应当支持各种类型的OSS,比如阿里等。所以这里先定义一个枚举类,提供除了Minio还适配其他厂商的支持。  /**  * 文件上传存储类型  * @author wuKeFan  * @date 2021/01/20  */ public enum OssType {      /**      * 阿里云oss      */     ALI(0),      /**      * minio      */     MINIO(1), ;      private final Integer value;      public Integer value() {         return value;     }      OssType(Integer value) {         this.value = value;     }  } 3 测试 首先,在yml中添加Minio的配置:  biz:   oss:     # resources-url是带有bucket的     resources-url: http://127.0.0.1:9000/mall4cloud     # 文件上传类型 0.阿里云 1.minio     type: 1     endpoint: http://127.0.0.1:9000     bucket: mall4cloud     access-key-id: username     access-key-secret: password 然后创建一个访问接口,直接调用minioTemplate进行文件操作,这样就十分便利,达到了简化开发的目的  import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.IdUtil; import com.mall4j.cloud.biz.config.MinioTemplate; import com.mall4j.cloud.biz.config.OssConfig; import com.mall4j.cloud.biz.constant.OssType; import com.mall4j.cloud.biz.vo.OssVO; import com.mall4j.cloud.common.response.ServerResponseEntity; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile;  import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects;  /**  * @author wuKeFan  * @date 2020/9/10  */ @RequestMapping(value = "/oss") @RestController @Tag(name = "文件管理") public class OssController {      /**      * 上传的文件夹(根据时间确定)      */     public static final String NORM_DAY_PATTERN = "yyyy/MM/dd";      @Autowired     private OssConfig ossConfig;     @Autowired     private MinioTemplate minioTemplate;      @GetMapping(value = "/info")     @Operation(summary = "token" , description = "获取文件上传需要的token")     @Parameter(name = "fileNum", description = "需要获取token的文件数量")     public ServerResponseEntity<OssVO> info(@RequestParam("fileNum") Integer fileNum) {         OssVO ossVO = new OssVO();         // minio文件上传         if (Objects.equals(ossConfig.getOssType(), OssType.MINIO.value())) {             fillMinIoInfo(ossVO, fileNum);         }         return ServerResponseEntity.success(ossVO);     }      private void fillMinIoInfo(OssVO ossVo, Integer fileNum) {         List<OssVO> ossVOList = new ArrayList<>();         for (int i = 0; i<fileNum; i++) {             OssVO oss = loadOssVO(new OssVO());             String actionUrl = minioTemplate.getPresignedObjectUrl(oss.getDir() + oss.getFileName());             oss.setActionUrl(actionUrl);             ossVOList.add(oss);         }         ossVo.setOssList(ossVOList);     }      private OssVO loadOssVO(OssVO ossVo) {         String dir = DateUtil.format(new Date(), NORM_DAY_PATTERN)+ "/";         String fileName = IdUtil.simpleUUID();         ossVo.setDir(dir);         ossVo.setFileName(fileName);         return ossVo;     }      @PostMapping("/upload_minio")     @Operation(summary = "文件上传接口" , description = "上传文件,返回文件路径与域名")     public ServerResponseEntity<OssVO> uploadFile(@RequestParam("file") MultipartFile file) throws IOException {         if (file.isEmpty()) {             return ServerResponseEntity.success();         }         OssVO oss = loadOssVO(new OssVO());         minioTemplate.uploadMinio(file.getBytes(), oss.getDir() + oss.getFileName(), file.getContentType());         return ServerResponseEntity.success(oss);     }  }  ———————————————— 版权声明:本文为CSDN博主「吴名氏.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_37284798/article/details/133084693 
  • [技术干货] 一个依赖解决 Spring Boot 反爬虫,防止接口盗刷【转】
    kk-anti-reptile 是适用于基于spring-boot开发的分布式系统的反爬虫组件 系统要求 基于 spring-boot 开发 (spring-boot1.x, spring-boot2.x 均可) 需要使用 redis 工作流程 kk-anti-reptile 使用基于 Servlet 规范的的 Filter 对请求进行过滤,在其内部通过 spring-boot 的扩展点机制,实例化一个 Filter,并注入到 Spring 容器 FilterRegistrationBean 中,通过 Spring 注入到 Servlet 容器中,从而实现对请求的过滤 在 kk-anti-reptile 的过滤 Filter 内部,又通过责任链模式,将各种不同的过滤规则织入,并提供抽象接口,可由调用方进行规则扩展 Filter 调用则链进行请求过滤,如过滤不通过,则拦截请求,返回状态码 509,并输出验证码输入页面,输出验证码正确后,调用过滤规则链对规则进行重置 目前规则链中有如下两个规则 ip-rule ip-rule 通过时间窗口统计当前时间窗口内请求数,小于规定的最大请求数则可通过,否则不通过。时间窗口、最大请求数、ip 白名单等均可配置 ua-rule ua-rule 通过判断请求携带的 User-Agent,得到操作系统、设备信息、浏览器信息等,可配置各种维度对请求进行过滤 命中规则后 命中爬虫和防盗刷规则后,会阻断请求,并生成接除阻断的验证码,验证码有多种组合方式,如果客户端可以正确输入验证码,则可以继续访问 验证码有中文、英文字母 + 数字、简单算术三种形式,每种形式又有静态图片和 GIF 动图两种图片格式,即目前共有如下六种,所有类型的验证码会随机出现,目前技术手段识别难度极高,可有效阻止防止爬虫大规模爬取数据  接入使用 后端接入非常简单,只需要引用 kk-anti-reptile 的 maven 依赖,并配置启用 kk-anti-reptile 即可 加入 maven 依赖 <dependency>     <groupId>cn.keking.project</groupId>     <artifactId>kk-anti-reptile</artifactId>     <version>1.0.0-SNAPSHOT</version> </dependency> 配置启用 kk-anti-reptile anti.reptile.manager.enabled=true 前端需要在统一发送请求的 ajax 处加入拦截,拦截到请求返回状态码 509 后弹出一个新页面,并把响应内容转出到页面中,然后向页面中传入后端接口 baseUrl 参数即可,以使用 axios 请求为例:  import axios from 'axios';import {baseUrl} from './config'; axios.interceptors.response.use( data => { return data; }, error => { if (error.response.status === 509) { let html = error.response.data; let verifyWindow = window.open("","_blank","height=400,width=560"); verifyWindow.document.write(html); verifyWindow.document.getElementById("baseUrl").value = baseUrl; } }); export default axios;注意 apollo-client 需启用 bootstrap 使用 apollo 配置中心的用户,由于组件内部用到 @ConditionalOnProperty,要在 application.properties/bootstrap.properties 中加入如下样例配置,(apollo-client 需要 0.10.0 及以上版本)详见 apollo bootstrap 说明 apollo.bootstrap.enabled = true 需要有 Redisson 连接 如果项目中有用到 Redisson,kk-anti-reptile 会自动获取 RedissonClient 实例对象;如果没用到,需要在配置文件加入如下 Redisson 连接相关配置 spring.redisson.address=redis://192.168.1.204:6379 spring.redisson.password=xxx 配置一览表 在 spring-boot 中,所有配置在配置文件都会有自动提示和说明,如下图  所有配置都以 anti.reptile.manager 为前缀,如下为所有配置项及说明 ———————————————— 版权声明:本文为CSDN博主「吴名氏.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_37284798/article/details/125888492 
  • [技术干货] springboot 调用外部接口的21种方式
    使用Spring Boot调用外部接口时,可以使用多种方式。以下是常用的几种方式:1. 使用RestTemplate类:RestTemplate是Spring提供的用于简化HTTP请求的客户端,可以发送GET、POST等HTTP请求,并处理响应结果。2. 使用WebClient类:WebClient是Spring WebFlux中的响应式客户端,可以使用类似的方式发送HTTP请求并处理响应。3. 使用Feign客户端:Feign是一个声明式的HTTP客户端,可以通过定义接口的方式来使用外部API,Spring Cloud中广泛使用。4. 使用Apache HttpClient库:Apache HttpClient是一个功能强大的HTTP客户端库,可以直接使用其提供的类发送HTTP请求。5. 使用URLConnection类:Java标准库提供的URLConnection类可以用于发送HTTP请求,可以通过手动编写代码来实现。6. 使用OkHttp库:OkHttp是一个高性能的HTTP客户端库,可以用于发送HTTP请求,并支持异步请求和回调。7. 使用Retrofit库:Retrofit是一个简化HTTP请求的库,可以通过定义接口的方式来使用外部API,内部使用OkHttp进行请求。8. 使用AsyncRestTemplate类:AsyncRestTemplate是一个异步的RestTemplate实现,可以通过回调方式处理异步请求的结果。9. 使用Java的HttpClient类(从 Java 11 开始引入):HttpClient是Java标准库提供的一个新的HTTP客户端,可以用于发送HTTP请求。10. 使用第三方的HTTP客户端库:还可以使用其他成熟的第三方HTTP客户端库,如OkHttp、Apache HttpClient等,根据需求选择合适的库进行调用。11. 使用Java的HttpURLConnection类:HttpURLConnection是Java标准库提供的一个HTTP客户端类,可以手动创建连接并发送HTTP请求。12. 使用Feign + Ribbon:Feign与Ribbon结合使用可以实现负载均衡的外部接口调用。Feign用于定义接口并进行声明式的HTTP调用,Ribbon用于在多个实例之间进行负载均衡。13. 使用AsyncHttpClient库:AsyncHttpClient是一个轻量级、高效的异步HTTP客户端库,可以用于发送异步请求和处理响应。14. 使用WebSocket进行双向通信:如果需要进行双向通信,可以使用WebSocket协议与外部接口进行通信。Spring Boot提供了对WebSocket的支持,可以简化WebSocket的使用。15. 使用Akka进行并发和消息传递:如果需要处理高并发的场景,可以使用Akka,它是一个强大的并发框架,支持消息传递和Actor模型。16. 使用MQTT协议进行消息传递:如果需要使用发布-订阅方式进行消息传递,可以使用MQTT协议与外部接口进行通信。Spring Boot提供了对MQTT的支持。17. 使用Apache HttpClient的Fluent API:除了直接使用Apache HttpClient类外,还可以使用其提供的Fluent API,它提供了更简洁、易读的方法链式调用。18. 使用Spring WebClient与WebFlux:Spring 5引入了WebFlux,它是一个响应式编程框架,可以使用其提供的WebClient类进行异步 HTTP 调用。19. 使用Spring Integration进行集成:Spring Integration是一个用于企业集成的框架,可以通过定义消息流程来实现与外部系统的集成。20. 使用REST Assured进行接口测试:如果目的是对外部接口进行测试,REST Assured是一个流行的Java库,可以方便地对REST API进行验证和测试。21. 使用Spring Cloud Gateway进行接口转发:Spring Cloud Gateway是一个基于Spring Boot的API网关,它可以将外部接口的请求转发到后端的多个服务中。转自:https://mp.weixin.qq.com/s/pp4-kAMgR4zq3-ByuH0ZOg
  • [专题汇总] 技术干货30篇,一次看过瘾。速进。
     大家好7月给大家带来codeArts板块的技术干货30篇合集,免去爬楼烦恼,希望可以帮到大家。  1.Nginx反向代理后,web服务器获取真实访问IP的方法【转】 https://bbs.huaweicloud.com/forum/thread-0212126166776222008-1-1.html  2.Eclipse中maven项目报错: org.springframework.web.filter.CharacterEncodingFilter https://bbs.huaweicloud.com/forum/thread-0212126166611645007-1-1.html  3.如何将公司外网数据库部署到客户内网服务器中,以MySQL为例【转】     https://bbs.huaweicloud.com/forum/thread-0218126166402927009-1-1.html  4.Mysql分组查询每组最新的一条数据(三种实现方法)【转】 https://bbs.huaweicloud.com/forum/thread-0212126166137614006-1-1.html  5.服务端回调报错。Cookie 的 domain 传入非法字符 【转】 https://bbs.huaweicloud.com/forum/thread-0284126165918635010-1-1.html  6.Java获取HttpServletRequest request中所有参数的方法【转】 https://bbs.huaweicloud.com/forum/thread-0249126165722109058-1-1.html  7.Java下载文件的几种方式【转】 https://bbs.huaweicloud.com/forum/thread-0284126165496966009-1-1.html  8.Python工程实践之np.loadtxt()读取数据 https://bbs.huaweicloud.com/forum/thread-0284126154810611008-1-1.html  9.python中的extend功能及用法【转】 https://bbs.huaweicloud.com/forum/thread-0218126154771208007-1-1.html  10.Python isalnum()函数的具体使用【转】 https://bbs.huaweicloud.com/forum/thread-0227126154735209007-1-1.html  11.Python endswith()函数的具体使用【转】 https://bbs.huaweicloud.com/forum/thread-0283126154645130002-1-1.html  12.Python Requests使用Cookie的几种方式详解【转】 https://bbs.huaweicloud.com/forum/thread-0205126154329369004-1-1.html  13.Python大批量写入数据(百万级别)的方法【转】 https://bbs.huaweicloud.com/forum/thread-0227126154192353006-1-1.html  14.python自动化神器pyautogui使用步骤【转】 https://bbs.huaweicloud.com/forum/thread-0249126153906646056-1-1.html  15.Python 获取图片GPS等信息锁定图片拍摄地点、拍摄时间(实例代码)【转】 https://bbs.huaweicloud.com/forum/thread-0212126153709428005-1-1.html  16.python绘制ROC曲线的示例代码【转】 https://bbs.huaweicloud.com/forum/thread-0284126153513019006-1-1.html  17.Python中map函数的技巧分享【转】 https://bbs.huaweicloud.com/forum/thread-0227126153401950004-1-1.html  18.Python列表pop()函数使用实例详解【转】 https://bbs.huaweicloud.com/forum/thread-0222126153273615007-1-1.html  19.使用Pandas计算系统客户名称的相似度【转】 https://bbs.huaweicloud.com/forum/thread-0283126152370390001-1-1.html  20.Python字典get()函数使用详解【转】 https://bbs.huaweicloud.com/forum/thread-0212126152189966004-1-1.html  21.Python集合add()函数使用详解【转】 https://bbs.huaweicloud.com/forum/thread-0218126152083399006-1-1.html  22.python中Scikit-learn库的高级特性和实践分享【转】 https://bbs.huaweicloud.com/forum/thread-0218126152004011005-1-1.html  23.MapUtils工具类【转】 https://bbs.huaweicloud.com/forum/thread-0222125918123973044-1-1.html  24.HTTP响应的状态码415解决【转】 https://bbs.huaweicloud.com/forum/thread-0283125917982618029-1-1.html  25.js保留两位小数方法总结【转】 https://bbs.huaweicloud.com/forum/thread-0222125917923319043-1-1.html  26.聊聊springboot项目引用第三平台私有jar踩到的坑【转】 https://bbs.huaweicloud.com/forum/thread-0284125917178124048-1-1.html  27.如何利用mysql5.7提供的虚拟列来提高查询效率【转】 https://bbs.huaweicloud.com/forum/thread-0284125916421713047-1-1.html  28.怎样修改检查约束条件 https://bbs.huaweicloud.com/forum/thread-0284125913342309042-1-1.html  29.【MySQL新手入门系列五】:MySQL的高级特性简介及MySQL的安全简介-转载 https://bbs.huaweicloud.com/forum/thread-0284125377030598012-1-1.html  30.【SQL应知应会】行列转换(二)• MySQL版-转载 https://bbs.huaweicloud.com/forum/thread-0249125376948578009-1-1.html 
  • [技术干货] SpringBoot实现热部署详解
    前言请注意,热部署只适用于开发环境,并且对于某些修改,可能需要重启应用程序才能生效。因此,在生产环境中不建议使用热部署。Spring Boot热部署是一种开发时极为有用的功能,它能够让开发人员在代码修改后无需手动重启应用程序就能立即看到变化的效果。以下是使用Spring Boot热部署的几个主要原因:1.提高开发效率热部署使开发人员能够更快地验证和测试他们的代码更改。无需手动重启应用程序,每次修改后只需保存文件即可立即查看结果。这大大缩短了开发和调试周期,提高了开发效率。2.实时调试通过热部署,开发人员可以在应用程序运行时动态调试代码。他们可以添加断点,检查变量的值,以及在应用程序运行期间观察代码的行为。这对于快速定位和解决问题非常有帮助。3.编码体验使用热部署可以使开发人员保持在一个持续的编码状态,无需中断来手动重启应用程序。他们可以实时查看他们的代码修改的效果,使得编码过程更加流畅和连贯。4.减少重复操作热部署避免了频繁的应用程序重启,减少了无意义的等待时间。开发人员只需保存文件,系统就会自动重新加载相关的类和资源,使得每次代码修改都会立即生效。这有助于减少工作流程中重复的操作,提高工作效率。原理Spring Boot实现热部署的原理主要是利用了Java虚拟机(JVM)的类加载机制和文件监控机制。下面是热部署的基本原理:1.类加载机制Java虚拟机使用类加载器(ClassLoader)来加载和链接类。当应用程序运行时,类加载器会根据需要动态加载类并创建类的对象。Spring Boot利用了Java虚拟机的类加载机制,通过重新加载修改后的类实现热部署。2.文件监控机制Spring Boot将应用程序和开发环境中的文件系统进行关联,并监听所关联的文件夹中的文件更改。当检测到文件更改时,Spring Boot会重新加载与更改文件相关的类。通过以下几种方式实现项目的热部署:1.spring-boot-devtools这是SpringBoot提供的热部署工具,添加依赖:<dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-devtools</artifactId>   <optional>true</optional> </dependency>实现资源修改后的自动重启等功能。启动应用程序时,DevTools会自动配置热部署,并在保存文件时重新启动应用程序。DevTools还提供了其他功能,如自动重新启动、自动刷新页面等,以提高开发效率。2.使用Spring LoadedSpring LoadedSpring的热部署程序,实现修改类后的自动重载。实现原理是使用自定义ClassLoader,可以实现代码热替换。具体实现如下:2.1 在pom.xml文件中添加Spring Loaded的依赖:<dependency>   <groupId>org.springframework</groupId>   <artifactId>springloaded</artifactId>   <version>1.2.8.RELEASE</version> </dependency>2.2 在IDE或编译器中配置项目的自动构建功能。确保在保存文件时自动重新构建项目。   2.3 启动应用程序时,添加以下JVM参数:-javaagent:/path/to/springloaded.jar -noverify其中/path/to/springloaded.jar是Spring Loaded JAR文件的路径,根据你的实际情况进行相应的修改。2.4 启动应用程序并进行开发。每当保存文件时,Spring Loaded会自动检测到更改并重新加载修改后的类,使得你的更改能够立即生效。需要注意的是,Spring Loaded是一个第三方库,使用它可能会有一些限制和不稳定性。Spring官方已经不再维护Spring Loaded3.JRebel插件JRebel收费的热部署软件,需要添加JRebel插件,可以实现代码热部署。效果非常好,但是需要付费使用。4.Spring Boot Maven插件该插件可以监控代码变动,自动重启应用。<plugin>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-maven-plugin</artifactId>   <configuration>       <fork>true</fork>   </configuration> </plugin>5.在IntelliJ IDEA中设置Spring Boot项目的热部署5.1 在IntelliJ IDEA中打开你的Spring Boot项目。5.2 确保已经安装了Spring Boot DevTools插件。可以通过 File -> Settings -> Plugins 进入插件管理页面,搜索并安装Spring Boot DevTools插件。5.3 在IntelliJ IDEA的顶部菜单栏中,选择 Run -> Edit Configurations。5.4 在弹出的Run/Debug Configurations对话框中,选择左侧的 Spring Boot。5.5 在右侧的 Spring Boot 配置窗口中,将 On-frame deactivation 和 On-update action 选项设置为 Update classes and resources。On-frame deactivation:当你切换到其他窗口时,配置的更新策略。On-update action:当检测到文件更改时,配置的更新策略。这样设置后,当你切换到其他窗口时,应用程序会在后台重新启动,同时当检测到文件更改时,应用程序会更新相关的类和资源。5.6 点击 Apply 或 OK 按钮保存配置。5.7 点击IntelliJ IDEA的顶部菜单栏中的 Build -> Build Project 来构建你的项目。5.8 在构建完成后,点击工具栏上的绿色箭头图标或使用快捷键 Shift + F10 来运行你的Spring Boot应用程序。现在,当你修改代码并保存文件时,IntelliJ IDEA会自动将更改的类和资源重新加载到运行的应用程序中,实现热部署。转自:https://mp.weixin.qq.com/s/UwRRgui7l2MD1pITUowZsw
  • [技术干货] SpringBoot 实现 elasticsearch 查询操作(RestHighLevelClient 的案例实战)-转载
     1. 环境准备 案例用到的索引库结构  PUT /hotel {   "mappings": {     "properties": {       "id": {         "type": "keyword"       },       "name":{         "type": "text",         "analyzer": "ik_max_word",         "copy_to": "all"       },       "address":{         "type": "keyword",         "index": false       },       "price":{         "type": "integer"       },       "score":{         "type": "integer"       },       "brand":{         "type": "keyword",         "copy_to": "all"       },       "city":{         "type": "keyword",         "copy_to": "all"       },       "starName":{         "type": "keyword"       },       "business":{         "type": "keyword"       },       "location":{         "type": "geo_point"       },       "pic":{         "type": "keyword",         "index": false       },       "all":{         "type": "text",         "analyzer": "ik_max_word"       }     }   } } 1. 查询全部 @GetMapping("/searchAll") public List<HotelDoc> searchAll() throws Exception {     //1.创建请求语义对象     SearchRequest searchRequest = new SearchRequest("索引名称");     // QueryBuilders: 构建查询类型     searchRequest.source().query(QueryBuilders.matchAllQuery());      SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);      return handleResponse(searchResponse); } 2. 根据 name 查询 match 分词查询  @GetMapping("/searchByName/{name}") public List<HotelDoc> searchByName(@PathVariable("name") String name)      throws Exception {     //1.创建请求语义对象     SearchRequest searchRequest = new SearchRequest("索引名称");     // QueryBuilders: 构建查询类型     searchRequest.source().query(QueryBuilders.matchQuery("name", name));      SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);      return handleResponse(searchResponse); } 3. 根据 name 和 品牌查询 multiMatch 分词查询 @GetMapping("/searchByNameAndBrand/{name}") public List<HotelDoc> searchByNameAndBrand(@PathVariable("name") String name) throws Exception {     //1.创建请求语义对象     SearchRequest searchRequest = new SearchRequest("索引名称");     // QueryBuilders: 构建查询类型     searchRequest.source().query(QueryBuilders.multiMatchQuery(name,"name","brand"));     SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);      return handleResponse(searchResponse); } 4. 根据 brand 查询 match 分词查询 @GetMapping("/searchByBrand/{name}") public List<HotelDoc> searchByBrand(@PathVariable("name") String name) throws Exception {     //1.创建请求语义对象     SearchRequest searchRequest = new SearchRequest("索引名称");     // QueryBuilders: 构建查询类型     searchRequest.source().query(QueryBuilders.matchQuery("brand", name));     SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);      return handleResponse(searchResponse); 5. 按照价格 范围查询  @GetMapping("/searchByPrice/{low}/{high}") public List<HotelDoc> searchByPrice(@PathVariable("low") String low, @PathVariable("high") String high) throws Exception {     //1.创建请求语义对象     SearchRequest searchRequest = new SearchRequest("索引名称");     // QueryBuilders: 构建查询类型     searchRequest.source().query(QueryBuilders.rangeQuery("price").gte(low).lte(high));     SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);      return handleResponse(searchResponse); } 6. 精确查询 @GetMapping("/termQueryCity/{city}") public List<HotelDoc> termQueryCity(@PathVariable("city") String city) throws Exception {     //1.创建请求语义对象     SearchRequest searchRequest = new SearchRequest("索引名称");     // QueryBuilders: 构建查询类型     SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();     //searchSourceBuilder.query(QueryBuilders.termQuery("city", city)); 这行有点小问题     //    https://zhuanlan.zhihu.com/p/270426807 参考     searchSourceBuilder.query(QueryBuilders.termQuery("city.keyword", city));      searchRequest.source(searchSourceBuilder);     SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);      return handleResponse(searchResponse); }  7. boolQuery @GetMapping("/testBool") public List<HotelDoc> testBool() throws Exception {     // 1.准备Request     SearchRequest request = new SearchRequest("索引名称");     // 2.准备DSL     // 2.1.准备BooleanQuery     BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();     // 2.2.添加term     boolQuery.must(QueryBuilders.termQuery("city.keyword", "杭州"));     // 2.3.添加range     boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));      request.source().query(boolQuery);     // 3.发送请求     SearchResponse response = client.search(request, RequestOptions.DEFAULT);     // 4.解析响应     return handleResponse(response); }  8. 分页  @GetMapping("/testPageAndSort/{currentPage}/{pageSize}") public List<HotelDoc> testPageAndSort(@PathVariable("currentPage") Integer currentPage, @PathVariable("pageSize") Integer pageSize) throws Exception {     // 页码,每页大小      // 1.准备Request     SearchRequest request = new SearchRequest("索引名称");     // 2.准备DSL     // 2.1.query     request.source().query(QueryBuilders.matchAllQuery());     // 2.2.排序 sort     request.source().sort("price", SortOrder.ASC);     // 2.3.分页 from、size     request.source().from((currentPage - 1) * pageSize).size(pageSize);     // 3.发送请求     SearchResponse response = client.search(request, RequestOptions.DEFAULT);     // 4.解析响应     return handleResponse(response); }  9. 高亮查询  @GetMapping("/testHighlight/{name}") void testHighlight(@PathVariable("name") String name) throws Exception {     // 1.准备Request     SearchRequest request = new SearchRequest("索引名称");     // 2.准备DSL     // 2.1.query     request.source().query(QueryBuilders.matchQuery("name", name));     // 2.2.高亮     request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));     // 3.发送请求     SearchResponse response = client.search(request, RequestOptions.DEFAULT);     // 4.解析响应     handleResponse2(response); }  9. 公共解析 private List<HotelDoc> handleResponse(SearchResponse response) throws Exception {     // 获取命中的所有内容     SearchHits searchHits = response.getHits();     // 获取命中的总条数     long count = searchHits.getTotalHits().value;     System.out.println("命中的条数为: "+ count);     // 获取命中的文档对象数组     SearchHit[] hits = searchHits.getHits();     List<HotelDoc> docList = new ArrayList<>();     for (SearchHit hit : hits) {         // 解析每一个hit对象得到对应的文档数据         String json = hit.getSourceAsString();         //  HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);         docList.add(JSON.parseObject(json, HotelDoc.class));     }     //destroy();     return docList; }  private void handleResponse2(SearchResponse response) {     // 4.解析响应     SearchHits searchHits = response.getHits();     // 4.1.获取总条数     long total = searchHits.getTotalHits().value;     System.out.println("共搜索到" + total + "条数据");     // 4.2.文档数组     SearchHit[] hits = searchHits.getHits();     // 4.3.遍历     for (SearchHit hit : hits) {         // 获取文档source         String json = hit.getSourceAsString();         // 反序列化         HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);         // 获取高亮结果         Map<String, HighlightField> highlightFields = hit.getHighlightFields();         if ( !CollectionUtils.isEmpty(highlightFields) ) {             // 根据字段名获取高亮结果             HighlightField highlightField = highlightFields.get("name");             if (highlightField != null) {                 // 获取高亮值                 String name = highlightField.getFragments()[0].string();                 // 覆盖非高亮结果                 hotelDoc.setName(name);             }         }         System.out.println("hotelDoc = " + hotelDoc);     } } ———————————————— 版权声明:本文为CSDN博主「我有一颗五叶草」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_60915009/article/details/131323431 
  • [技术干货] Spring Boot 中的缓存注解-转载
     Spring Boot 中的缓存注解 在 Spring Boot 中,缓存是一个非常重要的话题。当我们需要频繁读取一些数据时,为了提高性能,可以将这些数据缓存起来,避免每次都从数据库中读取。为了实现缓存,Spring Boot 提供了一些缓存注解,可以方便地实现缓存功能。   缓存注解是什么? Spring Boot 提供了四个缓存注解,分别是:  @Cacheable @CachePut @CacheEvict @Caching 这些注解可以用来标记一个方法需要被缓存,或者缓存需要被更新或删除。  缓存注解的原理 在 Spring Boot 中,缓存的实现是通过缓存管理器来实现的。缓存管理器负责缓存的创建、读取、更新和删除等操作。Spring Boot 提供了多种缓存管理器的实现,例如 Ehcache、Redis、Caffeine 等。  当一个方法被标记为缓存方法时,Spring Boot 会先查找是否存在缓存,如果存在,则直接从缓存中读取数据。如果缓存中不存在,则执行方法并将结果缓存到缓存中。  当一个方法被标记为更新或删除缓存时,Spring Boot 会根据注解中的参数来更新或删除缓存。例如,@CachePut 注解会将方法的结果缓存起来,而 @CacheEvict 注解会删除缓存。  如何使用缓存注解? 在 Spring Boot 中,可以通过在方法上添加缓存注解来开启缓存功能。下面介绍四个常用的缓存注解。  @Cacheable @Cacheable 注解可以标记一个方法需要被缓存。在注解中,可以指定缓存的名称和缓存的键。例如:  @Cacheable(value = "users", key = "#id") public User getUserById(Long id) {     // 从数据库中读取用户信息 } 在上面的例子中,缓存的名称是 users,缓存的键是方法的参数 id。当方法被执行时,Spring Boot 会先查找缓存,如果缓存中存在相应的数据,则直接从缓存中读取,否则执行方法并将结果缓存到缓存中。  @CachePut @CachePut 注解可以标记一个方法需要更新缓存。在注解中,可以指定缓存的名称和缓存的键。例如:  @CachePut(value = "users", key = "#user.id") public User updateUser(User user) {     // 更新数据库中的用户信息 } 在上面的例子中,缓存的名称是 users,缓存的键是方法返回值 user.id。当方法被执行时,Spring Boot 会更新缓存中的数据。  @CacheEvict @CacheEvict 注解可以标记一个方法需要删除缓存。在注解中,可以指定缓存的名称和缓存的键。例如:  @CacheEvict(value = "users", key = "#id") public void deleteUserById(Long id) {     // 删除数据库中的用户信息 } 在上面的例子中,缓存的名称是 users,缓存的键是方法的参数 id。当方法被执行时,Spring Boot 会删除缓存中对应的数据。  @Caching @Caching 注解可以将多个缓存注解组合在一起使用。例如:  @Caching(     cacheable = @Cacheable(value = "users", key = "#id"),     put = @CachePut(value = "users", key = "#result.id"),     evict = @CacheEvict(value = "allUsers", allEntries = true) ) public User getUserById(Long id) {     // 从数据库中读取用户信息 } 在上面的例子中,@Caching 注解包含了三个缓存注解:@Cacheable、@CachePut 和 @CacheEvict。当方法被执行时,Spring Boot 会先查找缓存,如果缓存中存在相应的数据,则直接从缓存中读取;如果缓存中不存在,则执行方法并将结果缓存到缓存中;同时更新 users 缓存中的数据,并删除 allUsers 缓存中的所有数据。  缓存注解的配置 在 Spring Boot 中,可以通过配置文件来配置缓存的属性。下面是一个使用 Redis 作为缓存管理器的配置文件示例:  spring:   cache:     type: redis     redis:       host: localhost       port: 6379       password: password       time-to-live: 30000 在上面的例子中,使用 Redis 作为缓存管理器,设置 Redis 的主机地址、端口号、密码和超时时间。可以根据实际情况进行配置。  代码示例 下面是一个使用缓存注解的代码示例。在这个例子中,我们定义了一个 UserService 类,其中包含一个 getUserById() 方法和一个 updateUser() 方法。在方法上添加了缓存注解,可以方便地实现缓存功能。  @Service public class UserService {      @Autowired     private UserRepository userRepository;      @Cacheable(value = "users", key = "#id")     public User getUserById(Long id) {         return userRepository.findById(id).orElse(null);     }      @CachePut(value = "users", key = "#user.id")     public User updateUser(User user) {         userRepository.save(user);         return user;     }  } 在上面的例子中,getUserById() 方法被标记为 @Cacheable 注解,缓存的名称是 users,缓存的键是方法的参数 id;updateUser() 方法被标记为 @CachePut 注解,缓存的名称是 users,缓存的键是方法返回值 user.id。当方法被执行时,Spring Boot 会先查找缓存,如果缓存中存在相应的数据,则直接从缓存中读取,否则执行方法并将结果缓存到缓存中。  总结 在 Spring Boot 中,缓存是非常重要的。通过使用缓存注解,可以方便地实现缓存功能,提高程序的性能。在代码中,我们可以通过使用 @Cacheable、@CachePut、@CacheEvict 和 @Caching 注解来开启缓存功能,也可以通过配置文件来配置缓存属性。 ———————————————— 版权声明:本文为CSDN博主「程序媛徐师姐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/2301_77835649/article/details/131434427