首页
友链
关于
免责声明
Search
1
王者营地战绩数据王者荣耀查询网页源码
6,209 阅读
2
群晖Active Backup for Business套件备份Linux服务器教程
4,384 阅读
3
影视分享
4,313 阅读
4
(亲测)Jrebel激活破解方式2019-08-21
4,289 阅读
5
centos7 安装及卸载 jekenis
3,573 阅读
日常
文章
后端
前端
Linux
异常
Flutter
分享
群辉
登录
Search
标签搜索
docker
springboot
Spring Boot
java
linux
Shiro
Graphics2D
图片
游戏账号交易
Mybatis
Spring Cloud
centos
脚本
Web Station
群辉
王者营地
战绩查询
平台对接
Spring Cloud Alibaba
nacos
绿林寻猫
累计撰写
249
篇文章
累计收到
26
条评论
首页
栏目
日常
文章
后端
前端
Linux
异常
Flutter
分享
群辉
页面
友链
关于
免责声明
搜索到
237
篇与
文章
的结果
2021-12-08
Spring Boot 整合Shiro(一)登录认证和授权(附源码)
shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。主要功能三个核心组件:Subject, SecurityManager 和 Realms.Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。 Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。 SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。 Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。 从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。 Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。 1.项目版本Spring Boot 2.x shiro 1.3.21.1导入依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>2.类配置2.1 ShiroConfig相当于之前的xml配置,包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。import com.shiro.realm.CustomRealm; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.Map; /** * 过滤的文件和权限,密码加密的算法,其用注解等相关功能 */ @Configuration public class ShiroConfig { @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // Shiro的核心安全接口,这个属性是必须的 shiroFilterFactoryBean.setSecurityManager(securityManager); // 身份认证失败,则跳转到登录页面的配置 shiroFilterFactoryBean.setLoginUrl("/login"); // 权限认证失败,则跳转到指定页面 shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/front/**", "anon"); filterChainDefinitionMap.put("/api/**", "anon"); filterChainDefinitionMap.put("/admin/**", "authc"); filterChainDefinitionMap.put("/user/**", "authc"); //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); defaultSecurityManager.setRealm(customRealm()); return defaultSecurityManager; } @Bean public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); return customRealm; } }2.1.1 shiroFilter方法shiro的过滤器,可以设置登录页面(setLoginUrl)、权限不足跳转页面(setUnauthorizedUrl)、具体某些页面的权限控制或者身份认证。注意:这里是需要设置SecurityManager(setSecurityManager)。默认的过滤器还有:anno、authc、authcBasic、logout、noSessionCreation、perms、port、rest、roles、ssl、user过滤器。具体的大家可以查看package org.apache.shiro.web.filter.mgt.DefaultFilter。这个类,常用的也就authc、anno。2.1.2 securityManager 方法public interface SecurityManager extends Authenticator, Authorizer, SessionManager { //登录方法 Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException; //注销方法 void logout(Subject subject); //创建subject Subject createSubject(SubjectContext context); }由于项目是一个web项目,所以我们使用的是DefaultWebSecurityManager ,然后设置自己的Realm。2.1.3 CustomRealm 方法将 customRealm的实例化交给spring去管理,当然这里也可以利用注解的方式去注入 2.2 CustomRealm自定义的CustomRealm继承AuthorizingRealm。并且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashSet; import java.util.Set; /** * */ public class CustomRealm extends AuthorizingRealm { /** * 权限相关 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //账户 String username = (String) SecurityUtils.getSubject().getPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //从数据库获取账户权限信息 Set<String> stringSet = new HashSet<>(); stringSet.add("user:show"); stringSet.add("user:admin"); info.setStringPermissions(stringSet); return info; } /** * 身份认证 * 这里可以注入userService,为了方便演示直接写死账户和密码 * 获取即将需要认证的信息 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("-------身份认证方法--------"); String userName = (String) authenticationToken.getPrincipal(); String userPwd = new String((char[]) authenticationToken.getCredentials()); System.out.println(userPwd); //根据用户名从数据库获取密码 String password = "123"; if (userName == null) { throw new AccountException("用户名不正确"); } else if (!userPwd.equals(password )) { throw new AccountException("密码不正确"); } return new SimpleAuthenticationInfo(userName, password,getName()); } } 自定义的Realm类继承AuthorizingRealm类,并且重载doGetAuthorizationInfo和doGetAuthenticationInfo两个方法。doGetAuthorizationInfo: 权限认证,即登录过后,每个身份不一定,对应的所能看的页面也不一样。doGetAuthenticationInfo:身份认证。即登录通过账号和密码验证登陆人的身份信息。 3.实战演练3.1 登陆认证创建LoginControllerimport org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.subject.Subject; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping public class LoginController { @RequestMapping(value = "/login", method = RequestMethod.GET) public String defaultLogin() { return "首页"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String login(@RequestParam("username") String username, @RequestParam("password") String password) { // 从SecurityUtils里边创建一个 subject Subject subject = SecurityUtils.getSubject(); // 在认证提交前准备 token(令牌) UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 执行认证登陆 try { subject.login(token); } catch (UnknownAccountException uae) { return "未知账户"; } catch (IncorrectCredentialsException ice) { return "密码不正确"; } catch (LockedAccountException lae) { return "账户已锁定"; } catch (ExcessiveAttemptsException eae) { return "用户名或密码错误次数过多"; } catch (AuthenticationException ae) { return "用户名或密码不正确!"; } if (subject.isAuthenticated()) { return "登录成功"; } else { token.clear(); return "登录失败"; } } }测试结果:3.2 权限测试在ShiroConfig添加如下代码,开启注解。@Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * * * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 * * * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 * * @return */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; }创建UserControllerimport com.shiro.utils.BaseController; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/user") @RestController public class UserController extends BaseController { @RequiresPermissions("user:list") @RequestMapping("/show") public String showUser() { return "张三信息"; } }再创建 BaseController,UserController继承BaseController,用于捕获没有权限时的异常import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.UnauthorizedException; import org.springframework.web.bind.annotation.ExceptionHandler; import java.util.HashMap; import java.util.Map; public class BaseController { /** * 捕获没有权限时的异常 * @return */ @ExceptionHandler({ UnauthorizedException.class, AuthorizationException.class }) public Map<String, Object> authorizationException(){ Map<String, Object> map = new HashMap<String, Object>(); System.out.println("没有权限"); map.put("success", true); map.put("msg", "当前用户没有此权限"); return map; } } 运行测试,先执行 http://127.0.0.1:8080/login,再执行http://127.0.0.1:8080/user/show:方法上是 @RequiresPermissions("user:list"),之前在customRealm只添加了两个所以没有权限现在把方法上的改为@RequiresPermissions("user:show")测试现在就有权限访问数据啦4.项目源码项目地址 下篇介绍《Spring Boot 整合Shiro(二)加密登录与密码加盐处理》
2021年12月08日
425 阅读
0 评论
0 点赞
2021-12-08
Spring Boot 整合Shiro(二)加密登录与密码加盐处理
该项目是根据上篇《Spring Boot 整合Shiro(一)登录认证和授权(附源码)》进行配置,若有不明白的请先查看上篇文章。1.加密工具用户注册时的密码用这个加密保存数据库 /** * 账户密码加密 * @param username * @param pwd * @return */ public static String MD5Pwd(String username, String pwd) { String hashAlgorithmName = "MD5";//加密方式 Object crdentials = pwd;//密码原值 ByteSource salt = ByteSource.Util.bytes(username);//以账号作为盐值 int hashIterations = 1024;//加密1024次 return new SimpleHash(hashAlgorithmName,crdentials,salt,hashIterations).toString(); }2.Shiro配置2.1修改ShiroConfig添加以下方法: /** * 加密配置 * @return */ @Bean(name = "credentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // 散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列的次数,比如散列两次,相当于 md5(md5("")); hashedCredentialsMatcher.setHashIterations(1024); // storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码 hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); return hashedCredentialsMatcher; }修改customRealm: @Bean public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); // 告诉realm,使用credentialsMatcher加密算法类来验证密文 customRealm.setCredentialsMatcher(hashedCredentialsMatcher()); customRealm.setCachingEnabled(false); return customRealm; }2.2修改CustomRealm当中“password”参数值是用户注册时使用加密工具生产保存的密码,可在CustomRealm中配置UserService。/** * 身份认证 * 这里可以注入userService,为了方便演示直接写死账户和密码 * 获取即将需要认证的信息 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("-------身份认证方法--------"); String userName = (String) authenticationToken.getPrincipal(); // String userPwd = new String((char[]) authenticationToken.getCredentials()); //根据用户名从数据库获取密码 String password = "89267a06ce552c28e3edc11be28e4f80";// 使用账户和明文密码:123加密后 // if (userName == null) { // throw new AccountException("用户名不正确"); // } else if (!userPwd.equals(password )) { // throw new AccountException("密码不正确"); // } //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配 ByteSource salt = ByteSource.Util.bytes(userName); return new SimpleAuthenticationInfo(userName, password, salt, getName()); }3.实战演练使用账户:aa和密码:123 进行测试,可以看到提示登录成功。 之前的密码是根据账户和密码进行处理的,要注意的是注册的加密方式和设置的加密方式还有Realm中身份认证的方式都是要一模一样的 下篇介绍《Spring Boot 整合 Shiro(三)Kaptcha验证码》
2021年12月08日
363 阅读
0 评论
0 点赞
2021-12-08
Spring Boot 整合 RabbitMQ(附源码)
一、前言RabbitMQ 的模式介绍可以看看笔者写的这篇文章《RabbitMQ 工作模式介绍》二、整合 RabbitMQ2.1 添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>2.2 添加配置spring.rabbitmq.host=192.168.2.101 spring.rabbitmq.port=5672 spring.rabbitmq.username=admin spring.rabbitmq.password=admin2.3 配置类AmqpConfirguration.javaimport org.springframework.amqp.core.*; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import java.util.logging.Logger; /** * 配置类: */ @Configuration public class AmqpConfirguration { private final Logger logger = Logger.getLogger(this.getClass().getName()); @Value("${spring.rabbitmq.host}") private String host; @Value("${spring.rabbitmq.port}") private int port; @Value("${spring.rabbitmq.username}") private String username; @Value("${spring.rabbitmq.password}") private String password; @Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host,port); connectionFactory.setUsername(username); connectionFactory.setPassword(password); connectionFactory.setVirtualHost("/"); connectionFactory.setPublisherConfirms(true); return connectionFactory; } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //必须是prototype类型 public RabbitTemplate rabbitTemplate() { RabbitTemplate template = new RabbitTemplate(connectionFactory()); return template; } //=============简单、工作队列模式=============== public static final String SIMPLE_QUEUE = "simple_queue"; @Bean public Queue queue() { return new Queue(SIMPLE_QUEUE, true); } //===============发布/订阅模式============ public static final String PS_QUEUE_1 = "ps_queue_1"; public static final String PS_QUEUE_2 = "ps_queue_2"; public static final String FANOUT_EXCHANGE = "fanout_exchange"; @Bean public Queue psQueue1() { return new Queue(PS_QUEUE_1, true); } @Bean public Queue psQueue2() { return new Queue(PS_QUEUE_2, true); } @Bean public FanoutExchange fanoutExchange() { return new FanoutExchange(FANOUT_EXCHANGE); } @Bean public Binding fanoutBinding1() { return BindingBuilder.bind(psQueue1()).to(fanoutExchange()); } @Bean public Binding fanoutBinding2() { return BindingBuilder.bind(psQueue2()).to(fanoutExchange()); } //===============路由模式============ public static final String ROUTING_QUEUE_1 = "routing_queue_1"; public static final String ROUTING_QUEUE_2 = "routing_queue_2"; public static final String DIRECT_EXCHANGE = "direct_exchange"; @Bean public Queue routingQueue1() { return new Queue(ROUTING_QUEUE_1, true); } @Bean public Queue routingQueue2() { return new Queue(ROUTING_QUEUE_2, true); } @Bean public DirectExchange directExchange() { return new DirectExchange(DIRECT_EXCHANGE); } @Bean public Binding directBinding1() { return BindingBuilder.bind(routingQueue1()).to(directExchange()).with("user"); } @Bean public Binding directBinding2() { return BindingBuilder.bind(routingQueue2()).to(directExchange()).with("order"); } //===============主题模式============ public static final String TOPIC_QUEUE_1 = "topic_queue_1"; public static final String TOPIC_QUEUE_2 = "topic_queue_2"; public static final String TOPIC_QUEUE_3 = "topic_queue_3"; public static final String TOPIC_EXCHANGE = "topic_exchange"; @Bean public Queue topicQueue1() { return new Queue(TOPIC_QUEUE_1, true); } @Bean public Queue topicQueue2() { return new Queue(TOPIC_QUEUE_2, true); } @Bean public Queue topicQueue3() { return new Queue(TOPIC_QUEUE_3, true); } @Bean public TopicExchange topicExchange() { return new TopicExchange(TOPIC_EXCHANGE); } @Bean public Binding topicBinding1() { return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("user.add"); } @Bean public Binding topicBinding2() { return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("user.#"); } @Bean public Binding topicBinding3() { return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("test.#"); } }2.4 消息生产者AmqpSender.javaimport org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * 消息生产者 */ @Component public class AmqpSender { @Autowired private AmqpTemplate amqpTemplate; /** * 简单模式发送 * * @param message */ public void simpleSend(String message) { this.amqpTemplate.convertAndSend(AmqpConfirguration.SIMPLE_QUEUE, message); } /** * 发布/订阅模式发送 * * @param message */ public void psSend(String message) { this.amqpTemplate.convertAndSend(AmqpConfirguration.FANOUT_EXCHANGE, "", message); } /** * 路由模式发送 * * @param message */ public void routingSend(String routingKey, String message) { this.amqpTemplate.convertAndSend(AmqpConfirguration.DIRECT_EXCHANGE, routingKey, message); } /** * 主题模式发送 * * @param routingKey * @param message */ public void topicSend(String routingKey, String message) { this.amqpTemplate.convertAndSend(AmqpConfirguration.TOPIC_EXCHANGE, routingKey, message); } }2.5 消息消费者AmqpReceiver.javaimport org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; /** * 消息消费者 */ @Component public class AmqpReceiver { //使@RabbitListener 注解监听消息。 /** * 简单模式接收 * * @param message */ @RabbitListener(queues = AmqpConfirguration.SIMPLE_QUEUE) public void simpleReceive1(String message) { System.out.println("接收消息1:" + message); } @RabbitListener(queues = AmqpConfirguration.SIMPLE_QUEUE) public void simpleReceive2(String message) { System.out.println("接收消息2:" + message); } /** * 发布/订阅模式接收 * * @param message */ @RabbitListener(queues = AmqpConfirguration.PS_QUEUE_1) public void psReceive1(String message) { System.out.println(AmqpConfirguration.PS_QUEUE_1 + "接收消息:" + message); } @RabbitListener(queues = AmqpConfirguration.PS_QUEUE_2) public void psReceive2(String message) { System.out.println(AmqpConfirguration.PS_QUEUE_2 + "接收消息:" + message); } /** * 路由模式接收 * * @param message */ @RabbitListener(queues = AmqpConfirguration.ROUTING_QUEUE_1) public void routingReceive1(String message) { System.out.println(AmqpConfirguration.ROUTING_QUEUE_1 + "接收消息:" + message); } @RabbitListener(queues = AmqpConfirguration.ROUTING_QUEUE_2) public void routingReceive2(String message) { System.out.println(AmqpConfirguration.ROUTING_QUEUE_2 + "接收消息:" + message); } /** * 主题模式接收 * * @param message */ @RabbitListener(queues = AmqpConfirguration.TOPIC_QUEUE_1) public void topicReceive1(String message) { System.out.println(AmqpConfirguration.TOPIC_QUEUE_1 + "接收消息:" + message); } @RabbitListener(queues = AmqpConfirguration.TOPIC_QUEUE_2) public void topicReceive2(String message) { System.out.println(AmqpConfirguration.TOPIC_QUEUE_2 + "接收消息:" + message); } @RabbitListener(queues = AmqpConfirguration.TOPIC_QUEUE_3) public void topicReceive3(String message) { System.out.println(AmqpConfirguration.TOPIC_QUEUE_3 + "接收消息:" + message); } }2.6 测试类@RunWith(SpringRunner.class) @SpringBootTest public class RabbitmqApplicationTests { @Autowired private AmqpSender sender; @Test public void testSimpleSend() { //简单模式发送 for (int i = 1; i < 6; i++) { this.sender.simpleSend("test simpleSend " + i); } } @Test public void testPsSend() { //发布/订阅模式发送 for (int i = 1; i < 6; i++) { this.sender.psSend("test psSend " + i); } } @Test public void testRoutingSend() { //路由模式发送 for (int i = 1; i < 6; i++) { this.sender.routingSend("order", "test routingSend " + i); } } @Test public void testTopicSend() { //主题模式发送 for (int i = 1; i < 6; i++) { this.sender.topicSend("user.add", "test topicSend " + i); } } } 三、实战演练 3.1 简单模式与工作队列模式一个消息消费者:结果:两个消息消费者:结果:由两个消息消费者平均消费消息3.2 发布/订阅模式两个消息消费者:结果:所有消息消费者都能收到所有信息 3.3路由模式在AmqpConfirguration定义了“user”和"order",在路由上绑定了routingKey为“user”与“order”两个队列两个消息消费者: 测试:routingKey为“order” 可以看到只有routing_queue_2收到了消息,routing_queue_2的routingKey为“order”3.4 主题模式在路由上绑定了三个队列,routingKey分别为“user.add”、“user.#”、“test.#”3个消息消费者测试:routingKey为“user.add”可以发现topic_queue_1和topic_queue_2都接收到了消息,而topic_queue_3没有。因为主题模式中routingKey里的 符号 “#” 匹配一个或多个词,符号“*”匹配不多不少一个词。相当于模糊匹配,而topic_queue_3匹配不上三、项目源码源码下载
2021年12月08日
255 阅读
0 评论
0 点赞
2021-12-08
Spring Boot 整合 Shiro(三)Kaptcha验证码 附源码
前言本文是根据上篇《Spring Boot 整合Shiro(二)加密登录与密码加盐处理》进行修改,如有不明白的转上篇文章了解。1.导入依赖 <!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha --> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency> 2.配置创建KaptchaConfig.javaimport com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import java.util.Properties; @Component public class KaptchaConfig { @Bean public DefaultKaptcha getDefaultKaptcha() { com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha(); Properties properties = new Properties(); // 图片边框 properties.setProperty("kaptcha.border", "yes"); // 边框颜色 properties.setProperty("kaptcha.border.color", "105,179,90"); // 字体颜色 properties.setProperty("kaptcha.textproducer.font.color", "red"); // 图片宽 properties.setProperty("kaptcha.image.width", "110"); // 图片高 properties.setProperty("kaptcha.image.height", "40"); // 字体大小 properties.setProperty("kaptcha.textproducer.font.size", "30"); // session key properties.setProperty("kaptcha.session.key", "code"); // 验证码长度 properties.setProperty("kaptcha.textproducer.char.length", "4"); // 字体 properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑"); //可以设置很多属性,具体看com.google.code.kaptcha.Constants // kaptcha.border 是否有边框 默认为true 我们可以自己设置yes,no // kaptcha.border.color 边框颜色 默认为Color.BLACK // kaptcha.border.thickness 边框粗细度 默认为1 // kaptcha.producer.impl 验证码生成器 默认为DefaultKaptcha // kaptcha.textproducer.impl 验证码文本生成器 默认为DefaultTextCreator // kaptcha.textproducer.char.string 验证码文本字符内容范围 默认为abcde2345678gfynmnpwx // kaptcha.textproducer.char.length 验证码文本字符长度 默认为5 // kaptcha.textproducer.font.names 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) // kaptcha.textproducer.font.size 验证码文本字符大小 默认为40 // kaptcha.textproducer.font.color 验证码文本字符颜色 默认为Color.BLACK // kaptcha.textproducer.char.space 验证码文本字符间距 默认为2 // kaptcha.noise.impl 验证码噪点生成对象 默认为DefaultNoise // kaptcha.noise.color 验证码噪点颜色 默认为Color.BLACK // kaptcha.obscurificator.impl 验证码样式引擎 默认为WaterRipple // kaptcha.word.impl 验证码文本字符渲染 默认为DefaultWordRenderer // kaptcha.background.impl 验证码背景生成器 默认为DefaultBackground // kaptcha.background.clear.from 验证码背景颜色渐进 默认为Color.LIGHT_GRAY // kaptcha.background.clear.to 验证码背景颜色渐进 默认为Color.WHITE // kaptcha.image.width 验证码图片宽度 默认为200 // kaptcha.image.height 验证码图片高度 默认为50 Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } } 3.验证码生成/验证import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import com.google.code.kaptcha.impl.DefaultKaptcha; @Controller @RequestMapping(value ="/kaptcha" ) public class KaptchaController { /** * 1、验证码工具 */ @Autowired DefaultKaptcha defaultKaptcha; /** * 2、生成验证码 * @param httpServletRequest * @param httpServletResponse * @throws Exception */ @RequestMapping("/defaultKaptcha") public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { byte[] captchaChallengeAsJpeg = null; ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream(); try { // 生产验证码字符串并保存到session中 String createText = defaultKaptcha.createText(); httpServletRequest.getSession().setAttribute("rightCode", createText); // 使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中 BufferedImage challenge = defaultKaptcha.createImage(createText); ImageIO.write(challenge, "jpg", jpegOutputStream); } catch (IllegalArgumentException e) { httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组 captchaChallengeAsJpeg = jpegOutputStream.toByteArray(); httpServletResponse.setHeader("Cache-Control", "no-store"); httpServletResponse.setHeader("Pragma", "no-cache"); httpServletResponse.setDateHeader("Expires", 0); httpServletResponse.setContentType("image/jpeg"); ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream(); responseOutputStream.write(captchaChallengeAsJpeg); responseOutputStream.flush(); responseOutputStream.close(); } /** * 3、校对验证码 * @param httpServletRequest * @param httpServletResponse * @return */ @RequestMapping("/imgvrifyControllerDefaultKaptcha") public ModelAndView imgvrifyControllerDefaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { ModelAndView andView = new ModelAndView(); String rightCode = (String) httpServletRequest.getSession().getAttribute("rightCode"); String tryCode = httpServletRequest.getParameter("tryCode"); System.out.println("rightCode:"+rightCode+" ———— tryCode:"+tryCode); if (!rightCode.equals(tryCode)) { andView.addObject("info", "错误的验证码"); andView.setViewName("/login"); } else { andView.addObject("info", "登录成功"); andView.setViewName("success"); } return andView; } } 4.修改ShiroConfig开放相应的验证码路径,避免拦截@Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // Shiro的核心安全接口,这个属性是必须的 shiroFilterFactoryBean.setSecurityManager(securityManager); // 身份认证失败,则跳转到登录页面的配置 shiroFilterFactoryBean.setLoginUrl("/login"); // 权限认证失败,则跳转到指定页面 shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/loginOut", "anon"); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/front/**", "anon"); filterChainDefinitionMap.put("/api/**", "anon"); filterChainDefinitionMap.put("/kaptcha/**", "anon"); filterChainDefinitionMap.put("/success/**", "anon"); filterChainDefinitionMap.put("/admin/**", "authc"); filterChainDefinitionMap.put("/user/**", "authc"); //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }5.修改LoginController@Controller @RequestMapping public class LoginController { private Logger logger = Logger.getLogger(this.getClass().getName()); /** * 界面 * @return */ @RequestMapping(value = "/login", method = RequestMethod.GET) public String defaultLogin() { return "login"; } /** * 退出 * @return */ @RequestMapping(value = "/loginOut", method = RequestMethod.GET) public String loginOut() { Subject subject = SecurityUtils.getSubject(); subject.logout(); return "login"; } /** * 登录提交 * @param username * @param tryCode * @param password * @param redirectAttributes * @return */ @RequestMapping(value = "/login", method = RequestMethod.POST) public String login(@RequestParam("username") String username, @RequestParam("tryCode") String tryCode, @RequestParam("password") String password, RedirectAttributes redirectAttributes) { //判断验证码 if(StringUtils.isBlank(tryCode)){ logger.info("验证码为空了!"); redirectAttributes.addFlashAttribute("message", "验证码不能为空!"); return "redirect:login"; } Session session = SecurityUtils.getSubject().getSession(); String code = (String) session.getAttribute("rightCode"); System.out.println(code+"*************"+tryCode); if(!tryCode.equalsIgnoreCase(code)){ logger.info("验证码错误!"); redirectAttributes.addFlashAttribute("message", "验证码错误!"); return "redirect:login"; } // 从SecurityUtils里边创建一个 subject Subject subject = SecurityUtils.getSubject(); // 在认证提交前准备 token(令牌) UsernamePasswordToken token = new UsernamePasswordToken(username, password); String attributeValue = null; // 执行认证登陆 try { subject.login(token); } catch (UnknownAccountException uae) { attributeValue="未知账户!"; } catch (IncorrectCredentialsException ice) { attributeValue="密码不正确!"; } catch (LockedAccountException lae) { attributeValue= "账户已锁定"; } catch (ExcessiveAttemptsException eae) { attributeValue= "用户名或密码错误次数过多"; } catch (AuthenticationException ae) { attributeValue= "用户名或密码不正确!"; }finally { redirectAttributes.addFlashAttribute("message", attributeValue); if (subject.isAuthenticated()) { return "success"; } else { token.clear(); return "redirect:login"; } } } }6.界面6.1整合thymeleaf模板添加依赖;<!--thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>编辑application.ymlserver: port: 80 servlet: context-path: / #thymeleaf模板 spring: thymeleaf: cache: true prefix: classpath: /templates/ suffix: .html mode: HTML5 encoding: UTF-8 servlet: content-type: text/html 6.2创建login.html<!DOCTYPE html> <!-- thymeleaf 提示功能 --> <html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html"> <head lang="en"> <meta charset="UTF-8"></meta> <title>验证码</title> </head> <style type="text/css"> body { padding: 10px } </style> <body> <!-- 提示 --> <h3 th:text="${message}"></h3> <div> <!-- 后面添加参数起到清除缓存作用 --> <img alt="验证码" onclick="this.src='/kaptcha/defaultKaptcha?d='+new Date()*1" src="/kaptcha/defaultKaptcha" /> </div> <form action="/login" method="post"> 账户:<input type="text" name="username" /></br> 密码:<input type="text" name="password" /></br> 验证码:<input type="text" name="tryCode" /> <input type="submit" value="提交" ></input> </form> </body> </html> success.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"></meta> <title>成功</title> </head> <body> <h1>登录成功</h1> <a href="/loginOut">退出</a> </body> </html> 7. 实战演练 8.项目源码《源码下载》下篇介绍Spring Boot 整合 Shiro(四)thymeleaf模板权限控制 附源码
2021年12月08日
388 阅读
0 评论
0 点赞
2021-12-08
SpringBoot+Mybatis启动项目*required a single bean, but 2 were found*
异常问题为controller中存在多个相同的实例*************************** APPLICATION FAILED TO START *************************** Description: Field sysJobService in com.comet.controller.SysJobController required a single bean, but 2 were found: - sysJobServiceImpl: defined in file [G:\IdeaProject\comet\comet-quartz\target\classes\com\comet\service\impl\SysJobServiceImpl.class] - ISysJobService: defined in file [G:\IdeaProject\comet\comet-quartz\target\classes\com\comet\service\ISysJobService.class] Action: Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed 我个人在配置映射时没有制定到mapper包中才出现上述问题 改成这样就能成功启动并访问数据库
2021年12月08日
200 阅读
0 评论
0 点赞
2021-12-08
SpringBoot +Mybatis druid多数据源
配置依赖导入 <!-- springboot,druid 整合包 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--aop--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- springboot,mybatis 整合包 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency>application.yml# 开发环境配置 server: # 服务器的HTTP端口,默认为80 port: 80 servlet: # 应用的访问路径 context-path: / tomcat: # tomcat的URI编码 uri-encoding: UTF-8 # tomcat最大线程数,默认为200 max-threads: 800 # Tomcat启动初始化的线程数,默认值25 min-spare-threads: 30 # Spring配置 spring: # 模板引擎 thymeleaf: mode: HTML encoding: utf-8 # 禁用缓存 cache: false profiles: active: druid # mybatis 配置 mybatis: mapper-locations: classpath:mybatis/mappers/*.xml configuration: map-underscore-to-camel-case: true #驼峰转换 use-generated-keys: true #获取数据库自增列 use-column-label: true #使用列别名替换列名 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl default-executor-type: reuse #配置默认的执行器 cache-enabled: true #全局映射器启用缓存 type-aliases-package: com.comet #实体类配置别名 application-druid.yml# 数据源配置 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.jdbc.Driver druid: # 主库数据源 master: url: jdbc:mysql://192.168.1.20:3306/comet1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: root # 从库数据源 cluster: # 从数据源开关/默认关闭 enabled: true url: jdbc:mysql://192.168.1.21:3306/comet2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: root # 初始连接数 initialSize: 5 # 最小连接池数量 minIdle: 10 # 最大连接池数量 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最大生存的时间,单位是毫秒 maxEvictableIdleTimeMillis: 900000 # 配置检测连接是否有效 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false # 配置DruidStatFilter webStatFilter: enabled: true url-pattern: "/*" exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" # 配置DruidStatViewServlet statViewServlet: enabled: true # 设置白名单,不填则允许所有访问 allow: url-pattern: /druid/* # 控制台管理用户名和密码 login-username: login-password: filter: stat: enabled: true # 慢SQL记录 log-slow-sql: true slow-sql-millis: 1000 merge-sql: true wall: config: multi-statement-allow: true代码枚举类 数据类型/** * @author Unclue_liu * @organization comet * @date 2019/9/3 0003 17:00 * @desc TODO 数据类型 */ public enum DataSourceType { /** * 主数据 */ MASTER, /** * 从数据 */ CLUSTER }注解类 自定义注解import com.comet.common.enums.DataSourceType; import java.lang.annotation.*; /** * @author Unclue_liu * @organization comet * @date 2019/9/3 0003 17:00 * @desc TODO 自定义多数据源切换注解,可以用于service类或者方法级别 方法级别优先级 > 类级别 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource { /** * 切换数据源名称 */ DataSourceType value() default DataSourceType.MASTER; }druid 配置属性import com.alibaba.druid.pool.DruidDataSource; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO druid 配置属性 */ @Data @Configuration public class DruidProperties { @Value("${spring.datasource.druid.initialSize}") private int initialSize; @Value("${spring.datasource.druid.minIdle}") private int minIdle; @Value("${spring.datasource.druid.maxActive}") private int maxActive; @Value("${spring.datasource.druid.maxWait}") private int maxWait; @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") private int timeBetweenEvictionRunsMillis; @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") private int minEvictableIdleTimeMillis; @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") private int maxEvictableIdleTimeMillis; @Value("${spring.datasource.druid.validationQuery}") private String validationQuery; @Value("${spring.datasource.druid.testWhileIdle}") private boolean testWhileIdle; @Value("${spring.datasource.druid.testOnBorrow}") private boolean testOnBorrow; @Value("${spring.datasource.druid.testOnReturn}") private boolean testOnReturn; public DruidDataSource dataSource(DruidDataSource datasource) { /** 配置初始化大小、最小、最大 */ datasource.setInitialSize(initialSize); datasource.setMaxActive(maxActive); datasource.setMinIdle(minIdle); /** 配置获取连接等待超时的时间 */ datasource.setMaxWait(maxWait); /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); /** * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 */ datasource.setValidationQuery(validationQuery); /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ datasource.setTestWhileIdle(testWhileIdle); /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnBorrow(testOnBorrow); /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnReturn(testOnReturn); return datasource; } }数据源切换处理import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO 数据源切换处理 */ public class DynamicDataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** * CONTEXT_HOLDER代表一个可以存放String类型的ThreadLocal对象, * 此时任何一个线程可以并发访问这个变量, * 对它进行写入、读取操作,都是线程安全的。 * 比如一个线程通过CONTEXT_HOLDER.set(“aaaa”);将数据写入ThreadLocal中, * 在任何一个地方,都可以通过CONTEXT_HOLDER.get();将值获取出来。 * 这里写入的就是数据库名, */ private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); /** * 设置数据源的变量 */ public static void setDataSourceType(String dsType) { log.info("切换到{}数据源", dsType); CONTEXT_HOLDER.set(dsType); } /** * 获得数据源的变量 */ public static String getDataSourceType() { return CONTEXT_HOLDER.get(); } /** * 清空数据源变量 */ public static void clearDataSourceType() { CONTEXT_HOLDER.remove(); } }动态数据源import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.Map; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO 动态数据源 */ public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }druid 配置多数据源import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; import com.alibaba.druid.util.Utils; import com.comet.config.datasource.DynamicDataSource; import com.comet.common.enums.DataSourceType; import com.comet.config.properties.DruidProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.servlet.*; import javax.sql.DataSource; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO druid 配置多数据源 */ @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.druid.master") public javax.sql.DataSource masterDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean @ConfigurationProperties("spring.datasource.druid.cluster") @ConditionalOnProperty(prefix = "spring.datasource.druid.cluster", name = "enabled", havingValue = "true") public javax.sql.DataSource clusterDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dataSource(javax.sql.DataSource masterDataSource, DataSource clusterDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); targetDataSources.put(DataSourceType.CLUSTER.name(), clusterDataSource); return new DynamicDataSource(masterDataSource, targetDataSources); } /** * 去除监控页面底部的广告 */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) { // 获取web监控页面的参数 DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); // 提取common.js的配置路径 String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); final String filePath = "support/http/resources/js/common.js"; // 创建filter进行过滤 Filter filter = new Filter() { @Override public void init(javax.servlet.FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); // 重置缓冲区,响应头不会被重置 response.resetBuffer(); // 获取common.js String text = Utils.readFromResource(filePath); // 正则替换banner, 除去底部的广告信息 text = text.replaceAll("<a.*?banner\"></a><br/>", ""); text = text.replaceAll("powered.*?shrek.wang</a>", ""); response.getWriter().write(text); } @Override public void destroy() { } }; FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(filter); registrationBean.addUrlPatterns(commonJsPattern); return registrationBean; } }多数据源处理import com.comet.common.annotation.DataSource; import com.comet.common.utils.StringUtils; import com.comet.config.datasource.DynamicDataSourceContextHolder; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; 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; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO 多数据源处理 */ @Aspect @Order(1) @Component public class DataSourceAspect { @Pointcut("@annotation(com.comet.common.annotation.DataSource)" + "|| @within(com.comet.common.annotation.DataSource)") public void dsPointCut() { } @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { DataSource dataSource = getDataSource(point); if (StringUtils.isNotNull(dataSource)) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return point.proceed(); } finally { // 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); } } /** * 获取需要切换的数据源 */ public DataSource getDataSource(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); Class<? extends Object> targetClass = point.getTarget().getClass(); DataSource targetDataSource = targetClass.getAnnotation(DataSource.class); if (StringUtils.isNotNull(targetDataSource)) { return targetDataSource; } else { Method method = signature.getMethod(); DataSource dataSource = method.getAnnotation(DataSource.class); return dataSource; } } }至此多数据源配置完成。使用如果主从数据都使用,将从数据源改为true否则反之。这里配置druid的登录账户密码,如果没有配置则直接通过http://127.0.0.1/druid/index.html访问项目启动默认使用主数据源,如需切换从数据可使用如下方式:或
2021年12月08日
549 阅读
0 评论
0 点赞
2021-10-16
CentOS Frp内网穿透:Frps+Nginx反向代理
目录 服务器使用配置一、Nginx安装二、Frps安装三、frpc安装服务器使用配置CentOS 7.6 CPU: 2核 内存: 4GB 一、Nginx安装参考《Centos配置Nginx+tomcat》,这里就不做过多阐述 二、Frps安装这里使用的是阿里源#下载脚本 wget https://code.aliyun.com/MvsCode/frps-onekey/raw/master/install-frps.sh -O ./install-frps.sh #设置脚本运行权限 chmod 700 ./install-frps.sh #执行脚本 ./install-frps.sh install第一步选择源1是Aliyun,2是Github。我们选1 因博主安装完之后没有截图,后续步骤均可默认再修改配置文件即可,文章最后也会放出配置文件及说明注意事项Frps安装步骤可参考《Frp第一篇)Frp内网穿透安装教程#Frps服务端一键安装脚本#》 安装完之后我的目录是在/usr/local/frps,目录下有frps.ini即参数配置文件# [common] is integral section [common] # A literal address or host name for IPv6 must be enclosed # in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80" bind_addr = 0.0.0.0 bind_port = 7000 # 这个端口的作用是在客户端连接服务端时是通过这个端口连接的 # udp port used for kcp protocol, it can be same with 'bind_port' # if not set, kcp is disabled in frps kcp_bind_port = 7000 # if you want to configure or reload frps by dashboard, dashboard_port must be set dashboard_port = 7500 # 这个是管理端端口 # dashboard assets directory(only for debug mode) dashboard_user = admin dashboard_pwd = password # 这里设置为管理面板配置的密码 # assets_dir = ./static vhost_http_port = 5000 # 设置http连接的端口 vhost_https_port = 444 # 设置http连接的端口 # console or real logFile path like ./frps.log log_file = ./frps.log # debug, info, warn, error log_level = info log_max_days = 3 # auth token token = 123456 #客户端连接token # It is convenient to use subdomain configure for http、https type when many people use one frps server together. subdomain_host = 域名/IP # only allow frpc to bind ports you list, if you set nothing, there won't be any limit #allow_ports = 1-65535 # pool_count in each proxy will change to max_pool_count if they exceed the maximum value max_pool_count = 50 # if tcp stream multiplexing is used, default is true tcp_mux = truenginx.conf配置user root; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile off; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 80; server_name *.baidu.com; location / { proxy_pass http://127.0.0.1:5000; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_max_temp_file_size 0; proxy_redirect off; proxy_read_timeout 240s; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } }面板访问:http://ip:7500 三、frpc安装linux 安装wget https://files.ioiox.com/projects/frp/frpc/frpc_synology_install.sh && chmod +x frpc_synology_install.sh && ./frpc_synology_install.sh 安装成功,请先修改 frpc.ini 文件,确保格式及配置正确无误! vi /usr/local/frp/frpc.ini 修改完毕后执行以下命令启动服务并保持后台运行: nohup /usr/local/frp/frpc -c /usr/local/frp/frpc.ini >/dev/null 2>&1 &修改frpc.ini配置[common] # 服务器IP或者地址 server_addr = 服务端IP/域名 # 服务器提供的端口号 server_port = 7000 kcp_bind_port = 7000 # 服务器提供的token token = 123456 # 为避免错误,一定需更改为比较特殊的名称,不能和服务器端其他配置重名. [web1_xxxxxx] # http协议 type = http # 填写内网IP. local_ip = 192.168.0.88 # 内网HTTP端口 local_port = 8080 # 填写你的域名 custom_domains = a.b.com
2021年10月16日
495 阅读
0 评论
0 点赞
1
...
23
24