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

Spring Boot中使用Sa-Token实现轻量级登录与鉴权

1. Sa-Token 介绍
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。

功能结构图

在这里插入图片描述

2. 登录认证
对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加一层接口校验:

如果校验通过,则:正常返回数据。
如果校验未通过,则:抛出异常,告知其需要先进行登录。
那么,判断会话是否登录的依据是什么?我们先来简单分析一下登录访问流程:

用户提交 name + password 参数,调用登录接口。
登录成功,返回这个用户的 Token 会话凭证。
用户后续的每次请求,都携带上这个 Token。
服务器根据 Token 判断此会话是否登录成功。
所谓登录认证,指的就是服务器校验账号密码,为用户颁发 Token 会话凭证的过程,这个Token 也是我们后续判断会话是否登录的关键所在。

在这里插入图片描述

2.1 登录与注销

// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object id);  

只此一句代码,便可以使会话登录成功,实际上,Sa-Token 在背后做了大量的工作,包括但不限于:

  • 检查此账号是否之前已有登录
  • 为账号生成 Token 凭证与 Session 会话
  • 通知全局侦听器,xx 账号登录成功
  • 将 Token 注入到请求上下文
  • 等等其它工作……

只需要记住关键一点:Sa-Token 为这个账号创建了一个Token凭证,且通过 Cookie 上下文返回给了前端。

此处仅仅做了会话登录,但并没有主动向前端返回 Token 信息。严格来讲是需要的,只不过 StpUtil.login(id) 方法利用了 Cookie 自动注入的特性,省略了你手写返回 Token 的代码。

// 当前会话注销登录
StpUtil.logout();// 获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.isLogin();// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();

2.2 会话查询

// 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.getLoginId();// 类似查询API还有:
StpUtil.getLoginIdAsString();    // 获取当前会话账号id, 并转化为`String`类型
StpUtil.getLoginIdAsInt();       // 获取当前会话账号id, 并转化为`int`类型
StpUtil.getLoginIdAsLong();      // 获取当前会话账号id, 并转化为`long`类型// ---------- 指定未登录情形下返回的默认值 ----------// 获取当前会话账号id, 如果未登录,则返回null 
StpUtil.getLoginIdDefaultNull();// 获取当前会话账号id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型)
StpUtil.getLoginId(T defaultValue);

2.3 Token 查询

// 获取当前会话的token值
StpUtil.getTokenValue();// 获取当前`StpLogic`的token名称
StpUtil.getTokenName();// 获取指定token对应的账号id,如果未登录,则返回 null
StpUtil.getLoginIdByToken(String tokenValue);// 获取当前会话剩余有效期(单位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();// 获取当前会话的token信息参数
StpUtil.getTokenInfo();

3. 权限认证
所谓权限认证,核心逻辑就是判断一个账号是否拥有指定权限:

  • 有,就让你通过。
  • 没有?那么禁止访问!

深入到底层数据中,就是每个账号都会拥有一个权限码集合,框架来校验这个集合中是否包含指定的权限码。

例如:当前账号拥有权限码集合

[“user-add”, “user-delete”, “user-get”],

这时候我来校验权限 “user-update”,则其结果就是:验证失败,禁止访问。
在这里插入图片描述

3.1 获取当前账号权限码集合
因为每个项目的需求不同,其权限设计也千变万化,因此 [ 获取当前账号权限码集合 ] 这一操作不可能内置到框架中, 所以 Sa-Token 将此操作以接口的方式暴露给你,以方便你根据自己的业务逻辑进行重写。

你需要做的就是新建一个类,实现 StpInterface接口,例如以下代码:

/*** 自定义权限验证接口扩展*/
@Component    // 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展 
public class StpInterfaceImpl implements StpInterface {/*** 返回一个账号所拥有的权限码集合 */@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限List<String> list = new ArrayList<String>();    list.add("101");list.add("user.add");list.add("user.update");list.add("user.get");// list.add("user.delete");list.add("art.*");return list;}/*** 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)*/@Overridepublic List<String> getRoleList(Object loginId, String loginType) {// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色List<String> list = new ArrayList<String>();    list.add("admin");list.add("super-admin");return list;}
}

