-
Notifications
You must be signed in to change notification settings - Fork 301
perf(backend,frontend): 优化核心性能,添加缓存,调整项目配置 #164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e07a51f
758b836
12201ba
fc1d8e2
512e785
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,5 @@ | ||||||||||||||||||||||||||
| """Novel 应用服务""" | ||||||||||||||||||||||||||
| import time as _time | ||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||
| from datetime import datetime, timezone | ||||||||||||||||||||||||||
| from typing import List, Optional, Dict, Any | ||||||||||||||||||||||||||
|
|
@@ -143,6 +144,7 @@ def create_novel( | |||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| self.novel_repository.save(novel) | ||||||||||||||||||||||||||
| self._invalidate_novels_cache() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return NovelDTO.from_domain(novel) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -186,19 +188,56 @@ def _check_has_outline(self, novel_id: str) -> bool: | |||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # ── 小说列表 TTL 缓存(模块级,跨请求共享)── | ||||||||||||||||||||||||||
| _NOVELS_LIST_CACHE_TTL = 10.0 # 秒 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| _novels_list_cache: Dict[str, Dict[str, Any]] = {} | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||||||
| def _invalidate_novels_cache() -> None: | ||||||||||||||||||||||||||
| """创建/更新/删除小说时清除列表缓存。""" | ||||||||||||||||||||||||||
| NovelService._novels_list_cache.pop("_all_novels_dtos", None) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def list_novels(self) -> List[NovelDTO]: | ||||||||||||||||||||||||||
| """列出所有小说 | ||||||||||||||||||||||||||
| """列出所有小说(批量查询 + TTL 缓存)。 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 优化:使用 LEFT JOIN 一次性加载 novels + chapters + bible + outline, | ||||||||||||||||||||||||||
| 查询数从 1+3N 降至 3 次。模块级 TTL 缓存避免重复查询。 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||
| NovelDTO 列表 | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| novels = self.novel_repository.list_all() | ||||||||||||||||||||||||||
| cache_key = "_all_novels_dtos" | ||||||||||||||||||||||||||
| cached = self._novels_list_cache.get(cache_key) | ||||||||||||||||||||||||||
| if cached is not None and _time.time() - cached["ts"] < self._NOVELS_LIST_CACHE_TTL: | ||||||||||||||||||||||||||
| return cached["data"] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # 尝试批量优化查询 | ||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||
| from infrastructure.persistence.database.query_optimizations import ( | ||||||||||||||||||||||||||
| list_all_novels_optimized, | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| db = getattr(self.novel_repository, "db", None) | ||||||||||||||||||||||||||
| if db is not None: | ||||||||||||||||||||||||||
| novels = list_all_novels_optimized(db) | ||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| novels = self.novel_repository.list_all() | ||||||||||||||||||||||||||
| for novel in novels: | ||||||||||||||||||||||||||
| self._hydrate_chapters(novel) | ||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||
| novels = self.novel_repository.list_all() | ||||||||||||||||||||||||||
| for novel in novels: | ||||||||||||||||||||||||||
| self._hydrate_chapters(novel) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| dtos = [] | ||||||||||||||||||||||||||
| for novel in novels: | ||||||||||||||||||||||||||
| dto = NovelDTO.from_domain(self._hydrate_chapters(novel)) | ||||||||||||||||||||||||||
| dto.has_bible = self._check_has_bible(novel.novel_id.value) | ||||||||||||||||||||||||||
| dto.has_outline = self._check_has_outline(novel.novel_id.value) | ||||||||||||||||||||||||||
| dto = NovelDTO.from_domain(novel) | ||||||||||||||||||||||||||
| # 批量查询已预计算 has_bible / has_outline | ||||||||||||||||||||||||||
| dto.has_bible = getattr(novel, "_has_bible", self._check_has_bible(novel.novel_id.value)) | ||||||||||||||||||||||||||
| dto.has_outline = getattr(novel, "_has_outline", self._check_has_outline(novel.novel_id.value)) | ||||||||||||||||||||||||||
|
Comment on lines
+236
to
+237
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid eager fallback evaluation in At Line 235 and Line 236, Proposed fix- dto.has_bible = getattr(novel, "_has_bible", self._check_has_bible(novel.novel_id.value))
- dto.has_outline = getattr(novel, "_has_outline", self._check_has_outline(novel.novel_id.value))
+ dto.has_bible = (
+ novel._has_bible
+ if hasattr(novel, "_has_bible")
+ else self._check_has_bible(novel.novel_id.value)
+ )
+ dto.has_outline = (
+ novel._has_outline
+ if hasattr(novel, "_has_outline")
+ else self._check_has_outline(novel.novel_id.value)
+ )📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| dtos.append(dto) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| self._novels_list_cache[cache_key] = {"data": dtos, "ts": _time.time()} | ||||||||||||||||||||||||||
| return dtos | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def delete_novel(self, novel_id: str) -> None: | ||||||||||||||||||||||||||
|
|
@@ -208,6 +247,7 @@ def delete_novel(self, novel_id: str) -> None: | |||||||||||||||||||||||||
| novel_id: 小说 ID | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| self.novel_repository.delete(NovelId(novel_id)) | ||||||||||||||||||||||||||
| self._invalidate_novels_cache() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def add_chapter( | ||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||
|
|
@@ -260,6 +300,7 @@ def add_chapter( | |||||||||||||||||||||||||
| if not any(getattr(c, "number", None) == chapter.number for c in novel.chapters): | ||||||||||||||||||||||||||
| novel.chapters.append(chapter) | ||||||||||||||||||||||||||
| self.novel_repository.save(novel) | ||||||||||||||||||||||||||
| self._invalidate_novels_cache() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # 同步创建 StoryNode 章节节点,并关联到当前活跃的幕 | ||||||||||||||||||||||||||
| if self.story_node_repository: | ||||||||||||||||||||||||||
|
|
@@ -358,6 +399,8 @@ def update_novel( | |||||||||||||||||||||||||
| novel.generation_prefs, generation_prefs | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| self.novel_repository.save(novel) | ||||||||||||||||||||||||||
| self._invalidate_novels_cache() | ||||||||||||||||||||||||||
| # 增量 patch:避免全量 save 把 autopilot_status 等未改字段写回 stopped | ||||||||||||||||||||||||||
|
Comment on lines
+402
to
404
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove redundant full save before patch to avoid unintended field overwrite. Line 402 performs Proposed fix- self.novel_repository.save(novel)
- self._invalidate_novels_cache()
# 增量 patch:避免全量 save 把 autopilot_status 等未改字段写回 stopped
patch_fields: Dict[str, Any] = {}
...
if patch_fields:
self.novel_repository.patch(NovelId(novel_id), **patch_fields)
+ self._invalidate_novels_cache()Also applies to: 420-421 🧰 Tools🪛 Ruff (0.15.13)[warning] 404-404: Comment contains ambiguous (RUF003) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| patch_fields: Dict[str, Any] = {} | ||||||||||||||||||||||||||
| if title is not None: | ||||||||||||||||||||||||||
|
|
@@ -398,6 +441,7 @@ def update_novel_stage(self, novel_id: str, stage: str) -> NovelDTO: | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| novel.stage = NovelStage(stage) | ||||||||||||||||||||||||||
| self.novel_repository.save(novel) | ||||||||||||||||||||||||||
| self._invalidate_novels_cache() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return NovelDTO.from_domain(self._hydrate_chapters(novel)) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -420,6 +464,7 @@ def update_auto_approve_mode(self, novel_id: str, auto_approve_mode: bool) -> No | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| novel.auto_approve_mode = auto_approve_mode | ||||||||||||||||||||||||||
| self.novel_repository.save(novel) | ||||||||||||||||||||||||||
| self._invalidate_novels_cache() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return NovelDTO.from_domain(self._hydrate_chapters(novel)) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not return/store mutable cached DTO objects by reference.
Line 211-Line 213 and Line 239 currently share the same mutable list/DTO instances across requests. Any in-place mutation by one caller can pollute later responses.
Proposed fix
Also applies to: 239-239
🤖 Prompt for AI Agents