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,76 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"coinexchange_test.go",
"coinstream_test.go",
"mc_wallet_test.go",
"metal_test.go",
"pub_test.go",
"redis_test.go",
"tx_test.go",
"wallet_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/live/wallet/conf:go_default_library",
"//app/service/live/wallet/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"coinexchange.go",
"coinstream.go",
"dao.go",
"mc_wallet.go",
"metal.go",
"pub.go",
"redis.go",
"tx.go",
"wallet.go",
],
importpath = "go-common/app/service/live/wallet/dao",
tags = ["automanaged"],
deps = [
"//app/service/live/wallet/conf:go_default_library",
"//app/service/live/wallet/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/queue/databus: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,25 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/live/wallet/model"
)
const (
_newCoinExchange = "insert into %s (uid, transaction_id, src_type,src_num,dest_type,dest_num,status,exchange_time) values(?,?,?,?,?,?,?,?)"
)
func getCoinExchangeTableIndex(uid int64) string {
return fmt.Sprintf("%02d", uid%10)
}
func getCoinExchangeTable(uid int64) string {
return fmt.Sprintf("t_coin_exchange_%s", getCoinExchangeTableIndex(uid))
}
func (d *Dao) NewCoinExchangeRecord(c context.Context, record *model.CoinExchangeRecord) (int64, error) {
s := fmt.Sprintf(_newCoinExchange, getCoinExchangeTable(record.Uid))
date := model.GetWalletFormatTime(record.ExchangeTime)
return execSqlWithBindParams(d, c, &s, record.Uid, record.TransactionId, record.SrcType, record.SrcNum, record.DestType, record.DestNum, record.Status, date)
}

View File

