【Spring Security系列】权限之旅:SpringSecurity小程序登录深度探索

作者:后端小肥肠

创作不易,未经允许严禁转载。

姊妹篇:

【Spring Security系列】Spring Security+JWT+Redis实现用户认证登录及登出_spring security jwt 退出登录-CSDN博客

1. 前言

欢迎来到【Spring Security系列】!在当今数字化时代,安全是任何应用程序都必须优先考虑的核心问题之一。而在众多安全框架中,Spring Security 作为一个功能强大且广泛应用的安全框架,为 Java 应用程序提供了全面的身份验证、授权、攻击防护等功能。而随着移动应用的普及,小程序作为一种轻量级、跨平台的应用形式,其安全性也成为了开发者们关注的焦点。本文将带领您深入探索如何使用 Spring Security 来保护小程序的登录认证,旨在为您提供全方位的学习体验和实践指导。

2. 小程序登录涉及SpringSecurity核心组件介绍

如果要在SpringSecurity默认的用户名密码模式登录模式上扩展小程序登录,涉及到的核心组件如下:

  1. AuthenticationProvider

    创建自定义的AuthenticationProvider,负责处理从微信开放平台获取的用户信息,并进行身份验证。
  2. UserDetailsService

    调整UserDetailsService来获取并管理基于微信OpenID的用户信息。
  3. AuthenticationManager

    确保您的自定义AuthenticationProvider被正确注册到AuthenticationManager中,以便处理小程序登录请求。
  4. SecurityConfigurer

    创建一个SecurityConfigurer来配置Spring Security以支持小程序登录,并将其添加到Spring Security的配置类中。
  5. Filter

    创建一个自定义的过滤器来拦截和处理小程序登录请求,提取微信登录凭证,并将其传递给您的自定义AuthenticationProvider进行处理。

要扩展Spring Security以支持小程序登录,您需要创建自定义的AuthenticationProvider并调整UserDetailsService以处理微信OpenID的用户信息。

3. SpringSecurity集成小程序登录原理

3.1. 小程序登录流程

以下是微信官方文档中小程序登录的流程:

由上图可看出,小程序登录使用微信提供的登录凭证 code,通过微信开放平台的接口获取用户的唯一标识 OpenID 和会话密钥 SessionKey。在集成小程序登录时,我们需要将这些凭证传递给后端服务器,由后端服务器进行校验和处理,最终完成用户的登录认证。

3.2. SpringSecurity集成小程序登录流程梳理

结合SpringSecurity原理,在SpringSecurity中集成小程序登录的流程如下:

  • 小程序端:通过微信登录接口获取登录凭证 code,这里取名为loginCode。
  • 小程序端,通过手机号快速验证组件获取phoneCode。
  • 小程序端,获取用户昵称(nickName)和用户头像(imageUrl)地址。
  • 小程序端:将登录凭证 loginCodephoneCodenickNameimageUrl发送给后端服务器。
  • 后端服务器:接收到登录凭证 loginCode后,调用微信开放平台的接口,换取用户的唯一标识 OpenID 和会话密钥 SessionKey
  • 后端服务器:根据 OpenID 查询用户信息,如果用户不存在,则创建新用户;如果用户已存在,则返回用户信息。
  • 后端服务器:生成用户的身份认证信息JWT Token,返回给小程序端。
  • 小程序端:存储用户的身份认证信息,后续请求携带该信息进行访问控制。

大体流程只是在3.1小程序登录流程上做了细化,图我就不画了(因为懒)。

3.3. 小程序登录接口设计

小程序登录接口如下图所示:

由上图所示,我们需要传入loginCode,phoneCode(获取手机号),nickName(昵称用于登录后展示),imageUrl(头像用于登录后展示)这几个必传参数。

4. 核心代码讲解

4.1. 小程序端获取必要参数

1. 小程序端调用微信登录接口,获取用户登录凭证 loginCode。

