背景
顧客は、商品情報や正確な在庫を保持するジュエリーERPを長年使用してきたが、独自の発注システムでは事業展開のニーズに対応できなくなったため、PC版と小型プログラム版に切り替えることになった。より高度で充実した機能を備えたジュエリーオーダーシステム - プレミアムオーダー。
ニーズの表明
顧客は、ERP の製品情報と在庫情報が自動的に発注システムに接続され、運用負荷が軽減され、データの同期が維持されることを望んでいます。
実行計画
当初、ERP はページングをサポートするデータ クエリ インターフェイスのみを提供しており、クエリ インターフェイスは製品バーコード、製品名、製品作成時刻の 3 つのフィールドによる検索をサポートしていました。
分析したところ、現在のERPインターフェースでは、製品数が多く、ERPサーバーの構成や帯域幅が十分ではなく、応答速度も遅いため、リアルタイムのデータ同期を実現することが不可能であることがわかりました。あまりにも頻繁なクエリをサポートできないため、ERP 側と連絡を取り、「最終更新時刻」フィールドを追加し、クエリ インターフェイスの「最終更新時刻」フィールド間隔に基づいてクエリ サポートを追加し、順序付けを追加するように依頼しました。システムは、15 分ごとに変更されたクエリを 15 分ごとに開始しました。製品在庫クエリの場合、結果が取得された場合、データは同期されます。結果が空の場合は、この期間内に製品情報が変更されていないことを意味します。この時間間隔を更新済みとしてマークし、次の更新を待ちます。ロジックについては、以下のフローチャートを参照してください。
ソリューションの利点
ロジックは厳密であり、更新ログが書き込まれる前に、各時間間隔の各ページで同期が成功したことを確認する必要があるため、2 つのシステム間のデータ同期における一般的なネットワーク エラーはデータ同期エラーを引き起こしません。問題が発生した場合、正常に戻った後、最後に更新されたレコードから同期タスクを復元し、更新を続行できます。さらに、同期プロセス全体が視覚的に出力されるため、非常に明確かつ簡単です問題を追跡するための詳細なログもあります。
フローチャート
データベース設計
フィールドの説明:
コアコードリファレンス (php)
これはスケジュールされたタスクのメインメソッド、つまり入口です。
/**
* [定时任务入口]增量同步商品信息,主要是找到更新到哪个时间段的哪一页了
*/
public function additionalSync(){
Tools::realTimeOutputPrepare();
$page = 1;
$lastRecord = ErpSyncModel::open()->order('updateAt', 'desc')->first();
if($lastRecord){
if($lastRecord['status'] === 1){
//说明上次的更新已经完成
$startTime = $lastRecord['endTime']+1;
}else{
//如果未完成则继续更新
$startTime = $lastRecord['startTime'];
$page = $lastRecord['page'];
if(time() - $lastRecord['updateAt'] <= 60){
die('距离上次更新未超过60秒,暂不执行更新');
}
}
}else{
//没有记录则从头开始
$startTime = strtotime('-1 days');
}
$endTime = $startTime + 15*60;//每15分钟为一个周期
if($endTime > time()){
die('当前已经是最新数据了,请等待下一轮更新');
}
$this->pullData($startTime, $endTime, $page);
}
以下は、ERP インターフェースにアクセスしてデータを同期し、同期レコードを更新する手順です。
/**
* 拉取数据
* @param $startTime
* @param $endTime
* @param $page
*/
public function pullData($startTime, $endTime, $page){
while(true){
$startTimeString = urlencode(date('Y-m-d H:i:s', $startTime));
$endTimeString = urlencode(date('Y-m-d H:i:s', $endTime));
Tools::realTimeOutput('正在获取['.date('Y-m-d H:i:s', $startTime).']至['.date('Y-m-d H:i:s', $endTime).']的数据,当前是第['.$page.']页');
$url = 'https://api.xxx.com?pageId='.$page.'&pageSize='.$this->pageSize.'&startCreateDate='.$startTimeString.'&endCreateDate='.$endTimeString;
$res = Tools::curlGet($url, 30);
if($res['success']){
//CURL成功
$responseData = Tools::jsonToArray($res['data']);
if(intval($responseData['status']) === 200){
//表示接口返回是成功的
$productList = $responseData['data'];//商品列表数据
$getProductCount = count($productList);
Tools::realTimeOutput('已获取到第['.$page.']页数据,共['.$getProductCount.']条记录');
$upsertData = [
'startTime' => $startTime,
'endTime' => $endTime,
'count' => $getProductCount,
'page' => $page,
'status' => 0//表示这个时间段的已经拉取完了
];
if($getProductCount === 0){
$upsertData['status'] = 1;//如果没有记录了就将记录状态改为完成
ErpSyncModel::open()->upsert($upsertData, $upsertData, 'startTime,endTime');
break;
}else{
//如果有记录就更新记录
ErpSyncModel::open()->upsert($upsertData, $upsertData, 'startTime,endTime');
try{
foreach($productList as $item){
$this->importOneByOne($item);
}
if($getProductCount >= $this->pageSize){
//说明还有下一页
++$page;
continue;
}else{
//说明没有下一页了
$upsertData['status'] = 1;
ErpSyncModel::open()->upsert($upsertData, $upsertData, 'startTime,endTime');
break;
}
}catch (Exception $e){
$upsertData['data'] = $productList;
$upsertData['message'] = $e->getMessage();
ErpSyncExceptionModel::open()->add($upsertData);
++$page;
}catch(\PDOException $pe){
$upsertData['data'] = $productList;
$upsertData['message'] = $pe->getMessage();
ErpSyncExceptionModel::open()->add($upsertData);
++$page;
}
}
}else{
Tools::realTimeOutput('CURL未获取到数据,5秒后将重试获取第'.$page.'页');
sleep(5);
}
}else{
//如果curl获取失败就睡5秒再试
Tools::realTimeOutput('CURL获取失败,5秒后将重试获取第'.$page.'页');
sleep(5);
}
}