微信登录模块封装

文章目录

    • 1.资质申请
    • 2.combinations-wx-login-starter
        • 1.目录结构
        • 2.pom.xml 引入okhttp依赖
        • 3.WxLoginProperties.java 属性配置
        • 4.WxLoginUtil.java 后端通过 code 获取 access_token的工具类
        • 5.WxLoginAutoConfiguration.java 自动配置类
        • 6.spring.factories 激活自动配置类
    • 3.combinations-wx-starter-demo
        • 1.目录结构
        • 2.pom.xml 引入依赖
        • 3.application.yml 配置AppID和AppSecret
        • 4.application-prod.yml 配置生产环境的日志和.env文件路径
        • 5.CodeAndState.java 接受code和state的bean
        • 6.WxLoginController.java 微信登录Controller
        • 7.WxApplication.java 启动类
    • 4.微信登录流程梳理
        • 1.用户点击微信登录按钮
        • 2.前端向开放平台发送请求主要携带appId和redirectUri
        • 3.此时开放平台会弹出一个扫码的页面,用户扫码确认
        • 4.用户确认成功后,开放平台会将code和state作为参数去请求redirectUri(前端页面)
        • 5.前端页面获取code和state,再向后端发送请求
        • 6.后端使用code进行微信登录,可以获取到AccessTokenResponse

1.资质申请

  1. 主体为企业的域名和备案的服务器
  2. 主体为企业的微信开放平台的开发者资质认证
  3. 微信开放平台创建应用获取AppID和AppSecret

2.combinations-wx-login-starter

1.目录结构

CleanShot 2025-01-23 at 21.17.45@2x

2.pom.xml 引入okhttp依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.sunxiansheng</groupId>
        <artifactId>sunrays-combinations</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>combinations-wx-login-starter</artifactId>
    <!-- 项目名 -->
    <name>${project.groupId}:${project.artifactId}</name>
    <!-- 简单描述 -->
    <description>微信登录模块封装</description>

    <dependencies>
        <!-- okhttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
    </dependencies>
</project>
3.WxLoginProperties.java 属性配置
package cn.sunxiansheng.wx.login.config.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Description: 微信登录的属性配置
 *
 * @Author sun
 * @Create 2025/1/23 18:49
 * @Version 1.0
 */
@ConfigurationProperties(prefix = "sun-rays.wx.login")
@Data
public class WxLoginProperties {

    /**
     * 微信开放平台应用的AppID
     */
    private String appId;

    /**
     * 微信开放平台应用的AppSecret
     */
    private String appSecret;

    /**
     * 微信开放平台的access_token_url前缀,有默认值,可以不填,为了防止变化!
     */
    private String accessTokenUrlPrefix = "https://api.weixin.qq.com/sns/oauth2/access_token";
}
4.WxLoginUtil.java 后端通过 code 获取 access_token的工具类
package cn.sunxiansheng.wx.login.utils;

import cn.sunxiansheng.wx.login.config.properties.WxLoginProperties;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import javax.annotation.Resource;

/**
 * Description: 微信登录工具类
 *
 * @Author sun
 * @Create 2025/1/23 19:01
 * @Version 1.0
 */
@Slf4j
public class WxLoginUtil {

    /**
     * 获取微信登录的配置
     */
    @Resource
    private WxLoginProperties wxLoginProperties;

    /**
     * 微信登录的响应类
     */
    @Data
    public static class AccessTokenResponse {

        @SerializedName("access_token")
        private String accessToken;

        @SerializedName("expires_in")
        private Integer expiresIn;

        @SerializedName("refresh_token")
        private String refreshToken;

        @SerializedName("openid")
        private String openId;

        @SerializedName("scope")
        private String scope;

        @SerializedName("unionid")
        private String unionId;
    }

    /**
     * 根据code来完成微信登录
     *
     * @param code 微信开放平台返回的code
     * @return 返回AccessTokenResponse
     */
    public AccessTokenResponse wxLogin(String code) {
        return getAccessToken(wxLoginProperties.getAppId(), wxLoginProperties.getAppSecret(), code);
    }

