This commit is contained in:
Wyle.Gong-巩文昕
2025-04-22 16:42:48 +08:00
commit 67b0ad2723
95 changed files with 10508 additions and 0 deletions
+611
View File
@@ -0,0 +1,611 @@
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
}
+11
View File
@@ -0,0 +1,11 @@
package demand
import (
"github.com/veypi/OneBD/rest"
)
var Router = rest.NewRouter()
func init() {
// 初始化路由
}