当前位置: 首页 > article >正文

黑马点评--基于Redis实现共享session登录

集群的session共享问题分析

session共享问题:多台Tomcat无法共享session存储空间,当请求切换到不同Tomcat服务时,原来存储在一台Tomcat服务中的数据,在其他Tomcat中是看不到的,这就导致了导致数据丢失的问题。

虽然系统为单体式的架构,但是为了将来应对并发要做水平扩展,部署多个形成负载均衡的集群。

当请求进入nignx会在多台Tomcat之间做一个轮询,每一个Tomcat都有自己的session空间,假设用户请求第一次被负载均衡到了Tomcat1,去发送验证码或者登录时所获取到的用户信息仅仅是保存到这一台Tomcat里了,当用户第二次来执行某些业务被负载均衡到了第二台Tomcat服务上,当该服务要去获取验证码或者用户信息时,而他自身的session内存空间空无一物,这时服务就会中断。

这就是session共享问题。

解决方案:

在初期,为了解决session共享问题,官方提供了session拷贝功能,多台Tomcat之间只要做好一些配置,它们之间就可以互相实现一个数据拷贝,但数据拷贝也有不小的问题,首先就是多台Tomcat保存相同的数据,浪费内存空间,其次拷贝数据是需要时间的,这就造成了时间延迟,在这个延迟之间如果有服务来访问,依旧会造成数据不一致的问题。问题太多,该方案就被pass了。

因此就必须寻找到可以替代session的方案,且必须满足:

  • 数据共享

  • 内存存储(因为session是基于内存的,读写效率较高,像登录校验这种业务访问频率较高,需要满足高并发的需求)

  • 键值对结构(session存储较为简单)

这就是Redis,首先redis是在Tomcat以外的一个存储方案,,任意一台Tomcat都能访问到Redis,所以就可以实现数据共享了,就不会出现数据丢失的情况 其次Redis就是存储在内存中的,而且性能非常强,并且redis就是键值对类型的数据库,因此我们是可以用redis来代替session。

基于Redis实现共享session登录
业务流程

如果要使用Redis来代替session,那么前面的短信登录业务也有相应的变化。

比如在发送验证码的业务流程中,需要将验证码存入redis,并且还需要考虑value是什么类型的数据结构,存入验证码时就可以直接用String类型即可。

而key类型就不能和原先一致,因为session在发送请求的时候都有一个独立的session,在Tomcat服务中维护了很多session,那么不同浏览器携带的手机号请求服务时都有着自己独立的session,这些服务都是使用code作为key,但是互相之间互不干扰。

在使用session时,不需要考虑取数据的问题,因为Tomcat会自动的帮助我们去维护session:浏览器发去请求时,Tomcat就为浏览器新建一个session,如果session存在,直接使用即可,在创建session时,就会自动创建sessionID写入到对应浏览器的cookie中,以后浏览器的每次请求就会带着cookie,带着sessionID,这样就能精确找到对应的session,也不用去考虑取的问题。

但redis是一个共享的内存空间,不管是哪个服务发请求,都是往redis的内存空间存储的,如果每个服务使用的key都是code,就会相互覆盖,就会造成数据丢失。而必须要确保每次不同服务访问的key都不同。

那既然如此,那就直接将手机号码作为每个服务的key,这样就能保证每个服务都有自己不同的key,这样的操作也有助于将来我们去获取验证码进行验证。

image-20250524144322613

现在要使用redis,没有维护,现在以手机为key存入进去,那浏览器做登录时还需要带着这个key的值来取,才可以验证。

而在等短信验证码登录注册时,需要将手机号码与验证码存入,正好可以根据手机号码去拿到value。

再去根据手机号查询用户,如果用户存在,则将用户存入redis中。

此时需要考虑两个问题,一是value的数据类型选择问题,二是考虑key的命名,

在短信登录业务存入的是Java对象,那么redis的value虽然可以使用string类型,用json字符串保存,比较直观,但是无法针对单个字段作出修改,只能修改整个字段。

这时可以使用hash类型,hash结构可以将对象中的每个字段独立存储,可以针对单个字段做修改,并且内存占用更少(Hash结构只需要保存数据本身即可,但是String类型还需要保存json字符串的格式)。

如果从优化角度来看,比较推荐Hash结构。

而key的要求也有两点:一是唯一性,二是较为便携。

这里推荐使用随机的token(随机字符串,可以使用UUID来生成)作为key存储用户数据。

而在登录校验这一业务中,以前使用session时的登录凭证就是sessionID,被存在浏览器的cookie中被一直携带,且一直被Tomcat维护。

而现在使用的redis来代替session,则我们使用的随机token则是登录凭证,也就意味着以后浏览器来访问我们需要携带token将其作为凭证。而Tomcat不会将其自动的写入浏览器中,我们需要手动的将其返回前端,那么此处流程就产生了变化。

image-20250524142713311

那当服务器拿到token之后,我们就可以基于token来从redis获取用户信息,剩下校验登录状态流程就不变

image-20250524142915111

而登录凭证是通过前端的逻辑代码进行接收并保留的,在前端使用axios的拦截器,利用拦截器将用户token放在“authorization”头,这样每一条用户请求就会携带token。如果我们使用手机号码作为key去保存,将来返回到前端直接保存在浏览器会有泄漏的风险。这就是我们key不能再次使用手机号码的原因。

代码修改

发送验证码

 @Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {//2.如果不符合,返回错误信息return Result.fail("手机号格式错误");}//3.如果符合,生成验证码String code = RandomUtil.randomNumbers(6);//4.保存验证码到redis 还需要给验证码设置有效时间 set key value ex 120//一般都会定义一个工具类来保存常量,避免重复及手误stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);//5.发送验证码(模拟发送验证码,该业务并未实现)log.debug("发送短信验证码成功,验证码:{}",code);// 6.返回结果return Result.ok();}

短信验证码登录注册

 @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {//2.如果不符合,返回错误信息return Result.fail("手机号格式错误");}//2.从Redis中获取验证码并校验String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();// 一般校验时,从反向校验,这种校验不需要if嵌套,否则会嵌套if,避免if嵌套过深if (cacheCode == null || !cacheCode.equals(code)){//3.不一致,返回错误信息return Result.fail("验证码错误");}//4.一致,根据手机号查询用户 select   * from user where phone = ?User user = query().eq("phone", phone).one();//5.判断用户是否存在if (user == null){//6.不存在,创建新用户并保存// 方法定义在函数中 创建用户//在创建完用户到数据库后还需要保存在session中,所以直接赋值给useruser = createUserWithPhone(phone);}//7.保存用户信息到redis,//7.1随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);//7.2将user对象转换为hash存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);//7.3存储到redis中 利用工具类将userDTO转为mapstringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY+token,BeanUtil.beanToMap(userDTO));// 7.4设置token有效期stringRedisTemplate.expire(LOGIN_USER_KEY+token,LOGIN_USER_TTL,TimeUnit.MINUTES);//7.4返回token给客户端return Result.ok(token);}

问题提出:

在之前使用session时,session的有效期是30分钟,但session的有效期是指只要一直在访问session,那么session的有效期就一直是30分钟,只有超过30分钟不访问session,session才会失效。

但是redis的有效时间就是从新建到移除的时间,不在乎是否访问,这样就有弊端。

应该像session一样,只要用户在访问,就应该更新有效时间(即Redis中的token有效期),但是Redis无法得知用户有没有访问服务端,也无法得知用户何时访问服务端。

而在登录拦截校验中,我们所有的请求访问时都要经过拦截器的拦截与校验,只要经过了这个校验,就能证明该浏览器是一个正在活跃着的用户,这是我们就可以更新redis的有效期。

这样就可以做到和session一样的效果,只要有浏览器访问服务端,那么Redis就会去更新token的有效期。所以在接下来修改登录拦截校验代码时还需要添加更新token有效期的逻辑。

代码如下:

 public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}// 前置拦截器@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");// 判断是否存在if (StrUtil.isBlank(token)) {response.setStatus(401);return false;}//  2.以token为key获取redis的用户Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);//  3.判断用户是否存在if (userMap.isEmpty()) {//  4.不存在 拦截器拦截 返回401状态码 未授权response.setStatus(401);return false;}//5.将查询到的Hash数据转换为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//  6.存在,保存用户信息到ThreadLocal// 在工具类中定义了一个UserHolder 是一个线程安全的ThreadLocal变量,用于保存当前线程的用户信息。// 其中有三个方法:saveUser( 保存),getUser(拿到),removeUser(移除)。UserHolder.saveUser(userDTO);//7.刷新token有效期stringRedisTemplate.expire(LOGIN_USER_KEY+token, LOGIN_USER_TTL, TimeUnit.MINUTES);//8.放行return true;}​//  拦截器 后处理@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//  移除用户 避免用户泄漏UserHolder.removeUser();}}

修改成功,

测试结果:

报错,类强制转换异常ClassCastException,redis serializer报错 long类型不能转换成String类型,在UserServiceImpl中向redis存入用户信息时报错,userMap来自userDTO,userDTO中的id为long类型,无法存储到redis中去,因为我们使用的RedisTemplate为StringRedisTemplate,他要求key与value都必须是string类型,但userMap中的id为long类型,因此报错。

解决方案:

  • 自己在重写toMap函数,在将userDTO转换成userMap时将值的类型转换成string字符串

  • 提供的工具类有自定义的功能,如下所示

 Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));

再次测试:

image-20250524200915128

解决状态登录刷新的问题

问题引入:

目前的短信登录拦截器无法做到只要用户一直登陆就不会过期。

因为拦截器拦截的不是一切路径,而是那些需要登录校验的路径,比如user/me,或者将来用户的下单,支付这样一些对用户信息有需求的路径,或者说被拦截器拦截的路径,但拦截器并不是拦截一切。

如果用户一直访问的是不需要登录的页面,比如首页或者商户详情页,这些不需要登录校验就可以看,这些就不会去刷新有效期,过了指定的有效期后,即使用户还在访问,但token就会被移除,问题因此出现。

解决方案:

在原有拦截器的基础上再加上一个拦截器,这样用户请求就要先经过第一个拦截器,在经过第二个,第一个拦截器拦截全部路径,所有请求都会被拦截,就可以在这个拦截器中做刷新token有效期的业务(获取token,查询Redis用户,保存到ThreadLocal中,刷新token有效期,放行),第一个拦截器不做拦截,这样就可以确保一切请求都可以触发刷新的动作,第二个拦截器只需要做拦截业务(查询ThreadLocal的用户,不存在则拦截,存在,则继续)即可

代码展示:

LoginInterceptor.java

 public class LoginInterceptor implements HandlerInterceptor {// 前置拦截器@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.判断是否需要拦截( ThreadLocal中是否有用户)if (UserHolder.getUser() == null) {//  2.没有,拦截,返回401response.setStatus(401);return false;} else {// 有用户,放行return true;}}

RefreshTokenInterceptor.java

 public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;​public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}​// 前置拦截器@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");// 判断是否存在if (StrUtil.isBlank(token)) {return true;}//  2.以token为key获取redis的用户Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);//  3.判断用户是否存在if (userMap.isEmpty()) {//  4.不存在 拦截器拦截 返回401状态码 未授权return true;}//5.将查询到的Hash数据转换为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//  6.存在,保存用户信息到ThreadLocal// 在工具类中定义了一个UserHolder 是一个线程安全的ThreadLocal变量,用于保存当前线程的用户信息。// 其中有三个方法:saveUser( 保存),getUser(拿到),removeUser(移除)。UserHolder.saveUser(userDTO);//7.刷新token有效期stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.SECONDS);//8.放行return true;}​//  拦截器 后处理@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//  移除用户 避免用户泄漏UserHolder.removeUser();}}

装配拦截器:

 
@Configurationpublic class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//  登录拦截器registry.addInterceptor(new LoginInterceptor())//  除了这些路径,其他路径都进行拦截.excludePathPatterns("/user/code","/user/login","/blog/hot","/voucher/**","/shop/**","/shop-type/**","/upload/**","/blog/query/hot","/druid/**").order(1);// token刷新拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}}

如何控制拦截器的前后顺序:在源码中order的值越大,优先级越低,值越小,优先级越高。

测试成功,无论访问哪个页面,都会刷新token的有效期。

至此,基于Redis实现共享session登录的业务完成。

总结:基于Redis改造短信登录,改造的点如下:

  • 发送短信验证码时将验证码存入redis中,key使用的是手机号码,value的类型为String

  • 短信登录时保存用户到Redis,key要保证唯一以及便携,因此将key设为了UUID,放在了前端的请求头中,返回给了用户,保存到浏览器中,这样一来浏览器可以携带token来访问服务端,从而实现登陆的效果。

注意事项:

  • 在使用redis存储数据的时候,key的规范非常重要。还有数据类型的选择,code选择String类型,而用户选择了Hash类型。

  • 我们在存储数据的过程中,要记得设置存储有效期。

  • 要选择合适的存储粒度,我们并没有存储完整的用户信息,而是将一些敏感信息给去掉了,只保存一些不太敏感、页面需要的数据,这样还可以节省内存空间

相关文章:

黑马点评--基于Redis实现共享session登录

集群的session共享问题分析 session共享问题&#xff1a;多台Tomcat无法共享session存储空间&#xff0c;当请求切换到不同Tomcat服务时&#xff0c;原来存储在一台Tomcat服务中的数据&#xff0c;在其他Tomcat中是看不到的&#xff0c;这就导致了导致数据丢失的问题。 虽然系…...

Mujoco 学习系列(二)基础功能与xml使用

这篇文章是 Mujoco 学习系列第二篇&#xff0c;主要介绍一些基础功能与 xmI 使用&#xff0c;重点在于如何编写与读懂 xml 文件。 运行这篇博客前请先确保正确安装 Mujoco 并通过了基本功能与GUI的验证&#xff0c;即至少完整下面这个博客的 第二章节 内容&#xff1a; Mujoc…...

比特授权云外壳加密支持Android 15!

在信息化时代&#xff0c;多数软件供应商需要适配安卓系统&#xff0c;以扩大市场、满足用户需求并提升竞争力。APK作为Android应用的安装包&#xff0c;包含代码、资源、配置文件等运行所需组件&#xff0c;用于在设备端分发和安装应用。企业在分发软件时&#xff0c;需要通过…...

uniapp使用sse连接后端,接收后端推过来的消息(app不支持!!)

小白终成大白 文章目录 小白终成大白前言一、什么是SSE呢&#xff1f;和websocket的异同点有什么&#xff1f;相同点不同点 二、直接上实现代码总结 前言 一般的请求就是前端发 后端回复 你一下我一下 如果需要有什么实时性的 后端可以主动告诉前端的技术 我首先会想到 webso…...

历年复旦大学保研上机真题

2025复旦大学保研上机真题 2024复旦大学保研上机真题 2023复旦大学保研上机真题 在线测评链接&#xff1a;https://pgcode.cn/problem?classification1 最大公共子串 题目描述 输入 3 个子串&#xff0c;输出这 3 个子串的最大公共子串。 输入格式 输入包含 3 个子串&…...

黑马点评-实现安全秒杀优惠券(使并发一人一单,防止并发超卖)

一.实现优惠券秒杀 1.最原始代码&#xff1a; Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {Resourceprivate ISeckillVoucherService seckillVoucherService;Resourcepriv…...

解决论文中字体未嵌入的问题

文章总览&#xff1a;YuanDaiMa2048博客文章总览 解决论文中字体未嵌入的问题 问题描述解决方案&#xff1a;使用 Adobe PDF 打印机嵌入字体&#xff08;WPS版&#xff09;步骤一&#xff1a;打开 PDF 文件步骤二&#xff1a;选择打印到 Adobe PDF步骤三&#xff1a;修改 Adobe…...

leetcode 131. Palindrome Partitioning

目录 一、题目描述 二、方法1、回溯法每次暴力判断回文子串 三、方法2、动态规划回溯法 一、题目描述 分割回文子串 131. Palindrome Partitioning 二、方法1、回溯法每次暴力判断回文子串 class Solution {vector<vector<string>> res;vector<string>…...

Android本地语音识别引擎深度对比与集成指南:Vosk vs SherpaOnnx

技术选型对比矩阵 对比维度VoskSherpaOnnx核心架构基于Kaldi二次开发ONNX Runtime + K2新一代架构模型格式专用格式(需专用工具转换)ONNX标准格式(跨框架通用)中文识别精度89.2% (TDNN模型)92.7% (Zipformer流式模型)内存占用60-150MB30-80MB迟表现320-500ms180-300ms多线程…...

审计报告附注救星!实现Word表格纵向求和+横向计算及其对应的智能校验

在审计工作中&#xff0c;Word附注通常包含很多表格。为了确保附注数字的准确性&#xff0c;我们需要对这些表格进行数字逻辑校验&#xff0c;主要包含两个维度&#xff1a;在纵向上验证合计项金额是否正确&#xff1b;在横向上检查“年末金额年初金额本期增加-本期减少”的勾稽…...

人工智能数学基础实验(四):最大似然估计的-AI 模型训练与参数优化

一、实验目的 理解最大似然估计&#xff08;MLE&#xff09;原理&#xff1a;掌握通过最大化数据出现概率估计模型参数的核心思想。实现 MLE 与 AI 模型结合&#xff1a;使用 MLE 手动估计朴素贝叶斯模型参数&#xff0c;并与 Scikit-learn 内置模型对比&#xff0c;深入理解参…...

告别延迟!Ethernetip转modbustcp网关在熔炼车间监控的极速时代

熔炼车间热火朝天&#xff0c;巨大的热风炉发出隆隆的轰鸣声&#xff0c;我作为一名技术操控工&#xff0c;正密切关注着监控系统上跳动的各项参数。这套基于EtherNET/ip的监控系统&#xff0c;是我们车间数字化改造的核心&#xff0c;它将原本分散的控制单元整合在一起&#x…...

Kotlin协程优化Android ANR问题

引言 在Android开发中&#xff0c;ANR&#xff08;Application Not Responding&#xff09;是用户体验的致命杀手。当主线程被耗时操作阻塞超过阈值&#xff08;5秒前台/10秒后台&#xff09;&#xff0c;系统会直接弹窗提示应用无响应。本文将深入剖析如何通过Kotlin协程将耗…...

Visual Studio Code插件离线安装指南:从市场获取并手动部署

Visual Studio Code插件离线安装指南&#xff1a;从市场获取并手动部署 一、场景背景二、操作步骤详解步骤1&#xff1a;访问官方插件市场步骤2&#xff1a;定位目标版本步骤3&#xff1a;提取关键参数步骤4&#xff1a;构造下载链接步骤5&#xff1a;下载与安装 三、注意事项 …...

构建安全AI风险识别大模型:CoT、训练集与Agent vs. Fine-Tuning对比

构建安全AI风险识别大模型:CoT、训练集与Agent vs. Fine-Tuning对比 安全AI风险识别大模型旨在通过自然语言处理(NLP)技术,检测和分析潜在的安全威胁,如数据泄露、合规违规或恶意行为。本文从Chain-of-Thought (CoT)设计、训练集构建、以及Agent-based方法与**AI直接调优…...

计算机视觉---YOLOv1

YOLOv1深度解析&#xff1a;单阶段目标检测的开山之作 一、YOLOv1概述 提出背景&#xff1a; 2016年由Joseph Redmon等人提出&#xff0c;全称"You Only Look Once"&#xff0c;首次将目标检测视为回归问题&#xff0c;开创单阶段&#xff08;One-Stage&#xff09…...

无法同步书签,火狐浏览器修改使用国内的账号服务器

自动更新版本后,变为国际服版本的了,点击右上角无法登录firefox,也无法同步书签,现在国际服的火狐浏览器修改使用国内的账号服务器&#xff0c;需要先在搜索框输入 about:config 中改变三项配置&#xff0c;然后重启浏览器&#xff0c;才能正常使用国内的火狐账号服务器 ident…...

动态防御体系实战:AI如何重构DDoS攻防逻辑

1. 传统高防IP的静态瓶颈 传统高防IP依赖预定义规则库&#xff0c;面对SYN Flood、CC攻击等常见威胁时&#xff0c;常因规则更新滞后导致误封合法流量。例如&#xff0c;某电商平台遭遇HTTP慢速攻击时&#xff0c;静态阈值过滤无法区分正常用户与攻击者&#xff0c;导致订单接…...

Kotlin Native与C/C++高效互操作:技术原理与性能优化指南

一、互操作基础与性能瓶颈分析 1.1 Kotlin Native调用原理 Kotlin Native通过LLVM编译器生成机器码,与C/C++的互操作基于以下核心机制: CInterop工具:解析C头文件生成Kotlin/Native绑定(.klib),自动生成类型映射和包装函数双向调用约定: Kotlin调用C:直接通过生成的绑…...

爬虫核心概念与工作原理详解

爬虫核心概念与工作原理详解 1. 什么是网络爬虫&#xff1f; 网络爬虫&#xff08;Web Crawler&#xff09;是一种按照特定规则自动抓取互联网信息的程序或脚本&#xff0c;本质是模拟人类浏览器行为&#xff0c;通过HTTP请求获取网页数据并解析处理。 形象比喻&#xff1a;如…...

Flink架构概览,Flink DataStream API 的使用,FlinkCDC的使用

一、Flink与其他组件的协同 Flink 是一个分布式、高性能、始终可用、准确一次&#xff08;Exactly-Once&#xff09;语义的流处理引擎&#xff0c;广泛应用于大数据实时处理场景中。它与 Hadoop 生态系统中的组件可以深度集成&#xff0c;形成完整的大数据处理链路。下面我们从…...

vue3前端后端地址可配置方案

在开发vue3项目过程中&#xff0c;需要切换不同的服务器部署&#xff0c;代码中配置的服务需要可灵活配置&#xff0c;不随着run npm build把网址打包到代码资源中&#xff0c;不然每次切换都需要重新run npm build。需要一个配置文件可以修改服务地址&#xff0c;而打包的代码…...

Es6中怎么使用class实现面向对象编程

在 JavaScript 中&#xff0c;面向对象的类可以通过 class 关键字来定义。以下是一个简单的示例&#xff0c;展示了如何定义一个类、创建对象以及添加方法&#xff1a; 基础类定义 // 定义一个类 class MyClass { // 构造函数&#xff0c;用于初始化对象的属性 constructor(pa…...

digitalworld.local: FALL靶场

digitalworld.local: FALL 来自 <digitalworld.local: FALL ~ VulnHub> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.182&#xff0c;靶场IP192.168.23.4 3&…...

MySQL---库操作

mysql> create database if not exists kuku3; 1.库操作的语法 create database [if not exists] db_name [create_specification [, create_specification] ...] create_specification: [default] character set charset_name [default] collate collation_name详细解释…...

动态规划算法:字符串类问题(2)公共串

0 前言 上节课我们已经讲述了使用动态规划求取回文串长度与数量的方法&#xff08;和本节课关系不大&#xff0c;感兴趣可以去看字符串类问题&#xff08;1&#xff09;回文串&#xff09;&#xff0c;这节课我们继续探索字符串问题中的动态规划问题。 进入本篇文章前&#x…...

uni-app(5):Vue3语法基础上

Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统&#xff0c;只关注视图层&#xff0c;…...

深度解析Vue项目Webpack打包分包策略 从基础配置到高级优化,全面掌握性能优化核心技巧

深度解析Vue项目Webpack打包分包策略 从基础配置到高级优化&#xff0c;全面掌握性能优化核心技巧 一、分包核心价值与基本原理 1.1 为什么需要分包 首屏加载优化&#xff1a;减少主包体积&#xff0c;提升TTI&#xff08;Time to Interactive&#xff09;缓存利用率提升&am…...

ubuntu下docker安装mongodb-支持单副本集

1.mogodb支持事务的前提 1) MongoDB 版本&#xff1a;确保 MongoDB 版本大于或等于 4.0&#xff0c;因为事务支持是在 4.0 版本中引入的。 2) 副本集配置&#xff1a;MongoDB 必须以副本集&#xff08;Replica Set&#xff09;模式运行&#xff0c;即使是单节点副本集&#x…...

spring-boot-starter-data-redis应用详解

一、依赖引入与基础配置 添加依赖 在 pom.xml 中引入 Spring Data Redis 的 Starter 依赖&#xff0c;默认使用 Lettuce 客户端&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis<…...