@@ -0,0 +1,27 @@
package dao
import (
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/wallet/model"
"testing"
"time"
)
func TestDao_NewCoinExchangeRecord(t *testing.T) {
Convey("New CoinExchange", t, func() {
once.Do(startService)
record := new(model.CoinExchangeRecord)
record.ExchangeTime = time.Now().Unix()
record.Status = 1
record.DestNum = 1
record.DestType = 1
record.SrcType = 2
record.SrcNum = 1
record.TransactionId = "abcdef"
record.Uid = 1
affect, err := d.NewCoinExchangeRecord(ctx, record)
So(affect, ShouldEqual, 1)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,91 @@
package dao
import (
"context"
"crypto/md5"
"fmt"
"go-common/app/service/live/wallet/model"
"go-common/library/log"
"time"
)
const (
_newCoinStremaRecord = "INSERT INTO %s (uid, transaction_id, extend_tid, coin_type, delta_coin_num, org_coin_num, op_result, op_reason, op_type, op_time, reserved2, area, source, reserved3, reserved4,platform,reserved1, reserved5) values(?, ?, ?,?, ?, ?,?, ?, ?,?, ?, ?,?, ?, ?, ?, ?, ?)"
_getCoinStream = "SELECT uid,transaction_id,extend_tid, coin_type,delta_coin_num,org_coin_num,op_result,op_reason,op_type, reserved3,area,reserved2,source,reserved4,platform,reserved1,reserved5 FROM %s WHERE transaction_id = ? order by id desc limit %d,1"
_getCoinStreamByUid = "SELECT uid,transaction_id,extend_tid, coin_type,delta_coin_num,org_coin_num,op_result,op_reason,op_type, reserved3,area,reserved2,source,reserved4, platform , reserved1,reserved5 FROM %s WHERE transaction_id = ? and uid = ? order by id desc limit %d,1"
)
func hexdec(s string) uint64 {
d := uint64(0)
for i := 0; i < len(s); i++ {
x := uint64(s[i])
if x >= 'a' {
x -= 'a' - 'A'
}
d1 := x - '0'
if d1 > 9 {
d1 = 10 + d1 - ('A' - '0')
}
if 0 > d1 || d1 > 15 {
return 0
}
d = (16 * d) + d1
}
return d
}
func getCoinStreamTable(transactionId string) string {
tlen := len(transactionId)
year := hexdec(transactionId[tlen-3 : tlen-1])
if year == 0 {
log.Error("illegal tid : %s", transactionId)
}
year = year + 2000
t := fmt.Sprintf("t_coin_stream_%d%02d", year, hexdec(transactionId[tlen-1:tlen]))
return t
}
func GetTid(serviceType model.ServiceType, v interface{}) string {
s := fmt.Sprintf("%v%s", v, randomString(5))
bizTid := fmt.Sprintf("%x", md5.Sum([]byte(s)))
now := time.Now()
year := now.Year() - 2000
month := int(now.Month())
return fmt.Sprintf("%s%x%02x%x", bizTid, serviceType, year, month)
}
func (d *Dao) NewCoinStreamRecord(c context.Context, record *model.CoinStreamRecord) (int64, error) {
s := fmt.Sprintf(_newCoinStremaRecord, getCoinStreamTable(record.TransactionId))
date := model.GetWalletFormatTime(record.OpTime)
return execSqlWithBindParams(d, c, &s, record.Uid, record.TransactionId, record.ExtendTid, record.CoinType,
record.DeltaCoinNum, record.OrgCoinNum, record.OpResult, record.OpReason, record.OpType, date,
record.BizCode, record.Area, record.Source, record.MetaData, record.BizSource,
record.Platform, record.Reserved1, record.Version,
)
}
func (d *Dao) GetCoinStreamByTid(c context.Context, tid string) (record *model.CoinStreamRecord, err error) {
return d.GetCoinStreamByTidAndOffset(c, tid, 0)
}
func (d *Dao) GetCoinStreamByTidAndOffset(c context.Context, tid string, offset int) (record *model.CoinStreamRecord, err error) {
s := fmt.Sprintf(_getCoinStream, getCoinStreamTable(tid), offset)
row := d.db.QueryRow(c, s, tid)
record = &model.CoinStreamRecord{}
err = row.Scan(&record.Uid, &record.TransactionId, &record.ExtendTid, &record.CoinType, &record.DeltaCoinNum,
&record.OrgCoinNum, &record.OpResult, &record.OpReason, &record.OpType, &record.MetaData, &record.Area,
&record.BizCode, &record.Source, &record.BizSource, &record.Platform, &record.Reserved1, &record.Version)
return
}
func (d *Dao) GetCoinStreamByUidTid(c context.Context, uid int64, tid string) (record *model.CoinStreamRecord, err error) {
s := fmt.Sprintf(_getCoinStreamByUid, getCoinStreamTable(tid), 0)
row := d.db.QueryRow(c, s, tid, uid)
record = &model.CoinStreamRecord{}
err = row.Scan(&record.Uid, &record.TransactionId, &record.ExtendTid, &record.CoinType, &record.DeltaCoinNum,
&record.OrgCoinNum, &record.OpResult, &record.OpReason, &record.OpType, &record.MetaData, &record.Area,
&record.BizCode, &record.Source, &record.BizSource, &record.Platform, &record.Reserved1, &record.Version)
return
}

View File

@@ -0,0 +1,61 @@
package dao
import (
"testing"
"fmt"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/wallet/model"
"time"
)
func TestGetTable(t *testing.T) {
Convey("Get Table", t, func() {
once.Do(startService)
t := getCoinStreamTable("89689d8c2290d93183b96c7ec83ea3c61114")
So(t, ShouldEqual, "t_coin_stream_201704")
t = getCoinStreamTable("31ea4d87162d3ed9c1dfb804eb960c640119")
So(t, ShouldEqual, "t_coin_stream_201709")
t = getCoinStreamTable("9937727a8c7fed5bbecd93642b4982561121")
So(t, ShouldEqual, "t_coin_stream_201801")
tid := GetTid(1, map[string]string{"a": "a", "b": "b"})
t = getCoinStreamTable(tid)
now := time.Now()
So(t, ShouldEqual, fmt.Sprintf("t_coin_stream_%d%02d", now.Year(), int(now.Month())))
})
}
func TestDao_NewCoinStreamRecord(t *testing.T) {
Convey("new coin stream", t, func() {
once.Do(startService)
r := model.CoinStreamRecord{
Uid: 1,
TransactionId: GetTid(1, map[string]string{"a": "a", "b": "b"}),
ExtendTid: "abcdef",
DeltaCoinNum: 1,
CoinType: 0,
OrgCoinNum: 100,
OpResult: 2,
OpReason: 0,
OpType: 1,
OpTime: time.Now().Unix(),
BizCode: "wtf",
Area: 0,
Source: "",
MetaData: "",
BizSource: "",
}
affect, err := d.NewCoinStreamRecord(ctx, &r)
So(err, ShouldBeNil)
So(affect, ShouldEqual, 1)
})
}

View File

@@ -0,0 +1,235 @@
package dao
import (
"context"
"go-common/app/service/live/wallet/conf"
"go-common/app/service/live/wallet/model"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
xsql "go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
httpx "go-common/library/net/http/blademaster"
"go-common/library/queue/databus"
"math/rand"
"time"
)
type Dao struct {
c *conf.Config
mc *memcache.Pool
db *xsql.DB
redis *redis.Pool
cacheExpire int32
httpClient *httpx.Client
changeDataBus *databus.Databus
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
mc: memcache.NewPool(c.Memcache.Wallet),
db: xsql.NewMySQL(c.DB.Wallet),
redis: redis.NewPool(c.Redis.Wallet),
cacheExpire: c.WalletExpire,
httpClient: httpx.NewClient(c.HTTPClient),
changeDataBus: databus.New(c.DataBus.Change),
}
return
}
// Close close the resource.
func (d *Dao) Close() {
d.mc.Close()
d.db.Close()
d.redis.Close()
d.changeDataBus.Close()
}
// Ping dao ping
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.db.Ping(c); err != nil {
log.Error("PingDb error(%v)", err)
return
}
if err = d.pingMC(c); err != nil {
return err
}
return d.PingRedis(c)
}
// pingMc ping
func (d *Dao) pingMC(c context.Context) (err error) {
item := &memcache.Item{
Key: "ping",
Value: []byte{1},
Expiration: d.cacheExpire,
}
conn := d.mc.Get(c)
err = conn.Set(item)
conn.Close()
if err != nil {
log.Error("PingMemcache conn.Set(%v) error(%v)", item, err)
}
return
}
func (d *Dao) PingRedis(c context.Context) (err error) {
var conn = d.redis.Get(c)
_, err = conn.Do("SET", "PING", "PONG")
conn.Close()
return
}
// 通过提供的sql和bind来更新没有实际业务意义只是为了少写重复代码
func execSqlWithBindParams(d *Dao, c context.Context, sql *string, bindParams ...interface{}) (affect int64, err error) {
res, err := d.db.Exec(c, *sql, bindParams...)
if err != nil {
log.Error("db.Exec(%s) error(%v)", *sql, err)
return
}
return res.RowsAffected()
}
func randomString(l int) string {
str := "0123456789abcdefghijklmnopqrstuvwxyz"
bytes := []byte(str)
result := []byte{}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < l; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return string(result)
}
func (d *Dao) GetDetailByCache(c context.Context, uid int64) (wallet *model.Detail, err error) {
mcDetail, err := d.WalletCache(c, uid)
if err == ecode.ServerErr {
return
}
if err == nil {
if d.IsNewVersion(c, mcDetail) {
wallet = mcDetail.Detail
return
} else {
log.Info("user wallet hit but version old uid: %d", uid)
}
}
if wallet, err = d.Detail(c, uid); err == nil {
d.SetWalletCache(c, &model.McDetail{Detail: wallet, Exist: true, Version: d.CacheVersion(c)}, 86400)
}
return
}
// Melonseed 获取瓜子数
func (d *Dao) GetMelonseedByCache(c context.Context, uid int64) (wallet *model.Melonseed, err error) {
detail, err := d.GetDetailByCache(c, uid)
wallet = new(model.Melonseed)
if err == nil {
wallet = &model.Melonseed{
Uid: detail.Uid,
Gold: detail.Gold,
IapGold: detail.IapGold,
Silver: detail.Silver,
}
}
return
}
func (d *Dao) ModifyCoin(c context.Context, coinNum int, uid int64, coinTypeNo int32, delCache bool) (bool, error) {
var (
affect int64
err error
res bool
)
switch coinTypeNo {
case model.SysCoinTypeIosGold:
affect, err = d.AddIapGold(c, uid, coinNum)
case model.SysCoinTypeSilver:
affect, err = d.AddSilver(c, uid, coinNum)
case model.SysCoinTypeGold:
affect, err = d.AddGold(c, uid, coinNum)
default:
// do nothing
}
if affect > 0 {
res = true
if delCache {
d.DelWalletCache(c, uid)
}
}
return res, err
}
func (d *Dao) GetCoin(c context.Context, coinTypeNo int32, uid int64) (interface{}, error) {
userCoin, err := d.Melonseed(c, uid)
switch coinTypeNo {
case model.SysCoinTypeIosGold:
return userCoin.IapGold, err
case model.SysCoinTypeGold:
return userCoin.Gold, err
case model.SysCoinTypeSilver:
return userCoin.Silver, err
case model.SysCoinTypeMetal:
metal, err := d.GetMetal(c, uid)
return metal, err
default:
return nil, nil
}
}
func (d *Dao) RechargeCoin(c context.Context, coinNum int, uid int64, coinTypeNo int32, delCache bool) (bool, error) {
var (
affect int64
err error
res bool
)
switch coinTypeNo {
case model.SysCoinTypeIosGold:
affect, err = d.RechargeIapGold(c, uid, coinNum)
case model.SysCoinTypeSilver:
affect, err = d.AddSilver(c, uid, coinNum)
case model.SysCoinTypeGold:
affect, err = d.RechargeGold(c, uid, coinNum)
default:
// do nothing
}
if affect > 0 {
res = true
if delCache {
d.DelWalletCache(c, uid)
}
}
return res, err
}
func (d *Dao) ConsumeCoin(c context.Context, coinNum int, uid int64, coinTypeNo int32, seeds int64, delCache bool, reason interface{}) (success bool, err error) {
var affect int64
switch coinTypeNo {
case model.SysCoinTypeGold:
affect, err = d.ConsumeGold(c, uid, coinNum)
case model.SysCoinTypeIosGold:
affect, err = d.ConsumeIapGold(c, uid, coinNum)
case model.SysCoinTypeSilver:
affect, err = d.ConsumeSilver(c, uid, coinNum)
case model.SysCoinTypeMetal:
success, _, err = d.ModifyMetal(c, uid, int64(-1*coinNum), seeds, reason)
default:
}
if model.IsLocalCoin(coinTypeNo) {
if affect > 0 {
success = true
if delCache {
d.DelWalletCache(c, uid)
}
}
}
return
}

View File

@@ -0,0 +1,80 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/live/wallet/model"
mc "go-common/library/cache/memcache"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_walletMcKey = "wu:%d" // 钱包数据的mc缓存
)
func mcKey(uid int64) string {
return fmt.Sprintf(_walletMcKey, uid)
}
func (d *Dao) CacheVersion(c context.Context) int32 {
return 1
}
func (d *Dao) IsNewVersion(c context.Context, detail *model.McDetail) bool {
return detail.Version == d.CacheVersion(c)
}
// WalletCache 获取钱包缓存
func (d *Dao) WalletCache(c context.Context, uid int64) (detail *model.McDetail, err error) {
key := mcKey(uid)
conn := d.mc.Get(c)
defer conn.Close()
r, err := conn.Get(key)
if err != nil {
if err == mc.ErrNotFound {
return
}
log.Error("[dao.mc_wallet|WalletCache] conn.Get(%s) error(%v)", key, err)
err = ecode.ServerErr
return
}
detail = &model.McDetail{}
if err = conn.Scan(r, detail); err != nil {
log.Error("[dao.mc_wallet|WalletCache] conn.Scan(%s) error(%v)", string(r.Value), err)
}
return
}
// SetWalletCache 设置钱包缓存
func (d *Dao) SetWalletCache(c context.Context, detail *model.McDetail, expire int32) (err error) {
key := mcKey(detail.Detail.Uid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Set(&mc.Item{
Key: key,
Object: detail,
Flags: mc.FlagProtobuf,
Expiration: expire,
}); err != nil {
log.Error("[dao.mc_wallet|SetWalletCache] conn.Set(%s, %v) error(%v)", key, detail, err)
}
return
}
// DelWalletCache 删除等级缓存
func (d *Dao) DelWalletCache(c context.Context, uid int64) (err error) {
key := mcKey(uid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Delete(key); err == mc.ErrNotFound {
return
}
if err != nil {
log.Error("[dao.mc_wallet|DelWalletCache] conn.Delete(%s) error(%v)", key, err)
}
return
}

View File

@@ -0,0 +1,103 @@
package dao
import (
. "github.com/smartystreets/goconvey/convey"
"testing"
"go-common/app/service/live/wallet/model"
mc "go-common/library/cache/memcache"
"math/rand"
"time"
)
func TestDao_WalletCache(t *testing.T) {
once.Do(startService)
Convey("Get Nil Cache", t, func() {
r = rand.New(rand.NewSource(time.Now().UnixNano()))
uid := r.Int63n(1000000)
_, err := d.WalletCache(ctx, uid)
So(err, ShouldEqual, mc.ErrNotFound)
})
Convey("Set And Get And Del", t, func() {
r = rand.New(rand.NewSource(time.Now().UnixNano()))
uid := r.Int63n(1000000)
detail := &model.Detail{Uid: uid, Gold: 1, IapGold: 1, Silver: 1, GoldPayCnt: 1, GoldRechargeCnt: 1, SilverPayCnt: 1}
mcDetail := &model.McDetail{Detail: detail, Exist: true, Version: d.CacheVersion(ctx)}
err := d.SetWalletCache(ctx, mcDetail, d.cacheExpire)
So(err, ShouldBeNil)
nd, err := d.WalletCache(ctx, uid)
So(err, ShouldBeNil)
So(nd.Exist, ShouldEqual, true)
So(nd.Detail.Gold, ShouldEqual, 1)
So(nd.Detail.IapGold, ShouldEqual, 1)
So(nd.Detail.Silver, ShouldEqual, 1)
So(nd.Detail.GoldPayCnt, ShouldEqual, 1)
So(nd.Detail.GoldRechargeCnt, ShouldEqual, 1)
So(nd.Detail.SilverPayCnt, ShouldEqual, 1)
So(nd.Version, ShouldEqual, d.CacheVersion(ctx))
err = d.DelWalletCache(ctx, uid)
So(err, ShouldBeNil)
nnd, err := d.WalletCache(ctx, uid)
So(err, ShouldEqual, mc.ErrNotFound)
So(nnd, ShouldBeNil)
})
Convey("version", t, func() {
Convey("just set", func() {
uid := int64(1)
d.DelWalletCache(ctx, uid)
d.GetDetailByCache(ctx, uid)
detail, err := d.WalletCache(ctx, uid)
So(err, ShouldBeNil)
So(detail.Version, ShouldEqual, 1)
})
Convey("old to new", func() {
uid := int64(1)
od, err := d.Detail(ctx, uid)
So(err, ShouldBeNil)
d.DelWalletCache(ctx, uid)
detail := &model.Detail{Uid: uid, Gold: od.Gold + 1, IapGold: 1, Silver: 1, GoldPayCnt: 1, GoldRechargeCnt: 1, SilverPayCnt: 1} // fake data
mcDetail := &model.McDetail{Detail: detail, Exist: true} // old version
err = d.SetWalletCache(ctx, mcDetail, d.cacheExpire)
So(err, ShouldBeNil)
So(mcDetail.Version, ShouldEqual, 0)
So(mcDetail.Detail.Gold, ShouldEqual, od.Gold+1)
So(mcDetail.Detail.IapGold, ShouldEqual, 1)
So(mcDetail.Detail.Silver, ShouldEqual, 1)
So(mcDetail.Detail.GoldPayCnt, ShouldEqual, 1)
mcDetail1, err := d.WalletCache(ctx, uid) //
So(err, ShouldBeNil)
So(mcDetail1.Detail.Gold, ShouldEqual, od.Gold+1)
fd, err := d.GetDetailByCache(ctx, uid) // check version if it is old update cache
So(err, ShouldBeNil)
So(fd.Gold, ShouldEqual, od.Gold)
So(fd.Silver, ShouldEqual, od.Silver)
So(fd.GoldPayCnt, ShouldEqual, od.GoldPayCnt)
mcDetail2, err := d.WalletCache(ctx, uid)
So(err, ShouldBeNil)
So(mcDetail2.Version, ShouldEqual, d.CacheVersion(ctx))
So(mcDetail2.Detail.Gold, ShouldEqual, od.Gold)
})
})
}

View File

@@ -0,0 +1,132 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"go-common/app/service/live/wallet/model"
"go-common/library/ecode"
"go-common/library/log"
"net/http"
"net/url"
"time"
)
const (
ModifyUrl = "http://api.bilibili.co/x/internal/v1/coin/user/modify"
InfoUrl = "http://api.bilibili.co/x/internal/v1/coin/user/count"
ExchangeSilverReason = "兑换直播银瓜子 %d"
ExchangeMetalReason = "银瓜子兑换硬币"
)
var (
respCodeError = errors.New("query response code err")
paramError = errors.New("param error")
)
func (d *Dao) GetMetal(c context.Context, uid int64) (metal float64, err error) {
params := url.Values{}
params.Set("mid", fmt.Sprintf("%d", uid))
now := time.Now().Unix()
params.Set("ts", fmt.Sprintf("%d", now))
var req *http.Request
if req, err = d.httpClient.NewRequest("GET", InfoUrl, "", params); err != nil {
log.Error("wallet metal newRequest err:%s", err.Error())
return
}
queryUrl := req.URL.String()
var res struct {
Code int `json:"code"`
Data model.MetalData `json:"data"`
}
err = d.httpClient.Do(c, req, &res)
if err != nil {
log.Error("wallet query metal err url: %s,err:%s", queryUrl, err.Error())
return
}
resStr, _ := json.Marshal(res)
log.Info("wallet query metal success url:%s,res:%s", queryUrl, resStr)
if res.Code != 0 {
err = respCodeError
} else {
metal = res.Data.Count
}
return
}
func (d *Dao) ModifyMetal(c context.Context, uid int64, coins int64, seeds int64, reason interface{}) (success bool, code int, err error) {
if coins == 0 {
err = paramError
return
}
if coins < 0 {
// 检查是否足够
metal, _ := d.GetMetal(c, uid)
if metal < float64(0-coins) {
err = ecode.CoinNotEnough
return
}
}
params := url.Values{}
params.Set("mid", fmt.Sprintf("%d", uid))
params.Set("count", fmt.Sprintf("%d", coins))
now := time.Now().Unix()
params.Set("ts", fmt.Sprintf("%d", now))
var realReason string
switch reason.(type) {
case string:
realReason = reason.(string)
default:
if coins < 0 {
realReason = fmt.Sprintf(ExchangeSilverReason, seeds)
} else {
realReason = ExchangeMetalReason
}
}
log.Info("user %d consume or recharge metal %d by reason %s", uid, coins, realReason)
params.Set("reason", realReason)
var req *http.Request
req, err = d.httpClient.NewRequest("POST", ModifyUrl, "", params)
if err != nil {
log.Error("wallet metal newRequest err:%s", err.Error())
return
}
queryUrl := req.URL.String()
var res struct {
Code int `json:"code"`
}
err = d.httpClient.Do(c, req, &res)
if err != nil {
log.Error("Metal#wallet query metal err url: %s,err:%s uid:%d, count:%d", queryUrl, err.Error(), uid, coins)
// 认为成功
err = nil
success = true
code = 0
return
}
resStr, _ := json.Marshal(res)
log.Info("wallet query metal success url:%s, uid:%d, count:%d res:%s", queryUrl, uid, coins, resStr)
code = res.Code
if res.Code == 0 {
success = true
} else {
if res.Code == ecode.LackOfCoins.Code() {
err = ecode.CoinNotEnough
} else {
log.Error("Metal#wallet query metal code failed : uid:%d, count:%d,code:%d", uid, coins, res.Code)
err = respCodeError
}
}
return
}

View File

@@ -0,0 +1,62 @@
package dao
import (
. "github.com/smartystreets/goconvey/convey"
"go-common/library/ecode"
"testing"
)
func TestDao_GetMetal(t *testing.T) {
Convey("GetMetal", t, func() {
once.Do(startService)
var uid int64 = 1
metal, err := d.GetMetal(ctx, uid)
So(err, ShouldBeNil)
So(metal, ShouldBeGreaterThanOrEqualTo, 0)
})
}
func TestDao_ModifyMetal(t *testing.T) {
Convey("ModifyMetal", t, func() {
once.Do(startService)
var uid int64 = 1
metal, err := d.GetMetal(ctx, uid)
So(err, ShouldBeNil)
if metal < 10 {
d.ModifyMetal(ctx, uid, 10, 0, nil)
}
var coins int64 = -5
success, code, err := d.ModifyMetal(ctx, uid, coins, 500, "ut")
So(code, ShouldEqual, 0)
So(success, ShouldEqual, true)
So(err, ShouldBeNil)
coins = 0 - coins
success, code, err = d.ModifyMetal(ctx, uid, coins, 0, nil)
So(code, ShouldEqual, 0)
So(success, ShouldEqual, true)
So(err, ShouldBeNil)
nmetal, err := d.GetMetal(ctx, uid)
So(err, ShouldBeNil)
So(metal, ShouldEqual, nmetal)
})
Convey("ModifyMetal not enough", t, func() {
once.Do(startService)
var uid int64 = 1
metal, err := d.GetMetal(ctx, uid)
So(err, ShouldBeNil)
coins := 0 - int64(metal+1)
_, _, err = d.ModifyMetal(ctx, uid, coins, 400, nil)
So(err, ShouldEqual, ecode.CoinNotEnough)
})
}

View File

@@ -0,0 +1,16 @@
package dao
import (
"context"
"go-common/library/log"
"strconv"
)
func (d *Dao) Pub(c context.Context, uid int64, msg interface{}) error {
key := strconv.FormatInt(uid, 10)
err := d.changeDataBus.Send(c, key, msg)
if err != nil {
log.Error("pub wallet change failed uid:%d, msg:%+v", uid, msg)
}
return err
}

View File

@@ -0,0 +1,29 @@
package dao
import (
. "github.com/smartystreets/goconvey/convey"
"testing"
)
type testMsg struct {
Name string
Age int64
}
func TestDao_Pub(t *testing.T) {
Convey("pub", t, func() {
once.Do(startService)
//startSubDataBus()
//msgs := subDataBus.Messages()
var uid int64 = 1
u := testMsg{
Name: "test",
Age: 23,
}
err := d.Pub(ctx, uid, &u)
So(err, ShouldBeNil)
t.Logf("pub")
})
}

View File

@@ -0,0 +1,115 @@
package dao
import (
"context"
"fmt"
"github.com/pkg/errors"
"go-common/library/cache/redis"
"go-common/library/log"
"time"
)
var (
UnLockGetWrong = "UnLockGetWrong"
LockFailed = "LockFailed"
UserWalletPrefix = "user_wallet_lock_uid_"
ErrUnLockGet = errors.New(UnLockGetWrong)
ErrLockFailed = errors.New(LockFailed)
)
func (d *Dao) IsLockFailedError(err error) bool {
return err == ErrLockFailed
}
func lockKey(k string) string {
return "wallet_lock_key:" + k
}
/*
ttl ms
retry 重试次数
retryDelay us
*/
func (d *Dao) Lock(ctx context.Context, key string, ttl int, retry int, retryDelay int) (err error, gotLock bool, lockValue string) {
if retry <= 0 {
retry = 1
}
lockValue = "locked:" + randomString(5)
retryTimes := 0
conn := d.redis.Get(ctx)
defer conn.Close()
realKey := lockKey(key)
for ; retryTimes < retry; retryTimes++ {
var res interface{}
res, err = conn.Do("SET", realKey, lockValue, "PX", ttl, "NX")
if err != nil {
log.Error("redis_lock failed:%s:%s", realKey, err.Error())
break
}
if res != nil {
gotLock = true
break
}
time.Sleep(time.Duration(retryDelay * 1000))
}
return
}
func (d *Dao) UnLock(ctx context.Context, key string, lockValue string) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
realKey := lockKey(key)
res, err := redis.String(conn.Do("GET", realKey))
if err != nil {
return
}
if res != lockValue {
err = ErrUnLockGet
return
}
_, err = conn.Do("DEL", realKey)
return
}
func (d *Dao) ForceUnLock(ctx context.Context, key string) (err error) {
realKey := lockKey(key)
conn := d.redis.Get(ctx)
defer conn.Close()
_, err = conn.Do("DEL", realKey)
return
}
func (d *Dao) LockTransactionId(ctx context.Context, tid string) (err error) {
err, gotLock, _ := d.Lock(ctx, tid, 300*1000, 0, 200000)
if err != nil {
return
}
if !gotLock {
err = ErrLockFailed
}
return
}
func (d *Dao) LockUser(ctx context.Context, uid int64) (err error, gotLock bool, lockValue string) {
lockTime := 600
retry := 1
retryDelay := 10
return d.Lock(ctx, getUserLockKey(uid), lockTime*1000, retry, retryDelay*1000)
}
func (d *Dao) UnLockUser(ctx context.Context, uid int64, lockValue string) error {
return d.UnLock(ctx, getUserLockKey(uid), lockValue)
}
func getUserLockKey(uid int64) string {
return fmt.Sprintf("%s%v", UserWalletPrefix, uid)
}

View File

@@ -0,0 +1,153 @@
package dao
import (
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/wallet/model"
"go-common/library/cache/redis"
"testing"
)
func TestDao_Lock(t *testing.T) {
Convey("Lock", t, func() {
once.Do(startService)
key := "test_lock_key" + randomString(5)
ttl := 2000
retryDelay := 10000
err, gotLock, lockValue := d.Lock(ctx, key, ttl, 0, retryDelay)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
So(lockValue, ShouldNotBeNil)
})
Convey("Lock Twice", t, func() {
once.Do(startService)
key := "test_lock_key" + randomString(5)
ttl := 2000
retryDelay := 10000
err, gotLock, lockValue := d.Lock(ctx, key, ttl, 0, retryDelay)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
So(lockValue, ShouldNotBeNil)
err, gotLock, lockValue = d.Lock(ctx, key, ttl, 3, retryDelay)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, false)
})
}
func TestDao_UnLock(t *testing.T) {
Convey("UnLock", t, func() {
once.Do(startService)
key := "test_lock_key" + randomString(5)
value := "test"
err := d.UnLock(ctx, key, value)
So(err, ShouldEqual, redis.ErrNil)
})
Convey("UnLock wrong value", t, func() {
once.Do(startService)
key := "test_lock_key" + randomString(5)
ttl := 2000
retryDelay := 10000
err, gotLock, lockValue := d.Lock(ctx, key, ttl, 3, retryDelay)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
So(lockValue, ShouldNotBeNil)
value := "test"
err = d.UnLock(ctx, key, value)
So(err, ShouldEqual, ErrUnLockGet)
})
Convey("LockAndUnLock", t, func() {
once.Do(startService)
key := "test_lock_key" + randomString(5)
ttl := 2000
retryDelay := 10000
err, gotLock, lockValue := d.Lock(ctx, key, ttl, 0, retryDelay)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
So(lockValue, ShouldNotBeNil)
err = d.UnLock(ctx, key, lockValue)
So(err, ShouldBeNil)
err, gotLock, lockValue = d.Lock(ctx, key, ttl, 0, retryDelay)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
So(lockValue, ShouldNotBeNil)
})
}
func TestDao_ForceUnLock(t *testing.T) {
Convey("ForceUnLock", t, func() {
once.Do(startService)
key := "test_lock_key" + randomString(5)
err := d.ForceUnLock(ctx, key)
So(err, ShouldBeNil)
})
Convey("LockAndForceUnLock", t, func() {
once.Do(startService)
key := "test_lock_key" + randomString(5)
ttl := 2000
retryDelay := 10000
err, gotLock, lockValue := d.Lock(ctx, key, ttl, 0, retryDelay)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
So(lockValue, ShouldNotBeNil)
err = d.ForceUnLock(ctx, key)
So(err, ShouldBeNil)
err, gotLock, lockValue = d.Lock(ctx, key, ttl, 0, retryDelay)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
So(lockValue, ShouldNotBeNil)
})
}
func TestDao_LockTransactionId(t *testing.T) {
Convey("LockTid Twice", t, func() {
tid := GetTid(model.PAYTYPE, "test")
err := d.LockTransactionId(ctx, tid)
So(err, ShouldBeNil)
err = d.LockTransactionId(ctx, tid)
So(err, ShouldEqual, ErrLockFailed)
})
}
func TestDao_LockUser(t *testing.T) {
Convey("LockUser", t, func() {
uid := r.Int63n(100000000)
err, gotLock, localValue := d.LockUser(ctx, uid)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
err = d.UnLockUser(ctx, uid, localValue)
So(err, ShouldBeNil)
err, gotLock, localValue = d.LockUser(ctx, uid)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
})
Convey("LockUser Twice", t, func() {
uid := r.Int63n(100000000)
err, gotLock, _ := d.LockUser(ctx, uid)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, true)
err, gotLock, _ = d.LockUser(ctx, uid)
So(err, ShouldBeNil)
So(gotLock, ShouldEqual, false)
})
}

