JWT认证

news2024/9/22 13:37:10

一、什么是JWT

官网地址: https://jwt.io/introduction/

jsonwebtoken(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名

JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理

 

 

二、JWT能做什么

  • 授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
  • 信息交换:JSON Web Token是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。

注意:jwt跟session不一样,jwt存储在客户端,session存储在服务器端,服务器断电后session就没了,而jwt因为存储在客户端,所以就不会被影响,只要jwt不过期,就可以继续使用。

  

 

三、为什么选择JWT

3.1、基于传统的Session认证

1、认证方式:

        我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

2、认证流程:

在这里插入图片描述

3、暴露问题:

  • 每个用户经过我们的应用认证之后,我们的应用都要在服务端做 一次记录,以方便用户下次请求的鉴别,通常而言session都是保 存在内存中,而随着认证用户的增多,服务端的开销会明显增大
  • 用户认证之后,服务端做认证记录,如果认证的记录被保存在 内存中的话,这意味着用户下次请求还必须要请求在这台服务 器上,这样才能拿到授权的资源,这样在分布式的应用上,相应 的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
  • 因为是基于cookie来进行用户识别的, cookie如果被截获,用户 就会很容易受到跨站请求伪造的攻击。
  • 在前后端分离系统中就更加痛苦:如下图所示也就是说前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session 每次携带sessionid 到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是CSRF(跨站伪造请求攻    击)攻击,session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。还有就是sessionid就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。不方便集群应用。

在这里插入图片描述

 

3.2、基于JWT认证 

在这里插入图片描述认证流程:

1、⾸先,前端通过Web表单将⾃⼰的⽤⼾名和密码发送到后端的接⼝。这⼀过程⼀般是⼀个HTTP POST请求。建议的⽅式是通过SSL加密的传输(https协议) ,从⽽避免敏感信息被嗅探。

2、后端核对⽤⼾名和密码成功后,将⽤⼾的id等其他信息作为JWT Payload (负载),将其与头部分别 进⾏Base64编码拼接后签名,形成⼀个JWT(Token)。形成的JWT就是⼀个形同11. zzz. xxx的字符 串。token head.payload.signature

3、后端将JWT字符串作为登录成功的返回结果返回给前端。 前端可以将返回的结果保存在 localStorage或sessionStorage上, 退出登录时前端删除保存的JWT即可。

4、前端在每次请求时将JWT放⼊HTTP Header中的Authorization位。 (解决XSS和XSRF问题)

5、后端检查是否存在,如存在验证JWT的有效性。

  • 检查签名是否正确; ◦ 检查Token是否过期;
  • 检查Token的接收⽅是否是⾃⼰(可选)

6、验证通过后后端使⽤JWT中包含的⽤⼾信息进⾏其他逻辑操作,返回相应结果。 

JWT优势:

  • 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,数据量⼩,传输速度快
  • ⾃包含(Self-contained):负载中包含了所有⽤⼾所需要的信息,避免了多次查询数据库
  • 因为Token是 以JSON加密的形式保存在客⼾端的,所以JWT是跨语⾔的,原则上任何web形式都⽀持。
  • 不需要在服务端保存会话信息,特别适⽤于分布式微服务。

 

 

四、JWT具体结构

token   string  ====>  header.payload.singnature  token   

 

4.1、令牌组成

  • 标头(Header)
  • 有效载荷(Payload)
  • 签名(Signature)

因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz   Header.Payload.Signature

 

4.2、header

标头通常由两部分组成: 令牌的类型(即JWT) 和所使⽤的签名算法,例如HMAC、SHA256或RSA。 它 会使⽤Base64 编码组成JWT 结构的第⼀部分。

注意:Base64是⼀ 种编码,也就是说,它是可以被翻译回原来的样⼦来的。它并不是⼀种加密过程。

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

 

4.3、Payload

令牌的第⼆部分是有效负载,其中包含声明。声明是有关实体(通常是⽤⼾)和其他数据的声明。同样 的,它会使⽤Base64 编码组成JWT结构的第⼆部分

{
    "sub" : "HS256"
    "name" : "yjiewei"
    "admin" : "true"
}

 

4.4、Signature

header和payload都是结果Base64编码过的,中间⽤.隔开,第三部分就是前⾯两部分合起来做签 名,密钥绝对⾃⼰保管好,签名值同样做Base64编码拼接在JWT后⾯。(签名并编码)

HMACSHA256 (base64Ur1Encode(header) + "." + base64Ur1Encode(payload) , secret);

 签名目的


最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

 信息安全问题


问题:Base64是一种编码, 是可逆的,那么我的信息不就被暴露了吗?

 

是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

在这里插入图片描述 

 

 

4.5、整合在一起

输出是三个由点分隔的Base64-URL字符串,可以在HTML和 HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML) 相比,它更紧凑。

  • 简洁(Compact):可以通过URL, POST 参数或者在 HTTP header 发送,因为 数据量小,传输速度快
  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

