【Spring Security系列】权限之旅:SpringSecurity小程序登录深度探索
作者:后端小肥肠
创作不易,未经允许严禁转载。
姊妹篇:
【Spring Security系列】Spring Security+JWT+Redis实现用户认证登录及登出_spring security jwt 退出登录-CSDN博客
1. 前言
欢迎来到【Spring Security系列】!在当今数字化时代,安全是任何应用程序都必须优先考虑的核心问题之一。而在众多安全框架中,Spring Security 作为一个功能强大且广泛应用的安全框架,为 Java 应用程序提供了全面的身份验证、授权、攻击防护等功能。而随着移动应用的普及,小程序作为一种轻量级、跨平台的应用形式,其安全性也成为了开发者们关注的焦点。本文将带领您深入探索如何使用 Spring Security 来保护小程序的登录认证,旨在为您提供全方位的学习体验和实践指导。
2. 小程序登录涉及SpringSecurity核心组件介绍
如果要在SpringSecurity默认的用户名密码模式登录模式上扩展小程序登录,涉及到的核心组件如下:
-
AuthenticationProvider:
创建自定义的AuthenticationProvider,负责处理从微信开放平台获取的用户信息,并进行身份验证。 -
UserDetailsService:
调整UserDetailsService来获取并管理基于微信OpenID的用户信息。 -
AuthenticationManager:
确保您的自定义AuthenticationProvider被正确注册到AuthenticationManager中,以便处理小程序登录请求。 -
SecurityConfigurer:
创建一个SecurityConfigurer来配置Spring Security以支持小程序登录,并将其添加到Spring Security的配置类中。 -
Filter:
创建一个自定义的过滤器来拦截和处理小程序登录请求,提取微信登录凭证,并将其传递给您的自定义AuthenticationProvider进行处理。
要扩展Spring Security以支持小程序登录,您需要创建自定义的AuthenticationProvider并调整UserDetailsService以处理微信OpenID的用户信息。
3. SpringSecurity集成小程序登录原理
3.1. 小程序登录流程
以下是微信官方文档中小程序登录的流程:

由上图可看出,小程序登录使用微信提供的登录凭证 code,通过微信开放平台的接口获取用户的唯一标识 OpenID 和会话密钥 SessionKey。在集成小程序登录时,我们需要将这些凭证传递给后端服务器,由后端服务器进行校验和处理,最终完成用户的登录认证。
3.2. SpringSecurity集成小程序登录流程梳理
结合SpringSecurity原理,在SpringSecurity中集成小程序登录的流程如下:
- 小程序端:通过微信登录接口获取登录凭证 code,这里取名为loginCode。
- 小程序端,通过手机号快速验证组件获取phoneCode。
- 小程序端,获取用户昵称(nickName)和用户头像(imageUrl)地址。
- 小程序端:将登录凭证 loginCode、phoneCode、nickName、imageUrl发送给后端服务器。
- 后端服务器:接收到登录凭证 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)
相关文章:
【Spring Security系列】权限之旅:SpringSecurity小程序登录深度探索
作者:后端小肥肠 创作不易,未经允许严禁转载。 姊妹篇: 【Spring Security系列】Spring SecurityJWTRedis实现用户认证登录及登出_spring security jwt 退出登录-CSDN博客 1. 前言 欢迎来到【Spring Security系列】!在当今数字化…...
【收录 Hello 算法】第 10 章 搜索
目录 第 10 章 搜索 本章内容 第 10 章 搜索 搜索是一场未知的冒险,我们或许需要走遍神秘空间的每个角落,又或许可以快速锁定目标。 在这场寻觅之旅中,每一次探索都可能得到一个未曾料想的答案。 本章内容 10.1 二分查找10.2 二…...
【MySQL精通之路】SQL优化(1)-查询优化(11)-多范围查询优化
主博客: 【MySQL精通之路】SQL优化(1)-查询优化-CSDN博客 上一篇: 【MySQL精通之路】SQL优化(1)-查询优化(10)-外部联接简化-CSDN博客 下一篇: 当基表很大且未存储在存储引擎的缓存中时,使用辅助索引上的范围扫描读取行可能会…...
Mysql之基本架构
1.Mysql简介 mysql是一种关系型数据库,由表结构来存储数据与数据之间的关系,同时为sql(结构化查询语句)来进行数据操作。 sql语句进行操作又分为几个重要的操作类型 DQL: Data Query Language 数据查询语句 DML: Data Manipulation Language 添加、删…...
Python学习---基于正则表达式的简单爬取电影下载信息案例
一、定义函数获取列表页的内容页地址 get_movie_links() 1、定义列表的地址 2、打开url地址,获取数据 3、解码获取到的数据 4、使用正则得到所有的影片内容也地址 4.1 遍历,取出内容页地址 4.2 拼接内容页地址 4.3 打开内容页地址 4.4 获…...
.DS_store文件
感觉mac里的这个.DS_store文件烦人,老是莫名其妙的出现,然后造成困扰 处理方式如下: import os pic_list os.listdir("./mask_pic/") print(len(pic_list)) # 从文件夹中删掉 if(".DS_Store" in pic_list):print(&quo…...
【webrtc】内置opus解码器的移植
m98 ,不知道是什么版本的opus,之前的交叉编译构建: 【mia】ffmpeg + opus 交叉编译 【mia】ubuntu22.04 : mingw:编译ffmpeg支持opus编解码 看起来是opus是1.3.1 只需要移植libopus和opus的webrtc解码部分即可。 linux构建的windows可运行的opus库 G:\NDDEV\aliply-0.4\C…...
Java注解:讲解Java注解(Annotations)的概念,使用,并展示如何自定义注解,甚至框架级别的使用说明
1. 注解的概念 1.1 介绍Annotation的基础概念 Java注解(Annotation)是Java 5.0及更高版本中引入的一种元数据(meta-data),即数据的数据。它以一种形式附着在代码中,但是对代码的运行不产生直接效果。注解可以用于创建文档、追踪代码依赖性、甚至执行编译期版错误检查等…...
二维矩阵乘法案例
二维矩阵相乘计算原理:第一个矩阵的每一行分别与第二个矩阵的每一列做向量点乘,将所得结果填入新矩阵相应的位置。 例如,给定矩阵 A [ [1, 2 ], [3, 4] ]和 B [ [5, 6 ], [7, 8] ],它们的乘积AB分别为: AB[ 0 ] [ 0…...
selenium安装出错
selenium安装步骤(法1): 安装失败法1 第一次实验,失败 又试了一次,失败 安装法2-失败: ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问。: c:\\programdata\\a…...
前端中 dayjs 时间的插件使用(在vue 项目中)
Day.js中文网 这是dayjs的中文文档 里面包括了使用方法 下面我来详细介绍一下这个插件的使用 Day.js 可以运行在浏览器和 Node.js 中。 一般咱直接是 npm 安装 npm install dayjs 目前应该使用的是Es6 的语法 import dayjs from dayjs 当前时间 直接调用 dayjs() 将返回…...
tp5问题集记录 一
tp5问题集记录 一 前言车祸现场 前言 在写tp5接口的时候,发现model里面的参数查询出来之后,怎么改都不生效,也是自己不熟悉钻牛角尖了。 车祸现场 例如下面的代码使用model处理预处理 // SPUpublic function getSpuAttr($value, $data){$…...
AGI技术与原理浅析:曙光还是迷失?
前言:回顾以往博客文章,最近一次更新在2020-07,内容以机器学习、深度学习、CV、Slam为主,顺带夹杂个人感悟。笔者并非算法科班出身,本科学制药、研究生学金融,最原始的算法积累都来源于网络,当时…...
探秘机器学习经典:K-近邻算法(KNN)全解析
在浩瀚的机器学习宇宙中,K-近邻算法(K-Nearest Neighbors,简称KNN)如同一颗璀璨的明星,以其简洁直观的原理和广泛的应用范围,赢得了众多数据科学家的喜爱。今天,让我们一起揭开KNN的神秘面纱,深入探讨它的运作机制、优缺点、应用场景,以及如何在实际项目中灵活运用。 …...
数据可视化每周挑战——全国星巴克门店数据可视化
这是我国星巴克门店的位置,营业时间等数据。 1.导入需要用的库,同时设置绘图时用到的字体,同时防止绘图时负号无法正常显示的情况。 import pandas as pd from pyecharts.charts import Bar,Map,Line,Pie,Geo from pyecharts import option…...
【前端】js通过元素属性获取元素
【前端】js通过元素属性获取元素 <div for"hc_opportunity_config">aaaaa</div>//通过属性获取元素document.querySelector([for"hc_opportunity_config"]) document.querySelector([属性"属性值"])...
申请轻纺行业工程设计资乙级对企业有什么要求
注册资金:企业的注册资金应至少达到三百万,这是衡量企业经济实力和承担风险能力的重要指标。独立法人资格:企业应具备独立的法人资格,能够独立承担民事责任,并具备相应的经营自主权。专业技术人员配备:企业…...
基于单片机电梯控制系统设计与实现
摘 要: 介绍了电梯控制系统架构 , 指出了该系统的硬件设计和控制系统的软件设计以及系统调试 , 使系统可根据按键 要求完成载客任务,为电梯控制系统的优化提供了参考 。 关键词 : 电梯控制 ; 单片机 ; 系统设计 0 引言 在高层建筑中发挥…...
嵌入式单片机笔试题
DC-DC 和 LDO两者有何区别? DC-DC转换器(直流-直流转换器)和LDO(低压差线性稳压器)都是用于电源管理的设备,但它们在原理和特性上有一些显著的区别: 原理: DC-DC转换器通过改变输…...
生活小区火灾预警新篇章:泵吸式可燃气体报警器的检定与运用
在现代化的生活小区中,燃气设备广泛应用于居民的日常生活之中,但同时也带来了潜在的火灾风险。 可燃气体报警器作为一种安全监测设备,能够及时检测到燃气泄漏等安全隐患,并在达到预设的阈值时发出警报,提醒居民采取相…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
