龙飞 пре 7 месеци
родитељ
комит
4668407b6e
1 измењених фајлова са 214 додато и 58 уклоњено
  1. 214
    58
      src/SyncService.php

+ 214
- 58
src/SyncService.php Прегледај датотеку

58
     /**
58
     /**
59
      * @var array 有哪些字段需要特殊处理的, 将json字符串转化成json
59
      * @var array 有哪些字段需要特殊处理的, 将json字符串转化成json
60
      */
60
      */
61
-    protected array $json_fields = ['title', 'content', 'addrress'];
61
+    protected array $json_fields = [];
62
 
62
 
63
     /**
63
     /**
64
      * Redis key, 格式: key + 表名
64
      * Redis key, 格式: key + 表名
80
     public function sync(): bool
80
     public function sync(): bool
81
     {
81
     {
82
         try {
82
         try {
83
-            // 获取上次同步时间
83
+            // 获取上次同步时间 - 增加防御性处理
84
             $lastSyncTime = $this->getLastSyncTime();
84
             $lastSyncTime = $this->getLastSyncTime();
85
-            Log::info("更新 {$this->getTable()} 表开始, 最后更新时间: " . $lastSyncTime->format('Y-m-d H:i:s'));
85
+            $this->logInfo("更新 {$this->getTable()} 表开始, 最后更新时间: " . $lastSyncTime->format('Y-m-d H:i:s'));
86
 
86
 
87
             // 从A服务器获取更新数据
87
             // 从A服务器获取更新数据
88
             $response = $this->fetchUpdatesFromServer($lastSyncTime);
88
             $response = $this->fetchUpdatesFromServer($lastSyncTime);
89
 
89
 
90
             if ($response['err'] > 0 || empty($response['data'])) {
90
             if ($response['err'] > 0 || empty($response['data'])) {
91
-                Log::info("远程 {$this->getTable()} 表无更新数据");
91
+                $this->logInfo("远程 {$this->getTable()} 表无更新数据");
92
                 $this->updateLastSyncTime(Carbon::now());
92
                 $this->updateLastSyncTime(Carbon::now());
93
                 return true;
93
                 return true;
94
             }
94
             }
97
             $this->processMeetingData($response['data']);
97
             $this->processMeetingData($response['data']);
98
 
98
 
99
             // 更新最后同步时间
99
             // 更新最后同步时间
100
-            $latestUpdate = $response['latest_update']
101
-                ? Carbon::createFromFormat('Y-m-d H:i:s', $response['latest_update'])
102
-                : Carbon::now();
100
+            // 避免使用可能有问题的latest_update,直接使用当前时间
101
+            $currentTime = Carbon::now();
102
+            $this->updateLastSyncTime($currentTime);
103
+            $this->logInfo("更新 {$this->getTable()} 表完成. 最后更新时间: " . $currentTime->format('Y-m-d H:i:s'));
103
 
104
 
104
-            $this->updateLastSyncTime($latestUpdate);
105
-
106
-            Log::info("更新 {$this->getTable()} 表成功. 最后更新时间: " . $latestUpdate->format('Y-m-d H:i:s'));
107
             return true;
105
             return true;
108
         } catch (\Exception $e) {
106
         } catch (\Exception $e) {
109
-            Log::error("数据表 {$this->getTable()} 更新失败: " . $e->getMessage());
107
+            $this->logError("数据表 {$this->getTable()} 更新失败: " . $e->getMessage());
110
             return false;
108
             return false;
111
         }
109
         }
112
     }
110
     }
140
      */
138
      */
141
     protected function processMeetingData(array $meetings): void
139
     protected function processMeetingData(array $meetings): void
