用户登录认证和权限授权(SpringSecurity、JWT、session)

文章目录

  • 前言
  • 一、登录认证
    • 1. 问题引入
    • 2. Session
      • 2.1 实现原理
      • 2.2 过滤器Filter
      • 2.3 上下文对象
    • 3. JWT
      • 3.2 实现步骤
      • 3.3 拦截器 HandlerInterceptorAdapter
      • 3.4 上下文对象
    • 4. Session VS JWT
  • 二、权限授权
    • 1. 权限类型
      • 1.1 页面权限(菜单项权限)
        • 1.2 ACL模型 (Access Control List访问控制列表)不推荐
        • 1.3 RBAC模型(Role-Based Access Controller基于角色访问控制模型)
    • 1.2 接口权限(操作权限或按钮权限)
    • 1.3 数据权限
  • 三、SpringSecurity
    • 1. 概述
    • 2. 登录认证
      • 2.1 了解各个组件
      • 2.2 JWT+SpringSecurity
    • 3. 权限授权
  • 总结


前言

登录认证和权限授权是所有项目中必不可少的功能,本篇文章首先将通过最简单的方式(Session和JWT)实现登录认证和权限授权,然后再整合Springsecurity框架实现。


一、登录认证

登录认证简言之就是对用户的身份进行确认,判断用户的账号和密码是否正确。

1. 问题引入

HTTP请求是一个无状态的协议,每一次发送的请求都是独立的,需要一种机制来记住用户登录过? 需要凭证
怎么保存凭证?
Session/JWT ,原理都是token机制

  1. 前端发起登录认证请求
  2. 后端登录验证通过,返回给前端一个凭证
  3. 前端发起新的请求时携带凭证

2. Session

有状态的管理机制
如果用户第一次访问某个服务器时,服务器响应数据时会在响应头的Set-Cookie标识里将Session Id返回给浏览器,浏览器就将标识中的数据存在Cookie中,浏览器后续访问服务器就会携带Cookie。
在这里插入图片描述

2.1 实现原理

在这里插入图片描述

2.2 过滤器Filter

问题引入:除了登录接口外,我们其他接口都要在Controller层里做登录判断,这太麻烦了。
解决:使用过滤器拦截,判断有没有登录,登录就放行,没登录结束请求

2.3 上下文对象

问题引入:要在service层获取操作用户对象,需要从Controller层传参过来太麻烦了。
解决:通过SpringMVC提供的RequestContextHolder对象在程序任何地方获取到当前请求对象,从而获取我们保存在HttpSession中的用户对象,写一个上下文对象来实现这一功能。
然后在Service层直接调用我们写的方法就可以获取到用户对象

3. JWT

Json web token。无状态管理机制

  1. 可以将一段数据加密成一段字符串,也可以从这字符串解密回数据
  2. 可以对这个字符串进行校验,比如有没有过期,有没有被篡改
    在这里插入图片描述

3.2 实现步骤

1、写一个JWT的工具类,工具类就提供两个方法一个生成一个解析
2、工具类做好之后我们可以开始写登录接口,登录的时候生成token
3、后续会话中,用户访问其他接口时就可以校验token来判断其是否已经登录。
一般来说,前端将token一般会放在请求头的Authorization项传递过来,其格式一般为类型 + token
调用其他接口的时候,从请求头中获取token,然后再通过JWT工具类解析token判断。

3.3 拦截器 HandlerInterceptorAdapter

如果每个接口都要手动判断一下用户有没有登录太麻烦了,所以我们做一个统一处理。
拦截器类写好之后,别忘了要使其生效,这里我们直接让SpringBoot启动类实现WebMvcConfigurer接
口,重写addInterceptors方法

3.4 上下文对象

在一个线程中横跨若干方法调用,需要传递的对象,我们通常称之为上下文(Context)
JWT不像Session把用户信息直接存储起来,所以JWT的上下文对象要靠我们自己来实现。
这个类专门存储JWT解析出来的用户信息。我们要用到ThreadLocal存储用户信息,以防止线程冲突
通过上下文对象类(如UserContext),可以在程序的其他地方直接获取到数据。上下文对象.方法()

