Spring security之JWT

JWT

这里写目录标题

  • JWT
  • 一级目录
    • 二级目录
      • 三级目录
      • 1.什么是JWT
    • 2.JWT的组成部分
    • 3.编码/解码
    • 4.特点
    • 5. 为什么使用JWT
      • 5.1传统的验证方式
    • 5.2基于JWT的验证方式
    • 6.JWT进行登录验证
      • 6.1依赖安装
      • 6.2编写UserDetailServiceImpl类
      • 6.3编写UserDetailsImpl类
      • 6.4 实现config.SecurityConfig类
      • 6.5实现utils.JwtUtil类
      • 6.6实现config.filter.JwtAuthenticationTokenFilter类
      • 6.7配置config.SecurityConfig类
    • 7.常见的面试题

一级目录

二级目录

三级目录

1.什么是JWT

  • 全称 JSON Web Token,简称JWT,是一个基于RFC7519的爱看i发数据标准,定义了一种宽松且紧凑的数据组合方式
  • 是一种加密后的数据载体,可在各应用之间进行数据传输
  • 最常应用于授权认证,一旦用户登录,每个请求都将包含JWT,系统每次处理用户请求都会及逆行JWT安全检验,通过之后在进行处理

2.JWT的组成部分

JWT由3部分组成,使用.拼接

  • Header

    • 声明类型,默认JWT
    • 声明加密算法,HMAC,RSA,ECDSA等常用算法
    {
    "alg": "HS256",
    "typ": "JWT"
    }
    
    • alg:表示签名的算法,默认HMAC SHA256(写成HS256)
    • type:表示声明类型,默认JWT

    使用Base64编码后构成JWT,的第一部分

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    
  • Payload

    • 存放有效信息的地方

    标准载荷

    建议但不强制使用

    标准载荷介绍
    iss (issuer)签发人(谁签发的)
    exp (expiration time)过期时间,必须要大于签发时间
    sub (subject)主题(用来做什么)
    aud (audience)受众(给谁用的)比如:http://www.xxx.com
    nbf (Not Before)生效时间
    iat (Issued At)签发时间
    jti (JWT ID)编号,JWT 的唯一身份标识

    自定义载荷

    可以添加任何信息,一般添加用户的相关信息或业务所需要的必要信息。但不建议添加敏感信息

    {
     //第一部分用户信息
     "user_info": [
       {
         "id": "1"//id
       },
       {
         "name": "dafei"//姓名
       },
       {
         "age": "18"//年龄
       }
     ],
     "iat": 1681571257,//签发时间
     "exp": 1682783999,//过期时间
     "aud": "xiaofei",//受众
     "iss": "dafei",//签发人
     "sub": "alluser"//主题
    }
    

    使用Base64编码后构成了第二部分

    eyJ1c2VyX2luZm8iOlt7ImlkIjoiMSJ9LHsibmFtZSI6ImRhZmVpIn0seyJhZ2UiOiIxOCJ9XSwiaWF0IjoxNjgxNTcxMjU3LCJleHAiOjE2ODI3ODM5OTksImF1ZCI6InhpYW9mZWkiLCJpc3MiOiJkYWZlaSIsInN1YiI6ImFsbHVzZXIifQ
    
  • signature

    • 私钥:只有服务器才知道
    • 公钥:按照以下算法产生
    signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
    

    用Base64编码,构成第三部分

    将三部分拼接成一个字符串,便是jwt了

由于密钥的存在,即使调用方修改了前两部分内容,在验证不放呢会出现签名不一致的情况,保证了全性

3.编码/解码

编码工具

https://tooltt.com/jwt-encode/

解码工具

https://tool.box3.cn/jwt.html

4.特点

