乐优商城(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
- 要注意,页面展示的是商品分类和品牌名称,而数据库中保存的是id,新建一个类,继承SPU,并且拓展cname和bname属性,写到
编写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中的
cid1
,cid2
,cid3
属性 - 品牌:是spu中的
brandId
属性 - 标题:是spu中的
title
属性 - 子标题:是spu中的
subTitle
属性 - 售后服务:是SpuDetail中的
afterService
属性 - 包装列表:是SpuDetail中的
packingList
属性
- 商品分类:是SPU中的
- 商品描述:是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