go-linq de l'entrée au combat réel

Qu'est-ce que go-linq ?

Une bibliothèque puissante pour intégrer des requêtes dans le langage Go.

avantages go-linq

  1. Il s'agit d'une bibliothèque implémentée en langage Go, sans autres bibliothèques dépendantes ;
  2. Utilisez le mode itérateur pour implémenter le chargement différé, c'est-à-dire exécuter une méthode sans utiliser de méthode et l'exécuter uniquement lorsque la sortie est requise ;
  3. sécurité de la simultanéité ;
  4. Les fonctions communes rendent le code plus concis ;
  5. Les collections de données prennent en charge plusieurs types : tranche, tableau, carte, canal, collections personnalisées.

Comment utiliser go-linq ?

Go-linq utilise une logique de contrôle commune et des appels de méthode enchaînés pour traiter les collectes de données, similaires à l'utilisation d'ORM pour exploiter une base de données, affine les opérations et les contrôles communs, sépare les préoccupations et n'a besoin de se concentrer que sur l'entrée et la sortie.

par exemple:

From(slice) .Where(predicate) .Select(selector) .Union(data)

  • De : génère un type spécial de requête pour go-linq à partir d'un ensemble de données. Différents ensembles de données fournissent différents itérateurs
  • Où : une méthode de contrôle générale fournie par go-linq, qui consiste à filtrer les éléments de collection qui remplissent les conditions
  • Prédicat : prédicat conditionnel, c'est-à-dire l'élément de xxxxxx
  • Sélectionner : sélectionner ou transformer, sélectionner, sélectionner ou transformer
  • Union : une méthode générique fournie par go-linq pour combiner deux ensembles de données

Nous n'avons qu'à nous concentrer sur trois points : entrée -> contrôle -> sortie

Tant que ces trois points sont précisés à chaque étape de l'opération, go-linq peut être bien utilisé pour simplifier le code et améliorer la lisibilité du code.

Entrer

Les différents ensembles de données pris en charge par Go-linq génèrent des structures de requête. Différents types d'ensembles de données ont différents itérateurs dans Query.

type Query struct {
    Iterate func() Iterator
}

Les ensembles de données suivants sont pris en charge :

  1. Tranche
  2. Déployer
  3. Carte
  4. Canaliser
  5. Collections personnalisées

La méthode de génération de la structure Query à partir de l'ensemble de données :

// 支持任一类型的集合生成 query
func From(source interface{}) Query

// 只支持 string  类型, 生成 query
func FromString(source string) Query

// 只支持 channel 类型, 生成 query
func FromChannel(source <-chan interface{}) Query

// 支持自定义集合生成 query
func FromIterable(source Iterable) Query

// 在指定范围内, 生成集合
func Range(start, count int) Query

// 指定值和重复次数, 生成重复数据的集合
func Repeat(value interface{}, count int) Query

其中最常用的是 From(source interface{}) Query 方法,支持任一数据集类型生成 Query,甚至是自定义的集合,源码中使用反射根据类型生成 query:

From

  • 衍生方法: FromString, FromChannel, FromIterable, Range, Repeat
  • 含义: 通过数据集合生成 Query 结构,用于迭代控制

举例:

// case 1:
From("hello world")
FromString("hello world")

// case 2:
Range(1, 10)
Repeat("say 3 times", 3)

// case 3:
type user struct {
   name  string
   cards []string
}

userList := []*user{
   {name: "tom", cards: []string{"a", "b", "c"}},
   {name: "jack", cards: []string{"d", "e", "f"}},
   {name: "json", cards: []string{"g", "h", "i"}}
}

From(userList)

实战:

 /**
  * 构建人才相关搜索语句
  */
func buildTalentSearchClause(talentQuery *TalentQuery) (queries []elastic.Query) {

   linq.From(buildTalentSearchTermsQuery(talentQuery)).
      Concat(linq.From(buildTalentSearchBoolQuery(talentQuery))).
      Concat(linq.From(buildTalentSearchNestedQuery(talentQuery))).
      Concat(linq.From(buildTalentSearchCareerNestedQuery(talentQuery))).
      Concat(linq.From(buildTalentSearchEducationNestedQuery(talentQuery))).
      Concat(linq.From(buildTalentSearchRangeQuery(talentQuery))).ToSlice(&queries)

   return

}

控制

Go-linq 提供的通用数据集操作,可参考官方文档,日常开发中常用的大概有以下几类:

  • 去肥增瘦
  • 挑三拣四
  • 查漏补缺
  • 拉帮结伙

其中有三个关键字,对于理解控制方法非常重要:

  1. withT:T -> type,表示闭包的参数,指定为和数据集合元素一样的类型,避免使用时强转(常用);
  2. By:以什么为依据:比如:分组操作,可以指定以哪个参数来分组;
  3. ByT:也是以什么为依据。闭包中指定和数据集合一样的类型(常用)。

