Sakura AI 仓库扫描报告
扫描概览
| 指标 |
值 |
| 仓库 |
Giggitycountless/LOL-Desktop-Assistant |
| 扫描时间 |
2026-05-20 15:47:36 |
| Commit |
167134c |
| 扫描文件数 |
2445 |
| 🟡 健康评分 |
78/100 |
问题统计
| 严重性 |
数量 |
| 🔴 Critical |
0 |
| 🟡 Major |
7 |
| 🟠 Minor |
6 |
| 💡 Suggestion |
3 |
🟡 MAJOR
1. summoners_by_names 中 N+1 顺序 HTTP 调用
- 文件:
crates/adapters/src/lib.rs:941-984
- 类别: performance
- 置信度: 95%
- 描述: 在
summoners_by_names 中,对每个召唤师名称顺序发起独立的 LCU HTTP 请求(最多尝试 2 个端点),当名称列表较长时延迟将线性增长。与之对比,summoners_by_ids 会先尝试批量接口。
- 建议: 与 summoners_by_ids 类似,优先尝试批量接口(如 /lol-summoner/v2/summoners?names=...),仅在批量失败时回退到逐个查询。也可使用 rayon 并行化逐个查询的回退路径。
2. adapters/src/lib.rs 超过 3400 行,职责过于集中
- 文件:
crates/adapters/src/lib.rs:1-3419
- 类别: maintainability
- 置信度: 95%
- 描述:
crates/adapters/src/lib.rs 达到 3419 行,涵盖 LCU 适配器、会话管理、数据映射、游戏流程解析、URL 构造、工具函数等多种职责。项目记忆中也多次指出此问题。这使得代码导航、review 和测试都变得困难。
- 建议: 按功能拆分为独立模块:例如
lcu_client.rs(主适配器)、match_mapping.rs(比赛数据映射)、overlay.rs(实时覆层数据)、url_builder.rs(URL 构造)、champ_select.rs(选人阶段处理)。现有的 community_dragon.rs 和 tencent_lol_api.rs 是良好的拆分范例。
3. platform/src/lib.rs 超过 3299 行,混合命令处理与事件循环
- 文件:
crates/platform/src/lib.rs:1-3299
- 类别: maintainability
- 置信度: 95%
- 描述:
crates/platform/src/lib.rs 达到 3299 行,将 Tauri 命令处理、WebSocket 事件循环、自动接受逻辑、选人缓存管理、覆盖层窗口管理等多种关注点混合在一个文件中。
- 建议: 拆分为:
commands.rs(所有 Tauri 命令处理)、league_events.rs(WebSocket 事件循环和自动接受)、champ_select_cache.rs(选人缓存与 hydration)、overlay.rs(覆盖层窗口管理)。AppState 定义可保留在 lib.rs 中。
4. enrich_live_overlay_players 中每个玩家 3 次串行 HTTP 请求
- 文件:
crates/adapters/src/lib.rs:1296-1339
- 类别: performance
- 置信度: 90%
- 描述: 在
enrich_live_overlay_players 中,虽然外层使用 par_iter_mut 并行处理不同玩家,但每个玩家内部仍需顺序获取 items、scores、summoner_spells 三类数据。10 名玩家 × 3 请求 = 30 个请求,每个玩家内部串行延迟约 3×3s=9s 最坏情况。
- 建议: 将每个玩家的 3 个请求也改为并行(例如使用
tokio::join! 或 rayon 嵌套并行),或在 par_iter_mut 内部使用 futures::join! 异步并发获取三类数据。
5. 前端 Advisor.tsx 和 formatting.ts 存在多处未国际化的硬编码字符串
- 文件:
src/pages/Advisor.tsx:7-153
- 类别: maintainability
- 置信度: 90%
- 描述: Advisor.tsx 中存在大量硬编码英文字符串未使用
t() 函数:"Late build"(行147)、"Situational"(行148)、"Lane"(行152)、"Teamfights"(行153)、"Score"(行133)。formatLeaguePhase 函数(formatting.ts:56-79)中 "Partial data"、"Unauthorized"、"Not logged in" 等也直接硬编码。laneLabel 函数(行221-234)同样返回硬编码字符串。
- 建议: 将所有用户可见字符串添加到 i18n 翻译表中,使用
t() 函数引用。特别是 lanes 数组(行7-13)、laneLabel(行221-234)、AdviceBlock 标题、InfoBlock 标签等。formatLeaguePhase 中的字符串也应国际化。
6. SQLite 每次操作新建连接,无连接池
- 文件:
crates/storage/src/lib.rs:33-209
- 类别: performance
- 置信度: 85%
- 描述: SqliteStore 的每个方法(get_settings、list_activity_entries 等)都调用
Connection::open(&self.database_path) 创建新连接。在高频轮询场景下(如 champ_select 刷新),频繁开关连接会带来不必要的 I/O 开销和锁竞争。
- 建议: 引入连接池(如
r2d2 或 deadpool-sqlite),或在 SqliteStore 中持有单一的长期 Connection(SQLite 单写者模式下足够),避免重复打开/关闭。同时也减少 configure_connection 的重复调用。
7. advisor_data_snapshots 和 ranked_champion_snapshots 的 payload_json 缺少 version 字段
- 文件:
crates/storage/migrations/0007_advisor_data_cache.sql:1-20
- 类别: reliability
- 置信度: 85%
- 描述: 数据库迁移 0004 和 0007 中创建的
payload_json 列存储了序列化的 JSON 数据,但未包含 version 或 schema_version 字段。如果未来数据结构变更,无法区分不同版本的存储格式,必须全量迁移。
- 建议: 在存储 JSON 时始终附带
format_version 字段(类似于 LocalDataExport.format_version 的做法)。读取时检查版本号,按版本分支解析。虽然远程数据已有 RANKED_CHAMPION_FORMAT_VERSION,但本地存储层也应记录。
🟠 MINOR
1. 多处静默失败未记录日志(if let Ok(_) 模式)
- 文件:
crates/adapters/src/lib.rs:292-309
- 类别: reliability
- 置信度: 90%
- 描述: 在
resolve_champion_alias(行292-306)、read_game_asset(行322-323)、read_completed_match(行374-377)等处,HTTP 请求失败被 .ok() 静默吞掉,没有日志记录。当数据异常时难以排查根因。项目记忆中也多次标记此模式。
- 建议: 对所有静默忽略错误的位置添加
log::warn! 或 tracing::debug! 日志,至少记录请求路径和失败原因。例如:if let Ok(summaries) = session.get_json(...) { ... } else { log::warn!("champion summary fetch failed for alias resolution"); }
2. lock_or_recover 函数在多个模块中重复定义
- 文件:
crates/adapters/src/lib.rs:111-113
- 类别: maintainability
- 置信度: 85%
- 描述:
lock_or_recover 函数在 crates/adapters/src/lib.rs(行111-113)和 crates/platform/src/lib.rs(行162-170)中各自独立定义,逻辑完全相同(恢复 poisoned mutex)。属于代码重复。
- 建议: 提取到
application 或 domain 层作为公共工具函数,或创建一个 shared 工具模块,避免两处维护同一逻辑。
3. champion-summary.json 在多个方法中被重复请求
- 文件:
crates/adapters/src/lib.rs:192-203
- 类别: performance
- 置信度: 80%
- 描述:
champion-summary.json 端点在 read_self_data、read_completed_match、read_participant_recent_stats、read_participant_recent_stats_batch 等多个方法中各自独立请求。虽然结果通过 champion_aliases 缓存了别名映射,但 champion_name_map 结果未被缓存。
- 建议: 将
champion_name_map 结果也纳入 champion_aliases 缓存(或新建专门的 champion_names 缓存),避免在同一个轮询周期内对同一 localhost 端点重复发起请求。
4. 缓存使用 Clone 克隆大对象而非 Arc 共享
- 文件:
crates/adapters/src/community_dragon.rs:134-186
- 类别: performance
- 置信度: 75%
- 描述: CommunityDragon 的
BinChampionData(包含 HashMap<String, BinSpellData>)和 Tencent 的 TencentChampionData(包含 HashMap<String, TencentSpell>)在缓存命中时均通过 .clone() 返回副本。对于包含多个英雄数据的大型缓存,这会导致不必要的内存分配和拷贝。
- 建议: 将缓存值类型改为
Arc<BinChampionData> 和 Arc<TencentChampionData>,缓存命中时返回 Arc::clone(仅增加引用计数),仅在更新缓存时才克隆。读取端改为引用 Arc。
5. live overlay 定时刷新未处理组件卸载后的 setState
- 文件:
src/state/useOverlayEvents.ts:83-94
- 类别: reliability
- 置信度: 75%
- 描述: 在
useOverlayEvents.ts 中,overlay 模式下使用 setInterval 每 5 秒刷新 live overlay 数据。虽然 effect 清理时清除了 interval,但 fetchLiveOverlaySnapshot().then(setLiveOverlaySnapshot) 的 Promise 可能在 interval 被清除后才 resolve,此时组件可能已卸载。
- 建议: 使用
wasCancelled 标志或 AbortController 模式:在 cleanup 函数中设置标志,Promise resolve 后检查标志再决定是否 setState。项目记忆中也标记了此模式。
6. AppState 包含 14 个 Arc 字段,存在潜在锁竞争风险
- 文件:
crates/platform/src/lib.rs:142-160
- 类别: architecture
- 置信度: 70%
- 描述:
AppState 结构体包含 14 个 Arc<Mutex<...>> 字段和多个 Arc<Atomic*> 字段。在高频轮询(champ_select 每 250ms、live overlay 每 5s)下,不同线程可能同时竞争同一锁,特别是 recent_stats_cache 和 champ_select_cache。
- 建议: 评估使用
RwLock 替代读多写少的场景(如缓存查询),或使用 DashMap 替代 Mutex<HashMap> 以减少锁粒度。对于 champ_select_cache 等写入频率低的场景,RwLock 尤其适合。
💡 SUGGESTION
1. 仓库根目录包含测试输出文件 token_audit.txt / token_audit_after.txt
- 文件:
token_audit.txt:1-141
- 类别: maintainability
- 置信度: 95%
- 描述: 仓库根目录存在
token_audit.txt(17KB)和 token_audit_after.txt(13KB)两个文件,内容为测试运行输出。这些属于本地开发产物,不应提交到仓库中。
- 建议: 将这两个文件添加到
.gitignore 并从版本控制中移除。如需保留审计结果作为 CI 产物,应存储在 CI artifacts 中而非仓库内。
2. session 开启模式在多处重复(match open_session 模式)
- 文件:
crates/adapters/src/lib.rs:152-162
- 类别: maintainability
- 置信度: 90%
- 描述: LocalLeagueClient 的几乎所有公开方法都包含相同的 session 开启模式:
let session = match self.open_session() { SessionOpenResult::Ready(s) => s, SessionOpenResult::Status(st) => return Err(read_error_from_status(st)) };。这段代码在 lib.rs 中重复出现约 10 次。
- 建议: 提取为辅助方法,例如
fn require_session(&self) -> Result<LcuSession, LeagueClientReadError>,简化所有公开方法的入口代码。
3. CommunityDragon CDN URL 硬编码 /latest/ 路径无版本协商
- 文件:
crates/adapters/src/community_dragon.rs:8
- 类别: architecture
- 置信度: 80%
- 描述: CommunityDragon 的基础 URL 使用硬编码的
/latest/ 路径(https://raw.communitydragon.org/latest/game/data/characters),没有版本锁定机制。当游戏版本更新时,/latest/ 指向的数据可能突然变化,导致解析失败。
- 建议: 从 LCU 的 game info 中获取当前游戏版本号,将
/latest/ 替换为具体版本号(如 /14.10.1/)。同时保留 /latest/ 作为回退选项,并在 CDN 请求失败时记录详细的版本信息。
此报告由 Sakura AI Reviewer 自动生成
Sakura AI 仓库扫描报告
扫描概览
Giggitycountless/LOL-Desktop-Assistant167134c问题统计
🟡 MAJOR
1. summoners_by_names 中 N+1 顺序 HTTP 调用
crates/adapters/src/lib.rs:941-984summoners_by_names中,对每个召唤师名称顺序发起独立的 LCU HTTP 请求(最多尝试 2 个端点),当名称列表较长时延迟将线性增长。与之对比,summoners_by_ids会先尝试批量接口。2. adapters/src/lib.rs 超过 3400 行,职责过于集中
crates/adapters/src/lib.rs:1-3419crates/adapters/src/lib.rs达到 3419 行,涵盖 LCU 适配器、会话管理、数据映射、游戏流程解析、URL 构造、工具函数等多种职责。项目记忆中也多次指出此问题。这使得代码导航、review 和测试都变得困难。lcu_client.rs(主适配器)、match_mapping.rs(比赛数据映射)、overlay.rs(实时覆层数据)、url_builder.rs(URL 构造)、champ_select.rs(选人阶段处理)。现有的community_dragon.rs和tencent_lol_api.rs是良好的拆分范例。3. platform/src/lib.rs 超过 3299 行,混合命令处理与事件循环
crates/platform/src/lib.rs:1-3299crates/platform/src/lib.rs达到 3299 行,将 Tauri 命令处理、WebSocket 事件循环、自动接受逻辑、选人缓存管理、覆盖层窗口管理等多种关注点混合在一个文件中。commands.rs(所有 Tauri 命令处理)、league_events.rs(WebSocket 事件循环和自动接受)、champ_select_cache.rs(选人缓存与 hydration)、overlay.rs(覆盖层窗口管理)。AppState定义可保留在lib.rs中。4. enrich_live_overlay_players 中每个玩家 3 次串行 HTTP 请求
crates/adapters/src/lib.rs:1296-1339enrich_live_overlay_players中,虽然外层使用par_iter_mut并行处理不同玩家,但每个玩家内部仍需顺序获取 items、scores、summoner_spells 三类数据。10 名玩家 × 3 请求 = 30 个请求,每个玩家内部串行延迟约 3×3s=9s 最坏情况。tokio::join!或rayon嵌套并行),或在par_iter_mut内部使用futures::join!异步并发获取三类数据。5. 前端 Advisor.tsx 和 formatting.ts 存在多处未国际化的硬编码字符串
src/pages/Advisor.tsx:7-153t()函数:"Late build"(行147)、"Situational"(行148)、"Lane"(行152)、"Teamfights"(行153)、"Score"(行133)。formatLeaguePhase函数(formatting.ts:56-79)中 "Partial data"、"Unauthorized"、"Not logged in" 等也直接硬编码。laneLabel函数(行221-234)同样返回硬编码字符串。t()函数引用。特别是lanes数组(行7-13)、laneLabel(行221-234)、AdviceBlock标题、InfoBlock标签等。formatLeaguePhase中的字符串也应国际化。6. SQLite 每次操作新建连接,无连接池
crates/storage/src/lib.rs:33-209Connection::open(&self.database_path)创建新连接。在高频轮询场景下(如 champ_select 刷新),频繁开关连接会带来不必要的 I/O 开销和锁竞争。r2d2或deadpool-sqlite),或在SqliteStore中持有单一的长期Connection(SQLite 单写者模式下足够),避免重复打开/关闭。同时也减少configure_connection的重复调用。7. advisor_data_snapshots 和 ranked_champion_snapshots 的 payload_json 缺少 version 字段
crates/storage/migrations/0007_advisor_data_cache.sql:1-20payload_json列存储了序列化的 JSON 数据,但未包含version或schema_version字段。如果未来数据结构变更,无法区分不同版本的存储格式,必须全量迁移。format_version字段(类似于LocalDataExport.format_version的做法)。读取时检查版本号,按版本分支解析。虽然远程数据已有RANKED_CHAMPION_FORMAT_VERSION,但本地存储层也应记录。🟠 MINOR
1. 多处静默失败未记录日志(if let Ok(_) 模式)
crates/adapters/src/lib.rs:292-309resolve_champion_alias(行292-306)、read_game_asset(行322-323)、read_completed_match(行374-377)等处,HTTP 请求失败被.ok()静默吞掉,没有日志记录。当数据异常时难以排查根因。项目记忆中也多次标记此模式。log::warn!或tracing::debug!日志,至少记录请求路径和失败原因。例如:if let Ok(summaries) = session.get_json(...) { ... } else { log::warn!("champion summary fetch failed for alias resolution"); }2. lock_or_recover 函数在多个模块中重复定义
crates/adapters/src/lib.rs:111-113lock_or_recover函数在crates/adapters/src/lib.rs(行111-113)和crates/platform/src/lib.rs(行162-170)中各自独立定义,逻辑完全相同(恢复 poisoned mutex)。属于代码重复。application或domain层作为公共工具函数,或创建一个shared工具模块,避免两处维护同一逻辑。3. champion-summary.json 在多个方法中被重复请求
crates/adapters/src/lib.rs:192-203champion-summary.json端点在read_self_data、read_completed_match、read_participant_recent_stats、read_participant_recent_stats_batch等多个方法中各自独立请求。虽然结果通过champion_aliases缓存了别名映射,但champion_name_map结果未被缓存。champion_name_map结果也纳入champion_aliases缓存(或新建专门的 champion_names 缓存),避免在同一个轮询周期内对同一 localhost 端点重复发起请求。4. 缓存使用 Clone 克隆大对象而非 Arc 共享
crates/adapters/src/community_dragon.rs:134-186BinChampionData(包含HashMap<String, BinSpellData>)和 Tencent 的TencentChampionData(包含HashMap<String, TencentSpell>)在缓存命中时均通过.clone()返回副本。对于包含多个英雄数据的大型缓存,这会导致不必要的内存分配和拷贝。Arc<BinChampionData>和Arc<TencentChampionData>,缓存命中时返回Arc::clone(仅增加引用计数),仅在更新缓存时才克隆。读取端改为引用 Arc。5. live overlay 定时刷新未处理组件卸载后的 setState
src/state/useOverlayEvents.ts:83-94useOverlayEvents.ts中,overlay 模式下使用setInterval每 5 秒刷新 live overlay 数据。虽然 effect 清理时清除了 interval,但fetchLiveOverlaySnapshot().then(setLiveOverlaySnapshot)的 Promise 可能在 interval 被清除后才 resolve,此时组件可能已卸载。wasCancelled标志或AbortController模式:在 cleanup 函数中设置标志,Promise resolve 后检查标志再决定是否 setState。项目记忆中也标记了此模式。6. AppState 包含 14 个 Arc 字段,存在潜在锁竞争风险
crates/platform/src/lib.rs:142-160AppState结构体包含 14 个Arc<Mutex<...>>字段和多个Arc<Atomic*>字段。在高频轮询(champ_select 每 250ms、live overlay 每 5s)下,不同线程可能同时竞争同一锁,特别是recent_stats_cache和champ_select_cache。RwLock替代读多写少的场景(如缓存查询),或使用DashMap替代Mutex<HashMap>以减少锁粒度。对于champ_select_cache等写入频率低的场景,RwLock尤其适合。💡 SUGGESTION
1. 仓库根目录包含测试输出文件 token_audit.txt / token_audit_after.txt
token_audit.txt:1-141token_audit.txt(17KB)和token_audit_after.txt(13KB)两个文件,内容为测试运行输出。这些属于本地开发产物,不应提交到仓库中。.gitignore并从版本控制中移除。如需保留审计结果作为 CI 产物,应存储在 CI artifacts 中而非仓库内。2. session 开启模式在多处重复(match open_session 模式)
crates/adapters/src/lib.rs:152-162let session = match self.open_session() { SessionOpenResult::Ready(s) => s, SessionOpenResult::Status(st) => return Err(read_error_from_status(st)) };。这段代码在 lib.rs 中重复出现约 10 次。fn require_session(&self) -> Result<LcuSession, LeagueClientReadError>,简化所有公开方法的入口代码。3. CommunityDragon CDN URL 硬编码 /latest/ 路径无版本协商
crates/adapters/src/community_dragon.rs:8/latest/路径(https://raw.communitydragon.org/latest/game/data/characters),没有版本锁定机制。当游戏版本更新时,/latest/指向的数据可能突然变化,导致解析失败。/latest/替换为具体版本号(如/14.10.1/)。同时保留/latest/作为回退选项,并在 CDN 请求失败时记录详细的版本信息。此报告由 Sakura AI Reviewer 自动生成