谷粒商城——基础篇

一、分布式基础概念

1、微服务

微服务架构风格就是把单独的应用程序构造为一种松耦合服务,每个小服务运行在自己的进程中,并使用轻量级机制通信,通常是httpApi,这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。这些服务可以使用不同编程语言,以及不同存储技术,并保持最低限度的集中式管理
简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行

2、集群&分布式

集群:同一个业务,部署在多个服务器上。解决的是服务器的压力问题,当一台服务器不足以支撑所有的请求,就需要考虑集群服务器,多台服务器共同提供服务。它是一种物理形态,广义上来讲,只要是一堆机器都可以叫集群,他们是否一起协作干活,这个谁也不知道
分布式:一个业务拆分成多个子业务,部署在不同的服务器。解决的一个大项目的臃肿问题,它把一个大的业务拆分成若干份小业务。是一种工作方式。

例如:京东是一个分布式系统,众多业务运行在不同的机器上,所有业务构成一个大型的业务集群。每个小的业务,比如用户系统,访问压力大的时候一台服务器是不够的,我们就需要将用户系统部署到多个服务器,也就是可以将每个业务系统集群化

3、远程调用

在分布式系统中,各个服务可能处于不同主机,但服务之间不可避免的相互调用,我们称之为远程调用。springCloud使用http+json的方式完成远程调用。
好处:跨平台,json在任意平台都可以使用。http请求在php、c++等任意系统都可以接收和发送

4、负载均衡

谷粒商城——基础篇_第1张图片

在分布式系统中,A服务需要调用B服务,B服务在多台机器中存在,A调用任意一个服务器均可完成,为了使每个服务不要太忙也不要太闲,我们可以负载均衡的调用每一个服务器,提高网站的健壮性。
常见的负载均衡算法:

  • 轮询:为第一个请求分发到第一台后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环
  • 最小连接:优先选择连接数最少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式
  • 散列:根据请求源的ip的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器,可以考虑采取这种方式

5、springCloud涉及到的微服务组件:

谷粒商城——基础篇_第2张图片

  • 服务注册/发现&注册中心:
    A服务调用B服务,A服务并不知道B服务当前在哪几台服务器有,哪些是正常的,哪些服务已经下线。解决这个问题可以引入注册中心。如果某些服务下线,其他人可以实时的感知其他服务的状态,从而避免调用不可用的服务。配置中心。
  • 配置中心
    每个服务最终都有大量的配置,并且每个服务都可能部署在多台服务器上,我们经常需要变更配置,所以我们可以让每个服务从配置中心获取自己的配置。配置中心就是用来集中管理微服务的配置信息
  • 服务熔断&服务降级:
    在微服务架构中,微服务之间通过网络进行通信,存在相互依赖,当其中一个服务不可用时,会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。
    例如:订单服务–>商品服务–>库存服务;
    库存服务出现故障导致响应慢,导致商品服务需要等待,可能等到10s后库存服务才能响应,库存服务的不可用导致商品服务阻塞,商品服务等待期间,订单服务也处于阻塞。一个服务不可用导致整个服务链都阻塞。如果高并发,第一个请求调用后阻塞10s得不到结果,第二个请求直接阻塞10s,更多的请求进来导致请求积压,全部阻塞,最终服务器的资源耗尽。导致雪崩。解决方案:
    1、服务熔断:指定超时时间,如库存服务3s没有响应就超时,如果经常失败,比如10s内100个请求都失败了。开启断路保护机制,下次请求进来不调用库存服务了,因为上一次100%错误的出现,我们直接在此中断,商品服务直接返回一些默认数据或null,而不调用库存服务了,这样就不会导致请求积压
    2、服务降级:在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业务降级运行。降级:服务不处理或简单处理【抛异常、返回null、调用Mock数据、调用Fallback处理逻辑】
  • API网关
    客户端发送请求到服务器路途中,设置一个网关,请求先到达网关,网关对请求进行统一认证(合法非法)和处理等操作,他是安检。在微服务架构中,API gateway作为整体架构的重要组件,它抽象了微服务中都需要的公共功能,同时提供了客户端负载均衡、服务自动熔断、灰度发布、统一认证、限流控制、日志统计等丰富的功能,帮我们解决了很多API管理难题

二、项目介绍

1、微服务架构

采用前后端分离开发,分为内网部署和外网部署,外网是面向公众访问的,部署前端项目,可以有手机APP、电脑网页;内网部署的是后端集群,前端在页面上操作发送请求到后端,在这途中会经过nginx集群,nginx把请求转交给API网关(springcloud gateway)(网关可以根据当前请求动态地路由到指定的服务,看当前请求是想调商品服务还是购物车服务还是检索),从路由过来如果请求很多,可以负载均衡地调用商品服务器中一台(商品服务复制了多份),当商品服务器出现问题也可以在网关层对服务进行熔断或降级(使用阿里的sentinel组件),网关还有其他的功能如认证授权、限流(只放行部分到服务器)等。

到达服务器后进行处理(springboot为微服务),服务与服务可能会相互调用(使用feign组件),有些请求可能经过登陆才能进行(基于OAuth2.0的认证中心。安全和权限使用springSecurity控制)

服务可能保存了一些数据或者需要使用缓存,我们使用redis集群(分片+哨兵集群)。持久化使用mysql,读写分离和分库分表

服务和服务之间使用消息队列(RabbitMQ),来完成异步解耦,分布式事务的一致性。有些服务可能需要全文检索,检索商品信息,使用ElasticSearch。

服务可能需要存取数据,使用阿里云的对象存储服务OSS。

项目上线后为了快速定位问题,使用ELK对日志进行处理,使用LogStash收集业务里的各种日志,把日志存储到ES中,用Kibana可视化页面从ES中检索出相关信息,帮助我们快速定位问题所在。

在分布式系统中,由于我们每个服务都可能部署在很多台机器,服务和服务可能相互调用,就得知道彼此都在哪里,所以需要将所有服务都注册到注册中心,服务从注册中心发现其他服务所在位置(使用Nacos作为注册中心)。

每个服务的配置众多,为了实现改一处配置相同配置就同步更改,就需要配置中心,也使用阿里的Nacos,服务从配置中心动态取配置。

