You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

search.go 36 kB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  1. package routers
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "sort"
  6. "strconv"
  7. "strings"
  8. "code.gitea.io/gitea/models"
  9. "code.gitea.io/gitea/modules/context"
  10. "code.gitea.io/gitea/modules/log"
  11. "code.gitea.io/gitea/modules/setting"
  12. "code.gitea.io/gitea/modules/timeutil"
  13. "github.com/olivere/elastic/v7"
  14. )
  15. type SearchRes struct {
  16. Total int64
  17. Result []map[string]interface{}
  18. PrivateTotal int64
  19. }
  20. var client *elastic.Client
  21. func InitESClient() {
  22. ESSearchUrl := setting.ESSearchURL
  23. var err error
  24. client, err = elastic.NewClient(elastic.SetSniff(false), elastic.SetURL(ESSearchUrl))
  25. if err != nil {
  26. log.Info("es init error.")
  27. //panic(err)
  28. }
  29. }
  30. func EmptySearch(ctx *context.Context) {
  31. log.Info("search template.")
  32. ctx.Data["Keyword"] = ""
  33. ctx.HTML(200, "explore/search_new")
  34. }
  35. func Search(ctx *context.Context) {
  36. log.Info("search template.")
  37. keyword := strings.Trim(ctx.Query("q"), " ")
  38. ctx.Data["Keyword"] = keyword
  39. ctx.Data["SortType"] = "newest"
  40. ctx.HTML(200, "explore/search_new")
  41. }
  42. func SearchApi(ctx *context.Context) {
  43. TableName := ctx.Query("TableName")
  44. Key := ctx.Query("Key")
  45. Page := ctx.QueryInt("Page")
  46. PageSize := ctx.QueryInt("PageSize")
  47. OnlyReturnNum := ctx.QueryBool("OnlyReturnNum")
  48. OnlySearchLabel := ctx.QueryBool("OnlySearchLabel")
  49. if Page <= 0 {
  50. Page = 1
  51. }
  52. if PageSize <= 0 || PageSize > 200 {
  53. PageSize = setting.UI.IssuePagingNum
  54. }
  55. if Key != "" && !OnlyReturnNum {
  56. go models.SaveSearchKeywordToDb(Key)
  57. }
  58. if TableName == "repository" {
  59. if OnlySearchLabel {
  60. searchRepoByLabel(ctx, Key, Page, PageSize)
  61. } else {
  62. searchRepo(ctx, "repository-es-index", Key, Page, PageSize, OnlyReturnNum)
  63. }
  64. return
  65. } else if TableName == "issue" {
  66. searchIssueOrPr(ctx, "issue-es-index", Key, Page, PageSize, OnlyReturnNum, "f")
  67. return
  68. } else if TableName == "user" {
  69. searchUserOrOrg(ctx, "user-es-index", Key, Page, PageSize, true, OnlyReturnNum)
  70. return
  71. } else if TableName == "org" {
  72. searchUserOrOrg(ctx, "user-es-index", Key, Page, PageSize, false, OnlyReturnNum)
  73. return
  74. } else if TableName == "dataset" {
  75. searchDataSet(ctx, "dataset-es-index", Key, Page, PageSize, OnlyReturnNum)
  76. return
  77. } else if TableName == "pr" {
  78. searchIssueOrPr(ctx, "issue-es-index", Key, Page, PageSize, OnlyReturnNum, "t")
  79. //searchPR(ctx, "issue-es-index", Key, Page, PageSize, OnlyReturnNum)
  80. return
  81. }
  82. }
  83. func searchRepoByLabel(ctx *context.Context, Key string, Page int, PageSize int) {
  84. /*
  85. 项目, ES名称: repository-es-index
  86. 搜索:
  87. name character varying(255) , 项目名称
  88. description text, 项目描述
  89. topics json, 标签
  90. 排序:
  91. updated_unix
  92. num_watches,
  93. num_stars,
  94. num_forks,
  95. */
  96. SortBy := ctx.Query("SortBy")
  97. PrivateTotal := ctx.QueryInt("PrivateTotal")
  98. WebTotal := ctx.QueryInt("WebTotal")
  99. ascending := ctx.QueryBool("Ascending")
  100. language := ctx.Query("language")
  101. if language == "" {
  102. language = "zh-CN"
  103. }
  104. from := (Page - 1) * PageSize
  105. resultObj := &SearchRes{}
  106. log.Info("WebTotal=" + fmt.Sprint(WebTotal))
  107. log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
  108. resultObj.Result = make([]map[string]interface{}, 0)
  109. if from == 0 {
  110. WebTotal = 0
  111. }
  112. if ctx.User != nil && (from < PrivateTotal || from == 0) {
  113. orderBy := models.SearchOrderByRecentUpdated
  114. switch SortBy {
  115. case "updated_unix.keyword":
  116. orderBy = models.SearchOrderByRecentUpdated
  117. case "num_stars":
  118. orderBy = models.SearchOrderByStarsReverse
  119. case "num_forks":
  120. orderBy = models.SearchOrderByForksReverse
  121. case "num_watches":
  122. orderBy = models.SearchOrderByWatches
  123. }
  124. log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
  125. repos, count, err := models.SearchRepository(&models.SearchRepoOptions{
  126. ListOptions: models.ListOptions{
  127. Page: Page,
  128. PageSize: PageSize,
  129. },
  130. Actor: ctx.User,
  131. OrderBy: orderBy,
  132. Private: true,
  133. OnlyPrivate: true,
  134. TopicOnly: true,
  135. TopicName: Key,
  136. IncludeDescription: setting.UI.SearchRepoDescription,
  137. })
  138. if err != nil {
  139. ctx.JSON(200, "")
  140. return
  141. }
  142. resultObj.PrivateTotal = count
  143. if repos.Len() > 0 {
  144. log.Info("Query private repo number is:" + fmt.Sprint(repos.Len()))
  145. makePrivateRepo(repos, resultObj, Key, language)
  146. } else {
  147. log.Info("not found private repo,keyword=" + Key)
  148. }
  149. if repos.Len() >= PageSize {
  150. if WebTotal > 0 {
  151. resultObj.Total = int64(WebTotal)
  152. ctx.JSON(200, resultObj)
  153. return
  154. }
  155. }
  156. } else {
  157. if ctx.User == nil {
  158. resultObj.PrivateTotal = 0
  159. } else {
  160. resultObj.PrivateTotal = int64(PrivateTotal)
  161. }
  162. }
  163. from = from - PrivateTotal
  164. if from < 0 {
  165. from = 0
  166. }
  167. Size := PageSize - len(resultObj.Result)
  168. log.Info("query searchRepoByLabel start")
  169. if Key != "" {
  170. boolQ := elastic.NewBoolQuery()
  171. topicsQuery := elastic.NewMatchQuery("topics", Key)
  172. boolQ.Should(topicsQuery)
  173. res, err := client.Search("repository-es-index").Query(boolQ).SortBy(getSort(SortBy, ascending)).From(from).Size(Size).Highlight(queryHighlight("topics")).Do(ctx.Req.Context())
  174. if err == nil {
  175. searchJson, _ := json.Marshal(res)
  176. log.Info("searchJson=" + string(searchJson))
  177. esresult := makeRepoResult(res, "", false, language)
  178. resultObj.Total = resultObj.PrivateTotal + esresult.Total
  179. resultObj.Result = append(resultObj.Result, esresult.Result...)
  180. ctx.JSON(200, resultObj)
  181. } else {
  182. log.Info("query es error," + err.Error())
  183. ctx.JSON(200, "")
  184. }
  185. } else {
  186. ctx.JSON(200, "")
  187. }
  188. }
  189. func getSort(SortBy string, ascending bool) elastic.Sorter {
  190. var sort elastic.Sorter
  191. sort = elastic.NewScoreSort()
  192. if SortBy != "" {
  193. if SortBy == "default" {
  194. return sort
  195. }
  196. return elastic.NewFieldSort(SortBy).Order(ascending)
  197. }
  198. return sort
  199. }
  200. func searchRepo(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool) {
  201. /*
  202. 项目, ES名称: repository-es-index
  203. 搜索:
  204. name character varying(255) , 项目名称
  205. description text, 项目描述
  206. topics json, 标签
  207. 排序:
  208. updated_unix
  209. num_watches,
  210. num_stars,
  211. num_forks,
  212. */
  213. SortBy := ctx.Query("SortBy")
  214. PrivateTotal := ctx.QueryInt("PrivateTotal")
  215. WebTotal := ctx.QueryInt("WebTotal")
  216. ascending := ctx.QueryBool("Ascending")
  217. from := (Page - 1) * PageSize
  218. resultObj := &SearchRes{}
  219. log.Info("WebTotal=" + fmt.Sprint(WebTotal))
  220. log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
  221. resultObj.Result = make([]map[string]interface{}, 0)
  222. if from == 0 {
  223. WebTotal = 0
  224. }
  225. language := ctx.Query("language")
  226. if language == "" {
  227. language = "zh-CN"
  228. }
  229. if ctx.User != nil && (from < PrivateTotal || from == 0) {
  230. orderBy := models.SearchOrderByRecentUpdated
  231. switch SortBy {
  232. case "updated_unix.keyword":
  233. orderBy = models.SearchOrderByRecentUpdated
  234. case "num_stars":
  235. orderBy = models.SearchOrderByStarsReverse
  236. case "num_forks":
  237. orderBy = models.SearchOrderByForksReverse
  238. case "num_watches":
  239. orderBy = models.SearchOrderByWatches
  240. }
  241. log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
  242. repos, count, err := models.SearchRepository(&models.SearchRepoOptions{
  243. ListOptions: models.ListOptions{
  244. Page: Page,
  245. PageSize: PageSize,
  246. },
  247. Actor: ctx.User,
  248. OrderBy: orderBy,
  249. Private: true,
  250. OnlyPrivate: true,
  251. Keyword: Key,
  252. IncludeDescription: setting.UI.SearchRepoDescription,
  253. OnlySearchPrivate: true,
  254. })
  255. if err != nil {
  256. ctx.JSON(200, "")
  257. return
  258. }
  259. resultObj.PrivateTotal = count
  260. if repos.Len() > 0 {
  261. log.Info("Query private repo number is:" + fmt.Sprint(repos.Len()))
  262. makePrivateRepo(repos, resultObj, Key, language)
  263. } else {
  264. log.Info("not found private repo,keyword=" + Key)
  265. }
  266. if repos.Len() >= PageSize {
  267. if WebTotal > 0 {
  268. resultObj.Total = int64(WebTotal)
  269. ctx.JSON(200, resultObj)
  270. return
  271. }
  272. }
  273. } else {
  274. if ctx.User == nil {
  275. resultObj.PrivateTotal = 0
  276. } else {
  277. resultObj.PrivateTotal = int64(PrivateTotal)
  278. }
  279. }
  280. from = from - PrivateTotal
  281. if from < 0 {
  282. from = 0
  283. }
  284. Size := PageSize - len(resultObj.Result)
  285. log.Info("query searchRepo start")
  286. if Key != "" {
  287. boolQ := elastic.NewBoolQuery()
  288. nameQuery := elastic.NewMatchQuery("alias", Key).Boost(1024).QueryName("f_first")
  289. descriptionQuery := elastic.NewMatchQuery("description", Key).Boost(1.5).QueryName("f_second")
  290. topicsQuery := elastic.NewMatchQuery("topics", Key).Boost(1).QueryName("f_third")
  291. boolQ.Should(nameQuery, descriptionQuery, topicsQuery)
  292. res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending)).From(from).Size(Size).Highlight(queryHighlight("alias", "description", "topics")).Do(ctx.Req.Context())
  293. if err == nil {
  294. searchJson, _ := json.Marshal(res)
  295. log.Info("searchJson=" + string(searchJson))
  296. esresult := makeRepoResult(res, Key, OnlyReturnNum, language)
  297. resultObj.Total = resultObj.PrivateTotal + esresult.Total
  298. isNeedSort := false
  299. if len(resultObj.Result) > 0 {
  300. isNeedSort = true
  301. }
  302. resultObj.Result = append(resultObj.Result, esresult.Result...)
  303. if isNeedSort {
  304. sortRepo(resultObj.Result, SortBy, ascending)
  305. }
  306. ctx.JSON(200, resultObj)
  307. } else {
  308. log.Info("query es error," + err.Error())
  309. ctx.JSON(200, "")
  310. }
  311. } else {
  312. log.Info("query all content.")
  313. //搜索的属性要指定{"timestamp":{"unmapped_type":"date"}}
  314. res, err := client.Search(TableName).SortBy(getSort(SortBy, ascending)).From(from).Size(Size).Do(ctx.Req.Context())
  315. if err == nil {
  316. searchJson, _ := json.Marshal(res)
  317. log.Info("searchJson=" + string(searchJson))
  318. esresult := makeRepoResult(res, "", OnlyReturnNum, language)
  319. resultObj.Total = resultObj.PrivateTotal + esresult.Total
  320. resultObj.Result = append(resultObj.Result, esresult.Result...)
  321. ctx.JSON(200, resultObj)
  322. } else {
  323. log.Info("query es error," + err.Error())
  324. ctx.JSON(200, "")
  325. }
  326. }
  327. }
  328. func sortRepo(Result []map[string]interface{}, SortBy string, ascending bool) {
  329. orderBy := ""
  330. switch SortBy {
  331. case "updated_unix.keyword":
  332. orderBy = "updated_unix"
  333. case "num_stars":
  334. orderBy = "num_stars"
  335. case "num_forks":
  336. orderBy = "num_forks"
  337. case "num_watches":
  338. orderBy = "num_watches"
  339. }
  340. sort.Slice(Result, func(i, j int) bool {
  341. return getInt(Result[i][orderBy], orderBy) > getInt(Result[j][orderBy], orderBy)
  342. })
  343. }
  344. func getInt(tmp interface{}, orderBy string) int64 {
  345. timeInt, err := strconv.ParseInt(fmt.Sprint(tmp), 10, 64)
  346. if err == nil {
  347. return timeInt
  348. } else {
  349. log.Info("convert " + orderBy + " error type=" + fmt.Sprint(tmp))
  350. }
  351. return -1
  352. }
  353. func makePrivateRepo(repos models.RepositoryList, res *SearchRes, keyword string, language string) {
  354. for _, repo := range repos {
  355. record := make(map[string]interface{})
  356. record["id"] = repo.ID
  357. record["name"] = makeHighLight(keyword, repo.Name)
  358. record["real_name"] = repo.Name
  359. record["owner_name"] = repo.OwnerName
  360. record["description"] = truncLongText(makeHighLight(keyword, repo.Description), true)
  361. hightTopics := make([]string, 0)
  362. if len(repo.Topics) > 0 {
  363. for _, t := range repo.Topics {
  364. hightTopics = append(hightTopics, makeHighLight(keyword, t))
  365. }
  366. }
  367. record["hightTopics"] = hightTopics
  368. record["num_watches"] = repo.NumWatches
  369. record["num_stars"] = repo.NumStars
  370. record["num_forks"] = repo.NumForks
  371. record["alias"] = truncLongText(makeHighLight(keyword, repo.Alias), true)
  372. record["lower_alias"] = repo.LowerAlias
  373. record["topics"] = repo.Topics
  374. record["avatar"] = repo.RelAvatarLink()
  375. if len(repo.RelAvatarLink()) == 0 {
  376. record["avatar"] = setting.RepositoryAvatarFallbackImage
  377. }
  378. record["updated_unix"] = repo.UpdatedUnix
  379. record["updated_html"] = timeutil.TimeSinceUnix(repo.UpdatedUnix, language)
  380. lang, err := repo.GetTopLanguageStats(1)
  381. if err == nil && len(lang) > 0 {
  382. record["lang"] = lang[0].Language
  383. } else {
  384. record["lang"] = ""
  385. }
  386. record["is_private"] = true
  387. res.Result = append(res.Result, record)
  388. }
  389. }
  390. func makeHighLight(keyword string, dest string) string {
  391. dest = replaceIngoreUpperOrLower(dest, strings.ToLower(dest), strings.ToLower(keyword))
  392. return dest
  393. }
  394. func replaceIngoreUpperOrLower(dest string, destLower string, keywordLower string) string {
  395. re := ""
  396. last := 0
  397. lenDestLower := len(destLower)
  398. lenkeywordLower := len(keywordLower)
  399. for i := 0; i < lenDestLower; i++ {
  400. if destLower[i] == keywordLower[0] {
  401. isFind := true
  402. for j := 1; j < lenkeywordLower; j++ {
  403. if (i+j) < lenDestLower && keywordLower[j] != destLower[i+j] {
  404. isFind = false
  405. break
  406. }
  407. }
  408. if isFind && (i+lenkeywordLower) <= lenDestLower {
  409. re += dest[last:i] + "\u003cfont color='red'\u003e" + dest[i:(i+lenkeywordLower)] + "\u003c/font\u003e"
  410. i = i + lenkeywordLower
  411. last = i
  412. }
  413. }
  414. }
  415. if last < lenDestLower {
  416. re += dest[last:lenDestLower]
  417. }
  418. return re
  419. }
  420. func makeRepoResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes {
  421. total := sRes.Hits.TotalHits.Value
  422. result := make([]map[string]interface{}, 0)
  423. if !OnlyReturnNum {
  424. for i, hit := range sRes.Hits.Hits {
  425. log.Info("this is repo query " + fmt.Sprint(i) + " result.")
  426. recordSource := make(map[string]interface{})
  427. source, err := hit.Source.MarshalJSON()
  428. if err == nil {
  429. err = json.Unmarshal(source, &recordSource)
  430. if err == nil {
  431. record := make(map[string]interface{})
  432. record["id"] = hit.Id
  433. record["alias"] = getLabelValue("alias", recordSource, hit.Highlight)
  434. record["real_name"] = recordSource["name"]
  435. record["owner_name"] = recordSource["owner_name"]
  436. if recordSource["description"] != nil {
  437. desc := getLabelValue("description", recordSource, hit.Highlight)
  438. record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
  439. } else {
  440. record["description"] = ""
  441. }
  442. record["hightTopics"] = jsonStrToArray(getLabelValue("topics", recordSource, hit.Highlight))
  443. record["num_watches"] = recordSource["num_watches"]
  444. record["num_stars"] = recordSource["num_stars"]
  445. record["num_forks"] = recordSource["num_forks"]
  446. record["lower_alias"] = recordSource["lower_alias"]
  447. if recordSource["topics"] != nil {
  448. topicsStr := recordSource["topics"].(string)
  449. log.Info("topicsStr=" + topicsStr)
  450. if topicsStr != "null" {
  451. record["topics"] = jsonStrToArray(topicsStr)
  452. }
  453. }
  454. if recordSource["avatar"] != nil {
  455. avatarstr := recordSource["avatar"].(string)
  456. if len(avatarstr) == 0 {
  457. record["avatar"] = setting.RepositoryAvatarFallbackImage
  458. } else {
  459. record["avatar"] = setting.AppSubURL + "/repo-avatars/" + avatarstr
  460. }
  461. }
  462. record["updated_unix"] = recordSource["updated_unix"]
  463. setUpdateHtml(record, recordSource["updated_unix"].(string), language)
  464. record["lang"] = recordSource["lang"]
  465. record["is_private"] = false
  466. result = append(result, record)
  467. } else {
  468. log.Info("deal repo source error," + err.Error())
  469. }
  470. } else {
  471. log.Info("deal repo source error," + err.Error())
  472. }
  473. }
  474. }
  475. returnObj := &SearchRes{
  476. Total: total,
  477. Result: result,
  478. }
  479. return returnObj
  480. }
  481. func setUpdateHtml(record map[string]interface{}, updated_unix string, language string) {
  482. timeInt, err := strconv.ParseInt(updated_unix, 10, 64)
  483. if err == nil {
  484. record["updated_html"] = timeutil.TimeSinceUnix(timeutil.TimeStamp(timeInt), language)
  485. }
  486. }
  487. func jsonStrToArray(str string) []string {
  488. b := []byte(str)
  489. strs := make([]string, 0)
  490. err := json.Unmarshal(b, &strs)
  491. if err != nil {
  492. log.Info("convert str arrar error, str=" + str)
  493. }
  494. return strs
  495. }
  496. func dealLongText(text string, Key string, MatchedQueries []string) string {
  497. var isNeedToDealText bool
  498. isNeedToDealText = false
  499. if len(MatchedQueries) > 0 && Key != "" {
  500. if MatchedQueries[0] == "f_second" || MatchedQueries[0] == "f_third" {
  501. isNeedToDealText = true
  502. }
  503. }
  504. return truncLongText(text, isNeedToDealText)
  505. }
  506. func truncLongText(text string, isNeedToDealText bool) string {
  507. startStr := "color="
  508. textRune := []rune(text)
  509. stringlen := len(textRune)
  510. if isNeedToDealText && stringlen > 200 {
  511. index := findFont(textRune, []rune(startStr))
  512. if index > 0 {
  513. start := index - 50
  514. if start < 0 {
  515. start = 0
  516. }
  517. end := index + 150
  518. if end >= stringlen {
  519. end = stringlen
  520. }
  521. return trimFontHtml(textRune[start:end]) + "..."
  522. } else {
  523. return trimFontHtml(textRune[0:200]) + "..."
  524. }
  525. } else {
  526. if stringlen > 200 {
  527. return trimFontHtml(textRune[0:200]) + "..."
  528. } else {
  529. return text
  530. }
  531. }
  532. }
  533. func trimFontHtml(text []rune) string {
  534. startRune := rune('<')
  535. endRune := rune('>')
  536. count := 0
  537. for i := 0; i < len(text); i++ {
  538. if text[i] == startRune { //start <
  539. re := false
  540. j := i + 1
  541. for ; j < len(text); j++ {
  542. if text[j] == endRune {
  543. re = true
  544. break
  545. }
  546. }
  547. if re { //found >
  548. i = j
  549. count++
  550. } else {
  551. if count%2 == 1 {
  552. return string(text[0:i]) + "</font>"
  553. } else {
  554. return string(text[0:i])
  555. }
  556. }
  557. }
  558. }
  559. return string(text)
  560. }
  561. func trimHrefHtml(result string) string {
  562. result = strings.Replace(result, "</a>", "", -1)
  563. result = strings.Replace(result, "\n", "", -1)
  564. var index int
  565. for {
  566. index = findSubstr(result, 0, "<a")
  567. if index != -1 {
  568. sIndex := findSubstr(result, index+2, ">")
  569. if sIndex != -1 {
  570. result = result[0:index] + result[sIndex+1:]
  571. } else {
  572. result = result[0:index] + result[index+2:]
  573. }
  574. } else {
  575. break
  576. }
  577. }
  578. return result
  579. }
  580. func findFont(text []rune, childText []rune) int {
  581. for i := 0; i < len(text); i++ {
  582. if text[i] == childText[0] {
  583. re := true
  584. for j, k := range childText {
  585. if k != text[i+j] {
  586. re = false
  587. break
  588. }
  589. }
  590. if re {
  591. return i
  592. }
  593. }
  594. }
  595. return -1
  596. }
  597. func findSubstr(text string, startindex int, childText string) int {
  598. for i := startindex; i < len(text); i++ {
  599. if text[i] == childText[0] {
  600. re := true
  601. for k := range childText {
  602. if childText[k] != text[i+k] {
  603. re = false
  604. break
  605. }
  606. }
  607. if re {
  608. return i
  609. }
  610. }
  611. }
  612. return -1
  613. }
  614. func searchUserOrOrg(ctx *context.Context, TableName string, Key string, Page int, PageSize int, IsQueryUser bool, OnlyReturnNum bool) {
  615. /*
  616. 用户或者组织 ES名称: user-es-index
  617. 搜索:
  618. name , 名称
  619. full_name 全名
  620. description 描述或者简介
  621. 排序:
  622. created_unix
  623. 名称字母序
  624. */
  625. SortBy := ctx.Query("SortBy")
  626. ascending := ctx.QueryBool("Ascending")
  627. boolQ := elastic.NewBoolQuery()
  628. typeValue := 1
  629. if IsQueryUser {
  630. typeValue = 0
  631. }
  632. UserOrOrgQuery := elastic.NewTermQuery("type", typeValue)
  633. if Key != "" {
  634. boolKeyQ := elastic.NewBoolQuery()
  635. log.Info("user or org Key=" + Key)
  636. nameQuery := elastic.NewMatchQuery("name", Key).Boost(2).QueryName("f_first")
  637. full_nameQuery := elastic.NewMatchQuery("full_name", Key).Boost(1.5).QueryName("f_second")
  638. descriptionQuery := elastic.NewMatchQuery("description", Key).Boost(1).QueryName("f_third")
  639. boolKeyQ.Should(nameQuery, full_nameQuery, descriptionQuery)
  640. boolQ.Must(UserOrOrgQuery, boolKeyQ)
  641. } else {
  642. boolQ.Must(UserOrOrgQuery)
  643. }
  644. res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending)).From((Page - 1) * PageSize).Size(PageSize).Highlight(queryHighlight("name", "full_name", "description")).Do(ctx.Req.Context())
  645. if err == nil {
  646. searchJson, _ := json.Marshal(res)
  647. log.Info("searchJson=" + string(searchJson))
  648. result := makeUserOrOrgResult(res, Key, ctx, OnlyReturnNum)
  649. ctx.JSON(200, result)
  650. } else {
  651. log.Info("query es error," + err.Error())
  652. ctx.JSON(200, "")
  653. }
  654. }
  655. func getLabelValue(key string, recordSource map[string]interface{}, searchHighliht elastic.SearchHitHighlight) string {
  656. if value, ok := searchHighliht[key]; !ok {
  657. if recordSource[key] != nil {
  658. return recordSource[key].(string)
  659. } else {
  660. return ""
  661. }
  662. } else {
  663. return value[0]
  664. }
  665. }
  666. func makeUserOrOrgResult(sRes *elastic.SearchResult, Key string, ctx *context.Context, OnlyReturnNum bool) *SearchRes {
  667. total := sRes.Hits.TotalHits.Value
  668. result := make([]map[string]interface{}, 0)
  669. if !OnlyReturnNum {
  670. for i, hit := range sRes.Hits.Hits {
  671. log.Info("this is user query " + fmt.Sprint(i) + " result.")
  672. recordSource := make(map[string]interface{})
  673. source, err := hit.Source.MarshalJSON()
  674. if err == nil {
  675. err = json.Unmarshal(source, &recordSource)
  676. if err == nil {
  677. record := make(map[string]interface{})
  678. record["id"] = hit.Id
  679. record["name"] = getLabelValue("name", recordSource, hit.Highlight)
  680. record["real_name"] = recordSource["name"]
  681. record["full_name"] = getLabelValue("full_name", recordSource, hit.Highlight)
  682. if recordSource["description"] != nil {
  683. desc := getLabelValue("description", recordSource, hit.Highlight)
  684. record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
  685. } else {
  686. record["description"] = ""
  687. }
  688. if ctx.User != nil {
  689. record["email"] = recordSource["email"]
  690. } else {
  691. record["email"] = ""
  692. }
  693. record["location"] = recordSource["location"]
  694. record["website"] = recordSource["website"]
  695. record["num_repos"] = recordSource["num_repos"]
  696. record["num_teams"] = recordSource["num_teams"]
  697. record["num_members"] = recordSource["num_members"]
  698. record["avatar"] = strings.TrimRight(setting.AppSubURL, "/") + "/user/avatar/" + recordSource["name"].(string) + "/" + strconv.Itoa(-1)
  699. record["updated_unix"] = recordSource["updated_unix"]
  700. record["created_unix"] = recordSource["created_unix"]
  701. record["add_time"] = getAddTime(recordSource["created_unix"].(string))
  702. result = append(result, record)
  703. } else {
  704. log.Info("deal user source error," + err.Error())
  705. }
  706. } else {
  707. log.Info("deal user source error," + err.Error())
  708. }
  709. }
  710. }
  711. returnObj := &SearchRes{
  712. Total: total,
  713. Result: result,
  714. }
  715. return returnObj
  716. }
  717. func getAddTime(time string) string {
  718. timeInt, err := strconv.ParseInt(time, 10, 64)
  719. if err == nil {
  720. t := timeutil.TimeStamp(timeInt)
  721. return t.FormatShort()
  722. }
  723. return ""
  724. }
  725. func searchDataSet(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool) {
  726. /*
  727. 数据集,ES名称:dataset-es-index
  728. 搜索:
  729. title , 名称
  730. description 描述
  731. category 标签
  732. file_name 数据集文件名称
  733. 排序:
  734. download_times
  735. */
  736. log.Info("query searchdataset start")
  737. SortBy := ctx.Query("SortBy")
  738. ascending := ctx.QueryBool("Ascending")
  739. PrivateTotal := ctx.QueryInt("PrivateTotal")
  740. WebTotal := ctx.QueryInt("WebTotal")
  741. language := ctx.Query("language")
  742. if language == "" {
  743. language = "zh-CN"
  744. }
  745. from := (Page - 1) * PageSize
  746. if from == 0 {
  747. WebTotal = 0
  748. }
  749. resultObj := &SearchRes{}
  750. log.Info("WebTotal=" + fmt.Sprint(WebTotal))
  751. log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
  752. resultObj.Result = make([]map[string]interface{}, 0)
  753. if ctx.User != nil && (from < PrivateTotal || from == 0) {
  754. log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
  755. datasets, count, err := models.SearchDatasetBySQL(Page, PageSize, Key, ctx.User.ID)
  756. if err != nil {
  757. ctx.JSON(200, "")
  758. return
  759. }
  760. resultObj.PrivateTotal = count
  761. datasetSize := len(datasets)
  762. if datasetSize > 0 {
  763. log.Info("Query private dataset number is:" + fmt.Sprint(datasetSize) + " count=" + fmt.Sprint(count))
  764. makePrivateDataSet(datasets, resultObj, Key, language)
  765. } else {
  766. log.Info("not found private dataset, keyword=" + Key)
  767. }
  768. if datasetSize >= PageSize {
  769. if WebTotal > 0 { //next page, not first query.
  770. resultObj.Total = int64(WebTotal)
  771. ctx.JSON(200, resultObj)
  772. return
  773. }
  774. }
  775. } else {
  776. resultObj.PrivateTotal = int64(PrivateTotal)
  777. }
  778. from = from - PrivateTotal
  779. if from < 0 {
  780. from = 0
  781. }
  782. Size := PageSize - len(resultObj.Result)
  783. boolQ := elastic.NewBoolQuery()
  784. if Key != "" {
  785. nameQuery := elastic.NewMatchQuery("title", Key).Boost(2).QueryName("f_first")
  786. descQuery := elastic.NewMatchQuery("description", Key).Boost(1.5).QueryName("f_second")
  787. fileNameQuery := elastic.NewMatchQuery("file_name", Key).Boost(1).QueryName("f_third")
  788. categoryQuery := elastic.NewMatchQuery("category", Key).Boost(1).QueryName("f_fourth")
  789. boolQ.Should(nameQuery, descQuery, categoryQuery, fileNameQuery)
  790. res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending)).From(from).Size(Size).Highlight(queryHighlight("title", "description", "file_name", "category")).Do(ctx.Req.Context())
  791. if err == nil {
  792. searchJson, _ := json.Marshal(res)
  793. log.Info("searchJson=" + string(searchJson))
  794. esresult := makeDatasetResult(res, Key, OnlyReturnNum, language)
  795. resultObj.Total = resultObj.PrivateTotal + esresult.Total
  796. log.Info("query dataset es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total))
  797. resultObj.Result = append(resultObj.Result, esresult.Result...)
  798. ctx.JSON(200, resultObj)
  799. } else {
  800. log.Info("query es error," + err.Error())
  801. }
  802. } else {
  803. log.Info("query all datasets.")
  804. //搜索的属性要指定{"timestamp":{"unmapped_type":"date"}}
  805. res, err := client.Search(TableName).SortBy(getSort(SortBy, ascending)).From(from).Size(Size).Do(ctx.Req.Context())
  806. if err == nil {
  807. searchJson, _ := json.Marshal(res)
  808. log.Info("searchJson=" + string(searchJson))
  809. esresult := makeDatasetResult(res, "", OnlyReturnNum, language)
  810. resultObj.Total = resultObj.PrivateTotal + esresult.Total
  811. log.Info("query dataset es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total))
  812. resultObj.Result = append(resultObj.Result, esresult.Result...)
  813. ctx.JSON(200, resultObj)
  814. } else {
  815. log.Info("query es error," + err.Error())
  816. ctx.JSON(200, "")
  817. }
  818. }
  819. }
  820. func makePrivateDataSet(datasets []*models.Dataset, res *SearchRes, Key string, language string) {
  821. for _, dataset := range datasets {
  822. record := make(map[string]interface{})
  823. record["id"] = dataset.ID
  824. userId := dataset.UserID
  825. user, errUser := models.GetUserByID(userId)
  826. if errUser == nil {
  827. record["owerName"] = user.GetDisplayName()
  828. record["avatar"] = user.RelAvatarLink()
  829. }
  830. repo, errRepo := models.GetRepositoryByID(dataset.RepoID)
  831. if errRepo == nil {
  832. log.Info("repo_url=" + repo.FullName())
  833. record["repoUrl"] = repo.FullName()
  834. record["avatar"] = repo.RelAvatarLink()
  835. } else {
  836. log.Info("repo err=" + errRepo.Error())
  837. }
  838. record["title"] = makeHighLight(Key, dataset.Title)
  839. record["description"] = truncLongText(makeHighLight(Key, dataset.Description), true)
  840. record["category"] = dataset.Category
  841. record["task"] = dataset.Task
  842. record["download_times"] = dataset.DownloadTimes
  843. record["created_unix"] = dataset.CreatedUnix
  844. record["updated_unix"] = repo.UpdatedUnix
  845. record["updated_html"] = timeutil.TimeSinceUnix(repo.UpdatedUnix, language)
  846. res.Result = append(res.Result, record)
  847. }
  848. }
  849. func makeDatasetResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes {
  850. total := sRes.Hits.TotalHits.Value
  851. result := make([]map[string]interface{}, 0)
  852. if !OnlyReturnNum {
  853. for i, hit := range sRes.Hits.Hits {
  854. log.Info("this is dataset query " + fmt.Sprint(i) + " result.")
  855. recordSource := make(map[string]interface{})
  856. source, err := hit.Source.MarshalJSON()
  857. if err == nil {
  858. err = json.Unmarshal(source, &recordSource)
  859. if err == nil {
  860. record := make(map[string]interface{})
  861. record["id"] = hit.Id
  862. userIdStr := recordSource["user_id"].(string)
  863. userId, cerr := strconv.ParseInt(userIdStr, 10, 64)
  864. if cerr == nil {
  865. user, errUser := models.GetUserByID(userId)
  866. if errUser == nil {
  867. record["owerName"] = user.GetDisplayName()
  868. record["avatar"] = user.RelAvatarLink()
  869. }
  870. }
  871. setRepoInfo(recordSource, record)
  872. record["title"] = getLabelValue("title", recordSource, hit.Highlight)
  873. record["category"] = getLabelValue("category", recordSource, hit.Highlight)
  874. if recordSource["description"] != nil {
  875. desc := getLabelValue("description", recordSource, hit.Highlight)
  876. record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
  877. } else {
  878. record["description"] = ""
  879. }
  880. record["file_name"] = getDatasetFileName(getLabelValue("file_name", recordSource, hit.Highlight))
  881. record["task"] = recordSource["task"]
  882. record["download_times"] = recordSource["download_times"]
  883. record["created_unix"] = recordSource["created_unix"]
  884. setUpdateHtml(record, recordSource["updated_unix"].(string), language)
  885. result = append(result, record)
  886. } else {
  887. log.Info("deal dataset source error," + err.Error())
  888. }
  889. } else {
  890. log.Info("deal dataset source error," + err.Error())
  891. }
  892. }
  893. }
  894. returnObj := &SearchRes{
  895. Total: total,
  896. Result: result,
  897. }
  898. return returnObj
  899. }
  900. func getDatasetFileName(fileName string) string {
  901. slices := strings.Split(fileName, "-#,#-")
  902. fileName = strings.Join(slices, ", ")
  903. return fileName
  904. }
  905. func searchIssueOrPr(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool, issueOrPr string) {
  906. /*
  907. 任务,合并请求 ES名称:issue-es-index
  908. 搜索:
  909. name character varying(255) , 标题
  910. content text, 内容
  911. comment text, 评论
  912. 排序:
  913. updated_unix
  914. */
  915. SortBy := ctx.Query("SortBy")
  916. ascending := ctx.QueryBool("Ascending")
  917. PrivateTotal := ctx.QueryInt("PrivateTotal")
  918. WebTotal := ctx.QueryInt("WebTotal")
  919. language := ctx.Query("language")
  920. if language == "" {
  921. language = "zh-CN"
  922. }
  923. from := (Page - 1) * PageSize
  924. if from == 0 {
  925. WebTotal = 0
  926. }
  927. resultObj := &SearchRes{}
  928. log.Info("WebTotal=" + fmt.Sprint(WebTotal))
  929. log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
  930. resultObj.Result = make([]map[string]interface{}, 0)
  931. isPull := false
  932. if issueOrPr == "t" {
  933. isPull = true
  934. }
  935. if ctx.User != nil && (from < PrivateTotal || from == 0) {
  936. log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
  937. issues, count, err := models.SearchPrivateIssueOrPr(Page, PageSize, Key, isPull, ctx.User.ID)
  938. if err != nil {
  939. ctx.JSON(200, "")
  940. return
  941. }
  942. resultObj.PrivateTotal = count
  943. issuesSize := len(issues)
  944. if issuesSize > 0 {
  945. log.Info("Query private repo issue number is:" + fmt.Sprint(issuesSize) + " count=" + fmt.Sprint(count))
  946. makePrivateIssueOrPr(issues, resultObj, Key, language)
  947. } else {
  948. log.Info("not found private repo issue,keyword=" + Key)
  949. }
  950. if issuesSize >= PageSize {
  951. if WebTotal > 0 { //next page, not first query.
  952. resultObj.Total = int64(WebTotal)
  953. ctx.JSON(200, resultObj)
  954. return
  955. }
  956. }
  957. } else {
  958. resultObj.PrivateTotal = int64(PrivateTotal)
  959. }
  960. from = from - PrivateTotal
  961. if from < 0 {
  962. from = 0
  963. }
  964. Size := PageSize - len(resultObj.Result)
  965. boolQ := elastic.NewBoolQuery()
  966. isIssueQuery := elastic.NewTermQuery("is_pull", issueOrPr)
  967. if Key != "" {
  968. boolKeyQ := elastic.NewBoolQuery()
  969. log.Info("issue Key=" + Key)
  970. nameQuery := elastic.NewMatchQuery("name", Key).Boost(2).QueryName("f_first")
  971. contentQuery := elastic.NewMatchQuery("content", Key).Boost(1.5).QueryName("f_second")
  972. commentQuery := elastic.NewMatchQuery("comment", Key).Boost(1).QueryName("f_third")
  973. boolKeyQ.Should(nameQuery, contentQuery, commentQuery)
  974. boolQ.Must(isIssueQuery, boolKeyQ)
  975. } else {
  976. boolQ.Must(isIssueQuery)
  977. }
  978. res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending)).From(from).Size(Size).Highlight(queryHighlight("name", "content", "comment")).Do(ctx.Req.Context())
  979. if err == nil {
  980. searchJson, _ := json.Marshal(res)
  981. log.Info("searchJson=" + string(searchJson))
  982. esresult := makeIssueResult(res, Key, OnlyReturnNum, language)
  983. resultObj.Total = resultObj.PrivateTotal + esresult.Total
  984. log.Info("query issue es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total))
  985. resultObj.Result = append(resultObj.Result, esresult.Result...)
  986. ctx.JSON(200, resultObj)
  987. } else {
  988. log.Info("query es error," + err.Error())
  989. }
  990. }
  991. func queryHighlight(names ...string) *elastic.Highlight {
  992. re := elastic.NewHighlight()
  993. for i := 0; i < len(names); i++ {
  994. field := &elastic.HighlighterField{
  995. Name: names[i],
  996. }
  997. re.Fields(field)
  998. }
  999. re.PreTags("<font color='red'>")
  1000. re.PostTags("</font>")
  1001. return re
  1002. }
  1003. func setRepoInfo(recordSource map[string]interface{}, record map[string]interface{}) {
  1004. repoIdstr := recordSource["repo_id"].(string)
  1005. repoId, cerr := strconv.ParseInt(repoIdstr, 10, 64)
  1006. if cerr == nil {
  1007. repo, errRepo := models.GetRepositoryByID(repoId)
  1008. if errRepo == nil {
  1009. log.Info("repo_url=" + repo.FullName())
  1010. record["repoUrl"] = repo.FullName()
  1011. record["avatar"] = repo.RelAvatarLink()
  1012. } else {
  1013. log.Info("repo err=" + errRepo.Error())
  1014. }
  1015. } else {
  1016. log.Info("parse int err=" + cerr.Error())
  1017. }
  1018. }
  1019. func makePrivateIssueOrPr(issues []*models.Issue, res *SearchRes, Key string, language string) {
  1020. for _, issue := range issues {
  1021. record := make(map[string]interface{})
  1022. record["id"] = issue.ID
  1023. record["repo_id"] = issue.RepoID
  1024. repo, errRepo := models.GetRepositoryByID(issue.RepoID)
  1025. if errRepo == nil {
  1026. log.Info("repo_url=" + repo.FullName())
  1027. record["repoUrl"] = repo.FullName()
  1028. record["avatar"] = repo.RelAvatarLink()
  1029. } else {
  1030. log.Info("repo err=" + errRepo.Error())
  1031. }
  1032. record["name"] = makeHighLight(Key, issue.Title)
  1033. record["content"] = truncLongText(makeHighLight(Key, issue.Content), true)
  1034. if issue.IsPull {
  1035. pr, err1 := issue.GetPullRequest()
  1036. if err1 == nil && pr != nil {
  1037. record["pr_id"] = pr.ID
  1038. }
  1039. }
  1040. record["index"] = issue.Index
  1041. record["num_comments"] = issue.NumComments
  1042. record["is_closed"] = issue.IsClosed
  1043. record["updated_unix"] = issue.UpdatedUnix
  1044. record["updated_html"] = timeutil.TimeSinceUnix(repo.UpdatedUnix, language)
  1045. res.Result = append(res.Result, record)
  1046. }
  1047. }
  1048. func makeIssueResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes {
  1049. total := sRes.Hits.TotalHits.Value
  1050. result := make([]map[string]interface{}, 0)
  1051. if !OnlyReturnNum {
  1052. for i, hit := range sRes.Hits.Hits {
  1053. log.Info("this is issue query " + fmt.Sprint(i) + " result.")
  1054. recordSource := make(map[string]interface{})
  1055. source, err := hit.Source.MarshalJSON()
  1056. if err == nil {
  1057. err = json.Unmarshal(source, &recordSource)
  1058. if err == nil {
  1059. record := make(map[string]interface{})
  1060. record["id"] = hit.Id
  1061. record["repo_id"] = recordSource["repo_id"]
  1062. log.Info("recordSource[\"repo_id\"]=" + fmt.Sprint(recordSource["repo_id"]))
  1063. setRepoInfo(recordSource, record)
  1064. record["name"] = getLabelValue("name", recordSource, hit.Highlight)
  1065. if recordSource["content"] != nil {
  1066. desc := getLabelValue("content", recordSource, hit.Highlight)
  1067. record["content"] = dealLongText(desc, Key, hit.MatchedQueries)
  1068. if _, ok := hit.Highlight["content"]; !ok {
  1069. if _, ok_comment := hit.Highlight["comment"]; ok_comment {
  1070. desc := getLabelValue("comment", recordSource, hit.Highlight)
  1071. record["content"] = trimHrefHtml(dealLongText(desc, Key, hit.MatchedQueries))
  1072. }
  1073. }
  1074. } else {
  1075. if recordSource["comment"] != nil {
  1076. desc := getLabelValue("comment", recordSource, hit.Highlight)
  1077. record["content"] = dealLongText(desc, Key, hit.MatchedQueries)
  1078. }
  1079. }
  1080. if recordSource["pr_id"] != nil {
  1081. record["pr_id"] = recordSource["pr_id"]
  1082. }
  1083. log.Info("index=" + recordSource["index"].(string))
  1084. record["index"] = recordSource["index"]
  1085. record["num_comments"] = recordSource["num_comments"]
  1086. record["is_closed"] = recordSource["is_closed"]
  1087. record["updated_unix"] = recordSource["updated_unix"]
  1088. setUpdateHtml(record, recordSource["updated_unix"].(string), language)
  1089. result = append(result, record)
  1090. } else {
  1091. log.Info("deal issue source error," + err.Error())
  1092. }
  1093. } else {
  1094. log.Info("deal issue source error," + err.Error())
  1095. }
  1096. }
  1097. }
  1098. returnObj := &SearchRes{
  1099. Total: total,
  1100. Result: result,
  1101. }
  1102. return returnObj
  1103. }