ForEach

  • 衍生方法: ForEachT, ForEachIndexed, ForEachIndexedT
  • 含义: 遍历数据集合的元素,同 for 循环

举例:

type user struct {
   name  string
   cards []string
}

userList := []*user{
   {name: "tom", cards: []string{"a", "b", "c"}},
   {name: "jack", cards: []string{"d", "e", "f"}},
   {name: "json", cards: []string{"g", "h", "i"}}
}

From(userList).ForEachT(func(user *user) {
   fmt.Printf("name:%s, cards:%v\n", user.name, userList)
})

output:
name:tom, cards:[0xc000076180 0xc0000761e0 0xc000076240]
name:jack, cards:[0xc000076180 0xc0000761e0 0xc000076240]
name:json, cards:[0xc000076180 0xc0000761e0 0xc000076240]

实战:

 /**
  * 各个评估结论的个数-映射关系
  */
func (self *evaluationSummaryService) getConclusionCount(evaluationList []*domain.Evaluation) (result conclusionCount) {

   result = make(conclusionCount)

   linq.From(evaluationList).ForEachT(
      func(evaluation *domain.Evaluation) {
         if *evaluation.CommitStatus == constants.CommitStatus_UnCommitted {
            result[constants.Conclusion_NoConclusion] += 1
         } else if evaluation.Conclusion != nil {
            result[*evaluation.Conclusion] += 1
         }
      },
   )
   
   return
}

Where

  • 衍生方法: WhereT, WhereIndexed, WhereIndexedT
  • 含义: 筛选出符合条件的集合元素

举例:

numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).Where(func(i interface{}) bool {
   return i.(int64) > 8
})

fmt.Println(query.Results())

output:
[13]

实战:

 /**
  * 获取除了"已入职"以外的所有阶段
  */
func (self *evaluationSearchProdService) listAllStageIDWithoutHired(ctx context.Context) (result []string) {

   allStageData := facade.ApplicationFacade.ListAllStageWithCache(ctx)
   linq.From(allStageData).WhereT(func(item *aInf.StageInfo) bool {
      return *item.TypeA1 != aconstants.StageType_Hired
   }).SelectT(func(item *aInf.StageInfo) string {
      return *item.ID
   }).ToSlice(&result)

   return
}

Any

  • 衍生方法: AnyWith, AnyWithT
  • 含义: 任意一个,with / withT 可指定条件,即满足条件的任意一个

举例:

numList := []int64{1, 2, 3, 5, 8, 13}

query := From(numList).AnyWithT(func(i int64) bool {
   return i > 8
})

fmt.Println(query)

output:
true

实战:

 /**
  * 判断是否需要扩充阶段
  */
func (self *evaluationSearchProdService) needExtendStage(stageIDList []string) bool {
   return linq.From(stageIDList).AnyWithT(func(item string) bool {
      return item == constant.ApplicationStatusGroupInProgress
   })

}

Append / Prepend

  • 衍生方法:
  • 含义: 集合中插入元素,后插/前插,类似于列表的 append 方法

举例:

numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).Append(100)
fmt.Println(query.Results())

output:
[1 2 3 5 8 13 100]

numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).prepend(100)
fmt.Println(query.Results())

output:
[100 1 2 3 5 8 13]

实战: 暂无

Select

  • 衍生方法: SelectT, SelectMany, SelectManyBy, SelectManyByT, SelectIndexed, SelectIndexedT, SelectManyIndexedT, SelectManyIndexed, SelectManyByIndexed, SelectManyByIndexedT
  • 含义: 挑选出集合中的元素,可进行转化后组成新的集合,这是最常用的控制方法,效果同 flatmap 操作

举例:

// case 1:
numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).SelectT(func(i int64) string {
   return fmt.Sprintf("num: %d\n", i)
})
fmt.Println(query.Results())

output:
[
    num: 1 
    num: 2 
    num: 3 
    num: 5 
    num: 8 
    num: 13
]

// case 2:
num1 := []int64{1, 2, 3}
num2 := []int64{4, 5, 6}
num3 := []int64{7, 8, 9}

numList := [][]int64{
   num1,
   num2,
   num3,
}

query := From(numList).SelectManyT(func(num []int64) Query {
   return From(num).SelectT(func(i int64) string {
      return fmt.Sprintf("num: %d", i)
   })
})

fmt.Println(query.Results())

output:
[
    num: 1 
    num: 2 
    num: 3 
    num: 4 
    num: 5 
    num: 6 
    num: 7 
    num: 8 
    num: 9
]

// case 3:
num1 := []int64{1, 2, 3}
num2 := []int64{4, 5, 6}
num3 := []int64{7, 8, 9}

numList := [][]int64{
   num1,
   num2,
   num3,
}

query := From(numList).SelectManyByT(
   func(num []int64) Query { return From(num) },
   func(item int64, num []int64) string { return fmt.Sprintf("num:%v, numList:%v\n", num, numList) },
)

fmt.Println(query.Results())

