【OAuth2系列】集成微信小程序登录到 Spring Security OAuth 2.0

news2024/9/21 6:30:24

作者:后端小肥肠

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

姊妹篇:

【Spring Security系列】权限之旅:SpringSecurity小程序登录深度探索_spring security 微信小程序登录-CSDN博客

目录

1. 前言

2. 总体登录流程

3. 数据表设计

3.1. sys_user表

3.2. user_auth表

3.3. 表关系

4. OAuth2 扩展实现小程序登录

 4.1 OAuth2登录涉及到的重要组件讲解

4.2 微信小程序自定义登录

 5. 结语


1. 前言

随着微信小程序在国内的广泛应用,越来越多的企业希望将微信小程序登录功能集成到他们的系统中,以提供更便捷的用户体验。Spring Security OAuth 2.0 是一个强大的框架,可以帮助我们实现这一需求。本文将详细介绍如何在 Spring Security OAuth 2.0 中扩展支持微信小程序登录,通过自定义授权方式实现无缝登录。

2. 总体登录流程

微信登录绑定drawio-第 2 页.drawio.png
                                                                                                                后端小肥肠

上述流程图描述了小程序集成OAuth2获取Token登录步骤及日常携带token访问接口步骤:

登录步骤:

1. 获取用户基本信息

在小程序端,调用 wx.getUserProfile() 方法来获取用户的基本信息。这一步通常是在用户同意授权后进行的,用于获取用户的头像、昵称等基本资料。

2. 获取登录凭证

通过调用 wx.login() 方法,小程序端可以获取到一个 loginCode,这是一个临时登录凭证,用于后续的认证请求。

3. 获取手机号凭证

调用 getPhoneNumber 方法,小程序端可以获取到一个 phoneCode,这是一个用于获取用户手机号的临时凭证。

4. 发送登录请求

小程序端将 loginCodephoneCode 一起发送给开发者服务器。开发者服务器接收到这些凭证后,开始处理登录请求。

5. 调用微信接口获取 OpenID

