商品中心—6.商品考核系统的技术文档
大纲
1.基于大数据系统的商品考核数据指标
2.基于商品考核数据指标的商品考核流程
3.商品考核失败后的处理 + 考核流程的设计
4.商品考核系统数据库模型设计
5.商品考核系统核心接口
6.商品生命周期系统的定时考核任务
1.基于大数据系统的商品考核数据指标
(1)试销期考核指标
(2)成熟期(滞销期)考核指标
(1)试销期考核指标
一.货品严重质量问题,主要是供应商的产品质量问题 二.供应商合同诚信问题,是否按合同供货 三.销售额:由运营根据市场⾏情配置 四.出货⽐例:销售量 / 总库存 五.单利润率:(平均单价 - 每单成本) / 每单成本 * 100% 六.退货率:退单量 / 出单量 * 100 七.客诉率:投诉量 / 出单量 * 100 八.损耗率:损耗量 / 出单量 * 100
(2)成熟期(滞销期)考核指标
一.货品严重质量问题 二.供应商合同诚信问题 三.连续考核失败次数 四.销售额:由运营根据市场⾏情配置 五.出货⽐例:销售量 / 总库存 六.单利润率:(平均单价 - 每单成本) / 每单成本 * 100% 七.退货率:退单量 / 出单量 * 100 八.客诉率:投诉量 / 出单量 * 100 九.损耗率:损耗量 / 出单量 * 100
2.基于商品考核数据指标的商品考核流程
(1)试销期考核
(2)成熟期(滞销期)考核
(1)试销期考核
一.考核流程图
二.考核逻辑
其中供货问题或合同问题出现任意⼀个,即可直接定为考核失败。销售额由运营上架货品时配置销售额预期,未达到则直接定为考核失败。出货⽐例、单利润率、退货率、客诉率指标,条件满⾜数量n,当考核指标满⾜n个条件,即可认为考核通过。
例如:商品A考核通过的配置是:⼀个考核周期内,销售额10000元、出货⽐例5%、单利润率2%、退货率5‰、客诉率1‰、损耗率2‰,考核通过需满⾜的指标个数为3。
步骤一:出现货品严重质量问题,考核结果为考核失败 步骤二:出现合同诚信问题,考核结果为考核失败 步骤三:销售额未达到10000,考核结果为考核失败 步骤四:销售额达到10000、出货⽐例10%、单利润率4%、退货率3‰,客诉率4‰,考核结果为考核成功 步骤五:销售额达到10000、出货⽐例10%、单利润率1%、退货率6‰、客诉率0.2‰、考核结果为考核失败 步骤六:销售额未达到10000,直接考核失败
(2)成熟期(滞销期)考核
一.考核流程图
二.考核逻辑
以下考核内容为每轮考核内容,在滞销期考核周期内,会多次按照下述检查指标的步骤进⾏考核。
其中供货问题或合同问题出现任意⼀个,即可直接定为考核失败。销售额由运营上架货品时配置销售额预期,未达到则直接定为考核失败。出货⽐例、单利润率、退货率、客诉率指标,条件满⾜数量n,当考核指标满⾜n个条件,即可认为本轮次考核通过。当本轮次出现考核失败时,若连续考核失败次数设置为m,而当前考核失败次数 + 1刚好等于m,则认为商品考核失败。
如商品A考核通过的配置:销售额10000元、出货⽐例5%、单利润率2%、退货率5‰、客诉率1‰ 。
步骤一:出现货品严重质量问题,本轮次考核结果为考核失败 步骤二:出现合同诚信问题,本轮次考核结果为考核失败 步骤三:⼤考核周期内出现2次考核失败,本周期考核结果为考核失败 步骤四:销售额未达到10000,本轮次考核结果为考核失败,失败轮数+1 步骤五:销售额达到10000,出货⽐例10%、单利润率4%、退货率3‰、客诉率4‰,本轮次考核结果为考核成功 步骤六:销售额达到10000,出货⽐例10%、单利润率1%、退货率6‰、客诉率0.2‰,考核结果为考核失败,失败轮数+1
3.商品考核失败后的处理 + 考核流程的设计
(1)商品考核失败后的后续处理
(2)商品考核的整体业务流程
(3)商品考核流程的设计
(1)商品考核失败后的后续处理
在⼤周期考核失败后,⽴即发送消息通知运营执⾏相应操作,运营可以选择如下三种操作:
操作一:变更考核状态为免考核,过滤掉免考核商品 操作二:变更商品状态为下架,过滤下架商品不再进⾏考核 操作三:当前状态不再进⾏考核,进入下⼀个状态,进⾏正常考核
例如:当前状态是试销期,试销期不再进⾏考核。然后进⼊成熟期,在成熟期再继续进行考核。
场景1:商品状态更改为试销上架,接到MQ消息,商品⽣命周期状态更改为试销期。此时开启试销期考核,并进行试销期考核时间的判定。开始时间:商品进⼊试销期时间,即商品进⼊试销上架状态的时间。考核周期结束时间 = 商品进⼊试销期时间 + 配置的考核周期天数。
场景2:商品进⼊成熟期(滞销期),则试销期考核作废,进⼊滞销期考核。没有开始时间和结束时间的判定,直到考核失败就不再继续考核,同时会同步发送考核失败的消息给运营⼈员。
(2)商品考核的整体业务流程
商品试销上架/上架 -> 商品进⼊考核周期 -> 商品考核开始 -> 商品考核失败推送 -> 运营操作考核失败商品 -> 继续商品考核逻辑。
(3)商品考核流程的设计实现
考核任务业务逻辑:
步骤一:开启考核定时任务
步骤⼆:判断是否存在严重质量问题或合同诚信问题,存在则考核不通过
步骤三:拉取考核统计数据,需要根据过滤掉⽩名单的商品和考核失败的商品。这里的考核失败是指最终考核失败,而不是某轮次的考核失败
步骤四:判断考核类型是试销期还是滞销期
步骤五:判断获取的数据是否达到了考核指标
步骤六:累加考核失败次数,判断是否考核失败,判断是否达到了配置的考核次数,考核失败则异步推送消息⾄运营
步骤七:保存考核结果⾄考核结果缓冲表。如果达到了失败次数,此时考核结果的状态值为考核失败。如果是某⼀轮次考核失败还未达到失败次数,此时状态值是考核中
步骤⼋:启动定时任务每天拉取考核结果缓冲表的数据保存⾄考核结果表,并且设置可采可补可售结果
4.商品考核系统数据库模型设计
(1)商品考核结果缓冲表
(2)品类考核⽩名单表
(3)商品质量问题表
(4)考核指标配置表
(5)商品考核数据统计结果表
(1)商品考核结果缓冲表
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 '考核规则', `fail_times` tinyint(4) default 0 comment '考核周期内连续考核失败的次数', `del_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标记(1-有效,0-删除)', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品考核结果缓冲表';
(2)品类考核⽩名单表
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)商品质量问题表
create table `product_bad_issues_result` ( `id` bigint(40) primary key AUTO_INCREMENT COMMENT '主键', `category_id` int(10) NOT NULL DEFAULT 0 COMMENT '商品品类ID', `item_id` varchar(40) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '商品ID', `non_conforming_product` tinyint(2) not null default 1 comment '货品质量是否合格,0-不合格,1-合格(运营⼿动设置)', `contract_integrity` tinyint(2) not null default 1 comment '供应商合同是否诚信,0-不诚信,1-诚信(运营⼿动设置)', `extra` varchar(255) 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 '更改时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品质量问题表';
(4)考核指标配置表
create table `product_expri_configuration` ( `id` bigint(40) primary key AUTO_INCREMENT COMMENT '主键', `category_id` int(10) NOT NULL DEFAULT 0 COMMENT '商品品类ID', `assessment_type` tinyint(2) not null comment '商品考核类型,1-试销期考核,2-滞销期考核', `assessment_period` tinyint(2) not null comment 'BI系统写⼊', `fail_times` tinyint(4) not null comment '考核周期内允许考核失败的次数', `pass_target` tinyint(2) not null comment '考核通过的指标数量指标,通过的数量达到此值,代表考核通过,未达到代表考核不通过', `sales_volume` int(20) not null comment '商品销售额考核指标,单位为分,达到此值视为考核通过,未达到视为考核不通过', `sold_propotion` int(4) not null comment '商品销售⽐例考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满⾜考核,但不代表考核通过,需要配和其他指标判定是否考核通过)', `order_profit_margin` int(4) not null comment '商品每单利润率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满⾜考核,但不代表考核通过,需要配和其他指标判定是否考核通过)', `return_rate` int(4) not null comment '商品退货⽐率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满⾜考核,但不代表考核通过,需要配和其他指标判定是否考核通过)', `complaint_rate` int(4) not null comment '商品客户投诉⽐率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满⾜考核,但不代表考核通过,需要配和其他指标判定是否考核通过)', `loss_rate` int(4) not null comment '商品损耗⽐率考核指标,单位为千分位,例如:1代表1‰,10代表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 '更改时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='考核指标配置表';
(5)商品考核数据统计结果表
create table `product_experi_data_result` ( `id` bigint(40) primary key AUTO_INCREMENT COMMENT '主键', `category_id` int(10) NOT NULL DEFAULT 0 COMMENT '商品品类ID', `item_id` varchar(40) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '商品ID', `assessment_type` tinyint(2) not null comment '商品考核类型,1-试销期考核,2-滞销期考核', `sales_volume` int(20) not null comment '商品销售额考核指标,单位为分,达到此值视为考核通过,未达到视为考核不通过', `sold_propotion` int(4) not null comment '商品销售⽐例考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满⾜考核,但不代表考核通过,需要配和其他指标判定是否考核通过)', `order_profit_margin` int(4) not null comment '商品每单利润率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满⾜考核,但不代表考核通过,需要配和其他指标判定是否考核通过)', `return_rate` int(4) not null comment '商品退货⽐率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满⾜考核,但不代表考核通过,需要配和其他指标判定是否考核通过)', `complaint_rate` int(4) not null comment '商品客户投诉⽐率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满⾜考核,但不代表考核通过,需要配和其他指标判定是否考核通过)', `loss_rate` int(4) not null comment '商品损耗⽐率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满⾜考核,但不代表考核通过,需要配和其他指标判定是否考核通过)', `is_effective` tinyint(2) not null default 1 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 '更改时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品考核数据统计结果表';
5.商品考核系统核心接口
(1)新增货品质量问题记录
(2)修改货品质量问题记录
(3)查询货品质量问题记录
(4)分⻚查询货品质量问题记录
(5)新增考核配置信息
(6)修改考核配置信息
(7)查询考核配置信息
(8)分⻚查询考核配置信息
(9)新增商品的考核指标数据
(1)新增货品质量问题记录
运营⼈员对存在质量或者诚信问题的货品保存⼀条记录,该记录在商品⽣命周期阶段考核时起作⽤。如果商品存在质量或诚信问题,那么商品该轮次的考核将不通过。
@RestController @RequestMapping("/badIssuesResult") public class ProductBadIssuesResultController { @Autowired private ProductBadIssuesResultService productBadIssuesResultService; //新增货品质量问题记录 @RequestMapping("/saveProductBadIssuesResult") public JsonResult<Long> saveProductBadIssuesResult(ProductBadIssuesResultRequest request) { try { Long count = productBadIssuesResultService.saveProductBadIssuesResult(request); return JsonResult.buildSuccess(count); } catch (Exception e) { log.error("system error: request={}", JSON.toJSONString(request), e); return JsonResult.buildError(e.getMessage()); } } ... } //货品质量问题请求入参 @Data @AllArgsConstructor @NoArgsConstructor public class ProductBadIssuesResultRequest extends PageRequest implements Serializable { //主键 private Long id; //末级品类ID private Integer categoryId; //ITEM ID private String itemId; //货品质量是否合格,0:不合格,1:合格(运营手动设置) private Integer nonConformingProduct; //供应商合同是否诚信,0:不诚信,1:诚信(运营手动设置) private Integer contractIntegrity; } @Service public class ProductBadIssuesResultServiceImpl implements ProductBadIssuesResultService { @Autowired private ProductExpriRepository productExpriRepository; //新增货品质量问题记录 @Override @Transactional(rollbackFor = Exception.class) public Long saveProductBadIssuesResult(ProductBadIssuesResultRequest request) { //参数检查 checkInsertParams(request); return productExpriRepository.saveProductBadIssuesResult(request); } ... } //商品考核资源管理 @Repository public class ProductExpriRepository { ... //新增货品质量问题记录 public Long saveProductBadIssuesResult(ProductBadIssuesResultRequest request) { LambdaQueryWrapper<ProductBadIssuesResultDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(ProductBadIssuesResultDO::getItemId, request.getItemId()); ProductBadIssuesResultDO productBadIssuesResultDO = productBadIssuesResultMapper.selectOne(queryWrapper); if (Objects.nonNull(productBadIssuesResultDO)) { throw new ProductBizException("该商品已存在质量问题记录,不能重复创建"); } //保存 productBadIssuesResultDO = productBadIssuesResultConverter.requestToEntity(request); productBadIssuesResultDO.initCommon(); int count = productBadIssuesResultMapper.insert(productBadIssuesResultDO); if (count <= 0) { throw new ProductBizException(CommonErrorCodeEnum.SQL_ERROR); } return productBadIssuesResultDO.getId(); } ... }
(2)修改货品质量问题记录
可以根据itemId或者ID修改货品质量问题。
@RestController @RequestMapping("/badIssuesResult") public class ProductBadIssuesResultController { @Autowired private ProductBadIssuesResultService productBadIssuesResultService; //修改货品质量问题记录 @RequestMapping("/updateProductBadIssuesResult") public JsonResult<Boolean> updateProductBadIssuesResult(ProductBadIssuesResultRequest request) { try { Boolean result = productBadIssuesResultService.updateProductBadIssuesResult(request); return JsonResult.buildSuccess(result); } catch (Exception e) { log.error("system error: request={}", JSON.toJSONString(request), e); return JsonResult.buildError(e.getMessage()); } } ... } //货品质量问题请求入参 @Data @AllArgsConstructor @NoArgsConstructor public class ProductBadIssuesResultRequest extends PageRequest implements Serializable { //主键 private Long id; //末级品类ID private Integer categoryId; //ITEM ID private String itemId; //货品质量是否合格,0:不合格,1:合格(运营手动设置) private Integer nonConformingProduct; //供应商合同是否诚信,0:不诚信,1:诚信(运营手动设置) private Integer contractIntegrity; } @Service public class ProductBadIssuesResultServiceImpl implements ProductBadIssuesResultService { @Autowired private ProductExpriRepository productExpriRepository; //修改货品质量问题记录 @Override @Transactional(rollbackFor = Exception.class) public Boolean updateProductBadIssuesResult(ProductBadIssuesResultRequest request) { //参数检查 checkUpdateParams(request); return productExpriRepository.updateProductBadIssuesResult(request); } ... } //商品考核资源管理 @Repository public class ProductExpriRepository { ... //修改货品质量问题记录 public Boolean updateProductBadIssuesResult(ProductBadIssuesResultRequest request) { LambdaQueryWrapper<ProductBadIssuesResultDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(Objects.nonNull(request.getId()), ProductBadIssuesResultDO::getId, request.getId()); queryWrapper.eq(Objects.nonNull(request.getItemId()), ProductBadIssuesResultDO::getItemId, request.getItemId()); ProductBadIssuesResultDO selectResult = productBadIssuesResultMapper.selectOne(queryWrapper); if (Objects.isNull(selectResult)) { throw new ProductBizException("该商品质量问题记录不存在,无法修改"); } ProductBadIssuesResultDO issuesResultDO = productBadIssuesResultConverter.requestToEntity(request); issuesResultDO.initCommon(); int count = productBadIssuesResultMapper.updateById(issuesResultDO); if (count <= 0) { throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL); } return true; } ... }
(3)查询货品质量问题记录
根据ID或者itemId查询货品的质量问题记录。
@RestController @RequestMapping("/badIssuesResult") public class ProductBadIssuesResultController { @Autowired private ProductBadIssuesResultService productBadIssuesResultService; //查询货品质量问题记录 @RequestMapping("/getProductBadIssuesResult") public JsonResult<ProductBadIssuesResultDTO> getProductBadIssuesResult(ProductBadIssuesResultRequest request) { try { ProductBadIssuesResultDTO result = productBadIssuesResultService.getProductBadIssuesResult(request); return JsonResult.buildSuccess(result); } catch (Exception e) { log.error("system error: request={}", JSON.toJSONString(request), e); return JsonResult.buildError(e.getMessage()); } } ... } //货品质量问题请求入参 @Data @AllArgsConstructor @NoArgsConstructor public class ProductBadIssuesResultRequest extends PageRequest implements Serializable { //主键 private Long id; //末级品类ID private Integer categoryId; //ITEM ID private String itemId; //货品质量是否合格,0:不合格,1:合格(运营手动设置) private Integer nonConformingProduct; //供应商合同是否诚信,0:不诚信,1:诚信(运营手动设置) private Integer contractIntegrity; } @Service public class ProductBadIssuesResultServiceImpl implements ProductBadIssuesResultService { @Autowired private ProductExpriRepository productExpriRepository; //查询货品质量问题记录 @Override public ProductBadIssuesResultDTO getProductBadIssuesResult(ProductBadIssuesResultRequest request) { return productExpriRepository.getProductBadIssuesResult(request); } ... } //商品考核资源管理 @Repository public class ProductExpriRepository { ... //查询货品质量问题记录 public ProductBadIssuesResultDTO getProductBadIssuesResult(ProductBadIssuesResultRequest request) { LambdaQueryWrapper<ProductBadIssuesResultDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(ProductBadIssuesResultDO::getId, request.getId()); queryWrapper.eq(Objects.nonNull(request.getItemId()), ProductBadIssuesResultDO::getItemId, request.getItemId()); return productBadIssuesResultConverter.entityToDTO(productBadIssuesResultMapper.selectOne(queryWrapper)); } ... }
(4)分⻚查询货品质量问题记录
批量查询货品质量问题记录,可以根据商品ID、品类ID、是否存在质量问题、是否存在诚信问题,这四个字段进⾏查询。
@RestController @RequestMapping("/badIssuesResult") public class ProductBadIssuesResultController { @Autowired private ProductBadIssuesResultService productBadIssuesResultService; //分页查询货品质量问题记录 @RequestMapping("/queryProductBadIssuesResultByPage") public JsonResult<PageResult<ProductBadIssuesResultDTO>> queryProductBadIssuesResultByPage(ProductBadIssuesResultRequest request) { try { PageResult<ProductBadIssuesResultDTO> result = productBadIssuesResultService.queryProductBadIssuesResultByPage(request); return JsonResult.buildSuccess(result); } catch (Exception e) { log.error("system error: request={}", JSON.toJSONString(request), e); return JsonResult.buildError(e.getMessage()); } } ... } //货品质量问题请求入参 @Data @AllArgsConstructor @NoArgsConstructor public class ProductBadIssuesResultRequest extends PageRequest implements Serializable { //主键 private Long id; //末级品类ID private Integer categoryId; //ITEM ID private String itemId; //货品质量是否合格,0:不合格,1:合格(运营手动设置) private Integer nonConformingProduct; //供应商合同是否诚信,0:不诚信,1:诚信(运营手动设置) private Integer contractIntegrity; } @Service public class ProductBadIssuesResultServiceImpl implements ProductBadIssuesResultService { @Autowired private ProductExpriRepository productExpriRepository; //分页查询货品质量问题记录 @Override public PageResult<ProductBadIssuesResultDTO> queryProductBadIssuesResultByPage(ProductBadIssuesResultRequest request) { return productExpriRepository.queryProductBadIssuesResultByPage(request); } ... } //商品考核资源管理 @Repository public class ProductExpriRepository { ... //分页查询货品质量问题记录 public PageResult<ProductBadIssuesResultDTO> queryProductBadIssuesResultByPage(ProductBadIssuesResultRequest request) { LambdaQueryWrapper<ProductBadIssuesResultDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(Objects.nonNull(request.getCategoryId()), ProductBadIssuesResultDO::getCategoryId, request.getCategoryId()); queryWrapper.eq(Objects.nonNull(request.getItemId()), ProductBadIssuesResultDO::getItemId, request.getItemId()); queryWrapper.eq(Objects.nonNull(request.getNonConformingProduct()), ProductBadIssuesResultDO::getNonConformingProduct, request.getNonConformingProduct()); queryWrapper.eq(Objects.nonNull(request.getContractIntegrity()), ProductBadIssuesResultDO::getContractIntegrity, request.getContractIntegrity()); Page<ProductBadIssuesResultDO> page = new Page<>(request.getPageNum(), request.getPageSize()); Page<ProductBadIssuesResultDO> issuesResultPage = productBadIssuesResultMapper.selectPage(page, queryWrapper); List<ProductBadIssuesResultDTO> productBadIssuesResultDTOList = productBadIssuesResultConverter.listEntityToDTO(issuesResultPage.getRecords()); return new PageResult<>(productBadIssuesResultDTOList); } ... }
(5)新增考核配置信息
以品类为粒度,为该品类保存⼀条考核指标配置。包括考核类型、考核周期、允许连续失败次数、销售额等属性。在执⾏考核任务时,根据商品所属品类的考核指标配置,看考核是否通过。考核类型即包括:试销期考核和滞销期考核。
@RestController @RequestMapping("/badIssuesResult") public class ProductExpriConfigurationController { @Autowired private ProductExpriConfigurationService productExpriConfigurationService; //新增考核配置信息 @RequestMapping("/saveProductExpriConfiguration") public JsonResult<Long> saveProductExpriConfiguration(ProductExpriConfigurationRequest request) { try { Long count = productExpriConfigurationService.saveProductExpriConfiguration(request); return JsonResult.buildSuccess(count); } catch (Exception e) { log.error("system error: request={}", JSON.toJSONString(request), e); return JsonResult.buildError(e.getMessage()); } } ... } //考核指标配置请求入参 @Data @AllArgsConstructor @NoArgsConstructor public class ProductExpriConfigurationRequest extends PageRequest implements Serializable { //主键ID private Long id; //商品品类ID private Integer categoryId; //商品考核类型,1:试销期考核,2滞销期考核 private Integer assessmentType; //考核周期为多少天:代表大数据按照多少天的维度给出统计数据 private Integer assessmentPeriod; //考核周期内允许考核失败的次数 private Integer failTimes; //考核通过的指标数量指标,通过的数量达到此值,代表考核通过,未达到,代表考核不通过 private Integer passTarget; //商品销售额考核指标,单位为分,达到此值视为考核通过,未达到视为考核不通过 private Integer salesVolume; //商品销售比例考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer soldPropotion; //商品每单利润率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer orderProfitMargin; //商品退货比率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer returnRate; //商品客户投诉比率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer complaintRate; //商品损耗比率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer lossRate; } @Service public class ProductExpriConfigurationServiceImpl implements ProductExpriConfigurationService { @Autowired private ProductExpriRepository productExpriRepository; //新增考核配置信息 @Override @Transactional(rollbackFor = Exception.class) public Long saveProductExpriConfiguration(ProductExpriConfigurationRequest request) { //参数检查 checkInsertParams(request); return productExpriRepository.saveProductExpriConfiguration(request); } ... } @Repository public class ProductExpriRepository { ... //新增考核配置信息 public Long saveProductExpriConfiguration(ProductExpriConfigurationRequest request) { LambdaQueryWrapper<ProductExpriConfigurationDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(ProductExpriConfigurationDO::getCategoryId, request.getCategoryId()); ProductExpriConfigurationDO expriConfigurationDO = productExpriConfigurationMapper.selectOne(queryWrapper); if (Objects.nonNull(expriConfigurationDO)) { throw new ProductBizException("该品类已存在考核配置记录,不能重复创建"); } //保存 expriConfigurationDO = productExpriConfigurationConverter.requestToEntity(request); expriConfigurationDO.initCommon(); int count = productExpriConfigurationMapper.insert(expriConfigurationDO); if (count <= 0) { throw new ProductBizException(CommonErrorCodeEnum.SQL_ERROR); } return expriConfigurationDO.getId(); } ... }
(6)修改考核配置信息
对某⼀项考核指标进⾏修改。
@RestController @RequestMapping("/badIssuesResult") public class ProductExpriConfigurationController { @Autowired private ProductExpriConfigurationService productExpriConfigurationService; //修改考核配置信息 @RequestMapping("/updateProductExpriConfiguration") public JsonResult<Boolean> updateProductExpriConfiguration(ProductExpriConfigurationRequest request) { try { Boolean result = productExpriConfigurationService.updateProductExpriConfiguration(request); return JsonResult.buildSuccess(result); } catch (Exception e) { log.error("system error: request={}", JSON.toJSONString(request), e); return JsonResult.buildError(e.getMessage()); } } ... } @Service public class ProductExpriConfigurationServiceImpl implements ProductExpriConfigurationService { @Autowired private ProductExpriRepository productExpriRepository; //修改考核配置信息 @Override @Transactional(rollbackFor = Exception.class) public Boolean updateProductExpriConfiguration(ProductExpriConfigurationRequest request) { //参数检查 checkUpdateParams(request); return productExpriRepository.updateProductExpriConfiguration(request); } ... } @Repository public class ProductExpriRepository { ... //修改考核配置信息 public Boolean updateProductExpriConfiguration(ProductExpriConfigurationRequest request) { LambdaQueryWrapper<ProductExpriConfigurationDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(Objects.nonNull(request.getId()), ProductExpriConfigurationDO::getId, request.getId()); queryWrapper.eq(Objects.nonNull(request.getCategoryId()), ProductExpriConfigurationDO::getCategoryId, request.getCategoryId()); ProductExpriConfigurationDO selectResult = productExpriConfigurationMapper.selectOne(queryWrapper); if (Objects.isNull(selectResult)) { throw new ProductBizException("该品类下的考核配置信息不存在,无法修改"); } ProductExpriConfigurationDO expriConfigurationDO = productExpriConfigurationConverter.requestToEntity(request); expriConfigurationDO.initCommon(); int count = productExpriConfigurationMapper.updateById(expriConfigurationDO); if (count <= 0) { throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL); } return true; } ... }
(7)查询考核配置信息
根据ID或者品类ID查询某⼀项考核配置信息。
@RestController @RequestMapping("/badIssuesResult") public class ProductExpriConfigurationController { @Autowired private ProductExpriConfigurationService productExpriConfigurationService; //查询考核配置信息 @RequestMapping("/getProductExpriConfiguration") public JsonResult<ProductExpriConfigurationDTO> getProductExpriConfiguration(ProductExpriConfigurationRequest request) { try { ProductExpriConfigurationDTO result = productExpriConfigurationService.getProductExpriConfiguration(request); return JsonResult.buildSuccess(result); } catch (Exception e) { log.error("system error: request={}", JSON.toJSONString(request), e); return JsonResult.buildError(e.getMessage()); } } ... } @Service public class ProductExpriConfigurationServiceImpl implements ProductExpriConfigurationService { @Autowired private ProductExpriRepository productExpriRepository; //查询考核配置信息 @Override public ProductExpriConfigurationDTO getProductExpriConfiguration(ProductExpriConfigurationRequest request) { return productExpriRepository.getProductExpriConfiguration(request); } ... } @Repository public class ProductExpriRepository { ... //查询考核配置信息 public ProductExpriConfigurationDTO getProductExpriConfiguration(ProductExpriConfigurationRequest request) { LambdaQueryWrapper<ProductExpriConfigurationDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(Objects.nonNull(request.getId()), ProductExpriConfigurationDO::getId, request.getId()); queryWrapper.eq(Objects.nonNull(request.getCategoryId()), ProductExpriConfigurationDO::getCategoryId, request.getCategoryId()); return productExpriConfigurationConverter.entityToDTO(productExpriConfigurationMapper.selectOne(queryWrapper)); } ... }
(8)分⻚查询考核配置信息
批量查询考核配置信息,可以根据品类ID、商品考核类型、允许连续失败次数3个字段进⾏查询。
@RestController @RequestMapping("/badIssuesResult") public class ProductExpriConfigurationController { @Autowired private ProductExpriConfigurationService productExpriConfigurationService; //分页查询考核配置信息 @RequestMapping("/queryProductExpriConfigurationByPage") public JsonResult<PageResult<ProductExpriConfigurationDTO>> queryProductExpriConfigurationByPage(ProductExpriConfigurationRequest request) { try { PageResult<ProductExpriConfigurationDTO> result = productExpriConfigurationService.queryProductExpriConfigurationByPage(request); return JsonResult.buildSuccess(result); } catch (Exception e) { log.error("system error: request={}", JSON.toJSONString(request), e); return JsonResult.buildError(e.getMessage()); } } ... } @Service public class ProductExpriConfigurationServiceImpl implements ProductExpriConfigurationService { @Autowired private ProductExpriRepository productExpriRepository; //分页查询考核配置信息 @Override public PageResult<ProductExpriConfigurationDTO> queryProductExpriConfigurationByPage(ProductExpriConfigurationRequest request) { return productExpriRepository.queryProductExpriConfigurationByPage(request); } ... } @Repository public class ProductExpriRepository { ... //分页查询考核配置信息 public PageResult<ProductExpriConfigurationDTO> queryProductExpriConfigurationByPage(ProductExpriConfigurationRequest request) { LambdaQueryWrapper<ProductExpriConfigurationDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(Objects.nonNull(request.getCategoryId()), ProductExpriConfigurationDO::getCategoryId, request.getCategoryId()); queryWrapper.eq(Objects.nonNull(request.getAssessmentType()), ProductExpriConfigurationDO::getAssessmentType, request.getAssessmentType()); queryWrapper.eq(Objects.nonNull(request.getFailTimes()), ProductExpriConfigurationDO::getFailTimes, request.getFailTimes()); Page<ProductExpriConfigurationDO> page = new Page<>(request.getPageNum(), request.getPageSize()); Page<ProductExpriConfigurationDO> configurationPage = productExpriConfigurationMapper.selectPage(page, queryWrapper); List<ProductExpriConfigurationDTO> records = productExpriConfigurationConverter.listEntityToDTO(configurationPage.getRecords()); return new PageResult<>(records); } ... }
(9)新增商品的考核指标数据
⼤数据系统计算出商品的考核指标数据后,调⽤⽣命周期服务提供的接⼝,将考核指标数据保存到考核指标数据表中。⼤数据统计的指标结果,⼀般是单独存在⼤数据的数据库中。
@Service public class ProductExpriDataResultServiceImpl implements ProductExpriDataResultService { @Autowired private ProductExpriRepository productExpriRepository; //新增考核指标数据 @Override public Long saveProductExpriDataResult(ProductExpriDataResultRequest request) { checkParams(request); return productExpriRepository.saveProductExpriDataResult(request); } ... } //考核指标数据请求入参 @Data @AllArgsConstructor @NoArgsConstructor public class ProductExpriDataResultRequest extends PageRequest implements Serializable { //主键 private Long id; //商品品类ID private Integer categoryId; //商品ID private String itemId; //商品考核类型,1:试销期考核,2滞销期考核 private Integer assessmentType; //商品销售额考核指标,单位为分,达到此值视为考核通过,未达到视为考核不通过 private Integer salesVolume; //商品销售比例考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer soldPropotion; //商品每单利润率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer orderProfitMargin; //商品退货比率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer returnRate; //商品客户投诉比率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer complaintRate; //商品损耗比率考核指标,单位为千分位,例如:1代表1‰,10代表1%(达到此指标代表该指标已满足考核,但不代表考核通过,需要配和其他指标判定是否考核通过) private Integer lossRate; //考核数据是否生效,0:失效,1:生效 private Integer isEffective; } @Repository public class ProductExpriRepository { ... //新增考核指标数据 public Long saveProductExpriDataResult(ProductExpriDataResultRequest request) { //保存 ProductExpriDataResultDO productExpriDataResultDO = productExpriDataResultConverter.requestToEntity(request); productExpriDataResultDO.initCommon(); int count = productExpriDataResultMapper.insert(productExpriDataResultDO); if (count <= 0) { throw new ProductBizException(CommonErrorCodeEnum.SQL_ERROR); } return productExpriDataResultDO.getId(); } ... }
6.商品生命周期系统的定时考核任务
(1)定时任务处理考核指标数据的设计
(2)定时任务处理考核指标数据的实现
(3)定时任务基于考核结果设置商品可采可补可售
(4)考核结果数据的Excel导出
(1)定时任务处理考核指标数据的设计
定时任务会根据考核指标数据,计算出商品的考核结果。定时查询出考核指标数据,根据商品质量、诚信、销量、利润等数据,分别对试销期和滞销期商品进⾏考核,并将考核结果写⼊考核结果缓冲表。
在⼀轮考核任务中,对于配置了⽩名单的品类商品,可以免除本轮次考核,免除本轮次考核的考核状态为免考核。
质量和诚信问题,具有⼀票否决权,⼆者任⼀不满⾜,则本轮考核不通过。滞销期考核也直接不通过,考核状态为考核不通过。
销售⽐率、利润率、退货⽐率、客诉率、损耗率五个考核指标,通过率未超过考核指标配置的考核通过率,则本轮考核不通过。此时状态是考核中,需⽐较连续失败次数是否超过阈值,来决定考核不通过。
对于试销期商品,本轮考核不通过则该商品直接考核不通过。对于滞销期商品,本轮考核不通过需⽐较考核指标配置的连续失败次数,只有达到了配置的连续失败次数,才会考核不通过。如果没有达到连续失败次数,则对该数值进⾏累加,然后等待下⼀轮考核。
当本轮次考核结束,会将计算得到的考核结果写⼊考核结果缓冲表。同时让该考核指标数据失效,避免下⼀轮重复拉取该考核指标数据。
(2)定时任务处理考核指标数据的实现
一.任务执⾏说明
考核任务的周期在xxljob的管理界⾯中指定,并且调度任务时需传⼊参数。参数要指定该任务的考核类型,是试销期考核还是滞销期考核,不传⼊参数指定考核类型则会抛出异常。
二.任务流程图
三.任务具体实现
步骤一:查询大数据系统计算出来的商品考核指标数据 步骤二:考核指标数据对应的商品的生命周期与考核类型对比 步骤三:过滤出在白名单中免考核的考核指标数据 步骤四:过滤出有质量或者诚信问题的商品指标数据 步骤五:分页查询出考核配置数据 步骤六:对商品指标数据进行考核以及划分结果 步骤七:商品考核数据分多种类别写入结果缓冲表 步骤八:将参与本轮考核计算的考核指标数据都设置为失效状态
//生命周期阶段考核 @Component public class CalculateExpriResultSchedule { //考核结果资源管理 @Autowired private ItemExpriResultRepository itemExpriResultRepository; //生命周期资源管理 @Autowired private ItemPeriodStageRepository itemPeriodStageRepository; //考核白名单资源管理 @Autowired private CategoryExpriWhiteRepository categoryExpriWhiteRepository; //商品考核资源管理 @Autowired private ProductExpriRepository productExpriRepository; //考核结果对象克隆 @Resource private ItemExpriResultConverter itemExpriResultConverter; //MQ producer @Autowired private DefaultProducer defaultProducer; //定时任务:根据商品的质量和诚信问题、销量、客诉率等数据,对试销期、成熟期商品进行考核,并将考核结果写入考核结果缓冲表 //一般情况下:每天凌晨3点钟执行 @XxlJob("assessing") @Transactional(rollbackFor = Exception.class) public void assessing() { //1.获取考核类型参数(试销期1,成熟期2) Integer assessmentType = getAssessmentType(); //2.查询大数据系统计算出来的商品考核指标数据 List<ProductExpriDataResultDTO> totalExpriDataList = productExpriRepository.listExpriDataResultByPage(assessmentType); //3.考核指标数据对应的itemIdList List<String> itemIdList = getItemIdList(totalExpriDataList); //4.考核指标数据对应的categoryIdList List<Integer> categoryIdList = getCategoryIdList(totalExpriDataList); //5.查询生命周期阶段 Map<String, ItemPeriodStageDTO> itemPeriodStageMap = listItemPeriodStageByPage(assessmentType, itemIdList); //6.过滤掉商品所处生命周期阶段和当前考核类型不匹配的数据,并返回生命周期阶段Map totalExpriDataList = filterPeriodConsistencyToMap(totalExpriDataList, itemPeriodStageMap); //7.过滤出在白名单中免考核的考核指标数据 List<ProductExpriDataResultDTO> nonAssessmentExpriDataList = filterNonAssessmentToList(totalExpriDataList, categoryIdList); //8.分页查询出考核配置数据 Map<Integer, ProductExpriConfigurationDTO> expriConfigurationMap = listExpriConfigurationByPage(categoryIdList, assessmentType); //9.批量查质量或诚信问题数据,并过滤出存在质量或诚信问题,考核不通过的数据 List<ProductExpriDataResultDTO> assessFailureList = filterConformingIntegrityRecordToList(totalExpriDataList, itemIdList); //10.比较销售额和销售率、客诉率等考核指标项,将考核数据分别划分到:考核不通过、考核通过、考核中三个集合中 List<ProductExpriDataResultDTO> assessingList = filterSalesVolumePassTargetToList(totalExpriDataList, assessFailureList, expriConfigurationMap, assessmentType); //11.将考核不通过、考核中、考核通过,免考核的考核指标数据,批量写入到考核结果缓冲表 batchInsertAssessmentResult(itemIdList, assessFailureList, assessingList, totalExpriDataList, nonAssessmentExpriDataList, itemPeriodStageMap); //12.将参与本轮考核计算的考核指标数据都设置为失效状态 batchInvalidExpriData(itemIdList); } ... }
步骤一:查询大数据系统计算的商品考核指标数据
//商品考核资源管理 @Repository public class ProductExpriRepository { ... //分页查询某考核类型下所有生效的商品考核指标数据 public List<ProductExpriDataResultDTO> listExpriDataResultByPage(Integer assessmentType) { //构造查询条件 LambdaQueryWrapper<ProductExpriDataResultDO> queryWrapper = Wrappers.lambdaQuery(); //是否生效 queryWrapper.eq(ProductExpriDataResultDO::getIsEffective, YesOrNoEnum.YES.getCode()); //是否删除 queryWrapper.eq(ProductExpriDataResultDO::getDelFlag, YesOrNoEnum.YES.getCode()); //考核类型:试销期考核/成熟期考核 queryWrapper.eq(ProductExpriDataResultDO::getAssessmentType, assessmentType); //分页查询存放数据的总集合 List<ProductExpriDataResultDO> results = new ArrayList<>(); int pageNum = 1; //设置每次查询的数据量,最大为200 int pageSize = ProductConstants.QUERY_ITEM_MAX_COUNT; Page<ProductExpriDataResultDO> page = new Page<>(pageNum, pageSize); //查询第一页数据 Page<ProductExpriDataResultDO> pageResult = productExpriDataResultMapper.selectPage(page, queryWrapper); //判断是否还有数据,还有数据则页码 + 1继续执行分页查询 List<ProductExpriDataResultDO> batchResult = pageResult.getRecords(); try { while (pageNum <= pageResult.getTotal()) { results.addAll(batchResult); pageNum += 1; page.setCurrent(pageNum); pageResult = productExpriDataResultMapper.selectPage(page, queryWrapper); batchResult = pageResult.getRecords(); //每次循环获取数据后,休眠20ms,避免对数据库造成太大压力 Thread.sleep(20); } } catch (InterruptedException e) { throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL); } //最后一组数据也放入结果集中 results.addAll(page.getRecords()); //转为DTO List<ProductExpriDataResultDTO> productExpriDataResultList = productExpriDataResultConverter.listEntityToDTO(results); return Objects.isNull(productExpriDataResultList) ? Lists.newArrayList() : productExpriDataResultList; } ... }
步骤二:考核指标数据对应的商品的生命周期与考核类型对比
@Component public class CalculateExpriResultSchedule { ... //过滤掉所处的生命周期阶段和当前考核类型不匹配的数据 //@param totalExpriDataList 考核指标数据集合 //@param itemPeriodStageMap 生命周期Map,key为itemId //@return 生命周期阶段和考核类型匹配的考核指标数据集合 private List<ProductExpriDataResultDTO> filterPeriodConsistencyToMap(List<ProductExpriDataResultDTO> totalExpriDataList, Map<String, ItemPeriodStageDTO> itemPeriodStageMap) { //过滤考核指标数据 totalExpriDataList = totalExpriDataList.stream() .filter(productExpriDataResultDTO -> Objects.nonNull(itemPeriodStageMap.get(productExpriDataResultDTO.getItemId()))) .collect(Collectors.toList()); return totalExpriDataList; } ... }
步骤三:过滤出在白名单中免考核的考核指标数据
@Component public class CalculateExpriResultSchedule { ... //过滤出在白名单中的考核指标数据 //@param totalExpriDataList 考核指标数据集合 //@return 在白名单中的考核指标数据集合 private List<ProductExpriDataResultDTO> filterNonAssessmentToList(List<ProductExpriDataResultDTO> totalExpriDataList, List<Integer> categoryIdList) { //1.分页查询出考核指标数据中对应的白名单所属的categoryIdList List<Integer> whiteCategoryIdList = categoryExpriWhiteRepository.queryExpriWhiteByCategoryIdList(categoryIdList); //2.过滤出在白名单范围内的考核指标数据 List<ProductExpriDataResultDTO> nonAssessmentExpriDataList = totalExpriDataList.stream() .filter(productExpriDataResultDTO -> whiteCategoryIdList.contains(productExpriDataResultDTO.getCategoryId())) .collect(Collectors.toList()); //3.剔除掉在白名单范围内的考核指标数据 totalExpriDataList.removeAll(nonAssessmentExpriDataList); //4.返回在白名单内的考核指标数据 return nonAssessmentExpriDataList; } ... } //考核白名单资源管理 @Repository public class CategoryExpriWhiteRepository { ... //分页查询出categoryIdList对应的白名单集合 //@param categoryIdList 品类ID集合 //@return 处于白名单范围内的categoryIdList public List<Integer> queryExpriWhiteByCategoryIdList(List<Integer> categoryIdList) { //处于白名单范围内的categoryIdList List<Integer> whiteCategoryIdList = new ArrayList<>(); //一次最大查询200个数据,多个分页查询,这里做数据切割 List<List<Integer>> splitList = DataCuttingUtil.dataCuttingString(categoryIdList, ProductConstants.QUERY_ITEM_MAX_COUNT); for (List<Integer> categoryIds : splitList) { LambdaQueryWrapper<CategoryExpriWhiteDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.in(CategoryExpriWhiteDO::getCategoryId, categoryIds); queryWrapper.eq(CategoryExpriWhiteDO::getDelFlag, YesOrNoEnum.YES.getCode()); queryWrapper.eq(CategoryExpriWhiteDO::getActiveFlag, YesOrNoEnum.YES.getCode()); //分批次查询出categoryIdList对应的白名单集合 List<CategoryExpriWhiteDO> categoryResultList = categoryExpriWhiteMapper.selectList(queryWrapper); if (!CollectionUtils.isEmpty(categoryResultList)) { whiteCategoryIdList.addAll(categoryResultList.stream().map(CategoryExpriWhiteDO::getCategoryId).collect(Collectors.toList())); } } return whiteCategoryIdList; } ... }
步骤四:过滤出有质量或者诚信问题的商品指标数据
@Component public class CalculateExpriResultSchedule { ... //批量查询出质量或诚信问题数据集合,并对比考核数据,将对应的考核数据添加到考核不通过的集合中 //@param totalExpriDataList 考核结果数据 //@param itemIdList 商品ID集合 //@return 考核不通过考核指标数据集合 private List<ProductExpriDataResultDTO> filterConformingIntegrityRecordToList(List<ProductExpriDataResultDTO> totalExpriDataList, List<String> itemIdList) { //批量查询出质量诚信问题数据集合 List<ProductBadIssuesResultDTO> productBadIssuesResultList = productExpriRepository.queryProductBadIssuesResultByItemIdList(itemIdList); //List转为Map,itemId作为key Map<String, ProductBadIssuesResultDTO> productBadIssuesResultMap = productBadIssuesResultList.stream() .collect(Collectors.toMap(ProductBadIssuesResultDTO::getItemId, productBadIssuesResult -> productBadIssuesResult, (oldKey, newKey) -> oldKey)); //过滤出存在质量问题的考核指标数据 List<ProductExpriDataResultDTO> assessFailureList = totalExpriDataList.stream().filter(productExpriDataResultDTO -> { //根据考核结果数据的itemId获取质量、诚信问题记录 ProductBadIssuesResultDTO productBadIssuesResult = productBadIssuesResultMap.get(productExpriDataResultDTO.getItemId()); //如果该考核结果数据存在质量诚信问题,就保留,否则就过滤掉 if (productBadIssuesResult == null) { return false; } if (productBadIssuesResult.getContractIntegrity().equals(YesOrNoEnum.YES.getCode()) && productBadIssuesResult.getNonConformingProduct().equals(YesOrNoEnum.YES.getCode())) { return false; } return true; }).collect(Collectors.toList()); //剔除掉在存在质量问题的考核指标数据 totalExpriDataList.removeAll(assessFailureList); //返回考核不通过的考核指标数据 return assessFailureList; } ... }
步骤五:分页查询出考核配置数据
@Component public class CalculateExpriResultSchedule { ... //分页查考核配置数据 //@param categoryIdList 品类ID集合 //@return 封装为Map,categoryId作为key private Map<Integer, ProductExpriConfigurationDTO> listExpriConfigurationByPage(List<Integer> categoryIdList, Integer assessmentType) { //分页查询出考核配置数据 List<ProductExpriConfigurationDTO> productExpriConfigurationList = productExpriRepository.queryExpriConfigurationByCategoryIdList(categoryIdList, assessmentType); //List转为Map,categoryId作为key Map<Integer, ProductExpriConfigurationDTO> productExpriConfigurationMap = productExpriConfigurationList.stream() .collect(Collectors.toMap(ProductExpriConfigurationDTO::getCategoryId, productExpriConfiguration -> productExpriConfiguration, (oldKey, newKey) -> oldKey)); return productExpriConfigurationMap; } ... } //商品考核资源管理 @Repository public class ProductExpriRepository { ... //分页查询出categoryIdList对应的考核配置集合 //@param categoryIdList 品类ID集合 //@return 考核配置集合 public List<ProductExpriConfigurationDTO> queryExpriConfigurationByCategoryIdList(List<Integer> categoryIdList, Integer assessmentType) { //考核配置集合 List<ProductExpriConfigurationDTO> productExpriConfigurationList = new ArrayList<>(); //一次最大查询200个数据,多个分页查询,这里做数据切割 List<List<Integer>> splitList = DataCuttingUtil.dataCuttingString(categoryIdList, ProductConstants.QUERY_ITEM_MAX_COUNT); for (List<Integer> categoryIds : splitList) { LambdaQueryWrapper<ProductExpriConfigurationDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.in(ProductExpriConfigurationDO::getCategoryId, categoryIds); queryWrapper.eq(ProductExpriConfigurationDO::getDelFlag, YesOrNoEnum.YES.getCode()); queryWrapper.eq(ProductExpriConfigurationDO::getAssessmentType, assessmentType); //分批次查询出categoryIdList对应的考核配置集合 List<ProductExpriConfigurationDO> configurationResultList = productExpriConfigurationMapper.selectList(queryWrapper); if (!CollectionUtils.isEmpty(configurationResultList)) { productExpriConfigurationList.addAll(productExpriConfigurationConverter.listEntityToDTO(configurationResultList)); } } return productExpriConfigurationList; } ... }
步骤六:对商品指标数据进行考核以及划分结果
@Component public class CalculateExpriResultSchedule { ... //比较考核结果数据和考核配置数据,判断考核结果数据是否通过考核 //销售额数据未达到要求,本轮考核不通过 //销售比率、客诉率、损耗率等5项指标,达到要求的指标数量未超过考核配置中通过考核的指标数量,本轮考核不通过 //对于试销期考核,本轮考核不通过,则考核状态即为不通过 //但是对于滞销期考核,本轮考核不通过,还需要比较考核配置中的连续失败次数 //如果连续失败次数超过了配置中的数值,则考核不通过 //否则,对连续失败次数进行累加操作,考核状态为考核中 //@param totalExpriDataList 考核指标数据(所有的考核数据集合) //@param assessFailureList 考核未通过的考核指标数据 //@param expriConfigurationMap 考核指标配置 //@param assessmentType 考核类型 //@return 考核中的考核指标数据 private List<ProductExpriDataResultDTO> filterSalesVolumePassTargetToList(List<ProductExpriDataResultDTO> totalExpriDataList, List<ProductExpriDataResultDTO> assessFailureList, Map<Integer, ProductExpriConfigurationDTO> expriConfigurationMap, Integer assessmentType) { //考核中的考核指标数据集合 List<ProductExpriDataResultDTO> assessingList = new ArrayList<>(); //批量查询出连续失败次数,作为Map,id是itemId Map<String, Integer> failTimesMap = itemExpriResultRepository.listFailTimesByPage(); for (ProductExpriDataResultDTO expriResult : totalExpriDataList) { //考核指标数据对应的考核配置 ProductExpriConfigurationDTO expriConfiguration = expriConfigurationMap.get(expriResult.getCategoryId()); //是否低于最低销售额 if (expriResult.getSalesVolume() < expriConfiguration.getSalesVolume()) { fillListByAssessmentType(assessFailureList, assessingList, failTimesMap, assessmentType, expriResult, expriConfiguration); } //销售比率、客诉率、退货率是否符合要求 if (!checkAssessment(expriResult, expriConfiguration)) { fillListByAssessmentType(assessFailureList, assessingList, failTimesMap, assessmentType, expriResult, expriConfiguration); } } //剔除掉考核不通过的考核指标数据 totalExpriDataList.removeAll(assessFailureList); //剔除掉考核中的考核指标数据 totalExpriDataList.removeAll(assessingList); //考核的流程执行到这里,totalExpriDataList中剩下的就是考核成功的考核指标数据 //返回考核中的考核指标数据 return assessingList; } //检查客诉率、退货率的指标是否符合要求 //@param expriData 考核指标数据 //@param expriConfiguration 考核指标配置 //@return true:符合;false:不符合 private boolean checkAssessment(ProductExpriDataResultDTO expriData, ProductExpriConfigurationDTO expriConfiguration) { //考核通过指标数量 Integer passTargetConfig = expriConfiguration.getPassTarget(); Integer passTarget = 0; //销售比例配置 Integer soldPropotionConfig = expriConfiguration.getSoldPropotion(); Integer soldPropotion = expriData.getSoldPropotion(); if (soldPropotion >= soldPropotionConfig) { passTarget++; } //每单利润率配置 Integer orderProfitMarginConfig = expriConfiguration.getOrderProfitMargin(); Integer orderProfitMargin = expriData.getOrderProfitMargin(); if (orderProfitMargin >= orderProfitMarginConfig) { passTarget++; } //退货比率配置 Integer returnRateConfig = expriConfiguration.getReturnRate(); Integer returnRate = expriData.getReturnRate(); if (returnRate <= returnRateConfig) { passTarget++; } //客诉率配置 Integer complaintRateConfig = expriConfiguration.getComplaintRate(); Integer complaintRate = expriData.getComplaintRate(); if (complaintRate <= complaintRateConfig) { passTarget++; } //损耗比率配置 Integer lossRateConfig = expriConfiguration.getLossRate(); Integer lossRate = expriData.getLossRate(); if (lossRate <= lossRateConfig) { passTarget++; } if (passTarget >= passTargetConfig) { return true; } return false; } //考核不通过时,根据考核类型(试销期/滞销期)将考核指标数据,拆分填充到考核失败集合、考核中集合 //@param assessFailureList 考核不通过集合 //@param assessingList 考核中集合 //@param failTimesMap 连续失败次数数据Map,key是itemId //@param assessmentType 考核类型 //@param expriResult 考核指标数据 //@param expriConfiguration 考核指标配置 private void fillListByAssessmentType(List<ProductExpriDataResultDTO> assessFailureList, List<ProductExpriDataResultDTO> assessingList, Map<String, Integer> failTimesMap, Integer assessmentType, ProductExpriDataResultDTO expriResult, ProductExpriConfigurationDTO expriConfiguration) { //如果是试销考核 if (assessmentType.equals(AssessmentTypeEnum.TRY_SALE_ASSESS.getCode())) { //直接添加到考核不通过集合中 assessFailureList.add(expriResult); } //如果是滞销考核 if (assessmentType.equals(AssessmentTypeEnum.GROWN_SALE_ASSESS.getCode())) { //比较失败次数 Integer failTimes = failTimesMap.getOrDefault(expriResult.getItemId(), BigDecimal.ZERO.intValue()); Integer failTimesConfig = expriConfiguration.getFailTimes(); //如果小于配置的连续失败次数 if (++failTimes < failTimesConfig) { //添加到考核中集合 assessingList.add(expriResult); } else { //否则添加到考核不通过集合 assessFailureList.add(expriResult); } } } ... } //考核结果资源管理 @Repository public class ItemExpriResultRepository { ... //分页查询出状态为考核中的考核缓冲结果数据中的连续失败次数集合 //@return 连续失败次数集合 public Map<String, Integer> listFailTimesByPage() { //构造查询条件 LambdaQueryWrapper<ItemExpriResultBufferDO> queryWrapper = Wrappers.lambdaQuery(); //考核状态为考核中 queryWrapper.eq(ItemExpriResultBufferDO::getMarketExpriResult, MarketExpriResultEnum.UNDER_EXAMINATION.getCode()); queryWrapper.eq(ItemExpriResultBufferDO::getDelFlag, YesOrNoEnum.YES.getCode()); //分页查询存放数据的总集合 Map<String, Integer> results = new HashMap<>(); int pageNum = 1; //设置每次查询的数据量,最大为200 int pageSize = ProductConstants.QUERY_ITEM_MAX_COUNT; Page<ItemExpriResultBufferDO> page = new Page<>(pageNum, pageSize); //查询第一页数据 Page<ItemExpriResultBufferDO> pageResult = itemExpriResultBufferMapper.selectPage(page, queryWrapper); //判断是否还有数据,还有数据则页码+1继续执行分页查询 List<ItemExpriResultBufferDO> batchResult = pageResult.getRecords(); try { while (batchResult.size() >= ProductConstants.QUERY_ITEM_MAX_COUNT) { //将分页查询出来的list数据中的failTimes属性添加到Map集合中,itemId作为key putAll(results, batchResult); pageNum += 1; page.setCurrent(pageNum); page = itemExpriResultBufferMapper.selectPage(page, queryWrapper); batchResult = page.getRecords(); //每次循环获取数据后,休眠20ms,避免对数据库造成太大压力 Thread.sleep(20); } } catch (InterruptedException e) { throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL); } //最后一组数据也放入结果集中 putAll(results, batchResult); return results; } ... }
步骤七:商品考核数据分多种类别写入结果缓冲表
@Component public class CalculateExpriResultSchedule { ... //批量将考核指标数据写入到考核结果缓冲表中 //@param itemIdList itemId集合 //@param assessFailureList 考核不通过的考核指标数据 //@param assessingList 考核中的考核指标数据 //@param totalExpriDataList 考核通过的考核指标数据 //@param nonAssessmentExpriDataList 免考核的考核指标数据 //@param itemPeriodStageMap 生命周期数据集合,key为itemId private void batchInsertAssessmentResult(List<String> itemIdList, List<ProductExpriDataResultDTO> assessFailureList, List<ProductExpriDataResultDTO> assessingList, List<ProductExpriDataResultDTO> totalExpriDataList, List<ProductExpriDataResultDTO> nonAssessmentExpriDataList, Map<String, ItemPeriodStageDTO> itemPeriodStageMap) { //1.分页查询考核缓冲表中考核状态是考核中的数据 Map<String, ItemExpriResultBufferDTO> itemExpriResultBufferMap = itemExpriResultRepository.listAssessingBufferResultByPage(itemIdList); //2.批量写入考核不通过的考核指标数据 batchInsertByMarketExpriType(itemExpriResultBufferMap, assessFailureList, itemPeriodStageMap, MarketExpriResultEnum.FAIL_PASS_EXAMINATION.getCode(), BigDecimal.ONE.intValue(), BigDecimal.ONE.intValue()); //3.批量写入考核中的考核指标数据 batchInsertByMarketExpriType(itemExpriResultBufferMap, assessingList, itemPeriodStageMap, MarketExpriResultEnum.UNDER_EXAMINATION.getCode(), BigDecimal.ONE.intValue(), BigDecimal.ONE.intValue()); //4.批量写入考核通过的考核指标数据 batchInsertByMarketExpriType(itemExpriResultBufferMap, totalExpriDataList, itemPeriodStageMap, MarketExpriResultEnum.PASS_EXAMINATION.getCode(), BigDecimal.ZERO.intValue(), BigDecimal.ZERO.intValue()); //5.批量写入免考核指标数据 batchInsertByMarketExpriType(itemExpriResultBufferMap, nonAssessmentExpriDataList, itemPeriodStageMap, MarketExpriResultEnum.NON_EXAMINATION.getCode(), BigDecimal.ZERO.intValue(), BigDecimal.ZERO.intValue()); } //根据考核状态批量将考核指标数据批量写入到考核结果缓冲表 //考核不通过: 新增操作默认连续失败次数为1,修改操作连续失败次数增加1 //考核中: 新增操作默认连续失败次数为1,修改操作连续失败次数增加1 //考核通过: 新增操作默认连续失败次数为0,修改操作连续失败次数增加0 //免考核: 新增操作默认连续失败次数为0,修改操作连续失败次数增加0 //@param itemExpriResultBufferMap 已存在的考核状态为考核中的考核结果缓冲数据,key为itemId //@param expriDataResultList 考核指标数据集合 //@param itemPeriodStageMap 生命周期数据,key为itemId //@param marketExpriResultType 考核状态:0:考核中;1:考核通过;2:考核不通过;3:不考核或暂停考核 //@param defaultFailTimes 新增考核缓冲结果数据时,默认的失败次数 //@param incrementFailTimes 修改考核缓冲结果数据时,增加的失败次数 private void batchInsertByMarketExpriType(Map<String, ItemExpriResultBufferDTO> itemExpriResultBufferMap, List<ProductExpriDataResultDTO> expriDataResultList, Map<String, ItemPeriodStageDTO> itemPeriodStageMap, Integer marketExpriResultType, Integer defaultFailTimes, Integer incrementFailTimes) { //需要插入考核结果缓冲表的集合数据 List<ItemExpriResultBufferRequest> insertRequest = new ArrayList<>(); for (ProductExpriDataResultDTO result : expriDataResultList) { String itemId = result.getItemId(); //如果考核结果缓冲表中不存在该记录 if (Objects.isNull(itemExpriResultBufferMap.get(itemId))) { //准备批量插入数据 ItemExpriResultBufferRequest request = new ItemExpriResultBufferRequest(); request.setItemId(itemId); request.setCategoryId(result.getCategoryId()); request.setPeriodStageId(itemPeriodStageMap.get(itemId).getProductPeriodId()); request.setFailTimes(defaultFailTimes); request.setMarketExpriResult(marketExpriResultType); request.setDelFlag(YesOrNoEnum.YES.getCode()); insertRequest.add(request); } else { //考核结果表中存在该记录,执行修改操作 ItemExpriResultBufferRequest request = new ItemExpriResultBufferRequest(); request.setItemId(itemId); Integer failTimes = itemExpriResultBufferMap.get(itemId).getFailTimes() + incrementFailTimes; request.setFailTimes(failTimes); request.setMarketExpriResult(marketExpriResultType); itemExpriResultRepository.updateItemExpriResultBuffer(request); } } //批量插入考核结果缓冲表 itemExpriResultRepository.insertBatch(insertRequest); } ... } //考核结果资源管理 @Repository public class ItemExpriResultRepository { ... //分页查询考核缓冲表中考核状态是考核中的数据 //@param itemIdList itemId集合 //@return 考核数据Map,key为itemId public Map<String, ItemExpriResultBufferDTO> listAssessingBufferResultByPage(List<String> itemIdList) { if (CollectionUtils.isEmpty(itemIdList)) { return CollectionUtils.newHashMap(); } //考核中的考核结果缓冲数据集合 Map<String, ItemExpriResultBufferDTO> resultMap = new HashMap<>(); //一次最大查询200个数据,多个分页查询,这里做数据切割 List<List<String>> splitList = DataCuttingUtil.dataCuttingString(itemIdList, ProductConstants.QUERY_ITEM_MAX_COUNT); for (List<String> itemIds : splitList) { LambdaQueryWrapper<ItemExpriResultBufferDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.in(ItemExpriResultBufferDO::getItemId, itemIds); queryWrapper.eq(ItemExpriResultBufferDO::getMarketExpriResult, MarketExpriResultEnum.UNDER_EXAMINATION.getCode()); queryWrapper.eq(ItemExpriResultBufferDO::getDelFlag, YesOrNoEnum.YES.getCode()); //分批次查询出符合条件的考核结果缓冲数据集合 List<ItemExpriResultBufferDO> assessingBufferResultList = itemExpriResultBufferMapper.selectList(queryWrapper); if (!CollectionUtils.isEmpty(assessingBufferResultList)) { Map<String, ItemExpriResultBufferDTO> result = assessingBufferResultList.stream() .map(itemExpriResultBufferDO -> itemExpriResultBufferConverter.entityToDTO(itemExpriResultBufferDO)) .collect(Collectors.toMap(ItemExpriResultBufferDTO::getItemId, itemExpriResultBuffer -> itemExpriResultBuffer, (oldKey, newKey) -> oldKey)); resultMap.putAll(result); } } return resultMap; } //批量插入考核缓冲结果数据 public void insertBatch(List<ItemExpriResultBufferRequest> insertRequest) { List<ItemExpriResultBufferDO> bufferEntityList = itemExpriResultBufferConverter.listRequestToEntity(insertRequest); if (CollectionUtils.isEmpty(bufferEntityList)) { return; } Integer count = itemExpriResultBufferMapper.insertBatch(bufferEntityList); if (count <= 0) { throw new ProductBizException(CommonErrorCodeEnum.SQL_ERROR); } } ... }
步骤八:将参与本轮考核计算的考核指标数据都设置为失效状态
@Component public class CalculateExpriResultSchedule { ... //将参与本轮考核计算的考核指标数据都设置为失效状态 //@param itemIdList 本轮考核指标数据对应的itemId集合 private void batchInvalidExpriData(List<String> itemIdList) { productExpriRepository.invalidExpriDataResult(itemIdList); } ... } //商品考核资源管理 @Repository public class ProductExpriRepository { ... //将考核指标数据设置为失效状态 public Boolean invalidExpriDataResult(List<String> itemIdList) { if (CollectionUtils.isEmpty(itemIdList)) { return false; } LambdaUpdateWrapper<ProductExpriDataResultDO> updateWrapper = Wrappers.lambdaUpdate(); updateWrapper.in(ProductExpriDataResultDO::getItemId, itemIdList); updateWrapper.set(ProductExpriDataResultDO::getIsEffective, YesOrNoEnum.NO.getCode()); int count = productExpriDataResultMapper.update(null, updateWrapper); if (count <= 0) { throw new ProductBizException(CommonErrorCodeEnum.SQL_ERROR); } return true; } ... }
(3)定时任务基于考核结果设置商品可采可补可售
步骤一:分页查每页1000条数据 步骤二:判断是否考核通过,通过的放在passList,失败的放在failList 步骤三:针对failList,设置供需状态,批量保存到考核结果表 步骤四:针对passList,判断所处生命周期,拆分成滞销期list,试销期list 步骤五:针对滞销期/试销期list,执行供需状态设置,保存到考核结果表 步骤六:针对passList发送考核通过消息,针对failList发送考核失败消息 步骤七:批量逻辑删除所有的考核通过/不通过的buffer数据
@Component public class CalculateExpriResultSchedule { ... //根据考核结果缓冲表中的考核状态和生命周期阶段,写入供需属性 @XxlJob("calculateExpri") @Transactional(rollbackFor = Exception.class) public void calculateExpri() { //1.分页查,每页1000条数据 //2.批量判断是否考核通过,通过的放在passList,失败的放在failList //3.针对failList的逻辑,执行对象转换,设置供需状态,批量保存到考核结果表 //4.针对passList的逻辑,执行对象转换,判断所处生命周期,拆分成滞销期list,试销期list //5.针对滞销期/试销期list,执行供需状态设置,批量保存到考核结果表 //6.批量针对passList发送考核通过消息,针对failList发送考核失败消息 //7.批量逻辑删除所有的考核通过/不通过的buffer数据 //查询出考核结果数据 List<ItemExpriResultBufferDO> passExpriResultBufferList = getPassExpriResultBufferList(); List<ItemExpriResultBufferDO> failExpriResultBufferList = getFailExpriResultBufferList(); //批量处理考核成功结果 List<ItemExpriResultDO> passedHandledList = batchHandlePassExpriResults(passExpriResultBufferList); //批量处理考核失败结果 List<ItemExpriResultDO> failedHandledList = batchHandleFailExpriResults(failExpriResultBufferList); //批量分批次写入处理结果到考核表中 batchSaveExpriResult(passedHandledList); batchSaveExpriResult(failedHandledList); //批量更新考核结果buffer数据为已失效 itemExpriResultRepository.batchDeleteExpriBuffer(passExpriResultBufferList); itemExpriResultRepository.batchDeleteExpriBuffer(failExpriResultBufferList); } ... }
一.批量处理考核成功的结果
@Component public class CalculateExpriResultSchedule { ... //查询考核通过的buffer数据list private List<ItemExpriResultBufferDO> getPassExpriResultBufferList() { List<ItemExpriResultBufferDO> passExpriResultBufferList = new ArrayList<>(ProductConstants.QUERY_ITEM_MAX_COUNT * 10); Integer pageNum = 1; try { while (true) { //分页查询考核结果缓冲数据 PageResult<ItemExpriResultBufferDO> page = itemExpriResultRepository.queryPassExpriResultBufferByPage(pageNum, ProductConstants.QUERY_ITEM_MAX_COUNT * 5); //内容为空时,跳出循环 if (Objects.isNull(page) || CollectionUtils.isEmpty(page.getContent())) { break; } //查询到的结果集加入到通过list中 passExpriResultBufferList.addAll(page.getContent()); //页码+1 pageNum += 1; Thread.sleep(20); } } catch (InterruptedException e) { throw new BaseBizException("根据考核结果更新供需状态job,查询缓冲数据执行异常"); } return passExpriResultBufferList; } //考核成功结果处理供需属性 private List<ItemExpriResultDO> batchHandlePassExpriResults(List<ItemExpriResultBufferDO> passExpriResultBufferList) { //批量处理考核成功结果 List<String> itemIds = new ArrayList<>(passExpriResultBufferList.size()); for (ItemExpriResultBufferDO resultBufferDO : passExpriResultBufferList) { //商品itemId String itemId = resultBufferDO.getItemId(); itemIds.add(itemId); } log.info("itemIds:{}", JSON.toJSONString(itemIds)); //分页处理考核通过结果 List<ItemPeriodStageDTO> stageDTOS = new ArrayList<>(passExpriResultBufferList.size()); Integer fromIndex = 0; while (true) { //如果起始大于了list的结束位置,就退出循环 if (fromIndex > passExpriResultBufferList.size() - 1) { break; } List<String> sbList = itemIds.subList(fromIndex, Math.min(fromIndex + ProductConstants.QUERY_ITEM_MAX_COUNT, passExpriResultBufferList.size())); //分批次更新商品前一次考核结果数据为失效 itemExpriResultRepository.batchDeleteExpriResult(sbList); //分批查询商品生命周期所处阶段 stageDTOS.addAll(itemPeriodStageRepository.queryPeriodStageByItemIds(new QueryPeriodListRequest(sbList))); fromIndex += ProductConstants.QUERY_ITEM_MAX_COUNT; } HashMap<String, Integer> itemStageMap = new HashMap(stageDTOS.size()); //转换成map结构,避免后续处理的时候,多次遍历stageDTOS list for (ItemPeriodStageDTO itemPeriodStageDTO : stageDTOS) { itemStageMap.put(itemPeriodStageDTO.getItemId(), itemPeriodStageDTO.getPeriodStage()); } //循环设置考核结果供需属性 List<ItemExpriResultDO> handledPassResults = new ArrayList<>(passExpriResultBufferList.size()); for (ItemExpriResultBufferDO resultBufferDO : passExpriResultBufferList) { //生命周期阶段 Integer periodStage = itemStageMap.get(resultBufferDO.getItemId()); ItemExpriResultDO itemExpriResultDO = itemExpriResultConverter.converterBuffer(resultBufferDO); //试销期 if (periodStage.equals(ProductStageEnum.TRY_SALE.getCode())) { //设置对象初始值 itemExpriResultDO.setUpdateUser(1); itemExpriResultDO.initCommon(); //设置试销期供需状态 setTrySaleSupplyStatus(itemExpriResultDO); handledPassResults.add(itemExpriResultDO); } //成熟期 if (periodStage.equals(ProductStageEnum.GROWN.getCode())) { //设置对象初始值 itemExpriResultDO.setUpdateUser(1); itemExpriResultDO.initCommon(); //设置成熟期供需状态 setGrownSupplyStatus(itemExpriResultDO); handledPassResults.add(itemExpriResultDO); } } return handledPassResults; } //设置试销期供需状态 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 = itemPeriodStageRepository.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());//不可售 } ... } @Repository public class ItemExpriResultRepository { ... //分页查询考核通过数据集 public PageResult<ItemExpriResultBufferDO> queryPassExpriResultBufferByPage(Integer pageNum, Integer pageSize) { LambdaQueryWrapper<ItemExpriResultBufferDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(ItemExpriResultBufferDO::getMarketExpriResult, MarketExpriResultEnum.PASS_EXAMINATION.getCode()); queryWrapper.eq(ItemExpriResultBufferDO::getDelFlag, YesOrNoEnum.YES.getCode()); Page<ItemExpriResultBufferDO> page = new Page<>(pageNum, pageSize); return new PageResult<>(itemExpriResultBufferMapper.selectPage(page, queryWrapper)); } ... }
二.批量处理考核失败的结果
@Component public class CalculateExpriResultSchedule { ... //查询考核失败的buffer数据list private List<ItemExpriResultBufferDO> getFailExpriResultBufferList() { List<ItemExpriResultBufferDO> passExpriResultBufferList = new ArrayList<>(ProductConstants.QUERY_ITEM_MAX_COUNT * 10); Integer pageNum = 1; try { while (true) { //分页查询考核结果缓冲数据 PageResult<ItemExpriResultBufferDO> page = itemExpriResultRepository.queryFailExpriResultBufferByPage(pageNum, ProductConstants.QUERY_ITEM_MAX_COUNT * 5); //内容为空时,跳出循环 if (Objects.isNull(page) || CollectionUtils.isEmpty(page.getContent())) { break; } //查询到的结果集加入到通过list中 passExpriResultBufferList.addAll(page.getContent()); //页码+1 pageNum += 1; Thread.sleep(20); } } catch (InterruptedException e) { throw new BaseBizException("根据考核结果更新供需状态job,查询缓冲数据执行异常"); } return passExpriResultBufferList; } //考核失败结果处理供需属性 private List<ItemExpriResultDO> batchHandleFailExpriResults(List<ItemExpriResultBufferDO> failExpriResultBufferList) { //考核结果数据集,考核缓冲数据处理转换成考核结果数据集合,并批量保存至数据库 List<ItemExpriResultDO> failExpriResultList = new ArrayList<>(failExpriResultBufferList.size()); List<String> itemIds = new ArrayList<>(failExpriResultBufferList.size()); //处理考核失败数据,设置淘汰期供需属性 for (ItemExpriResultBufferDO resultBufferDO : failExpriResultBufferList) { //克隆缓存对象属性到考核结果对象 ItemExpriResultDO itemExpriResultDO = itemExpriResultConverter.converterBuffer(resultBufferDO); itemExpriResultDO.setUpdateUser(1); itemExpriResultDO.initCommon(); //设置淘汰期供需状态 setPreEliminateSupplyStatus(itemExpriResultDO); failExpriResultList.add(itemExpriResultDO); //提取itemId itemIds.add(itemExpriResultDO.getItemId()); } Integer fromIndex = 0; while (true) { //如果起始大于了list的结束位置,就退出循环 if (fromIndex > failExpriResultBufferList.size() - 1) { break; } List<String> sbList = itemIds.subList(fromIndex, Math.min(fromIndex + ProductConstants.QUERY_ITEM_MAX_COUNT, failExpriResultBufferList.size())); //分批次更新商品前一次考核结果数据为失效 itemExpriResultRepository.batchDeleteExpriResult(sbList); fromIndex += ProductConstants.QUERY_ITEM_MAX_COUNT; } return failExpriResultList; } //设置淘汰期供需状态 private void setPreEliminateSupplyStatus(ItemExpriResultDO itemExpriResultDO) { itemExpriResultDO.setPurchaseStatus(YesOrNoEnum.NO.getCode());//不可采 itemExpriResultDO.setReplenishmentStatus(YesOrNoEnum.NO.getCode());//不可补 itemExpriResultDO.setSaleStatus(YesOrNoEnum.YES.getCode());//可售 } //批量保存处理过的考核结果数据到考核结果 private void batchSaveExpriResult(List<ItemExpriResultDO> handledList) { //分页写入考核失败结果至考核结果表 Integer fromIndex = 0; try { while (true) { //判断下一次的list拆分是否超过了 if (fromIndex > handledList.size() - 1) { break; } //每200条数据执行一次批量写 List<ItemExpriResultDO> subList = handledList.subList(fromIndex, Math.min(fromIndex + ProductConstants.QUERY_ITEM_MAX_COUNT, handledList.size())); itemExpriResultRepository.saveItemExpriResultBatch(subList); //设置下一页的起始位置 fromIndex += ProductConstants.QUERY_ITEM_MAX_COUNT; Thread.sleep(20); } } catch (InterruptedException e) { throw new BaseBizException("批量保存考核结果至考核结果表异常"); } } ... } @Repository public class ItemExpriResultRepository { ... //分页查询考核不通过数据集 public PageResult<ItemExpriResultBufferDO> queryFailExpriResultBufferByPage(Integer pageNum, Integer pageSize) { LambdaQueryWrapper<ItemExpriResultBufferDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(ItemExpriResultBufferDO::getMarketExpriResult, MarketExpriResultEnum.FAIL_PASS_EXAMINATION.getCode()); queryWrapper.eq(ItemExpriResultBufferDO::getDelFlag, YesOrNoEnum.YES.getCode()); Page<ItemExpriResultBufferDO> page = new Page<>(pageNum, pageSize); return new PageResult<>(itemExpriResultBufferMapper.selectPage(page, queryWrapper)); } //批量写入到考核结果表中 public void saveItemExpriResultBatch(List<ItemExpriResultDO> list) { if (CollectionUtils.isEmpty(list)) { return; } int count = itemExpriResultMapper.saveBatch(list); if (count <= 0) { throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL); } } //批量设置考核结果buffer数据为失效状态 public void batchDeleteExpriBuffer(List<ItemExpriResultBufferDO> list) { if (CollectionUtils.isEmpty(list)) { return; } int count = itemExpriResultBufferMapper.batchDelete(list, YesOrNoEnum.NO.getCode()); if (count <= 0) { throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL); } } ... }
(4)考核结果数据的Excel导出
@RestController @RequestMapping("/ItemPeriodStage") public class ItemPeriodStageController { @Autowired private ItemPeriodStageService itemPeriodStageService; ... //导出数据到Excel @PostMapping("/exportItemExpriToExcel") public void exportExcel(@RequestBody ItemExpriReslutPageRequest request, HttpServletResponse response) throws IOException { try { List<ItemExpriResultToExcelDTO> itemExpriResults = itemPeriodStageService.queryItemExpriResultBatch(request); String title = "商品考核结果"; String sheetName = title; String fileName = title + System.currentTimeMillis(); log.info("result:{}", itemExpriResults); ExcelUtils.exportExcel(itemExpriResults, title, sheetName, ItemExpriResultToExcelDTO.class, fileName, response); } catch (BaseBizException baseBizException) { log.error("biz error: request={}", JSON.toJSONString(request), baseBizException); } catch (Exception e) { log.error("system error: request={}", JSON.toJSONString(request), e); } } ... } @Service public class ItemPeriodStageServiceImpl implements ItemPeriodStageService { ... //分页批量查询商品考核结果 @Override public List<ItemExpriResultToExcelDTO> queryItemExpriResultBatch(ItemExpriReslutPageRequest request) { //查询考核结果每次的数据量可能会比较大,所以预设一个较大的List初始大小,避免多次扩容 List<ItemExpriResultDTO> results = new ArrayList<>(ProductConstants.QUERY_ITEM_MAX_COUNT * 10); //1.设置每次查询的数据量,最大为200 request.setPageSize(ProductConstants.QUERY_ITEM_MAX_COUNT); Integer pageNum = request.getPageNum(); List<ItemExpriResultDTO> batchResult; try { while (true) { //2.分页数据 log.info("request:{}", JSON.toJSONString(request)); PageResult<ItemExpriResultDTO> page = itemPeriodStageRepository.queryItemExpriResultByPage(request); //3.判断是否还有数据,还有数据则页码+1继续执行分页查询 log.info("page:{}", JSON.toJSONString(page)); if (Objects.isNull(page) || CollectionUtils.isEmpty(page.getContent())) { break; } //4.拿到本次查询的数据 batchResult = page.getContent(); log.info("dataList:{}", JSON.toJSONString(batchResult)); //判断拿到的数据是否为空,或为null,若为空或为null,说明本次查询未查到数据,可直接break if (CollectionUtils.isEmpty(batchResult)) { break; } //6.查询到的数据全部加入到返回的结果集中,页码+1 results.addAll(batchResult); request.setPageNum(++pageNum); //每次循环获取数据后,休眠20ms,避免对数据库造成太大压力 Thread.sleep(20); } } catch (InterruptedException e) { throw new BaseBizException("查询出错,request:{}", JSON.toJSONString(request)); } log.info("result:{}", results); //7.返回转换后的结果集 return transferItemExpriResultToExcelDTO(results); } ... } @Repository public class ItemPeriodStageRepository { ... //根据分页查询考核结果 public PageResult<ItemExpriResultDTO> queryItemExpriResultByPage(ItemExpriReslutPageRequest request) { LambdaQueryWrapper<ItemExpriResultDO> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(ItemExpriResultDO::getDelFlag, YesOrNoEnum.YES.getCode()); //参数不为null,则设置为条件 if (!Objects.isNull(request.getCategoryId())) { queryWrapper.eq(ItemExpriResultDO::getCategoryId, request.getCategoryId()); } if (!Objects.isNull(request.getItemId())) { queryWrapper.eq(ItemExpriResultDO::getItemId, request.getItemId()); } Page<ItemExpriResultDO> page = new Page<>(request.getPageNum(), request.getPageSize()); Page<ItemExpriResultDO> skuPeriodStagePage = itemExpriResultMapper.selectPage(page, queryWrapper); return new PageResult<>(itemExpriResultConverter.converterDTOList(skuPeriodStagePage.getRecords())); } ... } //Excel导入导出工具类 public class ExcelUtils { ... //excel 导出 //@param list 数据列表 //@param title 表格内数据标题 //@param sheetName sheet名称 //@param pojoClass pojo类型 //@param fileName 导出时的excel名称 //@param response public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) throws IOException { defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName, ExcelType.XSSF)); } //excel 导出 //@param list 数据列表 //@param pojoClass pojo类型 //@param fileName 导出时的excel名称 //@param response //@param exportParams 导出参数(标题、sheet名称、是否创建表头,表格类型) private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) throws IOException { //把表头、表格数据添加到excel表格中 Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list); //下载Excel到本地 downLoadExcel(fileName, response, workbook); } //excel下载 //@param fileName 下载时的文件名称 //@param response //@param workbook excel数据 private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) throws IOException { try { response.setCharacterEncoding("UTF-8"); response.setHeader("content-Type", "application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8")); workbook.write(response.getOutputStream()); } catch (Exception e) { throw new IOException(e.getMessage()); } } ... }
详细介绍后端技术栈的基础内容,包括但不限于:MySQL原理和优化、Redis原理和应用、JVM和G1原理和优化、RocketMQ原理应用及源码、Kafka原理应用及源码、ElasticSearch原理应用及源码、JUC源码、Netty源码、zk源码、Dubbo源码、Spring源码、Spring Boot源码、SCA源码、分布式锁源码、分布式事务、分库分表和TiDB、大型商品系统、大型订单系统等