Spring Security 框架篇-深入了解 Spring Security 的授权核心功能(RBAC 权限模型、自定义异常处理器、校验权限方法)

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍

文章目录

        1.0 权限系统

        1.1 引入

        1.2 RBAC 权限模型

        1.3 数据库设计

        2.0 Spring Security 核心功能-授权

        2.1 思路分析

        2.2 编写 SQL 语句

        2.3 将用户权限进行封装

        2.4 获取用户权限

        2.5 启动预授权

        3.0 自定义异常处理器

        4.0 自定义校验权限方法


        1.0 权限系统

        在 Java 开发中,Spring Security 是一个非常强大的框架,用于处理应用程序的安全性。它不仅提供了认证(Authentication)功能,还提供了授权(Authorization)功能,确保用户只能访问他们被授权的资源。

        Spring Security 框架中对认证功能进行介绍:

Spring Security 框架篇-深入了解 Spring Security 的认证功能流程和自定义实现登录接口(实现自定义认证过滤器、登出功能)-CSDN博客

        1.1 引入

Spring Security 的授权功能主要围绕着几个核心概念构建:

        1)权限(Permissions):
        权限是最低级别的安全控制,通常与特定的操作或资源相关联。例如,read、write、delete等权限可以被分配给特定的数据对象或业务操作。
        2)角色(Roles):
        角色是一组权限的集合。在应用中,通常会根据职责的不同来定义不同的角色,如管理员、普通用户、访客等。每个角色拥有一系列权限,这些权限定义了该角色可以执行的操作。
        3)访问决策管理器(Access Decision Manager):
        这个组件负责决定是否允许请求访问受保护的资源。它基于配置的策略(如一致同意、多数同意等)来评估所有相关的权限和角色。
        4)投票者(Voters):
        投票者是访问决策管理器的一部分,它们根据用户的权限对访问请求进行投票。投票结果影响最终的访问决策。
        5)方法安全(Method Security):
        Spring Security 提供了在方法级别上实现细粒度安全控制的能力。通过使用注解如 @PreAuthorize 和 @PostAuthorize,可以为特定的方法调用设置访问控制规则。
        6)URL 安全(URL Security):
        通过配置 HTTP 请求的安全性,可以限制对特定 URL 模式的访问。这通常是通过在 HttpSecurity 配置中定义匹配模式和相应的访问规则来实现的。

        1.2 RBAC 权限模型

        RBAC,即基于角色的访问控制,是一种广泛使用的安全模型,它将权限与角色关联起来,而不是直接与用户关联。这样做的好处包括但不限于:
        简化管理:通过管理少量的角色及其权限,而不是大量的个人用户权限,大大减少了管理工作量。
        提高灵活性:当组织结构或业务需求发生变化时,可以通过调整角色和权限来快速适应,而不需要逐个修改用户权限。
        增强安全性:通过最小权限原则,每个用户只拥有完成其工作所需的最少权限,从而减少潜在的安全风险。

在 Spring Security 中实现 RBAC 模型通常涉及以下步骤:
        定义角色:根据应用的需求定义不同的角色。
        分配权限:为每个角色分配适当的权限。
        用户角色映射:将用户与一个或多个角色关联起来。
        配置安全策略:利用 Spring Security 提供的工具(如 @Secured 注解、@PreAuthorize 表达式等)来定义哪些角色可以访问哪些资源或执行哪些操作。
通过这种方式,Spring Security 可以有效地支持复杂的权限管理和访问控制需求,帮助开发者构建更加安全可靠的应用程序。

        1.3 数据库设计

如果将其分解进行数据库设计,RBAC0基本模型可以分成以下五个部分:

        1)User(用户):每个用户都有唯一的UID识别,并被授予不同的角色
        2)Role(角色):不同角色具有不同的权限
        3)Permission(权限):访问权限
        4)用户-角色映射:用户和角色之间的映射关系
        5)角色-权限映射:角色和权限之间的映射

转换成数据库表的话大致如下所示:

        这就可以实现给用户进行授权相关的权限。

根据业务需求,对数据库进行设计:

-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `menu_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NULL' COMMENT '菜单名',
  `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '路由地址',
  `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '组件路径',
  `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
  `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '#' COMMENT '菜单图标',
  `create_by` bigint(0) NULL DEFAULT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL,
  `update_by` bigint(0) NULL DEFAULT NULL,
  `update_time` datetime(0) NULL DEFAULT NULL,
  `del_flag` int(0) NULL DEFAULT 0 COMMENT '是否删除(0未删除 1已删除)',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '菜单表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, '部门管理', ' dept', ' system/dept/index', '0', '0', 'system:dept:list', '#', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (2, ' 测试', 'test', 'system/test/index', '0', '0', 'system:test:list', '#', NULL, NULL, NULL, NULL, 0, NULL);

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色权限字符串',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '角色状态(0正常 1停用)',
  `del_flag` int(0) NULL DEFAULT 0 COMMENT 'del_flag',
  `create_by` bigint(0) NULL DEFAULT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL,
  `update_by` bigint(0) NULL DEFAULT NULL,
  `update_time` datetime(0) NULL DEFAULT NULL,
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'CEO', 'ceo', '0', 0, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `sys_role` VALUES (2, 'Coder', ' coder', '0', 0, NULL, NULL, NULL, NULL, NULL);

-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu`  (
  `role_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `menu_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '菜单id',
  PRIMARY KEY (`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (1, 1);
INSERT INTO `sys_role_menu` VALUES (1, 2);
INSERT INTO `sys_role_menu` VALUES (2, 2);

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NULL' COMMENT '用户名',
  `nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NULL' COMMENT '昵称',
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NULL' COMMENT '密码',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
  `email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱',
  `phonenumber` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号',
  `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
  `avatar` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像',
  `user_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',
  `create_by` bigint(0) NULL DEFAULT NULL COMMENT '创建人的用户id',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint(0) NULL DEFAULT NULL COMMENT '更新人',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `del_flag` int(0) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'xbs', '小扳手', '$2a$10$WCD7xp6lxrS.PvGmL86nhuFHMKJTc58Sh0dG1EQw0zSHjlLFyFvde', '0', NULL, NULL, NULL, NULL, '1', NULL, NULL, NULL, NULL, 0);
INSERT INTO `sys_user` VALUES (2, 'test', '测试用户', '$10$WCD7xp6lxrS.PvGmL86nhuFHMKJTc58Sh0dG1EQw0zSHjlLFyFvde', '0', NULL, NULL, NULL, NULL, '1', NULL, NULL, NULL, NULL, 0);

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `user_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `role_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '角色id',
  PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);

        2.0 Spring Security 核心功能-授权

        2.1 思路分析

        在登录验证过程中,即在实现 UserDetailsService 接口的 MyUserDetailService 实现类里面除了将用户的基本信息进行封装返回,还需要将用户的权限信息也要进行封装返回。

        2.2 编写 SQL 语句

以用户 "xbs" 为例子:

        当然可以直接通过多个表关联查询,这里将 SQL 分开是为了更好梳理用户、角色、权限之间的关系。

        2.3 将用户权限进行封装

        由于需要封装到 LoginUser 类中,则添加 List<String> permissions 成员变量来存放权限信息,且为了其他类可以获取到这些权限信息,还需要重写 getAuthorities() 方法,返回权限信息。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
    /** 使用构造方法初始化 */
    private User user;

    /** 存储权限信息 */
    private List<String> permissions;

    /** 存储SpringSecurity所需要的权限信息的集合 */
    @JSONField(serialize = false)
    private List<GrantedAuthority> authorities;



    /** 重写getAuthorities方法,将String类型的权限List转换成GrantedAuthority类型的集合 */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (authorities != null){
            return authorities;
        }
        authorities = permissions.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return authorities;
    }

    public LoginUser(User user, List<String> roles) {
        this.user = user;
        this.permissions = roles;
    }

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

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



    /** 下面的方法暂时全部都让他们返回true */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

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

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

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

        如果其他类频繁的调用  getAuthorities() 方法来获取权限信息,就会重复的进行封装 GrantedAuthority 实体类,因此,当 authorities 不为 Null,说明之前已经被调用 getAuthorities() 方法来获取权限信息,现在直接可以从 authorities 实体获取权限信息即可。

        这里需要注意的是因为 SimpleGrantedAuthority 无法序列化,如果直接使用,fastjson 转换时就会出错,即 Redis 存取时会运行异常,因此我们需要使用 @JSONField 关闭其序列化; 

MyUserDetailService 类:

@Service
public class MyUserDetailService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //根据username查询数据库
        User user = userMapper.selectByUserName(username);
        if (user == null){
            throw new RuntimeException("用户不存在");
        }

        //还需要查询用户权限信息
        List<String> roles = userMapper.selectUserRoleByUserId(user.getId());

        //将查询到的结果user和权限进行封装
        return new LoginUser(user,roles);
    }
}

        2.4 获取用户权限

        在登录认证中,需要获取用户权限,且将用户权限存放到 SecurityContextHolder 容器中,方便在后续的过滤器进行权限校验。

        在 MySecurityFilter 登录认证过滤器中,验证成功之后,将获取的用户权限加载到 SecurityContextHolder 容器,在后续的 FlterSecurityInterceptor 权限校验器中进行校验,如果校验失败则会抛出 403 异常。

代码如下:

MySecurityFilter 登录认证过滤器

@Configuration
public class MySecurityFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //先去尝试获取从请求头中获取token数据
        String jwt = request.getHeader("token");
        //判断token是否为null
        if (StringUtils.isNullOrEmpty(jwt)){
            //这里有两种情况
            //第一种就是正在去登录过程
            //第二种就是token过期了
            //因此直接放行即可,在SpringSecurity后续的过滤器中会进行判断
            filterChain.doFilter(request,response);
            return;
        }
        //如果token不为null,则需要去解析token
        String userId;
        try {
            //解析token
            userId = JwtUtil.parseJWT(jwt).getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            //解析失败,直接返回错误信息
            throw new RuntimeException("token非法");
        }

        //根据用户id去Redis中查找缓存信息
        LoginUser loginUser = redisCache.getCacheObject("login:" + userId);
        if (Objects.isNull(loginUser)){
            throw new RuntimeException("用户登录已过期,请重新登录");
        }
        //获取用户权限信息
        Collection<? extends GrantedAuthority> authorities = loginUser.getAuthorities();
        //将用户的基本信息与权限信息封装到UsernamePasswordAuthenticationToken中
        UsernamePasswordAuthenticationToken loginUserToken
                = new UsernamePasswordAuthenticationToken(loginUser,null,authorities);
        //再将封装好的信息放入SecurityContextHolder中,为了后续的过滤器进行判断
        SecurityContextHolder.getContext().setAuthentication(loginUserToken);
        //放行
        filterChain.doFilter(request,response);

    }
}

        2.5 启动预授权

        Spring Security 为我们提供了基于注解的权限控制方案,可以使用注解去指定访问对应的资源所需的权限,需要手动开启,在 SecurityConfig 配置类上开启预授权功能。

        在开启了预授权之后,就可以使用 @PreAuthorize 注解对接口进行权限控制了,该注解中使用 SPEL 表达式进行权限配置,表达式中的参数为权限名,对应我们获取的 perms 权限集合。

举个例子:

        使用 @PreAuthorize 注解,调用 hasAuthority("权限标识") 方法,如果该用户存在该权限,则返回 true,证明该用户有权限访问该接口;如果该用户不存在该权限,则返回 false,说明该用户不存在该权限,不允许访问当前接口。

相关源码:

        3.0 自定义异常处理器

        这写着认证授权时我们会发现有些地方我们是手动抛了异常的,但是为什么在返回的数据中并没有我们想要的数据呢?

        这是因为在 Spring Security 中默认对抛出的异常进行了捕获并对其进行了处理,因此就会返回上图中的数据。但是没明显我们并不是很想让他返回这种数据,而是在抛异常时也能返回我们的统一返回格式,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道 Spring Security 的异常处理机制。

        在 Spring Security 过滤链中,可以通过重写相关的方法来自定义异常处理器。

1)对于登录验证过程中抛出的异常:

        需要实现 AuthenticationEntryPoint 接口,重写 commence() 方法,从而来手动自定义异常处理器。

        这个可以使用统一返回格式,使用 Result 对象封装好相关的信息,然后通过 httpServletResponse.getWriter() 方法得到 PrintWriter 对象,再由该对象调用 write() 方法写到客户端。

        举个简单的例子,这里就直接返回字符串给客户端了。

@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        PrintWriter writer = httpServletResponse.getWriter();
        writer.write("登录验证过程中抛出的异常");
    }
}

2)对于授权验证过程中抛出的异常:

        需要实现 AccessDeniedHandler 接口,重写 handle() 方法。

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        PrintWriter writer = httpServletResponse.getWriter();
        writer.write("权限不足");
    }
}

        在编写完成对应的处理器之后,需要配置给 Spring Security 框架,让框架使用我们自定义的异常处理器,这同样是在 SecurityConfig 配置类中的 configure 方法进行配置。

SecurityConfig 配置类:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 将BCryptPasswordEncoder加入到容器中
     **/
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

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

    @Autowired
    private MySecurityFilter mySecurityFilter;

    @Autowired
    private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
    @Autowired
    private AccessDeniedHandlerImpl accessDeniedHandlerImpl;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/userLogin").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        // 参数列表分别为需要插入的过滤器和标识过滤器的字节码
        http.addFilterBefore(mySecurityFilter, UsernamePasswordAuthenticationFilter.class);

        //配置自定义异常处理器
        http.exceptionHandling()
                .authenticationEntryPoint(myAuthenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandlerImpl);
    }
}

        4.0 自定义校验权限方法

        在 Spring Security 框架中,一般是通过 @PreAuthorize("hasAuthority('权限标识')") 这种方法来进行校验的,也可以手动自定义来创建校验权限的方法,会更加灵活。

        首先定义一个实体类,且将该类交给 IOC 容器成为 Bean 对象:

@Component("ex")
public class ExpressionRoot {

    public boolean hasAuthority(String authority){
        //先获取当前用户权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser principal = (LoginUser) authentication.getPrincipal();
        //再获取权限集合
        List<String> permissions = principal.getPermissions();
        //判断用户权限集合中是否存在该权限
        return permissions.contains(authority);
    }
}

        在 SecurityContextHolder 容器中获取用户权限信息,直接调用 getPermissions() 方法获取到权限集合。接着判断用户权限集合是否包含当前接口所需要的权限 authority 。

        然后在 @PreAuthorize 注解中调用该类的方法即可:

    @GetMapping("/hello")
    @PreAuthorize("@ex.hasAuthority('system:test:list')")
    public String hello(){
        return "hello xbs";
    }

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

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

相关文章

STM32G0xx使用LL库将Flash页分块方式存储数据实现一次擦除可多次写入

STM32G0xx使用LL库将Flash页分块方式存储数据实现一次擦除可多次写入 参考例程例程说明一、存储到Flash中的数据二、Flash最底层操作(解锁&#xff0c;加锁&#xff0c;擦除&#xff0c;读写)三、从Flash块中读取数据五、测试验证 参考例程 STM32G0xx HAL和LL库Flash读写擦除操…

Spark SQL大数据分析快速上手-DataFrame应用体验

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 本节主要介绍如何使用DataFrame进行编程。 4.1.1 SparkSession 在旧版本中&#xff0c;Spark SQL提供…

QT信号和槽与自定义的信号和槽

QT信号和槽与自定义的信号和槽 1.概述 这篇文章介绍下QT信号和槽的入门知识&#xff0c;通过一个案例介绍如何创建信号和槽&#xff0c;并调用他们。 2.信号和槽使用 下面通过点击按钮关闭窗口的案例介绍如何使用信号和槽。 创建按钮 在widget.cpp文件中创建按钮代码如下 …

YOLO11改进 | 融合改进 | C3k2融合 Context Anchor Attention 【两个版本融合-独家创新】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文给大家带来的教程是将YOLO11的C3k2替…

机械制造工控自动化监控界面:功能与美观兼具

机械制造工控自动化监控界面需做到功能与美观兼具。在功能方面&#xff0c;清晰展示设备运行状态、参数指标等关键信息&#xff0c;提供实时监控和预警功能&#xff0c;确保生产安全高效。 界面布局应合理&#xff0c;操作简便&#xff0c;便于工作人员快速掌握和操作。而在美…

SpringBoot项目集成ONLYOFFICE

ONLYOFFICE 文档8.2版本已发布&#xff1a;PDF 协作编辑、改进界面、性能优化、表格中的 RTL 支持等更新 文章目录 前言ONLYOFFICE 产品简介功能与特点Spring Boot 项目中集成 OnlyOffice1. 环境准备2. 部署OnlyOffice Document Server3. 配置Spring Boot项目4. 实现文档编辑功…

explain执行计划分析 ref_

这里写目录标题 什么是ExplainExplain命令扩展explain extendedexplain partitions 两点重要提示本文示例使用的数据库表Explain命令(关键字)explain简单示例explain结果列说明【id列】【select_type列】【table列】【type列】 【possible_keys列】【key列】【key_len列】【ref…

AIDOVECL数据集:包含超过15000张AI生成的车辆图像数据集,目的解决旨在解决眼水平分类和定位问题。

2024-11-01&#xff0c;由伊利诺伊大学厄巴纳-香槟分校的研究团队创建的AIDOVECL数据集&#xff0c;通过AI生成的车辆图像&#xff0c;显著减少了手动标注工作&#xff0c;为自动驾驶、城市规划和环境监测等领域提供了丰富的眼水平车辆图像资源。 数据集地址&#xff1a;AIDOV…

24/11/7 算法笔记 PCA主成分分析

假如我们的数据集是n维的&#xff0c;共有m个数据(x,x,...,x)。我们希望将这m个数据的维度从n维降到k维&#xff0c;希望这m个k维的数据集尽可能的代表原始数据集。我们知道数据从n维降到k维肯定会有损失&#xff0c;但是我们希望损失尽可能的小。那么如何让这k维的数据尽可能表…

2-142【软件无线电原理与应用作业】基于matlab的圆形阵列的波束形成进行仿真

【软件无线电原理与应用作业】基于matlab的圆形阵列的波束形成进行仿真&#xff0c;具有14页文档。假设发射信号载频为1GHz&#xff0c;圆形阵列半径为0.8米&#xff0c;在圆周上均匀布置30个阵元。1.画出指向0度的方向图。2.如果目标在0度&#xff0c;有一不相干的干扰信号在3…

<项目代码>YOLOv8 苹果腐烂识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

python练习相关代码

一元二次方程的求根公式为&#xff1a; import mathdef quadratic(a, b, c):discriminant b**2 - 4*a*cif discriminant < 0:return Noneelif discriminant 0:return [-b / (2*a)]else:root1 (-b math.sqrt(discriminant)) / (2*a)root2 (-b - math.sqrt(discriminant)…

2024软件测试面试热点问题

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 大厂面试热点问题 1、测试人员需要何时参加需求分析&#xff1f; 如果条件循序 原则上来说 是越早介入需求分析越好 因为测试人员对需求理解越深刻 对测试工…

windows、linux安装jmeter及设置中文显示

系列文章目录 1.windows、linux安装jmeter及设置中文显示 2.jmeter常用配置元件介绍总结之安装插件 3.jmeter常用配置元件介绍总结之取样器 windows、linux安装jmeter及设置中文显示 前言一、jdk安装1.windows安装jdk1.1.复制环境变量快捷跳转 2.linux安装jdk 二、下载安装jmet…

各种数据库介绍

1. 关系型数据库&#xff08;RDBMS&#xff09; MySQL • 特点&#xff1a;开源、免费&#xff0c;社区版功能强大且稳定。支持大量的并发连接&#xff0c;常用于Web应用。 • 适用场景&#xff1a;中小型网站、博客、电商等。 PostgreSQL • 特点&#xff1a;功能丰富&#xf…

【linux】查看不同网络命名空间的端口

在部署harbor时&#xff0c;内部用的是数据库postgresql&#xff0c;端口默认是: 5432&#xff0c;一开始以为这个数据库docker容器是在本命名空间中&#xff0c;一直用ss -lnt查询系统的端口&#xff0c;找不到5432端口。但是harbor要能正常使用&#xff0c;所有怀疑harbor的容…

使用ffmpeg和mediamtx模拟多通道rtsp相机

首先下载ffmpeg&#xff0c;在windows系统上直接下载可执行文件&#xff0c;并配置环境变量即可在命令行当中调用执行。 下载地址&#xff1a; https://ffmpeg.org/再在github上下载mediamtx搭建rtsp服务器&#xff0c;使用ffmpeg将码流推流到rtsp服务器。 下载地址&#xff1…

大数据分库分表方案

分库分表介绍 分库分表应用场景 分库分表介绍 大数据分库分表是一种数据库架构技术&#xff0c;旨在应对大数据量场景下的数据库性能瓶颈。以下是对大数据分库分表的详细解释&#xff1a; 一、定义与背景 定义&#xff1a; 分库&#xff1a;将一个大型数据库按照一定的规则…

关于word 页眉页脚的一些小问题

去掉页眉底纹&#xff1a; 对文档的段落边框和底纹进行设置&#xff0c;也是页眉横线怎么删除的一种解决方式&#xff0c;具体操作如下&#xff1a; 选中页眉中的横线文本&#xff1b; 点击【开始】选项卡&#xff0c;在【段落】组中点击【边框】按钮的下拉箭头&#xff1b; …

爬虫-------字体反爬

目录 一、了解什么是字体加密 二. 定位字体位置 三. python处理字体 1. 工具库 2. 字体读取 3. 处理字体 案例1:起点 案例2:字符偏移: 5请求数据 - 发现偏移量 5.4 多套字体替换 套用模板 版本1 版本2 四.项目实战 1. 采集目标 2. 逆向结果 一、了解什么是…