spring boot+vue实现H5聊天室客服功能
spring boot+vue实现H5聊天室客服功能
h5效果图
vue效果图
功能实现
spring boot
+webSocket
实现- 官方地址 https://docs.spring.io/spring-framework/docs/5.0.8.RELEASE/spring-framework-reference/web.html#websocket
maven 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
</parent>
<groupId>org.example</groupId>
<artifactId>webChat</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</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>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
webSocket
配置
package com.example.webchat.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
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 org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
/**
* @author Mr.Fang
* @title: WebSocketConfig
* @Description: web socket 配置
* @date 2021/11/14 13:12
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "myHandler/") // 访问路径
.addInterceptors(new WebSocketHandlerInterceptor()) // 配置拦截器
.setAllowedOrigins("*"); // 跨域
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192); // 例如消息缓冲区大小、空闲超时等
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
消息处理类
package com.example.webchat.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.webchat.pojo.DataVo;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Mr.Fang
* @title: MyHandler
* @Description: 消息处理类
* @date 2021/11/14 13:12
*/
public class MyHandler extends AbstractWebSocketHandler {
private static int onlineCount = 0;
// 线程安全
private static Map<String, WebSocketSession> userMap = new ConcurrentHashMap<>(); // 用户
private static Map<String, WebSocketSession> adminMap = new ConcurrentHashMap<>(); // 客服
/**
* @Description: 连接成功之后
* @param session
* @return void
* @Author Mr.Fang
* @date 2021/11/14 13:15
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
addOnlineCount(); // 当前用户加 1
System.out.println(session.getId());
Map<String, Object> map = session.getAttributes();
Object token = map.get("token");
Object admin = map.get("admin");
DataVo dataVo = new DataVo();
dataVo.setCode(9001).setMsg("连接成功");
if (Objects.nonNull(admin)) {
adminMap.put(session.getId(), session); // 添加客服
} else {
// 分配客服
userMap.put(session.getId(), session); // 添加当前用户
distribution(dataVo);
}
dataVo.setId(session.getId());
System.out.println("用户连接成功:" + admin);
System.out.println("用户连接成功:" + token);
System.out.println("在线用户:" + getOnlineCount());
this.sendMsg(session, JSONObject.toJSONString(dataVo));
}
/**
* @param vo
* @return void
* @Description: 分配客服
* @Author Mr.Fang
* @date 2021/11/14 13:13
*/
private void distribution(DataVo vo) {
if (adminMap.size() != 0) {
Random random = new Random();
int x = random.nextInt(adminMap.size());
Set<String> values = adminMap.keySet();
int j = 0;
for (String str : values) {
if (j == x) {
vo.setRecId(str);
System.out.println("分配ID:" + str);
break;
}
j++;
}
}
}
/**
* @param session
* @param message
* @return void
* @Description: 收发消息
* @Author Mr.Fang
* @date 2021/11/14 13:13
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.print("用户ID:" + session.getId());
String payload = message.getPayload();
System.out.println("接受到的数据:" + payload);
DataVo dataVo = JSON.toJavaObject(JSON.parseObject(payload), DataVo.class); // json 转对象
if (Objects.isNull(dataVo.getRecId()) || dataVo.getRecId().equals("")) { // 用户客服为空 分配客服
WebSocketSession socketSession = adminMap.get(session.getId());
if (Objects.isNull(socketSession)) {
this.distribution(dataVo);
}
}
if (dataVo.getCode() == 9002) {
if (Objects.nonNull(dataVo.getRecId())) { // user -> admin
WebSocketSession socketSession = adminMap.get(dataVo.getRecId());
dataVo.setSelfId(session.getId()).setRecId("");
this.sendMsg(socketSession, JSONObject.toJSONString(dataVo));
} else if (Objects.nonNull(dataVo.getSelfId())) { // admin ->user
WebSocketSession socketSession = userMap.get(dataVo.getSelfId());
dataVo.setRecId(session.getId()).setSelfId("");
this.sendMsg(socketSession, JSONObject.toJSONString(dataVo));
}
}
}
/**
* @param session
* @param msg
* @return void
* @Description: 发送消息
* @Author Mr.Fang
* @date 2021/11/14 13:14
*/
private void sendMsg(WebSocketSession session, String msg) throws IOException {
session.sendMessage(new TextMessage(msg));
}
/**
* @Description: 断开连接之后
* @param session
* @param status
* @return void
* @Author Mr.Fang
* @date 2021/11/14 13:14
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
subOnlineCount(); // 当前用户加 1
adminMap.remove(session.getId());
userMap.remove(session.getId());
System.out.println("用户断开连接token:" + session.getId());
System.out.println("用户断开连接admin:" + session.getId());
System.out.println("在线用户:" + getOnlineCount());
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* @Description: 在线用户 +1
* @return void
* @Author Mr.Fang
* @date 2021/11/14 13:16
*/
public static synchronized void addOnlineCount() {
MyHandler.onlineCount++;
}
/**
* @Description: 在线用户 -1
* @return void
* @Author Mr.Fang
* @date 2021/11/14 13:16
*/
public static synchronized void subOnlineCount() {
MyHandler.onlineCount--;
}
}
配置拦截器
package com.example.webchat.config;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;
/**
* @author Mr.Fang
* @title: WebSocketHandlerInterceptor
* @Description: 拦截器
* @date 2021/11/14 13:12
*/
public class WebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor {
/**
* @param request
* @param response
* @param wsHandler
* @param attributes
* @return boolean
* @Description: 握手之前
* @Author Mr.Fang
* @date 2021/11/14 13:18
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpServletRequest re = servletRequest.getServletRequest();
Object token = re.getParameter("token");
Object admin = re.getParameter("admin");
if (Objects.isNull(token)) {
return false;
}
re.getSession().setAttribute("admin", admin);
re.getSession().setAttribute("token", token);
return super.beforeHandshake(request, response, wsHandler, attributes);
}
/**
* @param request
* @param response
* @param wsHandler
* @param ex
* @return boolean
* @Description: 握手之后
* @Author Mr.Fang
* @date 2021/11/14 13:18
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
super.afterHandshake(request, response, wsHandler, ex);
}
}
h5服务端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>服务端</title>
<style type="text/css">
#client {
margin: 0px auto;
width: 500px;
}
input {
width: 80%;
height: 40px;
border-radius: 5px;
border-color: #CCCCCC;
outline: #01FA01;
}
#button {
width: 84px;
height: 46px;
background-color: #5af3a5;
color: #fff;
font-size: 20px;
border-radius: 5px;
border: none;
box-shadow: 1px 1px 1px 1px #ccc;
cursor: pointer;
outline: #01FA01;
}
</style>
</head>
<body>
<div id="client">
<h1 style="text-align: center;">服务端发送消息</h1>
<div id="content" contenteditable=true
style="width: 500px;height: 500px;margin: 0px auto;border: 1px solid #000000;padding: 10px;border-radius: 10px;overflow: auto;">
</div>
<div style="padding: 5px;0px">
<input type="" value="" /> <button id="button" type="button">发送</button>
</div>
</div>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script type="text/javascript">
$(() => {
var pushData = {
code: 9002,
msg: '',
selfId: '',
};
var time = null;
var path = 'ws://127.0.0.1:8009/myHandler/';
if (typeof(WebSocket) === "undefined") {
alert('不支持websocket')
return;
}
let id = Math.random(); // 随机数
// 实例化socket
var webSocket = new WebSocket(path + '?token=' + id+'&admin=1');
// 监听连接
webSocket.onopen = function(event) {
console.log(event);
interval();
};
// 监听消息
webSocket.onmessage = function(event) {
let data = JSON.parse(event.data);
pushData.selfId = data.selfId;
if (data.code == 9002) {
$('#content').append(
`<p style="text-align: right;"><span style="color:chocolate;">${data.msg}</span>:客户端</p>`
)
} else if (data.code == 9001) {
$('#content').append(`<p style="color:#a09b9b;text-align:center;" >连接成功</p>`);
}
console.log(event)
};
// 监听错误
webSocket.onerror = function(event) {
console.log(event)
$('#content').append(`<p style="color:#a09b9b;text-align:center;" >连接错误</p>`);
clearInterval();
};
// 发送消息
$('#button').click(() => {
let v = $('input').val();
if (v) {
pushData.code = 9002;
pushData.msg = v;
webSocket.send(JSON.stringify(pushData));
$('#content').append(
`<p>服务端:<span style="color: blueviolet;">${v}</span></p>`
)
$('input').val('');
}
})
function interval() {
time = setInterval(() => {
pushData.code = 9003;
pushData.msg = '心跳';
webSocket.send(JSON.stringify(pushData));
}, 5000);
}
function clearInterval() {
clearInterval(time);
}
})
</script>
</body>
</html>
客户端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>客户端</title>
<style type="text/css">
#client {
margin: 0px auto;
width: 500px;
}
input {
width: 80%;
height: 40px;
border-radius: 5px;
border-color: #CCCCCC;
outline: #01FA01;
}
#button {
width: 84px;
height: 46px;
background-color: #5af3a5;
color: #fff;
font-size: 20px;
border-radius: 5px;
border: none;
box-shadow: 1px 1px 1px 1px #ccc;
cursor: pointer;
outline: #01FA01;
}
</style>
</head>
<body>
<div id="client">
<h1 style="text-align: center;">客户端发送消息</h1>
<div id="content" contenteditable=true
style="width: 500px;height: 500px;margin: 0px auto;border: 1px solid #000000;padding: 10px;border-radius: 10px;overflow: auto;">
</div>
<div style="padding: 5px;0px">
<input type="" value="" /> <button id="button" type="button">发送</button>
</div>
</div>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script type="text/javascript">
$(() => {
var pushData = {
code: 9002,
msg: '',
recId: '',
};
var time = null;
var path = 'ws://127.0.0.1:8009/myHandler/';
if (typeof(WebSocket) === "undefined") {
alert('不支持websocket')
return;
}
let id = Math.random(); // 随机数
// 实例化socket
var webSocket = new WebSocket(path + '?token=' + id);
// 监听连接
webSocket.onopen = function(event) {
console.log(event);
interval();
};
// 监听消息
webSocket.onmessage = function(event) {
let data = JSON.parse(event.data);
if (data.code == 9002) {
$('#content').append(
`<p style="text-align: right;"><span style="color:chocolate;">${data.msg}</span>:服务端</p>`
)
} else if (data.code == 9001) {
$('#content').append(`<p style="color:#a09b9b;text-align:center;" >连接成功</p>`);
}
console.log(event)
};
// 监听错误
webSocket.onerror = function(event) {
console.log(event)
$('#content').append(`<p style="color:#a09b9b;text-align:center;" >连接错误</p>`);
clearInterval();
};
// 发送消息
$('#button').click(() => {
let v = $('input').val();
if (v) {
pushData.code = 9002;
pushData.msg = v;
webSocket.send(JSON.stringify(pushData));
$('#content').append(
`<p>客户端:<span style="color: blueviolet;">${v}</span></p>`
)
$('input').val('');
}
})
function interval() {
time = setInterval(() => {
pushData.code = 9003;
pushData.msg = '心跳';
webSocket.send(JSON.stringify(pushData));
}, 5000);
}
function clearInterval() {
clearInterval(time);
}
})
</script>
</body>
</html>
vue
连接webSocket
<template>
<div class="chat">
<van-nav-bar fixed placeholder title="聊天内容" left-arrow />
<div id="content" ref="rightBody">
<div v-for="item in list" :key="item.id">
<div class="chat-model" v-if="item.isSelf">
<div>
<van-image width="45px" height="45px" fit="fill" round src="https://img01.yzcdn.cn/vant/cat.jpeg" />
</div>
<div class="chat-content chat-content-l">
{{item.content}}
</div>
</div>
<div class="chat-model" style="justify-content: flex-end" v-else>
<div class="chat-content chat-content-r">
{{item.content}}
</div>
<div>
<van-image width="45px" height="45px" fit="fill" round src="https://img01.yzcdn.cn/vant/cat.jpeg" />
</div>
</div>
</div>
</div>
<div id="bottom">
<input type="text" v-model="text" />
<van-button @click="onSend">发送</van-button>
</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
path: "ws://192.168.31.156:8009/myHandler/", // socket 地址
socket: "",
text: '',
data: {
code: 9002,
msg: '',
recId: '',
},
list: [],
time: '', // 定时器
}
},
created() {
this.init()
},
methods: {
onSend() {
if (this.socket.readyState != 1) {
this.$toast('连接失败请重新进入');
return;
}
if (!this.text) {
this.$toast('请输入内容')
return;
}
var data = {
avator: 'https://img01.yzcdn.cn/vant/cat.jpeg',
content: this.text,
isSelf: false
}
this.list.push(data);
this.send()
this.text = '';
this.$refs.rightBody.scrollTop = this.$refs.rightBody.scrollHeight;
},
init: function() {
// 0 CONNECTING 连接尚未建立
// 1 OPEN WebSocket的链接已经建立
// 2 CLOSING 连接正在关闭
// 3 CLOSED 连接已经关闭或不可用
if (typeof(WebSocket) === "undefined") {
this.$toast('您的浏览器不支持socket')
} else {
let id = Math.random(); // 随机数
// 实例化socket
this.socket = new WebSocket(this.path + '?token=' + id);
// 监听socket连接
this.socket.onopen = this.open
// 监听socket错误信息
this.socket.onerror = this.error
// 监听socket消息
this.socket.onmessage = this.getMessage
// this.onHeartbeat(); // 心跳防止断开连接
}
},
open: function() {
this.$toast('连接成功')
},
error: function() {
this.$toast('连接失败')
},
getMessage: function(res) {
let t = JSON.parse(res.data);
var data = {
avator: 'https://img01.yzcdn.cn/vant/cat.jpeg',
content: t.msg,
isSelf: true
}
if (t.code == 9002) {
this.list.push(data);
}
this.data.recId = t.recId;
this.$refs.rightBody.scrollTop = this.$refs.rightBody.scrollHeight;
},
send: function() {
if (this.socket) {
this.data.code = 9002;
this.data.msg = this.text;
this.socket.send(JSON.stringify(this.data))
}
},
close: function() {
console.log("socket已经关闭")
},
onHeartbeat() {
var time = setInterval(() => {
this.data.code = 9003;
this.data.msg = '心跳';
this.socket.send(JSON.stringify(this.data))
}, 5000);
this.time = time;
}
},
destroyed() {
// 销毁监听
clearInterval(this.time);
this.socket.onclose = this.close
}
}
</script>
<style>
.chat {
height: 100vh;
background-color: #f1f1f3;
}
#content {
overflow: auto;
height: 100vh;
padding-bottom: 100px;
background-color: #f1f1f3;
}
#bottom {
position: fixed;
bottom: 0px;
width: 100%;
display: flex;
justify-content: space-evenly;
padding: 10px 0px;
background-color: #F1F1F3;
}
#bottom input {
background-color: white;
width: 72%;
height: 30px;
padding: 3px 5px;
vertical-align: sub;
border-style: none;
border-radius: 5px;
}
#bottom button {
height: 32px;
background-color: rgb(245, 158, 1);
border-radius: 5px;
color: #fff;
}
.chat-model {
display: flex;
flex-direction: row;
margin: 10px 10px;
margin-top: 30px;
align-items: center;
}
.chat-content {
position: relative;
max-width: 67%;
word-break: break-all;
word-wrap: break-word;
top: 18px;
padding: 10px;
border-radius: 5px;
background-color: white;
}
.chat-content-r {
right: 10px;
}
.chat-content-l {
left: 10px;
}
</style>
源码地址 https://gitee.com/bxmms/web-chat.git
spring boot+vue实现H5聊天室客服功能的更多相关文章
- 利用spring boot+vue做的一个博客项目
技术栈: 后端 Springboot druid Spring security 数据库 MySQL 前端 vue elementUI 项目演示: GitHub地址: 后端:https://githu ...
- spring boot + vue + element-ui全栈开发入门——开篇
最近经常看到很多java程序员朋友还在使用Spring 3.x,Spring MVC(struts),JSP.jQuery等这样传统技术.其实,我并不认为这些传统技术不好,而我想表达的是,技术的新旧程 ...
- spring boot + vue + element-ui全栈开发入门——基于Electron桌面应用开发
前言 Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库. Electron通过将Chromium和Node.js合并到同一个运行时环 ...
- 前后端分离,我怎么就选择了 Spring Boot + Vue 技术栈?
前两天又有小伙伴私信松哥,问题还是职业规划,Java 技术栈路线这种,实际上对于这一类问题我经常不太敢回答,每个人的情况都不太一样,而小伙伴也很少详细介绍自己的情况,大都是一两句话就把问题抛出来了,啥 ...
- spring boot + vue + element-ui全栈开发入门
今天想弄弄element-ui 然后就在网上找了个例子 感觉还是可以用的 第一步是完成了 果断 拿过来 放到我这里这 下面直接是连接 点进去 就可以用啊 本想着不用vue 直接导入连接 ...
- 一个实际的案例介绍Spring Boot + Vue 前后端分离
介绍 最近在工作中做个新项目,后端选用Spring Boot,前端选用Vue技术.众所周知现在开发都是前后端分离,本文就将介绍一种前后端分离方式. 常规的开发方式 采用Spring Boot 开发项目 ...
- spring boot + vue + element-ui
spring boot + vue + element-ui 一.页面 1.布局 假设,我们要开发一个会员列表的页面. 首先,添加vue页面文件“src\pages\Member.vue” 参照文档h ...
- 喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了
折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...
- 部署spring boot + Vue遇到的坑(权限、刷新404、跨域、内存)
部署spring boot + Vue遇到的坑(权限.刷新404.跨域.内存) 项目背景是采用前后端分离,前端使用vue,后端使用springboot. 工具 工欲善其事必先利其器,我们先找一个操作L ...
随机推荐
- 鸿蒙源码分析系列(总目录) | 百万汉字注解 百篇博客分析 | 深入挖透OpenHarmony源码 | v8.23
百篇博客系列篇.本篇为: v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o 百篇博客.往期回顾 在给OpenHarmony内核源码加注过程中,整理出以下 ...
- JVM学习笔记——堆
堆 Heap 一个 JVM 只有一个堆,堆也是 Java 内存管理的核心区域.在 JVM 启动时堆被创建,同时大小在启动时已设定好,堆是 JVM 管理最大的一块内存空间,其大小可以调节. 堆的内存空间 ...
- 记一次 .NET 某招聘网后端服务 内存暴涨分析
一:背景 1. 讲故事 前段时间有位朋友wx找到我,说他的程序存在内存阶段性暴涨,寻求如何解决,和朋友沟通下来,他的内存平时大概是5G 左右,在某些时点附近会暴涨到 10G+, 画个图大概就是这样. ...
- dubbo-admin的使用
目录 了解 dubbo-admin 下载 dubbo-admin 使用 dubbo-admin 1.dubbo-admin是什么 dubbo-admin是一个监控程序,可以通过web很方便的管理监控众 ...
- C 编译预处理和宏
前置知识 0x00 cmd编译运行程序 https://blog.csdn.net/WWIandMC/article/details/106265734 0x01 --save-temps gcc m ...
- 记一个非常诡异的关于 shared_ptr 的 bug
问题描述 今天写项目的时候遇见一个特别诡异的 bug,体现在在执行某条语句时,程序会莫名崩溃,并且给出的错误信息也非常难懂,只有一个malloc(): invalid size (unsorted)错 ...
- Scrum Meeting 0605
零.说明 日期:2021-6-5 任务:简要汇报两日内已完成任务,计划后两日完成任务 一.进度情况 组员 负责 两日内已完成的任务 后两日计划完成的任务 困难 qsy PM&前端 暂无 重新设 ...
- Prometheus的单机部署
Prometheus的单机部署 一.什么是Prometheus 二.Prometheus的特性 三.支持的指标类型 1.Counter 计数器 2.Gauge 仪表盘 3.Histogram 直方图 ...
- 如何用PADS进行PCB设计?这6步就够了
在使用PADS进行PCB设计的过程中,需要对印制板的设计流程以及相关的注意事项进行重点关注,这样才能更好的为工作组中的设计人员提供系统的设计规范,同时也方便设计人员之间进行相互的交流和检查. 02 设 ...
- 从零开始的DIY智能家居 - 基于 ESP32 的智能浇水器
前言 上次 土壤湿度传感器 完成之后,就立下一个 flag 要搭建一个智慧浇水的智能场景,现在终于有时间填坑了!(o゚▽゚)o 智慧浇水场景的核心设备有三个: 检测土壤状态的:土壤湿度传感器 通过这个 ...