Files
zyt/EXPRESS_TRACKING_SYSTEM.md
2026-04-07 18:13:03 +08:00

13 KiB
Raw Permalink Blame History

物流追踪系统完整方案

系统架构

核心功能

  1. 物流信息存储 - 将快递100查询结果存储到数据库
  2. 自动更新机制 - 定时自动查询更新物流状态
  3. 状态变更检测 - 监控物流状态变化
  4. 异常通知 - 签收、异常时自动通知
  5. 查询日志 - 记录所有查询请求和响应

数据库表结构

表名 说明 用途
zyt_express_tracking 物流追踪主表 存储快递单基本信息和当前状态
zyt_express_trace 物流轨迹明细表 存储每条物流轨迹记录
zyt_express_state_log 状态变更记录表 记录状态变化历史
zyt_express_query_log 查询日志表 记录所有查询请求

安装步骤

1. 创建数据库表

# 执行SQL脚本
mysql -u root -p your_database < server/database/migrations/create_express_tracking_tables.sql

或在数据库管理工具中执行 create_express_tracking_tables.sql 文件。

2. 注册定时任务命令

server/config/console.php 中添加:

'commands' => [
    // ... 其他命令
    'express:auto-update' => \app\command\ExpressAutoUpdate::class,
],

3. 配置crontab定时任务

# 编辑crontab
crontab -e

# 添加以下行(每10分钟执行一次)
*/10 * * * * cd /path/to/your/server && php think express:auto-update >> /dev/null 2>&1

4. 测试定时任务

cd server
php think express:auto-update

应该看到输出:

开始自动更新物流信息...
更新完成!
总数: 0
成功: 0
失败: 0
耗时: 0.05秒

使用方法

1. 创建物流追踪

当订单发货时,创建物流追踪记录:

use app\common\service\ExpressTrackingService;

// 创建追踪记录
$tracking = ExpressTrackingService::createOrUpdate([
    'order_id' => 123,                          // 订单ID
    'order_type' => 'prescription',             // 订单类型
    'tracking_number' => 'JD0230761381812',     // 快递单号
    'express_company' => 'jd',                  // 快递公司
    'recipient_phone' => '13800138000',         // 收件人手机
    'recipient_name' => '张三',                  // 收件人姓名
    'recipient_address' => '北京市朝阳区xxx',    // 收件地址
]);

// 系统会立即查询一次物流信息并存储

2. 手动更新物流信息

// 更新指定追踪记录
$result = ExpressTrackingService::queryAndUpdate($trackingId, false);

3. 获取物流详情

// 获取完整的物流信息(包含轨迹)
$detail = ExpressTrackingService::getDetail($trackingId);

// 返回数据包含:
// - 基本信息
// - 当前状态
// - 所有轨迹记录
// - 状态变更历史

4. 自动更新配置

// 修改更新间隔(秒)
$tracking->update_interval = 1800; // 30分钟
$tracking->save();

// 停止自动更新
$tracking->auto_update = 0;
$tracking->save();

// 启用自动更新
$tracking->auto_update = 1;
$tracking->next_update_time = time(); // 立即更新
$tracking->save();

集成到现有订单系统

修改订单创建/编辑逻辑

PrescriptionOrderLogic.php 中:

public static function create(array $params, int $adminId, array $adminInfo)
{
    // ... 原有创建逻辑
    
    $order->save();
    
    // 创建物流追踪
    if (!empty($params['tracking_number'])) {
        ExpressTrackingService::createOrUpdate([
            'order_id' => $order->id,
            'order_type' => 'prescription',
            'tracking_number' => $params['tracking_number'],
            'express_company' => $params['express_company'] ?? 'auto',
            'recipient_phone' => $order->recipient_phone,
            'recipient_name' => $order->recipient_name,
            'recipient_address' => $order->shipping_address,
        ]);
    }
    
    return $out;
}

public static function edit(array $params, int $adminId, array $adminInfo)
{
    // ... 原有编辑逻辑
    
    // 更新物流追踪
    if (array_key_exists('tracking_number', $params)) {
        if (!empty($params['tracking_number'])) {
            ExpressTrackingService::createOrUpdate([
                'order_id' => $order->id,
                'order_type' => 'prescription',
                'tracking_number' => $params['tracking_number'],
                'express_company' => $order->express_company,
                'recipient_phone' => $order->recipient_phone,
                'recipient_name' => $order->recipient_name,
                'recipient_address' => $order->shipping_address,
            ]);
        }
    }
    
    return $out;
}

