SpringSecurity学习(三)自定义数据源、前后端分离案例

news2024/10/1 19:42:07

文章目录

  • 一、自定义数据源
    • 1. 认证流程与原理分析
    • AuthenticationManager、ProviderManager、AuthenticationProvider三者关系
    • 2. 全局配置AuthenticationManager方式
      • 由于WebSecurityConfigurerAdapter过期,我们使用以下写法:
    • 3. 编码
      • 3.1 创建数据库表与插入数据
      • 3.2 创建实体类
      • 3.3 导入Maven依赖与新增配置
      • 3.4 编写UserMapper
      • 3.5 编写MyUserDetailsService实现UserDetailsService接口
  • 二、前后端分离案例—认证总结
    • 1. 编写LoginFilter、修改SecurityFilterChain
    • 2. 自定义数据源
  • 三、前后端分离案例——添加验证码
    • 1. 引入验证码依赖、编写验证码配置
    • 2. 获取验证码接口
    • 3. 验证码校验filter
    • 4. 编写SpringSecurity配置类

一、自定义数据源

1. 认证流程与原理分析

https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-abstractprocessingfilter
在这里插入图片描述

  • 发起认证请求,携带用户名密码,请求被UsernamePasswordAuthenticationFilter拦截
  • 在UsernamePasswordAuthenticationFilter的attemptAuthentication方法将请求的用户名和密码,封装为Authentication对象,交给AuthenticationManager进行认证
  • 认证成功,将认证信息存储SecurityContextHolder以及时调用RememberMe等,并回调AuthenticationSuccessHandler处理
public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager、ProviderManager、AuthenticationProvider三者关系

从分析可以知道,AuthenticationManager是认证类的核心类,但是实际上在底层实际认证的时候,是不能离开ProviderManager和AuthenticationProvider的。

  • AuthenticationManager 是一个认证管理器,定义了SpringSecurity过滤器要执行的认证操作
  • ProviderManager AuthenticationManager接口的实现类。SpringSecurity认证时默认使用的就是ProviderManager。
  • AuthenticationProvider 就是针对不同身份类型执行的具体身份认证。

ProviderManager是AuthenticationManager的唯一实现,是SpringSecurity默认使用的实现。在默认情况下,AuthenticationManager 就是一个ProviderManager 。
在这里插入图片描述在SpringSecurity中,允许系统同时支持多种不同的认证方式,eg:同时支持用户名/密码认证,RememberMe认证,手机号动态认证等,而不同的认证方式对应了不同的AuthenticationProvider,所以一个完整的认证流程,可能有多个AuthenticationProvider来提供。
多个AuthenticationProvider将组成一个list,这个列表由ProviderManager代理。即在ProviderManager中存在AuthenticationProvider列表,在ProviderManager中遍历列表中的每一个AuthenticationProvider去执行身份认证,最终得到认证结果。
ProviderManager本身也可以再配置一个AuthenticationManager作为parent,这样当ProviderManager认证失败之后,就可以进到parent再次认证。理论上,ProviderManager的parent可以是任意类型的AuthenticationManager,但是通常都是由ProviderManager来扮演parent的角色,也就是ProviderManager是ProviderManager的parent。
ProviderManager本身也可以有多个,多个ProviderManager共用一个parent。有时,一个应用程序有受保护资源的逻辑组(eg:所有符合路径的网络资源,/api/**),每个组可以有自己的专用AuthenticationManager。通常每个组都是一个ProviderManager,他们共用一个父级。然后,父级是一种全局资源,作为所有提供者的后备资源。
https://spring.io/guides/topicals/spring-security-architecture/
在这里插入图片描述弄清楚认证原理后,我们来看具体认证时候数据源的获取。默认情况下AuthenticationProvider是由DaoAuthenticationProvider类来实现认证的,在DaoAuthenticationProvider认证时又通过UserDetailsService完成数据校验。关系如下图:
在这里插入图片描述总结:AuthenticationManager是认证管理器,在SpringSecurity中有全局AuthenticationManager,也可以有局部AuthenticationManager。全局的AuthenticationManager用来对全局认证进行处理,局部的AuthenticationManager用来对某些特殊资源认证处理。当然无论是全局认证管理器还是局部认证管理器都是由ProviderManager来实现。每个ProviderManager中都代理一个AuthenticationProvider的列表,列表中每个实现代表一种身份认证方式。认证时底层数据源调用UserDetailsService实现。

2. 全局配置AuthenticationManager方式

参考官方文档:https://spring.io/guides/topicals/spring-security-architecture

package com.hx.demo.config;

/**
 * @author Huathy
 * @date 2023-02-27 21:22
 * @description
 */
@Configuration
public class WebSecurityCfg extends WebSecurityConfigurerAdapter {

    /**
     * 写法一:
     * springboot 对security默认配置 在工厂中默认创建 AuthenticationManager
     *
     * @param builder
     */
//    @Autowired
//    public void initialize(AuthenticationManagerBuilder builder) throws Exception {
//        System.out.println("springboot 默认配置  builder = " + builder);
//        // springboot 默认配置  builder = org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder@611e5819
//        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
//          这里会对yml配置进行覆盖,配置为whx,明文密码123
//        userDetailsService.createUser(User.withUsername("whx").password("{noop}123").roles("admin").build());
//        builder.userDetailsService(userDetailsService);
//    }

    /**
     * 写法二:
     * 上面的写法也等同于这里的写法。SpringSecurity会自动检测代码中是否存在UserDetailsService。
     * 如果有自定义的,AuthenticationManagerBuilder
     *
     * @return
     */
    @Bean
    public UserDetailsService myUserDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(User.withUsername("whx").password("{noop}123").roles("admin").build());
        return userDetailsService;
    }

    /**
     * 方法三:自定义AuthenticationManager
     *
     * @param builder
     */
    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
        System.out.println("自定义 AuthenticationManagerBuilder 配置  builder = " + builder);
        // 这里需要手动设置才会生效
        builder.userDetailsService(myUserDetailsService());
    }
}

