谷粒商城--品牌管理详情

news2024/11/16 11:58:26

目录

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资源的权限
这里是必须要做的,因为子账户默认是没有任何权限的,必须手动给他赋予权限

image-20220731223234229

引入依赖

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.1.0</version>
</dependency>

测试用例

@SpringBootTest
class MallProductApplicationTests {

    @Test
    public 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();
            }
        }
    }

}

image-20220731223835859

 


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: xxx
      secret-key: xxx
      oss:
        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("上传完成...");

}

image-20220802215310630


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>

 image-20220803164312380

 启动类添加注解

主启动类@EnableDiscoveryClient

application.yml配置文件如下:

#用来指定注册中心地址
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #nacos地址
    alicloud:
      access-key: ...
      secret-key: ...
      oss:
        endpoint: oss-cn-hangzhou.aliyuncs.com
        bucket: pyy-mall

bootstrap.yml文件指定注册中心

#用来指定配置中心地址
spring:
  application:
    name: mall-third-service
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: 2bbd2076-36c8-44b2-9c2e-17ce5406afb7
        file-extension: yaml
        extension-configs:
          - data-id: mall-third-service.yml
            group: DEFAULT_GROUP
            refresh: true

测试

@SpringBootTest
class MallThirdServiceApplicationTests {
    @Resource
    OSSClient ossClient;

    @Test
    void 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 {

    @Resource
    private 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,成功获取

image-20220803165251881

 设置网关代理

- id: mall-third-service
  uri: lb://mall-third-service
  predicates:
    - Path=/api/thirdservice/**
  filters:
    - RewritePath= /api/thirdservice/(?<segment>.*),/$\{segment}

测试这个请求,http://localhost:88/api/thirdservice/oss/policy

image-20220803164139979

至此,我们的功能都没问题了,那么现在就来前端的代码


4.前端

singleUpload.vue

单文件上传组件 

<template>
  <div>
    <el-upload
      action="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-upload
      action="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);
        })
    });
}

 阿里云开启跨域

 

测试 

图片可以正常上传和显示

image-20220803200620561 

image-20220803200535023 

 


5.数据校验

前端数据校验

就是规定添加的属性要符合规定,不然会出现想不到的异常!

例如:添加品牌选项框中,设置检索首字母那么我们就要规定首字母不能是多个字母只能是a-z或A-Z之间的一个

那么我们就可以对这个输入框进行绑定,如下

image-20220803201346675 

实现效果如下:

image-20220803201146001 


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
    */
   @TableId
   private Long brandId;
   /**
    * 品牌名
    */
   @NotBlank
   private String name;
   /**
    * 品牌logo地址
    */
   private String logo;
   /**
    * 介绍
    */
   private String descript;
   /**
    * 显示状态[0-不显示;1-显示]
    */
   @NotNull
   private Integer showStatus;
   /**
    * 检索首字母
    */
   @NotEmpty
   private String firstLetter;
   /**
    * 排序
    */
   @NotNull
   @Min(0)
   private Integer sort;

}

未在控制类中指定方法开启校验时如下:

image-20220805214722499

controller中给请求方法加校验注解@Valid,开启校验

/**
 * 保存
 */
@RequestMapping("/save")
public R save(@RequestBody @Valid BrandEntity brand){
    brandService.save(brand);
    return R.ok();
}

 这里我错误信息只返回是Bad Request

视频中老师所讲的是有详细信息的,这个差异应该是版本原因不影响!

image-20220805214850184 

 这种返回的错误结果并不符合我们的业务需要。我们想让捕捉这个错误的详细信息,并且能够统一返回我们自定义的信息

通过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();
}

再次测试

image-20220805215355999

但是,这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以统一的对于异常进行处理。 

统一异常处理

可以使用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方法的校验,看看统一异常处理能否生效

image-20220805221126581 

错误状态码 

正规开发过程中,错误状态码有着严格的定义规则