优点

  • 无状态

    JWT 不需要在服务端存储任何状态,客户端可以携带 JWT 来访问服务端,从而使服务端变得无状态。这样,服务端就可以更轻松地实现扩展和负载均衡。

  • 自定义

    JWT 的载荷部分可以自定义,可以存储任何 JSON 格式的数据。这意味着我们可以使用 JWT 来实现一些自定义的功能,例如存储用户喜好、配置信息等等。

  • 扩展性强

    JWT 有一套标准规范,因此很容易在不同平台和语言之间共享和解析。此外,开发人员可以根据需要自定义声明(claims)来实现更加灵活的功能。

  • 调试性好

    由于 JWT 的内容是以 Base64 编码后的字符串形式存在的,因此非常容易进行调试和分析。

缺点

  • 安全性取决于密钥管理

    JWT 的安全性取决于密钥的管理。如果密钥被泄露或者被不当管理,那么 JWT 将会受到攻击。因此,在使用 JWT 时,一定要注意密钥的管理,包括生成、存储、更新、分发等等。

  • 无法撤销

    由于 JWT 是无状态的,一旦 JWT 被签发,就无法撤销。如果用户在使用 JWT 认证期间被注销或禁用,那么服务端就无法阻止该用户继续使用之前签发的 JWT。因此,开发人员需要设计额外的机制来撤销 JWT,例如使用黑名单或者设置短期有效期等等。

  • 需要缓存到客户端

    由于 JWT 包含了用户信息和授权信息,一般需要客户端缓存,这意味着 JWT 有被窃取的风险。

  • 载荷大小有限制

    由于 JWT 需要传输到客户端,因此载荷大小也有限制。一般不建议载荷超过 1KB,会影响性能。

5. 为什么使用JWT

5.1传统的验证方式

  • 传统的认证方式,是基于Session的认证

Cookie和Session

  • Cookie:一种让程序员在本地存储数据的能力,让数据在客户端这边更持久化
  • Cookie里面存的是键值对的格式数据,键值对用“;”分割,键和值之间用“,”分割

登录的认证

Session

  • session是在服务器的一种机制,因为cookie是客户端保存的数据,而这些数据又是跟用户强烈相关联的,显然保存在客户端这边就不太合适(太多,也占资源),所以把数据都保存在服务器这边就比较的合适;保存的方式就是通过session的方式来进行保存的。键值对结构

  • 使用之后,客户端只需要保存 sessionId就可以了,后续的请求带上 sessionId,服务器就会根据 sessionId 就会找到对应的用户数据详细的信息。

认证流程

image-20230808162008667

  1. 客户端:发起一个认证请求
  2. 服务器端:认证通过存储一个session的用户信息;把sessionid写入客户端cookie
  3. 客户端再次访问:自动携带sessionid,找到对应session

问题分析

  • 通常来说session保存在服务器的内存中,随着认证用户增多,会加大服务器的开销

  • 因为session保存在服务器内存中,所以对于分布式应用来说,限制了应用的扩展能力

  • cookie被截取,会容易受造跨站请求伪造攻击

  • 在前后端分离的系统中更加痛苦,比如后端是集群分布,需要设计共享机制、

5.2基于JWT的验证方式

image-20230808165551764

  1. 客户端: 发起一个请求认证,带有用户名,密码
  2. 服务器:认证通过,生成JWT,响应给客户端
  3. 客户端:将Token保存到本地

日后客户端再次认证

  1. 客户端:携带JWT发送给服务器
  2. 服务器:进行验证,通过,展示数据;否则返回错误信息

6.JWT进行登录验证

6.1依赖安装

  • spring-boot-starter-security
  • jjwt-api
  • jjwt-impl
  • jjwt-jackson

meaven仓库地址:Maven Repository: Search/Browse/Explore (mvnrepository.com)

6.2编写UserDetailServiceImpl类

  • 继承UserDetailsService接口
  • 作用:接入数据库信息,主要是用来根据用户名查找用户
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("username",username);
        User user=userMapper.selectOne(queryWrapper);
        if(user==null){
            throw new RuntimeException("用户不存在");
        }
        return new UserDetailsImpl(user);
    }
}

6.3编写UserDetailsImpl类

  • 使用户的一个接口类,用来判断用户的密码,权限是否失效,过期。
