龙飞 vor 7 Monaten
Ursprung
Commit
4668407b6e
1 geänderte Dateien mit 214 neuen und 58 gelöschten Zeilen
  1. 214
    58
      src/SyncService.php

+ 214
- 58
src/SyncService.php Datei anzeigen

@@ -58,7 +58,7 @@ abstract class SyncService
58 58
     /**
59 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 64
      * Redis key, 格式: key + 表名
@@ -80,15 +80,15 @@ abstract class SyncService
80 80
     public function sync(): bool
81 81
     {
82 82
         try {
83
-            // 获取上次同步时间
83
+            // 获取上次同步时间 - 增加防御性处理
84 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 87
             // 从A服务器获取更新数据
88 88
             $response = $this->fetchUpdatesFromServer($lastSyncTime);
89 89
 
90 90
             if ($response['err'] > 0 || empty($response['data'])) {
91
-                Log::info("远程 {$this->getTable()} 表无更新数据");
91
+                $this->logInfo("远程 {$this->getTable()} 表无更新数据");
92 92
                 $this->updateLastSyncTime(Carbon::now());
93 93
                 return true;
94 94
             }
@@ -97,16 +97,14 @@ abstract class SyncService
97 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 105
             return true;
108 106
         } catch (\Exception $e) {
109
-            Log::error("数据表 {$this->getTable()} 更新失败: " . $e->getMessage());
107
+            $this->logError("数据表 {$this->getTable()} 更新失败: " . $e->getMessage());
110 108
             return false;
111 109
         }
112 110
     }
@@ -140,30 +138,46 @@ abstract class SyncService
140 138
      */
141 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,32 +193,136 @@ abstract class SyncService
179 193
     }
180 194
 
181 195
     /**
182
-     * 处理JSON字段
196
+     * 处理JSON字段 - 采用智能策略尝试解析JSON数据,失败时进行清理
183 197
      */
184 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,7 +354,7 @@ abstract class SyncService
236 354
     protected function afterUpdate($id): void {
237 355
         //$this->_model->getOne($id, true);
238 356
     }
239
-    
357
+
240 358
     /**
241 359
      * 创建后处理数据
242 360
      * @param $id
@@ -260,16 +378,23 @@ abstract class SyncService
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 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,10 +402,41 @@ abstract class SyncService
277 402
      */
278 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 417
     protected function getTable(): string {
284 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
 }