142
     {
140
     {
143
-        foreach ($meetings as $meetingData) {
144
-            // 处理字段
145
-            $processedData = $this->processJsonFields($meetingData);
141
+        foreach ($meetings as $index => $meetingData) {
142
+            try {
143
+                // 确保数据是数组
144
+                if (!is_array($meetingData)) {
145
+                    continue;
146
+                }
146
 
147
 
147
-            $id = $processedData[$this->key]; // 主键
148
+                // 处理字段
149
+                $processedData = $this->processJsonFields($meetingData);
148
 
150
 
149
-            // 状态为0表示需要删除
150
-            if ($this->is_delete($processedData)) {
151
-                $this->handleDeletion($id);
152
-                Log::info("删除数据, id=" . $id);
153
-                continue;
154
-            }
151
+                // 检查主键是否存在
152
+                if (!isset($processedData[$this->key])) {
153
+                    $this->logError("缺少主键字段,跳过此记录");
154
+                    continue;
155
+                }
155
 
156
 
156
-            // 检查记录是否存在
157
-            $meeting = $this->_model->find($id);
158
-
159
-            if ($meeting) {
160
-                // 更新现有记录,保留B服务器特有字段
161
-                $this->updateMeeting($meeting, $processedData);
162
-                Log::info("更新数据, id=" . $id);
163
-            } else {
164
-                // 创建新记录,设置B服务器特有字段默认值
165
-                $this->createMeeting($processedData);
166
-                Log::info("创建数据, id=" . $id);
157
+                $id = $processedData[$this->key]; // 主键
158
+
159
+                // 状态为0表示需要删除
160
+                if ($this->is_delete($processedData)) {
161
+                    $this->handleDeletion($id);
162
+                    $this->logInfo("删除数据, id=" . $id);
163
+                    continue;
164
+                }
165
+
166
+                // 检查记录是否存在
167
+                $meeting = $this->_model->find($id);
168
+                if ($meeting) {
169
+                    // 更新现有记录,保留B服务器特有字段
170
+                    $this->updateMeeting($meeting, $processedData);
171
+                    $this->logInfo("更新数据, id=" . $id);
172
+                } else {
173
+                    // 创建新记录,设置B服务器特有字段默认值
174
+                    $this->createMeeting($processedData);
175
+                    $this->logInfo("创建数据, id=" . $id);
176
+                }
177
+            } catch (\Exception $e) {
178
+                $this->logError("处理单条记录时发生未捕获的异常: " . $e->getMessage());
179
+                // 继续处理下一条记录
180
+                continue;
167
             }
181
             }
168
         }
182
         }
169
     }
183
     }
179
     }
193
     }
180
 
194
 
181
     /**
195
     /**
182
-     * 处理JSON字段
196
+     * 处理JSON字段 - 采用智能策略尝试解析JSON数据,失败时进行清理
183
      */
197
      */
184
     protected function processJsonFields(array $data): array
198
     protected function processJsonFields(array $data): array
