gulimall-分布式基础篇-品牌管理
一、前端
1.1 生成侧边栏导航菜单
1.2 前端代码
在renren-fast里生成代码并复制
brand.vue ——品牌列表
brand-add-or-update.vue —— 品牌增加修改
二、权限认证关闭
前端src→utils→index.js
三、优化逆向生成代码
3.1 brand.vue
<template>
<div class="mod-config">
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
<el-button
v-if="isAuth('product:brand:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增
</el-button>
<el-button
v-if="isAuth('product:brand:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除
</el-button>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%;"
>
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<el-table-column prop="brandId" header-align="center" align="center" label="品牌id"></el-table-column>
<el-table-column prop="name" header-align="center" align="center" label="品牌名"></el-table-column>
<el-table-column prop="logo" header-align="center" align="center" label="品牌logo地址">
<template slot-scope="scope">
<!-- <el-image
style="width: 100px; height: 80px"
:src="scope.row.logo"
fit="fill"></el-image>-->
<img :src="scope.row.logo" style="width: 100px; height: 80px"/>
</template>
</el-table-column>
<el-table-column prop="descript" header-align="center" align="center" label="介绍"></el-table-column>
<el-table-column prop="showStatus" header-align="center" align="center" label="显示状态">
<template slot-scope="scope">
<el-switch
v-model="scope.row.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
@change="updateBrandStatus(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column prop="firstLetter" header-align="center" align="center" label="检索首字母"></el-table-column>
<el-table-column prop="sort" header-align="center" align="center" label="排序"></el-table-column>
<el-table-column fixed="right" header-align="center" align="center" width="250" label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="updateCatelogHandle(scope.row.brandId)">关联分类</el-button>
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.brandId)">修改</el-button>
<el-button type="text" size="small" @click="deleteHandle(scope.row.brandId)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
<el-dialog title="关联分类" :visible.sync="cateRelationDialogVisible" width="30%">
<el-popover placement="right-end" v-model="popCatelogSelectVisible">
<category-cascader :catelogPath.sync="catelogPath"></category-cascader>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="popCatelogSelectVisible = false">取消</el-button>
<el-button type="primary" size="mini" @click="addCatelogSelect">确定</el-button>
</div>
<el-button slot="reference">新增关联</el-button>
</el-popover>
<el-table :data="cateRelationTableData" style="width: 100%">
<el-table-column prop="id" label="#"></el-table-column>
<el-table-column prop="brandName" label="品牌名"></el-table-column>
<el-table-column prop="catelogName" label="分类名"></el-table-column>
<el-table-column fixed="right" header-align="center" align="center" label="操作">
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="deleteCateRelationHandle(scope.row.id,scope.row.brandId)"
>移除
</el-button>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="cateRelationDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="cateRelationDialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import AddOrUpdate from "./brand-add-or-update";
// import CategoryCascader from "../common/category-cascader";
export default {
data() {
return {
dataForm: {
key: ""
},
brandId: 0,
catelogPath: [],
dataList: [],
cateRelationTableData: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
cateRelationDialogVisible: false,
popCatelogSelectVisible: false
};
},
components: {
AddOrUpdate,
// CategoryCascader
},
activated() {
this.getDataList();
},
methods: {
addCatelogSelect() {
//{"brandId":1,"catelogId":2}
this.popCatelogSelectVisible = false;
this.$http({
url: this.$http.adornUrl("/product/categorybrandrelation/save"),
method: "post",
data: this.$http.adornData({
brandId: this.brandId,
catelogId: this.catelogPath[this.catelogPath.length - 1]
}, false)
}).then(({data}) => {
this.getCateRelation();
});
},
deleteCateRelationHandle(id, brandId) {
this.$http({
url: this.$http.adornUrl("/product/categorybrandrelation/delete"),
method: "post",
data: this.$http.adornData([id], false)
}).then(({data}) => {
this.getCateRelation();
});
},
updateCatelogHandle(brandId) {
this.cateRelationDialogVisible = true;
this.brandId = brandId;
this.getCateRelation();
},
getCateRelation() {
this.$http({
url: this.$http.adornUrl("/product/categorybrandrelation/catelog/list"),
method: "get",
params: this.$http.adornParams({
brandId: this.brandId
})
}).then(({data}) => {
this.cateRelationTableData = data.data;
});
},
// 获取数据列表
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl("/product/brand/list"),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key
})
}).then(({data}) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
});
},
updateBrandStatus(data) {
console.log("最新信息", data);
let {brandId, showStatus} = data;
//发送请求修改状态
this.$http({
url: this.$http.adornUrl("/product/brand/update/status"),
method: "post",
data: this.$http.adornData({brandId, showStatus}, false)
}).then(({data}) => {
this.$message({
type: "success",
message: "状态更新成功"
});
});
},
// 每页数
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
// 当前页
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
// 多选
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// 新增 / 修改
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id);
});
},
// 删除
deleteHandle(id) {
var ids = id
? [id]
: this.dataListSelections.map(item => {
return item.brandId;
});
this.$confirm(
`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}
).then(() => {
this.$http({
url: this.$http.adornUrl("/product/brand/delete"),
method: "post",
data: this.$http.adornData(ids, false)
}).then(({data}) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
}
});
} else {
this.$message.error(data.msg);
}
});
});
}
}
};
</script>
<style scoped>
</style>
3.2 brand-add-or-update.vue
<template>
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
>
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="dataFormSubmit()"
label-width="140px"
>
<el-form-item label="品牌名" prop="name">
<el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
</el-form-item>
<el-form-item label="品牌logo地址" prop="logo">
<!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> -->
<single-upload v-model="dataForm.logo"></single-upload>
</el-form-item>
<el-form-item label="介绍" prop="descript">
<el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
</el-form-item>
<el-form-item label="显示状态" prop="showStatus">
<el-switch
v-model="dataForm.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
<el-form-item label="检索首字母" prop="firstLetter">
<el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
</span>
</el-dialog>
</template>
<script>
import SingleUpload from '@/components/upload/singleUpload';
export default {
components: {SingleUpload},
data() {
return {
visible: false,
dataForm: {
brandId: 0,
name: "",
logo: "",
descript: "",
showStatus: 1,
firstLetter: "",
sort: 0
},
dataRule: {
name: [{required: true, message: "品牌名不能为空", trigger: "blur"}],
logo: [
{required: true, message: "品牌logo地址不能为空", trigger: "blur"}
],
descript: [
{required: true, message: "介绍不能为空", trigger: "blur"}
],
showStatus: [
{
required: true,
message: "显示状态[0-不显示;1-显示]不能为空",
trigger: "blur"
}
],
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) => {
if (value == "") {
callback(new Error("排序字段必须填写"));
} else if (!Number.isInteger(value) || value < 0) {
callback(new Error("排序必须是一个大于等于0的整数"));
} else {
callback();
}
},
trigger: "blur"
}
]
}
};
},
methods: {
init(id) {
this.dataForm.brandId = id || 0;
this.visible = true;
this.$nextTick(() => {
this.$refs["dataForm"].resetFields();
if (this.dataForm.brandId) {
this.$http({
url: this.$http.adornUrl(
`/product/brand/info/${this.dataForm.brandId}`
),
method: "get",
params: this.$http.adornParams()
}).then(({data}) => {
if (data && data.code === 0) {
this.dataForm.name = data.brand.name;
this.dataForm.logo = data.brand.logo;
this.dataForm.descript = data.brand.descript;
this.dataForm.showStatus = data.brand.showStatus;
this.dataForm.firstLetter = data.brand.firstLetter;
this.dataForm.sort = data.brand.sort;
}
});
}
});
},
// 表单提交
dataFormSubmit() {
this.$refs["dataForm"].validate(valid => {
if (valid) {
this.$http({
url: this.$http.adornUrl(
`/product/brand/${!this.dataForm.brandId ? "save" : "update"}`
),
method: "post",
data: this.$http.adornData({
brandId: this.dataForm.brandId || undefined,
name: this.dataForm.name,
logo: this.dataForm.logo,
descript: this.dataForm.descript,
showStatus: this.dataForm.showStatus,
firstLetter: this.dataForm.firstLetter,
sort: this.dataForm.sort
})
}).then(({data}) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.visible = false;
this.$emit("refreshDataList");
}
});
} else {
this.$message.error(data.msg);
}
});
}
});
}
}
};
</script>
3.3. 上传组件使用
单文件上传组件-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
六、换前端bucket地址
七、JSR303数据校验
JSR303
1)、给Bean添加校验注解:javax.validation.Constraints,并定义自己的message提示
2)、在需要校验的bean上添加注解 开启校验功能 @Valid
效果:校验错误以后会有默认的响应;
3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
4)、分组校验(多场景的复杂校验)
1)、@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
给校验注解标注什么情况需要进行校验
2)、@Validated({AddGroup.class})
3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况下不生效,只会在@Validated生效
7.1 引入依赖
父POM引入validation-api依赖,引入gulimall-common中
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
7.2 使用
开启校验:实体类上增加校验注解,接口参数前增加@Valid 开启校验
实体类增加校验注解
package com.jyyy.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 jyyy
* @email jyyy@gmail.com
* @date 2022-11-26 15:59:30
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交")
private String name;
/**
* 品牌logo地址
*/
@NotEmpty
@URL(message = "logo必须是一个合法的url地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty
@Pattern(regexp = "/^[a-zA-Z]$/", message = "检索首字母必须是一个字母")
private String firstLetter;
/**
* 排序
*/
@NotNull
@Min(value = 0, message = "排序必须大于等于0")
private Integer sort;
}
接口参数前增加@Valid 开启校验,并代码捕获异常
后面将改写成自动捕获异常
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
if (result.hasErrors()){
Map<String, String> map = new HashMap<>();
//1、获取校验的结果
result.getFieldErrors().forEach((item)->{
//获取到错误提示
String message = item.getDefaultMessage();
//获取到错误属性的名字
String field = item.getField();
map.put(field, message);
});
return R.error().put("data", map);
}else{
brandService.save(brand);
}
return R.ok();
}
7.3 错误抓取
错误码规范
针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义
在gulimall-common中新建BizCodeEnume用来存储状态码
package com.jyyy..gulimallcommon.exception;
/***
* 错误码和错误信息定义类
* 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;
}
}
在product里面新建类GulimallExceptionControllerAdvice,用来集中处理所有异
package com.jyyy.gulimall.product.exception;
import com.jyyy.gulimall.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.jyyy.gulimall.exception.BizCodeEnum;
import java.util.HashMap;
import java.util.Map;
/**
* 集中处理所有异常
* @author ZSY
*/
@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "com.zsy.product.controller")
@RestControllerAdvice(basePackages = "com.jyyy.gulimall.product.controller")
public class MallExceptionControllerAdvice {
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleValidException(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(BizCodeEnum.VALID_EXCEPTION.getCode(), BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("错误:",throwable);
return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(), BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());
}
}
还原productController代码
/**
* 保存
*/
@RequestMapping("/save")
// @RequiresPermissions("product:brand:save")
public R save(@RequestBody @Valid BrandEntity brand){
brandService.save(brand);
return R.ok();
}
7.4 自定义校验器和校验注解
自定义校验注解
JSR-303 是一个规范,那么我应该如何去实现它?在 官方文档 中第一个例子 2.1.2 所描述:1.先创建一个符合规范的注解,2.然后将该注解类标记在实体类上即可
符合 JSR-303 标准的注解类,必须有 3 个字段
字段 message 定义:返回的错误消息。用于当 JavaBean 验证失败后,返回该内容
字段 groups 定义:该元素指定与约束声明关联的处理组。比如设我们的昵称,我们可能会设定多个限制,只允许英文,长度在 10 个以内。以 yyds_开头…登等, 像这样有多重限制的规则可以定义在一个组内 NamesGroups,当然默认位空,该规则就是单独的。
方法 payload() 定义:有效负载元素,该元素指定与约束声明关联的有效负载。这一方法通常是指定该注解只能够添加到哪个字段上去。默认是为空的,可以添加在任何字段上
--- 自定义注解 ---
@Target:指定被修饰的Annotation可以放置的位置(被修饰的目标)
@Documented:指定被修饰的该Annotation可以被javadoc工具提取成文档.
@Retention:这个注解规定了我们自定义注解的生命周期,里面有个属性叫RetentionPolicy,
runtime:运行时保留,
SOURCE:编译后立刻丢弃,注解仅存在于源码中,在class字节码文件中不包含
CLASS:默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
关联校验器和校验注解:在校验注解的@Constraint注解上关联校验器
package com.jyyy.gulimall.common.valid;
/**
* 自定义校验注解 声明可以取那些值
* 这个注解就像原来的@max()一样的道理
**/
@Documented
// 关联校验器和校验注解:在校验注解的@Constraint注解上关联校验器
@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.jyyy.gulimall.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] vals() default {};
}
自定义校验器
package com.jyyy.gulimall.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 final Set<Integer> set = new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
//判断是否校验成功
/**
*
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
新建配置文件ValidationMessages.properties保存注解信息
com.jyyy.gulimall.common.valid.ListValue.message=必须提交指定的值
在gulimall-common中新建valid包,里面新建两个空接口AddGroup,UpdateGroup,UpdateStatusGroup用来分组
7.5 重写校验字段
package com.jyyy.gulimall.product.entity;
import javax.validation.constraints.*;
import org.hibernate.validator.constraints.URL;
/**
* 品牌
*
*/
@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;
}
7.6 重写接口验证注解
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
...
}
八、修复逆向生成代码
8.1 修复前端分页页码错误
后端
package com.jyyy.gulimall.product.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
}
8.2 修复模糊查询
后端
BrandServiceImpl
@Override
public PageUtils queryPage(Map<String, Object> params) {
// 获取key
String key = (String) params.get("key");
QueryWrapper<BrandEntity> queryWrapper = new QueryWrapper<>();
if (!StringUtils.isEmpty(key)){
queryWrapper.eq("brand_id",key).or().like("name",key);
}
IPage<BrandEntity> page = this.page(
new Query<BrandEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
九、品牌获取关联分类
这里请使用完整版前端测试
一个分类对应多个品牌,例如:手机下有华为,小米
一个品牌也关联多个分类,多对多的关系
CategoryBrandRelationController
/**
* 获取品牌的关联分类
* @param brandId
* @return
*/
@GetMapping("/catelog/list")
public R catologList(@Param("brandId") Long brandId){
List<CategoryBrandRelationEntity> data = categoryBrandRelationService.list(new QueryWrapper<CategoryBrandRelationEntity>()
.eq("brand_id",brandId));
return R.ok().put("data",data);
}
十、品牌新增
10.1 CategoryBrandRelationController
/**
* 保存
* 大表数据不做关联,每次查损耗很多性能
*/
@RequestMapping("/save")
// @RequiresPermissions("product:categorybrandrelation:save")
public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
categoryBrandRelationService.saveDetail(categoryBrandRelation);
return R.ok();
}
10.2 CategoryBrandRelationService
/**
* 新增品牌与分类关联关系
* @param categoryBrandRelation
*/
void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
10.3 CategoryBrandRelationServiceImpl
/**
* 新增品牌与分类关联关系
* @param categoryBrandRelation
*/
public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
// 获取品牌ID和分类ID
Long brandId = categoryBrandRelation.getBrandId();
Long categoryId = categoryBrandRelation.getCatelogId();
// 数据库查询
BrandEntity brand = brandDao.selectById(brandId);
CategoryEntity category = categoryDao.selectById(categoryId);
// 设置参数
categoryBrandRelation.setBrandName(brand.getName());
categoryBrandRelation.setCatelogName(category.getName());
this.save(categoryBrandRelation);
}
十一、品牌/分类信息更新同时更新品牌关联分类表
为了保证数据的一致行,当数据更新时,冗余表里面的数据也要更新
使用两种方法做演示
11.1 品牌信息更新
BrandController
/**
* 修改
*/
@RequestMapping("/update")
// @RequiresPermissions("product:brand:update")
public R update(@RequestBody BrandEntity brand){
// brandService.updateById(brand);
brandService.updateDetail(brand);
return R.ok();
}
BrandService
/**
* 更新品牌同时更新冗余表
* @param brand
*/
void updateDetail(BrandEntity brand);
BrandServiceImpl
/**
* 更新品牌同时更新冗余表
* @param brand
*/
public void updateDetail(BrandEntity brand) {
// 更新原有表
this.updateById(brand);
if (!StringUtils.isEmpty(brand.getName())) {
// 更新分类表
categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
}
}
CategoryBrandRelationService
/**
* 根据参数修改所有数据
* @param brandId
* @param name
*/
void updateBrand(Long brandId, String name);
CategoryBrandRelationServiceImpl
/**
* 根据参数修改所有数据
* @param brandId
* @param name
*/
public void updateBrand(Long brandId, String name) {
CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
categoryBrandRelationEntity.setBrandId(brandId);
categoryBrandRelationEntity.setBrandName(name);
this.update(categoryBrandRelationEntity, new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId));
}
11.2 分类信息更新
CategoryController
/**
* 修改
*/
@RequestMapping("/update")
// @RequiresPermissions("product:category:update")
public R update(@RequestBody CategoryEntity category){
// categoryService.updateById(category);
categoryService.updateCascade(category);
return R.ok();
}
CategoryService
/**
* 更新分类同时更新冗余表数据
* @param category
*/
void updateCascade(CategoryEntity category);
CategoryServiceImpl
/**
* 更新分类同时更新冗余表数据
* @param category
*/
public void updateCascade(CategoryEntity category) {
this.updateById(category);
if (!StringUtils.isEmpty(category.getName())) {
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}
}
CategoryBrandRelationService
/**
* 根据参数修改所有数据
* @param catId
* @param name
*/
void updateCategory(Long catId, String name);
CategoryBrandRelationServiceImpl
/**
* 根据参数修改所有数据
* @param catId
* @param name
*/
public void updateCategory(Long catId, String name) {
this.baseMapper.updateCategory(catId,name);
}
CategoryBrandRelationDao
void updateCategory(@Param("catId") Long catId, @Param("name") String name);
<update id="updateCategory">
update `pms_category_brand_relation` set `catelog_name` = #{name} where catelog_id = #{catId}
</update>