开发项目专栏
SpringBoot+Thymleaf项目初入(二) - 配置基础页面访问

1.建基础包/文件夹2.application.propertis编写application.propertis基础配置和数据库连接3.index.ftl注:在index.ftl页面可以输入感叹号!,然后按tab键一键生成HTML代码4.IndexController.java5.启动服务启动服务,后再浏览器输入http://localhost:8080,是否能成功访问:

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
工作日志
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状态

开发项目专栏
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(&quot;/static/**&quot;).addResourceLocations(&quot;classpath:/static/&quot;);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(&quot;/static/images/login_bg.jpg&quot;);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

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

全国建筑市场监管公共服务平台(四库一平台)接口数据解密需要爬取建筑市场公司的不良行为记录进入服务平台页面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所爬取数据请勿用于非法用途

个人随笔
Vue + SpringBoot实现WebSocket通信

Vue+SpringBoot实现WebSocket通信服务端在SpringBoot项目中添加ServerEndpointExporterBean的方法@BeanpublicServerEndpointExporterexporter(){returnnewServerEndpointExporter();}创建WebSocket客户端管理类:WebSocketComponent.javapackagecn.coralcloud.blog.web.component;importcom.alibaba.fastjson.JSON;importorg.springframework.stereotype.Component;importjavax.websocket.*;importjavax.websocket.server.ServerEndpoint;importjava.io.IOException;importjava.util.Objects;importjava.util.concurrent.CopyOnWriteArraySet;/**@authorgeff@nameWebSocketComponent@description@date2019-12-1814:22/@ServerEndpoint(value=&quot;/websocket&quot;)@ComponentpublicclassWebSocketComponent{/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。/privatestaticintonlineCount=0;/**concurrent包的线程安全Set,用来存放每个客户端对应的CumWebSocket对象。/privatestaticCopyOnWriteArraySetwebSocketSet=newCopyOnWriteArraySet&lt;&gt;();/**与某个客户端的连接会话,需要通过它来给客户端发送数据/privateSessionsession;/**连接建立成功调用的方法@paramsessionsession/@OnOpenpublicvoidonOpen(Sessionsession){this.session=session;//加入set中webSocketSet.add(this);//添加在线人数addOnlineCount();System.out.println(&quot;新连接接入。当前在线人数为:&quot;+getOnlineCount());}/**连接关闭调用的方法/@OnClosepublicvoidonClose(){//从set中删除webSocketSet.remove(this);//在线数减1subOnlineCount();System.out.println(&quot;有连接关闭。当前在线人数为:&quot;+getOnlineCount());}/**收到客户端消息后调用@parammessagemessage@paramsessionsession/@OnMessagepublicvoidonMessage(Stringmessage,Sessionsession){System.out.println(&quot;客户端发送的消息:&quot;+message);sendAll(JSON.toJSONString(messageDTO),session.getId());}/**群发@parammessagemessage/privatestaticvoidsendAll(Stringmessage,StringsessionId){webSocketSet.forEach(item-&gt;{if(!item.session.getId().equals(sessionId)){//群发try{item.sendMessage(message);}catch(IOExceptione){e.printStackTrace();}}});}/**发生错误时调用@paramsessionsession@paramerrorerror/@OnErrorpublicvoidonError(Sessionsession,Throwableerror){System.out.println(&quot;----websocket-------有异常啦&quot;);error.printStackTrace();}/**减少在线人数/privatevoidsubOnlineCount(){WebSocketComponent.onlineCount--;}/**添加在线人数/privatevoidaddOnlineCount(){WebSocketComponent.onlineCount++;}/**当前在线人数@returnint/publicstaticsynchronizedintgetOnlineCount(){returnonlineCount;}/**发送信息@parammessagemessagethrowsIOException/publicvoidsendMessage(Stringmessage)throwsIOException{//获取session远程基本连接发送文本消息this.session.getBasicRemote().sendText(message);//this.session.getAsyncRemote().sendText(message);}@Overridepublicbooleanequals(Objecto){if(this==o){returntrue;}if(o==null||getClass()!=o.getClass()){returnfalse;}WebSocketComponentthat=(WebSocketComponent)o;returnObjects.equals(session,that.session);}@OverridepublicinthashCode(){returnObjects.hash(session);}}@ServerEndpoint注解标识当前WebSocket服务端endpoint地址,本文实际前端访问的ws地址为:ws://localhost:8080/websocket。至此,服务端工作完成。页面VUE端&lt;template&gt;&lt;el-cardv-loading=&quot;loading&quot;element-loading-spinner=&quot;el-icon-loading&quot;:body-style=&quot;{padding:&#39;5px&#39;,backgroundColor:&#39;#eee&#39;}&quot;class=&quot;socket-box&quot;shadow=&quot;hover&quot;&gt;&lt;divclass=&quot;socket-box__content&quot;:style=&quot;{height:(boxHeight-125)+&#39;px&#39;}&quot;id=&quot;socket-content&quot;&gt;&lt;divv-if=&quot;hasMore&quot;@click=&quot;loadMore&quot;class=&quot;load-more&quot;&gt;&lt;span&gt;加载更多&lt;/span&gt;&lt;/div&gt;&lt;divv-elsestyle=&quot;width:100%;text-align:center;font-size:12px&quot;&gt;没有更多了&lt;/div&gt;&lt;divclass=&quot;item&quot;v-for=&quot;minmessages&quot;:class=&quot;checkMe(m)?&#39;sender&#39;:&#39;&#39;&quot;&gt;&lt;divclass=&quot;slide&quot;&gt;&lt;divclass=&quot;avatar&quot;:style=&quot;{background:m.background}&quot;&gt;{{m.name.substring(0,1)}}&lt;/div&gt;&lt;divclass=&quot;meta&quot;&gt;&lt;divclass=&quot;name&quot;&gt;{{m.name}}&lt;/div&gt;&lt;divclass=&quot;date&quot;&gt;{{m.createTime|datetime}}&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;{{m.content}}&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;divclass=&quot;socket-box__footer&quot;&gt;&lt;el-form@submit.native.prevent&gt;&lt;el-form-item&gt;&lt;el-inputtype=&quot;textarea&quot;resize=&quot;none&quot;:rows=&quot;3&quot;:disabled=&quot;!connect&quot;:placeholder=&quot;connect?&#39;输入内容...&#39;:&#39;当前连接断开,请刷新重试!&#39;&quot;:clearable=&quot;true&quot;v-model=&quot;message&quot;@keydown.native.enter=&quot;submitMsgForm&quot;&gt;&lt;/el-input&gt;&lt;/el-form-item&gt;&lt;el-form-item&gt;&lt;el-button@click=&quot;sendMsg(message)&quot;:disabled=&quot;!connect&quot;style=&quot;width:100%&quot;type=&quot;primary&quot;size=&quot;small&quot;&gt;发送(Enter)&lt;/el-button&gt;&lt;/el-form-item&gt;&lt;/el-form&gt;&lt;/div&gt;&lt;/el-card&gt;&lt;/template&gt;&lt;script&gt;import{GET}from&quot;@/api&quot;;exportdefault{name:&quot;Chatroom&quot;,data(){return{messages:[],message:&#39;&#39;,//boxHeight:document.documentElement.clientHeight-85,hasMore:true,pager:{pageNo:1,pageSize:10,total:0},loading:false,connect:false}},props:{boxHeight:{type:Number,required:true}},methods:{submitMsgForm(event){if(event.shiftKey){return;}event.preventDefault();this.sendMsg(this.message)},checkMe(message){letuser=localStorage.getItem(&quot;socketUser&quot;);if(user){user=JSON.parse(user);returnuser.uid===message.uid}else{returnfalse;}},initWebSocket:function(){this.websock=newWebSocket(`ws://localhost:8080/websocket`);this.websock.onopen=this.websocketonopen;this.websock.onerror=this.websocketonerror;this.websock.onmessage=this.websocketonmessage;this.websock.onclose=this.websocketclose;constthat=this;that.loading=true;GET({url:&#39;/api/personal/web/message/socketData?pageNo=1&#39;,callback:res=&gt;{if(res.code===200){that.messages=res.data.messages;that.hasMore=res.data.messages.length===that.pager.pageSize;that.$nextTick(function(){document.getElementById(&quot;socket-content&quot;).scroll({top:document.getElementById(&quot;socket-content&quot;).scrollHeight,left:0,behavior:&#39;smooth&#39;})})}that.loading=false}})},sendMsg(data){if(/^\s*$/.test(data)){this.message=&#39;&#39;;return;}//发送时传入JSON(UID,昵称,内容)constlocal=localStorage.getItem(&quot;socketUser&quot;);if(local){constl=JSON.parse(local);this.send(l,data)}else{//弹框this.$prompt(&#39;首次发表,请输入昵称&#39;,&#39;提示&#39;,{confirmButtonText:&#39;确定&#39;,cancelButtonText:&#39;取消&#39;,}).then(({value})=&gt;{//随机生成UIDconstuid=this.randomVideoUuid(32,16);constform={uid:uid,name:value,background:`rgb(${Math.random()*255},${Math.random()*255},${Math.random()*255})`};localStorage.setItem(&quot;socketUser&quot;,JSON.stringify(form));this.send(form,data)}).catch(()=&gt;{this.$message({type:&#39;info&#39;,message:&#39;取消输入&#39;});});}},send(obj,data){obj.content=data;obj.createTime=newDate().getTime();this.websock.send(JSON.stringify(obj));this.message=&#39;&#39;;this.messages.push(obj);this.$nextTick(function(){document.getElementById(&quot;socket-content&quot;).scroll({top:document.getElementById(&quot;socket-content&quot;).scrollHeight,left:0,behavior:&#39;smooth&#39;})})},loadMore(){this.loading=true;constthat=this;if(this.hasMore){this.pager.pageNo+=1;GET({url:&#39;/api/personal/web/message/socketData?pageNo=&#39;+this.pager.pageNo,callback:res=&gt;{if(res.code===200){that.messages=[...res.data.messages,...that.messages];that.hasMore=res.data.messages.length&gt;=that.pager.pageSize;}that.loading=false;}})}},websocketonopen:function(e){console.log(&quot;WebSocket连接成功&quot;,e);this.connect=true;},websocketonerror:function(e){console.log(&quot;WebSocket连接发生错误&quot;);this.connect=false;},websocketonmessage:function(e){constda=JSON.parse(e.data);this.messages.push(da);this.$nextTick(function(){document.getElementById(&quot;socket-content&quot;).scroll({top:document.getElementById(&quot;socket-content&quot;).scrollHeight,left:0,behavior:&#39;smooth&#39;})})},websocketclose:function(e){console.log(&quot;connectionclosed(&quot;+e.code+&quot;)&quot;);},randomVideoUuid(len,radix){letchars=&#39;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&#39;.split(&#39;&#39;);letuuid=[];radix=radix||chars.length;if(len){for(leti=0;i&lt;len;i++)uuid[i]=chars[0|Math.random()*radix];}else{letr;uuid[8]=uuid[13]=uuid[18]=uuid[23]=&#39;-&#39;;uuid[14]=&#39;4&#39;;for(leti=0;i&lt;36;i++){if(!uuid[i]){r=0|Math.random()*16;uuid[i]=chars[(i===19)?(r&amp;0x3)|0x8:r];}}}returnuuid.join(&#39;&#39;);},},mounted(){this.initWebSocket();}}&lt;/script&gt;本段代码为简单的Vue实现的网页聊天室代码,其中@/api为自己简单封装的JS函数,用户初次进入页面时会生成一个随机UID保存到localStorage中,在mounted周期中初始化websocket连接。本聊天室最终效果地址:https://web.coralcloud.cn/blog/message

Java开发
Spring注解之@Component

1.@Component,@Service,@Controller,@Repository是spring注解,注解后可以被spring框架所扫描并注入到spring容器来进行管理2.@Component是通用注解,其他三个注解是这个注解的拓展,并且具有了特定的功能3.如果想使用自定义的组件注解,那么只要在你定义的新注解中加上@Component即可:4.@Repository注解在持久层中,具有将数据库操作抛出的原生异常翻译转化为spring的持久层异常的功能。5.@Controller层是spring-mvc的注解,具有将请求进行转发,重定向的功能。6.@Service层是业务逻辑层注解,这个注解只是标注该类处于业务逻辑层。7.用这些注解对应用进行分层之后,就能将请求处理,义务逻辑处理,数据库操作处理分离出来,为代码解耦,也方便了以后项目的维护和开发。

Tags: JAVA
Java开发
文件上传之@RequestParam与@RequestPart

1.、@RequestParam与@RequestPart主要用来接收文件,两者都能用于后端接收文件2.@RequestPart这个注解用在multipart/form-data表单提交请求的方法上。支持的请求方法的方式MultipartFile,属于Spring的MultipartResolver类。这个请求是通过http协议传输的。3.@RequestParam也同样支持multipart/form-data请求。当请求方法的请求参数类型是String类型的时候。4.@RequestParam适用于name-valueString类型的请求域,@RequestPart适用于复杂的请求域(像JSON,XML)5.@RequestPart注解会生成临时文件,而@RequestParam则不会生成临时文件,效率上ReqeustParam会比RequestPart快一些

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.最终效果

开发项目专栏
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;