SpringSecurity简单的练手项目(SpringBoot+SpringSecurity+JWT)

news2024/11/28 15:44:49

文章目录

    • 一、项目介绍
    • 二、SpringSecurity简介
        • SpringSecurity中的几个重要组件:
        • 1.SecurityContextHolder(class)
        • 2.SecurityContext(Interface)
        • 3.Authentication(Interface)
        • 4.AuthenticationManager(Interface)
        • 5.GrantedAuthority(Interface)
    • 二、整理思路
    • 三、具体实现步骤
        • 1.项目主要相关依赖
        • 2.用户表
        • 3.项目中用到的几个user相关对象
        • 4.dao层开发
        • 5.实现UserDetail接口和UserDetailService接口
        • 6.JwtAuthenticationTokenFilter
        • 7.JWTAuthenticationFilter
        • 8.service层开发
        • 9.Controller层开发
        • 10.通过SecurityConfig类对SpringSecurity进行配置
        • 11.postman测试

一、项目介绍

通过springboot整合jwt和security,以用户名/密码的方式进行认证和授权。认证通过jwt+数据库的,授权这里使用了两种方式,分别是SpringSecurity自带的hasRole方法+SecurityConfig 和 我们自定义的permission+@PreAuthorize注解。

二、SpringSecurity简介

SpringSecurity中的几个重要组件:

1.SecurityContextHolder(class)

用来存储和获取当前线程关联的 SecurityContext 对象的类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ku2An95n-1683728698707)(E:/Blog/lansg/source/img/1683186758072-ccd6a67a-c2c5-4ec7-aca3-b42404ce8277.png)]

其中有两种 SecurityContext 模式:

  • MODE_THREADLOCAL:将 SecurityContext 对象存储到当前线程中,只在当前线程中可见。多线程时,每个线程的 SecurityContext 对象都是独立的。
  • MODE_INHERITABLETHREADLOCAL:将 SecurityContext 对象存储到当前线程中,对当前线程和子线程都可见。也就是说,在当前线程中存储的 SecurityContext 对象可以被传递给子线程使用。

表示用户已通过身份验证的最简单方法就是设置 SecurityContextHolder!

2.SecurityContext(Interface)

用来存储当前已经被认证的用户,包含了当前执行操作的线程上下文信息以及用户认证和授权信息。包含Authentication。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KFfG9auG-1683728698708)(E:/Blog/lansg/source/img/1683186616655-7d1408b0-f5aa-4a06-ae7e-417d63c3fe94.png)]

3.Authentication(Interface)

存储了当前正在执行操作的用户的身份验证信息,包括用户名、密码、权限,可以作为AuthenticationManager的输入,它包含principal、credentials、authorities。

  • principal:用户的标识。通常情况下是UserDetails接口的一个实例。
  • credentials:用户的密码。多数情况下,为确保密码不被泄露,会在用户身份验证之后被清除。
  • authorities:用户拥有的权限。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sq1BhhWT-1683728698716)(E:/Blog/lansg/source/img/1683187131663-71356d21-a5e4-498c-b268-0e2620bdd1af.png)]

Authentication 接口的常用实现类有以下几种:

  • AnonymousAuthenticationToken:匿名用户的身份验证信息。
  • UsernamePasswordAuthenticationToken:用户名/密码的身份验证信息。
  • RememberMeAuthenticationToken:用于“记住我”功能的身份验证信息。
  • PreAuthenticatedAuthenticationToken:基于预先认证信息的身份验证。

通常情况下,用户在进行登录时需要通过身份验证,当身份验证成功时,就会通过 Authentication 接口封装用户的身份信息。在后续的操作中,认证后的用户可以通过 SecurityContextHolder 获取 Authentication 对象,并根据其中的信息获得用户的身份信息及相应的权限等。

对比SecurityContext和Authentication

Authentication 是一个封装了用户身份认证信息的对象,表示用户已经通过了验证。

SecurityContext 则是一个上下文类对象,用于保存和获取当前线程关联的上下文信息,包括了 Authentication 对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QYXpz36P-1683728698716)(E:/Blog/lansg/source/img/1682596753963-e83a6ebf-2ca0-414c-9601-22ec633c6103.png)]

