image.png

  1. create table pms_brand (
  2. brand_id bigint not null auto_increment comment '品牌id',
  3. name char(50) comment '品牌名',
  4. logo varchar(2000) comment '品牌logo地址',
  5. descript longtext comment '介绍',
  6. show_status tinyint comment '显示状态[0-不显示;1-显示]',
  7. first_letter char(1) comment '检索首字母',
  8. sort int comment '排序',
  9. primary key (brand_id)
  10. );
  11. alter table pms_brand comment '品牌';

image.png

1、文件存储

1)传统的文件存储

image.png

2)阿里云-普通上传方式

  1. 先把字节流给服务器,服务器转发给阿里云 <br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22523384/1638187173170-6dba85fc-337f-4137-8dc3-78883ccdd5af.png#clientId=u82f70192-7dc6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=324&id=u0804a304&margin=%5Bobject%20Object%5D&name=image.png&originHeight=324&originWidth=692&originalType=binary&ratio=1&rotation=0&showTitle=false&size=90339&status=done&style=none&taskId=u09c7e65b-f2cb-471c-9b77-23fae800c22&title=&width=692)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22523384/1638188288824-a44be300-aec3-4c79-ac73-417a0eb9d56d.png#clientId=u82f70192-7dc6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=554&id=ufa97eabc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=554&originWidth=1175&originalType=binary&ratio=1&rotation=0&showTitle=false&size=66858&status=done&style=none&taskId=ufe16fd79-da1a-478d-8116-95fdcdcd23f&title=&width=1175)<br />endpoint的取值:点击概览就可以看到你的endpoint信息,endpoint在这里就是上海等地区,如 oss-cn-qingdao.aliyuncs.com<br /> bucket域名:就是签名加上bucket,如gulimall-fermhan.oss-cn-qingdao.aliyuncs.com

accessKey的获取
accessKeyId和accessKeySecret需要创建一个RAM账号:
image.png
选上编程访问
创建用户完毕后,会得到一个“AccessKey ID”和“AccessKeySecret”,然后复制这两个值到代码的“AccessKey ID”和“AccessKeySecret”。
另外还需要添加访问控制权限:
image.png

使用SpringCloud Alibaba来管理oss:
1)添加依赖

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
  4. </dependency>

2)配置key,secret和endpoint相关信息

  1. spring:
  2. cloud:
  3. nacos:
  4. discovery:
  5. server-addr: 127.0.0.1:8848
  6. alicloud:
  7. access-key: LTAI4FwvfjSycd1APnuG9bjj
  8. secret-key: O6xaxyiWfSIitcOkSuK27ju4hXT5Hl
  9. oss:
  10. endpoint: oss-cn-beijing.aliyuncs.com
  11. bucket: gulimall-hello

3)注入OSSClient并进行文件上传下载等操作
image.png

3)阿里云-服务端签名后直传

文件还需要传到java后端中转一下,没有必要,如何能直接传到oss就好了。所以考虑把AccessKeyID和AcessKeySecret给前端传过去,前端直接传到oss?

但是,采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,ali-OSS提供了服务端签名后直传的方案。
image.png
002-品牌管理 - 图9
image.png
image.png

2、服务端签名整合测试

新建gulimall-third-party服务,添加依赖

  1. <!-- 阿里云对象存储OSS -->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-alicloud-oss</artifactId>
  5. </dependency>

1)bootstrap.properties

  1. spring.application.name=gulimall-third-party
  2. spring.cloud.nacos.config.server-addr=127.0.0.1:8848
  3. spring.cloud.nacos.config.namespace=4252b29b-4d4a-4f17-91ce-a164163f288a
  4. spring.cloud.nacos.config.extension-configs[0].data-id=oss.yml
  5. spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
  6. spring.cloud.nacos.config.extension-configs[0].refresh=true

2)application.yml

  1. spring:
  2. cloud:
  3. nacos:
  4. discovery:
  5. server-addr: 127.0.0.1:8848
  6. alicloud:
  7. access-key: LTAI5tRK=424xr1mB12
  8. secret-key: SyfI5Zh3JwDgUzuIUL
  9. oss:
  10. endpoint: oss-cn-chengdu.aliyuncs.com
  11. bucket: gulimall-200615
  12. application:
  13. name: gulimall-third-party
  14. server:
  15. port: 30000

