|
|
@@ -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
|
}
|