185
     {
199
     {
186
-        // 先删除字段
187
-        $data = Arr::except($data, array_keys($this->mapping_add));
188
-        $data = Arr::except($data, $this->filter);
189
-        // 再替换字段名
190
-        foreach ($this->mapping_replace as $key_serv => $key_local) {
191
-            if (isset($data[$key_serv])) {
192
-                $data[$key_local] = $data[$key_serv]; // 赋值给本地字段
193
-                unset($data[$key_serv]); // 删除原服务器字段,避免冲突
200
+        try {
201
+            // 先删除字段
202
+            $data = Arr::except($data, array_keys($this->mapping_add));
203
+            $data = Arr::except($data, $this->filter);
204
+
205
+            // 再替换字段名
206
+            foreach ($this->mapping_replace as $key_serv => $key_local) {
207
+                if (isset($data[$key_serv])) {
208
+                    $data[$key_local] = $data[$key_serv]; // 赋值给本地字段
209
+                    unset($data[$key_serv]); // 删除原服务器字段,避免冲突
210
+                }
211
+            }
212
+
213
+            // 智能处理JSON字段
214
+            foreach ($this->json_fields as $field) {
215
+                // 检查字段是否存在
216
+                if (!isset($data[$field])) {
217
+                    $data[$field] = [];
218
+                    $this->logInfo("处理后JSON字段 - {$field}: 字段不存在,已设置为空数组");
219
+                    continue;
220
+                }
221
+
222
+                // 如果已经是数组,不需要解析
223
+                if (is_array($data[$field])) {
224
+                    $this->logInfo("处理后JSON字段 - {$field}: 已是数组格式,无需解析");
225
+                    continue;
226
+                }
227
+
228
+                // 如果是null或空字符串,设置为空数组
229
+                if ($data[$field] === null || $data[$field] === '') {
230
+                    $data[$field] = [];
231
+                    $this->logInfo("处理后JSON字段 - {$field}: 为null或空,已设置为空数组");
232
+                    continue;
233
+                }
234
+
235
+                // 确保是字符串
236
+                if (!is_string($data[$field])) {
237
+                    $data[$field] = [];
238
+                    $this->logInfo("处理后JSON字段 - {$field}: 非字符串格式,已设置为空数组");
239
+                    continue;
240
+                }
241
+
242
+                // 尝试正常解析JSON
243
+                $parsed = json_decode($data[$field], true);
244
+                if (json_last_error() === JSON_ERROR_NONE && is_array($parsed)) {
245
+                    $data[$field] = $parsed;
246
+                    $this->logInfo("处理后JSON字段 - {$field}: JSON解析成功");
247
+                    continue;
248
+                }
249
+
250
+                // 解析失败,尝试清理JSON字符串
251
+                $cleanedJson = $this->cleanJsonString($data[$field]);
252
+
253
+                // 尝试解析清理后的JSON
254
+                $parsed = json_decode($cleanedJson, true);
255
+                if (json_last_error() === JSON_ERROR_NONE && is_array($parsed)) {
256
+                    $data[$field] = $parsed;
257
+                    $this->logInfo("处理后JSON字段 - {$field}: 清理后JSON解析成功");
258
+                    continue;
259
+                }
260
+
261
+                // 所有尝试都失败,设置为空数组
262
+                $data[$field] = [];
263
+                $this->logInfo("处理后JSON字段 - {$field}: 清理后仍解析失败,已设置为空数组");
264
+            }
265
+
266
+            return $data;
267
+        } catch (\Exception $e) {
268
+            $this->logError("处理JSON字段时出错: " . $e->getMessage());
269
+
270
+            // 创建一个全新的数据数组,只保留主键,其他字段都设置为安全值
271
+            $safeData = [];
272
+            if (isset($data[$this->key])) {
273
+                $safeData[$this->key] = $data[$this->key];
274
+            }
275
+
276
+            // 确保所有json字段都设置为空数组
277
+            foreach ($this->json_fields as $field) {
278
+                $safeData[$field] = [];
194
             }
279
             }
280
+
281
+            return $safeData;
195
         }
282
         }
196
-        // json字符串 => json
197
-        foreach ($this->json_fields as $field) {
198
-            if (isset($data[$field]) && is_string($data[$field])) {
199
-                $data[$field] = json_decode($data[$field], true);
200
-                // 容错:若JSON解码失败,保留原字符串(避免数据丢失)
201
-                if (json_last_error() !== JSON_ERROR_NONE) {
202
-                    Log::warning("JSON解码失败,字段: {$field},值: {$data[$field]}");
203
-                    $data[$field] = $data[$field]; // 保留原字符串
283
+    }
284
+
285
+    /**
286
+     * 清理JSON字符串以修复可能的格式问题
287
+     * @param string $json 原始JSON字符串
288
+     * @return string 清理后的JSON字符串
289
+     */
290
+    private function cleanJsonString(string $json): string
291
+    {
292
+        try {
293
+            // 移除首尾空白字符
294
+            $json = trim($json);
295
+
296
+            // 检查是否是有效的数组或对象格式
297
+            if (!preg_match('/^\[.*\]$|^\{.*\}$/s', $json)) {
298
+                // 如果不是有效的数组或对象,检查是否需要添加括号
299
+                if (substr($json, 0, 1) !== '[' && substr($json, 0, 1) !== '{') {
300
+                    // 尝试作为对象处理
301
+                    if (strpos($json, ':') !== false) {
302
+                        $json = '{"' . str_replace([':', ','], ['":"', '","'], $json) . '"}';
303
+                    } else {
304
+                        // 尝试作为数组处理
305
+                        $json = '[' . $json . ']';
306
+                    }
204
                 }
307
                 }
205
             }
308
             }
309
+
310
+            // 处理常见的JSON格式问题
311
+            // 1. 移除末尾的逗号
312
+            $json = preg_replace('/,(\s*[}\]])/', '$1', $json);
313
+
314
+            // 2. 确保所有键都有引号
315
+            $json = preg_replace('/([{,])\s*([\w\-]+)\s*:/', '$1"$2":', $json);
316
+
317
+            // 3. 确保字符串值有引号
318
+            $json = preg_replace('/:\s*([^"\'\[\{\s][^\[\{\s]*)/', ': "$1"', $json);
319
+
320
+            return $json;
321
+        } catch (\Exception $e) {
322
+            $this->logError("清理JSON字符串时出错: " . $e->getMessage());
323
+            // 出错时返回空对象
324
+            return '{}';
206
         }
325
         }
207
-        return $data;
208
     }
326
     }
