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

下载与ES对应的版本:官网地址解压修改$KIBANA_HOME/config/kibana.yml文件启动Kibana:sudo$KIBANA_HOME/bin/kibana浏览器输入host:5601能正常打开页面即可KAAE插件安装KAAE为Kibana的插件,主要用来监控和报警,用户可以根据需求配置相应的监控条件,达到某个条件会发出报警消息,同时KAAE也提供有报告Report功能,能够将查询到的结果生成图表发送到指定邮箱。安装:$KIBANA_HOME/bin/kibana-pulgininstallhttps://github.com/sirensolutions/sentinl/releases/download/tag-6.2.3-3/sentinl-v6.2.4.zip配置kibana.yml文件,在最后加上:重启Kibana后,浏览器输入:http://ip:5601出现以下界面说明插件安装成功可以在页面上按照需求配置监听报警

工作日志
【基于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,直到连接成功,数据会再次发送。

个人随笔
Centos7安装Redis

一、安装Redis1.下载Rediswgethttp://download.redis.io/releases/redis-4.0.6.tar.gz2.解压tar-zxvfredis-4.0.6.tar.gz3.yum安装gccyuminstall-ygcc4.编译安装rediscdredis-4.0.6makeMALLOC=libccdsrc&&makeinstall二、启动Redis1.在/etc目录下新建redis目录mkdir-p/etc/redis2.将/usr/local/redis-4.0.6/redis.conf文件复制一份到/etc/redis目录下,并命名为6379.confcp/usr/local/redis-4.0.6/redis.conf/etc/redis/6379.conf3.将redis的启动脚本复制一份放到/etc/init.d目录下cp/usr/local/redis-4.0.6/utils/redis_init_script/etc/init.d/redisd4.设置redis开机自启动cd/etc/init.dchkconfigredisdonserviceredisddoesnotsupportchkconfig看结果是redisd不支持chkconfig,编辑redisd文件,加入如下注释:#chkconfig:23459010#description:Redisisapersistentkey-valuedatabasechkconfigredisdon启动redis:serviceredisdstart停止redis:serviceredisdstop

开发项目专栏
SpringBoot+Thymleaf项目初入(四) - 用户登录页面优化

