后端之路——登录校验前言(Cookie\ Session\ JWT令牌)

前言:Servlet

【登录校验】这个功能技术的基础是【会话技术】,那么在讲【会话技术】的时候必然要谈到【Cookie】和【Session】这两个东西,那么在这之前必须要先讲一下一个很重要但是很多人都会忽略的一个知识点:【Servlet

什么是Servlet?

        Servlet是用java编写的应用在服务器端的程序;对于它的定义,“广义上”是一个个很大的【类】,“狭义上”是【接口】

.

Servlet容器是什么?

        我们经常听说的Tomcat、Weblogic......这些都是各种【Servlet容器】,而【Servlet容器】就是Servlet的运行环境,也可以理解是Servlet的引擎,为请求和响应的这些操作提供网络服务。

        那么我们知道,我们运行网络服务的时候都要启动Tomcat服务器,Tomcat来解析处理【请求】、【响应】,并处理成报文信息,但是这些服务器的缺点是底层代码写死了,很不灵活,而且有时处理完的数据格式也不够规范;那么这时候就诞生了Servlet,它被用来“扩展服务器的性能”,它能够灵活的处理请求、响应数据并以规范形式返回。

        那么说回Servlet,其实最简单最简单的理解,就是一个很大很规范的【类】,它里面包含了很多其他【子类】(准确来说是接口),这些子类包括:CookieSessionHttpServletRequestHttpServletResponse、ServletConfig、ServletContext......

        那么这些【子类】里也写好了很详细、规范的各种处理网络服务的方法,当我们需要处理一些网络服务逻辑的时候,就需要调用Servlet里的【子类】的【实例化对象】的【方法】

        打个比方:处理前端客户端发送请求的数据并生成对应的请求头报文时,就需要调用【HttpServletRequest】的实例化对象的方法;  处理服务器端返回回去的响应头报文的时候,就需要调用【HttpServletResponse】的实例化对象的方法......等等

那么现在我们重新来看一下(B/S架构)浏览器客户端与服务器端传输的流程:

1、浏览器客户端发送请求到服务器端

2、服务器端接收到信息,交给Servlet

3、Servlet(通过调用里面的一些子类的方法)处理逻辑,然后生成响应信息,给回服务器

4、服务器将响应结果返回浏览器客户端

一、会话技术

1、何为会话?

浏览器与服务器之间的一次连接就是一次会话

2、会话跟踪

【会话跟踪】就是:识别多个请求是否来自于同一个浏览器,然后在同一个浏览器的多个请求之间共享数据

3、会话跟踪方案:

浏览器与服务器之间的交互使用的是【http】协议,但是【http】协议是无状态的,也就是所有请求都是相互独立的,并不能在多个请求之间共享数据

那么就有了这么几种【会话跟踪技术】:

1、客户端会话跟踪技术:Cookie

.

2、服务端会话跟踪技术:Session

3、token令牌技术:JWT

这里不讲解HTTP是啥,这里有一篇讲的比较详细:HTTP 协议详解(史上最全)-CSDN博客

HTTP协议里的【请求头】和【响应头】的文章:Request Headers 和Response Headers——请求头和响应头-CSDN博客

只需要知道HTTP协议是一个浏览器与服务器联系传输数据的超文本传输协议,它两规定了一种传输数据的格式,然后里面有【响应报文Response Headers】和【请求报文Request Headers】这两部分,下面是简单讲解:

请求报文Request Headers】:

前端通过【浏览器/客户端】发送请求传给【服务器】的数据信息

一个【请求报文Request Headers】里包括了【请求行】【请求头】【请求体】

【请求体】就是前端传过来的具体的数据信息

【请求头】是服务器获取客户端信息的一些依据(用什么格式获取?从哪个地址获取?哪个浏览器发的?......)

响应头报文Reponse Headers】:

【服务器】返回给【浏览器/客户端】的响应数据信息

一样,一个【响应报文Response Headers】里也包括了【响应行】【响应头】【响应体】

【响应体】就是针对前端发来的请求数据而相应回去的具体数据值

【响应头】包含了服务器的响应讯息,如http版本,压缩方式,响应文件类型,文件编码等

(1)Cookie技术(Cookie是一个Servlet里的“子类”)

虽然每个请求和响应之间的Http协议是独立的,但是Http协议的【请求头】和【响应头】支持携带【Cookie】这个信息,那多个请求间就可以通过Cookie这个标识来获取用户信息数据

