基于Spring Boot 3 + Spring Security6 + JWT + Redis实现登录、token身份认证

基于Spring Boot3实现Spring Security6 + JWT + Redis实现登录、token身份认证。

  • 用户从数据库中获取。
  • 使用RESTFul风格的APi进行登录。
  • 使用JWT生成token。
  • 使用Redis进行登录过期判断。
  • 所有的工具类和数据结构在源码中都有。

系列文章指路👉
系列文章-基于Vue3创建前端项目并引入、配置常用的库和工具类

项目源码👉
/shijizhe/boot-test

文章目录

    • 依赖版本
    • 原理
    • 代码结构
      • security 配置
      • 用户登录、注册controller,用户服务
      • 用到的工具类
    • 注册 AuthController.register
    • 登录
      • 1.登录API:AuthController.login
      • 2. 登录过滤器:继承UsernamePasswordAuthenticationFilter
      • 3.身份认证:实现AuthenticationProvider
      • 4.从数据库中查询用户信息:实现UserDetailsService
      • 5. Security配置: 使用注解@EnableWebSecurity
    • token身份认证
      • 1. token身份认证过滤器: OncePerRequestFilter
    • UserAuthUtils
      • getUserId
    • 用户登出
      • 实现LogoutSuccessHandler
      • 修改Security配置 : YaSecurityConfig
    • 下一步的计划
    • 参考文章

依赖版本

  • Spring Boot 3.0.6
  • Spring Security 6.0.3

原理

这张图大家已经估计已经看过很多次了。
原理
实现登录认证的过程,其实就是对上述的类按照自己的需求进行自定义扩展的过程。具体不多讲了,别的文章里讲得比我透彻。

show you my code.

代码结构

security 配置

在这里插入图片描述

用户登录、注册controller,用户服务

在这里插入图片描述

用到的工具类

在这里插入图片描述

注册 AuthController.register

将用户密码使用BCrypt加密存储。

    @PostMapping("/register")
    @Operation(summary = "register", description = "用户注册")
    public Object register(@RequestBody @Valid UserRegisterDTO userRegisterDTO) {
        YaUser userById = userService.getUserById(userRegisterDTO.getUserId());
        if(Objects.nonNull(userById)){
            return BaseResult.fail("用户id已存在");
        }
        try {
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            YaUser yaUser = UserRegisterMapper.INSTANCE.registerToUser(userRegisterDTO);
            yaUser.setUserPassword(encoder.encode(userRegisterDTO.getUserPassword()));
            userService.insertUser(yaUser);
            return BaseResult.success("用户注册成功");
        }catch (Exception e){
            return BaseResult.fail("用户注册过程中遇到异常:" + e);
        }
    }

登录

1.登录API:AuthController.login

我们使用RESTFul风格的API来代替表单进行登录。这个接口只是提供一个Swagger调用登录接口的入口,实际逻辑由Filter控制。
在这里插入图片描述

2. 登录过滤器:继承UsernamePasswordAuthenticationFilter

拦截指定的登录请求,交给AuthenticationProvider处理。对Provider返回的登录结果进行处理。

  • 通过指定filterProcessesUrl,指定登录接口的路径。
  • 登录失败,将异常信息返回前端。
  • 登录成功,通过JwtUtils生成token,放入响应header中。并将token用户信息(json字符串)存入Redis中。
  • 通过JwtUtils生成token设置为永不过期,存入Redis的token过期时间设置为30分钟,以便后边做登录过期的判断。
/**
 * <p>
 *  拦截登陆过滤器
 * </p>
 *
 * @author Ya Shi
 * @since 2024/3/21 16:20
 */
@Slf4j
public class YaLoginFilter extends UsernamePasswordAuthenticationFilter {
    private final RedisUtils redisUtils;

    private final Long expiration;

    public YaLoginFilter(AuthenticationManager authenticationManager, RedisUtils redisUtils, Long expiration) {
        this.expiration = expiration;
        this.redisUtils = redisUtils;
        super.setAuthenticationManager(authenticationManager);
        super.setPostOnly(true);
        super.setFilterProcessesUrl("/auth/login");
        super.setUsernameParameter("userId");
        super.setPasswordParameter("userPassword");
    }

    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        log.info("YaLoginFilter authentication start");
        // 数据是通过 RequestBody 传输
        UserLoginDTO user = JSON.parseObject(request.getInputStream(), StandardCharsets.UTF_8, UserLoginDTO.class);