服务追踪,追踪服务调用链哪里出现问题,使用springcloud提供的Sleuth、Zipkin、Metrics,把每个服务的信息交给开源的Prometheus进行聚合分析,再由Grafana进行可视化展示,提供Prometheus提供的AlterManager实时得到服务的告警信息,以短信/邮件的方式告知服务开发人员。

还提供了持续集成和持续部署,项目发布起来后,因为微服务众多,每一个都打包部署到服务器太麻烦,有了持续集成后开发人员可以将修改后的代码提交到github,运维人员可以通过自动化工具Jenkins Pipeline将github中获取的代码打包成docker镜像,最终是由k8s集成docker服务,将服务以docker容器的形式运行

2、微服务划分

谷粒商城——基础篇_第3张图片
反映了需要创建的微服务以及相关技术
前后端分离开发。前端项目分为admin-vue(工作人员使用的后台管理系统)、shop-vue(面向公众访问的web网站)、app(公众)、小程序(公众)

  • 商品服务:商品的增删改查、商品的上下架、商品详情
  • 支付服务
  • 优惠服务
  • 用户服务:用户的个人中心、收获地址
  • 仓储服务:商品的库存
  • 秒杀服务
  • 订单服务:订单增删改查
  • 检索服务:商品的检索ES
  • 中央认证服务:登陆、注册、单点登录、社交登录
  • 购物车服务:
  • 后台管理系统:添加优惠信息等

三、linux环境搭建

1、安装&配置linux虚拟机

(1)安装linux

linux虚拟机可以使用VMWare,但推荐安装vitualbox,它是开源的。

  • 下载:https://www.virtualbox.org
  • 安装:需要cpu开启虚拟化,在开机启动的时候设置:cpu configuration—>Intel Vitualization Technology,重启电脑后,再安装
    谷粒商城——基础篇_第4张图片谷粒商城——基础篇_第5张图片
    普通安装linux虚拟机太麻烦,我们可以利用vagrant快速创建一个虚拟机,vagrant有一个镜像仓库,里面有好多做好的镜像,比如centos,我们只需要用vagrant连接vitualbox快速的从镜像仓库中创建一个虚拟机
  • 下载:https://www.vagrantup.com/downloads
  • 安装:傻瓜式安装,安装完成后重启电脑,cmd中输入vagrant有vagrant的命令提示代表安装成功
  • 创建虚拟机:在cmd输入vagrant init centos/7,即可初始化一个centos7(注意这个命令在哪个目录下执行的,他的Vagrantfile就生成在哪里)
    补充:初始化的版本可以从vagrant镜像仓库查看
  • 启动虚拟机:vagrant up,启动成功出现default: Rsyncing folder: /Users/wangyang/ => /vagrant。然后ctrl+c退出
  • 连接虚拟机:vagrant ssh,使用exit退出

下次使用可以直接vagrant up直接启动,但要确保当前文件夹下有一个Vagrantfile,不过我们也可以配置环境变量。
启动成功后使用vagrant ssh连接上即可

(2)虚拟网络设置

虚拟机默认的网络使用的是网络地址转换NAT(端口转发)

使用端口转发的方式,若想要访问linux的mysql,需要在vituralbox里设置本机windows的端口比如3333与linux的3306端口绑定,并且每次在linux装一个软件都需要做一个端口映射。我们希望给虚拟机一个固定的ip地址,windows可以和虚拟机互相ping通,虚拟机里面装好一个软件,我们就拿ip地址访问就可以了。有两种方式:

  • 在虚拟机中改网卡的信息,这样比较麻烦
  • vagrant默认使用用户文件下的vagrantFile这个文件帮我们创建出虚拟机信息的,这个文件里有关于网络的一些配置。修改其中的config.vm.network "private_network",ip:"192.168.56.10",这个ip需要在windows的ipconfig中查到vitualbox的虚拟网卡ip,然后更改下最后一个数字就行(不能是1,1是我们的主机)。配置完后vagrant reload重启虚拟机。在虚拟机中ip addr就可以查看到地址了(eth1的ip)。互相ping也能ping通。

2、安装docker

docker是虚拟化容器技术,基于镜像,可以秒级启动各种容器,每个容器都是一个完整的运行环境,容器之间互相隔离
解决的痛点:以前在虚拟机上装软件,比如装mysql、redis需要源码编译、执行运行、开启服务等一大堆步骤,而且某个软件在运行期间如果出现了问题,可能会影响linux系统,linux里面安装的其他环境都会出现问题。

安装文档:https://docs.docker.com/engine/install/centos/
镜像仓库:dockerHub
安装步骤:

#卸载系统之前的docker 
sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
                  
#安装一些必须依赖的包                  
sudo yum install -y yum-utils

# 配置镜像(告诉linux,docker去哪里装)
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
# 设置开机自启动
sudo systemctl enable docker

docker常用命令:

#查看docker版本
docker -v
#查看docker镜像
sudo docker images

为docker配置镜像加速:
因为docker下载镜像默认从dockerHub中下载,而dockerHub是一个国外的网站,下载速度比较慢,我们可以配置一个国内的镜像加速,以后docker想要下载镜像就非常快了,镜像加速推荐大家使用阿里云。根据https://cr.console.aliyun.com/cn-qingdao/instances/mirrors执行完命令

#创建一些目录
sudo mkdir -p /etc/docker

#配置镜像加速器地址
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://chqac97z.mirror.aliyuncs.com"]
}
EOF

#重启docker的伴随线程
sudo systemctl daemon-reload

#重启docker服务
sudo systemctl restart docker

3、docker安装mysql

  1. 从docker镜像仓库中搜索并下载mysql,如果不指定版本则下载最新版本
#下载镜像
sudo docker pull mysql:5.7
#检查下载的镜像
sudo docker images
  1. 启动mysql
sudo 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

参数说明:
-p:端口映射,将容器的3306端口映射到主机的3306端口
–name:指定容器名字
-v:目录挂载
-e:设置mysql参数(初始化root用户的密码)
-d:后台运行,mysql5.7指的是我们是用哪个镜像启动

