文章目录
5.1 逆向生成的前端代码
① 在renren-fast-vue项目的菜单管理里中新增一个菜单:和之前的分类维护一样,再次创建一个品牌管理。
② 将之前逆向工程生成的product微服务的代码(renren-generator代码生成器生成各个微服务的前后端代码)
F:\JAVA\谷粒商城电商项目-2020-尚硅谷-雷丰阳\renren\ren_gulimall_product\main\resources\src\views\modules\product
项目下的brand-add-or-update.vue
和brand.vue
文件拷贝到前端项目renren-fast-vue/src/views/modules/product中,然后在终端重新执行npm run dev重新运行项目,打开菜单栏的品牌管理,发现没有新增和删除功能:
③ 这是因为权限控制的原因,将renren-fast-vue项目的src\utils\index.js中的isAuth()方法永远返回true即可
/**
* 是否有权限
*/
export function isAuth (key) {
return true;
}
④测试新增和删除一个品牌,均正确
5.2 显示状态按钮
①前端代码
brand.vue
<template slot-scope="scope">
<el-switch
v-model="scope.row.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
@change="updateBrandStatus(scope.row)"
:active-value = "1"
:inactive-value = "0"
></el-switch>
</template>
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: "状态更新成功"
});
});
},
brand-add-or-update.vue
<el-form-item label="显示状态" prop="showStatus">
<el-switch v-model="dataForm.showStatus" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
</el-form-item>
② 点击新增时也能够将显示状态设置为开关:
可以看到在brand.vue文件中引入了一个组件brand-add-or-update.vue,注意addOrUpdateHandle (id) 方法:
<script>
//从外部导入的功能
import AddOrUpdate from './brand-add-or-update'
export default {
data () {
return {
addOrUpdateVisible: false
}
},
components: {
AddOrUpdate
},
methods: {
// 新增 / 修改
addOrUpdateHandle (id) {
this.addOrUpdateVisible = true
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id)
})
},
}
}
</script>
点击新增就会触发addOrUpdateHandle()方法:
<el-button v-if="isAuth('product:brand:save')" type="primary"
@click="addOrUpdateHandle()">新增</el-button>
addOrUpdateHandle()方法中会将this.addOrUpdateVisible = true,会显示add-or-update组件:
<!-- 弹窗, 新增 / 修改 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
所以我们要到brand-add-or-update.vue中修改显示状态将它设置为开关:
<el-form-item label="显示状态" prop="showStatus">
<template slot-scope="scope">
<el-switch v-model="dataForm.showStatus" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
</template>
</el-form-item>
③ 点击开关修改显示状态,并改变数据库中show_status的值(数据库中显示为1,不显示为0),而ElementUI的开关组件默认是显示为true,不显示为false,因此需要给开关组件的active-value和inactive-value属性绑定值,让其显示为1,不显示为0,同时还需要编写一个@change="updateBrandStatus(scope.row)"方法,将数据库中的show_status的值做相应更改:
<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>
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: "状态更新成功"
})
})
},
5.3 云存储的开通与使用(文件上传功能)
1、OSS开通
需求:点击新增后显示新增品牌,我们希望将品牌logo进行文件上传,和传统的单体应用不同,这里我们选择将数据上传到分布式文件服务器上。
① 进入https://www.aliyun.com/,选择产品,存储,对象存储OSS,立即开通
② 使用支付宝登录后完成实名认证,然后再开通对象存储OSS,管理控制台创建Bucket:
③ 找到创建的bucket,点击文件上传,上传一张图片,然后点击详情,复制url即可在浏览器访问呢图片:
④ 这种方式是手动上传图片,实际上我们可以在程序中设置自动上传图片到阿里云对象存储, 文件上传方式:
2、文件上传操作
参考:https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.920.15b85cb1mYuz5t
方式1:原生方式整合OSS:
① 在Maven工程中使用OSS Java SDK,只需在pom.xml中加入相应依赖即可,在gulimall-product服务中:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
② 在gulimall-product服务中编写一个测试类GulimallProductApplicationTests,上传文件流:
endpoint的取值和accessKeyId和accessKeySecret的取值需要从阿里云的对象存储OSS中获取
@Test
public void test() throws FileNotFoundException {
// Endpoint以杭州为例,https://oss.console.aliyun.com/bucket/oss-cn-hangzhou/gulimall-hengheng/overview
String endpoint = "oss-cn-beijing.aliyuncs.com";
// 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,https://ram.console.aliyun.com/users/gulimall/
String accessKeyId = "LTAI4GAJzjPD4YcS4pEGae7b";
String accessKeySecret = "sVID87zolwivkbSbG3ioXsYScXs0Py";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传文件流。
InputStream inputStream = new FileInputStream("F:\\JAVA\\谷粒商城电商项目-2020-尚硅谷-雷丰阳\\谷粒商城图片\\product55.png");
ossClient.putObject("gulimall-ghh", "product55.png", inputStream);
// 关闭OSSClient。
ossClient.shutdown();
System.out.println("上传成功");
}
③endpoint的取值:
④accessKeyId和accessKeySecret需要创建一个RAM账号:
注册成功的RAM账户:accessKeyId和accessKeySecret如下复制即可
⑤要想后端代码上传成功,还要添加权限
⑥启动测试
方式2:SpringCloud Alibaba-OSS
https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample
① 在gulimall-common服务的pom文件中导入依赖(记住要加入版本号,因为这个最高才2.2.0,与父项目的版本不匹配):
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
② 在gulimall-product服务的application.yml文件中配置access-key和secret-key
spring:
cloud:
alicloud:
access-key: LTAI4GAJzjPD4YcS4pEGae7b
secret-key: sVID87zolwivkbSbG3ioXsYScXs0Py
oss:
endpoint: oss-cn-beijing.aliyuncs.com
③ 测试:GulimallProductApplicationTests.java
因为导入了yml配置文件,所以可以注释掉相关代码
@Resource
OSSClient ossClient;
@Test
public void test() throws FileNotFoundException {
/*// Endpoint以杭州为例,https://oss.console.aliyun.com/bucket/oss-cn-hangzhou/gulimall-hengheng/overview
String endpoint = "oss-cn-beijing.aliyuncs.com";
// 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,https://ram.console.aliyun.com/users/gulimall/
String accessKeyId = "LTAI4GAJzjPD4YcS4pEGae7b";
String accessKeySecret = "sVID87zolwivkbSbG3ioXsYScXs0Py";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);*/
// 上传文件流。
InputStream inputStream = new FileInputStream("F:\\JAVA\\谷粒商城电商项目-2020-尚硅谷-雷丰阳\\谷粒商城图片\\product9.png");
ossClient.putObject("gulimall-ghh", "product9.png", inputStream);
// 关闭OSSClient。
ossClient.shutdown();
System.out.println("上传成功");
}
3、创建gulimall-third-party服务集成OSS服务
① 创建gulimall-third-party服务专门用来集成第三方服务,修改pom文件,将OSS这个第三方SDK不再放入gulimall-common中而是放入gulimall-third-party服务中:
将gulimall-common中的spring-cloud-starter-alicloud-oss的依赖剪切到gulimall-third-party的pom.xml里,因为其他微服务继承common,这样每一个微服务yml文件都需要配置OSS,如果不配置就报错。所以采用下面方式:
<dependencies>
<dependency>
<groupId>com.atguigu.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>
<!--OSS文件上传-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
② 编写bootstrap.properties文件,配置nacos的配置中心地址,同时加载oss.yml中配置:
spring.cloud.nacos.config.server-addr=localhost:8848
spring.application.name=gulimall-third-party
spring.cloud.nacos.config.namespace=b4d070ce-6025-4963-b873-f16c542c9128
spring.cloud.nacos.config.ext-config[0].data-id=oss.yml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true
③编写thirdparty微服务的application.yml。
将product的oss配置剪切到thirdparty里,因为oss作为第三方以后应该放在第三方微服务里
spring:
application:
name: gulimall-third-party
cloud:
nacos:
discovery:
server-addr: localhost:8848
alicloud:
access-key: LTAI4GAJzjPD4YcS4pEGae7b
secret-key: sVID87zolwivkbSbG3ioXsYScXs0Py
oss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket: gulimall-ghh
server:
port: 30000
④将原先的product微服务的测试也剪切到thirdparty微服务的测试类;然后主启动添加@EnableDiscoveryClient
测试类:
@Resource
private OSSClient ossClient;
@Test
public void test() throws FileNotFoundException {
InputStream inputStream = new FileInputStream("F:\\JAVA\\谷粒商城电商项目-2020-尚硅谷-雷丰阳\\谷粒商城图片\\product60.png");
ossClient.putObject("gulimall-ghh", "product60.png", inputStream);
// 关闭OSSClient。
ossClient.shutdown();
System.out.println("上传成功");
}
4、OSS服务端签名后直传
原因:为什么采用OSS服务端签名后直传?
采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,OSS提供了服务端签名后直传的方案。
原理介绍:
服务端签名后直传的原理如下:
- 用户发送上传Policy请求到应用服务器。
- 应用服务器返回上传Policy和签名给用户。
- 用户直接上传数据到OSS。
① 在gulimall-third-party服务中编写com.bigdata.gulimall.thirdparty.controller.OssController类:
@RestController
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("/oss/policy")
public Map<String,String> policy() {
// host的格式为 bucketname.endpoint
String host = "https://" + bucket + "." + endpoint;
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));
} catch (Exception e) {
System.out.println(e.getMessage());
}
return respMap;
}
}
② 访问http://localhost:30000/oss/policy,可以得到签名数据:
{"accessid":"LTAI4GAJzjPD4YcS4pEGae7b","policy":"eyJleHBpcmF0aW9uIjoiMjAyMS0wMS0wNVQwNToyMzo1MS4zOTNaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIxLTAxLTA1LyJdXX0=","signature":"DXOPaKpr2Trug1md6SfJmuynolo=","dir":"2021-01-05/","host":"https://gulimall-ghh.oss-cn-beijing.aliyuncs.com","expire":"1609824231"}
③ 在gulimall-gateway中application.yml配置网关路由, 上传文件路径为:http://localhost:88/api/thirdparty/oss/policy
这个精确的要放在模糊的前面,防止顺序出错,带来结果出错
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
5、OSS前后端联调测试上传
① 将项目提供的upload文件放在renren-fast-vue\src\components目录下,然后将两个文件的文件上传地址,改为阿里云提供的 Bucket 域名:http://gulimall-ghh.oss-cn-beijing.aliyuncs.com
② 在brand-add-or-update.vue文件中使用singleUpload.vue这个单文件上传组件,如何使用?
首先,需要在brand-add-or-update.vue文件中导入外部组件singleUpload.vueL:
//导入外部组件
import SingleUpload from "@/components/upload/singleUpload";
其次,在components中指明这个vue组件中需要用到哪些组件,指明后就可以在vue中使用了:
export default {
components: {SingleUpload},
}
最后,在vue组件中使用这个组件:
<el-form-item label="品牌logo地址" prop="logo">
<!-- 属性名要和components指明的组件名称相同,SingleUpload或single-upload -->
<single-upload v-model="dataForm.logo"></single-upload>
</el-form-item>
③ 修改后端com.bigdata.gulimall.thirdparty.controller.OssController类的返回值:
@RestController
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("/oss/policy")
public R policy() {
String host = "https://" + bucket + "." + endpoint;
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));
} catch (Exception e) {
System.out.println(e.getMessage());
}
// return respMap;
return R.ok().put("data",respMap);
}
}
④ 在页面点击新增,然后文件上传品牌logo,但是出现了如下的问题:
这又是一个跨域问题,在阿里云上开启跨域权限:
⑤ 解决完成后,重新进行文件上传,即可:
5.4 表单校验
1、前端表单校验
① 显示图片:
点击新增,新增一个品牌:
在brand.vue中动态绑定图片地址:
<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>
② 前端表单校验:
参考:https://element.eleme.cn/#/zh-CN/component/form,ElementUI的form表单组件:
在brand-add-or-update.vue中添加:
<el-form-item label="排序" prop="sort">
<!--将sort字段绑定一个数字-->
<el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>
dataRule: {
name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
logo: [{ required: true, message: "品牌logo地址不能为空", trigger: "blur" }],
descript: [{ required: true, message: "介绍不能为空", trigger: "blur" }],
showStatus: [
{
required: true,
message: "显示状态",
trigger: "blur",
},
],
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()
}
},
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"
}
},
2、JSR303后端数据校验
①添加后端JSR303时,需要添加以下依赖
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.20.Final</version>
</dependency>
②BrandEntity实体类添加校验注解,并添加自己的message提示:
//品牌
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull(message = "修改必须指定品牌id")
@Null(message = "新增不能指定id")
@TableId
private Long brandId;
@NotBlank(message = "品牌名必须提交")
private String name;
//该注解不能为null,并且至少包含一个非空字符。
@NotBlank
@URL(message = "logo必须是一个合法的url地址")
private String logo;
private String descript;
private Integer showStatus;
@NotEmpty
@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母")
private String firstLetter;
@NotNull
@Min(value = 0,message = "排序必须大于等于0")
private Integer sort;
}
③BrandController
开启校验功能@Valid,接收校验出错的结果BingResult:
/**
* 保存
*/
@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)->{
//获取错误的属性的名字
String field = item.getField();
//FieldError 获取到错误提示
String message = item.getDefaultMessage();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}else {
brandService.save(brand);
return R.ok();
}
}
④使用postman测试:http://localhost:88/api/product/brand/save
3、统一异常处理
上一节中对于参数校验发生的异常,我们使用了 BindingResult result这个变量来接收,但是这样做太复杂,因为参数校验的实体类很多,我们需要在每个Controller层的相应方法中加上参数校验并接收异常响应结果,因此只需要做统一异常处理即可,即将Controller层中所有的异常都抛出去,然后统一处理Controller层的异常。
① 在com.atguigu.gulimall**.product.e**xception包下新建一个统一异常处理类:
@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());
}
}
②在package com.atguigu**.common**.exception包下封装一个枚举类,定义各种响应状态码和响应消息:
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;
}
}
③将原先的BrandController的save方法修改,只保留成功的方法(因为异常方法已经在统一异常处理了GulimallExceptionControllerAdvice类)
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
④postman测试:http://localhost:88/api/product/brand/save
4、JSR303分组数据校验
新增品牌和修改品牌的某些字段的注解校验规则不一样时,可以分组校验,校验注解只有在指定的分组下才生效,而且如果开启了分组校验注解功能,那些没有指定分组的校验注解就会不生效。
@Validated(UpdateGroup.class)分组校验
@Validated不分组校验
① 在com.atguigu.common.valid包下定义两个接口,不用写具体实现:
public interface UpdateGroup {
}
public interface AddGroup {
}
② 开启分组校验注解功能,在方法上使用@Validated({AddGroup.class})注解指明校验字段所属的分组类:
/**
* 保存/新增
*/
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
public R update(@Validated(UpdateGroup.class)@RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
③ 给校验注解标注什么情况需要进行校验,默认没有指定分组的校验注解,在开启了分组校验的情况下不生效。
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
@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;
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
private String logo;
private String descript;
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;
}
5、JSR303自定义数据校验
在gulimall-common服务中,com.atguigu.common.valid包下:
① 自定义一个校验注解:ListValue
@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 { };
}
ValidationMessages.properties文件:
com.atguigu.common.valid.ListValue.message=必须提交指定的值
② 编写一个自定义校验器ConstraintValidator:
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);
}
}
//判断是否校验成功
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
//判断set集合中是否包含这个值,如果不包含就报错
return set.contains(value);
}
}
public interface UpdateStatusGroup {
}
③④是gulimall-product的entity和controller
③ 给BrandEntity实体类的showStatus字段添加自定义校验注解@ListValue并指明分组:
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
④ BrandController类 @Validated(UpdateStatusGroup.class)开启注解校验功能:
@RequestMapping("/update/status")
public R updateStatus(@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
⑤ 测试:
访问新增方法:http://localhost:88/api/product/brand/save
访问修改方法:http://localhost:88/api/product/brand/update/status
⑥ 测试前后端联调,前端点击新增和修改一个品牌: