123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- package service
- import (
- "context"
- "crypto/md5"
- "encoding/hex"
- "fmt"
- "math/rand"
- "net"
- "sort"
- "sync"
- "time"
- "go-common/app/admin/main/usersuit/model"
- accmdl "go-common/app/service/main/account/api"
- "go-common/library/ecode"
- "go-common/library/log"
- "go-common/library/net/metadata"
- xtime "go-common/library/time"
- "go-common/library/sync/errgroup"
- )
- const (
- _geneMaxLimit = int64(1000)
- _geneSubCount = 200
- _batch = 50
- _fetchInfoTimeout = time.Second * 5
- )
- var (
- _emptyRichInvites = make([]*model.RichInvite, 0)
- _emptyInfoMap = make(map[int64]*accmdl.Info)
- )
- // Generate generate invite codes in batch.
- func (s *Service) Generate(c context.Context, mid, num, expireDay int64) (res []*model.RichInvite, err error) {
- if num > _geneMaxLimit {
- err = ecode.UsersuitInviteReachMaxGeneLimit
- return
- }
- expireSeconds := expireDay * 86400
- nowTs := time.Now().Unix()
- cm, err1 := concurrentGenerateCode(mid, nowTs, int(num), _geneSubCount)
- if err1 != nil {
- log.Error("concurrentGenerateCode(%d, %d, %d, %d) error(%v)", mid, nowTs, num, _geneSubCount, err)
- }
- ginvs := make([]*model.Invite, 0, num)
- buyIP := net.ParseIP(metadata.String(c, metadata.RemoteIP))
- for code := range cm {
- ginvs = append(ginvs, &model.Invite{
- Mid: mid,
- Code: code,
- IP: IPv4toN(buyIP),
- IPng: buyIP,
- Expires: nowTs + expireSeconds,
- Ctime: xtime.Time(nowTs),
- })
- }
- invs := make([]*model.Invite, 0)
- var rc int64
- for _, inv := range ginvs {
- if rc, err = s.d.AddIgnoreInvite(c, inv); err != nil {
- err = nil
- break
- }
- if rc == 0 {
- log.Error("service.dao.AddIgnoreInvite(%s), duplicate entry for invite_code %s", inv.Code, inv.Code)
- continue
- }
- invs = append(invs, inv)
- }
- res = s.fillStatusAndInviteeInfo(c, invs)
- return
- }
- func concurrentGenerateCode(mid, ts int64, num, subCount int) (res map[string]int, err error) {
- batches := num / subCount
- eg, _ := errgroup.WithContext(context.TODO())
- ims := make([]map[string]int, batches)
- mu := sync.Mutex{}
- for i := 0; i < batches; i++ {
- idx := i
- eg.Go(func() error {
- im := make(map[string]int)
- for len(im) < subCount {
- im[geneInviteCode(mid, ts)] = 1
- }
- mu.Lock()
- ims[idx] = im
- mu.Unlock()
- return nil
- })
- }
- err = eg.Wait()
- m := make(map[string]int)
- for _, im := range ims {
- for code := range im {
- m[code] = 1
- }
- }
- for len(m) < num {
- m[geneInviteCode(mid, ts)] = 1
- }
- res = m
- return
- }
- func geneInviteCode(mid int64, ts int64) string {
- data := md5.Sum([]byte(fmt.Sprintf("%d,%d,%d", ts, mid, rand.Int63())))
- h := hex.EncodeToString(data[:])
- return h[8:24]
- }
- // List list one's invite codes range time start and end.
- func (s *Service) List(c context.Context, mid, start, end int64) (res []*model.RichInvite, err error) {
- if start > end {
- res = _emptyRichInvites
- return
- }
- var invs []*model.Invite
- if invs, err = s.d.RangeInvites(c, mid, time.Unix(start, 0), time.Unix(end, 0)); err != nil {
- return
- }
- sort.Slice(invs, func(i, j int) bool {
- return int64(invs[i].Ctime) > int64(invs[j].Ctime)
- })
- res = s.fillStatusAndInviteeInfo(c, invs)
- return
- }
- func (s *Service) fillStatusAndInviteeInfo(c context.Context, invs []*model.Invite) []*model.RichInvite {
- if len(invs) == 0 {
- return _emptyRichInvites
- }
- imidm := make(map[int64]struct{})
- now := time.Now().Unix()
- for _, inv := range invs {
- inv.FillStatus(now)
- if inv.Status == model.StatusUsed {
- imidm[inv.Imid] = struct{}{}
- }
- }
- infom := _emptyInfoMap
- if len(imidm) > 0 {
- imids := make([]int64, 0, len(imidm))
- for imid := range imidm {
- imids = append(imids, imid)
- }
- var err1 error
- if infom, err1 = s.fetchInfos(c, imids, _fetchInfoTimeout); err1 != nil {
- log.Error("service.fetchInfos(%v, %s) error(%v)", imids, _fetchInfoTimeout, err1)
- }
- }
- rinvs := make([]*model.RichInvite, 0)
- for _, inv := range invs {
- rinvs = append(rinvs, model.NewRichInvite(inv, infom[inv.Imid]))
- }
- return rinvs
- }
- func (s *Service) fetchInfos(c context.Context, mids []int64, timeout time.Duration) (res map[int64]*accmdl.Info, err error) {
- if len(mids) == 0 {
- res = _emptyInfoMap
- return
- }
- batches := len(mids)/_batch + 1
- tc, cancel := context.WithTimeout(c, timeout)
- defer cancel()
- eg, errCtx := errgroup.WithContext(tc)
- bms := make([]map[int64]*accmdl.Info, batches)
- mu := sync.Mutex{}
- for i := 0; i < batches; i++ {
- idx := i
- end := (idx + 1) * _batch
- if idx == batches-1 {
- end = len(mids)
- }
- ids := mids[idx*_batch : end]
- eg.Go(func() error {
- m, err1 := s.accountClient.Infos3(errCtx, &accmdl.MidsReq{Mids: ids})
- mu.Lock()
- bms[idx] = m.Infos
- mu.Unlock()
- return err1
- })
- }
- err = eg.Wait()
- res = make(map[int64]*accmdl.Info)
- for _, bm := range bms {
- for mid, info := range bm {
- res[mid] = info
- }
- }
- return
- }
- // IPv4toN is
- func IPv4toN(ip net.IP) (sum uint32) {
- v4 := ip.To4()
- if v4 == nil {
- return
- }
- sum += uint32(v4[0]) << 24
- sum += uint32(v4[1]) << 16
- sum += uint32(v4[2]) << 8
- sum += uint32(v4[3])
- return sum
- }
|