谷粒商城:09.商品服务 — 品牌管理
品牌数据表对应数据库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-显示]
删除
[0-不显示;1-显示]
列表
brand.vue
显示状态新增更新
brand-add-or-update.vue
显示状态
将显示状态改为用
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>
效果
监听
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. 使用步骤
开通阿里云对象存储服务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
。创建Bucket
4.2. 图片上传方式
4.2.1. 方式一:普通上传方式
这种方式用户上传还要经过自己的应用服务器,额外操作。
4.2.2. 方式一:服务端签名后直传
4.2.3. 项目采用上传方式
- 阿里云存储对象账号密码存储在自己的应用服务器中
- 前端向阿里云发送数据的时候,首先向服务器请求Policy上传策略,服务器根据账号密码生成防伪签名(防伪策略,令牌,地址等)
- 前端携带防伪签名访问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. 测试文件上传
复制文件上传代码
// 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();
参数
endpoint:地域节点
accessKeyId、accessKeySecret:
管理AccessKey
使用子用户AccessKey
创建用户
设置账号
开通后生成accessKeyId、accessKeySecret
新建账户没有任何权限,添加权限
完整代码
@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. 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代码
在
gulimall-third-part
微服务中创建controller并添加@RestController
注解编写获取签名接口
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;} }
启动测试测试接口
- 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前后端联调上传功能
文件上传组件
el-upload
的action
属性对应Bucket
域名更新后端接口,返回
R
对象R policy() {···return R.ok().put("data", respMap); }
配置OSS跨域
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>
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);})}); }
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>
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. 新增品牌界面
设置
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>
新增数据的时候做校验
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. 品牌列表界面
设置品牌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服务端数据校验
给Bean添加校验注解
注解可以参考
javax\validation\constraints
使用注解
@Valid
告知SpringMVC进行校验,开启校验功能Postman测试
效果:校验错误后会有默认响应
错误信息可以通过注解自定义
给校验的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();} }
给其他字段增加校验注解
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
使用步骤:
抽取异常处理类
使用
@ControllerAdvice
注解标识使用
basePackages
标识哪个位置出现异常进行处理接口使用
BindingResult
会接收错误,感应异常。删除掉后就不再对异常进行处理,而是直接抛出异常。
GulimallExceptionControllerAdvice
的作用就是感应异常,集中处理。错误信息以
JSON
格式返回,需要给类添加@ResponseBody
注解@ResponseBody
注解和@ControllerAdvice(basePackages
注解可以合并为@RestControllerAdvice
注解//@ResponseBody //@ControllerAdvice(basePackages = "com/atguigu/gulimall/product/controller") @RestControllerAdvice(basePackages = "com/atguigu/gulimall/product/controller")
统一处理
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: 物流
状态码在很多地方都需要,因此在
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;} }
定义完成后,在接口中不需要再额外写状态码
原来返回状态码方式:
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分组校验功能
在
gulimall-common
中定义不同分组,例如新增分组和修改分组。使用
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;
在
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(); }
注意
默认
没有指定分组
的校验注解,例如下图中@NotEmpty
在分组校验
@Validated
情况下不会生效,只会在不分组的情况下生效。
13. JSR303自定义校验
在一些特殊情况下,例如显示状态[0-不显示 1-显示],没有内置的校验注解使用,需要自己写校验方法。
- 使用
@Pattern
注解正则表达式 - 自定义校验
使用过程:
编写一个自定义的校验注解
编写一个自定义的校验器
关联自定义的校验器和自定义的校验注解
让校验器校验校验注解标识的字段
13.1. 编写一个自定义的校验注解
希望有一个注解@ListValue(values = {0, 1})
,用来规定字段可以使用的值(0和1).
创建
ListValue
校验注解注解必须拥有三个属性
- message:当校验出错后,错误信息去哪取
- groups:支持分组校验
- payload:自定义负载信息
注解必须有以下原信息数据
@Documented @Constraint(validatedBy = {}) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME)
- Target:注解可以标注在哪些位置
- Retention:时机,可以在运行时获取到
- Constraint:注解使用哪个校验器进行校验,可以指定校验器
导入相关包
Payload、Constraint
依赖validation-api
,在pom.xml
导入依赖基础注解
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
中引入使用在
ListValue.java
中指定value
数组int[] values() default {};
指定错误信息
将
message
的默认值改为ListValue全类名.message
String message() default "{com.atguigu.common.valid.ListValue.message}";
创建配置文件
ValidationMessages.properties
在配置文件中配置错误信息
13.2. 编写一个自定义的校验器
创建校验器类文件
ListValueConstraintValidator.java
ListValueConstraintValidator
实现接口ConstraintValidator
ConstraintValidator
接口包含两个泛型,第一个为对应注解,第二个为校验数据类型实现
ConstraintValidator
接口两个方法initialize
初始化方法 参数constraintAnnotation
包含默认合法的值isValid
校验方法 参数integer
为提交过来需要检验的值校验器代码
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);} }
注解与代码数据对应关系
注解使用的时候,会使用
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. 测试
指定在添加的时候必须携带
/*** 显示状态[0-不显示;1-显示]*/ @ListValue(values = {0, 1}, groups = {AddGroup.class}) private Integer showStatus;
save
测试错误测试
正确测试
14. 将修改品牌状态单独抽取一个方法
添加修改状态分组
新建接口,指定使用
UpdateStatusGroup
分组/*** 修改品牌显示状态*/ @RequestMapping("/update/status") public R updateStatus(@Validated({UpdateStatusGroup.class}) @RequestBody BrandEntity brand) {brandService.updateById(brand);return R.ok(); }
在
BrandEntity
中进行配置,在UpdateStatusGroup
组只判断showStatus
/*** 显示状态[0-不显示;1-显示]*/ @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class}) @ListValue(values = {0, 1}, groups = {AddGroup.class, UpdateStatusGroup.class}) private Integer showStatus;
修改前端项目更新状态请求
测试成功
谷粒商城:09.商品服务 — 品牌管理相关推荐
- 谷粒商城七商品服务品牌管理之oss文件存储
使用renren-generator生成crud页面 todo谷粒商城二本地虚拟机环境搭建及项目初始化在逆向工程的时候,resources下有一个view文件夹,下面都是可以直接使用的vue文件,我们 ...
- 第5章-商品服务-品牌管理
第5章 商品服务-品牌管理 文章目录 第5章 商品服务-品牌管理 1. 使用逆向工程的代码 1.1 导入代码 1.2 显示状态优化 2. 文件上传技术 2.1 阿里云---对象存储OSS 云存储开启 ...
- 谷粒商城之商品服务-平台属性-属性组管理
目录 什么是SPU? 什么是SKU? 规格参数 销售属性 三级分类-属性组-属性的关联关系 SPU-属性&SKU-属性的关联关系 预期效果: 属性分组之前端组件抽取&父子组件交互 ...
- 谷粒商城六商品服务三级分类
递归-树形结构数据获取 sql文件 sql文件太大了,这个博主写的非常厉害,看他的就ok了 CategoryController package com.atguigu.gulistore.produ ...
- 微盟电子商城网络交易系统——Day04【商品服务-品牌管理、商品服务-属性分组】
❤ 作者主页:欢迎来到我的技术博客
- 谷粒商城九商品服务之商品属性及仓储服务todo
之前的文章我都是把整篇的代码直接复制到文章中,这样容易抓不住重点, 但是一段代码都贴出来,又显得繁琐, 从这篇开始,我会把重点步骤写出来,代码还是贴完整的 从这篇开始的mybatis-plus分页插件 ...
- 谷粒商城之商品服务-三级分类(展示与删除)
目录 三级类目查询后台代码实现 后台管理系统的菜单创建 配置网关和路径重写 网关统一配置跨域 三级类目后台管理系统的页面显示 三级分类删除页面效果的编写 三级分类逻辑删除后台实现 三级类目删除功能 ...
- 谷粒商城(新增商品、商品管理、仓库管理)思路详解
谷粒商城基础分布式总结 1.新增商品 1.调试member服务 2.查询分类下的所有品牌 2.获取分类下面带属性的属性分组. 3.保存设定好的spu和sku信息 2.商品管理 1.spu展示查询(sp ...
- 尚硅谷2020微服务分布式电商项目《谷粒商城》-商品搜索
关注公众号:java星星 获取全套课件资料 1. 导入商品数据 1.1. 搭建搜索工程 pom.xml内容如下: <?xml version="1.0" encoding=& ...
- 【谷粒商城之订单服务-支付】
本笔记内容为尚硅谷谷粒商城订单服务支付部分 目录 一.支付宝沙箱 沙箱环境 二.公钥.私钥.加密.加签.验签 1.公钥私钥 2.加密和数字签名 3.对称加密和非对称加密 三.内网穿透 四.整合支付 1 ...
最新文章
- html 后台参数attribute_平台管理后台与商家菜单资源管理:商家权限及其菜单资源管理设计...
- Individual Project-——word_frequency——final requirement
- springcloud全局过滤_微服务技术SpringCloud 互联网网站架构演变过程
- 从零开始的linux 第十一章
- Mybatis应用(一)应用步骤
- Maven(1)--坐标与依赖
- 速看 | 电子元器件如何确定好坏?
- oracle的排序分页,oracle 排序分页 高效sql语句
- 三级菜单 python_python三级菜单
- 前 1 号店 CTO 黄哲铿揭秘:微服务架构在超大场景下的应用
- 宽带ADSL安装过程的18个问答
- 你知道R中的赋值符号箭头(lt;-)和等号(=)的区别吗?
- sql server 新语法 收藏
- Java 泛型完全解读
- 【演化(进化)算法】遗传算法原理及python实现
- matlab disp输出换行,matlab输出语句disp
- 什么是NIC(网络接口卡)?
- 广西南宁机器人比赛_缤纷校园|2018年广西中小学电脑机器人竞赛开赛 南宁学子大显身手...
- 小程序开发之全栈开发(一)
- 【Linux操作系统】--攥写一个简易的shell工具
热门文章
- sql中的两个简单嵌套
- 13、MVC 设计思想
- cmd进入mysql界面的命令
- angular中利用zone避归没必要的开销提高程序的性能demo
- 讯为开发板的最小LINUX系统烧写及U盘的挂载及卸载
- 使用jQuery快速高效制作网页交互特效(6)
- # 学号20155308 2006-2007-2 《Java程序设计》第4周学习总结
- 安装最新版本的PHPUnit后,不能使用
- JWPlayer快速入门指南(中文)
- MEncoder的基础用法—6.8. 从多个输入图像文件进行编码(JPEG, PNG, TGA等)