210 lines
4.9 KiB
Go
210 lines
4.9 KiB
Go
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
|
|
}
|