Initial commit

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

View File

@@ -0,0 +1,40 @@
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/service/main/tv/internal/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/http/blademaster/middleware/verify:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace: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,140 @@
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/http/blademaster/middleware/verify"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
xtime "go-common/library/time"
"github.com/BurntSushi/toml"
)
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config .
type Config struct {
Log *log.Config
BM *bm.ServerConfig
Verify *verify.Config
Tracer *trace.Config
Memcache *memcache.Config
MySQL *sql.Config
GRPC *warden.ServerConfig
HTTPClient *bm.ClientConfig
MVIPClient *warden.ClientConfig
ACCClient *warden.ClientConfig
YST *YstConfig
PAY *PayConfig
Ticker *TickerConfig
MVIP *MVIPConfig
CacheTTL *CacheTTL
}
// CacheTTL contains ttl configs.
type CacheTTL struct {
UserInfoTTL int32
PayParamTTL int32
LockTTL int32
}
// TickerConfig contains durations configs of ticker proc.
type TickerConfig struct {
PanelRefreshDuration string
UnpaidDurationStime string
UnpaidDurationEtime string
UnpaidRefreshDuratuion string
}
// MVIPConfig contains mvip configs.
type MVIPConfig struct {
BatchIdsMap map[string]int
BatchUserInfoUrl string
}
// YstConfig contains yst configs.
type YstConfig struct {
Domain string
Key string
}
// PayConfig contains pay configs.
type PayConfig struct {
PayExpireDuration string
RenewFromDuration string
RenewToDuration string
OrderRateFromDuration xtime.Duration
OrderRateMaxNumber int
QrURL string
GuestQrURL string
}
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
}
go func() {
for range client.Event() {
log.Info("config reload")
if load() != nil {
log.Error("config reload error (%v)", err)
}
}
}()
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,88 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"account.go",
"change_history.go",
"contract.go",
"dao.cache.go",
"dao.go",
"lock.go",
"main_vip.go",
"mc.cache.go",
"mc.cache.key.go",
"pay_order.go",
"price_config.go",
"user_info.go",
"yst.go",
],
importpath = "go-common/app/service/main/tv/internal/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/account/api:go_default_library",
"//app/service/main/tv/internal/conf:go_default_library",
"//app/service/main/tv/internal/model:go_default_library",
"//app/service/main/tv/internal/pkg:go_default_library",
"//app/service/main/vipinfo/api:go_default_library",
"//library/cache/memcache: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/stat/prom:go_default_library",
"//library/sync/pipeline/fanout: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"],
)
go_test(
name = "go_default_test",
srcs = [
"account_test.go",
"change_history_test.go",
"contract_test.go",
"dao.cache_test.go",
"dao_test.go",
"lock_test.go",
"main_vip_test.go",
"mc.cache.key_test.go",
"mc.cache_test.go",
"pay_order_test.go",
"price_config_test.go",
"user_info_test.go",
"yst_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/service/main/tv/internal/conf:go_default_library",
"//app/service/main/tv/internal/model:go_default_library",
"//library/ecode:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,19 @@
package dao
import (
"context"
"go-common/app/service/main/account/api"
"go-common/library/log"
)
// AccountInfo queries account info by user id.
func (d *Dao) AccountInfo(c context.Context, mid int64) (ai *api.Info, err error) {
req := &api.MidReq{Mid: int64(mid)}
res, err := d.accCli.Info3(c, req)
if err != nil {
log.Error("d.AccountInfo(%d) err(%v)", mid, err)
return
}
log.Info("d.AccountInfo(%d) res(%+v)", mid, res.Info)
return res.Info, nil
}

View File

@@ -0,0 +1,24 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoAccountInfo(t *testing.T) {
convey.Convey("AccountInfo", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ai, err := d.AccountInfo(c, mid)
ctx.Convey("Then err should be nil.ai should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ai, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,110 @@
package dao
import (
"context"
"database/sql"
"go-common/app/service/main/tv/internal/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
"github.com/pkg/errors"
)
const (
_getUserChangeHistoryByID = "SELECT `id`, `mid`, `change_type`, `change_time`, `order_no`, `days`, `operator_id`, `remark`, `ctime`, `mtime` FROM `tv_user_change_history` WHERE `id`=?"
_getUserChangeHistorysByMid = "SELECT `id`, `mid`, `change_type`, `change_time`, `order_no`, `days`, `operator_id`, `remark`, `ctime`, `mtime` FROM `tv_user_change_history` WHERE `mid`=? ORDER BY `ctime` DESC LIMIT ?,?"
_countUserChangeHistoryByMid = "SELECT count(*) FROM `tv_user_change_history` WHERE `mid`=?"
_getUserChangeHistorysByMidAndCtime = "SELECT `id`, `mid`, `change_type`, `change_time`, `order_no`, `days`, `operator_id`, `remark`, `ctime`, `mtime` FROM `tv_user_change_history` WHERE `mid`=? AND `ctime`>=? AND `ctime`<? ORDER BY `ctime` DESC LIMIT ?,?"
_countUserChangeHistoryByMidAndCtime = "SELECT count(*) FROM `tv_user_change_history` WHERE `mid`=? AND `ctime`>=? AND `ctime`<?"
_insertUserChangeHistory = "INSERT INTO tv_user_change_history (`mid`, `change_type`, `change_time`, `order_no`, `days`, `operator_id`, `remark`) VALUES (?,?,?,?,?,?,?)"
)
// UserChangeHistoryByID quires one row from tv_user_change_history.
func (d *Dao) UserChangeHistoryByID(c context.Context, id int32) (uch *model.UserChangeHistory, err error) {
row := d.db.QueryRow(c, _getUserChangeHistoryByID, id)
uch = &model.UserChangeHistory{}
err = row.Scan(&uch.ID, &uch.Mid, &uch.ChangeType, &uch.ChangeTime, &uch.OrderNo, &uch.Days, &uch.OperatorId, &uch.Remark, &uch.Ctime, &uch.Mtime)
if err != nil {
log.Error("rows.Scan(%s) error(%v)", _getUserChangeHistoryByID, err)
err = errors.WithStack(err)
return nil, err
}
return uch, nil
}
// UserChangeHistorysByMid quires rows from tv_user_change_history.
func (d *Dao) UserChangeHistorysByMid(c context.Context, mid int64, pn, ps int32) (res []*model.UserChangeHistory, total int, err error) {
res = make([]*model.UserChangeHistory, 0)
totalRow := d.db.QueryRow(c, _countUserChangeHistoryByMid, mid)
if err = totalRow.Scan(&total); err != nil {
log.Error("row.ScanCount error(%v)", err)
err = errors.WithStack(err)
return
}
rows, err := d.db.Query(c, _getUserChangeHistorysByMid, mid, (pn-1)*ps, ps)
if err != nil {
log.Error("db.Query(%s) error(%v)", _getUserChangeHistorysByMid, err)
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
uch := &model.UserChangeHistory{}
if err = rows.Scan(&uch.ID, &uch.Mid, &uch.ChangeType, &uch.ChangeTime, &uch.OrderNo, &uch.Days, &uch.OperatorId, &uch.Remark, &uch.Ctime, &uch.Mtime); err != nil {
log.Error("rows.Scan() error(%v)", err)
err = errors.WithStack(err)
return
}
res = append(res, uch)
}
return
}
// UserChangeHistorysByMidAndCtime quires rows from tv_user_change_history.
func (d *Dao) UserChangeHistorysByMidAndCtime(c context.Context, mid int64, from, to xtime.Time, pn, ps int32) (res []*model.UserChangeHistory, total int, err error) {
res = make([]*model.UserChangeHistory, 0)
totalRow := d.db.QueryRow(c, _countUserChangeHistoryByMidAndCtime, mid, from, to)
if err = totalRow.Scan(&total); err != nil {
log.Error("row.ScanCount error(%v)", err)
err = errors.WithStack(err)
return
}
rows, err := d.db.Query(c, _getUserChangeHistorysByMidAndCtime, mid, from, to, (pn-1)*ps, ps)
if err != nil {
log.Error("db.Query(%s) error(%v)", _getUserChangeHistorysByMidAndCtime, err)
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
uch := &model.UserChangeHistory{}
if err = rows.Scan(&uch.ID, &uch.Mid, &uch.ChangeType, &uch.ChangeTime, &uch.OrderNo, &uch.Days, &uch.OperatorId, &uch.Remark, &uch.Ctime, &uch.Mtime); err != nil {
log.Error("rows.Scan() error(%v)", err)
err = errors.WithStack(err)
return
}
res = append(res, uch)
}
return
}
// TxInsertUserChangeHistory insert one row into tv_user_change_history.
func (d *Dao) TxInsertUserChangeHistory(ctx context.Context, tx *xsql.Tx, uch *model.UserChangeHistory) (id int64, err error) {
var (
res sql.Result
)
if res, err = tx.Exec(_insertUserChangeHistory, uch.Mid, uch.ChangeType, uch.ChangeTime, uch.OrderNo, uch.Days, uch.OperatorId, uch.Remark); err != nil {
log.Error("tx.Exec(%s) error(%v)", _insertUserChangeHistory, err)
err = errors.WithStack(err)
return
}
if id, err = res.LastInsertId(); err != nil {
log.Error("res.LastInsertId(%s) error(%v)", _insertUserChangeHistory, err)
err = errors.WithStack(err)
return
}
return
}

View File

@@ -0,0 +1,95 @@
package dao
import (
"context"
"go-common/app/service/main/tv/internal/model"
xtime "go-common/library/time"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoUserChangeHistoryByID(t *testing.T) {
convey.Convey("UserChangeHistoryByID", t, func(ctx convey.C) {
var (
c = context.Background()
id = int32(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
uch, err := d.UserChangeHistoryByID(c, id)
ctx.Convey("Then err should be nil.uch should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(uch, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoUserChangeHistorysByMid(t *testing.T) {
convey.Convey("UserChangeHistorysByMid", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
pn = int32(1)
ps = int32(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, total, err := d.UserChangeHistorysByMid(c, mid, pn, ps)
ctx.Convey("Then err should be nil.res,total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoUserChangeHistorysByMidAndCtime(t *testing.T) {
convey.Convey("UserChangeHistorysByMidAndCtime", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
from = xtime.Time(0)
to = xtime.Time(time.Now().Unix())
pn = int32(1)
ps = int32(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, total, err := d.UserChangeHistorysByMidAndCtime(c, mid, from, to, pn, ps)
ctx.Convey("Then err should be nil.res,total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxInsertUserChangeHistory(t *testing.T) {
convey.Convey("TxInsertUserChangeHistory", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
uch = &model.UserChangeHistory{
Mid: 27515308,
ChangeType: model.UserChangeTypeCancelContract,
ChangeTime: xtime.Time(time.Now().Unix()),
OrderNo: "T12345678345678",
Days: 31,
OperatorId: "",
Remark: "test",
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
id, err := d.TxInsertUserChangeHistory(c, tx, uch)
ctx.Convey("Then err should be nil.id should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(id, convey.ShouldNotBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}

View File

@@ -0,0 +1,81 @@
package dao
import (
"context"
"database/sql"
"go-common/app/service/main/tv/internal/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_getUserContractByMid = "SELECT `id`, `mid`, `contract_id`, `order_no`, `is_deleted`, `ctime`, `mtime` FROM `tv_user_contract` WHERE `mid`=? AND `is_deleted`=0"
_getUserContractByContractId = "SELECT `id`, `mid`, `contract_id`, `order_no`, `is_deleted`, `ctime`, `mtime` FROM `tv_user_contract` WHERE `contract_id`=? AND `is_deleted`=0"
_deleteUserContract = "UPDATE `tv_user_contract` SET `is_deleted`=1 WHERE `id`=?"
_insertUserContract = "INSERT INTO tv_user_contract (`mid`, `contract_id`, `order_no`) VALUES (?,?,?)"
)
// UserContractByMid quires one row from tv_user_contract.
func (d *Dao) UserContractByMid(c context.Context, mid int64) (uc *model.UserContract, err error) {
row := d.db.QueryRow(c, _getUserContractByMid, mid)
uc = &model.UserContract{}
err = row.Scan(&uc.ID, &uc.Mid, &uc.ContractId, &uc.OrderNo, &uc.IsDeleted, &uc.Ctime, &uc.Mtime)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
log.Error("rows.Scan(%s) error(%v)", _getUserContractByMid, err)
err = errors.WithStack(err)
return nil, err
}
return uc, nil
}
// UserContractByContractId quires one row from tv_user_contract.
func (d *Dao) UserContractByContractId(c context.Context, contractId string) (uc *model.UserContract, err error) {
row := d.db.QueryRow(c, _getUserContractByContractId, contractId)
uc = &model.UserContract{}
err = row.Scan(&uc.ID, &uc.Mid, &uc.ContractId, &uc.OrderNo, &uc.IsDeleted, &uc.Ctime, &uc.Mtime)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
log.Error("rows.Scan(%s) error(%v)", _getUserContractByContractId, err)
err = errors.WithStack(err)
return nil, err
}
return uc, nil
}
// TxDeleteUserContract deletes one user contract record.
func (d *Dao) TxDeleteUserContract(ctx context.Context, tx *xsql.Tx, id int32) (err error) {
if _, err = tx.Exec(_deleteUserContract, id); err != nil {
log.Error("rows.Scan(%s) error(%v)", _deleteUserContract, err)
err = errors.WithStack(err)
return
}
return
}
// TxInsertUserContract insert one row into tv_user_contract.
func (d *Dao) TxInsertUserContract(ctx context.Context, tx *xsql.Tx, uc *model.UserContract) (id int64, err error) {
var (
res sql.Result
)
if res, err = tx.Exec(_insertUserContract, uc.Mid, uc.ContractId, uc.OrderNo); err != nil {
log.Error("d.TxInsertUserContract(%+v) err(%+v)", uc, err)
err = errors.WithStack(err)
return
}
if id, err = res.LastInsertId(); err != nil {
log.Error("d.TxInsertUserContract(%+v) err(%+v)", uc, err)
err = errors.WithStack(err)
return
}
return
}

View File

@@ -0,0 +1,85 @@
package dao
import (
"context"
"testing"
"go-common/app/service/main/tv/internal/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoUserContractByMid(t *testing.T) {
convey.Convey("UserContractByMid", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
uc, err := d.UserContractByMid(c, mid)
ctx.Convey("Then err should be nil.uc should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(uc, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoUserContractByContractId(t *testing.T) {
convey.Convey("UserContractByContractId", t, func(ctx convey.C) {
var (
c = context.Background()
contractId = "Wx45678934567893456789"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
uc, err := d.UserContractByContractId(c, contractId)
ctx.Convey("Then err should be nil.uc should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(uc, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxDeleteUserContract(t *testing.T) {
convey.Convey("TxDeleteUserContract", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
id = int32(1)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.TxDeleteUserContract(c, tx, id)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
func TestDaoTxInsertUserContract(t *testing.T) {
convey.Convey("TxInsertUserContract", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
uc = &model.UserContract{
Mid: 27515308,
ContractId: "Wx45678934567893456789",
OrderNo: "T234567890456789",
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
id, err := d.TxInsertUserContract(c, tx, uc)
ctx.Convey("Then err should be nil.id should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(id, convey.ShouldNotBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}

View File

@@ -0,0 +1,52 @@
// Code generated by $GOPATH/src/go-common/app/tool/cache/gen. DO NOT EDIT.
/*
Package dao is a generated cache proxy package.
It is generated from:
type _cache interface {
// cache: -nullcache=&model.UserInfo{ID:-1} -check_null_code=$.ID==-1
UserInfoByMid(c context.Context, key int64) (*model.UserInfo, error)
}
*/
package dao
import (
"context"
"go-common/app/service/main/tv/internal/model"
"go-common/library/stat/prom"
)
var _ _cache
// UserInfoByMid get data from cache if miss will call source method, then add to cache.
func (d *Dao) UserInfoByMid(c context.Context, id int64) (res *model.UserInfo, err error) {
addCache := true
res, err = d.CacheUserInfoByMid(c, id)
if err != nil {
addCache = false
err = nil
}
if res != nil {
prom.CacheHit.Incr("UserInfoByMid")
return
}
prom.CacheMiss.Incr("UserInfoByMid")
res, err = d.RawUserInfoByMid(c, id)
if err != nil {
return
}
miss := res
if miss == nil {
miss = &model.UserInfo{ID: -1}
res = miss
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheUserInfoByMid(c, id, miss)
})
return
}

View File

@@ -0,0 +1,24 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoUserInfoByMid(t *testing.T) {
convey.Convey("UserInfoByMid", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, err := d.UserInfoByMid(c, id)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,115 @@
package dao
import (
"context"
acclgrpc "go-common/app/service/main/account/api"
"go-common/app/service/main/tv/internal/conf"
"go-common/app/service/main/tv/internal/model"
"go-common/app/service/main/tv/internal/pkg"
mvipgrpc "go-common/app/service/main/vipinfo/api"
"go-common/library/cache/memcache"
xsql "go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/sync/pipeline/fanout"
)
// Dao dao
type Dao struct {
c *conf.Config
mc *memcache.Pool
db *xsql.DB
httpCli *bm.Client
mvipCli mvipgrpc.VipInfoClient
mvipHttpCli *bm.Client
accCli acclgrpc.AccountClient
ystCli *YstClient
cache *fanout.Fanout
cacheTTL *conf.CacheTTL
signer *pkg.Signer
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
httpCli := bm.NewClient(c.HTTPClient)
mvipHttpCli := bm.NewClient(c.HTTPClient)
mvipCli, err := mvipgrpc.NewClient(c.MVIPClient)
if err != nil {
panic(err)
}
accCli, err := acclgrpc.NewClient(c.ACCClient)
if err != nil {
panic(err)
}
dao = &Dao{
c: c,
mc: memcache.NewPool(c.Memcache),
db: xsql.NewMySQL(c.MySQL),
mvipCli: mvipCli,
mvipHttpCli: mvipHttpCli,
accCli: accCli,
httpCli: httpCli,
ystCli: NewYstClient(httpCli),
cache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
cacheTTL: c.CacheTTL,
signer: &pkg.Signer{Key: c.YST.Key},
}
return
}
// Close close the resource.
func (d *Dao) Close() {
d.mc.Close()
d.db.Close()
}
// Ping dao ping
func (d *Dao) Ping(ctx context.Context) error {
return d.db.Ping(ctx)
}
// BeginTran begins transaction.
func (d *Dao) BeginTran(c context.Context) (*xsql.Tx, error) {
return d.db.Begin(c)
}
// EndTran ends transaction.
func (d *Dao) EndTran(tx *xsql.Tx, err error) error {
if err != nil {
log.Info("d.EndTran.Rollback(%+v) err(%+v)", tx, err)
tx.Rollback()
} else {
err = tx.Commit()
}
return err
}
// Signer returns yst signer.
func (d *Dao) Signer() *pkg.Signer {
return d.signer
}
//go:generate $GOPATH/src/go-common/app/tool/cache/gen
type _cache interface {
// cache: -nullcache=&model.UserInfo{ID:-1} -check_null_code=$.ID==-1
UserInfoByMid(c context.Context, key int64) (*model.UserInfo, error)
}
//go:generate $GOPATH/src/go-common/app/tool/cache/mc
type _mc interface {
// mc: -key=userInfoKey
CacheUserInfoByMid(c context.Context, key int64) (*model.UserInfo, error)
// mc: -key=userInfoKey -expire=d.cacheTTL.UserInfoTTL -encode=json
AddCacheUserInfoByMid(c context.Context, key int64, value *model.UserInfo) error
// mc: -key=userInfoKey
DelCacheUserInfoByMid(c context.Context, key int64) error
// mc: -key=payParamKey
CachePayParamByToken(c context.Context, token string) (*model.PayParam, error)
// mc: -key=payParamKey
CachePayParamsByTokens(c context.Context, tokens []string) (map[string]*model.PayParam, error)
// mc: -key=payParamKey -expire=d.cacheTTL.PayParamTTL -encode=json
AddCachePayParam(c context.Context, key string, value *model.PayParam) error
// mc: -type=replace -key=payParamKey -expire=d.cacheTTL.PayParamTTL -encode=json
UpdateCachePayParam(c context.Context, key string, value *model.PayParam) error
}

View File

@@ -0,0 +1,61 @@
package dao
import (
"context"
"flag"
"github.com/smartystreets/goconvey/convey"
"go-common/app/service/main/tv/internal/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.web-svr.tv-service")
flag.Set("conf_token", "c6efbe82ac5d9c68e5e619c30a26d32e")
flag.Set("tree_id", "74910")
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/test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}
func TestDaoPing(t *testing.T) {
convey.Convey("Ping", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Ping(c)
ctx.Convey("Then err should be nil.ai should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoSigner(t *testing.T) {
convey.Convey("Signer", t, func(ctx convey.C) {
ctx.Convey("When everything gose positive", func(ctx convey.C) {
s := d.Signer()
ctx.Convey("Then err should be nil.ai should not be nil.", func(ctx convey.C) {
ctx.So(s, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,72 @@
package dao
import (
"context"
"fmt"
"go-common/library/cache/memcache"
"go-common/library/log"
"go-common/library/stat/prom"
)
func (d *Dao) Lock(c context.Context, key string, val string) (err error) {
if val == "" {
return
}
conn := d.mc.Get(c)
defer conn.Close()
obj := &struct {
Value string
}{
Value: val,
}
item := &memcache.Item{Key: key, Object: obj, Expiration: d.cacheTTL.LockTTL, Flags: memcache.FlagJSON}
if err = conn.Add(item); err != nil {
prom.BusinessErrCount.Incr("mc:Lock")
log.Errorv(c, log.KV("Lock", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// no thread-safe, but it's works.
// bad case of unlocking
// 1, process-a gets lock
// 2, lock expires
// 3, process-b gets lock
// 4, process-a releases lock
func (d *Dao) Unlock(c context.Context, key string, val string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:Unlock")
log.Errorv(c, log.KV("Unlock", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res := &struct {
Value string
}{}
err = conn.Scan(reply, &res)
if err != nil {
prom.BusinessErrCount.Incr("mc:Unlock")
log.Errorv(c, log.KV("Unlock", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
if res.Value != val {
return nil
}
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:Unlock")
log.Errorv(c, log.KV("Unlock", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}

View File

@@ -0,0 +1,40 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoLock(t *testing.T) {
convey.Convey("Lock", t, func(ctx convey.C) {
var (
c = context.Background()
key = "LOCK:ORDER:12345"
val = "123456789"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Lock(c, key, val)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoUnlock(t *testing.T) {
convey.Convey("Unlock", t, func(ctx convey.C) {
var (
c = context.Background()
key = "LOCK:ORDER:12345"
val = "123456789"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Unlock(c, key, val)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,64 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/app/service/main/tv/internal/model"
mvip "go-common/app/service/main/vipinfo/api"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_mvipGiftRemark = "电视大会员赠送"
)
// MainVip returns main vip info.
func (d *Dao) MainVip(c context.Context, mid int64) (mv *model.MainVip, err error) {
var (
res *mvip.InfoReply
)
res, err = d.mvipCli.Info(c, &mvip.InfoReq{Mid: int64(mid)})
if err != nil {
log.Error("d.MainVip(%d) err(%v)", mid, err)
return
}
mv = &model.MainVip{
Mid: int64(mid),
VipType: int8(res.Res.Type),
VipStatus: int8(res.Res.Status),
VipDueDate: res.Res.DueDate,
}
log.Info("d.MainVip(%d) res(%+v)", mid, res)
return
}
// GiveMVipGift gives bilibili vip to user.
func (d *Dao) GiveMVipGift(c context.Context, mid int64, batchId int, orderNo string) error {
var (
err error
)
res := new(struct {
Code int `json:"code"`
Message string `json:"message"`
TTL int `json:"ttl"`
})
params := url.Values{}
params.Set("batchId", strconv.Itoa(batchId))
params.Set("mid", strconv.Itoa(int(mid)))
params.Set("orderNo", orderNo)
params.Set("remark", _mvipGiftRemark)
url := d.c.MVIP.BatchUserInfoUrl
if err = d.mvipHttpCli.Post(c, url, "", params, res); err != nil {
log.Error("d.mvipHttpCli.Post(%s, %+v) err(%+v)", url, params, err)
return err
}
if res.Code != 0 {
log.Error("d.mvipHttpCli.Post(%s, %+v) res(%+v)", url, params, res)
return ecode.TVIPGiveMVipFailed
}
log.Info("d.GiveMVipGift(%d, %d, %s) res(%+v)", mid, batchId, orderNo, res)
return nil
}

View File

@@ -0,0 +1,42 @@
package dao
import (
"context"
"github.com/smartystreets/goconvey/convey"
"math/rand"
"strconv"
"testing"
)
func TestDaoMainVip(t *testing.T) {
convey.Convey("MainVip", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
mv, err := d.MainVip(c, mid)
ctx.Convey("Then err should be nil.mv should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(mv, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGiveMVipGift(t *testing.T) {
convey.Convey("GiveMVipGift", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
batchId = int(21)
orderNo = "1" + strconv.Itoa(rand.Int()/100000)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.GiveMVipGift(c, mid, batchId, orderNo)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,192 @@
// Code generated by $GOPATH/src/go-common/app/tool/cache/mc. DO NOT EDIT.
/*
Package dao is a generated mc cache package.
It is generated from:
type _mc interface {
// mc: -key=userInfoKey
CacheUserInfoByMid(c context.Context, key int64) (*model.UserInfo, error)
// mc: -key=userInfoKey -expire=d.cacheTTL.UserInfoTTL -encode=json
AddCacheUserInfoByMid(c context.Context, key int64, value *model.UserInfo) error
// mc: -key=userInfoKey
DelCacheUserInfoByMid(c context.Context, key int64) error
// mc: -key=payParamKey
CachePayParamByToken(c context.Context, token string) (*model.PayParam, error)
// mc: -key=payParamKey
CachePayParamsByTokens(c context.Context, tokens []string) (map[string]*model.PayParam, error)
// mc: -key=payParamKey -expire=d.cacheTTL.PayParamTTL -encode=json
AddCachePayParam(c context.Context, key string, value *model.PayParam) error
// mc: -type=replace -key=payParamKey -expire=d.cacheTTL.PayParamTTL -encode=json
UpdateCachePayParam(c context.Context, key string, value *model.PayParam) error
}
*/
package dao
import (
"context"
"fmt"
"go-common/app/service/main/tv/internal/model"
"go-common/library/cache/memcache"
"go-common/library/log"
"go-common/library/stat/prom"
)
var _ _mc
// CacheUserInfoByMid get data from mc
func (d *Dao) CacheUserInfoByMid(c context.Context, id int64) (res *model.UserInfo, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := userInfoKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheUserInfoByMid")
log.Errorv(c, log.KV("CacheUserInfoByMid", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.UserInfo{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheUserInfoByMid")
log.Errorv(c, log.KV("CacheUserInfoByMid", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheUserInfoByMid Set data to mc
func (d *Dao) AddCacheUserInfoByMid(c context.Context, id int64, val *model.UserInfo) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := userInfoKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.cacheTTL.UserInfoTTL, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheUserInfoByMid")
log.Errorv(c, log.KV("AddCacheUserInfoByMid", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// DelCacheUserInfoByMid delete data from mc
func (d *Dao) DelCacheUserInfoByMid(c context.Context, id int64) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := userInfoKey(id)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:DelCacheUserInfoByMid")
log.Errorv(c, log.KV("DelCacheUserInfoByMid", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CachePayParamByToken get data from mc
func (d *Dao) CachePayParamByToken(c context.Context, id string) (res *model.PayParam, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := payParamKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CachePayParamByToken")
log.Errorv(c, log.KV("CachePayParamByToken", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.PayParam{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CachePayParamByToken")
log.Errorv(c, log.KV("CachePayParamByToken", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CachePayParamsByTokens get data from mc
func (d *Dao) CachePayParamsByTokens(c context.Context, ids []string) (res map[string]*model.PayParam, err error) {
l := len(ids)
if l == 0 {
return
}
keysMap := make(map[string]string, l)
keys := make([]string, 0, l)
for _, id := range ids {
key := payParamKey(id)
keysMap[key] = id
keys = append(keys, key)
}
conn := d.mc.Get(c)
defer conn.Close()
replies, err := conn.GetMulti(keys)
if err != nil {
prom.BusinessErrCount.Incr("mc:CachePayParamsByTokens")
log.Errorv(c, log.KV("CachePayParamsByTokens", fmt.Sprintf("%+v", err)), log.KV("keys", keys))
return
}
for key, reply := range replies {
var v *model.PayParam
v = &model.PayParam{}
err = conn.Scan(reply, v)
if err != nil {
prom.BusinessErrCount.Incr("mc:CachePayParamsByTokens")
log.Errorv(c, log.KV("CachePayParamsByTokens", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
if res == nil {
res = make(map[string]*model.PayParam, len(keys))
}
res[keysMap[key]] = v
}
return
}
// AddCachePayParam Set data to mc
func (d *Dao) AddCachePayParam(c context.Context, id string, val *model.PayParam) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := payParamKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.cacheTTL.PayParamTTL, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCachePayParam")
log.Errorv(c, log.KV("AddCachePayParam", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// UpdateCachePayParam Replace data to mc
func (d *Dao) UpdateCachePayParam(c context.Context, id string, val *model.PayParam) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := payParamKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.cacheTTL.PayParamTTL, Flags: memcache.FlagJSON}
if err = conn.Replace(item); err != nil {
prom.BusinessErrCount.Incr("mc:UpdateCachePayParam")
log.Errorv(c, log.KV("UpdateCachePayParam", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}

View File

@@ -0,0 +1,11 @@
package dao
import "fmt"
func userInfoKey(mid int64) string {
return fmt.Sprintf("cache_tv_vip_ui_%d", mid)
}
func payParamKey(token string) string {
return token
}

View File

@@ -0,0 +1,39 @@
package dao
import (
"context"
"go-common/app/service/main/tv/internal/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaouserInfoKey(t *testing.T) {
convey.Convey("userInfoKey", t, func(ctx convey.C) {
var (
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := userInfoKey(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaopayParamKey(t *testing.T) {
convey.Convey("payParamKey", t, func(ctx convey.C) {
var (
token = "TOKEN:34567345678"
)
d.AddCachePayParam(context.TODO(), token, &model.PayParam{})
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := payParamKey(token)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,123 @@
package dao
import (
"context"
"go-common/app/service/main/tv/internal/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoCacheUserInfoByMid(t *testing.T) {
convey.Convey("CacheUserInfoByMid", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(27515308)
)
d.AddCacheUserInfoByMid(c, id, &model.UserInfo{})
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, err := d.CacheUserInfoByMid(c, id)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAddCacheUserInfoByMid(t *testing.T) {
convey.Convey("AddCacheUserInfoByMid", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(27515308)
val = &model.UserInfo{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.AddCacheUserInfoByMid(c, id, val)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelCacheUserInfoByMid(t *testing.T) {
convey.Convey("DelCacheUserInfoByMid", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.DelCacheUserInfoByMid(c, id)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoCachePayParamByToken(t *testing.T) {
convey.Convey("CachePayParamByToken", t, func(ctx convey.C) {
var (
c = context.Background()
id = "TOKEN:34567345678"
)
d.AddCachePayParam(context.TODO(), id, &model.PayParam{})
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, err := d.CachePayParamByToken(c, id)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoCachePayParamsByTokens(t *testing.T) {
convey.Convey("CachePayParamsByTokens", t, func(ctx convey.C) {
var (
c = context.Background()
ids = []string{"TOKEN:34567345678"}
)
d.AddCachePayParam(context.TODO(), ids[0], &model.PayParam{})
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, err := d.CachePayParamsByTokens(c, ids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAddCachePayParam(t *testing.T) {
convey.Convey("AddCachePayParam", t, func(ctx convey.C) {
var (
c = context.Background()
id = "TOKEN:34567345678"
val = &model.PayParam{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.AddCachePayParam(c, id, val)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoUpdateCachePayParam(t *testing.T) {
convey.Convey("UpdateCachePayParam", t, func(ctx convey.C) {
var (
c = context.Background()
id = "TOKEN:34567345678"
val = &model.PayParam{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.UpdateCachePayParam(c, id, val)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,194 @@
package dao
import (
"context"
"database/sql"
"time"
"go-common/app/service/main/tv/internal/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
"github.com/pkg/errors"
)
const (
_getPayOrderByID = "SELECT `id`, `order_no`, `platform`, `order_type`, `mid`, `buy_months`, `product_id`, `money`, `quantity`, `refund_amount`, `status`, `third_trade_no`, `payment_money`, `payment_type`, `payment_time`, `ver`, `token`, `ctime`, `mtime` FROM `tv_pay_order` WHERE `id`=?"
_getPayOrderByOrderNo = "SELECT `id`, `order_no`, `platform`, `order_type`, `mid`, `buy_months`, `product_id`, `money`, `quantity`, `refund_amount`, `status`, `third_trade_no`, `payment_money`, `payment_type`, `payment_time`, `ver`, `token`, `ctime`, `mtime` FROM `tv_pay_order` WHERE `order_no`=?"
_getPayOrdersByMid = "SELECT `id`, `order_no`, `platform`, `order_type`, `mid`, `buy_months`, `product_id`, `money`, `quantity`, `refund_amount`, `status`, `third_trade_no`, `payment_money`, `payment_type`, `payment_time`, `ver`, `token`, `ctime`, `mtime` FROM `tv_pay_order` WHERE `mid`=? ORDER BY `ctime` DESC LIMIT ?,?"
_countPayOrderByMid = "SELECT count(*) FROM `tv_pay_order` WHERE `mid`=?"
_getPayOrdersByMidAndStatus = "SELECT `id`, `order_no`, `platform`, `order_type`, `mid`, `buy_months`, `product_id`, `money`, `quantity`, `refund_amount`, `status`, `third_trade_no`, `payment_money`, `payment_type`, `payment_time`, `ver`, `token`, `ctime`, `mtime` FROM `tv_pay_order` WHERE `mid`=? AND `status`=? ORDER BY `ctime` DESC LIMIT ?,?"
_countPayOrderByMidAndStatus = "SELECT count(*) FROM `tv_pay_order` WHERE `mid`=? AND `status`=?"
_getPayOrdersByMidAndStatusAndCtime = "SELECT `id`, `order_no`, `platform`, `order_type`, `mid`, `buy_months`, `product_id`, `money`, `quantity`, `refund_amount`, `status`, `third_trade_no`, `payment_money`, `payment_type`, `payment_time`, `ver`, `ctime`, `mtime`, `token` FROM `tv_pay_order` WHERE `mid`=? AND `status`=? AND `ctime`>= ? AND `ctime` <= ? ORDER BY `ctime` DESC LIMIT ?,?"
_countPayOrderByMidAndStatusAndCtime = "SELECT count(*) FROM `tv_pay_order` WHERE `mid`=? AND `status`=? AND `ctime`>= ? AND `ctime` <= ?"
_insertPayOrder = "INSERT INTO tv_pay_order (`order_no`, `platform`, `order_type`, `mid`, `buy_months`, `product_id`, `money`, `quantity`, `refund_amount`, `status`, `third_trade_no`, `payment_money`, `payment_type`, `payment_time`, `ver`, `token`, `app_channel`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
_updatePayOrder = "UPDATE `tv_pay_order` SET `status`=?, `payment_time`=?, `ver` = `ver` + 1 WHERE `id`=? AND `ver`=?"
_getUnpaidNoCallbackOrder = "SELECT `id`, `order_no`, `platform`, `order_type`, `mid`, `buy_months`, `product_id`, `money`, `quantity`, `refund_amount`, `status`, `third_trade_no`, `payment_money`, `payment_type`, `payment_time`, `ver`, `token`, `ctime`, `mtime` FROM `tv_pay_order` WHERE `status` = 1 and ctime > ? and ctime < ? order by id LIMIT ?,?"
)
// PayOrderByID quires one row from tv_pay_order.
func (d *Dao) PayOrderByID(c context.Context, id int) (po *model.PayOrder, err error) {
row := d.db.QueryRow(c, _getPayOrderByID, id)
po = &model.PayOrder{}
err = row.Scan(&po.ID, &po.OrderNo, &po.Platform, &po.OrderType, &po.Mid, &po.BuyMonths, &po.ProductId, &po.Money, &po.Quantity, &po.RefundAmount, &po.Status, &po.ThirdTradeNo, &po.PaymentMoney, &po.PaymentType, &po.PaymentTime, &po.Ver, &po.Token, &po.Ctime, &po.Mtime)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
log.Error("rows.Scan(%s) error(%v)", _getPayOrderByID, err)
err = errors.WithStack(err)
return nil, err
}
return po, nil
}
// PayOrderByOrderNo quires one row from tv_pay_order.
func (d *Dao) PayOrderByOrderNo(c context.Context, orderNo string) (po *model.PayOrder, err error) {
row := d.db.QueryRow(c, _getPayOrderByOrderNo, orderNo)
po = &model.PayOrder{}
err = row.Scan(&po.ID, &po.OrderNo, &po.Platform, &po.OrderType, &po.Mid, &po.BuyMonths, &po.ProductId, &po.Money, &po.Quantity, &po.RefundAmount, &po.Status, &po.ThirdTradeNo, &po.PaymentMoney, &po.PaymentType, &po.PaymentTime, &po.Ver, &po.Token, &po.Ctime, &po.Mtime)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
log.Error("rows.Scan(%s) error(%v)", _getPayOrderByOrderNo, err)
err = errors.WithStack(err)
return nil, err
}
return po, nil
}
// PayOrdersByMid quires rows from tv_pay_order.
func (d *Dao) PayOrdersByMid(c context.Context, mid int, pn, ps int) (res []*model.PayOrder, total int, err error) {
res = make([]*model.PayOrder, 0)
totalRow := d.db.QueryRow(c, _countPayOrderByMid, mid)
if err = totalRow.Scan(&total); err != nil {
log.Error("row.ScanCount error(%v)", err)
err = errors.WithStack(err)
return
}
rows, err := d.db.Query(c, _getPayOrdersByMid, mid, (pn-1)*ps, ps)
if err != nil {
log.Error("db.Query(%s) error(%v)", _getPayOrdersByMid, err)
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
po := &model.PayOrder{}
if err = rows.Scan(&po.ID, &po.OrderNo, &po.Platform, &po.OrderType, &po.Mid, &po.BuyMonths, &po.ProductId, &po.Money, &po.Quantity, &po.RefundAmount, &po.Status, &po.ThirdTradeNo, &po.PaymentMoney, &po.PaymentType, &po.PaymentTime, &po.Ver, &po.Token, &po.Ctime, &po.Mtime); err != nil {
log.Error("rows.Scan() error(%v)", err)
err = errors.WithStack(err)
return
}
res = append(res, po)
}
return
}
// PayOrdersByMidAndStatus quires rows from tv_pay_order.
func (d *Dao) PayOrdersByMidAndStatus(c context.Context, mid int, status int8, pn, ps int) (res []*model.PayOrder, total int, err error) {
res = make([]*model.PayOrder, 0)
totalRow := d.db.QueryRow(c, _countPayOrderByMidAndStatus, mid, status)
if err = totalRow.Scan(&total); err != nil {
log.Error("row.ScanCount error(%v)", err)
err = errors.WithStack(err)
return
}
rows, err := d.db.Query(c, _getPayOrdersByMidAndStatus, mid, status, (pn-1)*ps, ps)
if err != nil {
log.Error("db.Query(%s) error(%v)", _getPayOrdersByMidAndStatus, err)
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
po := &model.PayOrder{}
if err = rows.Scan(&po.ID, &po.OrderNo, &po.Platform, &po.OrderType, &po.Mid, &po.BuyMonths, &po.ProductId, &po.Money, &po.Quantity, &po.RefundAmount, &po.Status, &po.ThirdTradeNo, &po.PaymentMoney, &po.PaymentType, &po.PaymentTime, &po.Ver, &po.Token, &po.Ctime, &po.Mtime); err != nil {
log.Error("rows.Scan() error(%v)", err)
err = errors.WithStack(err)
return
}
res = append(res, po)
}
return
}
// PayOrdersByMidAndStatusAndCtime quires rows from tv_pay_order.
func (d *Dao) PayOrdersByMidAndStatusAndCtime(c context.Context, mid int64, status int8, from, to xtime.Time, pn, ps int) (res []*model.PayOrder, total int, err error) {
res = make([]*model.PayOrder, 0)
totalRow := d.db.QueryRow(c, _countPayOrderByMidAndStatusAndCtime, mid, status, from, to)
if err = totalRow.Scan(&total); err != nil {
log.Error("row.ScanCount error(%v)", err)
err = errors.WithStack(err)
return
}
rows, err := d.db.Query(c, _getPayOrdersByMidAndStatusAndCtime, mid, status, from, to, (pn-1)*ps, ps)
if err != nil {
log.Error("db.Query(%s) error(%v)", _getPayOrdersByMidAndStatusAndCtime, err)
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
po := &model.PayOrder{}
if err = rows.Scan(&po.ID, &po.OrderNo, &po.Platform, &po.OrderType, &po.Mid, &po.BuyMonths, &po.ProductId, &po.Money, &po.Quantity, &po.RefundAmount, &po.Status, &po.ThirdTradeNo, &po.PaymentMoney, &po.PaymentType, &po.PaymentTime, &po.Ver, &po.Ctime, &po.Mtime, &po.Token); err != nil {
log.Error("rows.Scan() error(%v)", err)
err = errors.WithStack(err)
return
}
res = append(res, po)
}
return
}
// TxInsertPayOrder insert one row into tv_pay_order.
func (d *Dao) TxInsertPayOrder(ctx context.Context, tx *xsql.Tx, po *model.PayOrder) (id int64, err error) {
var (
res sql.Result
)
if res, err = tx.Exec(_insertPayOrder, po.OrderNo, po.Platform, po.OrderType, po.Mid, po.BuyMonths, po.ProductId, po.Money, po.Quantity, po.RefundAmount, po.Status, po.ThirdTradeNo, po.PaymentMoney, po.PaymentType, po.PaymentTime, po.Ver, po.Token, po.AppChannel); err != nil {
log.Error("tx.Exec(%s) error(%v)", _insertPayOrder, err)
err = errors.WithStack(err)
return
}
if id, err = res.LastInsertId(); err != nil {
err = errors.WithStack(err)
return
}
return
}
// TxUpdatePayOrder updates status, third party no and payment time.
func (d *Dao) TxUpdatePayOrder(ctx context.Context, tx *xsql.Tx, po *model.PayOrder) error {
if _, err := tx.Exec(_updatePayOrder, po.Status, xtime.Time(time.Now().Unix()), po.ID, po.Ver); err != nil {
log.Error("tx.Exec(%s) error(%v)", _updatePayOrder, err)
err = errors.WithStack(err)
return err
}
return nil
}
//UnpaidNotCallbackOrder get orders not paid where stime < ctime < etime
func (d *Dao) UnpaidNotCallbackOrder(c context.Context, stime, etime xtime.Time, pn, ps int) (res []*model.PayOrder, err error) {
rows, err := d.db.Query(c, _getUnpaidNoCallbackOrder, stime, etime, (pn-1)*ps, ps)
if err != nil {
log.Error("db.Query(%s) error(%v)", _getUnpaidNoCallbackOrder, err)
err = errors.WithStack(err)
return
}
for rows.Next() {
po := &model.PayOrder{}
if err = rows.Scan(&po.ID, &po.OrderNo, &po.Platform, &po.OrderType, &po.Mid, &po.BuyMonths, &po.ProductId, &po.Money, &po.Quantity, &po.RefundAmount, &po.Status, &po.ThirdTradeNo, &po.PaymentMoney, &po.PaymentType, &po.PaymentTime, &po.Ver, &po.Token, &po.Ctime, &po.Mtime); err != nil {
log.Error("rows.Scan() error(%v)", err)
err = errors.WithStack(err)
return
}
res = append(res, po)
}
return
}

View File

@@ -0,0 +1,181 @@
package dao
import (
"context"
"go-common/app/service/main/tv/internal/model"
"math/rand"
"strconv"
"testing"
"time"
xtime "go-common/library/time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPayOrderById(t *testing.T) {
convey.Convey("PayOrderById", t, func(ctx convey.C) {
var (
c = context.Background()
id = 20
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
po, err := d.PayOrderByID(c, id)
ctx.Convey("Then err should be nil.po should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(po, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoPayOrderByOrderNo(t *testing.T) {
convey.Convey("PayOrderByOrderNo", t, func(ctx convey.C) {
var (
c = context.Background()
orderNo = "T123456789"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
po, err := d.PayOrderByOrderNo(c, orderNo)
ctx.Convey("Then err should be nil.po should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(po, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoPayOrdersByMid(t *testing.T) {
convey.Convey("PayOrdersByMid", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int(27515308)
pn = int(1)
ps = int(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, total, err := d.PayOrdersByMid(c, mid, pn, ps)
ctx.Convey("Then err should be nil.res,total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoPayOrdersByMidAndStatus(t *testing.T) {
convey.Convey("PayOrdersByMidAndStatus", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int(27515308)
status = int8(1)
pn = int(1)
ps = int(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, total, err := d.PayOrdersByMidAndStatus(c, mid, status, pn, ps)
ctx.Convey("Then err should be nil.res,total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoPayOrdersByMidAndStatusAndCtime(t *testing.T) {
convey.Convey("PayOrdersByMidAndStatusAndCtime", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
status = int8(1)
from = xtime.Time(time.Now().Add(-time.Hour * 24).Unix())
to = xtime.Time(time.Now().Unix())
pn = int(1)
ps = int(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, total, err := d.PayOrdersByMidAndStatusAndCtime(c, mid, status, from, to, pn, ps)
ctx.Convey("Then err should be nil.res,total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxInsertPayOrder(t *testing.T) {
convey.Convey("TxInsertPayOrder", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
po = &model.PayOrder{
OrderNo: "T123456789",
Platform: 1,
OrderType: 1,
Mid: 27515308,
BuyMonths: 1,
ProductId: "wx345678",
Quantity: 1,
Status: 1,
PaymentMoney: 100,
PaymentType: "wechat",
Ver: 1,
Token: "TOKEN:123456789",
AppChannel: "master",
}
)
d.TxInsertPayOrder(c, tx, po)
po.OrderNo = "T" + strconv.Itoa(rand.Int()/100000)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
id, err := d.TxInsertPayOrder(c, tx, po)
ctx.Convey("Then err should be nil.id should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(id, convey.ShouldNotBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
func TestDaoUnpaidNotCallbackOrder(t *testing.T) {
convey.Convey("UnpaidNotCallbackOrder", t, func(ctx convey.C) {
var (
c = context.Background()
stime xtime.Time
etime = xtime.Time(time.Now().Unix())
ps = int(500)
pn = int(1)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, err := d.UnpaidNotCallbackOrder(c, stime, etime, pn, ps)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxUpdatePayOrder(t *testing.T) {
convey.Convey("TxUpdatePayOrder", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
)
po, _ := d.PayOrderByID(c, 60)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.TxUpdatePayOrder(c, tx, po)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}

View File

@@ -0,0 +1,73 @@
package dao
import (
"context"
"go-common/app/service/main/tv/internal/model"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_getPriceConfigsByStatusAndPid = "SELECT `id`, `pid`, `platform`, `product_name`, `product_id`, `suit_type`, `month`, `sub_type`, `price`, `selected`, `remark`, `status`, `superscript`, `operator`, `oper_id`, `stime`, `etime`, `ctime`, `mtime` FROM `tv_price_config` WHERE `status`=? AND `pid`=? ORDER BY `ctime` ASC "
_countPriceConfigByStatusAndPid = "SELECT count(*) FROM `tv_price_config` WHERE `status`=? AND `pid`=?"
_getSaledPriceConfigsByStatusAndPid = "SELECT `id`, `pid`, `platform`, `product_name`, `product_id`, `suit_type`, `month`, `sub_type`, `price`, `selected`, `remark`, `status`, `superscript`, `operator`, `oper_id`, `stime`, `etime`, `ctime`, `mtime` FROM `tv_price_config` WHERE `status`=? AND `pid`!=0 AND `stime`<=now() AND `etime`>now() ORDER BY `ctime` ASC "
_countSaledPriceConfigByStatusAndPid = "SELECT count(*) FROM `tv_price_config` WHERE `status`=? AND `pid`!=0 AND `stime`<=now() AND `etime`>now()"
)
// PriceConfigsByStatus quires rows from tv_price_config.
func (d *Dao) PriceConfigsByStatus(c context.Context, status int8) (res []*model.PriceConfig, total int, err error) {
res = make([]*model.PriceConfig, 0)
pid := 0
totalRow := d.db.QueryRow(c, _countPriceConfigByStatusAndPid, status, pid)
if err = totalRow.Scan(&total); err != nil {
log.Error("row.ScanCount error(%v)", err)
err = errors.WithStack(err)
return
}
rows, err := d.db.Query(c, _getPriceConfigsByStatusAndPid, status, pid)
if err != nil {
log.Error("db.Query(%s) error(%v)", _getPriceConfigsByStatusAndPid, err)
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
pc := &model.PriceConfig{}
if err = rows.Scan(&pc.ID, &pc.Pid, &pc.Platform, &pc.ProductName, &pc.ProductId, &pc.SuitType, &pc.Month, &pc.SubType, &pc.Price, &pc.Selected, &pc.Remark, &pc.Status, &pc.Superscript, &pc.Operator, &pc.OperId, &pc.Stime, &pc.Etime, &pc.Ctime, &pc.Mtime); err != nil {
log.Error("rows.Scan() error(%v)", err)
err = errors.WithStack(err)
return
}
res = append(res, pc)
}
return
}
// SaledPriceConfigsByStatus quires rows from tv_price_config.
func (d *Dao) SaledPriceConfigsByStatus(c context.Context, status int8) (res []*model.PriceConfig, total int, err error) {
res = make([]*model.PriceConfig, 0)
totalRow := d.db.QueryRow(c, _countSaledPriceConfigByStatusAndPid, status)
if err = totalRow.Scan(&total); err != nil {
log.Error("row.ScanCount error(%v)", err)
err = errors.WithStack(err)
return
}
rows, err := d.db.Query(c, _getSaledPriceConfigsByStatusAndPid, status)
if err != nil {
log.Error("db.Query(%s) error(%v)", _getPriceConfigsByStatusAndPid, err)
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
pc := &model.PriceConfig{}
if err = rows.Scan(&pc.ID, &pc.Pid, &pc.Platform, &pc.ProductName, &pc.ProductId, &pc.SuitType, &pc.Month, &pc.SubType, &pc.Price, &pc.Selected, &pc.Remark, &pc.Status, &pc.Superscript, &pc.Operator, &pc.OperId, &pc.Stime, &pc.Etime, &pc.Ctime, &pc.Mtime); err != nil {
log.Error("rows.Scan() error(%v)", err)
err = errors.WithStack(err)
return
}
res = append(res, pc)
}
return
}

View File

@@ -0,0 +1,42 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPriceConfigsByStatus(t *testing.T) {
convey.Convey("PriceConfigsByStatus", t, func(ctx convey.C) {
var (
c = context.Background()
status = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, total, err := d.PriceConfigsByStatus(c, status)
ctx.Convey("Then err should be nil.res,total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoSaledPriceConfigsByStatus(t *testing.T) {
convey.Convey("SaledPriceConfigsByStatus", t, func(ctx convey.C) {
var (
c = context.Background()
status = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, total, err := d.SaledPriceConfigsByStatus(c, status)
ctx.Convey("Then err should be nil.res,total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,85 @@
package dao
import (
"context"
"database/sql"
"go-common/app/service/main/tv/internal/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_getUserInfoByMid = "SELECT `id`, `mid`, `ver`, `vip_type`, `pay_type`, `pay_channel_id`, `status`, `overdue_time`, `recent_pay_time`, `ctime`, `mtime` FROM `tv_user_info` WHERE `mid`=?"
_insertUserInfo = "INSERT INTO tv_user_info (`mid`, `ver`, `vip_type`, `pay_type`, `pay_channel_id`, `status`, `overdue_time`, `recent_pay_time`) VALUES (?,?,?,?,?,?,?,?)"
_updateUserInfo = "UPDATE tv_user_info SET `status` = ?, `vip_type` = ?, `overdue_time`=?, `recent_pay_time`=?, `ver` = `ver` + 1 WHERE `mid` = ? AND `ver` = ?"
_updateUserStatus = "UPDATE tv_user_info SET `status` = ?, `ver` = `ver` + 1 WHERE `mid` = ? AND `ver` = ?"
_updateUserPayType = "UPDATE tv_user_info SET `pay_type` = ?, `ver` = `ver` + 1 WHERE `mid` = ? AND `ver` = ?"
)
// UserInfoByMid quires one row from tv_user_info.
func (d *Dao) RawUserInfoByMid(c context.Context, mid int64) (ui *model.UserInfo, err error) {
row := d.db.QueryRow(c, _getUserInfoByMid, mid)
ui = &model.UserInfo{}
err = row.Scan(&ui.ID, &ui.Mid, &ui.Ver, &ui.VipType, &ui.PayType, &ui.PayChannelId, &ui.Status, &ui.OverdueTime, &ui.RecentPayTime, &ui.Ctime, &ui.Mtime)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
log.Error("rows.Scan(%s) error(%v)", _getUserInfoByMid, err)
err = errors.WithStack(err)
return nil, err
}
return ui, nil
}
// TxInsertUserInfo insert one row into tv_user_info.
func (d *Dao) TxInsertUserInfo(ctx context.Context, tx *xsql.Tx, ui *model.UserInfo) (id int64, err error) {
var (
res sql.Result
)
if res, err = tx.Exec(_insertUserInfo, ui.Mid, ui.Ver, ui.VipType, ui.PayType, ui.PayChannelId, ui.Status, ui.OverdueTime, ui.RecentPayTime); err != nil {
log.Error("tx.Exec(%s) error(%v)", _insertUserInfo, err)
err = errors.WithStack(err)
return
}
if id, err = res.LastInsertId(); err != nil {
err = errors.WithStack(err)
return
}
return
}
// TxUpdateUserInfo updates user info.
func (d *Dao) TxUpdateUserInfo(ctx context.Context, tx *xsql.Tx, ui *model.UserInfo) (err error) {
if _, err = tx.Exec(_updateUserInfo, ui.Status, ui.VipType, ui.OverdueTime, ui.RecentPayTime, ui.Mid, ui.Ver); err != nil {
log.Error("tx.Exec(%s) error(%v)", _updateUserInfo, err)
err = errors.WithStack(err)
return
}
return
}
// TxUpdateUserInfo updates vip status of user.
func (d *Dao) TxUpdateUserStatus(ctx context.Context, tx *xsql.Tx, ui *model.UserInfo) (err error) {
if _, err = tx.Exec(_updateUserStatus, ui.Status, ui.Mid, ui.Ver); err != nil {
log.Error("tx.Exec(%s) error(%v)", _updateUserStatus, err)
err = errors.WithStack(err)
return
}
return
}
// TxUpdateUserPayType updates pay type of user.
func (d *Dao) TxUpdateUserPayType(ctx context.Context, tx *xsql.Tx, ui *model.UserInfo) (err error) {
if _, err = tx.Exec(_updateUserPayType, ui.PayType, ui.Mid, ui.Ver); err != nil {
log.Error("tx.Exec(%s) error(%v)", _updateUserPayType, err)
err = errors.WithStack(err)
return
}
return
}

View File

@@ -0,0 +1,122 @@
package dao
import (
"context"
"go-common/app/service/main/tv/internal/model"
"math/rand"
"testing"
"time"
xtime "go-common/library/time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoRawUserInfoByMid(t *testing.T) {
convey.Convey("RawUserInfoByMid", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ui, err := d.RawUserInfoByMid(c, mid)
ctx.Convey("Then err should be nil.ui should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ui, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxInsertUserInfo(t *testing.T) {
convey.Convey("TxInsertUserInfo", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
ui = &model.UserInfo{
Mid: int64(rand.Int() / 10000000000),
Ver: 1,
VipType: 1,
OverdueTime: xtime.Time(time.Now().Unix() + 60*60*24*31),
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
id, err := d.TxInsertUserInfo(c, tx, ui)
ctx.Convey("Then err should be nil.id should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(id, convey.ShouldNotBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
func TestDaoTxUpdateUserInfo(t *testing.T) {
convey.Convey("TxUpdateUserInfo", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
ui = &model.UserInfo{
Mid: 27515308,
Ver: 1,
VipType: 1,
OverdueTime: xtime.Time(time.Now().Unix() + 60*60*24*31),
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.TxUpdateUserInfo(c, tx, ui)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
func TestDaoTxUpdateUserStatus(t *testing.T) {
convey.Convey("TxUpdateUserStatus", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
ui = &model.UserInfo{
Mid: 27515308,
Status: 1,
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.TxUpdateUserStatus(c, tx, ui)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
func TestDaoTxUpdateUserPayType(t *testing.T) {
convey.Convey("TxUpdateUserPayType", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
ui = &model.UserInfo{
Mid: 27515308,
PayType: 1,
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.TxUpdateUserPayType(c, tx, ui)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}

View File

@@ -0,0 +1,127 @@
package dao
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"go-common/app/service/main/tv/internal/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
const (
ystUrl = "https://%s/%s"
ystCreateOrderPath = "getBiliOrder"
ystRenewOrderPath = "papPayApply"
ystOrderStatePath = "getBiliOrderState"
)
// YstClient represents http client for sending requests to yst.
type YstClient struct {
cli *bm.Client
}
// NewYstClient news a yst client.
func NewYstClient(cli *bm.Client) *YstClient {
return &YstClient{cli: cli}
}
// Post sends one post request.
func (yc *YstClient) Post(c context.Context, uri string, data interface{}, res interface{}) error {
req, err := yc.NewRequest(uri, data)
if err != nil {
return err
}
return yc.cli.Do(c, req, res)
}
// NewRequest news a post request.
func (yc *YstClient) NewRequest(uri string, data interface{}) (request *http.Request, err error) {
var dataBytes []byte
dataBytes, err = json.Marshal(data)
if err != nil {
return
}
request, err = http.NewRequest(http.MethodPost, uri, bytes.NewBuffer(dataBytes))
if err != nil {
return
}
request.Header.Set("Content-Type", "application/json")
return request, nil
}
func resultCode2err(code string) error {
switch code {
case model.YstResultFail:
return ecode.TVIPYstRequestErr
case model.YstResultSysErr:
return ecode.TVIPYstSystemErr
default:
return ecode.TVIPYstUnknownErr
}
}
// CreateYstOrder creates one yst order.
func (d *Dao) CreateYstOrder(c context.Context, req *model.YstCreateOrderReq) (res *model.YstCreateOrderReply, err error) {
req.Sign, err = d.signer.Sign(req)
if err != nil {
return
}
res = &model.YstCreateOrderReply{}
uri := fmt.Sprintf(ystUrl, d.c.YST.Domain, ystCreateOrderPath)
if err = d.ystCli.Post(c, uri, req, res); err != nil {
log.Error("d.ystCli.Post(%s, %+v) err(%+v)", uri, req, err)
return
}
if res.ResultCode != model.YstResultSuccess {
log.Error("d.ystCli.Post(%s, %+v) res(%+v)", uri, req, res)
return nil, resultCode2err(res.ResultCode)
}
log.Info("dao.CreateYstOrder(%+v) res(%+v)", req, res)
return
}
// RenewYstOrder creates one renew order.
func (d *Dao) RenewYstOrder(c context.Context, req *model.YstRenewOrderReq) (res *model.YstRenewOrderReply, err error) {
req.Sign, err = d.signer.Sign(req)
if err != nil {
return
}
res = &model.YstRenewOrderReply{}
uri := fmt.Sprintf(ystUrl, d.c.YST.Domain, ystRenewOrderPath)
if err = d.ystCli.Post(c, uri, req, res); err != nil {
log.Error("d.ystCli.Post(%s, %+v) err(%+v)", uri, req, err)
return
}
if res.ResultCode != model.YstResultSuccess {
log.Error("d.ystCli.Post(%s, %+v) res(%+v)", uri, req, res)
return nil, resultCode2err(res.ResultCode)
}
log.Info("dao.RenewYstOrder(%+v) res(%+v)", req, res)
return
}
// YstOrderState queries order details from yst.
func (d *Dao) YstOrderState(c context.Context, req *model.YstOrderStateReq) (res *model.YstOrderStateReply, err error) {
req.Sign, err = d.signer.Sign(req)
if err != nil {
return
}
res = &model.YstOrderStateReply{}
uri := fmt.Sprintf(ystUrl, d.c.YST.Domain, ystOrderStatePath)
if err = d.ystCli.Post(c, uri, req, res); err != nil {
log.Error("d.ystCli.Post(%s, %+v) err(%+v)", uri, req, err)
return
}
if res.Result != model.YstResultSuccess {
log.Error("d.ystCli.Post(%s, %+v) res(%+v)", uri, req, res)
return nil, resultCode2err(res.Result)
}
log.Info("dao.YstOrderState(%+v) res(%+v)", req, res)
return
}

View File

@@ -0,0 +1,75 @@
package dao
import (
"context"
"go-common/app/service/main/tv/internal/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoNewYstClient(t *testing.T) {
convey.Convey("NewYstClient", t, func(ctx convey.C) {
var (
cli = &bm.Client{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := NewYstClient(cli)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoNewRequest(t *testing.T) {
convey.Convey("NewRequest", t, func(ctx convey.C) {
var (
cli = NewYstClient(&bm.Client{})
uri = ""
data = interface{}(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
request, err := cli.NewRequest(uri, data)
ctx.Convey("Then err should be nil.request should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(request, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoresultCode2err(t *testing.T) {
convey.Convey("resultCode2err", t, func(ctx convey.C) {
var (
code = "999"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := resultCode2err(code)
ctx.Convey("Then err should be TVIPYstSystemErr.", func(ctx convey.C) {
ctx.So(err, convey.ShouldEqual, ecode.TVIPYstSystemErr)
})
})
})
}
func TestDaoYstOrderState(t *testing.T) {
convey.Convey("YstOrderState", t, func(ctx convey.C) {
var (
c = context.Background()
req = &model.YstOrderStateReq{
SeqNo: "08265187190109103054",
TraceNo: "CA20190109102306315819491155",
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, err := d.YstOrderState(c, req)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"change_history.go",
"contract.go",
"enum.go",
"main_vip.go",
"pay_order.go",
"price_config.go",
"qr.go",
"token.go",
"user_info.go",
"yst.go",
],
importpath = "go-common/app/service/main/tv/internal/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/account/api:go_default_library",
"//library/log:go_default_library",
"//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,47 @@
package model
import (
"strconv"
"time"
"go-common/library/log"
xtime "go-common/library/time"
)
// UserChangeHistory 会员变动流水.
type UserChangeHistory struct {
ID int32 `json:"id"` // vip开通历史
Mid int64 `json:"mid"` // 用户mid
ChangeType int8 `json:"change_type"` // 变更类型(1:充值开通 2:系统发放 3:活动赠送 4:重复领取扣除)
ChangeTime xtime.Time `json:"change_time"` // 变更时间
OrderNo string `json:"order_no"` // 关联订单号
Days int32 `json:"days"` // 开通天数
OperatorId string `json:"operator_id"` // 操作人id
Remark string `json:"remark"` // 备注
Ctime xtime.Time `json:"ctime"` // 创建时间
Mtime xtime.Time `json:"mtime"` // 修改时间
}
func (uc *UserChangeHistory) orderType2ChangeType(orderType int8) int8 {
var ct int8
switch orderType {
case PayOrderTypeNormal:
ct = UserChangeTypeRecharge
case PayOrderTypeSub:
ct = UserChangeTypeSystem
default:
log.Error("uc.CopyFromPayOrder() err(UnknownOrderType) orderType(%d)", orderType)
ct = UserChangeTypeRecharge
}
return ct
}
// CopyFromPayOrder copies fields from pay order.
func (uc *UserChangeHistory) CopyFromPayOrder(po *PayOrder) {
uc.Mid = po.Mid
uc.OrderNo = po.OrderNo
uc.Days = int32(po.BuyMonths) * 31
uc.OperatorId = strconv.Itoa(int(po.Mid))
uc.ChangeTime = xtime.Time(time.Now().Unix())
uc.ChangeType = uc.orderType2ChangeType(po.OrderType)
}

View File

@@ -0,0 +1,14 @@
package model
import xtime "go-common/library/time"
// UserContract represents user contract record.
type UserContract struct {
ID int32
Mid int64
ContractId string
OrderNo string
IsDeleted int8
Ctime xtime.Time
Mtime xtime.Time
}

View File

@@ -0,0 +1,67 @@
package model
// enums.
const (
PlatformAndriod = 1
PlatformWechatMp = 2
PlatformSystem = 3
SuitTypeAll = 0
SuitTypeMvip = 10
SubTypeOther = 0
SubTypeContract = 1
PayOrderTypeNormal = 0
PayOrderTypeSub = 1
PayOrderStatusPending = 0
PayOrderStatusPaying = 1
PayOrderStatusSuccess = 2
PayOrderStatusFail = 3
PaymentTypeAliPay = "alipay"
PaymentTypeWechat = "wechat"
UserChangeTypeRecharge = 1
UserChangeTypeSystem = 2
UserChangeTypeGift = 3
UserChangeTypeDup = 4
UserChangeTypeRenewVIp = 5
UserChangeTypeSignContract = 6
UserChangeTypeCancelContract = 7
VipTypeVip = 1
VipTypeAnnualVip = 2
VipPayTypeNormal = 0
VipPayTypeSub = 1
PayChannelAli = "alipay"
PayChannelWechat = "wechat"
VipStatusActive = 1
VipStatusExpired = 0
YstPayWayQr = "1"
YstPayTypeAliPay = "1"
YstPayTypeWechat = "2"
YstPayStatusPaied = "1"
YstPayStatusPending = "0"
YstTradeStateSuccess = "SUCCESS"
YstTradeStateRefund = "REFUND"
YstTradeStateNotPay = "NOTPAY"
YstTradeStateClosed = "CLOSED"
YstTradeStateAccept = "ACCEPT"
YstTradeStatePayFail = "PAY_FAIL"
YstResultSuccess = "0"
YstResultFail = "998"
YstResultSysErr = "999"
YST_CONTRACT_TYPE_SIGN = "ADD"
YST_CONTRSCT_TYPE_CANCEL = "DELETE"
)

View File

@@ -0,0 +1,33 @@
package model
import (
"math"
"time"
)
// MainVip represents bilibili vip info.
type MainVip struct {
Mid int64 `json:"mid"`
VipType int8 `json:"vip_type"` // 大会员类型 0.非大会员 1.月度大会员 2.年度会员
PayType int8 `json:"pay_type"`
VipStatus int8 `json:"vip_status"` //大会员状态: 0.过期 1.未过期 2.冻结 3.封禁
VipDueDate int64 `json:"vip_due_date"`
}
// IsVip returns true if user is vip.
func (mv *MainVip) IsVip() bool {
return mv.VipType != 0 && (mv.VipStatus == 1 || mv.VipStatus == 3)
}
// Months returns vip months.
func (mv *MainVip) Months() int32 {
if !mv.IsVip() {
return 0
}
nowInMs := time.Now().UnixNano() / int64(time.Millisecond)
span := mv.VipDueDate - nowInMs
if span <= 0 {
return 0
}
return int32(math.Floor(float64(span) / 1000 / 60 / 60 / 24 / 31))
}

View File

@@ -0,0 +1,50 @@
package model
import (
xtime "go-common/library/time"
)
// PayOrder represents pay order.
type PayOrder struct {
ID int32 `json:"id"` // 订单表自增ID
OrderNo string `json:"order_no"` // 订单号
Platform int8 `json:"platform"` // 设备平台,1:tv安卓 2:公众号
OrderType int8 `json:"order_type"` // 订单类型0-普通订单 1-自动续费订单
Mid int64 `json:"mid"` // 下单支付的用户mid
BuyMonths int8 `json:"buy_months"` // 购买vip时长
ProductId string `json:"product_id"` // 产品id
Money int32 `json:"money"` // vip单价单位分
Quantity int32 `json:"quantity"` // 购买数量
RefundAmount int32 `json:"refund_amount"` // 退款金额,单位分
Status int8 `json:"status"` // 订单状态1.消费中 2.消费成功 3.消费失败
ThirdTradeNo string `json:"third_trade_no"` // 第三方订单号yst订单号
PaymentMoney int32 `json:"payment_money"` // 真正支付金额,单位分
PaymentType string `json:"payment_type"` // 支付方式alipay,wechat
PaymentTime xtime.Time `json:"payment_time"` // 支付时间
Ver int32 `json:"ver"` // 版本号,用于乐观锁
AppChannel string `json:"app_channel"` // 应用渠道
Token string
Ctime xtime.Time `json:"ctime"` // 创建时间
Mtime xtime.Time `json:"mtime"` // 修改时间
}
// CopyFromPayParam copies fiels from pay param.
func (p *PayOrder) CopyFromPayParam(pp *PayParam) {
p.OrderNo = pp.OrderNo
p.Quantity = pp.BuyNum
p.AppChannel = pp.AppChannel
}
// CopyFromPanel copies field from panel.
func (p *PayOrder) CopyFromPanel(panel *PanelPriceConfig) {
if panel.SubType == 0 {
p.OrderType = 0
}
if panel.SubType == 1 {
p.OrderType = 1
}
p.ProductId = panel.ProductId
p.Money = panel.Price
p.BuyMonths = int8(panel.Month * p.Quantity)
p.PaymentMoney = panel.Price * p.Quantity
}

View File

@@ -0,0 +1,73 @@
package model
import (
xtime "go-common/library/time"
)
// PriceConfig represents price config of tv vip.
type PriceConfig struct {
ID int32 `json:"id"` // 主键id
Pid int32 `json:"pid"` // 父id为空表示为原价信息
Platform int8 `json:"platform"` // 类型: 1:tv安卓 2:公众号
ProductName string `json:"product_name"` // 产品展示名
ProductId string `json:"product_id"` // 产品id
SuitType int8 `json:"suit_type"` // 适用人群: 0.所有用户 1.旧客 2.新客 3.续期旧客 4.续期新客 5.套餐旧客 6.套餐新客 10.主站vip专项
Month int32 `json:"month"` // 月份单位
SubType int8 `json:"sub_type"` // 订阅类型0.其他1.连续包月
Price int32 `json:"price"` // 价格pid为0表示原价,单位:分
Selected int8 `json:"selected"` // 选中状态: 0.未选中1.选中
Remark string `json:"remark"` // 促销tip
Status int8 `json:"status"` // 状态0:有效,1:失效
Superscript string `json:"superscript"` // 角标
Operator string `json:"operator"` // 操作者
OperId int64 `json:"oper_id"` // 操作者id
Stime xtime.Time `json:"stime"` // 折扣开始时间
Etime xtime.Time `json:"etime"` // 折扣结束时间
Ctime xtime.Time `json:"ctime"` // 创建时间
Mtime xtime.Time `json:"mtime"` // 最后修改时间
}
// PanelPriceConfig represents panel config of tv vip.
type PanelPriceConfig struct {
PriceConfig
MaxNum int32 // 允许最大购买数量,-1 表示不限制
OriginPrice int32 // 原价
}
// CopyFromPriceConfig copies fields from price config.
func (pi *PanelPriceConfig) CopyFromPriceConfig(pc *PriceConfig) {
pi.ID = pc.ID
pi.Pid = pc.Pid
pi.Platform = pc.Platform
pi.ProductName = pc.ProductName
pi.ProductId = pc.ProductId
pi.SuitType = pc.SuitType
pi.Month = pc.Month
pi.SubType = pc.SubType
pi.Price = pc.Price
pi.Selected = pc.Selected
pi.Remark = pc.Remark
pi.Status = pc.Status
pi.Superscript = pc.Superscript
pi.Operator = pc.Operator
pi.OperId = pc.OperId
pi.Stime = pc.Stime
pi.Etime = pc.Etime
pi.Ctime = pc.Ctime
pi.Mtime = pc.Mtime
pi.MaxNum = 1
}
// IsContracted returns true if panel is contracted package.
func (pi *PanelPriceConfig) IsContracted() bool {
return pi.SubType == SubTypeContract
}
// PidOrId returns panel parent id or panel id.
func (pi *PanelPriceConfig) PidOrId() int32 {
if pi.Pid != 0 {
return pi.Pid
}
return pi.ID
}

View File

@@ -0,0 +1,44 @@
package model
import (
"crypto/md5"
"fmt"
"io"
"strconv"
"time"
xtime "go-common/library/time"
)
// QR represents pay qr info.
type QR struct {
ExpireAt xtime.Time
URL string
Token string
}
// PayParam represents pay params.
type PayParam struct {
Mid int64
Pid int32
BuyNum int32
Guid string
AppChannel string
Status int8
OrderNo string
ExpireAt xtime.Time
}
// MD5 calculates md5 of pay params.
func (p *PayParam) MD5() string {
m := md5.New()
io.WriteString(m, strconv.Itoa(int(p.Mid)))
io.WriteString(m, strconv.Itoa(int(p.Pid)))
io.WriteString(m, strconv.Itoa(int(p.ExpireAt)))
return fmt.Sprintf("%x", string(m.Sum(nil)))
}
// IsExpired return true if pay param is expired.
func (p *PayParam) IsExpired() bool {
return int64(p.ExpireAt) < time.Now().Unix()
}

View File

@@ -0,0 +1,16 @@
package model
// TokenInfo represents pay token.
type TokenInfo struct {
Token string
OrderNo string
Status int8
Mid int64
}
// CopyFromPayParam copies fields from pay param.
func (t *TokenInfo) CopyFromPayParam(pp *PayParam) {
t.Mid = pp.Mid
t.Status = pp.Status
t.OrderNo = pp.OrderNo
}

View File

@@ -0,0 +1,69 @@
package model
import (
"time"
xtime "go-common/library/time"
)
// UserInfo represents user info.
type UserInfo struct {
ID int32 `json:"id"` // 用户信息表
Mid int64 `json:"mid"` // 用户mid
Ver int32 `json:"ver"` // 版本控制
VipType int8 `json:"vip_type"` // tv-vip类型:1.vip 2.年费vip
PayType int8 `json:"pay_type"` // tv-vip购买类型:0.正常购买 1.连续包月
PayChannelId string `json:"pay_channel_id"` // 自动续费渠道:wechat,alipay
Status int8 `json:"status"` // tv-vip状态:0:过期 1:未过期
OverdueTime xtime.Time `json:"overdue_time"` // tv-vip过期时间
RecentPayTime xtime.Time `json:"recent_pay_time"` // tv-vip最近开通时间
Ctime xtime.Time `json:"ctime"` // 创建时间
Mtime xtime.Time `json:"mtime"` // 修改时间
}
// IsEmpty returns true if user id equals -1.
func (ui *UserInfo) IsEmpty() bool {
return ui.ID == -1
}
// IsExpired returns true if user is expired vip.
func (ui *UserInfo) IsExpired() bool {
return ui.OverdueTime < xtime.Time(time.Now().Unix())
}
// MarkExpired sets user status to expired status.
func (ui *UserInfo) MarkExpired() {
ui.Status = 0
}
// IsVip returns true if user is vip.
func (ui *UserInfo) IsVip() bool {
if ui.IsEmpty() {
return false
}
if ui.IsExpired() {
return false
}
return ui.Status == 1
}
// IsContracted returns true if user buys contracted package.
func (ui *UserInfo) IsContracted() bool {
return ui.PayType == 1
}
// CopyFromPayOrder copies fileds from pay order.
func (ui *UserInfo) CopyFromPayOrder(po *PayOrder) {
ui.VipType = VipTypeVip
ui.PayChannelId = po.PaymentType
ui.RecentPayTime = xtime.Time(time.Now().Unix())
}
// CopyFromPanel copies field from panel.
func (ui *UserInfo) CopyFromPanel(p *PanelPriceConfig) {
if p.SubType == SubTypeContract {
ui.PayType = VipPayTypeSub
return
}
ui.PayType = VipPayTypeNormal
}

View File

@@ -0,0 +1,167 @@
package model
import (
"strconv"
"go-common/app/service/main/account/api"
)
// YstCreateOrderReq represents request of yst order creation.
type YstCreateOrderReq struct {
SeqNo string `json:"seqno" url:"seqno"`
Source string `json:"source" url:"source"`
ProductId string `json:"product_id" url:"product_id"`
Price int32 `json:"price" url:"price"`
Total int32 `json:"total" url:"total"`
BuyNum int32 `json:"buy_num" url:"buy_num"`
VideoType string `json:"video_type" url:"video_type"`
PayType string `json:"pay_type" url:"pay_type"`
PayWay string `json:"pay_way" url:"pay_way"`
UserLogin string `json:"user_login" url:"user_login"`
UserId string `json:"user_id" url:"user_id"`
GUID string `json:"guid" url:"guid"`
ClientIp string `json:"client_ip" url:"client_ip"`
Sign string `json:"sign" url:"sign,omitempty"`
LoginName string `json:"login_name" url:"login_name"`
}
// CopyFromPayOrder copies fields from pay order.
func (y *YstCreateOrderReq) CopyFromPayOrder(po *PayOrder) {
y.SeqNo = po.OrderNo
y.Source = "snm_bilibili"
y.ProductId = po.ProductId
y.Price = po.Money
y.Total = po.PaymentMoney
y.BuyNum = po.Quantity
if po.OrderType == 0 {
y.VideoType = "fvod"
}
if po.OrderType == 1 {
y.VideoType = "svod"
}
if po.PaymentType == PaymentTypeAliPay {
y.PayType = YstPayTypeAliPay
}
if po.PaymentType == PaymentTypeWechat {
y.PayType = YstPayTypeWechat
}
y.PayWay = YstPayWayQr
}
// CopyFromAccount copies fields from account info.
func (y *YstCreateOrderReq) CopyFromAccount(account *api.Info) {
y.LoginName = account.Name
y.UserId = strconv.Itoa(int(account.Mid))
}
// YstCreateOrderReply represents response of yst order creation.
type YstCreateOrderReply struct {
SeqNo string `json:"seqno" url:"seqno"`
TraceNo string `json:"traceno" url:"traceno"`
PayWary string `json:"pay_wary" url:"pay_wary"`
CodeUrl string `json:"code_url" url:"code_url"`
ContractCode string `json:"contract_code" url:"contract_code"`
Price int32 `json:"price" url:"price"`
VideoType string `json:"video_type" url:"video_type"`
PayParam string `json:"pay_param" url:"pay_param"`
ResultCode string `json:"result_code" url:"result_code"`
ResultMsg string `json:"result_msg" url:"result_msg"`
Sign string `json:"sign"`
}
// YstPayCallbackReq represents request of pay callback.
type YstPayCallbackReq struct {
SeqNo string `json:"seqno" url:"seqno"`
TraceNo string `json:"traceno" url:"traceno"`
TradeState string `json:"trade_state" url:"trade_state"`
ContractId string `json:"contract_id" url:"contract_id,omitempty"`
Sign string `json:"sign" url:"sign"`
}
// YstPayCallbackReply represents response of pay callback.
type YstPayCallbackReply struct {
TraceNo string
Result string
Msg string
}
// PayInfo represents short pay details.
type PayInfo struct {
OrderNo string
PaymentType string
CodeUrl string
PaymentMoney int32
}
// CopyFromPayOrder copies fields from pay order.
func (p *PayInfo) CopyFromPayOrder(po *PayOrder) {
p.OrderNo = po.OrderNo
p.PaymentType = po.PaymentType
p.PaymentMoney = po.PaymentMoney
}
// YstRenewOrderReq.
type YstRenewOrderReq struct {
SeqNo string `json:"seqno" url:"seqno"`
Source string `json:"source" url:"source"`
ProductId string `json:"product_id" url:"product_id"`
Price int32 `json:"price" url:"price"`
BuyNum int32 `json:"buy_num" url:"buy_num"`
Total int32 `json:"total" url:"total"`
VideoType string `json:"video_type" url:"video_type"`
PayType string `json:"pay_type" url:"pay_type"`
UserId string `json:"user_id" url:"user_id"`
ContractId string `json:"contract_id" url:"contract_id"`
Sandbox string `json:"sandbox" url:"sandbox"`
ClientIp string `json:"client_ip" url:"client_ip"`
Sign string `json:"sign"`
}
// YstRenewOrderReply.
type YstRenewOrderReply struct {
SeqNo string `json:"seqno" url:"seqno"`
TraceNo string `json:"traceno" url:"traceno"`
Price int32 `json:"price" url:"price"`
VideoType string `json:"video_type" url:"video_type"`
ResultCode string `json:"result_code" url:"result_code"`
ResultMsg string `json:"result_msg" url:"result_msg"`
Sign string `json:"sign" `
}
// YstOrderState.
type YstOrderStateReq struct {
SeqNo string `json:"seqno" url:"seqno"`
TraceNo string `json:"traceno" url:"traceno"`
Sign string `json:"sign" url:"sign"`
}
// YstOrderStateReply.
type YstOrderStateReply struct {
SeqNo string `json:"seqno" `
TraceNo string `json:"traceno" `
PayStatus string `json:"pay_status"`
Result string `json:"result"`
Msg string `json:"msg"`
}
// YstUserInfoReq.
type YstUserInfoReq struct {
Mid int32 `json:"mid" url:"mid"`
Sign string `json:"sign" url:"sign,omitempty"`
}
// WxContractCallbackReq.
type WxContractCallbackReq struct {
ContractId string `json:"contract_id" url:"contract_id"`
ContractCode string `json:"contract_code" url:"contract_code"`
ChangeType string `json:"change_type" url:"contract_id"`
ContractTerminationMode string `json:"contract_termination_mode" url:"contract_termination_mode,omitempty"`
Sign string `json:"sign" url:"sign"`
}
// WxContractCallbackReply.
type WxContractCallbackReply struct {
ContractId string `json:"contract_id"`
Result string `json:"result"`
Msg string `json:"msg"`
}

View File

@@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["sign.go"],
importpath = "go-common/app/service/main/tv/internal/pkg",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/log:go_default_library",
"//vendor/github.com/google/go-querystring/query: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,84 @@
package pkg
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"net/url"
"sort"
"strings"
"go-common/library/log"
"github.com/google/go-querystring/query"
)
const (
_sign = "sign"
)
// Signer.
type Signer struct {
Key string
}
func (s *Signer) struct2Values(in interface{}) (out url.Values, err error) {
out, err = query.Values(in)
if err != nil {
return
}
out.Del(_sign)
return
}
func (s *Signer) encodeUrlValues(values url.Values) (res string) {
keys := make([]string, 0)
for key := range values {
keys = append(keys, key)
}
sort.Strings(keys)
kvSli := make([]string, 0)
for _, key := range keys {
kvStr := fmt.Sprintf("%s=%s", key, values.Get(key))
kvSli = append(kvSli, kvStr)
}
paramsStr := strings.Join(kvSli, "&")
return strings.ToLower(paramsStr) + "&key=" + s.Key
}
func (s *Signer) escapeStr(str string) (res string) {
return s.adapt(url.QueryEscape(str))
}
func (s *Signer) md5(str string) (res string) {
var (
buf bytes.Buffer
)
buf.WriteString(str)
hexMd5 := md5.Sum(buf.Bytes())
return strings.ToUpper(hex.EncodeToString(hexMd5[:]))
}
// Sign makes sign for yst request.
func (s *Signer) Sign(in interface{}) (sign string, err error) {
var (
values url.Values
)
if values, err = s.struct2Values(in); err != nil {
log.Error("signer.struct2Values(%+v) err(%+v)", in, err)
return
}
encodedVals := s.encodeUrlValues(values)
escapedVals := s.escapeStr(encodedVals)
sign = s.md5(escapedVals)
log.Info("Signer.Sign(%+v) sign(%s)", in, sign)
return
}
// NOTE: 经核对,云视听只有 `*` 不需要转义
func (s *Signer) adapt(str string) string {
res := str
res = strings.Replace(res, "%2A", "*", -1)
return res
}

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 = ["server.go"],
importpath = "go-common/app/service/main/tv/internal/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/tv/api:go_default_library",
"//app/service/main/tv/internal/model:go_default_library",
"//app/service/main/tv/internal/service:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/rpc/warden: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,233 @@
package grpc
import (
"context"
pb "go-common/app/service/main/tv/api"
"go-common/app/service/main/tv/internal/model"
"go-common/app/service/main/tv/internal/service"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/rpc/warden"
)
// New new warden rpc server
func New(c *warden.ServerConfig, svc *service.Service) *warden.Server {
ws := warden.NewServer(c)
pb.RegisterTVServiceServer(ws.Server(), &server{svc})
ws, err := ws.Start()
if err != nil {
panic(err)
}
log.Info("start grpc server")
return ws
}
type server struct {
svr *service.Service
}
var _ pb.TVServiceServer = &server{}
// UserInfo implementation
func (s *server) UserInfo(ctx context.Context, req *pb.UserInfoReq) (resp *pb.UserInfoReply, err error) {
ui, err := s.svr.UserInfo(ctx, req.Mid)
if err != nil {
return
}
if ui == nil {
return nil, ecode.NothingFound
}
resp = &pb.UserInfoReply{}
resp.DeepCopyFromUserInfo(ui)
return
}
// ChangeHistory implementation
func (s *server) ChangeHistory(ctx context.Context, req *pb.ChangeHistoryReq) (resp *pb.ChangeHistoryReply, err error) {
ch, err := s.svr.ChangeHistory(ctx, req.Id)
if err != nil {
return
}
if ch == nil {
return nil, ecode.NothingFound
}
resp = &pb.ChangeHistoryReply{}
resp.DeepCopyFromUserChangeHistory(ch)
return
}
// ChangeHistorys implementation
func (s *server) ChangeHistorys(ctx context.Context, req *pb.ChangeHistorysReq) (resp *pb.ChangeHistorysReply, err error) {
chs, total, err := s.svr.ChangeHistorys(ctx, req.Mid, req.From, req.To, req.Pn, req.Ps)
if err != nil {
return
}
resp = &pb.ChangeHistorysReply{Total: int32(total)}
resp.Historys = make([]*pb.ChangeHistoryReply, 0, len(chs))
for _, ch := range chs {
chr := &pb.ChangeHistoryReply{}
chr.DeepCopyFromUserChangeHistory(ch)
resp.Historys = append(resp.Historys, chr)
}
return
}
func suitType2String(st int8) string {
switch st {
case model.SuitTypeAll:
return "ALL"
case model.SuitTypeMvip:
return "MVIP"
default:
return "ALL"
}
}
// PanelInfo implemention
func (s *server) PanelInfo(ctx context.Context, req *pb.PanelInfoReq) (resp *pb.PanelInfoReply, err error) {
pi, err := s.svr.PanelInfo(ctx, req.Mid)
if err != nil {
return
}
resp = &pb.PanelInfoReply{}
resp.PriceConfigs = make(map[string]*pb.PanelPriceConfigs)
for st, ps := range pi {
ppcs := &pb.PanelPriceConfigs{}
ppcs.PriceConfigs = make([]*pb.PanelPriceConfig, 0)
for _, p := range ps {
item := &pb.PanelPriceConfig{}
item.DeepCopyFromPanelPriceConfig(p)
ppcs.PriceConfigs = append(ppcs.PriceConfigs, item)
}
resp.PriceConfigs[suitType2String(st)] = ppcs
}
return
}
// GuestPanelInfo implemention
func (s *server) GuestPanelInfo(ctx context.Context, req *pb.GuestPanelInfoReq) (resp *pb.GuestPanelInfoReply, err error) {
pi, err := s.svr.GuestPanelInfo(ctx)
if err != nil {
return
}
resp = &pb.GuestPanelInfoReply{}
resp.PriceConfigs = make(map[string]*pb.PanelPriceConfigs)
for st, ps := range pi {
ppcs := &pb.PanelPriceConfigs{}
ppcs.PriceConfigs = make([]*pb.PanelPriceConfig, 0, len(ps))
for _, p := range ps {
item := &pb.PanelPriceConfig{}
item.DeepCopyFromPanelPriceConfig(p)
ppcs.PriceConfigs = append(ppcs.PriceConfigs, item)
}
resp.PriceConfigs[suitType2String(st)] = ppcs
}
return
}
// PayOrder implementation
func (s *server) PayOrder(ctx context.Context, req *pb.PayOrderReq) (resp *pb.PayOrderReply, err error) {
resp = &pb.PayOrderReply{}
return
}
// CreateQr implementation
func (s *server) CreateQr(ctx context.Context, req *pb.CreateQrReq) (resp *pb.CreateQrReply, err error) {
qr, err := s.svr.CreateQr(ctx, req.Mid, req.Pid, req.BuyNum, req.Guid, req.AppChannel)
if err != nil {
return
}
resp = &pb.CreateQrReply{}
resp.DeepCopyFromQR(qr)
return
}
// CreateGuestQr implementation
func (s *server) CreateGuestQr(ctx context.Context, req *pb.CreateGuestQrReq) (resp *pb.CreateGuestQrReply, err error) {
qr, err := s.svr.CreateGuestQr(ctx, req.Pid, req.BuyNum, req.Guid, req.AppChannel)
if err != nil {
return
}
resp = &pb.CreateGuestQrReply{}
resp.DeepCopyFromQR(qr)
return
}
// CreateOrder implementation
func (s *server) CreateOrder(ctx context.Context, req *pb.CreateOrderReq) (resp *pb.CreateOrderReply, err error) {
pi, err := s.svr.CreateOrder(ctx, req.Token, req.Platform, req.PaymentType, req.ClientIp)
if err != nil {
return
}
resp = &pb.CreateOrderReply{}
resp.DeepCopyFromPayInfo(pi)
return
}
// CreateGuestOrder implementation
func (s *server) CreateGuestOrder(ctx context.Context, req *pb.CreateGuestOrderReq) (resp *pb.CreateGuestOrderReply, err error) {
pi, err := s.svr.CreateGuestOrder(ctx, req.Mid, req.Token, req.Platform, req.PaymentType, req.ClientIp)
if err != nil {
return
}
resp = &pb.CreateGuestOrderReply{}
resp.DeepCopyFromPayInfo(pi)
return
}
// RenewVip implementation
func (s *server) TokenInfo(ctx context.Context, req *pb.TokenInfoReq) (resp *pb.TokenInfoReply, err error) {
ti, err := s.svr.TokenInfos(ctx, req.Token)
if err != nil {
return
}
resp = &pb.TokenInfoReply{}
resp.Tokens = make([]*pb.TokenInfo, 0)
for _, v := range ti {
t := &pb.TokenInfo{}
t.DeepCopyFromTokenInfo(v)
resp.Tokens = append(resp.Tokens, t)
}
return
}
// RenewVip implementation
func (s *server) RenewVip(ctx context.Context, req *pb.RenewVipReq) (resp *pb.RenewVipReply, err error) {
err = s.svr.RenewVip(ctx, req.Mid)
if err != nil {
return
}
resp = &pb.RenewVipReply{}
return
}
// YstUserInfo implementation
func (s *server) YstUserInfo(ctx context.Context, req *pb.YstUserInfoReq) (resp *pb.YstUserInfoReply, err error) {
resp = &pb.YstUserInfoReply{}
ui, err := s.svr.YstUserInfo(ctx, req.DeepCopyAsYstUserInfoReq())
if err != nil {
resp.Result = "998"
resp.Msg = err.Error()
return
}
resp.DeepCopyFromUserInfo(ui)
resp.Result = "0"
resp.Msg = "ok"
return
}
// PayCallback implementation
func (s *server) PayCallback(ctx context.Context, req *pb.PayCallbackReq) (resp *pb.PayCallbackReply, err error) {
ystReq := &model.YstPayCallbackReq{}
req.DeepCopyAsIntoYstPayCallbackReq(ystReq)
ystReply := s.svr.PayCallback(ctx, ystReq)
resp = &pb.PayCallbackReply{}
resp.DeepCopyFromYstPayCallbackReply(ystReply)
return
}
// WxContractCallback implementation.
func (s *server) WxContractCallback(ctx context.Context, req *pb.WxContractCallbackReq) (resp *pb.WxContractCallbackReply, err error) {
resp = &pb.WxContractCallbackReply{}
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 = [
"http.go",
"tv.go",
],
importpath = "go-common/app/service/main/tv/internal/server/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/tv/internal/conf:go_default_library",
"//app/service/main/tv/internal/service:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/verify: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,48 @@
package http
import (
"net/http"
"go-common/app/service/main/tv/internal/conf"
"go-common/app/service/main/tv/internal/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
vfy *verify.Verify
svc *service.Service
)
// Init init
func Init(c *conf.Config, s *service.Service) {
svc = s
vfy = verify.New(c.Verify)
engine := bm.DefaultServer(c.BM)
route(engine)
if err := engine.Start(); err != nil {
log.Error("bm Start error(%v)", err)
panic(err)
}
}
func route(e *bm.Engine) {
e.Ping(ping)
e.Register(register)
g := e.Group("/x/internal/tv/vip", vfy.Verify)
{
g.GET("/user/info", userInfo)
}
}
func ping(ctx *bm.Context) {
if err := svc.Ping(ctx); err != nil {
log.Error("ping error(%v)", err)
ctx.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}

View File

@@ -0,0 +1,22 @@
package http
import (
"strconv"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
func userInfo(c *bm.Context) {
var (
mid int
err error
)
midStr := c.Request.Form.Get("mid")
if mid, err = strconv.Atoi(midStr); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
res, err := svc.UserInfo(c, int64(mid))
c.JSON(res, err)
}

View File

@@ -0,0 +1,73 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"change_history.go",
"order.go",
"panel.go",
"pay.go",
"qr.go",
"service.go",
"token.go",
"user_info.go",
],
importpath = "go-common/app/service/main/tv/internal/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/account/api:go_default_library",
"//app/service/main/tv/internal/conf:go_default_library",
"//app/service/main/tv/internal/dao:go_default_library",
"//app/service/main/tv/internal/model:go_default_library",
"//app/service/main/tv/internal/service/validator:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/time:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/service/main/tv/internal/service/validator:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"change_history_test.go",
"order_test.go",
"panel_test.go",
"pay_test.go",
"qr_test.go",
"service_test.go",
"token_test.go",
"user_info_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/service/main/tv/internal/conf:go_default_library",
"//app/service/main/tv/internal/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,19 @@
package service
import (
"context"
"go-common/app/service/main/tv/internal/model"
xtime "go-common/library/time"
)
func (s *Service) ChangeHistory(c context.Context, hid int32) (ch *model.UserChangeHistory, err error) {
return s.dao.UserChangeHistoryByID(c, hid)
}
func (s *Service) ChangeHistorys(c context.Context, mid int64, from, to, pn, ps int32) (chs []*model.UserChangeHistory, total int, err error) {
if from == 0 || to == 0 {
return s.dao.UserChangeHistorysByMid(c, mid, pn, ps)
}
return s.dao.UserChangeHistorysByMidAndCtime(c, mid, xtime.Time(from), xtime.Time(to), pn, ps)
}

View File

@@ -0,0 +1,46 @@
package service
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceChangeHistory(t *testing.T) {
convey.Convey("ChangeHistory", t, func(ctx convey.C) {
var (
c = context.Background()
hid = int32(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ch, err := s.ChangeHistory(c, hid)
ctx.Convey("Then err should be nil.ch should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ch, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceChangeHistorys(t *testing.T) {
convey.Convey("ChangeHistorys", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
from = int32(0)
to = int32(time.Now().Unix())
pn = int32(1)
ps = int32(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
chs, total, err := s.ChangeHistorys(c, mid, from, to, pn, ps)
ctx.Convey("Then err should be nil.chs,total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
ctx.So(chs, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,209 @@
package service
import (
"bytes"
"context"
"fmt"
"strconv"
"time"
"go-common/app/service/main/account/api"
"go-common/app/service/main/tv/internal/model"
"go-common/app/service/main/tv/internal/service/validator"
xsql "go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
// makeOrderNo get order id
func (s *Service) makeOrderNo() string {
var b bytes.Buffer
b.WriteString(fmt.Sprintf("%05d", s.r.Int63n(99999)))
b.WriteString(fmt.Sprintf("%03d", time.Now().UnixNano()/1e6%1000))
b.WriteString(time.Now().Format("060102150405"))
return b.String()
}
func (s *Service) createOrder(c context.Context, mid int64, token string, platform int8, paymentType string, clientIp string) (pi *model.PayInfo, err error) {
var (
payParam *model.PayParam
panel *model.PanelPriceConfig
mvipPanels []*model.PanelPriceConfig
mvip *model.MainVip
payOrder *model.PayOrder
payingOrders []*model.PayOrder
account *api.Info
ui *model.UserInfo
tx *xsql.Tx
)
if payParam, err = s.dao.CachePayParamByToken(c, token); payParam == nil || err != nil {
if err == nil {
err = ecode.TVIPTokenErr
}
log.Error("s.dao.CachePayParamByToken(%s) err(%v)", token, err)
return
}
if panel, err = s.PanelPriceConfigByPid(c, payParam.Pid); panel == nil || err != nil {
if err == nil {
err = ecode.TVIPPanelNotFound
}
log.Error("s.PanelPriceConfigByPid(%d) err(%v)", payParam.Pid, err)
return
}
if mid == -1 {
mid = payParam.Mid
}
key := fmt.Sprintf("LOCK:ORDER:%d", mid)
val := strconv.Itoa(time.Now().Nanosecond())
if err = s.dao.Lock(c, key, val); err != nil {
log.Error("s.dao.Lock(%s, %s) err(%+v)", key, err)
return
}
defer func() {
if err := s.dao.Unlock(c, key, val); err != nil {
log.Error("s.dao.Unlock(%s, %s) err(%+v)", key, val, err)
}
}()
if mvip, err = s.dao.MainVip(c, mid); err != nil {
log.Error("s.dao.MainVip(%d) err(%v)", mid, err)
return
}
if payOrder, err = s.dao.PayOrderByOrderNo(c, payParam.OrderNo); err != nil {
log.Error("s.dao.PayOrderByOrderNo(%s) err(%v)", payParam.OrderNo, err)
return
}
if ui, err = s.dao.RawUserInfoByMid(c, mid); err != nil {
log.Error("s.dao.RawUserInfoByMid(%d) err(%+v)", mid, err)
return
}
from := xtime.Time(time.Now().Add(-time.Duration(s.c.PAY.OrderRateFromDuration)).Unix())
to := xtime.Time(time.Now().Unix())
if mvipPanels, err = s.PanelPriceConfigsBySuitType(c, model.SuitTypeMvip); err != nil {
log.Error("s.PanelPriceConfigsBySuitType(%d) err(%+v)", model.SuitTypeMvip, err)
return
}
if payingOrders, _, err = s.dao.PayOrdersByMidAndStatusAndCtime(c, mid, model.PayOrderStatusPaying, from, to, 1, 100); err != nil {
log.Error("s.dao.PayOrdersByMidAndStatusAndCtime(%d, %d, %v, %v) err(%+v)", mid, model.PayOrderStatusPaying, from, to, err)
return
}
log.Info("payingOrders mid: %d from: %+v to: %+v orders: %+v", mid, from, to, payingOrders)
cv := &validator.CreateOrderValidator{
PayOrder: payOrder,
PaymentType: paymentType,
PayParam: payParam,
MVip: mvip,
Panel: panel,
Ui: ui,
PayingOrders: payingOrders,
MVipPanels: mvipPanels,
MVipRateMaxNumber: s.c.PAY.OrderRateMaxNumber,
}
if err = cv.Validate(); err != nil {
log.Error("cv.Validate err(%v)", err)
return
}
if payParam.OrderNo == "" {
payParam.OrderNo = s.makeOrderNo()
}
payOrder = &model.PayOrder{
Platform: platform,
Mid: mid,
Status: 1,
PaymentType: paymentType,
Ver: 1,
Token: token,
}
payOrder.CopyFromPayParam(payParam)
payOrder.CopyFromPanel(panel)
ystCreateOrderReq := &model.YstCreateOrderReq{GUID: payParam.Guid, ClientIp: clientIp}
ystCreateOrderReq.CopyFromPayOrder(payOrder)
if account, err = s.dao.AccountInfo(c, mid); err != nil {
return
}
ystCreateOrderReq.CopyFromAccount(account)
ystOrder, err := s.dao.CreateYstOrder(c, ystCreateOrderReq)
if err != nil {
log.Error("s.dao.CreateYstOrder(%+v) err(%+v)", ystCreateOrderReq, err)
return
}
payParam.Status = model.PayOrderStatusPaying
payParam.OrderNo = payOrder.OrderNo
payOrder.ThirdTradeNo = ystOrder.TraceNo
tx, err = s.dao.BeginTran(c)
if err != nil {
log.Error("s.dao.BeginTran() err(%+v)", err)
return
}
defer func() {
s.dao.EndTran(tx, err)
}()
if _, err = s.dao.TxInsertPayOrder(c, tx, payOrder); err != nil {
log.Error("s.dao.TxInsertPayOrder(%+v) err(%+v)", payOrder, err)
return
}
s.flushPayParamAsync(c, token, payParam)
pi = &model.PayInfo{CodeUrl: ystOrder.CodeUrl}
pi.CopyFromPayOrder(payOrder)
return pi, nil
}
func (s *Service) CreateOrder(c context.Context, token string, platform int8, paymentType string, clientIp string) (pi *model.PayInfo, err error) {
return s.createOrder(c, -1, token, platform, paymentType, clientIp)
}
func (s *Service) CreateGuestOrder(c context.Context, mid int64, token string, platform int8, paymentType string, clientIp string) (pi *model.PayInfo, err error) {
return s.createOrder(c, mid, token, platform, paymentType, clientIp)
}
func (s *Service) MakeUpOrderStatus() error {
var (
err error
res []*model.PayOrder
std time.Duration
etd time.Duration
)
c := context.TODO()
now := time.Now()
if std, err = time.ParseDuration(s.c.Ticker.UnpaidDurationEtime); err != nil {
return err
}
stime := now.Add(-std)
if etd, err = time.ParseDuration(s.c.Ticker.UnpaidDurationStime); err != nil {
return err
}
etime := now.Add(-etd)
if res, err = s.dao.UnpaidNotCallbackOrder(c, xtime.Time(stime.Unix()), xtime.Time(etime.Unix()), 1, 500); err != nil {
log.Error("s.dao.UnpaidNoCallbackOrder err(%v)", err)
return err
}
for _, payOrder := range res {
var (
ystOrder *model.YstOrderStateReply
)
ystOrderReq := &model.YstOrderStateReq{
SeqNo: payOrder.OrderNo,
TraceNo: payOrder.ThirdTradeNo,
}
if ystOrder, err = s.dao.YstOrderState(c, ystOrderReq); err != nil {
log.Error("s.dao.YstOrderState(%+v) err(%+v)", ystOrderReq, err)
continue
}
if ystOrder.PayStatus == model.YstPayStatusPaied {
s.mission(func() {
if err = s.paySuccess(c, ystOrder.PayStatus, payOrder); err != nil {
log.Error("s.MakeUpPaySuccessStatus(%s, %+v) err(%+v)", ystOrder.PayStatus, payOrder, err)
}
})
} else if ystOrder.PayStatus == model.YstPayStatusPending {
s.mission(func() {
if err = s.payFail(c, ystOrder.PayStatus, payOrder); err != nil {
log.Error("s.MakeUpPayFailStatus(%s, %+v) err(%+v)", ystOrder.PayStatus, payOrder, err)
}
})
}
}
return err
}

View File

@@ -0,0 +1,78 @@
package service
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServicemakeOrderNo(t *testing.T) {
convey.Convey("makeOrderNo", t, func(ctx convey.C) {
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := s.makeOrderNo()
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
//
//func TestServicecreateOrder(t *testing.T) {
// convey.Convey("createOrder", t, func(ctx convey.C) {
// var (
// c = context.Background()
// mid = int64(0)
// token = ""
// platform = int8(0)
// paymentType = ""
// clientIp = ""
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// pi, err := s.createOrder(c, mid, token, platform, paymentType, clientIp)
// ctx.Convey("Then err should be nil.pi should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(pi, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceCreateOrder(t *testing.T) {
// convey.Convey("CreateOrder", t, func(ctx convey.C) {
// var (
// c = context.Background()
// token = ""
// platform = int8(0)
// paymentType = ""
// clientIp = ""
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// pi, err := s.CreateOrder(c, token, platform, paymentType, clientIp)
// ctx.Convey("Then err should be nil.pi should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(pi, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceCreateGuestOrder(t *testing.T) {
// convey.Convey("CreateGuestOrder", t, func(ctx convey.C) {
// var (
// c = context.Background()
// mid = int64(0)
// token = ""
// platform = int8(0)
// paymentType = ""
// clientIp = ""
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// pi, err := s.CreateGuestOrder(c, mid, token, platform, paymentType, clientIp)
// ctx.Convey("Then err should be nil.pi should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(pi, convey.ShouldNotBeNil)
// })
// })
// })
//}

View File

@@ -0,0 +1,124 @@
package service
import (
"context"
"go-common/app/service/main/tv/internal/model"
"go-common/library/log"
)
const (
ALL int8 = 0
MAIN_VIP int8 = 10
)
func (s *Service) GetPpcsMap() map[int8][]*model.PanelPriceConfig {
return s.ppcsMap.Load().(map[int8][]*model.PanelPriceConfig)
}
func (s *Service) PanelPriceConfigsBySuitType(c context.Context, st int8) (ppcs []*model.PanelPriceConfig, err error) {
ppcsMap := s.GetPpcsMap()
ppcs, ok := ppcsMap[st]
if !ok {
return make([]*model.PanelPriceConfig, 0), nil
}
return ppcs, nil
}
func (s *Service) PanelPriceConfigByProductId(c context.Context, productId string) (ppc *model.PanelPriceConfig, err error) {
ppcsMap := s.GetPpcsMap()
for _, ppcs := range ppcsMap {
for _, ppc = range ppcs {
if ppc.ProductId == productId {
return ppc, nil
}
}
}
return nil, nil
}
func (s *Service) PanelPriceConfigByPid(c context.Context, pid int32) (ppc *model.PanelPriceConfig, err error) {
ppcsMap := s.GetPpcsMap()
for _, ppcs := range ppcsMap {
for _, ppc = range ppcs {
if ppc.ID == pid {
return ppc, nil
}
}
}
return nil, nil
}
func (s *Service) GuestPanelPriceConfigs(c context.Context) (ppcs []*model.PanelPriceConfig, err error) {
return s.PanelPriceConfigsBySuitType(c, ALL)
}
func (s *Service) MVipPanelPriceConfigs(c context.Context, mid int64) (ppcs []*model.PanelPriceConfig, err error) {
mv, err := s.dao.MainVip(c, mid)
if err != nil {
return nil, err
}
ppcs, err = s.PanelPriceConfigsBySuitType(c, MAIN_VIP)
if err != nil {
log.Error("s.PanelPriceConfigsBySuitType(%d), err(%v)", MAIN_VIP, err)
return nil, err
}
if !mv.IsVip() || mv.Months() < 1 {
return make([]*model.PanelPriceConfig, 0), nil
}
for _, ppc := range ppcs {
ppc.MaxNum = mv.Months()
}
return ppcs, nil
}
func (s *Service) GuestPanelInfo(c context.Context) (pi map[int8][]*model.PanelPriceConfig, err error) {
gpps, err := s.GuestPanelPriceConfigs(c)
if err != nil {
log.Error("s.GuestPanelPriceConfigs err(%v)", err)
return
}
pi = make(map[int8][]*model.PanelPriceConfig)
pi[ALL] = gpps
return pi, nil
}
func (s *Service) PanelInfo(c context.Context, mid int64) (pi map[int8][]*model.PanelPriceConfig, err error) {
gpps, err := s.GuestPanelPriceConfigs(c)
if err != nil {
log.Error("s.GuestPanelPriceConfigs err(%v)", err)
return
}
mvpps, err := s.MVipPanelPriceConfigs(c, mid)
if err != nil {
log.Error("s.MVipPanelPriceConfigs(%d) err(%v)", mid, err)
return
}
// NOTE: do we need to use uc replace ui?
ui, err := s.dao.RawUserInfoByMid(c, mid)
if err != nil {
return
}
// NOTE: 包月用户无法看到包月套餐即使用户vip过期
if ui != nil && ui.PayType == model.VipPayTypeSub {
for i := 0; i < len(gpps); i++ {
if gpps[i].SubType == model.SubTypeContract {
gpps = append(gpps[:i], gpps[i+1:]...)
i--
}
}
for i := 0; i < len(mvpps); i++ {
if mvpps[i].SubType == model.SubTypeContract {
mvpps = append(mvpps[:i], mvpps[i+1:]...)
i--
}
}
}
// NOTE: tv 会员无法通过主站会员升级方式购买
if ui != nil && ui.IsVip() {
mvpps = make([]*model.PanelPriceConfig, 0)
}
pi = make(map[int8][]*model.PanelPriceConfig)
pi[ALL] = gpps
pi[MAIN_VIP] = mvpps
return pi, nil
}

View File

@@ -0,0 +1,129 @@
package service
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceGetPpcsMap(t *testing.T) {
convey.Convey("GetPpcsMap", t, func(ctx convey.C) {
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := s.GetPpcsMap()
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServicePanelPriceConfigsBySuitType(t *testing.T) {
convey.Convey("PanelPriceConfigsBySuitType", t, func(ctx convey.C) {
var (
c = context.Background()
st = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ppcs, err := s.PanelPriceConfigsBySuitType(c, st)
ctx.Convey("Then err should be nil.ppcs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ppcs, convey.ShouldNotBeNil)
})
})
})
}
func TestServicePanelPriceConfigByProductId(t *testing.T) {
convey.Convey("PanelPriceConfigByProductId", t, func(ctx convey.C) {
var (
c = context.Background()
productId = "zc20181206"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ppc, err := s.PanelPriceConfigByProductId(c, productId)
ctx.Convey("Then err should be nil.ppc should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ppc, convey.ShouldNotBeNil)
})
})
})
}
func TestServicePanelPriceConfigByPid(t *testing.T) {
convey.Convey("PanelPriceConfigByPid", t, func(ctx convey.C) {
var (
c = context.Background()
pid = int32(1)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ppc, err := s.PanelPriceConfigByPid(c, pid)
ctx.Convey("Then err should be nil.ppc should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ppc, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceGuestPanelPriceConfigs(t *testing.T) {
convey.Convey("GuestPanelPriceConfigs", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ppcs, err := s.GuestPanelPriceConfigs(c)
ctx.Convey("Then err should be nil.ppcs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ppcs, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceMVipPanelPriceConfigs(t *testing.T) {
convey.Convey("MVipPanelPriceConfigs", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ppcs, err := s.MVipPanelPriceConfigs(c, mid)
ctx.Convey("Then err should be nil.ppcs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ppcs, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceGuestPanelInfo(t *testing.T) {
convey.Convey("GuestPanelInfo", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
pi, err := s.GuestPanelInfo(c)
ctx.Convey("Then err should be nil.pi should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(pi, convey.ShouldNotBeNil)
})
})
})
}
func TestServicePanelInfo(t *testing.T) {
convey.Convey("PanelInfo", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
pi, err := s.PanelInfo(c, mid)
ctx.Convey("Then err should be nil.pi should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(pi, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,398 @@
package service
import (
"context"
"strconv"
"time"
"go-common/app/service/main/tv/internal/model"
"go-common/app/service/main/tv/internal/service/validator"
xsql "go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
func (s *Service) flushPayParamAsync(c context.Context, token string, payParam *model.PayParam) {
s.mission(func() {
if err := s.dao.UpdateCachePayParam(context.TODO(), token, payParam); err != nil {
log.Error("s.dao.UpdateCachePayParam(%s, %+v) err(%+v)", token, payParam, err)
}
})
}
func (s *Service) flushUserInfoAsync(c context.Context, mid int64) {
s.mission(func() {
// delete user info cache
if err := s.dao.DelCacheUserInfoByMid(context.TODO(), mid); err != nil {
log.Error("s.dao.DelCacheUserInfoByMid(%d) err(%+v)", mid, err)
}
})
}
func (s *Service) giveMVipGiftAsync(c context.Context, mid int64, pid int32, orderNo string) {
s.mission(func() {
if err := s.GiveMVipGift(context.TODO(), mid, pid, orderNo); err != nil {
log.Error("s.GiveMVipGift(%d, %d, %s)", mid, pid, orderNo)
}
})
}
func (s *Service) initUserInfo(c context.Context, tx *xsql.Tx, mid int64) (ui *model.UserInfo, err error) {
ui = &model.UserInfo{
Mid: mid,
VipType: model.VipTypeVip,
}
if _, err = s.dao.TxInsertUserInfo(c, tx, ui); err != nil {
log.Info("s.dao.TxInsertUserInfo(%+v) err(%+v)", ui, err)
return ui, err
}
return ui, nil
}
func (s *Service) PayOrder(c context.Context, id int) (po *model.PayOrder, err error) {
return s.dao.PayOrderByID(c, id)
}
func (s *Service) YstOrderState(c context.Context, seqNo string, traceNo string) (res *model.YstOrderStateReply, err error) {
ystOrderReq := &model.YstOrderStateReq{
SeqNo: seqNo,
TraceNo: traceNo,
}
return s.dao.YstOrderState(c, ystOrderReq)
}
func (s *Service) PayPending(c context.Context, req *model.YstPayCallbackReq, payOrder *model.PayOrder) error {
log.Info("s.PayCallback.PayPending(%+v, %+v)", req, payOrder)
return nil
}
func (s *Service) payFail(c context.Context, status string, payOrder *model.PayOrder) error {
log.Info("s.PayCallback.PayFail(%s, %+v)", status, payOrder)
var (
payParam *model.PayParam
//ystOrder *model.YstOrderStateReply
tx *xsql.Tx
err error
)
if payOrder.Status == model.PayOrderStatusFail {
log.Info("s.PayFail(%s, %+v) msg(DuplicatedCallback)", status, payOrder)
return nil
}
//if ystOrder, err = s.YstOrderState(c, payOrder.OrderNo, payOrder.ThirdTradeNo); err != nil {
// log.Error("s.YstOrderState(%s, %s) err(%+v)", payOrder.OrderNo, payOrder.ThirdTradeNo, err)
// return err
//}
//if ystOrder.PayStatus != model.YstPayStatusPending {
// return ecode.TVIPYstRequestErr
//}
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("s.dao.BeginTran() err(%v)", err)
return err
}
defer func() {
if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
payOrder.Status = model.PayOrderStatusFail
if err = s.dao.TxUpdatePayOrder(c, tx, payOrder); err != nil {
log.Error("s.dao.TxUpdatePayOrder(%v) err(%v)", payOrder, err)
return err
}
if payOrder.OrderType == model.PayOrderTypeSub {
return nil
}
if payParam, err = s.dao.CachePayParamByToken(c, payOrder.Token); err != nil {
log.Info("s.dao.CachePayParamByToken(%s) err(%+v)", payOrder.Token, err)
return err
}
payParam.Status = model.PayOrderStatusFail
payParam.OrderNo = payOrder.OrderNo
s.flushPayParamAsync(c, payOrder.Token, payParam)
return nil
}
func (s *Service) paySuccess(c context.Context, status string, payOrder *model.PayOrder) error {
log.Info("s.PayCallback.paySuccess(%s, %+v)", status, payOrder)
var (
tx *xsql.Tx
ui *model.UserInfo
uch *model.UserChangeHistory
uc *model.UserContract
payParam *model.PayParam
panel *model.PanelPriceConfig
//ystOrder *model.YstOrderStateReply
err error
)
if payOrder.Status == model.PayOrderStatusSuccess {
log.Info("s.PaySuccess(%s, %+v) msg(DuplicatedCallback)", status, payOrder)
return nil
}
//ystOrderReq := &model.YstOrderStateReq{
// SeqNo: payOrder.OrderNo,
// TraceNo: payOrder.ThirdTradeNo,
//}
//if ystOrder, err = s.dao.YstOrderState(c, ystOrderReq); err != nil {
// log.Error("s.dao.YstOrderState(%+v) err(%+v)", ystOrderReq, err)
// return err
//}
//if ystOrder.PayStatus != model.YstPayStatusPending {
// return ecode.TVIPYstRequestErr
//}
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("s.dao.BeginTran() err(%v)", err)
return err
}
defer func() {
if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
payOrder.Status = model.PayOrderStatusSuccess
if err = s.dao.TxUpdatePayOrder(c, tx, payOrder); err != nil {
log.Error("s.dao.TxUpdatePayOrder(%v) err(%v)", payOrder, err)
return err
}
if panel, err = s.PanelPriceConfigByProductId(c, payOrder.ProductId); err != nil {
log.Info("s.dao.PanelPriceConfigByProductId(%s) err(%v)", payOrder.ProductId, err)
return err
}
if ui, err = s.dao.RawUserInfoByMid(c, payOrder.Mid); err != nil {
log.Info("s.dao.RawInfoByMid(%d) err(%v)", payOrder.Mid, err)
return err
}
if ui == nil {
if ui, err = s.initUserInfo(c, tx, payOrder.Mid); err != nil {
log.Info("s.initUserInfo(%d) err(%+v)", payOrder.Mid, err)
return err
}
}
ui.CopyFromPayOrder(payOrder)
ui.CopyFromPanel(panel)
buyDuration := int64(time.Hour) * int64(payOrder.BuyMonths) * 24 * 31
if err = s.incrVipDuration(c, tx, ui, time.Duration(buyDuration)); err != nil {
log.Error("s.incrVipDuration(%v, %v) err(%v)", ui, buyDuration, err)
return err
}
uch = &model.UserChangeHistory{}
uch.CopyFromPayOrder(payOrder)
if _, err = s.dao.TxInsertUserChangeHistory(c, tx, uch); err != nil {
log.Error("s.dao.TxInsertUserChangeHistory(%v) err(%v)", uch, err)
return err
}
// Note: send contract request to yst when a user buys contracted package with ali pay.
// TODO: replace contract id by contract code
if panel.IsContracted() && payOrder.PaymentType == model.PaymentTypeWechat {
uc = &model.UserContract{
Mid: payOrder.Mid,
OrderNo: payOrder.OrderNo,
//ContractId: req.ContractId,
}
if _, err = s.dao.TxInsertUserContract(c, tx, uc); err != nil {
log.Error("s.dao.TxInsertUserContract(%v) err(%v)", uc, err)
return err
}
}
if payOrder.OrderType == model.PayOrderTypeNormal {
if payParam, err = s.dao.CachePayParamByToken(c, payOrder.Token); err != nil {
log.Info("s.dao.CachePayParamByToken(%s) err(%+v)", payOrder.Token, err)
return err
}
payParam.Status = model.PayOrderStatusSuccess
payParam.OrderNo = payOrder.OrderNo
s.flushPayParamAsync(c, payOrder.Token, payParam)
}
s.flushUserInfoAsync(c, payOrder.Mid)
if panel.SuitType != model.SuitTypeMvip {
// NOTE: 存在多送和少送的可能性
// 多送发起赠送请求db commit 失败
// 少送发起赠送请求失败db commit 成功
s.giveMVipGiftAsync(c, payOrder.Mid, panel.PidOrId(), payOrder.OrderNo)
}
return nil
}
func (s *Service) PayCallback(c context.Context, req *model.YstPayCallbackReq) (res *model.YstPayCallbackReply) {
var (
err error
)
res = &model.YstPayCallbackReply{TraceNo: req.TraceNo}
payOrder, err := s.dao.PayOrderByOrderNo(c, req.SeqNo)
if err != nil {
log.Error("s.dao.PayOrderByOrderNo(%s) err(%v)", req.SeqNo, err)
res.Result = model.YstResultSysErr
res.Msg = err.Error()
return
}
pv := &validator.PayCallbackValidator{
Signer: s.dao.Signer(),
CallbackReq: req,
PayOrder: payOrder,
ExpireDuration: s.c.PAY.PayExpireDuration,
}
if err = pv.Validate(); err != nil {
log.Error("payCallbackValidator.Validate() err(%v)", err)
res.Result = model.YstResultFail
res.Msg = err.Error()
return
}
payOrder.ThirdTradeNo = req.TraceNo
switch req.TradeState {
case model.YstTradeStateSuccess:
err = s.paySuccess(c, req.TradeState, payOrder)
case model.YstTradeStateClosed, model.YstTradeStatePayFail:
err = s.payFail(c, req.TradeState, payOrder)
default:
err = ecode.TVIPYstUnknownTradeState
}
if err != nil {
res.Result = model.YstResultSysErr
res.Msg = err.Error()
} else {
res.Result = model.YstResultSuccess
res.Msg = "ok"
}
return
}
func (s *Service) GiveMVipGift(c context.Context, mid int64, pid int32, orderNo string) error {
batchId, ok := s.c.MVIP.BatchIdsMap[strconv.Itoa(int(pid))]
if !ok {
log.Error("s.c.MVIP.BatchIdsMap(%d) err(UnknownBatchId)", pid)
return ecode.TVIPBatchIdNotFound
}
return s.dao.GiveMVipGift(c, mid, batchId, orderNo)
}
// TODO: query user info from tv_user_contract.
func (s *Service) UserInfoByContractCode(c context.Context, contractCode string) (ui *model.UserInfo, err error) {
return nil, nil
}
func (s *Service) SignContract(c context.Context, contractCode string, contractId string, remark string) (err error) {
var (
uc *model.UserContract
ui *model.UserInfo
tx *xsql.Tx
)
if ui, err = s.dao.RawUserInfoByMid(c, uc.Mid); err != nil {
return
}
if ui == nil {
return ecode.RequestErr
}
if uc, err = s.dao.UserContractByContractId(c, contractId); err != nil {
return
}
if uc != nil {
return nil
}
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("s.dao.BeginTran() err(%v)", err)
return
}
defer func() {
err = s.dao.EndTran(tx, err)
}()
ui.PayType = model.VipPayTypeSub
if err = s.dao.TxUpdateUserPayType(c, tx, ui); err != nil {
log.Error("s.dao.TxUpdateUserPayType(%+v) err(%v)", ui, err)
return
}
uc = &model.UserContract{
Mid: ui.Mid,
ContractId: contractId,
}
if _, err = s.dao.TxInsertUserContract(c, tx, uc); err != nil {
log.Error("s.dao.TxInsertUserContract(%d) err(%+v)", uc.ID, err)
return
}
uch := &model.UserChangeHistory{
Mid: ui.Mid,
ChangeType: model.UserChangeTypeSignContract,
ChangeTime: xtime.Time(time.Now().Unix()),
Remark: remark,
}
if _, err = s.dao.TxInsertUserChangeHistory(c, tx, uch); err != nil {
log.Error("s.dao.TxInsertUserChangeHistory(%+v) err(%+v)", uch, err)
return
}
s.flushUserInfoAsync(c, ui.Mid)
return nil
}
func (s *Service) CancelContract(c context.Context, contractCode string, contractId string, remark string) (err error) {
var (
uc *model.UserContract
ui *model.UserInfo
tx *xsql.Tx
)
if ui, err = s.dao.RawUserInfoByMid(c, uc.Mid); err != nil {
return
}
if ui == nil {
return ecode.RequestErr
}
if uc, err = s.dao.UserContractByContractId(c, contractId); err != nil {
return
}
if uc == nil {
log.Error("s.CancelContract(%d, %s, %s) err(UserContractNotFound)", ui.Mid, contractId, remark)
return ecode.TVIPNotContracted
}
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("s.dao.BeginTran() err(%v)", err)
return
}
defer func() {
err = s.dao.EndTran(tx, err)
}()
ui.PayType = model.VipPayTypeNormal
if err = s.dao.TxUpdateUserPayType(c, tx, ui); err != nil {
log.Error("s.dao.TxUpdateUserPayType(%+v) err(%v)", ui, err)
return
}
if err = s.dao.TxDeleteUserContract(c, tx, uc.ID); err != nil {
log.Error("s.dao.TxDeleteUserContract(%d) err(%+v)", uc.ID, err)
return
}
uch := &model.UserChangeHistory{
Mid: ui.Mid,
ChangeType: model.UserChangeTypeCancelContract,
ChangeTime: xtime.Time(time.Now().Unix()),
Remark: remark,
}
if _, err = s.dao.TxInsertUserChangeHistory(c, tx, uch); err != nil {
log.Error("s.dao.TxInsertUserChangeHistory(%+v) err(%+v)", uch, err)
return
}
s.flushUserInfoAsync(c, ui.Mid)
return nil
}
func (s *Service) WxContractCallback(c context.Context, req *model.WxContractCallbackReq) (res *model.WxContractCallbackReply) {
var err error
if req.ChangeType == model.YST_CONTRACT_TYPE_SIGN {
err = s.SignContract(c, req.ContractCode, req.ContractId, req.ContractTerminationMode)
} else {
err = s.CancelContract(c, req.ContractCode, req.ContractId, req.ContractTerminationMode)
}
res = &model.WxContractCallbackReply{
ContractId: req.ContractId,
}
if err != nil {
res.Result = model.YstResultSysErr
res.Msg = err.Error()
} else {
res.Result = model.YstResultSuccess
res.Msg = "ok"
}
return res
}

View File

@@ -0,0 +1,245 @@
package service
//
//func TestServiceflushPayParamAsync(t *testing.T) {
// convey.Convey("flushPayParamAsync", t, func(ctx convey.C) {
// var (
// c = context.Background()
// token = ""
// payParam = &model.PayParam{}
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// s.flushPayParamAsync(c, token, payParam)
// ctx.Convey("No return values", func(ctx convey.C) {
// })
// })
// })
//}
//
//func TestServiceflushUserInfoAsync(t *testing.T) {
// convey.Convey("flushUserInfoAsync", t, func(ctx convey.C) {
// var (
// c = context.Background()
// mid = int64(0)
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// s.flushUserInfoAsync(c, mid)
// ctx.Convey("No return values", func(ctx convey.C) {
// })
// })
// })
//}
//
//func TestServicegiveMVipGiftAsync(t *testing.T) {
// convey.Convey("giveMVipGiftAsync", t, func(ctx convey.C) {
// var (
// c = context.Background()
// mid = int64(0)
// pid = int32(0)
// orderNo = ""
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// s.giveMVipGiftAsync(c, mid, pid, orderNo)
// ctx.Convey("No return values", func(ctx convey.C) {
// })
// })
// })
//}
//
//func TestServiceinitUserInfo(t *testing.T) {
// convey.Convey("initUserInfo", t, func(ctx convey.C) {
// var (
// c = context.Background()
// tx, _ = s.dao.BeginTran(c)
// mid = int64(0)
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// ui, err := s.initUserInfo(c, tx, mid)
// ctx.Convey("Then err should be nil.ui should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(ui, convey.ShouldNotBeNil)
// })
// })
// ctx.Reset(func() {
// tx.Commit()
// })
// })
//}
//
//func TestServicePayOrder(t *testing.T) {
// convey.Convey("PayOrder", t, func(ctx convey.C) {
// var (
// c = context.Background()
// id = int(0)
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// po, err := s.PayOrder(c, id)
// ctx.Convey("Then err should be nil.po should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(po, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceYstOrderState(t *testing.T) {
// convey.Convey("YstOrderState", t, func(ctx convey.C) {
// var (
// c = context.Background()
// seqNo = ""
// traceNo = ""
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// res, err := s.YstOrderState(c, seqNo, traceNo)
// ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServicePayPending(t *testing.T) {
// convey.Convey("PayPending", t, func(ctx convey.C) {
// var (
// c = context.Background()
// req = &model.YstPayCallbackReq{}
// payOrder = &model.PayOrder{}
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// err := s.PayPending(c, req, payOrder)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
//}
//
//func TestServicePayFail(t *testing.T) {
// convey.Convey("PayFail", t, func(ctx convey.C) {
// var (
// c = context.Background()
// req = &model.YstPayCallbackReq{}
// payOrder = &model.PayOrder{}
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// err := s.PayFail(c, req, payOrder)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
//}
//
//func TestServicePaySuccess(t *testing.T) {
// convey.Convey("PaySuccess", t, func(ctx convey.C) {
// var (
// c = context.Background()
// req = &model.YstPayCallbackReq{}
// payOrder = &model.PayOrder{}
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// err := s.PaySuccess(c, req, payOrder)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
//}
//
//func TestServicePayCallback(t *testing.T) {
// convey.Convey("PayCallback", t, func(ctx convey.C) {
// var (
// c = context.Background()
// req = &model.YstPayCallbackReq{}
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// res := s.PayCallback(c, req)
// ctx.Convey("Then res should not be nil.", func(ctx convey.C) {
// ctx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceGiveMVipGift(t *testing.T) {
// convey.Convey("GiveMVipGift", t, func(ctx convey.C) {
// var (
// c = context.Background()
// mid = int64(0)
// pid = int32(0)
// orderNo = ""
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// err := s.GiveMVipGift(c, mid, pid, orderNo)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
//}
//
//func TestServiceUserInfoByContractCode(t *testing.T) {
// convey.Convey("UserInfoByContractCode", t, func(ctx convey.C) {
// var (
// c = context.Background()
// contractCode = ""
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// ui, err := s.UserInfoByContractCode(c, contractCode)
// ctx.Convey("Then err should be nil.ui should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(ui, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceSignContract(t *testing.T) {
// convey.Convey("SignContract", t, func(ctx convey.C) {
// var (
// c = context.Background()
// contractCode = ""
// contractId = ""
// remark = ""
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// err := s.SignContract(c, contractCode, contractId, remark)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
//}
//
//func TestServiceCancelContract(t *testing.T) {
// convey.Convey("CancelContract", t, func(ctx convey.C) {
// var (
// c = context.Background()
// contractCode = ""
// contractId = ""
// remark = ""
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// err := s.CancelContract(c, contractCode, contractId, remark)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
//}
//
//func TestServiceWxContractCallback(t *testing.T) {
// convey.Convey("WxContractCallback", t, func(ctx convey.C) {
// var (
// c = context.Background()
// req = &model.WxContractCallbackReq{}
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// res := s.WxContractCallback(c, req)
// ctx.Convey("Then res should not be nil.", func(ctx convey.C) {
// ctx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}

View File

@@ -0,0 +1,47 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/service/main/tv/internal/model"
xtime "go-common/library/time"
)
func (s *Service) MakePayParam(c context.Context, mid int64, pid int32, buyNum int32, guid string, appChannel string) (p *model.PayParam) {
return &model.PayParam{
Mid: mid,
Pid: pid,
OrderNo: s.makeOrderNo(),
BuyNum: buyNum,
Guid: guid,
AppChannel: appChannel,
Status: model.PayOrderStatusPending,
ExpireAt: xtime.Time(time.Now().Unix() + int64(s.c.CacheTTL.PayParamTTL)),
}
}
func (s *Service) CreateQr(c context.Context, mid int64, pid int32, buyNum int32, guid string, appChannel string) (qr *model.QR, err error) {
payParam := s.MakePayParam(c, mid, pid, buyNum, guid, appChannel)
token := payParam.MD5()
qr = &model.QR{
ExpireAt: payParam.ExpireAt,
Token: token,
URL: fmt.Sprintf("%s?token=%s", s.c.PAY.QrURL, token),
}
s.dao.AddCachePayParam(c, token, payParam)
return qr, nil
}
func (s *Service) CreateGuestQr(c context.Context, pid int32, buyNum int32, guid string, appChannel string) (qr *model.QR, err error) {
payParam := s.MakePayParam(c, -1, pid, buyNum, guid, appChannel)
token := payParam.MD5()
qr = &model.QR{
ExpireAt: payParam.ExpireAt,
Token: token,
URL: fmt.Sprintf("%s?token=%s", s.c.PAY.GuestQrURL, token),
}
s.dao.AddCachePayParam(c, token, payParam)
return qr, nil
}

View File

@@ -0,0 +1,63 @@
package service
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceMakePayParam(t *testing.T) {
convey.Convey("MakePayParam", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
pid = int32(1)
buyNum = int32(1)
guid = "bv23456789"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p := s.MakePayParam(c, mid, pid, buyNum, guid, "master")
ctx.Convey("Then p should not be nil.", func(ctx convey.C) {
ctx.So(p, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceCreateQr(t *testing.T) {
convey.Convey("CreateQr", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
pid = int32(1)
buyNum = int32(1)
guid = "bv23456789"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
qr, err := s.CreateQr(c, mid, pid, buyNum, guid, "master")
ctx.Convey("Then err should be nil.qr should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(qr, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceCreateGuestQr(t *testing.T) {
convey.Convey("CreateGuestQr", t, func(ctx convey.C) {
var (
c = context.Background()
pid = int32(1)
buyNum = int32(1)
guid = "4567890"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
qr, err := s.CreateGuestQr(c, pid, buyNum, guid, "master")
ctx.Convey("Then err should be nil.qr should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(qr, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,217 @@
package service
import (
"context"
"math/rand"
"sort"
"sync/atomic"
"time"
"go-common/app/service/main/tv/internal/conf"
"go-common/app/service/main/tv/internal/dao"
"go-common/app/service/main/tv/internal/model"
"go-common/library/log"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
ppcsMap atomic.Value
r *rand.Rand
eventch chan func()
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
r: rand.New(rand.NewSource(time.Now().Unix())),
eventch: make(chan func(), 1024),
}
s.initPanels()
go s.ppcproc()
go s.eventproc()
go s.makeupproc()
return s
}
// Ping Service
func (s *Service) Ping(ctx context.Context) (err error) {
return s.dao.Ping(ctx)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}
func (s *Service) loadPanels() (map[int8][]*model.PanelPriceConfig, error) {
pcs, _, err := s.dao.PriceConfigsByStatus(context.TODO(), 0)
if err != nil {
log.Error("s.dao.PriceConfigsByStatus err(%v)", err)
return nil, err
}
ppcs := make([]*model.PanelPriceConfig, 0, len(pcs))
for _, pc := range pcs {
ppc := new(model.PanelPriceConfig)
ppc.CopyFromPriceConfig(pc)
ppcs = append(ppcs, ppc)
}
spcs, _, err := s.dao.SaledPriceConfigsByStatus(context.TODO(), 0)
if err != nil {
log.Error("s.dao.SaledPriceConfigsByStatus err(%v)", err)
return nil, err
}
sppcs := make([]*model.PanelPriceConfig, 0, len(spcs))
for _, spc := range spcs {
sppc := new(model.PanelPriceConfig)
sppc.CopyFromPriceConfig(spc)
sppcs = append(sppcs, sppc)
}
ppcsMap := make(map[int8][]*model.PanelPriceConfig)
for _, sppc := range sppcs {
if _, ok := ppcsMap[sppc.SuitType]; !ok {
ppcsMap[sppc.SuitType] = make([]*model.PanelPriceConfig, 0)
}
included := false
for _, ppc := range ppcs {
if sppc.Pid == ppc.ID {
included = true
break
}
}
if included {
ppcsMap[sppc.SuitType] = append(ppcsMap[sppc.SuitType], sppc)
}
}
for _, ppc := range ppcs {
if _, ok := ppcsMap[ppc.SuitType]; !ok {
ppcsMap[ppc.SuitType] = make([]*model.PanelPriceConfig, 0)
}
included := false
for _, sppc := range sppcs {
if ppc.ID == sppc.Pid && ppc.SuitType == sppc.SuitType {
included = true
sppc.OriginPrice = ppc.Price
break
}
}
if !included {
ppcsMap[ppc.SuitType] = append(ppcsMap[ppc.SuitType], ppc)
}
}
for st := range ppcsMap {
sort.Slice(ppcsMap[st], func(l, r int) bool {
lp := ppcsMap[st][l]
rp := ppcsMap[st][r]
if lp.SubType > rp.SubType {
return true
}
if lp.SubType < rp.SubType {
return false
}
return lp.Month > rp.Month
})
}
return ppcsMap, nil
}
func (s *Service) initPanels() {
var (
ppcsMap map[int8][]*model.PanelPriceConfig
err error
)
if ppcsMap, err = s.loadPanels(); err != nil {
log.Error("s.initPanels() err(%+v)", err)
panic(err)
}
if ppcsMap == nil {
panic(nil)
}
s.ppcsMap.Store(ppcsMap)
}
// pcproc auto load price configs periodically
func (s *Service) ppcproc() {
defer func() {
if err := recover(); err != nil {
log.Error("s.pcproc err(%v) ", err)
go s.ppcproc()
log.Info("s.pcproc recover")
}
}()
log.Info("s.ppcproc start")
var (
td time.Duration
err error
)
if td, err = time.ParseDuration(s.c.Ticker.PanelRefreshDuration); err != nil {
panic(err)
}
ticker := time.NewTicker(td)
for range ticker.C {
ppcsMap, err := s.loadPanels()
if err != nil {
log.Info("s.loadPanels() err(%+v)", err)
continue
}
log.Info("s.ppcproc updatePcsMap(%+v)", ppcsMap)
s.ppcsMap.Store(ppcsMap)
}
}
func (s *Service) eventproc() {
defer func() {
if x := recover(); x != nil {
log.Error("service.eventproc panic(%v)", x)
go s.eventproc()
log.Info("service.eventproc recover")
}
}()
for {
f := <-s.eventch
f()
}
}
func (s *Service) mission(f func()) {
defer func() {
if x := recover(); x != nil {
log.Error("service.mission panic(%v)", x)
}
}()
select {
case s.eventch <- f:
default:
log.Error("service.missproc chan full")
}
}
// make up tv-vip order status
func (s *Service) makeupproc() {
var (
td time.Duration
err error
)
if td, err = time.ParseDuration(s.c.Ticker.UnpaidRefreshDuratuion); err != nil {
panic(err)
}
ticker := time.NewTicker(td)
defer func() {
if err := recover(); err != nil {
log.Error("service.makeUpOrderStatus panic(%v)", err)
go s.makeupproc()
log.Info("service.makeUpOrderStatus recover")
}
}()
log.Info("TV Vip Order Status Make Up Event Start!")
for range ticker.C {
log.Info("TV Vip Order Status Making Up Event!")
if err := s.MakeUpOrderStatus(); err != nil {
log.Error("s.MakeUpOrderStatus() err(%+v)", err)
}
}
}

View File

@@ -0,0 +1,34 @@
package service
import (
"flag"
"go-common/app/service/main/tv/internal/conf"
"os"
"testing"
)
var (
s *Service
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "")
flag.Set("conf_token", "")
flag.Set("tree_id", "")
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/test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
s = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,22 @@
package service
import (
"context"
"go-common/app/service/main/tv/internal/model"
"go-common/library/log"
)
func (s *Service) TokenInfos(c context.Context, tokens []string) (tis []*model.TokenInfo, err error) {
payParams, err := s.dao.CachePayParamsByTokens(c, tokens)
if err != nil {
log.Error("s.dao.CachePayParamsByTokens(%v) err(%v)", tokens, err)
return
}
tis = make([]*model.TokenInfo, 0, len(tokens))
for tn, pp := range payParams {
token := &model.TokenInfo{Token: tn}
token.CopyFromPayParam(pp)
tis = append(tis, token)
}
return
}

View File

@@ -0,0 +1,26 @@
package service
import (
"context"
"go-common/app/service/main/tv/internal/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceTokenInfos(t *testing.T) {
convey.Convey("TokenInfos", t, func(ctx convey.C) {
var (
c = context.Background()
tokens = []string{"TOKEN:34567345678"}
)
s.dao.AddCachePayParam(context.TODO(), tokens[0], &model.PayParam{})
ctx.Convey("When everything gose positive", func(ctx convey.C) {
tis, err := s.TokenInfos(c, tokens)
ctx.Convey("Then err should be nil.tis should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(tis, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,179 @@
package service
import (
"context"
"time"
"go-common/app/service/main/tv/internal/model"
"go-common/app/service/main/tv/internal/service/validator"
xsql "go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
func (s *Service) UserInfo(c context.Context, mid int64) (ui *model.UserInfo, err error) {
ui, err = s.dao.UserInfoByMid(c, int64(mid))
if err != nil {
return nil, err
}
if ui.IsEmpty() {
return nil, ecode.NothingFound
}
if ui.IsExpired() {
ui.MarkExpired()
s.ExpireUserAsync(c, ui.Mid)
}
return ui, nil
}
func (s *Service) YstUserInfo(c context.Context, req *model.YstUserInfoReq) (ui *model.UserInfo, err error) {
v := &validator.SignerValidator{
Sign: req.Sign,
Signer: s.dao.Signer(),
Val: req,
}
if err = v.Validate(); err != nil {
log.Error("signValidator.Validate(%+v) err(%+v)", req, err)
return
}
ui, err = s.dao.UserInfoByMid(c, int64(req.Mid))
if err != nil {
return nil, err
}
if ui.IsEmpty() {
return nil, ecode.NothingFound
}
if ui.IsExpired() {
ui.MarkExpired()
s.ExpireUserAsync(c, ui.Mid)
}
return ui, nil
}
func (s *Service) ExpireUserAsync(c context.Context, mid int64) {
s.mission(func() {
if err := s.ExpireUser(context.TODO(), mid); err != nil {
log.Error("s.ExpireUser(%d) err(%+v)", mid, err)
}
log.Info("s.ExpireUserAsync(%d) msg(success)", mid)
})
}
func (s *Service) ExpireUser(c context.Context, mid int64) (err error) {
var (
ui *model.UserInfo
tx *xsql.Tx
)
// check overdue time
if ui, err = s.dao.RawUserInfoByMid(c, mid); err != nil {
return
}
if !ui.IsExpired() {
return nil
}
if tx, err = s.dao.BeginTran(c); err != nil {
return
}
defer func() {
err = s.dao.EndTran(tx, err)
}()
ui.MarkExpired()
if err = s.dao.TxUpdateUserInfo(c, tx, ui); err != nil {
return
}
s.flushUserInfoAsync(context.TODO(), mid)
return nil
}
func (s *Service) incrVipDuration(c context.Context, tx *xsql.Tx, ui *model.UserInfo, d time.Duration) (err error) {
// update user status
ui.Status = model.VipStatusActive
// update user overdue time
now := time.Now()
nowUnix := time.Now().Unix()
ui.RecentPayTime = xtime.Time(nowUnix)
loc, _ := time.LoadLocation("Asia/Shanghai")
startDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc).Add(time.Hour * 24)
if int64(ui.OverdueTime) <= nowUnix {
ui.OverdueTime = xtime.Time(startDate.Unix() + int64(d.Seconds()))
} else {
ui.OverdueTime = xtime.Time(int64(ui.OverdueTime) + int64(d.Seconds()))
}
if err = s.dao.TxUpdateUserInfo(c, tx, ui); err != nil {
return
}
log.Info("s.incrVipDuration(%+v, %+v)", ui, d)
return nil
}
func (s *Service) RenewPanel(c context.Context, mid int64) (p *model.PanelPriceConfig, err error) {
uc, err := s.dao.UserContractByMid(c, mid)
if err != nil {
return
}
po, err := s.dao.PayOrderByOrderNo(c, uc.OrderNo)
if err != nil {
return
}
p, err = s.PanelPriceConfigByProductId(c, po.ProductId)
if err != nil {
return
}
if p == nil {
return nil, ecode.TVIPPanelNotFound
}
return
}
func (s *Service) RenewVip(c context.Context, mid int64) (err error) {
ui, err := s.dao.RawUserInfoByMid(c, mid)
if err != nil {
return
}
// validate
validator := &validator.RenewVipValidator{
UserInfo: ui,
FromDuration: s.c.PAY.RenewFromDuration,
ToDuration: s.c.PAY.RenewToDuration,
}
if err = validator.Validate(); err != nil {
log.Error("renew.Validate() err(%v)", err)
return
}
// get renew panel
rp, err := s.RenewPanel(c, mid)
if err != nil {
return
}
// add pay order
payOrder := &model.PayOrder{
Platform: model.PlatformSystem,
Mid: mid,
Status: 1,
PaymentType: ui.PayChannelId,
Ver: 1,
OrderType: model.PayOrderTypeSub,
}
var tx *xsql.Tx
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("s.dao.BeginTran() err(%v)", err)
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
payOrder.CopyFromPanel(rp)
if _, err = s.dao.TxInsertPayOrder(c, tx, payOrder); err != nil {
return
}
s.mission(func() {
// 允许云视听失败job查询兜底
s.dao.RenewYstOrder(c, nil)
})
return
}

View File

@@ -0,0 +1,128 @@
package service
import (
"context"
"go-common/app/service/main/tv/internal/model"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceUserInfo(t *testing.T) {
convey.Convey("UserInfo", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ui, err := s.UserInfo(c, mid)
ctx.Convey("Then err should be nil.ui should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ui, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceYstUserInfo(t *testing.T) {
convey.Convey("YstUserInfo", t, func(ctx convey.C) {
var (
c = context.Background()
req = &model.YstUserInfoReq{
Mid: 27515308,
}
)
req.Sign, _ = s.dao.Signer().Sign(req)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ui, err := s.YstUserInfo(c, req)
ctx.Convey("Then err should be nil.ui should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ui, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceExpireUserAsync(t *testing.T) {
convey.Convey("ExpireUserAsync", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
s.ExpireUserAsync(c, mid)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
})
}
func TestServiceExpireUser(t *testing.T) {
convey.Convey("ExpireUser", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(27515308)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := s.ExpireUser(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestServiceincrVipDuration(t *testing.T) {
convey.Convey("incrVipDuration", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = s.dao.BeginTran(c)
ui = &model.UserInfo{
Mid: 27515308,
}
d = time.Hour * 24 * 31
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := s.incrVipDuration(c, tx, ui, d)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
//
//func TestServiceRenewPanel(t *testing.T) {
// convey.Convey("RenewPanel", t, func(ctx convey.C) {
// var (
// c = context.Background()
// mid = int64(27515308)
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// p, err := s.RenewPanel(c, mid)
// ctx.Convey("Then err should be nil.p should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(p, convey.ShouldNotBeNil)
// })
// })
// })
//}
//func TestServiceRenewVip(t *testing.T) {
// convey.Convey("RenewVip", t, func(ctx convey.C) {
// var (
// c = context.Background()
// mid = int64(0)
// )
// ctx.Convey("When everything gose positive", func(ctx convey.C) {
// err := s.RenewVip(c, mid)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
//}

View File

@@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"pay.go",
"sign.go",
],
importpath = "go-common/app/service/main/tv/internal/service/validator",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/tv/internal/model:go_default_library",
"//app/service/main/tv/internal/pkg:go_default_library",
"//library/ecode: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,214 @@
package validator
import (
"go-common/app/service/main/tv/internal/model"
"go-common/app/service/main/tv/internal/pkg"
"go-common/library/ecode"
"go-common/library/log"
"time"
)
type PayParamValidator struct {
PayParam *model.PayParam
}
func (pv *PayParamValidator) Validate() error {
if pv.PayParam == nil {
return ecode.TVIPTokenErr
}
if pv.PayParam.IsExpired() {
return ecode.TVIPTokenExpire
}
return nil
}
type PanelValidator struct {
Panel *model.PanelPriceConfig
MVip *model.MainVip
BuyNum int32
Ui *model.UserInfo
}
func (pv *PanelValidator) isSuitable() bool {
switch pv.Panel.SuitType {
case model.SuitTypeAll:
return true
case model.SuitTypeMvip:
// NOTE: tv 会员无法通过主站会员升级方式购买
if pv.Ui != nil && pv.Ui.IsVip() {
return false
}
return pv.MVip.IsVip() && pv.MVip.Months() >= 1
default:
log.Error("pv.isSuitable err(%s)", "SuiteTypeUnknown")
return false
}
}
func (pv *PanelValidator) isBuyNumExceeded() bool {
if pv.Panel.SubType != model.SuitTypeMvip {
return false
}
return pv.BuyNum > pv.MVip.Months()
}
func (pv *PanelValidator) Validate() error {
if pv.Panel == nil {
return ecode.TVIPPanelNotFound
}
if !pv.isSuitable() {
return ecode.TVIPPanelNotSuitalbe
}
if pv.isBuyNumExceeded() {
return ecode.TVIPBuyNumExceeded
}
return nil
}
type CreateOrderValidator struct {
PayOrder *model.PayOrder
PaymentType string
PayParam *model.PayParam
Panel *model.PanelPriceConfig
MVip *model.MainVip
Ui *model.UserInfo
PayingOrders []*model.PayOrder
MVipPanels []*model.PanelPriceConfig
MVipRateMaxNumber int
}
func (cv *CreateOrderValidator) isAliContract() bool {
return cv.Panel.SubType == model.SubTypeContract && cv.PaymentType == model.PaymentTypeAliPay
}
func (cv *CreateOrderValidator) isDupOrderNo() bool {
return cv.PayOrder != nil && cv.PayOrder.OrderNo == cv.PayParam.OrderNo
}
func (cv *CreateOrderValidator) isRateExceed() bool {
return len(cv.PayingOrders) >= cv.MVipRateMaxNumber
}
func (cv *CreateOrderValidator) isMVipExceed() bool {
if cv.Panel.SuitType != model.SuitTypeMvip {
return false
}
for _, order := range cv.PayingOrders {
productId := order.ProductId
for _, panel := range cv.MVipPanels {
if panel.ProductId == productId {
return true
}
}
}
return false
}
func (cv *CreateOrderValidator) Validate() error {
if cv.isRateExceed() {
return ecode.TVIPBuyRateExceeded
}
if cv.isMVipExceed() {
return ecode.TVIPMVipRateExceeded
}
if cv.isAliContract() {
return ecode.TVIPPanelNotSuitalbe
}
if cv.isDupOrderNo() {
return ecode.TVIPDupOrderNo
}
paramValidator := &PayParamValidator{PayParam: cv.PayParam}
if err := paramValidator.Validate(); err != nil {
return err
}
panelValidator := &PanelValidator{Panel: cv.Panel, MVip: cv.MVip, Ui: cv.Ui}
return panelValidator.Validate()
}
type RenewVipValidator struct {
UserInfo *model.UserInfo
FromDuration string
ToDuration string
}
func (rv *RenewVipValidator) isTooEarly() bool {
d, err := time.ParseDuration(rv.FromDuration)
if err != nil {
log.Error("rv.ParseDuration(%s) err(%v)", rv.FromDuration, err)
d = time.Hour * 24
}
return int64(rv.UserInfo.OverdueTime)-time.Now().Unix() > int64(d.Seconds())
}
func (rv *RenewVipValidator) isTooLate() bool {
d, err := time.ParseDuration(rv.ToDuration)
if err != nil {
log.Error("rv.ParseDuration(%s) err(%v)", rv.ToDuration, err)
d = time.Hour * 24
}
return time.Now().Unix()-int64(rv.UserInfo.OverdueTime) > int64(d.Seconds())
}
func (rv *RenewVipValidator) isContracted() bool {
return rv.UserInfo.IsContracted()
}
func (rv *RenewVipValidator) Validate() error {
if rv.isTooEarly() {
return ecode.TVIPRenewTooEarly
}
if rv.isTooLate() {
return ecode.TVIPRenewTooLate
}
if !rv.isContracted() {
return ecode.TVIPNotContracted
}
return nil
}
type PayCallbackValidator struct {
Signer *pkg.Signer
CallbackReq *model.YstPayCallbackReq
PayOrder *model.PayOrder
ExpireDuration string
}
func (pv *PayCallbackValidator) isSignValid() bool {
sign, err := pv.Signer.Sign(pv.CallbackReq)
if err != nil {
return false
}
return sign == pv.CallbackReq.Sign
}
func (pv *PayCallbackValidator) hasPayingOrder() bool {
if pv.PayOrder == nil {
return false
}
if pv.PayOrder.Status != model.PayOrderStatusPaying {
return false
}
return true
}
func (pv *PayCallbackValidator) isOrderExpired() bool {
return false
//d, err := time.ParseDuration(pv.ExpireDuration)
//if err != nil {
// d = time.Hour * 2
//}
//return time.Now().Unix()-int64(pv.PayOrder.Ctime) > int64(d.Seconds())
}
func (pv *PayCallbackValidator) Validate() error {
if !pv.isSignValid() {
return ecode.TVIPSignErr
}
if !pv.hasPayingOrder() {
return ecode.TVIPPayOrderNotFound
}
if pv.isOrderExpired() {
return ecode.TVIPPayOrderExpired
}
return nil
}

View File

@@ -0,0 +1,23 @@
package validator
import (
"go-common/app/service/main/tv/internal/pkg"
"go-common/library/ecode"
)
type SignerValidator struct {
Signer *pkg.Signer
Sign string
Val interface{}
}
func (sv *SignerValidator) Validate() error {
sign, err := sv.Signer.Sign(sv.Val)
if err != nil {
return err
}
if sign != sv.Sign {
return ecode.TVIPSignErr
}
return nil
}