知名网站建设定制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 Gateway)抽象了每个服务都需要的公共功能,提供了客户端负载均衡,服务自动熔断,灰度发布,统一认证,限流流控,日志统计等丰富功能。

1.3 环境搭建

1.3.1 安装Linux虚拟机

  下载安转,需要进入bios界面开启CPU虚拟化,一般是开启了的。安装后界面如下:

  下载安装,可以在VirtualBox上快速创建虚拟机,安装后需要重启,重启后在cmd命令行输入vagrant如果显示相关信息,表明安装成功。在cmd命令行中输入Vagrant init centos/7
  做好了许多镜像,其中一个便是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步骤如下:

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

  最后使用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
  • 2
  • 3
  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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

查看运行中的容器: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


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=utf8collation-server=utf8_unicode_ciskip-character-set-client-handshakeskip-name-resolve
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  重启Docker中的MySQL,使配置文件生效,docker restart mysql

1.3.4 Docker安装Redis

  先切换到root用户,再执行命令。

  1. 下载Redis镜像,不写版本号默认下载最新版,为了保持一致下载5.0.5
docker pull redis:5.0.5
  • 1
  1. 创建实例并启动。这里有个坑,-d后面要加上版本号,否则默认latest,会重新到官网下载redis镜像,可以先删除容器docker rm -f 容器id,再删除镜像文件docker rmi -f redis
mkdir -p /mydata/redis/conftouch /mydata/redis/conf/redis.confdocker 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 测试是否启动成功
docker exec -it redis redis-cli
  • 1

  1. 持久化Redis数据,vi redis.conf,加入下面内容,使用aof持久化方式(现在似乎默认是aof)
appendonly=yes
  • 1

1.3.4.1 Redis可视化客户端

  为了方便,可以安装redis可视化客户端RedisDesktopManager。

1.3.4.2 Redis配置

  要了解可以配置哪些参数,可以参考。

1.3.4.3 设置MySQL和Redis容器自启动

docker update mysql --restart=alwaysdocker update redis --restart=always
  • 1
  • 2

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  配置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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

1.3.5.2 Idea&VsCode

  在开始界面的All settings设置里配置mvn。


  在Plugins里安装常用的插件Lombok(2021内置不需要安装)和MyBatisX。
  使用Idea开发后台微服务项目,前端的后台管理系统使用开发,下载并安装。安装好后再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有时登录不上,因此选择。下载,下载慢可以使用,安装好后,右键Git Bash进行基本配置。

//配置用户名git config --global user.name "zkp"//配置邮箱git config --global user.email "2512139262@qq.com"
  • 1
  • 2
  • 3
  • 4

  使用SSH免密连接:

ssh-keygen -t rsa -C "2512139262@qq.com"//查看秘钥内容cat ~/.ssh/id_rsa.pub
  • 1
  • 2
  • 3

  将秘钥内容复制到码云里,点击设置——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初始化一个项目

  首先创建仓库,设置名称,初始化,模板,分支类型。

  创建好仓库后,复制仓库地址,我这里是,打开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
  需要先安装,安装好后可以打开pdm文件。

五个微服务对应五个数据库,每个数据库下有多个表,虽然有很多的表,但是表之间不建立外键,这是因为电商系统中数据量很大,做外键关联是非常耗费数据库性能的操作。假设有几十万或者几百万条数据,每次插入或者删除一条数据,都需要对外键进行检查,来保证数据一致性和完整性。

在PowerDesigner中可以方便地设计表和字段,也可以在Name中起名字作为注释。

导入到数据库中

1.4 后台管理系统

  为了简化后台管理系统前后端的开发,使用码云上开源项目,使用renren-fast和renren-fast-vue。

克隆到本地:

git clone https://gitee.com/renrenio/renren-fast.gitgit clone https://gitee.com/renrenio/renren-fast-vue.git
  • 1
  • 2

将renren-fast里的.git删除,再粘贴到gulimall中,在gulimall的pom.xml中将renren-fast添加为模块,再启动renren-fast可以看到成功运行。

1.5 前端项目

  接下来用VSCode打开renren-fast-vue,并安装前端项目的运行环境

1.5.1 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 逆向工程搭建&使用

  克隆人人开源的代码生成器,删除.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>
  • 1
  • 2
  • 3
  • 4
  • 5

  最后添加模块的application.yml,配置数据源。
  对其他模块也进行同样的操作,生成代码之前要修改代码生成器的generator.properties配置和连接数据库配置application.yml。注意coupon对应sms表,member对应ums表。在application.yml中用server:port可以编排端口