    /**
     * 后端通过 code 获取 access_token
     *
     * @param appid  微信应用的 appid
     * @param secret 微信应用的 secret
     * @param code   后端已经获得的 code 参数
     * @return 返回封装的 AccessTokenResponse 对象
     */
    private AccessTokenResponse getAccessToken(String appid, String secret, String code) {
        // 构造请求 URL
        String url = String.format("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
                wxLoginProperties.getAccessTokenUrlPrefix(), appid, secret, code);

        // 创建 OkHttpClient 实例
        OkHttpClient client = new OkHttpClient();

        // 创建 Request 对象
        Request request = new Request.Builder()
                .url(url)
                .build();

        // 执行请求并处理响应
        try (Response response = client.newCall(request).execute()) {
            // 检查请求是否成功
            if (!response.isSuccessful()) {
                String responseBody = response.body() != null ? response.body().string() : "响应体为空";
                log.error("后端通过 code 获取 access_token 的请求失败,响应码:{}, 响应体:{}", response.code(), responseBody);
                return null;
            }

            // 打印成功的响应
            String jsonResponse = response.body() != null ? response.body().string() : "响应体为空";
            log.info("成功获取 access_token,响应:{}", jsonResponse);

            // 使用 Gson 解析 JSON 数据并封装成 AccessTokenResponse 对象
            Gson gson = new Gson();

            // 返回封装的对象
            return gson.fromJson(jsonResponse, AccessTokenResponse.class);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            // 返回 null 或者其他错误处理
            return null;
        }
    }
}
5.WxLoginAutoConfiguration.java 自动配置类
package cn.sunxiansheng.wx.login.config;

import cn.sunxiansheng.wx.login.config.properties.WxLoginProperties;
import cn.sunxiansheng.wx.login.utils.WxLoginUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * Description: 微信登录自动配置类
 *
 * @Author sun
 * @Create 2025/1/13 16:11
 * @Version 1.0
 */
@Configuration
@EnableConfigurationProperties({WxLoginProperties.class})
@Slf4j
public class WxLoginAutoConfiguration {

    /**
     * 自动配置成功日志
     */
    @PostConstruct
    public void logConfigSuccess() {
        log.info("WxLoginAutoConfiguration has been loaded successfully!");
    }

    /**
     * 注入WxLoginUtil
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    WxLoginUtil wxLoginUtil() {
        return new WxLoginUtil();
    }
}
6.spring.factories 激活自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.sunxiansheng.wx.login.config.WxLoginAutoConfiguration

3.combinations-wx-starter-demo

1.目录结构

CleanShot 2025-01-23 at 21.25.48@2x

2.pom.xml 引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.sunxiansheng</groupId>
        <artifactId>sunrays-combinations-demo</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>combinations-wx-starter-demo</artifactId>

    <dependencies>
        <!-- combinations-wx-login-starter -->
        <dependency>
            <groupId>cn.sunxiansheng</groupId>
            <artifactId>combinations-wx-login-starter</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!-- common-web-starter -->
        <dependency>
            <groupId>cn.sunxiansheng</groupId>
            <artifactId>common-web-starter</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

    <!-- maven 打包常规配置 -->
    <build>
        <!-- 打包成 jar 包时的名字为项目的artifactId + version -->
        <finalName>${project.artifactId}-${project.version}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <!-- 引用父模块中统一管理的插件版本(与SpringBoot的版本一致! -->
                <executions>
                    <execution>
                        <goals>
                            <!-- 将所有的依赖包都打到这个模块中 -->
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
3.application.yml 配置AppID和AppSecret
sun-rays:
  log4j2:
    home: /Users/sunxiansheng/IdeaProjects/sunrays-framework/sunrays-combinations-demo/combinations-wx-starter-demo/logs # 日志存储根目录
  env:
    path: /Users/sunxiansheng/IdeaProjects/sunrays-framework/sunrays-combinations-demo/combinations-wx-starter-demo # .env文件的绝对路径
  wx:
    login:
      app-id: ${WX_LOGIN_APP_ID} # 微信开放平台应用的AppID
      app-secret: ${WX_LOGIN_APP_SECRET} # 微信开放平台应用的AppSecret
spring:
  profiles:
    active: prod # 激活的环境
4.application-prod.yml 配置生产环境的日志和.env文件路径
sun-rays:
  log4j2:
    home: /www/wwwroot/sunrays-framework/logs # 日志存储根目录
  env:
    path: /www/wwwroot/sunrays-framework # .env文件的绝对路径
5.CodeAndState.java 接受code和state的bean
package cn.sunxiansheng.wx.entity;

import lombok.Data;

/**
 * Description: 接受code和state的bean
 *
 * @Author sun
 * @Create 2025/1/16 19:15
 * @Version 1.0
 */

@Data
public class CodeAndState {
    /**
     * 微信的code
     */
    private String code;
    /**
     * 微信的state
     */
    private String state;
}
6.WxLoginController.java 微信登录Controller
package cn.sunxiansheng.wx.controller;

import cn.sunxiansheng.wx.entity.CodeAndState;
import cn.sunxiansheng.wx.login.utils.WxLoginUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * Description: 微信登录Controller
 *
 * @Author sun
 * @Create 2025/1/13 16:26
 * @Version 1.0
 */
@Slf4j
@RestController
@RequestMapping("/wx")
public class WxLoginController {