修改物流查询接口

PrescriptionOrderLogic.php 中:

public static function logisticsTrace(int $id, string $expressCompanyOverride, int $adminId, array $adminInfo): ?array
{
    // ... 原有逻辑
    
    // 先从数据库获取
    $tracking = ExpressTracking::where('order_id', $id)
        ->where('order_type', 'prescription')
        ->whereNull('delete_time')
        ->find();
    
    if ($tracking) {
        // 如果距离上次查询超过5分钟,重新查询
        if (time() - $tracking->last_query_time > 300) {
            ExpressTrackingService::queryAndUpdate($tracking->id, false);
            $tracking->refresh();
        }
        
        // 返回数据库中的数据
        $payload = [
            'carrier' => $tracking->express_company,
            'carrier_label' => $tracking->express_company_name,
            'kuaidi_com' => $tracking->express_company,
            'traces' => $tracking->traces->map(function($trace) {
                return [
                    'time' => $trace->trace_time,
                    'context' => $trace->trace_context,
                    'status' => $trace->status,
                    'location' => $trace->location,
                ];
            })->toArray(),
            'state' => $tracking->current_state,
            'state_text' => $tracking->current_state_text,
            'source' => $tracking->data_source,
            'hint' => '',
            'official_url' => ExpressTrackService::officialUrls($tracking->tracking_number)[$tracking->express_company] ?? '',
            'official_urls' => ExpressTrackService::officialUrls($tracking->tracking_number),
            'tracking_number' => $tracking->tracking_number,
            'express_company_used' => $tracking->express_company,
            'is_signed' => $tracking->is_signed,
            'sign_time' => $tracking->sign_time,
            'estimated_arrival_time' => $tracking->estimated_arrival_time,
        ];
        
        return $payload;
    }
    
    // 如果数据库中没有,走原有逻辑
    // ... 原有查询逻辑
}

自动更新机制

更新策略

  1. 创建时立即查询 - 创建追踪记录时立即查询一次
  2. 定时自动更新 - 每10分钟检查一次需要更新的记录
  3. 智能更新间隔 - 默认30分钟更新一次
  4. 终态停止更新 - 签收、退签、拒签后停止自动更新

更新间隔建议

物流状态 更新间隔 说明
已下单/待揽收 30分钟 等待揽收
已揽收/在途 30分钟 运输中
派件中 15分钟 即将签收,加快更新
已签收 停止 终态,无需更新
疑难/异常 60分钟 降低频率

定时任务配置

# 每10分钟执行一次(推荐)
*/10 * * * * cd /path/to/server && php think express:auto-update

# 每5分钟执行一次(高频)
*/5 * * * * cd /path/to/server && php think express:auto-update

# 每30分钟执行一次(低频)
*/30 * * * * cd /path/to/server && php think express:auto-update

状态变更通知

通知触发条件

  1. 签收通知 - 快递签收时通知
  2. 异常通知 - 出现疑难、拒签等异常时通知
  3. 状态变更 - 每次状态变化都会记录

通知渠道(可扩展)

1. 企业微信通知

// 在 ExpressTrackingService::sendNotification() 中实现
use app\common\service\WechatWorkService;

WechatWorkService::sendMessage([
    'touser' => $userId,
    'msgtype' => 'text',
    'text' => [
        'content' => "【物流通知】\n" .
                    "快递单号:{$tracking->tracking_number}\n" .
                    "状态变更:{$log->old_state_text}{$log->new_state_text}\n" .
                    "最新动态:{$log->change_reason}"
    ]
]);

2. 短信通知

// 对接短信服务商
SmsService::send($tracking->recipient_phone, [
    'template' => 'express_status_change',
    'params' => [
        'tracking_number' => $tracking->tracking_number,
        'status' => $log->new_state_text,
    ]
]);

3. 系统内通知

// 创建系统通知记录
NoticeService::create([
    'user_id' => $userId,
    'title' => '物流状态更新',
    'content' => "您的快递 {$tracking->tracking_number}{$log->new_state_text}",
]);

数据统计和分析

查询统计

-- 查询成功率
SELECT 
    DATE(FROM_UNIXTIME(query_time)) as date,
    COUNT(*) as total,
    SUM(is_success) as success,
    ROUND(SUM(is_success) / COUNT(*) * 100, 2) as success_rate
FROM zyt_express_query_log
GROUP BY date
ORDER BY date DESC;

