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/coin/api:all-srcs",
"//app/service/main/coin/cmd:all-srcs",
"//app/service/main/coin/conf:all-srcs",
"//app/service/main/coin/dao:all-srcs",
"//app/service/main/coin/model:all-srcs",
"//app/service/main/coin/server/gorpc:all-srcs",
"//app/service/main/coin/server/grpc:all-srcs",
"//app/service/main/coin/server/http:all-srcs",
"//app/service/main/coin/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,442 @@
#### 硬币基础服务
#### V2.16.6
> 1. 修复硬币修改-104问题
#### V2.16.5
> 1. 异步add cache
#### V2.16.4
> 1. 修复投币强依赖redis的问题
#### V2.16.3
> 1. add nolog
#### V2.16.2
> 1. add type to log
#### V2.16.1
> 1. 使用member grpc 替换老的java 接口
#### V2.16.0
> 1. 异步发消息
#### V2.15.1
> 1. 移动grpc目录
#### V2.15.0
> 1. remove net/ip
#### V2.14.1
> 1. bazel编译
#### V2.14.0
> 1. 拜年祭需求
#### V2.13.1
> 1. 修复缓存miss时可以重复投币的bug
#### V2.13.0
> 1. 移除掉对稿件的依赖
#### V2.12.0
> 1. 增加item/coins接口
#### V2.11.0
> 1. log增加oid
#### V2.10.4
> 1. 初始化dao层ut
> 2. 移除hbase
#### V2.10.3
> 1. 去掉remote ip
#### V2.10.2
> 1. gorpc不注册zk
#### V2.10.1
> 1. 修改proto文件
#### V2.10.0
> 1. 使用新verify
#### V2.9.2
> 1. 移动grpc目录
#### V2.9.1
> 1. 修复addCoins bug
#### V2.9.0
> 1. 使用新的目录结构
> 2. service统一名称
> 3. 重构grpc 增加req的验证
> 4. grpc增加client代码
#### V2.8.0
> 1. 使用es sdk查询日志
#### V2.7.0
> 1. 增加内网投币接口
#### V2.6.0
> 1. 新增grpc
#### V2.5.0
> 1. 去掉account service调用
#### V2.4.1
> 1. 去掉debug和trace配置
#### V2.4.0
> 1. 统一业务标志
#### V2.3.1
> 1. 用户投币列表加空缓存
#### V2.3.0
> 1. remove stat-T articleStat-T topic
#### V2.2.0
> 1. add todayExp rpc
#### V2.1.1
> 1. 修复硬币记录查询时间问题
#### V2.1.0
> 1. 硬币记录查询切换到es
> 2. 修改硬币数接口支持保存操作人
#### V2.0.7
> 1. 升级tools/cache
#### V2.0.6
> 1. 使用bm
#### V2.0.5
> 1. 增加register
#### V2.0.4
> 1. 修复小数问题
#### V2.0.3
> 1. 改硬币增加info日志
#### V2.0.2
> 1. 修复小数问题
#### V2.0.1
> 1. 修复热加载问题
### Version 2.0.0
> 1. 代码整体重构 增加prom监控 移除无用的代码
> 2. 硬币数*100的转换下沉到dao层去做 对service透明
> 3. business放入配置中 不再写死
> 4. 投币日志接入行为日志平台
> 5. 投币接口去掉登录态校验
> 6. 缓存工具重构代码
> 7. 修复经验先加缓存再加经验的问题 改为先加经验再加缓存
> 8. 投币改为事务操作 其他表的修改 放入job去做
> 9. 投币改为走消息队列 不再用异步channel防止消息丢失
> 10. 投币时去掉多余的投币总数查询sql
> 11. 投币记录增加日志转换功能 提供转换后的日志
> 12. 使用common/cache替换之前项目自己实现的异步channel
> 13. 修复up主硬币为负数 用户无法投币的问题
### Version 1.26.0
> 1. 增加UserLog rpc
### Version 1.25.8
> 1. 去掉反作弊日志上报
### Version 1.25.7
> 1. 修复帐号未激活的问题
### Version 1.25.6
> 1. 使用account-service v7
### Version 1.25.4
> 1. 修复第一次投币双倍的问题
### Version 1.25.3
> 1. 修复音乐验证问题
### Version 1.25.2
> 1. 硬币记录只返回一周数据
> 2. 修复投币记录负数溢出的问题
### Version 1.25.1
> 1.fix coin zero val
### Version 1.25.0
> 1.change user coin to memcache
### Version 1.24.1
> 1.删除经验补偿逻辑
### Version 1.23.0
> 1.添加checkzero检验
> 2.增加硬币数返回值
### Version 1.22.0
> 1.添加list接口
### Version 1.21.0
> 1.添加修改硬币rpc方法
### Version 1.20.0
> 1.硬币去除java依赖
### Version 1.19.0
> 1.open auth
### Version 1.18.3
> 1.修复时间戳转换
### Version 1.18.2
> 1.修复硬币记录
### Version 1.18.1
> 1.补硬币记录
### Version 1.18.0
> 1.登陆奖励接口去重
### Version 1.17.2
> 1.修复日志读取scan bug
### Version 1.17.1
> 1.增加用户ip获取
### Version 1.17.0
> 1.双写硬币计数流
### Version 1.16.2
> 1.音乐投币加经验
### Version 1.16.1
> 1.ingore duplicate key.
### Version 1.16.0
> 1.增加音频投币reason
### Version 1.15.0
> 1.添加稿件硬币数接口
### Version 1.14.1
> 1. add log proc
### Version 1.14.0
> 1. 用户硬币重构
### Version 1.13.0
> 1.添加硬币防刷
### Version 1.12.1
> 1.修复rpc参数
### Version 1.12.0
> 1.接入archive pb
> 2.去除ding
### Version 1.11.2
> 1.修复redis没有设置过期时间
> 2.优化稿件投币缓存只展示最近20个稿件投币
### Version 1.11.1
> 1.修复分表sharding
### Version 1.11.0
> 1.迁移大仓库
### Version 1.10.2
> 1.修改硬币模板
#### Version 1.10.1
> 1. 修复投币奖励模板
#### Version 1.10.0
> 1.文章投币
#### Version 1.9.6
> 1.规范spy header
#### Version 1.9.5
> 1.兼容帐号硬币接口
#### Version 1.9.4
> 1.上报反作弊数据
#### Version 1.9.3
> 1.切换发布平台
#### Version 1.9.2
> 1.升级基础包
#### Version 1.9.1
> 1.修复硬币可以扣为负数
#### Version 1.9.0
> 1.稿件统计计数-大数据更新接口
#### Version 1.8.9
> 1.粉丝勋章
#### Version 1.8.8
> 1.update go-common and go-business
#### Version 1.8.7
> 1.接入prom监控
#### Version 1.8.6
> 1.一天内同一个稿件的投币自动合并计数
#### Version 1.8.3
> 1.剔除archive hbase计数和DB更新
#### Version 1.8.2
> 1.更改missch为1024
#### Version 1.8.0
> 1.接入新配置中心
#### Version 1.7.8
> 1.修复hbase context
#### Version 1.7.7
> 1.升级go-business到v2.7.0
> 2.更新monitorPing
#### Version 1.7.6
> 1.添加查询用户投币rpc接口
#### Version 1.7.5
> 1.剔除archivePGC上报大数据
#### Version 1.7.4
> 1.剔除account-service依赖
#### Version 1.7.2
> 1.支持配置中心版本
#### Version 1.7.1
> 1.更新go-common、golang、go-business包
#### Version 1.7.0
> 1.硬币库从老库迁移到新库
> 2.新增稿件投币总数表
#### Version 1.6.3
> 1.去掉databus v1
> 2.去掉写daily库
> 3.ding异步
#### Version 1.6.2
> 1.升级vendor
#### Version 1.6.1
> 1.升级vendor
> 2.硬币数双写
#### Version 1.6.0
> 1.databus双写V1和V2
> 2.2016/11/25 00:00后写推送投币记录给coin-job
#### Version 1.5.2
> 1.trace v2
#### Version 1.5.1
> 1.fix添加硬币bug
#### Version 1.5.0
> 1.临时版本2016/11/01 00:00之后投币将不再增加upper经验
> 2.取消特权判断
#### Version 1.4.0
> 1.硬币数写hbase由增量写改为写绝对值
#### Version 1.3.1
> 1.databus消息新增type和pgc_id两个字段
#### Version 1.3.0
> 1.投币记录推送到databus给大数据动态使用
#### Version 1.2.8
> 1.优化日志显示
#### Version 1.2.7
> 1.修改投币上报描述
#### Version 1.2.6
> 1. 修复稿件计数
#### Version 1.2.5
> 1.增加稿件状态判断
#### Version 1.2.4
> 1.删除多余参数
#### Version 1.2.3
> 1.修复bug初始化max_coin
#### Version 1.2.2
> 1.允许PC端没绑定手机进行投币
#### Version 1.2.1
> 1.更新稿件投币动态
#### Version 1.2.0
> 1.细化返回错误码
> 2.接入trace2
> 3.增加upper主视频收益
#### Version 1.1.1
> 1.兼容list请求参数错误
#### Version 1.1.0
> 1.获取当日投币经验
#### Version 1.0.0
> 1.投币业务重构
> 2.投币业务接口
> 3.投币历史记录查询

View File

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

View File

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

View File

@@ -0,0 +1,60 @@
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 = "api_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "api_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_grpc"],
importpath = "go-common/app/service/main/coin/api",
proto = ":api_proto",
tags = ["automanaged"],
deps = ["@com_github_gogo_protobuf//gogoproto:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
embed = [":api_go_proto"],
importpath = "go-common/app/service/main/coin/api",
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",
"//app/service/main/coin/api/gorpc:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
// +bili:type=service
// Code generated by warden.
syntax = "proto3";
package community.service.coin.v1;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "api";
// AddCoinReply reply
message AddCoinReply {
}
// AddCoinReq req
message AddCoinReq {
// ip
string ip = 1 [(gogoproto.jsontag) = "ip", (gogoproto.customname) = "IP"];
// mid
int64 mid = 2 [(gogoproto.moretags) = "form:\"mid\" validate:\"required,min=1\""];
// up 主mid
int64 upmid = 3 [(gogoproto.moretags) = 'form:"upid" validate:"required"'];
// 最大投币数
int64 max_coin = 4 [(gogoproto.moretags) = 'form:"max"'];
// aid
int64 aid = 5 [(gogoproto.moretags) = "form:\"aid\" validate:\"required\""];
// 业务
string business = 6 [(gogoproto.moretags) = "form:\"business\" validate:\"required\""];
// 数量
int64 number = 7 [(gogoproto.moretags) = "form:\"number\" validate:\"required,min=1\""];
// 稿件typeid(稿件专用)
int32 typeid = 8 [(gogoproto.moretags) = "form:\"typeid\""];
// 稿件发布时间
int64 pub_time = 9 [(gogoproto.moretags) = "form:\"pub_time\""];
}
// AddUserCoinExpReply add coin exp reply
message AddUserCoinExpReply {
}
// AddUserCoinExpReq req
message AddUserCoinExpReq {
// ip
string ip = 1 [(gogoproto.jsontag) = "ip", (gogoproto.customname) = "IP"];
// mid
int64 mid = 2;
// business
string business = 3;
// number
int64 number = 4;
}
// CoinsLogReply reply
message CoinsLogReply {
// log
repeated ModelLog list = 1;
}
// CoinsLogReq req
message CoinsLogReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = "form:\"mid\" validate:\"required,min=1\""];
// 返回最近一周还是全部
bool recent = 2 [(gogoproto.moretags) = "form:\"recent\""];
// 翻译后的格式 还是原始格式
bool translate = 3 [(gogoproto.moretags) = "form:\"translate\""];
}
// ItemUserCoinsReply reply
message ItemUserCoinsReply {
// number
int64 number = 1[(gogoproto.jsontag) = "number"] ;
}
// ItemUserCoinsReq req
message ItemUserCoinsReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = 'form:"mid" validate:"required"'];
// aid
int64 aid = 2 [(gogoproto.moretags) = "form:\"aid\" validate:\"required\""];
// 业务
string business = 3 [(gogoproto.moretags) = "form:\"business\" validate:\"required\""];
}
// ListReply reply
message ListReply {
// log
repeated ModelList list = 1;
}
// ListReq .
message ListReq {
// mid
int64 mid = 1;
// business
string business = 2;
// 时间戳
int64 ts = 3;
}
// ModelArchiveUserCoins .
message ModelArchiveUserCoins {
// number
int64 number = 1;
}
// ModelArgModifyCoin .
message ModelArgModifyCoin {
// mid
int64 mid = 1;
// count
double count = 2;
// 原因
string reason = 3;
// ip
string ip = 4 [(gogoproto.jsontag) = "ip", (gogoproto.customname) = "IP"];
// 操作人
string operator = 5;
// 是否要检查余额数量 默认检查 为1则不检查
int32 check_zero = 6;
}
// ModelList .
message ModelList {
// aid
int64 aid = 1;
// number
int64 number = 2;
// 时间戳
int64 ts = 3;
// ip
uint32 ip = 4 [(gogoproto.jsontag) = "ip", (gogoproto.customname) = "IP"];
}
// ModelLog .
message ModelLog {
// 修改前硬币数
double from = 1;
// 修改后硬币数
double to = 2;
// ip
string ip = 3 [(gogoproto.jsontag) = "ip", (gogoproto.customname) = "IP"];
// 原因
string desc = 4;
// 时间戳
int64 time_stamp = 5;
}
// ModelRecord record
message ModelRecord {
// aid
int64 aid = 1;
// mid
int64 mid = 2;
// up主id
int64 up = 3;
// 时间戳
int64 timestamp = 4;
// number
int64 number = 5;
// 业务
string business = 6;
// ip
uint32 ip = 7 [(gogoproto.jsontag) = "ip", (gogoproto.customname) = "IP"];
}
// ModifyCoinsReply reply
message ModifyCoinsReply {
// result
double result = 1;
}
// ModifyCoinsReq req
message ModifyCoinsReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = "form:\"mid\" validate:\"required\""];
// 变更的计数 例如10为硬币数加10
double count = 2 [(gogoproto.moretags) = "form:\"count\" validate:\"required\""];
// 改变的原因
string reason = 3 [(gogoproto.moretags) = "form:\"reason\" validate:\"required\""];
// ip
string ip = 4 [(gogoproto.jsontag) = "ip", (gogoproto.customname) = "IP"];
// 操作人
string operator = 5 [(gogoproto.moretags) = "form:\"operator\""];
// 是否要检查余额数量 默认检查 为1则不检查
int32 check_zero = 6 [(gogoproto.moretags) = "form:\"check_zero\""];
// 时间戳
int64 ts = 7;
}
// TodayExpReply reply
message TodayExpReply {
// exp
int64 exp = 1;
}
// TodayExpReq req
message TodayExpReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = "form:\"mid\" validate:\"required,min=1\""];
}
// UpdateAddCoinReply reply
message UpdateAddCoinReply {
}
// UpdateAddCoinReq req
message UpdateAddCoinReq {
// aid
int64 aid = 1;
// mid
int64 mid = 2;
// up mid
int64 up = 3;
// timestamp
int64 timestamp = 4;
// number
int64 number = 5;
// business
string business = 6;
// ip
uint32 ip = 7 [(gogoproto.jsontag) = "ip", (gogoproto.customname) = "IP"];
string ipv6 = 8 [(gogoproto.jsontag) = "ip_v6", (gogoproto.customname) = "IPV6"];
}
// UserCoinsReply reply
message UserCoinsReply {
// count
double count = 1;
}
// UserCoinsReq req
message UserCoinsReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = "form:\"mid\" validate:\"required,min=1\""];
}
// Coin rpc
service Coin {
// AddCoin add coin. 投币接口
rpc AddCoin(AddCoinReq) returns(AddCoinReply);
// ItemUserCoins get coins added of archive. 投币数量接口
rpc ItemUserCoins(ItemUserCoinsReq) returns(ItemUserCoinsReply);
// UserCoins get user coins. 用户硬币余额
rpc UserCoins(UserCoinsReq) returns(UserCoinsReply);
// ModifyCoins modify user coins. 修改硬币数
rpc ModifyCoins(ModifyCoinsReq) returns(ModifyCoinsReply);
// List get coin added list.投币列表
rpc List(ListReq) returns(ListReply);
// CoinsLog coins log 投币日志
rpc CoinsLog(CoinsLogReq) returns(CoinsLogReply);
// AddUserCoinExp add user coin exp for job
rpc AddUserCoinExp(AddUserCoinExpReq) returns(AddUserCoinExpReply);
// UpdateAddCoin update db after add coin for job.
rpc UpdateAddCoin(UpdateAddCoinReq) returns(UpdateAddCoinReply);
// TodayExp get today coin added exp. 今日投币经验
rpc TodayExp(TodayExpReq) returns(TodayExpReply);
}

