/**
* @notes 京东收到派服务费核对逻辑
* @param $yearMonth
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @author 胡军
* @date 2025/07/01
*/
public function deliveryPrimaryRegionsVerifyDo($yearMonth):bool{
$monthTimeRange = $this->getMonthTimeRange($yearMonth);
$status = true; // 初始化状态
// 使用use关键字将$yearMonth传入闭包 如果闭包返回 false,chunk() 会立即停止处理后续数据块
DeliveryPrimaryRegionsItemizationModel::whereBetween('order_time', [
$monthTimeRange['startTime'],
$monthTimeRange['endTime']
])->chunk(100, function ($items) use ($yearMonth,&$status) {
// 临时存储当前批次的结果
$itemizationMonthList = [];
foreach ($items as $item) {
//print_r($item->id."处理完成".PHP_EOL);
$processRet = $this->processItem($item, $yearMonth);
if(!empty($processRet)){
$itemizationMonthList[] = $processRet;
}
}
// 优化后的事务处理->将整个批次作为一个事务处理,减少事务开启次数
$itemizationModel = new DeliveryPrimaryRegionsItemizationModel();
try {
$itemizationModel->startTrans();
// 直接处理整个批次数据
$itemizationModel->saveAll($itemizationMonthList);
$itemizationModel->commit();
} catch (\Exception $e) {
$itemizationModel->rollback();
Log::error('【京东收派服务费明细核对】--月份为:'.$yearMonth."的费用核对发生错误:" . $e->getMessage());
$status = false;
return false;
}
});
return $status;
}
// 将处理逻辑封装到单独方法当中
private function processItem($item,$yearMonth) {
//实际配送特惠送区域型
$deliveryPrimary = DeliveryPrimaryRegionsQuoteModel::where('origin_province','=',$item['origin_province'])
->where('origin_location','=',$item['origin_city'])
->where('destination_province','=',$item['destination_province'])
->where('destination_location','=',$item['destination_city'])
->find();
if(!empty($deliveryPrimary)){
$deliveryPrimaryData = $deliveryPrimary->toArray();
//1、实际配送特惠送区域型->始发省&始发市&目的省&目的市去匹配对应收派服务费报价表中的始发省&始发市&目的省&目的市对应的京东标快分区值
$item['actual_delivery_area_type'] = $deliveryPrimaryData['jd_standard_partition'];
}else{
//记录错误日志
Log::warning('【京东收派服务费明细核对】--月份为:'.$yearMonth."的明细表la_primary_regions_mx当中的[始发省&始发市&目的省&目的市] 没有匹配到 京东收派服务费报价表:la_primary_regions_quote对应的 [始发省&始发市&目的省&目的市],明细记录id为".$item['id']);
return [];
}
//本应发货特惠送区域类型
$laPrimaryRegions = DeliveryPrimaryRegionsModel::where('province','=',$item['destination_province'])
->where('city','=',$item['destination_city'])->find();
if(!empty($laPrimaryRegions)){
//2、本应发货特惠送区域类型->目的省&目的市匹配对应一级区域表中的省&市对应的京东标快分区值
$laPrimaryRegionsData = $laPrimaryRegions->toArray();
$item['expected_delivery_area_type'] = $laPrimaryRegionsData['jd_zone'];
}else{
//记录错误日志
Log::warning('【京东收派服务费明细核对】--月份为:'.$yearMonth."的明细表la_primary_regions_mx当中的[目的省&目的市] 没有匹配到 京东收派服务费一级区域表:la_primary_regions对应的 [目的省&目的市],明细记录id为".$item['id']);
return [];
}
//3、是否跨仓->当实际配送特惠送区域型=实际配送特惠送区域时写入不跨仓否则跨仓
$item['is_cross_warehouse'] = $item['actual_delivery_area_type'] == $item['expected_delivery_area_type'] ? '2' : '1';
//4、理论重量: 收派服务费核对明细的平台订单号关联la_scm_day_outbound_data当中的billNo字段,查询该订单billNo下货品goodsNo及数量quantity,再通过关联基础数据物料la_warehouse_facility表的单品重量(通过条码)计算该订单下各货品的重量,并按照订单号分组、求和。
$scmDayOutboundData = ScmDayOutboundDataModel::where('billNo','=',$item['platform_order_number'])->select()->toArray();
if(!empty($scmDayOutboundData)){
$weightTotal = 0;
foreach($scmDayOutboundData as $k => $v){
//skuBarcode
$goodsWeightModel = GoodsWeightModel::where('barcode','=',$v['skuBarcode'])->field('single_item_weight')->find();
if(!empty($goodsWeightModel)){
$goodsWeight = $goodsWeightModel->toArray();
//la_scm_day_outbound_data数量*la_warehouse_facility单品重量(kg)
$weightTotal += $v['quantity'] * $goodsWeight['single_item_weight'];
}else{
//记录错误日志
Log::warning('【京东收派服务费明细核对】--月份为:'.$yearMonth."的明细表平台订单号关联la_scm_day_outbound_data当中的billNo字段 但是billNo字段关联基础数据物料la_warehouse_facility表的条码skuBarcode失败,明细记录id为".$item['id']);
return [];
}
}
$item['theoretical_weight'] = $weightTotal;
}else{
Log::warning('【京东收派服务费明细核对】--月份为:'.$yearMonth."的明细表la_primary_regions_mx当中的平台订单号platform_order_number 没有匹配到 每日出库数据明细表:la_scm_day_outbound_data对应的关联单号billNo,明细记录id为".$item['id']);
return [];
}
//5、重量取整-> 理论重量向上取0.5
if(is_numeric($item['theoretical_weight'])) { //如果是浮点数那么向上取0.5
$floatValue = (float)$item['theoretical_weight'];
$item['weight_rounded'] = ceil($floatValue * 2) / 2;
}else{ //如果是整数那么就直接赋值即可
$item['weight_rounded'] = $item['theoretical_weight'];
}
//6、重量差异-> 计费重量-重量取整
$item['weight_difference'] = $item['billing_weight'] - $item['weight_rounded'];
//7、金额核验->(存在重复代码待优化 赶工期......)
// ①费用类型=快递改址费 5;
// ②费用类型=保价费 0.3;
// ③费用类型=快递_转寄服务费 5;
// ④费用类型=快递超长超重,【计费重量】*1元/kg
// ⑤费用类型=快递运费,始发省&始发市&目的省&目的市,匹配对应 收派服务费报价表中的 始发省&始发市&目的省&目的市 对应的报价,使用【计费重量】按照报价计算:
// 若 计费重量<=1 计算公式为:首重金额*0.28;
// 若1<计费用重量<=30 计算公式为:(首重金额+(计费重量-1)*续重1金额)*0.28;
// 若 计费重量>30 计算公式为:(首重金额+(计费重量-1)*续重1金额+(计费重量-30)*续重2金额)*0.28
if($item['fee_type'] == '快递改址费' || $item['fee_type'] == '快递_转寄服务费'){
$item['amount_verification'] = 5;
}elseif($item['fee_type'] == '保价费'){
$item['amount_verification'] = 0.3;
}elseif($item['fee_type'] == '快递超长超重'){
$item['amount_verification'] = $item['billing_weight'] * 1;
}elseif($item['fee_type'] == '快递运费'){
if($item['billing_weight'] <= 1){
$item['amount_verification'] = $deliveryPrimary->first_weight * 0.28;
}elseif($item['billing_weight'] <= 30){
$item['amount_verification'] = ($deliveryPrimary->first_weight + ($item['billing_weight'] - 1) * $deliveryPrimary->continued_weight1) * 0.28;
}else{ // >30的情况
$item['amount_verification'] = ($deliveryPrimary->first_weight + ($item['billing_weight'] - 1) * $deliveryPrimary->continued_weight1 + ($item['billing_weight'] - 30) * $deliveryPrimary->continued_weight2) * 0.28;
}
}else{
Log::warning('【京东收派服务费明细核对】--月份为:'.$yearMonth."的明细表la_primary_regions_mx当中的费用类型fee_type 没有匹配到 京东收派服务费报价表:la_delivery_primary_regions对应的费用类型,明细记录id为".$item['id']);
return [];
}
//8、核验差异—>结算金额-金额核验
$item['verification_difference'] = $item['settlement_amount'] - $item['amount_verification'];
//9、理论计费->(存在重复代码待优化 赶工期......)
// ①费用类型=快递改址费 5;
// ②费用类型=保价费 0.3;
// ③费用类型=快递_转寄服务费 5;
// ④费用类型=快递超长超重,【计费重量】*1元/kg
// ⑤费用类型=快递运费, 始发省&始发市&目的省&目的市去匹配对应 收派服务费报价表中的 始发省&始发市&目的省&目的市 当中对应的报价,使用【理论重量】向上取整 1,按照报价计算
// 若理论重量取整后<=1 计算公式为->首重*0.28;
// 若1<理论重量<=30 计算公式为:(首重+(理论重量-1)*续重1)*0.28;
// 若理论重量>30 计算公式为:(首重+(理论重量-1)*续重1+(理论重量-30)*续重2)*0.28
if($item['fee_type'] == '快递改址费' || $item['fee_type'] == '快递_转寄服务费'){
$item['theoretical_billing'] = 5;
}elseif($item['fee_type'] == '保价费'){
$item['theoretical_billing'] = 0.3;
}elseif($item['fee_type'] == '快递超长超重'){
$item['theoretical_billing'] = $item['billing_weight'] * 1;
}elseif($item['fee_type'] == '快递运费'){
$roundedUp = ceil($item['theoretical_weight']);
if($roundedUp <= 1){
$item['theoretical_billing'] = $deliveryPrimary->first_weight * 0.28;
}elseif($roundedUp <= 30){
$item['theoretical_billing'] = ($deliveryPrimary->first_weight + ($item['billing_weight'] - 1) * $deliveryPrimary->continued_weight1) * 0.28;
}else{ // >30的情况
$item['theoretical_billing'] = ($deliveryPrimary->first_weight + ($item['billing_weight'] - 1) * $deliveryPrimary->continued_weight1 + ($item['billing_weight'] - 30) * $deliveryPrimary->continued_weight2) * 0.28;
}
}else{
Log::warning('【京东收派服务费明细核对】--月份为:'.$yearMonth."的明细表la_primary_regions_mx当中的费用类型fee_type 没有匹配到 京东收派服务费报价表:la_delivery_primary_regions对应的费用类型,明细记录id为".$item['id']);
return [];
}
//10、理论差异->结算金额-理论计费
$item['theoretical_difference'] = $item['settlement_amount'] - $item['theoretical_billing'];
//11、不跨仓收费->(存在重复代码待优化 赶工期......)
// ①费用类型=快递改址费 5;
// ②费用类型=保价费 0.3;
// ③费用类型=快递_转寄服务费 5;
// ④费用类型=快递超长超重,【计费重量】*1元/kg
// ⑤费用类型=快递运费,目的省&目的市,匹配对应 收派服务费报价表中的 目的省&目的市 对应的报价,使用【计费重量】按照一级区域报价计算
// 若计费重量<=1 计算公式为:首重*0.28;
// 若1<计费用重量<=30 计算公式为:(首重+(计费重量-1)*续重1)*0.28;
// 若计费重量>30 计算公式为:(首重+(计费重量-1)*续重1+(计费重量-30)*续重2)*0.28;
$deliveryPrimaryRegions = DeliveryPrimaryRegionsModel::where('province','=',$item['destination_province'])
->where('city','=',$item['destination_city'])
->find();
if(empty($deliveryPrimaryRegions)){
//记录错误日志
Log::warning('【京东收派服务费明细核对】--月份为:'.$yearMonth."的明细表la_primary_regions_mx当中的[目的省&目的市] 没有匹配到 京东收派服务费一级区域表:la_primary_regions对应的 [目的省&目的市],明细记录id为".$item['id']);
return [];
}
if($item['fee_type'] == '快递改址费' || $item['fee_type'] == '快递_转寄服务费'){
$item['non_cross_warehouse_fee'] = 5;
}elseif($item['fee_type'] == '保价费'){
$item['non_cross_warehouse_fee'] = 0.3;
}elseif($item['fee_type'] == '快递超长超重'){
$item['non_cross_warehouse_fee'] = $item['billing_weight'] * 1;
}elseif($item['fee_type'] == '快递运费'){
if($item['billing_weight'] <= 1){
$item['non_cross_warehouse_fee'] = $deliveryPrimaryRegions->first_wt * 0.28;
}elseif($item['billing_weight'] <= 30){
$item['non_cross_warehouse_fee'] = ($deliveryPrimaryRegions->first_wt + ($item['billing_weight'] - 1) * $deliveryPrimaryRegions->add_wt1) * 0.28;
}else{ // >30的情况
$item['non_cross_warehouse_fee'] = ($deliveryPrimaryRegions->first_wt + ($item['billing_weight'] - 1) * $deliveryPrimaryRegions->add_wt1 + ($item['billing_weight'] - 30) * $deliveryPrimaryRegions->add_wt2) * 0.28;
}
}else{
Log::warning('【京东收派服务费明细核对】--月份为:'.$yearMonth."的明细表la_primary_regions_mx当中的[目的省&目的市] 没有匹配到 京东收派服务费一级区域表:la_primary_regions对应的 [目的省&目的市],明细记录id为".$item['id']);
return [];
}
//12、跨仓差异->结算金额-不跨仓收费
$item['cross_warehouse_difference'] = $item['settlement_amount'] - $item['theoretical_billing'];
return $item->toArray();
}
/**
* @notes 根据年月比如2025-05获取当月时间范围 精确到秒 (git有问题未解决 暂时无法写入common.php当中 临时放这)
* @param string $yearMonth
* @return array
* @author 胡军
* @date 2025/07/01
*/
private function getMonthTimeRange(string $yearMonth): array
{
// 验证输入格式 (YYYY-MM)
if (!preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $yearMonth)) {
throw new InvalidArgumentException('输入格式不正确,必须为YYYY-MM格式');
}
list($year, $month) = explode('-', $yearMonth);
// 构建开始时间
$startTime = "{$year}-{$month}-01 00:00:00";
// 使用DateTime类计算当月最后一天
$lastDay = (new \DateTime("{$year}-{$month}-01"))
->modify('last day of this month')
->format('d');
// 构建结束时间
$endTime = "{$year}-{$month}-{$lastDay} 23:59:59";
return [
'startTime' => $startTime,
'endTime' => $endTime
];
}
这是我核算100万数据的代码,虽然使用了分块处理但是我感觉还有优化的空间,我之前优化完的代码就很好我可以提供给你代码做参考:
/**
* @notes 非销售出库明细核对逻辑(优化版)
* @param $yearMonth
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @author 胡军
* @date 2025/06/25
*/
public function nonSalesFeeVerifyDo($yearMonth): bool {
// 获取指定月份的时间范围(月初到月末)
$monthTimeRange = $this->getMonthTimeRange($yearMonth);
// 预加载并缓存报价数据,使用键值对存储(仓库名 => 报价信息)
// 避免在后续循环中重复查询数据库,提高性能
$warehouseQuotes = [];
$warehouseList = NonSalesItemizationModel::whereBetween('business_time', [
$monthTimeRange['startTime'],
$monthTimeRange['endTime']
])->group('warehouse_name')->column('warehouse_name');
// 批量获取所有仓库的报价记录
foreach ($warehouseList as $warehouse) {
// 获取该仓库的报价记录
$quoteResult = NonSalesQuoteModel::where('warehouse', $warehouse)->select()->toArray();
if (!empty($quoteResult)) {
$newQuoteResult = [];
foreach ($quoteResult as $item) {
$newQuoteResult[$item['fee_item']] = $item;
}
$warehouseQuotes[$warehouse] = $newQuoteResult;
}
}
$status = true; // 处理状态标识
$batchSize = 500; // 批次大小即每次读取的数据条数
$reconnectInterval = 200; // 每10万条重新连接一次数据库(200批次=10万条)
$updateCount = 0; // 记录已更新的总记录数
$batchCount = 0; // 记录当前批次数
try {
// 使用游标分批处理数据,每次只加载少量数据到内存
// 避免一次性加载大量数据导致内存溢出
$query = NonSalesItemizationModel::whereBetween('business_time', [
$monthTimeRange['startTime'],
$monthTimeRange['endTime']
]);
$query->chunk($batchSize, function ($items) use (
&$status, // 引用传递处理状态
&$updateCount, // 引用传递更新计数
&$batchCount, // 引用传递批次计数
$warehouseQuotes, // 仓库报价数据
$reconnectInterval, // 重连间隔
$yearMonth
) {
$batchCount++;
$updateBuffer = []; // 批量更新缓冲区,存储待更新的数据
// 遍历当前批次的所有明细记录
foreach ($items as $item) {
$warehouseName = $item->warehouse_name;
$itemData = $item->toArray();
// 检查仓库是否有对应的报价数据
if (isset($warehouseQuotes[$warehouseName])) {
$quoteData = $warehouseQuotes[$warehouseName];
// 处理数量数据,确保不为空
$boxCount = !empty($itemData['box_count']) ? $itemData['box_count'] : 0;
$looseItems = !empty($itemData['loose_items']) ? $itemData['loose_items'] : 0;
// 计算理论费用
$theoreticalFee = $boxCount * $quoteData['整箱']['amount'] + $looseItems * $quoteData['拆零']['amount'];
// 计算费用差异
$feeDifference = $itemData['charge_amount'] - $theoreticalFee;
} else {
// 记录无匹配报价的日志,方便后续排查
Log::warning('【菜鸟非销售出库费明细核对报价单仓库名称不匹配】--月份为:' . $yearMonth .
"的订单明细核对匹配不到非销售出库费报价表la_nonsales_fee_quote当中的仓库名称,明细数据id为" .
$itemData['id']);
// 对于未匹配到的记录,设置为null以便标识
$theoreticalFee = null;
$feeDifference = null;
}
// 准备更新数据,存入缓冲区
$updateBuffer[$itemData['id']] = [
'theoretical_fee' => $theoreticalFee, // 理论费用
'fee_difference' => $feeDifference // 费用差异
];
}
// 使用高效的批量更新方法将缓冲区数据写入数据库
$this->batchUpdate('la_nosales_fee', $updateBuffer, [
'theoretical_fee',
'fee_difference'
]);
$updateCount += count($updateBuffer); // 更新总计数
// 定期重连数据库,避免长时间运行导致连接断开
if ($batchCount % $reconnectInterval === 0) {
$this->reconnectDatabase();
Log::info("菜鸟非销售出库费用核对已处理 {$updateCount} 条,进行数据库重连");
}
// 释放内存,避免内存泄漏
unset($items, $updateBuffer);
});
} catch (\Exception $e) {
// 记录异常信息,确保错误可追溯
Log::error('【菜鸟非销售出库费明细核对异常】--月份为:' . $yearMonth . "的费用核对发生错误:" . $e->getMessage());
$status = false; // 标记处理失败
}
// 记录处理完成信息,包含更新的总记录数
Log::info("菜鸟非销售出库费用核对处理完成,共更新 {$updateCount} 条记录");
return $status;
}
/**
* 高效批量更新方法(单SQL更新多条)
* @param string $table 表名
* @param array $data 更新数据,格式:[id => ['field1' => 'value1', 'field2' => 'value2']]
* @param array $fields 需要更新的字段
*/
private function batchUpdate(string $table, array $data, array $fields)
{
// 数据为空时直接返回,避免无效操作
if (empty($data) || empty($fields)) return;
$cases = []; // 存储CASE WHEN语句的数组
$ids = []; // 存储所有需要更新的ID
$params = []; // 存储预处理语句的参数
// 构建CASE WHEN更新语句(每个字段一个CASE WHEN)
foreach ($fields as $field) {
$cases[$field] = 'CASE id ';
// 为每个ID和对应字段值构建WHEN子句
foreach ($data as $id => $row) {
$cases[$field] .= "WHEN {$id} THEN ? "; // 占位符用于预处理语句
$params[] = $row[$field]; // 对应的值
$ids[] = $id; // 记录ID
}
$cases[$field] .= 'END'; // 结束CASE语句
}
// 去重并拼接ID列表
$idsStr = implode(',', array_unique($ids));
// 构建SET子句(每个字段的CASE WHEN语句)
$setClauses = [];
foreach ($fields as $field) {
$setClauses[] = "{$field} = {$cases[$field]}";
}
// 构建完整的UPDATE SQL语句
/* 最后的执行sql可以视为(案例sql):
UPDATE la_reverse_delivery_fee
SET
theoretical_amount = CASE id
WHEN 1001 THEN 100 -- 当ID=1001时,更新为100
WHEN 1002 THEN 200 -- 当ID=1002时,更新为200
END,
fee_difference = CASE id
WHEN 1001 THEN 5 -- 当ID=1001时,更新为5
WHEN 1002 THEN 10 -- 当ID=1002时,更新为10
END
WHERE id IN (1001,1002);
其实就是巧妙地利用了 SQL 的CASE WHEN语法,将多行更新合并为单条 SQL,是一种常见的数据库优化技巧!
*/
$sql = "UPDATE {$table} SET " . implode(', ', $setClauses) . " WHERE id IN ({$idsStr})";
// 执行预处理语句,提高安全性和性能
Db::execute($sql, $params);
}
// 数据库重连方法,用于长时间运行的任务,避免连接超时
private function reconnectDatabase()
{
try {
$connection = \think\facade\Db::connect();
$connection->close(); // 关闭当前连接
$connection->connect(); // 重新建立连接
} catch (\Exception $e) {
// 记录重连失败日志,但不中断程序执行
Log::error('【菜鸟非销售出库费用核对数据库重连失败】' . $e->getMessage());
}
}
/**
* @notes 根据年月比如2025-05获取当月时间范围 精确到秒
* @param string $yearMonth
* @return array
* @author 胡军
* @date 2025/06/25
*/
private function getMonthTimeRange(string $yearMonth): array
{
// 验证输入格式 (YYYY-MM)
if (!preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $yearMonth)) {
throw new InvalidArgumentException('输入格式不正确,必须为YYYY-MM格式');
}
list($year, $month) = explode('-', $yearMonth);
// 构建开始时间
$startTime = "{$year}-{$month}-01 00:00:00";
// 使用DateTime类计算当月最后一天
$lastDay = (new \DateTime("{$year}-{$month}-01"))
->modify('last day of this month')
->format('d');
// 构建结束时间
$endTime = "{$year}-{$month}-{$lastDay} 23:59:59";
return [
'startTime' => $startTime,
'endTime' => $endTime
];
}
请根据我后面给你的优化完的代码使用的技术方案来优化我给你的要优化的代码,记住一定最后要提供完整代码并且我的原有的注释信息请保留下来方便我阅读
最新发布