-- 平均响应时间
SELECT 
    query_source,
    AVG(response_time) as avg_response_time,
    MAX(response_time) as max_response_time
FROM zyt_express_query_log
WHERE is_success = 1
GROUP BY query_source;

物流状态分布

-- 当前物流状态分布
SELECT 
    current_state,
    current_state_text,
    COUNT(*) as count
FROM zyt_express_tracking
WHERE delete_time IS NULL
GROUP BY current_state, current_state_text
ORDER BY count DESC;

-- 签收率统计
SELECT 
    DATE(FROM_UNIXTIME(sign_time)) as date,
    COUNT(*) as signed_count
FROM zyt_express_tracking
WHERE is_signed = 1
GROUP BY date
ORDER BY date DESC;

异常统计

-- 异常快递统计
SELECT 
    tracking_number,
    express_company_name,
    current_state_text,
    latest_trace_context,
    FROM_UNIXTIME(update_time) as update_time
FROM zyt_express_tracking
WHERE current_state IN ('2', '13', '14')  -- 疑难、清关异常、拒签
AND delete_time IS NULL
ORDER BY update_time DESC;

性能优化

1. 批量更新优化

// 每次处理50条记录
ExpressTrackingService::autoUpdateBatch(50);

// 根据服务器性能调整
// - 性能好:100条
// - 性能一般:50条
// - 性能差:20条

2. 查询频率控制

// 避免频繁查询同一单号
if (time() - $tracking->last_query_time < 300) {
    // 5分钟内不重复查询
    return $cachedResult;
}

3. 索引优化

数据库表已创建必要索引:

  • idx_order_id - 订单ID索引
  • idx_auto_update - 自动更新索引
  • idx_tracking_number - 快递单号索引

4. 数据清理

-- 清理90天前的查询日志
DELETE FROM zyt_express_query_log 
WHERE create_time < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY));

-- 清理已签收超过30天的轨迹明细
DELETE t FROM zyt_express_trace t
INNER JOIN zyt_express_tracking tr ON t.tracking_id = tr.id
WHERE tr.is_signed = 1 
AND tr.sign_time < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY));

监控和告警

1. 监控指标

  • 自动更新成功率
  • 平均响应时间
  • 异常快递数量
  • 查询失败率

2. 告警规则

// 在定时任务中添加告警逻辑
$result = ExpressTrackingService::autoUpdateBatch(50);

if ($result['failed'] > $result['success'] * 0.5) {
    // 失败率超过50%,发送告警
    AlertService::send('物流自动更新失败率过高', $result);
}

常见问题

Q1: 定时任务不执行?

检查步骤:

  1. 确认crontab已配置:crontab -l
  2. 检查PHP路径:which php
  3. 检查项目路径是否正确
  4. 查看cron日志:tail -f /var/log/cron

Q2: 更新频率太高,快递100余额消耗快?

解决方案:

  1. 增加更新间隔:update_interval = 36001小时)
  2. 减少批量处理数量:autoUpdateBatch(20)
  3. 签收后立即停止更新
  4. 使用智能更新策略

Q3: 如何查看某个快递的完整历史?

$detail = ExpressTrackingService::getDetail($trackingId);

// 包含:
// - traces: 所有轨迹记录
// - state_logs: 状态变更历史

Q4: 如何手动触发更新?

// 方法1:通过服务类
ExpressTrackingService::queryAndUpdate($trackingId, false);

// 方法2:通过命令行
php think express:auto-update

总结

优势

  1. 数据持久化 - 所有物流信息存储在数据库,随时查询
  2. 自动更新 - 无需手动查询,系统自动追踪
  3. 状态监控 - 实时监控物流状态变化
  4. 异常通知 - 及时发现和处理异常
  5. 数据分析 - 支持物流数据统计分析

成本

  1. 快递100费用 - 按查询次数计费
  2. 服务器资源 - 定时任务占用少量CPU和内存
  3. 数据库存储 - 需要额外的存储空间

建议

  1. 充值快递100 - 确保有足够余额
  2. 合理设置更新间隔 - 平衡实时性和成本
  3. 定期清理数据 - 避免数据库膨胀
  4. 监控运行状态 - 及时发现问题

相关文档

  • LOGISTICS_INTEGRATION_GUIDE.md - 物流集成指南
  • KUAIDI100_ACCOUNT_ISSUE.md - 快递100账号问题
  • JTEXPRESS_SUPPORT_ADDED.md - 极兔速递支持