JWT详解(文章内嵌jwt工具类)

news2024/11/25 22:45:29

JWT 基础概念详解,工具类和使用方法放在最后

什么是 JWT?

JWT (JSON Web Token) 是目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。 从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。

JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。

jwt生成的token如下(例子):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRlIjoxNjY4NjczOTM4LCJib29sIjp0cnVlLCJkb3VibGUiOjExMi4yMjIsIkxvbmciOjExMiwicm9zZSI6ImFkbWluIiwiaW50ZWdlciI6MTExMSwiZXhwIjoxNjY4Njc0ODM4LCJpYXQiOjE2Njg2NzM5MzgsInVzZXJJZCI6IjEyMzQ1NiJ9.ryTEhOnX9B_vIhfMdgWVPeCBN8Y5mjNoQC-ba8tOXj8

目前我们不需要了解这个很长的 token 的意思,我们只要了解他是由三部分组成,用 . 进行分割,里面拥有信息(自定义参数、私钥、生成时间、过期时间、 token 算法等),由后端生成传递给前端(登录时,账号密码对了,后端(后端不保留 token 值)就生成个 token 给前端(这个token可以携带自己定义的参数,如:用户id(userId))),然后前端把 token 保存起来(仅在前端保存 token 就行了),每次请求时带上这个 token ,我们可以根据后台的 jwt 算法与 jwt 私钥参数(这些我都写在工具类里,拿这个工具类来解析这个 token 就行),对前端再次传过来的这段 token 进行解密,并得到自己需要的参数(如:userId ),那么就可以拿到这个 userId 操作数据库。

一、例子如下:

//下面这段代码只是一个参照,没有实现token生成的代码,只是告诉我们jwt是干嘛的
public static void main(String[] args) throws InterruptedException {
		//我们可以自定义一个map,并把map传进token里面
        Map<String,Object> map = new HashMap<>();
        //可以加很多的参数,包括但不限于以下两种
        map.put("userId","123456");
        map.put("rose","admin");
    
        // sign() 是一个当前文件的静态方法,之后调用的所有方法都是当前文件的方法,封装了一个工具类,在最后面,我会给大家,目前先理解着
    
        String token = sign(map); //把map丢进去,生成token
        System.out.println(token); //输出token
        System.out.println(verify(token));//验证token是否正确
        String dd = getClaims(token).get("userId").asString(); //使用方法
        System.out.println(dd);  // 获取 userId ,后端根据这个token就可以拿到 userId 了,那么就可以拿着这个id去增删改数据库了
        System.out.println("获取签发token时间:" +getIssuedAt(token));
        System.out.println("获取过期时间:"+getExpiresAt(token));
        System.out.println("检查是否已过期:"+isExpired(token));
        System.out.println("获取头"+getHeaderByBase64(token));
        System.out.println("获取负荷"+getPayloadByBase64(token));
    }

二、我的项目中的使用案例:(写的有点差,大佬们看看就行)

登录传递token:

    @Override
    public R login(LoginDTO loginDTO) {
        User loginUser = new User();
        loginUser.setUserName(loginDTO.getUserName())
                .setPassword(loginDTO.getPassword());
        try {
            //根据账号密码拿到用户信息
            User user = this.baseMapper.selectOne(
                    new LambdaQueryWrapper<User>()
                            .eq(User::getUserName, loginUser.getUserName())
 // 这是我毕设,目前还在开发中,前端是vue写的,vue太难了,就直接明文传输了,在后端加密跟数据库匹配,体现我的确是知道密码是要加密的,但前端太难,懒得做了
                            .eq(User::getPassword, MD5Util.md5(loginUser.getPassword())));
            if (user!=null){
                // 把两个参数加入到map中
                Map<String,Object> map = new HashMap<>(2);
                map.put("userId",user.getUserId());
                map.put("jurisdiction",user.getJurisdiction());
                // 把map丢给jwt处理,拿到token返回
                String token = Auth0JwtUtils.sign(map);
                Map<String , String> map2 = new HashMap<>(1);
                map2.put("token",token);
                // 因为我的vue模板要k-v键值对的方式传递token,所以我也懒得改,直接返个map给前端就行了
                return R.Success(map2);
            }
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage());
        }
        return R.Failed("账号密码错误");
    }

根据前端返回的token获取用户信息:

    @Override
	// 获取用户信息的 serviceImpl
    public R getUserInfo(String token) {
        try {
            // 判断token是否正确且在有效期范围内
            if (Auth0JwtUtils.verify(token)&&!Auth0JwtUtils.isExpired(token)){
                // 根据token,获取到 jurisdiction(权限)和 userId 用户的id
                Integer jurisdiction = Auth0JwtUtils.getInteger(token,"jurisdiction");
                Integer userId = Auth0JwtUtils.getInteger(token,"userId");
                // 根据 userId 查询数据库
                User user = this.baseMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserId, userId));
                Map<String , Object> map = new HashMap<>(10);
                // 写了个枚举来遍历权限
                for (JurisdictionEnum value : JurisdictionEnum.values()) {
                    if (value.getJurisdiction().equals(jurisdiction)){
                        map.put("role",value.getName());
                        map.put("roles",value.getX());
                        break;
                    }
                }
                // 挨个装起来
                map.put("sex",user.getSex());
                map.put("introduction",user.getIntroduction());
                map.put("avatar",user.getImage());
                map.put("phone",user.getPhone());
                map.put("name",user.getRealName());
                // 返回给前端
                return R.Success(map);
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return R.Failed();
    }

可以看出,JWT 更符合设计 RESTful API 时的「Stateless(无状态)」原则

并且, 使用 JWT 认证可以有效避免 CSRF 攻击,因为 JWT 一般是存在在 localStorage 中,使用 JWT 进行身份验证的过程中是不会涉及到 Cookie 的。

下面是 RFC 7519open in new window 对 JWT 做的较为正式的定义。

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ——JSON Web Token (JWT)open in new window

JWT 由哪些部分组成?

此图片来源于:https://supertokens.com/blog/oauth-vs-jwt

JWT 本质上就是一组字串,通过(.)切分成三个为 Base64 编码的部分:

  • Header : 描述 JWT 的元数据,定义了生成签名的算法以及 Token 的类型。
  • Payload : 用来存放实际需要传递的数据
  • Signature(签名) :服务器通过 Payload、Header 和一个密钥(Secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。

JWT 通常是这样的:xxxxx.yyyyy.zzzzz

示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

你可以在 jwt.ioopen in new window 这个网站上对其 JWT 进行解码,解码之后得到的就是 Header、Payload、Signature 这三部分。

Header 和 Payload 都是 JSON 格式的数据,Signature 由 Payload、Header 和 Secret(密钥)通过特定的计算公式和加密算法得到。

img

Header

Header 通常由两部分组成:

  • typ(Type):令牌类型,也就是 JWT。
  • alg(Algorithm) :签名算法,比如 HS256。

示例:

{
  "alg": "HS256",
  "typ": "JWT"
}

JSON 形式的 Header 被转换成 Base64 编码,成为 JWT 的第一部分。

Payload

Payload 也是 JSON 格式数据,其中包含了 Claims(声明,包含 JWT 的相关信息)。

Claims 分为三种类型:

  • Registered Claims(注册声明) :预定义的一些声明,建议使用,但不是强制性的。
  • Public Claims(公有声明) :JWT 签发方可以自定义的声明,但是为了避免冲突,应该在 IANA JSON Web Token Registryopen in new window 中定义它们。
  • Private Claims(私有声明) :JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用。

下面是一些常见的注册声明:

  • iss(issuer):JWT 签发方。
  • iat(issued at time):JWT 签发时间。
  • sub(subject):JWT 主题。
  • aud(audience):JWT 接收方。
  • exp(expiration time):JWT 的过期时间。
  • nbf(not before time):JWT 生效时间,早于该定义的时间的 JWT 不能被接受处理。
  • jti(JWT ID):JWT 唯一标识。

示例:

{
  "uid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
  "sub": "1234567890",
  "name": "John Doe",
  "exp": 15323232,
  "iat": 1516239022,
  "scope": ["admin", "user"]
}

Payload 部分默认是不加密的,一定不要将隐私信息存放在 Payload 当中!!!

JSON 形式的 Payload 被转换成 Base64 编码,成为 JWT 的第二部分。

Signature

Signature 部分是对前两部分的签名,作用是防止 JWT(主要是 payload) 被篡改。

这个签名的生成需要用到:

  • Header + Payload。
  • 存放在服务端的密钥(一定不要泄露出去)。
  • 签名算法。

签名的计算公式如下:

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

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,这个字符串就是 JWT 。

如何基于 JWT 进行身份验证?

在基于 JWT 进行身份验证的的应用程序中,服务器通过 Payload、Header 和 Secret(密钥)创建 JWT 并将 JWT 发送给客户端。客户端接收到 JWT 之后,会将其保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。

 JWT 身份验证示意图

简化后的步骤如下:

  1. 用户向服务器发送用户名、密码以及验证码用于登陆系统。
  2. 如果用户用户名、密码以及验证码校验正确的话,服务端会返回已经签名的 Token,也就是 JWT。
  3. 用户以后每次向后端发请求都在 Header 中带上这个 JWT 。
  4. 服务端检查 JWT 并从中获取用户相关信息。

两点建议:

  1. 建议将 JWT 存放在 localStorage 中,放在 Cookie 中会有 CSRF 风险。
  2. 请求服务端并携带 JWT 的常见做法是将其放在 HTTP Header 的 Authorization 字段中(Authorization: Bearer Token)。

spring-security-jwt-guideopen in new window 就是一个基于 JWT 来做身份认证的简单案例,感兴趣的可以看看。

如何防止 JWT 被篡改?

有了签名之后,即使 JWT 被泄露或者截获,黑客也没办法同时篡改 Signature 、Header 、Payload。

这是为什么呢?因为服务端拿到 JWT 之后,会解析出其中包含的 Header、Payload 以及 Signature 。服务端会根据 Header、Payload、密钥再次生成一个 Signature。拿新生成的 Signature 和 JWT 中的 Signature 作对比,如果一样就说明 Header 和 Payload 没有被修改。

不过,如果服务端的秘钥也被泄露的话,黑客就可以同时篡改 Signature 、Header 、Payload 了。黑客直接修改了 Header 和 Payload 之后,再重新生成一个 Signature 就可以了。

密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。

如何加强 JWT 的安全性?

  1. 使用安全系数高的加密算法。
  2. 使用成熟的开源库,没必要造轮子。
  3. JWT 存放在 localStorage 中而不是 Cookie 中,避免 CSRF 风险。
  4. 一定不要将隐私信息存放在 Payload 当中。
  5. 密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。
  6. Payload 要加入 exp (JWT 的过期时间),永久有效的 JWT 不合理。并且,JWT 的过期时间不易过长。
  7. and so on

好了,讲了这么多,其实我也记不全,直接拿JWT工具类来用就行,但稍微理解下原理的话,那么就可以应对各种各样的问题了不是

maven包:在这里插入图片描述

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.9.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>

工具类:

package com.zhao.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 *@author:Tlimited
 */
@Slf4j
public class Auth0JwtUtils {
    //过期时间 15分钟
    private static final long EXPIRE_TIME = 15* 60 * 1000;
    //私钥
    private static final String TOKEN_SECRET = "privateKey";

    /**
     * 生成签名,15分钟过期
     * 根据内部改造,支持6中类型,Integer,Long,Boolean,Double,String,Date
     * @param map
     * @return
     */
    public static String sign(Map<String,Object> map) {
        try {
            // 设置过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            // 私钥和加密算法
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            // 设置头部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("typ", "jwt");
            // 返回token字符串
           JWTCreator.Builder builder =  JWT.create()
                    .withHeader(header)
                    .withIssuedAt(new Date()) //发证时间
                    .withExpiresAt(date);  //过期时间
                 //   .sign(algorithm);  //密钥
             // map.entrySet().forEach(entry -> builder.withClaim( entry.getKey(),entry.getValue()));
              map.entrySet().forEach(entry -> {
                  if (entry.getValue() instanceof Integer) {
                      builder.withClaim( entry.getKey(),(Integer)entry.getValue());
                  } else if (entry.getValue() instanceof Long) {
                      builder.withClaim( entry.getKey(),(Long)entry.getValue());
                  } else if (entry.getValue() instanceof Boolean) {

                      builder.withClaim( entry.getKey(),(Boolean) entry.getValue());
                  } else if (entry.getValue() instanceof String) {
                      builder.withClaim( entry.getKey(),String.valueOf(entry.getValue()));
                  } else if (entry.getValue() instanceof Double) {
                      builder.withClaim( entry.getKey(),(Double)entry.getValue());
                  } else if (entry.getValue() instanceof Date) {
                          builder.withClaim( entry.getKey(),(Date)entry.getValue());
                  }
              });
            return builder.sign(algorithm);
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
    }


    /**
     * 检验token是否正确
     * @param **token**
     * @return
     */
    public static boolean verify(String token){
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            verifier.verify(token);

            return true;
        } catch (Exception e){
            log.error(e.getMessage());
            return false;
        }
    }

    /**
     *获取用户自定义Claim集合
     * @param token
     * @return
     */
    public static Map<String, Claim> getClaims(String token){
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        JWTVerifier verifier = JWT.require(algorithm).build();
        Map<String, Claim> jwt = verifier.verify(token).getClaims();
        return jwt;
    }

    /**
     *获取用户自定义根据token和字符串拿到String数据
     * @param token
     * @return String
     */
    public static String getString(String token,String z) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(z).asString();
        } catch (JWTDecodeException e) {
            log.error(e.getMessage());
            return null;
        }
    }
    /**
     *获取用户自定义根据token和字符串拿到Integer数据
     * @param token
     * @return Integer
     */
    public static Integer getInteger(String token,String z) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(z).asInt();
        } catch (JWTDecodeException e) {
            log.error(e.getMessage());
            return null;
        }
    }


    /**
     * 获取过期时间
     * @param token
     * @return
     */
    public static Date getExpiresAt(String token){
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
         return  JWT.require(algorithm).build().verify(token).getExpiresAt();
    }

    /**
     * 获取jwt发布时间
     */
    public static Date getIssuedAt(String token){
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        return  JWT.require(algorithm).build().verify(token).getIssuedAt();
    }

    /**
     * 验证token是否失效
     *
     * @param token
     * @return true:过期   false:没过期
     */
    public static boolean isExpired(String token) {
        try {
            final Date expiration = getExpiresAt(token);
            return expiration.before(new Date());
        }catch (TokenExpiredException e) {
            log.error(e.getMessage());
            return true;
        }

    }

    /**
     * 直接Base64解密获取header内容
     * @param token
     * @return
     */
    public static String getHeaderByBase64(String token){
        if (StringUtils.isEmpty(token)){
            return null;
        }else {
            byte[] header_byte = Base64.getDecoder().decode(token.split("\\.")[0]);
            String header = new String(header_byte);
            return header;
        }

    }

    /**
     * 直接Base64解密获取payload内容
     * @param token
     * @return
     */
    public static String getPayloadByBase64(String token){

        if (StringUtils.isEmpty(token)){
            return null;
        }else {
            byte[] payload_byte = Base64.getDecoder().decode(token.split("\\.")[1]);
            String payload = new String(payload_byte);
            return payload;
        }

    }
    public static void main(String[] args) throws InterruptedException {
        Map<String,Object> map = new HashMap<>();
        map.put("userId","123456");
        map.put("rose","admin");
        map.put("integer",1111);
        map.put("double",112.222);
        map.put("Long",112L);
        map.put("bool",true);
        map.put("date",new Date());
        String token = sign(map); //生成token
        System.out.println(token);
        System.out.println(verify(token));//验证token是否正确
        String dd = getClaims(token).get("integer").asString(); //使用方法
        System.out.println(dd);
        String userId = getString(token, "userId");  //获取String 类型的 userId
        System.out.println(userId);
        Integer integer = getInteger(token, "integer"); // 获取Integer 类型的 userId
        System.out.println(integer);
        System.out.println("获取签发token时间:" +getIssuedAt(token));
        System.out.println("获取过期时间:"+getExpiresAt(token));
       // Thread.sleep(1000*40);
        System.out.println("检查是否已过期:"+isExpired(token));
        System.out.println("获取头"+getHeaderByBase64(token));
        System.out.println("获取负荷"+getPayloadByBase64(token));
    }

}

输出:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRlIjoxNjY4Njc3NzQyLCJib29sIjp0cnVlLCJkb3VibGUiOjExMi4yMjIsIkxvbmciOjExMiwicm9zZSI6ImFkbWluIiwiaW50ZWdlciI6MTExMSwiZXhwIjoxNjY4Njc4NjQyLCJpYXQiOjE2Njg2Nzc3NDIsInVzZXJJZCI6IjEyMzQ1NiJ9.48may1A-FwGUdB6N2dbGnVX2NuKCGfUBSehsA6ARm5Y
true
null
123456
1111
获取签发token时间:Thu Nov 17 17:35:42 CST 2022
获取过期时间:Thu Nov 17 17:50:42 CST 2022
检查是否已过期:false
获取头{“typ”:“JWT”,“alg”:“HS256”}
System.out.println(“获取负荷”+getPayloadByBase64(token));
}

参考文章:JWT 基础概念详解

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

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

相关文章

【现代密码学原理】——消息认证码(学习笔记)

&#x1f4d6; 前言&#xff1a;消息认证码 MAC&#xff08;Message Authentication Code&#xff09;是经过特定算法后产生的一小段信息&#xff0c;检查某段消息的完整性&#xff0c;以及作身份验证。它可以用来检查在消息传递过程中&#xff0c;其内容是否被更改过&#xff…

Appium 移动端自动化测试(Mac)

目录 adb调试工具&#xff08;Android Debug Bridge&#xff09; adb常用命令 Appium使用 快速体验步骤 Appium常用方法 UIAutomatorViewer 获取元素 元素等待&#xff08;在指定时间内一直等待 元素操作 Swipe 滑动和拖拽事件&#xff08;Scroll、drag_and_drop&#…

pytorch深度学习实战19

第十九课 卷积层的填充和步幅 目录 理论部分 实践部分 理论部分 首先看一下卷积层的填充。 上图所示的情况会有个问题&#xff0c;如果卷积核不变的话&#xff08;一直是5*5的卷积核&#xff09;&#xff0c;那么我的网络最多也就只能弄到第七层了&#xff0c;如果我想搭建更…

恒太照明在北交所上市:募资规模缩水三成,第三季度收入下滑

11月17日&#xff0c;江苏恒太照明股份有限公司&#xff08;下称“恒太照明”&#xff0c;NQ:873339&#xff09;在北京证券交易所&#xff08;即“北交所”&#xff09;上市。本次上市&#xff0c;恒太照明的发行价格为6.28元/股&#xff0c;发行数量为2220万股&#xff0c;募…

Linux|centos7下部署安装alertmanager并实现邮箱和微信告警

前言&#xff1a; 一个成熟的符合一般人预想的资源监控平台应该是能够多维度的展示各种各样的数据&#xff0c;比如&#xff0c;服务器的内存使用率&#xff0c;磁盘io状态&#xff0c;磁盘使用率&#xff0c;CPU的负载状态&#xff0c;某个服务的状态&#xff08;比如&#x…

Pandas数据分析33——数据多条件筛选(点估计和区间估计评价指标)

本次是写论文代码区间估计评价指标有感..... 数据框有两列的时候&#xff0c;我们想筛选A列大于B列的样本出来时&#xff0c;只需要用布尔值索引就行&#xff1a; df[df[A]>df[B]] 可是多条件的筛选的时候怎么办&#xff0c;比如我需要A大于B列&#xff0c;还有A小于C列。…

软件测试“摆烂”已经成为常态化,我们应该怎样冲出重围?

网络日新月异发展的今天&#xff0c;每隔一段时间就会出现一些新的网络热词&#xff0c;最近“摆烂”成功突出重围&#xff0c;成为大家热议的中心。什么是“摆烂”&#xff1f;“摆烂”就是事情无法向好发展的时候直接选择妥协&#xff0c;不采取任何措施加以改变&#xff0c;…

兼容 信创鲲鹏/M1 arm64架构的kafka镜像

当前热度比较高的kafka镜像是wurstmeister/kafka&#xff0c;在dockerhub上有很多的使用次数。我起初很开心最新版支持arm64架构&#xff0c;然后拉到本地用Mac M1跑也很完美 但是&#xff01;我放到信创鲲鹏的生产环境&#xff0c;导入镜像没问题&#xff0c;但一用docker-com…

IPWorks Zip Delphi 流式压缩组件

IPWorks Zip Delphi 流式压缩组件 IPWorks Zip允许开发人员使用Zip、Tar、Gzip、7-Zip、Bzip2、ZCompress或Jar压缩标准轻松地将压缩和解压缩集成到应用程序中。IPWorks Zip组件是从头开始构建的&#xff0c;将卓越的速度与出色的数据压缩比相结合。 IPWorks Zip功能 文件压…

Allegro如何给铜皮导弧操作详解

Allegro如何给铜皮导弧操作详解 当需要给如下铜皮导弧的,是一件比较麻烦的事情,但是可以用以下两个方法来实现 方法一 具体操作如下 shape-decompose shape Find选择shapes Options选择层面,比如top层,选择delete shape after decompose 框选铜皮 得到下图效果,然后…

SNMP 协议解析(一)

♥️作者&#xff1a;小刘在C站 ♥️每天分享课堂笔记&#xff0c;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放。 一.SNMP是什么 是基于TCP/IP协议族的网络管理标准&#xff0c;它的前身是简单网关监控协议(SGMP)&#xf…

HOST前后端分离小操作

“本地测试项目配置域名小操作” ​ 相关文章【欢迎关注该公众号“一路向东”】&#xff08;CORS处理跨域问题&#xff09;&#xff1a; CORS跨域问题配置白名单CORS_ORIGIN_WHITELIST HOSTS 本地测试域名必然少不了修改&#xff1a;C:/Windows/System32/driver/etc/host文件&…

MVVM的构建(javakotlin)

概念性的描述就不写了&#xff0c;直接上代码 MVVM框架&#xff0c;主要是构建基类&#xff0c;实现ViewModel绑定&#xff0c;ViewBinding的绑定&#xff0c;在构建好基类后&#xff0c;后续开发中新建activity或fragment的viewModel和viewBinding就会在基类的方法中实现绑定…

Python virtualenv工具设置虚拟环境和VS code调试Python

Python virtualenv工具设置虚拟环境和VS code调试Python1. Window环境下采用VS code调试Python和虚拟环境1.1 安装Python1.2 安装虚拟环境工具virtualenv1.3 Windows上使用虚拟环境的基本操作1.4 Windows上VS code使用虚拟环境2. Linux环境下采用VS code调试Python和虚拟环境2.…

SAP 一次性读取工单的所有状态(工单抬头、工序、子工序、检验特性等等)

1. 前言 支持读取状态列表 2. 实现 代码 DATA: ls_objects TYPE tca11,ls_t490_imp TYPE t490. DATA: lt_ord_pre_imp TYPE TABLE OF ord_pre. FIELD-SYMBOLS: <fs_jest> TYPE any.DATA: lt_jest TYPE TABLE OF jest."fill var ls_objects VALUE #( flg_alt X…

[搞点好玩的] JETSONNANO 受苦记 -- 001 (布置环境,未完待续)

最开始是打算从0开始自己调试适配yahboom的IO板子&#xff0c;但是后来各种版本失配翻墙困难&#xff0c;前期先是用人家的包烧录进去。(所以叫未完待续) 直接粘贴我的debug记录了哈&#xff0c;凑合看吧&#xff1a; 记录 本项目是开源的实现基于jetbot的图像处理流程项目&a…

分享购商城模式详情特点和优势解析

随着互联网的不断发展&#xff0c;市面上出现了很多新怡的商业模式&#xff0c;就以分享购这个模式举例说来&#xff0c;直接解决了消费者自买省、好省、分享赚的逻辑玩法&#xff0c;之前有一个平台一个月直接引入70万的会员&#xff0c;引爆了网络上又一波话题&#xff0c;热…

【物理应用】大气辐射和透射率模型及太阳和月亮模型(Matlab代码实现)

&#x1f496;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f947;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为…

基于分时电价策略的家庭能量系统优化附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

《计算机导论》课程学习笔记

目录 第一章认识计算机 1.1计算思维概述 1.2冯诺依曼体系结构 1.3计算机硬件组成 1.4计算机软件 1.5计算机操作系统 第一章认识计算机 1.1计算思维概述 1.计算思维能力概念 计算思维能力的核心是问题求解能力。 发现问题寻求解决问题的思路分析比较不同的方案验证方案…