3)OssController

  1. @RestController
  2. public class OssController {
  3. // 创建OSSClient实例
  4. @Autowired
  5. OSS ossClient;
  6. @Value("${spring.cloud.alicloud.oss.endpoint}")
  7. private String endpoint;
  8. @Value("${spring.cloud.alicloud.oss.bucket}")
  9. private String bucket;
  10. @Value("${spring.cloud.alicloud.access-key}")
  11. private String accessId;
  12. @RequestMapping("/oss/policy")
  13. public R policy(){
  14. // host的格式为 bucketname.endpoint
  15. String host = "https://" + bucket + "." + endpoint;
  16. // callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
  17. //String callbackUrl = "http://88.88.88.88:8888";
  18. String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
  19. String dir = format + "/"; // 用户上传文件时指定的前缀。即文件夹
  20. Map<String, String> respMap = null;
  21. try {
  22. long expireTime = 30;
  23. long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
  24. Date expiration = new Date(expireEndTime);
  25. // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
  26. PolicyConditions policyConds = new PolicyConditions();
  27. policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
  28. policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
  29. String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
  30. byte[] binaryData = postPolicy.getBytes("utf-8");
  31. String encodedPolicy = BinaryUtil.toBase64String(binaryData);
  32. String postSignature = ossClient.calculatePostSignature(postPolicy);
  33. respMap = new LinkedHashMap<String, String>();
  34. respMap.put("accessid", accessId);
  35. respMap.put("policy", encodedPolicy);
  36. respMap.put("signature", postSignature);
  37. respMap.put("dir", dir);
  38. respMap.put("host", host);
  39. respMap.put("expire", String.valueOf(expireEndTime / 1000));
  40. // respMap.put("expire", formatISO8601Date(expiration));
  41. } catch (Exception e) {
  42. // Assert.fail(e.getMessage());
  43. System.out.println(e.getMessage());
  44. } finally {
  45. ossClient.shutdown();
  46. }
  47. return R.ok().put("data",respMap);
  48. }
  49. }

4)测试

image.png
image.png

3、表单校验&自定义校验器

1)前端表单校验

image.png
image.png

2)自定义校验器

  1. /**
  2. * 集中处理所有controller抛过来的异常
  3. */
  4. @Slf4j
  5. //@ResponseBody
  6. //@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.app")
  7. @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.app")
  8. public class GulimallExceptionControllerAdvice {
  9. /**
  10. * 处理controller数据校验错误。精确处理异常
  11. * @param e
  12. * @return
  13. */
  14. @ExceptionHandler(value= MethodArgumentNotValidException.class)
  15. public R handleVaildException(MethodArgumentNotValidException e){
  16. log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
  17. BindingResult bindingResult = e.getBindingResult();
  18. Map<String,String> errorMap = new HashMap<>();
  19. bindingResult.getFieldErrors().forEach((fieldError)->{
  20. // 获取到错误提示
  21. String message = fieldError.getDefaultMessage();
  22. // 获取错误属性的名字
  23. String field = fieldError.getField();
  24. errorMap.put(field,message);
  25. });
  26. return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(), BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
  27. }
  28. /**
  29. * 处理自定义的异常
  30. * @param e
  31. * @return
  32. */
  33. @ExceptionHandler(value = GuliException.class)
  34. public R GuliException(GuliException e){
  35. log.error("错误:", e);
  36. return R.error(e.getCode(), e.getMsg());
  37. }
  38. /**
  39. * 处理所有类型的异常
  40. * @param throwable
  41. * @return
  42. */
  43. @ExceptionHandler(value = Throwable.class)
  44. public R handleException(Throwable throwable){
  45. log.error("错误:",throwable);
  46. return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(), BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
  47. }
  48. }