    @Resource
    private WxLoginUtil wxLoginUtil;

    @RequestMapping("/test")
    public String test() {
        return "test";
    }

    /**
     * 微信登录
     *
     * @param codeAndState 前端传过来的code和state
     * @return 返回unionId
     */
    @RequestMapping("/login")
    public String login(@RequestBody CodeAndState codeAndState) {
        // 使用code来完成微信登录
        WxLoginUtil.AccessTokenResponse accessTokenResponse = wxLoginUtil.wxLogin(codeAndState.getCode());
        if (accessTokenResponse == null) {
            log.error("accessToken is null");
            return "null";
        }
        // 获取unionId
        String unionId = accessTokenResponse.getUnionId();
        if (unionId == null) {
            log.error("unionId is null");
            return "null";
        }
        // 获取unionId
        return accessTokenResponse.getUnionId();
    }
}
7.WxApplication.java 启动类
package cn.sunxiansheng.wx;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Description: 微信启动类
 *
 * @Author sun
 * @Create 2025/1/13 17:54
 * @Version 1.0
 */
@SpringBootApplication
public class WxApplication {

    public static void main(String[] args) {
        SpringApplication.run(WxApplication.class, args);
    }
}

4.微信登录流程梳理

1.用户点击微信登录按钮

CleanShot 2025-01-23 at 21.36.20@2x

2.前端向开放平台发送请求主要携带appId和redirectUri
<template>
  <button @click="handleLogin" class="wechat-login-button">
    微信登录
  </button>
</template>

<script>
export default {
  methods: {
    handleLogin() {
      // 从环境变量中获取参数
      const appId = import.meta.env.VITE_APP_ID; // 从环境变量中读取 appId
      const redirectUri = encodeURIComponent(import.meta.env.VITE_REDIRECT_URI); // 从环境变量中读取 redirectUri

      const responseType = 'code';
      const scope = 'snsapi_login'; // 网页应用固定填写 snsapi_login

      // 生成一个随机的 state 参数,用于防止 CSRF 攻击
      const state = Math.random().toString(36).substring(2); // 或者使用更安全的方式生成一个随机字符串

      // 拼接请求URL,并加入 state 参数
      const wechatLoginUrl = `https://open.weixin.qq.com/connect/qrconnect?appid=${appId}&redirect_uri=${redirectUri}&response_type=${responseType}&scope=${scope}&state=${state}#wechat_redirect`;

      // 跳转到微信登录页面
      window.location.href = wechatLoginUrl;
    },
  },
};
</script>

<style scoped>
.wechat-login-button {
  background-color: #1aad19;
  color: white;
  border: none;
  border-radius: 5px;
  padding: 10px 20px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.wechat-login-button:hover {
  background-color: #128c13;
}
</style>
3.此时开放平台会弹出一个扫码的页面,用户扫码确认

CleanShot 2025-01-23 at 21.36.33@2x

4.用户确认成功后,开放平台会将code和state作为参数去请求redirectUri(前端页面)

CleanShot 2025-01-23 at 21.36.47@2x

5.前端页面获取code和state,再向后端发送请求
<template>
  <div class="login-container">
    <div class="loading-spinner"></div>
    <p class="loading-text">微信登录中,请稍候...</p>
  </div>
</template>

<script>
export default {
  async mounted() {
    const urlParams = new URLSearchParams(window.location.search);
    const code = urlParams.get("code");
    const state = urlParams.get("state");

    if (!code) {
      console.error("未获取到微信返回的 code");
      alert("登录失败,请重试");
      return;
    }

    try {
      const response = await fetch("/wx/login", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ code, state }),
      });

      const result = await response.json();

      if (result.success) {
        const unionid = result.data;
        alert(`登录成功,您的unionid是:${unionid}`);
        this.$router.push({ path: "/products" });
      } else {
        alert("登录失败,请重试");
      }
    } catch (error) {
      console.error("请求失败", error);
      alert("网络错误,请稍后重试");
    }
  },
};
</script>

