1.商品生命周期分析

(1)商品⽣命周期的定义

商品⽣命周期是⼀个商品由引⼊试销到淘汰停售的全过程,商品⽣命周期是针对商品从新品到淘汰的不同阶段的描述。具体的阶段有:新品期、试销期、成熟期、休眠期、淘汰期、已淘汰。

商品⽣命周期主要跟随商品状态的变化⽽变化,商品本身的状态主要有如下⼏种:准备上架、试销上架、上架、预下架、下架、已停售。

(2)为什么要有商品生命周期

一.建⽴商品淘汰策略避免占用库存(滞销考核)

只要是商品,就存在畅销、滞销等不同情况。由于⾃营平台的仓储系统成本⾮常⼤,因此需要及时对商品进⾏优胜劣汰,避免因滞销品不能及时淘汰⽽造成的库存积压和流动资⾦占⽤。

二.降低采购成本(试销考核)

除了建⽴淘汰策略避免占用库存,还可以降低采购成本。根据商品⽣命周期可以建⽴⼀套试销考核机制,在经过试销期 + 销售效果不错的情况下,再进⾏⼤规模采购,从而减少因为采购过多 + 滞销商品而带来的资⾦占⽤。

三.提升商品与平台的匹配度

从⻓远来看,上述两个不同周期的考核策略:滞销考核 + 试销考核,可以帮助平台找到更加匹配的商品,降低滞销商品⽐率,提升平台利润。

(3)商品⽣命周期6个阶段

一.新品期:商品建⽴后的初始⽣命周期状态

二.试销期:商品进⼊试销环节,开始试销考核

三.成熟期:商品通过试销考核,进⼊正式销售阶段

四.休眠期:商品由于短期⽆货或季节原因进⼊休眠状态,不再对其考核

五.淘汰期:商品未通过试销考核、滞销考核,开始清货

六.已淘汰:商品清货完毕,不再进⾏销售

其变化情况如下图所示:

(4)商品生命周期整个流程

一.商品数据录入与发起采购流程

二.商品采购入库到库存变更的流程

三.基于少量库存的商品试销流程

四.商品试销上架和考核流程

五.商品生命周期的状态流转流程

试销期:商品新发布完成,在试销上架后,会进⼊试销考核,这段考核周期称为试销考核期。

滞销期:商品通过试销考核正式上架后,会进⼊正式售卖阶段,整个售卖阶段(下架前)处于滞销考核期。

如下图示:

商品主要在试销期、滞销期存在考核。在考核过程中,根据⼀定的考核规则,例如销量、退单率、投诉率等,对商品进⾏考核。考核不通过的商品,会⽣成考核不通过的记录。考核通过的商品,继续进⾏考核、或暂停考核。考核不通过的记录,会影响商品库存系统的采购、补货等操作。

注意:⽣命周期状态不可⾃动推进。需要运营⼈员根据考核结果,以及运营策略做⼿动变更操作,且变更操作对象为商品状态,商品⽣命周期随商品状态变更⾃动变更。

六.考核白名单机制

考核⽩名单:针对指定的⼀批商品不进⾏考核,默认不进⾏商品淘汰操作。

2.商品状态与生命周期相关数据库表建模

(1)数据库表的设计

商品⽣命周期表和商品状态表,是⼀对⼀的关系,⼀个商品不存在在某个状态下有多个⽣命周期的情况。

商品⽣命周期表和商品考核结果表,是⼀对多的关系。因为考核可能存在多轮,每⼀段时间(一周)就会存在⼀条考核结果记录。

商品考核结果缓冲表和商品考核结果表,是⼀对⼀的关系。注意:商品考核结果缓冲表⽤于和⼤数据系统的交互。因为考核结果的更新频率为1day,⼤数据的统计数据集的更新频率为10min。因此设置⼀张缓冲表以对这两种频率进⾏解耦。

(2)商品⽣命周期状态表

