274 lines
7.0 KiB
Go
274 lines
7.0 KiB
Go
|
package service
|
|||
|
|
|||
|
import (
|
|||
|
"bufio"
|
|||
|
"bytes"
|
|||
|
"context"
|
|||
|
"encoding/csv"
|
|||
|
"fmt"
|
|||
|
"io/ioutil"
|
|||
|
"os"
|
|||
|
"strconv"
|
|||
|
"strings"
|
|||
|
"time"
|
|||
|
|
|||
|
"go-common/app/admin/main/coupon/model"
|
|||
|
"go-common/library/database/sql"
|
|||
|
"go-common/library/ecode"
|
|||
|
"go-common/library/log"
|
|||
|
"go-common/library/net/metadata"
|
|||
|
xtime "go-common/library/time"
|
|||
|
"go-common/library/xstr"
|
|||
|
|
|||
|
"github.com/pkg/errors"
|
|||
|
)
|
|||
|
|
|||
|
// ActivitySalaryCoupon activity salary coupon.
|
|||
|
func (s *Service) ActivitySalaryCoupon(c context.Context, req *model.ArgBatchSalaryCoupon) (err error) {
|
|||
|
var (
|
|||
|
ok bool
|
|||
|
r *model.CouponBatchInfo
|
|||
|
)
|
|||
|
if r, err = s.dao.BatchInfo(c, req.BranchToken); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if r == nil {
|
|||
|
return ecode.CouPonBatchNotExistErr
|
|||
|
}
|
|||
|
if r.State != model.BatchStateNormal {
|
|||
|
return ecode.CouPonHadBlockErr
|
|||
|
}
|
|||
|
if r.ExpireDay != -1 {
|
|||
|
return ecode.CouponTypeNotSupportErr
|
|||
|
}
|
|||
|
if ok = s.dao.AddGrantUniqueLock(c, req.BranchToken, _lockseconds); !ok {
|
|||
|
return ecode.CouponSalaryHadRunErr
|
|||
|
}
|
|||
|
// mids index
|
|||
|
s.runSalary(metadata.String(c, metadata.RemoteIP), r, req)
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// runSalary run batch salary.
|
|||
|
func (s *Service) runSalary(ip string, b *model.CouponBatchInfo, req *model.ArgBatchSalaryCoupon) (err error) {
|
|||
|
go func() {
|
|||
|
var (
|
|||
|
err error
|
|||
|
fails []int64
|
|||
|
tmp []int64
|
|||
|
mids = []int64{}
|
|||
|
total int64
|
|||
|
midmap map[int64][]int64
|
|||
|
c = context.Background()
|
|||
|
)
|
|||
|
defer func() {
|
|||
|
if x := recover(); x != nil {
|
|||
|
x = errors.WithStack(x.(error))
|
|||
|
log.Error("runtime error caught: %+v", x)
|
|||
|
}
|
|||
|
//Write fail mids
|
|||
|
if len(fails) > 0 {
|
|||
|
s.OutFile(c, []byte(xstr.JoinInts(fails)), req.BranchToken+"_failmids.csv")
|
|||
|
}
|
|||
|
}()
|
|||
|
log.Info("batch salary analysis slary file[req(%v)]", req)
|
|||
|
// analysis slary file
|
|||
|
if midmap, total, err = s.AnalysisFile(c, req.FileURL); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if total != req.Count {
|
|||
|
log.Error("[batch salary count err req(%v),filetotal(%d)]", req, total)
|
|||
|
s.dao.DelGrantUniqueLock(c, b.BatchToken)
|
|||
|
return
|
|||
|
}
|
|||
|
log.Info("batch salary start[count(%d),token(%s)]", total, req.BranchToken)
|
|||
|
var crrent int
|
|||
|
for _, allmids := range midmap {
|
|||
|
for i, v := range allmids {
|
|||
|
mids = append(mids, v)
|
|||
|
if len(mids)%req.SliceSize != 0 && i != len(allmids)-1 {
|
|||
|
continue
|
|||
|
}
|
|||
|
crrent = crrent + len(mids)
|
|||
|
log.Info("batch salary ing[mid(%d),i(%d),token(%s),crrent(%d)]", v, i, req.BranchToken, crrent)
|
|||
|
if tmp, err = s.batchSalary(c, mids, ip, b); err != nil && len(tmp) > 0 {
|
|||
|
fails = append(fails, tmp...)
|
|||
|
log.Error("batch salary err[mids(%v),i(%d),token(%s)] err(%v)", mids, i, req.BranchToken, err)
|
|||
|
}
|
|||
|
mids = []int64{}
|
|||
|
}
|
|||
|
}
|
|||
|
log.Info("batch salary end[count(%d),token(%s),faillen(%d)]", total, req.BranchToken, len(fails))
|
|||
|
}()
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// batchSalary batch salary.
|
|||
|
func (s *Service) batchSalary(c context.Context, mids []int64, ip string, b *model.CouponBatchInfo) (falls []int64, err error) {
|
|||
|
var (
|
|||
|
tx *sql.Tx
|
|||
|
aff int64
|
|||
|
)
|
|||
|
if tx, err = s.dao.BeginTran(c); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
defer func() {
|
|||
|
if err != nil {
|
|||
|
falls = mids
|
|||
|
if err1 := tx.Rollback(); err1 != nil {
|
|||
|
log.Error("batch salary rollback: %+v", err1)
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
if err = tx.Commit(); err != nil {
|
|||
|
falls = mids
|
|||
|
log.Error("batch salary commit: %+v", err)
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
//del cache
|
|||
|
s.cache.Do(c, func(c context.Context) {
|
|||
|
for _, v := range mids {
|
|||
|
if err = s.dao.DelCouponAllowancesKey(context.Background(), v, model.NotUsed); err != nil {
|
|||
|
log.Error("batch salary cache del(%d) err: %+v", v, err)
|
|||
|
}
|
|||
|
}
|
|||
|
})
|
|||
|
//send msg
|
|||
|
if s.c.Prop.SalaryMsgOpen {
|
|||
|
s.sendMsg(mids, ip, 1, true, _msgMeng)
|
|||
|
}
|
|||
|
time.Sleep(time.Duration(s.c.Prop.SalarySleepTime))
|
|||
|
}()
|
|||
|
cps := make([]*model.CouponAllowanceInfo, len(mids))
|
|||
|
for i, v := range mids {
|
|||
|
cps[i] = &model.CouponAllowanceInfo{
|
|||
|
CouponToken: s.tokeni(i),
|
|||
|
Mid: v,
|
|||
|
State: model.NotUsed,
|
|||
|
StartTime: b.StartTime,
|
|||
|
ExpireTime: b.ExpireTime,
|
|||
|
Origin: model.AllowanceSystemAdmin,
|
|||
|
CTime: xtime.Time(time.Now().Unix()),
|
|||
|
BatchToken: b.BatchToken,
|
|||
|
Amount: b.Amount,
|
|||
|
FullAmount: b.FullAmount,
|
|||
|
AppID: b.AppID,
|
|||
|
}
|
|||
|
}
|
|||
|
// mid should be same index.
|
|||
|
if aff, err = s.dao.BatchAddAllowanceCoupon(c, tx, cps); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if len(mids) != int(aff) {
|
|||
|
err = fmt.Errorf("batch salary midslen(%d) != aff(%d)", len(mids), aff)
|
|||
|
return
|
|||
|
}
|
|||
|
if _, err = s.dao.UpdateBatchInfo(c, tx, b.BatchToken, len(mids)); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// AnalysisFile analysis slary file.
|
|||
|
func (s *Service) AnalysisFile(c context.Context, fileURL string) (midmap map[int64][]int64, total int64, err error) {
|
|||
|
cntb, err := ioutil.ReadFile(fileURL)
|
|||
|
if err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
var (
|
|||
|
mid int64
|
|||
|
records [][]string
|
|||
|
)
|
|||
|
r := csv.NewReader(strings.NewReader(string(cntb)))
|
|||
|
records, err = r.ReadAll()
|
|||
|
if err != nil {
|
|||
|
err = errors.WithStack(err)
|
|||
|
return
|
|||
|
}
|
|||
|
midmap = make(map[int64][]int64, s.c.Prop.AllowanceTableCount)
|
|||
|
for i, v := range records {
|
|||
|
if len(v) <= 0 {
|
|||
|
continue
|
|||
|
}
|
|||
|
if mid, err = strconv.ParseInt(v[0], 10, 64); err != nil {
|
|||
|
err = errors.Wrapf(err, "read csv line err(%d,%v)", i, v)
|
|||
|
break
|
|||
|
}
|
|||
|
index := mid % s.c.Prop.AllowanceTableCount
|
|||
|
if midmap[index] == nil {
|
|||
|
midmap[index] = []int64{}
|
|||
|
}
|
|||
|
midmap[index] = append(midmap[index], mid)
|
|||
|
total++
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// OutFile out file.
|
|||
|
func (s *Service) OutFile(c context.Context, bs []byte, url string) (err error) {
|
|||
|
var outFile *os.File
|
|||
|
outFile, err = os.Create(url)
|
|||
|
if err != nil {
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
defer outFile.Close()
|
|||
|
b := bufio.NewWriter(outFile)
|
|||
|
_, err = b.Write(bs)
|
|||
|
if err != nil {
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
err = b.Flush()
|
|||
|
if err != nil {
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// get coupon tokeni
|
|||
|
func (s *Service) tokeni(i int) string {
|
|||
|
var b bytes.Buffer
|
|||
|
b.WriteString(fmt.Sprintf("%05d", i))
|
|||
|
b.WriteString(fmt.Sprintf("%02d", s.r.Int63n(99)))
|
|||
|
b.WriteString(fmt.Sprintf("%03d", time.Now().UnixNano()/1e6%1000))
|
|||
|
b.WriteString(time.Now().Format("20060102150405"))
|
|||
|
return b.String()
|
|||
|
}
|
|||
|
|
|||
|
func (s *Service) sendMsg(mids []int64, ip string, times int, b bool, msgType string) (err error) {
|
|||
|
msg, ok := msgMap[msgType]
|
|||
|
if !ok {
|
|||
|
log.Warn("sendMsg not support msgType(%s)", msgType)
|
|||
|
return
|
|||
|
}
|
|||
|
for i := 1; i <= times; i++ {
|
|||
|
if err = s.dao.SendMessage(context.Background(), xstr.JoinInts(mids), msg["title"], msg["content"], ip); err != nil {
|
|||
|
if i != times {
|
|||
|
continue
|
|||
|
}
|
|||
|
if b {
|
|||
|
log.Error("batch salary msg send mids(%v) err: %+v", mids, err)
|
|||
|
}
|
|||
|
}
|
|||
|
break
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
const (
|
|||
|
_msgMeng = "meng"
|
|||
|
)
|
|||
|
|
|||
|
var (
|
|||
|
msgMap = map[string]map[string]string{
|
|||
|
"vip": {
|
|||
|
"title": "大会员代金券到账通知",
|
|||
|
"content": "大会员代金券已到账,快到“我的代金券”看看吧!IOS端需要在网页使用。#{\"传送门→\"}{\"https://account.bilibili.com/account/big/voucher\"}",
|
|||
|
},
|
|||
|
"meng": {
|
|||
|
"title": "大会员代金券到账提醒",
|
|||
|
"content": "您的专属大会员代金券即将在10月10日0点到账,年费半价基础上再享折上折,请注意查收!代金券有效期1天,暂不支持苹果支付 #{\"点击查看详情\"}{\"https://www.bilibili.com/read/cv1291213\"}",
|
|||
|
},
|
|||
|
}
|
|||
|
)
|