output:
[
 num:1, numList:[1 2 3]
 num:2, numList:[1 2 3]
 num:3, numList:[1 2 3]
 num:4, numList:[4 5 6]
 num:5, numList:[4 5 6]
 num:6, numList:[4 5 6]
 num:7, numList:[7 8 9]
 num:8, numList:[7 8 9]
 num:9, numList:[7 8 9]
]

实战:

 /**
  * 获取除了"已入职"以外的所有阶段
  */
func (self *evaluationSearchProdService) listAllStageIDWithoutHired(ctx context.Context) (result []string) {
   allStageData := facade.ApplicationFacade.ListAllStageWithCache(ctx)

   linq.From(allStageData).WhereT(func(item *aInf.StageInfo) bool {
      return *item.TypeA1 != aconstants.StageType_Hired
   }).SelectT(func(item *aInf.StageInfo) string {
      return *item.ID
   }).ToSlice(&result)

   return
}

Group

  • 衍生方法: GroupBy, GroupByT, GroupJoin, GroupJoinT
  • 含义: 根据字段来分组

注意:分组后产生的是一个新的数据结构 Group

type Group struct {
   Key   interface{}
   Group []interface{}
}

举例:

// case 1:
type Person struct {
   Name string
   Age  int64
}

personList := []*Person{
   {Name: "Jack", Age: 18},
   {Name: "Tom", Age: 23},
   {Name: "John", Age: 23},
   {Name: "Susan", Age: 38},
}

query := From(personList).GroupByT(
   func(person *Person) int64 { return person.Age },
   func(person *Person) string { return person.Name },
)

fmt.Println(query.Results())

output:
[{18 [Jack]} {23 [Tom John]} {38 [Susan]}]

实战:

 /**
  * 查询笔试阅卷数据, key: examID, value: examMarking list
  */
func queryExamMarkingMap(ctx context.Context, examList []*domain.Exam) (result map[string][]*domain.ExamMarking) {

   markingList := ExamMarkingService.QueryByExamIDList(ctx, util.ExtractStringFromSlice(examList, "ID"))
   result = make(map[string][]*domain.ExamMarking, len(markingList))

   // convert list to map, key: examID, value: examMarking list
   linq.From(markingList).
      WhereT(func(marking *domain.ExamMarking) bool {
         return marking != nil && util.IsNotStrBlankPtr(marking.ExamID)
      }).
      GroupByT(
         func(marking *domain.ExamMarking) string { return *marking.ExamID },
         func(marking *domain.ExamMarking) *domain.ExamMarking { return marking },
      ).
      ToMapByT(
         &result,
         func(g linq.Group) string { return g.Key.(string) },
         func(g linq.Group) (examMarkingList []*domain.ExamMarking) {
            linq.From(g.Group).SelectT(
               func(gVal interface{}) *domain.ExamMarking { return gVal.(*domain.ExamMarking) },
            ).ToSlice(&examMarkingList)
            return
         },
      )

   return
}

Join

  • 衍生方法: JoinT
  • 含义: 基于相同的键,将两个集合的元素进行关联,跟 sql 的 join 使用类似

举例:

// case1: 

fruits := []string{
   "apple",
   "banana",
   "apricot",
   "cherry",
   "clementine",
}

query := Range(1, 10).
   JoinT(From(fruits),
      func(num int) int { return num },
      func(fruit string) int { return len(fruit) },
      func(num int, fruit string) KeyValue {
         return KeyValue{Key: num, Value: fruit}
      })

fmt.Println(query.Results())

output:
[{5 apple} {6 banana} {6 cherry} {7 apricot} {10 clementine}]

// case 2:
type Person struct {
   Name string
   Age  int64
}

type Pet struct {
   Name  string
   Owner Person
}

magnus := Person{Name: "Hedlund, Magnus"}
terry := Person{Name: "Adams, Terry"}
charlotte := Person{Name: "Weiss, Charlotte"}

barley := Pet{Name: "Barley", Owner: Person{Name: "Adams, Terry"}}
boots := Pet{Name: "Boots", Owner: Person{Name: "Adams, Terry"}}
whiskers := Pet{Name: "Whiskers", Owner: Person{Name: "Weiss, Charlotte"}}
daisy := Pet{Name: "Daisy", Owner: Person{Name: "Hedlund, Magnus"}}

people := []Person{magnus, terry, charlotte}
pets := []Pet{barley, boots, whiskers, daisy}

query := From(people).
   JoinT(From(pets),
      func(person Person) Person { return person },
      func(pet Pet) Person { return pet.Owner },
      func(person Person, pet Pet) string {
         return fmt.Sprintf("%s - %s\n", person.Name, pet.Name)
      },
   )

fmt.Println(query.Results())

output:
[
 Hedlund, Magnus - Daisy
 Adams, Terry - Barley
 Adams, Terry - Boots
 Weiss, Charlotte - Whiskers
]

实战:

func (self *interviewerLarkCalendarLimiter) check(in AppointmentTimeSlice) bool {

   userIsBusyMap := make(map[string]bool, len(in))
   userBusyTimeMap := self.queryUserBusyTimeMap(in)

   From(in).
      JoinT(
         From(userBusyTimeMap),
         func(iter *AppointmentTime) interface{} { return iter.UserID },
         func(kv KeyValue) interface{} { return kv.Key },
         func(src *AppointmentTime, kv KeyValue) KeyValue {
            idle := src.RecommendTimeList.Reject(kv.Value.(utils.IntervalMixSlice))
            return KeyValue{Key: src.UserID, Value: src.RecommendTimeList.Equals(idle)}
         },
      ).
      ToMap(&userIsBusyMap)

   for _, isBusy := range userIsBusyMap {
      if isBusy {
         return false
      }
   }

   return true

}

Intersect

  • 衍生方法: IntersectBy, IntersectBy
  • 含义: 两个数据集合取交集,带 by 关键字的表示以字段为依据取交集

举例:

// case1: 值类型的基本数据类型
id1 := []int{44, 26, 92, 30, 71, 38}
id2 := []int{39, 59, 83, 47, 26, 4, 30}

query := From(id1).Intersect(From(id2))
fmt.Println(query.Results())

output:
[26 30]

// case1: 值类型的struct  类型
type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

var both []Person
From(aList).Intersect(From(bList)).ToSlice(&both)
fmt.Println(both)

output:
[{Jon 18}]

// case 3: 引用类型
type Person struct {
   Name string
   Age  int64
}

p1 := &Person{Name: "Jack", Age: 23}
p2 := &Person{Name: "Jon", Age: 18}
p3 := &Person{Name: "Tom", Age: 23}
p4 := &Person{Name: "Tom", Age: 23}

aList := []*Person{p1, p2, p4}
bList := []*Person{p2, p3}

var both []*Person
From(aList).Intersect(From(bList)).ToSlice(&both)

for _, p := range both {
   fmt.Printf("%p, %s, %d", p, p.Name, p.Age)
}

output:
0xc00000c0a0, Jon, 18

// case 4: 指定求交集的依据
type Person struct {
   Name string
   Age  int64
}

p1 := &Person{Name: "Jack", Age: 23}
p2 := &Person{Name: "Jon", Age: 18}
p3 := &Person{Name: "Tom", Age: 23}
p4 := &Person{Name: "Tom", Age: 23}

aList := []*Person{p1, p2, p4}
bList := []*Person{p2, p3}

var both []*Person
From(aList).
   IntersectByT(From(bList), func(i *Person) int64 {
      return i.Age
   }).
   ToSlice(&both)

for _, p := range both {
   fmt.Printf("%p, %s, %d\n", p, p.Name, p.Age)
}

output:
0xc00000c080, Jack, 23
0xc00000c0a0, Jon, 18

实战:

func selectAvailableInterviewerTime(ctx context.Context, appointmentTask *domain.InterviewAppointmentTask,
   filteredUserTimeList []*domain.InterviewAppointmentInterviewerTime) (result []*domain.InterviewAppointmentInterviewerTime) {

   From(appointmentTask.InterviewerTimeList).
      IntersectByT(
         From(filteredUserTimeList),
         func(interviewerTime *domain.InterviewAppointmentInterviewerTime) string { return *interviewerTime.UserID },
      ).
      DistinctByT(func(interviewerTime *domain.InterviewAppointmentInterviewerTime) int64 {
         return *interviewerTime.EndTime - *interviewerTime.BeginTime
      }).
      ToSlice(&result)

   log.Info(ctx, "[selectAvailableInterviewerTime] result: %v", data_tk.Show(result))

   return
}

Except

  • 衍生方法: ExceptBy, ExceptByT
  • 含义: 两个数据集合取差集,带 by 关键字的表示以字段为依据取差集

举例:

// case 1

type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

var both []Person
From(aList).Except(From(bList)).ToSlice(&both)
fmt.Println(both)

output:
[{Jack 23}]

实战: 暂无

Union

  • 衍生方法:
  • 含义: 两个集合的并集,相同的元素去重

举例:

// case1
id1 := []int{1, 2, 3, 4}
id2 := []int{3, 4, 5, 6}

query := From(id1).Union(From(id2))
fmt.Println(query.Results())

output:
[1 2 3 4 5 6]

实战: 暂无

Concat

  • 衍生方法:
  • 含义: 两个集合的并集,相同的元素不去重

举例:

// case1
id1 := []int{1, 2, 3, 4}
id2 := []int{3, 4, 5, 6}

query := From(id1).Concat(From(id2))
fmt.Println(query.Results())

output:
[1 2 3 4 3 4 5 6]

实战:

func (self *evaluationSearchProdService) supplyApplicationStageAndStatus(ctx context.Context,
   stageAndStatusIDList []string) (result []string) {

   if len(stageAndStatusIDList) == 0 {
      return
   }
   
   // 判断是否需要扩展阶段
   existExtend := self.needExtendStage(stageAndStatusIDList)
   if !existExtend {
      return stageAndStatusIDList
   }

   // 主要构成两部分: 1.前端传的阶段, 去掉"进行中"; 2.补充除了"已入职"的所有阶段
   // 即: "进行中"阶段 == 除了"已入职"阶段的阶段
   linq.From(
      self.filterStageIDWithoutInProgress(stageAndStatusIDList),
   ).Concat(
      linq.From(self.listAllStageIDWithoutHired(ctx)),
   ).SelectT(func(item string) string {
      return item
   }).ToSlice(&result)

   log.Info(ctx, "[supplyApplicationStageAndStatus] stage_id_list:%v", result)

   return

}

Distinct

  • 衍生方法: DistinctBy, DistinctByT
  • 含义: 数据集合去重,union() == concat().distinct()

举例:

// case1
id1 := []int{1, 2, 3, 4}
id2 := []int{3, 4, 5, 6}

query := From(id1).Concat(From(id2)).Distinct()
fmt.Println(query.Results())

output:
[1 2 3 4 5 6]

实战:

 /**

 * 补充投递来源

 */

func (self *evaluationSearchProdService) supplyApplicationSource(ctx context.Context, sourceIDList []string) (
   result []string) {

   if len(sourceIDList) == 0 {
      return
   }

   // 查询投递来源
   resumeSourceList := facade.TalentFacade.ListResumeSourceByIDList(ctx,tconstants.ResumeSourceUsagePtr(tconstants.ResumeSourceUsage_Application),
      util.BoolPtr(true),
      sourceIDList,
   ).ResumeSourceInfoList

   // 查找投递来源 ID, concat 前端传入的 applicationSource, 防止 talent 服务返回的 resumeSourceList 为空
   linq.From(resumeSourceList).
      SelectManyT(func(item *tInf.ResumeSourceInfo) linq.Query {
         return linq.From(builder.ExtractResumeSourceIDList(item))
      }).
      Concat(linq.From(sourceIDList)).
      Distinct().
      ToSlice(&result)

   return
}

输出

Last

  • 衍生方法: LastWith, LastWithT
  • 含义: 取数据集中的最后一个元素,带 with 关键字,表示以什么条件取最后一个元素

举例:

// case1
id1 := []int{1, 2, 3, 4}
query := From(id1).Last()
fmt.Println(query)

output:
4

实战: 暂无

First

  • 衍生方法: FirstWith, FirstWithT
  • 含义: 取数据集中的第一个元素,带 with 关键字,表示以什么条件取第一个元素

举例:

// case 1
id1 := []int{1, 2, 3, 4}

query := From(id1).FirstWithT(func(i int) bool {
   return i > 3
})
fmt.Println(query)

output:
4

实战:

 /**

 * 批量更新评估的 evaluation_status

 */

func (self *evaluationStatusService) batchUpdateEvaluationStatus(ctx context.Context, db *db.DB,
   evaluationList []*domain.Evaluation, evaluationStatus evalCts.ActivityStatus) {

   if len(evaluationList) == 0 {
      log.Info(ctx, "[evaluationStatusService][batchUpdateEvaluationStatus] evaluation list is empty")
      return
   }

   evaluationIDList := util.ExtractStringFromSlice(evaluationList, "ID")
   evaluationExtList := repo.EvaluationExtRepo.QueryByEvaluationIDList(ctx, db, evaluationIDList)
   if len(evaluationExtList) == 0 {
      log.Info(ctx, "[evaluationStatusService][batchUpdateEvaluationStatus] "+
         "evaluation ext list is empty by evaluation_id_list: [%v]", evaluationIDList)
      return
   }

   evaluationExtIDList := util.ExtractStringFromSlice(evaluationExtList, "ID")
   // 更新评估状态
   tempEvaluationExt := linq.From(evaluationExtList).First().(*domain.EvaluationExt)
   tempEvaluationExt.EvaluationStatus = evalCts.ActivityStatusPtr(evaluationStatus)
   tempEvaluationExt.BizModifyTime = util.Int64Ptr(util.GetMilliTimestamp())
   repo.EvaluationExtRepo.BatchUpdateByIDList(ctx, db, evaluationExtIDList, tempEvaluationExt)

   log.Info(ctx, "[evaluationStatusService][batchUpdateEvaluationStatus] "+
      "update evaluation_status [%v] for evaluation_id_list:[%v]", evaluationStatus, evaluationIDList)
}

Min

  • 衍生方法:
  • 含义: 取数据集中的最小的元素

举例:

// case 1
id1 := []int{1, 2, 3, 4}
query := From(id1).Min()
fmt.Println(query)

output:
1

实战:

 /**

 * 根据场次获取面试官日历范围

 */

