基于Ruoyi的同一token跨系统访问,后端单点登录并且鉴权方案

基于Ruoyi的同一token跨系统访问,后端单点登录并且鉴权方案

    • 需求场景以及先决条件
    • 默认方案
    • 改造思路
    • 改造代码,一共4个类需要变更
    • 完整需要修改的代码

需求场景以及先决条件

  1. 同一环境下的多个ruoyi项目,各自使用相同的一组用户(我这里用的是LDAP的登录,不影响本文),但是每个权限拥有各自项目的权限.
  2. 希望一个前端登录的token,可以跨越不同的后端同时使用,即一个token访问所有系统,每个系统的权限,系统内部自行判断.

默认方案

  1. 默认情况下,ruoyi框架的设计是使用redis存储一个uuid作为token的key,用户信息作为value,存入到redis中的,例如

login_tokens:27e3c1ed-dee7-4495-99c7-d175beb4f0b1
{“@type”:“com.ruoyi.framework.security.LoginUser”,“browser”:“Firefox 13”,“deptId”:100,“expireTime”:1731988245275,“ipaddr”:“127.0.0.1”,“loginLocation”:“内网IP”,“loginTime”:1731984645275,“os”:“Windows 10”,“permissions”:Set[“:😗”],“token”:“27e3c1ed-dee7-4495-99c7-d175beb4f0b1”,“…省略以下”}

  1. 且每一个项目使用一个redis的db,互相独立
  2. 前端持有的jwt,是以uuid生成的令牌,即

Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjI3ZTNjMWVkLWRlZTctNDQ5NS05OWM3LWQxNzViZWI0ZjBiMSJ9.Ec0a7hRsyAL2ehZfiXWox0aczb58rKpARVipWfhbuir6sPrNsCArbbF47zDA2Mtmf1CHBdyodJx1bsWZrjLiKw

等价于

login_tokens:27e3c1ed-dee7-4495-99c7-d175beb4f0b1

改造思路

  1. 使用一个db,例如db0,所有的项目都多建立一个redis连接,连到db0.实现redis的部分共享
  2. 读取到token存在,即认为用户已登录
  3. 读取对应的程序的token,如果不存在,就是系统内部登录,创建一个对应token.
  4. 示例如下,指示一个token: 27e… 同时登录到了report和spc_mini2个系统,第一个key存储的值是username,其他的都是自己用的user信息,即权限信息等; 3和7是各自系统的其他redis缓存
    在这里插入图片描述

改造代码,一共4个类需要变更