package com.kob.backend.service.impl.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import  com.kob.backend.pojo.User;

@Data
@AllArgsConstructor
@NoArgsConstructor

public class UserDetailsImpl implements UserDetails {
    private User user;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

6.4 实现config.SecurityConfig类

  • 用来实现用户密码的加密存储
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

6.5实现utils.JwtUtil类

  • 是jwt工具类的一种
  • 作用:用来创建,解析jwt token
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtUtil {
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天
    public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }

        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)
                .setSubject(subject)
                .setIssuer("sg")
                .setIssuedAt(now)
                .signWith(signatureAlgorithm, secretKey)
                .setExpiration(expDate);
    }

    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }

    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(jwt)
                .getBody();
    }
}

6.6实现config.filter.JwtAuthenticationTokenFilter类

  • 进行jwt token的验证,如果成功,将用户信息注入上下文中
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private UserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");

        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        token = token.substring(7);

        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        User user = userMapper.selectById(Integer.parseInt(userid));

        if (user == null) {
            throw new RuntimeException("用户名未登录");
        }

        UserDetailsImpl loginUser = new UserDetailsImpl(user);
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }
}


6.7配置config.SecurityConfig类

  • 放行登录,注册页面
import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
            //在这里方形的登录注册页面,填写访问地址
                .antMatchers("/user/account/token/", "/user/account/register/").permitAll()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
}


7.常见的面试题

问:什么是JWT?解释一下它的结构。

JWT是一种开放标准,用于在网络上安全地传输信息。它由三部分组成:头部、载荷和签名。头部包含令牌的元数据,载荷包含实际的信息(例如用户ID、角色等),签名用于验证令牌是否被篡改。

问:JWT的优点是什么?它与传统的session-based身份验证相比有什么优缺点?

JWT的优点包括无状态、可扩展、跨语言、易于实现和良好的安全性。相比之下,传统的session-based身份验证需要在服务端维护会话状态,使得服务端的负载更高,并且不适用于分布式系统。

问:在JWT的结构中,分别有哪些部分?每个部分的作用是什么?

JWT的结构由三部分组成:头部、载荷和签名。头部包含令牌类型和算法,载荷包含实际的信息,签名由头部、载荷和密钥生成。

问:JWT如何工作?从开始到验证过程的完整流程是怎样的?

JWT的工作流程分为三个步骤:生成令牌、发送令牌、验证令牌。在生成令牌时,服务端使用密钥对头部和载荷进行签名。在发送令牌时,将令牌发送给客户端。在验证令牌时,客户端从令牌中解析出头部和载荷,并使用相同的密钥验证签名。

问:什么是JWT的签名?为什么需要对JWT进行签名?如何验证JWT的签名?

JWT的签名是由头部、载荷和密钥生成的,用于验证令牌是否被篡改。签名使用HMAC算法或RSA算法生成。在验证JWT的签名时,客户端使用相同的密钥和算法生成签名,并将生成的签名与令牌中的签名进行比较。

问:什么是JWT的令牌刷新?为什么需要这个功能?

令牌刷新是一种机制,用于解决JWT过期后需要重新登录的问题。在令牌刷新中,服务端生成新的JWT,并将其发送给客户端。客户端使用新的JWT替换旧的JWT,从而延长令牌的有效期。

问:JWT是否加密?如果是,加密的部分是哪些?如果不是,那么它如何保证数据安全性?

JWT本身并不加密,但可以在载荷中包含敏感信息。为了保护这些信息,可以使用JWE(JSON Web Encryption)对载荷进行加密。如果不加密,则需要在生成JWT时确保不在载荷中包含敏感信息。

问:在JWT中,如何处理Token过期的问题?有哪些方法可以处理?

JWT过期后,客户端需要重新获取新的JWT。可以通过在JWT中包含过期时间或使用refresh token等机制来解决过期问题。

问:JWT和OAuth2有什么关系?它们之间有什么区别?