3)错误码枚举类

  1. package com.atguigu.common.exception;
  2. /***
  3. * 错误码和错误信息定义类
  4. * 1. 错误码定义规则为5为数字
  5. * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
  6. * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
  7. * 错误码列表:
  8. * 10: 通用
  9. * 001:参数格式校验
  10. * 11: 商品
  11. * 12: 订单
  12. * 13: 购物车
  13. * 14: 物流
  14. */
  15. public enum BizCodeEnume {
  16. UNKNOW_EXCEPTION(10000,"系统未知异常"),
  17. VAILD_EXCEPTION(10001,"参数格式校验失败"),
  18. PRODUCT_UP_EXCEPTION(11000,"商品上架异常"),
  19. SMS_CODE_EXCEPTION(10002,"发送验证码太频繁,请稍后再试"),
  20. TO_MANY_REQUEST(10003,"请求流量过大"),
  21. USER_EXIST_EXCEPTION(15001,"用户已存在"),
  22. PHONE_EXIST_EXCEPTION(15002,"手机号码已存在"),
  23. PHONE_NULL_EXCEPTION(15003,"未输入手机号码"),
  24. NO_STOCK_EXCEPTION(21000,"商品库存不足"),
  25. LOGINACCT_PASSWORD_INVAILD_EXCEPTION(15002,"账号或密码错误");
  26. private int code;
  27. private String msg;
  28. BizCodeEnume(int code, String msg){
  29. this.code = code;
  30. this.msg = msg;
  31. }
  32. public int getCode() {
  33. return code;
  34. }
  35. public String getMsg() {
  36. return msg;
  37. }
  38. }

4、JSR303数据校验

1)给Bean添加校验注解

给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示

  1. /**
  2. * 品牌
  3. */
  4. @Data
  5. @TableName("pms_brand")
  6. public class BrandEntity implements Serializable {
  7. private static final long serialVersionUID = 1L;
  8. /**
  9. * 品牌id
  10. */
  11. @NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
  12. @Null(message = "新增不能指定id",groups = {AddGroup.class})
  13. @TableId
  14. private Long brandId;
  15. /**
  16. * 品牌名
  17. */
  18. @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
  19. private String name;
  20. /**
  21. * 品牌logo地址
  22. */
  23. @NotBlank(groups = {AddGroup.class})
  24. @URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
  25. private String logo;
  26. /**
  27. * 介绍
  28. */
  29. private String descript;
  30. /**
  31. * 显示状态[0-不显示;1-显示]
  32. */
  33. // @Pattern()
  34. @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
  35. @ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
  36. private Integer showStatus;
  37. /**
  38. * 检索首字母
  39. */
  40. @NotEmpty(groups={AddGroup.class})
  41. @Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
  42. private String firstLetter;
  43. /**
  44. * 排序
  45. */
  46. @NotNull(groups={AddGroup.class})
  47. @Min(value = 0,message = "排序必须大于等于0",groups={AddGroup.class,UpdateGroup.class})
  48. private Integer sort;
  49. }

2)开启校验功能@Valid

效果:告诉springMVC这个参数需要校验。校验错误以后会有默认的响应;

  1. /**
  2. * 保存
  3. */
  4. @RequestMapping("/save")
  5. public R save(@Valid @RequestBody BrandEntity brand){
  6. brandService.save(brand);
  7. return R.ok();
  8. }

image.png
3)使用BindingResult

给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果

  1. /**
  2. * 保存
  3. */
  4. @RequestMapping("/save")
  5. public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
  6. if(result.hasErrors()){
  7. HashMap<String, String> map = new HashMap<>();
  8. // 1、获取所有校验的错误结果
  9. result.getFieldErrors().forEach(fieldError -> {
  10. // 获取到错误提示
  11. String message = fieldError.getDefaultMessage();
  12. // 获取错误属性的名字
  13. String field = fieldError.getField();
  14. map.put(field,message);
  15. });
  16. return R.error(400, "参数不合法").put("data",map);
  17. }else {
  18. brandService.save(brand);
  19. return R.ok();
  20. }
  21. }

4)分组校验(多场景的复杂校验)

① 自定义分组接口

  1. package com.atguigu.common.valid;
  2. public interface AddGroup {
  3. }
  1. package com.atguigu.common.valid;
  2. public interface UpdateGroup {
  3. }
  1. package com.atguigu.common.valid;
  2. public interface UpdateStatusGroup {
  3. }

② 校验注解加上校验场景

