若依框架集成WebSocket带用户信息认证
一、WebSocket 基础知识
二、若依框架集成WebSocket
- RUOYI-VUE 集成websocket后 使用SecurityUtils获取用户信息报错 · Issue #I49SKT · 若依/RuoYi - Gitee.com
- webSocket接口中无法使用@Autowired和@Resource自动装配 · Issue #I5VT2N · 若依/RuoYi-Vue - Gitee.com
- 请问vue websocket握手如何在请求头中携带token · Issue #I5KAKK · 若依/RuoYi-Cloud - Gitee.com
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** websocket 配置* * @author ruoyi*/
@Configuration
public class WebSocketConfig
{@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
} 工具类 SemaphoreUtils
import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 信号量相关处理* * @author ruoyi*/
public class SemaphoreUtils{/*** SemaphoreUtils 日志控制器*/private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtils.class);/*** 获取信号量* * @param semaphore* @return*/public static boolean tryAcquire(Semaphore semaphore){boolean flag = false;try{flag = semaphore.tryAcquire();}catch (Exception e){LOGGER.error("获取信号量异常", e);}return flag;}/*** 释放信号量* * @param semaphore*/public static void release(Semaphore semaphore){try{semaphore.release();}catch (Exception e){LOGGER.error("释放信号量异常", e);}}
} 服务端类WebSocketServer
import java.util.concurrent.Semaphore;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import com.lxh.demo.util.SemaphoreUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;/*** websocket 消息处理* * @author ruoyi*/
@Component
@ServerEndpoint("/websocket/message")
public class WebSocketServer
{/*** WebSocketServer 日志控制器*/private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);/*** 默认最多允许同时在线人数100*/public static int socketMaxOnlineCount = 100;private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount);/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session) throws Exception{boolean semaphoreFlag = false;// 尝试获取信号量semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore);if (!semaphoreFlag){// 未获取到信号量LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount);WebSocketUsers.sendMessageToUserByText(session, "当前在线人数超过限制数:" + socketMaxOnlineCount);session.close();}else{// 添加用户WebSocketUsers.put(session.getId(), session);LOGGER.info("\n 建立连接 - {}", session);LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size());WebSocketUsers.sendMessageToUserByText(session, "连接成功");}}/*** 连接关闭时处理*/@OnClosepublic void onClose(Session session){LOGGER.info("\n 关闭连接 - {}", session);// 移除用户WebSocketUsers.remove(session.getId());// 获取到信号量则需释放SemaphoreUtils.release(socketSemaphore);}/*** 抛出异常时处理*/@OnErrorpublic void onError(Session session, Throwable exception) throws Exception{if (session.isOpen()){// 关闭连接session.close();}String sessionId = session.getId();LOGGER.info("\n 连接异常 - {}", sessionId);LOGGER.info("\n 异常信息 - {}", exception);// 移出用户WebSocketUsers.remove(sessionId);// 获取到信号量则需释放SemaphoreUtils.release(socketSemaphore);}/*** 服务器接收到客户端消息时调用的方法*/@OnMessagepublic void onMessage(String message, Session session){String msg = message.replace("你", "我").replace("吗", "");WebSocketUsers.sendMessageToUserByText(session, msg);}
} WebSocketUsers工具类
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** websocket 客户端用户集* * @author ruoyi*/
public class WebSocketUsers
{/*** WebSocketUsers 日志控制器*/private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class);/*** 用户集*/private static Map<String, Session> USERS = new ConcurrentHashMap<String, Session>();/*** 存储用户** @param key 唯一键* @param session 用户信息*/public static void put(String key, Session session){USERS.put(key, session);}/*** 移除用户** @param session 用户信息** @return 移除结果*/public static boolean remove(Session session){String key = null;boolean flag = USERS.containsValue(session);if (flag){Set<Map.Entry<String, Session>> entries = USERS.entrySet();for (Map.Entry<String, Session> entry : entries){Session value = entry.getValue();if (value.equals(session)){key = entry.getKey();break;}}}else{return true;}return remove(key);}/*** 移出用户** @param key 键*/public static boolean remove(String key){LOGGER.info("\n 正在移出用户 - {}", key);Session remove = USERS.remove(key);if (remove != null){boolean containsValue = USERS.containsValue(remove);LOGGER.info("\n 移出结果 - {}", containsValue ? "失败" : "成功");return containsValue;}else{return true;}}/*** 获取在线用户列表** @return 返回用户集合*/public static Map<String, Session> getUsers(){return USERS;}/*** 群发消息文本消息** @param message 消息内容*/public static void sendMessageToUsersByText(String message){Collection<Session> values = USERS.values();for (Session value : values){sendMessageToUserByText(value, message);}}/*** 发送文本消息** @param session 缓存* @param message 消息内容*/public static void sendMessageToUserByText(Session session, String message){if (session != null){try{session.getBasicRemote().sendText(message);}catch (IOException e){LOGGER.error("\n[发送消息异常]", e);}}else{LOGGER.info("\n[你已离线]");}}
} Html 页面代码
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>测试界面</title>
</head><body><div><input type="text" style="width: 20%" value="ws://127.0.0.1/websocket/message" id="url"><button id="btn_join">连接</button><button id="btn_exit">断开</button>
</div>
<br/>
<textarea id="message" cols="100" rows="9"></textarea> <button id="btn_send">发送消息</button>
<br/>
<br/>
<textarea id="text_content" readonly="readonly" cols="100" rows="9"></textarea>返回内容
<br/>
<br/>
<script th:src="@{/js/jquery.min.js}" ></script>
<script type="text/javascript">$(document).ready(function(){var ws = null;// 连接$('#btn_join').click(function() {var url = $("#url").val();ws = new WebSocket(url);ws.onopen = function(event) {$('#text_content').append('已经打开连接!' + '\n');}ws.onmessage = function(event) {$('#text_content').append(event.data + '\n');}ws.onclose = function(event) {$('#text_content').append('已经关闭连接!' + '\n');}});// 发送消息$('#btn_send').click(function() {var message = $('#message').val();if (ws) {ws.send(message);} else {alert("未连接到服务器");}});//断开$('#btn_exit').click(function() {if (ws) {ws.close();ws = null;}});})
</script>
</body>
</html> 成功运行后,页面如下

注意此时没有走用户认证,那么就要对路径放行,因为若依框架用的是SpringSecurity,所以找到文件SecurityConfig.java ,进行路径放行

三、用户认证问题
虽然按着上述步骤我们完成了浏览器(客户端)和Java(服务端)的WebSocket通信,但是我们不能限定哪些用户可以连接我们的服务端获取数据,服务端也不知道应该具体给哪些用户发送消息,在我们框架之前交互我们是通过浏览器传递toke 值来实现用户身份确认的,那么我们的WebSocket可不可以也这样呢?
很不幸的是 ws连接是无法像http一样完全自主定义请求头的,给token认证带来了不便,我们大致可以通过以下集中方式完成用户认证
1、将 token 明文携带在 url 中,例如ws://localhost:8080/weggo/websocket/message?Authorization=Bearer+token
2、通过websocket下的子协议来实现,Stomp这个协议来实现,前端采用SocketJs框架来实现对应定制请求头。实现携带authorization=Bearer +token 的需求,这样就可以正常建立连接
3、利用子协议数组,将 token 携带在 protocols 里,var ws = new WebSocket(url, ["token"]);
这样后端在 onOpen 事件中,就可以从 server 中读取 Sec-WebSocket-Protocol 属性来进行 token 的获取,具体可以参考WebScoket构造函数官方文档
var aWebSocket = new WebSocket(url [, protocols]);
url
要连接的URL;这应该是WebSocket服务器将响应的URL。
protocols 可选
一个协议字符串或者一个包含协议字符串的数组。这些字符串用于指定子协议,这样单个服务器可以实现多个WebSocket子协议
(例如,您可能希望一台服务器能够根据指定的协议(protocol)处理不同类型的交互)。如果不指定协议字符串,则假定为空字符串。 protocols对应的就是发起ws连接时, 携带在请求头中的Sec-WebSocket-Protocol属性, 服务端可以获取到此属性的值用于通信逻辑(即通信子协议,当然用来进行token认证也是完全没问题的),前端人员在请求头上携带sec-websocket-protocol=Bearer +token后台在请求到达oauth2之前进行拦截,然后将在请求头上添加Authorization=Bearer +token(key首字母大写),然后在响应头(respone)上添加sec-websocket-protocol=Bearer +token(不添加会报错)
方法3部分代码示例
//前端
var aWebSocket = new WebSocket(url ['用户token']);//后端
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {//这里就是我们所提交的tokenString submitedToken=session.getHandshakeHeaders().get("sec-websocket-protocol").get(0);//根据token取得登录用户信息(业务逻辑根据你自己的来处理)
} 另外,如果需要在第一次握手前的时候就取得token,只需要在header里面取得就可以啦
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {System.out.println("准备握手");String submitedToken = serverHttpRequest.getHeaders().get("sec-websocket-protocol")return true;
} 因为我的项目是APP 移动端与服务端进行交互,所以后来选择了最简单实现的方案一
首先要解决的就是在拦截器获取url 的token 信息,原框架只从head里面获取,所以需要稍加改动
找到TokenService.java文件里的getToken方法,改成如下,这样就可以获取url 中的token 了又不影响原来的Http 请求
private String getToken(HttpServletRequest request){String token = Optional.ofNullable(request.getHeader(header)).orElse(request.getParameter(header));if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)){token = token.replace(Constants.TOKEN_PREFIX, "");}return token;} 接下来就是需要对我们的WebSocket类进行改造了,为了方便阅读,去除了WebSocketUsers类,添加了类变量webSocketSet来存储客户端对象
import com.alibaba.fastjson2.JSON;
import com.tongchuang.common.utils.SecurityUtils;
import com.tongchuang.web.mqtt.domain.DeviceInfo;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;/*** websocket 消息处理** @author stronger*/
@Component
@ServerEndpoint("/websocket/message")
public class WebSocketServer {/*========================声明类变量,意在所有实例共享=================================================*//*** WebSocketServer 日志控制器*/private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);/*** 默认最多允许同时在线人数100*/public static int socketMaxOnlineCount = 100;private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount);HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 8);/*** concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/private static final CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();/*** 连接数*/private static final AtomicInteger count = new AtomicInteger();/*========================声明实例变量,意在每个实例独享=======================================================*//*** 与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 用户id*/private String sid = "";/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session) throws Exception {// 尝试获取信号量boolean semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore);if (!semaphoreFlag) {// 未获取到信号量LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount);// 给当前Session 登录用户发送消息sendMessageToUserByText(session, "当前在线人数超过限制数:" + socketMaxOnlineCount);session.close();} else {// 返回此会话的经过身份验证的用户,如果此会话没有经过身份验证的用户,则返回nullAuthentication authentication = (Authentication) session.getUserPrincipal();SecurityUtils.setAuthentication(authentication);String username = SecurityUtils.getUsername();this.session = session;//如果存在就先删除一个,防止重复推送消息for (WebSocketServer webSocket : webSocketSet) {if (webSocket.sid.equals(username)) {webSocketSet.remove(webSocket);count.getAndDecrement();}}count.getAndIncrement();webSocketSet.add(this);this.sid = username;LOGGER.info("\n 当前人数 - {}", count);sendMessageToUserByText(session, "连接成功");}}/*** 连接关闭时处理*/@OnClosepublic void onClose(Session session) {LOGGER.info("\n 关闭连接 - {}", session);// 移除用户webSocketSet.remove(session);// 获取到信号量则需释放SemaphoreUtils.release(socketSemaphore);}/*** 抛出异常时处理*/@OnErrorpublic void onError(Session session, Throwable exception) throws Exception {if (session.isOpen()) {// 关闭连接session.close();}String sessionId = session.getId();LOGGER.info("\n 连接异常 - {}", sessionId);LOGGER.info("\n 异常信息 - {}", exception);// 移出用户webSocketSet.remove(session);// 获取到信号量则需释放SemaphoreUtils.release(socketSemaphore);}/*** 服务器接收到客户端消息时调用的方法*/@OnMessagepublic void onMessage(String message, Session session) {Authentication authentication = (Authentication) session.getUserPrincipal();LOGGER.info("收到来自" + sid + "的信息:" + message);// 实时更新this.refresh(sid, authentication);sendMessageToUserByText(session, "我收到了你的新消息哦");}/*** 刷新定时任务,发送信息*/private void refresh(String userId, Authentication authentication) {this.start(5000L, task -> {// 判断用户是否在线,不在线则不用处理,因为在内部无法关闭该定时任务,所以通过返回值在外部进行判断。if (WebSocketServer.isConn(userId)) {// 因为这里是长链接,不会和普通网页一样,每次发送http 请求可以走拦截器【doFilterInternal】续约,所以需要手动续约SecurityUtils.setAuthentication(authentication);// 从数据库或者缓存中获取信息,构建自定义的BeanDeviceInfo deviceInfo = DeviceInfo.builder().Macaddress("de5a735951ee").Imei("351517175516665").Battery("99").Charge("0").Latitude("116.402649").Latitude("39.914859").Altitude("80").Method(SecurityUtils.getUsername()).build();// TODO判断数据是否有更新// 发送最新数据给前端WebSocketServer.sendInfo("JSON", deviceInfo, userId);// 设置返回值,判断是否需要继续执行return true;}return false;});}private void start(long delay, Function<Timeout, Boolean> function) {timer.newTimeout(t -> {// 获取返回值,判断是否执行Boolean result = function.apply(t);if (result) {timer.newTimeout(t.task(), delay, TimeUnit.MILLISECONDS);}}, delay, TimeUnit.MILLISECONDS);}/*** 判断是否有链接** @return*/public static boolean isConn(String sid) {for (WebSocketServer item : webSocketSet) {if (item.sid.equals(sid)) {return true;}}return false;}/*** 群发自定义消息* 或者指定用户发送消息*/public static void sendInfo(String type, Object data, @PathParam("sid") String sid) {// 遍历WebSocketServer对象集合,如果符合条件就推送for (WebSocketServer item : webSocketSet) {try {//这里可以设定只推送给这个sid的,为null则全部推送if (sid == null) {item.sendMessage(type, data);} else if (item.sid.equals(sid)) {item.sendMessage(type, data);}} catch (IOException ignored) {}}}/*** 实现服务器主动推送*/private void sendMessage(String type, Object data) throws IOException {Map<String, Object> result = new HashMap<>();result.put("type", type);result.put("data", data);this.session.getAsyncRemote().sendText(JSON.toJSONString(result));}/*** 实现服务器主动推送-根据session*/public static void sendMessageToUserByText(Session session, String message) {if (session != null) {try {session.getBasicRemote().sendText(message);} catch (IOException e) {LOGGER.error("\n[发送消息异常]", e);}} else {LOGGER.info("\n[你已离线]");}}
} 
相关文章:
若依框架集成WebSocket带用户信息认证
一、WebSocket 基础知识 我们平时前后台请求用的最多的就是 HTTP/1.1协议,它有一个缺陷, 通信只能由客户端发起,如果想要不断获取服务器信息就要不断轮询发出请求,那么如果我们需要服务器状态变化的时候能够主动通知客户端就需要用…...
0基础学习VR全景平台篇 第101篇:企业版功能-子账号分配管理
大家好,欢迎观看蛙色VR官方系列——后台使用课程! 本期为大家带来蛙色VR平台,企业版教程-子账号分配管理功能! 功能位置示意 一、本功能将用在哪里? 子账号分配管理功能,主要用于企业版用户为自己服务的终…...
adb 命令集
adb 查看app启动时间 1.清除时间 adb shell am start -S com.android.systemui/.SystemUIService2.启动应用并记录 adb shell am start -W <PACKAGE_NAME>/.<ACTIVITY_NAME>TotalTime: 491 adb 查看分辨率、dpi 分辨率 adb shell wm sizePhysical size: 1080…...
分享78个Python源代码总有一个是你想要的
分享78个Python源代码总有一个是你想要的 源码下载链接:https://pan.baidu.com/s/1ZhXDsVuYsZpOUQIUjHU2ww?pwd8888 提取码:8888 下面是文件的名字。 12个python项目源码 Apache Superset数据探查与可视化平台v2.0.1 API Star工具箱v0.7.2 Archery…...
springcloud3 指定nacos的服务名称和配置文件的group,名称空间
一 指定读取微服务的配置文件 1.1 工程结构 1.2 nacos的配置 1.配置文件 2.内容 1.3 微服务的配置文件 1.bootstrap.yml内容 2.application.yml文件内容 1.4 验证访问 控制台: 1.5 nacos服务空间名称和groupid配置 1.配置文件配置 2.nacos的查看...
go-redis简单使用
目录 一:官方文档和安装方式二:简单案例使用 一:官方文档和安装方式 官方中文文档:https://redis.uptrace.dev/zh/guide/go-redis.html安装:go get github.com/redis/go-redis/v9 二:简单案例使用 简单的…...
33. 搜索旋转排序数组-二重二分查找
33. 搜索旋转排序数组-二分查找 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, n…...
mysql自动删除过期的binlog
一、binlog_expire_logs_seconds 配置项 mysql 8.0使用配置项 binlog_expire_logs_seconds 设置binlog过期时间,单位为秒。 mysql旧版本使用配置项 expire_logs_days 设置binlog过期时间,单位为天,不方便测试。 在 8.0 使用 expire_logs_d…...
Java面向对象(1)
static静态变量 public class Student {static String name;private double score;public Student(){};public Student(double score) {this.score score;}public double getScore() {return score;}public void setScore(double score) {this.score score;} }public class t…...
【计算机毕业设计】基于SpringBoot+Vue金融产品销售系统的设计与实现
博主主页:一季春秋博主简介:专注Java技术领域和毕业设计项目实战、Java、微信小程序、安卓等技术开发,远程调试部署、代码讲解、文档指导、ppt制作等技术指导。主要内容:毕业设计(Java项目、小程序、安卓等)、简历模板、学习资料、…...
【面试题精讲】Mysql如何实现乐观锁
❝ 有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top ❞ 首发博客地址 文章更新计划 系列文章地址 在 MySQL 中,可以通过使用乐观锁来实现并发控制,以避免数据冲突和并发更新问…...
从零开始搭建java web springboot Eclipse MyBatis jsp mysql开发环境
文章目录 1 第一步软件安装1.1 下载并安装Eclipse1.2 下载并安装Java1.3 下载并安装Apache Maven1.4 下载并安装MySQL 2 创建所需要的表和数据3 创建Maven 工程、修改jdk4 通过pom.xml获取所需要的jar包5 安装Eclipse的MyBatis插件6 创建文件夹以及jsp文件7 创建下面各种java类…...
【VsCode】整理代码
在VsCode中,你可以使用插件"Beautify"来格式化你的HTML代码,使其更加整齐清晰。而对于JSON代码,你可以使用"vscode-json"插件来格式化为易读的树状结构,方便查看和编辑。这些插件可以帮助你更加高效地整理HTM…...
盘点总结汇总植物病虫害、人体疾病识别相关的项目实践
在前面的很多项目中做了许多有关于植物病虫害比如:苹果病虫害、番茄病虫害、小麦病虫害、辣椒病虫害、白菜病虫害、木薯病虫害、葡萄病虫害、柑橘病虫害等等,还有一些是有关于人体疾病识别相关的,比如:病理细胞识别、癌症识别、皮…...
【测试开发】用例篇 · 熟悉黑盒测试用例设计方法(2)· 正交表 · 场景设计 · 常见案例练习
【测试开发】用例篇(2) 文章目录 【测试开发】用例篇(2)1. 正交表法1.1 什么是正交表1.2 两个重要概念1.3 如何通过正交表设计测试用例1.3.1 充分理解需求1.3.2 确定因素、确定水平1.3.3 allpairs画正交表1.3.4 补充正交表1.3.5 将…...
【ES】笔记-数值扩展
数值扩展 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>数值扩展</title> </head> &l…...
浅谈Rust内存管理
Rust因在内存管理上的独到之处,近年来受到了不少开发者的青睐。Rust内存管理的核心功能就是所有权。不同的语言采取了不同的内存管理方式,主要分为开发者手动管理或者编译器辅助管理,以及垃圾回收机制等。Rust的所有权机制,有别于…...
Vue路由跳转至页面后多次渲染
在 Vue 中,当你跳转到一个新的路由或者重新加载当前路由时,由于 Vue Router 或其他路由管理工具的机制,会导致该页面组件重新渲染多次的情况发生。这可能是因为以下原因: 组件复用:Vue Router 默认情况下会尝试复用已经…...
CDH大数据平台集群部署
文章目录 1. 资源准备2. 部署 Mariadb 数据库3. 安装CM服务4. 安装数据节点5. 登录CM系统 1. 资源准备 准备好CDH安装包资源,官方网站下载需要账号,如果没有账号可以去网上到处搜搜。主要涉及到的资源有: cloudera-manager-servercloudera-m…...
基于springboot+vue的校园资产管理系统
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...
Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
