谷粒商城项目(学习笔记一)
谷粒商城项目(学习笔记二)
谷粒商城项目(学习笔记三)
谷粒商城项目(学习笔记四)
谷粒商城项目(学习笔记五)
第四章:商品服务——品牌管理
一、逆向工程添加
二、优化页面
1.关掉代码检查
?2.快速显示开关
3.文件OSS上传功能
4.表单的管理
一、逆向工程添加
将renren-fast逆向工程生成的代码放入renren-fast-vue的视图中
brand.vue和brand-add-or-update.vue
CURL开关默认是关的,注销掉按钮显示的判断,默认开启
/**
* 是否有权限
* @param {*} key
*/
export function isAuth(key) {
// return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
return true;
}
二、优化页面
1.关掉代码检查
在build的webpack.base.conf.js中,注销createLintingRule,重启
?2.快速显示开关
1.引入自定义的table表格模板,为brand-add-or-update显示状态添加一个开关
注意:新的mybatis-puls的逻辑删除,Show-Status会出bug,所以改brand的状态是display_status
<el-table-column
prop="displayStatus"
header-align="center"
align="center"
label="显示状态"
>
<template slot-scope="scope">
<el-switch
v-model="scope.row.displayStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
@change="updateBrandStatus(scope.row)"
>
</el-switch
></template>
</el-table-column>
2.添加改变绑定状态方法
//改变显示状态
updateBrandStatus(data) {
console.log("最新信息", data);
let { brandId, displayStatus } = data;
//发送更新信息给后端
this.$http({
url: this.$http.adornUrl("/product/brand/update"),
method: "post",
data: this.$http.adornData({ brandId, displayStatus }, false)
}).then(({ data }) => {
this.$message({
message: "操作成功",
type: "success"
});
});
},
?3.修改brand显示状态也为一个开关并且同步显示
<el-form-item label="显示状态" prop="displayStatus">
<el-switch
v-model="dataForm.displayStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
>
</el-switch>
</el-form-item>
3.文件OSS上传功能
官方指南安装 - 对象存储 OSS - 阿里云
1.在阿里云OSS新建一个仓库
2.为common中引入spring-cloud-alibaba的oss的sdk
注意:spring-boot和spring-cloud和spring-cloud-alibaba的版本问题
<!--spring-cloud-alibaba的oss服务-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
3.引入自己角色的key
alibaba:
cloud:
access-key: LTAI****************
secret-key: 5hm*******************
oss:
endpoint: oss-cn-beijing.aliyuncs.com
4.在项目中进行测试
@SpringBootTest
public class ProductApplicationTests {
@Resource
OSSClient ossClient;
@Test
public void testUpload() throws FileNotFoundException {
// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
InputStream inputStream = new FileInputStream("D:\\YY\\Pictures\\Saved Pictures\\b.png");
// 依次填写Bucket名称(例如examplebucket)和Object完整路径(例如exampledir/exampleobject.txt)。Object完整路径中不能包含Bucket名称。
ossClient.putObject("gulimall-20211118", "b1.png", inputStream);
// 关闭OSSClient。
ossClient.shutdown();
}
}
5.新建一个第三方模组,封装oss服务
1)新建模组 gulimall-third-party
将第三方spring-cloud-starter-alicloud-oss移动到gulimall-third-party中
同时引入gulimall-common时,需要排除mybatis和mysql组件
<!--spring-cloud-alibaba的oss服务-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.yangyan.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</exclusion>
</exclusions>
</dependency>
2)配置yaml,并将oss配置到nacos中
server:
port: 5000
spring:
application:
name: gulimall-third-party
cloud:
nacos:
server-addr: localhost:8848
alicloud:
access-key: LTAI5t******************
secret-key: 5hmWDy*************************
oss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket: gulima***********
3)新建OssController控制器
注意:ossClient要配置为OSS
@RestController
public class OssController {
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Resource
OSS ossClient;
@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("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;
}
}
4)将访问地址提供给网关
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
5)测试访问
http://localhost:88/api/thirdparty/oss/policyhttp://localhost:88/api/thirdparty/oss/policy6.与前端整合
1)将upload文件夹放入src/components目录下
2)修改action为自己仓库的地址并检查policy.js的addUrl与后端的地址是否一致
action="http://gulimall-20211118.oss-cn-beijing.aliyuncs.com"
3)在brand-add-or-update.vue中导入脚本
注意这里斜杠为"/"而不是"\"
注意添加components
import SingleUpload from "@/components/upload/singleUpload";
export default {
components: {
SingleUpload
},
4)修改brand-add-or-update品牌logo地址为一个提交文件的按钮
<single-upload v-model="dataForm.logo"></single-upload>
5)测试提交
4.表单的管理
1.前端logo的回显
自定义显示一个图片
注意如果无法引入图片可能是模板没有,需要手动添加模板
Element - The world's most popular Vue UI framework可以看看官网的快速入门
在src>element-ui>index.js中
<template slot-scope="scope">
<img
:src="scope.row.logo"
style="width: 100px; height: 100px"
fit="contain"
/>
</template>
2.表单前端的校验
1)校验首字母
修改dataRule的校验规则:首字母必须填写,首字母必须是A-Z或者是a-z之间
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"
}
],
2)校验排序
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"
}
]
要接收一个数字必须先将接收的内容规定为数字
即改为v-model.number
<el-form-item label="排序" prop="sort">
<el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>
3.表单后端的校验(jsr303)
1)标记校验注解javax.validation.constraints
注意看各个注解可以标注的属性
注意Pattern的正则表达式需要删除左右的两个“/”
@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地址
*/
@NotBlank(message = "品牌logo地址不能为空")
@URL(message = "logo必须是一个合法的url地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer displayStatus;
/**
* 检索首字母
*/
@NotEmpty(message = "首字母不能为空")
@Pattern(regexp = "^[a-zA-Z]$",message = "首字母必须是一个字母")
private String firstLetter;
/**
* 排序
*/
@NotNull(message = "排序字段不能为空")
@Min(value = 0,message = "排序必须大于等于0")
private Integer sort;
}
2)为controller开启校验
@Valid为开启验证,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)->{
String message = item.getDefaultMessage();
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}else {
brandService.save(brand);
return R.ok();
}
}
3)统一异常处理
在common中新建com.yangyan.gulimall.product.exception.ExceptionControllerAdvice
将之前的错误判断放过来,并且添加一个错误信息枚举
@Slf4j
@RestControllerAdvice(basePackages = "com.yangyan.gulimall.product.controller")
public class ExceptionControllerAdvice {
@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(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Exception.class)
public R handleException(Exception e) {
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
package com.yangyan.common.exception;
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
*
*
*/
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_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;
}
}
测试成功
?4.分组校验
1)在common中抽取分组接口,作为注解,给属性分组
/**
* 品牌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;
2)在控制器添加分组
将save的开启注解改为@Validated({AddGroup.class})
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){}
加了AddGroup.class的都会进行判断,而不加的则不会判断
3)全部修改后
/**
* 品牌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(message = "品牌logo地址不能为空",groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址",groups = {AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer displayStatus;
/**
* 检索首字母
*/
@NotEmpty(message = "首字母不能为空",groups = {AddGroup.class})
@Pattern(regexp = "^[a-zA-Z]$",message = "首字母必须是一个字母",groups = {AddGroup.class,UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(message = "排序字段不能为空",groups = {AddGroup.class})
@Min(value = 0,message = "排序必须大于等于0",groups = {AddGroup.class,UpdateGroup.class})
private Integer sort;
5.自定义校验
1)编写一个自定义的校验注解ListValue?
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
public @interface ListValue {
String message() default "{com.yangyan.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] vals() ;
}
2)编写一个自定义的校验器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private 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);
}
}
3)关联自定义的检验注解和校验器
在校验注解中添加校验器的实现类
@Constraint(validatedBy = {ListValueConstraintValidator.class})
4)添加上注解进行前后端的测试
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue( message = "必须提交指定的值",vals={0,1},groups = {AddGroup.class})
private Integer displayStatus;
?BilBil视频地址:尚硅谷电商教程《谷粒商城》对标阿里P6/P7,40-60万年薪_哔哩哔哩_bilibili
|