商城图片更换为oss

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了商城图片更换为oss相关的知识,希望对你有一定的参考价值。

参考技术A 在网站未接入阿里云OSS储存时,我们编写文章上传图片都是上传到服务器本地的。文章量少还好,一旦文章量大了,图片附件也会非常大,定期备份网站,备份包就很大。如果你使用的ZBlog php建站,想要采用阿里云OSS储存图片,则可以参考文章《ZBlog PHP插件阿里云OSS - Free 文章附件图片自动上传》使用插件即可。但是今天要说的是,如果以前是使用的本地图片,现在接入了阿里云OSS图片储存,要如何将以前的图片上传到阿里云OSS中,并且在文章中使用该图片。想知道话,就继续往下看吧。

二、准备

1、ossbrowser浏览器 软件(阿里云官方提供的OSS浏览器)

下载地址:https://help.aliyun.com/document_detail/61872.html

2、Navicat Premium 12 数据库连接工具

下载地址:https://www.zjh336.cn/?id=270

三、声明

操作不当可能会造成文件丢失或者图片显示不出来,建议事先备份网站和数据库

四、开始

1、获取网站附件上传路径,可以直接打开远程服务器,也可以使用其他工具



2、打开OSS Browser工具,连接上你的空间



3、参考第一步骤中的路径,在OSS中创建相同的目录



4、将第一步骤中的图片,拖拽上传到OSS新建的目录下

5、打开Navicat连接工具,连接到网站数据库



6、获取OSS文件的访问路径

以本站为例,使用的https://www.zjh336.cn/zb_users 也可使用OSS的外网访问地址,具体可从阿里云OSS控制台中获取

或者参考文章https://www.zjh336.cn/?id=21

7、执行sql 其中第二个参数,替换为对应的访问地址即可

 SQL
update `zbp_post`
set log_Content=
REPLACE(log_Content,'https://www.zjh336.cn/zb_users','https://www.zjh336.cn/zb_users'),
log_Meta=
REPLACE(log_Meta,'https://www.zjh336.cn/zb_users','https://www.zjh336.cn/zb_users')


8、查看文章,图片正常访问,地址已替换



9、接下来就可以删除upload下的图片内容了,至此大功告成

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

目录

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 

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


 


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("上传完成...");



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.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,成功获取

 设置网关代理

- 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

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


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);
        )
    );

 阿里云开启跨域

 

测试 

图片可以正常上传和显示

 

 

 


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
    */
   @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;


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

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)->
            //获取发生错误时的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();

再次测试

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

统一异常处理

可以使用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)
	@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了所以返回错误信息

 

测试其他字段

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

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

 


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指定这个注解由哪一个校验器校验,详细信息如图:

 

配置注解错误返回信息

在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文件内容乱码

设置好,清理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)
    @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();
  

测试前后端的校验

测试状态修改

 

测试修改

 

 测试新增

 

以上是关于商城图片更换为oss的主要内容,如果未能解决你的问题,请参考以下文章

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

商城项目09_品牌管理菜单快速显示开关阿里云进行文件上传结合Alibaba管理OSS服务端签名后直传

商城项目09_品牌管理菜单快速显示开关阿里云进行文件上传结合Alibaba管理OSS服务端签名后直传

人人商城分享后不出现简介和图片 求解?

开发电子商城1

Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)九(设置nginx保存图片的代理路径,修改和删除品牌)