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,62 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"cache_test.go",
"dao_test.go",
"mysql_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/service/live/xuser/conf:go_default_library",
"//app/service/live/xuser/model:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"cache.go",
"dao.go",
"mysql.go",
],
importpath = "go-common/app/service/live/xuser/dao/vip",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/live/xuser/conf:go_default_library",
"//app/service/live/xuser/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/time: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,168 @@
package vip
import (
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"go-common/app/service/live/xuser/model"
"go-common/library/cache/redis"
"go-common/library/log"
"go-common/library/net/metadata"
"time"
)
// redis cache
const (
_userInfoRedisKey = "us:infoo_v2:%d" // 用户缓存key prefix
_vipFieldName = "vip" // v3 vip attr field
_levelFieldName = "level" // v2 level attr field, Todo: remove level attr
_userExpired = 86400 // user cache expire time
)
type vipCache struct {
Vip interface{} `json:"vip"`
VipTime string `json:"vip_time"`
Svip interface{} `json:"svip"`
SvipTime string `json:"svip_time"`
}
// GetVipFromCache get user vip info from cache
func (d *Dao) GetVipFromCache(ctx context.Context, uid int64) (info *model.VipInfo, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
reply, err := redis.String(conn.Do("HGET", getUserCacheKey(uid), _vipFieldName))
if err != nil {
if err == redis.ErrNil {
// key or field not exists, return nil, nil, back to db
log.Info("[dao.vip.cache|GetVipFromCache] cache key or field not exists err(%v), uid(%d)", err, uid)
return nil, nil
}
log.Error("[dao.vip.cache|GetVipFromCache] hget error(%v), uid(%d)", err, uid)
return
}
if reply == "" {
return nil, nil
}
// ===== begin eat others' dog food =====
// 1.兼容缓存中vip/svip可能是int or string的问题
rawInfo := &vipCache{}
if err = json.Unmarshal([]byte(reply), rawInfo); err != nil {
log.Error("[dao.vip.cache|GetVipFromCache] json.Unmarshal rawInfo error(%v), uid(%d), reply(%s)",
err, uid, reply)
// parse cache json error, return nil, nil, back to db and restore cache
return nil, nil
}
if info, err = d.formatVipCache(rawInfo); err != nil {
log.Error("[dao.vip.cache|GetVipFromCache] format rawInfo error(%v), uid(%d), reply(%s)", err, uid, reply)
return nil, nil
}
// 2.注意!!! cache里的vip_time/svip_time不一定正确可能含有已经过期的time
currentTime := time.Now().Unix()
// vip time
if info.Vip, err = d.checkVipTime(info.VipTime, info.Vip, currentTime); err != nil {
log.Error("[dao.vip.cache|GetVipFromCache] check vip time error(%v), uid(%d), info(%v), reply(%s)",
err, uid, info, reply)
return nil, nil
}
if info.Svip, err = d.checkVipTime(info.SvipTime, info.Svip, currentTime); err != nil {
log.Error("[dao.vip.cache|GetVipFromCache] check svip time error(%v), uid(%d), info(%v), reply(%s)",
err, uid, info, reply)
return nil, nil
}
// ===== end =====
return
}
// formatVipCache 转换vip/svip的格式
func (d *Dao) formatVipCache(info *vipCache) (v *model.VipInfo, err error) {
v = &model.VipInfo{
VipTime: info.VipTime,
SvipTime: info.SvipTime,
}
if v.Vip, err = toInt(info.Vip); err != nil {
return
}
if v.Svip, err = toInt(info.Svip); err != nil {
return
}
// format info struct
v = d.initInfo(v)
return
}
// checkVipTime 检查缓存中vip_time/svip_time是否过期
func (d *Dao) checkVipTime(t string, f int, compare int64) (int, error) {
if t == model.TimeEmpty {
if f != 0 {
return 0, errors.New("empty time with not zero flag.")
}
} else {
vt, err := time.Parse(model.TimeNano, t)
if err != nil {
return 0, errors.New("time parse error.")
}
if vt.Unix() <= compare {
return 0, nil
}
}
return f, nil
}
// SetVipCache set vip to cache
func (d *Dao) SetVipCache(ctx context.Context, uid int64, info *model.VipInfo) (err error) {
var vipJson []byte
conn := d.redis.Get(ctx)
key := getUserCacheKey(uid)
defer conn.Close()
// format info struct
info = d.initInfo(info)
// format info json string
vipJson, err = json.Marshal(info)
if err != nil {
log.Error("[dao.vip.cache|SetVipCache] json.Marshal error(%v), uid(%d), info(%v)", err, uid, info)
// if marshal error, clear cache
goto CLEAR
}
_, err = conn.Do("HSET", key, _vipFieldName, string(vipJson))
if err != nil {
log.Error("[dao.vip.cache|SetVipCache] HSET error(%v), uid(%d), info(%v)", err, uid, info)
// if hset error, clear cache
goto CLEAR
}
_, err = conn.Do("EXPIRE", key, _userExpired)
if err != nil {
log.Error("[dao.vip.cache|SetVipCache] EXPIRE error(%v), uid(%d), info(%v)", err, uid, info)
// if set expire error, clear cache
goto CLEAR
}
return
CLEAR:
log.Error("[dao.vip.cache|SetVipCache] set error, aysnc clear, uid(%d), info(%v)", uid, info)
go d.ClearCache(metadata.WithContext(ctx), uid)
return
}
// ClearCache clear user's vip and level field cache
// Todo: remove level attr
func (d *Dao) ClearCache(ctx context.Context, uid int64) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := getUserCacheKey(uid)
_, err = conn.Do("HDEL", key, _vipFieldName, _levelFieldName)
if err != nil {
err = errors.Wrapf(err, "conn.Do(HDEL, %s, %s, %s)", key, _vipFieldName, _levelFieldName)
log.Error("[dao.vip.cache|ClearCache] hdel uid(%d) vip and level attr err(%v)", uid, err)
}
return
}
func getUserCacheKey(uid int64) string {
return fmt.Sprintf(_userInfoRedisKey, uid)
}