View File

@@ -0,0 +1,190 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/live/wallet/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
"time"
)
const (
_selWallet = "SELECT uid,gold,iap_gold,silver,cost_base,gold_recharge_cnt,gold_pay_cnt,silver_pay_cnt,snapshot_time,snapshot_gold,snapshot_iap_gold,snapshot_silver,reserved1,reserved2 FROM user_wallet_%d WHERE uid=? FOR UPDATE"
_recharge = "UPDATE user_wallet_%d set %s = %s + %d,%s=%s+%d where uid = ?"
_rechargeWihoutCnt = "UPDATE user_wallet_%d set %s=%s + %d where uid = ?"
_rechargeWithSnap = "UPDATE user_wallet_%d set %s=%s + %d,%s = %s+ %d, snapshot_time = ? , snapshot_gold = ? , snapshot_iap_gold = ? , snapshot_silver = ? where uid = ?"
_rechargeWithSnapWithoutCnt = "UPDATE user_wallet_%d set %s = %s + %d, snapshot_time = ? , snapshot_gold = ? , snapshot_iap_gold = ? , snapshot_silver = ? where uid = ?"
_exchange = "UPDATE user_wallet_%d set %s = %s - %d , %s = %s + %d, %s = %s + %d, %s = %s + %d where uid = ?"
_exhcangeWithSnap = "UPDATE user_wallet_%d set %s = %s - %d , %s = %s + %d, %s = %s + %d, %s = %s + %d, snapshot_time = ? , snapshot_gold = ? , snapshot_iap_gold = ? , snapshot_silver = ? where uid = ? "
_modifyCnt = "UPDATE user_wallet_%d set gold_pay_cnt = gold_pay_cnt + %d, gold_recharge_cnt = gold_recharge_cnt + %d, silver_pay_cnt = silver_pay_cnt + %d where uid = ?"
)
// 开启事务
func (d *Dao) BeginTx(c context.Context) (conn *sql.Tx, err error) {
return d.db.Begin(c)
}
func (d *Dao) DoTx(c context.Context, doFunc func(conn *sql.Tx) (v interface{}, err error)) (v interface{}, err error) {
conn, err := d.BeginTx(c)
if err != nil {
err = ecode.ServerErr
return
}
v, err = doFunc(conn)
var txErr error
if err != nil {
conn.Rollback()
err = ecode.ServerErr
} else {
txErr = conn.Commit()
if txErr != nil {
err = ecode.ServerErr
v = nil
}
}
return v, err
}
// 为了后续的更新获取数据
func (d *Dao) WalletForUpdate(conn *sql.Tx, uid int64) (wallet *model.DetailWithSnapShot, err error) {
row := conn.QueryRow(fmt.Sprintf(_selWallet, tableIndex(uid)), uid)
wallet = &model.DetailWithSnapShot{}
var snapShotTime time.Time
if err = row.Scan(&wallet.Uid, &wallet.Gold, &wallet.IapGold, &wallet.Silver, &wallet.CostBase, &wallet.GoldRechargeCnt,
&wallet.GoldPayCnt, &wallet.SilverPayCnt, &snapShotTime, &wallet.SnapShotGold, &wallet.SnapShotIapGold,
&wallet.SnapShotSilver, &wallet.Reserved1, &wallet.Reserved2); err == sql.ErrNoRows {
// 查询结果为空时,初始化数据
_, err = d.InitWalletInTx(conn, uid, 0, 0, 0)
wallet.SnapShotTime = snapShotTime.Format("2006-01-02 15:04:05")
return
}
if err != nil {
log.Error("[tx.wallet|Melonseed] row.Scan err: %s", err.Error())
return
}
wallet.SnapShotTime = snapShotTime.Format("2006-01-02 15:04:05")
return
}
// InitExp 初始化用户钱包,用于首次查询
func (d *Dao) InitWalletInTx(conn *sql.Tx, uid int64, gold int64, iap_gold int64, silver int64) (row int64, err error) {
res, err := conn.Exec(fmt.Sprintf(_insWallet, tableIndex(uid)), uid, gold, iap_gold, silver)
if err != nil {
log.Error("[tx.wallet|InitWallet] Exec err: %v", err)
return
}
return res.RowsAffected()
}
func (d *Dao) execSqlInTx(conn *sql.Tx, sql *string, params ...interface{}) (affect int64, err error) {
res, err := conn.Exec(*sql, params...)
if err != nil {
log.Error("[tx.wallet|execSqlInTx] Exec err: %v sql:%s", err, *sql)
return
}
return res.RowsAffected()
}
func (d *Dao) changeCoinInTx(conn *sql.Tx, uid int64, sysCoinTypeNo int32, num int64, originWallet *model.DetailWithSnapShot, cntField string) (affect int64, err error) {
// 判断
coinType := model.GetSysCoinTypeByNo(sysCoinTypeNo)
var s string
absNum := num
if absNum < 0 {
absNum = -absNum
}
if model.TodayNeedSnapShot(originWallet) {
if cntField == "" {
s = getRechargeWithoutCntWithSnapShotSQL(uid, coinType, num)
} else {
s = getRechargeWithSnapShotSQL(uid, coinType, num, cntField, absNum)
}
date := time.Now().Format("2006-01-02 15:04:05")
return d.execSqlInTx(conn, &s, date, originWallet.Gold, originWallet.IapGold, originWallet.Silver, uid)
} else {
if cntField == "" {
s = getRechargeWithoutCntSQL(uid, coinType, num)
} else {
s = getRechargeSQL(uid, coinType, num, cntField, absNum)
}
return d.execSqlInTx(conn, &s, uid)
}
}
// RechargeGold 充值IOS金瓜子 记入充值总值
func (d *Dao) RechargeCoinInTx(conn *sql.Tx, uid int64, sysCoinTypeNo int32, num int64, originWallet *model.DetailWithSnapShot) (affect int64, err error) {
rechargerCntField := model.GetRechargeCnt(sysCoinTypeNo)
return d.changeCoinInTx(conn, uid, sysCoinTypeNo, num, originWallet, rechargerCntField)
}
func (d *Dao) PayCoinInTx(conn *sql.Tx, uid int64, sysCoinTypeNo int32, num int64, originWallet *model.DetailWithSnapShot) (affect int64, err error) {
cntField := model.GetPayCnt(sysCoinTypeNo)
return d.changeCoinInTx(conn, uid, sysCoinTypeNo, -num, originWallet, cntField)
}
func (d *Dao) ModifyCoinInTx(conn *sql.Tx, uid int64, sysCoinTypeNo int32, num int64, originWallet *model.DetailWithSnapShot) (affect int64, err error) {
return d.changeCoinInTx(conn, uid, sysCoinTypeNo, num, originWallet, "")
}
func (d *Dao) ExchangeCoinInTx(conn *sql.Tx, uid int64, srcSysCoinTypeNo int32, srcNum int64, destSysCoinTypeNo int32, destNum int64, originWallet *model.DetailWithSnapShot) (affect int64, err error) {
rechargeCntNum := destNum
var rechargerCntField string
if destSysCoinTypeNo == model.SysCoinTypeSilver { // 如果为银瓜子则不存在充值统计总数字段 使用 消费统计字段代替 适配sql
rechargerCntField = "silver_pay_cnt"
rechargeCntNum = 0
} else {
rechargerCntField = model.GetRechargeCnt(destSysCoinTypeNo)
}
payCntField := model.GetPayCnt(srcSysCoinTypeNo)
srcCoinType := model.GetSysCoinTypeByNo(srcSysCoinTypeNo)
destCoinType := model.GetSysCoinTypeByNo(destSysCoinTypeNo)
var s string
if model.TodayNeedSnapShot(originWallet) {
s = fmt.Sprintf(_exhcangeWithSnap, tableIndex(uid), srcCoinType, srcCoinType, srcNum, destCoinType, destCoinType, destNum, rechargerCntField, rechargerCntField, rechargeCntNum, payCntField, payCntField, srcNum)
date := time.Now().Format("2006-01-02 15:04:05")
return d.execSqlInTx(conn, &s, date, originWallet.Gold, originWallet.IapGold, originWallet.Silver, uid)
} else {
s = fmt.Sprintf(_exchange, tableIndex(uid), srcCoinType, srcCoinType, srcNum, destCoinType, destCoinType, destNum, rechargerCntField, rechargerCntField, rechargeCntNum, payCntField, payCntField, srcNum)
return d.execSqlInTx(conn, &s, uid)
}
}
func getRechargeWithSnapShotSQL(uid int64, coinType string, num int64, cntField string, cntNum int64) string {
return fmt.Sprintf(_rechargeWithSnap, tableIndex(uid), coinType, coinType, num, cntField, cntField, cntNum)
}
func getRechargeWithoutCntWithSnapShotSQL(uid int64, coinType string, num int64) string {
return fmt.Sprintf(_rechargeWithSnapWithoutCnt, tableIndex(uid), coinType, coinType, num)
}
func getRechargeSQL(uid int64, coinType string, num int64, cntField string, cntNum int64) string {
return fmt.Sprintf(_recharge, tableIndex(uid), coinType, coinType, num, cntField, cntField, cntNum)
}
func getRechargeWithoutCntSQL(uid int64, coinType string, num int64) string {
return fmt.Sprintf(_rechargeWihoutCnt, tableIndex(uid), coinType, coinType, num)
}
func (d *Dao) NewCoinStreamRecordInTx(conn *sql.Tx, record *model.CoinStreamRecord) (int64, error) {
s := fmt.Sprintf(_newCoinStremaRecord, getCoinStreamTable(record.TransactionId))
date := model.GetWalletFormatTime(record.OpTime)
return d.execSqlInTx(conn, &s, record.Uid, record.TransactionId, record.ExtendTid, record.CoinType,
record.DeltaCoinNum, record.OrgCoinNum, record.OpResult, record.OpReason, record.OpType, date,
record.BizCode, record.Area, record.Source, record.MetaData, record.BizSource,
record.Platform, record.Reserved1, record.Version)
}
func (d *Dao) NewCoinExchangeRecordInTx(conn *sql.Tx, record *model.CoinExchangeRecord) (int64, error) {
s := fmt.Sprintf(_newCoinExchange, getCoinExchangeTable(record.Uid))
date := model.GetWalletFormatTime(record.ExchangeTime)
return d.execSqlInTx(conn, &s, record.Uid, record.TransactionId, record.SrcType, record.SrcNum, record.DestType, record.DestNum, record.Status, date)
}
func (d *Dao) ModifyCntInTx(conn *sql.Tx, uid int64, goldPay int, goldRecharge int, silverPay int) (int64, error) {
s := fmt.Sprintf(_modifyCnt, tableIndex(uid), goldPay, goldRecharge, silverPay)
return d.execSqlInTx(conn, &s, uid)
}

View File

@@ -0,0 +1,138 @@
package dao
import (
"fmt"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/wallet/model"
"testing"
"time"
)
func TestDao_Tx1(t *testing.T) {
Convey("commit wrong sample", t, func() {
once.Do(startService)
uid := int64(1)
var wallet *model.Melonseed
wallet, err := d.Melonseed(ctx, uid)
t.Logf("uid gold : %d", wallet.Gold)
og := wallet.Gold
So(err, ShouldBeNil)
So(1, ShouldEqual, 1)
tx, err := d.db.Begin(ctx)
So(err, ShouldBeNil)
affect, err := d.AddGold(ctx, uid, 1)
So(affect, ShouldBeGreaterThan, 0)
So(err, ShouldBeNil)
err = tx.Commit()
So(err, ShouldBeNil)
wallet, err = d.Melonseed(ctx, uid)
t.Logf("uid gold : %d", wallet.Gold)
So(err, ShouldBeNil)
So(wallet.Gold-og, ShouldEqual, 1)
})
Convey("rollback wrong sample use db ins instead tx", t, func() {
once.Do(startService)
uid := int64(1)
var wallet *model.Melonseed
wallet, err := d.Melonseed(ctx, uid)
t.Logf("uid gold : %d", wallet.Gold)
og := wallet.Gold
So(err, ShouldBeNil)
So(1, ShouldEqual, 1)
tx, err := d.db.Begin(ctx)
So(err, ShouldBeNil)
affect, err := d.AddGold(ctx, uid, 1)
So(affect, ShouldBeGreaterThan, 0)
So(err, ShouldBeNil)
err = tx.Rollback()
So(err, ShouldBeNil)
wallet, err = d.Melonseed(ctx, uid)
t.Logf("uid gold : %d", wallet.Gold)
So(err, ShouldBeNil)
So(wallet.Gold-og, ShouldEqual, 1)
})
Convey("commit", t, func() {
once.Do(startService)
uid := int64(1)
var wallet *model.Melonseed
wallet, err := d.Melonseed(ctx, uid)
t.Logf("uid gold : %d", wallet.Gold)
og := wallet.Gold
So(err, ShouldBeNil)
So(1, ShouldEqual, 1)
tx, err := d.db.Begin(ctx)
So(err, ShouldBeNil)
res, err := tx.Exec(fmt.Sprintf("update user_wallet_%d set gold = gold + ? where uid = ?", uid%10), 1, uid)
So(err, ShouldBeNil)
affect, err := res.RowsAffected()
So(affect, ShouldEqual, 1)
So(err, ShouldBeNil)
err = tx.Commit()
So(err, ShouldBeNil)
wallet, err = d.Melonseed(ctx, uid)
t.Logf("uid gold : %d", wallet.Gold)
So(err, ShouldBeNil)
So(wallet.Gold-og, ShouldEqual, 1)
})
Convey("rollback", t, func() {
once.Do(startService)
uid := int64(1)
var wallet *model.Melonseed
wallet, err := d.Melonseed(ctx, uid)
t.Logf("uid gold : %d", wallet.Gold)
og := wallet.Gold
So(err, ShouldBeNil)
So(1, ShouldEqual, 1)
tx, err := d.db.Begin(ctx)
So(err, ShouldBeNil)
res, err := tx.Exec(fmt.Sprintf("update user_wallet_%d set gold = gold + ? where uid = ?", uid%10), 1, uid)
So(err, ShouldBeNil)
affect, err := res.RowsAffected()
So(affect, ShouldEqual, 1)
So(err, ShouldBeNil)
err = tx.Rollback()
So(err, ShouldBeNil)
wallet, err = d.Melonseed(ctx, uid)
t.Logf("uid gold : %d", wallet.Gold)
So(err, ShouldBeNil)
So(wallet.Gold-og, ShouldEqual, 0)
})
}
func TestDao_DoubleCoin(t *testing.T) {
Convey("check1", t, func() {
d := &model.DetailWithSnapShot{}
So(model.NeedSnapshot(d, time.Now()), ShouldBeTrue)
d.SnapShotTime = "2018-09-22 16:39:05"
So(model.NeedSnapshot(d, time.Now()), ShouldBeTrue)
now, _ := time.Parse("2006-01-02 15:04:05", "2018-09-22 16:39:04")
t.Logf("today:%+v", now)
So(model.NeedSnapshot(d, now), ShouldBeFalse)
now = model.GetTodayTime(now)
t.Logf("today:%+v", now)
d.SnapShotTime = "2018-09-22 04:39:04"
So(model.NeedSnapshot(d, now), ShouldBeFalse)
d.SnapShotTime = "2018-09-21 04:39:04"
So(model.NeedSnapshot(d, now), ShouldBeTrue)
d.SnapShotTime = time.Now().Format("2006-01-02 15:04:05")
t.Logf("today:%+v", d.SnapShotTime)
So(model.TodayNeedSnapShot(d), ShouldBeFalse)
d.SnapShotTime = time.Now().Add(-time.Second * 86400).Format("2006-01-02 15:04:05")
t.Logf("today:%+v", d.SnapShotTime)
So(model.TodayNeedSnapShot(d), ShouldBeTrue)
})
}

