project_new.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. package main
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "github.com/olivere/elastic/v7"
  8. util "jygit.jydev.jianyu360.cn/data_processing/common_utils"
  9. "log"
  10. "sort"
  11. //"sort"
  12. "strings"
  13. "time"
  14. )
  15. func SearchProjectFullScoring(client *elastic.Client, target InputData, projectName, areacode, publish string) ([]map[string]interface{}, error) {
  16. seenIDs := make(map[string]*elastic.SearchHit)
  17. province, city := "", ""
  18. if areacode != "" {
  19. code := areacode[:6]
  20. where := map[string]interface{}{
  21. "code": code,
  22. }
  23. res, _ := MgoQY.FindOne("address_new_2020", where)
  24. province = util.ObjToString((*res)["province"])
  25. city = util.ObjToString((*res)["city"])
  26. }
  27. // 1. 精准查询(权重 1.0)
  28. preciseHits, err := searchPrecise(client, projectName, province, city, publish, 20)
  29. if err != nil {
  30. return nil, err
  31. }
  32. for _, hit := range preciseHits {
  33. if _, exists := seenIDs[hit.Id]; !exists {
  34. seenIDs[hit.Id] = hit
  35. }
  36. }
  37. // 2. 分词查询(权重 0.8)
  38. tokenHits, err := searchByToken(client, projectName, province, city, publish, 20)
  39. if err != nil {
  40. return nil, err
  41. }
  42. for _, hit := range tokenHits {
  43. if _, exists := seenIDs[hit.Id]; !exists {
  44. seenIDs[hit.Id] = hit
  45. }
  46. }
  47. // 3. common 查询(权重 0.5)
  48. commonHits, err := searchCommon(client, projectName, province, city, publish, 10)
  49. if err != nil {
  50. return nil, err
  51. }
  52. for _, hit := range commonHits {
  53. if _, exists := seenIDs[hit.Id]; !exists {
  54. seenIDs[hit.Id] = hit
  55. }
  56. }
  57. // 4. 合并 + 打分增强
  58. var results []map[string]interface{}
  59. //nameScore := computeNameScore(projectName)
  60. var allCandidates = []EsDocument{}
  61. for id, hit := range seenIDs {
  62. var doc map[string]interface{}
  63. if err := json.Unmarshal(hit.Source, &doc); err != nil {
  64. continue
  65. }
  66. // 从 Mongo 读取 detail 字段用于后续 buyer 过滤
  67. bidd, _ := MgoB.FindById("bidding", id, nil)
  68. detail := util.ObjToString((*bidd)["detail"])
  69. doc["detail"] = detail
  70. /**
  71. "id", "title", "projectname", "projectcode", "bidamount", "area", "city",
  72. "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel",
  73. "s_winner", "winnertel", "agency", "publishtime"
  74. */
  75. candidate := EsDocument{
  76. Id: util.ObjToString(doc["id"]),
  77. Title: util.ObjToString(doc["title"]),
  78. Projectname: util.ObjToString(doc["projectname"]),
  79. Toptype: util.ObjToString(doc["toptype"]),
  80. Subtype: util.ObjToString(doc["subtype"]),
  81. Area: util.ObjToString(doc["area"]),
  82. City: util.ObjToString(doc["city"]),
  83. Buyer: util.ObjToString(doc["buyer"]),
  84. SWinner: util.ObjToString(doc["s_winner"]),
  85. Bidamount: util.Float64All(doc["bidamount"]),
  86. Publishtime: util.Int64All(doc["publishtime"]),
  87. Agency: util.ObjToString(doc["agency"]),
  88. WinnerTel: util.ObjToString(doc["winnertel"]),
  89. BuyerTel: util.ObjToString(doc["buyertel"]),
  90. BuyerPerson: util.ObjToString(doc["buyerperson"]),
  91. Budget: util.Float64All(doc["budget"]),
  92. }
  93. score := calculateConfidenceScore(target, candidate)
  94. candidate.Score = score
  95. allCandidates = append(allCandidates, candidate)
  96. }
  97. // 打印打分调试
  98. //for i, c := range allCandidates {
  99. // fmt.Printf("Candidat 排序前: %d Score: %.4f\n", i, c.Score)
  100. //}
  101. // 排序(降序)
  102. sort.SliceStable(allCandidates, func(i, j int) bool {
  103. return allCandidates[i].Score > allCandidates[j].Score
  104. })
  105. //for i, c := range allCandidates {
  106. // fmt.Printf("Candidate 排序后: %d Score: %.4f\n", i, c.Score)
  107. //}
  108. //// 5. 排序
  109. //sort.Slice(allCandidates, func(i, j int) bool {
  110. // return allCandidates[i].Score > allCandidates[j].Score
  111. //})
  112. for _, doc := range allCandidates {
  113. item := map[string]interface{}{
  114. "id": doc.Id,
  115. "title": doc.Title,
  116. "projectname": doc.Projectname,
  117. "projectcode": doc.ProjectCode,
  118. "toptype": doc.Toptype,
  119. "subtype": doc.Subtype,
  120. "area": doc.Area,
  121. "city": doc.City,
  122. "buyer": doc.Buyer,
  123. "budget": doc.Budget,
  124. "bidamount": doc.Bidamount,
  125. "winner": doc.Winner,
  126. "detail": doc.Detail,
  127. "publishtime": doc.Publishtime,
  128. "agency": doc.Agency,
  129. "s_winner": doc.SWinner,
  130. "winnertel": doc.WinnerTel,
  131. "buyertel": doc.BuyerTel,
  132. "buyerperson": doc.BuyerPerson,
  133. "score": doc.Score,
  134. }
  135. results = append(results, item)
  136. }
  137. return results, nil
  138. }
  139. // searchPrecise 精准查询
  140. func searchPrecise22(client *elastic.Client, projectName, province, city, publish string, maxResults int) ([]*elastic.SearchHit, error) {
  141. fieldsToTry := []string{"projectname.pname", "title", "detail"}
  142. filtersToTry := [][]elastic.Query{
  143. {elastic.NewTermsQuery("subtype", "中标", "成交", "合同", "单一")},
  144. {elastic.NewTermsQuery("toptype", "招标", "预告", "采购意向")},
  145. {elastic.NewTermsQuery("toptype", "拟建")},
  146. }
  147. var allResults []*elastic.SearchHit
  148. seenIDs := make(map[string]bool)
  149. query := elastic.NewBoolQuery()
  150. for _, field := range fieldsToTry {
  151. if field == "detail" && len(allResults) > maxResults {
  152. break
  153. }
  154. for _, filter := range filtersToTry {
  155. //query := elastic.NewBoolQuery().
  156. query.Must(elastic.NewMultiMatchQuery(projectName, field).Type("phrase")).
  157. Filter(filter...)
  158. fetchFields := elastic.NewFetchSourceContext(true).Include("id", "title", "projectname", "projectcode", "bidamount", "area", "city", "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel", "s_winner", "winnertel", "agency", "publishtime")
  159. searchResult, err := client.Search().
  160. Index("bidding").
  161. Query(query).
  162. Size(maxResults).
  163. FetchSourceContext(fetchFields).
  164. Do(context.Background())
  165. if err != nil {
  166. return nil, err
  167. }
  168. for _, hit := range searchResult.Hits.Hits {
  169. if !seenIDs[hit.Id] {
  170. seenIDs[hit.Id] = true
  171. allResults = append(allResults, hit)
  172. }
  173. }
  174. }
  175. }
  176. return allResults, nil
  177. }
  178. func searchPrecise(client *elastic.Client, projectName, province, city, publish string, maxResults int) ([]*elastic.SearchHit, error) {
  179. fieldsToTry := []string{"projectname.pname", "title", "detail"}
  180. filtersToTry := [][]elastic.Query{
  181. {elastic.NewTermsQuery("subtype", "中标", "成交", "合同", "单一")},
  182. {elastic.NewTermsQuery("toptype", "招标", "预告", "采购意向")},
  183. {elastic.NewTermsQuery("toptype", "拟建")},
  184. }
  185. var allResults []*elastic.SearchHit
  186. seenIDs := make(map[string]bool)
  187. // 解析发布时间
  188. var t time.Time
  189. var err error
  190. if publish != "" {
  191. t, err = time.Parse("200601", publish)
  192. if err != nil {
  193. log.Println("时间解析失败:", err)
  194. }
  195. }
  196. for _, field := range fieldsToTry {
  197. var dateRangeStart, dateRangeEnd int64
  198. if !t.IsZero() {
  199. if field == "detail" {
  200. dateRangeStart, dateRangeEnd = getYearRange(t, 60)
  201. } else {
  202. dateRangeStart, dateRangeEnd = getYearRange(t, 36)
  203. }
  204. }
  205. for _, filters := range filtersToTry {
  206. var queries []*elastic.BoolQuery
  207. if field == "detail" {
  208. // detail 只加时间 + filter
  209. query := elastic.NewBoolQuery()
  210. query = query.Must(elastic.NewMultiMatchQuery(projectName, field).Type("phrase"))
  211. query = query.Filter(filters...)
  212. if !t.IsZero() {
  213. query = query.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  214. }
  215. queries = append(queries, query)
  216. } else {
  217. // 1. 省 + 市 查询
  218. if province != "" && city != "" {
  219. query1 := elastic.NewBoolQuery()
  220. query1 = query1.Must(elastic.NewMultiMatchQuery(projectName, field).Type("phrase"))
  221. query1 = query1.Must(elastic.NewTermQuery("province", province))
  222. query1 = query1.Must(elastic.NewTermQuery("city", city))
  223. query1 = query1.Filter(filters...)
  224. if !t.IsZero() {
  225. query1 = query1.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  226. }
  227. queries = append(queries, query1)
  228. }
  229. // 2. 仅省份(城市为空或不同)
  230. if province != "" {
  231. query2 := elastic.NewBoolQuery()
  232. query2 = query2.Must(elastic.NewMultiMatchQuery(projectName, field).Type("phrase"))
  233. query2 = query2.Must(elastic.NewTermQuery("province", province))
  234. query2 = query2.Filter(filters...)
  235. if !t.IsZero() {
  236. query2 = query2.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  237. }
  238. queries = append(queries, query2)
  239. }
  240. // 3. 不限制省市
  241. if province != "" {
  242. query3 := elastic.NewBoolQuery()
  243. query3 = query3.Must(elastic.NewMultiMatchQuery(projectName, field).Type("phrase"))
  244. query3 = query3.Filter(filters...)
  245. if !t.IsZero() {
  246. query3 = query3.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  247. }
  248. queries = append(queries, query3)
  249. }
  250. }
  251. for _, query := range queries {
  252. fetchFields := elastic.NewFetchSourceContext(true).Include(
  253. "id", "title", "projectname", "projectcode", "bidamount", "area", "city",
  254. "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel",
  255. "s_winner", "winnertel", "agency", "publishtime")
  256. searchResult, err := client.Search().
  257. Index("bidding").
  258. Query(query).
  259. Size(maxResults).
  260. FetchSourceContext(fetchFields).
  261. Do(context.Background())
  262. if err != nil {
  263. return nil, err
  264. }
  265. // 打印 query JSON(调试用)
  266. //if sourceQ, err := query.Source(); err == nil {
  267. // log.Println(printInterfaceAsJSON(sourceQ))
  268. //}
  269. for _, hit := range searchResult.Hits.Hits {
  270. if !seenIDs[hit.Id] {
  271. seenIDs[hit.Id] = true
  272. allResults = append(allResults, hit)
  273. }
  274. }
  275. }
  276. }
  277. // detail 的命中足够就提前结束
  278. if field == "detail" && len(allResults) > maxResults {
  279. break
  280. }
  281. }
  282. return allResults, nil
  283. }
  284. // searchByToken 分词查询
  285. func searchByToken22(client *elastic.Client, projectName, province, city, publish string, maxResults int) ([]*elastic.SearchHit, error) {
  286. fieldsToTry := []string{"projectname.pname", "title", "detail"}
  287. filtersToTry := [][]elastic.Query{
  288. {elastic.NewTermsQuery("subtype", "中标", "成交", "合同", "单一")},
  289. {elastic.NewTermsQuery("toptype", "招标", "预告", "采购意向")},
  290. {elastic.NewTermsQuery("toptype", "拟建")},
  291. }
  292. analyzeResp, err := client.IndexAnalyze().
  293. Index("bidding").
  294. Analyzer("ik_smart").
  295. Text(projectName).
  296. Do(context.Background())
  297. if err != nil {
  298. return nil, err
  299. }
  300. var tokens []string
  301. for _, token := range analyzeResp.Tokens {
  302. tokens = append(tokens, token.Token)
  303. }
  304. if len(tokens) == 0 {
  305. return nil, fmt.Errorf("no tokens found from ik_smart")
  306. }
  307. queryText := strings.Join(tokens, " ")
  308. var allHits []*elastic.SearchHit
  309. seen := make(map[string]bool)
  310. for _, filter := range filtersToTry {
  311. query := elastic.NewBoolQuery().
  312. Must(elastic.NewMultiMatchQuery(queryText, fieldsToTry...).MinimumShouldMatch("100%")).
  313. Filter(filter...)
  314. searchResult, err := client.Search().
  315. Index("bidding").
  316. Query(query).
  317. Size(maxResults).
  318. Do(context.Background())
  319. if err != nil {
  320. continue
  321. }
  322. for _, hit := range searchResult.Hits.Hits {
  323. if !seen[hit.Id] {
  324. seen[hit.Id] = true
  325. allHits = append(allHits, hit)
  326. if len(allHits) >= maxResults {
  327. break
  328. }
  329. }
  330. }
  331. if len(allHits) >= maxResults {
  332. break
  333. }
  334. }
  335. return allHits, nil
  336. }
  337. func searchByToken(client *elastic.Client, projectName, province, city, publish string, maxResults int) ([]*elastic.SearchHit, error) {
  338. fieldsToTry := []string{"projectname.pname", "title", "detail"}
  339. filtersToTry := [][]elastic.Query{
  340. {elastic.NewTermsQuery("subtype", "中标", "成交", "合同", "单一")},
  341. {elastic.NewTermsQuery("toptype", "招标", "预告", "采购意向")},
  342. {elastic.NewTermsQuery("toptype", "拟建")},
  343. }
  344. // 解析时间
  345. var t time.Time
  346. var err error
  347. if publish != "" {
  348. t, err = time.Parse("200601", publish)
  349. if err != nil {
  350. log.Println("时间解析失败:", err)
  351. }
  352. }
  353. // 分词处理
  354. analyzeResp, err := client.IndexAnalyze().
  355. Index("bidding").
  356. Analyzer("ik_smart").
  357. Text(projectName).
  358. Do(context.Background())
  359. if err != nil {
  360. return nil, err
  361. }
  362. var tokens []string
  363. for _, token := range analyzeResp.Tokens {
  364. tokens = append(tokens, token.Token)
  365. }
  366. if len(tokens) == 0 {
  367. return nil, fmt.Errorf("no tokens found from ik_smart")
  368. }
  369. queryText := strings.Join(tokens, " ")
  370. // 指定返回字段
  371. fetchFields := elastic.NewFetchSourceContext(true).Include(
  372. "id", "title", "projectname", "projectcode", "bidamount", "area", "city",
  373. "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel",
  374. "s_winner", "winnertel", "agency", "publishtime")
  375. var allHits []*elastic.SearchHit
  376. seen := make(map[string]bool)
  377. for _, field := range fieldsToTry {
  378. var dateRangeStart, dateRangeEnd int64
  379. if !t.IsZero() {
  380. if field == "detail" {
  381. dateRangeStart, dateRangeEnd = getYearRange(t, 60)
  382. } else {
  383. dateRangeStart, dateRangeEnd = getYearRange(t, 36)
  384. }
  385. }
  386. for _, filters := range filtersToTry {
  387. var queries []*elastic.BoolQuery
  388. if field == "detail" {
  389. query := elastic.NewBoolQuery().
  390. Must(elastic.NewMatchQuery(field, queryText)).
  391. Filter(filters...)
  392. if !t.IsZero() {
  393. query = query.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  394. }
  395. queries = append(queries, query)
  396. } else {
  397. // 省+市
  398. if province != "" && city != "" {
  399. q := elastic.NewBoolQuery().
  400. Must(elastic.NewMatchQuery(field, queryText)).
  401. Must(elastic.NewTermQuery("province", province)).
  402. Must(elastic.NewTermQuery("city", city)).
  403. Filter(filters...)
  404. if !t.IsZero() {
  405. q = q.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  406. }
  407. queries = append(queries, q)
  408. }
  409. // 仅省
  410. if province != "" {
  411. q := elastic.NewBoolQuery().
  412. Must(elastic.NewMatchQuery(field, queryText)).
  413. Must(elastic.NewTermQuery("province", province)).
  414. Filter(filters...)
  415. if !t.IsZero() {
  416. q = q.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  417. }
  418. queries = append(queries, q)
  419. }
  420. // 无省市
  421. q := elastic.NewBoolQuery().
  422. Must(elastic.NewMatchQuery(field, queryText)).
  423. Filter(filters...)
  424. if !t.IsZero() {
  425. q = q.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  426. }
  427. queries = append(queries, q)
  428. }
  429. for _, query := range queries {
  430. searchResult, err := client.Search().
  431. Index("bidding").
  432. Query(query).
  433. Size(maxResults).
  434. FetchSourceContext(fetchFields).
  435. Do(context.Background())
  436. if err != nil {
  437. continue
  438. }
  439. for _, hit := range searchResult.Hits.Hits {
  440. if !seen[hit.Id] {
  441. seen[hit.Id] = true
  442. allHits = append(allHits, hit)
  443. if len(allHits) >= maxResults {
  444. return allHits, nil
  445. }
  446. }
  447. }
  448. }
  449. }
  450. if field == "detail" && len(allHits) >= maxResults {
  451. break
  452. }
  453. }
  454. return allHits, nil
  455. }
  456. // searchCommon common 查询
  457. func searchCommon22(client *elastic.Client, projectName, province, city, publish string, maxResults int) ([]*elastic.SearchHit, error) {
  458. queryMap := map[string]interface{}{
  459. "bool": map[string]interface{}{
  460. "should": []interface{}{
  461. map[string]interface{}{"common": map[string]interface{}{"projectname.pname": map[string]interface{}{"query": projectName, "cutoff_frequency": 0.01, "low_freq_operator": "and", "boost": 0.2}}},
  462. map[string]interface{}{"common": map[string]interface{}{"title": map[string]interface{}{"query": projectName, "cutoff_frequency": 0.01, "low_freq_operator": "and", "boost": 0.2}}},
  463. map[string]interface{}{"common": map[string]interface{}{"detail": map[string]interface{}{"query": projectName, "cutoff_frequency": 0.01, "low_freq_operator": "and", "boost": 0.1}}},
  464. },
  465. "minimum_should_match": 1,
  466. },
  467. }
  468. queryBytes, _ := json.Marshal(queryMap)
  469. queryBase64 := base64.StdEncoding.EncodeToString(queryBytes)
  470. query := elastic.NewWrapperQuery(queryBase64)
  471. fetchFields := elastic.NewFetchSourceContext(true).Include("id", "title", "projectname", "projectcode", "bidamount", "area", "city", "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel", "s_winner", "winnertel", "agency", "publishtime")
  472. searchResult, err := client.Search().
  473. Index("bidding").
  474. Query(query).
  475. Size(maxResults).
  476. FetchSourceContext(fetchFields).
  477. Do(context.Background())
  478. if err != nil {
  479. return nil, err
  480. }
  481. var allHits []*elastic.SearchHit
  482. seen := make(map[string]bool)
  483. for _, hit := range searchResult.Hits.Hits {
  484. if !seen[hit.Id] {
  485. seen[hit.Id] = true
  486. allHits = append(allHits, hit)
  487. }
  488. }
  489. return allHits, nil
  490. }
  491. func searchCommon(client *elastic.Client, projectName, province, city, publish string, maxResults int) ([]*elastic.SearchHit, error) {
  492. fields := []string{"projectname.pname", "title", "detail"}
  493. var t time.Time
  494. var err error
  495. if publish != "" {
  496. t, err = time.Parse("200601", publish)
  497. if err != nil {
  498. log.Println("时间解析失败:", err)
  499. }
  500. }
  501. var allHits []*elastic.SearchHit
  502. seen := make(map[string]bool)
  503. fetchFields := elastic.NewFetchSourceContext(true).Include(
  504. "id", "title", "projectname", "projectcode", "bidamount", "area", "city",
  505. "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel",
  506. "s_winner", "winnertel", "agency", "publishtime")
  507. for _, field := range fields {
  508. var dateRangeStart, dateRangeEnd int64
  509. if !t.IsZero() {
  510. if field == "detail" {
  511. dateRangeStart, dateRangeEnd = getYearRange(t, 60)
  512. } else {
  513. dateRangeStart, dateRangeEnd = getYearRange(t, 36)
  514. }
  515. }
  516. var queries []map[string]interface{}
  517. commonQuery := func(f string, boost float64) map[string]interface{} {
  518. return map[string]interface{}{
  519. "common": map[string]interface{}{
  520. f: map[string]interface{}{
  521. "query": projectName,
  522. "cutoff_frequency": 0.01,
  523. "low_freq_operator": "and",
  524. "boost": boost,
  525. },
  526. },
  527. }
  528. }
  529. if field == "detail" {
  530. // 只做普通匹配 + 时间
  531. boolQuery := map[string]interface{}{
  532. "bool": map[string]interface{}{
  533. "must": []interface{}{
  534. commonQuery(field, 0.1),
  535. },
  536. },
  537. }
  538. if !t.IsZero() {
  539. boolQuery["bool"].(map[string]interface{})["filter"] = []interface{}{
  540. map[string]interface{}{
  541. "range": map[string]interface{}{
  542. "publishtime": map[string]interface{}{
  543. "gte": dateRangeStart,
  544. "lt": dateRangeEnd,
  545. },
  546. },
  547. },
  548. }
  549. }
  550. queries = append(queries, boolQuery)
  551. } else {
  552. // 1. 省+市
  553. if province != "" && city != "" {
  554. q := map[string]interface{}{
  555. "bool": map[string]interface{}{
  556. "must": []interface{}{
  557. commonQuery(field, 0.2),
  558. map[string]interface{}{"term": map[string]interface{}{"province": province}},
  559. map[string]interface{}{"term": map[string]interface{}{"city": city}},
  560. },
  561. },
  562. }
  563. if !t.IsZero() {
  564. q["bool"].(map[string]interface{})["filter"] = []interface{}{
  565. map[string]interface{}{
  566. "range": map[string]interface{}{
  567. "publishtime": map[string]interface{}{
  568. "gte": dateRangeStart,
  569. "lt": dateRangeEnd,
  570. },
  571. },
  572. },
  573. }
  574. }
  575. queries = append(queries, q)
  576. }
  577. // 2. 仅省
  578. if province != "" {
  579. q := map[string]interface{}{
  580. "bool": map[string]interface{}{
  581. "must": []interface{}{
  582. commonQuery(field, 0.2),
  583. map[string]interface{}{"term": map[string]interface{}{"province": province}},
  584. },
  585. },
  586. }
  587. if !t.IsZero() {
  588. q["bool"].(map[string]interface{})["filter"] = []interface{}{
  589. map[string]interface{}{
  590. "range": map[string]interface{}{
  591. "publishtime": map[string]interface{}{
  592. "gte": dateRangeStart,
  593. "lt": dateRangeEnd,
  594. },
  595. },
  596. },
  597. }
  598. }
  599. queries = append(queries, q)
  600. }
  601. // 3. 不加省市
  602. q := map[string]interface{}{
  603. "bool": map[string]interface{}{
  604. "must": []interface{}{
  605. commonQuery(field, 0.2),
  606. },
  607. },
  608. }
  609. if !t.IsZero() {
  610. q["bool"].(map[string]interface{})["filter"] = []interface{}{
  611. map[string]interface{}{
  612. "range": map[string]interface{}{
  613. "publishtime": map[string]interface{}{
  614. "gte": dateRangeStart,
  615. "lt": dateRangeEnd,
  616. },
  617. },
  618. },
  619. }
  620. }
  621. queries = append(queries, q)
  622. }
  623. for _, q := range queries {
  624. // 编码 query 为 base64
  625. queryBytes, _ := json.Marshal(q)
  626. queryBase64 := base64.StdEncoding.EncodeToString(queryBytes)
  627. query := elastic.NewWrapperQuery(queryBase64)
  628. searchResult, err := client.Search().
  629. Index("bidding").
  630. Query(query).
  631. Size(maxResults).
  632. FetchSourceContext(fetchFields).
  633. Do(context.Background())
  634. if err != nil {
  635. log.Println("searchCommon 查询失败:", err)
  636. continue
  637. }
  638. for _, hit := range searchResult.Hits.Hits {
  639. if !seen[hit.Id] {
  640. seen[hit.Id] = true
  641. allHits = append(allHits, hit)
  642. if len(allHits) >= maxResults {
  643. return allHits, nil
  644. }
  645. }
  646. }
  647. }
  648. }
  649. return allHits, nil
  650. }
  651. // getYearRange calculates a date range of +/- 1 year from the base date.
  652. func getYearRange(baseDate time.Time, m int) (start, end int64) {
  653. endTime := baseDate.AddDate(0, m, 0)
  654. startTime := baseDate.AddDate(0, -m, 0)
  655. return startTime.Unix(), endTime.Unix()
  656. }