server:  port:7000
  • 1
  • 2

1.6.1 整合MyBatis-plus

  在gulimall-common里导入依赖

<dependency>    <groupId>com.baomidou</groupId>    <artifactId>mybatis-plus-boot-starter</artifactId>    <version>3.2.0</version></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

  参照进行配置,包括导入驱动

 <dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <version>8.0.27</version></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

配置数据源

配置MyBatis-plus,使用MapperScan,以及映射文件地址,使用classpath*表示除了自己的classpath,依赖的classpath也会扫描映射文件。


设置主键自增

测试一下插入数据,成功

测试查询操作,成功

1.7 微服务-注册中心、配置中心、网关

  每一个微服务上线,都应该先将自己注册到注册中心,这样其他服务就能知道想要调用的服务是否可用。每个服务在配置中心拉取自己的配置。网关有分配,权限控制等功能。
  我们不使用官网的SpringCloud,而是用,只需要少量注解和少量配置,就能迅速搭建分布式应用系统。使用步骤可以参考。
  不使用官网的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 注册中心

具体可以参考。
1、导入Nacos Discovery Starter
2、下载 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。


  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));    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3、member服务里编写一个接口,告诉openfeign这个接口需要调用远程服务,放在feign包下
4、在接口上加注解@FeignClient("gulimall-coupon"),把要调用的方法的完整签名复制过来,注意路径要写全

    @RequestMapping("、coupon/coupon/member/list")    public R memberCoupons();
  • 1
  • 2

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  调用成功!

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>
  • 1
  • 2
  • 3
  • 4

2、在src/main/resources/下创建bootstrap.properties,这个配置文件会先于application.properties读取

spring.application.name=gulimall-couponspring.cloud.nacos.config.server-addr=127.0.0.1:8848
  • 1
  • 2
  • 3

  在不设置配置中心时,是这样获取配置文件的内容(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.ymlspring.cloud.nacos.config.ext-config[0].group=devspring.cloud.nacos.config.ext-config[0].refresh=true
  • 1
  • 2
  • 3

  就可以加载数据源配置文件,其他配置文件类似。读取顺序:先读取配置中心的,配置中心没有再读取配置文件。
小插曲:完成上述配置后尝试访问数据库时报错,在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>
  • 1
  • 2
  • 3
  • 4

2、使用见

1.9.2 Gateway

  网关作为流量的入口,把请求路由到各个服务,统一做鉴权、限流、日志输出等功能。。下面创建并测试API网关。
创建模块

  添加到配置中心和注册中心,详细步骤见上一节。运行时报错,因为依赖了gulimall-common,而它依赖了mybatis,因此可以排除与数据库有关的配置。设置端口为88。

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
  • 1

在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);        }    });})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

提取之后代码如下:

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)    })
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

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}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

或者直接在变量名之前使用export。

  • import
    单个导入或者多个导入
import util from "./hello.js"import {name,age} from "./hello.js"
  • 1
  • 2

2.2 Vue

2.2.1 MVVM

  数据和页面能同时变,只要有一方变了,另外一方能跟着变。新建文件夹,初始化,安装vue2:

npm init -ynpm install vue@2
  • 1
  • 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2、双向绑定
3、事件处理
安装插件:
Vue 2 Snippets
安装浏览器插件:

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}"
  • 1

双向绑定: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>
  • 1

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                }            }        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

还可以注册局部组件,在Vue实例中在components中进行引用。
生命周期:在合适的时候钩子函数会被触发。

2.2.4 Vue模块化开发

搭建脚手架工程,win+r,cmd回车,打开Windows命令行窗口
1、全局安装webpack

npm install webpack -g
  • 1

2、全局安装Vue脚手架

npm install -g @vue/cli
  • 1

3、初始化Vue项目
在新建的文件夹中打开终端,输入:

vue init webpack vue-demo
  • 1

如果出现node.js版本需要升级,先查看node版本,再去官网下载需要更新的版本,然后安装覆盖以前的node。

node -vwhere node
  • 1
  • 2

前面三个直接回车,第四个选择上面的运行环境加编译环境,再回车

4、运行:

cd vue-demonpm run dev
  • 1
  • 2

然后在浏览器中打开相应端口,这里因为有后端项目占用了8080,因此在8081打开:

界面如下:

主要在src下编写,单文件组件:xxx.vue。

2.2.5 整合ElementUI

1、安装

npm install element-ui 
  • 1

2、在mian.js中导入

import ElementUI from 'element-ui';import 'element-ui/lib/theme-chalk/index.css';
  • 1
  • 2

3、使用

Vue.use(ElementUI);
  • 1

使用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请求"	}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

3 商品服务

3.1 三级分类

前端项目运行:

npm run dev
  • 1

如果报错但是正常运行,可以注释掉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}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

跨域:浏览器对JavaScript施加的安全限制,浏览器不能执行其他网站的脚本。
同源策略:协议,域名,端口都需要一样,只要有一个不一样就会产生跨域。
跨域流程:
1、非简单请求(put,delete),先发送预检请求,options
2、响应允许跨域
3、发送真实数据
4、响应数据
为什么获取验证码不会跨域?
因为get是简单请求,简单请求包括(get,head,post),且值要是三种之一:

解决跨域:
1、使用nginx配置为同一域
2、配置当次请求允许跨域
选择第二个方案,并且复用,直接在gateway中配置filter加上响应头
网关统一配置跨域,renren-fast配置的跨域需要注释掉

@Configurationpublic 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);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16


配置路由时,如果都是路径断言,有优先级,要把更精确的路由放在前面。效果如下:

使用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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2、实体字段加上@TableLogic注解

@TableLogic(value = "1",delval = "0")private Integer showStatus;
  • 1
  • 2

拖拽节点实现修改

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>
  • 1
  • 2
  • 3
  • 4
  • 5

创建子用户,添加权限。
可以通过上传文件流,也可以使用封装好的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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

1、导入starter依赖,由于其他微服务也要使用到,因此导入到common的pom.xml中

<dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alicloud-oss</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4

2、配置文件中配置

spring:  cloud:    alicloud:      access-key: LTAI5tSh95txzVw97xq3BKMh      secret-key: ZvbB3Q9VhpfrJGPNszutwL5PAN5knh      oss:        endpoint: oss-cn-beijing.aliyuncs.com
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6


前后联调:
直接用写好的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;
  • 1
  • 2

对校验做统一的处理,使用@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);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

如果不能精确匹配,就来到最大的异常处理:

  • 1

写业务时,如果有异常,放心大胆地抛出去,统一进行处理。前两位业务场景,最后三位表示错误码。创建枚举类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;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

分组校验,例如新增时品牌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 { };}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
com.atguigu.common.valid.ListValue.message=必须提交指定的值
  • 1

如果中文乱码,先删除文件,然后修改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);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

3、关联自定义的校验器和自定义的校验注解,一个校验注解可以有多个校验器

@Constraint(validatedBy = { ListValueConstraintValidator.class })
  • 1

更新状态时,如果提示品牌名必须提交,

3.3 SPU和SKU

  标准化产品单元(Standard Product Unit,SPU),是商品信息聚合的最小单元。
库存量单位(Stock Keeping Unit,SKU),即库存进出计量的基本单位。
例如:Iphone X是一个SPU,但买到手的具体配置是一个SKU。
在gulimall_admin中执行sys_menus.sql,建立这些菜单:

接口文档地址:。
接下来编写平台属性——属性分组。
父子组件传递数据:
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);
  • 1

使用注解@RequestBody将请求体中的数据封装成指定类。
注意实体类要标注@Data,自动生成setter和getter,否则没有getter和setter不能转换成指定类型。

3.4 新增商品

执行架构篇的gulimall_pms.sql,插入一些数据。
遇到没有发送请求的情况,评论中做法:
1、执行命令

npm install --save pubsub-js
  • 1

2、在src的main.js中加上

import PubSub from 'pubsub-js'Vue.prototype.PubSub = PubSub
  • 1
  • 2
  • 3

把gulimall-member注册到注册中心,注解开启服务注册与发现。网关配置路由关系。将前端的modules下的member,ware等文件夹拷到项目modules中。
,粘贴后点击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;
  • 1

断点调试时如果前面加了一行,断点会失效,可以把要加的放在后面。
对于空图片路径的需要过滤掉。filter返回true就是需要保留。

此外,满0件打0折,满0元减0元是无用信息,需要剔除。BigDecimal的比较使用方法compareTo

skuReductionTo.getFullPrice().compareTo(new BigDecimal("0"))==1
  • 1

只要有一个就保存,更深入地在具体保存时再选择性保存存在的信息。
过滤会员价格,如果是0过滤。
还有其他更多细节在高级篇完善。

SPU检索
返回到前端的时间需要格式化,否则是下面的样子:

在application.yml中添加:

spring:  jackson:    date-format: yyyy-MM-dd HH:mm:ss
  • 1
  • 2
  • 3

如果设置后不对可以加上时区: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 } }
  • 1

再把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
网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发