谷粒商城分布式基础(七)—— 商品服务API—品牌管理 (oss云存储 & 前端校验器 & 后端JSR303校验)
一、商品服务API—品牌管理
1、sql脚本
DROP TABLE IF EXISTS `pms_brand`; CREATE TABLE `pms_brand` ( `brand_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '品牌id', `name` char(50) DEFAULT NULL COMMENT '品牌名', `logo` varchar(2000) DEFAULT NULL COMMENT '品牌logo地址', `descript` longtext COMMENT '介绍', `show_status` tinyint(4) DEFAULT NULL COMMENT '显示状态[0-不显示;1-显示]', `first_letter` char(1) DEFAULT NULL COMMENT '检索首字母', `sort` int(11) DEFAULT NULL COMMENT '排序', PRIMARY KEY (`brand_id`) ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COMMENT='品牌'; -- ---------------------------- -- Records of pms_brand -- ---------------------------- INSERT INTO `pms_brand` VALUES ('9', '华为', 'https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-18/de2426bd-a689-41d0-865a-d45d1afa7cde_huawei.png', '华为', '1', 'H', '1'); INSERT INTO `pms_brand` VALUES ('10', '小米', 'https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-18/1f9e6968-cf92-462e-869a-4c2331a4113f_xiaomi.png', '小米', '1', 'M', '1'); INSERT INTO `pms_brand` VALUES ('11', 'oppo', 'https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-18/5c8303f2-8b0c-4a5b-89a6-86513133d758_oppo.png', 'oppo', '1', 'O', '1'); INSERT INTO `pms_brand` VALUES ('12', 'Apple', 'https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-18/819bb0b1-3ed8-4072-8304-78811a289781_apple.png', '苹果', '1', 'A', '1');
2、使用逆向工程的前后端代码
(1)添加菜单“品牌管理”
(2)将“”逆向工程得到的 resources\src\views\modules\product 文件拷贝到 gulimall/renren-fast-vue/src/views/modules/product目录下,也就是下面的两个文件
brand.vue brand-add-or-update.vue
添加了新的组件, 需要停掉当前项目,并且 npm run dev 重启一下项目
但是显示的页面没有新增和删除功能,这是因为权限控制的原因
查看“isAuth”的定义位置:
它是在“index.js”中定义,暂时将它设置为返回值为true,即可显示添加和删除功能。
再次刷新页面能够看到,按钮已经出现了:
进行添加 测试成功, 进行修改 也会自动回显
build/webpack.base.conf.js 中注释掉createLintingRule()函数体,不进行lint语法检查
注释后需要重启vue项目
3、效果优化与快速显示开关
brand.vue
<template> <div class="mod-config"> <el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()" > <el-form-item> <el-input v-model="dataForm.key" placeholder="参数名" clearable ></el-input> </el-form-item> <el-form-item> <el-button @click="getDataList()">查询</el-button> <el-button v-if="isAuth('product:brand:save')" type="primary" @click="addOrUpdateHandle()" >新增</el-button > <el-button v-if="isAuth('product:brand:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0" >批量删除</el-button > </el-form-item> </el-form> <el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%" > <el-table-column type="selection" header-align="center" align="center" width="50" > </el-table-column> <el-table-column prop="brandId" header-align="center" align="center" label="品牌id" > </el-table-column> <el-table-column prop="name" header-align="center" align="center" label="品牌名" > </el-table-column> <el-table-column prop="logo" header-align="center" align="center" label="品牌logo地址" > </el-table-column> <el-table-column prop="descript" header-align="center" align="center" label="介绍" > </el-table-column> <el-table-column prop="showStatus" header-align="center" align="center" label="显示状态" > <template slot-scope="scope"> <el-switch v-model="scope.row.showStatus" active-color="#13ce66" inactive-color="#ff4949" :active-value="1" :inactive-value="0" @change="updateBrandStatus(scope.row)" ></el-switch> </template> </el-table-column> <el-table-column prop="firstLetter" header-align="center" align="center" label="检索首字母" > </el-table-column> <el-table-column prop="sort" header-align="center" align="center" label="排序" > </el-table-column> <el-table-column fixed="right" header-align="center" align="center" width="150" label="操作" > <template slot-scope="scope"> <el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.brandId)" >修改</el-button > <el-button type="text" size="small" @click="deleteHandle(scope.row.brandId)" >删除</el-button > </template> </el-table-column> </el-table> <el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalPage" layout="total, sizes, prev, pager, next, jumper" > </el-pagination> <!-- 弹窗, 新增 / 修改 --> <add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList" ></add-or-update> </div> </template> <script> import AddOrUpdate from "./brand-add-or-update"; export default { data() { return { dataForm: { key: "", }, dataList: [], pageIndex: 1, pageSize: 10, totalPage: 0, dataListLoading: false, dataListSelections: [], addOrUpdateVisible: false, }; }, components: { AddOrUpdate, }, activated() { this.getDataList(); }, methods: { // 获取数据列表 getDataList() { this.dataListLoading = true; this.$http({ url: this.$http.adornUrl("/product/brand/list"), method: "get", params: this.$http.adornParams({ page: this.pageIndex, limit: this.pageSize, key: this.dataForm.key, }), }).then(({ data }) => { if (data && data.code === 0) { this.dataList = data.page.list; this.totalPage = data.page.totalCount; } else { this.dataList = []; this.totalPage = 0; } this.dataListLoading = false; }); }, updateBrandStatus(data) { console.log("最新信息", data); //发送请求修改状态 let {brandId, showStatus} = data; this.$http({ url: this.$http.adornUrl("/product/brand/update"), method: "post", data: this.$http.adornData({brandId, showStatus}, false), }).then(({ data }) => { this.$message({ type: "success", message: "状态更新成功" }) }); }, // 每页数 sizeChangeHandle(val) { this.pageSize = val; this.pageIndex = 1; this.getDataList(); }, // 当前页 currentChangeHandle(val) { this.pageIndex = val; this.getDataList(); }, // 多选 selectionChangeHandle(val) { this.dataListSelections = val; }, // 新增 / 修改 addOrUpdateHandle(id) { this.addOrUpdateVisible = true; this.$nextTick(() => { this.$refs.addOrUpdate.init(id); }); }, // 删除 deleteHandle(id) { var ids = id ? [id] : this.dataListSelections.map((item) => { return item.brandId; }); this.$confirm( `确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`, "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", } ).then(() => { this.$http({ url: this.$http.adornUrl("/product/brand/delete"), method: "post", data: this.$http.adornData(ids, false), }).then(({ data }) => { if (data && data.code === 0) { this.$message({ message: "操作成功", type: "success", duration: 1500, onClose: () => { this.getDataList(); }, }); } else { this.$message.error(data.msg); } }); }); }, }, }; </script>
brand-add-or-update.vue
<template> <el-dialog :title="!dataForm.brandId ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible" > <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="140px" > <el-form-item label="品牌名" prop="name"> <el-input v-model="dataForm.name" placeholder="品牌名"></el-input> </el-form-item> <el-form-item label="品牌logo地址" prop="logo"> <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> </el-form-item> <el-form-item label="介绍" prop="descript"> <el-input v-model="dataForm.descript" placeholder="介绍"></el-input> </el-form-item> <el-form-item label="显示状态" prop="showStatus"> <el-switch v-model="dataForm.showStatus" active-color="#13ce66" inactive-color="#ff4949" > </el-switch> </el-form-item> <el-form-item label="检索首字母" prop="firstLetter"> <el-input v-model="dataForm.firstLetter" placeholder="检索首字母" ></el-input> </el-form-item> <el-form-item label="排序" prop="sort"> <el-input v-model="dataForm.sort" placeholder="排序"></el-input> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="visible = false">取消</el-button> <el-button type="primary" @click="dataFormSubmit()">确定</el-button> </span> </el-dialog> </template> <script> export default { data() { return { visible: false, dataForm: { brandId: 0, name: "", logo: "", descript: "", showStatus: "", firstLetter: "", sort: "", }, dataRule: { name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }], logo: [ { required: true, message: "品牌logo地址不能为空", trigger: "blur" }, ], descript: [ { required: true, message: "介绍不能为空", trigger: "blur" }, ], showStatus: [ { required: true, message: "显示状态[0-不显示;1-显示]不能为空", trigger: "blur", }, ], firstLetter: [ { required: true, message: "检索首字母不能为空", trigger: "blur" }, ], sort: [{ required: true, message: "排序不能为空", trigger: "blur" }], }, }; }, methods: { init(id) { this.dataForm.brandId = id || 0; this.visible = true; this.$nextTick(() => { this.$refs["dataForm"].resetFields(); if (this.dataForm.brandId) { this.$http({ url: this.$http.adornUrl( `/product/brand/info/${this.dataForm.brandId}` ), method: "get", params: this.$http.adornParams(), }).then(({ data }) => { if (data && data.code === 0) { this.dataForm.name = data.brand.name; this.dataForm.logo = data.brand.logo; this.dataForm.descript = data.brand.descript; this.dataForm.showStatus = data.brand.showStatus; this.dataForm.firstLetter = data.brand.firstLetter; this.dataForm.sort = data.brand.sort; } }); } }); }, // 表单提交 dataFormSubmit() { this.$refs["dataForm"].validate((valid) => { if (valid) { this.$http({ url: this.$http.adornUrl( `/product/brand/${!this.dataForm.brandId ? "save" : "update"}` ), method: "post", data: this.$http.adornData({ brandId: this.dataForm.brandId || undefined, name: this.dataForm.name, logo: this.dataForm.logo, descript: this.dataForm.descript, showStatus: this.dataForm.showStatus, firstLetter: this.dataForm.firstLetter, sort: this.dataForm.sort, }), }).then(({ data }) => { if (data && data.code === 0) { this.$message({ message: "操作成功", type: "success", duration: 1500, onClose: () => { this.visible = false; this.$emit("refreshDataList"); }, }); } else { this.$message.error(data.msg); } }); } }); }, }, }; </script>
4、文件上传(oss前后联调测试上传)
请先参考文章 谷粒商城分布式基础(四)—— 分布式组件SpringCloud & SpringCloud Alibaba 的 “6、SpringCloud Alibaba-OSS【文件存储】” 搭建完毕第三方模块 gulimall-third-party,其中主要讲述了如何使用SpringCloud Alibaba的组件 OSS 搭建阿里云存储功能
1、上传组件
(1)放置项目提供的upload文件夹到components/目录下,一个是单文件上传,另外一个是多文件上传
policy.js封装一个Promise,发送/thirdparty/oss/policy请求。vue项目会自动加上api前缀
multiUpload.vue多文件上传。
singleUpload.vue单文件上传。
(2)修改upload文件
修改multiUpload.vue 和 singleUpload.vue
要替换里面的action中的内容action=“http://gulimall-hr.oss-cn-beijing.aliyuncs.com”
内容来源于阿里云平台
(3)修改brand-add-or-update.vue
(a)要使用文件上传组件,先导入import SingleUpload from “@/components/upload/singleUpload”;
(b)写明要使用的组件components: { SingleUpload },填入<single-upload v-model="dataForm.logo"></single-upload>
(c)修改el-form-item label="品牌logo地址"内容
(4)最终样式效果
点击一下文件上传,发现发送了两个请求
为什么会报错呢?
(5)上传问题分析
我们在后端准备好了签名controller,那么前端是在哪里获取的呢
而文件上传前调用的方法: :before-upload=“beforeUpload”
发现该方法返回了一个new Promise,调用了policy(),该方法是policy.js中的
import { policy } from "./policy";
在vue中看是response.data.policy,在控制台看response.policy。所以去java里面改返回值为R。return R.ok().put(“data”,respMap);
开始执行上传,但是在上传过程中,出现了跨域请求问题:
Access to XMLHttpRequest at 'http://gulimall-f.oss-cn-qingdao.aliyuncs.com/' from origin 'http://localhost:8001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
这又是一个跨域的问题,解决方法就是在阿里云上开启跨域访问:
再次测试上传,注意上传时他的key变成了response.data.dir +getUUID()+"_${filename}";
ok,上传成功
5、表单校验 & 自定义校验器
(1)修改brand.vue的品牌logo显示
效果:
(2)修改brand-add-or-update如下: :active-value="1"
:inactive-value="0" # 激活为1,不激活为0
添加表单校验 & 自定义校验器
修改后的效果
brand.vue
6、JSR303 数据校验
问题引入:填写form时应该有前端校验,后端也应该有校验 前端 前端的校验是element-ui表单验证 Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。
后端
(1)@NotNull等,给Bean添加校验注解
在Java中提供了一系列的校验方式,它这些校验方式在“javax.validation.constraints”包中,提供了如@Email,@NotNull等注解。
<!--jsr3参数校验器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
里面依赖了hibernate-validator,在非空处理方式上提供了@NotNull,@NotBlank和@NotEmpty
(a)@NotNull
The annotated element must not be null. Accepts any type. 注解元素禁止为null,能够接收任何类型
(b)@NotEmpty
the annotated element must not be null nor empty. 该注解修饰的字段不能为null或""
Supported types are: 支持以下几种类型
CharSequence (length of character sequence is evaluated)字符序列(字符序列长度的计算)
Collection (collection size is evaluated)
集合长度的计算
Map (map size is evaluated)
map长度的计算
Array (array length is evaluated)
数组长度的计算
(c)@NotBlank
The annotated element must not be null and must contain at least one non-whitespace character. Accepts CharSequence.
该注解不能为null,并且至少包含一个非空格字符。接收字符序列。
(2)@Valid,controller中加校验注解@Valid,开启校验
@RequestMapping("/save") //@RequiresPermissions("product:brand:save") public R save(@Valid @RequestBody BrandEntity brand){ brandService.save(brand); return R.ok(); } 测试:http://localhost:88/api/product/brand/save 在postman中发送上面的请求
能够看到"defaultMessage": “不能为空”,这些错误消息定义在“hibernate-validator”的“\org\hibernate\validator\ValidationMessages_zh_CN.properties”文件中。在该文件中定义了很多的错误规则:
javax.validation.constraints.AssertFalse.message = 只能为false
javax.validation.constraints.AssertTrue.message = 只能为true
javax.validation.constraints.DecimalMax.message = 必须小于或等于{value}
javax.validation.constraints.DecimalMin.message = 必须大于或等于{value}
javax.validation.constraints.Digits.message = 数字的值超出了允许范围(只允许在{integer}位整数和{fraction}位小数范围内)
javax.validation.constraints.Email.message = 不是一个合法的电子邮件地址
javax.validation.constraints.Future.message = 需要是一个将来的时间
javax.validation.constraints.FutureOrPresent.message = 需要是一个将来或现在的时间
javax.validation.constraints.Max.message = 最大不能超过{value}
javax.validation.constraints.Min.message = 最小不能小于{value}
javax.validation.constraints.Negative.message = 必须是负数
javax.validation.constraints.NegativeOrZero.message = 必须是负数或零
javax.validation.constraints.NotBlank.message = 不能为空
javax.validation.constraints.NotEmpty.message = 不能为空
javax.validation.constraints.NotNull.message = 不能为null
javax.validation.constraints.Null.message = 必须为null
javax.validation.constraints.Past.message = 需要是一个过去的时间
javax.validation.constraints.PastOrPresent.message = 需要是一个过去或现在的时间
javax.validation.constraints.Pattern.message = 需要匹配正则表达式"{regexp}"
javax.validation.constraints.Positive.message = 必须是正数
javax.validation.constraints.PositiveOrZero.message = 必须是正数或零
javax.validation.constraints.Size.message = 个数必须在{min}和{max}之间
org.hibernate.validator.constraints.CreditCardNumber.message = 不合法的信用卡号码
org.hibernate.validator.constraints.Currency.message = 不合法的货币 (必须是{value}其中之一)
org.hibernate.validator.constraints.EAN.message = 不合法的{type}条形码
org.hibernate.validator.constraints.Email.message = 不是一个合法的电子邮件地址
org.hibernate.validator.constraints.Length.message = 长度需要在{min}和{max}之间
org.hibernate.validator.constraints.CodePointLength.message = 长度需要在{min}和{max}之间
org.hibernate.validator.constraints.LuhnCheck.message = ${validatedValue}的校验码不合法, Luhn模10校验和不匹配
org.hibernate.validator.constraints.Mod10Check.message = ${validatedValue}的校验码不合法, 模10校验和不匹配
org.hibernate.validator.constraints.Mod11Check.message = ${validatedValue}的校验码不合法, 模11校验和不匹配
org.hibernate.validator.constraints.ModCheck.message = ${validatedValue}的校验码不合法, ${modType}校验和不匹配
org.hibernate.validator.constraints.NotBlank.message = 不能为空
org.hibernate.validator.constraints.NotEmpty.message = 不能为空
org.hibernate.validator.constraints.ParametersScriptAssert.message = 执行脚本表达式"{script}"没有返回期望结果
org.hibernate.validator.constraints.Range.message = 需要在{min}和{max}之间
org.hibernate.validator.constraints.SafeHtml.message = 可能有不安全的HTML内容
org.hibernate.validator.constraints.ScriptAssert.message = 执行脚本表达式"{script}"没有返回期望结果
org.hibernate.validator.constraints.URL.message = 需要是一个合法的URL
org.hibernate.validator.constraints.time.DurationMax.message = 必须小于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
org.hibernate.validator.constraints.time.DurationMin.message = 必须大于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
想要自定义错误消息,可以覆盖默认的错误提示信息,如@NotBlank的默认message是
public @interface NotBlank {
String message() default "{javax.validation.constraints.NotBlank.message}";
}
可以在添加注解的时候,修改message:
@NotBlank(message = "品牌名必须非空")
private String name;
当再次发送请求时,得到的错误提示信息:
(3)BindResult,给校验的Bean后,紧跟一个BindResult,就可以获取到校验的结果。拿到校验的结果,就可以自定义的封装
/**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
if( result.hasErrors()){
Map<String,String> map=new HashMap<>();
//1.获取错误的校验结果
result.getFieldErrors().forEach((item)->{
//获取发生错误时的message
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();
}
postman测试结果:
这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以统一的对于异常进行处理
7、统一异常处理
可以使用SpringMvc所提供的@ControllerAdvice,通过“backPackages”能够说明处理哪些路径下的异常
(1)抽取一个异常处理
package com.atguigu.gulimall.product.exception;
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.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = Exception.class) // 也可以返回ModelAndView
public R handleValidException(MethodArgumentNotValidException exception){
Map<String,String> map=new HashMap<>();
// 获取数据校验的错误结果
BindingResult bindingResult = exception.getBindingResult();
bindingResult.getFieldErrors().forEach(fieldError -> {
String message = fieldError.getDefaultMessage();
String field = fieldError.getField();
map.put(field,message);
});
log.error("数据校验出现问题{},异常类型{}",exception.getMessage(),exception.getClass());
return R.error(400,"数据校验出现问题").put("data",map);
}
}
测试
http://localhost:88/api/product/brand/save
(2)默认异常处理
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("未知异常{},异常类型{}",throwable.getMessage(),throwable.getClass());
return R.error(400,"数据校验出现问题");
}
(3)错误状态代码
上面代码中,针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义
在gulimall-common中创建 com.atguigu.common.exception.BizCodeEnume
package com.atguigu.common.exception;
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
*
*
*/
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败");
private int code;
private String msg;
BizCodeEnume(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
修改统一异常处理
package com.atguigu.gulimall.product.exception;
import com.atguigu.common.exception.BizCodeEnume;
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.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;
import java.util.HashMap;
import java.util.Map;
/**
* 集中处理所有异常
*/
@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleVaildException(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(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("错误:",throwable);
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
测试:http://localhost:88/api/product/brand/save
8、JSR303分组校验
(1)groups
给校验注解,标注上groups,指定什么情况下才需要进行校验
groups里面的内容要以接口的形式显示出来 如:指定在更新和添加的时候,都需要进行校验。新增时不需要带id,修改时必须带id
@NotNull(message = "修改必须定制品牌id", groups = {UpdateGroup.class}) @Null(message = "新增不能指定id", groups = {AddGroup.class}) @TableId private Long brandId;
在这种情况下,没有指定分组的校验注解,默认是不起作用的。想要起作用就必须要加groups。
(2)@Validated
业务方法参数上使用@Validated注解
@Validated的value方法:
Specify one or more validation groups to apply to the validation step kicked off by this annotation. 指定一个或多个验证组以应用于此注释启动的验证步骤。 JSR-303 defines validation groups as custom annotations which an application declares for the sole purpose of using them as type-safe group arguments, as implemented in SpringValidatorAdapter. JSR-303 将验证组定义为自定义注释,应用程序声明的唯一目的是将它们用作类型安全组参数,如 SpringValidatorAdapter 中实现的那样。 Other SmartValidator implementations may support class arguments in other ways as well. 其他SmartValidator 实现也可以以其他方式支持类参数。
@RequestMapping("/save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand) {
brandService.save(brand);
return R.ok();
}
@RequestMapping("/delete")
//@RequiresPermissions("${moduleNamez}:brand:delete")
public R delete(@RequestBody Long[] brandIds) {
brandService.removeByIds(Arrays.asList(brandIds));
return R.ok();
}
(3)分组情况下,校验注解生效问题
默认情况下,在分组校验情况下,没有指定分组的校验注解,将不会生效,它只会在不分组的情况下生效。
9、自定义校验功能
场景:要校验showStatus的01状态,可以用正则,但我们可以利用其他方式解决复杂场景。比如我们想要下面的场景
/**
* 显示状态[0-不显示;1-显示]
*/
// @Pattern()
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
如何做:
gulimall-common添加依赖
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
(2)编写自定义的校验注解
必须有3个属性
message()错误信息
groups()分组校验
payload()自定义负载信息
package com.atguigu.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @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 {
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
该属性值取哪里取呢? common创建文件ValidationMessages.properties 里面写上com.atguigu.common.valid.ListValue.message=必须提交指定的值 [0,1]
(2)编写自定义的校验器
package com.atguigu.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* @Description:
* @Created: with IntelliJ IDEA.
* @author: 夏沫止水
* @createTime: 2020-05-27 17:54
**/
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;
}
}
(3)关联校验器和校验注解
@Constraint(validatedBy = { ListValueConstraintValidator.class})
(4)使用实例
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;