Create & Init Project...

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

View File

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

View File

@@ -0,0 +1,22 @@
# v1.2.0
1. 完善订单退费逻辑
2. 增加订单CAS更新机制
# v1.1.0
1. 增加订单退费逻辑
# v1.0.0
一期正式版本
# v0.2.0
Added
1. dao 实现
2. income HTTP接口
3. income GRPC接口
4. trade GRPC接口
# v0.1.0
Added
1. 服务基建
3. asset HTTP接口
4. asset GRPC接口

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:
- main
- service
- service/main/ugcpay
options:
no_parent_owners: true
reviewers:
- muyang
- zhaogangtao

View File

@@ -0,0 +1,13 @@
# ugcpay-service
# 项目简介
ugcpay 是 ugc 付费相关(elec充电、ugc内容付费平台
# 编译环境
go >= 1.9
# 依赖包
go-common
# 编译执行
go run ./cmd/main.go -t test.toml

View File

@@ -0,0 +1,57 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "v1_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "v1_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_grpc"],
importpath = "go-common/app/service/main/ugcpay/api/grpc/v1",
proto = ":v1_proto",
tags = ["automanaged"],
deps = ["@com_github_gogo_protobuf//gogoproto:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
embed = [":v1_go_proto"],
importpath = "go-common/app/service/main/ugcpay/api/grpc/v1",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/net/rpc/warden:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context: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"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,190 @@
syntax = "proto3";
package ugcpay.service.v1;
option go_package = "v1";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
// EmptyStruct填充返回值
message EmptyStruct{}
// Asset
message AssetRegisterReq {
int64 mid = 1 ;
// oid 内容ID
int64 oid = 2 ;
// otype 内容类型
string otype = 3 ;
// currency 货币类型
string currency = 4 ;
// price 注册价格(分)
int64 price = 5 ;
}
message AssetQueryReq {
// oid 内容ID
int64 oid = 1 ;
// otype 内容类型
string otype = 2 ;
// currency 货币类型
string currency = 3 ;
}
message AssetQueryResp {
// price 内容注册价格(分)
int64 price = 1 ;
// platform_price 平台价格(分)
map<string,int64> platform_price = 2 ;
}
message AssetRelationReq {
int64 mid = 1 ;
// oid 内容ID
int64 oid = 2 ;
// otype 内容类型
string otype = 3 ;
}
message AssetRelationResp {
// state 关系状态
string state = 1 ;
}
message AssetRelationDetailReq {
int64 mid = 1 ;
// oid 内容ID
int64 oid = 2 ;
// otype 内容类型
string otype = 3 ;
// currency 货币类型
string currency = 4 ;
}
message AssetRelationDetailResp {
// relation_state 关系状态
string relation_state = 1 ;
// asset_price 内容注册价格(分)
int64 asset_price = 2 ;
// asset_platform_price 平台价格(分)
map<string,int64> asset_platform_price = 3 ;
}
// Trade
message TradeCreateReq {
// platform 支付平台
string platform = 1 ;
int64 mid = 2 ;
// oid 内容ID
int64 oid = 3 ;
// otype 内容类型
string otype = 4 ;
// currency 货币类型
string currency = 5 ;
}
message TradeCreateResp {
// order_id 订单ID
string order_id = 1 ;
// pay_data 支付平台参数
string pay_data = 2 ;
}
message TradeOrderReq {
// id 订单号
string id = 1 ;
}
message TradeOrderResp {
// order_id 订单号
string order_id = 1 ;
int64 mid = 2 ;
// biz 业务渠道
string biz = 3 ;
// platform 支付平台
string platform = 4 ;
int64 oid = 5 ;
string otype = 6 ;
// fee 支付费用
int64 fee = 7 ;
// currency 货币类型
string currency = 8 ;
// pay_id 支付平台支付ID
string pay_id = 9 ;
// state 订单状态
string state = 10 ;
string reason = 11 ;
}
// Income
message IncomeUserAssetOverviewReq {
int64 mid = 1 ;
}
message IncomeUserAssetOverviewResp {
// total 累计收入
int64 total = 1 ;
// total_buy_times 累计购买次数
int64 total_buy_times = 2 ;
// month_new 新增月收入(待结算)
int64 month_new = 3 ;
// day_new 新增日收入(待结算)
int64 day_new = 4 ;
}
message IncomeUserAssetListReq {
int64 mid = 1 ;
// ver 月版本号,如:201809
int64 ver = 2 ;
int64 ps = 3 ;
int64 pn = 4;
}
message IncomeUserAssetListResp {
// list 内容维度月收入信息
repeated IncomeUserAsset list = 1 ;
Page page = 2 ;
}
message IncomeUserAsset {
int64 oid = 1 ;
string otype = 2 ;
string currency = 3 ;
int64 price = 4 ;
int64 total_buy_times = 5 ;
int64 new_buy_times = 6 ;
int64 total_err_times = 7;
int64 new_err_times = 8 ;
}
message Page {
int64 Num = 1 ;
int64 Size = 2 ;
int64 Total = 3 ;
}
service UGCPay {
// AssetRegister 内容注册
rpc AssetRegister(AssetRegisterReq) returns(EmptyStruct);
// AssetQuery 内容查询
rpc AssetQuery(AssetQueryReq) returns(AssetQueryResp);
// AssetRelation 内容关系查询
rpc AssetRelation(AssetRelationReq) returns(AssetRelationResp);
// AssetRelationDetail 内容关系详情
rpc AssetRelationDetail(AssetRelationDetailReq) returns(AssetRelationDetailResp);
// TradeCreate 支付创建
rpc TradeCreate(TradeCreateReq) returns(TradeCreateResp);
// TradeQuery 支付查询
rpc TradeQuery(TradeOrderReq) returns(TradeOrderResp);
// TradeCancel 支付取消
rpc TradeCancel(TradeOrderReq) returns(EmptyStruct);
// TradeConfirm 支付确认(请求支付平台)
rpc TradeConfirm(TradeOrderReq) returns(TradeOrderResp);
// TradeRefund 支付退款(原路返回)
rpc TradeRefund(TradeOrderReq) returns(EmptyStruct);
// IncomeUserAssetOverview 用户收入总览
rpc IncomeUserAssetOverview(IncomeUserAssetOverviewReq) returns(IncomeUserAssetOverviewResp);
// IncomeUserAssetList 用户资产维度收入
rpc IncomeUserAssetList(IncomeUserAssetListReq) returns(IncomeUserAssetListResp);
}

View File

@@ -0,0 +1,22 @@
package v1
import (
"context"
"google.golang.org/grpc"
"go-common/library/net/rpc/warden"
)
// AppID unique app id for service discovery
const AppID = "account.service.ugcpay"
// NewClient new identify grpc client
func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (UGCPayClient, error) {
client := warden.NewClient(cfg, opts...)
conn, err := client.Dial(context.Background(), "discovery://default/"+AppID)
if err != nil {
return nil, err
}
return NewUGCPayClient(conn), nil
}

View File

@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"asset.go",
"income.go",
"trade.go",
],
importpath = "go-common/app/service/main/ugcpay/api/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//app/service/main/ugcpay/model: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 @@
# HTTP API文档

View File

@@ -0,0 +1,61 @@
package http
import (
"go-common/app/service/main/ugcpay/model"
)
// ArgAssetRegister .
type ArgAssetRegister struct {
MID int64 `form:"mid" validate:"required"`
OID int64 `form:"oid" validate:"required"`
OType string `form:"otype" validate:"required"`
Price int64 `form:"price" validate:"required"`
Currency string `form:"currency" validate:"required"`
}
// ArgAssetQuery .
type ArgAssetQuery struct {
OID int64 `form:"oid" validate:"required"`
OType string `form:"otype" validate:"required"`
Currency string `form:"currency" validate:"required"`
}
// RespAssetQuery .
type RespAssetQuery struct {
Price int64 `json:"price"`
PlatformPrice map[string]int64 `json:"platform_price"`
}
// Parse .
func (r *RespAssetQuery) Parse(as *model.Asset, pp map[string]int64) {
if as == nil {
return
}
r.Price = as.Price
r.PlatformPrice = pp
}
// ArgAssetRelation .
type ArgAssetRelation struct {
MID int64 `form:"mid" validate:"required"`
OID int64 `form:"oid" validate:"required"`
OType string `form:"otype" validate:"required"`
}
// RespAssetRelation .
type RespAssetRelation struct {
State string `json:"state"`
}
// ArgAssetRelationDetail .
type ArgAssetRelationDetail struct {
ArgAssetRelation
Currency string `form:"currency" validate:"required"`
}
// RespAssetRelationDetail .
type RespAssetRelationDetail struct {
RelationState string `json:"relation_state"`
AssetPrice int64 `json:"asset_price"`
AssetPlatformPrice map[string]int64 `json:"asset_platform_price"`
}

View File

@@ -0,0 +1,37 @@
package http
// ArgIncomeAssetOverview .
type ArgIncomeAssetOverview struct {
MID int64 `form:"mid" validate:"required"`
}
// RespIncomeAssetOverview .
type RespIncomeAssetOverview struct {
Total int64 `json:"total"`
TotalBuyTimes int64 `json:"total_buy_times"`
MonthNew int64 `json:"month_new"`
DayNew int64 `json:"day_new"`
}
// ArgIncomeAssetMonthly .
type ArgIncomeAssetMonthly struct {
MID int64 `form:"mid" validate:"required"`
Ver string `form:"ver"`
}
// RespIncomeAssetMonthly .
type RespIncomeAssetMonthly struct {
List []*RespIncomeAssetMonthlyByContent `json:"list"`
}
// RespIncomeAssetMonthlyByContent .
type RespIncomeAssetMonthlyByContent struct {
OID int64 `json:"oid"`
OType string `json:"otype"`
Currency string `json:"currency"`
Price int64 `json:"price"`
TotalBuyTimes int64 `json:"total_buy_times"`
NewBuyTimes int64 `json:"new_buy_times"`
TotalErrTimes int64 `json:"total_err_times"`
NewErrTimes int64 `json:"new_err_times"`
}

View File

@@ -0,0 +1,17 @@
package http
// ArgTradeCallback .
type ArgTradeCallback struct {
MSGID int64 `form:"msgId" validate:"required"`
MSGContent string `form:"msgContent" validate:"required"`
}
// ArgTradeRefund .
type ArgTradeRefund struct {
OrderID string `form:"order_id" validate:"required"`
}
// ArgTradeRefunds .
type ArgTradeRefunds struct {
OrderIDs []string `form:"order_ids,split" validate:"required"`
}

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/service/main/ugcpay/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/ugcpay/conf:go_default_library",
"//app/service/main/ugcpay/server/http:go_default_library",
"//app/service/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,45 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/service/main/ugcpay/conf"
"go-common/app/service/main/ugcpay/server/http"
"go-common/app/service/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-service start")
ecode.Init(conf.Conf.Ecode)
svc := service.New(conf.Conf)
http.Init(conf.Conf, 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-service exit")
time.Sleep(time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,54 @@
[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"
readDSN = ["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 = ""
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
active = 50
idle = 10
idleTimeout = "10s"
[redis]
name = "ugcpay"
proto = "tcp"
addr = ""
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
active = 50
idle = 10
idleTimeout = "10s"
[cacheTTL]
OrderTTL = 1800
AssetTTL = 64800
AssetRelationTTL = 7200
AggrIncomeUserTTL = 7200
AggrIncomeUserMonthlyTTL = 7200
[biz]
RunCASTimes = 3
[biz.pay]
Token = "310f84dcc88ec8a0a9247f3b75b93614"
ID = "10017"
OrderTTL = 1800
URLQuery = "http://pay.bilibili.com/payplatform/pay/query"
URLCancel = "http://pay.bilibili.co/payplatform/pay/cancel"
URLPayCallback = "http://api.bilibili.co/x/internal/ugcpay/trade/pay/callback"
URLRefund = "http://pay.bilibili.co/payplatform/refund/request"
URLRefundCallback = "http://api.bilibili.co/x/internal/ugcpay/trade/pay/refund/callback"
[biz.price]
[biz.price.PlatformTax]
ios = 1.3

View File

@@ -0,0 +1,49 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/service/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/http/blademaster/middleware/verify: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"],
)
go_test(
name = "go_default_test",
srcs = ["conf_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//vendor/github.com/smartystreets/goconvey/convey:go_default_library"],
)

View File

@@ -0,0 +1,115 @@
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/http/blademaster/middleware/verify"
"github.com/BurntSushi/toml"
)
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config .
type Config struct {
Log *log.Config
BM *bm.ServerConfig
Verify *verify.Config
Redis *redis.Config
Memcache *memcache.Config
MySQL *sql.Config
Ecode *ecode.Config
CacheTTL *CacheTTL
Biz *Biz
}
// CacheTTL .
type CacheTTL struct {
OrderTTL int32
AssetTTL int32
AssetRelationTTL int32
AggrIncomeUserTTL int32
AggrIncomeUserMonthlyTTL int32
}
// Biz .
type Biz struct {
RunCASTimes int64
Pay struct {
ID string
Token string
OrderTTL int32
URLQuery string
URLRefund string
URLCancel string
URLPayCallback string
URLRefundCallback string
}
Price struct {
PlatformTax map[string]float64
}
}
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,16 @@
package conf
import (
"flag"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestConf(t *testing.T) {
Convey("", t, func() {
flag.Set("conf", "../cmd/test.toml")
err := Init()
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,75 @@
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.cache.go",
"dao.go",
"grpc.go",
"http.go",
"mc.cache.go",
"mysql.go",
"redis.go",
],
importpath = "go-common/app/service/main/ugcpay/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/archive/api:go_default_library",
"//app/service/main/ugcpay/conf:go_default_library",
"//app/service/main/ugcpay/model:go_default_library",
"//library/cache:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/stat/prom:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/golang.org/x/sync/singleflight: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.cache_test.go",
"dao_test.go",
"grpc_test.go",
"http_test.go",
"mc.cache_test.go",
"mysql_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/ugcpay/conf:go_default_library",
"//app/service/main/ugcpay/model:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,177 @@
// Code generated by $GOPATH/src/go-common/app/tool/cache/gen. DO NOT EDIT.
/*
Package dao is a generated cache proxy package.
It is generated from:
type _cache interface {
// cache: -nullcache=&model.Order{ID:-1} -check_null_code=$!=nil&&$.ID==-1
OrderUser(c context.Context, id int64) (*model.Order, error)
// cache: -nullcache=&model.Asset{ID:-1} -check_null_code=$!=nil&&$.ID==-1 -singleflight=true
Asset(c context.Context, oid int64, otype string, currency string) (*model.Asset, error)
// cache: -nullcache=&model.AggrIncomeUser{Total:-1} -check_null_code=$!=nil&&$.Total==-1
AggrIncomeUser(c context.Context, mid int64) (*model.AggrIncomeUser, error)
// cache: -nullcache=&model.AggrIncomeUserMonthly{TotalPayTimes:-1} -check_null_code=$!=nil&&$.TotalPayTimes==-1
AggrIncomeUserMonthly(c context.Context, mid int64, ver string) (*model.AggrIncomeUserMonthly, error)
}
*/
package dao
import (
"context"
"go-common/app/service/main/ugcpay/model"
"go-common/library/net/metadata"
"go-common/library/stat/prom"
"golang.org/x/sync/singleflight"
)
var _ _cache
var cacheSingleFlights = [1]*singleflight.Group{{}}
// OrderUser get data from cache if miss will call source method, then add to cache.
func (d *Dao) OrderUser(c context.Context, id string) (res *model.Order, err error) {
addCache := true
res, err = d.CacheOrderUser(c, id)
if err != nil {
addCache = false
err = nil
}
defer func() {
if res != nil && res.ID == -1 {
res = nil
}
}()
if res != nil {
prom.CacheHit.Incr("OrderUser")
return
}
prom.CacheMiss.Incr("OrderUser")
res, err = d.RawOrderUser(c, id)
if err != nil {
return
}
miss := res
if miss == nil {
miss = &model.Order{ID: -1}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheOrderUser(metadata.WithContext(c), id, miss)
})
return
}
// Asset get data from cache if miss will call source method, then add to cache.
func (d *Dao) Asset(c context.Context, id int64, otype string, currency string) (res *model.Asset, err error) {
addCache := true
res, err = d.CacheAsset(c, id, otype, currency)
if err != nil {
addCache = false
err = nil
}
defer func() {
if res != nil && res.ID == -1 {
res = nil
}
}()
if res != nil {
prom.CacheHit.Incr("Asset")
return
}
var rr interface{}
sf := d.cacheSFAsset(id, otype, currency)
rr, err, _ = cacheSingleFlights[0].Do(sf, func() (r interface{}, e error) {
prom.CacheMiss.Incr("Asset")
r, e = d.RawAsset(c, id, otype, currency)
return
})
res = rr.(*model.Asset)
if err != nil {
return
}
miss := res
if miss == nil {
miss = &model.Asset{ID: -1}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheAsset(metadata.WithContext(c), id, otype, currency, miss)
})
return
}
// // AggrIncomeUser get data from cache if miss will call source method, then add to cache.
// func (d *Dao) AggrIncomeUser(c context.Context, mid int64, currency string) (res *model.AggrIncomeUser, err error) {
// addCache := true
// res, err = d.CacheAggrIncomeUser(c, mid, currency)
// if err != nil {
// addCache = false
// err = nil
// }
// defer func() {
// if res != nil && res.MID == -1 {
// res = nil
// }
// }()
// if res != nil {
// prom.CacheHit.Incr("AggrIncomeUser")
// return
// }
// prom.CacheMiss.Incr("AggrIncomeUser")
// res, err = d.RawAggrIncomeUser(c, mid, currency)
// if err != nil {
// return
// }
// miss := res
// if miss == nil {
// miss = &model.AggrIncomeUser{MID: -1}
// }
// if !addCache {
// return
// }
// d.cache.Save(func() {
// d.AddCacheAggrIncomeUser(metadata.WithContext(c), mid, currency, miss)
// })
// return
// }
// // AggrIncomeUseAssetList get data from cache if miss will call source method, then add to cache.
// func (d *Dao) AggrIncomeUseAssetList(c context.Context, mid int64, currency string, ver int64) (res []*model.AggrIncomeUserAsset, err error) {
// addCache := true
// res, err = d.CacheAggrIncomeUserAssetList(c, mid, currency, ver)
// if err != nil {
// addCache = false
// err = nil
// }
// defer func() {
// if res != nil && len(res) == 0 {
// res = nil
// }
// }()
// if res != nil {
// prom.CacheHit.Incr("AggrIncomeUseAssetList")
// return
// }
// prom.CacheMiss.Incr("AggrIncomeUseAssetList")
// res, err = d.RawAggrIncomeUserAssetList(c, mid, currency, ver)
// if err != nil {
// return
// }
// miss := res
// if miss == nil {
// miss = make([]*model.AggrIncomeUserAsset, 0)
// }
// if !addCache {
// return
// }
// d.cache.Save(func() {
// d.AddCacheAggrIncomeUserAssetList(metadata.WithContext(c), mid, currency, ver, miss)
// })
// return
// }

View File

@@ -0,0 +1,79 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoOrderUser(t *testing.T) {
convey.Convey("OrderUser", t, func(ctx convey.C) {
var (
c = context.Background()
id = "56272730181109202542"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.OrderUser(c, id)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAsset(t *testing.T) {
convey.Convey("Asset", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(10110745)
otype = "archive"
currency = "bp"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelCacheAsset(c, id, otype, currency)
convey.So(err, convey.ShouldBeNil)
res, err := d.Asset(c, id, otype, currency)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
// func TestDaoAggrIncomeUser(t *testing.T) {
// convey.Convey("AggrIncomeUser", t, func(ctx convey.C) {
// var (
// c = context.Background()
// mid = int64(46333)
// cur = "bp"
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// res, err := d.AggrIncomeUser(c, mid, cur)
// ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
// }
// func TestDaoAggrIncomeUserMonthly(t *testing.T) {
// convey.Convey("AggrIncomeUserMonthly", t, func(ctx convey.C) {
// var (
// c = context.Background()
// id = int64(46333)
// ver = int64(201810)
// cur = "bp"
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// res, err := d.AggrIncomeUseAssetList(c, id, cur, ver)
// ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
// }

View File

@@ -0,0 +1,89 @@
package dao
import (
"context"
"fmt"
arcGRPC "go-common/app/service/main/archive/api"
"go-common/app/service/main/ugcpay/conf"
"go-common/app/service/main/ugcpay/model"
"go-common/library/cache"
"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
redis *redis.Pool
db *xsql.DB
cache *cache.Cache
archiveAPI arcGRPC.ArchiveClient
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
mc: memcache.NewPool(c.Memcache),
redis: redis.NewPool(c.Redis),
db: xsql.NewMySQL(c.MySQL),
cache: cache.New(10, 10240),
}
var err error
if dao.archiveAPI, err = arcGRPC.NewClient(nil); 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 d.db.Ping(c)
}
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 (d *Dao) cacheSFAsset(oid int64, otype string, currency string) string {
return fmt.Sprintf("up_a_sf_%d_%s_%s", oid, otype, currency)
}
//go:generate $GOPATH/src/go-common/app/tool/cache/mc
type _mc interface {
//mc: -key=orderKey -type=get
CacheOrderUser(c context.Context, id int64) (*model.Order, error)
//mc: -key=orderKey -expire=d.cacheTTL.OrderTTL
AddCacheOrderUser(c context.Context, id int64, value *model.Order) error
//mc: -key=orderKey
DelCacheOrderUser(c context.Context, id int64) error
//mc: -key=assetKey -type=get
CacheAsset(c context.Context, oid int64, otype string, currency string) (*model.Asset, error)
//mc: -key=assetKey -expire=d.cacheTTL.AssetTTL
AddCacheAsset(c context.Context, oid int64, otype string, currency string, value *model.Asset) error
//mc: -key=assetKey
DelCacheAsset(c context.Context, oid int64, otype string, currency string) error
}
//go:generate $GOPATH/src/go-common/app/tool/cache/gen
type _cache interface {
// cache: -nullcache=&model.Order{ID:-1} -check_null_code=$!=nil&&$.ID==-1
OrderUser(c context.Context, id int64) (*model.Order, error)
// cache: -nullcache=&model.Asset{ID:-1} -check_null_code=$!=nil&&$.ID==-1 -singleflight=true
Asset(c context.Context, oid int64, otype string, currency string) (*model.Asset, error)
}

View File

@@ -0,0 +1,43 @@
package dao
import (
"context"
"flag"
"go-common/app/service/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-service")
flag.Set("conf_token", "a3e98fbfb0f63520a8cdc699365460c2")
flag.Set("tree_id", "62221")
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)
conf.Conf.CacheTTL.OrderTTL = 1
conf.Conf.CacheTTL.AssetTTL = 1
conf.Conf.CacheTTL.AssetRelationTTL = 1
conf.Conf.CacheTTL.AggrIncomeUserTTL = 1
conf.Conf.CacheTTL.AggrIncomeUserMonthlyTTL = 1
if err := d.Ping(context.Background()); err != nil {
panic(err)
}
os.Exit(m.Run())
}

View File

@@ -0,0 +1,32 @@
package dao
import (
"context"
arc "go-common/app/service/main/archive/api"
"go-common/library/ecode"
)
// ArchiveUGCPay get archive ugcpay flag.
func (d *Dao) ArchiveUGCPay(ctx context.Context, aid int64) (pay bool, err error) {
var (
req = &arc.ArcRequest{
Aid: aid,
}
reply *arc.ArcReply
)
if reply, err = d.archiveAPI.Arc(ctx, req); err != nil {
if err == ecode.NothingFound {
err = nil
pay = false
return
}
return
}
if reply != nil && reply.Arc != nil && reply.Arc.Rights.UGCPay == 1 {
pay = true
} else {
pay = false
}
return
}

View File

@@ -0,0 +1,24 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoArchiveUGCPay(t *testing.T) {
convey.Convey("ArchiveUGCPay", t, func(ctx convey.C) {
var (
c = context.Background()
aid = int64(2333)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.ArchiveUGCPay(c, aid)
ctx.Convey("Then err should be nil.pay should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
// ctx.So(pay, convey.ShouldEqual, false)
})
})
})
}

View File

@@ -0,0 +1,111 @@
package dao
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"go-common/app/service/main/ugcpay/conf"
"go-common/app/service/main/ugcpay/model"
"go-common/library/ecode"
"go-common/library/log"
"github.com/pkg/errors"
)
// PayRefund 调用支付平台退款接口
func (d *Dao) PayRefund(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.URLRefund, dataJSON, resp); err != nil {
err = errors.WithStack(err)
return
}
if resp.Code != ecode.OK.Code() {
err = ecode.Int(resp.Code)
}
return
}
// PayCancel 调用支付平台订单取消接口
func (d *Dao) PayCancel(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.URLCancel, dataJSON, resp); err != nil {
err = errors.WithStack(err)
return
}
if resp.Code != ecode.OK.Code() {
err = ecode.Int(resp.Code)
}
return
}
// PayQuery 调用支付平台订单查询接口 return map[orderID]*model.PayOrder
func (d *Dao) PayQuery(c context.Context, dataJSON string) (orders map[string][]*model.PayOrder, err error) {
resp := new(struct {
Code int `json:"errno"`
Msg string `json:"msg"`
Data *model.PayQuery `json:"data"`
})
if err = d.paySend(c, conf.Conf.Biz.Pay.URLQuery, 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("PayQuery got nil data, resp: %+v", resp)
return
}
orders = make(map[string][]*model.PayOrder)
for _, o := range resp.Data.Orders {
orders[o.OrderID] = append(orders[o.OrderID], 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
)
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
}
log.Info("paySend call url: %s, body: %s, resp: %s", url, jsonData, bs)
if err = json.Unmarshal(bs, respData); err != nil {
err = errors.WithStack(err)
return
}
log.Info("paySend call url: %v, body: %s, resp: %+v", url, jsonData, respData)
return
}

View File

@@ -0,0 +1,55 @@
package dao
import (
"context"
"testing"
"go-common/library/ecode"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPayRefund(t *testing.T) {
convey.Convey("PayRefund", t, func(ctx convey.C) {
var (
c = context.Background()
dataJSON = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.PayRefund(c, dataJSON)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldEqual, ecode.Int(8004020001))
})
})
})
}
func TestDaoPayCancel(t *testing.T) {
convey.Convey("PayCancel", t, func(ctx convey.C) {
var (
c = context.Background()
dataJSON = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.PayCancel(c, dataJSON)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldEqual, ecode.Int(8004020001))
})
})
})
}
func TestDaoPayQuery(t *testing.T) {
convey.Convey("PayQuery", t, func(ctx convey.C) {
var (
c = context.Background()
dataJSON = `{"customerId":"10017","orderIds":"77546846181122123422","sign":"860d970710eac87650f221d0e0db6940","signType":"MD5","timestamp":"1542882768000","traceId":"1542882768117855000","version":"1.0"}`
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.PayQuery(c, dataJSON)
ctx.Convey("Then err should be nil.orders should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,132 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/main/ugcpay/conf"
"go-common/app/service/main/ugcpay/model"
"go-common/library/cache/memcache"
"go-common/library/log"
"go-common/library/stat/prom"
)
var _ _mc
// CacheOrderUser get data from mc
func (d *Dao) CacheOrderUser(c context.Context, id string) (res *model.Order, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := orderKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheOrderUser")
log.Errorv(c, log.KV("CacheOrderUser", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.Order{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheOrderUser")
log.Errorv(c, log.KV("CacheOrderUser", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheOrderUser Set data to mc
func (d *Dao) AddCacheOrderUser(c context.Context, id string, val *model.Order) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := orderKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: conf.Conf.CacheTTL.OrderTTL, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheOrderUser")
log.Errorv(c, log.KV("AddCacheOrderUser", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// DelCacheOrderUser delete data from mc
func (d *Dao) DelCacheOrderUser(c context.Context, id string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := orderKey(id)
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
}
// CacheAsset get data from mc
func (d *Dao) CacheAsset(c context.Context, id int64, otype string, currency string) (res *model.Asset, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := assetKey(id, otype, currency)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheAsset")
log.Errorv(c, log.KV("CacheAsset", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.Asset{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheAsset")
log.Errorv(c, log.KV("CacheAsset", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheAsset Set data to mc
func (d *Dao) AddCacheAsset(c context.Context, id int64, otype string, currency string, value *model.Asset) (err error) {
if value == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := assetKey(id, otype, currency)
item := &memcache.Item{Key: key, Object: value, Expiration: conf.Conf.CacheTTL.AssetTTL, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheAsset")
log.Errorv(c, log.KV("AddCacheAsset", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// DelCacheAsset delete data from mc
func (d *Dao) DelCacheAsset(c context.Context, id int64, otype string, currency string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := assetKey(id, 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
}

View File

@@ -0,0 +1,212 @@
package dao
import (
"context"
"go-common/app/service/main/ugcpay/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoAddCacheOrderUser(t *testing.T) {
convey.Convey("AddCacheOrderUser", t, func(ctx convey.C) {
var (
c = context.Background()
id = "test"
val = &model.Order{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCacheOrderUser(c, id, val)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoCacheOrderUser(t *testing.T) {
convey.Convey("CacheOrderUser", t, func(ctx convey.C) {
var (
c = context.Background()
id = "test"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.CacheOrderUser(c, id)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoDelCacheOrderUser(t *testing.T) {
convey.Convey("DelCacheOrderUser", t, func(ctx convey.C) {
var (
c = context.Background()
id = "test"
)
ctx.Convey("When everything goes 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 TestDaoAddCacheAsset(t *testing.T) {
convey.Convey("AddCacheAsset", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(233333)
otype = "archive"
currency = "bp"
value = &model.Asset{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCacheAsset(c, id, otype, currency, value)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoCacheAsset(t *testing.T) {
convey.Convey("CacheAsset", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(233333)
otype = "archive"
currency = "bp"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.CacheAsset(c, id, otype, currency)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoDelCacheAsset(t *testing.T) {
convey.Convey("DelCacheAsset", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(233333)
otype = "archive"
currency = "bp"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelCacheAsset(c, id, otype, currency)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
// func TestDaoCacheAggrIncomeUser(t *testing.T) {
// convey.Convey("CacheAggrIncomeUser", t, func(ctx convey.C) {
// var (
// c = context.Background()
// id = int64(46333)
// cur = "bp"
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// res, err := d.CacheAggrIncomeUser(c, id, cur)
// ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
// }
// func TestDaoAddCacheAggrIncomeUser(t *testing.T) {
// convey.Convey("AddCacheAggrIncomeUser", t, func(ctx convey.C) {
// var (
// c = context.Background()
// id = int64(46333)
// val = &model.AggrIncomeUser{}
// cur = "bp"
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// err := d.AddCacheAggrIncomeUser(c, id, cur, val)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
// }
// func TestDaoDelCacheAggrIncomeUser(t *testing.T) {
// convey.Convey("DelCacheAggrIncomeUser", t, func(ctx convey.C) {
// var (
// c = context.Background()
// id = int64(46333)
// cur = "bp"
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// err := d.DelCacheAggrIncomeUser(c, id, cur)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
// }
// func TestDaoCacheAggrIncomeUserMonthly(t *testing.T) {
// convey.Convey("CacheAggrIncomeUserMonthly", t, func(ctx convey.C) {
// var (
// c = context.Background()
// id = int64(46333)
// ver = int64(123)
// cur = "bp"
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// res, err := d.CacheAggrIncomeUserAssetList(c, id, cur, ver)
// ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
// }
// func TestDaoAddCacheAggrIncomeUserMonthly(t *testing.T) {
// convey.Convey("AddCacheAggrIncomeUserMonthly", t, func(ctx convey.C) {
// var (
// c = context.Background()
// id = int64(46333)
// ver = int64(123)
// cur = "bp"
// value = []*model.AggrIncomeUserAsset{}
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// err := d.AddCacheAggrIncomeUserAssetList(c, id, cur, ver, value)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
// }
// func TestDaoDelCacheAggrIncomeUserMonthly(t *testing.T) {
// convey.Convey("DelCacheAggrIncomeUserMonthly", t, func(ctx convey.C) {
// var (
// c = context.Background()
// id = int64(46333)
// ver = int64(123)
// cur = "bp"
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// err := d.DelCacheAggrIncomeUserAssetList(c, id, cur, ver)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
// }

View File

@@ -0,0 +1,293 @@
package dao
import (
"context"
"database/sql"
"go-common/app/service/main/ugcpay/model"
xsql "go-common/library/database/sql"
)
var (
_selectOrderUser = `SELECT id,order_id,mid,biz,platform,oid,otype,fee,real_fee,currency,pay_id,pay_reason,pay_time,state,ctime,mtime,refund_time,version FROM order_user WHERE order_id=? LIMIT 1`
_insertOrderUser = "INSERT INTO order_user (order_id,mid,biz,platform,oid,otype,fee,real_fee,currency,pay_id,pay_reason,pay_time,state,refund_time,version) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
_updateOrderUser = "UPDATE order_user SET order_id=?,mid=?,biz=?,platform=?,oid=?,otype=?,fee=?,real_fee=?,currency=?,pay_id=?,pay_reason=?,pay_time=?,state=?,refund_time=?,version=version+1 WHERE order_id=? AND version=?"
_insertLogOrderUser = "INSERT INTO log_order_user (order_id,from_state,to_state,`desc`) VALUES (?,?,?,?)"
_selectAsset = "SELECT id,mid,oid,otype,currency,price,state,ctime,mtime FROM asset WHERE oid=? AND otype=? AND currency=? LIMIT 1"
_upsertAsset = "INSERT INTO asset (mid,oid,otype,currency,price,state) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE mid=?,price=?,state=?"
_selectAssetRelation = "SELECT id,oid,otype,mid,state,ctime,mtime FROM asset_relation WHERE oid=? AND otype=? AND mid=? LIMIT 1"
_upsertAssetRelation = "INSERT INTO asset_relation (oid,otype,mid,state) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE state=?"
_selectBillUserDaily = "SELECT id,mid,biz,currency,`in`,`out`,ver,ctime,mtime,version FROM bill_user_daily WHERE mid=? AND biz=? AND currency=? AND ver=? LIMIT 1"
_selectBillUserDailyListByMonthVer = "SELECT id,mid,biz,currency,`in`,`out`,ver,ctime,mtime,version FROM bill_user_daily WHERE mid=? AND biz=? AND currency=? AND month_ver=?"
_selectAggrIncomeUser = "SELECT id,mid,currency,pay_success,pay_error,total_in,total_out,ctime,mtime FROM aggr_income_user WHERE mid=? AND currency=? LIMIT 1"
_selectAggrIncomeAssetList = "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=? ORDER BY oid DESC LIMIT ?"
_selectAggrIncomeAsset = "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"
_orderRechargeShell = "SELECT id,mid,order_id,biz,amount,pay_msg,state,`ver`,ctime,mtime FROM order_recharge_shell WHERE order_id=?"
_updateOrderRechargeShell = "UPDATE order_recharge_shell SET mid=?,order_id=?,biz=?,amount=?,pay_msg=?,state=?,`ver`=? WHERE order_id=?"
_insertOrderRechargeShellLog = "INSERT INTO log_order_recharge_shell (order_id,from_state,to_state,`desc`,bill_user_monthly_id) VALUES (?,?,?,?,?)"
)
// BeginTran begin transcation.
func (d *Dao) BeginTran(c context.Context) (tx *xsql.Tx, err error) {
return d.db.Begin(c)
}
// RawOrderUser get user order
func (d *Dao) RawOrderUser(ctx context.Context, id string) (data *model.Order, err error) {
data = &model.Order{}
row := d.db.Master().QueryRow(ctx, _selectOrderUser, id)
if err = row.Scan(&data.ID, &data.OrderID, &data.MID, &data.Biz, &data.Platform, &data.OID, &data.OType, &data.Fee, &data.RealFee, &data.Currency, &data.PayID, &data.PayReason, &data.PayTime, &data.State, &data.CTime, &data.MTime, &data.RefundTime, &data.Version); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
return
}
return
}
return
}
// InsertOrderUser is.
func (d *Dao) InsertOrderUser(ctx context.Context, data *model.Order) (id int64, err error) {
var (
res sql.Result
)
if res, err = d.db.Exec(ctx, _insertOrderUser, data.OrderID, data.MID, data.Biz, data.Platform, data.OID, data.OType, data.Fee, data.RealFee, data.Currency, data.PayID, data.PayReason, data.PayTime, data.State, data.RefundTime, data.Version); err != nil {
return
}
if id, err = res.LastInsertId(); err != nil {
return
}
return
}
// TXUpdateOrderUser .
func (d *Dao) TXUpdateOrderUser(ctx context.Context, tx *xsql.Tx, data *model.Order) (affected int64, err error) {
res, err := tx.Exec(_updateOrderUser, data.OrderID, data.MID, data.Biz, data.Platform, data.OID, data.OType, data.Fee, data.RealFee, data.Currency, data.PayID, data.PayReason, data.PayTime, data.State, data.RefundTime, data.OrderID, data.Version)
if err != nil {
return
}
affected, err = res.RowsAffected()
return
}
// TXInsertOrderUserLog .
func (d *Dao) TXInsertOrderUserLog(ctx context.Context, tx *xsql.Tx, data *model.LogOrder) (id int64, err error) {
var (
res sql.Result
)
if res, err = tx.Exec(_insertLogOrderUser, data.OrderID, data.FromState, data.ToState, data.Desc); err != nil {
return
}
if id, err = res.LastInsertId(); err != nil {
return
}
return
}
// RawAsset is
func (d *Dao) RawAsset(ctx context.Context, oid int64, otype string, currency string) (data *model.Asset, err error) {
data = &model.Asset{}
row := d.db.Master().QueryRow(ctx, _selectAsset, oid, otype, currency)
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
}
return
}
// UpsertAsset is
func (d *Dao) UpsertAsset(ctx context.Context, data *model.Asset) (err error) {
if _, err = d.db.Exec(ctx, _upsertAsset, data.MID, data.OID, data.OType, data.Currency, data.Price, data.State, data.MID, data.Price, data.State); err != nil {
return
}
return
}
// RawAssetRelation is
func (d *Dao) RawAssetRelation(ctx context.Context, mid int64, oid int64, otype string) (data *model.AssetRelation, err error) {
data = &model.AssetRelation{}
row := d.db.Master().QueryRow(ctx, _selectAssetRelation, oid, otype, mid)
if err = row.Scan(&data.ID, &data.OID, &data.OType, &data.MID, &data.State, &data.CTime, &data.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
return
}
return
}
return
}
// UpsertAssetRelation is
func (d *Dao) UpsertAssetRelation(ctx context.Context, data *model.AssetRelation) (rows int64, err error) {
var (
result sql.Result
)
if result, err = d.db.Exec(ctx, _upsertAssetRelation, data.OID, data.OType, data.MID, data.State, data.State); err != nil {
return
}
if rows, err = result.RowsAffected(); err != nil {
return
}
return
}
// TXUpsertAssetRelation is
func (d *Dao) TXUpsertAssetRelation(ctx context.Context, tx *xsql.Tx, data *model.AssetRelation) (rows int64, err error) {
var (
result sql.Result
)
if result, err = tx.Exec(_upsertAssetRelation, data.OID, data.OType, data.MID, data.State, data.State); err != nil {
return
}
if rows, err = result.RowsAffected(); err != nil {
return
}
return
}
// RawAggrIncomeUser is.
func (d *Dao) RawAggrIncomeUser(ctx context.Context, mid int64, currency string) (data *model.AggrIncomeUser, err error) {
data = &model.AggrIncomeUser{}
row := d.db.QueryRow(ctx, _selectAggrIncomeUser, mid, currency)
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
}
return
}
// RawAggrIncomeUserAssetList is.
func (d *Dao) RawAggrIncomeUserAssetList(ctx context.Context, mid int64, currency string, ver int64, limit int) (data []*model.AggrIncomeUserAsset, err error) {
var (
rows *xsql.Rows
)
if rows, err = d.db.Query(ctx, _selectAggrIncomeAssetList, mid, currency, ver, limit); err != nil {
return
}
defer rows.Close()
for rows.Next() {
var (
d = &model.AggrIncomeUserAsset{}
)
if err = rows.Scan(&d.ID, &d.MID, &d.Currency, &d.Ver, &d.OID, &d.OType, &d.PaySuccess, &d.PayError, &d.TotalIn, &d.TotalOut, &d.CTime, &d.MTime); err != nil {
return
}
data = append(data, d)
}
if err = rows.Err(); err != nil {
return
}
return
}
// RawAggrIncomeUserAsset .
func (d *Dao) RawAggrIncomeUserAsset(ctx context.Context, mid int64, currency string, oid int64, otype string, ver int64) (data *model.AggrIncomeUserAsset, err error) {
data = &model.AggrIncomeUserAsset{}
row := d.db.QueryRow(ctx, _selectAggrIncomeAsset, mid, currency, ver, oid, otype)
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
}
return
}
// RawBillUserDaily is.
func (d *Dao) RawBillUserDaily(ctx context.Context, mid int64, biz string, currency string, ver int64) (data *model.Bill, err error) {
data = &model.Bill{}
row := d.db.QueryRow(ctx, _selectBillUserDaily, mid, biz, currency, ver)
if err = row.Scan(&data.ID, &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
}
return
}
// RawBillUserDailyByMonthVer .
func (d *Dao) RawBillUserDailyByMonthVer(ctx context.Context, mid int64, biz string, currency string, monthVer int64) (datas []*model.Bill, err error) {
rows, err := d.db.Query(ctx, _selectBillUserDailyListByMonthVer, mid, biz, currency, monthVer)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
data := &model.Bill{}
if err = rows.Scan(&data.ID, &data.MID, &data.Biz, &data.Currency, &data.In, &data.Out, &data.Ver, &data.CTime, &data.MTime, &data.Version); err != nil {
return
}
datas = append(datas, data)
}
err = rows.Err()
return
}
// RawAccountUser is.
// func (d *Dao) RawAccountUser(ctx context.Context, mid int64, biz string, currency string) (data *model.AccountUser, err error) {
// data = &model.AccountUser{}
// row := d.db.QueryRow(ctx, _selectAccountUser, mid, biz, currency)
// if err = row.Scan(&data.ID, &data.Biz, &data.MID, &data.Currency, &data.Balance, &data.Ver, &data.State, &data.CTime, &data.MTime); err != nil {
// if err == xsql.ErrNoRows {
// err = nil
// data = nil
// return
// }
// return
// }
// return
// }
// RawOrderRechargeShell .
func (d *Dao) RawOrderRechargeShell(ctx context.Context, orderID string) (data *model.OrderRechargeShell, err error) {
data = &model.OrderRechargeShell{}
row := d.db.QueryRow(ctx, _orderRechargeShell, orderID)
if err = row.Scan(&data.ID, &data.MID, &data.OrderID, &data.Biz, &data.Amount, &data.PayMSG, &data.State, &data.Ver, &data.CTime, &data.MTime); err != nil {
if err == xsql.ErrNoRows {
err = nil
data = nil
return
}
return
}
return
}
// TXUpdateOrderRechargeShell .
func (d *Dao) TXUpdateOrderRechargeShell(ctx context.Context, tx *xsql.Tx, data *model.OrderRechargeShell) (err error) {
if _, err = tx.Exec(_updateOrderRechargeShell, data.MID, data.OrderID, data.Biz, data.Amount, data.PayMSG, data.State, data.Ver, data.OrderID); err != nil {
return
}
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
}

View File

@@ -0,0 +1,354 @@
package dao
import (
"context"
"fmt"
"strconv"
"time"
"testing"
"go-common/app/service/main/ugcpay/model"
xsql "go-common/library/database/sql"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoBeginTran(t *testing.T) {
convey.Convey("BeginTran", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
tx, err := d.BeginTran(c)
ctx.Convey("Then err should be nil.tx should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(tx, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoRawOrderUser(t *testing.T) {
convey.Convey("RawOrderUser", t, func(ctx convey.C) {
var (
c = context.Background()
id = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
data, err := d.RawOrderUser(c, id)
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 TestDaoInsertOrderUser(t *testing.T) {
convey.Convey("InsertOrderUser", t, func(ctx convey.C) {
var (
c = context.Background()
data = &model.Order{
OrderID: strconv.FormatInt(time.Now().Unix(), 10),
MID: 35858,
Biz: "asset",
Platform: "ios",
OID: 2333,
OType: "archive",
Fee: 1300,
Currency: "bp",
State: "created",
}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
id, err := d.InsertOrderUser(c, data)
ctx.Convey("Then err should be nil.id should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(id, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoRawAsset(t *testing.T) {
convey.Convey("RawAsset", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(2333)
otype = "archive"
currency = "bp"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
data, err := d.RawAsset(c, oid, otype, currency)
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 TestDaoUpsertAsset(t *testing.T) {
convey.Convey("UpsertAsset", t, func(ctx convey.C) {
var (
c = context.Background()
data = &model.Asset{
MID: 46333,
OID: 2333,
OType: "archive",
Currency: "bp",
Price: 1000,
State: "valid",
}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.UpsertAsset(c, data)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoRawAssetRelation(t *testing.T) {
convey.Convey("RawAssetRelation", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(35858)
oid = int64(2333)
otype = "archive"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
data, err := d.RawAssetRelation(c, mid, oid, otype)
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 TestDaoUpsertAssetRelation(t *testing.T) {
convey.Convey("UpsertAssetRelation", t, func(ctx convey.C) {
var (
c = context.Background()
data = &model.AssetRelation{
OID: 2333,
OType: "archive",
MID: 35858,
State: "paid",
}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.UpsertAssetRelation(c, data)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoRawAggrIncomeUser(t *testing.T) {
convey.Convey("RawAggrIncomeUser", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
cur = "bp"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
data, err := d.RawAggrIncomeUser(c, mid, cur)
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 TestDaoRawAggrIncomeUserMonthly(t *testing.T) {
convey.Convey("RawAggrIncomeUserMonthly", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
cur = "bp"
ver = int64(201809)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
data, err := d.RawAggrIncomeUserAssetList(c, mid, cur, ver, 1000)
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 TestDaoRawAggrIncomeUserAsset(t *testing.T) {
convey.Convey("RawAggrIncomeUserAsset", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
cur = "bp"
ver = int64(201809)
oid = int64(10110842)
otype = "archive"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
data, err := d.RawAggrIncomeUserAsset(c, mid, cur, oid, otype, ver)
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 TestDaoRawBillUserDaily(t *testing.T) {
convey.Convey("RawBillUserDaily", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
biz = "asset"
cur = "bp"
ver = int64(20181030)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
data, err := d.RawBillUserDaily(c, mid, biz, cur, ver)
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)
t.Log(data)
})
})
})
}
func TestDaoRawBillUserDailyListByMonthVer(t *testing.T) {
convey.Convey("RawBillUserDailyListByMonthVer", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
biz = "asset"
cur = "bp"
ver = int64(201811)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
datas, err := d.RawBillUserDailyByMonthVer(c, mid, biz, cur, ver)
ctx.Convey("Then err should be nil.data should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(datas, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTXInsertOrderUserLog(t *testing.T) {
convey.Convey("TestDaoTXInsertOrderUserLog", t, func(ctx convey.C) {
var (
c = context.Background()
log = &model.LogOrder{
OrderID: "test",
FromState: "ut1",
ToState: "ut2",
Desc: "hahah",
}
tx *xsql.Tx
)
tx, err := d.BeginTran(c)
convey.So(err, convey.ShouldBeNil)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
id, err := d.TXInsertOrderUserLog(c, tx, log)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(id, convey.ShouldBeGreaterThan, 0)
})
})
})
}
func TestDaoOrderRechargeShell(t *testing.T) {
convey.Convey("TestDaoOrderRechargeShell", t, func(ctx convey.C) {
var (
c = context.Background()
orderID = "23656019181109210220"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
data, err := d.RawOrderRechargeShell(c, orderID)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(data, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTXUpdateOrderRechargeShell(t *testing.T) {
convey.Convey("TestDaoTXOrderRechargeShell", t, func(ctx convey.C) {
var (
c = context.Background()
data = &model.OrderRechargeShell{
MID: 46333,
OrderID: "123",
Biz: "asset",
Amount: 1,
PayMSG: "ut test",
State: "test",
Ver: 201811,
}
)
tx, err := d.BeginTran(c)
convey.So(err, convey.ShouldBeNil)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.TXUpdateOrderRechargeShell(c, tx, data)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoTXInsertOrderRechargeShellLog(t *testing.T) {
convey.Convey("TestDaoTXInsertOrderRechargeShellLog", t, func(ctx convey.C) {
var (
c = context.Background()
data = &model.OrderRechargeShellLog{
OrderID: "233",
FromState: "ut1",
ToState: "ut2",
Desc: "ut test",
BillUserMonthlyID: fmt.Sprintf("ut_%d", time.Now().Unix()),
}
)
tx, err := d.BeginTran(c)
convey.So(err, convey.ShouldBeNil)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
id, err := d.TXInsertOrderRechargeShellLog(c, tx, data)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(id, convey.ShouldBeGreaterThan, 0)
})
})
})
}
func TestDaoTXUpdateOrderUser(t *testing.T) {
convey.Convey("TXUpdateOrderUser", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
data = &model.Order{
OrderID: "96841175181102170810",
State: "ut test",
}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.TXUpdateOrderUser(c, tx, data)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}

View File

@@ -0,0 +1,96 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/main/ugcpay/conf"
"go-common/library/cache/redis"
"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)
}
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("SET", "PING", "PONG"); err != nil {
err = errors.WithStack(err)
}
return
}
// CacheAssetRelationState get asset relation state.
func (d *Dao) CacheAssetRelationState(c context.Context, oid int64, otype string, mid int64) (state string, err error) {
var (
key = assetRelationKey(mid)
field = assetRelationField(oid, otype)
conn = d.redis.Get(c)
)
defer conn.Close()
if state, err = redis.String(conn.Do("HGET", key, field)); err != nil {
if err == redis.ErrNil {
err = nil
state = "miss"
return
}
err = errors.Wrapf(err, "conn.Do(HGET, %s ,%s)", key, field)
return
}
return
}
// AddCacheAssetRelationState set asset relation state.
func (d *Dao) AddCacheAssetRelationState(c context.Context, oid int64, otype string, mid int64, state string) (err error) {
var (
key = assetRelationKey(mid)
field = assetRelationField(oid, otype)
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("HSET", key, field, state); err != nil {
err = errors.Wrapf(err, "conn.Do(HSET, %s, %s, %s)", key, field, state)
return
}
if _, err = conn.Do("EXPIRE", key, conf.Conf.CacheTTL.AssetRelationTTL); err != nil {
err = errors.Wrapf(err, "conn.Do(EXPIRE, %s, %d)", key, conf.Conf.CacheTTL.AssetRelationTTL)
return
}
return
}
// 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
}
// DelCacheAssetRelation delete assetrelation.
func (d *Dao) DelCacheAssetRelation(c context.Context, mid int64) (err error) {
var (
key = assetRelationKey(mid)
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("DEL", key); err != nil {
err = errors.Wrapf(err, "conn.Do(DEL, %s)", key)
return
}
return
}

View File

@@ -0,0 +1,119 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoAddCacheAssetRelationState(t *testing.T) {
convey.Convey("AddCacheAssetRelationState", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(2233)
otype = "archive"
mid = int64(46333)
state = "paid"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCacheAssetRelationState(c, oid, otype, mid, state)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoassetRelationKey(t *testing.T) {
convey.Convey("assetRelationKey", t, func(ctx convey.C) {
var (
mid = int64(46333)
)
ctx.Convey("When everything goes 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(2233)
otype = "archive"
)
ctx.Convey("When everything goes 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 TestDaopingRedis(t *testing.T) {
convey.Convey("pingRedis", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.pingRedis(c)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoCacheAssetRelationState(t *testing.T) {
convey.Convey("CacheAssetRelationState", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(2233)
otype = "archive"
mid = int64(46333)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
state, err := d.CacheAssetRelationState(c, oid, otype, mid)
ctx.Convey("Then err should be nil.state should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(state, convey.ShouldEqual, "paid")
})
})
})
}
func TestDaoDelCacheAssetRelationState(t *testing.T) {
convey.Convey("DelCacheAssetRelationState", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(2233)
otype = "archive"
mid = int64(46333)
)
ctx.Convey("When everything goes 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)
})
})
})
}
func TestDaoDelCacheAssetRelation(t *testing.T) {
convey.Convey("DelCacheAssetRelation", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelCacheAssetRelation(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,38 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"account.go",
"aggr.go",
"asset.go",
"bill.go",
"log.go",
"model.go",
"pay.go",
"trade.go",
],
importpath = "go-common/app/service/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"],
)

View File

@@ -0,0 +1,56 @@
package model
import (
"time"
)
// AccountUser .
type AccountUser struct {
ID int64
Biz string
MID int64
Currency string
Balance int64
Ver int64
State string
CTime time.Time
MTime time.Time
}
// AccountBiz .
type AccountBiz struct {
ID int64
Biz string
Currency string
Balance int64
Ver int64
State 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,49 @@
package model
import (
"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
}
// AggrIncomeUserAssetList .
type AggrIncomeUserAssetList struct {
MID int64
Ver int64
Assets []*AggrIncomeUserAsset
Page *Page
}
// Page .
type Page struct {
Num int64
Size int64
Total int64
}

View File

@@ -0,0 +1,38 @@
package model
import (
"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
}
// PickPrice 获得平台相应的价格
func (a Asset) PickPrice(platform string, pp map[string]int64) (price int64) {
var ok bool
if price, ok = pp[platform]; ok {
return
}
return a.Price
}
// AssetRelation .
type AssetRelation struct {
ID int64
OID int64
OType string
MID int64
State string
CTime time.Time
MTime time.Time
}

View File

@@ -0,0 +1,19 @@
package model
import (
"time"
)
// Bill .
type Bill struct {
ID int64
MID int64
Biz string
Currency string
In int64
Out int64
Ver int64
Version int64
CTime time.Time
MTime time.Time
}

View File

@@ -0,0 +1,17 @@
package model
import (
"time"
)
// LogAccount .
type LogAccount struct {
ID int64
AccountID int64
From int64
To int64
Ver int64
State string
CTime time.Time
MTime time.Time
}

View File

@@ -0,0 +1,44 @@
package model
// 各种状态枚举
const (
StateValid = "valid"
StateInvalid = "invalid"
BizAsset = "asset"
BizElec = "elec"
AssetRelationPaid = "paid"
AssetRelationNone = "none"
AssetRelationMiss = "miss"
OTypeArchive = "archive"
OrderStateCreated = "created"
OrderStatePaying = "paying"
OrderStatePaid = "paid"
OrderStateFailed = "failed"
OrderStateClosed = "closed"
OrderStateExpired = "expired"
OrderStateSettled = "settled"
OrderStateRefunding = "refunding"
OrderStateRefunded = "refunded"
OrderStateSettledRefunding = "st_refunding"
OrderStateSettledRefunded = "st_refunded"
OrderStateRefundFinished = "ref_finished"
OrderStateDupRefunded = "dup_refunded"
OrderStateBadDebt = "bad_debt"
PayStatePaying = "PAYING"
PayStateOverdue = "OVERDUE"
PayStateClosed = "CLOSED"
PayStateFail = "FAIL"
PayStateSuccess = "SUCCESS"
PayStateFinished = "FINISHED"
PayStatePaySuccessAndCancel = "PAY_SUCCESS_AND_CANCEL"
PayStatePayCancel = "PAY_CANCEL"
PayStateRefund = "REFUND_SUCCESS"
RechargeShellSuccess = "success"
RechargeShellFail = "fail"
)

View File

@@ -0,0 +1,73 @@
package model
// PayCallbackMSG .
type PayCallbackMSG struct {
CustomerID int64 `json:"customerId"` //业务id
ServiceType int64 `json:"serviceType"` //业务方业务类型
TXID int64 `json:"txId"` //支付平台支付id
OrderID string `json:"orderId"` //业务方订单id
DeviceType int64 `json:"deviceType"` //支付设备渠道类型, 1 pc 2 webapp 3 app 4jsapi 5 server 6小程序支付 7聚合二维码支付
PayStatus string `json:"payStatus"` //支付状态FINISHED交易成功|SUCCESS成功|REFUND(退款中)|PAYING支付中|CLOSED关闭|NOT_PAY未支付|FAIL(支付失败)|WITHDRAW(支付撤销) 支付回调暂时仅通知 SUCCESS, 其他状态不通知。
PayChannelID int64 `json:"payChannelId"` //支付渠道id, 用户实际选择的支付实体渠道。(payChannel 代表笼统的微信、支付宝等第三方渠道, payChannelId 代表实际签约的实体渠道 id)
PayChannel string `json:"payChannel"` //支付渠道alipay(支付宝)、wechat(微信) ,paypal(paypal), iap(In App Purchase)、qpay(QQ支付)、huabei(花呗支付)、ali_bank网银支付、bocom交行信用卡支付、bpB币支付
PayChannelName string `json:"payChannelName"` //支付渠道名称 如支付宝、微信、PayPal、IAP、QQ、花呗、网银支付、B币支付
PayAccount string `json:"payAccount"` //支付渠道账号
PayBank string `json:"payBank"` //支付银行
FeeType string `json:"feeType"` //货币类型默认人民币CNY
PayAmount int64 `json:"payAmount"` //实际支付金额
PayMsgContent string `json:"payMsgContent"` //支付返回的额外信息json格式字符串比如payCounponAmount使用B币券金额单位 分payBpAmountB币金额
ExtData string `json:"extData"` //支付请求时的扩展json串
ExpiredTime int64 `json:"expiredTime"` //IAP代扣过期时间毫秒值,业务方需要判断expiredTime的值因为重复通知返回的expiredTime是一样的
OrderPayTime string `json:"orderPayTime"` //订单支付时间格式0000-00-00 00:00:00
Timestamp string `json:"timestamp"` //请求时间戳,毫秒
TraceID string `json:"traceId"` //追踪id
SignType string `json:"signType"` //签名类型 默认MD5
Sign string `json:"sign"` //签名(应当支持支付平台这边新增返回字段)
}
// IsSuccess 是否为成功支付内容
func (p *PayCallbackMSG) IsSuccess() bool {
return p.PayStatus == PayStateFinished || p.PayStatus == PayStateSuccess
}
// PayRefundCallbackMSG .
type PayRefundCallbackMSG struct {
CustomerID int64 `json:"customerId"` //业务id
OrderID string `json:"orderId"` //业务方订单id
TXID int64 `json:"txId"` //支付平台支付id
List []PayRefundCallbackEle `json:"batchRefundList"` //
}
// PayRefundCallbackEle .
type PayRefundCallbackEle struct {
RefundStatus string `json:"refundStatus"`
RefundStatusDesc string `json:"refundStatusDesc"`
RefundEndTime string `json:"refundEndTime"`
}
// IsSuccess 是否为成功退款内容
func (p *PayRefundCallbackEle) IsSuccess() bool {
return p.RefundStatus == PayStateRefund
}
// RechargeShellCallbackMSG .
type RechargeShellCallbackMSG struct {
CustomerID int64 `json:"customerId"`
Status string `json:"status"`
ThirdOrderNo string `json:"thirdOrderNo"`
MID int64 `json:"mid"`
}
// 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,138 @@
package model
import (
"time"
"go-common/library/log"
)
// LogOrder .
type LogOrder struct {
ID int64
OrderID string
FromState string
ToState string
Desc 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
Version int64
State string // created 已创建, paying 支付中, paid 已支付, failed 支付失败, closed 已关闭, expired 已超时, finished 已完成(支付成功且对账成功)
CTime time.Time
MTime time.Time
}
// IsPay 是否已支付
func (o Order) IsPay() bool {
return o.State == OrderStatePaid || o.State == OrderStateSettled
}
// IsRefunding 是否为退款中
func (o Order) IsRefunding() bool {
return o.State == OrderStateRefunding
}
// ParsePaidTime 解析支付成功时间
func (o *Order) ParsePaidTime(t string) {
o.PayTime = o.parsePayTime(t)
}
// ParseRefundedTime 解析退款成功时间
func (o *Order) ParseRefundedTime(t string) {
o.RefundTime = o.parsePayTime(t)
}
func (o Order) parsePayTime(tstr string) (t time.Time) {
var err error
if t, err = time.ParseInLocation("2006-01-02 15:04:05", tstr, time.Local); err != nil {
log.Error("Order parse pay time from timestr: %s, err: %+v", t, err)
t = time.Now() // 兜底
}
return
}
// ReturnState 返回前端显示state重复下单的退费状态被认为是已付费
func (o Order) ReturnState() string {
if o.State == OrderStateDupRefunded {
return OrderStatePaid
}
return o.State
}
// UpdateState 更新状态return true 更新成功false 更新失败(无需更新)
func (o *Order) UpdateState(state string) (changed bool) {
switch o.State { // 当前state
case OrderStateCreated, OrderStatePaying:
switch state {
case OrderStatePaying, OrderStatePaid, OrderStateFailed, OrderStateClosed, OrderStateExpired, OrderStateDupRefunded:
changed = true
}
case OrderStatePaid:
if state == OrderStateSettled || state == OrderStateRefunding || state == OrderStateDupRefunded || state == OrderStateRefunded || state == OrderStateBadDebt {
changed = true
}
case OrderStateFailed, OrderStateClosed, OrderStateExpired:
if state == OrderStatePaid {
changed = true
}
case OrderStateRefunding:
if state == OrderStateRefunded {
changed = true
}
case OrderStateSettled:
if state == OrderStateSettledRefunding || state == OrderStateSettledRefunded {
changed = true
}
case OrderStateSettledRefunding:
if state == OrderStateSettledRefunded {
changed = true
}
case OrderStateSettledRefunded:
if state == OrderStateRefundFinished {
changed = true
}
}
if changed {
o.State = state
} else {
log.Error("order update state from : %s , to : %s", o.State, state)
}
return
}
// OrderRefund .
type OrderRefund struct {
ID int64
OrderID int64
State string
CTime time.Time
MTime time.Time
}
// OrderBadDebt .
type OrderBadDebt struct {
ID int64
OrderID int64
Type string
State string
CTime time.Time
MTime time.Time
}

View File

@@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["asset.go"],
importpath = "go-common/app/service/main/ugcpay/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/ugcpay/api/grpc/v1:go_default_library",
"//app/service/main/ugcpay/model:go_default_library",
"//app/service/main/ugcpay/service:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/net/rpc/warden:go_default_library",
"@org_golang_google_grpc//: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,307 @@
package grpc
import (
"context"
"fmt"
"go-common/app/service/main/ugcpay/api/grpc/v1"
"go-common/app/service/main/ugcpay/model"
"go-common/app/service/main/ugcpay/service"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/net/rpc/warden"
"google.golang.org/grpc"
)
// New Identify warden rpc server
func New(cfg *warden.ServerConfig, s *service.Service) *warden.Server {
w := warden.NewServer(cfg)
w.Use(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
if resp, err = handler(ctx, req); err == nil {
log.Infov(ctx,
log.KV("path", info.FullMethod),
log.KV("caller", metadata.String(ctx, metadata.Caller)),
log.KV("remote_ip", metadata.String(ctx, metadata.RemoteIP)),
log.KV("args", fmt.Sprintf("%s", req)),
log.KV("retVal", fmt.Sprintf("%s", resp)))
}
return
})
v1.RegisterUGCPayServer(w.Server(), &UGCPayServer{s})
ws, err := w.Start()
if err != nil {
panic(err)
}
return ws
}
// UGCPayServer .
type UGCPayServer struct {
svr *service.Service
}
var _ v1.UGCPayServer = &UGCPayServer{}
// AssetRegister .
func (u *UGCPayServer) AssetRegister(ctx context.Context, req *v1.AssetRegisterReq) (*v1.EmptyStruct, error) {
err := u.svr.AssetRegister(ctx, req.Mid, req.Oid, req.Otype, req.Currency, req.Price)
if err != nil {
return nil, err
}
return &v1.EmptyStruct{}, nil
}
// AssetQuery .
func (u *UGCPayServer) AssetQuery(ctx context.Context, req *v1.AssetQueryReq) (*v1.AssetQueryResp, error) {
res, pp, err := u.svr.AssetQuery(ctx, req.Oid, req.Otype, req.Currency)
if err != nil {
return nil, err
}
return &v1.AssetQueryResp{
Price: res.Price,
PlatformPrice: pp,
}, nil
}
// AssetRelation .
func (u *UGCPayServer) AssetRelation(ctx context.Context, req *v1.AssetRelationReq) (*v1.AssetRelationResp, error) {
res, err := u.svr.AssetRelation(ctx, req.Mid, req.Oid, req.Otype)
if err != nil {
return nil, err
}
return &v1.AssetRelationResp{
State: res,
}, nil
}
// AssetRelationDetail .
func (u *UGCPayServer) AssetRelationDetail(ctx context.Context, req *v1.AssetRelationDetailReq) (*v1.AssetRelationDetailResp, error) {
state, err := u.svr.AssetRelation(ctx, req.Mid, req.Oid, req.Otype)
if err != nil {
return nil, err
}
res, pp, err := u.svr.AssetQuery(ctx, req.Oid, req.Otype, req.Currency)
if err != nil {
return nil, err
}
return &v1.AssetRelationDetailResp{
RelationState: state,
AssetPrice: res.Price,
AssetPlatformPrice: pp,
}, nil
}
// TradeCreate .
func (u *UGCPayServer) TradeCreate(ctx context.Context, req *v1.TradeCreateReq) (*v1.TradeCreateResp, error) {
orderID, payData, err := u.svr.TradeCreate(ctx, req.Platform, req.Mid, req.Oid, req.Otype, req.Currency)
if err != nil {
return nil, err
}
return &v1.TradeCreateResp{
OrderId: orderID,
PayData: payData,
}, nil
}
// TradeQuery .
func (u *UGCPayServer) TradeQuery(ctx context.Context, req *v1.TradeOrderReq) (*v1.TradeOrderResp, error) {
order, err := u.svr.TradeQuery(ctx, req.Id)
if err != nil {
return nil, err
}
order.State = order.ReturnState()
return &v1.TradeOrderResp{
OrderId: order.OrderID,
Mid: order.MID,
Biz: order.Biz,
Platform: order.Platform,
Oid: order.OID,
Otype: order.OType,
Fee: order.Fee,
Currency: order.Currency,
PayId: order.PayID,
State: order.State,
Reason: order.PayReason,
}, nil
}
// TradeCancel .
func (u *UGCPayServer) TradeCancel(ctx context.Context, req *v1.TradeOrderReq) (*v1.EmptyStruct, error) {
err := u.svr.TradeCancel(ctx, req.Id)
if err != nil {
return nil, err
}
return &v1.EmptyStruct{}, nil
}
// TradeConfirm .
func (u *UGCPayServer) TradeConfirm(ctx context.Context, req *v1.TradeOrderReq) (*v1.TradeOrderResp, error) {
order, err := u.svr.TradeConfirm(ctx, req.Id)
if err != nil {
return nil, err
}
order.State = order.ReturnState()
return &v1.TradeOrderResp{
OrderId: order.OrderID,
Mid: order.MID,
Biz: order.Biz,
Platform: order.Platform,
Oid: order.OID,
Otype: order.OType,
Fee: order.Fee,
Currency: order.Currency,
PayId: order.PayID,
State: order.State,
Reason: order.PayReason,
}, nil
}
// TradeRefund .
func (u *UGCPayServer) TradeRefund(ctx context.Context, req *v1.TradeOrderReq) (*v1.EmptyStruct, error) {
err := u.svr.TradeRefund(ctx, req.Id)
if err != nil {
return nil, err
}
return &v1.EmptyStruct{}, nil
}
// IncomeUserAssetOverview .
func (u *UGCPayServer) IncomeUserAssetOverview(ctx context.Context, req *v1.IncomeUserAssetOverviewReq) (*v1.IncomeUserAssetOverviewResp, error) {
overview, monthReady, newDailyBill, err := u.svr.IncomeUserAssetOverview(ctx, req.Mid, "bp")
if err != nil {
return nil, err
}
// 总计收入
resp := &v1.IncomeUserAssetOverviewResp{}
if overview != nil {
resp.Total = overview.TotalIn - overview.TotalOut
resp.TotalBuyTimes = overview.PaySuccess - overview.PayError
}
// 本月新增收入
resp.MonthNew = monthReady
// 前日新增收入
if newDailyBill != nil {
resp.DayNew = newDailyBill.In - newDailyBill.Out
}
log.Info("IncomeUserAssetOverview grpc resp: %+v", resp)
return resp, nil
}
// IncomeUserAssetList .
func (u *UGCPayServer) IncomeUserAssetList(ctx context.Context, req *v1.IncomeUserAssetListReq) (resp *v1.IncomeUserAssetListResp, err error) {
// 月度收入
if req.Ver > 0 {
return u.incomeUserAssetListByVer(ctx, req.Mid, "bp", req.Ver, req.Pn, req.Ps)
}
// 总计收入
return u.incomeUserAssetListByAll(ctx, req.Mid, "bp", req.Pn, req.Ps)
}
func (u *UGCPayServer) incomeUserAssetListByAll(ctx context.Context, mid int64, currency string, pn, ps int64) (resp *v1.IncomeUserAssetListResp, err error) {
var (
allList *model.AggrIncomeUserAssetList
allPage *model.Page
)
// 获得总计收入
if allList, allPage, err = u.svr.IncomeUserAssetList(ctx, mid, currency, 0, pn, ps); err != nil {
return
}
// 查询 asset price
assetPriceMap := make(map[string]int64) // assetPriceMap map[assetKey]price
for _, a := range allList.Assets {
as, _, err := u.svr.AssetQuery(ctx, a.OID, a.OType, a.Currency)
if err != nil {
log.Error("IncomeUserAssetList found invalid asset oid: %d, otype: %s, err: %+v", a.OID, a.OType, err)
err = nil
continue
}
assetPriceMap[assetKey(a.OID, a.OType, a.Currency)] = as.Price
}
// 写入返回值
resp = &v1.IncomeUserAssetListResp{
Page: &v1.Page{
Num: allPage.Num,
Size_: allPage.Size,
Total: allPage.Total,
},
}
for _, a := range allList.Assets {
asset := &v1.IncomeUserAsset{
Oid: a.OID,
Otype: a.OType,
Currency: a.Currency,
Price: assetPriceMap[assetKey(a.OID, a.OType, a.Currency)],
TotalBuyTimes: a.PaySuccess,
NewBuyTimes: 0,
TotalErrTimes: a.PayError,
NewErrTimes: 0,
}
resp.List = append(resp.List, asset)
}
return resp, nil
}
func (u *UGCPayServer) incomeUserAssetListByVer(ctx context.Context, mid int64, currency string, ver int64, pn, ps int64) (resp *v1.IncomeUserAssetListResp, err error) {
var (
monthList *model.AggrIncomeUserAssetList
monthPage *model.Page
)
// 获得月份收入
if monthList, monthPage, err = u.svr.IncomeUserAssetList(ctx, mid, currency, ver, pn, ps); err != nil {
return
}
// 查询 asset price
assetPriceMap := make(map[string]int64) // assetPriceMap map[assetKey]price
for _, a := range monthList.Assets {
as, _, err := u.svr.AssetQuery(ctx, a.OID, a.OType, a.Currency)
if err != nil {
log.Error("IncomeUserAssetList found invalid asset oid: %d, otype: %s, err: %+v", a.OID, a.OType, err)
err = nil
continue
}
assetPriceMap[assetKey(a.OID, a.OType, a.Currency)] = as.Price
}
// 写入返回值
resp = &v1.IncomeUserAssetListResp{
Page: &v1.Page{
Num: monthPage.Num,
Size_: monthPage.Size,
Total: monthPage.Total,
},
}
for _, a := range monthList.Assets {
var (
asset = &v1.IncomeUserAsset{
Oid: a.OID,
Otype: a.OType,
Currency: a.Currency,
Price: 0,
TotalBuyTimes: 0,
NewBuyTimes: a.PaySuccess,
TotalErrTimes: 0,
NewErrTimes: a.PayError,
}
)
asset.Price = assetPriceMap[assetKey(a.OID, a.OType, a.Currency)]
allAsset, err := u.svr.IncomeUserAsset(ctx, mid, a.OID, a.OType, a.Currency, 0)
if err != nil {
log.Error("u.svr.IncomeUserAsset mid: %d, oid: %d, otype: %s, currency: %s, err: %+v", mid, a.OID, a.OType, a.Currency, err)
err = nil
continue
}
if allAsset == nil {
log.Error("u.svr.IncomeUserAsset got nil asset, mid: %d, oid: %d, otype: %s, currency: %s", mid, a.OID, a.OType, a.Currency)
err = nil
continue
}
asset.TotalBuyTimes = allAsset.PaySuccess
asset.TotalErrTimes = allAsset.PayError
resp.List = append(resp.List, asset)
}
return
}
func assetKey(oid int64, otype, currency string) string {
return fmt.Sprintf("%d_%s_%s", oid, otype, currency)
}

View File

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

View File

@@ -0,0 +1,77 @@
package http
import (
api "go-common/app/service/main/ugcpay/api/http"
"go-common/app/service/main/ugcpay/model"
bm "go-common/library/net/http/blademaster"
)
func assetQuery(ctx *bm.Context) {
var (
err error
arg = &api.ArgAssetQuery{}
resp = &api.RespAssetQuery{}
asset *model.Asset
pp map[string]int64
)
if err = ctx.Bind(arg); err != nil {
return
}
if asset, pp, err = srv.AssetQuery(ctx, arg.OID, arg.OType, arg.Currency); err != nil {
ctx.JSON(nil, err)
return
}
resp.Parse(asset, pp)
ctx.JSON(resp, err)
}
func assetRegister(ctx *bm.Context) {
var (
err error
arg = &api.ArgAssetRegister{}
)
if err = ctx.Bind(arg); err != nil {
return
}
ctx.JSON(nil, srv.AssetRegister(ctx, arg.MID, arg.OID, arg.OType, arg.Currency, arg.Price))
}
func assetRelation(ctx *bm.Context) {
var (
err error
arg = &api.ArgAssetRelation{}
resp = &api.RespAssetRelation{}
state string
)
if err = ctx.Bind(arg); err != nil {
return
}
if state, err = srv.AssetRelation(ctx, arg.MID, arg.OID, arg.OType); err != nil {
ctx.JSON(nil, err)
return
}
resp.State = state
ctx.JSON(resp, err)
}
func assetRelationDetail(ctx *bm.Context) {
var (
err error
arg = &api.ArgAssetRelationDetail{}
resp = &api.RespAssetRelationDetail{}
asset *model.Asset
)
if err = ctx.Bind(arg); err != nil {
return
}
if resp.RelationState, err = srv.AssetRelation(ctx, arg.MID, arg.OID, arg.OType); err != nil {
ctx.JSON(nil, err)
return
}
if asset, resp.AssetPlatformPrice, err = srv.AssetQuery(ctx, arg.OID, arg.OType, arg.Currency); err != nil {
ctx.JSON(nil, err)
return
}
resp.AssetPrice = asset.Price
ctx.JSON(resp, err)
}

View File

@@ -0,0 +1,56 @@
package http
import (
"fmt"
"go-common/app/service/main/ugcpay/conf"
"go-common/app/service/main/ugcpay/server/grpc"
"go-common/app/service/main/ugcpay/service"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
srv *service.Service
vfy *verify.Verify
)
// Init init
func Init(c *conf.Config, s *service.Service) {
srv = s
vfy = verify.New(c.Verify)
grpc.New(nil, srv)
engine := bm.DefaultServer(c.BM)
route(engine)
if err := engine.Start(); err != nil {
panic(fmt.Sprintf("BM start err(%+v)", err))
}
}
func route(e *bm.Engine) {
e.Ping(ping)
g := e.Group("/x/internal/ugcpay", vfy.Verify)
{
g1 := g.Group("/asset")
{
g1.GET("", assetQuery)
g1.POST("/register", assetRegister)
g1.GET("/relation", assetRelation)
g1.GET("/relation/detail", assetRelationDetail)
}
}
g = e.Group("/x/internal/ugcpay")
{
g1 := g.Group("/trade")
{
g1.POST("refund", vfy.Verify, tradePayRefund)
g1.POST("refunds", vfy.Verify, tradePayRefunds)
g1.GET("/pay/callback", tradePayCallback)
g1.GET("/pay/refund/callback", tradePayRefundCallback)
g1.GET("/pay/recharge/callback", tradePayRechargeCallback)
}
}
}
func ping(c *bm.Context) {
}

View File

@@ -0,0 +1,83 @@
package http
import (
"net/http"
api "go-common/app/service/main/ugcpay/api/http"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
func tradePayRefund(ctx *bm.Context) {
var (
err error
arg = &api.ArgTradeRefund{}
)
if err = ctx.Bind(arg); err != nil {
return
}
ctx.JSON(nil, srv.TradeRefund(ctx, arg.OrderID))
}
func tradePayRefunds(ctx *bm.Context) {
var (
err error
arg = &api.ArgTradeRefunds{}
)
if err = ctx.Bind(arg); err != nil {
return
}
if len(arg.OrderIDs) > 20 {
err = ecode.RequestErr
return
}
ctx.JSON(nil, srv.TradeRefunds(ctx, arg.OrderIDs))
}
func tradePayCallback(ctx *bm.Context) {
var (
err error
arg = &api.ArgTradeCallback{}
retMSG string
)
if err = ctx.Bind(arg); err != nil {
return
}
if retMSG, err = srv.TradePayCallback(ctx, arg.MSGID, arg.MSGContent); err != nil {
ctx.JSON(nil, err)
return
}
ctx.String(http.StatusOK, retMSG)
}
func tradePayRefundCallback(ctx *bm.Context) {
var (
err error
arg = &api.ArgTradeCallback{}
retMSG string
)
if err = ctx.Bind(arg); err != nil {
return
}
if retMSG, err = srv.TradeRefundCallback(ctx, arg.MSGID, arg.MSGContent); err != nil {
ctx.JSON(nil, err)
return
}
ctx.String(http.StatusOK, retMSG)
}
func tradePayRechargeCallback(ctx *bm.Context) {
var (
err error
arg = &api.ArgTradeCallback{}
retMSG string
)
if err = ctx.Bind(arg); err != nil {
return
}
if retMSG, err = srv.TradeRefundCallback(ctx, arg.MSGID, arg.MSGContent); err != nil {
ctx.JSON(nil, err)
return
}
ctx.String(http.StatusOK, retMSG)
}

View File

@@ -0,0 +1,61 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"asset.go",
"income.go",
"pay.go",
"service.go",
"trade.go",
],
importpath = "go-common/app/service/main/ugcpay/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/ugcpay/conf:go_default_library",
"//app/service/main/ugcpay/dao:go_default_library",
"//app/service/main/ugcpay/model:go_default_library",
"//app/service/main/ugcpay/service/pay:go_default_library",
"//library/cache:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//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",
"//app/service/main/ugcpay/service/pay:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["income_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/ugcpay/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,101 @@
package service
import (
"context"
"math"
"time"
"go-common/app/service/main/ugcpay/conf"
"go-common/app/service/main/ugcpay/model"
"go-common/library/ecode"
"go-common/library/log"
)
// AssetRegister register a content to asset
func (s *Service) AssetRegister(ctx context.Context, mid int64, oid int64, otype string, currency string, price int64) (err error) {
var (
asset = &model.Asset{
ID: 1,
MID: mid,
OID: oid,
OType: otype,
Currency: currency,
Price: price,
State: "valid",
CTime: time.Now(),
MTime: time.Now(),
}
)
if err = s.dao.UpsertAsset(ctx, asset); err != nil {
return
}
if err = s.dao.DelCacheAsset(ctx, oid, otype, currency); err != nil {
return
}
return
}
// AssetQuery get asset by content
func (s *Service) AssetQuery(ctx context.Context, oid int64, otype string, currency string) (as *model.Asset, pp map[string]int64, err error) {
log.Info("asset query oid : %d, otype : %s, currency : %s", oid, otype, currency)
if as, err = s.dao.Asset(ctx, oid, otype, currency); err != nil {
return
}
if as == nil {
err = ecode.UGCPayAssetInvalid
return
}
pp = s.calcPlatformPrice(ctx, as.Price)
return
}
func (s *Service) calcPlatformPrice(ctx context.Context, price int64) (pp map[string]int64) {
pp = make(map[string]int64)
for platform, rate := range conf.Conf.Biz.Price.PlatformTax {
if rate > 1.0 {
pp[platform] = int64(math.Ceil(float64(price) * rate))
}
}
return
}
// func (s *Service) calcRawPrice(ctx context.Context, platform string, fee int64) (rawFee int64) {
// tax, ok := conf.Conf.Biz.Price.PlatformTax[platform]
// if !ok {
// rawFee = fee
// return
// }
// rawFee = int64(float64(fee) / tax)
// return
// }
// AssetRelation get relation
func (s *Service) AssetRelation(ctx context.Context, mid int64, oid int64, otype string) (state string, err error) {
if mid <= 0 {
return "none", nil
}
var (
assetRelation *model.AssetRelation
)
if state, err = s.dao.CacheAssetRelationState(ctx, oid, otype, mid); err != nil {
return
}
if state != "miss" {
return
}
if assetRelation, err = s.dao.RawAssetRelation(ctx, mid, oid, otype); err != nil {
return
}
if assetRelation == nil {
state = "none"
} else {
state = assetRelation.State
}
s.cache.Save(func() {
if theErr := s.dao.AddCacheAssetRelationState(context.Background(), oid, otype, mid, state); theErr != nil {
log.Error("s.dao.AddCacheAssetRelationState error : %+v", theErr)
return
}
})
return
}

View File

@@ -0,0 +1,86 @@
package service
import (
"context"
"time"
"go-common/app/service/main/ugcpay/model"
"go-common/library/log"
)
// IncomeUserAssetOverview .
func (s *Service) IncomeUserAssetOverview(ctx context.Context, mid int64, currency string) (user *model.AggrIncomeUser, monthReady int64, newDailyBill *model.Bill, err error) {
if user, err = s.dao.RawAggrIncomeUser(ctx, mid, currency); err != nil {
return
}
var (
dayVer = s.dayVer(time.Now().Add(-time.Hour * 48))
monthVer = s.monthVer(time.Now())
billList []*model.Bill
biz = "asset"
)
log.Info("IncomeUserAssetOverview mid: %d, biz: %s, currency: %s, month_ver: %d", mid, biz, currency, monthVer)
if billList, err = s.dao.RawBillUserDailyByMonthVer(ctx, mid, biz, currency, monthVer); err != nil {
return
}
for _, b := range billList {
monthReady += b.In - b.Out
}
if newDailyBill, err = s.dao.RawBillUserDaily(ctx, mid, biz, currency, dayVer); err != nil {
return
}
return
}
func (s *Service) dayVer(t time.Time) (ver int64) {
return int64(t.Year()*10000 + int(t.Month())*100 + t.Day())
}
func (s *Service) monthVer(t time.Time) (ver int64) {
return int64(t.Year()*100 + int(t.Month()))
}
// IncomeUserAssetList .
func (s *Service) IncomeUserAssetList(ctx context.Context, mid int64, currency string, ver int64, pn, ps int64) (list *model.AggrIncomeUserAssetList, page *model.Page, err error) {
var (
assets []*model.AggrIncomeUserAsset
limit = 1000
)
if assets, err = s.dao.RawAggrIncomeUserAssetList(ctx, mid, currency, ver, limit); err != nil {
return
}
l, page := s.pageIncomeUseAsset(assets, pn, ps)
list = &model.AggrIncomeUserAssetList{
MID: mid,
Ver: ver,
Assets: l,
Page: page,
}
return
}
// IncomeUserAsset .
func (s *Service) IncomeUserAsset(ctx context.Context, mid int64, oid int64, otype string, currency string, ver int64) (asset *model.AggrIncomeUserAsset, err error) {
return s.dao.RawAggrIncomeUserAsset(ctx, mid, currency, oid, otype, ver)
}
func (s *Service) pageIncomeUseAsset(list []*model.AggrIncomeUserAsset, pn, ps int64) (l []*model.AggrIncomeUserAsset, page *model.Page) {
page = &model.Page{
Num: pn,
Size: ps,
Total: int64(len(list)),
}
l = make([]*model.AggrIncomeUserAsset, 0)
from := (pn - 1) * ps
to := pn * ps
if page.Total < from {
return
}
if page.Total < to {
l = append(l, list[from:]...)
} else {
l = append(l, list[from:to]...)
}
return
}

View File

@@ -0,0 +1,37 @@
package service
import (
"testing"
"go-common/app/service/main/ugcpay/model"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func TestIncomeAssetMonthly(t *testing.T) {
Convey("", t, func() {
list := []*model.AggrIncomeUserAsset{
{
OID: 2333,
OType: "archive",
Currency: "bp",
},
{
OID: 2333,
OType: "archive",
Currency: "bp",
},
{
OID: 2333,
OType: "archive",
Currency: "bp",
},
}
l, page := s.pageIncomeUseAsset(list, 4, 20)
t.Log(l, page)
})
}

View File

@@ -0,0 +1,63 @@
package service
import (
"context"
"go-common/app/service/main/ugcpay/model"
"go-common/library/log"
)
func (s *Service) createPayData(ctx context.Context, order *model.Order) (data string, err error) {
if order == nil {
return
}
title := s.payTitle(order.OID, order.Fee)
payParam := s.pay.Create(order.OrderID, order.OID, order.Fee, s.pay.DeviceType(order.Platform), s.pay.ServiceType(order.Platform), order.MID, title)
if err = s.pay.Sign(payParam); err != nil {
return
}
return s.pay.ToJSON(payParam)
}
func (s *Service) refundOrder(ctx context.Context, order *model.Order, desc string) (err error) {
if order == nil {
return
}
fn := func() (err error) {
refundParam := s.pay.Refund(order.PayID, order.Fee, desc)
if err = s.pay.Sign(refundParam); err != nil {
return
}
refundJSON, err := s.pay.ToJSON(refundParam)
if err != nil {
return
}
return s.dao.PayRefund(ctx, refundJSON)
}
return s.tryHard(fn, "payRefund", 2)
}
func (s *Service) cancelOrder(ctx context.Context, orderID string) (err error) {
cancelParam := s.pay.Cancel(orderID)
if err = s.pay.Sign(cancelParam); err != nil {
return
}
cancelJSON, err := s.pay.ToJSON(cancelParam)
if err != nil {
return
}
return s.dao.PayCancel(ctx, cancelJSON)
}
func (s *Service) tryHard(fn func() error, fname string, tryTimes int) (err error) {
for tryTimes > 0 {
tryTimes--
if err = fn(); err != nil {
log.Error("func: %s, err: %+v, try times left: %d", fname, err, tryTimes)
} else {
log.Info("func: %s, run success, try times left: %d", fname, tryTimes)
break
}
}
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/service/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,182 @@
package pay
import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"net/url"
"strconv"
"time"
"go-common/library/log"
"github.com/pkg/errors"
)
// Pay is.
type Pay struct {
ID string
Token string
OrderTTL int
NotifyURL string
RefundNotifyURL string
}
// Create 返回订单创建param
func (p *Pay) Create(orderID string, productID int64, price int64, deviceType int64, serviceType int, mid int64, title string) (params url.Values) {
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("serviceType", strconv.Itoa(serviceType))
params.Set("orderId", orderID)
params.Set("orderCreateTime", strconv.FormatInt(time.Now().Unix()*1000, 10))
params.Set("orderExpire", strconv.Itoa(p.OrderTTL))
params.Set("payAmount", strconv.FormatInt(price, 10))
params.Set("originalAmount", strconv.FormatInt(price, 10))
params.Set("deviceType", strconv.FormatInt(deviceType, 10))
params.Set("notifyUrl", p.NotifyURL)
params.Set("productId", strconv.FormatInt(productID, 10))
params.Set("showTitle", title)
params.Set("traceId", p.TraceID())
params.Set("timestamp", strconv.FormatInt(time.Now().Unix()*1000, 10))
params.Set("version", "1.0")
params.Set("uid", strconv.FormatInt(mid, 10))
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
}
// TraceID 生成traceID
func (p *Pay) TraceID() string {
return strconv.FormatInt(time.Now().UnixNano(), 10)
}
// Refund 原路返回退款params
func (p *Pay) Refund(txID string, refundFee int64, refundDesc string) (params url.Values) {
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("txId", txID)
params.Set("totalAmount", strconv.FormatInt(refundFee, 10))
params.Set("refundAmount", strconv.FormatInt(refundFee, 10))
params.Set("refundDesc", refundDesc)
params.Set("notifyUrl", p.RefundNotifyURL)
params.Set("version", "1.0")
params.Set("traceId", p.TraceID())
params.Set("timestamp", strconv.FormatInt(time.Now().Unix()*1000, 10))
return
}
// Cancel 返回订单取消param
func (p *Pay) Cancel(orderID string) (params url.Values) {
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("orderId", orderID)
params.Set("timestamp", strconv.FormatInt(time.Now().Unix()*1000, 10))
params.Set("traceId", p.TraceID())
params.Set("version", "1.0")
return
}
// ToJSON 将param转换为支付平台请求的body: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 通过platform获得支付平台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
}
}
// ServiceType 通过platform获得支付平台ServiceType
func (p *Pay) ServiceType(platform string) (t int) {
/*
业务方业务类型用于业务方定制支付渠道不同的serviceType可以配置成不同的支付渠道列表
每个渠道列表可以自定义顺序,以下值具有特殊含义:
1. 7 签约代扣类,如微信代扣,支付宝代扣 5.25 支持)
2. 100 表示IAP支付 5.24 支持)
3. 100 IAP代扣也传100根据subscribeType区分是不是代扣
4. 99 表示 客户端B币快捷支付 5.26 支持)
5 98 表示 业务方B币快捷支付1.1 B币支付
*/
switch platform {
case "ios", "android":
return 0
default:
return 99
}
}
// Sign 对param进行签名
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,118 @@
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,
OrderTTL: 1800,
NotifyURL: "http://api.bilibili.co/x/internal/ugcpay/trade/pay/callback",
}
m.Run()
os.Exit(0)
}
func TestCreate(t *testing.T) {
Convey("", t, func() {
param := p.Create("1", 233, 1000, p.DeviceType("web"), 99, 46333, "测试title")
t.Log(param.Encode())
})
}
func TestQuery(t *testing.T) {
Convey("", t, func() {
param := p.Query("77546846181122123422")
t.Log(param.Encode())
p.Sign(param)
t.Log(p.ToJSON(param))
})
}
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,71 @@
package service
import (
"context"
"math/rand"
"time"
"go-common/app/service/main/ugcpay/conf"
"go-common/app/service/main/ugcpay/dao"
"go-common/app/service/main/ugcpay/service/pay"
"go-common/library/cache"
"go-common/library/log"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
pay *pay.Pay
cache *cache.Cache
rnd *rand.Rand
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
pay: &pay.Pay{
ID: conf.Conf.Biz.Pay.ID,
Token: conf.Conf.Biz.Pay.Token,
OrderTTL: 1800,
NotifyURL: conf.Conf.Biz.Pay.URLPayCallback,
RefundNotifyURL: conf.Conf.Biz.Pay.URLRefundCallback,
},
cache: cache.New(10, 10240),
rnd: rand.New(rand.NewSource(time.Now().Unix())),
}
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()
}
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 = 3
}
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
}

View File

@@ -0,0 +1,629 @@
package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
"time"
"go-common/app/service/main/ugcpay/model"
xsql "go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
"github.com/pkg/errors"
)
// TradeCreate .
func (s *Service) TradeCreate(c context.Context, platform string, mid int64, oid int64, otype string, currency string) (orderID string, payData string, err error) {
// 1. 查询asset信息
var (
asset *model.Asset
platformPrice map[string]int64
price int64
state string
order *model.Order
)
if asset, platformPrice, err = s.AssetQuery(c, oid, otype, currency); err != nil {
return
}
if asset == nil {
err = ecode.UGCPayAssetCantBuy
return
}
// 2. 判断是否已付费
if state, err = s.AssetRelation(c, mid, oid, otype); err != nil {
return
}
if state == model.AssetRelationPaid {
err = ecode.UGCPayAssetPaid
return
}
// 3. 重复下单直接返回
// if order, err = s.dao.RawOrderUserByAsset(c, mid, oid, otype, platform, time.Now().Add(-time.Second*time.Duration(conf.Conf.Biz.Pay.OrderTTL*2))); err != nil {
// return
// }
// if order != nil {
// if order.IsPay() {
// err = ecode.UGCPayAssetPaid
// return
// }
// orderID = order.ID
// payData, err = s.createPayData(c, order)
// if err == nil {
// // 恢复原订单成功,直接返回
// return
// }
// // 恢复原订单失败,继续执行
// log.Error("Recover Order: %+v, failed: %+v", order, err)
// err = nil
// order = nil
// }
// 4. 获得平台价格
price = asset.PickPrice(platform, platformPrice)
if price <= 0 {
log.Error("Asset : oid %d , otype %s , pick price : platform %s , currency %s , price <= 0", oid, otype, platform, currency)
err = ecode.UGCPayAssetCantBuy
return
}
// 5. 检查archive付费状态
var (
payflag = false
)
switch otype {
case model.OTypeArchive:
if payflag, err = s.dao.ArchiveUGCPay(c, oid); err != nil {
log.Error("s.dao.ArchiveUGCPay err : %+v", err)
err = ecode.UGCPayDependArchiveErr
return
}
}
if !payflag {
err = ecode.UGCPayAssetCantBuy
return
}
// 6. 创建订单
order = &model.Order{
OrderID: s.orderID(),
MID: mid,
Biz: model.BizAsset,
Platform: platform,
OID: oid,
OType: otype,
Fee: price,
RealFee: asset.Price,
Currency: currency,
State: model.OrderStateCreated,
}
orderID = order.OrderID
// 7. 创建支付参数
if payData, err = s.createPayData(c, order); err != nil {
return
}
if order.ID, err = s.dao.InsertOrderUser(c, order); err != nil {
return
}
// 8. 清理订单状态缓存
s.cache.Save(func() {
if theErr := s.dao.DelCacheOrderUser(context.Background(), order.OrderID); theErr != nil {
log.Error("%+v", theErr)
return
}
})
return
}
// TradeQuery .
func (s *Service) TradeQuery(ctx context.Context, orderID string) (order *model.Order, err error) {
order, err = s.dao.OrderUser(ctx, orderID)
if order == nil {
err = ecode.UGCPayOrderInvalid
return
}
if order.IsPay() {
return
}
if err = s.tradeConfirm(ctx, order); err != nil {
return
}
return
}
// TradeCancel .
func (s *Service) TradeCancel(ctx context.Context, orderID string) (err error) {
if err = s.updateOrder(ctx, orderID, model.OrderStateClosed, "TradeCancel"); err != nil {
return
}
// 支付中心取消
if err = s.cancelOrder(ctx, orderID); err != nil {
return
}
return
}
// TradeConfirm a trade state from pay
func (s *Service) TradeConfirm(ctx context.Context, orderID string) (order *model.Order, err error) {
// 1. 查询本地订单
if order, err = s.dao.OrderUser(ctx, orderID); err != nil {
return
}
if order == nil {
err = ecode.UGCPayOrderInvalid
return
}
if err = s.tradeConfirm(ctx, order); err != nil {
return
}
return
}
func (s *Service) tradeConfirm(ctx context.Context, order *model.Order) (err error) {
// 2. 从支付平台获取订单状态
var (
params url.Values
jsonData string
orders map[string][]*model.PayOrder
payOrders []*model.PayOrder
ok bool
)
params = s.pay.Query(order.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[order.OrderID]; !ok || len(payOrders) == 0 {
log.Info("TradeConfirm from pay platform not found order: %s", order.OrderID)
return
}
// 3. 变更订单状态
var (
changed bool
txID = ""
payStatusDesc = ""
payFailReason = ""
)
for _, o := range payOrders {
changed = false
switch o.PayStatus {
case model.PayStateSuccess, model.PayStateFinished:
changed = order.UpdateState(model.OrderStatePaid)
case model.PayStateOverdue:
changed = order.UpdateState(model.OrderStateExpired)
case model.PayStateClosed, model.PayStatePaySuccessAndCancel, model.PayStatePayCancel:
changed = order.UpdateState(model.OrderStateClosed)
case model.PayStateFail:
changed = order.UpdateState(model.OrderStateFailed)
default:
log.Error("TradeConfirm unknown pay status: %+v", o)
}
if changed {
payStatusDesc = o.PayStatusDesc
payFailReason = o.FailReason
txID = strconv.FormatInt(o.TXID, 10)
}
}
if !changed {
log.Info("tradeConfirm: %+v no need to update", order)
return
}
orderUpdater := func(order *model.Order) {
order.PayID = txID
order.PayReason = s.payReason(payStatusDesc, payFailReason)
}
return s.updateOrder(ctx, order.OrderID, order.State, "TradeConfirm", orderUpdater)
}
func (s *Service) payReason(desc string, failReason string) string {
if failReason == "" {
return desc
}
return desc + ":" + failReason
}
// TradeRefunds .
func (s *Service) TradeRefunds(ctx context.Context, orderIDs []string) (err error) {
for _, id := range orderIDs {
if err = s.TradeRefund(ctx, id); err != nil {
return
}
}
return
}
// TradeRefund .
func (s *Service) TradeRefund(ctx context.Context, orderID string) (err error) {
var (
order *model.Order
)
if order, err = s.dao.OrderUser(ctx, orderID); err != nil {
return
}
if order == nil {
err = ecode.UGCPayOrderInvalid
return
}
if order.PayID == "" {
err = ecode.UGCPayOrderNotPay
return
}
var toState string
if order.State == model.OrderStatePaid {
toState = model.OrderStateRefunding
} else if order.State == model.OrderStateSettled {
toState = model.OrderStateSettledRefunding
} else {
err = ecode.UGCPayOrderNotPay
return
}
extra := func(ctx context.Context) error {
// 通知支付平台退款
return s.refundOrder(ctx, order, "后台退款")
}
return s.updateOrderTrans(ctx, orderID, toState, "TradeRefund", extra)
}
// TradePayCallback 支付订单回调
func (s *Service) TradePayCallback(ctx context.Context, msgID int64, msgData string) (retMSG string, err error) {
// 1. 解析msg
retMSG = "SUCCESS"
msg := &model.PayCallbackMSG{}
if err = json.Unmarshal([]byte(msgData), msg); err != nil {
log.Error("TradePayCallback decode err : %+v", errors.WithStack(err))
err = nil
return
}
var (
order *model.Order
)
// 2. 获得order
if order, err = s.dao.OrderUser(ctx, msg.OrderID); err != nil {
log.Error("TradePayCallback s.dao.OrderUser err : %+v", err)
err = nil
retMSG = "FAIL"
return
}
if order == nil {
log.Error("TradePayCallback order not found err : %+v", err)
return
}
// 3. 校验价格
if msg.PayAmount != order.Fee {
log.Error("TradePayCallback check fee not equal, msg: %+v, order: %+v", msg, order)
return
}
// 4. 更新order
orderUpdater := func(order *model.Order) {
order.PayID = strconv.FormatInt(msg.TXID, 10)
order.ParsePaidTime(msg.OrderPayTime)
}
if err = s.updateOrder(ctx, order.OrderID, model.OrderStatePaid, "TradePayCallback", orderUpdater); err != nil {
log.Error("TradePayCallback s.updateOrder err : %+v", err)
err = nil
retMSG = "FAIL"
return
}
return
}
// TradeRefundCallback 交易退款回调
func (s *Service) TradeRefundCallback(ctx context.Context, msgID int64, msgData string) (retMSG string, err error) {
// 1. 解析msg
retMSG = "SUCCESS"
msg := &model.PayRefundCallbackMSG{}
if err = json.Unmarshal([]byte(msgData), msg); err != nil {
log.Error("TradeRefundCallback decode err : %+v", errors.WithStack(err))
err = nil
return
}
// 检查是否成功退款
flag := false
refundTime := ""
for _, ele := range msg.List {
if ele.IsSuccess() {
flag = true
refundTime = ele.RefundEndTime
log.Info("TradeRefundCallback got refunded msg: %+v", ele)
}
}
if !flag {
log.Warn("TradeRefundCallback got msg with not refunded state: %+v", msg)
return
}
// 2. 获得order
var (
order *model.Order
)
if order, err = s.dao.OrderUser(ctx, msg.OrderID); err != nil {
log.Error("TradeRefundCallback s.dao.OrderUser err : %+v", err)
err = nil
retMSG = "FAIL"
return
}
if order == nil {
log.Error("TradeRefundCallback order not found err : %+v", err)
return
}
// 3. 更新order
var toState string
switch order.State {
case model.OrderStateRefunding, model.OrderStatePaid:
toState = model.OrderStateRefunded
case model.OrderStateSettledRefunding, model.OrderStateSettled:
toState = model.OrderStateSettledRefunded
default:
toState = model.OrderStateDupRefunded
}
orderUpdater := func(order *model.Order) {
order.PayID = strconv.FormatInt(msg.TXID, 10)
order.ParseRefundedTime(refundTime)
}
if err = s.updateOrder(ctx, order.OrderID, toState, "TradeRefundCallback", orderUpdater); err != nil {
log.Error("TradeRefundCallback s.updateOrder err : %+v", err)
err = nil
retMSG = "FAIL"
return
}
return
}
// RechargeShellCallback 转换贝壳回调
func (s *Service) RechargeShellCallback(ctx context.Context, msgID int64, msgData string) (retMSG string, err error) {
// 1. 解析msg
retMSG = "SUCCESS"
msg := &model.RechargeShellCallbackMSG{}
if err = json.Unmarshal([]byte(msgData), msg); err != nil {
log.Error("RechargeShellCallback decode err : %+v", errors.WithStack(err))
err = nil
return
}
// 查询转贝壳订单
order, err := s.dao.RawOrderRechargeShell(ctx, msg.ThirdOrderNo)
if err != nil {
retMSG = "FAIL"
log.Error("%+v", err)
err = nil
return
}
if order == nil {
log.Error("RechargeShellCallback not found valid order: %+v", msg)
return
}
// 决定状态流转
state := model.RechargeShellFail
switch msg.Status {
case "SUCCESS":
state = model.RechargeShellSuccess
case "FAIL":
state = model.RechargeShellFail
default:
log.Error("RechargeShellCallback got unknown statue: %+v", msg)
}
var (
rechargeShellLog = &model.OrderRechargeShellLog{
OrderID: order.OrderID,
FromState: "created",
ToState: state,
Desc: msg.Status,
BillUserMonthlyID: fmt.Sprintf("end_%d", time.Now().Unix()),
}
)
order.State = state
// 开始事务
tx, err := s.dao.BeginTran(ctx)
if err != nil {
log.Error("%+v", err)
err = nil
return
}
if err = s.dao.TXUpdateOrderRechargeShell(ctx, tx, order); err != nil {
tx.Rollback()
log.Error("%+v", err)
err = nil
retMSG = "FAIL"
return
}
if _, err = s.dao.TXInsertOrderRechargeShellLog(ctx, tx, rechargeShellLog); err != nil {
tx.Rollback()
log.Error("%+v", err)
err = nil
retMSG = "FAIL"
return
}
if err = tx.Commit(); err != nil {
log.Error("%+v", err)
err = nil
retMSG = "FAIL"
return
}
return
}
func (s *Service) updateOrder(ctx context.Context, orderID string, toState string, taskName string, orderUpdaters ...func(order *model.Order)) (err error) {
return s.updateOrderTrans(ctx, orderID, toState, taskName, nil, orderUpdaters...)
}
func (s *Service) updateOrderTrans(ctx context.Context, orderID string, toState string, taskName string, extra func(context.Context) error, orderUpdaters ...func(order *model.Order)) (err error) {
// 需要异步处理的函数列表
asyncFuncList := make([]func() error, 0)
fn := func(ctx context.Context) (affected bool, err error) {
var (
tx *xsql.Tx
order *model.Order
)
affected = true
// 查找 order
// if order, err = s.dao.OrderUser(ctx, orderID); err != nil {
// return
// }
if order, err = s.dao.RawOrderUser(ctx, orderID); err != nil {
return
}
if order == nil {
log.Error("updateOrder: %s get nil order: %s", taskName, orderID)
err = ecode.UGCPayOrderInvalid
return
}
log.Info("updateOrderTrans: orderID: %s, toState: %s, order: %+v", orderID, toState, order)
preState := order.State
// 通过状态机,内存变更
changed := order.UpdateState(toState)
if !changed {
log.Error("updateOrder: %s, change state from order: %+v to state: %s failed", taskName, order, toState)
return
}
for _, ou := range orderUpdaters {
ou(order)
}
// 更新DB
var (
rowAffected int64
logOrder = &model.LogOrder{
OrderID: order.OrderID,
FromState: preState,
ToState: order.State,
Desc: taskName,
}
)
if tx, err = s.dao.BeginTran(ctx); err != nil {
return
}
if rowAffected, err = s.dao.TXUpdateOrderUser(ctx, tx, order); err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
affected = false
log.Error("updateOrder: %s, TXUpdateOrderUser: %+v rowAffected: %d <= 0", taskName, order, rowAffected)
tx.Rollback()
return
}
if _, err = s.dao.TXInsertOrderUserLog(ctx, tx, logOrder); err != nil {
tx.Rollback()
return
}
// 根据更新后 state 各种后处理
switch order.State {
case model.OrderStatePaid: // 支付订单完成
var (
relation = &model.AssetRelation{
OID: order.OID,
OType: order.OType,
MID: order.MID,
State: model.OrderStatePaid,
}
assetRelationAffected int64
)
if assetRelationAffected, err = s.dao.TXUpsertAssetRelation(ctx, tx, relation); err != nil {
tx.Rollback()
return
}
// 如果 asset_relation 更新失败, 认为是重复订单, 这里不对 order 的 state 做流转
if assetRelationAffected <= 0 {
asyncFuncList = append(asyncFuncList, func() error {
return s.refundOrder(context.Background(), order, "重复扣费退款")
})
}
// 清理付费关系缓存
asyncFuncList = append(asyncFuncList, func() error {
return s.dao.DelCacheAssetRelationState(context.Background(), relation.OID, relation.OType, relation.MID)
})
case model.OrderStateRefunded, model.OrderStateSettledRefunded: // 支付订单退款
var (
relation = &model.AssetRelation{
OID: order.OID,
OType: order.OType,
MID: order.MID,
State: "none",
}
)
if _, err = s.dao.TXUpsertAssetRelation(ctx, tx, relation); err != nil {
tx.Rollback()
return
}
// 清理付费关系缓存
asyncFuncList = append(asyncFuncList, func() error {
return s.dao.DelCacheAssetRelationState(context.Background(), relation.OID, relation.OType, relation.MID)
})
default: // 默认
}
// 额外任务执行
if extra != nil {
if err = extra(ctx); err != nil {
tx.Rollback()
return
}
}
// 提交事务
if err = tx.Commit(); err != nil {
return
}
return
}
if err = runCAS(ctx, fn); err != nil {
return
}
asyncFuncList = append(asyncFuncList, func() error {
return s.dao.DelCacheOrderUser(context.Background(), orderID)
})
// 启动需要异步处理的函数
for _, f := range asyncFuncList {
f := f
s.cache.Save(func() {
if theErr := f(); theErr != nil {
log.Error("updateOrder: %s, err: %+v", taskName, theErr)
}
})
}
return
}
// orderID get order id
func (s *Service) orderID() string {
var b bytes.Buffer
b.WriteString(fmt.Sprintf("%05d", s.rnd.Int63n(99999)))
b.WriteString(fmt.Sprintf("%03d", time.Now().UnixNano()/1e6%1000))
b.WriteString(time.Now().Format("060102150405"))
return b.String()
}
func (s *Service) payTitle(oid int64, fee int64) string {
return fmt.Sprintf("付费视频观看av%d消费%.2fB币", oid, float64(fee)/100)
}