SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室

一、概述

WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

WebSocket复用了HTTP的握手通道,使用ws或wss的统一资源标志符,类似于HTTPS。其中wss表示使用了TLS的Websocket。如:ws://example.com/wsapi或者wss://secure.example.com/wsapi

代码可以在SpringBoot组件化构建https://www.pomit.cn/java/spring/springboot.html中的WebSocket组件中查看,并下载。

首发地址:
品茗IT-同步发布

如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以加入我们的java学习圈,点击即可加入,共同学习,节约学习时间,减少很多在学习中遇到的难题。

二、配置

本文假设你已经引入spring-boot-starter-web。已经是个SpringBoot项目了,如果不会搭建,可以打开这篇文章看一看《SpringBoot入门建站全系列(一)项目建立》。

2.1 Maven依赖

使用WebSocket需要引入spring-boot-starter-websocket。本文使用fastjson做json数据的传输。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId>
</dependency>

2.2 配置文件

无需额外配置。

三、WebSocket配置

3.1 WebSocket处理相关配置

Springboot整合WebSocket需要配置websocket的监听url、配置WebSocketInterceptor(连接握手配置)、webSocketHandler(连接成功配置)。

SpringWebSocketConfig:

package com.cff.springbootwork.websocket.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;import com.cff.springbootwork.websocket.handler.WebSocketHandler;
import com.cff.springbootwork.websocket.handler.WebSocketInterceptor;@Configuration
@EnableWebSocket
public class SpringWebSocketConfig implements WebSocketConfigurer {public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(webSocketHandler(), "/websocket").addInterceptors(new WebSocketInterceptor());registry.addHandler(webSocketHandler(), "/sockjs").addInterceptors(new WebSocketInterceptor()).withSockJS();}@Beanpublic WebSocketHandler webSocketHandler() {return new WebSocketHandler();}}

3.2 WebSocket的session拦截处理

WebSocketInterceptor对WebSocket的连接进行过滤,可以对连接前和连接后自定义处理。

WebSocketInterceptor:

package com.cff.springbootwork.websocket.handler;import java.util.Map;import javax.servlet.http.HttpSession;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;/*** websocket 握手处理器* @author fufei**/
@Component
public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {private Logger log = LoggerFactory.getLogger(this.getClass());@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,Map<String, Object> attributes) throws Exception {log.info("收到握手请求。");if (request instanceof ServletServerHttpRequest) {ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;HttpSession session = servletRequest.getServletRequest().getSession(false);if (session == null) {return false;}// 使用userName区分WebSocketHandler,以便定向发送消息String userName = (String) session.getAttribute("userName");if (StringUtils.isEmpty(userName)) {return false;}log.info("获取到用户信息:{}", userName);attributes.put("userId", userName);}return super.beforeHandshake(request, response, wsHandler, attributes);}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,Exception ex) {log.info("成功握手。");super.afterHandshake(request, response, wsHandler, ex);}}

3.3 WebSocket消息处理

WebsocketHandler负责处理消息发送接收的逻辑:

package com.cff.springbootwork.websocket.handler;import java.io.IOException;
import java.util.List;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.AbstractWebSocketMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import com.alibaba.fastjson.JSONObject;
import com.cff.springbootwork.websocket.dto.MessageDTO;
import com.cff.springbootwork.websocket.memory.WebSocketUser;/*** websocket消息处理器* * @author fufei**/
public class WebSocketHandler extends TextWebSocketHandler {protected Logger logger = LoggerFactory.getLogger(getClass());public static WebSocketUser users = new WebSocketUser();@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {logger.info("收到websocket消息:{}", message.toString());super.handleTextMessage(session, message);String msg = message.getPayload();logger.info("收到websocket消息的消息体:{}", msg);if (!StringUtils.isEmpty(msg)) {MessageDTO messageDTO = JSONObject.parseObject(msg, MessageDTO.class);sendMessageToUser(messageDTO.getTargetUserName(), message);}}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {String userName = (String) session.getAttributes().get("userName");if (StringUtils.isEmpty(userName)) {logger.error("用户不能为空!");}WebSocketUser.add(userName, session);MessageDTO messageDTO = new MessageDTO();messageDTO.setFromUserName("");messageDTO.setTargetUserName(userName);messageDTO.setMessageType(MessageDTO.Type.TYPE_NEW.getMessageType());messageDTO.setMessage("欢迎您!");session.sendMessage(new TextMessage(JSONObject.toJSONString(messageDTO)));}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {String userName = (String) session.getAttributes().get("userName");if (StringUtils.isEmpty(userName)) {logger.error("用户不能为空!");}WebSocketUser.removeWebSocketSession(userName, session);super.afterConnectionClosed(session, status);}/*** 给某个用户发送消息** @param userName* @param message*/public void sendMessageToUser(String userName, AbstractWebSocketMessage<?> message) {List<WebSocketSession> webUsers = WebSocketUser.getSessionByUserName(userName);if (webUsers == null || webUsers.size() == 0) {logger.error("发送给{},当前无session", userName);return;}logger.info("发送给{},当前session个数为:{}", userName, webUsers.size());for (int i = 0; i < webUsers.size(); i++) {WebSocketSession session = webUsers.get(i);try {if (!session.isOpen()) {WebSocketUser.removeWebSocketSession(userName, session);}session.sendMessage(message);} catch (IOException e) {e.printStackTrace();}}}
}

3.4 用户与WebSocketSession对应

WebSocketUser是使用内存来存储用户:

package com.cff.springbootwork.websocket.memory;import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;import org.springframework.util.CollectionUtils;
import org.springframework.web.socket.WebSocketSession;public class WebSocketUser {private static Map<String, List<WebSocketSession>> userNameWebsession = new ConcurrentHashMap<>();public static void add(String userName, WebSocketSession webSocketSession) {userNameWebsession.computeIfAbsent(userName, v -> new ArrayList<WebSocketSession>()).add(webSocketSession);}/*** 根据昵称拿WebSocketSession* * @param nickName* @return*/public static List<WebSocketSession> getSessionByUserName(String userName) {return userNameWebsession.get(userName);}/*** 移除失效的WebSocketSession* * @param webSocketSession*/public static void removeWebSocketSession(String userName, WebSocketSession webSocketSession) {if (webSocketSession == null)return;List<WebSocketSession> webSessoin = userNameWebsession.get(userName);if (webSessoin == null || CollectionUtils.isEmpty(webSessoin))return;webSessoin.remove(webSocketSession);}public static Set<String> getUserList(){return userNameWebsession.keySet();}
}

四、用户生成、查询及文件发送

如果我们想发送文件,需要上传文件后转为二进制数据并使用websocket发送。另外,这里使用setUser来产生用户,并提供获取当前用户,用户列表的接口。

package com.cff.springbootwork.websocket.web;import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;import javax.servlet.http.HttpServletRequest;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.socket.BinaryMessage;import com.cff.springbootwork.websocket.dto.ResultModel;
import com.cff.springbootwork.websocket.handler.WebSocketHandler;
import com.cff.springbootwork.websocket.memory.WebSocketUser;@RestController
@RequestMapping("/im")
public class WebSocketController {protected Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate WebSocketHandler websocketHandler;@RequestMapping(value = "/fileUpload")public ResultModel fileUpload(@RequestParam("userName") String userName, @RequestParam MultipartFile[] myfiles,HttpServletRequest request) {logger.info("收到发往用户[{}]的文件上传请求;文件数量:{}", userName, myfiles.length);int count = 0;for (MultipartFile myfile : myfiles) {if (myfile.isEmpty()) {count++;}logger.info("文件原名:{};文件类型:", myfile.getOriginalFilename(), myfile.getContentType());try (ByteArrayOutputStream swapStream = new ByteArrayOutputStream();InputStream is = myfile.getInputStream();) {byte[] buff = new byte[100]; // buff用于存放循环读取的临时数据int rc = 0;while ((rc = is.read(buff, 0, 100)) > 0) {swapStream.write(buff, 0, rc);}byte[] in_b = swapStream.toByteArray(); // in_b为转换之后的结果logger.info("正在发送文件: ");websocketHandler.sendMessageToUser(userName, new BinaryMessage(in_b));} catch (IOException e) {logger.error("文件原名:{}", myfile.getOriginalFilename(), e);e.printStackTrace();count++;continue;}}return ResultModel.ok(count);}@RequestMapping(value = "/setUser")public ResultModel setUser(@RequestParam("userName") String userName, HttpServletRequest request) {logger.info("设置用户[{}]", userName);request.getSession().setAttribute("userName", userName);return ResultModel.ok();}@RequestMapping(value = "/user")public ResultModel user(HttpServletRequest request) {Object userName = request.getSession().getAttribute("userName");if(userName == null)return ResultModel.error("无用户");return ResultModel.ok(userName);}@RequestMapping(value = "/userList")public ResultModel userList() {return ResultModel.ok(WebSocketUser.getUserList());}
}

五、聊天室页面

为了实现我们的简单聊天功能,我们需要前端进行配合。

chat.html实现了简单的聊天室,支持文字、表情、文件等:

该html需要很多js配合,下面贴出html和websocket.js,其他js都是很普遍的js,如果需要我发送,加入群聊向群主索要。

<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>品茗IT-WebSocket测试</title><!-- CSS  --><link href="https://lib.baomitu.com/material-design-icons/3.0.1/iconfont/material-icons.min.css" rel="stylesheet"><link href="https://lib.baomitu.com/materialize/0.100.2/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/><style>
body { text-align:left; margin:0; font:normal 12px Verdana, Arial;
background:#FFEEFF } form { margin:0; font:normal 12px Verdana,
Arial; } table,input { font:normal 12px Verdana, Arial; }
a:link,a:visited{ text-decoration:none; color:#333333; } a:hover{text-decoration:none; color:#FF6600 } #main { width:400px;
position:absolute; left:600px; top:100px; background:#EFEFFF;
text-align:left; filter:Alpha(opacity=90) } #ChatHead {text-align:right; padding:3px; border:1px solid #003399;
background:#DCDCFF; font-size:20px; color:#3366FF; cursor:move; }
#ChatHead a:link,#ChatHead a:visited, { font-size:14px;
font-weight:bold; padding:0 3px } #ChatBody { border:1px solid
#003399; border-top:none; padding:2px; } #ChatContent {height:200px; padding:6px; overflow-y:scroll; word-break: break-all
}#ChatBtn { border-top:1px solid #003399; padding:2px }</style>
</head>
<script type="text/javascript">
var ws = null;
var curUser=null;
var chatUser = null;
var imgName = null;
var fileImgSize = 0;
window.onbeforeunload = function()
{disconnect(ws);
}function gs(d) {var t = document.getElementById(d);if (t) {return t.style;} else {return null;}
}
function gs2(d, a) {if (d.currentStyle) {var curVal = d.currentStyle[a]} else {var curVal = document.defaultView.getComputedStyle(d, null)[a]}return curVal;
}
function ChatHidden() {gs("ChatBody").display = "none";
}
function ChatShow() {gs("ChatBody").display = "";
}
function ChatClose() {gs("main").display = "none";
}
function ChatNew(userId) {gs("main").display = "";chatUser = userId;$("#ChatUsers").html(chatUser);$('.emotion').qqFace({id : 'facebox', assign:'saytext', path: './img/arclist/'   //表情存放的路径});
}
function ChatClear(obj) {$("#ChatContent").html("");
}function ChatRead() {if(document.getElementById(chatUser)){document.getElementById(chatUser).setAttribute('src', './img/users.png');}
}function ChatSend(obj) {var o = obj.ChatValue;var msg = replace_em(o.value);if (o.value.length > 0) {$("#ChatContent").append("<p align=\"right\"><strong>" + curUser + "(我) :</strong>" + msg+ "</p>");var number = $("#ChatContent").scrollTop();number += 16;$("#ChatContent").scrollTop(number);if(ws!=null){var json={"fromUserName":curUser,"targetUserName":chatUser,"message":o.value,"messageType":"1000"};// encodeURI(o.value)console.log(json);ws.send(JSON.stringify(json));}o.value = '';}var img = obj.ChatFile;if (img.value.length > 0){$("#ChatContent").append("<p align=\"right\"><strong>" + nickName + "(我) :</strong>" + img.value+ "</p><br/>");imgName = nickName+'(我)';fileImgSize = img.files.length;//alert(fileImgSize);$.ajaxFileUpload({//处理文件上传操作的服务器端地址(可以传参数,已亲测可用)url:'im/fileUpload?userId='+muserId,secureuri:true,                       //是否启用安全提交,默认为false fileElementId:'ChatFile',           //文件选择框的id属性dataType:'text',                       //服务器返回的格式,可以是json或xml等success:function(data, status){        //服务器响应成功时的处理函数//$("#ChatContent").append("<p align=\"right\">" + data + "</p><br/>");},error:function(data, status, e){ //服务器响应失败时的处理函数$("#ChatContent").append('<p align=\"right\">图片上传失败,请重试!!</p><br/>');imgName = msgUser;}});}
}if (document.getElementById) {(function() {if (window.opera) {document.write("<input type='hidden' id='Q' value=' '>");}var n = 500;var dragok = false;var y, x, d, dy, dx;function move(e) {if (!e)e = window.event;if (dragok) {d.style.left = dx + e.clientX - x + "px";d.style.top = dy + e.clientY - y + "px";return false;}}function down(e) {if (!e)e = window.event;var temp = (typeof e.target != "undefined") ? e.target: e.srcElement;if (temp.tagName != "HTML" | "BODY"&& temp.className != "dragclass") {temp = (typeof temp.parentNode != "undefined") ? temp.parentNode: temp.parentElement;}if ('TR' == temp.tagName) {temp = (typeof temp.parentNode != "undefined") ? temp.parentNode: temp.parentElement;temp = (typeof temp.parentNode != "undefined") ? temp.parentNode: temp.parentElement;temp = (typeof temp.parentNode != "undefined") ? temp.parentNode: temp.parentElement;}if (temp.className == "dragclass") {if (window.opera) {document.getElementById("Q").focus();}dragok = true;temp.style.zIndex = n++;d = temp;dx = parseInt(gs2(temp, "left")) | 0;dy = parseInt(gs2(temp, "top")) | 0;x = e.clientX;y = e.clientY;document.onmousemove = move;return false;}}function up() {dragok = false;document.onmousemove = null;}document.onmousedown = down;document.onmouseup = up;})();}function toIndex(){window.location.href= contextPath + "/index.jsp";}</script>
<body>
<div id="main" class="dragclass" onclick="ChatRead()" style="left: 400px; top: 200px;"><div id="ChatUsers" style="width:100px; padding:3px; font-size:15px;float:left; display:inline"></div><div id="ChatHead"><a href="#" onclick="ChatHidden();">-</a> <a href="#"onclick="ChatShow();">+</a> <a href="#" onclick="ChatClose();">x</a></div><div id="ChatBody"><div id="ChatContent"></div><div id="ChatBtn"><form action="" name="chat" method="post"><textarea name="ChatValue" id="saytext" rows="3" style="width: 350px"></textarea><input name="Submit" type="button" value="发送"onclick="ChatSend(this.form);" /><input name="ClearMsg" type="button" value="清空记录"onclick="ChatClear(this.form);" /><input type="button" class="emotion" value="表情"><input id="ChatFile" type="file" name="myfiles"  multiple>   </form></div></div>
</div>
<div id="modalAddUser" class="modal modal-fixed-footer" style="max-width:400px;max-height:400px"><div class="modal-content"><h4>生成用户名</h4><div class="row center"><input class="browser-default searchInput" placeholder="请输入用户名" style="margin-top:50px;margin-left:20px;max-width:300px" id="catoryAddText" type="text" ></div><div class="row center">                              <a class="waves-effect waves-light btn" id="userAddBtn" style="color:white;"><i class="material-icons" style="font-size:1.1rem">添用户</i></a></div></div><div class="modal-footer"><a href="#!" class=" modal-action modal-close waves-effect waves-green btn-flat">关闭</a></div>
</div>
<div align="left" style="margin-top: 50px;margin-left: 20px;"><p>欢迎您,<span id="userName">匿名用户</span></p><a id="addUser" class="btn waves-effect waves-light white cyan-text" style="border-radius: 40px;">添加用户</a><p id="content"></p>
</div>
<script src="https://lib.baomitu.com/jquery/3.3.0/jquery.min.js"></script>
<script src="https://lib.baomitu.com/materialize/0.100.2/js/materialize.min.js"></script>
<script src="./js/websocket.js"></script>
<script src="./js/ajaxfileupload.js"></script>
<script src="./js/jquery-browser.js"></script>
<script src="./js/jquery.qqFace.js"></script>
<script>
function getUser(){$.ajax({type : "get",url : "im/user",dataType : "json",data : {} ,success : function(data) {if(data.errorCode == "0000"){$("#userName").html(data.data);curUser = data.data;}},error : function(XMLHttpRequest, textStatus, errorThrown) {alert(errorThrown);}});
}
function addUser(userName){$.ajax({type : "post",url : "im/setUser",dataType : "json",data : {"userName":userName} ,success : function(data) {if(data.errorCode == "0000"){$("#userName").html(userName);curUser = data.data;}},error : function(XMLHttpRequest, textStatus, errorThrown) {alert(errorThrown);}});
}
function userList(){$.ajax({type : "get",url : "im/userList",dataType : "json",data : {} ,success : function(data) {if(data.errorCode == "0000"){var content = "";for(var i =0;i<data.data.length;i++){var userId = data.data[i];content += "<img src=\"./img/msgget.gif\" id=\""+ userId+ "\" alt=\"\" style=\"cursor: pointer\" width='40px' "+ "οnclick=\"ChatNew('"+userId+"')\" />"+ userId+ "<br><br>";}$("#content").append(content);}},error : function(XMLHttpRequest, textStatus, errorThrown) {alert(errorThrown);}});
}window.onbeforeunload = function(){disconnect(ws);
}
$(function () {$('.modal').modal({dismissible: true, // 点击模态外面模态消失关闭opacity: 0.1, // 相对于背景的不透明度in_duration: 300, // 显示特效的时间out_duration: 200, // 消失特效时间starting_top: '80%', // 启动时的样式属性ending_top: '20%', // 结束时的样式属性ready: function(modal, trigger) { // 模态加载完成触发事件},complete: function() { } // 关闭时触发的事件});getUser();$("#addUser").click(function() {$('#modalAddUser').modal('open');    });$("#userAddBtn").click(function() {var catory = $('#catoryAddText').val();addUser(catory);});userList();if (ws == null) {var url = getUrl();//alert("url:"+url);  if (!url) {  return;  }  console.log(url);ws = new WebSocket(url);  connect(ws);ChatClose();}
});
</script>
</body>
<script>
var _hmt = _hmt || [];
(function() {var hm = document.createElement("script");hm.src = "https://hm.baidu.com/hm.js?e553ae2bb23494dee9b6f43415a1ce5a";var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s);
})();
</script></html>

这个html需要websocket.js配合:

聊天室界面如下:

六、过程中用到的其他实体

MessageDTO:


ResultModel:


详细完整的代码,可以访问品茗IT-博客《SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室》进行查看

品茗IT-博客专题:https://www.pomit.cn/lecture.html汇总了Spring专题、Springboot专题、SpringCloud专题、web基础配置专题。

快速构建项目

Spring项目快速开发工具:

一键快速构建Spring项目工具

一键快速构建SpringBoot项目工具

一键快速构建SpringCloud项目工具

一站式Springboot项目生成

Mysql一键生成Mybatis注解Mapper

Spring组件化构建

SpringBoot组件化构建

SpringCloud服务化构建

喜欢这篇文章么,喜欢就加入我们一起讨论SpringBoot使用吧!

SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室相关推荐

  1. SpringBoot入门建站全系列(二十八)整合Kafka做日志监控

    SpringBoot入门建站全系列(二十八)整合Kafka做日志监控 一.概述 Apache Kafka是一个分布式发布 - 订阅消息系统和一个强大的队列,可以处理大量的数据,并使您能够将消息从一个端 ...

  2. SpringBoot入门建站全系列(二十六)Mongodb非关系型数据库的使用

    SpringBoot入门建站全系列(二十六)Mongodb非关系型数据库的使用 一.概述 MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写.旨在为 WEB 应用提供可扩展的高性能 ...

  3. SpringBoot入门建站全系列(九)文件上传功能与下载方式

    SpringBoot入门建站全系列(九)文件上传功能与下载方式 Spring对文件上传做了简单的封装,就是用MultipartFile这个对象去接收文件,当然有很多种写法,下面会一一介绍. 文件的下载 ...

  4. SpringBoot入门建站全系列(六)Spring-data-jpa进阶使用

    SpringBoot入门建站全系列(六)Spring-data-jpa进阶使用 上一篇介绍了Mybatis的配置和基本用法<SpringBoot入门建站全系列(五)使用Spring-data-j ...

  5. kafka maven 依赖_SpringBoot入门建站全系列(二十八)整合Kafka做日志监控

    SpringBoot入门建站全系列(二十八)整合Kafka做日志监控 一.概述 Apache Kafka是一个分布式发布 - 订阅消息系统和一个强大的队列,可以处理大量的数据,并使您能够将消息从一个端 ...

  6. spring配置文件_SpringBoot入门建站全系列(二十三)配置文件优先级及自定义配置文件...

    SpringBoot入门建站全系列(二十三)配置文件优先级及自定义配置文件 一.概述 Spring Boot允许多种配置来源,官网是这样说的: Spring Boot使用一种非常特殊的Property ...

  7. python建站部署_SpringBoot入门建站全系列(三十二)接入xxl-job分布式任务调度平台...

    SpringBoot入门建站全系列(三十二)接入xxl-job分布式任务调度平台 一.概述 XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速.学习简单.轻量级.易扩展.现已开放源 ...

  8. springboot mybatis ehcache_SpringBoot入门建站全系列(十四)集成Redis缓存

    SpringBoot入门建站全系列(十四)集成Redis缓存 一.概述 本地缓存,就是使用应用内使用本地内存将数据暂缓存储,一般数据库的查询如果不怎么改动,可以用本地缓存暂存. 远程缓存,比如redi ...

  9. springboot util 测试类怎么写_SpringBoot入门建站全系列(九)文件上传功能与下载方式...

    SpringBoot入门建站全系列(九)文件上传功能与下载方式 Spring对文件上传做了简单的封装,就是用MultipartFile这个对象去接收文件,当然有很多种写法,下面会一一介绍. 文件的下载 ...

最新文章

  1. Python最重要的5大功能,​要是能早点了解就好了
  2. js_高级_表格排序案例---分别用面向过程和面向对象实现
  3. 创建型模式 简单工厂模式
  4. Java 集合系列10: HashMap深入解析(2)
  5. python编程100行_自己动手写100行Python代码抢火车票!
  6. 老板必读:如何找到靠谱活好的人才?
  7. hdu 5280(最大子串和变形,dp)
  8. CSUOJ-1980 不堪重负的数(区间dp)
  9. 年终总结 Trustdata:2017年中国移动互联网行业发展分析报告
  10. 【Touchinput 】创建一个输入法(19)
  11. Oracle视图分类及各种操作讲解(超级好文)
  12. 计算机应用技术专业盲打键盘,一种双手八指轨道定键位盲打器与盲打键盘
  13. EDA技术实用教程 | 复习二 | Verilog基本语法
  14. 良心安利陶瓷材质贴图素材网站
  15. 从零学Java目录导航
  16. 细数我曾经看过的动画片(不完全统计)
  17. daemontoolslite(DAEMONtoolslite 感叹号)
  18. 2019牛客暑期多校训练营(第八场) Beauty Values
  19. 数字IC前端设计精讲课——IC修真院直播
  20. 清除目录下的SVN信息

热门文章

  1. 医学研究人员为什么要学习R语言?哪本书最适合学习?
  2. 云笔记软件有很多,哪款笔记比较好用?
  3. 派生类的构造函数和析构函数
  4. 11_MySQL笔记-主从复制-延迟备份-读写分离
  5. 实现生产者消费者的三种方式
  6. 一语成金,说透管理,点透人生!
  7. 从0糖到0防腐剂,元气森林缘何偏向“虎山行”?
  8. html5游戏 陈书艺,陈书艺:推动游戏前行 开启HTML5游戏之门
  9. 复杂网络基础——《链接》
  10. L1-6 喝嘤料 (15 分)