品牌数据表对应数据库gulimall-pms中的pms-brand

数据表结构

字段名称 字段类型 字段长度 不是null 主键 注释
brand_id bigint 20 品牌id
name char 50 品牌名
logo varchar 2000 品牌logo地址
descript longtext 介绍
show_status tinyint 4 显示状态[0-不显示;1-显示]
first_letter char 1 检索首字母
sort int 11 排序

之前的三级分类中,自己设计界面进行增删改查。其实在逆向工程renren-generator生成的代码中,会生成对应数据表的增删改查界面。

1. 新增品牌管理菜单

2. 搭建基础增删改查界面

使用逆向生成的代码

将两个vue界面复制到modules/product中

重启项目,打开品牌维护界面,基础增删改查已经就绪

由于系统默认会做权限控制,有一些按钮会做权限判断是否显示

将权限控制设置为永远返回true

重新查看页面效果

3. 优化细节—显示状态[0-不显示;1-显示]

  1. 删除[0-不显示;1-显示]

    • 列表brand.vue显示状态

    • 新增更新brand-add-or-update.vue显示状态

  2. 将显示状态改为用switch组件控制

    • 列表brand.vue显示状态

      <el-table-columnprop="showStatus"header-align="center"align="center"label="显示状态"
      ><template slot-scope="scope"><el-switchv-model="scope.row.showStatus"active-color="#13ce66"inactive-color="#ff4949"></el-switch></template>
      </el-table-column>
      
    • 新增更新brand-add-or-update.vue显示状态

      <el-form-item label="显示状态" prop="showStatus"><el-switchv-model="dataForm.showStatus"active-color="#13ce66"inactive-color="#ff4949"></el-switch>
      </el-form-item>
      
    • 效果

  3. 监听Switch组件点击事件

    • 列表brand.vue显示状态

      <el-switchv-model="scope.row.showStatus"active-color="#13ce66"inactive-color="#ff4949"@change="updateBrandStatus(scope.row)":active-value="1":inactive-value="0"
      >
      

      直接使用逆向生成代码中的product/brand/update接口来更新数据

      updateBrandStatus(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: "状态更新成功",});});
      },
      

4. 文件上传

分布式文件上传将所有的文件存储服务在统一位置处理。

4.1 阿里云对象存储OSS

4.1.1. 简介

对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。

4.1.2. 使用步骤

  1. 开通阿里云对象存储服务https://www.aliyun.com/product/oss

    进入控制台,可以查看API文档

    资源术语

    中文 英文 说明
    存储空间 Bucket 存储空间是您用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。
    对象/文件 Object 对象是 OSS 存储数据的基本单元,也被称为OSS的文件。对象由元信息(Object Meta)、用户数据(Data)和文件名(Key)组成。对象由存储空间内部唯一的Key来标识。
    地域 Region 地域表示 OSS 的数据中心所在物理位置。您可以根据费用、请求来源等综合选择数据存储的地域。详情请查看OSS已经开通的Region。
    访问域名 Endpoint Endpoint 表示OSS对外服务的访问域名。OSS以HTTP RESTful API的形式对外提供服务,当访问不同地域的时候,需要不同的域名。通过内网和外网访问同一个地域所需要的域名也是不同的。具体的内容请参见各个Region对应的Endpoint。
    访问密钥 AccessKey AccessKey,简称 AK,指的是访问身份验证中用到的AccessKeyId 和AccessKeySecret。OSS通过使用AccessKeyId 和AccessKeySecret对称加密的方法来验证某个请求的发送者身份。AccessKeyId用于标识用户,AccessKeySecret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,其中AccessKeySecret 必须保密。

    推荐:一个项目创建一个Bucket

  2. 创建Bucket

4.2. 图片上传方式

4.2.1. 方式一:普通上传方式

这种方式用户上传还要经过自己的应用服务器,额外操作。

4.2.2. 方式一:服务端签名后直传

4.2.3. 项目采用上传方式

  1. 阿里云存储对象账号密码存储在自己的应用服务器中
  2. 前端向阿里云发送数据的时候,首先向服务器请求Policy上传策略,服务器根据账号密码生成防伪签名(防伪策略,令牌,地址等)
  3. 前端携带防伪签名访问OSS,如果正确接收上传请求。

4.3. 文件上传实现

官方文档

4.3.1. 安装SDK,引入依赖

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

4.3.2. 测试文件上传

  1. 复制文件上传代码

    // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    String endpoint = "yourEndpoint";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";// 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);// 创建PutObjectRequest对象。
    // 填写Bucket名称、Object完整路径和本地文件的完整路径。Object完整路径中不能包含Bucket名称。
    // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
    PutObjectRequest putObjectRequest = new PutObjectRequest("examplebucket", "exampleobject.txt", new File("D:\\localpath\\examplefile.txt"));// 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
    // ObjectMetadata metadata = new ObjectMetadata();
    // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
    // metadata.setObjectAcl(CannedAccessControlList.Private);
    // putObjectRequest.setMetadata(metadata);// 上传文件。
    ossClient.putObject(putObjectRequest);// 关闭OSSClient。
    ossClient.shutdown();
    
  2. 参数

    endpoint:地域节点

    accessKeyId、accessKeySecret:

    • 管理AccessKey

    • 使用子用户AccessKey

    • 创建用户

    • 设置账号

    • 开通后生成accessKeyId、accessKeySecret

    • 新建账户没有任何权限,添加权限

  3. 完整代码

    @Test
    public void testUpload() {String endpoint = "xxx";String accessKeyId = "xxx";String accessKeySecret = "xxx";OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);PutObjectRequest putObjectRequest = new PutObjectRequest("gulimall-kaisarh", "login.png", new File("C:\\Users\\Administrator\\Pictures\\login.png"));ossClient.putObject(putObjectRequest);ossClient.shutdown();System.out.println("上传完成");
    }
    
  4. 测试

4.4. SpringCloud Alibaba-OSS实现对象存储

4.4.1. 引入SpringCloud Alibaba-OSS

由于很多服务都可能使用文件上传,因此直接在gulimall-common中引入

--引入spring-alibaba-oss-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>

4.4.2. 配置阿里云oss 相关的账号信息

spring: cloud:alicloud: oss:endpoint: oss-cn-shanghai.aliyuncs.comaccess-key: xxxxxxsecret-key: xxxxxx

注意:必须申请 RAM 账号信息,并且分配 OSS 操作权限

4.4.3 测试使用OssClient 上传

@Autowired
OSSClient ossClient;@Test
public void testUpload() throws FileNotFoundException {InputStream inputStream = new FileInputStream("C:\\Users\\Administrator\\Pictures\\removeAll.png");ossClient.putObject("gulimall-kaisarh", "removeAll.png", inputStream);ossClient.shutdown();System.out.println("上传完成");
}

5. 创建微服务,整合第三方功能

5.1. 创建微服务gulimall-third-party

5.2. 修改依赖

<dependency><groupId>com.atguigu.gulimall</groupId><artifactId>gulimall-common</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>

将对象存储依赖放到gulimall-third-party

5.3. 将gulimall-third-parthy添加到nacos中

5.3.1. 新建命名空间

新建配置oss.yml

完善配置

5.3.2. 配置gulimall-third-party配置中心

spring.application.name=gulimall-third-party
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=e5b7c2f9-afd4-4750-94c7-f0be48b97fb8
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

5.3.3. 配置gulimall-third-party注册中心

spring:cloud:nacos:discovery:server-addr: 127.0.0.1:8848application:name: gulimall-third-party
server:port: 30000

开启服务注册发现

去除数据源mybatis依赖

<exclusions><exclusion><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></exclusion>
</exclusions>

5.4. 启动项目

启动项目后可以在Nacos的服务列表查询

测试上传通过

6. 服务端签名后直传

6.1. 文档

服务端签名后直传文档

6.2. 流程介绍

Web端向服务端请求签名,然后直接上传,不会对服务端产生压力,而且安全可靠。

6.3. 代码

Java代码

  1. gulimall-third-part微服务中创建controller并添加@RestController注解

  2. 编写获取签名接口

    package com.atguigu.gulimall.thirdparty.controller;import com.aliyun.oss.OSS;
    import com.aliyun.oss.common.utils.BinaryUtil;
    import com.aliyun.oss.model.MatchMode;
    import com.aliyun.oss.model.PolicyConditions;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;import java.nio.charset.StandardCharsets;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.LinkedHashMap;
    import java.util.Map;@RestController
    public class OssController {@AutowiredOSS 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() {String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。// String callbackUrl = "http://88.88.88.88:8888";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);// 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(StandardCharsets.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 respMap;}
    }
    
  3. 启动测试测试接口

    • expire:过期时间
    • dir:上传文件名
    • host:上传文件地址
    • policy:签名

6.4. 配置网关

- id: third_part_routeuri: lb://gulimall-third-partypredicates:## 前端项目发送请求,带有/api前缀- Path=/api/thirdparty/**filters:- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}

测试:测试接口

7. OOS前后端联调上传功能

  1. 文件上传组件el-uploadaction属性对应Bucket域名

  2. 更新后端接口,返回R对象

     R policy() {···return R.ok().put("data", respMap);
    }
    

  3. 配置OSS跨域

  4. multiUpload.vue

    <template><div><el-uploadaction="http://gulimall-kaisarh.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>
    
  5. policy.js

    import http from '@/utils/httpRequest.js'
    export function policy() {return new Promise((resolve, reject) => {http({url: http.adornUrl("/thirdparty/oss/policy"),method: "get",params: http.adornParams({})}).then(({ data }) => {resolve(data);})});
    }
    
  6. singleUpload.vue

    <template><div><el-uploadaction="http://gulimall-kaisarh.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>
    
  7. brand-add-or-update.vue

    <template>···<el-form-item label="品牌logo地址" prop="logo"><single-upload v-model="dataForm.logo"></single-upload></el-form-item>···
    </template><script>
    import singleUpload from "../../../components/upload/singleUpload.vue";
    export default {components: { singleUpload },···
    };
    </script>
    

8. 品牌管理功能完善

8.1. 新增品牌界面

  1. 设置switch显示状态开关,激活为0,不激活为1

    <el-form-item label="显示状态" prop="showStatus"><el-switchv-model="dataForm.showStatus"active-color="#13ce66"inactive-color="#ff4949":active-value="1":inactive-value="0"></el-switch>
    </el-form-item>
    
  2. 新增数据的时候做校验

    dataRule: {···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) => {console.log(value);console.log(typeof value);if (value === "") {callback(new Error("排序字段必须填写!"));} else if (!Number.isInteger(value) || value < 0) {callback(new Error("排序字段必须是大于等于0的整数"));} else {callback();}},trigger: "blur",},],
    },
    

8.2. 品牌列表界面

  1. 设置品牌logo为图片

    <template>···<el-table-columnprop="logo"header-align="center"align="center"label="品牌logo"><template slot-scope="scope"><img:src="scope.row.logo"alt="logo"style="width: 100px; height: 80px"/></template></el-table-column>···
    </template>
    

