Spring Security + JWT 实现登录认证和权限控制

Spring Security + JWT 实现登录认证和权限控制

准备步骤

准备好一些常用的工具类,比如jwtUtil,redisUtil等。引入数据库,mybatis等,配置好controller,service,mapper,保证能够正常的数据请求。这里就省略了

1. 实体类User
package com.example.logindemo.domain.entity;import lombok.Data;import java.io.Serializable;@Data
public class User implements Serializable {private static final long serialVersionUID = 1L;private Integer id;private String username;private String account;		//账号。我是用的这个登录,没用usernameprivate String password;private String empCode;private Integer sex;private Integer age;private String role;
}
2. LoginUser类

由于Security默认需要一个UserDetails,所以单独用了这个LoginUser类来实现UserDetails接口,把我们自己的User放进来

/*** @title: LoginUser* @Author DengMj* @Date: 2024/5/6 11:32* @Version 1.0*/
@Data
@AllArgsConstructor
public class LoginUser implements UserDetails {private User user;/*** @return 返回用户权限,我这里直接放的角色*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {Set<GrantedAuthority> authorities = new HashSet<>();authorities.add(new SimpleGrantedAuthority("ROLE_" + this.user.getRole()));return authorities;}@Overridepublic String getPassword() {return this.user.getPassword();}@Overridepublic String getUsername() {return this.user.getAccount();}@Overridepublic boolean isAccountNonExpired() {return false;}@Overridepublic boolean isAccountNonLocked() {return false;}@Overridepublic boolean isCredentialsNonExpired() {return false;}@Overridepublic boolean isEnabled() {return false;}
}
3. 重写loadUserByUsername方法

让我们的UserService接口去继承UserDetailsService

public interface UserService extends UserDetailsService {User getUserById(int id);
}

UserDetailsService类有一个loadUserByUsername方法需要在我们的UserServiceImpl中重写

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User getUserById(int id) {return userMapper.getUserById(id);}//这里需要返回一个UserDetails,所以我们定义了LoginUser类,当然也可以直接用User类去实现UserDetails接口@Overridepublic UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {User user = userMapper.getUserByAccount(account);if (null == user){throw new UsernameNotFoundException("账号不存在!");}return new LoginUser(user);}
}
4. 登录页面login.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Login</title>
</head>
<body><h2>Login</h2><form action="/login" method="post"><div><label>account:<input type="text" name="account"></label></div><div><label>password:<input type="password" name="password"></label></div><div><input type="submit"></div>
</form>
</body>
</html>

关键步骤

1.SecurityConfig配置类

在这里需要重写两个configure方法

  1. void configure(AuthenticationManagerBuilder auth),用来自定义登录验证的逻辑
  2. void configure(HttpSecurity http),用来配置请求拦截等策略
/*** @title: SecrityConfig* @Author DengMj* @Date: 2024/5/5 16:09* @Version 1.0*/
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)  // 开启权限注解,默认是关闭的
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredUserAuthenticationProvider userAuthenticationProvider;@Autowiredprivate UserLoginSuccessHandler userLoginSuccessHandler;@Autowiredprivate UserLoginFailureHandler userLoginFailureHandler;@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Autowiredprivate UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;@Autowiredprivate UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;@Autowiredprivate UserPermissionEvaluator userPermissionEvaluator;//自定义的登陆验证逻辑@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(userAuthenticationProvider);}//注入自定义PermissionEvaluator,使用hasPermission()的时候才需要@Beanpublic DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();handler.setPermissionEvaluator(userPermissionEvaluator);return handler;}//登录拦截配置@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过session获取securityContext,通过每个请求中携带的Token来识别用户.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().cors().and()//所有URL都需要认证.authorizeRequests().antMatchers("/**").authenticated().and()//未登录处理类.httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler).and()//对于登录接口允许访问.formLogin().loginPage("/login.html").loginProcessingUrl("/login").permitAll()//自定义登录用户名为account,默认的是username.usernameParameter("account").passwordParameter("password")//登录认证成功handler.successHandler(userLoginSuccessHandler)//登录认证失败handler.failureHandler(userLoginFailureHandler).and()//配置登出地址.logout().logoutUrl("/logout")//成功登出.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)).and()//用户无权限handler.exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler).and()//配置认证过滤器.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)//禁用缓存.headers().cacheControl();}
}

以下是几个关键的自定义的类,我们需要一一实现

image-20240506185522388

2. UserAuthenticationProvider实现自定义的登录逻辑

自定义UserAuthenticationProvider类实现AuthenticationProvider接口,用户发起登录请求后会执行authenticate()方法来进行登录验证。

该方法会返回一个UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)类型的结果,该类的父类AbstractAuthenticationToken实现了Authentication接口,用来封装用户的认证信息。

/*** 自定义登录验证逻辑** @title: UserAuthenticationProvider* @Author DengMj* @Date: 2024/5/5 17:14* @Version 1.0*/
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate UserService userService;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String account = (String) authentication.getPrincipal();	//登录请求中的账号String password = (String) authentication.getCredentials();		//登录密码LoginUser loginUser = (LoginUser) userService.loadUserByUsername(account);if (null == loginUser) {throw new UsernameNotFoundException("账号不存在!");}//这里直接用了MD5加密,没用Security中的passwordEncoderif (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(loginUser.getPassword()))throw new BadCredentialsException("密码错误!");//UsernamePasswordAuthenticationToken的第三个参数需要Set<GrantedAuthority>类型的。//所以需要把我们的角色信息封装进去Set<GrantedAuthority> authorities = new HashSet<>();authorities.add(new SimpleGrantedAuthority(loginUser.getUser().getRole()));//返回封装好的用户认证信息return new UsernamePasswordAuthenticationToken(account, password, authorities);}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}

以下是UsernamePasswordAuthenticationToken类的构造函数,其中principle表示用户信息(比如用户名),credentials是用户凭证(比如密码),authorities权限信息。

image-20240506193317740

登录后的用户认证信息默认会被保存在SecurityContext中,每个作用域是HTTP session,所以一次登录之后,该会话中的请求都可以通过上下文信息中的用户认证信息认证成功。

但是我们一般都会使用无状态的RESTful API,不依赖于服务器端的会话来维持用户的认证状态,而是通过每个请求中携带的Token来识别用户。所以我们不想要Spring Security维护HTTP会话,即不使用HTTP会话来存储安全上下文信息,例如认证信息 。所以需要在SecurityConfig中做以下配置:

image-20240506194540891

3. JwtAuthenticationTokenFilter过滤器

自定义一个JwtAuthenticationTokenFilter类去实现OncePerRequestFilter接口,这样每次有请求进来的时候,都会先去执行doFilterInternal()方法。

/*** @title: JwtAuthenticationTokenFilter* @Author DengMj* @Date: 2024/5/6 11:04* @Version 1.0*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@AutowiredRedisUtil redisUtil;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("token");//没有tokenif (StringUtils.isEmpty(token)){//放行,交个后续的其他过滤器处理filterChain.doFilter(request, response);return;}//验证tokenif (!JwtUtils.verify(token)){throw new RemoteException("token非法");}//解析token拿到User信息User userByToken = JwtUtils.getUserByToken(token);String redisKey = "token:" + userByToken.getAccount();JSONObject jsonObject = (JSONObject) redisUtil.get(redisKey);User user = jsonObject.toJavaObject(User.class);//redis中没有记录,说明用户没有登录,或者登录过期了if (null == user){throw new RemoteException("用户未登录");}//用户的权限信息Set<GrantedAuthority> authorities = new HashSet<>();authorities.add(new SimpleGrantedAuthority(user.getRole()));//在上下文中保存用户认证信息UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(new LoginUser(user), token, authorities);SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request, response);}
}

将过滤器添加到配置类的HttpSecurity配置中

image-20240506210640510

4. 自定义登录相关的处理类

SecurityConfig配置类中,void configure(HttpSecurity http)方法可以指定登录认证后各种情况的handler,接下来我们挨个实现这些类。

image-20240506195850279

1.UserLoginSuccessHandler

如果通过了UserAuthenticationProvider的登录逻辑验证,那么就会执行该类中的onAuthenticationSuccess()方法,我们可以在这里将用户信息存入到Redis中,并且返回jwt签署的token。

/*** @title: UserLoginSuccessHandler 登录成功处理类* @Author DengMj* @Date: 2024/5/5 19:51* @Version 1.0*/
@Slf4j
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {@AutowiredUserService userService;@AutowiredRedisUtil redisUtil;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {String account = (String) authentication.getPrincipal();LoginUser loginUser = (LoginUser) userService.loadUserByUsername(account);User user = loginUser.getUser();String token = JwtUtils.sign(user);//把用户信息存入到Redis中String redisKey = "token:" + user.getAccount();redisUtil.set(redisKey, user);redisUtil.expire(redisKey, TimeUnit.HOURS.toMillis(2));log.info(APIResult.newSuccessResult(token));//返回tokenresponse.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().println(APIResult.newSuccessResult(token));}
}
2.UserLoginFailureHandler

如果登录失败就会走这个handler,可以根据抛出的不同的异常来判定登陆失败的原因并通过response返回。

/*** 登录失败处理类* @title: UserLoginFailureHandler* @Author DengMj* @Date: 2024/5/6 15:39* @Version 1.0*/
@Slf4j
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {response.setContentType("application/json");response.setCharacterEncoding("utf-8");log.info("登陆失败:{}", e.getMessage());if (e instanceof UsernameNotFoundException)response.getWriter().println(APIResult.newFailResult("账号不存在!"));else if (e instanceof LockedException)response.getWriter().println(APIResult.newFailResult("账号被冻结!"));else if (e instanceof BadCredentialsException)response.getWriter().println(APIResult.newFailResult("密码错误!"));elseresponse.getWriter().println(APIResult.newFailResult("登陆失败!"));}
}
3.UserAuthenticationEntryPointHandler

Spring Security 没有登录时,会被 UsernamePasswordAuthenticationFilter 拦截器拦截,它是处理表单登录的默认拦截器。如果没有登录就尝试访问受保护的资源,Spring Security 会返回登录页面或者返回401 Unauthorized错误,具体取决于配置。

如果想自定义未登录的处理方式,可以通过实现 AuthenticationEntryPoint 接口来定制。

/*** 用户未登录处理类* @title: UserAuthenticationEntryPointHandler* @Author DengMj* @Date: 2024/5/6 15:52* @Version 1.0*/
@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().println(APIResult.newFailResult(401, "未登录!"));}
}
5. 认证流程总结

到这里登录认证就已经实现了,总结一下整个流程就是:

  1. 用户发起登录请求

  2. 请求被JwtAuthenticationTokenFilter过滤器拦截,执行doFilterInternal方法,此时发现token为null,因此放行给后续过滤器处理

  3. 后续过滤器发现这是一个地址为/login的登录请求,由于我们在配置类的configure方法中配置了允许登录接口访问,因此该请求不会被拦截

    image-20240506204707178

  4. 然后会调用UserAuthenticationProvider中的authenticate方法进行登录验证

  5. 根据登录验证的结果判断是调用UserLoginFailureHandler还是UserLoginSuccessHandler

  6. response返回结果,整个登录认证的过程就结束了

大概的执行流程就是这样,debug得出来的这个执行流程,具体底层的东西还不是特别清楚。

6. 在请求方法上添加注解@PreAuthorize

使用注解@PreAuthorize(“hasAuthority(‘admin’)”)表明该请求需要admin权限

@PreAuthorize("hasAuthority('admin')")
@GetMapping("/getUserById/{id}")
// @PreAuthorize("hasPermission('/user', 'admin')")
public String getUserById(@PathVariable int id){User userById = userService.getUserById(id);return APIResult.newSuccessResult(userById);
}

@PreAuthorize注解可选的参数包括这些,其中hasRole()hasAuthority()方法是差不多的,只是一个前缀ROLE_的区别,具体的看后面会讲到。

image-20240506213233571

如果使用hasPermission()可以自己定义UserPermissionEvaluator类通过实现PermissionEvaluator接口来自定义鉴权逻辑

UserPermissionEvaluator
/*** @title: UserPermissionEvaluator* @Author DengMj* @Date: 2024/5/6 16:39* @Version 1.0*/
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {/*** @param authentication 用户信息* @param targetUrl  请求路径* @param permission 需要的权限* @return*/@Overridepublic boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {LoginUser loginUser = (LoginUser) authentication.getPrincipal();return loginUser.getAuthorities().contains(permission);}@Overridepublic boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {return false;}
}
7. UserAuthAccessDeniedHandler

自定义UserAuthAccessDeniedHandler类用来处理当访问被拒绝时的逻辑,并将其加入到配置类中

/*** 暂无权限处理类* @title: UserAuthAccessDeniedHandler* @Author DengMj* @Date: 2024/5/6 15:54* @Version 1.0*/
@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().println(APIResult.newFailResult(403, "暂无权限"));}
}

image-20240506211202753

8. 鉴权流程总结

这样我们的鉴权功能就实现了,总结一下实现的流程:

  1. jwt过滤器从请求中拿到token,解析出用户信息,每个用户信息中都带有该用户的角色信息(role字段),将用户认证信息保存在上下文中
  2. 根据我们在controller方法上标注的注解@PreAuthorize("hasAuthority('admin')"),会执行源码中的这段代码,实现鉴权

image-20240506213045237

  1. 如果没有权限则会通过UserAuthAccessDeniedHandler来处理访问被拒绝时的逻辑

以上只是简单的实现了一下Security的登录认证和鉴权相关的操作,底层原理还需要再花时间学习~

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Debian是什么?有哪些常用命令

目录 一、Debian是什么&#xff1f; 二、Debian常用命令 三、Debian和CentOS的区别 四、Debian和CentOS的优缺点 五、Debian和CentOS的运用场景 一、Debian是什么&#xff1f; Debian是一种流行的开源Linux操作系统。 Debian是一个以Linux内核为基础的操…

数据结构十一:数组相关经典面试题

本篇博客详细介绍分析数组/顺序表常见的面试题&#xff0c;对于前面所学知识进行一个巩固&#xff0c;同时介绍一些力扣刷题中的一些概念&#xff1a;如&#xff1a;输出型参数等&#xff0c;在刷题中培养自己的编程思维&#xff0c;掌握常见的编程套路&#xff0c;形成题感&am…

php基础知识快速入门

一、PHP基本知识 1、php介绍&#xff1a; php是一种创建动态交互性的强有力的服务器脚本语言&#xff0c;PHP是开源免费的&#xff0c;并且使用广泛。PHP是解释性语言&#xff0c;按顺序从上往下执行&#xff0c;无需编译&#xff0c;直接运行。PHP脚本在服务器上运行。 2、ph…

瑞_23种设计模式_解释器模式

文章目录 1 解释器模式&#xff08;Interpreter Pattern&#xff09;1.1 介绍1.2 概述1.2.1 文法&#xff08;语法&#xff09;规则1.2.2 抽象语法树 1.3 解释器模式的结构1.4 解释器模式的优缺点1.5 解释器模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代…

基于JSP的酒店客房管理系统(三)

目录 第四章 系统各模块的实现 4.1客房管理系统首页的实现 4.1.1 客房管理系统首页概述 4.2客房管理系统前台的实现 4.2.1 客房管理系统前台概述 4.2.2 客房管理系统前台实现过程 4.2.3 预定客房信息及客房信息的查询 4.3客房管理系统后台的实现 4.3.1 客房管理系统后…

基于springboot+vue+Mysql的在线动漫信息平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

[蓝桥杯2024]-PWN:ezheap解析(堆glibc2.31,glibc2.31下的double free)

查看保护 查看ida 大致就是只能创建0x60大小的堆块&#xff0c;并且uaf只能利用一次 完整exp&#xff1a; from pwn import* #context(log_leveldebug) pprocess(./ezheap2.31)def alloc(content):p.sendlineafter(b4.exit,b1)p.send(content) def free(index):p.sendlineaft…

LeetCode 226.翻转二叉树(全网最多的解法)

LeetCode 226.翻转二叉树 1、题目 题目链接&#xff1a;226. 翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#…

怎么通过Java语言实现远程控制无人售货柜

怎么通过Java语言实现远程控制无人售货柜呢&#xff1f; 本文描述了使用Java语言调用HTTP接口&#xff0c;实现控制无人售货柜&#xff0c;独立控制售货柜、格子柜的柜门。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规格 序号设备名称厂商1智能WiFi控…

52. 【Android教程】网页视图:WebView

