乐优商城(五)商品管理

发布时间:2023年12月26日

1. 商品新增

1.1 商品新增前端

  1. 进入商品列表,点击新增商品

  2. 出现一个弹窗

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

基本信息:主要是一些简单的文本数据,包含了 SPU 和 SpuDetail 的部分数据

  • 商品分类:是 Spu 中的 cid1,cid2,cid3 属性
  • 品牌:是 Spu 中的 brandId 属性
  • 标题:是 Spu 中的 title 属性
  • 子标题:是 Spu 中的 subTitle 属性
  • 售后服务:是 SpuDetail 中的 afterService 属性
  • 包装列表:是 SpuDetail 中的 packingList 属性

商品描述:是 SpuDetail 中的 description 属性,数据较多,所以单独放一个页面

规格参数:商品规格信息,对应 SpuDetail 中的 genericSpec 属性

SKU 属性:SPU 下的所有 SKU 信息

1.2 基本信息

1.2.1 商品分类

商品分类的前端

商品分类的查询之前做过了,所以这里点击就能选择商品分类

1.2.2 品牌选择

品牌选择前端

选择分类后,我们需要这个分类下的所有品牌,所以会发送一个根据分类 id 查询品牌的请求

我们找到前端请求品牌数据的代码

由此可以得知:

  • 请求方式:GET
  • 请求路径:brand/cid
  • 请求参数:分类 id,这里用的是 Rest 风格的占位符
  • 返回参数:品牌的集合

后台接口

在 BrandController 中添加 queryBrandsByCid 方法

/**
 * 根据分类 id 查询品牌信息
 * @param cid
 * @return
 */
