Java实战项目——《谷粒商城》的学习笔记——分布式基础

视频链接:Java项目《谷粒商城》.

分布式基础(全栈开发)

1.1 项目简介

1.1.1 项目架构

微服务架构图:
在这里插入图片描述
微服务划分图:
在这里插入图片描述

1.1.2 电商模式

常见的五种电商模式为:B2B,B2C,C2B,C2C,O2O。谷粒商城属于B2C模式的电商平台。

  1. B2B模式(Business to Business),商家与商家建立的商业关系,如阿里巴巴
  2. B2C模式(Business to Consumer),供应商直接把商品卖给用户,也就是通常说的商业零售,如:苏宁易购,京东,天猫,小米商城
  3. C2B模式(Consumer to Business),先有消费者需求后有商家生产
  4. C2C模式(Consumer to Consumer),客户之间把东西放到网上卖,如淘宝,闲鱼
  5. O2O模式(Online to Offline),线下商务与互联网结合,互联网成为线下交易的平台,线上快速支付,线下享受服务,如饿了么,美团,淘票票,京东到家

1.1.3 项目技术&特色

  1. 前后端分离开发,开发基于vue的后台管理系统
  2. SpringCloud全新的解决方案
  3. 应用监控、限流、网关、熔断降级等分布式方案
  4. 分布式事务,分布式锁等分布式系统的难点
  5. 分析高并发场景的编码方式,线程池,异步编排等使用
  6. 压力测试与性能优化
  7. 各种集群技术的区别及使用
  8. CI/CD使用

1.1.4 项目前置要求

  1. 熟悉SpringBoot及常见解决方案
  2. 了解SpringCloud
  3. 熟悉git,maven
  4. 熟悉redis,linux,docker基本操作
  5. 了解html,css,javascript,vue
  6. 熟练使用idea开发项目

1.2 分布式基础概念

1.2.1 微服务

  简而言之,拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。

1.2.2 集群、分布式、节点

  集群是物理形态,分布式是工作方式。分布式中的每一个节点,都可以做集群,而集群并不一定是分布式。节点:集群中的一个服务器

1.2.3 远程调用

  在分布式系统中,每个服务可能存在不同的主机,但服务之间不可避免地需要相互调用,称为远程调用。SpringCloud使用HTTP+JSON的方式完成远程调用。
在这里插入图片描述

1.2.4 负载均衡

  分布式系统中,A服务需要调用B服务,而B服务在多台服务器运行,为了使每个服务器不要太忙或者太闲,需要负载均衡地调用每个服务器,提升网络健壮性。
  常见的负载均衡算法:

  1. 轮询,即按顺序循环调用
  2. 最小连接,优先选择连接数最少的服务器
  3. 散列:根据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端口。这样的话,每次安装一个软件都需要配置端口映射,会很麻烦。
在这里插入图片描述
在这里插入图片描述
  解决方法有两种:

  1. 修改虚拟机的网络配置文件,比较麻烦
  2. 在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步骤如下:

  1. 使用官方文档中的命令卸载以前Docker
sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
  1. 设置Docker仓库
sudo yum install -y yum-utils
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
  1. 安装Docker
sudo yum install docker-ce docker-ce-cli containerd.io
  1. 启动Docker
sudo systemctl start docker
  1. 设置Docker开机自启动
sudo systemctl enable docker

  最后使用docker -v检查版本,效果如下:
在这里插入图片描述

1.3.2.1 配置Docker国内镜像加速

  注册阿里云,点击控制台,点击镜像加速服务,点击镜像加速器,选择centos下的命令,按顺序执行命令即可配置。

1.3.3 Docker安装MySQL

  一直用sudo比较麻烦,可以切换到root用户,使用su,密码为vagrant

  1. 从镜像仓库拉取
sudo docker pull mysql:5.7
//检查当前有哪些镜像
sudo docker images
  1. 创建实例并运行
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用户,再执行命令。

  1. 下载Redis镜像,不写版本号默认下载最新版,为了保持一致下载5.0.5
docker pull redis:5.0.5
  1. 创建实例并启动。这里有个坑,-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
  1. 测试是否启动成功
docker exec -it redis redis-cli

在这里插入图片描述

  1. 持久化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+fVSCode代码整理。需求:先查询用户,再根据用户查询课程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
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