612 lines
15 KiB
Go
612 lines
15 KiB
Go
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
|
||
|
||
}
|