3、加密算法-AES和RSA

前两节博客主要是针对MD5和哈希算法,数字签名算法做了阐述和总结;
这篇文章主要是针对AES和RSA做一个概况和比较,以及相关的一些概念;

一、对称加密算法

对称加密算法是指加密和解密采用相同的密钥口,是可逆的(即可解密)。

1、AES

AES算法的发展可以追溯到1997年,是一种对称加密算法,其核心思想是将明文数据分成128位块,并通过多轮加密操作生成加密密文。
AES算法共包括10、12或14轮加密,根据密钥长度不同而有所变化。每轮加密包括四个步骤:置换、子密钥生成、异或操作和添加轮密钥。

优势

  • 快;AES算法在保证安全性的同时,具有较高的性能优势
  • 通用;对机器性能要求较低

劣势

  • AES算法对密钥管理的严格要求,因为加解密都是一套秘钥

2、DES、3DES

DES 算法在现代加密标准中已经被认为是不安全的。由于 DES 算法的密钥长度较短,且密钥管理困难,因此容易受到暴力破解攻击。【不做过多的概述,了解即可】

3DES是针对DES算法密钥过短、存在安全性的问题而改进的一个措施,被称为“3DES”。其实只是通过简单的执行3次DES来达到增加密钥长度和安全而已;

  • 密钥长度短
    DES算法的密钥长度只有64位,安全性较低,易受到暴力破解攻击。
  • 密钥弱化问题
    DES算法存在一些密钥弱化问题,即某些密钥可以被用于多次加密和解密,这会导致密钥的安全性受到威胁。
  • 安全性受到质疑
    DES算法已经被成功攻击,证明其安全性受到威胁,已经不适合作为现代加密算法使用。
  • 不适用于开放环境
    DES算法需要共享密钥,因此不适用于开放的环境,例如互联网。
  • 无法实现安全的密钥交换
    DES算法需要事先共享密钥,因此在传输密钥的过程中容易被攻击者截获并破解。因此,密钥交换的安全性成为了DES算法的一个难点。

二、非对称加密算法

非对称加密算法是指加密和解密采用不同的密钥(公钥和私钥),因此非对称加密也叫公钥加密,是可逆的(即可解密)。

1976年,两位美国计算机学家Whitfield Diffie 和 Martin Hellman,提出了一种崭新构思,可以在不直接传递密钥的情况下,完成解密。这被称为"Diffie-Hellman密钥交换算法"。
(1)甲要传密信给乙,乙先根据某种算法得出本次与甲通信的公钥与私钥
(2)乙将公钥传给甲(公钥可以让任何人知道,即使泄露也没有任何关系)
(3)甲使用乙传给的公钥加密要发送的信息原文m,发送给乙密文c
(4)乙使用自己的私钥解密密文c,得到信息原文m
如果公钥加密的信息只有私钥解得开,那么只要私钥不泄漏,通信就是安全的。

RSA

RSA加密算法是基于一个十分简单的数论事实: 将两个大素数相乘十分容易,但是想要对其乘积进行因式分解极其困难;
因此可以将乘积公开作为加密密钥。虽然RSA的安全性一直未能得到理论上的证明,但它经历了各种攻击至今未被完全攻破。

网上不少例子说的是公钥用于加密,私钥用于解密,其实这个说法不对,私钥和公钥是一对,都可以加解密,配对使用,只不过公钥可以公布出去,而私钥是持有者自己保留的。

一般的用法是私钥加密用于签名防数据被篡改,公钥加密用于加密防敏感信息,防止泄露。

RSA用途

  • 数据加密
    发送者用公钥加密,接收者用私钥解密(只有拥有私钥的接收者才能解读加密的内容)
  • 数字签名
    甲方用私钥加密,乙方用公钥解密(乙方解密成功说明就是甲方加的密,甲方就不可以抵赖)

优势

  • 非常安全;加密和解密的密钥不一致,公钥是可以公开的,只需保证私钥不被泄露即可(私钥在开发时协定好)
  • 密钥的传递变的简单很多,灵活高效;

劣势

  • 扩展性不好;由于素数产生技术的限制,难以做到一次一密
  • 加密速度慢;它的难度是基于大整数分解素数产生的难度,因此产生密钥耗时、也耗CPU【小数据量、低并发的场景】

三、线性散列算法

经典代表:MD5、SHA1、HMAC

