
Java实战项目——《谷粒商城》分布式基础篇
Java实战项目——《谷粒商城》的学习笔记1 分布式基础(全栈开发)1.1 项目简介1.1.1 项目架构1.1.2 电商模式1.1.3 项目技术&特色1.1.4 项目前置要求1.2 分布式基础概念1.2.1 微服务1.2.2 集群、分布式、节点1.2.3 远程调用1.2.4 负载均衡1.2.5 服务注册/发现、注册中心1.2.6 配置中心1.2.7 服务熔断、服务降级1.2.8 API服务
Java实战项目——《谷粒商城》的学习笔记——分布式基础
视频链接:Java项目《谷粒商城》.
分布式基础(全栈开发)
1.1 项目简介
1.1.1 项目架构
微服务架构图:
微服务划分图:
1.1.2 电商模式
常见的五种电商模式为:B2B,B2C,C2B,C2C,O2O。谷粒商城属于B2C模式的电商平台。
- B2B模式(Business to Business),商家与商家建立的商业关系,如阿里巴巴
- B2C模式(Business to Consumer),供应商直接把商品卖给用户,也就是通常说的商业零售,如:苏宁易购,京东,天猫,小米商城
- C2B模式(Consumer to Business),先有消费者需求后有商家生产
- C2C模式(Consumer to Consumer),客户之间把东西放到网上卖,如淘宝,闲鱼
- O2O模式(Online to Offline),线下商务与互联网结合,互联网成为线下交易的平台,线上快速支付,线下享受服务,如饿了么,美团,淘票票,京东到家
1.1.3 项目技术&特色
- 前后端分离开发,开发基于vue的后台管理系统
- SpringCloud全新的解决方案
- 应用监控、限流、网关、熔断降级等分布式方案
- 分布式事务,分布式锁等分布式系统的难点
- 分析高并发场景的编码方式,线程池,异步编排等使用
- 压力测试与性能优化
- 各种集群技术的区别及使用
- CI/CD使用
- …
1.1.4 项目前置要求
- 熟悉SpringBoot及常见解决方案
- 了解SpringCloud
- 熟悉git,maven
- 熟悉redis,linux,docker基本操作
- 了解html,css,javascript,vue
- 熟练使用idea开发项目
1.2 分布式基础概念
1.2.1 微服务
简而言之,拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。
1.2.2 集群、分布式、节点
集群是物理形态,分布式是工作方式。分布式中的每一个节点,都可以做集群,而集群并不一定是分布式。节点:集群中的一个服务器。
1.2.3 远程调用
在分布式系统中,每个服务可能存在不同的主机,但服务之间不可避免地需要相互调用,称为远程调用。SpringCloud使用HTTP+JSON的方式完成远程调用。
1.2.4 负载均衡
分布式系统中,A服务需要调用B服务,而B服务在多台服务器运行,为了使每个服务器不要太忙或者太闲,需要负载均衡地调用每个服务器,提升网络健壮性。
常见的负载均衡算法:
- 轮询,即按顺序循环调用
- 最小连接,优先选择连接数最少的服务器
- 散列:根据IP地址选择要转发的服务器,可以保证同一用户连接到相同服务器
1.2.5 服务注册/发现、注册中心
A服务调用B服务,但是不知道B服务在哪个服务器,也不知道哪个服务器可用,哪个服务已经下线,解决这个问题可以引入注册中心,可以实时感受到其他服务状态。
1.2.6 配置中心
每个服务都有大量配置,每个服务可能部署到多台机器上,经常需要变更配置,可以让每个服务在服务中心获取自己的配置。配置中心用来集中管理微服务的配置信息。
1.2.7 服务熔断、服务降级
在微服务架构中,可能存在依赖关系,如果一个服务不可用时,有可能造成雪崩效应,要避免这种情况,需要有容错机制来保护服务。
服务熔断:首先设置服务的超时,当多次调用服务失败达到阈值时,开启短路保护机制,后来的请求不再调用这个服务,而是本地直接返回默认的数据。
服务降级:运维期间,当系统处于高峰期,系统资源紧张,让非核心业务降级运行。降级:某些业务不处理,或者简单处理(抛异常、返回NULL,调用Mock数据、调用FallBack处理逻辑)
1.2.8 API服务网关
API网关(API Gateway)抽象了每个服务都需要的公共功能,提供了客户端负载均衡,服务自动熔断,灰度发布,统一认证,限流流控,日志统计等丰富功能。
1.3 环境搭建
1.3.1 安装Linux虚拟机
下载安转VirtualBox,需要进入bios界面开启CPU虚拟化,一般是开启了的。安装后界面如下:
下载安装Vagrant,可以在VirtualBox上快速创建虚拟机,安装后需要重启,重启后在cmd命令行输入vagrant
如果显示相关信息,表明安装成功。在cmd命令行中输入Vagrant init centos/7
Vagrant官方镜像仓库做好了许多镜像,其中一个便是centos/7。回车后效果如下,并且会在当前目录下生成Vagrantfile。然后使用vagrant up
会自动在官方镜像仓库下载镜像,并将环境启动,以后可以使用这个命令启动。
可以看到已经有一个虚拟机在运行了。
先用ctrl+c停止,再使用命令vagrant ssh
连接正在运行的虚拟机,从上图可以看到默认创建了SSH连接,用户名是vagrant,这时候可以正常使用linux命令了。
虚拟机关机后,可以使用命令vagrant up
启动虚拟机,前提是在Vagrantfile目录下。
1.3.1.1 Linux虚拟机网络设置
按照上面的步骤安装好虚拟机后,默认网络配置是网络地址转换+端口转发,如下图所示,在虚拟机中安装的软件比如redis使用端口6379,需要和windows端口3333绑定,当客户端访问windows3333端口,就会将请求转发到虚拟机3306端口。这样的话,每次安装一个软件都需要配置端口映射,会很麻烦。
解决方法有两种:
- 修改虚拟机的网络配置文件,比较麻烦
- 在Vagrantfile(默认在C:\Users\你的用户名\)里修改
config.vm.network "private_network", ip: "192.168.33.10"
,去掉注释,并修改私有ip地址。在cmd命令行使用ipconfig找到VirtualBox,由于这里的IP地址为192.168.56.1,因此Vagrantfile里的IP地址改成192.168.56.XX就行,比如192.168.56.10。改完后重启虚拟机,使用vagrant reload
。
再打开cmd,使用ping 192.168.56.10
看是否能ping通虚拟机,相同的,使用虚拟机看是否能ping通主机。
1.3.2 安装Docker
已经安装好了虚拟机,下面在虚拟机中安装Docker,简单介绍一下Docker。Docker是虚拟化容器技术,基于镜像,可以秒级启动各种容器,每一个容器都是完整的运行环境,容器之间互相隔离。Docker官方镜像网站,Docker会去软件仓库里面下载镜像,下载后在本地基于镜像直接启动容器,某一容器出现问题不会影响其他容器。Docker官方文档,安装docker步骤如下:
- 使用官方文档中的命令卸载以前Docker
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
- 设置Docker仓库
sudo yum install -y yum-utils
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
- 安装Docker
sudo yum install docker-ce docker-ce-cli containerd.io
- 启动Docker
sudo systemctl start docker
- 设置Docker开机自启动
sudo systemctl enable docker
最后使用docker -v检查版本,效果如下:
1.3.2.1 配置Docker国内镜像加速
注册阿里云,点击控制台,点击镜像加速服务,点击镜像加速器,选择centos下的命令,按顺序执行命令即可配置。
1.3.3 Docker安装MySQL
一直用sudo比较麻烦,可以切换到root用户,使用su
,密码为vagrant
。
- 从镜像仓库拉取
sudo docker pull mysql:5.7
//检查当前有哪些镜像
sudo docker images
- 创建实例并运行
docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
查看运行中的容器:docker ps
。
1.3.3.1 命令详细信息介绍
对于上面创建实例并运行的命令,可以借助上图来解释,docker容器挂载与端口映射。
- 第1行,run启动容器,端口映射,虚拟机3306端口映射到mysql3306端口,前面一个是linux端口号,后面一个是容器里mysql的端口号,起名为mysql,反斜杠表示换行
- 第2、3、4行称为目录挂载,将容器内部的文件夹映射到linux目录里边,这样避免了每次都要进到容器内部修改配置文件,查看日志文件等麻烦的操作。冒号左边是linux目录,冒号右边是容器内mysql的目录。第一个是日志文件,第二个是数据,相当于备份数据文件,第三个是配置文件,以后在linux中修改即可。
- 第5行,设置root密码为root
- 以后台方式运行,启动的哪个镜像
每次创建一个mysql容器,都包含了一个完整的mysql运行环境,mysql装在linux里,因此容器就是一个完整的linux。验证如下,首先进入mysql容器内部,进入/bin/bash,可以看到能够访问,并且里面也有完整的linux目录结构。
docker exec -it 容器id或者名字 /bin/bash
1.3.3.2 配置MySQL
在/mydata/mysql/conf/my.cnf里粘贴这些配置,如果没有my.cnf会自动创建,先vi my.cnf
再粘贴,这些步骤都是在root用户下执行的,如果有问题可以尝试切换到root用户。
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
init_connect='SET collation_connection=utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve
重启Docker中的MySQL,使配置文件生效,docker restart mysql
。
1.3.4 Docker安装Redis
先切换到root用户,再执行命令。
- 下载Redis镜像,不写版本号默认下载最新版,为了保持一致下载5.0.5
docker pull redis:5.0.5
- 创建实例并启动。这里有个坑,-d后面要加上版本号,否则默认latest,会重新到官网下载redis镜像,可以先删除容器
docker rm -f 容器id
,再删除镜像文件docker rmi -f redis
。
mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf
docker run -p 6379:6379 --name redis \
-v /mydata/redis/data:/data \
-v /mydata/redis/config/redis.conf:/etc/redis/redis.conf \
-d redis:5.0.5 redis-server /etc/redis/redis.conf
- 测试是否启动成功
docker exec -it redis redis-cli
- 持久化Redis数据,
vi redis.conf
,加入下面内容,使用aof持久化方式(现在似乎默认是aof)
appendonly=yes
1.3.4.1 Redis可视化客户端
为了方便,可以安装redis可视化客户端RedisDesktopManager。
1.3.4.2 Redis配置
要了解可以配置哪些参数,可以参考Redis官方文档。
1.3.4.3 设置MySQL和Redis容器自启动
docker update mysql --restart=always
docker update redis --restart=always
1.3.5 开发环境统一
1.3.5.1 Maven
下载安装好maven3.6.1,在conf目录下修改settings.xml,配置镜像源
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
配置jdk1.8编译项目
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
1.3.5.2 Idea&VsCode
在开始界面的All settings设置里配置mvn。
在Plugins里安装常用的插件Lombok(2021内置不需要安装)和MyBatisX。
使用Idea开发后台微服务项目,前端的后台管理系统使用VsCode开发,下载并安装。安装好后再VsCode里安装常用插件(比较多):Auto Close Tag,Auto Renam Tag,Chinese(Simplified),ESLint,HTML CSS Support,HTML Snippets,JavaScript (ES6) code snippets,Live Server,open in browser,Vetur。
1.3.5.3 Git
使用Git进行版本控制,由于GitHub有时登录不上,因此选择码云Gitee。下载Git,下载慢可以使用Git镜像源,安装好后,右键Git Bash进行基本配置。
//配置用户名
git config --global user.name "zkp"
//配置邮箱
git config --global user.email "2512139262@qq.com"
使用SSH免密连接:
ssh-keygen -t rsa -C "2512139262@qq.com"
//查看秘钥内容
cat ~/.ssh/id_rsa.pub
将秘钥内容复制到码云里,点击设置——SSH公钥,进行添加。ssh -T git@Gitee.com
测试是否成功。
1.3.6 创建项目微服务
将商城的微服务划分为:商品服务,仓储服务,订单服务,优惠券服务,用户服务。
这些微服务共同点:
1、都要导入Spring Web和OpenFeign
2、规范,包名为:com.atguigu.gulimall.XXX(product、ware、order、coupon、member)
3、模块名为:gulimall-XXX
4、组织名为:com.atguigu.gulimall
1.3.6.1 从gitee初始化一个项目
首先创建仓库,设置名称,初始化,模板,分支类型。
创建好仓库后,复制仓库地址,我这里是gulimall,打开Idea,新建项目,选择从版本控制新建项目,输入仓库地址进行clone。
gulimall项目作为总项目,微服务模块在总项目里进行创建。
以商品服务为例,设置好Name,Group等后,导入Spring Web和OpenFeign。
都创建好后,可以打开Run DashBoard,在Idea2019后变成了Services。
接下来,将gulimall设置为总项目来聚合小项目,复制一个pom.xml到gulimall下,并修改内容如下:
修改ignore模板,有些文件不需要上传。
安装码云插件gitee,点击commit and push即可提交到本地仓库并提交到码云。
1.3.7 数据库设计
数据库ip:192.168.56.10
需要先安装PowerDesigner,安装好后可以打开pdm文件。
五个微服务对应五个数据库,每个数据库下有多个表,虽然有很多的表,但是表之间不建立外键,这是因为电商系统中数据量很大,做外键关联是非常耗费数据库性能的操作。假设有几十万或者几百万条数据,每次插入或者删除一条数据,都需要对外键进行检查,来保证数据一致性和完整性。
在PowerDesigner中可以方便地设计表和字段,也可以在Name中起名字作为注释。
导入到数据库中
1.4 后台管理系统
为了简化后台管理系统前后端的开发,使用码云上开源项目人人开源,使用renren-fast和renren-fast-vue。
克隆到本地:
git clone https://gitee.com/renrenio/renren-fast.git
git clone https://gitee.com/renrenio/renren-fast-vue.git
将renren-fast里的.git删除,再粘贴到gulimall中,在gulimall的pom.xml中将renren-fast添加为模块,再启动renren-fast可以看到成功运行。
1.5 前端项目
接下来用VSCode打开renren-fast-vue,并安装前端项目的运行环境
1.5.1 Node.js
下载安装Node.js,我们要用随同安装的NPM,它相当于Java的Maven,是管理Javascript的工具。安装好后使用node -v
查看版本。
配置NPM,使用淘宝镜像npm config set registry http://registry.npm.taobao.org/
。
安装好后,在VSCode的终端中输入npm install
,下载项目需要的依赖,因为package.json里描述了项目需要什么依赖。这里可能容易报错,需要排查一下哪里有问题,我是没有安装好python3.0及以上版本,并配置到环境变量中。再把项目文件夹下的package.json里面的node-sass4.9.0改成4.9.2,最后npm config set msvs_version 2022
设置为我已经安装的Visual Studio版本2022即可。
下载完需要的组件后运行项目:npm run dev
,需要后端项目已经启动。
1.6 逆向工程搭建&使用
克隆人人开源的代码生成器renren-generator,删除.git文件夹,添加到gulimall模块,修改application配置文件,不同模块对应不同数据库;修改generator.properties配置文件,包名模块名需要修改,后续方便直接把生成的代码粘贴到项目中。
启动项目,勾选所有表,生成代码。
在main文件夹里已经生成好了mapper和三层架构加实体类,直接将main文件夹复制到相应微服务项目即可。会发现许多包都没有导入或者创建,因此额外创建一个gulimall-commom,每个微服务公共的类,公共的依赖都放到这里。别的项目依赖这个项目,并在gulimall-common中添加公共依赖。
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
最后添加模块的application.yml,配置数据源。
对其他模块也进行同样的操作,生成代码之前要修改代码生成器的generator.properties配置和连接数据库配置application.yml。注意coupon对应sms表,member对应ums表。在application.yml中用server:port可以编排端口
server:
port:7000
1.6.1 整合MyBatis-plus
在gulimall-common里导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
参照MyBatis-plus官方文档进行配置,包括导入驱动
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
配置数据源
配置MyBatis-plus,使用MapperScan,以及映射文件地址,使用classpath*表示除了自己的classpath,依赖的classpath也会扫描映射文件。
设置主键自增
测试一下插入数据,成功
测试查询操作,成功
1.7 微服务-注册中心、配置中心、网关
每一个微服务上线,都应该先将自己注册到注册中心,这样其他服务就能知道想要调用的服务是否可用。每个服务在配置中心拉取自己的配置。网关有分配,权限控制等功能。
我们不使用官网的SpringCloud,而是用SpringCloud Alibaba,只需要少量注解和少量配置,就能迅速搭建分布式应用系统。使用步骤可以参考SpringCloud Alibaba中文文档。
不使用官网的SpringCloud几大组件的原因是
- 1、部分组件停止维护和更新,例如eureka停止维护
- 2、部分环境搭建复杂,没有完善的可视化,需要二次开发和定制
- 3、配置复杂,难以上手,部分配置难以区分和合理应用
SpringCloud Alibaba的优势为:
- 1、性能强悍,经过考验,设计合理
- 2、可视化界面开发,搭建简单,学习成本低
最终我们的技术搭配方案为: - 1、SpringCloud Alibaba Nacos:注册中心
- 2、SpringCloud Alibaba Nacos:配置中心
- 3、SpringCloud Ribbon:负载均衡
- 4、SpirngCloud Feign:声明式HTTP客户端
- 5、SpringCloud Alibaba Sentinel:服务容错
- 6、SpringCloud GateWay:API网关
- 7、SpringCloud Sleuth:调用链监控
- 8、SpringCloud Alibaba Seata(原Fescar):分布式事务解决方案
1.8 SpringCloud Alibaba
这里修改和视频中SpringBoot和SpringCloud的版本一致,并添加了junit-jupiter-api依赖,能够成功运行,可以把junit-jupiter-api放到gulimall_common依赖中,因为其他微服务都要依赖gulimall_common,去掉scope。或者把Test重新导包!下面把SpringCloud Alibaba简称为SCA。
1.8.1 SCA Nacos 注册中心
具体可以参考nacos官方文档。
1、导入Nacos Discovery Starter
2、下载nacos server 1.1.3版本,运行bin文件夹下的start.up启动注册中心,默认端口是8848
3、每个微服务在注册中心选择注册中心的端口(spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
),服务名(spring.application.name=gulimall-coupon
)
4、在Application前面加上注解@EnableDiscoveryClient
完成上述步骤后,启动微服务,并在浏览器中打开127.0.0.1:8848/nacos
,登录的账号名密码默认都是nacos,可以看到服务列表里有gulimall-coupon。
Hello!
OpenFeign测试远程调用,以member微服务调用coupon微服务为例
1、member微服务引入openfeign依赖
2、coupon服务返回优惠券
@RequestMapping("/member/list")
public R memberCoupons(){
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满100减10");
return R.ok().put("coupon",Arrays.asList(couponEntity));
}
3、member服务里编写一个接口,告诉openfeign这个接口需要调用远程服务,放在feign包下
4、在接口上加注解@FeignClient("gulimall-coupon")
,把要调用的方法的完整签名复制过来,注意路径要写全
@RequestMapping("、coupon/coupon/member/list")
public R memberCoupons();
5、开启远程调用功能,在Application加上注解@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")
6、编写测试方法
@RequestMapping("/coupons")
public R test(){
MemberEntity memberEntity = new MemberEntity();
memberEntity.setNickname("张三");
R memberCoupons = couponFeignService.memberCoupons();
return R.ok().put("member",memberEntity).put("coupons",memberCoupons.get("coupons"));
}
调用成功!
1.8.2 SCA Nacos 配置中心
可以参考上面的SpringCloud Alibaba中文文档,使用步骤如下:
1、在gulimall-common引入Nacos Config Starter依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2、在src/main/resources/下创建bootstrap.properties
,这个配置文件会先于application.properties读取
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
在不设置配置中心时,是这样获取配置文件的内容(user.name会获取当前用户的名字,因此使用coupon.user.name):
获取成功!
3、在Nacos配置中心创建配置,Data ID一般为微服务名.properties
可以看到name和age都和配置中心的一样。
如果是使用application.xml,如果需要修改配置文件,需要重新打包再上线,而用了配置中心可以直接在配置中心修改,再点击发布,可以看到idea中显示刷新了配置信息。
4、但是刷新页面没有修改信息,需要在controlle里加入注解:@RefreshScope
,重新运行后再重复上述修改,可以看到配置信息实时改变!
1.8.2.1 核心概念
- 命名空间
用来做配置隔离,默认是public(保留空间),默认新增的所有配置都在public命名空间下。开发配置,测试配置,生产配置需要隔离。可以创建命名空间:
在bootstrp.properties中加上spring.cloud.nacos.config.namespace=4f10e303-0d6a-4b4e-b157-fb77e8d38087
,后面的ID复制dev后面的一串字符。
如果微服务很多,配置文件也很多,可以设置每个微服务的命名空间。因此命名空间可以基于环境进行隔离,也可以基于微服务进行隔离。 - 配置集
所有配置的集合。 - 配置集ID
类似于配置文件的文件名,即Data ID。 - 配置分组
默认所有配置集都属于DEFAULT_GROUP,新建时可以设置GROUP,例如双十一使用一组配置 1111,618使用另外一组配置。需要在bootstrap.properties加上spring.cloud.nacos.config.group=1111
。
我们使用这样的方案:每个微服务创建自己的命名空间,每个命名空间根据配置文组区分环境,dev,test,prod。
1.8.2.2 同时加载多个配置集
每个微服务的一个环境中会有多个配置文件,方便维护,例如和数据源有关的放在一个配置文件,和框架有关的配置放在一个配置文件,在bootstrap.properties中注释掉spring.cloud.nacos.config.group=dev
,加上
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true
就可以加载数据源配置文件,其他配置文件类似。读取顺序:先读取配置中心的,配置中心没有再读取配置文件。
小插曲:完成上述配置后尝试访问数据库时报错,在url后加上?useSSL=false
就可以了。
小插曲:端口号8848被占用,首先net -ano | findstr 8848
查看哪些进程ID使用了端口号,最右侧为PID;再使用taskkill -PID 5812 -F
。
1.9 SpringCloud
1.9.1 Feign声明式远程调用
Feign式声明式远程HTTP客户端,发送的是HTTP,
使用步骤如下:
1、引入OpenFeign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、使用见1.8.1
1.9.2 Gateway
网关作为流量的入口,把请求路由到各个服务,统一做鉴权、限流、日志输出等功能。官方文档。下面创建并测试API网关。
创建模块
添加到配置中心和注册中心,详细步骤见上一节。运行时报错,因为依赖了gulimall-common,而它依赖了mybatis,因此可以排除与数据库有关的配置。设置端口为88。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
在application.yml写配置
当输入http://localhost:88/?url=qq就能跳转到qq页面。
2 前端基础
前后端技术栈类比,Javascript也有新特性ES 6,7,8。
2.1 ES6
ECMAScript6.0,是JavaScript语言的标准,每年一个新版本,从ES6开始以年号作为版本。ECMAScript是浏览器脚本语言的规范,JavaScript是规范的具体实现。
2.1.1 ES6新特性
新建一个项目es6,新建let.html文件,使用快捷键shift+!+enter
快速生成html。右键Open With Live Server,ctrl+s保存后可以实时看到变化。
1、let新特性
- let声明的变量有严格作用域,var声明的变量可能越狱
- var可以声明多次,let只能声明一次
- let不存在变量提升,var存在变量提升
使用const声明常量,一旦声明之后不允许改变。
2.1.2 解构表达式&字符串拓展
数组解构:
对象解构:可以为对象重命名,name:abc,取对象中的name属性,赋给abc变量。
字符串拓展:
字符串模板:使用反引号`,多行字符串;${}插值在字符串中插入变量和表达,或者调用函数。
2.1.3 函数优化
1、函数参数默认值
2、不定参数
用...变量名
来表示
3、箭头函数
有点类似Java的lambda表达式,从->变成=>。在类中,箭头函数不能使用this。
还可以将解构表达式与箭头函数结合:
2.1.4 对象优化
1、新增的API
声明对象简写:
对象的函数属性简写:
对象拓展运算符:
用于对象深拷贝,或者合并对象
2.1.5 map和reduce
2.1.6 Promise
JavaScript是单线程执行的,因此所有的网络操作,浏览器事件都必须是异步执行的。如果后面的函数需要前面的结果,就要用回调函数层层嵌套。
Promise可以封装异步操作,alt+shift+f
VSCode代码整理。需求:先查询用户,再根据用户查询课程ID,再根据课程ID查询成绩。没提取代码之前需要这样写:
let p = new Promise((resolve, reject) => {
$.ajax({
url: "mock/user.json",
success: function (data) {
console.log("查询用户成功:", data)
resolve(data);
},
error: function (err) {
reject(err);
}
});
});
p.then((obj) => {
new Promise((resolve, reject) => {
$.ajax({
url: `mock/user_course_${obj.id}.json`,
success: function (data) {
console.log("查询用户课程成功:", data)
resolve(data);
},
error: function (err) {
reject(err);
}
});
});
}).then((data) => {
console / log("上一步的结果:", data)
$.ajax({
url: `mock/course_score_${obj.id}.json`,
success: function (data) {
console.log("查询课程得分成功:", data)
resolve(data);
},
error: function (err) {
reject(err);
}
});
})
提取之后代码如下:
function get(url, data) {
return new Promise((resolve, reject) => {
$.ajax({
url: url,
data: data,
success: function (data) {
resolve(data);
},
error: function (err) {
reject(err);
}
});
})
}
get("mock/user.json")
.then((data) => {
console.log("用户查询成功",data)
return get(`mock/user_course_${data.id}.json`);
})
.then((data) => {
console.log("课程查询成功",data)
return get(`mock/corse_scoes_${data.id}.json`);
})
.then((data)=>{
console.log("课程成绩查询成功",data)
})
.catch((err)=>{
console.log("出现异常",err)
})
2.1.7 模块化
模块化就是把代码拆分,方便重复利用,类似Java导包的概念,但是Javascript对应的是导入模块。模块功能主要有两个命令:export规定模块的对外接口,import导入其他模块提供的功能。
- export
export可以导入对象,变量,只有导出的才可以被其他js文件导入
const util = {
sum(a, b) {
return a + b;
}
}
var name = "jack";
var age = 21;
export{util,name,age}
或者直接在变量名之前使用export。
- import
单个导入或者多个导入
import util from "./hello.js"
import {name,age} from "./hello.js"
2.2 Vue
2.2.1 MVVM
数据和页面能同时变,只要有一方变了,另外一方能跟着变。新建文件夹,初始化,安装vue2:
npm init -y
npm install vue@2
1、声明式渲染
<div id="app">
<h1>{{name}},hello,vue</h1>
</div>
<script>
let vm = new Vue({
el: "#app", //绑定元素
data: { //封装数据
name: "张三",
num: 1
},
methods: { //封装方法
cancel(){
this.num -- ;
}
}
});
</script>
2、双向绑定
3、事件处理
安装插件:
Vue 2 Snippets
安装浏览器插件:
vue-devtools
2.2.2 Vue指令
v-html表示将数据转换成html表示
v-text展示原来的文本
插值闪烁:直接用{{name}},会出现先显示{{name}},再显示值,因此最好用v-html或者v-text
v-bind:给属性绑定,有短横杠需要单引号括起来
例如
v-bind:class="{active:isActive,'text-danger':hasDanger}"
双向绑定:v-model
v-on:绑定事件,例如:v-on:click="cancel"
可以简写成@click="cancel"
事件冒泡:点小div会触发大div,v-on:click.stop="cancel"
,stop阻止父类冒泡
按键修饰符:监听按键,v-on:keyup.up="num++"
组合键:@click.ctrl="num=10"
v-for:遍历循环,v-for="(item,index) in items"
,遍历的时候加上:key
区分不同数据,加快渲染速度。例如,用index区分:
<li v-for="(num,index) in nums" :key="index"></li>
v-if:是否显示,如果不显示,标签会消失,可以和v-for联用,后于v-for执行
v-show:是否显示,如果不显示,只是style改变
v-else,v-else-if:和v-if联用
计算属性:在Vue对象中的属性,computed
可以动态计算
监听器:例如监听商品数量是否超过库存,使用watch
属性
过滤器:filters
属性
2.2.3 组件化
1、全局声明注册一个组件
注意要放到Vue对象前面
Vue.component("counter", {
template: '<button v-on:click="count++">我被点击了{{count}}次</button>',
data() {
return {
count: 1
}
}
});
还可以注册局部组件,在Vue实例中在components中进行引用。
生命周期:在合适的时候钩子函数会被触发。
2.2.4 Vue模块化开发
搭建脚手架工程,win+r,cmd回车,打开Windows命令行窗口
1、全局安装webpack
npm install webpack -g
2、全局安装Vue脚手架
npm install -g @vue/cli
3、初始化Vue项目
在新建的文件夹中打开终端,输入:
vue init webpack vue-demo
如果出现node.js版本需要升级,先查看node版本,再去官网下载需要更新的版本,然后安装覆盖以前的node。
node -v
where node
前面三个直接回车,第四个选择上面的运行环境加编译环境,再回车
4、运行:
cd vue-demo
npm run dev
然后在浏览器中打开相应端口,这里因为有后端项目占用了8080,因此在8081打开:
界面如下:
主要在src下编写,单文件组件:xxx.vue。
2.2.5 整合ElementUI
1、安装
npm install element-ui
2、在mian.js中导入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
3、使用
Vue.use(ElementUI);
使用VSCode生成Vue模板:
文件-首选项-用户代码片段-新建全局代码片段,起名vue,回车后粘贴以下代码:
{
"Print to console": {
"prefix": "vue",
"body": [
"<!-- $1 -->",
"<template>",
"<div class='$2'>$5</div>",
"</template>",
"",
"<script>",
"//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)",
"//例如:import 《组件名称》 from '《组件路径》';",
"",
"export default {",
"//import引入的组件需要注入到对象中才能使用",
"components: {},",
"data() {",
"//这里存放数据",
"return {",
"",
"};",
"},",
"//监听属性 类似于data概念",
"computed: {},",
"//监控data中的数据变化",
"watch: {},",
"//方法集合",
"methods: {",
"",
"},",
"//生命周期 - 创建完成(可以访问当前this实例)",
"created() {",
"",
"},",
"//生命周期 - 挂载完成(可以访问DOM元素)",
"mounted() {",
"",
"},",
"beforeCreate() {}, //生命周期 - 创建之前",
"beforeMount() {}, //生命周期 - 挂载之前",
"beforeUpdate() {}, //生命周期 - 更新之前",
"updated() {}, //生命周期 - 更新之后",
"beforeDestroy() {}, //生命周期 - 销毁之前",
"destroyed() {}, //生命周期 - 销毁完成",
"activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发",
"}",
"</script>",
"<style scoped>",
"$4",
"</style>"
],
"description": "生成vue模板"
},
"http-get请求": {
"prefix": "httpget",
"body": [
"this.\\$http({",
"url: this.\\$http.adornUrl(''),",
"method: 'get',",
"params: this.\\$http.adornParams({})",
"}).then(({ data }) => {",
"})"
],
"description": "httpGET请求"
},
"http-post请求": {
"prefix": "httppost",
"body": [
"this.\\$http({",
"url: this.\\$http.adornUrl(''),",
"method: 'post',",
"data: this.\\$http.adornData(data, false)",
"}).then(({ data }) => { });"
],
"description": "httpPOST请求"
}
}
3 商品服务
3.1 三级分类
前端项目运行:
npm run dev
如果报错但是正常运行,可以注释掉eslint,把build下的webpack.base.conf.js这部分注释掉:
再重启项目即可。
配置网关路由和路径重写,在gulimall-gateway的application.yml中:
spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://www.baidu.com
predicates:
- Query=url, baidu
- id: qq_route
uri: https://www.qq.com
predicates:
- Query=url, qq
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
跨域:浏览器对JavaScript施加的安全限制,浏览器不能执行其他网站的脚本。
同源策略:协议,域名,端口都需要一样,只要有一个不一样就会产生跨域。
跨域流程:
1、非简单请求(put,delete),先发送预检请求,options
2、响应允许跨域
3、发送真实数据
4、响应数据
为什么获取验证码不会跨域?
因为get是简单请求,简单请求包括(get,head,post),且值要是三种之一:
解决跨域:
1、使用nginx配置为同一域
2、配置当次请求允许跨域
选择第二个方案,并且复用,直接在gateway中配置filter加上响应头
网关统一配置跨域,renren-fast配置的跨域需要注释掉
@Configuration
public class GulimallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
配置路由时,如果都是路径断言,有优先级,要把更精确的路由放在前面。效果如下:
使用MyBatis-plus逻辑删除:可以参考官网
1、配置application.yml
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
2、实体字段加上@TableLogic注解
@TableLogic(value = "1",delval = "0")
private Integer showStatus;
拖拽节点实现修改
3.2 品牌管理
基本的增删改查使用逆向生成好的代码,无论是前后端。真正的核心业务逻辑,才会自己写。
品牌表对应数据库中的表pms_brand。
在系统管理——菜单管理中新增:
以前生成的代码中复制这两个文件,粘贴到前端项目的product目录下
可以把权限返回为true,这样才能增加和删除。
3.2.1 文件上传功能
普通上传,普通上传的分布式情况,云存储。
对象存储(Object Storage Service,OSS)。
打开阿里云开通OSS,一个项目创建一个Bucket,
普通上传:用户上传给服务器,服务器再上传给OSS
服务端签名后上传:1、用户向服务器请求上传Policy
2、服务器返回上传Policy
3、用户直接上传数据到OSS
阿里云可以验证防伪签名。
安装SDK:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
创建子用户,添加权限。
可以通过上传文件流,也可以使用封装好的SpringCloud Alibaba-OSS进行对象存储。
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "oss-cn-beijing.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "LTAI5tSh95txzVw97xq3BKMh";
String accessKeySecret = "ZvbB3Q9VhpfrJGPNszutwL5PAN5knh";
// 填写Bucket名称,例如examplebucket。
String bucketName = "zkp-gulimall";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String objectName = "exampledir/java.jpg";
// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
String filePath= "E:\\c\\博客\\CSDN\\2022-5-13-jar包\\java.jpg";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
InputStream inputStream = new FileInputStream(filePath);
// 创建PutObject请求。
ossClient.putObject(bucketName, objectName, inputStream);
System.out.println("上传完成...");
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
1、导入starter依赖,由于其他微服务也要使用到,因此导入到common的pom.xml中
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
2、配置文件中配置
spring:
cloud:
alicloud:
access-key: LTAI5tSh95txzVw97xq3BKMh
secret-key: ZvbB3Q9VhpfrJGPNszutwL5PAN5knh
oss:
endpoint: oss-cn-beijing.aliyuncs.com
OSS获取服务端签名:
创建第三方服务模块gulimall-third-party,将之前的对象存储的依赖放到这里,并将common的依赖管理也放过来。
在nacos中新建oss.yml,并将对象存储的配置复制过来。排除数据库相关的依赖,否则还需要配置数据库。添加注解@EnableDiscoveryClient,占用30000端口。这里要修改Spring Cloud版本为Greenwich.SR3。
包括签名直传服务和上传回调服务。
使用@RestController注解,创建OssController,包含了@ResponseBody注解,将方法返回的对象写成json返回给浏览器。要注入OSS接口,不要写OSSClient实现类。效果如下:
配置网关,使得访问http://localhost:88/api/thirdparty/oss/policy
即可。
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
前后联调:
直接用写好的upload中的三个文件,放到components下,修改访问地址
有跨域问题,开启跨域
概览——基础设置——跨域访问,设置——创建规则
表单校验。服务器也要校验,因为如果绕过前端来提交数据,后端不校验会很危险。
3.2.2 JSR303
1、给需要校验的数据添加校验注解,javax.validation.constrains
2、给方法的形参加注解@Valid,告诉SpringMVC
3、紧跟校验bean后加BindingResult,就可以得到校验结果
规定统一返回[code,msg,data]。
自定义校验注解 @Pattern
@Pattern(regexp = "/^[a-zA-Z]$/",message = "检索首字母必须是一个字母")
private String firstLetter;
对校验做统一的处理,使用@ControllerAdvice
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError)->{
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(400,"数据校验出现问题").put("data",errorMap);
}
}
如果不能精确匹配,就来到最大的异常处理:
写业务时,如果有异常,放心大胆地抛出去,统一进行处理。前两位业务场景,最后三位表示错误码。创建枚举类BizCodeEnume放在common中。
public enum BizCodeEnume {
UNKNOWN_EXCEPTION(10000,"系统未知异常"),
VALID_EXCEPTION(10001,"参数格式校验失败");
private int code;
private String msg;
BizCodeEnume(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
分组校验,例如新增时品牌id是自增的,不需要携带,而修改时需要携带品牌id。
1、在校验注解上有属性groups,并且必须为接口的class。
2、在Controller上不使用@Valid注解,使用@Validated注解,可以指定校验分组。如果属性的注解不加分组将会不起作用,因此要加上分组。
自定义校验:
1、编写自定义的校验注解
在JSR303中校验注解必须拥有三个属性,message,groups,payload
还要创建配置文件ValidationMessages.properties
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
com.atguigu.common.valid.ListValue.message=必须提交指定的值
如果中文乱码,先删除文件,然后修改File——Settings——File Encoding:
再重启项目。
2、编写自定义的校验器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
//判断是否校验成功
/**
*
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
3、关联自定义的校验器和自定义的校验注解,一个校验注解可以有多个校验器
@Constraint(validatedBy = { ListValueConstraintValidator.class })
更新状态时,如果提示品牌名必须提交,
3.3 SPU和SKU
标准化产品单元(Standard Product Unit,SPU),是商品信息聚合的最小单元。
库存量单位(Stock Keeping Unit,SKU),即库存进出计量的基本单位。
例如:Iphone X是一个SPU,但买到手的具体配置是一个SKU。
在gulimall_admin中执行sys_menus.sql,建立这些菜单:
接口文档地址:https://easydoc.xyz/#/s/78237135。
接下来编写平台属性——属性分组。
父子组件传递数据:
1、子组件给父组件传递数据使用事件机制,子组件给父组件发送一个数据,this.$emit(“事件名”,若干个需要携带的数据);
获取分类属性分组。
分组新增。
给children属性使用注解 @JsonInclude(JsonInclude.Include.NON_EMPTY),使得如果该属性为空,则不携带该数据。
修改回显分类。
MyBatis-Plus引入分页插件。
一个品牌对应多个分类。一个分类可以有多个品牌。多对多的关系,需要有中间表。如果有冗余数据,要确保数据修改的一致性,需要业务代码保证。加上@Transactional注解,并且要在配置类MyBatisConfig中开启事务@EnableTransactionManagement。
3.3 平台属性
VO(Value/View Object),值对象,接收页面传递的数据,封装对象。或者是将业务处理完的对象,封装成页面要用的数据。@Data会使lombok自动生成getter和setter。使用Spring的方法复制属性。
BeanUtils.copyProperties(attr,attrEntity);
使用注解@RequestBody将请求体中的数据封装成指定类。
注意实体类要标注@Data,自动生成setter和getter,否则没有getter和setter不能转换成指定类型。
3.4 新增商品
执行架构篇的gulimall_pms.sql,插入一些数据。
遇到没有发送请求的情况,评论中做法:
1、执行命令
npm install --save pubsub-js
2、在src的main.js中加上
import PubSub from 'pubsub-js'
Vue.prototype.PubSub = PubSub
把gulimall-member注册到注册中心,注解开启服务注册与发现。网关配置路由关系。将前端的modules下的member,ware等文件夹拷到项目modules中。
json格式化工具,粘贴后点击JSON转Java实体类,价格使用Big Decimal,不用double,有的int也需要该成BigDecimal。这样运算不会丢失精度。getter和setter可以删除,并使用注解@Data。
openfeign远程调用。在product中吧所有需要远程调用的接口都写在feign包下。
1、写成接口
2、注解@FeignClient(“gulimall-coupon”)声明调用哪个远程服务
3、在启动类上加注解@EnableFeignClients(basePackages = “com.atguigu.gulimall.product.feign”)
4、接收和发送都需要,因此在common中建立TO
5、在接口中加上注解@PostMapping(“/coupon/spubounds/save”)
6、接口形参使用@RequestBody
修改完后重启服务,为了批量重启,并且设置每个微服务占用内存,首先Edit Configurations
创建Compound
将服务加入进来
每个服务可以点击Edit修改
在VM options中设置-Xmx100m
,设置最大占用100M就够用。
最后给Compound起名gulimall
以后直接选中gulimall,点击右边重启就行
可以在product服务中设置断点,然后以debug模式重启,由于设置了事务,而MySQL默认事务隔离级别为可重复读,为了能够实时看到表中数据变化,可以设置隔离级别,当前窗口就能读到未提交的数据。
set session transaction isolation level read uncommitted;
断点调试时如果前面加了一行,断点会失效,可以把要加的放在后面。
对于空图片路径的需要过滤掉。filter返回true就是需要保留。
此外,满0件打0折,满0元减0元是无用信息,需要剔除。BigDecimal的比较使用方法compareTo
skuReductionTo.getFullPrice().compareTo(new BigDecimal("0"))==1
只要有一个就保存,更深入地在具体保存时再选择性保存存在的信息。
过滤会员价格,如果是0过滤。
还有其他更多细节在高级篇完善。
SPU检索
返回到前端的时间需要格式化,否则是下面的样子:
在application.yml中添加:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
如果设置后不对可以加上时区:time-zone: GMT+8
。
商品管理
这里检索sku信息。
ge表示大于等于,le表示小于等于。
仓库管理
开启注解:@EnableTransactionManagement
@MapperScan(“com.atguigu.gulimall.ware.dao”)
配置网关
采购单
订单状态是枚举类,写到common中
完成采购
分页有问题,需要做MyBatis的配置,新建配置类,内容和product中的一样。
远程查询sku的名字
前端页面出错,先在src/router/index.js里面的mainRoutes——>children加上:
{ path: '/product-attrupdate', component: _import('modules/product/attrupdate'), name: 'attr-update', meta: { title: '规格维护', isTab: true } }
再把spuinfo.vue的catalogId改成catelogId即可。
获取Spu规格
4 分布式基础篇总结
4.1 分布式基础概念
- 微服务
- 注册中心
- 配置中心
- 远程调用
- Feign
- 网关
4.2 基础开发
- SpingBoot2.0
- SpringCloud
- MyBatis-Plus
- Vue组件化
- 阿里云对象存储
4.3 环境
- Vagrant
- Linux
- Docker
- MySQL
- Redis
- 逆向工程&人人开源
4.4 开发规范
- 数据校验JSR303、全局异常处理、全局统一返回、全局跨域处理
- 枚举状态、业务状态码、VO与TO与PO区分、逻辑删除
- Lombok:@Data @Slf4j
更多推荐