4.AuthenticationManager(Interface)

定义了用户身份验证的api接口(例如将用户名和密码和数据库进行比对)。可以接收一个Authentication对象作为入参,验证成功后会返回已被验证的Authentication对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WR39B9kU-1683728698717)(E:/Blog/lansg/source/img/1683185823144-bb651118-ba37-4d48-a1d1-a3b82875226f.png)]

5.GrantedAuthority(Interface)

授权信息以GrantedAuthority的形式存储在Authentication对象中,GrantedAuthority接口表示一个授权(权限)对象,包含一个字符串类型的授权名字(authority name)。授权名字通常是一个表示权限的字符串,例如"ROLE_ADMIN"、"ROLE_USER"等

二、整理思路

  1. 搭建springboot项目,导入相关依赖
  2. 在数据库导入sql创建用户表
  3. 创建几个关于User的对象便于数据传输
  4. dao层开发(对用户信息增删改查)
  5. 实现UserDetails接口和UserDetailService接口
  6. 自定义实现校验token的拦截器JwtAuthenticationTokenFilter
  7. 自定义实现用户登录校验的拦截器JWTAuthenticationFilter
  8. service层开发(包括PermissionService和UserService)
  9. 创建测试Controller
  10. 实现SpringSecurity的配置类SecurityConfig
  11. 通过postman进行测试

三、具体实现步骤

1.项目主要相关依赖

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.3.12.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.3.12.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
  </dependency>
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.0</version>
  </dependency>
</dependencies>

2.用户表

创建一个名为security_jwt_demo的数据库,导入项目根目录下/db/db.sql文件即可。


3.项目中用到的几个user相关对象

(1)User实体

对应数据库中的user表

@Data
public class User {

    private Long id;
    private String username;
    private String password;
    private String permission;
    private String role;

    @Override
    public String toString() {
        return "User{" +
        "id=" + id +
        ", username='" + username + '\'' +
        ", password='" + password + '\'' +
        ", permission='" + permission + '\'' +
        ", role='" + role + '\'' +
        '}';
    }
}

(2)LoginUser

UserDetails的实现类,使用用户名/密码验证时需要用到其作为返回值。这个类中包含了用户名、密码和当前登录用户所具备的权限。

@Data
public class LoginUser implements UserDetails {

    private Long id;
    private String username;
    private String password;
    //通过自定义方式进行授权
    private Set<String> permissions = new HashSet<String>();

    //通过springSecurity进行授权
    private Collection<? extends GrantedAuthority> authorities;

    public LoginUser(){}

    public LoginUser(User user,Collection<? extends GrantedAuthority> authorities) {
        id = user.getId();
        username = user.getUsername();
        password = user.getPassword();
        permissions.add(user.getPermission());
        this.authorities = authorities;

    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

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

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

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

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

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

(3)UserVO

主要用来登录时从输入流中获取登录用户的信息,对应了前端传递的参数,包括用户名、密码、记住我等属性。

@Data
public class UserVO {
    private String username;
    private String password;
    private Integer rememberMe;
}

4.dao层开发

创建mapper接口并配置对应的mapper.xml

@Mapper
public interface UserMapper {

    //根据用户名获取用户
    User getByName(String username);

    //根据用户id获取用户权限
    List<String> getPermissionById(Long id);

    //新增一个用户
    int insertUser(User user);
}

5.实现UserDetail接口和UserDetailService接口

首先说一下为什么要实现这两个接口。

SpringSecurity提供了多种身份验证的方式,在这里我们使用的是用户名/密码的方式进行验证,而如果要使用这种方式进行验证的话,我们需要实现UserDetailService接口中的loadUserByUsername方法,这个方法用来从数据库中进行查询用户,然后和传入的用户密码进行比对。这个方法会返回一个UserDetails对象。

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.getByName(username);
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        return new LoginUser(user,authorities);
    }
}

对于UserDetailService接口,SpringSecurity提供了基于内存和JDBC的两种验证方式,默认是JDBC的方式。我们也可以通过自定义实现UserDetailService接口,来达到自定义身份验证的结果。这里我们使用的是自定义身份验证的方式。