JWT和OAuth2都是用于身份验证和授权的开放标准。JWT是一种身份验证机制,而OAuth2是一种授权机制。JWT用于在不同的系统中安全地传输信息,OAuth2用于授权第三方应用程序访问受保护的资源。

问:JWT在什么场景下使用较为合适?它的局限性是什么?

JWT在单体应用或微服务架构中的使用比较合适。它的局限性包括无法撤销、令牌较大、无法处理并发等问题。在需要针对每次请求进行访问控制或需要撤销令牌的情况下,JWT可能不是最佳选择。

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

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

相关文章

算法与数据结构(二十二)动态规划解题套路框架

动态规划解题套路框架 此文只在个人总结 labuladong 动态规划框架&#xff0c;仅限于学习交流&#xff0c;版权归原作者所有&#xff1b; 动态规划问题&#xff08;Dynamic Programming&#xff09;应该是很多读者头疼的&#xff0c;不过这类问题也是最具有技巧性&#xff0c…

替换开源LDAP,某科技企业用宁盾目录统一身份,为业务敏捷提供支撑

客户介绍 某高科技企业成立于2015年&#xff0c;是一家深耕于大物流领域的人工智能公司&#xff0c;迄今为止已为全球16个国家和地区&#xff0c;120余家客户打造智能化升级体验&#xff0c;场景覆盖海陆空铁、工厂等货运物流领域。 该公司使用开源LDAP面临的挑战 挑战1 开源…

【用于全变分去噪的分裂布雷格曼方法】实施拆分布雷格曼方法进行总变异去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

docker菜谱

DockerHub&#xff1a;https://hub.docker.com/ 记录docker常用软件安装&#xff0c;欢迎大家来投稿。&#x1f60e;&#x1f60e;&#x1f60e; 文章目录 1. Redis2. MariaDB 1. Redis dockerhub:https://hub.docker.com/_/redis 1、下载redis镜像&#xff1a; docker pull r…

【MFC】05.MFC第一大机制:程序启动机制-笔记

MFC程序开发所谓是非常简单&#xff0c;但是对于我们逆向人员来说&#xff0c;如果想要逆向MFC程序&#xff0c;那么我们就必须了解它背后的机制&#xff0c;这样我们才能够清晰地逆向出MFC程序&#xff0c;今天这篇文章就来带领大家了解MFC的第一大机制&#xff1a;程序启动机…

echarts 图表饼状图 实例

效果图&#xff1a; 代码&#xff1a; draw(data1, data2) {var option {// backgroundColor: rgb(10,36,68),color: [#F19611 ,#0095FE,#162D86,#0096FF,#05F8FF,#FFD985,#FACDAA,#F4A49E,#EE7B91,#E85285,#BE408C,#942D93,#171E6D,#1E3388,#27539B,#3073AE,#3993C2,#42B3D…

JVM如何调优

一、JVM内存模型及垃圾收集算法 1.根据Java虚拟机规范&#xff0c;JVM将内存划分为&#xff1a; New&#xff08;年轻代&#xff09; Tenured&#xff08;年老代&#xff09; 永久代&#xff08;Perm&#xff09; 其中New和Tenured属于堆内存&#xff0c;堆内存会从JVM启动参…

day0808

1.单链表实现约瑟夫环 #include "joseph.h" LoopLink list_create(int m) {LoopLink L (LoopLink)malloc(sizeof(Node));if(NULLL){printf("内存创建失败\n");return 0;}LoopLink qL;for(int i1; i<m; i){LoopLink p (LoopLink)malloc(sizeof(Node));…

MyBatis-XML映射文件

XML映射文件 规范 XML映射文件的名称与Mapper接口名称一致&#xff08;EmpMapper对应EmpMpper.xml&#xff09;&#xff0c;并且将XML映射文件和Mapper接口放置在相同包下&#xff08;同包同名&#xff09; ​​​ 在maven项目结构中所有的配置文件都在resources目录之下&…

二级python和二级c哪个简单,二级c语言和二级python

大家好&#xff0c;小编为大家解答二级c语言和二级office一起报可以吗的问题。很多人还不知道计算机二级c语言和python哪个好考&#xff0c;现在让我们一起来看看吧&#xff01; 介绍Python有很多库和使用Qt编写的接口,这自然创建c调用Python的需求。一路摸索,充满艰辛的添加头…

docker compose一键部署lnmt环境

创建docker compose 目录 [rootlocalhost ~]# mkdir -p /compose_lnmt 编写nginx的dockerfile文件 创建目录 [rootlocalhost compose_lnmt]# mkdir -p nginx 编写nginx配置文件 [rootlocalhost nginx]# vim nginx.conf user root; #运行身份#nginx自动设置进程…

Learning Rich Features for Image Manipulation Detection阅读笔记

文章目录 Abstract3.3. 双线性池 Abstract 图像篡改检测与传统的语义目标检测&#xff08;semantic object detection&#xff09;不同&#xff0c;因为它更关注篡改伪影&#xff08;tampering artifacts&#xff09;而不是图像内容&#xff0c;这表明需要学习更丰富的特征。我…

flutter开发实战-实现css线性渐变转换flutter渐变LinearGradient功能

flutter开发实战-实现css线性渐变转换flutter渐变LinearGradient功能 在之前项目开发中&#xff0c;遇到更换样式&#xff0c;由于从服务器端获取的样式均为css属性值&#xff0c;需要将其转换成flutter类对应的属性值。这里只处理线性渐变linear-gradient 比如渐变 “linear-…

Unity 基础函数

Mathf&#xff1a; //1.π-PI print(Mathf.PI); //2.取绝对值-Abs print(Mathf.Abs(-10)); print(Mathf.Abs(-20)); print(Mathf.Abs(1)); //3.向上取整-Ce il To In t float f 1.3f; int i (int)f; …

什么是Milvus

原文出处&#xff1a;https://www.yii666.com/blog/393941.html 什么是Milvus Milvus 是一款云原生向量数据库&#xff0c;它具备高可用、高性能、易拓展的特点&#xff0c;用于海量向量数据的实时召回。 Milvus 基于 FAISS、Annoy、HNSW 等向量搜索库构建&#xff0c;核心是…

java+springboot+mysql校园通讯录管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的校园通讯录管理系统&#xff0c;系统包含超级管理员、管理员、用户角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理&#xff1b;部门管理&#xff1b;用户管理&#xff1b;留言管理&#xff1b;公…

抽象工厂模式-java实现

介绍 抽象工厂模式基于工厂方法模式引入了“产品族”的概念&#xff0c;即我们认为具体产品是固定的&#xff0c;具体产品存在等级之分&#xff0c;比如我们常说的手机&#xff0c;有“青春版”&#xff0c;“至尊版”&#xff0c;“至臻版”。一个产品有多个版本族。这时候&a…

ROS实现自定义信息以及使用

常见的消息包 消息包定义一般如下&#x1f447; &#xff08;1&#xff09;创建包和依赖项 &#xff08;2&#xff09;在新建的qq_msgs的包新建msgs的文件夹&#xff0c;在该文件夹里面新建Carry.msg类型的文件。 其实&#xff0c;Carry.msg就是你自己定义的消息类型&am…

vue3项目中引入dialog插件,支持最大最小化、还原、拖拽

效果图&#xff1a; 上图是layui-vue组件库中的layer插件&#xff0c;我的项目使用的是element-plus组件库&#xff0c;在用不上layui组件库的情况下&#xff0c;就单独引入layui/layer-vue这个弹层插件就可以了 npm地址&#xff1a;layui/layer-vue - npm layui-vue组件库地址…

数仓架构模型设计参考

1、数据技术架构 1.1、技术架构 1.2、数据分层 将数据仓库分为三层&#xff0c;自下而上为&#xff1a;数据引入层&#xff08;ODS&#xff0c;Operation Data Store&#xff09;、数据公共层&#xff08;CDM&#xff0c;Common Data Model&#xff09;和数据应用层&#xff…