Spring Security——结合JWT实现令牌的验证与授权
目录
JWT(JSON Web Token)
项目总结
新建一个SpringBoot项目
pom.xml
PayloadDto
JwtUtil工具类
MyAuthenticationSuccessHandler(验证成功处理器)
JwtAuthenticationFilter(自定义token过滤器)
WebSecurityConfig配置类
WebSecurityController控制器
index.html
项目测试
- 本文以Spring Security项目为基础,此处只给出有关JWT的核心代码,其他代码可查看文章:http://t.csdnimg.cn/hLyKB
- 参考文章:JWT 基础概念详解 | JavaGuide
JWT(JSON Web Token)
- 基于token的认证方式相较于Session认证方式的好处:
- 可以节省服务器端的开销,因为每个认证用户占一个Session对象是需要消耗服务器内存资源的,而token是在每次请求时传递给服务器端的
- 当服务器端做集群部署时,基于token的认证方式也更容易扩展
- 无须考虑CSRF,由于不同依赖cookie,所以采用token认证方式不会发生CSRF
- 更适合于移动端,当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会更简单
- JWT就是token的一种实现方式
- JWT可以用于认证和信息交换,流程:
- 在Web应用程序中,当用户成功登录后,服务器可以生成一个JWT并将其发送回客户端
- 客户端存储该令牌,并在后续每次请求服务器接口时,都在请求报头中携带JWT
- 服务器校验JWT签名,得到用户信息,如果验证通过,则根据授权规则返回前端请求的数据
- JWT是一种用于在网络上安全传输信息的开放标准(RFC 7519),通过使用数字签名(或加密)来验证消息的发送者以及确保消息在传输过程中没有被篡改
- JWT由三部分组成(用点号(.)分隔开)
- Header(头部):是一个描述JWT元数据的JSON对象
- 令牌的类型(JWT)
- 正在使用的签名算法(如HMAC SHA256或者RSA)
- Payload(载荷):是一个JSON对象,存放有效信息
- 三种声明类型,关于用户和附加数据的陈述
- 1、registered
- iss(issuer):签发人
- sub(subject):主题
- aud(audience):受众
- exp(expiration time):过期时间
- nbf(not before):生效时间
- iat(issued at):签发时间
- jti(jwt id):JWT的唯一身份标识
- 2、public
- 3、private
- Signature(签名):对头部和载荷采用单向散列算法生成一个哈希码,以确保数据不会被篡改。签名用于验证消息在此过程中是否被更改,并且对于使用私钥签名的令牌还可以验证JWT的发送者是不是它所声称的
- JWT=Header.Payload.Signature
- 优点:
- 简洁:由于其信息是以JSON对象的形式存储的,因此JWT是非常紧凑的
- 自包含:JWT包含了所有必要的信息,因此不需要查询数据库来验证令牌
- 可扩展性:由于其灵活的结构,JWT可以包含任意的额外信息
- 缺点:
- 信息是以Base64编码的形式存储的,所以不适合存储敏感信息,例如密码
- 使用时要小心JWT被盗用或篡改
- 应用场景:
- 授权:一旦用户登录,每个后续请求就都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录(Single Sign On)是目前被广泛使用JWT的一个功能,因为它开销小,并且能够轻松地跨不同的域
- 信息交换:可以在各方之间安全传输信息。因为可以对JWT进行签名(例如使用公钥或私钥对),所以可以确定发送者是特定的人。此外,由于签名是使用标头和有效载荷计算的,因此还可以验证内容是否被篡改
项目总结
工作流程:
1、用户注册和登录:
- 用户通过注册接口提交注册信息,系统保存用户信息到数据库。
- 用户通过登录接口提交用户名和密码,系统验证用户信息,成功后生成并返回JWT令牌。
2、请求保护资源:
- 客户端在每次请求时将JWT令牌放在请求头中。
JwtAuthenticationFilter拦截请求,从请求头中提取JWT令牌,并通过getAuthentication(token)方法验证和解析令牌。- 若验证成功,将
UsernamePasswordAuthenticationToken设置到SecurityContextHolder中,使当前线程拥有安全上下文。- 后续处理链和控制器可以通过安全上下文获取用户信息进行权限验证。
3、安全考量:
- JWT加密:使用HMAC算法对JWT进行签名,确保令牌的完整性和真实性。
- 过期时间:设置JWT的有效期,减小令牌被盗用的风险。
- 角色和权限:通过角色和权限控制资源访问,确保不同用户只能访问其权限范围内的资源。
新建一个SpringBoot项目
项目的结构

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.12.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.study</groupId><artifactId>jwt</artifactId><version>0.0.1-SNAPSHOT</version><name>jwt</name><description>Demo project for Spring Boot</description><properties><java.version>8</java.version></properties><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--hutool是一个Java工具类库--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.5.7</version></dependency><!--jwt相关依赖--><dependency><groupId>com.nimbusds</groupId><artifactId>nimbus-jose-jwt</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
PayloadDto
package com.study.jwt.dto;import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;import java.util.List;/*** 用于封装JWT中的有效载荷*/
@Data
//这个注解单独应用时会生成 equals 和 hashCode 方法,callSuper = false 表示生成的方法不会调用父类的 equals 和 hashCode 方法
@EqualsAndHashCode(callSuper = false)
//用于生成 Builder 模式相关的代码。它会生成一个静态内部类 ExampleBuilder,使得创建对象更加简洁和灵活
@Builder
public class PayloadDto {private String sub;//主题private Long iat;//签发时间private Long exp;//过期时间private String jti;//JWT的IDprivate String username;//用户名称private List<String> authorities;//用户拥有的权限
}
JwtUtil工具类
package com.study.jwt.util;import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.study.jwt.dto.PayloadDto;import java.text.ParseException;
import java.util.Date;/*** 工具类,用于提供生成和验证JWT的方法*/
public class JwtUtil {//默认密钥public static final String DEFAULT_SECRET="mySecret";/*** 使用HMAC SHA-256签名算法* @param payloadStr 有效载荷* @param secret 密钥* @return JWS串*/public static String generateTokenByHMAC(String payloadStr,String secret) throws JOSEException {//创建JWS头,设置签名算法和类型JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build();//将载荷信息封装到Payload中Payload payload = new Payload(payloadStr);//创建JWS对象JWSObject jwsObject = new JWSObject(jwsHeader, payload);//创建HMAC签名器JWSSigner jwsSigner = new MACSigner(secret);//签名jwsObject.sign(jwsSigner);return jwsObject.serialize();}/*** 验证签名,提取有效载荷,以PayloadDto对象形式返回* @param token JWS串* @param secret 密钥* @return PayloadDto对象*/public static PayloadDto verifyTokenByHMAC(String token,String secret) throws ParseException, JOSEException {//从token中解析JWS对象JWSObject jwsObject = JWSObject.parse(token);//创建HMAC验证器JWSVerifier jwsVerifier = new MACVerifier(secret);if (!jwsObject.verify(jwsVerifier)) {throw new JOSEException("token签名不合法!");}String payload = jwsObject.getPayload().toString();PayloadDto payloadDto = JSONUtil.toBean(payload, PayloadDto.class);if (payloadDto.getExp() < new Date().getTime()){throw new JOSEException("token已过期!");}return payloadDto;}
}
MyAuthenticationSuccessHandler(验证成功处理器)
package com.study.jwt.config.handler;import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.JOSEException;
import com.study.jwt.dto.PayloadDto;
import com.study.jwt.util.JwtUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;/*** 修改验证成功处理器,用户成功登录后,在响应报头中发送token*/
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {// 获取认证主体信息Object principal = authentication.getPrincipal();// 用户登录成功后,在响应头中发送 tokenif(principal instanceof UserDetails){// 如果主体信息是用户详情实例UserDetails user=(UserDetails)principal;// 获取用户的权限信息Collection<? extends GrantedAuthority> authorities=authentication.getAuthorities();List<String> authoritiesList = new ArrayList<>(authorities.size());// 将用户权限列表转换为字符串列表authorities.forEach(authority -> {authoritiesList.add(authority.getAuthority());});// 定义 token 的创建时间和过期时间Date now = new Date();DateTime exp = DateUtil.offsetSecond(now, 60 * 60); // 1小时后过期// 创建 payload 数据传输对象PayloadDto payloadDto=PayloadDto.builder().sub(user.getUsername()).iat(now.getTime()).exp(exp.getTime()).jti(UUID.randomUUID().toString()).username(user.getUsername()).authorities(authoritiesList).build();String token=null;try {// 生成 JWT tokentoken= JwtUtil.generateTokenByHMAC(//nimbus-jose-jwt所使用的HMAC SHA256算法所需密钥长度为256位(32字节),因此先用md5加密JSONUtil.toJsonStr(payloadDto),SecureUtil.md5(JwtUtil.DEFAULT_SECRET));// 设置响应头和响应内容类型response.setHeader("Authorization",token);response.setContentType("application/json;charset=UTF-8");PrintWriter out = response.getWriter();out.write("登录成功!");out.close();} catch (JOSEException e) {e.printStackTrace();}}}
}
JwtAuthenticationFilter(自定义token过滤器)
package com.study.jwt.filter;import cn.hutool.crypto.SecureUtil;
import com.nimbusds.jose.JOSEException;
import com.study.jwt.dto.PayloadDto;
import com.study.jwt.util.JwtUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;/*** JwtAuthenticationFilter是一个过滤器,拦截用户请求,验证token* 继承OncePerRequestFilter,保证在任何servlet容器上每次请求调度都能执行一次。*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {/*** 过滤器逻辑,提取和验证JWT,如果有效则设置认证信息。** @param request HttpServletRequest对象* @param response HttpServletResponse对象* @param filterChain FilterChain对象*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("Authorization"); // 从请求头中获取JWTif (token == null) {// 如果请求头中没有token,则直接放行filterChain.doFilter(request, response);return;}// 如果请求头中有token,则进行解析,并且设置认证信息try {// 验证并解析token,并将认证信息设置到SecurityContext中//它将经过认证的用户信息设置到当前的安全上下文中 (SecurityContext),从而使得后续的安全决策可以基于该用户的信息进行SecurityContextHolder.getContext().setAuthentication(getAuthentication(token));filterChain.doFilter(request, response); // 继续执行过滤链} catch (ParseException | JOSEException e) {e.printStackTrace(); // 打印异常信息}}/*** 验证token,并解析token,返回以用户名和密码所表示的经过身份验证的主体的令牌。** @param token JWT字符串* @return UsernamePasswordAuthenticationToken对象* @throws ParseException JSON解析异常* @throws JOSEException JWT处理异常*/private UsernamePasswordAuthenticationToken getAuthentication(String token) throws ParseException, JOSEException {// 使用HMAC算法验证并解析JWT,获取PayloadDto对象PayloadDto payloadDto = JwtUtil.verifyTokenByHMAC(token, SecureUtil.md5(JwtUtil.DEFAULT_SECRET));String username = payloadDto.getUsername(); // 从Payload中获取用户名List<String> roles = payloadDto.getAuthorities(); // 从Payload中获取角色列表Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();// 将角色列表转换为Spring Security的SimpleGrantedAuthority集合roles.forEach(role -> authorities.add(new SimpleGrantedAuthority(role)));if (username != null) {return new UsernamePasswordAuthenticationToken(username, null, authorities);}return null; // 如果用户名为空,返回null}
}
WebSecurityConfig配置类
package com.study.jwt.config;import com.study.jwt.filter.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@EnableWebSecurity // 启用Spring Security的Web安全支持
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate AuthenticationSuccessHandler authenticationSuccessHandler; // 成功处理器注入@Autowiredprivate AuthenticationFailureHandler authenticationFailureHandler; // 失败处理器注入@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;/*** 配置HTTP安全** @param http HttpSecurity对象* @throws Exception 异常处理*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/", "/home", "/login").permitAll() // 允许所有人访问.antMatchers("/admin/**").hasRole("ADMIN") // 只有具有 "ADMIN" 角色的用户才能访问以 "/admin/" 开头的路径.anyRequest().hasRole("USER") // 其他请求必须有USER角色.and().formLogin().loginPage("/login") // 指定登录页面.successHandler(authenticationSuccessHandler) // 登录成功处理器.failureHandler(authenticationFailureHandler) // 登录失败处理器.and().logout() // 启用注销功能.and()// 添加自定义的 JWT 过滤器.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);http.csrf().disable(); // 禁用CSRF保护}/*** 配置密码编码器*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(); // 使用BCrypt加密密码}
}
WebSecurityController控制器
package com.study.jwt.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;/*** 1.将configure(HttpSecurity http)方法中设置的不同的URL映射到不同的页面* 2.方法返回的是视图名称,需要视图解析器将视图名称解析成实际的HTML文件* 然后访问url就可以跳转到HTML页面了,否则返回的只是一个字符串* 3.在application.properties配置文件中配置视图解析器,springboot已经默认配置好了,你不用写了*/
@Controller
public class WebSecurityController {/*** 登录后跳转到home.html页面*/@GetMapping("/home")public String home(){return "home";}/*** 登录页面*/@GetMapping("/login")public String login(){return "login";//login.html}/*** 当访问/resource时,会重定向到/login,登录后才可以访问受保护的页面resource.html*/@GetMapping("/resource")public String resource(){return "resource";//resource.html}@GetMapping("/admin/index")public String index(){return "index";//index.html}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>index</title>
</head>
<body>
这是网站的首页!
</body>
</html>
项目测试
1、访问网址:http://localhost:8080/login
下面的Headers里的Authorization里的Value即为token,它被点号(.)分成三个部分,第一部分为Header,第二部分为Payload,第三部分为Signature
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiIsIlJPTEVfQURNSU4iXSwiZXhwIjoxNzE4NDM4NDY1NDM3LCJpYXQiOjE3MTg0MzQ4NjU0MzcsImp0aSI6ImQyNDFmMDlkLTgwNzYtNDZhZC1hYTg0LTc3MjQxNTIyZjUyZSIsInVzZXJuYW1lIjoiYWRtaW4ifQ.izNsanv4m-UJiIH23m8A2FvqWIvMfy0AEbleanxnMt8

2、访问网址:http://localhost:8080/resource
携带token进行后续访问:把登录时的token复制到Headers里的新增的Authorization里,访问受保护资源

3、访问网址:http://localhost:8080/admin/index
该地址需要角色为“ADMIN”,所以需要使用第二个账号"admin"登录
如果不用特定账号登录,则返回403错误

相关文章:
Spring Security——结合JWT实现令牌的验证与授权
目录 JWT(JSON Web Token) 项目总结 新建一个SpringBoot项目 pom.xml PayloadDto JwtUtil工具类 MyAuthenticationSuccessHandler(验证成功处理器) JwtAuthenticationFilter(自定义token过滤器) W…...
Vector的底层结构剖析
vector的介绍: 1.Vector实现了List接口的集合。 2.Vector的底层也是一个数组,protected Object[] elementData; 3.Vector 是线程同步的,即线程安全,Vector类的操作方法带有Synchronized. 4.在开发中,需要线程同步时࿰…...
实现抖音视频滑动功能vue3+swiper
首先,你需要安装和引入Swiper库。可以使用npm或者yarn进行安装。 pnpm install swiper然后在Vue组件中引入Swiper库和样式。 // 导入Swiper组件和SwiperSlide组件,用于创建轮播图 import {Swiper, SwiperSlide } from swiper/vue; // 导入Swiper的CSS样式,确保轮播图的正确…...
Linux文件系统【真的很详细】
目录 一.认识磁盘 1.1磁盘的物理结构 1.2磁盘的存储结构 1.3磁盘的逻辑存储结构 二.理解文件系统 2.1如何管理磁盘 2.2如何在磁盘中找到文件 2.3关于文件名 哈喽,大家好。今天我们学习文件系统,我们之前在Linux基础IO中研究的是进程和被打开文件…...
JAVA学习笔记DAY5——Spring_Ioc
文章目录 Bean配置注解方式配置注解配置文件调用组件 注解方法作用域 DI注入注解引用类型自动装配文件结构自动装配实现 基本数据类型DI装配 Bean配置 注解方式配置 类上添加Ioc注解配置文件中告诉SpringIoc容器要检查哪些包 注解仅是一个标记 注解 不同注解仅是为了方便开…...
WPF中的隧道路由和冒泡路由事件
文章目录 简介:一、事件最基本的用法二、理解路由事件 简介: WPF中使用路由事件升级了传统应用开发中的事件,在WPF中使用路由事件能更好的处理事件相关的逻辑,我们从这篇开始整理事件的用法和什么是直接路由,什么是冒…...
ISO七层模型 tcp/ip
OSI七层模型(重点例子) OSI(Open Systems Interconnection)模型,也称为开放系统互连模型,是一个理论模型,由国际标准化组织(ISO)制定,用于描述和理解不同网络…...
MySQL的三种重要的日志
日志 Mysql有三大日志系统 Undo Log(回滚日志):记录修改前的数据,用于事务回滚和 MVCC(多版本并发控制)。 Redo Log(重做日志):记录数据变更,用于崩溃恢复&…...
神经网络学习2
张量(Tensor)是深度学习和科学计算中的基本数据结构,用于表示多维数组。张量可以看作是一个更广义的概念,涵盖了标量、向量、矩阵以及更高维度的数据结构。具体来说,张量的维度可以是以下几种形式: 标量&am…...
Spring Boot整合Redis通过Zset数据类型+定时任务实现延迟队列
😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…...
Android入门第69天-AndroidStudio中的Gradle使用国内镜像最强教程
背景 AndroidStudio默认连接的是dl.google的gadle仓库。 每次重新build时: 下载速度慢;等待了半天总时build faild;build到一半connection timeout;即使使用了魔法也难以一次build好;这严重影响了我们的学习、开发效率。 当前网络上的使用国内镜像的教程不全 网上的教程…...
深入浅出 Qt 中 QListView 的设计思想,并掌握大规模、高性能列表的实现方法
在大规模列表控件的显示需求中,必须解决2个问题才能获得较好的性能: 第一就是数据存在哪里, 避免出现数据的副本。第二就是如何展示Item,如何复用或避免创建大量的Item控件。 在QListView体系里,QAbstractListModel解…...
课设--学生成绩管理系统
欢迎来到 Papicatch的博客 文章目录 🍉技术核心 🍉引言 🍈标识 🍈背景 🍈项目概述 🍈 文档概述 🍉可行性分析的前提 🍈项目的要求 🍈项目的目标 🍈…...
MySQL性能分析
一、查看执行频率 sql执行频率,执行下述指令可以看到select,update,delete等操作的次数 show global status like Com_______; 具体我们在终端登录mysql看下,使用下述命令登录mysql,并输入命令 mysql -u 用户名 -p 上述查询,删…...
为什么要学习Flink系统管理及优化课程?
Flink系统是一种流式处理框架,能够高效地处理大规模数据流。然而,要确保Flink系统的正常运行,就需要进行系统管理和优化。系统管理是指对Flink集群的监控、调度和维护,而系统优化则是指通过调整参数和优化算法,提高Fli…...
把本机的bash构建到docker镜像里面
最近突发奇想,想把本机的bash放到docker镜像里面,接下来看操作。 获取bash以及依赖 [rootbogon ~]# cat get_lib_info.sh #!/bin/bash# 函数:显示帮助信息 show_help() {echo "Usage: $(basename "$0") -h -f <file>…...
【数据分析】推断统计学及Python实现
各位大佬好 ,这里是阿川的博客,祝您变得更强 个人主页:在线OJ的阿川 大佬的支持和鼓励,将是我成长路上最大的动力 阿川水平有限,如有错误,欢迎大佬指正 Python 初阶 Python–语言基础与由来介绍 Python–…...
探索交互的本质:从指令到界面的演进与Linux基础指令的深入剖析
目录 1.指令 vs 界面//选读 1.1交互的需求 满足需求的第一阶段-指令 满足需求的第二阶段-界面 1.2 指令 和 界面交互 区别 2.操作系统介绍 2.1 举例说明 驱动软件层 2.2 为什么要有操作系统? 0x03 为什么要进行指令操作? 3.Linux基本指令 l…...
uniapp vue分享功能集成
分享必须通过button设置open-type"share"拉起 <view class"img horizontal center" style"margin-right: 20rpx;"><image class"img" :src"src" click"onTapClick(xxx)" style"z-index: 1;" …...
软件工程实务:软件产品
目录 1、软件产品的基本概念 2、软件工程是什么? 为什么产生软件工程? 软件工程是做什么的? 3、定制软件和软件产品的工程比较 4 、软件产品的运行模式 5、软件产品开发时需要考虑的两个基本技术因素 6、产品愿景 7、软件产品管理 8、产品原型设计 9、小结…...
Ventoy终极指南:一个U盘启动所有系统,告别重复格式化烦恼 [特殊字符]
Ventoy终极指南:一个U盘启动所有系统,告别重复格式化烦恼 😎 【免费下载链接】Ventoy A new bootable USB solution. 项目地址: https://gitcode.com/GitHub_Trending/ve/Ventoy 还在为每次安装系统都要重新制作启动盘而烦恼吗&#x…...
保姆级教程:在ROS2 Humble/Foxy的Gazebo中配置RGB-D相机(附解决点云颜色/坐标问题)
ROS2 Humble/Foxy中Gazebo深度相机仿真全攻略:从配置到点云问题解决在机器人仿真开发中,深度相机(RGB-D)是不可或缺的传感器之一。它能够同时提供彩色图像和深度信息,为SLAM、物体识别、避障等任务提供关键数据支持。本…...
告别手写UI!用NXP GUI Guider拖拽设计LVGL界面,5分钟搞定音乐播放器Demo
嵌入式UI开发革命:5分钟用GUI Guider构建LVGL音乐播放器在嵌入式系统开发中,用户界面(UI)设计曾长期是工程师的痛点——既要考虑资源受限的硬件环境,又要实现流畅美观的交互体验。传统手动编写UI代码的方式不仅效率低下,调试过程更…...
PCB虚焊/走线断裂/焊盘脱落工程师易漏判
PCB 故障中,30% 并非元件损坏,而是 PCB 本身的隐性故障—— 虚焊、走线断裂、焊盘脱落、过孔开路。这类故障外观隐蔽、时好时坏、排查难度大,很多工程师反复更换元件仍无法解决,最终误判为 “板报废”。一、PCB 隐性故障核心成因…...
厨房空调技术白皮书:从风冷到水冷,制冷系统在厨房场景中的工程化演进
厨房空调是暖通行业近三年技术迭代最密集的细分品类。从最初的"凉霸"(本质是风扇),到风冷分体式,再到水冷一体式,每代技术都在解决上一代没有覆盖的用户痛点。本文以工程技术视角,梳理四代厨房制…...
YOLOv8晶圆体缺识别检测系统(项目源码+YOLO数据集+模型权重+UI界面+python+深度学习+环境配置)
摘要 晶圆制造过程中的缺陷检测是保证芯片良率的关键环节。本文基于YOLOv8目标检测算法,构建了一套针对晶圆表面9类典型缺陷的自动检测系统。所识别的缺陷类型包括:Center、Donut、Edge-Loc、Edge-Ring、Loc、Near-full、None、Random、Scratch。模型在…...
终极免费音乐解锁工具:打破平台枷锁,让音乐重获自由
终极免费音乐解锁工具:打破平台枷锁,让音乐重获自由 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库: 1. https://github.com/unlock-music/unlock-music ;2. https://git.unlock-music.dev/um/web 项目地…...
DIY智能USB充电器:基于电流检测与双稳态继电器的零功耗节能方案
1. 项目概述:打造一款智能、节能的USB手机充电器作为一名电子爱好者,我经常折腾各种电源项目。市面上很多手机充电器,包括一些原装货,都存在一个通病:手机充满电后,充电器依然插在插座上,内部电…...
YOLO训练前数据检查必备:一个脚本批量转换LabelImg的txt标签并可视化核对
YOLO训练前数据检查实战:批量转换与可视化核验脚本开发指南 在计算机视觉项目的实际落地过程中,数据质量往往比模型架构更能决定最终效果的上限。许多团队花费大量时间调整超参数和网络结构,却忽略了最基础的标注数据验证环节。当使用LabelIm…...
从B站缓存困境到MP4自由:m4s-converter完整解决方案
从B站缓存困境到MP4自由:m4s-converter完整解决方案 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 当B站视频突然下架,那…...