CREATE TABLE `item_period_stage` (

`id` bigint(40) NOT NULL AUTO_INCREMENT COMMENT '主键',

`item_id` varchar(40) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '商品ID',

`period_stage` tinyint(3) NOT NULL DEFAULT '0' COMMENT '⽣命周期阶段:0-新品期,1-试销期,2-成熟期,3-淘汰期,4-已淘汰',

`item_name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '商品名称',

`del_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标记(1-有效,0-删除)',

`create_user` int(10) NOT NULL DEFAULT '0' COMMENT '创建⼈',

`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',

`update_user` int(10) NOT NULL DEFAULT '0' COMMENT '更新⼈',

`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更改时间',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品⽣命周期状态表';

(3)商品状态表

CREATE TABLE `item_period_status` (

`id` bigint(40) NOT NULL AUTO_INCREMENT COMMENT '主键',

`item_id` varchar(40) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '商品ID',

`item_name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '商品名称',

`period_status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '商品状态:1-准备上架,2-试销上架,3-上架,4-预下架,5-下架,6-停售',

`del_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标记(1-有效,0-删除)',

`create_user` int(10) NOT NULL DEFAULT '0' COMMENT '创建⼈',

`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',

`update_user` int(10) NOT NULL DEFAULT '0' COMMENT '更新⼈',

`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更改时间',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品状态表';

(4)商品考核结果表

CREATE TABLE `item_expri_result` (

`id` bigint(40) NOT NULL AUTO_INCREMENT COMMENT '主键',

`period_stage_id` bigint(40) NOT NULL COMMENT '商品⽣命周期状态id',

`category_id` int(10) NOT NULL DEFAULT '0' COMMENT '末级品类ID',

`item_id` varchar(40) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '商品ID',

`market_expri_result` tinyint(3) NOT NULL DEFAULT '0' COMMENT '考核状态:0-考核中;1-考核通过;2-考核不通过;3-不考核或暂停考核',

`market_expri_result_desc` varchar(255) DEFAULT '' COMMENT '考核结果描述',

`rule` varchar(255) default '考核规则',

`purchase_status` tinyint(3) NOT NULL COMMENT '是否可采:0-不可采;1-可采;',

`replenishment_status` tinyint(3) NOT NULL COMMENT '是否可补:0-不可补;1-可补',

`sale_status` tinyint(3) NOT NULL COMMENT '是否可售:0-不可售;1-可售',

`del_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标记(1-有效,0-删除)',

`create_user` int(10) NOT NULL DEFAULT '0' COMMENT '创建⼈',

`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',

`update_user` int(10) NOT NULL DEFAULT '0' COMMENT '更新⼈',

`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更改时间',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品考核结果表';

(5)商品考核结果缓冲表

CREATE TABLE `item_expri_result_buffer` (

`id` bigint(40) NOT NULL AUTO_INCREMENT COMMENT '主键',

`period_stage_id` bigint(40) NOT NULL COMMENT '商品⽣命周期状态id',

`category_id` int(10) NOT NULL DEFAULT '0' COMMENT '末级品类ID',

`item_id` varchar(40) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '商品ID',

`market_expri_result` tinyint(3) NOT NULL DEFAULT '0' COMMENT '考核状态:0-考核中;1-考核通过;2-考核不通过;3-不考核或暂停考核',

`market_expri_result_desc` varchar(255) DEFAULT '' COMMENT '考核结果描述',

`rule` varchar(255) DEFAULT '考核规则',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品考核结果缓冲表';

(6)品类考核⽩名单表

create table `category_expri_white` (

`id` bigint(40) primary key AUTO_INCREMENT COMMENT '主键',

`category_id` int(10) NOT NULL DEFAULT 0 COMMENT '末级品类ID',

`category_name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '品类名称',

`active_flag` tinyint(1) NULL DEFAULT 1 COMMENT '⽣效状态:0-未⽣效;1-⽣效',

`start_time` timestamp default '1970-01-01 08:00:01' comment '开始时间',

`end_time` timestamp default '2038-01-19 11:14:07' comment '结束时间',

`del_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标记(1-有效,0-删除)',

`create_user` int(10) NOT NULL DEFAULT '0' COMMENT '创建⼈',

`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',

`update_user` int(10) NOT NULL DEFAULT '0' COMMENT '更新⼈',

`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更改时间'

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='品类考核⽩名单表';

3.建品流程与商品状态初始化的异步驱动

商品M端系统建品后会发送MQ消息,商品生命周期系统会订阅该消息,然后创建该商品的准备上架状态,并且当消息消费失败时可以主动调⽤接⼝创建商品状态。

具体实现如下:

//商品M端系统建品后会发送MQ消息

@Service

public class ProductServiceImpl implements ProductService {

...

//建品/编辑商品

@Transactional(rollbackFor = Exception.class)

@Override

@ParamsValidate

public ProductDTO product(ProductRequest productRequest) {

//入参检查

checkProductRequestParam(productRequest);

//商品数据处理

ProductDTO productDTO = handleProduct(productRequest);

//返回商品信息

return productDTO;

}

//商品数据处理

private ProductDTO handleProduct(ProductRequest productRequest) {

//构建商品的全量信息

FullProductData fullProductData = buildProduct(productRequest);

//是否构建填充itemId

Boolean createFlag = whetherBuildProductItemId(fullProductData);

//判断是否需要审核,根据配置的sku或者item或者品类信息,如果需要审核,只处理草稿表中的数据,正式表中的数据不动

if (productAuditRepository.needAudit(fullProductData, createFlag)) {

//需要审核,则正式表中的数据不变更,只新增草稿表记录

FullDraftData fullDraftData = buildDraft(fullProductData, AuditTypeEnum.GOODS.getCode());

//保存草稿信息

productAuditRepository.saveDraft(fullDraftData);

return new ProductDTO(null, null);

}

//如果不需要审核,则保存商品信息

this.saveOrUpdateDBProduct(fullProductData, createFlag);

//发送MQ消息通知订阅方

sendUpdateProductMessage(fullProductData);

//返回商品返回结果

return new ProductDTO(fullProductData.getItemInfoDO().getItemId(), buildProductSkuIds(fullProductData));

}

private void sendUpdateProductMessage(FullProductData fullProductData) {

//构建商品修改消息对象

UpdateProductMessage message = buildUpdateProductMessage(fullProductData);

//发送商品修改消息

defaultProducer.sendMessage(RocketMqConstant.PRODUCT_UPDATE_TOPIC, JSONObject.toJSONString(message), "商品修改");

}

...

}

@Component

public class DefaultProducer {

private DefaultMQProducer producer;

...

//发送消息

public void sendMessage(String topic, String message, Integer delayTimeLevel, String type) {

Message msg = new Message(topic, message.getBytes(StandardCharsets.UTF_8));

try {

if (delayTimeLevel > 0) {

msg.setDelayTimeLevel(delayTimeLevel);

}

SendResult send = producer.send(msg);

if (SendStatus.SEND_OK == send.getSendStatus()) {

log.info("发送MQ消息成功, type:{}, message:{}", type, message);

} else {

throw new ProductBizException(send.getSendStatus().toString());

}

} catch (Exception e) {

log.error("发送MQ消息失败:", e);

throw new ProductBizException(CommonErrorCodeEnum.SEND_MQ_FAILED);

}

}

...

}

//商品生命周期系统会订阅该消息,然后创建该商品的默认状态

@Configuration

public class ConsumerBeanConfig {

...

//配置内容对象

@Autowired

private RocketMQProperties rocketMQProperties;

//新增商品消息消费者,负责创建商品生命周期 todo 需要监听消息体结构变更后新的topic

@Bean("periodStageUpdateTopic")

public DefaultMQPushConsumer createItemStageConsumer(ItemUpdateListener itemUpdateListener) throws MQClientException {

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(RocketMqConstant.PRODUCT_DEFAULT_CONSUMER_GROUP);

consumer.setNamesrvAddr(rocketMQProperties.getNameServer());

consumer.subscribe(RocketMqConstant.PRODUCT_UPDATE_TOPIC, "*");

consumer.registerMessageListener(itemUpdateListener);

consumer.start();

return consumer;

}

...

}

//监听建品&修改商品topic,修改生命周期中维护的商品状态和生命周期

@Component

public class ItemUpdateListener implements MessageListenerConcurrently {

@Autowired

private ItemPeriodStatusService itemPeriodStatusService;

@Override

public ConsumeConcurrentlyStatus consumeMessage(List list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {

try {

for (MessageExt messageExt : list) {

log.info("商品变更,消息内容:{}", messageExt.getBody());

String msg = new String(messageExt.getBody());

...

itemPeriodStatusService.saveItemStatus(request);

...

}

} catch (Exception e) {

log.error("consume error, 商品缓存更新失败", e);

//本次消费失败,下次重新消费

return ConsumeConcurrentlyStatus.RECONSUME_LATER;

}

return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;

}

}

@DubboService(version = "1.0.0", interfaceClass = ItemPeriodStatusApi.class, retries = 0)

public class ItemPeriodStatusApiImpl implements ItemPeriodStatusApi {

...

@Autowired

private ItemPeriodStatusService itemPeriodStatusService;

//保存商品状态

@Override

public JsonResult saveItemStatus(ItemPeriodStatusRequest request) {

try {

ItemPeriodStageResponse response = itemPeriodStatusService.saveItemStatus(request);

return JsonResult.buildSuccess(response);

} catch (ProductBizException e) {

log.error("biz error: request={}", request, e);

return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());

} catch (Exception e) {

log.error("system error: request={}", request, e);

return JsonResult.buildError(e.getMessage());

}

}

...

}

@Service

public class ItemPeriodStatusServiceImpl implements ItemPeriodStatusService {

@Resource

private ItemPeriodStatusRepository itemPeriodStatusRepository;

@Resource

private ItemPeriodStageRepository itemPeriodStageRepository;

...

//保存商品的状态

@Override

@Transactional(rollbackFor = Exception.class)

public ItemPeriodStageResponse saveItemStatus(ItemPeriodStatusRequest request) {

//入参检查

checkParams(request);

//保存商品状态

itemPeriodStatusRepository.saveItemStatus(request);

//保存生命周期状态

ItemPeriodRequest periodRequest = new ItemPeriodRequest();

periodRequest.setItemId(request.getItemId());

periodRequest.setItemName(request.getItemName());

itemPeriodStageRepository.saveProductStage(periodRequest);

return new ItemPeriodStageResponse();

}

...

}

//商品状态资源管理

@Repository

public class ItemPeriodStatusRepository {

...

//保存商品状态

public ItemPeriodStageResponse saveItemStatus(ItemPeriodStatusRequest request) {

LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery();

queryWrapper.eq(Objects.nonNull(request.getItemId()), ItemPeriodStatusDO::getItemId, request.getItemId());

ItemPeriodStatusDO itemStatusDO = itemPeriodStatusMapper.selectOne(queryWrapper);

if (Objects.nonNull(itemStatusDO)) {

return new ItemPeriodStageResponse(Boolean.FALSE, "需要保存的记录已存在");

}

itemStatusDO = itemPeriodStatusConverter.requestToEntity(request);

//默认状态

itemStatusDO.setPeriodStatus(ProductStatusEnum.PRE_ON_MARKET.getCode());

itemStatusDO.initCommon();

int count = itemPeriodStatusMapper.insert(itemStatusDO);

if (count <= 0) {

throw new ProductBizException(CommonErrorCodeEnum.SQL_ERROR);

}

return new ItemPeriodStageResponse();

}

...

}

@Repository

public class ItemPeriodStageRepository {

...

//创建生命周期

public Long saveProductStage(ItemPeriodRequest itemPeriodRequest) {

//对象转换

ItemPeriodStageDO itemPeriodStageDO = itemPeriodStageConverter.converterPeriodStageDO(itemPeriodRequest);

//设置商品生命周期状态为新品期

itemPeriodStageDO.setPeriodStage(ProductStageEnum.NEW.getCode());

itemPeriodStageDO.initCommon();

//查询是否存在一条非下架状态的生命周期记录

ItemPeriodStageDO item = itemPeriodStageMapper.selectByItemId(itemPeriodRequest.getItemId());

//如果存在记录,并且其状态不等于已淘汰,则不允许创建新的生命周期记录

if (!Objects.isNull(item) && !item.getPeriodStage().equals(ProductStageEnum.ELIMINATED.getCode())) {

throw new ProductBizException("该商品存在生效中的生命周期数据,不能重复创建");

}

//保存

int count = itemPeriodStageMapper.insert(itemPeriodStageDO);

if (count <= 0) {

throw new ProductBizException(CommonErrorCodeEnum.SQL_ERROR);

}

return itemPeriodStageDO.getId();

}

...

}

4.驱动商品状态流转的接口实现

驱动商品状态流转的接口其实就是修改商品状态的接口:先根据itemId查询该商品的状态记录是否已经存在,如果不存在就抛出异常 + 终⽌后续的流程,如果存在则判断商品状态的修改是否是逆向的。

商品状态的流转只⽀持顺序变更:准备上架 -> 试销上架 -> 上架 -> 准备下架 -> 下架 -> 停售,不⽀持逆向变更。

如果以上条件都满⾜,那么就修改商品的状态。

@DubboService(version = "1.0.0", interfaceClass = ItemPeriodStatusApi.class, retries = 0)

public class ItemPeriodStatusApiImpl implements ItemPeriodStatusApi {

...

//修改商品状态

@Override

public JsonResult updateItemStatus(ItemPeriodStatusRequest request) {

try {

ItemPeriodStageResponse response = itemPeriodStatusService.updateItemStatus(request);

return JsonResult.buildSuccess(response);

} catch (ProductBizException e) {

log.error("biz error: request={}", request, e);

return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());

} catch (Exception e) {

log.error("system error: request={}", request, e);

return JsonResult.buildError(e.getMessage());

}

}

...

}

@Service

public class ItemPeriodStatusServiceImpl implements ItemPeriodStatusService {

...

@Resource

private ItemPeriodStatusRepository itemPeriodStatusRepository;

//修改商品状态

@Override

@Transactional(rollbackFor = Exception.class)

public ItemPeriodStageResponse updateItemStatus(ItemPeriodStatusRequest request) {

//入参检查

checkUpdateParams(request);

//修改状态

return itemPeriodStatusRepository.updateItemStatus(request);

}

...

}

//商品状态资源管理

@Repository

public class ItemPeriodStatusRepository {

...

//更新商品状态

public ItemPeriodStageResponse updateItemStatus(ItemPeriodStatusRequest request) {

LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery();

queryWrapper.eq(Objects.nonNull(request.getItemId()), ItemPeriodStatusDO::getItemId, request.getItemId());

//查询商品状态

ItemPeriodStatusDO itemStatusDO = itemPeriodStatusMapper.selectOne(queryWrapper);

if (Objects.isNull(itemStatusDO)) {

log.warn("修改商品状态过程中,在生命周期系统中未查询到该商品状态,itemId: {}", request.getItemId());

//如果从生命周期系统中没有查到 就从商品系统中查

List data = listItems(request);

//从商品系统中也没有查到

if (CollectionUtils.isEmpty(data)) {

log.error("修改商品状态过程中,在商品系统中未查询到该商品状态,itemId: {}", request.getItemId());

return new ItemPeriodStageResponse(Boolean.FALSE, "选择修改的记录不存在");

}

//构造商品状态DO

itemStatusDO = buildItemPeriodStatusDO(data);

itemPeriodStatusMapper.insert(itemStatusDO);

return new ItemPeriodStageResponse();

}

//禁止逆向操作

Integer status = itemStatusDO.getPeriodStatus();

if (request.getItemStatus() < status) {

return new ItemPeriodStageResponse(Boolean.FALSE, "不能逆向修改商品状态");

}

//修改状态

itemStatusDO.setPeriodStatus(request.getItemStatus());

itemPeriodStatusMapper.updateById(itemStatusDO);

return new ItemPeriodStageResponse();

}

...

}

5.基于大数据的商品考核计算架构

6.定时任务根据考核结果变更商品⽣命周期

(1)定时任务的整体流程

首先定时从考核结果缓冲表中查询出考核状态为通过或者不通过的数据,然后根据这些考核结果数据判断出⽣命周期并将结果保存到考核结果表中,接着发送一条考核结果消息到MQ,消费者会订阅该消息并通知运营操作该商品⽣命周期和状态变更。

(2)定时任务的执行说明

任务开启后,会从考核结果缓冲表中,查询出考核结果通过/不通过的数据。然后根据考核结果以及当前⽣命周期阶段,设置对应的供需状态,而且只有试销期、成熟期的商品才会进⾏考核。最后会删除考核结果缓冲表中的记录,防⽌重复考核。

(3)定时任务的具体实现

@Component

public class CalculateExpriResultSchedule {

@Autowired

private ItemExpriResultBufferRepository itemExpriResultBufferRepository;

@Autowired

private ItemExpriResultRepository itemExpriResultRepository;

@Autowired

private ItemPeriodStageRepository itemPeriodStageRepository;

@Autowired

private ItemPeriodStatusRepository itemPeriodStatusRepository;

@Autowired

private CategoryExpriWhiteRepository categoryExpriWhiteRepository;

@Resource

private ItemExpriResultConverter itemExpriResultConverter;

@Autowired

private DefaultProducer defaultProducer;

//根据考核结果缓冲表中的考核状态和生命周期阶段,写入供需属性

@XxlJob("calculateExpri")

@Transactional(rollbackFor = Exception.class)

public void calculateExpri() {

log.info("开始执行调度任务");

//查询出考核结果数据

List expriResultBufferList = itemExpriResultBufferRepository.getItemExpriResultBufferList();

log.info("查询出考核结果数据:expriResultList: {}", JsonUtil.object2Json(expriResultBufferList));

//依次判断是否要变更生命周期

for (ItemExpriResultBufferDO resultBufferDO : expriResultBufferList) {

//判断是否在白名单范围内

Integer categoryId = resultBufferDO.getCategoryId();

CategoryExpriWhiteDTO expriWhiteResult = categoryExpriWhiteRepository.getExpriWhite(categoryId);

//如果存在白名单记录,并且状态是启用,就不进行考核

if (Objects.nonNull(expriWhiteResult) && expriWhiteResult.getActiveFlag().equals(YesOrNoEnum.YES.getCode())) {

continue;

}

//克隆缓存对象属性到考核结果对象

ItemExpriResultDO itemExpriResultDO = itemExpriResultConverter.converterBuffer(resultBufferDO);

//考核状态

Integer expriResult = resultBufferDO.getMarketExpriResult();

//考核未通过

if (expriResult.equals(MarketExpriResultEnum.FAIL_PASS_EXAMINATION.getCode())) {

//设置淘汰期供需状态

setPreEliminateSupplyStatus(itemExpriResultDO);

//写入考核结果表

itemExpriResultRepository.saveOrUpdateItemExpriResult(itemExpriResultDO);

//考核结果发送MQ

sendPeriodStageChangeMessage(itemExpriResultDO);

//删除缓冲表中的记录

itemExpriResultBufferRepository.deleteById(resultBufferDO.getId());

log.info("商品未通过考核,itemId: {}", itemExpriResultDO.getItemId());

continue;

}

//生命周期ID

Long periodStageId = resultBufferDO.getPeriodStageId();

//查询生命周期ID对应的生命周期阶段

ItemPeriodStageDTO itemPeriodStageDTO = new ItemPeriodStageDTO();

itemPeriodStageDTO.setProductPeriodId(periodStageId);

ItemPeriodStageDO itemPeriodStageDO = itemPeriodStageRepository.queryProductPeriod(itemPeriodStageDTO);

//生命周期阶段

Integer periodStage = itemPeriodStageDO.getPeriodStage();

//试销期

if (periodStage.equals(ProductStageEnum.TRY_SALE.getCode())) {

//设置试销期供需状态

setTrySaleSupplyStatus(itemExpriResultDO);

}

//成熟期

if (periodStage.equals(ProductStageEnum.GROWN.getCode())) {

//设置成熟期供需状态

setGrownSupplyStatus(itemExpriResultDO);

}

//写入考核结果表

itemExpriResultRepository.saveOrUpdateItemExpriResult(itemExpriResultDO);

//考核结果发送MQ

sendPeriodStageChangeMessage(itemExpriResultDO);

//删除缓冲表中的记录

itemExpriResultBufferRepository.deleteById(resultBufferDO.getId());

}

}

//发送考核结果到MQ

private void sendPeriodStageChangeMessage(ItemExpriResultDO itemPeriodStageDO) {

TableDataChangeDTO message = new TableDataChangeDTO();

message.setAction("insert");

message.setTable("item_expri_result");

message.setData(itemPeriodStageDO);

//发送考核结果消息

defaultProducer.sendMessage(RocketMqConstant.INTERIOR_ITEM_EXPRI_RESULT_TOPIC, JSONObject.toJSONString(message), "考核结果通知");

}

//设置淘汰期供需状态

private void setPreEliminateSupplyStatus(ItemExpriResultDO itemExpriResultDO) {

//考核没有通过,不可以采购,不可以补货,但是可以售卖

itemExpriResultDO.setPurchaseStatus(YesOrNoEnum.NO.getCode());

itemExpriResultDO.setReplenishmentStatus(YesOrNoEnum.NO.getCode());

itemExpriResultDO.setSaleStatus(YesOrNoEnum.YES.getCode());

}

//设置试销期供需状态

private void setTrySaleSupplyStatus(ItemExpriResultDO itemExpriResultDO) {

//试销期,考核通过了,正式上架了,可以采购,补货,销售

itemExpriResultDO.setPurchaseStatus(YesOrNoEnum.YES.getCode());

itemExpriResultDO.setReplenishmentStatus(YesOrNoEnum.YES.getCode());

itemExpriResultDO.setSaleStatus(YesOrNoEnum.YES.getCode());

}

//设置成熟期供需状态

private void setGrownSupplyStatus(ItemExpriResultDO itemExpriResultDO) {

String itemId = itemExpriResultDO.getItemId();

//商品状态

Integer itemStatus = itemPeriodStatusRepository.getItemStatus(itemId);

//处于上架状态

if (itemStatus.equals(ProductStatusEnum.PUT_ON_THE_MARKET.getCode())) {

itemExpriResultDO.setPurchaseStatus(YesOrNoEnum.YES.getCode());

itemExpriResultDO.setReplenishmentStatus(YesOrNoEnum.YES.getCode());

itemExpriResultDO.setSaleStatus(YesOrNoEnum.YES.getCode());

return;

}

//不处于上架状态

itemExpriResultDO.setPurchaseStatus(YesOrNoEnum.NO.getCode());

itemExpriResultDO.setReplenishmentStatus(YesOrNoEnum.NO.getCode());

itemExpriResultDO.setSaleStatus(YesOrNoEnum.NO.getCode());

}

}

7.商品生命周期相关接口的代码实现

(1)创建商品⽣命周期状态接⼝

一.接口说明

创建的⽣命周期所处状态默认为0(新品期)。

二.使用场景

创建商品时需调⽤此接⼝⽣成⼀个⽣命周期记录&⽣命周期,⽤于商品创建后的商品不同阶段的考核。

@DubboService(version = "1.0.0", interfaceClass = ItemPeriodStageApi.class, retries = 0)

public class ItemPeriodStageApiImpl implements ItemPeriodStageApi {

@Autowired

private ItemPeriodStageService itemPeriodStageService;

//为商品创建一条生命周期记录

@Override

public JsonResult createItemPeriod(ItemPeriodRequest request) {

try {

ItemPeriodStageResultDTO dto = itemPeriodStageService.saveItemPeriodStage(request);

return JsonResult.buildSuccess(dto);

} catch (ProductBizException e) {

log.error("biz error: request={}", JSON.toJSONString(request), e);

return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());

} catch (Exception e) {

log.error("system error: request={}", JSON.toJSONString(request), e);

return JsonResult.buildError(e.getMessage());

}

}

...

}

@Service

public class ItemPeriodStageServiceImpl implements ItemPeriodStageService {

@Resource

private ItemPeriodStageRepository itemPeriodStageRepository;

//创建商品状态及商品生命周期状态

@Transactional(rollbackFor = Exception.class)

@Override

public ItemPeriodStageResultDTO saveItemPeriodStage(ItemPeriodRequest itemPeriodRequest) {

itemPeriodStageRepository.saveItemPeriodStage(itemPeriodRequest);

return new ItemPeriodStageResultDTO(Boolean.TRUE);

}

...

}

@Repository

public class ItemPeriodStageRepository {

...

//创建生命周期和商品状态

public void saveItemPeriodStage(ItemPeriodRequest itemPeriodRequest) {

//1.创建商品生命周期状态信息

saveProductStage(itemPeriodRequest);

//2.创建商品状态信息

saveProductStatus(itemPeriodRequest);

}

//创建生命周期

public Long saveProductStage(ItemPeriodRequest itemPeriodRequest) {

//对象转换

ItemPeriodStageDO itemPeriodStageDO = itemPeriodStageConverter.converterPeriodStageDO(itemPeriodRequest);

//设置商品生命周期状态为新品期

itemPeriodStageDO.setPeriodStage(ProductStageEnum.NEW.getCode());

itemPeriodStageDO.initCommon();

//查询是否存在一条非下架状态的生命周期记录

ItemPeriodStageDO item = itemPeriodStageMapper.selectByItemId(itemPeriodRequest.getItemId());

//如果存在记录,并且其状态不等于已淘汰,则不允许创建新的生命周期记录

if (!Objects.isNull(item) && !item.getPeriodStage().equals(ProductStageEnum.ELIMINATED.getCode())) {

throw new ProductBizException("该商品存在生效中的生命周期数据,不能重复创建");

}

//保存

int count = itemPeriodStageMapper.insert(itemPeriodStageDO);

if (count <= 0) {

throw new ProductBizException(CommonErrorCodeEnum.SQL_ERROR);

}

return itemPeriodStageDO.getId();

}

//创建商品状态

public void saveProductStatus(ItemPeriodRequest itemPeriodRequest) {

//对象转换

ItemPeriodStatusDO itemPeriodStatusDO = itemPeriodStatusConverter.converterPeriodStageDO(itemPeriodRequest, itemPeriodRequest.getOperateUser());

//设置商品初始状态

itemPeriodStatusDO.setPeriodStatus(ProductStatusEnum.PRE_ON_MARKET.getCode());

//value对象字段初始化

itemPeriodStatusDO.initCommon();

//入库

int count = itemPeriodStatusMapper.insert(itemPeriodStatusDO);

if (count <= 0) {

throw new ProductBizException(CommonErrorCodeEnum.SQL_ERROR);

}

}

...

}

(2)商品⽣命周期查询接⼝

@DubboService(version = "1.0.0", interfaceClass = ItemPeriodStageApi.class, retries = 0)

public class ItemPeriodStageApiImpl implements ItemPeriodStageApi {

@Autowired

private ItemPeriodStageService itemPeriodStageService;

...

//根据商品id查询商品生命周期

@Override

public JsonResult queryProductPeriod(String itemId) {

try {

ItemExpriResultDTO dto = itemPeriodStageService.queryItemExpriResultByItemId(itemId);

return JsonResult.buildSuccess(dto);

} catch (Exception e) {

log.error("system error: parma={}", JSON.toJSONString(itemId), e);

return JsonResult.buildError(e.getMessage());

}

}

...

}

@Service

public class ItemPeriodStageServiceImpl implements ItemPeriodStageService {

...

//根据itemId查询商品生命周期可采可补可售结果

@Override

public ItemExpriResultDTO queryItemExpriResultByItemId(String itemId) {

return itemPeriodStageRepository.queryItemExpriResultByItemId(itemId);

}

...

}

@Repository

public class ItemPeriodStageRepository {

...

//根据itemId查询商品生命周期状态考核结果信息

public ItemExpriResultDTO queryItemExpriResultByItemId(String itemId) {

LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery();

queryWrapper.eq(ItemExpriResultDO::getItemId, itemId);

return itemExpriResultConverter.convertToDTO(

itemExpriResultMapper.selectOne(queryWrapper));

}

...

}

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ItemExpriResultDTO implements Serializable {

//商品itemId

private String itemId;

//商品考核结果

private Integer marketExpriResult;

//商品考核结果描述,如:考核失败原因

private String marketExpriResultDesc;

//是否可采:0:不可采;1:可采;

private Integer purchaseStatus;

//是否可补:0:不可补;1:可补

private Integer replenishmentStatus;

//是否可售:0:不可售;1:可售

private Integer saleStatus;

}

(3)商品⽣命周期修改

一.接口说明

用于修改⽣命周期进⼊下⼀个阶段,例如:新品期 -> 试销期。

二.使用场景

在商品系统修改商品状态时,商品⽣命周期系统会接收到MQ消息,然后根据消息体中的商品状态,同步更新商品对应的⽣命周期状态。例如:商品状态从准备上架 -> 试销上架,那么商品对应的⽣命周期状态需要从新品期 -> 试销期。

@Service

public class ItemPeriodStageServiceImpl implements ItemPeriodStageService {

@Resource

private ItemPeriodStageRepository itemPeriodStageRepository;

...

//更新商品生命周期状态

@Transactional(rollbackFor = Exception.class)

@Override

public ItemPeriodStageResponse updateProductPeriod(ItemPeriodStageDTO itemPeriodStageDTO) {

//1.先查询出这条生命周期状态记录

ItemPeriodStageDO itemPeriodStageDO = itemPeriodStageRepository.queryProductPeriod(itemPeriodStageDTO);

log.info("生命周期状态:{}", JSON.toJSONString(itemPeriodStageDTO));

//2.判断是否存在记录

if (Objects.isNull(itemPeriodStageDO)) {

return new ItemPeriodStageResponse(Boolean.FALSE, "选择修改的记录不存在");

}

log.info("生命周期状态:{}", JSON.toJSONString(itemPeriodStageDO));

//3.判读是否允许修改

if ((itemPeriodStageDTO.getPeriodStage() - itemPeriodStageDO.getPeriodStage()) < 0) {

return new ItemPeriodStageResponse(Boolean.FALSE, "不能逆向修改商品生命周期状态");

}

//4.更新生命周期状态

itemPeriodStageRepository.updateProductPeriod(itemPeriodStageDO, itemPeriodStageDTO);

return new ItemPeriodStageResponse();

}

...

}

@Repository

public class ItemPeriodStageRepository {

...

//查询生命周期

public ItemPeriodStageDO queryProductPeriod(ItemPeriodStageDTO itemPeriodStageDTO) {

//按照修改对象中的内容找

LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery();

queryWrapper.eq(ItemPeriodStageDO::getId, itemPeriodStageDTO.getProductPeriodId());

queryWrapper.eq(Objects.nonNull(itemPeriodStageDTO.getItemId()), ItemPeriodStageDO::getItemId, itemPeriodStageDTO.getItemId());

return itemPeriodStageMapper.selectOne(queryWrapper);

}

//更新商品生命周期

public Boolean updateProductPeriod(ItemPeriodStageDO itemPeriodStageDO, ItemPeriodStageDTO itemPeriodStageDTO) {

itemPeriodStageDO.setPeriodStage(itemPeriodStageDTO.getPeriodStage());

int count = itemPeriodStageMapper.updateById(itemPeriodStageDO);

if (count <= 0) {

throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);

}

return true;

}

...

}

(4)商品⽣命周期考核结果&可采可补修改

使用场景:在商品经历⼀轮商品考核后,定时任务job调⽤该接⼝,用来更新考核结果,可采可补状态等信息。

@Service

public class ItemPeriodStageServiceImpl implements ItemPeriodStageService {

...

//更新商品生命周期考核结果&可采可补可售状态

@Transactional(rollbackFor = Exception.class)

@Override

public Boolean updateProductPeriodMarketExpri(ItemPeriodStageDetailDTO itemPeriodStageDetailDTO) {

return itemPeriodStageRepository.updateProductPeriodMarketExpri(itemPeriodStageDetailDTO);

}

...

}

@Repository

public class ItemPeriodStageRepository {

...

//商品生命周期考核结果&可采可补修改

public Boolean updateProductPeriodMarketExpri(ItemPeriodStageDetailDTO itemPeriodStageDetailDTO) {

LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate();

updateWrapper.eq(ItemExpriResultDO::getPeriodStageId, itemPeriodStageDetailDTO.getProductPeriodId());

updateWrapper.eq(ItemExpriResultDO::getItemId, itemPeriodStageDetailDTO.getItemId());

updateWrapper.set(ItemExpriResultDO::getReplenishmentStatus, itemPeriodStageDetailDTO.getReplenishmentStatus());

updateWrapper.set(ItemExpriResultDO::getPurchaseStatus, itemPeriodStageDetailDTO.getPurchaseStatus());

updateWrapper.set(ItemExpriResultDO::getSaleStatus, itemPeriodStageDetailDTO.getSaleStatus());

updateWrapper.set(ItemExpriResultDO::getMarketExpriResult, itemPeriodStageDetailDTO.getMarketExpriResult());

updateWrapper.set(ItemExpriResultDO::getMarketExpriResultDesc, "后台修改考核结果");

int count = itemExpriResultMapper.update(updateWrapper.getEntity(), updateWrapper);

if (count <= 0) {

throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);

}

return true;

}

...

}

