Mycat
主从复制启动1 .下载jre包 2 下载mycat包 3 打包镜像 Dockerfile 1234567891011121314FROM centos# 解压这个包到home下面ADD server-jre-8u251-linux-x64.tar.gz /home/ADD ./Mycat-server-1.6.7.4-release-20200105164103-linux.tar.gz /homeENV WORKPATH /home/mycat/WORKDIR $WORKPATHENV JAVA_HOME /home/jdk1.8.0_251ENV CLASSPATH $JAVA_HOME/dt.jar:$JAVA_HOME/lib/tools.jarENV PATH $PATH:$JAVA_HOME/BIN:$CATALINA_HOME/lib:$CATALINA_HOME/bin# 暴露8066端口EXPOSE 8066CMD /home/mycat/bin/mycat console 把mycat里的conf 和 logs 从容器中拷贝出来 再挂在两个目录,并启动 123456789101112131415docker run -it \ --name mycat \ -p 8066:8066 \ -v /home/anthony/mycat/conf:/home/mycat/conf/ \ -v /home/anthony/mycat/logs:/home/mycat/logs/ \ java1234/mycat:1.0Running Mycat-server...Removed stale pid file: /home/mycat/logs/mycat.pidwrapper | --> Wrapper Started as Consolewrapper | Launching a JVM...jvm 1 | Wrapper (Version 3.2.3) http://wrapper.tanukisoftware.orgjvm 1 | Copyright 1999-2006 Tanuki Software, Inc. All Rights Reserved.jvm 1 |jvm 1 | MyCAT Server startup successfully. see logs in logs/mycat.log 配置 schems.xml 定义逻辑库,表,分片节点等内容 rule.xml 定义分片规则 server.xml 定义用户以及系统相关变量 安装mysql5.7123# 从容器中拷贝这些文件docker cp xxx:/etc/mysql/mysql.conf.d/ /home/anthony/mysqldocker cp xxx:/var/log/ /home/anthony/mysql 启动的时候映射这些 创建网络12345678910# 可以查看容器的虚拟ipdocker inspect ac7# 查看网络docker network ls# 自定义一个网络模式# extnetwork 是网络模式的名字# 172.20.0.1 是网关docker network create --subnet=172.20.0.0/16 extnetwork mysql,mycat指定网路,重建容器12345678910111213141516171819202122232425262728# 指定网络模式和ip地址--net extnetwork --ip 172.20.0.2docker run -itd \ --name mysql-master \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /home/anthony/mysql/mysql.conf.d/:/etc/mysql/conf.d/ \ -v /home/anthony/mysql/log/:/var/log/ \ --net extnetwork --ip 172.20.0.2 \ mysql:5.7docker run -itd \ --name mysql-slave \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /home/anthony/mysql2/mysql.conf.d/:/etc/mysql/conf.d/ \ -v /home/anthony/mysql2/log/:/var/log/ \ --net extnetwork --ip 172.20.0.3 \ mysql:5.7docker run -it \ --name mycat \ -p 8066:8066 \ -v /home/anthony/mycat/conf:/home/mycat/conf/ \ -v /home/anthony/mycat/logs:/home/mycat/logs/ \ --net extnetwork --ip 172.20.0.4 \ java1234/mycat:1.0 主从配置主,在[mysqld] 12345678910111213# 主服务器ID,必须唯一server-id=2# 开启和设置二进制日志文件名称log_bin=mysql-bin# 要同步的数据库binlog-do-db=db_java1234# 不需要同步的数据库binlog-ignore_db=mysqlbinlog-ignore_db=information_schemabinlog-ignore_db=performation_schemabinlog_ignore_db=sys# 设置logbin格式,mysql默认采用statement,建议使用mixedbinlog_format=MIXED 从,在[mysqld] 12345678910111213141516# 主服务器ID,必须唯一server-id=3# 这行是从库需要添加的relay-log=mysql-relay# 开启和设置二进制日志文件名称log_bin=mysql-bin# 要同步的数据库binlog-do-db=db_java1234# 不需要同步的数据库binlog-ignore_db=mysqlbinlog-ignore_db=information_schemabinlog-ignore_db=performation_schemabinlog_ignore_db=sys# 设置logbin格式,mysql默认采用statement,建议使用mixedbinlog_format=MIXED 测试主从还没有生效 12345678910mysql> show master status;+------------------+----------+--------------+--------------------------------------------------+-------------------+| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |+------------------+----------+--------------+--------------------------------------------------+-------------------+| mysql-bin.000003 | 357 | db_java1234 | mysql,information_schema,performation_schema,sys | |+------------------+----------+--------------+--------------------------------------------------+-------------------+1 row in set (0.01 sec)mysql> show slave status;Empty set 设置用户名在master执行 12345678910111213# 创建用户名为slave1# 172.20.0.3从库ip# 123456 密码CREATE USER 'slave1'@'172.20.0.3' IDENTIFIED BY '123456';# 语法:GRANT privileges ON databasename.tablename TO 'username'@'host'# 172.20.0.3 从库# 两个权限:REPLICATION和SLAVE# *.* 所有的库和表GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'172.20.0.3';# 刷新权限FLUSH PRIVILEGES; 在slave执行 1234CHANGE MASTER TO MASTER_HOST='172.20.0.2',MASTER_USER='slave1',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000003',MASTER_LOG_POS=964;# 开启主从start slave; 在从库执行 1show slave status; 主要看Slave_io_running =yes 主要看Slave_sql_running =yes 读写分离scheml.xmlschema标签1234567<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1"> <!-- auto sharding by id (long) --> <!--splitTableNames 启用<table name 属性使用逗号分割配置多个表,即多个表使用这个配置--> <table name="travelrecord,address" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" splitTableNames ="true"/> <!-- <table name="oc_call" primaryKey="ID" dataNode="dn1$0-743" rule="latest-month-calldate" /> --></schema> name: 配置逻辑库的名称 checkSQLschema: 当执行select * from T_Account.user 为true时,mycat会把sql转成select * from user false 会报错 sqlMaxLimit:最大查询每页条数,如果sql指定了limit,就以sql为准,没有,就以配置为准 randomDataNode:这个先不用,先改成datanote='dn1' datanote标签123<dataNode name="dn1" dataHost="localhost1" database="db1" /><dataNode name="dn2" dataHost="localhost1" database="db2" /><dataNode name="dn3" dataHost="localhost1" database="db3" /> name:数据分片节点名称 dataHost:定义该数据分片节点属于哪台数据库主机 database:指定哪个数据库,db_java1234 datahost标签123456789<dataHost name="localhost1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM1" url="localhost:3306" user="root" password="123456"> </writeHost> <!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> --></dataHost> name:跟datanote标签的datahost对应 maxCon 指定每个读写实例连接池的的最大链接,write合readhost标签都会使用这个属性的值来实例化出连接池的最大连接数 min 最小的连接 balance: 0 不开启读写分离,所有读操作都发送到当前可用的writehost上 1 2 所有读操作都随机在writehost合readehost上分发 3 所有读请求随机分发到writehost合readhos 双组双从第二套主从1.在mysql添加中添加 123log-slave-update=1auto-increment-increment=2auto-increment-offset=1 2.把mysql文件复制一份mysql3 3.把mysql3的配置文件修修改这些配置 123# 主服务器ID,必须唯一server-id=5auto-increment-offset=2 4.再创建两个容器,m2 和 s2 master 和 master同步就是相互slave 测试读写分离插入语句 1insert into test valus(@@hostname)
Spring Security
Spring Security1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency> 登录业务 1234567891011121314151617181920212223242526272829303132333435363738@Servicepublic class LoginService { public static Map<String, RedisUser> map = new HashMap<>(); @Resource AuthenticationManager authenticationManager; public String login(SysUser sysUser) { // AuthenticationManager 进行用户认证 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword()); Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken); // 如果认证不通过,给出对应的提示 if (Objects.isNull(authenticate)) { throw new RuntimeException("登录失败"); } // 如果认证通过,使用username给jwt生成一个 RedisUser principal = (RedisUser) authenticate.getPrincipal(); map.put(principal.getUsername(), principal); return createJWT("1234567", 9990000, principal.getUsername()); } public void logout() { UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); RedisUser principal = (RedisUser) authentication.getPrincipal(); String username = principal.getUsername(); // 从redis里删除 map.remove(username); }} 123456789101112131415161718192021222324@RestController@RequestMapping("/")public class LoginController { @Resource LoginService loginService; @PostMapping("/user/login") public ResponseEntity<HashMap> login(@RequestParam("username") String username,@RequestParam("password") String password){ SysUser sysUser = new SysUser(); sysUser.setUsername(username); sysUser.setPassword(password); HashMap<String, Object> stringObjectHashMap = new HashMap<>(); stringObjectHashMap.put("token", loginService.login(sysUser)); return ResponseEntity.status(200).body(stringObjectHashMap); } @PostMapping("/user/logout") public ResponseEntity<String> logout(){ loginService.logout(); return ResponseEntity.ok("success"); }} 验证账户 12345678910111213141516171819202122232425262728293031323334353637383940414243import com.example.spirngsecutirylearn.pojo.RedisUser;import com.example.spirngsecutirylearn.pojo.SysUser;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;import java.util.ArrayList;@Servicepublic class TestService implements UserDetailsService { static String user = "anthony"; static String password = "$2a$10$yyR9WuT9JY/bpe1VPU0yguqlv0lWpgzTD9NEetf2.n8y7NXIa1rfm"; /** * DaoAuthenticationProvier 会调用这个方法查询用户,并且返回UserDetails对象 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库查询用户信息 SysUser sysUser = new SysUser(); if (username.equals(user)) { sysUser.setUsername(user); sysUser.setPassword(password); }else { // 没有查到用户 throw new RuntimeException("没有查到用户信息"); } // 查询对应的权限信息 ArrayList<String> strings = new ArrayList<>();// strings.add("test"); strings.add("admin"); // 返回封装的信息 return new RedisUser(sysUser,strings); }} 缓存的用户对象 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263package com.example.spirngsecutirylearn.pojo;import lombok.AllArgsConstructor;import lombok.Data;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;import java.util.List;import java.util.stream.Collectors;@Datapublic class RedisUser implements UserDetails { SysUser sysUser; List<String> list; public RedisUser(SysUser sysUser, List<String> list) { this.sysUser = sysUser; this.list = list; } /** * 权限 * @return */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return list.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); } @Override public String getPassword() { return sysUser.getPassword(); } @Override public String getUsername() { return sysUser.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }} 认证失败的回调 123456789101112131415161718192021222324252627282930313233package com.example.spirngsecutirylearn.handler;import cn.hutool.json.JSONUtil;import org.springframework.http.ResponseEntity;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AuthException implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { ResponseEntity<String> responseEntity = ResponseEntity.ok().body("认证失败,请重新登录"); String str = JSONUtil.toJsonStr(responseEntity); /// 处理异常 extracted(response,str); } private static void extracted(HttpServletResponse response,String string) throws IOException { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().println(string); }} 授权失败的回调 123456789101112131415161718192021222324252627282930313233package com.example.spirngsecutirylearn.handler;import cn.hutool.json.JSONUtil;import org.springframework.http.ResponseEntity;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AccessException implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { ResponseEntity<String> responseEntity = ResponseEntity.ok().body("权限不租"); String str = JSONUtil.toJsonStr(responseEntity); /// 处理异常 extracted(response,str); } private static void extracted(HttpServletResponse response,String string) throws IOException { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().println(string); }} 开启注解权限控制和自定义注解 1234567891011121314151617181920@RestController@RequestMapping("/test")public class TestController { @GetMapping("/hello") @PreAuthorize("hasAnyAuthority('test')") public ResponseEntity<String> hello(){ return ResponseEntity.ok("hello server"); } /** * 自定义校验权限 * @return */ @GetMapping("/hello2") @PreAuthorize("@anthony.hasAnyAuthority('test')") public ResponseEntity<String> hello2(){ return ResponseEntity.ok("hello server2"); }} 配置 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100package com.example.spirngsecutirylearn.cofig;import com.example.spirngsecutirylearn.filter.JwtFilter;import com.example.spirngsecutirylearn.handler.AccessException;import com.example.spirngsecutirylearn.handler.AuthException;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.authentication.logout.LogoutFilter;import javax.annotation.Resource;@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource JwtFilter jwtFilter; @Resource AuthException authException; @Resource AccessException accessException; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity // CSRF禁用,因为不使用session .csrf().disable() // 基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() // 过滤请求 .authorizeRequests() // 对于登录login 注册register 验证码captchaImage 允许匿名访问 .antMatchers("/user/login").anonymous() // 静态资源,permitAll 有没有登录都访问// .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()// .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated();// .and()// .headers().frameOptions().disable(); // 添加Logout filter// httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);// // 添加JWT filter httpSecurity.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);// // 添加CORS filter// httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);// httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); // 添加自定义的认证和授权的自定义失败处理 httpSecurity.exceptionHandling().authenticationEntryPoint(authException).accessDeniedHandler(accessException); httpSecurity.cors(); } /** * 密码加密的规则 * @return */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * BCryptPasswordEncoder 的测试方法 * @param args */ public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); // 加密 String encode = bCryptPasswordEncoder.encode("123456"); System.out.println(encode); // 解密 boolean matches = bCryptPasswordEncoder.matches("123456", encode); System.out.println(matches); }} 自定义注解 123456789101112131415161718@Component("anthony")public class TestExpress { public boolean hasAnyAuthority(String authority){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); RedisUser principal = (RedisUser) authentication.getPrincipal();// List<String> list = (List<String>)principal.getAuthorities(); List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) principal.getAuthorities(); for (SimpleGrantedAuthority simpleGrantedAuthority : authorities) { if (simpleGrantedAuthority.getAuthority().equals(authority)) { return true; } } return false; }} 找Java包的路径 12# mac/usr/libexec/java_home -V