209
 
327
 
210
     /**
328
     /**
236
     protected function afterUpdate($id): void {
354
     protected function afterUpdate($id): void {
237
         //$this->_model->getOne($id, true);
355
         //$this->_model->getOne($id, true);
238
     }
356
     }
239
-    
357
+
240
     /**
358
     /**
241
      * 创建后处理数据
359
      * 创建后处理数据
242
      * @param $id
360
      * @param $id
260
     /**
378
     /**
261
      * 获取上次同步时间
379
      * 获取上次同步时间
262
      */
380
      */
263
-    protected function getLastSyncTime(): bool|Carbon
381
+    protected function getLastSyncTime(): \Illuminate\Support\Carbon|bool
264
     {
382
     {
265
-        $lastSync = Redis::get(self::REDISKEY . $this->getTable());
383
+        try {
384
+            $redisKey = self::REDISKEY . $this->getTable();
385
+            $lastSync = Redis::get($redisKey);
386
+
387
+            if (!$lastSync) {
388
+                // 如果没有同步记录,默认使用系统最早时间
389
+                return Carbon::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00');
390
+            }
266
 
391
 
267
-        if (!$lastSync) {
268
-            // 如果没有同步记录,默认使用系统最早时间
392
+            return Carbon::createFromFormat('Y-m-d H:i:s', $lastSync);
393
+        } catch (\Exception $e) {
394
+            $this->logError("获取Redis数据失败: " . $e->getMessage());
395
+            // Redis操作失败时使用默认时间
269
             return Carbon::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00');
396
             return Carbon::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00');
270
         }
397
         }
271
-
272
-        return Carbon::createFromFormat('Y-m-d H:i:s', $lastSync);
273
     }
398
     }
274
 
399
 
275
     /**
400
     /**
277
      */
402
      */
278
     protected function updateLastSyncTime(Carbon $time): void
403
     protected function updateLastSyncTime(Carbon $time): void
279
     {
404
     {
280
-        Redis::set(self::REDISKEY . $this->getTable(), $time->format('Y-m-d H:i:s'));
405
+        try {
406
+            $redisKey = self::REDISKEY . $this->getTable();
407
+            $timeString = $time->format('Y-m-d H:i:s');
408
+
409
+            // 增加Redis操作的异常处理
410
+            Redis::set($redisKey, $timeString);
411
+        } catch (\Exception $e) {
412
+            $this->logError("更新Redis同步时间失败: " . $e->getMessage());
413
+            // 即使Redis更新失败,也不中断整个同步过程
414
+        }
281
     }
415
     }
282
 
416
 
283
     protected function getTable(): string {
417
     protected function getTable(): string {
284
         return $this->_model->getTable();
418
         return $this->_model->getTable();
285
     }
419
     }
420
+
421
+    /**
422
+     * 记录信息级别日志
423
+     * 子类可以重写此方法来自定义日志行为
424
+     *
425
+     * @param string $message 日志消息
426
+     */
427
+    protected function logInfo(string $message): void
428
+    {
429
+        Log::info($message);
430
+    }
431
+
432
+    /**
433
+     * 记录错误级别日志
434
+     * 子类可以重写此方法来自定义日志行为
435
+     *
436
+     * @param string $message 日志消息
437
+     */
438
+    protected function logError(string $message): void
439
+    {
440
+        Log::error($message);
441
+    }
286
 }
442
 }