func (self *calendarProdService) getCalendarRangeFromSession(ctx context.Context,
   jFSession *domain.JFSession) (startTime, endTime *int64) {

   if len(jFSession.SubSessionList) == 0 {
      return
   }

   var subSessionStartTimeMSList []int64
   // 取 SubSession startTime 列表
   linq.From(jFSession.SubSessionList).
      WhereT(func(item *domain.JFSubSession) bool {
         return item != nil && item.StartTime != nil
      }).
      SelectT(func(item *domain.JFSubSession) int64 {
         return *item.StartTime
      }).
      ToSlice(&subSessionStartTimeMSList)

   if linq.From(subSessionStartTimeMSList).Count() == 0 {
      return
   }

   start := linq.From(subSessionStartTimeMSList).Min().(int64)
   end := linq.From(subSessionStartTimeMSList).Max().(int64)

   // 判断 SubSession 最大的 startTime 是否超过 session 的 endTime
   if end > *jFSession.EndTime {
      end = *jFSession.EndTime
   }

   log.Info(ctx, "[getCalendarRangeFromSession] calendar start_time:%d, end_time:%d", start, end)

   return util.Int64Ptr(start), util.Int64Ptr(end)
}

Max

  • 衍生方法:
  • 含义: 取数据集中的最大的元素

举例:

// case 1
id1 := []int{1, 2, 3, 4, 4, 4}
query := From(id1).Max()
fmt.Println(query)

output:
4

实战:

 /**

 * 根据场次获取面试官日历范围

 */

func (self *calendarProdService) getCalendarRangeFromSession(ctx context.Context,
   jFSession *domain.JFSession) (startTime, endTime *int64) {
   
   if len(jFSession.SubSessionList) == 0 {
      return
   }

   var subSessionStartTimeMSList []int64
   // 取 SubSession startTime 列表
   linq.From(jFSession.SubSessionList).
      WhereT(func(item *domain.JFSubSession) bool {
         return item != nil && item.StartTime != nil
      }).
      SelectT(func(item *domain.JFSubSession) int64 {
         return *item.StartTime
      }).
      ToSlice(&subSessionStartTimeMSList)

   if linq.From(subSessionStartTimeMSList).Count() == 0 {
      return
   }



   start := linq.From(subSessionStartTimeMSList).Min().(int64)
   end := linq.From(subSessionStartTimeMSList).Max().(int64)

   // 判断 SubSession 最大的 startTime 是否超过 session 的 endTime
   if end > *jFSession.EndTime {
      end = *jFSession.EndTime
   }

   log.Info(ctx, "[getCalendarRangeFromSession] calendar start_time:%d, end_time:%d", start, end)

   return util.Int64Ptr(start), util.Int64Ptr(end)
}

Single

  • 相关方法: SingleWith, SingleWithT
  • 含义: 返回集合中的唯一元素,如果集合中的元素不唯一返回 nil

举例:

// case 1:
numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).Single()
fmt.Println(query)

output:
nil

// case 2:
numList := []int64{1}
query := From(numList).Single()
fmt.Println(query)

output:
1

// case 3:
numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).SingleWithT(func(i int64) bool {
   return i > 8
})
fmt.Println(query)

output:
13

实战: 暂无

ToSlice

  • 衍生方法:
  • 含义: 将数据集 query 的值输出到指定 slice 中,这是最常用的方法之一

举例:

// case1
type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

var both []Person
From(aList).Except(From(bList)).ToSlice(&both)
fmt.Println(both)

output:
[{Jack 23}]

实战:

func (self *evaluationSearchProdService) supplyApplicationStageAndStatus(ctx context.Context,
   stageAndStatusIDList []string) (result []string) {

   if len(stageAndStatusIDList) == 0 {
      return
   }

   // 判断是否需要扩展阶段
   existExtend := self.needExtendStage(stageAndStatusIDList)
   if !existExtend {
      return stageAndStatusIDList
   }
   
   // 主要构成两部分: 1.前端传的阶段, 去掉"进行中"; 2.补充除了"已入职"的所有阶段
   // 即: "进行中"阶段 == 除了"已入职"阶段的阶段
   linq.From(
      self.filterStageIDWithoutInProgress(stageAndStatusIDList),
   ).Concat(
      linq.From(self.listAllStageIDWithoutHired(ctx)),
   ).SelectT(func(item string) string {
      return item
   }).ToSlice(&result)

   log.Info(ctx, "[supplyApplicationStageAndStatus] stage_id_list:%v", result)

   return
}

ToMap

  • 衍生方法: ToMapBy, ToMapByT
  • 含义: 将数据集 query 的值输出到指定 map 中,这是最常用的方法之一

注意:要提前 alloc map

举例:

type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

pMap := make(map[string]int64)
From(aList).
   Except(From(bList)).
   ToMapByT(&pMap,
      func(p Person) string { return p.Name },
      func(p Person) int64 { return p.Age },
   )

fmt.Println(pMap)

output:
map[Jack:23]

实战:

func (self *interviewerLarkCalendarLimiter) queryUserBusyTimeMap(in AppointmentTimeSlice) (
   result map[string]utils.IntervalMixSlice) {

   startUTCDateTimestamp, endUTCDateTimestamp := in.getTotalDurationIgnoreTimezone(self.ctx)
   busyTimes := UserBusyPeriods(self.ctx, self.userList, startUTCDateTimestamp,
      endUTCDateTimestamp)

   result = make(map[string]utils.IntervalMixSlice, len(busyTimes))
   From(busyTimes).ToMapByT(
      &result,
      func(iter *AppointmentTime) interface{} { return iter.UserID },
      func(iter *AppointmentTime) utils.IntervalMixSlice { return iter.RecommendTimeList },
   )

   return
}

Contains

  • 衍生方法:
  • 含义: 数据集合中是否包含某个元素,不用 for 循环去遍历集合再判断

举例:

// case1
type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

isContains := From(aList).
   Except(From(bList)).
   Contains(p1)
   
fmt.Println(isContains)

output:
true

实战:

func (self *interviewAppointmentTaskProdService) checkAppointmentLinkExpiryAndStatus(appointment *domain.InterviewAppointmentTask) (result func() error) {

   return func() error {
      // 约面任务是否存在
       if appointment == nil {
           return bizerr.AppointmentTaskNotExit
       }
   
       // 约面链接是否失效
       if *appointment.TerminateTime < util.GetMilliTimestamp() {
           return bizerr.AppointmentLinkNotAvailable
       }
   
       // 约面任务是否可以确认/拒绝
       acceptStatusList := []intCts.AppointmentStatus{
           intCts.AppointmentStatus_Dating,
           intCts.AppointmentStatus_Selecting,
       }

       if !From(acceptStatusList).Contains(*appointment.AppointmentStatus) {
           return bizerr.AppointmentTaskIsCanceled

       }
   
       return nil
   }

}

综合实战

原来的逻辑:

 // 投递状态V2版本需要扩展, 为树形结构
if len(request.StageIDList) > 0 {
   existExtend := false
   expandIDList := make([]string, 0)
   for _, stageEnum := range request.StageIDList {
      if stageEnum == constant.ApplicationStatusGroupInProgress {
         existExtend = true
         break
      }
   }

   if existExtend {
      allStageData := facade.ApplicationFacade.ListAllStageWithCache(ctx)
      for _, v := range request.StageIDList {
         if v == constant.ApplicationStatusGroupInProgress {
            for _, stageData := range allStageData {
               if *stageData.TypeA1 != aconstants.StageType_Hired {
                  expandIDList = append(expandIDList, *stageData.ID)
               }
            }
         } else {
            expandIDList = append(expandIDList, v)
         }
      }
      request.StageIDList = expandIDList
   }

   log.Info(ctx, "Evaluation application_status expand %s", data_tk.Show(expandIDList))
}

使用 go-linq 重构后的逻辑:

// 补充投递状态
func (self *evaluationSearchProdService) supplyApplicationStageAndStatus(ctx context.Context,
   stageAndStatusIDList []string) (result []string) {

   if len(stageAndStatusIDList) == 0 {
      return
   }

   // 判断是否需要扩展阶段
   existExtend := self.needExtendStage(stageAndStatusIDList)
   if !existExtend {
      return stageAndStatusIDList
   }

   // 主要构成两部分: 1.前端传的阶段, 去掉"进行中"; 2.补充除了"已入职"的所有阶段
   // 即: "进行中"阶段 == 除了"已入职"阶段的阶段
   linq.From(
      self.filterStageIDWithoutInProgress(stageAndStatusIDList),
   ).Concat(
      linq.From(self.listAllStageIDWithoutHired(ctx)),
   ).SelectT(func(item string) string {
      return item
   }).ToSlice(&result)

   log.Info(ctx, "[supplyApplicationStageAndStatus] stage_id_list:%v", result)

   return
}

 /**
  * 判断是否需要扩充阶段
  */

func (self *evaluationSearchProdService) needExtendStage(stageIDList []string) bool {
   return linq.From(stageIDList).AnyWithT(func(item string) bool {
      return item == constant.ApplicationStatusGroupInProgress
   })
}

 /**
  * 过滤掉"进行中"阶段
  */
func (self *evaluationSearchProdService) filterStageIDWithoutInProgress(stageIDList []string) (result []string) {

   linq.From(stageIDList).WhereT(func(item string) bool {
      return item != constant.ApplicationStatusGroupInProgress
   }).ToSlice(&result)

   return
}



 /**
  * 获取除了"已入职"以外的所有阶段
  */
func (self *evaluationSearchProdService) listAllStageIDWithoutHired(ctx context.Context) (result []string) {

   allStageData := facade.ApplicationFacade.ListAllStageWithCache(ctx)
   
   linq.From(allStageData).WhereT(func(item *aInf.StageInfo) bool {
      return *item.TypeA1 != aconstants.StageType_Hired
   }).SelectT(func(item *aInf.StageInfo) string {
      return *item.ID
   }).ToSlice(&result)

   return
}

