规格模块
后端
编写实体类
package com.leyou.pojo;
import lombok.Data;
import javax.persistence.Id;
import javax.persistence.Table;
@Table(name = "tb_specification")
@Data
public class Specification {
@Id
private Long categoryId;
private String specifications;
}
编写Mapper
package com.leyou.item.mapper;
import com.leyou.pojo.Specification;
import tk.mybatis.mapper.common.Mapper;
public interface SpecificationMapper extends Mapper<Specification> {
}
编写Service
package com.leyou.item.service;
import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.item.mapper.CategoryMapper;
import com.leyou.item.mapper.SpecificationMapper;
import com.leyou.pojo.Specification;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class SpecificationService {
@Autowired
private SpecificationMapper specificationMapper;
@Autowired
private CategoryMapper categoryMapper;
/**
* 按分类ID查询规格(此表中分类ID其实为该表的ID)
*
* @param categoryId 分类ID
* @return 规格信息
*/
public Specification queryById(Long categoryId) {
return specificationMapper.selectByPrimaryKey(categoryId);
}
/**
* 新增规格
*
* @param specification 规格信息
* @return 规格信息
*/
@Transactional
public Specification addSpecification(Specification specification) {
// 先查询并判断是否有该分类
if (categoryMapper.selectByPrimaryKey(specification.getCategoryId()) == null) {
throw new LyException(LyExceptionEnum.CATEGORY_NOT_FOUND);
}
// 新增
if (specificationMapper.insert(specification) == 1)
return specification;
throw new LyException(LyExceptionEnum.SAVE_FAILURE);
}
/**
* 编辑规格
* @param specification 规格信息
* @return 规格信息
*/
public Specification saveSpecification(Specification specification) {
// 先查询并判断是否有该分类
if (categoryMapper.selectByPrimaryKey(specification.getCategoryId()) == null) {
throw new LyException(LyExceptionEnum.CATEGORY_NOT_FOUND);
}
// 编辑
if (specificationMapper.updateByPrimaryKey(specification) == 1)
return specification;
throw new LyException(LyExceptionEnum.SAVE_FAILURE);
}
}
编写Controller
package com.leyou.item.cotroller;
import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.item.service.SpecificationService;
import com.leyou.pojo.Specification;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("spec")
public class SpecificationController {
@Autowired
private SpecificationService specificationService;
@GetMapping("/{categoryId}")
public ResponseEntity<String> querySpecificationByCategoryId(@PathVariable("categoryId") Long categoryId) {
if (categoryId == null) {
throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
}
// 查询规格
Specification specification = specificationService.queryById(categoryId);
if (specification == null) {
throw new LyException(LyExceptionEnum.SPU_NOT_FOUND);
}
// 返回存储的规格JSON字符串
return ResponseEntity.ok(specification.getSpecifications());
}
@PostMapping
public ResponseEntity<Specification> addSpecification(@RequestBody Specification specification) {
if (specification == null || specification.getCategoryId() == null) {
throw new LyException(LyExceptionEnum.PARAM_INVALID);
}
// 新增
return ResponseEntity.status(HttpStatus.CREATED).body(specificationService.addSpecification(specification));
}
@PutMapping
public ResponseEntity<Specification> saveSpecification(@RequestBody Specification specification) {
if (specification == null || specification.getCategoryId() == null) {
throw new LyException(LyExceptionEnum.PARAM_INVALID);
}
// 新增
return ResponseEntity.ok(specificationService.saveSpecification(specification));
}
}
这里规格的修改和删除为一体
商品模块
后端
实体类
- Spu
package com.leyou.pojo;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Table(name = "tb_spu")
@Data
public class Spu {
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
private Long brandId;
private Long cid1;// 1级类目
private Long cid2;// 2级类目
private Long cid3;// 3级类目
private String title;// 标题
private String subTitle;// 子标题
private Boolean saleable;// 是否上架
private Boolean valid;// 是否有效,逻辑删除用
private Date createTime;// 创建时间
private Date lastUpdateTime;// 最后修改时间
}
-
SpuDetail
package com.leyou.pojo; import lombok.Data; import javax.persistence.Id; import javax.persistence.Table; @Table(name="tb_spu_detail") @Data public class SpuDetail { @Id private Long spuId;// 对应的SPU的id private String description;// 商品描述 private String specTemplate;// 商品特殊规格的名称及可选值模板 private String specifications;// 商品的全局规格属性 private String packingList;// 包装清单 private String afterService;// 售后服务 }
编写Mapper
package com.leyou.item.mapper;
import com.leyou.pojo.Spu;
import tk.mybatis.mapper.common.Mapper;
public interface GoodsMapper extends Mapper<Spu> {
}
编写Service
package com.leyou.item.service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.leyou.BO.SpuBO;
import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.util.LeyouConstants;
import com.leyou.common.vo.PageResult;
import com.leyou.item.mapper.*;
import com.leyou.pojo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class GoodsService {
@Autowired
private GoodsMapper goodsMapper;
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private BrandMapper brandMapper;
@Autowired
private SkuMapper skuMapper;
@Autowired
private SpuDetailMapper spuDetailMapper;
@Autowired
private StockMapper stockMapper;
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 分页查询商品列表
*
* @param page 当前页
* @param rows 每页记录数
* @param sortBy 排序字段
* @param desc 是否降序
* @param key 查询关键字
* @param saleable 是否过滤上下架
* @return 商品列表
*/
public PageResult<SpuBO> querySpuByPage(int page, int rows, String sortBy, Boolean desc, String key, Boolean saleable) {
// 分页
PageHelper.startPage(page, rows);
// 封装查询关键字
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);
}
// 排序
if (StringUtils.isNotBlank(sortBy)) {
example.setOrderByClause(sortBy + (desc ? " DESC" : " ASC"));
}
// 执行查询
List<Spu> spuList = goodsMapper.selectByExample(example);
if (spuList.size() < 1) {
throw new LyException(LyExceptionEnum.SPU_LIST_NOT_FOUND);
}
PageInfo<Spu> pageInfo = new PageInfo<>(spuList);
// 使用spu集合 构造spuBO集合
List<SpuBO> spuBOList = pageInfo.getList().stream().map(spu -> {
SpuBO spuBO = new SpuBO();
BeanUtils.copyProperties(spu, spuBO);
// 查询分类名称列表并处理成字符串
Optional<String> categoryNameString = categoryMapper.selectByIdList(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()))
.stream()
.map(Category::getName)
.reduce((name1, name2) -> name1 + "/" + name2);
// 查询品牌名称
Brand brand = brandMapper.selectByPrimaryKey(spu.getBrandId());
if (brand == null) {
throw new LyException(LyExceptionEnum.BRAND_NOT_FOUND);
}
// 设置分类以及品牌名称
spuBO.setBname(brand.getName());
spuBO.setCname(categoryNameString.get());
return spuBO;
}).collect(Collectors.toList());
return new PageResult<>(pageInfo.getTotal(), spuBOList);
}
/**
* 新增商品
*
* @param goods 商品信息
* @return SPU
*/
@Transactional
public Spu addGoods(SpuBO goods) {
try {
// 保存SPU
goods.setSaleable(true); // 上架
goods.setCreateTime(new Date()); // 新增时间
goods.setLastUpdateTime(goods.getCreateTime()); // 最后更新时间
goodsMapper.insert(goods);
// 保存SPU描述
SpuDetail spuDetail = goods.getSpuDetail();
spuDetail.setSpuId(goods.getId()); // 设置ID
spuDetailMapper.insert(spuDetail);
// 保存SKU列表
saveSkuList(goods);
} catch (Exception e) {
throw new LyException(LyExceptionEnum.SAVE_FAILURE);
}
return goods;
}
// 保存SKU列表
private void saveSkuList(SpuBO goods) {
goods.getSkuList().forEach(sku -> {
if (sku.getEnable()) {
// 保存sku信息
sku.setCreateTime(goods.getCreateTime());
sku.setSpuId(goods.getId());
sku.setLastUpdateTime(goods.getCreateTime());
skuMapper.insert(sku);
// 保存库存信息
Stock stock = new Stock();
stock.setSkuId(sku.getId());
stock.setStock(sku.getStock());
stockMapper.insert(stock);
}
});
}
/**
* 编辑商品
*
* @param goods 商品信息
* @return SPU
*/
@Transactional
public Spu saveGoods(SpuBO goods) {
try {
// 保存SPU
goods.setSaleable(true); // 上架
goods.setLastUpdateTime(new Date()); // 最后更新时间
goodsMapper.updateByPrimaryKey(goods);
// 保存SPU描述
spuDetailMapper.updateByPrimaryKeySelective(goods.getSpuDetail());
// 保存SKU列表,需要先删除原先的SKU列表
Sku sku = new Sku();
sku.setSpuId(goods.getId());
skuMapper.delete(sku);
// 删除库存信息
List<Sku> skuList = skuMapper.select(sku);
List<Long> ids = skuList.stream().map(Sku::getId).collect(Collectors.toList());
stockMapper.deleteByIdList(ids);
// 保存更新后的数据
saveSkuList(goods);
} catch (Exception e) {
throw new LyException(LyExceptionEnum.SAVE_FAILURE);
}
return goods;
}
/**
* 按ID查询商品信息
*
* @param spuId spuid
* @return 商品信息
*/
public SpuBO queryGoodsById(Long spuId) {
SpuBO spuBO = new SpuBO();
// 查询spu基本信息
Spu spu = goodsMapper.selectByPrimaryKey(spuId);
if (spu == null || spu.getId() == null) {
throw new LyException(LyExceptionEnum.GOODS_NOT_FOUND);
}
BeanUtils.copyProperties(spu, spuBO);
// 查询商品描述信息
spuBO.setSpuDetail(spuDetailMapper.selectByPrimaryKey(spuId));
// 查询商品SKU列表
Sku sku = new Sku();
sku.setSpuId(spuId);
List<Sku> skuList = skuMapper.select(sku);
spuBO.setSkuList(skuList);
return spuBO;
}
/**
* 按spuid查询商品描述
*
* @param spuId id
* @return 描述信息
*/
public SpuDetail querySpuDetailById(Long spuId) {
SpuDetail spuDetail = spuDetailMapper.selectByPrimaryKey(spuId);
if (spuDetail == null) {
throw new LyException(LyExceptionEnum.GOODS_NOT_FOUND);
}
return spuDetail;
}
/**
* 按spuid查询sku列表
*
* @param spuId id
* @return sku列表
*/
public List<Sku> querySkuListById(Long spuId) {
Sku sku = new Sku();
sku.setSpuId(spuId);
List<Sku> skuList = skuMapper.select(sku);
if (skuList.isEmpty()) {
throw new LyException(LyExceptionEnum.SKU_LIST_NOT_FOUND);
}
// 查询每个sku的库存信息
List<Stock> stockList = stockMapper.selectByIdList(skuList.stream().map(Sku::getId).collect(Collectors.toList()));
if (stockList.isEmpty()) {
throw new LyException(LyExceptionEnum.GOODS_STOCK_NOT_FOUND);
}
// 将库存数量与skuId生成map
Map<Long, Long> stockMap = stockList.stream().collect(Collectors.toMap(Stock::getSkuId, Stock::getStock));
// 设置库存数量
skuList.forEach(s -> s.setStock(stockMap.get(s.getId())));
return skuList;
}
/**
* 更新商品状态(上下架)
*
* @param skuId skuId
* @param status 商品状态
*/
public Sku updateGoodsStatus(Long skuId, Boolean status) {
// 查询sku
Sku sku = skuMapper.selectByPrimaryKey(skuId);
if (sku == null) {
throw new LyException(LyExceptionEnum.GOODS_NOT_FOUND);
}
// 修改商品状态
sku.setEnable(status);
// 保存
skuMapper.updateByPrimaryKey(sku);
return sku;
}
/**
* 删除商品
*
* @param spuId 商品ID
* @return 被删除的商品
*/
@Transactional
public Spu deleteGoods(Long spuId) {
// 查询spu
Spu spu = goodsMapper.selectByPrimaryKey(spuId);
if (spu == null) {
throw new LyException(LyExceptionEnum.GOODS_NOT_FOUND);
}
// 删除spu和spuDetail
goodsMapper.deleteByPrimaryKey(spuId);
spuDetailMapper.deleteByPrimaryKey(spuId);
// 删除sku列表
Sku sku = new Sku();
sku.setSpuId(spuId);
List<Sku> skuList = skuMapper.select(sku);
List<Long> ids = skuList.stream().map(Sku::getId).collect(Collectors.toList());
skuMapper.deleteByIdList(ids);
// 删除库存信息
stockMapper.deleteByIdList(ids);
return spu;
}
}
编写Controller
package com.leyou.item.cotroller;
import com.leyou.BO.SpuBO;
import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.vo.PageResult;
import com.leyou.item.service.GoodsService;
import com.leyou.pojo.Sku;
import com.leyou.pojo.Spu;
import com.leyou.pojo.SpuDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
/**
* 分页查询商品列表
*
* @param page 当前页码
* @param rows 每页记录数
* @param sortBy 排序字段
* @param desc 是否降序
* @param key 查询关键字
* @param saleable 是否上架
* @return List<SpuBO>
*/
@GetMapping("/spu/page")
public ResponseEntity<PageResult<SpuBO>> querySpuByPage(@RequestParam(value = "page", defaultValue = "1") int page,
@RequestParam(value = "rows", defaultValue = "5") int rows,
@RequestParam(value = "sortBy", required = false) String sortBy,
@RequestParam(value = "desc", required = false, defaultValue = "false") Boolean desc,
@RequestParam(value = "key", required = false) String key,
@RequestParam(value = "saleable", required = false) Boolean saleable) {
return ResponseEntity.ok(goodsService.querySpuByPage(page, rows, sortBy, desc, key, saleable));
}
/**
* 新增商品
*
* @param goods 商品
* @return Spu
*/
@PostMapping
public ResponseEntity<Spu> addGoods(@RequestBody SpuBO goods) {
if (goods == null)
throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
// 保存
return ResponseEntity.status(HttpStatus.CREATED).body(goodsService.addGoods(goods));
}
/**
* 编辑商品
*
* @param goods 商品
* @return Spu
*/
@PutMapping
public ResponseEntity<Spu> saveGoods(@RequestBody SpuBO goods) {
if (goods == null)
throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
// 保存
return ResponseEntity.ok(goodsService.saveGoods(goods));
}
/**
* 按商品ID查询商品描述
*
* @param spuId 商品ID
* @return SpuDetail
*/
@GetMapping("/spu/detail/{spuId}")
public ResponseEntity<SpuDetail> querySpuDetailById(@PathVariable("spuId") Long spuId) {
if (spuId == null) {
throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
}
return ResponseEntity.ok(goodsService.querySpuDetailById(spuId));
}
/**
* 按商品ID查询该商品所有规格的商品列表
*
* @param spuId 商品ID
* @return List<Sku>
*/
@GetMapping("/sku/list/{spuId}")
public ResponseEntity<List<Sku>> querySkuListById(@PathVariable("spuId") Long spuId) {
if (spuId == null) {
throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
}
return ResponseEntity.ok(goodsService.querySkuListById(spuId));
}
/**
* 上架下架
*
* @param skuId skuID
* @param status 修改后的商品状态
* @return Sku
*/
@GetMapping("/sku/status/{skuId}")
public ResponseEntity<Sku> updateSkuStatus(@PathVariable("skuId") Long skuId,
@RequestParam("status") Boolean status) {
return ResponseEntity.ok(goodsService.updateGoodsStatus(skuId, status));
}
/**
* 删除指定spuId的商品
*
* @param spuId 商品ID
* @return Spu 被删除的spu
*/
@DeleteMapping("/{spuId}")
public ResponseEntity<Spu> deleteGoods(@PathVariable("spuId") Long spuId) {
return ResponseEntity.ok(goodsService.deleteGoods(spuId));
}
/**
* 查询spu信息
*
* @param spuId 商品ID
* @return Spu
*/
@GetMapping("/{spuId}")
public ResponseEntity<SpuBO> queryGoodsById(@PathVariable("spuId") Long spuId) {
return ResponseEntity.ok(goodsService.queryGoodsById(spuId));
}
}
其他后端改动
分类Mapper新增继承
package com.leyou.item.mapper;
import com.leyou.pojo.Category;
import tk.mybatis.mapper.additional.idlist.SelectByIdListMapper;
import tk.mybatis.mapper.common.Mapper;
public interface CategoryMapper extends Mapper<Category>, SelectByIdListMapper<Category, Long> {
}
继承SelectByIdListMapper<T, PK>,泛型第一参数为实体类,第二个参数为ID类型
品牌新增接口
-
BrandController
@GetMapping("/cid/{categoryId}") public ResponseEntity<List<Brand>> queryBrandByCategoryId(@PathVariable("categoryId") Long categoryId) { if (categoryId == null) { throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL); } // 查询 return ResponseEntity.ok(brandService.queryBrandByCategoryId(categoryId)); }
-
BrandService
/** * 按分类ID查询品牌列表 * * @param categoryId 分类ID * @return 品牌列表 */ public List<Brand> queryBrandByCategoryId(Long categoryId) { List<Brand> brands = brandMapper.queryBrandByCategoryId(categoryId); if (brands.isEmpty()) { throw new LyException(LyExceptionEnum.BRAND_LIST_NOT_FOUND); } return brands; }
-
BrandMapper
@Select("SELECT b.* FROM tb_category_brand cb, tb_brand b WHERE cb.category_id = #{categoryId} AND cb.brand_id = b.id") List<Brand> queryBrandByCategoryId(Long categoryId);
新增商品BO
package com.leyou.BO;
import com.leyou.pojo.Sku;
import com.leyou.pojo.Spu;
import com.leyou.pojo.SpuDetail;
import lombok.Data;
import javax.persistence.Transient;
import java.util.List;
@Data
public class SpuBO extends Spu {
// 分类名称
@Transient
private String cname;
// 品牌名称
@Transient
private String bname;
// 商品描述
@Transient
private SpuDetail spuDetail;
// 商品列表
@Transient
private List<Sku> skuList;
}
新增异常枚举
SPU_LIST_NOT_FOUND(404, "未查询到商品列表"),
SPU_NOT_FOUND(404, "未查询到商品列表"),
前端
Goods.vue
getDataFromApi() {
this.loading = true;
this.$http.get("/item/goods/spu/page", {
params: {
page: this.pagination.page, // 当前页
rows: this.pagination.rowsPerPage, // 每页条数
sortBy: this.pagination.sortBy, // 排序字段
desc: this.pagination.descending, // 是否降序
key: this.search.key, // 查询字段
saleable: this.search.saleable // 过滤上下架
}
}).then(response => { // 获取响应结果对象
this.totalItems = response.data.total; // 总条数
this.items = response.data.items; // 品牌数据
this.loading = false; // 加载完成
}).catch(response => {
// http返回的error结构很复杂,并不是直接返回你定义好的对象格式。
// 如果你也想使用 response.data.message 获取到错误消息
// 需要在http.js中自定义个一个响应拦截器
// 或者你也可以使用 response.response.data.message 直接获取到错误消息
this.$message.error(response.data.message);
});
}
goods.vue
其他地方均无太大改动。如有报错,注意查看浏览器中提示的错误信息。
http.js新增响应拦截器
axios.interceptors.response.use(
response => {
const res = response.status
if (res !== 200) {
return Promise.reject('error')
} else {
return response
}
},
error => {
console.log('err' + error.response) // for debug
console.log('err' + error.message)
return Promise.reject(error.response)
}
)
由于我的那部分乐优资料中的SQL语句和视频中不一致,这一块有很多的实现都和视频中的不同,我写这一块的时候也有许多的BUG,但是并不要紧,这不是关键流程,写不写得出来问题都不大。