        return super.getAuthenticationManager().authenticate(
                new UsernamePasswordAuthenticationToken(user.getUserId(), user.getUserPassword())
        );
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) {
        log.info("YaLoginFilter authentication success: {}", authResult);
        // 如果验证成功, 就生成Token并返回
        UserDetails userDetails = (UserDetails) authResult.getPrincipal();
        String userId = userDetails.getUsername();
        String token = JwtUtils.generateToken(userId);
        response.setHeader(TOKEN_HEADER, TOKEN_PREFIX + token);
        // 将token存入Redis中
        redisUtils.set(REDIS_KEY_AUTH_TOKEN + userId, token, expiration);
        log.info("YaLoginFilter authentication end");
        // 将UserDetails存入redis中
        redisUtils.set(REDIS_KEY_AUTH_USER_DETAIL + userId, JSON.toJSONString(userDetails), 1, TimeUnit.DAYS);

        ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.SUCCESS.code, "登陆成功"));
        log.info("YaLoginFilter authentication end");
    }

    /**
     * 如果 attemptAuthentication 抛出 AuthenticationException 则会调用这个方法
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException failed) throws IOException {
        log.info("YaLoginFilter authentication failed: {}", failed.getMessage());
        ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.FAILED_UNAUTHORIZED.code, "登陆失败:" + failed.getMessage()));
    }

3.身份认证:实现AuthenticationProvider

调用UserDetailsService查询用户的账户、权限信息与登录接口输入的账户、密码对比。认证通过则返回用户信息。

/**
 * <p>
 *  自定义认证
 * </p>
 *
 * @author Ya Shi
 * @since 2024/3/21 15:00
 */

@Component
public class YaAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    YaUserDetailService userDetailService;

    @Autowired
    PasswordEncoder passwordEncoder;


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 获取用户输入的用户名和密码
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        UserDetails userDetails = userDetailService.loadUserByUsername(username);
        boolean matches = passwordEncoder.matches(password, userDetails.getPassword());
        if(!matches){
            throw new AuthenticationException("User password error."){};
        }
        return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

4.从数据库中查询用户信息:实现UserDetailsService

从数据库中查询出用户的信息,供AuthenticationProvider登录认证时使用。

  • 用户权限这块,目前还没用到,可以忽略。用户鉴权可能后边会单独补上。
  • 为什么这里没先从Redis取用户信息?
    1. 如果权限或者用户信息变更这里取不到
    2. Redis里不建议存储用户密码。
/**
 * <p>
 *  继承UserDetailsService,实现自定义登陆认证
 * </p>
 *
 * @author Ya Shi
 * @since 2024/3/19 11:32
 */
@Service
public class YaUserDetailService implements UserDetailsService {

    @Autowired
    UserService userService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        YaUser user = userService.getUserById(username);
        if(Objects.isNull(user)){
            throw new UsernameNotFoundException("User not Found.");
        }
        List<YaUserRole> roles = userService.listRoleById(username);
        List<GrantedAuthority> authorities = new ArrayList<>(roles.size());
        roles.forEach( role -> authorities.add(new SimpleGrantedAuthority(role.getRoleId())));

        return new User(username, user.getUserPassword(), authorities);
    }
}

5. Security配置: 使用注解@EnableWebSecurity

  • 注意:Spring Security 6 配置不再继承adapterextends WebSecurityConfigurerAdapter,而是使用@EnableWebSecurity
  • YaTokenFilter是token身份认证过滤器,每次请求都会拦截,然后校验请求header中的token,这个下面会讲。
  • 配置了身份认证过滤器以后,每个请求都会被拦截,即使是在过滤链中配置了permitAll(),还是会返回请求403.
    1. 因此,针对匿名请求、静态资源和swagger请求,在WebSecurityCustomizer中配置WebSecurity.ignoring,相当于直接绕过所有的Filter
    2. 针对登录和注册请求,在身份过滤器中额外配置白名单,单独放行。
  • 自己学习的过程中,很多文章没有按照代码执行顺序去讲,登录和身份认证也是混着讲的,导致整个登录认证的流程理解起来有些困难。
/**
 * <p>
 *  Spring Security 配置文件
 * </p>
 *
 * @author Ya Shi
 * @since 2024/2/29 11:27
 */
@Configuration
@EnableWebSecurity // 开启网络安全注解
public class YaSecurityConfig {

    @Autowired
    private AuthenticationConfiguration authenticationConfiguration;

