go-common/app/admin/main/coupon/service/activity.go
2019-04-22 18:49:16 +08:00

274 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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\"}",
},
}
)