Browse Source

完善股权穿透

wcc 3 months ago
parent
commit
a286c58658
4 changed files with 298 additions and 6 deletions
  1. BIN
      graph/graph-http
  2. 2 2
      graph/graph_test.go
  3. 24 3
      graph/main.go
  4. 272 1
      graph/utils.go

BIN
graph/graph-http


+ 2 - 2
graph/graph_test.go

@@ -12,9 +12,9 @@ func TestCheckInvestRelation(t *testing.T) {
 	}
 	defer pool.Close()
 	defer session.Release()
-	names := []string{"北京剑鱼信息技术有限公司", "河南折扣牛哟有限公司", "南京三六五网络公司", "上海元藩投资有限公司"}
+	names := []string{"华芳创业投资有限公司", "北京剑鱼信息技术有限公司", "河南折扣牛哟有限公司", "上海元藩投资有限公司"}
 	//res, err := CheckLegalRelationsGraph(session, names, 3)
-	has, res, err := CheckLegalRelationships2(session, names, 3, 1)
+	has, res, err := CheckLegalRelationships4(session, names, 3, 1)
 	if err != nil {
 		log.Println(res, err, has)
 	}

+ 24 - 3
graph/main.go

@@ -98,6 +98,12 @@ type CheckRequest struct {
 	Stype int      `json:"stype"` //0.简易模式,匹配到直接返回;1.匹配完所有的数据
 }
 
+type CheckResponse struct {
+	Code int      `json:"code"`
+	Data []string `json:"data"`
+	Msg  string   `json:"msg"`
+}
+
 func main() {
 	//InitMgo()
 	//dda()
@@ -125,13 +131,28 @@ func main() {
 			return
 		}
 
-		_, results, err := CheckLegalRelationships(session, req.Names, req.Deep, req.Stype)
+		has, results, err := CheckLegalRelationships4(session, req.Names, req.Deep, req.Stype)
 		if err != nil {
-			c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败", "details": err.Error()})
+			res := CheckResponse{
+				Code: -1,
+				Data: results,
+				Msg:  "请求失败",
+			}
+			c.JSON(http.StatusInternalServerError, res)
 			return
 		}
 
-		c.JSON(http.StatusOK, results)
+		res := CheckResponse{
+			Code: 200,
+			Data: results,
+		}
+		if has {
+			res.Msg = "存在投资关系"
+		} else {
+			res.Msg = "不存在投资关系"
+		}
+
+		c.JSON(http.StatusOK, res)
 	})
 
 	// 启动服务

+ 272 - 1
graph/utils.go

@@ -606,7 +606,8 @@ RETURN p LIMIT 1
 				}
 
 				if len(pathNames) >= 2 {
-					allPaths = append(allPaths, strings.Join(pathNames, " -> "))
+					dd := strings.Join(pathNames, " -> ")
+					allPaths = append(allPaths, dd)
 					if stype == 0 {
 						return true, allPaths, nil
 					}
@@ -652,3 +653,273 @@ FETCH PROP ON Legal "%s" YIELD Legal.name, Legal.code;`, Table_Space, vid)
 	}
 	return info, nil
 }
+
+// CheckLegalRelationships3 逻辑正确,但是存咋重复数据,还有数据路径存在包含的关系
+func CheckLegalRelationships3(session *nebula.Session, names []string, deep, stype int) (bool, []string, error) {
+	if len(names) < 2 {
+		return false, nil, fmt.Errorf("企业数量不足,至少需要两个")
+	}
+
+	var allPaths []string
+
+	for i := 0; i < len(names); i++ {
+		start := names[i]
+
+		// 构造剩下的企业列表作为目标
+		targets := []string{}
+		for j := 0; j < len(names); j++ {
+			if i != j {
+				targets = append(targets, fmt.Sprintf(`"%s"`, names[j]))
+			}
+		}
+		targetList := strings.Join(targets, ", ")
+
+		// 构造查询语句
+		query := fmt.Sprintf(`
+USE %s;
+MATCH p=(a:Legal{name:"%s"})-[*1..%d]-(b:Legal)
+WHERE b.Legal.name IN [%s]
+RETURN p LIMIT 1
+`, Table_Space, start, deep, targetList)
+
+		resp, err := session.Execute(query)
+		if err != nil {
+			return false, nil, fmt.Errorf("查询失败: %w", err)
+		}
+		if !resp.IsSucceed() {
+			return false, nil, fmt.Errorf("查询执行失败: %s", resp.GetErrorMsg())
+		}
+
+		if resp.GetRowSize() > 0 {
+			for _, row := range resp.GetRows() {
+				if len(row.Values) == 0 {
+					continue
+				}
+				val := row.Values[0]
+				if !val.IsSetPVal() {
+					continue
+				}
+
+				path := val.GetPVal()
+				var builder strings.Builder
+
+				// 获取起点名称
+				curName := ""
+				srcVertex := path.Src
+				if srcVertex != nil && srcVertex.Vid != nil && srcVertex.Vid.IsSetSVal() {
+					vid := string(srcVertex.Vid.GetSVal())
+					lea, err := getLegalByVid(session, vid)
+					if err != nil {
+						log.Println("getLegalByVid err:", err, vid)
+					} else {
+						curName = lea.Name
+					}
+				}
+				builder.WriteString(curName)
+
+				// 遍历路径步骤,考虑方向
+				for _, step := range path.Steps {
+					dstName := ""
+					if step.Dst != nil && step.Dst.Vid != nil && step.Dst.Vid.IsSetSVal() {
+						vid := string(step.Dst.Vid.GetSVal())
+						lea, err := getLegalByVid(session, vid)
+						if err != nil {
+							log.Println("getLegalByVid err:", err, vid)
+						} else {
+							dstName = lea.Name
+						}
+					}
+
+					// 判断方向
+					if step.Type > 0 {
+						// 正向边:cur → dst
+						builder.WriteString(" → ")
+						builder.WriteString(dstName)
+						curName = dstName
+					} else if step.Type < 0 {
+						// 反向边:cur ← dst(即 dst → cur)
+						builder.WriteString(" ← ")
+						builder.WriteString(dstName)
+						curName = dstName
+					} else {
+						// 无法判断方向
+						builder.WriteString(" - ")
+						builder.WriteString(dstName)
+						curName = dstName
+					}
+				}
+
+				strPath := builder.String()
+				if strings.Count(strPath, "→")+strings.Count(strPath, "←") >= 1 {
+					allPaths = append(allPaths, strPath)
+					if stype == 0 {
+						return true, allPaths, nil
+					}
+				}
+			}
+		}
+	}
+
+	if len(allPaths) > 0 {
+		return true, allPaths, nil
+	}
+	return false, nil, nil
+}
+
+// ----------//
+// 判断 pathA 是否是 pathB 的子路径(正向完全包含)
+func isSubPath(pathA, pathB []string) bool {
+	if len(pathA) >= len(pathB) {
+		return false
+	}
+	for i := 0; i <= len(pathB)-len(pathA); i++ {
+		match := true
+		for j := 0; j < len(pathA); j++ {
+			if pathA[j] != pathB[i+j] {
+				match = false
+				break
+			}
+		}
+		if match {
+			return true
+		}
+	}
+	return false
+}
+
+// 返回路径唯一 key(方向不敏感)
+func generatePathKey(path []string) string {
+	// 保证方向一致性,字典序小的作为 key
+	normal := strings.Join(path, "->")
+	reversed := strings.Join(reverseSlice(path), "->")
+	if normal < reversed {
+		return normal
+	}
+	return reversed
+}
+
+// 反转字符串切片
+func reverseSlice(slice []string) []string {
+	reversed := make([]string, len(slice))
+	for i, v := range slice {
+		reversed[len(slice)-1-i] = v
+	}
+	return reversed
+}
+
+// CheckLegalRelationships4  CheckLegalRelationships4
+func CheckLegalRelationships4(session *nebula.Session, names []string, deep, stype int) (bool, []string, error) {
+	if len(names) < 2 {
+		return false, nil, fmt.Errorf("企业数量不足,至少需要两个")
+	}
+
+	var rawNodeLists [][]string
+
+	for i := 0; i < len(names); i++ {
+		start := names[i]
+
+		// 构造剩下的企业列表作为目标
+		targets := []string{}
+		for j := 0; j < len(names); j++ {
+			if i != j {
+				targets = append(targets, fmt.Sprintf(`"%s"`, names[j]))
+			}
+		}
+		targetList := strings.Join(targets, ", ")
+
+		// 构造查询语句
+		query := fmt.Sprintf(`
+USE %s;
+MATCH p=(a:Legal{name:"%s"})-[*1..%d]-(b:Legal)
+WHERE b.Legal.name IN [%s]
+RETURN p LIMIT 1
+`, Table_Space, start, deep, targetList)
+
+		resp, err := session.Execute(query)
+		if err != nil {
+			return false, nil, fmt.Errorf("查询失败: %w", err)
+		}
+		if !resp.IsSucceed() {
+			return false, nil, fmt.Errorf("查询执行失败: %s", resp.GetErrorMsg())
+		}
+
+		if resp.GetRowSize() > 0 {
+			for _, row := range resp.GetRows() {
+				if len(row.Values) == 0 {
+					continue
+				}
+				val := row.Values[0]
+				if !val.IsSetPVal() {
+					continue
+				}
+
+				path := val.GetPVal()
+				var pathNames []string
+
+				// 起点
+				srcVertex := path.Src
+				if srcVertex != nil && srcVertex.Vid != nil && srcVertex.Vid.IsSetSVal() {
+					vid := string(srcVertex.Vid.GetSVal())
+					lea, err := getLegalByVid(session, vid)
+					if err != nil {
+						log.Println("getVidByName err", err, vid)
+					}
+					pathNames = append(pathNames, lea.Name)
+				}
+
+				// 解析带方向的路径
+				for _, step := range path.Steps {
+					dstVertex := step.Dst
+					if dstVertex != nil && dstVertex.Vid != nil && dstVertex.Vid.IsSetSVal() {
+						vid := string(dstVertex.Vid.GetSVal())
+						lea, err := getLegalByVid(session, vid)
+						if err != nil {
+							log.Println("getVidByName err", err, vid)
+						}
+						pathNames = append(pathNames, lea.Name)
+					}
+				}
+
+				if len(pathNames) >= 2 {
+					rawNodeLists = append(rawNodeLists, pathNames)
+					if stype == 0 {
+						return true, []string{strings.Join(pathNames, " → ")}, nil
+					}
+				}
+			}
+		}
+	}
+
+	// 去除重复、包含的路径(保留最长链条)
+	uniquePathMap := map[string][]string{}
+
+	for _, nodes := range rawNodeLists {
+		key := generatePathKey(nodes)
+
+		shouldAdd := true
+		for k, existing := range uniquePathMap {
+			if isSubPath(nodes, existing) || isSubPath(reverseSlice(nodes), existing) {
+				shouldAdd = false
+				break
+			}
+			if isSubPath(existing, nodes) {
+				delete(uniquePathMap, k)
+			}
+		}
+
+		if shouldAdd {
+			uniquePathMap[key] = nodes
+		}
+	}
+
+	var result []string
+	for _, path := range uniquePathMap {
+		result = append(result, strings.Join(path, " → "))
+	}
+
+	if len(result) > 0 {
+		return true, result, nil
+	}
+
+	return false, nil, nil
+}