diff --git a/.gitignore b/.gitignore
index 3048ae356..68fcda8cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,6 @@ Touchkebao/.specstory/
Serverruntime/
Moncter/提示词/
*.log
+*.code-workspace
+项目整体分析报告.md
+Server/docs/traffic_pool_design.md
diff --git a/Server/README_scheduler.md b/Server/README_scheduler.md
index 4c485684f..64d7d6ebd 100644
--- a/Server/README_scheduler.md
+++ b/Server/README_scheduler.md
@@ -44,7 +44,7 @@
```bash
# 每分钟执行一次调度器(调度器内部会根据 cron 表达式判断哪些任务需要执行)
-* * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think scheduler:run >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/scheduler.log 2>&1
+* * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think scheduler:run >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/scheduler.log 2>&1
```
### 4. 系统要求
diff --git a/Server/application/api/controller/MessageController.php b/Server/application/api/controller/MessageController.php
index 8e4dd7e4e..ff521bb98 100644
--- a/Server/application/api/controller/MessageController.php
+++ b/Server/application/api/controller/MessageController.php
@@ -4,7 +4,10 @@ namespace app\api\controller;
use app\api\model\WechatMessageModel;
use app\common\service\FriendTransferService;
+use app\common\service\WechatMediaArchiveService;
+use app\job\MediaArchiveJob;
use think\Db;
+use think\facade\Log;
use think\facade\Request;
class MessageController extends BaseController
@@ -418,6 +421,7 @@ class MessageController extends BaseController
'type' => 1,
'accountId' => $item['accountId'],
'content' => $item['content'],
+ 'originalContent' => $item['content'],
'createTime' => $createTime,
'deleteTime' => $deleteTime,
'isDeleted' => $item['isDeleted'] ?? false,
@@ -463,6 +467,9 @@ class MessageController extends BaseController
}else{
$id = $data['id'];
unset($data['id']);
+ if (!empty($exists['originalContent'])) {
+ unset($data['originalContent']);
+ }
$res = $exists->save($data);
}
@@ -496,6 +503,9 @@ class MessageController extends BaseController
}
}
}
+ if (in_array((int)($item['msgType'] ?? 0), [3, 34, 43, 47, 49], true) && !empty($id)) {
+ MediaArchiveJob::dispatch('message', $id, ['source' => 'saveMessage']);
+ }
return true;
}
@@ -577,6 +587,9 @@ class MessageController extends BaseController
throw new \Exception('更新群聊消息记录失败');
}
}
+ if (in_array((int)($item['msgType'] ?? 0), [3, 34, 43, 47, 49], true)) {
+ MediaArchiveJob::dispatch('message', $item['id'], ['source' => 'saveChatroomMessage']);
+ }
return true;
} catch (\Exception $e) {
// 记录错误日志,便于调试
@@ -588,6 +601,29 @@ class MessageController extends BaseController
}
}
+ /**
+ * 持久化视频/文件下载后的真实地址,并重新触发 OSS 归档
+ * @param array $data
+ * @return bool
+ */
+ public function updateDownloadedMessageMedia($data)
+ {
+ $messageId = (int)($data['friendMessageId'] ?? $data['chatroomMessageId'] ?? 0);
+ $downloadUrl = trim((string)($data['url'] ?? ''));
+
+ if ($messageId <= 0 || empty($downloadUrl)) {
+ return false;
+ }
+
+ $updated = WechatMediaArchiveService::updateDownloadedMessageMedia($messageId, $downloadUrl);
+ if (!$updated) {
+ return false;
+ }
+
+ MediaArchiveJob::dispatch('message', $messageId, ['source' => $data['type'] ?? 'download_result']);
+ return true;
+ }
+
/**
* 处理消息内容,提取发送者ID和消息内容
* @param string $content 原始消息内容
diff --git a/Server/application/api/controller/WebSocketController.php b/Server/application/api/controller/WebSocketController.php
index 2c6424507..bbc954263 100644
--- a/Server/application/api/controller/WebSocketController.php
+++ b/Server/application/api/controller/WebSocketController.php
@@ -12,7 +12,7 @@ use think\facade\Env;
use app\api\model\WechatFriendModel as WechatFriend;
use app\api\model\WechatMomentsModel as WechatMoments;
use think\facade\Cache;
-use app\common\util\AliyunOSS;
+use app\common\service\MediaOssArchiveService;
class WebSocketController extends BaseController
@@ -468,7 +468,7 @@ class WebSocketController extends BaseController
// 更新数据库:保存原始URL和OSS URL,并标记已上传
$updateData = [
'resUrls' => $urls,
- 'isOssUploaded' => 1, // 标识已上传到OSS
+ 'isOssUploaded' => !empty($ossUrls) ? 1 : 0,
'update_time' => time()
];
@@ -513,7 +513,7 @@ class WebSocketController extends BaseController
// 更新数据库:保存原始URL和OSS URL,并标记已上传
$updateData = [
'resUrls' => $urls,
- 'isOssUploaded' => 1, // 标识已上传到OSS
+ 'isOssUploaded' => !empty($ossUrls) ? 1 : 0,
'update_time' => time()
];
@@ -557,76 +557,23 @@ class WebSocketController extends BaseController
if (empty($urls) || !is_array($urls)) {
return $ossUrls;
}
-
- try {
- // 创建临时目录(兼容无 runtime_path() 辅助函数的环境)
- if (function_exists('runtime_path')) {
- $baseRuntimePath = rtrim(runtime_path(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
- } elseif (defined('RUNTIME_PATH')) {
- $baseRuntimePath = rtrim(RUNTIME_PATH, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
- } else {
- // 兜底:使用项目根目录下的 runtime 目录
- $baseRuntimePath = rtrim(ROOT_PATH, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR;
+
+ foreach ($urls as $url) {
+ if (!MediaOssArchiveService::isRemoteHttpUrl($url)) {
+ continue;
}
- $tempDir = $baseRuntimePath . 'temp' . DIRECTORY_SEPARATOR . 'moments' . DIRECTORY_SEPARATOR . date('Y' . DIRECTORY_SEPARATOR . 'm' . DIRECTORY_SEPARATOR . 'd') . DIRECTORY_SEPARATOR;
+ $resourceType = preg_match('/\.(mp4|mov|avi|webm|mkv)(\?.*)?$/i', $url) ? 'video' : 'image';
+ $result = MediaOssArchiveService::archiveRemoteUrl($url, 'moments', $resourceType, (string)$snsId);
+ if (!empty($result['success']) && !empty($result['url'])) {
+ $ossUrls[] = $result['url'];
+ continue;
+ }
- if (!is_dir($tempDir)) {
- mkdir($tempDir, 0755, true);
- }
-
- foreach ($urls as $index => $url) {
- if (empty($url)) {
- continue;
- }
-
- try {
- // 下载图片到临时文件
- $tempFile = $tempDir . md5($url . $snsId . $index) . '.jpg';
-
- // 使用curl下载图片
- $ch = curl_init($url);
- $fp = fopen($tempFile, 'wb');
- curl_setopt($ch, CURLOPT_FILE, $fp);
- curl_setopt($ch, CURLOPT_HEADER, 0);
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($ch, CURLOPT_TIMEOUT, 30);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- curl_close($ch);
- fclose($fp);
-
- if ($httpCode != 200 || !file_exists($tempFile) || filesize($tempFile) == 0) {
- Log::warning('下载朋友圈图片失败:' . $url . ', HTTP Code: ' . $httpCode);
- @unlink($tempFile);
- continue;
- }
-
- // 生成OSS对象名称
- $objectName = 'moments/' . date('Y/m/d/') . md5($snsId . $index . time()) . '.jpg';
-
- // 上传到OSS
- $result = AliyunOSS::uploadFile($tempFile, $objectName);
- if ($result['success']) {
- $ossUrls[] = $result['url'];
- } else {
- Log::error('朋友圈图片上传OSS失败:' . $url . ', 错误:' . ($result['error'] ?? '未知错误'));
- }
-
- // 删除临时文件
- @unlink($tempFile);
-
- } catch (\Exception $e) {
- Log::error('上传朋友圈图片到OSS异常:' . $e->getMessage() . ', URL: ' . $url);
- if (isset($tempFile) && file_exists($tempFile)) {
- @unlink($tempFile);
- }
- }
- }
-
- } catch (\Exception $e) {
- Log::error('上传朋友圈图片到OSS异常:' . $e->getMessage());
+ Log::error('朋友圈媒体上传OSS失败:' . ($result['error'] ?? '未知错误'), [
+ 'snsId' => $snsId,
+ 'url' => $url,
+ ]);
}
return $ossUrls;
@@ -696,8 +643,8 @@ class WebSocketController extends BaseController
}
// 获取资源链接(检查是否已上传到OSS,如果已上传则跳过)
- if(empty($momentEntity['urls']) || $moment['type'] != 1) {
- // 如果没有urls或类型不是1,跳过
+ if(empty($momentEntity['urls'])) {
+ // 如果没有urls,跳过
} elseif ($isOssUploaded == 1) {
// 如果已上传到OSS,跳过采集
} else {
diff --git a/Server/application/chukebao/controller/DataProcessing.php b/Server/application/chukebao/controller/DataProcessing.php
index 5f0121cc6..56d3bd514 100644
--- a/Server/application/chukebao/controller/DataProcessing.php
+++ b/Server/application/chukebao/controller/DataProcessing.php
@@ -37,6 +37,8 @@ class DataProcessing extends BaseController
'CmdChatroomOperate', //修改群信息 {chatroomName(群名)、announce(公告)、extra(公告)、wechatAccountId、wechatChatroomId}
'CmdNewMessage', //接收消息
'CmdSendMessageResult', //更新消息状态
+ 'CmdDownloadVideoResult', //视频下载结果回写
+ 'CmdDownloadFileResult', //文件下载结果回写
'CmdPinToTop', //置顶
];
@@ -168,6 +170,34 @@ class DataProcessing extends BaseController
$msg = '更新消息状态成功';
break;
+ case 'CmdDownloadVideoResult':
+ case 'CmdDownloadFileResult':
+ $friendMessageId = $this->request->param('friendMessageId', 0);
+ $chatroomMessageId = $this->request->param('chatroomMessageId', 0);
+ $url = trim((string)$this->request->param('url', ''));
+
+ if (empty($friendMessageId) && empty($chatroomMessageId)) {
+ return ResponseHelper::error('friendMessageId或chatroomMessageId至少提供一个');
+ }
+
+ if (empty($url)) {
+ return ResponseHelper::error('url不能为空');
+ }
+
+ $messageController = new MessageController();
+ $updated = $messageController->updateDownloadedMessageMedia([
+ 'friendMessageId' => $friendMessageId,
+ 'chatroomMessageId' => $chatroomMessageId,
+ 'url' => $url,
+ 'type' => $type,
+ ]);
+
+ if (!$updated) {
+ return ResponseHelper::error('媒体地址回写失败');
+ }
+
+ $msg = '媒体地址回写成功';
+ break;
case 'CmdPinToTop': //置顶
$wechatFriendId = $this->request->param('wechatFriendId', 0);
$wechatChatroomId = $this->request->param('wechatChatroomId', 0);
diff --git a/Server/application/command.php b/Server/application/command.php
index 45b6d7da8..65e4966ce 100644
--- a/Server/application/command.php
+++ b/Server/application/command.php
@@ -53,4 +53,5 @@ return [
// V2 流量池数据迁移
'migrate:trafficPoolV2' => 'app\command\MigrateTrafficPoolV2Command', // 迁移数据到 V2 流量池系统
+ 'media:archive' => 'app\command\BackfillMediaOssCommand', // 历史媒体资源归档到OSS
];
diff --git a/Server/application/command/BackfillMediaOssCommand.php b/Server/application/command/BackfillMediaOssCommand.php
new file mode 100644
index 000000000..3c1a71e75
--- /dev/null
+++ b/Server/application/command/BackfillMediaOssCommand.php
@@ -0,0 +1,121 @@
+setName('media:archive')
+ ->setDescription('扫描聊天记录和朋友圈媒体资源并加入 OSS 归档队列')
+ ->addOption('scope', null, Option::VALUE_OPTIONAL, '归档范围:chat|moments|all', 'all')
+ ->addOption('limit', null, Option::VALUE_OPTIONAL, '单次扫描数量', 100)
+ ->addOption('startId', null, Option::VALUE_OPTIONAL, '仅扫描大于该ID的记录', 0)
+ ->addOption('messageId', null, Option::VALUE_OPTIONAL, '仅同步归档单条聊天记录(本地调试用,不走队列)', 0);
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $singleMessageId = (int)$input->getOption('messageId');
+ if ($singleMessageId > 0) {
+ return $this->archiveOneChatMessage($singleMessageId, $output);
+ }
+
+ $scope = strtolower((string)$input->getOption('scope'));
+ $limit = max(1, (int)$input->getOption('limit'));
+ $startId = max(0, (int)$input->getOption('startId'));
+
+ $messageCount = 0;
+ $momentCount = 0;
+
+ if (in_array($scope, ['all', 'chat'])) {
+ $messageIds = Db::table('s2_wechat_message')
+ ->where('id', '>', $startId)
+ ->whereIn('msgType', [3, 34, 43, 47, 49])
+ ->where(function ($query) {
+ $query->whereLike('content', 'http%')
+ ->whereOrLike('content', '%"url"%')
+ ->whereOrLike('content', '%"previewImage"%')
+ ->whereOrLike('content', '%"tencentUrl"%');
+ })
+ ->order('id', 'asc')
+ ->limit($limit)
+ ->column('id');
+
+ foreach ($messageIds as $id) {
+ MediaArchiveJob::dispatch('message', $id, ['source' => 'backfill_command']);
+ $messageCount++;
+ }
+ }
+
+ if (in_array($scope, ['all', 'moments'])) {
+ $momentIds = Db::table('s2_wechat_moments')
+ ->where('id', '>', $startId)
+ ->where(function ($query) {
+ $query->where('isOssUploaded', 0)
+ ->whereOr('ossUrls', 'null')
+ ->whereOr('ossUrls', '')
+ ->whereOr('ossUrls', '[]');
+ })
+ ->where(function ($query) {
+ $query->where('resUrls', '<>', '')
+ ->whereOr('urls', '<>', '');
+ })
+ ->order('id', 'asc')
+ ->limit($limit)
+ ->column('id');
+
+ foreach ($momentIds as $id) {
+ MediaArchiveJob::dispatch('moment', $id, ['source' => 'backfill_command']);
+ $momentCount++;
+ }
+ }
+
+ $output->writeln(sprintf(
+ '已加入 OSS 归档队列:聊天记录 %d 条,朋友圈 %d 条。',
+ $messageCount,
+ $momentCount
+ ));
+
+ return 0;
+ }
+
+ /**
+ * 单条聊天消息同步归档(用于本地验证 AliyunOSS 与下载链路)
+ */
+ protected function archiveOneChatMessage($messageId, Output $output)
+ {
+ $row = Db::table('s2_wechat_message')->where('id', $messageId)->find();
+ if (empty($row)) {
+ $output->writeln('错误:未找到消息 id=' . $messageId);
+ return 1;
+ }
+
+ $preview = function ($s, $len) {
+ $s = (string)$s;
+ return function_exists('mb_substr') ? mb_substr($s, 0, $len) : substr($s, 0, $len);
+ };
+
+ $output->writeln('msgType=' . ($row['msgType'] ?? '') . ' content(前200字): ' . $preview($row['content'] ?? '', 200));
+
+ $ok = WechatMediaArchiveService::archiveMessageById($messageId);
+ if (!$ok) {
+ $output->writeln('失败:归档未执行或无需更新(请确认 msgType 为 3/34/43/47/49 且 content 含可下载 http 资源)');
+ return 1;
+ }
+
+ $after = Db::table('s2_wechat_message')->where('id', $messageId)->find();
+ $output->writeln('成功:归档完成。更新后 content(前300字):');
+ $output->writeln($preview($after['content'] ?? '', 300));
+
+ return 0;
+ }
+}
diff --git a/Server/application/command/SyncWechatDataToCkbTask.php b/Server/application/command/SyncWechatDataToCkbTask.php
index 8d0409cbe..671fd0b48 100644
--- a/Server/application/command/SyncWechatDataToCkbTask.php
+++ b/Server/application/command/SyncWechatDataToCkbTask.php
@@ -9,7 +9,7 @@ use think\console\Command;
use think\facade\App;
use WeChatDeviceApi\Adapters\ChuKeBao\Adapter as ChuKeBaoAdapter;
-// */7 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think sync:wechatData >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/sync_wechat_data.log 2>&1
+// */7 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think sync:wechatData >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/sync_wechat_data.log 2>&1
class SyncWechatDataToCkbTask extends Command
{
protected $lockFile;
diff --git a/Server/application/common/service/MediaOssArchiveService.php b/Server/application/common/service/MediaOssArchiveService.php
new file mode 100644
index 000000000..1f150da63
--- /dev/null
+++ b/Server/application/common/service/MediaOssArchiveService.php
@@ -0,0 +1,261 @@
+ 'jpg',
+ 'image/jpg' => 'jpg',
+ 'image/png' => 'png',
+ 'image/gif' => 'gif',
+ 'image/webp' => 'webp',
+ 'image/bmp' => 'bmp',
+ 'image/svg+xml' => 'svg',
+ 'video/mp4' => 'mp4',
+ 'video/quicktime' => 'mov',
+ 'video/x-msvideo' => 'avi',
+ 'video/webm' => 'webm',
+ 'audio/mpeg' => 'mp3',
+ 'audio/mp3' => 'mp3',
+ 'audio/wav' => 'wav',
+ 'audio/x-wav' => 'wav',
+ 'audio/amr' => 'amr',
+ 'audio/aac' => 'aac',
+ 'audio/mp4' => 'm4a',
+ 'audio/ogg' => 'ogg',
+ 'application/pdf' => 'pdf',
+ 'application/msword' => 'doc',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
+ 'application/vnd.ms-excel' => 'xls',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
+ 'application/vnd.ms-powerpoint' => 'ppt',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
+ 'application/zip' => 'zip',
+ 'application/x-rar-compressed' => 'rar',
+ 'application/x-7z-compressed' => '7z',
+ 'text/plain' => 'txt',
+ ];
+
+ public static function isRemoteHttpUrl($url)
+ {
+ if (!is_string($url)) {
+ return false;
+ }
+
+ return (bool)preg_match('/^https?:\/\//i', trim($url));
+ }
+
+ public static function isOwnOssUrl($url)
+ {
+ if (!self::isRemoteHttpUrl($url)) {
+ return false;
+ }
+
+ $host = strtolower((string)parse_url($url, PHP_URL_HOST));
+ if (empty($host)) {
+ return false;
+ }
+
+ $customHost = strtolower((string)parse_url(AliyunOSS::ossUrl, PHP_URL_HOST));
+ $bucketHost = strtolower(AliyunOSS::BUCKET . '.' . AliyunOSS::ENDPOINT);
+
+ return $host === $customHost || $host === $bucketHost || strpos($host, strtolower(AliyunOSS::BUCKET . '.')) === 0;
+ }
+
+ public static function archiveRemoteUrl($url, $bizType, $resourceType, $bizKey, array $options = [])
+ {
+ $url = trim((string)$url);
+ if (!self::isRemoteHttpUrl($url)) {
+ return [
+ 'success' => false,
+ 'error' => 'URL不是有效的http(s)地址',
+ 'url' => '',
+ ];
+ }
+
+ if (self::isOwnOssUrl($url)) {
+ return [
+ 'success' => true,
+ 'alreadyArchived' => true,
+ 'url' => self::normalizeOssUrl($url),
+ 'originalUrl' => $url,
+ 'object_name' => '',
+ ];
+ }
+
+ $tempFile = '';
+ try {
+ $tempDir = self::ensureTempDirectory();
+ $tempFile = $tempDir . md5($url . microtime(true) . mt_rand()) . '.tmp';
+
+ $downloadResult = self::downloadToLocal($url, $tempFile, (int)($options['timeout'] ?? 60));
+ if (!$downloadResult['success']) {
+ return $downloadResult;
+ }
+
+ $extension = self::detectExtension(
+ $url,
+ $tempFile,
+ $downloadResult['contentType'] ?? '',
+ $options['extension'] ?? ''
+ );
+
+ $objectName = self::buildObjectName($bizType, $resourceType, $bizKey, $extension);
+ $result = AliyunOSS::uploadFile($tempFile, $objectName);
+ if (!$result['success']) {
+ return [
+ 'success' => false,
+ 'error' => $result['error'] ?? '上传OSS失败',
+ 'url' => '',
+ ];
+ }
+
+ return [
+ 'success' => true,
+ 'alreadyArchived' => false,
+ 'url' => self::normalizeOssUrl($result['url'] ?? ''),
+ 'originalUrl' => $url,
+ 'object_name' => $objectName,
+ 'extension' => $extension,
+ 'mime_type' => $result['mime_type'] ?? ($downloadResult['contentType'] ?? ''),
+ 'size' => $result['size'] ?? 0,
+ ];
+ } catch (\Exception $e) {
+ Log::error('媒体资源上传OSS失败:' . $e->getMessage(), [
+ 'url' => $url,
+ 'bizType' => $bizType,
+ 'resourceType' => $resourceType,
+ 'bizKey' => $bizKey,
+ ]);
+
+ return [
+ 'success' => false,
+ 'error' => $e->getMessage(),
+ 'url' => '',
+ ];
+ } finally {
+ if (!empty($tempFile) && file_exists($tempFile)) {
+ @unlink($tempFile);
+ }
+ }
+ }
+
+ protected static function ensureTempDirectory()
+ {
+ if (function_exists('runtime_path')) {
+ $baseRuntimePath = rtrim(runtime_path(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+ } elseif (defined('RUNTIME_PATH')) {
+ $baseRuntimePath = rtrim(RUNTIME_PATH, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+ } else {
+ $baseRuntimePath = rtrim(ROOT_PATH, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR;
+ }
+
+ $tempDir = $baseRuntimePath . 'temp' . DIRECTORY_SEPARATOR . 'media_archive' . DIRECTORY_SEPARATOR . date('Y' . DIRECTORY_SEPARATOR . 'm' . DIRECTORY_SEPARATOR . 'd') . DIRECTORY_SEPARATOR;
+ if (!is_dir($tempDir)) {
+ mkdir($tempDir, 0755, true);
+ }
+
+ return $tempDir;
+ }
+
+ protected static function downloadToLocal($url, $tempFile, $timeout)
+ {
+ $ch = curl_init($url);
+ $fp = fopen($tempFile, 'wb');
+
+ curl_setopt($ch, CURLOPT_FILE, $fp);
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+ curl_setopt($ch, CURLOPT_USERAGENT, 'CKB-MediaArchive/1.0');
+ curl_exec($ch);
+
+ $curlError = curl_error($ch);
+ $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $contentType = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
+ curl_close($ch);
+ fclose($fp);
+
+ if (!empty($curlError)) {
+ @unlink($tempFile);
+ return [
+ 'success' => false,
+ 'error' => '下载资源失败:' . $curlError,
+ 'url' => '',
+ ];
+ }
+
+ if ($httpCode < 200 || $httpCode >= 300 || !file_exists($tempFile) || filesize($tempFile) <= 0) {
+ @unlink($tempFile);
+ return [
+ 'success' => false,
+ 'error' => '下载资源失败,HTTP状态码:' . $httpCode,
+ 'url' => '',
+ ];
+ }
+
+ return [
+ 'success' => true,
+ 'contentType' => $contentType,
+ 'httpCode' => $httpCode,
+ ];
+ }
+
+ protected static function detectExtension($url, $tempFile, $contentType = '', $forcedExtension = '')
+ {
+ $forcedExtension = strtolower(trim((string)$forcedExtension, '. '));
+ if (!empty($forcedExtension)) {
+ return $forcedExtension;
+ }
+
+ $pathExtension = strtolower((string)pathinfo((string)parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
+ if (preg_match('/^[a-z0-9]{1,8}$/i', $pathExtension)) {
+ return $pathExtension;
+ }
+
+ $contentType = strtolower(trim((string)$contentType));
+ if (!empty($contentType)) {
+ $contentType = trim(explode(';', $contentType)[0]);
+ if (!empty(self::$mimeExtensionMap[$contentType])) {
+ return self::$mimeExtensionMap[$contentType];
+ }
+ }
+
+ $mimeType = '';
+ if (function_exists('mime_content_type')) {
+ $mimeType = strtolower((string)mime_content_type($tempFile));
+ }
+ if (!empty($mimeType) && !empty(self::$mimeExtensionMap[$mimeType])) {
+ return self::$mimeExtensionMap[$mimeType];
+ }
+
+ return 'bin';
+ }
+
+ protected static function buildObjectName($bizType, $resourceType, $bizKey, $extension)
+ {
+ $bizType = trim((string)$bizType, '/');
+ $resourceType = trim((string)$resourceType, '/');
+ $bizKey = preg_replace('/[^a-zA-Z0-9_\-]/', '_', (string)$bizKey);
+ $extension = trim((string)$extension, '.');
+
+ return $bizType . '/' . $resourceType . '/' . date('Y/m/d/') . $bizKey . '_' . substr(md5(uniqid('', true)), 0, 16) . '.' . $extension;
+ }
+
+ protected static function normalizeOssUrl($url)
+ {
+ $url = trim((string)$url);
+ if (strpos($url, 'http://') === 0) {
+ return 'https://' . substr($url, 7);
+ }
+
+ return $url;
+ }
+}
diff --git a/Server/application/common/service/WechatMediaArchiveService.php b/Server/application/common/service/WechatMediaArchiveService.php
new file mode 100644
index 000000000..c587d9a19
--- /dev/null
+++ b/Server/application/common/service/WechatMediaArchiveService.php
@@ -0,0 +1,372 @@
+where('id', $messageId)->find();
+ if (empty($row)) {
+ return false;
+ }
+
+ $update = self::buildMessageArchiveUpdate($row);
+ if (empty($update)) {
+ return false;
+ }
+
+ Db::table('s2_wechat_message')->where('id', $messageId)->update($update);
+ return true;
+ }
+
+ public static function archiveMomentById($momentId)
+ {
+ $momentId = (int)$momentId;
+ if ($momentId <= 0) {
+ return false;
+ }
+
+ $row = Db::table('s2_wechat_moments')->where('id', $momentId)->find();
+ if (empty($row)) {
+ return false;
+ }
+
+ $displayUrls = self::decodeJsonArray($row['resUrls'] ?? '');
+ $rawUrls = self::decodeJsonArray($row['urls'] ?? '');
+ $candidateUrls = !empty($displayUrls) ? $displayUrls : $rawUrls;
+ if (empty($candidateUrls)) {
+ return false;
+ }
+
+ $ossUrls = [];
+ foreach ($candidateUrls as $index => $url) {
+ if (!MediaOssArchiveService::isRemoteHttpUrl($url)) {
+ continue;
+ }
+
+ $resourceType = self::isVideoUrl($url) ? 'video' : 'image';
+ $result = MediaOssArchiveService::archiveRemoteUrl($url, 'moments', $resourceType, (string)($row['snsId'] ?? $momentId), [
+ 'index' => $index,
+ ]);
+ if (!empty($result['success']) && !empty($result['url'])) {
+ $ossUrls[] = $result['url'];
+ }
+ }
+
+ if (empty($ossUrls)) {
+ return false;
+ }
+
+ Db::table('s2_wechat_moments')->where('id', $momentId)->update([
+ 'ossUrls' => json_encode($ossUrls, JSON_UNESCAPED_UNICODE),
+ 'isOssUploaded' => 1,
+ 'update_time' => time(),
+ ]);
+
+ return true;
+ }
+
+ public static function updateDownloadedMessageMedia($messageId, $downloadUrl)
+ {
+ $messageId = (int)$messageId;
+ $downloadUrl = trim((string)$downloadUrl);
+ if ($messageId <= 0 || empty($downloadUrl)) {
+ return false;
+ }
+
+ $row = Db::table('s2_wechat_message')->where('id', $messageId)->find();
+ if (empty($row)) {
+ return false;
+ }
+
+ $content = (string)($row['content'] ?? '');
+ $originalContent = (string)($row['originalContent'] ?? '');
+ $msgType = (int)($row['msgType'] ?? 0);
+
+ $update = [];
+ if (empty($originalContent)) {
+ $update['originalContent'] = $content;
+ }
+
+ if ($msgType === 43) {
+ $payload = self::decodeJsonObject($content);
+ if (empty($payload)) {
+ $payload = [];
+ }
+ $payload['videoUrl'] = $downloadUrl;
+ $payload['isLoading'] = false;
+ $update['content'] = self::encodeJson($payload);
+ } elseif ($msgType === 49) {
+ $payload = self::decodeJsonObject($content);
+ if (empty($payload) || strtolower((string)($payload['type'] ?? '')) !== 'file') {
+ $payload = [
+ 'type' => 'file',
+ 'title' => self::extractFileTitle($content),
+ ];
+ }
+ $payload['url'] = $downloadUrl;
+ $payload['isDownloading'] = false;
+ $update['content'] = self::encodeJson($payload);
+ } else {
+ return false;
+ }
+
+ Db::table('s2_wechat_message')->where('id', $messageId)->update($update);
+ return true;
+ }
+
+ protected static function buildMessageArchiveUpdate(array $row)
+ {
+ $msgType = (int)($row['msgType'] ?? 0);
+ $content = (string)($row['content'] ?? '');
+ $originalContent = (string)($row['originalContent'] ?? '');
+ $sourceOriginalContent = $originalContent !== '' ? $originalContent : $content;
+
+ $update = [];
+ switch ($msgType) {
+ case 3:
+ case 47:
+ $imageUpdate = self::archiveImageLikeContent($content, $msgType, $row['id']);
+ if (!empty($imageUpdate)) {
+ $update = array_merge($update, $imageUpdate);
+ }
+ break;
+ case 34:
+ $audioUpdate = self::archiveAudioContent($content, $row['id']);
+ if (!empty($audioUpdate)) {
+ $update = array_merge($update, $audioUpdate);
+ }
+ break;
+ case 43:
+ $videoUpdate = self::archiveVideoContent($content, $row['id']);
+ if (!empty($videoUpdate)) {
+ $update = array_merge($update, $videoUpdate);
+ }
+ break;
+ case 49:
+ $fileUpdate = self::archiveFileContent($content, $row['id']);
+ if (!empty($fileUpdate)) {
+ $update = array_merge($update, $fileUpdate);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!empty($update) && empty($originalContent) && !empty($sourceOriginalContent)) {
+ $update['originalContent'] = $sourceOriginalContent;
+ }
+
+ return $update;
+ }
+
+ protected static function archiveImageLikeContent($content, $msgType, $messageId)
+ {
+ $payload = self::decodeJsonObject($content);
+ if (!empty($payload) && !empty($payload['url'])) {
+ $sourceUrl = (string)($payload['originUrl'] ?? $payload['url']);
+ $result = MediaOssArchiveService::archiveRemoteUrl(
+ $sourceUrl,
+ 'messages',
+ $msgType == 47 ? 'emoji' : 'image',
+ (string)$messageId
+ );
+ if (empty($result['success']) || empty($result['url'])) {
+ return [];
+ }
+
+ $payload['originUrl'] = $payload['originUrl'] ?? $sourceUrl;
+ $payload['ossUrl'] = $result['url'];
+ $payload['url'] = $result['url'];
+ return ['content' => self::encodeJson($payload)];
+ }
+
+ if (!MediaOssArchiveService::isRemoteHttpUrl($content)) {
+ return [];
+ }
+
+ $result = MediaOssArchiveService::archiveRemoteUrl(
+ $content,
+ 'messages',
+ $msgType == 47 ? 'emoji' : 'image',
+ (string)$messageId
+ );
+ if (empty($result['success']) || empty($result['url'])) {
+ return [];
+ }
+
+ return ['content' => $result['url']];
+ }
+
+ protected static function archiveAudioContent($content, $messageId)
+ {
+ $payload = self::decodeJsonObject($content);
+ if (!empty($payload) && !empty($payload['url'])) {
+ $sourceUrl = (string)($payload['originUrl'] ?? $payload['url']);
+ $result = MediaOssArchiveService::archiveRemoteUrl($sourceUrl, 'messages', 'audio', (string)$messageId);
+ if (empty($result['success']) || empty($result['url'])) {
+ return [];
+ }
+
+ $payload['originUrl'] = $payload['originUrl'] ?? $sourceUrl;
+ $payload['ossUrl'] = $result['url'];
+ $payload['url'] = $result['url'];
+ return ['content' => self::encodeJson($payload)];
+ }
+
+ if (!MediaOssArchiveService::isRemoteHttpUrl($content)) {
+ return [];
+ }
+
+ $result = MediaOssArchiveService::archiveRemoteUrl($content, 'messages', 'audio', (string)$messageId);
+ if (empty($result['success']) || empty($result['url'])) {
+ return [];
+ }
+
+ return ['content' => $result['url']];
+ }
+
+ protected static function archiveVideoContent($content, $messageId)
+ {
+ $payload = self::decodeJsonObject($content);
+ if (empty($payload)) {
+ if (!MediaOssArchiveService::isRemoteHttpUrl($content)) {
+ return [];
+ }
+
+ $result = MediaOssArchiveService::archiveRemoteUrl($content, 'messages', 'video', (string)$messageId);
+ if (empty($result['success']) || empty($result['url'])) {
+ return [];
+ }
+
+ return ['content' => $result['url']];
+ }
+
+ $changed = false;
+ $previewSource = (string)($payload['previewImageOriginUrl'] ?? $payload['previewImage'] ?? '');
+ if (MediaOssArchiveService::isRemoteHttpUrl($previewSource)) {
+ $previewResult = MediaOssArchiveService::archiveRemoteUrl($previewSource, 'messages', 'video_cover', (string)$messageId);
+ if (!empty($previewResult['success']) && !empty($previewResult['url'])) {
+ $payload['previewImageOriginUrl'] = $payload['previewImageOriginUrl'] ?? $previewSource;
+ $payload['previewImageOssUrl'] = $previewResult['url'];
+ $payload['previewImage'] = $previewResult['url'];
+ $changed = true;
+ }
+ }
+
+ $videoSource = (string)($payload['originUrl'] ?? $payload['videoUrl'] ?? $payload['ossUrl'] ?? $payload['tencentUrl'] ?? $payload['url'] ?? '');
+ if (MediaOssArchiveService::isRemoteHttpUrl($videoSource)) {
+ $videoResult = MediaOssArchiveService::archiveRemoteUrl($videoSource, 'messages', 'video', (string)$messageId);
+ if (!empty($videoResult['success']) && !empty($videoResult['url'])) {
+ $payload['originUrl'] = $payload['originUrl'] ?? $videoSource;
+ $payload['ossUrl'] = $videoResult['url'];
+ $payload['videoUrl'] = $videoResult['url'];
+ $payload['url'] = $videoResult['url'];
+ $payload['isLoading'] = false;
+ $changed = true;
+ }
+ }
+
+ if (!$changed) {
+ return [];
+ }
+
+ return ['content' => self::encodeJson($payload)];
+ }
+
+ protected static function archiveFileContent($content, $messageId)
+ {
+ $payload = self::decodeJsonObject($content);
+ if (empty($payload) || strtolower((string)($payload['type'] ?? '')) !== 'file') {
+ return [];
+ }
+
+ $sourceUrl = (string)($payload['originUrl'] ?? $payload['url'] ?? '');
+ if (!MediaOssArchiveService::isRemoteHttpUrl($sourceUrl)) {
+ return [];
+ }
+
+ $result = MediaOssArchiveService::archiveRemoteUrl(
+ $sourceUrl,
+ 'messages',
+ 'file',
+ (string)$messageId,
+ [
+ 'extension' => $payload['fileext'] ?? '',
+ ]
+ );
+ if (empty($result['success']) || empty($result['url'])) {
+ return [];
+ }
+
+ $payload['originUrl'] = $payload['originUrl'] ?? $sourceUrl;
+ $payload['ossUrl'] = $result['url'];
+ $payload['url'] = $result['url'];
+ $payload['isDownloading'] = false;
+ return ['content' => self::encodeJson($payload)];
+ }
+
+ protected static function decodeJsonObject($value)
+ {
+ if (!is_string($value)) {
+ return [];
+ }
+
+ $decoded = json_decode(trim($value), true);
+ return is_array($decoded) ? $decoded : [];
+ }
+
+ protected static function decodeJsonArray($value)
+ {
+ if (is_array($value)) {
+ return $value;
+ }
+
+ if (!is_string($value) || trim($value) === '') {
+ return [];
+ }
+
+ $decoded = json_decode($value, true);
+ return is_array($decoded) ? $decoded : [];
+ }
+
+ protected static function encodeJson(array $payload)
+ {
+ return json_encode($payload, JSON_UNESCAPED_UNICODE);
+ }
+
+ protected static function extractFileTitle($rawContent)
+ {
+ if (!is_string($rawContent) || trim($rawContent) === '') {
+ return '文件';
+ }
+
+ if (preg_match('/
<\/title>/i', $rawContent, $matches) && !empty($matches[1])) {
+ return trim($matches[1]);
+ }
+
+ if (preg_match('/([^<]+)<\/title>/i', $rawContent, $matches) && !empty($matches[1])) {
+ return trim($matches[1]);
+ }
+
+ return '文件';
+ }
+
+ protected static function isVideoUrl($url)
+ {
+ if (!is_string($url)) {
+ return false;
+ }
+
+ return (bool)preg_match('/\.(mp4|mov|avi|webm|mkv)(\?.*)?$/i', $url);
+ }
+}
diff --git a/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php b/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php
index 06f6c3be9..414a5d85b 100644
--- a/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php
+++ b/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php
@@ -201,7 +201,7 @@ class GetWechatMomentsV1Controller extends BaseController
// 格式化数据
$rows = [];
foreach ($moments as $moment) {
- $resUrls = $this->decodeJson($moment['resUrls'] ?? null);
+ $resUrls = $this->preferredMediaUrls($moment);
$imageUrls = is_array($resUrls) ? $resUrls : [];
// 格式化日期和时间
@@ -300,7 +300,8 @@ class GetWechatMomentsV1Controller extends BaseController
'content' => $row['content'] ?? '',
'commentList' => $this->decodeJson($row['commentList'] ?? null),
'likeList' => $this->decodeJson($row['likeList'] ?? null),
- 'resUrls' => $this->decodeJson($row['resUrls'] ?? null),
+ 'resUrls' => $this->preferredMediaUrls($row),
+ 'ossUrls' => $this->decodeJson($row['ossUrls'] ?? null),
'createTime' => $formatTime($row['createTime'] ?? null),
'momentEntity' => [
'lat' => $row['lat'] ?? 0,
@@ -312,6 +313,16 @@ class GetWechatMomentsV1Controller extends BaseController
];
}
+ protected function preferredMediaUrls(array $row): array
+ {
+ $ossUrls = $this->decodeJson($row['ossUrls'] ?? null);
+ if (!empty($ossUrls)) {
+ return $ossUrls;
+ }
+
+ return $this->decodeJson($row['resUrls'] ?? null);
+ }
+
/**
* JSON字段解析
*
diff --git a/Server/application/job/MediaArchiveJob.php b/Server/application/job/MediaArchiveJob.php
new file mode 100644
index 000000000..5a1e7ff6d
--- /dev/null
+++ b/Server/application/job/MediaArchiveJob.php
@@ -0,0 +1,57 @@
+delete();
+ return;
+ }
+
+ $success = false;
+ if ($scope === 'message') {
+ $success = WechatMediaArchiveService::archiveMessageById($id);
+ } elseif ($scope === 'moment') {
+ $success = WechatMediaArchiveService::archiveMomentById($id);
+ }
+
+ if ($success || $job->attempts() >= 3) {
+ $job->delete();
+ return;
+ }
+
+ $job->release(Config::get('queue.failed_delay', 10));
+ } catch (\Exception $e) {
+ Log::error('媒体资源归档任务失败:' . $e->getMessage(), $data ?: []);
+ if ($job->attempts() >= 3) {
+ $job->delete();
+ } else {
+ $job->release(Config::get('queue.failed_delay', 10));
+ }
+ }
+ }
+
+ public static function dispatch($scope, $id, array $extra = [])
+ {
+ $payload = array_merge($extra, [
+ 'scope' => $scope,
+ 'id' => (int)$id,
+ ]);
+
+ return Queue::push(self::class, $payload, self::QUEUE_NAME);
+ }
+}
diff --git a/Server/crontab_tasks.md b/Server/crontab_tasks.md
index 81def4327..b89b3043b 100644
--- a/Server/crontab_tasks.md
+++ b/Server/crontab_tasks.md
@@ -4,85 +4,85 @@
```bash
# 设备列表
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think device:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/device_list.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think device:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/device_list.log 2>&1
# 微信好友列表
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechatFriends:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/wechat_friends_list.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechatFriends:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/wechat_friends_list.log 2>&1
# 微信群列表
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechatChatroom:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/wechat_chatroom_list.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechatChatroom:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/wechat_chatroom_list.log 2>&1
# 添加好友任务列表
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think friendTask:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/friend_task_list.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think friendTask:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/friend_task_list.log 2>&1
# 微信客服列表
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechatList:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/wechat_list.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechatList:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/wechat_list.log 2>&1
# 公司账号列表
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think account:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/account_list.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think account:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/account_list.log 2>&1
# 微信好友消息列表
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think message:friendsList >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/message_friends_list.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think message:friendsList >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/message_friends_list.log 2>&1
# 微信群聊消息列表
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think message:chatroomList >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/message_chatroom_list.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think message:chatroomList >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/message_chatroom_list.log 2>&1
# 部门列表
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think department:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/department_list.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think department:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/department_list.log 2>&1
# 同步内容库
-0 2 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think content:sync >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/content_sync.log 2>&1
+0 2 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think content:sync >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/content_sync.log 2>&1
# 微信群好友列表
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think groupFriends:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/group_friends_list.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think groupFriends:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/group_friends_list.log 2>&1
# 获取通话记录
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think call-recording:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/call_recording.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think call-recording:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/call_recording.log 2>&1
# 分配规则列表
-0 3 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think allotrule:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/allot_rule_list.log 2>&1
+0 3 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think allotrule:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/allot_rule_list.log 2>&1
# 自动创建分配规则
-0 4 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think allotrule:autocreate >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/allot_rule_autocreate.log 2>&1
+0 4 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think allotrule:autocreate >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/allot_rule_autocreate.log 2>&1
# 内容采集任务
-0 5 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think content:collect >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/content_collect.log 2>&1
+0 5 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think content:collect >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/content_collect.log 2>&1
# 朋友圈采集任务
-0 6 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think moments:collect >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/moments_collect.log 2>&1
+0 6 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think moments:collect >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/moments_collect.log 2>&1
# 工作台自动点赞任务
-0 7 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:autoLike >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_auto_like.log 2>&1
+0 7 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:autoLike >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/workbench_auto_like.log 2>&1
# 工作台朋友圈同步任务
-0 8 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:moments >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_moments.log 2>&1
+0 8 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:moments >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/workbench_moments.log 2>&1
# 同步微信数据到存客宝
-0 9 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think sync:wechatData >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/sync_wechat_data.log 2>&1
+0 9 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think sync:wechatData >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/sync_wechat_data.log 2>&1
# 工作台群发消息
-*/2 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:groupPush >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_groupPush.log 2>&1
+*/2 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:groupPush >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/workbench_groupPush.log 2>&1
# 工作台建群
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:groupCreate >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_groupCreate.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:groupCreate >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/workbench_groupCreate.log 2>&1
# 工作台通讯录导入
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:import-contact >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/import_contact.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:import-contact >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/import_contact.log 2>&1
# 工作台流量分发
-0 9 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:trafficDistribute >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/traffic_distribute.log 2>&1
+0 9 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:trafficDistribute >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/traffic_distribute.log 2>&1
# 预防性切换好友
-*/2 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think switch:friends >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/switch_friends.log 2>&1
+*/2 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think switch:friends >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/switch_friends.log 2>&1
# 消息提醒
-*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think kf:notice >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/kf_notice.log 2>&1
+*/1 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think kf:notice >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/kf_notice.log 2>&1
# 客服评分
-0 2 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechat:calculate-score >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/calculate_score.log 2>&1
+0 2 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechat:calculate-score >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/calculate_score.log 2>&1
@@ -92,12 +92,12 @@
## 说明
-- 所有命令都在 `/www/wwwroot/mckb_quwanzhi_com/Server` 目录下执行
+- 所有命令都在 `/www/wwwroot/ckbapi.quwanzhi.com/Server` 目录下执行
- 默认只获取未删除(活跃)的设备、微信好友和群聊
- 已注释的命令(以#开头)是获取已删除或已停用数据的任务,可根据需要取消注释启用
- 每个命令的执行结果都会记录到对应的日志文件中
- 日志文件名格式包含了数据状态(如 `_active`, `_deleted`, `_stopped`)
-- 日志文件位于 `/www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/` 目录下
+- 日志文件位于 `/www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/` 目录下
- 大部分任务每5分钟执行一次(`*/5 * * * *` 表示每小时的第0,5,10,15...55分钟执行)
- 设备列表的未删除设备任务每天凌晨1点执行一次(`0 1 * * *`)
- 自动创建分配规则每小时整点执行一次(`0 * * * *`)
@@ -120,64 +120,64 @@ crontab -l
# 设备列表 - 未删除设备(每半小时执行)
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think device:list --isDel=0 >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_device_active.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think device:list --isDel=0 >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_device_active.log 2>&1
# 设备列表 - 已删除设备(每天1点执行)
-0 1 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think device:list --isDel=1 >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_device_deleted.log 2>&1
+0 1 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think device:list --isDel=1 >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_device_deleted.log 2>&1
# 设备列表 - 已停用设备(每天1:10执行)
-10 1 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think device:list --isDel=2 >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_device_stopped.log 2>&1
+10 1 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think device:list --isDel=2 >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_device_stopped.log 2>&1
# 微信好友列表 - 未删除好友(每1分钟执行)
-*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechatFriends:list --isDel=0 >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_wechatFriends_active.log 2>&1
+*/1 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechatFriends:list --isDel=0 >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_wechatFriends_active.log 2>&1
# 微信好友列表 - 已删除好友(每天1:30分执行)
-30 1 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechatFriends:list --isDel=1 >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_wechatFriends_deleted.log 2>&1
+30 1 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechatFriends:list --isDel=1 >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_wechatFriends_deleted.log 2>&1
# 微信群列表 - 未删除群(每5分钟执行)
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechatChatroom:list --isDel=0 >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_wechatChatroom_active.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechatChatroom:list --isDel=0 >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_wechatChatroom_active.log 2>&1
# 微信群列表 - 已删除群(每天1:30分执行)
-30 1 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechatChatroom:list --isDel=1 >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_wechatChatroom_deleted.log 2>&1
+30 1 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechatChatroom:list --isDel=1 >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_wechatChatroom_deleted.log 2>&1
# 微信群好友列表(没5分钟执行)
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think groupFriends:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_groupFriends.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think groupFriends:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_groupFriends.log 2>&1
# 添加好友任务列表(每1分钟执行)
-*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think friendTask:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_friendTask.log 2>&1
+*/1 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think friendTask:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_friendTask.log 2>&1
# 微信客服列表(每5分钟执行)
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechatList:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_wechatList.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechatList:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_wechatList.log 2>&1
# 公司账号列表(每5分钟执行)
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think account:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_account.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think account:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_account.log 2>&1
# 微信好友消息列表(每30分钟执行)
-*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think message:friendsList >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_messageFriends.log 2>&1
+*/1 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think message:friendsList >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_messageFriends.log 2>&1
# 微信群聊消息列表(每30分钟执行)
-*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think message:chatroomList >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_messageChatroom.log 2>&1
+*/1 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think message:chatroomList >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_messageChatroom.log 2>&1
# 获取通话记录
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think call-recording:list >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/call_recording.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think call-recording:list >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/call_recording.log 2>&1
# 清洗微信数据
-*/2 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think sync:wechatData >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/sync_wechat_data.log 2>&1
+*/2 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think sync:wechatData >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/sync_wechat_data.log 2>&1
# 内容采集任务(每5分钟执行)
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think content:collect >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_contentCollect.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think content:collect >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_contentCollect.log 2>&1
# 工作台任务_自动点赞(每10分钟执行)
-*/6 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:autoLike >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/crontab_workbench_autoLike.log 2>&1
+*/6 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:autoLike >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/crontab_workbench_autoLike.log 2>&1
# 每3天的3点同步所有好友
-0 3 */3 * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think sync:allFriends >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/all_friends.log 2>&1
+0 3 */3 * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think sync:allFriends >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/all_friends.log 2>&1
# 工作台流量分发
-*/2 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:trafficDistribute >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/traffic_distribute.log 2>&1
+*/2 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:trafficDistribute >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/traffic_distribute.log 2>&1
# 工作台朋友圈同步任务
-*/2 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:moments >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_moments.log 2>&1
+*/2 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:moments >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/workbench_moments.log 2>&1
# 工作台群发消息
-#*/2 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:groupPush >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_groupPush.log 2>&1
+#*/2 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:groupPush >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/workbench_groupPush.log 2>&1
# 预防性切换好友
-*/2 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think switch:friends >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/switch_friends.log 2>&1
+*/2 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think switch:friends >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/switch_friends.log 2>&1
# 工作台建群
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:groupCreate >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_groupCreate.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:groupCreate >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/workbench_groupCreate.log 2>&1
# 工作台通讯录导入
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:import-contact >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/import_contact.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:import-contact >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/import_contact.log 2>&1
# 工作台入群欢迎语
-*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:groupWelcome >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_groupWelcome.log 2>&1
+*/1 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think workbench:groupWelcome >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/workbench_groupWelcome.log 2>&1
# 消息提醒
-*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think kf:notice >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/kf_notice.log 2>&1
+*/1 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think kf:notice >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/kf_notice.log 2>&1
# 客服评分
-0 2 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechat:calculate-score >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/calculate_score.log 2>&1
+0 2 * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think wechat:calculate-score >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/calculate_score.log 2>&1
# 采集客服自己的朋友圈
-*/30 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think own:moments:collect >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/own_moments_collect.log 2>&1
+*/30 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think own:moments:collect >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/own_moments_collect.log 2>&1
# 检查未读/未回复消息并自动迁移好友
-*/5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think check:unread-message --minutes=30 >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/check_unread_message.log 2>&1
+*/5 * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think check:unread-message --minutes=30 >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/check_unread_message.log 2>&1
# 每分钟执行一次调度器(调度器内部会自动判断哪些任务需要执行)
-* * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think scheduler:run >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/scheduler.log 2>&1
+* * * * * cd /www/wwwroot/ckbapi.quwanzhi.com/Server && php think scheduler:run >> /www/wwwroot/ckbapi.quwanzhi.com/Server/runtime/log/scheduler.log 2>&1
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/AudioMessage/AudioMessage.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/AudioMessage/AudioMessage.tsx
index d9840279b..52e4145f7 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/AudioMessage/AudioMessage.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/AudioMessage/AudioMessage.tsx
@@ -18,10 +18,10 @@ const parseAudioUrl = (audioUrl: string): AudioData => {
try {
// 尝试解析为JSON
const parsed = JSON.parse(audioUrl);
- if (parsed.url) {
+ if (parsed.url || parsed.ossUrl) {
return {
durationMs: parsed.durationMs,
- url: parsed.url,
+ url: parsed.ossUrl || parsed.url,
text: parsed.text,
};
}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/FileMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/FileMessage/index.tsx
index afc769df4..625a6cd81 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/FileMessage/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/FileMessage/index.tsx
@@ -226,20 +226,22 @@ export const FileMessage: React.FC = ({
// 提取文件信息
const {
url = "",
+ ossUrl = "",
title,
fileName,
filename,
fileext,
isDownloading = false,
} = fileMessageData;
+ const resolvedUrl = ossUrl || url;
// 解析文件名(优先级:title > fileName > filename > URL中提取)
const resolvedFileName =
title ||
fileName ||
filename ||
- (typeof url === "string" && url
- ? url.split("/").pop()?.split("?")[0]
+ (typeof resolvedUrl === "string" && resolvedUrl
+ ? resolvedUrl.split("/").pop()?.split("?")[0]
: "") ||
"文件";
@@ -278,7 +280,7 @@ export const FileMessage: React.FC = ({
// 判断是否有可用的文件URL
const isUrlAvailable =
- typeof url === "string" && url.trim().length > 0;
+ typeof resolvedUrl === "string" && resolvedUrl.trim().length > 0;
// 文件下载处理函数
const handleFileDownload = () => {
@@ -305,7 +307,7 @@ export const FileMessage: React.FC = ({
event.stopPropagation();
if (isUrlAvailable) {
try {
- window.open(url, "_blank");
+ window.open(resolvedUrl, "_blank");
} catch (e) {
console.error("文件打开失败:", e);
}
@@ -320,7 +322,7 @@ export const FileMessage: React.FC = ({
className={styles.fileCard}
onClick={() => {
if (isUrlAvailable) {
- window.open(url, "_blank");
+ window.open(resolvedUrl, "_blank");
} else if (!isDownloading) {
handleFileDownload();
}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/index.tsx
index a2c5252b9..c328b2872 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/index.tsx
@@ -88,34 +88,36 @@ const VideoMessage: React.FC = ({
videoData &&
typeof videoData === "object" &&
videoData.previewImage &&
- videoData.tencentUrl
+ (videoData.tencentUrl || videoData.videoUrl || videoData.ossUrl)
) {
const previewImageUrl = String(videoData.previewImage).replace(
/[`"']/g,
"",
);
+ const resolvedVideoUrl =
+ videoData.videoUrl || videoData.ossUrl || videoData.tencentUrl;
// 创建点击处理函数
const handlePlayClick = (e: React.MouseEvent, msg: ChatRecord) => {
e.stopPropagation();
// 如果没有视频URL且不在加载中,则发起下载请求
- if (!videoData.videoUrl && !videoData.isLoading) {
+ if (!resolvedVideoUrl && !videoData.isLoading) {
handleVideoPlayRequest(videoData.tencentUrl, msg.id);
}
};
// 如果已有视频URL,显示视频播放器
- if (videoData.videoUrl) {
+ if (videoData.videoUrl || videoData.ossUrl) {
return (
= ({ content }) => {
+ let imageUrl = content;
+ try {
+ const parsed = JSON.parse(content);
+ if (parsed && typeof parsed === "object") {
+ imageUrl = parsed.ossUrl || parsed.url || parsed.originUrl || content;
+ }
+ } catch (error) {
+ imageUrl = content;
+ }
+
const handleImageError = (event: React.SyntheticEvent) => {
const target = event.target as HTMLImageElement;
const parent = target.parentElement;
@@ -22,14 +32,14 @@ export const ImageMessage: React.FC = ({ content }) => {

window.open(content, "_blank")}
+ onClick={() => window.open(imageUrl, "_blank")}
onError={handleImageError}
/>
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/components/friendCard.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/components/friendCard.tsx
index b9429fe1e..df00aa8b4 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/components/friendCard.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/components/friendCard.tsx
@@ -33,7 +33,11 @@ export const FriendCard: React.FC
= ({
formatTime,
}) => {
const content = monent?.momentEntity?.content || "";
- const images = monent?.momentEntity?.resUrls || [];
+ const mediaUrls = monent?.momentEntity?.resUrls || [];
+ const isVideoUrl = (url: string) =>
+ /\.(mp4|mov|avi|webm|mkv)(\?.*)?$/i.test(url || "");
+ const videos = mediaUrls.filter((url: string) => isVideoUrl(url));
+ const images = mediaUrls.filter((url: string) => !isVideoUrl(url));
const time = formatTime(monent.createTime);
const likesCount = monent?.likeList?.length || 0;
const commentsCount = monent?.commentList?.length || 0;
@@ -153,6 +157,19 @@ export const FriendCard: React.FC = ({
{content}
+ {videos.length > 0 && (
+
+ {videos.map((video, index) => (
+
+ ))}
+
+ )}
{images && images.length > 0 && (
{images.map((image, index) => (
diff --git a/Touchkebao/src/store/module/websocket/msgManage.ts b/Touchkebao/src/store/module/websocket/msgManage.ts
index 2ba36b3a1..963a60d8e 100644
--- a/Touchkebao/src/store/module/websocket/msgManage.ts
+++ b/Touchkebao/src/store/module/websocket/msgManage.ts
@@ -35,6 +35,7 @@ const getWeChatStoreMethods = () => {
updateMessage: state.updateMessage,
updateMomentCommonLoading: state.updateMomentCommonLoading,
addMomentCommon: state.addMomentCommon,
+ setVideoUrl: state.setVideoUrl,
setFileDownloadUrl: state.setFileDownloadUrl,
setFileDownloading: state.setFileDownloading,
};
@@ -508,9 +509,24 @@ const messageHandlers: Record = {
},
CmdDownloadVideoResult: message => {
- // 在这里添加具体的处理逻辑
+ const { setVideoUrl } = getWeChatStoreMethods();
+ const messageId = message.friendMessageId || message.chatroomMessageId;
+
console.log("视频下载结果:", message);
- // setVideoUrl(message.friendMessageId, message.url);
+ if (!messageId || !message.url) {
+ return;
+ }
+
+ setVideoUrl(messageId, message.url);
+ dataProcessing({
+ type: "CmdDownloadVideoResult",
+ wechatAccountId: message.wechatAccountId || 1,
+ friendMessageId: message.friendMessageId,
+ chatroomMessageId: message.chatroomMessageId,
+ url: message.url,
+ }).catch(error => {
+ console.error("回写视频下载地址失败:", error);
+ });
},
CmdDownloadFileResult: message => {
const { setFileDownloadUrl, setFileDownloading } = getWeChatStoreMethods();
@@ -528,6 +544,15 @@ const messageHandlers: Record = {
}
setFileDownloadUrl(messageId, message.url);
+ dataProcessing({
+ type: "CmdDownloadFileResult",
+ wechatAccountId: message.wechatAccountId || 1,
+ friendMessageId: message.friendMessageId,
+ chatroomMessageId: message.chatroomMessageId,
+ url: message.url,
+ }).catch(error => {
+ console.error("回写文件下载地址失败:", error);
+ });
},
CmdFetchMomentResult: message => {