一、项目简介
1、项目架构图
2、项目划分图
3、环境搭建
mysql连接工具sqlyog:https://sqlyog.en.softonic.com/download
1)安装linux虚拟机
下载&安装Virtualbox https://www.virtualbox.org/wiki/Downloads
注意:要开启CPU虚拟化
下载完成后双击安装的适合的位置,打开后如图
2)安装vagrant
下载地址:https://www.vagrantup.com/downloads
vagrant的镜像库:https://app.vagrantup.com/boxes/search
下载后双击安装
安装完成后在dos下输入vagrant
出现如下图的命令提示表示vagrant安装成功
dos下执行命令
vagrant的镜像库:https://app.vagrantup.com/boxes/search
注意:
1.这个命令在哪个目录下执行的,他的Vagrantfile就生成在哪里
2.centos/7 这个名称是通过镜像库中对应的名字而来。
vagrant init centos/7
并且文件加会多一个Vagrantfile文件
启动虚拟环境
vagrant up
启动后出现default folder:/cygdrive/c/User/… =>/vagrant。然后ctrl+c退出
输入 vagrant ssh 练起虚拟机
vagrant ssh
此时进入linux系统
我们想要给虚拟机一个固定的ip地址,windows和虚拟机可以互相ping通
查看windows下的虚拟机的ip地址,记住ip为56段的,linux下的设置ip时必须也是56段的。
打开VagrantFile文件
这样,我linux的IP地址为:192.168.56.10
注意:配置完成后一定要vagrant reload 重启一下,这个文件才会被从新加载,不执行这个重启电脑都没用
vagrantfile详解:https://blog.csdn.net/raoxiaoya/article/details/93638960
vagrant reload
输入vagrant init centos/7,即可初始化一个centos7系统。(注意这个命令在哪个目录下执行的,他的Vagrantfile就生成在哪里)
vagrant up启动虚拟机环境。
启动后出现default folder:/cygdrive/c/User/… =>/vagrant。然后ctrl+c退出
前面的页面中有ssh账号信息。vagrant ssh 就会连上虚拟机。可以使用exit退出
## 初始化vagrant
vagrant init centos/7
## 启动vagrant
vagrant up
## 重新加载vagrant 修改了Vagrantfile 后一定要重新加载,否则不会生效
vagrant reload
## 连接接linux 以上代码只需要执行一次,安装完成后就不需要再进行上面的错做了,只需要连接就可以
vagrant ssh
3) 安装docker
下载docker:https://www.docker.com/
安装文档:https://docs.docker.com/engine/install/centos/
docker镜像仓库:https://registry.hub.docker.com/
4)安装mysql
##mysql安装#########################################################start##
## 切换到root用户 当前root密码是:vagrant
su root
## 下载5.7版本的mysql
sudo docker pull mysql:5.7
## 创建实例并启动
## -p 3306:3306 将容器的3306端口映射到主机的3306端口
## --name指定容器名字 -v目录挂载 -p指定端口映射 -e设置mysql参数 -d后台运行
## -v /mydata/mysql/log:/var/log/mysql \ -v表示目录挂载,将日志文件夹挂在到主机,: 前面的地址是linux下的地址,:号后面的mysql容器内部的地址,挂在以后在linux下的目录中就可以修改和获取到mysql内部的数据。
## -v /mydata/mysql/data:/var/lib/mysql \ -v表示目录挂载,将库文件夹挂在到主机,: 前面的地址是linux下的地址,:号后面的mysql容器内部的地址,挂在以后在linux下的目录中就可以修改和获取到mysql内部的数据。
## -v /mydata/mysql/conf:/etc/mysql \ -v表示目录挂载,将配日志文件夹挂在到主机,: 前面的地址是linux下的地址,:号后面的mysql容器内部的地址,挂在以后在linux下的目录中就可以修改和获取到mysql内部的数据。
## -e MYSQL_ROOT_PASSWORD=root \ 初始化root用户的密码为root
## -d mysql:5.7 -d表示后台运行 mysql:5.7 这个容器,注意docker本身是个容器,儿docker里面的每个镜像也是一个独立的容器,可以通过命令进入容器内部。
sudo docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
## 查看docker正在运行中的镜像
sudo docker ps
## 进入到mysql 容器内部
## -it 表示以交互模式进入
## mysql 是容器的名称,上面设置过的也可以是容器的id输入前几位就行,通过上面sudo docker ps可以看到mysql的id
## /bin/bash 进入容器的bash控制台
sudo docker exec -it mysql /bin/bash
## 退出
exit
## 用id进入mysql容器
sudo docker exec -it dd6027481ad1 /bin/bash
exit
## 在linux下修改mysql的配置文件
cd /
cd mydata
cd mysql
cd conf
## 打开这个文件,没有则添加
vi my.conf
## 切换到插入状态
i
## 插入文件的信息
[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
## 推出插入状态
esc
## 保存并退出
:wq
## 不保存推出
:q!
## 重启mysql
docker restart mysql
## 设置docker启动后自动启动mysql
docker update mysql --restart=always
## 删除一个镜像
docker rmi mysql:latest
## 检查镜像列表中是否已经安装成功了mysql
sudo docker images
##mysql安装#########################################################end##
5)安装redis
redis的官方配置文档:https://raw.githubusercontent.com/redis/redis/6.0/redis.conf
##redis安装#########################################################start##
## 切换为root权限
su root
## 下载redis镜像 如果后面不写版本号,表示最新版本
sudo docker pull redis
# 在虚拟机中新建配置文件
mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf
## 创建实例并启动
## redis-server /etc/redis/redis.conf 通过加载后面这个配置文件来启动redis
## -v /mydata/redis/conf/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
## 设置持久化
打开配置文件
vim /mydata/redis/conf/redis.conf
# 插入下面内容 持久化
appendonly yes
# 保存并退出
# 设置redis在docker启动时启动
docker update redis --restart=always
# 直接进去redis客户端。
docker exec -it redis redis-cli
exit
## 保存,重启并保存配置
docker restart redis
##redis安装#########################################################end####
6)开发环境
IDEA(后端)
6.1 maven3.6 配置jdk和仓库
在settings中配置阿里云镜像,配置jdk1.8。这个基本都配置过
D:\Program Files\Java\apache-maven-3.6.0\conf\settings.xml
<!-- 阿里云仓库 -->
<mirror>?
???? <id>alimaven</id>?
???? <name>aliyun maven</name>?
???? <url>https://maven.aliyun.com/repository/public</url>?
???? <mirrorOf>central</mirrorOf>?
?? </mirror>
</mirrors>
<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>
6.2 安装插件
IDEA安装插件lombok,mybatisX,gitee
IDEA设置里配置好maven
6.3 本地安装git
下载git:https://git-scm.com/
安装后在任意位置右键点击》Git GUI/bash Here。去bash
# 配置用户名
git config --global user.name "username" //(名字,随意写)
# 配置邮箱
git config --global user.email "jd@qq.com" // 注册账号时使用的邮箱
# 配置ssh免密登录
ssh-keygen -t rsa -C "jd@qq.com"
三次回车后生成了密钥:公钥私钥
cat ~/.ssh/id_rsa.pub
也可以查看密钥
浏览器登录码云后,个人头像上点 设置--ssh公钥---随便填个标题---复制
# 登录码云:https://gitee.com/
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6MWhGXSKdRxr1mGPZysDrcwABMTrxc8Va2IWZyIMMRHH9Qn/wy3PN2I9144UUqg65W0CDE/thxbOdn78MygFFsIG4j0wdT9sdjmSfzQikLHFsJ02yr58V6J2zwXcW9AhIlaGr+XIlGKDUy5mXb4OF+6UMXM6HKF7rY9FYh9wL6bun9f1jV4Ydlxftb/xtV8oQXXNJbI6OoqkogPKBYcNdWzMbjJdmbq2bSQugGaPVnHEqAD74Qgkw1G7SIDTXnY55gBlFPVzjLWUu74OWFCx4pFHH6LRZOCLlMaJ9haTwT2DB/sFzOG/Js+cEExx/arJ2rvvdmTMwlv/T+6xhrMS3 553736044@qq.com
# 测试
ssh -T git@gitee.com
测试成功,就可以无密给码云推送仓库了
**vsCode(前端)**
下载vsCode用于前端管理系统。在vsCode里安装插件。
Auto Close Tag 开闭和标签用的
Auto Rename Tag
Chinese 中文包
ESlint 语法检查
HTML CSS Support
HTML Snippets
JavaScript ES6
Live Server
open in brower
Vetur
## 7)git安装
4、搭建微服务
4.1在码云上创建项目并clone下来
在码云新建仓库,仓库名gulimall,选择语言java,在.gitignore选中maven(就会忽略掉maven一些个人无需上传的配置文件),许可证选Apache-2.0,开发模型选生成/开发模型,开发时在dev分支,发布时在master分支,创建。
在IDEA中New–Project from version control–git–复制刚才项目的地址,如https://github.com/1046762075/mall
IDEA然后New Module–Spring Initializer–com.atguigu.gulimall , Artifact填 gulimall-product。Next—选择web(web开发),springcloud routing里选中openFeign(rpc调用)。
依次创建出以下服务
商品服务product
存储服务ware
订单服务order
优惠券服务coupon
用户服务member
共同点:
导入web和openFeign
group:com.atguigu.gulimall
Artifact:gulimall-XXX
每一个服务,包名com.atguigu.gulimall.XXX{product/order/ware/coupon/member}
模块名:gulimall-XXX
4.2 设置聚合服务
从微服务项目冲复制出一个pom.xml文件进行修改,最终修改成一下的结构
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall</name>
<description>聚合服务</description>
<packaging>pom</packaging>
<modules>
<module>gulimall-coupon</module>
<module>gulimall-member</module>
<module>gulimall-order</module>
<module>gulimall-ware</module>
<module>gulimall-product</module>
</modules>
</project>
maven工具中添加一个聚合maven来控制整体
`
最终效果
4.3 设置提交的过滤文件
设置聚合项目的的 .gitignore 文件
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
# 一下内容都是不提交到码云的数据
**/mvnw
**/mvnw.cmd
**/.mvn
**/target/
.idea
**/.gitignore
1)数据库初始化
https://www.fujieace.com/software/powerdesigner.html
安装powerDesigner软件。http://forspeed.onlinedown.net/down/powerdesigner1029.zip
5、搭建后台管理系统
https://gitee.com/renrenio
http://localhost:8080/renren-fast/
6、搭建vue环境
6.1 配置node.js
node历史版本:这里一定选择 10.16.3
:https://nodejs.org/zh-cn/download/releases/
安装完成后打开dos
NPM是随同NodeJS一起安装的包管理工具。JavaScript-NPM类似于java-Maven。
命令行输入node -v 检查配置好了,配置npm的镜像仓库地址,再执
## 查看版本
node -v
## 设置淘宝镜像,下载依赖更快
npm config set registry http://registry.npm.taobao.org/
6.2 在vs中运行代码
## 下载package.json 中的依赖 是要去拉取依赖(package.json类似于pom.xml的dependency)
npm install
## 运行项目
npm run dev
正常启动后
P16 npm install报错问题
视频评论区没几个说对的,个人的各种分析写到了这里:https://blog.csdn.net/hancoder/article/details/113821646
7、使用ganerator构建微服务
通过开源项目:https://gitee.com/renrenio
通过git下载:renren-generator
找到项目
任意位置右键:
git clone https://gitee.com/renrenio/renren-generator.git
打开文件夹,删除原有的git
把项目复制到我们的微服务路径下面
在ideal中添加到聚合服务中
对renren-generator中的controller模板进行修改
对人人-generator进行配置
启动renren-generator
调用:localhost 查看页面
把生成的文件放在项目中。
解压下载的文件,找到main文件夹,直接拷贝到微服务的src下面。
这样gennerator就构建构建完成了。
8、设置微服务的公共依赖
有一些公共的依赖,是所有的微服务都需要的,我们把它构建在一个公共的maven项目中,其他的微服务都要依赖这个公共的微服务。
创建公共的maven项目
在pom中依赖公共的依赖:如mysql,mybaitsplus,工具类等
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>gulimall</artifactId>
<groupId>com.atguigu.gulimall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gulimall-common</artifactId>
<description>每个微服务的公共依赖,bean,工具类等</description>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>cn.itlym.shoulder</groupId>
<artifactId>lombok</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.12</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.45</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.2-android</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
其他的微服务每个pom中都要依赖这个公共的微服务。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
9、整合mybaitsplus
mybaitspllus的官方配置:https://mp.baomidou.com/guide/quick-start.html
第一步
/**
* 整合mybaitsplus
* 1.导入依赖
* <dependency>
* <groupId>mysql</groupId>
* <artifactId>mysql-connector-java</artifactId>
* <version>8.0.17</version>
* </dependency>
* 2.配置
* 1)导入数据库驱动
* 2)在application.yml中配置数据源相关的信息
* 3.配置mybaits-plus
* 1)使用@mapperScan
* 2) 告诉mybaits-plus,sql的映射文件在哪儿
*
*/
classpath 和 classpath* 区别:
classpath 和 classpath* 区别:
classpath:只会到你的class路径中查找找文件;
classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找
classpath*的使用:当项目中有多个classpath路径,并同时加载多个classpath路径下(此种情况多数不会遇到)的文件,*就发挥了作用,如果不加*,则表示仅仅加载第一个classpath路径
mbaitsplus配置
spring:
datasource:
username: root
password: root
# url: jdbc:mysql://192.168.56.10:3306/gulimall_pms # 可能中文会乱码
url: jdbc:mysql://192.168.56.10:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.jdbc.Driver
# classpath* 表示扫描自己和所有依赖的路径
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
global-config:
db-config:
id-type: auto # 设置主键进行自增
第二步:配置分页
在config目录下定义自己的分页配置类
package com.atguigu.gulimall.product.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @Description:
* @Created: with IntelliJ IDEA.
* @author: 夏沫止水
* @createTime: 2020-05-28 18:53
**/
@Configuration
//开启事务功能
@EnableTransactionManagement
// 指定mapper接口的位置
@MapperScan("com.atguigu.gulimall.product.dao")
public class MyBatisConfig {
//引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
10、SpringCloud Alibaba简介
10.1 结合 SpringCloud Alibaba 我们最终的技术搭配方案
SpringCloud Alibaba - Nacos :注册中心(服务发现/ 注册)
SpringCloud Alibaba - Nacos :配置中心(动态配置管理)
SpringCloud - Ribbon :负载均衡
SpringCloud - Feign :声明式 HTTP 客户端(调用远程服务)
SpringCloud Alibaba - Sentinel :服务容错(限流、降级、熔断)
SpringCloud - Gateway :API 网关(webflux 编程模式)
SpringCloud - Sleuth :调用链监控
SpringCloud Alibaba - Seata原 :原 Fescar
10.2 版本的选择
springalibaba地址:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
我当卡的springboot使用的是2.4.5版本,所以,要选择的springalibaba的版本为2021.x
10.3 服务发现与注册(nacos)
第一步:下载并启动nacos服务器
nocos下载地址:https://github.com/alibaba/nacos/releases
下载完成以后
启动nacos服务。右键管理员身份运行
如果无法启动,可能是由于你的jdk不是1.8版造成的,修改一下环境变量的JAVA_HOME换成1.8 或者编辑startup.cmd文件,手动设置成1.8
启动完成后访问服务器地址:http://localhost:8848/nacos/index.html 用户名密码都是:nacos
配置依赖,在common中
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在 </dependencies> 下面添加
```bash
<!-- alibabacloud 的依赖管理用于统一管理版本号-->
<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>
第二步:在微服务中配置nacos服务器地址
```bash
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
更多配置:
https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md#more
第三步:在微服务中开启服务的注册与发现功能
@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@RestController
class EchoController {
@GetMapping(value = "/echo/{string}")
public String echo(@PathVariable String string) {
return string;
}
}
}
最终效果
10.4 Feign(远程调用)与注册中心
Feign传递对象的原理
调用远程的接口
/**
* 1、CouponFeignService.saveSpuBounds(spuBoundTo);
* 1)、@RequestBody将这个对象转为json。
* 2)、找到gulimall-coupon服务,给/coupon/spubounds/save发送请求。
* 将上一步转的json放在请求体位置,发送请求;
* 3)、对方服务收到请求。请求体里有json数据。
* (@RequestBody SpuBoundsEntity spuBounds);将请求体的json转为SpuBoundsEntity;
* 只要json数据模型是兼容的。双方服务无需使用同一个to
* @param spuBoundTo
* @return
*/
@PostMapping("/coupon/spubounds/save")
R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);
被调用的接口
/**
* 保存
*/
@PostMapping("/save")
//@RequiresPermissions("coupon:spubounds:save")
public R save(@RequestBody SpuBoundsEntity spuBounds){
spuBoundsService.save(spuBounds);
return R.ok();
}
第一步:在common中添加依赖 和 版本控制
在common中添加依赖
添加springcloud和springcloudalibaba的版本控制配置
<!-- alibabacloud 的依赖管理用于统一管理版本号-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
添加openfeign的依赖
<!--微服务之间的通信组件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:把所有的远程接口都放在feign包下面
所有的远程接口都在feign包下编写
package com.atguigu.gulimall.member.feign;
import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
// gulimall-coupon 服务的名称,要与注册中心里面的名称相对应
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
// 获取会员的而优惠卷
@RequestMapping("/coupon/coupon/member/list")
public R membercoupons();
}
第三步:开启远程调用注解
@EnableFeignClients(basePackages=“com.atguigu.gulimall.member.feign”)
package com.atguigu.gulimall.member;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 想要远程调用别的服务
* 1)、引入open-feign
* 2)、编写一个接口,告诉springCloud,把所有的远程接口都放在feign包下
* 3)、申明接口的每个方法都是调用哪个远程服务的那个接口
* 4)、开启远程调用注解
*/
//开启远程访问
@EnableFeignClients(basePackages="com.atguigu.gulimall.member.feign")
//注册到注册中心
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallMemberApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallMemberApplication.class, args);
}
}
10.5 配置中心(nacos)
配置文档:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/readme-zh.md.
第一步:在common中添加配置中心的依赖
<!--配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
第二步:在微服务的 /src/main/resources/bootstrap.properties 配置文件中配置 Nacos Config 元数据
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
第三步:在应用的 /src/main/resources/application.properties 配置文件中添加测试数据
coupon.user.name=张三
coupon.user.age = 18
第四步:添加测试接口
// 测试配置中心
@Value("${coupon.user.name}")
private String name;
@Value("${coupon.user.age}")
private Integer age;
@RequestMapping("/test")
public R test(){
return R.ok().put("name",name).put("age",age);
}
第五步:动态刷新配置
@RefreshScope
package com.atguigu.gulimall.coupon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
/**
* 如何使用配置中心管理配置
* 1)、引入依赖
* <dependency>
* <groupId>com.alibaba.cloud</groupId>
* <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
* </dependency>
* 2)、创建一个bootstrap.properties
*# 应用的名字
* spring.application.name=gulimall-coupon
* # nacos地址
* spring.cloud.nacos.config.server-addr=127.0.0.1:8848
* 3)、需要给配置中心默认添加一个数据集(Data Id)gulimall-coupon.properties 应用名.properties
* 4)、动态获取并刷新配置
* //配置刷新
* @RefreshScope
* @Value("${coupon.user.name}")
*/
//配置刷新
@RefreshScope
//nacos 注册到nacos服务器
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallCouponApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallCouponApplication.class, args);
}
}
第六步:在nacos服务器中添加配置
注意:nacos中的配置优先级高于微服务本身
最佳实战
用命名空间指定微服务,用group指定发布版本。
设置:D:\workerspace_idea_2019\gulimall\gulimall-coupon\src\main\resources\bootstrap.properties
# 应用的名字
spring.application.name=gulimall-coupon
# nacos地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
# 设置命名空间,注意时ID,命名空间指定微服务
spring.cloud.nacos.config.namespace=eb8f707c-224a-4268-b5f9-928334c52ea6
# 设置分组,指定开发测试状态Group
spring.cloud.nacos.config.group=dev
#dataID
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yaml
# 分组
spring.cloud.nacos.config.ext-config[0].group=dev
# nacos 配置文件更改后自动刷新
spring.cloud.nacos.config.ext-config[0].refresh=true
#dataID
spring.cloud.nacos.config.ext-config[1].data-id=mybaits.yml
# 分组
spring.cloud.nacos.config.ext-config[1].group=dev
# nacos 配置文件更改后自动刷新
spring.cloud.nacos.config.ext-config[1].refresh=true
#dataID
spring.cloud.nacos.config.ext-config[2].data-id=other.yml
# 分组
spring.cloud.nacos.config.ext-config[2].group=dev
# nacos 配置文件更改后自动刷新
spring.cloud.nacos.config.ext-config[2].refresh=true
在nacos服务其中,添加如下配置
配置集与组的结合应用
我们一个微服务的配置并不是在一个配置文件中的,如果有多个配置文件时我们应该这么做
比如,我把关于数据库的配置放在一个配置集中,把mybaits的放在一个配置集中,剩下的放在other中
10.6 网关gateway
官网:https://spring.io/projects/spring-cloud-gateway
官方文档:https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/
动态上下线:发送请求需要知道商品服务的地址,如果商品服务器有123服务器,1号掉线后,还得改,所以需要网关动态地管理,他能从注册中心中实时地感知某个服务上线还是下线。【先通过网关,网关路由到服务提供者】拦截:请求也要加上询问权限,看用户有没有权限访问这个请求,也需要网关。
所以我们使用spring cloud的gateway组件做网关功能。
网关是请求流量的入口,常用功能包括路由转发,权限校验,限流控制等。springcloud gateway取代了zuul网关。
三大核心概念:
Route(路由): The basic building block of the gateway. It is defined by an ID, a destination URI, a collection of predicates断言, and a collection of filters. A route is matched if the aggregate predicate is true.发一个请求给网关,网关要将请求路由到指定的服务。路由有id,目的地uri,断言的集合,匹配了断言就能到达指定位置,
Predicate(断言): This is a Java 8 Function Predicate. The input type is a Spring Framework ServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters.就是java里的断言函数,匹配请求里的任何信息,包括请求头等。根据请求头路由哪个服务
Filter(过滤): These are instances of Spring Framework GatewayFilter that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.过滤器请求和响应都可以被修改。
网关流程
客户端请求,会带着路由给网关,网关通过断言进行判断,通过后进行过滤,完成后跳转到指定的路由。
第一步:添加依赖
<!--网关的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
第二步:把网关添加到注册与发现中,由于我们的nacos注册和配置在common中,所以添加common的依赖即可
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
第三步:添加版本控制,版本控制不能从common中被依赖
注意:在common中一定不能有webstartor和mvcstartor启动器,会与gateway冲突造成gateway无法启动
<!-- alibabacloud 的依赖管理用于统一管理版本号-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
第四步:去除datasouce的报错
package com.atguigu.gulimall.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
@RefreshScope
@EnableDiscoveryClient
// 不加载数据库相关的配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class GulimallGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallGatewayApplication.class, args);
}
}
10.6.1 gateway的使用
使用文档:https://spring.io/projects/spring-cloud-gateway#learn
spring:
cloud:
nacos:
config:
namespace: 04bb1f5e-af43-4684-81c3-439fd7baae56
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes: # 路由
- id: test_route # 唯一id
uri: https://www.baidu.com # 路由地址
predicates: # 断言
- Query=url,baidu # 参数rul中包含baidu就跳转
- id: qq_route
uri: https://www.qq.com
predicates:
- Query=url,qq
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
- id: member_route
uri: lb://gulimall-member
predicates:
- Path=/api/member/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: ware_route
uri: lb://gulimall-ware
predicates:
- Path=/api/ware/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters: # 这段过滤器和验证码有关,api内容缓存了/renren-fast,还得注意/renren-fast也注册到nacos中
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
application:
name: gulimall-gateway
测试效果:
10.6.2 gateway的注意事项
- gateway的依赖不能与spring-web共存
- gateway的依赖愈能与spring-webmvc共存
- gateway的依赖不能与org.apache.tomcat.embed共存,有tomcat 依赖时会以tomcat服务器启动儿不能以netty启动
这三种情况的报错信息如下:
@RequestBody 获取请求体中的内容,只有post请求才会有请求体
11、renren-generator快速生成前后端代码
11.1 后端代码
通过renren-generator 快速生成前后端的代码
使用步骤
第一步:在appllication.yml 中配置数据库的连接`在这里插入代码片`
server:
port: 8888
# mysql
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
#MySQL配置
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.10:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
# url: jdbc:mysql://192.168.56.10:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
# url: jdbc:mysql://192.168.56.10:3306/gulimall_ums?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
# url: jdbc:mysql://192.168.56.10:3306/gulimall_oms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
# url: jdbc:mysql://192.168.56.10:3306/gulimall_wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
#oracle配置
# driverClassName: oracle.jdbc.OracleDriver
# url: jdbc:oracle:thin:@47.100.206.162:1521:xe
# username: renren
# password: 123456
#SQLServer配置
# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
# url: jdbc:sqlserver://192.168.10.10:1433;DatabaseName=renren_fast
# username: sa
# password: 123456
#PostgreSQL配置
# driverClassName: org.postgresql.Driver
# url: jdbc:postgresql://192.168.10.10:5432/renren_fast
# username: postgres
# password: 123456
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
resources:
static-locations: classpath:/static/,classpath:/views/
#mongodb:
# host: localhost
# port: 27017
# auth: false #是否使用密码验证
# username: tincery
# password: renren
# source: 123456
# database: test
mybatis-plus:
mapperLocations: classpath:mapper/**/*.xml
pagehelper:
reasonable: true
supportMethodsArguments: true
params: count=countSql
#指定数据库,可选值有【mysql、oracle、sqlserver、postgresql、mongodb】
renren:
database: mysql
第二步:在generator.properties 中配置 主要路径,包名, module名称 以及表前缀
#代码生成器,配置信息
mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
# 商品服务
moduleName=product
# 优惠服务
#moduleName=coupon
#用户服务
#moduleName=member
# 订单服务
#moduleName=order
# 存储服务
#moduleName=ware
#作者
author=maruis
#Email
email=maruis@gmail.com
#表前缀(类名不会包含表前缀)
tablePrefix=pms_
#tablePrefix=sms_
#tablePrefix=ums_
#tablePrefix=oms_
#tablePrefix=wms_
#类型转换,配置信息
tinyint=Integer
smallint=Integer
mediumint=Integer
int=Integer
integer=Integer
bigint=Long
float=Float
double=Double
decimal=BigDecimal
bit=Boolean
char=String
varchar=String
tinytext=String
text=String
mediumtext=String
longtext=String
date=Date
datetime=Date
timestamp=Date
NUMBER=Integer
INT=Integer
INTEGER=Integer
BINARY_INTEGER=Integer
LONG=String
FLOAT=Float
BINARY_FLOAT=Float
DOUBLE=Double
BINARY_DOUBLE=Double
DECIMAL=BigDecimal
CHAR=String
VARCHAR=String
VARCHAR2=String
NVARCHAR=String
NVARCHAR2=String
CLOB=String
BLOB=String
DATE=Date
DATETIME=Date
TIMESTAMP=Date
TIMESTAMP(6)=Date
int8=Long
int4=Integer
int2=Integer
numeric=BigDecimal
nvarchar=String
第三步:运行项目,把生成文件后解压放在项目适当位置即可
11.2 前端代码
位置:E:\谷粒商城\genery\prodect\main\resources\src\views\modules
根据不同的表,生成的前端的代码,把代码拷贝到相应的位置
11.3 前端按钮的权限控制
暂时去除掉权限
11.4 由于语法检查太严格vscode报错的解决
12、微服务的文件上传
由于有负载均衡,需要把文件放在一个统一的地方,有两种方式
1.自己搭建服务器:如FastDFS 或 vs ftpd
特点:搭建复杂,维护成本高,前期费用高
2.云存储,如阿里云对象存储,七牛云存储
特点:即开即用,无需维护,按批收费
使用案例云对象存储使用文档:
https://help.aliyun.com/document_detail/31925.html?spm=a2c4g.11174283.6.1731.e0547da2smGWE2
最佳实战:我们把文件存储到云服务器上,上传文件通过前端访问我们自己的服务,拿到云服务器的令牌,前端带着令牌直接把文件上传到云服务器,这样可以很好的解决我们服务器对于文件存储的压力。
原理:
第一步:在阿里云上开通,对象存储oss ,并创建bucket
第二步:在阿里云上对oss进行跨域设置,否则无法上传,在概览》基础设置 中找到 跨域访问 进行设置
第三步:在阿里云服务器通过子账号设置令牌
第四步:在java后台编写接口用于给客户端返回令牌
我们把所有的第三方的服务单独的放在一个微服务中
1)pom中添加依赖
<!--阿里云oss对象存储-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alicloud-oss</artifactId>
</dependency>
2)配置文件中添加配置
spring:
cloud:
alicloud:
access-key: "LTAI5tLZWW3zqJDyerm"
secret-key: "D8Vqn9nMQcuZFYBeOp"
oss:
endpoint: "oss-cn-beijing.aliyuncs.com"
bucket: "gulimall-2010" # 自定义的
3)写一个接口用于生成令牌
package com.atguigu.gulimall.thirdparty.controller;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.atguigu.common.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description:
* @Created: with IntelliJ IDEA.
* @createTime: 2020-05-27 15:56
**/
@RestController
public class OssController {
@Autowired
OSS ossClient;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.access-key}")
private String accessKeyId;
@Value("${spring.cloud.alicloud.secret-key}")
private String accessKeySecret;
@Value("${spring.cloud.alicloud.bucket}")
private String bucket = "gulimall-20210530";
/**
* * 获取签名令牌
*/
@RequestMapping("/oss/policy")
public R policy() {
//https://gulimall-clouds.oss-cn-beijing.aliyuncs.com/iqiyi.png
String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
//String callbackUrl = "http://88.88.88.88:8888";
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format ; // 用户上传文件时指定的前缀。
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
Map<String, String> respMap = null;
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessKeyId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
ossClient.shutdown();
}
return R.ok().put("data",respMap);
}
}
第五步:前台带这令牌上传文件请求
a) 添加element-ui 上传组件
:before-upload 上传文件之前调用的方法,在这个方法中获取后台服务的令牌
:data=“dataObj” 包含令牌中的所有数据
:on-success=“handleUploadSuccess” 上传成功后的回调
<el-upload
action="http://gulimall-20210530.oss-cn-beijing.aliyuncs.com"
:data="dataObj"
list-type="picture"
:multiple="false" :show-file-list="showFileList"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10MB</div>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="fileList[0].url" alt="">
</el-dialog>
data() {
return {
dataObj: {
policy: '',
signature: '',
key: '',
ossaccessKeyId: '',
dir: '',
host: '',
// callback:'',
},
dialogVisible: false
};
},
beforeUpload(file) {
let _self = this;
return new Promise((resolve, reject) => {
policy().then(response => {
_self.dataObj.policy = response.data.policy;
_self.dataObj.signature = response.data.signature;
_self.dataObj.ossaccessKeyId = response.data.accessid;
_self.dataObj.key = response.data.dir + '/'+getUUID()+'_${filename}';
_self.dataObj.dir = response.data.dir;
_self.dataObj.host = response.data.host;
console.log("data-->",response.data)
resolve(true)
}).catch(err => {
reject(false)
})
})
},
在这个方法中,把上传后的文件路径保存在了fileList 了,前台保存时,调用我们自己的服务保存到后台。
handleUploadSuccess(res, file) {
console.log("上传成功...")
this.showFileList = true;
this.fileList.pop();
this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) });
this.emitInput(this.fileList[0].url);
}
<single-upload v-model="dataForm.logo"></single-upload>
13、后台数据校验JSR303
@NotEmpty 用在集合类上面
加了@NotEmpty的String类、Collection、Map、数组,是不能为null或者长度为0的(String Collection Map的isEmpty()方法)
@NotBlank只用于String,不能为null且trim()之后size>0
@NotNull:不能为null,但可以为empty,没有Size的约束
java规范体验第303号
- 3、JSR303
- 1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
- 2)、开启校验功能@Valid
-
效果:校验错误以后会有默认的响应;
- 3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
- 4)、分组校验(多场景的复杂校验)
-
1)、 @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
-
给校验注解标注什么情况需要进行校验
-
2)、@Validated({AddGroup.class})
-
3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;
- 5)、自定义校验
-
1)、编写一个自定义的校验注解
-
2)、编写一个自定义的校验器 ConstraintValidator
-
3)、关联自定义的校验器和自定义的校验注解
-
@Documented
- @Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器,适配不同类型的校验】 })
- @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
- @Retention(RUNTIME)
- public @interface ListValue {
第一步:给bean加上javax.validation.constraints下的校验注解
@NotBlank(message = "品牌名不能为空")
private String name;
/**
* 检索首字母,
* 利用正则表达式进行验证
*/
@Pattern(regexp = "/^a-zA-Z$/",message = "首字母必须是a到z或者A到Z的字母")
private String firstLetter;
/**
* 排序
*/
@Min(value = 0,message = "排序必须大于0")
private Integer sort;
第二步:开启校验 @Validated
/**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated @RequestBody BrandEntity brand, BindingResult bindingResult){
// 在校验对象,紧跟着一个参数bindingResult,用于获取校验的信息
if(bindingResult.hasErrors()){
Map<String,String>map = new HashMap<>();
bindingResult.getFieldErrors().forEach((item)->{
// 校验的错误信息
String message = item.getDefaultMessage();
// 校验的字段名称
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}else{
brandService.save(brand);
return R.ok();
}
}
第三步:分组校验
1)、 @NotBlank(message = “品牌名必须提交”,groups = {AddGroup.class,UpdateGroup.class})
-
给校验注解标注什么情况需要进行校验
-
2)、@Validated({AddGroup.class})
-
3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;
/**
* 品牌id
*/
@NotNull(message = "修改是必须指定ID",groups = {UpdateGroup.class})
@Null(message = "添加是不需要指定ID",groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名不能为空",groups = {UpdateGroup.class,AddGroup.class})
private String name;
@Validated(AddGroup.class)
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand /* , BindingResult bindingResult */)
第四步:自定义校验规则
自定义的校验规则我们放在common下
1) 、添加依赖
2)、编写一个自定义的校验注解
3)、编写一个自定义的校验器 ConstraintValidator
4)、关联自定义的校验器和自定义的校验注解
添加依赖
<!--用于校验-->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.2</version>
<scope>compile</scope>
</dependency>
编写一个自定义的校验注解
/**
* @Description: 自定义注解规则
* @Created: with IntelliJ IDEA.
* @author: 夏沫止水
* @createTime: 2020-05-27 17:48
**/
@Documented
// 指定校验器
@Constraint(validatedBy = { ListValueConstraintValidator.class })
// 注解可以标注的位置
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
// 运行时校验
@Retention(RUNTIME)
public @interface ListValue {
// 默认提示的错误信息会从ValidationMessages.properties 文件下查找com.atguigu.common.valid.ListValue.message 信息进行输出
// alidationMessages.properties 也可以在resources去自定义
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
自定义自己的错误提示配置文件ValidationMessages.properties,放在resources下
com.atguigu.common.valid.ListValue.message=必须提交指定的值
编写一个自定义的校验器 ConstraintValidator
package com.atguigu.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* ConstraintValidator<ListValue,Integer>
* 第一个参数:指定注解
* 第二个参数:指定什么类型的数据
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();
/**
* 初始化方法
* @param constraintAnnotation
*/
@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) {
//判断是否有包含的值
boolean contains = set.contains(value);
return contains;
}
}
关联自定义的校验器和自定义的校验注解
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals = {0,1})
private Integer showStatus;
14、统一的异常处理
第一步:自己的为服务下新建一个exception包,用于存放专门进行异步处理的controller
第二部:在common中新建一个exception包,存放所有的状态码和信息
错误码的规则用五位数,前两位表微服务,后3位标具体错误西南西
package com.atguigu.common.exception;
/**
* @Description: 错误状态码枚举
* @Created: with IntelliJ IDEA.
* @createTime: 2020-05-27 17:29
*
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 002:短信验证码频率太高
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
* 15:用户
*
*
*
**/
public enum BizCodeEnum {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败"),
TO_MANY_REQUEST(10002,"请求流量过大,请稍后再试"),
SMS_CODE_EXCEPTION(10002,"验证码获取频率太高,请稍后再试"),
PRODUCT_UP_EXCEPTION(11000,"商品上架异常"),
USER_EXIST_EXCEPTION(15001,"存在相同的用户"),
PHONE_EXIST_EXCEPTION(15002,"存在相同的手机号"),
NO_STOCK_EXCEPTION(21000,"商品库存不足"),
LOGINACCT_PASSWORD_EXCEPTION(15003,"账号或密码错误"),
;
private Integer code;
private String message;
BizCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
第三步:在第一步中的controller中利用两个注解完成统一的已处理逻辑@RestControllerAdvice,
package com.atguigu.gulimall.product.exception;
import com.atguigu.common.exception.BizCodeEnum;
import com.atguigu.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;
import java.util.Map;
/**
* User: maruis
* Date: 2021/6/4 20:31
* Description:
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall")
public class GulimallExceptionControllerAdvice {
// 用于捕捉数据校验的异常
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handlerVaildException(MethodArgumentNotValidException e){
BindingResult result = e.getBindingResult();
Map<String,String> map = new HashMap<>();
result.getFieldErrors().forEach((item)->{
map.put(item.getField(),item.getDefaultMessage());
});
e.printStackTrace();
return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(),BizCodeEnum.VAILD_EXCEPTION.getMessage()).put("data",map);
}
// 用于捕捉所有异常
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handlerException(Throwable e){
log.error("异常发生的类{}------>异常信息{}",e.getClass(),e.getMessage());
e.printStackTrace();
return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.UNKNOW_EXCEPTION.getMessage());
}
}
注意
idea更新至2020.3之后打开以前的项目,发现测试类莫名奇妙没有启动按钮了,方法名上还有黄色的警告。
解决方法,在测试类和测试方法加上public关键字即可。
java entity中的正则表达式写法与js不同
@Pattern(regexp = "^[a-zA-Z]+$",message = "首字母必须是a到z或者A到Z的字母")
private String firstLetter;
使用案例云对象存储使用文档:
https://help.aliyun.com/document_detail/31925.html?spm=a2c4g.11174283.6.1731.e0547da2smGWE2
JSR303 java规范体验303号
给需要校验的数据加上校验注解,数据校验不通过时会返回400
,步骤
第一步:给需要校验的数据加上校验注解,在import javax.validation 包中
如:@Email @NotBlank(message = "品牌名必须提交") @NotEmpty
第二步:在controller中添加校验注解的开关,只有添加了@Valid 才会进行校验
如:public R save(@Valid @RequestBody BrandEntity brand)
package com.atguigu.gulimall.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
import org.apache.ibatis.annotations.Param;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
/**
* 品牌
*
* @author maruis
* @email maruis@gmail.com
* @date 2021-05-22 19:42:32
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交")
private String name;
/**
* 品牌logo地址
*/
@URL(message = "必须是一个合法的url地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
// 自定义的校验规则
@Pattern(regexp = "/^[a-zA-Z]]$/",message = "检索首字母必须时一个字母")
private String firstLetter;
/**
* 排序
*/
private Integer sort;
}
/**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
if (result.hasErrors()){
Map<String,Object> map = new HashMap<>();
result.getFieldErrors().forEach((item)->{
// 获取错误的提示信息
String message = item.getDefaultMessage();
// 获取错误字段的名字
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}
brandService.save(brand);
return R.ok();
}
统一的异常处理
使用springmvc为我们提供的ControllerAdvice
15、mybaitsplus中配置逻辑删除
第一步:在配置文件中进行全局配置状态标志,这个全局的逻辑删除规则也可以不设置
# classpath* 表示扫描自己和所有依赖的路径
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
global-config:
db-config:
id-type: auto # 设置主键进行自增
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
第二步:给bean上加上逻辑删除注解,这个中的配置规则比全局配置的规则优先级高
/**
* 是否显示[0-不显示,1显示]
*/
@TableLogic(value = "1",delval = "0")
private Integer showStatus;