su root 密码为vagrant,这样就可以不写sudo了
#查看docker正在运行的进行
[root@localhost vagrant]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
6a685a33103f        mysql:5.7           "docker-entrypoint.s…"   32 seconds ago      Up 30 seconds       0.0.0.0:3306->3306/tcp, 33060/tcp   mysql

此时就可以用客户端连接mysql了
补充知识:
1⃣️、每docker run就会启动一个容器,而且容器跟容器之间是互相隔离的,且每一个容器都是一个完整的运行环境(其实就是一个完整的linux)
如何验证?

#以交互模式进入容器中linux的bash控制台
docker exec -it mysql /bin/bash

ls /
#发现目录结构就是一个完整的linux目录结构,mysql就被装在这个linux里面

#查询mysql在哪里
whereis mysql

#推出交互模式
exit

2⃣️、docker容器端口映射和文件挂载
端口映射: mysql装到了容器里面,mysql默认会有一个端口3306,但这个端口相当于是在mysql容器里用的端口,如果我们想要访问mysql需要把3306映射到linux里面,-p 3306:3306意思就是linux的3306端口与mysql容器内部的3306端口对应,相当于访问linux的3306就能访问到容器内部装的mysql
文件挂载: mysql是被装在容器内部,mysql的配置文件是在/etc/mysql下,mysql的相关日志在/var/log/mysql目录下。但是难道我们每次修改mysql的相关配置都需要进到容器内部来改嘛?太麻烦了,我们希望将我们经常要用到或查看的内容映射到linux的目录里,-v的命令就是这个作用。相当于我们之前做的快捷方式
谷粒商城——基础篇_第6张图片
3. 修改mysql的配置

#因为有目录映射,所以我们可以直接在linux里修改
vi /mydata/mysql/conf/my.conf 

