feat(note): add audio upload and sharing functionality
- Introduced `note_audio` table for storing audio attachments related to notes. - Implemented audio upload endpoint in `Note` controller to handle audio file uploads. - Added sharing functionality with `note_share` table to manage share tokens and view counts. - Updated API routes to include endpoints for audio uploads and share creation. - Enhanced documentation to reflect new audio and sharing features.
This commit is contained in:
@@ -3,9 +3,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\note\service;
|
||||
|
||||
use app\note\model\NoteAudio;
|
||||
use app\note\model\NoteAiSummary;
|
||||
use app\note\model\NoteItem;
|
||||
use app\note\model\NoteShare;
|
||||
use app\note\model\NoteTranscript;
|
||||
use think\File;
|
||||
use think\facade\Filesystem;
|
||||
|
||||
/**
|
||||
* note 模块笔记服务
|
||||
@@ -98,8 +102,10 @@ class NoteService
|
||||
{
|
||||
$note = $this->getOwnedNote($noteUserId, $id);
|
||||
$summary = NoteAiSummary::findLatestByNoteId($id);
|
||||
$audio = NoteAudio::findLatestByNoteId($id);
|
||||
|
||||
$result = $this->formatNoteItem($note);
|
||||
$result['audio'] = $audio ? $this->formatAudio($audio) : null;
|
||||
$result['summary'] = $summary ? [
|
||||
'summary_id' => (int) $summary->id,
|
||||
'summary_type' => (string) $summary->summary_type,
|
||||
@@ -216,6 +222,131 @@ class NoteService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传录音文件
|
||||
*
|
||||
* @param int $noteUserId
|
||||
* @param int $id
|
||||
* @param File $file
|
||||
* @param int $durationMs
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function uploadAudio(int $noteUserId, int $id, File $file, int $durationMs = 0): array
|
||||
{
|
||||
$note = $this->getOwnedNote($noteUserId, $id);
|
||||
$savedPath = str_replace('\\', '/', Filesystem::disk('public')->putFile('note/audio', $file));
|
||||
$now = time();
|
||||
|
||||
$audio = NoteAudio::findLatestByNoteId($id);
|
||||
if (!$audio) {
|
||||
$audio = new NoteAudio();
|
||||
$audio->note_id = $id;
|
||||
$audio->created_at = $now;
|
||||
}
|
||||
|
||||
$audio->disk = 'public';
|
||||
$audio->file_path = $savedPath;
|
||||
$audio->file_url = $this->buildPublicFileUrl($savedPath);
|
||||
$audio->file_size = (int) $file->getSize();
|
||||
$audio->mime_type = (string) $file->getMime();
|
||||
$audio->duration_ms = max(0, $durationMs);
|
||||
$audio->updated_at = $now;
|
||||
$audio->save();
|
||||
|
||||
$note->audio_duration_ms = max((int) $note->audio_duration_ms, (int) $audio->duration_ms);
|
||||
if ($note->source_type === 'text') {
|
||||
$note->source_type = trim((string) $note->content) !== '' ? 'mix' : 'audio';
|
||||
}
|
||||
$note->updated_at = $now;
|
||||
$note->save();
|
||||
|
||||
return $this->formatAudio($audio);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建分享
|
||||
*
|
||||
* @param int $noteUserId
|
||||
* @param int $id
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createShare(int $noteUserId, int $id): array
|
||||
{
|
||||
$note = $this->getOwnedNote($noteUserId, $id);
|
||||
$share = NoteShare::findActiveByNote($id, $noteUserId);
|
||||
$now = time();
|
||||
|
||||
if (!$share) {
|
||||
$share = new NoteShare();
|
||||
$share->note_id = $id;
|
||||
$share->note_user_id = $noteUserId;
|
||||
$share->share_token = bin2hex(random_bytes(16));
|
||||
$share->view_count = 0;
|
||||
$share->status = 1;
|
||||
$share->created_at = $now;
|
||||
}
|
||||
|
||||
$share->title = (string) $note->title;
|
||||
$share->updated_at = $now;
|
||||
$share->save();
|
||||
|
||||
return [
|
||||
'note_id' => $id,
|
||||
'share_token' => (string) $share->share_token,
|
||||
'share_path' => '/pages/note/edit?share_token=' . $share->share_token,
|
||||
'title' => (string) $note->title,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分享详情
|
||||
*
|
||||
* @param string $token
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getSharedDetail(string $token): array
|
||||
{
|
||||
$share = NoteShare::findByToken($token);
|
||||
if (!$share) {
|
||||
throw new \Exception('分享内容不存在或已失效', 404);
|
||||
}
|
||||
|
||||
if ((int) $share->expired_at > 0 && (int) $share->expired_at < time()) {
|
||||
throw new \Exception('分享已过期', 410);
|
||||
}
|
||||
|
||||
$note = NoteItem::where('id', (int) $share->note_id)
|
||||
->where('deleted_at', 0)
|
||||
->find();
|
||||
if (!$note) {
|
||||
throw new \Exception('分享内容不存在', 404);
|
||||
}
|
||||
|
||||
$summary = NoteAiSummary::findLatestByNoteId((int) $note->id);
|
||||
$audio = NoteAudio::findLatestByNoteId((int) $note->id);
|
||||
|
||||
$share->view_count = (int) $share->view_count + 1;
|
||||
$share->last_view_time = time();
|
||||
$share->save();
|
||||
|
||||
$result = $this->formatNoteItem($note);
|
||||
$result['audio'] = $audio ? $this->formatAudio($audio) : null;
|
||||
$result['summary'] = $summary ? [
|
||||
'summary_text' => (string) $summary->summary_text,
|
||||
'status' => (string) $summary->status,
|
||||
] : null;
|
||||
$result['share'] = [
|
||||
'share_token' => (string) $share->share_token,
|
||||
'title' => (string) $share->title,
|
||||
'view_count' => (int) $share->view_count,
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户拥有的笔记
|
||||
*
|
||||
@@ -258,6 +389,26 @@ class NoteService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化音频附件
|
||||
*
|
||||
* @param NoteAudio $audio
|
||||
* @return array
|
||||
*/
|
||||
private function formatAudio(NoteAudio $audio): array
|
||||
{
|
||||
return [
|
||||
'audio_id' => (int) $audio->id,
|
||||
'disk' => (string) $audio->disk,
|
||||
'file_path' => (string) $audio->file_path,
|
||||
'audio_url' => (string) $audio->file_url,
|
||||
'file_size' => (int) $audio->file_size,
|
||||
'mime_type' => (string) $audio->mime_type,
|
||||
'duration_ms' => (int) $audio->duration_ms,
|
||||
'updated_at' => (int) $audio->updated_at,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化标题
|
||||
*
|
||||
@@ -291,4 +442,15 @@ class NoteService
|
||||
$decoded = json_decode($value, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接公开文件 URL
|
||||
*
|
||||
* @param string $savedPath
|
||||
* @return string
|
||||
*/
|
||||
private function buildPublicFileUrl(string $savedPath): string
|
||||
{
|
||||
return rtrim((string) request()->domain(), '/') . '/storage/' . ltrim($savedPath, '/');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user