search.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. package util
  2. import (
  3. MC "app.yhyue.com/moapp/jybase/common"
  4. ME "app.yhyue.com/moapp/jybase/encrypt"
  5. "crypto/rand"
  6. "encoding/json"
  7. "fmt"
  8. "github.com/zeromicro/go-zero/core/logx"
  9. "io/ioutil"
  10. IC "jyBXCore/rpc/init"
  11. "jyBXCore/rpc/internal/config"
  12. "jyBXCore/rpc/type/bxcore"
  13. "math/big"
  14. "net/http"
  15. "net/url"
  16. "regexp"
  17. "strconv"
  18. "strings"
  19. "time"
  20. "unicode"
  21. )
  22. var (
  23. ClearHtml = regexp.MustCompile("<[^>]*>")
  24. MatchSpace = regexp.MustCompile("\\s+")
  25. filterReg3 = regexp.MustCompile("(项目|公告|公示)$")
  26. filterReg2 = regexp.MustCompile("^[)\\)>》】\\]}}〕,,;;::'\"“”。.\\??、/+=\\_—*&……\\^%$¥@!!`~·(\\(<《【\\[{{〔]+$")
  27. filterReg1 = regexp.MustCompile("^([0-9]{1,3}|[零一二三四五六七八九十]{1,2}|联系人?|电话|地址|编号|采购|政府采购|成交|更正|招标|中标|变更|结果)$")
  28. filterReg = regexp.MustCompile("^[的人号时元万公告项目地址电话邮编日期联系招标中结果成交项目项目采购采购项目政府采购公告更正公告]+$")
  29. //PhoneReg = regexp.MustCompile("^[1][3-9][0-9]{9}$")
  30. //EmailPattern = regexp.MustCompile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$")
  31. )
  32. // SearchHistory 格式化 关键词搜索历史记录
  33. func SearchHistory(history, searchValue, additionalWords string) (arrS []string) {
  34. //主关键词
  35. var searchKeys = strings.Split(searchValue, IC.C.JYKeyMark)
  36. //附加词
  37. if additionalWords != "" {
  38. for _, aws := range strings.Split(additionalWords, ",") {
  39. for _, as := range strings.Split(aws, IC.C.JYKeyMark) {
  40. searchKeys = append(searchKeys, as)
  41. }
  42. }
  43. }
  44. //关键词 和 附加词 合并,作为新的关键词历史搜索记录
  45. if len(searchKeys) > 0 {
  46. arrS = strings.Split(history, ",")
  47. //新增历史记录
  48. if history == "" {
  49. arrS = make([]string, 0)
  50. }
  51. for _, sv := range searchKeys {
  52. for k, v := range arrS {
  53. if v == strings.TrimSpace(sv) {
  54. arrS = append(arrS[:k], arrS[k+1:]...)
  55. break
  56. }
  57. }
  58. }
  59. arrS = append(arrS, searchKeys...)
  60. if len(arrS) > 10 {
  61. arrS = arrS[len(arrS)-10:]
  62. }
  63. }
  64. return arrS
  65. }
  66. func FilterKey(k string) string {
  67. k = strings.TrimSpace(k)
  68. k = filterReg3.ReplaceAllString(k, "")
  69. k = filterReg2.ReplaceAllString(k, "")
  70. k = filterReg1.ReplaceAllString(k, "")
  71. k = filterReg.ReplaceAllString(k, "")
  72. return k
  73. }
  74. // InterceptSearchKW 超过keywordsLimit个字,截断
  75. // 返回截取后的字符串和截取掉中的前3个字
  76. // b_word:截取后的关键词;a_word:截取后 后面三个字;s_word:已截取 处理过的关键词
  77. func InterceptSearchKW(word string, keywordsLimit int, isFilter bool) (bWord, aWord, sWord string) {
  78. if isFilter {
  79. word = FilterKey(word)
  80. }
  81. word = MatchSpace.ReplaceAllString(strings.TrimSpace(word), " ")
  82. words := []rune(word)
  83. if len(words) > keywordsLimit {
  84. bWord = string(words[:keywordsLimit])
  85. bWord = strings.TrimSpace(bWord)
  86. if len(words) > keywordsLimit+3 {
  87. aWord = string(words[keywordsLimit : keywordsLimit+3])
  88. } else {
  89. aWord = string(words[keywordsLimit:])
  90. }
  91. } else {
  92. bWord = word
  93. }
  94. aWord = strings.TrimSpace(aWord)
  95. sWord = MatchSpace.ReplaceAllString(bWord, IC.C.JYKeyMark)
  96. return
  97. }
  98. func HttpEs(ques, analyzer, esAddress string) (res string) {
  99. var adders []string
  100. curl := ""
  101. for _, s := range strings.Split(esAddress, ",") {
  102. adders = append(adders, s)
  103. }
  104. i, _ := rand.Int(rand.Reader, big.NewInt(int64(len(adders)))) //随机
  105. curl = adders[int(i.Int64())] + "/bidding/_analyze"
  106. URL, _ := url.Parse(curl)
  107. Q := URL.Query()
  108. Q.Add("text", ques)
  109. Q.Add("analyzer", analyzer)
  110. URL.RawQuery = Q.Encode()
  111. resp, err := http.Get(URL.String())
  112. if err != nil {
  113. logx.Info("es连接失败 err1:", err)
  114. resp, err = getESResp(ques, analyzer, adders)
  115. if err != nil {
  116. return
  117. }
  118. }
  119. result, err1 := ioutil.ReadAll(resp.Body)
  120. if err1 == nil {
  121. defer resp.Body.Close()
  122. var resMap map[string]interface{}
  123. json.Unmarshal(result, &resMap)
  124. if resMap != nil && resMap["tokens"] != nil {
  125. tokens := MC.ObjArrToMapArr(resMap["tokens"].([]interface{}))
  126. for _, v := range tokens {
  127. token := MC.ObjToString(v["token"])
  128. if len([]rune(token)) == 1 && !unicode.Is(unicode.Scripts["Han"], []rune(token)[0]) { //(P260保留单个汉字)
  129. continue
  130. }
  131. if res != "" && token != "" {
  132. res += IC.C.JYKeyMark
  133. }
  134. res += token
  135. }
  136. }
  137. }
  138. return
  139. }
  140. func getESResp(ques, analyzer string, adds []string) (resp *http.Response, err error) {
  141. for _, v := range adds {
  142. curl := v + "/bidding/_analyze"
  143. URL, _ := url.Parse(curl)
  144. Q := URL.Query()
  145. Q.Add("text", ques)
  146. Q.Add("analyzer", analyzer)
  147. URL.RawQuery = Q.Encode()
  148. resp, err = http.Get(URL.String())
  149. if err == nil {
  150. break
  151. }
  152. }
  153. return resp, err
  154. }
  155. const (
  156. RedisName = "other"
  157. RedisNameNew = "newother"
  158. SearchPageSize = 50 //招标搜索分页--每页显示数量
  159. //招标搜索分页--最大页数
  160. SearchMaxPageNum = 10 //免费用户500条记录
  161. SearchMaxPageNum_PAYED = 100 //付费用户5000条记录
  162. )
  163. // MakeCollection 是否收藏
  164. func MakeCollection(userId string, list []*bxcore.SearchList) {
  165. if userId == "" {
  166. return
  167. }
  168. if list == nil || len(list) == 0 {
  169. return
  170. }
  171. param := []interface{}{userId}
  172. var wh []string
  173. for _, v := range list {
  174. logx.Info(v.Title, "---v.id---:", v.Id)
  175. array := ME.DecodeArticleId2ByCheck(v.Id)
  176. if len(array) == 1 && array[0] != "" {
  177. param = append(param, array[0])
  178. wh = append(wh, "?")
  179. }
  180. }
  181. if len(wh) > 0 {
  182. result := IC.MainMysql.SelectBySql(`select bid from bdcollection where userid=? and bid in (`+strings.Join(wh, ",")+`)`, param...)
  183. bidMap := map[string]bool{}
  184. if result != nil {
  185. for _, v := range *result {
  186. bidMap[ME.EncodeArticleId2ByCheck(MC.ObjToString(v["bid"]))] = true
  187. }
  188. }
  189. for _, v := range list {
  190. if bidMap[v.Id] {
  191. v.IsCollected = true
  192. }
  193. }
  194. }
  195. }
  196. // IndustryFormat 行业处理
  197. func IndustryFormat(industry, subScopeClass string) (newIndustry string) {
  198. commonSubstring := func(v string) (value string) {
  199. bcs := strings.Split(v, "_")
  200. if len(bcs) == 1 {
  201. value = bcs[0]
  202. } else if len(bcs) == 2 {
  203. value = bcs[0]
  204. if strings.TrimSpace(value) == "" {
  205. value = bcs[0]
  206. }
  207. }
  208. return
  209. }
  210. bct := strings.Split(subScopeClass, ",")
  211. if bct == nil || len(bct) == 0 {
  212. return
  213. }
  214. //搜索条件中没有行业的话,取查询结果中第一个行业
  215. if industry == "" {
  216. newIndustry = commonSubstring(bct[0])
  217. } else { //搜索条件中有行业的话,取行业中和搜索条件相对应的第一个
  218. industryArr := strings.Split(industry, ",")
  219. L:
  220. for _, bc := range bct {
  221. for _, is := range industryArr {
  222. if bc == is {
  223. newIndustry = strings.TrimSpace(commonSubstring(bc))
  224. break L
  225. }
  226. }
  227. }
  228. }
  229. return
  230. }
  231. // SearchListFormat 格式化数据
  232. func SearchListFormat(userid, industry string, repl *[]map[string]interface{}, b bool) (list []*bxcore.SearchList) {
  233. for _, v := range *repl {
  234. var searchList = &bxcore.SearchList{}
  235. //正文
  236. if b {
  237. //正文匹配检索关键词
  238. highlight, _ := v["highlight"].(map[string][]string)
  239. detail := ""
  240. for _, val := range highlight["detail"] {
  241. detail += ClearHtml.ReplaceAllString(val, "")
  242. }
  243. searchList.Detail = detail
  244. }
  245. searchList.Id = ME.EncodeArticleId2ByCheck(MC.ObjToString(v["_id"])) //ME.EncodeArticleId2ByCheck(MC.ObjToString(v["_id"])) //加密信息id
  246. searchList.Area = MC.ObjToString(v["area"]) //地区
  247. searchList.AreaUrl = IC.LabelMap[searchList.Area].Url //地区分类链接
  248. searchList.BuyerClass = MC.ObjToString(v["buyerclass"]) //采购单位类型
  249. searchList.City = MC.ObjToString(v["city"]) //城市
  250. searchList.Industry = IndustryFormat(industry, strings.Trim(MC.ObjToString(v["s_subscopeclass"]), ",")) //行业
  251. searchList.IndustryUrl = IC.LabelMap[searchList.Industry].Url //行业分类地址
  252. searchList.PublishTime = MC.Int64All(v["publishtime"]) //发布时间
  253. searchList.FileExists, _ = v["isValidFile"].(bool) //是否有附件
  254. searchList.Subtype = MC.ObjToString(v["subtype"]) //信息类型
  255. searchList.SubtypeUrl = IC.LabelMap[searchList.Subtype].Url //信息类型分类链接
  256. searchList.Title = MC.ObjToString(v["title"]) //标题
  257. searchList.ProjectName = MC.ObjToString(v["projectname"]) //项目名称
  258. searchList.ProjectCode = MC.ObjToString(v["projectcode"]) //项目代码
  259. if budget, ok := v["budget"].(float64); ok && budget > 0 { //预算
  260. searchList.Budget = int64(budget)
  261. }
  262. if bidAmount, ok := v["bidamount"].(float64); ok && bidAmount > 0 { //中标金额
  263. searchList.BidAmount = int64(bidAmount)
  264. }
  265. searchList.Buyer = MC.ObjToString(v["buyer"]) //采购单位
  266. searchList.BuyerTel = MC.ObjToString(v["buyertel"]) //采购单位联系方式
  267. searchList.BuyerPerson = MC.ObjToString(v["buyerperson"]) //采购单位联系人
  268. searchList.Agency = MC.ObjToString(v["agency"]) //代理机构
  269. searchList.AgencyTel = MC.ObjToString(v["agencytel"]) //代理机构联系电话
  270. searchList.AgencyPerson = MC.ObjToString(v["agencyperson"]) //代理机构联系人
  271. searchList.BidOpenTime = MC.Int64All(v["bidopentime"]) //开标时间
  272. searchList.BidEndTime = MC.Int64All(v["bidendtime"]) //发布时间
  273. searchList.SignEndTime = MC.Int64All(v["signendtime"]) //投标截止日期
  274. searchList.Site = MC.ObjToString(v["site"]) //网站来源名称
  275. searchList.SpiderCode = MC.ObjToString(v["spidercode"]) //网站来源代码
  276. searchList.Winner = MC.ObjToString(v["winner"]) //中标企业
  277. winnerList := MC.ObjToString(v["s_winner"]) //中标企业名称集合
  278. if winnerList != "" && len(strings.Split(winnerList, ",")) > 0 {
  279. for wk, wv := range strings.Split(winnerList, ",") {
  280. var (
  281. winnerId = ""
  282. )
  283. if v["entidlist"] != nil {
  284. if entIdList := MC.ObjArrToStringArr(v["entidlist"].([]interface{})); len(entIdList) > wk { //中标企业id集合
  285. winnerId = entIdList[wk]
  286. }
  287. }
  288. searchList.WinnerInfo = append(searchList.WinnerInfo, &bxcore.WinnerInfo{
  289. Winner: wv, //中标企业 需要单独处理
  290. WinnerTel: MC.ObjToString(v["winnertel"]), //中标企业联系电话
  291. WinnerPerson: MC.ObjToString(v["winnerperson"]), //中标企业联系人
  292. WinnerId: MC.If(winnerId != "" && len([]rune(winnerId)) > 12, ME.EncodeArticleId2ByCheck(winnerId), "").(string), //中标企业加密id 存在winnerId 异常的情况
  293. })
  294. }
  295. }
  296. searchList.ProjectInfo = &bxcore.PInfo{} //拟建项目信息
  297. if v["projectinfo"] != nil {
  298. pInfo := MC.ObjToMap(v["projectinfo"])
  299. searchList.ProjectInfo.ApproveCode = MC.ObjToString((*pInfo)["approvecode"])
  300. searchList.ProjectInfo.ApproveContent = MC.ObjToString((*pInfo)["approvecontent"])
  301. searchList.ProjectInfo.ApproveDept = MC.ObjToString((*pInfo)["approvedept"])
  302. searchList.ProjectInfo.ApproveStatus = MC.ObjToString((*pInfo)["approvestatus"])
  303. searchList.ProjectInfo.ProjectType = MC.ObjToString((*pInfo)["projecttype"])
  304. searchList.ProjectInfo.ApproveNumber = MC.ObjToString((*pInfo)["approvenumber"])
  305. searchList.ProjectInfo.ApproveTime = MC.ObjToString((*pInfo)["approvetime"])
  306. }
  307. list = append(list, searchList)
  308. }
  309. return
  310. }
  311. // IsOptimize 付费用户搜索优化
  312. // 需求来源:付费用户 默认查询 五年内数据,数据查询耗时,
  313. // 付费用户 且开关打开,针对前两页数据,满足关键词(< 7个字),查询时间范围一年以上,缩短查询时间
  314. func IsOptimize(cc config.Config, in *bxcore.SearchReq) bool {
  315. if cc.PaySearchLimit.Switch && in.UserType != "fType" {
  316. //首页----字数(<7)
  317. if in.PageNum <= cc.PaySearchLimit.PageNum && len([]rune(in.KeyWords)) < cc.PaySearchLimit.WordSize {
  318. //时间超过一年----
  319. if pTime := GetPublishTime(cc.PaySearchLimit.Year, cc.PaySearchLimit.Month, in.PublishTime); pTime != "" {
  320. in.PublishTime = pTime
  321. return true
  322. }
  323. }
  324. }
  325. return false
  326. }
  327. // GetPublishTime 查询时间调整
  328. func GetPublishTime(y, m int, publishTime string) string {
  329. //发布时间
  330. timeArray := strings.Split(publishTime, "-")
  331. if len(timeArray) == 2 {
  332. startTime, err1 := strconv.ParseInt(timeArray[0], 10, 64)
  333. endTime, err2 := strconv.ParseInt(timeArray[1], 10, 64)
  334. if err1 == nil && err2 == nil {
  335. if endTime == 0 {
  336. endTime = time.Now().Unix()
  337. }
  338. //重新计算数据查询 开始时间
  339. pTime := time.Unix(endTime, 0).AddDate(y, m, 0).Unix()
  340. //从新定义搜索时间跨度
  341. if endTime-startTime > pTime {
  342. return fmt.Sprintf("%d-%d", pTime, endTime)
  343. }
  344. }
  345. }
  346. return ""
  347. }
  348. // GetQueryItems 免费 标题(title) 正文(content) 老用户【中标企业(winner)】
  349. // 付费用户 全部(all)、标题(title) 正文(content) 会员: 采购单位(buyer) 中标企业(winner) 招标代理机构(agency) 附件(file)
  350. // 项目名称projectname和标的物purchasing(ppa)
  351. func GetQueryItems(selectType string, limitOldTime, registerData int64, isPay bool) (items []string) {
  352. if isPay {
  353. for _, t := range strings.Split(selectType, ",") {
  354. if t == "content" {
  355. items = append(items, "detail")
  356. } else if t == "buyer" {
  357. items = append(items, "mbuyer")
  358. } else if t == "winner" {
  359. items = append(items, "mwinner")
  360. } else if t == "agency" {
  361. items = append(items, "magency")
  362. } else if t == "title" {
  363. items = append(items, "title")
  364. } else if t == "ppa" {
  365. items = append(items, []string{"purchasing", "projectname.pname"}...)
  366. } else if t == "file" { //dev4.7.8 标讯优化:搜索范围附件-》全部用户可用
  367. items = append(items, "filetext")
  368. }
  369. }
  370. return
  371. }
  372. //老用户 使用付费功能
  373. isOldUser := registerData != 0 && registerData < limitOldTime
  374. for _, t := range strings.Split(selectType, ",") {
  375. if t == "winner" && isOldUser {
  376. items = append(items, "mwinner")
  377. } else if t == "title" {
  378. items = append(items, "title")
  379. } else if t == "content" {
  380. items = append(items, "detail")
  381. } else if t == "file" { //dev4.7.8 标讯优化:搜索范围附件-》全部用户可用
  382. items = append(items, "filetext")
  383. }
  384. }
  385. return
  386. }