View File

@ -0,0 +1,169 @@
package vip
import (
"context"
"fmt"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/xuser/model"
"go-common/library/log"
"testing"
"time"
)
func TestDao_GetVipFromCache(t *testing.T) {
initd()
Convey("test get vip cache", t, testWithTestUser(func(u *TestUser) {
var (
ctx = context.Background()
info *model.VipInfo
err error
conn = d.redis.Get(ctx)
key = getUserCacheKey(u.Uid)
)
log.Info("TestDao_GetVipFromCache uid(%d), key(%s)", u.Uid, key)
// delete key at begin
conn.Do("DEL", key)
// should get nil info and err
Convey("should get nil info and err", func() {
info, err = d.GetVipFromCache(ctx, u.Uid)
So(info, ShouldBeNil)
So(err, ShouldBeNil)
})
// set empty data
Convey("set empty data", func() {
conn.Do("HSET", key, _vipFieldName, "")
info, err = d.GetVipFromCache(ctx, u.Uid)
So(info, ShouldBeNil)
So(err, ShouldBeNil)
})
// set not a json
Convey("set not a json", func() {
conn.Do("HSET", key, _vipFieldName, "test")
info, err = d.GetVipFromCache(ctx, u.Uid)
So(info, ShouldBeNil)
So(err, ShouldBeNil)
})
// test vip/svip string format
Convey("test vip/svip string format", func() {
vipTime := time.Now().Add(time.Hour * 12).Format(model.TimeNano)
svipTime := time.Now().AddDate(0, -1, 0).Format(model.TimeNano)
conn.Do("HSET", key, _vipFieldName, fmt.Sprintf(`{"vip":"1","vip_time":"%s","svip":0,"svip_time":"%s"}`, vipTime, svipTime))
info, err = d.GetVipFromCache(ctx, u.Uid)
So(info.Vip, ShouldEqual, 1)
So(info.Svip, ShouldEqual, 0)
So(err, ShouldBeNil)
})
// set valid data
Convey("set valid data", func() {
vipTime := time.Now().Add(time.Hour * 12).Format(model.TimeNano)
conn.Do("HSET", key, _vipFieldName, fmt.Sprintf(`{"vip":1,"vip_time":"%s","svip":0,"svip_time":"0000-00-00 00:00:00"}`, vipTime))
info, err = d.GetVipFromCache(ctx, u.Uid)
So(info, ShouldNotBeNil)
So(info.Vip, ShouldEqual, 1)
So(info.VipTime, ShouldEqual, vipTime)
So(info.Svip, ShouldEqual, 0)
So(info.SvipTime, ShouldEqual, model.TimeEmpty)
So(err, ShouldBeNil)
})
// expired vip time
Convey("set valid but expired vip time", func() {
vip := 1
vipTime := time.Now().AddDate(0, 0, -1).Format(model.TimeNano)
svip := 1
svipTime := time.Now().AddDate(0, -1, -1).Format(model.TimeNano)
conn.Do("HSET", key, _vipFieldName, fmt.Sprintf(`{"vip":%d,"vip_time":"%s","svip":%d,"svip_time":"%s"}`, vip, vipTime, svip, svipTime))
info, err = d.GetVipFromCache(ctx, u.Uid)
log.Info("expired vip time, info(%+v)", info)
So(info, ShouldNotBeNil)
So(info.Vip, ShouldEqual, 0)
So(info.VipTime, ShouldEqual, vipTime)
So(info.Svip, ShouldEqual, 0)
So(info.SvipTime, ShouldEqual, svipTime)
So(err, ShouldBeNil)
})
}))
}
func TestDao_SetVipCache(t *testing.T) {
initd()
Convey("test set vip cache", t, testWithTestUser(func(u *TestUser) {
var (
ctx = context.Background()
info *model.VipInfo
err error
conn = d.redis.Get(ctx)
key = getUserCacheKey(u.Uid)
)
log.Info("TestDao_GetVipFromCache uid(%d), key(%s)", u.Uid, key)
// delete key at begin
conn.Do("DEL", key)
// nil info
Convey("nil info", func() {
err = d.SetVipCache(ctx, u.Uid, nil)
So(err, ShouldBeNil)
info, err = d.GetVipFromCache(ctx, u.Uid)
log.Info("TestDao_SetVipCache get info1(%v), err(%v)", info, err)
So(err, ShouldBeNil)
So(info.Vip, ShouldEqual, 0)
So(info.VipTime, ShouldEqual, model.TimeEmpty)
So(info.Svip, ShouldEqual, 0)
So(info.SvipTime, ShouldEqual, model.TimeEmpty)
})
// set valid info
Convey("set valid info", func() {
info = &model.VipInfo{
Vip: 1,
VipTime: time.Now().Add(time.Hour * 12).Format(model.TimeNano),
Svip: 1,
SvipTime: time.Now().Add(time.Hour * 6).Format(model.TimeNano),
}
err = d.SetVipCache(ctx, u.Uid, info)
So(err, ShouldBeNil)
info2, err := d.GetVipFromCache(ctx, u.Uid)
log.Info("TestDao_SetVipCache get info2(%v), err(%v)", info, err)
So(err, ShouldBeNil)
So(info2.Vip, ShouldEqual, info.Vip)
So(info2.VipTime, ShouldEqual, info.VipTime)
So(info2.Svip, ShouldEqual, info.Svip)
So(info2.SvipTime, ShouldEqual, info.SvipTime)
})
}))
}
func TestDao_ClearCache(t *testing.T) {
initd()
Convey("test clear cache", t, testWithTestUser(func(u *TestUser) {
var (
ctx = context.Background()
err error
conn = d.redis.Get(ctx)
key = getUserCacheKey(u.Uid)
)
log.Info("TestDao_ClearCache uid(%d), key(%s)", u.Uid, key)
// delete key at begin
conn.Do("DEL", key)
// del already deleted key
Convey("del already deleted key", func() {
err = d.ClearCache(ctx, u.Uid)
So(err, ShouldBeNil)
})
// set valid info
Convey("set valid info", func() {
err = d.SetVipCache(ctx, u.Uid, nil)
So(err, ShouldBeNil)
err = d.ClearCache(ctx, u.Uid)
So(err, ShouldBeNil)
})
}))
}

