乐优商城(五)商品管理
1. 商品新增
1.1 商品新增前端
-  进入商品列表,点击新增商品  
-  出现一个弹窗 

里面把商品的数据分为了 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:特有规格参数的下标
 
 
- sku: 
    
我们找到前端提交商品信息请求的代码

由此可以得知:
- 请求方式: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. 商品修改
商品修改需要以下步骤:
- 将商品信息回显到页面
- 修改商品信息
- 提交修改
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
接下来我们就来实现域名访问:
- 配置 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
成功通过域名访问

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!