Create & Init Project...

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

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

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

View File

@@ -0,0 +1,13 @@
# v1.2.0
1. 增加充电用户设置同步
# v1.1.0
1. 增加充电榜单同步
# v1.0.0
1. daily_bill 结算
2. monthly_bill 结算
3. recharge_shell 结算
4. binlog 订阅&清理缓存
5. 订单退款结算支持

View File

@@ -0,0 +1,8 @@
# Owner
zhaogangtao
# Author
muyang
# Reviewer
zhaogangtao

View File

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

View File

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

View File

@@ -0,0 +1 @@
# HTTP API文档

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["test.toml"],
importpath = "go-common/app/job/main/ugcpay/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/ugcpay/conf:go_default_library",
"//app/job/main/ugcpay/server/http:go_default_library",
"//app/job/main/ugcpay/service:go_default_library",
"//library/ecode/tip: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,44 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/job/main/ugcpay/conf"
"go-common/app/job/main/ugcpay/server/http"
"go-common/app/job/main/ugcpay/service"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
log.Info("ugcpay-job start")
ecode.Init(conf.Conf.Ecode)
svc := service.New(conf.Conf)
http.Init(svc)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
svc.Close()
log.Info("ugcpay-job exit")
time.Sleep(time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,91 @@
[mysql]
addr = "127.0.0.1:3306"
dsn = "test:test@tcp(127.0.0.1:3306)/bilibili_ugcpay?timeout=200ms&readTimeout=200ms&writeTimeout=200ms&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 20
idle = 10
idleTimeout ="4h"
queryTimeout = "100ms"
execTimeout = "100ms"
tranTimeout = "200ms"
[memcache]
name = "ugcpay"
proto = "tcp"
addr = "172.22.33.137:11217"
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
active = 50
idle = 10
idleTimeout = "10s"
[memcacheRank]
name = "ugcpay-rank"
proto = "tcp"
addr = "172.22.33.137:11220"
active = 50
idle = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "24h"
[redis]
name = "ugcpay"
proto = "tcp"
addr = "172.22.33.137:6836"
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
active = 50
idle = 10
idleTimeout = "10s"
[BinlogMQ]
key = "4ba46ba31f9a44ef"
secret = "e4c5a7fce28695209e6b4f0af8cf91c5"
group = "UgcpayBinlog-MainAccount-S"
topic = "UgcpayBinlog-T"
action = "sub"
buffer = 2048
name = "ugcpay-job/databus"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 2
active = 10
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[Biz]
RunCASTimes = 3
AccountUserMin = -20000
[Biz.Cron]
TaskDailyBill = "@every 1h"
TaskAccountUser = "@every 1h"
TaskAccountBiz = "@every 1h"
TaskMonthlyBill = "@every 1h"
TaskRechargeShell = "@every 1h"
[Biz.Tax]
AssetRate = 0.05
[Biz.Pay]
ID = "10017"
Token = "310f84dcc88ec8a0a9247f3b75b93614"
CheckOrderURL = "http://pay-mng.bilibili.co/reco/bills/queryByTxIds"
CheckRefundOrderURL = "http://pay-mng.bilibili.co/reco/refundbills/queryByTxIds"
RechargeShellURL = "http://pay.bilibili.co/bk-int/brokerage/rechargeBrokerage"
RechargeCallbackURL = "http://api.bilibili.co/x/internal/ugcpay/trade/pay/recharge/callback"
OrderQueryURL = "http://pay.bilibili.com/payplatform/pay/query"
[Biz.Task]
DailyBillPrefix = "TaskBillDaily"
DailyBillOffset = -1
AccountUserPrefix = "TaskAccountUser"
AccountBizPrefix = "TaskAccountBiz"
MonthBillPrefix = "TaskBillMonthly"
MonthBillOffset = -1
RechargeShellPrefix = "TaskRechargeShell"
RechargeShellOffset = -1
[CacheTTL]
TaskTTL = 72000

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/job/main/ugcpay/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/queue/databus: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,134 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/sql"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/rpc/warden"
"go-common/library/queue/databus"
"github.com/BurntSushi/toml"
)
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config .
type Config struct {
Log *log.Config
BM *bm.ServerConfig
Redis *redis.Config
Memcache *memcache.Config
MemcacheRank *memcache.Config
MySQL *sql.Config
MySQLRank *sql.Config
MySQLRankOld *sql.Config
GRPCUGCPayRank *warden.ClientConfig
Ecode *ecode.Config
BinlogMQ *databus.Config
ElecBinlogMQ *databus.Config
CacheTTL *CacheTTL
Biz *Biz
}
// Biz .
type Biz struct {
RunCASTimes int64
AccountUserMin int64
Cron *struct {
TaskDailyBill string
TaskAccountUser string
TaskAccountBiz string
TaskMonthlyBill string
TaskRechargeShell string
}
Tax *struct {
AssetRate float64
}
Pay struct {
ID string
Token string
CheckOrderURL string
CheckRefundOrderURL string
RechargeShellURL string
RechargeCallbackURL string
OrderQueryURL string
}
Task *struct {
DailyBillPrefix string
DailyBillOffset int
AccountUserPrefix string
AccountBizPrefix string
MonthBillPrefix string
MonthBillOffset int
RechargeShellPrefix string
RechargeShellOffset int
}
}
// CacheTTL .
type CacheTTL struct {
ElecOrderIDTTL int32
}
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,71 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"elec_mc.go",
"elec_mysql.go",
"grpc.go",
"http.go",
"mc.go",
"mysql.go",
"redis.go",
],
importpath = "go-common/app/job/main/ugcpay/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/ugcpay/conf:go_default_library",
"//app/job/main/ugcpay/model:go_default_library",
"//app/service/main/ugcpay-rank/api/v1:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/stat/prom: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 = [
"dao_test.go",
"elec_mc_test.go",
"elec_mysql_test.go",
"grpc_test.go",
"http_test.go",
"mc_test.go",
"mysql_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/job/main/ugcpay/conf:go_default_library",
"//app/job/main/ugcpay/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,63 @@
package dao
import (
"context"
"go-common/app/job/main/ugcpay/conf"
ugcpay_rank "go-common/app/service/main/ugcpay-rank/api/v1"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
xsql "go-common/library/database/sql"
)
// Dao dao
type Dao struct {
c *conf.Config
mc *memcache.Pool
mcRank *memcache.Pool
redis *redis.Pool
db *xsql.DB
dbrank *xsql.DB
dbrankold *xsql.DB
ugcPayRankAPI ugcpay_rank.UGCPayRankClient
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
mc: memcache.NewPool(c.Memcache),
mcRank: memcache.NewPool(c.MemcacheRank),
redis: redis.NewPool(c.Redis),
db: xsql.NewMySQL(c.MySQL),
dbrank: xsql.NewMySQL(c.MySQLRank),
dbrankold: xsql.NewMySQL(c.MySQLRankOld),
}
var err error
if dao.ugcPayRankAPI, err = ugcpay_rank.NewClient(c.GRPCUGCPayRank); err != nil {
panic(err)
}
return
}
// Close close the resource.
func (d *Dao) Close() {
d.mc.Close()
d.redis.Close()
d.db.Close()
}
// Ping dao ping
func (d *Dao) Ping(c context.Context) error {
return nil
}
// BeginTran begin transaction.
func (d *Dao) BeginTran(c context.Context) (*xsql.Tx, error) {
return d.db.Begin(c)
}
// BeginTranRank .
func (d *Dao) BeginTranRank(c context.Context) (*xsql.Tx, error) {
return d.dbrank.Begin(c)
}

View File

@@ -0,0 +1,34 @@
package dao
import (
"flag"
"go-common/app/job/main/ugcpay/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account.ugcpay-job")
flag.Set("conf_token", "1e43538fbcc8005ebd158898b7e7f6b1")
flag.Set("tree_id", "65679")
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())
}

View File