参数解释:

  • loginId: 账号id,即你在调用 StpUtil.login(id) 时写入的标识值。
  • loginType: 账号体系标识,此处暂时忽略,可以详细了解 [ 多账户认证 ]

3.2 权限校验
然后就可以用以下api来鉴权了

// 获取:当前账号所拥有的权限集合
StpUtil.getPermissionList();// 判断:当前账号是否含有指定权限, 返回 true 或 false
StpUtil.hasPermission("user.add");        // 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException 
StpUtil.checkPermission("user.add");        // 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");        // 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");   

3.3 角色校验
在Sa-Token中,角色和权限可以独立验证

// 获取:当前账号所拥有的角色集合
StpUtil.getRoleList();// 判断:当前账号是否拥有指定角色, 返回 true 或 false
StpUtil.hasRole("super-admin");        // 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
StpUtil.checkRole("super-admin");        // 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
StpUtil.checkRoleAnd("super-admin", "shop-admin");        // 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] 
StpUtil.checkRoleOr("super-admin", "shop-admin");   

权限通配符
Sa-Token允许你根据通配符指定泛权限,例如当一个账号拥有art.*的权限时,art.add、art.delete、art.update都将匹配通过

上帝权限:当一个账号拥有 “*” 权限时,他可以验证通过任何权限码 (角色认证同理)

前端有了鉴权后端还需要鉴权吗?
需要!

前端的鉴权只是一个辅助功能,对于专业人员这些限制都是可以轻松绕过的,为保证服务器安全,无论前端是否进行了权限校验,后端接口都需要对会话请求再次进行权限校验!

  1. 前后台分离(无Cookie模式)
    1.何为无 Cookie 模式?

无 Cookie 模式:特指不支持 Cookie 功能的终端,通俗来讲就是我们常说的 —— 前后台分离模式。

后端将 token 返回到前端

首先调用 StpUtil.login(id) 进行登录。

调用 StpUtil.getTokenInfo() 返回当前会话的 token 详细参数。

此方法返回一个对象,其有两个关键属性:tokenName和tokenValue(token 的名称和 token 的值)。
将此对象传递到前台,让前端人员将这两个值保存到本地。
2.前端将 token 提交到后端

无论是app还是小程序,其传递方式都大同小异。

那就是,将 token 塞到请求header里 ,格式为:{tokenName: tokenValue}。

3.只要按照如此方法将token值传递到后端,Sa-Token 就能像传统PC端一样自动读取到 token 值,进行鉴权。

4.你可能会有疑问,难道我每个ajax都要写这么一坨?岂不是麻烦死了?

你当然不能每个 ajax 都写这么一坨,因为这种重复性代码都是要封装在一个函数里统一调用的。

  1. Sa-Token 集成 Redis
    Sa-Token 默认将数据保存在内存中,此模式读写速度最快,且避免了序列化与反序列化带来的性能消耗,但是此模式也有一些缺点,比如:

重启后数据会丢失。
无法在分布式环境中共享数据。
为此,Sa-Token 提供了扩展接口,你可以轻松将会话数据存储在 Redis、Memcached等专业的缓存中间件中, 做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性。

以下是官方提供的 Redis 集成包:

<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-dao-redis-jackson</artifactId><version>1.34.0</version>
</dependency>

集成 Redis 请注意:

1.无论使用哪种序列化方式,你都必须为项目提供一个 Redis 实例化方案,例如:

<!-- 提供Redis连接池 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

2.配置Redis 连接信息

spring: # redis配置 redis:# Redis数据库索引(默认为0)database: 0# Redis服务器地址host: 127.0.0.1# Redis服务器连接端口port: 6379# Redis服务器连接密码(默认为空)# password: # 连接超时时间timeout: 10slettuce:pool:# 连接池最大连接数max-active: 200# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms# 连接池中的最大空闲连接max-idle: 10# 连接池中的最小空闲连接min-idle: 0

3.集成 Redis 后,是我额外手动保存数据,还是框架自动保存?

框架自动保存。集成 Redis 只需要引入对应的 pom依赖 即可,框架所有上层 API 保持不变。

4.集成包版本问题