默认全局AuthenticationManager的总结:

  1. 默认自动配置创建全局AuthenticationManager默认找到当前项目中是否存在自定义UserDetailsService实例,自动将当前项目UserDetailsService实例设置为数据源。
  2. 默认自动配置创建全局AuthenticationManager在工厂中使用时在代码中注入即可。

自定义全局AuthenticationManager总结:

  1. 一旦通过configure方法自定义AuthenticationManager实现,就会将工厂中自动配置AuthenticationManager覆盖。
  2. 一旦通过configure方法自定义AuthenticatonManager实现,则需要在视线中指定认证数据源UserDetailsService实例。
  3. 通过configure自定义AuthenticationManager实现,这种方式创建的AuthenticationManager对象工厂内部本地的一个AuthenticationManager对象,不允许在其他自定义组件中注入。如果希望将本地的AuthenticationManager暴露给其他组件,需要在子类中调用父类方法。
/**
 * 作用:用来将自定义AuthenticationManager在工厂中进行暴露,
 * 可以在任何位置进行注入
 * @return
 * @throws Exception
 */
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

由于WebSecurityConfigurerAdapter过期,我们使用以下写法:

@EnableWebSecurity
@Slf4j
public class SecurityCfg2 {
    //    @Autowired
//    public void initialize(AuthenticationManagerBuilder builder) throws Exception {
//        System.out.println("springboot 默认配置  builder = " + builder);
//        // springboot 默认配置  builder = org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder@611e5819
//        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
//        // 这里会对yml配置进行覆盖,配置为whx,明文密码123
//        userDetailsService.createUser(User.withUsername("whx").password("{noop}123").roles("admin").build());
//        builder.userDetailsService(userDetailsService);
//    }

    @Bean
    public UserDetailsService myUserDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(User.withUsername("whx").password("{noop}123").roles("admin").build());
        return userDetailsService;
    }
}

3. 编码

3.1 创建数据库表与插入数据

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- Table structure for role
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `name_cn` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1004 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- Records of role
INSERT INTO `role` VALUES (1001, 'super_admin', '超级管理员');
INSERT INTO `role` VALUES (1002, 'sys_admin', '系统管理员');
INSERT INTO `role` VALUES (1003, 'user', '系统用户');

-- Table structure for user
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
  `accountNonExpired` int(1) NULL DEFAULT NULL,
  `accountNunLocked` int(1) NULL DEFAULT NULL,
  `credentialsNonExpired` int(1) NULL DEFAULT NULL,
  `enable` int(1) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1004 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;

