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

news2025/2/24 23:35:24

作者:后端小肥肠

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

姊妹篇:

【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"));
    }

    @Override
    public 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;
    }

    @Override
    public 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();
        //获取openId
        JwtUser 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());
        }
        //获取手机号第一步,获取accessToken
        String 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());
    }

    @Override
    public 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);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public 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;

    @Autowired
    private SecurOncePerRequestFilter securOncePerRequestFilter;
    @Autowired
    private SecurAuthenticationEntryPoint securAuthenticationEntryPoint;
    @Autowired
    private SecurAccessDeniedHandler securAccessDeniedHandler;

    //登录成功处理器
    @Autowired
    private SecurAuthenticationSuccessHandler securAuthenticationSuccessHandler;
    @Autowired
    private SecurAuthenticationFailureHandler securAuthenticationFailureHandler;

    //退出处理器
    @Autowired
    private SecurLogoutHandler securLogoutHandler;
    @Autowired
    private SecurLogoutSuccessHandler securLogoutSuccessHandler;

    @Autowired
    BCryptPasswordEncoderUtil bCryptPasswordEncoderUtil;


    @Value("${wechat.appid}")
    private String appId;
    @Value("${wechat.secret}")
    private String secret;

    @Autowired
    WechatConfig wechatConfig;

    @Autowired
    private  WeChatService weChatService;
    @Autowired
    private ISysUserAuthService sysUserAuthService;

