feat: add note module and route fixes

This commit is contained in:
nepiedg
2026-04-17 07:48:44 +00:00
parent 866ddb046b
commit 84e1c0daac
25 changed files with 2196 additions and 38 deletions
+294
View File
@@ -0,0 +1,294 @@
<?php
declare(strict_types=1);
namespace app\note\service;
use app\note\model\NoteAiSummary;
use app\note\model\NoteItem;
use app\note\model\NoteTranscript;
/**
* note 模块笔记服务
*/
class NoteService
{
/**
* 创建笔记
*
* @param int $noteUserId
* @param array $data
* @return array
*/
public function create(int $noteUserId, array $data): array
{
$now = time();
$note = new NoteItem();
$note->note_user_id = $noteUserId;
$note->title = $this->normalizeTitle(
(string) ($data['title'] ?? ''),
(string) ($data['content'] ?? '')
);
$note->content = (string) ($data['content'] ?? '');
$note->transcript_text = '';
$note->source_type = (string) ($data['source_type'] ?? 'text');
$note->status = (string) ($data['status'] ?? 'draft');
$note->audio_duration_ms = (int) ($data['audio_duration_ms'] ?? 0);
$note->summary_status = 'none';
$note->last_transcript_time = 0;
$note->created_at = $now;
$note->updated_at = $now;
$note->deleted_at = 0;
$note->save();
return $this->formatNoteItem($note);
}
/**
* 获取笔记列表
*
* @param int $noteUserId
* @param array $params
* @return array
*/
public function getList(int $noteUserId, array $params): array
{
$page = max(1, (int) ($params['page'] ?? 1));
$pageSize = max(1, min(100, (int) ($params['page_size'] ?? 10)));
$keyword = trim((string) ($params['keyword'] ?? ''));
$status = trim((string) ($params['status'] ?? ''));
$query = NoteItem::buildUserQuery($noteUserId);
if ($status !== '') {
$query->where('status', $status);
}
if ($keyword !== '') {
$query->where(function ($subQuery) use ($keyword) {
$subQuery->whereLike('title', '%' . $keyword . '%')
->whereOrLike('content', '%' . $keyword . '%')
->whereOrLike('transcript_text', '%' . $keyword . '%');
});
}
$total = (int) $query->count();
$list = $query->order('id', 'desc')
->page($page, $pageSize)
->select();
return [
'list' => array_map(function ($item) {
return $this->formatNoteItem($item);
}, $list->all()),
'total' => $total,
'page' => $page,
'page_size' => $pageSize,
];
}
/**
* 获取笔记详情
*
* @param int $noteUserId
* @param int $id
* @return array
* @throws \Exception
*/
public function getDetail(int $noteUserId, int $id): array
{
$note = $this->getOwnedNote($noteUserId, $id);
$summary = NoteAiSummary::findLatestByNoteId($id);
$result = $this->formatNoteItem($note);
$result['summary'] = $summary ? [
'summary_id' => (int) $summary->id,
'summary_type' => (string) $summary->summary_type,
'summary_text' => (string) $summary->summary_text,
'todo_list' => $this->decodeJsonList((string) $summary->todo_list),
'keywords' => $this->decodeJsonList((string) $summary->keywords),
'status' => (string) $summary->status,
] : null;
return $result;
}
/**
* 更新笔记
*
* @param int $noteUserId
* @param int $id
* @param array $data
* @return array
* @throws \Exception
*/
public function update(int $noteUserId, int $id, array $data): array
{
$note = $this->getOwnedNote($noteUserId, $id);
if (array_key_exists('title', $data)) {
$note->title = $this->normalizeTitle((string) $data['title'], (string) ($data['content'] ?? $note->content));
}
if (array_key_exists('content', $data)) {
$note->content = (string) $data['content'];
if (trim((string) $note->title) === '') {
$note->title = $this->normalizeTitle('', (string) $note->content);
}
}
if (array_key_exists('status', $data) && $data['status'] !== '') {
$note->status = (string) $data['status'];
}
if (array_key_exists('audio_duration_ms', $data)) {
$note->audio_duration_ms = max(0, (int) $data['audio_duration_ms']);
}
$note->updated_at = time();
$note->save();
return $this->formatNoteItem($note);
}
/**
* 删除笔记
*
* @param int $noteUserId
* @param int $id
* @return array
* @throws \Exception
*/
public function delete(int $noteUserId, int $id): array
{
$note = $this->getOwnedNote($noteUserId, $id);
$note->deleted_at = time();
$note->updated_at = time();
$note->save();
return [
'deleted' => true,
'id' => $id,
];
}
/**
* 保存转写内容
*
* @param int $noteUserId
* @param int $id
* @param array $data
* @return array
* @throws \Exception
*/
public function saveTranscript(int $noteUserId, int $id, array $data): array
{
$note = $this->getOwnedNote($noteUserId, $id);
$segmentNo = max(0, (int) ($data['segment_no'] ?? 0));
$now = time();
$transcript = NoteTranscript::findByNoteAndSegment($id, $segmentNo);
if (!$transcript) {
$transcript = new NoteTranscript();
$transcript->note_id = $id;
$transcript->segment_no = $segmentNo;
$transcript->created_at = $now;
}
$transcript->segment_text = (string) ($data['segment_text'] ?? '');
$transcript->full_text = (string) ($data['full_text'] ?? '');
$transcript->is_final = empty($data['is_final']) ? 0 : 1;
$transcript->audio_duration_ms = max(0, (int) ($data['audio_duration_ms'] ?? 0));
$transcript->save();
$note->transcript_text = $transcript->full_text;
$note->audio_duration_ms = max($note->audio_duration_ms, (int) $transcript->audio_duration_ms);
$note->last_transcript_time = $now;
$note->updated_at = $now;
if ($note->title === '') {
$note->title = $this->normalizeTitle('', $note->transcript_text);
}
$note->save();
return [
'note_id' => $id,
'segment_no' => $segmentNo,
'is_final' => (int) $transcript->is_final,
'transcript_text' => (string) $note->transcript_text,
'audio_duration_ms' => (int) $note->audio_duration_ms,
'updated_at' => (int) $note->updated_at,
];
}
/**
* 获取当前用户拥有的笔记
*
* @param int $noteUserId
* @param int $id
* @return NoteItem
* @throws \Exception
*/
public function getOwnedNote(int $noteUserId, int $id): NoteItem
{
$note = NoteItem::findOwnedNote($noteUserId, $id);
if (!$note) {
throw new \Exception('笔记不存在', 404);
}
return $note;
}
/**
* 格式化笔记返回
*
* @param NoteItem $note
* @return array
*/
private function formatNoteItem(NoteItem $note): array
{
return [
'id' => (int) $note->id,
'note_user_id' => (int) $note->note_user_id,
'title' => (string) $note->title,
'content' => (string) $note->content,
'transcript_text' => (string) $note->transcript_text,
'source_type' => (string) $note->source_type,
'status' => (string) $note->status,
'audio_duration_ms' => (int) $note->audio_duration_ms,
'summary_status' => (string) $note->summary_status,
'last_transcript_time' => (int) $note->last_transcript_time,
'created_at' => (int) $note->created_at,
'updated_at' => (int) $note->updated_at,
];
}
/**
* 规范化标题
*
* @param string $title
* @param string $fallback
* @return string
*/
private function normalizeTitle(string $title, string $fallback): string
{
$title = trim($title);
if ($title !== '') {
return mb_substr($title, 0, 255);
}
$fallback = trim(preg_replace('/\s+/', ' ', $fallback));
if ($fallback === '') {
return '未命名笔记';
}
return mb_substr($fallback, 0, 50);
}
/**
* 解析 JSON 列表
*
* @param string $value
* @return array
*/
private function decodeJsonList(string $value): array
{
$decoded = json_decode($value, true);
return is_array($decoded) ? $decoded : [];
}
}