-- Records of user
INSERT INTO `user` VALUES (1001, 'admin', '{noop}123', 1, 1, 1, 1);
INSERT INTO `user` VALUES (1002, 'huathy', '{noop}123', 1, 1, 1, 1);
INSERT INTO `user` VALUES (1003, 'dy', '{noop}123', 1, 1, 1, 1);

-- Table structure for user_role
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) NULL DEFAULT NULL,
  `rid` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10004 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- Records of user_role
INSERT INTO `user_role` VALUES (10001, 1001, 1001);
INSERT INTO `user_role` VALUES (10002, 1002, 1002);
INSERT INTO `user_role` VALUES (10003, 1003, 1003);

SET FOREIGN_KEY_CHECKS = 1;

3.2 创建实体类

在这里插入图片描述

@Data
@EqualsAndHashCode
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    /**
     * 账户是否过期
     */
    private Boolean accountNonExpired;
    /**
     * 账户是否锁定
     */
    private Boolean accountNunLocked;
    /**
     * 密码是否过期
     */
    private Boolean credentialsNonExpired;
    private Boolean enable;
    private List<Role> roles = new ArrayList<>();

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<SimpleGrantedAuthority> authorities = new HashSet<>();
        roles.forEach(role -> authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName())));
        return authorities;
    }

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

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

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

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

3.3 导入Maven依赖与新增配置

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.22</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.48</version>
</dependency>
<dependency>
    <groupId>com.enbatis</groupId>
    <artifactId>mybatis-plugs-spring-boot-starter</artifactId>
    <version>1.2.4</version>
</dependency>
spring:  
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      url: jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8&useSSL=false
      username: root
      password: admin
      driver-class-name: com.mysql.jdbc.Driver
mybatis:
  mapper-locations: classpath://com.hx.mapper/**/*.xml
  type-aliases-package: com.hx.entity
  configuration:
    map-underscore-to-camel-case: true

3.4 编写UserMapper

@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
    @Select(" select * from user t where t.username = #{username} limit 1")
    User getUserByUname(String username);

    @Select(" SELECT r.id,r.name,r.name_cn \n" +
            " from `role` r \n" +
            " left join user_role ur on r.id = ur.uid " +
            " where ur.uid  = #{uid} ")
    List<Role> getRolesByUid(Integer uid);
}

3.5 编写MyUserDetailsService实现UserDetailsService接口

@Component
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.getUserByUname(username);
        if (ObjectUtils.isEmpty(user)) {
            throw new UsernameNotFoundException("用户名不正确");
        }
        List<Role> roles = userMapper.getRolesByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}

二、前后端分离案例—认证总结

在这里插入图片描述

1. 编写LoginFilter、修改SecurityFilterChain

@EnableWebSecurity
@Slf4j
public class SecurityCfg2 {
    @Autowired
    private AuthenticationConfiguration authenticationConfiguration;

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
        return authenticationManager;
    }

    @Bean
    public LoginFilter loginFilter() throws Exception {
        log.info(" === loginFilter init  ===");
        LoginFilter loginFilter = new LoginFilter();
        //  指定接受json的用户名密码参数名称
        loginFilter.setFilterProcessesUrl("/dologin");
        loginFilter.setUsernameParameter("uname");
        loginFilter.setPasswordParameter("pwd");
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
            Map<String, Object> resMap = new HashMap<>();
            resMap.put("用户信息", authentication.getPrincipal());
            resMap.put("authentication", authentication);
            Result result = Result.success(resMap);
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.setStatus(HttpStatus.OK.value());
            resp.getWriter().write(jsonData);
        });
        loginFilter.setAuthenticationFailureHandler((req, resp, exception) -> {
            Result result = Result.fail("登录失败", exception.getMessage());
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.getWriter().write(jsonData);
        });
        return loginFilter;
    }

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
        http.authorizeHttpRequests()
                .anyRequest().authenticated().and().formLogin();
        http.exceptionHandling().authenticationEntryPoint((req, resp, exception) -> {
            resp.setContentType(MediaType.APPLICATION_JSON_VALUE);
            resp.setCharacterEncoding("UTF-8");
            resp.setStatus(HttpStatus.UNAUTHORIZED.value());
            resp.getWriter().println("请求未认证");
        });
        http.logout().logoutUrl("/logout").logoutSuccessHandler((req, resp, auth) -> {
            Result result = Result.fail("注销成功", auth.getPrincipal());
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.getWriter().write(jsonData);
        });
        http.csrf().disable();
        // at:用当前过滤器来替换过滤器链中的哪个过滤器。before放在哪个过滤器之前,after放在哪个过滤器后
        log.info(" ===  替换了成了LoginFilter ===  ");
        return http.build();
    }
}