View File

@@ -0,0 +1,146 @@
package dao
import (
"context"
"database/sql"
"fmt"
"go-common/app/service/live/wallet/model"
"go-common/library/log"
)
const (
_shard = 10
_insWallet = "INSERT IGNORE INTO user_wallet_%d (uid,gold,iap_gold,silver) VALUES(?,?,?,?)"
_selMelonseed = "SELECT uid,gold,iap_gold,silver FROM user_wallet_%d WHERE uid=?"
_selDetail = "SELECT uid,gold,iap_gold,silver,gold_recharge_cnt,gold_pay_cnt,silver_pay_cnt, cost_base FROM user_wallet_%d WHERE uid=?"
_addGold = "INSERT INTO user_wallet_%d(uid,gold) VALUES(?,?) ON DUPLICATE KEY UPDATE gold=gold+%d"
_rechargeGold = "INSERT INTO user_wallet_%d(uid,gold,gold_recharge_cnt) VALUES(?,?,?) ON DUPLICATE KEY UPDATE gold=gold+%d,gold_recharge_cnt=gold_recharge_cnt+%d"
_addIapGold = "INSERT INTO user_wallet_%d(uid,iap_gold) VALUES(?,?) ON DUPLICATE KEY UPDATE iap_gold=iap_gold+%d"
_rechargeIapGold = "INSERT INTO user_wallet_%d(uid,iap_gold,gold_recharge_cnt) VALUES(?,?,?) ON DUPLICATE KEY UPDATE iap_gold=iap_gold+%d,gold_recharge_cnt=gold_recharge_cnt+%d"
_addSilver = "INSERT INTO user_wallet_%d(uid,silver) VALUES(?,?) ON DUPLICATE KEY UPDATE silver=silver+%d"
_consumeGold = "UPDATE user_wallet_%d SET gold=gold-%d,gold_pay_cnt=gold_pay_cnt+%d WHERE uid=?"
_consumeIapGold = "UPDATE user_wallet_%d SET iap_gold=iap_gold-%d,gold_pay_cnt=gold_pay_cnt+%d WHERE uid=?"
_consumeSilver = "UPDATE user_wallet_%d SET silver=silver-%d,silver_pay_cnt=silver_pay_cnt+%d WHERE uid=?"
_changeCosetBase = "UPDATE user_wallet_%d SET cost_base=? WHERE uid=?"
)
func tableIndex(uid int64) int64 {
return uid % _shard
}
// InitExp 初始化用户钱包,用于首次查询
func (d *Dao) InitWallet(c context.Context, uid int64, gold int64, iap_gold int64, silver int64) (row int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_insWallet, tableIndex(uid)), uid, gold, iap_gold, silver)
if err != nil {
log.Error("[dao.wallet|InitWallet] d.db.Exec err: %v", err)
return
}
return res.RowsAffected()
}
// Melonseed 获取瓜子数
func (d *Dao) Melonseed(c context.Context, uid int64) (wallet *model.Melonseed, err error) {
row := d.db.QueryRow(c, fmt.Sprintf(_selMelonseed, tableIndex(uid)), uid)
wallet = &model.Melonseed{}
if err = row.Scan(&wallet.Uid, &wallet.Gold, &wallet.IapGold, &wallet.Silver); err == sql.ErrNoRows {
// 查询结果为空时,初始化数据
_, err = d.InitWallet(c, uid, 0, 0, 0)
}
if err != nil {
log.Error("[dao.wallet|Melonseed] row.Scan err: %v", err)
return
}
return
}
// Detail 详细数据
func (d *Dao) Detail(c context.Context, uid int64) (detail *model.Detail, err error) {
row := d.db.QueryRow(c, fmt.Sprintf(_selDetail, tableIndex(uid)), uid)
detail = &model.Detail{}
err = row.Scan(&detail.Uid, &detail.Gold, &detail.IapGold, &detail.Silver, &detail.GoldRechargeCnt,
&detail.GoldPayCnt, &detail.SilverPayCnt, &detail.CostBase)
if err == sql.ErrNoRows {
detail.Gold = 0
detail.IapGold = 0
detail.Silver = 0
detail.GoldPayCnt = 0
detail.GoldRechargeCnt = 0
detail.SilverPayCnt = 0
detail.CostBase = 0
detail.Uid = uid
err = nil
return
}
if err != nil {
log.Error("[dao.wallet|Detail] row.Scan err: %v", err)
return
}
return
}
func (d *Dao) DetailWithoutDefault(c context.Context, uid int64) (detail *model.Detail, err error) {
row := d.db.QueryRow(c, fmt.Sprintf(_selDetail, tableIndex(uid)), uid)
detail = &model.Detail{}
err = row.Scan(&detail.Uid, &detail.Gold, &detail.IapGold, &detail.Silver, &detail.GoldRechargeCnt,
&detail.GoldPayCnt, &detail.SilverPayCnt, &detail.CostBase)
return
}
// AddGold 添加金瓜子
func (d *Dao) AddGold(c context.Context, uid int64, num int) (affect int64, err error) {
s := fmt.Sprintf(_addGold, tableIndex(uid), num)
return execSqlWithBindParams(d, c, &s, uid, num)
}
// RechargeGold 充值金瓜子 记入充值总值
func (d *Dao) RechargeGold(c context.Context, uid int64, num int) (affect int64, err error) {
s := fmt.Sprintf(_rechargeGold, tableIndex(uid), num, num)
return execSqlWithBindParams(d, c, &s, uid, num, num)
}
// RechargeGold 添加IOS金瓜子
func (d *Dao) AddIapGold(c context.Context, uid int64, num int) (affect int64, err error) {
s := fmt.Sprintf(_addIapGold, tableIndex(uid), num)
return execSqlWithBindParams(d, c, &s, uid, num)
}
// RechargeGold 充值IOS金瓜子 记入充值总值
func (d *Dao) RechargeIapGold(c context.Context, uid int64, num int) (affect int64, err error) {
s := fmt.Sprintf(_rechargeIapGold, tableIndex(uid), num, num)
return execSqlWithBindParams(d, c, &s, uid, num, num)
}
// AddSilver 添加银瓜子
func (d *Dao) AddSilver(c context.Context, uid int64, num int) (affect int64, err error) {
s := fmt.Sprintf(_addSilver, tableIndex(uid), num)
return execSqlWithBindParams(d, c, &s, uid, num)
}
// AddSilver 消费金瓜子 记入消费总值
func (d *Dao) ConsumeGold(c context.Context, uid int64, num int) (affect int64, err error) {
s := fmt.Sprintf(_consumeGold, tableIndex(uid), num, num)
return execSqlWithBindParams(d, c, &s, uid)
}
// AddSilver 消费IOS金瓜子 记入消费总值
func (d *Dao) ConsumeIapGold(c context.Context, uid int64, num int) (affect int64, err error) {
s := fmt.Sprintf(_consumeIapGold, tableIndex(uid), num, num)
return execSqlWithBindParams(d, c, &s, uid)
}
// ConsumeSilver 消费银瓜子 记入消费总值
func (d *Dao) ConsumeSilver(c context.Context, uid int64, num int) (affect int64, err error) {
s := fmt.Sprintf(_consumeSilver, tableIndex(uid), num, num)
return execSqlWithBindParams(d, c, &s, uid)
}
func (d *Dao) ChangeCostBase(c context.Context, uid int64, num int64) (affect int64, err error) {
s := fmt.Sprintf(_changeCosetBase, tableIndex(uid))
return execSqlWithBindParams(d, c, &s, num, uid)
}
// 更新镜像时间 方便测试
func (d *Dao) UpdateSnapShotTime(context context.Context, uid int64, time string) (int64, error) {
s := fmt.Sprintf("update user_wallet_%d set snapshot_time = ?", tableIndex(uid))
return execSqlWithBindParams(d, context, &s, time)
}