Cookie简单说:就是存放在客户端(浏览器)】的会话信息

—— Cookie是通过在【请求报文Request Headers】里的【请求头】处传递的

—— 传递关系是:【客户端(浏览器)——传Cookie——>服务器

—— Set-Cookie是通过【响应报文Response Headers】里的【响应头】处传递的

—— 传递关系是:【服务器——返回Set-Cookie——>客户端(浏览器)

简单用代码直观一点展示Cookie和Set-Cookie:(切记别记忆这些代码,简单了解即可)

Set-Cookie:

【HttpServletResponse】这个接口的实现类是专门设置【响应报文】信息的(切记:要导入的包一定要选这个【javax.servlet.http】)

.

然后【HttpServletResponse】的实现类对象的【.addCookie( )】方法能设置一个Cookie的值,需要往里面传一个【Cookie对象】(切记:这个【Cookie对象】对应的类型必须也是【javax.servlet.http】)

.

最后【Cookie对象】里数据形式是“键值对Key=value”:【name=value】,所以要传一个name参数、一个value值,分别代表 “键” 和 “值”

/**
 * 模拟【服务器】设置【用户的Cookie】的操作
 * @param response
 * @return
 */
@GetMapping("/setCookie")
public Result setCookie(HttpServletResponse response){
    //调用这个响应方法就能设置一个Cookie值,Cookie对象里是【键值对】信息:name=value
    response.addCookie(new javax.servlet.http.Cookie("userName","岑梓铭"));
    return Result.success();
}

怎么看效果?

1、首先输入网址,千万先别回车

2、摁F12打开网页检查,然后再在网址那回车,就会看到一个网络响应

3、单击它,然后就能在【响应报文Response Header】处看到【Set-Cookie】

Cookie:

【HttpServletRequest】这个接口的实现类是专门设置【请求报文】信息的(切记:要导入的包一定要选这个【javax.servlet.http】)

.

然后【HttpServletRequest】的实现类对象的【.getCookies( )】方法能返回所有Cookie的值,返回值是一个数组;需要用一个【Cookie对象类型的数组】接收(切记:这个【Cookie对象】类型对应的类型必须也是【javax.servlet.http】)

.

最后【Cookie对象】里数据形式是“键值对Key=value”:【name=value】,所以要获取Cookie数组里每一个Cookie的 “键”,就要调用【Cookie对象】的【.getName( )】方法

@GetMapping("/getCookie")
public Result getCookie(HttpServletRequest request){
    //发送请求后,获取返回的【所有的Cookies】
    javax.servlet.http.Cookie[] cookies = request.getCookies();

    //遍历所有Cookie,如果有对应这个【键(name)】的,就返回对应的【值(value)】
    for(javax.servlet.http.Cookie cookie : cookies){
        if(cookie.getName().equals( "userName" )){
            System.out.println("userName: " + "【" + cookie.getValue() + "】");
        }
    }

    return Result.success();
}

 怎么看效果?还是一样

1、首先输入网址,千万先别回车

2、摁F12打开网页检查,然后再在网址那回车,就会看到一个网络响应

3、单击它,然后就能在【响应报文Response Header】处看到【Set-Cookie】

缺点

1、移动端环境不是浏览器,浏览器才有Cookie这玩意

.

2、用户可以随意自己设置禁用Cookie,会用电脑的应该不用我解释,浏览器设置那里有

.

3、Cookie不能跨域(跨域就是【协议、IP/域名、端口】至少其中一样不一样,就是两个域,就存在跨域访问,那么我们都知道前端、后端是分别部署到两个不同的服务器的,不同服务器的地址肯定是不一样的,浏览器在发请求、返回响应时必然会要访问前端和后端的两个服务器,就会跨域)

总结

(2)Session技术(Session也是Servlet里的一个“子类”)

Session的本质其实就是对Cookie的优化,是存放在【服务器端】的会话信息

为什么说是Cookie的优化?因为它的逻辑其实是这样:

        首先浏览器发请求,产生一个会话,然后服务器这边就立刻产生一个【Session会话信息】和一个对应这个Session会话信息的【sessionId】,然后把【Session会话信息】存在服务器,只会把【sessionId】存进Cookie,通过Set-Cookie响应回给浏览器

