SpringBoot第 17 讲:SpringBoot+JWT

news2024/11/24 18:43:25

关于JWT的讲解请参考:SpringCloud第14讲:(番外篇)JWT

一、项目演示

没有登陆直接请求列表接口,系统会要求先进行登录
在这里插入图片描述

登录成功后请求列表接口,可以正常响应数据

在这里插入图片描述

二、后台开发

2.1、pom.xml

添加redis、jwt坐标

<!--jwt坐标-->
<dependency>
	<groupId>com.nimbusds</groupId>
	<artifactId>nimbus-jose-jwt</artifactId>
	<version>9.11.1</version>
</dependency>

2.2、application.yml

配置redis

server:
  port: 8070
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.8:3306/test_plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    username: root
    password: Aa123123.
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
  redis:
    port: 6379 # Redis服务器连接端口
    host: 127.0.0.1 # Redis服务器地址
    database: 0 # Redis数据库索引(默认为0)
    password: # Redis服务器连接密码(默认为空)
    timeout: 5000ms # 连接超时时间(毫秒)
    jedis:
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8 # 连接池中的最大空闲连接
        min-idle: 0 # 连接池中的最小空闲连接
mybatis-plus:
  type-aliases-package: demo.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      table-prefix: t_
      id-type: auto
  mapper-locations: classpath:mappers/*.xml

2.3、RedisConfig

解决redisTemplate保存数据出现乱码问题

package demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        //创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //设置连接工厂
        template.setConnectionFactory(connectionFactory);
        //创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        //设置value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashKeySerializer(jsonRedisSerializer);
        //返回
        return template;
    }
}

2.4、实体层开发

package demo.entity;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String userName;
    private String passwd;
}
package demo.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

@Data
public class Article {
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;
    private String title;
    private String logo;
    private String descn;
    private Date createTime;
    private Long cid;
}

2.5、数据库访问层开发

UserMapper

package demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import demo.entity.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper extends BaseMapper<User> {
}

ArticleMapper

package demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import demo.entity.Article;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface ArticleMapper extends BaseMapper<Article> {
}

2.6、控制层

package demo.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import demo.entity.Article;
import demo.mapper.ArticleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ArticleController {

    @Autowired
    private ArticleMapper articleMapper;

    @GetMapping("/list")
    public Page<Article> findAll(Long pageIndex, Long pageSize){
        Page<Article> page = articleMapper.selectPage(new Page(pageIndex, pageSize), null);
        return page;
    }
}

在登陆成功之后,生成token并保存到redis

package demo.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import demo.entity.User;
import demo.mapper.UserMapper;
import demo.utils.Cast;
import demo.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired(required = false)
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping("/login")
    public Object login(@RequestBody User vo){
        String token = null;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .eq("user_name", vo.getUserName())
                .eq("passwd", vo.getPasswd());
        User usr = userMapper.selectOne(queryWrapper);
        if(usr != null){
            try {
                //登陆成功,生成token保存到reids中
                token = jwtUtils.createJwtToken(usr.getUserName());
                redisTemplate.boundValueOps(Cast.JWT_TOKEN_PREFIX+usr.getUserName()).set(token, 30, TimeUnit.MINUTES);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return token;
    }
}

2.7、工具类

公有常量类

package demo.utils;

/**
 * 公有常量
 */
public class Cast {
    public static final String JWT_HEADER_KEY = "x-jwt-token"; //请求头中的jwt
    public static final String JWT_TOKEN_PREFIX = "login:token:"; //保存在redis中用于校验jwt的key的前缀
}

Jwt工具类

package demo.utils;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;

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

/**
 * jwt工具类
 */
@Component
public class JwtUtils {
    //使用uuid生成密钥
    private  final String secret= UUID.randomUUID().toString();
    //用户数据的key
    private  final String usernameKey="usernameKey";

