WebSocket实现HTML+SpringBoot聊天功能,小程序+SpringBoot聊天功能
目录
一、认识WebSocket
二、HTML实现聊天
三、微信小程序实现聊天
一、认识WebSocket
1.首先博主在初学Java时自我感觉走了很多弯路,因为以前见识短,在接触聊天功能时根本就没能想到有WebSocket这个聊天框架,就只能用底层的UDP或TCP实现聊天功能,及其繁琐。
1.在入门Java后的朋友学到网络编程会知道UDP和TCP两个知识点,没错WebSocket是一种在单个TCP连接上进行全双工通信的协议。基于TCP协议的一个框架,TCP知识点比较多,具体咱们就不多说了,直接实践怎么使用吧。
二、HTML实现聊天
首先我先贴出完整代码,然后解释
1.html代码,这里我就不单独写js文件了(这个html实现的是一对一聊天,还有一对多,多对多群聊)
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="https://cdn.jsdelivr.net/sockjs/1.1.4/sockjs.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script><script>var stompClient = null;function connect() {var content = $("#message").val();var sender = 'tbb';var socket = new SockJS('http://127.0.0.1:8080/chat');stompClient = Stomp.over(socket);stompClient.connect({}, function (frame) {console.log('Connected: ' + frame);stompClient.subscribe('/user/tbb/message', function (message) {showMessage(JSON.parse(message.body));});});}function disconnect() {if (stompClient !== null) {stompClient.disconnect();}console.log("Disconnected");}function sendMessage() {var content = $("#message").val();var sender = $("#sender").val();stompClient.send("/app/chat/send/to/user", {}, JSON.stringify({content: content,sender: sender,unionId: sender,}));}function showMessage(message) {$("#chat").append("<div>" + message.sender + ": " + message.content + "</div>");}
</script><div><input type="text" id="sender" placeholder="Enter your name"><input type="text" id="message" placeholder="Type a message..."><button onclick="sendMessage()">Send</button><button onclick="connect()">Connect</button><button onclick="disconnect()">Disconnect</button>
</div><div id="chat">123</div>
2.SpringBoot完整代码
(1)WebSocketConfig.java配置文件(关键文件)
package com.example.mengchuangyuan.common.chat.config;import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker("/user","/agent","/topic"); // 定义消息代理,客户端订阅的地址前缀config.setApplicationDestinationPrefixes("/app"); // 定义客户端发送消息的地址前缀config.setUserDestinationPrefix("/user");}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/chat").setAllowedOriginPatterns("*").withSockJS(); // 定义WebSocket端点,客户端连接的地址}
}
(2)ChatController.java控制层
package com.example.mengchuangyuan.common.chat.controller;import com.example.mengchuangyuan.common.chat.entry.ChatMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;@Slf4j
@Controller
public class ChatController {@Autowiredprivate SimpMessagingTemplate simpMessagingTemplate;/*** 发消息(群发)* @param chatMessage 获取用户消息* @return 返回要回复的消息*///第一种方式@MessageMapping("/chat/send") // 定义消息映射路径@SendTo("/agent/public") // 发送消息到指定的代理路径public ChatMessage sendMessage(ChatMessage chatMessage) {log.info(chatMessage.toString());return chatMessage;}//方法二
// @MessageMapping("/agent/send")
// public void getAgentInfo (@Payload ChatMessage chatMessage) {
// System.out.println("发送群发消息");
// // 使用api进行推送
// simpMessagingTemplate.convertAndSend("/agent/public2", chatMessage);
// }/*** 发送给自己?* @param chatMessage* @return*/@MessageMapping("/agent/send/user")
// 这里的路径必须还是以广播的前缀为前缀,否则无法接收@SendToUser("/agent/info")public ChatMessage sendUserMessage(ChatMessage chatMessage) {log.info(chatMessage.toString());return chatMessage;}/*** 发送给指定用户* @param chatMessage* @return*/@MessageMapping("/chat/send/to/user")
// 这里的路径必须还是以广播的前缀为前缀,否则无法接收public void sendToUserMessage(ChatMessage chatMessage) {log.info("发送给指定用户:"+chatMessage.toString());simpMessagingTemplate.convertAndSendToUser(chatMessage.getUnionId(),"/message",chatMessage);}
}
package com.example.mengchuangyuan.common.chat.entry;import lombok.Data;@Data
public class ChatMessage {private String unionId;private String content;private String sender;
}
以上就是完整代码
3.接下来我来简单解释一下,因为一对一聊天比其他相对绕一点,所以博主就解释它就好了,且看下面四段被截取的代码
function connect() {var content = $("#message").val();//发送的消息内容var sender = 'tbb';//接收的人var socket = new SockJS('http://127.0.0.1:8080/chat');//连接后端socket的地址stompClient = Stomp.over(socket);stompClient.connect({}, function (frame) {console.log('Connected: ' + frame);//这里我写死了,接收来自tbb这个人的消息,'/user/tbb/message'可以改成'/user/'+sender+'/message'stompClient.subscribe('/user/tbb/message', function (message) { showMessage(JSON.parse(message.body));});});}
上面的代码可以称之为连接服务器并且实时监听tbb给后端发来的消息
package com.example.mengchuangyuan.common.chat.config;import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker("/user","/agent","/topic"); // 定义消息代理,客户端订阅的地址前缀config.setApplicationDestinationPrefixes("/app"); // 定义客户端发送消息的地址前缀config.setUserDestinationPrefix("/user");}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/chat").setAllowedOriginPatterns("*").withSockJS(); // 定义WebSocket端点,客户端连接的地址}
}
上面两段是对应的,在html的http://127.0.0.1:8080/chat中chat对应上面Java代码的chat,必须一致,要不然连不上,/user/tbb/message路由中user对应上面Java代码的user,必须一致,要不然发消息时对方收不到消息(博主以前踩过这个坑),而这个tbb则是接收发消息的人的消息
function sendMessage() { //发消息给对方var content = $("#message").val();var sender = $("#sender").val();stompClient.send("/app/chat/send/to/user", {}, JSON.stringify({ //发消息的后端的路由content: content,//消息内容sender: sender,//消息姓名unionId: sender,//消息id,这里我把消息姓名也作为id}));}
/*** 发送给指定用户* @param chatMessage* @return*/@MessageMapping("/chat/send/to/user")
// 这里的路径必须还是以广播的前缀为前缀,否则无法接收public void sendToUserMessage(ChatMessage chatMessage) {log.info("发送给指定用户:"+chatMessage.toString());//chatMessage.getUnionId()是发送给某人的id,/message对应四段中第一段的/user/tbb/message中的message
simpMessagingTemplate.convertAndSendToUser(chatMessage.getUnionId(),"/message",chatMessage);}
最后两段是对应的,用于给对方发消息。
三、微信小程序实现聊天
首先,这是博主自己摸索了很久出来的一套小程序聊天体系。
1.聊天数据结构及框架
涉及到了redis缓存,因此需要下载redis的依赖包
聊天数据结构如下(自我感觉存在一定缺陷,懒得改进了):
整体储存结构:
聊天界面结构:{openid1+openid2:{linkType:[info1,info2]}},
如图聊天界面:
聊天列表结构:{linkType:[openid1+openid2,openid3+openid4]}
如图聊天列表:
info的结构:{mid:"",type:"",linkType:"",formUser:fromUser,toUser:toUser,message:"",date:"",nowDate:""}
fromUser和toUser的结构:{openid:"",phone:"",name:"",headImg:""}
openid1和openid2为fromUser和toUser的openid
linkType:属于哪个板块聊天(比如相亲聊天板块或者外卖或商城聊天板块)
type:作用于获取redis缓存的聊天记录和聊天心跳检测(备注:因为获取历史聊天记录和心跳检测是以聊天方式向后端发起请求,因此我用type的聊天要区分是用户发起聊天还是其他请求。由前端自动发动聊天请求,获取历史聊天记录,由前端发起聊天请求检测心跳,检测心跳的目的是为了确保聊天过程不掉线)
date:聊天的时间段(备注:可以设置5分钟显示一个时间段聊天的时间,比如微信隔5分钟后再发一条信息上面会显示时间,这里我设置的date就充当这个角色)
nowDate:每一句话的时间,主要用于计算当前时间是否与上一句聊天记录的时间是否间隔5分钟,如果间隔5分钟那么上面的date记录该时间,如果间隔不到5分钟,则date设置为空。
整体结构上:两个人的openid连接作为获取他们之间所有功能板块历史聊天记录的内容的键(key)。以linkType作为键(key)获取某个功能板块的所有历史聊天记录,这里聊天记录用集合来储存保证了聊天记录顺序。
2.Java SpringBoot代码
(1)MiniWebSocketConfig.java文件
package com.example.mengchuangyuan.common.chat.mini.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class MiniWebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
(2)MiniWebSocketController.java文件
package com.example.mengchuangyuan.common.chat.mini.controller;import com.example.mengchuangyuan.common.redis.tool.SufferVariable;
import com.example.mengchuangyuan.common.tool.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.util.*;@Slf4j
@RestController
@RequestMapping("/mini/socket")
public class MiniWebSocketController {@Value("${upload.messageChatUrl}")private String messageChatUrl;@Value("${upload.messageChatPath}")private String messageChatPath;private final List<Map<String,Object>> mapList = new ArrayList<>();@GetMapping("/get/openid/history")private Result getOpenidHistory(@RequestParam("openid") String openid,@RequestParam("linkType") String linkType){
// log.info(openid);List<String> openids = (List<String>) SufferVariable.openidKeys.get(linkType);
// if (keyMap==null){
// return Result.error();
// }
// List<String> openids = (List<String>) keyMap.get(openid);
// log.info("所以openid"+String.valueOf(openids));if (openids==null){return Result.error();}mapList.clear();for (String openid1: openids) {if (openid1.contains(openid)) {
// if (SufferVariable.messageMap.get(openid1)!=null){mapList.add((Map<String, Object>) SufferVariable.messageMap.get(openid1));
// log.info("聊天记录缓存"+openid);
// log.info(String.valueOf(SufferVariable.messageMap.get(openid1)));// }}}return Result.success(mapList);}//上传聊天图片@PostMapping("/img/upload")public Result imgUpDown(@RequestParam("file") MultipartFile file,@RequestParam("filename") String filename) throws IOException {System.out.println(filename);File file1 = new File(messageChatPath,filename);if(!file1.exists()) {if(!file1.mkdirs()){ //创建目录return Result.error();}}//获取文件名String fileName = file.getOriginalFilename();//获取文件后缀名。也可以在这里添加判断语句,规定特定格式的图片才能上传,否则拒绝保存。String suffixName = fileName.substring(fileName.lastIndexOf("."));//为了避免发生图片替换,这里使用了文件名重新生成fileName = UUID.randomUUID()+suffixName;file.transferTo(new File(file1,fileName));return Result.success(messageChatUrl+filename+"/"+fileName);}
}
(3)WebSocketEndPoint.java文件
package com.example.mengchuangyuan.common.chat.mini.mapper;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.mengchuangyuan.common.redis.mapper.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;//对外公布的一个后端站点
//ws://localhost:8080/websocket/用户id
@ServerEndpoint(value = "/websocket/{userId}")
@Component
@Slf4j
public class WebSocketEndPoint {//与某个客户端的连接会话,需要他来给客户端发送数据private Session session;@Autowiredprivate SessionPool sessionPool;
// @Autowired
// private RedisUtils redisUtils;private static WebSocketEndPoint webSocketEndPoint;//初始化 ②@PostConstructpublic void init() {webSocketEndPoint = this;webSocketEndPoint.sessionPool = this.sessionPool;}//连接建立成功调用的方法@OnOpenpublic void onOpen(Session session, @PathParam("userId") String userId) {//把会话加入连接池中//userId通过用户传入,session是系统自动产生SessionPool.sessions.put(userId, session);//TODO 可以添加日志操作}//关闭会话的时候@OnClosepublic void onClose(Session session) throws IOException {webSocketEndPoint.sessionPool.close(session.getId());session.close();}//接收客户端的消息后调用的方法,在这里可以进行各种业务逻辑的操作@OnMessagepublic void onMessage(String message, Session session) {System.out.println(message);
// log.info("redisUtils:"+redisUtils);//心跳检测if (message.equalsIgnoreCase("ping")) {try {Map<String, Object> params = new HashMap<>();params.put("type", "pong");session.getBasicRemote().sendText(JSON.toJSONString(params));} catch (Exception e) {e.printStackTrace();}return;}//将Json字符串转为键值对
// HashMap params = JSON.parseObject(message, HashMap.class);JSONObject params = JSON.parseObject(message);webSocketEndPoint.sessionPool.sendMessage(params);//这里的业务逻辑仅仅是把收到的消息返回给前端
// SessionPool.sendMessage(message);}
}
(4)SessionPool.java文件
package com.example.mengchuangyuan.common.chat.mini.mapper;import com.alibaba.fastjson.JSON;
import com.example.mengchuangyuan.common.redis.mapper.RedisUtils;
import com.example.mengchuangyuan.common.redis.tool.SufferVariable;
import com.example.mengchuangyuan.common.tool.DateYMDms;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;import javax.websocket.Session;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;@Component
@Slf4j
public class SessionPool {@Autowiredprivate RedisUtils redisUtils;//key-value : userId - 会话(系统创建)public static Map<String, Session> sessions = new ConcurrentHashMap<>();//避免多线程问题public void close(String sessionId) {//sessionId是在session中添加了一个标识,准确定位某条sessionfor (String userId : SessionPool.sessions.keySet()) {Session session = SessionPool.sessions.get(userId);if (session.getId().equals(sessionId)) {sessions.remove(userId);break;}}}public void sendMessage(String userId, String message) {sessions.get(userId).getAsyncRemote().sendText(message);}//消息的群发,业务逻辑的群发public void sendMessage(String message) {
// redisUtils.cacheValue("chatMessage","String.valueOf(SufferVariable.messageMap)");for (String sessionId : SessionPool.sessions.keySet()) {SessionPool.sessions.get(sessionId).getAsyncRemote().sendText(message);}}//点对点的消息推送public void sendMessage(Map<String, Object> params) {log.info("消息内容:"+String.valueOf(params));long mid = System.currentTimeMillis();Map<String,Object> formUser = (Map<String, Object>) params.get("formUser");Map<String,Object> toUser = (Map<String, Object>) params.get("toUser");String userId = formUser.get("openid").toString();String toUserId = toUser.get("openid").toString();
// String msg = params.get("message").toString();String type = params.get("type").toString();String linkType = params.get("linkType").toString();//获取用户sessionSession session = sessions.get(toUserId);Map<String,Object> keyMap;List<String> setOpenid;Map<String,Object> map;List<Object> list;String uid = userId+toUserId;params.put("mid",mid);if (SufferVariable.messageMap.get(userId+toUserId)==null&&SufferVariable.messageMap.get(toUserId+userId)==null){
// messageMap.put(userId+toUserId,userId+toUserId);map = new HashMap<>();list = new ArrayList<>();map.put(linkType,list);SufferVariable.messageMap.put(uid,map);// list.add(params);}else {if (SufferVariable.messageMap.get(userId+toUserId)!=null){uid = userId+toUserId;}else {uid = toUserId+userId;}map = (Map<String, Object>) SufferVariable.messageMap.get(uid);list = (List<Object>) map.get(linkType);}//获取历史记录if (type.equalsIgnoreCase("history")){if (sessions.get(userId) != null) {Map<String, Object> map2 = new HashMap<>();
// map = (Map<String, Object>) SufferVariable.messageMap.get(uid);map2.put("type", "isHistory");map2.put("message",list);sessions.get(userId).getAsyncRemote().sendText(JSON.toJSONString(map2));}return;}String nowDate = DateYMDms.getUtilDate();if (list.size()!=0) {Map<String, Object> lastMap = (Map<String, Object>) list.get(list.size() - 1);String date = DateYMDms.getYMDms(5, lastMap.get("nowDate").toString());params.put("date", date);}else {params.put("date", nowDate);}
// if (SufferVariable.openidKeys.get(linkType)==null){
// keyMap = new HashMap<>();
// }else {
// keyMap = (Map<String, Object>) SufferVariable.openidKeys.get(linkType);
// }
// if (keyMap.get(userId)==null){
// setOpenid = new ArrayList<>();
// }else {
// log.info(String.valueOf(SufferVariable.openidKeys));
// setOpenid = (List<String>) keyMap.get(userId);
// }if (SufferVariable.openidKeys.get(linkType) == null){setOpenid = new ArrayList<>();}else {setOpenid = (List<String>) SufferVariable.openidKeys.get(linkType);}setOpenid.add(uid);Set<String> set = new LinkedHashSet<>(setOpenid);setOpenid.clear();setOpenid.addAll(set);
// keyMap.put(userId,setOpenid);SufferVariable.openidKeys.put(linkType,setOpenid);params.put("nowDate",nowDate);list.add(params);map.put(linkType,list);SufferVariable.messageMap.put(uid,map);redisUtils.cacheValue("chatMessage",SufferVariable.messageMap);redisUtils.cacheValue("chatOpenidKeys",SufferVariable.openidKeys);if (session != null) {log.info(uid+":"+String.valueOf(SufferVariable.messageMap.get(uid)));session.getAsyncRemote().sendText(JSON.toJSONString(params));}
// log.info(String.valueOf(messageMap));
// params.remove("formUserId");//session不为空的情况下进行点对点推送}
}
以上小程序相关代码存在一些缺陷,并且未完整,
需要小程序及SpringBoot完整代码的朋友可以私信博主,
好了本次分享就到此结束。
相关文章:
WebSocket实现HTML+SpringBoot聊天功能,小程序+SpringBoot聊天功能
目录 一、认识WebSocket 二、HTML实现聊天 三、微信小程序实现聊天 一、认识WebSocket 1.首先博主在初学Java时自我感觉走了很多弯路,因为以前见识短,在接触聊天功能时根本就没能想到有WebSocket这个聊天框架,就只能用底层的UDP或TCP实现聊…...
SpringMVC-RESTFul
文章目录 RESTFul一、基础概念二、增删改查1.查询全部用户信息 (GET)2.根据id查询用户信息3.添加用户(POST)4.修改用户 (PUT)5.删除用户 (DELETE) RESTFul 一、基础概念 二、增删改…...
Spring Boot3整合knife4j(swagger3)
目录 1.前置条件 2.导依赖 3.配置 1.前置条件 已经初始化好一个spring boot项目且版本为3X,项目可正常启动。 作者版本为3.2.2 初始化教程: 新版idea创建spring boot项目-CSDN博客https://blog.csdn.net/qq_62262918/article/details/135785412?…...
解决Windows系统本地端口被占用
目录 一、被程序占用端口 1.通过终端杀掉占用端口的进程 2.任务管理器 二、被系统列为保留端口 前言: 首先了解为什么会出现端口被占用的情况 端口被占用的情况可能出现的原因有很多,主要有以下几点: 1.多个应用程序同时启动&…...
GPS位置虚拟软件 AnyGo mac激活版
AnyGo for Mac是一款一键将iPhone的GPS位置更改为任何位置的强大软件!使用AnyGo在其iOS或Android设备上改变其GPS位置,并在任何想要的地方显示自己的位置。这对那些需要测试应用程序、游戏或其他依赖于地理位置信息的应用程序的开发人员来说非常有用&…...
视频号视频怎么使用视频号下载助手提取视频呢?
微信视频号怎么使用视频下载助手提取视频,今天就和大家一起来看看我是如何操作的。 关于视频下载助手,给大家准备好了。获取方式在文末。注意看下关键词,家人们。 微信视频号是微信平台上的一个短视频分享功能,类似于抖音、快手这…...
第一篇【传奇开心果短博文系列】鸿蒙开发技术点案例示例:从helloworld开始理解鸿蒙开发ArkTS编程思路
传奇开心果短博文系列 系列短博文目录鸿蒙开发技术点案例示例系列 短博文目录一、前言二、初步解读鸿蒙的helloworld三、进一步深入解读理解 系列短博文目录 鸿蒙开发技术点案例示例系列 短博文目录 一、前言 从掰碎了揉烂了详细注释解读helloworld开始,理解Ark…...
四、MySQL之DML DQL
有关数据表的DML操作 INSERT 针对于数据的插入DELETE 针对于数据的删除UPDATE 针对于数据的修改 4.1 INSERT语句 INSERT INTO 表名 [(列名1,列名2,....)] VALUES (值1,值2,...); 默认情况下,一条插入命令只针对一行进行影响INSERT INTO 表…...
YOLOv8优化策略:注意力涨点系列篇 | 多尺度双视觉Dualattention | Dual-ViT,顶刊TPAMI 2023
🚀🚀🚀本文改进:多尺度双视觉Dualattention注意yolo,提升小目标检测能力 🚀🚀🚀YOLOv8改进专栏:http://t.csdnimg.cn/hGhVK 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1.原理介绍 论文:Dual Vision Transformer | IEEE Journals & Magazine …...
视频渲染靠cpu还是显卡 会声会影视频渲染的作用是什么
视频渲染最占用的资源就是CPU,多核心多线程,这样才能渲染快。渲染可以在时间线上实时平滑预览,便于编辑,最终导出成片的时候速度也会快一些,渲染就是对每桢的图像进行重新优化的过程。 渲染的作用主要是能够保证使用者…...
v-if 导致 elementui 表单校验失效问题解决
问题 在使用 elementui 表单的过程中,某些表单项需要通过 v-if 来判断是否展示,但是这些表单项出现了检验失效的问题。 解决方法 1、给需要 v-if 判断的表单项添加 key 值 <el-form ref"form" :model"form"><el-form-i…...
Linux本地部署SVN服务结合内网穿透实现远程访问
文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…...
短信平台(电信)
通信方式 采用http1.1通信方式,数据以post方式提交 http 头设置:application/json 签名 采用MD5加密方式, 源字符串采用字段拼接方式 签名中appSecret是平台分配密码 签名方法: 如:String signmd5(param1param2param3…paramN) …...
11.STM32F4 输入捕获
一、输入捕获概念 输入捕获模式可以用来测量脉冲宽度或者测量频率。我们以测量脉宽为例,用一个简图来说明输入捕获的原理,如图1所示: 图1:输入捕获脉宽测量原理图 STM32F4的输入捕获,简单的说就是通过检测TIMx_CHx上的…...
opencv#30 线性滤波
均值滤波原理 均值滤波步骤 Step1:求和。 Step2:计算平均值。 所谓均值滤波,就是求平均值的意思。我们假设在一个3*3的范围内有一个图像,其中这个图像每一个像素可能含有噪声,也可能不含噪声,我们是不知道的,因此通…...
如何使用iPhone或iPad上的二维码共享Wi-Fi密码?这里有详细步骤
你有没有想过在不泄露网络密码的情况下与客人共享你的家庭或工作Wi-Fi?你肯定不是第一个这样想的人,我们很高兴地通知你,多亏了以下这个的变通方法,你现在可以使用iPhone或iPad做到这一点。 通常,如果你想让其他人访问网络,你需要共享你的Wi-Fi密码。苹果通过引入与任何…...
在游戏里开公司!基于ERNIE SDK的多智能体游戏应用
在虚拟世界有一座神奇的办公室,当你输入你的创业方向,办公室的智慧打工人们将团结合作,为你的项目勤劳奔走,并在过程中,把日报周报都写好,让你随时掌握项目进度和最终成果!该项目基于ERNIE SDK开…...
【SpringCloud Nacos】 微服务治理介绍及Nacos引入初体验
文章目录 前言服务治理介绍什么是服务治理1、服务发现2、服务配置3、服务健康检测 常见的注册中心ZookeeperEurekaConsulNacos Nacos 简介Nacos 实战入门搭建nacos环境1、安装nacos2、配置nacos3、访问nacos 将商品微服务注册到 nacos1、在 pom. xml 中添加 nacos 的依赖2、在主…...
JavaEE进阶(6)SpringBoot 配置文件(作用、格式、properties配置文件说明、yml配置文件说明、验证码案例)
接上次博客:JavaEE进阶(5)Spring IoC&DI:入门、IoC介绍、IoC详解(两种主要IoC容器实现、IoC和DI对对象的管理、Bean存储、方法注解 Bean)、DI详解:注入方式、总结-CSDN博客 目录 配置文件作用 Sprin…...
面包屑是什么
面包屑是网站导航中的一种可视化路径提示,通常以层次结构显示用户当前页面的位置,帮助用户了解他们在网站上的位置和浏览历史。这个术语来源于童话故事《汉赛尔与格莱特》中的面包屑小径,代表着一种追踪轨迹的方法。 假设你在一个电子商务网站…...
C++ 设计模式之责任链模式
【声明】本题目来源于卡码网(卡码网KamaCoder) 【提示:如果不想看文字介绍,可以直接跳转到C编码部分】 【设计模式大纲】 【简介】 --什么是责任链模式(第21种设计模式) 责任链模式是⼀种行为型设计模式&am…...
HTML5与App封装技术将网站一键打包成App
HTML5:跨平台的利器HTML5作为一种先进的网页标记语言,其最大的优势在于跨平台性。开发者仅需编写一次代码,即可在各种操作系统和设备上运行,无需为每个平台单独开发App。这种“编写一次,运行处处”的模式,大…...
openssl3.2/test/certs - 060 - any.bad.com is excluded by CA2.
文章目录 openssl3.2/test/certs - 060 - any.bad.com is excluded by CA2.概述笔记END openssl3.2/test/certs - 060 - any.bad.com is excluded by CA2. 概述 openssl3.2 - 官方demo学习 - test - certs 笔记 /*! * \file D:\my_dev\my_local_git_prj\study\openSSL\test…...
SpringBoot整理-数据库操作
在 Spring Boot 中进行数据库操作是一项常见的任务。Spring Boot 提供了多种方式来集成和操作数据库,使得与数据库的交互更为简便和高效。以下是在 Spring Boot 中进行数据库操作的几种主要方式: 1. Spring Data JPA 介绍: Spring Data JPA 是 Spring Data 的一部分,它简化了…...
IP被封怎么办?访问网站时IP被阻止?解决IP禁令全方法
相信很多人遇到过IP禁令:比如你在访问社交媒体、搜索引擎或电子商务网站时会被限制访问,又或者你的的账号莫名被封,这些由于网络上的种种限制我们经常会遭遇IP被封的情况,导致无法使用继续进行网络行动。在本文中,我们…...
利用aiohttp异步爬虫实现网站数据高效抓取
前言 大数据时代,网站数据的高效抓取对于众多应用程序和服务来说至关重要。传统的同步爬虫技术在面对大规模数据抓取时往往效率低下,而异步爬虫技术的出现为解决这一问题提供了新的思路。本文将介绍如何利用aiohttp异步爬虫技术实现网站数据抓取&#x…...
navicat连接postgresql、人大金仓等数据库报错
navicat连接postgresql、人大金仓数据库报错问题是一个偶现的问题,需要我们特别关注: 1、客户端连接人大金仓数据库 这里注意:navicat连接postgresql、人大金仓数据库时均选择postgresql类型,因为人大金仓数据库底层和psql数据库…...
AUTOSAR看门狗篇 -看门狗驱动(Wdg)
文章目录 功能介绍通用设计规则外部看门狗驱动内部看门狗驱动支持窗口看门狗的触发概念模块API介绍类型定义Wdg_ConfigType函数定义Wdg_InitWdg_SetModeWdg_SetTriggerConditionWdg_GetVersionInfo时序图看门狗初始化、设置触发条件...
数字图像处理:图像内插
图像内插 内插通常在图像放大、缩小旋转和几何校正等任务中使用。内插是用已知数据来估计未知位置的值的过程下面用—个简单的例子开始这—主题的探讨。假设大小为500500像素的—幅图像要放大1.5倍即放大到750750像素。一种简单的放大方法是,创建—个大小为750750像…...
Cantor表(刷题)(C语言)
个人博客主页:https://blog.csdn.net/2301_79293429?typeblog 专栏:https://blog.csdn.net/2301_79293429/category_12545690.html 题目描述 现代数学的著名证明之一是 Georg Cantor 证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的&…...
wordpress代码修改/常见的网络营销手段
和很多人一样,我本来也认为这不会成为一个问题,执行netstat -an命令查看当前网络连接情况,就可以知道FTP服务需要开放的端口是21。可实际使用过后,才发现并不是那么简单。使用IIS搭建一台FTP服务器,启用Windows防火墙&…...
龙华品牌网站制作/谷歌推广一年多少钱
点分治 点分治可以用来处理有关树上路径的问题 首先选取当前子树的重心作为分治点,因为重心可保证最大的子树不超过(u/2),这样每次递归的处理下去,复杂度是(nlogn)的 求重心代码: vo…...
免费响应式网站建设/佛山网站搜索排名
一. 再探Activity生命周期 为了研究activity的生命周期,简单測试代码例如以下。 package com.example.testactivity;import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.Menu;public class MainActivity extend…...
建筑设计方案网站/职业培训网络平台
对开发者和记者来说,同时有几部手机需要测试的时候,只得尽力对付。然而,微软会限制你用一个Live账号绑定WP手机的数量,绑定手机的时间间隔也有要求。对此,我们想一窥究竟。 Lumia 900即将到来,现在我得数数…...
广州白云区疫情/商丘seo外包
在网站中嵌入动画已成为近年来的一个设计趋势,许多公司都已开始转向并拥抱HTML5、CSS3和JavaScript这个技术“三人组”。尽管这些技术还不能制作一些非常复杂的动画(像flash所实现的),但是如果拥有好的想法及创造性思维࿰…...
自主建设公司网站/黄冈网站推广策略
开门见山,文档不足 下载下来的是zip包,不是tar包,未能解决 下载 wget https://releases.hashicorp.com/consul/1.3.0/consul_1.3.0_linux_amd64.zip 安装uzip yum -y install wget 解压 unzip consul_1.3.0_linux_amd64.zip 测试 ./c…...