View File

@ -0,0 +1,84 @@
package vip
import (
"context"
"fmt"
"go-common/app/service/live/xuser/conf"
"go-common/app/service/live/xuser/model"
"go-common/library/cache/redis"
xsql "go-common/library/database/sql"
"strconv"
)
// Dao vip dao
type Dao struct {
c *conf.Config
db *xsql.DB
redis *redis.Pool
}
// New new vip dao
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
db: xsql.NewMySQL(c.LiveUserMysql),
redis: redis.NewPool(c.VipRedis),
}
return
}
// Close close the resource.
func (d *Dao) Close() {
d.db.Close()
d.redis.Close()
}
// initInfo init info struct
func (d *Dao) initInfo(info *model.VipInfo) *model.VipInfo {
if info == nil {
info = &model.VipInfo{Vip: 0, VipTime: model.TimeEmpty, Svip: 0, SvipTime: model.TimeEmpty}
} else {
if info.VipTime == "" {
info.VipTime = model.TimeEmpty
}
if info.SvipTime == "" {
info.SvipTime = model.TimeEmpty
}
}
return info
}
// Ping dao ping
func (d *Dao) Ping(ctx context.Context) error {
// TODO: add mc,redis... if you use
return nil
}
// toInt try trans interface input to int
func toInt(in interface{}) (int, error) {
switch in.(type) {
case int:
return in.(int), nil
case int32:
return int(in.(int32)), nil
case int64:
return int(in.(int64)), nil
case float32:
return int(in.(float32)), nil
case float64:
return int(in.(float64)), nil
case string:
i, err := strconv.Atoi(in.(string))
if err != nil {
return 0, err
}
return i, nil
case []byte:
i, err := strconv.Atoi(string(in.([]byte)))
if err != nil {
return 0, err
}
return i, nil
}
return 0, fmt.Errorf("invalid input(%v)", in)
}