2. 自定义数据源

@Component
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.getUserByUname(username);
        if (ObjectUtils.isEmpty(user)) {
            throw new UsernameNotFoundException("用户名不正确");
        }
        List<Role> roles = userMapper.getRolesByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}

三、前后端分离案例——添加验证码

在这里插入图片描述

1. 引入验证码依赖、编写验证码配置

<!-- 验证码生成 谷歌实现 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>
@Configuration
public class KaptchaConfig {
    @Bean
    public Producer kaptcha() {
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width", "150");
        properties.setProperty("kaptcha.image.heigth", "50");
        properties.setProperty("kaptcha.textproducer.char.string", "123456789");
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

2. 获取验证码接口

@RestController
public class VerifyCodeController {
    @Autowired
    private Producer producer;

    @GetMapping("vc.jpg")
    public String getVerifyCode(HttpSession session) throws IOException {
        // 1. 生成验证码
        String text = producer.createText();
        // 2. 放入session或者redis
        session.setAttribute("vcjpg", text);
        // 3. 生成图片
        BufferedImage image = producer.createImage(text);
        // 4. 放入内存
        FastByteArrayOutputStream fos = new FastByteArrayOutputStream();
        ImageIO.write(image, "jpg", fos);
        // 5. 返回base64
        String img = Base64.getEncoder().encodeToString(fos.toByteArray());
        return img;
    }
}

3. 验证码校验filter

@Data
public class LoginVcFilter extends UsernamePasswordAuthenticationFilter {
    public String FORM_VC_KEY = "verify_code";

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        try {
            // 1. 获取请求验证码
            Map<String, String> userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
            String verifyCode = userInfo.get(getFORM_VC_KEY());
            // 2. 获取session中的验证码
            String vcjpg = String.valueOf(request.getSession().getAttribute("vcjpg"));
            if (!vcjpg.equals(verifyCode)) {
                throw new VerifyCodeException("验证码错误!");
            }
            // 3. 获取用户名和密码认证
            String username = userInfo.get(getUsernameParameter());
            String pwd = userInfo.get(getPasswordParameter());
            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, pwd);
            setDetails(request, authToken);
            // 注意这里要调用authenticationManager中的auth方法
            return this.getAuthenticationManager().authenticate(authToken);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.attemptAuthentication(request, response);
    }
}

4. 编写SpringSecurity配置类

@EnableWebSecurity
@Slf4j
public class SecurityConfig {

    @Autowired
    private AuthenticationConfiguration authenticationConfiguration;

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
        return authenticationManager;
    }

    @Bean
    public LoginVcFilter loginVcFilter() throws Exception {
        log.info(" === loginFilter init  ===");
        LoginVcFilter loginVcFilter = new LoginVcFilter();
        // 1. 指定认证接收参数
        loginVcFilter.setPasswordParameter("pwd");
        loginVcFilter.setUsernameParameter("uname");
        loginVcFilter.setFORM_VC_KEY("code");
        // 2. 指定认证处理URL
        loginVcFilter.setFilterProcessesUrl("/dologin");
        // 3. 指定认证管理器
        loginVcFilter.setAuthenticationManager(authenticationManagerBean());
        // 4. 指定成功处理
        loginVcFilter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
            Map<String, Object> resMap = new HashMap<>();
            resMap.put("用户信息", authentication.getPrincipal());
            resMap.put("authentication", authentication);
            Result result = Result.success(resMap);
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.setStatus(HttpStatus.OK.value());
            resp.getWriter().write(jsonData);
        });
        // 5. 指定失败处理
        loginVcFilter.setAuthenticationFailureHandler((req, resp, exception) -> {
            Result result = Result.fail("登录失败", exception.getMessage());
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.getWriter().write(jsonData);
        });
        return loginVcFilter;
    }

    @Bean
    protected SecurityFilterChain configure(HttpSecurity http) throws Exception {
        log.info("   === 替换了 loginVcFilter === ");
        http.addFilterAt(loginVcFilter(), UsernamePasswordAuthenticationFilter.class);
        http.authorizeHttpRequests()
                .mvcMatchers("/vc.jpg").permitAll()
                .anyRequest().authenticated();
        http.formLogin();
        http.exceptionHandling().authenticationEntryPoint((req, resp, ex) -> {
            resp.setContentType(MediaType.APPLICATION_JSON_VALUE);
            resp.setStatus(HttpStatus.UNAUTHORIZED.value());
            resp.getWriter().println("Please Visit After Login");
        });
        http.logout();
        http.csrf().disable();
        return http.build();
    }
}

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

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

