testFlow/api/project/demand/handler.go
Wyle.Gong-巩文昕 67b0ad2723 init
2025-04-22 16:42:48 +08:00

612 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}