@GetMapping("/cid/{cid}")
public ResponseEntity<List<Brand>> queryBrandsByCid(@PathVariable("cid") Long cid) {
    List<Brand> brands = brandService.queryBrandsByCid(cid);
    if(CollectionUtils.isEmpty(brands)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(brands);
}

在 BrandService 中添加 queryBrandsByCid 方法

/**
 * 根据分类 id 查询品牌信息
 * @param cid
 * @return
 */
public List<Brand> queryBrandsByCid(Long cid) {
    List<Brand> brands = brandMapper.queryBrandsByCid(cid);
    return brands;
}

在 BrandMapper 中添加 queryBrandsByCid 方法

/**
 * 根据分类 id 查询品牌信息
 * @param cid
 * @return
 */
@Select("SELECT * FROM tb_brand WHERE id IN (SELECT brand_id FROM tb_category_brand WHERE category_id = #{cid})")
List<Brand> queryBrandsByCid(Long cid);

测试

成功得到手机分类下的所有品牌

1.2.3 其他文本框

剩下的都是普通的文本框,直接填写即可

1.3 商品描述

商品描述经常需要图文并茂,甚至视频,所以这里使用了一个富文本编辑器 Vue-Quill-Editor,它支持文字、图片、视频等功能。

1.4 规格参数

规格参数前端

选择分类后,我们需要这个分类下的所有规格参数,所以会发送一个根据分类 id 查询规格参数的请求

我们找到前端请求规格参数数据的代码

由此可以得知:

  • 请求方式:GET
  • 请求路径:spec/params
  • 请求参数:分类 id
  • 返回参数:规格参数集合

后台接口

前面在 SpecificationController 中已经写过一个 querySpecParams 方法,路径为 spec/params,参数为规格组 id,如下

/**
 * 根据条件查询规格参数
 *
 * @param gid
 * @return
 */
@GetMapping("/params")
public ResponseEntity<List<SpecParam>> querySpecParams(@RequestParam("gid") Long gid) {
    List<SpecParam> params = specificationService.querySpecParams(gid);
    if (CollectionUtils.isEmpty(params)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(params);
}

所以我们就不用再写新的方法了,直接在 querySpecParams 方法中增加分类 id 参数。考虑到以后可能还会根据是否搜索、是否为通用属性等条件过滤,我们多添加几个过滤条件

/**
 * 根据条件查询规格参数
 *
 * @param gid
 * @return
 */
@GetMapping("/params")
public ResponseEntity<List<SpecParam>> querySpecParams(
    @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 = specificationService.querySpecParams(gid, cid, generic, searching);
    if (CollectionUtils.isEmpty(params)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(params);
}

修改 SpecificationService 中的 querySpecParams 方法

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

测试

成功得到规格参数,这部分规格参数是 sku 通用的属性,即 tb_spec_param 中 generic 为 1 的字段。

1.5 SKU 属性

成功得到 sku 属性,这部分规格参数是 sku 特有的属性,即 tb_spec_param 中 generic 为 0 的字段。

当我们填写一些 sku 属性后,会在页面下方生成一个 sku 表格,可以添加价格、库存、是否启用等

1.6 提交商品信息

1.6.1 提交表单前端

填写完所有商品信息后,只剩下最后一步了,那就是提交商品信息

点击提交商品信息后,可以看到发送给了一个提交商品信息的请求

再来看看请求参数

整体是一个 JSON 格式数据,包含了 tb_spu、tb_spu_detail、tb_sku、tb_stock 四张表的数据

  • brandId:品牌 id
  • cid1、cid2、cid3:商品分类 id
  • subTitle:副标题
  • title:标题
  • spuDetail:
    • afterService:售后服务
    • description:商品描述
    • packingList:包装列表
    • specialSpec:sku规格属性模板
    • genericSpec:通用规格参数
  • skus:
    • sku:
      • title:标题
      • images:图片
      • price:价格
      • stock:库存
      • ownSpec:特有规格参数
      • indexes:特有规格参数的下标

我们找到前端提交商品信息请求的代码

由此可以得知:

  • 请求方式:POST
  • 请求路径:spu
  • 请求参数:商品信息,JSON 格式
  • 返回参数:无
1.6.2 后台接口

实体类

在 leyou-item-interface 项目中添加实体类 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;// 库存
    
    // getter、setter、toString 方法省略
}

在 leyou-item-interface 项目中添加实体类 Stock

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

在实体类 SpuBo 中添加 spuDetail、skus 属性,用来封装前端提交过来的商品信息

public class SpuBo extends Spu{
    private String cname;
    private String bname;
    private SpuDetail spuDetail;
    private List<Sku> skus;
	
    // getter、setter、toString 方法省略
}

Mapper

在 leyou-item-service 项目中添加 SkuMapper

public interface SkuMapper extends Mapper<Sku> {
}

在 leyou-item-service 项目中添加 StockMapper

public interface StockMapper extends Mapper<Stock> {
}

Controller

在 SpuController 中添加 saveSpu 方法

/**
 * 新增商品
 * @param spuBo
 * @return
 */
@PostMapping
public ResponseEntity<Void> saveSpu(@RequestBody SpuBo spuBo) {
    spuService.saveSpu(spuBo);
    return ResponseEntity.status(HttpStatus.CREATED).build();
}

Service

在 SpuService 中添加 saveSpu 方法

/**
 * 新增商品
 * @param spuBo
 * @return
 */
@Transactional
public void saveSpu(SpuBo spuBo) {
    // 先新增 Spu
    spuBo.setId(null);
    spuBo.setSaleable(true);
    spuBo.setValid(true);
    spuBo.setCreateTime(new Date());
    spuBo.setLastUpdateTime(spuBo.getCreateTime());
    spuMapper.insertSelective(spuBo);

    // 再新增 SpuDetail
    SpuDetail spuDetail = spuBo.getSpuDetail();
    spuDetail.setSpuId(spuBo.getId());
    spuDetailMapper.insertSelective(spuDetail);

    // 再新增 Sku
    List<Sku> skus = spuBo.getSkus();
    for (Sku sku : skus) {
        sku.setId(null);
        sku.setSpuId(spuBo.getId());
        sku.setCreateTime(new Date());
        sku.setLastUpdateTime(sku.getCreateTime());
        skuMapper.insertSelective(sku);
        // 新增 Stock
        Stock stock = new Stock();
        stock.setSkuId(sku.getId());
        stock.setStock(sku.getStock());
        stockMapper.insertSelective(stock);
    }
}
1.6.3 测试

看到新增的商品了

2. 商品修改

商品修改需要以下步骤:

  1. 将商品信息回显到页面
  2. 修改商品信息
  3. 提交修改

2.1 商品回显

2.1.1 商品回显前端

点击修改按钮就会把商品信息回显到页面上

我们找到前端商品信息回显请求的代码

由此可知,发送了两个请求:

  • 查询 spuDetail
    • 请求方式:GET
    • 请求路径:spu/detail
    • 请求参数:spuId,这里用的是 Rest 风格的占位符
    • 返回参数:SpuDetail
  • 查询 Sku 集合
    • 请求方式:GET
    • 请求路径:spu/sku/list
    • 请求参数:spuId
    • 返回参数:Sku 集合
2.1.2 后台接口

Controller

在 SpuController 中添加 querySpuDetailBySpuId 方法

/**
 * 通过 spuId 查询 SpuDetail
 * @param spuId
 * @return
 */
@GetMapping("/detail/{spuId}")
public ResponseEntity<SpuDetail> querySpuDetailBySpuId(@PathVariable("spuId") Long spuId) {
    SpuDetail spuDetail = spuService.querySpuDetailBySpuId(spuId);
    if (spuDetail == null) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(spuDetail);
}

在 SpuController 中添加 querySkusBySpuId 方法

/**
 * 通过 spuId 查询 Sku 集合
 * @param spuId
 * @return
 */
@GetMapping("/sku/list")
public ResponseEntity<List<Sku>> querySkusBySpuId(@RequestParam("id") Long spuId) {
    List<Sku> skus = spuService.querySkusBySpuId(spuId);
    if(CollectionUtils.isEmpty(skus)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(skus);
}

Service

在 SpuService 中添加 querySpuDetailBySpuId 方法

/**
 * 通过 spuId 查询 SpuDetail
 * @param spuId
 * @return
 */
public SpuDetail querySpuDetailBySpuId(Long spuId) {
    SpuDetail spuDetail = spuDetailMapper.selectByPrimaryKey(spuId);
    return spuDetail;
}

在 SpuService 中添加 querySkusBySpuId 方法

/**
 * 通过 spuId 查询 Sku 集合
 * @param spuId
 * @return
 */
public List<Sku> querySkusBySpuId(Long spuId) {
    Sku sku = new Sku();
    sku.setSpuId(spuId);
    List<Sku> skus = skuMapper.select(sku);
    for (Sku sku1 : skus) {
        Stock stock = stockMapper.selectByPrimaryKey(sku1.getId());
        sku1.setStock(stock.getStock());
    }
    return skus;
}
2.1.3 测试

商品信息回显成功

2.2 提交修改

2.2.1 提交表单前端

我们找到前端提交商品信息请求的代码,商品新增时是 POST 请求,商品修改是 PUT 请求

由此可以得知:

  • 请求方式:PUT
  • 请求路径:spu
  • 请求参数:商品信息,JSON 格式
  • 返回参数:无
2.2.2 后台接口

Controller

在 SpuController 中添加 updateSpu 方法

/**
 * 更新商品
 * @param spuBo
 * @return
 */
@PutMapping
public ResponseEntity<Void> updateSpu(@RequestBody SpuBo spuBo) {
    spuService.updateSpu(spuBo);
    return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}

Service

在 SpuService 中添加 updateSpu 方法

/**
 * 更新商品
 * @param spuBo
 * @return
 */
@Transactional
public void updateSpu(SpuBo spuBo) {
    // 获取要删除的 Sku
    Sku sku = new Sku();
    sku.setSpuId(spuBo.getId());
    List<Sku> skus = skuMapper.select(sku);

    // 删除原来的 Stock
    for (Sku sku1 : skus) {
        Stock stock = new Stock();
        stock.setSkuId(sku1.getId());
        stockMapper.delete(stock);
    }

    // 删除原来的 Sku
    skuMapper.delete(sku);

    // 新增 Sku 和 Stock
    saveSkuAndStock(spuBo);

    // 更新 Spu
    spuBo.setCreateTime(null);
    spuBo.setLastUpdateTime(new Date());
    spuBo.setValid(null);
    spuBo.setSaleable(null);
    spuMapper.updateByPrimaryKeySelective(spuBo);

    // 更新 SpuDetail
    spuDetailMapper.updateByPrimaryKeySelective(spuBo.getSpuDetail());
}

新增 Sku 和 Stock 的代码之前写过了,为了避免代码冗余,所以抽取成 saveSkuAndStock 方法

/**
 * 新增 Sku 和 Stock
 * @param spuBo
 */
private void saveSkuAndStock(SpuBo spuBo) {
    // 再新增 Sku
    List<Sku> skus = spuBo.getSkus();
    for (Sku sku : skus) {
        sku.setId(null);
        sku.setSpuId(spuBo.getId());
        sku.setCreateTime(new Date());
        sku.setLastUpdateTime(sku.getCreateTime());
        skuMapper.insertSelective(sku);
        // 新增 Stock
        Stock stock = new Stock();
        stock.setSkuId(sku.getId());
        stock.setStock(sku.getStock());
        stockMapper.insertSelective(stock);
    }
}
2.2.3 测试

把小米 10 修改为小米 11

3. 搭建门户系统

后台系统的内容暂时告一段落,有了商品,接下来我们就要在页面展示商品,给用户提供浏览和购买的入口,那就是我们的门户系统。

门户系统面向的是用户,安全性很重要,而且搜索引擎对于单页应用并不友好。因此我们的门户系统不再采用与后台系统类似的 SPA(单页应用),而是采用多页应用,不过依旧是前后端分离。

3.1 创建工程

3.2 导入静态资源

将 leyou-portal 解压并复制到工程中

3.3 live-server

webpack 打包多页应用配置比较繁琐,所以我们使用另一种热部署的方式:live-server

3.3.1 live-server 简介

live-server 是一款带有热加载功能的小型开发服务器。用它来展示你的 HTML / JavaScript / CSS,但不能用于部署最终的网站。

3.3.2 安装 live-server

在 terminal 中输入以下命令:

npm install -g live-server

3.4 启动项目

在 terminal 中输入以下命令:

live-server --port=9002

启动成功

3.5 域名访问

现在访问:http://127.0.0.1:9002/

域名访问:http://www.leyou.com

接下来我们就来实现域名访问:

  1. 配置 host 文件
127.0.0.1 www.leyou.com

2.在 nginx.conf 添加以下配置,实现将 www.leyou.com 反向代理到 http://127.0.0.1:9002

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

重新加载 nginx

nginx.exe -s reload

成功通过域名访问

文章来源:https://blog.csdn.net/you4580/article/details/135221199
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。