进阶

数据集合输入可以指定自定义的集合,只要实现对应的接口,实现自己的迭代器即可使用 go-linq 提供的控制方法和输出方法。

Iterate 和 CompareTo

示例:

数组的数组,进行求交集/差集/并集,目前自动计算推荐面试时间可用到,类似于下面的时间切片数组

  • timeList1: [ [10, 11], [12, 15], [16, 30] ]
  • timeList2: [ [15, 16], [16, 20] ]

对时间切片数组中的元素取交集/差集/并集

type MixTime [2]int64

func (f MixTime) CompareTo(c Comparable) int {
   a, b := f, c.(MixTime)
   if a[0] > b[0] {
      return 1
   } else if a[0] < b[0] {
      return -1
   }

   return 0
}



type MixTimeArray []MixTime

func (MixTimeArray) Iterate() Iterator {
   return func() (item interface{}, ok bool) {
      return
   }
}

func TestIterable() {
   q1 := MixTimeArray{
      {1, 2},
      {3, 4},
      {5, 6},
   }
   
   q2 := MixTimeArray{
      {1, 200},
      {3, 4},
      {7, 8},
   }

   res1 := From(q1).Intersect(From(q2)).Results()
   fmt.Printf("intersect: %v\n", res1)

   res2 := From(q1).Except(From(q2)).Results()
   fmt.Printf("Except: %v\n", res2)

   res3 := From(q1).Union(From(q2)).Results()
   fmt.Printf("Union: %v\n", res3)

   q3 := MixTimeArray{
      {1, 2},
      {3, 4},
      {5, 6},
   }

   res4 := From(q1).SequenceEqual(From(q2))
   res5 := From(q1).SequenceEqual(From(q3))
   fmt.Printf("Equal: q1 & q2 %v; q1 & q3 %v\n", res4, res5)
}

output:
intersect: [[3 4]]
Except: [[1 2] [5 6]]
Union: [[1 2] [3 4] [5 6] [1 200] [7 8]]
Equal: q1 & q2 false; q1 & q3 true

前车之鉴

使用 go-linq 的目的不是为了解决圈复杂度,而是为了让代码更简洁,更具有可读性,带来的好处之一就是圈复杂度降低了,从侧面也说明代码复杂度降低了。

  1. 分离关注点,明确输入和输出,再选择合适的控制方法;
  2. 使用带 withT 的关键字时,需要注意数据源的元素类型和闭包中指定的类型要一致,否则运行时 panic;
  3. 使用 groupBy 后的输出要注意 []interface{} 的类型转换,可以使用 selecT 进行单独转换,不能强制类型转换 []interface {} -> []type;
  4. 在使用 go-linq 时,尽量将复杂的闭包封装成小函数,好处:
    • 可读性更高
    • 小函数职责单一,可复用
    • 分支控制和逻辑处理独立
  5. 弄清楚每个 go-linq 方法的场景和特性,比较模糊的时候可以写 demo 测试一下:
    • 比如:takeWith,skipWith 理解和具体使用有混淆的地方;
    • Autre exemple : les opérations d'ensemble telles que l'intersection/la différence/l'union de deux ensembles, certains types d'ensembles de données peuvent ne pas être possibles, et le type doit prendre en charge le hachage, etc.
  6. Lorsque vous rencontrez une conversion plus complexe, vous pouvez penser de l'arrière vers l'avant, quelle méthode peut être utilisée pour obtenir la sortie, vous pouvez penser à quel type d'entrée, puis l'examiner couche par couche ;
  7. Comme les modèles de conception, go-linq n'est pas une panacée. N'oubliez pas de ne pas en abuser. Vous devez comprendre quelle est la méthode de contrôle à résoudre ;
  8. À l'exception de l'introduction sur le site officiel, il n'y a presque aucune information sur Internet et vous pouvez discuter entre vous pour obtenir les meilleures pratiques.

Rejoignez-nous

Nous sommes de Lark Business Applications de ByteDance Feishu.Actuellement, nous avons établi des bureaux à Pékin, Shenzhen, Shanghai, Wuhan, Hangzhou, Chengdu, Guangzhou et Sanya. Les domaines de produits sur lesquels nous nous concentrons sont principalement les logiciels de gestion de l'expérience d'entreprise, y compris Feishu OKR, les performances Feishu, le recrutement Feishu, le personnel Feishu et d'autres systèmes HCM, ainsi que les systèmes d'approbation Feishu, OA, affaires juridiques, finances, achats, voyages et remboursement. Bienvenue à vous joindre à nous.

WeChat scanne le code pour trouver des emplois et soumettre des CV

Livraison site officiel : job.toutiao.com/s/FyL7DRg

Je suppose que tu aimes

Origine juejin.im/post/7122403456417021989
conseillé
Classement