微服务之电商系统
电商系统微服务整体架构一、系统环境准备系统环境:MAC系统、CentOS 7系统技术:IDEA、Maven 、Docker 、K8s、MYSQL、Redis、RabbitMQ、SpringCloud、Termius、Nginx 、Git、MyCat、ElasticSearch、Jenkins、Nacos、Sentinel、RabbonELK、LogStash、ES 、Kibana、Promethe
电商系统微服务整体架构
一、系统环境准备
系统环境: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系统安装及网络配置
四、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
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,也可以通过正则表达式和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;
更多推荐
所有评论(0)