[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

#重启mysql
docker restart mysql

这些配置也会映射到容器内部

4、docker安装redis

  1. 下载镜像文件
docker pull redis
  1. 创建实例并启动
# 如果直接挂载的话docker会以为挂载的是一个目录,所以我们先创建一个文件然后再挂载,在虚拟机中。
# 在虚拟机中
mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf

#启动redis
docker run -p 6379:6379 --name redis \
-v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf
  1. 连接redis
docker exec -it redis redis-cli

redis默认是不持久化的。在配置文件中输入appendonly yes,就可以aof持久化了。

vim /mydata/redis/conf/redis.conf
# 插入下面内容
appendonly yes

#重启redis
docker restart redis

设置redis容器在docker启动的时候启动

docker update redis --restart=always

四、开发环境

  1. jdk最低使用1.8版本的
  2. maven在settings中配置阿里云镜像,配置jdk1.8
  3. idea安装插件lombok,mybatisX。idea设置我们的maven
  4. 下载vsCode用于前端管理系统。在vsCode里安装插件:Auto Close Tag、Auto Rename Tag、Chinese、ESlint、HTML CSS Support、HTML Snippets、JavaScript ES6、Live Server、open in brower、Vetur
  5. git相关:
    下载&安装git,右键git bash/gui here,去bash
# 配置用户名
git config --global user.name "username"  //(名字,随意写)

# 配置邮箱
git config --global user.email "[email protected]" // 注册账号时使用的邮箱

# 配置ssh免密登录
ssh-keygen -t rsa -C "[email protected]"
#三次回车后生成了密钥
#查看密钥
cat ~/.ssh/id_rsa.pub

#需要将密钥复制到码云里面,浏览器登录码云后,个人头像上点设置--ssh公钥
#添加公钥,标题随便写,将查看的id_rsa.pub里的公钥内容复制过来

# 测试
ssh -T [email protected]
#测试成功,手动输入yes,就可以无密给码云推送仓库了

五、创建微服务项目

在码云新建仓库,仓库名gulimall,选择语言java,在.gitignore选中maven(就会忽略掉maven一些个人无需上传的配置文件),许可证选Apache-2.0,开发模型选生产/开发模型,开发时在dev分支,发布时在master分支。

在IDEA中New–Project from version control–git–复制刚才项目的地址,如https://github.com/1046762075/mall

IDEA然后New Module–Spring Initializer–group填com.atguigu.gulimall、Artifact填 gulimall-product、describe填谷粒商城商品服务,Next—选择web(web开发)、springcloud routing里选中openFeign(rpc调用)。
依次创建出以下服务:

  • 商品服务product
  • 仓储服务ware
  • 订单服务order
  • 优惠券服务coupon
  • 用户服务member

共同点:

  • 导入web和openFeign
  • group:com.atguigu.gulimall
  • Artifact:gulimall-XXX
  • 每一个服务,包名com.atguigu.gulimall.XXX{product/order/ware/coupon/member}
  • 模块名:gulimall-XXX

然后右下角显示了springboot的service选项,选择
从某个项目粘贴个pom.xml粘贴到项目目录,修改他

<?xml version="1.0" encoding="UTF-8"?>
://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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	>4.0.0>

	>com.atguigu.gulimall>
	>gulimall>
	>0.0.1-SNAPSHOT>
	>gulimall>
	>聚合服务>

	>pom>

	>
		>gulimall-coupon>
		>gulimall-member>
		>gulimall-order>
		>gulimall-product>
		>gulimall-ware>

	>

>

在maven窗口刷新,并点击+号,找到刚才的pom.xml添加进来,发现多了个root。这样比如运行root的clean命令,其他项目也一起clean了。

修改总项目的.gitignore,把小项目里的垃圾文件在提交的时候忽略掉,比如target

**/mvnw
**/mvnw.cmd
**/.mvn
**/target/
.idea
**/.gitignore

如果你是拉取的我的仓库,你也可以看到我仓库里是没有那些target目录的,非常精简

在git/local Changes,点击刷新看Unversioned Files,可以看到变化。

全选最后剩下21个文件,选择右键、Add to VCS。

在IDEA中安装插件:gitee,重启IDEA。

在Default changelist右键点击commit,去掉右面的勾选Perform code analysis、CHECK TODO,然后点击COMMIT,有个下拉列表,点击commit and push才会提交到云端。此时就可以在浏览器中看到了。

commit只是保存更新到本地
push才是提交到gitee

六、数据库

打开navicat,连接linux里的mysql docker镜像

注意:重启linux和docker,docker里的容器就关了

sudo docker ps
sudo docker ps -a
#这两个命令的区别:后者会显示已创建未启动的容器

#接下来设置我们要用的容器每次都自启
sudo docker update redis --restart=always
sudo docker update mysql --restart=always
#如果不设置上面的自启的话,也可以手动启动
sudo docker start mysql
sudo docker start redis
#如果要进入已启动的容器
sudo docker exec -it mysql /bin/bash
# /bin/bash就是进入一般的命令行,如果改成redis就是进入了redis

使用powerDesigner打开并查看数据库表关系

name是给我们看的,code才是数据库里真正的信息。
选择primary和identity作为主键。然后点preview就可以看到生成这张表的语句。
点击菜单栏database–generate database—点击确定

使用navicat分别创建gulimall-oms、gulimall-pms、gulimall-sms、gulimall-ums、gulimall-wms库,并创建数据库表,sql:https://github.com/FermHan/gulimall。

1、所有的数据库数据再复杂也不建立外键,因为在电商系统中,数据量大,做外间关联很耗性能
2、字符集选utf8mb4,他能兼容utf8且能解决一些乱码的问题。

七、人人开源搭建后台管理系统

在码云上搜索人人开源,我们使用renren-fast(后端)、renren-fast-vue(前端)项目。项目地址:https://gitee.com/renrenio

#克隆代码
git clone https://gitee.com/renrenio/renren-fast.git
git clone https://gitee.com/renrenio/renren-fast-vue.git

下载到桌面,我们把renren-fast移到我们项目文件夹里(删掉.git文件),renren-fast-vue用VSCode打开(后面再弄)

1、使用人人开源搭建后端

在idea的父项目的pom.xml中添加一个renren-fast

<modules>
    <module>gulimall-couponmodule>
    <module>gulimall-membermodule>
    <module>gulimall-ordermodule>
    <module>gulimall-productmodule>
    <module>gulimall-waremodule>

    <module>renren-fastmodule>
modules>

然后打开renren-fast/db/mysql.sql,复制全部,在navicat中创建库guli-admin,粘贴刚才的内容执行。
然后修改项目里renren-fast中的application.yml,修改application-dev.yml中的数库库的url、username、password

url: jdbc:mysql://localhost:3306/guli_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root

然后运行该java项目下的RenrenApplication

2、使用人人开源搭建前端

用VSCode打开renren-fast-vue
安装node.js:http://nodejs.cn/download/ ,下载完安装。
npm是随同nodejs一起安装的包管理工具,javaScript-npm类似于maven
命令行输入node -v检查node是否安装成功,配置npm的仓库地址

node -v
npm config set registry http://registry.npm.taobao.org/

然后去VScode的项目终端中输入npm install,是要去拉取依赖(package.json类似于pom.xml的dependency)
在终端输入npm run dev运行项目

八、逆向工程

1、使用人人项目逆向工程生成product

克隆人人项目的逆向工程项目

git clone https://gitee.com/renrenio/renren-generator.git

下载到桌面后,同样把里面的.git文件删除,然后移动到我们IDEA项目目录中,同样配置好pom.xml

<modules>
    <module>gulimall-couponmodule>
    <module>gulimall-membermodule>
    <module>gulimall-ordermodule>
    <module>gulimall-productmodule>
    <module>gulimall-waremodule>
    <module>renren-fastmodule>
    <module>renren-generatormodule>
modules>

在maven中刷新一下,让项目名变粗体,稍等下面进度条完成。
修改application.yml

url: jdbc:mysql://localhost:3306/gulimall-pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root

然后修改generator.properties

# 主目录
mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
#模块名
moduleName=product
#作者
author=hh
#email
[email protected]
#表前缀(类名不会包含表前缀) # 我们的pms数据库中的表的前缀都pms
# 如果写了表前缀,每一张表对于的javaBean就不会添加前缀了
tablePrefix=pms_

运行RenrenApplication。访问http://localhost,点击renren-fast,显示全部的表,全选,点击生成代码,下载了压缩包,解压压缩包,把main放到gulimall-product的同级目录下

2、common:

  • 作用:把每个微服务里公共的类和依赖放到common里
  • 做法:在项目上右击——new modules— maven—然后在name上输入gulimall-common
  • 在总项目的pom.xml中也自动添加了gulimall-common
  • 在common项目的pom.xml中添加公共依赖:

<dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>3.3.2version>
dependency>

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    <version>1.18.8version>
dependency>

<dependency>
    <groupId>org.apache.httpcomponentsgroupId>
    <artifactId>httpcoreartifactId>
    <version>4.4.13version>
dependency>
<dependency>
    <groupId>commons-langgroupId>
    <artifactId>commons-langartifactId>
    <version>2.6version>
dependency>

tips: shift+F6修改项目名
此外,说下maven依赖的问题:
:一般用在父模块中,用于管理子模块依赖的版本号,但并没有实际引入
:在子模块中对依赖进行声明。倘若子模块不声明自己的依赖,是不会从父模块的dependencyManagement继承的,只有子模块中也声明了依赖,并且没有写对应的版本号它才会从父类中继承,并且version和scope都是取自父类;此外要是子模块中自己定义了自己的版本号,是不会继承自父类的。
:写在dependencies里,代表本项目依赖,子项目也依赖。如果有个标签,代表本项目依赖,但是子项目不依赖

在product项目中的pom.xml,引入common

<dependency>
    <groupId>com.atguigu.gulimallgroupId>
    <artifactId>gulimall-commonartifactId>
    <version>0.0.1-SNAPSHOTversion>
dependency>

将人人项目中用到的类复制过来:

  • renren-fast项目中utils包下的Query和PageUtils、R、Constant复制到common项目的java/com.atguigu.common.utils下。
  • 复制renren-fast中的xss包粘贴到common的com.atguigu.common目录下。
  • 复制了exception文件夹,对应的位置关系自己观察一下就行

将暂时不用的注释:

  • 把@RequiresPermissions这些注解掉,因为是shiro的
  • 注释掉product项目下类中的//import org.apache.shiro.authz.annotation.RequiresPermissions;,他是shiro的东西
  • 注释逆向工程renren-generator\src\main\resources\template/Controller中所有的@RequiresPermissions。## import org.apache.shiro.authz.annotation.RequiresPermissions;

总之什么报错就去renren-fast项目里找。重启逆向工程。重新在页面上得到压缩包。重新解压出来,不过只把里面的controller复制粘贴到product项目对应的目录就行。

3、逆向生成coupon(优惠券服务)

打开renren-generator,修改generator.properties(主要修改模块名、表前缀)

# 主目录
mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
#模块名
moduleName=coupon
#作者
autho=wangyang
#email
[email protected]
#表前缀(类名不包含表前缀)
tablePrefix=sms_

修改yml的数据库连接信息

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver

启动RenrenApplication.java,运行后去浏览器80端口查看,同样让他一页全显示后选择全部后生成。生成后解压将main文件夹复制到coupon项目对应目录下,将resources下src包先删除

让coupon也依赖于common,修改pom.xml

<dependency>
    <groupId>com.atguigu.gulimallgroupId>
    <artifactId>gulimall-commonartifactId>
    <version>0.0.1-SNAPSHOTversion>
dependency>

添加application.yml

server:
  port: 7000
  
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall-sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver


mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

运行gulimallCouponApplication.java

浏览器输入http://localhost:7000/coupon/coupon/list,返回如下信息,表明成功

{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}

同理逆向生成其它模块(membre会员系统、order订单系统、ware仓储系统)

九、配置&测试微服务的crud功能

1、整合mybatis-plus

1.1、导入依赖(已经将mybatis-plus的依赖导入common里了)

<dependency>
 	<groupId>com.baomidougroupId>
	 	<artifactId>mybatis-plus-boot-starterartifactId>
 	<version>3.3.2version>
dependency>

1.2、配置

参考mybaits的官方文档:https://mp.baomidou.com/guide/quick-start.html,主要分为这几个步骤:

  • 配置数据源(主要指连向哪些数据库)

1)、导入数据库驱动(由于后面所有的微服务都需要驱动,我们直接将驱动导入到common中,并且注意mysql驱动的版本与数据库版本对应)


      <dependency>
           <groupId>mysqlgroupId>
           <artifactId>mysql-connector-javaartifactId>
           <version>8.0.22version>
      dependency>