/***
 * 错误码和错误信息定义类
 * 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})
	@TableId
	private 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了所以返回错误信息

image-20220805230241988 

测试其他字段

可以看到即使name字段加非空了,我们测试用空值也是可以生效的

说明在分组校验情况下,没有指定指定分组的校验注解,将不会生效,它只会在不分组的情况下生效。

 image-20220805230445826


8.自定义校验功能

导入依赖 

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

编写自定义注解

注解的格式不会写怎么办

直接复制其他注解的形式

特别说明

  1. @Target的意思是说,这个注解能标注在哪里,后面通过{}进行指定
  2. String message() default “{com.caq.common.valid.ListValue.message}”;这个意思是指,@ListValue注解的错误提示信息会去配置文件中找这个message的值当作信息
  3. int[] vals() default {};这里的定义是值@ListValue这个注解可以有变量名为vals的int数组做为参数

@Constraint( validatedBy = {})的说明

validatedBy指定这个注解由哪一个校验器校验,详细信息如图:

image-20220805233440319 

配置注解错误返回信息

在resource文件下创建:ValidationMessages.properties

com.zsy.common.valid.ListValue.message=必须提交指定的值

 自定义的校验器

public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
    private final Set<Integer> set = new HashSet<>();

    /**
     * 初始化方法
     * 参数:自定义注解的详细信息
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {
        //constraintAnnotation.vals()意思是获得你注解里的参数
        int[] values = constraintAnnotation.vals();
        //把获取到的参数放到set集合里
        for (int v al : values) {
            set.add(val);
        }
    }

    /**
     * 判断是否校验成功
     * @param value   需要校验的值
     */
    @Override
    public 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文件内容乱码

image-20220806001349906

设置好,清理target,重新编译,再次测试 

image-20220806001259826 


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})
    @TableId
    private 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();
  }

测试前后端的校验

测试状态修改

image-20220806002244394 

测试修改

image-20220806002715222 

 测试新增

image-20220806003002793

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/339904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

杨校老师课堂之JavaScript定时器案例的红绿灯设计--原始写法

主要介绍了JavaScript定时器设置、使用与倒计时案例,详细分析了javascript定时器的设置、取消、循环调用并附带一个倒计时功能应用案例,需要的朋友可以参考下&#xff1a; 运行效果图&#xff1a; 配套视频课程 基于JavaScript的红绿灯设计演示代码如下: <!DOCTYPE html>…

《Keras深度学习:入门、实战与进阶》之印第安人糖尿病诊断

本文摘自《Keras深度学习&#xff1a;入门、实战与进阶》。 1、数据理解 本节使用Pima Indians糖尿病发病情况数据集。该数据集最初来自国家糖尿病/消化/肾脏疾病研究所。数据集的目标是基于数据集中包含的某些诊断测量来诊断性的预测患者是否患有糖尿病。数据集由多个医学预…

python+django大学生成绩综合考评系统pycharm项目

开发语言&#xff1a;Python 框架&#xff1a;django Python版本&#xff1a;python3.7.7 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;PyCharm 通常 一个Django model 对应一张数据表&#xff0c;model是以类的形式表现的 实现了ORM 对象…

Unity SRP自定义渲染管线学习2.2: 合批(Batching) SRP Batcher

接下来我们要来学习下自定义渲染管线中的合批&#xff0c;这一节主要学习SRP Batcher 每一次的Draw Call都需要CPU和GPU之间的通信&#xff0c;如果有大量的数据需要从CPU发送到GPU中&#xff0c;那GPU就可能因为等待数据而浪费时间&#xff0c;而CPU会因为忙于发送数据导致无…

第五章:Windows server加域

加入AD域&#xff1a;教学视频&#xff1a;https://www.bilibili.com/video/BV1xM4y1D7oL/?spm_id_from333.999.0.0首先我们选择一个干净的&#xff0c;也就是新建的没动过的Windows server虚拟机。我们将DNS改成域的ip地址&#xff0c;还要保证它们之间能ping的通&#xff0c…

详细解读ChatGPT:如何调用ChatGPT的API接口到官方例子的说明以及GitHub上的源码应用

文章目录1. 解读ChatGPT1.1 词语解释1.2 功能解读2. GitHub上ChatGPT的应用源码3. 调用ChatGPT的API4. 官方例子说明5. 集成ChatGPT自ChatGPT出来到如今&#xff0c;始终走在火热的道路上&#xff0c;如今日活用户破亿&#xff0c;他为何有如此大的魅力&#xff0c;深受广大用户…

opencv保存图片

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

SIGIR22:User-controllable Recommendation Against Filter Bubbles

User-controllable Recommendation Against Filter Bubbles 摘要 推荐系统经常面临过滤气泡的问题&#xff1a;过度推荐基于用户特征以及历史交互的同质化项目。过滤气泡将会随着反馈循环增长&#xff0c;缩小了用户兴趣。现有的工作通常通过纳入诸如多样性和公平性等准确性之…

AcWing、第 90 场周赛:4806. 首字母大写、4807. 找数字、4808. 构造字符串(C++)