MD5全称是Message-Digest Algorithm 5(信息摘要算法5),单向的算法不可逆(被MD5加密的数据不能被解密)。MD5加密后的数据长度要比加密数据小的多,且长度固定,且加密后的串是唯一的。【可以看一下我之前的MD5加密博文,不做过多描述,基本上如果对安全要求严苛的场景下,已经不适用了】
适用场景:常用在不可还原的密码存储、信息完整性校验等。

四、(RSA+AES)混合加密

由于以上加密算法都有各自的缺点(RSA加密速度慢、AES密钥存储问题、MD5加密不可逆),因此实际应用时常将几种加密算法混合使用。
采用RSA加密AES的密钥,采用AES对数据进行加密,这样集成了两种加密算法的优点,既保证了数据加密的速度,又实现了安全方便的密钥管理。
RSA(灵活高效的安全传输)+ AES(高效加解密)=》混合加密;

  1. RSA加密慢,但是安全、灵活(小数据量);AES加密效率高(大数据量),但是不安全
  2. RSA充当密码的载体,将AES的加密数据从前端=》服务端,再由RSA解码后,AES再去对加密数据解析;
    3.RSA适合密钥交换和数字签名;AES适合做数据加解密;

五、Base64加密

格意义讲,Base64并不能算是一种加密算法,而是一种编码格式,是网络上最常见的用于传输8字节代码的编码方式之一。
Base64编码可用于在HTTP环境下传递较长的标识信息,Base编码不仅不仅比较简单,同时也据有不可读性(编码的数据不会被肉眼直接看到)。
适用场景:常用于前后台针对隐私或敏感数据做加密,比如手机号、跳转路由之类,只希望前端做中转的数据透传场景。屏蔽肉眼识别的一种编码

实战

1.RSA+AES数据传输【混合加密】

代码

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.Scanner;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import io.jsonwebtoken.Claims;
import org.apache.commons.codec.binary.Base64;

/**
 * RSA加密工具类
 */
public class RSAUtil {
    private static Map<String, String> keyMap; // 用于封装随机产生的公钥与私钥

    static {
        // 如果是分布式项目,再加一个redis的分布式锁
        synchronized (RSAUtil.class) {
            // 1.生成秘钥
            long start = System.currentTimeMillis();
            keyMap = initRsaKeys();
            System.out.println("服务端项目启动,公私秘钥生成...." + (System.currentTimeMillis() - start));// 这个耗时还是很长的,我本地大概是1.8s
            System.out.println("写入到数据库或者redis....");
        }
    }

    public static void main(String[] args) throws Exception {
//        dataTransportCase();//测试AES的key:ABCDEFGHIJKLMNOP
        jwtCase();
    }

	/**
     * jwt登录实战
     */
    public static void jwtCase() throws Exception {
        // 1.用户登录[可以使用case1做账户密码的接收]
		System.out.print("1.界面:用户输入用户名和密码:");
		Scanner scanner = new Scanner(System.in);
		String account = scanner.nextLine();
		String privateKey = keyMap.get("private");
		System.out.println("2.后台:判断用户存在,生成token给到前端....");
		String token = JwtUtil.generateToken(account, "1000", privateKey);
		// 2.token校验
		System.out.print("3.前端:新发起一个请求,header中携带了token信息:" + token);
		String publicKey = keyMap.get("public");
		Claims playLoad = JwtUtil.getPlayLoad(token, publicKey);
		System.out.print("\n4.后台:解析前端在header中携带的token信息中的playLoad:");
		System.out.println(JSON.toJSONString(playLoad));
		System.out.println("5.后台:通过用户名去redis拿登录用户的缓存信息....");    }

    /**
     * RSA+AES加密传输实战
     */
    public static void dataTransportCase() throws Exception {
        // 1.项目启动
        String publicKey = keyMap.get("public");
        String privateKey = keyMap.get("private");
        // 2.用户输入密码
        Scanner scanner = new Scanner(System.in);
        System.out.print("1.界面:用户输入用户名和密码:");
        String userInfo = scanner.nextLine();
        System.out.println("2.全局约定:请设置一个登录相关的AES的加密key(也可以直接通过api从后台拿):");
        String aesKey = scanner.next();
        // 3.前端将密码进行AES加密
        String aesEncryptPwd = AESUtil.encrypt(aesKey, userInfo);
        System.out.println("3.前端:对用户输入的密码加密结果传递给后台:" + aesEncryptPwd);
        // 4.前端将加密的AES密码,再使用RSA的公钥进行加密
        String rsaEncryptData = encrypt(aesEncryptPwd, publicKey);
        // 5.后台直接通过项目启动时生成的RSA公私秘钥对,使用私钥进行解密
        String rsaDecryptData = decrypt(rsaEncryptData, privateKey);
        System.out.println("4.后台:接收到经过RSA加密的AES数据,先对其使用RSA秘钥解密...." + rsaDecryptData);
        String aesDecryptPwd = AESUtil.decrypt(aesKey, rsaDecryptData);
        System.out.println("5.后台:RSA解密后,再使用AES对密码解密...." + aesDecryptPwd);
        System.out.println(aesDecryptPwd);
    }