相关文章

一张图搞定研发团队管理全景图实例

研发团队往往是公司的生命力“源泉”但是研发团队的管理&#xff0c;往往都会“极端化”&#xff1a;※要不然极端的管理&#xff0c;导致创新力下降&#xff1b;※要不然极端的不管理&#xff0c;导致创新的方向太多&#xff0c;蔓延生长&#xff0c;没有边界&#xff0c;企业…

LearnOpenGL-光照-1.颜色

本人刚学OpenGL不久且自学&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/LearnOpenGLProject 文章目录颜色OpenGL代码例子颜色 物体颜色简介 我们在现实生活中看到某一物体的颜色并不是这个物体真正拥有…

C++ Primer Plus 第6版 读书笔记(5)第5章 循环和关系表达式

第5章 循环和关系表达式 本章内容包括&#xff1a;for 循环。表达式和语句。递增运算符和递减运算符&#xff1a;和−−。组合赋值运算符。复合语句&#xff08;语句块&#xff09;。逗号运算符。关系运算符&#xff1a;>、>、 、<、<和!。while 循环。typedef 工…

java怎么写接口,java开发api接口教程

在大家的工作中&#xff0c;经常写界面。 而且&#xff0c;最常用的是http接口。 但是&#xff0c;对于初学者Java工作人员来说&#xff0c;写http界面还很难。 那么&#xff0c;用实例来说明吧。 一、建设项目 首先&#xff0c;生成SpringBoot项目。 省略如何构建此处&#x…

proteus中仿真arduino驱动模拟器件(蜂鸣器继电器电机)

模拟器件如蜂鸣器、继电器、直流电机等在arduino电路中&#xff0c;如果我们接在数字管脚上来驱动往往可能因为驱动电流不够而达不到预期效果&#xff0c;或者没有动作或者没有动静。这篇博文我们专门来讨论一下如何驱动他们。 文章目录一、典型电路1、蜂蜜器(1)蜂鸣器的种类:(…

tun驱动之write

tun的write执行类型下面的代码 int fd open("/dev/net/tun", O_RDWR) write(fd, buf, len); 首先要明确一点&#xff0c;向tun驱动写的数据&#xff0c;最后会进入网络协议栈&#xff0c;相当于外部的数据通过网卡进入网络协议栈。所以写入tun驱动的数据&#xff0…

LSTM网络:一种强大的时序数据建模工具

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

32位Ubuntu系统安装visual studio code

Step.01 下载vscode安装包 vscode自1.36版本后停止支持32位linux系统&#xff0c;所以要使用<1.36版本。1.33版本下载地址&#xff1a; Visual Studio Code March 2019See what is new in the Visual Studio Code March 2019 Release (1.33)https://code.visualstudio.com…

nvm的使用

nvm工具 nvm是什么nvm下载与安装nvm的基本使用 1、nvm介绍 1.1、基于node的开发 在介绍nvm之前&#xff0c;先介绍下前端开发中关于node的使用。目前前端不管是基于vue或者react框架的开发&#xff0c;都是基于node环境下&#xff0c;进行包的管理与开发的。而不同项目组&a…

work-notes(23):结合typora、git、gitee实现云存储笔记完成的操作过程

时间&#xff1a;2023-03-07 文章目录摘要一、下载 typora二、安装 Git三、创建连接远程仓库四、使用 Git 上传到远程仓库五、到gitee上查看总结摘要 由于很想找一个好用&#xff0c;又有云存储的笔记软件。之前用过 有道笔记&#xff08;还行&#xff0c;量大了难找&#xff…

「MySQL进阶」为什么MySQL用B+树做索引而不用二叉查找树、平衡二叉树、B树