@NotBlank(message = “品牌名必须提交”,groups = {AddGroup.class,UpdateGroup.class}),给校验注解标注什么情况需要进行校验

  1. @Data
  2. @TableName("pms_brand")
  3. public class BrandEntity implements Serializable {
  4. private static final long serialVersionUID = 1L;
  5. /**
  6. * 品牌id
  7. */
  8. @NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
  9. @Null(message = "新增不能指定id",groups = {AddGroup.class})
  10. @TableId
  11. private Long brandId;
  12. /**
  13. * 品牌名
  14. */
  15. @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
  16. private String name;
  17. ********
  18. }

③ controller上使用@Validated({UpdateGroup.class})

  1. /**
  2. * 修改
  3. */
  4. @RequestMapping("/update")
  5. public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
  6. brandService.updateDetail(brand);
  7. return R.ok();
  8. }

注意:默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({UpdateGroup.class})下不生效,只会在@Validated生效;所以使用@Validated时,所有属性均要使用groups指定分组才会起作用

5)自定义校验

① 编写自定义的校验注解ListValue

  1. //ListValueConstraintValidator 校验器
  2. @Documented
  3. // 可以指定多个不同的校验器,适配不同类型的校验
  4. @Constraint(validatedBy = { ListValueConstraintValidator.class })
  5. @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
  6. @Retention(RUNTIME)
  7. public @interface ListValue {
  8. // 在配置文件ValidationMessages.properties中
  9. // 校验出错后,出错信息获取的位置
  10. String message() default "{com.atguigu.common.valid.ListValue.message}";
  11. // 支持分组校验的功能
  12. Class<?>[] groups() default { };
  13. // 支持自定义负载信息
  14. Class<? extends Payload>[] payload() default { };
  15. // 数组,需要用户自己指定
  16. int[] vals() default { };
  17. }

image.png

② 编写自定义的校验器 ConstraintValidator

  1. public class ListValueConstraintValidator
  2. implements ConstraintValidator<ListValue,Integer> {//<校验注解, 校验值类型>
  3. // 存储所有可能的值
  4. private Set<Integer> set = new HashSet<>();
  5. // 初始化方法,可以获取注解上的内容并进行处理
  6. @Override
  7. public void initialize(ListValue constraintAnnotation) {
  8. // 获取属性上的所有数据,这个vals就是ListValue里的vals,属性上的注解是@ListValue(vals={0,1})
  9. int[] vals = constraintAnnotation.vals();
  10. for (int val : vals) {
  11. set.add(val);
  12. }
  13. }
  14. /**
  15. * 判断是否校验成功
  16. * @param value 需要校验的值
  17. * @param context 整个上下文环境信息
  18. * @return 判断需要校验的值是否在属性上的所有枚举值
  19. */
  20. @Override
  21. public boolean isValid(Integer value, ConstraintValidatorContext context) {
  22. return set.contains(value);
  23. }
  24. }

③ 关联自定义的校验器和自定义的校验注解

@Constraint(validatedBy = { ListValueConstraintValidator.class })

  1. //ListValueConstraintValidator 校验器
  2. @Documented
  3. // 可以指定多个不同的校验器,适配不同类型的校验
  4. @Constraint(validatedBy = { ListValueConstraintValidator.class })
  5. @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
  6. @Retention(RUNTIME)
  7. public @interface ListValue {
  8. // 在配置文件ValidationMessages.properties中
  9. // 校验出错后,出错信息获取的位置
  10. String message() default "{com.atguigu.common.valid.ListValue.message}";
  11. // 支持分组校验的功能
  12. Class<?>[] groups() default { };
  13. // 支持自定义负载信息
  14. Class<? extends Payload>[] payload() default { };
  15. // 数组,需要用户自己指定
  16. int[] vals() default { };
  17. }

④ 使用自定义校验注解

  1. /**
  2. * 显示状态[0-不显示;1-显示]
  3. */
  4. @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
  5. @ListValue(vals={0,1}, groups = {AddGroup.class, UpdateStatusGroup.class})
  6. private Integer showStatus;

5、品牌与分类关联

image.png

  1. create table pms_category_brand_relation (
  2. id bigint not null auto_increment,
  3. brand_id bigint comment '品牌id',
  4. catelog_id bigint comment '分类id',
  5. brand_name varchar(255),
  6. catelog_name varchar(255),
  7. primary key (id)
  8. );
  9. alter table pms_category_brand_relation comment '品牌分类关联';