<style scoped>
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap");

:root {
  --primary-color: #4facfe;
  --secondary-color: #00f2fe;
  --text-color: #333;
}

.login-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background: linear-gradient(120deg, #ffffff, #f0f0f0);
  font-family: "Poppins", sans-serif;
}

.loading-spinner {
  width: 60px;
  height: 60px;
  border: 6px solid #e0e0e0;
  border-top: 6px solid var(--primary-color);
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.loading-text {
  margin-top: 20px;
  font-size: 18px;
  font-weight: 500;
  color: var(--text-color);
  animation: fadeIn 2s ease-in-out infinite alternate;
}

@keyframes fadeIn {
  0% {
    opacity: 0.6;
  }
  100% {
    opacity: 1;
  }
}
</style>
6.后端使用code进行微信登录,可以获取到AccessTokenResponse
/**
 * 微信登录的响应类
 */
@Data
public static class AccessTokenResponse {

    @SerializedName("access_token")
    private String accessToken;

    @SerializedName("expires_in")
    private Integer expiresIn;

    @SerializedName("refresh_token")
    private String refreshToken;

    @SerializedName("openid")
    private String openId;

    @SerializedName("scope")
    private String scope;

    @SerializedName("unionid")
    private String unionId;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

KNIME:开源 AI 数据科学

KNIME&#xff08;Konstanz Information Miner&#xff09;是一款开源且功能强大的数据科学平台&#xff0c;由德国康斯坦茨大学的软件工程师团队开发&#xff0c;自2004年推出以来&#xff0c;广泛应用于数据分析、数据挖掘、机器学习和可视化等领域。以下是对KNIME的深度介绍…

如何让DeepSeek恢复联网功能?解决(由于技术原因,联网搜索暂不可用)

DeekSeek提示&#xff1a;&#xff08;由于技术原因&#xff0c;联网搜索暂不可用&#xff09; 众所周知&#xff0c;因为海外黑客的ddos攻击、僵尸网络攻击&#xff0c;deepseek的联网功能一直处于宕机阶段&#xff0c;但是很多问题不联网出来的结果都还是2023年的&#xff0c…

【优先算法】专题——前缀和

目录 一、【模版】前缀和 参考代码&#xff1a; 二、【模版】 二维前缀和 参考代码&#xff1a; 三、寻找数组的中心下标 参考代码&#xff1a; 四、除自身以外数组的乘积 参考代码&#xff1a; 五、和为K的子数组 参考代码&#xff1a; 六、和可被K整除的子数组 参…

刷题记录 动态规划-6: 62. 不同路径

题目&#xff1a;62. 不同路径 难度&#xff1a;中等 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#x…

梯度、梯度下降、最小二乘法

在求解机器学习算法的模型参数&#xff0c;即无约束优化问题时&#xff0c;梯度下降是最常采用的方法之一&#xff0c;另一种常用的方法是最小二乘法。 1. 梯度和梯度下降 在微积分里面&#xff0c;对多元函数的参数求∂偏导数&#xff0c;把求得的各个参数的偏导数以向量的形式…

基于STM32的智能安防监控系统

1. 引言 随着物联网技术的普及&#xff0c;智能安防系统在家庭与工业场景中的应用日益广泛。本文设计了一款基于STM32的智能安防监控系统&#xff0c;集成人体感应、环境异常检测、图像识别与云端联动功能&#xff0c;支持实时报警、远程监控与数据回溯。该系统采用边缘计算与…

优化代码性能:利用CPU缓存原理

在计算机的世界里&#xff0c;有一场如同龟兔赛跑般的速度较量&#xff0c;主角便是 CPU 和内存 。龟兔赛跑的故事大家都耳熟能详&#xff0c;兔子速度飞快&#xff0c;乌龟则慢吞吞的。在计算机中&#xff0c;CPU 就如同那敏捷的兔子&#xff0c;拥有超高的运算速度&#xff0…

Notepad++消除生成bak文件

设置(T) ⇒ 首选项... ⇒ 备份 ⇒ 勾选 "禁用" 勾选禁用 就不会再生成bak文件了 notepad怎么修改字符集编码格式为gbk 如图所示

如何创建折叠式Title

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了SliverGrid组件相关的内容&#xff0c;本章回中将介绍SliverAppBar组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在本章回中介绍的SliverAppBar和普通的AppBar类似&#xff0c;它们的…

K个不同子数组的数目--滑动窗口--字节--亚马逊

Stay hungry, stay foolish 题目描述 给定一个正整数数组 nums和一个整数 k&#xff0c;返回 nums 中 「好子数组」 的数目。 如果 nums 的某个子数组中不同整数的个数恰好为 k&#xff0c;则称 nums 的这个连续、不一定不同的子数组为 「好子数组 」。 例如&#xff0c;[1,2,…

Chromium132 编译指南 - Android 篇(一):编译前准备

1. 引言 欢迎来到《Chromium 132 编译指南 - Android 篇》系列的第一部分。本系列指南将引导您逐步完成在 Android 平台上编译 Chromium 132 版本的全过程。Chromium 作为一款由 Google 主导开发的开源浏览器引擎&#xff0c;为众多现代浏览器提供了核心驱动力。而 Android 作…

webpack传输性能优化

手动分包 基本原理 手动分包的总体思路是&#xff1a;先打包公共模块&#xff0c;然后再打包业务代码。 打包公共模块 公共模块会被打包成为动态链接库&#xff08;dll Dynamic Link Library&#xff09;&#xff0c;并生成资源清单。 打包业务代码 打包时&#xff0c;如果…

6 [新一代Github投毒针对网络安全人员钓鱼]

0x01 前言 在Github上APT组织“海莲花”发布存在后门的提权BOF&#xff0c;通过该项目针对网络安全从业人员进行钓鱼。不过其实早在几年前就已经有人对Visual Studio项目恶意利用进行过研究&#xff0c;所以投毒的手法也不算是新的技术。但这次国内有大量的安全从业者转发该钓…

加载数据,并切分

# Step 3 . WebBaseLoader 配置为专门从 Lilian Weng 的博客文章中抓取和加载内容。它仅针对网页的相关部分&#xff08;例如帖子内容、标题和标头&#xff09;进行处理。 加载信息 from langchain_community.document_loaders import WebBaseLoader loader WebBaseLoader(w…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.5 高级索引应用:图像处理中的区域提取

2.5 高级索引应用&#xff1a;图像处理中的区域提取 目录/提纲 #mermaid-svg-BI09xc20YqcpUam7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-BI09xc20YqcpUam7 .error-icon{fill:#552222;}#mermaid-svg-BI09xc20…

房屋中介管理系统的设计与实现

房屋中介管理系统的设计与实现 摘要&#xff1a;随着房地产市场的快速发展&#xff0c;房屋中介行业的信息管理需求日益增长。传统的管理方式已无法满足中介公司对房源信息、客户信息以及业务流程的高效管理需求。为此&#xff0c;本文设计并实现了一套房屋中介管理系统&#x…

Vue指令v-on

目录 一、Vue中的v-on指令是什么&#xff1f;二、v-on指令的简写三、v-on指令的使用 一、Vue中的v-on指令是什么&#xff1f; v-on指令的作用是&#xff1a;为元素绑定事件。 二、v-on指令的简写 “v-on&#xff1a;“指令可以简写为”” 三、v-on指令的使用 1、v-on指令绑…

力扣第435场周赛讲解

文章目录 题目总览题目详解3442.奇偶频次间的最大差值I3443.K次修改后的最大曼哈顿距离3444. 使数组包含目标值倍数的最少增量3445.奇偶频次间的最大差值 II 题目总览 奇偶频次间的最大差值I K次修改后的最大曼哈顿距离 使数组包含目标值倍数的最少增量 奇偶频次间的最大差值I…

编程AI深度实战:给vim装上AI

系列文章&#xff1a; 编程AI深度实战&#xff1a;私有模型deep seek r1&#xff0c;必会ollama-CSDN博客 编程AI深度实战&#xff1a;自己的AI&#xff0c;必会LangChain-CSDN博客 编程AI深度实战&#xff1a;给vim装上AI-CSDN博客 编程AI深度实战&#xff1a;火的编程AI&…

嵌入式知识点总结 操作系统 专题提升(四)-上下文

针对于嵌入式软件杂乱的知识点总结起来&#xff0c;提供给读者学习复习对下述内容的强化。 目录 1.上下文有哪些?怎么理解? 2.为什么会有上下文这种概念? 3.什么情况下进行用户态到内核态的切换? 4.中断上下文代码中有哪些注意事项&#xff1f; 5.请问线程需要保存哪些…