    /**
     * 生成token
     * @param username 用户名
     * @return
     */
    public  String createJwtToken(String username) throws Exception {
        //创建头部对象
        JWSHeader jwsHeader =
                new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                        .type(JOSEObjectType.JWT) // 静态常量
                        .build();

        //创建载荷
        Map<String,Object> map=new HashMap<String,Object>();
        map.put(usernameKey, username);
        Payload payload= new Payload(map);

        //创建签名器
        JWSSigner jwsSigner = new MACSigner(secret);//密钥

        //创建签名
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷
        jwsObject.sign(jwsSigner);//再+签名部分


        //生成token字符串
        return jwsObject.serialize();
    }

    /**
     * 验证jwt token是否合法
     * @param jwtStr
     * @return
     */
    @SneakyThrows
    public  boolean verify(String jwtStr) {
        JWSObject jwsObject=JWSObject.parse(jwtStr);
        JWSVerifier jwsVerifier=new MACVerifier(secret);
        return jwsObject.verify(jwsVerifier);
    }

    /**
     * 从token中解析出用户名
     * @param jwtStr
     * @return
     */
    @SneakyThrows
    public String getUserNameFormJwt(String jwtStr){
        JWSObject jwsObject=JWSObject.parse(jwtStr);
        Map<String,Object> map=jwsObject.getPayload().toJSONObject();
        return (String) map.get(usernameKey);
    }
}

2.8、拦截器

在拦截器中拦截HttpRequest请求,执行以下业务逻辑

  • 1、判断请求头中是否携带token
  • 2、判断token是否有效
  • 3、判断token中保存的用户信息是否有效
  • 4、判断请求头的token和redis中的token是否一致
  • 5、请求拦截或token续期
package demo.interceptor;

import demo.entity.User;
import demo.mapper.UserMapper;
import demo.utils.Cast;
import demo.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

@Configuration
@Slf4j
public class MyInterceptor extends WebMvcConfigurerAdapter {

    @Autowired(required = false)
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private JwtUtils jwtUtils;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        HandlerInterceptor handlerInterceptor = new HandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                boolean isOk = false;
                String uri = request.getRequestURI();
                //静态资源放行
                if(uri.contains("js") || uri.contains("html") || uri.contains("css") || uri.contains("login") || uri.equals("/favicon.ico") || uri.equals("/error")){
                    isOk = true;
                } else { //用户鉴权
                    String token = request.getHeader(Cast.JWT_HEADER_KEY);
                    //验证请求头中的token是否合法
                    if(token != null && jwtUtils.verify(token)){
                        // token合法则取出token中保存的账号
                        String account = jwtUtils.getUserNameFormJwt(token);

                        //根据账号验证redis中保存的token是否存在
                        if(redisTemplate.hasKey(Cast.JWT_TOKEN_PREFIX+account)){

                            String jwtToken = (String) redisTemplate.opsForValue().get(Cast.JWT_TOKEN_PREFIX+account);
                            //判断是否是同一个token
                            if(token.equals(jwtToken)){
                                //对token进行续期
                                redisTemplate.opsForValue().set(Cast.JWT_TOKEN_PREFIX+account, jwtToken, 30, TimeUnit.MINUTES);
                                isOk = true;
                            }
                        }
                    }
                    if(!isOk){
                        response.setContentType("application/json;charset=utf8");
                        PrintWriter writer = response.getWriter();
                        writer.print("{\"code\":403, \"msg\":\"没有访问权限\"}");
                        isOk = false;
                    }
                }

                return isOk;
            }
        };
        registry.addInterceptor(handlerInterceptor);
    }
}

三、前端开发

3.1、登陆页面login.html

登陆成功后将token保存到localStorage中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="assets/bootstrap-3.3.7-dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="assets/jquery-3.5.1.min.js"></script>
    <script src="assets/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
    <script src="assets/vue.min-v2.5.16.js"></script>
    <script src="assets/axios.min.js"></script>