View File

@ -0,0 +1,70 @@
package vip
import (
"context"
"flag"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/xuser/conf"
"math/rand"
"path/filepath"
"testing"
)
// vip dao and conf
var (
d *Dao
)
type TestUser struct {
Uid int64
}
// initd init vip dao
func initd() {
dir, _ := filepath.Abs("../../cmd/test.toml")
flag.Set("conf", dir)
flag.Set("deploy_env", "uat")
conf.Init()
d = New(conf.Conf)
}
func initTestUser() *TestUser {
return &TestUser{
Uid: int64(rand.Int31()),
}
}
func (t *TestUser) Reset() {
d.ClearCache(context.Background(), t.Uid)
d.deleteVip(context.Background(), t.Uid)
}
func testWithTestUser(f func(u *TestUser)) func() {
u := initTestUser()
return func() {
f(u)
u.Reset()
}
}
func TestToInt(t *testing.T) {
var (
err error
out int
)
Convey("test toInt", t, func() {
out, err = toInt(1)
So(out, ShouldEqual, 1)
So(err, ShouldBeNil)
})
Convey("test toInt", t, func() {
out, err = toInt("123")
So(out, ShouldEqual, 123)
So(err, ShouldBeNil)
})
Convey("test toInt", t, func() {
out, err = toInt("test")
So(out, ShouldEqual, 0)
So(err, ShouldNotBeNil)
})
}

View File