9. JSR303服务端数据校验

  1. 给Bean添加校验注解

    注解可以参考javax\validation\constraints

  2. 使用注解@Valid告知SpringMVC进行校验,开启校验功能

  3. Postman测试

    效果:校验错误后会有默认响应

  4. 错误信息可以通过注解自定义

  5. 给校验的bean后紧跟一个BindingResult就可以获取到校验结果,可以自定义错误返回信息

    /*** 保存*/
    @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 -> {// FieldError 获取错误提示String defaultMessage = item.getDefaultMessage();// 获取错误属性名称String field = item.getField();map.put(field, defaultMessage);});return R.error(400, "提交的数据不合法").put("data", map);} else {brandService.save(brand);return R.ok();}
    }
    

  6. 给其他字段增加校验注解

    package com.atguigu.gulimall.product.entity;import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;
    import java.util.Date;import lombok.Data;
    import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;/*** 品牌** @author KaiSarH* @email huankai7@163.com* @date 2021-04-15 16:20:25*/
    @Data
    @TableName("pms_brand")
    public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名必须提交")private String name;/*** 品牌logo地址*/@URL(message = "logo必须是一个合法的url地址")@NotEmptyprivate String logo;/*** 介绍*/private String descript;/*** 显示状态[0-不显示;1-显示]*/private Integer showStatus;/*** 检索首字母*/@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")private String firstLetter;/*** 排序*/@Min(value = 0, message = "排序必须大于等于0")private Integer sort;
    }
    

10. 统一异常处理

很多业务都需要进行数据验证,代码大部分都是重复的,可以做统一异常处理

使用SpringMVC提供的@ControllerAdvice

使用步骤:

  1. 抽取异常处理类

    使用@ControllerAdvice注解标识

    使用basePackages标识哪个位置出现异常进行处理

  2. 接口使用BindingResult会接收错误,感应异常。

    删除掉后就不再对异常进行处理,而是直接抛出异常。

    GulimallExceptionControllerAdvice的作用就是感应异常,集中处理。

  3. 错误信息以JSON格式返回,需要给类添加@ResponseBody注解

    @ResponseBody注解和@ControllerAdvice(basePackages注解可以合并为@RestControllerAdvice注解

    //@ResponseBody
    //@ControllerAdvice(basePackages = "com/atguigu/gulimall/product/controller")
    @RestControllerAdvice(basePackages = "com/atguigu/gulimall/product/controller")
    
  4. 统一处理MethodArgumentNotValidException异常

    package com.atguigu.gulimall.product.exception;import com.atguigu.common.utils.R;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap;
    import java.util.Map;/*** 集中处理所有异常*/
    @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(400, "数据校验出现问题").put("data", errorMap);}// 无法准确匹配后处理@ExceptionHandler(value = Throwable.class)public R handleException(Throwable throwable) {return R.error();}
    }
    

11. 全局状态码枚举类

错误码和错误信息定义类

  • 错误码定义规则为 5 为数字
  • 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
  • 维护错误码后需要维护错误描述,将他们定义为枚举形式

错误码列表:

  • 10: 通用

    001:参数格式校验

  • 11: 商品

  • 12: 订单

  • 13: 购物车

  • 14: 物流

  1. 状态码在很多地方都需要,因此在gulimall-common中定义枚举类BizCodeEnume

    package com.atguigu.common.exception;/**** 错误码和错误信息定义类*    1. 错误码定义规则为 5 为数字*  2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常*    3. 维护错误码后需要维护错误描述,将他们定义为枚举形式*    错误码列表:*  10: 通用* 001:参数格式校验*  11:  商品*    12:  订单*    13: 购物车*    14: 物流*/
    public enum BizCodeEnume {UNKNOW_EXCEPTION(10000, "系统未知异常"),VAILE_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;}
    }
    

  2. 定义完成后,在接口中不需要再额外写状态码

    原来返回状态码方式:

    return R.error(400, "数据校验出现问题").put("data", errorMap);
    

    定义全局状态码枚举类后返回状态码方式:

    return R.error(BizCodeEnume.VAILE_EXCEPTION.getCode(), BizCodeEnume.VAILE_EXCEPTION.getMsg()).put("data", errorMap);
    

12. JSR303分组校验

JSR303分组校验可以完成多场景复杂校验。

在新增数据和修改数据的时候,校验的字段可能不同。

例如:

  • 新增品牌的时候,由于ID是自动生成的自增长ID,所以新增的时候不携带ID

  • 修改品牌的时候,需要根据ID进行修改

  • 无论是新增还是修改,品牌名都不能为空

  • 某些字段如logo在新增的时候需要录入必须提交,修改的时候可以不用必须提交

解决:使用JSR303分组校验功能

  1. gulimall-common中定义不同分组,例如新增分组和修改分组。

  2. 使用groups属性给校验注解标注什么情况下需要校验。groups为情况数组,可以添加一个或多个。

    /**** 品牌id*/
    @NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class})
    @Null(message = "新增不能指定id", groups = {AddGroup.class})
    @TableId
    private Long brandId;
    /*** 品牌名*/
    @NotBlank(message = "品牌名必须提交", groups = {UpdateGroup.class, AddGroup.class})
    private String name;
    

  3. Controller中,使用@Validated注解标识接口进行哪一组的校验

    /*** 保存*/
    @RequestMapping("/save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*, BindingResult result*/) {brandService.save(brand);return R.ok();
    }/*** 修改*/
    @RequestMapping("/update")
    public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand) {brandService.updateById(brand);return R.ok();
    }
    

  4. 注意

    默认没有指定分组的校验注解,例如下图中@NotEmpty

    在分组校验@Validated情况下不会生效,只会在不分组的情况下生效。

13. JSR303自定义校验

在一些特殊情况下,例如显示状态[0-不显示 1-显示],没有内置的校验注解使用,需要自己写校验方法。

  • 使用@Pattern注解正则表达式
  • 自定义校验

使用过程:

  1. 编写一个自定义的校验注解

  2. 编写一个自定义的校验器

  3. 关联自定义的校验器和自定义的校验注解

    让校验器校验校验注解标识的字段

13.1. 编写一个自定义的校验注解

希望有一个注解@ListValue(values = {0, 1}),用来规定字段可以使用的值(0和1).

  1. 创建ListValue校验注解

  2. 注解必须拥有三个属性

    • message:当校验出错后,错误信息去哪取
    • groups:支持分组校验
    • payload:自定义负载信息
  3. 注解必须有以下原信息数据

    @Documented
    @Constraint(validatedBy = {})
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    
    • Target:注解可以标注在哪些位置
    • Retention:时机,可以在运行时获取到
    • Constraint:注解使用哪个校验器进行校验,可以指定校验器
  4. 导入相关包

    Payload、Constraint依赖validation-api,在pom.xml导入依赖

  5. 基础注解ListValue.java

    package com.atguigu.common.valid;import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.*;@Documented
    @Constraint(validatedBy = {})
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ListValue {String message() default "{javax.validation.constraints.NotEmpty.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
    }
    

    定义完成后,可以在BrandEntity.java0中引入使用

  6. ListValue.java中指定value数组

    int[] values() default {};
    
  7. 指定错误信息

    • message的默认值改为ListValue全类名.message

      String message() default "{com.atguigu.common.valid.ListValue.message}";
      
    • 创建配置文件ValidationMessages.properties

    • 在配置文件中配置错误信息

13.2. 编写一个自定义的校验器

  1. 创建校验器类文件ListValueConstraintValidator.java

  2. ListValueConstraintValidator实现接口ConstraintValidator

    ConstraintValidator接口包含两个泛型,第一个为对应注解,第二个为校验数据类型

  3. 实现ConstraintValidator接口两个方法

    initialize初始化方法 参数constraintAnnotation包含默认合法的值

    isValid校验方法 参数integer为提交过来需要检验的值

  4. 校验器代码

    package com.atguigu.common.valid;import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import java.util.HashSet;
    import java.util.Set;public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {private Set<Integer> set = new HashSet<>();// 初始化方法@Overridepublic void initialize(ListValue constraintAnnotation) {// 合法的值int[] values = constraintAnnotation.values();// 将合法值全部放到set中,便于查找是否存在for (int value : values) {set.add(value);}}// 判断是否校验成功@Override/*** @params value 提交过来需要检验的值* @params context 校验的上下文环境信息* @return*/public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {return set.contains(value);}
    }
    

  5. 注解与代码数据对应关系

    注解使用的时候,会使用values = {0, 1}指定值

    @ListValue(values = {0, 1})
    

    这些值对应的就是initialize初始化方法中的合法值。

    isValid校验方法中的值,指的是客户端传递过来需要校验的值。