2)配置数据源(推荐application.yml)

spring:
 datasource:
   username: root
   password: 123456
   url: jdbc:mysql://localhost:3306/gulimall_pms
   driver-class-name: com.mysql.jdbc.Driver
  • 配置mybatisplus相关的信息

1、在主启动类上使用@MapperScan告诉mapper接口都在哪

@MapperScan("com.atguigu.gulimall.product.dao")

2、告诉mybatisPlus,sql映射文件在哪里

mybatis-plus:
   mapper-locations: classpath*:/mapper/**/*.xml

可以点进去mapper-locations看看默认的地址(classpath*:/mapper/**/.xml)
classpath后面加个
表示不止扫描自己的类路径,包括引用的其它jar里的类路径。要想精确可以不加
3、主键自增
如果每一次都调整需要在每个entry都需要设置,可以直接在application.yml中设置

mybatis-plus:
  global-config:
   	db-config:
   		id-type: auto

1.3、解决common里其它的报错:

1⃣️、要用的servlet相关的东西,导入后重新导包


 <dependency>
       <groupId>javax.servletgroupId>
       <artifactId>servlet-apiartifactId>
       <version>2.5version>
       <scope>providedscope>
dependency>

2⃣️、删掉common里xssFiler和XssHttpServletRequestWrapper
3⃣️、将renren-fast中RRException复制过来

1.4、测试

@SpringBootTest
class GulimallProductApplicationTests {

    @Autowired
    BrandService brandService;
    
    @Test
    void saveBrand() {
        BrandEntity brandEntity = new BrandEntity();
        brandEntity.setName("华为");
        brandService.save(brandEntity);
        System.out.println("保存成功");
    }

	@Test
    void getBrand(){
        List<BrandEntity> list = brandService.list(new QueryWrapper<BrandEntity>().eq("brand_id", 1l));
        list.forEach((item)->{
            System.out.println(item);
        });
    }
}

十、springCloud Alibaba简介

在分布式开发里面,首先要知道注册中心、配置中心、网关这三个模块。
springCloud Alibaba一站式解决方案:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
谷粒商城——基础篇_第7张图片

  • 注册中心:每一个微服务上线都应该将它自己注册到注册中心,这样做的好处是,如果订单服务想要调用商品服务,订单服务可以先去注册中心看一下,哪几个商品服务都注册进来了,订单服务就可以挑选一个商品服务来调用。
  • 配置中心:各个微服务的配置众多,比如商品服务微服务在十台机器上,我们想要改商品服务的某一项配置,那么这十台都要改挺麻烦的,我们希望有一个配置中心来集中管理配置,所有的商品服务从配置中心获取他自己的配置,在配置中心改掉某一个配置,同样的这些服务实时修改。
  • 网关,我们所有前端的请求先网关来进行鉴权、过滤、路由等等,由我们的网关抵达其它服务来做一些预先的工作

为什么选择springCloud Alibaba?
springCloud的痛点:1⃣️部分组件停止维护和更新,给开发带来不便。2⃣️部分环境搭建复杂,没有完善的可视化界面,需要大量的二次开发和定制,3⃣️配置复杂,难以上手,部分配置差别难以区分和合理应用
springCloudAlibaba的优点:1⃣️阿里使用过的组件,经历过了考验,设计合理、性能强悍,现在开源出来。2⃣️成套的产品搭配完善的可视化界面给开发运维带来了极大的方便。3⃣️搭建简单,学习成本低

结合springCloud Alibaba最终我们的技术搭配方案:
springCloud Alibaba Nacos——注册中心,服务注册/发现
springCloud Alibaba Nacos——配置中心(动态配置管理)
springCloud Ribbon——负载均衡
springCloud Feign——声明式Http客户端(调用远程服务)
springCloud Alibaba Sentinel——服务容错(限流、降级、熔断)
springCloud Gateway——API网关(webFlux编程模式)
springCloud Sleuth——调用链监控
springCloud Alibaba Seata——原Fescar,即分布式事务解决方案

在common的pom.xml中添加springCloud Alibaba