    @Autowired
    private RedisUtils redisUtils;

    @Value("${ya-app.auth.jwt.expiration:1800}")
    private Long expiration;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 禁用basic明文验证
                .httpBasic().disable()
                // 禁用csrf保护
                .csrf().disable()
                // 禁用session
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 身份认证过滤器
                .authenticationManager(authenticationManager(authenticationConfiguration))
                .authenticationProvider(new YaAuthenticationProvider())
                .authorizeHttpRequests(authorizeHttpRequests ->
                        authorizeHttpRequests
                                // 允许OPTIONS请求访问
                                .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                                // 允许登录/注册接口访问
                                .requestMatchers(HttpMethod.POST, "/auth/login").permitAll()
                                .requestMatchers(HttpMethod.POST, "/auth/register").permitAll()
                                // 允许匿名接口访问
                                .requestMatchers("/anon/**").permitAll()
                                // 允许swagger访问
                                .requestMatchers("/swagger-ui/**").permitAll()
                                .requestMatchers("/doc.html/**").permitAll()
                                .requestMatchers("/v3/api-docs/**").permitAll()
                                .requestMatchers("/webjars/**").permitAll()
                                .anyRequest().authenticated()
                )
                .addFilterAt(new YaLoginFilter(authenticationManager(authenticationConfiguration), redisUtils, expiration), UsernamePasswordAuthenticationFilter.class)
                // 让校验Token的过滤器在身份认证过滤器之前
                .addFilterBefore(new YaTokenFilter(redisUtils, expiration), YaLoginFilter.class)
                // 禁用默认登出页
                .logout().disable();
        return http.build();
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                .requestMatchers("/webjars/**")
                .requestMatchers("/swagger-ui/**", "/doc.html/**", "/v3/api-docs/**")
                .requestMatchers("/anon/**");
    }

    /**
     * 使用BCrypt加密密码
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

token身份认证

1. token身份认证过滤器: OncePerRequestFilter

  • 对于注册、登录请求,直接放行。
  • 认证失败的几种情况:
    1. 未登录: 未携带token
    2. 凭证异常: 携带错误token
    3. 登录过期: 携带正确的token,但是token在Redis中不存在
    4. 账号在别处登录: 携带正确的token,但是token与Redis中的token不一致。
  • token认证成功后,重新设置Redis中的token的有效时间,实现token续期。查询Redis中的用户信息,如果没有,使用UserDetailsService的服务重新查询出信息,存入缓存中。
    *调用 SecurityContextHolder.getContext().setAuthentication()将用户信息存入Security上下文中,完成身份认证。
/**
 * <p>
 *  每次请求过滤token
 * </p>
 *
 * @author Ya Shi
 * @since 2024/3/21 16:52
 */
@Slf4j
public class YaTokenFilter extends OncePerRequestFilter {

    private final RedisUtils redisUtils;

    private final Long expiration;

    private static final Set<String> WHITE_LIST = Stream.of(
            "/auth/register",
            "/auth/login"
            ).collect(Collectors.toSet());

    public YaTokenFilter(RedisUtils redisUtils, Long expiration) {
        this.redisUtils = redisUtils;
        this.expiration = expiration;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("YaTokenFilter doFilterInternal start");
        final String authorization = request.getHeader(AuthConstants.TOKEN_HEADER);
        log.info("YaTokenFilter ya-auth-token: {}", authorization);

        // 白名单
        if (WHITE_LIST.contains(request.getServletPath())) {
            chain.doFilter(request, response);
            return;
        }

        // 1.请求头中没有携带token
        if (StrUtil.isBlank(authorization)) {
            ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.FAILED_UNAUTHORIZED));
            return;
        }

        // 携带token
        final String token = authorization.replace(AuthConstants.TOKEN_PREFIX, "");
        String userId;
        // 2.提供的token异常
        try {
            userId =  JwtUtils.extractUserId(token);
        }catch (Exception e){
            log.error("YaTokenFilter doFilterInternal 解析jwt异常:{}", e.toString());
            ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.FAILED_UNAUTHORIZED.code, "凭证异常"));
            return;
        }

        String redisToken = redisUtils.getString(AuthConstants.REDIS_KEY_AUTH_TOKEN + userId);
        // 3.token过期
        if(StrUtil.isBlank(redisToken)){
            ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.FAILED_UNAUTHORIZED.code, "登录已过期,请重新登录过期。"));
            return;
        }

        // 4.提供的token是合法的,但是redis中的token又被使用登录功能重新刷新了一下,导致不一致。
        if(!Objects.equals(redisToken, token)){
            ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.FAILED_UNAUTHORIZED.code, "账号在别处登陆。"));
            return;
        }
        // token续期
        redisUtils.set(REDIS_KEY_AUTH_TOKEN + userId, token, expiration);
        // 获取用户信息和权限
        String userDetailStr =  redisUtils.getString(AuthConstants.REDIS_KEY_AUTH_USER_DETAIL + userId);
        UserDetails userDetails;
        if(Objects.isNull(userDetailStr)){
            userDetails = yaUserDetailService().loadUserByUsername(userId);
            redisUtils.set(REDIS_KEY_AUTH_USER_DETAIL + userId, JSON.toJSONString(userDetails), 1, TimeUnit.DAYS);
        }else{
            userDetails = initUser(userDetailStr);
        }
        SecurityContextHolder.getContext().setAuthentication(
                new UsernamePasswordAuthenticationToken(
                        userDetails, userDetails.getPassword(), userDetails.getAuthorities()
                )
        );
        log.info("YaTokenFilter doFilterInternal end");
        chain.doFilter(request, response);
    }

    private YaUserDetailService yaUserDetailService(){
        return SpringContextUtils.getBean(YaUserDetailService.class);
    }

    private User initUser(String userJsonStr){
        JSONObject userJson = JSON.parseObject(userJsonStr);

        String userId = userJson.getString("username");
        JSONArray authArray = userJson.getJSONArray("authorities");

        List<GrantedAuthority> authorities = new ArrayList<>(authArray.size());
        for(int i=0; i< authArray.size();i++){
            JSONObject authObj = authArray.getJSONObject(i);
            authorities.add(new SimpleGrantedAuthority(authObj.getString("authority")));
        }
        return new User(userId, "[PROTECTED]", authorities);
    }

}

UserAuthUtils

已经登录的用户,可以从Security的上下文中获取用户的账号、基本信息、权限等。可以将其封装为工具类。因为练手的用户表较为简单,也没有部分、员工、角色、权限等概念,因此仅封装了getUserId做抛砖引玉的作用。可以根据实际使用自己封装更多的方法。

getUserId

public static String getUserId() {
        if (Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
            return null;
        }
        UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (Objects.isNull(userDetails)) {
            return null;
        }
        return userDetails.getUsername();
    }

用户登出

JWT本身是无状态的,但是我们后端将jwt存到redis里,相当于手动使JWT变得有状态了。那么我们在登出时就需要清空Redis中的jwt。

实现LogoutSuccessHandler

/**
 * <p>
 *  登出成功
 * </p>
 *
 * @author Ya Shi
 * @since 2024/3/28 10:47
 */
@Slf4j
public class YaLogoutSuccessHandler implements LogoutSuccessHandler {
    private final RedisUtils redisUtils;

    public YaLogoutSuccessHandler(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        final String authorization = request.getHeader(AuthConstants.TOKEN_HEADER);
        // 1.请求头中没有携带token
        if (StrUtil.isBlank(authorization)) {
            ServletUtils.renderResult(response, BaseResult.successWithMessage("没有登录信息,无需退出"));
            return;
        }

        // 携带token
        final String token = authorization.replace(AuthConstants.TOKEN_PREFIX, "");
        String userId;
        // 2.提供的token异常
        try {
            userId =  JwtUtils.extractUserId(token);
        }catch (Exception e){
            log.error("YaLogoutHandler logout 解析jwt异常:{}", e.toString());
            ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.FAILED_UNAUTHORIZED.code, "凭证异常"));
            return;
        }
        // 清空Redis
        redisUtils.delete(REDIS_KEY_AUTH_TOKEN + userId);
        log.info("YaLogoutSuccessHandler onLogoutSuccess");
        ServletUtils.renderResult(response, BaseResult.successWithMessage("退出登录成功"));
    }
}

修改Security配置 : YaSecurityConfig

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	http  ... // 前面的配置忽略
	.logout().logoutUrl("/auth/logout").logoutSuccessHandler(new YaLogoutSuccessHandler(redisUtils));
	return http.build();
}

下一步的计划

  • 用户鉴权
  • 排查permitAll()失效的问题。
  • 做一个练手用的用户中心,提供统一的注册、登录、认证、鉴权服务,供其他的应用调用。
  • 把前期已经实现的基础的配置和工具类封装为jar包,供以后的程序使用。

参考文章

  • SpringBoot3.0 + SpringSecurity6.0+JWT
  • SpringSecurity (3) SpringBoot + JWT 实现身份认证和权限验证
  • Spring Security 6.0(spring boot 3.0) 下认证配置流程
  • SpringSecurity的PermitAll、WebSecurityCustomizer和授权
  • springsecurity的http.permitall与web.ignoring的区别

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

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

相关文章

【机器学习300问】55、介绍推荐系统中的矩阵分解算法是什么、有什么用、怎么用?

本来这篇文章我想先讲矩阵分解算法是什么东西的&#xff0c;但这样会陷入枯燥的定义中去&#xff0c;让原本非常有趣技术在业务场景中直观的使用路径被切断。所以我觉得先通过一个具体的推荐算法的例子&#xff0c;来为大家感性的介绍矩阵分解有什么用会更加合理。 如果你还不知…

iOS开发进阶(十一):ViewController 控制器详解

文章目录 一、前言二、UIViewController三、UINavigationController四、UITabBarController五、UIPageViewController六、拓展阅读 一、前言 iOS 界面开发最重要的首属ViewController和View&#xff0c;ViewController是View的控制器&#xff0c;也就是一般的页面&#xff0c;…

WordPress Git主题 响应式CMS主题模板

分享的是新版本&#xff0c;旧版本少了很多功能&#xff0c;尤其在新版支持自动更新后&#xff0c;该主题可以用来搭建个人博客&#xff0c;素材下载网站&#xff0c;图片站等 主题特点 兼容 IE9、谷歌 Chrome 、火狐 Firefox 等主流浏览器 扁平化的设计加响应式布局&#x…

MySQL count(*/column)查询优化

count()是SQL中一个常用的聚合函数&#xff0c;其被用来统计记录的总数&#xff0c;下面通过几个示例来说明此类查询的注意事项及应用技巧。 文章目录 一、count()的含义二、count()的应用技巧2.1 同时统计多列2.2 利用执行计划 一、count()的含义 count()用于统计符合条件的记…

常用的8个应用和中间件的Docker运行示例

文章目录 1、Docker Web 管理工具 portainer2、在线代码编辑器 Code Server3、MySQL4、Redis5、Nginx6、PostgreSQL7、媒体管理工具 Dim8、Gitlab 1、Docker Web 管理工具 portainer Portainer 是一个轻量级的管理 UI &#xff0c;可让你轻松管理不同的 Docker 环境&#xff0…

API是什么,如何保障API安全

随着移动APP、微服务架构、云、物联网的兴起&#xff0c;企业API数量呈爆发式增长。作为数字世界的连接者&#xff0c;API为企业搭建起了一条内外相连、四通八达的“数据公路”。 API是什么&#xff1f;API&#xff0c;全称Application Programming Interface&#xff0c;即应用…

八大技术趋势案例(区块链量子计算)

科技巨变,未来已来,八大技术趋势引领数字化时代。信息技术的迅猛发展,深刻改变了我们的生活、工作和生产方式。人工智能、物联网、云计算、大数据、虚拟现实、增强现实、区块链、量子计算等新兴技术在各行各业得到广泛应用,为各个领域带来了新的活力和变革。 为了更好地了解…

uniapp对接萤石云 实现监控播放、云台控制、截图、录像、历史映像等功能

萤石云开发平台地址&#xff1a;文档概述 萤石开放平台API文档 (ys7.com) 萤石云监控播放 首先引入萤石云js js地址&#xff1a;GitHub - Ezviz-OpenBiz/EZUIKit-JavaScript-npm: 轻应用npm版本&#xff0c;降低接入难度&#xff0c;适配自定义UI&#xff0c;适配主流框架 vi…

贝朗生物邀您到场参观2024第13届生物发酵展

参展企业介绍 贝朗生物工程设备&#xff08;江苏&#xff09;有限公司是一家专业从事成套发酵设备的研发、制造和销售的企业。我公司与中国科学院、中国农科院、甘肃省科学院、清华大学、兰州大学、天津科技大学、河北农业大学&#xff0c;甘肃农业大学、青海大学、新疆农业大…

服务器停止解析域名,但仍然可以访问到