13.3. 关联校验器和校验注解

在校验注解位置,使用@Constraint注解指定校验器

@Constraint(validatedBy = {ListValueConstraintValidator.class})

可以指定多个校验器,适配不同类型的校验

@Constraint(validatedBy = {A.class,B.class,C.class})

13.4. 完整校验注解代码与校验器

13.4.1. 校验注解

package com.atguigu.common.valid;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {String message() default "{com.atguigu.common.valid.ListValue.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};int[] values() default {};
}

13.4.2. 校验器

package com.atguigu.common.valid;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {private Set<Integer> set = new HashSet<>();// 初始化方法@Overridepublic void initialize(ListValue constraintAnnotation) {// 合法的值int[] values = constraintAnnotation.values();// 将合法值全部放到set中,便于查找是否存在for (int value : values) {set.add(value);}}// 判断是否校验成功@Override/*** @params value 提交过来需要检验的值* @params context 校验的上下文环境信息* @return*/public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {return set.contains(value);}
}

13.4.3. 注解使用

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 显示状态[0-不显示;1-显示]*/@ListValue(values = {0, 1})private Integer showStatus;}

13.5. 测试

  1. 指定在添加的时候必须携带

    /*** 显示状态[0-不显示;1-显示]*/
    @ListValue(values = {0, 1}, groups = {AddGroup.class})
    private Integer showStatus;
    
  2. save测试

    • 错误测试

    • 正确测试

14. 将修改品牌状态单独抽取一个方法

  1. 添加修改状态分组

  2. 新建接口,指定使用UpdateStatusGroup分组

    /*** 修改品牌显示状态*/
    @RequestMapping("/update/status")
    public R updateStatus(@Validated({UpdateStatusGroup.class}) @RequestBody BrandEntity brand) {brandService.updateById(brand);return R.ok();
    }
    

  3. BrandEntity中进行配置,在UpdateStatusGroup组只判断showStatus

    /*** 显示状态[0-不显示;1-显示]*/
    @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
    @ListValue(values = {0, 1}, groups = {AddGroup.class, UpdateStatusGroup.class})
    private Integer showStatus;
    

  4. 修改前端项目更新状态请求

  5. 测试成功