public final class UserContext {
    private static final ThreadLocal<String> user = new ThreadLocal<String>();

    public static void add(String userName) {
        user.set(userName);
    }
    public static void remove() {
        user.remove();
    }
    /**
     * @return 当前登录用户的用户名
     */
    public static String getCurrentUserName() {
        return user.get();
    }
}

4. Session VS JWT

  • Session是有状态的,JWT是无状态的
  • Session在服务端保存了用户信息,而JWT在服务端没有保存任何信息。
  • 当前端携带Session Id到服务端时,服务端要检查其对应的HttpSession中有没有保存用户信息,保存了就代表登录了
  • 当使用JWT时,服务端只需要对这个字符串进行校验,校验通过就代表登录了

二、权限授权

对用户能否问某个资源进行确认。认证成功之后,再确认能访问什么。
权限系统的设计,第一步就是考虑要保护什么资源,再接着思考如何保护这个资源。

1. 权限类型

1.1 页面权限(菜单项权限)

有权限的用户就会显示所有菜单,无权限的用户就只会显示部分菜单
在这里插入图片描述
一个页面(菜单)对应一个URI地址,当用户登录的时候判断这个用户拥有哪些页面权限,自然
而然就知道要渲染出什么导航菜单。

1.2 ACL模型 (Access Control List访问控制列表)不推荐

数据库设计三张表格,一张表用来存储用户信息,一张表存储资源路径,中间表用来映射用户和资源表的关系。
在这里插入图片描述具体实现
前端给后端发请求后,后端回返回数据给前端,这些数据中就包含了用户能够访问的资源信息,前端本地也存有一个映射字典,字典里有资源的信息,比如id对应哪个路径、名称等等,前端拿到了用户的id后根据字典进行判断就可以做到相应的功能。

1.3 RBAC模型(Role-Based Access Controller基于角色访问控制模型)

很多用户的权限都是相同的,通过封装一层角色信息,实现角色和权限绑定,用户和角色绑定。
在这里插入图片描述

1.2 接口权限(操作权限或按钮权限)

前后端分离的模式下,后端在登录的时候将权限数据甩给前端后就再也不管了,如果此时用户的权限发生变化是无法通知前端的,并且数据存储在前端也容易被用户直接篡改,所以很不安全。
例如:没有这个删除权限的人就不会显示该按钮,或者该按钮被禁用.
页面渲染不走后端,但接口可必须得走后端,只需要对每个接口进行一个权限判断。
实现方式

  1. 接口扫描
    RequestMappingInfoHandlerMapping,这个类可以拿到所有你声明的web接口信息
    通过代码将接口信息批量添加到数据库,标记一下哪些接口是否需要被权限管理(注解形式)
  2. 接口扫描具体实现
    使用SpringBoot提供的ApplicationRunner接口来进行处理,重写该接口的方法会在程序启动时被
    执行。
  3. 拦截器
    每一个权限安全判断都是写在方法内,且这个逻辑判断代码都是一样的,会有重复代码
    获取请求的最佳匹配路径,例如:/API/user/test/{id}路径参数
    拦截器中获取权限数据现在是直接查的数据库,实际开发中一定要将权限数据存在缓存里(如Redis)

1.3 数据权限

不同用户所能访问到的数据不同。
1、硬编码,通过编写sql语句(不推荐)
2、Mybatis拦截插件
提供了一个Interceptor接口,实现该接口定义一个拦截器对sql语句进行拦截达到数据过滤效果

三、SpringSecurity

1. 概述

Web系统中登录认证(Authentication)的核心就是凭证机制,无论是Session还是JWT,都是在用户成功登录时返回给用户一个凭证,后续用户访问接口需携带凭证来标明自己的身份。权限授权(Authorization)是对用户能否访问某个资源进行确认,这种通用逻辑都是放在过滤器里进行的统一操作。
Spring Security对Web系统的支持就是基于这一个个过滤器组成的过滤器链
在这里插入图片描述
Spring Security的核心逻辑全在这一套过滤器中,过滤器里会调用各种组件完成功能,掌握了这些过滤器和组件就掌握了Spring Security。
在这里插入图片描述
使用Spring Security肯定是要先引入依赖包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. 登录认证

2.1 了解各个组件

了解各个组件实现简单的登入认证,基于Session机制。
确认当前是哪个用户正在使用我们系统就是登录认证的最终目的
这一概念在Spring Security中的体现就是 Authentication,它存储了认证信息,代表当前登录用户。
通过 SecurityContext(上下文对象) 来获取Authentication
上下文对象则是交由 **SecurityContextHolder **进行管理,用来在程序任何地方获取SecurityContext

Authentication   authentication =  SecurityContextHolder.getContext().getAuthentication()

在这里插入图片描述
认证信息
Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象
Credentials:用户凭证,一般是密码
Authorities:用户权限
认证流程
登录接口代码

@RestController
@RequestMapping("/API")
public class LoginController {
    @Autowired
    private AuthenticationManager authenticationManager;
    @PostMapping("/login")
    public String login(@RequestBody LoginParam param) {
        // 生成一个包含账号密码的认证信息
        Authentication token = new UsernamePasswordAuthenticationToken(param.getUsername(), param.getPassword());
        // AuthenticationManager校验这个认证信息,返回一个已认证的Authentication
        Authentication authentication = authenticationManager.authenticate(token);
        // 将返回的Authentication存到上下文中
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return "登录成功";
    }
}

AuthenticationManager认证方式
Spring Security用于执行身份验证的组件,只需要调用它的authenticate方法即可完成认证
默认的认证方式就是在UsernamePasswordAuthenticationFilter这个过滤器中调用这个组件,该过滤器
负责认证逻辑。
加密器PasswordEncoder(重写)
实现此接口定义自己的加密规则和校验规则,或者采用Spring Security提供的加密器实现

public interface PasswordEncoder {
    //加密
    String encode(CharSequence rawPassword);
    //将未加密的字符串(前端传递过来的密码)和已加密的字符串(数据库中存储的密码)进行校验
    boolean matches(CharSequence rawPassword, String encodedPassword);
}

配置类里配置

@Bean
public PasswordEncoder passwordEncoder() {
    // bcrypt加密算法,安全性比较高
    return new BCryptPasswordEncoder();
}

往数据库中添加用户数据时就要将密码进行加密,否则后续进行密码校验时从数据库拿出来的还是明文密码。
实现用户注册接口

@Autowired
private PasswordEncoder passwordEncoder;
@PostMapping("/register")
public String register(@RequestBody UserParam param) {
    UserEntity user = new UserEntity();
    // 调用加密器将前端传递过来的密码进行加密
    user.setUsername(param.getUsername()).setPassword(passwordEncoder.encode(param.getPassword()));
    // 将用户实体对象添加到数据库
    userService.save(user);
    return "注册成功";
}

用户对象UserDetails (重写)
提供了用户的一些通用属性

public interface UserDetails extends Serializable {
   // 用户权限集合
   Collection<? extends GrantedAuthority> getAuthorities();
   // 用户密码
   String getPassword();
   // 用户名
   String getUsername();
   // 用户没过期返回true,反之则false
   boolean isAccountNonExpired();
   //......
}

以上默认属性满足不了实际开发需求,可以继承Spring Security提供org.springframework.security.core.userdetails.User类,该类实现了
UserDetails接口帮我们省去了重写方法的工作

public class UserDetail extends User {
    //我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。
    private UserEntity userEntity;
    public UserDetail(UserEntity userEntity, Collection<? extends GrantedAuthority> authorities) {
        // 必须调用父类的构造方法,以初始化用户名、密码、权限
        super(userEntity.getUsername(), userEntity.getPassword(), authorities);
        this.userEntity = userEntity;
    }
}

业务对象UserDetailsService(重写)

public interface UserDetailsService {
    //根据用户名获取用户对象(获取不到直接抛异常)
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

可以自己定义逻辑

@Service
public class UserServiceImpl implements UserService, UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) {
        // 从数据库中查询出用户实体对象
        UserEntity user = userMapper.selectByUsername(username);
        // 若没查询到一定要抛出该异常,这样才能被Spring Security的错误处理器处理
        if (user == null) {
            throw new UsernameNotFoundException("没有找到该用户");
        }
        // 走到这代表查询到了实体对象,那就返回我们自定义的UserDetail对象(这里权限暂时放个空集合,后面我会讲解)
        return new UserDetail(user, Collections.emptyList());
    }
}

认证异常处理器AuthenticationEntryPoint接口
当我们查询用户失败时或者校验密码失败时都会抛出Spring Security的自定义异常。这些异常不可能放任不管,Spring Security对于这些异常都是在ExceptionTranslationFilter过滤器中进行处理,AuthenticationEntryPoint则专门处理认证异常。
也可以自定义一个类实现AuthenticationEntryPoint接口从而实现我们自己的错误处理逻辑

public class MyEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        // 直接提示前端认证错误
        out.write("认证错误");
        out.flush();
        out.close();
    }
}

配置
Spring Security对哪些接口进行保护、什么组件生效、某些功能是否启用等等都需要在配置类中进行配置

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    ....
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
     @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

** 实现流程**

  1. 用户进行登录操作,传递账号密码过来 登录接口调用AuthenticationManager
  2. 根据用户名查询出用户数据 UserDetailService查询出UserDetails
  3. 将传递过来的密码和数据库中的密码进行对比校验 PasswordEncoder
  4. 校验通过则将认证信息存入到上下文中 将UserDetails存入到Authentication,将Authentication存入到SecurityContext
  5. 如果认证失败则抛出异常 由AuthenticationEntryPoint处理
    基于session认证:认证后Spring Security会将Authentication存入到session中(不推荐)

2.2 JWT+SpringSecurity

1、当用户登录成功的同时,我们需要生成token并返回给前端,这样前端才能访问其他接口时携带token。

@Autowired
private UserService userService;
@PostMapping("/login")
public UserVO login(@RequestBody @Validated LoginParam user) {
    // 调用业务层执行登录操作
    return userService.login(user);
}

service层实现login方法

public UserVO login(LoginParam param) {
    // 根据用户名查询出用户实体对象
    UserEntity user = userMapper.selectByUsername(param.getUsername());
    // 若没有查到用户 或者 密码校验失败则抛出自定义异常
    if (user == null || !passwordEncoder.matches(param.getPassword(), user.getPassword())) {
        throw new ApiException("账号密码错误");
    }
    // 需要返回给前端的VO对象
    UserVO userVO = new UserVO();
    userVO.setId(user.getId())
        .setUsername(user.getUsername())
        // 生成JWT,将用户名数据存入其中
        .setToken(jwtManager.generate(user.getUsername()));
    return userVO;
}

2、登录成功返回token,后续我们再访问其它接口时需要将token放到请求头中。这里我们需要自定义一个认证过滤器,来对token进行校验。每当一个请求来时我们都会校验JWT进行认证,上下文对象中有了Authentication后续过滤器就会知道该请求已经认证过了。

@Component
public class LoginFilter extends OncePerRequestFilter {
    @Autowired
    private JwtManager jwtManager;
    @Autowired
    private UserServiceImpl userService;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 从请求头中获取token字符串并解析
        Claims claims = jwtManager.parse(request.getHeader("Authorization"));
        if (claims != null) {
            // 从`JWT`中提取出之前存储好的用户名
            String username = claims.getSubject();
            // 查询出用户对象
            UserDetails user = userService.loadUserByUsername(username);
            // 手动组装一个认证对象
            Authentication authentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
            // 将认证对象放到上下文中
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }
}

3、自定义的认证过滤器需要插入到默认的认证过滤器之前,这样我们的过滤器才能生效。

http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);

3. 权限授权

接口权限授权流程

  1. 当一个请求过来,我们先得知道这个请求的规则,即需要怎样的权限才能访问
  2. 然后获取当前登录用户所拥有的权限
  3. 再校验当前用户是否拥有该请求的权限
  4. 用户拥有这个权限则正常返回数据,没有权限则拒绝请求

Spring Security的授权发生在FilterSecurityInterceptor过滤器中,步骤如下:

  1. 首先调用的是 SecurityMetadataSource,来获取当前请求的鉴权规则
  2. 然后通过Authentication获取当前登录用户所有权限数据: GrantedAuthority认证对象里存放这权限数据
  3. 再调用 AccessDecisionManager 来校验当前用户是否拥有该权限
  4. 如果有就放行接口,没有则抛出异常,该异常会被 授权错误处理器AccessDeniedHandler 处理
  5. 定义好以上组件,就是写一个过滤器,进行属性配置,让组件生效
  6. 回到Spring Security配置类让这个过滤器替换掉原有的过滤器

总结

文章中内容均来源于https://zhuanlan.zhihu.com/p/342755411,原文作者讲解得十分透彻,不仅让我理解了技术框架的使用方法,还对框架中的源码,为什么要这样去做有了更深入的认识,这篇文章是对原文作者写的三篇文章的整合,简化了很多,方便本人之后复习巩固。
在这里插入图片描述

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

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

相关文章

axios传参方式

params参数通常用于GET请求添加查询参数&#xff0c;POST一般使用data参数传递参数 1、data传参 1-1、表单传参 // 方法定义 export function save(data) {return request({url: /url,headers: { Content-Type: multipart/form-data },method: post,data: data,}) }// 调用函…

Cisco Firepower FTD通过FMC修改syslog发送级别

默认FTD是将informational级别log发到 syslog server 但数量太多&#xff0c;所在调整为只发Warning级别以上的log 1 Devices -->platform settings 进入相应的 policy 2 左侧syslog–>Logging Destination—> syslog server 3 修改为warning (默认为information)…

Windows---CMD常用指令大全

CMD是什么&#xff1f; Windows操作系统中的命令行界面程序&#xff0c;全称为命令提示符 CMD可以干什么&#xff1f; 允许用户在文本界面下输入命令来执行各种操作&#xff0c;如文件管理、系统设置、软件安装等 帮助用户更好地控制和管理Windows系统 windows系统CMD指…

新一代GPT!GPT-4O:更快、更懂人类情感的人工智能新纪元

今天凌晨&#xff08;5.14凌晨&#xff09;&#xff0c;OpenAI 的 GPT-4O 版本在自然语言处理领域带来了革命性的改变。不仅在处理速度上获得了显著提升&#xff0c;GPT-4O 还增加了对人类情感的理解能力&#xff0c;这使得它在与人类的交互中更加自然和富有同理心。本文将深入…

软件设计师笔记(三)-设计模式和算法设计

本文内容来自笔者学习zst 留下的笔记&#xff0c;都是零碎的要点&#xff0c;查缺补漏&#xff0c;希望大家都能通过&#xff0c;记得加上免费的关注&#xff01;谢谢&#xff01;本章主要以下午题出现形式为主&#xff01; 文章编辑于&#xff1a;2024-5-13 13:43:47 目录 1…

果蔬经营平台|基于SSM+vue的果蔬经营平台系统的设计与实现(源码+数据库+文档)

果蔬经营平台系统 目录 基于SSM&#xff0b;vue的果蔬经营平台系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介…

黑马点评项目总结及个人优化

怎么根据前端代码实现自己的后端业务,实现不同接口 查阅文档:如果有完善的接口文档,可以直接查阅文档来了解后端所有接口的业务逻辑和功能。 阅读后端代码:通过阅读后端代码,特别是控制器(Controller)层和服务(Service)层的代码,可以了解后端所有接口的具体实现逻辑。…

Linux系统编程:进程控制

1.进程创建 1.1 fork函数 fork&#xff08;&#xff09;通过复制调用进程来创建一个新进程。新进程称为子进程&#xff0c;是调用进程的精确副本 进程&#xff0c;但以下几点除外&#xff1a; 子进程有自己的PID&#xff0c;此PID与任何现有进程组的ID不匹配子进程的父进程ID…

差异基因散点图绘制教程

差异基因散点图绘制教程 本期教程 小杜的生信笔记&#xff0c;自2021年11月开始做的知识分享&#xff0c;主要内容是R语言绘图教程、转录组上游分析、转录组下游分析等内容。凡事在社群同学&#xff0c;可免费获得自2021年11月份至今全部教程&#xff0c;教程配备事例数据和相…

简单有效的数据加密方法你知道几个?

1. 文件和邮件加密 利用华企盾DSC数据防泄密系统&#xff0c;企业可以实现文件和邮件的加密。系统提供了一键式的文件加密解决方案&#xff0c;确保敏感信息在电子邮件中传输时得到安全保护&#xff0c;即使邮件被截获&#xff0c;内容也无法被未授权人员阅读。 2. 端到端数据…

Ubuntu系统如何使用宝塔面板搭建HYBBS论坛并发布公网远程访问

文章目录 前言1. HYBBS网站搭建1.1 HYBBS网站安装1.2 HYBBS网站测试1.3. cpolar的安装和注册 2. 本地网页发布2.1.Cpolar临时数据隧道2.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3.公网访问测试总结 前言 在国内…

Redis教程——哨兵

在上篇文章我们学习了Redis教程——主从复制&#xff0c;这篇文章我们学习Redis教程——哨兵监控。 在主从复制中如果主机发生宕机&#xff0c;从机Redis会一直等到主机的恢复&#xff0c;这样会导致只能进行读操作&#xff0c;不能进行写操作&#xff0c;这大大降低了系统的高…

flexible.js+rem页面适配

简介 flexible.js 介绍 flexible.js 是一个用于移动端页面适配的 JavaScript 库&#xff0c;由阿里巴巴团队开发并开源。在移动 web 开发中&#xff0c;由于设备屏幕尺寸、分辨率以及像素比的差异&#xff0c;开发者通常需要编写额外的代码来确保页面在不同设备上都能正确显示…

【WEEK12】 【DAY1】整合JDBC【中文版】

2024.5.13 Monday 目录 11.整合JDBC11.1.SpringData简介11.2.新建springboot-04-data项目11.3.新建application.yaml11.4.连接数据库11.5.修改Springboot04DataApplicationTests.java11.5.1.查看DataSourceProperties.java和DataSourceAutoConfiguration.java 11.6.JDBCTempla…

在CentOS 7服务器及Windows 10客户端间建立并配置NFS服务

在CentOS 7服务器及Windows 10客户端间建立并配置NFS服务 引言 网络文件系统(Network File System)&#xff0c;简称NFS&#xff0c;是一种分布式文件系统协议。它允许网络上的客户端机器像访问本地磁盘文件一样&#xff0c;通过网络访问服务器上的文件。在某些特定的业务场景中…

机器学习材料性能预测与材料基因工程应用实战

传统的材料研发技术是通过实验合成表征对材料进行试错和验证&#xff0c;而过去的计算手段受限于算法效率&#xff0c;无法有效求解实际工业生产中面临的复杂问题。 近几年随着大数据和人工智能介入&#xff0c;通过采用支持向量机、神经网络等机器学习算法训练数据集来构建模…

【JAVA】BOSS系统发版艺术:构建高效、优雅的微服务部署策略

在现代软件开发领域&#xff0c;微服务架构与容器化部署已迅速成为行业新趋势。微服务架构通过将应用拆分成多个小型、自治的服务单元&#xff0c;每个服务承担某项特定的业务功能。而容器化部署则以其轻量级和高度可移植的特性&#xff0c;为这些微服务的有效打包、分发和运行…

ApiHug 官方

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | MarketplaceApiHug-H…

java版数据结构:二叉树

引言&#xff1a;什么是树 树是一种非线性的数据结构&#xff0c;由节点组成&#xff0c;节点之间以边连接。树结构中最重要的特点是&#xff0c;每个节点可以有多个子节点&#xff0c;但每个节点只有一个父节点&#xff08;除了根节点&#xff09;。树结构中没有环路&#xff…

水雨情监测系统—实时监测水位信息

TH-SW3水雨情监测系统是一种专门用于实时监测和收集水文气象数据的自动化系统。它能够实时获取区域内降雨和水情数据&#xff0c;并将其存储到数据库中进行分析处理&#xff0c;从而为防汛指挥人员提供及时准确的信息服务。 水雨情监测系统的主要功能包括实时监测水位、流速、流…