在这里插入图片描述

  1. 新建数据源RedisCacheUser
  2. 配置数据源在RedisConfig
 @Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<String, Object> redisTemplateDb0(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();// 使用原工厂配置,创建一个新的,但是指向其他的dbLettuceConnectionFactory originalLettuceFactory = (LettuceConnectionFactory) connectionFactory;RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();redisStandaloneConfiguration.setHostName(originalLettuceFactory.getHostName());redisStandaloneConfiguration.setPort(originalLettuceFactory.getPort());redisStandaloneConfiguration.setPassword(originalLettuceFactory.getPassword());LettuceConnectionFactory clonedFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);clonedFactory.setDatabase(0);clonedFactory.afterPropertiesSet();template.setConnectionFactory(clonedFactory);FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);serializer.setObjectMapper(mapper);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}
  1. 修改登录和获取用户的方式,就是存入到redis的key
    /*** 获取用户身份信息** @return 用户信息*/public LoginUser getLoginUser(HttpServletRequest request) {// 获取请求携带的令牌String token = getToken(request);if (StringUtils.isNotEmpty(token)) {try {Claims claims = parseToken(token);// 解析对应的权限以及用户信息String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);String userKey = getTokenKey(uuid);LoginUser user = redisCache.getCacheObject(userKey);if (user == null){String userKeyUser = getTokenUser(uuid);String username = redisCache.getCacheObject(userKeyUser);user = (LoginUser) userDetailsService.loadUserByUsername(username);user.setToken(uuid);setUserAgent(user);refreshToken(user);}return user;} catch (Exception e) {}}return null;}

完整需要修改的代码

package com.ruoyi.framework.config;import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;/*** redis配置** @author ruoyi*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{@Bean@Primary@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);serializer.setObjectMapper(mapper);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}@Beanpublic DefaultRedisScript<Long> limitScript(){DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(limitScriptText());redisScript.setResultType(Long.class);return redisScript;}/*** 限流脚本*/private String limitScriptText(){return "local key = KEYS[1]\n" +"local count = tonumber(ARGV[1])\n" +"local time = tonumber(ARGV[2])\n" +"local current = redis.call('get', key);\n" +"if current and tonumber(current) > count then\n" +"    return tonumber(current);\n" +"end\n" +"current = redis.call('incr', key)\n" +"if tonumber(current) == 1 then\n" +"    redis.call('expire', key, time)\n" +"end\n" +"return tonumber(current);";}@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<String, Object> redisTemplateDb0(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();// 使用原工厂配置,创建一个新的,但是指向其他的dbLettuceConnectionFactory originalLettuceFactory = (LettuceConnectionFactory) connectionFactory;RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();redisStandaloneConfiguration.setHostName(originalLettuceFactory.getHostName());redisStandaloneConfiguration.setPort(originalLettuceFactory.getPort());redisStandaloneConfiguration.setPassword(originalLettuceFactory.getPassword());LettuceConnectionFactory clonedFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);clonedFactory.setDatabase(0);clonedFactory.afterPropertiesSet();template.setConnectionFactory(clonedFactory);FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);serializer.setObjectMapper(mapper);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}}
package com.ruoyi.framework.redis;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;import java.util.*;
import java.util.concurrent.TimeUnit;/*** spring redis 工具类** @author ruoyi**/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCacheUser
{@Autowired@Qualifier("redisTemplateDb0")public RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit){redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @param unit 时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit){return redisTemplate.expire(key, timeout, unit);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key){ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key){return redisTemplate.delete(key);}/*** 删除集合对象** @param collection 多个对象* @return*/public long deleteObject(final Collection collection){return redisTemplate.delete(collection);}/*** 缓存List数据** @param key 缓存的键值* @param values 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList){Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key){return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet){BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()){setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key){return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap){if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value){redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey){HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 获取多个Hash中的数据** @param key Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys){return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern){return redisTemplate.keys(pattern);}
}
package com.ruoyi.framework.security.service;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;import com.ruoyi.framework.redis.RedisCacheUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.framework.redis.RedisCache;
import com.ruoyi.framework.security.LoginUser;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;/*** token验证处理** @author ruoyi*/
@Component
public class TokenService
{// 令牌自定义标识@Value("${token.header}")private String header;// 令牌秘钥@Value("${token.secret}")private String secret;// 令牌有效期(默认30分钟)@Value("${token.ExpireTime}")private int ExpireTime;@Value("${ruoyi.name}")private String projectName;protected static final long MILLIS_SECOND = 1000;protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;@Autowiredprivate RedisCacheUser redisCache;@Autowiredprivate UserDetailsService userDetailsService;/*** 获取用户身份信息** @return 用户信息*/public LoginUser getLoginUser(HttpServletRequest request) {// 获取请求携带的令牌String token = getToken(request);if (StringUtils.isNotEmpty(token)) {try {Claims claims = parseToken(token);// 解析对应的权限以及用户信息String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);String userKey = getTokenKey(uuid);LoginUser user = redisCache.getCacheObject(userKey);if (user == null){String userKeyUser = getTokenUser(uuid);String username = redisCache.getCacheObject(userKeyUser);user = (LoginUser) userDetailsService.loadUserByUsername(username);user.setToken(uuid);setUserAgent(user);refreshToken(user);}return user;} catch (Exception e) {}}return null;}/*** 设置用户身份信息*/public void setLoginUser(LoginUser loginUser) {if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {refreshToken(loginUser);}}/*** 删除用户身份信息*/public void delLoginUser(String token) {if (StringUtils.isNotEmpty(token)) {String userKey = getTokenKey(token);redisCache.deleteObject(userKey);}}/*** 创建令牌** @param loginUser 用户信息* @return 令牌*/public String createToken(LoginUser loginUser) {String token = IdUtils.fastUUID();loginUser.setToken(token);setUserAgent(loginUser);refreshToken(loginUser);Map<String, Object> claims = new HashMap<>();claims.put(Constants.LOGIN_USER_KEY, token);return createToken(claims);}/*** 验证令牌有效期,相差不足20分钟,自动刷新缓存** @param token 令牌* @return 令牌*/public void verifyToken(LoginUser loginUser) {long ExpireTime = loginUser.getExpireTime();long currentTime = System.currentTimeMillis();if (ExpireTime - currentTime <= MILLIS_MINUTE_TEN) {refreshToken(loginUser);}}/*** 刷新令牌有效期** @param loginUser 登录信息*/public void refreshToken(LoginUser loginUser) {loginUser.setLoginTime(System.currentTimeMillis());loginUser.setExpireTime(loginUser.getLoginTime() + ExpireTime * MILLIS_MINUTE);// 根据uuid将loginUser缓存String userKey = getTokenKey(loginUser.getToken());redisCache.setCacheObject(userKey, loginUser, ExpireTime, TimeUnit.MINUTES);//再保存一个key,来存储用户名称String userKeyUser = getTokenUser(loginUser.getToken());redisCache.setCacheObject(userKeyUser, loginUser.getUsername(), ExpireTime, TimeUnit.MINUTES);}/*** 设置用户代理信息** @param loginUser 登录信息*/public void setUserAgent(LoginUser loginUser) {UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));String ip = IpUtils.getIpAddr(ServletUtils.getRequest());loginUser.setIpaddr(ip);loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));loginUser.setBrowser(userAgent.getBrowser().getName());loginUser.setOs(userAgent.getOperatingSystem().getName());}/*** 从数据声明生成令牌** @param claims 数据声明* @return 令牌*/private String createToken(Map<String, Object> claims) {String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();return token;}/*** 从令牌中获取数据声明** @param token 令牌* @return 数据声明*/private Claims parseToken(String token) {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}/*** 从令牌中获取用户名** @param token 令牌* @return 用户名*/public String getUsernameFromToken(String token) {Claims claims = parseToken(token);return claims.getSubject();}/*** 获取请求token** @param request* @return token*/private String getToken(HttpServletRequest request) {String token = request.getHeader(header);if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {token = token.replace(Constants.TOKEN_PREFIX, "");}return token;}private String getTokenKey(String uuid) {return Constants.LOGIN_TOKEN_KEY + projectName + "." + uuid;}private String getTokenUser(String uuid) {return Constants.LOGIN_TOKEN_KEY + uuid;}
}
package com.ruoyi.project.monitor.controller;import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;import com.ruoyi.framework.redis.RedisCacheUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.redis.RedisCache;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.page.TableDataInfo;
import com.ruoyi.project.monitor.domain.SysUserOnline;
import com.ruoyi.project.system.service.ISysUserOnlineService;/*** 在线用户监控** @author ruoyi*/
@RestController
@RequestMapping("/monitor/online")
public class SysUserOnlineController extends BaseController {@Autowiredprivate ISysUserOnlineService userOnlineService;@Autowiredprivate RedisCacheUser redisCache;@PreAuthorize("@ss.hasPermi('monitor:online:list')")@GetMapping("/list")public TableDataInfo list(String ipaddr, String userName) {Collection<String> keys = redisCache.keys(Constants.LOGIN_TOKEN_KEY + "*");List<SysUserOnline> userOnlineList = new ArrayList<SysUserOnline>();for (String key : keys) {try {LoginUser user = redisCache.getCacheObject(key);if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) {if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) {userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user));}} else if (StringUtils.isNotEmpty(ipaddr)) {if (StringUtils.equals(ipaddr, user.getIpaddr())) {userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user));}} else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) {if (StringUtils.equals(userName, user.getUsername())) {userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));}} else {userOnlineList.add(userOnlineService.loginUserToUserOnline(user));}} catch (ClassCastException ignored) {continue;}}Collections.reverse(userOnlineList);userOnlineList.removeAll(Collections.singleton(null));return getDataTable(userOnlineList);}/*** 强退用户*/@PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')")@Log(title = "在线用户", businessType = BusinessType.FORCE)@DeleteMapping("/{tokenId}")public AjaxResult forceLogout(@PathVariable String tokenId) {redisCache.deleteObject(Constants.LOGIN_TOKEN_KEY + tokenId);return AjaxResult.success();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/20322.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于Lora通讯加STM32空气质量检测WIFI通讯-分享

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着环境污染问题的日益严重&#xff0c;空气质量的监测与管理已经…

