1.项目介绍本项目基础架构为:SpringBoot+Thymleaf+Mybatis整合SpringBoot版本:2.3.52.创建项目(打开IDEA,选择File->New->Project,在弹出的对话框中选择SpringInitializr)3.设置项目基础信息4.选择项目基础依赖5.设置项目保存路径6.创建完成等待项目依赖下载完成,则项目框架到此全部搭建完成7.pom.xml最终项目的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.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.3.5.RELEASE</version><relativePath/><!--lookupparentfromrepository--></parent><groupId>cn.coralcloud</groupId><artifactId>ims</artifactId><version>0.0.1-SNAPSHOT</version><name>ims</name><description>DemoprojectforSpringBoot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version></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-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
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,直到连接成功,数据会再次发送。
1.@Component,@Service,@Controller,@Repository是spring注解,注解后可以被spring框架所扫描并注入到spring容器来进行管理2.@Component是通用注解,其他三个注解是这个注解的拓展,并且具有了特定的功能3.如果想使用自定义的组件注解,那么只要在你定义的新注解中加上@Component即可:4.@Repository注解在持久层中,具有将数据库操作抛出的原生异常翻译转化为spring的持久层异常的功能。5.@Controller层是spring-mvc的注解,具有将请求进行转发,重定向的功能。6.@Service层是业务逻辑层注解,这个注解只是标注该类处于业务逻辑层。7.用这些注解对应用进行分层之后,就能将请求处理,义务逻辑处理,数据库操作处理分离出来,为代码解耦,也方便了以后项目的维护和开发。
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状态
存储集群应与业务服务器不在同一台机器,此处搭建ES存储集群使用单节点的方式。ES集群机器需要实现安装配置好JAVA_HOME环境变量。安装配置下载,版本应与filebeat版本一致:官网地址解压修改elasticsearch.yml配置文件:其中cluster.name避免使用默认名称,各节点中配置为相同的名称,ES就会自动搜索加入启动elasticsearch:==ES集群启动不允许使用root账户==,所以应该先创建一个用户,使用新创建的用户启动elasticsearchgroupaddelkuseradd-gelkelkpasswdelk#创建elk用户密码chown-Relk:elk$ELASTICSEARCH_HOMEsuelk$ELASTICSEARCH_HOME/bin/elasticsearch-d#后台启动ES启动完成后测试,浏览器输入http://host:9200测试,有如下结果则为正常:问题解决启动elasticsearch时出现:elasticsearch:which:nojavain(/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin)[解决]在$ES_HOME/config/elasticsearch文件中加入以下配置:JAVA_HOME=/usr/local/jdk1.8maxnumberofthreads[1024]foruser[elk]likelytoolow,increasetoatleast[2048].[解决]切换到root用户,进入limits.d目录下修改配置文件:vi/etc/security/limits.d/90-nproc.conf找到并修改为softnproc2048hardnproc4096maxfiledescriptors[4096]forelasticsearchprocesslikelytoolow,increasetoatleast[65536][解决]切换到root用户,进入limits.d目录下修改配置文件。vi/etc/security/limits.d/90-nproc.conf找到并修改为softnofile65536hardnofile131072maxvirtualmemoryareasvm.max_map_count[65530]likelytoolow,increasetoatleast[262144][解决]修改:vim/etc/sysctl.conf添加下面配置:vm.max_map_count=655360并执行命令:sysctl-p
1.@RestController注解相当于@ResponseBody+@Controller合在一起的作用。2.如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器InternalResourceViewResolver不起作用,返回的内容就是Return里的内容。3.如果需要返回到指定页面,则需要用@Controller配合视图解析器InternalResourceViewResolver才行。4.如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。)
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”子句)。
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
1.建基础包/文件夹2.application.propertis编写application.propertis基础配置和数据库连接3.index.ftl注:在index.ftl页面可以输入感叹号!,然后按tab键一键生成HTML代码4.IndexController.java5.启动服务启动服务,后再浏览器输入http://localhost:8080,是否能成功访问:
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="/websocket")@ComponentpublicclassWebSocketComponent{/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。/privatestaticintonlineCount=0;/**concurrent包的线程安全Set,用来存放每个客户端对应的CumWebSocket对象。/privatestaticCopyOnWriteArraySetwebSocketSet=newCopyOnWriteArraySet<>();/**与某个客户端的连接会话,需要通过它来给客户端发送数据/privateSessionsession;/**连接建立成功调用的方法@paramsessionsession/@OnOpenpublicvoidonOpen(Sessionsession){this.session=session;//加入set中webSocketSet.add(this);//添加在线人数addOnlineCount();System.out.println("新连接接入。当前在线人数为:"+getOnlineCount());}/**连接关闭调用的方法/@OnClosepublicvoidonClose(){//从set中删除webSocketSet.remove(this);//在线数减1subOnlineCount();System.out.println("有连接关闭。当前在线人数为:"+getOnlineCount());}/**收到客户端消息后调用@parammessagemessage@paramsessionsession/@OnMessagepublicvoidonMessage(Stringmessage,Sessionsession){System.out.println("客户端发送的消息:"+message);sendAll(JSON.toJSONString(messageDTO),session.getId());}/**群发@parammessagemessage/privatestaticvoidsendAll(Stringmessage,StringsessionId){webSocketSet.forEach(item->{if(!item.session.getId().equals(sessionId)){//群发try{item.sendMessage(message);}catch(IOExceptione){e.printStackTrace();}}});}/**发生错误时调用@paramsessionsession@paramerrorerror/@OnErrorpublicvoidonError(Sessionsession,Throwableerror){System.out.println("----websocket-------有异常啦");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端<template><el-cardv-loading="loading"element-loading-spinner="el-icon-loading":body-style="{padding:'5px',backgroundColor:'#eee'}"class="socket-box"shadow="hover"><divclass="socket-box__content":style="{height:(boxHeight-125)+'px'}"id="socket-content"><divv-if="hasMore"@click="loadMore"class="load-more"><span>加载更多</span></div><divv-elsestyle="width:100%;text-align:center;font-size:12px">没有更多了</div><divclass="item"v-for="minmessages":class="checkMe(m)?'sender':''"><divclass="slide"><divclass="avatar":style="{background:m.background}">{{m.name.substring(0,1)}}</div><divclass="meta"><divclass="name">{{m.name}}</div><divclass="date">{{m.createTime|datetime}}</div></div></div><p>{{m.content}}</p></div></div><divclass="socket-box__footer"><el-form@submit.native.prevent><el-form-item><el-inputtype="textarea"resize="none":rows="3":disabled="!connect":placeholder="connect?'输入内容...':'当前连接断开,请刷新重试!'":clearable="true"v-model="message"@keydown.native.enter="submitMsgForm"></el-input></el-form-item><el-form-item><el-button@click="sendMsg(message)":disabled="!connect"style="width:100%"type="primary"size="small">发送(Enter)</el-button></el-form-item></el-form></div></el-card></template><script>import{GET}from"@/api";exportdefault{name:"Chatroom",data(){return{messages:[],message:'',//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("socketUser");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:'/api/personal/web/message/socketData?pageNo=1',callback:res=>{if(res.code===200){that.messages=res.data.messages;that.hasMore=res.data.messages.length===that.pager.pageSize;that.$nextTick(function(){document.getElementById("socket-content").scroll({top:document.getElementById("socket-content").scrollHeight,left:0,behavior:'smooth'})})}that.loading=false}})},sendMsg(data){if(/^\s*$/.test(data)){this.message='';return;}//发送时传入JSON(UID,昵称,内容)constlocal=localStorage.getItem("socketUser");if(local){constl=JSON.parse(local);this.send(l,data)}else{//弹框this.$prompt('首次发表,请输入昵称','提示',{confirmButtonText:'确定',cancelButtonText:'取消',}).then(({value})=>{//随机生成UIDconstuid=this.randomVideoUuid(32,16);constform={uid:uid,name:value,background:`rgb(${Math.random()*255},${Math.random()*255},${Math.random()*255})`};localStorage.setItem("socketUser",JSON.stringify(form));this.send(form,data)}).catch(()=>{this.$message({type:'info',message:'取消输入'});});}},send(obj,data){obj.content=data;obj.createTime=newDate().getTime();this.websock.send(JSON.stringify(obj));this.message='';this.messages.push(obj);this.$nextTick(function(){document.getElementById("socket-content").scroll({top:document.getElementById("socket-content").scrollHeight,left:0,behavior:'smooth'})})},loadMore(){this.loading=true;constthat=this;if(this.hasMore){this.pager.pageNo+=1;GET({url:'/api/personal/web/message/socketData?pageNo='+this.pager.pageNo,callback:res=>{if(res.code===200){that.messages=[...res.data.messages,...that.messages];that.hasMore=res.data.messages.length>=that.pager.pageSize;}that.loading=false;}})}},websocketonopen:function(e){console.log("WebSocket连接成功",e);this.connect=true;},websocketonerror:function(e){console.log("WebSocket连接发生错误");this.connect=false;},websocketonmessage:function(e){constda=JSON.parse(e.data);this.messages.push(da);this.$nextTick(function(){document.getElementById("socket-content").scroll({top:document.getElementById("socket-content").scrollHeight,left:0,behavior:'smooth'})})},websocketclose:function(e){console.log("connectionclosed("+e.code+")");},randomVideoUuid(len,radix){letchars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');letuuid=[];radix=radix||chars.length;if(len){for(leti=0;i<len;i++)uuid[i]=chars[0|Math.random()*radix];}else{letr;uuid[8]=uuid[13]=uuid[18]=uuid[23]='-';uuid[14]='4';for(leti=0;i<36;i++){if(!uuid[i]){r=0|Math.random()*16;uuid[i]=chars[(i===19)?(r&0x3)|0x8:r];}}}returnuuid.join('');},},mounted(){this.initWebSocket();}}</script>本段代码为简单的Vue实现的网页聊天室代码,其中@/api为自己简单封装的JS函数,用户初次进入页面时会生成一个随机UID保存到localStorage中,在mounted周期中初始化websocket连接。本聊天室最终效果地址:https://web.coralcloud.cn/blog/message
- SpringBoot+Thymleaf项目初入(五) - 图片验证码
- SpringBoot+Thymleaf项目初入(四) - 用户登录页面优化
- SpringBoot+Thymleaf项目初入(三) - 用户登录
- SpringBoot+Thymleaf项目初入(二) - 配置基础页面访问
- SpringBoot+Thymleaf项目初入(一) - 基础项目搭建
- MyBatis之where关键字与<where>标签的区别
- 文件上传之@RequestParam与@RequestPart
- Spring注解之@Component
- SpringBoot框架之@Controller和@RestController的区别?
- Centos安装ApacheHadoop2.7.7
