个人随笔
WebStorm破解激活

下载破解文件JetbrainsIdesCrack-3.4.jar将下载的文件复制到WebStorm安装文件夹下的bin文件夹下(在桌面上WebStorm快捷方式上右键【打开文件所在的位置】可直接进入)双击打开WebStorm进入激活页面,点选【Evaluateforfree】试用30天,进入应用界面在菜单【Help】->【EditcustomVMoptions..】点击打开配置文件.在配置文件最下方加入一行:-javaagent:D:\software\WebStorm2019.1.3\bin\JetbrainsIdesCrack-3.4.jar这里的文件路径具体指向到刚才复制的破解文件路径下在菜单【Help】->【Register...】点击打开激活页面,选择【licenseserver】激活方式,在下面输入框中填写:https://fls.jetbrains-agent.com点击【Activate】激活后,重启WebStorm生效.

工作日志
【基于ELK搭建日志分析系统】- 简单介绍

ELK日志分析系统介绍1.系统概述本系统为业务日志分析监控系统,使用ELK+Beats实现对系统业务日志的收集、存储、分析,业务系统运行期间将相关日志输出到一个指定的文件夹/文件内,使用FileBeat组件实现对日志文件夹/文件的监听,可以直接将新增的数据发往设定好的Logstash中过滤,或直接发往ElasticSearch分类存储,当系统运行出现问题时,运维人员可以使用Kibana对存储在ES中的日志数据根据相关字段搜索查找,Kibana也支持对数据进行相应的可视化图表展现。2.系统实现描述2.1采集-filebeat对于日志数据收集使用Filebeat部署在业务服务器后台监听日志文件的方式。Filebeat运行环境没有任何依赖,后台运行占用内存资源极低,相比于Logstash可以忽略不计,不影响服务器正常的业务。Filebeat可以运行在MacOS、Windows、Linux等系统下。Filebeat监听指定的文件(可以使用通配符),一旦文件中有新的一行内容追加则会将这条数据发往配置好的output路径。Filebeat的output支持logstash、ElastciSearch、file、console等,一般的不需要复杂过滤的可以直接发往ES存储,多节点日志采集也可以经过Logstash汇总过滤后,再存储进ES。在Filebaet运行过程中,每个Prospector的状态信息都会保存在内存里。当Filebeat进行了重启后,会从注册表文件里恢复重启之前的状态信息,让FIlebeat继续从之前已知的位置开始进行数据读取。2.2解析-Logstash对于数据解析主要包括:(1)对汇总多节点后的日志进行区分(2)将不规则格式数据转换为规则数据(3)将不符合格式要求的数据过滤去除因为Filebeat只支持简单的数据解析,对于日志的解析过滤整体可以使用Logstash。Logstash内置许多解析格式:grok、date、ip、json...,支持对不规则的数据字符串进行规则化输出,也能够在数据传输过程中添加或删除某些指定字段。将采集到的日志数据经过logstash过滤转换后发往ES建立索引存储。因为logstash占用内存资源较大(默认1G),为不影响业务尽量不部署在业务服务器上。2.3存储-ElasticSearch日志数据存储使用ElasticSearch,由logstash将过滤完成后的规则化数据存入ES指定索引中。ES有自动发现功能,初期使用ElasticSearch的单节点集群模式,后续想要添加节点只需指定elasticsearch集群名称保持一致,就能自动加入集群,ES就会按照配置将索引分片到新加入的节点上。2.4展现-Kibana(1)ELK中Kibana专门为ES中的数据提供可视化展现的,支持搜索、汇总计算,图表展现等。(2)ElasticSearch也提供有RESTAPI,支持调用接口的方式访问操作索引数据。(3)使用插件进行数据异常监控报警功能实现,如系统日志出现异常报错则可配置发送邮件通知相关人员。(4)Kibana也支持对系统日志进行可视化监控展现,包括CPU、内存、硬盘等。3.系统可用性测试系统运行过程中,logstash宕机:Filebeat会记录发送不成功的数据,并尝试连接logstash,成功连接后会再次将数据发往logstash,下图为再次发送成功后的日志。系统运行过程中,Filebeat宕机:在Filebaet运行过程中,每个Prospector的状态信息都会保存在内存里。当Filebeat进行了宕机重启后,会从注册表文件里恢复重启之前的状态信息,让FIlebeat继续从之前已读取的位置开始往后进行数据读取。系统运行过程中,ES集群宕机::::hljs-centerLogstash日志::::::hljs-centerFilebeat日志:::logstash没有数据存储功能,ES集群宕机,logstash数据无法发送,Filebeat会记录未成功发送的数据,同时logstash定时尝试连接ES,直到连接成功,数据会再次发送。

开发项目专栏
SpringBoot+Thymleaf项目初入(三) - 用户登录

1.新建数据库表ims-learn.user2.Model包下新建User类注:因为之前初始化项目时添加了lombok依赖,所以model类可以不用写setter/getter,直接使用@Data注解替代3.编写UserDao接口类4.UserMapper.xml<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="cn.coralcloud.ims.dao.UserDao"><resultMapid="userResultMap"type="User"><idproperty="id"column="id"/><resultproperty="createTime"column="create_time"/><resultproperty="updateTime"column="update_time"/></resultMap><selectid="login"resultMap="userResultMap">select*fromuserwhereemail=#{email}andpassword=#{password}</select></mapper>5.mybatis.xml编写mybatis基础配置,新建resources/mybatis/mybatis.xml<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEconfigurationPUBLIC"-//mybatis.org//DTDConfig3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><settings><settingname="logImpl"value="STDOUT_LOGGING"/><settingname="lazyLoadingEnabled"value="true"/><settingname="aggressiveLazyLoading"value="false"/><settingname="lazyLoadTriggerMethods"value=""/></settings></configuration>6.UserService.java7.UserController.javapackagecn.coralcloud.ims.controller;importcn.coralcloud.ims.model.User;importcn.coralcloud.ims.service.UserService;importcom.sun.org.apache.xpath.internal.operations.Mod;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Controller;importorg.springframework.ui.Model;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.servlet.ModelAndView;importorg.springframework.web.servlet.View;importjavax.servlet.http.HttpSession;/***@authorc-geff*@nameUserController*@description*@date2020-11-0310:45*/@Controller@RequestMapping("/user")publicclassUserController{@AutowiredprivateUserServiceuserService;@PostMapping("/login")publicModelAndViewlogin(Stringemail,Stringpassword,HttpSessionsession){ModelAndViewview=newModelAndView();view.setViewName("user/login");Useruser=userService.login(email,password);if(user!=null){session.setAttribute("AdminUserKey",user);view.setViewName("redirect:/index");returnview;}view.addObject("errmsg","用户名或密码错误!");returnview;}@GetMapping("/login")publicStringlogin(){return"user/login";}}8.login.ftl9.IndexController改造10.index.ftl改造,展示用户登录后的用户名11.数据库添加一条用户数据,然后在/user/login页面登录测试

工作日志
【基于ELK搭建日志分析系统】- Logstash安装

汇总节点安装Logstash。Logstash运行需要JDK环境,所以需要首先配置相应JAVA_HOME环境变量。下载与filebeat对应版本压缩包:官网地址根据操作系统选择对应的版本下载解压测试:与Filebeat配置连接,新建beat-logstash.conf:指定配置文件启动logstash(cd$LOGSTASH_HOME):bin/logstash-fconfig/beat-logstash.conf后台启动使用nohupnohup./bin/logstash-fconfig/beat-logstash.conf--config.reload.automatic>/dev/null2>&1&

大数据开发
全国建筑市场监管公共服务平台接口数据爬取