。。

        然后下次浏览器再次发送请求想获取这个【Session会话信息】的时候,传递过去的是【装着sessionId的Cookie】,然后服务器检查Cookie里的【sessionId】,再到session里找有没有对应这个【sessionId】的【Session会话信息】,找到了的话,把【sessionId】和【Session会话信息】一起塞进【Cookie】返回给浏览器

模拟服务器【保存session】并【生成sessionId】的逻辑
/**
 * 模拟【服务器端】生成并响应回【session】的操作
 * @param session
 * @return
 */
@GetMapping("/setSession")
public Result setSession(HttpSession session){
    //打印一下当前session的哈希码值,这个哈希码值代表指向了哪一个session会话,是【整数】
    //但是注意区分,这个不是sessionId,sessionId是HttpSession对象的唯一标识符,是【字符串】
    log.info("session_hashCode: {}",session.hashCode());

    //调用这个方法可以往session会话里存入一个数据,然后对应生成一个sessionID并塞进Cookie里
    session.setAttribute("LoginName","岑梓铭");

    return Result.success();
}

模拟浏览器通过sessionId【接收session信息】的逻辑
/**
 * 模拟【浏览器】发请求后获得服务器生成的【session】的操作
 * @param request
 * @return
 */
@GetMapping("/getSession")
public Result getSession(HttpServletRequest request){
    //调用HttpServletRequest对象的getSession()方法可以获取到session会话对象
    HttpSession session = request.getSession();

    //打印一下当前session的哈希码值,这个哈希码值代表指向了哪一个session会话,是【整数】
    log.info("session_hashCode: {}",session.hashCode());

    //Session的getAttribute方法,里面传入“键”参数,就能返回对应的seesion会话对象里的具体值
    //逻辑是浏览器这边Cookie里只有sessionId,然后通过Session的getAttribute方法把Cookie里的sessionId给到服务器端
    //最终经过服务器检测sessionId,然后将session会话里对应“userName”的会话具体数据再塞进Cookie,返回给浏览器
    Object userInfo = session.getAttribute("LoginName");
    log.info("userInfo: {}",userInfo);

    return Result.success(userInfo);
}

 缺点

1、服务器集群情况下(也就是连接多个服务器的情况下),不同的服务器之间存着不同的会话信息,那就算浏览器的Cookie里有sessionId,那第二台服务器那里能靠第一台服务器seesion的 “钥匙” 来 “开“ 第二台服务器seesion的 “大门” 呢?

.

2、session会话信息都存在服务器,随着浏览器请求增多,服务器内存越来越不够用

.

3、既然它还是基于Cookie优化而来的,那必然也继承了Cookie的缺点

总结

(3)JWT令牌技术

——概念:

JWT,全称:【Json Web Token】,简单来说就是一长串带有【数字签名、签名算法、具体自定义信息......等等】json字符串,每一次请求响应都会带着它,通过计算来校验、得出其中的身份信息

一个JWT字符串主要包括三大部分:

        第一部分:Header(头),记录令牌类型、名算法等。例如:{"alg":"HS256","type":"JWT"}

        第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","username":"Tom"}

        第三部分:Siqnature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

而组成这个字符串的原理是:

1、前面两部分是【Base64编码】,是一种由【A-Z  a-z  1-9  还有/】组成的来表示二进制数据的编码

2、最后一部分那一段是根据前面指定的一种【签名算法】计算后得到的编码

应用场景:

其实很简单,流程就是:

1、浏览器发送请求到服务器

2、服务器拦截请求,查看有没有token?没有就拒绝访问数据,并生成一个JWT令牌

3、下一次再来检查到有JWT令牌了,那就校验JWT令牌对不对,不对就拒绝访问;对就开放访问权限。

生成JWT令牌的方法:
1、引入依赖

在pom.xml文件引入下面依赖

<!-- JWT令牌 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

注意,如果点了右上角的【maven刷新】按钮只后还是爆红,又可能只是连接中央库下载安装这个依赖包的时候网络不好,毕竟这些依赖都是在国外的公司的中央库,那么控制台那会有一个 “蓝色” 的提示——“尝试使用 -U 标记(强制更新快照)运行 Maven导入”,直接点它让Maven帮我们换个方案下载安装就行了

2、在Test类测试一下生成JWT令牌

只需要记住6步:

1、先设置一个哈希表集合,因为jwt令牌的【有效信息部分】要用【哈希表集合类型】接收

2、创建一个jwt的方法是【Jwts.builder( )】

3、一个Jwt令牌的第一部分是指定 “签名算法类型”“签名密钥”;那么【.signWith( )方法】就是设置jwt令牌第一部分。以我个人理解,“签名算法类型” 就是指根据不同类型的不同算法,“签名密钥” 就是以你自定义输入的一串字符串作为一个 “密钥”,用你的这个 “签名密钥” 才能在解析jwt令牌时知道你要解析的是哪一个(至于“签名算法类型”具体哪些类型有啥区别我也不知道,尽量先都用HS256这个类型就行)

4、一个Jwt令牌的第二部分是【有效荷载】,也就是用户的一些【有效信息部分】,【Jwts.addClaims( )】方法则是在创建一个jwt对象之后接收这个【有效信息部分】的方法,接收【哈希表集合】类型数据

5、jwt令牌要有个【有效时间】,就跟你们平时登录时的验证码一样,不然的话没时间限制那不是留够了时间给黑客破解吗。【.setExiration( )】方法就是设置【有效时间】,需要接受的是Date时间类型(System.currentTimeMillis()是目前的系统时间,加一个有效时间期限就行)

6、jwt是一个对象,要用【.compact( )】方法才能转化成【字符串】

@Test
void testGenJWT(){
    //先设置一个哈希表集合,因为jwt令牌的【有效信息部分】要用【哈希表集合类型】接收
    Map<String , Object> claims = new HashMap<>(); //值用Object因为可能是数字、可能是字符串
    claims.put("id",1);
    claims.put("name","岑梓铭");

    String jwt = Jwts.builder() //builder就是创建一个JWT令牌
            .signWith(SignatureAlgorithm.HS256,"yjtlwkbz") //设置【签名算法的类型(比如HS256)】、【签名内容(比如yjtlwkbz)】
            .addClaims(claims) //有效荷载部分,接收哈希表集合类型,存入用户有效信息
            .setExpiration(new Date(System.currentTimeMillis() + 3600*1000)) //设置有效时间1h(3600秒 * 1000毫秒)
            .compact(); //把结果生成字符串

    System.out.println(jwt);
}

然后提示几点:

1、因为顶上的【@SpringBootTest】注解会影响整个项目,直接注释了,然后点对应这个当前这个【@Test】测试方法运行最快

.

2、如果刚刚编写生成jwt令牌代码时爆红爆错,检查这几个问题:

—— 报错classNotFoundException的下载jaxb-api依赖,2.1版本(版本别填错了)

—— 使用Base64编码字符串长度至少为43位,位数报错的可以把 “签名内容” 改成任意的大于等于43位的字符串(比如我代码里的"yjtlwkbz"改成"ahjahsdgaysdgkuywdgjwdbasbcjhcjasyasgkjjsh")

3、还有有的人可能会出现【test】包下的test类(class文件)全变成java文件了,没法运行,右键也不能新建class类文件,那可能是IDE出了点问题,清楚IDE缓存再重新启动一次就行,见下图

然后运行完成后,我们把控制台生成的【jwt令牌】复制,到这个网站可以查看解析我们的【jwt令牌】的信息:JSON Web Tokens - jwt.io

解析JWT令牌的方法

更简单,三步:

1、【Jwts.parser( )】方法解析jwt令牌

2、【.setSigningKey( )】方法就是根据你前面生成jwt令牌时的那个【签名密钥】来 “打开解密大门”

3、【.parseClaimsJws( )】把刚刚生成的【jwt令牌】整个塞进去就能被解析了

注意,别选成【.parseClaimsJwt( )】了,这两是两个东西,选下图这个

4、【.getBody( )】能够获取出【有效荷载】部分那些具体信息,并封装在一个Claims类型对象

@Test
void testGetJWT(){
    Claims claims = Jwts.parser()
            .setSigningKey("yjtlwkbz")  //对应生成jwt的.signWith(SignatureAlgorithm.HS256,"yjtlwkbz")那个密钥
            //对应刚刚运行生成的jwt令牌
            .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi5bKR5qKT6ZOtIiwiaWQiOjEsImV4cCI6MTcyMDQwOTg1Nn0.OmpQBJoP50BjjPnItLBGCmhgAVTcDzYGsTBMT7qohoE")
            .getBody(); //获取有效荷载部分

    System.out.println(claims);
}

注意几点:

1、有效信息最后要用一个Claims对象来接收

