Java JWT:原理、机制及案例示范

一、什么是JWT?

1.1 JWT的基本概念

JWT(JSON Web Token)是一种用于在各方之间传递JSON格式信息的紧凑、URL安全的令牌(Token)。JWT的主要作用是验证用户身份或权限。它由三部分组成:

  1. Header(头部):标识令牌的类型和加密算法。
  2. Payload(载荷):包含了实际的身份信息及其他数据。
  3. Signature(签名):使用头部和载荷生成的签名,用于验证数据完整性和来源的可靠性。

1.2 JWT的结构

JWT的结构由三部分组成,它们通过点号(.)进行分隔,格式如下:

Header.Payload.Signature

具体每一部分的内容如下:

  • Header(头部): 通常包含两部分信息:令牌的类型(JWT)和签名的算法(如HMAC SHA256或RSA)。

    {"alg": "HS256","typ": "JWT"
    }
    
  • Payload(载荷): 载荷是JWT的主体部分,包含了需要传输的数据。通常包含以下几种常见的声明(Claims):

    • iss:签发者
    • exp:过期时间
    • sub:主题
    • aud:接收者
    • iat:签发时间
    • nbf:在此之前不可用
    {"sub": "1234567890","name": "John Doe","admin": true
    }
    
  • Signature(签名): 签名部分是用来验证消息的完整性,并确保其未被篡改。签名的生成方式如下:

    HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
    

二、JWT的原理和机制

2.1 JWT的工作机制

JWT是无状态的令牌,每次客户端(如浏览器)与服务器进行交互时,客户端会在请求头中携带这个令牌,服务器根据JWT中的签名验证令牌的合法性,并通过载荷(Payload)中的信息进行身份确认。

JWT认证的基本流程

  1. 用户登录:用户提交用户名和密码。
  2. 服务器验证用户:服务器验证用户名和密码是否正确。
  3. 生成JWT:验证成功后,服务器根据用户信息生成一个JWT并返回给客户端。
  4. 客户端保存JWT:客户端通常会将JWT存储在本地存储(LocalStorage)或Cookies中。
  5. 请求携带JWT:每次客户端向服务器发送请求时,会在请求头中附加JWT。
  6. 服务器验证JWT:服务器通过解析JWT,确认用户身份及权限,进而决定是否响应请求。

2.2 为什么使用JWT?

在一个复杂的分布式系统中,例如电商交易系统,多个微服务之间需要频繁通信。传统的基于会话的认证方式存在以下缺点:

  • 状态依赖:服务器需要存储每个用户的会话信息,随着用户数量的增加,服务器的存储负担增加。
  • 扩展性差:在分布式环境中,多个服务器需要共享会话状态,这增加了系统复杂性。

相比之下,JWT是一种无状态的认证机制,不需要服务器存储会话信息。JWT本身携带了用户的认证信息,具有如下优点:

  • 扩展性强:无状态特性使得JWT在分布式架构下具有良好的扩展性。
  • 跨平台、跨语言支持:JWT是JSON格式的令牌,几乎可以被所有语言和平台解析使用。
  • 灵活性:JWT的Payload部分可以自定义数据,从而支持复杂的权限系统。

2.3 JwtUtils.extractClaims实现

JwtUtils.extractClaims是一个工具方法,用于解析JWT并提取其中的声明(claims),如用户ID、角色信息和过期时间。下面是一个典型的实现,它展示了如何验证JWT的有效性、检查签名,以及处理异常。

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;import java.security.Key;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;public class JwtUtils {// 用于签名验证的密钥(示例使用HMAC)private static final String SECRET_KEY = "your-256-bit-secret";// 解码并生成用于验证的密钥对象private static Key getSigningKey() {byte[] keyBytes = Base64.getDecoder().decode(SECRET_KEY);return new SecretKeySpec(keyBytes, "HmacSHA256");}// 解析JWT并提取Claimspublic static Claims extractClaims(String token) {try {return Jwts.parserBuilder().setSigningKey(getSigningKey()) // 设置密钥用于签名验证.build().parseClaimsJws(token) // 解析并验证JWT.getBody(); // 返回Payload部分的Claims} catch (ExpiredJwtException e) {System.err.println("JWT已过期: " + e.getMessage());throw e;} catch (UnsupportedJwtException | MalformedJwtException e) {System.err.println("无效的JWT: " + e.getMessage());throw new IllegalArgumentException("Invalid JWT");} catch (SignatureException e) {System.err.println("JWT签名不匹配: " + e.getMessage());throw new SecurityException("Invalid signature");} catch (IllegalArgumentException e) {System.err.println("JWT为空或格式不正确: " + e.getMessage());throw e;}}
}

解析说明:

  1. 密钥管理getSigningKey()方法用于将Base64编码的字符串转换为签名密钥。
  2. 异常处理:解析JWT时可能出现多种异常,例如JWT过期签名无效格式错误等。通过捕获这些异常,我们可以进行相应的错误处理。
  3. 验证Token的有效性parseClaimsJws()会自动验证签名和Token格式,确保JWT未被篡改。

2.4 JwtUtils.extractClaims 所需的 Maven 依赖

为了实现 JwtUtils.extractClaims 方法,需要使用一个专门处理 JWT 的库。在之前的实现中,我们使用了 JJWT (Java JWT) 库。以下是 JJWT 所需的 Maven 依赖配置。

<!-- pom.xml -->
<dependencies><!-- JJWT: JSON Web Token for Java and Android --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if you prefer --><version>0.11.5</version><scope>runtime</scope></dependency><!-- 其他依赖项 --><!-- ... -->
</dependencies>

依赖说明:

  1. jjwt-api:提供 JWT 的核心接口和类。
  2. jjwt-impl:包含 jjwt 的实现部分,必须与 jjwt-api 一起使用。
  3. jjwt-jackson:用于 JSON 处理,支持使用 Jackson 库进行序列化和反序列化。如果您更喜欢使用 Gson,可以替换为 jjwt-gson

注意事项:

  • 确保所有 jjwt 依赖的版本一致,以避免兼容性问题。
  • 如果您使用的是 Spring Boot,可以考虑使用 Spring Security 提供的 JWT 支持,以简化集成过程。

三、JWT在电商交易系统中的应用

3.1 电商系统中的角色与权限

在电商交易系统中,涉及多个角色,例如:

  • 普通用户:可以浏览商品、下订单、查看订单状态。
  • 商家用户:可以管理商品、处理订单。
  • 管理员:可以管理平台用户、审核商家、处理投诉等。

系统需要根据不同用户的角色赋予相应的权限,而JWT可以通过其载荷中的角色信息实现权限管理。

3.2 电商系统的JWT认证流程

我们以一个电商交易系统为例,说明JWT在用户身份验证、订单管理中的应用。系统架构主要分为以下几个模块:

  1. 用户服务:负责用户的注册、登录、生成JWT令牌。
  2. 商品服务:提供商品的查询、展示等功能。
  3. 订单服务:处理订单的生成、状态管理。
  4. 支付服务:处理用户支付。
3.2.1 用户登录与JWT生成

用户登录时,系统会验证用户的用户名和密码,验证通过后生成JWT令牌并返回给客户端。这个令牌包含了用户的身份信息和角色权限。

登录流程的时序图:

在这里插入图片描述

3.2.2 请求保护资源

用户登录后,可以浏览商品或下订单。在每次请求时,客户端会在请求头中携带JWT令牌,服务器通过验证JWT来确认用户身份。

请求订单详情时的时序图:

在这里插入图片描述

3.3 电商系统中的JWT权限控制

在电商系统中,不同角色的用户具备不同的权限,JWT的Payload中可以存储用户的角色信息(如role),系统在处理请求时,根据JWT中的角色信息进行权限验证。

3.3.1 权限检查

例如:

  • 普通用户只能查看自己的订单;
  • 商家用户可以查看商家店铺的订单;
  • 管理员可以查看所有订单。

订单服务中的权限检查流程:

@RestController
@RequestMapping("/orders")
public class OrderController {@GetMapping("/{orderId}")public ResponseEntity<?> getOrder(@RequestHeader("Authorization") String token, @PathVariable Long orderId) {Claims claims = JwtUtils.extractClaims(token.replace("Bearer ", ""));String role = claims.get("role", String.class);Long userId = claims.get("userId", Long.class);Order order = orderService.findOrderById(orderId);if (order == null) {return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Order not found");}// 权限检查if ("USER".equals(role) && !order.getUserId().equals(userId)) {return ResponseEntity.status(HttpStatus.FORBIDDEN).body("You don't have permission to access this order");}return ResponseEntity.ok(order);}
}

在这个示例中,OrderController首先从JWT中提取用户角色和用户ID,然后根据不同角色的权限规则决定是否返回订单详情。

四、前端与后端的JWT交互示例

在电商系统中,前端通常通过JavaScript与后端进行交互。以下我们提供一个简单的HTML和JavaScript示例,展示如何通过JWT进行用户登录、访问受保护的资源。

4.1 登录功能

当用户提交用户名和密码时,前端会将这些数据发送给后端,后端验证成功后返回JWT,前端将其存储在localStorage中,用于后续的API请求。

HTML和JavaScript代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>JWT Login</title>
</head>
<body><h1>JWT Login Example</h1><form id="loginForm"><label for="username">Username:</label><input type="text" id="username" required><br><label for="password">Password:</label><input type="password" id="password" required><br><button type="submit">Login</button></form><div id="orderDetails" style="display:none;"><h2>Order Details</h2><pre id="orderContent"></pre></div><script>document.getElementById('loginForm').addEventListener('submit', function (event) {event.preventDefault();const username = document.getElementById('username').value;const password = document.getElementById('password').value;// 发送登录请求fetch('http://localhost:8080/api/login', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ username, password })}).then(response => response.json()).then(data => {// 存储JWT到localStoragelocalStorage.setItem('jwt', data.token);alert('Login successful');}).catch(error => console.error('Error:', error));});// 请求受保护的资源function fetchOrderDetails(orderId) {const token = localStorage.getItem('jwt');if (!token) {alert('Please login first');return;}fetch(`http://localhost:8080/api/orders/${orderId}`, {method: 'GET',headers: {'Authorization': `Bearer ${token}`}}).then(response => response.json()).then(order => {document.getElementById('orderContent').textContent = JSON.stringify(order, null, 2);document.getElementById('orderDetails').style.display = 'block';}).catch(error => console.error('Error:', error));}// 示例调用fetchOrderDetails(12345);</script>
</body>
</html>

4.2 前端代码说明

  • 用户登录时,前端通过fetch API向后端发送POST请求,后端验证成功后返回JWT,前端将其存储在localStorage中。
  • 在请求订单详情时,前端会将JWT添加到请求头中发送给后端,后端验证JWT的合法性后返回订单数据。

五、常见问题与解决方案

5.1 JWT过期问题的细化解决方案

问题:JWT一旦达到exp设定的过期时间,就会被视为无效,用户需要重新登录。这在用户体验上可能造成困扰,特别是当会话中断时。

解决方案一:刷新Token机制(Refresh Token)

Refresh Token是一种辅助机制,用于延长会话时长。具体步骤如下:

  1. 初次登录:服务器返回一个短时效的JWT和一个长期有效的Refresh Token。

  2. Token过期检查:在客户端请求时,如果JWT已过期,则使用Refresh Token获取新的JWT。

  3. 刷新JWT:

    • 客户端向服务器发送Refresh Token。
    • 服务器验证Refresh Token的合法性,若有效则生成新的JWT并返回。
    • 将新的JWT存储到客户端,并继续操作。

示例代码:刷新Token的实现

@PostMapping("/api/refresh-token")
public ResponseEntity<?> refreshToken(@RequestBody String refreshToken) {try {Claims claims = JwtUtils.extractClaims(refreshToken);if (claims != null) {// 验证通过,生成新的JWTString newJwt = JwtUtils.generateToken(claims.getSubject(), 15); // 新的JWT有效期15分钟return ResponseEntity.ok(Map.of("token", newJwt));}} catch (ExpiredJwtException e) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Refresh Token expired");}return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid Refresh Token");
}

注意:Refresh Token应该比JWT更长效,但同样需要妥善保护,避免泄露。


解决方案二:短时效与长会话相结合

思路:通过设置短时效JWT并动态刷新,确保在用户活动期间会话不会中断。

流程

  1. JWT过期时间设置为10~15分钟
  2. 每次用户操作时,通过客户端检测JWT的剩余有效期:
    • 如果剩余有效期不足5分钟,则向服务器请求新的Token。
  3. 用户不活动时,不自动刷新JWT。这样可以限制Token滥用的风险。

示例:检测并自动刷新Token的JavaScript代码

function isTokenExpiringSoon(token) {const payload = JSON.parse(atob(token.split('.')[1]));const expTime = payload.exp * 1000; // 转换为毫秒const currentTime = Date.now();return (expTime - currentTime) < 5 * 60 * 1000; // 剩余不足5分钟
}function refreshTokenIfNeeded() {const token = localStorage.getItem('jwt');if (token && isTokenExpiringSoon(token)) {fetch('http://localhost:8080/api/refresh-token', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(token)}).then(response => response.json()).then(data => localStorage.setItem('jwt', data.token)).catch(err => console.error('Token刷新失败:', err));}
}// 页面加载或用户操作时调用
document.addEventListener('click', refreshTokenIfNeeded);

5.2 JWT的大小问题

问题: JWT包含较多信息(如用户ID、角色、权限等),且使用Base64编码后体积较大,增加了网络带宽消耗。

解决方案:

  1. 精简 Payload:减少JWT中存储的数据,只存储必要的用户身份信息。
  2. 压缩 Token:可以通过GZIP等方式对JWT进行压缩后再进行传输。
5.2.1 精简 Payload

策略:

  • 仅存储必要信息:确保JWT的Payload中只包含验证用户身份所需的最少信息。例如,用户ID、用户名和角色可能是必要的,而不需要包含用户的详细资料。
  • 使用简短的声明名称:采用简短的键名来减少总体大小。例如,使用uid代替userIdr代替role

示例:

{"uid": "1234567890","n": "John Doe","r": "ADMIN"
}

实现示例:

public class JwtUtils {// 生成精简的JWTpublic static String generateToken(String userId, String username, String role, long expirationMinutes) {Claims claims = Jwts.claims().setSubject(userId);claims.put("n", username); // 简短的键名claims.put("r", role);     // 简短的键名return Jwts.builder().setClaims(claims).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expirationMinutes * 60 * 1000)).signWith(getSigningKey(), SignatureAlgorithm.HS256).compact();}
}

优点:

  • 减少 Token 大小:通过减少Payload中的数据量,显著降低JWT的总体大小。
  • 提升性能:较小的Token减少了网络传输的带宽消耗,提升请求的响应速度。

注意事项:

  • 平衡信息量与安全性:确保仅删除不必要的信息,避免影响系统的功能和安全性。
  • 一致性:在前后端以及各个微服务中统一Payload的结构和字段名称,避免解析错误。
5.2.2 压缩 Token

策略:

  • 使用压缩算法:在生成JWT后,使用GZIP等压缩算法对其进行压缩,然后进行Base64编码传输。
  • 服务器端解压:接收端需要在解析JWT前先进行解压缩。

实现示例:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.GZIPOutputStream;
import java.util.zip.GZIPInputStream;public class JwtUtils {// 压缩JWTpublic static String compressToken(String token) throws IOException {ByteArrayOutputStream byteStream = new ByteArrayOutputStream(token.length());try (GZIPOutputStream gzip = new GZIPOutputStream(byteStream)) {gzip.write(token.getBytes());}return Base64.getEncoder().encodeToString(byteStream.toByteArray());}// 解压JWTpublic static String decompressToken(String compressedToken) throws IOException {byte[] compressedBytes = Base64.getDecoder().decode(compressedToken);ByteArrayInputStream byteStream = new ByteArrayInputStream(compressedBytes);ByteArrayOutputStream out = new ByteArrayOutputStream();try (GZIPInputStream gzip = new GZIPInputStream(byteStream)) {byte[] buffer = new byte[256];int n;while ((n = gzip.read(buffer)) >= 0) {out.write(buffer, 0, n);}}return out.toString();}
}

前端与后端的集成:

  • 生成压缩Token:

    String token = JwtUtils.generateToken(userId, username, role, 15);
    String compressedToken = JwtUtils.compressToken(token);
    // 将compressedToken发送给客户端
    
  • 客户端存储与传输:

    客户端接收并存储压缩后的Token。每次发送请求时,发送压缩Token。

  • 服务器端接收与解压:

    @GetMapping("/api/orders/{orderId}")
    public ResponseEntity<?> getOrder(@RequestHeader("Authorization") String compressedToken, @PathVariable Long orderId) {try {String token = JwtUtils.decompressToken(compressedToken.replace("Bearer ", ""));Claims claims = JwtUtils.extractClaims(token);// 继续处理请求} catch (IOException e) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid Token");}// ...
    }
    

优点:

  • 显著减小Token体积:尤其是在Payload包含较多数据时,压缩效果显著。
  • 透明性:对前后端系统的改动较小,只需在传输环节增加压缩与解压步骤。

缺点:

  • 增加处理开销:压缩与解压缩会增加一定的CPU资源消耗,可能影响性能。
  • 复杂性:需要在前后端系统中增加相应的压缩与解压缩逻辑,增加系统复杂性。

注意事项:

  • 选择合适的压缩算法:GZIP是常用的选择,但也可以根据具体需求选择其他压缩算法。
  • 确保一致性:前后端必须严格按照相同的压缩与解压缩流程进行操作,避免Token解析失败。
5.2.3 其他优化策略

除了精简Payload和压缩Token外,还可以考虑以下优化策略:

  1. 使用短期Token与会话机制
    • 概述:结合使用短期有效的JWT和基于会话的状态管理。
    • 实现:JWT用于身份验证,短期内有效,结合服务器端的会话存储,用于管理用户的会话状态。
  2. 分片存储
    • 概述:将部分数据存储在客户端(如LocalStorage),将敏感数据存储在服务器端。
    • 实现:JWT只包含必要的身份信息,其他详细数据通过API查询获取。
  3. 自定义编码
    • 概述:使用自定义的编码方式替代标准的Base64,以减少字符数。
    • 实现:例如使用Base62编码替代Base64,减少Token长度。

选择适合的优化策略需要根据具体的系统需求和约束条件进行权衡。


5.3 JWT的安全性问题

问题: 虽然JWT签名可以保证数据不被篡改,但JWT的Payload部分是明文的,攻击者可以解码其中的敏感信息。

解决方案:

  1. 加密JWT:使用JWE(JSON Web Encryption)标准对JWT进行加密,确保敏感信息的安全性。
  2. 使用 HTTPS:始终通过HTTPS协议传输JWT,避免在网络传输中被拦截。
5.3.1 加密JWT

概述:

  • JWE (JSON Web Encryption) 是一种标准,用于对JWT的内容进行加密,确保数据的机密性。
  • 与JWS(JSON Web Signature)不同,JWE不仅可以验证数据的完整性和来源,还能保护数据不被未经授权的第三方读取。

实现示例:

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;public class JwtUtils {// 生成加密密钥(示例使用AES)private static final String SECRET_KEY = "your-256-bit-secret-your-256-bit-secret"; // 32字节密钥private static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());// 生成加密JWTpublic static String generateEncryptedToken(String userId, String username, String role, long expirationMinutes) {return Jwts.builder().setSubject(userId).claim("n", username).claim("r", role).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expirationMinutes * 60 * 1000)).signWith(KEY, SignatureAlgorithm.HS256).compressWith(CompressionCodecs.GZIP) // 可选:压缩Payload.encryptWith(Keys.secretKeyFor(SignatureAlgorithm.HS256)) // 使用对称密钥加密.compact();}// 解密JWTpublic static Claims extractEncryptedClaims(String encryptedToken) {try {return Jwts.parserBuilder().setSigningKey(KEY).build().parseClaimsJws(encryptedToken).getBody();} catch (JwtException e) {// 处理异常throw new SecurityException("Invalid encrypted JWT");}}
}

注意事项:

  • 密钥管理:加密密钥必须妥善保管,避免泄露。建议使用环境变量或专用的密钥管理服务(如 AWS KMS、Azure Key Vault)。
  • 性能开销:加密与解密会带来额外的计算开销,需评估对系统性能的影响。
  • 兼容性:确保前后端系统支持JWE标准,或使用相同的加密机制。
5.3.2 使用 HTTPS

概述:

  • HTTPS (HTTP Secure) 是在HTTP基础上加入SSL/TLS协议,确保数据在传输过程中被加密,防止被窃听或篡改。
  • 在传输JWT时,使用HTTPS可以防止Token被中间人攻击者截获和利用。

实现步骤:

  1. 获取 SSL/TLS 证书
  • 可以通过证书颁发机构(如 Let’s Encrypt)获取免费的SSL/TLS证书。
  1. 配置服务器
  • 在后端服务器(如 Nginx、Apache、Tomcat)上配置SSL/TLS,确保所有API请求通过HTTPS进行。

  • 示例(Nginx)

    server {listen 443 ssl;server_name yourdomain.com;ssl_certificate /path/to/fullchain.pem;ssl_certificate_key /path/to/privkey.pem;ssl_protocols TLSv1.2 TLSv1.3;ssl_ciphers HIGH:!aNULL:!MD5;location /api/ {proxy_pass http://localhost:8080/api/;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}
    }
    
  1. 强制 HTTPS
  • 确保所有HTTP请求重定向到HTTPS,避免用户通过不安全的连接访问系统。

  • 示例(Nginx)

    server {listen 80;server_name yourdomain.com;return 301 https://$host$request_uri;
    }
    
  1. 前端配置
  • 确保前端应用通过HTTPS协议与后端进行通信,避免混合内容问题。

优点:

  • 数据加密:确保JWT在传输过程中不被窃听或篡改。
  • 数据完整性:防止数据在传输过程中被修改。

注意事项:

  • 证书管理:定期更新SSL/TLS证书,避免证书过期导致服务中断。
  • 性能优化:使用HTTP/2等协议优化HTTPS连接的性能。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu 上安装 MySQL 并且实现远程登录

目录 1. 安装MySQL 2. 安全配置MySQL 3. 配置MySQL远程登录 3.1. 允许远程连接 3.2. 重启MySQL服务 3.3. 为用户分配远程访问权限 进入MySQL后&#xff0c;执行以下命令&#xff1a; 3.4. 创建新用户 3.5. 授予权限 3.6. 刷新权限 3.7. 退出 MySQL 控制台 4. 配置防火…

大数据新视界 --大数据大厂之大数据驱动下的物流供应链优化:实时追踪与智能调配

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

科研绘图系列:R语言蝴蝶图(Butterfly Chart)

文章目录 介绍加载R包数据函数画图系统信息介绍 蝴蝶图(Butterfly Chart),也被称为龙卷风图(Tornado Chart)或双轴图(Dual-Axis Chart),是一种用于展示两组对比数据的图表。这种图表通过在中心轴两侧分别展示两组数据的条形图,形似蝴蝶的翅膀,因此得名。蝴蝶图的特点…

汉语言文学做大数据七年实际工作经验分享普通人快来围观

&#xff08;一&#xff09;没有人带你 社会上&#xff0c;都很现实。就是进了公司&#xff0c;有师傅&#xff0c;师傅也没空带你&#xff0c;最多就是有空的时候帮你解决问题。 无论是做啥工作&#xff0c;都要靠自己努力。努力不会成为笑话&#xff0c;不努力就是笑话。就…

STM32 QSPI接口驱动GD/W25Qxx配置简要

STM32 QSPI接口GD/W25Qxx配置简要 &#x1f4dd;本篇会具体涉及介绍Winbond&#xff08;华邦&#xff09;和GD(兆易创新) NOR flash相关型号指令差异。由于网络上可以搜索到很多相关QSPI相关知识内容&#xff0c;不对QSPI通讯协议做深度解析。 &#x1f516;首先确保所使用的ST…

VScode 自定义代码配色方案

vscode是一款高度自定义配置的编辑器, 我们来看看如何使用它自定义配色吧 首先自定义代码配色是什么呢? 看看我的代码界面 简而言之, 就是给你的代码的不同语义(类名, 函数名, 关键字, 变量)等设置不同的颜色, 使得代码的可读性变强. 其实很多主题已经给出了定制好的配色方案…

闯关leetcode——88. Merge Sorted Array

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/merge-sorted-array/description/ 内容 You are given two integer arrays nums1 and nums2, sorted in non-decreasing order, and two integers m and n, representing the number of elements …

为什么人工智能用 Python?

人工智能领域倾向于使用Python&#xff0c;主要归因于Python的多个显著优势&#xff1a; 简洁性与可读性&#xff1a;Python的语法设计简洁明了&#xff0c;代码易于阅读和理解&#xff0c;这对于涉及复杂算法和逻辑的人工智能项目尤为重要。它降低了编程门槛&#xff0c;使得…

Unity3D 单例模式

Unity3D 泛型单例 单例模式 单例模式是一种创建型设计模式&#xff0c;能够保证一个类只有一个实例&#xff0c;提供访问实例的全局节点。 通常会把一些管理类设置成单例&#xff0c;例如 GameManager、UIManager 等&#xff0c;可以很方便地使用这些管理类单例&#xff0c;…

DGX的优势

NVIDIA DGX 的 AI 领导力 文章目录 前言一、概述推动跨行业的 AI 创新二、优势客户体验到哪些好处?1. 利用生成式 AI 释放研究人员的潜力2. 加快现代应用程序的上市时间3. 利用 AI 改善客户体验三、性能性能很重要1. 为世界上最先进的超级计算机提供动力2. 打破世界纪录3. 提高…

Go编译为可执行文件

在window下打包成其他系统可运行的文件 1.在window下打包成window下可执行文件 在项目main.go同级目录下&#xff0c;逐条执行以下命令 set CGO_ENABLED0 set GOOSwindows set GOARCHamd64 go build -o main-windows.exe main.go 2.在window下打包成linux 在项目main.go同级目…

Python从入门到高手6.3节-字符串操作方法

目录 6.3.1 字符串常用操作方法 6.3.2 获取字符串长度 6.3.3 字符串的大小写操作 6.3.4 删除字符串中的指定字符 6.3.5 字符串的子串查找 6.3.6 字符串的子串统计 6.3.7 字符串的子串替换 6.3.8 字符串的拆分函数 6.3.9 字符串的前缀与后缀 6.3.10 你一定要成为高手 …

FLINK内存管理解析,taskmanager、jobmanager

1、在 Flink 中设置内存的方法是配置以下两个选项之一&#xff1a; 1&#xff09;Total Flink memory&#xff1a;taskmanager.memory.flink.sizejobmanager.memory.flink.size 2&#xff09;Total process memory&#xff1a;taskmanager.memory.process.sizejobmanager.mem…

Linux驱动开发(速记版)--设备模型

第八十章 设备模型基本框架-kobject 和 kset 80.1 什么是设备模型 设备模型使Linux内核处理复杂设备更高效。 字符设备驱动适用于简单设备&#xff0c;但对于电源管理和热插拔&#xff0c;不够灵活。 设备模型允许开发人员以高级方式描述硬件及关系&#xff0c;提供API处理设备…

vue3学习之插槽slot

关于slot web组件内部的占位符&#xff0c;可以使用自己的标记填充这个占位符 &#xff0c;具名插槽就是在slot标签上添加name属性&#xff08;https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/slot&#xff09; vue3官方文档&#xff1a;https://cn.vuejs.org/gui…

论文阅读 BLIP-2

Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models 使用冻结的图像编码器和大型语言模型进行语言-图像预训练的引导 BLIP-2 通过一个轻量级的查询变换器弥合了模态之间的差距。 Querying Transformer 第一阶段通过冻结的图像编…

构建流媒体管道:利用 Docker 部署 Nginx-RTMP 从 FFmpeg RTMP 推流到 HLS 播放的完整流程

最近要实现一个类似导播台的功能&#xff0c;于是我先用 FFmpeg 实现一个参考对照的 Demo&#xff0c;我将其整理为一篇文章&#xff0c;方便后续大家或者和自己参考&#xff01; 1、软件工具介绍 本次部署相关软件 / 工具如下&#xff1a; FFmpeg&#xff1a;全称是 Fast Fo…

YOLO11改进 | 注意力机制| 对小目标友好的BiFormer【CVPR2023】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文介绍了一种新颖的动态稀疏注意力机制…

【多线程】多线程(12):多线程环境下使用哈希表

【多线程环境下使用哈希表&#xff08;重点掌握&#xff09;】 可以使用类&#xff1a;“ConcurrentHashMap” ★ConcurrentHashMap对比HashMap和Hashtable的优化点 1.优化了锁的粒度【最核心】 //Hashtable的加锁&#xff0c;就是直接给put&#xff0c;get等方法加上synch…

LLM | Tokenization 从原理与代码了解GPT的分词器

声明&#xff1a;以上内容全是学习Andrej Karpathy油管教学视频的总结。 --------------------------------------------------------------------------------------------------------------------------------- 大家好。在今天我们学习llm中的Tokenization&#xff0c;即分…