</head>
<body>
    <div class="container" id="app">
        <div class="row">
            <div class="col-md-4 col-md-push-4">
                <h3 style="margin-top: 30px;">用户登录</h3>
                <div style="margin-top: 30px;">
                    <label for="uname">用户名:</label>
                    <input id="uname" class="form-control" type="text" v-model="userName">
                </div>
                <div style="margin-top: 15px;">
                    <label for="upasswd">密码:</label>
                    <input id="upasswd" class="form-control" type="password" v-model="passwd">
                </div>
                <div style="margin-top: 15px;">
                    <button class="btn btn-primary" @click="doLogin">登录</button>
                </div>
            </div>
        </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                userName: null,
                passwd: null
            },
            methods: {
                doLogin(){
                    axios.post("/user/login", {
                        userName: this.userName,
                        passwd: this.passwd
                    }).then(res => {
                        console.log(res.data)
                        let token = res.data;
                        if(token == false){
                            alert("用户名或密码错误!");
                        } else {
                            localStorage.setItem("token", res.data)
                            window.location.href = "index.html"
                        }
                    });
                }
            }
        });
    </script>
</body>
</html>

3.2、新闻列表页index.html

在列表页请求后台接口,需要在header中携带token

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="assets/bootstrap-3.3.7-dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="assets/jquery-3.5.1.min.js"></script>
    <script src="assets/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
    <script src="assets/vue.min-v2.5.16.js"></script>
    <script src="assets/axios.min.js"></script>
</head>
<body>
    <div class="container" id="app">
        <table class="table table-striped">
            <caption>文章列表</caption>
            <thead>
            <tr>
                <th align="center">编号</th>
                <th align="center">标题</th>
                <th align="center">描述</th>
                <th align="center">发布时间</th>
                <th align="center">操作</th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="art in articleList">
                <td>{{art.id}}</td>
                <td>{{art.title}}</td>
                <td>{{art.descn}}</td>
                <td>{{art.createTime}}</td>
                <td align="center">
                    <button class="btn btn-link" style="margin-right: 10px;">修改</button>
                    <button class="btn btn-link">删除</button>
                </td>
            </tr>
            </tbody>
        </table>
        <ul class="pagination" v-for="p in pageCnt">
            <li v-if="p == pageIndex" class="active"><a href="#" @click="doGo(p)">{{p}}</a></li>
            <li v-else="p == pageIndex"><a href="#" @click="doGo(p)">{{p}}</a></li>
        </ul>
    </div>

    <script>
        new Vue({
            el: '#app',
            data: {
                articleList: null,
                //用于分页
                pageIndex: 1, //页码
                pageSize: 3, //每页显示的条数
                pageTotal: 0, //总条数
                pageCnt: 0 //总页数
            },
            methods: {
                requestArticleList(){
                    axios.get("/list", {
                            params: {
                                pageIndex: this.pageIndex,
                                pageSize: this.pageSize
                            },
                            headers: {'x-jwt-token': localStorage.getItem("token")}
                        }
                    ).then(res => {
                        console.log(res.data)
                        if(res.data.code == 403){
                            alert("请先登录");
                            window.location.href = "login.html"
                        } else {
                            this.articleList = res.data.records
                            this.pageCnt = res.data.pages
                            this.pageTotal = res.data.total
                            this.pageIndex = res.data.current
                            this.pageSize = res.data.size
                        }
                    })
                },
                doGo(p){
                    this.pageIndex = p
                    this.requestArticleList()
                }
            },
            created: function () {
                this.requestArticleList()
            }
        })
    </script>
</body>
</html>

四、附录

4.1、完整pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>page-helper-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>page-helper-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- Spring&SpringMVC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
        <!-- mysql-connector -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.32</version>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--jwt坐标-->
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.11.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

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

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

相关文章

迪赛智慧数——柱状图(堆叠柱状图):各年龄段人群服装消费频次