1.新建CSS和JS文件2.新建静态资源配置类:ImsConfig.javapackagecn.coralcloud.ims.config;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;/***@authorc-geff*@nameImsConfig*@description*@date2020-11-0315:28*/@ConfigurationpublicclassImsConfigextendsWebMvcConfigurerAdapter{@OverridepublicvoidaddResourceHandlers(ResourceHandlerRegistryregistry){registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");super.addResourceHandlers(registry);}}注:新建config包,然后新建ImsConfig类,继承自WebMvcConfigurerAdapter,实现了addResourceHandlers方法,该方法设置了访问/static/路径的文件时会映射到项目static文件夹下3.login.ftl修改,head添加CSS引入:4.login.ftl修改,页面布局修改:5.编写对应的main.css文件,该文件为通用样式html,body{padding:0;margin:0;}.ims-form-label{font-size:15px;color:rgba(0,0,0,.7);width:100px;height:40px;line-height:40px;letter-spacing:3px;}.ims-form-input{height:40px;line-height:40px;flex:1;outline:0;padding:015px;border:1pxsolid#DCDFE6;border-radius:4px;}.ims-form-input:focus{border-color:#409EFF;}.ims-form-item{margin:15px0;width:100%;display:flex;}.ims-button{height:40px;line-height:40px;border-radius:4px;padding:030px;border:none;color:#FFFFFF;font-size:14px;background-color:#409EFF;cursor:pointer;margin:15px0;outline:0;}.ims-button:hover{background-color:#3888e0;}.ims-button:focus{background-color:rgba(64,158,255,0.81);}6.编写对应的login.css文件,该文件为登录页专用样式.login-container{display:flex;align-content:center;justify-content:center;text-align:center;-webkit-box-pack:center;-webkit-box-align:center;align-items:center;width:100vw;height:100vh;background-image:url("/static/images/login_bg.jpg");background-repeat:no-repeat;background-size:100%100%;}.login-containerform{width:350px;height:300px;background-color:rgba(255,255,255,.7);border-radius:5px;padding:10px20px;}.login-container.ims-button{width:100%}.errormsg{color:#ed2322;font-size:13px;margin-bottom:0;}7.static目录下新建文件夹images保存背景图片login_bg.jpglogin_bg.jpg

Java开发
MyBatis之where关键字与<where>标签的区别

1.在使用mybatis的动态sql时,有时候遇到根据条件判断添加where后面的筛选条件的情况,会出现多余的AND或者OR:2.使用where关键字:2.1当第一个参数为空时,拼接后的sql为:select*fromtdwhereandphone=.......;2.2当所有的参数都为空时,拼接后的sql为:select*fromtdwhere.....,显然这样的sql不是完整的sql,执行时会报错.3.使用where标签时:3.1当第一个参数为空时,拼接后的sql为:select*fromtdwherephone=......(若语句的开头为AND或者OR时,where元素会将他们去除).3.2当所有的参数都为空时,拼接后的sql为:select*fromtd.(where元素只会在至少有一个子元素的条件返回SQL子句的情况下才去插入“WHERE”子句)。

Tags: JAVA
开发项目专栏
SpringBoot+Thymleaf项目初入(一) - 基础项目搭建

1.项目介绍本项目基础架构为:SpringBoot+Thymleaf+Mybatis整合SpringBoot版本:2.3.52.创建项目(打开IDEA,选择File-&gt;New-&gt;Project,在弹出的对话框中选择SpringInitializr)3.设置项目基础信息4.选择项目基础依赖5.设置项目保存路径6.创建完成等待项目依赖下载完成,则项目框架到此全部搭建完成7.pom.xml最终项目的pom.xml:&lt;?xmlversion=&quot;1.0&quot;encoding=&quot;UTF-8&quot;?&gt;&lt;projectxmlns=&quot;http://maven.apache.org/POM/4.0.0&quot;xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;&lt;parent&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;&lt;version&gt;2.3.5.RELEASE&lt;/version&gt;&lt;relativePath/&gt;&lt;!--lookupparentfromrepository--&gt;&lt;/parent&gt;&lt;groupId&gt;cn.coralcloud&lt;/groupId&gt;&lt;artifactId&gt;ims&lt;/artifactId&gt;&lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;&lt;name&gt;ims&lt;/name&gt;&lt;description&gt;DemoprojectforSpringBoot&lt;/description&gt;&lt;properties&gt;&lt;java.version&gt;1.8&lt;/java.version&gt;&lt;/properties&gt;&lt;dependencies&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-jdbc&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;&lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;&lt;version&gt;2.1.3&lt;/version&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;&lt;scope&gt;runtime&lt;/scope&gt;&lt;optional&gt;true&lt;/optional&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;mysql&lt;/groupId&gt;&lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;&lt;scope&gt;runtime&lt;/scope&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-configuration-processor&lt;/artifactId&gt;&lt;optional&gt;true&lt;/optional&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.projectlombok&lt;/groupId&gt;&lt;artifactId&gt;lombok&lt;/artifactId&gt;&lt;optional&gt;true&lt;/optional&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;&lt;scope&gt;test&lt;/scope&gt;&lt;exclusions&gt;&lt;exclusion&gt;&lt;groupId&gt;org.junit.vintage&lt;/groupId&gt;&lt;artifactId&gt;junit-vintage-engine&lt;/artifactId&gt;&lt;/exclusion&gt;&lt;/exclusions&gt;&lt;/dependency&gt;&lt;/dependencies&gt;&lt;build&gt;&lt;plugins&gt;&lt;plugin&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;&lt;/plugin&gt;&lt;/plugins&gt;&lt;/build&gt;&lt;/project&gt;

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

全国建筑市场监管公共服务平台(四库一平台)接口数据解密需要爬取建筑市场公司的不良行为记录进入服务平台页面http://jzsc.mohurd.gov.cn,点击顶部的搜索,发现返回的数据是经过加密的:1.寻找返回的数据既然数据是通过这个url返回的,全局搜索url:http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&amp;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&amp;pgsz=15所返回的数据e这其中的数据正是我们想要的数据2.分析加密方式既然我们已经知道了数据的加密方式,那我们就重点分析一下这个地方其中t.data我们在第一步已经分出来了使我们第一步请求http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&amp;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=&#39;jo8j9wGw%6HbxfFn&#39;vi=&#39;0123456789ABCDEF&#39;#将请求返回的16进制数据转换为二进制数据text=binascii.a2b_hex(text)#构建解密对象cipher=AES.new(key.encode(&#39;utf8&#39;),AES.MODE_CBC,vi.encode(&#39;utf8&#39;))text_decrypted=cipher.decrypt(text)unpad=lambdas:s[0:-s[-1]]text_decrypted=unpad(text_decrypted)#去补位text_decrypted=text_decrypted.decode(&#39;utf8&#39;)returntext_decrypted5.最后基于Python的requests实现了简单的请求+解密代码#-*-coding:utf-8-*-importjsonimportrequestsimportbinasciifromCrypto.CipherimportAESdefdecrypt(text):key=&#39;jo8j9wGw%6HbxfFn&#39;vi=&#39;0123456789ABCDEF&#39;#将请求返回的16进制数据转换为二进制数据text=binascii.a2b_hex(text)#构建解密对象cipher=AES.new(key.encode(&#39;utf8&#39;),AES.MODE_CBC,vi.encode(&#39;utf8&#39;))text_decrypted=cipher.decrypt(text)unpad=lambdas:s[0:-s[-1]]text_decrypted=unpad(text_decrypted)#去补位text_decrypted=text_decrypted.decode(&#39;utf8&#39;)returntext_decryptedif__name__==&#39;__main__&#39;:headers={&#39;User-Agent&#39;:&#39;Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/74.0.3729.108Safari/537.36&#39;}#获取到当前搜索结果,并解密data=requests.get(&#39;http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&amp;pgsz=15&amp;total=0&amp;complexname=&#39;,headers=headers).textres=json.loads(decrypt(data))print(res)6.注意本文参考地址:https://www.cnblogs.com/mingyangliang/p/11875925.html平台具有防爬取策略,频繁爬取会导致封IP,可以使用IP代理,或者设置爬取间隔在1.5s所爬取数据请勿用于非法用途

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

采集节点主要安装Filebeat组件即可,Filebeat可以很简单运行在Windows/Linux操作系统上,不需要其余环境。下载Filebeat压缩包:https://www.elastic.co/downloads/past-releases/filebeat-6-2-4根据操作系统选择对应的版本下载解压修改目录下filebeat.yml文件,具体配置下图:上图为filebeat的输入端配置,指定监听文件上图为filebeat的输出端配置,指定输出到本机的Logstash中Filebeat启动(cd$FILEBEAT_HOME):./filebeat-e-cfilebeat.yml后台启动可使用nohup命令nohup./filebeat-e-cfilebeat.yml-d&quot;publish&quot;&gt;logs/filebeat.log2&gt;&amp;1&amp;

Java开发
SpringBoot框架之@Controller和@RestController的区别?

1.@RestController注解相当于@ResponseBody+@Controller合在一起的作用。2.如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器InternalResourceViewResolver不起作用,返回的内容就是Return里的内容。3.如果需要返回到指定页面,则需要用@Controller配合视图解析器InternalResourceViewResolver才行。4.如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。)

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=&quot;23456789ABCDEFGHJKLMNPQRSTUVWXYZ&quot;;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&lt;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&lt;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&lt;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&lt;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(&quot;Algerian&quot;,Font.ITALIC,fontSize);g2.setFont(font);char[]chars=code.toCharArray();for(inti=0;i&lt;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,&quot;jpg&quot;,os);}privatestaticfinalIntegerCOLOR_MAX_INT=255;privatestaticColorgetRandColor(intfc,intbc){if(fc&gt;COLOR_MAX_INT){fc=COLOR_MAX_INT;}if(bc&gt;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&lt;&lt;8;color=color|c;}returncolor;}privatestaticint[]getRandomRgb(){int[]rgb=newint[3];intloop=3;for(inti=0;i&lt;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&lt;h1;i++){doubled=(double)(period&gt;&gt;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&lt;w1;i++){doubled=(double)(period&gt;&gt;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(&quot;/captcha&quot;)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(&quot;/login&quot;)publicModelAndViewlogin(Stringemail,Stringpassword,Stringcaptcha,HttpSessionsession){StringsessionCaptcha=(String)session.getAttribute(SessionKey.ADMIN_CAPTCHA_KEY);ModelAndViewview=newModelAndView();view.setViewName(&quot;user/login&quot;);//将email和password写回到页面,使得登录失败时输入的账号密码不会丢失view.addObject(&quot;email&quot;,email);view.addObject(&quot;password&quot;,password);if(StringUtils.isEmpty(captcha)||!Objects.equals(captcha,sessionCaptcha)){view.addObject(&quot;errmsg&quot;,&quot;验证码错误!&quot;);returnview;}Useruser=userService.login(email,password);if(user!=null){session.setAttribute(SessionKey.ADMIN_USER_KEY,user);view.setViewName(&quot;redirect:/index&quot;);returnview;}view.addObject(&quot;errmsg&quot;,&quot;用户名或密码错误!&quot;);returnview;}注:此处将Session的Key值通过一个常量类SessionKey保存2.3SessionKey类在utils包下新建SessionKey类packagecn.coralcloud.ims.utils;/***@authorc-geff*@nameSessionKey*@description*@date2020-11-0410:12*/publicclassSessionKey{publicstaticfinalStringADMIN_USER_KEY=&quot;AdminUserKey&quot;;publicstaticfinalStringADMIN_CAPTCHA_KEY=&quot;AdminLoginCaptchaCode&quot;;}3.修改login.ftl3.1引入jquery下载jqeury文件jquery.min.js在/static/js文件夹下新建jquery文件夹,将jquery.min.js复制到文件夹下修改login.ftl文件,最终login.ftl内容为下:&lt;!doctypehtml&gt;&lt;htmllang=&quot;en&quot;xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;&lt;head&gt;&lt;metacharset=&quot;UTF-8&quot;&gt;&lt;metaname=&quot;viewport&quot;content=&quot;width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0&quot;&gt;&lt;metahttp-equiv=&quot;X-UA-Compatible&quot;content=&quot;ie=edge&quot;&gt;&lt;linktype=&quot;text/css&quot;rel=&quot;stylesheet&quot;href=&quot;/static/css/main.css&quot;&gt;&lt;linktype=&quot;text/css&quot;rel=&quot;stylesheet&quot;href=&quot;/static/css/login.css&quot;&gt;&lt;scriptsrc=&quot;/static/js/jquery/jquery.min.js&quot;type=&quot;application/javascript&quot;&gt;&lt;/script&gt;&lt;title&gt;用户登录&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;divclass=&quot;login-container&quot;&gt;&lt;formaction=&quot;/user/login&quot;method=&quot;post&quot;&gt;&lt;div&gt;&lt;h3&gt;用户登录&lt;/h3&gt;&lt;/div&gt;&lt;divclass=&quot;ims-form-item&quot;&gt;&lt;labelclass=&quot;ims-form-label&quot;for=&quot;email&quot;&gt;登录邮箱&lt;/label&gt;&lt;inputclass=&quot;ims-form-input&quot;id=&quot;email&quot;autocomplete=&quot;off&quot;th:value=&quot;${email}&quot;placeholder=&quot;请输入邮箱&quot;type=&quot;text&quot;name=&quot;email&quot;&gt;&lt;/div&gt;&lt;divclass=&quot;ims-form-item&quot;&gt;&lt;labelclass=&quot;ims-form-label&quot;for=&quot;password&quot;&gt;登录密码&lt;/label&gt;&lt;inputclass=&quot;ims-form-input&quot;id=&quot;password&quot;autocomplete=&quot;new-password&quot;th:value=&quot;${password}&quot;placeholder=&quot;请输入密码&quot;type=&quot;password&quot;name=&quot;password&quot;&gt;&lt;/div&gt;&lt;divclass=&quot;ims-form-item&quot;&gt;&lt;labelclass=&quot;ims-form-label&quot;for=&quot;captcha&quot;&gt;图片验证&lt;/label&gt;&lt;inputclass=&quot;ims-form-input&quot;id=&quot;captcha&quot;type=&quot;text&quot;placeholder=&quot;请输入图片验证码&quot;name=&quot;captcha&quot;&gt;&lt;imgsrc=&quot;/user/captcha&quot;class=&quot;captcha&quot;alt=&quot;图片验证码&quot;&gt;&lt;/div&gt;&lt;pclass=&quot;errormsg&quot;th:if=&quot;${errmsg}!=null&quot;th:text=&quot;${errmsg}&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;buttontype=&quot;submit&quot;class=&quot;ims-button&quot;&gt;登录&lt;/button&gt;&lt;/div&gt;&lt;/form&gt;&lt;/div&gt;&lt;/body&gt;&lt;scripttype=&quot;application/javascript&quot;&gt;$(document).ready(function(){$(&#39;img.captcha&#39;).click(function(){$(this).attr(&quot;src&quot;,&quot;/user/captcha?_=&quot;+newDate().getTime())})})&lt;/script&gt;&lt;/html&gt;注,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.最终效果