//请求入参

@Data

public class ItemPeriodStageDetailDTO implements Serializable {

//商品生命周期id

private Long productPeriodId;

//商品id

private String itemId;

//0:新品期,1:试销期,2:成熟期,3:淘汰期,4:已淘汰

private Integer periodStage;

//商品生命周期考核结果 考核状态:0:考核中;1:考核通过;2:考核不同过;3:不考核或暂停考核

private Integer marketExpriResult;

//是否可采:0:不可采;1:可采;

private Integer purchaseStatus;

//是否可可补:0:不可补;1:可补;

private Integer replenishmentStatus;

//是否可售:0:不可售;1:可售

private Integer saleStatus;

}

(5)商品⽣命周期考核结果&可采可补查询

使用场景:提供给商品系统,可以按照itemId查询单条商品可采可补可售状态信息。

@Service

public class ItemPeriodStageServiceImpl implements ItemPeriodStageService {

...

//根据itemId查询商品生命周期可采可补可售结果

@Override

public ItemExpriResultDTO queryItemExpriResultByItemId(String itemId) {

return itemPeriodStageRepository.queryItemExpriResultByItemId(itemId);

}

...

}

@Repository

public class ItemPeriodStageRepository {

...

//根据itemId查询商品生命周期状态考核结果信息

public ItemExpriResultDTO queryItemExpriResultByItemId(String itemId) {

LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery();

queryWrapper.eq(ItemExpriResultDO::getItemId, itemId);

return itemExpriResultConverter.convertToDTO(itemExpriResultMapper.selectOne(queryWrapper));

}

...

}

文章转载自:东阳马生架构

原文链接:商品中心—2.商品生命周期和状态的技术文档 - 东阳马生架构 - 博客园

体验地址:JNPF快速开发平台