全国建筑市场监管公共服务平台(四库一平台)接口数据解密需要爬取建筑市场公司的不良行为记录进入服务平台页面http://jzsc.mohurd.gov.cn,点击顶部的搜索,发现返回的数据是经过加密的:1.寻找返回的数据既然数据是通过这个url返回的,全局搜索url:http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&pgsz=15尝试全局模糊搜索/query/comp/list,开发者工具切换到sources页签,CTRL+SHIFT+F进行全局搜索,然后点击进入JS函数,再格式化后的JS文件里面再次CTRL+F搜索:返回的结果是请求url/dataservice/query/comp/list得到的,打上断点,重新点击页面上的搜索,一步一步调试js代码:调试过程就不一步一步分析了,最终定位到,感觉像我们想要的数据,进入Console打印一下t和et中data是最初我们请求http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&pgsz=15所返回的数据e这其中的数据正是我们想要的数据2.分析加密方式既然我们已经知道了数据的加密方式,那我们就重点分析一下这个地方其中t.data我们在第一步已经分出来了使我们第一步请求http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&pgsz=15得到的结果那我们重点分析p函数的处理过程,点击进入p函数,结果如下:对数据经过层层加密处理后,调用toString方法,既然加密函数已经找到,我们就可以编写JS代码了3.代码实现我们将函数p的代码复制出来,data是加密后返回的数据,我们先复制出来用一下运行一下项目报错的原因,其中u和d没有进行初始化我们寻找一下u和d,就在函数p的上方我们把u和d添加到代码中运行项目进行测试其中返回的数据,正是我们想要的结果4.Python实现到这里已经能成功使用JS解密返回的数据了,但是如果实现爬虫自动解析需要使用Python实现:Python中AES解密可以使用Crypto库实现,具体实现代码如下:defdecrypt(text):key='jo8j9wGw%6HbxfFn'vi='0123456789ABCDEF'#将请求返回的16进制数据转换为二进制数据text=binascii.a2b_hex(text)#构建解密对象cipher=AES.new(key.encode('utf8'),AES.MODE_CBC,vi.encode('utf8'))text_decrypted=cipher.decrypt(text)unpad=lambdas:s[0:-s[-1]]text_decrypted=unpad(text_decrypted)#去补位text_decrypted=text_decrypted.decode('utf8')returntext_decrypted5.最后基于Python的requests实现了简单的请求+解密代码#-*-coding:utf-8-*-importjsonimportrequestsimportbinasciifromCrypto.CipherimportAESdefdecrypt(text):key='jo8j9wGw%6HbxfFn'vi='0123456789ABCDEF'#将请求返回的16进制数据转换为二进制数据text=binascii.a2b_hex(text)#构建解密对象cipher=AES.new(key.encode('utf8'),AES.MODE_CBC,vi.encode('utf8'))text_decrypted=cipher.decrypt(text)unpad=lambdas:s[0:-s[-1]]text_decrypted=unpad(text_decrypted)#去补位text_decrypted=text_decrypted.decode('utf8')returntext_decryptedif__name__=='__main__':headers={'User-Agent':'Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/74.0.3729.108Safari/537.36'}#获取到当前搜索结果,并解密data=requests.get('http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&pgsz=15&total=0&complexname=',headers=headers).textres=json.loads(decrypt(data))print(res)6.注意本文参考地址:https://www.cnblogs.com/mingyangliang/p/11875925.html平台具有防爬取策略,频繁爬取会导致封IP,可以使用IP代理,或者设置爬取间隔在1.5s所爬取数据请勿用于非法用途

大数据开发
Centos安装ApacheHadoop2.7.7

Centos安装ApacheHadoop2.7.7准备文件:hadoop-2.7.7.tar.gz一、安装JDK安装Hadoop之前需要保证JDK8成功安装java-version二、上传Hadoop压缩包上传成功后解压文件:tar-zxvfhadoop-2.7.7.tar.gz假设解压后的Hadoop目录为:/home/hadoop/hadoop-2.7.7注意配置HADOOP_HOME环境变量exportHADOOP_HOME=/home/hadoop/hadoop-2.7.7exportPATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin三、配置服务器免密登录$ssh-keygen-trsa-P''-f~/.ssh/id_rsa$cat~/.ssh/id_rsa.pub>>~/.ssh/authorized_keys$chmod0600~/.ssh/authorized_keys执行完成后:sshmaster命令则能够跳过输入密码阶段直接连接四、修改配置==配置文件所在位置:$HADOOP_HOME/etc/hadoop==core-size.xml<configuration><property><name>fs.defaultFS</name><value>hdfs://master:9000</value></property></configuration>hdfs-size.xml<configuration><property><name>dfs.replication</name><value>1</value></property></configuration>mapred-site.xml<configuration><property><name>mapreduce.framework.name</name><value>yarn</value></property><property><name>mapreduce.application.classpath</name><value>$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/*:$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/lib/*</value></property></configuration>yarn-site.xml<configuration><property><name>yarn.nodemanager.aux-services</name><value>mapreduce_shuffle</value></property><property><name>yarn.nodemanager.env-whitelist</name><value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value></property></configuration>五、启动初始化HDFShdfsnamenode-format启动HDFSstart-dfs.sh启动YARNstart-yarn.sh启动成功后访问http://master:50070查看HDFS状态六、相关命令start-dfs.sh启动HDFS(namenode/datanode/SecondaryNamenode)stop-dfs.sh停止HDFSstart-yarn.sh启动Yarnstop-yarn.sh停止Yarnstart-all.sh启动HDFS和Yarnstop-all.sh停止HDFS和Yarn

Java开发
基于SpringSecurity的用户权限控制

1.说明本文主要介绍基于SpringSecurity的用户权限控制的简单实现。1.1环境版本SpringBoot:2.0.7SpringSecurity:5.0.10JDK:1.82.项目配置2.1引入maven包<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.7.RELEASE</version><relativePath/><!--lookupparentfromrepository--></parent><groupId>cn.coralcloud</groupId><artifactId>security</artifactId><version>0.0.1-SNAPSHOT</version><name>security</name><description>DemoSecurityprojectforSpringBoot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.9.9</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.60</version></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>22.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>2.2application.ymlserver.port:9091spring.application.name:spring-webspring.http.encoding.charset:utf8spring:session:store-type:redisredis:host:localhostport:6379password:123456datasource:driver-class-name:com.mysql.jdbc.Driverurl:jdbc:mysql://127.0.0.1:3306/security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername:rootpassword:123456servlet:multipart:max-file-size:1024MBmax-request-size:1024MBmybatis:mapper-locations:classpath*:mapper/*Mapper.xmltype-aliases-package:cn.coralcloud.security.model2.3数据库初始化脚本/*NavicatMySQLDataTransferSourceServer:localhostSourceServerVersion:50644SourceHost:localhost:3306SourceDatabase:securityTargetServerType:MYSQLTargetServerVersion:50644FileEncoding:65001Date:2019-12-0216:28:44*/SETFOREIGN_KEY_CHECKS=0;--------------------------------Tablestructureforrole------------------------------DROPTABLEIFEXISTS`role`;CREATETABLE`role`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`name`varchar(50)NOTNULL,`nickname`varchar(50)NOTNULL,`system`bit(1)NOTNULLDEFAULTb'0',`description`varchar(500)DEFAULTNULL,`permission`mediumtext,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=2DEFAULTCHARSET=utf8mb4;--------------------------------Tablestructureforuser------------------------------DROPTABLEIFEXISTS`user`;CREATETABLE`user`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`username`varchar(50)NOTNULL,`password`varchar(300)NOTNULL,`role`varchar(500)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=3DEFAULTCHARSET=utf8mb4;3.系统初始化文件3.1users.json[{"username":"admin","password":"ea48576f30be1669971699c09ad05c94","role":"ROLE_ADMINISTRATOR"}]3.2roles.json[{"name":"ROLE_ADMINISTRATOR","nickname":"管理员","description":"系统超级管理员,不允许用户更改","system":true,"permissions":[{"resourceId":"user","resourceName":"用户管理","privileges":{"list":"查看用户列表","add":"新增用户","update":"修改用户信息","delete":"删除用户"}},{"resourceId":"permission","resourceName":"权限","privileges":{"read":"查看权限","write":"新增权限","update":"更新权限","delete":"删除权限"}}]}]4.数据持久化DAO层4.1UserDao.javapackagecn.coralcloud.security.dao;importcn.coralcloud.security.model.User;importorg.apache.ibatis.annotations.Mapper;importorg.springframework.stereotype.Component;importjava.util.List;/***@authorgeff*@nameUserDao*@description*@date2019-11-2910:31*/@Component@MapperpublicinterfaceUserDao{/***根据用户名查找*@date2019/11/2915:24*@authorgeff*@paramusernameusername*@returncn.coralcloud.security.model.User*/UserfindByUsername(Stringusername);/***创建用户*@date2019/11/2915:24*@authorgeff*@paramuseruser*/voidsave(Useruser);/***获取用户列表*@date2019/11/2915:24*@authorgeff*@param*@returnjava.util.List<cn.coralcloud.security.model.User>*/List<User>list();}4.2RoleDao.javapackagecn.coralcloud.security.dao;importcn.coralcloud.security.model.Role;importorg.apache.ibatis.annotations.Mapper;importorg.springframework.stereotype.Component;/***@authorgeff*@nameRoleDao*@description*@date2019-11-2910:31*/@Component@MapperpublicinterfaceRoleDao{/***根据名称查找*@date2019/11/2915:23*@authorgeff*@paramnamename*@returncn.coralcloud.security.model.Role*/RolefindByName(Stringname);/***保存数据*@date2019/11/2915:23*@authorgeff*@paramrolerole*/voidsave(Rolerole);}4.3UserMapper.xml<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="cn.coralcloud.security.dao.UserDao"><resultMapid="userMapper"type="cn.coralcloud.security.model.User"><idproperty="id"column="id"/></resultMap><selectid="findByUsername"parameterType="String"resultMap="userMapper">select*from`user`where`username`=#{username}</select><selectid="list"resultType="cn.coralcloud.security.model.User">select*from`user`</select><insertid="save"parameterType="cn.coralcloud.security.model.User"useGeneratedKeys="true"keyProperty="id">insertinto`user`(username,password,role)values(#{username},#{password},#{role})</insert></mapper>4.4RoleMapper.xml<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="cn.coralcloud.security.dao.RoleDao"><resultMapid="roleMapper"type="cn.coralcloud.security.model.Role"><idproperty="id"column="id"/></resultMap><selectid="findByName"parameterType="String"resultMap="roleMapper">select*fromrolewhere`name`=#{name}</select><insertid="save"parameterType="cn.coralcloud.security.model.Role"useGeneratedKeys="true"keyProperty="id">insertintorole(name,nickname,description,system,permission)values(#{name},#{nickname},#{description},#{system},#{permission})</insert></mapper>5.处理用户权限认证逻辑5.1SpringSecurity配置要在项目中使用@PreAuthorize等注解实现方法级别权限控制,则需要在项目启动类上添加注解@EnableGlobalMethodSecurity(prePostEnabled=true),本文项目启动类:packagecn.coralcloud.security;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.boot.builder.SpringApplicationBuilder;importorg.springframework.boot.web.servlet.support.SpringBootServletInitializer;importorg.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;/***@authorgeff*/@SpringBootApplication@EnableGlobalMethodSecurity(prePostEnabled=true)publicclassSecurityApplicationextendsSpringBootServletInitializer{@OverrideprotectedSpringApplicationBuilderconfigure(SpringApplicationBuilderapplicationBuilder){returnapplicationBuilder.sources(SecurityApplication.class);}publicstaticvoidmain(String[]args){SpringApplication.run(SecurityApplication.class,args);}}5.2用户对象SpringSecurity自带的有UserDetails接口主要保存用户对象数据,所以我们的用户对象需要实现UserDetails接口packagecn.coralcloud.security.model;importcom.fasterxml.jackson.annotation.JsonIgnore;importlombok.Data;importlombok.NoArgsConstructor;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importjava.io.Serializable;importjava.util.*;/***@authorgeff*/@Data@NoArgsConstructorpublicclassUserimplementsUserDetails,Serializable{privateLongid;/***用户登录名*/privateStringusername;/***用户登录密码,用户的密码不应该暴露给客户端*/@JsonIgnoreprivateStringpassword;/***用户在系统中的角色列表,将根据角色对用户操作权限进行限制*/privateStringrole;privateList<Role>roles;@OverridepublicCollection<?extendsGrantedAuthority>getAuthorities(){Collection<GrantedAuthority>authorities=newArrayList<>();if(roles!=null){for(Rolerole:roles){if(role==null){continue;}for(Permissionpermission:role.getPermissions()){for(Stringprivilege:permission.getPrivileges().keySet()){authorities.add(newSimpleGrantedAuthority(String.format("%s-%s",permission.getResourceId(),privilege)));}}}}returnauthorities;}@OverridepublicbooleanisAccountNonExpired(){returntrue;}@OverridepublicbooleanisAccountNonLocked(){returntrue;}@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}@OverridepublicbooleanisEnabled(){returntrue;}}在用户对象的publicCollection<?extendsGrantedAuthority>getAuthorities()方法中,需要根据用户当前角色生成当前用户权限列表,本文权限机制使用resourceId-privilege方式5.3角色对象Role.javapackagecn.coralcloud.security.model;importcom.alibaba.fastjson.JSON;importlombok.Data;importorg.springframework.util.StringUtils;importjava.io.Serializable;importjava.util.List;/***@authorgeff*@nameRole*@description*@date2019-11-2910:08*/@DatapublicclassRoleimplementsSerializable{privateLongid;/***角色名,用于权限校验*/privateStringname;/***角色中文名,用于显示*/privateStringnickname;/***角色描述信息*/privateStringdescription;/***是否为内置*/privateBooleansystem;/***角色可进行的操作列表*/privateList<Permission>permissions;privateStringpermission;/***SpringSecurity4.0以上版本角色都默认以'ROLE_'开头*@paramnamename*/publicvoidsetName(Stringname){if(!name.contains("ROLE_")){this.name="ROLE_"+name;}else{this.name=name;}}publicList<Permission>getPermissions(){if(permissions==null){if(!StringUtils.isEmpty(permission)){this.permissions=JSON.parseArray(permission,Permission.class);}}returnpermissions;}publicStringgetPermission(){if(StringUtils.isEmpty(permission)){this.permission=JSON.toJSONString(permissions);}returnpermission;}}SpringSecurity4.0以上所有的角色名称默认都要以ROLE_开头,所有本文在获取角色名称是会自动添加前缀。为了简单方便,本文角色对象的permission字段保存着该角色的所有权限列表的JSON字符串5.4权限对象Permission.javapackagecn.coralcloud.security.model;importlombok.Data;importjava.io.Serializable;importjava.util.Map;/***@authorgeff*@namePermission*@description*@date2019-11-2910:07*/@DatapublicclassPermissionimplementsSerializable{privateStringresourceId;privateStringresourceName;privateMap<String,String>privileges;privatebooleanabandon=false;}5.5统一数据返回对象因为是前后端分离项目,所以本文封装了统一数据返回对象Response类packagecn.coralcloud.security.model;importjava.io.Serializable;/***响应*@authorgeff*/publicclassResponse<T>implementsSerializable{privatefinalstaticintSUCCESS=0;privatefinalstaticintERROR=-1;privateintcode;privateTdata;privateStringmessage;publicintgetCode(){returncode;}publicvoidsetCode(intcode){this.code=code;}publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicTgetData(){returndata;}publicvoidsetData(Tdata){this.data=data;}publicstatic<T>Response<T>ok(){Response<T>response=newResponse<>();response.setCode(SUCCESS);returnresponse;}publicstatic<T>Response<T>ok(Tdata){Response<T>response=newResponse<>();response.setCode(SUCCESS);response.setData(data);returnresponse;}publicstatic<T>Response<T>fail(Stringmessage){Response<T>response=newResponse<>();response.setCode(ERROR);response.setMessage(message);returnresponse;}publicstatic<T>Response<T>fail(intcode,Stringmessage){Response<T>response=newResponse<>();response.setCode(code);response.setMessage(message);returnresponse;}}5.6UserDetailsService接口获取用户信息SpringSecurity同时在用户登录认证时会通过调用UserDetailsService的loadUserByUsername来获取当前登录的用户信息,当用户认证通过后会将用户对象保存到自定义的Token对象中。所以本文需要实现UserDetailsService接口,完成loadUserByUsername方法:packagecn.coralcloud.security.service;importcn.coralcloud.security.dao.RoleDao;importcn.coralcloud.security.model.Role;importcn.coralcloud.security.model.User;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.stereotype.Service;importorg.springframework.util.StringUtils;importjava.util.ArrayList;importjava.util.List;/***@authorgeff*/@Service("myUserDetailsService")publicclassUserDetailsServiceImplimplementsUserDetailsService{@AutowiredprivateUserServiceuserService;@AutowiredprivateRoleDaoroleDao;@OverridepublicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{Useruser=userService.findByUsername(username);if(user==null){thrownewUsernameNotFoundException(String.format("Nouserfoundwithusername:%s",username));}if(!StringUtils.isEmpty(user.getRole())){String[]roles=user.getRole().split(",");List<Role>roleList=newArrayList<>();for(StringroleName:roles){Rolerole=roleDao.findByName(roleName);roleList.add(role);}user.setRoles(roleList);}returnuser;}}5.7自定义方法权限处理器自定义方法权限处理器需要实现PermissionEvaluator接口,完成publicbooleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission)方法。packagecn.coralcloud.security.config;importcn.coralcloud.security.model.User;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.access.PermissionEvaluator;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.GrantedAuthority;importjava.io.Serializable;/***@authorgeff*/@ConfigurationpublicclassMyPermissionEvaluatorimplementsPermissionEvaluator{@OverridepublicbooleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission){booleanaccessable=false;if(authentication.getPrincipal()instanceofUser){Stringprivilege=targetDomainObject+"-"+permission;for(GrantedAuthorityauthority:authentication.getAuthorities()){if(privilege.equalsIgnoreCase(authority.getAuthority())){accessable=true;break;}}returnaccessable;}returnfalse;}@OverridepublicbooleanhasPermission(Authenticationauthentication,SerializabletargetId,StringtargetType,Objectpermission){//TODOAuto-generatedmethodstubreturnfalse;}}本文通过根据resourceId-privilege方式验证权限5.8系统初始化配置类本文系统初始化配置类主要实现在系统启动时根据配置的users.json和roles.json自动生成初始化用户和角色信息。packagecn.coralcloud.security.component;importcn.coralcloud.security.dao.RoleDao;importcn.coralcloud.security.dao.UserDao;importcn.coralcloud.security.model.Role;importcn.coralcloud.security.model.User;importcom.google.gson.GsonBuilder;importcom.google.gson.reflect.TypeToken;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.beans.factory.annotation.Value;importjavax.annotation.PostConstruct;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.lang.reflect.Type;importjava.nio.charset.StandardCharsets;importjava.util.ArrayList;/***系统初始化配置类,主要用于加载内置数据到目标数据库上*@authorgeff*/@ComponentpublicclassSystemInitializer{@Value("${initialzation.file.users:users.json}")privateStringuserFileName;@Value("${initialzation.file.roles:roles.json}")privateStringroleFileName;@AutowiredprivateUserDaouserDao;@AutowiredprivateRoleDaoroleDao;@AutowiredprivateGsonBuildergsonBuilder;@PostConstructpublicbooleaninitialize(){try{InputStreamuserInputStream=getClass().getClassLoader().getResourceAsStream(userFileName);if(userInputStream==null){thrownewException("initialzationuserfilenotfound:"+userFileName);}InputStreamroleInputStream=getClass().getClassLoader().getResourceAsStream(roleFileName);if(roleInputStream==null){thrownewException("initialzationrolefilenotfound:"+roleFileName);}//导入初始的系统超级管理员角色TyperoleTokenType=newTypeToken<ArrayList<Role>>(){}.getType();ArrayList<Role>roles=gsonBuilder.create().fromJson(newInputStreamReader(roleInputStream,StandardCharsets.UTF_8),roleTokenType);for(Rolerole:roles){if(roleDao.findByName(role.getName())==null){roleDao.save(role);}}//导入初始的系统管理员用户TypeteacherTokenType=newTypeToken<ArrayList<User>>(){}.getType();ArrayList<User>users=gsonBuilder.create().fromJson(newInputStreamReader(userInputStream,StandardCharsets.UTF_8),teacherTokenType);for(Useruser:users){if(userDao.findByUsername(user.getUsername())==null){userDao.save(user);}}}catch(Exceptione){e.printStackTrace();}returntrue;}}6.用户认证相关自定义实现6.1.自定义用户认证过滤器​用户认证过滤器拦截用户发送的认证请求,然后从请求中获取用户账号和密码等认证信息并封装成一个未认证的AthenticationToken对象,然后调用AuthenticationManager对AthenticationToken进行认证。​自定义用户认证过滤器需要继承AbstractAuthenticationProcessingFilter,然后重写attemptAuthentication方法,在方法内部根据请求参数封装成未认证的AthenticationToken对象packagecn.coralcloud.security.component;importorg.springframework.security.authentication.AuthenticationServiceException;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;importorg.springframework.security.web.util.matcher.AntPathRequestMatcher;importorg.springframework.util.StringUtils;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;/***自定义的用户名密码认证过滤器*@authorgeff*/publicclassAuthFilterextendsAbstractAuthenticationProcessingFilter{privatestaticStringhttpMethod="POST";publicAuthFilter(){/**设置该过滤器对POST请求/api/user/login进行拦截*/super(newAntPathRequestMatcher("/api/user/login",httpMethod));}@OverridepublicAuthenticationattemptAuthentication(HttpServletRequestrequest,HttpServletResponseresponse)throwsAuthenticationException{if(!request.getMethod().equals(httpMethod)){thrownewAuthenticationServiceException("Authenticationmethodnotsupported:"+request.getMethod());}else{/**从http请求中获取用户输入的用户名和密码信息*这里接收的是form形式的参数,如果要接收json形式的参数,修改这里即可*/Stringusername=this.obtainUsername(request);Stringpassword=this.obtainPassword(request);if(StringUtils.isEmpty(username)&&StringUtils.isEmpty(password)){thrownewUsernameNotFoundException("用户名或密码错误");}/**使用用户输入的用户名和密码信息创建一个未认证的用户认证Token*/AuthTokenauthRequest=newAuthToken(username,password);/**设置一些详情信息*/this.setDetails(request,authRequest);/**通过AuthenticationManager调用相应的AuthenticationProvider进行用户认证*/returnthis.getAuthenticationManager().authenticate(authRequest);}}privateStringobtainUsername(HttpServletRequestrequest){StringusernameParameter="username";returnrequest.getParameter(usernameParameter);}privateStringobtainPassword(HttpServletRequestrequest){StringpasswordParameter="password";returnrequest.getParameter(passwordParameter);}privatevoidsetDetails(HttpServletRequestrequest,AuthTokenauthRequest){authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}}自定义用户认证拦截器要在构造方法中指定拦截的认证请求(本文中是POST类型的/api/user/login请求,可根据需求设置),并在attemptAuthentication()方法中实现获取用户认证信息、封装AuthenticationToken对象、调用AuthenticationManager对AuthenticationToken进行认证等逻辑。6.2自定义用户认证处理器用户认证处理器主要是对用户提交的认证信息进行认证,SpringSecurity默认实现的认证处理器的认证处理逻辑并不一定符合所有的业务需求(例如,默认的认证处理无法处理验证码),因此,可以自定义用户认证处理器。自定义的用户认证处理器,需要实现AuthenticationProvider接口,主要是实现publicAuthenticationauthenticate(Authenticationauthentication)方法和publicbooleansupports(Class<?>authentication)方法,前者主要是实现具体的认证逻辑,后者主要是指定认证处理器能对哪种AuthenticationToken对象进行认证。packagecn.coralcloud.security.component;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.authentication.AuthenticationProvider;importorg.springframework.security.authentication.BadCredentialsException;importorg.springframework.security.authentication.dao.DaoAuthenticationProvider;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;importorg.springframework.stereotype.Component;/***@authorgeff*@nameAuthProvider*@description*登录认证的Provider,自定义实现了{@linkAuthenticationProvider}<br>*Provider默认实现是{@linkDaoAuthenticationProvider}<br>*{@linkUsernamePasswordAuthenticationFilter}调用=>{@linkAuthenticationManager}=>{@linkAuthenticationProvider}验证<br>**@date2019-11-2915:52*/@Slf4j@ComponentpublicclassAuthProviderimplementsAuthenticationProvider{privatefinalUserDetailsServiceuserDetailService;privatefinalPasswordEncoderpasswordEncoder;@AutowiredpublicAuthProvider(@Qualifier("myUserDetailsService")UserDetailsServiceuserDetailService,PasswordEncoderpasswordEncoder){this.userDetailService=userDetailService;this.passwordEncoder=passwordEncoder;}@OverridepublicAuthenticationauthenticate(Authenticationauthentication)throwsAuthenticationException{/**将未认证的Authentication转换成自定义的用户认证Token*/AuthTokenauthenticationToken=(AuthToken)authentication;/**根据用户Token中的用户名查找用户信息,如果有该用户信息,则验证用户密码是否正确*/UserDetailsuser=userDetailService.loadUserByUsername((String)(authenticationToken.getPrincipal()));if(user==null){thrownewBadCredentialsException("用户名或密码不正确");}elseif(!this.passwordEncoder.matches((CharSequence)authenticationToken.getCredentials(),user.getPassword())){thrownewBadCredentialsException("用户名或密码不正确");}/**认证成功则创建一个已认证的用户认证Token*/AuthTokenauthenticationResult=newAuthToken(user,user.getPassword(),user.getAuthorities());/**设置一些详情信息*/authenticationResult.setDetails(authenticationToken.getDetails());returnauthenticationResult;}/***是否支持处理当前Authentication对象类似*/@Overridepublicbooleansupports(Class<?>authentication){returntrue;}}6.3自定义用户认证对象用户认证对象是在用户认证拦截器中创建的,在用户认证处理器中使用的。用户认证对象(AuthenticationToken)中封装的是用户认证信息,例如UsernamePasswordAuthenticationToken中封装的是用户名和密码。实际业务中,可能需要根据不同的用户信息进行认证(例如,手机号和验证码),此时就需要自定义用户认证对象。自定义的用户认证对象,需要继承AbstractAuthenticationToken类,并设定根据认证时使用的是哪些信息。packagecn.coralcloud.security.component;importorg.springframework.security.authentication.AbstractAuthenticationToken;importorg.springframework.security.core.GrantedAuthority;importjava.util.Collection;/***自定义的用户名密码认证对象*@authorgeff*/publicclassAuthTokenextendsAbstractAuthenticationToken{/***用户名*/privatefinalObjectprincipal;/***密码*/privateObjectcredentials;/***创建未认证的用户名密码认证对象*/publicAuthToken(Objectprincipal,Objectcredentials){super(null);this.principal=principal;this.credentials=credentials;this.setAuthenticated(false);}/***创建已认证的用户密码认证对象*/publicAuthToken(Objectprincipal,Objectcredentials,Collection<?extendsGrantedAuthority>authorities){super(authorities);this.principal=principal;this.credentials=credentials;super.setAuthenticated(true);}@OverridepublicObjectgetCredentials(){returnthis.credentials;}@OverridepublicObjectgetPrincipal(){returnthis.principal;}@OverridepublicvoidsetAuthenticated(booleanisAuthenticated)throwsIllegalArgumentException{if(isAuthenticated){thrownewIllegalArgumentException("Cannotsetthistokentotrusted-useconstructorwhichtakesaGrantedAuthoritylistinstead");}else{super.setAuthenticated(false);}}@OverridepublicvoideraseCredentials(){super.eraseCredentials();this.credentials=null;}}6.4自定义用户认证成功处理器用户认证成功处理器在用户认证成功之后调用,主要是执行一些额外的操作(例如,操作Cookie、页面跳转等)。自定义的用户认证成功处理器可以通过实现AuthenticationSuccessHandler接口,或者通过继承AbstractAuthenticationTargetUrlRequestHandler类及其子类来实现。本文自定义的用户认证成功处理器是通过继承AbstractAuthenticationTargetUrlRequestHandler的子类SavedRequestAwareAuthenticationSuccessHandler来实现的。packagecn.coralcloud.security.component;importcn.coralcloud.security.model.User;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.Authentication;importorg.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjava.io.IOException;/***自定义的用户认证成功处理器*@authorgeff*/@Component@Slf4jpublicclassAuthSuccessHandlerextendsSavedRequestAwareAuthenticationSuccessHandler{@AutowiredprivateObjectMapperobjectMapper;publicAuthSuccessHandler(){}@OverridepublicvoidonAuthenticationSuccess(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication)throwsIOException{//认证成功返回jsonUseruser=(User)authentication.getPrincipal();//写入session?HttpSessionsession=request.getSession();session.setAttribute("User",user);StringjsonStr=objectMapper.writeValueAsString(user);log.info("认证成功:{}",jsonStr);response.setContentType("application/json;charset=UTF-8");response.getWriter().write(jsonStr);}}6.5自定义用户认证失败处理器用户认证失败处理器是在用户认证失败之后调用,主要是执行一些额外的操作(例如操作Cookie、页面跳转、返回错误信息等)。自定义的用户认证失败处理器可以通过实现AuthenticationFailureHandler接口,或者通过继承AuthenticationFailureHandler接口的其它实现类来实现。本文自定义的用户认证失败处理器是通过继承AuthenticationFailureHandler接口的实现类SimpleUrlAuthenticationFailureHandler来实现的。packagecn.coralcloud.security.component;importcn.coralcloud.security.model.Response;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.HttpStatus;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***自定义的用户认证失败处理器*@authorgeff*/@Component@Slf4jpublicclassAuthFailureHandlerextendsSimpleUrlAuthenticationFailureHandler{@AutowiredprivateObjectMapperobjectMapper;@OverridepublicvoidonAuthenticationFailure(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationExceptionexception)throwsIOException{/**返回JSON*/log.error("认证失败:{}",exception.getMessage());Responseres=Response.fail(-1,exception.getMessage());response.setStatus(HttpStatus.OK.value());response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(res));response.getWriter().flush();}}6.6自定义访问拒绝处理器自定义访问拒绝处理器用来解决认证过的用户访问无权限资源时的异常。前后端分离的情况下可以通过自定义访问拒绝处理器实现JSON格式的数据返回,自定义访问拒绝处理器通过实现AccessDeniedHandler接口,然后实现publicvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,AccessDeniedExceptione)方法,在方法内部处理返回数据。packagecn.coralcloud.security.component;importcn.coralcloud.security.model.Response;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.HttpStatus;importorg.springframework.security.access.AccessDeniedException;importorg.springframework.security.web.access.AccessDeniedHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@authorgeff*@nameAuthAccessDeniedHandler*@description*@date2019-11-2916:57*/@Slf4j@ComponentpublicclassAuthAccessDeniedHandlerimplementsAccessDeniedHandler{@AutowiredprivateObjectMapperobjectMapper;@Overridepublicvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,AccessDeniedExceptione)throwsIOException{log.error("无权访问:{}",e.getMessage());Responseres=Response.fail(401,"无权访问");response.setStatus(HttpStatus.UNAUTHORIZED.value());response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(res));response.getWriter().flush();}}6.7自定义加密类本文密码使用Md5(password,salt)的形式,所以需要自定义SpringSecurity加密类,然后再Config配置类注入自定义加密类需要实现PasswordEncoder,完成encode和matches方法packagecn.coralcloud.security.component;importcn.coralcloud.security.utils.SecretUtils;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.util.StringUtils;importjava.util.Objects;/***@authorgeff*@nameMd5SaltPasswordEncoder*@description*@date2019-12-0209:27*/publicclassMd5SaltPasswordEncoderimplementsPasswordEncoder{@OverridepublicStringencode(CharSequencecharSequence){Stringstring=charSequence.toString();String[]array=string.split(",");Stringsalt="";if(array.length>1){salt=array[1];}returnSecretUtils.md5(array[0],salt);}@Overridepublicbooleanmatches(CharSequencecharSequence,Strings){if(!StringUtils.isEmpty(s)){StringencodePassword=encode(charSequence);returnObjects.equals(encodePassword,s);}returnfalse;}}7.SpringSecurity相关配置类7.1自定义的用户名密码认证配置类packagecn.coralcloud.security.component;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.config.annotation.SecurityConfigurerAdapter;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.web.DefaultSecurityFilterChain;importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;importorg.springframework.stereotype.Component;/***自定义的用户名密码认证配置类*@authorgeff*/@ComponentpublicclassAuthConfigextendsSecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity>{@AutowiredAuthProviderprovider;@AutowiredAuthSuccessHandlerauthSuccessHandler;@AutowiredAuthFailureHandlerauthFailureHandler;@Overridepublicvoidconfigure(HttpSecurityhttp){AuthFilterauthFilter=newAuthFilter();/**自定义用户认证处理逻辑时,需要指定AuthenticationManager,否则无法认证*/authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));/**指定自定义的认证成功和失败的处理器*/authFilter.setAuthenticationSuccessHandler(authSuccessHandler);authFilter.setAuthenticationFailureHandler(authFailureHandler);/**把自定义的用户名密码认证过滤器和处理器添加到UsernamePasswordAuthenticationFilter过滤器之前*/http.authenticationProvider(provider).addFilterBefore(authFilter,UsernamePasswordAuthenticationFilter.class);}}7.2SpringSecurity核心配置类packagecn.coralcloud.security.config;importcn.coralcloud.security.component.AuthAccessDeniedHandler;importcn.coralcloud.security.component.AuthConfig;importcn.coralcloud.security.component.Md5SaltPasswordEncoder;importcn.coralcloud.security.model.Response;importcn.coralcloud.security.service.UserDetailsServiceImpl;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.crypto.password.PasswordEncoder;importjava.io.PrintWriter;/***@authorgeff*/@Configuration@EnableWebSecurity@Slf4jpublicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredprivateAuthConfigauthConfig;@AutowiredprivateAuthAccessDeniedHandleraccessDeniedHandler;@Overrideprotectedvoidconfigure(HttpSecurityhttpSecurity)throwsException{httpSecurity.apply(authConfig).and().authorizeRequests().antMatchers("/static/**","/api/user/login").permitAll().anyRequest().authenticated().and().csrf().disable();httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint((request,response,e)->{response.setContentType("application/json;charset=utf-8");PrintWriterout=response.getWriter();Responseres=Response.fail(-14,"会话超时,请重新登录!");out.write(newObjectMapper().writeValueAsString(res));out.flush();out.close();});}@AutowiredpublicvoidconfigureGlobal(AuthenticationManagerBuilderauth)throwsException{auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());}@BeanpublicPasswordEncoderpasswordEncoder(){returnnewMd5SaltPasswordEncoder();}@Bean@OverridepublicUserDetailsServiceuserDetailsService(){returnnewUserDetailsServiceImpl();}}本文通过自定义AuthenticationEntryPoint来解决匿名用户访问无权限资源时的异常