在前面的章节我们所围绕的全部都是纯客户端开发&#xff0c;我们叫 Native 开发。这样的好处就是体验和性能会非常好&#xff0c;但是在实际的使用中我们会发现存在大量的 H5 页面。这样就可以结合 Native / H5 双端的优势完成一个混合开发&#xff0c;而在这种开发模式中首当其…

[HNOI2003]激光炸弹

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 二维前缀和板题。 注意从&#xff08;1,1&#xff09;开始存即可&#xff0c;所以每次输入x,y之后&#xff0c;要x,y。 因为m的范围最大为…

微搭低代码入门05文件的上传和下载

目录 1 创建数据源2 创建应用3 创建页面4 设置导航功能5 文件上传6 文件下载总结 小程序中&#xff0c;我们通常会有文件的上传和下载的需&#xff0c;在微搭中&#xff0c;文件是存放在云存储中&#xff0c;每一个文件都会有一个唯一的fileid&#xff0c;我们本篇就介绍如何通…

农药生产厂污废水如何处理达标

农药生产厂的污废水处理是确保该行业对环境的负面影响最小化的重要环节。下面是一些常见的处理方法和步骤&#xff0c;可以帮助农药生产厂的污废水达到排放标准&#xff1a; 预处理&#xff1a;将废水进行初步处理&#xff0c;去除大颗粒悬浮物和固体残渣。这可以通过筛网、沉淀…

ArthasGC日志GCeasy详解

Arthas详解 Arthas是阿里巴巴在2018年9月开源的Java诊断工具,支持JDK6,采用命令行交互模式,可以方便定位和诊断线上程序运行问题.Arthas官方文档十分详细.详见:官方文档 Arthas使用场景 Arthas使用 # github下载arthas wget https://alibaba.github.io/arthas/arthas-boot.j…

C++例题:大数运算---字符串相加(使用数字字符串来模拟竖式计算)

1.代码速览 class Solution2 { public:string addStrings(string num1, string num2){//end1和end1是下标int end1 num1.size() - 1;int end2 num2.size() - 1;string str;//下标(指针)从后向前走,走到头才可以结束,所以是end>0int next 0;while (end1 > 0 || end2 &…

ADS基础教程9-理想模型和厂商模型实现及对比

目录 一、概要二、厂商库使用1.新建cell2.调用厂商库中元器件3.元器件替换及参数选择4.完成参数选择5.导入子图 三、仿真实现注意事项 一、概要 本文将介绍在ADS中调用厂商提供的库&#xff0c;来进行原理图仿真&#xff0c;并实现与ADS系统提供的理想元器件之间的比较。 二、…

触摸OpenNJet,感悟云原生

小程一言 云原生使得应用充分利用云计算、容器化和微服务架构等现代技术来构建和运行应用程序。 云原生技术的用处在于提高应用程序的可靠性、可伸缩性和灵活性&#xff0c;加快开发和部署速度&#xff0c;降低成本&#xff0c;提升整体的效率和竞争力。通过采用云原生技术&a…

SpringBoot+Vue+Element-UI实现协同过滤算法商品推荐系统

前言介绍 本次设计任务是要设计一个基于协同过滤算法的商品推荐系统&#xff0c;通过这个系统能够满足商品推荐系统的管理功能。系统的主要包括首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商品类型管理&#xff0c;商品信息管理&#xff0c;系统管理&#xff0…

LabVIEW航空发动机主轴承试验器数据采集与监测

LabVIEW航空发动机主轴承试验器数据采集与监测 随着航空技术的迅速发展&#xff0c;对航空发动机性能的测试与监测提出了更高的要求。传统的数据采集与监测方法已难以满足当前高精度和高可靠性的需求&#xff0c;特别是在主轴承试验方面。基于LabVIEW的航空发动机主轴承试验器…

小工具 - 用Astyle的DLL封装一个对目录进行代码格式化的工具

文章目录 小工具 - 用Astyle的DLL封装一个对目录进行代码格式化的工具概述笔记效果编译AStyle的DLL初次使用接口的小疑惑测试程序 - 头文件测试程序 - 实现文件测试程序 - RC备注END 小工具 - 用Astyle的DLL封装一个对目录进行代码格式化的工具 概述 上一个实验(vs2019 - ast…