乐优商城(05)--商品管理

时间:2024-11-15 13:33:38

乐优商城(05)–商品管理

一、导入图片资源

现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,因此需要把图片导入到服务器中:

images.zip文件上传至/leyou/static目录:在leyou下创建static目录

使用命令解压:

unzip 
  • 1

修改Nginx配置,使nginx反向代理这些图片地址:

vim /opt/nginx/config/
  • 1

修改成如下配置:

server {
    listen       80;
    server_name  ;

    # 监听域名中带有group的,交给FastDFS模块处理
    location ~/group([0-9])/ {
        ngx_fastdfs_module;
    }
    # 将其它图片代理指向本地的/leyou/static目录
    location / {
        root   /leyou/static/;
    }

    error_page   500 502 503 504  /;
    location = / {
        root   html;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

不要忘记重新加载nginx配置

nginx -s reload
  • 1

二、商品查询

2.1、前端页面

先看整体页面结构(Goods.vue):

在这里插入图片描述

并且在Vue实例挂载后就会发起查询(mounted调用getDataFromServer方法初始化数据):

在这里插入图片描述

data中数据的修改:

在这里插入图片描述

2.2、后端实现

2.2.1、实体类

Spu

@Table(name = "tb_spu")
public class Spu {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;                //主键id
    private String title;           //标题
    private String subTitle;        //小标题
    private Long cid1;              //1级类目
    private Long cid2;              //2级类目
    private Long cid3;              //3级类目
    private Long brandId;           //品牌id
    private Boolean saleable;       //是否上架
    private Boolean valid;          //是否有效  逻辑删除
    private Date createTime;        //创建时间
    private Date lastUpdateTime;    //最后修改时间
    //get和set
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

SpuDetail

@Table(name="tb_spu_detail")
public class SpuDetail {
    @Id
    private Long spuId;// 对应的SPU的id
    private String description;// 商品描述
    private String specialSpec;// 商品特殊规格的名称及可选值模板
    private String genericSpec;// 商品的全局规格属性
    private String packingList;// 包装清单
    private String afterService;// 售后服务
    // get和set
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
2.2.2、mapper
public interface SpuMapper extends Mapper<Spu> {
}
  • 1
  • 2
2.2.3、controller
  • 请求方式:GET

  • 请求路径:/spu/page

  • 请求参数:

    • page:当前页
    • rows:每页大小
    • key:过滤条件
    • saleable:上架或下架
    • sortBy:排序字段
  • 返回结果:商品SPU的分页信息。

    • 要注意,页面展示的是商品分类和品牌名称,而数据库中保存的是id,新建一个类,继承SPU,并且拓展cname和bname属性,写到leyou-item-interface
    public class SpuBo extends Spu {
    
        private String cname; // 商品分类名称
        private String bname; // 品牌名称
        private SpuDetail spuDetail;  //商品详情
        private List<Sku> skus;       //sku列表
        //get和set
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里提前贴上Sku的代码

    @Table(name = "tb_sku")
    public class Sku {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;                //sku主键id
        private Long spuId;             //spu的id
        private String title;           //商品标题
        private String images;          //商品图片,会有多张,以,分隔
        private Long price;             //销售价格,以分为单位
        private String indexes;         //特有属性在spu中对应的下标
        private String ownSpec;         //商品特殊规格的键值对
        private Boolean enable;         //是否有效,逻辑删除用
        private Date createTime;        //创建时间
        private Date lastUpdateTime;    //最后修改时间
        @Transient
        private Integer stock;          //库存
         //get和set
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

编写controller代码:

这里把与商品相关的一切业务接口都放到一起,起名为GoodsController,业务层也是这样

@RestController
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    /**
     * 根据查询条件分页并排序查询商品信息
     * @param key
     * @param saleable
     * @param page
     * @param rows
     * @return
     */
    @GetMapping("/spu/page")
    public ResponseEntity<PageResult<SpuBo>> querySpuBoByPage(
            @RequestParam(value = "key",required = false) String key,
            @RequestParam(value = "saleable",required = false) Boolean saleable,
            @RequestParam(value = "page",defaultValue = "1") Integer page,
            @RequestParam(value = "rows",defaultValue = "5") Integer rows,
            @RequestParam(value = "sortBy",required = false) String sortBy,
            @RequestParam(value = "desc",required = false) Boolean desc
    ){
        PageResult<SpuBo> pageResult = this.goodsService.querySpuBoByPage(key,saleable,page,rows,sortBy,desc);
        if (CollectionUtils.isEmpty(pageResult.getItems())){
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(pageResult);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
2.2.4、service
public interface GoodsService {

    /**
     * 根据查询条件分页并排序查询商品信息
     * @param key
     * @param saleable
     * @param page
     * @param rows
     * @return
     */
    PageResult<SpuBo> querySpuBoByPage(String key, Boolean saleable, Integer page, Integer rows,String sortBy,Boolean desc);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

实现类:

@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private SpuMapper spuMapper;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandMapper brandMapper;

    /**
     * 根据查询条件分页并排序查询商品信息
     *
     * @param key
     * @param saleable
     * @param page
     * @param rows
     * @return
     */
    @Override
    public PageResult<SpuBo> querySpuBoByPage(String key, Boolean saleable, Integer page, Integer rows,String sortBy,Boolean desc) {
        //添加搜素条件
        Example example = new Example(Spu.class);
        Example.Criteria criteria = example.createCriteria();
        //过滤条件
        if (StringUtils.isNotBlank(key)){
            criteria.andLike("title","%"+key+"%");
        }
        //上架下架状态查询
        if (saleable != null){
            criteria.andEqualTo("saleable",saleable);
        }
        //分页条件
        PageHelper.startPage(page,rows);
        // 添加排序条件
        if (StringUtils.isNotBlank(sortBy)){
            example.setOrderByClause(sortBy + " " + (desc ? "desc" : "asc"));
        }
        //查询数据
        List<Spu> spus = this.spuMapper.selectByExample(example);
        PageInfo<Spu> spuPageInfo = new PageInfo<>(spus);
        //将Spu->SpuBo
        ArrayList<SpuBo> spuBos = new ArrayList<>();
        spus.forEach(spu -> {
            SpuBo spuBo = new SpuBo();
            //将spu的属性copy到spuBo
            BeanUtils.copyProperties(spu,spuBo);
            //查询分类名称
            List<String> names =  this.categoryService.queryNamesByIds(Arrays.asList(spu.getCid1(),spu.getCid2(),spu.getCid3()));
            //spuBo设置分类名称
            spuBo.setCname(StringUtils.join(names,"/"));
            //spuBo设置品牌名称
            spuBo.setBname(this.brandMapper.selectByPrimaryKey(spu.getBrandId()).getName());
            spuBos.add(spuBo);
        });
        //返回分页结果信息
        return new PageResult<>(spuPageInfo.getTotal(),spuBos);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
2.2.5、Category中拓展查询名称的功能

页面需要商品的分类名称需要在这里查询,因此要额外提供查询分类名称的功能,

在CategoryService中添加功能:

/**
 * 通过分类id查询分类名称
 *
 * @param ids
 * @return
 */
@Override
public List<String> queryNamesByIds(List<Long> ids) {
    List<Category> categories = this.categoryMapper.selectByIdList(ids);
    return categories.stream().map(Category::getName).collect(Collectors.toList());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

mapper的selectByIdList方法是来自于通用mapper。不过需要我们在mapper上继承一个通用mapper接口:

public interface CategoryMapper extends Mapper<Category>, SelectByIdListMapper<Category, Long> { 

}
  • 1
  • 2
  • 3

三、商品新增

当点击新增商品按钮,会出现一个弹窗:

在这里插入图片描述

里面把商品的数据分为了4部分来填写:

  • 基本信息:主要是一些简单的文本数据,包含了SPU和SpuDetail的部分数据,如
    • 商品分类:是SPU中的cid1cid2cid3属性
    • 品牌:是spu中的brandId属性
    • 标题:是spu中的title属性
    • 子标题:是spu中的subTitle属性
    • 售后服务:是SpuDetail中的afterService属性
    • 包装列表:是SpuDetail中的packingList属性
  • 商品描述:是SpuDetail中的description属性,数据较多,所以单独放一个页面
  • 规格参数:商品规格信息,对应SpuDetail中的genericSpec属性
  • SKU属性:spu下的所有Sku信息

对应到页面中的四个v-stepper-content

该弹窗是一个独立组件为,在页面中import导入使用并渲染,当按钮被点击时,会改变data中的show属性,使弹窗可见。

3.1、第一部分:基本信息

3.1.1、前端页面

点击商品分类,可以发现有分类信息显示,这里商品分类信息的接口前面已经实现了

在这里插入图片描述

点击所属品牌,查看控制台,请求已经发出:

在这里插入图片描述

前端页面是通过watch函数监控商品分类的变化实现的,每当商品分类值有变化,就会发起请求,查询品牌列表:

在这里插入图片描述

3.1.2、后端实现

controller

在BrandController中添加:

请求方式:GET

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

请求参数:cid

响应数据:品牌集合

/**
 * 根据分类id查询品牌集合
 * @param cid
 * @return
 */
@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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

service

/**
 * 根据分类id查询品牌集合
 * @param cid
 * @return
 */
List<Brand> queryBrandsByCid(Long cid);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

实现类:

/**
 * 根据分类id查询品牌集合
 *
 * @param cid
 * @return
 */
@Override
public List<Brand> queryBrandsByCid(Long cid) {
    return this.brandMapper.queryBrandsByCid(cid);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

mapper

/**
 * 根据分类id查询品牌集合
 * @param cid
 * @return
 */
@Select("SELECT b.* from tb_brand b INNER JOIN tb_category_brand cb on =cb.brand_id where cb.category_id=#{cid}")
List<Brand> queryBrandsByCid(@Param("cid") Long cid);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
3.1.3、其它文本框

剩余的几个属性:标题、子标题等都是普通文本框,直接填写即可。

在这里插入图片描述

3.2、第二部分:商品描述

商品描述信息比较复杂,而且图文并茂,甚至包括视频。这样的内容,一般都会使用富文本编辑器。

这里使用的是:Vue-Quill-Editor

GitHub的主页:/surmon-china/vue-quill-editor

Vue-Quill-Editor是一个基于Quill的富文本编辑器:Quill的官网

3.2.1、使用指南

使用非常简单:已经在项目中集成。以下步骤不需操作,仅供参考

第一步:安装,使用npm命令:

npm install vue-quill-editor --save
  • 1

第二步:加载,在js中引入:

全局引入:

import Vue from 'vue'
import VueQuillEditor from 'vue-quill-editor'

const options = {}; /* { default global options } */

Vue.use(VueQuillEditor, options); // options可选
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

局部引入:

import 'quill/dist/'
import 'quill/dist/'
import 'quill/dist/'

import {quillEditor} from 'vue-quill-editor'

var vm = new Vue({
    components:{
        quillEditor
    }
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

本项目这里采用局部引用:

在这里插入图片描述

第三步:页面使用:

<quill-editor v-model="" :options="editorOption"/>
  • 1
3.2.2、自定义的富文本编辑器

过这个组件有个小问题,就是图片上传的无法直接上传到后台,因此这里对其进行了封装,支持了图片的上传。

在目录src/components/form/

使用也非常简单:

<v-stepper-content step="2">
    <v-editor v-model="" upload-url="/upload/image"/>
</v-stepper-content>
  • 1
  • 2
  • 3
  • upload-url:是图片上传的路径
  • v-model:双向绑定,将富文本编辑器的内容绑定到

3.3、第三部分:规格参数

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

在这里插入图片描述

3.3.1、改造规格参数查询接口

在SpecificationController中修改queryParamsByGid

/**
 * 根据 多参数查询具体 SpecParam
 * @param gid
 * @param cid
 * @param generic
 * @param searching
 * @return
 */
@GetMapping("/params")
public ResponseEntity<List<SpecParam>> queryParams(
        @RequestParam(value = "gid",required = false)Long gid,
        @RequestParam(value = "cid",required = false)Long cid,
        @RequestParam(value = "generic",required = false)Boolean generic,
        @RequestParam(value = "searching",required = false)Boolean searching
){
    List<SpecParam> params = this.specificationService.queryParams(gid,cid,generic,searching);
    if (CollectionUtils.isEmpty(params)){
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(params);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

service

/**
 * 根据 多参数查询具体 SpecParam
 * @param gid
 * @param cid
 * @param generic
 * @param searching
 * @return
 */
List<SpecParam> queryParams(Long gid,Long cid,Boolean generic,Boolean searching);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

实现类:

/**
 * 根据 多参数查询具体 SpecParam
 * @param gid
 * @param cid
 * @param generic
 * @param searching
 * @return
 */
@Override
public List<SpecParam> queryParams(Long gid,Long cid,Boolean generic,Boolean searching) {
    //param中有属性为null,则不会把属性作为查询条件,因此该方法具备通用性
    SpecParam specParam = new SpecParam();
    specParam.setGroupId(gid);
    specParam.setCid(cid);
    specParam.setGeneric(generic);
    specParam.setSearching(searching);
    return this.specParamMapper.select(specParam);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

注意,由于之前deleteSpecGroup方法中依赖了queryParamsByGid,所以同样要修改:

List<SpecParam> specParams = queryParams(gid,null,null,null);
  • 1

3.4、第四部分:SKU信息

3.4.1、前端页面

Sku属性是SPU下的每个商品的不同特征,当我们填写一些属性后,会在页面下方生成一个sku表格,比如:

  • 颜色共2种:迷夜黑,勃艮第红,绚丽蓝
  • 内存共2种:4GB,6GB
  • 机身存储1种:64GB,128GB

此时会产生多少种SKU呢? 应该是 3 * 2 * 2 = 12种,这其实就是在求笛卡尔积。

在这里插入图片描述

在sku列表的下方,有一个提交按钮,绑定了一个点击事件submit,点击后会将数据组织并发送给后台

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

在这里插入图片描述

整体是一个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:特有规格参数的下标

3.4.2、后端实现

实体类

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

@Table(name = "tb_stock")
public class Stock {
    @Id
    private Long skuId;
    private Integer seckillStock;// 秒杀可用库存
    private Integer seckillTotal;// 已秒杀数量
    private Integer stock;// 正常库存
    
    //get和set
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

GoodsController

  • 请求方式:POST

  • 请求路径:/goods

  • 请求参数:Spu的json格式的对象,spu中包含spuDetail和Sku集合。

    • 这里该怎么接收?之前定义了一个SpuBo对象,作为业务对象。这里也可以用它,不过需要再扩展spuDetail和skus字段,之前贴的SpuBo代码是完整的,这里省略
  • 返回类型:无

/**
 * 添加新商品
 * @param spuBo
 * @return
 */
@PostMapping("/goods")
public ResponseEntity<Void> addGoods(@RequestBody SpuBo spuBo){
    this.goodsService.addGoods(spuBo);
    return ResponseEntity.status(HttpStatus.CREATED).build();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

service

/**
 * 添加新商品
 * @param spuBo
 * @return
 */
void addGoods(SpuBo spuBo);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

/**
 * 添加新商品
 * @param spuBo
 * @return
 */
@Override
@Transactional
public void addGoods(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);

}

/**
 * 添加 sku 和 stock
 * @param spuBo
 */
private void saveSkuAndStock(SpuBo spuBo){
    spuBo.getSkus().forEach(sku -> {
        //增sku
        sku.setId(null);
        sku.setSpuId(spuBo.getId());
        sku.setCreateTime(new Date());
        sku.setLastUpdateTime(sku.getCreateTime());
        this.skuMapper.insertSelective(sku);
        //最后增stock
        Stock stock = new Stock();
        stock.setSkuId(sku.getId());
        stock.setStock(sku.getStock());
        this.stockMapper.insertSelective(stock);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

mapper

/**
 * SpuDetail 的通用mapper
 */
public interface SpuDetailMapper extends Mapper<SpuDetail> {
}
  • 1
  • 2
  • 3
  • 4
  • 5
/**
 * Sku 通用mapper
 */
public interface SkuMapper extends Mapper<Sku> {
}
  • 1
  • 2
  • 3
  • 4
  • 5
/**
 * Stock 的通用mapper
 */
public interface StockMapper extends Mapper<Stock> {
}
  • 1
  • 2
  • 3
  • 4
  • 5

四、商品修改

4.1、前端页面

点击修改按钮,会先发送一个请求获取数据

在这里插入图片描述

页面代码:

在这里插入图片描述

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

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

4.2、后端实现

4.2.1、查询SpuDetail接口

controller

  • 请求方式:GET
  • 请求路径:/item/spu/detail/{id}
  • 请求参数:id,应该是spu的id
  • 返回结果:SpuDetail对象
/**
 * 通过 spuId 查询 spuDetail
 * @param spuId
 * @return
 */
@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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

service

/**
 * 通过 spuId 查询 spuDetail
 * @param spuId
 * @return
 */
SpuDetail querySpuDetailBySpuId(Long spuId);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

实现类:

/**
 * 通过 spuId 查询 spuDetail
 *
 * @param spuId
 * @return
 */
@Override
public SpuDetail querySpuDetailBySpuId(Long spuId) {
    return this.spuDetailMapper.selectByPrimaryKey(spuId);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
4.2.2、查询sku

controller

  • 请求方式:Get
  • 请求路径:/sku/list
  • 请求参数:id,应该是spu的id
  • 返回结果:sku的集合
/**
 * 通过 spuId 查询 Sku 集合
 * @param spuId
 * @return
 */
@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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

service

/**
 * 通过 spuId 查询 Sku 集合
 * @param spuId
 * @return
 */
List<Sku> querySkusBySpuId(Long spuId);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

实现类:

/**
 * 通过 spuId 查询 Sku 集合
 *
 * @param spuId
 * @return
 */
@Override
public List<Sku> querySkusBySpuId(Long spuId) {
    Sku sku = new Sku();
    sku.setSpuId(spuId);
    List<Sku> skus = this.skuMapper.select(sku);
    skus.forEach(s -> {
        //设置每个 sku 的库存量
        Stock stock = this.stockMapper.selectByPrimaryKey(s.getId());
        s.setStock(stock.getStock());
    });
    return skus;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

到这里,点击编辑按钮,可以发现数据回显了,修改一点数据,进行保存提交时,发现又发送一个请求

在这里插入图片描述

4.2.3、更新商品

controller

  • 请求方式:PUT
  • 请求路径:/goods
  • 请求参数:Spu对象
  • 返回结果:无
/**
 * 更新商品信息
 * @param spuBo
 */
@PutMapping("/goods")
public ResponseEntity<Void> updateGoods(@RequestBody SpuBo spuBo){
    this.goodsService.updateGoods(spuBo);
    return ResponseEntity.noContent().build();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

service

/**
 * 更新商品信息
 * @param spuBo
 */
void updateGoods(SpuBo spuBo);
  • 1
  • 2
  • 3
  • 4
  • 5

实现类:

/**
 * 更新商品信息
 *
 * @param spuBo
 */
@Override
@Transactional
public void updateGoods(SpuBo spuBo) {
    //删除再添加,完成更新

    //先查询以前的数据
    List<Sku> skus = this.querySkusBySpuId(spuBo.getId());
    if (!CollectionUtils.isEmpty(skus)){
        List<Long> ids = skus.stream().map(Sku::getId).collect(Collectors.toList());
        //先删除stock
        Example example = new Example(Stock.class);
        example.createCriteria().andIn("skuId",ids);
        this.stockMapper.deleteByExample(example);
        //再删除sku
        Sku sku = new Sku();
        sku.setSpuId(spuBo.getId());
        this.skuMapper.delete(sku);
    }
    // 新增sku和stock
    saveSkuAndStock(spuBo);
    //更新spu
    //数据库某些字段只能有服务器更改
    //也防止用户恶意修改
    spuBo.setLastUpdateTime(new Date());
    spuBo.setCreateTime(null);
    spuBo.setValid(null);
    spuBo.setSaleable(null);
    this.spuMapper.updateByPrimaryKeySelective(spuBo);
    //更新spuDetail
    this.spuDetailMapper.updateByPrimaryKeySelective(spuBo.getSpuDetail());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

五、商品删除及上下架

5.1、前端页面

点击删除按钮确定后,可以发现已经发生请求

在这里插入图片描述

页面代码:

在这里插入图片描述

5.2、后端实现

5.2.1、商品删除

controller

  • 请求方式:DELETE
  • 请求路径:/goods
  • 请求参数:SpuId
  • 返回结果:无
/**
 * 通过spuId删除商品
 * @param spuId
 * @return
 */
@DeleteMapping("/goods")
public ResponseEntity<Void> deleteGoodsBySpuId(@RequestParam("id") Long spuId){
    this.goodsService.deleteGoodsBySpuId(spuId);
    return ResponseEntity.ok().build();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

service

/**
 * 通过spuId删除商品
 * @param spuId
 * @return
 */
void deleteGoodsBySpuId(Long spuId);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

实现类:

/**
 * 通过spuId删除商品
 *
 * @param spuId
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteGoodsBySpuId(Long spuId) {
    Sku sku = new Sku();
    sku.setSpuId(spuId);
    //删除sku和stock
    deleteSkuAndStock(sku);
    //删除spuDetail
    this.spuDetailMapper.deleteByPrimaryKey(spuId);
    //删除spu
    this.spuMapper.deleteByPrimaryKey(spuId);
}

/**
 * 删除sku和stock的方法
 * @param sku
 */
private void deleteSkuAndStock(Sku sku){
    List<Sku> skuList = this.skuMapper.select(sku);
    if (!CollectionUtils.isEmpty(skuList)){
        //删除sku
        this.skuMapper.delete(sku);
        //删除stock
        List<Long> ids = skuList.stream().map(Sku::getId).collect(Collectors.toList());
        this.stockMapper.deleteByIdList(ids);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

mapper

/**
 * Stock 的通用mapper
 */
public interface StockMapper extends Mapper<Stock>, IdListMapper<Stock,Long> {
}
  • 1
  • 2
  • 3
  • 4
  • 5
5.2.2、上下架

controller

  • 请求方式:PUT
  • 请求路径:/goods/spu/out/{id}
  • 请求参数:SpuId
  • 返回结果:无
/**
 * 通过 spuId 修改商品的上下架状态
 * @param spuId
 * @return
 */
@PutMapping("/goods/spu/out/{id}")
public ResponseEntity<Void> soldOutPut(@PathVariable("id") Long spuId){
    this.goodsService.updateSaleableBySpuId(spuId);
    return ResponseEntity.ok().build();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

service

/**
 * 通过 spuId 修改商品的上下架状态
 * @param spuId
 * @return
 */
void updateSaleableBySpuId(Long spuId);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

实现类:

/**
 * 通过 spuId 修改商品的上下架状态
 *
 * @param spuId
 * @return
 */
@Override
@Transactional
public void updateSaleableBySpuId(Long spuId) {
    Spu spu = this.spuMapper.selectByPrimaryKey(spuId);
    Spu spu1 = new Spu();
    spu1.setId(spu.getId());
    spu1.setSaleable(!spu.getSaleable());
    this.spuMapper.updateByPrimaryKeySelective(spu1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15