zoukankan      html  css  js  c++  java
  • (10)商品管理

     

    新增商品

    1. 初始化数据
      1. 根据cid查询品牌
      2. 根据cid查询规格参数
    2. 新增spu
    3. 新增spuDetail
    4. sku
    5. stock

    1.初始化数据

    1.1.根据cid查询品牌

    1.1.1.前端页面

    品牌也是一个下拉选框,不过其选项是不确定的,只有当用户选择了商品分类,才会把这个分类下的所有品牌展示出来。

    所以页面编写了watch函数,监控商品分类的变化,每当商品分类值有变化,就会发起请求,查询品牌列表:

     选择商品分类后,可以看到请求发起:

     接下来,我们只要编写后台接口,根据商品分类id,查询对应品牌即可。

    1.1.2.后台接口

    页面需要去后台查询品牌信息,我们自然需要提供:

    请求方式:GET

    请求路径:/brand/cid/{cid}

    请求参数:cid

    响应数据:品牌集合

    BrandController

    @GetMapping("cid/{cid}")
    public ResponseEntity<List<Brand>> queryBrandsByCid(@PathVariable("cid")Long cid){
        List<Brand> brands = this.brandService.queryBrandsByCid(cid);
        if (CollectionUtils.isEmpty(brands)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(brands);
    }

    BrandService

    public List<Brand> queryBrandsByCid(Long cid) {
    
        return this.brandMapper.selectBrandByCid(cid);
    }

    BrandMapper

    根据分类查询品牌有中间表,需要自己编写Sql:

    @Select("SELECT b.* from tb_brand b INNER JOIN tb_category_brand cb on b.id=cb.brand_id where cb.category_id=#{cid}")
    List<Brand> selectBrandByCid(Long cid);

    1.1.3.效果

    1.2.根据cid查询规格参数

    1.2.1.Vue的Watch商品分类变化

    规格参数的查询我们之前也已经编写过接口,因为商品规格参数也是与商品分类绑定,所以需要在商品分类变化后去查询,我们也是通过watch监控来实现:

    可以看到这里是根据商品分类id查询规格参数:SpecParam。我们之前写过一个根据gid(分组id)来查询规格参数的接口,我们接下来完成根据分类id查询规格参数。

    1.2.2.改造查询规格参数接口

    我们在原来的根据 gid(规格组id)查询规格参数的接口上,添加一个参数:cid,即商品分类id。

    等一下, 考虑到以后可能还会根据是否搜索、是否为通用属性等条件过滤,我们多添加几个过滤条件:

      /**
         * 根据gid查询规格参数
         * @param gid
         * @return
         */
        public List<SpecParam> queryParams(Long gid, Long cid, Boolean generic, Boolean searching) {
            SpecParam record = new SpecParam();
            record.setGroupId(gid);
            record.setCid(cid);
            record.setGeneric(generic);
            record.setSearching(searching);
            return this.specParamMapper.select(record);
        }

    如果param中有属性为null,则不会把属性作为查询条件,因此该方法具备通用性,即可根据gid查询,也可根据cid查询。

    测试:

     刷新页面测试:

    2.提交保存(新增spu、新增spuDetail、新增sku、新增stock)

    2.1.前端页面的数据处理

    在sku列表的下方,有一个提交按钮:

     并且绑定了点击事件:

     点击提交,查看控制台提交的数据格式:

    整体是一个json格式数据,包含Spu表所有数据:

    • brandId:品牌id

    • cid1、cid2、cid3:商品分类id

    • subTitle:副标题

    • title:标题

    • spuDetail:是一个json对象,代表商品详情表数据

      • afterService:售后服务

      • description:商品描述

      • packingList:包装列表

      • specialSpec:sku规格属性模板

      • genericSpec:通用规格参数

    • skus:spu下的所有sku数组,元素是每个sku对象:

      • title:标题

      • images:图片

      • price:价格

      • stock:库存

      • ownSpec:特有规格参数

      • indexes:特有规格参数的下标

    2.2.后台实现接口

    2.2.1.实体类

    SPU和SpuDetail实体类已经添加过,添加Sku和Stock对象:

     Sku

    @Table(name = "tb_sku")
    public class Sku {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private Long spuId;
        private String title;
        private String images;
        private Long price;
        private String ownSpec;// 商品特殊规格的键值对
        private String indexes;// 商品特殊规格的下标
        private Boolean enable;// 是否有效,逻辑删除用
        private Date createTime;// 创建时间
        private Date lastUpdateTime;// 最后修改时间
        @Transient
        private Integer stock;// 库存
    }

    注意:这里保存了一个库存字段,在数据库中是另外一张表保存的,方便查询。

    Stock

    @Table(name = "tb_stock")
    public class Stock {
        @Id
        private Long skuId;
        private Integer seckillStock;// 秒杀可用库存
        private Integer seckillTotal;// 已秒杀数量
        private Integer stock;// 正常库存
    }

    2.2.2.GoodsController

    结合浏览器页面控制台,可以发现:

    请求方式:POST

    请求路径:/goods

    请求参数:Spu的json格式的对象,spu中包含spuDetail和Sku集合。这里我们该怎么接收?我们之前定义了一个SpuBo对象,作为业务对象。这里也可以用它,不过需要再扩展spuDetail和skus字段:

    public class SpuBo extends Spu {
    
        String cname;// 商品分类名称
        String bname;// 品牌名称
        SpuDetail spuDetail;// 商品详情
        List<Sku> skus;// sku列表
    }

    返回类型:无

    在GoodsController中添加新增商品的代码:

    @PostMapping("goods")
    public ResponseEntity<Void> saveGoods(@RequestBody SpuBo spuBo){
        this.goodsService.saveGoods(spuBo);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

    注意:通过@RequestBody注解来接收Json请求

    2.2.3.GoodsService

    这里的逻辑比较复杂,我们除了要对SPU新增以外,还要对SpuDetail、Sku、Stock进行保存

    /**
         * 新增商品
         * @param spuBo
         */
    @Transactional
    public void saveGoods(SpuBo spuBo) {
        // 新增spu
        // 设置默认字段
        spuBo.setId(null);
        spuBo.setSaleable(true);
        spuBo.setValid(true);
        spuBo.setCreateTime(new Date());
        spuBo.setLastUpdateTime(spuBo.getCreateTime());
        this.spuMapper.insertSelective(spuBo);
    
        // 新增spuDetail
        SpuDetail spuDetail = spuBo.getSpuDetail();
        spuDetail.setSpuId(spuBo.getId());
        this.spuDetailMapper.insertSelective(spuDetail);
    
        saveSkuAndStock(spuBo);
    }
    
    private void saveSkuAndStock(SpuBo spuBo) {
        spuBo.getSkus().forEach(sku -> {
            // 新增sku
            sku.setSpuId(spuBo.getId());
            sku.setCreateTime(new Date());
            sku.setLastUpdateTime(sku.getCreateTime());
            this.skuMapper.insertSelective(sku);
    
            // 新增库存
            Stock stock = new Stock();
            stock.setSkuId(sku.getId());
            stock.setStock(sku.getStock());
            this.stockMapper.insertSelective(stock);
        });
    }

    2.2.4.Mapper

    都是通用Mapper,略

    更新商品

    1. 回显
      1. 根据spuId查询spuDetail
      2. 根据spuId查询skus
    2. 先删除stock
    3. 然后删除sku
    4. 新增sku和stock
    5. 更新spu和spuDetail

    1.回显

    1.1.编辑按钮点击事件

    在商品详情页,每一个商品后面,都会有一个编辑按钮:

     点击这个按钮,就会打开一个商品编辑窗口,我们看下它所绑定的点击事件:(在item/Goods.vue)

     对应的方法:

    可以看到这里发起了两个请求,在查询商品详情和sku信息。

    因为在商品列表页面,只有spu的基本信息:id、标题、品牌、商品分类等。比较复杂的商品详情(spuDetail)和sku信息都没有,编辑页面要回显数据,就需要查询这些内容。

    因此,接下来我们就编写后台接口,提供查询服务接口。

    1.2.根据spuId查询spuDetail

    GoodsController

    需要分析的内容:

    • 请求方式:GET

    • 请求路径:/spu/detail/{id}

    • 请求参数:id,应该是spu的id

    • 返回结果:SpuDetail对象

    @GetMapping("spu/detail/{spuId}")
    public ResponseEntity<SpuDetail> querySpuDetailBySpuId(@PathVariable("spuId")Long spuId){
        SpuDetail spuDetail = this.goodsService.querySpuDetailBySpuId(spuId);
        if (spuDetail == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(spuDetail);
    }

    GoodsService

    /**
         * 根据spuId查询spuDetail
         * @param spuId
         * @return
         */
    public SpuDetail querySpuDetailBySpuId(Long spuId) {
    
        return this.spuDetailMapper.selectByPrimaryKey(spuId);
    }

    测试

    1.3.根据spuId查询skus

    分析

    • 请求方式:Get

    • 请求路径:/sku/list

    • 请求参数:id,应该是spu的id

    • 返回结果:sku的集合

    GoodsController

    @GetMapping("sku/list")
    public ResponseEntity<List<Sku>> querySkusBySpuId(@RequestParam("id")Long spuId){
        List<Sku> skus = this.goodsService.querySkusBySpuId(spuId);
        if (CollectionUtils.isEmpty(skus)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(skus);
    }

    GoodsService

    需要注意的是,为了页面回显方便,我们一并把sku的库存stock也查询出来

    /**
         * 根据spuId查询sku的集合
         * @param spuId
         * @return
         */
    public List<Sku> querySkusBySpuId(Long spuId) {
        Sku sku = new Sku();
        sku.setSpuId(spuId);
        List<Sku> skus = this.skuMapper.select(sku);
        skus.forEach(s -> {
            Stock stock = this.stockMapper.selectByPrimaryKey(s.getId());
            s.setStock(stock.getStock());
        });
        return skus;
    }

    测试:

    页面回显

    随便点击一个编辑按钮,发现数据回显完成:

     

     

     

    2.页面提交(先删除stock、然后删除sku、新增sku和stock、更新spu和spuDetail)

    2.1.前端页面发送请求

    这里的保存按钮与新增其实是同一个,因此提交的逻辑也是一样的,这里不再赘述。

    随便修改点数据,然后点击保存,可以看到浏览器已经发出请求:

    2.2.后台页面实现接口

    GoodsController

    • 请求方式:PUT

    • 请求路径:/

    • 请求参数:Spu对象

    • 返回结果:无

    @PutMapping("goods")
    public ResponseEntity<Void> updateGoods(@RequestBody SpuBo spuBo){
        this.goodsService.updateGoods(spuBo);
        return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
    }

    GoodsService

    spu数据可以修改,但是SKU数据无法修改,因为有可能之前存在的SKU现在已经不存在了,或者以前的sku属性都不存在了。比如以前内存有4G,现在没了。

    因此这里直接删除以前的SKU,然后新增即可。

    代码:

    @Transactional
    public void update(SpuBo spu) {
        // 查询以前sku
        List<Sku> skus = this.querySkuBySpuId(spu.getId());
        // 如果以前存在,则删除
        if(!CollectionUtils.isEmpty(skus)) {
            List<Long> ids = skus.stream().map(s -> s.getId()).collect(Collectors.toList());
            // 删除以前库存
            Example example = new Example(Stock.class);
            example.createCriteria().andIn("skuId", ids);
            this.stockMapper.deleteByExample(example);
    
            // 删除以前的sku
            Sku record = new Sku();
            record.setSpuId(spu.getId());
            this.skuMapper.delete(record);
    
        }
        // 新增sku和库存
        saveSkuAndStock(spu);
    
        // 更新spu
        spu.setLastUpdateTime(new Date());
        spu.setCreateTime(null);
        spu.setValid(null);
        spu.setSaleable(null);
        this.spuMapper.updateByPrimaryKeySelective(spu);
    
        // 更新spu详情
        this.spuDetailMapper.updateByPrimaryKeySelective(spu.getSpuDetail());
    }

    mapper

      与以前一样。

    其它

      商品的删除、上下架大家自行实现。

    搭建前台系统

    1. live-server  --port=9002
    2. 配置host文件
    3. 配置nginx
    4. commonjs:getUrlParam、formatprice、formatDate、stringify、parse

    live-server  --port=9002

    live-server --port=9002

    配置host文件

    127.0.0.1 www.leyou.com

    配置nginx

    server {
        listen       80;
        server_name  www.leyou.com;
    
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    
        location / {
            proxy_pass http://127.0.0.1:9002;
            proxy_connect_timeout 600;
            proxy_read_timeout 600;
        }
    }

    commonjs:getUrlParam、formatprice、formatDate、stringify、parse

    为了方便后续的开发,我们在前台系统中定义了一些工具,放在了common.js中:

     部分代码截图:

    首先对axios进行了一些全局配置,请求超时时间,请求的基础路径,是否允许跨域操作cookie等

    定义了对象 ly ,也叫leyou,包含了下面的属性:

    • getUrlParam(key):获取url路径中的参数

    • http:axios对象的别名。以后发起ajax请求,可以用ly.http.get()

    • store:localstorage便捷操作,后面用到再详细说明

    • formatPrice:格式化价格,如果传入的是字符串,则扩大100被并转为数字,如果传入是数字,则缩小100倍并转为字符串

    • formatDate(val, pattern):对日期对象val按照指定的pattern模板进行格式化

    • stringify:将对象转为参数字符串

    • parse:将参数字符串变为js对象

    学习中,博客都是自己学习用的笔记,持续更新改正。。。
  • 相关阅读:
    openlayers + webpack
    openlayers Map 和 es6的容器Map重名
    git 代理
    剑魂史诗套配装
    剑魂卢克攻略
    DNF斩铁剑魂每日1-5及打团须知
    APP自识别安卓苹果
    各浏览器老板键
    Apache+mod_encoding解决URL中文编码问题
    linux命令之crontab定时执行任务
  • 原文地址:https://www.cnblogs.com/Tunan-Ki/p/11944494.html
Copyright © 2011-2022 走看看