@@ -0,0 +1,52 @@
package dao
import (
"context"
"fmt"
"go-common/app/job/main/ugcpay/conf"
"go-common/library/cache/memcache"
"go-common/library/log"
"go-common/library/stat/prom"
)
func elecOrderIDKey(orderID string) string {
return fmt.Sprintf("ur_eoi_%s", orderID)
}
// AddCacheOrderID Set data to mc
func (d *Dao) AddCacheOrderID(c context.Context, orderID string) (ok bool, err error) {
ok = true
conn := d.mc.Get(c)
defer conn.Close()
key := elecOrderIDKey(orderID)
item := &memcache.Item{Key: key, Object: struct{}{}, Expiration: conf.Conf.CacheTTL.ElecOrderIDTTL, Flags: memcache.FlagJSON}
if err = conn.Add(item); err != nil {
if err == memcache.ErrNotStored {
ok = false
err = nil
return
}
prom.BusinessErrCount.Incr("mc:AddCacheOrderID")
log.Errorv(c, log.KV("AddCacheOrderID", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// DelCacheOrderID .
func (d *Dao) DelCacheOrderID(c context.Context, orderID string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := elecOrderIDKey(orderID)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:DelCacheOrderID")
log.Errorv(c, log.KV("DelCacheOrderID", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}

View File

@@ -0,0 +1,39 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoAddCacheOrderID(t *testing.T) {
convey.Convey("AddCacheOrderID", t, func(ctx convey.C) {
var (
c = context.Background()
orderID = "233"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ok, err := d.AddCacheOrderID(c, orderID)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoDelCacheOrderID(t *testing.T) {
convey.Convey("DelCacheOrderID", t, func(ctx convey.C) {
var (
c = context.Background()
orderID = "233"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.DelCacheOrderID(c, orderID)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,135 @@
package dao
import (
"context"
"math"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
)
const (
_oldElecOrderList = `SELECT id,mid,pay_mid,order_no,elec_num,status,ctime,mtime FROM elec_pay_order WHERE id>? ORDER BY ID ASC LIMIT ?`
_oldElecMessageList = `SELECT id,mid,ref_mid,IFNULL(ref_id,0),message,av_no,date_version,type,state,ctime,mtime FROM elec_message WHERE id>? ORDER BY ID ASC LIMIT ?`
_upsertElecMessage = "INSERT INTO elec_message (id,`ver`,avid,up_mid,pay_mid,message,replied,hidden,ctime,mtime) VALUES (?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE `ver`=?,avid=?,up_mid=?,pay_mid=?,message=?,replied=?,hidden=?"
_upsertElecReply = "INSERT INTO elec_reply (id,message_id,reply,hidden,ctime,mtime) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE message_id=?,reply=?,hidden=?"
_upsertElecAVRank = "INSERT INTO rank_elec_av (ver,avid,up_mid,pay_mid,pay_amount,hidden) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE pay_amount = pay_amount + ?"
_upsertElecUPRank = "INSERT INTO rank_elec_up (ver,up_mid,pay_mid,pay_amount,hidden) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE pay_amount = pay_amount + ?"
_selectOldElecTradeInfo = "SELECT id,order_no,IFNULL(av_no,0) FROM elec_pay_trade_info WHERE order_no=? LIMIT 1"
)
// UpsertElecMessage .
func (d *Dao) UpsertElecMessage(ctx context.Context, data *model.DBElecMessage) (err error) {
_, err = d.dbrank.Exec(ctx, _upsertElecMessage, data.ID, data.Ver, data.AVID, data.UPMID, data.PayMID, data.Message, data.Replied, data.Hidden, data.CTime, data.MTime, data.Ver, data.AVID, data.UPMID, data.PayMID, data.Message, data.Replied, data.Hidden)
return
}
// UpsertElecReply .
func (d *Dao) UpsertElecReply(ctx context.Context, data *model.DBElecReply) (err error) {
_, err = d.dbrank.Exec(ctx, _upsertElecReply, data.ID, data.MSGID, data.Reply, data.Hidden, data.CTime, data.MTime, data.MSGID, data.Reply, data.Hidden)
return
}
// TXUpsertElecAVRank .
func (d *Dao) TXUpsertElecAVRank(ctx context.Context, tx *xsql.Tx, ver int64, avID int64, upMID, payMID int64, deltaPayAmount int64, hidden bool) (err error) {
_, err = tx.Exec(_upsertElecAVRank, ver, avID, upMID, payMID, deltaPayAmount, hidden, deltaPayAmount)
return
}
// TXUpsertElecUPRank .
func (d *Dao) TXUpsertElecUPRank(ctx context.Context, tx *xsql.Tx, ver int64, upMID, payMID int64, deltaPayAmount int64, hidden bool) (err error) {
_, err = tx.Exec(_upsertElecUPRank, ver, upMID, payMID, deltaPayAmount, hidden, deltaPayAmount)
return
}
// RawOldElecTradeInfo .
func (d *Dao) RawOldElecTradeInfo(ctx context.Context, orderID string) (data *model.DBOldElecPayTradeInfo, err error) {
row := d.dbrankold.QueryRow(ctx, _selectOldElecTradeInfo, orderID)
data = &model.DBOldElecPayTradeInfo{}
if err = row.Scan(&data.ID, &data.OrderID, &data.AVID); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// OldElecMessageList .
func (d *Dao) OldElecMessageList(ctx context.Context, startID int64, limit int) (maxID int64, list []*model.DBOldElecMessage, err error) {
var (
rows *xsql.Rows
)
if rows, err = d.dbrankold.Query(ctx, _oldElecMessageList, startID, limit); err != nil {
return
}
defer rows.Close()
for rows.Next() {
var (
p = &model.DBOldElecMessage{}
)
if err = rows.Scan(&p.ID, &p.MID, &p.RefMID, &p.RefID, &p.Message, &p.AVID, &p.DateVer, &p.Type, &p.State, &p.CTime, &p.MTime); err != nil {
return
}
if maxID < p.ID {
maxID = p.ID
}
list = append(list, p)
}
if err = rows.Err(); err != nil {
return
}
return
}
// OldElecOrderList .
func (d *Dao) OldElecOrderList(ctx context.Context, startID int64, limit int) (maxID int64, list []*model.DBOldElecPayOrder, err error) {
var (
rows *xsql.Rows
)
if rows, err = d.dbrankold.Query(ctx, _oldElecOrderList, startID, limit); err != nil {
return
}
defer rows.Close()
for rows.Next() {
var (
p = &model.DBOldElecPayOrder{}
)
if err = rows.Scan(&p.ID, &p.UPMID, &p.PayMID, &p.OrderID, &p.ElecNum, &p.Status, &p.CTime, &p.MTime); err != nil {
return
}
if maxID < p.ID {
maxID = p.ID
}
list = append(list, p)
}
if err = rows.Err(); err != nil {
return
}
return
}
const (
_elecAddSetting = `INSERT INTO elec_user_setting (mid,value) VALUES (?,?) ON DUPLICATE KEY UPDATE value=value|?`
_elecDeleteSetting = `INSERT INTO elec_user_setting (mid,value) VALUES (?,?) ON DUPLICATE KEY UPDATE value=value&?`
)
// ElecAddSetting .
func (d *Dao) ElecAddSetting(ctx context.Context, defaultValue int32, mid int64, bitValue int32) (err error) {
defaultValue |= bitValue
_, err = d.dbrank.Exec(ctx, _elecAddSetting, mid, defaultValue, bitValue)
return
}
// ElecDeleteSetting .
func (d *Dao) ElecDeleteSetting(ctx context.Context, defaultValue int32, mid int64, bitValue int32) (err error) {
andValue := math.MaxInt32 ^ bitValue
defaultValue &= andValue
_, err = d.dbrank.Exec(ctx, _elecDeleteSetting, mid, defaultValue, andValue)
return
}

View File

@@ -0,0 +1,183 @@
package dao
import (
"context"
"math"
"testing"
"go-common/app/job/main/ugcpay/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoRawOldElecTradeInfo(t *testing.T) {
convey.Convey("RawOldElecTradeInfo", t, func(ctx convey.C) {
var (
c = context.Background()
orderID = "2016081812152789113137"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
data, err := d.RawOldElecTradeInfo(c, orderID)
ctx.Convey("Then err should be nil.data should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(data, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTXUpsertElecUPRank(t *testing.T) {
convey.Convey("TXUpsertElecUPRank", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTranRank(c)
ver = int64(0)
upMID = int64(233)
payMID = int64(322)
deltaPayAmount = int64(0)
hidden bool
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.TXUpsertElecUPRank(c, tx, ver, upMID, payMID, deltaPayAmount, hidden)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
func TestDaoTXUpsertElecAVRank(t *testing.T) {
convey.Convey("TXUpsertElecAVRank", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTranRank(c)
ver = int64(0)
avID = int64(233)
upMID = int64(233)
payMID = int64(322)
deltaPayAmount = int64(0)
hidden bool
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.TXUpsertElecAVRank(c, tx, ver, avID, upMID, payMID, deltaPayAmount, hidden)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
func TestDaoUpsertElecMessage(t *testing.T) {
convey.Convey("UpsertElecMessage", t, func(ctx convey.C) {
var (
c = context.Background()
data = &model.DBElecMessage{
ID: 233333,
AVID: 233,
UPMID: 233,
PayMID: 322,
Message: "ut",
Replied: true,
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.UpsertElecMessage(c, data)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoUpsertElecReply(t *testing.T) {
convey.Convey("UpsertElecReply", t, func(ctx convey.C) {
var (
c = context.Background()
data = &model.DBElecReply{
ID: 233334,
MSGID: 233333,
Reply: "ut_reply",
Hidden: false,
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.UpsertElecReply(c, data)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoOldElecMessageList(t *testing.T) {
convey.Convey("OldElecMessageList", t, func(ctx convey.C) {
var (
c = context.Background()
startID = int64(0)
limit = int(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
maxID, list, err := d.OldElecMessageList(c, startID, limit)
ctx.Convey("Then err should be nil.maxID,list should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(list, convey.ShouldNotBeNil)
ctx.So(maxID, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoOldElecOrderList(t *testing.T) {
convey.Convey("OldElecOrderList", t, func(ctx convey.C) {
var (
c = context.Background()
startID = int64(0)
limit = int(10)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
maxID, list, err := d.OldElecOrderList(c, startID, limit)
ctx.Convey("Then err should be nil.maxID,list should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(list, convey.ShouldNotBeNil)
ctx.So(maxID, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoElecAddSetting(t *testing.T) {
convey.Convey("ElecAddSetting", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
orValue = int32(0x01)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.ElecAddSetting(c, math.MaxInt32, mid, orValue)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoElecDeleteSetting(t *testing.T) {
convey.Convey("ElecDeleteSetting", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
andValue = int32(0x7ffffffd)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.ElecDeleteSetting(c, math.MaxInt32, mid, andValue)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,38 @@
package dao
import (
"context"
ugcpay_rank "go-common/app/service/main/ugcpay-rank/api/v1"
)
// RankElecUpdateOrder .
func (d *Dao) RankElecUpdateOrder(ctx context.Context, avID, upMID, payMID, ver, fee int64) (err error) {
var (
req = &ugcpay_rank.RankElecUpdateOrderReq{
AVID: avID,
UPMID: upMID,
PayMID: payMID,
Ver: ver,
Fee: fee,
}
)
_, err = d.ugcPayRankAPI.RankElecUpdateOrder(ctx, req)
return
}
// RankElecUpdateMessage .
func (d *Dao) RankElecUpdateMessage(ctx context.Context, avID, upMID, payMID, ver int64, message string, hidden bool) (err error) {
var (
req = &ugcpay_rank.RankElecUpdateMessageReq{
AVID: avID,
UPMID: upMID,
PayMID: payMID,
Ver: ver,
Message: message,
Hidden: hidden,
}
)
_, err = d.ugcPayRankAPI.RankElecUpdateMessage(ctx, req)
return
}

View File

@@ -0,0 +1,47 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoRankElecUpdateOrder(t *testing.T) {
convey.Convey("RankElecUpdateOrder", t, func(ctx convey.C) {
var (
c = context.Background()
avID = int64(0)
upMID = int64(0)
payMID = int64(0)
ver = int64(0)
fee = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.RankElecUpdateOrder(c, avID, upMID, payMID, ver, fee)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoRankElecUpdateMessage(t *testing.T) {
convey.Convey("RankElecUpdateMessage", t, func(ctx convey.C) {
var (
c = context.Background()
avID = int64(0)
upMID = int64(0)
payMID = int64(0)
ver = int64(0)
message = ""
hidden bool
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.RankElecUpdateMessage(c, avID, upMID, payMID, ver, message, hidden)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,124 @@
package dao
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"go-common/app/job/main/ugcpay/conf"
"go-common/app/job/main/ugcpay/model"
"go-common/library/ecode"
"go-common/library/log"
"github.com/pkg/errors"
)
// PayRechargeShell .
func (d *Dao) PayRechargeShell(c context.Context, dataJSON string) (err error) {
resp := new(struct {
Code int `json:"errno"`
Msg string `json:"msg"`
})
if err = d.paySend(c, conf.Conf.Biz.Pay.RechargeShellURL, dataJSON, resp); err != nil {
err = errors.WithStack(err)
return
}
if resp.Code != ecode.OK.Code() {
err = ecode.Int(resp.Code)
}
return
}
// PayCheckRefundOrder return map[txID]*model.PayCheckOrder
func (d *Dao) PayCheckRefundOrder(c context.Context, dataJSON string) (orders map[string][]*model.PayCheckRefundOrderEle, err error) {
resp := new(struct {
Code int `json:"code"`
Msg string `json:"message"`
Data []*model.PayCheckRefundOrder `json:"data"`
})
if err = d.paySend(c, conf.Conf.Biz.Pay.CheckRefundOrderURL, dataJSON, resp); err != nil {
err = errors.WithStack(err)
return
}
if resp.Code != ecode.OK.Code() {
err = ecode.Int(resp.Code)
return
}
if resp.Data == nil {
err = errors.Errorf("PayCheckRefundOrder got nil data, url: %s, body: %s, resp: %+v", conf.Conf.Biz.Pay.CheckOrderURL, dataJSON, resp)
return
}
orders = make(map[string][]*model.PayCheckRefundOrderEle)
for _, o := range resp.Data {
orders[o.TXID] = append(orders[o.TXID], o.Elements...)
}
return
}
// PayCheckOrder return map[txID]*model.PayCheckOrder
func (d *Dao) PayCheckOrder(c context.Context, dataJSON string) (orders map[string]*model.PayCheckOrder, err error) {
resp := new(struct {
Code int `json:"code"`
Msg string `json:"message"`
Data []*model.PayCheckOrder `json:"data"`
})
if err = d.paySend(c, conf.Conf.Biz.Pay.CheckOrderURL, dataJSON, resp); err != nil {
err = errors.WithStack(err)
return
}
if resp.Code != ecode.OK.Code() {
err = ecode.Int(resp.Code)
return
}
if resp.Data == nil {
err = errors.Errorf("PayCheckOrder got nil data, url: %s, body: %s, resp: %+v", conf.Conf.Biz.Pay.CheckOrderURL, dataJSON, resp)
return
}
orders = make(map[string]*model.PayCheckOrder)
for _, o := range resp.Data {
orders[o.TxID] = o
}
return
}
func (d *Dao) paySend(c context.Context, url string, jsonData string, respData interface{}) (err error) {
var (
req *http.Request
client = new(http.Client)
resp *http.Response
bs []byte
)
log.Info("paySend call url: %s, body: %s", url, jsonData)
defer func() {
log.Info("paySend call url: %v, body: %s, resp: %+v, err: %+v", url, jsonData, respData, err)
}()
if req, err = http.NewRequest(http.MethodPost, url, strings.NewReader(jsonData)); err != nil {
err = errors.WithStack(err)
return
}
req.Header.Add("Content-Type", "application/json")
if resp, err = client.Do(req); err != nil {
err = errors.Wrapf(err, "call url: %s, body: %s", url, jsonData)
return
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
err = errors.Errorf("d.paySend incorrect http status: %d, host: %s, url: %s", resp.StatusCode, req.URL.Host, req.URL.String())
return
}
if bs, err = ioutil.ReadAll(resp.Body); err != nil {
err = errors.Wrapf(err, "d.paySend ioutil.ReadAll")
return
}
if err = json.Unmarshal(bs, respData); err != nil {
err = errors.WithStack(err)
return
}
return
}

View File

@@ -0,0 +1,72 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPayRechargeShell(t *testing.T) {
convey.Convey("PayRechargeShell", t, func(ctx convey.C) {
var (
c = context.Background()
dataJSON = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.PayRechargeShell(c, dataJSON)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoPayCheckRefundOrder(t *testing.T) {
convey.Convey("PayCheckRefundOrder", t, func(ctx convey.C) {
var (
c = context.Background()
dataJSON = `{"customerId":"10017","sign":"072dfe62a270c7c44f619244962737a6","signType":"MD5","txIds":"3059753508505497600"}`
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
_, err := d.PayCheckRefundOrder(c, dataJSON)
ctx.Convey("Then err should be nil.orders should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
// ctx.So(orders, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoPayCheckOrder(t *testing.T) {
convey.Convey("PayCheckOrder", t, func(ctx convey.C) {
var (
c = context.Background()
dataJSON = `{"customerId":"10017","sign":"072dfe62a270c7c44f619244962737a6","signType":"MD5","txIds":"3059753508505497600"}`
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
_, err := d.PayCheckOrder(c, dataJSON)
ctx.Convey("Then err should be nil.orders should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
// ctx.So(orders, convey.ShouldNotBeNil)
})
})
})
}
func TestDaopaySend(t *testing.T) {
convey.Convey("paySend", t, func(ctx convey.C) {
var (
c = context.Background()
url = ""
jsonData = ""
respData = interface{}(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.paySend(c, url, jsonData, respData)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,125 @@
package dao
import (
"context"
"fmt"
"go-common/library/cache/memcache"
"go-common/library/log"
"go-common/library/stat/prom"
"github.com/pkg/errors"
)
func orderKey(id string) string {
return fmt.Sprintf("up_o_%s", id)
}
func assetKey(oid int64, otype string, currency string) string {
return fmt.Sprintf("up_a_%d_%s_%s", oid, otype, currency)
}
func taskKey(task string) string {
return fmt.Sprintf("up_t_%s", task)
}
// DelCacheOrderUser delete data from mc
func (d *Dao) DelCacheOrderUser(c context.Context, orderID string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := orderKey(orderID)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:DelCacheOrderUser")
log.Errorv(c, log.KV("DelCacheOrderUser", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// DelCacheAsset delete data from mc
func (d *Dao) DelCacheAsset(c context.Context, oid int64, otype string, currency string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := assetKey(oid, otype, currency)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:DelCacheAsset")
log.Errorv(c, log.KV("DelCacheAsset", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheTask .
func (d *Dao) AddCacheTask(c context.Context, task string, ttl int32) (ok bool, err error) {
var (
conn = d.mc.Get(c)
item = &memcache.Item{
Key: taskKey(task),
Value: []byte{0},
Expiration: ttl,
}
)
defer conn.Close()
if err = conn.Add(item); err != nil {
if err == memcache.ErrNotStored {
err = nil
ok = false
return
}
err = errors.WithStack(err)
return
}
ok = true
return
}
// DelCacheTask .
func (d *Dao) DelCacheTask(c context.Context, task string) (err error) {
var (
conn = d.mc.Get(c)
key = taskKey(task)
)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
err = errors.WithStack(err)
}
}
return
}
// ugcpay-rank
func elecUserSetting(mid int64) string {
return fmt.Sprintf("eus_%d", mid)
}
// DelCacheUserSetting .
func (d *Dao) DelCacheUserSetting(c context.Context, mid int64) (err error) {
var (
conn = d.mcRank.Get(c)
key = elecUserSetting(mid)
)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
err = errors.WithStack(err)
}
}
return
}

View File

@@ -0,0 +1,130 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoorderKey(t *testing.T) {
convey.Convey("orderKey", t, func(ctx convey.C) {
var (
id = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := orderKey(id)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoassetKey(t *testing.T) {
convey.Convey("assetKey", t, func(ctx convey.C) {
var (
oid = int64(0)
otype = ""
currency = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := assetKey(oid, otype, currency)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaotaskKey(t *testing.T) {
convey.Convey("taskKey", t, func(ctx convey.C) {
var (
task = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := taskKey(task)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoDelCacheOrderUser(t *testing.T) {
convey.Convey("DelCacheOrderUser", t, func(ctx convey.C) {
var (
c = context.Background()
id = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.DelCacheOrderUser(c, id)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelCacheAsset(t *testing.T) {
convey.Convey("DelCacheAsset", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
otype = ""
currency = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.DelCacheAsset(c, oid, otype, currency)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoAddCacheTask(t *testing.T) {
convey.Convey("AddCacheTask", t, func(ctx convey.C) {
var (
c = context.Background()
task = ""
ttl = int32(60)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ok, err := d.AddCacheTask(c, task, ttl)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoDelCacheTask(t *testing.T) {
convey.Convey("DelCacheTask", t, func(ctx convey.C) {
var (
c = context.Background()
task = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.DelCacheTask(c, task)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelCacheUserSetting(t *testing.T) {
convey.Convey("DelCacheUserSetting", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.DelCacheUserSetting(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,912 @@
package dao
import (
"context"
"math"
"time"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
)
const (
_asset = `SELECT id,mid,oid,otype,currency,price,state,ctime,mtime FROM asset WHERE oid=? AND otype=? AND currency=? LIMIT 1`
_countPaidOrderUser = "SELECT count(1) FROM order_user WHERE pay_time BETWEEN ? AND ? AND state='paid' LIMIT 1"
_countRefundedOrderUser = "SELECT count(1) FROM order_user WHERE refund_time BETWEEN ? AND ? AND state='st_refunded' LIMIT 1"
_sumPaidOrderUserRealFee = "SELECT IFNULL(sum(real_fee),0) FROM order_user WHERE pay_time BETWEEN ? AND ? AND state='settled' LIMIT 1"
_sumRefundedOrderUserRealFee = "SELECT IFNULL(sum(real_fee),0) FROM order_user WHERE refund_time BETWEEN ? AND ? AND state='ref_finished' LIMIT 1"
_minIDOrderPaid = `SELECT id FROM order_user WHERE pay_time>=? AND state='paid' ORDER BY id ASC LIMIT 1`
_minIDOrderRefunded = `SELECT id FROM order_user WHERE refund_time>=? AND state='st_refunded' ORDER BY id ASC LIMIT 1`
_orderPaidList = `SELECT id,mid,order_id,biz,platform,oid,otype,fee,real_fee,currency,pay_id,pay_reason,pay_time,state,ctime,mtime,refund_time,version FROM order_user WHERE pay_time BETWEEN ? AND ? AND state='paid' AND id>? ORDER BY ID ASC LIMIT ?`
_orderRefundedList = `SELECT id,mid,order_id,biz,platform,oid,otype,fee,real_fee,currency,pay_id,pay_reason,pay_time,state,ctime,mtime,refund_time,version FROM order_user WHERE refund_time BETWEEN ? AND ? AND state='st_refunded' AND id>? ORDER BY ID ASC LIMIT ?`
_updateOrder = `UPDATE order_user SET mid=?,order_id=?,biz=?,platform=?,oid=?,otype=?,fee=?,real_fee=?,currency=?,pay_id=?,pay_reason=?,pay_time=?,state=?,refund_time=?,version=version+1 WHERE id=? AND version=?`
_insertLogOrderUser = "INSERT INTO log_order_user (order_id,from_state,to_state,`desc`) VALUES (?,?,?,?)"
_orderBadDebt = `SELECT id,order_id,type,state,ctime,mtime FROM order_bad_debt WHERE order_id=? ORDER BY id ASC LIMIT 1`
_insertOrderBadDebt = `INSERT INTO order_bad_debt (order_id,type,state) VALUES (?,?,?)`
_updateOrderBadDebt = `UPDATE order_bad_debt SET order_id=?,type=?,state=? WHERE order_id=?`
_countDailyBillByVer = "SELECT count(1) FROM bill_user_daily WHERE ver=? LIMIT 1"
_countDailyBillByMonthVer = "SELECT count(1) FROM bill_user_daily WHERE month_ver=? LIMIT 1"
_sumDailyBill = "SELECT IFNULL(sum(`in`),0),IFNULL(sum(`out`),0) FROM bill_user_daily WHERE ver=? LIMIT 1"
_minIDDailyBillByMonthVer = `SELECT id FROM bill_user_daily WHERE month_ver=? ORDER BY id ASC LIMIT 1`
_dailyBillListByMonthVer = "SELECT id,bill_id,mid,biz,currency,`in`,`out`,ver,month_ver,ctime,mtime,version FROM bill_user_daily WHERE month_ver=? AND id>? ORDER BY ID ASC LIMIT ?"
_minIDDailyBillByVer = `SELECT id FROM bill_user_daily WHERE ver=? ORDER BY id ASC LIMIT 1`
_dailyBillListByVer = "SELECT id,bill_id,mid,biz,currency,`in`,`out`,ver,month_ver,ctime,mtime,version FROM bill_user_daily WHERE ver=? AND id>? ORDER BY ID ASC LIMIT ?"
_dailyBill = "SELECT id,bill_id,mid,biz,currency,`in`,`out`,ver,month_ver,ctime,mtime,version FROM bill_user_daily WHERE mid=? AND biz=? AND currency=? AND ver=? LIMIT 1"
_insertDailyBill = "INSERT INTO bill_user_daily (bill_id,mid,biz,currency,`in`,`out`,ver,month_ver,version) VALUES (?,?,?,?,?,?,?,?,?)"
_updateDailyBill = "UPDATE bill_user_daily SET bill_id=?,mid=?,biz=?,currency=?,`in`=?,`out`=?,ver=?,month_ver=?,version=version+1 WHERE mid=? AND biz=? AND currency=? AND ver=? AND version=?"
_insertDailyBillLog = "INSERT INTO log_bill_user_daily (bill_id,from_in,to_in,from_out,to_out,order_id) VALUES (?,?,?,?,?,?)"
_countMonthlyBillByVer = "SELECT count(1) FROM bill_user_monthly WHERE ver=? LIMIT 1"
_minIDMonthlyBill = `SELECT id FROM bill_user_monthly WHERE ver=? ORDER BY id ASC LIMIT 1`
_monthlyBillList = "SELECT id,bill_id,mid,biz,currency,`in`,`out`,ver,ctime,mtime,version FROM bill_user_monthly WHERE ver=? AND id>? ORDER BY ID ASC LIMIT ?"
_monthlyBill = "SELECT id,bill_id,mid,biz,currency,`in`,`out`,ver,ctime,mtime,version FROM bill_user_monthly WHERE mid=? AND biz=? AND currency=? AND ver=? LIMIT 1"
_insertMonthlyBill = "INSERT INTO bill_user_monthly (bill_id,mid,biz,currency,`in`,`out`,ver,version) VALUES (?,?,?,?,?,?,?,?)"
_updateMonthlyBill = "UPDATE bill_user_monthly SET bill_id=?,mid=?,biz=?,currency=?,`in`=?,`out`=?,ver=?,version=version+1 WHERE mid=? AND biz=? AND currency=? AND ver=? AND version=?"
_insertMonthlyBillLog = "INSERT INTO log_bill_user_monthly (bill_id,from_in,to_in,from_out,to_out,bill_user_daily_id) VALUES (?,?,?,?,?,?)"
_minIDUserAccount = `SELECT id FROM account_user WHERE mtime>=? ORDER BY id ASC LIMIT 1`
_userAccountList = "SELECT id,mid,biz,currency,balance,ver,state,ctime,mtime FROM account_user WHERE mtime BETWEEN ? AND ? AND id>? ORDER BY ID ASC LIMIT ?"
_userAccount = `SELECT id,mid,biz,currency,balance,ver,state,ctime,mtime FROM account_user WHERE mid=? AND biz=? AND currency=? LIMIT 1`
_insertUserAccount = `INSERT INTO account_user (mid,biz,currency,balance,ver,state) VALUES (?,?,?,?,?,?)`
_updateUserAccount = `UPDATE account_user SET mid=?,biz=?,currency=?,balance=?,ver=ver+1,state=? WHERE mid=? AND biz=? AND currency=? AND ver=?`
_insertUserAccountLog = "INSERT INTO log_account_user (account_id,`from`,`to`,ver,state,name) VALUES (?,?,?,?,?,?)"
_bizAccount = `SELECT id,biz,currency,balance,ver,state,ctime,mtime FROM account_biz WHERE biz=? AND currency=? LIMIT 1`
_insertBizAccount = `INSERT INTO account_biz (biz,currency,balance,ver,state) VALUES (?,?,?,?,?)`
_updateBizAccount = `UPDATE account_biz SET biz=?,currency=?,balance=?,ver=ver+1,state=? WHERE biz=? AND currency=? AND ver=?`
_insertBizAccountLog = "INSERT INTO log_account_biz (account_id,`from`,`to`,ver,state,name) VALUES (?,?,?,?,?,?)"
_aggrIncomeUser = "SELECT id,mid,currency,pay_success,pay_error,total_in,total_out,ctime,mtime FROM aggr_income_user WHERE mid=? AND currency=? LIMIT 1"
_insertAggrIncomeUser = "INSERT INTO aggr_income_user (mid,currency,pay_success,pay_error,total_in,total_out) VALUES (?,?,?,?,?,?)"
_updateAggrIncomeUser = "UPDATE aggr_income_user SET mid=?,currency=?,pay_success=?,pay_error=?,total_in=?,total_out=? WHERE mid=? AND currency=?"
_aggrIncomeUserAsset = "SELECT id,mid,currency,ver,oid,otype,pay_success,pay_error,total_in,total_out,ctime,mtime FROM aggr_income_user_asset WHERE mid=? AND currency=? AND ver=? AND oid=? AND otype=? LIMIT 1"
_insertAggrIncomeUserAsset = "INSERT INTO aggr_income_user_asset (mid,currency,`ver`,oid,otype,pay_success,pay_error,total_in,total_out) VALUES (?,?,?,?,?,?,?,?,?)"
_updateAggrIncomeUserAsset = "UPDATE aggr_income_user_asset SET mid=?,currency=?,ver=?,oid=?,otype=?,pay_success=?,pay_error=?,total_in=?,total_out=? WHERE mid=? AND currency=? AND ver=? AND oid=? AND otype=?"
_insertOrderRechargeShell = "INSERT INTO order_recharge_shell (mid,order_id,biz,amount,pay_msg,state,`ver`) VALUES (?,?,?,?,?,?,?)"
_insertOrderRechargeShellLog = "INSERT INTO log_order_recharge_shell (order_id,from_state,to_state,`desc`,bill_user_monthly_id) VALUES (?,?,?,?,?)"
_logTask = "SELECT id,name,expect,success,failure,state,ctime,mtime FROM log_task WHERE name=? LIMIT 1"
_insertLogTask = "INSERT INTO log_task (name,expect,success,failure,state) VALUES (?,?,?,?,?)"
_logTaskSuccessIncr = "UPDATE log_task SET success=success+1 WHERE name=?"
_logTaskFailureIncr = "UPDATE log_task SET failure=failure+1 WHERE name=?"
)
// CountPaidOrderUser .
func (d *Dao) CountPaidOrderUser(ctx context.Context, beginTime, endTime time.Time) (count int64, err error) {
row := d.db.QueryRow(ctx, _countPaidOrderUser, beginTime, endTime)
if err = row.Scan(&count); err != nil {
if err == xsql.ErrNoRows {
err = nil
count = 0
}
return
}
return
}
// CountRefundedOrderUser .
func (d *Dao) CountRefundedOrderUser(ctx context.Context, beginTime, endTime time.Time) (count int64, err error) {
row := d.db.QueryRow(ctx, _countRefundedOrderUser, beginTime, endTime)
if err = row.Scan(&count); err != nil {
if err == xsql.ErrNoRows {
err = nil
count = 0
}
return
}
return
}
// CountDailyBillByVer .
func (d *Dao) CountDailyBillByVer(ctx context.Context, ver int64) (count int64, err error) {
row := d.db.QueryRow(ctx, _countDailyBillByVer, ver)
if err = row.Scan(&count); err != nil {
if err == xsql.ErrNoRows {
err = nil
count = 0
}
return
}
return
}
// CountDailyBillByMonthVer .
func (d *Dao) CountDailyBillByMonthVer(ctx context.Context, monthVer int64) (count int64, err error) {
row := d.db.QueryRow(ctx, _countDailyBillByMonthVer, monthVer)
if err = row.Scan(&count); err != nil {
if err == xsql.ErrNoRows {
err = nil
count = 0
}
return
}
return
}
// CountMonthlyBillByVer .
func (d *Dao) CountMonthlyBillByVer(ctx context.Context, ver int64) (count int64, err error) {
row := d.db.QueryRow(ctx, _countMonthlyBillByVer, ver)
if err = row.Scan(&count); err != nil {
if err == xsql.ErrNoRows {
err = nil
count = 0
}
return
}
return
}
// LogTask .
func (d *Dao) LogTask(ctx context.Context, name string) (data *model.LogTask, err error) {
row := d.db.QueryRow(ctx, _logTask, name)
data = &model.LogTask{}
if err = row.Scan(&data.ID, &data.Name, &data.Expect, &data.Success, &data.Failure, &data.State, &data.CTime, &data.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// InsertLogTask .
func (d *Dao) InsertLogTask(ctx context.Context, data *model.LogTask) (id int64, err error) {
result, err := d.db.Exec(ctx, _insertLogTask, data.Name, data.Expect, data.Success, data.Failure, data.State)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXIncrLogTaskSuccess .
func (d *Dao) TXIncrLogTaskSuccess(ctx context.Context, tx *xsql.Tx, name string) (rows int64, err error) {
result, err := tx.Exec(_logTaskSuccessIncr, name)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// IncrLogTaskFailure .
func (d *Dao) IncrLogTaskFailure(ctx context.Context, name string) (rows int64, err error) {
result, err := d.db.Exec(ctx, _logTaskFailureIncr, name)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// Asset .
func (d *Dao) Asset(ctx context.Context, oid int64, otype string, currency string) (data *model.Asset, err error) {
row := d.db.QueryRow(ctx, _asset, oid, otype, currency)
data = &model.Asset{}
if err = row.Scan(&data.ID, &data.MID, &data.OID, &data.OType, &data.Currency, &data.Price, &data.State, &data.CTime, &data.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// SumPaidOrderUserRealFee .
func (d *Dao) SumPaidOrderUserRealFee(ctx context.Context, beginTime, endTime time.Time) (sum int64, err error) {
row := d.db.QueryRow(ctx, _sumPaidOrderUserRealFee, beginTime, endTime)
if err = row.Scan(&sum); err != nil {
if err == xsql.ErrNoRows {
err = nil
sum = 0
}
return
}
return
}
// SumRefundedOrderUserRealFee .
func (d *Dao) SumRefundedOrderUserRealFee(ctx context.Context, beginTime, endTime time.Time) (sum int64, err error) {
row := d.db.QueryRow(ctx, _sumRefundedOrderUserRealFee, beginTime, endTime)
if err = row.Scan(&sum); err != nil {
if err == xsql.ErrNoRows {
err = nil
sum = 0
}
return
}
return
}
// SumDailyBill .
func (d *Dao) SumDailyBill(ctx context.Context, ver int64) (sumIn int64, sumOut int64, err error) {
row := d.db.QueryRow(ctx, _sumDailyBill, ver)
if err = row.Scan(&sumIn, &sumOut); err != nil {
if err == xsql.ErrNoRows {
err = nil
sumIn = 0
sumOut = 0
}
return
}
return
}
// MinIDOrderPaid .
func (d *Dao) MinIDOrderPaid(ctx context.Context, beginTime time.Time) (minID int64, err error) {
row := d.db.QueryRow(ctx, _minIDOrderPaid, beginTime)
if err = row.Scan(&minID); err != nil {
if err == xsql.ErrNoRows {
err = nil
minID = math.MaxInt32
}
return
}
minID--
return
}
// OrderPaidList order list
func (d *Dao) OrderPaidList(ctx context.Context, beginTime time.Time, endTime time.Time, fromID int64, limit int) (maxID int64, data []*model.Order, err error) {
rows, err := d.db.Query(ctx, _orderPaidList, beginTime, endTime, fromID, limit)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
d := &model.Order{}
if err = rows.Scan(&d.ID, &d.MID, &d.OrderID, &d.Biz, &d.Platform, &d.OID, &d.OType, &d.Fee, &d.RealFee, &d.Currency, &d.PayID, &d.PayReason, &d.PayTime, &d.State, &d.CTime, &d.MTime, &d.RefundTime, &d.Version); err != nil {
return
}
if d.ID > maxID {
maxID = d.ID
}
data = append(data, d)
}
err = rows.Err()
return
}
// MinIDOrderRefunded .
func (d *Dao) MinIDOrderRefunded(ctx context.Context, beginTime time.Time) (minID int64, err error) {
row := d.db.QueryRow(ctx, _minIDOrderRefunded, beginTime)
if err = row.Scan(&minID); err != nil {
if err == xsql.ErrNoRows {
err = nil
minID = math.MaxInt32
}
return
}
minID--
return
}
// OrderRefundedList order list
func (d *Dao) OrderRefundedList(ctx context.Context, beginTime time.Time, endTime time.Time, fromID int64, limit int) (maxID int64, data []*model.Order, err error) {
rows, err := d.db.Query(ctx, _orderRefundedList, beginTime, endTime, fromID, limit)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
d := &model.Order{}
if err = rows.Scan(&d.ID, &d.MID, &d.OrderID, &d.Biz, &d.Platform, &d.OID, &d.OType, &d.Fee, &d.RealFee, &d.Currency, &d.PayID, &d.PayReason, &d.PayTime, &d.State, &d.CTime, &d.MTime, &d.RefundTime, &d.Version); err != nil {
return
}
if d.ID > maxID {
maxID = d.ID
}
data = append(data, d)
}
err = rows.Err()
return
}
// TXUpdateOrder .
func (d *Dao) TXUpdateOrder(ctx context.Context, tx *xsql.Tx, order *model.Order) (rows int64, err error) {
result, err := tx.Exec(_updateOrder, order.MID, order.OrderID, order.Biz, order.Platform, order.OID, order.OType, order.Fee, order.RealFee, order.Currency, order.PayID, order.PayReason, order.PayTime, order.State, order.RefundTime, order.ID, order.Version)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXInsertOrderUserLog .
func (d *Dao) TXInsertOrderUserLog(ctx context.Context, tx *xsql.Tx, data *model.LogOrder) (id int64, err error) {
result, err := tx.Exec(_insertLogOrderUser, data.OrderID, data.FromState, data.ToState, data.Desc)
if err != nil {
return
}
if id, err = result.LastInsertId(); err != nil {
return
}
return
}
// MinIDDailyBillByVer .
func (d *Dao) MinIDDailyBillByVer(ctx context.Context, ver int64) (minID int64, err error) {
row := d.db.QueryRow(ctx, _minIDDailyBillByVer, ver)
if err = row.Scan(&minID); err != nil {
if err == xsql.ErrNoRows {
err = nil
minID = math.MaxInt32
}
return
}
minID--
return
}
// DailyBillListByVer bill list
func (d *Dao) DailyBillListByVer(ctx context.Context, ver int64, fromID int64, limit int) (maxID int64, data []*model.DailyBill, err error) {
rows, err := d.db.Query(ctx, _dailyBillListByVer, ver, fromID, limit)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
d := &model.DailyBill{}
if err = rows.Scan(&d.ID, &d.BillID, &d.MID, &d.Biz, &d.Currency, &d.In, &d.Out, &d.Ver, &d.MonthVer, &d.CTime, &d.MTime, &d.Version); err != nil {
return
}
if d.ID > maxID {
maxID = d.ID
}
data = append(data, d)
}
err = rows.Err()
return
}
// MinIDDailyBillByMonthVer .
func (d *Dao) MinIDDailyBillByMonthVer(ctx context.Context, monthVer int64) (minID int64, err error) {
row := d.db.QueryRow(ctx, _minIDDailyBillByMonthVer, monthVer)
if err = row.Scan(&minID); err != nil {
if err == xsql.ErrNoRows {
err = nil
minID = math.MaxInt32
}
return
}
minID--
return
}
// DailyBillListByMonthVer bill list
func (d *Dao) DailyBillListByMonthVer(ctx context.Context, monthVer int64, fromID int64, limit int) (maxID int64, data []*model.DailyBill, err error) {
rows, err := d.db.Query(ctx, _dailyBillListByMonthVer, monthVer, fromID, limit)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
d := &model.DailyBill{}
if err = rows.Scan(&d.ID, &d.BillID, &d.MID, &d.Biz, &d.Currency, &d.In, &d.Out, &d.Ver, &d.MonthVer, &d.CTime, &d.MTime, &d.Version); err != nil {
return
}
if d.ID > maxID {
maxID = d.ID
}
data = append(data, d)
}
err = rows.Err()
return
}
// TXInsertLogDailyBill .
func (d *Dao) TXInsertLogDailyBill(ctx context.Context, tx *xsql.Tx, log *model.LogBillDaily) (id int64, err error) {
result, err := tx.Exec(_insertDailyBillLog, log.BillID, log.FromIn, log.ToIn, log.FromOut, log.ToOut, log.OrderID)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXInsertLogMonthlyBill .
func (d *Dao) TXInsertLogMonthlyBill(ctx context.Context, tx *xsql.Tx, log *model.LogBillMonthly) (id int64, err error) {
result, err := tx.Exec(_insertMonthlyBillLog, log.BillID, log.FromIn, log.ToIn, log.FromOut, log.ToOut, log.BillUserDailyID)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// MinIDMonthlyBill .
func (d *Dao) MinIDMonthlyBill(ctx context.Context, ver int64) (minID int64, err error) {
row := d.db.QueryRow(ctx, _minIDMonthlyBill, ver)
if err = row.Scan(&minID); err != nil {
if err == xsql.ErrNoRows {
err = nil
minID = math.MaxInt32
}
return
}
minID--
return
}
// MonthlyBillList bill list
func (d *Dao) MonthlyBillList(ctx context.Context, ver int64, fromID int64, limit int) (maxID int64, data []*model.Bill, err error) {
rows, err := d.db.Query(ctx, _monthlyBillList, ver, fromID, limit)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
d := &model.Bill{}
if err = rows.Scan(&d.ID, &d.BillID, &d.MID, &d.Biz, &d.Currency, &d.In, &d.Out, &d.Ver, &d.CTime, &d.MTime, &d.Version); err != nil {
return
}
if d.ID > maxID {
maxID = d.ID
}
data = append(data, d)
}
err = rows.Err()
return
}
// DailyBill .
func (d *Dao) DailyBill(ctx context.Context, mid int64, biz string, currency string, ver int64) (data *model.DailyBill, err error) {
row := d.db.QueryRow(ctx, _dailyBill, mid, biz, currency, ver)
data = &model.DailyBill{}
if err = row.Scan(&data.ID, &data.BillID, &data.MID, &data.Biz, &data.Currency, &data.In, &data.Out, &data.Ver, &data.MonthVer, &data.CTime, &data.MTime, &data.Version); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// InsertDailyBill .
func (d *Dao) InsertDailyBill(ctx context.Context, bill *model.DailyBill) (id int64, err error) {
result, err := d.db.Exec(ctx, _insertDailyBill, bill.BillID, bill.MID, bill.Biz, bill.Currency, bill.In, bill.Out, bill.Ver, bill.MonthVer, bill.Version)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXUpdateDailyBill .
func (d *Dao) TXUpdateDailyBill(ctx context.Context, tx *xsql.Tx, bill *model.DailyBill) (rows int64, err error) {
result, err := tx.Exec(_updateDailyBill, bill.BillID, bill.MID, bill.Biz, bill.Currency, bill.In, bill.Out, bill.Ver, bill.MonthVer, bill.MID, bill.Biz, bill.Currency, bill.Ver, bill.Version)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
const (
_upsertDeltaDailyBill = "INSERT INTO bill_user_daily (bill_id,mid,biz,currency,`in`,`out`,ver,month_ver) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE `in`=`in`+?,`out`=`out`+?"
_updateDeltaDailyBill = "UPDATE bill_user_daily SET `in`=`in`+?,`out`=`out`+? WHERE mid=? AND biz=? AND currency=? AND ver=?"
)
// TXUpsertDeltaDailyBill .
func (d *Dao) TXUpsertDeltaDailyBill(ctx context.Context, tx *xsql.Tx, bill *model.DailyBill, deltaIn, deltaOut int64) (rows int64, err error) {
result, err := tx.Exec(_upsertDeltaDailyBill, bill.BillID, bill.MID, bill.Biz, bill.Currency, deltaIn, deltaOut, bill.Ver, bill.MonthVer, deltaIn, deltaOut)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXUpdateDeltaDailyBill .
func (d *Dao) TXUpdateDeltaDailyBill(ctx context.Context, tx *xsql.Tx, deltaIn, deltaOut int64, mid int64, biz string, currency string, ver int64) (rows int64, err error) {
result, err := tx.Exec(_updateDeltaDailyBill, deltaIn, deltaOut, mid, biz, currency, ver)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// MonthlyBill .
func (d *Dao) MonthlyBill(ctx context.Context, mid int64, biz string, currency string, ver int64) (data *model.Bill, err error) {
row := d.db.QueryRow(ctx, _monthlyBill, mid, biz, currency, ver)
data = &model.Bill{}
if err = row.Scan(&data.ID, &data.BillID, &data.MID, &data.Biz, &data.Currency, &data.In, &data.Out, &data.Ver, &data.CTime, &data.MTime, &data.Version); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// InsertMonthlyBill .
func (d *Dao) InsertMonthlyBill(ctx context.Context, bill *model.Bill) (id int64, err error) {
result, err := d.db.Exec(ctx, _insertMonthlyBill, bill.BillID, bill.MID, bill.Biz, bill.Currency, bill.In, bill.Out, bill.Ver, bill.Version)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXUpdateMonthlyBill .
func (d *Dao) TXUpdateMonthlyBill(ctx context.Context, tx *xsql.Tx, bill *model.Bill) (rows int64, err error) {
result, err := tx.Exec(_updateMonthlyBill, bill.BillID, bill.MID, bill.Biz, bill.Currency, bill.In, bill.Out, bill.Ver, bill.MID, bill.Biz, bill.Currency, bill.Ver, bill.Version)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
const (
_upsertDeltaMonthlyBill = "INSERT INTO bill_user_monthly (bill_id,mid,biz,currency,`in`,`out`,ver) VALUES (?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE `in`=`in`+?,`out`=`out`+?"
_updateDeltaMonthlyBill = "UPDATE bill_user_monthly SET `in`=`in`+?,`out`=`out`+? WHERE mid=? AND biz=? AND currency=? AND ver=?"
)
// TXUpsertDeltaMonthlyBill .
func (d *Dao) TXUpsertDeltaMonthlyBill(ctx context.Context, tx *xsql.Tx, bill *model.Bill, deltaIn, deltaOut int64) (rows int64, err error) {
result, err := tx.Exec(_upsertDeltaMonthlyBill, bill.BillID, bill.MID, bill.Biz, bill.Currency, deltaIn, deltaOut, bill.Ver, deltaIn, deltaOut)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXUpdateDeltaMonthlyBill .
func (d *Dao) TXUpdateDeltaMonthlyBill(ctx context.Context, tx *xsql.Tx, deltaIn, deltaOut int64, mid int64, biz string, currency string, ver int64) (rows int64, err error) {
result, err := tx.Exec(_updateDeltaMonthlyBill, deltaIn, deltaOut, mid, biz, currency, ver)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// MinIDUserAccount .
func (d *Dao) MinIDUserAccount(ctx context.Context, beginTime time.Time) (minID int64, err error) {
row := d.db.QueryRow(ctx, _minIDUserAccount, beginTime)
if err = row.Scan(&minID); err != nil {
if err == xsql.ErrNoRows {
err = nil
minID = math.MaxInt32
}
return
}
minID--
return
}
// UserAccountList bill list
func (d *Dao) UserAccountList(ctx context.Context, beginTime time.Time, endTime time.Time, fromID int64, limit int) (maxID int64, datas []*model.UserAccount, err error) {
rows, err := d.db.Query(ctx, _userAccountList, beginTime, endTime, fromID, limit)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
data := &model.UserAccount{}
if err = rows.Scan(&data.ID, &data.MID, &data.Biz, &data.Currency, &data.Balance, &data.Ver, &data.State, &data.CTime, &data.MTime); err != nil {
return
}
if data.ID > maxID {
maxID = data.ID
}
datas = append(datas, data)
}
err = rows.Err()
return
}
// UserAccount .
func (d *Dao) UserAccount(ctx context.Context, mid int64, biz string, currency string) (data *model.UserAccount, err error) {
row := d.db.QueryRow(ctx, _userAccount, mid, biz, currency)
data = &model.UserAccount{}
if err = row.Scan(&data.ID, &data.MID, &data.Biz, &data.Currency, &data.Balance, &data.Ver, &data.State, &data.CTime, &data.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// InsertUserAccount .
func (d *Dao) InsertUserAccount(ctx context.Context, account *model.UserAccount) (id int64, err error) {
result, err := d.db.Exec(ctx, _insertUserAccount, account.MID, account.Biz, account.Currency, account.Balance, account.Ver, account.State)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXUpdateUserAccount .
func (d *Dao) TXUpdateUserAccount(ctx context.Context, tx *xsql.Tx, account *model.UserAccount) (rows int64, err error) {
result, err := tx.Exec(_updateUserAccount, account.MID, account.Biz, account.Currency, account.Balance, account.State, account.MID, account.Biz, account.Currency, account.Ver)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
const (
_upsertDeltaUserAccount = "INSERT INTO account_user (mid,biz,currency,balance,ver,state) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE balance=balance+?,ver=ver+1"
_updateDeltaUserAccount = `UPDATE account_user SET balance=balance+?,ver=ver+1 WHERE mid=? AND biz=? AND currency=?`
)
// TXUpsertDeltaUserAccount .
func (d *Dao) TXUpsertDeltaUserAccount(ctx context.Context, tx *xsql.Tx, account *model.UserAccount, deltaBalance int64) (rows int64, err error) {
result, err := tx.Exec(_upsertDeltaUserAccount, account.MID, account.Biz, account.Currency, deltaBalance, account.Ver, account.State, deltaBalance)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXUpdateDeltaUserAccount .
func (d *Dao) TXUpdateDeltaUserAccount(ctx context.Context, tx *xsql.Tx, deltaBalance int64, mid int64, biz string, currency string) (rows int64, err error) {
result, err := tx.Exec(_updateDeltaUserAccount, deltaBalance, mid, biz, currency)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXInsertUserAccountLog .
func (d *Dao) TXInsertUserAccountLog(ctx context.Context, tx *xsql.Tx, accountLog *model.AccountLog) (err error) {
_, err = tx.Exec(_insertUserAccountLog, accountLog.AccountID, accountLog.From, accountLog.To, accountLog.Ver, accountLog.State, accountLog.Name)
return
}
// BizAccount .
func (d *Dao) BizAccount(ctx context.Context, biz string, currency string) (data *model.BizAccount, err error) {
row := d.db.QueryRow(ctx, _bizAccount, biz, currency)
data = &model.BizAccount{}
if err = row.Scan(&data.ID, &data.Biz, &data.Currency, &data.Balance, &data.Ver, &data.State, &data.CTime, &data.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// InsertBizAccount .
func (d *Dao) InsertBizAccount(ctx context.Context, account *model.BizAccount) (id int64, err error) {
result, err := d.db.Exec(ctx, _insertBizAccount, account.Biz, account.Currency, account.Balance, account.Ver, account.State)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXUpdateBizAccount .
func (d *Dao) TXUpdateBizAccount(ctx context.Context, tx *xsql.Tx, account *model.BizAccount) (rows int64, err error) {
result, err := tx.Exec(_updateBizAccount, account.Biz, account.Currency, account.Balance, account.State, account.Biz, account.Currency, account.Ver)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
const (
_upsertDeltaBizAccount = "INSERT INTO account_biz (biz,currency,balance,ver,state) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE balance=balance+?,ver=ver+1"
_updateDeltaBizAccount = `UPDATE account_biz SET balance=balance+?,ver=ver+1 WHERE biz=? AND currency=?`
)
// TXUpsertDeltaBizAccount .
func (d *Dao) TXUpsertDeltaBizAccount(ctx context.Context, tx *xsql.Tx, account *model.BizAccount, deltaBalance int64) (rows int64, err error) {
result, err := tx.Exec(_upsertDeltaBizAccount, account.Biz, account.Currency, deltaBalance, account.Ver, account.State, deltaBalance)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXUpdateDeltaBizAccount .
func (d *Dao) TXUpdateDeltaBizAccount(ctx context.Context, tx *xsql.Tx, deltaBalance int64, biz string, currency string) (rows int64, err error) {
result, err := tx.Exec(_updateDeltaBizAccount, deltaBalance, biz, currency)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXInsertBizAccountLog .
func (d *Dao) TXInsertBizAccountLog(ctx context.Context, tx *xsql.Tx, accountLog *model.AccountLog) (err error) {
_, err = tx.Exec(_insertBizAccountLog, accountLog.AccountID, accountLog.From, accountLog.To, accountLog.Ver, accountLog.State, accountLog.Name)
return
}
// AggrIncomeUser .
func (d *Dao) AggrIncomeUser(ctx context.Context, mid int64, currency string) (data *model.AggrIncomeUser, err error) {
row := d.db.QueryRow(ctx, _aggrIncomeUser, mid, currency)
data = &model.AggrIncomeUser{}
if err = row.Scan(&data.ID, &data.MID, &data.Currency, &data.PaySuccess, &data.PayError, &data.TotalIn, &data.TotalOut, &data.CTime, &data.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// InsertAggrIncomeUser .
func (d *Dao) InsertAggrIncomeUser(ctx context.Context, aggr *model.AggrIncomeUser) (id int64, err error) {
result, err := d.db.Exec(ctx, _insertAggrIncomeUser, aggr.MID, aggr.Currency, aggr.PaySuccess, aggr.PayError, aggr.TotalIn, aggr.TotalOut)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXUpdateAggrIncomeUser .
func (d *Dao) TXUpdateAggrIncomeUser(ctx context.Context, tx *xsql.Tx, aggr *model.AggrIncomeUser) (rows int64, err error) {
result, err := tx.Exec(_updateAggrIncomeUser, aggr.MID, aggr.Currency, aggr.PaySuccess, aggr.PayError, aggr.TotalIn, aggr.TotalOut, aggr.MID, aggr.Currency)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
const (
_upsertDeltaAggrIncomeUser = "INSERT INTO aggr_income_user (mid,currency,pay_success,pay_error,total_in,total_out) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE pay_success=pay_success+?,pay_error=pay_error+?,total_in=total_in+?,total_out=total_out+?"
_updateDeltaAggrIncomeUser = "UPDATE aggr_income_user SET pay_success=pay_success+?,pay_error=pay_error+?,total_in=total_in+?,total_out=total_out+? WHERE mid=? AND currency=?"
)
// TXUpsertDeltaAggrIncomeUser .
func (d *Dao) TXUpsertDeltaAggrIncomeUser(ctx context.Context, tx *xsql.Tx, aggr *model.AggrIncomeUser, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut int64) (rows int64, err error) {
result, err := tx.Exec(_upsertDeltaAggrIncomeUser, aggr.MID, aggr.Currency, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXUpdateDeltaAggrIncomeUser .
func (d *Dao) TXUpdateDeltaAggrIncomeUser(ctx context.Context, tx *xsql.Tx, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut int64, mid int64, currency string) (rows int64, err error) {
result, err := tx.Exec(_updateDeltaAggrIncomeUser, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut, mid, currency)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// AggrIncomeUserAsset .
func (d *Dao) AggrIncomeUserAsset(ctx context.Context, mid int64, currency string, ver int64, oid int64, otype string) (data *model.AggrIncomeUserAsset, err error) {
row := d.db.QueryRow(ctx, _aggrIncomeUserAsset, mid, currency, ver, oid, otype)
data = &model.AggrIncomeUserAsset{}
if err = row.Scan(&data.ID, &data.MID, &data.Currency, &data.Ver, &data.OID, &data.OType, &data.PaySuccess, &data.PayError, &data.TotalIn, &data.TotalOut, &data.CTime, &data.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// InsertAggrIncomeUserAsset .
func (d *Dao) InsertAggrIncomeUserAsset(ctx context.Context, aggr *model.AggrIncomeUserAsset) (id int64, err error) {
result, err := d.db.Exec(ctx, _insertAggrIncomeUserAsset, aggr.MID, aggr.Currency, aggr.Ver, aggr.OID, aggr.OType, aggr.PaySuccess, aggr.PayError, aggr.TotalIn, aggr.TotalOut)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXUpdateAggrIncomeUserAsset .
func (d *Dao) TXUpdateAggrIncomeUserAsset(ctx context.Context, tx *xsql.Tx, aggr *model.AggrIncomeUserAsset) (rows int64, err error) {
result, err := tx.Exec(_updateAggrIncomeUserAsset, aggr.MID, aggr.Currency, aggr.Ver, aggr.OID, aggr.OType, aggr.PaySuccess, aggr.PayError, aggr.TotalIn, aggr.TotalOut, aggr.MID, aggr.Currency, aggr.Ver, aggr.OID, aggr.OType)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
const (
_upsertDeltaAggrIncomeUserAsset = "INSERT INTO aggr_income_user_asset (mid,currency,`ver`,oid,otype,pay_success,pay_error,total_in,total_out) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE pay_success=pay_success+?,pay_error=pay_error+?,total_in=total_in+?,total_out=total_out+?"
_updateDeltaAggrIncomeUserAsset = "UPDATE aggr_income_user_asset SET pay_success=pay_success+?,pay_error=pay_error+?,total_in=total_in+?,total_out=total_out+? WHERE mid=? AND currency=? AND ver=? AND oid=? AND otype=?"
)
// TXUpsertDeltaAggrIncomeUserAsset .
func (d *Dao) TXUpsertDeltaAggrIncomeUserAsset(ctx context.Context, tx *xsql.Tx, aggr *model.AggrIncomeUserAsset, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut int64) (rows int64, err error) {
result, err := tx.Exec(_upsertDeltaAggrIncomeUserAsset, aggr.MID, aggr.Currency, aggr.Ver, aggr.OID, aggr.OType, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXUpdateDeltaAggrIncomeUserAsset .
func (d *Dao) TXUpdateDeltaAggrIncomeUserAsset(ctx context.Context, tx *xsql.Tx, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut int64, mid int64, currency string, ver int64, oid int64, otype string) (rows int64, err error) {
result, err := tx.Exec(_updateDeltaAggrIncomeUserAsset, deltaPaySuccess, deltaPayError, deltaTotalIn, deltaTotalOut, mid, currency, ver, oid, otype)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// OrderBadDebt .
func (d *Dao) OrderBadDebt(ctx context.Context, orderID string) (data *model.OrderBadDebt, err error) {
row := d.db.QueryRow(ctx, _orderBadDebt, orderID)
data = &model.OrderBadDebt{}
if err = row.Scan(&data.ID, &data.OrderID, &data.Type, &data.State, &data.CTime, &data.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
}
return
}
return
}
// InsertOrderBadDebt .
func (d *Dao) InsertOrderBadDebt(ctx context.Context, order *model.OrderBadDebt) (id int64, err error) {
result, err := d.db.Exec(ctx, _insertOrderBadDebt, order.OrderID, order.Type, order.State)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXUpdateOrderBadDebt .
func (d *Dao) TXUpdateOrderBadDebt(ctx context.Context, tx *xsql.Tx, order *model.OrderBadDebt) (rows int64, err error) {
result, err := tx.Exec(_updateOrderBadDebt, order.OrderID, order.Type, order.State, order.OrderID)
if err != nil {
return
}
rows, err = result.RowsAffected()
return
}
// TXInsertOrderRechargeShell .
func (d *Dao) TXInsertOrderRechargeShell(ctx context.Context, tx *xsql.Tx, order *model.OrderRechargeShell) (id int64, err error) {
result, err := tx.Exec(_insertOrderRechargeShell, order.MID, order.OrderID, order.Biz, order.Amount, order.PayMSG, order.State, order.Ver)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}
// TXInsertOrderRechargeShellLog .
func (d *Dao) TXInsertOrderRechargeShellLog(ctx context.Context, tx *xsql.Tx, order *model.OrderRechargeShellLog) (id int64, err error) {
result, err := tx.Exec(_insertOrderRechargeShellLog, order.OrderID, order.FromState, order.ToState, order.Desc, order.BillUserMonthlyID)
if err != nil {
return
}
id, err = result.LastInsertId()
return
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
package dao
import (
"context"
"fmt"
"github.com/pkg/errors"
)
func assetRelationKey(mid int64) string {
return fmt.Sprintf("up_ar_%d", mid)
}
func assetRelationField(oid int64, otype string) string {
return fmt.Sprintf("%s_%d", otype, oid)
}
// DelCacheAssetRelationState delete asset relation state.
func (d *Dao) DelCacheAssetRelationState(c context.Context, oid int64, otype string, mid int64) (err error) {
var (
key = assetRelationKey(mid)
field = assetRelationField(oid, otype)
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("HDEL", key, field); err != nil {
err = errors.Wrapf(err, "conn.Do(HDEL, %s, %s)", key, field)
return
}
return
}

View File

@@ -0,0 +1,54 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoassetRelationKey(t *testing.T) {
convey.Convey("assetRelationKey", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := assetRelationKey(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoassetRelationField(t *testing.T) {
convey.Convey("assetRelationField", t, func(ctx convey.C) {
var (
oid = int64(0)
otype = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := assetRelationField(oid, otype)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoDelCacheAssetRelationState(t *testing.T) {
convey.Convey("DelCacheAssetRelationState", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
otype = ""
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.DelCacheAssetRelationState(c, oid, otype, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"db.go",
"elec_db.go",
"model.go",
],
importpath = "go-common/app/job/main/ugcpay/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//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"],
)
go_test(
name = "go_default_test",
srcs = ["model_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = ["//vendor/github.com/smartystreets/goconvey/convey:go_default_library"],
)

View File

@@ -0,0 +1,206 @@
package model
import (
"time"
)
// LogTask .
type LogTask struct {
ID int64
Name string
Expect int64
Success int64
Failure int64
State string
CTime time.Time
MTime time.Time
}
// Asset .
type Asset struct {
ID int64
MID int64
OID int64
OType string
Currency string
Price int64
State string
CTime time.Time
MTime time.Time
}
// Order .
type Order struct {
ID int64
OrderID string
MID int64
Biz string
Platform string
OID int64
OType string
Fee int64
RealFee int64
Currency string
PayID string
PayReason string
PayTime time.Time
RefundTime time.Time
State string // created 已创建, paying 支付中, paid 已支付, failed 支付失败, closed 已关闭, expired 已超时, finished 已完成(支付成功且对账成功)
Version int64 // 乐观锁,每次更新,值++
CTime time.Time
MTime time.Time
}
// LogOrder .
type LogOrder struct {
ID int64
OrderID string
FromState string
ToState string
Desc string
CTime time.Time
MTime time.Time
}
// Bill .
type Bill struct {
ID int64
BillID string
MID int64
Biz string
Currency string
In int64
Out int64
Ver int64 // 账单版本,比如 201812, 20181202
Version int64 // 乐观锁,每次更新++
CTime time.Time
MTime time.Time
}
// DailyBill .
type DailyBill struct {
Bill
MonthVer int64
}
// BizAccount .
type BizAccount struct {
ID int64
Biz string
Currency string
Balance int64
Ver int64
State string
CTime time.Time
MTime time.Time
}
// UserAccount .
type UserAccount struct {
BizAccount
MID int64
}
// AccountLog .
type AccountLog struct {
ID int64
Name string
AccountID int64
From int64
To int64
Ver int64
State string
CTime time.Time
MTime time.Time
}
// AggrIncomeUser .
type AggrIncomeUser struct {
ID int64
MID int64
Currency string
PaySuccess int64
PayError int64
TotalIn int64
TotalOut int64
CTime time.Time
MTime time.Time
}
// AggrIncomeUserAsset .
type AggrIncomeUserAsset struct {
ID int64
MID int64
Currency string
Ver int64
OID int64
OType string
PaySuccess int64
PayError int64
TotalIn int64
TotalOut int64
CTime time.Time
MTime time.Time
}
// OrderBadDebt .
type OrderBadDebt struct {
ID int64
OrderID string
Type string
State string
CTime time.Time
MTime time.Time
}
// LogBillDaily .
type LogBillDaily struct {
ID int64
BillID string
FromIn int64
ToIn int64
FromOut int64
ToOut int64
OrderID string
CTime time.Time
MTime time.Time
}
// LogBillMonthly .
type LogBillMonthly struct {
ID int64
BillID string
FromIn int64
ToIn int64
FromOut int64
ToOut int64
BillUserDailyID string
CTime time.Time
MTime time.Time
}
// OrderRechargeShell .
type OrderRechargeShell struct {
ID int64
MID int64
OrderID string
Biz string
Amount int64
PayMSG string
State string
Ver int64
CTime time.Time
MTime time.Time
}
// OrderRechargeShellLog .
type OrderRechargeShellLog struct {
ID int64
OrderID string
FromState string
ToState string
Desc string
BillUserMonthlyID string
CTime time.Time
MTime time.Time
}

View File

@@ -0,0 +1,153 @@
package model
import (
"time"
"go-common/library/log"
)
// DBOldElecPayTradeInfo .
type DBOldElecPayTradeInfo struct {
ID int64
OrderID string
AVID string
}
// DBOldElecPayOrder .
type DBOldElecPayOrder struct {
ID int64 `json:"id"`
APPID int `json:"app_id"`
UPMID int64 `json:"mid"`
PayMID int64 `json:"pay_mid"`
OrderID string `json:"order_no"`
ElecNum int64 `json:"elec_num"`
Status int `json:"status"` // 订单状态1.消费中 2.消费成功 3.消费失败
CTimeStr string `json:"ctime"`
MTimeStr string `json:"mtime"`
CTime time.Time
MTime time.Time
}
// IsPaid .
func (d *DBOldElecPayOrder) IsPaid() bool {
return d.Status == 2
}
// IsHiddnRank .
func (d *DBOldElecPayOrder) IsHiddnRank() bool {
return d.APPID == 19 // 动态互推
}
// ParseCTime .
func (d *DBOldElecPayOrder) ParseCTime() (t time.Time) {
if !d.CTime.IsZero() {
return d.CTime
}
var err error
if t, err = time.ParseInLocation("2006-01-02 15:04:05", d.CTimeStr, time.Local); err != nil {
log.Error("DBOldElecPayOrder ctime parse failed: %s, err: %+v", d.CTimeStr, err)
t = time.Now()
}
return
}
// ParseMTime .
func (d *DBOldElecPayOrder) ParseMTime() (t time.Time) {
if !d.MTime.IsZero() {
return d.MTime
}
var err error
if t, err = time.ParseInLocation("2006-01-02 15:04:05", d.MTimeStr, time.Local); err != nil {
log.Error("DBOldElecPayOrder mtime parse failed: %s, err: %+v", d.MTimeStr, err)
t = time.Now()
}
return
}
// DBOldElecMessage .
type DBOldElecMessage struct {
ID int64 `json:"id"`
MID int64 `json:"mid"`
RefMID int64 `json:"ref_mid"`
RefID int64 `json:"ref_id"`
Message string `json:"message"`
AVID string `json:"av_no"`
DateVer string `json:"date_version"` // yyyy-MM格式年-月
Type int `json:"type"` // 留言类型, 1.用户对up主留言 2.up回复用户留言
State int `json:"state"` // 留言状态 0.未回复 1.已回复 2 已屏蔽
CTimeStr string `json:"ctime"`
MTimeStr string `json:"mtime"`
CTime time.Time
MTime time.Time
}
// ParseCTime .
func (d *DBOldElecMessage) ParseCTime() (t time.Time) {
if !d.CTime.IsZero() {
return d.CTime
}
var err error
if t, err = time.ParseInLocation("2006-01-02 15:04:05", d.CTimeStr, time.Local); err != nil {
log.Error("DBOldElecMessage ctime parse failed: %s, err: %+v", d.CTimeStr, err)
t = time.Now()
}
return
}
// ParseMTime .
func (d *DBOldElecMessage) ParseMTime() (t time.Time) {
if !d.MTime.IsZero() {
return d.MTime
}
var err error
if t, err = time.ParseInLocation("2006-01-02 15:04:05", d.MTimeStr, time.Local); err != nil {
log.Error("DBOldElecMessage mtime parse failed: %s, err: %+v", d.MTimeStr, err)
t = time.Now()
}
return
}
// DBOldElecUserSetting .
type DBOldElecUserSetting struct {
ID int64 `json:"id"`
MID int64 `json:"mid"`
SettingID int `json:"setting_id"`
Status int `json:"status"`
}
// BitValue 返回该配置位==1的数值
func (d *DBOldElecUserSetting) BitValue() int32 {
switch d.SettingID {
case 1:
return 0x1
case 2:
return 0x2
default:
log.Error("DBOldElecUserSetting unknown SettingID:%d, %+v", d.SettingID, d)
}
return 0
}
// DBElecMessage .
type DBElecMessage struct {
ID int64
Ver int64
AVID int64
UPMID int64
PayMID int64
Message string
Replied bool
Hidden bool
CTime time.Time
MTime time.Time
}
// DBElecReply .
type DBElecReply struct {
ID int64
MSGID int64
Reply string
Hidden bool
CTime time.Time
MTime time.Time
}

View File

@@ -0,0 +1,98 @@
package model
import (
"encoding/json"
"math"
)
// 各种状态枚举
const (
OrderStatePaid = "paid"
OrderStateSettled = "settled"
OrderStateBadDebt = "bad_debt"
OrderStateRefundFinished = "ref_finished"
OrderStateSettledRefunded = "st_refunded"
BizAsset = "asset"
CurrencyBP = "bp"
StateRunning = "running"
StateValid = "valid"
AccountStateIncome = "income"
AccountStateWithdraw = "withdraw"
AccountStateProfit = "profit"
AccountStateLoss = "fill_loss"
PayCheckOrderStateING = "WAIT_RECONCILIATION"
PayCheckOrderStateSuccess = "RECONCILIATION_SUCCESS"
PayCheckOrderStateFail = "RECONCILIATION_FAIL"
DefaultUserSetting = math.MaxInt32
)
// Message binlog databus msg.
type Message struct {
Action string `json:"action"`
Table string `json:"table"`
New json.RawMessage `json:"new"`
Old json.RawMessage `json:"old"`
}
// BinlogOrderUser .
type BinlogOrderUser struct {
OrderID string `json:"order_id"`
}
// BinlogAsset .
type BinlogAsset struct {
OID int64 `json:"oid"`
OType string `json:"otype"`
Currency string `json:"currency"`
}
// BinlogAssetRelation .
type BinlogAssetRelation struct {
OID int64 `json:"oid"`
OType string `json:"otype"`
MID int64 `json:"mid"`
}
// PayCheckRefundOrder .
type PayCheckRefundOrder struct {
Elements []*PayCheckRefundOrderEle `json:"batchRefundBillVOS"`
TXID string `json:"txId"`
}
// PayCheckRefundOrderEle .
type PayCheckRefundOrderEle struct {
RefundNO string `json:"refundNo"`
RefundAmount int64 `json:"refundAmount"`
CustomerRefundID string `json:"customerRefundId"`
RecoStatusDesc string `json:"recoStatusDesc"`
TXID string `json:"txId"`
}
// PayCheckOrder .
type PayCheckOrder struct {
PayChannelOrderNo string `json:"payChannelOrderNo"` //第三方支付渠道支付流水号
TxID string `json:"txId"`
BankAmount int64 `json:"bankAmount"` // 订单支付金额
PayTime int64 `json:"payTime"` // 订单支付时间,毫秒值
RecoStatusDesc string `json:"recoStatusDesc"` // 对账状态 WAIT_RECONCILIATION对账中,RECONCILIATION_SUCCESS对账成功,RECONCILIATION_FAIL对账失败
}
// PayQuery .
type PayQuery struct {
Orders []*PayOrder `json:"orders"`
}
// PayOrder .
type PayOrder struct {
TXID int64 `json:"txId"`
OrderID string `json:"orderId"`
PayStatus string `json:"payStatus"`
PayStatusDesc string `json:"payStatusDesc"`
FailReason string `json:"failReason"`
}

View File

@@ -0,0 +1,24 @@
package model
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestElecUserSetting(t *testing.T) {
Convey("", t, func() {
data := &DBOldElecUserSetting{
MID: 46333,
SettingID: 1,
Status: 1,
}
So(data.BitValue(), ShouldEqual, 0x01)
data = &DBOldElecUserSetting{
MID: 46333,
SettingID: 2,
Status: 1,
}
So(data.BitValue(), ShouldEqual, 0x02)
})
}

View File

@@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["http.go"],
importpath = "go-common/app/job/main/ugcpay/server/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/ugcpay/conf:go_default_library",
"//app/job/main/ugcpay/service:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,30 @@
package http
import (
"go-common/app/job/main/ugcpay/conf"
"go-common/app/job/main/ugcpay/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
var (
srv *service.Service
)
// Init init
func Init(s *service.Service) {
srv = s
engine := bm.DefaultServer(conf.Conf.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)
}
func ping(c *bm.Context) {
}

View File

@@ -0,0 +1,64 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"binlog.go",
"binlog_elec.go",
"elec_syncdb.go",
"limited_list.go",
"repaire.go",
"service.go",
"task.go",
"task_account_biz.go",
"task_account_user.go",
"task_bill_daily.go",
"task_bill_monthly.go",
"task_shell_recharge.go",
"tool.go",
],
importpath = "go-common/app/job/main/ugcpay/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/ugcpay/conf:go_default_library",
"//app/job/main/ugcpay/dao:go_default_library",
"//app/job/main/ugcpay/model:go_default_library",
"//app/job/main/ugcpay/service/pay:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/robfig/cron:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/job/main/ugcpay/service/pay:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,79 @@
package service
import (
"context"
"encoding/json"
"runtime/debug"
"go-common/app/job/main/ugcpay/model"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_tableOrderUser = "order_user"
_tableAsset = "asset"
_tableAssetRelation = "asset_relation"
)
func (s *Service) binlogproc() (err error) {
defer func() {
if x := recover(); x != nil {
log.Error("binlogproc panic(%+v) :\n %s", x, debug.Stack())
go s.binlogproc()
}
}()
var (
c = context.Background()
)
for res := range s.binlogMQ.Messages() {
if err != nil {
log.Error("%+v", err)
err = nil
}
msg := &model.Message{}
if err = json.Unmarshal(res.Value, msg); err != nil {
err = errors.WithStack(err)
continue
}
switch msg.Table {
case _tableOrderUser:
ms := &model.BinlogOrderUser{}
if err = json.Unmarshal(msg.New, ms); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
log.Info("Delete order_user cache : %+v", ms)
if err = s.dao.DelCacheOrderUser(c, ms.OrderID); err != nil {
continue
}
case _tableAsset:
ms := &model.BinlogAsset{}
if err = json.Unmarshal(msg.New, ms); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
log.Info("Delete asset cache : %+v", ms)
if err = s.dao.DelCacheAsset(c, ms.OID, ms.OType, ms.Currency); err != nil {
continue
}
case _tableAssetRelation:
ms := &model.BinlogAssetRelation{}
if err = json.Unmarshal(msg.New, ms); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
log.Info("Delete asset_relation cache : %+v", ms)
if err = s.dao.DelCacheAssetRelationState(c, ms.OID, ms.OType, ms.MID); err != nil {
continue
}
}
if err = res.Commit(); err != nil {
err = errors.Wrapf(err, "binlogproc commit")
continue
}
log.Info("binlogproc consume key:%v, topic: %v, part:%v, offset:%v, message %s,", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
}
return
}

View File

@@ -0,0 +1,249 @@
package service
import (
"context"
"encoding/json"
"runtime/debug"
"strconv"
"time"
"go-common/app/job/main/ugcpay/model"
// "go-common/app/service/main/ugcpay-rank/internal/service/rank"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_tableOldElecOrder = "elec_pay_order"
_tableOldElecMessage = "elec_message"
_tableOldElecUserSetting = "elec_user_setting"
)
var (
_ctx = context.Background()
)
func (s *Service) elecbinlogproc() {
defer func() {
if x := recover(); x != nil {
log.Error("binlogproc panic(%+v) :\n %s", x, debug.Stack())
go s.binlogproc()
}
}()
log.Info("Start binlogproc")
var (
err error
)
for res := range s.elecBinlogMQ.Messages() {
if err != nil {
log.Error("binlogproc consume key:%v, topic: %v, part:%v, offset:%v, message %s, err: %+v", res.Key, res.Topic, res.Partition, res.Offset, res.Value, err)
err = nil
}
if time.Since(time.Unix(res.Timestamp, 0)) >= time.Hour*24 {
log.Error("binlogproc consume expired msg, key:%v, topic: %v, part:%v, offset:%v, message %s", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
continue
}
msg := &model.Message{}
if err = json.Unmarshal(res.Value, msg); err != nil {
err = errors.WithStack(err)
continue
}
switch msg.Table {
case _tableOldElecOrder:
data := &model.DBOldElecPayOrder{}
if err = json.Unmarshal(msg.New, data); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
if err = s.handleElecOrder(_ctx, data); err != nil {
continue
}
case _tableOldElecMessage:
data := &model.DBOldElecMessage{}
if err = json.Unmarshal(msg.New, data); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
if err = s.handleOldElecMessage(_ctx, data); err != nil {
continue
}
case _tableOldElecUserSetting:
data := &model.DBOldElecUserSetting{}
if err = json.Unmarshal(msg.New, data); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
if err = s.handleElecUserSetting(_ctx, data); err != nil {
continue
}
default:
log.Error("binlogproc unknown table: %s", msg.Table)
}
if err = res.Commit(); err != nil {
err = errors.Wrapf(err, "binlogproc commit")
continue
}
log.Info("binlogproc consume msg, key:%v, topic: %v, part:%v, offset:%v, message %s", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
}
log.Info("End binlogproc")
}
func (s *Service) handleOldElecMessage(ctx context.Context, msg *model.DBOldElecMessage) (err error) {
log.Info("handleOldElecMessage message: %+v", msg)
var (
ver int64
verTime time.Time
avID int64
)
if verTime, err = time.Parse("2006-01", msg.DateVer); err != nil {
err = errors.WithStack(err)
return
}
ver = monthlyBillVer(verTime)
if msg.AVID != "" {
if avID, err = strconv.ParseInt(msg.AVID, 10, 64); err != nil {
log.Error("%+v", errors.WithStack(err))
avID = 0
err = nil
}
}
switch msg.Type {
// 用户对up主留言
case 1:
dbMSG := &model.DBElecMessage{
ID: msg.ID,
Ver: ver,
AVID: avID,
UPMID: msg.RefMID,
PayMID: msg.MID,
Message: msg.Message,
Replied: msg.State == 1,
Hidden: msg.State == 2,
CTime: msg.ParseCTime(),
MTime: msg.ParseMTime(),
}
if err = s.dao.UpsertElecMessage(ctx, dbMSG); err != nil {
return
}
if err = s.dao.RankElecUpdateMessage(ctx, dbMSG.AVID, dbMSG.UPMID, dbMSG.PayMID, dbMSG.Ver, dbMSG.Message, dbMSG.Hidden); err != nil {
return
}
// up主回复用户
case 2:
dbReply := &model.DBElecReply{
ID: msg.ID,
MSGID: msg.RefID,
Reply: msg.Message,
Hidden: msg.State == 2,
CTime: msg.ParseCTime(),
MTime: msg.ParseMTime(),
}
if err = s.dao.UpsertElecReply(ctx, dbReply); err != nil {
return
}
default:
log.Error("old_ele_message unknown type: %+v", msg)
}
return
}
func (s *Service) handleElecOrder(ctx context.Context, order *model.DBOldElecPayOrder) (err error) {
log.Info("handleElecOrder order: %+v", order)
if !order.IsPaid() {
return
}
var ok bool
ok, err = s.dao.AddCacheOrderID(ctx, order.OrderID)
if err != nil {
return
}
// 重复消费
if !ok {
log.Info("handleElecOrder order: %+v, has consumed before", order)
err = nil
return
}
if order.IsHiddnRank() {
log.Info("handleElecOrder order: %+v which app_id == 19", order)
return
}
tradeInfo, err := s.dao.RawOldElecTradeInfo(ctx, order.OrderID)
if err != nil {
return
}
avID := int64(0)
if tradeInfo != nil {
// log.Info("RawOldElecTradeInfo data not found, order: %+v", order)
if avID, err = strconv.ParseInt(tradeInfo.AVID, 10, 64); err != nil {
log.Error("handleElecOrder cant convert avID from: %s, err: %+v", tradeInfo.AVID, err)
avID = 0
}
}
var (
ver = monthlyBillVer(order.ParseMTime())
hidden = false
)
// 更新DB
tx, err := s.dao.BeginTranRank(ctx)
if err != nil {
return
}
rollbackFN := func() {
if theErr := s.dao.DelCacheOrderID(ctx, order.OrderID); theErr != nil {
log.Error("%+v", theErr)
}
tx.Rollback()
}
if avID != 0 {
if err = s.dao.TXUpsertElecAVRank(ctx, tx, 0, avID, order.UPMID, order.PayMID, order.ElecNum, hidden); err != nil {
rollbackFN()
return
}
if err = s.dao.TXUpsertElecAVRank(ctx, tx, ver, avID, order.UPMID, order.PayMID, order.ElecNum, hidden); err != nil {
rollbackFN()
return
}
}
if err = s.dao.TXUpsertElecUPRank(ctx, tx, 0, order.UPMID, order.PayMID, order.ElecNum, hidden); err != nil {
rollbackFN()
return
}
if err = s.dao.TXUpsertElecUPRank(ctx, tx, ver, order.UPMID, order.PayMID, order.ElecNum, hidden); err != nil {
rollbackFN()
return
}
if err = tx.Commit(); err != nil {
return
}
err = s.dao.RankElecUpdateOrder(ctx, avID, order.UPMID, order.PayMID, ver, order.ElecNum)
return
}
func (s *Service) handleElecUserSetting(ctx context.Context, setting *model.DBOldElecUserSetting) (err error) {
if setting.Status > 0 {
log.Info("handleElecUserSetting add setting: %+v", setting)
err = s.dao.ElecAddSetting(ctx, model.DefaultUserSetting, setting.MID, setting.BitValue())
} else {
log.Info("handleElecUserSetting delete setting: %+v", setting)
err = s.dao.ElecDeleteSetting(ctx, model.DefaultUserSetting, setting.MID, setting.BitValue())
}
if err != nil {
return
}
// 清理缓存
if err = s.dao.DelCacheUserSetting(ctx, setting.MID); err != nil {
log.Error("DelCacheUserSetting: %d, err: %+v", setting.MID, err)
err = nil
}
return
}

View File

@@ -0,0 +1,82 @@
package service
import (
"context"
"runtime/debug"
"time"
"go-common/app/job/main/ugcpay/model"
"go-common/library/log"
)
// SyncElecOrderList 同步老充电订单
func (s *Service) SyncElecOrderList(c context.Context) {
defer func() {
if x := recover(); x != nil {
log.Error("syncElecOrderSync panic(%+v) :\n %s", x, debug.Stack())
}
}()
var (
limit = 100
list = make([]*model.DBOldElecPayOrder, limit)
startID = int64(0)
err error
)
log.Info("Start syncElecOrderSync from elec_pay_order")
for len(list) >= limit {
log.Info("sync progress elec_pay_order fromID (%d)", startID)
// 1. load old data
if startID, list, err = s.dao.OldElecOrderList(_ctx, startID, limit); err != nil {
log.Error("%+v", err)
return
}
// 2. save new data
for _, ele := range list {
if err = s.handleElecOrder(_ctx, ele); err != nil {
log.Error("s.handleElecOrder: %+v, err: %+v", ele, err)
return
}
}
// 3. give db a break time
time.Sleep(time.Millisecond * 20)
}
log.Info("End syncElecOrderSync from elec_pay_order")
}
// SyncElecMessageList 同步老充电留言
func (s *Service) SyncElecMessageList(c context.Context) {
defer func() {
if x := recover(); x != nil {
log.Error("syncElecMessageList panic(%+v) :\n %s", x, debug.Stack())
}
}()
var (
limit = 100
list = make([]*model.DBOldElecMessage, limit)
startID = int64(0)
err error
)
log.Info("Start syncElecMessageList from elec_message")
for len(list) >= limit {
log.Info("sync progress elec_message fromID (%d)", startID)
// 1. load old data
if startID, list, err = s.dao.OldElecMessageList(_ctx, startID, limit); err != nil {
log.Error("%+v", err)
return
}
// 2. save new data
for _, ele := range list {
if err = s.handleOldElecMessage(_ctx, ele); err != nil {
log.Error("s.handleOldElecMessage: %+v, err: %+v", ele, err)
return
}
}
// 3. give db a break time
time.Sleep(time.Millisecond * 20)
}
log.Info("End syncElecMessageList from elec_message")
}

View File

@@ -0,0 +1,177 @@
package service
import (
"context"
"time"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
"go-common/library/log"
)
type limitedList interface {
LimitSize() int
BeginID(ctx context.Context) (id int64, err error)
List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error)
}
func runLimitedList(ctx context.Context, ll limitedList, sleep time.Duration, handler func(ctx context.Context, ele interface{}) error) (err error) {
if ll.LimitSize() <= 0 {
return
}
var (
id int64
list = make([]interface{}, ll.LimitSize())
)
if id, err = ll.BeginID(ctx); err != nil {
return
}
for len(list) >= ll.LimitSize() {
if id, list, err = ll.List(ctx, id); err != nil {
return
}
for _, ele := range list {
if err = handler(ctx, ele); err != nil {
log.Error("handle failed, ele: %+v, err: %+v", ele, err)
err = nil
continue
}
if sleep > 0 {
time.Sleep(sleep)
}
}
}
return
}
type orderPaidLL struct {
beginTime time.Time
endTime time.Time
limit int
dao *dao.Dao
}
func (o *orderPaidLL) LimitSize() int {
return o.limit
}
func (o *orderPaidLL) BeginID(ctx context.Context) (id int64, err error) {
return o.dao.MinIDOrderPaid(ctx, o.beginTime)
}
func (o *orderPaidLL) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.Order
if maxID, rawList, err = o.dao.OrderPaidList(ctx, o.beginTime, o.endTime, beginID, o.limit); err != nil {
return
}
log.Info("orderPaidLL beginID: %d, beginTime: %+v, endTime: %+v, limit: %d, size: %d", beginID, o.beginTime, o.endTime, o.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}
type orderRefundedLL struct {
beginTime time.Time
endTime time.Time
limit int
dao *dao.Dao
}
func (o *orderRefundedLL) LimitSize() int {
return o.limit
}
func (o *orderRefundedLL) BeginID(ctx context.Context) (id int64, err error) {
return o.dao.MinIDOrderRefunded(ctx, o.beginTime)
}
func (o *orderRefundedLL) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.Order
if maxID, rawList, err = o.dao.OrderRefundedList(ctx, o.beginTime, o.endTime, beginID, o.limit); err != nil {
return
}
log.Info("orderRefundedLL beginID: %d, beginTime: %+v, endTime: %+v, limit: %d, size: %d", beginID, o.beginTime, o.endTime, o.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}
type dailyBillLLByVer struct {
ver int64
limit int
dao *dao.Dao
}
func (d *dailyBillLLByVer) LimitSize() int {
return d.limit
}
func (d *dailyBillLLByVer) BeginID(ctx context.Context) (id int64, err error) {
return d.dao.MinIDDailyBillByVer(ctx, d.ver)
}
func (d *dailyBillLLByVer) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.DailyBill
if maxID, rawList, err = d.dao.DailyBillListByVer(ctx, d.ver, beginID, d.limit); err != nil {
return
}
log.Info("dailyBillLLByVer beginID: %d, ver: %d, limit: %d, size: %d", beginID, d.ver, d.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}
type dailyBillLLByMonthVer struct {
monthVer int64
limit int
dao *dao.Dao
}
func (d *dailyBillLLByMonthVer) LimitSize() int {
return d.limit
}
func (d *dailyBillLLByMonthVer) BeginID(ctx context.Context) (id int64, err error) {
return d.dao.MinIDDailyBillByMonthVer(ctx, d.monthVer)
}
func (d *dailyBillLLByMonthVer) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.DailyBill
if maxID, rawList, err = d.dao.DailyBillListByMonthVer(ctx, d.monthVer, beginID, d.limit); err != nil {
return
}
log.Info("dailyBillLLByMonthVer beginID: %d, monthVer: %d, limit: %d, size: %d", beginID, d.monthVer, d.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}
type monthlyBillLL struct {
ver int64
limit int
dao *dao.Dao
}
func (m *monthlyBillLL) LimitSize() int {
return m.limit
}
func (m *monthlyBillLL) BeginID(ctx context.Context) (id int64, err error) {
return m.dao.MinIDMonthlyBill(ctx, m.ver)
}
func (m *monthlyBillLL) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.Bill
if maxID, rawList, err = m.dao.MonthlyBillList(ctx, m.ver, beginID, m.limit); err != nil {
return
}
log.Info("monthlyBillLL beginID: %d, ver: %d, limit: %d, size: %d", beginID, m.ver, m.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["pay_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/ugcpay/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["pay.go"],
importpath = "go-common/app/job/main/ugcpay/service/pay",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/log:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,196 @@
package pay
import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"net/url"
"strconv"
"time"
"github.com/pkg/errors"
"go-common/library/log"
)
// Pay is.
type Pay struct {
ID string
Token string
RechargeShellNotifyURL string
}
// TraceID .
func (p *Pay) TraceID() string {
return strconv.FormatInt(time.Now().UnixNano(), 10)
}
// RechargeShellReq .
type RechargeShellReq struct {
CustomerID string `json:"customerId"`
ProductName string `json:"productName"`
Rate string `json:"rate"`
NotifyURL string `json:"notifyUrl"`
Timestamp int64 `json:"timestamp"`
SignType string `json:"signType"`
Sign string `json:"sign"`
Data []RechargeShellData `json:"data"`
}
// RechargeShellData .
type RechargeShellData struct {
ThirdOrderNo string `json:"thirdOrderNo"`
MID int64 `json:"mid"`
ThirdCoin string `json:"thirdCoin"`
Brokerage string `json:"brokerage"`
ThirdCtime int64 `json:"thirdCtime"`
}
// RechargeShell 转入贝壳
func (p *Pay) RechargeShell(orderID string, mid int64, assetBP int64, shell int64) (params url.Values, jsonData string, err error) {
var (
productName = "UGC付费"
rate = "1.00"
timestamp = time.Now().Unix() * 1000
thirdCoin = float64(assetBP) / 100
brokerage = float64(shell) / 100
)
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("productName", productName)
params.Set("rate", rate)
params.Set("notifyUrl", p.RechargeShellNotifyURL)
params.Set("timestamp", strconv.FormatInt(timestamp, 10))
params.Set("data", fmt.Sprintf("[{brokerage=%.2f&mid=%d&thirdCoin=%.2f&thirdCtime=%d&thirdOrderNo=%s}]", brokerage, mid, thirdCoin, timestamp, orderID))
p.Sign(params)
data := RechargeShellData{
ThirdOrderNo: orderID,
MID: mid,
ThirdCoin: fmt.Sprintf("%.2f", thirdCoin),
Brokerage: fmt.Sprintf("%.2f", brokerage),
ThirdCtime: timestamp,
}
req := RechargeShellReq{
CustomerID: p.ID,
ProductName: productName,
Rate: rate,
NotifyURL: p.RechargeShellNotifyURL,
Timestamp: timestamp,
SignType: params.Get("signType"),
Sign: params.Get("sign"),
Data: []RechargeShellData{data},
}
payBytes, err := json.Marshal(req)
if err != nil {
err = errors.Wrapf(err, "pay.RechargeShell.ToJSON : %s", params.Encode())
return
}
jsonData = string(payBytes)
return
}
// CheckOrder 对账param
func (p Pay) CheckOrder(txID string) (params url.Values) {
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("txIds", txID)
return
}
// CheckRefundOrder 退款对账param
func (p Pay) CheckRefundOrder(txID string) (params url.Values) {
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("txIds", txID)
return
}
// Query 返回订单查询param
func (p *Pay) Query(orderID string) (params url.Values) {
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("orderIds", orderID)
params.Set("timestamp", strconv.FormatInt(time.Now().Unix()*1000, 10))
params.Set("traceId", p.TraceID())
params.Set("version", "1.0")
return
}
// ToJSON param to json
func (p *Pay) ToJSON(params url.Values) (j string, err error) {
var (
payBytes []byte
pmap = make(map[string]string)
)
for k, v := range params {
if len(v) > 0 {
pmap[k] = v[0]
}
}
if payBytes, err = json.Marshal(pmap); err != nil {
err = errors.Wrapf(err, "pay.ToJSON : %s", params.Encode())
return
}
j = string(payBytes)
return
}
// DeviceType 支付平台DeviceType
func (p *Pay) DeviceType(platform string) (t int64) {
// 支付设备渠道类型, 1 pc 2 webapp 3 app 4jsapi 5 server 6小程序支付 7聚合二维码支付
switch platform {
case "ios", "android":
return 3
default:
return 1
}
}
// Sign 支付平台接口签名
func (p *Pay) Sign(params url.Values) (err error) {
params.Set("signType", "MD5")
sortedStr := params.Encode()
if sortedStr, err = url.QueryUnescape(sortedStr); err != nil {
return
}
b := bytes.Buffer{}
b.WriteString(sortedStr)
b.WriteString("&token=" + p.Token)
signMD5 := md5.Sum(b.Bytes())
sign := hex.EncodeToString(signMD5[:])
params.Set("sign", sign)
return
}
// Verify 支付平台返回param校验
func (p *Pay) Verify(params url.Values) (ok bool) {
var (
rs = params.Get("sign")
s string
)
ok = false
defer func() {
if !ok {
params.Set("sign", rs)
log.Error("Verify pay sign error, expect : %s, actual : %s, params : %s", s, rs, params.Encode())
}
}()
if rs == "" {
return
}
params.Del("sign")
if err := p.Sign(params); err != nil {
log.Error("Verify pay sign error : %+v", err)
return
}
s = params.Get("sign")
if rs == s {
ok = true
return
}
return
}

View File

@@ -0,0 +1,131 @@
package pay
import (
"encoding/json"
"flag"
"net/url"
"os"
"testing"
"go-common/app/service/main/ugcpay/conf"
. "github.com/smartystreets/goconvey/convey"
)
var (
p *Pay
)
func TestMain(m *testing.M) {
flag.Set("conf", "../../cmd/test.toml")
if err := conf.Init(); err != nil {
panic(err)
}
p = &Pay{
ID: conf.Conf.Biz.Pay.ID,
Token: conf.Conf.Biz.Pay.Token,
RechargeShellNotifyURL: "http://api.bilibili.co/x/internal/ugcpay/trade/recharge/callback",
}
m.Run()
os.Exit(0)
}
func TestCheckOrder(t *testing.T) {
Convey("", t, func() {
param := p.CheckOrder("3059753508505497600")
p.Sign(param)
t.Log(p.ToJSON(param))
})
}
func TestCheckRefundOrder(t *testing.T) {
Convey("", t, func() {
param := p.CheckRefundOrder("3059753508505497600")
p.Sign(param)
t.Log(p.ToJSON(param))
})
}
func TestRechargeShell(t *testing.T) {
var (
orderID = "123"
mid = int64(46333)
assetBP = int64(1)
shell = int64(1)
)
Convey("", t, func() {
_, json, err := p.RechargeShell(orderID, mid, assetBP, shell)
So(err, ShouldBeNil)
t.Log(json)
})
}
func TestSign(t *testing.T) {
Convey("", t, func() {
var (
param = url.Values{
"customerId": []string{"10017"},
"deviceType": []string{"3"},
"notifyUrl": []string{"http://api.bilibili.co/x/internal/ugcpay/trade/pay/callback"},
"orderCreateTime": []string{"1539935981000"},
"orderExpire": []string{"1800"},
"orderId": []string{"224"},
"originalAmount": []string{"2000"},
"payAmount": []string{"2000"},
"productId": []string{"10110688"},
"serviceType": []string{"99"},
"showTitle": []string{"传点什么好呢?"},
"timestamp": []string{"1539935981000"},
"traceId": []string{"1539935981967342977"},
"uid": []string{"27515244"},
"version": []string{"1.0"},
"feeType": []string{"CNY"},
}
)
err := p.Sign(param)
So(err, ShouldBeNil)
pmap := make(map[string]string)
var payBytes []byte
for k, v := range param {
if len(v) > 0 {
pmap[k] = v[0]
}
}
if payBytes, err = json.Marshal(pmap); err != nil {
return
}
t.Log(string(payBytes))
})
}
func TestSignVerify(t *testing.T) {
Convey("", t, func() {
var (
param = url.Values{
"customerId": []string{"10017"},
"deviceType": []string{"3"},
"notifyUrl": []string{"http://api.bilibili.co/x/internal/ugcpay/trade/pay/callback"},
"orderCreateTime": []string{"1539935981000"},
"orderExpire": []string{"1800"},
"orderId": []string{"15"},
"originalAmount": []string{"2000"},
"payAmount": []string{"2000"},
"productId": []string{"10110688"},
"serviceType": []string{"99"},
"showTitle": []string{"传点什么好呢?"},
"timestamp": []string{"1539935981000"},
"traceId": []string{"1539935981967342977"},
"uid": []string{"27515244"},
"version": []string{"1.0"},
"feeType": []string{"CNY"},
}
)
err := p.Sign(param)
So(err, ShouldBeNil)
ok := p.Verify(param)
So(ok, ShouldBeTrue)
})
}

View File

@@ -0,0 +1,85 @@
package service
// import (
// "context"
// "fmt"
// "net/url"
// "time"
// "go-common/app/job/main/ugcpay/model"
// "go-common/library/log"
// )
// func (s *Service) repairOrderUser() {
// var (
// ctx = context.Background()
// beginTime = time.Date(2018, time.October, 1, 0, 0, 0, 0, time.Local)
// endTime = time.Now()
// state = "paid"
// limit = 1000
// )
// maxID, orderList, err := s.dao.OrderList(ctx, beginTime, endTime, state, 0, limit)
// if err != nil {
// log.Error("%+v", err)
// return
// }
// log.Info("repaireOrderUser got list: %d, maxID: %d", len(orderList), maxID)
// for _, order := range orderList {
// // 修复 pay_time
// order.PayTime = order.CTime
// // 修复 real_fee
// asset, err := s.dao.Asset(ctx, order.OID, order.OType, order.Currency)
// if err != nil {
// log.Error("order:%+v, err: %+v", order, err)
// continue
// }
// order.RealFee = asset.Price
// // 修复 pay_id
// if len(order.PayID) < 2 {
// order.PayID, err = s.tradeQuery(ctx, order.OrderID)
// if err != nil {
// log.Error("order:%+v, err: %+v", order, err)
// continue
// }
// }
// if _, err = s.dao.UpdateOrder(ctx, order); err != nil {
// log.Error("order:%+v, err: %+v", order, err)
// err = nil
// }
// log.Info("repaireOrderUser success, order: %+v", order)
// }
// }
// func (s *Service) tradeQuery(ctx context.Context, orderID string) (payID string, err error) {
// // 2. 从支付平台获取订单状态
// var (
// params url.Values
// jsonData string
// orders map[string][]*model.PayOrder
// payOrders []*model.PayOrder
// ok bool
// )
// params = s.pay.Query(orderID)
// if err = s.pay.Sign(params); err != nil {
// return
// }
// if jsonData, err = s.pay.ToJSON(params); err != nil {
// return
// }
// if orders, err = s.dao.PayQuery(ctx, jsonData); err != nil {
// return
// }
// if payOrders, ok = orders[orderID]; !ok || len(payOrders) == 0 {
// log.Info("tradeQuery from pay platform not found order: %s", orderID)
// return
// }
// for _, po := range payOrders {
// if po.TXID > 0 {
// log.Info("tradeQuery order: %s, txID: %d", orderID, po.TXID)
// payID = fmt.Sprintf("%d", po.TXID)
// return
// }
// }
// return
// }

View File

@@ -0,0 +1,132 @@
package service
import (
"context"
"math/rand"
"time"
"go-common/app/job/main/ugcpay/conf"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/service/pay"
"go-common/library/queue/databus"
"github.com/robfig/cron"
)
var (
ctx = context.Background()
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
binlogMQ *databus.Databus
elecBinlogMQ *databus.Databus
cron *cron.Cron
pay *pay.Pay
taskLog *taskLog
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
binlogMQ: databus.New(c.BinlogMQ),
elecBinlogMQ: databus.New(c.ElecBinlogMQ),
cron: cron.New(),
pay: &pay.Pay{
ID: conf.Conf.Biz.Pay.ID,
Token: conf.Conf.Biz.Pay.Token,
RechargeShellNotifyURL: conf.Conf.Biz.Pay.RechargeCallbackURL,
},
}
s.taskLog = &taskLog{
d: s.dao,
}
// 创建日账单任务
taskBillDaily := &taskBillDaily{
dao: s.dao,
pay: s.pay,
rnd: rand.New(rand.NewSource(time.Now().Unix())),
dayOffset: conf.Conf.Biz.Task.DailyBillOffset,
namePrefix: conf.Conf.Biz.Task.DailyBillPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskDailyBill, s.wrapDisProc(taskBillDaily)); err != nil {
panic(err)
}
// 创建up虚拟账户入账任务
taskAccountUser := &taskAccountUser{
dao: s.dao,
taskPre: taskBillDaily, // 前置任务
dayOffset: conf.Conf.Biz.Task.DailyBillOffset,
namePrefix: conf.Conf.Biz.Task.AccountUserPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskAccountUser, s.wrapDisProc(taskAccountUser)); err != nil {
panic(err)
}
// 创建资金池入账任务
taskAccountBiz := &taskAccountBiz{
dao: s.dao,
taskPre: taskBillDaily, // 前置任务
dayOffset: conf.Conf.Biz.Task.DailyBillOffset,
namePrefix: conf.Conf.Biz.Task.AccountBizPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskAccountBiz, s.wrapDisProc(taskAccountBiz)); err != nil {
panic(err)
}
// 创建月账单任务
taskBillMonthly := &taskBillMonthly{
dao: s.dao,
rnd: rand.New(rand.NewSource(time.Now().Unix())),
monthOffset: conf.Conf.Biz.Task.MonthBillOffset,
namePrefix: conf.Conf.Biz.Task.MonthBillPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskMonthlyBill, s.wrapDisProc(taskBillMonthly)); err != nil {
panic(err)
}
// 创建转贝壳任务
taskRechargeShell := &taskRechargeShell{
dao: s.dao,
pay: s.pay,
rnd: rand.New(rand.NewSource(time.Now().Unix())),
monthOffset: conf.Conf.Biz.Task.RechargeShellOffset,
namePrefix: conf.Conf.Biz.Task.RechargeShellPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskRechargeShell, s.wrapDisProc(taskRechargeShell)); err != nil {
panic(err)
}
s.cron.Start()
go s.binlogproc()
go s.elecbinlogproc()
// go s.repairOrderUser() 修复订单用
return s
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,59 @@
package service
import (
"testing"
)
type c struct {
balance int64
loss int64
expectUserRefund int64
expectBizRefund int64
}
func TestCalcRefundFee(t *testing.T) {
var min int64 = -20000
cases := []c{
c{
balance: 10000,
loss: 20000,
expectUserRefund: 20000,
expectBizRefund: 0,
}, c{
balance: 0,
loss: 10000,
expectUserRefund: 10000,
expectBizRefund: 0,
}, c{
balance: -1,
loss: 20000,
expectUserRefund: 19999,
expectBizRefund: 1,
}, c{
balance: -19999,
loss: 20000,
expectUserRefund: 1,
expectBizRefund: 19999,
}, c{
balance: -20000,
loss: 20000,
expectUserRefund: 0,
expectBizRefund: 20000,
}, c{
balance: -30000,
loss: 20000,
expectUserRefund: 0,
expectBizRefund: 20000,
},
}
for _, c := range cases {
bizRefund, userRefund := calcRefundFee(c.balance, c.loss, min)
if userRefund != c.expectUserRefund {
t.Fatalf("TestCalcRefundFee case: %+v expectUserRefund not right, actual: %d\n", c, userRefund)
}
if bizRefund != c.expectBizRefund {
t.Fatalf("TestCalcRefundFee case: %+v expectBizRefund not right, actual: %d\n", c, bizRefund)
}
}
}

View File

@@ -0,0 +1,155 @@
package service
import (
"context"
"runtime/debug"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
func (s *Service) wrapDisProc(tp TaskProcess) func() {
return func() {
defer func() {
if x := recover(); x != nil {
log.Error("task : %s, panic(%+v): %s", tp.Name(), x, debug.Stack())
}
}()
var (
ok bool
err error
)
if ok, err = s.taskCreate(tp.Name(), tp.TTL()); err != nil {
log.Info("s.taskCreate err: %+v", err)
return
}
if !ok {
log.Info("task : %s end, other task is running", tp.Name())
return
}
defer func() {
if err = s.taskDone(tp.Name()); err != nil {
log.Error("task : %s, taskDone error: %+v", tp.Name(), err)
}
}()
log.Info("task : %s, task start", tp.Name())
if err = tp.Run(); err != nil {
log.Error("task : %s end, error: %+v", tp.Name(), err)
}
}
}
// TaskProcess .
type TaskProcess interface {
Run() error // 运行任务
TTL() int32 // 任务的最长生命周期
Name() string // 任务名称
}
func (s *Service) taskCreate(task string, ttl int32) (ok bool, err error) {
log.Info("task create: %s, ttl: %d", task, ttl)
return s.dao.AddCacheTask(context.Background(), task, ttl)
}
func (s *Service) taskDone(task string) (err error) {
// return s.dao.DelCacheTask(context.Background(), task)
return
}
func checkOrCreateTaskFromLog(ctx context.Context, task TaskProcess, tl *taskLog, expectFN func(context.Context) (int64, error)) (finished bool, err error) {
var (
taskCreated bool
expect int64
)
if taskCreated, finished = tl.checkTask(task); finished {
log.Info("%s already finished", task.Name())
return
}
if !taskCreated {
if expect, err = expectFN(ctx); err != nil {
return
}
if _, err = tl.createTask(ctx, task, expect); err != nil {
return
}
}
return
}
func runTXCASTaskWithLog(ctx context.Context, task TaskProcess, tl *taskLog, biz func(context.Context, *xsql.Tx) (bool, error)) (err error) {
fn := func(ctx context.Context) (affected bool, err error) {
affected = true
tx, err := tl.d.BeginTran(ctx)
if err != nil {
return
}
if affected, err = biz(ctx, tx); err != nil {
// 业务报错不主动rollback
return
}
if err = tl.recordTaskSuccess(ctx, tx, task); err != nil {
tx.Rollback()
return
}
err = tx.Commit()
return
}
if err = runCAS(ctx, fn); err != nil {
tl.recordTaskFailure(ctx, task)
}
return
}
type taskLog struct {
d *dao.Dao
}
func (t *taskLog) createTask(ctx context.Context, task TaskProcess, expect int64) (logTask *model.LogTask, err error) {
logTask = &model.LogTask{
Name: task.Name(),
Expect: expect,
State: "created",
}
logTask.ID, err = t.d.InsertLogTask(ctx, logTask)
return
}
func (t *taskLog) recordTaskSuccess(ctx context.Context, tx *xsql.Tx, task TaskProcess) (err error) {
_, err = t.d.TXIncrLogTaskSuccess(ctx, tx, task.Name())
if err != nil {
err = errors.Wrapf(err, "taskLog recordTaskSuccess: %s", task.Name())
}
return
}
func (t *taskLog) recordTaskFailure(ctx context.Context, task TaskProcess) {
_, err := t.d.IncrLogTaskFailure(ctx, task.Name())
if err != nil {
err = errors.Wrapf(err, "taskLog recordTaskFailure: %s", task.Name())
log.Error("%+v", err)
}
}
func (t *taskLog) checkTask(task TaskProcess) (created, finished bool) {
data, err := t.d.LogTask(ctx, task.Name())
if err != nil {
return
}
if data == nil {
return
}
log.Info("checkTask: %s, data: %+v", task.Name(), data)
created = true
if data.State == "success" {
finished = true
return
}
if data.Expect == data.Success {
finished = true
}
return
}

View File

@@ -0,0 +1,143 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
type taskAccountBiz struct {
dao *dao.Dao
taskPre TaskProcess
dayOffset int
namePrefix string
tl *taskLog
}
func (s *taskAccountBiz) Run() (err error) {
// 检查日账单任务是否完成
if _, finished := s.tl.checkTask(s.taskPre); !finished {
log.Info("taskAccountBiz check task: %s not finished", s.taskPre.Name())
return nil
}
var (
ctx = context.Background()
finished bool
expectFN = func(ctx context.Context) (expect int64, err error) {
expect = 1
return
}
)
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
return
}
return runTXCASTaskWithLog(ctx, s, s.tl, s.run)
}
func (s *taskAccountBiz) TTL() int32 {
return 3600 * 2
}
func (s *taskAccountBiz) Name() string {
return fmt.Sprintf("%s_%d", s.namePrefix, dailyBillVer(time.Now()))
}
func (s *taskAccountBiz) run(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
var (
timeFrom, timeTo time.Time = dayRange(s.dayOffset)
ver = dailyBillVer(timeFrom)
bizAccount *model.BizAccount
bizAccountLog *model.AccountLog
sumPaidOrderRealfee int64
sumRefundedOrderRealfee int64
sumBillDailyIn int64
sumBillDailyOut int64
bizProfit int64
)
affected = true
if sumPaidOrderRealfee, err = s.dao.SumPaidOrderUserRealFee(ctx, timeFrom, timeTo); err != nil {
return
}
if sumRefundedOrderRealfee, err = s.dao.SumRefundedOrderUserRealFee(ctx, timeFrom, timeTo); err != nil {
return
}
if sumBillDailyIn, sumBillDailyOut, err = s.dao.SumDailyBill(ctx, ver); err != nil {
return
}
log.Info("taskAccountBiz: %s, sumPaidOrderRealfee: %d, sumRefundedOrderRealfee: %d, sumBillDailyIn: %d, sumBillDailyOut: %d", s.Name(), sumPaidOrderRealfee, sumRefundedOrderRealfee, sumBillDailyIn, sumBillDailyOut)
if sumPaidOrderRealfee < sumBillDailyIn {
err = errors.Errorf("taskAccountBiz find sumPaidOrderRealfee(%d) < sumBillDailyIn(%d), ver: %d", sumPaidOrderRealfee, sumBillDailyIn, ver)
return
}
if sumRefundedOrderRealfee < sumBillDailyOut {
err = errors.Errorf("taskAccountBiz find sumRefundedOrderRealfee(%d) < sumBillDailyOut(%d), ver: %d", sumRefundedOrderRealfee, sumBillDailyOut, ver)
return
}
// 日收益 - 日支出
bizProfit = (sumPaidOrderRealfee - sumBillDailyIn) - (sumRefundedOrderRealfee - sumBillDailyOut)
// 获得 biz_account
if bizAccount, err = s.dao.BizAccount(ctx, model.BizAsset, model.CurrencyBP); err != nil {
return
}
// 初始化 biz_account
if bizAccount == nil {
if bizAccount, err = initBizAccount(ctx, model.BizAsset, model.CurrencyBP, s.dao); err != nil {
return
}
}
bizAccountLog = &model.AccountLog{
AccountID: bizAccount.ID,
Name: s.Name(),
From: bizAccount.Balance,
To: bizAccount.Balance + bizProfit,
Ver: bizAccount.Ver + 1,
State: model.AccountStateProfit,
}
bizAccount.Balance = bizAccount.Balance + bizProfit
// 更新 biz account
rowAffected, err := s.dao.TXUpdateBizAccount(ctx, tx, bizAccount)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpdateBizAccount no affected biz account: %+v", bizAccount)
tx.Rollback()
affected = false
return
}
// 添加资金池账户 log
err = s.dao.TXInsertBizAccountLog(ctx, tx, bizAccountLog)
if err != nil {
tx.Rollback()
return
}
log.Info("taskAccountBiz: %+v ", bizAccount)
return
}
func initBizAccount(ctx context.Context, biz, currency string, dao *dao.Dao) (bizAccount *model.BizAccount, err error) {
bizAccount = &model.BizAccount{
Biz: biz,
Currency: currency,
State: model.StateValid,
Ver: 1,
}
if bizAccount.ID, err = dao.InsertBizAccount(ctx, bizAccount); err != nil {
return
}
return
}

View File

@@ -0,0 +1,201 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/job/main/ugcpay/conf"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
type taskAccountUser struct {
dao *dao.Dao
taskPre TaskProcess
dayOffset int
namePrefix string
tl *taskLog
}
func (s *taskAccountUser) Run() (err error) {
// 检查日账单任务是否完成
if _, finished := s.tl.checkTask(s.taskPre); !finished {
log.Info("taskAccountUser check task: %s not finished", s.taskPre.Name())
return nil
}
var (
ctx = context.Background()
finished bool
expectFN = func(ctx context.Context) (expect int64, err error) {
var (
beginTime, _ = dayRange(s.dayOffset)
ver = dailyBillVer(beginTime)
)
if expect, err = s.dao.CountDailyBillByVer(ctx, ver); err != nil {
return
}
return
}
)
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
return
}
return s.run(ctx)
}
func (s *taskAccountUser) TTL() int32 {
return 3600 * 2
}
func (s *taskAccountUser) Name() string {
return fmt.Sprintf("%s_%d", s.namePrefix, dailyBillVer(time.Now()))
}
func (s *taskAccountUser) run(ctx context.Context) (err error) {
ll := &dailyBillLLByVer{
limit: 1000,
dao: s.dao,
}
beginTime, _ := dayRange(s.dayOffset)
ll.ver = dailyBillVer(beginTime)
return runLimitedList(ctx, ll, time.Millisecond*2, s.runDailyBill)
}
func (s *taskAccountUser) runDailyBill(ctx context.Context, ele interface{}) (err error) {
dailyBill, ok := ele.(*model.DailyBill)
if !ok {
err = errors.Errorf("taskAccountUser convert ele: %+v failed", dailyBill)
return
}
log.Info("taskAccountUser handle dailyBill: %+v", dailyBill)
fn := func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
var (
account *model.UserAccount
accountLog *model.AccountLog
userProfit = dailyBill.In - dailyBill.Out
)
affected = true
// 获得该 mid 的 account
if account, err = s.dao.UserAccount(ctx, dailyBill.MID, dailyBill.Biz, dailyBill.Currency); err != nil {
return
}
// 初始化 biz_account
if account == nil {
if account, err = s.initUserAccount(ctx, dailyBill.MID, dailyBill.Biz, dailyBill.Currency); err != nil {
return
}
}
// 虚拟账户平账,低于一定阈值由虚拟账户转出
if userProfit < 0 {
bizRefund, userRefund := calcRefundFee(account.Balance, -userProfit, conf.Conf.Biz.AccountUserMin)
userProfit = -userRefund
if bizRefund > 0 {
// 获得 biz_account
var bizAccount *model.BizAccount
if bizAccount, err = s.dao.BizAccount(ctx, model.BizAsset, model.CurrencyBP); err != nil {
return
}
// 初始化 biz_account
if bizAccount == nil {
if bizAccount, err = initBizAccount(ctx, model.BizAsset, model.CurrencyBP, s.dao); err != nil {
return
}
}
bizAccountLog := &model.AccountLog{
AccountID: bizAccount.ID,
Name: s.Name(),
From: bizAccount.Balance,
To: bizAccount.Balance - bizRefund,
Ver: bizAccount.Ver + 1,
State: model.AccountStateLoss,
}
bizAccount.Balance = bizAccount.Balance - bizRefund
// 更新 biz account
var rowAffected int64
if rowAffected, err = s.dao.TXUpdateBizAccount(ctx, tx, bizAccount); err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpdateBizAccount no affected biz account: %+v", bizAccount)
tx.Rollback()
affected = false
return
}
// 添加资金池账户 log
if err = s.dao.TXInsertBizAccountLog(ctx, tx, bizAccountLog); err != nil {
tx.Rollback()
return
}
}
}
accountLog = &model.AccountLog{
AccountID: account.ID,
Name: fmt.Sprintf("%s_%d", s.Name(), dailyBill.MID),
From: account.Balance,
To: account.Balance + userProfit,
Ver: account.Ver + 1,
State: model.AccountStateIncome,
}
account.Balance = account.Balance + userProfit
// 更新 user account
rowAffected, err := s.dao.TXUpdateUserAccount(ctx, tx, account)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpdateUserAccount no affected user account: %+v", account)
tx.Rollback()
affected = false
return
}
// 添加资金池账户 log
err = s.dao.TXInsertUserAccountLog(ctx, tx, accountLog)
if err != nil {
tx.Rollback()
return
}
log.Info("taskAccountUser: %+v ", account)
return
}
return runTXCASTaskWithLog(ctx, s, s.tl, fn)
}
func (s *taskAccountUser) initUserAccount(ctx context.Context, mid int64, biz, currency string) (account *model.UserAccount, err error) {
account = &model.UserAccount{}
account.MID = mid
account.Biz = biz
account.Currency = currency
account.State = model.StateValid
account.Ver = 1
if account.ID, err = s.dao.InsertUserAccount(ctx, account); err != nil {
return
}
return
}
// 虚拟账户平账
func calcRefundFee(balance int64, loss int64, minBalance int64) (bizRefund int64, userRefund int64) {
if balance-loss >= minBalance {
userRefund = loss
bizRefund = 0
return
}
if balance > minBalance {
userRefund = balance - minBalance
}
bizRefund = loss - userRefund
return
}

View File

@@ -0,0 +1,471 @@
package service
import (
"context"
"fmt"
"math/rand"
"time"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
"go-common/app/job/main/ugcpay/service/pay"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
type taskBillDaily struct {
dao *dao.Dao
pay *pay.Pay
rnd *rand.Rand
dayOffset int
namePrefix string
tl *taskLog
}
func (s *taskBillDaily) Run() (err error) {
var (
ctx = context.Background()
finished bool
expectFN = func(ctx context.Context) (expect int64, err error) {
var (
beginTime, endTime = dayRange(s.dayOffset)
expectPaid, expectRefunded int64
)
if expectPaid, err = s.dao.CountPaidOrderUser(ctx, beginTime, endTime); err != nil {
return
}
if expectRefunded, err = s.dao.CountRefundedOrderUser(ctx, beginTime, endTime); err != nil {
return
}
expect = expectPaid + expectRefunded
return
}
)
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
return
}
return s.run(ctx)
}
func (s *taskBillDaily) TTL() int32 {
return 3600 * 2
}
func (s *taskBillDaily) Name() string {
return fmt.Sprintf("%s_%d", s.namePrefix, dailyBillVer(time.Now()))
}
// 日账单生成
func (s *taskBillDaily) run(ctx context.Context) (err error) {
// 已支付成功订单入账
paidLL := &orderPaidLL{
limit: 1000,
dao: s.dao,
}
paidLL.beginTime, paidLL.endTime = dayRange(s.dayOffset)
if err = runLimitedList(ctx, paidLL, time.Millisecond*5, s.runPaidOrder); err != nil {
return
}
// 已退款订单入账
refundLL := &orderRefundedLL{
limit: 1000,
dao: s.dao,
}
refundLL.beginTime, refundLL.endTime = dayRange(s.dayOffset)
return runLimitedList(ctx, refundLL, time.Millisecond*5, s.runRefundedOrder)
}
// 处理退款order
func (s *taskBillDaily) runRefundedOrder(ctx context.Context, ele interface{}) (err error) {
order, ok := ele.(*model.Order)
if !ok {
return errors.Errorf("refundedOrderHandler convert ele: %+v failed", order)
}
log.Info("runRefundedOrder handle order: %+v", order)
logOrder := &model.LogOrder{
OrderID: order.OrderID,
FromState: order.State,
ToState: model.OrderStateRefundFinished,
}
order.State = model.OrderStateRefundFinished
fn := func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
var (
bill *model.DailyBill
asset *model.Asset
ver = dailyBillVer(order.RefundTime)
monthVer = monthlyBillVer(order.RefundTime)
billDailyLog *model.LogBillDaily
userIncome, _ = calcAssetIncome(order.RealFee) // 收入计算结果
)
affected = true
// 获得订单对应的asset
if asset, err = s.dao.Asset(ctx, order.OID, order.OType, order.Currency); err != nil {
return
}
if asset == nil {
err = errors.Errorf("dailyBillHander find invalid asset order, order: %+v", order)
return
}
// 获得该mid对应的日账单
if bill, err = s.dao.DailyBill(ctx, asset.MID, model.BizAsset, model.CurrencyBP, ver); err != nil {
return
}
if bill == nil {
if bill, err = s.initDailyBill(ctx, asset.MID, model.BizAsset, model.CurrencyBP, ver, monthVer); err != nil {
return
}
}
// 计算日账单
billDailyLog = &model.LogBillDaily{
BillID: bill.BillID,
FromIn: bill.In,
ToIn: bill.In,
FromOut: bill.Out + userIncome,
ToOut: bill.Out,
OrderID: order.OrderID + "_r",
}
bill.Out += userIncome
// 更新order
rowAffected, err := s.dao.TXUpdateOrder(ctx, tx, order)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
tx.Rollback()
log.Error("UpdateOrder no affected from order: %+v", order)
affected = false
return
}
// 添加 order log
_, err = s.dao.TXInsertOrderUserLog(ctx, tx, logOrder)
if err != nil {
tx.Rollback()
return
}
// 更新daily_bill
rowAffected, err = s.dao.TXUpdateDailyBill(ctx, tx, bill)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpdateDailyBill no affected bill: %+v", bill)
tx.Rollback()
affected = false
return
}
// 添加 daily bill log , uk order_id
_, err = s.dao.TXInsertLogDailyBill(ctx, tx, billDailyLog)
if err != nil {
tx.Rollback()
return
}
// 更新 aggr
aggrMonthlyAsset := &model.AggrIncomeUserAsset{
MID: bill.MID,
Currency: bill.Currency,
Ver: monthVer,
OID: order.OID,
OType: order.OType,
}
aggrAllAsset := &model.AggrIncomeUserAsset{
MID: bill.MID,
Currency: bill.Currency,
Ver: 0,
OID: order.OID,
OType: order.OType,
}
aggrUser := &model.AggrIncomeUser{
MID: bill.MID,
Currency: bill.Currency,
}
_, err = s.dao.TXUpsertDeltaAggrIncomeUserAsset(ctx, tx, aggrAllAsset, 0, 1, 0, userIncome)
if err != nil {
tx.Rollback()
return
}
_, err = s.dao.TXUpsertDeltaAggrIncomeUserAsset(ctx, tx, aggrMonthlyAsset, 0, 1, 0, userIncome)
if err != nil {
tx.Rollback()
return
}
_, err = s.dao.TXUpsertDeltaAggrIncomeUser(ctx, tx, aggrUser, 0, 1, 0, userIncome)
if err != nil {
tx.Rollback()
return
}
log.Info("Settle daily bill: %+v, aggrAllAsset: %+v, aggrMonthlyAsset: %+v, aggrUser: %+v, from refunded order: %+v", bill, aggrAllAsset, aggrMonthlyAsset, aggrUser, order)
return
}
return runTXCASTaskWithLog(ctx, s, s.tl, fn)
}
func (s *taskBillDaily) runPaidOrder(ctx context.Context, ele interface{}) (err error) {
order, ok := ele.(*model.Order)
if !ok {
return errors.Errorf("runPaidOrder convert ele: %+v failed", order)
}
log.Info("runPaidOrder handle order: %+v", order)
checkOK, payDesc, err := s.checkOrder(ctx, order) // 对支付订单对账
if err != nil {
return err
}
logOrder := &model.LogOrder{
OrderID: order.OrderID,
FromState: order.State,
Desc: payDesc,
}
var fn func(context.Context, *xsql.Tx) (affected bool, err error)
if checkOK { // 对账成功
logOrder.ToState = model.OrderStateSettled
order.State = model.OrderStateSettled
fn = func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
var (
bill *model.DailyBill
asset *model.Asset
ver = dailyBillVer(order.PayTime)
monthVer = monthlyBillVer(order.PayTime)
billDailyLog *model.LogBillDaily
userIncome, _ = calcAssetIncome(order.RealFee) // 收入计算结果
)
affected = true
// 获得订单对应的asset
if asset, err = s.dao.Asset(ctx, order.OID, order.OType, order.Currency); err != nil {
return
}
if asset == nil {
log.Error("runPaidOrder find invalid asset order, order: %+v", order)
return
}
// 获得该mid对应的日账单
if bill, err = s.dao.DailyBill(ctx, asset.MID, model.BizAsset, model.CurrencyBP, ver); err != nil {
return
}
if bill == nil {
if bill, err = s.initDailyBill(ctx, asset.MID, model.BizAsset, model.CurrencyBP, ver, monthVer); err != nil {
return
}
}
// 计算日账单
billDailyLog = &model.LogBillDaily{
BillID: bill.BillID,
FromIn: bill.In,
ToIn: bill.In + userIncome,
FromOut: bill.Out,
ToOut: bill.Out,
OrderID: order.OrderID,
}
bill.In += userIncome
// 更新order
rowAffected, err := s.dao.TXUpdateOrder(ctx, tx, order)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
tx.Rollback()
log.Error("UpdateOrder no affected from order: %+v", order)
affected = false
return
}
// 添加 order log
_, err = s.dao.TXInsertOrderUserLog(ctx, tx, logOrder)
if err != nil {
tx.Rollback()
return
}
// 更新daily_bill
rowAffected, err = s.dao.TXUpdateDailyBill(ctx, tx, bill)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpsertDeltaDailyBill no affected bill: %+v", bill)
tx.Rollback()
affected = false
return
}
// 添加 daily bill log , uk order_id
_, err = s.dao.TXInsertLogDailyBill(ctx, tx, billDailyLog)
if err != nil {
tx.Rollback()
return
}
// 更新 aggr
aggrMonthlyAsset := &model.AggrIncomeUserAsset{
MID: bill.MID,
Currency: bill.Currency,
Ver: monthVer,
OID: order.OID,
OType: order.OType,
}
aggrAllAsset := &model.AggrIncomeUserAsset{
MID: bill.MID,
Currency: bill.Currency,
Ver: 0,
OID: order.OID,
OType: order.OType,
}
aggrUser := &model.AggrIncomeUser{
MID: bill.MID,
Currency: bill.Currency,
}
_, err = s.dao.TXUpsertDeltaAggrIncomeUserAsset(ctx, tx, aggrAllAsset, 1, 0, userIncome, 0)
if err != nil {
tx.Rollback()
return
}
_, err = s.dao.TXUpsertDeltaAggrIncomeUserAsset(ctx, tx, aggrMonthlyAsset, 1, 0, userIncome, 0)
if err != nil {
tx.Rollback()
return
}
_, err = s.dao.TXUpsertDeltaAggrIncomeUser(ctx, tx, aggrUser, 1, 0, userIncome, 0)
if err != nil {
tx.Rollback()
return
}
log.Info("taskBillDaily: %+v, aggrAllAsset: %+v, aggrMonthlyAsset: %+v, aggrUser: %+v, from paid order: %+v", bill, aggrAllAsset, aggrMonthlyAsset, aggrUser, order)
return
}
} else { // 对账失败
logOrder.ToState = model.OrderStateBadDebt
order.State = model.OrderStateBadDebt
fn = func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
var (
orderBadDebt *model.OrderBadDebt
)
affected = true
if orderBadDebt, err = s.dao.OrderBadDebt(ctx, order.OrderID); err != nil {
return
}
if orderBadDebt == nil {
if orderBadDebt, err = s.initBadDebt(ctx, order.OrderID); err != nil {
return
}
}
orderBadDebt.Type = "unknown"
orderBadDebt.State = "failed"
// 更新order
rowAffected, theErr := s.dao.TXUpdateOrder(ctx, tx, order)
if theErr != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
tx.Rollback()
log.Error("UpdateOrder no affected from order: %+v", order)
affected = false
return
}
// 添加order log
_, theErr = s.dao.TXInsertOrderUserLog(ctx, tx, logOrder)
if theErr != nil {
tx.Rollback()
return
}
// 添加坏账表
_, err = s.dao.TXUpdateOrderBadDebt(ctx, tx, orderBadDebt)
if err != nil {
tx.Rollback()
return
}
log.Info("Add bad debt: %+v", orderBadDebt)
return
}
}
return runTXCASTaskWithLog(ctx, s, s.tl, fn)
}
func (s *taskBillDaily) checkOrder(ctx context.Context, order *model.Order) (ok bool, payDesc string, err error) {
ok = false
if order == nil {
return
}
if order.PayID == "" {
log.Error("Check order found baddebt order: %+v", order)
return
}
payParam := s.pay.CheckOrder(order.PayID)
s.pay.Sign(payParam)
payJSON, err := s.pay.ToJSON(payParam)
if err != nil {
return
}
orders, err := s.dao.PayCheckOrder(ctx, payJSON)
if err != nil {
return
}
result, ok := orders[order.PayID]
if !ok {
return
}
payDesc = result.RecoStatusDesc
switch result.RecoStatusDesc {
case model.PayCheckOrderStateSuccess:
ok = true
default:
ok = false
}
return
}
func (s *taskBillDaily) initDailyBill(ctx context.Context, mid int64, biz, currency string, ver, monthVer int64) (bill *model.DailyBill, err error) {
bill = &model.DailyBill{}
bill.BillID = orderID(s.rnd)
bill.MID = mid
bill.Biz = model.BizAsset
bill.Currency = model.CurrencyBP
bill.In = 0
bill.Out = 0
bill.Ver = ver
bill.MonthVer = monthVer
bill.Version = 1
if bill.ID, err = s.dao.InsertDailyBill(ctx, bill); err != nil {
return
}
return
}
func (s *taskBillDaily) initBadDebt(ctx context.Context, orderID string) (data *model.OrderBadDebt, err error) {
data = &model.OrderBadDebt{
OrderID: orderID,
Type: "",
State: "",
}
if data.ID, err = s.dao.InsertOrderBadDebt(ctx, data); err != nil {
return
}
return
}

View File

@@ -0,0 +1,143 @@
package service
import (
"bytes"
"context"
"fmt"
"math/rand"
"time"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
type taskBillMonthly struct {
dao *dao.Dao
rnd *rand.Rand
monthOffset int
namePrefix string
tl *taskLog
}
func (s *taskBillMonthly) Run() (err error) {
var (
ctx = context.Background()
finished bool
expectFN = func(ctx context.Context) (expect int64, err error) {
var (
beginTime, _ = monthRange(s.monthOffset)
monthVer = monthlyBillVer(beginTime)
)
if expect, err = s.dao.CountDailyBillByMonthVer(ctx, monthVer); err != nil {
return
}
return
}
)
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
return
}
return s.run(ctx)
}
func (s *taskBillMonthly) TTL() int32 {
return 3600 * 2
}
func (s *taskBillMonthly) Name() string {
return fmt.Sprintf("%s_%d", s.namePrefix, monthlyBillVer(time.Now()))
}
// 月账单生成
func (s *taskBillMonthly) run(ctx context.Context) (err error) {
ll := &dailyBillLLByMonthVer{
limit: 1000,
dao: s.dao,
}
beginTime, _ := monthRange(s.monthOffset)
ll.monthVer = monthlyBillVer(beginTime)
return runLimitedList(ctx, ll, time.Millisecond*2, s.runDailyBill)
}
func (s *taskBillMonthly) runDailyBill(ctx context.Context, ele interface{}) (err error) {
dailyBill, ok := ele.(*model.DailyBill)
if !ok {
return errors.Errorf("taskBillMonthly convert ele: %+v failed", dailyBill)
}
log.Info("taskBillMonthly start handle daily biil: %+v", dailyBill)
fn := func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
var (
monthlyBill *model.Bill
monthVer = dailyBill.MonthVer
monthlyBillLog *model.LogBillMonthly
)
affected = true
// 获得该 mid 的 daily_bill
if monthlyBill, err = s.dao.MonthlyBill(ctx, dailyBill.MID, model.BizAsset, model.CurrencyBP, monthVer); err != nil {
return
}
if monthlyBill == nil {
if monthlyBill, err = s.initMonthlyBill(ctx, dailyBill.MID, dailyBill.Biz, dailyBill.Currency, dailyBill.MonthVer); err != nil {
return
}
}
monthlyBillLog = &model.LogBillMonthly{
BillID: monthlyBill.BillID,
FromIn: monthlyBill.In,
ToIn: monthlyBill.In + dailyBill.In,
FromOut: monthlyBill.Out,
ToOut: monthlyBill.Out + dailyBill.Out,
BillUserDailyID: dailyBill.BillID,
}
monthlyBill.In += dailyBill.In
monthlyBill.Out += dailyBill.Out
// 添加 monthly bill log , uk : daily_bill_id
_, err = s.dao.TXInsertLogMonthlyBill(ctx, tx, monthlyBillLog)
if err != nil {
tx.Rollback()
return
}
// 更新 monthly bill
_, err = s.dao.TXUpdateMonthlyBill(ctx, tx, monthlyBill)
if err != nil {
tx.Rollback()
return
}
log.Info("taskBillMonthly: %+v,from daily bill: %+v", monthlyBill, dailyBill)
return
}
return runTXCASTaskWithLog(ctx, s, s.tl, fn)
}
func (s *taskBillMonthly) initMonthlyBill(ctx context.Context, mid int64, biz, currency string, ver int64) (data *model.Bill, err error) {
data = &model.Bill{
BillID: orderID(s.rnd),
MID: mid,
Biz: biz,
Currency: currency,
In: 0,
Out: 0,
Ver: ver,
Version: 1,
}
if data.ID, err = s.dao.InsertMonthlyBill(ctx, data); err != nil {
return
}
return
}
func orderID(rnd *rand.Rand) string {
var b bytes.Buffer
b.WriteString(fmt.Sprintf("%05d", rnd.Int63n(99999)))
b.WriteString(fmt.Sprintf("%03d", time.Now().UnixNano()/1e6%1000))
b.WriteString(time.Now().Format("060102150405"))
return b.String()
}

View File

@@ -0,0 +1,223 @@
package service
import (
"context"
"fmt"
"math/rand"
"time"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
"go-common/app/job/main/ugcpay/service/pay"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
// 结算贝壳任务
type taskRechargeShell struct {
dao *dao.Dao
pay *pay.Pay
rnd *rand.Rand
monthOffset int
namePrefix string
tl *taskLog
}
func (s *taskRechargeShell) Run() (err error) {
var (
ctx = context.Background()
finished bool
expectFN = func(ctx context.Context) (expect int64, err error) {
var (
beginTime, _ = monthRange(s.monthOffset)
monthVer = monthlyBillVer(beginTime)
)
if expect, err = s.dao.CountMonthlyBillByVer(ctx, monthVer); err != nil {
return
}
return
}
)
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
return
}
return s.run(ctx)
}
func (s *taskRechargeShell) TTL() int32 {
return 3600 * 2
}
func (s *taskRechargeShell) Name() string {
return fmt.Sprintf("%s_%d", s.namePrefix, monthlyBillVer(time.Now()))
}
func (s *taskRechargeShell) run(ctx context.Context) (err error) {
ll := &monthlyBillLL{
limit: 1000,
dao: s.dao,
}
beginTime, _ := monthRange(s.monthOffset)
ll.ver = monthlyBillVer(beginTime)
return runLimitedList(ctx, ll, time.Millisecond*2, s.runMonthlyBill)
}
func (s *taskRechargeShell) runMonthlyBill(ctx context.Context, ele interface{}) (err error) {
monthlyBill, ok := ele.(*model.Bill)
if !ok {
return errors.Errorf("runMonthlyBill convert ele: %+v failed", monthlyBill)
}
log.Info("taskRechargeShell start handle monthly bill: %+v", monthlyBill)
var fn func(ctx context.Context, tx *xsql.Tx) (affected bool, err error)
if monthlyBill.In-monthlyBill.Out > 0 {
fn = s.fnRechargeShell(ctx, monthlyBill)
} else {
fn = s.fnRecordRecharge(ctx, monthlyBill)
}
if err = runTXCASTaskWithLog(ctx, s, s.tl, fn); err != nil {
return
}
return
}
func (s *taskRechargeShell) fnRecordRecharge(ctx context.Context, monthlyBill *model.Bill) func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
return func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
affected = true
var (
readyRecharge = monthlyBill.In - monthlyBill.Out
)
// 记录贝壳订单
orderRechargeShell := &model.OrderRechargeShell{
MID: monthlyBill.MID,
OrderID: orderID(s.rnd),
Biz: model.BizAsset,
Amount: readyRecharge,
State: "finished",
Ver: monthlyBill.Ver,
}
var (
orderRechargeShellLog = &model.OrderRechargeShellLog{
OrderID: orderRechargeShell.OrderID,
FromState: "finished",
ToState: "finished",
BillUserMonthlyID: monthlyBill.BillID,
}
)
_, err = s.dao.TXInsertOrderRechargeShell(ctx, tx, orderRechargeShell)
if err != nil {
tx.Rollback()
return
}
// 插入 order_recharge_shell_log, uk: bill_monthly_bill_id
_, err = s.dao.TXInsertOrderRechargeShellLog(ctx, tx, orderRechargeShellLog)
if err != nil {
tx.Rollback()
return
}
log.Info("fnRecordRecharge : %+v, orderRechargeShell: %+v", monthlyBill, orderRechargeShell)
return
}
}
func (s *taskRechargeShell) fnRechargeShell(ctx context.Context, monthlyBill *model.Bill) func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
return func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
affected = true
var (
account *model.UserAccount
readyRecharge = monthlyBill.In - monthlyBill.Out
accountLog *model.AccountLog
)
// 获得该 mid 的 虚拟账户
if account, err = s.dao.UserAccount(ctx, monthlyBill.MID, model.BizAsset, model.CurrencyBP); err != nil {
return
}
if account == nil {
err = errors.Errorf("runMonthlyBill not found valid user_account, monthly_bill: %+v", monthlyBill)
return
}
// 检查虚拟账户余额是否足够
if account.Balance-readyRecharge < 0 {
err = errors.Errorf("runMonthlyBill failed, account.Balance - readyRecharge < 0 !!!! account: %+v, monthly bill: %+v", account, monthlyBill)
return
}
accountLog = &model.AccountLog{
AccountID: account.ID,
Name: s.Name(),
From: account.Balance,
To: account.Balance - readyRecharge,
Ver: account.Ver + 1,
State: model.AccountStateWithdraw,
}
account.Balance -= readyRecharge
// 扣减虚拟账户余额
rowAffected, err := s.dao.TXUpdateUserAccount(ctx, tx, account)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpdateUserAccount no affected user account: %+v", account)
affected = false
tx.Rollback()
return
}
err = s.dao.TXInsertUserAccountLog(ctx, tx, accountLog)
if err != nil {
tx.Rollback()
return
}
// 开始转贝壳
orderRechargeShell := &model.OrderRechargeShell{
MID: monthlyBill.MID,
OrderID: orderID(s.rnd),
Biz: model.BizAsset,
Amount: readyRecharge,
State: "created",
Ver: monthlyBill.Ver,
}
var (
orderRechargeShellLog = &model.OrderRechargeShellLog{
OrderID: orderRechargeShell.OrderID,
FromState: "created",
ToState: "created",
BillUserMonthlyID: monthlyBill.BillID,
}
)
_, err = s.dao.TXInsertOrderRechargeShell(ctx, tx, orderRechargeShell)
if err != nil {
tx.Rollback()
return
}
// 插入 order_recharge_shell_log, uk: bill_monthly_bill_id
_, err = s.dao.TXInsertOrderRechargeShellLog(ctx, tx, orderRechargeShellLog)
if err != nil {
tx.Rollback()
return
}
// 请求支付中心转贝壳
_, payJSON, err := s.pay.RechargeShell(orderRechargeShell.OrderID, orderRechargeShell.MID, orderRechargeShell.Amount, orderRechargeShell.Amount)
if err != nil {
tx.Rollback()
return
}
if err = s.dao.PayRechargeShell(ctx, payJSON); err != nil {
tx.Rollback()
return
}
log.Info("fnRechargeShell: %+v, account: %+v, orderRechargeShell: %+v", monthlyBill, account, orderRechargeShell)
return
}
}

View File

@@ -0,0 +1,62 @@
package service
import (
"context"
"time"
"go-common/app/job/main/ugcpay/conf"
"go-common/library/log"
)
func dayRange(offset int) (from, to time.Time) {
tmp := time.Now().AddDate(0, 0, offset)
from = time.Date(tmp.Year(), tmp.Month(), tmp.Day(), 0, 0, 0, 0, time.Local)
to = from.Add(24*time.Hour - 1)
return
}
func monthRange(offset int) (from, to time.Time) {
tmp := time.Now().AddDate(0, offset, 0)
from = time.Date(tmp.Year(), tmp.Month(), 1, 0, 0, 0, 0, time.Local)
to = from.AddDate(0, 1, 0).Add(-1)
return
}
func dailyBillVer(t time.Time) int64 {
// 2006-01-02 15:04:05
return int64(t.Year()*10000 + int(t.Month())*100 + t.Day())
}
func monthlyBillVer(t time.Time) int64 {
return int64(t.Year()*100 + int(t.Month()))
}
func runCAS(ctx context.Context, fn func(ctx context.Context) (effected bool, err error)) (err error) {
times := conf.Conf.Biz.RunCASTimes
if times <= 0 {
times = 2
}
effected := false
for times > 0 {
times--
if effected, err = fn(ctx); err != nil {
return
}
if effected {
return
}
}
if times <= 0 {
log.Error("runCAS failed!!!")
}
return
}
func calcAssetIncome(fee int64) (userIncome int64, bizIncome int64) {
if fee <= 0 {
return 0, 0
}
userIncome = int64((1.0 - conf.Conf.Biz.Tax.AssetRate) * float64(fee))
bizIncome = fee - userIncome
return
}