@ -0,0 +1,204 @@
package vip
import (
"context"
"crypto/md5"
"database/sql"
"encoding/hex"
"fmt"
"strconv"
"time"
"go-common/app/service/live/xuser/model"
"go-common/library/log"
xtime "go-common/library/time"
"github.com/pkg/errors"
xsql "go-common/library/database/sql"
)
const (
_userVipRecordPrefix = "user_vip_record_%d"
_userVipRecordCount = 10
_userLevelPrefix = "user_"
)
var (
errUpdateVipTimeInvalid = errors.New("update vip but vip_time invalid")
)
var (
// get vip info from user_x table
_getVipInfo = "SELECT `vip`,`vip_time`,`svip`,`svip_time` FROM `%s` WHERE uid=?;"
// insert into user_vip_record_n
_insertUserVipRecord = "INSERT INTO `%s` (`uid`,`vip_type`,`vip_num`,`order_id`,`platform`,`source`) VALUES (?,?,?,?,?,?);"
// update user_vip_record_n before_time & status after update vip success
_updateUserVipRecordStatus = "UPDATE `%s` SET `before_vip`=?,`before_vip_time`=?,`before_svip`=?,`before_svip_time`=?,`status`=? WHERE `id`=?;"
// update user_x vip info
_updateAllVip = "UPDATE `%s` SET `vip`=?,`vip_time`=?,`svip`=?,`svip_time`=? WHERE uid=?;"
_updateVip = "UPDATE `%s` SET `vip`=?,`vip_time`=? WHERE uid=?;"
// insert vip
_insertVip = "INSERT INTO `%s` (`uid`,`vip`,`vip_time`,`svip`,`svip_time`) VALUES (?,?,?,?,?);"
// delete vip
_deleteVip = "DELETE FROM `%s` WHERE `uid`=? LIMIT 1;"
// insert ap_vip_record
_insertApVipRecord = "INSERT INTO `ap_vip_record`(`uid`,`type`,`vip_time`,`platform`) VALUES (?,?,?,?);"
)
// GetVipFromDB get vip info by uid
// return error maybe sql no row or just scan error, how to handle decided by upper business
func (d *Dao) GetVipFromDB(ctx context.Context, uid int64) (info *model.VipInfo, err error) {
var (
vipTime, sVipTime xtime.Time
currentTime = xtime.Time(time.Now().Unix())
)
row := d.db.QueryRow(ctx, fmt.Sprintf(_getVipInfo, getUserLevelTable(uid)), uid)
info = &model.VipInfo{}
if err = row.Scan(&info.Vip, &vipTime, &info.Svip, &sVipTime); err != nil {
log.Error("[dao.vip.mysql|GetVipFromDB] row scan error(%v), uid(%d)", err, uid)
// no rows in user_x table, async insert one, don't return error
if err == xsql.ErrNoRows {
go d.createVip(context.TODO(), uid, info)
err = nil
return
}
return
}
// format info vip time
if vipTime <= 0 {
info.VipTime = model.TimeEmpty
} else {
info.VipTime = vipTime.Time().Format(model.TimeNano)
}
if sVipTime <= 0 {
info.SvipTime = model.TimeEmpty
} else {
info.SvipTime = sVipTime.Time().Format(model.TimeNano)
}
// format vip & svip
// 注意!!! db里的数据不一定正确可能含有已经过期的time
if vipTime <= currentTime {
info.Vip = 0
}
if sVipTime <= currentTime {
info.Svip = 0
}
return
}
// AddVip update user_n vip fields, add vip/svip time
// weather add vip or svip, vipTime should not be empty
func (d *Dao) AddVip(ctx context.Context, uid int64, vipTime, sVipTime xtime.Time) (row int64, err error) {
var (
vt, st string
res sql.Result
updateType string
currentTime = xtime.Time(time.Now().Unix())
)
if vipTime <= currentTime {
return 0, errUpdateVipTimeInvalid
}
vt = vipTime.Time().Format(model.TimeNano)
if sVipTime > currentTime {
// update vip and svip
st = sVipTime.Time().Format(model.TimeNano)
updateType = "all"
res, err = d.db.Exec(ctx, fmt.Sprintf(_updateAllVip, getUserLevelTable(uid)), 1, vt, 1, st, uid)
} else {
// update vip only
updateType = "vip"
res, err = d.db.Exec(ctx, fmt.Sprintf(_updateVip, getUserLevelTable(uid)), 1, vt, uid)
}
if err != nil {
log.Error("[dao.vip.mysql|AddVip] update vip error(%v), type(%s), uid(%d), vip(%s), svip(%s)",
err, updateType, uid, vt, st)
return
}
row, _ = res.RowsAffected()
return
}
// createVip create user_n vip row, for internal usage only. Do not use in business!
func (d *Dao) createVip(ctx context.Context, uid int64, info *model.VipInfo) (err error) {
info = d.initInfo(info)
log.Info("[dao.vip.mysql|createVip] create user_n row, uid(%d), info(%v)", uid, info)
_, err = d.db.Exec(ctx, fmt.Sprintf(_insertVip, getUserLevelTable(uid)),
uid, info.Vip, info.VipTime, info.Svip, info.SvipTime)
if err != nil {
log.Error("[dao.vip.mysql|createVip] create error(%v), uid(%d), info(%v)", err, uid, info)
}
return
}
// deleteVip delete user_n vip row, for internal usage only. Do not use in business!
func (d *Dao) deleteVip(ctx context.Context, uid int64) (err error) {
log.Info("[dao.vip.mysql|deleteVip] delete user_n row, uid(%d)", uid)
_, err = d.db.Exec(ctx, fmt.Sprintf(_deleteVip, getUserLevelTable(uid)), uid)
if err != nil {
log.Error("[dao.vip.mysql|deleteVip] delete error(%v), uid(%d)", err, uid)
}
return
}
// CreateVipRecord create user vip record if not exists
// return error maybe unique key exists err or other db error, upper business should notice
// unique key is (uid,order_id)
func (d *Dao) CreateVipRecord(ctx context.Context, req *model.VipBuy) (recordID int64, err error) {
res, err := d.db.Exec(ctx, fmt.Sprintf(_insertUserVipRecord, getUserVipRecordTable(req.Uid)),
req.Uid, req.GoodID, req.GoodNum, req.OrderID, req.Platform, req.Source)
if err != nil {
log.Error("[dao.vip.mysql|CreateUserVipRecord] create user vip record error(%v), req(%v)", err, req)
return
}
if recordID, err = res.LastInsertId(); err != nil {
err = errors.WithStack(err)
log.Error("[dao.vip.mysql|CreateUserVipRecord] get last insert id error(%v), req(%v)", err, req)
}
return
}
// UpdateVipRecord update user vip record after buy success
func (d *Dao) UpdateVipRecord(ctx context.Context, recordID, uid int64, info *model.VipInfo) (err error) {
execSql := fmt.Sprintf(_updateUserVipRecordStatus, getUserVipRecordTable(uid))
_, err = d.db.Exec(ctx, execSql, info.Vip, info.VipTime, info.Svip, info.SvipTime, model.BuyStatusSuccess, recordID)
if err != nil {
log.Error("[dao.vip.mysql|UpdateVipRecordLater] update error(%v), record id(%d), uid(%d), info(%v)",
err, recordID, uid, info)
}
return
}
// CreateApVipRecord create ap_vip_record
func (d *Dao) CreateApVipRecord(ctx context.Context, record *model.VipRecord) (row int64, err error) {
res, err := d.db.Exec(ctx, _insertApVipRecord, record.Uid, record.VipType, record.AfterVipTime, record.Platform)
if err != nil {
log.Error("[dao.vip.mysql|CreateApVipRecord] insert ap_vip_record error(%v), record(%v)", err, record)
return
}
row, _ = res.RowsAffected()
return
}
// getUserLevelTable get user_x table by uid
func getUserLevelTable(uid int64) string {
uidStr := strconv.FormatInt(uid, 10)
md5Ctx := md5.New()
md5Ctx.Write([]byte(uidStr))
cipher := md5Ctx.Sum(nil)
return _userLevelPrefix + hex.EncodeToString(cipher)[0:1]
}
// getUserVipRecordTable get user_vip_record_x table by uid
func getUserVipRecordTable(uid int64) string {
return fmt.Sprintf(_userVipRecordPrefix, uid%_userVipRecordCount)
}