How to paint colors to the assets cube through .urdf

1. Find your assets/cube.urdf Something looks like this <?xml version"1.0"?> <robot name"object"><link name"object"><visual><origin xyz"0 0 0"/><geometry><box size"0.05…

刚学php序列化/反序列化遇到的坑(攻防世界:Web_php_unserialize)

刚开始遇到题目的时候&#xff0c;思路还是很明确。 原题入口&#xff1a;攻防世界 (xctf.org.cn) 中的 Web_php_unserialize 两个函数 serialize() //将一个对象转换成一个字符串 unserialize() //将字符串还原成一个对象 首先看到 unserialize() 可以知道基本上能得…

5G 现网信令参数学习(3) - RrcSetup(2)

前一篇&#xff1a;5G 现网信令参数学习(3) - RrcSetup(1) 目录 1. rlf-TimersAndConstants 2. spCellConfigDedicated 2.1 initialDownlinkBWP 2.1.1 pdcch-Config 2.1.1.1 controlResourceSetToAddModList 2.1.1.2 searchSpacesToAddModList 2.1.2 pdsch-Config 2.1…

在windows上打包mediasoup arm64版本的docker镜像

mediasoup版本&#xff1a;3.14.14 mediasoup-demo版本&#xff1a;v3 windows 10 专业版 docker-desktop版本&#xff1a;4.30.0 (149282) docker info: Client:Version: 26.1.1Plugins:buildx: Docker Buildx (Docker Inc.)Version: v0.14.0-desktop.1Path: C:\Prog…

11.19机器学习_逻辑回归

十二 逻辑回归 1.概念 逻辑回归(Logistic Regression)是机器学习中的一种分类模型&#xff0c;逻辑回归是一种分类算法&#xff0c;虽然名字中带有回归&#xff0c;但是它与回归之间有一定的联系。由于算法的简单和高效&#xff0c;在实际中应用非常广泛。 逻辑回归一般用于…

【LLM训练系列01】Qlora如何加载、训练、合并大模型

示例1&#xff1a;Qlora训练Qwen2.5 参考脚本&#xff1a;https://github.com/QwenLM/Qwen/blob/main/recipes/finetune/deepspeed/finetune_qlora_multi_gpu.ipynb 训练命令如下&#xff1a; !torchrun --nproc_per_node 2 --nnodes 1 --node_rank 0 --master_addr localho…

Jmeter数据库压测之达梦数据库的配置方法

目录 1、概述 2、测试环境 3、数据库压测配置 3.1 安装jmeter 3.2 选择语言 3.3 新建测试计划 3.4 配置JDBC连接池 3.5 配置线程组 3.6 配置测试报告 3.7 执行测试 1、概述 Jmeter是Apache组织开发的基于Java的压力测试工具&#xff0c;用于对软件做压力测试。 它最…

[ 应急响应进阶篇-1 ] Windows 创建后门并进行应急处置-5:启动项后门

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

编译报错:protoc did not exit cleanly. Review output for more information.

