package routers import ( "encoding/json" "fmt" "sort" "strconv" "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "github.com/olivere/elastic/v7" ) type SearchRes struct { Total int64 Result []map[string]interface{} PrivateTotal int64 } var client *elastic.Client func InitESClient() { ESSearchUrl := setting.ESSearchURL var err error client, err = elastic.NewClient(elastic.SetSniff(false), elastic.SetURL(ESSearchUrl)) if err != nil { log.Info("es init error.") //panic(err) } } func EmptySearch(ctx *context.Context) { log.Info("search template.") ctx.Data["Keyword"] = "" ctx.HTML(200, "explore/search_new") } func Search(ctx *context.Context) { log.Info("search template.") keyword := strings.Trim(ctx.Query("q"), " ") ctx.Data["Keyword"] = keyword ctx.Data["SortType"] = "newest" ctx.HTML(200, "explore/search_new") } func SearchApi(ctx *context.Context) { TableName := ctx.Query("TableName") Key := ctx.Query("Key") Page := ctx.QueryInt("Page") PageSize := ctx.QueryInt("PageSize") OnlyReturnNum := ctx.QueryBool("OnlyReturnNum") OnlySearchLabel := ctx.QueryBool("OnlySearchLabel") if Page <= 0 { Page = 1 } if PageSize <= 0 || PageSize > 200 { PageSize = setting.UI.IssuePagingNum } if Key != "" && !OnlyReturnNum { go models.SaveSearchKeywordToDb(Key) } if TableName == "repository" { if OnlySearchLabel { searchRepoByLabel(ctx, Key, Page, PageSize) } else { searchRepo(ctx, "repository-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, OnlyReturnNum) } return } else if TableName == "issue" { searchIssueOrPr(ctx, "issue-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, OnlyReturnNum, "f") return } else if TableName == "user" { searchUserOrOrg(ctx, "user-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, true, OnlyReturnNum) return } else if TableName == "org" { searchUserOrOrg(ctx, "user-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, false, OnlyReturnNum) return } else if TableName == "dataset" { searchDataSet(ctx, "dataset-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, OnlyReturnNum) return } else if TableName == "pr" { searchIssueOrPr(ctx, "issue-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, OnlyReturnNum, "t") //searchPR(ctx, "issue-es-index", Key, Page, PageSize, OnlyReturnNum) return } } func searchRepoByLabel(ctx *context.Context, Key string, Page int, PageSize int) { /* 项目, ES名称: repository-es-index 搜索: name character varying(255) , 项目名称 description text, 项目描述 topics json, 标签 排序: updated_unix num_watches, num_stars, num_forks, */ SortBy := ctx.Query("SortBy") PrivateTotal := ctx.QueryInt("PrivateTotal") WebTotal := ctx.QueryInt("WebTotal") ascending := ctx.QueryBool("Ascending") language := ctx.Query("language") if language == "" { language = "zh-CN" } from := (Page - 1) * PageSize resultObj := &SearchRes{} log.Info("WebTotal=" + fmt.Sprint(WebTotal)) log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal)) resultObj.Result = make([]map[string]interface{}, 0) if from == 0 { WebTotal = 0 } if ctx.User != nil && (from < PrivateTotal || from == 0) { orderBy := models.SearchOrderByRecentUpdated switch SortBy { case "updated_unix.keyword": orderBy = models.SearchOrderByRecentUpdated case "num_stars": orderBy = models.SearchOrderByStarsReverse case "num_forks": orderBy = models.SearchOrderByForksReverse case "num_watches": orderBy = models.SearchOrderByWatches } log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil)) repos, count, err := models.SearchRepository(&models.SearchRepoOptions{ ListOptions: models.ListOptions{ Page: Page, PageSize: PageSize, }, Actor: ctx.User, OrderBy: orderBy, Private: true, OnlyPrivate: true, TopicOnly: true, TopicName: Key, IncludeDescription: setting.UI.SearchRepoDescription, }) if err != nil { ctx.JSON(200, "") return } resultObj.PrivateTotal = count if repos.Len() > 0 { log.Info("Query private repo number is:" + fmt.Sprint(repos.Len())) makePrivateRepo(repos, resultObj, Key, language) } else { log.Info("not found private repo,keyword=" + Key) } if repos.Len() >= PageSize { if WebTotal > 0 { resultObj.Total = int64(WebTotal) ctx.JSON(200, resultObj) return } } } else { if ctx.User == nil { resultObj.PrivateTotal = 0 } else { resultObj.PrivateTotal = int64(PrivateTotal) } } from = from - PrivateTotal if from < 0 { from = 0 } Size := PageSize - len(resultObj.Result) log.Info("query searchRepoByLabel start") if Key != "" { boolQ := elastic.NewBoolQuery() topicsQuery := elastic.NewMatchQuery("topics", Key) boolQ.Should(topicsQuery) res, err := client.Search("repository-es-index").Query(boolQ).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Highlight(queryHighlight("topics")).Do(ctx.Req.Context()) if err == nil { searchJson, _ := json.Marshal(res) log.Info("searchJson=" + string(searchJson)) esresult := makeRepoResult(res, "", false, language) resultObj.Total = resultObj.PrivateTotal + esresult.Total resultObj.Result = append(resultObj.Result, esresult.Result...) ctx.JSON(200, resultObj) } else { log.Info("query es error," + err.Error()) ctx.JSON(200, "") } } else { ctx.JSON(200, "") } } func getSort(SortBy string, ascending bool, secondSortBy string, secondAscending bool) []elastic.Sorter { sort := make([]elastic.Sorter, 0) if SortBy == "default" || SortBy == "" { sort = append(sort, elastic.NewScoreSort()) if secondSortBy != "" { log.Info("SortBy=" + SortBy + " secondSortBy=" + secondSortBy) sort = append(sort, elastic.NewFieldSort(secondSortBy).Order(secondAscending)) } } else { sort = append(sort, elastic.NewFieldSort(SortBy).Order(ascending)) } log.Info("sort size=" + fmt.Sprint(len(sort))) return sort } func searchRepo(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool) { /* 项目, ES名称: repository-es-index 搜索: name character varying(255) , 项目名称 description text, 项目描述 topics json, 标签 排序: updated_unix num_watches, num_stars, num_forks, */ SortBy := ctx.Query("SortBy") PrivateTotal := ctx.QueryInt("PrivateTotal") WebTotal := ctx.QueryInt("WebTotal") ascending := ctx.QueryBool("Ascending") from := (Page - 1) * PageSize resultObj := &SearchRes{} log.Info("WebTotal=" + fmt.Sprint(WebTotal)) log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal)) resultObj.Result = make([]map[string]interface{}, 0) if from == 0 { WebTotal = 0 } language := ctx.Query("language") if language == "" { language = "zh-CN" } if ctx.User != nil && (from < PrivateTotal || from == 0) { orderBy := models.SearchOrderByRecentUpdated switch SortBy { case "updated_unix.keyword": orderBy = models.SearchOrderByRecentUpdated case "num_stars": orderBy = models.SearchOrderByStarsReverse case "num_forks": orderBy = models.SearchOrderByForksReverse case "num_watches": orderBy = models.SearchOrderByWatches } log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil)) repos, count, err := models.SearchRepository(&models.SearchRepoOptions{ ListOptions: models.ListOptions{ Page: Page, PageSize: PageSize, }, Actor: ctx.User, OrderBy: orderBy, Private: true, OnlyPrivate: true, Keyword: Key, IncludeDescription: setting.UI.SearchRepoDescription, OnlySearchPrivate: true, }) if err != nil { ctx.JSON(200, "") return } resultObj.PrivateTotal = count if repos.Len() > 0 { log.Info("Query private repo number is:" + fmt.Sprint(repos.Len())) makePrivateRepo(repos, resultObj, Key, language) } else { log.Info("not found private repo,keyword=" + Key) } if repos.Len() >= PageSize { if WebTotal > 0 { resultObj.Total = int64(WebTotal) ctx.JSON(200, resultObj) return } } } else { if ctx.User == nil { resultObj.PrivateTotal = 0 } else { resultObj.PrivateTotal = int64(PrivateTotal) } } from = from - PrivateTotal if from < 0 { from = 0 } Size := PageSize - len(resultObj.Result) log.Info("query searchRepo start") if Key != "" { boolQ := elastic.NewBoolQuery() nameQuery := elastic.NewMatchQuery("alias", Key).Boost(1024).QueryName("f_first") descriptionQuery := elastic.NewMatchQuery("description", Key).Boost(1.5).QueryName("f_second") topicsQuery := elastic.NewMatchQuery("topics", Key).Boost(1).QueryName("f_third") boolQ.Should(nameQuery, descriptionQuery, topicsQuery) res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending, "num_stars", false)...).From(from).Size(Size).Highlight(queryHighlight("alias", "description", "topics")).Do(ctx.Req.Context()) if err == nil { searchJson, _ := json.Marshal(res) log.Info("searchJson=" + string(searchJson)) esresult := makeRepoResult(res, Key, OnlyReturnNum, language) resultObj.Total = resultObj.PrivateTotal + esresult.Total isNeedSort := false if len(resultObj.Result) > 0 { isNeedSort = true } resultObj.Result = append(resultObj.Result, esresult.Result...) if isNeedSort { sortRepo(resultObj.Result, SortBy, ascending) } ctx.JSON(200, resultObj) } else { log.Info("query es error," + err.Error()) ctx.JSON(200, "") } } else { log.Info("query all content.") //搜索的属性要指定{"timestamp":{"unmapped_type":"date"}} res, err := client.Search(TableName).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Do(ctx.Req.Context()) if err == nil { searchJson, _ := json.Marshal(res) log.Info("searchJson=" + string(searchJson)) esresult := makeRepoResult(res, "", OnlyReturnNum, language) resultObj.Total = resultObj.PrivateTotal + esresult.Total resultObj.Result = append(resultObj.Result, esresult.Result...) ctx.JSON(200, resultObj) } else { log.Info("query es error," + err.Error()) ctx.JSON(200, "") } } } func sortRepo(Result []map[string]interface{}, SortBy string, ascending bool) { orderBy := "" switch SortBy { case "updated_unix.keyword": orderBy = "updated_unix" case "num_stars": orderBy = "num_stars" case "num_forks": orderBy = "num_forks" case "num_watches": orderBy = "num_watches" } sort.Slice(Result, func(i, j int) bool { return getInt(Result[i][orderBy], orderBy) > getInt(Result[j][orderBy], orderBy) }) } func getInt(tmp interface{}, orderBy string) int64 { timeInt, err := strconv.ParseInt(fmt.Sprint(tmp), 10, 64) if err == nil { return timeInt } else { log.Info("convert " + orderBy + " error type=" + fmt.Sprint(tmp)) } return -1 } func makePrivateRepo(repos models.RepositoryList, res *SearchRes, keyword string, language string) { for _, repo := range repos { record := make(map[string]interface{}) record["id"] = repo.ID record["name"] = makeHighLight(keyword, repo.Name) record["real_name"] = repo.Name record["owner_name"] = repo.OwnerName record["description"] = truncLongText(makeHighLight(keyword, repo.Description), true) hightTopics := make([]string, 0) if len(repo.Topics) > 0 { for _, t := range repo.Topics { hightTopics = append(hightTopics, makeHighLight(keyword, t)) } } record["hightTopics"] = hightTopics record["num_watches"] = repo.NumWatches record["num_stars"] = repo.NumStars record["num_forks"] = repo.NumForks record["alias"] = truncLongText(makeHighLight(keyword, repo.Alias), true) record["lower_alias"] = repo.LowerAlias record["topics"] = repo.Topics record["avatar"] = repo.RelAvatarLink() if len(repo.RelAvatarLink()) == 0 { record["avatar"] = setting.RepositoryAvatarFallbackImage } record["updated_unix"] = repo.UpdatedUnix record["updated_html"] = timeutil.TimeSinceUnix(repo.UpdatedUnix, language) lang, err := repo.GetTopLanguageStats(1) if err == nil && len(lang) > 0 { record["lang"] = lang[0].Language } else { record["lang"] = "" } record["is_private"] = true res.Result = append(res.Result, record) } } func makeHighLight(keyword string, dest string) string { dest = replaceIngoreUpperOrLower(dest, strings.ToLower(dest), strings.ToLower(keyword)) return dest } func replaceIngoreUpperOrLower(dest string, destLower string, keywordLower string) string { re := "" last := 0 lenDestLower := len(destLower) lenkeywordLower := len(keywordLower) for i := 0; i < lenDestLower; i++ { if destLower[i] == keywordLower[0] { isFind := true for j := 1; j < lenkeywordLower; j++ { if (i+j) < lenDestLower && keywordLower[j] != destLower[i+j] { isFind = false break } } if isFind && (i+lenkeywordLower) <= lenDestLower { re += dest[last:i] + "\u003cfont color='red'\u003e" + dest[i:(i+lenkeywordLower)] + "\u003c/font\u003e" i = i + lenkeywordLower last = i } } } if last < lenDestLower { re += dest[last:lenDestLower] } return re } func makeRepoResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes { total := sRes.Hits.TotalHits.Value result := make([]map[string]interface{}, 0) if !OnlyReturnNum { for i, hit := range sRes.Hits.Hits { log.Info("this is repo query " + fmt.Sprint(i) + " result.") recordSource := make(map[string]interface{}) source, err := hit.Source.MarshalJSON() if err == nil { err = json.Unmarshal(source, &recordSource) if err == nil { record := make(map[string]interface{}) record["id"] = hit.Id record["alias"] = getLabelValue("alias", recordSource, hit.Highlight) record["real_name"] = recordSource["name"] record["owner_name"] = recordSource["owner_name"] if recordSource["description"] != nil { desc := getLabelValue("description", recordSource, hit.Highlight) record["description"] = dealLongText(desc, Key, hit.MatchedQueries) } else { record["description"] = "" } record["hightTopics"] = jsonStrToArray(getLabelValue("topics", recordSource, hit.Highlight)) record["num_watches"] = recordSource["num_watches"] record["num_stars"] = recordSource["num_stars"] record["num_forks"] = recordSource["num_forks"] record["lower_alias"] = recordSource["lower_alias"] if recordSource["topics"] != nil { topicsStr := recordSource["topics"].(string) log.Info("topicsStr=" + topicsStr) if topicsStr != "null" { record["topics"] = jsonStrToArray(topicsStr) } } if recordSource["avatar"] != nil { avatarstr := recordSource["avatar"].(string) if len(avatarstr) == 0 { record["avatar"] = setting.RepositoryAvatarFallbackImage } else { record["avatar"] = setting.AppSubURL + "/repo-avatars/" + avatarstr } } record["updated_unix"] = recordSource["updated_unix"] setUpdateHtml(record, recordSource["updated_unix"].(string), language) record["lang"] = recordSource["lang"] record["is_private"] = false result = append(result, record) } else { log.Info("deal repo source error," + err.Error()) } } else { log.Info("deal repo source error," + err.Error()) } } } returnObj := &SearchRes{ Total: total, Result: result, } return returnObj } func setUpdateHtml(record map[string]interface{}, updated_unix string, language string) { timeInt, err := strconv.ParseInt(updated_unix, 10, 64) if err == nil { record["updated_html"] = timeutil.TimeSinceUnix(timeutil.TimeStamp(timeInt), language) } } func jsonStrToArray(str string) []string { b := []byte(str) strs := make([]string, 0) err := json.Unmarshal(b, &strs) if err != nil { log.Info("convert str arrar error, str=" + str) } return strs } func dealLongText(text string, Key string, MatchedQueries []string) string { var isNeedToDealText bool isNeedToDealText = false if len(MatchedQueries) > 0 && Key != "" { if MatchedQueries[0] == "f_second" || MatchedQueries[0] == "f_third" { isNeedToDealText = true } } return truncLongText(text, isNeedToDealText) } func truncLongText(text string, isNeedToDealText bool) string { startStr := "color=" textRune := []rune(text) stringlen := len(textRune) if isNeedToDealText && stringlen > 200 { index := findFont(textRune, []rune(startStr)) if index > 0 { start := index - 50 if start < 0 { start = 0 } end := index + 150 if end >= stringlen { end = stringlen } return trimFontHtml(textRune[start:end]) + "..." } else { return trimFontHtml(textRune[0:200]) + "..." } } else { if stringlen > 200 { return trimFontHtml(textRune[0:200]) + "..." } else { return text } } } func trimFontHtml(text []rune) string { startRune := rune('<') endRune := rune('>') count := 0 i := 0 for ; i < len(text); i++ { if text[i] == startRune { //start < re := false j := i + 1 for ; j < len(text); j++ { if text[j] == endRune { re = true break } } if re { //found > i = j count++ } else { if count%2 == 1 { return string(text[0:i]) + "" } else { return string(text[0:i]) } } } } if count%2 == 1 { return string(text[0:i]) + "" } else { return string(text[0:i]) } } func trimHrefHtml(result string) string { result = strings.Replace(result, "", "", -1) result = strings.Replace(result, "\n", "", -1) var index int for { index = findSubstr(result, 0, "") if sIndex != -1 { result = result[0:index] + result[sIndex+1:] } else { result = result[0:index] + result[index+2:] } } else { break } } return result } func findFont(text []rune, childText []rune) int { for i := 0; i < len(text); i++ { if text[i] == childText[0] { re := true for j, k := range childText { if k != text[i+j] { re = false break } } if re { return i } } } return -1 } func findSubstr(text string, startindex int, childText string) int { for i := startindex; i < len(text); i++ { if text[i] == childText[0] { re := true for k := range childText { if childText[k] != text[i+k] { re = false break } } if re { return i } } } return -1 } func searchUserOrOrg(ctx *context.Context, TableName string, Key string, Page int, PageSize int, IsQueryUser bool, OnlyReturnNum bool) { /* 用户或者组织 ES名称: user-es-index 搜索: name , 名称 full_name 全名 description 描述或者简介 排序: created_unix 名称字母序 */ SortBy := ctx.Query("SortBy") ascending := ctx.QueryBool("Ascending") boolQ := elastic.NewBoolQuery() typeValue := 1 if IsQueryUser { typeValue = 0 } UserOrOrgQuery := elastic.NewTermQuery("type", typeValue) if Key != "" { boolKeyQ := elastic.NewBoolQuery() log.Info("user or org Key=" + Key) nameQuery := elastic.NewMatchQuery("name", Key).Boost(2).QueryName("f_first") full_nameQuery := elastic.NewMatchQuery("full_name", Key).Boost(1.5).QueryName("f_second") descriptionQuery := elastic.NewMatchQuery("description", Key).Boost(1).QueryName("f_third") boolKeyQ.Should(nameQuery, full_nameQuery, descriptionQuery) boolQ.Must(UserOrOrgQuery, boolKeyQ) } else { boolQ.Must(UserOrOrgQuery) } res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From((Page - 1) * PageSize).Size(PageSize).Highlight(queryHighlight("name", "full_name", "description")).Do(ctx.Req.Context()) if err == nil { searchJson, _ := json.Marshal(res) log.Info("searchJson=" + string(searchJson)) result := makeUserOrOrgResult(res, Key, ctx, OnlyReturnNum) ctx.JSON(200, result) } else { log.Info("query es error," + err.Error()) ctx.JSON(200, "") } } func getLabelValue(key string, recordSource map[string]interface{}, searchHighliht elastic.SearchHitHighlight) string { if value, ok := searchHighliht[key]; !ok { if recordSource[key] != nil { return recordSource[key].(string) } else { return "" } } else { return value[0] } } func makeUserOrOrgResult(sRes *elastic.SearchResult, Key string, ctx *context.Context, OnlyReturnNum bool) *SearchRes { total := sRes.Hits.TotalHits.Value result := make([]map[string]interface{}, 0) if !OnlyReturnNum { for i, hit := range sRes.Hits.Hits { log.Info("this is user query " + fmt.Sprint(i) + " result.") recordSource := make(map[string]interface{}) source, err := hit.Source.MarshalJSON() if err == nil { err = json.Unmarshal(source, &recordSource) if err == nil { record := make(map[string]interface{}) record["id"] = hit.Id record["name"] = getLabelValue("name", recordSource, hit.Highlight) record["real_name"] = recordSource["name"] record["full_name"] = getLabelValue("full_name", recordSource, hit.Highlight) if recordSource["description"] != nil { desc := getLabelValue("description", recordSource, hit.Highlight) record["description"] = dealLongText(desc, Key, hit.MatchedQueries) } else { record["description"] = "" } if ctx.User != nil { record["email"] = recordSource["email"] } else { record["email"] = "" } record["location"] = recordSource["location"] record["website"] = recordSource["website"] record["num_repos"] = recordSource["num_repos"] record["num_teams"] = recordSource["num_teams"] record["num_members"] = recordSource["num_members"] record["avatar"] = strings.TrimRight(setting.AppSubURL, "/") + "/user/avatar/" + recordSource["name"].(string) + "/" + strconv.Itoa(-1) record["updated_unix"] = recordSource["updated_unix"] record["created_unix"] = recordSource["created_unix"] record["add_time"] = getAddTime(recordSource["created_unix"].(string)) result = append(result, record) } else { log.Info("deal user source error," + err.Error()) } } else { log.Info("deal user source error," + err.Error()) } } } returnObj := &SearchRes{ Total: total, Result: result, } return returnObj } func getAddTime(time string) string { timeInt, err := strconv.ParseInt(time, 10, 64) if err == nil { t := timeutil.TimeStamp(timeInt) return t.FormatShort() } return "" } func searchDataSet(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool) { /* 数据集,ES名称:dataset-es-index 搜索: title , 名称 description 描述 category 标签 file_name 数据集文件名称 排序: download_times */ log.Info("query searchdataset start") SortBy := ctx.Query("SortBy") ascending := ctx.QueryBool("Ascending") PrivateTotal := ctx.QueryInt("PrivateTotal") WebTotal := ctx.QueryInt("WebTotal") language := ctx.Query("language") if language == "" { language = "zh-CN" } from := (Page - 1) * PageSize if from == 0 { WebTotal = 0 } resultObj := &SearchRes{} log.Info("WebTotal=" + fmt.Sprint(WebTotal)) log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal)) resultObj.Result = make([]map[string]interface{}, 0) if ctx.User != nil && (from < PrivateTotal || from == 0) { log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil)) datasets, count, err := models.SearchDatasetBySQL(Page, PageSize, Key, ctx.User.ID) if err != nil { ctx.JSON(200, "") return } resultObj.PrivateTotal = count datasetSize := len(datasets) if datasetSize > 0 { log.Info("Query private dataset number is:" + fmt.Sprint(datasetSize) + " count=" + fmt.Sprint(count)) makePrivateDataSet(datasets, resultObj, Key, language) } else { log.Info("not found private dataset, keyword=" + Key) } if datasetSize >= PageSize { if WebTotal > 0 { //next page, not first query. resultObj.Total = int64(WebTotal) ctx.JSON(200, resultObj) return } } } else { resultObj.PrivateTotal = int64(PrivateTotal) } from = from - PrivateTotal if from < 0 { from = 0 } Size := PageSize - len(resultObj.Result) boolQ := elastic.NewBoolQuery() if Key != "" { nameQuery := elastic.NewMatchQuery("title", Key).Boost(2).QueryName("f_first") descQuery := elastic.NewMatchQuery("description", Key).Boost(1.5).QueryName("f_second") fileNameQuery := elastic.NewMatchQuery("file_name", Key).Boost(1).QueryName("f_third") categoryQuery := elastic.NewMatchQuery("category", Key).Boost(1).QueryName("f_fourth") boolQ.Should(nameQuery, descQuery, categoryQuery, fileNameQuery) res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Highlight(queryHighlight("title", "description", "file_name", "category")).Do(ctx.Req.Context()) if err == nil { searchJson, _ := json.Marshal(res) log.Info("searchJson=" + string(searchJson)) esresult := makeDatasetResult(res, Key, OnlyReturnNum, language) resultObj.Total = resultObj.PrivateTotal + esresult.Total log.Info("query dataset es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total)) resultObj.Result = append(resultObj.Result, esresult.Result...) ctx.JSON(200, resultObj) } else { log.Info("query es error," + err.Error()) } } else { log.Info("query all datasets.") //搜索的属性要指定{"timestamp":{"unmapped_type":"date"}} res, err := client.Search(TableName).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Do(ctx.Req.Context()) if err == nil { searchJson, _ := json.Marshal(res) log.Info("searchJson=" + string(searchJson)) esresult := makeDatasetResult(res, "", OnlyReturnNum, language) resultObj.Total = resultObj.PrivateTotal + esresult.Total log.Info("query dataset es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total)) resultObj.Result = append(resultObj.Result, esresult.Result...) ctx.JSON(200, resultObj) } else { log.Info("query es error," + err.Error()) ctx.JSON(200, "") } } } func makePrivateDataSet(datasets []*models.Dataset, res *SearchRes, Key string, language string) { for _, dataset := range datasets { record := make(map[string]interface{}) record["id"] = dataset.ID userId := dataset.UserID user, errUser := models.GetUserByID(userId) if errUser == nil { record["owerName"] = user.GetDisplayName() record["avatar"] = user.RelAvatarLink() } repo, errRepo := models.GetRepositoryByID(dataset.RepoID) if errRepo == nil { log.Info("repo_url=" + repo.FullName()) record["repoUrl"] = repo.FullName() record["avatar"] = repo.RelAvatarLink() } else { log.Info("repo err=" + errRepo.Error()) } record["title"] = makeHighLight(Key, dataset.Title) record["description"] = truncLongText(makeHighLight(Key, dataset.Description), true) record["category"] = dataset.Category record["task"] = dataset.Task record["download_times"] = dataset.DownloadTimes record["created_unix"] = dataset.CreatedUnix record["updated_unix"] = repo.UpdatedUnix record["updated_html"] = timeutil.TimeSinceUnix(repo.UpdatedUnix, language) res.Result = append(res.Result, record) } } func makeDatasetResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes { total := sRes.Hits.TotalHits.Value result := make([]map[string]interface{}, 0) if !OnlyReturnNum { for i, hit := range sRes.Hits.Hits { log.Info("this is dataset query " + fmt.Sprint(i) + " result.") recordSource := make(map[string]interface{}) source, err := hit.Source.MarshalJSON() if err == nil { err = json.Unmarshal(source, &recordSource) if err == nil { record := make(map[string]interface{}) record["id"] = hit.Id userIdStr := recordSource["user_id"].(string) userId, cerr := strconv.ParseInt(userIdStr, 10, 64) if cerr == nil { user, errUser := models.GetUserByID(userId) if errUser == nil { record["owerName"] = user.GetDisplayName() record["avatar"] = user.RelAvatarLink() } } setRepoInfo(recordSource, record) record["title"] = getLabelValue("title", recordSource, hit.Highlight) record["category"] = getLabelValue("category", recordSource, hit.Highlight) if recordSource["description"] != nil { desc := getLabelValue("description", recordSource, hit.Highlight) record["description"] = dealLongText(desc, Key, hit.MatchedQueries) } else { record["description"] = "" } record["file_name"] = getDatasetFileName(getLabelValue("file_name", recordSource, hit.Highlight)) record["task"] = recordSource["task"] record["download_times"] = recordSource["download_times"] record["created_unix"] = recordSource["created_unix"] setUpdateHtml(record, recordSource["updated_unix"].(string), language) result = append(result, record) } else { log.Info("deal dataset source error," + err.Error()) } } else { log.Info("deal dataset source error," + err.Error()) } } } returnObj := &SearchRes{ Total: total, Result: result, } return returnObj } func getDatasetFileName(fileName string) string { slices := strings.Split(fileName, "-#,#-") fileName = strings.Join(slices, ", ") return fileName } func searchIssueOrPr(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool, issueOrPr string) { /* 任务,合并请求 ES名称:issue-es-index 搜索: name character varying(255) , 标题 content text, 内容 comment text, 评论 排序: updated_unix */ SortBy := ctx.Query("SortBy") ascending := ctx.QueryBool("Ascending") PrivateTotal := ctx.QueryInt("PrivateTotal") WebTotal := ctx.QueryInt("WebTotal") language := ctx.Query("language") if language == "" { language = "zh-CN" } from := (Page - 1) * PageSize if from == 0 { WebTotal = 0 } resultObj := &SearchRes{} log.Info("WebTotal=" + fmt.Sprint(WebTotal)) log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal)) resultObj.Result = make([]map[string]interface{}, 0) isPull := false if issueOrPr == "t" { isPull = true } if ctx.User != nil && (from < PrivateTotal || from == 0) { log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil)) issues, count, err := models.SearchPrivateIssueOrPr(Page, PageSize, Key, isPull, ctx.User.ID) if err != nil { ctx.JSON(200, "") return } resultObj.PrivateTotal = count issuesSize := len(issues) if issuesSize > 0 { log.Info("Query private repo issue number is:" + fmt.Sprint(issuesSize) + " count=" + fmt.Sprint(count)) makePrivateIssueOrPr(issues, resultObj, Key, language) } else { log.Info("not found private repo issue,keyword=" + Key) } if issuesSize >= PageSize { if WebTotal > 0 { //next page, not first query. resultObj.Total = int64(WebTotal) ctx.JSON(200, resultObj) return } } } else { resultObj.PrivateTotal = int64(PrivateTotal) } from = from - PrivateTotal if from < 0 { from = 0 } Size := PageSize - len(resultObj.Result) boolQ := elastic.NewBoolQuery() isIssueQuery := elastic.NewTermQuery("is_pull", issueOrPr) if Key != "" { boolKeyQ := elastic.NewBoolQuery() log.Info("issue Key=" + Key) nameQuery := elastic.NewMatchQuery("name", Key).Boost(2).QueryName("f_first") contentQuery := elastic.NewMatchQuery("content", Key).Boost(1.5).QueryName("f_second") commentQuery := elastic.NewMatchQuery("comment", Key).Boost(1).QueryName("f_third") boolKeyQ.Should(nameQuery, contentQuery, commentQuery) boolQ.Must(isIssueQuery, boolKeyQ) } else { boolQ.Must(isIssueQuery) } res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Highlight(queryHighlight("name", "content", "comment")).Do(ctx.Req.Context()) if err == nil { searchJson, _ := json.Marshal(res) log.Info("searchJson=" + string(searchJson)) esresult := makeIssueResult(res, Key, OnlyReturnNum, language) resultObj.Total = resultObj.PrivateTotal + esresult.Total log.Info("query issue es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total)) resultObj.Result = append(resultObj.Result, esresult.Result...) ctx.JSON(200, resultObj) } else { log.Info("query es error," + err.Error()) } } func queryHighlight(names ...string) *elastic.Highlight { re := elastic.NewHighlight() for i := 0; i < len(names); i++ { field := &elastic.HighlighterField{ Name: names[i], } re.Fields(field) } re.PreTags("") re.PostTags("") return re } func setRepoInfo(recordSource map[string]interface{}, record map[string]interface{}) { repoIdstr := recordSource["repo_id"].(string) repoId, cerr := strconv.ParseInt(repoIdstr, 10, 64) if cerr == nil { repo, errRepo := models.GetRepositoryByID(repoId) if errRepo == nil { log.Info("repo_url=" + repo.FullName()) record["repoUrl"] = repo.FullName() record["avatar"] = repo.RelAvatarLink() } else { log.Info("repo err=" + errRepo.Error()) } } else { log.Info("parse int err=" + cerr.Error()) } } func makePrivateIssueOrPr(issues []*models.Issue, res *SearchRes, Key string, language string) { for _, issue := range issues { record := make(map[string]interface{}) record["id"] = issue.ID record["repo_id"] = issue.RepoID repo, errRepo := models.GetRepositoryByID(issue.RepoID) if errRepo == nil { log.Info("repo_url=" + repo.FullName()) record["repoUrl"] = repo.FullName() record["avatar"] = repo.RelAvatarLink() } else { log.Info("repo err=" + errRepo.Error()) } record["name"] = makeHighLight(Key, issue.Title) record["content"] = truncLongText(makeHighLight(Key, issue.Content), true) if issue.IsPull { pr, err1 := issue.GetPullRequest() if err1 == nil && pr != nil { record["pr_id"] = pr.ID } } record["index"] = issue.Index record["num_comments"] = issue.NumComments record["is_closed"] = issue.IsClosed record["updated_unix"] = issue.UpdatedUnix record["updated_html"] = timeutil.TimeSinceUnix(issue.UpdatedUnix, language) res.Result = append(res.Result, record) } } func makeIssueResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes { total := sRes.Hits.TotalHits.Value result := make([]map[string]interface{}, 0) if !OnlyReturnNum { for i, hit := range sRes.Hits.Hits { log.Info("this is issue query " + fmt.Sprint(i) + " result.") recordSource := make(map[string]interface{}) source, err := hit.Source.MarshalJSON() if err == nil { err = json.Unmarshal(source, &recordSource) if err == nil { record := make(map[string]interface{}) record["id"] = hit.Id record["repo_id"] = recordSource["repo_id"] log.Info("recordSource[\"repo_id\"]=" + fmt.Sprint(recordSource["repo_id"])) setRepoInfo(recordSource, record) record["name"] = getLabelValue("name", recordSource, hit.Highlight) if recordSource["content"] != nil { desc := getLabelValue("content", recordSource, hit.Highlight) record["content"] = dealLongText(desc, Key, hit.MatchedQueries) if _, ok := hit.Highlight["content"]; !ok { if _, ok_comment := hit.Highlight["comment"]; ok_comment { desc := getLabelValue("comment", recordSource, hit.Highlight) record["content"] = trimHrefHtml(dealLongText(desc, Key, hit.MatchedQueries)) } } } else { if recordSource["comment"] != nil { desc := getLabelValue("comment", recordSource, hit.Highlight) record["content"] = dealLongText(desc, Key, hit.MatchedQueries) } } if recordSource["pr_id"] != nil { record["pr_id"] = recordSource["pr_id"] } log.Info("index=" + recordSource["index"].(string)) record["index"] = recordSource["index"] record["num_comments"] = recordSource["num_comments"] record["is_closed"] = recordSource["is_closed"] record["updated_unix"] = recordSource["updated_unix"] setUpdateHtml(record, recordSource["updated_unix"].(string), language) result = append(result, record) } else { log.Info("deal issue source error," + err.Error()) } } else { log.Info("deal issue source error," + err.Error()) } } } returnObj := &SearchRes{ Total: total, Result: result, } return returnObj }