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

21
app/job/main/coupon/BUILD Normal file
View File

@@ -0,0 +1,21 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/job/main/coupon/cmd:all-srcs",
"//app/job/main/coupon/conf:all-srcs",
"//app/job/main/coupon/dao:all-srcs",
"//app/job/main/coupon/http:all-srcs",
"//app/job/main/coupon/model:all-srcs",
"//app/job/main/coupon/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,18 @@
# v2.1.1
1. waitgroup
# v2.1.0
1. 大会员元旦活动
# v2.0.1
1. fix check table
# v2.0.0
1. 漫画劵
# v1.0.1
1. remove auto load conf
# v1.0.0
1.观影劵一期

View File

@@ -0,0 +1,9 @@
# Owner
zhaogangtao
yubaihai
# Author
yubaihai
# Reviewer
zhaogangtao

View File

@@ -0,0 +1,14 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- yubaihai
- zhaogangtao
labels:
- job
- job/main/coupon
- main
options:
no_parent_owners: true
reviewers:
- yubaihai
- zhaogangtao

View File

@@ -0,0 +1,14 @@
# coupon
# 项目简介
1.
# 编译环境
# 依赖包
# 编译执行

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["coupon-job.toml"],
importpath = "go-common/app/job/main/coupon/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/coupon/conf:go_default_library",
"//app/job/main/coupon/http:go_default_library",
"//app/job/main/coupon/service:go_default_library",
"//library/log: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,92 @@
# This is a TOML document. Boom
version = "1.0.0"
user = "nobody"
pid = "/tmp/coupon.pid"
dir = "./"
[log]
# dir = "/data/log/coupon"
stdout = true
[tracer]
proto = "udp"
addr = "172.16.33.46:5140"
tag = "mypro"
[bm]
addr = "0.0.0.0:9001"
maxListen = 10
timeout = "1s"
[mysql]
addr = "127.0.0.1:3306"
dsn = "root:123456@tcp(127.0.0.1:3306)/bilibili_coupon?timeout=3s&readTimeout=3s&writeTimeout=3s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 20
idle = 10
idleTimeout ="4h"
queryTimeout = "1s"
execTimeout = "1s"
tranTimeout = "2s"
[mysql.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[memcache]
name = "coupon"
proto = "tcp"
addr = ""
idle = 5
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "24h"
[properties]
maxRetries = 30
bangumiNotifyURL = "http://bangumi.bilibili.com/pay/inner/notify_ticket"
checkInUseCouponCron = "0 * */1 * * ?"
CheckInUseCouponCartoonCron = "0 * */1 * * ?"
notifyTimeInterval = "5s"
[httpClient]
dial = "1s"
timeout = "3s"
keepAlive = "60s"
timer = 1000
key = "zxnh4k92dwe61t27"
secret = "dnu3bwpxyswqwf1ixpsczthury1nqiew"
[httpClient.breaker]
window = "3s"
sleep = "500ms"
bucket = 10
ratio = 0.5
request = 100
switchoff = false
[dataBus]
[dataBus.couponBinlog]
key = "4ba46ba31f9a44ef"
secret = "e4c5a7fce28695209e6b4f0af8cf91c5"
group = "CouponBinlog-MainAccount-S"
topic = "CouponBinlog-T"
action = "sub"
offset = "old"
buffer = 1024
name = "vip-job"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 1
active = 1
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[newYearConf]
# 活动id
actID = 1

View File

@@ -0,0 +1,49 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"go-common/app/job/main/coupon/conf"
"go-common/app/job/main/coupon/http"
"go-common/app/job/main/coupon/service"
"go-common/library/log"
)
var (
srv *service.Service
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
// init log
log.Init(conf.Conf.Log)
defer log.Close()
log.Info("coupon start")
// service init
srv = service.New(conf.Conf)
http.Init(conf.Conf, srv)
// init pprof conf.Conf.Perf
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("coupon get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("coupon exit")
srv.Close()
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,39 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/job/main/coupon/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/memcache:go_default_library",
"//library/conf:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/netutil:go_default_library",
"//library/queue/databus:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml: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,109 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/memcache"
"go-common/library/conf"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/netutil"
"go-common/library/queue/databus"
xtime "go-common/library/time"
"github.com/BurntSushi/toml"
)
// global var
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config config set
type Config struct {
// base
// elk
Log *log.Config
// http
BM *bm.ServerConfig
// memcache
Memcache *memcache.Config
// MySQL
MySQL *sql.Config
// Databus
DataBus *DataSource
// Properties
Properties *Properties
// Backoff retries config
Backoff *netutil.BackoffConfig
// http client
HTTPClient *bm.ClientConfig
NewYearConf *NewYearConf
}
// NewYearConf .
type NewYearConf struct {
ActID int64
}
// DataSource databus source
type DataSource struct {
CouponBinlog *databus.Config
}
// Properties def.
type Properties struct {
MaxRetries int
BangumiNotifyURL string
CheckInUseCouponCron string
CheckInUseCouponCartoonCron string
NotifyTimeInterval xtime.Duration
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init conf
func Init() error {
if confPath != "" {
return local()
}
return remote()
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
if err = load(); err != nil {
return
}
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}

View File

@@ -0,0 +1,63 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"memcache_test.go",
"mysql_test.go",
"notify_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",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
"//vendor/gopkg.in/h2non/gock.v1:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"memcache.go",
"mysql.go",
"notify.go",
],
importpath = "go-common/app/job/main/coupon/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/coupon/conf:go_default_library",
"//app/job/main/coupon/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster: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"],
)

View File

@@ -0,0 +1,52 @@
package dao
import (
"context"
"go-common/app/job/main/coupon/conf"
"go-common/library/cache/memcache"
xsql "go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
)
// Dao dao
type Dao struct {
c *conf.Config
db *xsql.DB
client *bm.Client
mc *memcache.Pool
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
db: xsql.NewMySQL(c.MySQL),
client: bm.NewClient(c.HTTPClient),
mc: memcache.NewPool(c.Memcache),
}
return
}
// Close close the resource.
func (d *Dao) Close() {
d.mc.Close()
d.db.Close()
}
// Ping dao ping
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.db.Ping(c); err != nil {
return
}
err = d.pingMC(c)
return
}
// pingMc ping
func (d *Dao) pingMC(c context.Context) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: 60}
return conn.Set(&item)
}

View File

@@ -0,0 +1,66 @@
package dao
import (
"context"
"flag"
"os"
"strings"
"testing"
"github.com/smartystreets/goconvey/convey"
"go-common/app/job/main/coupon/conf"
gock "gopkg.in/h2non/gock.v1"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account.coupon-job")
flag.Set("conf_token", "ddd3dd8ed06d0ca10b6c8f122581d035")
flag.Set("tree_id", "23029")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../cmd/coupon-job.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
d.client.SetTransport(gock.DefaultTransport)
os.Exit(m.Run())
}
func httpMock(method, url string) *gock.Request {
r := gock.New(url)
r.Method = strings.ToUpper(method)
return r
}
func TestDaoPing(t *testing.T) {
convey.Convey("TestDaoPing", t, func(convCtx convey.C) {
err := d.Ping(context.Background())
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaopingMC(t *testing.T) {
convey.Convey("TestDaopingMC", t, func(convCtx convey.C) {
err := d.pingMC(context.Background())
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,70 @@
package dao
import (
"context"
"fmt"
gmc "go-common/library/cache/memcache"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_prefixCoupons = "cs:%d:%d"
_prefixCouponBlance = "cbl:%d:%d"
_prefixprizeCard = "nypc:%d:%d:%d" // 元旦活动卡片
_prefixprizeCards = "nypcs:%d:%d" // 元旦活动卡片列表
)
func couponBalancesKey(mid int64, ct int8) string {
return fmt.Sprintf(_prefixCouponBlance, ct, mid)
}
// DelCouponBalancesCache delete user coupons blance cache.
func (d *Dao) DelCouponBalancesCache(c context.Context, mid int64, ct int8) (err error) {
return d.delCache(c, couponBalancesKey(mid, ct))
}
func couponsKey(mid int64, ct int8) string {
return fmt.Sprintf(_prefixCoupons, ct, mid)
}
func prizeCardKey(mid, actID int64, cardType int8) string {
return fmt.Sprintf(_prefixprizeCard, mid, actID, cardType)
}
func prizeCardsKey(mid, actID int64) string {
return fmt.Sprintf(_prefixprizeCards, mid, actID)
}
// DelCouponsCache delete user coupons cache.
func (d *Dao) DelCouponsCache(c context.Context, mid int64, ct int8) (err error) {
return d.delCache(c, couponsKey(mid, ct))
}
// DelCache del cache.
func (d *Dao) delCache(c context.Context, key string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == gmc.ErrNotFound {
log.Warn("delCache ErrNotFound(%s)", key)
err = nil
} else {
err = errors.Wrapf(err, "mc.Delete(%s)", key)
}
}
return
}
// DelPrizeCardKey .
func (d *Dao) DelPrizeCardKey(c context.Context, mid, actID int64, cardType int8) (err error) {
return d.delCache(c, prizeCardKey(mid, actID, cardType))
}
// DelPrizeCardsKey .
func (d *Dao) DelPrizeCardsKey(c context.Context, mid, actID int64) (err error) {
log.Warn("DelPrizeCardsKey(%s)", prizeCardsKey(mid, actID))
return d.delCache(c, prizeCardsKey(mid, actID))
}

View File

@@ -0,0 +1,153 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaocouponBalancesKey(t *testing.T) {
convey.Convey("couponBalancesKey", t, func(convCtx convey.C) {
var (
mid = int64(1)
ct = int8(2)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := couponBalancesKey(mid, ct)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldEqual, "cbl:2:1")
})
})
})
}
func TestDaoDelCouponBalancesCache(t *testing.T) {
convey.Convey("DelCouponBalancesCache", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
ct = int8(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.DelCouponBalancesCache(c, mid, ct)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaocouponsKey(t *testing.T) {
convey.Convey("couponsKey", t, func(convCtx convey.C) {
var (
mid = int64(22)
ct = int8(33)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := couponsKey(mid, ct)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldEqual, "cs:33:22")
})
})
})
}
func TestDaoprizeCardKey(t *testing.T) {
convey.Convey("prizeCardKey", t, func(convCtx convey.C) {
var (
mid = int64(22)
actID = int64(1)
cardType = int8(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := prizeCardKey(mid, actID, cardType)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldEqual, "nypc:22:1:0")
})
})
})
}
func TestDaoprizeCardsKey(t *testing.T) {
convey.Convey("prizeCardsKey", t, func(convCtx convey.C) {
var (
mid = int64(33)
actID = int64(1)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := prizeCardsKey(mid, actID)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldEqual, "nypcs:33:1")
})
})
})
}
func TestDaoDelCouponsCache(t *testing.T) {
convey.Convey("DelCouponsCache", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(1)
ct = int8(1)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.DelCouponsCache(c, mid, ct)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaodelCache(t *testing.T) {
convey.Convey("delCache", t, func(convCtx convey.C) {
var (
c = context.Background()
key = "1"
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.delCache(c, key)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
err = d.delCache(c, "")
convCtx.Convey("Then err should be not nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoDelPrizeCardKey(t *testing.T) {
convey.Convey("DelPrizeCardKey", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
actID = int64(0)
cardType = int8(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.DelPrizeCardKey(c, mid, actID, cardType)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelPrizeCardsKey(t *testing.T) {
convey.Convey("DelPrizeCardsKey", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
actID = int64(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.DelPrizeCardsKey(c, mid, actID)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,354 @@
package dao
import (
"bytes"
"context"
xsql "database/sql"
"fmt"
"strconv"
"time"
"go-common/app/job/main/coupon/model"
"go-common/library/database/sql"
"go-common/library/xstr"
"github.com/pkg/errors"
)
const (
_updateStateSQL = "UPDATE `coupon_info_%02d` SET `state` = ?,`use_ver` = ?,`ver` = ? WHERE `coupon_token` = ? AND `ver` = ?;"
_couponByTokenSQL = "SELECT `id`,`coupon_token`,`mid`,`state`,`start_time`,`expire_time`,`origin`,`coupon_type`,`order_no`,`oid`,`remark`,`use_ver`,`ver`,`ctime`,`mtime` FROM `coupon_info_%02d` WHERE `coupon_token` = ?;"
_couponsSQL = "SELECT `id`,`coupon_token`,`mid`,`state`,`start_time`,`expire_time`,`origin`,`coupon_type`,`order_no`,`oid`,`remark`,`use_ver`,`ver`,`ctime`,`mtime` FROM `coupon_info_%02d` WHERE `state` = ? AND `mtime` > ?;"
_addCouponChangeLogSQL = "INSERT INTO `coupon_change_log_%02d` (`coupon_token`,`mid`,`state`,`ctime`) VALUES(?,?,?,?);"
// balance coupon
_byOrderNoSQL = "SELECT `id`,`order_no`,`mid`,`count`,`state`,`coupon_type`,`third_trade_no`,`remark`,`tips`,`use_ver`,`ver`,`ctime`,`mtime` FROM `coupon_order` WHERE `order_no` = ?;"
_updateOrderSQL = "UPDATE `coupon_order` SET `state` = ?,`use_ver` =?,`ver` = ? WHERE `order_no` = ? AND `ver` = ?;"
_addOrderLogSQL = "INSERT INTO `coupon_order_log`(`order_no`,`mid`,`state`,`ctime`)VALUES(?,?,?,?);"
_consumeCouponLogSQL = "SELECT `id`,`order_no`,`mid`,`batch_token`,`balance`,`change_balance`,`change_type`,`ctime`,`mtime` FROM `coupon_balance_change_log_%02d` WHERE `order_no` = ? AND `change_type` = ?;"
_byMidAndBatchTokenSQL = "SELECT `id`,`batch_token`,`mid`,`balance`,`start_time`,`expire_time`,`origin`,`coupon_type`,`ver`,`ctime`,`mtime` FROM `coupon_balance_info_%02d` WHERE `mid` = ? AND `batch_token` = ? ;"
_inPayOrderSQL = "SELECT `id`,`order_no`,`mid`,`count`,`state`,`coupon_type`,`third_trade_no`,`remark`,`tips`,`use_ver`,`ver`,`ctime`,`mtime` FROM `coupon_order` WHERE `state` = ? AND `mtime` > ?;"
_batchUpdateBalance = "UPDATE `coupon_balance_info_%02d` SET `ver` =`ver` + 1, `balance` = CASE id"
_addBalanceLogSQL = "INSERT INTO `coupon_balance_change_log_%02d`(`order_no`,`mid`,`batch_token`,`balance`,`change_balance`,`change_type`,`ctime`)VALUES "
_couponBlancesSQL = "SELECT `id`,`batch_token`,`mid`,`balance`,`start_time`,`expire_time`,`origin`,`coupon_type`,`ver`,`ctime`,`mtime` FROM `coupon_balance_info_%02d` WHERE `mid` = ? AND `coupon_type` = ?;"
_updateBlanceSQL = "UPDATE `coupon_balance_info_%02d` SET `balance` = ?,`ver` = `ver` + 1 WHERE `id` = ? AND `ver` = ?;"
_updateUserCardSQL = "UPDATE coupon_user_card SET state=? WHERE mid=? AND coupon_token=? AND batch_token=?"
)
func hitInfo(mid int64) int64 {
return mid % 100
}
func hitChangeLog(mid int64) int64 {
return mid % 100
}
func hitUser(mid int64) int64 {
return mid % 10
}
func hitUserLog(mid int64) int64 {
return mid % 10
}
// UpdateCoupon update coupon in use.
func (d *Dao) UpdateCoupon(c context.Context, tx *sql.Tx, mid int64, state int8, useVer int64, ver int64, couponToken string) (a int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_updateStateSQL, hitInfo(mid)), state, useVer, ver+1, couponToken, ver); err != nil {
err = errors.WithStack(err)
return
}
if a, err = res.RowsAffected(); err != nil {
err = errors.WithStack(err)
return
}
return
}
// CouponInfo coupon info.
func (d *Dao) CouponInfo(c context.Context, mid int64, token string) (r *model.CouponInfo, err error) {
var row *sql.Row
r = &model.CouponInfo{}
row = d.db.QueryRow(c, fmt.Sprintf(_couponByTokenSQL, hitInfo(mid)), token)
if err = row.Scan(&r.ID, &r.CouponToken, &r.Mid, &r.State, &r.StartTime, &r.ExpireTime, &r.Origin, &r.CouponType, &r.OrderNO, &r.Oid, &r.Remark,
&r.UseVer, &r.Ver, &r.CTime, &r.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
r = nil
return
}
err = errors.WithStack(err)
return
}
return
}
// CouponList query .
func (d *Dao) CouponList(c context.Context, index int64, state int8, t time.Time) (res []*model.CouponInfo, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_couponsSQL, hitInfo(index)), state, t); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
r := &model.CouponInfo{}
if err = rows.Scan(&r.ID, &r.CouponToken, &r.Mid, &r.State, &r.StartTime, &r.ExpireTime, &r.Origin, &r.CouponType, &r.OrderNO, &r.Oid, &r.Remark,
&r.UseVer, &r.Ver, &r.CTime, &r.MTime); err != nil {
err = errors.WithStack(err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}
//InsertPointHistory .
func (d *Dao) InsertPointHistory(c context.Context, tx *sql.Tx, l *model.CouponChangeLog) (a int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_addCouponChangeLogSQL, hitChangeLog(l.Mid)), l.CouponToken, l.Mid, l.State, l.Ctime); err != nil {
err = errors.WithStack(err)
return
}
if a, err = res.RowsAffected(); err != nil {
err = errors.WithStack(err)
}
return
}
// BeginTran begin transaction.
func (d *Dao) BeginTran(c context.Context) (*sql.Tx, error) {
return d.db.Begin(c)
}
// ByOrderNo query order by order no.
func (d *Dao) ByOrderNo(c context.Context, orderNo string) (r *model.CouponOrder, err error) {
var row *sql.Row
r = &model.CouponOrder{}
row = d.db.QueryRow(c, _byOrderNoSQL, orderNo)
if err = row.Scan(&r.ID, &r.OrderNo, &r.Mid, &r.Count, &r.State, &r.CouponType, &r.ThirdTradeNo, &r.Remark, &r.Tips, &r.UseVer, &r.Ver, &r.Ctime, &r.Mtime); err != nil {
if err == sql.ErrNoRows {
err = nil
r = nil
return
}
err = errors.WithStack(err)
return
}
return
}
// UpdateOrderState update order state.
func (d *Dao) UpdateOrderState(c context.Context, tx *sql.Tx, mid int64, state int8, useVer int64, ver int64, orderNo string) (a int64, err error) {
var res xsql.Result
if res, err = tx.Exec(_updateOrderSQL, state, useVer, ver+1, orderNo, ver); err != nil {
err = errors.WithStack(err)
return
}
if a, err = res.RowsAffected(); err != nil {
err = errors.WithStack(err)
return
}
return
}
// AddOrderLog add order log.
func (d *Dao) AddOrderLog(c context.Context, tx *sql.Tx, o *model.CouponOrderLog) (a int64, err error) {
var res xsql.Result
if res, err = tx.Exec(_addOrderLogSQL, o.OrderNo, o.Mid, o.State, o.Ctime); err != nil {
err = errors.WithStack(err)
return
}
if a, err = res.RowsAffected(); err != nil {
err = errors.WithStack(err)
}
return
}
// ConsumeCouponLog consume coupon log.
func (d *Dao) ConsumeCouponLog(c context.Context, mid int64, orderNo string, ct int8) (rs []*model.CouponBalanceChangeLog, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_consumeCouponLogSQL, hitUserLog(mid)), orderNo, ct); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
r := &model.CouponBalanceChangeLog{}
if err = rows.Scan(&r.ID, &r.OrderNo, &r.Mid, &r.BatchToken, &r.Balance, &r.ChangeBalance, &r.ChangeType, &r.Ctime, &r.Mtime); err != nil {
err = errors.WithStack(err)
rs = nil
return
}
rs = append(rs, r)
}
err = rows.Err()
return
}
// ByMidAndBatchToken query coupon by batch token and mid.
func (d *Dao) ByMidAndBatchToken(c context.Context, mid int64, batchToken string) (r *model.CouponBalanceInfo, err error) {
var row *sql.Row
r = &model.CouponBalanceInfo{}
row = d.db.QueryRow(c, fmt.Sprintf(_byMidAndBatchTokenSQL, hitUser(mid)), mid, batchToken)
if err = row.Scan(&r.ID, &r.BatchToken, &r.Mid, &r.Balance, &r.StartTime, &r.ExpireTime, &r.Origin, &r.CouponType, &r.Ver, &r.CTime, &r.MTime); err != nil {
if err == sql.ErrNoRows {
err = nil
r = nil
return
}
err = errors.WithStack(err)
return
}
return
}
// UpdateBlance update blance.
func (d *Dao) UpdateBlance(c context.Context, tx *sql.Tx, id int64, mid int64, ver int64, balance int64) (a int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_updateBlanceSQL, hitUser(mid)), balance, id, ver); err != nil {
err = errors.WithStack(err)
return
}
if a, err = res.RowsAffected(); err != nil {
err = errors.WithStack(err)
return
}
return
}
// OrderInPay order in pay.
func (d *Dao) OrderInPay(c context.Context, state int8, t time.Time) (res []*model.CouponOrder, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, _inPayOrderSQL, state, t); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
r := &model.CouponOrder{}
if err = rows.Scan(&r.ID, &r.OrderNo, &r.Mid, &r.Count, &r.State, &r.CouponType, &r.ThirdTradeNo, &r.Remark, &r.Tips, &r.UseVer,
&r.Ver, &r.Ctime, &r.Mtime); err != nil {
if err == sql.ErrNoRows {
err = nil
res = nil
return
}
err = errors.WithStack(err)
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// BatchUpdateBlance batch update blance.
func (d *Dao) BatchUpdateBlance(c context.Context, tx *sql.Tx, mid int64, blances []*model.CouponBalanceInfo) (a int64, err error) {
var (
res xsql.Result
buf bytes.Buffer
ids []int64
)
buf.WriteString(fmt.Sprintf(_batchUpdateBalance, hitUser(mid)))
for _, v := range blances {
buf.WriteString(" WHEN ")
buf.WriteString(strconv.FormatInt(v.ID, 10))
buf.WriteString(" THEN ")
buf.WriteString(strconv.FormatInt(v.Balance, 10))
ids = append(ids, v.ID)
}
buf.WriteString(" END WHERE `id` in (")
buf.WriteString(xstr.JoinInts(ids))
buf.WriteString(") AND `ver` = CASE id ")
for _, v := range blances {
buf.WriteString(" WHEN ")
buf.WriteString(strconv.FormatInt(v.ID, 10))
buf.WriteString(" THEN ")
buf.WriteString(strconv.FormatInt(v.Ver, 10))
}
buf.WriteString(" END;")
if res, err = tx.Exec(buf.String()); err != nil {
err = errors.WithStack(err)
return
}
if a, err = res.RowsAffected(); err != nil {
err = errors.WithStack(err)
return
}
return
}
// BatchInsertBlanceLog Batch Insert Balance log
func (d *Dao) BatchInsertBlanceLog(c context.Context, tx *sql.Tx, mid int64, ls []*model.CouponBalanceChangeLog) (a int64, err error) {
var (
buf bytes.Buffer
res xsql.Result
sql string
)
buf.WriteString(fmt.Sprintf(_addBalanceLogSQL, hitUserLog(mid)))
for _, v := range ls {
buf.WriteString("('")
buf.WriteString(v.OrderNo)
buf.WriteString("',")
buf.WriteString(strconv.FormatInt(v.Mid, 10))
buf.WriteString(",'")
buf.WriteString(v.BatchToken)
buf.WriteString("',")
buf.WriteString(strconv.FormatInt(v.Balance, 10))
buf.WriteString(",")
buf.WriteString(strconv.FormatInt(v.ChangeBalance, 10))
buf.WriteString(",")
buf.WriteString(strconv.Itoa(int(v.ChangeType)))
buf.WriteString(",'")
buf.WriteString(fmt.Sprintf("%v", v.Ctime.Time().Format("2006-01-02 15:04:05")))
buf.WriteString("'),")
}
sql = buf.String()
if res, err = tx.Exec(sql[0 : len(sql)-1]); err != nil {
err = errors.WithStack(err)
return
}
if a, err = res.RowsAffected(); err != nil {
err = errors.WithStack(err)
}
return
}
// BlanceList user balance by mid.
func (d *Dao) BlanceList(c context.Context, mid int64, ct int8) (res []*model.CouponBalanceInfo, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_couponBlancesSQL, hitUser(mid)), mid, ct); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
r := &model.CouponBalanceInfo{}
if err = rows.Scan(&r.ID, &r.BatchToken, &r.Mid, &r.Balance, &r.StartTime, &r.ExpireTime, &r.Origin, &r.CouponType, &r.Ver, &r.CTime, &r.MTime); err != nil {
err = errors.WithStack(err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// UpdateUserCard .
func (d *Dao) UpdateUserCard(c context.Context, mid int64, state int8, couponToken, batchToken string) (a int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _updateUserCardSQL, state, mid, couponToken, batchToken); err != nil {
err = errors.WithStack(err)
return
}
if a, err = res.RowsAffected(); err != nil {
err = errors.WithStack(err)
return
}
return
}

View File

@@ -0,0 +1,413 @@
package dao
import (
"context"
// "database/sql"
"go-common/app/job/main/coupon/model"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaohitInfo(t *testing.T) {
convey.Convey("hitInfo", t, func(convCtx convey.C) {
var (
mid = int64(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := hitInfo(mid)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaohitChangeLog(t *testing.T) {
convey.Convey("hitChangeLog", t, func(convCtx convey.C) {
var (
mid = int64(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := hitChangeLog(mid)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaohitUser(t *testing.T) {
convey.Convey("hitUser", t, func(convCtx convey.C) {
var (
mid = int64(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := hitUser(mid)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaohitUserLog(t *testing.T) {
convey.Convey("hitUserLog", t, func(convCtx convey.C) {
var (
mid = int64(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := hitUserLog(mid)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
// go test -test.v -test.run TestDaoUpdateCoupon
func TestDaoUpdateCoupon(t *testing.T) {
convey.Convey("UpdateCoupon", t, func(convCtx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(context.Background())
mid = int64(1)
state = int8(1)
useVer = int64(11)
ver = int64(2)
couponToken = "729792667120180402161647"
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
a, err := d.UpdateCoupon(c, tx, mid, state, useVer, ver, couponToken)
if err == nil {
if err = tx.Commit(); err != nil {
tx.Rollback()
}
} else {
tx.Rollback()
}
convCtx.Convey("Then err should be nil.a should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(a, convey.ShouldBeGreaterThanOrEqualTo, 0)
})
})
})
}
func TestDaoCouponInfo(t *testing.T) {
convey.Convey("CouponInfo", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
token = ""
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
r, err := d.CouponInfo(c, mid, token)
convCtx.Convey("Then err should be nil.r should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(r, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoCouponList(t *testing.T) {
convey.Convey("CouponList", t, func(convCtx convey.C) {
var (
c = context.Background()
index = int64(0)
state = int8(0)
no, _ = time.Parse("2006-01-02 15:04:05", "2018-12-27 17:28:51")
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := d.CouponList(c, index, state, no)
convCtx.Convey("Then err should be nil.res should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldBeNil)
})
})
})
}
func TestDaoInsertPointHistory(t *testing.T) {
convey.Convey("InsertPointHistory", t, func(convCtx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(context.Background())
l = &model.CouponChangeLog{}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
a, err := d.InsertPointHistory(c, tx, l)
if err == nil {
if err = tx.Commit(); err != nil {
tx.Rollback()
}
} else {
tx.Rollback()
}
convCtx.Convey("Then err should be nil.a should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(a, convey.ShouldBeGreaterThanOrEqualTo, 0)
})
})
})
}
func TestDaoBeginTran(t *testing.T) {
convey.Convey("BeginTran", t, func(convCtx convey.C) {
var (
c = context.Background()
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := d.BeginTran(c)
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoByOrderNo(t *testing.T) {
convey.Convey("ByOrderNo", t, func(convCtx convey.C) {
var (
c = context.Background()
orderNo = ""
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
r, err := d.ByOrderNo(c, orderNo)
convCtx.Convey("Then err should be nil.r should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(r, convey.ShouldBeNil)
})
})
})
}
func TestDaoUpdateOrderState(t *testing.T) {
convey.Convey("UpdateOrderState", t, func(convCtx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(context.Background())
mid = int64(0)
state = int8(0)
useVer = int64(0)
ver = int64(0)
orderNo = ""
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
a, err := d.UpdateOrderState(c, tx, mid, state, useVer, ver, orderNo)
if err == nil {
if err = tx.Commit(); err != nil {
tx.Rollback()
}
} else {
tx.Rollback()
}
convCtx.Convey("Then err should be nil.a should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(a, convey.ShouldBeGreaterThanOrEqualTo, 0)
})
})
})
}
func TestDaoAddOrderLog(t *testing.T) {
convey.Convey("AddOrderLog", t, func(convCtx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(context.Background())
o = &model.CouponOrderLog{}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
a, err := d.AddOrderLog(c, tx, o)
if err == nil {
if err = tx.Commit(); err != nil {
tx.Rollback()
}
} else {
tx.Rollback()
}
convCtx.Convey("Then err should be nil.a should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(a, convey.ShouldBeGreaterThanOrEqualTo, 0)
})
})
})
}
func TestDaoConsumeCouponLog(t *testing.T) {
convey.Convey("ConsumeCouponLog", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
orderNo = ""
ct = int8(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
rs, err := d.ConsumeCouponLog(c, mid, orderNo, ct)
convCtx.Convey("Then err should be nil.rs should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(rs, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoByMidAndBatchToken(t *testing.T) {
convey.Convey("ByMidAndBatchToken", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
batchToken = ""
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
r, err := d.ByMidAndBatchToken(c, mid, batchToken)
convCtx.Convey("Then err should be nil.r should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(r, convey.ShouldBeNil)
})
})
})
}
func TestDaoUpdateBlance(t *testing.T) {
convey.Convey("UpdateBlance", t, func(convCtx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(context.Background())
id = int64(0)
mid = int64(0)
ver = int64(0)
balance = int64(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
a, err := d.UpdateBlance(c, tx, id, mid, ver, balance)
if err == nil {
if err = tx.Commit(); err != nil {
tx.Rollback()
}
} else {
tx.Rollback()
}
convCtx.Convey("Then err should be nil.a should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(a, convey.ShouldBeGreaterThanOrEqualTo, 0)
})
})
})
}
func TestDaoOrderInPay(t *testing.T) {
convey.Convey("OrderInPay", t, func(convCtx convey.C) {
var (
c = context.Background()
state = int8(0)
no = time.Now()
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := d.OrderInPay(c, state, no)
convCtx.Convey("Then err should be nil.res should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldBeNil)
})
})
})
}
func TestDaoBatchUpdateBlance(t *testing.T) {
convey.Convey("BatchUpdateBlance", t, func(convCtx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(context.Background())
mid = int64(0)
blances = []*model.CouponBalanceInfo{}
blance = &model.CouponBalanceInfo{}
)
blances = append(blances, blance)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
a, err := d.BatchUpdateBlance(c, tx, mid, blances)
if err == nil {
if err = tx.Commit(); err != nil {
tx.Rollback()
}
} else {
tx.Rollback()
}
convCtx.Convey("Then err should be nil.a should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(a, convey.ShouldBeGreaterThanOrEqualTo, 0)
})
})
})
}
func TestDaoBatchInsertBlanceLog(t *testing.T) {
convey.Convey("BatchInsertBlanceLog", t, func(convCtx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(context.Background())
mid = int64(0)
ls = []*model.CouponBalanceChangeLog{}
l = &model.CouponBalanceChangeLog{}
)
ls = append(ls, l)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
a, err := d.BatchInsertBlanceLog(c, tx, mid, ls)
if err == nil {
if err = tx.Commit(); err != nil {
tx.Rollback()
}
} else {
tx.Rollback()
}
convCtx.Convey("Then err should be nil.a should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(a, convey.ShouldBeGreaterThanOrEqualTo, 0)
})
})
})
}
func TestDaoBlanceList(t *testing.T) {
convey.Convey("BlanceList", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
ct = int8(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := d.BlanceList(c, mid, ct)
convCtx.Convey("Then err should be nil.res should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldBeNil)
})
})
})
}
func TestDaoUpdateUserCard(t *testing.T) {
convey.Convey("UpdateUserCard", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
state = int8(0)
couponToken = ""
batchToken = ""
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
a, err := d.UpdateUserCard(c, mid, state, couponToken, batchToken)
convCtx.Convey("Then err should be nil.a should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(a, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoClose(t *testing.T) {
convey.Convey("TestDaoClose", t, func(convCtx convey.C) {
d.Close()
})
}

View File

@@ -0,0 +1,42 @@
package dao
import (
"context"
"fmt"
"net/url"
"go-common/app/job/main/coupon/model"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_defPltform = "inner"
)
// NotifyRet notify use coupon ret.
func (d *Dao) NotifyRet(c context.Context, notifyURL string, ticketNO string, orderNO string, ip string) (data *model.CallBackRet, err error) {
params := url.Values{}
params.Set("ticket_no", ticketNO)
params.Set("order_no", orderNO)
params.Set("platform", _defPltform)
params.Set("build", "0")
log.Info("call service notify ret %s", notifyURL+"?"+params.Encode())
var res struct {
Code int `json:"code"`
Message string `json:"message"`
Data *model.CallBackRet `json:"result"`
}
if err = d.client.Post(c, notifyURL, ip, params, &res); err != nil {
err = errors.Wrapf(err, "call service(%s) error", notifyURL+"?"+params.Encode())
return
}
if res.Code != 0 {
err = errors.WithStack(fmt.Errorf("call service(%s) error, res code is not 0, resp:%v", notifyURL+"?"+params.Encode(), res))
return
}
data = res.Data
log.Info("call service notify ret suc req(%s) data(%v) ", params.Encode(), data)
return
}

View File

@@ -0,0 +1,45 @@
package dao
import (
"context"
"testing"
gock "gopkg.in/h2non/gock.v1"
"github.com/smartystreets/goconvey/convey"
)
// go test -test.v -test.run TestDaoNotifyRet
func TestDaoNotifyRet(t *testing.T) {
convey.Convey("NotifyRet", t, func(convCtx convey.C) {
var (
c = context.Background()
notifyURL = "http://bangumi.bilibili.com/pay/inner/notify_ticket"
ticketNO = "706821058124120180326154"
orderNO = "20180326153703795"
ip = "127.0.0.1"
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
data, err := d.NotifyRet(c, notifyURL, ticketNO, orderNO, ip)
t.Logf("data (%v)", data)
convCtx.Convey("Then err should be nil.data should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(data, convey.ShouldNotBeNil)
})
convCtx.Convey("res.Code!=0 Then err should not be nil.data should be nil.", func(convCtx convey.C) {
defer gock.OffAll()
httpMock("POST", notifyURL).Reply(200).JSON(`{"code":-1}`)
data, err = d.NotifyRet(c, notifyURL, ticketNO, orderNO, ip)
convCtx.So(err, convey.ShouldNotBeNil)
convCtx.So(data, convey.ShouldBeNil)
})
convCtx.Convey("call service error Then err should not be nil.data should be nil.", func(convCtx convey.C) {
defer gock.OffAll()
httpMock("POST", "").Reply(-400).JSON(`{"code":-1}`)
data, err = d.NotifyRet(c, notifyURL, ticketNO, orderNO, ip)
convCtx.So(err, convey.ShouldNotBeNil)
convCtx.So(data, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,36 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["http.go"],
importpath = "go-common/app/job/main/coupon/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/coupon/conf:go_default_library",
"//app/job/main/coupon/model:go_default_library",
"//app/job/main/coupon/service:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster: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,74 @@
package http
import (
"net/http"
"go-common/app/job/main/coupon/conf"
"go-common/app/job/main/coupon/model"
"go-common/app/job/main/coupon/service"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
var (
svc *service.Service
)
// Init init
func Init(c *conf.Config, s *service.Service) {
svc = s
// init router
engineInner := bm.DefaultServer(c.BM)
innerRouter(engineInner)
if err := engineInner.Start(); err != nil {
log.Error("bm.DefaultServer error(%v)", err)
panic(err)
}
}
// innerRouter init inner router api path.
func innerRouter(e *bm.Engine) {
//init api
e.GET("/monitor/ping", ping)
e.GET("/deliver/success", cartoonDeliverSuccess)
}
// ping check server ok.
func ping(c *bm.Context) {
if err := svc.Ping(c); err != nil {
log.Error("coupon http service ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func cartoonDeliverSuccess(c *bm.Context) {
var (
err error
o *model.CouponOrder
)
arg := new(struct {
OrderNo string `form:"order_no"`
State int8 `form:"state"`
Ver int64 `form:"ver"`
IsPaid int8 `form:"is_paid"`
})
if err = c.Bind(arg); err != nil {
log.Error("add coupon page %+v", err)
return
}
if o, err = svc.ByOrderNo(c, arg.OrderNo); err != nil {
c.JSON(nil, err)
return
}
if o == nil {
c.JSON(nil, ecode.RequestErr)
return
}
if err = svc.UpdateOrderState(c, o, arg.State, &model.CallBackRet{Ver: arg.Ver, IsPaid: arg.IsPaid}); err != nil {
log.Error("svc.UpdateOrderState(%s) %+v", arg.OrderNo, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}

View File

@@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["coupon.go"],
importpath = "go-common/app/job/main/coupon/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//library/time: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,178 @@
package model
import (
"encoding/json"
xtime "go-common/library/time"
)
// coupon state.
const (
NotUsed = iota
InUse
Used
Expire
)
// coupon type.
const (
BangumiVideo = iota + 1
Cartoon
)
// call back status
const (
Unpaid = iota
PaidSuccess
)
// coupon state.
const (
WaitPay = iota
InPay
PaySuccess
PayFaild
)
// blance change type
const (
VipSalary int8 = iota + 1
SystemAdminSalary
Consume
ConsumeFaildBack
)
//allowance origin
const (
AllowanceNone = iota
AllowanceSystemAdmin
AllowanceBusinessReceive
AllowanceBusinessNewYear
)
// CouponInfo coupon info.
type CouponInfo struct {
ID int64 `json:"_"`
CouponToken string `json:"coupon_token"`
Mid int64 `json:"mid"`
State int64 `json:"state"`
StartTime int64 `json:"start_time"`
ExpireTime int64 `json:"expire_time"`
Origin int64 `json:"origin"`
CouponType int64 `json:"coupon_type"`
OrderNO string `json:"order_no"`
Ver int64 `json:"ver"`
Oid int64 `json:"oid"`
Remark string `json:"remark"`
UseVer int64 `json:"use_ver"`
CTime xtime.Time `json:"-"`
MTime xtime.Time `json:"-"`
}
// MsgCanal canal message struct.
type MsgCanal struct {
Action string `json:"action"`
Table string `json:"table"`
New json.RawMessage `json:"new"`
Old json.RawMessage `json:"old"`
}
// CallBackRet .
type CallBackRet struct {
Ver int64 `json:"ver"`
IsPaid int8 `json:"is_paid"`
}
// NotifyParam notify param.
type NotifyParam struct {
CouponToken string `json:"coupon_token"`
Mid int64 `json:"mid"`
NotifyURL string `json:"notify_url"`
NotifyCount int `json:"count"`
Type int64 `json:"type"`
}
// CouponChangeLog coupon change log.
type CouponChangeLog struct {
ID int64 `json:"-"`
CouponToken string `json:"coupon_token"`
Mid int64 `json:"mid"`
State int8 `json:"state"`
Ctime xtime.Time `json:"ctime"`
Mtime xtime.Time `json:"mtime"`
}
// CouponOrder coupon order info.
type CouponOrder struct {
ID int64 `json:"id"`
OrderNo string `json:"order_no"`
Mid int64 `json:"mid"`
Count int64 `json:"count"`
State int8 `json:"state"`
CouponType int8 `json:"coupon_type"`
ThirdTradeNo string `json:"third_trade_no"`
Remark string `json:"remark"`
Tips string `json:"tips"`
UseVer int64 `json:"use_ver"`
Ver int64 `json:"ver"`
Ctime xtime.Time `json:"-"`
Mtime xtime.Time `json:"-"`
}
// CouponOrderLog coupon order log.
type CouponOrderLog struct {
ID int64 `json:"id"`
OrderNo string `json:"order_no"`
Mid int64 `json:"mid"`
State int8 `json:"state"`
Ctime xtime.Time `json:"ctime"`
Mtime xtime.Time `json:"mtime"`
}
// CouponBalanceChangeLog coupon balance change log.
type CouponBalanceChangeLog struct {
ID int64 `json:"id"`
OrderNo string `json:"order_no"`
Mid int64 `json:"mid"`
BatchToken string `json:"batch_token"`
Balance int64 `json:"balance"`
ChangeBalance int64 `json:"change_balance"`
ChangeType int8 `json:"change_type"`
Ctime xtime.Time `json:"ctime"`
Mtime xtime.Time `json:"mtime"`
}
// CouponBalanceInfo def.
type CouponBalanceInfo struct {
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"_"`
BatchToken string `protobuf:"bytes,2,opt,name=BatchToken,proto3" json:"batch_token"`
Mid int64 `protobuf:"varint,3,opt,name=Mid,proto3" json:"mid"`
Balance int64 `protobuf:"varint,4,opt,name=Balance,proto3" json:"balance"`
StartTime int64 `protobuf:"varint,5,opt,name=StartTime,proto3" json:"start_time"`
ExpireTime int64 `protobuf:"varint,6,opt,name=ExpireTime,proto3" json:"expire_time"`
Origin int64 `protobuf:"varint,7,opt,name=Origin,proto3" json:"origin"`
CouponType int64 `protobuf:"varint,8,opt,name=CouponType,proto3" json:"coupon_type"`
Ver int64 `protobuf:"varint,9,opt,name=Ver,proto3" json:"ver"`
CTime xtime.Time `protobuf:"varint,10,opt,name=CTime,proto3,casttype=go-common/library/time.Time" json:"-"`
MTime xtime.Time `protobuf:"varint,11,opt,name=MTime,proto3,casttype=go-common/library/time.Time" json:"-"`
}
// CouponAllowanceInfo struct .
type CouponAllowanceInfo struct {
ID int64 `gorm:"column:id" json:"id" form:"id"`
CouponToken string `gorm:"column:coupon_token" json:"coupon_token" form:"coupon_token"`
MID int64 `gorm:"column:mid" json:"mid" form:"mid"`
State int8 `gorm:"column:state" json:"state" form:"state"`
StartTime int64 `gorm:"column:start_time" json:"start_time" form:"start_time"`
ExpireTime int64 `gorm:"column:expire_time" json:"expire_time" form:"expire_time"`
Origin int8 `gorm:"column:origin" json:"origin" form:"origin"`
Ver int64 `gorm:"column:ver" json:"ver" form:"ver"`
BatchToken string `gorm:"column:batch_token" json:"batch_token" form:"batch_token"`
OrderNo string `gorm:"column:order_no" json:"order_no" form:"order_no"`
Amount float64 `gorm:"column:amount" json:"amount" form:"amount"`
FullAmount float64 `gorm:"column:full_amount" json:"full_amount" form:"full_amount"`
Ctime xtime.Time `gorm:"column:ctime" json:"-" form:"ctime"`
Mtime xtime.Time `gorm:"column:mtime" json:"-" form:"mtime"`
Remark string `gorm:"column:remark" json:"remark" form:"remark"`
AppID int64 `gorm:"column:app_id" json:"app_id" form:"app_id"`
}

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)
})
}