wx.login({success (res) {if (res.code) {//发起网络请求wx.request({url: 'https://example.com/onLogin',data: {code: res.code}})} else {console.log('登录失败!' + res.errMsg)}}
})

2. 获取PhoneCode

3. 获取头像昵称

4.2. 编写WeChatAuthenticationFilter

public class WeChatAuthenticationFilter extends AbstractAuthenticationProcessingFilter {private final String loginCode = "loginCode";private final String phoneCode="phoneCode";private final String nickName="nickName";private final String imageUrl="imageUrl";public WeChatAuthenticationFilter(String appId, String secret) {super(new AntPathRequestMatcher("/wx/login", "POST"));}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {String loginCode = obtainLoginCode(request)==null?"":obtainLoginCode(request).trim();String phoneCode=obtainPhoneCode(request)==null?"":obtainPhoneCode(request).trim();String nickName=obtainNickName(request)==null?"":obtainNickName(request).trim();String imageUrl=obtainImageUrl(request)==null?"":obtainImageUrl(request).trim();WechatAuthenticationToken authRequest = new WechatAuthenticationToken(loginCode,phoneCode,nickName,imageUrl);return this.getAuthenticationManager().authenticate(authRequest);}protected String obtainLoginCode(HttpServletRequest request) {return request.getParameter(loginCode);}protected String obtainPhoneCode(HttpServletRequest request){return request.getParameter(phoneCode);}protected String obtainNickName(HttpServletRequest request){return request.getParameter(nickName);}protected String obtainImageUrl(HttpServletRequest request){return request.getParameter(imageUrl);}
}

以上代码定义了一个名为WeChatAuthenticationFilter的类,它继承自AbstractAuthenticationProcessingFilter类,用于处理微信登录认证。在构造函数中,指定了请求匹配路径为"/wx/login",请求方法为POST。类中定义了四个常量:loginCode、phoneCode、nickName和imageUrl,分别表示登录码、手机号码、昵称和头像URL。

attemptAuthentication方法中,首先通过obtainLoginCode、obtainPhoneCode、obtainNickName和obtainImageUrl方法获取请求中的登录码、手机号码、昵称和头像URL,并进行了空值处理。然后将这些信息封装到WechatAuthenticationToken对象中,并通过getAuthenticationManager().authenticate方法进行认证。

4.3. 编写WeChatAuthenticationProvider

@Slf4j
public class WeChatAuthenticationProvider implements AuthenticationProvider {private final WechatConfig wechatConfig;private  RestTemplate restTemplate;private final WeChatService weChatService;private final ISysUserAuthService sysUserAuthService;public WeChatAuthenticationProvider(WechatConfig wechatConfig, WeChatService weChatService,ISysUserAuthService sysUserAuthService) {this.wechatConfig = wechatConfig;this.weChatService = weChatService;this.sysUserAuthService=sysUserAuthService;}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {WechatAuthenticationToken wechatAuthenticationToken = (WechatAuthenticationToken) authentication;String loginCode = wechatAuthenticationToken.getPrincipal().toString();log.info("loginCode is {}",loginCode);String phoneCode=wechatAuthenticationToken.getPhoneCode().toString();log.info("phoneCode is {}",phoneCode);String nickName=wechatAuthenticationToken.getNickName().toString();log.info("nickName is {}",nickName);String imageUrl=wechatAuthenticationToken.getImageUrl().toString();log.info("imageUrl is {}",imageUrl);restTemplate=new RestTemplate();//获取openIdJwtUser jwtUser=null;String url = "https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code";Map<String, String> requestMap = new HashMap<>();requestMap.put("appid", wechatConfig.getAppid());requestMap.put("secret", wechatConfig.getSecret());requestMap.put("code", loginCode);ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class,requestMap);JSONObject jsonObject= JSONObject.parseObject(responseEntity.getBody());log.info(JSONObject.toJSONString(jsonObject));String openId=jsonObject.getString("openid");if(StringUtils.isBlank(openId)) {throw new BadCredentialsException("weChat get openId error");}if(sysUserAuthService.getUserAuthCountByIdentifier(openId)>0){jwtUser = (JwtUser) weChatService.getUserByOpenId(openId);if(!jwtUser.isEnabled()){throw new BadCredentialsException("用户已失效");}return getauthenticationToken(jwtUser,jwtUser.getAuthorities());}//获取手机号第一步,获取accessTokenString accessTokenUrl="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}";Map<String, String> accessTokenRequestMap = new HashMap<>();accessTokenRequestMap.put("appid", wechatConfig.getAppid());accessTokenRequestMap.put("secret", wechatConfig.getSecret());ResponseEntity<String>  accessTokenResponseEntity = restTemplate.getForEntity(accessTokenUrl, String.class,accessTokenRequestMap);JSONObject  accessTokenJsonObject= JSONObject.parseObject(accessTokenResponseEntity.getBody());log.info(JSONObject.toJSONString(accessTokenJsonObject));String  accessToken=accessTokenJsonObject.getString("access_token");if(StringUtils.isBlank(accessToken)) {throw new BadCredentialsException("weChat get accessToken error");}//获取手机号第二部,远程请求获取手机号String pohoneUrl="https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token="+accessToken+"";JSONObject phoneJson=new JSONObject();phoneJson.put("code",phoneCode);String resPhoneStr= RestTemplateUtil.postForJson(pohoneUrl,phoneJson,restTemplate);log.info(resPhoneStr);JSONObject resPhonJson= JSON.parseObject(resPhoneStr);JSONObject phoneInfo=resPhonJson.getJSONObject("phone_info");String mobile=phoneInfo.getString("phoneNumber");if(StringUtils.isBlank(mobile)){throw new BadCredentialsException("Wechat get mobile error");}jwtUser= (JwtUser) weChatService.getUserByMobile(mobile,nickName,imageUrl);sysUserAuthService.saveUserAuth(new AddUserAuthReq(jwtUser.getUid(),"wechat",openId));return getauthenticationToken(jwtUser,jwtUser.getAuthorities());}@Overridepublic boolean supports(Class<?> authentication) {return WechatAuthenticationToken.class.isAssignableFrom(authentication);}public WechatAuthenticationToken getauthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities){WechatAuthenticationToken authenticationToken=new WechatAuthenticationToken(principal,authorities);LinkedHashMap<Object, Object> linkedHashMap = new LinkedHashMap<>();linkedHashMap.put("principal", authenticationToken.getPrincipal());authenticationToken.setDetails(linkedHashMap);return authenticationToken;}
}

上述代码是一个自定义的认证提供者类,名为WeChatAuthenticationProvider。其主要功能是处理微信登录认证。在authenticate方法中,首先从传入的Authentication对象中提取出微信登录所需的参数,包括登录码、手机号码、昵称和头像URL。然后通过RestTemplate发送HTTP请求到微信API获取用户的openId,以验证用户身份。若成功获取openId,则检查系统中是否存在该用户的认证信息,若存在则直接返回认证token;若不存在,则继续获取用户的手机号,并根据手机号获取用户信息,并保存用户认证信息。最后,返回经过认证的token。

4.4. 编写WechatAuthenticationToken

public class WechatAuthenticationToken extends AbstractAuthenticationToken {private final Object principal;private  Object phoneCode;private Object nickName;private Object imageUrl;public WechatAuthenticationToken(String loginCode,String phoneCode,String nickName,String imageUrl) {super(null);this.principal = loginCode;this.phoneCode=phoneCode;this.nickName=nickName;this.imageUrl=imageUrl;setAuthenticated(false);}public WechatAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;super.setAuthenticated(true);}@Overridepublic Object getCredentials() {return null;}@Overridepublic Object getPrincipal() {return this.principal;}public Object getPhoneCode() {return phoneCode;}public Object getNickName() {return nickName;}public Object getImageUrl() {return imageUrl;}
}

4.5. WechatConfig

@Data
@Component
@ConfigurationProperties(prefix="wechat")
public class WechatConfig {private String appid;private String secret;
}

4.6. 更新WebSecurityConfigurer

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {@Autowired@Qualifier("authUserDetailsServiceImpl")private UserDetailsService userDetailsService;@Autowiredprivate SecurOncePerRequestFilter securOncePerRequestFilter;@Autowiredprivate SecurAuthenticationEntryPoint securAuthenticationEntryPoint;@Autowiredprivate SecurAccessDeniedHandler securAccessDeniedHandler;//登录成功处理器@Autowiredprivate SecurAuthenticationSuccessHandler securAuthenticationSuccessHandler;@Autowiredprivate SecurAuthenticationFailureHandler securAuthenticationFailureHandler;//退出处理器@Autowiredprivate SecurLogoutHandler securLogoutHandler;@Autowiredprivate SecurLogoutSuccessHandler securLogoutSuccessHandler;@AutowiredBCryptPasswordEncoderUtil bCryptPasswordEncoderUtil;@Value("${wechat.appid}")private String appId;@Value("${wechat.secret}")private String secret;@AutowiredWechatConfig wechatConfig;@Autowiredprivate  WeChatService weChatService;@Autowiredprivate ISysUserAuthService sysUserAuthService;//    @Autowired
//    DynamicPermission dynamicPermission;/*** 从容器中取出 AuthenticationManagerBuilder,执行方法里面的逻辑之后,放回容器** @param authenticationManagerBuilder* @throws Exception*/@Autowiredpublic void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoderUtil);}@Overrideprotected void configure(HttpSecurity http) throws Exception {//第1步:解决跨域问题。cors 预检请求放行,让Spring security 放行所有preflight request(cors 预检请求)http.authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll();//第2步:让Security永远不会创建HttpSession,它不会使用HttpSession来获取SecurityContexthttp.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().headers().cacheControl();//第3步:请求权限配置//放行注册API请求,其它任何请求都必须经过身份验证.http.authorizeRequests()
//                .antMatchers("/**").permitAll().antMatchers(HttpMethod.POST,"/sys-user/register").permitAll().antMatchers(HttpMethod.GET,"/temp/create","/department/enable-department","/instance/**","/file/download/**").permitAll().antMatchers("/css/**", "/js/**", "/images/**", "/fonts/**","/editor-app/**","/model/**","/editor/**").permitAll().antMatchers("/modeler.html/**").permitAll().antMatchers("/feign/**").permitAll()//ROLE_ADMIN可以操作任何事情.antMatchers("/v2/api-docs", "/v2/feign-docs","/swagger-resources/configuration/ui","/swagger-resources","/swagger-resources/configuration/security","/swagger-ui.html", "/webjars/**").permitAll().antMatchers(HttpMethod.POST, "/user/wx/login").permitAll().anyRequest().authenticated();//                .antMatchers("/**").hasAnyAuthority("USER","SUPER_ADMIN","ADMIN");/*由于使用动态资源配置,以上代码在数据库中配置如下:在sys_backend_api_table中添加一条记录backend_api_id=1,backend_api_name = 所有API,backend_api_url=/**,backend_api_method=GET,POST,PUT,DELETE*///动态加载资源
//                .anyRequest().access("@dynamicPermission.checkPermisstion(request,authentication)");//第4步:拦截账号、密码。覆盖 UsernamePasswordAuthenticationFilter过滤器http.addFilterAt(securUsernamePasswordAuthenticationFilter() , UsernamePasswordAuthenticationFilter.class);//第5步:拦截token,并检测。在 UsernamePasswordAuthenticationFilter 之前添加 JwtAuthenticationTokenFilterhttp.addFilterBefore(securOncePerRequestFilter, UsernamePasswordAuthenticationFilter.class);http.addFilterBefore(weChatAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);//第6步:处理异常情况:认证失败和权限不足http.exceptionHandling().authenticationEntryPoint(securAuthenticationEntryPoint).accessDeniedHandler(securAccessDeniedHandler);//第7步:登录,因为使用前端发送JSON方式进行登录,所以登录模式不设置也是可以的。http.formLogin();//第8步:退出http.logout().addLogoutHandler(securLogoutHandler).logoutSuccessHandler(securLogoutSuccessHandler);}@Beanpublic WeChatAuthenticationFilter weChatAuthenticationFilter() throws Exception {WeChatAuthenticationFilter filter = new WeChatAuthenticationFilter(appId, secret);filter.setAuthenticationManager(authenticationManagerBean());filter.setAuthenticationSuccessHandler(securAuthenticationSuccessHandler);filter.setAuthenticationFailureHandler(securAuthenticationFailureHandler);return filter;}/*** 手动注册账号、密码拦截器* @return* @throws Exception*/@BeanSecurUsernamePasswordAuthenticationFilter securUsernamePasswordAuthenticationFilter() throws Exception {SecurUsernamePasswordAuthenticationFilter filter = new SecurUsernamePasswordAuthenticationFilter();//成功后处理filter.setAuthenticationSuccessHandler(securAuthenticationSuccessHandler);//失败后处理filter.setAuthenticationFailureHandler(securAuthenticationFailureHandler);filter.setAuthenticationManager(authenticationManagerBean());return filter;}@Beanpublic WeChatAuthenticationProvider weChatAuthenticationProvider() {return new WeChatAuthenticationProvider(wechatConfig,weChatService,sysUserAuthService);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 添加微信登录认证提供者auth.authenticationProvider(weChatAuthenticationProvider());// 添加用户名密码登录认证提供者auth.authenticationProvider(daoAuthenticationProvider());}@Beanpublic DaoAuthenticationProvider daoAuthenticationProvider() {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();provider.setUserDetailsService(userDetailsService);provider.setPasswordEncoder(new BCryptPasswordEncoder());return provider;}
}

说一个容易踩坑的地方:在Spring Security中,当你配置了自定义的认证提供者(如weChatAuthenticationProvider())来处理特定类型的认证(如微信登录),如果没有同时配置默认的认证提供者(如daoAuthenticationProvider()),则原有的基于用户名和密码的认证机制不会自动生效。这是因为Spring Security的认证机制是基于一个可配置的AuthenticationManager,它管理一个AuthenticationProvider列表,这些提供者会依次尝试认证用户提交的Authentication请求。

5. 结语

在本文中以流程讲解和代码实操讲解了如何在已有用户名和密码登录的基础上,实现微信小程序登录集成。下期将介绍基于OAuth2框架如何实现小程序登录,感兴趣的同学动动你们发财的小手点点关注吧~

 6. 参考链接 

开放能力 / 用户信息 / 手机号快速验证组件 (qq.com)

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

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

相关文章

识别剪贴板的内容并且添加磁力头

有时候复制的磁力链接并没有磁力头&#xff0c;而只有后面的内容&#xff0c;这个时候就必须给磁力链接添加开头magnet:xturn:btih:下载软件才能识别&#xff0c;如果经常需要这么做比较麻烦&#xff0c;于是我写了一个自动添加磁力头的小软件&#xff0c;运行即可自动添加&…

记录一个写SpringBoot中Hive数据可以正常提取但无法存到MySQL的bug

【背景】 我正在用SpringBoot框架写一个数据治理项目&#xff0c;目前所处阶段是将hive和hdfs中的元数据提取出来&#xff0c;存储到MySQL中&#xff0c;我的hive和hdfs上的数据存储在三台Linux服务器上&#xff08;hadoop102-104&#xff09;&#xff0c;MySQL在我本地Window…

JAVA 转 Golang——速通 Golang 基础

文章目录 1. 前言&#xff1a;2. go的下载与配置3. Golang 目录结构4. Golang 的基础语法4.1. 变量声明4.2. 输入输出4.3. 条件控制4.4. 数组和切片4.5. 映射表 1. 前言&#xff1a; 根据鼠鼠的实习投递经历&#xff0c;由于越来越多中大型公司都使用 Golang&#xff0c;在现在…

vue + SpringBoot + flowable 实现工作流审批功能 (流程图部署)

目录 搭建前端vue项目 vue init webpack project_name 初始化项目 导入 element-ui 框架 npm install element-ui -s 设置 element-ui 全局配置 编辑 main.js 文件 import ElementUI from "element-ui"; // ui框架导入 import element-ui/lib/theme-chal…

文心大模型4.0创建智能体:资深研发专家一对一辅导

目录 前言 一、什么是文心智能体平台&#xff1f; 1、通过平台能做什么 2、平台的优势 3、智能体类型 二、如何访问和使用这个智能体&#xff1f; 1、零代码开发&#xff1a;一句话创建智能体 2、资深研发专家一对一辅导智能体介绍 总结 前言 在当今快节奏和高度竞争的…

Sping源码(八)—registerBeanPostProcessors

序言 之前我们用大量的篇幅介绍过invokeBeanFactoryPostProcessors()方法的执行流程。 而invokeBeanFactoryPostProcessors的主要逻辑就是遍历执行实现了BeanDefinitionRegistryPostProcesso类(主要是针对BeanDefinition的操作)和BeanFactoryPostProcessor(主要针对BeanFacrot…

干货 | 什么是单相感应电机控制器?一文带你看感应交流电机解决方案KP86202

单相感应电机控制器是一种用于控制单相感应电机运行的电子设备。单相感应电机是一种常见的电动机类型&#xff0c;广泛应用于家用电器、商业设备以及轻工制造等领域。 单相感应电机控制器通常包括电源模块、控制逻辑模块和功率输出模块。其主要功能是对单相感应电机进行启停、…

如何生成Github Badge徽章图标

如何生成徽章Badge 什么是徽章(Badge)生成小徽章shields网站开源项目的徽章lib版本徽章代码测试覆盖度开源协议Github workflow的徽章 开源代码实践效果py-enumjs-enumerate 什么是徽章(Badge) 在开源项目的README中&#xff0c;经常会见到一些徽章(Badge)小图标&#xff0c;如…

FastCopy

目录 背景: 简介&#xff1a; 原理: 下载地址: 工具的使用: 背景: 简介&#xff1a; FastCopy是一款速度非常快的拷贝软件&#xff0c;软件版本为5.7.1 Fastcopy是日本的最快的文件拷贝工具&#xff0c;磁盘间相互拷贝文件是司空见惯的事情&#xff0c;通常情况…

redis--redis Cluster

简介 解决了redis单机写入的瓶颈问题&#xff0c;即单机的redis写入性能受限于单机的内存大小、并发数量、网卡速率等因素无中心架构的redis cluster机制&#xff0c;在无中心的redis集群当中&#xff0c;其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连…

好书推荐|MATLAB科技绘图与数据分析

提升你的数据洞察力&#xff0c;用于精确绘图和分析的高级MATLAB技术 MATLAB科技绘图与数据分析——jd 本书内容 《MATLAB科技绘图与数据分析》结合作者多年的数据分析与科研绘图经验&#xff0c;详细讲解MATLAB在科技图表制作与数据分析中的使用方法与技巧。全书分为3部分&a…

C语言 数组——数组的定义和初始化

目录 为什么使用数组(Array)? 一维数组的定义 一维数组的初始化 一维数组元素的访问 一维数组元素的赋值 数组的逻辑存储结构 数组的物理存储结构 二维数组的定义和初始化 为什么使用数组(Array)? 一维数组的定义 一维 数组的定义 int a[10]; 定义一个有 10 个 int 型元素的…

构建传统企业信息化数字化智能化技术架构:挑战与机遇

随着数字化和智能化技术的快速发展&#xff0c;传统企业在信息化转型过程中面临着前所未有的机遇和挑战。如何构建适应企业需求的信息化数字化智能化技术架构&#xff0c;成为企业发展的关键之一。本文将探讨传统企业信息化数字化智能化技术架构的设计与实践。 一、数字化转型的…

【缺失的第一个正数】leetcode,python

真是越做越觉得自己所学尚浅&#xff0c;&#xff0c; 啊&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f; 直接上石山代码&#xff1a;&#xff08;过不了一点&#xff09;。。。。。 class Solution:def firstMissingPositive(self…

SpringBoot发送邮箱

一、导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId> </dependency> 二、添加配置 application.yml文件 将username修改为自己的邮箱&#xff0c;password修改为…

Linux基础入门和帮助-第二篇

马哥教育 Linux SRE 学习笔记 用户登录信息查看命令 whoami: 显示当前登录有效用户 [rootrocky8 ~]$whoami rootwho: 系统当前所有的登录会话 [rootrocky8 ~]$who root pts/0 2024-05-24 12:55 (10.0.0.1)w: 系统当前所有的登录会话及所做的操作 [rootrocky8 ~]…

WordPress 发布了独立的 SQLite 插件

之前 WordPress 在官方的 Performance Lab 插件实现 SQLite 模块&#xff0c;现在重构 SQLite 的实现&#xff0c;并且将其发布成一个独立的插件&#xff1a;SQLite Database Integration。 独立 SQLite 插件 最初的功能模块实现是基于 aaemnnosttv 的 wp-sqlite-db 插件修改实…

使用CyberRT写第一个代码, test ok

简介 计算框架是自动驾驶系统中的重中之重,也是整个系统得以高效稳定运行的基础。为了实时地完成感知、决策和执行,系统需要一系列的模块相互紧密配合,高效地执行任务流。由于各种原因,这些模块可能位于不同进程,也可能位于不同机器。这就要求计算框架中具有灵活的、高性…

谷歌蜘蛛池是什么?

或称为谷歌爬虫池&#xff0c;是一项专门针对谷歌搜索引擎优化&#xff08;SEO&#xff09;的先进技术&#xff0c;这种技术的主要目的是通过建立庞大的网站群体和复杂的链接结构来吸引和维持谷歌的爬虫程序的注意力&#xff0c;其核心是通过这种结构优化&#xff0c;增强特定网…

大学计算机专业三天看完《Python背记手册》全彩版,轻松学会 Python不迷路!

Python作为一门编程语言&#xff0c;Python提供了高效的高级数据结构&#xff0c;还能简单有效地面向对象编程。Python语法和动态类型&#xff0c;以及解释型语言的本质&#xff0c;使它成为多数平台上写脚本和快速开发应用的编程语言&#xff0c;随着版本的不断更新和语言新功…