今天代码评审,提出了几个问题。
1. jdk8 stream的使用。
我有一个代码是这么写的:
List<PtStorageDetail> details = ptStorageDetailDao.getDetails(ptStorageOrder.getId());
List<PtStorageDetailVO> detailVOS = Lists.newArrayList();
details.stream().forEach(o-> {
PtStorageDetailVO storageDetailVO = new PtStorageDetailVO();
BeanUtils.copyProperties(o, storageDetailVO);
SkuBO skuBO = SkuUtil.get(o.getSkuId());
if(null != skuBO){
storageDetailVO.setSkuName(skuBO.getSkuName());
}
UnitBO unit = UnitUtil.get(o.getUnitId());
if (null != unit) {
storageDetailVO.setUnitName(unit.getName());
}
storageDetailVO.setSkuCode(storageDetailVO.getSkuNo());
detailVOS.add(storageDetailVO);
});
有个同学指出来,可以使用stream的map方式,简便效率又高。
List<PtStorageDetailVO> detailVOS = details.stream().map(o -> {
PtStorageDetailVO storageDetailVO = new PtStorageDetailVO();
BeanUtils.copyProperties(o, storageDetailVO);
SkuBO skuBO = SkuUtil.get(o.getSkuId());
if(null != skuBO){
storageDetailVO.setSkuName(skuBO.getSkuName());
}
UnitBO unit = UnitUtil.get(o.getUnitId());
if (null != unit) {
storageDetailVO.setUnitName(unit.getName());
}
storageDetailVO.setSkuCode(storageDetailVO.getSkuNo());
return storageDetailVO;
}).collect(Collectors.toList());
2. 关于BigDecimal整除的判断。
业务场景,如果能整除,就换算成二级单位。假设100瓶,10瓶/箱,显示10箱。
假设99瓶,10瓶/箱,不能整除,不换算,直接显示99瓶。
我是通过抛异常的方式进行判断整除的:
BigDecimal applyStoreRate = null;
try {
applyStoreRate = warehouseInventoryVO.getApplyStore().divide(secondUnitSku.getRelationship(), 0, BigDecimal.ROUND_UNNECESSARY);
} catch (ArithmeticException e) {
return;
}
warehouseInventoryVO.setUnitId(secondUnitSku.getUnitId());
UnitBO unitBO = UnitUtil.get(secondUnitSku.getUnitId());
warehouseInventoryVO.setUnitName(unitBO.getName());
warehouseInventoryVO.setApplyStore(applyStoreRate);
现在改成工具类:
if(MathUtil.isInteger(warehouseInventoryVO.getApplyStore().divide(secondUnitSku.getRelationship(), 10, BigDecimal.ROUND_HALF_DOWN))) {
applyStoreRate = warehouseInventoryVO.getApplyStore().divide(secondUnitSku.getRelationship());
warehouseInventoryVO.setUnitId(secondUnitSku.getUnitId());
UnitBO unitBO = UnitUtil.get(secondUnitSku.getUnitId());
warehouseInventoryVO.setUnitName(unitBO.getName());
}
//判断是否为整数
public static boolean isInteger(BigDecimal number) {
if (new BigDecimal(number.intValue()).compareTo(number) == 0) {
return true;
}
return false;
}
3. 编辑/同步订单时候,分布式锁放在最前面。我之前是放在后面。
分布式锁防止高并发。
正确方式:
public BaseResultVo syncToOms(Long id) throws BaseException {
boolean hasLock = distributedLocker.tryLock(String.valueOf(LockTypeEnum.STORAGE_SYNC_OMS.getCode()) + id, TimeUnit.SECONDS,5,6);
//获得分布式锁
if(!hasLock){
return BaseResultVo.error(MessageConstants.SysExceptionMsg.SYS_BUSY);
}
PtStorageOrder ptStorageOrder = ptStorageOrderDao.selectById(id);
if (Objects.isNull(ptStorageOrder)) {
return BaseResultVo.error("无入库单信息");
}
if (!OrderEnum.StorageOrderStatusEnum.wait_receive.getId().equals(ptStorageOrder.getStatus())) {
return BaseResultVo.error("非待接单订单不能下单");
}
//生成wms同步DTO
ReceiveOrderPortalDTO receiveOrderPortalDTO = new ReceiveOrderPortalDTO();
receiveOrderPortalDTO.setActionType("ADD");
receiveOrderPortalDTO.setCustomerOrderNo(ptStorageOrder.getStorageNo());
receiveOrderPortalDTO.setExpectReceiveDate(ptStorageOrder.getExpectReceiveDate());
receiveOrderPortalDTO.setOwnerCode(CustomerUtil.get(LoginUserUtil.getLoginUser().getCustomerId()).getCode());
receiveOrderPortalDTO.setCompanyId(ptStorageOrder.getCompanyId());
receiveOrderPortalDTO.setStoreId(ptStorageOrder.getWarehouseId());
receiveOrderPortalDTO.setOwnerId(ptStorageOrder.getCustomerId());
receiveOrderPortalDTO.setReceiveMethod(ptStorageOrder.getReachType());
receiveOrderPortalDTO.setRemark(ptStorageOrder.getRemark());
receiveOrderPortalDTO.setSenderContactName(ptStorageOrder.getContactName());
receiveOrderPortalDTO.setSenderPhone(ptStorageOrder.getContactPhone());
receiveOrderPortalDTO.setType(ptStorageOrder.getType());
receiveOrderPortalDTO.setOrderTime(ptStorageOrder.getOrderTime());
List<PtStorageDetail> details = ptStorageDetailDao.getDetails(id);
List<ReceiveOrderDetailPortalDTO> detailList = details.stream().map(detail -> {
ReceiveOrderDetailPortalDTO detailPortalDTO = new ReceiveOrderDetailPortalDTO();
detailPortalDTO.setNumber(detail.getGoodsAmount().intValue());
SkuBO skuBO = SkuUtil.get(detail.getSkuId());
if(!Objects.isNull(skuBO)){
detailPortalDTO.setSkuName(skuBO.getSkuName());
}
UnitBO unitBO = UnitUtil.get(detail.getUnitId());
if(!Objects.isNull(unitBO)){
detailPortalDTO.setPackageUnit(unitBO.getName());
}
detailPortalDTO.setSkuCode(detail.getSkuNo());
detailPortalDTO.setSkuId(detail.getSkuId());
detailPortalDTO.setUnitId(detail.getUnitId());
return detailPortalDTO;
}).collect(Collectors.toList());
receiveOrderPortalDTO.setDetailList(detailList);
//通知oms接单
MqDTO mqDTO = new MqDTO();
mqDTO.setMqTypeEnum(STORAGE_PORTAL_TO_OMS);
mqDTO.setData(JSONObject.toJSONString(receiveOrderPortalDTO));
if (!mqProducerApi.producer(mqDTO)){
distributedLocker.unlock(String.valueOf(LockTypeEnum.STORAGE_SYNC_OMS.getCode()) + id);
throw new BaseException("oms入库单接单失败");
}
int count = ptStorageOrderDao.updateStatus(ptStorageOrder.getId(), OrderEnum.StorageOrderStatusEnum.order_pushed.getId(),
LoginUserUtil.getLoginUser().getId());
distributedLocker.unlock(String.valueOf(LockTypeEnum.STORAGE_SYNC_OMS.getCode()) + id);
if (count > 0) {
return BaseResultVo.success();
}
return BaseResultVo.error();
}
4. 要加事务控制
捕获异常,处理。
打印日志,切记切记。 log.error("outbound_portal_to_oms 接单失败", e);
并且要有异常栈信息,error显示。
@Override
public BaseResultVo senderOrderForPortal(@RequestBody SenderOrderErpDTO dto) {
try {
return senderOrderService.senderOrderForERP(dto, null);
} catch (Exception e) {
log.error("outbound_portal_to_oms 接单失败", e);
JSONObject json = new JSONObject();
//接单失败
json.put("state", OrderEnum.OutBoundOrderStatusEnum.fail_received.getId());
json.put("customerOrderNo", dto.getCustomerOrderNo());
json.put("updateUser", 0);
BaseResultVo resultVo = ptOutboundOrderApi.updateStatus(json.toJSONString());
dingDingApi.sendDingMsgForService("outbound_portal_to_oms 接单失败, CustomerOrderNo ==> 【" + dto.getCustomerOrderNo() + "】", "15967103436");
return BaseResultVo.error("系统异常");
}
}
加上事务: @Transactional(rollbackFor = Exception.class),并且不能只单单加 @Transactional,而不去加哪种异常回滚。因为默认有RuntimeException才会回滚。
@Override
@Transactional(rollbackFor = Exception.class)
public BaseResultVo senderOrderForERP(SenderOrderErpDTO senderOrderErpDTO, String sign) {
//参数校验
validateParam(senderOrderErpDTO, sign);
//生成发货单
SenderOrder senderOrder = generateSenderOrder(senderOrderErpDTO);
//生成发货单详情
List<SenderOrderDetail> senderOrderDetailList = generateSenderOrderDetailList(senderOrder, senderOrderErpDTO);
SendOrderMainDTO sendOrderMainDTO = generateSendOrderMainAddDTO(senderOrder, senderOrderDetailList);
if ("ADD".equals(senderOrderErpDTO.getActionType())) {
int count = senderOrderDao.add(senderOrder, senderOrderDetailList);
if (0 >= count) {
throw new BaseException("oms新增出库单失败");
}
//通知wms接单
MqDTO mqDTO = new MqDTO();
mqDTO.setMqTypeEnum(OUTBOUND__OMS_TO_WMS);
mqDTO.setData(JSONObject.toJSONString(sendOrderMainDTO));
mqDTO.setTimeOut(10);
if (!mqProducerApi.produer(mqDTO)){
throw new BaseException("oms推送出库单到wms失败");
}
//修改OMS发货单状态:已下发
int update = senderOrderDao.updateStatus(senderOrder.getOrderNo(), OmsOrderStateEnum.SEND.getCode(), 0L);
if (update <=0 ) {
throw new BaseException("修改OMS发货单状态:已下发失败");
}
} else if ("UPDATE".equals(senderOrderErpDTO.getActionType())) {
doUpdate(senderOrder, senderOrderErpDTO, senderOrderDetailList);
updateToWmsAndOms(sendOrderMainDTO, null);
}
return BaseResultVo.success("oms推送出库单到wms成功");
}
5. 错误示例
@Transactional(rollbackFor = Exception.class)
@Override
public BaseResultVo insert(SenderOrderDTO dto) {
SenderOrder senderOrder = new SenderOrder();
SenderOrderDetailDTO senderOrderDetailDTO = dto.getSenderOrderDetailDTO();
SenderOrderDetail senderOrderDetail = new SenderOrderDetail();
BeanUtils.copyProperties(dto, senderOrder);
BeanUtils.copyProperties(senderOrderDetailDTO, senderOrderDetail);
senderOrder.setOrderNo(OrderNoUtil.getOmsOrderSerialNo(OrderNoTypeEnum.SENDER_ORDER));
int count = senderOrderDao.insertSelective(senderOrder);
if (count > 0) {
if(senderOrderDetailDao.insertSelective(senderOrderDetail) <= 0) {
//直接返回,不抛异常,这样是错的,事务就不会回滚。
return BaseResultVo.error();
}
}
// 默认直接返回成功,也是不对。
return BaseResultVo.success();
}
以上代码两个错误点:
1)//直接返回,不抛异常,这样是错的,事务就不会回滚。
2) // 默认直接返回成功,也是不对。需要根据结果进行返回。
6. 插入数据异常
一个环境单据导入到另一个环境。redis获取单号重复了。
private static String getSerial(OrderNoTypeEnum orderNoTypeEnum) {
String key = RedisKeyEnum.SERIAL.getKey() + "_" + orderNoTypeEnum.getCode() + "_" + DateUtil.formatTimestampSimple();
String no = RedisUtil.incr(key, (60 * 60 * 24) + 10) + "";
if (no.length() == NO_LENGTH) {
return no;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < NO_LENGTH - no.length(); i++) {
sb.append("0");
}
sb.append(no);
return sb.toString();
}
public static Long incr(String key,Integer seconds) {
Long rtn = null;
if (key == null || "".equals(key)) {
return rtn;
}
Jedis jedis = null;
try {
jedis = getJedis(jedis);
rtn = jedis.incr(key);
if (null == seconds) {
jedis.expire(key, seconds);
}
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
} finally {
returnResource(jedis);
}
return rtn;
}