--- 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(…) 或 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 / Flux ↔ HTTP 200|201。 • 流式 :Flux> ↔ 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,在进入 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.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 均写单元测试