//    @Autowired
//    DynamicPermission dynamicPermission;

    /**
     * 从容器中取出 AuthenticationManagerBuilder,执行方法里面的逻辑之后,放回容器
     *
     * @param authenticationManagerBuilder
     * @throws Exception
     */
    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoderUtil);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //第1步:解决跨域问题。cors 预检请求放行,让Spring security 放行所有preflight request(cors 预检请求)
        http.authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll();

        //第2步:让Security永远不会创建HttpSession,它不会使用HttpSession来获取SecurityContext
        http.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 之前添加 JwtAuthenticationTokenFilter
        http.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);

    }

    @Bean
    public WeChatAuthenticationFilter weChatAuthenticationFilter() throws Exception {
        WeChatAuthenticationFilter filter = new WeChatAuthenticationFilter(appId, secret);
        filter.setAuthenticationManager(authenticationManagerBean());
        filter.setAuthenticationSuccessHandler(securAuthenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(securAuthenticationFailureHandler);
        return filter;
    }
    /**
     * 手动注册账号、密码拦截器
     * @return
     * @throws Exception
     */
    @Bean
    SecurUsernamePasswordAuthenticationFilter securUsernamePasswordAuthenticationFilter() throws Exception {
        SecurUsernamePasswordAuthenticationFilter filter = new SecurUsernamePasswordAuthenticationFilter();
        //成功后处理
        filter.setAuthenticationSuccessHandler(securAuthenticationSuccessHandler);
        //失败后处理
        filter.setAuthenticationFailureHandler(securAuthenticationFailureHandler);

        filter.setAuthenticationManager(authenticationManagerBean());
        return filter;
    }
    @Bean
    public WeChatAuthenticationProvider weChatAuthenticationProvider() {
        return new WeChatAuthenticationProvider(wechatConfig,weChatService,sysUserAuthService);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 添加微信登录认证提供者
        auth.authenticationProvider(weChatAuthenticationProvider());
        // 添加用户名密码登录认证提供者
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    @Bean
    public 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.coloradmin.cn/o/1691143.html

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

相关文章

【Java】/*类和对象(下)*/

目录 一、封装 1.1 初识封装 1.2 如何封装成员变量 1.3 如何使用封装后的成员变量 二、访问限定符 三、包 3.1 包的概念 3.2 如何自定义包 3.3 导入包中的类 3.4 包的访问权限控制举例 示例一&#xff1a;private修饰成员变量 示例二&#xff1a; 不去修饰成员变…

网络拓扑—DNS服务搭建

文章目录 DNS服务搭建网络拓扑配置网络DNSPC 安装DNS服务配置DNS服务创建正向查找区域创建反向查找区域创建子域名 PC机DNS域名解析 DNS服务搭建 网络拓扑 为了节省我的U盘空间&#xff0c;没有用路由器&#xff0c;所以搭建的环境只要在同网段即可。 //交换机不用考虑 DNS&a…

hugging face笔记:PEFT

1 介绍 PEFT (Parameter-Efficient Fine Tuning) 方法在微调时冻结预训练模型参数&#xff0c;并在其上添加少量可训练的参数&#xff08;称为适配器&#xff09;这些适配器被训练用来学习特定任务的信息。这种方法已被证明在内存效率和计算使用上非常高效&#xff0c;同时能产…

靠着单干实现财富自由,可太爽了

这里所说的“单干”&#xff0c;并不是单打独斗的意思&#xff0c;而是一种商业认知&#xff0c;以及由这种认知衍生出来的商业模式、商业方法和商业实践。 之前提到单干&#xff0c;会本能地以为它是指脱离公司等组织形式&#xff0c;自己一个人做生意。现在单干有了更丰富的…

第十二章 网络编程

第十二章 网络编程 网络协议概述 通信协议&#xff1a; 协议即规则&#xff0c;就好比汽车上路要遵守交通规则一样&#xff0c;为了使全世界不同类型的计算机都可以连接起来&#xff0c;所以制定了一套全球通用的通信协议——Internet协议。有了Internet协议&#xff0c;任何…

【网络技术】【Kali Linux】Wireshark嗅探(十二)NBNS协议报文捕获及分析

往期 Kali Linux 上的 Wireshark 嗅探实验见博客&#xff1a; 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;一&#xff09;ping 和 ICMP 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;二&#xff09;TCP 协议 【网络技术】【Kali Linux】Wireshark嗅探&…

Swift 类和结构体

类和结构体 一、结构体和类对比1、类型定义的语法2、结构体和类的实例3、属性访问4、结构体类型的成员逐一构造器 二、结构体和枚举是值类型三、类是引用类型1、恒等运算符2、指针 结构体和类作为一种通用而又灵活的结构&#xff0c;成为了人们构建代码的基础。你可以使用定义常…

汇舟问卷:海外问卷调查如何闭坑?

大家好&#xff0c;我是汇舟问卷。有很多人问我这行有什么骗局吗&#xff1f;怎么说呢&#xff1f;其实每个行业都是真实存在的&#xff0c;也都有赚到钱的和赚不到钱的&#xff0c;那区别在哪里呢&#xff1f; 在你的源头&#xff0c;也就是教你或者说是代理加盟的上家&#…

LabVIEW舱段测控系统开发

LabVIEW舱段测控系统开发 在航空技术飞速发展的当下&#xff0c;对于航空器的测控系统的需求日益增加&#xff0c;特别是对舱段测控系统的设计与实现。开发了一款基于LabVIEW开发的舱段测控系统&#xff0c;包括系统设计需求、系统组成、工作原理以及系统实现等方面。 开发了…

电赛经验分享——赛前准备

⏩ 大家好哇&#xff01;我是小光&#xff0c;想要成为系统架构师的嵌入式爱好者。 ⏩在之前的电赛中取得了省一的成绩&#xff0c;本文对电赛比赛前需要准备什么做一个经验分享。 ⏩感谢你的阅读&#xff0c;不对的地方欢迎指正。 加入小光嵌入式交流群&#xff08;qq群号&…

【问题解决】Android Studio Jellyfish新建Kotlin项目后Gradle Sync及Maven下载很慢

创建新项目之后&#xff0c;Gradle Sync和Build都很慢&#xff0c;因为下载Gradle和Maven等工具。 代码默认配置 settings.gradle.kts pluginManagement {repositories {google {content {includeGroupByRegex("com\\.android.*")includeGroupByRegex("com\\.g…

框架学习之SpringMVC学习笔记(一)

一、SpringMVC简介 1-介绍 Spring Web MVC是基于Servlet API构建的原始Web框架&#xff0c;从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称&#xff08; spring-webmvc &#xff09;&#xff0c;但它通常被称为“Spring MVC”。 在控制层…

C语言中的 ?: :三元运算符详解

C语言中的 ?: &#xff1a;三元运算符详解 在C语言的浩瀚代码海洋中&#xff0c;三元运算符&#xff08;?:&#xff09;如同一位优雅的舞者&#xff0c;以简洁的姿态完成条件判断与赋值的双重任务。它以问号&#xff08;?&#xff09;和冒号&#xff08;:&#xff09;这两个…

魔众文库系统v6.6.0分销功能,后台日志重构,文档转换优化

分销功能&#xff0c;后台日志重构&#xff0c;文档转换优化 [新功能] 升级支持支付宝授权登录最新方式 [新功能] 后台左上角标题支持自定义&#xff0c;修改 modstart.php 中 admin.title 配置 [新功能] 日志界面重构&#xff0c;全新日志查看体验 [新功能] 链接选择弹窗增…

C语言 | Leetcode C语言题解之第110题平衡二叉树

题目&#xff1a; 题解&#xff1a; int height(struct TreeNode* root) {if (root NULL) {return 0;}int leftHeight height(root->left);int rightHeight height(root->right);if (leftHeight -1 || rightHeight -1 || fabs(leftHeight - rightHeight) > 1) {…

SpringBoot3整合阿里云短信服务-1(配置阿里云短信服务)

SpringBoot3整合阿里云短信服务-1(配置阿里云短信服务) 一、开通阿里云短信服务 阿里云官网:阿里云官网 选择产品中企业服务与云通信中的短信服务 选择免费开通 选择快速学习和测试 根据这几个全部配置一下我这里是配置好了所以学习进度是100% 1.1 添加资质 首先选择新增资质 …

三台泵恒压供水站电控系统及PLC程序设计实例

本文由艺捷自动化编写&#xff0c;其旗下产品有艺捷自动化网站和易为二维码说明书小程序&#xff08;微信&#xff09; 本文以一个具体的项目案例&#xff0c;来讲述一个恒压供水站的电控柜设计过程。包括用户需求&#xff0c;材料选型&#xff0c;图纸设计&#xff0c;柜内布…

微软新功能Recall引发隐私担忧,英国数据监管机构展开调查

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

大语言模型的工程技巧(三)——分布式计算

相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型&#xff1a;从线性回归到通用人工智能》&#xff0c;欢迎有兴趣的读者多多支持。 本文将讨论如何利用多台机器进行神经网络的分布式训练。利用多台机器来加速大语言模型的训练&#xff0c;是其获得成功的重要原…

政安晨:【Keras机器学习示例演绎】(四十九)—— 利用 KerasNLP 实现语义相似性

目录 简介 数据集 设置 准备数据 配置特征空间 进一步自定义特征空间 根据训练数据调整特征空间 制作模型 训练模型 使用端到端模型对新数据进行推理 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实…