123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233 |
- package main
- import (
- "fmt"
- nebula "github.com/vesoft-inc/nebula-go/v3"
- "log"
- "strings"
- )
- // 表示企业关系结果
- //type AllRelationResult struct {
- // RelatedCompanies []string // 有关系的企业列表
- // Paths []string // 对应的路径
- //}
- //
- //// 批量获取企业的 VID
- //func getVidsByName(session *nebula.Session, names []string) (map[string]string, error) {
- // if len(names) == 0 {
- // return nil, nil
- // }
- // conditions := ""
- // for i, name := range names {
- // if i > 0 {
- // conditions += " OR "
- // }
- // conditions += fmt.Sprintf("Legal.name == \"%s\"", name)
- // }
- // query := fmt.Sprintf(`
- //USE %s;
- //LOOKUP ON Legal WHERE %s YIELD id(vertex) AS vid, properties(vertex).name AS name
- //`, Table_Space, conditions)
- // resp, err := session.Execute(query)
- // if err != nil {
- // return nil, err
- // }
- // nameToVid := make(map[string]string)
- // for _, row := range resp.GetRows() {
- // if len(row.Values) >= 2 {
- // if row.Values[0].SVal != nil && row.Values[1].SVal != nil {
- // nameToVid[string(row.Values[1].SVal)] = string(row.Values[0].SVal)
- // }
- // }
- // }
- // return nameToVid, nil
- //}
- //
- //// 获取 VID 对应的名称
- //func getVidName(session *nebula.Session, vid string) (string, error) {
- // query := fmt.Sprintf(`
- //USE %s;
- //FETCH PROP ON Legal "%s" YIELD properties(vertex).name AS name
- //`, Table_Space, vid)
- // resp, err := session.Execute(query)
- // if err != nil {
- // return "", err
- // }
- // names, err := getFirstColumnStrings(resp)
- // if err != nil || len(names) == 0 {
- // return "", fmt.Errorf("未找到 VID %s 的名称", vid)
- // }
- // return names[0], nil
- //}
- //
- //// 查找路径
- //func findPath(session *nebula.Session, fromVid, toVid string, maxStep int, pathCache map[string][]string) ([]string, error) {
- // key := fmt.Sprintf("%s->%s:%d", fromVid, toVid, maxStep)
- // if cachedPath, ok := pathCache[key]; ok {
- // return cachedPath, nil
- // }
- // query := fmt.Sprintf(`FIND ALL PATH FROM "%s" TO "%s" OVER Invest UPTO %d STEPS YIELD path as p`, fromVid, toVid, maxStep)
- // resp, err := session.Execute(query)
- // if err != nil {
- // return nil, err
- // }
- // path, err := getFirstColumnStrings(resp)
- // if err != nil {
- // return nil, err
- // }
- // pathCache[key] = path
- // return path, nil
- //}
- //
- //// 检查共同祖先
- //func checkCommonAncestor(session *nebula.Session, aVid, bVid string, deep int, pathCache map[string][]string) (bool, []string, string) {
- // key := fmt.Sprintf("%s&%s:%d", aVid, bVid, deep)
- // if cachedPath, ok := pathCache[key]; ok {
- // if len(cachedPath) > 0 {
- // return true, cachedPath, cachedPath[1]
- // }
- // return false, nil, ""
- // }
- // query := fmt.Sprintf(`
- // (
- // GO 1 TO %d STEPS FROM "%s" OVER Invest REVERSELY YIELD dst(edge) AS ancestor
- // )
- // INTERSECT
- // (
- // GO 1 TO %d STEPS FROM "%s" OVER Invest REVERSELY YIELD dst(edge) AS ancestor
- // );
- // `, deep, aVid, deep, bVid)
- // resp, err := session.Execute(query)
- // if err != nil {
- // return false, nil, ""
- // }
- // ancestors, err := getFirstColumnStrings(resp)
- // if err != nil || len(ancestors) == 0 {
- // pathCache[key] = nil
- // return false, nil, ""
- // }
- // pathA, _ := findPath(session, aVid, ancestors[0], deep, pathCache)
- // pathB, _ := findPath(session, bVid, ancestors[0], deep, pathCache)
- // var path []string
- // if len(pathB) > 1 {
- // path = append(pathA, pathB[1:]...)
- // } else {
- // path = append(pathA, pathB...)
- // }
- // pathCache[key] = path
- // return true, path, ancestors[0]
- //}
- //
- //// 将 VID 路径转换为名称路径
- //func convertVidPathToNamePath(session *nebula.Session, vidPath []string) (string, error) {
- // namePath := ""
- // for i, vid := range vidPath {
- // name, err := getVidName(session, vid)
- // if err != nil {
- // return "", err
- // }
- // if i > 0 {
- // namePath += "->"
- // }
- // namePath += name
- // }
- // return namePath, nil
- //}
- //
- //// 检查企业关系
- //func CheckLegalRelations(session *nebula.Session, names []string, deep int) (AllRelationResult, error) {
- // result := AllRelationResult{}
- // checked := make(map[string]bool)
- // nameToVid, err := getVidsByName(session, names)
- // if err != nil {
- // return result, err
- // }
- // pathCache := make(map[string][]string)
- // relatedCompaniesSet := make(map[string]bool)
- // var paths []string
- //
- // for i := 0; i < len(names); i++ {
- // for j := i + 1; j < len(names); j++ {
- // a, b := names[i], names[j]
- // vidA, okA := nameToVid[a]
- // vidB, okB := nameToVid[b]
- // if !okA || !okB {
- // continue
- // }
- //
- // key := vidA + "|" + vidB
- // if checked[key] {
- // continue
- // }
- // checked[key] = true
- //
- // // 1. a -> b
- // pathAB, err := findPath(session, vidA, vidB, deep, pathCache)
- // if err != nil {
- // log.Printf("查找 %s 到 %s 的路径失败: %v", a, b, err)
- // continue
- // }
- // if len(pathAB) > 0 {
- // pathStr, err := convertVidPathToNamePath(session, pathAB)
- // if err != nil {
- // log.Printf("转换 %s 到 %s 的路径失败: %v", a, b, err)
- // continue
- // }
- // paths = append(paths, pathStr)
- // for _, vid := range pathAB {
- // name, err := getVidName(session, vid)
- // if err != nil {
- // log.Printf("获取 VID %s 对应的名称失败: %v", vid, err)
- // continue
- // }
- // relatedCompaniesSet[name] = true
- // }
- // continue
- // }
- //
- // // 2. b -> a
- // pathBA, err := findPath(session, vidB, vidA, deep, pathCache)
- // if err != nil {
- // log.Printf("查找 %s 到 %s 的路径失败: %v", b, a, err)
- // continue
- // }
- // if len(pathBA) > 0 {
- // pathStr, err := convertVidPathToNamePath(session, pathBA)
- // if err != nil {
- // log.Printf("转换 %s 到 %s 的路径失败: %v", b, a, err)
- // continue
- // }
- // paths = append(paths, pathStr)
- // for _, vid := range pathBA {
- // name, err := getVidName(session, vid)
- // if err != nil {
- // log.Printf("获取 VID %s 对应的名称失败: %v", vid, err)
- // continue
- // }
- // relatedCompaniesSet[name] = true
- // }
- // continue
- // }
- //
- // // 3. common ancestor
- // common, path, _ := checkCommonAncestor(session, vidA, vidB, deep, pathCache)
- // if common {
- // pathStr, err := convertVidPathToNamePath(session, path)
- // if err != nil {
- // log.Printf("转换 %s 和 %s 到共同祖先的路径失败: %v", a, b, err)
- // continue
- // }
- // paths = append(paths, pathStr)
- // for _, vid := range path {
- // name, err := getVidName(session, vid)
- // if err != nil {
- // log.Printf("获取 VID %s 对应的名称失败: %v", vid, err)
- // continue
- // }
- // relatedCompaniesSet[name] = true
- // }
- // }
- // }
- // }
- //
- // for company := range relatedCompaniesSet {
- // result.RelatedCompanies = append(result.RelatedCompanies, company)
- // }
- // result.Paths = paths
- //
- // return result, nil
- //}
- //
- //// getFirstColumnStrings 适配 nebula-go v3 取出字符串类型列
- //func getFirstColumnStrings(resp *nebula.ResultSet) ([]string, error) {
- // if resp == nil {
- // return nil, fmt.Errorf("result set is nil")
- // }
- //
- // var values []string
- // for _, row := range resp.GetRows() {
- // if len(row.Values) == 0 {
- // continue
- // }
- // val := row.Values[0]
- // switch {
- // case val.SVal != nil:
- // values = append(values, string(val.SVal))
- // case val.IVal != nil:
- // values = append(values, fmt.Sprintf("%d", *val.IVal))
- // case val.BVal != nil:
- // values = append(values, fmt.Sprintf("%v", *val.BVal))
- // default:
- // log.Printf("未知类型值: %+v", val)
- // }
- // }
- // return values, nil
- //}
- func CheckLegalRelations(session *nebula.Session, names []string, deep int) ([]RelationResult, error) {
- results := []RelationResult{}
- checked := make(map[string]bool)
- nameToVid, err := getAllVids(session, names)
- if err != nil {
- return nil, err
- }
- vidToName := reverseMap(nameToVid)
- for i := 0; i < len(names); i++ {
- for j := i + 1; j < len(names); j++ {
- a, b := names[i], names[j]
- vidA, okA := nameToVid[a]
- vidB, okB := nameToVid[b]
- if !okA || !okB {
- continue
- }
- key := vidA + "|" + vidB
- if checked[key] {
- continue
- }
- checked[key] = true
- // 1. a -> b
- pathAB, err := findPath(session, vidA, vidB, deep)
- if err != nil {
- return nil, err
- }
- if len(pathAB) > 0 {
- readablePath := convertPathToNames(pathAB, vidToName)
- results = append(results, RelationResult{A: a, B: b, RelationType: "direct_or_indirect", Path: readablePath})
- continue
- }
- // 2. b -> a
- pathBA, err := findPath(session, vidB, vidA, deep)
- if err != nil {
- return nil, err
- }
- if len(pathBA) > 0 {
- readablePath := convertPathToNames(pathBA, vidToName)
- results = append(results, RelationResult{A: b, B: a, RelationType: "direct_or_indirect", Path: readablePath})
- continue
- }
- // 3. common ancestor
- common, ancestorVid, err := checkCommonAncestor(session, vidA, vidB, deep)
- if err != nil {
- return nil, err
- }
- if common {
- ancestorName := getAncestorName(session, ancestorVid, vidToName)
- aName := vidToName[vidA]
- bName := vidToName[vidB]
- if ancestorName != "" && aName != "" && bName != "" {
- readablePath := []string{
- fmt.Sprintf("%s -> %s", aName, ancestorName),
- fmt.Sprintf("%s -> %s", bName, ancestorName),
- }
- results = append(results, RelationResult{A: a, B: b, RelationType: "common_ancestor", Path: readablePath})
- }
- }
- }
- }
- return results, nil
- }
- func getAllVids(session *nebula.Session, names []string) (map[string]string, error) {
- nameToVid := make(map[string]string)
- for _, name := range names {
- vid, err := getVidByName(session, name)
- if err != nil {
- log.Printf("获取 %s 的 VID 失败: %v", name, err)
- continue
- }
- nameToVid[name] = vid
- }
- return nameToVid, nil
- }
- func checkCommonAncestor(session *nebula.Session, aVid, bVid string, deep int) (bool, string, error) {
- query := fmt.Sprintf(`
- (
- GO 1 TO %d STEPS FROM "%s" OVER Invest REVERSELY YIELD dst(edge) AS ancestor
- )
- INTERSECT
- (
- GO 1 TO %d STEPS FROM "%s" OVER Invest REVERSELY YIELD dst(edge) AS ancestor
- );
- `, deep, aVid, deep, bVid)
- resp, err := session.Execute(query)
- if err != nil {
- return false, "", err
- }
- ancestors, err := getFirstColumnStrings(resp)
- if err != nil || len(ancestors) == 0 {
- return false, "", nil
- }
- return true, ancestors[0], nil
- }
- func findPath(session *nebula.Session, fromVid, toVid string, maxStep int) ([]string, error) {
- query := fmt.Sprintf(`FIND ALL PATH FROM "%s" TO "%s" OVER Invest UPTO %d STEPS YIELD path as p`, fromVid, toVid, maxStep)
- resp, err := session.Execute(query)
- if err != nil {
- return nil, err
- }
- return getFirstColumnStrings(resp)
- }
- func getVidByName(session *nebula.Session, name string) (string, error) {
- query := fmt.Sprintf(`
- USE `+Table_Space+`;
- LOOKUP ON Legal WHERE Legal.name == "%s" YIELD id(vertex)`, name)
- resp, err := session.Execute(query)
- if err != nil {
- return "", err
- }
- values, err := getFirstColumnStrings(resp)
- if err != nil || len(values) == 0 {
- return "", fmt.Errorf("未找到公司: %s", name)
- }
- return values[0], nil
- }
- type RelationResult struct {
- A, B string // 公司名
- RelationType string // 关系类型:"direct_or_indirect", "common_ancestor"
- Path []string // 路径中的公司名称,以更直观的形式展示
- }
- // getFirstColumnStrings 适配 nebula-go v3 取出字符串类型列
- func getFirstColumnStrings(resp *nebula.ResultSet) ([]string, error) {
- if resp == nil {
- return nil, fmt.Errorf("result set is nil")
- }
- var values []string
- for _, row := range resp.GetRows() {
- if len(row.Values) == 0 {
- continue
- }
- val := row.Values[0]
- switch {
- case val.SVal != nil:
- values = append(values, string(val.SVal))
- case val.IVal != nil:
- values = append(values, fmt.Sprintf("%d", *val.IVal))
- case val.BVal != nil:
- values = append(values, fmt.Sprintf("%v", *val.BVal))
- case val.PVal != nil:
- // 处理点类型
- src := val.PVal.GetSrc()
- log.Println(src)
- //if src.GetId != nil {
- // values = append(values, string(*src.SVal))
- //} else if src.IVal != nil {
- // values = append(values, fmt.Sprintf("%d", *src.IVal))
- //} else {
- // log.Printf("未知的点源 ID 类型: %+v", src)
- //}
- default:
- log.Printf("未知类型值: %+v", val)
- }
- }
- return values, nil
- }
- func reverseMap(m map[string]string) map[string]string {
- result := make(map[string]string)
- for k, v := range m {
- result[v] = k
- }
- return result
- }
- func convertPathToNames(path []string, vidToName map[string]string) []string {
- readablePath := make([]string, 0, len(path))
- for i := 0; i < len(path)-1; i++ {
- fromName, okFrom := vidToName[path[i]]
- toName, okTo := vidToName[path[i+1]]
- if okFrom && okTo && fromName != toName {
- readablePath = append(readablePath, fmt.Sprintf("%s -> %s", fromName, toName))
- }
- }
- return readablePath
- }
- func getAncestorName(session *nebula.Session, ancestorVid string, vidToName map[string]string) string {
- if name, ok := vidToName[ancestorVid]; ok {
- return name
- }
- query := fmt.Sprintf(`
- USE `+Table_Space+`;
- FETCH PROP ON Legal "%s" YIELD Legal.name;
- `, ancestorVid)
- resp, err := session.Execute(query)
- if err != nil {
- log.Printf("获取祖先公司名称失败: %v", err)
- return ""
- }
- names, err := getFirstColumnStrings(resp)
- if err != nil || len(names) == 0 {
- return ""
- }
- return names[0]
- }
- //2222222222222222//
- // CheckLegalRelationships 检查给定企业之间是否存在关系
- func CheckLegalRelationships(session *nebula.Session, names []string, deep, stype int) (bool, string, error) {
- if len(names) < 2 {
- return false, "", fmt.Errorf("企业数量不足,至少需要两个")
- }
- found := false // 是否找到任意关系
- 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, ", ")
- // 构造 nGQL 查询
- 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, "", fmt.Errorf("查询失败: %w", err)
- }
- if !resp.IsSucceed() {
- return false, "", fmt.Errorf("查询执行失败: %s", resp.GetErrorMsg())
- }
- if resp.GetRowSize() > 0 {
- found = true
- // stype == 0:只要找到一个关系就返回
- if stype == 0 {
- return true, "企业之间存在关系", nil
- }
- }
- }
- // 所有组合查完,判断是否找到关系
- if found {
- return true, "企业之间存在关系", nil
- }
- return false, "企业之间无关联", nil
- }
- func CheckLegalRelationships2(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 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 {
- dd := strings.Join(pathNames, " -> ")
- allPaths = append(allPaths, dd)
- if stype == 0 {
- return true, allPaths, nil
- }
- }
- }
- }
- }
- if len(allPaths) > 0 {
- return true, allPaths, nil
- }
- return false, nil, nil
- }
- // getLegalByVid 根据vid 获取顶点
- func getLegalByVid(session *nebula.Session, vid string) (*Legal, error) {
- query := fmt.Sprintf(`
- USE %s;
- FETCH PROP ON Legal "%s" YIELD Legal.name, Legal.code;`, Table_Space, vid)
- resp, err := session.Execute(query)
- if err != nil {
- return nil, fmt.Errorf("failed to execute fetch query: %w", err)
- }
- if !resp.IsSucceed() {
- return nil, fmt.Errorf("query failed: %s", resp.GetErrorMsg())
- }
- rows := resp.GetRows()
- if len(rows) == 0 {
- return nil, nil // 没查到
- }
- values := rows[0].Values
- info := &Legal{}
- if values[0].GetSVal() != nil {
- info.Name = string(values[0].GetSVal())
- }
- if values[1].GetSVal() != nil {
- info.Code = string(values[1].GetSVal())
- }
- 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)
- }
- if lea != nil && lea.Name != "" {
- pathNames = append(pathNames, lea.Name)
- }
- //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)
- }
- if lea != nil && lea.Name != "" {
- 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
- }
- func CheckLegalRelationships5(session *nebula.Session, names []string, deep, stype int) (bool, []string, error) {
- if len(names) < 2 {
- return false, nil, fmt.Errorf("企业数量不足,至少需要两个")
- }
- var rawPaths []string
- 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 builder strings.Builder
- var nodeNames []string
- 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)
- nodeNames = append(nodeNames, 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 {
- if lea != nil && lea.Name != "" {
- dstName = lea.Name
- }
- }
- }
- if step.Type > 0 {
- builder.WriteString(" → ")
- } else if step.Type < 0 {
- builder.WriteString(" ← ")
- } else {
- builder.WriteString(" - ")
- }
- builder.WriteString(dstName)
- nodeNames = append(nodeNames, dstName)
- }
- rawPaths = append(rawPaths, builder.String())
- rawNodeLists = append(rawNodeLists, nodeNames)
- if stype == 0 {
- return true, []string{builder.String()}, nil
- }
- }
- }
- }
- // 去重 + 保留最长路径
- uniqueMap := map[string]string{}
- for i, nodes := range rawNodeLists {
- pathStr := rawPaths[i]
- key := generatePathKey(nodes)
- shouldAdd := true
- for k, _ := range uniqueMap {
- existingNodes := strings.Split(k, "|")
- if isSubPath(nodes, existingNodes) || isSubPath(reverseSlice(nodes), existingNodes) {
- shouldAdd = false
- break
- }
- if isSubPath(existingNodes, nodes) {
- delete(uniqueMap, k)
- }
- }
- if shouldAdd {
- uniqueMap[key] = pathStr
- }
- }
- var finalPaths []string
- for _, v := range uniqueMap {
- finalPaths = append(finalPaths, v)
- }
- if len(finalPaths) > 0 {
- return true, finalPaths, nil
- }
- return false, nil, nil
- }
- type GraphNode struct {
- Name string `json:"name"`
- }
- type GraphLink struct {
- Source string `json:"source"`
- Target string `json:"target"`
- Type string `json:"type"` // 可选字段,比如 "投资"
- }
- type GraphResult struct {
- Nodes []GraphNode `json:"nodes"`
- Links []GraphLink `json:"links"`
- }
- func (c *NebulaClient) CheckLegalRelationships(names []string, deep, stype int) (bool, []string, *GraphResult, error) {
- if len(names) < 2 {
- return false, nil, nil, fmt.Errorf("企业数量不足,至少需要两个")
- }
- if deep == 0 {
- deep = 1
- }
- var rawPaths []string
- var rawNodeLists [][]string
- nodeSet := make(map[string]struct{})
- graph := &GraphResult{}
- 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 := c.ExecuteWithReconnect(query)
- if err != nil {
- return false, nil, nil, fmt.Errorf("查询失败: %w", err)
- }
- if resp == nil {
- return false, nil, nil, fmt.Errorf("查询失败,response is nil")
- }
- if !resp.IsSucceed() {
- return false, nil, nil, fmt.Errorf("执行失败: %s", resp.GetErrorMsg())
- }
- if resp.GetRowSize() > 0 {
- for _, row := range resp.GetRows() {
- if len(row.Values) == 0 || !row.Values[0].IsSetPVal() {
- continue
- }
- path := row.Values[0].GetPVal()
- var builder strings.Builder
- var nodeNames []string
- // 起点
- src := path.Src
- curName := ""
- if src != nil && src.Vid != nil && src.Vid.IsSetSVal() {
- vid := string(src.Vid.GetSVal())
- lea, err := getLegalByVid(c.session, vid)
- if err != nil {
- log.Println("getLegalByVid err:", err, vid)
- } else if lea != nil && lea.Name != "" {
- curName = lea.Name
- }
- }
- builder.WriteString(curName)
- nodeNames = append(nodeNames, curName)
- // 图谱节点添加
- if curName != "" {
- if _, ok := nodeSet[curName]; !ok {
- nodeSet[curName] = struct{}{}
- graph.Nodes = append(graph.Nodes, GraphNode{Name: 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(c.session, vid)
- if err != nil {
- log.Println("getLegalByVid err:", err, vid)
- } else if lea != nil && lea.Name != "" {
- dstName = lea.Name
- }
- }
- if step.Type > 0 {
- builder.WriteString(" → ")
- } else if step.Type < 0 {
- builder.WriteString(" ← ")
- } else {
- builder.WriteString(" - ")
- }
- builder.WriteString(dstName)
- nodeNames = append(nodeNames, dstName)
- // 图谱节点添加
- if dstName != "" {
- if _, ok := nodeSet[dstName]; !ok {
- nodeSet[dstName] = struct{}{}
- graph.Nodes = append(graph.Nodes, GraphNode{Name: dstName})
- }
- }
- // 图谱边添加
- if curName != "" && dstName != "" {
- graph.Links = append(graph.Links, GraphLink{
- Source: curName,
- Target: dstName,
- Type: "投资",
- })
- }
- curName = dstName
- }
- rawPaths = append(rawPaths, builder.String())
- rawNodeLists = append(rawNodeLists, nodeNames)
- if stype == 0 {
- return true, []string{builder.String()}, graph, nil
- }
- }
- }
- }
- // 去重 + 最长路径保留
- uniqueMap := map[string]string{}
- for i, nodes := range rawNodeLists {
- pathStr := rawPaths[i]
- key := generatePathKey(nodes)
- shouldAdd := true
- for k := range uniqueMap {
- existingNodes := strings.Split(k, "|")
- if isSubPath(nodes, existingNodes) || isSubPath(reverseSlice(nodes), existingNodes) {
- shouldAdd = false
- break
- }
- if isSubPath(existingNodes, nodes) {
- delete(uniqueMap, k)
- }
- }
- if shouldAdd {
- uniqueMap[key] = pathStr
- }
- }
- var finalPaths []string
- for _, v := range uniqueMap {
- finalPaths = append(finalPaths, v)
- }
- if len(finalPaths) > 0 {
- return true, finalPaths, graph, nil
- }
- return false, nil, nil, nil
- }
|