<dependencyManagement>
      <dependencies>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                <version>2.2.0.RELEASEversion>
                <type>pomtype>
                <scope>importscope>
            dependency>
      dependencies>
dependencyManagement>

十一、nacos—注册中心

官方文档:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md

下载nacos服务端,并启动:
windows:下载–解压–双击bin/startup.cmd。
linux/Mac:进入解压后的bin目录,执行sh startup.sh -m standalone
访问:http://localhost:8848/nacos/ 账号密码:nacos/nacos

  • 因为每个微服务都需要注册,所以在common的pom文件中引入Nacos Discovery Starter
<dependency>
     <groupId>com.alibaba.cloudgroupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
 dependency>

不用写版本号,是因为之前之前引入了spring-cloud-alibaba-dependencies作为版本管理

  • 修改coupon模块的application.yml中配置nacos server的地址,并设置服务名
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-coupon
  • 在主启动类使用@EnableDiscoveryClient 注解开启服务注册与发现功能

依次给member、order、product、ware模块配置nacos server地址并设置服务名,使用@EnableDiscoverClient注解开启服务注册与发现。
启动后访问http://localhost:8848/nacos/ ,可以看到注册进来的实例

十二、feign—远程调用

feign是一个声明式的http客户端
比如member会员服务想要从coupon优惠券服务获取当前会员的所有优惠券,那么会员服务需要先从注册中心中找一下优惠券服务都在哪几台服务器上,会员服务挑一台机器发送请求,优惠券服务响应数据。

  • 只需要在会员服务中引入openfeign,他就拥有了调用远程服务的能力(我们之前已经引入了)
<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
  • 在优惠券添加接口供会员服务调用
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    @RequestMapping("member/list")
    public R memberCoupons(){
        CouponEntity couponEntity = new CouponEntity();
        couponEntity.setCouponName("满100减20");
        return R.ok().put("coupons",Arrays.asList(couponEntity));
    }
}
  • 新建一个feign包,这个包里就放远程调用的接口,并在这个包下编写接口,告诉springCloud这个接口需要调用远程服务
@FeignClient("gulimall-coupon")//告诉springCloud这个接口是一个远程客户端,要调用coupon服务(nacos中找到),其中gulimall-coupon是优惠服务注册到nacos的服务名
public interface CouponFeignService {
	//远程服务的url,注意要写全优惠券服务的方法的映射
	//注意这个地方不是控制层,所以这个请求映射请求的不是我们服务器上的东西,而是nacos注册中心的
    @RequestMapping("/coupon/coupon/member/list")
    public R memberCoupons();
}

@FeignClient和@RequestMapping构成远程调用的坐标。@FeignClient指定服务名,@RequestMapping指定方法的映射

  • 在会员服务的主启动类上加上@EnableFeignClients注解开启远程调用服务
@EnableFeignClients(basePackages="com.atguigu.gulimall.member.feign")//使用basePackages指定扫描哪个包下标注了@FeignClient注解的接口
@EnableDiscoveryClient
@MapperScan("com.atguigu.gulimall.member.dao")
@SpringBootApplication
public class GulimallMemberApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallMemberApplication.class);
    }
}
  • 测试
@RestController
@RequestMapping("member/member")
public class MemberController {
   @Autowired
   private MemberService memberService;

   @Autowired
   CouponFeignService couponFeignService;
   
   @RequestMapping("/coupons")
   public R test(){
      MemberEntity memberEntity = new MemberEntity();
      memberEntity.setNickname("张三");

      R coupons = couponFeignService.memberCoupons();
      return R.ok().put("member",memberEntity).put("coupons",coupons.get("coupons"));
    }

重启服务,发送http://localhost:8000/member/member/coupons,返回

{
    "msg":"success",
    "code":0,
    "coupons":[
        {"id":null,"couponType":null,"couponImg":null,"couponName":"满100-10","num":null,"amount":null,"perLimit":null,"minPoint":null,"startTime":null,"endTime":null,"useType":null,"note":null,"publishCount":null,"useCount":null,"receiveCount":null,"enableStartTime":null,"enableEndTime":null,"code":null,"memberLevel":null,"publish":null}
    ],
    "member":{"id":null,"levelId":null,"username":null,"password":null,"nickname":"会员昵称张三","mobile":null,"email":null,"header":null,"gender":null,"birth":null,"city":null,"job":null,"sign":null,"sourceType":null,"integration":null,"growth":null,"status":null,"createTime":null}
}

十三、nacos—配置中心

nacos除了可以作为注册中心,还可以作配置中心。这样就可以不在application.properties中配置,而是放在nacos配置中心里,这样就可以不用每次修改配置都需要打包部署到每一台服务器上
官方文档:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/readme-zh.md

1、配置中心的使用

  • 在common的pom.xml中添加依赖
<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
  • 在coupons项目中创建/src/main/resources/bootstrap.properties ,这个文件是springboot里规定的,他优先级别application.properties高
# 配置服务名字和nacos配置中心的地址 
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
  • 在浏览器中nacos里的配置列表,点击+号,data ID:gulimall-coupon.properties(默认读取 服务名.properties中的配置),配置
coupon.user.name="配置中心"      
coupon.user.age=12
  • 还是使用@Value注解获取配置文件中的配置,优先读取nacos中的配置,nacos中没有才会读取配置文件
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Value("${coupon.user.name}")//从application.properties中获取,不要写user.name,他是环境里的变量
    private String name;
    @Value("${coupon.user.age}")
    private Integer age;

    @RequestMapping("/test")
    public R test(){
        return R.ok().put("name",name).put("age",age);
    }
}
  • 在CouponController上加上@RefreshScope,在nacos中修改配置时,就能动态的获取(不用在修改配置后重启服务)
  • 访问http://localhost:7000/coupon/coupon/test
{"msg":"success","code":0,"name":"配置中心","age":12}

2、配置中心的进阶

  • 命名空间:用作配置隔离,默认public(保留空间),默认新增的配置都在此空间。
    1)、开发、测试、生产的配置不一样,可以用命名空间进行配置隔离
    通过bootstrap.properties指定需要使用的命名空间