2、一个jwt令牌有时效和使用次效,你如果超过了你设置的失效期限、或者已经运行执行了一次解析jwt令牌,那么这串jwt令牌就作废了,需要你再次运行【生成jwt令牌】,然后再运行【解析jwt令牌】获取信息,否则会报错

3、【.parseClaimsJws( )】别写成了【.parseClaimsJwt( )】

二、利用jwt令牌技术校验身份

现在我们学完了最先进的jwt令牌技术,那么就来实践一下如何运用它。

1、先为了前后端请求响应方便,封装好一个jwt令牌工具类

很简单,我们前面已经知道怎么【生成】和【解析】jwt令牌了,那么在封装工具类里只要改几点:

/

1、【生成jwt令牌】的时候首先要接收一个前端传过来的装着用户有效信息的【哈希表集合】参数;并最后要把生成的令牌字符串return出去

.

2、【解析jwt令牌】的时候需要接收生成的jwt字符串;并把解析获得【有效荷载信息】return回前端

package com.czm.tliaswebmanagement.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

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

//别忘了加这个注解,让类放入IOC容器
@Component
public class JwtUtils {
    private static String signKey = "yjtlwkbz"; //定义【签名密钥】是“yjtlwkbz”
    private static Long time = (long)60*5 * 1000; //定义有效时间是5分钟

    /**
     * 接收前端有效信息,生成jwt令牌并返回给前端
     * @param claims
     * @return
     */
    public String generateJWT(Map<String,Object> claims){
        String jwt = Jwts.builder() //builder就是创建一个JWT令牌
                .signWith(SignatureAlgorithm.HS256,signKey) //设置【签名算法的类型】、【签名内容】
                .addClaims(claims) //有效荷载部分,接收哈希表集合类型,存入用户有效信息
                .setExpiration(new Date(System.currentTimeMillis() + time)) //设置有效时间
                .compact(); //把结果生成字符串

        return jwt;
    }

    /**
     * 接收前端传来的jwt令牌,解析并返回有效荷载
     * @param jwt
     * @return
     */
    public Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();

        return claims;
    }
}

2、然后完成登录接口(三层架构)代码编写

1、首先根据接口文档规定,来确定前端传入的是什么格式数据

比如这个文档,以它为例子,那么确定前端传入【JSON格式】的【用户登录信息】,那么就要用一个Emp对象(我前几篇一直用的案例,员工对象)来接收这些参数值,然后用【@RequestBody】解析JSON成对象。然后post请求跟接口是“/login”,那就【@PostMapping("/login")】

(contrller)

2、第二步,在controller层调用service、并把刚刚解析的参数Emp传给service,service调用mapping进行sql查询,根据这个【用户登录信息】参数查询完数据库之后返回结果,再一级一级返回controller,老生常谈的流程我就不细说了。

(controllerr)

(service )

(mapping)        

3、然后在controller层再用一个【新的Emp对象】接收【查询完返回的结果】,如果查询到结果就说明数据库有这个账户,那么调用【JWT工具类】为这个账户【生成一个jwt令牌】(生成jwt的逻辑,在JWT工具类已经帮我们做好了,我们只需要传一个【装有用户信息】的【Map哈希表集合类的参数】给JWT工具类就行了)

4、最后,在查询到账户的情况下,将生成含有用户信息的JWT令牌返回给前端即可;如果查不到信息,就说明账户密码有误,查无此人,那就直接返回失败。

controller的完整代码:(其他的层的就不展示了)

package com.czm.tliaswebmanagement.controller;

import com.czm.tliaswebmanagement.pojo.Emp;
import com.czm.tliaswebmanagement.pojo.Result;
import com.czm.tliaswebmanagement.service.EmpService;
import com.czm.tliaswebmanagement.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@Slf4j
//@RequestMapping("/emps")
@RestController
public class EmpController {
    @Autowired
    private EmpService empService;

    //获取JWT令牌工具类
    @Autowired
    private JwtUtils jwtUtils;
    

    /**
     * 登录接口
     */
    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        log.info("传过来的员工账号密码信息:{}",emp);

        //先调用service查找数据库有无此账户
        Emp e = empService.login(emp);