目录 4806. 首字母大写 题目描述&#xff1a; 实现代码&#xff1a; 4807. 找数字 题目描述&#xff1a; 实现代码&#xff1a; 回溯&#xff08;超时&#xff09;&#xff1a; 原理思路&#xff1a; 贪心&#xff1a; 原理思路&#xff1a; 4808. 构造字符串 问题…

53. 最大子数组和

文章目录题目描述暴力法动态规划法分治法参考文献题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 示例 1&#xff1a; 输入&…

JavaScript中数组常用的方法

文章目录前言常用数组方法1、 join( )2、push&#xff08;&#xff09;与 pop&#xff08;&#xff09;3、shift&#xff08;&#xff09;与 unshift&#xff08;&#xff09;4、sort&#xff08;&#xff09;5、reverse&#xff08;&#xff09;6、slice&#xff08;&#xff…

代码随想录算法训练营第二十七天 | 93.复原IP地址,78.子集,90.子集II

一、参考资料复原IP地址题目链接/文章讲解&#xff1a;https://programmercarl.com/0093.%E5%A4%8D%E5%8E%9FIP%E5%9C%B0%E5%9D%80.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1XP4y1U73i/子集题目链接/文章讲解&#xff1a;https://programmercarl.com/0078.…

乐观锁、雪花算法、MyBatis-Plus多数据源

乐观锁、雪花算法、MyBatis-Plus多数据源e>雪花算法2、乐观锁a>场景b>乐观锁与悲观锁c>模拟修改冲突d>乐观锁实现流程e>Mybatis-Plus实现乐观锁七、通用枚举a>数据库表添加字段sexb>创建通用枚举类型c>配置扫描通用枚举d>测试九、多数据源1、创建…

电子学会2022年12月青少年软件编程(图形化)等级考试试卷(二级)答案解析

青少年软件编程&#xff08;图形化&#xff09;等级考试试卷&#xff08;二级&#xff09; 一、单选题(共25题&#xff0c;共50分) 1. 一个骰子&#xff0c;从3个不同角度看过去的点数如图所示&#xff0c;请问5的对面是什么点数&#xff1f;&#xff08; &#xff09; …

数据库(单表查询)

素材&#xff1a; 表名&#xff1a;worker-- 表中字段均为中文&#xff0c;比如 部门号 工资 职工号 参加工作 等 CREATE TABLE worker ( 部门号 int(11) NOT NULL, 职工号 int(11) NOT NULL, 工作时间 date NOT NULL, 工资 float(8,2) NOT NULL, 政治面貌 varchar(10) NOT NUL…

【C语言技能树】程序环境和预处理

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

一文掌握,单机Redis、哨兵和Redis Cluster的搭建,建议收藏

本篇文章讲述了 Redis 单机环境、主备、哨兵 Sentinel 模式以及 Redis Cluster 集群模式下的操作步骤&#xff0c;关于这些操作我们没必要死记硬背&#xff0c;只需要总结下来&#xff0c;下次使用直接拿出来就好。建议当作操作手册收藏。安装单实例 Redis编译Redis1.下载Redis…

小白都能学会的红帽(RedHat8)RHEL8系统安装实战

文章目录前言一. 实验环境二. 安装虚拟机三. 安装操作系统四. 系统安装成功后的操作总结前言 本文是应一位大佬的提议&#xff0c;建议我写写红帽系列&#xff0c;centos8已经不维护了&#xff0c;centos7 维护到2024年6月30日&#xff0c; 也就是明年的事情了&#xff0c;所以…

【Flutter入门到进阶】Dart基础篇---基于对比Java学习Dart

1 Dart语言特性 1.1 简介 1.1.1 说明 2011年10月&#xff0c;在丹麦召开的 GOTO 大会上&#xff0c;Google 发布了一种新的编程语言 Dart 。如同 Kotlin 和 Swift 的出现&#xff0c;分别是为了解决 Java 和 Objective-C 在编写应用程序的一些实际问题一样&#xff0c;Dart 的…

算法笔记(二)—— 认识N(logN)的排序算法

递归行为的时间复杂度估算 整个递归过程是一棵多叉树&#xff0c;递归过程相当于利用栈做了一次后序遍历。 对于master公式&#xff0c;T(N)表明母问题的规模为N&#xff0c;T(N/b)表明每次子问题的规模&#xff0c;a为调用次数&#xff0c;加号后面表明&#xff0c;除去调用之…