开发者服务器使用 appidappsecret 以及 loginCode 调用微信的登录凭证校验接口(https://api.weixin.qq.com/sns/jscode2session),微信返回 session_keyopenid 等信息。

6. 检查 OpenID

开发者服务器查询本地的 user_auth 表,检查是否存在对应的 openid。如果 openid 已经存在,说明用户已经注册过,直接进行登录操作。

7. 获取 AccessToken

如果 openid 不存在,开发者服务器需要调用微信的接口获取 access_tokenhttps://api.weixin.qq.com/cgi-bin/token)。

8. 获取用户手机号

使用获取到的 access_tokenphoneCode,开发者服务器调用微信接口(https://api.weixin.qq.com/wxa/business/getuserphonenumber)来获取用户的手机号。

9. 用户注册或绑定

开发者服务器将获取到的手机号与 user 表中的数据进行比对。如果存在匹配的手机号,则在 user_auth 表中新增一条记录,将 openid 绑定到用户账户上。如果不存在匹配的手机号,则创建一个新的用户账户,并将 openid 与该新账户绑定。

10. 自定义登录状态

开发者服务器根据用户的注册或绑定结果,创建并返回一个自定义的 OAuth2 登录状态(OAuth2Authentication 对象),并生成相应的 Token。

11. 返回 Token 和用户基本信息

开发者服务器将生成的 Token 和用户的基本信息返回给小程序端。

携带token访问接口步骤:

1. 业务请求

小程序端在后续的业务请求中,将 Token 放在请求头中发送给开发者服务器。

2. 验证登录状态

开发者服务器在接收到业务请求后,验证请求头中的 Token 以确认用户的登录状态,并处理相应的业务逻辑。

3. 返回业务数据

验证通过后,开发者服务器返回相应的业务数据给小程序端,完成整个流程。

通过以上步骤,实现了微信小程序与 OAuth2 的无缝集成,确保了用户的便捷登录和系统的安全性。


3. 数据表设计

3.1. sys_user表

CREATE TABLE "public"."sys_user" (
  "id" "pg_catalog"."varchar" COLLATE "pg_catalog"."default" NOT NULL,
  "username" "pg_catalog"."varchar" COLLATE "pg_catalog"."default" NOT NULL,
  "password" "pg_catalog"."varchar" COLLATE "pg_catalog"."default",
  "is_enabled" "pg_catalog"."int4",
  "mobile" "pg_catalog"."varchar" COLLATE "pg_catalog"."default",
  "create_time" "pg_catalog"."timestamp",
  "update_time" "pg_catalog"."timestamp",
  "version" "pg_catalog"."int4" DEFAULT 1,
  "department_id" "pg_catalog"."varchar" COLLATE "pg_catalog"."default",
  "name" "pg_catalog"."varchar" COLLATE "pg_catalog"."default" NOT NULL,
  "image_url" "pg_catalog"."varchar" COLLATE "pg_catalog"."default",
  CONSTRAINT "sys_user_intranet_pkey" PRIMARY KEY ("id")
)
;


COMMENT ON COLUMN "public"."sys_user"."id" IS '用户 ID';

COMMENT ON COLUMN "public"."sys_user"."username" IS '用户名';

COMMENT ON COLUMN "public"."sys_user"."password" IS '密码,加密存储, admin/1234';

COMMENT ON COLUMN "public"."sys_user"."is_enabled" IS '帐户是否可用(1 可用,0 删除用户)';

COMMENT ON COLUMN "public"."sys_user"."mobile" IS '注册手机号';

COMMENT ON COLUMN "public"."sys_user"."create_time" IS '创建时间';

COMMENT ON COLUMN "public"."sys_user"."update_time" IS '更新时间';

COMMENT ON COLUMN "public"."sys_user"."version" IS '乐观锁';

COMMENT ON COLUMN "public"."sys_user"."name" IS '真实姓名';

COMMENT ON TABLE "public"."sys_user" IS '用户信息表';

3.2. user_auth表

CREATE TABLE "public"."sys_user_auth" (
  "id" "pg_catalog"."varchar" COLLATE "pg_catalog"."default" NOT NULL,
  "user_id" "pg_catalog"."varchar" COLLATE "pg_catalog"."default" NOT NULL,
  "identity_type" "pg_catalog"."varchar" COLLATE "pg_catalog"."default" NOT NULL,
  "identifier" "pg_catalog"."varchar" COLLATE "pg_catalog"."default",
  "credential" "pg_catalog"."varchar" COLLATE "pg_catalog"."default",
  "log_time" "pg_catalog"."timestamp",
  "is_phone_verified" "pg_catalog"."int2",
  "is_email_verified" "pg_catalog"."int2",
  CONSTRAINT "sys_user_auth_pkey" PRIMARY KEY ("id"),
  CONSTRAINT "user_auth_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."sys_user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
)
;

 上表中的identifier字段为小程序登录时的openId:

3.3. 表关系

在上图中可看出sys_user表的id与sys_user_auth中的user_id为逻辑外键的关系。 


4. OAuth2 扩展实现小程序登录

 
4.1 OAuth2登录涉及到的重要组件讲解

1. TokenEndpoint

Token请求的入口是 TokenEndpoint。客户端通过 /oauth/token 发送请求来获取访问令牌。

2. ClientDetailsService

ClientDetailsService 负责加载客户端的详细信息。InMemoryClientDetailsService 是一种典型的实现方式,它从内存中加载客户端详细信息,但也可以使用其他实现,例如从数据库加载数据的 JdbcClientDetailsService

3. TokenRequest

加载客户端详细信息后,会创建一个 TokenRequest 对象,该对象包含关于客户端请求令牌的信息,例如客户端ID、授权类型、范围等。

4. TokenGranter

TokenGranter 接口定义了授予令牌的机制。CompositeTokenGranter 是一种典型的实现,根据授权类型(例如授权码、密码、客户端凭证等)委派给其他 TokenGranter 实现。

5. OAuth2Request

OAuth2Request 代表客户端发起的OAuth2请求。该对象封装了客户端请求的所有参数。

6. Authentication

认证过程验证客户端凭据以及其他必要的认证步骤(例如,如果是密码授权类型,还需要验证用户的凭据)。

7. OAuth2Authentication

OAuth2Authentication 对象将 OAuth2Request 与认证的主体(用户详细信息或客户端详细信息)结合起来。这个对象用于创建访问令牌。

8. AuthorizationServerTokenServices

AuthorizationServerTokenServices 接口定义了发放令牌的操作。DefaultTokenServices 是一种典型的实现,处理令牌的创建和持久化。

9. TokenStore 和 TokenEnhancer

TokenStore 接口定义了如何存储和检索令牌(例如,内存、数据库、JWT等)。TokenEnhancer 允许在令牌发放之前添加额外的信息。

10. OAuth2AccessToken

这个令牌包含访问令牌本身,以及其他信息如过期时间、刷新令牌、范围等。

4.2 微信小程序自定义登录

OAuth2默认授权方式为5种(授权码模式、简化模式、密码模式、客户端凭据模式和刷新令牌模式),不包含小程序登录,需要编写自定义授权代码,拓展AbstractTokenGranter,步骤如下:

1.在原有的五种授权模式上新增WechatTokenGranter,集成自AbstractTokenGranter:

package com.geoscene.ynbackoauth2server.oauth2.granter;

/**
 * @version 1.0
 * @description: TODO
 * @author: xfc
 * @date 2022-10-10 15:12
 */

import com.geoscene.ynbackoauth2server.oauth2.authentication.WechatAuthenticationToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;


public class WechatTokenGranter extends AbstractTokenGranter {

    // 自定义授权方式为 wechat
    private static final String GRANT_TYPE = "wechat";

    private final AuthenticationManager authenticationManager;

    public WechatTokenGranter(AuthenticationManager authenticationManager,
                              AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
    }

    protected WechatTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                                 ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

        Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
        String loginCode = parameters.get("loginCode");
        String phoneCode=parameters.get("phoneCode");
//        String encryptedData = parameters.get("encryptedData");
//        String iv = parameters.get("iv");

        // 移除后续无用参数
        parameters.remove("loginCode");
        parameters.remove("phoneCode");
//        parameters.remove("encryptedData");
//        parameters.remove("iv");

        Authentication userAuth = new WechatAuthenticationToken(loginCode,phoneCode); // 未认证状态
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);

        try {
            userAuth = this.authenticationManager.authenticate(userAuth); // 认证中
        } catch (Exception e) {
            throw new InvalidGrantException(e.getMessage());
        }

        if (userAuth != null && userAuth.isAuthenticated()) { // 认证成功
            OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
            return new OAuth2Authentication(storedOAuth2Request, userAuth);
        } else { // 认证失败
            throw new InvalidGrantException("Could not authenticate code: " + loginCode);
        }
    }
}

