电商系统微服务整体架构

一、系统环境准备

系统环境:MAC系统、CentOS 7 
系统技术:IDEA、Maven 、Docker 、K8s、MYSQL、Redis、RabbitMQ、SpringCloud、	
 Termius、Nginx 、Git、MyCat、ElasticSearch、Jenkins、Nacos、Sentinel、Rabbon
 ELK、LogStash、ES 、Kibana、Prometheus、VM

二、Git安装

1、安装brew

# 方法一中科大安装
https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/
# 查看是否安装:ruby
which ruby
# 查看ruby版本号
ruby --version
# 执行命令安装brew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
# 如果出现404 配置host
sudo vi /etc/hosts
# 配置如下映射
199.232.68.133 raw.githubusercontent.com
199.232.68.133 user-images.githubusercontent.com
199.232.68.133 avatars2.githubusercontent.com
199.232.68.133 avatars1.githubusercontent.com
# 重新执行 这里需要一段时间
# 安装完 后查询一下版本号
brew --version

2、安装Git

# 执行如下命令安装git
brew install git

三、CentOS系统安装及网络配置

网络配置请查看 CentOS系统安装及网络配置篇

四、Docker安装

docker官方地址:https://docs.docker.com/engine/install/centos/

# 删除旧版本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
# 配置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

安装成功如图所示:
在这里插入图片描述
开启开机自动启动

# 开机自起的命令
sudo systemctl enable docker

配置镜像加速 登录阿里云 - 找到控制台 - 产品与服务 - 云服务器ECS
在这里插入图片描述
执行如下命令,进行对镜像加速
在这里插入图片描述
重启docker 容器

# 重启docker容器命令如下
sudo systemctl restart docker

五、Docker安装MYSQL

hub.docker官方网站搜索镜像

docker pull mysql:5.7

安装成功,如图所示:
安装成功效果
查看docker中的镜像

#查看已经安装的镜像
docker images

创建实例并启动

#创建实例并启动命令如下:
docker run -p 3306:3306 --name mysql -v /mydata/mysql/log:/var/log/mysql -v /mydata/mysql/data:/var/lib/mysql -v /mydata/mysql/conf:/etc/mysql  -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7

命令详解

 -p  3306:3306:将容器的3306端口映射到主机的3306端口
 -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 :初始化root 用户的密码

启动成功,如图所示:
在这里插入图片描述
查看在docker 中已启动的镜像

# 查看已经启动的容器
docker ps
# 查看容器安装位置
whereis mysql
# 进入容器命令
docker exec -it mysql /bin/bash

修改没有MYSQL配置

# 修改配置文件 没有的话新创建一个
vi /mydata/mysql/conf/my.cnf
# 配置内容如下
[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
# 查看配置文件
docker exec -it mysql /bin/bash
# 配置mysql在容器中开机启动
docker update mysql --restart=always

执行结果如图所示:
在这里插入图片描述

六、Docker中安装redis

1、安装redis基本安装

	# 安装redis命令
	docker pull redis

安装成功,如图所示:
在这里插入图片描述
通过命令查看images
查看结果如图所示:
在这里插入图片描述
2、创建实例并启动

创建2个文件 命令如下:
 # 创建2个文件
 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/conf/redis.conf:/etc/redis/redis.conf -d redis redis-server /etc/redis/redis.conf

启动成功,如图所示:
在这里插入图片描述
3、进入redis

# 进入redis 客户端
docker exec -it redis redis-cli
# 退出redis客户端
exit

4、配置redis持久化机制 默认不开启

cd /mydata/redis/conf
# 编辑redis.conf
vim	 redis.conf
# 添加如下内容:开启日志模式aof
appendonly yes
# 重启redis 
docker restart redis
#配置镜像开机启动
docker update redis --restart=always

5、通过RDM连接redis
官方地址:https://redisdesktop.com
如图所示:
在这里插入图片描述

七、Maven配置

Maven 版本 3.6.3
主要配置如下:

仓库路径:

#配置本地址仓库地址
<localRepository>/Users/zxh/Documents/MavenData</localRepository>

私服镜像地址:

<mirror>  
    <id>nexus-aliyun</id>  
    <mirrorOf>central</mirrorOf>    
    <name>Nexus aliyun</name>  
    <url>http://maven.aliyun.com/nexus/content/groups/public</url>  
</mirror>

jdk环境配置1.8:

<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>

八、IDEA安装 Mybatisx插件

在这里插入图片描述

九、安装VS Code软件

VSCode 官方下载地址:https://code.visualstudio.com/
如图所示:
在这里插入图片描述
VSCode中安装插件
安装第一个插件:auto close Tag 与 Auto Rename Tag
在这里插入图片描述
安装简体中文包 Chinese在这里插入图片描述
安装HTML CSS
在这里插入图片描述

十、GIT基本配置

打开控制台
# 配置用户信息 
git config --global user.name "ahui"
# 配置邮箱信息
git config --global user.email "***132134@qq.com"
配置git免密操作
ssh-keygen -t rsa -C "***132134@qq.com"
# 输入此命令后进行三次回车键
# 查看密钥内容命令
cat ~/.ssh/id_rsa.pub
注册一个码云账号 官方网:https://gitee.com/
配置密钥--》 个人中心--》SSH Key --》 填写密钥

如图所示:
在这里插入图片描述

配置标题:随便写
公钥:填写生成的公钥
需要输入登录密码
# 测试连接
ssh -T get@gitee.com

十一、初始化项目

在码云上创建一个公开的项目
在这里插入图片描述

1、从码云上拉取项目

在这里插入图片描述

2、创建一个项目 Module

选择springboot 工程
在这里插入图片描述
倒入jar包依赖 web、openfeign
在这里插入图片描述
全部项目目录
在这里插入图片描述
复制一个pom文件到gulimall中并修改
在这里插入图片描述

十二、创建数据库

如图所示:
在这里插入图片描述

十三、克隆后台管理系统

在这里插入图片描述

第一个:https://gitee.com/renrenio/renren-fast-vue.git
第二个:https://gitee.com/renrenio/renren-fast.git

在这里插入图片描述
导入项目:renren-fast
1、删除.git文件
2、导入renren-fast项目
3、导入数据表
在这里插入图片描述
数据库表名自己定义:
在这里插入图片描述
修改配置文件:
在这里插入图片描述
测试访问项目
在这里插入图片描述
使用前端项目进行访问:

十四、安装Node.js

官方下载地址:http://nodejs.cn/download/
测试环境与版本
node  -v

在这里插入图片描述
配置淘宝镜像地址:

npm config set registry http://registry.npm.taobao.org/

在这里插入图片描述

十五、配置前端页面

VSCode 导入项目renren-fast-vue
在这里插入图片描述
执行安装命令

#执行安装命令
npm install

下载依赖
在这里插入图片描述
初始化完成
在这里插入图片描述
启动项目

npm run dev

启动成功

登录:账号:admin 密码:admin
在这里插入图片描述
登录成功页面:
在这里插入图片描述

十六、克隆人人代码生成项目

项目地址:https://gitee.com/renrenio/renren-generator.git
在这里插入图片描述
删除.git 文件
把项目添加到module
在这里插入图片描述
修改数据库连接配置:
在这里插入图片描述
修改代码生成配置:
在这里插入图片描述
运行项目:
在这里插入图片描述

访问项目:
在这里插入图片描述
生成代码:
将生成到的代码中的main文件复制以前项目中,进行替换
在这里插入图片描述
创建一个公共类common类
从renren-fast项目中复制一下类进来,如图所示:
在这里插入图片描述
在pom中导入依赖如下:

 <dependencies>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.13</version>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

十七、导入项目,配置项目

将生成的项目导入到项目中

1、coupon 替换 main文件夹

在这里插入图片描述

2、member 替换 main文件夹

在这里插入图片描述

3、order 替换 main文件夹

在这里插入图片描述

4、product 替换 main文件夹

在这里插入图片描述

5、ware 替换 main文件夹

在这里插入图片描述

十八、配置数据源

1、添加公共依赖

配置数据源之前,先导入mysql驱动 导入到common 公共模块中到pom中

gulimall-common --> pom.xml 配置如下:

<!--     添加数据库驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>

2、coupon application.yml

修改application.yml文件
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://10.168.8.50:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root

mybatis-plus:
  mapper-locations: classpath:/mapper/*/*.xml
  global-config:
    db-config:
      id-type: auto

3、member application.yml

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

mybatis-plus:
  mapper-locations: classpath:/mapper/*/*.xml
  global-config:
    db-config:
      id-type: auto

4、order application.yml

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

mybatis-plus:
  mapper-locations: classpath:/mapper/*/*.xml
  global-config:
    db-config:
      id-type: auto

5、product application.yml

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

mybatis-plus:
  mapper-locations: classpath:/mapper/*/*.xml
  global-config:
    db-config:
      id-type: auto

6、ware application.yml

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

mybatis-plus:
  mapper-locations: classpath:/mapper/*/*.xml
  global-config:
    db-config:
      id-type: auto

十九、添加dao接口扫描

在5个模块中的启动类上加一个接口扫描 

以下图为例:
在这里插入图片描述

二十、接口测试

随便找一个模块,在测试类中进行接口测试

以下图为例:
在这里插入图片描述

二十一、配置端口

模块端口配置如图所示:

在这里插入图片描述

二十二、配置导入SpringCloud alibaba

在工具类common中添加依赖 
<!--依赖管理-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

二十三、配置Nacos Server

下载Nacos 官方地址:https://nacos.io/zh-cn/docs/quick-start.html
默认端口:8848
# 启动命令
sh startup.sh -m standalone

访问服务管理
在这里插入图片描述

1、模块注入到Nacos中去需要配置Nacos服务地址:

以coupon模块为例
在application.yml文件中配置如下

  # 配置nacos
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  #配置服务模块名称
  application: 
  	name: gulimall-coupon

2、开启注册服务注解

在启动类中添加如下注解:
@EnableDiscoveryClient

3、查看注册结果

在这里插入图片描述

二十四、远程调用之Feign

Feign 是一个声明式的HTTP客户端,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址信息等
Feign 整合了Ribbon (负载均衡组件)和 Hystrix (服务熔断)可以让我们不需要显示的使用者这两组件

1、配置远程调用

编写coupon 模块,在Controller 编写一个方法

如图所示:
在这里插入图片描述

编写调用方,member模块 MemberController

在这里插入图片描述

编写feign 调用接口 创建一个包feign 创建一个接口CouponFeignService
接口要声明 加入FeignClient 注解

在这里插入图片描述

开启feign 接口扫描

在这里插入图片描述

测试结果, 调用成功结果如图所示:

在这里插入图片描述

二十五、配置 Nacos Config

1、Nacos Config 依赖导入

在common 公共模块中导入 Nacos Config 依赖
配置如下所示:
<!--        配置中心 用来服务的配置管理;-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

2、创建一个配置文件 bootstrap.properties

bootstrap.properties 文件优先级要高application.proterties文件
在bootstrap.properties 编写应用名称与Nacos Config服务 地址
配置如下:

在这里插入图片描述

在application.properties 文件中编写 两个属性用来做测试
如图所示:

在这里插入图片描述

编写测试类进行测试
在CouponController 类中编写测试方法
如图所示:

在这里插入图片描述
测试结果如下:
在这里插入图片描述

3、Nacos 网页控制端 添加配置

打开网页:
在这里插入图片描述
创建配置项
在这里插入图片描述
查看配置项中的Data ID 配置好点击发布即可
在这里插入图片描述

添加配置文件刷新注解
打开启动类 在启动类上添加如下注解

在类上加此@RefreshScope

测试结果:
在这里插入图片描述

4、自定义命名空间配置

创建自定义命名空间
在这里插入图片描述
配置命名空间的使用
复制命名空间ID值
在这里插入图片描述
项目中配置
修改bootstrap.properties配置文件
在这里插入图片描述
克隆配置文件
在这里插入图片描述
选择克隆到到目录
在这里插入图片描述

5、总结Nacos Config

当配置中心和当前应用当配置文件中都配置了相同都项,优先使用配置中心的配置
细节:
	命名空间:
		作用:配置隔离
		默认:public(保留空间);默认新增的所有配置都在public空间。
		例如:开发、测试、生产
		在bootstrap.properties文件中配置如下代码
		spring.cloud.nacos.config.namespace=命名空间自动生成的ID值
		每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加
		载自己命名空间下的所有配置。
	配置集:
		一组相关或者不相关的配置项的集合称为配置集,在系统中,一个配置文件通
		常就是一个配置集,包含了系统各个方面的配置,例如:一个配置可能包含了
		数据源,线程池,日志级别等配置项
	配置集ID:
		Nacos 中的某个配置集的ID,配置集ID是组织划分配置的维度之一,DataID 通
		常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个
		配置集都可以被一个有意义都名称标识,DataID 通常采用类Java包(如 
		com.taobao.tc)的命名规则保证全局唯一性,此命名规则非强制。
	配置集分组:
		Nacos 中的一组配置集,是组织配置的维度之一,通过一个有意义的字符串
		(如:Buy 或 Trade )对配置集进行分组,从而区分 DataID 相同的配置集,
		当您在Nacos上创建一个配置时,如果未填写配置分组的名称,则配置分组的
		名称默认采用,DEFAULT_GROUP 。配置分组的常见场景,

二十六、API网关 Gateway

1、简介

网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等
springcloud gateway 作为springcloud 官方推出等第二代网关框架,取代
了Zuul网关。

2、基本组成

路由:网关的基本构建块。它由ID,目标URI,断言集合和过滤器集合定义。如果聚合断言为true,则匹配路由。
断言:这是Java 8函数断言。输入类型是Spring FrameworkServerWebExchange。这使您可以匹配HTTP请求中的所有内容,例如标头或参数。
过滤:这些是使用特定工厂构造的Spring FrameworkGatewayFilter实例。在这里,您可以在发送下游请求之前或之后修改请求和响应。

3、基本原理

在这里插入图片描述

4、创建网关项目

如图所示:
在这里插入图片描述
添加网关依赖

<!--依赖公共pom--->
<dependency>
			<groupId>com.cy.gulimall</groupId>
			<artifactId>gulimall-common</artifactId>
			<version>0.0.1-SNAPSHOT</version>

		</dependency>
<!---网关依赖-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>

配置注册中心:
application.properties文件

server.port=88
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.application.name=gulimall-gateway

配置nacos配置统一管理:
bootstrap.properties 配置

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=21054115-ec4a-4a47-9b1a-1f14e6a304e1

配置路由规则:
application.yml配置

spring:
  cloud:
    gateway:
      routes:
      #过滤url=baidu 请求跳转到百度uri
        - id: test_route
          uri: https://www.baidu.com
          predicates:
            - Query=url,baidu
      #过滤URL=qq 请求跳转到qq
        - id: qq_route
          uri: https://www.qq.com
          predicates:
            - Query=url,qq

测试结果如图所示:
在这里插入图片描述

二十七、Vue模块开发

# 安装全局 打包工具
npm install webpack -g
# 安装全局 vue 脚手架
npm install -g @vue/cli-init
# 查看vue 版本号
vue --version
#  如果版本号查下失败 找不到vue
npm install -g vue-cli
# 初始化项目
vue init webpack vue-dome # vue-dome 为项目名称
# 进入项目目录
cd vue-dome
# 运行项目
npm run dev

二十八、整合Element-ui

# 安装 element-ui 组建
npm i element-ui

基础使用根据官方文档操作
网址:https://element.eleme.cn/#/zh-CN/component/installation
官方组建添加:
在这里插入图片描述
创建MyTable.vue页面
在这里插入图片描述
Vue 代码生成模板

{
	"生成vue模板":{
	"prefix": "vue",
	"body": [
		"<template>",
		"<div></div>",
		"</template>",
		"",
		"<script>",
		"//这里可以导入其他文件(比如:组件,工具js 第三方插件 js json文件,图片文件等等)",
		"//例如:import 《组件名称》from '<组件路径>';",
		"",
		"export default {",
		"//import 引入的组件需要注入到对象中才能使用",
		"components: {} ,",
		"props: {},",
		"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模板"
	}
}

路由设置:
在这里插入图片描述
添加路由展示标签
在这里插入图片描述
路由设置
在这里插入图片描述
导入Element-ui
在这里插入图片描述
显示效果:
在这里插入图片描述

二十九、三级分类

三级分类如京东商城所示:
在这里插入图片描述
编写控制层:

 /**
     * 查出所有分类以及子分类,以树形结构组装起来
     * 编写CategoryController
     */
    @RequestMapping("/list/tree")
   // @RequiresPermissions("product:category:list")
    public R list(){
    List<CategoryEntity> entityList  = categoryService.lisWithTree();

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

在CategoryService添加一个方法

   List<CategoryEntity> lisWithTree();

编写具体的业务实现

//此业务实现存在一个问题,新添加的2级菜单不能添加3级菜单BUG
//TOTD 需要修改此方法   
@Override
    public List<CategoryEntity> lisWithTree() {
        //TODO 1、查出所有分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        //TODO 2、组装成父子分的树形结构
        System.out.println(categoryEntities);
        //TODO 找到所有的一级分类
        List<CategoryEntity> collect = categoryEntities.stream().filter(categoryEntity ->
                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());
        System.out.println(collect);
        return collect;
    }
    
/**
     * 递归查找所有的子菜单
     * @param root
     * @param all
     * @return
     */
    private List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){
        List<CategoryEntity> children= all.stream().filter(categoryEntity -> {
            return  categoryEntity.getParentCid()==root.getCatId();
        }).map(categoryEntity -> {
            //TODO 1、找到子菜单
            categoryEntity.setChildren(getChildrens(categoryEntity,all));
            return categoryEntity;
        }).sorted((menu1,menu2)->{
            //TODO 2、菜单的排序
            return  (menu1.getSort()==null?0:menu1.getSort())-(menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());
        return children;
    }

前端数据展示:

//编写一个获取数据方法
getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        this.menus = data.data;
      });
    }