在这里插入图片描述

  

 

五、使用JWT

引入依赖


<!--引⼊jwt-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

完整代码


public class Jwt {
    /**
    * 获取JWT令牌
    */
    @Test
    public void getToken() {
        Map<String, Object> map = new HashMap<>();
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND, 2000);

        /**
        * header可以不写有默认值
        * payload 通常⽤来存放⽤⼾信息
        * signature 是前两个合起来的签名值
        */
        String token = JWT.create().withHeader(map) //header
            .withClaim("userId", 21)//payload
            .withClaim("username", "yjiewei")//payload
            .withExpiresAt(instance.getTime())//指定令牌的过期时间
            .sign(Algorithm.HMAC256("!RHO4$%*^fi$R")); //签名,密钥⾃⼰记住
        System.out.println(token);
    }

    /**
    * 令牌验证:根据令牌和签名解析数据
    * 常⻅异常:
    * SignatureVerificationException 签名不⼀致异常
    * TokenExpiredException 令牌过期异常
    * AlgorithmMismatchException 算法不匹配异常
    * InvalidClaimException 失效的payload异常
    */
    @Test
    public void tokenVerify() {
        // token值传⼊做验证
        String token =
            "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MjkyMDg0NjgsInVzZXJJZCI6MjEsIn
        VzZXJuYW1lIjoieWppZXdlaSJ9.e4auZWkykZ2Hu8Q20toaks-4e62gerPlDEPHvhunCnQ";

        /**
        * ⽤⼾Id:21
        * ⽤⼾名:yjiewei
        * 过期时间:Tue Aug 17 21:54:28 CST 2021
        */
        JWTVerifier jwtVerifier =
        JWT.require(Algorithm.HMAC256("!RHO4$%*^fi$R")).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token); // 验证并获取解码后的
        token
        System.out.println("⽤⼾Id:" + decodedJWT.getClaim("userId").asInt());
        System.out.println("⽤⼾名:" +
        decodedJWT.getClaim("username").asString());
        decodedJWT.getClaim("username").asString());
        System.out.println("过期时间:" + decodedJWT.getExpiresAt());
    }
}

 

六、封装工具类

Java就是封装抽象封装抽象...所以这⾥也封装⼀个⼯具类

public class JWTUtil {
    /**
    * 密钥要⾃⼰保管好
    */
    private static String SECRET = "privatekey#^&^%!save";
    /**
    * 传⼊payload信息获取token
    * @param map payload
    * @return token
    */
    public static String getToken(Map<String, String> map) {
    JWTCreator.Builder builder = JWT.create();
    //payload
    map.forEach(builder::withClaim);
    Calendar instance = Calendar.getInstance();
    instance.add(Calendar.DATE, 3); //默认3天过期
    builder.withExpiresAt(instance.getTime());//指定令牌的过期时间
    return builder.sign(Algorithm.HMAC256(SECRET));
}

/**
* 验证token
*/
public static DecodedJWT verify(String token) {
    //如果有任何验证异常,此处都会抛出异常
    return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
}

/**
* 获取token中的payload
*/
public static Map<String, Claim> getPayloadFromToken(String token) {
    return
    JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token).getClaims();
    }
}

 

 

七、JWT 整合SpringBoot

引入依赖、编写配置

 

<!--引入jwt-->
<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>3.4.0</version>
</dependency>

<!--引入mybatis-->
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.1.3</version>
</dependency>

