Skip to content

refactor(quality): 提升后端错误一致性与工程质量基线#2

Merged
MF-B merged 18 commits intomainfrom
refactor/quality
Mar 30, 2026
Merged

refactor(quality): 提升后端错误一致性与工程质量基线#2
MF-B merged 18 commits intomainfrom
refactor/quality

Conversation

@MF-B
Copy link
Copy Markdown
Owner

@MF-B MF-B commented Mar 30, 2026

背景

本 PR 主要聚焦质量改进与可维护性提升,不引入新的业务功能。目标是统一错误语义、补齐关键测试、规范工程流程,并整理文档结构。

主要改动

  1. 后端错误与分层职责
  • 新增领域错误语义,并在接口层统一映射 HTTP 状态码。
  • 冲突类场景(如名称冲突、运行中删除)统一返回 409。
  • 服务层明确依赖存储抽象,降低业务逻辑与存储实现耦合。
  1. 存储与稳定性
  • 存储实现聚焦 SQLite 持久化能力,完善约束冲突处理。
  • 统一实例状态写入与读取路径,增强错误可定位性。
  1. 测试补强
  • 新增 API Handler 单测,覆盖列表、创建、启停、删除及异常流程。
  • 新增 SQLite Store 单测,覆盖持久化、查询与约束行为。
  1. 工具链与 CI
  • 扩展 Task 任务:格式检查/修复、lint、test、vet、build。
  • 新增文档 lint、链接检查与格式检查流程。
  • 新增 TODO 索引生成脚本,支持从注释自动生成待办索引。
  • CI/Release 工作流对齐上述任务并优化构建流程。
  1. 文档重构与前端可维护性
  • 文档目录按规范/API/设计/执行计划分层重组,补充目录入口。
  • 前端补充关键注释(API 错误转换、Store 副作用、视图关键交互),减少维护歧义。

影响评估

  • 行为变化:错误码返回更明确,冲突类错误语义更一致。
  • 工程影响:本地与 CI 校验更严格,能更早暴露质量问题。
  • 兼容性:无 API 路径层面的破坏性改动。

验证建议

  • task fmt:check
  • task lint
  • task test
  • task vet
  • task build

Copilot AI review requested due to automatic review settings March 30, 2026 07:23
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 以“质量基线”建设为主,围绕后端错误语义统一、分层职责清晰化、测试补强,以及工程化任务/CI 与文档结构重整,提升整体一致性与可维护性。

Changes:

  • 后端:引入领域错误(model 包)并在 API Handler 侧集中映射 HTTP 状态码;Service/Store 依赖改为接口化,补充 SQLite Store 与 Handler 单测。
  • 工程化:扩展 Task 任务(fmt/lint/test/vet/build + docs 校验),CI/Release 工作流对齐;新增 TODO 索引生成器。
  • 文档:重构标准/契约/设计/执行计划目录结构,补齐规范与入口导航(AGENTS.md)。

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
scripts/todo-gen/main.go 新增 TODO 索引生成器(扫描注释并输出 Markdown)。
frontend/src/views/ContainerList.vue 增补关键交互注释,明确视图层职责。
frontend/src/stores/containers.ts 增补 store 副作用/错误收敛注释,强化“视图只触发 action”边界。
frontend/src/components/TopBar.vue 注释更新,强调外部点击监听成对移除。
frontend/src/components/Sidebar.vue 增补状态开关语义注释。
frontend/src/api/index.ts 统一非 2xx 错误提取逻辑(优先读取后端 error 字段)。
docs/standards/ops.md 新增运维/CI/CD 标准与任务门禁说明。
docs/standards/frontend.md 新增前端规范(架构、注释、异常处理、样式约束等)。
docs/standards/directory.md 新增项目目录结构规范说明。
docs/standards/backend.md 新增后端规范(错误处理、依赖注入、SQLite 并发等)。
docs/ops_engineering_standards.md 删除旧运维标准文档(被新结构替代)。
docs/exec-plans/TODO.md 新增 TODO 索引产物(由脚本生成)。
docs/exec-plans/README.md 新增执行计划目录说明与模板约定。
docs/domain_instance_lifecycle.md 删除旧领域文档(迁移到设计文档目录)。
docs/design-docs/instance_lifecycle.md 新增实例生命周期设计文档(含一致性/回滚策略)。
docs/api/contracts.md 新增 API 合约文档(路径、状态码、样例)。
docs/02_API_Contracts.md 删除旧 API 合约文档(被新合约替代)。
docs/01_Frontend_Standards.md 删除旧前端规范文档(被新规范替代)。
docs/01_Backend_Standards.md 删除旧后端规范文档(被新规范替代)。
docs/00_Root_Context.md 删除旧 Root Context 文档(目录重构)。
backend/internal/store/sqlite_test.go 新增 SQLite Store 单测(Save/Get/List/Delete 与唯一约束)。
backend/internal/store/sqlite.go 统一错误语义(使用 model.ErrNameExists),补充注释与 schema 初始化。
backend/internal/service/docker_service.go store 依赖改为接口,错误 wrap 更一致,补充关键流程注释/TODO。
backend/internal/model/errors.go 新增领域错误定义(名称冲突、运行中删除等)。
backend/internal/api/router.go 增补路由/中间件注释,使用集中路由注册。
backend/internal/api/handlers_test.go 新增 Handler 单测(成功/异常/冲突映射)。
backend/internal/api/handlers.go 引入 InstanceService 接口与集中 mapErrorCode,统一错误映射。
backend/.golangci.yml 新增后端 lint 配置(govet/staticcheck 等)。
Taskfile.yml 扩展 fmt/lint/docs 校验任务,CI 门禁对齐。
Readme.md 精简运行指南片段与待办清单排版调整。
AGENTS.md 新增文档导航入口。
.github/workflows/release.yml workflow 对齐新的 task 名称(fmt:check)。
.github/workflows/ci.yml CI 对齐新的 fmt:check 任务。

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3 to +5
Generated at: 2026-03-30T00:42:39+08:00
Source pattern: TODO: description
Repository root: C:/Users/guzem/Desktop/Core/毕设/MineDock
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