Tags: JAVA
转载分享
Spring Boot 集成Sharding-jdbc + Mybatis-Plus实现分库分表

一、Sharding-jdbc简介Sharding-jdbc是开源的数据库操作中间件;定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。官方文档地址:https://shardingsphere.apache.org/document/current/cn/overview/本文demo实现了分库分表功能。作者能力有限,如有错误,欢迎各位在评论中指出。不胜感激!二、项目结构首先创建一个一般的Springboot项目,项目采用三层架构,结构图如下:项目目录结构图POM.xml文件如下:<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.6.RELEASE</version><relativePath/><!--lookupparentfromrepository--></parent><groupId>com.macky</groupId><artifactId>spring-boot-shardingjdbc</artifactId><version>0.0.1-SNAPSHOT</version><name>spring-boot-shardingjdbc</name><description>Demoprojectforspring-boot-shardingjdbc</description><properties><java.version>1.8</java.version></properties><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><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--Mybatis-Plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.1</version></dependency><!--shardingspherestart--><!--forspringboot--><dependency><groupId>io.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>3.1.0</version></dependency><!--forspringnamespace--><dependency><groupId>io.shardingsphere</groupId><artifactId>sharding-jdbc-spring-namespace</artifactId><version>3.1.0</version></dependency><!--shardingsphereend--><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>实体类以书本为例packagecom.macky.springbootshardingjdbc.entity;importcom.baomidou.mybatisplus.annotation.TableName;importcom.baomidou.mybatisplus.extension.activerecord.Model;importgroovy.transform.EqualsAndHashCode;importlombok.Data;importlombok.experimental.Accessors;/***@authorMacky*@TitleclassBook*@Description:书籍是实体类*@date2019/7/1315:23*/@Data@EqualsAndHashCode(callSuper=true)@Accessors(chain=true)@TableName("book")publicclassBookextendsModel<Book>{privateintid;privateStringname;privateintcount;}开放保存和查询两个接口,代码如下:packagecom.macky.springbootshardingjdbc.controller;importcom.macky.springbootshardingjdbc.entity.Book;importcom.macky.springbootshardingjdbc.service.BookService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.*;importjava.util.List;/***@authorMacky*@TitleclassBookController*@Description:TODO*@date2019/7/1220:53*/@RestControllerpublicclassBookController{@AutowiredBookServicebookService;@RequestMapping(value="/book",method=RequestMethod.GET)publicList<Book>getItems(){returnbookService.getBookList();}@RequestMapping(value="/book",method=RequestMethod.POST)publicBooleansaveItem(Bookbook){returnbookService.save(book);}}BookServiceImpl.javapackagecom.macky.springbootshardingjdbc.service.impl;importcom.baomidou.mybatisplus.core.toolkit.Wrappers;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importcom.macky.springbootshardingjdbc.entity.Book;importcom.macky.springbootshardingjdbc.mapper.BookMapper;importcom.macky.springbootshardingjdbc.service.BookService;importorg.springframework.stereotype.Service;importjava.util.List;/***@authorMacky*@TitleclassBookServiceImpl*@Description:TODO*@date2019/7/1220:47*/@ServicepublicclassBookServiceImplextendsServiceImpl<BookMapper,Book>implementsBookService{@OverridepublicList<Book>getBookList(){returnbaseMapper.selectList(Wrappers.<Book>lambdaQuery());}@Overridepublicbooleansave(Bookbook){returnsuper.save(book);}}BookMapper.javapackagecom.macky.springbootshardingjdbc.mapper;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.macky.springbootshardingjdbc.entity.Book;/***@authorMacky*@TitleclassBookMapper*@Description:TODO*@date2019/7/1220:46*/publicinterfaceBookMapperextendsBaseMapper<Book>{}创建数据库表,DDL语句如下:#创建数据库表数据CREATEDATABASEIFNOTEXISTS`db0`;USE`db0`;DROPTABLEIFEXISTS`book_0`;CREATETABLE`book_0`(`id`INT(11)NOTNULL,`name`VARCHAR(255)DEFAULTNULL,`count`INT(11)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=INNODBDEFAULTCHARSET=utf8mb4;DROPTABLEIFEXISTS`book_1`;CREATETABLE`book_1`(`id`INT(11)NOTNULL,`name`VARCHAR(255)DEFAULTNULL,`count`INT(11)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=INNODBDEFAULTCHARSET=utf8mb4;CREATEDATABASEIFNOTEXISTS`db1`;USE`db1`;DROPTABLEIFEXISTS`book_0`;CREATETABLE`book_0`(`id`INT(11)NOTNULL,`name`VARCHAR(255)DEFAULTNULL,`count`INT(11)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=INNODBDEFAULTCHARSET=utf8mb4;DROPTABLEIFEXISTS`book_1`;CREATETABLE`book_1`(`id`INT(11)NOTNULL,`name`VARCHAR(255)DEFAULTNULL,`count`INT(11)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=INNODBDEFAULTCHARSET=utf8mb4;CREATEDATABASEIFNOTEXISTS`db2`;USE`db2`;DROPTABLEIFEXISTS`book_0`;CREATETABLE`book_0`(`id`INT(11)NOTNULL,`name`VARCHAR(255)DEFAULTNULL,`count`INT(11)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=INNODBDEFAULTCHARSET=utf8mb4;DROPTABLEIFEXISTS`book_1`;CREATETABLE`book_1`(`id`INT(11)NOTNULL,`name`VARCHAR(255)DEFAULTNULL,`count`INT(11)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=INNODBDEFAULTCHARSET=utf8mb4;配置分库分表策略application.properties:#数据源db0,db1,db2sharding.jdbc.datasource.names=db0,db1,db2#第一个数据库sharding.jdbc.datasource.db0.type=com.zaxxer.hikari.HikariDataSourcesharding.jdbc.datasource.db0.driver-class-name=com.mysql.cj.jdbc.Driversharding.jdbc.datasource.db0.jdbc-url=jdbc:mysql://localhost:3306/db0?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTCsharding.jdbc.datasource.db0.username=rootsharding.jdbc.datasource.db0.password=Aa123456#第二个数据库sharding.jdbc.datasource.db1.type=com.zaxxer.hikari.HikariDataSourcesharding.jdbc.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driversharding.jdbc.datasource.db1.jdbc-url=jdbc:mysql://localhost:3306/db1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTCsharding.jdbc.datasource.db1.username=rootsharding.jdbc.datasource.db1.password=Aa123456#第三个数据库sharding.jdbc.datasource.db2.type=com.zaxxer.hikari.HikariDataSourcesharding.jdbc.datasource.db2.driver-class-name=com.mysql.cj.jdbc.Driversharding.jdbc.datasource.db2.jdbc-url=jdbc:mysql://localhost:3306/db2?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTCsharding.jdbc.datasource.db2.username=rootsharding.jdbc.datasource.db2.password=Aa123456#水平拆分的数据库(表)配置分库+分表策略行表达式分片策略#分库策略sharding.jdbc.config.sharding.default-database-strategy.inline.sharding-column=idsharding.jdbc.config.sharding.default-database-strategy.inline.algorithm-expression=db$->{id%3}#分表策略其中book为逻辑表分表主要取决于id行sharding.jdbc.config.sharding.tables.book.actual-data-nodes=db$->{0..2}.book_$->{0..2}sharding.jdbc.config.sharding.tables.book.table-strategy.inline.sharding-column=count#分片算法表达式sharding.jdbc.config.sharding.tables.book.table-strategy.inline.algorithm-expression=book_$->{count%3}#主键UUID18位数如果是分布式还要进行一个设置防止主键重复#sharding.jdbc.config.sharding.tables.user.key-generator-column-name=id#打印执行的数据库以及语句sharding.jdbc.config.props..sql.show=truespring.main.allow-bean-definition-overriding=true#读写分离sharding.jdbc.datasource.dsmaster=接口测试使用postman示例:GET请求------>http://localhost:8080/bookPOST请求:------->http://localhost:8080/book?id=1&name=java编程思想&count=8demo的github地址:https://github.com/Macky-He/spring-boot--shardingsphere-examples如各位觉得有帮助的话,还请给个star鼓励鼓励博主,谢谢!三、总结分库分表实现按照官方文档做一个demo是第一步,如需深入还需要研究源码,研究架构,研究思想;此文仅作为入门demo搭建指南,如需深入理解,还请移步至官方文档。参考资料1.官方文档:https://shardingsphere.apache.org/document/current/cn/manual/sharding-jdbc/usage/sharding/

Tags: JAVA
开发项目专栏
SpringBoot+Thymleaf项目初入(五) - 图片验证码

1.验证码工具类1.新建包utils2.新建VerifyCodeUtil类packagecn.coralcloud.ims.utils;importjavax.imageio.ImageIO;importjava.awt.*;importjava.awt.geom.AffineTransform;importjava.awt.image.BufferedImage;importjava.io.File;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.OutputStream;importjava.util.Arrays;importjava.util.Random;/***@authorc-geff*/publicclassVerifyCodeUtil{/***使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符*/privatestaticfinalStringVERIFY_CODES="23456789ABCDEFGHJKLMNPQRSTUVWXYZ";privatestaticRandomrandom=newRandom();/***使用系统默认字符源生成验证码*@paramverifySize验证码长度*@return*/publicstaticStringgenerateVerifyCode(intverifySize){returngenerateVerifyCode(verifySize,VERIFY_CODES);}/***使用指定源生成验证码*@paramverifySize验证码长度*@paramsources验证码字符源*@return*/publicstaticStringgenerateVerifyCode(intverifySize,Stringsources){if(sources==null||sources.length()==0){sources=VERIFY_CODES;}intcodesLen=sources.length();Randomrand=newRandom(System.currentTimeMillis());StringBuilderverifyCode=newStringBuilder(verifySize);for(inti=0;i<verifySize;i++){verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));}returnverifyCode.toString();}/***生成随机验证码文件,并返回验证码值*@paramw*@paramh*@paramoutputFile*@paramverifySize*@return*@throwsIOException*/publicstaticStringoutputVerifyImage(intw,inth,FileoutputFile,intverifySize)throwsIOException{StringverifyCode=generateVerifyCode(verifySize);outputImage(w,h,outputFile,verifyCode);returnverifyCode;}/***输出随机验证码图片流,并返回验证码值*@paramw*@paramh*@paramos*@paramverifySize*@return*@throwsIOException*/publicstaticStringoutputVerifyImage(intw,inth,OutputStreamos,intverifySize)throwsIOException{StringverifyCode=generateVerifyCode(verifySize);outputImage(w,h,os,verifyCode);returnverifyCode;}/***生成指定验证码图像文件*@paramw*@paramh*@paramoutputFile*@paramcode*@throwsIOException*/publicstaticvoidoutputImage(intw,inth,FileoutputFile,Stringcode)throwsIOException{if(outputFile==null){return;}Filedir=outputFile.getParentFile();if(!dir.exists()){dir.mkdirs();}try{outputFile.createNewFile();FileOutputStreamfos=newFileOutputStream(outputFile);outputImage(w,h,fos,code);fos.close();}catch(IOExceptione){throwe;}}/***输出指定验证码图片流*@paramw*@paramh*@paramos*@paramcode*@throwsIOException*/publicstaticvoidoutputImage(intw,inth,OutputStreamos,Stringcode)throwsIOException{intverifySize=code.length();BufferedImageimage=newBufferedImage(w,h,BufferedImage.TYPE_INT_RGB);Randomrand=newRandom();Graphics2Dg2=image.createGraphics();g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);Color[]colors=newColor[5];Color[]colorSpaces=newColor[]{Color.WHITE,Color.CYAN,Color.GRAY,Color.LIGHT_GRAY,Color.MAGENTA,Color.ORANGE,Color.PINK,Color.YELLOW};float[]fractions=newfloat[colors.length];for(inti=0;i<colors.length;i++){colors[i]=colorSpaces[rand.nextInt(colorSpaces.length)];fractions[i]=rand.nextFloat();}Arrays.sort(fractions);//设置边框色g2.setColor(Color.GRAY);g2.fillRect(0,0,w,h);Colorc=getRandColor(200,250);//设置背景色g2.setColor(c);g2.fillRect(0,2,w,h-4);//绘制干扰线Randomrandom=newRandom();//设置线条的颜色g2.setColor(getRandColor(160,200));intloop=20;for(inti=0;i<loop;i++){intx=random.nextInt(w-1);inty=random.nextInt(h-1);intxl=random.nextInt(6)+1;intyl=random.nextInt(12)+1;g2.drawLine(x,y,x+xl+40,y+yl+20);}//添加噪点//噪声率floatyawpRate=0.05f;intarea=(int)(yawpRate*w*h);for(inti=0;i<area;i++){intx=random.nextInt(w);inty=random.nextInt(h);intrgb=getRandomIntColor();image.setRGB(x,y,rgb);}//使图片扭曲shear(g2,w,h,c);g2.setColor(getRandColor(100,160));intfontSize=h-4;Fontfont=newFont("Algerian",Font.ITALIC,fontSize);g2.setFont(font);char[]chars=code.toCharArray();for(inti=0;i<verifySize;i++){AffineTransformaffine=newAffineTransform();affine.setToRotation(Math.PI/4*rand.nextDouble()*(rand.nextBoolean()?1:-1),(w/verifySize)*i+fontSize/2,h/2);g2.setTransform(affine);g2.drawChars(chars,i,1,((w-10)/verifySize)*i+5,h/2+fontSize/2-10);}g2.dispose();ImageIO.write(image,"jpg",os);}privatestaticfinalIntegerCOLOR_MAX_INT=255;privatestaticColorgetRandColor(intfc,intbc){if(fc>COLOR_MAX_INT){fc=COLOR_MAX_INT;}if(bc>COLOR_MAX_INT){bc=COLOR_MAX_INT;}intr=fc+random.nextInt(bc-fc);intg=fc+random.nextInt(bc-fc);intb=fc+random.nextInt(bc-fc);returnnewColor(r,g,b);}privatestaticintgetRandomIntColor(){int[]rgb=getRandomRgb();intcolor=0;for(intc:rgb){color=color<<8;color=color|c;}returncolor;}privatestaticint[]getRandomRgb(){int[]rgb=newint[3];intloop=3;for(inti=0;i<loop;i++){rgb[i]=random.nextInt(255);}returnrgb;}privatestaticvoidshear(Graphicsg,intw1,inth1,Colorcolor){shearx(g,w1,h1,color);sheary(g,w1,h1,color);}privatestaticvoidshearx(Graphicsg,intw1,inth1,Colorcolor){intperiod=random.nextInt(2);booleanborderGap=true;intframes=1;intphase=random.nextInt(2);for(inti=0;i<h1;i++){doubled=(double)(period>>1)*Math.sin((double)i/(double)period+(6.2831853071795862D*(double)phase)/(double)frames);g.copyArea(0,i,w1,1,(int)d,0);if(borderGap){g.setColor(color);g.drawLine((int)d,i,0,i);g.drawLine((int)d+w1,i,w1,i);}}}privatestaticvoidsheary(Graphicsg,intw1,inth1,Colorcolor){intperiod=random.nextInt(40)+10;booleanborderGap=true;intframes=20;intphase=7;for(inti=0;i<w1;i++){doubled=(double)(period>>1)*Math.sin((double)i/(double)period+(6.2831853071795862D*(double)phase)/(double)frames);g.copyArea(i,0,1,h1,0,(int)d);if(borderGap){g.setColor(color);g.drawLine(i,(int)d,i,0);g.drawLine(i,(int)d+h1,i,h1);}}}}2.UserController新增验证码接口2.1新增captcha方法@GetMapping("/captcha")publicvoidcaptcha(HttpServletResponseresponse,HttpSessionsession){Stringcode=VerifyCodeUtil.generateVerifyCode(4);session.setAttribute(SessionKey.ADMIN_CAPTCHA_KEY,code);try{VerifyCodeUtil.outputImage(150,50,response.getOutputStream(),code);}catch(IOExceptione){e.printStackTrace();}}2.2修改login方法,增加验证下方为最终的登录验证方法:@PostMapping("/login")publicModelAndViewlogin(Stringemail,Stringpassword,Stringcaptcha,HttpSessionsession){StringsessionCaptcha=(String)session.getAttribute(SessionKey.ADMIN_CAPTCHA_KEY);ModelAndViewview=newModelAndView();view.setViewName("user/login");//将email和password写回到页面,使得登录失败时输入的账号密码不会丢失view.addObject("email",email);view.addObject("password",password);if(StringUtils.isEmpty(captcha)||!Objects.equals(captcha,sessionCaptcha)){view.addObject("errmsg","验证码错误!");returnview;}Useruser=userService.login(email,password);if(user!=null){session.setAttribute(SessionKey.ADMIN_USER_KEY,user);view.setViewName("redirect:/index");returnview;}view.addObject("errmsg","用户名或密码错误!");returnview;}注:此处将Session的Key值通过一个常量类SessionKey保存2.3SessionKey类在utils包下新建SessionKey类packagecn.coralcloud.ims.utils;/***@authorc-geff*@nameSessionKey*@description*@date2020-11-0410:12*/publicclassSessionKey{publicstaticfinalStringADMIN_USER_KEY="AdminUserKey";publicstaticfinalStringADMIN_CAPTCHA_KEY="AdminLoginCaptchaCode";}3.修改login.ftl3.1引入jquery下载jqeury文件jquery.min.js在/static/js文件夹下新建jquery文件夹,将jquery.min.js复制到文件夹下修改login.ftl文件,最终login.ftl内容为下:<!doctypehtml><htmllang="en"xmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"><metahttp-equiv="X-UA-Compatible"content="ie=edge"><linktype="text/css"rel="stylesheet"href="/static/css/main.css"><linktype="text/css"rel="stylesheet"href="/static/css/login.css"><scriptsrc="/static/js/jquery/jquery.min.js"type="application/javascript"></script><title>用户登录</title></head><body><divclass="login-container"><formaction="/user/login"method="post"><div><h3>用户登录</h3></div><divclass="ims-form-item"><labelclass="ims-form-label"for="email">登录邮箱</label><inputclass="ims-form-input"id="email"autocomplete="off"th:value="${email}"placeholder="请输入邮箱"type="text"name="email"></div><divclass="ims-form-item"><labelclass="ims-form-label"for="password">登录密码</label><inputclass="ims-form-input"id="password"autocomplete="new-password"th:value="${password}"placeholder="请输入密码"type="password"name="password"></div><divclass="ims-form-item"><labelclass="ims-form-label"for="captcha">图片验证</label><inputclass="ims-form-input"id="captcha"type="text"placeholder="请输入图片验证码"name="captcha"><imgsrc="/user/captcha"class="captcha"alt="图片验证码"></div><pclass="errormsg"th:if="${errmsg}!=null"th:text="${errmsg}"></p><div><buttontype="submit"class="ims-button">登录</button></div></form></div></body><scripttype="application/javascript">$(document).ready(function(){$('img.captcha').click(function(){$(this).attr("src","/user/captcha?_="+newDate().getTime())})})</script></html>注,login.ftl变更如下:1.head新增引入jquery.min.js2.input新增读取前面保存的email和password3.新增图片验证码输入框及图片4.新建图片点击监听事件,更换图片验证码4.修改样式login.css#captcha{width:100px;border-top-right-radius:0;border-bottom-right-radius:0;}.captcha{width:1px;flex:1;height:42px;cursor:pointer;border-top-right-radius:4px;border-bottom-right-radius:4px;}5.最终效果

工作日志
FastDFS服务器迁移

FastDFS服务器迁移1.1目标服务器安装FastDFS首先按照上一篇文章《FastDFS介绍和安装》搭建好新的FastDFS服务器(Tracker,Storage),先配置好不用启动1.2修改配置文件修改新的Storage配置文件/etc/fdfs/storage.conf将tracker_server的IP修改为旧的Tracker服务器IP1.3启动目标Storage进程,同步数据servicefdfs_storagedrestart查看同步进程,在旧tracker服务器执行:fdfs_monitor/etc/fdfs/client.conf可以看到同步状态:等待数据同步...ACTIVE标识表示数据已经同步完成数据同步完成后,停掉Storage服务servicefdfs_storagedstop1.4修改storage配置文件修改/etc/fdfs/storage.conf文件将tracker_server的IP修改为新的tracker服务器IP1.5修改.data_init_flag文件该文件所在位置为storage.conf文件中所配置的base_path路径后面的data路径下,如配置为/home/data/fastdfs/storage,则文件所在位置为/home/data/fastdfs/storage/data/下将文件中sync_src_server配置项留空,其它配置项不变sync_src_server=1.6启动进程启动新的storage服务器进程,启动新的tracker服务器进程,在新tracker服务器下fdfs_monitor/etc/fdfs/client.conf查看Storage状态