View File

@@ -0,0 +1,25 @@
package api
import (
"context"
"fmt"
"go-common/library/net/rpc/warden"
"google.golang.org/grpc"
)
// AppID .
const AppID = "community.service.coin"
// NewClient new grpc client
func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (CoinClient, error) {
client := warden.NewClient(cfg, opts...)
cc, err := client.Dial(context.Background(), fmt.Sprintf("discovery://default/%s", AppID))
if err != nil {
return nil, err
}
return NewCoinClient(cc), nil
}
//go:generate $GOPATH/src/go-common/app/tool/warden/protoc.sh

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 = ["coin_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//app/service/main/coin/model:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"coin.go",
"mock.go",
],
importpath = "go-common/app/service/main/coin/api/gorpc",
tags = ["automanaged"],
deps = [
"//app/service/main/coin/model:go_default_library",
"//library/net/rpc:go_default_library",
"//vendor/github.com/golang/mock/gomock: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,104 @@
package coin
import (
"context"
"go-common/app/service/main/coin/model"
"go-common/library/net/rpc"
)
const (
_addCoins = "RPC.AddCoins"
_archiveUserCoins = "RPC.ArchiveUserCoins"
_userCoins = "RPC.UserCoins"
_modifyCoin = "RPC.ModifyCoin"
_list = "RPC.List"
_userLog = "RPC.UserLog"
_addUserCoinExp = "RPC.AddUserCoinExp"
_updateAddCoin = "RPC.UpdateAddCoin"
_todayExp = "RPC.TodayExp"
)
const (
_appid = "community.service.coin"
)
var (
_noRes = &struct{}{}
)
// Service rpc service.
type Service struct {
client *rpc.Client2
}
//go:generate mockgen -source coin.go -destination mock.go -package coin
// RPC coin rpc
type RPC interface {
AddCoins(c context.Context, arg *model.ArgAddCoin) (err error)
ArchiveUserCoins(c context.Context, arg *model.ArgCoinInfo) (res *model.ArchiveUserCoins, err error)
UserCoins(c context.Context, arg *model.ArgCoinInfo) (count float64, err error)
}
// New new service.
func New(c *rpc.ClientConfig) (s *Service) {
s = &Service{}
s.client = rpc.NewDiscoveryCli(_appid, c)
return
}
// AddCoins coin to archive.
func (s *Service) AddCoins(c context.Context, arg *model.ArgAddCoin) (err error) {
err = s.client.Call(c, _addCoins, arg, _noRes)
return
}
// ArchiveUserCoins get archive User added coins.
func (s *Service) ArchiveUserCoins(c context.Context, arg *model.ArgCoinInfo) (res *model.ArchiveUserCoins, err error) {
res = &model.ArchiveUserCoins{}
err = s.client.Call(c, _archiveUserCoins, arg, res)
return
}
// UserCoins get user coins.
func (s *Service) UserCoins(c context.Context, arg *model.ArgCoinInfo) (count float64, err error) {
err = s.client.Call(c, _userCoins, arg, &count)
return
}
// ModifyCoin modify user coin.
func (s *Service) ModifyCoin(c context.Context, arg *model.ArgModifyCoin) (count float64, err error) {
err = s.client.Call(c, _modifyCoin, arg, &count)
return
}
// List coin added list.
func (s *Service) List(c context.Context, arg *model.ArgList) (list []*model.List, err error) {
err = s.client.Call(c, _list, arg, &list)
return
}
// UserLog user log
func (s *Service) UserLog(c context.Context, arg *model.ArgLog) (logs []*model.Log, err error) {
err = s.client.Call(c, _userLog, arg, &logs)
return
}
// AddUserCoinExp add user coin exp
func (s *Service) AddUserCoinExp(c context.Context, arg *model.ArgAddUserCoinExp) (err error) {
err = s.client.Call(c, _addUserCoinExp, arg, _noRes)
return
}
// UpdateAddCoin update db after add coin for job.
func (s *Service) UpdateAddCoin(c context.Context, arg *model.Record) (err error) {
err = s.client.Call(c, _updateAddCoin, arg, _noRes)
return
}
// TodayExp get today exp
func (s *Service) TodayExp(c context.Context, arg *model.ArgMid) (number int64, err error) {
err = s.client.Call(c, _todayExp, arg, &number)
return
}

View File

@@ -0,0 +1,38 @@
package coin
import (
"context"
"testing"
"time"
coin "go-common/app/service/main/coin/model"
)
const (
mid = 23675773
aid = 1
realIP = "127.0.0.1"
)
func TestCoin(t *testing.T) {
s := New(nil)
time.Sleep(1 * time.Second)
// coin
testAddCoins(t, s)
testArchiveUserCoins(t, s)
}
func testAddCoins(t *testing.T, s *Service) {
arg := coin.ArgAddCoin{Mid: mid, Aid: aid, Multiply: 1, RealIP: realIP}
if err := s.AddCoins(context.TODO(), &arg); err != nil {
t.Logf("call.AddCoins error(%v)", err)
}
}
func testArchiveUserCoins(t *testing.T, s *Service) {
arg := coin.ArgCoinInfo{Mid: mid, Aid: aid, RealIP: realIP}
if res, err := s.ArchiveUserCoins(context.TODO(), &arg); err != nil && res != nil {
t.Logf("call.ArchiveUserCoins error(%v)", err)
}
}

View File

@@ -0,0 +1,73 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: coin.go
// Package coin is a generated GoMock package.
package coin
import (
context "context"
gomock "github.com/golang/mock/gomock"
model "go-common/app/service/main/coin/model"
reflect "reflect"
)
// MockRPC is a mock of RPC interface
type MockRPC struct {
ctrl *gomock.Controller
recorder *MockRPCMockRecorder
}
// MockRPCMockRecorder is the mock recorder for MockRPC
type MockRPCMockRecorder struct {
mock *MockRPC
}
// NewMockRPC creates a new mock instance
func NewMockRPC(ctrl *gomock.Controller) *MockRPC {
mock := &MockRPC{ctrl: ctrl}
mock.recorder = &MockRPCMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockRPC) EXPECT() *MockRPCMockRecorder {
return m.recorder
}
// AddCoins mocks base method
func (m *MockRPC) AddCoins(c context.Context, arg *model.ArgAddCoin) error {
ret := m.ctrl.Call(m, "AddCoins", c, arg)
ret0, _ := ret[0].(error)
return ret0
}
// AddCoins indicates an expected call of AddCoins
func (mr *MockRPCMockRecorder) AddCoins(c, arg interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCoins", reflect.TypeOf((*MockRPC)(nil).AddCoins), c, arg)
}
// ArchiveUserCoins mocks base method
func (m *MockRPC) ArchiveUserCoins(c context.Context, arg *model.ArgCoinInfo) (*model.ArchiveUserCoins, error) {
ret := m.ctrl.Call(m, "ArchiveUserCoins", c, arg)
ret0, _ := ret[0].(*model.ArchiveUserCoins)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ArchiveUserCoins indicates an expected call of ArchiveUserCoins
func (mr *MockRPCMockRecorder) ArchiveUserCoins(c, arg interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ArchiveUserCoins", reflect.TypeOf((*MockRPC)(nil).ArchiveUserCoins), c, arg)
}
// UserCoins mocks base method
func (m *MockRPC) UserCoins(c context.Context, arg *model.ArgCoinInfo) (float64, error) {
ret := m.ctrl.Call(m, "UserCoins", c, arg)
ret0, _ := ret[0].(float64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UserCoins indicates an expected call of UserCoins
func (mr *MockRPCMockRecorder) UserCoins(c, arg interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserCoins", reflect.TypeOf((*MockRPC)(nil).UserCoins), c, arg)
}

View File

@@ -0,0 +1,2 @@
# API文档
http://info.bilibili.co/pages/viewpage.action?pageId=3692674

View File

@@ -0,0 +1,334 @@
{
"swagger": "2.0",
"info": {
"title": "go-common api",
"description": "api",
"version": "1.0",
"contact": {
"email": "lintanghui@bilibili.com"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"paths": {
"/x/internal/v1/coin/add": {
"get": {
"operationId": "/x/internal/v1/coin/add",
"parameters": [
{
"in": "query",
"name": "mid",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "max_coin",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "aid",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "business",
"type": "string"
},
{
"in": "query",
"name": "number",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "ip",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "upmid",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "typeid",
"type": "integer",
"format": "int32"
},
{
"in": "query",
"name": "pub_time",
"type": "integer",
"format": "int64"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/AddCoinReply",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/x/internal/v1/coin/user/count": {
"get": {
"operationId": "/x/internal/v1/coin/user/count",
"parameters": [
{
"in": "query",
"name": "mid",
"type": "integer",
"format": "int64"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/UserCoinsReply",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/x/internal/v1/coin/user/log": {
"get": {
"operationId": "/x/internal/v1/coin/user/log",
"parameters": [
{
"in": "query",
"name": "mid",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "recent",
"type": "boolean"
},
{
"in": "query",
"name": "translate",
"type": "boolean"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/CoinsLogReply",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/x/internal/v1/coin/user/modify": {
"post": {
"operationId": "/x/internal/v1/coin/user/modify",
"parameters": [
{
"in": "query",
"name": "ts",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "mid",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "count",
"type": "number",
"format": "double"
},
{
"in": "query",
"name": "reason",
"type": "string"
},
{
"in": "query",
"name": "ip",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "operator",
"type": "string"
},
{
"in": "query",
"name": "check_zero",
"type": "integer",
"format": "int32"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/ModifyCoinsReply",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
}
},
"definitions": {
"AddCoinReply": {
"title": "AddCoinReply",
"type": "object"
},
"CoinsLogReply": {
"title": "CoinsLogReply",
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/ModelLog",
"type": "object"
}
}
}
},
"ModelLog": {
"title": "ModelLog",
"required": [
"ip"
],
"type": "object",
"properties": {
"desc": {
"type": "string"
},
"from": {
"type": "number",
"format": "double"
},
"ip": {
"type": "string"
},
"time_stamp": {
"type": "integer",
"format": "int64"
},
"to": {
"type": "number",
"format": "double"
}
}
},
"ModifyCoinsReply": {
"title": "ModifyCoinsReply",
"type": "object",
"properties": {
"result": {
"type": "number",
"format": "double"
}
}
},
"UserCoinsReply": {
"title": "UserCoinsReply",
"type": "object",
"properties": {
"count": {
"type": "number",
"format": "double"
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
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-service-test.toml"],
importpath = "go-common/app/service/main/coin/cmd",
tags = ["automanaged"],
deps = [
"//app/service/main/coin/conf:go_default_library",
"//app/service/main/coin/server/gorpc:go_default_library",
"//app/service/main/coin/server/grpc:go_default_library",
"//app/service/main/coin/server/http:go_default_library",
"//app/service/main/coin/service:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus/report: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,192 @@
# This is a TOML document. Boom.
version = "1.0.0"
user = "nobody"
pid = "/tmp/coin-service.pid"
dir = "./"
perf = "127.0.0.1:6150"
checkFile = "/data/www/coin-service.html"
family = "coin-service"
address = "172.16.0.148"
dingURL = "http://api.bilibili.com/ding/dingcoin"
tagURL = "http://api.bilibili.com/x/tag/archive/tags"
accountURL = "http://account.bilibili.co"
[httpClient]
dial = "500ms"
timeout = "1s"
keepAlive = "60s"
timer = 10
key = "d6f625dd1867c36b"
secret = "eafdf317b90019bdef34917fd509390b"
[httpClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[log]
dir = "/data/log/coin-service/"
[log.agent]
taskID= "000003"
addr= "172.16.0.204:514"
proto= "tcp"
chan= 1024
[report]
family = "coin-service"
taskID = "000293"
proto = "unixgram"
addr = "/var/run/lancer/collector.sock"
chan = 10240
[dbCoinJob]
key = "0Pub71WwEMKXu63qtztu"
secret = "0Pub71WwEMKXu63qtztv"
group = "CoinJob-UGC-P"
topic = "CoinJob-T"
action = "pub"
name = "coin-job/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[dbBigData]
key = "0PtNTguCX35XCtPpjUGC"
secret= "0PtNTguCX35XCtPpjUGD"
group= "Coin-UGC-P"
topic= "Coin-T"
action="pub"
name = "coin-service/coin"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[grpc]
timeout = "1s"
addr = "0.0.0.0:6158"
[bm]
addr = "0.0.0.0:6152"
timeout = "1s"
[db]
[db.coin]
addr = "172.16.33.205:3306"
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_coin?timeout=5s&readTimeout=5s&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
[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"
[antispam]
on=true
second=15
n=30
hour=24
m=1000
[antispam.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"
[stat]
[stat.databus]
key = "0QEO9F8JuuIxZzNDvklH"
secret= "0QEO9F8JuuIxZzNDvklI"
group= "ArticleStat-Article-P"
topic= "ArticleStat-T"
action="pub"
name = "article/article-pub"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[tracer]
proto = "udp"
addr = "172.16.33.46:5140"
family = "coin-service"
[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"
ExpExpire = "72h"
[[Businesses]]
ID = 1
Name = "archive"
AddCoinReason = "Rating for %d : %d"
AddCoinUpperReason = "Rating for %d : {%d} from %d"
AddExpReason = "视频投币奖励"
[[Businesses]]
ID = 2
Name = "article"
AddCoinReason = "cv Rating for %d : %d"
AddCoinUpperReason = "cv Rating for %d : {%d} from %d"
AddExpReason = "文章投币奖励"
[[Businesses]]
ID = 3
Name = "audio"
AddCoinReason = "mv Rating for %d : %d"
AddCoinUpperReason = "mv Rating for %d : {%d} from %d"
AddExpReason = "音乐投币奖励"
[StatMerge]
Business = "archive"
Target = 8
Sources = [1,2,3]
[Coin]
ESLogURL= "http://bili-search.bilibili.co/x/admin/search/log"

View File

@@ -0,0 +1,60 @@
package main
import (
"context"
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/service/main/coin/conf"
"go-common/app/service/main/coin/server/gorpc"
grpc "go-common/app/service/main/coin/server/grpc"
"go-common/app/service/main/coin/server/http"
"go-common/app/service/main/coin/service"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/net/trace"
"go-common/library/queue/databus/report"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
trace.Init(conf.Conf.Tracer)
defer trace.Close()
ecode.Init(nil)
log.Info("coin-service start")
report.InitUser(conf.Conf.UserReport)
// service init
svr := service.New(conf.Conf)
rpcSvr := rpc.New(conf.Conf, svr)
grpcSvr := grpc.New(conf.Conf.GRPC, svr)
http.Init(conf.Conf, svr)
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("coin-service get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
grpcSvr.Shutdown(context.TODO())
rpcSvr.Close()
svr.Close()
time.Sleep(time.Second * 1)
log.Info("coin-service exit")
return
case syscall.SIGHUP:
// TODO reload
default:
return
}
}
}

View File

@@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/service/main/coin/conf",
tags = ["automanaged"],
deps = [
"//app/service/main/coin/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//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/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,140 @@
package conf
import (
"errors"
"flag"
"go-common/app/service/main/coin/model"
"go-common/library/cache/memcache"
"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/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/rpc"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
"go-common/library/queue/databus"
"go-common/library/time"
"github.com/BurntSushi/toml"
)
// global var
var (
confPath string
Conf = &Config{}
client *conf.Client
)
// Config config.
type Config struct {
// rpc server2
RPCServer *rpc.ServerConfig
GRPC *warden.ServerConfig
// BM
BM *bm.ServerConfig
// db
DB *DB
// redis
Redis *Redis
// databus
DbBigData *databus.Config
DbCoinJob *databus.Config
// new stat.
Stat *Stat
// rpc client
MemberRPC *warden.ClientConfig
// tracer
Tracer *trace.Config
// verify
Verify *verify.Config
// Log
Log *log.Config
Report *log.AgentConfig
// ding url
TagURL string
HTTPClient *bm.ClientConfig
// Antispam
Antispam *antispam.Config
// Memcache .
Memcache *Memcache
Businesses []*model.Business
Coin *Coin
UserReport *databus.Config
StatMerge *StatMerge
}
// Coin .
type Coin struct {
ESLogURL string
}
// StatMerge .
type StatMerge struct {
Business string
Target int64
Sources []int64
}
// Stat databus stat conf.
type Stat struct {
Databus *databus.Config
}
// DB db config.
type DB struct {
Coin *sql.Config
}
// Memcache mc config.
type Memcache struct {
*memcache.Config
Expire time.Duration
ExpExpire time.Duration
}
// Redis redis conf.
type Redis struct {
*redis.Config
Expire time.Duration
}
func configCenter() (err error) {
if client, err = conf.New(); err != nil {
panic(err)
}
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
}
func init() {
flag.StringVar(&confPath, "conf", "", "config path")
}
// Init init conf.
func Init() (err error) {
if confPath == "" {
return configCenter()
}
_, err = toml.DecodeFile(confPath, &Conf)
return
}

View File

@@ -0,0 +1,84 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"cache_test.go",
"dao.cache_test.go",
"dao_test.go",
"databus_test.go",
"log_test.go",
"mc.cache_test.go",
"mysql_coin_test.go",
"mysql_test.go",
"redis_test.go",
"relation_test.go",
"tag_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/coin/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
"//vendor/gopkg.in/h2non/gock.v1:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"cache.go",
"dao.cache.go",
"dao.go",
"databus.go",
"log.go",
"mc.cache.go",
"mysql.go",
"mysql_coin.go",
"redis.go",
"relation.go",
"tag.go",
],
importpath = "go-common/app/service/main/coin/dao",
tags = ["automanaged"],
deps = [
"//app/service/main/coin/api:go_default_library",
"//app/service/main/coin/conf:go_default_library",
"//app/service/main/coin/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/elastic:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/stat/prom:go_default_library",
"//library/sync/pipeline/fanout: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"],
)

View File

@@ -0,0 +1,56 @@
package dao
import (
"context"
"fmt"
"strconv"
"time"
)
func countKey(mid int64) string {
return "uct2_" + strconv.FormatInt(mid, 10)
}
func (d *Dao) cacheSFUserCoin(mid int64) string {
return "uct_" + strconv.FormatInt(mid, 10)
}
func itemCoinKey(aid, tp int64) string {
return fmt.Sprintf("itc_%d_%d", tp, aid)
}
func (d *Dao) cacheSFItemCoin(aid int64, tp int64) string {
return fmt.Sprintf("itc_%d_%d", aid, tp)
}
func expKey(mid int64) (key string) {
now := time.Now()
key = fmt.Sprintf("exp_%d_%02d%02d", mid, now.Month(), now.Day())
return
}
//go:generate $GOPATH/src/go-common/app/tool/cache/mc
type _mc interface {
// get user coin count.
// mc: -key=countKey
CacheUserCoin(c context.Context, mid int64) (count float64, err error)
// set user coin count
// mc: -key=countKey -expire=d.mcExpire
AddCacheUserCoin(c context.Context, mid int64, count float64) (err error)
// mc: -key=itemCoinKey
CacheItemCoin(c context.Context, aid int64, tp int64) (count int64, err error)
// mc: -key=itemCoinKey -expire=d.mcExpire
AddCacheItemCoin(c context.Context, aid int64, count int64, tp int64) (err error)
// mc: -key=expKey -type=get
Exp(c context.Context, mid int64) (exp int64, err error)
// mc: -key=expKey -expire=d.expireExp
SetTodayExpCache(c context.Context, mid int64, exp int64) (err error)
}
//go:generate $GOPATH/src/go-common/app/tool/cache/gen
type _cache interface {
// cache: -nullcache=-1 -check_null_code=$==-1 -singleflight=true
UserCoin(c context.Context, mid int64) (count float64, err error)
// cache: -nullcache=-1 -check_null_code=$==-1 -singleflight=true
ItemCoin(c context.Context, aid int64, tp int64) (count int64, err error)
}

View File

@@ -0,0 +1,69 @@
package dao
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaocountKey(t *testing.T) {
var (
mid = int64(123)
)
convey.Convey("countKey", t, func(ctx convey.C) {
p1 := countKey(mid)
ctx.Convey("p1 should not be nil", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeEmpty)
})
})
}
func TestDaocacheSFUserCoin(t *testing.T) {
var (
mid = int64(123)
)
convey.Convey("cacheSFUserCoin", t, func(ctx convey.C) {
p1 := d.cacheSFUserCoin(mid)
ctx.Convey("p1 should not be nil", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeEmpty)
})
})
}
func TestDaoitemCoinKey(t *testing.T) {
var (
aid = int64(1)
tp = int64(60)
)
convey.Convey("itemCoinKey", t, func(ctx convey.C) {
p1 := itemCoinKey(aid, tp)
ctx.Convey("p1 should not be nil", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeEmpty)
})
})
}
func TestDaocacheSFItemCoin(t *testing.T) {
var (
aid = int64(1)
tp = int64(60)
)
convey.Convey("cacheSFItemCoin", t, func(ctx convey.C) {
p1 := d.cacheSFItemCoin(aid, tp)
ctx.Convey("p1 should not be nil", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeEmpty)
})
})
}
func TestDaoexpKey(t *testing.T) {
var (
mid = int64(123)
)
convey.Convey("expKey", t, func(ctx convey.C) {
key := expKey(mid)
ctx.Convey("key should not be nil", func(ctx convey.C) {
ctx.So(key, convey.ShouldNotBeEmpty)
})
})
}

View File

@@ -0,0 +1,107 @@
// 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=-1 -check_null_code=$==-1 -singleflight=true
UserCoin(c context.Context, mid int64) (count float64, err error)
// cache: -nullcache=-1 -check_null_code=$==-1 -singleflight=true
ItemCoin(c context.Context, aid int64, tp int64) (count int64, err error)
}
*/
package dao
import (
"context"
"go-common/library/stat/prom"
"golang.org/x/sync/singleflight"
)
var _ _cache
var cacheSingleFlights = [2]*singleflight.Group{{}, {}}
// UserCoin get data from cache if miss will call source method, then add to cache.
func (d *Dao) UserCoin(c context.Context, id int64) (res float64, err error) {
addCache := true
res, err = d.CacheUserCoin(c, id)
if err != nil {
addCache = false
err = nil
}
defer func() {
if res == -1 {
res = 0
}
}()
if res != 0 {
prom.CacheHit.Incr("UserCoin")
return
}
var rr interface{}
sf := d.cacheSFUserCoin(id)
rr, err, _ = cacheSingleFlights[0].Do(sf, func() (r interface{}, e error) {
prom.CacheMiss.Incr("UserCoin")
r, e = d.RawUserCoin(c, id)
return
})
res = rr.(float64)
if err != nil {
return
}
miss := res
if miss == 0 {
miss = -1
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheUserCoin(c, id, miss)
})
return
}
// ItemCoin get data from cache if miss will call source method, then add to cache.
func (d *Dao) ItemCoin(c context.Context, id int64, tp int64) (res int64, err error) {
addCache := true
res, err = d.CacheItemCoin(c, id, tp)
if err != nil {
addCache = false
err = nil
}
defer func() {
if res == -1 {
res = 0
}
}()
if res != 0 {
prom.CacheHit.Incr("ItemCoin")
return
}
var rr interface{}
sf := d.cacheSFItemCoin(id, tp)
rr, err, _ = cacheSingleFlights[1].Do(sf, func() (r interface{}, e error) {
prom.CacheMiss.Incr("ItemCoin")
r, e = d.RawItemCoin(c, id, tp)
return
})
res = rr.(int64)
if err != nil {
return
}
miss := res
if miss == 0 {
miss = -1
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheItemCoin(c, id, miss, tp)
})
return
}

View File

@@ -0,0 +1,41 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoUserCoin(t *testing.T) {
var (
c = context.TODO()
id = int64(123)
)
convey.Convey("UserCoin", t, func(ctx convey.C) {
res, err := d.UserCoin(c, id)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("res should not be nil", func(ctx convey.C) {
ctx.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaoItemCoin(t *testing.T) {
var (
c = context.TODO()
id = int64(123)
tp = int64(60)
)
convey.Convey("ItemCoin", t, func(ctx convey.C) {
res, err := d.ItemCoin(c, id, tp)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("res should not be nil", func(ctx convey.C) {
ctx.So(res, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,83 @@
package dao
import (
"context"
"time"
"go-common/app/service/main/coin/conf"
"go-common/app/service/main/coin/model"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/database/elastic"
"go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
"go-common/library/queue/databus"
"go-common/library/stat/prom"
"go-common/library/sync/pipeline/fanout"
)
// Dao dao config.
type Dao struct {
c *conf.Config
// databus stat
stat *databus.Databus
// coin
coin *sql.DB
//http
httpClient *bm.Client
// databus
dbBigData *databus.Databus
// databus for coin-job
dbCoinJob *databus.Databus
// redis
redis *redis.Pool
expireAdded int32
expireExp int32
// tag url
tagURI string
mc *memcache.Pool
mcExpire int32
cache *fanout.Fanout
Businesses map[int64]*model.Business
BusinessNames map[string]*model.Business
es *elastic.Elastic
}
// New new a Dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
coin: sql.NewMySQL(c.DB.Coin),
httpClient: bm.NewClient(c.HTTPClient),
tagURI: c.TagURL,
redis: redis.NewPool(c.Redis.Config),
mc: memcache.NewPool(c.Memcache.Config),
mcExpire: int32(time.Duration(c.Memcache.Expire) / time.Second),
expireExp: int32(time.Duration(c.Memcache.ExpExpire) / time.Second),
dbBigData: databus.New(c.DbBigData),
dbCoinJob: databus.New(c.DbCoinJob),
stat: databus.New(c.Stat.Databus),
expireAdded: int32(time.Duration(c.Redis.Expire) / time.Second),
cache: fanout.New("cache", fanout.Buffer(10240)),
Businesses: make(map[int64]*model.Business),
BusinessNames: make(map[string]*model.Business),
es: elastic.NewElastic(nil),
}
if len(c.Businesses) > 0 {
for _, b := range c.Businesses {
d.Businesses[b.ID] = b
d.BusinessNames[b.Name] = b
}
}
return
}
// PromError .
func PromError(name string) {
prom.BusinessErrCount.Incr(name)
}
// Ping check service health.
func (dao *Dao) Ping(c context.Context) (err error) {
return dao.coin.Ping(c)
}

View File

@@ -0,0 +1,47 @@
package dao
import (
"flag"
"os"
"strings"
"testing"
"go-common/app/service/main/coin/conf"
"gopkg.in/h2non/gock.v1"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account.coin-service")
flag.Set("conf_appid", "coin-service")
flag.Set("conf_token", "G1Rp73FzNnV0k2EdGdL7BZO5YL49n1Mt")
flag.Set("tree_id", "2131")
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-service-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
d.httpClient.SetTransport(gock.DefaultTransport)
m.Run()
os.Exit(0)
}
func httpMock(method, url string) *gock.Request {
r := gock.New(url)
r.Method = strings.ToUpper(method)
return r
}

View File

@@ -0,0 +1,57 @@
package dao
import (
"context"
"strconv"
"time"
"go-common/library/log"
)
// PubBigData pub msg into databus.
func (d *Dao) PubBigData(c context.Context, aid int64, msg interface{}) (err error) {
key := strconv.FormatInt(aid, 10)
if err = d.dbBigData.Send(c, key, msg); err != nil {
log.Error("dbBigData.Pub(%s, %v) error (%v)", key, msg, err)
PromError("dbus:PubBigData")
}
return
}
// PubCoinJob pub job msg into databus.
func (d *Dao) PubCoinJob(c context.Context, aid int64, msg interface{}) (err error) {
key := strconv.FormatInt(aid, 10)
for i := 0; i < 3; i++ {
if err = d.dbCoinJob.Send(c, key, msg); err != nil {
log.Error("d.dbCoinJob.Pub(%s, %v) error (%v) times: %v", key, msg, err, i+1)
PromError("dbus:PubCoinJob")
time.Sleep(time.Millisecond * 50)
continue
}
break
}
return
}
// PubStat pub stat msg into databus.
func (d *Dao) PubStat(c context.Context, aid, tp, count int64) (err error) {
var s = &struct {
Type string `json:"type"`
ID int64 `json:"id"`
Count int64 `json:"count"`
Timestamp int64 `json:"timestamp"`
}{
ID: aid,
Count: count,
Timestamp: time.Now().Unix(),
}
// double write new databus.
if b, ok := d.Businesses[tp]; ok {
s.Type = b.Name
if err = d.stat.Send(c, strconv.FormatInt(aid, 10), s); err != nil {
log.Error("d.stat.Pub(%+v) error(%v)", s, err)
PromError("dbus:stat")
}
}
return
}

View File

@@ -0,0 +1,51 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPubBigData(t *testing.T) {
var (
c = context.TODO()
aid = int64(1)
msg = interface{}("PubBigData")
)
convey.Convey("PubBigData", t, func(ctx convey.C) {
err := d.PubBigData(c, aid, msg)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoPubCoinJob(t *testing.T) {
var (
c = context.TODO()
aid = int64(1)
msg = interface{}("PubCoinJob")
)
convey.Convey("PubCoinJob", t, func(ctx convey.C) {
err := d.PubCoinJob(c, aid, msg)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoPubStat(t *testing.T) {
var (
c = context.TODO()
aid = int64(1)
tp = int64(2)
count = int64(10)
)
convey.Convey("PubStat", t, func(ctx convey.C) {
err := d.PubStat(c, aid, tp, count)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,73 @@
package dao
import (
"context"
"encoding/json"
"time"
pb "go-common/app/service/main/coin/api"
"go-common/app/service/main/coin/model"
"go-common/library/database/elastic"
"go-common/library/log"
"go-common/library/queue/databus/report"
)
// AddLog .
func (d *Dao) AddLog(mid, ts int64, from, to float64, reason, ip, operator string, oid int64, typ int) {
report.User(&report.UserInfo{
Mid: mid,
Business: model.ReportType,
IP: ip,
Type: typ,
Ctime: time.Unix(ts, 0),
Index: []interface{}{operator},
Oid: oid,
Content: map[string]interface{}{
"from": int64(from * _multi),
"to": int64(to * _multi),
"reason": reason,
},
})
}
// CoinLog .
func (d *Dao) CoinLog(c context.Context, mid int64) (ls []*pb.ModelLog, err error) {
t := time.Now()
from := t.Add(-time.Hour * 24 * 7)
var res struct {
Page struct {
Num int `json:"num"`
Size int `json:"size"`
Total int `json:"total"`
} `json:"page"`
Result []*report.UserActionLog `json:"result"`
}
err = d.es.NewRequest("log_user_action").
IndexByTime("log_user_action_21", elastic.IndexTypeWeek, from, time.Now()).
WhereEq("mid", mid).
WhereRange("ctime", from.Format("2006-01-02 15:04:05"), "", elastic.RangeScopeLcRc).
Pn(1).Ps(1000).Order("ctime", elastic.OrderDesc).
Scan(c, &res)
if err != nil {
log.Error("coineslog mid: %v, err: %v", mid, err)
PromError("log:es")
return
}
for _, r := range res.Result {
ts, _ := time.ParseInLocation("2006-01-02 15:04:05", r.Ctime, time.Local)
var ex struct {
From int64
To int64
Reason string
}
json.Unmarshal([]byte(r.Extra), &ex)
ls = append(ls, &pb.ModelLog{
From: float64(ex.From) / _multi,
To: float64(ex.To) / _multi,
IP: r.IP,
Desc: ex.Reason,
TimeStamp: ts.Unix(),
})
}
return
}

View File

@@ -0,0 +1,25 @@
package dao
import (
"context"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestAddLog(t *testing.T) {
Convey("log", t, func() {
d.AddLog(1, time.Now().Unix(), 1, 2, "test", "127.0.0.1", "", 0, 0)
})
}
func TestLogs(t *testing.T) {
var (
c = context.TODO()
)
Convey("work", t, func() {
_, err := d.CoinLog(c, 88888929)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,177 @@
// Code generated by $GOPATH/src/go-common/app/tool/cache/mc. DO NOT EDIT.
/*
Package dao is a generated mc cache package.
It is generated from:
type _mc interface {
// get user coin count.
// mc: -key=countKey
CacheUserCoin(c context.Context, mid int64) (count float64, err error)
// set user coin count
// mc: -key=countKey -expire=d.mcExpire
AddCacheUserCoin(c context.Context, mid int64, count float64) (err error)
// mc: -key=itemCoinKey
CacheItemCoin(c context.Context, aid int64, tp int64) (count int64, err error)
// mc: -key=itemCoinKey -expire=d.mcExpire
AddCacheItemCoin(c context.Context, aid int64, count int64, tp int64) (err error)
// mc: -key=expKey -type=get
Exp(c context.Context, mid int64) (exp int64, err error)
// mc: -key=expKey -expire=d.expireExp
SetTodayExpCache(c context.Context, mid int64, exp int64) (err error)
}
*/
package dao
import (
"context"
"fmt"
"strconv"
"go-common/library/cache/memcache"
"go-common/library/log"
"go-common/library/stat/prom"
)
var _ _mc
// CacheUserCoin get user coin count.
func (d *Dao) CacheUserCoin(c context.Context, id int64) (res float64, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := countKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheUserCoin")
log.Errorv(c, log.KV("CacheUserCoin", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
var v string
err = conn.Scan(reply, &v)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheUserCoin")
log.Errorv(c, log.KV("CacheUserCoin", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
r, err := strconv.ParseFloat(v, 64)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheUserCoin")
log.Errorv(c, log.KV("CacheUserCoin", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = float64(r)
return
}
// AddCacheUserCoin set user coin count
func (d *Dao) AddCacheUserCoin(c context.Context, id int64, val float64) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := countKey(id)
bs := []byte(strconv.FormatFloat(val, 'E', -1, 64))
item := &memcache.Item{Key: key, Value: bs, Expiration: d.mcExpire, Flags: memcache.FlagRAW}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheUserCoin")
log.Errorv(c, log.KV("AddCacheUserCoin", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheItemCoin get data from mc
func (d *Dao) CacheItemCoin(c context.Context, id int64, tp int64) (res int64, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := itemCoinKey(id, tp)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheItemCoin")
log.Errorv(c, log.KV("CacheItemCoin", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
var v string
err = conn.Scan(reply, &v)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheItemCoin")
log.Errorv(c, log.KV("CacheItemCoin", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
r, err := strconv.ParseInt(v, 10, 64)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheItemCoin")
log.Errorv(c, log.KV("CacheItemCoin", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = int64(r)
return
}
// AddCacheItemCoin Set data to mc
func (d *Dao) AddCacheItemCoin(c context.Context, id int64, val int64, tp int64) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := itemCoinKey(id, tp)
bs := []byte(strconv.FormatInt(int64(val), 10))
item := &memcache.Item{Key: key, Value: bs, Expiration: d.mcExpire, Flags: memcache.FlagRAW}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheItemCoin")
log.Errorv(c, log.KV("AddCacheItemCoin", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// Exp get data from mc
func (d *Dao) Exp(c context.Context, id int64) (res int64, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := expKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:Exp")
log.Errorv(c, log.KV("Exp", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
var v string
err = conn.Scan(reply, &v)
if err != nil {
prom.BusinessErrCount.Incr("mc:Exp")
log.Errorv(c, log.KV("Exp", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
r, err := strconv.ParseInt(v, 10, 64)
if err != nil {
prom.BusinessErrCount.Incr("mc:Exp")
log.Errorv(c, log.KV("Exp", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = int64(r)
return
}
// SetTodayExpCache Set data to mc
func (d *Dao) SetTodayExpCache(c context.Context, id int64, val int64) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := expKey(id)
bs := []byte(strconv.FormatInt(int64(val), 10))
item := &memcache.Item{Key: key, Value: bs, Expiration: d.expireExp, Flags: memcache.FlagRAW}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:SetTodayExpCache")
log.Errorv(c, log.KV("SetTodayExpCache", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}

View File

@@ -0,0 +1,100 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoCacheUserCoin(t *testing.T) {
var (
c = context.TODO()
id = int64(1)
)
convey.Convey("CacheUserCoin", t, func(ctx convey.C) {
res, err := d.CacheUserCoin(c, id)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("res should not be nil", func(ctx convey.C) {
ctx.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaoAddCacheUserCoin(t *testing.T) {
var (
c = context.TODO()
id = int64(10)
val = float64(10)
)
convey.Convey("AddCacheUserCoin", t, func(ctx convey.C) {
err := d.AddCacheUserCoin(c, id, val)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoCacheItemCoin(t *testing.T) {
var (
c = context.TODO()
id = int64(1)
tp = int64(60)
)
convey.Convey("CacheItemCoin", t, func(ctx convey.C) {
res, err := d.CacheItemCoin(c, id, tp)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("res should not be nil", func(ctx convey.C) {
ctx.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaoAddCacheItemCoin(t *testing.T) {
var (
c = context.TODO()
id = int64(1)
val = int64(10)
tp = int64(60)
)
convey.Convey("AddCacheItemCoin", t, func(ctx convey.C) {
err := d.AddCacheItemCoin(c, id, val, tp)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoExp(t *testing.T) {
var (
c = context.TODO()
id = int64(1)
)
convey.Convey("Exp", t, func(ctx convey.C) {
res, err := d.Exp(c, id)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("res should not be nil", func(ctx convey.C) {
ctx.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaoSetTodayExpCache(t *testing.T) {
var (
c = context.TODO()
id = int64(1)
val = int64(10)
)
convey.Convey("SetTodayExpCache", t, func(ctx convey.C) {
err := d.SetTodayExpCache(c, id, val)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,267 @@
package dao
import (
"context"
"fmt"
"time"
pb "go-common/app/service/main/coin/api"
"go-common/library/database/sql"
"go-common/library/log"
)
const (
_dayOfEachMonth = 25
_sharding = 50
_getTotalCoins = "SELECT aid,type,SUM(multiply) FROM coin_member_%d WHERE mid = ? GROUP BY aid,type ORDER BY cid desc LIMIT 20"
_getTotalCoinsByMid = "SELECT IFNULL(SUM(multiply),0) FROM coin_member_%d WHERE mid=? AND aid=? AND type=?"
_getTotalCoinsByMidAndUpMid = "SELECT IFNULL(coin_count,0) FROM coin_user_count_%02d WHERE mid=? AND up_mid=?"
_getCoinCount = "SELECT IFNULL(count,0) FROM coin_count WHERE aid=? AND type=?"
_updateCoinCount = "INSERT INTO coin_count(aid, type, `count`, ctime, mtime) VALUES(?,?,?,?,?) ON DUPLICATE KEY UPDATE count=count+?,mtime=?"
_insertCoinArchive = "INSERT LOW_PRIORITY INTO coin_archive_%d (aid,type,mid,timestamp,multiply) VALUES (?,?,?,?,?)"
_insertCoinMember = "INSERT LOW_PRIORITY INTO coin_member_%d (aid,type,mid,timestamp,multiply,up_mid) VALUES (?,?,?,?,?,?)"
_updateCoinMemberCount = "INSERT INTO coin_user_count_%02d(mid, up_mid, coin_count, ctime) VALUES(?,?,?,?) ON DUPLICATE KEY UPDATE coin_count=coin_count+?"
_getMemberVideoList = "SELECT aid,ip,timestamp,multiply FROM coin_member_%d WHERE mid = ? AND type=? AND timestamp > ? ORDER BY cid DESC LIMIT ?"
// coin settle
_hitSettlePeriod = "SELECT id FROM coin_settle_period WHERE to_year=? AND to_month=?"
_updateSettleBD = "UPDATE coin_settle_%d SET exp_sub=?,description=?,itime=?,mtime=? WHERE aid=? and type=?" // update for bigdata
_updateArchiveCoinsBD = "UPDATE coin_count SET count=?, mtime=? WHERE aid=? AND type=?" // update for bigdata
)
func (dao *Dao) midHit(mid int64) int64 {
return mid % _sharding
}
func (dao *Dao) aidHit(aid int64) int64 {
return aid % _sharding
}
func (dao *Dao) hitCoinPeriod(c context.Context, now time.Time) (id int64, err error) {
year, month, day := now.Year(), now.Month(), now.Day()
if now.Day() < _dayOfEachMonth {
err = fmt.Errorf("opreation from bigdata must after the %dth of each month, today is (%d)", _dayOfEachMonth, day)
log.Error("%v", err)
return
}
row := dao.coin.QueryRow(c, _hitSettlePeriod, year, month)
if err != nil {
log.Error("dao.hitCoinPeriodStmt.QueryRow(%d, %d) error(%v)", year, month, err)
return
}
if err = row.Scan(&id); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
return
}
if id == 0 {
err = fmt.Errorf("zero id at(%s)", now.String())
log.Error("%v", err)
}
return
}
// UpdateCoinSettleBD update table coin_settle_%d.
func (dao *Dao) UpdateCoinSettleBD(c context.Context, aid, tp, expSub int64, describe string, now time.Time) (affect int64, err error) {
id, err := dao.hitCoinPeriod(c, now)
if err != nil {
return
}
sqlStr := fmt.Sprintf(_updateSettleBD, id)
result, err := dao.coin.Exec(c, sqlStr, expSub, describe, now, now, aid, tp)
if err != nil {
log.Error("dao.coin.Exec(%s, %d, %s, %v, %v, %d) error(%v)", sqlStr, expSub, describe, now, now, aid, err)
return
}
affect, err = result.LastInsertId()
return
}
// CoinList return video list of coin added in one month
func (dao *Dao) CoinList(c context.Context, mid, tp, ts, size int64) (rcs []*pb.ModelList, err error) {
var rows *sql.Rows
if rows, err = dao.coin.Query(c, fmt.Sprintf(_getMemberVideoList, dao.midHit(mid)), mid, tp, ts, size); err != nil {
log.Error("dao.getMemberVideoList.Query() error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
var rc = &pb.ModelList{}
if err = rows.Scan(&rc.Aid, &rc.IP, &rc.Ts, &rc.Number); err != nil {
log.Error("row.Scan(),error(%v)", err)
rcs = nil
return
}
rcs = append(rcs, rc)
}
return
}
// CoinsAddedByMid get coin added by mid of aid&tp.
func (dao *Dao) CoinsAddedByMid(c context.Context, mid, aid, tp int64) (added int64, err error) {
row := dao.coin.QueryRow(c, fmt.Sprintf(_getTotalCoinsByMid, dao.midHit(mid)), mid, aid, tp)
if err = row.Scan(&added); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
PromError("mysql:CoinsAddedByMid")
log.Errorv(c,
log.KV("log", "row.Scan"),
log.KV("err", err),
log.KV("mid", mid),
log.KV("aid", aid),
)
}
}
return
}
// AddedCoins get coins added to up_mid.
func (dao *Dao) AddedCoins(c context.Context, mid, upMid int64) (added int64, err error) {
row := dao.coin.QueryRow(c, fmt.Sprintf(_getTotalCoinsByMidAndUpMid, dao.midHit(mid)), mid, upMid)
if err = row.Scan(&added); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// UserCoinsAdded get user coin added.
func (dao *Dao) UserCoinsAdded(c context.Context, mid int64) (addeds map[int64]int64, err error) {
var rows *sql.Rows
addeds = make(map[int64]int64)
if rows, err = dao.coin.Query(c, fmt.Sprintf(_getTotalCoins, dao.midHit(mid)), mid); err != nil {
log.Errorv(c,
log.KV("log", "dao.getTotalCoins"),
log.KV("err", err),
log.KV("mid", mid),
)
PromError("db:UserCoinsAdded")
return
}
defer rows.Close()
for rows.Next() {
var (
added, aid, tp int64
)
if err = rows.Scan(&aid, &tp, &added); err != nil {
log.Errorv(c,
log.KV("log", "row.Scan"),
log.KV("err", err),
log.KV("mid", mid),
)
PromError("db:UserCoinsAdded")
addeds = nil
return
}
addeds[hashField(aid, tp)] = added
}
return
}
// InsertCoinArchive .
func (dao *Dao) InsertCoinArchive(c context.Context, aid, tp, mid, timestamp, multiply int64) (err error) {
if _, err = dao.coin.Exec(c, fmt.Sprintf(_insertCoinArchive, dao.aidHit(aid)), aid, tp, mid, timestamp, multiply); err != nil {
log.Errorv(c,
log.KV("log", "coin.Exec"),
log.KV("err", err),
log.KV("mid", mid),
log.KV("aid", aid),
)
PromError("db:InsertCoinArchive")
}
return
}
// InsertCoinMember .
func (dao *Dao) InsertCoinMember(c context.Context, aid, tp, mid, timestamp, multiply int64, upMid int64) (err error) {
if _, err = dao.coin.Exec(c, fmt.Sprintf(_insertCoinMember, dao.midHit(mid)), aid, tp, mid, timestamp, multiply, upMid); err != nil {
log.Errorv(c,
log.KV("log", "coin.Exec"),
log.KV("err", err),
log.KV("mid", mid),
log.KV("aid", aid),
)
PromError("db:InsertCoinMember")
}
return
}
// UpdateItemCoinCount update coin_count.
func (dao *Dao) UpdateItemCoinCount(c context.Context, aid, tp, count int64) (err error) {
now := time.Now()
if _, err = dao.coin.Exec(c, _updateCoinCount, aid, tp, count, now, now, count, now); err != nil {
log.Errorv(c,
log.KV("log", "UpdateItemCoinCount("),
log.KV("err", err),
log.KV("count", count),
log.KV("aid", aid),
)
PromError("db:UpdateItemCoinCount")
}
return
}
// RawItemCoin get count by aid.
func (dao *Dao) RawItemCoin(c context.Context, aid, tp int64) (count int64, err error) {
row := dao.coin.QueryRow(c, _getCoinCount, aid, tp)
if err = row.Scan(&count); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Errorv(c,
log.KV("log", "row.Scan"),
log.KV("err", err),
)
PromError("db:RawItemCoin")
}
}
return
}
// UpdateCoinMemberCount for archive
func (dao *Dao) UpdateCoinMemberCount(c context.Context, mid, upMid, count int64) (err error) {
if _, err = dao.coin.Exec(c, fmt.Sprintf(_updateCoinMemberCount, dao.midHit(mid)), mid, upMid, count, time.Now(), count); err != nil {
log.Errorv(c,
log.KV("log", "updateCoinMemberCount"),
log.KV("mid", mid),
log.KV("upmid", upMid),
log.KV("count", count),
log.KV("err", err),
)
PromError("db:UpdateCoinMemberCount")
}
return
}
// BeginTran begin a tx.
func (dao *Dao) BeginTran(c context.Context) (t *sql.Tx, err error) {
t, err = dao.coin.Begin(c)
if err != nil {
PromError("db:BeginTran")
log.Errorv(c,
log.KV("log", "d.BeginTran"),
log.KV("err", err),
)
}
return
}
// UpdateItemCoins update table coin_archive
func (dao *Dao) UpdateItemCoins(c context.Context, aid, tp, coins int64, now time.Time) (affect int64, err error) {
if err != nil {
return
}
result, err := dao.coin.Exec(c, _updateArchiveCoinsBD, coins, now, aid, tp)
if err != nil {
log.Error("dao.coin.Exec(%s, %d, %s, %d) error(%v)", _updateArchiveCoinsBD, coins, now, aid, err)
return
}
affect, err = result.RowsAffected()
return
}

View File

@@ -0,0 +1,72 @@
package dao
import (
"context"
"database/sql"
"fmt"
xsql "go-common/library/database/sql"
"go-common/library/log"
)
const (
_shard = 50
_multi = 100
_selCoin = "SELECT coins FROM coins_%02d where mid=?"
_txUpdateCoins = "INSERT INTO coins_%02d(mid,coins) VALUES(?,?) ON DUPLICATE KEY UPDATE coins=coins+?"
)
// UpdateCoin update user coins.
func (d *Dao) UpdateCoin(c context.Context, mid int64, coin float64) (err error) {
count := int64(coin * _multi)
if _, err = d.coin.Exec(c, fmt.Sprintf(_txUpdateCoins, mid%_shard), mid, count, count); err != nil {
PromError("db:UpdateCoin")
log.Errorv(c, log.KV("log", "UpdateCoin"), log.KV("err", err))
return
}
return
}
// TxUpdateCoins update coins
func (d *Dao) TxUpdateCoins(c context.Context, tx *xsql.Tx, mid int64, coin float64) (err error) {
count := int64(coin * _multi)
_, err = tx.Exec(fmt.Sprintf(_txUpdateCoins, mid%_shard), mid, count, count)
if err != nil {
PromError("db:TxUpdateCoins")
log.Errorv(c, log.KV("log", "TxUpdateCoins"), log.KV("err", err), log.KV("mid", mid))
return
}
return
}
// TxUserCoin tx user coin
func (d *Dao) TxUserCoin(c context.Context, tx *xsql.Tx, mid int64) (count float64, err error) {
var coin int64
row := tx.QueryRow(fmt.Sprintf(_selCoin, mid%_shard), mid)
if err = row.Scan(&coin); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:TxUserCoin")
log.Errorv(c, log.KV("log", "TxUserCoin"), log.KV("err", err))
}
count = float64(coin) / _multi
return
}
// RawUserCoin get user coins.
func (d *Dao) RawUserCoin(c context.Context, mid int64) (res float64, err error) {
var count int64
row := d.coin.QueryRow(c, fmt.Sprintf(_selCoin, mid%_shard), mid)
if err = row.Scan(&count); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:Coins")
log.Errorv(c, log.KV("log", "Coins"), log.KV("err", err))
}
res = float64(count) / _multi
return
}

View File

@@ -0,0 +1,81 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoUpdateCoin(t *testing.T) {
var (
c = context.TODO()
mid = int64(0)
coin = float64(0)
)
convey.Convey("UpdateCoin", t, func(ctx convey.C) {
err := d.UpdateCoin(c, mid, coin)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoTxUpdateCoins(t *testing.T) {
var (
c = context.TODO()
tx, _ = d.BeginTran(c)
mid = int64(1)
coin = float64(20)
)
convey.Convey("TxUpdateCoins", t, func(ctx convey.C) {
err := d.TxUpdateCoins(c, tx, mid, coin)
ctx.Convey("Error should be nil", func(ctx convey.C) {
if err != nil {
tx.Rollback()
}
ctx.So(err, convey.ShouldBeNil)
tx.Commit()
})
})
}
func TestDaoTxUserCoin(t *testing.T) {
var (
c = context.TODO()
tx, _ = d.BeginTran(c)
mid = int64(0)
)
convey.Convey("TxUserCoin", t, func(ctx convey.C) {
count, err := d.TxUserCoin(c, tx, mid)
ctx.Convey("Error should be nil", func(ctx convey.C) {
if err != nil {
tx.Rollback()
}
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("count should not be nil", func(ctx convey.C) {
if count != 0 {
tx.Commit()
}
ctx.So(count, convey.ShouldNotBeNil)
})
})
}
func TestDaoRawUserCoin(t *testing.T) {
var (
c = context.TODO()
mid = int64(0)
)
convey.Convey("RawUserCoin", t, func(ctx convey.C) {
res, err := d.RawUserCoin(c, mid)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("res should not be nil", func(ctx convey.C) {
ctx.So(res, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,259 @@
package dao
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaomidHit(t *testing.T) {
var (
mid = int64(0)
)
convey.Convey("midHit", t, func(ctx convey.C) {
p1 := d.midHit(mid)
ctx.Convey("p1 should not be nil", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoaidHit(t *testing.T) {
var (
aid = int64(0)
)
convey.Convey("aidHit", t, func(ctx convey.C) {
p1 := d.aidHit(aid)
ctx.Convey("p1 should not be nil", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaohitCoinPeriod(t *testing.T) {
var (
c = context.TODO()
now, _ = time.Parse("2006-01-02 15:04:05", "2018-01-26 00:00:00")
)
convey.Convey("hitCoinPeriod", t, func(ctx convey.C) {
id, err := d.hitCoinPeriod(c, now)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("id should not be nil", func(ctx convey.C) {
ctx.So(id, convey.ShouldNotBeNil)
})
})
}
func TestDaoUpdateCoinSettleBD(t *testing.T) {
var (
c = context.TODO()
aid = int64(4051951)
tp = int64(1)
expSub = int64(20)
describe = "UpdateCoinSettleBD"
now = time.Now()
)
convey.Convey("UpdateCoinSettleBD", t, func(ctx convey.C) {
currentYear, currentMonth, _ := now.Date()
curTime := now.Location()
firstDay := time.Date(currentYear, currentMonth, 1, 0, 0, 0, 0, curTime)
now = firstDay.AddDate(0, 1, -1)
affect, err := d.UpdateCoinSettleBD(c, aid, tp, expSub, describe, now)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("affect should not be nil", func(ctx convey.C) {
ctx.So(affect, convey.ShouldNotBeNil)
})
})
}
func TestDaoCoinList(t *testing.T) {
var (
c = context.TODO()
mid = int64(4051951)
tp = int64(1)
ts = int64(time.Now().Unix())
size = int64(1024)
)
convey.Convey("CoinList", t, func(ctx convey.C) {
_, err := d.CoinList(c, mid, tp, ts, size)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
// ctx.Convey("rcs should not be nil", func(ctx convey.C) {
// ctx.So(rcs, convey.ShouldNotBeEmpty)
// })
})
}
func TestDaoCoinsAddedByMid(t *testing.T) {
var (
c = context.TODO()
mid = int64(4051951)
aid = int64(12)
tp = int64(20)
)
convey.Convey("CoinsAddedByMid", t, func(ctx convey.C) {
added, err := d.CoinsAddedByMid(c, mid, aid, tp)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("added should not be nil", func(ctx convey.C) {
ctx.So(added, convey.ShouldNotBeNil)
})
})
}
func TestDaoAddedCoins(t *testing.T) {
var (
c = context.TODO()
mid = int64(4051951)
upMid = int64(4051950)
)
convey.Convey("AddedCoins", t, func(ctx convey.C) {
added, err := d.AddedCoins(c, mid, upMid)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("added should not be nil", func(ctx convey.C) {
ctx.So(added, convey.ShouldNotBeNil)
})
})
}
func TestDaoUserCoinsAdded(t *testing.T) {
var (
c = context.TODO()
mid = int64(1)
)
convey.Convey("UserCoinsAdded", t, func(ctx convey.C) {
addeds, err := d.UserCoinsAdded(c, mid)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("addeds should not be nil", func(ctx convey.C) {
ctx.So(addeds, convey.ShouldNotBeNil)
})
})
}
func TestDaoInsertCoinArchive(t *testing.T) {
var (
c = context.TODO()
aid = int64(1)
tp = int64(21)
mid = int64(4051951)
timestamp = int64(time.Now().Unix())
multiply = int64(0)
)
convey.Convey("InsertCoinArchive", t, func(ctx convey.C) {
err := d.InsertCoinArchive(c, aid, tp, mid, timestamp, multiply)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoInsertCoinMember(t *testing.T) {
var (
c = context.TODO()
aid = int64(1)
tp = int64(2)
mid = int64(4051951)
timestamp = int64(time.Now().Unix())
multiply = int64(21)
upMid = int64(1)
)
convey.Convey("InsertCoinMember", t, func(ctx convey.C) {
err := d.InsertCoinMember(c, aid, tp, mid, timestamp, multiply, upMid)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoUpdateItemCoinCount(t *testing.T) {
var (
c = context.TODO()
aid = int64(0)
tp = int64(0)
count = int64(0)
)
convey.Convey("UpdateItemCoinCount", t, func(ctx convey.C) {
err := d.UpdateItemCoinCount(c, aid, tp, count)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoRawItemCoin(t *testing.T) {
var (
c = context.TODO()
aid = int64(0)
tp = int64(0)
)
convey.Convey("RawItemCoin", t, func(ctx convey.C) {
count, err := d.RawItemCoin(c, aid, tp)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("count should not be nil", func(ctx convey.C) {
ctx.So(count, convey.ShouldNotBeNil)
})
})
}
func TestDaoUpdateCoinMemberCount(t *testing.T) {
var (
c = context.TODO()
mid = int64(4051951)
upMid = int64(1)
count = int64(2)
)
convey.Convey("UpdateCoinMemberCount", t, func(ctx convey.C) {
err := d.UpdateCoinMemberCount(c, mid, upMid, count)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoBeginTran(t *testing.T) {
var (
c = context.TODO()
)
convey.Convey("BeginTran", t, func(ctx convey.C) {
no, err := d.BeginTran(c)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("no should not be nil", func(ctx convey.C) {
ctx.So(no, convey.ShouldNotBeNil)
})
})
}
func TestDaoUpdateItemCoins(t *testing.T) {
var (
c = context.TODO()
aid = int64(1)
tp = int64(20)
coins = int64(20)
now = time.Now()
)
convey.Convey("UpdateItemCoins", t, func(ctx convey.C) {
affect, err := d.UpdateItemCoins(c, aid, tp, coins, now)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("affect should not be nil", func(ctx convey.C) {
ctx.So(affect, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,142 @@
package dao
import (
"context"
"strconv"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_addPrefix2 = "nca_"
)
func hashField(aid, tp int64) int64 {
return aid*1000 + tp
}
func addKey2(mid int64) (key string) {
key = _addPrefix2 + strconv.FormatInt(mid, 10)
return
}
// CoinsAddedCache get coin added of archive.
func (d *Dao) CoinsAddedCache(c context.Context, mid, aid, tp int64) (added int64, err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := addKey2(mid)
if added, err = redis.Int64(conn.Do("HGET", key, hashField(aid, tp))); err != nil {
if err == redis.ErrNil {
err = nil
return
}
PromError("redis:CoinsAddedCache")
log.Errorv(c, log.KV("log", "redis.Do(HGET)"), log.KV("err", err), log.KV("mid", mid))
}
return
}
// SetCoinAddedCache set coin added of archive
func (d *Dao) SetCoinAddedCache(c context.Context, mid, aid, tp, count int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := addKey2(mid)
defer func() {
if err != nil {
PromError("redis:SetCoinAddedCache")
log.Errorv(c,
log.KV("log", "s.coinDao.SetCoinAdded()"),
log.KV("mid", mid),
log.KV("aid", aid),
log.KV("err", err),
)
}
}()
if err = conn.Send("HSETNX", key, hashField(aid, tp), count); err != nil {
return
}
if err = conn.Send("EXPIRE", key, d.expireAdded); err != nil {
return
}
if err = conn.Flush(); err != nil {
return
}
if _, err = redis.Bool(conn.Receive()); err != nil {
return
}
conn.Receive()
return
}
// SetCoinAddedsCache multiset added cache
func (d *Dao) SetCoinAddedsCache(c context.Context, mid int64, counts map[int64]int64) (err error) {
if len(counts) == 0 {
// 空缓存
counts = map[int64]int64{-1: -1}
}
key := addKey2(mid)
conn := d.redis.Get(c)
defer conn.Close()
for field, count := range counts {
if err = conn.Send("HSETNX", key, field, count); err != nil {
log.Errorv(c,
log.KV("log", "conn.Send(HSETNX)"),
log.KV("err", err),
log.KV("mid", mid),
)
PromError("redis:SetCoinAddedsCache")
return
}
}
if err = conn.Send("EXPIRE", key, d.expireAdded); err != nil {
log.Errorv(c,
log.KV("log", "conn.Send(EXPIRE)"),
log.KV("err", err),
log.KV("mid", mid),
)
PromError("redis:SetCoinAddedsCache")
return
}
if err = conn.Flush(); err != nil {
log.Errorv(c, log.KV("log", "conn.Flush()"), log.KV("err", err), log.KV("mid", mid))
PromError("redis:SetCoinAddedsCache")
return
}
for i := 0; i < len(counts)+1; i++ {
if _, err = conn.Receive(); err != nil {
log.Errorv(c, log.KV("log", "conn.Recive()"), log.KV("err", err), log.KV("mid", mid))
PromError("redis:SetCoinAddedsCache")
return
}
}
return
}
// IncrCoinAddedCache Incr coin added
func (d *Dao) IncrCoinAddedCache(c context.Context, mid, aid, tp, count int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := addKey2(mid)
if _, err = conn.Do("HINCRBY", key, hashField(aid, tp), count); err != nil {
PromError("redis:IncrCoinAdded")
log.Errorv(c, log.KV("log", "conn.Do(HINCRBY) error"), log.KV("err", err), log.KV("mid", mid), log.KV("aid", aid))
}
return
}
// ExpireCoinAdded set expire time for coinadded
func (d *Dao) ExpireCoinAdded(c context.Context, mid int64) (ok bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := addKey2(mid)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.expireAdded)); err != nil {
if err == redis.ErrNil {
err = nil
return
}
PromError("redis:ExpireCoinAdded")
log.Errorv(c, log.KV("log", "conn.Do(EXPIRE)"), log.KV("err", err))
}
return
}

View File

@@ -0,0 +1,113 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaohashField(t *testing.T) {
var (
aid = int64(1)
tp = int64(1)
)
convey.Convey("hashField", t, func(ctx convey.C) {
p1 := hashField(aid, tp)
ctx.Convey("p1 should not be nil", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoaddKey2(t *testing.T) {
var (
mid = int64(123)
)
convey.Convey("addKey2", t, func(ctx convey.C) {
key := addKey2(mid)
ctx.Convey("key should not be nil", func(ctx convey.C) {
ctx.So(key, convey.ShouldNotBeNil)
})
})
}
func TestDaoCoinsAddedCache(t *testing.T) {
var (
c = context.TODO()
mid = int64(123)
aid = int64(1)
tp = int64(60)
)
convey.Convey("CoinsAddedCache", t, func(ctx convey.C) {
added, err := d.CoinsAddedCache(c, mid, aid, tp)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("added should not be nil", func(ctx convey.C) {
ctx.So(added, convey.ShouldNotBeNil)
})
})
}
func TestDaoSetCoinAddedCache(t *testing.T) {
var (
c = context.TODO()
mid = int64(123)
aid = int64(1)
tp = int64(60)
count = int64(20)
)
convey.Convey("SetCoinAddedCache", t, func(ctx convey.C) {
err := d.SetCoinAddedCache(c, mid, aid, tp, count)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoSetCoinAddedsCache(t *testing.T) {
var (
c = context.TODO()
mid = int64(123)
counts = map[int64]int64{10: 20, 20: 30, 30: 40}
)
convey.Convey("SetCoinAddedsCache", t, func(ctx convey.C) {
err := d.SetCoinAddedsCache(c, mid, counts)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoIncrCoinAddedCache(t *testing.T) {
var (
c = context.TODO()
mid = int64(123)
aid = int64(1)
tp = int64(60)
count = int64(20)
)
convey.Convey("IncrCoinAddedCache", t, func(ctx convey.C) {
err := d.IncrCoinAddedCache(c, mid, aid, tp, count)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoExpireCoinAdded(t *testing.T) {
var (
c = context.TODO()
mid = int64(123)
)
convey.Convey("ExpireCoinAdded", t, func(ctx convey.C) {
ok, err := d.ExpireCoinAdded(c, mid)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("ok should not be nil", func(ctx convey.C) {
ctx.So(ok, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,50 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/library/ecode"
"go-common/library/net/metadata"
"github.com/pkg/errors"
)
const _passportURL = "http://passport.bilibili.co/intranet/acc/bindDetail"
// PassportDetail .
type PassportDetail struct {
BindEmail bool `json:"bind_email"`
BindTel bool `json:"bind_tel"`
Mid int64 `json:"mid"`
}
// PassportDetail get detail.
func (d *Dao) PassportDetail(c context.Context, mid int64) (res *PassportDetail, err error) {
ip := metadata.String(c, metadata.RemoteIP)
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
var resp struct {
Code int `json:"code"`
Info *PassportDetail `json:"data"`
}
req, err := d.httpClient.NewRequest("GET", _passportURL, ip, params)
if err != nil {
err = errors.Wrap(err, "dao passport detail")
return
}
// req.Header.Set("Cookie", cookie)
req.Header.Set("X-BACKEND-BILI-REAL-IP", ip)
if err = d.httpClient.Do(c, req, &resp); err != nil {
err = errors.Wrap(err, "dao passport detail")
return
}
if resp.Code != 0 {
err = ecode.Int(resp.Code)
err = errors.Wrap(err, "dao passport detail")
return
}
res = resp.Info
return
}

View File

@@ -0,0 +1,18 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoGetPassportDetail(t *testing.T) {
convey.Convey("passport", t, func() {
var mid int64 = 27515586
httpMock("GET", _passportURL).Reply(200).BodyString(`{"code":0}`)
res, err := d.PassportDetail(context.TODO(), mid)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,34 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/library/log"
)
// TagIds get tag ids from tag
func (dao *Dao) TagIds(c context.Context, aid int64) (ids []int64, err error) {
params := url.Values{}
params.Set("aid", strconv.FormatInt(aid, 10))
var res struct {
Code int
Data []*struct {
ID int64 `json:"tag_id"`
}
}
if err = dao.httpClient.Get(c, dao.tagURI, "", params, &res); err != nil {
log.Error("dao.Ding api(%s) fail,err(%v)", dao.tagURI+"?"+params.Encode(), err)
return
}
if res.Code != 0 {
log.Error("dao.Ding api(%s) fail Code(%v)", dao.tagURI+"?"+params.Encode(), res.Code)
return
}
ids = make([]int64, 0, len(res.Data))
for _, d := range res.Data {
ids = append(ids, d.ID)
}
return
}

View File

@@ -0,0 +1,24 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoTagIds(t *testing.T) {
var (
c = context.TODO()
aid = int64(1)
)
convey.Convey("TagIds", t, func(ctx convey.C) {
_, err := d.TagIds(c, aid)
ctx.Convey("Error should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
// ctx.Convey("ids should not be nil", func(ctx convey.C) {
// ctx.So(ids, convey.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 = [
"coin.go",
"rpc.go",
],
importpath = "go-common/app/service/main/coin/model",
tags = ["automanaged"],
deps = [
"//app/service/main/archive/api:go_default_library",
"//library/time:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,125 @@
package model
import (
"go-common/app/service/main/archive/api"
"go-common/library/time"
)
var (
// TypeNone none type
TypeNone = 0
// TypeSend send type
TypeSend = 1
// TypeReceive receive type
TypeReceive = 2
// ReportType 上报business
ReportType = 21
)
// Record coin added record.
type Record struct {
Aid int64
Mid int64
Up int64
Timestamp int64
Multiply int64
AvType int64
Business string
IP uint32
IPV6 string
}
// AddedArchive archive info.
type AddedArchive struct {
*api.Arc
IP string `json:"ip"`
Time int64 `json:"time"`
Coins int64 `json:"coins"`
}
// DataBus databus msg.
type DataBus struct {
Mid int64 `json:"mid"` // user id
Avid int64 `json:"avid"` // archive id
AvType int8 `json:"avtp"` // archive type
UpID int64 `json:"upper_id"` // upper id
Multiply int64 `json:"multiply"` // multiply
Time time.Time `json:"time"` // archive pub date
IP string `json:"ip"` // userip
TypeID int16 `json:"rid"` // zone id
Tags string `json:"tags"` // tag ids
Ctime int64 `json:"ctime"` // add coin time
MsgID string `json:"msg_id"` // unique msg id
}
// CoinSettle .
type CoinSettle struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Aid int64 `json:"aid"`
CoinCount int64 `json:"coin_count"`
ExpTotal int64 `json:"exp_total"`
ExpSub int64 `json:"exp_sub"`
State int `json:"state"`
Describe string `json:"describe"`
ITime time.Time `json:"itime"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"mtime"`
}
// CoinSettlePeriod .
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"`
}
// AddCoins .
type AddCoins struct {
Count int64 `json:"count"`
}
// Log coins log.
type Log struct {
From float64 `json:"from"`
To float64 `json:"to"`
IP string `json:"ip"`
Desc string `json:"desc"`
TimeStamp int64 `json:"timestamp"`
}
// LogExp log exp
type LogExp struct {
List []*Exp `json:"list"`
Count int `json:"count"`
}
// Exp exp
type Exp struct {
Delta float64 `json:"delta"`
Time string `json:"time"`
Reason string `json:"reason"`
}
// List define coin added list.
type List struct {
Aid int64 `json:"aid,omitempty"`
Multiply int64 `json:"multiply,omitempty"`
Ts int64 `json:"ts,omitempty"`
IP uint32 `json:"ip,omitempty"`
}
// Business .
type Business struct {
ID int64
Name string
AddCoinReason string
AddCoinUpperReason string
AddExpReason string
}

View File

@@ -0,0 +1,69 @@
package model
// ArchiveUserCoins resp user coins.
type ArchiveUserCoins struct {
Multiply int64 `json:"multiply"`
}
// ArgCoinInfo arg coin info.
type ArgCoinInfo struct {
Mid int64
Aid int64
AvType int64
Business string
RealIP string
}
// ArgAddCoin arg add coin.
type ArgAddCoin struct {
Mid int64
UpMid int64
MaxCoin int64
Aid int64
AvType int64
Business string
Multiply int64
RealIP string
// archive only
TypeID int16
PubTime int64
}
// ArgModifyCoin rpc arg ,modify user coins.
type ArgModifyCoin struct {
Mid int64 `json:"mid" form:"mid" validate:"required"`
Count float64 `json:"count" form:"count" validate:"required"`
Reason string `json:"reason" form:"reason" validate:"required"`
IP string `json:"ip"`
Operator string `json:"operator" form:"operator"`
CheckZero int8 `json:"check_zore" form:"check_zero"`
}
// ArgList rpc arg list.
type ArgList struct {
Mid int64
TP int64
Business string
}
// ArgLog arg log
type ArgLog struct {
Mid int64
Recent bool
Translate bool
}
// ArgAddUserCoinExp .
type ArgAddUserCoinExp struct {
Mid int64
Business int64
BusinessName string
Number int64
RealIP string
}
// ArgMid .
type ArgMid struct {
Mid int64
RealIP string
}

View File

@@ -0,0 +1,12 @@
#### coin-service
### 项目简介
* 投币服务
* 提供文章 视频稿件投币
* 支持任意业务方接入投币
##### 依赖环境
> 1.Go 1.7.5或更高版本
##### 依赖包
> 1.公共包go-common

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 = ["rpc_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//app/service/main/coin/model:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["rpc.go"],
importpath = "go-common/app/service/main/coin/server/gorpc",
tags = ["automanaged"],
deps = [
"//app/service/main/coin/api:go_default_library",
"//app/service/main/coin/conf:go_default_library",
"//app/service/main/coin/model:go_default_library",
"//app/service/main/coin/service:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/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"],
)

View File

@@ -0,0 +1,237 @@
package rpc
import (
"time"
pb "go-common/app/service/main/coin/api"
"go-common/app/service/main/coin/conf"
coin "go-common/app/service/main/coin/model"
"go-common/app/service/main/coin/service"
"go-common/library/net/rpc"
"go-common/library/net/rpc/context"
)
// RPC define rpc.
type RPC struct {
s *service.Service
}
// New new rpc server.
func New(c *conf.Config, s *service.Service) (svr *rpc.Server) {
r := &RPC{s: s}
svr = rpc.NewServer(c.RPCServer)
if err := svr.Register(r); err != nil {
panic(err)
}
return
}
// Ping check connection success.
func (r *RPC) Ping(c context.Context, arg *struct{}, res *struct{}) (err error) {
return
}
// AddCoins add coin to archive.
func (r *RPC) AddCoins(c context.Context, ac *coin.ArgAddCoin, res *struct{}) (err error) {
tp, err := r.s.CheckBusiness(ac.Business)
if err != nil {
return
}
if tp > 0 {
ac.AvType = tp
}
if ac.AvType == 0 {
ac.AvType = 1
}
b, err := r.s.GetBusinessName(ac.AvType)
if err != nil {
return
}
arg := &pb.AddCoinReq{
IP: ac.RealIP,
Mid: ac.Mid,
Upmid: ac.UpMid,
MaxCoin: ac.MaxCoin,
Aid: ac.Aid,
Business: b,
Number: ac.Multiply,
Typeid: int32(ac.TypeID),
PubTime: ac.PubTime,
}
_, err = r.s.AddCoin(c, arg)
return
}
// ArchiveUserCoins archive coins.
func (r *RPC) ArchiveUserCoins(c context.Context, m *coin.ArgCoinInfo, res *coin.ArchiveUserCoins) (err error) {
tp, err := r.s.CheckBusiness(m.Business)
if err != nil {
return
}
if tp > 0 {
m.AvType = tp
}
if m.AvType == 0 {
m.AvType = 1
}
b, err := r.s.GetBusinessName(m.AvType)
if err != nil {
return
}
arg := pb.ItemUserCoinsReq{
Mid: m.Mid,
Aid: m.Aid,
Business: b,
}
var rr *pb.ItemUserCoinsReply
if rr, err = r.s.ItemUserCoins(c, &arg); err == nil && rr != nil {
*res = coin.ArchiveUserCoins{Multiply: rr.Number}
}
return
}
// UserCoins get user coins.
func (r *RPC) UserCoins(c context.Context, arg *coin.ArgCoinInfo, res *float64) (err error) {
reply, err := r.s.UserCoins(c, &pb.UserCoinsReq{Mid: arg.Mid})
if reply != nil {
*res = reply.Count
}
return
}
// ModifyCoin modify user coin.
func (r *RPC) ModifyCoin(c context.Context, arg *coin.ArgModifyCoin, res *float64) (err error) {
req := &pb.ModifyCoinsReq{
Mid: arg.Mid,
Count: arg.Count,
Reason: arg.Reason,
IP: arg.IP,
Operator: arg.Operator,
CheckZero: int32(arg.CheckZero),
Ts: time.Now().Unix(),
}
reply, err := r.s.ModifyCoins(c, req)
if err != nil {
return
}
*res = reply.Result
return
}
// List coin added list.
func (r *RPC) List(c context.Context, arg *coin.ArgList, res *[]*coin.List) (err error) {
tp, err := r.s.CheckBusiness(arg.Business)
if err != nil {
return
}
if tp > 0 {
arg.TP = tp
}
b, err := r.s.GetBusinessName(arg.TP)
if err != nil {
return
}
req := &pb.ListReq{
Mid: arg.Mid,
Business: b,
Ts: time.Now().Unix(),
}
reply, err := r.s.List(c, req)
if err != nil {
return
}
lists := []*coin.List{}
for _, r := range reply.List {
lists = append(lists, &coin.List{
Aid: r.Aid,
Multiply: r.Number,
Ts: r.Ts,
IP: r.IP,
})
}
*res = lists
return
}
// UserLog user log
func (r *RPC) UserLog(c context.Context, arg *coin.ArgLog, res *[]*coin.Log) (err error) {
req := &pb.CoinsLogReq{
Mid: arg.Mid,
Recent: arg.Recent,
Translate: arg.Translate,
}
reply, err := r.s.CoinsLog(c, req)
lists := []*coin.Log{}
for _, r := range reply.List {
lists = append(lists, &coin.Log{
From: r.From,
To: r.To,
IP: r.IP,
Desc: r.Desc,
TimeStamp: r.TimeStamp,
})
}
*res = lists
return
}
// AddUserCoinExp add user coin exp for job
func (r *RPC) AddUserCoinExp(c context.Context, arg *coin.ArgAddUserCoinExp, res *struct{}) (err error) {
tp, err := r.s.CheckBusiness(arg.BusinessName)
if err != nil {
return
}
if tp > 0 {
arg.Business = tp
}
b, err := r.s.GetBusinessName(arg.Business)
if err != nil {
return
}
req := &pb.AddUserCoinExpReq{
IP: arg.RealIP,
Mid: arg.Mid,
Business: b,
Number: arg.Number,
}
_, err = r.s.AddUserCoinExp(c, req)
return
}
// UpdateAddCoin update db after add coin for job.
func (r *RPC) UpdateAddCoin(c context.Context, arg *coin.Record, res *struct{}) (err error) {
tp, err := r.s.CheckBusiness(arg.Business)
if err != nil {
return
}
if tp > 0 {
arg.AvType = tp
}
b, err := r.s.GetBusinessName(arg.AvType)
if err != nil {
return
}
req := &pb.UpdateAddCoinReq{
Aid: arg.Aid,
Mid: arg.Mid,
Up: arg.Up,
Timestamp: arg.Timestamp,
Number: arg.Multiply,
Business: b,
IPV6: arg.IPV6,
}
_, err = r.s.UpdateAddCoin(c, req)
return
}
// TodayExp .
func (r *RPC) TodayExp(c context.Context, arg *coin.ArgMid, res *int64) (err error) {
req := &pb.TodayExpReq{
Mid: arg.Mid,
}
reply, err := r.s.TodayExp(c, req)
if reply != nil {
*res = reply.Exp
}
return
}

View File

@@ -0,0 +1,49 @@
package rpc
import (
"net/rpc"
"testing"
coidel "go-common/app/service/main/coin/model"
)
const (
addr = "172.16.12.122:6159"
mid = 23675773
aid = 1
added = 1
ip = "172.16.12.122"
coinInfo = "RPC.ArchiveUserCoins"
addCoin = "RPC.AddCoins"
)
func TestAddCoinsRpc(t *testing.T) {
client, err := rpc.Dial("tcp", addr)
if err != nil {
t.Errorf("rpc.Dial(tcp, (%s)) error(%v)", addr, err)
t.FailNow()
}
x := coidel.ArgAddCoin{Aid: aid, Mid: mid, Multiply: added, RealIP: ip}
cf := &coidel.ArchiveUserCoins{}
if err = client.Call(addCoin, x, cf); err != nil {
t.Logf("call.addMoral error(%v)", err)
}
t.Logf("res: %v", cf.Multiply)
}
func TestArchiveUserCoinsRpc(t *testing.T) {
client, err := rpc.Dial("tcp", addr)
if err != nil {
t.Errorf("rpc.Dial(tcp, (%s)) error(%v)", addr, err)
t.FailNow()
}
x := coidel.ArgCoinInfo{Aid: aid, Mid: mid}
cf := &coidel.ArchiveUserCoins{}
if err = client.Call(coinInfo, x, cf); err != nil {
t.Logf("call.addMoral error(%v)", err)
}
t.Logf("res: %v", cf.Multiply)
}

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 = ["server.go"],
importpath = "go-common/app/service/main/coin/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/coin/api:go_default_library",
"//app/service/main/coin/service:go_default_library",
"//library/net/rpc/warden:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,19 @@
// Package server generate by warden_gen
package server
import (
pb "go-common/app/service/main/coin/api"
"go-common/app/service/main/coin/service"
"go-common/library/net/rpc/warden"
)
// New Coin warden rpc server
func New(c *warden.ServerConfig, svr *service.Service) *warden.Server {
ws := warden.NewServer(c)
pb.RegisterCoinServer(ws.Server(), svr)
ws, err := ws.Start()
if err != nil {
panic(err)
}
return ws
}

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 = [
"coin.go",
"http.go",
"usercoin.go",
],
importpath = "go-common/app/service/main/coin/server/http",
tags = ["automanaged"],
deps = [
"//app/service/main/coin/api:go_default_library",
"//app/service/main/coin/conf:go_default_library",
"//app/service/main/coin/service:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/metadata: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,216 @@
package http
import (
"strconv"
"time"
pb "go-common/app/service/main/coin/api"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
// addCoin
func addCoin(c *bm.Context) {
var (
tp int64
upid int64
)
params := c.Request.Form
aidStr := params.Get("aid")
tpStr := params.Get("avtype")
multiplyStr := params.Get("multiply")
tpidStr := params.Get("typeid")
maxStr := params.Get("max")
upidStr := params.Get("upid")
mid, _ := strconv.ParseInt(params.Get("mid"), 10, 64)
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
aid, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
multiply, err := strconv.ParseInt(multiplyStr, 10, 64)
if err != nil || multiply <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if business, ok := c.Get("business"); ok {
tp = business.(int64)
} else {
if tpStr != "" {
if tp, err = strconv.ParseInt(tpStr, 10, 64); err != nil || tp < 1 || tp > 3 {
c.JSON(nil, ecode.RequestErr)
return
}
} else {
tp = 1
}
}
if upidStr != "" {
if upid, err = strconv.ParseInt(upidStr, 10, 64); err != nil || upid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
typeid, _ := strconv.ParseInt(tpidStr, 10, 64)
max, _ := strconv.ParseInt(maxStr, 10, 8)
c.JSON(nil, coinSvc.WebAddCoin(c, mid, upid, max, aid, tp, multiply, int16(typeid)))
}
func list(c *bm.Context) {
params := c.Request.Form
midStr := params.Get("mid")
tpStr := params.Get("tp")
mid, err := strconv.ParseInt(midStr, 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
var tp int64
if business, ok := c.Get("business"); ok {
tp = business.(int64)
} else {
if tp, err = strconv.ParseInt(tpStr, 10, 64); err != nil {
tp = 1
}
}
b, err := coinSvc.GetBusinessName(tp)
if err != nil {
c.JSON(nil, err)
}
arg := &pb.ListReq{Mid: mid, Business: b, Ts: time.Now().Unix()}
c.JSON(coinSvc.List(c, arg))
}
func todayexp(c *bm.Context) {
v := new(pb.TodayExpReq)
if err := c.Bind(v); err != nil {
return
}
res, err := coinSvc.TodayExp(c, v)
c.JSONMap(map[string]interface{}{
"number": res.Exp,
}, err)
}
func updateSettle(c *bm.Context) {
form := c.Request.Form
aidStr := form.Get("aid")
tpStr := form.Get("avtype")
aid, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
expSubStr := form.Get("exp_sub")
expSub, err := strconv.ParseInt(expSubStr, 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
var tp int64
if business, ok := c.Get("business"); ok {
tp = business.(int64)
} else {
var err error
tp, err = strconv.ParseInt(tpStr, 10, 64)
if err != nil || tp <= 0 {
tp = 1
}
}
c.JSON(nil, coinSvc.UpdateSettle(c, aid, tp, expSub, form.Get("describe")))
}
func coins(c *bm.Context) {
form := c.Request.Form
midStr := form.Get("mid")
upMidStr := form.Get("up_mid")
mid, err := strconv.ParseInt(midStr, 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
upMid, err := strconv.ParseInt(upMidStr, 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(coinSvc.AddedCoins(c, int64(mid), int64(upMid)))
}
func amend(c *bm.Context) {
form := c.Request.Form
aidStr := form.Get("aid")
tpStr := form.Get("avtype")
coinsStr := form.Get("coins")
aid, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
var tp int64
if business, ok := c.Get("business"); ok {
tp = business.(int64)
} else {
var err1 error
tp, err1 = strconv.ParseInt(tpStr, 10, 64)
if err1 != nil || tp <= 0 {
tp = 1
}
}
coins, err := strconv.ParseInt(coinsStr, 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, coinSvc.UpdateItemCoins(c, aid, tp, int64(coins)))
}
func ccounts(c *bm.Context) {
params := new(struct {
Aid int64 `json:"aid" form:"aid" validate:"required,min=1"`
Avtype int64 `json:"avtype" form:"avtype"`
IP string `json:"ip" `
})
if err := c.Bind(params); err != nil {
return
}
if business, ok := c.Get("business"); ok {
params.Avtype = business.(int64)
}
if params.Avtype == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
count, err := coinSvc.ItemCoin(c, params.Aid, params.Avtype)
c.JSON(map[string]interface{}{
"count": count,
}, err)
}
// @params AddCoinReq
// @router get /x/internal/v1/coin/add
// @response AddCoinReply
func internalAddCoin(c *bm.Context) {
v := new(pb.AddCoinReq)
if err := c.Bind(v); err != nil {
return
}
v.IP = metadata.String(c, metadata.RemoteIP)
c.JSON(coinSvc.AddCoin(c, v))
}
// @params ItemUserCoinsReq
// @router get /x/internal/v1/coin/item/coins
// @response ItemUserCoinsReply
func itemCoins(c *bm.Context) {
v := new(pb.ItemUserCoinsReq)
if err := c.Bind(v); err != nil {
return
}
c.JSON(coinSvc.ItemUserCoins(c, v))
}

View File

@@ -0,0 +1,83 @@
package http
import (
"net/http"
"go-common/app/service/main/coin/conf"
"go-common/app/service/main/coin/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
verifySrv *verify.Verify
coinSvc *service.Service
antispamM *antispam.Antispam
)
// Init init http linstening.
func Init(c *conf.Config, s *service.Service) {
coinSvc = s
antispamM = antispam.New(c.Antispam)
verifySrv = verify.New(c.Verify)
// init outer router
engine := bm.DefaultServer(c.BM)
outerRouter(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
// innerRouter init inner router.
func outerRouter(r *bm.Engine) {
r.Ping(ping)
r.Register(register)
cr := r.Group("/x/coin", verifySrv.Verify, Business)
{
cr.POST("/add", antispamM.ServeHTTP, addCoin)
cr.POST("/settle", updateSettle)
cr.GET("/v2/list", list)
cr.GET("/today/exp", todayexp)
}
cr1 := r.Group("/x/internal/v1/coin", verifySrv.Verify, Business)
{
cr1.POST("/add", antispamM.ServeHTTP, internalAddCoin)
cr1.GET("/list", list)
cr1.GET("/coins", coins)
cr1.GET("/item/coins", itemCoins)
cr1.POST("/amend", amend)
cr1.GET("/user/count", userCoins)
cr1.GET("/user/log", coinLog)
cr1.POST("/user/modify", modify)
cr1.GET("/creation/counts", ccounts)
}
}
func ping(c *bm.Context) {
if err := coinSvc.Ping(c); err != nil {
log.Error("ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}
// Business set business
func Business(c *bm.Context) {
business := c.Request.Form.Get("business")
if business == "" {
return
}
tp, err := coinSvc.CheckBusiness(business)
if err != nil {
c.JSON(nil, err)
c.Abort()
return
}
c.Set("business", tp)
}

View File

@@ -0,0 +1,44 @@
package http
import (
"time"
pb "go-common/app/service/main/coin/api"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
// @params ModifyCoinsReq
// @router post /x/internal/v1/coin/user/modify
// @response ModifyCoinsReply
func modify(c *bm.Context) {
arg := new(pb.ModifyCoinsReq)
if err := c.Bind(arg); err != nil {
return
}
arg.IP = metadata.String(c, metadata.RemoteIP)
arg.Ts = time.Now().Unix()
c.JSON(coinSvc.ModifyCoins(c, arg))
}
// @params UserCoinsReq
// @router get /x/internal/v1/coin/user/count
// @response UserCoinsReply
func userCoins(c *bm.Context) {
v := new(pb.UserCoinsReq)
if err := c.Bind(v); err != nil {
return
}
c.JSON(coinSvc.UserCoins(c, v))
}
// @params CoinsLogReq
// @router get /x/internal/v1/coin/user/log
// @response CoinsLogReply
func coinLog(c *bm.Context) {
v := new(pb.CoinsLogReq)
if err := c.Bind(v); err != nil {
return
}
c.JSON(coinSvc.CoinsLog(c, v))
}

View File

@@ -0,0 +1,70 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"coin_test.go",
"exp_test.go",
"service_test.go",
"usercoin_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/coin/api:go_default_library",
"//app/service/main/coin/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"archive.go",
"check.go",
"coin.go",
"exp.go",
"service.go",
"usercoin.go",
],
importpath = "go-common/app/service/main/coin/service",
tags = ["automanaged"],
deps = [
"//app/service/main/coin/api:go_default_library",
"//app/service/main/coin/conf:go_default_library",
"//app/service/main/coin/dao:go_default_library",
"//app/service/main/coin/model:go_default_library",
"//app/service/main/member/api:go_default_library",
"//app/service/main/member/model:go_default_library",
"//app/service/main/member/model/block: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/sync/errgroup.v2:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//library/time:go_default_library",
"//library/xstr: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,38 @@
package service
import (
"context"
"fmt"
"math/rand"
"time"
coin "go-common/app/service/main/coin/model"
"go-common/library/log"
xtime "go-common/library/time"
"go-common/library/xstr"
)
// sendArcMsg send msg to databus
func (s *Service) sendArcMsg(c context.Context, mid, aid, tp, upmid, multiply, pubTime int64, ip string, typeid int32, ts int64) {
msg := &coin.DataBus{
Mid: mid,
Avid: aid,
AvType: int8(tp),
UpID: upmid,
Multiply: multiply,
Time: xtime.Time(pubTime),
IP: ip,
TypeID: int16(typeid),
Ctime: ts,
MsgID: fmt.Sprintf("%d%06d%02d", time.Now().Unix(), rand.Intn(_maxRandVal), mid%_maxEXP),
}
ids, err := s.coinDao.TagIds(c, msg.Avid)
if err != nil {
log.Error("s.coinDao.TagIds error(%v)", err)
return
}
msg.Tags = xstr.JoinInts(ids)
if err = s.coinDao.PubBigData(c, msg.Avid, msg); err == nil {
log.Info("mabd m(%d)a(%d)", msg.Mid, msg.Avid)
}
}

View File

@@ -0,0 +1,157 @@
package service
import (
"context"
"fmt"
pb "go-common/app/service/main/coin/api"
"go-common/app/service/main/coin/dao"
mml "go-common/app/service/main/member/api"
mml2 "go-common/app/service/main/member/model"
"go-common/app/service/main/member/model/block"
bml "go-common/app/service/main/member/model/block"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/sync/errgroup.v2"
)
func boolToInt32(b bool) int32 {
if b {
return 1
}
return 0
}
func blockStatusToSilence(status bml.BlockStatus) int32 {
return boolToInt32(status == bml.BlockStatusForever || status == bml.BlockStatusLimit)
}
func identificationStatus(realNameStatus mml2.RealnameStatus) int32 {
return boolToInt32(realNameStatus == mml2.RealnameStatusTrue)
}
// User .
func (s *Service) checkUser(c context.Context, mid int64) (err error) {
eg := errgroup.WithCancel(c)
var rank, identification int32
eg.Go(func(c context.Context) error {
var mb *mml.MemberInfoReply
var e error
if mb, e = s.memRPC.Member(c, &mml.MemberMidReq{Mid: mid}); (e != nil) || (mb == nil) {
log.Error("d.mRPC.Member(%d) err(%v) (%+v)", mid, e, mb)
return nil
}
if mb.LevelInfo.Cur < 1 {
return ecode.UserLevelLow
}
rank = int32(mb.BaseInfo.Rank)
return nil
})
eg.Go(func(c context.Context) error {
mb, e := s.memRPC.BlockInfo(c, &mml.MemberMidReq{Mid: mid})
if (e != nil) || (mb == nil) {
log.Error("d.mRPC.Member(%d) err(%v) (%+v)", mid, e, mb)
return nil
}
if blockStatusToSilence(block.BlockStatus(uint8(mb.BlockStatus))) == 1 {
return ecode.UserDisabled
}
return nil
})
eg.Go(func(c context.Context) error {
mb, e := s.memRPC.RealnameStatus(c, &mml.MemberMidReq{Mid: mid})
if (e != nil) || (mb == nil) {
log.Error("d.mRPC.Member(%d) err(%v) (%+v)", mid, e, mb)
return nil
}
identification = identificationStatus(mml2.RealnameStatus(mb.RealnameStatus))
return nil
})
eg.Go(func(c context.Context) error {
mb, e := s.memRPC.Moral(c, &mml.MemberMidReq{Mid: mid})
if (e != nil) || (mb == nil) {
log.Error("d.mRPC.Member(%d) err(%v) (%+v)", mid, e, mb)
return nil
}
if (int32(mb.Moral) / 100) < 60 {
return ecode.LackOfScores
}
return nil
})
eg.Go(func(c context.Context) error {
pass, e := s.coinDao.PassportDetail(c, mid)
if e != nil {
log.Error("d.OldMyInfo(%d) err %v", mid, e)
return nil
}
if !pass.BindEmail && !pass.BindTel {
return ecode.UserInactive
}
if !pass.BindTel {
return ecode.MobileNoVerfiy
}
return nil
})
if err = eg.Wait(); err != nil {
dao.PromError("check:user")
return
}
if identification == 0 && rank == 5000 {
err = ecode.UserNoMember
}
return
}
// addCoinCheck check whether user can add coin
func (s *Service) addCoinCheck(c context.Context, mid, aid, tp, multiply, maxCoin, upmid int64) (err error) {
var (
added int64
exist bool
)
if _, ok := s.businesses[tp]; !ok {
err = ecode.RequestErr
return
}
if upmid == mid {
log.Errorv(c, log.KV("log", "user can not add coin to self archive"), log.KV("mid", mid))
err = ecode.CoinCannotAddToSelf
return
}
if multiply > maxCoin {
log.Errorv(c, log.KV("log", fmt.Sprintf("multiply(%d) can not bigger than maxCoin(%d)", multiply, maxCoin)), log.KV("mid", mid))
err = ecode.CoinIllegaMultiply
return
}
if err = s.checkUser(c, mid); err != nil {
log.Errorv(c, log.KV("log", "checkUser error"), log.KV("mid", mid), log.KV("err", err))
return
}
if exist, err = s.coinDao.ExpireCoinAdded(c, mid); err == nil && exist {
added, _ = s.coinDao.CoinsAddedCache(c, mid, aid, tp)
}
if !exist || (added == 0) {
if added, err = s.coinDao.CoinsAddedByMid(c, mid, aid, tp); err != nil {
return
}
s.cache.Do(c, func(c context.Context) {
s.coinDao.SetCoinAddedCache(c, mid, aid, tp, added)
if !exist {
s.loadUserCoinAddedCache(c, mid)
}
})
}
if added+multiply > maxCoin {
log.Errorv(c, log.KV("log", "add too much coins"), log.KV("mid", mid), log.KV("err", err))
err = ecode.CoinOverMax
return
}
var coins *pb.UserCoinsReply
if coins, err = s.UserCoins(c, &pb.UserCoinsReq{Mid: mid}); err != nil {
return
}
if coins.Count < (float64)(multiply) {
log.Errorv(c, log.KV("log", "have not enough money"), log.KV("mid", mid), log.KV("coins", coins.Count))
err = ecode.LackOfCoins
}
return
}

View File

@@ -0,0 +1,277 @@
package service
import (
"context"
"fmt"
"time"
pb "go-common/app/service/main/coin/api"
"go-common/app/service/main/coin/dao"
coin "go-common/app/service/main/coin/model"
xsql "go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
)
const (
_maxEXP = 50
_deltaEXP = 10
_maxArchiveSize = 300
_maxRandVal = 999999
_businessArchive = 1
)
// WebAddCoin http api old api some day need remove
func (s *Service) WebAddCoin(c context.Context, mid, upmid, maxCoin, aid, tp, multiply int64, typeid int16) (err error) {
arg := pb.AddCoinReq{
IP: metadata.String(c, metadata.RemoteIP),
Mid: mid,
Upmid: upmid,
MaxCoin: maxCoin,
Aid: aid,
Business: s.businesses[tp].Name,
Number: multiply,
Typeid: int32(typeid),
PubTime: 0,
}
if tp == _businessArchive {
err = ecode.RequestErr
return
}
_, err = s.AddCoin(c, &arg)
return
}
// AddCoin add coin to archive.
func (s *Service) AddCoin(c context.Context, arg *pb.AddCoinReq) (res *pb.AddCoinReply, err error) {
res = &pb.AddCoinReply{}
tp, err := s.MustCheckBusiness(arg.Business)
if err != nil {
return
}
var (
record *coin.Record
now = time.Now()
ts = now.Unix()
mid = arg.Mid
upmid = arg.Upmid
maxCoin = arg.MaxCoin
aid = arg.Aid
multiply = arg.Number
typeid = arg.Typeid
pubTime = arg.PubTime
ip = arg.IP
mergeTarget = s.mergeTarget(arg.Business, arg.Aid)
)
if maxCoin <= 0 || maxCoin > 2 {
maxCoin = 2
}
if err = s.addCoinCheck(c, mid, aid, tp, multiply, maxCoin, upmid); err != nil {
return
}
if err = s.addCoin(c, tp, mid, upmid, aid, multiply, ts, ip); err != nil {
return
}
if tp == _businessArchive {
s.cache.Do(c, func(c context.Context) {
s.sendArcMsg(c, mid, aid, tp, upmid, multiply, pubTime, ip, typeid, ts)
})
}
record = &coin.Record{
Aid: aid,
AvType: tp,
Business: s.businesses[tp].Name,
Up: upmid,
Mid: mid,
Multiply: multiply,
IPV6: ip,
Timestamp: ts,
}
if mergeTarget > 0 {
targetCount, err1 := s.coinDao.RawItemCoin(context.Background(), mergeTarget, tp)
if err1 == nil {
s.cache.Do(c, func(c context.Context) {
targetCount += multiply
s.coinDao.AddCacheItemCoin(c, mergeTarget, targetCount, tp)
s.coinDao.PubStat(c, mergeTarget, tp, targetCount)
})
}
}
s.job.Do(c, func(c context.Context) {
s.coinDao.PubCoinJob(c, record.Aid, record)
})
var count int64
// get count and send stat 为了尽可能实时
if count, err = s.coinDao.RawItemCoin(c, aid, tp); err != nil {
return
}
count += multiply
s.cache.Do(c, func(c context.Context) {
s.coinDao.AddCacheItemCoin(c, aid, count, tp)
s.coinDao.PubStat(c, aid, tp, count)
})
return
}
func (s *Service) addCoin(c context.Context, tp, mid, upmid, aid, number int64, ts int64, ip string) (err error) {
var count, upCount float64
var tx *xsql.Tx
if tx, err = s.coinDao.BeginTran(c); err != nil {
return
}
if count, err = s.coinDao.TxUserCoin(c, tx, mid); err != nil {
tx.Rollback()
return
}
if upCount, err = s.coinDao.TxUserCoin(c, tx, upmid); err != nil {
tx.Rollback()
return
}
if err = s.coinDao.TxUpdateCoins(c, tx, mid, -float64(number)); err != nil {
tx.Rollback()
return
}
if err = s.coinDao.TxUpdateCoins(c, tx, upmid, float64(number)/10); err != nil {
tx.Rollback()
return
}
if err = tx.Commit(); err != nil {
dao.PromError("coin:dbAddCoin")
log.Errorv(c, log.KV("log", "dbAddCoin commit"), log.KV("err", err))
return
}
var to, upperTo float64
to = Round(count - float64(number))
upperTo = Round(upCount + float64(number)/10)
s.cache.Do(c, func(c context.Context) {
s.coinDao.AddCacheUserCoin(c, mid, to)
s.coinDao.AddCacheUserCoin(c, upmid, upperTo)
s.coinDao.IncrCoinAddedCache(c, mid, aid, tp, number)
})
b := s.businesses[tp]
reason := fmt.Sprintf(b.AddCoinReason, aid, number)
upperReason := fmt.Sprintf(b.AddCoinUpperReason, aid, number, mid)
s.coinDao.AddLog(mid, ts, count, to, reason, ip, "", aid, coin.TypeSend)
s.coinDao.AddLog(upmid, ts, upCount, upperTo, upperReason, ip, "", aid, coin.TypeReceive)
return
}
// List get coin added list.
func (s *Service) List(c context.Context, arg *pb.ListReq) (res *pb.ListReply, err error) {
res = &pb.ListReply{}
tp, err := s.MustCheckBusiness(arg.Business)
if err != nil {
return
}
res.List, err = s.coinDao.CoinList(c, arg.Mid, tp, arg.Ts-86400*30, _maxArchiveSize)
return
}
func (s *Service) loadUserCoinAddedCache(c context.Context, mid int64) (err error) {
var (
addedMap map[int64]int64
)
if addedMap, err = s.coinDao.UserCoinsAdded(c, mid); err != nil {
return
}
err = s.coinDao.SetCoinAddedsCache(c, mid, addedMap)
return
}
// UpdateAddCoin .
func (s *Service) UpdateAddCoin(c context.Context, arg *pb.UpdateAddCoinReq) (res *pb.UpdateAddCoinReply, err error) {
res = &pb.UpdateAddCoinReply{}
tp, err := s.MustCheckBusiness(arg.Business)
if err != nil {
return
}
if err = s.coinDao.InsertCoinArchive(c, arg.Aid, tp, arg.Mid, arg.Timestamp, arg.Number); err != nil {
return
}
if err = s.coinDao.InsertCoinMember(c, arg.Aid, tp, arg.Mid, arg.Timestamp, arg.Number, arg.Up); err != nil {
return
}
if err = s.coinDao.UpdateItemCoinCount(c, arg.Aid, tp, arg.Number); err != nil {
return
}
target := s.mergeTarget(arg.Business, arg.Aid)
if target > 0 {
if err = s.coinDao.UpdateItemCoinCount(c, target, tp, arg.Number); err != nil {
return
}
}
// user added coin for upMid of archive.
if tp == _businessArchive {
err = s.coinDao.UpdateCoinMemberCount(c, arg.Mid, arg.Up, arg.Number)
}
return
}
// ItemUserCoins get coins added of archive.
func (s *Service) ItemUserCoins(c context.Context, arg *pb.ItemUserCoinsReq) (res *pb.ItemUserCoinsReply, err error) {
res = &pb.ItemUserCoinsReply{}
tp, err := s.MustCheckBusiness(arg.Business)
if err != nil {
return
}
var (
ok bool
)
if ok, _ = s.coinDao.ExpireCoinAdded(c, arg.Mid); ok {
res.Number, _ = s.coinDao.CoinsAddedCache(c, arg.Mid, arg.Aid, tp)
} else {
res.Number, err = s.coinDao.CoinsAddedByMid(c, arg.Mid, arg.Aid, tp)
s.cache.Do(c, func(c context.Context) {
s.loadUserCoinAddedCache(c, arg.Mid)
})
}
return
}
// AddedCoins get coin added to up.
func (s *Service) AddedCoins(c context.Context, mid, upMid int64) (a *coin.AddCoins, err error) {
count, err := s.coinDao.AddedCoins(c, mid, upMid)
if err != nil {
return
}
a = &coin.AddCoins{Count: count}
return
}
// UpdateItemCoins set archive coin added.
func (s *Service) UpdateItemCoins(c context.Context, aid, tp, coins int64) (err error) {
var affect int64
if affect, err = s.coinDao.UpdateItemCoins(c, aid, tp, coins, time.Now()); err != nil {
log.Error("s.coinDao.UpdateItemCoins(%d, %d) error(%v)", aid, coins, err)
}
if affect != 1 {
err = ecode.NothingFound
return
}
log.Infov(c, log.KV("log", "UpdateItemCoins"), log.KV("aid", aid), log.KV("type", tp), log.KV("coin", coins))
if err = s.coinDao.PubStat(c, aid, tp, coins); err != nil {
log.Error("s.coinDao.PubStat(%d, %d) error(%v)", aid, coins, err)
}
return
}
// ItemCoin get creat count cache.
func (s *Service) ItemCoin(c context.Context, aid, tp int64) (count int64, err error) {
return s.coinDao.ItemCoin(c, aid, tp)
}
// UpdateSettle coin settle.
func (s *Service) UpdateSettle(c context.Context, aid, tp, expSub int64, describe string) (err error) {
if _, err = s.coinDao.UpdateCoinSettleBD(c, aid, tp, expSub, describe, time.Now()); err != nil {
log.Error("s.coinDao.UpdateCoinSettleBD(%d, %d) error(%v)", aid, expSub, err)
}
return
}
func (s *Service) mergeTarget(business string, aid int64) int64 {
if s.statMerge != nil && s.statMerge.Business == business && s.statMerge.Sources[aid] {
return s.statMerge.Target
}
return 0
}

View File

@@ -0,0 +1,48 @@
package service
import (
"context"
"testing"
pb "go-common/app/service/main/coin/api"
. "github.com/smartystreets/goconvey/convey"
)
func TestAdd(t *testing.T) {
Convey("add coin", t, func() {
arg := &pb.AddCoinReq{
IP: "",
Mid: 4780461,
Upmid: 4052089,
MaxCoin: 2,
Aid: 1,
Business: "archive",
Number: 1,
Typeid: 1,
PubTime: 0,
}
_, err := s.AddCoin(context.TODO(), arg)
So(err, ShouldBeNil)
})
}
func TestUpdateItemCoins(t *testing.T) {
Convey("add coin", t, func() {
err := s.UpdateItemCoins(context.TODO(), 5462972, 1, 22)
So(err, ShouldBeNil)
})
}
func TestList(t *testing.T) {
Convey("list", t, func() {
arg := &pb.ListReq{
Mid: 88888929,
Business: "archive",
Ts: 0,
}
ls, err := s.List(context.TODO(), arg)
So(err, ShouldBeNil)
So(ls, ShouldNotBeEmpty)
})
}

View File

@@ -0,0 +1,64 @@
package service
import (
"context"
pb "go-common/app/service/main/coin/api"
"go-common/app/service/main/coin/dao"
memmdl "go-common/app/service/main/member/api"
"go-common/library/log"
)
// AddUserCoinExp .
func (s *Service) AddUserCoinExp(c context.Context, arg *pb.AddUserCoinExpReq) (res *pb.AddUserCoinExpReply, err error) {
res = &pb.AddUserCoinExpReply{}
tp, err := s.MustCheckBusiness(arg.Business)
if err != nil {
return
}
var (
todayExp, exp int64
)
if todayExp, err = s.coinDao.Exp(c, arg.Mid); err != nil {
return
}
if todayExp < _maxEXP {
exp = arg.Number * _deltaEXP
if todayExp += arg.Number * _deltaEXP; todayExp > _maxEXP {
todayExp = _maxEXP
exp = _deltaEXP
}
reason := s.businesses[tp].AddExpReason
if err = s.addExp(c, arg.Mid, float64(exp), reason, arg.IP); err != nil {
return
}
if err = s.coinDao.SetTodayExpCache(c, arg.Mid, todayExp); err != nil {
return
}
}
return
}
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 _, 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
}
log.Infov(c, log.KV("log", "add exp"), log.KV("mid", mid), log.KV("count", count), log.KV("reason", reason), log.KV("err", err))
return
}
// TodayExp get today coin added exp.
func (s *Service) TodayExp(c context.Context, arg *pb.TodayExpReq) (res *pb.TodayExpReply, err error) {
res = &pb.TodayExpReply{}
res.Exp, err = s.coinDao.Exp(c, arg.Mid)
return
}

View File

@@ -0,0 +1,20 @@
package service
import (
"context"
"testing"
pb "go-common/app/service/main/coin/api"
. "github.com/smartystreets/goconvey/convey"
)
func TestTodayExp(t *testing.T) {
Convey("todayexp", t, func() {
arg := &pb.TodayExpReq{
Mid: 1,
}
_, err := s.TodayExp(context.TODO(), arg)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,113 @@
package service
import (
"context"
"go-common/app/service/main/coin/conf"
"go-common/app/service/main/coin/dao"
"go-common/app/service/main/coin/model"
memrpc "go-common/app/service/main/member/api"
"go-common/library/ecode"
"go-common/library/sync/pipeline/fanout"
)
// Service define service.
type Service struct {
c *conf.Config
coinDao *dao.Dao
memRPC memrpc.MemberClient
cache *fanout.Fanout
job *fanout.Fanout
businesses map[int64]*model.Business
businessNames map[string]*model.Business
statMerge *statMerge
}
type statMerge struct {
Business string
Target int64
Sources map[int64]bool
}
// New new a Service and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
coinDao: dao.New(c),
cache: fanout.New("service-cache", fanout.Buffer(10240)),
job: fanout.New("job", fanout.Worker(10), fanout.Buffer(10240)),
}
var err error
if s.memRPC, err = memrpc.NewClient(c.MemberRPC); err != nil {
panic(err)
}
s.businesses = s.coinDao.Businesses
s.businessNames = s.coinDao.BusinessNames
if c.StatMerge != nil {
s.statMerge = &statMerge{
Business: c.StatMerge.Business,
Target: c.StatMerge.Target,
Sources: make(map[int64]bool),
}
for _, id := range c.StatMerge.Sources {
s.statMerge.Sources[id] = true
}
}
return
}
// Ping check service health.
func (s *Service) Ping(c context.Context) (err error) {
err = s.coinDao.Ping(c)
return
}
// Round 保留两位小数
func Round(x float64) float64 {
n := -0.5
if x > 0 {
n = 0.5
}
return float64(int64(x/0.01+n)) / 100.0
}
// CheckBusiness .
func (s *Service) CheckBusiness(bs string) (id int64, err error) {
if bs == "" {
return
}
b := s.businessNames[bs]
if b == nil {
err = ecode.AppDenied
return
}
id = b.ID
return
}
// MustCheckBusiness .
// +wd:ignore
func (s *Service) MustCheckBusiness(bs string) (id int64, err error) {
if bs == "" {
err = ecode.AppDenied
return
}
return s.CheckBusiness(bs)
}
// GetBusinessName .
func (s *Service) GetBusinessName(id int64) (res string, err error) {
b := s.businesses[id]
if b == nil {
err = ecode.AppDenied
return
}
return b.Name, nil
}
// Close .
func (s *Service) Close() (err error) {
s.job.Close()
s.cache.Close()
return
}

View File

@@ -0,0 +1,23 @@
package service
import (
"context"
"flag"
"path/filepath"
"time"
"go-common/app/service/main/coin/conf"
)
var (
s *Service
ctx = context.TODO()
)
func init() {
dir, _ := filepath.Abs("../cmd/coin-service-test.toml")
flag.Set("conf", dir)
conf.Init()
s = New(conf.Conf)
time.Sleep(time.Second)
}

View File

@@ -0,0 +1,109 @@
package service
import (
"context"
"regexp"
pb "go-common/app/service/main/coin/api"
"go-common/app/service/main/coin/model"
"go-common/library/ecode"
"go-common/library/log"
)
// ModifyCoins modify user coins.
func (s *Service) ModifyCoins(c context.Context, arg *pb.ModifyCoinsReq) (res *pb.ModifyCoinsReply, err error) {
res = &pb.ModifyCoinsReply{}
var (
count float64
)
log.Infov(c, log.KV("log", "ModifyCoins"), log.KV("mid", arg.Mid), log.KV("count", arg.Count), log.KV("reason", arg.Reason), log.KV("ip", arg.IP), log.KV("operator", arg.Operator))
count, err = s.coinDao.RawUserCoin(c, arg.Mid)
if err != nil {
return
}
res.Result = Round(count + arg.Count)
// checkZere为0才返回硬币不足
if arg.Count < 0 && res.Result < 0 && arg.CheckZero != 1 {
err = ecode.LackOfCoins
return
}
if err = s.coinDao.UpdateCoin(c, arg.Mid, arg.Count); err != nil {
return
}
s.coinDao.AddCacheUserCoin(c, arg.Mid, res.Result)
if arg.Operator == "nolog" {
return
}
s.coinDao.AddLog(arg.Mid, arg.Ts, count, res.Result, arg.Reason, arg.IP, arg.Operator, 0, model.TypeNone)
return
}
// UserCoins get user coins.
func (s *Service) UserCoins(c context.Context, arg *pb.UserCoinsReq) (res *pb.UserCoinsReply, err error) {
res = &pb.UserCoinsReply{}
res.Count, err = s.coinDao.UserCoin(c, arg.Mid)
return
}
// CoinsLog coins log
func (s *Service) CoinsLog(c context.Context, arg *pb.CoinsLogReq) (res *pb.CoinsLogReply, err error) {
res = &pb.CoinsLogReply{}
res.List, err = s.coinDao.CoinLog(c, arg.Mid)
if res.List == nil {
res.List = []*pb.ModelLog{}
}
if !arg.Translate {
return
}
for _, l := range res.List {
if l.Desc != "" {
l.Desc = translateLog(l.Desc)
}
}
return
}
func translateLog(l string) (res string) {
var match bool
// 依赖顺序
m := [][2]string{
{`cv Rating for (?P<var1>[0-9]+) : ([0-9]+) from ([0-9]+)`, `专栏 cv$var1 收到打赏`},
{`cv Rating for (?P<var1>[0-9]+)`, `给专栏 cv$var1 打赏`},
{`mv Rating for (?P<var1>[0-9]+) : ([0-9]+) from ([0-9]+)`, `音频 mv$var1 收到打赏`},
{`Rating for (?P<var1>[0-9]+) : ([0-9]+) from ([0-9]+)`, `视频 av$var1 收到打赏`},
{`cv Rating for (?P<var1>[0-9]+)`, `给专栏 $var1 打赏`},
{`mv Rating for (?P<var1>[0-9]+)`, `给音乐 $var1 打赏`},
{`Rating for (?P<var1>[0-9]+)`, `给视频 av$var1 打赏`},
{`ASS:DOWNLOAD SHARE:([0-9]+)`, `其他用户下载弹幕分享积分`},
{`通过审核AID:(?P<var1>[0-9]+)`, `投稿 av$var1 通过审核`},
{`删除投稿 AID:(?P<var1>[0-9]+)`, `删除投稿 av$var1`},
{`删除收藏 AID:(?P<var1>[0-9]+)`, `投稿 av$var1 低于100人收藏取消过100收藏的奖励积分`},
{`删除已审核投稿 AID:(?P<var1>[0-9]+)`, `删除投稿 av$var1`},
{`取消审核状态AID:(?P<var1>[0-9]+)`, `投稿被退回 av$var1`},
{`UPDATE:NICK:`, `修改昵称`},
{`^管理:(?P<var1>.+) Operator : (.+)$`, `$matches[1]`},
{`stow : (?P<var1>[0-9]+)`, `投稿 av$var1 超过100人收藏`},
{`Buy stow limits (?P<var1>[0-9]+)`, `购买收藏上限 $var1 个`},
{`BuyRank::([0-9]+)`, `购买标识`},
{`Click Ads b`, `支持广告,支持网站发展`},
{`Activity Award`, `活动奖励`},
{`兑换`, `礼品兑换`},
{`2015萌战活动`, `投票资格`},
}
for _, r := range m {
if match, res = regexpReplace(r[0], l, r[1]); match {
return
}
}
return l
}
func regexpReplace(reg, src, temp string) (match bool, res string) {
result := []byte{}
pattern := regexp.MustCompile(reg)
for _, submatches := range pattern.FindAllStringSubmatchIndex(src, -1) {
result = pattern.ExpandString(result, temp, src, submatches)
match = true
}
return match, string(result)
}

View File

@@ -0,0 +1,84 @@
package service
import (
"testing"
pb "go-common/app/service/main/coin/api"
. "github.com/smartystreets/goconvey/convey"
)
func TestUserCoins(t *testing.T) {
var mid int64 = 2
Convey("coin", t, func() {
arg1 := &pb.UserCoinsReq{
Mid: mid,
}
reply, err := s.UserCoins(ctx, arg1)
So(err, ShouldBeNil)
arg := &pb.ModifyCoinsReq{
Mid: mid,
Count: 2,
Reason: "test",
IP: "",
Operator: "",
CheckZero: 0,
Ts: 0,
}
s.ModifyCoins(ctx, arg)
ec, err := s.UserCoins(ctx, arg1)
So(err, ShouldBeNil)
So(ec.Count, ShouldEqual, reply.Count+2)
})
}
func TestLog(t *testing.T) {
Convey("log", t, func() {
arg := &pb.CoinsLogReq{
Mid: 88888929,
Recent: false,
Translate: true,
}
ls, err := s.CoinsLog(ctx, arg)
So(err, ShouldBeNil)
So(ls.List, ShouldNotBeEmpty)
})
}
func TestTranslateLog(t *testing.T) {
Convey("cv", t, func() {
l := "cv Rating for 123 : 3565 from 1234"
exp := "专栏 cv123 收到打赏"
So(translateLog(l), ShouldEqual, exp)
l = "cv Rating for 123"
exp = "给专栏 cv123 打赏"
So(translateLog(l), ShouldEqual, exp)
l = "2015萌战活动"
exp = "投票资格"
So(translateLog(l), ShouldEqual, exp)
})
}
func TestRound(t *testing.T) {
Convey("work", t, func() {
So(Round(0.7), ShouldEqual, 0.7)
So(Round(1.16), ShouldEqual, 1.16)
So(Round(33.3), ShouldEqual, 33.3)
So(Round(0.2900000000000001), ShouldEqual, 0.29)
})
}
func TestCheckBusiness(t *testing.T) {
Convey("present", t, func() {
tp, err := s.CheckBusiness("article")
So(tp, ShouldEqual, 1)
So(err, ShouldBeNil)
})
Convey("not present", t, func() {
tp, err := s.CheckBusiness("xxx")
So(tp, ShouldEqual, 0)
So(err, ShouldNotBeNil)
})
}