在data函数中添加一个menus属性:
在这里插入图片描述
在模版中绑定数据:

<template>
	<div>
		 <el-tree :data="menus" :props="defaultProps">
			//数据显示区域
			//添加,删除,修改button
			 <span class="custom-tree-node" slot-scope="{ node, data }">
          <span>{{ node.label }}</span>
          <span>
          //添加button
            <el-button
              v-if="node.level <= 2"
              type="text"
              size="mini"
              @click="() => append(data)"
            >
              Append
            </el-button>
            //修改button
            <el-button type="text" size="mini" @click="edit(data)">
              edit
            </el-button>
            //删除button
            <el-button
              v-if="node.childNodes.length == 0"
              type="text"
              size="mini"
              @click="() => remove(node, data)"
            >
              Delete
            </el-button>
          </span>
        </span>
		 </el-tree>
	</div>
</template>

编写增删改查完整代码:

<template>
  <div>
    <div>
      <el-tree
        :data="menus"
        :props="defaultProps"
        show-checkbox
        node-key="catId"
        :expand-on-click-node="false"
        :default-expanded-keys="expandedKey"
        draggable
        :allow-drop="allowDrop"
      >
        <span class="custom-tree-node" slot-scope="{ node, data }">
          <span>{{ node.label }}</span>
          <span>
            <el-button
              v-if="node.level <= 2"
              type="text"
              size="mini"
              @click="() => append(data)"
            >
              Append
            </el-button>
            <el-button type="text" size="mini" @click="edit(data)">
              edit
            </el-button>
            <el-button
              v-if="node.childNodes.length == 0"
              type="text"
              size="mini"
              @click="() => remove(node, data)"
            >
              Delete
            </el-button>
          </span>
        </span>
      </el-tree>
    </div>
    <el-dialog
      :title="title"
      :visible.sync="dialogVisible"
      width="30%"
      :close-on-click-modal="false"
    >
      <el-form :model="category">
        <el-form-item label="分类名称">
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <el-input
            v-model="category.productUnit"
            autocomplete="off"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
