package demand import ( "app/cfg" "app/models" "app/models/project" "app/utils" "encoding/json" "errors" "fmt" "io" "sort" "github.com/veypi/OneBD/rest" "gorm.io/gorm" ) var _ = Router.Get("/:id/:min_level/", getHandle) var _ = Router.Get("/:id/", getHandle) var _ = Router.Get("", getHandle) // 获取需求 func getHandle(x *rest.X) (any, error) { // fmt.Println("getHandle") utils.SetCORSHeaders(x) id, ok := x.Params.Get("id") minLevel, hasMinLevel := x.Params.Get("min_level") project_id := id // fmt.Println(project_id) // 获取所有根节点需求 var demands []project.Demand query := cfg.DB() // 如果指定了最小级别,添加筛选条件 if hasMinLevel { minLevelInt := 0 fmt.Sscanf(minLevel, "%d", &minLevelInt) query = query.Where("level >= ?", minLevelInt) } if !ok || id == "-1" { if err := query.Find(&demands).Error; err != nil { fmt.Println(err) return nil, err } // fmt.Println(demands) return demands, nil } // fmt.Println(project_id) if err := query.Where("project_id = ?", project_id).Find(&demands).Error; err != nil { fmt.Println(err) return nil, err } // 1. 先按照level从小到大排序 sort.Slice(demands, func(i, j int) bool { return demands[i].Level < demands[j].Level }) // 2. 进行拓扑排序 // 构建节点映射和依赖关系图 nodeMap := make(map[string]*project.Demand) for i := range demands { nodeMap[demands[i].ID] = &demands[i] } // 构建邻接表 graph := make(map[string][]string) for _, node := range demands { if node.ParentID != "-1" && node.ParentID != "" { // 父节点是子节点的依赖 if _, exists := nodeMap[node.ParentID]; exists { graph[node.ID] = append(graph[node.ID], node.ParentID) } } } // 拓扑排序 var result []project.Demand visited := make(map[string]bool) temp := make(map[string]bool) var visit func(string) bool visit = func(id string) bool { if temp[id] { // 检测到循环依赖,跳过 return false } if visited[id] { return true } temp[id] = true for _, dep := range graph[id] { if !visit(dep) { return false } } temp[id] = false visited[id] = true result = append(result, *nodeMap[id]) return true } // 对每个节点执行拓扑排序 for _, node := range demands { if !visited[node.ID] { visit(node.ID) } } // 如果拓扑排序成功,使用排序后的结果 if len(result) == len(demands) { demands = result } // 否则保持按level排序的结果 return demands, nil } var _ = Router.Get("/:id/descendants", getDescendantsHandle) // 获取所有后代节点 func getDescendantsHandle(x *rest.X) (any, error) { utils.SetCORSHeaders(x) id, ok := x.Params.Get("id") if !ok { return nil, errors.New("缺少id") } // 获取特定节点 var demand project.Demand if err := cfg.DB().Where("id = ?", id).First(&demand).Error; err != nil { return nil, err } // 获取所有子孙节点 descendants, err := demand.Descendants(cfg.DB()) if err != nil { return nil, err } return map[string]interface{}{ "node": demand, "descendants": descendants, }, nil } // 创建需求 var _ = Router.Post("/:id/", postHandle) func postHandle(x *rest.X) (any, error) { utils.SetCORSHeaders(x) projectID, hasprojectID := x.Params.Get("id") if !hasprojectID { return nil, errors.New("缺少project_id") } demand := &project.Demand{} if err := json.NewDecoder(x.Request.Body).Decode(&demand); err != nil { return nil, err } fmt.Printf("demand: %v\n", demand) fmt.Printf("demand.parentid: %v\n", demand.ParentID) // 设置文档ID和UUID demand.ProjectID = projectID // demand.ID = uuid.New().String()[0:32] // 设置级别 if demand.ParentID == "-1" || demand.ParentID == "" { demand.Level = 0 } else { var parent project.Demand if err := cfg.DB().Where("id = ? AND project_id = ?", demand.ParentID, projectID).First(&parent).Error; err != nil { return nil, fmt.Errorf("父节点不存在或不属于当前文档: %v", err) } demand.Level = parent.Level + 1 } if err := cfg.DB().Create(&demand).Error; err != nil { return nil, err } return demand, nil } var _ = Router.Patch("/:id/", patchHandle) // 更新需求 func patchHandle(x *rest.X) (any, error) { utils.SetCORSHeaders(x) body, err := io.ReadAll(x.Request.Body) if err != nil { return nil, fmt.Errorf("读取请求体失败: %v", err) } id, ok := x.Params.Get("id") if !ok { return nil, errors.New("缺少id") } var updateMap map[string]interface{} if err := json.Unmarshal(body, &updateMap); err != nil { return nil, fmt.Errorf("解析JSON失败: %v", err) } // 先检查需求是否存在 var demand project.Demand if err := cfg.DB().Where("id = ?", id).First(&demand).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("需求不存在") } return nil, err } // 如果更新包含parent_id,需要特殊处理层级 if parentID, ok := updateMap["parent_id"]; ok { newParentID, ok := parentID.(string) if !ok { return nil, errors.New("parent_id 必须是字符串类型") } // 检查是否形成循环引用 if newParentID == demand.ID { return nil, errors.New("不能将节点的父节点设置为自身") } if newParentID != demand.ParentID { var newLevel int // 检查新的父节点是否存在(除非是设置为根节点) if newParentID != "-1" && newParentID != "" { var parent project.Demand if err := cfg.DB().Where("id = ?", newParentID).First(&parent).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("父节点不存在") } return nil, err } // 检查新父节点是否是当前节点的子节点 children, err := demand.Children(cfg.DB()) if err != nil { return nil, err } for _, child := range children { if child.ID == newParentID { return nil, errors.New("不能将子节点设置为父节点") } } newLevel = parent.Level + 1 } else { newLevel = 0 } updateMap["level"] = newLevel } } // 删除不允许更新的字段 delete(updateMap, "id") delete(updateMap, "project_id") // 只更新提供的字段 if len(updateMap) > 0 { if err := cfg.DB().Model(&demand).Updates(updateMap).Error; err != nil { return nil, err } } // 重新查询更新后的完整数据 if err := cfg.DB().Where("id = ?", id).First(&demand).Error; err != nil { return nil, err } return demand, nil } var _ = Router.Delete("/:id/", deleteHandle) // 删除需求 func deleteHandle(x *rest.X) (any, error) { utils.SetCORSHeaders(x) id, ok := x.Params.Get("id") if !ok { return nil, errors.New("缺少id") } var demand project.Demand if err := cfg.DB().Where("id = ?", id).First(&demand).Error; err != nil { return nil, err } // 删除需求及其所有子需求 descendants, err := demand.Descendants(cfg.DB()) if err != nil { return nil, err } tx := cfg.DB().Begin() // 删除所有子需求 for _, desc := range descendants { if err := tx.Delete(&desc).Error; err != nil { tx.Rollback() return nil, err } } // 删除当前需求 if err := tx.Delete(&demand).Error; err != nil { tx.Rollback() return nil, err } tx.Commit() return map[string]interface{}{ "msg": "删除成功", }, nil } // 在现有代码后添加新的接口处理函数 var _ = Router.Post("/:id/merge", mergeNodesHandle) // 合并多个节点到目标节点 func mergeNodesHandle(x *rest.X) (any, error) { utils.SetCORSHeaders(x) // 获取目标项目ID docID, hasDocID := x.Params.Get("id") if !hasDocID { return nil, errors.New("缺少文档ID") } // 通过文档ID查询项目ID var doc models.Doc if err := cfg.DB().Where("id = ?", docID).First(&doc).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("文档不存在") } return nil, fmt.Errorf("查询文档失败: %v", err) } // 获取项目ID projectID := doc.ProjectID // 解析请求体 var mergeRequest struct { TargetNodeID string `json:"target_node_id"` // 目标节点ID SourceNodeIDs []string `json:"source_node_ids"` // 要合并的源节点ID列表 } if err := json.NewDecoder(x.Request.Body).Decode(&mergeRequest); err != nil { return nil, fmt.Errorf("解析请求体失败: %v", err) } // 验证请求参数 if mergeRequest.TargetNodeID == "" { return nil, errors.New("缺少目标节点ID") } if len(mergeRequest.SourceNodeIDs) == 0 { return nil, errors.New("缺少源节点ID列表") } // 验证目标节点是否存在且属于当前项目 var targetNode project.Demand if mergeRequest.TargetNodeID != "-1" { // 如果不是根节点 if err := cfg.DB().Where("id = ? AND project_id = ?", mergeRequest.TargetNodeID, projectID).First(&targetNode).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("目标节点不存在或不属于当前项目") } return nil, err } } // 开始事务 tx := cfg.DB().Begin() // 1. 获取所有选中的节点及其关系 sourceNodes := make(map[string]project.Demand) for _, sourceID := range mergeRequest.SourceNodeIDs { var origin models.Demand if err := tx.Where("id = ?", sourceID).First(&origin).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("源节点 %s 不存在: %v", sourceID, err) } sourceNodes[sourceID] = project.Demand{ Tree: models.Tree{ Name: origin.Name, ParentID: origin.ParentID, Level: origin.Level, }, ProjectID: projectID, // 使用当前项目ID Description: origin.Description, ReqID: origin.ReqID, ParentReqID: origin.ParentReqID, Priority: origin.Priority, Type: origin.Type, Status: origin.Status, } } // 2. 构建节点间的依赖关系图 dependencies := make(map[string][]string) // 子节点 -> 父节点 for id, node := range sourceNodes { if node.ParentID != "-1" && node.ParentID != "" { // 如果父节点也在选中列表中,记录依赖关系 if _, exists := sourceNodes[node.ParentID]; exists { dependencies[id] = append(dependencies[id], node.ParentID) } } } // 3. 拓扑排序 var sortedIDs []string visited := make(map[string]bool) temp := make(map[string]bool) var visit func(string) error visit = func(id string) error { if temp[id] { return fmt.Errorf("检测到循环依赖") } if visited[id] { return nil } temp[id] = true for _, dep := range dependencies[id] { if err := visit(dep); err != nil { return err } } temp[id] = false visited[id] = true sortedIDs = append(sortedIDs, id) return nil } // 对每个节点执行拓扑排序 for id := range sourceNodes { if !visited[id] { if err := visit(id); err != nil { tx.Rollback() return nil, err } } } // 4. 创建新节点映射表,用于跟踪旧ID到新ID的映射 idMapping := make(map[string]string) // 5. 按照拓扑排序的顺序创建节点 var processedNodes []project.Demand for _, sourceID := range sortedIDs { sourceNode := sourceNodes[sourceID] // 创建新节点 newNode := project.Demand{ Description: sourceNode.Description, ReqID: sourceNode.ReqID, ParentReqID: sourceNode.ParentReqID, Priority: sourceNode.Priority, Type: sourceNode.Type, Status: sourceNode.Status, ProjectID: projectID, Tree: models.Tree{ Name: sourceNode.Name, }, } // 设置父节点ID if sourceNode.ParentID == "-1" || sourceNode.ParentID == "" { // 如果原节点是根节点 if mergeRequest.TargetNodeID == "-1" { newNode.ParentID = "-1" newNode.Level = 0 } else { newNode.ParentID = mergeRequest.TargetNodeID newNode.Level = targetNode.Level + 1 } } else { // 检查父节点是否在选中列表中 _, parentExists := sourceNodes[sourceNode.ParentID] if !parentExists { // 如果父节点不在选中列表中 if mergeRequest.TargetNodeID == "-1" { newNode.ParentID = "-1" newNode.Level = 0 } else { newNode.ParentID = mergeRequest.TargetNodeID newNode.Level = targetNode.Level + 1 } } else { // 如果原节点的父节点在选中列表中,使用新的父节点ID newParentID, exists := idMapping[sourceNode.ParentID] if !exists { tx.Rollback() return nil, fmt.Errorf("找不到节点 %s 的新父节点ID", sourceID) } newNode.ParentID = newParentID // 查找新父节点的级别 var parentNode project.Demand if err := tx.Where("id = ?", newParentID).First(&parentNode).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("找不到新父节点: %v", err) } newNode.Level = parentNode.Level + 1 } } // 保存新节点 if err := tx.Create(&newNode).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("创建新节点失败: %v", err) } // 记录ID映射 idMapping[sourceID] = newNode.ID processedNodes = append(processedNodes, newNode) } // 6. 处理未选中的子节点 for _, sourceID := range sortedIDs { // 获取新节点ID newParentID := idMapping[sourceID] // 获取原节点的所有子节点 var children []project.Demand if err := tx.Where("parent_id = ?", sourceID).Find(&children).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("获取子节点失败: %v", err) } // 处理未被选中的子节点 for _, child := range children { if _, selected := sourceNodes[child.ID]; !selected { // 递归复制未选中的子树 if err := copySubtree(tx, child, newParentID, projectID); err != nil { tx.Rollback() return nil, err } } } } // 提交事务 if err := tx.Commit().Error; err != nil { return nil, fmt.Errorf("提交事务失败: %v", err) } return map[string]interface{}{ "message": fmt.Sprintf("成功合并 %d 个节点", len(mergeRequest.SourceNodeIDs)), "nodes": processedNodes, }, nil } // 复制子树 func copySubtree(tx *gorm.DB, node project.Demand, newParentID, projectID string) error { // 创建新节点 newNode := project.Demand{ Description: node.Description, ReqID: node.ReqID, ParentReqID: node.ParentReqID, Priority: node.Priority, Type: node.Type, Status: node.Status, ProjectID: projectID, Tree: models.Tree{ ParentID: newParentID, Name: node.Name, }, } // 查找新父节点的级别 var parentNode project.Demand if err := tx.Where("id = ?", newParentID).First(&parentNode).Error; err != nil { return fmt.Errorf("找不到新父节点: %v", err) } newNode.Level = parentNode.Level + 1 // 保存新节点 if err := tx.Create(&newNode).Error; err != nil { return fmt.Errorf("创建节点失败: %v", err) } // 获取原节点的所有子节点 var children []project.Demand if err := tx.Where("parent_id = ?", node.ID).Find(&children).Error; err != nil { return fmt.Errorf("获取子节点失败: %v", err) } // 递归处理子节点 for _, child := range children { if err := copySubtree(tx, child, newNode.ID, projectID); err != nil { return err } } return nil } var _ = Router.Any("/*", anyHandle) func anyHandle(x *rest.X) (any, error) { utils.SetCORSHeaders(x) return nil, nil }