        //判断能否根据用户名、密码在数据库查到此人
        //有的话,生成属于它的令牌,并返回给他
        if(e != null){
            //因为生成jwt令牌需要的是【哈希表集合】类型,所以用一个【哈希表集合】装查到的员工信息
            Map<String , Object> claims = new HashMap<>();
            //这里经过service、mapper查询回来的员工信息e,获取出他的id、name、username作为有效荷载信息
            claims.put("id",e.getId());
            claims.put("name",e.getName());
            claims.put("username",e.getUsername());

            //调用jwt工具类生成jwt方法获取jwt令牌
            String jwt = jwtUtils.generateJWT(claims);
            //然后把jwt令牌返回给前端
            return Result.success(jwt);
        }

        //那么如果数据库都没查到这个账户,就说明账号密码输入错误,返回错误就行了
        return Result.error("登陆失败,查无此人");
    }
}

然后我要解释一下这个jwt令牌到底在哪里传输

就在一个叫【token的玩意里存着,你可以理解为【token是一张磁卡,然后 jwt 就是类似这个磁卡的信号信息,你用【token这个磁卡刷门禁、刷刷卡机的时候,把 jwt 信息传过去验证。

然后这个【token可以放请求体、也可以是请求头,不过一般都是放请求头

那么现在前端只要登陆成功,就已经能获取到后端为之生成的jwt令牌了,现在只需要下次再携带这个jwt令牌发送请求,后端就可针对这个令牌进行判断:是否给这个用户放行使用软件、网页了,那么这就涉及到【过滤器filter】和【拦截器Interceptor】,下一篇再讲

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

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

相关文章

Suricata引擎二次开发之命中规则定位

二开背景 suricata是一款高性能的开源网络入侵检测防御引擎&#xff0c;旨在检测、预防和应对网络中的恶意活动和攻击。suricata引擎使用多线程技术&#xff0c;能够快速、准确地分析网络流量并识别潜在的安全威胁&#xff0c;是众多IDS和IPS厂商的底层规则检测模块。 前段时间…

css实现每个小盒子占32%,超出就换行

代码 <div class"visitors"><visitor class"item" v-for"(user,index) in userArr" :key"user.id" :user"user" :index"index"></visitor></div><style lang"scss" scoped&…

openWrt(4) - uci

uci show 1) uci show - 查看所有配置文件列表 2)查看特定配置文件的详细信息&#xff1a; uci show network 我们以 network 为例 3&#xff09;查看特定配置项的详细信息&#xff1a; uci show network.wan 添加一个新的配置条目&#xff1a;uci add network interface …

STM32杂交版(HAL库、音乐盒、闹钟、点阵屏、温湿度)

一、设计描述 本设计精心构建了一个以STM32MP157A高性能单片机为核心控制单元的综合性嵌入式系统。该系统巧妙融合了蜂鸣器、数码管显示器、点阵屏、温湿度传感器、LED指示灯以及按键等多种外设模块&#xff0c;形成了一个功能丰富、操作便捷的杂交版智能设备。通过串口…

Android APT实战

Android开发中,注解平时我们用的比较多,也许我们会比较好奇,注解的背后是如何工作的,这篇文章帮大家一步步创建一个简单的注解处理器。 简介 APT(Annotation Processing Tool)即注解处理器,在编译的时候可以处理注解然后搞一些事情,也可以在编译时生成一些文件之类的。…

Linux 命令探秘:揭秘那些有趣的小命令

目录 1.发现隐藏在终端的惊喜小命令 2.小火车 1.安装EPEL 2.小火车出发准备 3.输入命令 3.linux_logo 1.安装linux_logo 2.输入命令 3.linux_logo介绍 4.牛讲话 1.安装命令 2.输入命令 5. figlet 1.安装命令 2.输入命令 “如果您在解决类似问题时也遇到了困…

stm32学习笔记---I2C通信协议(理论部分)

目录 串口通信协议和I2C通信协议的联系 同步和异步的区别 I2C通信 硬件电路 指定地址写的流程 当前地址读的流程 指定地址读的流程 声明&#xff1a;本专栏是本人跟着B站江科大的视频的学习过程中记录下来的笔记&#xff0c;我之所以记录下来是为了方便自己日后复习。如…

软航文档控件VUE示例运行及控件替换方法记录

目录 示例运行 步骤一、npm install 步骤二、npm run dev 软航文档控件替换 附 vue小白记录一下软航文档控件VUE示例的运行方法以及示例中控件的替换过程。 示例运行 在已经安装好VUE环境的电脑上&#xff0c;VUE环境部署可以参考另一篇&#xff1a;配置VUE环境过程中 …

数据结构复习计划之复杂度分析(时间、空间)

第二节&#xff1a;算法 时间复杂度和空间复杂度 算法(Algorithm)&#xff1a;是对特定问题求解方法(步骤)的一种描述&#xff0c;是指令的有限序列&#xff0c;其中每一条指令表示一个或多个操作。 算法可以有三种表示形式&#xff1a; 伪代码 自然语言 流程图 算法的五…

【正点原子i.MX93开发板试用连载体验】为什么模型不能运行在NPU上

本文最早发表于电子发烧友论坛&#xff1a;【新提醒】【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制 - 正点原子学习小组 - 电子技术论坛 - 广受欢迎的专业电子论坛! (elecfans.com) 昨天提到要使模型运行的NPU上&#xff0c;必须先将其量化。如果对没有量化…

编程零基础教程,从知道什么是前端开始

本文作者&#xff1a;程序员鱼皮 免费编程学习 - 编程导航网&#xff1a;https://www.code-nav.cn 鱼小皮&#xff1a;百哥&#xff0c;我想学编程&#xff0c;应该先学啥呢&#xff1f; 老百&#xff1a;小皮&#xff0c;怎么突然想学编程了&#xff0c;不会又是三分钟热度吧&…

vue学习day06-脚手架目录文件介绍与项目运行流程、组件化开发和根组件、普通组件的注册使用-局部注册、全局注册

16、脚手架目录文件介绍与项目运行流程 &#xff08;1&#xff09;脚手架目录文件介绍 &#xff08;2&#xff09;Index.html &#xff08;3&#xff09;Main.js 17、组件化开发和根组件 &#xff08;1&#xff09;组件化 1&#xff09;概念 一个页面可以拆分成一个个组件&am…

Spring源码二十二:Bean实例化流程五

上一篇Spring源码二十一&#xff1a;Bean实例化流程四&#xff0c;咱们主要分析里createBeanInstance方法Spring给我们提供给的FactoryMethod方法&#xff0c;举例说明了factoryMethod属性如何使用&#xff0c;同时简单讨论了具体实现逻辑。 这一篇咱们将进入反射实例化Bean&am…

MySQL的事务使用

文章目录 特点JDBC使用事务 特点 事务的基本属性ACID&#xff1a; 数据库事务的ACID特性是指保证数据库在执行事务操作时能够可靠和正确的四个基本属性。ACID是原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isol…

科研绘图之tSNE图

t-SNE&#xff08;t-Distributed Stochastic Neighbor Embedding&#xff0c;t分布随机邻域嵌入&#xff09;是一种用于数据降维和可视化的算法。它可以将高维数据映射到二维或三维空间&#xff0c;同时尽可能地保留数据点之间的局部关系。t-SNE特别适用于探索数据的内部结构和…

C语言 指针和数组——指针数组的应用:命令行参数

目录 命令行参数 演示命令行参数与main函数形参间的关系 命令行参数  什么是 命令行参数&#xff08; Command Line Arguments &#xff09;&#xff1f;  GUI 界面之前&#xff0c;计算机的操作界面都是字符式的命令行界面 &#xff08; DOS 、 UNIX 、 Linux &…

IEPE数据采集卡的作用说明

IEPE指的是一种自带电量放大器或电压放大器的加速度传感器。IEPE是压电集成电路的缩写。因为由加速度传感器产生的电量是很小的&#xff0c;因此传感器产生的电信号很容易受到噪声干扰&#xff0c;需要用灵敏的电子器件对其进行放大和信号调理。IEPE中集成了灵敏的电子器件使其…

连锁行业观察:一线门店设备如何运维?化“管理”为“服务”

连锁零售行业的数字化发展&#xff0c;离开不了大量智能设备的支撑&#xff0c;比如我们日常见到的各种门店互动终端、自助收银设备、无人值守售货机等等。 由于连锁行业的特性&#xff0c;这些设备往往位置分散&#xff0c;数量众多&#xff0c;难以集中管理。一旦这些设备遇…

ARM功耗管理之多核处理器启动

安全之安全(security)博客目录导读 思考&#xff1a;SecureBoot&#xff1f;多核处理器启动流程&#xff1f;PSCI启动方式&#xff1f; 一般嵌入式系统使用的都是对称多处理器&#xff08;Symmetric Multi-Processor, SMP&#xff09;系统&#xff0c;包含了多个cpu, 这几个cp…

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

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv8改进有效…