目录标题 解决“protoc did not exit cleanly”的报错问题检查.proto文件的语法 解决“protoc did not exit cleanly”的报错问题 今天做的项目需要用到grpc&#xff0c;然后需要编写proto然后编译后实现grpc的具体方法&#xff01; 结果编译的时候报了protoc did not exit cl…

Java码农人生开启手册——重载和重写

一、重载 有时在调用现有方法时会出现参数类型不匹配的问题&#xff0c;在Java中&#xff0c;如果多个方法的名字相同&#xff0c;参数列表不同&#xff0c;则称该几种方法被重载了。 注意&#xff1a; 方法名必须相同参数列表必须不同与返回值是否相同无关编译器在编译代码时&…

ComfyUI-unclip模型部署指南

一、介绍 unCLIP 模型是 SD 模型的版本&#xff0c;经过专门调整&#xff0c;除了文本提示之外&#xff0c;还可以接收图像概念作为输入。使用这些模型附带的 CLIPVision 对图像进行编码&#xff0c;然后在采样时将其提取的概念传递给主模型。 它并不是按照传统意义将图像混合…

优雅关闭:避免服务停机带来的业务损失

服务关闭有什么问题&#xff1f; 在“单体应用”复杂到一定程度后&#xff0c;一般会进行系统拆分&#xff0c;也就是微服务架构。服务拆分之后&#xff0c;就需要协同&#xff0c;于是RPC框架就出来了&#xff0c;用来解决各个子系统之间的通信问题。 拆分系统的目的&#x…

硬件知识 cadence16.6 原理图输出为pdf 网络名下划线偏移 (ORCAD)

1. cadence原理图输出为PDF网络名下划线偏移 生这种情况的原因 1. 设计的原理图图纸大小比正常的 A4图纸大。 2. 打印为PDF 的时候&#xff0c;打印机的设置有问题。 2.cadence原理图输出为 PDF网络名下划线偏移的情况 可以看到上图&#xff0c;网络名往上漂移。 3. 解决办法 …

Linux插件zsh(oh-my-zsh)

一、oh-my-zsh基本介绍 oh-my-zsh&#xff1a; https://github.com/ohmyzsh/ohmyzshhttps://github.com/ohmyzsh/ohmyzsh 注意&#xff1a;需要先安装zsh命令&#xff0c;才能安装oh-my-zsh&#xff0c;先测试是否安装了zsh rootserver:/opt # zsh --version zsh 5.8 (x86_6…

异或和之和

//暴力做法 枚举每个子区间 O(n^3) //优化1 利用前缀异或和快速求出区间异或和 O(n^2) //优化2 处理位运算的常用方法&#xff1a;拆位法 常用的思想&#xff1a;贡献法思想 下面详见优化2&#xff1a; 1.拆位贡献法 2.实战真题1 题目链接&#xff1a;1.异或和之和 - 蓝桥…

A039-基于SpringBoot的农产品销售系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

【大数据学习 | Spark】RDD的概念与Spark任务的执行流程

1. RDD的设计背景 在实际应用中&#xff0c;存在许多迭代式计算&#xff0c;这些应用场景的共同之处是&#xff0c;不同计算阶段之间会重用中间结果&#xff0c;即一个阶段的输出结果会作为下一个阶段的输入。但是&#xff0c;目前的MapReduce框架都是把中间结果写入到HDFS中&…

jmeter操作数据库

简介 Apache JMeter 是一个强大的开源工具&#xff0c;用于负载测试和性能测量。除了Web应用外&#xff0c;JMeter还可以用于测试各种数据库系统&#xff0c;包括MySQL。本文将详细介绍如何使用JMeter来测试MySQL数据库的性能。 环境准备 安装Java&#xff1a;确保你已经安装…

最小生成树——Kruskal、Prim算法

图的存储&#xff1a; 高阶数据结构——图 文章目录 目录 文章目录 一、kruskal算法 二、Prim算法 前言 连通图中的每一棵生成树&#xff0c;都是原图的一个极大无环子图&#xff0c;即&#xff1a;从其中删去任何一条边&#xff0c;生成树 就不在连通&#xff1b;反之&#xf…