|
|
@@ -340,12 +340,14 @@ class WorkOrder extends Api{
|
|
|
|
|
|
$firstImageUrl = $this->finalizeFrameImageToOss($firstFrame, $taskId);
|
|
|
$lastImageUrl = $this->finalizeFrameImageToOss($lastFrame, $taskId);
|
|
|
+ $firstImageDb = $this->toDbUploadPath($firstImageUrl);
|
|
|
+ $lastImageDb = $this->toDbUploadPath($lastImageUrl);
|
|
|
|
|
|
$videoData = [
|
|
|
'video_id' => $taskId,
|
|
|
'prompt' => $prompt,
|
|
|
- 'first_image_url' => $firstImageUrl,
|
|
|
- 'last_image_url' => $lastImageUrl,
|
|
|
+ 'first_image_url' => $firstImageDb,
|
|
|
+ 'last_image_url' => $lastImageDb,
|
|
|
'model' => $data['model'],
|
|
|
'seconds' => (string)$data['duration'],
|
|
|
'size' => (string)$data['ratio'],
|
|
|
@@ -374,15 +376,14 @@ class WorkOrder extends Api{
|
|
|
'status' => $responseData['status'] ?? '',
|
|
|
'created_at' => $responseData['created_at'] ?? '',
|
|
|
'mode' => $lastImageUrl !== '' ? 'first_last_frame' : 'single_frame',
|
|
|
- 'first_image_url' => $firstImageUrl,
|
|
|
- 'last_image_url' => $lastImageUrl,
|
|
|
+ 'first_image_url' => $this->toPublicMediaUrl($firstImageDb),
|
|
|
+ 'last_image_url' => $this->toPublicMediaUrl($lastImageDb),
|
|
|
]
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 即梦AI--获取视频接口
|
|
|
- * 首帧图 + 尾帧图 = 新效果视频
|
|
|
+ * 即梦AI--获取视频接口(先 GET 查询任务状态,succeeded 后再落盘/OSS/入库)
|
|
|
*/
|
|
|
public function Get_ImgToVideo()
|
|
|
{
|
|
|
@@ -390,93 +391,74 @@ class WorkOrder extends Api{
|
|
|
$apiKey = 'ark-1ca8aa97-3663-4bc7-8c53-d4ab516883f1-d2339';
|
|
|
|
|
|
$params = $this->request->param();
|
|
|
- $taskId = $params['task_id'] ?? $params['video_id'] ?? '';
|
|
|
+ $taskId = trim((string)($params['task_id'] ?? $params['video_id'] ?? ''));
|
|
|
if ($taskId === '') {
|
|
|
return json(['code' => 0, 'msg' => '任务 ID 不能为空']);
|
|
|
}
|
|
|
|
|
|
- // 查询任务状态
|
|
|
- $queryUrl = $apiUrl . '/' . $taskId;
|
|
|
- $ch2 = curl_init();
|
|
|
- curl_setopt($ch2, CURLOPT_URL, $queryUrl);
|
|
|
- curl_setopt($ch2, CURLOPT_HTTPHEADER, [
|
|
|
- 'Content-Type: application/json',
|
|
|
- 'Authorization: Bearer ' . $apiKey
|
|
|
- ]);
|
|
|
- curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
|
|
|
- curl_setopt($ch2, CURLOPT_SSL_VERIFYPEER, false); // 开发环境临时关闭SSL验证
|
|
|
- curl_setopt($ch2, CURLOPT_TIMEOUT, 60); // 超时时间
|
|
|
-
|
|
|
- $queryResponse = curl_exec($ch2);
|
|
|
-
|
|
|
- // 检查 cURL 错误
|
|
|
- if (curl_errno($ch2)) {
|
|
|
- $error = curl_error($ch2);
|
|
|
- curl_close($ch2);
|
|
|
- return json(['code' => 0, 'msg' => 'Curl 错误: ' . $error]);
|
|
|
+ // 1. 先根据任务 ID 查询即梦任务状态(GET /tasks/{id})
|
|
|
+ $queryResult = $this->fetchVolcImgToVideoTask($taskId, $apiUrl, $apiKey);
|
|
|
+ if (!$queryResult['ok']) {
|
|
|
+ return json([
|
|
|
+ 'code' => 0,
|
|
|
+ 'msg' => '查询任务失败: ' . $queryResult['error'],
|
|
|
+ 'data' => [
|
|
|
+ 'task_id' => $taskId,
|
|
|
+ 'http_code' => $queryResult['http_code'],
|
|
|
+ 'task' => $queryResult['data'],
|
|
|
+ ],
|
|
|
+ ]);
|
|
|
}
|
|
|
- curl_close($ch2);
|
|
|
|
|
|
- // 解析查询响应
|
|
|
- $queryData = json_decode($queryResponse, true);
|
|
|
- // print_r($queryData);die;
|
|
|
-
|
|
|
- // 轮询任务状态,直到完成
|
|
|
- $maxPolls = 30;
|
|
|
- $pollCount = 0;
|
|
|
- $pollData = is_array($queryData) ? $queryData : [];
|
|
|
- $taskStatus = $pollData['status'] ?? '';
|
|
|
+ $taskData = $queryResult['data'];
|
|
|
+ $taskStatus = (string)($taskData['status'] ?? '');
|
|
|
|
|
|
- while (!in_array($taskStatus, ['completed', 'succeeded']) && $pollCount < $maxPolls) {
|
|
|
- sleep(5); // 每5秒轮询一次
|
|
|
- $pollCount++;
|
|
|
-
|
|
|
- // 再次查询任务状态
|
|
|
- $ch3 = curl_init();
|
|
|
- curl_setopt($ch3, CURLOPT_URL, $queryUrl);
|
|
|
- curl_setopt($ch3, CURLOPT_HTTPHEADER, [
|
|
|
- 'Content-Type: application/json',
|
|
|
- 'Authorization: Bearer ' . $apiKey
|
|
|
+ // 2. 终态(failed / cancelled / expired)直接返回,不进入下载/入库
|
|
|
+ $terminalMsg = $this->resolveVolcTaskTerminalFailure($taskStatus);
|
|
|
+ if ($terminalMsg !== null) {
|
|
|
+ return json([
|
|
|
+ 'code' => 0,
|
|
|
+ 'msg' => $terminalMsg,
|
|
|
+ 'data' => $this->formatVolcTaskQueryPayload($taskData),
|
|
|
]);
|
|
|
- curl_setopt($ch3, CURLOPT_RETURNTRANSFER, true);
|
|
|
- curl_setopt($ch3, CURLOPT_SSL_VERIFYPEER, false);
|
|
|
- curl_setopt($ch3, CURLOPT_TIMEOUT, 60);
|
|
|
-
|
|
|
- $pollResponse = curl_exec($ch3);
|
|
|
- curl_close($ch3);
|
|
|
-
|
|
|
- $pollData = json_decode($pollResponse, true);
|
|
|
- $taskStatus = $pollData['status'] ?? '';
|
|
|
-
|
|
|
- // 检查任务是否失败
|
|
|
- if ($taskStatus === 'failed') {
|
|
|
- return json(['code' => 0, 'msg' => '任务执行失败']);
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
- // 如果任务已经成功,直接使用 $queryData
|
|
|
- if (empty($pollData) && isset($queryData['status']) && $queryData['status'] === 'succeeded') {
|
|
|
- $pollData = $queryData;
|
|
|
+ // 3. queued / running 等未完成态则轮询(最多 30 次,间隔 5 秒)
|
|
|
+ if (!$this->isVolcVideoTaskSucceeded($taskStatus)) {
|
|
|
+ $pollResult = $this->pollVolcImgToVideoTaskUntilDone($taskId, $apiUrl, $apiKey, 30, 5);
|
|
|
+ if (!$pollResult['ok']) {
|
|
|
+ return json([
|
|
|
+ 'code' => 0,
|
|
|
+ 'msg' => $pollResult['error'],
|
|
|
+ 'data' => $this->formatVolcTaskQueryPayload($pollResult['data']),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ $taskData = $pollResult['data'];
|
|
|
+ $taskStatus = (string)($taskData['status'] ?? '');
|
|
|
}
|
|
|
|
|
|
- // 检查轮询是否超时
|
|
|
- if (!in_array($taskStatus, ['completed', 'succeeded'])) {
|
|
|
- return json(['code' => 0, 'msg' => '任务执行超时']);
|
|
|
+ if (!$this->isVolcVideoTaskSucceeded($taskStatus)) {
|
|
|
+ return json([
|
|
|
+ 'code' => 0,
|
|
|
+ 'msg' => '轮询超时,当前状态:' . $this->getVolcTaskStatusMessage($taskStatus),
|
|
|
+ 'data' => $this->formatVolcTaskQueryPayload($taskData),
|
|
|
+ ]);
|
|
|
}
|
|
|
|
|
|
- // 获取视频 URL(兼容不同响应结构)
|
|
|
- $videoUrl = $pollData['content']['video_url']
|
|
|
- ?? $pollData['output']['video_url']
|
|
|
- ?? $pollData['video_url']
|
|
|
- ?? '';
|
|
|
+ // 4. 事务处理:下载视频 → 本地/OSS → 更新数据库
|
|
|
+ $videoUrl = $this->extractVideoUrlFromVolcTask($taskData);
|
|
|
if ($videoUrl === '') {
|
|
|
- return json(['code' => 0, 'msg' => '获取视频 URL 失败', 'data' => ['pollData' => $pollData]]);
|
|
|
+ return json([
|
|
|
+ 'code' => 0,
|
|
|
+ 'msg' => '获取视频 URL 失败',
|
|
|
+ 'data' => $this->formatVolcTaskQueryPayload($taskData),
|
|
|
+ ]);
|
|
|
}
|
|
|
|
|
|
$fileName = $this->sanitizeTaskIdSegment($taskId) . '.mp4';
|
|
|
$saveDir = $this->buildTaskMediaLocalDir($taskId);
|
|
|
- if (!is_dir($saveDir)) {
|
|
|
- mkdir($saveDir, 0755, true);
|
|
|
+ if (!is_dir($saveDir) && !@mkdir($saveDir, 0755, true) && !is_dir($saveDir)) {
|
|
|
+ return json(['code' => 0, 'msg' => '创建视频目录失败', 'data' => ['saveDir' => $saveDir]]);
|
|
|
}
|
|
|
|
|
|
$savePath = $saveDir . $fileName;
|
|
|
@@ -491,42 +473,222 @@ class WorkOrder extends Api{
|
|
|
|
|
|
$objectKey = $this->buildTaskMediaObjectKey($taskId, $fileName);
|
|
|
$upload = $this->uploadToOSS($savePath, $objectKey);
|
|
|
- if ($upload['success']) {
|
|
|
- $webUrl = $upload['url'] !== '' ? $upload['url'] : Common::ossFullUrl($objectKey);
|
|
|
- } else {
|
|
|
- $webUrl = $objectKey;
|
|
|
- Log::write('[Get_ImgToVideo] OSS上传失败,使用本地相对路径: ' . $objectKey, 'error');
|
|
|
+ $ossObjectKey = (string)($upload['object_key'] ?? $objectKey);
|
|
|
+ $webUrlDb = $this->toDbUploadPath($ossObjectKey);
|
|
|
+ if ($webUrlDb === '' || stripos($webUrlDb, 'uploads/') !== 0) {
|
|
|
+ $fullUrl = $upload['success']
|
|
|
+ ? ((string)($upload['url'] ?? '') !== '' ? $upload['url'] : Common::ossFullUrl($objectKey))
|
|
|
+ : ($this->buildPublicUploadUrl($savePath) ?: Common::ossFullUrl($objectKey));
|
|
|
+ $webUrlDb = $this->toDbUploadPath((string)$fullUrl);
|
|
|
+ if (!$upload['success']) {
|
|
|
+ Log::write('[Get_ImgToVideo] OSS上传失败,入库路径=' . $webUrlDb, 'error');
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- Db::name('video')->where('video_id', $taskId)->update(['web_url' => $webUrl]);
|
|
|
+ Db::name('video')->where('video_id', $taskId)->update(['web_url' => $webUrlDb]);
|
|
|
} catch (Exception $e) {
|
|
|
return json([
|
|
|
'code' => 0,
|
|
|
'msg' => '视频已生成,但数据库更新失败',
|
|
|
- 'data' => [
|
|
|
- 'task_id' => $taskId,
|
|
|
- 'web_url' => $webUrl,
|
|
|
+ 'data' => array_merge($this->formatVolcTaskQueryPayload($taskData), [
|
|
|
+ 'web_url' => $this->toPublicMediaUrl($webUrlDb),
|
|
|
'error_message' => $e->getMessage(),
|
|
|
- ],
|
|
|
+ ]),
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
return json([
|
|
|
'code' => 1,
|
|
|
'msg' => '视频获取成功',
|
|
|
- 'data' => [
|
|
|
+ 'data' => array_merge($this->formatVolcTaskQueryPayload($taskData), [
|
|
|
'task_id' => $taskId,
|
|
|
'video_id' => $taskId,
|
|
|
- 'status' => $taskStatus,
|
|
|
- 'web_url' => $webUrl,
|
|
|
- 'oss_object_key' => $upload['object_key'] ?? $objectKey,
|
|
|
+ 'web_url' => $this->toPublicMediaUrl($webUrlDb),
|
|
|
+ 'oss_object_key' => $ossObjectKey,
|
|
|
'oss_uploaded' => $upload['success'] ?? false,
|
|
|
- 'local_path' => $objectKey,
|
|
|
- ],
|
|
|
+ 'local_path' => $webUrlDb,
|
|
|
+ 'source_video_url' => $videoUrl,
|
|
|
+ ]),
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * GET 查询即梦图生视频任务:/api/v3/contents/generations/tasks/{task_id}
|
|
|
+ * @return array{ok:bool,data:array,error:string,http_code:int}
|
|
|
+ */
|
|
|
+ private function fetchVolcImgToVideoTask(string $taskId, string $apiUrl, string $apiKey): array
|
|
|
+ {
|
|
|
+ $empty = ['ok' => false, 'data' => [], 'error' => '', 'http_code' => 0];
|
|
|
+ $queryUrl = rtrim($apiUrl, '/') . '/' . str_replace('%2F', '/', rawurlencode($taskId));
|
|
|
+
|
|
|
+ $ch = curl_init();
|
|
|
+ curl_setopt($ch, CURLOPT_URL, $queryUrl);
|
|
|
+ curl_setopt($ch, CURLOPT_HTTPGET, true);
|
|
|
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
|
+ 'Content-Type: application/json',
|
|
|
+ 'Authorization: Bearer ' . $apiKey,
|
|
|
+ ]);
|
|
|
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
|
+ curl_setopt($ch, CURLOPT_TIMEOUT, 60);
|
|
|
+
|
|
|
+ $response = curl_exec($ch);
|
|
|
+ if (curl_errno($ch)) {
|
|
|
+ $empty['error'] = curl_error($ch);
|
|
|
+ curl_close($ch);
|
|
|
+ return $empty;
|
|
|
+ }
|
|
|
+
|
|
|
+ $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
+ curl_close($ch);
|
|
|
+
|
|
|
+ $data = json_decode((string)$response, true);
|
|
|
+ if (!is_array($data)) {
|
|
|
+ return ['ok' => false, 'data' => [], 'error' => '任务响应解析失败', 'http_code' => $httpCode];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isset($data['error'])) {
|
|
|
+ $errMsg = is_array($data['error'])
|
|
|
+ ? (string)($data['error']['message'] ?? json_encode($data['error'], JSON_UNESCAPED_UNICODE))
|
|
|
+ : (string)$data['error'];
|
|
|
+ return ['ok' => false, 'data' => $data, 'error' => $errMsg, 'http_code' => $httpCode];
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($httpCode < 200 || $httpCode >= 300) {
|
|
|
+ return ['ok' => false, 'data' => $data, 'error' => 'HTTP ' . $httpCode, 'http_code' => $httpCode];
|
|
|
+ }
|
|
|
+
|
|
|
+ return ['ok' => true, 'data' => $data, 'error' => '', 'http_code' => $httpCode];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 轮询任务直至 succeeded,或遇到 failed/cancelled/expired
|
|
|
+ * @return array{ok:bool,data:array,error:string}
|
|
|
+ */
|
|
|
+ private function pollVolcImgToVideoTaskUntilDone(
|
|
|
+ string $taskId,
|
|
|
+ string $apiUrl,
|
|
|
+ string $apiKey,
|
|
|
+ int $maxPolls = 30,
|
|
|
+ int $intervalSeconds = 5
|
|
|
+ ): array {
|
|
|
+ $pollCount = 0;
|
|
|
+ $lastData = [];
|
|
|
+
|
|
|
+ while ($pollCount < $maxPolls) {
|
|
|
+ sleep($intervalSeconds);
|
|
|
+ $pollCount++;
|
|
|
+
|
|
|
+ $result = $this->fetchVolcImgToVideoTask($taskId, $apiUrl, $apiKey);
|
|
|
+ if (!$result['ok']) {
|
|
|
+ return ['ok' => false, 'data' => $result['data'], 'error' => $result['error']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $lastData = $result['data'];
|
|
|
+ $status = (string)($lastData['status'] ?? '');
|
|
|
+
|
|
|
+ $terminalMsg = $this->resolveVolcTaskTerminalFailure($status);
|
|
|
+ if ($terminalMsg !== null) {
|
|
|
+ return ['ok' => false, 'data' => $lastData, 'error' => $terminalMsg];
|
|
|
+ }
|
|
|
+ if ($this->isVolcVideoTaskSucceeded($status)) {
|
|
|
+ return ['ok' => true, 'data' => $lastData, 'error' => ''];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $lastStatus = (string)($lastData['status'] ?? '');
|
|
|
+ return [
|
|
|
+ 'ok' => false,
|
|
|
+ 'data' => $lastData,
|
|
|
+ 'error' => '轮询超时,当前状态:' . $this->getVolcTaskStatusMessage($lastStatus),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 即梦任务 status 中文说明(官方:queued/running/cancelled/succeeded/failed/expired)
|
|
|
+ */
|
|
|
+ private function getVolcTaskStatusMessage(string $status): string
|
|
|
+ {
|
|
|
+ $map = [
|
|
|
+ 'queued' => '排队中',
|
|
|
+ 'running' => '任务运行中',
|
|
|
+ 'cancelled' => '任务已取消',
|
|
|
+ 'succeeded' => '任务成功',
|
|
|
+ 'failed' => '任务失败',
|
|
|
+ 'expired' => '任务超时',
|
|
|
+ 'completed' => '任务成功',
|
|
|
+ ];
|
|
|
+ return $map[$status] ?? ('未知状态(' . $status . ')');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否为可继续轮询的进行中状态
|
|
|
+ */
|
|
|
+ private function isVolcVideoTaskPending(string $status): bool
|
|
|
+ {
|
|
|
+ return in_array($status, ['queued', 'running'], true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 终态且不可下载:failed / cancelled / expired
|
|
|
+ */
|
|
|
+ private function resolveVolcTaskTerminalFailure(string $status): ?string
|
|
|
+ {
|
|
|
+ $map = [
|
|
|
+ 'failed' => '任务执行失败',
|
|
|
+ 'cancelled' => '任务已取消',
|
|
|
+ 'expired' => '任务超时',
|
|
|
+ ];
|
|
|
+ return $map[$status] ?? null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function isVolcVideoTaskSucceeded(string $status): bool
|
|
|
+ {
|
|
|
+ return in_array($status, ['succeeded', 'completed'], true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从即梦任务响应中取视频地址(优先 content.video_url)
|
|
|
+ */
|
|
|
+ private function extractVideoUrlFromVolcTask(array $taskData): string
|
|
|
+ {
|
|
|
+ $content = $taskData['content'] ?? null;
|
|
|
+ if (is_array($content) && !empty($content['video_url'])) {
|
|
|
+ return trim((string)$content['video_url']);
|
|
|
+ }
|
|
|
+ return trim((string)(
|
|
|
+ $taskData['output']['video_url']
|
|
|
+ ?? $taskData['video_url']
|
|
|
+ ?? ''
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化任务查询结果(与即梦 GET 响应字段对齐)
|
|
|
+ */
|
|
|
+ private function formatVolcTaskQueryPayload(array $taskData): array
|
|
|
+ {
|
|
|
+ $status = (string)($taskData['status'] ?? '');
|
|
|
+ return [
|
|
|
+ 'id' => $taskData['id'] ?? '',
|
|
|
+ 'model' => $taskData['model'] ?? '',
|
|
|
+ 'status' => $status,
|
|
|
+ 'status_text' => $this->getVolcTaskStatusMessage($status),
|
|
|
+ 'is_pending' => $this->isVolcVideoTaskPending($status),
|
|
|
+ 'is_succeeded' => $this->isVolcVideoTaskSucceeded($status),
|
|
|
+ 'content' => $taskData['content'] ?? null,
|
|
|
+ 'usage' => $taskData['usage'] ?? null,
|
|
|
+ 'created_at' => $taskData['created_at'] ?? null,
|
|
|
+ 'updated_at' => $taskData['updated_at'] ?? null,
|
|
|
+ 'resolution' => $taskData['resolution'] ?? '',
|
|
|
+ 'ratio' => $taskData['ratio'] ?? '',
|
|
|
+ 'duration' => $taskData['duration'] ?? null,
|
|
|
+ 'framespersecond' => $taskData['framespersecond'] ?? null,
|
|
|
+ 'generate_audio' => $taskData['generate_audio'] ?? null,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 下载远程文件(视频等)
|
|
|
* @return string|false
|
|
|
@@ -572,6 +734,18 @@ class WorkOrder extends Api{
|
|
|
->order('id desc')
|
|
|
->limit(($page - 1) * $limit, $limit)
|
|
|
->select();
|
|
|
+ foreach ($list as &$row) {
|
|
|
+ if (!empty($row['web_url'])) {
|
|
|
+ $row['web_url'] = $this->toPublicMediaUrl((string)$row['web_url']);
|
|
|
+ }
|
|
|
+ if (!empty($row['first_image_url'])) {
|
|
|
+ $row['first_image_url'] = $this->toPublicMediaUrl((string)$row['first_image_url']);
|
|
|
+ }
|
|
|
+ if (!empty($row['last_image_url'])) {
|
|
|
+ $row['last_image_url'] = $this->toPublicMediaUrl((string)$row['last_image_url']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($row);
|
|
|
$total = Db::name('video')->where('mod_rq', null)
|
|
|
->where($where)
|
|
|
->count();
|
|
|
@@ -1732,6 +1906,53 @@ class WorkOrder extends Api{
|
|
|
];
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 入库路径:仅保留 uploads/ 及后面部分
|
|
|
+ */
|
|
|
+ private function toDbUploadPath(string $path): string
|
|
|
+ {
|
|
|
+ $path = trim(str_replace('\\', '/', $path));
|
|
|
+ if ($path === '') {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (stripos($path, 'http://') === 0 || stripos($path, 'https://') === 0) {
|
|
|
+ $mark = '/uploads/';
|
|
|
+ $pos = stripos($path, $mark);
|
|
|
+ if ($pos !== false) {
|
|
|
+ return ltrim(substr($path, $pos + 1), '/');
|
|
|
+ }
|
|
|
+ return $path;
|
|
|
+ }
|
|
|
+
|
|
|
+ $path = ltrim($path, '/');
|
|
|
+ if (stripos($path, 'uploads/') === 0) {
|
|
|
+ return $path;
|
|
|
+ }
|
|
|
+
|
|
|
+ $pos = stripos($path, 'uploads/');
|
|
|
+ if ($pos !== false) {
|
|
|
+ return substr($path, $pos);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $path;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 接口返回:库内相对路径转完整 URL
|
|
|
+ */
|
|
|
+ private function toPublicMediaUrl(string $dbPath): string
|
|
|
+ {
|
|
|
+ $dbPath = trim($dbPath);
|
|
|
+ if ($dbPath === '') {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ if (stripos($dbPath, 'http://') === 0 || stripos($dbPath, 'https://') === 0) {
|
|
|
+ return $dbPath;
|
|
|
+ }
|
|
|
+ return Common::ossFullUrl($this->toDbUploadPath($dbPath));
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 将 public 下本地文件转为可公网访问的站点 URL(OSS 不可用时的回退)
|
|
|
*/
|