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

作者:后端小肥肠

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

姊妹篇:

【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 {// 自定义授权方式为 wechatprivate 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;}@Overrideprotected 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 {@Autowiredprivate RedisUtils redisUtils;@Autowiredprivate WechatConfig wechatConfig;@Autowiredprivate WeChatService weChatService;@AutowiredRestTemplate restTemplate;@AutowiredIFeignSystemController feignSystemController;@Override@SneakyThrowspublic 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);//获取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(feignSystemController.getuserAuthCountByIdentifier(openId)>0){jwtUser = (JwtUser) weChatService.getUserByOpenId(openId);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);feignSystemController.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;}
}

代码讲解: 

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.mzph.cn/web/47677.shtml

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

相关文章

Python测试服务器连接的实战代码

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

windows server——4.安装DNS管理器

windows server——4.安装DNS管理器 一、准备二、安装DNS管理器1.打开服务器管理器2.添加dns服务器 三、验证 一、准备 windows server电脑&#xff08;已安装IIS&#xff09; 静态网站数据包 二、安装DNS管理器 1.打开服务器管理器 2.添加dns服务器 点击管理——添加角色和…

Java语言程序设计基础篇_编程练习题*15.16(两个可移动的顶点以及它们间的距离)

*15.16&#xff08;两个可移动的顶点以及它们间的距离) 请编写一个程序&#xff0c;显示两个分别位于(40&#xff0c;40)和(120&#xff0c;150) 的半径为10的圆&#xff0c;并用一条直线连接两个圆&#xff0c;如图15-28b所示。圆之间的距离显示在直线上。 用户可以拖动圆&am…

指标平台新书发布:智能驱动,数据管研用一体化新革命

在当下数字化经营的市场环境中&#xff0c;企业面临着前所未有的挑战和机遇。随着业务的不断扩展和市场的日益复杂&#xff0c;数据作为企业的核心资产&#xff0c;其重要性愈发凸显。然而“数据孤岛和数据不清晰”这一问题却成为了制约企业数字化进程和竞争力的关键因素。为了…

Windows下载、安装、部署Redis服务的详细流程

本文介绍在Windows电脑中&#xff0c;下载、安装、部署并运行Redis数据库服务的方法。 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源、高性能的键值存储系统&#xff0c;最初由Salvatore Sanfilippo在2009年发布&#xff0c;并由Redis Labs维护。Redis因其…

<数据集>水果识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;10012张 标注数量(xml文件个数)&#xff1a;10012 标注数量(txt文件个数)&#xff1a;10012 标注类别数&#xff1a;7 标注类别名称&#xff1a;[Watermelon, Orange, Grape, Apple, peach, Banana, Pineapple] 序…

自建网站统计工具 Umami 替代 Google Analytics

本文首发于只抄博客,欢迎点击原文链接了解更多内容。 前言 Umami 是一款开源的网站统计工具,与 Google Analytics 相比更加的轻量,且不会收集网站用户的个人信息。同时,Umami 的仪表盘界面简洁,UI 精美,方便我们查看网站的历史统计数据。 Umami 使用方式也与 Google Ana…

n7.Nginx 第三方模块

Nginx 第三方模块 第三模块是对nginx 的功能扩展&#xff0c;第三方模块需要在编译安装Nginx 的时候使用参数–add-modulePATH指定路径添加&#xff0c;有的模块是由公司的开发人员针对业务需求定制开发的&#xff0c;有的模块是开 源爱好者开发好之后上传到github进行开源的模…

《0基础》学习Python——第二十四讲__爬虫/<7>深度爬取

一、深度爬取 深度爬取是指在网络爬虫中&#xff0c;获取网页上的所有链接并递归地访问这些链接&#xff0c;以获取更深层次的页面数据。 通常&#xff0c;一个简单的爬虫只会获取到初始页面上的链接&#xff0c;并不会进一步访问这些链接上的其他页面。而深度爬取则会不断地获…

python os库使用教程

os库使用教程 1.创建文件夹os.path.exists&#xff08;&#xff09;检查文件是否存在os.listdir查看文件夹下的所有文件filename.endswith()查看文件列表的png或者txt结尾的所有文件shutil.move移动目标到文件夹 1.创建文件夹 先在盘符里创建一个文件用来演示&#xff0c;我这里…

前端JS特效第48集:terseBanner焦点图轮播插件

terseBanner焦点图轮播插件&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下(全部代码在文章末尾)&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatibl…

HTTPServer改进思路1

Nginx源码思考项目改进 架构模式 事件驱动架构(EDA&#xff09;用于处理大量并发连接和IO操作 优点&#xff1a;高效处理大量并发请求&#xff0c;减少线程切换和阻塞调用技术实现&#xff1a;直接使用EPOLL&#xff0c;参考Node.js的http服务器 网络通信 协议&#xff1a;HTT…

【LeetCode】对称二叉树

目录 一、题目二、解法完整代码 一、题目 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#…

友力科技数据中心搬迁方案

将当前运行机房中的所有设备、应用系统安全搬迁至新数据中心机房&#xff0c;实现平滑切换、平稳过渡&#xff0c;最大限度地降低搬迁工作对业务的影响。 为了确保企事业单位能够顺利完成数据中心机房搬迁工作&#xff0c;我们根据实际经验提供了4个基本原则&#xff0c;希望能…

异步电机矢量控制matlab simulink

1、内容简介 略 86-可以交流、咨询、答疑 异步电机、矢量控制 2、内容说明 略 3、仿真分析 略 4、参考论文 略

YOLOv2小白精讲

YOLOv2是一个集成了分类和检测任务的神经网络&#xff0c;它将目标检测和分类任务统一在一个单一的网络中进行处理。 本文在yolov1的基础上&#xff0c;对yolov2的网络结构和改进部分进行讲解。yolov1的知识点可以看我另外一篇博客&#xff08;yolov1基础精讲-CSDN博客&#xf…

设计模式-抽象工厂

抽象工厂属于创建型模式。 抽象工厂和工厂设计模式的区别&#xff1a; 工厂模式的是设计模式中最简单的一种设计模式&#xff0c;主要设计思想是&#xff0c;分离对象的创建和使用&#xff0c;在Java中&#xff0c;如果需要使用一个对象时&#xff0c;需要new Class()&#xff…

RAG-LLM Survey

大模型虽然厉害&#xff0c;但是存在着幻觉、知识陈旧等问题。检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;可以通过挂载外部知识库&#xff0c;来提升生成内容的准确性和可信度。了解一个研究方向的最快的方法&#xff0c;就是阅读相关的综述。今…

Python数据可视化------动态柱状图

一、基础柱状图 # 基础柱状图 # 导包 from pyecharts.charts import Bar from pyecharts.options import *# 构建柱状图 bar Bar() # 添加数据&#xff08;列表&#xff09; x_list ["张三", "李四", "王五", "赵六"] y_list [50,…

你的生产车间有个好“布局”吗?

对于生产车间而言&#xff0c;科学合理的布局设计便是这“成功的一半”。在现代制造环境中&#xff0c;高效的物流动线、合理的设备配置、以及人性化的工作环境&#xff0c;是提升生产效率、保障产品质量的关键。 一个好布局的生产车间需具备以下几个关键特征&#xff0c;以确保…