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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  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. i := 0
  538. for ; i < len(text); i++ {
  539. if text[i] == startRune { //start <
  540. re := false
  541. j := i + 1
  542. for ; j < len(text); j++ {
  543. if text[j] == endRune {
  544. re = true
  545. break
  546. }
  547. }
  548. if re { //found >
  549. i = j
  550. count++
  551. } else {
  552. if count%2 == 1 {
  553. return string(text[0:i]) + "</font>"
  554. } else {
  555. return string(text[0:i])
  556. }
  557. }
  558. }
  559. }
  560. if count%2 == 1 {
  561. return string(text[0:i]) + "</font>"
  562. } else {
  563. return string(text[0:i])
  564. }
  565. }
  566. func trimHrefHtml(result string) string {
  567. result = strings.Replace(result, "</a>", "", -1)
  568. result = strings.Replace(result, "\n", "", -1)
  569. var index int
  570. for {
  571. index = findSubstr(result, 0, "<a")
  572. if index != -1 {
  573. sIndex := findSubstr(result, index+2, ">")
  574. if sIndex != -1 {
  575. result = result[0:index] + result[sIndex+1:]
  576. } else {
  577. result = result[0:index] + result[index+2:]
  578. }
  579. } else {
  580. break
  581. }
  582. }
  583. return result
  584. }
  585. func findFont(text []rune, childText []rune) int {
  586. for i := 0; i < len(text); i++ {
  587. if text[i] == childText[0] {
  588. re := true
  589. for j, k := range childText {
  590. if k != text[i+j] {
  591. re = false
  592. break
  593. }
  594. }
  595. if re {
  596. return i
  597. }
  598. }
  599. }
  600. return -1
  601. }
  602. func findSubstr(text string, startindex int, childText string) int {
  603. for i := startindex; i < len(text); i++ {
  604. if text[i] == childText[0] {
  605. re := true
  606. for k := range childText {
  607. if childText[k] != text[i+k] {
  608. re = false
  609. break
  610. }
  611. }
  612. if re {
  613. return i
  614. }
  615. }
  616. }
  617. return -1
  618. }
  619. func searchUserOrOrg(ctx *context.Context, TableName string, Key string, Page int, PageSize int, IsQueryUser bool, OnlyReturnNum bool) {
  620. /*
  621. 用户或者组织 ES名称: user-es-index
  622. 搜索:
  623. name , 名称
  624. full_name 全名
  625. description 描述或者简介
  626. 排序:
  627. created_unix
  628. 名称字母序
  629. */
  630. SortBy := ctx.Query("SortBy")
  631. ascending := ctx.QueryBool("Ascending")
  632. boolQ := elastic.NewBoolQuery()
  633. typeValue := 1
  634. if IsQueryUser {
  635. typeValue = 0
  636. }
  637. UserOrOrgQuery := elastic.NewTermQuery("type", typeValue)
  638. if Key != "" {
  639. boolKeyQ := elastic.NewBoolQuery()
  640. log.Info("user or org Key=" + Key)
  641. nameQuery := elastic.NewMatchQuery("name", Key).Boost(2).QueryName("f_first")
  642. full_nameQuery := elastic.NewMatchQuery("full_name", Key).Boost(1.5).QueryName("f_second")
  643. descriptionQuery := elastic.NewMatchQuery("description", Key).Boost(1).QueryName("f_third")
  644. boolKeyQ.Should(nameQuery, full_nameQuery, descriptionQuery)
  645. boolQ.Must(UserOrOrgQuery, boolKeyQ)
  646. } else {
  647. boolQ.Must(UserOrOrgQuery)
  648. }
  649. 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())
  650. if err == nil {
  651. searchJson, _ := json.Marshal(res)
  652. log.Info("searchJson=" + string(searchJson))
  653. result := makeUserOrOrgResult(res, Key, ctx, OnlyReturnNum)
  654. ctx.JSON(200, result)
  655. } else {
  656. log.Info("query es error," + err.Error())
  657. ctx.JSON(200, "")
  658. }
  659. }
  660. func getLabelValue(key string, recordSource map[string]interface{}, searchHighliht elastic.SearchHitHighlight) string {
  661. if value, ok := searchHighliht[key]; !ok {
  662. if recordSource[key] != nil {
  663. return recordSource[key].(string)
  664. } else {
  665. return ""
  666. }
  667. } else {
  668. return value[0]
  669. }
  670. }
  671. func makeUserOrOrgResult(sRes *elastic.SearchResult, Key string, ctx *context.Context, OnlyReturnNum bool) *SearchRes {
  672. total := sRes.Hits.TotalHits.Value
  673. result := make([]map[string]interface{}, 0)
  674. if !OnlyReturnNum {
  675. for i, hit := range sRes.Hits.Hits {
  676. log.Info("this is user query " + fmt.Sprint(i) + " result.")
  677. recordSource := make(map[string]interface{})
  678. source, err := hit.Source.MarshalJSON()
  679. if err == nil {
  680. err = json.Unmarshal(source, &recordSource)
  681. if err == nil {
  682. record := make(map[string]interface{})
  683. record["id"] = hit.Id
  684. record["name"] = getLabelValue("name", recordSource, hit.Highlight)
  685. record["real_name"] = recordSource["name"]
  686. record["full_name"] = getLabelValue("full_name", recordSource, hit.Highlight)
  687. if recordSource["description"] != nil {
  688. desc := getLabelValue("description", recordSource, hit.Highlight)
  689. record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
  690. } else {
  691. record["description"] = ""
  692. }
  693. if ctx.User != nil {
  694. record["email"] = recordSource["email"]
  695. } else {
  696. record["email"] = ""
  697. }
  698. record["location"] = recordSource["location"]
  699. record["website"] = recordSource["website"]
  700. record["num_repos"] = recordSource["num_repos"]
  701. record["num_teams"] = recordSource["num_teams"]
  702. record["num_members"] = recordSource["num_members"]
  703. record["avatar"] = strings.TrimRight(setting.AppSubURL, "/") + "/user/avatar/" + recordSource["name"].(string) + "/" + strconv.Itoa(-1)
  704. record["updated_unix"] = recordSource["updated_unix"]
  705. record["created_unix"] = recordSource["created_unix"]
  706. record["add_time"] = getAddTime(recordSource["created_unix"].(string))
  707. result = append(result, record)
  708. } else {
  709. log.Info("deal user source error," + err.Error())
  710. }
  711. } else {
  712. log.Info("deal user source error," + err.Error())
  713. }
  714. }
  715. }
  716. returnObj := &SearchRes{
  717. Total: total,
  718. Result: result,
  719. }
  720. return returnObj
  721. }
  722. func getAddTime(time string) string {
  723. timeInt, err := strconv.ParseInt(time, 10, 64)
  724. if err == nil {
  725. t := timeutil.TimeStamp(timeInt)
  726. return t.FormatShort()
  727. }
  728. return ""
  729. }
  730. func searchDataSet(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool) {
  731. /*
  732. 数据集,ES名称:dataset-es-index
  733. 搜索:
  734. title , 名称
  735. description 描述
  736. category 标签
  737. file_name 数据集文件名称
  738. 排序:
  739. download_times
  740. */
  741. log.Info("query searchdataset start")
  742. SortBy := ctx.Query("SortBy")
  743. ascending := ctx.QueryBool("Ascending")
  744. PrivateTotal := ctx.QueryInt("PrivateTotal")
  745. WebTotal := ctx.QueryInt("WebTotal")
  746. language := ctx.Query("language")
  747. if language == "" {
  748. language = "zh-CN"
  749. }
  750. from := (Page - 1) * PageSize
  751. if from == 0 {
  752. WebTotal = 0
  753. }
  754. resultObj := &SearchRes{}
  755. log.Info("WebTotal=" + fmt.Sprint(WebTotal))
  756. log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
  757. resultObj.Result = make([]map[string]interface{}, 0)
  758. if ctx.User != nil && (from < PrivateTotal || from == 0) {
  759. log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
  760. datasets, count, err := models.SearchDatasetBySQL(Page, PageSize, Key, ctx.User.ID)
  761. if err != nil {
  762. ctx.JSON(200, "")
  763. return
  764. }
  765. resultObj.PrivateTotal = count
  766. datasetSize := len(datasets)
  767. if datasetSize > 0 {
  768. log.Info("Query private dataset number is:" + fmt.Sprint(datasetSize) + " count=" + fmt.Sprint(count))
  769. makePrivateDataSet(datasets, resultObj, Key, language)
  770. } else {
  771. log.Info("not found private dataset, keyword=" + Key)
  772. }
  773. if datasetSize >= PageSize {
  774. if WebTotal > 0 { //next page, not first query.
  775. resultObj.Total = int64(WebTotal)
  776. ctx.JSON(200, resultObj)
  777. return
  778. }
  779. }
  780. } else {
  781. resultObj.PrivateTotal = int64(PrivateTotal)
  782. }
  783. from = from - PrivateTotal
  784. if from < 0 {
  785. from = 0
  786. }
  787. Size := PageSize - len(resultObj.Result)
  788. boolQ := elastic.NewBoolQuery()
  789. if Key != "" {
  790. nameQuery := elastic.NewMatchQuery("title", Key).Boost(2).QueryName("f_first")
  791. descQuery := elastic.NewMatchQuery("description", Key).Boost(1.5).QueryName("f_second")
  792. fileNameQuery := elastic.NewMatchQuery("file_name", Key).Boost(1).QueryName("f_third")
  793. categoryQuery := elastic.NewMatchQuery("category", Key).Boost(1).QueryName("f_fourth")
  794. boolQ.Should(nameQuery, descQuery, categoryQuery, fileNameQuery)
  795. 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())
  796. if err == nil {
  797. searchJson, _ := json.Marshal(res)
  798. log.Info("searchJson=" + string(searchJson))
  799. esresult := makeDatasetResult(res, Key, OnlyReturnNum, language)
  800. resultObj.Total = resultObj.PrivateTotal + esresult.Total
  801. log.Info("query dataset es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total))
  802. resultObj.Result = append(resultObj.Result, esresult.Result...)
  803. ctx.JSON(200, resultObj)
  804. } else {
  805. log.Info("query es error," + err.Error())
  806. }
  807. } else {
  808. log.Info("query all datasets.")
  809. //搜索的属性要指定{"timestamp":{"unmapped_type":"date"}}
  810. res, err := client.Search(TableName).SortBy(getSort(SortBy, ascending)).From(from).Size(Size).Do(ctx.Req.Context())
  811. if err == nil {
  812. searchJson, _ := json.Marshal(res)
  813. log.Info("searchJson=" + string(searchJson))
  814. esresult := makeDatasetResult(res, "", OnlyReturnNum, language)
  815. resultObj.Total = resultObj.PrivateTotal + esresult.Total
  816. log.Info("query dataset es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total))
  817. resultObj.Result = append(resultObj.Result, esresult.Result...)
  818. ctx.JSON(200, resultObj)
  819. } else {
  820. log.Info("query es error," + err.Error())
  821. ctx.JSON(200, "")
  822. }
  823. }
  824. }
  825. func makePrivateDataSet(datasets []*models.Dataset, res *SearchRes, Key string, language string) {
  826. for _, dataset := range datasets {
  827. record := make(map[string]interface{})
  828. record["id"] = dataset.ID
  829. userId := dataset.UserID
  830. user, errUser := models.GetUserByID(userId)
  831. if errUser == nil {
  832. record["owerName"] = user.GetDisplayName()
  833. record["avatar"] = user.RelAvatarLink()
  834. }
  835. repo, errRepo := models.GetRepositoryByID(dataset.RepoID)
  836. if errRepo == nil {
  837. log.Info("repo_url=" + repo.FullName())
  838. record["repoUrl"] = repo.FullName()
  839. record["avatar"] = repo.RelAvatarLink()
  840. } else {
  841. log.Info("repo err=" + errRepo.Error())
  842. }
  843. record["title"] = makeHighLight(Key, dataset.Title)
  844. record["description"] = truncLongText(makeHighLight(Key, dataset.Description), true)
  845. record["category"] = dataset.Category
  846. record["task"] = dataset.Task
  847. record["download_times"] = dataset.DownloadTimes
  848. record["created_unix"] = dataset.CreatedUnix
  849. record["updated_unix"] = repo.UpdatedUnix
  850. record["updated_html"] = timeutil.TimeSinceUnix(repo.UpdatedUnix, language)
  851. res.Result = append(res.Result, record)
  852. }
  853. }
  854. func makeDatasetResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes {
  855. total := sRes.Hits.TotalHits.Value
  856. result := make([]map[string]interface{}, 0)
  857. if !OnlyReturnNum {
  858. for i, hit := range sRes.Hits.Hits {
  859. log.Info("this is dataset query " + fmt.Sprint(i) + " result.")
  860. recordSource := make(map[string]interface{})
  861. source, err := hit.Source.MarshalJSON()
  862. if err == nil {
  863. err = json.Unmarshal(source, &recordSource)
  864. if err == nil {
  865. record := make(map[string]interface{})
  866. record["id"] = hit.Id
  867. userIdStr := recordSource["user_id"].(string)
  868. userId, cerr := strconv.ParseInt(userIdStr, 10, 64)
  869. if cerr == nil {
  870. user, errUser := models.GetUserByID(userId)
  871. if errUser == nil {
  872. record["owerName"] = user.GetDisplayName()
  873. record["avatar"] = user.RelAvatarLink()
  874. }
  875. }
  876. setRepoInfo(recordSource, record)
  877. record["title"] = getLabelValue("title", recordSource, hit.Highlight)
  878. record["category"] = getLabelValue("category", recordSource, hit.Highlight)
  879. if recordSource["description"] != nil {
  880. desc := getLabelValue("description", recordSource, hit.Highlight)
  881. record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
  882. } else {
  883. record["description"] = ""
  884. }
  885. record["file_name"] = getDatasetFileName(getLabelValue("file_name", recordSource, hit.Highlight))
  886. record["task"] = recordSource["task"]
  887. record["download_times"] = recordSource["download_times"]
  888. record["created_unix"] = recordSource["created_unix"]
  889. setUpdateHtml(record, recordSource["updated_unix"].(string), language)
  890. result = append(result, record)
  891. } else {
  892. log.Info("deal dataset source error," + err.Error())
  893. }
  894. } else {
  895. log.Info("deal dataset source error," + err.Error())
  896. }
  897. }
  898. }
  899. returnObj := &SearchRes{
  900. Total: total,
  901. Result: result,
  902. }
  903. return returnObj
  904. }
  905. func getDatasetFileName(fileName string) string {
  906. slices := strings.Split(fileName, "-#,#-")
  907. fileName = strings.Join(slices, ", ")
  908. return fileName
  909. }
  910. func searchIssueOrPr(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool, issueOrPr string) {
  911. /*
  912. 任务,合并请求 ES名称:issue-es-index
  913. 搜索:
  914. name character varying(255) , 标题
  915. content text, 内容
  916. comment text, 评论
  917. 排序:
  918. updated_unix
  919. */
  920. SortBy := ctx.Query("SortBy")
  921. ascending := ctx.QueryBool("Ascending")
  922. PrivateTotal := ctx.QueryInt("PrivateTotal")
  923. WebTotal := ctx.QueryInt("WebTotal")
  924. language := ctx.Query("language")
  925. if language == "" {
  926. language = "zh-CN"
  927. }
  928. from := (Page - 1) * PageSize
  929. if from == 0 {
  930. WebTotal = 0
  931. }
  932. resultObj := &SearchRes{}
  933. log.Info("WebTotal=" + fmt.Sprint(WebTotal))
  934. log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
  935. resultObj.Result = make([]map[string]interface{}, 0)
  936. isPull := false
  937. if issueOrPr == "t" {
  938. isPull = true
  939. }
  940. if ctx.User != nil && (from < PrivateTotal || from == 0) {
  941. log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
  942. issues, count, err := models.SearchPrivateIssueOrPr(Page, PageSize, Key, isPull, ctx.User.ID)
  943. if err != nil {
  944. ctx.JSON(200, "")
  945. return
  946. }
  947. resultObj.PrivateTotal = count
  948. issuesSize := len(issues)
  949. if issuesSize > 0 {
  950. log.Info("Query private repo issue number is:" + fmt.Sprint(issuesSize) + " count=" + fmt.Sprint(count))
  951. makePrivateIssueOrPr(issues, resultObj, Key, language)
  952. } else {
  953. log.Info("not found private repo issue,keyword=" + Key)
  954. }
  955. if issuesSize >= PageSize {
  956. if WebTotal > 0 { //next page, not first query.
  957. resultObj.Total = int64(WebTotal)
  958. ctx.JSON(200, resultObj)
  959. return
  960. }
  961. }
  962. } else {
  963. resultObj.PrivateTotal = int64(PrivateTotal)
  964. }
  965. from = from - PrivateTotal
  966. if from < 0 {
  967. from = 0
  968. }
  969. Size := PageSize - len(resultObj.Result)
  970. boolQ := elastic.NewBoolQuery()
  971. isIssueQuery := elastic.NewTermQuery("is_pull", issueOrPr)
  972. if Key != "" {
  973. boolKeyQ := elastic.NewBoolQuery()
  974. log.Info("issue Key=" + Key)
  975. nameQuery := elastic.NewMatchQuery("name", Key).Boost(2).QueryName("f_first")
  976. contentQuery := elastic.NewMatchQuery("content", Key).Boost(1.5).QueryName("f_second")
  977. commentQuery := elastic.NewMatchQuery("comment", Key).Boost(1).QueryName("f_third")
  978. boolKeyQ.Should(nameQuery, contentQuery, commentQuery)
  979. boolQ.Must(isIssueQuery, boolKeyQ)
  980. } else {
  981. boolQ.Must(isIssueQuery)
  982. }
  983. res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending)).From(from).Size(Size).Highlight(queryHighlight("name", "content", "comment")).Do(ctx.Req.Context())
  984. if err == nil {
  985. searchJson, _ := json.Marshal(res)
  986. log.Info("searchJson=" + string(searchJson))
  987. esresult := makeIssueResult(res, Key, OnlyReturnNum, language)
  988. resultObj.Total = resultObj.PrivateTotal + esresult.Total
  989. log.Info("query issue es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total))
  990. resultObj.Result = append(resultObj.Result, esresult.Result...)
  991. ctx.JSON(200, resultObj)
  992. } else {
  993. log.Info("query es error," + err.Error())
  994. }
  995. }
  996. func queryHighlight(names ...string) *elastic.Highlight {
  997. re := elastic.NewHighlight()
  998. for i := 0; i < len(names); i++ {
  999. field := &elastic.HighlighterField{
  1000. Name: names[i],
  1001. }
  1002. re.Fields(field)
  1003. }
  1004. re.PreTags("<font color='red'>")
  1005. re.PostTags("</font>")
  1006. return re
  1007. }
  1008. func setRepoInfo(recordSource map[string]interface{}, record map[string]interface{}) {
  1009. repoIdstr := recordSource["repo_id"].(string)
  1010. repoId, cerr := strconv.ParseInt(repoIdstr, 10, 64)
  1011. if cerr == nil {
  1012. repo, errRepo := models.GetRepositoryByID(repoId)
  1013. if errRepo == nil {
  1014. log.Info("repo_url=" + repo.FullName())
  1015. record["repoUrl"] = repo.FullName()
  1016. record["avatar"] = repo.RelAvatarLink()
  1017. } else {
  1018. log.Info("repo err=" + errRepo.Error())
  1019. }
  1020. } else {
  1021. log.Info("parse int err=" + cerr.Error())
  1022. }
  1023. }
  1024. func makePrivateIssueOrPr(issues []*models.Issue, res *SearchRes, Key string, language string) {
  1025. for _, issue := range issues {
  1026. record := make(map[string]interface{})
  1027. record["id"] = issue.ID
  1028. record["repo_id"] = issue.RepoID
  1029. repo, errRepo := models.GetRepositoryByID(issue.RepoID)
  1030. if errRepo == nil {
  1031. log.Info("repo_url=" + repo.FullName())
  1032. record["repoUrl"] = repo.FullName()
  1033. record["avatar"] = repo.RelAvatarLink()
  1034. } else {
  1035. log.Info("repo err=" + errRepo.Error())
  1036. }
  1037. record["name"] = makeHighLight(Key, issue.Title)
  1038. record["content"] = truncLongText(makeHighLight(Key, issue.Content), true)
  1039. if issue.IsPull {
  1040. pr, err1 := issue.GetPullRequest()
  1041. if err1 == nil && pr != nil {
  1042. record["pr_id"] = pr.ID
  1043. }
  1044. }
  1045. record["index"] = issue.Index
  1046. record["num_comments"] = issue.NumComments
  1047. record["is_closed"] = issue.IsClosed
  1048. record["updated_unix"] = issue.UpdatedUnix
  1049. record["updated_html"] = timeutil.TimeSinceUnix(issue.UpdatedUnix, language)
  1050. res.Result = append(res.Result, record)
  1051. }
  1052. }
  1053. func makeIssueResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes {
  1054. total := sRes.Hits.TotalHits.Value
  1055. result := make([]map[string]interface{}, 0)
  1056. if !OnlyReturnNum {
  1057. for i, hit := range sRes.Hits.Hits {
  1058. log.Info("this is issue query " + fmt.Sprint(i) + " result.")
  1059. recordSource := make(map[string]interface{})
  1060. source, err := hit.Source.MarshalJSON()
  1061. if err == nil {
  1062. err = json.Unmarshal(source, &recordSource)
  1063. if err == nil {
  1064. record := make(map[string]interface{})
  1065. record["id"] = hit.Id
  1066. record["repo_id"] = recordSource["repo_id"]
  1067. log.Info("recordSource[\"repo_id\"]=" + fmt.Sprint(recordSource["repo_id"]))
  1068. setRepoInfo(recordSource, record)
  1069. record["name"] = getLabelValue("name", recordSource, hit.Highlight)
  1070. if recordSource["content"] != nil {
  1071. desc := getLabelValue("content", recordSource, hit.Highlight)
  1072. record["content"] = dealLongText(desc, Key, hit.MatchedQueries)
  1073. if _, ok := hit.Highlight["content"]; !ok {
  1074. if _, ok_comment := hit.Highlight["comment"]; ok_comment {
  1075. desc := getLabelValue("comment", recordSource, hit.Highlight)
  1076. record["content"] = trimHrefHtml(dealLongText(desc, Key, hit.MatchedQueries))
  1077. }
  1078. }
  1079. } else {
  1080. if recordSource["comment"] != nil {
  1081. desc := getLabelValue("comment", recordSource, hit.Highlight)
  1082. record["content"] = dealLongText(desc, Key, hit.MatchedQueries)
  1083. }
  1084. }
  1085. if recordSource["pr_id"] != nil {
  1086. record["pr_id"] = recordSource["pr_id"]
  1087. }
  1088. log.Info("index=" + recordSource["index"].(string))
  1089. record["index"] = recordSource["index"]
  1090. record["num_comments"] = recordSource["num_comments"]
  1091. record["is_closed"] = recordSource["is_closed"]
  1092. record["updated_unix"] = recordSource["updated_unix"]
  1093. setUpdateHtml(record, recordSource["updated_unix"].(string), language)
  1094. result = append(result, record)
  1095. } else {
  1096. log.Info("deal issue source error," + err.Error())
  1097. }
  1098. } else {
  1099. log.Info("deal issue source error," + err.Error())
  1100. }
  1101. }
  1102. }
  1103. returnObj := &SearchRes{
  1104. Total: total,
  1105. Result: result,
  1106. }
  1107. return returnObj
  1108. }