当前位置: 首页 > news >正文

微信公众号扫码授权登录思路

引言

上学期研究了一下微信登录相关内容,也写了两三篇笔记,但是最后实际登录流程没有写,主要因为感觉功能完成有所欠缺,一直也没有好的思路;这两天我又看了看官方文档,重新构思了一下微信公众号登录相关的内容,可能还是有不足之处,但是基本架子已经搭起来了,在这里我就简单说明一下我的想法;

关于登录方式

登录方式在我看来是必须要设计好的一个关键点,我设计的系统涉及到了手机号验证码登录,每个用户绑定的手机号是唯一的;所以对于初次使用微信登录的用户,必须绑定一个手机号才可以;(如果你只是单纯的微信登录,就会少很多步骤,下面我会细说)

微信中有一个叫openId的参数,这个参数可以说对每个关注公众号的用户都是不一样的,也就是每个用户的唯一性标识;

那么我们可以分析出来,一个用户除了id主键唯一以外,他的手机号是唯一的,微信中对应的openId也是唯一的;

所以数据库中就需要有一个用来存放openId的字段,后期微信登录就要通过该字段来检索用户;

准备工作

想要实现微信公众号扫码登录,首先要知道以下三点的实现:

1,获取微信公众号二维码

2,微信网页授权的实现

3,微信公众号消息接收和回复的实现

这三点我之前已经写过对应文章了,可以结合微信官方文档学习;

  • 微信公众号扫码登录(一)—— 获取微信公众号二维码
  • 微信登录——授权登录获取用户信息
  • 微信公众号被动消息回复实现

下面代码可能会和上面文章有所重复,但也有修改之处,看不懂可以对比来看;

登录流程

我的思路如下:

image-20230213205721283

可以分为以下步骤:

1,判断fromUserName即用户openId在数据库中是否存在
2, 如果存在则通过该openId查询到该用户信息,生成token进行登录操作
3, 如果不存在用户信息则跳转到手机号绑定页面
4, 如果该手机号已经注册有用户,则绑定该openId
5, 如果没有注册,则获取用户微信信息,将openId和手机号绑定


大致思路就是这样,其中当然还有很多小细节,下面用代码来大致演示一下;

代码实现

这个接口其实是实现微信消息接收和推送的接口,上面文章中也有,这里只是提取了service层

/*** 接收微信公众号消息(微信登录也经由该接口)*/
@PostMapping("/callback")
@ResponseBody
public String responseMsg(HttpServletRequest req, HttpServletResponse resp) throws IOException {req.setCharacterEncoding("UTF-8");String respContent = wxService.responseMsg(req);return respContent;
}

service