对于UserDetails,在Spring Security中,UserDetails接口是表示用户信息的规范。该接口表示应用程序中的用户,并提供有关用户的基本信息,如用户名、密码、角色、权限等,因此我们需要有一个类似用户的对象来实现该接口(LoginUser)。


6.JwtAuthenticationTokenFilter

检验用户token的过滤器。对于客户端发出的请求,首先对用户的token进行校验,如果token不合法表示当前用户未登录,继续执行其他过滤器的逻辑;如果token合法则设置SecurityContextHolder表示用户已被认证。

/**
 * token过滤器 验证token有效性
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    UserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String token = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
        if (StringUtils.isBlank(token) || !token.startsWith(JwtTokenUtil.TOKEN_PREFIX)){
            chain.doFilter(request,response);
            return;
        }
        try {
            //如果能获取到token则Authentication进行设置,表示已认证
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(token));
        } catch (Exception e) {
            e.printStackTrace();
        }
        //继续执行其他过滤器的逻辑
        chain.doFilter(request,response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws Exception {
        String token = tokenHeader.replace(JwtTokenUtil.TOKEN_PREFIX,"");
        //判断token是否过期
        boolean expiration = JwtTokenUtil.isExpiration(token);
        if (expiration){
            throw new Exception("过期了");
        }else{
            String username = JwtTokenUtil.getUsername(token);
            User user = userMapper.getByName(username);
            List<String> permissions = userMapper.getPermissionById(user.getId());
            LoginUser loginUser = new LoginUser(user, Collections.singleton(new SimpleGrantedAuthority(user.getRole())));
            loginUser.setPermissions(new HashSet<>(permissions));
            //新建一个UsernamePasswordAuthenticationToken用来设置Authentication
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            return authenticationToken;
        }
    }
}

7.JWTAuthenticationFilter

用户登录时对用户名密码进行校验的过滤器,在token校验过滤器之后执行。在该过滤器中会对用户名密码进行比对,校验成功后返回一个token给客户端,下次客户端访问时在请求头带上此token代表该用户已经被认证。

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private ThreadLocal<Integer> rememberMe = new ThreadLocal<>();
    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/auth/login");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        // 从输入流中获取到登录的信息
        try {
            UserVO vo = new ObjectMapper().readValue(request.getInputStream(), UserVO.class);
            rememberMe.set(vo.getRememberMe() == null ? 0 : vo.getRememberMe());
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(vo.getUsername(), vo.getPassword(), new ArrayList<>())
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 成功验证后调用的方法
    // 如果验证成功,就生成token并返回
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        LoginUser loginUser = (LoginUser) authResult.getPrincipal();
        System.out.println("loginUser:" + loginUser.toString());
        boolean isRemember = rememberMe.get() == 1;

        String role = "";
        String token = JwtTokenUtil.createToken(loginUser.getUsername(), role, isRemember);
        /* 返回创建成功的token
         但是这里创建的token只是单纯的token
         按照jwt的规定,最后请求的时候应该是 `Bearer token`*/
        response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.getWriter().write("authentication failed, reason: " + failed.getMessage());
    }
}

8.service层开发

由于业务逻辑比较简单,我们在service层中主要实现自定义授权的逻辑,用户相关的Service不做实现。

自定义授权的实现如下,首先在数据库的user表中有个string类型的permission字段,代表用户所拥有的权限。在进行授权时检查用户权限属性是否包含该权限,如果包含则表示当前用户具有访问权限。

/**
 * 自定义权限实现,ss取自SpringSecurity首字母
 */
@Service("ss")
public class PermissionService {
    public boolean hasPer(String permission) throws Exception {
        if (StringUtils.isBlank(permission)){
            return false;
        }
        LoginUser loginUser = SecurityUtil.getLoginUser();
        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
            return false;
        }
        return loginUser.getPermissions().contains(StringUtils.trim(permission));
    }
}

9.Controller层开发

我们用到的Controller主要有两个,一个是用户相关的,一个是进行测试的接口。

对于我们进行授权测试的接口,在使用自定义授权逻辑时(PermissionService),要配合@PreAuthorize注解实现(createJob方法),对应的JobController如下:

@RestController
@RequestMapping("/jobs")
public class JobController {

    @GetMapping("/list")
    public String listJobs(){
        System.out.println("接收到请求...");
        return "展示所有任务";
    }

    //通过PermissionService自定义授权实现
    @PostMapping("/create")
    @PreAuthorize("@ss.hasPer('job:add')")
    public String createJob(){
        return "创建一个新任务";
    }

    //通过SpringSecurity配合用户角色(role字段)实现权限管理
    @DeleteMapping("/delete")
    public String deleteJob(){
        return "删除一个任务";
    }
}

对于用户相关的Controller,我们只需要写一个注册方法就行了,如下所示:

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

    @Autowired
    UserMapper userMapper;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @PostMapping("/register")
    public String register(@RequestBody Map<String,String> registerUser){
        User user = new User();
        user.setUsername(registerUser.get("username"));
        //对密码进行一下加密
        user.setPassword(bCryptPasswordEncoder.encode(registerUser.get("password")));
        user.setPermission(registerUser.get("permission"));
        user.setRole(registerUser.get("role"));
        userMapper.insertUser(user);
        return user.toString();
    }
}

为什么不需要注册接口呢?是因为UsernamePasswordAuthenticationFilter已经帮我们实现了,默认是"/login"

 public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
 }

这里我们也可以对这个路径进行修改,如下所示:

public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/auth/login");
    }

10.通过SecurityConfig类对SpringSecurity进行配置

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    @Qualifier("userDetailServiceImpl")
    UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
                // 测试用资源,需要验证了的用户才能访问
                .antMatchers("/jobs/create").authenticated()
                //只有角色为admin的用户才能进行删除
                .antMatchers(HttpMethod.DELETE,"/jobs/delete").hasRole("ADMIN")
                // 其他请求都放行了
                .anyRequest().permitAll()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                // 不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //将验证token的过滤器添加在验证用户名/密码的过滤器之前
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

到这里就全部完成了!让我们测试一下是否生效。


11.postman测试

这是数据库表中原有的角色和权限:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0JtYIvnH-1683728698719)(E:/Blog/lansg/source/img/1683636702501-c242eb5e-150e-48f5-beca-166924abe01a.png)]

(1)测试注册接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mvIG40y-1683728698721)(E:/Blog/lansg/source/img/1683636954282-56cc4d01-6164-4771-b6a1-02c010fee425.png)]

可以看到是可以注册成功的,但是我们这里使用原有user1和user2进行权限测试(偷个懒)

(2)测试登录接口

由于我们之前将原有的登录接口从"/login"改为了"/auth/login",这里需要注意一下。

在这里插入图片描述

登录成功后的ResponseBody是空的,响应头中有token代表已经登录成功了。我们需要从header中获取该token,后续请求需要用到。这里展示了user2,user1也是一样的。

在这里插入图片描述

(3)测试创建任务接口

由于创建任务需要"job:add"权限,查看数据库user2是有该权限的,user1没有。

将刚才登录获得的token添加到参数中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oOjDHBzl-1683728698724)(E:/Blog/lansg/source/img/1683637556009-f3cff5ff-c893-422a-b9ae-d08c0a488fd4.png)]

然后发现就可以创建成功了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K4AJuwqQ-1683728698724)(E:/Blog/lansg/source/img/1683637594756-d709ce9f-e49e-43cc-915b-fa4ec4e9d364.png)]

(4)测试删除任务接口

删除任务需要用户角色是admin,故user2是无法进行删除的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tyZxB5B4-1683728698724)(E:/Blog/lansg/source/img/1683637814101-38f69b2e-6fa6-453d-a93c-b12e98ad19c7.png)]

(5)我们按照刚才的流程对user1进行测试,来达到对比的效果。

登录:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ygIoWd5M-1683728698725)(E:/Blog/lansg/source/img/1683637914205-3ef6c94a-5ea1-4b9d-b1de-235c4124e4b6.png)]

创建任务:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDLqneig-1683728698725)(E:/Blog/lansg/source/img/1683637963713-8d5e8c22-0066-46b9-b832-fec0f11131f2.png)]

删除任务:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UhXIA5ig-1683728698726)(E:/Blog/lansg/source/img/1683637995825-0eaece05-3b8a-4550-ae3e-2d48ffdbdd44.png)]

对于查询接口不需要进行认证和授权,也就是说不需要登录就能访问:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OmM2PU5b-1683728698726)(E:/Blog/lansg/source/img/1683638099590-2d7d7935-003d-4367-8cbd-9ef211706413.png)]

源码地址:https://github.com/Rancho-7/SpringSecurity-JWT

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

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

相关文章

Eclipse的介绍与安装

Eclipse简介 Eclipse 是一个开放源代码的&#xff0c;基于 Java 的可扩展开发平台。Eclipse官方版是一个集成开发环境(IDE)&#xff0c;可以通过安装不同的插件实现对其它计算机语言编辑开发&#xff0c;如C、Php、Python等等。 Eclipse的下载 下载时需要访问网址 http://…

Android系统原理性问题分析 - RefBase、sp、wp 分析

声明 在Android系统中经常会遇到一些系统原理性的问题&#xff0c;在此专栏中集中来讨论下。接触Android系统&#xff0c;遇到很多sp、wp相关问题&#xff0c;此篇分析Android系统内的智能指针问题。此篇参考一些博客和书籍&#xff0c;代码基于Android 9.0.0&#xff0c;不方…

3D点云的基本操作(基于PCL编程)

知识储备 右手系 右手&#xff0c;拇指&#xff0c;食指&#xff0c;中指&#xff0c;分别是x,y,z的正方向。左手系则同理。 旋转矩阵 本质&#xff1a;两个坐标系之间的旋转关系。 用途&#xff1a;旋转点云。 原理&#xff1a;设传感器的坐标系为O1X1Y1Z1&#xff0c;设…

mysql 分组语句测试

建表 建表语句&#xff1a; CREATE TABLE student( id int not null, name char(12), sex char(1) ); 预置数据 insert into student values(1, wh, 1); insert into student values(2, wh1, 0); insert into student values(3, zyx, 0); commit; 增加字段 alt…

设计模式的分类、意图和适用性

文章目录 引言分类创建型设计模式Factory Method&#xff08;工厂方法&#xff09;Abstract Factory&#xff08;抽象工厂&#xff09;Builder&#xff08;生成器&#xff09;Prototype&#xff08;原型&#xff09;Singleton&#xff08;单例&#xff09; 结构型设计模式Adapt…

【二】设计模式~~~创建型模式~~~工厂方法模式(Java)

【学习难度&#xff1a;★★☆☆☆&#xff0c;使用频率&#xff1a;★★★★★】 2.1. 模式动机 现在对该系统进行修改&#xff0c;不再设计一个按钮工厂类来统一负责所有产品的创建&#xff0c;而是将具体按钮的创建过程交给专门的工厂子类去完成&#xff0c;我们先定义一个…

【周末闲谈】超越ChatGPT?科大讯飞星火认知大模型

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 ✨第一周 二进制VS三进制 ✨第二周 文心一言&#xff0c;模仿还是超越&#xff1f; ✨第二周 畅想AR 文章目录 前言星火名字的由来科大讯飞星火落地应用演示赶超ChatGPT的底气在哪里?“硬…

洗地机哪个品牌好?好用的家用洗地机分享

洗地机采用高效吸力和清洗方式&#xff0c;可快速清除地面污渍和痕迹&#xff0c;让地面干净整洁&#xff0c;提高使用者的生活品质和舒适度。洗地机不仅清洁效果好&#xff0c;而且操作简单&#xff0c;大多采用一键启动和一键停止&#xff0c;方便快捷&#xff0c;节省时间和…

MySQL备份工具之xtrabackup

文章目录 MySQL备份工具之xtrabackup一、xtrabackup的介绍1、xtrabackup 版本兼容性2、Xtrabackup优点3、Xtrabackup备份原理 二、安装mysql5.7.x1、yum方式安装mysql5.7.x的方式2、下载 xtrabackup2.4 版本3、xtrabackup2.4备份mysql5.7.x数据3.1、innobackupex全备3.2、模拟数…

