Create & Init Project...

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

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

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

View File

@@ -0,0 +1,126 @@
### coin job
#### V3.3.10
> 1. 减少日志
#### V3.3.10
> 1. fix log
#### V3.3.9
> 1. redis超时无限重试
#### V3.3.8
> 1. 白名单过滤事件
#### V3.3.7
> 1. 修复硬币事件business:105风控
#### V3.3.6
> 1. 忽略收到硬币事件
#### V3.3.5
> 1. 使用account/member grpc
#### V3.3.4
> 1. 忽略新增关注事件
#### V3.3.3
> 1. 修复硬币发放时间的bug
#### V3.3.2
> 1. 增加日志
> 2.修复bug
#### V3.3.1
> 1. 增加验证
#### V3.3.0
> 1. remove net/ip
#### V3.2.0
> 1. 结算信息增加日期
#### V3.1.1
> 1. use env
#### V3.1.0
> 1. 发放奖励时 新增监听accountExp事件
#### V3.0.2
> 1. 迁移到bm
#### V3.0.1
> 1. 修复热加载问题
#### V3.0.0
> 1. 代码重构 移除无用的代码 增加prom监控
> 2. 调用硬币rpc增加经验 增加重试
> 3. 硬币为负不再扣经验
> 4. 获取用户信息改为rpc接口 不再使用http
> 5. 投币后调用硬币rpc接口 异步修改其他表
> 6. 结算时增加错误重试
> 7. 去掉上次迁移时遗留的member databus
#### V2.7.3
> 1. 停止迁移脚本
#### V2.7.2
> 1. 修复登录bug
#### V2.7.1
> 1.fix coin zero val
#### V2.7.0
> 1.change user coin to memcache
#### V2.6.1
> 1.登陆硬币奖励增加rank判断
#### V2.6.0
> 1.加奖励databus消费
#### v2.5.0
> 1.并行消费登陆日志
#### V2.4.1
> 1.修改登录奖励文案
#### V2.4.0
> 1.增加奖励开启开关
#### V2.3.1
> 1.修复结算panic
#### V2.3.0
> 1.添加组件依赖监控
#### V2.2.1
> 1.添加log日志
#### V2.2.0
> 1.消费登陆日志加硬币
#### V2.1.1
> 1.关闭全量同步
#### V2.1.0
> 1.数据迁移
#### V2.0.0
> 1.迁移到大仓库
#### v1.2.0
> 1.批量导数据从dede库改为新库bilibili_coin
#### V1.1.0
> 1.vendor升级支持rpcx
#### v1.0.1
> 1.修复月结经验数
#### v1.0.0
> 1.用于硬币月结

View File

@@ -0,0 +1,11 @@
# Owner
lintanghui
linmiao
zhapuyu
# Author
lintanghui
wangxu01
# Reviewer
lintanghui

16
app/job/main/coin/OWNERS Normal file
View File

@@ -0,0 +1,16 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- linmiao
- lintanghui
- wangxu01
- zhapuyu
labels:
- job
- job/main/coin
- main
options:
no_parent_owners: true
reviewers:
- lintanghui
- wangxu01

View File

@@ -0,0 +1,11 @@
#### coin job
##### 项目简介
> 1.消费coin数据并进行硬币的结算
##### 编译环境
> 请只用golang v1.7.x以上版本编译执行。
##### 依赖包
> 1.公共包go-common

View File