View File

@ -0,0 +1,150 @@
package vip
import (
"context"
"fmt"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/xuser/model"
"go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
"math/rand"
"testing"
"time"
)
func Test_getUserLevelTable(t *testing.T) {
initd()
Convey("test get user_x table name by uid", t, func() {
var uid = int64(123)
table := getUserLevelTable(uid)
So(table, ShouldEqual, "user_2")
})
}
func Test_getUserVipRecordTable(t *testing.T) {
initd()
Convey("test get user_vip_record_x table name by uid", t, func() {
var uid = rand.Int63()
log.Info("Test_getUserVipRecordTable uid(%d)", uid)
table := getUserVipRecordTable(uid)
t := fmt.Sprintf(_userVipRecordPrefix, uid%_userVipRecordCount)
So(table, ShouldEqual, t)
})
}
func TestDao_GetVipFromDB(t *testing.T) {
initd()
Convey("test get vip from db", t, testWithTestUser(func(u *TestUser) {
log.Info("TestDao_GetVipFromDB uid(%d), table(%s)", u.Uid, getUserLevelTable(u.Uid))
var (
ctx = context.Background()
err error
info *model.VipInfo
)
// delete random uid at begin
err = d.deleteVip(ctx, u.Uid)
So(err, ShouldBeNil)
// get nil result from db
Convey("get nil result from db", func() {
info, err = d.GetVipFromDB(ctx, u.Uid)
So(err, ShouldResemble, sql.ErrNoRows)
So(info, ShouldNotBeNil)
So(info.Vip, ShouldEqual, 0)
So(info.VipTime, ShouldEqual, "")
So(info.Svip, ShouldEqual, 0)
So(info.SvipTime, ShouldEqual, "")
})
// insert and then get
Convey("insert and then get", func() {
var info2 *model.VipInfo
info = &model.VipInfo{
Vip: 1,
VipTime: time.Now().Add(time.Hour * 12).Format(model.TimeNano),
Svip: 1,
SvipTime: time.Now().Add(time.Hour * 12).Format(model.TimeNano),
}
err = d.createVip(ctx, u.Uid, info)
So(err, ShouldBeNil)
info2, err = d.GetVipFromDB(ctx, u.Uid)
log.Info("TestDao_GetVipFromDB info2(%v)", info2)
So(err, ShouldBeNil)
So(info, ShouldResemble, info2)
})
// valid info but expired vip time
Convey("insert valid but expired vip time", func() {
info = &model.VipInfo{
Vip: 1,
VipTime: time.Now().AddDate(0, -1, 0).Format(model.TimeNano),
Svip: 1,
SvipTime: time.Now().AddDate(-1, 0, 0).Format(model.TimeNano),
}
err = d.createVip(ctx, u.Uid, info)
So(err, ShouldBeNil)
info2, err := d.GetVipFromDB(ctx, u.Uid)
log.Info("TestDao_GetVipFromDB info2(%v)", info2)
So(err, ShouldBeNil)
So(info2.Vip, ShouldEqual, 0)
So(info2.Svip, ShouldEqual, 0)
So(info2.VipTime, ShouldEqual, info.VipTime)
So(info2.SvipTime, ShouldEqual, info.SvipTime)
})
}))
}
func TestDao_AddVip(t *testing.T) {
initd()
Convey("test add vip", t, testWithTestUser(func(u *TestUser) {
log.Info("TestDao_GetVipFromDB uid(%d), table(%s)", u.Uid, getUserLevelTable(u.Uid))
var (
ctx = context.Background()
err error
info *model.VipInfo
row int64
)
// create one row at begin
info = &model.VipInfo{
Vip: 1,
VipTime: time.Now().Add(time.Hour * 12).Format(model.TimeNano),
Svip: 1,
SvipTime: time.Now().Add(time.Hour * 12).Format(model.TimeNano),
}
err = d.createVip(ctx, u.Uid, info)
So(err, ShouldBeNil)
// empty vip time, should return err
Convey("empty vip time, should return err", func() {
row, err = d.AddVip(ctx, u.Uid, 0, 0)
So(row, ShouldEqual, 0)
So(err, ShouldResemble, errUpdateVipTimeInvalid)
})
// add vip and svip time
Convey("add vip and svip time", func() {
// add one month vip
dt := xtime.Time(30 * 86400)
vtime, err := time.Parse(model.TimeNano, info.VipTime)
So(err, ShouldBeNil)
newvtime := xtime.Time(vtime.Unix()) + dt
log.Info("TestDao_AddVip info(%v), oldvt(%v), newvtime(%v), dt(%v)", info, vtime.Unix(), newvtime, dt)
row, err := d.AddVip(ctx, u.Uid, newvtime, 0)
So(row, ShouldEqual, 1)
So(err, ShouldBeNil)
info2, err := d.GetVipFromDB(ctx, u.Uid)
log.Info("TestDao_AddVip info2(%v)", info2)
So(err, ShouldBeNil)
So(info2.Vip, ShouldEqual, info.Vip)
So(info2.Svip, ShouldEqual, info.Svip)
So(info2.SvipTime, ShouldEqual, info.SvipTime)
So(info2.VipTime, ShouldEqual, newvtime.Time().Format(model.TimeNano))
})
}))
}