#指定的是命名空间的id
spring.cloud.nacos.config.namespace=1a0f8db6-c095-44a7-a3ec-a8bb0c7f284a
 2)、也可以基于每个微服务互相隔离配置,每一个微服务都创建自己的命名空间,这样只加载
  自己命名空间的配置
  • 配置集:所有配置的集合
  • 配置集id:类似配置文件名,如application.yml
  • 配置分组:默认所有的配置集都属于DEFAULT_GROUP,可以自己定义,比如618和双十一的优惠策略放在不同的分组中,在bootstrap.properties中指定使用哪个分组的配置
spring.cloud.nacos.config.group=DEFAULT_GROUP

最终方案:每个微服务创建自己的命名空间,使用配置分组区分环境(dev/test/prod)

3、从配置中心中同时加载多个配置集

业务场景:每个微服务都会有非常多的配置,我们不会将所有的配置都放在一个配置中,比如跟数据源有关的配置、跟框架有关的配置都放在不同的配置文件中。
配置结果如下:
谷粒商城——基础篇_第8张图片
只需要在bootsrap.properties中额外加载这些配置文件

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

spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true

spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true

就不需要application.yml,只需要bootstrap.properties指定加载多个配置集。默认还是会加载coupon.properties

十四、gateway—网关

谷粒商城——基础篇_第9张图片
引入:
1、动态路由:比如前端需要调后台的商品服务,并不知道商品服务器(假如有123台服务器)哪个是正常的哪个是宕机,如果1号服务器掉线,需要手动改成2号服务器。这时就需要先通过网关帮我们动态的路由到正常的服务器,因为网关可以从注册中心实时感知某个服务上线还是下线
2、每一个请求过来,我们要给他加上权限、监控等,去每一个服务都需要做这些事,如果我们把这些功能写在各个服务上,就出现了很多重复开发。我们可以让每个请求先经过网关,进行鉴权、限流、日志输出后代转给其他服务

官方文档:https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/

三大核心概念:

  • 路由:发一个请求给网关,网关要将请求路由到指定的服务。路由有id、目的地uri、断言的集合、过滤器的集合。当断言为真时就能路由到指定位置。
  • 断言:匹配请求里的任何信息,比如请求头、请求参数。比如网关根据请求头的信息来判断路由到哪个服务
  • 过滤:请求和响应都可以被修改

客户端发送请求给服务端,先将请求发给API网关(gateway),gateway通过handler mapping(映射器)看当前请求能否被路由(处理),如果能被处理将请求交给handler(处理器),处理器要处理这些请求,就会经过一系列filter,然后路由到指定服务,指定服务处理完,又经过一系列filter,再返回给客户端
谷粒商城——基础篇_第10张图片
总结:当请求到达网关,网关先利用断言来判断这次请求是否符合某个路由规则,如果符合就按这个路由规则把它路由到指定地方,但要去这指定地方就要经过一些列filter进行过滤

使用:

  • 新建moudle,使用Spring initilizer,Group:com.atguigu.gulimall,Artifact: gulimall-gateway,java version:8,package:com.atguigu.gulimall.gateway。 搜索gateway选中。
  • pom.xml里加上common依赖, 修改springBoot和springCloud的版本
  • 在gateway的主启动类上加@EnableDiscoveryClient开启服务注册发现,排除mybatis的相关配置
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallGatewayApplication {
    public static void main(String[] args) {
       SpringApplication.run(GulimallGatewayApplication.class, args);
    }
}
  • application.properties配置文件中配置nacos的注册地址,gateway就可以注册进nacos中,也能从nacos中发现其他服务
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=gulimall-gateway
server.port=88
  • bootstrap.properties配置文件中配置nacos配置中心地址
spring.application.name=gulimall-gateway
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
#在nacos中创建gateway名称空间,并指向它
spring.cloud.nacos.config.namespace=1141622a-b05c-4ecd-b423-4c55694ba774
  • 需求:包含url参数,若url为baidu,就访问百度,若url为qq,则访问qq
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
  • 测试:访问http://localhost:88/?url=baidu或http://localhost:88/?url=qq

补充:gateway使用的是Netty,而不是tomcat。Netty有非常高的网络性能

十五、三级分类

1、准备工作

  • 1⃣️ 数据库:
    在gulimall_pms库里创建pms_category表(商品分类表)并添加数据
DROP TABLE IF EXISTS `pms_category`;

CREATE TABLE `pms_category` (
  `cat_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类id',
  `name` char(50) DEFAULT NULL COMMENT '分类名称',
  `parent_cid` bigint(20) DEFAULT NULL COMMENT '父分类id',
  `cat_level` int(11) DEFAULT NULL COMMENT '层级',
  `show_status` tinyint(4) DEFAULT NULL COMMENT '是否显示[0-不显示,1显示]',
  `sort` int(11) DEFAULT NULL COMMENT '排序',
  `icon` char(255) DEFAULT NULL COMMENT '图标地址',
  `product_unit` char(50) DEFAULT NULL COMMENT '计量单位',
  `product_count` int(11) DEFAULT NULL COMMENT '商品数量',
  PRIMARY KEY (`cat_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1433 DEFAULT CHARSET=utf8mb4 COMMENT='商品三级分类';
  • 2⃣️完成三级分类接口

controller:

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
 	/**
     * 查出所有分类及子分类,以树形列表展示出来
     */
    @RequestMapping("/list/tree")
    public R list(@RequestParam Map<String, Object> params){
        List<CategoryEntity> entities = categoryService.listTree();

        return R.ok().put("data", entities);
    }
}

service:

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
	@Override
    public List<CategoryEntity> listTree() {
        //1、查询所有
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);

        //2、组成树形
        List<CategoryEntity> level1Menus = categoryEntities.stream().filter((categoryEntity) -> {
            return categoryEntity.getParentCid() == 0;
        }).map((menu)->{
            menu.setChildren(getChildrens(menu,categoryEntities));
            return menu;
        }).sorted((menu1,menu2)->{
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());

        return level1Menus;
    }

    //递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildrens(CategoryEntity menu,List<CategoryEntity> all){
        List<CategoryEntity> collect = all.stream().filter((categoryEntity) -> {
            return categoryEntity.getParentCid() == menu.getCatId();
        }).map((categoryEntity)->{
             categoryEntity.setChildren(getChildrens(categoryEntity,all));
             return categoryEntity;
        }).sorted((menu1,menu2)->{
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());

        return collect;
    }

通过访问http://localhost:10000/product/category/list/tree测试

  • 3⃣️在renren-fast中添加菜单
    谷粒商城——基础篇_第11张图片
    刷新可以看到左边菜单栏多了一个商品系统,并且gulimall_admin.sys_menu多了一条数据

继续新增
谷粒商城——基础篇_第12张图片
刷新可以看到,商品系统里多了一个分类维护,并且gulimall_admin.sys_menu多了一条数据,父id是商品系统id

  • 4⃣️前端页面

想要做的效果:点击分类维护菜单,展示电商系统的三级分类

现在点击分类维护可以看到url地址为:http://localhost:8001/#/product-category,观察系统管理里的角色管理(url:http://localhost:8001/#/sys-role)对应的视图src/view/modules/sys/role.vue,因此我们需要在src/view/modules/下新建文件夹product,product里面新建category.vue。

输入vue快速生成vue模版,在然后去https://element.eleme.cn/#/zh-CN/component/tree看如何使用树形控件。修改的部分如下





2、配置网关路由与路径重写

目前的情况:获取验证码和登陆时都是renren-fast-vue直接调用renren-fast后台项目,而后台系统可能部署在多个服务器上
想要做的效果:前端renren-fast-vue先通过网关(一些列的断言、路过滤等操作)再调用renren-fast后台项目

  • 将renren-fast注册到nacos中,以便网关能够发现调用

<dependency>
	<groupId>com.atguigu.gulimallgroupId>
	<artifactId>gulimall-commonartifactId>
	<version>0.0.1-SNAPSHOTversion>
dependency>

<dependency>
	<groupId>com.google.code.gsongroupId>
	<artifactId>gsonartifactId>
	<version>2.8.5version>
dependency>
  • 在renren-fast的application.yml里给服务命名并指定nacos的地址
spring:
	application:
    	name: renren-fast
    cloud:
    	nacos:
      		discovery:
        		server-addr: 127.0.0.1:8848
  • 开启renren-fast的服务注册发现
//在renren-fast的启动类上加@EnableDiscoverCliet开启服务注册发现
@EnableDiscoveryClient
@SpringBootApplication
public class RenrenApplication {
	public static void main(String[] args) {
		SpringApplication.run(RenrenApplication.class, args);
	}
}

启动后可以在nacos的服务列表中看到

  • 在前端项目中找到调后台的配置(/static/config/index.js),地址改成网关的地址
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
//前端所有的请求都带上/api,以便网关进行断言
  • 在网关gateway中配置路由和路径重写
spring:
  cloud:
    gateway:
      routes:
        - id: admin_root
          uri: lb://renren-fast  #lb代表负载均衡
          predicates:
            - Path=/api/** #路径路由,路径中带api的断言为真
          filters:
            - RewritePath=/api/(?>.*),/renren-fast/$\{segment}
            # 把/api/* 改变成 /renren-fast/*

此时验证码正常,登陆的时候报跨域错误
在这里插入图片描述

3、跨域

  • 跨域:浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对js施加的安全限制(ajax可以)
    同源策略:是指协议、域名、端口都要相同,其中一个不同都会产生跨域
  • 跨域流程:
    谷粒商城——基础篇_第13张图片
    跨域请求会先发送一个预检请求(OPTIONs)探路,收到允许跨域的响应才发送真实请求。
  • 解决跨域的方案:
    方法1:在nginx服务器同时部署vue和gateway。都先请求nginx,这样就是同一个域了
    谷粒商城——基础篇_第14张图片
    方法2:在服务端配置允许跨域。在响应头中添加:参考:https://blog.csdn.net/qq_38128179/article/details/84956552
    • Access-Control-Allow-Origin : 支持哪些来源的请求跨域
    • Access-Control-Allow-Method : 支持那些方法跨域
    • Access-Control-Allow-Credentials :跨域请求默认不包含cookie,设置为true可以包含cookie
    • Access-Control-Expose-Headers : 跨域请求暴露的字段
      CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
    • Access-Control-Max-Age :表明该响应的有效时间为多少秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将失效

我们一般使用方法2跨域,在gateway里写一个过滤器,允许所有请求跨域

@Configuration
public class GulimallCorsConfiguration {
    @Bean // 添加过滤器
    public CorsWebFilter corsWebFilter(){
        // 基于url跨域,选择reactive包下的
        UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
        // 跨域配置信息
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 允许跨域的头
        corsConfiguration.addAllowedHeader("*");
        // 允许跨域的请求方式
        corsConfiguration.addAllowedMethod("*");
        // 允许跨域的请求来源
        corsConfiguration.addAllowedOrigin("*");
        // 是否允许携带cookie跨域
        corsConfiguration.setAllowCredentials(true);

        // 任意url都要进行跨域配置
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}

点击分类维护菜单,数据没有加载出来,这是因为网关上所做的路径映射不正确,映射后的路径为http://localhost:8001/renren-fast/product/category/list/tree,但是只有通过http://localhost:10000/product/category/list/tree路径才能够正常访问,所以会报404异常
谷粒商城——基础篇_第15张图片

4、展示三级分类

  • 在gateway中定义一个product模块的路径路由规则,且进行路径重写
- id: product_route
  uri: lb://gulimall-product
  predicates:
     - Path=/api/product/**
  filters:
     - RewritePath=/api/(?>.*),/$\{segment}

精确断言的路由放在模糊断言的路由前面,优先判断。比如/api/product/** 要放在/api/**的前面

  • 将product注册到nacos中。如果要使用nacos配置中心,新建bootstrap.yml,指定配置中心和命名空间
  • 在product的启动类上加@EnableDiscoveryClient注解开启注册
  • 前端展示
data() {
    return {
      menus: [],
      defaultProps: {
        children: "children",
        label: "name",
      },
    };
  },
  methods: {
    handleNodeClick(data) {
      console.log(data);
    },
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        console.log("成功获取到菜单数据.....", data.data);
        this.menus = data.data;
      });
    },
  }
}

你可能感兴趣的:(谷粒商城,java)