RuoYi-Vue 最新 SpringBoot3 前后端分离版本源码分析

RuoYi-Vue 最新 SpringBoot3 前后端分离版本源码分析

  • RuoYi-Vue 本地环境部署
  • 若依菜单类型
  • 权限管理
    • SpringSecurity 配置
    • 登录接口(认证管理)
      • Authentication 认证
      • token的生成
    • 权限控制
  • 异步任务管理
  • 操作日志
  • 数据权限

RuoYi-Vue 本地环境部署

直接去 gitee 上拉取最新版本即可,分支切换到 springboo3 就可以了,本地部署也非常简单,只需要更改数据库和 Redis 配置即可

在线体验

若依官网:http://ruoyi.vip
演示地址:http://vue.ruoyi.vip
代码下载:https://gitee.com/y_project/RuoYi/tree/springboot3/

在这里插入图片描述

若依菜单类型

在系统管理-菜单管理,新增菜单,可以看到有三种菜单类型,

目录可以理解成一级菜单,系统管理、系统监控、系统工具这些都算是目录了。

而菜单则可以理解成二级菜单,菜单管理、用户管理都算是菜单了

按钮则对应二级菜单页面的操作了,比如在用户管理列表页,新增用户、编辑用户、删除用户等都是按钮
在这里插入图片描述
sys_menu 菜单表有个字段 menu_type来区分这三种菜单类型
在这里插入图片描述
在这里插入图片描述

权限管理

RuoYi-Vue 最新 SpringBoot3 前后端分离版本是使用 SpringSecurity 来进行安全认证和权限控制的,从 maven 依赖可以看到版本是 SpringSecurity6.3.0

SpringSecurity 配置

SpringSecurity 的配置类是实现安全控制的核心部分,开启 SpringSecurity 各种功能,以确保 Web 应用程序的安全性,包括认证、授权、回话管理、过滤器添加等.

// 表示开启方法级别的权限控制=> @PreAuthorize
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig {//身份验证实现@Beanpublic AuthenticationManager authenticationManager() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();// 这句代码很重要,后续登录的时候会进入到UserDetailsServiceImpl#loadUserByUsername方法进行认证daoAuthenticationProvider.setUserDetailsService(userDetailsService);daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());return new ProviderManager(daoAuthenticationProvider);}@Beanprotected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {return httpSecurity// CSRF禁用,因为不使用session.csrf(csrf -> csrf.disable())// 禁用HTTP响应标头.headers((headersCustomizer) -> {headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());})// 认证失败处理类(认证失败的进入unauthorizedHandler类处理).exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))// 基于token,所以不需要session.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 注解标记允许匿名访问的url.authorizeHttpRequests((requests) -> {permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());// 对于登录login 注册register 验证码captchaImage 允许匿名访问requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()// 静态资源,可匿名访问.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll().requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();})// 添加Logout filter(退出的时候进入logoutSuccessHandler).logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))// 添加JWT filter(每次请求都会进入UsernamePasswordAuthenticationFilter,校验token有效性、合法性).addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)// 添加CORS filter.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class).addFilterBefore(corsFilter, LogoutFilter.class).build();}
}

看看退出账户的时候logoutSuccessHandler做啥了

@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {LoginUser loginUser = tokenService.getLoginUser(request);if (StringUtils.isNotNull(loginUser)) {String userName = loginUser.getUsername();// 删除用户缓存记录tokenService.delLoginUser(loginUser.getToken());// 记录用户退出日志AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));}ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success"))));
}

每次请求都会被 UsernamePasswordAuthenticationFilter 拦截

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {// 通过令牌服务获取登录用户信息LoginUser loginUser = tokenService.getLoginUser(request);if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {// 验证用户令牌是否有效tokenService.verifyToken(loginUser);// 创建认证对象UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());// 设置认证对象的详细信息,这些详细信息是基于web的认证细节authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 将认证对象设置到安全上下文中,这样应用的其它部分可以访问到用户信息SecurityContextHolder.getContext().setAuthentication(authenticationToken);}// 继续执行下一个过滤器链chain.doFilter(request, response);
}

登录接口(认证管理)

@PostMapping("/login")public AjaxResult login(@RequestBody LoginBody loginBody) {AjaxResult ajax = AjaxResult.success();// 生成令牌String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());ajax.put(Constants.TOKEN, token);return ajax;}

核心实现在 loginService.login 方法

public String login(String username, String password, String code, String uuid) {// 验证码校验validateCaptcha(username, code, uuid);// 登录前置校验(前端校验了,后端再次校验长度啊,空格、IP黑名单校验之类的...)loginPreCheck(username, password);// 用户验证,具体验证的细节是由 SpringSecurity 认证管理器来处理的Authentication authentication = null;try {UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);AuthenticationContextHolder.setContext(authenticationToken);// 该方法会去调用UserDetailsServiceImpl.loadUserByUsernameauthentication = authenticationManager.authenticate(authenticationToken);} catch (Exception e) {...} finally {AuthenticationContextHolder.clearContext();}// 异步生成登录日志AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));LoginUser loginUser = (LoginUser) authentication.getPrincipal();recordLoginInfo(loginUser.getUserId());// 生成tokenreturn tokenService.createToken(loginUser);
}
public void validateCaptcha(String username, String code, String uuid) {// 检查是否启用了验证码功能boolean captchaEnabled = configService.selectCaptchaEnabled();if (captchaEnabled) {// 构建验证码缓存 keyString verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");String captcha = redisCache.getCacheObject(verifyKey);// redis验证码不存在,抛出验证码已失效异常if (captcha == null) {AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));throw new CaptchaExpireException();}// 删除缓存的验证码,用完就删,因为验证码是一次生效的redisCache.deleteObject(verifyKey);// 校验用户提交的验证码和缓存的验证码,不成功,抛出验证码错误if (!code.equalsIgnoreCase(captcha)) {AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));throw new CaptchaException();}}
}

整个 login 方法大致分为两大块:
首先是认证细节
第二个就是认证完成之后的 token 生成

Authentication 认证

具体验证的细节是由 SpringSecurity 认证管理器来处理的,来看看UserDetailsServiceImpl.loadUserByUsername逻辑

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser user = userService.selectUserByUserName(username); // 查询用户信息if (StringUtils.isNull(user)) {log.info("登录用户:{} 不存在.", username);throw new ServiceException(MessageUtils.message("user.not.exists"));} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {log.info("登录用户:{} 已被删除.", username);throw new ServiceException(MessageUtils.message("user.password.delete"));} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {log.info("登录用户:{} 已被停用.", username);throw new ServiceException(MessageUtils.message("user.blocked"));}// 验证用户密码输入是否正确passwordService.validate(user);// 创建并返回登录用户对象return createLoginUser(user);
}

来看看密码校验部分逻辑

public void validate(SysUser user) {// 获取当前的认证信息,从认证信息中提取用户名和密码Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();String username = usernamePasswordAuthenticationToken.getName();String password = usernamePasswordAuthenticationToken.getCredentials().toString();// 尝试从缓存中获取当前用户的密码重试次数Integer retryCount = redisCache.getCacheObject(getCacheKey(username));// 如果缓存中没有,则初始化 0if (retryCount == null) {retryCount = 0;}// 如果重试次数超过了配置文件中配置的最大重试次数(${user.password.maxRetryCount}),抛异常if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) {throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);}if (!matches(user, password)) { // 对比用户密码和数据库密码,使用到 hash 散列算法处理retryCount = retryCount + 1; // 密码不匹配,增加重试次数redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);throw new UserPasswordNotMatchException();} else {clearLoginRecordCache(username);}}public boolean matches(SysUser user, String rawPassword) {return this.matchesPassword(rawPassword, user.getPassword());}public static boolean matchesPassword(String rawPassword, String encodedPassword) {BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();return passwordEncoder.matches(rawPassword, encodedPassword);}

创建登录用户对象返回

public UserDetails createLoginUser(SysUser user) {return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}

这个对象是这样的
在这里插入图片描述

token的生成

 public String createToken(LoginUser loginUser) {String token = IdUtils.fastUUID();loginUser.setToken(token);// 设置用户登录信息,都是一些 setXX操作setUserAgent(loginUser);// 登录后,在 redis会缓存登录用户信息,key是 login_tokens+uuidrefreshToken(loginUser);Map<String, Object> claims = new HashMap<>();claims.put(Constants.LOGIN_USER_KEY, token);return createToken(claims);}private String createToken(Map<String, Object> claims) {String token = Jwts.builder().setClaims(claims)// 使用 HS512 算法和 secret 密钥对 JWT 进行签名.signWith(SignatureAlgorithm.HS512, secret).compact();return token;}

最终生成的 token 会返回前端,存到 cookie中,所以登录后可以在 cooke 中看到这个 token
在这里插入图片描述
最后,总结一下登录流程
在这里插入图片描述

权限控制

前端页面,不同的用户登录会显示不同的操作权限,这是因为每个用户拥有的角色权限可能不一样,怎么判断的呢,使用 v-hasPermi指令,这个指令是若依框架自定义的组件

<el-buttonsize="mini"type="text"icon="el-icon-edit"@click="handleUpdate(scope.row)"v-hasPermi="['system:dept:edit']"
>修改</el-button>

那用户权限怎么获取呢?会发现,每次刷新浏览器都会发送 http://localhost/dev-api/getInfo 查询用户信息,可以看下这个接口,其实就是获取当前用户拥有的角色和权限集合

 @GetMapping("getInfo")public AjaxResult getInfo() {SysUser user = SecurityUtils.getLoginUser().getUser();// 角色集合Set<String> roles = permissionService.getRolePermission(user);// 权限集合Set<String> permissions = permissionService.getMenuPermission(user);AjaxResult ajax = AjaxResult.success();ajax.put("user", user);ajax.put("roles", roles);ajax.put("permissions", permissions);return ajax;}

整个流程大致是这样的
在这里插入图片描述
那后端权限怎么控制呢?SpringSecurity提供的@PreAuthorize 注解是实现方法级别访问控制的核心工具,它通过在方法前进行权限校验,确保只有符合条件的用户才能访问特定的功能

@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")public TableDataInfo list(SysUser user) {startPage();List<SysUser> list = userService.selectUserList(user);return getDataTable(list);}

其中@ss代表 PermissionService 类,就是调用这个类的 各种方法,原理就是 AOP,之所以会在调用接口前先执行这个方法,是因为 SecurityConfig 配置类上加了@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) 注解,准确的说是prePostEnabled = true 这个属性起了作用

@Service("ss")
public class PermissionService {
/*** 验证用户是否具备某权限** @param permission 权限字符串* @return 用户是否具备某权限*/
public boolean hasPermi(String permission) {// 判空if (StringUtils.isEmpty(permission)) {return false;}// 获取当前登录用户信息LoginUser loginUser = SecurityUtils.getLoginUser();// 为空直接返回 falseif (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {return false;}// 将权限信息设置到上下文中,供后续操作使用PermissionContextHolder.setContext(permission);// 检查用户权限集合中是否包含指定权限Set<String> permissions = loginUser.getPermissions();return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));}
}

PermissionService 类还定义了其它方法,如下
在这里插入图片描述

异步任务管理

来分析一下 登录的接口

    @PostMapping("/login")public AjaxResult login(@RequestBody LoginBody loginBody){AjaxResult ajax = AjaxResult.success();// 生成令牌String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),loginBody.getUuid());ajax.put(Constants.TOKEN, token);return ajax;}

接着定位到 loginService.login 方法

public String login(String username, String password, String code, String uuid){// 验证码校验validateCaptcha(username, code, uuid);// 登录前置校验loginPreCheck(username, password);// 用户验证Authentication authentication = null;....// 这行就是异步生成任务(记录登录日志),然后调用线程池执行任务AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));LoginUser loginUser = (LoginUser) authentication.getPrincipal();recordLoginInfo(loginUser.getUserId());// 生成tokenreturn tokenService.createToken(loginUser);}

记录的登录日志用于在这里查询
在这里插入图片描述

任务的生成,TimerTask 实现了 Runnable 接口,本质是个任务,这里是插入数据到库

public static TimerTask recordLogininfor(final String username, final String status, final String message,final Object... args){...return new TimerTask(){@Overridepublic void run(){...// 封装对象SysLogininfor logininfor = new SysLogininfor();...// 插入数据SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);}};}

而任务是由 AsyncManager.me().execute 调用线程池执行的

/*** 异步操作任务调度线程池*/
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");public void execute(TimerTask task)
{executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}

整个过程流程图总结如下
在这里插入图片描述

操作日志

在日常编程中,记录日志是我们的得力助手,尤其在处理关键业务时,它能帮助我们追踪和审查操作过程,那 RuoYi 是怎么记录操作日志的呢?

在需要被记录日志的 Controller 方法上添加 @Log 注解,使用方法如下:

// 删除用户
@PreAuthorize("@ss.hasPermi('system:user:remove')")
@Log(title = "用户管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds) {if (ArrayUtils.contains(userIds, getUserId())) {return error("当前用户不能删除");}return toAjax(userService.deleteUserByIds(userIds));
}

可以看到注解的定义

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {/*** 模块*/public String title() default "";/*** 功能*/public BusinessType businessType() default BusinessType.OTHER;/*** 操作人类别*/public OperatorType operatorType() default OperatorType.MANAGE;...
}

使用注解的形式记录操作日志,肯定是用 AOP 思想来做的,所以一定有个切面,LogAspect 这个切面用是前置通知和后置通知组合,当然也可以使用 around 环绕通知来做
在这里插入图片描述
最终也是异步任何结合线程池来保存数据的

protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {....  // 设置方法名称String className = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();operLog.setMethod(className + "." + methodName + "()");// 设置请求方式operLog.setRequestMethod(ServletUtils.getRequest().getMethod());// 处理设置注解上的参数getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);// 设置消耗时间operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());// 异步保存数据库AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}

大致流程如下
在这里插入图片描述

数据权限

如何确保用户只能访问他们授权查看的数据?这就是我们所说的数据权限控制
数据权限的场景:

  • 部门级权限
  • 公司级权限
  • 跨部门权限

若依管理系统中,角色定义了用户的权限,包括菜单权限(RBAC)和数据权限,若依的用户管理和部门管理实现了数据权限的功能,下面来做个测试,定义三个角色(因为权限是基于角色来控制的)
在这里插入图片描述
然后给 ry 这个用户分别赋予这三个角色,观察一下数据权限的查询范围
在这里插入图片描述
若依系统的数据权限设计主要通过用户、角色、部门表建立关系,实现对数据的访问控制
在这里插入图片描述
在需要数据权限控制的方法上加上 @DataScop 注解,其中d和u用来表示表的别名

 @Override@DataScope(deptAlias = "d", userAlias = "u")public List<SysUser> selectUserList(SysUser user){return userMapper.selectUserList(user);}

在这里插入图片描述
总结下流程
在这里插入图片描述

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

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

相关文章

<Rust>egui学习之小部件(三):如何为窗口UI元件设置布局(间隔、水平、垂直排列)?

前言 本专栏是关于Rust的GUI库egui的部件讲解及应用实例分析&#xff0c;主要讲解egui的源代码、部件属性、如何应用。 环境配置 系统&#xff1a;windows 平台&#xff1a;visual studio code 语言&#xff1a;rust 库&#xff1a;egui、eframe 概述 本文是本专栏的第三篇博…

C++和OpenGL实现3D游戏编程【连载7】——文字和汉字的显示

1、本节实现的内容 上一节我们讨论了纹理在二维平面内不规则图形贴图的相关基础操作,本节我们开始了解游戏里文字以及汉字的显示方法。本节课我们将从基本的ASCII字符显示,拓展到中文字符的显示,最后再讲到纹理字符的显示,并对各种文字显示方法的优缺点和使用场景进行分析…

使用Masscan扫描器进行信息搜集

Masscan 是一款极为高效的端口扫描工具&#xff0c;以其卓越的扫描速度和大规模扫描能力而著称。该工具不仅支持 TCP 和 UDP 协议的扫描&#xff0c;还允许用户根据需求灵活指定多个目标和端口。Masscan 通过采用先进的网络性能优化技术&#xff0c;充分利用操作系统的资源和多…

基于北斗+自组网技术的光伏电场人员位置监控系统优化方案

一、方案背景 1.1 用户需求 随着我国经济的快速发展&#xff0c;光伏电场等新能源项目的建设日益增多。然而&#xff0c;这些项目往往位于偏远地区&#xff0c;通信基础设施不完善&#xff0c;导致施工人员在作业过程中难以与外界保持实时联系。特别是在无人区或信号弱的地区…

【Qt 事件】—— 详解Qt事件处理

目录 &#xff08;一&#xff09;事件介绍 &#xff08;二&#xff09;事件的处理 &#xff08;三&#xff09;按键事件 3.1 单个按键 3.2 组合按键 &#xff08;四&#xff09;鼠标事件 4.1 鼠标单击事件 4.2 鼠标释放事件 4.3 鼠标双击事件 4.4 鼠标移动事件 4.5…

101.SAP MII功能详解(15)Workbench-Transaction Logic(Iterator)

目录 1.Logic->Iterator 2.演示 配置连接 Iterator使用示例 1.Logic->Iterator 您可以使用此操作迭代循环浏览List&#xff0c;迭代是指遍历某个List数据结构&#xff0c;逐个访问其元素的过程。迭代使用的场景不多。 2.演示 配置连接 Iterator使用示例 数据源是Lis…

Qt:玩转QPainter序列九(文本,文本框,填充)

前言 继续承接序列八 正文 1. drawImage系列函数 绘制图像 inline void drawImage(const QPoint &p, const QImage &image); 作用: 在指定的点 p 上绘制 QImage 图像。图像的左上角将对齐到 p 点。 inline void drawImage(int x, int y, const QImage &image,…

[001-07-001].Redis7缓存双写一致性之更新策略探讨

1、面试题&#xff1a; 1.只要使用缓存&#xff0c;就可能会涉及到redis缓存与数据库双存储双写&#xff0c;只要是双写&#xff0c;就存在数据一致性问题&#xff0c;那么是如何解决数据一致性问题的2.双写一致性&#xff0c;你先动缓存redis还是数据库MySQL&#xff0c;哪一个…

新能源汽车超级电容和电池能量管理系统的simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 电池模型 4.2 电池荷电状态&#xff08;SOC&#xff09;估算 4.3 超级电容器模型 4.4 能量管理 5.完整工程文件 1.课题概述 新能源汽车的能量管理系统&#xff08;Energy Management System, EMS…

中国艺术孙溟㠭凿篆《无用之用》

孙溟㠭凿篆作品《无用之用》 这方作品是孙溟㠭先生用凿木的方式凿刻出来的&#xff0c;呈现出了凿痕的效果&#xff0c;与众不同。 孙溟㠭凿篆《无用之用》 孙溟㠭凿篆《无用之用》 万般皆有所用&#xff0c;取其长补余短&#xff0c;无用之用是为大用&#xff0…

JS实现高度不等的列表虚拟滚动加载

当我们拿到后台返回的1万条数据展示时&#xff0c;如果完全渲染不仅渲染的时间很长&#xff0c;也会导致浏览器性能变差&#xff0c;使用过程中还可能会导致卡顿&#xff0c;使用体验变差。 就需要我们想办法优化这种情况&#xff0c;这个时候使用虚拟滚动加载就能很好的避免这…

ESXi服务器无法安装Windows11:“不符合此版本的Windows所需最低系统要求“

目录 一、问题描述1.使用环境2.问题截图3.问题解析 二、解决方法Ⅰ1.按 ShiftF10 弹出命令提示符2.在弹出的Dos框中输入regedit&#xff0c;回车&#xff0c;进入注册表。3.打开HKEY_LOCAL_MACHINE\SYSTEM\Setup&#xff0c;并新建 LabConfig 的项&#xff0c;在 LabConfig 下创…

SAP 查询中间表

可以看到如下代码中&#xff0c;查询了底表zdbconn&#xff0c;又查了中间表ZTFI0072 DATA: gv_dbs(20) ,go_exc_ref TYPE REF TO cx_sy_native_sql_error,gv_error_text TYPE string,lv_count TYPE syst_index.SELECT SINGLE conntxtFROM zdbconn INTO gv_dbsWHERE sy…

RK3568 Android 11 蓝牙BluetoothA2dpSink 获取用于生成频谱的PCM

Android 中的 A2DP Sink A2DP Sink 在 Android 系统中主要用于 接收 其他蓝牙设备&#xff08;如手机、平板、电脑等&#xff09;发送过来的 高质量的立体声音频。简单来说&#xff0c;它让你的 Android 设备可以充当一个 蓝牙音箱 或 耳机 的角色。 核心功能&#xff1a; 接…

【Java】SpringBoot 单体项目创建 与 整合 Mybatis-Plus

文章目录 前言1. 创建项目与整合MP1.1 IDEA创建SpringBoot项目1.2 SpringBoot整合Mybatis-Plus 2. 远程仓库2.1 创建远程仓库/本地仓库2.2 Add/Commit/Push/Pull 3. 总结与补充3.1 解决refusing to merge unrelated histories3.2 总结3.3 结语 参考资料 SpringBoot 单体项目创建…

Hadoop环境搭建

一、Linux环境准备 Linux命令查询https://www.linuxcool.com/ http://linux.51yip.com/ 安装Linux虚拟机 安装 sudo apt install open-vm-tools 安装 sudo apt install open-vm-tools-desktop &#xff08;可选&#xff09;换国内源 ​​ sudo apt update 更新软件列表&…

火焰传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 三、程序设计 main.c文件 IR.h文件 IR.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 火焰传感器是一种常用于检测火焰或特定波长&#xff08;760nm-1100nm&#xff09;红外光的传感器。探测角度60左右&am…

Docker占用根目录/存储空间过多如何清理?

问题背景 使用df -h查看磁盘空间时发现根目录空间不多了&#xff0c;已使用96%&#xff0c;红色警告&#xff01;&#xff01;&#xff01; 于是使用df -h /* 一层一层定位&#xff0c;终于找到了一个大文件 9G多的文件夹&#xff0c;位置是&#xff1a; /var/lib/docker/o…

无线通信-WIFI通信

文章目录 1. 基础知识2. 工作模式3. AT指令4. 常用AT指令实例5. 连接原子云6. 使用usb转ttl模块测试ATK-MW8266D7. 使用STM32F103ZET6战舰开发板透传模式8. 使用STM32F103ZET6战舰板连接原子云 1. 基础知识 ATK-ESP-01 ATK-ESP-01模块支持标准的IEEE802.11b/g/n协议&#xff0c…

【Linux】文件魔法师:时间与日历的解密

欢迎来到 CILMY23 的博客 &#x1f3c6;本篇主题为&#xff1a;文件魔法师&#xff1a;时间与日历的解密 &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux | 算法专题 | 代码…