1.centos7 如何刷新dns缓存 在CentOS 7上&#xff0c;DNS缓存由nscd&#xff08;Name Service Cache Daemon&#xff09;管理&#xff0c;如果系统上安装了nscd&#xff0c;可以通过清除nscd缓存来刷新DNS缓存。 要刷新DNS缓存&#xff0c;请执行以下命令&#xff1a; sudo …

人工智能(pytorch)搭建模型26-基于pytorch搭建胶囊模型(CapsNet)的实践,CapsNet模型结构介绍

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能(pytorch)搭建模型26-基于pytorch搭建胶囊模型(CapsNet)的实践&#xff0c;CapsNet模型结构介绍。CapsNet&#xff08;Capsule Network&#xff09;是一种创新的深度学习模型&#xff0c;由计算机科学家Geo…

前后端分离开发【Yapi平台】【Swagger注解自动生成接口文档平台】

前后端分离开发 介绍开发流程Yapi&#xff08;api接口文档编写平台&#xff09;介绍 Swagger使用方式1). 导入knife4j的maven坐标2). 导入knife4j相关配置类3). 设置静态资源映射4). 在LoginCheckFilter中设置不需要处理的请求路径 查看接口文档常用注解注解介绍 当前项目中&am…

Gitlab CI---could not read username for xxx: no such device or address

0 Preface/Foreword 项目开发中&#xff0c;经常会使用第三方的算法或者功能&#xff0c;那么就需要把对应的repo以子模块的方式添加到当前repo中。 添加命令&#xff1a; git submodule add <URL> 1 问题表现 子模块添加成功&#xff0c;但是GitLab CI阶段&#xff…

(C++) 属性说明符-标准属性

文章目录 前言标准属性&#x1f3f7;️noreturn⭐(C11) 指示函数不返回 &#x1f3f7;️carries_dependency⭐(C11) 指示在函数内外传播“释放-消费” std::memory_order 中的依赖链 &#x1f3f7;️deprecated⭐(C14) 指示以此属性声明的名字或实体&#xff0c;允许使用但因某…

GPT:多轮对话并搭建简单的聊天机器人

1 多轮对话 多轮对话能力至关重要&#xff0c;它不仅能深化交流&#xff0c;精准捕捉对方意图&#xff0c;还能促进有效沟通&#xff0c;增强理解。在智能客服、教育辅导等领域&#xff0c;多轮对话更是提升服务质量、增强用户体验的关键。 注意&#xff1a;大模型没有多轮对话…

如何在 Oracle 中使用 CREATE SEQUENCE 语句

在本文中&#xff0c;我们将讨论 Oracle CREATE SEQUENCE 语句&#xff0c;其主要目的是提供一种可靠的方法来生成唯一且连续的数值&#xff0c;通常用于数据库表中的主键字段。此功能对于维护数据完整性和效率、确保不同记录之间的标识符有序分配尤其重要。从本质上讲&#xf…

STM32G473之flash存储结构汇总

STM32G4系列单片机&#xff0c;为32位的微控制器&#xff0c;理论上其内部寄存器地址最多支持4GB的命名及查找&#xff08;2的32次方&#xff0c;地址命名为0x00000000至0xFFFFFFFF&#xff09;。STM32官方对4GB的地址存储进行编号时&#xff0c;又分割成了8个block区域&#x…

【python】网络编程socket TCP UDP

文章目录 socket常用方法TCP客户端服务器UDP客户端服务器网络编程就是实现两台计算机的通信 互联网协议族 即通用标准协议,任何私有网络只要支持这个协议,就可以接入互联网。 socket socke模块的socket()函数 import socketsock = socket.socket(Address Family, type)参…

SQLyog连接MySQL8.0+报错:错误码2058的解决方案

最近把mysql从5.7迁移到8.3.0发现连接不上 因为 MySQL 从 8.0 版本开始&#xff0c;新增了caching_sha2_password授权插件 技术博客 http://idea.coderyj.com/ 1.更换sqlyog 更新到13.1.3之后的版本 2.取消mysql8的加密授权机制 mysql> ALTER USER sqlyog% IDENTIFIED WIT…

ArcGIS制作风向频率玫瑰图

风玫瑰图是气象科学专业统计图表,用来统计某个地区一段时期内风向、风速发生频率,又分为“风向玫瑰图”和“风速玫瑰图” ;因图形似玫瑰花朵,故名。风玫瑰图对于涉及城市规划、环保、风力发电等领域有着重要的意义。风玫瑰图能够直观的显现某地区不同方位风向的频率特征,进…
最新文章