query.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. package elastic
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "net/url"
  8. "strings"
  9. "time"
  10. "go-common/library/ecode"
  11. httpx "go-common/library/net/http/blademaster"
  12. timex "go-common/library/time"
  13. "github.com/pkg/errors"
  14. )
  15. const (
  16. // OrderAsc order ascend
  17. OrderAsc = "asc"
  18. // OrderDesc order descend
  19. OrderDesc = "desc"
  20. // RangeScopeLoRo left open & right open
  21. RangeScopeLoRo rangeScope = "( )"
  22. // RangeScopeLoRc left open & right close
  23. RangeScopeLoRc rangeScope = "( ]"
  24. // RangeScopeLcRo left close & right open
  25. RangeScopeLcRo rangeScope = "[ )"
  26. // RangeScopeLcRc lect close & right close
  27. RangeScopeLcRc rangeScope = "[ ]"
  28. // NotTypeEq not type eq
  29. NotTypeEq notType = "eq"
  30. // NotTypeIn not type in
  31. NotTypeIn notType = "in"
  32. // NotTypeRange not type range
  33. NotTypeRange notType = "range"
  34. // LikeLevelHigh wildcard keyword
  35. LikeLevelHigh likeLevel = "high"
  36. // LikeLevelMiddle ngram(1,2)
  37. LikeLevelMiddle likeLevel = "middle"
  38. // LikeLevelLow match split word
  39. LikeLevelLow likeLevel = "low"
  40. // IndexTypeYear index by year
  41. IndexTypeYear indexType = "year"
  42. // IndexTypeMonth index by month
  43. IndexTypeMonth indexType = "month"
  44. // IndexTypeWeek index by week
  45. IndexTypeWeek indexType = "week"
  46. // IndexTypeDay index by day
  47. IndexTypeDay indexType = "day"
  48. // EnhancedModeGroupBy group by mode
  49. EnhancedModeGroupBy enhancedType = "group_by"
  50. // EnhancedModeDistinct distinct mode
  51. EnhancedModeDistinct enhancedType = "distinct"
  52. // EnhancedModeSum sum mode
  53. EnhancedModeSum enhancedType = "sum"
  54. )
  55. type (
  56. notType string
  57. rangeScope string
  58. likeLevel string
  59. indexType string
  60. enhancedType string
  61. )
  62. var (
  63. _defaultHost = "http://manager.bilibili.co"
  64. _pathQuery = "/x/admin/search/query"
  65. _pathUpsert = "/x/admin/search/upsert"
  66. _defaultHTTPClient = &httpx.ClientConfig{
  67. App: &httpx.App{
  68. Key: "3c4e41f926e51656",
  69. Secret: "26a2095b60c24154521d24ae62b885bb",
  70. },
  71. Dial: timex.Duration(time.Second),
  72. Timeout: timex.Duration(time.Second),
  73. }
  74. // for index by week
  75. weeks = map[int]string{0: "0107", 1: "0815", 2: "1623", 3: "2431"}
  76. )
  77. // Config Elastic config
  78. type Config struct {
  79. Host string
  80. HTTPClient *httpx.ClientConfig
  81. }
  82. // response query elastic response
  83. type response struct {
  84. Code int `json:"code"`
  85. Message string `json:"message"`
  86. Data json.RawMessage `json:"data"`
  87. }
  88. type query struct {
  89. Fields []string `json:"fields"`
  90. From string `json:"from"`
  91. OrderScoreFirst bool `json:"order_score_first"`
  92. OrderRandomSeed string `json:"order_random_seed"`
  93. Highlight bool `json:"highlight"`
  94. Pn int `json:"pn"`
  95. Ps int `json:"ps"`
  96. Order []map[string]string `json:"order,omitempty"`
  97. Where where `json:"where,omitempty"`
  98. }
  99. func (q *query) string() (string, error) {
  100. var (
  101. sli []string
  102. m = make(map[string]bool)
  103. )
  104. for _, i := range strings.Split(q.From, ",") {
  105. if m[i] {
  106. continue
  107. }
  108. m[i] = true
  109. sli = append(sli, i)
  110. }
  111. q.From = strings.Join(sli, ",")
  112. bs, err := json.Marshal(q)
  113. if err != nil {
  114. return "", err
  115. }
  116. return string(bs), nil
  117. }
  118. type where struct {
  119. GroupBy string `json:"group_by,omitempty"`
  120. Like []whereLike `json:"like,omitempty"`
  121. Eq map[string]interface{} `json:"eq,omitempty"`
  122. Or map[string]interface{} `json:"or,omitempty"`
  123. In map[string][]interface{} `json:"in,omitempty"`
  124. Range map[string]string `json:"range,omitempty"`
  125. Combo []*Combo `json:"combo,omitempty"`
  126. Not map[notType]map[string]bool `json:"not,omitempty"`
  127. Enhanced []interface{} `json:"enhanced,omitempty"`
  128. }
  129. type whereLike struct {
  130. Fields []string `json:"kw_fields"`
  131. Words []string `json:"kw"`
  132. Or bool `json:"or"`
  133. Level likeLevel `json:"level"`
  134. }
  135. // Combo mix eq & in & range
  136. type Combo struct {
  137. EQ []map[string]interface{} `json:"eq,omitempty"`
  138. In []map[string][]interface{} `json:"in,omitempty"`
  139. Range []map[string]string `json:"range,omitempty"`
  140. NotEQ []map[string]interface{} `json:"not_eq,omitempty"`
  141. NotIn []map[string][]interface{} `json:"not_in,omitempty"`
  142. NotRange []map[string]string `json:"not_range,omitempty"`
  143. Min struct {
  144. EQ int `json:"eq,omitempty"`
  145. In int `json:"in,omitempty"`
  146. Range int `json:"range,omitempty"`
  147. NotEQ int `json:"not_eq,omitempty"`
  148. NotIn int `json:"not_in,omitempty"`
  149. NotRange int `json:"not_range,omitempty"`
  150. Min int `json:"min"`
  151. } `json:"min"`
  152. }
  153. // ComboEQ .
  154. func (cmb *Combo) ComboEQ(eq []map[string]interface{}) *Combo {
  155. cmb.EQ = append(cmb.EQ, eq...)
  156. return cmb
  157. }
  158. // ComboRange .
  159. func (cmb *Combo) ComboRange(r []map[string]string) *Combo {
  160. cmb.Range = append(cmb.Range, r...)
  161. return cmb
  162. }
  163. // ComboIn .
  164. func (cmb *Combo) ComboIn(in []map[string][]interface{}) *Combo {
  165. cmb.In = append(cmb.In, in...)
  166. return cmb
  167. }
  168. // ComboNotEQ .
  169. func (cmb *Combo) ComboNotEQ(eq []map[string]interface{}) *Combo {
  170. cmb.NotEQ = append(cmb.NotEQ, eq...)
  171. return cmb
  172. }
  173. // ComboNotRange .
  174. func (cmb *Combo) ComboNotRange(r []map[string]string) *Combo {
  175. cmb.NotRange = append(cmb.NotRange, r...)
  176. return cmb
  177. }
  178. // ComboNotIn .
  179. func (cmb *Combo) ComboNotIn(in []map[string][]interface{}) *Combo {
  180. cmb.NotIn = append(cmb.NotIn, in...)
  181. return cmb
  182. }
  183. // MinEQ .
  184. func (cmb *Combo) MinEQ(min int) *Combo {
  185. cmb.Min.EQ = min
  186. return cmb
  187. }
  188. // MinIn .
  189. func (cmb *Combo) MinIn(min int) *Combo {
  190. cmb.Min.In = min
  191. return cmb
  192. }
  193. // MinRange .
  194. func (cmb *Combo) MinRange(min int) *Combo {
  195. cmb.Min.Range = min
  196. return cmb
  197. }
  198. // MinNotEQ .
  199. func (cmb *Combo) MinNotEQ(min int) *Combo {
  200. cmb.Min.NotEQ = min
  201. return cmb
  202. }
  203. // MinNotIn .
  204. func (cmb *Combo) MinNotIn(min int) *Combo {
  205. cmb.Min.NotIn = min
  206. return cmb
  207. }
  208. // MinNotRange .
  209. func (cmb *Combo) MinNotRange(min int) *Combo {
  210. cmb.Min.NotRange = min
  211. return cmb
  212. }
  213. // MinAll .
  214. func (cmb *Combo) MinAll(min int) *Combo {
  215. cmb.Min.Min = min
  216. return cmb
  217. }
  218. type groupBy struct {
  219. Mode enhancedType `json:"mode"`
  220. Field string `json:"field"`
  221. Order []map[string]string `json:"order"`
  222. }
  223. type enhance struct {
  224. Mode enhancedType `json:"mode"`
  225. Field string `json:"field"`
  226. Order []map[string]string `json:"order,omitempty"`
  227. Size int `json:"size,omitempty"`
  228. }
  229. // Elastic clastic instance
  230. type Elastic struct {
  231. c *Config
  232. client *httpx.Client
  233. }
  234. // NewElastic .
  235. func NewElastic(c *Config) *Elastic {
  236. if c == nil {
  237. c = &Config{
  238. Host: _defaultHost,
  239. HTTPClient: _defaultHTTPClient,
  240. }
  241. }
  242. return &Elastic{
  243. c: c,
  244. client: httpx.NewClient(c.HTTPClient),
  245. }
  246. }
  247. // Request request to elastic
  248. type Request struct {
  249. *Elastic
  250. q query
  251. business string
  252. }
  253. // NewRequest new a request every search query
  254. func (e *Elastic) NewRequest(business string) *Request {
  255. return &Request{
  256. Elastic: e,
  257. business: business,
  258. q: query{
  259. Fields: []string{},
  260. Highlight: false,
  261. OrderScoreFirst: true,
  262. OrderRandomSeed: "",
  263. Pn: 1,
  264. Ps: 10,
  265. },
  266. }
  267. }
  268. // Fields add query fields
  269. func (r *Request) Fields(fields ...string) *Request {
  270. r.q.Fields = append(r.q.Fields, fields...)
  271. return r
  272. }
  273. // Index add query index
  274. func (r *Request) Index(indexes ...string) *Request {
  275. r.q.From = strings.Join(indexes, ",")
  276. return r
  277. }
  278. // IndexByMod index by mod
  279. func (r *Request) IndexByMod(prefix string, val, mod int64) *Request {
  280. tmp := mod - 1
  281. var digit int
  282. for tmp > 0 {
  283. tmp /= 10
  284. digit++
  285. }
  286. format := fmt.Sprintf("%s_%%0%dd", prefix, digit)
  287. r.q.From = fmt.Sprintf(format, val%mod)
  288. return r
  289. }
  290. // IndexByTime index by time
  291. func (r *Request) IndexByTime(prefix string, typ indexType, begin, end time.Time) *Request {
  292. var (
  293. buf bytes.Buffer
  294. index string
  295. indexes = make(map[string]struct{})
  296. )
  297. for {
  298. year := begin.Format("2006")
  299. month := begin.Format("01")
  300. switch typ {
  301. case IndexTypeYear:
  302. index = strings.Join([]string{prefix, year}, "_")
  303. case IndexTypeMonth:
  304. index = strings.Join([]string{prefix, year, month}, "_")
  305. case IndexTypeDay:
  306. day := begin.Format("02")
  307. index = strings.Join([]string{prefix, year, month, day}, "_")
  308. case IndexTypeWeek:
  309. index = strings.Join([]string{prefix, year, month, weeks[begin.Day()/8]}, "_")
  310. }
  311. if begin.After(end) && begin.Day() != end.Day() {
  312. break
  313. }
  314. indexes[index] = struct{}{}
  315. begin = begin.AddDate(0, 0, 1)
  316. }
  317. for i := range indexes {
  318. buf.WriteString(i)
  319. buf.WriteString(",")
  320. }
  321. r.q.From = strings.TrimSuffix(buf.String(), ",")
  322. return r
  323. }
  324. // OrderScoreFirst switch for order score first
  325. func (r *Request) OrderScoreFirst(v bool) *Request {
  326. r.q.OrderScoreFirst = v
  327. return r
  328. }
  329. // OrderRandomSeed switch for order random
  330. func (r *Request) OrderRandomSeed(v string) *Request {
  331. r.q.OrderRandomSeed = v
  332. return r
  333. }
  334. // Highlight switch from highlight
  335. func (r *Request) Highlight(v bool) *Request {
  336. r.q.Highlight = v
  337. return r
  338. }
  339. // Pn page number
  340. func (r *Request) Pn(v int) *Request {
  341. r.q.Pn = v
  342. return r
  343. }
  344. // Ps page size
  345. func (r *Request) Ps(v int) *Request {
  346. r.q.Ps = v
  347. return r
  348. }
  349. // Order filed sort
  350. func (r *Request) Order(field, sort string) *Request {
  351. if sort != OrderAsc {
  352. sort = OrderDesc
  353. }
  354. r.q.Order = append(r.q.Order, map[string]string{field: sort})
  355. return r
  356. }
  357. // WhereEq where qual
  358. func (r *Request) WhereEq(field string, eq interface{}) *Request {
  359. if r.q.Where.Eq == nil {
  360. r.q.Where.Eq = make(map[string]interface{})
  361. }
  362. r.q.Where.Eq[field] = eq
  363. return r
  364. }
  365. // WhereOr where or
  366. func (r *Request) WhereOr(field string, or interface{}) *Request {
  367. if r.q.Where.Or == nil {
  368. r.q.Where.Or = make(map[string]interface{})
  369. }
  370. r.q.Where.Or[field] = or
  371. return r
  372. }
  373. // WhereIn where in
  374. func (r *Request) WhereIn(field string, in interface{}) *Request {
  375. if r.q.Where.In == nil {
  376. r.q.Where.In = make(map[string][]interface{})
  377. }
  378. switch v := in.(type) {
  379. case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, string:
  380. r.q.Where.In[field] = append(r.q.Where.In[field], v)
  381. case []int:
  382. for _, i := range v {
  383. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  384. }
  385. case []int64:
  386. for _, i := range v {
  387. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  388. }
  389. case []string:
  390. for _, i := range v {
  391. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  392. }
  393. case []int8:
  394. for _, i := range v {
  395. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  396. }
  397. case []int16:
  398. for _, i := range v {
  399. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  400. }
  401. case []int32:
  402. for _, i := range v {
  403. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  404. }
  405. case []uint:
  406. for _, i := range v {
  407. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  408. }
  409. case []uint8:
  410. for _, i := range v {
  411. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  412. }
  413. case []uint16:
  414. for _, i := range v {
  415. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  416. }
  417. case []uint32:
  418. for _, i := range v {
  419. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  420. }
  421. case []uint64:
  422. for _, i := range v {
  423. r.q.Where.In[field] = append(r.q.Where.In[field], i)
  424. }
  425. }
  426. return r
  427. }
  428. // WhereRange where range
  429. func (r *Request) WhereRange(field string, start, end interface{}, scope rangeScope) *Request {
  430. if r.q.Where.Range == nil {
  431. r.q.Where.Range = make(map[string]string)
  432. }
  433. if start == nil {
  434. start = ""
  435. }
  436. if end == nil {
  437. end = ""
  438. }
  439. switch scope {
  440. case RangeScopeLoRo:
  441. r.q.Where.Range[field] = fmt.Sprintf("(%v,%v)", start, end)
  442. case RangeScopeLoRc:
  443. r.q.Where.Range[field] = fmt.Sprintf("(%v,%v]", start, end)
  444. case RangeScopeLcRo:
  445. r.q.Where.Range[field] = fmt.Sprintf("[%v,%v)", start, end)
  446. case RangeScopeLcRc:
  447. r.q.Where.Range[field] = fmt.Sprintf("[%v,%v]", start, end)
  448. }
  449. return r
  450. }
  451. // WhereNot where not
  452. func (r *Request) WhereNot(typ notType, fields ...string) *Request {
  453. if r.q.Where.Not == nil {
  454. r.q.Where.Not = make(map[notType]map[string]bool)
  455. }
  456. if r.q.Where.Not[typ] == nil {
  457. r.q.Where.Not[typ] = make(map[string]bool)
  458. }
  459. for _, v := range fields {
  460. r.q.Where.Not[typ][v] = true
  461. }
  462. return r
  463. }
  464. // WhereLike where like
  465. func (r *Request) WhereLike(fields, words []string, or bool, level likeLevel) *Request {
  466. if len(fields) == 0 || len(words) == 0 {
  467. return r
  468. }
  469. l := whereLike{Fields: fields, Words: words, Or: or, Level: level}
  470. r.q.Where.Like = append(r.q.Where.Like, l)
  471. return r
  472. }
  473. // WhereCombo where combo
  474. func (r *Request) WhereCombo(cmb ...*Combo) *Request {
  475. r.q.Where.Combo = append(r.q.Where.Combo, cmb...)
  476. return r
  477. }
  478. // GroupBy where group by
  479. func (r *Request) GroupBy(mode enhancedType, field string, order []map[string]string) *Request {
  480. for _, i := range order {
  481. for k, v := range i {
  482. if v != OrderAsc {
  483. i[k] = OrderDesc
  484. }
  485. }
  486. }
  487. r.q.Where.Enhanced = append(r.q.Where.Enhanced, groupBy{Mode: mode, Field: field, Order: order})
  488. return r
  489. }
  490. // Sum where enhance sum
  491. func (r *Request) Sum(field string) *Request {
  492. r.q.Where.Enhanced = append(r.q.Where.Enhanced, enhance{Mode: EnhancedModeSum, Field: field})
  493. return r
  494. }
  495. // Scan parse the query response data
  496. func (r *Request) Scan(ctx context.Context, result interface{}) (err error) {
  497. q, err := r.q.string()
  498. if err != nil {
  499. return
  500. }
  501. params := url.Values{}
  502. params.Add("business", r.business)
  503. params.Add("query", q)
  504. response := new(response)
  505. if err = r.client.Get(ctx, r.c.Host+_pathQuery, "", params, &response); err != nil {
  506. return
  507. }
  508. if !ecode.Int(response.Code).Equal(ecode.OK) {
  509. return ecode.Int(response.Code)
  510. }
  511. err = errors.Wrapf(json.Unmarshal(response.Data, &result), "scan(%s)", response.Data)
  512. return
  513. }
  514. // Params get query parameters
  515. func (r *Request) Params() string {
  516. q, _ := r.q.string()
  517. return fmt.Sprintf("business=%s&query=%s", r.business, q)
  518. }