@@ -0,0 +1,41 @@
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 = ["coin-job-test.toml"],
importpath = "go-common/app/job/main/coin/cmd",
tags = ["automanaged"],
deps = [
"//app/job/main/coin/conf:go_default_library",
"//app/job/main/coin/http:go_default_library",
"//app/job/main/coin/service: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,167 @@
version = "0.1.0"
perf = "127.0.0.1:6078"
name = "127.0.0.1"
[xlog]
dir = "/data/log/coin-job/"
[bm]
addr = "0.0.0.0:6158"
timeout = "1s"
[databus]
key = "0Pub71WwEMKXu63qtztu"
secret = "0Pub71WwEMKXu63qtztv"
group = "CoinJob-UGC-S"
topic = "CoinJob-T"
action = "sub"
name = "coin-job/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[logindatabus]
key = "0QHEzXlXE9Ijewj8V4zu"
secret = "0QHEzXlXE9Ijewj8V4zv"
group = "PassportLog-T-Coin-S"
topic = "LogUserAction-T"
action = "sub"
offset = "new"
buffer = 2048
name = "secure-service/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[expDatabus]
key = "4ba46ba31f9a44ef"
secret = "99985eb4451cfb1b899ca0fbe3c4bdc8"
group = "AccountExp-MainAccount-Coin-S"
topic = "AccountExp-T"
action = "sub"
buffer = 2048
name = "coin-job/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[databusutil]
size = 100
chan = 1024
num = 4
ticker="30s"
[archiveRPC]
pullInterval = "10s"
group = "group2"
[archiveRPC.client]
timeout = "1s"
timer = 1000
[archiveRPC.client.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[archiveRPC.zookeeper]
root = "/microservice/archive-service/"
addrs = ["172.16.33.54:2181"]
timeout = "1s"
[coinRPC]
pullInterval = "10s"
group = "group2"
[coinRPC.client]
timeout = "1s"
timer = 1000
[coinRPC.client.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[coinRPC.zookeeper]
root = "/microservice/coin-service/"
addrs = ["172.16.33.54:2181"]
timeout = "1s"
[accountRPC]
timeout = "1s"
[memRPC]
timeout = "300ms"
[redis]
name = "coin-service/coin"
proto = "tcp"
addr = "172.16.0.204:6379"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[db]
[db.coin]
addr = "172.16.33.205:3308"
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_coin?timeout=5s&readTimeout=30s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
#addr = "127.0.0.1:3306"
#dsn = "root@tcp(127.0.0.1:3306)/bilibili_coins?timeout=5s&readTimeout=30s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
queryTimeout = "1s"
execTimeout = "1s"
tranTimeout = "2s"
[db.coin.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[db.dede]
addr = "172.16.33.101:3306"
dsn = "account:wx2U1MwXRyWEuURw@tcp(172.16.33.101:3306)/account?timeout=5s&readTimeout=30s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
queryTimeout = "2s"
execTimeout = "1s"
tranTimeout = "2s"
[db.dede.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[memcache]
name = "member-service"
proto = "tcp"
addr = "172.16.33.22:21211"
idle = 5
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "24h"
[coinJob]
start=false
loginExpire = "72h"

View File

@@ -0,0 +1,49 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"go-common/app/job/main/coin/conf"
"go-common/app/job/main/coin/http"
"go-common/app/job/main/coin/service"
"go-common/library/log"
)
var (
s *service.Service
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
log.Init(conf.Conf.Xlog)
defer log.Close()
log.Info("archive-service_consumer start")
s = service.New(conf.Conf)
http.Init(conf.Conf, s)
signalHandler()
}
func signalHandler() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
si := <-ch
switch si {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("get a signal %s, stop the consume process", si.String())
s.Close()
s.Wait()
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,40 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/job/main/coin/conf",
tags = ["automanaged"],
deps = [
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/databusutil:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,107 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/rpc"
"go-common/library/net/rpc/warden"
"go-common/library/queue/databus"
"go-common/library/queue/databus/databusutil"
xtime "go-common/library/time"
"github.com/BurntSushi/toml"
)
var (
confPath string
// Conf config.
Conf = &Config{}
client *conf.Client
)
// Config conf.
type Config struct {
Env string
Sms *Sms
Databus *databus.Config
LoginDatabus *databus.Config
ExpDatabus *databus.Config
Xlog *log.Config
DB *DB
CoinJob *CoinJob
AccountRPC *warden.ClientConfig
MemRPC *warden.ClientConfig
ArchiveRPC *rpc.ClientConfig
CoinRPC *rpc.ClientConfig
// BM
BM *bm.ServerConfig
// redis
Redis *redis.Config
Databusutil *databusutil.Config
}
// CoinJob job conf.
type CoinJob struct {
// award conf
StartTime int64
Start bool
LoginExpire xtime.Duration
}
// Sms sms conf.
type Sms struct {
Phone string
Token string
}
// DB db conf
type DB struct {
Coin *sql.Config
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init conf.
func Init() (err 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
}
err = load()
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,56 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"mysql_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/coin/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"mysql.go",
"redis.go",
],
importpath = "go-common/app/job/main/coin/dao",
tags = ["automanaged"],
deps = [
"//app/job/main/coin/conf:go_default_library",
"//app/job/main/coin/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/stat/prom: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,55 @@
package dao
import (
"context"
"fmt"
"time"
"go-common/app/job/main/coin/conf"
"go-common/library/cache/redis"
"go-common/library/database/sql"
"go-common/library/stat/prom"
)
// Dao define dao.
type Dao struct {
c *conf.Config
coinDB *sql.DB
hitSettlePeriodStmt *sql.Stmt
getSettlePeriodStmt *sql.Stmt
getTotalCoinsStmt []*sql.Stmt
redis *redis.Pool
loginExpire int32
}
// PromError .
func PromError(name string) {
prom.BusinessErrCount.Incr(name)
}
// New new and return service.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
coinDB: sql.NewMySQL(c.DB.Coin),
getTotalCoinsStmt: make([]*sql.Stmt, SHARDING),
redis: redis.NewPool(c.Redis),
loginExpire: int32(time.Duration(c.CoinJob.LoginExpire) / time.Second),
}
for i := 0; i < SHARDING; i++ {
d.getTotalCoinsStmt[i] = d.coinDB.Prepared(fmt.Sprintf(_getTotalCoins, i))
}
d.hitSettlePeriodStmt = d.coinDB.Prepared(_hitSettlePeriod)
d.getSettlePeriodStmt = d.coinDB.Prepared(_getSettlePeriod)
return
}
// Ping check service health.
func (d *Dao) Ping(c context.Context) error {
return d.coinDB.Ping(c)
}
// Close close sevice.
func (d *Dao) Close() {
d.coinDB.Close()
}

View File

@@ -0,0 +1,39 @@
package dao
import (
"context"
"flag"
"os"
"testing"
"go-common/app/job/main/coin/conf"
)
var (
d *Dao
ctx = context.TODO()
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account.coin-job")
flag.Set("conf_appid", "coin-job")
flag.Set("conf_token", "RiSyxxaGuihmdbKCq9Pba1JwHRTmQjMI")
flag.Set("tree_id", "2130")
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/coin-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}

View File

@@ -0,0 +1,164 @@
package dao
import (
"context"
"database/sql"
"fmt"
"strconv"
"time"
"go-common/app/job/main/coin/model"
"go-common/library/log"
)
const (
// SHARDING table shard.
SHARDING = 50
// coin_settle_x
_getSettle = "SELECT id,mid,aid,type,coin_count,exp_sub,state FROM coin_settle_%d WHERE id> ? LIMIT 10000"
_hitSettlePeriod = "SELECT id,from_year,from_month,from_day,to_year,to_month,to_day FROM coin_settle_period WHERE from_year*10000+from_month*100+from_day<=? AND to_year*10000+to_month*100+to_day>?"
_getSettlePeriod = "SELECT id,from_year,from_month,from_day,to_year,to_month,to_day FROM coin_settle_period WHERE id=?"
_clearCoinCount = "UPDATE coin_settle_%d SET coin_count=0, mtime=?"
_updateCoinCount = "UPDATE coin_settle_%d SET coin_count=?, mtime=? WHERE aid=? AND type=?"
_updateSettle = "UPDATE coin_settle_%d SET state=1, exp_total=?, mtime=? WHERE id=?"
_upsertSettle = "INSERT INTO coin_settle_%d (mid,aid,type,coin_count,ctime,mtime) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE coin_count=coin_count+?,state=0, mtime=?"
// coin_archive_x
_getTotalCoins = "SELECT aid,type,SUM(multiply) FROM coin_archive_%d WHERE timestamp>=? AND timestamp<? GROUP BY aid,type"
)
func hashField(aid, tp int64) int64 {
return aid*1000 + tp
}
// SettlePeriod settle coin by tableid.
func (dao *Dao) SettlePeriod(c context.Context, id int64) (period *model.CoinSettlePeriod, err error) {
row := dao.getSettlePeriodStmt.QueryRow(c, id)
period = &model.CoinSettlePeriod{}
if err = row.Scan(&period.ID,
&period.FromYear,
&period.FromMonth,
&period.FromDay,
&period.ToYear,
&period.ToMonth,
&period.ToDay); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// HitSettlePeriod get table id.
func (dao *Dao) HitSettlePeriod(c context.Context, now time.Time) (period *model.CoinSettlePeriod, err error) {
ymd, _ := strconv.Atoi(now.Format("20060102"))
row := dao.hitSettlePeriodStmt.QueryRow(c, ymd, ymd)
period = &model.CoinSettlePeriod{}
if err = row.Scan(&period.ID,
&period.FromYear,
&period.FromMonth,
&period.FromDay,
&period.ToYear,
&period.ToMonth,
&period.ToDay); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
return
}
if period.ID == 0 {
err = fmt.Errorf("zero id at(%d)", ymd)
}
return
}
// UpdateSettle update settle info.
func (dao *Dao) UpdateSettle(c context.Context, tableID, id, expTotal int64, now time.Time) (err error) {
sqlStr := fmt.Sprintf(_updateSettle, tableID)
if _, err = dao.coinDB.Exec(c, sqlStr, expTotal, now, id); err != nil {
log.Error("dao.coinDB.Exec(%s, %d, %v, %d) error(%v)", sqlStr, expTotal, now, id, err)
PromError("db:UpdateSettle")
}
return
}
// UpdateCoinCount update coin.
func (dao *Dao) UpdateCoinCount(c context.Context, tableID, aid, tp, count int64, now time.Time) (err error) {
sqlStr := fmt.Sprintf(_updateCoinCount, tableID)
if _, err = dao.coinDB.Exec(c, sqlStr, count, now, aid, tp); err != nil {
log.Error("dao.coinDB.Exec(%s, %d, %v, %d) error(%v)", sqlStr, count, now, aid, err)
}
return
}
// Every10000 get 10000 coin record.
func (dao *Dao) Every10000(c context.Context, tableID int64, idx int64) (settles []*model.CoinSettle, maxid int64, err error) {
sqlStr := fmt.Sprintf(_getSettle, tableID)
rows, err := dao.coinDB.Query(c, sqlStr, idx)
if err != nil {
log.Error("dao.coinDB.Query(%s, %d) error(%v)", sqlStr, idx, err)
return
}
defer rows.Close()
maxid = idx
settles = make([]*model.CoinSettle, 0, 10000)
for rows.Next() {
settle := &model.CoinSettle{}
if err = rows.Scan(&settle.ID,
&settle.Mid,
&settle.Aid,
&settle.AvType,
&settle.CoinCount,
&settle.ExpSub,
&settle.State); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
if settle.ID > maxid {
maxid = settle.ID
}
settles = append(settles, settle)
}
return
}
// UpsertSettle update coin settle.
func (dao *Dao) UpsertSettle(c context.Context, tableID, mid, aid, tp, coinCount int64, now time.Time) (err error) {
sqlStr := fmt.Sprintf(_upsertSettle, tableID)
if _, err = dao.coinDB.Exec(c, sqlStr, mid, aid, tp, coinCount, now, now, coinCount, now); err != nil {
log.Error("dao.coinDB.Exec(%s,%d,%d,%d,%v,%v,%d,%v) error(%v)", sqlStr, mid, aid, coinCount, now, now, coinCount, now, err)
}
return
}
// ClearCoinCount clear settle.
func (dao *Dao) ClearCoinCount(c context.Context, tableID int64, now time.Time) (err error) {
sqlStr := fmt.Sprintf(_clearCoinCount, tableID)
if _, err = dao.coinDB.Exec(c, sqlStr, now); err != nil {
log.Error("dao.coinDB.Exec(%s, %v) error(%v)", sqlStr, now, err)
}
return
}
// TotalCoins get total coins.
func (dao *Dao) TotalCoins(c context.Context, id int, start, end time.Time) (coins map[int64]int64, err error) {
rows, err := dao.getTotalCoinsStmt[id].Query(c, start.Unix(), end.Unix())
if err != nil {
log.Error("dao.getTotalCoinsStmt[%d].Query(%v, %v) error(%v)", id, start, end, err)
return
}
coins = make(map[int64]int64)
defer rows.Close()
for rows.Next() {
var aid, tp, count int64
if err = rows.Scan(&aid, &tp, &count); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
coins[hashField(aid, tp)] = count
}
return
}

View File

@@ -0,0 +1,57 @@
package dao
import (
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestSettlePeriod(t *testing.T) {
Convey("SettlePeriod", t, func() {
_, err := d.SettlePeriod(ctx, 2)
So(err, ShouldBeNil)
})
}
func TestHitSettlePeriod(t *testing.T) {
Convey("HitSettlePeriod", t, func() {
_, err := d.HitSettlePeriod(ctx, time.Now())
So(err, ShouldBeNil)
})
}
func TestUpdateSettle(t *testing.T) {
Convey("UpdateSettle", t, func() {
err := d.UpdateSettle(ctx, 1, 1, 10, time.Now())
So(err, ShouldBeNil)
})
}
func TestUpdateCoinCount(t *testing.T) {
Convey("UpdateCoinCount", t, func() {
err := d.UpdateCoinCount(ctx, 1, 1, 10, 1, time.Now())
So(err, ShouldBeNil)
})
}
func TestEvery10000(t *testing.T) {
Convey("Every10000", t, func() {
_, _, err := d.Every10000(ctx, 1, 1)
So(err, ShouldBeNil)
})
}
func TestUpsertSettle(t *testing.T) {
Convey("UpsertSettle", t, func() {
err := d.UpsertSettle(ctx, 1, 1, 1, 1, 1, time.Now())
So(err, ShouldBeNil)
})
}
func TestTotalCoins(t *testing.T) {
Convey("TotalCoins", t, func() {
_, err := d.TotalCoins(ctx, 1, time.Now(), time.Now())
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,62 @@
package dao
import (
"context"
"fmt"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_loginShard = 10000
_loginedPrefix = "logined_%d_%d"
)
func loginKey(mid, day int64) string {
return fmt.Sprintf(_loginedPrefix, day, mid/_loginShard)
}
// SetLogin set user logined,
func (d *Dao) SetLogin(c context.Context, mid, day int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("SETBIT", loginKey(mid, day), mid%_loginShard, 1); err != nil {
PromError("redis:SetLogin")
log.Error("d.SetLogin(%v,%v) redis: err: %v", mid, day, err)
return
}
if err = conn.Send("EXPIRE", loginKey(mid, day), d.loginExpire); err != nil {
PromError("redis:SetLogin")
log.Error("d.SetLogin(%v,%v) redis: err: %v", mid, day, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("d.SetLogin(%v,%v) redis: err: %v", mid, day, err)
PromError("redis:SetLogin")
return
}
if _, err = redis.Bool(conn.Receive()); err != nil {
log.Error("d.SetLogin(%v,%v) redis: err: %v", mid, day, err)
PromError("redis:SetLogin")
return
}
if _, err = conn.Receive(); err != nil {
log.Error("d.SetLogin(%v,%v) redis: err: %v", mid, day, err)
PromError("redis:SetLogin")
}
return
}
// Logined check if user logined.
func (d *Dao) Logined(c context.Context, mid, day int64) (b bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
if b, err = redis.Bool(conn.Do("GETBIT", loginKey(mid, day), mid%_loginShard)); err == redis.ErrNil {
err = nil
}
if err != nil {
log.Error("d.Logined(%v,%v) redis err: %+v", mid, day, err)
}
return
}

View File

@@ -0,0 +1,22 @@
package dao
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestSetLogin(t *testing.T) {
Convey("TestSetLogin", t, func() {
err := d.SetLogin(ctx, 1, 2)
if err != nil {
t.Errorf("dedeCoins err(%v)", err)
}
b, _ := d.Logined(ctx, 1, 2)
if !b {
t.Errorf("Logined should be true but get %v", b)
}
b, _ = d.Logined(ctx, 1, 3)
So(b, ShouldNotBeNil)
})
}

View File

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

View File

@@ -0,0 +1,66 @@
package http
import (
"net/http"
"strconv"
"go-common/app/job/main/coin/conf"
"go-common/app/job/main/coin/service"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
var coinSvr *service.Service
// Init init http router.
func Init(c *conf.Config, s *service.Service) {
// init internal router
coinSvr = s
// init outer router
engineOuter := bm.DefaultServer(c.BM)
outerRouter(engineOuter)
if err := engineOuter.Start(); err != nil {
log.Error("bm.DefaultServer error(%v)", err)
panic(err)
}
}
func outerRouter(r *bm.Engine) {
r.Ping(ping)
r.Register(register)
r.POST("/redo", redo)
r.POST("/settle", settle)
}
// ping check server ok.
func ping(c *bm.Context) {
if err := coinSvr.Ping(c); err != nil {
log.Error("ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func redo(c *bm.Context) {
idStr := c.Request.Form.Get("table_id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, coinSvr.Redo(id))
}
func settle(c *bm.Context) {
idStr := c.Request.Form.Get("table_id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, coinSvr.Settle(id))
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["coin.go"],
importpath = "go-common/app/job/main/coin/model",
tags = ["automanaged"],
deps = ["//library/time:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,78 @@
package model
import (
"encoding/json"
"go-common/library/time"
)
// Message binlog databus msg.
type Message struct {
Action string `json:"action"`
Table string `json:"table"`
New json.RawMessage `json:"new"`
Old json.RawMessage `json:"old"`
}
// UserCoin dede_member user coin.
type UserCoin struct {
Mid int64 `json:"mid"`
Money float32 `money:"money"`
Mtime time.Time `json:"mtime"`
}
// DatabusCoin databus coin msg.
type DatabusCoin struct {
Mid int64 `json:"mid"`
Money float32 `money:"money"`
Mtime string `json:"modify_time"`
}
// CoinSettle coin settle.
type CoinSettle struct {
ITime time.Time `json:"itime"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"mtime"`
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Aid int64 `json:"aid"`
AvType int64 `json:"avtype"`
CoinCount int64 `json:"coin_count"`
ExpTotal int64 `json:"exp_total"`
ExpSub int64 `json:"exp_sub"`
State int `json:"state"`
Describe string `json:"describe"`
}
// CoinSettlePeriod coin settle conf.
type CoinSettlePeriod struct {
ID int64 `json:"id"`
FromYear int `json:"from_year"`
FromMonth int `json:"from_month"`
FromDay int `json:"from_day"`
ToYear int `json:"to_year"`
ToMonth int `json:"to_month"`
ToDay int `json:"to_day"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"mtime"`
}
// LoginLog user login log.
type LoginLog struct {
Mid int64 `json:"mid,omitempty"`
IP string `json:"ip,omitempty"`
CTime string `json:"ctime"`
Action string `json:"action"`
Business int `json:"business"`
Type int `json:"type"`
RawData string
Timestamp int64
}
// AddExp databus add exp arg.
type AddExp struct {
Event string `json:"event,omitempty"`
Mid int64 `json:"mid,omitempty"`
IP string `json:"ip,omitempty"`
Ts int64 `json:"ts,omitempty"`
}

View File

@@ -0,0 +1,65 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"award_test.go",
"service_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/job/main/coin/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"award.go",
"exp.go",
"service.go",
"settle.go",
],
importpath = "go-common/app/job/main/coin/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/coin/conf:go_default_library",
"//app/job/main/coin/dao:go_default_library",
"//app/job/main/coin/model:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/archive/api:go_default_library",
"//app/service/main/archive/api/gorpc:go_default_library",
"//app/service/main/archive/model/archive:go_default_library",
"//app/service/main/coin/api/gorpc:go_default_library",
"//app/service/main/coin/model:go_default_library",
"//app/service/main/member/api:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/databusutil:go_default_library",
"//library/stat/prom: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,145 @@
package service
import (
"context"
"encoding/json"
"time"
"go-common/app/job/main/coin/dao"
"go-common/app/job/main/coin/model"
accmdl "go-common/app/service/main/account/api"
coinmdl "go-common/app/service/main/coin/model"
mmdl "go-common/app/service/main/member/api"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/queue/databus"
"go-common/library/stat/prom"
)
var _passportLog = 53
func (s *Service) awardDo(ms []interface{}) {
for _, m := range ms {
mu, ok := m.(*model.LoginLog)
if !ok {
continue
}
if mu.Business != _passportLog {
continue
}
prom.BusinessInfoCount.Incr("award-event")
err := s.award(context.TODO(), mu.Mid, mu.Timestamp, mu.IP)
if err != nil {
log.Error("s.award mid %v err %v", mu.Mid, err)
}
log.Info("conmsumer login log, mid:%v,time %d, ip: %s err: %v", mu.Mid, mu.Timestamp, mu.IP, err)
}
}
func split(msg *databus.Message, data interface{}) int {
t, ok := data.(*model.LoginLog)
if !ok {
return 0
}
return int(t.Mid)
}
func newMsg(msg *databus.Message) (res interface{}, err error) {
loginlog := new(model.LoginLog)
if err = json.Unmarshal(msg.Value, &loginlog); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", msg.Value, err)
dao.PromError("loginlog:Unmarshal")
return
}
var t time.Time
if t, err = time.ParseInLocation("2006-01-02 15:04:05", loginlog.CTime, time.Local); err != nil {
log.Error("time.parse(%s) error(%v)", msg.Value, err)
dao.PromError("loginlog:Timeparse")
return
}
loginlog.Timestamp = t.Unix()
loginlog.RawData = string(msg.Value)
res = loginlog
return
}
func newExpMsg(msg *databus.Message) (res interface{}, err error) {
loginlog := new(model.LoginLog)
explog := new(model.AddExp)
if err = json.Unmarshal(msg.Value, &explog); err != nil {
log.Error("newExpMsg json.Unmarshal(%s) error(%v)", msg.Value, err)
dao.PromError("loginlog:ExpUnmarshal")
return
}
loginlog.Mid = explog.Mid
loginlog.Timestamp = explog.Ts
loginlog.IP = explog.IP
loginlog.RawData = string(msg.Value)
res = loginlog
return
}
func (s *Service) award(c context.Context, mid, ts int64, ip string) (err error) {
if !s.c.CoinJob.Start || ts < s.c.CoinJob.StartTime {
return
}
if (mid == 0) || (ts == 0) || (ip == "") {
return
}
day := int64(time.Unix(ts, 0).Day())
var login bool
for {
if login, err = s.coinDao.Logined(c, mid, day); err == nil {
break
}
dao.PromError("redis-logined-retry")
time.Sleep(time.Millisecond * 500)
}
if login {
return
}
// false mean first login,
var base *mmdl.BaseInfoReply
base, err = s.memRPC.Base(c, &mmdl.MemberMidReq{Mid: mid})
if err != nil {
if err == ecode.MemberNotExist {
return
}
log.Errorv(c, log.KV("log", "memRPC"), log.KV("err", err), log.KV("mid", mid))
dao.PromError("登录奖励member")
return
}
if (base != nil) && (base.Rank == 5000) {
log.Infov(c, log.KV("log", "add coin but user not member"), log.KV("mid", mid))
return
}
var profile *accmdl.ProfileReply
profile, _ = s.profile(c, mid)
if (profile != nil) && (profile.Profile != nil) && (profile.Profile.TelStatus == 0) {
log.Infov(c, log.KV("log", "login award failed. no telphone"), log.KV("mid", mid))
return
}
if _, err = s.coinRPC.ModifyCoin(c, &coinmdl.ArgModifyCoin{Mid: mid, Count: 1, Reason: "登录奖励", IP: ip, CheckZero: 1}); err != nil {
dao.PromError("登录奖励RPC")
return
}
for {
if err = s.coinDao.SetLogin(c, mid, day); err == nil {
break
}
dao.PromError("登录奖励setLogin-retry")
time.Sleep(time.Millisecond * 500)
}
prom.BusinessInfoCount.Incr("award-event-success")
log.Info("add coin success mid: %+v", mid)
return
}
func (s *Service) profile(c context.Context, mid int64) (res *accmdl.ProfileReply, err error) {
arg := &accmdl.MidReq{Mid: mid}
if res, err = s.accRPC.Profile3(c, arg); err != nil {
dao.PromError("award:Profile3")
log.Errorv(c, log.KV("log", "Profile3"), log.KV("err", err))
}
return
}

View File

@@ -0,0 +1,16 @@
package service
import (
"context"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestAward(t *testing.T) {
Convey("award", t, func() {
err := s.award(context.TODO(), 1, time.Now().Unix(), "127.0.0.1")
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,40 @@
package service
import (
"context"
"go-common/app/job/main/coin/dao"
coinmdl "go-common/app/service/main/coin/model"
memmdl "go-common/app/service/main/member/api"
"go-common/library/log"
)
func (s *Service) addExp(c context.Context, mid int64, count float64, reason, ip string) (err error) {
argExp := &memmdl.AddExpReq{
Mid: mid,
Count: count,
Operate: "coin",
Reason: reason,
Ip: ip,
}
if count <= 0 {
log.Errorv(c, log.KV("log", "add exp count < 0"), log.KV("mid", mid), log.KV("err", err), log.KV("reason", reason), log.KV("count", count))
dao.PromError("exp:addExp0")
return
}
if _, err = s.memRPC.UpdateExp(c, argExp); err != nil {
log.Errorv(c, log.KV("log", "s.coinDao.IncrExp()"), log.KV("mid", mid), log.KV("err", err), log.KV("reason", reason), log.KV("count", count))
dao.PromError("exp:addExp")
return
}
return
}
func (s *Service) addCoinExp(c context.Context, mid, tp, number int64, ip string) (err error) {
arg := &coinmdl.ArgAddUserCoinExp{Mid: mid, Business: tp, Number: number, RealIP: ip}
if err = s.coinRPC.AddUserCoinExp(c, arg); err != nil {
log.Errorv(c, log.KV("log", "AddUserCoinExp"), log.KV("err", err))
dao.PromError("exp:addCoinExp")
}
return
}

View File

@@ -0,0 +1,163 @@
package service
import (
"context"
"encoding/json"
"sync"
"time"
"go-common/app/job/main/coin/conf"
"go-common/app/job/main/coin/dao"
"go-common/app/job/main/coin/model"
accrpc "go-common/app/service/main/account/api"
arcrpc "go-common/app/service/main/archive/api/gorpc"
coinrpc "go-common/app/service/main/coin/api/gorpc"
coinmdl "go-common/app/service/main/coin/model"
memrpc "go-common/app/service/main/member/api"
"go-common/library/log"
"go-common/library/queue/databus"
"go-common/library/queue/databus/databusutil"
)
// Service coin job service.
type Service struct {
coinDao *dao.Dao
c *conf.Config
waiter *sync.WaitGroup
accRPC accrpc.AccountClient
memRPC memrpc.MemberClient
arcRPC *arcrpc.Service2
coinRPC *coinrpc.Service
databus *databus.Databus
group *databusutil.Group
expGroup *databusutil.Group
}
// New new and return service.
func New(c *conf.Config) (s *Service) {
s = &Service{
coinDao: dao.New(c),
c: c,
waiter: new(sync.WaitGroup),
arcRPC: arcrpc.New2(c.ArchiveRPC),
coinRPC: coinrpc.New(c.CoinRPC),
}
var err error
if s.memRPC, err = memrpc.NewClient(c.MemRPC); err != nil {
panic(err)
}
if s.accRPC, err = accrpc.NewClient(c.AccountRPC); err != nil {
panic(err)
}
s.databus = databus.New(c.Databus)
g := databusutil.NewGroup(c.Databusutil, databus.New(c.LoginDatabus).Messages())
g.New = newMsg
g.Split = split
g.Do = s.awardDo
g.Start()
s.group = g
eg := databusutil.NewGroup(c.Databusutil, databus.New(c.ExpDatabus).Messages())
eg.New = newExpMsg
eg.Split = split
eg.Do = s.awardDo
eg.Start()
s.expGroup = eg
s.waiter.Add(1)
go s.consumeproc()
go s.settleproc()
return
}
func (s *Service) consumeproc() {
defer s.waiter.Done()
var (
msg *databus.Message
err error
ok bool
period *model.CoinSettlePeriod
ctx = context.TODO()
)
for {
if msg, ok = <-s.databus.Messages(); !ok {
log.Error("s.databus.Message err(%v)", err)
return
}
r := &coinmdl.Record{}
if err = json.Unmarshal([]byte(msg.Value), r); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", msg.Value, err)
dao.PromError("msg:JSON")
continue
}
var ip = r.IPV6
for i := 0; i < 3; i++ {
if err = s.addCoinExp(ctx, r.Mid, r.AvType, r.Multiply, ip); err != nil {
time.Sleep(time.Millisecond * 50)
continue
}
break
}
if err != nil {
log.Errorv(ctx, log.KV("log", "fix: addCoinExp"), log.KV("mid", r.Mid), log.KV("type", r.AvType), log.KV("num", r.Multiply), log.KV("ip", ip))
continue
}
if err = s.updateAddCoin(ctx, r); err != nil {
continue
}
at := time.Unix(r.Timestamp, 0)
if period, err = s.coinDao.HitSettlePeriod(ctx, at); err != nil {
log.Errorv(ctx, log.KV("log", "s.coinDao.HitCoinPeriod"), log.KV("record", r), log.KV("err", err))
dao.PromError("service:HitSettlePeriod")
continue
}
for i := 0; ; i++ {
if err = s.coinDao.UpsertSettle(ctx, period.ID, r.Up, r.Aid, r.AvType, r.Multiply, time.Now()); err != nil {
log.Error("s.coinDao.UpsertCoinSettle(%d, %d, %d) error(%v)", r.Up, r.Aid, r.Multiply)
dao.PromError("service:UpsertSettle")
i++
if i > 5 {
// if env.DeployEnv == env.DeployEnvProd {
// s.moni.Sms(ctx, s.c.Sms.Phone, s.c.Sms.Token, "coin-job upsetSettle fail for 5 time")
// }
break
}
continue
}
break
}
log.Info("key: %s,partion:%d,offset:%d success %s", msg.Key, msg.Partition, msg.Offset, msg.Value)
err = msg.Commit()
if err != nil {
log.Error("msg.Commit partition:%d offset:%d err %v", msg.Partition, msg.Offset, err)
dao.PromError("service:msgCommit")
}
}
}
// Close close service.
func (s *Service) Close() {
s.group.Close()
s.expGroup.Close()
s.databus.Close()
}
// Wait wait routine unitl all close.
func (s *Service) Wait() {
s.waiter.Wait()
}
// Ping check service health.
func (s *Service) Ping(c context.Context) error {
return s.coinDao.Ping(c)
}
func (s *Service) updateAddCoin(c context.Context, record *coinmdl.Record) (err error) {
if record == nil {
return
}
if err = s.coinRPC.UpdateAddCoin(c, record); err != nil {
log.Errorv(c, log.KV("log", "UpdateAddCoin"), log.KV("mid", record.Mid), log.KV("record", record))
dao.PromError("service:updateAddCoin")
}
return
}

View File

@@ -0,0 +1,36 @@
package service
import (
"context"
"flag"
"path/filepath"
"testing"
"time"
"go-common/app/job/main/coin/conf"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func init() {
dir, _ := filepath.Abs("../cmd/coin-job-test.toml")
flag.Set("conf", dir)
if err := conf.Init(); err != nil {
panic("conf.Init() error")
}
s = New(conf.Conf)
time.Sleep(time.Second * 1)
}
func TestService(t *testing.T) {
Convey("mock", t, func() {
s.Close()
s.Redo(0)
s.Ping(context.TODO())
s.Wait()
})
}

View File

@@ -0,0 +1,208 @@
package service
import (
"context"
"fmt"
"sync"
"time"
"go-common/app/job/main/coin/dao"
"go-common/app/job/main/coin/model"
"go-common/app/service/main/archive/api"
comarcmdl "go-common/app/service/main/archive/model/archive"
"go-common/library/log"
)
var lockRedo sync.Mutex
var lockSettle sync.Mutex
const (
_expPerCoin = 1
)
func (s *Service) settleproc() {
for {
now := time.Now()
tmp := now.AddDate(0, 1, 0)
// sleep one month
time.Sleep(time.Date(tmp.Year(), tmp.Month(), 1, 0, 0, 0, 0, tmp.Location()).Sub(now))
// settle coin
s.Settle(0)
}
}
// Redo redo settle.
func (s *Service) Redo(tableID int64) (err error) {
lockRedo.Lock()
defer lockRedo.Unlock()
ctx := context.TODO()
// if tableID has been seted than run test
if tableID != 0 {
return s.redo(ctx, tableID)
}
now := time.Now()
day := now.Day()
if day <= 25 {
err = fmt.Errorf("redo must after every 25th of each month, today is %d ", day)
return
}
period, err := s.coinDao.HitSettlePeriod(ctx, now)
if err != nil {
log.Error("job s.coinDao.HitCoinPeriod error(%v)", err)
return
}
return s.redo(ctx, period.ID-1)
}
func (s *Service) redo(ctx context.Context, tableID int64) (err error) {
period, err := s.coinDao.SettlePeriod(ctx, tableID)
if err != nil {
log.Error("s.coinDao.SettlePeriod(%d) error(%v)", tableID, err)
return
}
if err = s.coinDao.ClearCoinCount(ctx, tableID, time.Now()); err != nil {
log.Error("s.coinDao.ClearCoinCount(%d) error(%v)", tableID, err)
return
}
startTime := time.Date(period.FromYear, time.Month(period.FromMonth), period.FromDay, 0, 0, 0, 0, time.Local)
endTime := time.Date(period.ToYear, time.Month(period.ToMonth), period.ToDay, 0, 0, 0, 0, time.Local)
var (
coins map[int64]int64
aids []int64
aidMids map[int64]int64
argAids *comarcmdl.ArgAids2
arcs map[int64]*api.Arc
peer = 100
)
for i := 0; i < dao.SHARDING; i++ {
if coins, err = s.coinDao.TotalCoins(ctx, i, startTime, endTime); err != nil {
log.Error("s.coinDao.TotalCoins(%d, %v, %v) error(%v)", i, startTime, endTime, err)
return
}
aids = make([]int64, 0, len(coins))
for aid := range coins {
if aid%1000 == 1 {
aids = append(aids, aid/1000)
}
}
var (
length = len(aids)
cop = length / peer
mod = length % peer
)
aidMids = make(map[int64]int64, length)
for i := 0; i < cop; i++ {
argAids = &comarcmdl.ArgAids2{
Aids: aids[i*peer : peer*(i+1)],
}
if arcs, err = s.arcRPC.Archives3(ctx, argAids); err != nil {
log.Error("s.arcRPC.Archives2 error(%v)", err)
return
}
for aid, arc := range arcs {
aidMids[aid] = arc.Author.Mid
}
}
if mod != 0 {
argAids = &comarcmdl.ArgAids2{
Aids: aids[cop*peer:],
}
if arcs, err = s.arcRPC.Archives3(ctx, argAids); err != nil {
log.Error("s.arcRPC.Archives2 error(%v)", err)
return
}
for aid, arc := range arcs {
aidMids[aid] = arc.Author.Mid
}
}
for aid, count := range coins {
if mid, ok := aidMids[aid/1000]; ok {
if err = s.coinDao.UpsertSettle(ctx, tableID, mid, aid/1000, aid%1000, count, time.Now()); err != nil {
log.Error("s.coinDao.UpdateCoinCount(%d, %d, %d) error(%v)", tableID, aid, count, err)
return
}
}
}
}
return
}
// Settle do settle by table.
func (s *Service) Settle(tableID int64) (err error) {
lockSettle.Lock()
defer lockSettle.Unlock()
ctx := context.TODO()
// if tableID has been seted than run test
if tableID != 0 {
return s.settle(ctx, tableID)
}
period, err := s.coinDao.HitSettlePeriod(ctx, time.Now())
if err != nil {
log.Error("job s.coinDao.HitCoinPeriod error(%v)", err)
return
}
return s.settle(ctx, period.ID-1)
}
// job exec at every 1th of each month
func (s *Service) settle(ctx context.Context, tableID int64) (err error) {
var (
settles []*model.CoinSettle
)
period, err := s.coinDao.SettlePeriod(ctx, tableID)
if err != nil {
log.Error("s.coinDao.SettlePeriod(%d) error(%v)", tableID, err)
return
}
var i, maxid int64
for {
if settles, maxid, err = s.coinDao.Every10000(ctx, tableID, i); err != nil {
log.Error("settle: job s.coinDao.Every10000(%d) error(%v)", tableID, err)
time.Sleep(time.Second)
continue
}
if maxid == i {
log.Info("settle: maxid %d", maxid)
fmt.Println("maxid", maxid)
return
}
for _, settle := range settles {
if settle.State == 1 || settle.Mid == 0 {
continue
}
settle.ExpTotal = settle.CoinCount*_expPerCoin - settle.ExpSub
if settle.ExpTotal <= 0 {
log.Errorv(ctx, log.KV("log", "settle: ExpTotal err"), log.KV("count", settle.ExpTotal))
continue
}
// add exp
var reason string
switch settle.AvType {
case 1:
reason = fmt.Sprintf("%d.%d-%d.%d视频av%d投币获得奖励", period.FromMonth, period.FromDay, period.ToMonth, period.ToDay, settle.Aid)
case 2:
reason = fmt.Sprintf("%d.%d-%d.%d文章cv%d投币获得奖励", period.FromMonth, period.FromDay, period.ToMonth, period.ToDay, settle.Aid)
case 3:
reason = fmt.Sprintf("%d.%d-%d.%d音乐mv%d投币获得奖励", period.FromMonth, period.FromDay, period.ToMonth, period.ToDay, settle.Aid)
}
for i := 0; i < 3; i++ {
if err = s.addExp(ctx, settle.Mid, float64(settle.ExpTotal), reason, ""); err != nil {
time.Sleep(time.Second)
} else {
break
}
}
if err != nil {
log.Errorv(ctx, log.KV("log", "s.accRPC.AddExp2"), log.KV("mid", settle.Mid), log.KV("err", err))
continue
}
if err = s.coinDao.UpdateSettle(ctx, tableID, settle.ID, settle.ExpTotal, time.Now()); err != nil {
log.Error("settle: s.coinDao.UpdateState(%d, %d, %d) error(%v)", tableID, settle.ID, settle.ExpTotal, err)
continue
}
log.Info("settle: aid(%d) add exp(%d) success", settle.Aid, settle.ExpTotal)
}
fmt.Printf("settle:%d ,len(settle) %d\n", i, len(settles))
i = maxid
}
}