Redis专题-实战篇一-基于Session和Redis实现登录业务
GitHub项目地址:https://github.com/whltaoin/redisLearningProject_hm-dianping
基于Session实现登录业务功能提交版本码:e34399f
基于Redis实现登录业务提交版本码:60bf740
一、导入黑马点评后端项目
项目架构图
1. 前期阶段
2. 后续阶段
导入后端项目需要注意的问题
- 修改application.yaml文件
- mysql地址配置
- redis地址配置
- 该项目的JDK版本为8,需要修改的地方如下图所示:
- idea设置
2. 项目结构设置
项目启动报错
- 报错内容:
Failed to load property source from location ‘classpath:/application.yml‘
- 解决方法1
- 查看yaml文件中的配置是否**配置完整,格式正确**
- 解决方法2
- 设置yaml文件的文件格式为:UTF-8
- 设置方法为:file->setting->File Encodings
项目启动测试
- 喜爱过目正常启动后,访问:http://localhost:8081/shop-type/list
- 下图为正确启动结果
二、导入并启动前端项目
- 提示:前端项目已经打包并导入到了nginx-1.18.0文件夹中的。
- 启动前端项目只需要执行ngin的开启命令即可。
start nginx
- 访问前端路径:http://localhost:8080
三、基于session实现登录功能
具体实现流程图
实现发送验证码
- 查询发送验证码请求API
地址:/user/code
请求方式:POST
- 修改UserController中的发送验证码方法
/*** 发送手机验证码*/
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// // TODO 发送短信验证码并保存验证码// return Result.fail("功能未完成");// 实现发送验证码return userService.sendCode(phone,session);
}
- 在service中实现send方法
package com.hmdp.service.impl;import cn.hutool.Hutool;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpSession;/*** <p>* 服务实现类* </p>** @since 2021-12-22*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {/*** 发送验证码* @param phone* @param session* @return*/@Overridepublic Result sendCode(String phone, HttpSession session) {// 1 验证手机号if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误,请重新输入");}// 2 生成验证码String code = RandomUtil.randomString(6);// 3 存储验证码session.setAttribute("code",code);// 4 发送验证码,模拟,不调用第三方功能log.debug("发送短信验证码成功,验证码:{}",code);return Result.ok();}
}
- 深入正确的手机号,点击发送后的效果
登录功能实现
- 登录API信息
地址:/user/login
请求方式:POST
- 在UserService中添加Login方法
/*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){// TODO 实现登录功能
// return Result.fail("功能未完成");return userService.login(loginForm,session);}
- 实现Login方法
package com.hmdp.service.impl;import cn.hutool.Hutool;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import com.sun.deploy.security.WSeedGenerator;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpSession;import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;/*** <p>* 服务实现类* </p>** @since 2021-12-22*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {/*** 登录和注册* @param loginForm* @param session* @return*/@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1 判断手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误,请重新输入");}// 2 判断验证码String webCode = loginForm.getCode();String sessionCode = session.getAttribute("code").toString();if (webCode==null || !webCode.equals(sessionCode)) {return Result.fail("验证码错误");}//log.debug("手机号为:{};验证码为:{}",phone,sessionCode);// 3 查询用户是否存在User user = query().eq("phone",phone).one();System.out.println("-==--------------");System.out.println(user);System.out.println("-==--------------");// 4 不存在用户创建if (user==null){user = createUserWithPhone(phone);System.out.println(user);}// 5 存在用户到session中session.setAttribute("user",user);return Result.ok();}private User createUserWithPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(6));// 保存save(user);return user;}
}
- 实现效果
实现登录校验功能
- 涉及到的controller API
地址:/user/me
请求方式:get
- 编写登录拦截器工具类(LoginInterceptor)
package com.hmdp.utils;import com.hmdp.entity.User;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;public class LoginInterceptor implements HandlerInterceptor {/*** 此方法的作用是在请求进入到Controller进行拦截,有返回值* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1 获取sessionHttpSession session = request.getSession();// 2 获取用户Object user = session.getAttribute("user");// 3 判断用户if (user==null) {response.setStatus(401);return false;}// 4 存储用户UserHolder.saveUser((User) user);// 5 放行return true;}/*** 该方法是在ModelAndView返回给前端渲染后执行* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}
- 添加配置MVC拦截器配置类(MvcConfig)
package com.hmdp.config;import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/shop-type/**","/upload/**","blog/hot","/user/code","/user/login");}
}
- 让在threadLocal存储用户返回到签到(userController)
@GetMapping("/me")public Result me(){
// // TODO 获取当前登录的用户并返回
// return Result.fail("功能未完成");return Result.ok(UserHolder.getUser());}
- 运行效果
- 登录成功后,会跳转到用户个人主页,并显示用户的一些信息
UserDTO类使用
- 在后端直接返回User类后,可能用用户敏感信息泄露的风险。
- 所以我们在存如Session时,只存储不太重要的信息即可。
- 需要修改的地方:
- UserService中session存储类型
2. 拦截器
- 效果
集群Session共享存在的问题
- 存在问题,
- 因为在tomcat中的Session是不共享的,如果使用了tomcat集群的话,每个tomcat中的Session都需要重新存储数据,同时可能会造成数据的不一致和数据丢失的可能。
- 为了解决session存在问题,同时需要满足
- 数据共享
- 内存存储
- Key、value结构
- 从而引入了Redis替代Session
四、Redis替代Session的解决方案流程图
- 替换流程
Redis替换Session具体实现
- 首先将生成的验证码存入Redis中
@Overridepublic Result sendCode(String phone, HttpSession session) {// 1 验证手机号if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误,请重新输入");}// 2 生成验证码String code = RandomUtil.randomString(6);
// // 3 存储验证码
// session.setAttribute("code",code);// 3 修改为redis存储stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES); // 1分钟过期// 4 发送验证码,模拟,不调用第三方功能log.debug("发送短信验证码成功,验证码:{}",code);return Result.ok();}
- 在用户点击登录时,从Redis中获取验证码进行判断
- 后续将登录的用户存储到Redis中,并返回Token
@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1 判断手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误,请重新输入");}// 2 判断验证码String webCode = loginForm.getCode();// String sessionCode = session.getAttribute("code").toString();// 从redis中获取验证码String sessionCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);if (webCode==null || !webCode.equals(sessionCode)) {return Result.fail("验证码错误");}//log.debug("手机号为:{};验证码为:{}",phone,sessionCode);// 3 查询用户是否存在User user = query().eq("phone",phone).one();System.out.println("-==--------------");System.out.println(user);System.out.println("-==--------------");// 4 不存在用户创建if (user==null){user = createUserWithPhone(phone);System.out.println(user);}// 5 存在用户到session中// 6 为了防止敏感信息泄露,将user转存到UserDTO中session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);String token = UUID.randomUUID().toString(false);// 不带下划线的UUID// 将UserDTO bean转为HashMapMap<String, Object> userDTOMap = BeanUtil.beanToMap(userDTO);
// System.out.println(userDTOMap);userDTOMap.put("id",userDTO.getId().toString()); // StringRedisTamplate 只能存String类型的数据,ID为Long,需要单独处理String tokenKey = LOGIN_USER_KEY+token;stringRedisTemplate.opsForHash().putAll(tokenKey, userDTOMap);stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL, TimeUnit.SECONDS);return Result.ok(token);}
- 登录功能验证
- 用户登录完成后,在访问其他请求时,会进入到拦截器中,从请求的header中获取到Token,再通过Token查询Redis中的用户是否存在。存在就放行,并重新刷新Redis保存数据的TTL,不存在则拦截。
package com.hmdp.utils;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;/*** 刷新Token拦截器*/public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate ){this.stringRedisTemplate = stringRedisTemplate;}/*** 此方法的作用是在请求进入到Controller进行拦截,有返回值* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// // 1 获取session
// HttpSession session = request.getSession();// 获取tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}// 2 获取用户
// Object user = session.getAttribute("user");Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY+token);// 3 判断用户if (userMap.isEmpty()) {return true;}UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// map转bean
// 4 存储用户UserHolder.saveUser(userDTO);// 刷新token有效期stringRedisTemplate.expire(LOGIN_USER_KEY+token,LOGIN_USER_TTL, TimeUnit.SECONDS);// 5 放行return true;}/*** 该方法是在ModelAndView返回给前端渲染后执行* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}
登录遗留问题解决
- 我们之前在LoginInterceptor中有放行了一些不需要的认证的请求,这样就存在问题了。
- 例如:如果用户登录了,但是一直停留在不需要认证的请求上,Redis的TTL的刷新TTL功能就不会执行,导致用户被强制退出系统。
- 解决方案:
- 我们在LoginInterceptor之前在设置一个全局的拦截起,并设置全局拦截,并将Login拦截器内容判断都移动到RefershToken拦截器上,在Refresh执行后再进入到Login拦截器,在判断线程中是否有登录用户,在进行实际的拦截。
- 具体实现
LoginInterceptor
package com.hmdp.utils;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;/*** 登录拦截器*/public class LoginInterceptor implements HandlerInterceptor {/*** 此方法的作用是在请求进入到Controller进行拦截,有返回值* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取不到用户,拦截if (UserHolder.getUser()==null) {response.setStatus(401);return false;}return true;}/*** 该方法是在ModelAndView返回给前端渲染后执行* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}
RefreshTokenInterceptor如上图
MnvConfig配置类修改如下
package com.hmdp.config;import com.hmdp.utils.LoginInterceptor;import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** order值越小,拦截顺序越高。* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor( new RefreshTokenInterceptor(stringRedisTemplate)).order(0);registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(// 放行的路径"/shop/**","/shop-type/**","/upload/**","blog/hot","/user/code","/user/login").order(1);}
}
相关文章:

Redis专题-实战篇一-基于Session和Redis实现登录业务
GitHub项目地址:https://github.com/whltaoin/redisLearningProject_hm-dianping 基于Session实现登录业务功能提交版本码:e34399f 基于Redis实现登录业务提交版本码:60bf740 一、导入黑马点评后端项目 项目架构图 1. 前期阶段2. 后续阶段导…...

【前端实战】如何让用户回到上次阅读的位置?
目录 【前端实战】如何让用户回到上次阅读的位置? 一、总体思路 1、核心目标 2、涉及到的技术 二、实现方案详解 1、基础方法:监听滚动,记录 scrollTop(不推荐) 2、Intersection Observer 插入探针元素 3、基…...

dvwa11——XSS(Reflected)
LOW 分析源码:无过滤 和上一关一样,这一关在输入框内输入,成功回显 <script>alert(relee);</script> MEDIUM 分析源码,是把<script>替换成了空格,但没有禁用大写 改大写即可,注意函数…...
关于疲劳分析的各种方法
疲劳寿命预测方法很多。按疲劳裂纹形成寿命预测的基本假定和控制参数,可分为名义应力法、局部应力一应变法、能量法、场强法等。 1名义应力法 名义应力法是以结构的名义应力为试验和寿命估算的基础,采用雨流法取出一个个相互独立、互不相关的应力循环&…...
数据库优化实战指南:提升性能的黄金法则
在现代软件系统中,数据库性能直接影响应用的响应速度和用户体验。面对数据量激增、访问压力增大,数据库性能瓶颈经常成为项目痛点。如何科学有效地优化数据库,提升查询效率和系统稳定性,是每位开发与运维人员必备的技能。 本文结…...

【Axure高保真原型】图片列表添加和删除图片
今天和大家分享图片列表添加和删除图片的原型模板,效果包括: 点击图片列表的加号可以显示图片选择器,选择里面的图片; 选择图片后点击添加按钮,可以将该图片添加到图片列表; 鼠标移入图片列表的图片&…...

XXE漏洞知识
目录 1.XXE简介与危害 XML概念 XML与HTML的区别 1.pom.xml 主要作用 2.web.xml 3.mybatis 2.XXE概念与危害 案例:文件读取(需要Apache >5.4版本) 案例:内网探测(鸡肋) 案例:执行命…...

mq安装新版-3.13.7的安装
一、下载包,上传到服务器 https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.13.7/rabbitmq-server-generic-unix-3.13.7.tar.xz 二、 erlang直接安装 rpm -ivh erlang-26.2.4-1.el8.x86_64.rpm不需要配置环境变量,直接就安装了。 erl…...
第21节 Node.js 多进程
Node.js本身是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。 每个子进程总是带有三个流对象:child.stdin, child.stdout和child.stderr。他们可能会共享…...

DL00871-基于深度学习YOLOv11的盲人障碍物目标检测含完整数据集
基于深度学习YOLOv11的盲人障碍物目标检测:开启盲人出行新纪元 在全球范围内,盲人及视觉障碍者的出行问题一直是社会关注的重点。尽管技术不断进步,许多城市的无障碍设施依然未能满足盲人出行的实际需求。尤其是在复杂的城市环境中ÿ…...

华硕电脑,全新的超频方式,无需进入BIOS
想要追求更佳性能释放 或探索更多可玩性的小伙伴, 可能会需要为你的电脑超频。 但我们常用的不论是BIOS里的超频, 还是Armoury Crate奥创智控中心超频, 每次调节都要重启,有点麻烦。 TurboV Core 全新的超频方案来了 4不规…...

安全领域新突破:可视化让隐患无处遁形
在安全领域,隐患就像暗处的 “幽灵”,随时可能引发严重事故。传统安全排查手段,常常难以将它们一网打尽。你是否好奇,究竟是什么神奇力量,能让这些潜藏的隐患无所遁形?没错,就是可视化技术。它如…...
触发DMA传输错误中断问题排查
在STM32项目中,集成BLE模块后触发DMA传输错误中断(DMA2_Stream1_IRQHandler进入错误流程),但单独运行BLE模块时正常,表明问题可能源于原有线程与BLE模块的交互冲突。以下是逐步排查与解决方案: 一、问题根源…...

Vue.js教学第二十一章:vue实战项目二,个人博客搭建
基于 Vue 的个人博客网站搭建 摘要: 随着前端技术的不断发展,Vue 作为一种轻量级、高效的前端框架,为个人博客网站的搭建提供了极大的便利。本文详细介绍了基于 Vue 搭建个人博客网站的全过程,包括项目背景、技术选型、项目架构设计、功能模块实现、性能优化与测试等方面。…...

[KCTF]CORE CrackMe v2.0
这个Reverse比较古老,已经有20多年了,但难度确实不小。 先查壳 upx压缩壳,0.72,废弃版本,工具无法解压。 反正不用IDA进行调试,直接x32dbg中,dump内存,保存后拖入IDA。 这里说一下…...
Redis——Cluster配置
目录 分片 一、分片的本质与核心价值 二、分片实现方案对比 三、分片算法详解 1. 范围分片(顺序分片) 2. 哈希分片 3. 虚拟槽分片(Redis Cluster 方案) 四、Redis Cluster 分片实践要点 五、经典问题解析 C…...

Ubuntu 安装 Mysql 数据库
首先更新apt-get工具,执行命令如下: apt-get upgrade安装Mysql,执行如下命令: apt-get install mysql-server 开启Mysql 服务,执行命令如下: service mysql start并确认是否成功开启mysql,执行命令如下&am…...
结合PDE反应扩散方程与物理信息神经网络(PINN)进行稀疏数据预测的技术方案
以下是一个结合PDE反应扩散方程与物理信息神经网络(PINN)进行稀疏数据预测的技术方案,包含完整数学推导、PyTorch/TensorFlow双框架实现代码及对比实验分析。 基于PINN的反应扩散方程稀疏数据预测与大规模数据泛化能力研究 1. 问题定义与数学模型 1.1 反应扩散方程 考虑标…...
从0开始一篇文章学习Nginx
Nginx服务 HTTP介绍 ## HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。 ## HTTP工作在 TCP/IP协议体系中的TCP协议上&#…...

Java高级 |【实验八】springboot 使用Websocket
隶属文章:Java高级 | (二十二)Java常用类库-CSDN博客 系列文章:Java高级 | 【实验一】Springboot安装及测试 |最新-CSDN博客 Java高级 | 【实验二】Springboot 控制器类相关注解知识-CSDN博客 Java高级 | 【实验三】Springboot 静…...
scan_mode设计原则
scan_mode设计原则 在进行mtp controller设计时,基本功能设计完成后,需要设计scan_mode设计。 1、在进行scan_mode设计时,需要保证mtp处于standby模式,不会有擦写、编程动作。 2、只需要固定mtp datasheet说明的接口即可…...

设计模式-3 行为型模式
一、观察者模式 1、定义 定义对象之间的一对多的依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。 描述复杂的流程控制 描述多个类或者对象之间怎样互相协作共同完成单个对象都无法单独度完成的任务 它涉及算法与对象间职责…...
qt 双缓冲案例对比
双缓冲 1.双缓冲原理 单缓冲:在paintEvent中直接绘制到屏幕,绘制过程被用户看到 双缓冲:先在redrawBuffer绘制到缓冲区,然后一次性显示完整结果 代码结构 单缓冲:所有绘制逻辑在paintEvent中 双缓冲:绘制…...
2025年全国I卷数学压轴题解答
第19题第3问: b b b 使得存在 t t t, 对于任意的 x x x, 5 cos x − cos ( 5 x t ) < b 5\cos x-\cos(5xt)<b 5cosx−cos(5xt)<b, 求 b b b 的最小值. 解: b b b 的最小值 b m i n min t max x g ( x , t ) b_{min}\min_{t} \max_{x} g(x,t) bmi…...
JS设计模式(5): 发布订阅模式
解锁JavaScript发布订阅模式:让代码沟通更优雅 在JavaScript的世界里,我们常常会遇到这样的场景:多个模块之间需要相互通信,但是又不想让它们产生过于紧密的耦合。这时候,发布订阅模式就像一位优雅的信使,…...

实现p2p的webrtc-srs版本
1. 基本知识 1.1 webrtc 一、WebRTC的本质:实时通信的“网络协议栈”类比 将WebRTC类比为Linux网络协议栈极具洞察力,二者在架构设计和功能定位上高度相似: 分层协议栈架构 Linux网络协议栈:从底层物理层到应用层(如…...
Android多媒体——音/视频数据播放(十八)
在媒体数据完成解码并准备好之后,播放流程便进入了最终的呈现阶段。为了确保音视频内容能够顺利输出,系统需要首先对相应的播放设备进行初始化。只有在设备初始化成功后,才能真正开始音视频的同步渲染与播放。这一过程不仅影响播放的启动速度,也直接关系到播放的稳定性和用…...

第2篇:BLE 广播与扫描机制详解
本文是《BLE 协议从入门到专家》专栏第二篇,专注于解析 BLE 广播(Advertising)与扫描(Scanning)机制。我们将从协议层结构、广播包格式、设备发现流程、控制器行为、开发者 API、广播冲突与多设备调度等方面,全面拆解这一 BLE 最基础也是最关键的通信机制。 一、什么是 B…...

开源 vGPU 方案:HAMi,实现细粒度 GPU 切分
本文主要分享一个开源的 GPU 虚拟化方案:HAMi,包括如何安装、配置以及使用。 相比于上一篇分享的 TimeSlicing 方案,HAMi 除了 GPU 共享之外还可以实现 GPU core、memory 得限制,保证共享同一 GPU 的各个 Pod 都能拿到足够的资源。…...
EC2安装WebRTC sdk-c环境、构建、编译
1、登录新的ec2实例,证书可以跟之前的实例用一个: ssh -v -i ~/Documents/cert/qa.pem ec2-user70.xxx.165.xxx 2、按照sdk-c demo中readme的描述开始安装环境: https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c 2…...