123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578 |
- package elastic
- import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "net/url"
- "strings"
- "time"
- "go-common/library/ecode"
- httpx "go-common/library/net/http/blademaster"
- timex "go-common/library/time"
- "github.com/pkg/errors"
- )
- const (
- // OrderAsc order ascend
- OrderAsc = "asc"
- // OrderDesc order descend
- OrderDesc = "desc"
- // RangeScopeLoRo left open & right open
- RangeScopeLoRo rangeScope = "( )"
- // RangeScopeLoRc left open & right close
- RangeScopeLoRc rangeScope = "( ]"
- // RangeScopeLcRo left close & right open
- RangeScopeLcRo rangeScope = "[ )"
- // RangeScopeLcRc lect close & right close
- RangeScopeLcRc rangeScope = "[ ]"
- // NotTypeEq not type eq
- NotTypeEq notType = "eq"
- // NotTypeIn not type in
- NotTypeIn notType = "in"
- // NotTypeRange not type range
- NotTypeRange notType = "range"
- // LikeLevelHigh wildcard keyword
- LikeLevelHigh likeLevel = "high"
- // LikeLevelMiddle ngram(1,2)
- LikeLevelMiddle likeLevel = "middle"
- // LikeLevelLow match split word
- LikeLevelLow likeLevel = "low"
- // IndexTypeYear index by year
- IndexTypeYear indexType = "year"
- // IndexTypeMonth index by month
- IndexTypeMonth indexType = "month"
- // IndexTypeWeek index by week
- IndexTypeWeek indexType = "week"
- // IndexTypeDay index by day
- IndexTypeDay indexType = "day"
- // EnhancedModeGroupBy group by mode
- EnhancedModeGroupBy enhancedType = "group_by"
- // EnhancedModeDistinct distinct mode
- EnhancedModeDistinct enhancedType = "distinct"
- // EnhancedModeSum sum mode
- EnhancedModeSum enhancedType = "sum"
- )
- type (
- notType string
- rangeScope string
- likeLevel string
- indexType string
- enhancedType string
- )
- var (
- _defaultHost = "http://manager.bilibili.co"
- _pathQuery = "/x/admin/search/query"
- _pathUpsert = "/x/admin/search/upsert"
- _defaultHTTPClient = &httpx.ClientConfig{
- App: &httpx.App{
- Key: "3c4e41f926e51656",
- Secret: "26a2095b60c24154521d24ae62b885bb",
- },
- Dial: timex.Duration(time.Second),
- Timeout: timex.Duration(time.Second),
- }
- // for index by week
- weeks = map[int]string{0: "0107", 1: "0815", 2: "1623", 3: "2431"}
- )
- // Config Elastic config
- type Config struct {
- Host string
- HTTPClient *httpx.ClientConfig
- }
- // response query elastic response
- type response struct {
- Code int `json:"code"`
- Message string `json:"message"`
- Data json.RawMessage `json:"data"`
- }
- type query struct {
- Fields []string `json:"fields"`
- From string `json:"from"`
- OrderScoreFirst bool `json:"order_score_first"`
- OrderRandomSeed string `json:"order_random_seed"`
- Highlight bool `json:"highlight"`
- Pn int `json:"pn"`
- Ps int `json:"ps"`
- Order []map[string]string `json:"order,omitempty"`
- Where where `json:"where,omitempty"`
- }
- func (q *query) string() (string, error) {
- var (
- sli []string
- m = make(map[string]bool)
- )
- for _, i := range strings.Split(q.From, ",") {
- if m[i] {
- continue
- }
- m[i] = true
- sli = append(sli, i)
- }
- q.From = strings.Join(sli, ",")
- bs, err := json.Marshal(q)
- if err != nil {
- return "", err
- }
- return string(bs), nil
- }
- type where struct {
- GroupBy string `json:"group_by,omitempty"`
- Like []whereLike `json:"like,omitempty"`
- Eq map[string]interface{} `json:"eq,omitempty"`
- Or map[string]interface{} `json:"or,omitempty"`
- In map[string][]interface{} `json:"in,omitempty"`
- Range map[string]string `json:"range,omitempty"`
- Combo []*Combo `json:"combo,omitempty"`
- Not map[notType]map[string]bool `json:"not,omitempty"`
- Enhanced []interface{} `json:"enhanced,omitempty"`
- }
- type whereLike struct {
- Fields []string `json:"kw_fields"`
- Words []string `json:"kw"`
- Or bool `json:"or"`
- Level likeLevel `json:"level"`
- }
- // Combo mix eq & in & range
- type Combo struct {
- EQ []map[string]interface{} `json:"eq,omitempty"`
- In []map[string][]interface{} `json:"in,omitempty"`
- Range []map[string]string `json:"range,omitempty"`
- NotEQ []map[string]interface{} `json:"not_eq,omitempty"`
- NotIn []map[string][]interface{} `json:"not_in,omitempty"`
- NotRange []map[string]string `json:"not_range,omitempty"`
- Min struct {
- EQ int `json:"eq,omitempty"`
- In int `json:"in,omitempty"`
- Range int `json:"range,omitempty"`
- NotEQ int `json:"not_eq,omitempty"`
- NotIn int `json:"not_in,omitempty"`
- NotRange int `json:"not_range,omitempty"`
- Min int `json:"min"`
- } `json:"min"`
- }
- // ComboEQ .
- func (cmb *Combo) ComboEQ(eq []map[string]interface{}) *Combo {
- cmb.EQ = append(cmb.EQ, eq...)
- return cmb
- }
- // ComboRange .
- func (cmb *Combo) ComboRange(r []map[string]string) *Combo {
- cmb.Range = append(cmb.Range, r...)
- return cmb
- }
- // ComboIn .
- func (cmb *Combo) ComboIn(in []map[string][]interface{}) *Combo {
- cmb.In = append(cmb.In, in...)
- return cmb
- }
- // ComboNotEQ .
- func (cmb *Combo) ComboNotEQ(eq []map[string]interface{}) *Combo {
- cmb.NotEQ = append(cmb.NotEQ, eq...)
- return cmb
- }
- // ComboNotRange .
- func (cmb *Combo) ComboNotRange(r []map[string]string) *Combo {
- cmb.NotRange = append(cmb.NotRange, r...)
- return cmb
- }
- // ComboNotIn .
- func (cmb *Combo) ComboNotIn(in []map[string][]interface{}) *Combo {
- cmb.NotIn = append(cmb.NotIn, in...)
- return cmb
- }
- // MinEQ .
- func (cmb *Combo) MinEQ(min int) *Combo {
- cmb.Min.EQ = min
- return cmb
- }
- // MinIn .
- func (cmb *Combo) MinIn(min int) *Combo {
- cmb.Min.In = min
- return cmb
- }
- // MinRange .
- func (cmb *Combo) MinRange(min int) *Combo {
- cmb.Min.Range = min
- return cmb
- }
- // MinNotEQ .
- func (cmb *Combo) MinNotEQ(min int) *Combo {
- cmb.Min.NotEQ = min
- return cmb
- }
- // MinNotIn .
- func (cmb *Combo) MinNotIn(min int) *Combo {
- cmb.Min.NotIn = min
- return cmb
- }
- // MinNotRange .
- func (cmb *Combo) MinNotRange(min int) *Combo {
- cmb.Min.NotRange = min
- return cmb
- }
- // MinAll .
- func (cmb *Combo) MinAll(min int) *Combo {
- cmb.Min.Min = min
- return cmb
- }
- type groupBy struct {
- Mode enhancedType `json:"mode"`
- Field string `json:"field"`
- Order []map[string]string `json:"order"`
- }
- type enhance struct {
- Mode enhancedType `json:"mode"`
- Field string `json:"field"`
- Order []map[string]string `json:"order,omitempty"`
- Size int `json:"size,omitempty"`
- }
- // Elastic clastic instance
- type Elastic struct {
- c *Config
- client *httpx.Client
- }
- // NewElastic .
- func NewElastic(c *Config) *Elastic {
- if c == nil {
- c = &Config{
- Host: _defaultHost,
- HTTPClient: _defaultHTTPClient,
- }
- }
- return &Elastic{
- c: c,
- client: httpx.NewClient(c.HTTPClient),
- }
- }
- // Request request to elastic
- type Request struct {
- *Elastic
- q query
- business string
- }
- // NewRequest new a request every search query
- func (e *Elastic) NewRequest(business string) *Request {
- return &Request{
- Elastic: e,
- business: business,
- q: query{
- Fields: []string{},
- Highlight: false,
- OrderScoreFirst: true,
- OrderRandomSeed: "",
- Pn: 1,
- Ps: 10,
- },
- }
- }
- // Fields add query fields
- func (r *Request) Fields(fields ...string) *Request {
- r.q.Fields = append(r.q.Fields, fields...)
- return r
- }
- // Index add query index
- func (r *Request) Index(indexes ...string) *Request {
- r.q.From = strings.Join(indexes, ",")
- return r
- }
- // IndexByMod index by mod
- func (r *Request) IndexByMod(prefix string, val, mod int64) *Request {
- tmp := mod - 1
- var digit int
- for tmp > 0 {
- tmp /= 10
- digit++
- }
- format := fmt.Sprintf("%s_%%0%dd", prefix, digit)
- r.q.From = fmt.Sprintf(format, val%mod)
- return r
- }
- // IndexByTime index by time
- func (r *Request) IndexByTime(prefix string, typ indexType, begin, end time.Time) *Request {
- var (
- buf bytes.Buffer
- index string
- indexes = make(map[string]struct{})
- )
- for {
- year := begin.Format("2006")
- month := begin.Format("01")
- switch typ {
- case IndexTypeYear:
- index = strings.Join([]string{prefix, year}, "_")
- case IndexTypeMonth:
- index = strings.Join([]string{prefix, year, month}, "_")
- case IndexTypeDay:
- day := begin.Format("02")
- index = strings.Join([]string{prefix, year, month, day}, "_")
- case IndexTypeWeek:
- index = strings.Join([]string{prefix, year, month, weeks[begin.Day()/8]}, "_")
- }
- if begin.After(end) && begin.Day() != end.Day() {
- break
- }
- indexes[index] = struct{}{}
- begin = begin.AddDate(0, 0, 1)
- }
- for i := range indexes {
- buf.WriteString(i)
- buf.WriteString(",")
- }
- r.q.From = strings.TrimSuffix(buf.String(), ",")
- return r
- }
- // OrderScoreFirst switch for order score first
- func (r *Request) OrderScoreFirst(v bool) *Request {
- r.q.OrderScoreFirst = v
- return r
- }
- // OrderRandomSeed switch for order random
- func (r *Request) OrderRandomSeed(v string) *Request {
- r.q.OrderRandomSeed = v
- return r
- }
- // Highlight switch from highlight
- func (r *Request) Highlight(v bool) *Request {
- r.q.Highlight = v
- return r
- }
- // Pn page number
- func (r *Request) Pn(v int) *Request {
- r.q.Pn = v
- return r
- }
- // Ps page size
- func (r *Request) Ps(v int) *Request {
- r.q.Ps = v
- return r
- }
- // Order filed sort
- func (r *Request) Order(field, sort string) *Request {
- if sort != OrderAsc {
- sort = OrderDesc
- }
- r.q.Order = append(r.q.Order, map[string]string{field: sort})
- return r
- }
- // WhereEq where qual
- func (r *Request) WhereEq(field string, eq interface{}) *Request {
- if r.q.Where.Eq == nil {
- r.q.Where.Eq = make(map[string]interface{})
- }
- r.q.Where.Eq[field] = eq
- return r
- }
- // WhereOr where or
- func (r *Request) WhereOr(field string, or interface{}) *Request {
- if r.q.Where.Or == nil {
- r.q.Where.Or = make(map[string]interface{})
- }
- r.q.Where.Or[field] = or
- return r
- }
- // WhereIn where in
- func (r *Request) WhereIn(field string, in interface{}) *Request {
- if r.q.Where.In == nil {
- r.q.Where.In = make(map[string][]interface{})
- }
- switch v := in.(type) {
- case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, string:
- r.q.Where.In[field] = append(r.q.Where.In[field], v)
- case []int:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []int64:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []string:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []int8:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []int16:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []int32:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []uint:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []uint8:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []uint16:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []uint32:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- case []uint64:
- for _, i := range v {
- r.q.Where.In[field] = append(r.q.Where.In[field], i)
- }
- }
- return r
- }
- // WhereRange where range
- func (r *Request) WhereRange(field string, start, end interface{}, scope rangeScope) *Request {
- if r.q.Where.Range == nil {
- r.q.Where.Range = make(map[string]string)
- }
- if start == nil {
- start = ""
- }
- if end == nil {
- end = ""
- }
- switch scope {
- case RangeScopeLoRo:
- r.q.Where.Range[field] = fmt.Sprintf("(%v,%v)", start, end)
- case RangeScopeLoRc:
- r.q.Where.Range[field] = fmt.Sprintf("(%v,%v]", start, end)
- case RangeScopeLcRo:
- r.q.Where.Range[field] = fmt.Sprintf("[%v,%v)", start, end)
- case RangeScopeLcRc:
- r.q.Where.Range[field] = fmt.Sprintf("[%v,%v]", start, end)
- }
- return r
- }
- // WhereNot where not
- func (r *Request) WhereNot(typ notType, fields ...string) *Request {
- if r.q.Where.Not == nil {
- r.q.Where.Not = make(map[notType]map[string]bool)
- }
- if r.q.Where.Not[typ] == nil {
- r.q.Where.Not[typ] = make(map[string]bool)
- }
- for _, v := range fields {
- r.q.Where.Not[typ][v] = true
- }
- return r
- }
- // WhereLike where like
- func (r *Request) WhereLike(fields, words []string, or bool, level likeLevel) *Request {
- if len(fields) == 0 || len(words) == 0 {
- return r
- }
- l := whereLike{Fields: fields, Words: words, Or: or, Level: level}
- r.q.Where.Like = append(r.q.Where.Like, l)
- return r
- }
- // WhereCombo where combo
- func (r *Request) WhereCombo(cmb ...*Combo) *Request {
- r.q.Where.Combo = append(r.q.Where.Combo, cmb...)
- return r
- }
- // GroupBy where group by
- func (r *Request) GroupBy(mode enhancedType, field string, order []map[string]string) *Request {
- for _, i := range order {
- for k, v := range i {
- if v != OrderAsc {
- i[k] = OrderDesc
- }
- }
- }
- r.q.Where.Enhanced = append(r.q.Where.Enhanced, groupBy{Mode: mode, Field: field, Order: order})
- return r
- }
- // Sum where enhance sum
- func (r *Request) Sum(field string) *Request {
- r.q.Where.Enhanced = append(r.q.Where.Enhanced, enhance{Mode: EnhancedModeSum, Field: field})
- return r
- }
- // Scan parse the query response data
- func (r *Request) Scan(ctx context.Context, result interface{}) (err error) {
- q, err := r.q.string()
- if err != nil {
- return
- }
- params := url.Values{}
- params.Add("business", r.business)
- params.Add("query", q)
- response := new(response)
- if err = r.client.Get(ctx, r.c.Host+_pathQuery, "", params, &response); err != nil {
- return
- }
- if !ecode.Int(response.Code).Equal(ecode.OK) {
- return ecode.Int(response.Code)
- }
- err = errors.Wrapf(json.Unmarshal(response.Data, &result), "scan(%s)", response.Data)
- return
- }
- // Params get query parameters
- func (r *Request) Params() string {
- q, _ := r.q.string()
- return fmt.Sprintf("business=%s&query=%s", r.business, q)
- }
|