//这里可以导入其他文件(比如:组件,工具js 第三方插件 js json文件,图片文件等等)
//例如:import 《组件名称》from '<组件路径>';

export default {
  //import 引入的组件需要注入到对象中才能使用
  components: {},
  props: {},
  data() {
    return {
    maxLevel:0,
      title: "",
      dialogType: "",
      category: {
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        catId: null,
        productUnit: "",
        icon: "",
      },
      dialogVisible: false,
      menus: [],
      expandedKey: [],
      defaultProps: {
        children: "children",
        label: "name",
      },
    };
  },
  //计算属性 类似于 data 概率
  computed: {},
  //监控data 中的数据变化
  watch: {},
  //方法集合
  methods: {
    handleNodeClick(data) {
      console.log(data);
    },
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        this.menus = data.data;
      });
    },
    //添加菜单数据请求函数
    addCateory() {
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单保存成功!",
          type: "success",
        });
        //关闭对话框
        this.dialogVisible = false;
        //重新获取数据进行展示
        this.getMenus();
        //设置当前展开项
        this.expandedKey = [this.category.parentCid];
      });
    },
    //添加菜单弹出对话框按钮
    append(data) {
      this.dialogType = "add";
      this.title = "添加分类";
      this.dialogVisible = true;
      this.category.parentCid = data.catId;
      this.category.catLevel = data.catLevel * 1 + 1;
      this.category.name = "";
      this.category.catId = null;
      this.category.icon = "";
      this.category.productUnit = "";
      this.category.sort = 0;
      this.category.showStatus = 1;
    },
    //删除菜单项目处理函数
    remove(node, data) {
      var ids = [data.catId];
      this.$confirm(`是否删除【${data.name}】菜单?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false),
          }).then(({ data }) => {
            this.$message({
              message: "菜单删除成功!",
              type: "success",
            });
            //重新获取数据
            this.getMenus();
            //设置需要默认展开的菜单
            this.expandedKey = [node.parent.data.catId];
          });
        })
        .catch(() => {
          this.$message({});
        });
    },
    //修改数据
    edit(data) {
      this.dialogType = "edit";
      this.title = "修改菜单";
      this.dialogVisible = true;
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        mounted: "get"
      }).then(({ data }) => {
        this.category.name = data.data.name;
        this.category.catId = data.data.catId;
        this.category.icon = data.data.icon;
        this.category.productUnit = data.data.productUnit;
        this.category.parentCid = data.data.parentCid;
        this.category.catLevel = data.catLevel;
        this.category.sort = data.data.sort;
        this.category.showStatus = data.data.showStatus;
      });
    },
    submitData() {
      if (this.dialogType == "edit") {
        this.categoryEdit();
      }
      if (this.dialogType == "add") {
        this.addCateory();
      }
    },
    //修改提交请求
    categoryEdit() {
      var { catId, name, icon, productUnit } = this.category;
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        data: this.$http.adornData({ catId, name, icon, productUnit }, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单修改成功",
          type: "success",
        });
        //关闭对话框
        this.dialogVisible = false;
        //重新获取数据进行展示
        this.getMenus();
        //设置当前展开项
        this.expandedKey = [this.category.parentCid];
      });
    },
    allowDrop(draggingNode,dropNode,type){
        //被拖动的总节点以及所在的父节点总层数不能大于3
        // 被拖动的当前节点总层数
      console.log(draggingNode,dropNode,type)
      this.countNodeLevel(draggingNode.data);
      let deep =this.maxLevel-draggingNode.data.catLevel+1;
      if(type=="inner"){
          return (deep+dropNode.level)<=3;
      }else{
          return (deep+dropNode.parent.level)<=3;
      }
    },
    countNodeLevel(node){//遍历节点的深度
        if(node.children!=null&& node.children.length>0){
            for(let i=0;i<node.children.length;i++){
                if(node.children[i].catLevel>this.maxLevel){
                    this.maxLevel=node.children[i].catLevel;
                }
               this.countNodeLevel(node.children[i]);
            } 
        }
    }
  },
  //生命周期 -创建完成 (可以访问当前this 实例)
  created() {
    this.getMenus();
  },
  //生命周期 - 挂载完成 (可以访问 DOM元素)
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 -更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 -销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {} //如果页面有keep-alive 缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

三十、…

三十一、数据校验

1、校验所依赖的jar包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2、校验开启注解

使用此注解开启校验@Valid 加了此注解表示该对象需要做校验操作
例如:多注解顺序前后没有影响,但是要在实体对象之前

/**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }

3、校验常用注解说明

注解表示注解作用
@Null只限制能为null
@NotNull限制必须不为null,一般用来校验Integer类型不能为空
@AssertFalse限制必须为false
AssertTrue限制必须为true
@DecimalMax(value)限制必须为一个不大于指定值的数字
@DecimalMin(value)限制必须为一个不小于指定值的数字
Digits(integer,fraction)限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future限制必须是一个将来的日期
@Max(value)限制必须为一个不大于指定值的数字
@Min(value)限制必须为一个不小于指定值的数字
@Past限制必须是一个过去的日期
@Pattern(value)限制必须符合指定的正则表达式
@Size(max,min)限制字符长度必须在min到max之间
@NotEmpty验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0),一般用来校验List类型不能为空
NotBlank验证注解的元素值不为空(不为null、去除首位空格后长度为0),一般用来校验String类型不能为空,不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
@URL验证是否符合URL地址规范

4、@Validated和@Valid区别

@Validated:
(1)可以用在类型、方法和方法参数上,但是不能用在成员属性(字段)上。
(2)提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
(3)用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
@Valid:
(1)可以用在方法、构造函数、方法参数和成员属性(字段)上
(2)用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
例如:

package com.cy.gulimall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;

import com.cy.gulimall.common.valid.AddGroup;
import com.cy.gulimall.common.valid.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
 * 品牌
 *
 * @author ahui
 * @email 624132134@qq.com
 * @date 2020-11-10 00:03:39
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@NotNull(message = "修改必须指定品牌ID",groups = {UpdateGroup.class})
	@Null(message = "新增不能指定ID",groups = {AddGroup.class})
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 * @NotNull 不能为空
	 * 分组校验
	 */
	@NotBlank(message = "品牌名称不能为空",groups = {AddGroup.class,UpdateGroup.class})
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotEmpty
	@URL(message = "logo必须是一个合法的url地址")
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotEmpty
	@Pattern(regexp = "/^[a-zA-Z]$/",message = "检索首字母必须是一个字母")
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull
	@Min(value = 0,message = "排序号必须大于等于0")
	private Integer sort;

}

5、异常处理

单一处理方式:

/**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            HashMap<String, String> map = new HashMap<>();
            //1、获取校验的错误结果
            bindingResult.getFieldErrors().forEach((item)->{
                //FieldError 获取到错误提示
                String defaultMessage = item.getDefaultMessage();
                //获取错误的属性名称
                String field = item.getField();
                map.put(field,defaultMessage);
            });
            return  R.error(400,"提交数据不合法").put("data",map);
        }
        brandService.save(brand);
        return R.ok();
    }

6、全局处理方式

编写一个枚举类型对错误定义

package com.cy.gulimall.common.exception;

public enum BizCodeEnum {
    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败");

    private int code;
    private String msg;
    BizCodeEnum(int code,String msg){
        this.code=code;
        this.msg=msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

编写一个全局异常处理类

package com.cy.gulimall.product.exception;

import com.cy.gulimall.common.exception.BizCodeEnum;
import com.cy.gulimall.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;

/**
 * 本类用来集中处理异常
 */
@Slf4j
@RestControllerAdvice(basePackages = "com.cy.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
    /**
     *  处理数据校验异常处理类
     * @param e
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleVaildException(MethodArgumentNotValidException e){
        log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        HashMap<String, String> errorMap = new HashMap<>();
        bindingResult.getFieldErrors().forEach((fieldError -> {
            errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
        }));
        return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(),BizCodeEnum.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
    }

    /**
     * 处理其它异常类
     * @param throwable
     * @return
     */
    @ExceptionHandler(value = Throwable.class)
    public R HandleException(Throwable throwable){
        return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.VAILD_EXCEPTION.getMsg());
    }
}

编写2个分组空接口类

//第一个接口类
package com.cy.gulimall.common.valid;

public interface AddGroup {
}
//第二个接口类
package com.cy.gulimall.common.valid;

public interface UpdateGroup {
}

7、自定义校验

编写一个自定义注解类

package com.cy.gulimall.common.valid;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;


@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class})
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
    String message() default  "{com.cy.gulimall.common.valid.ListValue.message}";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
    int [] vals() default {};
}

编写一个校验类

package com.cy.gulimall.common.valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

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 constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
        return set.contains(value);
    }
}

使用此注解

	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	@ListValue(vals = {0,1},groups = {AddGroup.class})
	private Integer showStatus;
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