WechatTokenGranter 类通过自定义的授权类型 "wechat" 实现了微信小程序登录的认证流程。它扩展了 AbstractTokenGranter,并通过重写 getOAuth2Authentication 方法来处理微信特有的认证逻辑。该类确保了在微信小程序登录过程中,能够正确处理 loginCodephoneCode,并通过 authenticationManager 进行认证,最终返回 OAuth2 的认证结果。 

2.新增WechatAuthenticationProvider用于验证WechatAuthenticationToken:

package com.geoscene.ynbackoauth2server.oauth2.authentication;

/**
 * @version 1.0
 * @description: TODO
 * @author: xfc
 * @date 2022-10-10 15:30
 */

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.geoscene.ynbackapi.entities.SysUser;
import com.geoscene.ynbackapi.feign.IFeignSystemController;
import com.geoscene.ynbackapi.req.AddUserAuthReq;
import com.geoscene.ynbackoauth2server.oauth2.config.WechatConfig;
import com.geoscene.ynbackoauth2server.oauth2.service.JwtUser;
import com.geoscene.ynbackoauth2server.oauth2.service.WeChatService;
import com.geoscene.ynbackoauth2server.web.utils.RedisUtils;
import com.geoscene.ynbackoauth2server.web.utils.RestTemplateUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.*;

@Slf4j
@Component
public class WechatAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private WechatConfig wechatConfig;

    @Autowired
    private WeChatService weChatService;

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    IFeignSystemController feignSystemController;


    @Override
    @SneakyThrows
    public Authentication authenticate(Authentication authentication) {

        WechatAuthenticationToken wechatAuthenticationToken = (WechatAuthenticationToken) authentication;
        String loginCode = wechatAuthenticationToken.getPrincipal().toString();
        log.info("loginCode为:{}",loginCode);
        String phoneCode=wechatAuthenticationToken.getPhoneCode().toString();
        log.info("phoneCode为:{}",phoneCode);
        //获取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(feignSystemController.getuserAuthCountByIdentifier(openId)>0){
             jwtUser = (JwtUser) weChatService.getUserByOpenId(openId);
             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);
        feignSystemController.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;
    }
}

代码讲解: 

1. 根据前端传入的code,结合appid和secret,远程调用https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code获取openId;

2.根据获取的openId查看user_auth表中identifier是否有对应openId,有直接登录返回token,没有则调用获取手机号接口;

