feat(media): 微信消息媒体 OSS 归档与下载地址回写
- 新增 MediaArchiveJob、MediaOssArchiveService、WechatMediaArchiveService 与回填命令 - Message/DataProcessing 等支持归档调度与持久化下载 URL - WebSocket 控制器整理;朋友圈与文档/定时任务说明更新 Made-with: Cursor
This commit is contained in:
261
application/common/service/MediaOssArchiveService.php
Normal file
261
application/common/service/MediaOssArchiveService.php
Normal file
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\util\AliyunOSS;
|
||||
use think\facade\Log;
|
||||
|
||||
class MediaOssArchiveService
|
||||
{
|
||||
protected static $mimeExtensionMap = [
|
||||
'image/jpeg' => '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;
|
||||
}
|
||||
}
|
||||
372
application/common/service/WechatMediaArchiveService.php
Normal file
372
application/common/service/WechatMediaArchiveService.php
Normal file
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
|
||||
class WechatMediaArchiveService
|
||||
{
|
||||
public static function archiveMessageById($messageId)
|
||||
{
|
||||
$messageId = (int)$messageId;
|
||||
if ($messageId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$row = Db::table('s2_wechat_message')->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><!\[CDATA\[(.*?)\]\]><\/title>/i', $rawContent, $matches) && !empty($matches[1])) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
|
||||
if (preg_match('/<title>([^<]+)<\/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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user