个人笔记谷粒商城项目第三节(61集~80集)

目录

第61集,使用阿里云对象存储,

第62集,使用oss上传图片测试,

​编辑第63集

第64集,前端修改 后端

第65集,纯前端修改好即可

第66集  JSR303注解

 第67集 集中处理错有异常的类

第68集 分组校验

第69集 自定义校验注解 

 第70集 讲解了有关的电商的一些知识,表单的设计

第71集  前端省略

第72章 使用分页查询并且成功实现

 第73集 分组新增+前端级联选择器

第74集:前端回显

第75集 修改品牌管理的查询,分页功能

第76集 规格参数新增与VO

第77集 规格参数列表

第78集 规格参数列表 规格修改

第79集 销售属性维护

第80集 查询分组关联属性


第61集,使用阿里云对象存储,

从目前开始,必须要购买oss软件包了,所以要至少收5元钱才可以使用服务。

第62集,使用oss上传图片测试,

就按照老师的讲解和手册即可,很简单。之后再引入Ossclient进行相关操作。

第63集

建立gulimall-third-party专门处理OSS存储对象。给浏览器发送请求,返回签名

package com.buu.gulimall.thirdparty.controller;

import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.buu.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 javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

@RestController
public class OssController {

    @Autowired
    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() {

        //https://gulimall-hello.oss-cn-beijing.aliyuncs.com/hahaha.jpg

        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);
            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());
        }

        return R.ok().put("data",respMap);
    }
}

之后再将网关的88映射为third-party就行。

第64集,前端修改 后端

第65集,纯前端修改好即可

第66集  JSR303注解

在BrandEntity类的name字段添加注解@NotBlank代表不能为空串,且必须要在control层标注校验注解@valid,否则没有效果。
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        if(result.hasErrors()){
            Map<String,String>map=new HashMap<>();
            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();
    }

 第67集 集中处理错有异常的类

@ControllerAdvice这个注解可以参考这篇博客 @ControllerAdvice 用法-CSDN博客

使用@ControllerAdvice @ExceptionHandler 进行处理

@Slf4j
@RestControllerAdvice(basePackages = "com.buu.gulimall.product.controller")
public class GulimallExceptionControlAdvice {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleVaildException(MethodArgumentNotValidException e){
        log.error("数据异常出现问题{},异常类型{}",e.getMessage(),e.getClass());
        BindingResult result = e.getBindingResult();
        Map<String,String> map=new HashMap<>();
            result.getFieldErrors().forEach((item)->{
                String message = item.getDefaultMessage();
                String field = item.getField();
                map.put(field,message);
            });
            return R.error(400,"提交数据不合法").put("data",map);
    }
    
    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){

        log.error("错误:",throwable);
        return R.error(com.atguigu.common.exception.BizCodeEnume.UNKNOW_EXCEPTION.getCode(), com.atguigu.common.exception.BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
    }

}

给状态码设置下枚举

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;
    }
}

第68集 分组校验

目的,由于新增或者修改的情况下,我们需要校验的字段可能是不一样的。比如id,新增的时候是默认自增的,所以不需要这个字段。但是修改的时候就需要这个字段。是jsr303分组校验。分组校验可以完成多场景的复杂校验。

class BrandEntity
@NotNull(message = "修改指定id",groups = {UpdateGroup.class})
	@Null(message = "新增不用指定id",groups = {AddGroup.class})
	@TableId
	private Long brandId;
BrandController
    @RequestMapping("/save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
            brandService.save(brand);
        return R.ok();
    }
 @validated 如果指定了分组,那么Bean中只校验属于该分组注解标注的值是否合法,也就是entity类中也必须添加group分类才能有效

AddGroup
public interface AddGroup {
}

第69集 自定义校验注解 

我们需要设定特殊的校验规则。

1)、编写一个自定义的校验注解,@Documented,@Constraint,@Target,@Retention这些都为元注解,可以参考其他博客讲解

@Documented
@Constraint(validatedBy = { com.buu.common.valid.ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    String message() default "{com.atguigu.common.valid.ListValue.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    int[] vals() default { };
}

2)、编写一个自定义的校验器

3)、关联自定义的校验器和自定义的校验注解 @Constraint(validatedBy = { com.buu.common.valid.ListValueConstraintValidator.class })

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);
    }
}

4)、测试

	@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
	@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
	private Integer showStatus;

 第70集 讲解了有关的电商的一些知识,表单的设计

 第71集  前端省略

用老师的代码前端有转圈,原来url中list后面有个参数,删了就没事儿了,改为url: this.$http.adornUrl(`/product/attrgroup/list`),

第72章 使用分页查询并且成功实现

select * from pms_attr-group where catelog_id=?and(attr_group_id=key or attr_group_name like %key%)

编写AttrGroupServiceImpl

@Override
    public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
        String key = (String) params.get("key");
        //select * from pms_attr_group where catelog_id=? and (attr_group_id=key or attr_group_name like %key%)
        QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();
        if(!StringUtils.isEmpty(key)){
            wrapper.and((obj)->{
                return obj.eq("attr_group_id",key).or().like("attr_group_name",key);
            });
        }
        if( catelogId == 0){
            IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),
                    wrapper);
            return new PageUtils(page);
        }else {
            wrapper.eq("catelog_id",catelogId);
            IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),
                    wrapper);
            return new PageUtils(page);
        }
    }

 第73集 分组新增+前端级联选择器

设置当children不为空时才进行返回,

	@TableField(exist = false)
	@JsonInclude(JsonInclude.Include.NON_EMPTY)
	private List<CategoryEntity> children;

前端连带自动刷新功能,提交完成之后结果就可以显示出来

第74集:前端回显

回显的意思就是,在修改的时候,将数据全部显示出来。这里没有回显完全,因为我们请求后端的时候没有这个数据。

 在AttrGroupEntity类中加上这个数据,写一个service逻辑查出结果,合并为数据即可成功。

	@TableField(exist = false)
	private Long[]catelogPath;
@Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();List<Long> parentPath =findParentPath(catelogId,paths);
        Collections.reverse(parentPath);
        return parentPath.toArray(new Long[paths.size()]);
    }

    private List<Long> findParentPath(Long catelogId, List<Long> paths) {
        paths.add(catelogId);
        CategoryEntity byId = this.getById(catelogId);
        if(byId.getParentCid()!=0){
            findParentPath(byId.getParentCid(),paths);
        }
        return paths;
    }

第75集 修改品牌管理的查询,分页功能

要点1:表的设计逻辑+分页插件的使用

参考官方文档:老师视频讲的过时了。分页插件 | MyBatis-Plus

数据的冗余设计,我们在电商的数据库中,我们经常不适用多表联查,我们会在中间表中添加冗余字段,保证我们查询的效率。在商品与品牌的两张表中,关系是多对多的关系,一个商品对应多个品牌类,比如:华为手机同时属于手机和电器类。一个品牌对应多个商品,比如手机类对应多款手机比如小米,华为手机等等。对于多对多关系--->数据库设计就是三张表,一张品牌表,一张商品表,一张中间关系表(存储品牌和商品关系,这个里面可以添加冗余)。

这张中间表中我们添加品牌名字和商品名字冗余。但是如果商品表名字更改,我们这个中间表的冗余信息也要保持同步更改。

要点2:级联更新

CategoryServiceImpl

@Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }

CategoryBrandRelationDao

    <update id="updateCategory">
        UPDATE pms_category_brand_relation SET catelog_name=#{name} WHERE catelog_id=#{catId}
    </update>

意思就是pms_category_brand_relation的表的信息,因为他含有catelog_name字段,brand_name字段,就是在修改pms_category表的name的时候,就要同步的将pms_category_brand_relation的表也一起修改好,要不然导致信息不同步。相当于同一数据全部保持一致的意思。

第76集 规格参数新增与VO

首先修改了一个模糊查询没啥难度。

规格参数,就是基本不变的信息

引入vo概念,大致有下面这么多,概念需要了解,只用了解即可。

1.PO(persistant object) 持久对象
PO 就是对应数据库中某个表中的一条记录,多个记录可以用 PO 的集合。 PO 中应该不包
含任何对数据库的操作。
2.DO(Domain Object)领域对象
就是从现实世界中抽象出来的有形或无形的业务实体。
3.TO(Transfer Object) ,数据传输对象
不同的应用程序之间传输的对象
4.DTO(Data Transfer Object)数据传输对象
这个概念来源于 J2EE 的设计模式,原来的目的是为了 EJB 的分布式应用提供粗粒度的
数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这
里,泛指用于展示层与服务层之间的数据传输对象。
5.VO(value object) 值对象
通常用于业务层之间的数据传递,和 PO 一样也是仅仅包含数据而已。但应是抽象出
的业务对象 , 可以和表对应 , 也可以不 , 这根据业务的需要 。用 new 关键字创建,由
GC 回收的。
View object:视图对象;
接受页面传递来的数据,封装对象
将业务处理完成的对象,封装成页面要用的数据
6.BO(business object) 业务对象
从业务模型的角度看 , 见 UML 元件领域模型中的领域对象。封装业务逻辑的 java 对
象 , 通过调用 DAO 方法 , 结合 PO,VO 进行业务操作。business object: 业务对象 主要作
用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。 比如一个简
历,有教育经历、工作经历、社会关系等等。 我们可以把教育经历对应一个 PO ,工作经
历对应一个 PO ,社会关系对应一个 PO 。 建立一个对应简历的 BO 对象处理简历,每
个 BO 包含这些 PO 。 这样处理业务逻辑时,我们就可以针对 BO 去处理。
7.POJO(plain ordinary java object) 简单无规则 java 对象
传统意义的 java 对象。就是说在一些 Object/Relation Mapping 工具中,能够做到维护
数据库表记录的 persisent object 完全是一个符合 Java Bean 规范的纯 Java 对象,没有增
加别的属性和方法。我的理解就是最基本的 java Bean ,只有属性字段及 setter 和 getter
方法!。
POJO 是 DO/DTO/BO/VO 的统称。
8.DAO(data access object) 数据访问对象
是一个 sun 的一个标准 j2ee 设计模式, 这个模式中有个接口就是 DAO ,它负持久
层的操作。为业务层提供接口。此对象用于访问数据库。通常和 PO 结合使用, DAO 中包
含了各种数据库的操作方法。通过它的方法 , 结合 PO 对数据库进行相关的操作。夹在业
务逻辑与数据库资源中间。配合 VO, 提供数据库的 CRUD 操作.

要点1:使用了一个方法快速将AttrVo的所有匹配的值转为AttrEntity的值,相当于快速赋值。

@Override
    public void saveAttr(AttrVo attr) {
        AttrEntity attrEntity = new AttrEntity();
        BeanUtils.copyProperties(attr,attrEntity);
        this.save(attrEntity);
    }

要点2:使用@Transactional保证方法的要么同时成功要么同时失败。

因为这里要同时保存两张表内容,如果不添加事务,那么如果一个表插入成功,另外一个表插入失败,那么就导致数据不一致。

@Transactional注解详见大佬的这篇博客,这个很重要,但是初学慢慢学习即可。

Spring——事务注解@Transactional【建议收藏】-CSDN博客

@Transactional
    @Override
    public void saveAttr(AttrVo attr) {
        AttrEntity attrEntity = new AttrEntity();
        BeanUtils.copyProperties(attr,attrEntity);
        this.save(attrEntity);
        AttrAttrgroupRelationEntity relationEntity =new AttrAttrgroupRelationEntity();
        relationEntity.setAttrGroupId(attr.getAttrGroupId());
        relationEntity.setAttrId(attrEntity.getAttrId());
        relationDao.insert(relationEntity);
    }

第77集 规格参数列表

attrServiceImpl层,这段代码的大致意思就是获取一下AttrEntity的数据,填入到attrRespVo中去,然后attrRespVo有些冗余字段,需要查出,老师说不推荐使用多表联查,因为数据量大。所以就单独根据id查两张表,获取到数据,然后全部填充到attrRespVo中去。

@Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId,String type) {
        QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>().eq("attr_type","base".equalsIgnoreCase(type)?ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode(): ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());

        if(catelogId != 0){
            queryWrapper.eq("catelog_id",catelogId);
        }
        String key = (String) params.get("key");
        if(!StringUtils.isEmpty(key)){
            //attr_id  attr_name
            queryWrapper.and((wrapper)->{
                wrapper.eq("attr_id",key).or().like("attr_name",key);
                return wrapper;
            });
        }
        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),
                queryWrapper
        );
        PageUtils pageUtils = new PageUtils(page);
        List<AttrEntity> records = page.getRecords();
        List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
            AttrRespVo attrRespVo = new AttrRespVo();
            BeanUtils.copyProperties(attrEntity, attrRespVo);

            //1、设置分类和分组的名字
            if("base".equalsIgnoreCase(type)){
                AttrAttrgroupRelationEntity attrId = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
                if (attrId != null && attrId.getAttrGroupId()!=null) {
                    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrId.getAttrGroupId());
                    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
                }

            }
            CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
            if (categoryEntity != null) {
                attrRespVo.setCatelogName(categoryEntity.getName());
            }
            return attrRespVo;
        }).collect(Collectors.toList());

        pageUtils.setList(respVos);
        return pageUtils;

    }

第78集 规格参数列表 规格修改

这段代码和上面原理一样,也是查数据,然后分开查,之后放到Vo之中去。比较要注意的点就是需要不断的进行 判断字段是否为空等等这样的判断,这在工作中是十分必要的,要考虑信息完善。

@Override
    public AttrVo getAttrInfo(Long attrId) {
        AttrRespVo respVo = new AttrRespVo();
        AttrEntity attrEntity = this.getById(attrId);
        BeanUtils.copyProperties(attrEntity,respVo);
        if(attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()){
            //1、设置分组信息
            AttrAttrgroupRelationEntity attrgroupRelation = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
            if(attrgroupRelation!=null){
                respVo.setAttrGroupId(attrgroupRelation.getAttrGroupId());
                AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelation.getAttrGroupId());
                if(attrGroupEntity!=null){
                    respVo.setGroupName(attrGroupEntity.getAttrGroupName());
                }
            }
        }
        //2、设置分类信息
        Long catelogId = attrEntity.getCatelogId();
        Long[] catelogPath = categoryService.findCatelogPath(catelogId);
        respVo.setCatelogPath(catelogPath);

        CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
        if(categoryEntity!=null){
            respVo.setCatelogName(categoryEntity.getName());
        }
        return respVo;

    }

第79集 销售属性维护

重点一:使用枚举的方式,为啥要引入枚举呢?因为代码中有一些需要固定数字的地方,比如这里需要判断  if(attr.getAttrType()==1),这里我们知道1代表基本属性,这个代码的含义就是这个AttrType为基本属性的时候,执行语句。但是这里有点硬编码了,如果我们要设置这个基本属性不为1,之前有很多代码都默认1为基本属性,我们就要一个个找之前的代码,修改很麻烦,怎么办?我们使用枚举的好处就体现出来了,我们只用修改 枚举类一处地方就可以了。如下图

public class ProductConstant {


    public enum  AttrEnum{
        ATTR_TYPE_BASE(1,"基本属性"),ATTR_TYPE_SALE(0,"销售属性");
        private int code;
        private String msg;

        AttrEnum(int code,String msg){
            this.code = code;
            this.msg = msg;
        }

        public int getCode() {
            return code;
        }

        public String getMsg() {
            return msg;
        }
    }
}

使用的方式如下图:

if(attr.getAttrType()==ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()){
            xxxxxxx
        }

第80集 查询分组关联属性

就是基本的crud,过

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值