- 添加用户管理功能的测试,包括创建、更新、停用、激活用户及用户登录 JWT 测试 - 提供用户管理相关的请求验证类与控制器 - 引入 CORS 配置信息,支持跨域请求 - 添加数据库播种器以便创建根用户 - 配置 API 默认使用 JWT 认证 - 添加聊天会话和消息的模型、迁移文件及关联功能
133 lines
4.0 KiB
PHP
133 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Enums\ChatSessionStatus;
|
|
use App\Exceptions\ChatSessionStatusException;
|
|
use App\Models\ChatSession;
|
|
use App\Models\Message;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
|
|
class ChatService
|
|
{
|
|
public function createSession(string $name = null): ChatSession
|
|
{
|
|
return ChatSession::create([
|
|
'session_id' => (string) Str::uuid(),
|
|
'session_name' => $name,
|
|
'status' => ChatSessionStatus::OPEN,
|
|
'last_seq' => 0,
|
|
]);
|
|
}
|
|
|
|
public function getSession(string $sessionId): ChatSession
|
|
{
|
|
return ChatSession::query()->whereKey($sessionId)->firstOrFail();
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $dto
|
|
*/
|
|
public function appendMessage(array $dto): Message
|
|
{
|
|
return DB::transaction(function () use ($dto) {
|
|
/** @var ChatSession $session */
|
|
$session = ChatSession::query()
|
|
->whereKey($dto['session_id'])
|
|
->lockForUpdate()
|
|
->firstOrFail();
|
|
|
|
$this->ensureCanAppend($session, $dto['role'], $dto['type']);
|
|
|
|
$dedupeKey = $dto['dedupe_key'] ?? null;
|
|
if ($dedupeKey) {
|
|
$existing = Message::query()
|
|
->where('session_id', $session->session_id)
|
|
->where('dedupe_key', $dedupeKey)
|
|
->first();
|
|
|
|
if ($existing) {
|
|
return $existing;
|
|
}
|
|
}
|
|
|
|
$newSeq = $session->last_seq + 1;
|
|
|
|
$message = new Message([
|
|
'message_id' => (string) Str::uuid(),
|
|
'session_id' => $session->session_id,
|
|
'role' => $dto['role'],
|
|
'type' => $dto['type'],
|
|
'content' => $dto['content'] ?? null,
|
|
'payload' => $dto['payload'] ?? null,
|
|
'reply_to' => $dto['reply_to'] ?? null,
|
|
'dedupe_key' => $dedupeKey,
|
|
'seq' => $newSeq,
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
try {
|
|
$message->save();
|
|
} catch (QueryException $e) {
|
|
if ($this->isUniqueConstraint($e) && $dedupeKey) {
|
|
$existing = Message::query()
|
|
->where('session_id', $session->session_id)
|
|
->where('dedupe_key', $dedupeKey)
|
|
->first();
|
|
|
|
if ($existing) {
|
|
return $existing;
|
|
}
|
|
}
|
|
|
|
throw $e;
|
|
}
|
|
|
|
$session->update([
|
|
'last_seq' => $newSeq,
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
return $message;
|
|
});
|
|
}
|
|
|
|
public function listMessagesBySeq(string $sessionId, int $afterSeq, int $limit = 50): Collection
|
|
{
|
|
$this->getSession($sessionId); // ensure exists
|
|
|
|
return Message::query()
|
|
->where('session_id', $sessionId)
|
|
->where('seq', '>', $afterSeq)
|
|
->orderBy('seq')
|
|
->limit($limit)
|
|
->get();
|
|
}
|
|
|
|
private function ensureCanAppend(ChatSession $session, string $role, string $type): void
|
|
{
|
|
if ($session->status === ChatSessionStatus::CLOSED) {
|
|
$allowed = $role === Message::ROLE_SYSTEM && in_array($type, ['run.status', 'error'], true);
|
|
if (! $allowed) {
|
|
throw new ChatSessionStatusException('Session is closed');
|
|
}
|
|
}
|
|
|
|
if ($session->status === ChatSessionStatus::LOCKED) {
|
|
if ($role === Message::ROLE_USER && $type === 'user.prompt') {
|
|
throw new ChatSessionStatusException('Session is locked');
|
|
}
|
|
}
|
|
}
|
|
|
|
private function isUniqueConstraint(QueryException $e): bool
|
|
{
|
|
$sqlState = $e->getCode() ?: ($e->errorInfo[0] ?? null);
|
|
|
|
return $sqlState === '23505';
|
|
}
|
|
}
|