🚀 作者 :“二当家-小D”
🚀 博主简介:⭐前荔枝FM架构师、阿里资深工程师||曾任职于阿里巴巴担任多个项目负责人,8年开发架构经验,精通java,擅长分布式高并发架构,自动化压力测试,微服务容器化k8s等
🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬
🍅文末获取源码联系 配套笔记打包🍅
一、单机应用到分布式架构演进核心知识
简介:讲解单机到微服务架构的演进
1. 单机架构
- 优点: 易于测试 便于集成 小型项目友好
- 缺点: 开发速度慢 启动时间长 依赖庞大
2. 分布式架构
- SOA :Service Oriented Architecture 面向服务的架构 其中包含多个服务, 服务之间通过相互依赖最终提供一系列的功能, 一个服务 通常以独立的形式存在与操作系统进程中, 各个服务之间 通过网络调用。
- 微服务:将一个大的单体应用进行细粒度的服务化拆分,每个拆分出来的服务各自独立打包部署,各个服务之间 通过网络调用。
- 优点
- 易开发、理解和维护
- 独立的部署和启动
- 缺点
- 分布式系统-》分布式事务问题
- 需要管理多个服务-》服务治理
3. 业界微服务架构常见解决方案
简介:讲解业务微服务架构常见解决方案
-
ServiceComb
- 华为内部的CSE(Cloud Service Engine)框架开源, 一个微服务的开源解决方案,社区相对于下面几个比较小
- 文档不多,通信领域比较强
-
dubbo
-
zookeeper + dubbo + springmvc/springboot
-
官方地址:http://dubbo.apache.org/#!/?lang=zh-cn
-
配套
- 通信方式:rpc
- 注册中心:zookeper/redis/nacos
- 配置中心:diamond、nacos
-
-
SpringCloud
- 全家桶+轻松嵌入第三方组件(Netflix 奈飞)
- 官网:https://spring.io/projects/spring-cloud
- 配套
- 通信方式:http restful
- 注册中心:eruka
- 配置中心:config
- 断路器:hystrix
- 网关:zuul/gateway
- 分布式追踪系统:sleuth+zipkin
-
Spring Alibaba Cloud
- 全家桶+阿里生态多个组件组合+SpringCloud支持
- 官网 https://spring.io/projects/spring-cloud-alibaba
- 配套
- 通信方式:http restful
- 注册中心:nacos
- 配置中心:nacos
- 断路器:sentinel
- 网关:gateway
- 分布式追踪系统:sleuth+zipkin
二、新一代微服务AlibabaCloud介绍和架构环境准备
1. AlibabaCloud全家桶介绍
简介:讲解AlibabaCloud核心组件介绍
-
官网介绍
- https://spring.io/projects/spring-cloud-alibaba#overview
-
为什么要选择AlibabaCloud , 和SpringCloud的区别
-
SpringCloud和AlibabaCloud组件存在很大交集,互相配合
- SpringCloud很多组件是基于第三方整合,目前多个已经不更新了,比如zuul、eureka、hystrix等
- AlibabaCloud 提供一站式微服务解决方法,已经和SpringCloud进行了整合,组件互相支持
-
AlibabaCloud全家桶介绍
- https://github.com/alibaba/spring-cloud-alibaba
- 服务注册发现:Nacos
- 服务限流降级:Sentinel
- 分布配置中心:Nacos
- 服务网关:SpringCloud Gateway
- 服务之间调用:Feign、Ribbon
- 链路追踪:Sleuth+Zipkin
-
版本说明
Spring Cloud Alibaba | 版本发布说明
2. 小滴课堂在线教育微服务模块设计和环境准备
简介:在线教育环微服务模块划分和环境准备
-
在线教育模块划分
- 视频服务
- 订单服务
- 用户服务
-
必备基础环境:JDK8以上版本+Maven3.5(采用默认)+IDEA旗舰版+Mysql5.7以上版本
-
操作系统:Linux Centos7 64位(虚拟机) 或者 Mac苹果系统
- 虚拟机可以搜索博文
- Windows有些软件会不兼容,且坑难排查
- 学习期间务必关闭防火墙
-
文章绕这个基础项目进行学习 小而精的方式学习微服务
3. 小滴课堂在线教育微服务数据库介绍和数据导入
简介:讲解小滴课堂在线教育数据库表介绍和导入
- 采用3个数据库,每个服务单独一个库
- 视频服务数据库 video表
CREATE TABLE `video` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`title` varchar(524) DEFAULT NULL COMMENT '视频标题',`summary` varchar(1026) DEFAULT NULL COMMENT '概述',`cover_img` varchar(524) DEFAULT NULL COMMENT '封面图',`price` int(11) DEFAULT NULL COMMENT '价格,分',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`point` double(11,2) DEFAULT '8.70' COMMENT '默认8.7,最高10分',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8;
INSERT INTO `video` (`id`, `title`, `summary`, `cover_img`, `price`, `create_time`, `point`)
VALUES(30, '互联网架构之JAVA虚拟机JVM零基础到高级实战', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/maven/%E8%AF%A6%E6%83%85%E5%9B%BE.png', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/maven/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-mawen.png', 3980, '2021-06-24 22:14:00', 9.10),(40, '全新微信小程序零基础到项目实战', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E8%AF%A6%E6%83%85%E5%9B%BE.png', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-%E5%B0%8F%E7%A8%8B%E5%BA%8F.png', 5980, '2021-01-18 22:14:00', 9.10),(41, '玩转搜索框架ElasticSearch7.x实战', 'https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_backend/elasticsearch7_detail.jpeg', 'https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_backend/elasticsearch7.png', 4880, '2021-01-10 22:14:00', 8.70),(45, 'Docker实战视频教程入门到高级dockerfile/compose-Harbor', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/Docker/%E8%AF%A6%E6%83%85%E5%9B%BE.jpeg', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/Docker/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-docker.png', 5980, '2021-01-10 22:14:00', 9.30),(46, '新版javase零基础到高级教程小白自学编程', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/%E6%96%B0%E7%89%88javase/%E8%AF%A6%E6%83%85%E5%9B%BE.png', 'https://file.xdclass.net/video/2020/%E6%96%B0%E7%89%88javase/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-javase.png', 3980, '2021-01-24 22:14:00', 8.80),(47, 'Nodejs教程零基础入门到项目实战前端视频教程', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/node/%E5%AE%98%E7%BD%91%E8%AF%A6%E6%83%85%E5%9B%BE-node.png', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/node/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-node.png', 6980, '2021-01-24 22:14:00', 8.90);
- 用户服务数据库 user表
CREATE TABLE `user` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`phone` varchar(32) DEFAULT NULL,`pwd` varchar(128) DEFAULT NULL,`sex` int(2) DEFAULT NULL,`img` varchar(128) DEFAULT NULL,`create_time` datetime DEFAULT NULL,`role` int(11) DEFAULT NULL COMMENT '1是普通用户,2是管理员',`username` varchar(128) DEFAULT NULL,`wechat` varchar(128) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
INSERT INTO `user` (`id`, `phone`, `pwd`, `sex`, `img`, `create_time`, `role`, `username`, `wechat`)
VALUES(1, '123', '666', 1, 'xdclass.net', '2021-09-09 00:00:00', 1, 'jack', 'xdclass6'),(2, '2323432', '794666918', 1, 'wwwww', '2020-05-20 04:54:01', 1, '小滴Anna姐姐', 'xdclass-anna'),(3, '2323432', 'xdclass-lw', 1, 'wwwww', '2020-05-20 04:54:42', 1, '二当家小D', 'xdclass1'),(4, '2323432', '3232323', 1, 'wwwww', '2020-05-20 04:55:07', 1, '老王', 'xdclass-lw');
- 订单服务数据库video_order表
CREATE TABLE `video_order` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`out_trade_no` varchar(64) DEFAULT NULL COMMENT '订单唯一标识',`state` int(11) DEFAULT NULL COMMENT '0表示未支付,1表示已支付',`create_time` datetime DEFAULT NULL COMMENT '订单生成时间',`total_fee` int(11) DEFAULT NULL COMMENT '支付金额,单位分',`video_id` int(11) DEFAULT NULL COMMENT '视频主键',`video_title` varchar(256) DEFAULT NULL COMMENT '视频标题',`video_img` varchar(256) DEFAULT NULL COMMENT '视频图片',`user_id` int(12) DEFAULT NULL COMMENT '用户id',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8;
4. Maven聚合工程创建微服务项目实战
简介:使用Maven聚合工程创建微服务架构
- maven聚合工程
- xdclass-common
- xdclass-video-service
- xdclass-user-service
- xdclass-order-service
创建聚合工程(记得删除聚合工程src目录)
<?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><groupId>net.xdclass</groupId><artifactId>xdclass-cloud</artifactId><version>1.0-SNAPSHOT</version><modules><module>xdclass-common</module><module>xdclass-video-service</module><module>xdclass-user-service</module><module>xdclass-order-service</module></modules><!-- 一般来说父级项目的packaging都为pom,packaging默认类型jar类型--><packaging>pom</packaging><properties><java.version>17</java.version></properties><!--锁定版本--><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>3.0.2</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2022.0.0</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2022.0.0.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><addResources>true</addResources></configuration></plugin></plugins></build></project>
-
创建3个子项目
-
添加子项目依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>net.xdclass</groupId><artifactId>xdclass-common</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>
-
注意: 有些包maven下载慢,等待下载如果失败
- 删除本地仓库spring相关的包,重新执行 mvn install
5. AlibabaCloud微服务Mybatis连接Mysql数据库
简介:微服务打通Mybatis连接Mysql数据库
- 创建common包实体类
public class User {private Integer id;private String name;private String pwd;private String headImg;private String phone;private Date createTime;private String wechat;
}public class Video {private Integer id;private String title;private String summary;private String coverImg;private Integer price;private Date createTime;private Double point;
}public class VideoOrder {private Integer id;private String outTradeNo;private Integer state;private Date createTime;private Integer totalFee;private Integer videoId;private String videoTitle;private String videoImg;private Integer userId;
}
-
Mybatis依赖导入+数据库配置
- xdclass-video-service
- xdclass-user-service
- xdclass-order-service
-
聚合工程pom.xml修改【注意】
<properties><java.version>17</java.version><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.2</version><type>pom</type><scope>import</scope></dependency>
-
添加mybatis依赖和数据库驱动
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>
-
3个模块配置数据库连接(记得修改 端口、应用名称、数据库名称)
server:port: 9000spring:application:name: xdclass-video-servicedatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/cloud_video?useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: xdclass.net# 控制台输出sql、下划线转驼峰 mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true
-
controller->service->mapper 开发
-
application配置
@SpringBootApplication @MapperScan("net.xdclass.dao")
6. 初探微服务之间的调用-下单购买视频
简介:初探微服务直接的调用-下单购买视频
-
服务直接怎么调用:
RPC:远程过程调用,像调用本地服务(方法)一样调用服务器的服务支持同步、异步调用客户端和服务器之间建立TCP连接,可以一次建立一个,也可以多个调用复用一次链接RPC数据包小protobufthriftrpc:编解码,序列化,链接,丢包,协议Rest(Http):http请求,支持多种协议和功能开发方便成本低http数据包大java开发:resttemplate或者httpclient
-
用户下单
- 订单服务->视频服务(查询价格和冗余信息)
@Bean public RestTemplate getRestTemplate(){return new RestTemplate(); }Video video = restTemplate.getForObject("http://localhost:9000/api/v1/video/find_by_id?videoId="+videoId,Video.class);
-
postman工具测试(安装包在资料里面)
-
存在的问题:
- 服务之间的IP信息写死
- 服务之间无法提供负载均衡
- 多个服务直接关系调用维护复杂
三、AlibabaCloud核心组件服务治理Nacos实战
1. 什么是微服务的注册中心
简介:什么是注册中心和常见的注册中心有哪些
-
微服务架构图讲解
-
什么是注册中心(服务治理)
- 服务注册:服务提供者provider,启动的时候向注册中心上报自己的网络信息
- 服务发现:服务消费者consumer,启动的时候向注册中心上报自己的网络信息,拉取provider的相关网络信息
- 核心:服务管理,是有个服务注册表,心跳机制动态维护,服务实例在启动时注册到服务注册表,并在关闭时注销。
- 服务注册:服务提供者provider,启动的时候向注册中心上报自己的网络信息
-
为什么要用
- 微服务应用和机器越来越多,调用方需要知道接口的网络地址,如果靠配置文件的方式去控制网络地址,对于动态新增机器,维护带来很大问题
-
主流的注册中心:zookeeper、Eureka、consul、etcd、Nacos
- AlibabaCloud搭配最好的是Nacos,且服务的注册发现之外,还支持动态配置服务
- 参考图片(nacos官网)
2. AlibabaCloud注册中心Nacos实战
简介:介绍什么是Nacos和搭建实战
- 注册中心Nacos介绍
- 官网:https://nacos.io/zh-cn/
- Linux/Mac安装Nacos
- 解压安装包
- 进入bin目录
- 启动 sh startup.sh -m standalone
- 访问 localhost:8848/nacos
- 默认账号密码 nacos/nacos
3. 基于Nacos实现订单-视频服务之间的调用
简介:项目集成Nacos实现服务直接的调用
-
视频服务集成Nacos
-
添加依赖
<!--添加nacos客户端--> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
-
配置Nacos地址
server:port: 9000spring:application:name: xdclass-video-servicecloud:nacos:discovery:server-addr: 127.0.0.1:8848
-
启动类增加注解
@EnableDiscoveryClient
-
-
订单服务集成Nacos
-
用户服务集成Nacos
-
服务之间的调用
@Autowiredprivate DiscoveryClient discoveryClient;@Autowiredprivate RestTemplate restTemplate;@RequestMapping("save")public VideoOrder save(int videoId){VideoOrder videoOrder = new VideoOrder();videoOrder.setVideoId(videoId);List<ServiceInstance> list = discoveryClient.getInstances("xdclass-video-service");ServiceInstance serviceInstance = list.get(0);Video video = restTemplate.getForObject("http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/api/v1/video/find_by_id?videoId="+videoId,Video.class);videoOrder.setVideoTitle(video.getTitle());videoOrder.setVideoId(video.getId());return videoOrder;}
4. 大话常见的负载均衡策略和解决方案
简介:讲解什么负载均衡和常见的解决方案
-
什么是负载均衡(Load Balance)
分布式系统中一个非常重要的概念,当访问的服务具有多个实例时,需要根据某种“均衡”的策略决定请求发往哪个节点,这就是所谓的负载均衡, 原理是将数据流量分摊到多个服务器执行,减轻每台服务器的压力,从而提高了数据的吞吐量
-
软硬件角度负载均衡的种类
- 通过硬件来进行解决,常见的硬件有NetScaler、F5、Radware和Array等商用的负载均衡器,但比较昂贵的
- 通过软件来进行解决,常见的软件有LVS、Nginx等,它们是基于Linux系统并且开源的负载均衡策略
-
从端的角度负载均衡有两种
- 服务端负载均衡
- 客户端负载均衡
-
常见的负载均衡策略(看组件的支持情况)
- 节点轮询
- 简介:每个请求按顺序分配到不同的后端服务器
- weight 权重配置
- 简介:weight和访问比率成正比,数字越大,分配得到的流量越高
- 固定分发
- 简介:根据请求按访问ip的hash结果分配,这样每个用户就可以固定访问一个后端服务器
- 随机选择、最短响应时间等等
- 节点轮询
5. AlibabaCloud集成Ribbon实现负载均衡
简介:AlibabaCloud集成Ribbon实现负载均衡
- 什么是Ribbon Ribbon是一个客户端负载均衡工具,通过Spring Cloud封装,可以轻松和AlibabaCloud整合
- 订单服务增加@LoadBalanced 注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {return new RestTemplate();
}
- 调用实战
Video video = restTemplate.getForObject("http://xdclass-video-service/api/v1/video/find_by_id?videoId="+videoId, Video.class);注意:方便大家看到负载均衡效果,在video类增加这个字段,记录当前机器ip+端口
四、负载均衡进阶之Ribbon和Feign实战+源码分析
1.【源码剖析】手把手教学-Ribbon负载均衡源码实战
简介: 讲解ribbon服务间调用负载均衡源码分析
- 源码分析思路
- 通过直接找入口
- 分析@LoadBalanced 1)首先从注册中心获取provider的列表 2)通过一定的策略选择其中一个节点 3)再返回给restTemplate调用
2. 高级篇之AlibabaCloud负载均衡策略调整实战
简介:自定义Ribbon负载均衡策略实战
- 源码分析知道ribbon支持多种负载均衡策略
- Ribbon支持的负载均衡策略介绍
策略类 | 命名 | 描述 |
---|---|---|
RandomRule | 随机策略 | 随机选择server |
RoundRobinRule | 轮询策略 | 按照顺序选择server(默认) |
RetryRule | 重试策略 | 当选择server不成功,短期内尝试选择一个可用的server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值) |
WeightedResponseTimeRule | 响应时间加权重策略 | 根据server的响应时间分配权重,以响应时间作为权重,响应时间越短的服务器被选中的概率越大,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间 |
ZoneAvoidanceRule | 区域权重策略 | 综合判断server所在区域的性能,和server的可用性,轮询选择server |
-
负载均衡策略调整实战
订单服务增加配置xdclass-video-service:ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
-
注意:订单服务中api方法名那边个save方法,还原为findById,备课的时候忘记还原,不影响使用
-
策略选择: 1、如果每个机器配置一样,则建议不修改策略 (推荐) 2、如果部分机器配置强,则可以改为 WeightedResponseTimeRule
3. 微服务新一代负载均衡组件Open-Feign介绍
简介:讲解新一代负载均衡组件feign介绍
-
原先ribbon代码存在的问题:不规范,风格不统一,维护性比较差
-
什么是Feign:
SpringCloud提供的伪http客户端(本质还是用http),封装了Http调用流程,更适合面向接口化 让用Java接口注解的方式调用Http请求.不用像Ribbon中通过封装HTTP请求报文的方式调用 Feign默认集成了Ribbon
-
官方文档
- https://spring.io/projects/spring-cloud-openfeign
- 版本 4.0.0
-
Nacos支持Feign,可以直接集成实现负载均衡的效果
4. 改造微服务 集成Open-Feign实现远程方法调用
简介:改造微服务 集成Feign实现远程方法调用
-
Feign让方法调用更加解耦
-
使用feign步骤讲解
-
加入依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
配置注解
启动类增加@EnableFeignClients
- 增加一个接口
订单服务增加接口,服务名称记得和nacos保持一样 @FeignClient(name="xdclass-video-service")
- 编写代码
@GetMapping(value = "/api/v1/video/find_by_id") Video findById(@RequestParam("videoId") int videoId);
-
5. post方式对象传输 Open-Feign 实现远程方法调用
简介:改造微服务 集成Feign实现远程方法调用
-
GET方式查询简单
-
POST方式提交怎么做
//订单服务这边 @PostMapping(value = "/api/v1/video/save") Video saveVideo(@RequestBody Video video);@PostMapping("save") public Object save(@RequestBody Video video){System.out.println(video.getTitle());return video; }
- 注意:
- 路径 核对
- Http方法必须对应
- 使用服务提供方用@RequestBody,要使用@PostMapping
- 注意:
-
Ribbon和feign两个的区别和选择
选择feign 默认集成了ribbon 写起来更加思路清晰和方便 采用注解方式进行配置,配置熔断等方式方便
小结
今天的文章就到这里了,还有更多内容下次继续。
想成为架构师的你不可不知道的分布式架构理论
高并发下的微服务架构存在的问题和解决方案
玩转Sentinel多种流空规则和实战
网关Gateway架构+断言+过滤器进阶实战
AlibabaCloud微服务下的链路追踪系统实战
AlibabaCloud微服务下的分布式配置中心实战
资料获取📚
完整代码和笔记已经准备好,如果有问题,可以再评论留言讨论💬
原创不易,别忘了点赞👍+收藏⭐哦!
点击下方链接即可获取完整资料压缩包🎁,快来领取吧!↓↓↓↓