Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"activity.go",
"allowance.go",
"code.go",
"coupon.go",
"service.go",
"view.go",
],
importpath = "go-common/app/admin/main/coupon/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/coupon/conf:go_default_library",
"//app/admin/main/coupon/dao:go_default_library",
"//app/admin/main/coupon/model:go_default_library",
"//app/service/main/coupon/model:go_default_library",
"//app/service/main/coupon/rpc/client:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"activity_test.go",
"allowance_test.go",
"code_test.go",
"coupon_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/coupon/conf:go_default_library",
"//app/admin/main/coupon/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,273 @@
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\"}",
},
}
)

View File

@@ -0,0 +1,59 @@
package service
import (
"context"
"testing"
"go-common/app/admin/main/coupon/model"
. "github.com/smartystreets/goconvey/convey"
)
// go test -test.v -test.run TestAnalysisFile
func TestAnalysisFile(t *testing.T) {
Convey("TestAnalysisFile ", t, func() {
res, total, err := s.AnalysisFile(c, "/data/lv4.csv")
t.Logf("res(%v) total(%d)", res, total)
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestTokeni
func TestTokeni(t *testing.T) {
Convey("TestTokeni ", t, func() {
token := s.tokeni(100)
t.Logf("token(%s)", token)
So(token, ShouldNotBeBlank)
})
}
// go test -test.v -test.run TestOutFile
func TestOutFile(t *testing.T) {
Convey("TestOutFile ", t, func() {
err := s.OutFile(context.Background(), []byte("haha"), "/data/test.csv")
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestBatchSalary
func TestBatchSalary(t *testing.T) {
Convey("TestbatchSalary ", t, func() {
r, err := s.dao.BatchInfo(c, "allowance_lv41-4")
So(err, ShouldBeNil)
_, err = s.batchSalary(context.Background(), []int64{1, 2, 3}, "127.0.0.1", r)
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestActivitySalaryCoupon
func TestActivitySalaryCoupon(t *testing.T) {
Convey("TestActivitySalaryCoupon ", t, func() {
err := s.ActivitySalaryCoupon(c, &model.ArgBatchSalaryCoupon{
FileURL: "/data/1.csv",
Count: 1,
BranchToken: "allowance_lv41-4",
SliceSize: 1000,
})
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,270 @@
package service
import (
"context"
"encoding/csv"
"fmt"
"mime/multipart"
"strconv"
"strings"
"time"
"go-common/app/admin/main/coupon/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
"github.com/pkg/errors"
)
// AddAllowanceBatchInfo add allowance batch info.
func (s *Service) AddAllowanceBatchInfo(c context.Context, b *model.CouponBatchInfo) (token string, err error) {
if b.ExpireDay < 0 && (b.StartTime <= 0 && b.ExpireTime <= 0) {
err = ecode.CouponExpireErr
return
}
if b.ExpireDay < 0 && b.StartTime >= b.ExpireTime {
err = ecode.CouPonBatchTimeErr
return
}
if b.Amount >= b.FullAmount {
err = ecode.CouponAmountErr
return
}
b.BatchToken = s.token()
b.Ctime = xtime.Time(time.Now().Unix())
if _, err = s.dao.AddAllowanceBatchInfo(c, b); err != nil {
err = errors.WithStack(err)
}
token = b.BatchToken
return
}
//UpdateAllowanceBatchInfo update allowance batch info.
func (s *Service) UpdateAllowanceBatchInfo(c context.Context, b *model.CouponBatchInfo) (err error) {
if _, err = s.dao.UpdateAllowanceBatchInfo(c, b); err != nil {
err = errors.WithStack(err)
}
return
}
//UpdateCodeBatchInfo update code batch info.
func (s *Service) UpdateCodeBatchInfo(c context.Context, b *model.CouponBatchInfo) (err error) {
var data *model.CouponBatchInfo
if data, err = s.BatchInfoByID(c, b.ID); err != nil {
return
}
if data == nil || batchState(data) != model.CodeBatchUsable {
return ecode.RequestErr
}
if _, err = s.dao.UpdateCodeBatchInfo(c, b); err != nil {
err = errors.WithStack(err)
}
return
}
//UpdateBatchStatus update batch status.
func (s *Service) UpdateBatchStatus(c context.Context, status int8, operator string, id int64) (err error) {
var data *model.CouponBatchInfo
if data, err = s.BatchInfoByID(c, id); err != nil {
return
}
if data == nil ||
(status == model.CodeBatchBlock && batchState(data) != model.CodeBatchUsable) ||
(status == model.CodeBatchUsable && batchState(data) != model.CodeBatchBlock) {
return ecode.RequestErr
}
if _, err = s.dao.UpdateBatchStatus(c, status, operator, id); err != nil {
err = errors.WithStack(err)
}
return
}
//BatchInfo allowance batch info.
func (s *Service) BatchInfo(c context.Context, token string) (res *model.CouponBatchInfo, err error) {
if res, err = s.dao.BatchInfo(c, token); err != nil {
err = errors.WithStack(err)
}
return
}
//BatchInfoByID allowance batch info.
func (s *Service) BatchInfoByID(c context.Context, id int64) (*model.CouponBatchInfo, error) {
return s.dao.BatchInfoByID(c, id)
}
// AllowanceSalary allowance salary.
func (s *Service) AllowanceSalary(c context.Context, f multipart.File, h *multipart.FileHeader, mids []int64, token, msgType string) (count int, err error) {
var bi *model.CouponBatchInfo
if len(mids) == 0 {
if mids, err = s.ReadCsv(f, h); err != nil {
err = errors.WithStack(err)
return
}
}
if len(mids) == 0 {
err = ecode.CouponBatchSalaryCountZeroErr
return
}
if len(mids) > _maxSalaryCount {
err = ecode.CouponBatchSalaryLimitErr
return
}
if bi, err = s.dao.BatchInfo(c, token); err != nil {
err = errors.WithStack(err)
return
}
if bi == nil {
err = ecode.CouPonTokenNotFoundErr
return
}
if bi.State != model.BatchStateNormal {
err = ecode.CouPonHadBlockErr
return
}
if bi.ExpireDay < 0 && bi.ExpireTime < time.Now().Unix() {
err = ecode.CouponBatchExpireTimeErr
return
}
if bi.MaxCount != _notLimitSalary && len(mids) > int(bi.MaxCount-bi.CurrentCount) {
err = ecode.CouponBatchSalaryLimitErr
return
}
s.RunSalaryCoupon(c, mids, token, bi.AppID, model.CouponAllowance, model.AdminSalaryOrigin, msgType)
count = len(mids)
return
}
// ReadCsv read csv file
func (s *Service) ReadCsv(f multipart.File, h *multipart.FileHeader) (mids []int64, err error) {
var (
mid int64
records [][]string
)
mids = []int64{}
defer f.Close()
if h != nil && !strings.HasSuffix(h.Filename, ".csv") {
err = ecode.CouponUpdateFileErr
return
}
r := csv.NewReader(f)
records, err = r.ReadAll()
if err != nil {
err = errors.WithStack(err)
return
}
for _, v := range records {
if len(v) <= 0 {
continue
}
if mid, err = strconv.ParseInt(v[0], 10, 64); err != nil {
err = errors.WithStack(err)
break
}
mids = append(mids, mid)
}
return
}
// //AllowancePage allowance page.
// func (s *Service) AllowancePage(c context.Context, arg *model.ArgAllowanceSearch) (res *model.PageCouponInfo, err error) {
// var page *model.SearchData
// res = &model.PageCouponInfo{}
// if page, err = s.dao.AllowancePage(c, arg); err != nil {
// err = errors.WithStack(err)
// return
// }
// if page != nil && page.Data != nil && page.Data.Page != nil {
// res.Count = page.Data.Page.Total
// res.Item = page.Data.Result
// }
// return
// }
//AllowanceList allowance list.
func (s *Service) AllowanceList(c context.Context, arg *model.ArgAllowanceSearch) (res []*model.CouponAllowanceInfo, err error) {
if res, err = s.dao.AllowanceList(c, arg); err != nil {
err = errors.WithStack(err)
}
for _, v := range res {
if v.State == model.NotUsed && v.ExpireTime < time.Now().Unix() {
v.State = model.Expire
}
}
return
}
//UpdateAllowanceState update allowance state.
func (s *Service) UpdateAllowanceState(c context.Context, mid int64, state int8, token string) (err error) {
var (
cp *model.CouponAllowanceInfo
changeType int8
)
if cp, err = s.dao.AllowanceByToken(c, mid, token); err != nil {
err = errors.WithStack(err)
return
}
if cp == nil {
err = ecode.CouPonTokenNotFoundErr
return
}
if state == model.Block {
changeType = model.AllowanceBlock
} else {
changeType = model.AllowanceUnBlock
}
if err = s.UpdateAllowanceCoupon(c, mid, state, token, cp.Ver, changeType, cp); err != nil {
err = errors.WithStack(err)
}
return
}
// UpdateAllowanceCoupon update coupon info.
func (s *Service) UpdateAllowanceCoupon(c context.Context, mid int64, state int8, token string, ver int64, changeType int8, cp *model.CouponAllowanceInfo) (err error) {
var (
tx *sql.Tx
aff int64
)
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("%+v", err)
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback %+v", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit %+v", err)
}
}()
if aff, err = s.dao.UpdateAllowanceStatus(c, tx, state, mid, token, ver); err != nil {
err = errors.WithStack(err)
}
if aff != 1 {
err = fmt.Errorf("coupon update faild")
return
}
l := &model.CouponAllowanceChangeLog{
CouponToken: cp.CouponToken,
Mid: cp.Mid,
State: state,
Ctime: xtime.Time(time.Now().Unix()),
OrderNO: cp.OrderNO,
ChangeType: changeType,
}
if aff, err = s.dao.InsertCouponAllowanceHistory(c, tx, l); err != nil {
err = errors.WithStack(err)
return
}
if aff != 1 {
err = fmt.Errorf("add change log faild")
return
}
s.dao.DelCouponAllowancesKey(c, cp.Mid, model.NotUsed)
s.dao.DelCouponAllowancesKey(c, cp.Mid, model.InUse)
return
}

View File

@@ -0,0 +1,107 @@
package service
import (
"go-common/app/admin/main/coupon/model"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
// go test -test.v -test.run TestAddAllowanceBatchInfo
func TestAddAllowanceBatchInfo(t *testing.T) {
Convey("TestAddAllowanceBatchInfo ", t, func() {
var err error
b := &model.CouponBatchInfo{
AppID: 1,
Name: "test1",
MaxCount: 1000,
CurrentCount: 1000,
StartTime: 1532057501,
ExpireTime: 1542057501,
Ver: 1,
Operator: "yubaihai",
LimitCount: 20,
FullAmount: 100,
Amount: 20,
State: 0,
CouponType: 3,
ExpireDay: 7,
PlatformLimit: "3,4",
}
_, err = s.AddAllowanceBatchInfo(c, b)
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestUpdateAllowanceBatchInfo
func TestUpdateAllowanceBatchInfo(t *testing.T) {
Convey("TestUpdateAllowanceBatchInfo ", t, func() {
var err error
b := &model.CouponBatchInfo{
ID: 2,
AppID: 1,
Name: "test2",
MaxCount: 10000,
CurrentCount: 1000,
StartTime: 1532057501,
ExpireTime: 1542057501,
Ver: 1,
Operator: "yubaihai",
LimitCount: 200,
FullAmount: 100,
Amount: 20,
State: 0,
CouponType: 3,
PlatformLimit: "3",
}
err = s.UpdateAllowanceBatchInfo(c, b)
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestUpdateBatchStatus
func TestUpdateBatchStatus(t *testing.T) {
Convey("TestUpdateBatchStatus ", t, func() {
So(s.UpdateBatchStatus(c, model.BatchStateNormal, "yubaihai", 150), ShouldBeNil)
})
}
// go test -test.v -test.run TestBatchInfo
func TestBatchInfo(t *testing.T) {
Convey("TestBatchInfo ", t, func() {
res, err := s.BatchInfo(c, "test2")
t.Logf("res(%v)", res)
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestAllowanceSalary
func TestAllowanceSalary(t *testing.T) {
Convey("TestAllowanceSalary ", t, func() {
count, err := s.AllowanceSalary(c, nil, nil, []int64{332}, "allowance_test1", "vip")
time.Sleep(time.Second * 1)
t.Logf("count(%v)", count)
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestUpdateAllowanceState
func TestUpdateAllowanceState(t *testing.T) {
Convey("TestUpdateAllowanceState ", t, func() {
err := s.UpdateAllowanceState(c, 1, model.NotUsed, "097060140820180713120943")
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestAllowanceList
func TestAllowanceList(t *testing.T) {
Convey("TestAllowanceList", t, func() {
res, err := s.AllowanceList(c, &model.ArgAllowanceSearch{
Mid: 1,
AppID: 0,
})
t.Logf("count(%v)", len(res))
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,137 @@
package service
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"math/rand"
"time"
"go-common/app/admin/main/coupon/model"
"go-common/library/ecode"
"go-common/library/log"
)
// CodePage code page.
func (s *Service) CodePage(c context.Context, a *model.ArgCouponCode) (res *model.CodePage, err error) {
res = new(model.CodePage)
var (
count int64
b *model.CouponBatchInfo
now = time.Now().Unix()
)
if count, err = s.dao.CountCode(c, a); err != nil {
return
}
if count <= 0 {
return
}
if res.CodeList, err = s.dao.CodeList(c, a); err != nil {
return
}
for _, v := range res.CodeList {
if b, err = s.BatchInfo(c, v.BatchToken); err != nil {
return
}
if b.ExpireDay == -1 && b.ExpireTime <= now {
v.State = model.CodeStateExpire
}
}
return
}
// CodeBlock code block.
func (s *Service) CodeBlock(c context.Context, a *model.ArgCouponCode) (err error) {
var code *model.CouponCode
if code, err = s.dao.CodeByID(c, a.ID); err != nil {
return
}
if code == nil || code.State != model.CodeStateNotUse {
return ecode.RequestErr
}
return s.dao.UpdateCodeBlock(c, &model.CouponCode{ID: a.ID, State: model.CodeStateBlock, Ver: code.Ver})
}
// CodeUnBlock code un block.
func (s *Service) CodeUnBlock(c context.Context, a *model.ArgCouponCode) (err error) {
var code *model.CouponCode
if code, err = s.dao.CodeByID(c, a.ID); err != nil {
return
}
if code == nil || code.State != model.CodeStateBlock {
return ecode.RequestErr
}
return s.dao.UpdateCodeBlock(c, &model.CouponCode{ID: a.ID, State: model.CodeStateNotUse, Ver: code.Ver})
}
// ExportCode export code.
func (s *Service) ExportCode(c context.Context, a *model.ArgCouponCode) (res []string, err error) {
if a.BatchToken == "" {
return nil, ecode.RequestErr
}
var cs []*model.CouponCode
a.Ps = 1000
a.Pn = 1
for {
if cs, err = s.dao.CodeList(context.Background(), a); err != nil {
return
}
if len(cs) == 0 {
break
}
for _, v := range cs {
res = append(res, v.Code)
}
a.Pn++
}
return
}
// InitCodes init codes
func (s *Service) InitCodes(c context.Context, token string) (err error) {
var info *model.CouponBatchInfo
if info, err = s.dao.BatchInfo(c, token); err != nil {
return
}
if info == nil {
return
}
if info.MaxCount == 0 || info.MaxCount > model.BatchCodeMaxCount {
return
}
// init code.
go func() {
log.Info("init code start arg[%s,%d]", token, info.MaxCount)
cs := []*model.CouponCode{}
for i := int64(0); i < info.MaxCount; i++ {
cs = append(cs, &model.CouponCode{
BatchToken: token,
State: model.CodeStateNotUse,
Code: codeToken(i),
CouponType: model.CouponAllowance,
})
if len(cs) == int(info.MaxCount) || len(cs)%model.BatchAddCodeSlice == 0 {
if err = s.dao.BatchAddCode(context.Background(), cs); err != nil {
log.Error("init code error[%s,%v]", token, err)
return
}
log.Info("init code ing arg[%s,%d,%d]", token, info.MaxCount, i)
time.Sleep(50 * time.Millisecond)
cs = []*model.CouponCode{}
}
}
log.Info("init code end arg[%s,%d]", token, info.MaxCount)
}()
return
}
func codeToken(i int64) string {
hash := md5.New()
unix := time.Now().UnixNano()
key := fmt.Sprintf("%v,%v,%v", unix, i, rand.Intn(100000000))
hash.Write([]byte(key))
sum := hash.Sum(nil)
code := hex.EncodeToString(sum)
return code[12:24]
}

View File

@@ -0,0 +1,33 @@
package service
import (
"testing"
"time"
"go-common/app/admin/main/coupon/model"
. "github.com/smartystreets/goconvey/convey"
)
// go test -test.v -test.run TestBatchInfo
func TestInitCodes(t *testing.T) {
Convey("TestInitCodes ", t, func() {
err := s.InitCodes(c, "allowance_batch100")
time.Sleep(2 * time.Second)
So(err, ShouldBeNil)
})
}
func TestCodeBlock(t *testing.T) {
Convey("TestCodeBlock ", t, func() {
err := s.CodeBlock(c, &model.ArgCouponCode{ID: 103})
So(err, ShouldBeNil)
})
}
func TestCodeUnBlock(t *testing.T) {
Convey("TestCodeUnBlock ", t, func() {
err := s.CodeUnBlock(c, &model.ArgCouponCode{ID: 103})
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,159 @@
package service
import (
"bytes"
"context"
"fmt"
"runtime/debug"
"time"
"go-common/app/admin/main/coupon/model"
col "go-common/app/service/main/coupon/model"
coumol "go-common/app/service/main/coupon/model"
"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"
)
// AddBatchInfo add batch info.
func (s *Service) AddBatchInfo(c context.Context, b *model.CouponBatchInfo) (err error) {
if b.StartTime >= b.ExpireTime {
err = ecode.CouPonBatchTimeErr
return
}
b.BatchToken = s.token()
b.Ctime = xtime.Time(time.Now().Unix())
if _, err = s.dao.AddBatchInfo(c, b); err != nil {
err = errors.WithStack(err)
}
return
}
// BatchList batch list.
func (s *Service) BatchList(c context.Context, arg *model.ArgBatchList) (res []*model.CouponBatchResp, err error) {
var bs []*model.CouponBatchInfo
if bs, err = s.dao.BatchList(c, arg.AppID, arg.Type); err != nil {
err = errors.WithStack(err)
return
}
for _, v := range bs {
r := new(model.CouponBatchResp)
r.ID = v.ID
r.AppID = v.AppID
r.AppName = s.allAppInfo[v.AppID]
r.Name = v.Name
r.BatchToken = v.BatchToken
r.MaxCount = v.MaxCount
r.CurrentCount = v.CurrentCount
r.StartTime = v.StartTime
r.ExpireTime = v.ExpireTime
r.ExpireDay = v.ExpireDay
r.Operator = v.Operator
r.LimitCount = v.LimitCount
r.UseLimitExplain = model.NoLimitExplain
r.Amount = v.Amount
r.FullAmount = v.FullAmount
if r.PlatfromLimit, err = xstr.SplitInts(v.PlatformLimit); err != nil {
log.Error("xstr.SplitInts() err[%+v] ", v.PlatformLimit, err)
err = nil
}
if r.PlatfromLimit == nil {
r.PlatfromLimit = []int64{}
}
r.ProdLimExplainFmt(v.ProdLimMonth, v.ProdLimRenewal) //ProductLimitExplain
r.ProdLimMonth = v.ProdLimMonth
r.ProdLimRenewal = v.ProdLimRenewal
r.State = batchState(v)
res = append(res, r)
}
return
}
func batchState(v *model.CouponBatchInfo) (state int8) {
state = v.State
now := time.Now().Unix()
if v.ExpireDay == -1 {
if v.ExpireTime <= now {
state = model.CodeBatchExpire
}
}
return
}
// get coupon token
func (s *Service) token() string {
var b bytes.Buffer
b.WriteString(fmt.Sprintf("%07d", s.r.Int63n(9999999)))
b.WriteString(fmt.Sprintf("%03d", time.Now().UnixNano()/1e6%1000))
b.WriteString(time.Now().Format("20060102150405"))
return b.String()
}
// AllAppInfo app app info.
func (s *Service) AllAppInfo(c context.Context) map[int64]string {
return s.allAppInfo
}
// SalaryCoupon salary coupon.
func (s *Service) SalaryCoupon(c context.Context, mid int64, ct int64, count int, token string) (err error) {
arg := new(coumol.ArgSalaryCoupon)
arg.Count = count
arg.CouponType = ct
arg.Mid = mid
arg.Origin = model.SystemAdminSalary
arg.BatchToken = token
if err = s.couRPC.SalaryCoupon(c, arg); err != nil {
err = errors.WithStack(err)
}
return
}
//RunSalaryCoupon run salary coupon.
func (s *Service) RunSalaryCoupon(c context.Context, mids []int64, token string, appID int64, couponType int64, origin int64, mt string) {
go func() {
var (
err error
msgmids = []int64{}
)
defer func() {
if x := recover(); x != nil {
log.Error("RunSalaryCoupon.GoRun arg[%s] panic[%+v]", token, x)
log.Error("%s", debug.Stack())
}
}()
for _, v := range mids {
for i := 0; i < _maxretry; i++ {
if err = s.couRPC.SalaryCoupon(context.Background(), &col.ArgSalaryCoupon{
Mid: v,
CouponType: couponType,
Origin: origin,
Count: 1,
BatchToken: token,
AppID: appID,
}); err != nil {
time.Sleep(200 * time.Millisecond)
continue
}
break
}
if err != nil {
log.Error("RunSalaryCoupon faild arg[%s,%d] err[%+v] ", token, v, err)
continue
}
time.Sleep(10 * time.Millisecond)
log.Info("RunSalaryCoupon suc arg[%s,%d]", token, v)
msgmids = append(msgmids, v)
}
if len(mt) > 0 && s.c.Prop.SalaryNormalMsgOpen && len(msgmids) > 0 {
if cerr := s.msgchan.Do(c, func(c context.Context) {
s.sendMsg(msgmids, metadata.String(c, metadata.RemoteIP), 1, true, mt)
}); cerr != nil {
log.Error("s.sendMsg err(%+v)", cerr)
}
}
}()
}

View File

@@ -0,0 +1,133 @@
package service
import (
"context"
"flag"
"testing"
"time"
"go-common/app/admin/main/coupon/conf"
"go-common/app/admin/main/coupon/model"
. "github.com/smartystreets/goconvey/convey"
)
var (
c = context.TODO()
s *Service
)
func init() {
var (
err error
)
flag.Set("conf", "../cmd/coupon-admin.toml")
if err = conf.Init(); err != nil {
panic(err)
}
c = context.Background()
if s == nil {
s = New(conf.Conf)
}
time.Sleep(time.Second)
}
// go test -test.v -test.run TestAddBatchInfo
func TestAddBatchInfo(t *testing.T) {
Convey("TestAddBatchInfo ", t, func() {
var err error
b := new(model.CouponBatchInfo)
b.AppID = int64(1)
b.Name = "name"
b.MaxCount = int64(100)
b.CurrentCount = int64(0)
b.StartTime = time.Now().Unix()
b.ExpireTime = time.Now().Unix() + int64(10000000)
err = s.AddBatchInfo(c, b)
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestBatchList
func TestBatchList(t *testing.T) {
Convey("TestBatchList ", t, func() {
var err error
_, err = s.BatchList(c, &model.ArgBatchList{AppID: 1})
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestSalaryCoupon
func TestSalaryCoupon(t *testing.T) {
Convey("TestSalaryCoupon ", t, func() {
var (
err error
mid int64 = 1
ct int64 = 2
token = "test03"
count = 20
)
err = s.SalaryCoupon(c, mid, ct, count, token)
So(err, ShouldBeNil)
})
}
func TestService_CouponViewBatchAdd(t *testing.T) {
Convey("view batch add ", t, func() {
arg := new(model.ArgCouponViewBatch)
arg.AppID = 1
arg.Name = "观影券test"
arg.StartTime = time.Now().Unix()
arg.ExpireTime = time.Now().AddDate(0, 0, 12).Unix()
arg.Operator = "admin"
arg.MaxCount = -1
arg.LimitCount = -1
err := s.CouponViewBatchAdd(c, arg)
So(err, ShouldBeNil)
})
}
func TestService_CouponViewbatchSave(t *testing.T) {
Convey("view batch save", t, func() {
arg := new(model.ArgCouponViewBatch)
arg.ID = 47
arg.AppID = 1
arg.Name = "观影券test"
arg.StartTime = time.Now().Unix()
arg.ExpireTime = time.Now().AddDate(0, 0, 12).Unix()
arg.Operator = "admin"
arg.MaxCount = 100
arg.LimitCount = -1
err := s.CouponViewbatchSave(c, arg)
So(err, ShouldBeNil)
})
}
func TestService_CouponViewBlock(t *testing.T) {
Convey("coupon view block", t, func() {
mid := int64(39)
couponToken := "326209901520180419161313"
err := s.CouponViewBlock(c, mid, couponToken)
So(err, ShouldBeNil)
})
}
func TestService_CouponViewUnblock(t *testing.T) {
Convey("coupon view un block", t, func() {
mid := int64(39)
couponToken := "326209901520180419161313"
err := s.CouponViewUnblock(c, mid, couponToken)
So(err, ShouldBeNil)
})
}
func TestService_CouponViewList(t *testing.T) {
Convey("coupon view list", t, func() {
arg := new(model.ArgSearchCouponView)
arg.Mid = 39
arg.AppID = 1
res, count, err := s.CouponViewList(c, arg)
t.Logf("res:%+v count:%+v", res, count)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,96 @@
package service
import (
"context"
"math/rand"
"time"
"go-common/app/admin/main/coupon/conf"
"go-common/app/admin/main/coupon/dao"
"go-common/app/admin/main/coupon/model"
courpc "go-common/app/service/main/coupon/rpc/client"
"go-common/library/log"
"go-common/library/sync/pipeline/fanout"
)
const (
_maxSalaryCount = 100000
_notLimitSalary = -1
_maxretry = 3
_lockseconds = 604800
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
r *rand.Rand
allAppInfo map[int64]string
couRPC *courpc.Service
// cache async del
cache *fanout.Fanout
// msg async send
msgchan *fanout.Fanout
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
r: rand.New(rand.NewSource(time.Now().Unix())),
allAppInfo: make(map[int64]string),
couRPC: courpc.New(c.RPCClient2.Coupon),
// cache chan
cache: fanout.New("cache", fanout.Worker(5), fanout.Buffer(1024)),
// msg chan
msgchan: fanout.New("cache", fanout.Worker(5), fanout.Buffer(10240)),
}
if err := s.loadappinfo(); err != nil {
panic(err)
}
go s.loadappinfoproc()
return s
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}
func (s *Service) loadappinfo() (err error) {
var (
c = context.Background()
as []*model.AppInfo
)
if as, err = s.dao.AllAppInfo(c); err != nil {
log.Error("loadappinfo allappinfo error(%v)", err)
return
}
tmp := make(map[int64]string, len(as))
for _, v := range as {
tmp[v.ID] = v.Name
}
s.allAppInfo = tmp
log.Info("loadappinfo (%v) load success", tmp)
return
}
func (s *Service) loadappinfoproc() {
defer func() {
if x := recover(); x != nil {
log.Error("service.loadappinfoproc panic(%v)", x)
go s.loadappinfoproc()
log.Info("service.loadappinfoproc recover")
}
}()
for {
time.Sleep(time.Minute * 2)
s.loadappinfo()
}
}

View File

@@ -0,0 +1,211 @@
package service
import (
"context"
"mime/multipart"
"time"
"go-common/app/admin/main/coupon/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
"github.com/pkg/errors"
)
//CouponViewBatchAdd view batch add.
func (s *Service) CouponViewBatchAdd(c context.Context, arg *model.ArgCouponViewBatch) (err error) {
if arg.StartTime > arg.ExpireTime {
err = ecode.CouPonBatchTimeErr
return
}
arg.BatchToken = s.token()
arg.CouponType = model.CouponVideo
if err = s.dao.AddViewBatch(c, arg); err != nil {
err = errors.WithStack(err)
}
return
}
//CouponViewbatchSave view batch save.
func (s *Service) CouponViewbatchSave(c context.Context, arg *model.ArgCouponViewBatch) (err error) {
if err = s.dao.UpdateViewBatch(c, arg); err != nil {
err = errors.WithStack(err)
}
return
}
//CouponViewBlock view block
func (s *Service) CouponViewBlock(c context.Context, mid int64, couponToken string) (err error) {
var (
r *model.CouponInfo
tx *sql.Tx
)
if tx, err = s.dao.BeginTran(c); err != nil {
err = errors.WithStack(err)
return
}
defer func() {
if err != nil {
tx.Rollback()
return
}
tx.Commit()
s.dao.DelCouponTypeCache(context.Background(), mid, model.CouponVideo)
}()
if r, err = s.dao.CouponViewInfo(c, couponToken, mid); err != nil {
err = errors.WithStack(err)
return
}
if r.State != model.NotUsed {
err = ecode.CouponInfoStateBlockErr
return
}
if err = s.dao.TxUpdateViewInfo(tx, model.Block, couponToken, mid); err != nil {
err = errors.WithStack(err)
return
}
olog := new(model.CouponChangeLog)
olog.Mid = mid
olog.CouponToken = couponToken
olog.State = model.Block
if err = s.dao.TxCouponViewLog(tx, olog); err != nil {
err = errors.WithStack(err)
}
return
}
//CouponViewUnblock view unblock
func (s *Service) CouponViewUnblock(c context.Context, mid int64, couponToken string) (err error) {
var (
r *model.CouponInfo
tx *sql.Tx
)
if tx, err = s.dao.BeginTran(c); err != nil {
err = errors.WithStack(err)
return
}
defer func() {
if err != nil {
tx.Rollback()
return
}
tx.Commit()
s.dao.DelCouponTypeCache(context.Background(), mid, model.CouponVideo)
}()
if r, err = s.dao.CouponViewInfo(c, couponToken, mid); err != nil {
err = errors.WithStack(err)
return
}
if r.State != model.Block {
err = ecode.CouponInfoStateUnblockErr
return
}
if err = s.dao.TxUpdateViewInfo(tx, model.NotUsed, couponToken, mid); err != nil {
err = errors.WithStack(err)
return
}
olog := new(model.CouponChangeLog)
olog.Mid = mid
olog.CouponToken = couponToken
olog.State = model.NotUsed
if err = s.dao.TxCouponViewLog(tx, olog); err != nil {
err = errors.WithStack(err)
}
return
}
//CouponViewList view list.
func (s *Service) CouponViewList(c context.Context, arg *model.ArgSearchCouponView) (res []*model.CouponInfo, count int64, err error) {
var (
bls []*model.CouponBatchInfo
)
if arg.AppID > 0 || len(arg.BatchToken) > 0 {
if bls, err = s.dao.BatchViewList(c, arg.AppID, arg.BatchToken, model.CouponVideo); err != nil {
err = errors.WithStack(err)
return
}
if len(bls) == 0 {
return
}
for _, v := range bls {
arg.BatchTokens = append(arg.BatchTokens, v.BatchToken)
}
}
if count, err = s.dao.SearchViewCouponCount(c, arg); err != nil {
err = errors.WithStack(err)
return
}
if count <= 0 {
return
}
if res, err = s.dao.SearchViewCouponInfo(c, arg); err != nil {
err = errors.WithStack(err)
return
}
now := time.Now()
for _, v := range res {
if v.OID == 0 {
v.Mtime = 0
} else {
var pgc *model.PGCInfoResq
if pgc, err = s.dao.GetPGCInfo(c, v.OID); err != nil {
log.Error("get pgc info coupon%+v error:%+v", v, err)
continue
}
if pgc != nil {
v.Title = pgc.Title
}
}
if len(v.BatchToken) > 0 {
var cbi *model.CouponBatchInfo
if cbi, err = s.dao.BatchInfo(c, v.BatchToken); err != nil {
err = errors.WithStack(err)
return
}
v.BatchName = cbi.Name
}
if v.ExpireTime < now.Unix() && v.State == model.NotUsed {
v.State = model.Expire
}
}
return
}
//CouponViewSalary view salary.
func (s *Service) CouponViewSalary(c context.Context, f multipart.File, h *multipart.FileHeader, mids []int64, token string) (count int, err error) {
var bi *model.CouponBatchInfo
if len(mids) == 0 {
if mids, err = s.ReadCsv(f, h); err != nil {
err = errors.WithStack(err)
return
}
}
if len(mids) == 0 {
err = ecode.CouponBatchSalaryCountZeroErr
return
}
if len(mids) > _maxSalaryCount {
err = ecode.CouponBatchSalaryLimitErr
return
}
if bi, err = s.dao.BatchInfo(c, token); err != nil {
err = errors.WithStack(err)
return
}
if bi == nil {
err = ecode.CouPonTokenNotFoundErr
return
}
if bi.State != model.BatchStateNormal {
err = ecode.CouPonHadBlockErr
return
}
if bi.MaxCount != _notLimitSalary && len(mids) > int(bi.MaxCount-bi.CurrentCount) {
err = ecode.CouponBatchSalaryLimitErr
return
}
s.RunSalaryCoupon(c, mids, token, bi.AppID, model.CouponVideo, model.SystemAdminSalary, "")
count = len(mids)
return
}