1)获取品牌关联的分类

  1. /**
  2. * 获取当前品牌关联的所有分类列表
  3. */
  4. @GetMapping("/catelog/list")
  5. public R catelogList(@RequestParam("brandId") Long branId){
  6. LambdaQueryWrapper<CategoryBrandRelationEntity> wrapper = new LambdaQueryWrapper<>();
  7. wrapper.eq(CategoryBrandRelationEntity::getBrandId,branId);
  8. List<CategoryBrandRelationEntity> list = categoryBrandRelationService.list(wrapper);
  9. return R.ok().put("data", list);
  10. }

2)新增品牌与分类关联

  1. /**
  2. * 新增品牌与分类关联关系
  3. */
  4. @RequestMapping("/save")
  5. //@RequiresPermissions("product:categorybrandrelation:save")
  6. public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
  7. categoryBrandRelationService.saveDetail(categoryBrandRelation);
  8. return R.ok();
  9. }
  1. /**
  2. * 新增品牌与分类关联关系
  3. * @param categoryBrandRelation
  4. */
  5. @Override
  6. public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
  7. Long brandId = categoryBrandRelation.getBrandId();
  8. Long catelogId = categoryBrandRelation.getCatelogId();
  9. BrandEntity brandEntity = brandDao.selectById(brandId);
  10. CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
  11. categoryBrandRelation.setBrandName(brandEntity.getName());
  12. categoryBrandRelation.setCatelogName(categoryEntity.getName());
  13. this.save(categoryBrandRelation);
  14. }

3)级联更新

image.png

  1. /**
  2. * BrandController:修改品牌
  3. */
  4. @RequestMapping("/update")
  5. public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
  6. brandService.updateDetail(brand);
  7. return R.ok();
  8. }
  9. /**
  10. * BrandServiceImpl:修改品牌
  11. */
  12. @Transactional
  13. @Override
  14. public void updateDetail(BrandEntity brand) {
  15. // 保证冗余字段的数据一致性
  16. // 1、首先更新自身表的数据
  17. this.updateById(brand);
  18. // 2、如果品牌名称不为空,更新关联表的数据
  19. if(!StringUtils.isEmpty(brand.getName())){
  20. categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
  21. //TODO 更新其他关联表的数据
  22. }
  23. }
  24. /**
  25. * CategoryBrandRelationServiceImpl:更新关联表的品牌名称
  26. * @param brandId
  27. * @param name
  28. */
  29. @Override
  30. public void updateBrand(Long brandId, String name) {
  31. CategoryBrandRelationEntity entity = new CategoryBrandRelationEntity();
  32. entity.setBrandId(brandId);
  33. entity.setBrandName(name);
  34. LambdaQueryWrapper<CategoryBrandRelationEntity> wrapper = new LambdaQueryWrapper<>();
  35. wrapper.eq(CategoryBrandRelationEntity::getBrandId,brandId);
  36. this.update(entity,wrapper);
  37. }
  1. /**
  2. * CategoryController:修改分类
  3. */
  4. @RequestMapping("/update")
  5. public R update(@RequestBody CategoryEntity category){
  6. categoryService.updateCascade(category);
  7. return R.ok();
  8. }
  9. /**
  10. * CategoryServiceImpl:修改分类
  11. */
  12. @Transactional
  13. @Override
  14. public void updateCascade(CategoryEntity category) {
  15. this.updateById(category);
  16. // 更新关联表
  17. categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
  18. }
  19. /**
  20. * CategoryBrandRelationServiceImpl:修改分类
  21. */
  22. @Transactional
  23. @Override
  24. public void updateCategory(Long catId, String name) {
  25. CategoryBrandRelationEntity entity = new CategoryBrandRelationEntity();
  26. entity.setCatelogId(catId);
  27. entity.setCatelogName(name);
  28. LambdaQueryWrapper<CategoryBrandRelationEntity> wrapper = new LambdaQueryWrapper<>();
  29. wrapper.eq(CategoryBrandRelationEntity::getCatelogId,catId);
  30. this.baseMapper.update(entity,wrapper);
  31. // this.baseMapper.updateCategory(catId,name);
  32. }