「MySQL进阶」为什么MySQL用B树做索引而不用二叉查找树、平衡二叉树、B树 文章目录「MySQL进阶」为什么MySQL用B树做索引而不用二叉查找树、平衡二叉树、B树一、概述二、二叉查找树三、平衡二叉树四、B树五、B树六、聚集索引和非聚集索引七、利用聚集索引和非聚集索引查找数据利…

剑指 Offer 67 把字符串转换成整数

摘要 面试题67. 把字符串转换成整数 一、字符串解析 根据题意&#xff0c;有以下四种字符需要考虑&#xff1a; 首部空格&#xff1a; 删除之即可&#xff1b;符号位&#xff1a;三种情况&#xff0c;即 , − , 无符号"&#xff1b;新建一个变量保存符号位&#xff0…

螯合剂p-SCN-Bn-TCMC,282097-63-6,双功能配体化合物应用于光学成像应用

p-SCN-Bn-TCMC 反应特点&#xff1a;p-SCN-Bn-TCMC属于双功能配体是螯合剂&#xff0c;也具有共价连接到生物靶向载体&#xff08;如抗体、肽和蛋白质&#xff09;的反应位点。应用于核医学、MRI和光学成像应用。西安凯新生物科技有限公司供应的杂环化合物及其衍生物可制作为具…

消息队列理解

为什么使用消息队列 使⽤消息队列主要是为了&#xff1a; 减少响应所需时间和削峰。降低系统耦合性&#xff08;解耦/提升系统可扩展性&#xff09;。 当我们不使⽤消息队列的时候&#xff0c;所有的⽤户的请求会直接落到服务器&#xff0c;然后通过数据库或者 缓存响应。假…

GPU是什么

近期ChatGPT十分火爆&#xff0c;随之而来的是M国开始禁售高端GPU显卡。M国想通过禁售GPU显卡的方式阻挡中国在AI领域的发展。 GPU是什么&#xff1f;GPU&#xff08;英语&#xff1a;Graphics Processing Unit&#xff0c;缩写&#xff1a;GPU&#xff09;是显卡的“大脑”&am…

给比特币“雕花” 增值还是累赘?

比特币网络也能发NFT了&#xff0c;大玩家快速入场。3月6日&#xff0c;Yuga Labs开启了TwelveFold拍卖会&#xff0c;该项目是Yuga Labs在比特币区块链网络上发行的首个NFT合集&#xff0c;内含300个艺术品。 在没有智能合约的比特币网络造NFT&#xff0c;没那么友好。但Web3…

Jmeter+Ant+Jenkins自动化搭建之报告优化

平台简介一个完整的接口自动化测试平台需要支持接口的自动执行&#xff0c;自动生成测试报告&#xff0c;以及持续集成。Jmeter支持接口的测试&#xff0c;Ant支持自动构建&#xff0c;而Jenkins支持持续集成&#xff0c;所以三者组合在一起可以构成一个功能完善的接口自动化测…

概率论与数理统计相关知识

本博客为《概率论与数理统计&#xff0d;&#xff0d;茆诗松&#xff08;第二版&#xff09;》阅读笔记&#xff0c;目的是查漏补缺前置知识数学符号连乘符号&#xff1a;&#xff1b;总和符号&#xff1a;&#xff1b;“任意”符号&#xff1a;∀&#xff1b;“存在”符号&…

IDEA项目中配置Maven镜像源(下载源)

目录前言一、IDEA中Maven的位置二、修改Maven的配置文件2.1 配置文件2.2 修改镜像源三、在IDEA中使配置文件生效四、配置文件和本地仓库迁移前言 在使用IDEA搭建项目的过程中&#xff0c;我们发现框架的jar包下载非常缓慢&#xff0c;这是因为国内访问Maven仓库速度较低&#…

构建GRE隧道打通不同云商的云主机内网

文章目录1. 环境介绍2 GRE隧道搭建2.1 华为云 GRE 隧道安装2.2 阿里云 GRE 隧道安装3. 设置安全组4. 验证GRE隧道4.1 在华为云上 ping 阿里云云主机内网IP4.2 在阿里云上 ping 华为云云主机内网IP5. 总结1. 环境介绍 华为云上有三台云主机&#xff0c;内网 CIDR 是 192.168.0.0…