View File

@@ -0,0 +1,283 @@
package dao
import (
"context"
"os"
"sync"
"testing"
"time"
"go-common/app/service/live/wallet/conf"
"go-common/library/log"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/wallet/model"
"math/rand"
)
var (
once sync.Once
d *Dao
ctx = context.Background()
testUID int64
r *rand.Rand
)
func initConf() {
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
}
func TestMain(m *testing.M) {
conf.ConfPath = "../cmd/live-wallet-test.toml"
once.Do(startService)
os.Exit(m.Run())
}
func startService() {
initConf()
d = New(conf.Conf)
r = rand.New(rand.NewSource(time.Now().UnixNano()))
testUID = r.Int63n(100000000)
log.Info("test uid:%ld", testUID)
time.Sleep(time.Second * 1)
}
func TestInitWallet(t *testing.T) {
Convey("Init Wallet", t, func() {
once.Do(startService)
_, err := d.InitWallet(ctx, testUID, 0, 0, 0)
So(err, ShouldBeNil)
})
}
func TestMelonseed(t *testing.T) {
Convey("Melon seed", t, func() {
once.Do(startService)
var wallet *model.Melonseed
wallet, err := d.Melonseed(ctx, testUID)
So(err, ShouldBeNil)
So(wallet.Gold, ShouldEqual, 0)
})
}
func TestDetail(t *testing.T) {
Convey("Detail", t, func() {
once.Do(startService)
var detail *model.Detail
detail, err := d.Detail(ctx, testUID)
So(err, ShouldBeNil)
So(detail.Gold, ShouldEqual, 0)
So(detail.Silver, ShouldEqual, 0)
So(detail.SilverPayCnt, ShouldEqual, 0)
So(detail.IapGold, ShouldEqual, 0)
So(detail.GoldRechargeCnt, ShouldEqual, 0)
So(detail.GoldPayCnt, ShouldEqual, 0)
})
}
func TestDao_AddGold(t *testing.T) {
Convey("Add Gold", t, func() {
once.Do(startService)
var testUID = r.Int63n(100000000)
var odetail *model.Detail
odetail, err := d.Detail(ctx, testUID)
So(err, ShouldBeNil)
num := 1000
affect, err := d.AddGold(ctx, testUID, num)
So(err, ShouldBeNil)
So(affect, ShouldBeGreaterThanOrEqualTo, 1) // 插入affect=1 更新affect=2
var ndetail *model.Detail
ndetail, err = d.Detail(ctx, testUID)
So(err, ShouldBeNil)
So(ndetail.Gold-odetail.Gold, ShouldEqual, num)
So(ndetail.GoldRechargeCnt, ShouldEqual, odetail.GoldRechargeCnt)
})
}
func TestDao_RechargeGold(t *testing.T) {
Convey("recharge gold", t, func() {
once.Do(startService)
var testUID = r.Int63n(100000000)
var odetail *model.Detail
odetail, err := d.Detail(ctx, testUID)
So(err, ShouldBeNil)
num := 1000
affect, err := d.RechargeGold(ctx, testUID, num)
So(err, ShouldBeNil)
So(affect, ShouldBeGreaterThanOrEqualTo, 1) // 插入affect=1 更新affect=2
var ndetail *model.Detail
ndetail, err = d.Detail(ctx, testUID)
So(err, ShouldBeNil)
So(ndetail.Gold-odetail.Gold, ShouldEqual, num)
So(ndetail.IapGold-odetail.IapGold, ShouldEqual, 0)
So(ndetail.GoldRechargeCnt-odetail.GoldRechargeCnt, ShouldEqual, num)
})
}
func TestDao_RechargeIapGold(t *testing.T) {
Convey("recharge iap gold", t, func() {
once.Do(startService)
var testUID = r.Int63n(100000000)
var odetail *model.Detail
odetail, err := d.Detail(ctx, testUID)
So(err, ShouldBeNil)
num := 1000
affect, err := d.RechargeIapGold(ctx, testUID, num)
So(err, ShouldBeNil)
So(affect, ShouldBeGreaterThanOrEqualTo, 1) // 插入affect=1 更新affect=2
var ndetail *model.Detail
ndetail, err = d.Detail(ctx, testUID)
So(err, ShouldBeNil)
So(ndetail.Gold-odetail.Gold, ShouldEqual, 0)
So(ndetail.IapGold-odetail.IapGold, ShouldEqual, num)
So(ndetail.GoldRechargeCnt-odetail.GoldRechargeCnt, ShouldEqual, num)
})
}
func TestDao_AddIapGold(t *testing.T) {
Convey("Add iap Gold", t, func() {
once.Do(startService)
var testUID = r.Int63n(100000000)
var odetail *model.Detail
odetail, err := d.Detail(ctx, testUID)
So(err, ShouldBeNil)
num := 1000
affect, err := d.AddIapGold(ctx, testUID, num)
So(err, ShouldBeNil)
So(affect, ShouldBeGreaterThanOrEqualTo, 1) // 插入affect=1 更新affect=2
var ndetail *model.Detail
ndetail, err = d.Detail(ctx, testUID)
So(err, ShouldBeNil)
So(ndetail.Gold-odetail.Gold, ShouldEqual, 0)
So(ndetail.IapGold-odetail.IapGold, ShouldEqual, num)
So(ndetail.GoldRechargeCnt, ShouldEqual, odetail.GoldRechargeCnt)
})
}
func TestDao_AddSilver(t *testing.T) {
Convey("Add silver", t, func() {
once.Do(startService)
var testUID = r.Int63n(100000000)
var odetail *model.Detail
odetail, err := d.Detail(ctx, testUID)
So(err, ShouldBeNil)
num := 1000
affect, err := d.AddSilver(ctx, testUID, num)
So(err, ShouldBeNil)
So(affect, ShouldBeGreaterThanOrEqualTo, 1) // 插入affect=1 更新affect=2
var ndetail *model.Detail
ndetail, err = d.Detail(ctx, testUID)
So(err, ShouldBeNil)
So(ndetail.Gold-odetail.Gold, ShouldEqual, 0)
So(ndetail.IapGold-odetail.IapGold, ShouldEqual, 0)
So(ndetail.Silver-odetail.Silver, ShouldEqual, num)
})
}
func TestDao_ConsumeGold(t *testing.T) {
Convey("Consume Gold", t, func() {
once.Do(startService)
var testUID = r.Int63n(100000000)
var odetail *model.Detail
odetail, err := d.Detail(ctx, testUID)
So(err, ShouldBeNil)
num := 1000
affect, err := d.RechargeGold(ctx, testUID, num)
So(err, ShouldBeNil)
So(affect, ShouldBeGreaterThanOrEqualTo, 1) // 插入affect=1 更新affect=2
pay := 500
affect, err = d.ConsumeGold(ctx, testUID, pay)
So(err, ShouldBeNil)
So(affect, ShouldEqual, 1)
var ndetail *model.Detail
ndetail, err = d.Detail(ctx, testUID)
So(err, ShouldBeNil)
So(ndetail.Gold-odetail.Gold, ShouldEqual, num-pay)
So(ndetail.IapGold-odetail.IapGold, ShouldEqual, 0)
So(ndetail.GoldRechargeCnt-odetail.GoldRechargeCnt, ShouldEqual, num)
So(ndetail.GoldPayCnt-odetail.GoldPayCnt, ShouldEqual, pay)
})
}
func TestDao_ConsumeIapGold(t *testing.T) {
Convey("Consume Iap Gold", t, func() {
once.Do(startService)
var testUID = r.Int63n(100000000)
var odetail *model.Detail
odetail, err := d.Detail(ctx, testUID)
So(err, ShouldBeNil)
num := 1000
affect, err := d.RechargeIapGold(ctx, testUID, num)
So(err, ShouldBeNil)
So(affect, ShouldBeGreaterThanOrEqualTo, 1) // 插入affect=1 更新affect=2
pay := 500
affect, err = d.ConsumeIapGold(ctx, testUID, pay)
So(err, ShouldBeNil)
So(affect, ShouldEqual, 1)
var ndetail *model.Detail
ndetail, err = d.Detail(ctx, testUID)
So(err, ShouldBeNil)
So(ndetail.IapGold-odetail.IapGold, ShouldEqual, num-pay)
So(ndetail.Gold-odetail.Gold, ShouldEqual, 0)
So(ndetail.GoldRechargeCnt-odetail.GoldRechargeCnt, ShouldEqual, num)
So(ndetail.GoldPayCnt-odetail.GoldPayCnt, ShouldEqual, pay)
})
}
func TestDao_ConsumeSilver(t *testing.T) {
Convey("Consume Silver", t, func() {
once.Do(startService)
var testUID = r.Int63n(100000000)
var odetail *model.Detail
odetail, err := d.Detail(ctx, testUID)
So(err, ShouldBeNil)
num := 1000
affect, err := d.AddSilver(ctx, testUID, num)
So(err, ShouldBeNil)
So(affect, ShouldBeGreaterThanOrEqualTo, 1) // 插入affect=1 更新affect=2
pay := 500
affect, err = d.ConsumeSilver(ctx, testUID, pay)
So(err, ShouldBeNil)
So(affect, ShouldEqual, 1)
var ndetail *model.Detail
ndetail, err = d.Detail(ctx, testUID)
So(err, ShouldBeNil)
So(ndetail.IapGold-odetail.IapGold, ShouldEqual, 0)
So(ndetail.Gold-odetail.Gold, ShouldEqual, 0)
So(ndetail.Silver-odetail.Silver, ShouldEqual, num-pay)
So(ndetail.GoldRechargeCnt-odetail.GoldRechargeCnt, ShouldEqual, 0)
So(ndetail.GoldPayCnt-odetail.GoldPayCnt, ShouldEqual, 0)
So(ndetail.SilverPayCnt-odetail.SilverPayCnt, ShouldEqual, pay)
})
}