feat: 添加游戏资源链接管理工具 - #71
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Reviewer's Guide引入一个新的游戏资源链接管理工具,通过 Windows junction 让用户在多份 maimai 安装之间共享 AssetBundleImages/MovieData/SoundData 目录,并包含后端服务、HTTP API、前端弹窗、本地化、文档和测试,且受严格安全规则约束,并支持按会话选择资源源目录和目标目录。 游戏资源 junction 管理流程时序图sequenceDiagram
actor User
participant ResourceJunctionModal
participant ApiClient as Api
participant ResourceJunctionController
participant ResourceJunctionService
User->>ResourceJunctionModal: trigger()
ResourceJunctionModal->>ApiClient: AutoSelectResourceJunctionSource
ApiClient->>ResourceJunctionController: AutoSelectResourceJunctionSource
ResourceJunctionController->>ResourceJunctionService: AutoSelectSource
ResourceJunctionService-->>ResourceJunctionController: ResourceJunctionOverview
ResourceJunctionController-->>ApiClient: ResourceJunctionOverview
ApiClient-->>ResourceJunctionModal: overview
User->>ResourceJunctionModal: click selectSource
ResourceJunctionModal->>ApiClient: SelectResourceJunctionSource
ApiClient->>ResourceJunctionController: SelectResourceJunctionSource
ResourceJunctionController->>ResourceJunctionService: SelectManualSource
ResourceJunctionService-->>ResourceJunctionController: ResourceJunctionOverview
ResourceJunctionController-->>ApiClient: ResourceJunctionOverview
ApiClient-->>ResourceJunctionModal: overview
User->>ResourceJunctionModal: confirm create
ResourceJunctionModal->>ApiClient: CreateResourceJunctions
ApiClient->>ResourceJunctionController: CreateResourceJunctions
ResourceJunctionController->>ResourceJunctionService: CreateLinks
ResourceJunctionService-->>ResourceJunctionController: ResourceJunctionItem[]
ResourceJunctionController-->>ApiClient: ResourceJunctionOverview(items)
ApiClient-->>ResourceJunctionModal: updated overview
User->>ResourceJunctionModal: confirm remove
ResourceJunctionModal->>ApiClient: RemoveResourceJunctions
ApiClient->>ResourceJunctionController: RemoveResourceJunctions
ResourceJunctionController->>ResourceJunctionService: RemoveLinks
ResourceJunctionService-->>ResourceJunctionController: ResourceJunctionItem[]
ResourceJunctionController-->>ApiClient: ResourceJunctionOverview(items)
ApiClient-->>ResourceJunctionModal: updated overview
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard,可以:
Getting HelpOriginal review guide in EnglishReviewer's GuideIntroduce a new game resource link management tool that lets users share AssetBundleImages/MovieData/SoundData directories between maimai installs via Windows junctions, with a backend service, HTTP APIs, a frontend modal, localization, documentation, and tests, all constrained by strict safety rules and per-session source/target selection. Sequence diagram for game resource junction management flowsequenceDiagram
actor User
participant ResourceJunctionModal
participant ApiClient as Api
participant ResourceJunctionController
participant ResourceJunctionService
User->>ResourceJunctionModal: trigger()
ResourceJunctionModal->>ApiClient: AutoSelectResourceJunctionSource
ApiClient->>ResourceJunctionController: AutoSelectResourceJunctionSource
ResourceJunctionController->>ResourceJunctionService: AutoSelectSource
ResourceJunctionService-->>ResourceJunctionController: ResourceJunctionOverview
ResourceJunctionController-->>ApiClient: ResourceJunctionOverview
ApiClient-->>ResourceJunctionModal: overview
User->>ResourceJunctionModal: click selectSource
ResourceJunctionModal->>ApiClient: SelectResourceJunctionSource
ApiClient->>ResourceJunctionController: SelectResourceJunctionSource
ResourceJunctionController->>ResourceJunctionService: SelectManualSource
ResourceJunctionService-->>ResourceJunctionController: ResourceJunctionOverview
ResourceJunctionController-->>ApiClient: ResourceJunctionOverview
ApiClient-->>ResourceJunctionModal: overview
User->>ResourceJunctionModal: confirm create
ResourceJunctionModal->>ApiClient: CreateResourceJunctions
ApiClient->>ResourceJunctionController: CreateResourceJunctions
ResourceJunctionController->>ResourceJunctionService: CreateLinks
ResourceJunctionService-->>ResourceJunctionController: ResourceJunctionItem[]
ResourceJunctionController-->>ApiClient: ResourceJunctionOverview(items)
ApiClient-->>ResourceJunctionModal: updated overview
User->>ResourceJunctionModal: confirm remove
ResourceJunctionModal->>ApiClient: RemoveResourceJunctions
ApiClient->>ResourceJunctionController: RemoveResourceJunctions
ResourceJunctionController->>ResourceJunctionService: RemoveLinks
ResourceJunctionService-->>ResourceJunctionController: ResourceJunctionItem[]
ResourceJunctionController-->>ApiClient: ResourceJunctionOverview(items)
ApiClient-->>ResourceJunctionModal: updated overview
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并给出了一些整体反馈:
- ResourceJunctionController 中的文件夹选择提示(“Select a source game directory or Package directory” / “Select a target game directory or Package directory”)是直接硬编码为英文的;建议把它们移到现有的 i18n/本地化系统中,以便与其他 UI 文本保持一致。
- 使用
mklink的连接创建逻辑在 ResourceJunctionService 和 ResourceJunctionServiceTests 中都有重复;建议将这部分逻辑集中到一个共享的辅助方法中,避免实现出现差异,并让后续调整实现更容易(例如,如果以后不再通过调用cmd来完成)。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- ResourceJunctionController 中的文件夹选择提示(“Select a source game directory or Package directory” / “Select a target game directory or Package directory”)是直接硬编码为英文的;建议把它们移到现有的 i18n/本地化系统中,以便与其他 UI 文本保持一致。
- 使用 `mklink` 的连接创建逻辑在 ResourceJunctionService 和 ResourceJunctionServiceTests 中都有重复;建议将这部分逻辑集中到一个共享的辅助方法中,避免实现出现差异,并让后续调整实现更容易(例如,如果以后不再通过调用 `cmd` 来完成)。
## Individual Comments
### Comment 1
<location path="MaiChartManager/Services/ResourceJunctionService.cs" line_range="384-393" />
<code_context>
+ Directory.Delete(root, true);
+ }
+
+ private static void CreateJunction(string source, string target)
+ {
+ var startInfo = new System.Diagnostics.ProcessStartInfo
+ {
+ FileName = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe",
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ };
+ startInfo.ArgumentList.Add("/d");
+ startInfo.ArgumentList.Add("/c");
+ startInfo.ArgumentList.Add("mklink");
+ startInfo.ArgumentList.Add("/J");
+ startInfo.ArgumentList.Add(target);
+ startInfo.ArgumentList.Add(source);
+ using var process = System.Diagnostics.Process.Start(startInfo)!;
+ process.WaitForExit();
</code_context>
<issue_to_address>
**issue (bug_risk):** 在使用 `cmd.exe mklink` 创建连接时,如果不对路径进行引号包裹,包含空格或特殊字符的路径会导致失败。
由于 `ArgumentList` 传递给 `mklink` 的参数没有加引号,`source` 或 `target` 中的任何空格或特殊字符都会让 `cmd` 错误解析这些参数,进而导致连接创建失败。请在构造命令时确保两个路径都经过适当的引号包裹/转义,或者用直接的 Win32 实现(通过 `CreateFile` + `DeviceIoControl` 调用 `FSCTL_SET_REPARSE_POINT`)或托管辅助方法来替代 `cmd mklink` 调用。至少需要保证在诸如 `Program Files` 和用户配置文件等典型路径位置下,连接创建是健壮可靠的。
</issue_to_address>Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- The folder selection prompts in ResourceJunctionController ("Select a source game directory or Package directory" / "Select a target game directory or Package directory") are hardcoded in English; consider moving them into the existing i18n/localization system so they match the rest of the UI.
- The junction-creation logic using
mklinkis duplicated in both ResourceJunctionService and ResourceJunctionServiceTests; consider centralizing this into a shared helper to avoid divergence and make it easier to adjust the implementation (e.g., if you later switch away from shelling out tocmd).
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The folder selection prompts in ResourceJunctionController ("Select a source game directory or Package directory" / "Select a target game directory or Package directory") are hardcoded in English; consider moving them into the existing i18n/localization system so they match the rest of the UI.
- The junction-creation logic using `mklink` is duplicated in both ResourceJunctionService and ResourceJunctionServiceTests; consider centralizing this into a shared helper to avoid divergence and make it easier to adjust the implementation (e.g., if you later switch away from shelling out to `cmd`).
## Individual Comments
### Comment 1
<location path="MaiChartManager/Services/ResourceJunctionService.cs" line_range="384-393" />
<code_context>
+ Directory.Delete(root, true);
+ }
+
+ private static void CreateJunction(string source, string target)
+ {
+ var startInfo = new System.Diagnostics.ProcessStartInfo
+ {
+ FileName = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe",
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ };
+ startInfo.ArgumentList.Add("/d");
+ startInfo.ArgumentList.Add("/c");
+ startInfo.ArgumentList.Add("mklink");
+ startInfo.ArgumentList.Add("/J");
+ startInfo.ArgumentList.Add(target);
+ startInfo.ArgumentList.Add(source);
+ using var process = System.Diagnostics.Process.Start(startInfo)!;
+ process.WaitForExit();
</code_context>
<issue_to_address>
**issue (bug_risk):** Creating junctions via `cmd.exe mklink` without quoting paths will break for paths with spaces or special characters.
Because `ArgumentList` passes `mklink` arguments without quoting, any spaces or special characters in `source` or `target` will cause `cmd` to parse them incorrectly and the junction creation to fail. Please either ensure both paths are properly quoted/escaped when constructing the command, or replace the `cmd mklink` call with a direct Win32-based implementation (`FSCTL_SET_REPARSE_POINT` via `CreateFile` + `DeviceIoControl`) or a managed helper. At minimum, junction creation must be robust for paths under typical locations like `Program Files` and user profiles.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| private static void CreateJunction(string source, string target) | ||
| { | ||
| var startInfo = new ProcessStartInfo | ||
| { | ||
| FileName = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe", | ||
| UseShellExecute = false, | ||
| CreateNoWindow = true, | ||
| RedirectStandardOutput = true, | ||
| RedirectStandardError = true, | ||
| }; |
There was a problem hiding this comment.
issue (bug_risk): 在使用 cmd.exe mklink 创建连接时,如果不对路径进行引号包裹,包含空格或特殊字符的路径会导致失败。
由于 ArgumentList 传递给 mklink 的参数没有加引号,source 或 target 中的任何空格或特殊字符都会让 cmd 错误解析这些参数,进而导致连接创建失败。请在构造命令时确保两个路径都经过适当的引号包裹/转义,或者用直接的 Win32 实现(通过 CreateFile + DeviceIoControl 调用 FSCTL_SET_REPARSE_POINT)或托管辅助方法来替代 cmd mklink 调用。至少需要保证在诸如 Program Files 和用户配置文件等典型路径位置下,连接创建是健壮可靠的。
Original comment in English
issue (bug_risk): Creating junctions via cmd.exe mklink without quoting paths will break for paths with spaces or special characters.
Because ArgumentList passes mklink arguments without quoting, any spaces or special characters in source or target will cause cmd to parse them incorrectly and the junction creation to fail. Please either ensure both paths are properly quoted/escaped when constructing the command, or replace the cmd mklink call with a direct Win32-based implementation (FSCTL_SET_REPARSE_POINT via CreateFile + DeviceIoControl) or a managed helper. At minimum, junction creation must be robust for paths under typical locations like Program Files and user profiles.
|
不如 PR 正文写一下你的提示词和使用的模型,还有 harness 和装了哪些 skills,这样我审核的更快 |
There was a problem hiding this comment.
Pull request overview
This PR adds a new “Game Resource Links” tool to MaiChartManager to share large maimai resource folders across multiple game installs by creating/removing Windows Junctions, with a front-end modal + backend API/service, safety checks, localization, docs, and unit tests.
Changes:
- Introduce
ResourceJunctionService+ResourceJunctionControllerto inspect/auto-select source/target roots and create/remove Junctions forAssetBundleImages,MovieData,SoundData. - Add a new Tools modal entry in the Vue UI to show selection info, file counts, Junction status, and to run create/remove with confirmation.
- Update generated client types/endpoints, add i18n strings, add user documentation, and add unit tests for selection + Junction behavior.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| MaiChartManager/Services/ResourceJunctionService.cs | Core logic: candidate discovery, file counting, Junction inspection/create/remove, and safety checks. |
| MaiChartManager/ServerManager.cs | Registers ResourceJunctionService in DI. |
| MaiChartManager/Front/src/views/Tools/ResourceJunctionModal.tsx | New modal UI for selecting source/target and managing Junctions. |
| MaiChartManager/Front/src/views/Tools/index.tsx | Adds the new tool entry to the Tools panel and mounts the modal. |
| MaiChartManager/Front/src/locales/zh.yaml | Adds Simplified Chinese strings for the new tool. |
| MaiChartManager/Front/src/locales/zh-TW.yaml | Adds Traditional Chinese strings for the new tool. |
| MaiChartManager/Front/src/locales/en.yaml | Adds English strings for the new tool. |
| MaiChartManager/Front/src/client/apiGen.ts | Updates generated API client types/endpoints (resource junction APIs + store purchase enum change). |
| MaiChartManager/Controllers/Tools/ResourceJunctionController.cs | New HTTP API endpoints for status/auto/manual select/create/remove, including local-action header gating for POSTs. |
| MaiChartManager.Tests/Services/ResourceJunctionServiceTests.cs | Adds unit tests for source selection logic and Junction create/remove safety behavior. |
| MaiChartManager.Tests/MaiChartManager.Tests.csproj | Updates test target framework to Windows TFM. |
| docs/resource-junction-manager.md | User-facing documentation describing selection rules, scope, and safety boundaries. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var directory = new DirectoryInfo(Path.Combine(root, name)); | ||
| if (!directory.Exists || (directory.Attributes & FileAttributes.ReparsePoint) != 0) | ||
| throw new IOException($"{name} is missing or is a reparse point."); | ||
| return new ResourceDirectoryFileCount(name, directory.EnumerateFiles("*", SearchOption.AllDirectories).LongCount()); | ||
| }).ToArray(); |
| export interface RequestPurchaseResult { | ||
| errorMessage?: string | null; | ||
| /** @format int32 */ | ||
| status?: number; | ||
| status?: StorePurchaseStatus; | ||
| } |
| const statusClass = (status?: ResourceJunctionStatus) => { | ||
| if (['Created', 'AlreadyLinked', 'Removed'].includes(status)) return 'text-green-700'; | ||
| if (status === 'Ready') return 'text-blue-700'; | ||
| return 'text-red-700'; | ||
| }; |
There was a problem hiding this comment.
2 issues found across 12 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="MaiChartManager.Tests/MaiChartManager.Tests.csproj">
<violation number="1" location="MaiChartManager.Tests/MaiChartManager.Tests.csproj:3">
P3: Changing the test project to a Windows-only TFM (net10.0-windows10.0.17763.0) makes `dotnet test`/`dotnet build` of this project fail on non-Windows machines/CI unless the Windows targeting pack is available. The main project deliberately keeps a net10.0 Linux configuration, but the test project now has no such escape hatch, so a Linux `dotnet test MaiChartManager.Tests` no longer works. Consider adding `<EnableWindowsTargeting>true</EnableWindowsTargeting>` so the project (and any future Linux CI) can still restore/build the Windows reference assemblies — this only affects build-time downloads and does not change runtime behavior.</violation>
</file>
<file name="MaiChartManager/ServerManager.cs">
<violation number="1" location="MaiChartManager/ServerManager.cs:107">
P2: ResourceJunctionService is registered as a singleton but stores the whole interactive selection session in mutable instance fields (`selectedSourceRoot`, `selectedTargetRoot`, `selectionMode`, ...). Since the local Kestrel services requests on the thread pool, two concurrent requests (a status poll racing a Create/Remove, or the tool opened in two tabs) can interleave mutations of these shared fields mid-operation, so the create/remove/verify sequence can observe another request's selection values. Either keep the per-request selection out of the singleton (pass source/target explicitly to the mutating methods), add synchronization, or register it scoped so each request owns its own state.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| .AddSingleton<MaidataImportService>() | ||
| .AddSingleton<MuModService>() | ||
| .AddSingleton<ModConfigService>() | ||
| .AddSingleton<MaiChartManager.Services.ResourceJunctionService>() |
There was a problem hiding this comment.
P2: ResourceJunctionService is registered as a singleton but stores the whole interactive selection session in mutable instance fields (selectedSourceRoot, selectedTargetRoot, selectionMode, ...). Since the local Kestrel services requests on the thread pool, two concurrent requests (a status poll racing a Create/Remove, or the tool opened in two tabs) can interleave mutations of these shared fields mid-operation, so the create/remove/verify sequence can observe another request's selection values. Either keep the per-request selection out of the singleton (pass source/target explicitly to the mutating methods), add synchronization, or register it scoped so each request owns its own state.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At MaiChartManager/ServerManager.cs, line 107:
<comment>ResourceJunctionService is registered as a singleton but stores the whole interactive selection session in mutable instance fields (`selectedSourceRoot`, `selectedTargetRoot`, `selectionMode`, ...). Since the local Kestrel services requests on the thread pool, two concurrent requests (a status poll racing a Create/Remove, or the tool opened in two tabs) can interleave mutations of these shared fields mid-operation, so the create/remove/verify sequence can observe another request's selection values. Either keep the per-request selection out of the singleton (pass source/target explicitly to the mutating methods), add synchronization, or register it scoped so each request owns its own state.</comment>
<file context>
@@ -104,6 +104,7 @@ public static Task StartApp(bool export, Action<string>? onStart = null, bool se
.AddSingleton<MaidataImportService>()
.AddSingleton<MuModService>()
.AddSingleton<ModConfigService>()
+ .AddSingleton<MaiChartManager.Services.ResourceJunctionService>()
.AddEndpointsApiExplorer()
.AddSwaggerGen(options => { options.CustomSchemaIds(type => type.Name == "Config" ? type.FullName : type.Name); })
</file context>
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <TargetFramework>net10.0-windows10.0.17763.0</TargetFramework> |
There was a problem hiding this comment.
P3: Changing the test project to a Windows-only TFM (net10.0-windows10.0.17763.0) makes dotnet test/dotnet build of this project fail on non-Windows machines/CI unless the Windows targeting pack is available. The main project deliberately keeps a net10.0 Linux configuration, but the test project now has no such escape hatch, so a Linux dotnet test MaiChartManager.Tests no longer works. Consider adding <EnableWindowsTargeting>true</EnableWindowsTargeting> so the project (and any future Linux CI) can still restore/build the Windows reference assemblies — this only affects build-time downloads and does not change runtime behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At MaiChartManager.Tests/MaiChartManager.Tests.csproj, line 3:
<comment>Changing the test project to a Windows-only TFM (net10.0-windows10.0.17763.0) makes `dotnet test`/`dotnet build` of this project fail on non-Windows machines/CI unless the Windows targeting pack is available. The main project deliberately keeps a net10.0 Linux configuration, but the test project now has no such escape hatch, so a Linux `dotnet test MaiChartManager.Tests` no longer works. Consider adding `<EnableWindowsTargeting>true</EnableWindowsTargeting>` so the project (and any future Linux CI) can still restore/build the Windows reference assemblies — this only affects build-time downloads and does not change runtime behavior.</comment>
<file context>
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net10.0</TargetFramework>
+ <TargetFramework>net10.0-windows10.0.17763.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</file context>
| <TargetFramework>net10.0-windows10.0.17763.0</TargetFramework> | |
| <TargetFramework>net10.0-windows10.0.17763.0</TargetFramework> | |
| <EnableWindowsTargeting>true</EnableWindowsTargeting> |
|
已按一次集中提交 已修复:
核实后未改动:
验证:
PR 正文也已补充模型、harness、实际工具、skills 使用情况和用户提示词摘要。 |
There was a problem hiding this comment.
0 issues found across 7 files (changes from recent commits).
Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.
Re-trigger cubic
|
你是什么模型 |
5.6sol捏,思考深度是中 |
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并给出了一些整体性的反馈:
- 目前控制器方法中重复进行了
Export/LocalAction头部检查;可以考虑提取一个小的辅助函数或过滤器来集中处理这些校验,从而减少在各个 ResourceJunction 端点之间的重复代码。 ResourceJunctionController中的文件夹选择提示目前是硬编码的英文字符串;如果能通过现有的本地化系统来传递这些文案,可以与其他 UI 保持一致,避免出现混合语言的对话框。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The controller methods currently repeat the `Export`/`LocalAction` header checks; consider extracting a small helper or filter to centralize this validation and reduce duplication across the ResourceJunction endpoints.
- Folder selection prompts in `ResourceJunctionController` are hard-coded English strings; wiring these through your existing localization system would keep them consistent with the rest of the UI and avoid mixed-language dialogs.
## Individual Comments
### Comment 1
<location path="MaiChartManager/Front/src/views/Tools/ResourceJunctionModal.tsx" line_range="22-31" />
<code_context>
+ const request = async (action: 'auto' | 'status' | 'manual' | 'manualTarget' | 'create' | 'remove') => {
</code_context>
<issue_to_address>
**suggestion:** Client-side error handling discards specific server error messages, which could be surfaced to improve UX.
The API returns BadRequest with a plain string for validation issues (e.g., invalid source/target), but Axios errors are always mapped to a generic `requestFailed` toast. Please read the response payload (e.g., `error.response?.data`) and include that message in the toast when available, so users see the specific validation error instead of a generic failure.
Suggested implementation:
```typescript
} catch (error) {
let detail: string | undefined;
// Try to surface server-side validation / error messages when available
if (axios.isAxiosError(error)) {
const payload = error.response?.data;
if (typeof payload === 'string') {
// API returns plain string for validation issues
detail = payload;
} else if (payload && typeof payload === 'object') {
// Fallback for common structured error shapes
const { message, error: errorMessage } = payload as { message?: string; error?: string };
detail = message ?? errorMessage;
}
}
toast.add({
severity: 'error',
summary: t('tools.resourceJunction.requestFailed'),
detail,
life: 5000,
});
} finally {
loading.value = false;
}
```
1. Ensure `axios` is imported at the top of this file, for example:
`import axios from 'axios';`
2. If your toast implementation uses a different property than `detail` for the message body, adjust the `toast.add` call to match the existing convention (e.g., `message` or `description`).
</issue_to_address>Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- The controller methods currently repeat the
Export/LocalActionheader checks; consider extracting a small helper or filter to centralize this validation and reduce duplication across the ResourceJunction endpoints. - Folder selection prompts in
ResourceJunctionControllerare hard-coded English strings; wiring these through your existing localization system would keep them consistent with the rest of the UI and avoid mixed-language dialogs.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The controller methods currently repeat the `Export`/`LocalAction` header checks; consider extracting a small helper or filter to centralize this validation and reduce duplication across the ResourceJunction endpoints.
- Folder selection prompts in `ResourceJunctionController` are hard-coded English strings; wiring these through your existing localization system would keep them consistent with the rest of the UI and avoid mixed-language dialogs.
## Individual Comments
### Comment 1
<location path="MaiChartManager/Front/src/views/Tools/ResourceJunctionModal.tsx" line_range="22-31" />
<code_context>
+ const request = async (action: 'auto' | 'status' | 'manual' | 'manualTarget' | 'create' | 'remove') => {
</code_context>
<issue_to_address>
**suggestion:** Client-side error handling discards specific server error messages, which could be surfaced to improve UX.
The API returns BadRequest with a plain string for validation issues (e.g., invalid source/target), but Axios errors are always mapped to a generic `requestFailed` toast. Please read the response payload (e.g., `error.response?.data`) and include that message in the toast when available, so users see the specific validation error instead of a generic failure.
Suggested implementation:
```typescript
} catch (error) {
let detail: string | undefined;
// Try to surface server-side validation / error messages when available
if (axios.isAxiosError(error)) {
const payload = error.response?.data;
if (typeof payload === 'string') {
// API returns plain string for validation issues
detail = payload;
} else if (payload && typeof payload === 'object') {
// Fallback for common structured error shapes
const { message, error: errorMessage } = payload as { message?: string; error?: string };
detail = message ?? errorMessage;
}
}
toast.add({
severity: 'error',
summary: t('tools.resourceJunction.requestFailed'),
detail,
life: 5000,
});
} finally {
loading.value = false;
}
```
1. Ensure `axios` is imported at the top of this file, for example:
`import axios from 'axios';`
2. If your toast implementation uses a different property than `detail` for the message body, adjust the `toast.add` call to match the existing convention (e.g., `message` or `description`).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
Addressed the latest Sourcery review in
Validation:
|
There was a problem hiding this comment.
0 issues found across 6 files (changes from recent commits).
Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.
Re-trigger cubic
|
一些建议
|
Codex 审查意见(commit 5f932b1)结论:当前不建议合并,建议修复以下阻塞项后重新审查。 必须修改
功能契约
已确认处理嵌套 reparse point、直接 关于 mklink “空格和 验证审查通道验证了 Debug 单测 18/18、Junction 专项 14/14、LinuxDebug 测试 18/18、LinuxDebug 构建和 — Codex review |
- 根据实际连接来源拒绝非 loopback 的资源链接状态与写操作 - 添加控制器测试,确保远程请求不会触发桌面目录选择
- 在打开并验证的重解析点句柄上执行删除 - 增加 Junction 替换竞态回归测试 - 允许测试程序集访问句柄删除测试入口
- 使用 session id 保存源目录、目标目录和选择结果 - 为每个会话串行化资源链接操作,避免并发请求互相覆盖 - 移除额外本地操作认证头,仅依赖 loopback 来源校验
- 非 Windows 平台统一返回 Unsupported 状态,不打开文件夹选择器 - 提供统一的不支持平台概览并覆盖控制器入口 - 增加不支持平台与概览契约测试
- 打开资源链接设置时先恢复当前会话状态 - 仅在没有手动来源时执行自动来源探测
There was a problem hiding this comment.
5 issues found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="MaiChartManager/Services/ResourceJunctionService.cs">
<violation number="1" location="MaiChartManager/Services/ResourceJunctionService.cs:399">
P2: 空白 `X-MCM-Resource-Junction-Session` 会让资源链接接口返回未处理的 500。空白值应归一化为 `"default"`,或由 Controller 显式转换为 400。</violation>
<violation number="2" location="MaiChartManager/Services/ResourceJunctionService.cs:401">
P2: 会话状态字典 `sessionStates` 只增不减:`GetState` 为每个新的 sessionId 永久写入一条 `SelectionState`,但整个服务里没有任何移除、过期或清理机制。前端每次挂载工具 Modal 都会用 `crypto.randomUUID()` 生成新会话 ID 并随请求头发送,因此用户反复进入/离开工具页后,后端会无限累积这些会话记录,造成长期运行时的内存泄漏。建议为会话提供明确的清理或复用策略:例如让 UI 复用单个固定会话、在 Modal 关闭时调用一个后端清理接口,或对会话做有界缓存的 LRU/过期淘汰。</violation>
<violation number="3" location="MaiChartManager/Services/ResourceJunctionService.cs:500">
P1: 移除流程可能删除实际未指向当前 `source` 的 Junction,破坏“只移除正确目标”的安全边界。重新校验时应解析并比较 `SubstituteName`(或通过可靠的目标解析 API 获取实际目标),不能优先信任 `PrintName`。</violation>
</file>
<file name="MaiChartManager.Tests/Controllers/Tools/ResourceJunctionControllerTests.cs">
<violation number="1" location="MaiChartManager.Tests/Controllers/Tools/ResourceJunctionControllerTests.cs:13">
P2: 测试 `LocalWriteActionDoesNotRequireAnAuthenticationHeader` 缺少 Linux 平台守卫,在 Linux 构建上会失败。控制器 `SelectResourceJunctionSource` 在 `if (!OperatingSystem.IsWindows()) return Ok(CreateUnsupportedOverview())` 之前不会调用文件夹选择器,因此 Linux 上 `PickFolderCalls` 为 0 而非断言的 1。建议按 `LinuxUnsupportedStatusDoesNotOpenFolderPicker` 的方式补上 `if (!OperatingSystem.IsWindows()) return;` 守卫或把该测试限定为仅 Windows 运行。</violation>
</file>
<file name="MaiChartManager/Controllers/Tools/ResourceJunctionController.cs">
<violation number="1" location="MaiChartManager/Controllers/Tools/ResourceJunctionController.cs:97">
P1: 资源链接写操作现在只要求请求来自 loopback,缺少操作意图或 CSRF 门槛;满足自动选源条件时,跨站请求或本机客户端可以绕过前端确认并创建、删除 Junction。建议在 loopback 校验之外要求前端显式发送非简单请求 header(例如当前的 `SessionHeader`),拒绝缺少该 header 的写请求。</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| if (BitConverter.ToUInt32(buffer, 0) != IoReparseTagMountPoint) | ||
| return false; | ||
|
|
||
| var destination = ReadMountPointDestination(buffer); |
There was a problem hiding this comment.
P1: 移除流程可能删除实际未指向当前 source 的 Junction,破坏“只移除正确目标”的安全边界。重新校验时应解析并比较 SubstituteName(或通过可靠的目标解析 API 获取实际目标),不能优先信任 PrintName。
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At MaiChartManager/Services/ResourceJunctionService.cs, line 500:
<comment>移除流程可能删除实际未指向当前 `source` 的 Junction,破坏“只移除正确目标”的安全边界。重新校验时应解析并比较 `SubstituteName`(或通过可靠的目标解析 API 获取实际目标),不能优先信任 `PrintName`。</comment>
<file context>
@@ -428,6 +479,57 @@ private static void CreateJunction(string source, string target)
+ if (BitConverter.ToUInt32(buffer, 0) != IoReparseTagMountPoint)
+ return false;
+
+ var destination = ReadMountPointDestination(buffer);
+ if (destination is null || !SamePath(destination, source))
+ return false;
</file context>
| { | ||
| if (!IsLoopbackRequest()) | ||
| return StatusCode(StatusCodes.Status403Forbidden); | ||
| return null; |
There was a problem hiding this comment.
P1: 资源链接写操作现在只要求请求来自 loopback,缺少操作意图或 CSRF 门槛;满足自动选源条件时,跨站请求或本机客户端可以绕过前端确认并创建、删除 Junction。建议在 loopback 校验之外要求前端显式发送非简单请求 header(例如当前的 SessionHeader),拒绝缺少该 header 的写请求。
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At MaiChartManager/Controllers/Tools/ResourceJunctionController.cs, line 97:
<comment>资源链接写操作现在只要求请求来自 loopback,缺少操作意图或 CSRF 门槛;满足自动选源条件时,跨站请求或本机客户端可以绕过前端确认并创建、删除 Junction。建议在 loopback 校验之外要求前端显式发送非简单请求 header(例如当前的 `SessionHeader`),拒绝缺少该 header 的写请求。</comment>
<file context>
@@ -68,22 +73,33 @@ public ActionResult<ResourceJunctionOverview> SelectResourceJunctionTarget()
- return Request.Headers[LocalActionHeader] != LocalActionValue ? BadRequest() : null;
+ if (!IsLoopbackRequest())
+ return StatusCode(StatusCodes.Status403Forbidden);
+ return null;
}
+
</file context>
| return null; | |
| return string.IsNullOrWhiteSpace(Request.Headers[SessionHeader].FirstOrDefault()) ? BadRequest() : null; |
|
|
||
| private SelectionState GetState(string sessionId) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Session id is required.", nameof(sessionId)); |
There was a problem hiding this comment.
P2: 空白 X-MCM-Resource-Junction-Session 会让资源链接接口返回未处理的 500。空白值应归一化为 "default",或由 Controller 显式转换为 400。
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At MaiChartManager/Services/ResourceJunctionService.cs, line 399:
<comment>空白 `X-MCM-Resource-Junction-Session` 会让资源链接接口返回未处理的 500。空白值应归一化为 `"default"`,或由 Controller 显式转换为 400。</comment>
<file context>
@@ -340,15 +383,23 @@ private static long CountFilesWithoutReparsePoints(DirectoryInfo root)
+
+ private SelectionState GetState(string sessionId)
+ {
+ if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Session id is required.", nameof(sessionId));
+ if (!sessionStates.TryGetValue(sessionId, out var state))
+ sessionStates[sessionId] = state = new SelectionState();
</file context>
| [Collection("Static settings")] | ||
| public sealed class ResourceJunctionControllerTests | ||
| { | ||
| [Fact] |
There was a problem hiding this comment.
P2: 测试 LocalWriteActionDoesNotRequireAnAuthenticationHeader 缺少 Linux 平台守卫,在 Linux 构建上会失败。控制器 SelectResourceJunctionSource 在 if (!OperatingSystem.IsWindows()) return Ok(CreateUnsupportedOverview()) 之前不会调用文件夹选择器,因此 Linux 上 PickFolderCalls 为 0 而非断言的 1。建议按 LinuxUnsupportedStatusDoesNotOpenFolderPicker 的方式补上 if (!OperatingSystem.IsWindows()) return; 守卫或把该测试限定为仅 Windows 运行。
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At MaiChartManager.Tests/Controllers/Tools/ResourceJunctionControllerTests.cs, line 13:
<comment>测试 `LocalWriteActionDoesNotRequireAnAuthenticationHeader` 缺少 Linux 平台守卫,在 Linux 构建上会失败。控制器 `SelectResourceJunctionSource` 在 `if (!OperatingSystem.IsWindows()) return Ok(CreateUnsupportedOverview())` 之前不会调用文件夹选择器,因此 Linux 上 `PickFolderCalls` 为 0 而非断言的 1。建议按 `LinuxUnsupportedStatusDoesNotOpenFolderPicker` 的方式补上 `if (!OperatingSystem.IsWindows()) return;` 守卫或把该测试限定为仅 Windows 运行。</comment>
<file context>
@@ -0,0 +1,91 @@
+[Collection("Static settings")]
+public sealed class ResourceJunctionControllerTests
+{
+ [Fact]
+ public void StatusRejectsRemoteRequests()
+ {
</file context>
| { | ||
| if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Session id is required.", nameof(sessionId)); | ||
| if (!sessionStates.TryGetValue(sessionId, out var state)) | ||
| sessionStates[sessionId] = state = new SelectionState(); |
There was a problem hiding this comment.
P2: 会话状态字典 sessionStates 只增不减:GetState 为每个新的 sessionId 永久写入一条 SelectionState,但整个服务里没有任何移除、过期或清理机制。前端每次挂载工具 Modal 都会用 crypto.randomUUID() 生成新会话 ID 并随请求头发送,因此用户反复进入/离开工具页后,后端会无限累积这些会话记录,造成长期运行时的内存泄漏。建议为会话提供明确的清理或复用策略:例如让 UI 复用单个固定会话、在 Modal 关闭时调用一个后端清理接口,或对会话做有界缓存的 LRU/过期淘汰。
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At MaiChartManager/Services/ResourceJunctionService.cs, line 401:
<comment>会话状态字典 `sessionStates` 只增不减:`GetState` 为每个新的 sessionId 永久写入一条 `SelectionState`,但整个服务里没有任何移除、过期或清理机制。前端每次挂载工具 Modal 都会用 `crypto.randomUUID()` 生成新会话 ID 并随请求头发送,因此用户反复进入/离开工具页后,后端会无限累积这些会话记录,造成长期运行时的内存泄漏。建议为会话提供明确的清理或复用策略:例如让 UI 复用单个固定会话、在 Modal 关闭时调用一个后端清理接口,或对会话做有界缓存的 LRU/过期淘汰。</comment>
<file context>
@@ -340,15 +383,23 @@ private static long CountFilesWithoutReparsePoints(DirectoryInfo root)
+ {
+ if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Session id is required.", nameof(sessionId));
+ if (!sessionStates.TryGetValue(sessionId, out var state))
+ sessionStates[sessionId] = state = new SelectionState();
+ return state;
}
</file context>
功能说明
新增“游戏资源链接”工具,用 Windows Junction 在多个 maimai 游戏目录之间共享体积较大的资源文件。
AssetBundleImages、MovieData、SoundData安全边界
验证
dotnet test MaiChartManager.Tests/MaiChartManager.Tests.csproj -c Debug --no-restore:18 项通过pnpm build:通过dotnet build MaiChartManager/MaiChartManager.csproj -c LinuxDebugBackend:通过测试覆盖目录归一化、自动候选排名、并列处理、直接
Sinmai_Data布局、嵌套重解析点跳过、手动源/目标覆盖、固定范围不可变、源目标重合保护、现有目录冲突、错误 Junction 拒绝,以及包含空格与&的临时路径下真实 Junction 的建立、读取和移除。Summary by Sourcery
引入一个游戏资源链接管理工具,通过 Windows 目录联接(junction)在多个 maimai 游戏安装之间共享大型资源目录,并提供严格的安全检查以及按会话选择资源源目录/目标目录的功能。
New Features:
添加后端服务和 HTTP API,用于检查、自动选择和管理用于游戏资源目录的 Windows 目录联接。
暴露一个新的前端工具弹窗,用于查看资源链接状态、选择源/目标游戏目录以及创建或移除目录联接。
扩展客户端 API 类型和枚举,以表示资源联接总览、条目和状态,并将该工具集成到主工具面板中。
为新的游戏资源链接管理工具添加英语、简体中文和繁体中文的本地化 UI 字符串。
Documentation:
Tests:
Original summary in English
Summary by Sourcery
Introduce a game resource link management tool that shares large resource directories between maimai game installs via Windows junctions, with strict safety checks and per-session source/target selection.
New Features:
Enhancements:
Documentation:
Tests:
本 PR 使用 AI 辅助实现,供审核参考:
AssetBundleImages、MovieData、SoundData;目标默认使用当前游戏目录且可按会话单独选择;源目录打开工具时自动按三类资源文件总数选择,同时保留手动选择;切换目标不触发游戏数据重载;源目录只读,不覆盖普通目录或错误链接;补测试、文档,并提交官方 PR。未在正文复制系统提示词或仓库代理指令;实现过程遵循仓库
AGENTS.md。工具的作用为:在例如SDGA,SDGB游戏文件的A000目录下建立符号链接,指向SDEZ的AssetBundleImages,MovieData和SoundData,实现复用SDEZ的主要资源文件,减少磁盘空间占用,已在本地和多台电脑上验证可用。
第一次传pr,可能有不当的操作打扰到各位了,抱歉捏
Summary by Sourcery
引入一个游戏资源链接管理工具,通过 Windows 连接点在多个 maimai 游戏安装之间共享大型资源目录,并支持按会话选择来源/目标目录以及严格的安全检查。
New Features:
Documentation:
Tests:
Original summary in English
Summary by Sourcery
Introduce a game resource link management tool that shares large resource directories between maimai game installs via Windows junctions, with per-session source/target selection and strict safety checks.
New Features:
Documentation:
Tests: