Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/coupon/conf:go_default_library",
"//app/job/main/coupon/model:go_default_library",
"//library/database/sql:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"notify.go",
"service.go",
],
importpath = "go-common/app/job/main/coupon/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/coupon/conf:go_default_library",
"//app/job/main/coupon/dao:go_default_library",
"//app/job/main/coupon/model:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/robfig/cron: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"],
)

View File

@@ -0,0 +1,481 @@
package service
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"go-common/app/job/main/coupon/model"
"go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
"github.com/pkg/errors"
)
// Notify notify.
func (s *Service) Notify(c context.Context, msg *model.MsgCanal) (err error) {
var (
mid int64
token string
ok, ok1 bool
ct int64
couponToken, batchToken string
)
if strings.Contains(msg.Table, _couponTable) {
if msg.Action != _updateAct {
return
}
if mid, token, ct, ok, err = s.conventMsg(c, msg); err != nil {
err = errors.WithStack(err)
return
}
} else if msg.Table == _orderTable {
if msg.Action != _insertAct {
return
}
if mid, token, ct, ok, err = s.conventOrderMsg(c, msg); err != nil {
err = errors.WithStack(err)
return
}
} else if strings.Contains(msg.Table, _couponAllowanceTable) { // 元旦活动
if msg.Action != _updateAct {
return
}
if mid, couponToken, batchToken, ok1, err = s.conventAllowanceInfoMsg(c, msg); err != nil {
err = errors.WithStack(err)
return
}
if ok1 {
if _, err = s.dao.UpdateUserCard(c, mid, model.Used, couponToken, batchToken); err != nil {
err = errors.WithStack(err)
return
}
if err = s.dao.DelPrizeCardsKey(c, mid, s.c.NewYearConf.ActID); err != nil {
err = errors.WithStack(err)
return
}
}
}
if !ok {
return
}
arg := &model.NotifyParam{
Mid: mid,
CouponToken: token,
NotifyURL: s.c.Properties.BangumiNotifyURL,
Type: ct,
}
if err = s.CheckCouponDeliver(c, arg); err != nil {
log.Error("CheckCouponDeliver fail arg(%v) err(%v)", arg, err)
arg.NotifyCount++
s.notifyChan <- arg
return
}
return
}
func (s *Service) conventMsg(c context.Context, msg *model.MsgCanal) (mid int64, token string, ct int64, ok bool, err error) {
ok = true
cnew := new(model.CouponInfo)
if err = json.Unmarshal(msg.New, cnew); err != nil {
err = errors.WithStack(err)
return
}
cold := new(model.CouponInfo)
if err = json.Unmarshal(msg.Old, cold); err != nil {
err = errors.WithStack(err)
return
}
if cold.State != model.NotUsed || cnew.State != model.InUse {
ok = false
}
mid = cnew.Mid
token = cnew.CouponToken
ct = cnew.CouponType
return
}
func (s *Service) conventOrderMsg(c context.Context, msg *model.MsgCanal) (mid int64, token string, ct int64, ok bool, err error) {
ok = true
cnew := new(model.CouponOrder)
if err = json.Unmarshal(msg.New, cnew); err != nil {
err = errors.WithStack(err)
return
}
if cnew.State != model.InPay {
ok = false
}
mid = cnew.Mid
token = cnew.OrderNo
ct = int64(cnew.CouponType)
return
}
func (s *Service) conventAllowanceInfoMsg(c context.Context, msg *model.MsgCanal) (mid int64, couponToken, batchToken string, ok bool, err error) {
ok = true
cnew := new(model.CouponAllowanceInfo)
if err = json.Unmarshal(msg.New, cnew); err != nil {
err = errors.WithStack(err)
return
}
log.Info("conventAllowanceInfoMsg(%+v)", cnew)
if cnew.State != model.Used || cnew.AppID != 1 || cnew.Origin != model.AllowanceBusinessNewYear {
ok = false
}
mid = cnew.MID
couponToken = cnew.CouponToken
batchToken = cnew.BatchToken
return
}
//CheckCouponDeliver check coupon deliver
func (s *Service) CheckCouponDeliver(c context.Context, arg *model.NotifyParam) (err error) {
switch arg.Type {
case model.BangumiVideo:
err = s.CouponDeliver(c, arg)
case model.Cartoon:
err = s.CouponCartoonDeliver(c, arg)
}
return
}
// CouponDeliver def.
func (s *Service) CouponDeliver(c context.Context, arg *model.NotifyParam) (err error) {
var (
data *model.CallBackRet
cp *model.CouponInfo
nstate int8
)
if cp, err = s.dao.CouponInfo(c, arg.Mid, arg.CouponToken); err != nil {
err = errors.WithStack(err)
return
}
if cp == nil {
log.Warn("notify coupon is nil(%v)", arg)
return
}
if cp.State != model.InUse {
log.Warn("notify coupon had deal with(%v)", arg)
return
}
if data, err = s.dao.NotifyRet(c, arg.NotifyURL, cp.CouponToken, cp.OrderNO, "127.0.0.1"); err != nil {
err = errors.WithStack(err)
return
}
if data.Ver == cp.UseVer {
err = fmt.Errorf("coupon ver not change resp(%v) db(%v)", data, cp)
return
}
switch data.IsPaid {
case model.PaidSuccess:
nstate = model.Used
case model.Unpaid:
nstate = model.NotUsed
default:
log.Warn("state not found resp(%v) db(%v)", data, cp)
return
}
log.Info("update coupon state(%s,%d,%d,%d,%d)", cp.CouponToken, cp.Mid, nstate, data.Ver, cp.Ver)
if err = s.updateCouponState(c, cp, nstate, data); err != nil {
log.Error("updateCouponState fail %+v", err)
return
}
return
}
func (s *Service) updateCouponState(c context.Context, cp *model.CouponInfo, nstate int8, data *model.CallBackRet) (err error) {
var (
tx *sql.Tx
aff int64
)
if tx, err = s.dao.BeginTran(c); err != nil {
err = errors.WithStack(err)
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback %+v", err)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit %+v", err)
}
}()
if aff, err = s.dao.UpdateCoupon(c, tx, cp.Mid, nstate, data.Ver, cp.Ver, cp.CouponToken); err != nil {
err = errors.WithStack(err)
return
}
if aff != 1 {
err = fmt.Errorf("coupon deal fail (%v) db(%v)", data, cp)
return
}
l := &model.CouponChangeLog{}
l.CouponToken = cp.CouponToken
l.Mid = cp.Mid
l.State = nstate
l.Ctime = xtime.Time(time.Now().Unix())
if _, err = s.dao.InsertPointHistory(c, tx, l); err != nil {
err = errors.WithStack(err)
return
}
s.dao.DelCouponsCache(c, cp.Mid, int8(cp.CouponType))
return
}
// CouponCartoonDeliver coupon cartoon deliver def.
func (s *Service) CouponCartoonDeliver(c context.Context, arg *model.NotifyParam) (err error) {
var (
data *model.CallBackRet
o *model.CouponOrder
nstate int8
)
if o, err = s.dao.ByOrderNo(c, arg.CouponToken); err != nil {
err = errors.WithStack(err)
return
}
if o == nil {
log.Warn("notify coupon order is nil(%v)", arg)
return
}
if o.State != model.InPay {
log.Warn("notify coupon order had deal with(%v)", arg)
return
}
if data, err = s.dao.NotifyRet(c, arg.NotifyURL, o.OrderNo, o.ThirdTradeNo, "127.0.0.1"); err != nil {
err = errors.WithStack(err)
return
}
if data.Ver == o.UseVer {
err = fmt.Errorf("coupon order ver not change resp(%v) db(%v)", data, o)
return
}
switch data.IsPaid {
case model.PaidSuccess:
nstate = model.PaySuccess
case model.Unpaid:
nstate = model.PayFaild
default:
log.Warn("order state not found resp(%v) db(%v)", data, o)
return
}
log.Info("update coupon order state(%s,%d,%d,%d,%d)", o.OrderNo, o.Mid, nstate, data.Ver, o.UseVer)
if err = s.UpdateOrderState(c, o, nstate, data); err != nil {
log.Error("updateCouponState fail %+v", err)
return
}
return
}
// UpdateOrderState update order state.
func (s *Service) UpdateOrderState(c context.Context, o *model.CouponOrder, nstate int8, data *model.CallBackRet) (err error) {
var (
tx *sql.Tx
aff int64
ls []*model.CouponBalanceChangeLog
)
if tx, err = s.dao.BeginTran(c); err != nil {
err = errors.WithStack(err)
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback %+v", err)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit %+v", err)
}
}()
if aff, err = s.dao.UpdateOrderState(c, tx, o.Mid, nstate, data.Ver, o.Ver, o.OrderNo); err != nil {
err = errors.WithStack(err)
return
}
if aff != 1 {
err = fmt.Errorf("coupon order deal fail (%v) db(%v)", data, o)
return
}
// add order log.
ol := new(model.CouponOrderLog)
ol.OrderNo = o.OrderNo
ol.Mid = o.Mid
ol.State = nstate
ol.Ctime = xtime.Time(time.Now().Unix())
if _, err = s.dao.AddOrderLog(c, tx, ol); err != nil {
err = errors.WithStack(err)
return
}
if nstate == model.PayFaild {
// coupon back to user
if ls, err = s.dao.ConsumeCouponLog(c, o.Mid, o.OrderNo, model.Consume); err != nil {
err = errors.WithStack(err)
return
}
if len(ls) == 0 {
err = fmt.Errorf("ConsumeCouponLog not found (mid:%d,orderNo:%s)", o.Mid, o.OrderNo)
return
}
if err = s.UpdateBalance(c, tx, o.Mid, o.CouponType, ls, o.OrderNo); err != nil {
err = errors.WithStack(err)
return
}
}
return
}
// UpdateBalance update user balance.
func (s *Service) UpdateBalance(c context.Context, tx *sql.Tx, mid int64, ct int8, ls []*model.CouponBalanceChangeLog, orderNo string) (err error) {
var (
now = time.Now()
bs []*model.CouponBalanceInfo
aff int64
usebs []*model.CouponBalanceInfo
blogs []*model.CouponBalanceChangeLog
)
if bs, err = s.dao.BlanceList(c, mid, ct); err != nil {
err = errors.WithStack(err)
return
}
if len(bs) == 0 {
err = fmt.Errorf("coupon balance not found (mid:%d ct:%d)", mid, ct)
return
}
for _, ob := range bs {
for _, l := range ls {
if ob.BatchToken == l.BatchToken {
b := new(model.CouponBalanceInfo)
b.ID = ob.ID
b.Ver = ob.Ver
b.Balance = ob.Balance - l.ChangeBalance
usebs = append(usebs, b)
blog := new(model.CouponBalanceChangeLog)
blog.OrderNo = orderNo
blog.Mid = mid
blog.BatchToken = ob.BatchToken
blog.ChangeType = model.ConsumeFaildBack
blog.Ctime = xtime.Time(now.Unix())
blog.Balance = b.Balance
blog.ChangeBalance = -l.ChangeBalance
blogs = append(blogs, blog)
}
}
}
if len(ls) != len(usebs) {
err = fmt.Errorf("coupon balance not found (mid:%d len(ls):%d) len(usebs):%d", mid, len(ls), len(usebs))
return
}
if len(usebs) == 1 {
b := usebs[0]
if aff, err = s.dao.UpdateBlance(c, tx, b.ID, mid, b.Ver, b.Balance); err != nil {
err = errors.WithStack(err)
return
}
} else {
if aff, err = s.dao.BatchUpdateBlance(c, tx, mid, usebs); err != nil {
err = errors.WithStack(err)
return
}
}
if int(aff) != len(usebs) {
err = fmt.Errorf("coupon balance back faild mid(%d) order(%s)", mid, orderNo)
return
}
if _, err = s.dao.BatchInsertBlanceLog(c, tx, mid, blogs); err != nil {
err = errors.WithStack(err)
return
}
s.dao.DelCouponBalancesCache(c, mid, ct)
return
}
// CheckInUseCoupon check inuse coupon.
func (s *Service) CheckInUseCoupon() {
var (
c = context.TODO()
cps []*model.CouponInfo
t = time.Now().AddDate(0, 0, -1)
err error
)
log.Info("check inuse coupon job start")
for i := 0; i < 100; i++ {
if cps, err = s.dao.CouponList(c, int64(i), model.InUse, t); err != nil {
log.Error("query coupon list(%d,%v) err(%v)", i, t, err)
return
}
log.Info("check inuse coupon job ing size(%d)", len(cps))
for _, v := range cps {
var notifyURL string
if v.CouponType == model.BangumiVideo {
notifyURL = s.c.Properties.BangumiNotifyURL
}
if len(notifyURL) == 0 {
continue
}
// point callback.
arg := &model.NotifyParam{
Mid: v.Mid,
CouponToken: v.CouponToken,
NotifyURL: notifyURL,
Type: v.CouponType,
}
if err = s.CheckCouponDeliver(c, arg); err != nil {
log.Error("CheckCouponDeliver fail arg(%v) err(%v)", arg, err)
continue
}
}
time.Sleep(time.Second * 1)
}
log.Info("check inuse coupon job start")
}
// CheckOrderInPayCoupon check order inuse coupon.
func (s *Service) CheckOrderInPayCoupon() {
var (
c = context.TODO()
cps []*model.CouponOrder
t = time.Now().AddDate(0, 0, -1)
err error
)
log.Info("check inuse coupon order job start")
if cps, err = s.dao.OrderInPay(c, model.InPay, t); err != nil {
log.Error("query coupon order list(%d,%v) err(%v)", model.InPay, t, err)
return
}
log.Info("check inuse coupon order job ing size(%d)", len(cps))
for _, v := range cps {
var notifyURL string
if v.CouponType == model.Cartoon {
notifyURL = s.c.Properties.BangumiNotifyURL
}
if len(notifyURL) == 0 {
continue
}
// point callback.
arg := &model.NotifyParam{
Mid: v.Mid,
CouponToken: v.OrderNo,
NotifyURL: notifyURL,
Type: int64(v.CouponType),
}
if err = s.CheckCouponDeliver(c, arg); err != nil {
log.Error("CheckCouponDeliver order fail arg(%v) err(%v)", arg, err)
continue
}
}
log.Info("check inuse coupon order job start")
}
// ByOrderNo by order no.
func (s *Service) ByOrderNo(c context.Context, orderNo string) (o *model.CouponOrder, err error) {
if o, err = s.dao.ByOrderNo(c, orderNo); err != nil {
err = errors.WithStack(err)
}
return
}

View File

@@ -0,0 +1,145 @@
package service
import (
"context"
"encoding/json"
"sync"
"time"
"go-common/app/job/main/coupon/conf"
"go-common/app/job/main/coupon/dao"
"go-common/app/job/main/coupon/model"
"go-common/library/log"
"go-common/library/queue/databus"
"github.com/robfig/cron"
)
const (
_couponTable = "coupon_info_"
_orderTable = "coupon_order"
_couponAllowanceTable = "coupon_allowance_info"
_updateAct = "update"
_insertAct = "insert"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
couponDatabus *databus.Databus
waiter sync.WaitGroup
notifyChan chan *model.NotifyParam
close bool
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
notifyChan: make(chan *model.NotifyParam, 10240),
}
if c.DataBus.CouponBinlog != nil {
s.couponDatabus = databus.New(c.DataBus.CouponBinlog)
s.waiter.Add(1)
go s.couponbinlogproc()
}
go s.notifyproc()
t := cron.New()
t.AddFunc(s.c.Properties.CheckInUseCouponCron, s.CheckInUseCoupon)
t.AddFunc(s.c.Properties.CheckInUseCouponCartoonCron, s.CheckOrderInPayCoupon)
t.Start()
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.close = true
s.couponDatabus.Close()
s.dao.Close()
s.waiter.Wait()
}
func (s *Service) couponbinlogproc() {
defer s.waiter.Done()
var (
err error
msg *databus.Message
msgChan = s.couponDatabus.Messages()
ok bool
c = context.Background()
)
for {
msg, ok = <-msgChan
if !ok || s.close {
log.Info("couponbinlogproc closed")
return
}
if err = msg.Commit(); err != nil {
log.Error("msg.Commit err(%+v)", err)
}
v := &model.MsgCanal{}
if err = json.Unmarshal([]byte(msg.Value), v); err != nil {
log.Error("json.Unmarshal(%v) err(%v)", v, err)
continue
}
log.Info("couponbinlogproc log(%+v)", v)
if err = s.Notify(c, v); err != nil {
log.Error("s.Notify(%v) err(%v)", v, err)
}
}
}
func (s *Service) notifyproc() {
var (
msg *model.NotifyParam
ticker = time.NewTicker(time.Duration(s.c.Properties.NotifyTimeInterval))
mergeMap = make(map[string]*model.NotifyParam)
maxMergeSize = 1000
full bool
ok bool
err error
)
for {
select {
case msg, ok = <-s.notifyChan:
if !ok {
log.Info("notifyproc msgChan closed")
return
}
if msg == nil {
continue
}
if _, ok := mergeMap[msg.CouponToken]; !ok {
mergeMap[msg.CouponToken] = msg
}
if len(mergeMap) < maxMergeSize {
continue
}
full = true
case <-ticker.C:
}
if len(mergeMap) > 0 {
for _, v := range mergeMap {
log.Info("retry notify coupon arg(%v)", v)
if err = s.CheckCouponDeliver(context.TODO(), v); err != nil {
log.Error("CheckCouponDeliver fail arg(%v) err(%v)", v, err)
v.NotifyCount++
if v.NotifyCount < s.c.Properties.MaxRetries {
s.notifyChan <- v
}
}
}
mergeMap = make(map[string]*model.NotifyParam)
}
if full {
time.Sleep(time.Duration(s.c.Properties.NotifyTimeInterval))
}
}
}

View File

@@ -0,0 +1,154 @@
package service
import (
"context"
"flag"
"testing"
"time"
"go-common/app/job/main/coupon/conf"
"go-common/app/job/main/coupon/model"
"go-common/library/database/sql"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
c context.Context
)
func init() {
var (
err error
)
flag.Set("conf", "../cmd/coupon-job.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 TestCheckCouponDeliver
func TestCheckCouponDeliver(t *testing.T) {
Convey("TestCheckCouponDeliver ", t, func() {
var (
err error
)
arg := &model.NotifyParam{
Mid: 1,
CouponToken: "676289266420180402162120",
NotifyURL: "http://bangumi.bilibili.com/pay/inner/notify_ticket",
}
err = s.CheckCouponDeliver(context.TODO(), arg)
So(err, ShouldBeNil)
})
}
func TestCheckInUseCoupon(t *testing.T) {
Convey("TestCheckInUseCoupon ", t, func() {
s.CheckInUseCoupon()
})
}
func TestNotifyproc(t *testing.T) {
Convey("TestNotifyproc ", t, func() {
var err error
time.Sleep(time.Duration(s.c.Properties.NotifyTimeInterval))
for i := 0; i < 10; i++ {
arg := &model.NotifyParam{
Mid: 1,
CouponToken: "729792667120180402161647",
NotifyURL: "http://bangumi.bilibili.com/pay/inner/notify_ticket",
}
if err = s.CheckCouponDeliver(context.TODO(), arg); err != nil {
arg.NotifyCount++
s.notifyChan <- arg
}
So(err, ShouldBeNil)
}
})
}
func TestUpdateCoupon(t *testing.T) {
Convey("TestUpdateCoupon ", t, func() {
cp := &model.CouponInfo{
CouponToken: "729792667120180402161647",
Mid: 1,
CouponType: 1,
Ver: 4,
}
data := &model.CallBackRet{
Ver: 3,
}
err := s.updateCouponState(c, cp, 2, data)
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestUpdateBalance
func TestUpdateBalance(t *testing.T) {
Convey("TestUpdateBalance ", t, func() {
var (
tx *sql.Tx
mid int64 = 1
orderNo = "9372774783174654609"
ls []*model.CouponBalanceChangeLog
err error
)
ls, err = s.dao.ConsumeCouponLog(c, mid, orderNo, model.Consume)
So(err, ShouldBeNil)
tx, err = s.dao.BeginTran(c)
So(err, ShouldBeNil)
err = s.UpdateBalance(c, tx, mid, model.Cartoon, ls, orderNo)
So(err, ShouldBeNil)
err = tx.Commit()
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestUpdateOrderState
func TestUpdateOrderState(t *testing.T) {
Convey("TestUpdateOrderState ", t, func() {
var (
orderNo = "6462644254161152528"
faildOrderNo = "9176715513161453816"
err error
o *model.CouponOrder
)
data := &model.CallBackRet{
Ver: 123456,
IsPaid: 1,
}
o, err = s.dao.ByOrderNo(c, orderNo)
So(err, ShouldBeNil)
err = s.UpdateOrderState(c, o, model.PaySuccess, data)
So(err, ShouldBeNil)
o, err = s.dao.ByOrderNo(c, faildOrderNo)
So(err, ShouldBeNil)
err = s.UpdateOrderState(c, o, model.PayFaild, data)
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestCouponCartoonDeliver
func TestCouponCartoonDeliver(t *testing.T) {
Convey("TestCouponCartoonDeliver ", t, func() {
var (
err error
)
arg := &model.NotifyParam{
CouponToken: "5586615697161708066",
Mid: 1,
Type: 2,
}
err = s.CouponCartoonDeliver(c, arg)
So(err, ShouldBeNil)
})
}