将逆向工程生成的两个vue文件放置到前端项目,可以参考电商项目2逆向工程生成
然后重启前端项目
只有查询,没有新增和其他按钮(批量删除、新增按钮)是因为有权限判断方法,此方法让它暂时返回为true
{{ scope.row.date }}
我们需要显示状态发生改变时。数据库也改掉
elementUI switch组件 监听事件
发送http请求
brand.vue
updateBrandStatus(data){
console.log("当前修改的数据:",data);
// 解构
let {brandId,showStatus} = data;
this.$http({
url: this.$http.adornUrl('/product/brand/update'),
method: 'post',
data: this.$http.adornData({brandId,showStatus:showStatus?0:1}, false)
}).then(({ data }) => {
this.$message({
message: "状态更新成功",
type: "success",
});
});
},
但是发现一个问题。数据库也改了状态。但是重新刷新页面又红了,1和0状态反了
elementUI Switch开关 属性
新增这两个属性
绑定数字的1,0
去掉三元表达式
功能做好了
测试是否可以上传访问:
1、点击上传文件
2、扫描文件
3、上传,点击详情
4、复制文件url。直接访问
5、
文档中心打开sdk文档
https://help.aliyun.com/document_detail/32009.html
1、引入maven依赖
com.aliyun.oss
aliyun-sdk-oss
3.15.1
2、将上传文件示例代码copy过来
GulimallProductApplicationTest
package com.ljs.gulimall.product;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ljs.gulimall.product.entity.BrandEntity;
import com.ljs.gulimall.product.service.BrandService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallProductApplicationTest {
@Autowired
private BrandService brandService;
@Test
public void uploadTest() throws FileNotFoundException {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "yourAccessKeyId";
String accessKeySecret = "yourAccessKeySecret";
// 填写Bucket名称,例如examplebucket。
String bucketName = "examplebucket";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String objectName = "exampledir/exampleobject.txt";
// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
String filePath= "D:\\localpath\\examplefile.txt";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
InputStream inputStream = new FileInputStream(filePath);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
// 设置该属性可以返回response。如果不设置,则返回的response为空。
putObjectRequest.setProcess("true");
// 创建PutObject请求。
PutObjectResult result = ossClient.putObject(putObjectRequest);
// 如果上传成功,则返回200。
System.out.println(result.getResponse().getStatusCode());
} 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();
}
}
}
根据实际需要修改里面的代码
3、keyId,secret申请
申请好以后,点击添加权限
修改代码:
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "oss-cn-shenzhen.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "LTAI5tGLaEV7SAFzbsBiqzLn";
String accessKeySecret = "xxxxxx";
// 填写Bucket名称,例如examplebucket。
String bucketName = "gulimall-ljshello";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String objectName = "huawei.png";
// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
String filePath= "C:\\Users\\Administrator\\Desktop\\huawei.png";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
InputStream inputStream = new FileInputStream(filePath);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
// 设置该属性可以返回response。如果不设置,则返回的response为空。
putObjectRequest.setProcess("true");
// 创建PutObject请求。
PutObjectResult result = ossClient.putObject(putObjectRequest);
// 如果上传成功,则返回200。
System.out.println(result.getResponse().getStatusCode());
System.out.println("上传成功。。。");
} 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();
}
}
4、springcloudalibaba oss对象存储
在common工程里引入
com.alibaba.cloud
spring-cloud-starter-alicloud-oss
在product工程里注释原生oss依赖
在product工程的application.yml里配置
alicloud:
access-key: LTAI5tGLaEV7SAFzbsBiqzLn
secret-key: xxxx
oss:
endpoint: oss-cn-shenzhen.aliyuncs.com
然后简化代码:
GulimallProductApplicationTest
package com.ljs.gulimall.product;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ljs.gulimall.product.entity.BrandEntity;
import com.ljs.gulimall.product.service.BrandService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallProductApplicationTest {
@Autowired
private BrandService brandService;
@Autowired
OSSClient ossClient;
@Test
public void uploadTest() throws FileNotFoundException {
// 填写Bucket名称,例如examplebucket。
String bucketName = "gulimall-ljshello";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String objectName = "xiaomi.png";
// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
String filePath= "C:\\Users\\Administrator\\Desktop\\xiaomi.png";
try {
InputStream inputStream = new FileInputStream(filePath);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
ossClient.putObject(putObjectRequest);
System.out.println("上传成功。。。");
} 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.1.8.release
将springcloudalibaba依赖和对象存储依赖放到third工程中
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.1.0.RELEASE
pom
import
com.alibaba.cloud
spring-cloud-starter-alicloud-oss
third工程引入common的依赖
com.ljs.gulimall
gulimall-common
0.0.1-SNAPSHOT
###将common服务的springcloudalibaba依赖和对象存储依赖删除
新建bootstrap.properties
spring.application.name=gulimall-third-party
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=e56f61fc-9f9f-43bd-9c27-16f894235366
spring.cloud.nacos.config.ext-config[0].data-id=oss.yaml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true
###nacos配置
###新建application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: gulimall-third-party
server:
port: 30000
因为第三方服务引入了common,common引入了mybatisplus。所以排除
com.ljs.gulimall
gulimall-common
0.0.1-SNAPSHOT
com.baomidou
mybatis-plus-boot-starter
GulimallThirdPartyApplication
package com.ljs.gulimall.thirdparty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GulimallThirdPartyApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallThirdPartyApplication.class, args);
}
}
测试第三方服务文件上传功能
将product的测试类移过来即可
参考阿里云oos官方文档
https://help.aliyun.com/document_detail/31926.html
application.yml
spring:
cloud:
alicloud:
access-key: LTAI5tGLaEV7SAFzbsBiqzLn
secret-key: xxx
oss:
endpoint: oss-cn-shenzhen.aliyuncs.com
bucket: gulimall-ljshello
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: gulimall-third-party
server:
port: 30000
OssController
package com.ljs.gulimall.thirdparty.controller;
import com.aliyun.oss.OSSClient;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
public class OssController {
@Autowired
private OSSClient 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 policy() {
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
// 填写Host地址,格式为https://bucketname.endpoint。
String host = "https://" + bucket + "." + endPoint;
// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
// String callbackUrl = "https://192.168.0.0:8888";
// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
// 前缀的生成方法可以根据每天日期作为文件夹生成
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format + "/";
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
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);
Map respMap = new LinkedHashMap();
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));
return respMap;
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
}
return null;
}
}
启动第三方服务报错
排查问题:
找到oss的三个自动装配类
发现这里注入了一个OSS的bean,而这个OSS是接口
所以我们自动装配时也直接装配这个OSS接口
走网关需要添加映射
gateway添加第三方服务路由
- id: third_party_route
#负载均衡到renren-fast服务
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
#网关重写
filters:
- RewritePath=/api/thirdparty/(?.*),/$\{segment}
重启网关。在访问
http://localhost:88/api/thirdparty/oss/policy
前端上传主要使用upload组件
将分布式基础的三个文件copy过来
多文件上传
multiUpload.vue
单文件上传
singleUpload.vue
点击上传
只能上传jpg/png文件,且不超过10MB
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);
})
});
}
将整个upload文件夹放于前端components文件夹下
修改代码
1、替换文件上传的地址
2、修改
brand-add-or-update.vue
// 引入文件上传组件
import SingleUpload from "@/components/upload/singleUpload";
package com.ljs.gulimall.thirdparty.controller;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.ljs.gulimall.common.utils.R;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
public class OssController {
@Autowired
private OSS 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 R policy() {
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
// 填写Host地址,格式为https://bucketname.endpoint。
String host = "https://" + bucket + "." + endPoint;
// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
// String callbackUrl = "https://192.168.0.0:8888";
// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
// 前缀的生成方法可以根据每天日期作为文件夹生成
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format + "/";
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
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);
Map respMap = new LinkedHashMap();
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));
return R.ok().put("data",respMap);
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
}
return null;
}
}
这时候需要设置阿里云对象存储
上传成功。
查看oss对象存储
继续完成新增,编辑功能
图片地址不应该以这种文本方式展示。应该展示图片
复制这块的template代码
重启发现前端报错
导入elementUI时图片组件可能忘记引入了
将这些复制进index.js
import {
Pagination,
Dialog,
Autocomplete,
Dropdown,
DropdownMenu,
DropdownItem,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Input,
InputNumber,
Radio,
RadioGroup,
RadioButton,
Checkbox,
CheckboxButton,
CheckboxGroup,
Switch,
Select,
Option,
OptionGroup,
Button,
ButtonGroup,
Table,
TableColumn,
DatePicker,
TimeSelect,
TimePicker,
Popover,
Tooltip,
Breadcrumb,
BreadcrumbItem,
Form,
FormItem,
Tabs,
TabPane,
Tag,
Tree,
Alert,
Slider,
Icon,
Row,
Col,
Upload,
Progress,
Spinner,
Badge,
Card,
Rate,
Steps,
Step,
Carousel,
CarouselItem,
Collapse,
CollapseItem,
Cascader,
ColorPicker,
Transfer,
Container,
Header,
Aside,
Main,
Footer,
Timeline,
TimelineItem,
Link,
Divider,
Image,
Calendar,
Backtop,
PageHeader,
CascaderPanel,
Loading,
MessageBox,
Message,
Notification
} from 'element-ui';
Vue.use(Pagination);
Vue.use(Dialog);
Vue.use(Autocomplete);
Vue.use(Dropdown);
Vue.use(DropdownMenu);
Vue.use(DropdownItem);
Vue.use(Menu);
Vue.use(Submenu);
Vue.use(MenuItem);
Vue.use(MenuItemGroup);
Vue.use(Input);
Vue.use(InputNumber);
Vue.use(Radio);
Vue.use(RadioGroup);
Vue.use(RadioButton);
Vue.use(Checkbox);
Vue.use(CheckboxButton);
Vue.use(CheckboxGroup);
Vue.use(Switch);
Vue.use(Select);
Vue.use(Option);
Vue.use(OptionGroup);
Vue.use(Button);
Vue.use(ButtonGroup);
Vue.use(Table);
Vue.use(TableColumn);
Vue.use(DatePicker);
Vue.use(TimeSelect);
Vue.use(TimePicker);
Vue.use(Popover);
Vue.use(Tooltip);
Vue.use(Breadcrumb);
Vue.use(BreadcrumbItem);
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Tabs);
Vue.use(TabPane);
Vue.use(Tag);
Vue.use(Tree);
Vue.use(Alert);
Vue.use(Slider);
Vue.use(Icon);
Vue.use(Row);
Vue.use(Col);
Vue.use(Upload);
Vue.use(Progress);
Vue.use(Spinner);
Vue.use(Badge);
Vue.use(Card);
Vue.use(Rate);
Vue.use(Steps);
Vue.use(Step);
Vue.use(Carousel);
Vue.use(CarouselItem);
Vue.use(Collapse);
Vue.use(CollapseItem);
Vue.use(Cascader);
Vue.use(ColorPicker);
Vue.use(Transfer);
Vue.use(Container);
Vue.use(Header);
Vue.use(Aside);
Vue.use(Main);
Vue.use(Footer);
Vue.use(Timeline);
Vue.use(TimelineItem);
Vue.use(Link);
Vue.use(Divider);
Vue.use(Image);
Vue.use(Calendar);
Vue.use(Backtop);
Vue.use(PageHeader);
Vue.use(CascaderPanel);
但还是加载失败
这里可以看到,有展示。但是展示不对。可以使用原生的img标签
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)) {
callback(new Error("排序必须是数字"));
} else if (value < 0){
callback(new Error("排序必须大于等于0"));
}else {
callback();
}
}, trigger: 'blur' },
],
1、给Bean添加校验注解:javax.validation.constaints,并自定义自己的message
2、开启校验功能valid。
效果:校验错误后,会有默认的提示
3、对校验的Bean后面可以紧跟BindingResult 对象。可以从此对象取出发生的错误信息和错误字段
BrandEntity
package com.ljs.gulimall.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
/**
* 品牌
*
* @author liangjiansong
* @email [email protected]
* @date 2022-10-22 21:21:08
*/
@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地址必须合法")
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;
}
BrandController
/**
* 保存
*/
@RequestMapping("/save")
// @RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand , BindingResult result){
if (result.hasErrors()){
Map map = new HashMap<>();
List fieldErrors = result.getFieldErrors();
fieldErrors.forEach(item -> {
// 获取封装的错误信息
String message = item.getDefaultMessage();
// 获取某个字段出现的错误
String field = item.getField();
map.put(field,message);
});
// error
return R.error(400,"提交的数据不合法").put("data",map);
}
brandService.save(brand);
return R.ok();
}
GulimallExcepitonControllerAdvice
package com.ljs.gulimall.product.exception;
import com.ljs.gulimall.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RestControllerAdvice(basePackages = "com.ljs.gulimall.product.controller")
public class GulimallExcepitonControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
BindingResult result = e.getBindingResult();
Map map = new HashMap<>();
List fieldErrors = result.getFieldErrors();
fieldErrors.forEach(item -> {
// 获取封装的错误信息
String message = item.getDefaultMessage();
// 获取某个字段出现的错误
String field = item.getField();
map.put(field,message);
});
// error
return R.error(400,"提交的数据不合法").put("data",map);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
return R.error();
}
}
这样就不用每个controller的方法里编写BindingResult处理了
BrandController
/**
* 保存
*/
@RequestMapping("/save")
// @RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand){
/* if (result.hasErrors()){
Map map = new HashMap<>();
List fieldErrors = result.getFieldErrors();
fieldErrors.forEach(item -> {
// 获取封装的错误信息
String message = item.getDefaultMessage();
// 获取某个字段出现的错误
String field = item.getField();
map.put(field,message);
});
// error
return R.error(400,"提交的数据不合法").put("data",map);
}*/
brandService.save(brand);
return R.ok();
}
###状态码的维护不应写死。应该维护。通常枚举
在common工程里新增一个枚举类
BizCodeEnum
package com.ljs.gulimall.common.Enum;
public enum BizCodeEnum {
UNKNOW_EXCEPTION(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;
}
}
修改全局异常类
package com.ljs.gulimall.product.exception;
import com.ljs.gulimall.common.Enum.BizCodeEnum;
import com.ljs.gulimall.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RestControllerAdvice(basePackages = "com.ljs.gulimall.product.controller")
public class GulimallExcepitonControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
BindingResult result = e.getBindingResult();
Map map = new HashMap<>();
List fieldErrors = result.getFieldErrors();
fieldErrors.forEach(item -> {
// 获取封装的错误信息
String message = item.getDefaultMessage();
// 获取某个字段出现的错误
String field = item.getField();
map.put(field,message);
});
// error
return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",map);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(), BizCodeEnum.UNKNOW_EXCEPTION.getMsg());
}
}
1、在实体类加groups属性
/**
* 品牌id
*/
@TableId
@NotNull(message = "编辑时id不能为空",groups = {UpdateGroup.class})
@Null(message = "新增时id为空",groups = {AddGroup.class})
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名不能为空",groups = {UpdateGroup.class,AddGroup.class})
private String name;
2、在common工程中新增AddGroup和UpdateGroup接口(空接口)
3、在controller中将@Valid注解换成@Validated注解
/**
* 保存
*/
@RequestMapping("/save")
// @RequiresPermissions("product:brand:save")
public R save(@Validated(value = {AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
当controller层如果@Validated指定了分组校验。则实体类指定了这个类的校验生效。如果如果@Validated没有指定分组校验。则实体类没有指定分组校验的注解生效。
1、编写自定义校验注解
2、编写自定义校验器
3、关联自定义校验器和自定义校验注解
ListValue
package com.ljs.gulimall.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{javax.validation.constraints.NotBlank.message}";
Class>[] groups() default { };
Class extends Payload>[] payload() default { };
int[] vals() default { };
}
BrandEntity
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals = {0,1}, groups = {AddGroup.class})
private Integer showStatus;
ValidationMessages.properties
javax.validation.constraints.NotBlank.message=提交的数据不在范围内
ListValueConstraintValidator
package com.ljs.gulimall.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
public class ListValueConstraintValidator implements ConstraintValidator {
Set set = new HashSet<>();
// 初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
// 判断是否校验成功
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
ListValue
// 可以指定多个不同的校验器,适配不同类型的校验
@Constraint(validatedBy = { ListValueConstraintValidator.class})
由于修改状态时。进了这个校验。所以单纯修改品牌不应该和修改状态为同一个接口
前后端都需要改
后端:
BrandController
/**
* 修改品牌状态
*/
@RequestMapping("/updateStatus")
// @RequiresPermissions("product:brand:update")
public R updateStatus(@Validated(value = {UpdateStatusGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
BrandEntity
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals = {0,1}, groups = {AddGroup.class,UpdateStatusGroup.class})
private Integer showStatus;
前端:
brand.vue
MybatisConfig
package com.ljs.gulimall.product.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@MapperScan("com.ljs.gulimall.product.dao")
public class MybatisConfig {
// 引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求页面大于最大页后操作,true到首页。false继续请求
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量 -1为不限制 500为具体数字
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
品牌模糊查询有问题
BrandServiceImpl
queryPage
@Override
public PageUtils queryPage(Map params) {
// 条件查询
QueryWrapper brandEntityQueryWrapper = new QueryWrapper<>();
if (Objects.nonNull(params.get("key"))){
String key = (String) params.get("key");
brandEntityQueryWrapper.eq("brand_id",key).or().like("name",key);
}
IPage page = this.page(
new Query().getPage(params),
brandEntityQueryWrapper
);
return new PageUtils(page);
}
将一部分前端代码拷贝入代码中
将前端代码的common和product放置于前端项目中
多对多关系。设计关联关系表
1、获取当前品牌关联关系列表(接口)
/**
* 列表
*/
@GetMapping("/catelog/list")
public R catelogList(@RequestParam("brandId") Long brandId){
List list = categoryBrandRelationService.
list(new QueryWrapper().eq("brand_id",brandId));
return R.ok().put("data", list);
}
2、新增品牌与分类的关联关系(接口)
/**
* 保存
*/
@RequestMapping("/save")
// @RequiresPermissions("product:categorybrandrelation:save")
public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
categoryBrandRelationService.saveDetail(categoryBrandRelation);
return R.ok();
}
void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
@Override
public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
BrandEntity brandEntity = brandDao.selectById(categoryBrandRelation.getBrandId());
CategoryEntity categoryEntity = categoryDao.selectById(categoryBrandRelation.getCatelogId());
if (brandEntity != null){
categoryBrandRelation.setBrandName(brandEntity.getName());
}
if (categoryEntity != null){
categoryBrandRelation.setCatelogName(categoryEntity.getName());
}
this.save(categoryBrandRelation);
}
有相同的前端问题
样式有问题。选中后无选中
在style中指定样式
测试了下目前关联关系展示、新增、删除无问题
###当品牌信息更新后,关联表品牌名无更新。就不好。需要做判断
在处理过程中还需加入事务
修改品牌及分类
BrandController
/**
* 修改
*/
@RequestMapping("/update")
// @RequiresPermissions("product:brand:update")
public R update(@Validated(value = {UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateDetail(brand);
return R.ok();
}
CategoryController
/**
* 修改
*/
@RequestMapping("/update")
// @RequiresPermissions("product:category:update")
public R update(@RequestBody CategoryEntity category){
categoryService.updateDetail(category);
return R.ok();
}
BrandService
void updateDetail(BrandEntity brand);
CategoryService
void updateDetail(CategoryEntity category);
BrandServiceImpl
@Override
public void updateDetail(BrandEntity brand) {
this.updateById(brand);
// 当更新了品牌名。其他关联关系表品牌名也需要更新
if(Strings.isNotBlank(brand.getName())){
categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
}
}
CategoryServiceImpl
@Override
public void updateDetail(CategoryEntity category) {
this.updateById(category);
// 当有级联更新分类名称时,同时更新关联表名称
if (Strings.isNotBlank(category.getName())){
categoryBrandRelationService.updateCateGory(category.getCatId(),category.getName());
}
}
CategoryBrandRelationService
void updateBrand(Long brandId, String name);
void updateCateGory(Long catId, String name);
CategoryBrandRelationServiceImpl
@Transactional
@Override
public void updateBrand(Long brandId, String name) {
CategoryBrandRelationEntity relationEntity = new CategoryBrandRelationEntity();
relationEntity.setBrandId(brandId);
relationEntity.setBrandName(name);
this.update(relationEntity,new QueryWrapper().eq("brand_id",brandId));
}
@Transactional
@Override
public void updateCateGory(Long catId, String name) {
// 自定义sql方式
this.baseMapper.updateCateGory(catId,name);
}
CategoryBrandRelationDao
/**
* updateCateGory
*
* @param catId catId
* @param name name
*/
void updateCateGory(@Param("catId") Long catId, @Param("name") String name);
CategoryBrandRelationDao.xml
update pms_category_brand_relation
set catelog_name = #{name}
where catelog_id = #{catId}