3.根据appid和secret调用https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}获取accessToken;

4.根据前端传来的code(与获取openId的code不同)结合accessToken调用https://api.weixin.qq.com/wxa/business/getuserphonenumber获取手机号;

5.将获取的手机号与user表中手机号进行对比,存在则在user_auth表中新增一条数据,并返回token;

6.不存在手机号则在user表中新增一条数据,用户名为手机号,权限为普通用户,同时在user_auth,user_role中新增一条记录,并登录返回token;

 5. 结语

通过本文的介绍,我们成功地在 Spring Security OAuth 2.0 中实现了微信小程序的登录扩展。通过自定义授权器和验证器,我们能够处理微信小程序特有的登录流程,确保用户能够安全、便捷地通过小程序登录到我们的系统中。这不仅提升了用户体验,也增强了系统的安全性和灵活性。如果你在实际操作中遇到任何问题或有更好的建议,欢迎交流与探讨。

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

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

相关文章

2025第25届北京环卫展|市政设施展|清洗设备展览会

2025第25届北京国际环卫与市政设施及清洗设备展览会 时间&#xff1a;2025年 4月10-12日 地点&#xff1a;全国农业展览馆&#xff08;朝阳区北三环东路16号&#xff09; 邀 请 函 指导支持&#xff1a;中国城市环境卫生协会 北京市城市管理委员会 主办单位&#xff1a;北京…

Coggle数据科学 | Kaggle 知识点:时序模型 Prophet

本文来源公众号“Coggle数据科学”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Kaggle 知识点&#xff1a;时序模型 Prophet Prophet 算法 在时间序列中Prophet是Facebook开源的时间序列预测算法&#xff0c;可以有效处理节假…

CSS学习笔记[Web开发]

CSS学习 本文为学习笔记&#xff0c;参考菜鸟和w3c 文章目录 CSS 简介CSS 插入外部 CSS内部 CSS行内 CSS多个样式表层叠顺序 CSS 语法例子解释 CSS 选择器CSS 元素选择器CSS id 选择器实例CSS 类选择器实例CSS 通用选择器实例CSS 分组选择器CSS 后代选择器CSS 子元素选择器CSS …

Java初级之集合(Map可变参数集合工具类)

目录 1、Map集合 1.1 Map集合的概述和特点 1.2 Map集合的基本功能 1.3 Map集合的获取功能 1.4 Map集合的遍历&#xff08;一&#xff09; 1.4 Map集合的遍历&#xff08;二&#xff09; 2、HashMap 2.1HashMap集合概述和特点 2.2HashMap集合应用案例 3、TreeMap集合 …

.Net Core 微服务之Consul(三)-KV存储分布式锁

引言: 集合上两期.Net Core 微服务之Consul(一)(.Net Core 微服务之Consul(一)-CSDN博客) 。.Net Core 微服务之Consul(二)-集群搭建)(.Net Core 微服务之Consul(二)-集群搭建-CSDN博客) 目录 一. Consul KV 存储 1. KV 存储介绍 1.1 数据模型 1.2 一致性和…

react18+

主要是围绕函数式组件讲&#xff0c;18主要用就是函数式组件&#xff0c;学习前先熟悉下原生js的基本使用&#xff0c;主要是事件 1、UI操作 1.1、书写jsx标签语言 基本写法和原生如同一则&#xff0c;只是放在一个方法里面返回而已&#xff0c;我们称这样的写法为函数式组件…

牛客JS题(三)文件扩展名

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; 正则表达式可选链操作符 题干&#xff1a; 我的答案 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /></head><body><script>/*** 可能…

Vue前端页面嵌入mermaid图表--流程图

一、安装Mermaid 首先&#xff0c;你需要在你的项目中安装Mermaid。可以通过npm或yarn来安装&#xff1a; npm install mermaid --save # 或者 yarn add mermaid结果如图&#xff1a; 二、Vue 方法一&#xff1a;使用pre标签 使用ref属性可以帮助你在Vue组件中访问DOM元素 …

JAVA同城服务场馆门店预约系统支持H5小程序APP源码程序

&#x1f525;同城服务场馆门店预约系统&#xff1a;一键预约&#xff0c;便捷生活新体验&#xff01;&#x1f389; &#x1f31f;告别排队等待&#xff0c;预约新风尚&#x1f31f; 你还在为去游泳馆、健身房或是瑜伽馆排队等待而烦恼吗&#xff1f;同城服务场馆门店预约系统…

