# skills 详细内容 ## ⚠️ 既有契约与读模型迁移门禁(必须) > **惨痛教训**:新写链路把真实数据写入新表,旧 API 仍读原表,前端虽然没改却从此看不到新数据。“后端已成功执行”不等于“用户能看到执行结果”。 开发计划涉及新表、新账本、新写路径、替换存储模型或接口版本时,必须先完成以下门禁。未通过时,不得把“新建平行表/新建平行 API”写成默认方案。 ### 1. 先证明旧模型不能复用 - [ ] 已检查现有表、字段、索引、状态机和扩展字段(如 metadata/JSON)。 - [ ] 已说明为什么增加字段、子表或现有类型无法满足需求。 - [ ] 新表承载的是真正独立的实体或生命周期,而不是为同一业务实体建立第二个彼此脱节的世界。 - [ ] 已区分“内部不可变证据/事件账本”和“产品对外读模型”;新建内部账本不代表必须更换原有产品接口。 ### 2. 真实调用方查证 必须从对外接口向所有消费方反向追踪,不能只审查新后端写路径: - [ ] 列出现有 API 路径、请求参数、响应结构和身份/分页语义。 - [ ] 使用 `rg` 查找 Web、iOS、Android、定时任务、报表和第三方调用方。 - [ ] 若仓库存在 `.codex-fe-ref/`,必须在其中查证配套前端的真实调用。 - [ ] 开发计划的变更文件清单必须覆盖“写入 → 投影/查询 → API → 页面”完整链路。 ### 3. 稳定契约默认不变 - 既有用户界面已消费的 API,默认保留路径和响应契约;除非用户明确批准破坏性迁移,不得通过新建版本接口把兼容成本转嫁给前端。 - 同一业务概念只保留一个产品级读模型。内部账本可以拆分,但必须投影回既有读模型,或由原 API 无感聚合。 - 日志、告警、信号或推测数据不得在查询降级时冒充业务事实(例如把 warning 当成交易记录)。 ### 4. 不可避免新写模型时的强制迁移计划 若确有独立生命周期、不可变审计或 1:N/N:N 证据需求,可以增加新表,但开发计划必须同时包含: 1. **写入兼容**:双写,或从新账本向既有读模型做事务性/幂等投影。 2. **历史回填**:有界、可恢复、可重跑,且不猜测无法证明的归属。 3. **去重身份**:定义跨旧/新数据源的稳定业务键,并用数据库唯一约束或等价强保证防重。 4. **读取切换**:定义何时以新证据为准、旧数据如何兼容,以及结果数量/金额/状态对账门槛。 5. **回滚策略**:回滚新写入时仍能读取已产生的真实业务事实,不删除或隐藏已成功数据。 6. **可观测性**:监控新写入成功但旧读模型缺失、投影延迟、去重冲突和回填差异。 ### 5. 用户可见性是必须验收项 测试不得只证明“新写路径成功”。至少要有一个真实业务记录贯穿测试,同时证明: - [ ] 新写路径产生了正确事实。 - [ ] 原有对外 API 可以立即或在约定 SLA 内返回该记录。 - [ ] 每个真实消费页面/客户端都能正确解码并展示。 - [ ] 时间、方向、状态、数量、金额与唯一标识在数据库、API 和 UI 之间一致。 - [ ] 部分失败、重试、重复投递、迟到数据和回滚后仍不丢数、不重复、不冒充。 开发计划必须把上述内容落成明确的变更文件、任务、测试用例和发布门禁,不得只在“风险”章节留一句提醒。 --- ## 3. 技术方案设计 ### 3.1 后端改动(Go) #### 3.1.1 数据模型(如需修改) ```go // backend/models/manual.go type Manual struct { ID uint `gorm:"primarykey"` ProjectID uint `gorm:"not null;index"` Title string `gorm:"size:500;not null"` // 新增字段(如果需要) } ``` #### 3.1.2 Repository 层(数据访问) **文件**:`backend/database/manual_repository.go` **新增方法**: - `GetManualByXXX()` - 按条件查询 - `UpdateManualXXX()` - 更新特定字段 - `BatchXXX()` - 批量操作 **实现要点**: - 使用 GORM 查询构建器 - 处理软删除(deleted_at) - 正确处理事务 #### 3.1.3 Service 层(业务逻辑) **文件**:`backend/services/manual_service.go` **新增方法**: - `CreateManualWithValidation()` - 创建 + 业务校验 - `PublishManual()` - 发布逻辑 **业务规则**: - 权限检查(使用 permission 包) - 数据校验(必填字段、格式检查) - 状态流转(Draft → Published) - 事务处理(多表操作) #### 3.1.4 Handler 层(HTTP 处理) **文件**:`backend/handlers/manual_handler.go` **新增接口**: ```go // POST /api/projects/:id/manuals/xxx func (h *ManualHandler) CreateXXX(c *gin.Context) { // 1. 解析请求参数 // 2. 参数验证 // 3. 调用 Service 层 // 4. 返回响应 } ``` **要点**: - 使用 `c.ShouldBindJSON()` 解析请求 - 使用 `validator` 进行参数验证 - 统一错误处理(使用 `c.JSON()`) - 记录日志(使用 logger) #### 3.1.5 Route 层(路由注册) **文件**:`backend/routes/manual_routes.go` ```go func RegisterManualRoutes(r *gin.RouterGroup, h *handlers.ManualHandler) { manuals := r.Group("/manuals") manuals.Use(middleware.Auth()) // 认证中间件 { manuals.POST("/xxx", h.CreateXXX) // 新增路由... } } ``` #### 3.1.6 数据库迁移(如需要) **文件**:`backend/migrations/YYYYMMDDHHMMSS_add_xxx_to_manuals.up.sql` ```sql -- 新增字段 ALTER TABLE manuals ADD COLUMN xxx VARCHAR(255); -- 新增索引 CREATE INDEX idx_manuals_xxx ON manuals(xxx); -- 新增表(如需要) CREATE TABLE manual_xxx ( id BIGSERIAL PRIMARY KEY, manual_id BIGINT NOT NULL REFERENCES manuals(id), created_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` ### 3.2 前端改动(React + TypeScript) #### 3.2.1 类型定义 **文件**:`frontend/src/types/manual.ts` ```typescript // 新增接口 export interface ManualXXX { id: number; title: string; // ... } // 新增请求类型 export interface CreateManualXXXRequest { title: string; // ... } ``` #### 3.2.2 API 服务 **文件**:`frontend/src/services/manualService.ts` ```typescript export const manualApi = { // 新增 API 方法 createXXX: async ( projectId: number, data: CreateManualXXXRequest ): Promise => { const response = await api.post( `/projects/${projectId}/manuals/xxx`, data ); return response.data; }, }; ``` #### 3.2.3 组件开发 **文件**:`frontend/src/components/ManualXXXCard.tsx`(如需要) ```typescript interface ManualXXXCardProps { manual: Manual; onXXX?: (manual: Manual) => void; } const ManualXXXCard: React.FC = ({ manual, onXXX }) => { // 组件实现 }; ``` #### 3.2.4 页面修改 **文件**:`frontend/src/pages/ManualListPage.tsx` **改动点**: - 新增状态:`const [xxxState, setXxxState] = useState(...)` - 新增处理函数:`const handleXXX = async () => { ... }` - 修改渲染逻辑:在合适位置添加新功能 UI #### 3.2.5 路由配置(如需要) **文件**:`frontend/src/App.tsx` ```typescript } /> ``` ### 3.3 iOS 改动(SwiftUI + MVVM) #### 3.3.1 数据模型 **文件**:`Models/ManualModel.swift` ```swift import Foundation struct Manual: Codable, Identifiable { let id: Int let title: String let content: String? let status: ManualStatus let createdAt: Date let updatedAt: Date enum CodingKeys: String, CodingKey { case id, title, content, status case createdAt = "created_at" case updatedAt = "updated_at" } } enum ManualStatus: String, Codable { case draft case published case archived } ``` #### 3.3.2 DTO(API 响应) **文件**:`Models/DTOs/ManualListResponse.swift` ```swift import Foundation struct ManualListResponse: Codable { let success: Bool let message: String let data: ManualListData } struct ManualListData: Codable { let data: [Manual] let pagination: Pagination } struct Pagination: Codable { let page: Int let pageSize: Int let total: Int let totalPages: Int enum CodingKeys: String, CodingKey { case page case pageSize = "page_size" case total case totalPages = "total_pages" } } ``` #### 3.3.3 API 端点 **文件**:`Core/Services/APIEndpoints.swift` ```swift // 在 APIEndpoints 枚举中添加 enum APIEndpoints { // ... 现有端点 // 手册相关 case manualList(projectId: Int, page: Int, pageSize: Int) case manualDetail(id: Int) case createManual(projectId: Int) case updateManual(id: Int) case deleteManual(id: Int) var path: String { switch self { case .manualList(let projectId, _, _): return "/projects/\(projectId)/manuals" case .manualDetail(let id): return "/manuals/\(id)" case .createManual(let projectId): return "/projects/\(projectId)/manuals" case .updateManual(let id): return "/manuals/\(id)" case .deleteManual(let id): return "/manuals/\(id)" } } var method: String { switch self { case .manualList, .manualDetail: return "GET" case .createManual: return "POST" case .updateManual: return "PUT" case .deleteManual: return "DELETE" } } } ``` #### 3.3.4 服务协议 **文件**:`Core/Services/ServiceProtocols.swift` ```swift // 添加手册服务协议 protocol ManualServiceProtocol { func fetchManuals(projectId: Int, page: Int, pageSize: Int) async throws -> [Manual] func fetchManualDetail(id: Int) async throws -> Manual func createManual(_ manual: CreateManualRequest) async throws -> Manual func updateManual(id: Int, _ update: UpdateManualRequest) async throws -> Manual func deleteManual(id: Int) async throws } ``` #### 3.3.5 ViewModel **文件**:`Features/Manuals/ManualListViewModel.swift` ```swift import Foundation import Combine @MainActor class ManualListViewModel: ObservableObject { // MARK: - Published Properties @Published var manuals: [Manual] = [] @Published var isLoading = false @Published var error: String? @Published var hasMorePages = true // MARK: - Private Properties private let manualService: ManualServiceProtocol private let projectId: Int private var currentPage = 1 private let pageSize = 20 // MARK: - Initialization init(projectId: Int, manualService: ManualServiceProtocol) { self.projectId = projectId self.manualService = manualService } // MARK: - Public Methods func loadManuals() async { guard !isLoading else { return } isLoading = true error = nil currentPage = 1 do { let result = try await manualService.fetchManuals( projectId: projectId, page: currentPage, pageSize: pageSize ) manuals = result hasMorePages = result.count >= pageSize } catch { self.error = error.localizedDescription } isLoading = false } func loadMoreIfNeeded(currentItem: Manual) async { guard hasMorePages, !isLoading, let lastItem = manuals.last, currentItem.id == lastItem.id else { return } currentPage += 1 isLoading = true do { let newManuals = try await manualService.fetchManuals( projectId: projectId, page: currentPage, pageSize: pageSize ) manuals.append(contentsOf: newManuals) hasMorePages = newManuals.count >= pageSize } catch { self.error = error.localizedDescription currentPage -= 1 } isLoading = false } } ``` #### 3.3.6 View **文件**:`Features/Manuals/ManualListView.swift` ```swift import SwiftUI struct ManualListView: View { @StateObject private var viewModel: ManualListViewModel init(projectId: Int, manualService: ManualServiceProtocol) { _viewModel = StateObject(wrappedValue: ManualListViewModel( projectId: projectId, manualService: manualService )) } var body: some View { NavigationStack { Group { if viewModel.isLoading && viewModel.manuals.isEmpty { ProgressView("加载中...") } else if let error = viewModel.error { ErrorView(message: error) { Task { await viewModel.loadManuals() } } } else { manualList } } .navigationTitle("项目手册") .toolbar { ToolbarItem(placement: .primaryAction) { Button(action: { /* 新建手册 */ }) { Image(systemName: "plus") } } } } .task { await viewModel.loadManuals() } } private var manualList: some View { List { ForEach(viewModel.manuals) { manual in ManualRowView(manual: manual) .onAppear { Task { await viewModel.loadMoreIfNeeded(currentItem: manual) } } } if viewModel.isLoading { HStack { Spacer() ProgressView() Spacer() } } } .refreshable { await viewModel.loadManuals() } } } ``` #### 3.3.7 导航注册 **文件**:`Core/Architecture/AppCoordinator.swift` ```swift // 在 AppCoordinator 中添加手册导航 case manualList(projectId: Int) case manualDetail(id: Int) // 在 navigationDestination 中处理 .navigationDestination(for: AppRoute.self) { route in switch route { case .manualList(let projectId): ManualListView( projectId: projectId, manualService: DIContainer.shared.manualService ) case .manualDetail(let id): ManualDetailView( manualId: id, manualService: DIContainer.shared.manualService ) // ... 其他路由 } } ``` --- ## 4. 开发任务拆分 ### 4.1 任务层级结构 ``` [DEV-PLAN] 需求标题 ├── [后端] Model 层改动 ├── [后端] Repository 层改动 ├── [后端] Service 层改动 ├── [后端] Handler 层改动 ├── [后端] Route 层改动 ├── [后端] Migration 脚本 ├── [Web前端] Types 定义 ├── [Web前端] API Service 封装 ├── [Web前端] 组件开发 ├── [Web前端] 页面修改 ├── [iOS] Model 层改动 ├── [iOS] DTO 定义 ├── [iOS] APIEndpoints 添加 ├── [iOS] ViewModel 实现 ├── [iOS] View 实现 └── [测试] 单元测试 + 集成测试 ``` ### 4.2 子任务创建规则 **按文件拆分**(推荐): - 每个文件对应一个子任务 - 任务标题格式:`[层级] 修改 文件名` - 示例:`[Handler] 修改 manual_handler.go 添加发布接口` **按功能模块拆分**(大改动): - 将相关文件归为一个模块 - 任务标题格式:`[模块] 功能描述` - 示例:`[手册发布] 实现发布功能(Service + Handler + Route)` ### 4.3 子任务创建示例 使用 `mcp__ai-proj__create_subtask` 创建子任务: ```typescript // 示例 1:后端子任务 mcp__ai-proj__create_subtask({ parentId: 5326, // 开发任务 ID title: "[Handler] 修改 manual_handler.go 添加发布接口" }) // 示例 2:前端子任务 mcp__ai-proj__create_subtask({ parentId: 5326, title: "[Page] 修改 ManualListPage.tsx 添加发布按钮" }) // 示例 3:数据库子任务 mcp__ai-proj__create_subtask({ parentId: 5326, title: "[Migration] 创建手册版本表" }) ``` --- ## 5. 实施顺序指南 ### 5.1 标准开发顺序(新功能) ``` 第一阶段:数据层(1-2天) ├── 1. Model 层:定义数据结构 ├── 2. Migration:创建/修改表 └── 3. Repository 层:实现数据访问 第二阶段:业务层(2-3天) ├── 4. Service 层:实现业务逻辑 └── 5. 编写单元测试 第三阶段:接口层(1-2天) ├── 6. Handler 层:实现 HTTP 接口 ├── 7. Route 层:注册路由 └── 8. API 文档(Swagger) 第四阶段:Web 前端层(3-4天) ├── 9. Types:定义 TypeScript 接口 ├── 10. Services:封装 API 调用 ├── 11. Components:开发可复用组件 └── 12. Pages:实现业务页面 第五阶段:iOS 客户端(2-3天) ├── 13. Model:定义数据模型 ├── 14. DTO:定义 API 响应结构 ├── 15. APIEndpoints:添加 API 端点 ├── 16. ViewModel:实现业务逻辑 └── 17. View:实现 SwiftUI 视图 第六阶段:测试与优化(1-2天) ├── 18. 后端单元测试 ├── 19. 前端组件测试 ├── 20. iOS 单元测试 └── 21. E2E/UI 测试 ``` ### 5.2 快速修复顺序(Bug Fix) ``` 1. 定位问题代码 2. 修改对应层级(通常是 Service 或 Handler) 3. 修改前端页面(如果有前端问题) 4. 添加/修改测试用例 5. 验证修复效果 ``` --- ## 6. 技术风险评估 ### 6.1 影响分析矩阵 | 改动类型 | 影响范围 | 风险等级 | 应对措施 | |---------|---------|---------|---------| | 新增字段(非必填) | 低 | 低 | 添加 Migration,向后兼容 | | 修改字段类型 | 中 | 中 | 数据迁移脚本,充分测试 | | 删除字段 | 高 | 高 | 确认无依赖,先标记废弃 | | 新增 API 接口 | 低 | 低 | 接口设计评审 | | 修改 API 接口 | 中 | 中 | 保留旧接口,添加版本号 | | 删除 API 接口 | 高 | 高 | 确认无调用,发布公告 | | 新增前端页面 | 低 | 低 | 路由冲突检查 | | 修改核心组件 | 高 | 高 | 充分测试所有使用场景 | ### 6.2 风险评估清单 **数据库风险**: - [ ] 是否需要数据迁移? - [ ] 是否影响现有数据完整性? - [ ] 是否需要回滚方案? - [ ] 是否有索引优化需求? **API 风险**: - [ ] 是否破坏现有 API 兼容性? - [ ] 是否需要版本管理? - [ ] 是否有性能影响? - [ ] 是否需要限流保护? **前端风险**: - [ ] 是否影响现有页面功能? - [ ] 是否有性能瓶颈(大列表、复杂计算)? - [ ] 是否兼容主流浏览器? - [ ] 是否响应式友好? **业务风险**: - [ ] 是否影响核心业务流程? - [ ] 是否需要通知用户? - [ ] 是否需要灰度发布? - [ ] 是否有回退方案? ### 6.3 性能影响分析 ```markdown ## 7. 代码规范检查 ### 7.1 Go 代码规范 ```go // ✅ 好的实践 func (r *ManualRepository) GetManualByID(id uint) (*models.Manual, error) { var manual models.Manual if err := r.db.First(&manual, id).Error; err != nil { return nil, err } return &manual, nil } // ❌ 不好的实践 func (r *ManualRepository) getManual(id uint) *models.Manual { var manual models.Manual r.db.First(&manual, id) return &manual // 未处理错误 } ``` **检查清单**: - [ ] 导出函数首字母大写 - [ ] 错误处理完整(不忽略 error) - [ ] 使用指针接收器(receiver) - [ ] 注释清晰(godoc 格式) - [ ] 命名符合 Go 规范(驼峰式) ### 7.2 TypeScript 代码规范 ```typescript // ✅ 好的实践 interface Manual { id: number; title: string; } export const manualApi = { async getManual(id: number): Promise { try { const response = await api.get(`/manuals/${id}`); return response.data; } catch (error) { console.error('Failed to get manual:', error); throw error; } } }; // ❌ 不好的实践 export const getManual = (id) => { return api.get('/manuals/' + id).then(r => r.data); } ``` **检查清单**: - [ ] 明确的类型定义(避免 any) - [ ] 使用 async/await(而非 .then()) - [ ] 错误处理完整 - [ ] 使用 ES6+ 语法(const, 箭头函数) - [ ] 导出清晰(export const/interface) ### 7.3 Swift/iOS 代码规范 ```swift // ✅ 好的实践 @MainActor class ManualListViewModel: ObservableObject { // MARK: - Published Properties @Published private(set) var manuals: [Manual] = [] @Published private(set) var isLoading = false @Published var error: String? // MARK: - Private Properties private let manualService: ManualServiceProtocol // MARK: - Initialization init(manualService: ManualServiceProtocol) { self.manualService = manualService } // MARK: - Public Methods func loadManuals() async { guard !isLoading else { return } isLoading = true defer { isLoading = false } do { manuals = try await manualService.fetchManuals() } catch { self.error = error.localizedDescription } } } // ❌ 不好的实践 class ManualVM: ObservableObject { @Published var manuals = [Manual]() @Published var loading = false func load() { loading = true // 未处理错误,未使用 @MainActor Task { manuals = try! await service.fetch() loading = false } } } ``` **检查清单**: - [ ] ViewModel 使用 `@MainActor` 确保主线程更新 - [ ] 使用 `MARK` 注释分隔代码区块 - [ ] Published 属性使用 `private(set)` 防止外部修改 - [ ] 使用协议依赖注入(方便测试) - [ ] 使用 `async/await` 而非 completion handler - [ ] 使用 `guard` 提前返回 - [ ] 使用 `defer` 确保状态重置 - [ ] 错误使用 `do-catch` 完整处理 **命名规范**: | 类型 | 规范 | 示例 | |------|------|------| | 文件名 | 大驼峰 | `ManualListView.swift` | | 类/结构体 | 大驼峰 | `ManualListViewModel` | | 协议 | 大驼峰 + Protocol 后缀 | `ManualServiceProtocol` | | 函数/变量 | 小驼峰 | `loadManuals()`, `isLoading` | | 常量 | 小驼峰 | `let pageSize = 20` | | 枚举 case | 小驼峰 | `case draft`, `case published` | **MVVM 层级规范**: | 层级 | 职责 | 命名规则 | |------|------|----------| | Model | 数据结构 | `XXXModel.swift` | | DTO | API 响应 | `XXXResponse.swift`, `XXXListResponse.swift` | | Service | 业务逻辑 | `XXXService.swift`, `XXXServiceProtocol` | | ViewModel | 视图状态 | `XXXViewModel.swift` | | View | UI 渲染 | `XXXView.swift`, `XXXListView.swift` | --- ## 8. 测试策略 ### 8.1 后端测试 **单元测试**(Go): ```go // backend/handlers/manual_handler_test.go func TestManualHandler_CreateManual(t *testing.T) { // Arrange handler := setupTestHandler() // Act req := httptest.NewRequest("POST", "/manuals", body) w := httptest.NewRecorder() handler.CreateManual(w, req) // Assert assert.Equal(t, 200, w.Code) } ``` **集成测试**: - 测试完整的 HTTP 请求流程 - 测试数据库交互 - 测试事务回滚 ### 8.2 前端测试 **组件测试**(Jest + React Testing Library): ```typescript // ManualListPage.test.tsx describe('ManualListPage', () => { it('should render manual list', async () => { render(); expect(await screen.findByText('项目手册')).toBeInTheDocument(); }); }); ``` **E2E 测试**(Playwright): ```typescript test('create manual flow', async ({ page }) => { await page.goto('/projects/1/manuals'); await page.click('text=新建手册'); await page.fill('[name="title"]', 'Test Manual'); await page.click('text=提交'); await expect(page.locator('text=Test Manual')).toBeVisible(); }); ``` ### 8.3 iOS 测试 **单元测试**(XCTest): ```swift // ManualListViewModelTests.swift import XCTest @testable import AI_Proj_iOS @MainActor final class ManualListViewModelTests: XCTestCase { var viewModel: ManualListViewModel! var mockService: MockManualService! override func setUp() { super.setUp() mockService = MockManualService() viewModel = ManualListViewModel( projectId: 1, manualService: mockService ) } func testLoadManuals_Success() async { // Arrange mockService.mockManuals = [ Manual(id: 1, title: "Test Manual", ...) ] // Act await viewModel.loadManuals() // Assert XCTAssertEqual(viewModel.manuals.count, 1) XCTAssertNil(viewModel.error) XCTAssertFalse(viewModel.isLoading) } func testLoadManuals_Error() async { // Arrange mockService.shouldFail = true // Act await viewModel.loadManuals() // Assert XCTAssertTrue(viewModel.manuals.isEmpty) XCTAssertNotNil(viewModel.error) } } // Mock Service class MockManualService: ManualServiceProtocol { var mockManuals: [Manual] = [] var shouldFail = false func fetchManuals(projectId: Int, page: Int, pageSize: Int) async throws -> [Manual] { if shouldFail { throw NetworkError.serverError(500, "Mock error") } return mockManuals } } ``` **UI 测试**(XCUITest): ```swift // ManualListUITests.swift import XCTest final class ManualListUITests: XCTestCase { let app = XCUIApplication() override func setUp() { continueAfterFailure = false app.launch() } func testManualListDisplays() { // Navigate to manual list app.buttons["手册"].tap() // Verify list appears let manualList = app.collectionViews["manualList"] XCTAssertTrue(manualList.waitForExistence(timeout: 5)) // Verify at least one item XCTAssertTrue(manualList.cells.count > 0) } func testPullToRefresh() { app.buttons["手册"].tap() let manualList = app.collectionViews["manualList"] manualList.swipeDown() // Verify refresh indicator appears XCTAssertTrue(app.activityIndicators.firstMatch.exists) } } ``` **Preview 测试**(SwiftUI Previews): ```swift #Preview("Manual List - Loading") { ManualListView( projectId: 1, manualService: PreviewMocks.manualService ) } #Preview("Manual List - With Data") { ManualListView( projectId: 1, manualService: PreviewMocks.manualServiceWithData ) } #Preview("Manual List - Error") { ManualListView( projectId: 1, manualService: PreviewMocks.manualServiceWithError ) } ``` --- ## 10. 最佳实践 ### 10.1 代码探索策略 **优先级排序**: 1. 先搜索完全匹配(精确关键词) 2. 再搜索模糊匹配(相关关键词) 3. 最后搜索文件模式(命名规范) **示例**: ```bash # 1. 精确搜索 Grep(pattern="Manual", path="backend/models") # 2. 模糊搜索 Grep(pattern="手册|manual", path="backend") # 3. 文件模式 Glob(pattern="**/*manual*.go") ``` ### 10.2 任务拆分原则 **SMART 原则**: - **Specific**(具体):明确要修改哪个文件 - **Measurable**(可衡量):清晰的完成标准 - **Achievable**(可实现):单个任务 2-4 小时完成 - **Relevant**(相关):与需求直接相关 - **Time-bound**(有时限):预估工时 **拆分粒度**: - ✅ 好的粒度:`[Handler] 修改 manual_handler.go 添加发布接口(2h)` - ❌ 太粗:`[后端] 实现所有后端功能` - ❌ 太细:`[Handler] 添加 import 语句` ### 10.3 文档编写原则 1. **准确性**:基于代码探索的真实信息 2. **完整性**:覆盖所有必要环节(Model → Route) 3. **可操作性**:开发者可直接按文档实施 4. **可追溯性**:记录决策原因和依据 ### 10.4 常见陷阱 ❌ **陷阱1:过度设计** - 问题:为简单需求设计复杂架构 - 解决:遵循 KISS 原则,简单实现 ❌ **陷阱2:忽略现有代码** - 问题:重复造轮子,不复用已有实现 - 解决:充分探索代码库,识别可复用代码 ❌ **陷阱3:缺少风险评估** - 问题:未考虑兼容性和性能影响 - 解决:使用风险评估清单,提前识别风险 ❌ **陷阱4:任务拆分不合理** - 问题:任务太粗或太细,难以执行和跟踪 - 解决:按 2-4 小时粒度拆分,遵循 SMART 原则 --- ## 11. 项目特定规范(ai-proj) ### 11.1 后端目录结构 ``` backend/ ├── models/ # GORM 模型(单一职责) ├── database/ # Repository(数据访问) ├── services/ # Service(业务逻辑) ├── handlers/ # HTTP Handler(请求处理) ├── routes/ # 路由注册 ├── middleware/ # 中间件(Auth, CORS, Logger) ├── migrations/ # SQL 迁移脚本 ├── utils/ # 工具函数 └── config/ # 配置管理 ``` ### 11.2 命名规范 **后端(Go)**: - 文件:小写下划线(`manual_handler.go`) - 结构体:大驼峰(`ManualHandler`) - 接口:大驼峰(`ManualRepository`) - 方法:大驼峰(`GetManualByID`) - 变量:小驼峰(`manualID`) **前端(TypeScript)**: - 文件:大驼峰(`ManualListPage.tsx`) - 组件:大驼峰(`ManualCard`) - 接口:大驼峰(`Manual`) - 函数:小驼峰(`getManual`) - 变量:小驼峰(`manualList`) **iOS(Swift)**: - 文件:大驼峰(`ManualListView.swift`) - 类/结构体:大驼峰(`ManualListViewModel`) - 协议:大驼峰 + Protocol(`ManualServiceProtocol`) - 函数:小驼峰(`loadManuals()`) - 变量:小驼峰(`isLoading`) - 枚举 case:小驼峰(`case draft`) ### 11.3 Git 提交规范 ``` feat(manual): add manual publishing feature (REQ-YYYYMMDD-XXXX) fix(manual): fix manual list page routing issue (REQ-YYYYMMDD-XXXX) refactor(manual): optimize manual query performance (REQ-YYYYMMDD-XXXX) docs(manual): update manual API documentation (REQ-YYYYMMDD-XXXX) test(manual): add manual handler unit tests (REQ-YYYYMMDD-XXXX) ``` **格式**:`(): ()` ---