谷粒商城:09.商品服务 — 品牌管理相关推荐

  1. 谷粒商城七商品服务品牌管理之oss文件存储

    使用renren-generator生成crud页面 todo谷粒商城二本地虚拟机环境搭建及项目初始化在逆向工程的时候,resources下有一个view文件夹,下面都是可以直接使用的vue文件,我们 ...

  2. 第5章-商品服务-品牌管理

    第5章 商品服务-品牌管理 文章目录 第5章 商品服务-品牌管理 1. 使用逆向工程的代码 1.1 导入代码 1.2 显示状态优化 2. 文件上传技术 2.1 阿里云---对象存储OSS 云存储开启 ...

  3. 谷粒商城之商品服务-平台属性-属性组管理

    目录 什么是SPU? 什么是SKU? 规格参数 ​ 销售属性 三级分类-属性组-属性的关联关系 SPU-属性&SKU-属性的关联关系 预期效果: 属性分组之前端组件抽取&父子组件交互 ...

  4. 谷粒商城六商品服务三级分类

    递归-树形结构数据获取 sql文件 sql文件太大了,这个博主写的非常厉害,看他的就ok了 CategoryController package com.atguigu.gulistore.produ ...

  5. 微盟电子商城网络交易系统——Day04【商品服务-品牌管理、商品服务-属性分组】

    ❤ 作者主页:欢迎来到我的技术博客

  6. 谷粒商城九商品服务之商品属性及仓储服务todo

    之前的文章我都是把整篇的代码直接复制到文章中,这样容易抓不住重点, 但是一段代码都贴出来,又显得繁琐, 从这篇开始,我会把重点步骤写出来,代码还是贴完整的 从这篇开始的mybatis-plus分页插件 ...

  7. 谷粒商城之商品服务-三级分类(展示与删除)

    目录 三级类目查询后台代码实现 后台管理系统的菜单创建 配置网关和路径重写 网关统一配置跨域 三级类目后台管理系统的页面显示 三级分类删除页面效果的编写 ​ 三级分类逻辑删除后台实现 三级类目删除功能 ...

  8. 谷粒商城(新增商品、商品管理、仓库管理)思路详解

    谷粒商城基础分布式总结 1.新增商品 1.调试member服务 2.查询分类下的所有品牌 2.获取分类下面带属性的属性分组. 3.保存设定好的spu和sku信息 2.商品管理 1.spu展示查询(sp ...

  9. 尚硅谷2020微服务分布式电商项目《谷粒商城》-商品搜索

    关注公众号:java星星 获取全套课件资料 1. 导入商品数据 1.1. 搭建搜索工程 pom.xml内容如下: <?xml version="1.0" encoding=& ...

  10. 【谷粒商城之订单服务-支付】

    本笔记内容为尚硅谷谷粒商城订单服务支付部分 目录 一.支付宝沙箱 沙箱环境 二.公钥.私钥.加密.加签.验签 1.公钥私钥 2.加密和数字签名 3.对称加密和非对称加密 三.内网穿透 四.整合支付 1 ...

最新文章

  1. html 后台参数attribute_平台管理后台与商家菜单资源管理:商家权限及其菜单资源管理设计...
  2. Individual Project-——word_frequency——final requirement
  3. springcloud全局过滤_微服务技术SpringCloud 互联网网站架构演变过程
  4. 从零开始的linux 第十一章
  5. Mybatis应用(一)应用步骤
  6. Maven(1)--坐标与依赖
  7. 速看 | 电子元器件如何确定好坏?
  8. oracle的排序分页,oracle 排序分页 高效sql语句
  9. 三级菜单 python_python三级菜单
  10. 前 1 号店 CTO 黄哲铿揭秘:微服务架构在超大场景下的应用
  11. 宽带ADSL安装过程的18个问答
  12. 你知道R中的赋值符号箭头(lt;-)和等号(=)的区别吗?
  13. sql server 新语法 收藏
  14. Java 泛型完全解读
  15. 【演化(进化)算法】遗传算法原理及python实现
  16. matlab disp输出换行,matlab输出语句disp
  17. 什么是NIC(网络接口卡)?
  18. 广西南宁机器人比赛_缤纷校园|2018年广西中小学电脑机器人竞赛开赛 南宁学子大显身手...
  19. 小程序开发之全栈开发(一)
  20. 【Linux操作系统】--攥写一个简易的shell工具

热门文章

  1. sql中的两个简单嵌套
  2. 13、MVC 设计思想
  3. cmd进入mysql界面的命令
  4. angular中利用zone避归没必要的开销提高程序的性能demo
  5. 讯为开发板的最小LINUX系统烧写及U盘的挂载及卸载
  6. 使用jQuery快速高效制作网页交互特效(6)
  7. # 学号20155308 2006-2007-2 《Java程序设计》第4周学习总结
  8. 安装最新版本的PHPUnit后,不能使用
  9. JWPlayer快速入门指南(中文)
  10. MEncoder的基础用法—6.8. 从多个输入图像文件进行编码(JPEG, PNG, TGA等)