谷粒商城–品牌管理开发-笔记六
1.新增品牌管理菜单
2.前端页面
由于只有简单的crud使用逆向工程生成的vue文件
将gulimall-product模块生成的逆向工程文件中的category.vue和category-add-or-update.vue复制到
renren-fast-vue/src/views/modules/product目录下
复制完成后 页面刷新结果
此时已经自带crud
3.样式优化
将显示状态
字段修改为开关显示
src/views/modules/product/brand.vue将template中的显示状态字段修改为以下代码
<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>
新增updateBrandStatus方法
//修改显示状态方法
updateBrandStatus(data) {
console.log("最新信息", data);
let { brandId, showStatus } = data;
//发送请求修改状态
this.$http({
url: this.$http.adornUrl("/product/brand/update/status"),
method: "post",
data: this.$http.adornData({ brandId, showStatus }, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "状态更新成功"
});
});
},
后台编写接口 并重启
gulimall-product/src/main/java/site/zhourui/gulimall/product/controller/BrandController.java
/**
* 修改状态
*/
@RequestMapping("/update/status")
//@RequiresPermissions("product:brand:update")
public R updateStatus(@RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
网关配置商品相关路由 并重启网关
gulimall-gateway/src/main/resources/application.yml
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
修改模态框中的
显示状态
字段src/views/modules/product/brand-add-or-update.vue
<el-form-item label="显示状态" prop="showStatus">
<el-switch
v-model="dataForm.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
效果:
4.云存储管理
0.为什么使用存储
- 分布式项目上传图片,不可能是只在某一台服务器上,但如果所有服务器上都有同样的资源,又造成资源浪费
- 解决方法
- 自建文件服务器 :搭建复杂,维护成本高,前期费用高
- 云存储:即开即用,无需维护,按量收费
为了品牌logo上传图片,使用阿里云云存储管理图片
1.登录注册阿里云
本人已经注册过阿里云账号了,没有的请自行注册
2.登录成功后
进入控制台
3.选择对象存储oss
4.开通对象存储服务 OSS
5.使用OSS
1.创建Bucket
2.上传测试
1.添加依赖包
gulimall-product/pom.xml
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.8.0</version>
</dependency>
2.添加测试方法
gulimall-product/src/test/java/site/zhourui/gulimall/product/GulimallProductApplicationTests.java
// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
String endpoint = "yourEndpoint";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "yourAccessKeyId";
String accessKeySecret = "yourAccessKeySecret";
// 填写Bucket名称,例如examplebucket。
String bucketName = "examplebucket";
// 填写文件名。文件名包含路径,不包含Bucket名称。例如exampledir/exampleobject.txt。
String objectName = "exampledir/exampleobject.txt";
OSS ossClient = null;
try {
// 创建OSSClient实例。
ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
String content = "Hello OSS";
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
} catch (OSSException e) {
e.printStackTrace();
} finally {
// 关闭OSSClient。
ossClient.shutdown();
}
3.endpoint与Bucket 域名
4.配置API访问子账号
阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
5.创建RAM子账号
6.为子账号添加oss权限
7.子账号创建AccessKey
8.配置完成过后进行测试
package site.zhourui.gulimall.product;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.ByteArrayInputStream;
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallProductApplicationTests {
@Test
public void contextLoads() {
// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
String endpoint = "oss-cn-chengdu.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "xxxxxxxxxx";
String accessKeySecret = "xxxxxxx";
// 填写Bucket名称,例如examplebucket。
String bucketName = "zr-gulimall-images";
// 填写文件名。文件名包含路径,不包含Bucket名称。例如exampledir/exampleobject.txt。
String objectName = "C:\\Users\\eric\\Pictures\\微信截图_20210930112358.png";
OSS ossClient = null;
try {
// 创建OSSClient实例。
ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
String content = "Hello OSS";
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
} catch (OSSException e) {
e.printStackTrace();
} finally {
// 关闭OSSClient。
ossClient.shutdown();
}
}
}
结果:成功
3.更为简单的使用方式,是使用SpringCloud Alibaba-Oss
1.创建gulimall-third-party模块
将所有三方服务抽取到一个单独的模块
pom文件
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-third-party</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall-third-party</name>
<description>第三方服务</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RC1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.zhourui.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--此处导出oss相关依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
GulimallThirdPartyApplication
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallThirdPartyApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallThirdPartyApplication.class, args);
}
}
application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
alicloud:
access-key: xxx
secret-key: xxx
oss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket: gulimall-hello
application:
name: gulimall-third-party
server:
port: 30000
2.上传测试
@SpringBootTest
class GulimallThirdPartyApplicationTests {
@Autowired
OSSClient ossClient;
@Test
public void testUpload() throws FileNotFoundException {
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "oss-cn-chengdu.aliyuncs.com";
// 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
String accessKeyId = "xxx";
String accessKeySecret = "xxx";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传文件流。
InputStream inputStream = new FileInputStream("C:\\Users\\eric\\Pictures\\sku_state_change_process.bpmn20.png");
ossClient.putObject("zr-gulimall-images", "hahaha.jpg", inputStream);
// 关闭OSSClient。
ossClient.shutdown();
System.out.println("上传完成...");
}
}
测试结果
3.编写OssController接口
gulimall-third-party/src/main/java/site/zhourui/gulimall/thirdparty/controller/OssController.java
package site.zhourui.gulimall.thirdparty.controller;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
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 site.zhourui.common.utils.R;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
public class OssController {
@Autowired
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("/oss/policy")
public R policy() {
//https://gulimall-hello.oss-cn-beijing.aliyuncs.com/hahaha.jpg
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 + "/"; // 用户上传文件时指定的前缀。
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);
}
}
接口测试
localhost:30000/oss/policy
测试结果
{
"msg": "success",
"code": 0,
"data": {
"accessid": "LTAI5t83gcohLLuvbzxxxxx",
"policy": "eyJleHBpcmF0aW9uIjoiMjAyMS0xMC0wOFQxMDowNDoyNy4xNTZaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIxLTEwLTA4LyJdXX0=",
"signature": "eFxjAhr4uWJxR1IkJftKpUWFzGU=",
"dir": "2021-10-08/",
"host": "https://zr-gulimall-images.oss-cn-chengdu.aliyuncs.com",
"expire": "1633687467"
}
}
5.阿里云对象存储上传方式
1.普通上传
优点: 安全性高
缺点:每次上传都通过服务器上传,造成服务器性能损失
2.服务器签名后前端直传
由服务器端获取认证,返回给前端,这样安全性的问题也解决了,服务器性能上的问题也解决了
因此本次我们选用第二种
5.品牌logo上传
1.编写上传组件
目录结构
multiUpload.vue
并配置el-upload
action
属性 格式:http://bucket.endpoint
<template>
<div>
<el-upload
action="http://zr-gulimall-images.oss-cn-chengdu.aliyuncs.com"
:data="dataObj"
:list-type="listType"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview"
:limit="maxCount"
:on-exceed="handleExceed"
:show-file-list="showFile"
>
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt />
</el-dialog>
</div>
</template>
<script>
import { policy } from "./policy";
import { getUUID } from '@/utils'
export default {
name: "multiUpload",
props: {
//图片属性数组
value: Array,
//最大上传图片数量
maxCount: {
type: Number,
default: 30
},
listType:{
type: String,
default: "picture-card"
},
showFile:{
type: Boolean,
default: true
}
},
data() {
return {
dataObj: {
policy: "",
signature: "",
key: "",
ossaccessKeyId: "",
dir: "",
host: "",
uuid: ""
},
dialogVisible: false,
dialogImageUrl: null
};
},
computed: {
fileList() {
let fileList = [];
for (let i = 0; i < this.value.length; i++) {
fileList.push({ url: this.value[i] });
}
return fileList;
}
},
mounted() {},
methods: {
emitInput(fileList) {
let value = [];
for (let i = 0; i < fileList.length; i++) {
value.push(fileList[i].url);
}
this.$emit("input", value);
},
handleRemove(file, fileList) {
this.emitInput(fileList);
},
handlePreview(file) {
this.dialogVisible = true;
this.dialogImageUrl = file.url;
},
//在每次更新前,都会向后台请求oss认证信息
beforeUpload(file) {
let _self = this;
return new Promise((resolve, reject) => {
policy()
.then(response => {
console.log("这是什么${filename}");
_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;
resolve(true);
})
.catch(err => {
console.log("出错了...",err)
reject(false);
});
});
},
handleUploadSuccess(res, file) {
this.fileList.push({
name: file.name,
// url: this.dataObj.host + "/" + this.dataObj.dir + "/" + file.name; 替换${filename}为真正的文件名
url: this.dataObj.host + "/" + this.dataObj.key.replace("${filename}",file.name)
});
this.emitInput(this.fileList);
},
handleExceed(files, fileList) {
this.$message({
message: "最多只能上传" + this.maxCount + "张图片",
type: "warning",
duration: 1000
});
}
}
};
</script>
<style>
</style>
singleUpload.vue
单文件上传组件
并配置el-upload
action
属性 格式:http://bucket.endpoint
<template>
<div>
<el-upload
action="http://zr-gulimall-images.oss-cn-chengdu.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>
</div>
</template>
<script>
import {policy} from './policy'
import { getUUID } from '@/utils'
export default {
name: 'singleUpload',
props: {
value: String
},
computed: {
imageUrl() {
return this.value;
},
imageName() {
if (this.value != null && this.value !== '') {
return this.value.substr(this.value.lastIndexOf("/") + 1);
} else {
return null;
}
},
fileList() {
return [{
name: this.imageName,
url: this.imageUrl
}]
},
showFileList: {
get: function () {
return this.value !== null && this.value !== ''&& this.value!==undefined;
},
set: function (newValue) {
}
}
},
data() {
return {
dataObj: {
policy: '',
signature: '',
key: '',
ossaccessKeyId: '',
dir: '',
host: '',
// callback:'',
},
dialogVisible: false
};
},
methods: {
emitInput(val) {
this.$emit('input', val)
},
handleRemove(file, fileList) {
this.emitInput('');
},
handlePreview(file) {
this.dialogVisible = true;
},
//在每次更新前,都会向后台请求oss认证信息
beforeUpload(file) {
let _self = this;
return new Promise((resolve, reject) => {
policy().then(response => {
console.log("响应的数据",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("响应的数据222。。。",_self.dataObj);
resolve(true)
}).catch(err => {
reject(false)
})
})
},
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);
}
}
}
</script>
<style>
</style>
policy.js
用于发送请求调用gulimall-third-party获取oss的认证信息
import http from '@/utils/httpRequest.js'
export function policy() {
return new Promise((resolve,reject)=>{
http({
url: http.adornUrl("/thirdparty/oss/policy"),
method: "get",
params: http.adornParams({})
}).then(({ data }) => {
resolve(data);
})
});
}
2.配置三方接口的模块相关路由
gulimall-gateway/src/main/resources/application.yml增加以下配置,并重启
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
3.在brand-add-or-update.vue中使用上传组件
此处我们需要上传的logo,所以选用单文件上传组件
singleUpload.vue
1.引入组件
import SingleUpload from "@/components/upload/singleUpload";
2.注册组件
components: { SingleUpload },
3.使用组件
<el-form-item label="品牌logo地址" prop="logo">
<single-upload v-model="dataForm.logo"></single-upload>
</el-form-item>
4.OSS跨域问题
问题
到这一步文件上传会出现跨域问题
解决
阿里云配置跨域设置
找到相应bucket的权限管理
找到跨域配置点击设置
新建规则,并配置
来源:允许谁跨域访问,可以设置自己的域名,这里我设置的
*
,就是所有methods:允许跨域请求方式,文件上传使用的是post请求,这里选择post,如果对oss有其他操作,可以勾选其他方法
允许header:请求需要带什么头部信息,可以自定义头信息,做进一步安全验证,这里我选的是任意
*
结果
上传成功返回图片链接并回显
6.品牌管理logo显示
<el-table-column prop="logo" header-align="center" align="center" label="品牌logo地址">
<template slot-scope="scope">
<img :src="scope.row.logo" style="width: 100px; height: 80px" />
</template>
</el-table-column>
效果:
7.关于新增按钮与批量按钮不显示问题
brand.vue中的
isAuth
为true才会显示而这需要权限,目前我们没有,所以修改这个方法一直返回true即可
isAuth在src/utils/index.js
8.表单前端校验
品牌新增与品牌修改时防止用于向服务端提交错误的数据
完整示例
<template>
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
>
<!--1.在需要前端校验的表单加上 :rules="dataRule" (dataRule是自定义的校验规则)-->
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="dataFormSubmit()"
label-width="140px"
>
<!--表单域 model 字段,在使用 validate、resetFields 方法的情况下,该属性是必填的-->
<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> -->
<single-upload v-model="dataForm.logo"></single-upload>
</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"
:active-value="1"
:inactive-value="0"
></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.number="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>
import SingleUpload from "@/components/upload/singleUpload";
export default {
components: { SingleUpload },
data() {
return {
visible: false,
dataForm: {
brandId: 0,
name: "",
logo: "",
descript: "",
showStatus: 1,
firstLetter: "",
sort: 0
},
//自定义校验规则
dataRule: {
//(prop对应列内容的字段名,也可以使用 property 属性),此处的字段名对应el-form-item的prop值
//对表单name字段进行校验 (trigger:触发器在何时触发当前表单字段的校验 ;blur:表示失去焦点时进行校验)
name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
//对表单logo字段进行校验 (trigger:触发器在何时触发当前表单字段的校验 ;blur:表示失去焦点时进行校验)
logo: [
{ required: true, message: "品牌logo地址不能为空", trigger: "blur" }
],
//对表单descript字段进行校验 (trigger:触发器在何时触发当前表单字段的校验 ;blur:表示失去焦点时进行校验)
descript: [
{ required: true, message: "介绍不能为空", trigger: "blur" }
],
//对表单showStatus字段进行校验
showStatus: [
{
required: true,
message: "显示状态[0-不显示;1-显示]不能为空",
trigger: "blur"
}
],
//对表单firstLetter字段进行校验
firstLetter: [
{
//在有特殊规则是自定义校验器,支持正则表达式
validator: (rule, value, callback) => {
if (value == "") {
callback(new Error("首字母必须填写"));
} else if (!/^[a-zA-Z]$/.test(value)) {
callback(new Error("首字母必须a-z或者A-Z之间"));
} else {
//callback没有参数表示正确
callback();
}
},
trigger: "blur"
}
],
sort: [
{
validator: (rule, value, callback) => {
if (value == "") {
callback(new Error("排序字段必须填写"));
} else if (!Number.isInteger(value) || value<0) {
callback(new Error("排序必须是一个大于等于0的整数"));
} else {
callback();
}
},
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>
效果
9.JSR303校验
只有前端校验是不够的,为了防止绕过前端验证的非法提交,在后端也需要表单提交的数据进行校验
此处使用的是JSR303校验
比如使用postman直接访问http://localhost:88/api/product/brand/save
这样就绕过了前端验证
1.步骤1:使用校验注解
在Java中提供了一系列的校验方式,它这些校验方式在“javax.validation.constraints”包中,提供了如@Email,@NotNull等注解。
在非空处理方式上提供了@NotNull,@Blank和@NotBlank
-
@NotEmpty
-
The annotated element must not be {@code null} nor empty. Supported types are:
-
不能是null
-
不能是空字符
-
集合框架中的元素不能为空
-
-
@NotNul
- 被修饰元素不能为null
-
@NotBlank
- The annotated element must not be {@code null} and must contain at least onenon-whitespace character.
- 这个注解用来判断字符串或者字符
gulimall-product/src/main/java/site/zhourui/gulimall/product/entity/BrandEntity.java
为name字段添加添加注解
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank
private String name;
/**
* 品牌logo地址
*/
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
@TableLogic(value = "1",delval = "0")
private Integer showStatus;
/**
* 检索首字母
*/
private String firstLetter;
/**
* 排序
*/
private Integer sort;
}
2.步骤2:使用校验注解@Valid,开启校验
在需要表单验证的controller接口上添加注解 并重启
在品牌的save方法的参数上添加注解@Valid
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
3.测试
postman直接访问http://localhost:88/api/product/brand/save
表单提交失败了
能够看到"defaultMessage": “不能为空”,这些错误消息定义在“hibernate-validator”的“\org\hibernate\validator\ValidationMessages_zh_CN.properties”文件中。在该文件中定义了很多的错误规则:
org\hibernate\validator\hibernate-validator\6.0.17.Final\hibernate-validator-6.0.17.Final.jar!\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是可以在添加注解的时候,修改message:
/**
* 品牌名
*/
@NotBlank(message = "品牌名称不能为空")
private String name;
再次访问
但是这种返回的错误结果并不符合我们的业务需要。
4.步骤3:自定义的封装校验结果
1.获取校验结果
给校验的Bean后,紧跟一个BindResult,就可以获取到校验的结果。拿到校验的结果,就可以自定义的封装。
@RequestMapping("/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();
}
测试http://localhost:88/api/product/brand/save
这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以统一的对于异常进行处理。
5.步骤4:统一异常处理
1.创建异常处理类
可以使用SpringMvc所提供的@ControllerAdvice,通过“basePackages”能够说明处理哪些路径下的异常。
gulimall-product/src/main/java/site/zhourui/gulimall/product/exception/GulimallExceptionControllerAdvice.java下创建
先删除以前单个处理
@RequestMapping("/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);
抽取一个异常处理类
gulimall-product/src/main/java/site/zhourui/gulimall/product/exception/GulimallExceptionControllerAdvice.java
@Slf4j
@ResponseBody
@ControllerAdvice(basePackages = "site.zhourui.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = Exception.class)
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.错误状态码
上面代码中,针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义
为了定义这些错误状态码,我们可以单独定义一个常量类,用来存储这些错误状态码
gulimall-common/src/main/java/site/zhourui/common/exception/BizCodeEnume.java
在gulimall-common公共模块
如果之后有需要增加错误信息,就在下面添加
package site.zhourui.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;
}
}
3.修改统一异常处理类
使用BizCodeEnume枚举替换GulimallExceptionControllerAdvice中的code与msg
新增其他所有异常处理
可以使用
@RestControllerAdvice(basePackages = "site.zhourui.gulimall.product.controller")
替换
@ResponseBody @ControllerAdvice(basePackages = "site.zhourui.gulimall.product.controller")
/**
* 集中处理所有异常
*/
@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "site.zhourui.gulimall.product.controller")
@RestControllerAdvice(basePackages = "site.zhourui.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所有错误,获取错误字段,以及对应的消息,存储为map返回
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());
}
}
10.JSR303分组校验(完成多场景的复杂校验)
比如此处对品牌的新增与修改都是接收的BrandEntity而BrandEntity每个字段上的校验注解现在只针对save也就是新增方法,而修改所需要校验的字段又有所不同这时我们需要分组校验
1.新增接口AddGroup,UpdateGroup
这两个主要用来区分是属于新增分组或者修改分组,主要是用做标识符
gulimall-common/src/main/java/site/zhourui/common/valid/AddGroup.java
package site.zhourui.common.valid;
public interface AddGroup {
}
gulimall-common/src/main/java/site/zhourui/common/valid/UpdateGroup.java
package site.zhourui.common.valid;
public interface UpdateGroup {
}
2.新增接口UpdateStatusGroup
gulimall-common/src/main/java/site/zhourui/common/valid/UpdateStatusGroup.java
package site.zhourui.common.valid;
public interface UpdateStatusGroup {
}
3.给校验注解,标注上groups,指定什么情况下才需要进行校验
package site.zhourui.gulimall.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import site.zhourui.common.valid.AddGroup;
import site.zhourui.common.valid.ListValue;
import site.zhourui.common.valid.UpdateGroup;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
/**
* 品牌
*
* @author zr
* @email 2437264464@qq.com
* @date 2021-09-28 15:45:02
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
private String name;
/**
* 品牌logo地址
*/
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
private String firstLetter;
/**
* 排序
*/
private Integer sort;
}
对品牌id与品牌名进行分组校验
- 品牌id
- 新增AddGroup分组:@Null(message = “新增不能指定id”,groups = {AddGroup.class}) , 新增分组的请求brandId为空才可以
- 修改UpdateGroup分组:@NotNull(message = “修改必须指定品牌id”,groups = {UpdateGroup.class}),修改分组的请求brandId不能为空
- 品牌名
- @NotBlank(message = “品牌名必须提交”,groups = {AddGroup.class,UpdateGroup.class}) ,修改分组与新增分组都必须不为空
4业务方法参数上使用@Validated注解
gulimall-product/src/main/java/site/zhourui/gulimall/product/controller/BrandController.java
save方法修改
@Valid
为分组校验注解@Validated({AddGroup.class})
updateStatus方法添加
@Validated(UpdateStatusGroup.class)
package site.zhourui.gulimall.product.controller;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import site.zhourui.common.valid.AddGroup;
import site.zhourui.common.valid.UpdateGroup;
import site.zhourui.common.valid.UpdateStatusGroup;
import site.zhourui.gulimall.product.entity.BrandEntity;
import site.zhourui.gulimall.product.service.BrandService;
import site.zhourui.common.utils.PageUtils;
import site.zhourui.common.utils.R;
import javax.validation.Valid;
/**
* 品牌
*
* @author zr
* @email 2437264464@qq.com
* @date 2021-09-28 15:45:02
*/
@RestController
@RequestMapping("product/brand")
public class BrandController {
@Autowired
private BrandService brandService;
/**
* 列表
*/
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page = brandService.queryPage(params);
return R.ok().put("page", page);
}
/**
* 信息
*/
@RequestMapping("/info/{brandId}")
public R info(@PathVariable("brandId") Long brandId){
BrandEntity brand = brandService.getById(brandId);
return R.ok().put("brand", brand);
}
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @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();
}
/**
* 修改状态
*/
@RequestMapping("/update/status")
//@RequiresPermissions("product:brand:update")
public R updateStatus(@RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
/**
* 删除
*/
@RequestMapping("/delete")
public R delete(@RequestBody Long[] brandIds){
brandService.removeByIds(Arrays.asList(brandIds));
return R.ok();
}
}
测试结果一:新增接口带上brandId,并且name为
""
http://localhost:88/api/product/brand/save
测试结果二:修改接口不带上brandId,并且name为
""
http://localhost:88/api/product/brand/update
因为showStatus字段的校验的特殊,所以我们需要自定义校验注解
5.自定义校验注解
1.编写一个自定义的校验注解@ListValue
package site.zhourui.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.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{site.zhourui.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
2.编写使用消息返回ValidationMessages.properties
site.zhourui.common.valid.ListValue.message=必须提交指定的值
3.编写一个自定义的校验器ListValueConstraintValidator
package site.zhourui.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
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 context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
4.关联自定义的校验器和自定义的校验注解
修改
@ListValue
package site.zhourui.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.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@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 { };
}
5.使用实例
在BrandEntity使用自定义注解
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
@TableLogic(value = "1",delval = "0")
private Integer showStatus;
gulimall-product/src/main/java/site/zhourui/gulimall/product/controller/BrandController.java
updateStatus方法添加
@Validated(UpdateStatusGroup.class)
/**
* 修改状态
*/
@RequestMapping("/update/status")
//@RequiresPermissions("product:brand:update")
public R updateStatus(@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
测试http://localhost:88/api/product/brand/update/status
BrandEntity 后端校验全部配置
package site.zhourui.gulimall.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import site.zhourui.common.valid.AddGroup;
import site.zhourui.common.valid.ListValue;
import site.zhourui.common.valid.UpdateGroup;
import site.zhourui.common.valid.UpdateStatusGroup;
import javax.validation.constraints.*;
/**
* 品牌
*
* @author zr
* @email 2437264464@qq.com
* @date 2021-09-28 15:45:02
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌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;
/**
* 品牌logo地址
*/
//品牌logo地址不能为空,并且logo必须是一个合法的url地址
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
//介绍不做限制
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
//自定义校验注解
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty(groups={AddGroup.class})
@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(groups={AddGroup.class})
@Min(value = 0,message = "排序必须大于等于0",groups={AddGroup.class,UpdateGroup.class})
private Integer sort;
}
测试效果http://localhost:88/api/product/brand/save
注意
默认情况下,在分组校验情况下,没有指定指定分组的校验注解,将不会生效,它只会在不分组的情况下生效。
如
/**
* 介绍
*/
//介绍不做限制
private String descript;