复制到 product 文件夹下,重启 renren-fast-vue 项目,但是这里只有查询,没有新增和删除,是因为权限的问题
Ctrl + Shift + F 全局搜索 isAuth
是在 index.js 中定义是否有权限的,注释掉直接返回 true,一直都有权限
/**
* 是否有权限
* @param {*} key
*/
/**
* 是否有权限
* @param {*} key
*/
export function isAuth (key) {
// return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
return true;
}
在测试就都好使了,但是细节需要完善
关闭 Eslint 语法检查
参考文档:https://help.aliyun.com/document_detail/31947.html?spm=5176.8465980.0.dexternal.4e701450OZd0NW
创建 gulimall-third-party 模块,引入第三方服务
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.8.RELEASEversion>
<relativePath/>
parent>
<groupId>com.atguigu.gulimallgroupId>
<artifactId>gulimall-third-partyartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>gulimall-third-partyname>
<description>第三方服务description>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Greenwich.SR3spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>com.atguigu.gulimallgroupId>
<artifactId>gulimall-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
<exclusions>
<exclusion>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alicloud-ossartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
注册中心 和 服务中心 的地址都要写上,一个不写注册中心都注册不上,端口号改为 3000
bootstrap.properties
spring.application.name=gulimall-third-party
spring.cloud.nacos.config.namespace=2c95d4e7-2400-49ae-ba0a-a3bd9e470c11
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
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
application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
alicloud:
access-key: 填写自己的阿里云
secret-key: 填写自己的阿里云
oss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket: gulishangcheng24
# alicloud:
# access-key: LTAI5tNhkwhLZWnEK5m2eukX
# acce
# oss:
# endpoint: oss-cn-beijing.aliyuncs.com
application:
name: gulimall-third-party
server:
port: 30000
配置中心新建 third-party 命名空间,填写阿里云 RAM 的配置(之后在配置也可以)
OssController
@RestController
public class OssController {
@Resource
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 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;
}
}
启动服务浏览器输入 http://localhost:30000/oss/policy/ 测试返回数据
浏览器就可以请求这个接口,然后服务器给浏览器返回这串信息,浏览器带着它以及要上传的文件提交到阿里云服务器,阿里云进行校验并存储上传的内容
接下来配置一下网关,localhost:88/api/thirdparty/oss/policy
测试成功
复制 upload 文件夹到 components
改成自己的阿里云地址
brand-add-or-update.vue 引入并声明组件
这个名字根据 components 定义来的
更改一下后端 policy 方法返回值,从返回 map 改为返回 R 对象,前端要从 data 里面拿数据
阿里云仓库配置一下跨域,要不上传文件 403
这个图片回显真搞我心态啊,弄了好久就是不回显,后端前端查,弄了半天把前端 upload 文件夹删了从弄一遍就回显了,应该是我前端哪块不小心弄错了
细节:显示状态按钮在数据库里面存放的是 0 和 1,激活是 1,不激活是 0
添加商品成功之后这显示的是文本信息,应该显示的是图片,我们要自定义显示,去 elementui 上面找
这引入 elementui 图片组件的时候报错,应该是 renren-fast-vue 的工程里没有引入这个组件,我们自己手动引入,到 elementui 官网快速上手里面有
改过之后还是不回显图片,干脆使用原生的 image 组件
前端表单校验
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",
},
],
统一异常处理
返回状态码放到 common 模块中
package com.atguigu.common.exception;
public enum BizCodeEnume {
UNKNOW_EXEPTION(10000,"系统未知异常"),
VALID_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;
}
}
异常处理类 Product 模块中
@Slf4j
@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(BizCodeEnume.VALID_EXCEPTION.getCode(), BizCodeEnume.VALID_EXCEPTION.getMsg()).put("data", errorMap);
}
// 大的异常
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable) {
return R.error(BizCodeEnume.UNKNOW_EXEPTION.getCode(), BizCodeEnume.UNKNOW_EXEPTION.getMsg());
}
}
新增情况下的校验和修改情况下的校验可能不一样 —— 分组校验
/**
* 品牌
*
* @author renjianbang
* @email [email protected]
* @date 2021-04-20 09:51:57
*/
@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-显示]
*/
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;
}
controller 不同的方法选择不同的分组,根据 spring 提供的注解 @Validated
/**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
groups 参数需要传递接口,接口规定了在什么时候触发规则
public interface AddGroup {
}
public interface UpdateGroup {
}
自定义校验
common 模块引入
<dependency>
<groupId>javax.validationgroupId>
<artifactId>validation-apiartifactId>
<version>2.0.1.Finalversion>
dependency>
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class}) // 校验器
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) // 哪都可以标注
@Retention(RUNTIME)
public @interface ListValue {
// 使用该属性去Validation.properties中取
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
// 数组,需要用户自己指定
int[] vals() default {};
}
public class ListValueConstraintValidator
implements ConstraintValidator<ListValue,Integer> { //<注解,校验值类型>
// 存储所有可能的值
private Set<Integer> set = new HashSet<>();
@Override // 初始化,你可以获取注解上的内容并进行处理
public void initialize(ListValue constraintAnnotation) {
// 获取后端写好的限制 // 这个value就是ListValue里的value,我们写的注解是@ListValue(value={0,1})
int[] value = constraintAnnotation.vals();
for (int i : value) {
set.add(i);
}
}
@Override // 覆写验证逻辑
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// 看是否在限制的值里
return set.contains(value);
}
}
实体类添加自定义注解
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals = {0, 1}, groups = {AddGroup.class})
private Integer showStatus;
SPU:Standard Product Unit(标准化产品单元)是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
iphoneX 是 SPU、MI 8 是 SPU
iphoneX 64G 黑曜石 是 SKU
MI8 8+64G+黑色 是 SKU
SKU:Stock Keeping Unit(库存量单位)即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU 这是对于大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的 SKU 号
SKU 是 SPU 更具体的信息
基本属性【规格参数】与销售属性
属性是以三级分类组织起来的
规格参数中有些是可以提供检索的
规格参数也是基本属性,他们具有自己的分组
属性的分组也是以三级分类组织起来的
属性名确定的,但是值是每一个商品不同来决定的
每个分类下的商品共享规格参数,与销售属性。只是有些商品不一定要用这个分类下全部的
属性;
属性表: pms_attr
属性分组表: pms_attr_group
属性表和属性分组表关联起来的表: pms_attr_attrgroup_relation
商品属性值表: pms_product_attr_value,,保存了各个属性所对应的属性值和各种 id 什么的
spu 描述信息表: pms_spu_info,概括商品信息,具体指在上面的那张表,这张表比较笼统
sku 信息表: pms_sku_info,spu 包含 sku,一个商品到底有多少个 sku 存放在这里面
sku 销售属性值表:pms_sku_sale_attr_value
结合数据库的表好好理解下面这两张图,就能清晰很多
【属性分组-规格参数-销售属性-三级分类】关联关系
SPU-SKU-属性表