You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
4.9 KiB
209 lines
4.9 KiB
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 |
|
}
|
|
|