<!--引入lombok-->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.12</version>
</dependency>

<!--引入druid-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.19</version>
</dependency>

<!--引入mysql-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.38</version>
</dependency>
server.port=8989
spring.application.name=jwt

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jwt?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root

mybatis.type-aliases-package=com.baizhi.entity
mybatis.mapper-locations=classpath:com/baizhi/mapper/*.xml

logging.level.com.baizhi.dao=debug

数据库


这里采用最简单的表结构验证JWT使用

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(80) DEFAULT NULL COMMENT '用户名',
  `password` varchar(40) DEFAULT NULL COMMENT '用户密码',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

实体类


@Data
@Accessors(chain=true)
public class User {
    private String id;
    private String name;
    private String password;
}

Mapper、Service 接口以及实现类


@Mapper
public interface UserDAO {
    User login(User user);
}
<mapper namespace="com.baizhi.dao.UserDAO">
    <!--这里就写的简单点了毕竟不是重点-->
    <select id="login" parameterType="User" resultType="User">
        select * from user where name=#{name} and password = #{password}
    </select>
</mapper>
public interface UserService {
    User login(User user);//登录接口
}
@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDAO userDAO;
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public User login(User user) {
        User userDB = userDAO.login(user);
        if(userDB!=null){
            return userDB;
        }
        throw  new RuntimeException("登录失败~~");
    }
}

controller


@RestController
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/user/login")
    public Map<String,Object> login(User user) {
        Map<String,Object> result = new HashMap<>();
        log.info("用户名: [{}]", user.getName());
        log.info("密码: [{}]", user.getPassword());
        try {
            User userDB = userService.login(user);
            Map<String, String> map = new HashMap<>();//用来存放payload
            map.put("id",userDB.getId());
            map.put("username", userDB.getName());
            String token = JWTUtils.getToken(map);
            result.put("state",true);
            result.put("msg","登录成功!!!");
            result.put("token",token); //成功返回token信息
        } catch (Exception e) {
            e.printStackTrace();
            result.put("state","false");
            result.put("msg",e.getMessage());
        }
        return result;
    }
}

通过postman模拟登录失败


在这里插入图片描述 


通过postman模拟登录成功


在这里插入图片描述


编写测试接口


@PostMapping("/test/test")
public Map<String, Object> test(String token) {
  Map<String, Object> map = new HashMap<>();
  try {
    JWTUtils.verify(token);
    map.put("msg", "验证通过~~~");
    map.put("state", true);
  } catch (TokenExpiredException e) {
    map.put("state", false);
    map.put("msg", "Token已经过期!!!");
  } catch (SignatureVerificationException e){
    map.put("state", false);
    map.put("msg", "签名错误!!!");
  } catch (AlgorithmMismatchException e){
    map.put("state", false);
    map.put("msg", "加密算法不匹配!!!");
  } catch (Exception e) {
    e.printStackTrace();
    map.put("state", false);
    map.put("msg", "无效token~~");
  }
  return map;
}

在这里插入图片描述


通过postman请求接口


在这里插入图片描述

在这里插入图片描述

问题:

  • 使用上述方式每次都要传递token数据,每个方法都需要验证token代码冗余,不够灵活? 如何优化
  • 使用拦截器进行优化
// 拦截器
@Slf4j
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
    HttpServletResponse response,
    Object handler) throws Exception {
    //获取请求头中的令牌
    String token = request.getHeader("token");
    log.info("当前token为:{}", token);
    Map<String, Object> map = new HashMap<>();
    try {
    JWTUtil.verify(token);
    return true;
    } catch (SignatureVerificationException e) {
    e.printStackTrace();
    map.put("msg", "签名不⼀致");
    } catch (TokenExpiredException e) {
    e.printStackTrace();
    map.put("msg", "令牌过期");
    } catch (AlgorithmMismatchException e) {
    e.printStackTrace();
    map.put("msg", "算法不匹配");
    } catch (InvalidClaimException e) {
    e.printStackTrace();
    map.put("msg", "失效的payload");
    } catch (Exception e) {
    e.printStackTrace();
    map.put("msg", "token⽆效");
    }
    map.put("status", false);
    //响应到前台: 将map转为json
    String json = new ObjectMapper().writeValueAsString(map);
    response.setContentType("application/json;charset=UTF-8");
    response.getWriter().println(json);
    return false;
    }
}
// 指定拦截路径
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
        .addPathPatterns("/user/test")
        .excludePathPatterns("/user/login");
    }
}
// 拦截请求并验证请求头中的token
@PostMapping("/user/test")
public Map<String, Object> test(HttpServletRequest request) {
    String token = request.getHeader("token");
    DecodedJWT verify = JWTUtil.verify(token);
    String id = verify.getClaim("id").asString(); // 我前⾯存的时候转字符串了
    String name = verify.getClaim("name").asString();
    log.info("⽤⼾id:{}", id);
    log.info("⽤⼾名: {}", name);
    //TODO:业务逻辑
    Map<String, Object> map = new HashMap<>();
    map.put("status", true);
    map.put("msg", "请求成功");
    return map;
}

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

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

相关文章

华为nqa实验拓扑案例

bqa是一种实时的网络性能探测和统计技术&#xff0c;可以对响应时间、网络抖动、丢包率等网络信息进行统计。如图1所示&#xff0c;接口备份与NQA联动功能配置相对简单&#xff0c;只需在本端RouterA上配置NQA测试例&#xff0c;并在RouterA的备份接口上配置接口备份与NQA联动&…

自定义组件中如何注入Spring底层的组件

1.概述 自定义的组件要想使用Spring容器底层的一些组件&#xff0c;比如ApplicationContext&#xff08;IOC容器&#xff09;、底层的BeanFactory等等&#xff0c;那么只需要让自定义组件实现XxxAware接口即可。此时&#xff0c;Spring在创建对象的时候&#xff0c;会调用XxxA…

搞懂 API,API 常见技术使用场景分享

API&#xff08;应用程序编程接口&#xff09;是一种允许软件应用程序之间相互交互和通信的技术。以下是API常用的使用场景&#xff1a; 应用程序开发 API通常被用于网站或应用程序的开发中&#xff0c;以便在不同平台、语言及数据库之间获取数据或进行消息传递。例如&#xff…

探索数字化转型新道路!流辰信息微服务与您一起创未来!

科技在进步&#xff0c;社会在发展&#xff0c;办公自动化也在高速发展中。数字化转型是当下企业获得长久发展的趋势之一&#xff0c;在信息瞬间万变的社会中&#xff0c;谁掌握了核心技术&#xff0c;谁能与时代同步&#xff0c;谁就能开启新的康庄大道&#xff0c;谁就能在转…

VS2017配置Qt——超详细步骤教学(看完不会算你狠)

一、环境要求 visual studio 2017 vsaddin Qt14.1 mysql 注意mysql环境与msvc2017编译器环境保持一致。 mysql32位 配 msvc2017 32位 或 mysql64位 配 msvc2017 64位 注意&#xff1a;环境不一致会导致软件运行错误&#xff0c;为了避免这些错误&#xff0c;要将…

第1章计算机系统漫游之 “源代码的编译与执行” 及 “操作系统管理硬件”

文章目录 1、信息就是位上下文2、程序被其他程序翻译成不同的格式3、了解编译系统如何工作的益处4、处理器读并解释储存在存储器中的指令4.1 系统的硬件组成4.2 执行 hello 程序 5、高速缓存6、形成层次结构的存储设备7、操作系统管理硬件7.1 进程7.2 线程7.3 虚拟存储器7.4 文…

docker容器内使用cat命令修改文件

有时候docker容器内部没装vi 或vim命令&#xff0c;无法使用vi来修改文件 可以使用cat命令来查看文件 cat 主要功能一次显示整个文件:cat filename 从键盘创建一个文件:cat > filename 只能创建新文件,不能编辑已有文件 将几个文件合并为一个文件:cat file1 file2 > fi…

最新黄金市场价格分析之干掉调整浪

等待的过程无疑是最令人心烦的。各位朋友应该试过&#xff0c;等待自己的朋友、亲人&#xff0c;等等结果&#xff0c;等待成绩公布等等。但是等待是我们干任何事都必不可少的过程&#xff0c;是我们缓冲、蓄力的阶段。最新黄金市场价格分析中的等待&#xff0c;体现在调整浪的…

Python心经(3)

这一节总结点demo和常用知识点 目录 有关字符串格式化打印的 lambda匿名函数&#xff0c;&#xff0c;将匿名函数作为参数传入 文件读写 生成器 python的装饰器 简单的网站代码&#xff1a; 有关三元运算 推导式&#xff1a; 新浪面试题&#xff1a; 有关面向对象里…

SpringBoot项目中一些常用的,工具类

推荐多使用这个&#xff1a; Hutool参考文档Hutool&#xff0c;Java工具集https://hutool.cn/docs/#/core/%E9%9B%86%E5%90%88%E7%B1%BB/%E9%9B%86%E5%90%88%E5%B7%A5%E5%85%B7-CollUtil?id%e4%bb%8b%e7%bb%8d 1&#xff1a;断言 断言是一个逻辑判断&#xff0c;用于检查不应…

芯片封装技术(三)

Interposer 是一种用于连接芯片的中间层技术&#xff0c;它的基底通常是一块硅基底&#xff0c;而硅基底也是 Substrate 的一种。因此&#xff0c;Interposer 与 Substrate 有一定的关系。对于RDL Interposer来说&#xff0c;Si Interposer的信号布线密度进一步提高&#xff0c…

[Linux] Linux文件系统

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【LINUX】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 文章目录 一、Linux文件系统1.1 磁盘1.2 inode1.3 软硬…

Philosophy of life: growing flowers in your heart

Growing flowers in your heart An aged man lived in a nice cottage with a large garden in a town in England. He is seen busy looking after his flowers all time. 第一部分介绍的是: 有一个老人在英格兰的镇上有一个带大花园的屋子&#xff0c;他一直在忙着照顾他的花…

机器学习实战:带你进入AI世界!

机器学习是人工智能领域的一个重要分支&#xff0c;可以帮助我们从大量数据中发现规律&#xff0c;进行预测和分类等任务。然而&#xff0c;想要真正掌握机器学习算法&#xff0c;并将其应用到实际问题中&#xff0c;还需要进行大量的实战练习。 本文将介绍几个常见的机器学习实…

seurat -- 细胞注释部分

文章目录 brief寻找差异基因部分注释细胞部分详细参数 brief 细胞注释大概分为两步&#xff1a;差异基因 --> marker genes —> map reference 差异基因可以是表达量上存在差异也可以是表达细胞占比上存在差异&#xff0c;通常二者兼顾考虑。 marker genes 个人理解为…

蓝牙网状网络的基本原理及应用开发

借助蓝牙 5 的网状网络功能&#xff0c;开发人员可以增强无线连接系统&#xff08;如物联网设备&#xff09;的通信范围和网络可用性。但是&#xff0c;网状网络的低功耗无线硬件设计与网状网络软件开发之间存在着复杂的层次&#xff0c;这可能会使开发人员迅速陷入混乱并危及项…

GLM论文精读-自回归填空的通用语言模型

GLM作为ChatGLM的前期基础论文&#xff0c;值得精读。本文是对GLM论文的精读笔记&#xff0c;希望对大家有帮助。GLM主要思想概述&#xff0c;利用自回归填空的思想&#xff0c;基于transformer的编码器实现了同时在NLU和有无条件生成任务上较好的表现。 基本信息 原文&#…

设计模式 -- 备忘录模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

邮件营销自动化:优化营销流程,提升转化率

对于希望与客户联系&#xff0c;并推广其产品或服务的企业来说&#xff0c;电子邮件营销是一个强大的工具。然而&#xff0c;随着电子邮件通信量的持续增长&#xff0c;企业要跟上客户对个性化和及时性消息的需求&#xff0c;可能会面临一定的挑战。而这就是电子邮件营销自动化…

干货满满!破解FP安全收款难题

怎样安全收款是做擦边产品卖家比较忧虑的问题&#xff0c;2023年已经即将来到了年中&#xff0c;跨境卖家们在这一方面做得怎么样了呢&#xff1f; 这期分享破解FP独立站收款难题的方法。 一、商家破解FP收款难题方法 1.第三方信用通道 优点&#xff1a;信用卡在国外使用率比…