赞
踩
① 在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;
}
④测试新增和删除一个品牌,均正确
①前端代码
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: "状态更新成功"
})
})
},
需求:点击新增后显示新增品牌,我们希望将品牌logo进行文件上传,和传统的单体应用不同,这里我们选择将数据上传到分布式文件服务器上。
① 进入https://www.aliyun.com/,选择产品,存储,对象存储OSS,立即开通
② 使用支付宝登录后完成实名认证,然后再开通对象存储OSS,管理控制台创建Bucket:
③ 找到创建的bucket,点击文件上传,上传一张图片,然后点击详情,复制url即可在浏览器访问呢图片:
④ 这种方式是手动上传图片,实际上我们可以在程序中设置自动上传图片到阿里云对象存储, 文件上传方式:
参考: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("上传成功"); }
① 创建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("上传成功");
}
原因:为什么采用OSS服务端签名后直传?
采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,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}
① 将项目提供的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,但是出现了如下的问题:
这又是一个跨域问题,在阿里云上开启跨域权限:
⑤ 解决完成后,重新进行文件上传,即可:
① 显示图片:
点击新增,新增一个品牌:
在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" } },
①添加后端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
上一节中对于参数校验发生的异常,我们使用了 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
新增品牌和修改品牌的某些字段的注解校验规则不一样时,可以分组校验,校验注解只有在指定的分组下才生效,而且如果开启了分组校验注解功能,那些没有指定分组的校验注解就会不生效。
@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; }
在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
⑥ 测试前后端联调,前端点击新增和修改一个品牌:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。