Sa-Token-Redis 集成包的版本尽量与 Sa-Token-Starter 集成包的版本一致,否则可能出现兼容性问题。

  1. SpringBoot 集成 Sa-Token
    以最新版本 1.34.0为例

6.1 创建项目
在 IDE 中新建一个 SpringBoot 项目,例如:sa-token-demo-springboot

6.2 添加依赖
在项目中添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.34.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-dao-redis-jackson</artifactId><version>1.34.0</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>

6.3 设置配置文件

server:# 端口port: 8081############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:# token名称 (同时也是cookie名称)token-name: satoken# token有效期,单位s 默认30, -1代表永不过期timeout: 2592000# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒activity-timeout: -1# 是否允许同一账号并发登录 (true时允许一起登录,false时新登录挤掉旧登录)is-concurrent: true# 在多人登录同一账号时,是否共用一个token (true时所有登录共用一个token,false时每次登录新建一个token)is-share: true# token风格token-style: uuid# 是否输出操作日志is-log: falsespring:# redis配置redis:# Redis数据库索引(默认为0)database: 0# Redis服务器地址host: 127.0.0.1# Redis服务器连接端口port: 6379# Redis服务器连接密码(默认为空)password: abc123# 连接超时时间timeout: 10slettuce:pool:# 连接池最大连接数max-active: 200# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms# 连接池中的最大空闲连接max-idle: 10# 连接池中的最小空闲连接min-idle: 0

6.4 创建启动类

@SpringBootApplication
public class SaTokenDemoApplication {public static void main(String[] args) throws JsonProcessingException {SpringApplication.run(SaTokenDemoApplication.class, args);System.out.println("启动成功:Sa-Token配置如下:" + SaManager.getConfig());}
}

6.5 定义用户信息类

@Data
@Accessors(chain = true)
@AllArgsConstructor
public class UserInfo {private Long userId;private String name;private String email;
}

6.6 自定义权限验证接口扩展

@Component    // 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {/*** 返回一个账号所拥有的权限码集合*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
//        List<String> list = new ArrayList<String>();
//        list.add("user.add");
//        list.add("user.get");// list.add("user.delete");
//        list.add("art.*");//从redis中获取权限List<String> authList = (List<String>) StpUtil.getSession().get("authList");return authList;}/*** 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)* @param loginId 账号id,即你在调用 StpUtil.login(id) 时写入的标识值* @param loginType 账号体系标识* @author: yh* @date: 2023/2/12*/@Overridepublic List<String> getRoleList(Object loginId, String loginType) {// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色List<String> list = new ArrayList<String>();list.add("admin");list.add("super-admin");return list;}
}

6.7 创建测试Controller

@RestController
@RequestMapping("/user")
public class UserController {/*** 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456*/@RequestMapping("/doLogin")public SaResult doLogin(String username, String password) {// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 if("zhang".equals(username) && "123456".equals(password)) {// 第1步,先登录上StpUtil.login(10001);}//第2步,加载用户信息和权限信息StpUtil.getSession().set("loginInfo", new UserInfo(10001L, "张三", "123123@163.com"));//加载权限,只给user.add的权限List<String> authList = new ArrayList<String>();authList.add("user.add");StpUtil.getSession().set("authList", authList);// 第3步,获取 Token  相关参数SaTokenInfo tokenInfo = StpUtil.getTokenInfo();// 第4步,返回给前端return SaResult.data(tokenInfo);}/*** 查询登录状态*/@RequestMapping("/isLogin")public String isLogin() {return "当前会话是否登录:" + StpUtil.isLogin();}/*** 获取用户信息*/@RequestMapping("/getUserInfo")public UserInfo getUserInfo() {UserInfo loginInfo = (UserInfo) StpUtil.getSession().get("loginInfo");return loginInfo;}/*** 测试方法校验权限*/@GetMapping(value = "/add")public String add(){StpUtil.checkPermission("user.add");return "ok";}@GetMapping(value = "/update")public String update(){StpUtil.checkPermission("user.update");return "ok";}
}

6.8 运行
启动项目,Copy Configuration再启动一个实例,这时候我们就同时启动了两个实例

在这里插入图片描述

在这里插入图片描述

这样就可以测试出在集群部署情况下登录信息会不会有问题,然后用接口测试工具依次访问上述测试接口

1、 登录

http://localhost:8081/user/doLogin?username=zhang&password=123456

在这里插入图片描述

此时查看redis中数据,可以看到登录信息和权限都保存在redis中了
在这里插入图片描述

2、 查询登录状态

http://localhost:8081/user/isLogin

注意header里没有cookie
在这里插入图片描述

把登录接口返回的tokenName和tokenValue加入到请求header中
在这里插入图片描述

只有请求携带对应的token,登录状态才为:true

3、 获取用户信息
这里调用端口为8082的实例

http://localhost:8082/user/getUserInfo
在这里插入图片描述

4、 调用添加用户接口
测试是否有添加接口权限,登录的时候我们赋予了添加用户的权限

http://localhost:8082/user/add

在这里插入图片描述

5、 调用更新用接口
测试是否有更新用户接口权限,登录的时候我们没有赋予更新用户的权限

http://localhost:8082/user/update

在这里插入图片描述

在这里插入图片描述

结果可以看到没有更新用户接口的权限。

相关文章:

Spring Boot中使用Sa-Token实现轻量级登录与鉴权

1. Sa-Token 介绍 Sa-Token 是一个轻量级 Java 权限认证框架&#xff0c;主要解决&#xff1a;登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。 功能结构图 2. 登录认证 对于一些登录之后才能访问的接口&#xff08;例如&…...

《分布式技术原理与算法解析》学习笔记Day20

CAP理论 什么是CAP理论&#xff1f; CAP理论用来指导分布式系统设计&#xff0c;以保证系统的可用性、数据一致性等。 C&#xff0c;Consistency&#xff0c;一致性&#xff0c;指所有节点在同一时刻的数据是相同的&#xff0c;即更新操作执行结束并响应用户完成后&#xff…...

【2023-2-23】FastDeploy 安装教程

【2023-2-22】FastDeploy 安装编译教程 该测试 FastDeploy CPU版本。 1. fastDeploy库编译 1.1 官方预编译库下载 预编译库下载安装 1.2 自定义CPU版本库编译 官方编译FastDeploy教程 CMakeGUI VS 2019 IDE编译FastDeploy 本人编译教程 CMAKE_CONFIGURATION_TYPES 属性设…...

rollup.js 一个简单实用的打包工具

最近在看vue3相关的知识的时候&#xff0c;发现了一个新的打包工具&#xff0c;至少于我而言是新鲜的。它就是rollup.js。一说到JS打包、合并、压缩、模块处理等都会想到webpack&#xff0c;这是王者&#xff0c;当然入门的难度偏高。而vue3中搭配的vite运行速度确实非常快&…...

数据结构与算法之最小爬楼梯费用动态规划

继续上一道题目&#xff0c;在上一道题目的基础之上&#xff0c;我们来解决这一道爬楼梯最小费用题。一.题目描述二.思路(动态规划五部曲)确定dp数组以及下标的含义使用动态规划&#xff0c;就要有一个数组来记录状态&#xff0c;本题只需要一个一维数组dp[i]就可以了。dp[i]的…...

阿里云ACA认证如何获取?

获取阿里云ACA&#xff08;Alibaba Cloud Certification Associate&#xff09;认证&#xff0c;需要按照以下步骤进行操作&#xff1a; 注册阿里云账号。如果您还没有阿里云账号&#xff0c;请先注册一个账号。登录阿里云官网。登录后&#xff0c;进入阿里云认证中心。选择AC…...

【Python入门第十六天】Python If ... Else

Python 条件和 If 语句 Python 支持来自数学的常用逻辑条件&#xff1a; 等于&#xff1a;a b不等于&#xff1a;a ! b小于&#xff1a;a < b小于等于&#xff1a;a < b大于&#xff1a;a > b大于等于&#xff1a;a > b 这些条件能够以多种方式使用&#xff0c…...

两数之和的解法

给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案…...

领导催我优化SQL语句,我求助了ChatGPT。这是ChatGPT给出的建议,你们觉得靠谱吗

作为一个程序员&#xff0c;无论在面试还是工作中&#xff0c;优化SQL都是绕不过去的难题。 为啥&#xff1f;工作之后才会明白&#xff0c;随着公司的业务量增多&#xff0c;SQL的执行效率对程系统运行效率的影响逐渐增大&#xff0c;相对于改造代码&#xff0c;优化SQL语句是…...

ArcGIS手动分割矢量面要素从而划分为多个面部分的方式:Cut Polygons Tool

本文介绍在ArcGIS下属ArcMap软件中&#xff0c;通过“Cut Polygons Tool”工具&#xff0c;对一个面要素矢量图层加以手动分割&#xff0c;从而将其划分为指定形状的多个部分的方法。 对于一个面要素矢量文件&#xff0c;有时我们需要对其加以划分&#xff0c;通过手动勾勒新的…...

【LeetCode】剑指 Offer 13. 机器人的运动范围 p92 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/ 1. 题目介绍&#xff08;13. 机器人的运动范围&#xff09; 地上有一个m行n列的方格&#xff0c;从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动&#xff0…...

[oeasy]python0091_仙童公司_八叛逆_intel_8080_altair8800_牛郎星

编码进化 个人电脑 计算机 通过电话网络 进行连接 极客 利用技术 做一些有趣的尝试 极客文化 是 认真研究技术的 文化 计算机 不再是 高校和研究机构高墙里面的 神秘事物而是 生活中常见的 家用电器 ibm 蓝色巨人脚步沉重 dec 小型机不断蚕食低端市场甚至组成网络干掉大型机…...

crontab 执行脚本报错,手动执行脚本正常的解决方法

一、出现的问题 有一个守护脚本XXX.sh&#xff0c;需要使用oracle用户在linux上配置定时任务&#xff0c;每1分钟检查执行一次。但是发现该脚本使用oralce用户手动启动没问题&#xff0c;能正常把程序启动起来&#xff0c;而使用crontab并没有把程序启动起来。 二、排查分析问…...

扎心话题 | 设计院背后的潜规则你知道吗?

大家好&#xff0c;我是建模助手。 大家都知道&#xff0c;在过去的2022年经济是真难&#xff01;以小编所在的广东为例&#xff0c;全年GDP增长仅1.9%。 这个数据足以呈现一个社会现象——不仅消费力咔咔下降&#xff0c;各行各业更有不同程度地嗝屁。这其中也包括一些设计院…...

【JavaEE初阶】第二节.多线程( 进阶篇 ) 锁的优化、JUC的常用类、线程安全的集合类

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、synchronized的优化操作 1.1 锁膨胀/锁升级 1.2 锁消除 1.3 锁粗化二、JUC 2.1 Callable接口 2.2 ReentrantLock类&…...

大数据核心技术是什么

大数据的核心层&#xff1a;数据采集层、数据存储与分析层、数据共享层、数据应用层&#xff0c;可能叫法有所不同本质上的角色都大同小异。 大数据的核心技术都包括什么&#xff1f; 1、数据采集 数据采集的任务就是把数据从各种数据源中采集和存储到数据存储上&#xff0c…...

「TCG 规范解读」初识 TPM 2.0 库续一

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…...

task与function

task和function主要是有助于代码的可重用性&#xff0c;都可以在module-endmodule之外声明。 1.function 1.1.function逻辑的综合 function&#xff1a;一个只有1个wire型输出值、全是组合逻辑的函数&#xff0c;且函数名即输出信号名&#xff0c;小括号中按顺序例化输入信号。…...

Android 基础知识4-3.1 TextView(文本框)详解

一、前言 TextView就是一个显示文本标签的控件&#xff0c;就是用来显示文本。可以在代码或者 XML中设置字体&#xff0c;字体大小&#xff0c;字体颜色 &#xff0c;字体样式 &#xff08;加粗级斜体&#xff09;&#xff0c;文字截断&#xff08;比如&#xff1a;只显示10个字…...

点击化学 PEG 试剂1858242-47-3,Propargyl丙炔基-PEG1-乙酸活性酯

Propargyl-PEG1-Acetic acid-NHS ester&#xff0c;丙炔基-聚乙二醇-乙酸琥珀酰亚胺酯&#xff0c;丙炔基-PEG1-乙酸活性酯&#xff0c;丙炔基-PEG1-乙酸-NHS 酯产品规格&#xff1a;1.CAS号&#xff1a;1858242-47-32.分子式&#xff1a;C9H9NO53.分子量&#xff1a;211.174.包…...

正则表达式是如何运作的?

在日常的开发工作当中&#xff0c;我们必不可免的会碰到需要使用正则的情况。 正则在很多时候通过不同的组合方式最后都可以达到既定的目标结果。比如我们有一个需要匹配的字符串&#xff1a; hello&#xff0c;我们可以通过 / .</p>/ 以及 / .?</p>/ 来匹配&…...

JVM参数GC线程数ParallelGCThreads设置

1. ParallelGCThreads参数含义JVM垃圾回收(GC)算法的两个优化标的&#xff1a;吞吐量和停顿时长。JVM会使用特定的GC收集线程&#xff0c;当GC开始的时候&#xff0c;GC线程会和业务线程抢占CPU时间&#xff0c;吞吐量定义为CPU用于业务线程的时间与CPU总消耗时间的比值。为了承…...

java 线程的那些事

什么是进程&#xff1a; 你把它理解成一个软件 什么是线程&#xff1a; 你把它理解成软件里面的一个功能&#xff0c;做的事情 什么是多线程&#xff1a; 你把它理解成 软件里面的某一个功能&#xff0c;原先是一个人累死累活的在那里完成&#xff0c;现在好了&#xff0c;多…...

如何利用 Python 进行客户分群分析(附源码)

每个电子商务数据分析师必须掌握的一项数据聚类技能 如果你是一名在电子商务公司工作的数据分析师&#xff0c;从客户数据中挖掘潜在价值&#xff0c;来提高客户留存率很可能就是你的工作任务之一。 然而&#xff0c;客户数据是巨大的&#xff0c;每个客户的行为都不一样。20…...

D1s RDC2022纪念版开发板开箱评测及点屏教程

作者new_bee 本文转自&#xff1a;https://bbs.aw-ol.com/topic/3005/ 目录 芯片介绍开发板介绍RT-Smart用户态系统编译使用感想引用 1. 芯片介绍 RISC-V架构由于其精简和开源的特性&#xff0c;得到业界的认可&#xff0c;近几年可谓相当热门。操作系统方面有RT-Thread&am…...

了解一下TCP/IP协议族

在《简单说说OSI网络七层模型》中讲到&#xff0c;目前实际使用的网络模型是 TCP/IP 模型&#xff0c;它对 OSI 模型进行了简化&#xff0c;只包含了四层&#xff0c;从上到下分别是应用层、传输层、网络层和链路层&#xff08;网络接口层&#xff09;&#xff0c;每一层都包含…...

【第十九部分】存储过程与存储函数

【第十九部分】存储过程与存储函数 文章目录【第十九部分】存储过程与存储函数19. 存储过程与存储函数19.1 存储过程19.2 创建、调用存储过程19.2.1 不带参数19.2.2 IN 类型19.2.3 OUT类型19.2.4 IN和OUT类型同时使用19.2.5 INOUT类型19.3 存储函数19.4 创建、调用存储函数19.5…...

字节序

字节序 字节序&#xff1a;字节在内存中存储的顺序。 小端字节序&#xff1a;数据的高位字节存储在内存的高位地址&#xff0c;低位字节存储在内存的低位地址 大端字节序&#xff1a;数据的低位字节存储在内存的高位地址&#xff0c;高位字节存储在内存的低位地址 bit ( 比特…...

PDF文件怎么转图片格式?转换有技巧

PDF文件有时为了更美观或者更直观的展现出效果&#xff0c;我们会把它转成图片格式&#xff0c;这样不论是归档总结还是存储起来都会更为高效。有没有合适的转换方法呢&#xff1f;这就来给你们罗列几种我个人用过体验还算不错的方式&#xff0c;大家可以拿来参考一下哈。1.用电…...

筑基七层 —— 数据在内存中的存储?拿来吧你

目录 零&#xff1a;移步 一.修炼必备 二.问题思考 三.整型在内存中的存储 三.大端字节序和小端字节序 四.浮点数在内存中的存储 零&#xff1a;移步 CSDN由于我的排版不怎么好看&#xff0c;我的有道云笔记相当的美观&#xff0c;请移步至有道云笔记 一.修炼必备 1.入门…...