效果图 你多久买一次新衣服&#xff1f; 70后买得“精”&#xff0c;90后换得“勤”&#xff0c;90后已成为服装消费者主力&#xff01;90后中平均每周1次或更多的占比达12%。当买衣服的理由越来越感性&#xff0c;服装貌似已不再是“这届” 消费者的基础刚需。 数据源&#…

数据库系统-查询优化

文章目录 一、查询优化概述二、优化的基本思路2.1 语义优化-内容等价性2.2 语义优化-语法等价性2.3 执行优化(物理层优化)2.4 优化在整个DBMS的位置 三、逻辑查询优化3.1 逻辑优化整体思路3.2 关系代数操作次序交换的等价性定理1 连接&连接 积&积 交换律定理2 连接&…

Leetcodes刷题之删除链表的倒数N个结点和删除链表的中间的结点

吾心信其可行&#xff0c;则移山填海之难&#xff0c;终有成功之日。 --孙中山 目录 &#x1f349;一.删除链表的倒数N个结点 &#x1f33b;1.双指针 &#x1f341;2.求链表的长度 &#x1f338;二.删除链表的中间的结点 &#x1f349;一.删除链…

DataGridView 真·列头不高亮 真·列头合并

高亮BUG VB.Net&#xff0c;在 .NET Framework 4.8 的 WinForm 下(即不是 WPF 的绘图模式、也不是 Core 或 Mono 的开发框架)&#xff0c;使用 DataGridView 行模式&#xff0c;还是有个列头表现为高亮显示&#xff1a; 查找各种解决方式: 设置 ColumnHeadersDefaultCellSty…

Python3语法笔记(前篇)

文章目录 前言基础杂项变量和数据变量与运算数值字符串列表&#xff08;list&#xff09;、元组&#xff08;tuple&#xff09;和range序列类型&#xff08;sequence types&#xff09;和切片&#xff08;slicing&#xff09;集合&#xff08;set&#xff09;和字典&#xff08…

PS批量给图片加水印

一、打开PS&#xff0c;导入图片 点击菜单栏-文件-打开-载入一张需要设置文字水印的图片 二、点击菜单栏-窗口-动作&#xff0c;打开Photshop的动作组工作台窗口 先点击新建组图标新建一个组类别&#xff0c;再点击新建动作图标新建一个动作&#xff0c;新建后自动开始录制 …

DASFAA 2023|创邻周研博士分享前沿图数据库观点

4月17-20日&#xff0c;2023年第28届高级应用数据库系统国际会议&#xff08;DASFAA2023&#xff09;在天津成功举行。创邻科技CTO周研博士受邀参会&#xff0c;围绕Galaxybase国产高性能图数据库进行精彩分享。 DASFAA 2023由DASFAA指导委员会&#xff08;DASFAA Steering Co…

CVPR 2023 | 语义分割新范式:点监督遇上隐式场

密集预测(dense prediction)网络是解决诸如语义分割和图像本征分解(intrinsic decomposition)等场景理解问题的基本框架。现有工作[1-2] 通常使用像素级标注作为训练密集预测模型的监督。但是像素级别的密集标注非常昂贵&#xff0c; 对一些任务也无法给出精准的像素标注&#…

Linux-初学者系列3——虚拟光驱使用mount挂载操作

虚拟光驱使用mount挂载操作 一、mount挂载操作1、利用图形将光盘镜像文件&#xff0c;放入光驱设备中2、找到Linux光驱设备&#xff08;青色&#xff09; 1、挂载光盘和分区mount命令语法&#xff1a;示例&#xff1a;卸载&#xff08;注意事项&#xff09; 二、Linux快捷键 一…

Vue(绑定样式、条件渲染、列表渲染)

一、绑定样式 1. class绑定样式名 标签中使用 :class"xxx" xxx可以是字符串&#xff0c;对象&#xff0c;数组 字符串&#xff1a;适用于类名不确定&#xff0c;动态获取 数组&#xff1a;适用于绑定多个样式&#xff0c;个数不确定&#xff0c;字数不确定 对象&…

ProteinGAN扩展蛋白质序列空间