生成的 TODO 文档里包含了本机绝对路径(Repository root: ...)和精确时间戳。把这类信息提交到仓库会泄露本地目录结构,并导致每次生成都产生无意义的 diff。建议在生成时改为输出仓库相对路径/固定值(例如 .),并考虑移除时间戳或改成日期级别,确保产物可复现。

Suggested change
Generated at: 2026-03-30T00:42:39+08:00
Source pattern: TODO: description
Repository root: C:/Users/guzem/Desktop/Core/毕设/MineDock
Generated at: 2026-03-30
Source pattern: TODO: description
Repository root: .

Copilot uses AI. Check for mistakes.
Comment on lines +189 to +197
if _, err := fmt.Fprintf(f, "\nGenerated at: %s\n", now); err != nil {
return err
}
if _, err := fmt.Fprintln(f, "Source pattern: TODO: description"); err != nil {
return err
}
if _, err := fmt.Fprintf(f, "Repository root: %s\n", filepath.ToSlash(root)); err != nil {
return err
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

writeMarkdown 会把 root(通常是绝对路径)写入输出文件的 Repository root: 字段,导致生成的 TODO.md 带上开发者本机路径、且产物不可复现。建议改为写入仓库相对路径(如 .)或直接省略该字段,并与 Taskfile 的执行目录/参数保持一致。

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +24
function getResponseErrorMessage(data: unknown, status: number): string {
// 后端契约约定优先返回 error 字段,前端统一在这里做错误信息提取。
if (data && typeof data === "object" && "error" in data) {
const error = (data as { error?: unknown }).error;
if (typeof error === "string" && error.trim().length > 0) {
return error;
}
}
return `request failed: ${status}`;
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fallback 分支返回的错误信息是硬编码英文字符串(request failed: ${status}),而该 Error 会被上层直接展示到 output 区域。根据新增的前端规范(错误提示用 i18n key 输出),这里更适合返回可翻译的 key/结构化错误(如携带 status 供 store 映射),避免把英文文案直接暴露到 UI。

Copilot uses AI. Check for mistakes.
Comment on lines 14 to 18
// 统一的输出区文本,视图层只负责渲染该结果。
const output = ref<string>("");

// 将异常对象收敛为可展示文本,避免视图层分散处理错误结构。
function getErrorMessage(error: unknown): string {
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

output 被定义为“统一输出区文本”,但当前 store 的 printError 会生成硬编码前缀(ERROR:)并透出后端/网络层原始字符串到 UI。若按前端规范要求错误提示使用 i18n key 输出,建议在 store 层把错误映射为稳定的 i18n key(或至少移除硬编码英文前缀),由视图层用 t(...) 渲染。

Copilot uses AI. Check for mistakes.
### 格式化

- 使用 ESLint + Prettier。
- 使用 TypeScript严格模式。
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

文档里 “TypeScript严格模式” 少了空格,建议统一为 “TypeScript 严格模式”。

Suggested change
- 使用 TypeScript严格模式
- 使用 TypeScript 严格模式

Copilot uses AI. Check for mistakes.

```text
MineDock/
├── .github/ # 存放ci/cd
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

目录说明里的 “ci/cd” 建议按常见写法大写为 “CI/CD”,与文档其它地方的用词保持一致。

Suggested change
├── .github/ # 存放ci/cd
├── .github/ # 存放 CI/CD

Copilot uses AI. Check for mistakes.
Taskfile.yml Outdated
docs:todo:
desc: Generate docs/exec-plans/TODO.md from TODO comments
cmds:
- go run ./scripts/todo-gen/main.go -root .. -out ../docs/exec-plans/TODO.md
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs:todo 任务当前从仓库根目录执行,但参数 -root .. -out ../docs/... 会把扫描根目录指向仓库上一级、并把输出写到仓库外的 ../docs。这会导致生成结果不稳定(还可能扫到无关目录),且在 CI/本地容易误写文件。建议把 root/out 改为仓库内路径(例如 -root . -out docs/exec-plans/TODO.md),或为该 task 设置 dir: scripts/todo-gen 后再使用相对路径。

Suggested change
- go run ./scripts/todo-gen/main.go -root .. -out ../docs/exec-plans/TODO.md
- go run ./scripts/todo-gen/main.go -root . -out docs/exec-plans/TODO.md

Copilot uses AI. Check for mistakes.
Taskfile.yml Outdated
desc: Run backend golangci-lint checks
dir: backend
cmds:
- go run github.com/golangci/golangci-lint/cmd/golangci-lint@latest run --config .golangci.yml ./...
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

backend:lint 使用 go run ...@latest 会让 CI/本地 lint 结果随时间漂移(不同日期拉到不同版本),可能导致不可复现的失败。建议固定 golangci-lint 的版本号(例如用明确的 @vX.Y.Z 或在工具链/CI 中单独安装固定版本),保证门禁稳定。

Suggested change
- go run github.com/golangci/golangci-lint/cmd/golangci-lint@latest run --config .golangci.yml ./...
- go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.1 run --config .golangci.yml ./...

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 65 to 70
inst := model.Instance{ContainerID: resp.ID, Name: name, Status: "Stopped"}
if err := s.store.Save(ctx, inst); err != nil {
// 说明:请求上下文取消时,清理逻辑会使用独立上下文做尽力回收。
_ = s.cli.ContainerRemove(context.Background(), resp.ID, container.RemoveOptions{Force: true})
return "", err
return "", fmt.Errorf("save instance record: %w", err)
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateInstance wraps store.Save errors with additional context ("save instance record: %w"). When the underlying error is a domain sentinel like model.ErrNameExists, this changes the string sent back in the API response (handlers currently return err.Error()), which breaks the frontend’s backendMessage-to-i18n mapping (it expects exactly "instance name already exists"). Consider either not wrapping known domain errors (return them as-is) or ensuring the handler returns a stable client-facing message for errors.Is(model.ErrNameExists).

Copilot uses AI. Check for mistakes.
code = http.StatusConflict
}
writeJSON(w, code, statusResponse{Status: "error", Error: err.Error()})
writeJSON(w, mapErrorCode(err), statusResponse{Status: "error", Error: err.Error()})
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handlers return err.Error() directly in the JSON body even when the status code is derived from errors.Is(...) (e.g., ErrNameExists/ErrInstanceRunning). This makes the client-facing error field unstable when upstream layers wrap errors, and may leak internal context strings. Prefer emitting a stable, canonical message for known domain errors (e.g., use the sentinel’s Error() value or a fixed string) while still keeping wrapped errors for server-side logging.

Suggested change
writeJSON(w, mapErrorCode(err), statusResponse{Status: "error", Error: err.Error()})
status := mapErrorCode(err)
msg := http.StatusText(status)
if msg == "" {
msg = "error"
}
writeJSON(w, status, statusResponse{Status: "error", Error: msg})

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +27
rootFlag := flag.String("root", "..", "repository root path")
outFlag := flag.String("out", "../docs/exec-plans/TODO.md", "output markdown file path")
flag.Parse()
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default flag values assume the binary is run from within scripts/todo-gen (root="..", out="../docs/..."), but the Taskfile runs it from the repo root with explicit flags. If someone runs go run ./scripts/todo-gen/main.go without flags, it will walk the parent directory and write outside the repo. Consider defaulting -root to "." and -out to "docs/exec-plans/TODO.md" to make the tool safe to run from the repo root.

Copilot uses AI. Check for mistakes.

## 拟定更改 (Proposed Changes)

按组件名称(如具体的 package、功能区或依赖层)将将要修改的文件分组归类,并按逻辑顺序(例如先依赖后实现)展开描述。建议使用水平分割线来区分不同组件。
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

存在重复用词,建议将“将将要修改”改为“将要修改”。

Suggested change
按组件名称(如具体的 package、功能区或依赖层)将将要修改的文件分组归类,并按逻辑顺序(例如先依赖后实现)展开描述。建议使用水平分割线来区分不同组件。
按组件名称(如具体的 package、功能区或依赖层)将要修改的文件分组归类,并按逻辑顺序(例如先依赖后实现)展开描述。建议使用水平分割线来区分不同组件。

Copilot uses AI. Check for mistakes.
@MF-B MF-B merged commit 0509b76 into main Mar 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants