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,23 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/service/main/history/api/grpc:all-srcs",
"//app/service/main/history/cmd:all-srcs",
"//app/service/main/history/conf:all-srcs",
"//app/service/main/history/dao:all-srcs",
"//app/service/main/history/model:all-srcs",
"//app/service/main/history/server/grpc:all-srcs",
"//app/service/main/history/server/http:all-srcs",
"//app/service/main/history/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,64 @@
# history-service
# v1.3.0
1. tidb client
# v1.2.13
1. UserHistoriesReq Ps 1000
# v1.2.12
1. 屏蔽掉 18446744073709551615
# v1.2.11
1. 去掉 DeleteHistories-NothingFound
# v1.2.10
1. DeleteHistories-NothingFound
# v1.2.9
1. 翻页重复
# v1.2.8
1. context.Background()
# v1.2.7
1. grpc v1
# v1.2.6
1. remove ping
# v1.2.5
1. 初始化grpc reply
# v1.2.4
1. 重新构建
# v1.2.1
1. 重新rebase master
# v1.2.0
1. merge增加kid 去重防止重复消费
2. 使用pipeline简化合并逻辑
3. 修复user接口panic bug
# v1.1.1
1. 修复panic bug
# v1.1.0
1. 异步清除播放历史
2. 分批删除数据
# v1.0.5
1. job写入数据库
# v1.0.4
1. 聚合批量写入数据库
# v1.0.3
1. 修改grpc package 名称
2. 去掉device验证
# v1.0.1
1. add ping redis
# v1.0.0
1. 上线功能播放历史

View File

@@ -0,0 +1,19 @@
# Owner
renwei
wangxu01
zhapuyu
liangkai
# Author
wangxu01
renyashun
zhangshengchao
liangkai
# Reviewer
zhapuyu
wangxu01
renyashun
chenzhihui
zhangshengchao

View File

@@ -0,0 +1,22 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- liangkai
- renwei
- renyashun
- wangxu01
- zhangshengchao
- zhapuyu
labels:
- main
- service
- service/main/history
options:
no_parent_owners: true
reviewers:
- chenzhihui
- liangkai
- renyashun
- wangxu01
- zhangshengchao
- zhapuyu

View File

@@ -0,0 +1,12 @@
# history-service
# 项目简介
1. 播放历史服务
# 编译环境
# 依赖包
# 编译执行

View File

@@ -0,0 +1,64 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "v1_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
deps = [
"//app/service/main/history/model:model_proto",
"@gogo_special_proto//github.com/gogo/protobuf/gogoproto",
],
)
go_proto_library(
name = "v1_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_grpc"],
importpath = "go-common/app/service/main/history/api/grpc",
proto = ":v1_proto",
tags = ["automanaged"],
deps = [
"//app/service/main/history/model:model_go_proto",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
embed = [":v1_go_proto"],
importpath = "go-common/app/service/main/history/api/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/history/model:go_default_library",
"//library/net/rpc/warden:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
syntax = "proto3";
package community.service.history.v1;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "app/service/main/history/model/history.proto";
option go_package = "v1";
// AddHistoryReply reply
message AddHistoryReply {
}
// AddHistoryReq req
message AddHistoryReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = "form:\"mid\" validate:\"required\""];
// business 业务
string business = 2 [(gogoproto.moretags) = "form:\"business\" validate:\"required\""];
// kid 业务中唯一id
int64 kid = 3 [(gogoproto.moretags) = "form:\"kid\" validate:\"required\""];
// aid
int64 aid = 4 [(gogoproto.moretags) = "form:\"aid\""];
// sid
int64 sid = 5 [(gogoproto.moretags) = "form:\"sid\""];
// epid
int64 epid = 6 [(gogoproto.moretags) = "form:\"epid\""];
// cid
int64 cid = 7 [(gogoproto.moretags) = "form:\"cid\""];
// sub_type 子类型
int32 sub_type = 8 [(gogoproto.moretags) = "form:\"sub_type\""];
// device 设备类型
int32 device = 9 [(gogoproto.moretags) = "form:\"device\""];
// progress 进度
int32 progress = 10 [(gogoproto.moretags) = "form:\"progress\""];
// 观看时间
int64 view_at = 11 [(gogoproto.moretags) = "form:\"view_at\" validate:\"required\""];
}
// AddHistoriesReq 增加多条记录
message AddHistoriesReq {
repeated AddHistoryReq histories = 1;
}
// AddHistoriesReply .
message AddHistoriesReply {
}
// DelHistoriesReq del histories request
message DelHistoriesReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = "form:\"mid\" validate:\"required,min=1\""];
message Record {
// business 业务
string business = 1 [(gogoproto.moretags) = "form:\"business\" validate:\"required\""];
// id
int64 id = 2 [(gogoproto.moretags) = "form:\"id\" validate:\"required\"", (gogoproto.customname) = "ID"];
}
repeated Record records = 2 [(gogoproto.moretags) = "validate:\"required\""];
}
//DelHistoriesReply del histories reply
message DelHistoriesReply {
}
// ClearHistoryReq clear histories request
message ClearHistoryReq {
// businesses 业务 为空为全部业务
repeated string businesses = 1 [(gogoproto.moretags) = 'form:"businesses,split"'];
// mid
int64 mid = 2 [(gogoproto.moretags) = "form:\"mid\" validate:\"required,min=1\""];
}
//ClearHistoryReply clear histories reply
message ClearHistoryReply {
}
// UserHistoriesReq .
message UserHistoriesReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = "form:\"mid\" validate:\"required,min=1\""];
// businesses 查询的业务范围 为空为全部业务
repeated string businesses = 2 [(gogoproto.moretags) = 'form:"businesses,split"'];
// business 上一条的业务类型
string business = 3 [(gogoproto.moretags) = 'form:"business"'];
// kid 上一条的id 业务中唯一id
int64 kid = 4 [(gogoproto.moretags) = "form:\"kid\""];
// 上一条的观看时间
int64 view_at = 5 [(gogoproto.moretags) = 'form:"view_at" validate:"required"'];
// 每页几条
int64 Ps = 6[(gogoproto.moretags) = 'form:"ps" validate:"required,min=1,max=1000"'];
}
// UserHistoriesReply .
message UserHistoriesReply {
repeated History histories = 1[(gogoproto.jsontag) = "histories"];
}
// HistoriesReq .
message HistoriesReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = "form:\"mid\" validate:\"required,min=1\""];
// business 业务
string business = 2 [(gogoproto.moretags) = 'form:"business" validate:"required"'];
// kids 业务id
repeated int64 kids = 3 [(gogoproto.moretags) = 'form:"kids,split" validate:"required"'];
}
// AddHistoriesReply .
message HistoriesReply {
map<int64, History> histories = 1[(gogoproto.jsontag) = "histories"];
}
// UserHideReq req
message UserHideReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = "form:\"mid\" validate:\"required,min=1\""];
}
// UserHideReply reply
message UserHideReply {
bool hide = 1 [(gogoproto.jsontag) = "hide"];
}
// UpdateUserHideReq req
message UpdateUserHideReq {
// mid
int64 mid = 1 [(gogoproto.moretags) = 'form:"mid" validate:"required,min=1"'];
bool hide = 2 [(gogoproto.moretags) = 'form:"hide"'];
}
// UpdateUserHideReply reply
message UpdateUserHideReply {
}
// History rpc
service History {
// AddHistory add history. 增加播放历史接口
rpc AddHistory (AddHistoryReq) returns (AddHistoryReply);
// AddHistories 增加多条播放历史记录
rpc AddHistories (AddHistoriesReq) returns (AddHistoriesReply);
// DelHistories delete histories. 批量删除播放历史接口
rpc DelHistories (DelHistoriesReq) returns (DelHistoriesReply);
// ClearHistory clear history 按照业务删除所有播放记录
rpc ClearHistory (ClearHistoryReq) returns (ClearHistoryReply);
// UserHistories 查询用户的播放历史列表
rpc UserHistories (UserHistoriesReq) returns (UserHistoriesReply);
// Histories 根据id查询播放历史
rpc Histories (HistoriesReq) returns (HistoriesReply);
// UserHide 查询是否记录播放历史
rpc UserHide(UserHideReq) returns (UserHideReply);
// UpdateUserHide 修改是否记录播放历史
rpc UpdateUserHide(UpdateUserHideReq) returns (UpdateUserHideReply);
}

View File

@@ -0,0 +1,25 @@
package v1
import (
"context"
"fmt"
"go-common/library/net/rpc/warden"
"google.golang.org/grpc"
)
// AppID .
const AppID = "history.service"
// NewClient new grpc client
func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (HistoryClient, 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 NewHistoryClient(cc), nil
}
//go:generate $GOPATH/src/go-common/app/tool/warden/protoc.sh

View File

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

View File

@@ -0,0 +1,44 @@
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 = ["history-service-test.toml"],
importpath = "go-common/app/service/main/history/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/history/conf:go_default_library",
"//app/service/main/history/server/grpc:go_default_library",
"//app/service/main/history/server/http:go_default_library",
"//app/service/main/history/service:go_default_library",
"//library/log:go_default_library",
"//library/net/trace: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,62 @@
[mysql]
addr = "127.0.0.1:4000"
dsn = "root:@tcp(127.0.0.1:4000)/bilibili_history?timeout=200ms&readTimeout=200ms&writeTimeout=200ms&parseTime=true&loc=Local&charset=utf8mb4"
active = 20
idle = 10
idleTimeout ="4h"
queryTimeout = "100ms"
execTimeout = "100ms"
tranTimeout = "200ms"
[TiDB]
dsn = "root:@tcp(127.0.0.1:4000)/bilibili_history?timeout=200ms&readTimeout=200ms&writeTimeout=200ms&parseTime=true&loc=Local&charset=utf8mb4"
active = 5
idle = 2
idleTimeout ="4h"
queryTimeout = "1s"
execTimeout = "1s"
tranTimeout = "1s"
[TiDB.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[redis]
name = "history-service"
proto = "tcp"
addr = "127.0.0.1:6379"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1m"
[databus]
[databus.merge]
key = "170e302355453683"
secret = "3d0e8db7bed0503949e545a469789279"
group= "HistoryServiceMerge-MainCommunity-P"
topic= "HistoryServiceMerge-T"
action="pub"
name = "history"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 1
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[History]
[merge]
MaxSize = 100
Interval = "1s"
Buffer = 1024
# 线上要大于100 不然partion 分布不均
Worker = 100
Sync = false

View File

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

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 = ["conf.go"],
importpath = "go-common/app/service/main/history/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/tidb:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus:go_default_library",
"//library/sync/pipeline: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,99 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/tidb"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
"go-common/library/queue/databus"
"go-common/library/sync/pipeline"
xtime "go-common/library/time"
"github.com/BurntSushi/toml"
)
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config .
type Config struct {
Log *log.Config
BM *bm.ServerConfig
GRPC *warden.ServerConfig
Verify *verify.Config
Tracer *trace.Config
Redis *Redis
DataBus *Databus
History *History
Merge *pipeline.Config
TiDB *tidb.Config
}
// Databus .
type Databus struct {
Merge *databus.Config
}
// Redis redis.
type Redis struct {
*redis.Config
Expire xtime.Duration
}
// History .
type History struct {
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init conf
func Init() error {
if confPath != "" {
return local()
}
return remote()
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
if err = load(); err != nil {
return
}
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}

View File

@@ -0,0 +1,65 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"databus_test.go",
"redis_test.go",
"tidb_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/history/api/grpc:go_default_library",
"//app/service/main/history/conf:go_default_library",
"//app/service/main/history/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"databus.go",
"redis.go",
"tidb.go",
],
importpath = "go-common/app/service/main/history/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/history/api/grpc:go_default_library",
"//app/service/main/history/conf:go_default_library",
"//app/service/main/history/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/database/tidb:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/stat/prom: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,96 @@
package dao
import (
"context"
"time"
"go-common/app/service/main/history/conf"
"go-common/app/service/main/history/model"
"go-common/library/cache/redis"
"go-common/library/database/tidb"
"go-common/library/queue/databus"
)
// Dao dao
type Dao struct {
c *conf.Config
tidb *tidb.DB
redis *redis.Pool
redisExpire int32
mergeDbus *databus.Databus
businessesStmt *tidb.Stmts
historiesStmt *tidb.Stmts
historyStmt *tidb.Stmts
insertStmt *tidb.Stmts
deleteHistoriesStmt *tidb.Stmts
clearAllHistoriesStmt *tidb.Stmts
userHideStmt *tidb.Stmts
updateUserHideStmt *tidb.Stmts
Businesses map[int64]*model.Business
BusinessNames map[string]*model.Business
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
redis: redis.NewPool(c.Redis.Config),
tidb: tidb.NewTiDB(c.TiDB),
mergeDbus: databus.New(c.DataBus.Merge),
redisExpire: int32(time.Duration(c.Redis.Expire) / time.Second),
}
dao.businessesStmt = dao.tidb.Prepared(_businessesSQL)
dao.historiesStmt = dao.tidb.Prepared(_historiesSQL)
dao.deleteHistoriesStmt = dao.tidb.Prepared(_deleteHistoriesSQL)
dao.clearAllHistoriesStmt = dao.tidb.Prepared(_clearAllHistoriesSQL)
dao.historyStmt = dao.tidb.Prepared(_historySQL)
dao.userHideStmt = dao.tidb.Prepared(_userHide)
dao.updateUserHideStmt = dao.tidb.Prepared(_updateUserHide)
dao.insertStmt = dao.tidb.Prepared(_addHistorySQL)
dao.loadBusiness()
go dao.loadBusinessproc()
return
}
// Close close the resource.
func (d *Dao) Close() {
d.redis.Close()
d.tidb.Close()
}
// Ping dao ping
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.tidb.Ping(c); err != nil {
return
}
return d.pingRedis(c)
}
// LoadBusiness .
func (d *Dao) loadBusiness() {
var business []*model.Business
var err error
businessMap := make(map[string]*model.Business)
businessIDMap := make(map[int64]*model.Business)
for {
if business, err = d.QueryBusinesses(context.TODO()); err != nil {
time.Sleep(time.Second)
continue
}
for _, b := range business {
businessMap[b.Name] = b
businessIDMap[b.ID] = b
}
d.BusinessNames = businessMap
d.Businesses = businessIDMap
return
}
}
func (d *Dao) loadBusinessproc() {
for {
time.Sleep(time.Minute * 5)
d.loadBusiness()
}
}

View File

@@ -0,0 +1,40 @@
package dao
import (
"flag"
"fmt"
"os"
"testing"
"go-common/app/service/main/history/conf"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.history-service")
flag.Set("conf_token", "10f1bb6e589c42e7e1ee2560aff96b81")
flag.Set("tree_id", "56699")
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 {
fmt.Println("") // 存在才能pass
flag.Set("conf", "../cmd/history-service-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}
// INSERT INTO `bilibili_history`.`histories`(`mtime`, `ctime`, `mid`, `business_id`, `kid`, `aid`, `sid`, `epid`, `sub_type`, `cid`, `device`, `progress`, `view_at`) VALUES ('2018-08-27 03:03:50', '2018-08-27 03:01:29', 1, 4, 2, 3, 4, 5, 7, 6, 8, 9, 10);

View File

@@ -0,0 +1,20 @@
package dao
import (
"context"
"strconv"
"go-common/app/service/main/history/model"
"go-common/library/log"
"go-common/library/stat/prom"
)
// AddHistoryMessage .
func (d *Dao) AddHistoryMessage(c context.Context, k int, msg []*model.Merge) (err error) {
key := strconv.Itoa(k)
prom.BusinessInfoCount.Add("dbus-"+key, int64(len(msg)))
if err = d.mergeDbus.Send(c, key, msg); err != nil {
log.Error("Pub(%s,%+v) error(%v)", key, msg, err)
}
return
}

View File

@@ -0,0 +1,28 @@
package dao
import (
"context"
"testing"
"go-common/app/service/main/history/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoAddHistoryMessage(t *testing.T) {
var (
c = context.Background()
k = int(1)
msg = []*model.Merge{{
Mid: 1,
Bid: 4,
Time: 10000,
}}
)
convey.Convey("AddHistoryMessage", t, func(ctx convey.C) {
err := d.AddHistoryMessage(c, k, msg)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,394 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"strconv"
pb "go-common/app/service/main/history/api/grpc"
"go-common/app/service/main/history/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_keySwitch = "s_" // mid -> bit(value)
_bucket = 1000 // bit bucket
)
// keyHistory return history key.
func keyHistory(business string, mid int64) string {
return fmt.Sprintf("h_%d_%s", mid, business)
}
// keyIndex return history index key.
func keyIndex(business string, mid int64) string {
return fmt.Sprintf("i_%d_%s", mid, business)
}
// keySwitch return Switch key.
func keySwitch(mid int64) string {
return _keySwitch + strconv.FormatInt(mid/_bucket, 10)
}
// ListCacheByTime get aids from redis where score.
func (d *Dao) ListCacheByTime(c context.Context, business string, mid int64, start int64) (aids []int64, err error) {
conn := d.redis.Get(c)
defer conn.Close()
values, err := redis.Values(conn.Do("ZRANGEBYSCORE", keyIndex(business, mid), start, "INF", "WITHSCORES"))
if err != nil {
log.Error("conn.Do(ZRANGEBYSCORE %v) error(%v)", keyIndex(business, mid), err)
return
}
if len(values) == 0 {
return
}
var aid, unix int64
for len(values) > 0 {
if values, err = redis.Scan(values, &aid, &unix); err != nil {
log.Error("redis.Scan(%v) error(%v)", values, err)
return
}
aids = append(aids, aid)
}
return
}
// ListsCacheByTime get aids from redis where score.
func (d *Dao) ListsCacheByTime(c context.Context, businesses []string, mid int64, viewAt, ps int64) (res map[string][]int64, err error) {
conn := d.redis.Get(c)
defer conn.Close()
var count int
for _, business := range businesses {
if err = conn.Send("ZREVRANGEBYSCORE", keyIndex(business, mid), viewAt, "-INF", "LIMIT", 0, ps); err != nil {
log.Error("conn.Do(ZRANGEBYSCORE %v) error(%v)", keyIndex(business, mid), err)
return
}
count++
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < count; i++ {
var values []int64
values, err = redis.Int64s(conn.Receive())
if err != nil {
if err == redis.ErrNil {
err = nil
continue
}
log.Error("receive error(%v)", err)
return
}
if len(values) == 0 {
continue
}
if res == nil {
res = make(map[string][]int64)
}
res[businesses[i]] = values
}
return
}
// SetUserHideCache set the user hide to redis.
func (d *Dao) SetUserHideCache(c context.Context, mid, value int64) (err error) {
key := keySwitch(mid)
conn := d.redis.Get(c)
if _, err = conn.Do("HSET", key, mid%_bucket, value); err != nil {
log.Error("conn.Do(HSET %s,%d) error(%v)", key, value, err)
}
conn.Close()
return
}
// UserHideCache return user hide state from redis.
func (d *Dao) UserHideCache(c context.Context, mid int64) (value int64, err error) {
key := keySwitch(mid)
conn := d.redis.Get(c)
defer conn.Close()
if value, err = redis.Int64(conn.Do("HGET", key, mid%_bucket)); err != nil {
if err == redis.ErrNil {
return model.HideStateNotFound, nil
}
log.Error("conn.Do(HGET %s) error(%v)", key, err)
}
return
}
// HistoriesCache return the user histories from redis.
func (d *Dao) HistoriesCache(c context.Context, mid int64, hs map[string][]int64) (res map[string]map[int64]*model.History, err error) {
var (
values, businesses []string
aid int64
k int
)
conn := d.redis.Get(c)
defer conn.Close()
for business, aids := range hs {
businesses = append(businesses, business)
key := keyHistory(business, mid)
args := []interface{}{key}
for _, aid := range aids {
args = append(args, aid)
}
if err = conn.Send("HMGET", args...); err != nil {
log.Error("conn.Send(HMGET %v %v) error(%v)", key, aids, err)
return
}
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < len(hs); i++ {
if values, err = redis.Strings(conn.Receive()); err != nil {
log.Error("conn.Receive error(%v)", err)
if err == redis.ErrNil {
continue
}
return
}
if res == nil {
res = make(map[string]map[int64]*model.History)
}
business := businesses[i]
for k, aid = range hs[business] {
if values[k] == "" {
continue
}
h := &model.History{}
if err = json.Unmarshal([]byte(values[k]), h); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", values[k], err)
err = nil
continue
}
h.BusinessID = d.BusinessNames[h.Business].ID
if res[business] == nil {
res[business] = make(map[int64]*model.History)
}
res[business][aid] = h
}
}
return
}
// ClearHistoryCache clear the user redis.
func (d *Dao) ClearHistoryCache(c context.Context, mid int64, businesses []string) (err error) {
var conn = d.redis.Get(c)
var count int
defer conn.Close()
for _, business := range businesses {
idxKey := keyIndex(business, mid)
key := keyHistory(business, mid)
if err = conn.Send("DEL", idxKey); err != nil {
log.Error("conn.Send(DEL %s) error(%v)", idxKey, err)
return
}
count++
if err = conn.Send("DEL", key); err != nil {
log.Error("conn.Send(DEL %s) error(%v)", key, err)
return
}
count++
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// DelHistoryCache delete the history redis.
func (d *Dao) DelHistoryCache(c context.Context, arg *pb.DelHistoriesReq) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
var count int
for _, r := range arg.Records {
var (
indxKey = keyIndex(r.Business, arg.Mid)
key = keyHistory(r.Business, arg.Mid)
)
if err = conn.Send("ZREM", indxKey, r.ID); err != nil {
log.Error("conn.Send(ZREM %s,%v) error(%v)", indxKey, r.ID, err)
return
}
count++
if err = conn.Send("HDEL", key, r.ID); err != nil {
log.Error("conn.Send(HDEL %s,%v) error(%v)", key, r.ID, err)
return
}
count++
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// AddHistoryCache add the history to redis.
func (d *Dao) AddHistoryCache(c context.Context, h *pb.AddHistoryReq) (err error) {
var (
b []byte
mid = h.Mid
idxKey, key = keyIndex(h.Business, mid), keyHistory(h.Business, mid)
)
if b, err = json.Marshal(h); err != nil {
return
}
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZADD", idxKey, h.ViewAt, h.Kid); err != nil {
log.Error("conn.Send(ZADD %s,%d) error(%v)", key, h.Kid, err)
return
}
if err = conn.Send("HSET", key, h.Kid, string(b)); err != nil {
log.Error("conn.Send(HSET %s,%d) error(%v)", key, h.Kid, err)
return
}
if err = conn.Send("EXPIRE", idxKey, d.redisExpire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < 2+2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// AddHistoriesCache add the user to redis.
func (d *Dao) AddHistoriesCache(c context.Context, hs []*pb.AddHistoryReq) (err error) {
if len(hs) == 0 {
return
}
conn := d.redis.Get(c)
defer conn.Close()
var count int
for _, h := range hs {
var (
b []byte
mid = h.Mid
idxKey, key = keyIndex(h.Business, mid), keyHistory(h.Business, mid)
)
if b, err = json.Marshal(h); err != nil {
continue
}
if err = conn.Send("ZADD", idxKey, h.ViewAt, h.Kid); err != nil {
log.Error("conn.Send(ZADD %s,%d) error(%v)", key, h.Kid, err)
continue
}
count++
if err = conn.Send("HSET", key, h.Kid, string(b)); err != nil {
log.Error("conn.Send(HSET %s,%d) error(%v)", key, h.Kid, err)
continue
}
count++
if err = conn.Send("EXPIRE", idxKey, d.redisExpire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
continue
}
count++
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
continue
}
count++
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// TrimCache trim history.
func (d *Dao) TrimCache(c context.Context, business string, mid int64, limit int) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
aids, err := redis.Int64s(conn.Do("ZRANGE", keyIndex(business, mid), 0, -limit-1))
if err != nil {
log.Error("conn.Do(ZRANGE %v) error(%v)", keyIndex(business, mid), err)
return
}
if len(aids) == 0 {
return
}
return d.DelCache(c, business, mid, aids)
}
// DelCache delete the history redis.
func (d *Dao) DelCache(c context.Context, business string, mid int64, aids []int64) (err error) {
var (
key1 = keyIndex(business, mid)
key2 = keyHistory(business, mid)
args1 = []interface{}{key1}
args2 = []interface{}{key2}
)
for _, aid := range aids {
args1 = append(args1, aid)
args2 = append(args2, aid)
}
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZREM", args1...); err != nil {
log.Error("conn.Send(ZREM %s,%v) error(%v)", key1, aids, err)
return
}
if err = conn.Send("HDEL", args2...); err != nil {
log.Error("conn.Send(HDEL %s,%v) error(%v)", key2, aids, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < 2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// pingRedis ping redis.
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
if _, err = conn.Do("SET", "PING", "PONG"); err != nil {
log.Error("redis: conn.Do(SET,PING,PONG) error(%v)", err)
}
conn.Close()
return
}

View File

@@ -0,0 +1,130 @@
package dao
import (
"context"
"testing"
pb "go-common/app/service/main/history/api/grpc"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoListCache(t *testing.T) {
var (
c = context.Background()
business = "pgc"
mid = int64(1)
start = int64(0)
his = []*pb.AddHistoryReq{{
Mid: 1,
Business: "pgc",
Kid: 1,
Aid: 2,
Sid: 3,
},
}
h = &pb.AddHistoryReq{
Mid: 2,
Business: "pgc",
Kid: 1,
Aid: 2,
Sid: 3,
}
)
convey.Convey("add his", t, func() {
convey.So(d.AddHistoriesCache(c, his), convey.ShouldBeNil)
convey.So(d.AddHistoryCache(c, h), convey.ShouldBeNil)
convey.Convey("ListCacheByTime", func(ctx convey.C) {
aids, err := d.ListCacheByTime(c, business, mid, start)
ctx.Convey("Then err should be nil.aids should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(aids, convey.ShouldNotBeEmpty)
})
})
convey.Convey("ListsCacheByTime", func(ctx convey.C) {
var (
c = context.Background()
businesses = []string{"pgc"}
viewAt = int64(100)
ps = int64(1)
)
res, err := d.ListsCacheByTime(c, businesses, mid, viewAt, ps)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
convey.Convey("HistoriesCache", func(ctx convey.C) {
var hs = map[string][]int64{"pgc": {1}}
res, err := d.HistoriesCache(c, 2, hs)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
convey.Convey("ClearHistoryCache", func(ctx convey.C) {
err := d.ClearHistoryCache(c, mid, []string{business})
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
convey.Convey("DelHistoryCache", func(ctx convey.C) {
ctx.So(d.DelHistoryCache(c, &pb.DelHistoriesReq{
Mid: 1, Records: []*pb.DelHistoriesReq_Record{{ID: 1, Business: "pgc"}},
}), convey.ShouldBeNil)
})
convey.Convey("TrimCache", func(ctx convey.C) {
err := d.TrimCache(c, business, mid, 10)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelCache(t *testing.T) {
var (
c = context.Background()
business = "pgc"
mid = int64(1)
aids = []int64{1}
)
convey.Convey("DelCache", t, func(ctx convey.C) {
err := d.DelCache(c, business, mid, aids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoSetUserHideCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
value = int64(1)
)
convey.Convey("SetUserHideCache", t, func(ctx convey.C) {
err := d.SetUserHideCache(c, mid, value)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoUserHideCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("UserHideCache", t, func(ctx convey.C) {
value, err := d.UserHideCache(c, mid)
ctx.Convey("Then err should be nil.value should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(value, convey.ShouldNotBeNil)
// ctx.So(value, convey.ShouldEqual, 1)
})
})
}

View File

@@ -0,0 +1,208 @@
package dao
import (
"context"
"fmt"
"strconv"
"strings"
pb "go-common/app/service/main/history/api/grpc"
"go-common/app/service/main/history/model"
"go-common/library/database/sql"
"go-common/library/database/tidb"
"go-common/library/log"
"go-common/library/xstr"
)
var (
_addHistorySQL = "INSERT INTO histories(mid, kid, business_id, aid, sid, epid, sub_type, cid, device, progress, view_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)" +
"ON DUPLICATE KEY UPDATE aid =?, sid=?, epid=?, sub_type=?, cid=?, device=?, progress=?, view_at=?"
_businessesSQL = "SELECT id, name, ttl FROM business"
_historiesSQL = "SELECT ctime, mtime, business_id, kid, aid, sid, epid, sub_type, cid, device, progress, view_at FROM histories WHERE mid=? AND view_at < ? ORDER BY view_at DESC LIMIT ?"
_partHistoriesSQL = "SELECT ctime, mtime, business_id, kid, aid, sid, epid, sub_type, cid, device, progress, view_at FROM histories WHERE mid=? AND business_id in (%s) AND view_at < ? ORDER BY view_at DESC LIMIT ? "
_queryHistoriesSQL = "SELECT ctime, mtime, business_id, kid, aid, sid, epid, sub_type, cid, device, progress, view_at FROM histories WHERE mid=? AND kid in (%s) AND business_id = ?"
_historySQL = "SELECT ctime, mtime, business_id, kid, aid, sid, epid, sub_type, cid, device, progress, view_at FROM histories WHERE mid=? AND kid = ? AND business_id = ?"
_deleteHistoriesSQL = "DELETE FROM histories WHERE mid = ? AND kid = ? AND business_id = ?"
_clearHistoriesSQL = "DELETE FROM histories WHERE mid = ? AND business_id in (%s)"
_clearAllHistoriesSQL = "DELETE FROM histories WHERE mid = ?"
_userHide = "SELECT hide FROM users WHERE mid = ?"
_updateUserHide = "INSERT INTO users(mid, hide) VALUES(?,?) ON DUPLICATE KEY UPDATE hide =?"
)
// AddHistories add histories to db
func (d *Dao) AddHistories(c context.Context, hs []*model.History) (err error) {
if len(hs) == 0 {
return
}
var tx *tidb.Tx
if tx, err = d.tidb.Begin(c); err != nil {
log.Error("tx.BeginTran() error(%v)", err)
return
}
for _, h := range hs {
if _, err = tx.Stmts(d.insertStmt).Exec(c, h.Mid, h.Kid, h.BusinessID, h.Aid, h.Sid, h.Epid, h.SubType, h.Cid, h.Device, h.Progress, h.ViewAt,
h.Aid, h.Sid, h.Epid, h.SubType, h.Cid, h.Device, h.Progress, h.ViewAt); err != nil {
log.Errorv(c, log.D{Key: "mid", Value: h.Mid}, log.D{Key: "err", Value: err}, log.D{Key: "detail", Value: h})
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
log.Error("add histories commit(%+v) err: %v", hs, err)
}
return
}
// QueryBusinesses business
func (d *Dao) QueryBusinesses(c context.Context) (res []*model.Business, err error) {
var rows *tidb.Rows
if rows, err = d.businessesStmt.Query(c); err != nil {
log.Error("db.businessesStmt.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
b := &model.Business{}
if err = rows.Scan(&b.ID, &b.Name, &b.TTL); err != nil {
log.Error("rows.Business.Scan error(%v)", err)
return
}
res = append(res, b)
}
err = rows.Err()
return
}
// UserHistories get histories by time
func (d *Dao) UserHistories(c context.Context, businesses []string, mid, viewAt, ps int64) (res map[string][]*model.History, err error) {
var rows *tidb.Rows
if len(businesses) == 0 {
if rows, err = d.historiesStmt.Query(c, mid, viewAt, ps); err != nil {
log.Error("db.Histories.Query error(%v)", err)
return
}
} else {
var ids []int64
for _, b := range businesses {
ids = append(ids, d.BusinessNames[b].ID)
}
sqlStr := fmt.Sprintf(_partHistoriesSQL, xstr.JoinInts(ids))
if rows, err = d.tidb.Query(c, sqlStr, mid, viewAt, ps); err != nil {
log.Error("UserHistories(%v,%d,%d,%d),db.Histories.Query error(%v)", businesses, mid, viewAt, ps, err)
return
}
}
defer rows.Close()
for rows.Next() {
b := &model.History{Mid: mid}
if err = rows.Scan(&b.Ctime, &b.Mtime, &b.BusinessID, &b.Kid, &b.Aid, &b.Sid, &b.Epid, &b.SubType, &b.Cid, &b.Device, &b.Progress, &b.ViewAt); err != nil {
log.Error("UserHistories(%v,%d,%d,%d),rows.Scan error(%v)", businesses, mid, viewAt, ps, err)
if strings.Contains(fmt.Sprintf("%v", err), "18446744073709551615") {
err = nil
continue
}
return
}
b.Business = d.Businesses[b.BusinessID].Name
if res == nil {
res = make(map[string][]*model.History)
}
res[b.Business] = append(res[b.Business], b)
}
err = rows.Err()
return
}
// Histories get histories by id
func (d *Dao) Histories(c context.Context, business string, mid int64, ids []int64) (res map[int64]*model.History, err error) {
var rows *tidb.Rows
bid := d.BusinessNames[business].ID
if len(ids) == 1 {
if rows, err = d.historyStmt.Query(c, mid, ids[0], bid); err != nil {
log.Error("db.Histories.Query error(%v)", err)
return
}
} else {
sqlStr := fmt.Sprintf(_queryHistoriesSQL, xstr.JoinInts(ids))
if rows, err = d.tidb.Query(c, sqlStr, mid, bid); err != nil {
log.Error("tidb.Histories.Query error(%v)", err)
return
}
}
defer rows.Close()
for rows.Next() {
b := &model.History{Mid: mid}
if err = rows.Scan(&b.Ctime, &b.Mtime, &b.BusinessID, &b.Kid, &b.Aid, &b.Sid, &b.Epid, &b.SubType, &b.Cid, &b.Device, &b.Progress, &b.ViewAt); err != nil {
log.Error("rows.Business.Scan error(%v)", err)
return
}
b.Business = d.Businesses[b.BusinessID].Name
if res == nil {
res = make(map[int64]*model.History)
}
res[b.Kid] = b
}
err = rows.Err()
return
}
// DeleteHistories .
func (d *Dao) DeleteHistories(c context.Context, h *pb.DelHistoriesReq) (err error) {
var tx *tidb.Tx
if tx, err = d.tidb.Begin(c); err != nil {
log.Error("tx.BeginTran() error(%v)", err)
return
}
for _, r := range h.Records {
_, err = tx.Stmts(d.deleteHistoriesStmt).Exec(c, h.Mid, r.ID, d.BusinessNames[r.Business].ID)
if err != nil {
tx.Rollback()
return err
}
}
if err = tx.Commit(); err != nil {
log.Error("DeleteHistories(%+v),commit err:%+v", h, err)
}
return
}
// ClearHistory clear histories
func (d *Dao) ClearHistory(c context.Context, mid int64, businesses []string) (err error) {
var ids []string
for _, b := range businesses {
ids = append(ids, strconv.FormatInt(d.BusinessNames[b].ID, 10))
}
sqlStr := fmt.Sprintf(_clearHistoriesSQL, strings.Join(ids, ","))
if _, err = d.tidb.Exec(c, sqlStr, mid); err != nil {
log.Error("mid: %d clear(%v) err: %v", mid, businesses, err)
}
return
}
// ClearAllHistory clear all histories
func (d *Dao) ClearAllHistory(c context.Context, mid int64) (err error) {
if _, err = d.clearAllHistoriesStmt.Exec(c, mid); err != nil {
log.Error("mid: %d clear all err: %v", mid, err)
}
return
}
// UpdateUserHide update user hide
func (d *Dao) UpdateUserHide(c context.Context, mid int64, hide bool) (err error) {
if _, err = d.updateUserHideStmt.Exec(c, mid, hide, hide); err != nil {
log.Error("mid: %d updateUserHide(%v) err: %v", mid, hide, err)
}
return
}
// UserHide get user hide
func (d *Dao) UserHide(c context.Context, mid int64) (hide bool, err error) {
if err = d.userHideStmt.QueryRow(c, mid).Scan(&hide); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
log.Error("mid: %d UserHide err: %v", mid, err)
}
return
}

View File

@@ -0,0 +1,183 @@
package dao
import (
"context"
"testing"
"time"
pb "go-common/app/service/main/history/api/grpc"
"go-common/app/service/main/history/model"
"github.com/smartystreets/goconvey/convey"
)
var _history = &model.History{
Mid: 1,
BusinessID: 4,
Business: "pgc",
Kid: 2,
Aid: 3,
Sid: 4,
Epid: 5,
Cid: 6,
SubType: 7,
Device: 8,
Progress: 9,
ViewAt: 10,
}
var hs = []*model.History{_history}
func TestDaoAddHistories(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("AddHistories", t, func(ctx convey.C) {
err := d.AddHistories(c, hs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoQueryBusinesses(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("QueryBusinesses", t, func(ctx convey.C) {
res, err := d.QueryBusinesses(c)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeEmpty)
})
})
}
func TestDaoUserHistories(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
viewAt = time.Now().Unix()
ps = int64(1)
)
convey.Convey("UserHistories all business", t, func(ctx convey.C) {
var businesses []string
res, err := d.UserHistories(c, businesses, mid, viewAt, ps)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeEmpty)
res["pgc"][0].Ctime = 0
res["pgc"][0].Mtime = 0
ctx.So(res, convey.ShouldResemble, map[string][]*model.History{
"pgc": {_history},
})
})
})
convey.Convey("UserHistories one business", t, func(ctx convey.C) {
var businesses = []string{"pgc"}
res, err := d.UserHistories(c, businesses, mid, viewAt, ps)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeEmpty)
res["pgc"][0].Ctime = 0
res["pgc"][0].Mtime = 0
ctx.So(res, convey.ShouldResemble, map[string][]*model.History{
"pgc": {_history},
})
})
})
}
func TestDaoHistories(t *testing.T) {
var (
c = context.Background()
business = "pgc"
mid = int64(1)
ids = []int64{2}
)
convey.Convey("Histories", t, func(ctx convey.C) {
res, err := d.Histories(c, business, mid, ids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeEmpty)
})
})
}
func TestDaoDeleteHistories(t *testing.T) {
var (
c = context.Background()
h = &pb.DelHistoriesReq{Mid: 1, Records: []*pb.DelHistoriesReq_Record{
{Business: "pgc", ID: 2},
},
}
)
convey.Convey("DeleteHistories", t, func(ctx convey.C) {
d.AddHistories(c, hs)
err := d.DeleteHistories(c, h)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoClearHistory(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
businesses = []string{"pgc"}
)
convey.Convey("ClearHistory", t, func(ctx convey.C) {
d.AddHistories(c, hs)
err := d.ClearHistory(c, mid, businesses)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoClearAllHistory(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("ClearAllHistory", t, func(ctx convey.C) {
err := d.ClearAllHistory(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoUpdateUserHide(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
hide = true
)
convey.Convey("UpdateUserHide", t, func(ctx convey.C) {
err := d.UpdateUserHide(c, mid, hide)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoUserHide(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("UserHide", t, func(ctx convey.C) {
hide, err := d.UserHide(c, mid)
ctx.Convey("Then err should be nil.hide should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(hide, convey.ShouldEqual, true)
})
hide, err = d.UserHide(c, 200)
ctx.Convey("not found .Then err should be nil.hide should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(hide, convey.ShouldBeFalse)
})
})
}

View File

@@ -0,0 +1,58 @@
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 = "model_proto",
srcs = ["history.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "model_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_proto"],
importpath = "go-common/app/service/main/history/model",
proto = ":model_proto",
tags = ["automanaged"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["model.go"],
embed = [":model_go_proto"],
importpath = "go-common/app/service/main/history/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
syntax = "proto3";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "model";
// History model
message History {
// ctime
int64 ctime = 1 [(gogoproto.jsontag) = "ctime", (gogoproto.casttype) = "go-common/library/time.Time"];
// mtime
int64 mtime = 2 [(gogoproto.jsontag) = "mtime", (gogoproto.casttype) = "go-common/library/time.Time"];
// mid
int64 mid = 3 [(gogoproto.jsontag) = "mid", (gogoproto.moretags) = 'form:"mid" validate:"required,min=1"'];
// business_id
int64 business_id = 4[(gogoproto.jsontag) = "business_id", (gogoproto.customname) = "BusinessID"];
// business 业务
string business = 5 [(gogoproto.jsontag) = "business", (gogoproto.moretags) = 'form:"business" validate:"required"'];
// kid 业务中唯一id
int64 kid = 6 [(gogoproto.jsontag) = "kid", (gogoproto.moretags) = 'form:"kid"'];
// aid
int64 aid = 7 [(gogoproto.moretags) = 'form:"aid"'];
// sid
int64 sid = 8 [(gogoproto.moretags) = 'form:"sid"'];
// epid
int64 epid = 9 [(gogoproto.moretags) = 'form:"epid"'];
// cid
int64 cid = 10 [(gogoproto.moretags) = 'form:"cid"'];
// sub_type 子类型
int32 sub_type = 11 [(gogoproto.moretags) = 'form:"sub_type"'];
// device 设备类型
int32 device = 12 [(gogoproto.jsontag) = "device", (gogoproto.moretags) = 'form:"device"'];
// progress 进度
int32 progress = 13 [(gogoproto.jsontag) = "progress", (gogoproto.moretags) = 'form:"progress"'];
// 观看时间
int64 view_at = 14 [(gogoproto.jsontag) = "view_at", (gogoproto.moretags) = 'form:"view_at"'];
}
// Business model
message Business {
// id
int64 id = 1[(gogoproto.jsontag) = "id", (gogoproto.customname) = "ID"];
// name
string name = 2 [(gogoproto.jsontag) = "name"];
// ttl
int64 ttl = 3[(gogoproto.customname) = "TTL"];
}
// Merge model
message Merge {
// mid
int64 mid = 1 [(gogoproto.moretags) = "validate:\"required\""];
// bid
int64 bid = 2 [(gogoproto.jsontag) = "bid", (gogoproto.customname) = "Bid", (gogoproto.moretags) = 'validate:"required"'];
// business 业务
string business = 3 [(gogoproto.jsontag) = "-"];
// time
int64 time = 4 [(gogoproto.moretags) = 'validate:"required"'];
// kid
int64 kid = 5 [(gogoproto.moretags) = 'validate:"required"'];
}

View File

@@ -0,0 +1,12 @@
package model
//go:generate $GOPATH/src/go-common/app/tool/warden/protoc.sh
const (
// HideStateON 不记录播放历史
HideStateON = 1
// HideStateOFF 记录播放历史
HideStateOFF = 0
// HideStateNotFound not found
HideStateNotFound = -1
)

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/history/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/history/api/grpc:go_default_library",
"//app/service/main/history/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,18 @@
package grpc
import (
pb "go-common/app/service/main/history/api/grpc"
"go-common/app/service/main/history/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.RegisterHistoryServer(ws.Server(), svr)
ws, err := ws.Start()
if err != nil {
panic(err)
}
return ws
}

View File

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

View File

@@ -0,0 +1,70 @@
package http
import (
pb "go-common/app/service/main/history/api/grpc"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/binding"
)
func add(c *bm.Context) {
arg := new(pb.AddHistoryReq)
if err := c.Bind(arg); err != nil {
return
}
c.JSON(srv.AddHistory(c, arg))
}
func addHistories(c *bm.Context) {
arg := new(pb.AddHistoriesReq)
if err := c.BindWith(arg, binding.JSON); err != nil {
return
}
c.JSON(srv.AddHistories(c, arg))
}
func del(c *bm.Context) {
arg := new(pb.DelHistoriesReq)
if err := c.BindWith(arg, binding.JSON); err != nil {
return
}
c.JSON(srv.DelHistories(c, arg))
}
func clear(c *bm.Context) {
arg := new(pb.ClearHistoryReq)
if err := c.Bind(arg); err != nil {
return
}
c.JSON(srv.ClearHistory(c, arg))
}
func userHistories(c *bm.Context) {
arg := new(pb.UserHistoriesReq)
if err := c.Bind(arg); err != nil {
return
}
c.JSON(srv.UserHistories(c, arg))
}
func histories(c *bm.Context) {
arg := new(pb.HistoriesReq)
if err := c.Bind(arg); err != nil {
return
}
c.JSON(srv.Histories(c, arg))
}
func userHide(c *bm.Context) {
arg := new(pb.UserHideReq)
if err := c.Bind(arg); err != nil {
return
}
c.JSON(srv.UserHide(c, arg))
}
func updateHide(c *bm.Context) {
arg := new(pb.UpdateUserHideReq)
if err := c.Bind(arg); err != nil {
return
}
c.JSON(srv.UpdateUserHide(c, arg))
}

View File

@@ -0,0 +1,49 @@
package http
import (
"go-common/app/service/main/history/conf"
"go-common/app/service/main/history/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
srv *service.Service
vfy *verify.Verify
)
// Init init
func Init(c *conf.Config, svc *service.Service) {
srv = svc
vfy = verify.New(c.Verify)
engine := bm.DefaultServer(c.BM)
interRouter(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
func interRouter(e *bm.Engine) {
e.Ping(ping)
e.Register(register)
g := e.Group("/x/internal/history", vfy.Verify)
{
g.POST("/add", add)
g.POST("/add/multi", addHistories)
g.POST("/del", del)
g.POST("/clear", clear)
g.GET("/user", userHistories)
g.GET("/aids", histories)
g.GET("/hide", userHide)
g.POST("/hide/update", updateHide)
}
}
func ping(c *bm.Context) {
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}

View File

@@ -0,0 +1,62 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"history.go",
"service.go",
"user.go",
],
importpath = "go-common/app/service/main/history/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/history/api/grpc:go_default_library",
"//app/service/main/history/conf:go_default_library",
"//app/service/main/history/dao:go_default_library",
"//app/service/main/history/model:go_default_library",
"//library/ecode:go_default_library",
"//library/stat/prom:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/sync/pipeline:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//vendor/golang.org/x/sync/singleflight:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"history_test.go",
"service_test.go",
"user_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/history/api/grpc:go_default_library",
"//app/service/main/history/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,247 @@
package service
import (
"context"
"sort"
"strconv"
"sync"
pb "go-common/app/service/main/history/api/grpc"
"go-common/app/service/main/history/model"
"go-common/library/sync/errgroup"
"go-common/library/sync/pipeline"
)
const _delBatch = 20
// AddHistory add history
func (s *Service) AddHistory(c context.Context, arg *pb.AddHistoryReq) (reply *pb.AddHistoryReply, err error) {
if err = s.checkBusiness(arg.Business); err != nil {
return
}
reply = &pb.AddHistoryReply{}
// 用户忽略播放历史
userReply, _ := s.UserHide(c, &pb.UserHideReq{Mid: arg.Mid})
if userReply != nil && userReply.Hide {
return
}
if err = s.dao.AddHistoryCache(c, arg); err != nil {
return
}
s.addMerge(c, arg.Business, arg.Mid, arg.Kid, arg.ViewAt)
return
}
// AddHistories 增加多条播放历史记录
func (s *Service) AddHistories(c context.Context, arg *pb.AddHistoriesReq) (reply *pb.AddHistoriesReply, err error) {
var his []*pb.AddHistoryReq
reply = &pb.AddHistoriesReply{}
g := &errgroup.Group{}
merges := make([]*pb.AddHistoryReq, 0, 100)
mutex := &sync.Mutex{}
for _, a := range arg.Histories {
if err = s.checkBusiness(a.Business); err != nil {
return
}
a := a
g.Go(func() error {
// 用户忽略播放历史
userReply, _ := s.UserHide(c, &pb.UserHideReq{Mid: a.Mid})
if userReply != nil && userReply.Hide {
return nil
}
mutex.Lock()
his = append(his, a)
merges = append(merges, a)
mutex.Unlock()
return nil
})
}
g.Wait()
if err = s.dao.AddHistoriesCache(c, his); err != nil {
return
}
for _, a := range merges {
s.addMerge(c, a.Business, a.Mid, a.Kid, a.ViewAt)
}
return
}
// DelHistories delete histories
func (s *Service) DelHistories(c context.Context, arg *pb.DelHistoriesReq) (reply *pb.DelHistoriesReply, err error) {
reply = &pb.DelHistoriesReply{}
for _, r := range arg.Records {
if err = s.checkBusiness(r.Business); err != nil {
return
}
}
if len(arg.Records) > _delBatch {
g := errgroup.Group{}
for i := 0; i < len(arg.Records); i += _delBatch {
a := &pb.DelHistoriesReq{Mid: arg.Mid}
if i+_delBatch > len(arg.Records) {
a.Records = arg.Records[i:len(arg.Records)]
} else {
a.Records = arg.Records[i : i+_delBatch]
}
g.Go(func() (err error) {
err = s.dao.DeleteHistories(c, a)
return
})
}
if err = g.Wait(); err != nil {
return
}
} else {
if err = s.dao.DeleteHistories(c, arg); err != nil {
return
}
}
err = s.dao.DelHistoryCache(c, arg)
return
}
// ClearHistory clear histories
func (s *Service) ClearHistory(c context.Context, arg *pb.ClearHistoryReq) (reply *pb.ClearHistoryReply, err error) {
reply = &pb.ClearHistoryReply{}
for _, business := range arg.Businesses {
if err = s.checkBusiness(business); err != nil {
return
}
}
// mid下数据量很大 异步处理 防止超时错误
s.asyncFunc(func() {
if len(arg.Businesses) > 0 {
s.dao.ClearHistory(context.Background(), arg.Mid, arg.Businesses)
return
}
s.dao.ClearAllHistory(context.Background(), arg.Mid)
})
var businesses []string
if len(arg.Businesses) == 0 {
for b := range s.businessNames {
businesses = append(businesses, b)
}
} else {
businesses = arg.Businesses
}
err = s.dao.ClearHistoryCache(c, arg.Mid, businesses)
return
}
// UserHistories 查询用户的播放历史列表
func (s *Service) UserHistories(c context.Context, arg *pb.UserHistoriesReq) (reply *pb.UserHistoriesReply, err error) {
g := &errgroup.Group{}
var hisIds map[string][]int64
var his map[string][]*model.History
var cacheHis map[string]map[int64]*model.History
var err1, err2 error
g.Go(func() error {
var names = arg.Businesses
if len(names) == 0 {
names = make([]string, 0)
for name := range s.businessNames {
names = append(names, name)
}
}
if hisIds, err1 = s.dao.ListsCacheByTime(c, names, arg.Mid, arg.ViewAt, arg.Ps); err1 != nil {
return nil
}
cacheHis, err1 = s.dao.HistoriesCache(c, arg.Mid, hisIds)
return nil
})
g.Go(func() error {
his, err2 = s.dao.UserHistories(c, arg.Businesses, arg.Mid, arg.ViewAt, arg.Ps)
return nil
})
g.Wait()
if err1 != nil && err2 != nil {
err = err2
return
}
if cacheHis == nil {
cacheHis = make(map[string]map[int64]*model.History)
}
// 去重 优先用缓存数据
for business, hs := range his {
if cacheHis[business] == nil {
cacheHis[business] = make(map[int64]*model.History)
}
for _, h := range hs {
if _, ok := cacheHis[business][h.Kid]; !ok {
cacheHis[business][h.Kid] = h
}
}
}
histories := make([]*model.History, 0, len(cacheHis))
for _, hs := range cacheHis {
for _, h := range hs {
// 过滤上一条
if h.Kid != arg.Kid || arg.Business != h.Business {
histories = append(histories, h)
}
}
}
sort.Slice(histories, func(i, j int) bool { return histories[i].ViewAt > histories[j].ViewAt })
if int64(len(histories)) > arg.Ps {
histories = histories[0:arg.Ps]
}
reply = &pb.UserHistoriesReply{Histories: histories}
return
}
// Histories 根据id查询播放历史
func (s *Service) Histories(c context.Context, arg *pb.HistoriesReq) (reply *pb.HistoriesReply, err error) {
var (
cacheHis map[string]map[int64]*model.History
his map[int64]*model.History
)
cacheHis, _ = s.dao.HistoriesCache(c, arg.Mid, map[string][]int64{arg.Business: arg.Kids})
var miss []int64
for _, id := range arg.Kids {
if (cacheHis[arg.Business] == nil) || (cacheHis[arg.Business][id] == nil) {
miss = append(miss, id)
}
}
if cacheHis[arg.Business] != nil {
his = cacheHis[arg.Business]
}
if his == nil {
his = make(map[int64]*model.History)
}
if len(miss) > 0 {
hs, _ := s.dao.Histories(c, arg.Business, arg.Mid, miss)
for k, v := range hs {
his[k] = v
}
}
reply = &pb.HistoriesReply{Histories: his}
return
}
func (s *Service) addMerge(c context.Context, business string, mid, kid, time int64) {
s.merge.Add(c, strconv.FormatInt(mid, 10), &model.Merge{
Mid: mid,
Kid: kid,
Bid: s.businessNames[business].ID,
Time: time,
})
}
func (s *Service) initMerge() {
s.merge = pipeline.NewPipeline(s.c.Merge)
s.merge.Split = func(a string) int {
mid, _ := strconv.ParseInt(a, 10, 64)
return int(mid) % s.c.Merge.Worker
}
s.merge.Do = func(c context.Context, ch int, values map[string][]interface{}) {
var merges []*model.Merge
for _, vs := range values {
for _, v := range vs {
merges = append(merges, v.(*model.Merge))
}
}
s.dao.AddHistoryMessage(c, ch, merges)
}
s.merge.Start()
}

View File

@@ -0,0 +1,79 @@
package service
import (
"context"
"testing"
pb "go-common/app/service/main/history/api/grpc"
. "github.com/smartystreets/goconvey/convey"
)
func TestService_AddHistory(t *testing.T) {
var (
c = context.Background()
arg = &pb.AddHistoryReq{Business: "pgc", Kid: 1, Mid: 3}
)
Convey("add data", t, func() {
_, err := s.AddHistory(c, arg)
So(err, ShouldBeNil)
})
}
func TestService_AddHistories(t *testing.T) {
var (
c = context.Background()
arg = &pb.AddHistoriesReq{Histories: []*pb.AddHistoryReq{{Business: "pgc", Kid: 1, Mid: 3}}}
)
Convey("add data", t, func() {
_, err := s.AddHistories(c, arg)
So(err, ShouldBeNil)
})
}
func TestService_DelHistories(t *testing.T) {
Convey("del data", t, func() {
c := context.Background()
arg := &pb.DelHistoriesReq{Mid: 1, Records: []*pb.DelHistoriesReq_Record{{Business: "pgc", ID: 1}}}
_, err := s.DelHistories(c, arg)
So(err, ShouldBeNil)
})
}
func TestService_ClearHistory(t *testing.T) {
Convey("clear data", t, func() {
c := context.Background()
arg := &pb.ClearHistoryReq{Mid: 1}
_, err := s.ClearHistory(c, arg)
So(err, ShouldBeNil)
})
}
func TestService_UserHistories(t *testing.T) {
Convey("get user histories", t, func() {
c := context.Background()
arg := &pb.UserHistoriesReq{Mid: 1, Ps: 10}
gotReply, err := s.UserHistories(c, arg)
So(err, ShouldBeNil)
So(gotReply, ShouldNotBeEmpty)
})
}
func TestService_Histories(t *testing.T) {
Convey("get histories", t, func() {
c := context.Background()
arg := &pb.HistoriesReq{Mid: 1, Business: "pgc", Kids: []int64{1}}
gotReply, err := s.Histories(c, arg)
So(err, ShouldBeNil)
So(gotReply, ShouldNotBeEmpty)
})
}
// func TestService_FlushCache(t *testing.T) {
// Convey("get histories", t, func() {
// c := context.Background()
// arg := &pb.FlushCacheReq{Merges: []*model.Merge{{Mid: 1, Bid: 4}}}
// _, err := s.FlushCache(c, arg)
// So(err, ShouldBeNil)
// })
// }

View File

@@ -0,0 +1,78 @@
package service
import (
"context"
"errors"
"go-common/app/service/main/history/conf"
"go-common/app/service/main/history/dao"
"go-common/app/service/main/history/model"
"go-common/library/ecode"
"go-common/library/sync/pipeline"
"go-common/library/sync/pipeline/fanout"
)
const asyncProcNum = 100
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
businesses map[int64]*model.Business
businessNames map[string]*model.Business
merge *pipeline.Pipeline
asyncChan chan func()
cache *fanout.Fanout
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
cache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
asyncChan: make(chan func(), 1024),
}
s.businesses = s.dao.Businesses
s.businessNames = s.dao.BusinessNames
s.initMerge()
for i := 0; i < asyncProcNum; i++ {
go s.asyncFuncproc()
}
return s
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service
func (s *Service) Close() {
s.merge.Close()
s.dao.Close()
}
// checkBusiness .
func (s *Service) checkBusiness(bs string) (err error) {
if s.businessNames[bs] == nil {
err = ecode.AppDenied
}
return
}
func (s *Service) asyncFuncproc() {
for {
fn := <-s.asyncChan
fn()
}
}
func (s *Service) asyncFunc(f func()) (err error) {
select {
case s.asyncChan <- f:
default:
err = errors.New("async full")
}
return
}

View File

@@ -0,0 +1,22 @@
package service
import (
"flag"
"os"
"testing"
"go-common/app/service/main/history/conf"
)
var s *Service
func TestMain(m *testing.M) {
flag.Set("conf", "../cmd/history-service-test.toml")
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
s = New(conf.Conf)
m.Run()
os.Exit(0)
}

View File

@@ -0,0 +1,65 @@
package service
import (
"context"
"fmt"
pb "go-common/app/service/main/history/api/grpc"
"go-common/app/service/main/history/model"
"go-common/library/stat/prom"
"golang.org/x/sync/singleflight"
)
var cacheSingleFlight = &singleflight.Group{}
// UserHide 查询是否记录播放历史
func (s *Service) UserHide(c context.Context, arg *pb.UserHideReq) (reply *pb.UserHideReply, err error) {
reply = &pb.UserHideReply{}
addCache := true
var value int64
value, err = s.dao.UserHideCache(c, arg.Mid)
reply.Hide = value == model.HideStateON
if err != nil {
addCache = false
err = nil
}
if value != model.HideStateNotFound {
prom.CacheHit.Incr("UserHide")
return
}
var rr interface{}
sf := fmt.Sprintf("sf_u%d", arg.Mid)
rr, err, _ = cacheSingleFlight.Do(sf, func() (r interface{}, e error) {
prom.CacheMiss.Incr("UserHide")
r, e = s.dao.UserHide(c, arg.Mid)
return
})
reply.Hide = rr.(bool)
if err != nil || !addCache {
return
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetUserHideCache(ctx, arg.Mid, hideToState(reply.Hide))
})
return
}
// UpdateUserHide 修改是否记录播放历史
func (s *Service) UpdateUserHide(c context.Context, arg *pb.UpdateUserHideReq) (reply *pb.UpdateUserHideReply, err error) {
reply = &pb.UpdateUserHideReply{}
if err = s.dao.UpdateUserHide(c, arg.Mid, arg.Hide); err != nil {
return
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetUserHideCache(ctx, arg.Mid, hideToState(arg.Hide))
})
return
}
func hideToState(hide bool) int64 {
if hide {
return model.HideStateON
}
return model.HideStateOFF
}

View File

@@ -0,0 +1,28 @@
package service
import (
"context"
"testing"
"time"
pb "go-common/app/service/main/history/api/grpc"
. "github.com/smartystreets/goconvey/convey"
)
func TestService_UserHide(t *testing.T) {
var (
c = context.Background()
mid = int64(100)
)
Convey("set data", t, func() {
_, err := s.UpdateUserHide(c, &pb.UpdateUserHideReq{Mid: mid, Hide: true})
So(err, ShouldBeNil)
time.Sleep(time.Millisecond * 50)
Convey("get data", func() {
gotReply, err := s.UserHide(c, &pb.UserHideReq{Mid: mid})
So(err, ShouldBeNil)
So(gotReply.Hide, ShouldBeTrue)
})
})
}