集成spring-boot-starter-validation
在pom.xml中添加依赖,如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
对保存接口和查询接口增加参数校验
对page
和size
添加限制:
page
不能为空size
不能为空,且最大不能超过1000
SpringBoot很多功能都是通过注解完成的,本例也是通过注解给page
和size
添加校验规则,如下,
package com.jepcc.test.req;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
public class PageReq {
@NotNull(message="页码 不能为空")
private int page;
@NotNull(message="每页条数 不能为空")
@Max(value=1000,message="每页条数 不能超过1000")
private int size;
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
@Override
public String toString() {
return "PageReq{" +
"page=" + page +
", size=" + size +
'}';
}
}
@NotNull
被注释的元素不能为空,且使用message
配置提示语句。@Max(value)
被注释的元素必须是一个数字,其值必须小于等于指定的值。@Valid
接口参数加入@Valid
注解,启用校验规则,表示当前实体类接收的参数需要根据配置的@NotNull
、@Max
进行判断。
添加完校验规则后,将size
值设置为1001,即超过既定的最大值1000,然后调用下接口试试。
可以看到,报了BindException异常 ,接口返回400。
2021-06-18 10:55:54.184 WARN 12600 --- [nio-8088-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'ebookReq' on field 'size': rejected value [1001]; codes [Max.ebookReq.size,Max.size,Max.int,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [ebookReq.size,size]; arguments []; default message [size],1000]; default message [每页条数 不能超过1000]]
校验不通过时,前端弹出错误提示
接口参数在后台校验不通过,抛出BindException异常,那我们如何捕获这个异常并将对应的提示信息返回给前端呢?这就需要实现一个统一异常处理的类。
@ControllerAdvice
+@ExceptionHandler
,可以实现异常捕获和处理。
@ControllerAdvice
被@Component
标记,所以由@ControllerAdvice
注解的类可以被扫描并放入Spring容器。
@ExceptionHandler
用来标记异常类型对应的处理方法。
具体代码如下,
package com.jepcc.test.controller;
import com.jepcc.test.resp.CommonResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.*;
@ControllerAdvice
public class ControllerExceptionHandler {
private final static Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class);
@ExceptionHandler(value = BindException.class)
@ResponseBody
public CommonResp validExceptionHandler(BindException e){
CommonResp commonResp = new CommonResp();
LOG.warn("参数校验失败:{}",e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
commonResp.setSuccess(false);
commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return commonResp;
}
}
接着,调用接口试试。
注意哈,本例中的BindException
,是org.springframework.validation.BindException
,不是java.net.BindException
。
最后就是添加前端代码了。
<template>
<a-layout>
<a-layout-content
:style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
>
<p>
<a-button type="primary" size="large" @click="add">新增</a-button>
</p>
<a-table :columns="columns"
:dataSource="ebooks"
:row-key="record=>record.id"
:pagination="pagination"
:loading="loading"
@change="handleTableChange">
<template #cover="{text:cover}">
<img v-if="cover" :src="cover" alt="avatar">
</template>
<template #action="{text,record}">
<a-space>
<a-button type="primary" @click="edit(record)">编辑</a-button>
<a-popconfirm
title="删除后不可恢复,确认删除?"
ok-text="确定"
cancel-text="取消"
@confirm="handleDelete(record)"
>
<a-button type="danger">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</a-layout-content>
</a-layout>
<a-modal
title="电子书表单"
v-model:visible="visible"
:confirm-loading="confirmLoading"
@ok="handleOk"
>
<a-form :model="ebook" :label-col="{span:4}" :wrapper-col="{span:16}">
<a-form-item label="封面">
<a-input v-model:value="ebook.cover" />
</a-form-item>
<a-form-item label="名称">
<a-input v-model:value="ebook.name" />
</a-form-item>
<a-form-item label="分类一">
<a-input v-model:value="ebook.category1Id" />
</a-form-item>
<a-form-item label="分类二">
<a-input v-model:value="ebook.category2Id" />
</a-form-item>
<a-form-item label="描述">
<a-input v-model:value="ebook.description" />
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts">
import { defineComponent,ref,onMounted} from 'vue';
import axios from "axios";
import {message} from "ant-design-vue";
export default defineComponent({
name:"AdminEbook",
setup() {
const ebooks = ref();
const pagination = ref({
current:1,
pageSize:4,
total:0
});
const loading = ref(false);
const columns = [
{
title:"封面",
dataIndex:"cover",
key:"cover",
slots:{customRender:'cover'}
},
{
title:"名称",
key:"name",
dataIndex:"name"
},
{
title:"分类一",
key:"category1Id",
dataIndex:"category1Id"
},
{
title:"分类二",
key:"category2Id",
dataIndex:"category2Id"
},
{
title:"文档数",
key:"docCount",
dataIndex:"docCount"
},
{
title:"阅读数",
key:"viewCount",
dataIndex:"viewCount"
},
{
title:"点赞数",
key:"vouteCount",
dataIndex:"voteCount"
},
{
title:"操作",
key:"action",
dataIndex:"action",
slots:{customRender:'action'}
}
];
const visible = ref(false);
const modalText = ref('test');
const confirmLoading = ref(false);
const ebook= ref({});
const handleQuery = (params:any) => {
loading.value = true;
axios.get("/ebook/list",{params:params}).then(response => {
loading.value = false;
const data = response.data;
const content = data.content;
if(data.success){
ebooks.value = content.list;
pagination.value.total = content.total;
pagination.value.current = params.page;
}else{
message.warn(data.message);
}
})
};
const handleTableChange = (pagination:any) => {
handleQuery({
page:pagination.current,
size:pagination.pageSize
});
}
const edit = (record:any) => {
visible.value = true;
ebook.value = record;
}
const add = () => {
visible.value = true;
ebook.value = {};
}
const handleDelete = (record:any) => {
axios.delete("ebook/delete/"+record.id).then(response => {
const data = response.data;
if(data.success){
handleQuery({
page:pagination.value.current,
size:pagination.value.pageSize
})
}
})
}
const handleOk = () => {
// modalText.value = 'The modal will be closed after two seconds';
// confirmLoading.value = true;
axios.post("/ebook/save",ebook.value).then(response => {
const data = response.data;
if(data.success){
visible.value = false;
confirmLoading.value = false;
handleQuery({
page:pagination.value.current,
size:pagination.value.pageSize
})
}
})
// setTimeout(() => {
// visible.value = false;
// confirmLoading.value = false;
// }, 2000);
};
onMounted(() => {
handleQuery({
page:1,
size:pagination.value.pageSize
});
})
return {
columns,
ebooks,
loading,
pagination,
handleTableChange,
visible,
modalText,
confirmLoading,
edit,
add,
handleDelete,
handleOk,
ebook
}
}
})
</script>
<style scoped>
img{
width: 48px;
height: 48px;
}
</style>
现在,将查询的条数修改为1001,测试下效果。
同样的,在新增电子书的时候,给电子书名称添加校验限制:名称不能为空。
现在来验证下,
不过,如果给名称输入一个空字符串,这里也能保存,如下,