# 设计断链修复计划 **文档版本**: v1.0 **编写日期**: 2026-04-01 **目的**: 修复当前项目的设计断链问题,确保前后端设计闭环 --- ## 一、当前设计断链清单 ### 1.1 优先级分类 **P0 - 严重断链(必须立即修复)** | ID | 断链类型 | 功能名称 | 影响 | 修复工作量 | |----|---------|---------|------|----------| | GAP-FE-001 | 前端缺失 | 管理员管理页 | 管理员无法通过后台管理管理员 | 3天 | | GAP-FE-002 | 前端缺失 | 系统设置页 | 系统配置无法管理 | 4天 | | GAP-FE-003 | 前端缺失 | 全局设备管理页 | 设备信息无法全局管理 | 3天 | | GAP-FE-004 | 前端缺失 | 登录日志导出 | 无法导出登录日志 | 1天 | | GAP-BE-001 | 后端缺失 | 系统设置API | 系统设置功能无法实现 | 3天 | | GAP-INT-001 | 接线缺失 | 设备信任检查 | 设备信任功能不生效 | 2天 | | GAP-INT-002 | 接线缺失 | 角色继承权限 | 角色继承功能不生效 | 2天 | **P1 - 中等断链(当前Sprint修复)** | ID | 断链类型 | 功能名称 | 影响 | 修复工作量 | |----|---------|---------|------|----------| | GAP-FE-005 | 前端缺失 | 批量操作(用户管理) | 批量删除/操作效率低 | 2天 | | GAP-INT-003 | 接线缺失 | 异常检测接入 | 异常检测功能不生效 | 2天 | | GAP-INT-004 | 接线缺失 | 密码历史记录检查 | 密码重复使用防护不生效 | 1天 | **P2 - 轻微断链(下一Sprint修复)** | ID | 断链类型 | 功能名称 | 影响 | 修复工作量 | |----|---------|---------|------|----------| | GAP-INT-005 | 接线缺失 | IP地理位置解析 | 异地登录检测不精确 | 1天 | | GAP-INT-006 | 接线缺失 | 设备指纹采集 | 设备识别不准确 | 1天 | ### 1.2 断链分布统计 ``` ┌─────────────────────────────────────────────────────────────┐ │ 设计断链分布统计 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 前端缺失: 4个 (管理员管理、系统设置、全局设备管理、导出) │ │ 后端缺失: 1个 (系统设置API) │ │ 接线缺失: 6个 (设备信任、角色继承、异常检测等) │ │ │ │ P0断链: 7个 │ │ P1断链: 3个 │ │ P2断链: 2个 │ │ │ │ 总修复工作量: 约30天 │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 二、修复计划 ### 2.1 修复优先级排序 **Sprint 12 (当前,剩余10天)** | ID | 断链名称 | 优先级 | 负责人 | 计划完成 | |----|---------|--------|--------|---------| | GAP-BE-001 | 系统设置API | P0 | 后端A | 04-03 | | GAP-INT-001 | 设备信任检查 | P0 | 后端A | 04-04 | | GAP-INT-002 | 角色继承权限 | P0 | 后端A | 04-05 | | GAP-INT-004 | 密码历史检查 | P1 | 后端A | 04-06 | **Sprint 13 (下周,14天)** | ID | 断链名称 | 优先级 | 负责人 | 计划完成 | |----|---------|--------|--------|---------| | GAP-FE-001 | 管理员管理页 | P0 | 前端A | 04-08 | | GAP-FE-002 | 系统设置页 | P0 | 前端A | 04-10 | | GAP-FE-003 | 全局设备管理页 | P0 | 前端A | 04-12 | | GAP-FE-004 | 登录日志导出 | P0 | 前端A | 04-13 | | GAP-INT-003 | 异常检测接入 | P1 | 后端A | 04-15 | | GAP-FE-005 | 批量操作 | P1 | 前端A | 04-17 | **Sprint 14 (下下周,14天)** | ID | 断链名称 | 优先级 | 负责人 | 计划完成 | |----|---------|--------|--------|---------| | GAP-INT-005 | IP地理位置解析 | P2 | 后端A | 04-22 | | GAP-INT-006 | 设备指纹采集 | P2 | 前端A | 04-23 | ### 2.2 详细修复方案 #### GAP-BE-001: 系统设置API **问题描述** - 前端系统设置页需要后端API支持 - 当前后端无系统设置相关接口 **修复方案** **1. 数据库设计** ```sql -- 系统设置表 CREATE TABLE system_configs ( id BIGINT PRIMARY KEY AUTO_INCREMENT, config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键', config_value TEXT NOT NULL COMMENT '配置值', config_type ENUM('string', 'number', 'boolean', 'json') NOT NULL DEFAULT 'string' COMMENT '配置类型', category VARCHAR(50) NOT NULL COMMENT '配置分类', description VARCHAR(255) COMMENT '配置描述', is_public TINYINT(1) DEFAULT 0 COMMENT '是否公开(前端可见)', is_editable TINYINT(1) DEFAULT 1 COMMENT '是否可编辑', default_value TEXT COMMENT '默认值', validation_rule VARCHAR(255) COMMENT '验证规则', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, created_by BIGINT COMMENT '创建人ID', updated_by BIGINT COMMENT '更新人ID', INDEX idx_category (category), INDEX idx_key (config_key) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表'; -- 初始化默认配置 INSERT INTO system_configs (config_key, config_value, config_type, category, description, is_public, is_editable, default_value) VALUES ('system.name', 'UMS', 'string', 'system', '系统名称', 1, 1, 'UMS'), ('system.logo_url', '', 'string', 'system', '系统Logo URL', 1, 1, ''), ('system.timezone', 'Asia/Shanghai', 'string', 'system', '系统时区', 1, 1, 'Asia/Shanghai'), ('system.language', 'zh-CN', 'string', 'system', '系统语言', 1, 1, 'zh-CN'), ('auth.password_min_length', '8', 'number', 'auth', '密码最小长度', 1, 1, '8'), ('auth.password_max_age_days', '90', 'number', 'auth', '密码有效期(天)', 1, 1, '90'), ('auth.session_timeout_minutes', '30', 'number', 'auth', '会话超时时间(分钟)', 1, 1, '30'), ('auth.enable_totp', 'true', 'boolean', 'auth', '启用双因素认证', 1, 1, 'true'), ('auth.enable_device_trust', 'true', 'boolean', 'auth', '启用设备信任', 1, 1, 'true'), ('auth.device_trust_duration_days', '30', 'number', 'auth', '设备信任有效期(天)', 1, 1, '30'), ('notification.email_enabled', 'true', 'boolean', 'notification', '启用邮件通知', 1, 1, 'true'), ('notification.sms_enabled', 'false', 'boolean', 'notification', '启用短信通知', 1, 1, 'false'), ('security.max_login_attempts', '5', 'number', 'security', '最大登录尝试次数', 1, 1, '5'), ('security.login_lockout_minutes', '30', 'number', 'security', '登录锁定时间(分钟)', 1, 1, '30'), ('logging.log_level', 'info', 'string', 'logging', '日志级别', 1, 1, 'info'), ('logging.log_retention_days', '30', 'number', 'logging', '日志保留天数', 1, 1, '30'); ``` **2. Domain模型** ```go // internal/domain/system_config.go package domain import "time" type SystemConfig struct { ID int64 `json:"id"` ConfigKey string `json:"config_key"` ConfigValue string `json:"config_value"` ConfigType string `json:"config_type"` // string, number, boolean, json Category string `json:"category"` Description string `json:"description"` IsPublic bool `json:"is_public"` IsEditable bool `json:"is_editable"` DefaultValue string `json:"default_value"` ValidationRule string `json:"validation_rule"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` CreatedBy *int64 `json:"created_by,omitempty"` UpdatedBy *int64 `json:"updated_by,omitempty"` } type SystemConfigUpdate struct { ConfigValue string `json:"config_value"` UpdatedBy int64 `json:"updated_by"` } ``` **3. Repository层** ```go // internal/repository/system_config.go package repository import ( "context" "d:/project/internal/domain" "gorm.io/gorm" ) type SystemConfigRepository struct { db *gorm.DB } func NewSystemConfigRepository(db *gorm.DB) *SystemConfigRepository { return &SystemConfigRepository{db: db} } func (r *SystemConfigRepository) GetByCategory(ctx context.Context, category string) ([]*domain.SystemConfig, error) { var configs []*domain.SystemConfig err := r.db.WithContext(ctx). Where("category = ?", category). Find(&configs).Error return configs, err } func (r *SystemConfigRepository) GetByKey(ctx context.Context, key string) (*domain.SystemConfig, error) { var config domain.SystemConfig err := r.db.WithContext(ctx). Where("config_key = ?", key). First(&config).Error if err != nil { return nil, err } return &config, nil } func (r *SystemConfigRepository) GetAllPublic(ctx context.Context) ([]*domain.SystemConfig, error) { var configs []*domain.SystemConfig err := r.db.WithContext(ctx). Where("is_public = ?", true). Order("category, config_key"). Find(&configs).Error return configs, err } func (r *SystemConfigRepository) GetAll(ctx context.Context) ([]*domain.SystemConfig, error) { var configs []*domain.SystemConfig err := r.db.WithContext(ctx). Order("category, config_key"). Find(&configs).Error return configs, err } func (r *SystemConfigRepository) Update(ctx context.Context, key string, update *domain.SystemConfigUpdate) error { return r.db.WithContext(ctx). Model(&domain.SystemConfig{}). Where("config_key = ? AND is_editable = ?", key, true). Updates(map[string]interface{}{ "config_value": update.ConfigValue, "updated_by": update.UpdatedBy, "updated_at": "NOW()", }).Error } func (r *SystemConfigRepository) GetByKeys(ctx context.Context, keys []string) (map[string]*domain.SystemConfig, error) { var configs []*domain.SystemConfig err := r.db.WithContext(ctx). Where("config_key IN ?", keys). Find(&configs).Error if err != nil { return nil, err } result := make(map[string]*domain.SystemConfig) for _, config := range configs { result[config.ConfigKey] = config } return result, nil } ``` **4. Service层** ```go // internal/service/system_config.go package service import ( "context" "errors" "fmt" "d:/project/internal/domain" "d:/project/internal/repository" "encoding/json" "strconv" ) type SystemConfigService struct { configRepo *repository.SystemConfigRepository } func NewSystemConfigService(configRepo *repository.SystemConfigRepository) *SystemConfigService { return &SystemConfigService{ configRepo: configRepo, } } type ConfigCategory struct { Name string `json:"name"` Label string `json:"label"` Configs []*domain.SystemConfig `json:"configs"` } func (s *SystemConfigService) GetPublicConfigs(ctx context.Context) ([]*ConfigCategory, error) { configs, err := s.configRepo.GetAllPublic(ctx) if err != nil { return nil, err } return s.groupByCategory(configs), nil } func (s *SystemConfigService) GetAllConfigs(ctx context.Context) ([]*ConfigCategory, error) { configs, err := s.configRepo.GetAll(ctx) if err != nil { return nil, err } return s.groupByCategory(configs), nil } func (s *SystemConfigService) GetConfig(ctx context.Context, key string) (*domain.SystemConfig, error) { return s.configRepo.GetByKey(ctx, key) } func (s *SystemConfigService) GetConfigsByKeys(ctx context.Context, keys []string) (map[string]*domain.SystemConfig, error) { return s.configRepo.GetByKeys(ctx, keys) } func (s *SystemConfigService) UpdateConfig(ctx context.Context, key string, value string, userID int64) error { config, err := s.configRepo.GetByKey(ctx, key) if err != nil { return fmt.Errorf("配置不存在: %s", key) } if !config.IsEditable { return fmt.Errorf("配置不允许编辑: %s", key) } // 验证配置值 if err := s.validateConfigValue(config, value); err != nil { return err } update := &domain.SystemConfigUpdate{ ConfigValue: value, UpdatedBy: userID, } return s.configRepo.Update(ctx, key, update) } func (s *SystemConfigService) GetConfigValue(ctx context.Context, key string, defaultValue interface{}) (interface{}, error) { config, err := s.configRepo.GetByKey(ctx, key) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return defaultValue, nil } return nil, err } return s.parseConfigValue(config), nil } func (s *SystemConfigService) GetString(ctx context.Context, key string, defaultValue string) (string, error) { value, err := s.GetConfigValue(ctx, key, defaultValue) if err != nil { return defaultValue, err } if str, ok := value.(string); ok { return str, nil } return defaultValue, nil } func (s *SystemConfigService) GetInt(ctx context.Context, key string, defaultValue int) (int, error) { value, err := s.GetConfigValue(ctx, key, defaultValue) if err != nil { return defaultValue, err } if num, ok := value.(int); ok { return num, nil } return defaultValue, nil } func (s *SystemConfigService) GetBool(ctx context.Context, key string, defaultValue bool) (bool, error) { value, err := s.GetConfigValue(ctx, key, defaultValue) if err != nil { return defaultValue, err } if b, ok := value.(bool); ok { return b, nil } return defaultValue, nil } // 辅助方法 func (s *SystemConfigService) groupByCategory(configs []*domain.SystemConfig) []*ConfigCategory { categoryMap := make(map[string]*ConfigCategory) categoryLabels := map[string]string{ "system": "系统设置", "auth": "认证设置", "notification": "通知设置", "security": "安全设置", "logging": "日志设置", } for _, config := range configs { if _, exists := categoryMap[config.Category]; !exists { categoryMap[config.Category] = &ConfigCategory{ Name: config.Category, Label: categoryLabels[config.Category], } } categoryMap[config.Category].Configs = append(categoryMap[config.Category].Configs, config) } var result []*ConfigCategory for _, category := range categoryMap { result = append(result, category) } return result } func (s *SystemConfigService) validateConfigValue(config *domain.SystemConfig, value string) error { switch config.ConfigType { case "number": _, err := strconv.Atoi(value) if err != nil { return fmt.Errorf("配置值必须是数字: %s", value) } case "boolean": if value != "true" && value != "false" { return fmt.Errorf("配置值必须是 true 或 false: %s", value) } case "json": var js interface{} if err := json.Unmarshal([]byte(value), &js); err != nil { return fmt.Errorf("配置值必须是有效的JSON: %s", value) } } // TODO: 实现 validation_rule 的验证逻辑 return nil } func (s *SystemConfigService) parseConfigValue(config *domain.SystemConfig) interface{} { switch config.ConfigType { case "number": if num, err := strconv.Atoi(config.ConfigValue); err == nil { return num } case "boolean": return config.ConfigValue == "true" case "json": var js interface{} if err := json.Unmarshal([]byte(config.ConfigValue), &js); err == nil { return js } } return config.ConfigValue } ``` **5. Handler层** ```go // internal/api/handler/system_config_handler.go package handler import ( "net/http" "d:/project/internal/service" "github.com/gin-gonic/gin" ) type SystemConfigHandler struct { configService *service.SystemConfigService } func NewSystemConfigHandler(configService *service.SystemConfigService) *SystemConfigHandler { return &SystemConfigHandler{ configService: configService, } } type UpdateConfigRequest struct { ConfigValue string `json:"config_value" binding:"required"` } // GetPublicConfigs 获取公开配置(前端可见) func (h *SystemConfigHandler) GetPublicConfigs(c *gin.Context) { configs, err := h.configService.GetPublicConfigs(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "data": configs, }) } // GetAllConfigs 获取所有配置(管理员) func (h *SystemConfigHandler) GetAllConfigs(c *gin.Context) { configs, err := h.configService.GetAllConfigs(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "data": configs, }) } // UpdateConfig 更新配置 func (h *SystemConfigHandler) UpdateConfig(c *gin.Context) { key := c.Param("key") var req UpdateConfigRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 从上下文获取用户ID userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"}) return } userIDInt, ok := userID.(int64) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "用户ID类型错误"}) return } err := h.configService.UpdateConfig(c.Request.Context(), key, req.ConfigValue, userIDInt) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "message": "配置更新成功", }) } // GetConfig 获取单个配置 func (h *SystemConfigHandler) GetConfig(c *gin.Context) { key := c.Param("key") config, err := h.configService.GetConfig(c.Request.Context(), key) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "配置不存在"}) return } c.JSON(http.StatusOK, gin.H{ "data": config, }) } ``` **6. 路由注册** ```go // internal/api/router/router.go // 添加系统设置路由 configGroup := apiV1.Group("/system") configGroup.Use(middleware.RequireAuth()) configGroup.Use(middleware.RequireAdmin()) // 管理员权限 systemConfigHandler := handler.NewSystemConfigHandler(systemConfigService) configGroup.GET("/configs/public", systemConfigHandler.GetPublicConfigs) configGroup.GET("/configs", systemConfigHandler.GetAllConfigs) configGroup.GET("/configs/:key", systemConfigHandler.GetConfig) configGroup.PUT("/configs/:key", systemConfigHandler.UpdateConfig) ``` **7. 启动注入** ```go // cmd/server/main.go // 初始化系统配置 systemConfigRepo := repository.NewSystemConfigRepository(db.DB) systemConfigService := service.NewSystemConfigService(systemConfigRepo) // 注入到其他需要使用配置的服务中 // authService.SetConfigService(systemConfigService) ``` **验收标准** - [ ] 数据库表创建成功 - [ ] 默认配置初始化成功 - [ ] GET /api/v1/system/configs/public 返回公开配置 - [ ] GET /api/v1/system/configs 返回所有配置(需管理员权限) - [ ] PUT /api/v1/system/configs/:key 更新配置成功 - [ ] 非可编辑配置不允许更新 - [ ] 单元测试覆盖率达到80% - [ ] API文档完整 --- #### GAP-INT-001: 设备信任检查接线 **问题描述** - 设备信任的CRUD API已实现,但登录流程未使用 - 用户信任设备后,登录时仍要求2FA验证 **修复方案** **1. 修改登录请求结构** ```go // internal/service/auth.go type LoginRequest struct { Account string `json:"account" binding:"required"` Password string `json:"password" binding:"required"` Remember bool `json:"remember"` DeviceID string `json:"device_id,omitempty"` DeviceName string `json:"device_name,omitempty"` DeviceOS string `json:"device_os,omitempty"` DeviceBrowser string `json:"device_browser,omitempty"` } ``` **2. 登录时自动注册设备** ```go // internal/service/auth.go func (s *AuthService) generateLoginResponse(ctx context.Context, user *domain.User, req *LoginRequest) (*LoginResponse, error) { // ... token生成逻辑 ... // 自动注册/更新设备记录 if s.deviceRepo != nil && req.DeviceID != "" { s.bestEffortRegisterDevice(ctx, user.ID, req) } // ... 返回逻辑 ... } func (s *AuthService) bestEffortRegisterDevice(ctx context.Context, userID int64, req *LoginRequest) { device := &domain.Device{ UserID: userID, DeviceID: req.DeviceID, DeviceName: req.DeviceName, OS: req.DeviceOS, Browser: req.DeviceBrowser, LastSeenAt: time.Now(), IsTrusted: false, TrustExpiresAt: nil, } // 尝试获取现有设备 existing, err := s.deviceRepo.GetByDeviceID(ctx, userID, req.DeviceID) if err == nil && existing != nil { // 更新设备信息 existing.DeviceName = req.DeviceName existing.OS = req.DeviceOS existing.Browser = req.DeviceBrowser existing.LastSeenAt = time.Now() s.deviceRepo.Update(ctx, existing) } else { // 创建新设备 s.deviceRepo.Create(ctx, device) } } ``` **3. 2FA验证时检查设备信任** ```go // internal/service/auth.go func (s *AuthService) VerifyTOTP(ctx context.Context, req *VerifyTOTPRequest) error { // 检查设备是否已信任 if req.DeviceID != "" && s.deviceRepo != nil { device, err := s.deviceRepo.GetByDeviceID(ctx, req.UserID, req.DeviceID) if err == nil && device != nil && device.IsTrusted { // 检查信任是否过期 if device.TrustExpiresAt == nil || device.TrustExpiresAt.After(time.Now()) { // 设备已信任且未过期,跳过2FA验证 return nil } } } // 正常TOTP验证流程 // ... } ``` **验收标准** - [ ] 登录时自动创建/更新设备记录 - [ ] 设备ID从登录请求中正确提取 - [ ] 信任设备的2FA验证被跳过 - [ ] 信任过期后重新要求2FA - [ ] 单元测试覆盖 --- #### GAP-INT-002: 角色继承权限接线 **问题描述** - 角色继承的Repository和Service已实现 - 但auth middleware未使用继承权限 **修复方案** **1. 修改auth middleware** ```go // internal/api/middleware/auth.go func (m *AuthMiddleware) getUserPermissions(ctx context.Context, userID int64) ([]string, error) { // 现状: 直接查询user_role_permissions表 // 修改: 调用roleService.GetRolePermissions(含继承) userRoles, err := m.userRepo.GetRoles(ctx, userID) if err != nil { return nil, err } var allPermissions []string seen := make(map[string]bool) for _, role := range userRoles { permissions, err := m.roleService.GetRolePermissions(ctx, role.ID) if err != nil { return nil, err } for _, perm := range permissions { if !seen[perm.Code] { seen[perm.Code] = true allPermissions = append(allPermissions, perm.Code) } } } return allPermissions, nil } ``` **2. 修改JWT生成逻辑** ```go // internal/service/auth.go func (s *AuthService) generateLoginResponse(ctx context.Context, user *domain.User, req *LoginRequest) (*LoginResponse, error) { // ... // 获取用户权限(含继承) permissions, err := s.getUserPermissions(ctx, user.ID) if err != nil { return nil, err } // 生成JWT时包含继承权限 token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": user.ID, "username": user.Username, "email": user.Email, "role_ids": user.RoleIDs, "permissions": permissions, "exp": time.Now().Add(tokenExpiry).Unix(), "iat": time.Now().Unix(), }) // ... } ``` **验收标准** - [ ] 用户持子角色能访问父角色权限 - [ ] JWT中的permissions包含继承权限 - [ ] auth middleware正确验证继承权限 - [ ] 单元测试覆盖角色继承场景 --- ### 2.3 前端断链修复(Sprint 13) **将在专家评审后详细设计前端页面** --- ## 三、修复验收标准 ### 3.1 通用验收标准 - [ ] 代码审查通过 - [ ] 单元测试覆盖率 > 80% - [ ] 集成测试通过 - [ ] API文档完整 - [ ] 无已知安全漏洞 - [ ] 性能测试达标 ### 3.2 特定验收标准 **后端API修复** - [ ] API符合RESTful规范 - [ ] 错误处理完整 - [ ] 输入验证完整 - [ ] 权限校验完整 - [ ] 数据库索引合理 **前端页面修复** - [ ] UI/UX符合设计规范 - [ ] 交互流程顺畅 - [ ] 错误提示友好 - [ ] 加载状态清晰 - [ ] 响应式设计良好 --- ## 四、风险与依赖 ### 4.1 技术风险 | 风险 | 影响 | 概率 | 缓解措施 | |------|------|------|---------| | 系统设置API影响范围大 | 高 | 中 | 分阶段发布,先灰度 | | 设备信任逻辑复杂 | 中 | 高 | 充分测试,回滚方案 | | 角色继承权限链路长 | 中 | 中 | 详细测试,代码审查 | ### 4.2 资源风险 | 风险 | 影响 | 概率 | 缓解措施 | |------|------|------|---------| | 前端开发资源紧张 | 高 | 中 | 优先P0功能,延期P2 | | 测试资源不足 | 中 | 中 | 自动化测试,专家评审 | ### 4.3 依赖项 - [ ] Sprint 12后端开发需要2名后端工程师 - [ ] Sprint 13前端开发需要1名前端工程师 - [ ] 专家评审需要各领域专家参与 --- ## 五、进度跟踪 ### 5.1 每日跟踪 ```markdown ## 设计断链修复进度 - 2026-04-01 ### 今日完成 - [x] 完成系统设置API设计 - [x] 完成设备信任检查设计 ### 今日进行中 - [ ] 系统设置API实现 (30%) - [ ] 角色继承权限修复 (20%) ### 今日计划 - [ ] 完成系统设置API Repository层 - [ ] 完成设备信任检查Handler层 - [ ] 开始角色继承权限修复 ### 阻碍 - 无 ### 明日计划 - [ ] 完成系统设置API Service层 - [ ] 完成系统设置API Handler层 - [ ] 完成角色继承权限修复 ``` --- ## 六、总结 本修复计划旨在: 1. ✅ **消除设计断链**: 修复12个设计断链问题 2. ✅ **确保功能完整**: 补齐缺失的页面和API 3. ✅ **提升用户体验**: 实现完整的设备信任和角色继承 4. ✅ **提高系统安全性**: 加强密码和登录安全 预期成果: - 设计断链修复率: 100% - 功能完整性: 95%+ - 用户满意度提升: 30% --- *本文档由高级项目经理 Agent 编制,2026-04-01*