谷粒商城--品牌管理详情
目录
1.简单上传测试
2.Aliyun Spring Boot OSS
3.模块mall-third-service
4.前端
5.数据校验
6.JSR303数据校验
7.分组校验功能
8.自定义校验功能
9.完善代码
1.简单上传测试
OSS是对象存储服务,有什么用呢?把图片存储到云服务器上能让所有人都访问到!
详细操作可查官方文档,下面只写关键代码
OSSJavaSDK兼容性和示例代码_对象存储-阿里云帮助中心
创建子用户测试用例
官方推荐使用子账户的AccessID和SecurityID,因为如果直接给账户的AccessID和SecurityID的话,如果不小心被其他人获取到了,那账户可是有全部权限的!!!
所以这里通过建立子账户,给子账户分配部分权限实习。
这里通过子账户管理OSS的时候,要给子账户添加操控OSS资源的权限
这里是必须要做的,因为子账户默认是没有任何权限的,必须手动给他赋予权限
引入依赖
<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.1.0</version>
</dependency>
测试用例
@SpringBootTest
class MallProductApplicationTests {@Testpublic void testUploads() throws FileNotFoundException {// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。String accessKeyId = "。。。";String accessKeySecret = "。。。";// 填写Bucket名称,例如examplebucket。String bucketName = "pyy-mall";// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。String objectName = "2022/testPhoto.txt";// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。String filePath= "C:\\Users\\Jack\\Desktop\\R-C.jfif";// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);try {InputStream inputStream = new FileInputStream(filePath);// 创建PutObject请求。ossClient.putObject(bucketName, objectName, inputStream);} catch (OSSException oe) {System.out.println("Caught an OSSException, which means your request made it to OSS, "+ "but was rejected with an error response for some reason.");System.out.println("Error Message:" + oe.getErrorMessage());System.out.println("Error Code:" + oe.getErrorCode());System.out.println("Request ID:" + oe.getRequestId());System.out.println("Host ID:" + oe.getHostId());} catch (ClientException ce) {System.out.println("Caught an ClientException, which means the client encountered "+ "a serious internal problem while trying to communicate with OSS, "+ "such as not being able to access the network.");System.out.println("Error Message:" + ce.getMessage());} finally {if (ossClient != null) {ossClient.shutdown();}}}}
2.Aliyun Spring Boot OSS
aliyun-spring-boot/README-zh.md at master · alibaba/aliyun-spring-boot · GitHubSpring Boot Starters for Aliyun services. Contribute to alibaba/aliyun-spring-boot development by creating an account on GitHub.https://github.com/alibaba/aliyun-spring-boot/blob/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample/README-zh.md引入依赖
我们不是进行依赖管理了吗?为什么还要显示写出2.1.1版本
这是因为这个包没有最新的包,只有和2.1.1匹配的
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alicloud-oss</artifactId><version>2.1.1.RELEASE</version></dependency>
在配置文件中配置 OSS 服务对应的 accessKey、secretKey 和 endpoint
alicloud:access-key: xxxsecret-key: xxxoss:endpoint: oss-cn-hangzhou.aliyuncs.com
注入OSSClient测试
@Resource
private OSSClient ossClient;@Test
public void testUploads() throws FileNotFoundException {// 上传文件流。InputStream inputStream = new FileInputStream("C:\\Users\\Jack\\Desktop\\LeetCode_Sharing.png");ossClient.putObject("pyy-mall", "2022/testPhoto2.png", inputStream);// 关闭OSSClient。ossClient.shutdown();System.out.println("上传完成...");}
3.模块mall-third-service
模块存放所有的第三方服务,像短信服务、图片服务、视频服务等等
引入依赖如下:
这里去除了mp的依赖,因为引入mp就需要配置数据库服务器地址
配置文件
<dependencies><dependency><groupId>com.xxh.mall</groupId><artifactId>mall-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>com.alibaba.cloud</groupId><artifactId>spring-cloud-alicloud-oss</artifactId><version>2.1.1.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><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>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>
启动类添加注解
主启动类@EnableDiscoveryClient
application.yml配置文件如下:
#用来指定注册中心地址
spring:cloud:nacos:discovery:server-addr: localhost:8848 #nacos地址alicloud:access-key: ...secret-key: ...oss:endpoint: oss-cn-hangzhou.aliyuncs.combucket: pyy-mall
bootstrap.yml文件指定注册中心
#用来指定配置中心地址
spring:application:name: mall-third-servicecloud:nacos:config:server-addr: 127.0.0.1:8848namespace: 2bbd2076-36c8-44b2-9c2e-17ce5406afb7file-extension: yamlextension-configs:- data-id: mall-third-service.ymlgroup: DEFAULT_GROUPrefresh: true
测试
@SpringBootTest
class MallThirdServiceApplicationTests {@ResourceOSSClient ossClient;@Testvoid contextLoads() throws FileNotFoundException {// 上传文件流。InputStream inputStream = new FileInputStream("C:\\Users\\Jack\\Desktop\\LeetCode_Sharing.png");ossClient.putObject("pyy-mall", "2022/testPhoto3.png", inputStream);// 关闭OSSClient。ossClient.shutdown();System.out.println("上传完成...");}
}
改善上传
服务端签名后直传
采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。
因此,OSS提供了服务端签名后直传的方案。
向服务器获取到签名,再去请求oss服务器
controller如下:
这里定义返回类为R是为了统一返回结果,到后面也会用到
package com.xxh.mall.thirdservice.controller;@RestController
@RequestMapping("oss")
public class OssController {@Resourceprivate OSS ossClient;@Value("${spring.cloud.alicloud.oss.endpoint}")public String endpoint;@Value("${spring.cloud.alicloud.oss.bucket}")public String bucket;@Value("${spring.cloud.alicloud.access-key}")public String accessId;private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");@GetMapping("/policy")public R getPolicy(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint// callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。// String callbackUrl = "http://88.88.88.88:8888";String dir = format.format(new Date())+"/"; // 用户上传文件时指定的前缀。以日期格式存储// 创建OSSClient实例。Map<String, String> respMap= null;try {long expireTime = 30;long expireEndTime = System.currentTimeMillis() + expireTime * 1000;Date expiration = new Date(expireEndTime);// PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。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());} finally {ossClient.shutdown();}return R.ok().put("data",respMap);}}
测试这个请求,http://localhost:9988/oss/policy
,成功获取
设置网关代理
- id: mall-third-serviceuri: lb://mall-third-servicepredicates:- Path=/api/thirdservice/**filters:- RewritePath= /api/thirdservice/(?<segment>.*),/$\{segment}
测试这个请求,http://localhost:88/api/thirdservice/oss/policy
至此,我们的功能都没问题了,那么现在就来前端的代码
4.前端
singleUpload.vue
单文件上传组件
<template><div><el-uploadaction="http://gulimall-hello.oss-cn-beijing.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;},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>
multiUpload.vue
多文件上传组件
<template><div><el-uploadaction="http://gulimall-hello.oss-cn-beijing.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;},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>
policy.js
服务端签名
import http from '@/utils/httpRequest.js'
export function policy() {return new Promise((resolve,reject)=>{http({url: http.adornUrl("/third-party/oss/policy"),method: "get",params: http.adornParams({})}).then(({ data }) => {resolve(data);})});
}
阿里云开启跨域
测试
图片可以正常上传和显示
5.数据校验
前端数据校验
就是规定添加的属性要符合规定,不然会出现想不到的异常!
例如:添加品牌选项框中,设置检索首字母那么我们就要规定首字母不能是多个字母只能是a-z或A-Z之间的一个
那么我们就可以对这个输入框进行绑定,如下
实现效果如下:
6.JSR303数据校验
后端的处理前端传来的数据时,虽然前端已做限制但是还不够严谨,例如我们可以跳过页面通过一些工具直接发送请求也可以完成添加等操作,所以后端也需要做数据校验!
java中也提供了一系列的校验方式,它这些校验方式在“javax.validation.constraints”包中,@Email,@NotNull等注解。
添加依赖
后面可能其他模块也能用到,所以这里把依赖添加到common模块
<!--jsr3参数校验器-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId><version>2.3.2.RELEASE</version>
</dependency>
这个依赖提供了NotNull,@NotBlank和@NotEmpty这些判断
给需要校验的bean添加注解
/*** 品牌* * @author xxh*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@TableIdprivate Long brandId;/*** 品牌名*/@NotBlankprivate String name;/*** 品牌logo地址*/private String logo;/*** 介绍*/private String descript;/*** 显示状态[0-不显示;1-显示]*/@NotNullprivate Integer showStatus;/*** 检索首字母*/@NotEmptyprivate String firstLetter;/*** 排序*/@NotNull@Min(0)private Integer sort;}
未在控制类中指定方法开启校验时如下:
controller中给请求方法加校验注解@Valid
,开启校验
/*** 保存*/
@RequestMapping("/save")
public R save(@RequestBody @Valid BrandEntity brand){brandService.save(brand);return R.ok();
}
这里我错误信息只返回是Bad Request
视频中老师所讲的是有详细信息的,这个差异应该是版本原因不影响!
这种返回的错误结果并不符合我们的业务需要。我们想让捕捉这个错误的详细信息,并且能够统一返回我们自定义的信息
通过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)->{//获取发生错误时的messageString 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();
}
再次测试
但是,这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以
统一的对于异常进行处理
。
统一异常处理
可以使用SpringMvc所提供的@ControllerAdvice,通过“basePackages”能够说明处理哪些路径下的异常。
抽取一个异常处理类
@Slf4j
@RestControllerAdvice(basePackages = "com.caq.mall.product.controller")
public class MallExceptionAdvice {//指定的包下所有的校验异常都会被这个方法捕捉@ExceptionHandler(value = MethodArgumentNotValidException.class)public R handleValidException(MethodArgumentNotValidException exception) {//定义map,存放所有错误信息Map<String, String> map = new HashMap<>();//通过BindResult捕获校验结果BindingResult bindingResult = exception.getBindingResult();//遍历校验结果中所有字段的错误,字段为key,错误信息为value存放到map中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
接下来我们去掉我们控制类中save方法的校验,看看统一异常处理能否生效
错误状态码
正规开发过程中,错误状态码有着严格的定义规则
/**** 错误码和错误信息定义类* 1. 错误码定义规则为5为数字* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式* 错误码列表:* 10: 通用* 001:参数格式校验* 11: 商品* 12: 订单* 13: 购物车* 14: 物流*/
public enum BizCodeEnum {UNKNOW_EXEPTION(10000,"系统未知异常"),VALID_EXCEPTION( 10001,"参数格式校验失败");private int code;private String msg;BizCodeEnum(int code, String msg) {this.code = code;this.msg = msg;}public int getCode() {return code;}public String getMsg() {return msg;}
}
默认异常处理
上面的统一异常处理只是针对了校验相关的错误,那么如果是其他异常呢?
那就再来个默认的异常处理呗
@ExceptionHandler(value = Throwable.class)public R handleException(Throwable throwable){log.error("未知异常{},异常类型{}",throwable.getMessage(),throwable.getClass());return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),BizCodeEnum.UNKNOW_EXEPTION.getMsg());}
7.分组校验功能
给校验注解,标注上groups,指定什么情况下才需要进行校验
如:指定在更新和添加的时候,都需要进行校验,我们对id进行限制
/*** 品牌id*/@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})@Null(message = "新增不能指定id",groups = {AddGroup.class})@TableIdprivate Long brandId;
这里的UpdateGroup和AddGroup都需要收到创建一下,为了演示可以只创建不写内容
使用@Validated注解
@Validated(AddGroup.class)指定新增的时候注解才会生效
其他的注解字段,即使标注校检也不生效
/*** 保存*/
@RequestMapping("/save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand){brandService.save(brand);return R.ok();
}
测试
因为指定了新增不能指定id,但是我们测试的时候加id了所以返回错误信息
测试其他字段
可以看到即使name字段加非空了,我们测试用空值也是可以生效的
说明在分组校验情况下,没有指定指定分组的校验注解,将不会生效,它只会在不分组的情况下生效。
8.自定义校验功能
导入依赖
<dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version>
</dependency>
编写自定义注解
注解的格式不会写怎么办
直接复制其他注解的形式
特别说明
- @Target的意思是说,这个注解能标注在哪里,后面通过{}进行指定
- String message() default “{com.caq.common.valid.ListValue.message}”;这个意思是指,@ListValue注解的错误提示信息会去配置文件中找这个message的值当作信息
- int[] vals() default {};这里的定义是值@ListValue这个注解可以有变量名为vals的int数组做为参数
@Constraint( validatedBy = {})的说明
validatedBy指定这个注解由哪一个校验器校验,详细信息如图:
配置注解错误返回信息
在resource文件下创建:ValidationMessages.properties
com.zsy.common.valid.ListValue.message=必须提交指定的值
自定义的校验器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {private final Set<Integer> set = new HashSet<>();/*** 初始化方法* 参数:自定义注解的详细信息*/@Overridepublic void initialize(ListValue constraintAnnotation) {//constraintAnnotation.vals()意思是获得你注解里的参数int[] values = constraintAnnotation.vals();//把获取到的参数放到set集合里for (int v al : values) {set.add(val);}}/*** 判断是否校验成功* @param value 需要校验的值*/@Overridepublic boolean isValid(Integer value, ConstraintValidatorContext context) {//这里的Integer value参数是指你注解里提交过来的参数//之后判断集合里是否有这个传进来的值,如果有返回true,没的话返回false并返回错误信息return set.contains(value);}
}
关联自定义校验器
通过validatedBy = {ListValueConstraintValidator.class}去指定即可!
那如果以后@ListValue注解支持的属性类型变为double了,我们只需要在指定新的校验器即可
/*** 自定义校验注解 声明可以取那些值*/
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {String message() default "{com.caq.common.validation.ListValue.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};int[] vals() default {};
}
测试
我们给状态字段指定分组检验,让它增加的时候才进行校验
/*** 显示状态[0-不显示;1-显示]*/@ListValue(vals = {0, 1}, groups = {AddGroup.class})private Integer showStatus;
读取properties文件内容乱码
设置好,清理target,重新编译,再次测试
9.完善代码
做检验的字段
/*** 品牌* @author xxh*/
@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})@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})private String name;/*** 品牌logo地址*/@NotBlank(groups = {AddGroup.class})@URL(message = "logo必须是一个合法的url地址", groups = {AddGroup.class, UpdateGroup.class})private String logo;/*** 介绍*/private String descript;/*** 显示状态[0-不显示;1-显示]*/
// @Pattern()@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;
}
controller中共三个方法做了数据校验
/*** 保存*/@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();}@RequestMapping("/update/status")public R updateStatus(@Validated({UpdateStatusGroup.class}) @RequestBody BrandEntity brand){brandService.updateById(brand);return R.ok();}
测试前后端的校验
测试状态修改
测试修改
测试新增
相关文章:
谷粒商城--品牌管理详情
目录 1.简单上传测试 2.Aliyun Spring Boot OSS 3.模块mall-third-service 4.前端 5.数据校验 6.JSR303数据校验 7.分组校验功能 8.自定义校验功能 9.完善代码 1.简单上传测试 OSS是对象存储服务,有什么用呢?把图片存储到云服务器上能让所有人…...
stack、queue和priority_queue
目录 一、栈(stack) 1.stack的使用 2.容器适配器 3.stack的模拟实现 二、队列(queue) 1.queue的使用 2.queue的模拟实现 三、双端队列(deque) 1.vector,list的优缺点 2.认识deque 四…...
面试题(二十二)消息队列与搜索引擎
2. 消息队列 2.1 MQ有什么用? 参考答案 消息队列有很多使用场景,比较常见的有3个:解耦、异步、削峰。 解耦:传统的软件开发模式,各个模块之间相互调用,数据共享,每个模块都要时刻关注其他模…...
Spring Security in Action 第三章 SpringSecurity管理用户
本专栏将从基础开始,循序渐进,以实战为线索,逐步深入SpringSecurity相关知识相关知识,打造完整的SpringSecurity学习步骤,提升工程化编码能力和思维能力,写出高质量代码。希望大家都能够从中有所收获&#…...
Java面试——maven篇
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
基于微信小程序的游戏账号交易小程序
文末联系获取源码 开发语言:Java 框架:ssm JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7/8.0 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包:Maven3.3.9 浏览器…...
Matlab绘制隐函数总结-二维和三维
1.二维隐函数 二维隐函数满足f(x,y)0f(x,y)0f(x,y)0,这里无法得到yf(x)yf(x)yf(x)的形式。不能通过普通函数绘制。 我们要关注的是使用fplot函数和fimplicit函数。 第1种情况:基本隐函数 基本的隐函数形式形如: x2y22x2(x2y2)12x^{2}y^{…...
如何直观地理解傅立叶变换?频域和时域的理解
如何直观地理解傅立叶变换 傅里叶变换连续形式的傅立叶变换如何直观地理解傅立叶变换?一、傅里叶级数1.1傅里叶级数的三角形式1.2 傅里叶级数的复指数形式二、傅里叶变换2.1一维连续傅里叶变换三、频谱和功率谱3.1频谱的获得3.2频谱图的特征3.3频谱图的组成频域(frequency do…...
STC15读取内部ID示例程序
STC15读取内部ID示例程序🎉本案例基于STC15F2K60S2为验证对象。 📑STC15 ID序列介绍 STC15系列STC最新一代STC15系列单片机出厂时都具有全球唯一身份证号码(ID号)。最新STC15系列单片机的程序存储器的最后7个字节单元的值是全球唯一ID号,用…...
Xml格式化与高亮显示
具体请参考:Xml格式化与高亮显示...
【GlobalMapper精品教程】045:空间分析工具(2)——相交
GlobalMapper提供的空间分析(操作)的方法有:交集、并集、单并集、差异、对称差集、相交、重叠、接触、包含、等于、内部、分离等,本文主要讲述相交工具的使用。 文章目录 一、实验数据二、符号化设置三、相交运算四、结果展示五、心灵感悟一、实验数据 加载配套实验数据(…...
4年外包终上岸,我只能说这类公司能不去就不去..
我大学学的是计算机专业,毕业的时候,对于找工作比较迷茫,也不知道当时怎么想的,一头就扎进了一家外包公司,一干就是4年。现在终于跳槽到了互联网公司了,我想说的是,但凡有点机会,千万…...
sklearn降维算法1 - 降维思想与PCA实现
目录1、概述1.1 维度概念2、PCA与SVD2.1 降维实现2.2 重要参数n_components2.2.1 案例:高维数据的可视化2.2.2 最大似然估计自选超参数2.2.3 按信息量占比选超参数1、概述 1.1 维度概念 shape返回的结果,几维几个方括号嵌套 特征矩阵特指二维的 一般来…...
「期末复习」线性代数
第一章 行列式 行列式是一个数,是一个结果三阶行列式的计算:主对角线的乘积全排列与对换逆序数为奇就为奇排列,逆序数为偶就为偶排列对换:定理一:一个排列的任意两个元素对换,排列改变奇偶性(和…...
伏并网低电压穿越技术
国内光伏并网低电压穿越要求 略: 低电压穿越方法 当前,光伏电站实现低电压穿越可通过两种方式,即增加硬件设备或者改变控制策略。本节对基于储能设备、基于无功补偿设备、基于无功电流电压支撑控制策略三种实现LVRT的典型方法进行介绍。 …...
opencv的环境搭建
大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页: lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...
C++智能指针
c11的三个智能指针 unique_ptr独占指针,用的最多 shared_ptr记数指针,其次 weak_ptr,shared_ptr的补充,很少用 引用他们要加上头文件#include unique_ptr独占指针: 1.只能有一个智能指针管理内存 2.当指针超出作用域…...
MongoDB--》MongoDB数据库以及可视化工具的安装与使用—保姆级教程
目录 数据库简介 MongoDB数据库的安装 MongoDB数据库的启动 MongoDB数据库环境变量的配置 MongoDB图形化管理工具 数据库简介 在使用MongoDB数据库之前,我们应该要知道我们使用它的原因: 在数据库当中,有常见的三高需求: Hi…...
JAVA 基础题
1. 面向对象有哪些特征?答:继承、封装、多态2. JDK与JRE的区别是什么?答:JDK是java开发时所需环境,它包含了Java开发时需要用到的API,JRE是Java的运行时环境,JDK包含了JRE,他们是包含…...
Flutter desktop端多屏幕展示问题处理
目前越来越多的人用Flutter来做桌面程序的开发,很多应用场景在Flutter开发端还不是很成熟,有些场景目前还没有很好的插件来支持,所以落地Flutter桌面版还是要慎重。 下面来说一下近期我遇到的一个问题,之前遇到一个需要双屏展示的…...
每天10个前端小知识 【Day 9】
👩 个人主页:不爱吃糖的程序媛 🙋♂️ 作者简介:前端领域新星创作者、CSDN内容合伙人,专注于前端各领域技术,成长的路上共同学习共同进步,一起加油呀! ✨系列专栏:前端…...
Elasticsearch的读写搜索过程
问题 Elasticsearch在读写数据的过程是什么样的?你该如何理解这个问题! Elasticsearch的写数据过程 客户端选择一个节点发送请求,这个时候我们所说的这个节点就是协调节点(coordinating node)协调节点对document进行了路由&am…...
线上服务质量的问题该如何去处理?你有什么思路?
线上服务质量的问题该如何去处理?你有什么思路? 目录:导读 发现线上故障 处理线上故障 修复线上故障 运营线上质量 就是前几天有个同学问了我一个问题:目前业内高可用部署主要采用方案? 看到这个问题,…...
IOC 配置,依赖注入的三种方式
xml 配置 顾名思义,就是将bean的信息配置.xml文件里,通过Spring加载文件为我们创建bean。这种方式出现很多早前的SSM项目中,将第三方类库或者一些配置工具类都以这种方式进行配置,主要原因是由于第三方类不支持Spring注解。 优点…...
自动机,即有限状态机
文章目录一、问题来源二、题目描述三、题解中的自动机四、自动机学习五、有限状态机的使用场景一、问题来源 今天做力克题目的时候看到了字符串转换整数的一道算法题,其中又看到了题解中有自动机的概念,所以在这里对自动机做个笔记。题目链接 二、题目描…...
第一部分:简单句——第一章:简单句的核心——二、简单句的核心变化(主语/宾语/表语的变化)
二、简单句的核心变化 简单句的核心变化其实就是 一主一谓(n. v.) 表达一件事情,谓语动词是其中最重要的部分,谓语动词的变化主要有四种:三态加一否(时态、语态、情态、否定),其中…...
VSCode Markdown写作引入符合规范的参考文献
Markdown可以用来写论文,写论文的时候无一例外要用到参考文献,今天来谈谈怎么自动生成参考文献。之前讲了怎么导出的pdf,文章在这里 VSCode vscode-pandoc插件将中文Markdown转换为好看的pdf文档(使用eisvogel模板) …...
电子学会2022年12月青少年软件编程(图形化)等级考试试卷(四级)答案解析
目录 一、单选题(共15题,共30分) 二、判断题(共10题,共20分) 三、编程题(共3题,共50分) 青少年软件编程(图形化)等级考试试卷(四级) 一、单选题(共15题,共30分) 1. 运行下列程序…...
JUC并发编程学习笔记(一)——知识补充(Threadlocal和引用类型)
强引用、弱引用、软引用、虚引用 Java执行 GC(垃圾回收)判断对象是否存活有两种方式,分别是引用计数法和引用链法(可达性分析法)。 **引用计数:**Java堆中给每个对象都有一个引用计数器,每当某个对象在其它地方被引用时,该对象的…...
2022级上岸浙理工MBA的复试经验提炼和备考建议
在等待联考成绩出来的那段时间,虽然内心很忐忑,但还是为复试在积极的做准备,虽然也进行了估分大概有201分,但成绩和分数线没下来之前,只能尽量多做些一些准备把。因为笔试报了达立易考的辅导班,对于浙江理工…...
信用渭南网站建设/企业网站seo优化
#include <stdio.h> #define T 10int main() {int i, j, a[T];printf("请输入%d个数字,空格分隔:\n", T - 1);for (i 1; i < T; i) //a[0] 存放交换时的临时数据scanf("%d", &a[i]);for (i 1; i < T; i) …...
外国人做的汉字网站/12345微信公众号
有的时候我们在使用ES时,由于资源有限或业务需求,我们只想保存最近一段时间的数据,所以有如下脚本可以定时删除数据 delete_es_by_day.sh #!/bin/sh # example: sh delete_es_by_day.sh logstash-kettle-log logsdate 30index_name$1 daycol…...
北京商地网站建设公司/微软优化大师
SimpleAdapter是ArrayList和 ListView的桥梁。这个ArrayList里边的每一项都是一个Map<String,?>类型。 ArrayList当中的每一项 Map对象都和ListView里边的每一项进行数据绑定一一对应。 SimpleAdapter的构造函数: SimpleAdapter(Context context, List<?…...
wordpress 联系地图/百度搜索高级搜索技巧
最近博客更新的少了,相对而言,我在自己的个人公众号里还是挺活跃的,大家可以扫描旁边的二维码,或者微信搜索公众号:“编程一生”加关注。 在分布式的年代,一个应用需要部署到多台服务器上。那么要查看日志文…...
中小企业建立网站最经济的方式/查关键词排名软件
最近在弄linux系统的环境,搭建vue环境,由于是第一次接触vue以前也没使用过,所以才搭建环境的时候还是挺闷逼的。记录一下,免得以后忘记了。 安装cnpm 由于不知道vue和npm的关系,所以自己想先安装npm并设置成阿里的cnpm…...
企业网站怎样做外链方法/google推广
题目描述: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 示例: 给定 nums …...