// 这个方法就是微信的登录方法
@Override
public String responseMsg(HttpServletRequest req) {if (req == null) {throw new BusinessException(StatusCode.SYSTEM_ERROR);}String message = "success";try {// 把微信返回的xml信息转义成mapMap<String, String> xmlMessage = WxMessageUtil.xmlToMap(req); // 解析微信发来的请求信息String fromUserName = xmlMessage.get("FromUserName"); // 这个就该事件的用户openIdString toUserName = xmlMessage.get("ToUserName"); // 这个开发者微信号String msgType = xmlMessage.get("MsgType"); // 消息类型(event或者text)String createTime = xmlMessage.get("CreateTime"); // 消息创建时间 (整型)log.info("发送方帐号(用户的openId)=>" + fromUserName);log.info("开发者微信号=>" + toUserName);log.info("消息类型为=>" + msgType);log.info("消息创建时间 (整型)=>" + createTime);if ("event".equals(msgType)) { // 如果是事件推送String eventType = xmlMessage.get("Event"); // 事件类型String eventKey = xmlMessage.get("EventKey"); // 获取事件KEY值if ("subscribe".equals(eventType)) { // 如果是扫描二维码后订阅消息String subscribeContent = "感谢关注";// 如果是扫码登录二维码后订阅公众号,则获取该用户信息进行登录操作if (!StringUtils.isAnyBlank(eventKey)&& WxConstant.LOGIN_QR_ID.toString().equals(eventKey.split("_")[1])) {subscribeContent = dealWithWxLoginUser(fromUserName, subscribeContent);}String subscribeReturnXml = WxMessageUtil.getWxReturnMsg(xmlMessage, subscribeContent);return subscribeReturnXml;}if ("SCAN".equals(eventType)) { // 如果是扫码消息String scanContent = "扫码成功";// 如果是扫描登录二维码,则获取该用户信息进行登录操作if (!StringUtils.isAnyBlank(eventKey)&& WxConstant.LOGIN_QR_ID.toString().equals(eventKey)) {scanContent = dealWithWxLoginUser(fromUserName, scanContent);}String scanReturnXml = WxMessageUtil.getWxReturnMsg(xmlMessage, scanContent);return scanReturnXml;}}if ("text".equals(msgType)) { // 如果是文本消息推送String content = xmlMessage.get("Content"); // 接收到的消息内容String textReturnXml = WxMessageUtil.getWxReturnMsg(xmlMessage, content);return textReturnXml; // 将接收到的文本消息变成xml格式再返回}} catch (IOException | DocumentException e) {throw new RuntimeException(e);}return message;
}/*** 处理微信登录的用户* @param openId 扫码登录用户的openId* @param content 处理结果* @return 处理信息*/
private String dealWithWxLoginUser(String openId, String content) {if (StringUtils.isAnyBlank(openId, content)) {throw new BusinessException(StatusCode.PARAMS_ERROR, "dealWithWxLoginUser方法参数为空");}// 1,判断fromUserName即用户openId在数据库中是否存在User loginUser = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getWechatNum, openId));if (loginUser == null) {// 如果不存在用户信息则跳转到手机号绑定页面// (此时微信登录就和这里的方法没有关系了,登录工作由下面跳转的绑定页面完成,这里链接目的之一是引导用户授权信息)// 能访问到该绑定页面只有两种情况,不符合这两种情况不能访问该页面:// 1,该openId对应用户未绑定手机号 2,该openId对应用户为空content = "[POLAR]该账号未绑定手机号,请绑定手机号后登录\n" +"<a href =\"http://内网穿透域名/api/user/wx/redirect\">[绑定手机号]</a>";} else {// 如果存在则通过该openId查询到该用户信息,生成token(存入redis)进行登录操作// 封装用户信息UserVo userVo = userUtil.setUserVo(loginUser);// 生成tokenString token = JwtUtil.createJWT(loginUser.getId().toString());// 将用户信息存入redisredisTemplate.opsForValue().set(RedisKey.LOGIN_USER + loginUser.getId(), userVo, 14, TimeUnit.DAYS);content = "用户" + userVo.getNickname() + "登录成功\n\n" +"登录日期:" + new Date();}return content;
}

这里实现的就是流程图中的第一步判断openId是否存在,主要判断方法就是dealWithWxLoginUser这个方法,该方法触发的条件就是用户扫码订阅公众号事件或者扫码登录事件(SCAN和subscribe)。

可以看到dealWithWxLoginUser方法中当用户不存在时,则需要引导用户进入手机号绑定界面,这里就是通过公众号向用户发送了手机号绑定超链接;实际效果如图:

image-20230213210702476

然后用户点击下面超链接进行手机号绑定;

如果已经绑定openId,则执行登录操作,效果如图:

image-20230213210649602


接下来就是绑定手机号步骤,绑定手机号链接到的是微信的网页授权接口,这一块在微信授权文章里说过,通过内网穿透映射到对应链接;

代码如下:

// 调用微信授权接口重定向
@GetMapping("/redirect")
@ResponseBody
public String toRedirectUrl(HttpServletResponse response) {String redirectUrl = "https://open.weixin.qq.com/connect/oauth2/authorize" +"?appid=" + WxConfigurationConstant.APP_ID +"&redirect_uri=" + WxConfigurationConstant.REDIRECT_URL +"&response_type=code" + "&scope=snsapi_userinfo" + // 只有关注公众号才能获取用户全部信息"&state=STATE" + "&connect_redirect=1#wechat_redirect";try {response.sendRedirect(redirectUrl); // 重定向url} catch (IOException e) {log.error("获取微信code失败: " + e.getMessage());}return "重定向成功";
}// 授权接口重定向回调方法
@GetMapping("/redirect/info")
public String redirectInfo(@RequestParam(value = "code") String code,@RequestParam(value = "state", required = false) String state,HttpServletResponse response,Model model) {// 获取登录用户信息WxUserInfo loginUserInfo = wxService.getWxLoginUserInfo(code);// 判断该用户是否已经绑定手机号(只有用户绑定手机号后恶意访问绑定链接才会触发),已绑定则跳转错误界面User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getWechatNum, loginUserInfo.getOpenid()));if (user != null) {return "BindPhoneErrorPage";}model.addAttribute("loginUserInfo", loginUserInfo);// 不响应json数据,返回一个手机号绑定界面return "BindPhonePage";
}

第一个/redirect接口就是上面超链接的url,通过该接口进行用户信息授权获取的:

image-20230213211237179

然后该接口重定向到下面的/redirect/info接口,这个接口的作用才是实际获取用户信息的接口,通过getWxLoginUserInfo方法获取扫码用户的微信信息;这里可能会有疑惑,既然/redirect/info接口是获取用户信息的接口,那么要/redirect接口干嘛,这里上面微信授权文章也讲过,目的就是一个:获取code参数,只有重定向方式才能活到到code,有了code参数才能获取用户信息;微信官方文档有写;

/redirect/info接口返回的是一个html页面,即手机号绑定页面,并把授权获取的用户信息loginUserInfo也传到了该页面,这里用的是springboot的thymleaf模板,因为我前端实在是烂,就简单写了个条条框框实现这个功能,后期再完善页面:

BindPhonePage:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>绑定手机号</title></head><body><div><br/>
<!--      <span id="hideWxUserInfo" th:text="${loginUserInfo}" hidden></span>--><input id="phoneNum" placeholder="请输入手机号" /> <br/><br/><input id="code" placeholder="请输入验证码" /><button id="sendCode">发送验证码</button><br/><br/><button id="bindClick">绑定</button></div></body><script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script><script type="text/javascript" th:inline="javascript">$("#sendCode").click(() => {// 发送ajax请求获取验证码var phone = $("#phoneNum").val();$.ajax({url:'http://内网穿透域名/api/user/sms/aliyun/code/' + phone,// data:{// },type:'post',// dataType:'json',success:function (data) {if (data.code == 20000) {alert('验证码发送成功')} else {// 提示信息alert(data.message);}}});})$("#bindClick").click(() => {// 发送手机号绑定请求var wxUserInfo = [[${loginUserInfo}]];var phone = $("#phoneNum").val();var code = $("#code").val();var allInfo = {"phone": phone,"code": code,"wxUserInfo": wxUserInfo}$.ajax({url:'http://内网穿透域名/api/user/bind/phone',data: JSON.stringify(allInfo),contentType: "application/json;charset=UTF-8",type:'post',dataType:'json',success:function (data) {if (data.code == 20000) {alert('绑定成功');// TODO 跳转失败不知道为什么window.location.href = 'BindPhoneErrorPage.html';} else {// 提示信息alert(data.description);}}});})</script>
</html>

可以看到绑定按钮点击后会发送一个绑定请求:http://内网穿透域名/api/user/bind/phone,这个接口主要完成流程图的注册或添加openId功能,代码如下:

@PostMapping("/bind/phone")
public BaseResponse<String> bindPhoneAndOpenId(@RequestBody BindPhoneAndOpenIdRequest bindRequest) {if (bindRequest == null|| StringUtils.isAnyBlank(bindRequest.getPhone(), bindRequest.getCode())|| bindRequest.getWxUserInfo() == null) {throw new BusinessException(StatusCode.PARAMS_ERROR);}String phone = bindRequest.getPhone();String code = bindRequest.getCode();WxUserInfo wxUserInfo = bindRequest.getWxUserInfo();userService.bindPhoneAndOpenId(phone, code, wxUserInfo);return ResultUtils.success("绑定成功");
}

service

@Transactional
@Override
public void bindPhoneAndOpenId(String phone, String code, WxUserInfo wxUserInfo) {// 参数校验if (StringUtils.isAnyBlank(phone, code) || wxUserInfo == null) {throw new BusinessException(StatusCode.PARAMS_ERROR, "参数为空");}RegExpUtil.regExpVerify(RegExpUtil.phoneRegExp, phone, "手机号格式错误");// 从redis中获取验证码进行校验String phoneCode = (String) redisTemplate.opsForValue().get(RedisKey.SMS_LOGIN_CODE + phone);if (StringUtils.isAnyBlank(phoneCode)) {throw new BusinessException(StatusCode.OPERATION_ERROR, "验证码不存在或已超时");}phoneCode = phoneCode.split("_")[0]; // 获取真正的验证码if (!code.equals(phoneCode)) {throw new BusinessException(StatusCode.OPERATION_ERROR, "验证码错误");}User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));if (user != null) { // 如果该手机号已经注册有用户,则绑定该openIduser.setWechatNum(wxUserInfo.getOpenid());user.setUpdateTime(null);userMapper.updateById(user);// 再次查询处理好的user数据user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));} else { // 如果没有注册,则获取用户微信信息,将openId和手机号绑定(用户注册流程)user = new User();user.setPhone(phone);user.setNickname(wxUserInfo.getNickname());user.setAvatar(wxUserInfo.getHeadimgurl());user.setGender(wxUserInfo.getSex());user.setWechatNum(wxUserInfo.getOpenid());user.setProfile("简单介绍一下自己吧!");userMapper.insert(user); // 新增用户// 重新获取新增用户信息user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));// 设置用户为普通用户Role normalRole = roleService.getOne(new LambdaQueryWrapper<Role>().eq(Role::getRoleName, "普通用户"));UserRoleRelation userRoleRelation = new UserRoleRelation();userRoleRelation.setUserId(user.getId());userRoleRelation.setRoleId(normalRole.getId());userRoleRelationService.save(userRoleRelation);}// 封装用户信息UserVo userVo = userUtil.setUserVo(user);// 生成tokenString token = JwtUtil.createJWT(user.getId().toString());// 将用户信息存入redisredisTemplate.opsForValue().set(RedisKey.LOGIN_USER + user.getId(), userVo, 14, TimeUnit.DAYS);// 登录成功后将验证码清除redisTemplate.delete(RedisKey.SMS_LOGIN_CODE + phone);
}

这里就是对应流程图的如下步骤:

image-20230213212511626

前面一大堆就是参数校验和验证码校验,后面if~else才是核心;

其中跳转BindPhoneErrorPage.html失败,没有找到原因,后期完善了补充;

BindPhoneErrorPage页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>ERROR</title>
</head><body><h1>绑定手机号成功</h1></body>
</html>

效果如下:

用户点击绑定手机号会有如下页面进行操作:

image-20230213214548462

获取验证码后点击绑定即可:

image-20230213220811635

image-20230213220842914

绑定成功后查看数据库可以看到完整用户数据,即phone和openId字段都有(我这里openId字段是wechat_num):

image-20230213212839267

第一条openId为空的数据就是没有使用过微信登录只通过手机号验证码登录的用户;第二条就是绑定好的数据;

查看redis:

image-20230213213114940

至此微信登录大致就完成了;

总结

其实我这里实现的仅仅是后端部分,前端如何判断用户是否扫码,如何获取用户登录的token都是待解决的问题,这里我可以提供两个思路,前端可以通过和后端长连接websocket通信进行判断用户是否扫码从而进一行下一步操作;或者是很多网站都是用的前端通过轮询的方式定时向后端发送请求查看后端是否登录完成;

以上思路和代码仅仅是我个人想法,稳定性和效率上没有经过测试,希望能給你提供一个思路,如果有问题请指点一二;

相关文章:

微信公众号扫码授权登录思路

引言 上学期研究了一下微信登录相关内容&#xff0c;也写了两三篇笔记&#xff0c;但是最后实际登录流程没有写&#xff0c;主要因为感觉功能完成有所欠缺&#xff0c;一直也没有好的思路&#xff1b;这两天我又看了看官方文档&#xff0c;重新构思了一下微信公众号登录相关的…...

数据结构与算法基础-学习-10-线性表之顺序栈的清理、销毁、压栈、弹栈

一、函数实现顺序栈的其他函数实现&#xff0c;请看之前的博客链接《数据结构与算法基础-学习-09-线性表之栈的理解、初始化顺序栈、判断顺序栈空、获取顺序栈长度的实现》。1、ClearSqStack&#xff08;1&#xff09;用途清理栈的空间。只需要栈顶指针和栈底指针相等&#xff…...

Hazel游戏引擎(005)

本人菜鸟&#xff0c;文中若有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/GameEngineLightWeight&#xff08;中文的注释适合中国人的你&#xff09; 文章目录前言关键操作代码文件关键代码代码流程代码文件关键代码exter…...

牛客网Python篇数据分析习题(四)

1.现有一个Nowcoder.csv文件&#xff0c;它记录了牛客网的部分用户数据&#xff0c;包含如下字段&#xff08;字段与字段之间以逗号间隔&#xff09;&#xff1a; Nowcoder_ID&#xff1a;用户ID Level&#xff1a;等级 Achievement_value&#xff1a;成就值 Num_of_exercise&a…...

盲盒如何创业?

所谓的“盲盒”&#xff0c;受众群体大部分是那些爱碰运气的人&#xff0c;顾客买的是那种在打开盲盒时一刹那的惊喜感和神秘感&#xff0c;在打开盲盒之前&#xff0c;谁也不知道自己会得到什么&#xff0c;这也是为什么消费者更愿意购买的原因。网上的盲盒&#xff0c;主要是…...

第1集丨Java中面向对象相关概念汇总

目录一、基本概念1.1 类1.2 属性1.3 方法1.4 静态1.5 包1.6 import二、高级概念2.1 构造方法2.2 继承2.3 super & this2.4 多态2.5 方法重载2.6 方法重写2.7 访问权限2.8 内部类2.9 final2.10 抽象2.11 接口2.12 匿名类面向对象的编程思想力图使计算机语言中对事物的描述与…...

高性能(二)

三、读写分离和分库分表 1.读写分离 1.1 概述 将数据库的读写操作分散到不同的数据库节点上 通常一主多从一台主数据库负责写&#xff0c;多台从数据库负责读。 主库和从库之间会进行数据同步&#xff0c;以保证从库中数据的准确性。 1.2 问题及解决 1.2.1 问题 主从同…...

Allegro如何实现同一个屏幕界面分屏显示操作指导

Allegro如何实现同一个屏幕界面分屏显示操作指导 在做PCB设计的时候,会需要分屏显示,比如一边是放大的视图,另外一边是缩小的视图,Allegro支持同一个屏幕界面下进行分屏显示,如下图 而且会实时同步起来 如何分屏,具体操作如下 点击View...

前后端一些下载与配置(第二篇 第10天过后)nuxt banner redis 短信服务

NUXT 应该是不用怎么装&#xff1f; 有现成的 axios 还需要在npm吗 好像已经有现成的了 banner banner 笔记汇总P396 Redis Linux安装redis tar -xzvf redis-6.2.6.tar.gz cd redis-6.2.6 照着他做 然后 cd /usr/local/redis/bin ./redis-server /usr/local/redis…...

OSG三维渲染引擎编程学习之四十八:“第五章:OSG场景渲染” 之 “5.6 多重纹理映射”

目录 第五章 OSG场景渲染 5.6 多重纹理映射 5.6.1 多重纹理映射介绍 5.6.2 多重纹理映射示例...

对Node.js 的理解?优缺点?应用场景?

一、是什么 Node.js 是一个开源与跨平台的 JavaScript 运行时环境 在浏览器外运行 V8 JavaScript 引擎&#xff08;Google Chrome 的内核&#xff09;&#xff0c;利用事件驱动、非阻塞和异步输入输出模型等技术提高性能 可以理解为 Node.js 就是一个服务器端的、非阻塞式I/…...

Bean的生命周期

所谓的生命周期指的是一个对象从诞生到销毁的整个生命过程&#xff0c;我们把这个过程就叫做一个对象的生命周期~~ Bean的生命周期分为以下五大部分&#xff1a; 实例化&#xff08;为 Bean 分配内存空间&#xff09; 设置属性&#xff08;Bean对象注入/装配&#xff09; 初…...

Python学习-----函数2.0(函数对象,名称空间,作用域-->全局变量与局部变量)

目录 前言&#xff1a; 1.函数对象 &#xff08;1&#xff09;函数对象的引用 &#xff08;2&#xff09;函数可以放到序列里面 &#xff08;3&#xff09;函数可以作为参数 &#xff0c; 传递给另一个函数 2.名称空间 3.作用域 &#xff08;1&#xff09;作用域的理解 …...

Java中Json字符串和Java对象的互转

JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式。诞生于 2002 年。易于人阅读和编写。同时也易于机器解析和生成。JSON 是目前主流的前后端数据传输方式。 JSON 采用完全独立于语言的文本格式&#xff0c;但是也使用了类似于 C 语言家族的…...

代码随想录NO42 | 动态规划_Leetcode70. 爬楼梯 (进阶) 322. 零钱兑换 279.完全平方数

动态规划_Leetcode70. 爬楼梯 &#xff08;进阶&#xff09; 322. 零钱兑换 279.完全平方数70. 爬楼梯 &#xff08;进阶&#xff09; 在原题基础上&#xff0c;改为&#xff1a;一步一个台阶&#xff0c;两个台阶&#xff0c;三个台阶&#xff0c;…&#xff0c;直到 m个台阶…...

【C++从入门到放弃】初识C++(基础知识入门详解)

&#x1f9d1;‍&#x1f4bb;作者&#xff1a; 情话0.0 &#x1f4dd;专栏&#xff1a;《C从入门到放弃》 &#x1f466;个人简介&#xff1a;一名双非编程菜鸟&#xff0c;在这里分享自己的编程学习笔记&#xff0c;欢迎大家的指正与点赞&#xff0c;谢谢&#xff01; C基础…...

企业工程项目管理系统源码+spring cloud 系统管理+java 系统设置+二次开发

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…...

【GPLT 三阶题目集】L3-016 二叉搜索树的结构

二叉搜索树或者是一棵空树&#xff0c;或者是具有下列性质的二叉树&#xff1a; 若它的左子树不空&#xff0c;则左子树上所有结点的值均小于它的根结点的值&#xff1b;若它的右子树不空&#xff0c;则右子树上所有结点的值均大于它的根结点的值&#xff1b;它的左、右子树也分…...

核心交换机安全多业务高性能万兆交换机

RG-S5750-24SFP/12GT交换机是锐捷网络推出的融合了高性能、高安全、多业务的新一代三层交换机。RG-S5750-24SFP/12GT 交换机能够提供灵活的介质接口&#xff0c;满足网络建设中不同介质的连接需要。全千兆的端口形态&#xff0c;加上可扩展的高密度万兆端口&#xff0c;提供1&a…...

Android APK 签名打包原理分析(三)【静默安装的实现方案】

背景 小编目前从事的系统定制类工作,有客户提出了,需要后台“静默安装”他们的app,也就是悄无声息的安装,而且特别强调,不可以跳出任何安装引导页面,他们的app下载完成之后,后台调用公开的android install代码,系统就后台完成安装,安装完成之后,重新打开应用就可以。…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

基于鸿蒙(HarmonyOS5)的打车小程序

1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...

多元隐函数 偏导公式

我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式&#xff0c;给定一个隐函数关系&#xff1a; F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 &#x1f9e0; 目标&#xff1a; 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z​、 …...