一、环境配置
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_HOMEexport 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.yml
group: DEFAULT_GROUP
refresh: 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_route
uri: 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: xxx
secret-key: xxx
oss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket: xxx
@RestController
@RequestMapping("/oss")
public class OssController {
@Autowired
private 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: 1
logic-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: 10000 error: include-binding-errors: always
问题4
ValidationMessages.properties 配置文件中的中文错误信息导致乱码问题
解决
添加配置类@Configuration public class ValidationConfig { @Bean public 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+8 date-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