前序准备
将gulimall-ware注册到nacos,修改配置文件,主启动类加上@EnableDiscoveryClient和@EnableTransactionManagement注解。
配置网关,新增路由规则
# 要放在renren-fast的转发之前
- id: ware_route
uri: lb://gulimall-ware
predicates:
- Path=/api/gulimallware/**
filters:
- RewritePath=/api/(?<segment>.*), /$\{segment}
一、仓库维护
这里需要完善条件查询
@Override
public PageUtils queryPage(Map<String, Object> params) {
QueryWrapper<WareInfoEntity> wrapper = new QueryWrapper<>();
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
wrapper.eq("id", key).or()
.like("name", key)
.or().like("address", key)
.or().like("areacode", key);
}
IPage<WareInfoEntity> page = this.page(
new Query<WareInfoEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
}
二、商品库存
2.1 查询sku库存
https://easydoc.net/s/78237135/ZUqEdvA4/hwXrEXBZ
进入商品库存页面会发送/waresku/list给后台,这里我们需要完善条件查询,直接在原来的方法上修改。
@Override
public PageUtils queryPage(Map<String, Object> params) {
/**
* skuId: 1
* wareId: 2
*/
QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();
String skuId = (String) params.get("skuId");
if (!StringUtils.isEmpty(skuId)) {
wrapper.eq("sku_id", skuId);
}
String wareId = (String) params.get("wareId");
if (!StringUtils.isEmpty(wareId)) {
wrapper.eq("ware_id", wareId);
}
IPage<WareSkuEntity> page = this.page(
new Query<WareSkuEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
}
2.2 新增sku库存(先完成采购单维护)
新增库存不是直接在商品库存页面进行新增,而是通过采购。所以我们需要先完成采购的相关功能。
三、采购单维护
3.1 采购需求
采购需求来自两方面:
- 人工添加的采购需求
-
3.1.1 查询采购需求
https://easydoc.net/s/78237135/ZUqEdvA4/Ss4zsV7R 查询wms_purchase_detail表,前端会发送查询条件,需要进行条件查询。
@Override
public PageUtils queryPage(Map<String, Object> params) {
/**
* key: '华为',//检索关键字
* status: 0,//状态
* wareId: 1,//仓库id
*/
QueryWrapper<PurchaseDetailEntity> wrapper = new QueryWrapper<>();
String key = (String) params.get("key");
// and (purchase_id = key or sku_id = key)
if (!StringUtils.isEmpty(key)) {
wrapper.and(w -> {
w.eq("purchase_id", key).or().eq("sku_id", key);
});
}
String status = (String) params.get("status");
if (!StringUtils.isEmpty(status)) {
wrapper.eq("status", status);
}
String wareId = (String) params.get("wareId");
if (!StringUtils.isEmpty(wareId)) {
wrapper.eq("ware_id", wareId);
}
IPage<PurchaseDetailEntity> page = this.page(
new Query<PurchaseDetailEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
}
3.1.2 合并采购需求
新增采购单
在合并采购单之前,需要新增采购单。
此时前端会发送请求http://localhost:88/api/gulimallware/purchase/unreceive/list?t=1647226496404 查询新建的和已分配的采购单(采购单有五种状态:新建、已分配、已领取、已完成、有异常五种状态;其中只有新建和已分配的采购单能够对采购内容进行修改)。
https://easydoc.net/s/78237135/ZUqEdvA4/cUlv9QvK
/**
* 查询新建和已分配状态的采购单
*/
// /ware/purchase/unreceive/list
@GetMapping("/unreceive/list")
public R unreceiveList(@RequestParam Map<String, Object> params) {
PageUtils page = purchaseService.queryPageUnreceivePurchase(params);
return R.ok().put("page", page);
}
/**
* 查询新建和已分配状态的采购单
* @param params
* @return
*/
@Override
public PageUtils queryPageUnreceivePurchase(Map<String, Object> params) {
// 采购单的status必须是0(新建)或者是1(已分配)
IPage<PurchaseEntity> page = this.page(
new Query<PurchaseEntity>().getPage(params),
new QueryWrapper<PurchaseEntity>().eq("status", 0).or().eq("status", 1)
);
return new PageUtils(page);
}
采购单分配给采购人员
合并采购单的时候,展示的应该是采购单的编号和采购人员,所以需要给采购单分配采购人员。
合并采购需求
https://easydoc.net/s/78237135/ZUqEdvA4/cUlv9QvK
当选定采购单时,会将采购需求合并到目标采购单;若没有选定采购单,则新建采购单。
新建一个MergeVo接收前端传过来的数据(采购单ID和采购计划ID)
package com.atguigu.gulimall.gulimallware.vo;
@Data
public class MergeVo {
private Long purchaseId;
private List<Long> items;
}
/**
* 合并采购需求
*/
// /ware/purchase/merge
@PostMapping("/merge")
public R merge(@RequestBody MergeVo vo) {
purchaseService.mergePurchase(vo);
return R.ok();
}
因为采购单和采购计划都有状态属性,新建WareConstant类,存放采购单与采购计划的状态枚举类
package com.atguigu.common.constant;
public class WareConstant {
public enum PurchaseStatusEnum {
CREATED(0, "新建状态"), ASSIGNED(1, "已分配"),
RECEIVE(2, "已领取"), FINISH(3, "已完成"),
ERROR(4, "有异常");
private int code;
private String message;
PurchaseStatusEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
public enum PurchaseDetailStatusEnum {
CREATED(0, "新建状态"), ASSIGNED(1, "已分配"),
BUYING(2, "正在采购"), FINISH(3, "已完成"),
FAIL(4, "采购失败");
private int code;
private String message;
PurchaseDetailStatusEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
}
方法声明&实现
@Autowired
PurchaseDetailServiceImpl purchaseDetailService;
/**
* 合并采购需求到采购单;若指定采购单,则合并到指定采购单,反之新建采购单合并
* @param vo
*/
@Transactional
@Override
public void mergePurchase(MergeVo vo) {
Long purchaseId = vo.getPurchaseId();
if (purchaseId == null) { //没有指定采购单,那么需要新建一个采购单
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode()); // 设置采购单状态 采购单状态使用一个枚举类
purchaseEntity.setCreateTime(new Date()); // 设置新建时间
purchaseEntity.setUpdateTime(new Date()); // 设置修改时间
this.save(purchaseEntity);
purchaseId = purchaseEntity.getId();
}
List<Long> purchaseDetailIds = vo.getItems(); // 获取需要合并到采购单的采购计划id
Long finalPurchaseId = purchaseId;
// 更新每个id对应的采购计划的属性
List<PurchaseDetailEntity> purchaseDetailEntities = purchaseDetailIds.stream().map(item -> {
PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
purchaseDetailEntity.setId(item);
purchaseDetailEntity.setPurchaseId(finalPurchaseId); // 更新采购计划属于哪个采购单
purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode()); // 更新状态
return purchaseDetailEntity;
}).collect(Collectors.toList());
purchaseDetailService.updateBatchById(purchaseDetailEntities); // 批量更新采购计划
// 更新采购单的更新时间
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setId(purchaseId);
purchaseEntity.setUpdateTime(new Date());
this.updateById(purchaseEntity);
}
3.2 领取采购单
采购单分配以后,被分配人员应该可以看到自己被分配到的采购单,然后领取;领取完成后,采购单的状态会改为已领取。同时,已经被领取的采购单将无法再合并采购需求,同时已经合并的采购需求状态变为正在采购中。(领取采购单不属于前端的操作,我们通过postman来模拟手机上领取采购单)
https://easydoc.net/s/78237135/ZUqEdvA4/vXMBBgw1 领取采购单api文档
/**
* 领取采购单
* @param purchaseIds
* @return
*/
// /ware/purchase/received
@PostMapping("/received")
public R receivedPurchase(@RequestBody List<Long> purchaseIds) {
purchaseService.receivedPurchase(purchaseIds);
return R.ok();
}
/**
* 领取采购单
* @param purchaseIds 采购单Id集合
*/
@Override
public void receivedPurchase(List<Long> purchaseIds) {
// 1、确认当前采购单是新建或者已分配状态
List<PurchaseEntity> purchaseEntities = purchaseIds.stream().map(id -> {
// 找出purchaseId对应的采购单实体
PurchaseEntity purchase = this.getById(id);
return purchase;
}).filter(item -> { // 选择已分配的采购单
boolean isValiable = item.getStatus() == WareConstant.PurchaseStatusEnum.ASSIGNED.getCode();
return isValiable;
}).map(purchase -> { // 更新采购单的状态为已领取
purchase.setStatus(WareConstant.PurchaseStatusEnum.RECEIVE.getCode()); // 状态为已领取
purchase.setUpdateTime(new Date()); // 更新时间
return purchase;
}).collect(Collectors.toList());
// 2、数据库中改变采购单的状态为已领取
this.updateBatchById(purchaseEntities);
// 3、改变采购需求的状态为采购中
purchaseEntities.forEach(item -> {
Long purchaseId = item.getId(); // 获取当前采购单Id
// 找出所有purchase_id对应的采购需求
List<PurchaseDetailEntity> detailEntityList = purchaseDetailService.listDetailByPurchaseId(purchaseId);
// 更新采购需求的状态
List<PurchaseDetailEntity> detailEntities = detailEntityList.stream().map(detail -> {
PurchaseDetailEntity entity = new PurchaseDetailEntity();
entity.setId(detail.getId());
entity.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
return entity;
}).collect(Collectors.toList());
purchaseDetailService.updateBatchById(detailEntities);
});
}
/**
* 通过采购单Id查询当前采购单下的所有采购计划
* @param purchaseId
* @return
*/
@Override
public List<PurchaseDetailEntity> listDetailByPurchaseId(Long purchaseId) {
QueryWrapper<PurchaseDetailEntity> wrapper = new QueryWrapper<>();
wrapper.eq("purchase_id", purchaseId);
List<PurchaseDetailEntity> purchaseDetailEntities = this.list(wrapper);
return purchaseDetailEntities;
}
3.3 完善合并采购需求(视频没写)
确认采购单状态是0,1的时候才能合并采购需求。这里我是按自己思路写的,视频里面没写这些。
我让mergePurchase方法返回一个boolean类项的值,以此来判断合并是否成功。
/**
* 合并采购需求
*/
// /ware/purchase/merge
@PostMapping("/merge")
public R merge(@RequestBody MergeVo vo) {
boolean mergePurchase = false;
mergePurchase = purchaseService.mergePurchase(vo);
if (mergePurchase) {
return R.ok();
}
return R.error(404, "所选采购单状态不合法");
}
/**
* 合并采购需求到采购单;若指定采购单,则合并到指定采购单,反之新建采购单合并
* @param vo
*/
@Transactional
@Override
public boolean mergePurchase (MergeVo vo) {
Long purchaseId = vo.getPurchaseId(); // 获取前端传送的采购单Id
if (purchaseId == null) { //没有指定采购单,那么需要新建一个采购单
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode()); // 设置采购单状态 采购单状态使用一个枚举类
purchaseEntity.setCreateTime(new Date()); // 设置新建时间
purchaseEntity.setUpdateTime(new Date()); // 设置修改时间
this.save(purchaseEntity);
purchaseId = purchaseEntity.getId();
}
// TODO 确认采购单的状态是0或者1才可以合并
Integer status = this.getById(purchaseId).getStatus();// 获取当前采购单的状态
if (status < WareConstant.PurchaseStatusEnum.RECEIVE.getCode()) { // 只有当采购单的状态是新建 或 已分配时,才能够合并采购计划
List<Long> purchaseDetailIds = vo.getItems(); // 获取需要合并到采购单的采购需求id
Long finalPurchaseId = purchaseId; // 采购单Id
// 更新每个id对应的采购计划的属性
List<PurchaseDetailEntity> purchaseDetailEntities = purchaseDetailIds.stream().map(item -> {
PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
purchaseDetailEntity.setId(item);
purchaseDetailEntity.setPurchaseId(finalPurchaseId); // 更新采购计划属于哪个采购单
purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode()); // 更新状态
return purchaseDetailEntity;
}).collect(Collectors.toList());
purchaseDetailService.updateBatchById(purchaseDetailEntities); // 批量更新采购计划
// 更新采购单的更新时间
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setId(purchaseId);
purchaseEntity.setUpdateTime(new Date());
this.updateById(purchaseEntity);
return true;
}
return false;
}
3.4 完成采购
完成采购后,采购需求将会添加到库存中去。 https://easydoc.net/s/78237135/ZUqEdvA4/cTQHGXbK
整个业务逻辑分为三个步骤:
- 改变采购项的状态
- 改变采购单的状态(如果采购需求项存在异常,那么采购单异常)
- 将采购成功的采购需求入库
新建Vo类型class,用于接收前端数据
@Data
public class PurchaseItemDoneVo {
private Long itemId; //采购需求Id
private Integer status; // 采需求状态
private String reason; // 采购失败原因
}
Mybatis配置类 (把MapperScan跟 @EnableTransactionManagement【开启事务支持】放到这边来,其实无所谓) 这里分页配置我是新版配置,跟视频里面的不一样,参考官网
@Configuration
@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.gulimallware.dao")
public class WareMyBatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInnerInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(1000L);
// optimizeJoin字段设置(默认为true)是否生成 countSql 优化掉 join 现在只支持 left join
paginationInnerInterceptor.setOptimizeJoin(true);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
/**
* 完成采购单
*/
@PostMapping("/done")
public R finish(@RequestBody PurchaseDoneVo doneVo) {
purchaseService.done(doneVo);
return R.ok();
}
@Autowired
WareSkuService wareSkuService;
/**
* 完成采购单
*/
@Override
public void done(PurchaseDoneVo doneVo) {
// 1、改变采购需求状态
Boolean flag = true; // 记录采购项是否都正常
List<PurchaseItemDoneVo> items = doneVo.getItems(); // 获取采购项
List<PurchaseDetailEntity> updates = new ArrayList<>();
for (PurchaseItemDoneVo item : items) {
PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
if (item.getStatus() == WareConstant.PurchaseDetailStatusEnum.FAIL.getCode()) {
flag = false;
} else { // 3、采购项采购成功,将成功采购的入库
// 通过id查出当前采购项的详细信息
PurchaseDetailEntity byId = purchaseDetailService.getById(item.getItemId());
// 将当前sku的skuId 仓库Id 以及采购数量传过去
wareSkuService.addStock(byId.getSkuId(), byId.getWareId(), byId.getSkuNum());
}
// PurchaseDetailEntity实体类封装需要改变的status
detailEntity.setStatus(item.getStatus());
detailEntity.setId(item.getItemId());
updates.add(detailEntity);
}
purchaseDetailService.updateBatchById(updates);
// 2、改变采购单状态
Long purchaseId = doneVo.getId();
PurchaseEntity purchase = new PurchaseEntity();
purchase.setId(purchaseId);
purchase.setStatus(flag ? WareConstant.PurchaseStatusEnum.FINISH.getCode() : WareConstant.PurchaseStatusEnum.ERROR.getCode());
purchase.setUpdateTime(new Date());
this.updateById(purchase);
}
在讲采购项入库的操作中,需要根据skuId获取到skuName,因此需要远程调用gulimall-product服务。主启动类不要忘记加上@EnableFeignClients注解。
这里远程调用有两种写法:
1. 让所有请求过网关:
@FeignClient(“gulimall-gateway”):给gulimall-gateway所在的机器发请求
@RequestMapping(“/api/gulimallproduct/skuinfo/info/{skuId}”)
2. 不过网关直接给后台服务发请求
@FeignClient(“gulimall-product”)
@RequestMapping(“/gulimallproduct/skuinfo/info/{skuId}”)
package com.atguigu.gulimall.gulimallware.feign;
@FeignClient("gulimall-product")
//@FeignClient("gulimall-gateway")
public interface ProductFeignService {
// @RequestMapping("/api/gulimallproduct/skuinfo/info/{skuId}")
@RequestMapping("/gulimallproduct/skuinfo/info/{skuId}")
//@RequiresPermissions("gulimallproduct:skuinfo:info")
public R info(@PathVariable("skuId") Long skuId);
}
在远程调用过程中,可能会出现异常,如果单纯是因为skuName失败就回滚整个事务,不太划算。这里可以采用try-catch捕获异常。也可以使用其他方法(服务降级,准备一个兜底方法),将会在高级篇讲解。
@Autowired
WareSkuDao wareSkuDao;
@Autowired
ProductFeignService productFeignService;
/**
* 添加sku库存
*
* @param skuId
* @param wareId
* @param skuNum
*/
@Transactional
@Override
public void addStock(Long skuId, Long wareId, Integer skuNum) {
// 1、判断是否存在这个库存记录,没有就是新增操作
List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
if (CollectionUtils.isEmpty(wareSkuEntities)) {
WareSkuEntity wareSkuEntity = new WareSkuEntity();
wareSkuEntity.setSkuId(skuId);
wareSkuEntity.setWareId(wareId);
wareSkuEntity.setStock(skuNum);
wareSkuEntity.setStockLocked(0);
// TODO 这里应该远程查询sku的名字,如果失败整个事务不需要回滚
// 方式1:自己catch异常
// TODO 还可用什么办法让异常出现以后不会滚? 高级
try {
R info = productFeignService.info(skuId);
if (info.getCode() == 0) {
Map<String, Object> map = (Map<String, Object>) info.get("skuInfo");
wareSkuEntity.setSkuName(map.get("skuName").toString());
}
}catch (Exception e) {
}
wareSkuDao.insert(wareSkuEntity);
}
else {
wareSkuDao.addStock(skuId, wareId, skuNum);
}
}
四、商品维护-SPU管理
4.1 解决规格维护不显示
点击spu的规格的时候就直接跳转到了400页面。这是因为gulimall-admin的sys-menu表里面没有规格的目录信息
我们添加上去即可
4.2 获取spu规格
https://easydoc.net/s/78237135/ZUqEdvA4/GhhJhkg7 api文档
@Autowired
private ProductAttrValueService productAttrValueService;
/**
* 按spuId查出spu的规格属性 pms_product_attr_value
*/
// /product/attr/base/listforspu/{spuId}
@GetMapping("/base/listforspu/{spuId}")
public R baseAttrListForSpu(@PathVariable("spuId") Long spuId) {
List<ProductAttrValueEntity> entities = productAttrValueService.baseAttrListForSpu(spuId); // 通过spuId查找spu对应的所有属性
return R.ok().put("data", entities);
}
声明&实现
/**
* 通过spuId查询spu对应的所有属性
* @param spuId
*/
@Override
public List<ProductAttrValueEntity> baseAttrListForSpu(Long spuId) {
List<ProductAttrValueEntity> entities = this.baseMapper.selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
return entities;
}
4.3 修改spu商品规格
https://easydoc.net/s/78237135/ZUqEdvA4/GhnJ0L85 api文档
修改spu商品规格,可以直接把spuId对应的所有属性删除后,再将前端发送过来的属性添加即可。
/**
* 根据spuId修改对应商品规格
*/
// /product/attr/update/{spuId}
@PostMapping("/update/{spuId}")
//@RequiresPermissions("gulimallproduct:attr:update")
public R updateSpuAttr(@PathVariable("spuId") Long spuId, @RequestBody List<ProductAttrValueEntity> entities){
productAttrValueService.updateSpuAttr(spuId, entities);
return R.ok();
}
/**
* 更新spuId对应的所有属性
* @param spuId
* @param entities
*/
@Transactional
@Override
public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities) {
// 1、删除spuId之前对应的所有属性
this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
// 2、更新属性,其实就是批量插入
List<ProductAttrValueEntity> productAttrValueEntities = entities.stream().map(item -> {
item.setSpuId(spuId);
return item;
}).collect(Collectors.toList());
this.saveBatch(productAttrValueEntities);
}