为了更广泛的医学应用&#xff0c;为催化任何所需的化学反应而重新设计蛋白质是蛋白质工程的重点。因此作者开发了ProteinGAN&#xff0c;这是一种基于自注意力的生成对抗网络&#xff0c;能够学习自然蛋白质序列的多样性&#xff0c;并生成功能性蛋白质序列。 来自&#xff1…

基于GEE平台的植被覆盖度(FVC)像元二分法计算

一、植被覆盖度计算方法 植被覆盖度FVC&#xff08;Fractional Vegetation Cover&#xff09;定义为单位面积内绿色植被冠层垂直投影面积所占比例。FVC是衡量地表植被状况的重要指标之一&#xff0c;也是区域生态系统环境变化的重要指示&#xff0c;对水文、生态、区域变化等都…

线程同步方式之一互斥锁

线程同步的4种方式&#xff1a;互斥锁、条件变量、读写锁、信号量 了解概念-临界资源、互斥、临界区、原子性 回想一下在信号量那部分提起过的几个概念&#xff0c;将多个执行流串行安全访问的共享资源称为临界资源&#xff0c;多个执行流中访问临界资源的代码所在的地址空间…

第4章 数据结构之“队列”

队列简介(queue) 1.一个先进先出的数据结构 2.javascript中没有这个数据结构&#xff0c;但是可以使用array实现队列的所有功能。 3.队列常用操作&#xff1a;push&#xff0c;shift&#xff0c;获取队列头部的元素&#xff1a;queue[0] const queue []// 入队&#xff1a; …

网络基础之网络传输基本流程

网络基础 此小节介绍网络基础概念 首先要明确的是 网络是层状结构&#xff01;分层->OP->解耦 网络发展&#xff1a;最早的时候&#xff0c;每台计算机之间是相互独立的。后续发展到网络互联&#xff0c;就是将多台计算机连接在一起&#xff0c;完成数据共享。 协议&…

Jmeter配置元件之csv数据文件配置

一、csv简介 csv是非常通用的一种文件格式&#xff0c;适用于批量导入数据到接口参数中&#xff0c;或者保存测试结果都可以使用csv数据文件(jmeter不仅仅支持这一种读取文件的方式)&#xff0c;csv数据一行即为数据表的一行&#xff0c;多个字段用逗号隔开。 登录接口 …

A+CLUB管理人支持计划第四期 | 香农投资

免责声明 本文内容仅对合格投资者开放&#xff01; 私募基金的合格投资者是指具备相应风险识别能力和风险承担能力&#xff0c;投资于单只私募基金的金额不低于100 万元且符合下列相关标准的单位和个人&#xff1a; &#xff08;一&#xff09;净资产不低于1000 万元的单位&…

Vue3技术7之toRaw与markRaw、customRef、provide与inject、响应式数据的判断、组合式API的优势分析

Vue3技术7 toRaw与markRawtoRawApp.vueDemo.vue markRawDemo.vue 总结 customRefApp.vue总结 provide与inject目录结构App.vueChild.vueSon.vue总结 响应式数据的判断App.vue总结 组合式API的优势配置式的API存在的问题组合式API的优势 toRaw与markRaw toRaw App.vue <te…

HDR tone mapping介绍

文章目录 HDR and tone mapping1.什么是HDR&#xff1f;2.为什么需要HDR&#xff1f;3.hdr文件格式4.tone mapping4.1 aces tone mapping4.2 Fast Bilateral Filtering for the Display of High-Dynamic-Range Images 5 参考 HDR and tone mapping 1.什么是HDR&#xff1f; 就…

nginx + springboot 实现限流

1.spring项目打成jar包后&#xff0c;运行起来 &#xff1a;例如我启动项目 ip 端口号&#xff1a;172.168.0.217:8090 2.修改nginx配置&#xff0c;增加如下配置 nginx 中有两个主要的指令可以用来配置限流&#xff1a;limit_req_zone 和 limit_req upstream myserver{serve…