diff --git a/app/note/controller/v1/Note.php b/app/note/controller/v1/Note.php index 3d263c1..e66bab8 100644 --- a/app/note/controller/v1/Note.php +++ b/app/note/controller/v1/Note.php @@ -178,4 +178,27 @@ class Note extends BaseController return Response::error($e->getMessage(), $e->getCode() ?: 500); } } + + /** + * 上传笔记图片 + * POST /note/v1/item/image/:id + */ + public function image(Request $request, int $id) + { + try { + if ($id <= 0) { + return Response::error('笔记 ID 不正确', 400); + } + + $file = $request->file('image'); + if (!$file) { + return Response::error('图片文件不能为空', 400); + } + + $result = $this->noteService->uploadImage($this->getCurrentNoteUserId(), $id, $file); + return Response::success($result, '上传成功'); + } catch (\Throwable $e) { + return Response::error($e->getMessage(), $e->getCode() ?: 500); + } + } } diff --git a/app/note/route/app.php b/app/note/route/app.php index 52313dd..b237869 100644 --- a/app/note/route/app.php +++ b/app/note/route/app.php @@ -33,6 +33,7 @@ Route::group('v1', function () { Route::post('item/delete/:id', [Note::class, 'delete']); Route::post('item/transcript/:id', [Note::class, 'transcript']); Route::post('item/audio/:id', [Note::class, 'audio']); + Route::post('item/image/:id', [Note::class, 'image']); Route::post('ai/summary/:id', [Ai::class, 'summary']); Route::get('ai/summary/:id', [Ai::class, 'readSummary']); diff --git a/app/note/service/NoteService.php b/app/note/service/NoteService.php index e2e1229..c8a5894 100644 --- a/app/note/service/NoteService.php +++ b/app/note/service/NoteService.php @@ -268,6 +268,33 @@ class NoteService return $this->formatAudio($audio); } + /** + * 上传笔记图片。 + * + * 这里不额外建表,直接返回公开图片地址,由前端把图片标记写回 note.content, + * 以便在跨设备打开同一笔记时仍能恢复图片内容。 + * + * @param int $noteUserId + * @param int $id + * @param File $file + * @return array + * @throws \Exception + */ + public function uploadImage(int $noteUserId, int $id, File $file): array + { + $this->getOwnedNote($noteUserId, $id); + $savedPath = str_replace('\\', '/', Filesystem::disk('public')->putFile('note/image', $file)); + + return [ + 'disk' => 'public', + 'file_path' => $savedPath, + 'image_url' => $this->buildPublicFileUrl($savedPath), + 'file_size' => (int) $file->getSize(), + 'mime_type' => $this->detectImageMimeType($savedPath, $file), + 'updated_at' => time(), + ]; + } + /** * 创建分享 * @@ -444,6 +471,38 @@ class NoteService return $mimeMap[$extension] ?? 'application/octet-stream'; } + /** + * 推断图片 MIME。 + * + * 与音频上传一样,这里优先按扩展名兜底,避免依赖 fileinfo 扩展。 + * + * @param string $savedPath + * @param File $file + * @return string + */ + private function detectImageMimeType(string $savedPath, File $file): string + { + $extension = strtolower((string) pathinfo($savedPath, PATHINFO_EXTENSION)); + if ($extension === '') { + $extension = strtolower((string) pathinfo((string) $file->getOriginalName(), PATHINFO_EXTENSION)); + } + + $mimeMap = [ + 'avif' => 'image/avif', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'heic' => 'image/heic', + 'heif' => 'image/heif', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'svg' => 'image/svg+xml', + 'webp' => 'image/webp', + ]; + + return $mimeMap[$extension] ?? 'application/octet-stream'; + } + /** * 规范化标题 * diff --git a/route/app.php b/route/app.php index ee8a4c5..2dda3fb 100644 --- a/route/app.php +++ b/route/app.php @@ -69,6 +69,7 @@ Route::group('note/v1', function () { Route::post('item/delete/:id', [NoteItem::class, 'delete']); Route::post('item/transcript/:id', [NoteItem::class, 'transcript']); Route::post('item/audio/:id', [NoteItem::class, 'audio']); + Route::post('item/image/:id', [NoteItem::class, 'image']); Route::post('ai/summary/:id', [NoteAi::class, 'summary']); Route::get('ai/summary/:id', [NoteAi::class, 'readSummary']);