    /**
     * 模拟项目启动生成密钥对
     * - 1.直接本地生成一个,丢到redis或者存储到文件里面,再存入到redis【不然服务挂了公私钥没了】
     * - 2.项目启动的时候,直接读redis的数据,如果没有值则直接生成一对公私密钥,用base64进行编码(公司秘钥的获取可以提供一个api接口被前端获取)
     * - 3.此处对公私秘钥用了一个base64加解密,其实没什么影响,只是做一下肉眼的保护而已,防止有心人直接拿去乱来
     */
    public static Map<String, String> initRsaKeys() {
        Map<String, String> result = Maps.newHashMap();
        KeyPairGenerator keyPairGen = null;
        try {
            keyPairGen = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        assert keyPairGen != null;
        keyPairGen.initialize(1024, new SecureRandom());
        // 生成一个密钥对,保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
        String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
        String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
        result.put("public", publicKeyString);
        result.put("private", privateKeyString);
        return result;
    }

    /**
     * RSA公钥加密
     *
     * @param str       加密字符串
     * @param publicKey 公钥
     * @return 密文
     */
    public static String encrypt(String str, String publicKey) {
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey pubKey = null;
        String outStr = null;
        try {
            pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
        } catch (InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException |
                 NoSuchPaddingException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        //RSA加密
        return outStr;
    }


    /**
     * RSA私钥解密
     *
     * @param str        加密字符串
     * @param privateKey 私钥
     * @return 明文
     */
    public static String decrypt(String str, String privateKey) {
        //64位解码加密后的字符串
        byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey priKey;
        //RSA解密
        Cipher cipher;
        String outStr = null;
        try {
            priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
            cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            outStr = new String(cipher.doFinal(inputByte));
        } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException |
                 IllegalBlockSizeException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return outStr;
    }

    /**
     * 公钥获取
     */
    public static PublicKey getPublicKey(byte[] publicKey) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKey);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePublic(spec);
    }

    /**
     * 私钥获取
     */
    public static PrivateKey getPrivateKey(byte[] privateKey) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKey);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(spec);
    }
}
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AESUtil {

    public static String encrypt(String key, String data) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedBytes = cipher.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    public static String decrypt(String key, String encryptedData) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
        return new String(decryptedBytes);
    }
}

运行结果

Jwt签名的机制和结构需要理解到才知道每一步做的事情,最终的是和项目的实际情况相结合;
在这里插入图片描述

2.JwtToken登录实战【签名算法】

JWT是一种数字签名算法;数字签名的具体实现,通常是先对数据进行一次 Hash 摘要(SHA1/SHA256/SHA512 等),然后再使用非对称加密算法(RSA/ECDSA 等)的私钥对这个摘要进行加密,这样得到的结果就是原始数据的一个签名。

用户在验证数据时,只需要使用公钥解密出 Hash 摘要,然后自己再对数据进行一次同样的摘要,对比两个摘要是否相同即可。
在介绍它之前,需要解释一下这几个概念;

相关概念

数字签名

数据的 Hash 值;主要用途也是用于身份认证,代表这份数据就是你自己的:真实、完整、不可伪造

数字摘要/指纹

它是一个唯一对应一个消息或文本的固定长度的值,它由一个单项Hash函数对消息进行计算而产出;

不定长的数据===》数字摘要算法(Hash)====>定长的数据

加密算法

就是对jwt进行加密的算法;如果稍微有用过jwt的人,应该见过下面这个代码:

@SneakyThrows
private static String generateToken(Map<String, Object> claims) {
    return Jwts.builder()
            // 数字加密主体
            .setClaims(claims)
            // 过期时间
            .setExpiration(generateExpirationDate())
            // 数字签名算法
            .signWith(SignatureAlgorithm.RS256, RsaKeyUtil.getPrivateKey(JwtRsaKeyAutoConfig.rsaKeyConfig.getPrivateKey()))
            // 根据规则进行加密
            .compact();
}