渲染技术如何帮助设计内容实现从平面到立体的转换

随着数字艺术和视觉特效的飞速发展&#xff0c;三维建模与渲染技术在影视、游戏、广告、工业设计、建筑可视化等多个领域展现出了其不可或缺的重要性。这一技术不仅实现了从平面到立体的跨越&#xff0c;还极大地丰富了视觉表达的层次感和真实感。 三维建模&#xff1a;构建虚…

模拟电子技术-实验四 二极管电路仿真

实验四 二极管电路仿真 一&#xff0e;实验类型 验证性实验 二&#xff0e;实验目的 1、验证二极管的单向导电性 2、验证二极管的稳压特性。 三&#xff0e;实验原理 二极管的单向导电性&#xff1a; 四、实验内容 1、二极管参数测试仿真实验 1&#xff09;仪表仿真…

【Linux】线程互斥和同步

目录 线程互斥 相关概念 互斥量mutex 互斥量的接口 初始化互斥量 销毁互斥量 互斥量加锁/解锁 可重入VS线程安全 概念 可重入与线程安全的联系 可重入与线程安全的区别 死锁 死锁的四个必要条件 避免死锁 避免死锁的算法 线程同步 条件变量 条件变量函数 初始…

【已解决】ModuleNotFoundError: No module named ‘numpy’

【已解决】ModuleNotFoundError: No module named ‘numpy’ 在Python编程中&#xff0c;遇到“ModuleNotFoundError: No module named ‘numpy’”这样的错误提示并不罕见。这个错误意味着Python解释器无法在你的环境中找到名为numpy的模块。numpy是Python中一个非常重要的库…

ElasticSearch(四)— 数据检索与查询

一、基本查询语法 所有的 REST 搜索请求使用_search 接口&#xff0c;既可以是 GET 请求&#xff0c;也可以是 POST请求&#xff0c;也可以通过在搜索 URL 中指定索引来限制范围。 _search 接口有两种请求方法&#xff0c;一种是基于 URI 的请求方式&#xff0c;另一种是基于…

JavaScript第一天

变量的基本使用 更新变量 let age 18age 19 用户名输入案例&#xff1a; let uname prompt(请输入姓名) document.write(uname) 这样在提示框中输入姓名之后&#xff0c;就会在网页中显示出来 当输入之后不在网页中显示的时候&#xff0c;可能是变量名写错了&#xf…

C++笔记---缺省参数和函数重载

1. 缺省参数 1.1 定义 缺省参数是声明或定义函数时为函数的参数指定一个缺省值&#xff08;默认值&#xff09;。在调用该函数时&#xff0c;如果没有指定实参 则采用该形参的缺省值&#xff0c;否则使用指定的实参&#xff0c;缺省参数分为全缺省和半缺省参数。 void Func(…

Linux源码阅读笔记14-IO体系结构与访问设备

IO体系结构 与外设通信通常称为输入输出&#xff0c;一般缩写为I/O。在实现外设IO的时候&#xff0c;内核必须处理三个可能出现的问题&#xff1a; 必须根据具体的设备类型和模型&#xff0c;使用各种方法对硬件寻址。内核必须向用户应用程序和系统工具提供访问各种设备的方法…

【Git多人协作开发】同一分支下的多人协作开发模式

目录 0.前言场景 1.开发者1☞完成准备工作&协作开发 1.1创建dev分支开发 1.2拉取远程dev分支至本地 1.3查看分支情况和分支联系情况 1.4创建本地dev分支且与远程dev分支建立联系 1.5在本地dev分支上开发file.txt 1.6推送push至远程仓库 2.开发者2☞完成准备工作&…

性能测试工具、负载测试工具、缺陷跟踪工具推荐

负载测试工具 - 有助于对站点或应用程序进行性能/负载测试 1&#xff09;WebLOAD WebLOAD 是一款出色的测试工具&#xff0c;提供了许多强大的脚本功能&#xff0c;有助于测试复杂场景。该工具支持从 Selenium 到移动端、从企业应用到网络协议的数百种技术。使用这款工具可以…

Java | Leetcode Java题解之第279题完全平方数

题目&#xff1a; 题解&#xff1a; class Solution {public int numSquares(int n) {if (isPerfectSquare(n)) {return 1;}if (checkAnswer4(n)) {return 4;}for (int i 1; i * i < n; i) {int j n - i * i;if (isPerfectSquare(j)) {return 2;}}return 3;}// 判断是否为…