275 lines
13 KiB
Plaintext
275 lines
13 KiB
Plaintext
---
|
||
description:
|
||
globs:
|
||
alwaysApply: false
|
||
---
|
||
一、统一前置约定
|
||
1. Base URL
|
||
AppConfig.apiBaseUrl 内部已拼接 “/api/v1/” 前缀,所有请求 path 必须以 “/” 开头,禁止再次写 /api/v1/**。
|
||
示例:`post('/ai-chat/sessions/create')` ✅ `post('/api/v1/ai-chat/sessions/create')` ❌
|
||
|
||
2. HTTP 方法
|
||
• 除个别 GET-SSE(小说导入进度)外,所有接口一律使用 POST。
|
||
• 请求体、响应体统一 JSON;SSE 数据位于 data 字段内,亦是 JSON。
|
||
|
||
3. 必备请求头
|
||
Authorization: Bearer {token}
|
||
X-User-Id: {userId}
|
||
Content-Type: application/json (Multipart 上传除外)
|
||
SSE 额外头:
|
||
Accept: text/event-stream
|
||
Cache-Control: no-cache
|
||
Connection: keep-alive
|
||
|
||
4. novelId 隔离
|
||
所有 AI 聊天 / 记忆模式 / 剧情推演及小说相关接口均需 novelId。前端省略将直接 4XX。旧版不带 novelId 方法已 @Deprecated。
|
||
|
||
────────────────────────────────────────
|
||
二、非流式(普通 HTTP)调用规范
|
||
1. 调用方式
|
||
await apiClient.post('/路径', data: {…});
|
||
Options 可选;大部分接口已经在 ApiClient 二次封装,优先调用对应 repository 方法。
|
||
|
||
2. 超时
|
||
connect 30s / send 30s / receive 5min(已在 ApiClient 配置)
|
||
|
||
3. 错误处理
|
||
后端统一错误 JSON:`{ "code": -1, "message": "错误描述", "error": "可选详情" }`
|
||
ApiClient 已将 401 拦截并自动调用 AuthService.logout()。其它错误统一抛 ApiException(statusCode,msg)。
|
||
|
||
────────────────────────────────────────
|
||
三、SSE 流式请求规范
|
||
1. 前端封装
|
||
使用 SseClient().streamEvents<T>(…) 或 ApiClient.postStream + _processStream。
|
||
|
||
2. 通用请求头与 Options
|
||
见「统一请求头」,并在 Dio Options 中声明 responseType: ResponseType.stream。
|
||
|
||
3. 事件格式(后端 ServerSentEvent)
|
||
id: {uuid} ❘ 可选
|
||
event: {eventName}
|
||
data: {JSON 字符串或 [DONE]}
|
||
\n\n 分隔多条记录
|
||
|
||
4. 事件名称白名单
|
||
chat-message AI 聊天普通/流式块
|
||
chat-error AI 聊天错误
|
||
chat-message-memory 记忆模式聊天
|
||
outline-chunk 剧情推演
|
||
message 通用 AI
|
||
complete 通用 AI 结束(data 为 {"data":"[DONE]"})
|
||
|
||
5. 结束判定
|
||
• 收到一条 data: [DONE] → 正常结束
|
||
• 收到 event: complete → 正常结束
|
||
• 服务器主动关闭连接 → onDone
|
||
• 本地 5 分钟未收包 → ApiClient 内置心跳超时会自动 addError & close
|
||
|
||
6. 前端过滤范式(示例 chat_repository_impl)
|
||
.streamEvents(...).where((event)=>
|
||
event.sessionId == currentSession && event.content != 'heartbeat');
|
||
|
||
────────────────────────────────────────
|
||
四、路径与 DTO 命名
|
||
1. 路径
|
||
POST /{模块}/{动作} 例:/novels/create
|
||
POST /{模块}/{资源}/{动作} 例:/ai-chat/messages/stream
|
||
SSE 路径保持同 POST 规则,仅响应类型不同。
|
||
|
||
2. 请求 DTO/VO
|
||
• {Action}{Resource}Dto / Request / Response
|
||
• SessionCreateDto, ImportPreviewRequest …
|
||
• 流式请求的 DTO 放 body,不走 query。
|
||
|
||
────────────────────────────────────────
|
||
五、文件上传 & 导入
|
||
1. Multipart FormData 字段
|
||
file 文件
|
||
userId (备用)
|
||
2. 三步导入
|
||
/upload-preview → 返回 previewSessionId
|
||
/preview → 返回章节解析预览
|
||
/confirm → 返回 jobId
|
||
进度监听 GET /novels/import/{jobId}/status SSE event: data = ImportStatus JSON
|
||
3. 长连接心跳
|
||
ApiClient.connectToLongRunningSSE 已内建 15s 心跳日志;2min 静默 → 本地进度提醒;5min → 超时断线。
|
||
|
||
────────────────────────────────────────
|
||
六、特殊模块注意
|
||
1. AI 聊天
|
||
• 创建会话 /ai-chat/sessions/create
|
||
• 流式发消息 /ai-chat/messages/stream Body 必含 userId、novelId、sessionId、content
|
||
• metadata 可携带 aiConfig(详见 extractAIConfigFromMetadata)
|
||
• 事件过滤:status==streaming 可用于打字机效果,最终完整消息 id 不以 temp_chunk_ 开头。
|
||
|
||
2. 记忆模式
|
||
• 路径加 -with-memory;event 名改为 chat-message-memory / chat-error-memory。
|
||
• 需要 memoryConfig 字段。
|
||
|
||
3. 剧情推演
|
||
• POST /novels/{novelId}/next-outlines/generate-stream
|
||
• event: outline-chunk
|
||
• 如遇 code+message 错误 JSON,解析器需 throw ApiException。
|
||
|
||
4. Universal AI(多功能 AI)
|
||
• 非流式 /ai/universal/request
|
||
• 流式 /ai/universal/stream event: message / complete
|
||
• 预估费用 /ai/universal/estimate-cost
|
||
• 预览提示 /ai/universal/preview
|
||
|
||
────────────────────────────────────────
|
||
七、客户端实现要点(Dart 侧)
|
||
1. ApiClient 已封装常用 CRUD;优先通过各 Repository,避免重复实现。
|
||
2. 401 在拦截器中自动登出,无需额外判断。
|
||
3. 日志等级:AppConfig.logLevel;生产默认 info,调试可设 warning 输出请求/响应体。
|
||
4. 模型 & 配置缓存
|
||
ChatRepositoryImpl 内部维护 novelId→sessionId→UniversalAIRequest 缓存,注意同步 & 清理。
|
||
5. 批量场景上传
|
||
统一使用 /novels/upsert-chapter-scenes-batch,数据结构符合 ChapterScenesDto。
|
||
|
||
────────────────────────────────────────
|
||
八、测试 Checklist(提交前自检)
|
||
☐ 请求 path 无 “/api/v1” 重复
|
||
☐ 必填头 Authorization / X-User-Id 已附加
|
||
☐ POST body 为 JSON;SSE 请求 Accept:text/event-stream
|
||
☐ novelId 已在 body 或 path 中提供
|
||
☐ 错误码 & message 正确解析,401 能触发登出
|
||
☐ SSE 解析:支持 event/data/id,多行合并,识别 [DONE]/complete
|
||
☐ 心跳或空行已过滤,打字机流块保留
|
||
☐ 长连接超时重连(最多 3 次)逻辑正常
|
||
☐ 文件上传 Multipart/form-data,字段 file / userId
|
||
☐ 日志在 debug 模式下可输出请求体 & 响应体
|
||
|
||
|
||
|
||
|
||
|
||
后端约束
|
||
|
||
一、通用约束
|
||
|
||
1. 路径前缀
|
||
所有 Controller 均挂载在 “/api/v1/**”,切勿在内部拼接重复前缀。
|
||
|
||
2. 返回类型
|
||
• 非流式:Mono<T> / Flux<T> ↔ HTTP 200|201。
|
||
• 流式 :Flux<ServerSentEvent<…>> ↔ Content-Type text/event-stream。
|
||
|
||
3. DTO & 命名
|
||
• 输入 DTO:SessionCreateDto / ImportPreviewRequest / PaginatedScenesRequestDto …
|
||
• 输出 DTO:NovelWithScenesDto / ChaptersForPreloadDto …
|
||
• 统一放在 web.dto 包;禁止 Controller 直接暴露实体 Entity。
|
||
|
||
4. 参数校验
|
||
• 使用 jakarta.validation @Valid,并在 DTO 字段加 @NotBlank @NotNull。
|
||
• Controller 若自行拼 Map<String,String>,在进入 Service 前必须手动判空并抛 ResponseStatusException 400。
|
||
|
||
5. 身份认证
|
||
• 使用 @CurrentUser 解析出 userId;如为空必须回退表单 userId;仍为空则返回 401。
|
||
• 鉴权逻辑统一在 Service 层做二次校验(session 属主、novel 属主等)。
|
||
• jwt配置 SecurityConfig需要增加新端点
|
||
|
||
6. novelId 隔离
|
||
• AIChatService / NovelService 等新接口必须带 novelId 版本,旧方法保留 @Deprecated 标记。
|
||
• Controller 在调用旧 Service 时,先 getSession(userId, novelId, sessionId) 校验归属。
|
||
|
||
7. 错误响应规范
|
||
```json
|
||
{ "code": -1, "message": "错误描述", "error": "可选堆栈/详情" }
|
||
```
|
||
• Controller 捕获异常后统一封装,避免直接返回 500 HTML。
|
||
• SSE 端点 onErrorResume 时应推送 chat-error / outline-error 之类事件,或者 data: {"code":-1,"message":"xxx"}。
|
||
|
||
────────────────────────────────────────
|
||
二、SSE 端点实现要求
|
||
|
||
1. 标准包装
|
||
```java
|
||
ServerSentEvent.<T>builder()
|
||
.id(UUID.randomUUID().toString())
|
||
.event("chat-message") // 见事件白名单
|
||
.data(payload) // payload 为对象,框架自动 JSON 序列化
|
||
.retry(Duration.ofSeconds(10))
|
||
.build();
|
||
```
|
||
|
||
2. 事件名称
|
||
chat-message | chat-error | chat-message-memory | outline-chunk | message | complete
|
||
其它请先在前后端约定后再扩展。
|
||
|
||
3. 结束规则
|
||
• 业务方最后 concat 一个 complete / data:[DONE] 信号,或正常 close。
|
||
• 服务器不得无限保持空闲长连接;无事件 2-3min 应考虑心跳注释 “:heartbeat”。
|
||
|
||
4. 流速控制
|
||
使用 `.delayElements(Duration.ofMillis(50))` 或下游 buffer,避免单秒百条刷屏。
|
||
|
||
5. 订阅日志
|
||
`doOnSubscribe` 记录连接;`doOnCancel` / `doOnError` 记录关闭与异常,方便排障。
|
||
|
||
────────────────────────────────────────
|
||
三、模块专项
|
||
|
||
1. AI 聊天 (AIChatController)
|
||
• createSession / getSession / listSessions / update / delete / count 全量支持 novelId。
|
||
• streamMessage() 必须先 extractAIConfigFromMetadata() → UniversalAIRequestDto;如无配置降级旧接口。
|
||
• 错误时返回 chat-error 事件,data 为 AIChatMessage(role=system, status=ERROR)。
|
||
|
||
2. 记忆模式
|
||
• routes 加后缀 -with-memory;事件名 chat-message-memory / chat-error-memory。
|
||
• ChatMemoryConfigDto 转 domain,Service 侧负责窗口剪裁。
|
||
|
||
3. Novel 管理 (NovelController)
|
||
• get-with-paginated-scenes 等分页接口必须校验 chaptersLimit 1-10。
|
||
• load-more-scenes direction 仅允许 up/down/center;非法值 400。
|
||
• 细粒度增删改(add-act-fine / delete-scene-fine 等)只处理局部,无需回整本 DTO,前端自动拉取最新结构。
|
||
• /import 流程:upload-preview → preview → confirm;每步严格校验必要字段并返回友好错误。
|
||
|
||
4. Next-Outline (剧情推演)
|
||
• generate-stream / regenerate-option 均推 outline-chunk。
|
||
• Service 内部 chunk.size 建议 ≤ 5KB;过大前端解析慢。
|
||
|
||
5. Universal AI
|
||
• /stream 结尾必须 concat 完成事件 `event:complete data:{"data":"[DONE]"}`。
|
||
• /estimate-cost 返回 {success, estimatedCost, errorMessage},不可抛异常给前端。
|
||
|
||
────────────────────────────────────────
|
||
四、性能 & 稳定性
|
||
|
||
1. I/O 超时
|
||
• WebClient/DQL 调 OpenAI 等第三方应限 2min;大任务另行异步处理并用 SSE 推进度。
|
||
|
||
2. 压力保护
|
||
• 单 userId 并发流连接 ≤ 10;可在 Service 层做计数。
|
||
• 若超额返回 429 JSON 并在 SSE 推送 error 事件。
|
||
|
||
3. 日志
|
||
• slf4j 级别:info 记录业务流程 & 关键ID;debug 打开 JSON 细节;error 打印堆栈。
|
||
• 不得在生产输出完整 prompt / apiKey。
|
||
|
||
────────────────────────────────────────
|
||
五、代码质量守则
|
||
|
||
1. Controller 只做参数检查 + 日志 + 调 Service;禁止业务逻辑堆叠。
|
||
2. Service 返回 Mono.error 时务必带语义化 message,前端直接展示。
|
||
3. DTO 层禁止 Lombok @Data;使用 @Getter/@Setter 或 record,避免 JSON 循环引用。
|
||
4. 所有 Mono/Flux 链路结尾必须 `onErrorResume` 友好处理,不能把 Reactor 异常原样抛给客户。
|
||
5. 不得在 SSE 控制器里使用 `share()` 导致多次订阅;一个请求一个冷流或 Service 级共享 hot 流。
|
||
|
||
────────────────────────────────────────
|
||
六、提交前检查清单(后端)
|
||
☐ 路径不含重复 /api/v1
|
||
☐ DTO 字段 @NotBlank 检验通过,全局异常处理返回统一结构
|
||
☐ novelId 校验正确,跨用户/跨小说数据隔离
|
||
☐ SSE 事件名符合白名单,结尾发送 complete 或 [DONE]
|
||
☐ 日志不输出敏感信息(apiKey, prompt)
|
||
☐ 新增接口在 Controller + Service + DTO 均写单元测试
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|