签名算法是使用私钥加密,确保得到的签名无法被伪造,同时所有人都可以使用公钥解密来验证签名。这和正常的数据加密算法是相反的

数字签名的部分,点开jwt的源码进去其实有好几种算法:

package io.jsonwebtoken;
import io.jsonwebtoken.lang.RuntimeEnvironment;

public enum SignatureAlgorithm {
    NONE("none", "No digital signature or MAC performed", "None", (String)null, false),
    HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true),
    HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true),
    HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true),
    RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true),
    RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true),
    RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true),
    ES256("ES256", "ECDSA using P-256 and SHA-256", "Elliptic Curve", "SHA256withECDSA", false),
    ES384("ES384", "ECDSA using P-384 and SHA-384", "Elliptic Curve", "SHA384withECDSA", false),
    ES512("ES512", "ECDSA using P-512 and SHA-512", "Elliptic Curve", "SHA512withECDSA", false),
    PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "SHA256withRSAandMGF1", false),
    PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "SHA384withRSAandMGF1", false),
    PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "SHA512withRSAandMGF1", false);
	......
HS256【默认,已过时】

SHA-256 的 HMAC,它使用同一个「secret_key」进行签名与验证(对称加密)。只适合集中式认证,签名和验证都必须由可信方进行。

传统的单体应用广泛使用这种算法,但是请不要在任何分布式的架构中使用它!

RS256【常用,推荐】

SHA-256 的 RSA 签名,使用 RSA 公钥进行验证。公钥即使泄漏也毫无影响,只要确保私钥安全就行

ES256【资料不多】

使用 ECDSA 进行签名,它的安全性和运算速度目前和 RS256 差距不大,但是拥有更短的签名长度。
对于需要频繁发送的 JWT 而言,更短的长度长期下来可以节约大量流量;

PS256【资料不多】

数据结构

在这里插入图片描述

1.JWT头

是一个描述JWT元数据的JSON对象,通常如下所示

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

alg:表示签名使用的算法,默认为HMAC SHA256(写为HS256)
typ:表示令牌的类型,JWT令牌统一写为JWT

2.有效载荷playload

JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据【划重点
有效载荷部分规定有如下七个默认字段供选择:

iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

除以上默认字段外,还可以自定义私有字段,可以放用户的基础信息进去;

3.签名

签名实际上是一个使用私钥加密的过程,是对上面两部分数据通过指定的算法生成哈希,以确保数据不会被篡改。

  • 1.服务器指定一个密码:secret
  • 2.使用jwt配置指定的加密算法: rsa256
  • 3.对各部分进行加密

param1 = sceret
param2 = base64(header) + “.” + base64(payload)
rsa256(param2, param1)

代码

工具类用第一个case的就可以

import cn.hutool.core.codec.Base64;
import com.blue.base.common.exception.BaseException;
import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtil {

    /**
     * 生成token
     */
    public static String generateToken(String account, String userId, String privateKey) throws Exception {
        // 1.header部分,用的不多
        Map<String, Object> claims = new HashMap<>(10);
        // 2.playLoad部分【自带的字段】
        claims.put("aud", account);
        claims.put("iat", new Date());
        // playLoad部分【自定义的字段】
        claims.put("userId", userId);
        // 3.拿到rsa的私钥(base64做下解密)
        PrivateKey rsaPrivateKey = RSAUtil.getPrivateKey(Base64.decode(privateKey));
        return Jwts.builder()
                // 数字加密主体
                .setClaims(claims)
                // 过期时间
                .setExpiration(new Date(System.currentTimeMillis() + 84000L * 1000))// 1天
                // 数字签名算法:rs256,使用私钥加密
                .signWith(SignatureAlgorithm.RS256, rsaPrivateKey)
                // 根据规则进行加密
                .compact();
    }

    /**
     * 获取jwt的claims部分(公钥解密token)
     */
    static Claims getPlayLoad(String token, String publicKey) throws Exception {
        if (StringUtils.isEmpty(token)) {
            throw new IllegalArgumentException("token参数为空!");
        }
        PublicKey rsaPublicKey = RSAUtil.getPublicKey(Base64.decode(publicKey));
        try {
            return Jwts.parser()
                    .setSigningKey(rsaPublicKey)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException e) {
            throw new BaseException(800, "登录已过期");
        } catch (Exception e) {
            throw new BaseException(800, "Token解析异常");
        }
    }
}

运行结果

在这里插入图片描述

3.SpringBoot配置文件加密

除了常规的传输加密,比如某些yml文件不想让非核心开发知道的太多,或者涉及到敏感信息,不想对编外人员开放,可以使用下面的策略;
https://www.cnblogs.com/eyewink/p/17933845.html,比较简单,贵在实用;

总结

  • 不用纠结非对称加密的公私秘钥在前端or后台生成,关键是看场景和主导方
  • RSA拿来做签名是:私钥加密,公钥解密
  • RSA拿来做数据传输加密是:公钥加密,私钥解密
  • jwt的token案例在了解RSA的逻辑之后,会变得非常简单

参考资料

  • https://blog.csdn.net/qq_42210428/article/details/135317456
  • https://zhuanlan.zhihu.com/p/688039336
  • https://www.cnblogs.com/tuyile006/p/10873975.html
  • https://www.jianshu.com/p/77ed9d7d4745
  • https://cloud.tencent.com/developer/article/1594839
  • https://www.anxinssl.com/12544.html
  • https://www.cnblogs.com/eyewink/p/17933845.html
  • https://www.cnblogs.com/kirito-c/p/12402066.html
  • https://blog.csdn.net/cj151525/article/details/131433755

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高等数学在Android开发中的应用:函数极限与算法优化

高等数学在Android开发中的应用:函数极限与算法优化 在Android开发中,高等数学中的许多概念和技术都能够显著提高应用程序的性能和功能。这篇博客将探讨一些具体的数学原理,特别是函数极限在Android中的实际应用。 函数极限的基本概念 函数极限是微积分的基础,广泛应用于…

R可视化:好看的气泡图

加载R包 library(tidyverse) library(camcorder)gg_record(dir "tidytuesday-temp", device "png", width 8, height 8, units "in", dpi 320)导入数据 team_results <- readr::read_csv(https://raw.githubusercontent.com/rfordata…

有手就行,轻松本地部署 Llama、Qwen 大模型,无需 GPU

用 CPU 也能部署私有化大模型&#xff1f; 对&#xff0c;没错&#xff0c;只要你的电脑有个 8G 内存&#xff0c;你就可以轻松部署 Llama、Gemma、Qwen 等多种开源大模型。 非技术人员&#xff0c;安装 Docker、Docker-compose 很费劲&#xff1f; 不用&#xff0c;这些都不…

vue中路由来回切换页面直接卡死

今天发现一个很严重的问题&#xff0c;项目好不容易做好了&#xff0c;结果页面多了&#xff0c;切换之后卡死。页面所有的交互效果都失效了。 排查了许久的错误原因最后发现原来是路由名称重复了。 如上图当页面跳转到riskdetails详细页面之后&#xff0c;框架则被这个详情页…

SSE代替轮询?

什么是 SSE SSE&#xff08;Server-Sent Events&#xff0c;服务器发送事件&#xff09;&#xff0c;为特定目的而扩展的 HTTP 协议&#xff0c;用于实现服务器向客户端推送实时数据的单向通信。如果连接断开&#xff0c;浏览器会自动重连&#xff0c;传输的数据基于文本格式。…

高温下的稳定选择 —— PP消解管,耐化学更耐用

PP消解管&#xff0c;即聚丙烯材质的消解管&#xff0c;是一种常用于化学分析中的实验室设备&#xff0c;主要用于样品的消解处理。以下是PP消解管的一些主要特性和应用&#xff1a; 主要特性&#xff1a; 1. 耐化学腐蚀&#xff1a;PP材料对多数酸、碱和有机溶剂具有良好的耐…

Vue笔记-vue中使用JS创建的函数

主要是公司对前端要求不高&#xff0c;能解决问题就行了&#xff0c;前端不太熟&#xff0c;用js这种处理起来方便&#xff0c;在此记录下。 在src中创建一个api目录&#xff0c;新建custom.js export const getDivHeightByClass (className) > {let divElements docume…

MySQL数据库中文乱码处理

出现中文乱码之后处理方式 1、执行下面语句查看一下关于编码方式 show variables like %char%结果展示&#xff1a;【你应该和我的不一样】 2、如果你的和我查询结果不一致请设置成一致语句&#xff0c;根据自己需要复制语句 如下&#xff1a;【除了最后一条记录哈】 SET G…

mysql中的递归函数recursive

递归部门 WITH recursive dept_tree AS (SELECTsd.mine_id AS mine_id,sd.dept_id AS dept_id,sd.tenant_id AS tenant_id,sd.order_num,sd.dept_name AS topName,sd.dept_id AS topIdFROMsys_dept sdWHERE<!-- 加上or后也会查询出dept节点 sd.parent_id #{deptId} or sd.…

.net core 的缓存方案

这里主要讲两个缓存的使用&#xff0c;MemoryCache和Redis 先讲讲常见的缓存 1、.net framework web中自带有Cache缓存&#xff0c;这种缓存属于粘性缓存&#xff0c;是缓存到项目中的&#xff0c;项目从服务器迁移的时候缓存的内容也能够随着服务器一起迁移 2、MemoryCache缓存…

The First Descendant联机失败?第一后裔联机异常这样做

第一后裔/The First Descendant是一款由nexon开发的免费网游TheFirstDescendant第一后即将正式上线了&#xff0c;游戏的外观自定义系统还是非常不错的&#xff0c;人物角色可以选择不同风格的面部妆容&#xff0c;而且外观不仅可以更改颜色&#xff0c;还可以更改对应的材质和…

linux 控制台非常好用的 PS1 设置

直接上代码 IP$(/sbin/ifconfig eth0 | awk /inet / {print $2}) export PS1"\[\e[35m\]^o^\[\e[0m\]$ \[\e[31m\]\t\[\e[0m\] [\[\e[36m\]\w\[\e[0m\]] \[\e[32m\]\u\[\e[0m\]\[\e[33m\]\[\e[0m\]\[\e[34m\]\h(\[\e[31m\]$IP\[\e[m\])\[\e[0m\]\n\[\e[35m\].O.\[\e[0m\]…

65、基于卷积神经网络的调制分类(matlab)

1、基于卷积神经网络的调制分类的原理及流程 基于卷积神经网络&#xff08;CNN&#xff09;的调制分类是一种常见的信号处理任务&#xff0c;用于识别或分类不同调制方式的信号。下面是基于CNN的调制分类的原理和流程&#xff1a; 原理&#xff1a; CNN是一种深度学习模型&a…

如何用matplotlib绘制图像分类任务的类别特征空间分布

import matplotlib.pyplot as plt import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris from mpl_toolkits.mplot3d import Axes3D# 加载示例数据&#xff08;Iris 数据集&#xff09; data load_iris() X data.data y data.…

SQLyog脚本无限试用重置脚本

文章目录 引言脚本(win)必要操作、说明 引言 SQLyog 需要po jie&#xff0c;但是网上的没看到很好使的&#xff0c;直接下的官方。能处理14天试用也是很ok的。 脚本(win) echo offREM SQLyog注册表key&#xff0c;可能跟你的不一样&#xff0c;如果不一样&#xff0c;请替换…

聊聊gitlab ci如何构建以时间为版本号的docker镜像

前言 最近朋友他们部门有部分内部项目&#xff0c;打算用gitlab ci来做项目持续集成部署&#xff0c;他们有个需求&#xff0c;构建docker镜像的时候&#xff0c;版本需要是以当前时间作为版本。其格式为yyyymmddhhmm 一开始朋友翻阅官方文档&#xff0c;发现gitlab ci有个变…

uniapp实现可拖动悬浮按钮(最新版2024-7月)

此章主要介绍如何使用uniapp跨平台开发&#xff0c;实现悬浮按钮&#xff0c;移动端经常会有所这样的需求&#xff0c;那么功能如下&#xff1a; 1.圆圈悬浮球&#xff0c;上下左右靠边显示 2.可以界面任何拖动&#xff0c;不会超出界面 3.单击悬浮球的点击事件 效果&#xf…

JAVA-Redis数据结构—跳跃表(Skiplist)【包含Java实现详情代码】

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

分析:地产行业使用短信群发平台营销引流效果如何?

地产行业使用短信群发平台营销引流的效果可以从以下几个方面进行分析和归纳&#xff1a; 一、营销效果显著提升 1.精准定位目标客户&#xff1a;通过短信群发平台&#xff0c;地产企业可以根据客户的年龄、职业、地域、购房需求等信息&#xff0c;进行精准筛选和定位&#xf…

ElementUI样式优化:el-input修改样式、el-table 修改表头样式、斑马格样式、修改滚动条样式、

效果图&#xff1a; 1、改变日期时间组件的字体颜色背景 .form-class ::v-deep .el-date-editor { border: 1px solid #326AFF; background: #04308D !important; } .form-class ::v-deep .el-date-editor.el-input__wrapper { box-shadow: 0 0 0 0px #326AFF inset; } // 输入…