目录
- 一、环境配置
- 1 Docker
- 1.1 Docker是什么?
- 1.2 安装&启动
- 1.2.1 阿里云镜像加速
- 1.3 安装MySQL
- 1.4 安装Redis
- 2 开发环境
- 2.1 Maven
- 2.2 Git
- 2.3 Node
- 二、创建微服务项目
- 1 内容
- 2 问题记录
- 3 renren-generator
- 三、分布式组件
- 1 Nacos
- 1.1 注册中心
- 1.2 配置中心
- 1.2.1 如何使用Nacos作为配置中心统一管理配置?
- 1.2.2 细节
- 1.2.3 同时加载多个配置集
- 2 Feign
- 1.1 使用场景
- 1.2 调用其他服务的Application
- 3 Gateway
- 4 Aliyun OSS
- 四、前端开发基础
- 1 ES6
- 1.1 let和var
- 1.2 解构表达式
- 1.3 字符串扩展
- 1.4 函数优化
- 1.5 对象优化
- 1.6 对象扩展运算符
- 1.7 map和reduce
- 1.8 promise
- 1.9 模块化
- 1.9.1 导入导出
- 1.9.2 简化导出
- 1.9.3 不命名导出
- 2 Vue
- 2.1 基础属性
- 2.2 计算属性,监听器,过滤器
- 2.3 组件化
- 五、业务处理
- 1 逻辑删除
- 2 JSR303校验
- 2.1 开启校验
- 2.2 使用ControllerAdvice来集中处理异常
- 2.3 分组校验
- 2.4 自定义校验
- 3 POJO和SKU
- 3.1 电商领域名词
- 3.2 Object分类
- 4 其他
- 六、总结
- 1 分布式基础概念
- 2 基础开发
- 3 环境
- 4 开发规范
一、环境配置
1 Docker
1.1 Docker是什么?
基于镜像启动各种容器,每一种容器都是一个完整的运行环境,容器之间互相隔离。
1.2 安装&启动
docker官网:Developers -> Docs -> Get Docker -> Docker For Linux
systemctl start docker
docker images
查看安装的全部镜像
systemctl enable docker
开机自启
1.2.1 阿里云镜像加速
阿里云官网 -> 登录 -> 控制台 -> 容器镜像服务 -> 镜像工具 -> 镜像加速器 -> CentOS
1.3 安装MySQL
docker pull mysql:5.7
docker run -p 3306:3306 --name mysql \\ # 主机3306映射到容器3306 --name起名字
-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用户密码
-d mysql:5.7 # 后台运行
下面供复制使用:
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/conf.d -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
docker ps
查看现在运行的全部镜像
docker exec -it mysql /bin/bash
进入到容器内部
whereis mysql
在容器内部执行来查看mysql位置
修改/mydata/mysql/conf下mysql配置
[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 restart mysql
1.4 安装Redis
docker pull redis
不写版本下载最新版本
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
在/mydata/redis/conf/redis.conf中添加
appendonly yes
开启持久化
虚拟机重启后自动启动镜像
docker update redis --restart=always
2 开发环境
2.1 Maven
问题1 依赖子模块的模块打包失败
解决 给上层(父)模块install后成功
2.2 Git
配置git
# 配置用户名
git config --global user.name "username" # 名字
# 配置邮箱
git config --global user.email "username@email.com" # 注册账号时使用的邮箱
在码云中进入设置 -> SSH公钥 -> 将本机公钥粘贴进去
实现后续免密提交
2.3 Node
npm配置镜像仓库:
npm config set register <http://registry.npm.taobao.org/>
install后使用npm run dev
运行
问题: npm安装失败。
解决 1. node使用10.X版本,python版本改3.7(非必要) 2. 网络问题 3. npm install遇到循环c++提示等待即可
二、创建微服务项目
1 内容
商品服务、仓储服务、订单服务、优惠券服务、用户服务
共同:
- web openfeign
- 每一个服务,包名
com.atguigu.gulimall.xxx(product/order/ware/coupon/member)
- 模块名:
gulimall-coupon
2 问题记录
问题1 报错 POM文件爆红,以及
<repository>
中的网址禁止访问
解决 Maven版本问题,3.6.3解决
问题2 报错如下
'parent.relativePath' of POM io.renren:renren-fast:3.0.0 (/Users/dujianzhang/Documents/IdeaProjects/gulimall/renren-fast/pom.xml) points at pers.djz.gulimall:gulimall instead of org.springframework.boot:spring-boot-starter-parent, please verify your project structure```
解决 子模块没有指向上级目录父模块,在
<parent>
标签中添加<relativePath/>
使Maven打包时每次都直接从仓库获取
问题3 报错如下
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)at java.lang.reflect.Constructor.newInstance(Constructor.java:423)at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
解决 尝试与MySQL建立SSL连接失败,在URL后面加上
?useSSL=false
3 renren-generator
使用方法
- 修改application.yml中的dataSource相关信息
- 修改generator.properties中生成项目目录结构信息
- 运行Application,在浏览器中前往生成器页面
- 勾选要进行逆向工程的数据库表,点击生成代码开始下载
问题1 idea不显示yaml小树叶
解决 备份编辑器设置,恢复编译器默认设置,重新导入备份设置不恢复file相关设置
问题2 Longblob无法解析
解决 替换成byte类型
问题3 renren generator实体类备注乱码
解决 使用navicat执行sql不要导入sql文件,复制sql文本执行查询
三、分布式组件
1 Nacos
1.1 注册中心
启动方式: ./startup.sh -m standalone
在application.yml中配置nacos注册中心的信息:
spring: cloud: nacos: discovery: server-addr: localhost:8848#一定要有应用名 application: name: gulimall-coupon
问题1 nacos在macOS上找不到
/Library/Internet
或Permission denied
解决 mac自带了一个jdk,在环境变量中声明自己安装的jdk的JAVA_HOME
export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home" export PATH="$PATH:$JAVA_HOME/bin" export CLASSPATH="$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:."
问题2 macOS上7000端口占用问题
解决 7000端口用于AirPlay,使用7001
1.2 配置中心
1.2.1 如何使用Nacos作为配置中心统一管理配置?
- 引入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
- 创建一个bootstrap.properties
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=localhost:8848
- 在配置中心添加一个默认的数据集(Data Id) gulimall-coupon.properties,默认规则:
应用名.properties
- 给应用名.properties添加任何配置
- 动态获取配置
@RefreshScope
动态获取并刷新配置,注解在Application类上
@Value("${配置项的名}")
获取到配置
如果配置中心和当前应用的配置文件中都配置了相同的项,优先使用配置中心的配置
1.2.2 细节
- 命名空间:配置隔离
默认:public(保留空间),默认新增的所有配置都在public空间- 开发、测试、生产,利用命名空间来做环境隔离
注意:在bootstrap.properties配置上,需要使用哪个命名空间下的配置
spring.cloud.nacos.config.namespace=aa34a377-e984-4082-8959-e41e35dd9d51
- 每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置
- 开发、测试、生产,利用命名空间来做环境隔离
- 配置集:所有的配置的集合
- 配置集ID:类似文件名
Data ID:类似文件名 - 配置分组
默认所有的配置集都属于DEFAULT_GROUP
spring.cloud.nacos.config.group=1111
每个微服务创建自己的命名空间,使用配置分组区分环境,dev, test, prod
1.2.3 同时加载多个配置集
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true
yaml写法
spring:cloud:nacos:ext-config:- data-id: oss.ymlgroup: DEFAULT_GROUPrefresh: true- data-id: ...group: ...refresh: ...
- 微服务任何配置信息,任何配置文件都可以放在配置中心中
- 只需要在bootstrap.properties中说明加载配置中心中哪些配置文件即可
@Value
@ConfigurationProperties
以前SpringBoot任何方法从配置文件中获取值都能使用,配置中心有的优先使用配置中心的
问题1 配置了bootstrap.properties中的nacos config服务器地址,依然报错endpoint is blank
解决 没有读取配置文件,springboot 2.4以上要在pom中添加依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> <version>3.1.0</version> </dependency>
或者是没有在resources下创建bootstrap配置文件指明配置中心地址
2 Feign
1.1 使用场景
- member想要调用coupon中的信息
- member向nacos注册中心查询目标服务地址
- 查询
1.2 调用其他服务的Application
/** 想要远程调用别的服务* 1 引入openfeign* 2 编写一个接口,告诉SpringCloud这个接口需要调用远程服务* 2.1 声明接口的每一个方法都是调用哪个远程服务的哪个请求* 3 开启远程调用功能* */
@EnableFeignClients(basePackages = "pers.djz.gulimall.member.feign")
@EnableDiscoveryClient
@SpringBootApplicationpublic
class GulimallMemberApplication { public static void main(String[] args) { SpringApplication.run(GulimallMemberApplication.class, args); }
}
feign包下的接口:
// name是nacos注册中心中要访问的微服务名称,url可不写
@FeignClient(name = "gulimall-coupon", url = "http://localhost:7001/")
public interface CouponFeignService { // 方法名不必相同,但访问url要对应上@PostMapping("/coupon/skufullreduction/saveInfo")R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}
@RequestBody
将这个对象转为json- 找到gulimall-coupon服务,给
/coupon/spubounds/save
发送请求。将上一步转的json放在请求体位置,发送请求 - 对方服务收到请求。请求体里有JSON数据,
@RequestBody SpuBoundsEntity spuBounds
将请求体的JSON转为SpuBoundsEntity
只要JSON数据模型是兼容的,双方服务无需使用同一个VO
问题 运行后feign报错ribbon和loadbalance问题
解决 pom中如下引用<dependency><groupId>com.octopus.gulimall</groupId><artifactId>common</artifactId><version>0.0.1-SNAPSHOT</version><exclusions><exclusion><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId></exclusion></exclusions> </dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
3 Gateway
spring:cloud:gateway:routes:- id: qq_route# uri是符合断言的请求会跳转到的地址uri: https://www.qq.com/predicates:- Query=url, qq- id: baidu_routeuri: https://www.weibo.com/predicates:- Query=url, weibo
问题1 启动网关application报错:
Description:Parameter 0 of method loadBalancerWebClientBuilderBeanPostProcessor in org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration required a bean of type 'org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction' that could not be found.Action:Consider defining a bean of type 'org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction' in your configuration.Process finished with exit code 1
解决 添加依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> <version>3.1.1</version> </dependency>
问题2 通过
lb://renren-fast
进行网关控制报503错误,提示No servers available for service: renren-fast
解决 SpringCloud Alibaba和SpringBoot版本问题,改为2.1.8.RELEASE
和Greenwich.SR3
4 Aliyun OSS
- 在Aliyun官网使用对象存储服务,创建一个新的bucket存储文件
- 使用公共读
- 右上角AccessKey创建子账户供Open API调用
- Java导入starter后,自动注入OssClient类对象(新版本使用OSS类)
- starter导入后在application.yaml中配置endpoint、access-key和secret-key
- 实际开发中客户端向服务器请求签名后直接带着签名上传到OSS服务器(使用方法参考文档)
spring:cloud:alicloud:access-key: xxxsecret-key: xxxoss:endpoint: oss-cn-beijing.aliyuncs.combucket: xxx
@RestController
@RequestMapping("/oss")
public class OssController {@Autowiredprivate OSS ossClient;@Value("${spring.cloud.alicloud.oss.endpoint}")private String endpoint;@Value("${spring.cloud.alicloud.oss.bucket}")private String bucket;@Value("${spring.cloud.alicloud.access-key}")private String accessId;@RequestMapping("/policy")public R policy() {// 填写Host地址,格式为https://bucketname.endpoint。String host = "https://" + bucket + "." + endpoint;// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
// String callbackUrl = "https://192.168.0.0:8888";// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());String dir = format + "/";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", accessId);respMap.put("policy", encodedPolicy);respMap.put("signature", postSignature);respMap.put("dir", dir);respMap.put("host", host);respMap.put("expire", String.valueOf(expireEndTime / 1000));// respMap.put("expire", formatISO8601Date(expiration));} catch (Exception e) {// Assert.fail(e.getMessage());System.out.println(e.getMessage());}return R.ok().put("data", respMap);}
}
四、前端开发基础
1 ES6
ECMAScript是浏览器脚本语言的规范
1.1 let和var
- let有严格的作用域,var会越域
- let不能重复声明同名变量,var可以
- let不存在变量提升,var会 变量提升:声明会提升到作用域顶端
function fun() {a = 1;var a;
}// 实际执行 var a; -> a = 1;
1.2 解构表达式
const [a, b, c] = [1, 2, 3];// a = 1, b = 2, c = 3
let person = {name: "jack", age: 18};
const {name, age} = person;// name = "jack", age = "age"
1.3 字符串扩展
let str = "hello.vue";
str.startsWith("hello");
// truestr.endsWith("vue");
// truestr.includes("e"); // true
// 字符串模版(多行字符串)
let ss = `<div> <span>hello</span></div>`
// 插入表达式(用``)
let info = `我是${abc}, 今年${age}了, 我想说: ${fun()}`;
1.4 函数优化
b = b || 1; // 判断b是否为空,如空给默认值1
function fun(a, b=1) { ...}
// 不定参数
function fun(...values) {console.log(values.length)
}
// 箭头函数
var print = obj => console.log(obj);
// print是一个函数
var sum = (a, b) => return a + b;
var sum2 = (a, b) => {let c = a + b; return a + c;
}
// 箭头函数+解构
var fun = ({name}) => console.log(name);
fun(person);
1.5 对象优化
const person = {name: "jack", age: 18};
Object.keys(person);
Object.values(person);
Object.entries(person);
// 合并
const target = {a: 1};
const source1 = {b: 2};
const source2 = {c: 3};
Object.assign(target, source1, source2);
// 声明对象简写
const age = 23;
const name = "张三";
const person = {age, name};
// 变量和属性同名可以简写
// 对象的函数属性简写
let person = {name: "jack",eat: function (food) {console.log(this.name + "在吃" + food);},// 箭头函数this不能使用,使用对象.属性来获取eat2: food => console.log(this.name + "在吃" + food);
}
person.eat("banana");
1.6 对象扩展运算符
// 拷贝对象
let p1 = {name: "jack", age: 18};
let someone = { ...p1 }
// 合并对象
let age1 = {age: 15};
let name1 = {name: "Amy"};
let p2 = {name: "zhangsan"};
p2 = {...age1, ...name1}; // p2.name = "Amy"
1.7 map和reduce
let arr = ['1', '20', '-5'];
arr = arr.map((item) => {return item * 2;
});
arr = arr.map(item => item * 2);
/**
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
1、previousValue(上一次调用回调返回的值,或者是提供的初始值(initialvalue))
2、currentvalue (数组中当前被处理的元素)
3、index(当前元素在数组中的索引)
4、array(调用 reduce 的数组)
*/
let result = arr.reduce((a, b) => {console.log(a, b);return a + b;
}, 100); // 100 1 ...
1.8 promise
Promise可以封装异步操作
function get(url, data) {return new Promise((resolve, reject) => {$.ajax({url: url,data: data,success: function (data) {resolve(data);},error: function (err) {reject(err);}});});
}
// 多级分类查询
get("mock/user.json").then((data) => {console.log("用户查询成功: ", data);return get(`mock/user_course_${data.id}.json`);
}).then((data) => {console.log("课程查询成功: ", data);return get(`mock/course_score_${data.id}.json`);
}).then((data) => {console.log("课程成绩查询成功", data);
}).catch((err) => {console.log("出现异常", err);
});
1.9 模块化
1.9.1 导入导出
util.js
var name = "jack";var age = 21;
function add(a, b) {return a + b;
}
export {name, age, add}
main.js
import {name, age, add} from "./util.js"
1.9.2 简化导出
export const util = ...
1.9.3 不命名导出
xx.js
export default {sum(a, b) {return a + b;}
}
// 随意命名
import abc from "./xx.js"
2 Vue
问题1 eslint语法检查过于严格
解决 在.eslintignore文件中添加要忽略的内容,如*.vue
<body><div id="app"><input type="text" v-model="num"/><button v-on:click="num++">👍</button><button v-on:click="cancel">👎</button><h1>hello {{ name }}! 有{{ num }}个人为你点赞</h1></div><script src="./node_modules/vue/dist/vue.js"></script><script>// 1 声明式渲染let vm = new Vue({el: "#app", // 绑定元素data: { // 封装数据name: "zhangsan",num: 1},methods: { // 封装方法cancel() {this.num--;}}})// 2 双向绑定,模型变化,视图变化。反之亦然// 3 事件处理// v-xx指令// 1 创建vue实例,关联页面的模版,将我们的数据data渲染到关联的模版,响应式的// 2 指令来简化对dom的一些操作// 3 声明方法来做更复杂的操作</script>
</body>
2.1 基础属性
v-text
v-html
v-bind:xxx
单向绑定(数据修改视图) e.g.v-bind:href
==:href
对于class、style:
<span v-bind:class="{active:isActive,'text-danger':hasError}" v-bind:style="{color: color1;'font-size': size;}">你好</span>
<!--连字符不合法,使用''或者驼峰命名如fontSize-->
<script>...data:{isActive:true,hasError:true,color1:'red',size:'36px'}
</script>
v-model=
双向绑定v-on
绑定事件v-on:click=
==@click
事件修饰符 阻止事件向上冒泡@click.stop
阻止默认行为@click.prevent
@click.prevent.stop
@click.once
@keyup.down
键盘↓ 5. v-for
遍历的时候加上:key来区分不同数据(要求key不重复),提高vue渲染效率
<li v-for="(user,index) in users" :key="user.name">{{ index }} -> {{ user }}<span v-for="(k,v,i) in user">{{ k }} -> {{ v }}</span>
<li>
v-if
和v-show
v-if="false"
直接删除dom元素v-show="false"
使用display: none
还可以与v-if
搭配v-else-if
和v-else
示例:
<button @click="isShow = !isShow">button</button>
<span v-if="isShow">span</span>
与v-for
搭配:
<li v-for="(user,index) in users" :key="user.name" v-if="user.gender == '女'">
2.2 计算属性,监听器,过滤器
- computed的函数在使用到的(data属性中的)变量发生改变后再次执行进行更新
- watch可以让我们监控一个值的变化,从而做出相应的反应
<li>总价:{{ totalPrice }}</li>
<li v-for="user in users">{{user.gender == 1 ? "男" : "女"}} 等同于过滤器 {{user.gender | genderFilter}}
</li>
<script>new Vue({el: "#app",data: {price: 20.00,num: 10},computed: {totalPrice() {return this.price * num;}},watch: {num: function(oldVal, newVal) {if (newVal > 3) {alert("超过3了");}}},filters: {genderFilter(val) {if (val == 1) {return "男";} else {return "女";}}}});
</script>
全局过滤器
Vue.filter("gFilter", function() { ...})
使用:
{{ user.gender | gFilter }}
2.3 组件化
<counter></counter>
<div id="app"><button-counter></button-counter>
</div>
<script>// 1. 全局声明注册一个组件Vue.component("counter", {template: `<button @click="count++">我被点击了 {{count}} 次</button>`,data() {return {count: 1}}});// 2. 局部声明一个组件const buttonCounter = {template: `<button @click="count++">我被点击了 {{count}} 次</button>`,data() {return {count: 1}}};new Vue({el: "#app",data: {count: 1},components: {'button-counter': buttonCounter}});
</script>
五、业务处理
1 逻辑删除
一般删除记录通过设置标志位进行逻辑删除,而不是真正的物理删除
使用MybatisPlus实现逻辑删除的步骤
- 配置全局逻辑删除规则(可省略)
mybatis-plus:global-config:db-config:logic-delete-value: 1logic-not-delete-value: 0
- 配置逻辑删除的组件Bean(过时)
- 给Bean的标志位属性加上逻辑删除注解
@TableLogic
数据库中对应字段值不能为空才能进行逻辑删除,否则删除后标志位仍然为空
实际执行:
UPDATE pms_category SET show_status=0 WHERE cat_id IN ( ? ) AND show_status=1
@TableLogic(value = "1", delval = "0")
private Integer showStatus;
2 JSR303校验
2.1 开启校验
- 给Bean的属性上方添加校验注解:
javax.validation.constraints
@NotNull
@NotBlank(message = "自定义提示信息")
- 开启校验功能,在controller方法参数中的要校验的Bean前添加
@Valid
效果:校验错误以后会有默认的响应 - 给校验的参数Bean后紧跟一个BindingResult,就可以获取到校验的结果
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result) {if (result.hasErrors()) {Map<String, String> map = new HashMap<>();result.getFieldErrors().forEach((item) -> {// FieldError获取到错误提示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();
}
常用注解
@Null
限制只能为null
@NotNull
限制必须不为null
@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之间
@Past
验证注解的元素值(日期类型)比当前时间早
@NotEmpty
验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank
验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty
,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
2.2 使用ControllerAdvice来集中处理异常
@Slf4j
@RestControllerAdvice(basePackages = "com.octopus.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {@ExceptionHandler(MethodArgumentNotValidException.class)public R handleValidException(MethodArgumentNotValidException e) {log.error("数据校验出现问题{}, 异常类型:{}", e.getMessage(), e.getClass());BindingResult bindingResult = e.getBindingResult();Map<String, String> errorMap = new HashMap<>();bindingResult.getFieldErrors().forEach(fieldError -> {errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());});return R.error(400, "数据校验出现问题").put("data", errorMap);}
}
2.3 分组校验
@NotBlank(message = "品牌名不能为空", groups = {AddGroup.class, UpdateGroup.class})
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand/* , BindingResult result */)
默认没有指定分组的校验注解
@NotBlank
,在分组校验情况下不生效,只会在@Validated
下生效
2.4 自定义校验
编写一个自定义的校验注解 - 编写一个自定义的校验, 实现ConstraintValidator的两个方法(初始化和校验方法),并添加到注解.java文件上面validatedBy
中
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);
}
- 关联自定义的校验器和自定义的校验注解
- 默认message可以读取类路径下ValidationMessages.properties中的内容
@Documented
// 可以指定多个不同的校验器,适配不同类型的校验,取决于父类ConstraintValidator的第二个范型
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {String message() default "{com.octopus.common.valid.ListValue.message}";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };int[] vals();
}
问题1
@Pattern
正则正确匹配也报错解决 regexp不要加前后
/
问题2 高版本SpringBoot(2.3之后)没有效果
解决 引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId> </dependency>
问题3 校验不通过后返回的响应为503且不含错误原因
解决 在yaml中配置如下,这样返回400错误并包含错误原因server:port: 10000error:include-binding-errors: always
问题4 ValidationMessages.properties 配置文件中的中文错误信息导致乱码问题
解决 添加配置类@Configuration public class ValidationConfig {@Beanpublic Validator validator() {LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();factoryBean.setMessageInterpolator(interpolatorFactory.getObject());ResourceBundleMessageSource source = new ResourceBundleMessageSource();source.setDefaultEncoding("utf-8");source.setCacheMillis(-1);// 文件名前缀source.setBasename("ValidationMessages");factoryBean.setValidationMessageSource(source);return factoryBean;} }
3 POJO和SKU
3.1 电商领域名词
- SPU(Standard Product Unit 标准化产品单元):
iPhone 13
决定规格参数 - SKU(Stock Keeping Unit 库存量单位):
iPhone 13 512GB 白色
决定销售属性
用面向对象作类比,SPU是类,SKU是对象
3.2 Object分类
PO(persist object) 持久对象 | 对应数据库中的一条记录 |
---|---|
DO(Domain Object) 领域对象 | 从现实世界抽象出来的有形或无形的业务实体 |
TO(Transfer Object) 数据传输对象 | 不同应用程序之间传输的对象 |
DTO(Data Transfer Object) 数据传输对象 | 泛指展示层与服务层之间的数据传输对象 |
VO(value object) 值对象 | View Object,视图对象;接受页面传递来的对象;业务处理完成的对象封装成页面要用的数据 |
BO(business object) 业务对象 | 主要用作把业务逻辑包括的一个或多个其他的对象封装为一个对象 |
POJO(plain ordinary java object) 简单无规则java对象 | 上述全属于POJO |
DAO(data access object) 数据访问对象 | 配合VO,提供CRUD操作 |
4 其他
- 让Entity对象在被转换为JSON格式时添加筛选条件
@JsonInclude(JsonInclude.Include.NON_EMPTY)
为空的属性包括数组长度为0的从JSON中移除 - 在application.yaml中配置jackson使返回给客户端的数据中的时间被格式化:
spring:jackson:time-zone: GMT+8date-format: yyyy-MM-dd HH:mm:ss
六、总结
1 分布式基础概念
- 微服务、 汪册中心,配置中心、远程调用、Feign、网关
2 基础开发
- SpringBoot2.0、 Springcloud、 Mybatis-Plus、vue组件化、阿里云对象存储
3 环境
- Vagrant. Linux、Docker、 MysQL、 Redis、逆向工程&人人开源
4 开发规范
- 数据校验JSR303、全局异常处理、全局统一返回、全局跨域处理 • 枚举状态、业务状态码、VO与TO与PO划分、逻辑删除 • Lombok:
@Data
@SIf4j