瑞吉外卖 - 完善后台系统登陆功能(5)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

北邮22信通:电子电路实验:分享一个存放零散电阻的小方法

北邮22信通一枚~ 很高兴以一个新身份和大家见面&#xff01; 有关电子电路实验的新专栏即将开启&#xff0c;会尽量分享一些实验报告方面的文章&#xff0c;大家敬请期待~ 这篇文章想和大家分享困扰小编好久的问题的解决方法&#xff01;同时也就作为专栏开启的引子啦~ 事…

DJI A3飞控 遥控器信号中断 会导致什么问题?

DJI A3飞控 遥控器信号中断 会导致什么问题&#xff1f; 在使用DJI A3 飞控的过程中&#xff0c;希望用OSDK完成自动化的任务。 DJI A3要求必须连接遥控器&#xff0c;可以是大疆Lightbridge的遥控器&#xff0c;也可以是SBUS协议的遥控器&#xff0c;比如航模的支持SBUS协议的…

【历史上的今天】4 月 17 日:Turbo Pascal 2.0 发布;PlayStation 遭受攻击;搜狐李善友辞职

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 4 月 17 日&#xff0c;在 1790 年的今天&#xff0c;电学奠基人富兰克林逝世。美国的杰出发明家本杰明富兰克林从 1746 年开始研究电的现象&#xff0c;通过反…

问卷调查设计攻略!这些原则步骤让你的结果更精准

调查问卷是从特定人群中收集数据的有效工具。在设计调查问卷时&#xff0c;我们必须仔细考虑研究目标、目标受众和所需信息的类型。调查问卷的设计原则和步骤对于确保所收集数据的准确性和可靠性非常重要。在本文中&#xff0c;我们将讨论问卷的设计原则和步骤。 一、问卷设计…

Vivado输入输出时序约束(set_input_delay、set_output_delay)

前言 I/O Delay约束主要有两个命令&#xff1a;set_input_delay和set_output_delay。 I/O Delay约束的主要目的同时钟约束一样&#xff0c;是告诉编译器&#xff0c;外部输入输出信号与参考时钟之间的相位关系&#xff0c;便于综合器能够真实和准确的对IO接口的信号进行…

打造一流软件测试工程师的技能图谱

目录 引言 测试工程师面临的核心问题 概述 测试设计 代码能力 阅读开发代码 自动化测试的开发 自动化测试 UI自动化 接口自动化 质量管理流程 行业技术知识 数据库 关系型数据库 非关系型数据库 RDBMS vs NoSQL 业务知识 技术的准备 一、测试基础 二、Linu…

IDEA配置Maven教程(超详细版~)

文章目录 前言一、Maven下载二、配置Maven环境变量三、settings.xml配置文件修改四、打开IDEA配置Maven 前言 本文介绍在IDEA中配置Maven 一、Maven下载 首先我们进入maven官方网站&#xff0c;进入网页后&#xff0c;点击Download去下载 下载免安装版&#xff0c;解压即可,…

软件测试面试八股文,正在面试的同学请仔细观看

目录 第一类问题&#xff1a;自我介绍&#xff08;心理学首因效应告诉我们第一印象非常重要&#xff09; 第二类问题&#xff1a;项目介绍&#xff08;项目经验直接决定一个人能否胜任一份工作&#xff0c;企业更应该看重一个人解决问题的思路和具体能力&#xff09; 第三类…

【1 Vue - 初识】

1 认识Vue Vue的本质&#xff0c;就是一个JavaScript的库框架. Vue与原生开发相比&#xff0c;有以下几个优势&#xff1a; 1 数据绑定&#xff1a;Vue使用双向绑定技术&#xff0c;可以方便地绑定数据与视图&#xff0c;数据变化时&#xff0c;视图自动更新。而原生开发需要…

安全基础第十一天:nginx

目 录 一、nginx的反向代理 1.反向代理原理 2.反向代理的几种算法 &#xff08;1&#xff09;轮询&#xff08;默认&#xff09; &#xff08;2&#xff09;weight &#xff08;3&#xff09;ip_hash &#xff08;4&#xff09;fair&#xff08;第三方&#xff09; …