Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/interface/main/history/api/grpc:all-srcs",
"//app/interface/main/history/cmd:all-srcs",
"//app/interface/main/history/conf:all-srcs",
"//app/interface/main/history/dao/history:all-srcs",
"//app/interface/main/history/dao/toview:all-srcs",
"//app/interface/main/history/http:all-srcs",
"//app/interface/main/history/model:all-srcs",
"//app/interface/main/history/rpc/client:all-srcs",
"//app/interface/main/history/server/gorpc:all-srcs",
"//app/interface/main/history/server/grpc:all-srcs",
"//app/interface/main/history/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,541 @@
#### Basic service of historical function
#### Version v5.9.11
> 1.调整 Histories businesses
#### Version v5.9.10
> 1.屏蔽aid =-1|| ugc&pgc cid==0
#### Version v5.9.9
> 1.屏蔽aid =-1
#### Version v5.9.8
> 1.无脑式删除两遍
#### Version v5.9.7
> 1.web aid pgc 删除
#### Version v5.9.6
> 1. aid pgc 删除
#### Version v5.9.5
> 1. position 返回NothingFound
#### Version v5.9.4
> 1. Business id 转换
#### Version v5.9.3
> 1. 删除番剧 fix
#### Version v5.9.2
> 1. 同步删除
> 2. 删除番剧
#### Version v5.9.1
> 1. read tidb view at
#### Version v5.9.0
> 1. read tidb
#### Version v5.8.5
> 1. 去除v1 目录
> 2. 删除pgc http
#### Version v5.8.4
> 1. NewEpIndex
> 2. 清空操作加入行为日志
#### Version v5.8.3
> 1. 重新打包docker
#### Version v5.8.2
> 1. ToViewOverMax
#### Version v5.8.1
> 1. 删除hbase 90天之前的数据
> 2. 时间相同 按照aid排序
#### Version v5.8.0
> 1. toview ugcPay
#### Version v5.7.8
> 1. 修复hide
#### Version v5.7.7
> 1. NewestEpIndex - NewEpShow
> 2. 配置文件控制迁移番剧grpc
#### Version v5.7.6
> 1. gorpc service
#### Version v5.7.5
> 1. bangumi grpc
#### Version v5.7.4
> 1. 增加clear 内网接口
#### Version v5.7.3
> 1. 修复 delete 无business bug
#### Version v5.7.1
> 1. delete 改为同步删除
#### Version v5.7.0
> 1. 接入history-service 双写
#### Version v5.6.11
> 1.grpc.
#### Version v5.6.10
> 1.rpc discovery.
#### Version v5.6.9
> 1.删除remote ip.
#### Version v5.6.8
> 1.删除PlayProgress-T 生产方代码 .
#### Version v5.6.7
> 1.屏蔽上报数据为离线类型 .
#### Version v5.6.6
> 1.mid .
#### Version v5.6.5
> 1.hbase 落库
#### Version v5.6.4
> 1. 重新打镜像
#### Version v5.6.3
> 1. auth&verify
#### Version v5.6.2
> 1. docker
#### Version v5.6.1
> 1. 缓存数据填充business
#### Version v5.6.0
> 1.合并播放进度
#### Version v5.5.2
> 1. 修改delete rpc 参数类型
#### Version v5.5.1
> 1. 修复查询bug
#### Version v5.5.0
> 1. 查询历史记录接口增加分类字段
> 2. 删除历史记录接口使用business标志
> 3. 增加清空列表rpc
#### Version v5.4.1
> 1. 优化游标未找到的情况
#### Version v5.4.0
> 1. 统一业务标志
#### Version v5.3.2
> 1.接入漫画
> 2.增加内网批量删除接口
#### Version v5.3.1
> 1.update hbase sdk
#### Version v5.3.0
> 1.update infoc sdk
#### Version v5.2.11
> 1.DELETE kafka
#### Version v5.2.10
> 1.单独获取直播|文章 历史记录列表
#### Version v5.2.9
> 1.兼容app 播放器上报参数缺少情况
#### Version v5.2.8
> 1.分批调用收藏RPC接口
> 2.修改内网查用户历史记录接口
#### Version v5.2.7
> 1.增加安卓端TV设备类型
#### Version v5.2.6
> 1.迁移收藏接口至rpc接口isFavs
#### Version v5.2.5
> 1.fix toviews adds, 已存在列表后,添加失败.
> 2.增加管理员查看用户的历史记录和稍后待看.
> 3.修改用户加经验调整顺序redis操作前.
#### Version v5.2.4
> 1.增加默认时间
#### Version v5.2.3
> 1.fix web 单删
#### Version v5.2.2
> 1.增加report 内网接口
> 2.用户唯一rowkey
> 3.增加live数据
> 4.删除source参数
#### Version v5.2.1
> 1.迁出kafka
> 2.toview去掉分区限制逻辑
#### Version v5.2.0
> 1.迁移用户增加经验通道至databus
#### Version v5.1.5
> 1.迁移至business/interface/main目录
#### Version v5.1.4
> 1.迁移至BM框架
> 2.删除bangumi 老接口
> 3.bangumi 批量请求
#### Version v5.1.3
> 1.databus client 重新打包
#### Version v5.1.2
> 1.删除statsd
#### Version v5.1.1
> 1.更新bangumi信息
#### Version v5.1.0
> 1.去掉老hbase
#### Version v5.0.1
> 1.fix mid
#### Version v5.0.0
> 1.历史记录平台化
#### Version v4.10.2
> 1.修复history热点key问题
#### Version v4.10.1
> 1.兼容android app端subtype
#### Version v4.10.0
> 1.独立出toview cache conf
#### Version v4.9.14
> 1.修复缓存context
#### Version v4.9.13
> 1.返回数据增加sub_type字段
#### Version v4.9.12
> 1.report增加sub_type字段
#### Version v4.9.10
> 1.升级archive3接口
#### Version v4.9.9
> 1.trace测试
#### Version v4.9.8
> 1.trace测试
#### Version v4.9.7
> 1.trace测试
#### Version v4.9.6
> 1.修复webToview data 为nil
> 2.同步番剧数据增加类型和子类型
> 3.增加hbase deadline-exceeded日志
#### Version v4.9.5
> 1.去掉调用稿件AidByCid
#### Version v4.9.4
> 1.优化用户无稍后再看数据问题
#### Version v4.9.3
> 1.用户稍后再看数据迁移
#### Version v4.9.2
> 1.用户稍后再看数据双写
#### Version v4.9.1
> 1.迁移用户开关状态
> 2.info hbase库family fix
#### Version v4.9.0
> 1.迁移hbase数据,不支持回滚
#### Version v4.8.2
> 1.增加双读hbase
#### Version v4.8.1
> 1.增加新hbase日志
#### Version v4.8.0
> 1.增加双写hbase
#### Version v4.7.3
> 1.web端带看列表增加bangumi信息
#### Version v4.7.2
> 1.获取待看列表cache map
#### Version v4.7.1
> 1.移除稿件过滤逻辑
#### Version v4.7.0
> 1.增加批量增加待看列表功能
#### Version v4.6.0
> 1.merge history into kratos
##### Version v4.5.10
> 1.fix map key
> 2.增加反作弊上报日志
> 3.冷方式删除番剧多集
##### Version v4.5.9
> 1.fix 移动端批量同步缓存
##### Version v4.5.8
> 1.待看列表删除大于30s
##### Version v4.5.7
> 1.接入log-agent
##### Version v4.5.6
> 1.更新go-common v6.16.0go-business v2.21.4
> 2.接入新prom
> 3.删除delChan增加监控
##### Version v4.5.5
> 1.优化databus key
> 2.databus增加字段
> 3.fix 版权问题
##### Version v4.5.4
> 1.修复hbase TODO.
##### Version v4.5.2
> 1.读hbase降级
> 2.去除hbase双写
> 3.升级go-common go-business
##### Version v4.5.1
> 1.fix hbase表名
##### Version v4.5.0
> 1.hbase表双写
##### Version v4.4.3
> 1.prom
##### Version v4.4.2
> 1.Identify&csrf
##### Version v4.4.1
> 1.修复aids 空间
> 2.接入prom
##### Version v4.4.0
> 1.去掉批量查询分P信息
> 2.修复P数待看列表的总数
> 3.接入proms
##### Version v4.3.0
> 1.增加web端待看列表接口
> 2.待看列表增加page分批信息
> 3.增加总P数
> 4.待看列表aid>0
##### Version v4.2.5
> 1.接入新配置中心
##### Version v4.2.4
> 1.修复AddHistories
> 2.增加rpc
##### Version v4.2.2
> 1.平滑发布
##### Version v4.2.0
> 1.删除report进度表
##### Version v4.1.2
> 1.去除report表逻辑
> 2.databus key mid%100
##### Version v4.1.1
> 1.修复toview列表数量超限
##### Version v4.0.21
> 1.增加白名单日志
> 2.修复了ctx声明
##### Version v4.0.20
> 1.修复mid为0不保存历史记录
##### Version v4.0.4
> 1.缓存数量限制
##### Version v4.0.3
> 1.修复缓存数量限制
##### Version v3.5.0
> 1.job异步持久化
> 2.重构历史记录
##### Version v3.4.5
> 1.修改monitor/ping调用方式
##### Version v3.4.4
> 1.更新http接口支持内外网流量
##### Version v3.4.3
> 1.增加收藏标识
> 2.历史记录暂停功能
##### Version v3.4.3
> 1.增加收藏标识
> 2.历史记录暂停功能
##### Version 3.4.0
> 1.接入ci配置中心
> 2.只记录登陆用户上报
> 3.添加inner接口
##### Version 3.3.1
> 1.兼容ios参数float
##### Version 3.3.0
> 1.增加cid查询稿件信息
> 2.更新identity
> 3.天马稍后再看
##### Version 3.2.1
> 1.更新vendor依赖
##### Version 3.2.0
> 1.更新vendor依赖
##### Version 3.1.0
> 1.番剧播放离线同步
##### Version 3.0.0
> 1.govendor支持
> 2.go-business依赖
> 3.增加habse平监控
##### Version 2.7.2
> 1.清除pay全部返回0
##### Version 2.7.1
> 1.修复历史记录条数截断
##### Version 2.7.0
> 1.movie字段特殊化处理
> 2.暂时去除稿件状态判断,兼容安卓
> 3.删除redis和hbase1 全量走hbase2
##### Version 2.6.2
> 1.修复移动端循环加载历史记录
##### Version 2.6.1
> 1.添加开关控制播放历史流量走新Hbase表
##### Version 2.6.0
> 1.添加trace_v2
> 2.修改hbase存储方式双写
##### Version 2.5.0
> 1.添加获取播放历史稿件id列表的接口
##### Version 2.4.1
> 1.[bug]判断页码页大小
> 2.[bug]hbase scan需要加上分隔符"|"
##### Version 2.4.0
> 1.持久化由redis迁移至hbase
##### Version 2.3.0
> 1.添加服务发现
##### Version 2.2.1
> 1.优化配置
##### Version 2.2.0
> 1.优化
> 2.add tracer id
> 3.add elk
##### Version 2.1.0
> 1.add tracer
##### Version 2.0.0
> 1.api 2.0
##### Version 1.2.0
> 1.reconstruction
##### Version 1.1.0
> 1.按照go-common重构
##### Version 1.0.0
> 1.重构历史

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
#### history
##### 项目简介
> 1.用户历史记录,待看列表项目
##### 编译环境
> 请只用golang v1.7.x以上版本编译执行。
##### 依赖包
> 1.公共包go-common
##### 特别说明
> 1.model目录可能会被其他项目引用请谨慎请改并通知各方。

View File

@@ -0,0 +1,53 @@
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"],
)
go_proto_library(
name = "v1_go_proto",
compilers = ["@io_bazel_rules_go//proto:go_grpc"],
importpath = "go-common/app/interface/main/history/api/grpc",
proto = ":v1_proto",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [],
embed = [":v1_go_proto"],
importpath = "go-common/app/interface/main/history/api/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"@com_github_golang_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,130 @@
syntax = "proto3";
package community.service.history;
option go_package = "v1";
message AddHistoryReply {
}
message AddHistoryReq {
int64 mid = 2;
int64 rtime = 3;
ModelHistory h = 4;
}
message ClearHistoryReply {
}
message ClearHistoryReq {
int64 mid = 2;
repeated string businesses = 3;
}
message DeleteReply {
}
message DeleteReq {
int64 mid = 2;
repeated ModelHistory his = 3;
}
message FlushHistoryReply {
}
message FlushHistoryReq {
repeated int64 mids = 2;
int64 stime = 3;
}
message HistoriesReply {
repeated ModelResource res = 1;
}
message HistoriesReq {
int64 mid = 2;
string business = 3;
int32 pn = 4;
int32 ps = 5;
}
message HistoryCursorReply {
repeated ModelResource res = 1;
}
message HistoryCursorReq {
int64 mid = 2;
int64 max = 3;
int64 view_at = 4;
int32 ps = 5;
string business = 6;
repeated string businesses = 7;
string ip = 8;
}
message ModelHistory {
int64 mid = 1;
int64 aid = 2;
int64 sid = 3;
int64 epid = 4;
int32 tp = 5;
string business = 6;
int32 stp = 7;
int64 cid = 8;
int32 dt = 9;
int64 pro = 10;
int64 unix = 11;
}
message ModelResource {
int64 mid = 1;
int64 oid = 2;
int64 sid = 3;
int64 epid = 4;
int32 tp = 5;
int32 stp = 6;
int64 cid = 7;
string business = 8;
int32 dt = 9;
int64 pro = 10;
int64 unix = 11;
}
message PositionReply {
ModelHistory res = 1;
}
message PositionReq {
int64 mid = 2;
int64 aid = 3;
string business = 4;
}
message ProgressReply {
map<int64, ModelHistory> res = 1;
}
message ProgressReq {
int64 mid = 2;
repeated int64 aids = 3;
}
service History {
// AddHistory add hisotry progress into hbase.
rpc AddHistory(AddHistoryReq) returns(AddHistoryReply);
// Progress get view progress from cache/hbase.
rpc Progress(ProgressReq) returns(ProgressReply);
// Position get view progress from cache/hbase.
rpc Position(PositionReq) returns(PositionReply);
// ClearHistory clear user's historys.
rpc ClearHistory(ClearHistoryReq) returns(ClearHistoryReply);
// Histories return the user all av history.
rpc Histories(HistoriesReq) returns(HistoriesReply);
// HistoryCursor return the user all av history.
rpc HistoryCursor(HistoryCursorReq) returns(HistoryCursorReply);
// Delete .
rpc Delete(DeleteReq) returns(DeleteReply);
// FlushHistory flush to hbase from cache.
rpc FlushHistory(FlushHistoryReq) returns(FlushHistoryReply);
}

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 = ["history-interface-test.toml"],
importpath = "go-common/app/interface/main/history/cmd",
tags = ["automanaged"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/http:go_default_library",
"//app/interface/main/history/server/gorpc:go_default_library",
"//app/interface/main/history/server/grpc:go_default_library",
"//app/interface/main/history/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,262 @@
# This is a TOML document. Boom.
version = "2.3.0"
user = "nobody"
pid = "/tmp/history.pid"
dir = "./"
perf = "0.0.0.0:6000"
checkFile = "/data/www/history.html"
family = "history"
address = ""
# history
[history]
max = 10
total = 20
cache = 10
page = 20
size = 100
ticker = "30s"
pgc = true
tid = [33,120,148,149,32,94,150,151]
Exp = true
mids = [14771787]
ConsumeSize = 100
[xlog]
dir = "/data/log/history/"
[collector]
taskID = "000078"
proto = "tcp"
addr = "172.19.100.20:5401"
chanSize = 1024
[thirdBusines]
favoriteURL = "http://api.bilibili.co/x/v2/fav/video/favoureds"
bangumiV2URL = "http://bangumi.bilibili.co/internal_api/get_eps_v2"
seasonURL = "http://bangumi.bilibili.co/api/inner/archive/aid2seasonid"
[thirdBusines.httpClient]
dial = "1s"
timeout = "1s"
keepAlive = "60s"
timer = 1000
key = "f6433799dbd88751"
secret = "36f8ddb1806207fe07013ab6a77a3935"
[thirdBusines.httpClient.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.1
request = 100
[app]
key = "f6433799dbd88751"
secret = "36f8ddb1806207fe07013ab6a77a3935"
[multiHTTP]
[multiHTTP.outer]
addrs = ["0.0.0.0:6001"]
maxListen = 10
[multiHTTP.inner]
addrs = ["0.0.0.0:6002"]
maxListen = 10
[multiHTTP.local]
addrs = ["0.0.0.0:6003"]
maxListen = 10
[bm]
addr = "0.0.0.0:6001"
maxListen = 1000
timeout = "800ms"
[identify]
whiteAccessKey = ""
whiteMid = 0
csrfOn = false
[identify.app]
key = "0dc647722719f2ea"
secret = "a3769b553680da10b40994265fb48d20"
[identify.memcache]
name = "go-business/identify"
proto = "tcp"
addr = "172.16.0.148:11211"
active = 5
idle = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[identify.host]
auth = "http://passport.bilibili.co"
secret = "http://open.bilibili.co"
[identify.httpClient]
dial = "30ms"
timeout = "100ms"
keepAlive = "60s"
key = "f6433799dbd88751"
secret = "36f8ddb1806207fe07013ab6a77a3935"
[identify.httpClient.breaker]
window = "10s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[identify.httpClient.url]
"http://passport.bilibili.co/intranet/auth/tokenInfo" = {timeout = "100ms"}
"http://passport.bilibili.co/intranet/auth/cookieInfo" = {timeout = "100ms"}
"http://open.bilibili.co/api/getsecret" = {timeout = "500ms"}
[rpcClient2]
[rpcClient2.account]
pullInterval = "10s"
[rpcClient2.account.client]
timeout = "1s"
timer = 1000
[rpcClient2.account.client.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[[rpcClient2.account.backup]]
proto = "tcp"
addr = "172.16.33.56:6079"
timeout = "1s"
timer = 1000
[rpcClient2.account.zookeeper]
root = "/microservice/account-service/"
addrs = ["172.18.33.172:2181"]
timeout = "30s"
[rpcClient2.archive]
group = "test"
pullInterval = "10s"
[rpcClient2.archive.client]
token = "123456"
timeout = "1s"
timer = 1000
[rpcClient2.archive.client.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[[rpcClient2.archive.backup]]
proto = "tcp"
addr = "172.16.33.56:6089"
timeout = "1s"
timer = 1000
[rpcClient2.archive.zookeeper]
root = "/microservice/archive-service/"
addrs = ["172.18.33.172:2181"]
timeout = "30s"
[rpcClient2.archive.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[toview]
name = "history"
proto = "tcp"
addr = "172.16.33.54:6381"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "6h"
[redis]
name = "history"
proto = "tcp"
addr = "172.16.33.54:6380"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "6h"
[databus]
[databus.merge]
key = "0Pub71WwEMKXu63qtztu"
secret= "0Pub71WwEMKXu63qtztv"
group= "HistoryMerge-History-P"
topic= "HistoryMerge-T"
action="pub"
name = "history"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[databus.Experience]
key = "0Pub71WwEMKXu63qtztu"
secret= "0Pub71WwEMKXu63qtztv"
group= "AccountExp-MainCommunity-P"
topic= "AccountExp-T"
action="pub"
name = "history"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[ecode]
domain = "172.16.33.248:6401"
all = "1h"
diff = "10m"
[ecode.clientconfig]
dial = "2000ms"
timeout = "2s"
keepAlive = "10s"
timer = 128
key = "test"
secret = "e6c4c252dc7e3d8a90805eecd7c73396"
[ecode.clientconfig.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[tracer]
proto = "udp"
addr = "172.16.33.46:5140"
tag = "platform/history"
[info]
master = ""
meta = ""
dialTimeout = "1s"
readTimeout = "10s"
readsTimeout = "10s"
writeTimeout = "10s"
writesTimeout = "10s"
[info.zookeeper]
root = ""
addrs = ["172.18.33.131:2181","172.18.33.168:2181","172.18.33.169:2181"]
timeout = "30s"
[grpc]
timeout = "1s"
addr = "0.0.0.0:6008"

View File

@@ -0,0 +1,58 @@
package main
import (
"context"
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/interface/main/history/conf"
"go-common/app/interface/main/history/http"
rpc "go-common/app/interface/main/history/server/gorpc"
"go-common/app/interface/main/history/server/grpc"
"go-common/app/interface/main/history/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.Xlog)
defer log.Close()
trace.Init(conf.Conf.Tracer)
defer trace.Close()
log.Info("history start")
ecode.Init(conf.Conf.Ecode)
report.InitUser(conf.Conf.Report)
svr := service.New(conf.Conf)
rpcSvr := rpc.New(conf.Conf, svr)
grpcSvr := grpc.New(conf.Conf.GRPC, svr)
http.Init(conf.Conf, svr)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("history get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("history exit")
grpcSvr.Shutdown(context.TODO())
time.Sleep(2 * time.Second)
rpcSvr.Close()
svr.Close()
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/interface/main/history/conf",
tags = ["automanaged"],
deps = [
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/hbase.v2:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/auth: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,134 @@
package conf
import (
"flag"
"go-common/library/cache/redis"
"go-common/library/conf"
ecode "go-common/library/ecode/tip"
xlog "go-common/library/log"
"go-common/library/log/infoc"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/auth"
"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"
xtime "go-common/library/time"
"go-common/library/database/hbase.v2"
"github.com/BurntSushi/toml"
)
const (
configKey = "history.toml"
)
var (
confPath string
// Conf global
Conf = &Config{}
)
// HBaseConfig ...
type HBaseConfig struct {
*hbase.Config
WriteTimeout xtime.Duration
ReadTimeout xtime.Duration
}
// Config service conf
type Config struct {
Tracer *trace.Config
History *History
BM *bm.ServerConfig
RPCClient2 *RPC
Toview *Redis
Redis *Redis
Xlog *xlog.Config
Info *HBaseConfig
DataBus *Databus
Auth *auth.Config
Verify *verify.Config
Collector *infoc.Config
Ecode *ecode.Config
RPCServer *rpc.ServerConfig
GRPC *warden.ServerConfig
ThirdBusines *ThirdBusines
Report *databus.Config
}
// History history.
type History struct {
Max int
Total int
Cache int
Page int
Size int
Ticker xtime.Duration
Pub bool
ConsumeSize int
Migration bool
Rate int64
Mids []int64
}
// ThirdBusines Bangumi favorite.
type ThirdBusines struct {
BangumiV2URL string
SeasonURL string
HTTPClient *bm.ClientConfig
}
// Databus .
type Databus struct {
PlayPro *databus.Config
Merge *databus.Config
Experience *databus.Config
Pub *databus.Config
}
// Redis redis.
type Redis struct {
*redis.Config
Expire xtime.Duration
}
// RPC rpc.
type RPC struct {
Archive *rpc.ClientConfig
Favorite *rpc.ClientConfig
History *warden.ClientConfig
}
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
}
func configCenter() (err error) {
var (
ok bool
value string
client *conf.Client
)
if client, err = conf.New(); err != nil {
return
}
if value, ok = client.Value(configKey); !ok {
panic(err)
}
_, err = toml.Decode(value, &Conf)
return
}

View File

@@ -0,0 +1,68 @@
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",
"exp_test.go",
"grpc_test.go",
"hbase_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/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",
"exp.go",
"grpc.go",
"hbase.go",
"redis.go",
],
importpath = "go-common/app/interface/main/history/dao/history",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/model:go_default_library",
"//app/service/openplatform/pgc-season/api/grpc/episode/v1:go_default_library",
"//library/cache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/hbase.v2:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//vendor/github.com/tsuna/gohbase/hrpc: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,73 @@
package history
import (
"context"
"time"
"go-common/app/interface/main/history/conf"
eprpc "go-common/app/service/openplatform/pgc-season/api/grpc/episode/v1"
"go-common/library/cache"
"go-common/library/cache/redis"
"go-common/library/queue/databus"
"go-common/library/database/hbase.v2"
)
// Dao dao.
type Dao struct {
conf *conf.Config
info *hbase.Client
redis *redis.Pool
playPro *databus.Databus
merge *databus.Databus
experience *databus.Databus
proPub *databus.Databus
delChan *cache.Cache
expire int
epidGRPC eprpc.EpisodeClient
}
// New new history dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
conf: c,
info: hbase.NewClient(c.Info.Config),
redis: redis.NewPool(c.Redis.Config),
playPro: databus.New(c.DataBus.PlayPro),
merge: databus.New(c.DataBus.Merge),
proPub: databus.New(c.DataBus.Pub),
experience: databus.New(c.DataBus.Experience),
delChan: cache.New(1, 1024),
expire: int(time.Duration(c.Redis.Expire) / time.Second),
}
var err error
if d.epidGRPC, err = eprpc.NewClient(nil); err != nil {
panic(err)
}
return
}
// Ping check connection success.
func (d *Dao) Ping(c context.Context) (err error) {
return d.PingRedis(c)
}
// Close close the redis and kafka resource.
func (d *Dao) Close() {
if d.redis != nil {
d.redis.Close()
}
if d.playPro != nil {
d.playPro.Close()
}
if d.merge != nil {
d.merge.Close()
}
if d.experience != nil {
d.experience.Close()
}
if d.proPub != nil {
d.proPub.Close()
}
}

View File

@@ -0,0 +1,33 @@
package history
import (
"flag"
"os"
"testing"
"go-common/app/interface/main/history/conf"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.history")
flag.Set("conf_token", "01423f5b752144440e01cb8ff4432e96")
flag.Set("tree_id", "2298")
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")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,41 @@
package history
import (
"context"
"strconv"
"go-common/library/log"
)
// PlayPro send history to databus.
func (d *Dao) PlayPro(c context.Context, key string, msg interface{}) (err error) {
if err = d.playPro.Send(c, key, msg); err != nil {
log.Error("d.pub.Pub(%s,%v) error(%v)", key, msg, err)
}
return
}
// Merge send history to databus.
func (d *Dao) Merge(c context.Context, mid int64, msg interface{}) (err error) {
key := strconv.FormatInt(mid, 10)
if err = d.merge.Send(c, key, msg); err != nil {
log.Error("d.pub.Pub(%s,%v) error(%v)", key, msg, err)
}
return
}
// experiencePub send history to databus.
func (d *Dao) experiencePub(c context.Context, key string, msg interface{}) (err error) {
if err = d.experience.Send(c, key, msg); err != nil {
log.Error("d.pub.Pub(%s,%v) error(%v)", key, msg, err)
}
return
}
// ProPub send history to databus.
func (d *Dao) ProPub(c context.Context, key string, msg interface{}) (err error) {
if err = d.proPub.Send(c, key, msg); err != nil {
log.Error("d.ProPub.Pub(%s,%v) error(%v)", key, msg, err)
}
return
}

View File

@@ -0,0 +1,69 @@
package history
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestHistoryPlayPro(t *testing.T) {
convey.Convey("PlayPro", t, func(ctx convey.C) {
var (
c = context.Background()
key = "14771787"
msg = interface{}(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.PlayPro(c, key, msg)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryMerge(t *testing.T) {
convey.Convey("Merge", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
msg = interface{}(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
d.Merge(c, mid, msg)
})
})
}
func TestHistoryexperiencePub(t *testing.T) {
convey.Convey("experiencePub", t, func(ctx convey.C) {
var (
c = context.Background()
key = "14771787"
msg = interface{}(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.experiencePub(c, key, msg)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryProPub(t *testing.T) {
convey.Convey("ProPub", t, func(ctx convey.C) {
var (
c = context.Background()
key = "14771787"
msg = interface{}(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.ProPub(c, key, msg)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,72 @@
package history
import (
"context"
"strconv"
"time"
"go-common/library/cache/redis"
"go-common/library/log"
"go-common/library/net/metadata"
)
const (
_keyFirst = "his_f_"
_timeMonth = "0102"
_view = "view"
)
type experienceMsg struct {
Event string `json:"event"`
Mid int64 `json:"mid"`
IP string `json:"ip"`
TS int64 `json:"ts"`
}
// keyFirst return first key
func keyFirst(mid int64, t string) string {
return _keyFirst + strconv.FormatInt(mid%1000, 10) + "_" + t
}
// PushFirstQueue push first view record every day into kafka.
func (d *Dao) PushFirstQueue(c context.Context, mid, aid, now int64) (err error) {
var (
today = time.Unix(now, 0)
md = today.Format(_timeMonth)
key = keyFirst(mid, md)
conn = d.redis.Get(c)
)
defer conn.Close()
rp, err := redis.Int(conn.Do("SISMEMBER", key, mid))
if err != nil {
log.Error("conn.Do(SISMEMBER, %s, %d) error(%v)", key, mid, err)
err = nil
}
// if key exist , donot push to kafka
if rp > 0 {
return
}
midStr := strconv.FormatInt(mid, 10)
ex := &experienceMsg{
Event: _view,
Mid: mid,
IP: metadata.String(c, metadata.RemoteIP),
TS: now,
}
err = d.experiencePub(c, midStr, ex)
conn.Send("SADD", key, mid)
conn.Send("EXPIRE", key, 24*60*60)
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive1() error(%v)", err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive2() error(%v)", err)
return
}
return
}

View File

@@ -0,0 +1,35 @@
package history
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestHistorykeyFirst(t *testing.T) {
convey.Convey("keyFirst", t, func(ctx convey.C) {
var (
mid = int64(14771787)
no = "14771787"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
keyFirst(mid, no)
})
})
}
func TestHistoryPushFirstQueue(t *testing.T) {
convey.Convey("PushFirstQueue", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aid = int64(1222)
now = time.Now().Unix()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
d.PushFirstQueue(c, mid, aid, now)
})
})
}

View File

@@ -0,0 +1,64 @@
package history
import (
"context"
"go-common/app/interface/main/history/model"
epmdl "go-common/app/service/openplatform/pgc-season/api/grpc/episode/v1"
"go-common/library/log"
)
// Bangumis .
func (d *Dao) Bangumis(c context.Context, mid int64, epid []int64) (map[int64]*model.Bangumi, error) {
req := &epmdl.EpReq{}
for _, aid := range epid {
req.Epids = append(req.Epids, int32(aid))
}
resp, err := d.epidGRPC.Cards(c, req)
if err != nil {
log.Error("s.epidGRPC.Cards(%v, %v) error(%v)", c, req, err)
return nil, err
}
data := make(map[int64]*model.Bangumi)
for k, v := range resp.Cards {
data[int64(k)] = &model.Bangumi{
Epid: int64(v.EpisodeId),
Title: v.Title,
LongTitle: v.LongTitle,
EpisodeStatus: int(v.EpisodeStatus),
Cover: v.Cover,
Season: &model.Season{
ID: int64(v.Season.SeasonId),
Title: v.Season.Title,
SeasonStatus: int(v.Season.SeasonStatus),
IsFinish: int(v.Season.IsFinish),
TotalCount: v.Season.TotalCount,
NewestEpid: int64(v.Season.NewEpId),
NewestEpIndex: v.Season.NewEpIndex,
SeasonType: int(v.Season.SeasonType),
},
}
}
return data, nil
}
// BangumisByAids .
func (d *Dao) BangumisByAids(c context.Context, mid int64, aids []int64, realIP string) (map[int64]*model.BangumiSeason, error) {
req := &epmdl.EpAidReq{}
for _, aid := range aids {
req.Aids = append(req.Aids, int32(aid))
}
resp, err := d.epidGRPC.ListByAids(c, req)
if err != nil {
log.Error("s.epidGRPC.ListByAids(%v, %v) error(%v)", c, req, err)
return nil, err
}
data := make(map[int64]*model.BangumiSeason)
for _, v := range resp.Infos {
data[int64(v.Aid)] = &model.BangumiSeason{
ID: int64(v.SeasonId),
Epid: int64(v.EpisodeId),
}
}
return data, nil
}

View File

@@ -0,0 +1,39 @@
package history
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestHistoryBangumis(t *testing.T) {
convey.Convey("Bangumis", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
epid = []int64{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
d.Bangumis(c, mid, epid)
})
})
}
func TestHistoryBangumisByAids(t *testing.T) {
convey.Convey("BangumisByAids", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{11, 33}
realIP = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.BangumisByAids(c, mid, aids, realIP)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,286 @@
package history
import (
"bytes"
"context"
"crypto/md5"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"time"
"go-common/app/interface/main/history/model"
"go-common/library/log"
"github.com/tsuna/gohbase/hrpc"
)
var (
tableInfo = "ugc:history"
family = "info"
familyB = []byte(family)
columnSW = "sw"
columnSWB = []byte(columnSW)
)
// hashRowKey create rowkey(md5(mid)[:2]+mid) for histroy by mid .
func hashRowKey(mid int64) string {
var bs = make([]byte, 8)
binary.LittleEndian.PutUint64(bs, uint64(mid))
rk := md5.Sum(bs)
return fmt.Sprintf("%x%d", rk[:2], mid)
}
func (d *Dao) column(aid int64, typ int8) string {
if typ < model.TypeArticle {
return strconv.FormatInt(aid, 10)
}
return fmt.Sprintf("%d_%d", aid, typ)
}
// Add add history list.
func (d *Dao) Add(ctx context.Context, mid int64, h *model.History) (err error) {
var (
valueByte []byte
column string
key = hashRowKey(mid)
fValues = make(map[string][]byte)
)
if h.Aid == 0 {
return
}
column = d.column(h.Aid, h.TP)
if valueByte, err = json.Marshal(h); err != nil {
log.Error("json.Marshal(%v) error(%v)", h, err)
return
}
fValues[column] = valueByte
values := map[string]map[string][]byte{family: fValues}
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
if _, err = d.info.PutStr(ctx, tableInfo, key, values); err != nil {
log.Error("info.PutStr error(%v)", err)
}
return
}
// AddMap add history list.
func (d *Dao) AddMap(ctx context.Context, mid int64, hs map[int64]*model.History) (err error) {
var (
timeB []byte
column string
key = hashRowKey(mid)
fValues = make(map[string][]byte)
)
for _, h := range hs {
if h.Aid == 0 {
continue
}
// TODO typ and h.type is or not consistent .
column = d.column(h.Aid, h.TP)
if timeB, err = json.Marshal(h); err != nil {
log.Error("json.Marshal(%v) error(%v)", h, err)
continue
}
fValues[column] = timeB
}
values := map[string]map[string][]byte{family: fValues}
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
if _, err = d.info.PutStr(ctx, tableInfo, key, values); err != nil {
log.Error("info.PutStr error(%v)", err)
}
return
}
// AidsMap get all Historys from hbase by aids.
func (d *Dao) AidsMap(ctx context.Context, mid int64, aids []int64) (his map[int64]*model.History, err error) {
key := hashRowKey(mid)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.ReadTimeout))
defer cancel()
var options []func(hrpc.Call) error
if len(aids) != 0 {
columns := make([]string, 0, len(aids))
for _, aid := range aids {
columns = append(columns, fmt.Sprintf("%d", aid))
}
options = append(options, hrpc.Families(map[string][]string{family: columns}))
}
if _, his, err = d.get(ctx, tableInfo, key, options...); err != nil {
log.Error("hbase get() error:%+v", err)
}
return
}
// Map get all Historys from hbase.
func (d *Dao) Map(ctx context.Context, mid int64) (his map[string]*model.History, err error) {
key := hashRowKey(mid)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.ReadTimeout))
defer cancel()
if his, _, err = d.get(ctx, tableInfo, key); err != nil {
log.Error("d.get() err:%+v", err)
}
return
}
// get hbase get op.
func (d *Dao) get(ctx context.Context, table, key string, options ...func(hrpc.Call) error) (his map[string]*model.History, av map[int64]*model.History, err error) {
var result *hrpc.Result
if result, err = d.info.GetStr(ctx, table, key, options...); err != nil {
log.Error("d.info.Get error(%v)", err)
return
}
if result == nil {
return
}
expire := time.Now().Unix() - 60*60*24*90 // NOTO hbase 90days validity.
delColumn := make(map[string][]byte)
his = make(map[string]*model.History, len(result.Cells))
av = make(map[int64]*model.History)
for _, c := range result.Cells {
if c != nil && bytes.Equal(c.Family, familyB) {
if bytes.Equal(c.Qualifier, columnSWB) {
continue
}
columns := strings.Split(string(c.Qualifier), "_")
if len(columns) == 0 {
continue
}
h := &model.History{}
h.Aid, err = strconv.ParseInt(columns[0], 10, 64)
if err != nil {
log.Error("strconv.ParseInt err(%v)", err)
continue
}
if err = json.Unmarshal(c.Value, h); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", string(c.Value), err)
continue
}
if h.TP == model.TypeOffline {
h.TP = model.TypeUGC
}
if h.Unix == 0 { // live 默认时间 7月19号删除
h.Unix = int64(*(c.Timestamp)) / 1000
}
if h.Unix < expire {
delColumn[string(c.Qualifier)] = []byte{}
continue
}
his[d.column(h.Aid, h.TP)] = h
h.FillBusiness()
if h.TP < model.TypeArticle {
av[h.Aid] = h
}
}
}
if len(delColumn) > 0 {
d.delChan.Save(func() {
log.Warn("delete hbase key:%s", key)
d.delete(context.Background(), table, key, delColumn)
})
}
return
}
// Delete delete more history.
func (d *Dao) delete(ctx context.Context, table, key string, delColumn map[string][]byte) (err error) {
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
values := map[string]map[string][]byte{family: delColumn}
if _, err = d.info.Delete(ctx, table, key, values); err != nil {
log.Error("info.Delete() delColumn:%+v,error(%v)", delColumn, err)
}
return
}
// DelAids delete more history.
func (d *Dao) DelAids(ctx context.Context, mid int64, aids []int64) (err error) {
columns := make(map[string][]byte, len(aids))
for _, aid := range aids {
columns[strconv.FormatInt(aid, 10)] = []byte{}
}
columns[strconv.FormatInt(mid, 10)] = []byte{}
columns[d.column(mid, model.TypeLive)] = []byte{}
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
key := hashRowKey(mid)
values := map[string]map[string][]byte{family: columns}
if _, err = d.info.Delete(ctx, tableInfo, key, values); err != nil {
log.Error("info.Delete()len:%d,error(%v)", len(aids), err)
}
return
}
// Delete delete more history.
func (d *Dao) Delete(ctx context.Context, mid int64, his []*model.History) (err error) {
columns := make(map[string][]byte, len(his))
for _, h := range his {
columns[d.column(h.Aid, h.TP)] = []byte{}
}
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
values := map[string]map[string][]byte{family: columns}
if _, err = d.info.Delete(ctx, tableInfo, hashRowKey(mid), values); err != nil {
log.Error("info.Delete()len:%d,error(%v)", len(his), err)
}
return
}
// Clear clear history.
func (d *Dao) Clear(ctx context.Context, mid int64) (err error) {
var sw int64
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.ReadTimeout))
defer cancel()
if sw, err = d.InfoShadow(ctx, mid); err != nil {
return
}
key := hashRowKey(mid)
if _, err = d.info.Delete(ctx, tableInfo, key, nil); err != nil {
log.Error("info.Delete error(%v)", err)
}
if sw == model.ShadowOn {
d.SetInfoShadow(ctx, mid, sw)
}
return
}
// SetInfoShadow set the user switch to hbase.
func (d *Dao) SetInfoShadow(ctx context.Context, mid, value int64) (err error) {
var key = hashRowKey(mid)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.ReadTimeout))
defer cancel()
valueB := []byte(strconv.FormatInt(value, 10))
values := map[string]map[string][]byte{family: {columnSW: valueB}}
if _, err = d.info.PutStr(ctx, tableInfo, key, values); err != nil {
log.Error("info.Put error(%v)", err)
}
return
}
// InfoShadow return the user switch from hbase.
func (d *Dao) InfoShadow(ctx context.Context, mid int64) (sw int64, err error) {
var key = hashRowKey(mid)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.ReadTimeout))
defer cancel()
result, err := d.info.GetStr(ctx, tableInfo, key, hrpc.Families(map[string][]string{family: {columnSW}}))
if err != nil {
log.Error("d.info.Get error(%v)", err)
return
}
if result == nil {
err = errors.New("info sw data null")
return
}
for _, c := range result.Cells {
if c != nil && bytes.Equal(c.Family, familyB) {
if columnSW == string(c.Qualifier) {
sw, _ = strconv.ParseInt(string(c.Value), 10, 0)
return
}
}
}
return
}

View File

@@ -0,0 +1,214 @@
package history
import (
"context"
"testing"
"go-common/app/interface/main/history/model"
"github.com/smartystreets/goconvey/convey"
)
func TestHistoryhashRowKey(t *testing.T) {
convey.Convey("hashRowKey", t, func(ctx convey.C) {
var (
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := hashRowKey(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestHistorycolumn(t *testing.T) {
convey.Convey("column", t, func(ctx convey.C) {
var (
aid = int64(14771787)
typ = int8(3)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.column(aid, typ)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestHistoryAdd(t *testing.T) {
convey.Convey("Add", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
h = &model.History{Mid: 14771787, Aid: 32767458, TP: 3, Pro: 20, Unix: 1540976376, DT: 3}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.Add(c, mid, h)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryAddMap(t *testing.T) {
convey.Convey("AddMap", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
h = &model.History{Mid: 14771787, Aid: 32767458, TP: 3, Pro: 20, Unix: 1540976376, DT: 3}
hs = map[int64]*model.History{14771787: h}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddMap(c, mid, hs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryAidsMap(t *testing.T) {
convey.Convey("AidsMap", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{14771787}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
his, err := d.AidsMap(c, mid, aids)
ctx.Convey("Then err should be nil.his should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(his, convey.ShouldNotBeNil)
})
})
})
}
func TestHistoryMap(t *testing.T) {
convey.Convey("Map", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
his, err := d.Map(c, mid)
ctx.Convey("Then err should be nil.his should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(his, convey.ShouldNotBeNil)
})
})
})
}
func TestHistoryDelAids(t *testing.T) {
convey.Convey("DelAids", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{32767458}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelAids(c, mid, aids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryget(t *testing.T) {
convey.Convey("delete", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, _, err := d.get(c, tableInfo, hashRowKey(mid))
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistorydelete(t *testing.T) {
convey.Convey("delete", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
delColumn = map[string][]byte{"info:14771787": []byte{}}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.delete(c, tableInfo, hashRowKey(mid), delColumn)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryDelete(t *testing.T) {
convey.Convey("Delete", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
h = &model.History{Mid: 14771787, Aid: 32767458, TP: 3, Pro: 20, Unix: 1540976376, DT: 3}
his = []*model.History{h}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.Delete(c, mid, his)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryClear(t *testing.T) {
convey.Convey("Clear", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.Clear(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistorySetInfoShadow(t *testing.T) {
convey.Convey("SetInfoShadow", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
value = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetInfoShadow(c, mid, value)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryInfoShadow(t *testing.T) {
convey.Convey("InfoShadow", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
sw, err := d.InfoShadow(c, mid)
ctx.Convey("Then err should be nil.sw should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(sw, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,371 @@
package history
import (
"context"
"encoding/json"
"strconv"
"go-common/app/interface/main/history/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_keyIndex = "i_" // mid -> score:time member:aid
_keyHistory = "h_" // mid -> hash(aid,progress)
_keySwitch = "s_" // mid -> bit(value)
_bucket = 1000 // bit bucket
)
// keyHistory return history key.
func keyHistory(mid int64) string {
return _keyHistory + strconv.FormatInt(mid, 10)
}
// keyIndex return history index key.
func keyIndex(mid int64) string {
return _keyIndex + strconv.FormatInt(mid, 10)
}
// keySwitch return Switch key.
func keySwitch(mid int64) string {
return _keySwitch + strconv.FormatInt(mid/_bucket, 10)
}
// ExpireIndexCache expire index cache.
func (d *Dao) ExpireIndexCache(c context.Context, mid int64) (bool, error) {
return d.expireCache(c, keyIndex(mid))
}
// ExpireCache expire the user State redis.
func (d *Dao) ExpireCache(c context.Context, mid int64) (bool, error) {
return d.expireCache(c, keyHistory(mid))
}
// ExpireToView expire toview by mid.
func (d *Dao) expireCache(c context.Context, key string) (ok bool, err error) {
conn := d.redis.Get(c)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.expire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", key, err)
}
conn.Close()
return
}
// IndexCache get history from redis.
func (d *Dao) IndexCache(c context.Context, mid int64, start, end int) (aids []int64, err error) {
conn := d.redis.Get(c)
defer conn.Close()
values, err := redis.Values(conn.Do("ZREVRANGE", keyIndex(mid), start, end, "WITHSCORES"))
if err != nil {
log.Error("conn.Do(ZREVRANGE %v) error(%v)", keyIndex(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
}
// IndexCacheByTime get aids from redis where score.
func (d *Dao) IndexCacheByTime(c context.Context, mid int64, start int64) (aids []int64, err error) {
conn := d.redis.Get(c)
defer conn.Close()
values, err := redis.Values(conn.Do("ZRANGEBYSCORE", keyIndex(mid), start, "INF", "WITHSCORES"))
if err != nil {
log.Error("conn.Do(ZRANGEBYSCORE %v) error(%v)", keyIndex(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
}
// SetShadowCache set the user switch to redis.
func (d *Dao) SetShadowCache(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
}
// ShadowCache return user switch redis.
func (d *Dao) ShadowCache(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.ShadowUnknown, nil
}
log.Error("conn.Do(HGET %s) error(%v)", key, err)
}
return
}
// CacheMap return the user State from redis.
func (d *Dao) CacheMap(c context.Context, mid int64) (amap map[int64]*model.History, err error) {
var (
values map[string]string
key = keyHistory(mid)
)
conn := d.redis.Get(c)
defer conn.Close()
if values, err = redis.StringMap(conn.Do("HGETALL", key)); err != nil {
log.Error("conn.Do(HGETALL %v) error(%v)", key, err)
if err == redis.ErrNil {
return nil, nil
}
return
}
amap = make(map[int64]*model.History)
for k, v := range values {
if v == "" {
continue
}
h := &model.History{}
if err = json.Unmarshal([]byte(v), h); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", v, err)
err = nil
continue
}
if h.TP == model.TypeOffline {
h.TP = model.TypeUGC
}
h.Mid = mid
h.Aid, _ = strconv.ParseInt(k, 10, 0)
h.FillBusiness()
amap[h.Aid] = h
}
return
}
// Cache return the user State from redis.
func (d *Dao) Cache(c context.Context, mid int64, aids []int64) (amap map[int64]*model.History, miss []int64, err error) {
var (
values []string
aid int64
k int
key = keyHistory(mid)
args = []interface{}{key}
)
for _, aid = range aids {
args = append(args, aid)
}
conn := d.redis.Get(c)
defer conn.Close()
if values, err = redis.Strings(conn.Do("HMGET", args...)); err != nil {
log.Error("conn.Do(HMGET %v) error(%v)", args, err)
if err == redis.ErrNil {
return nil, nil, nil
}
return
}
amap = make(map[int64]*model.History, len(aids))
for k, aid = range aids {
if values[k] == "" {
miss = append(miss, aid)
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
}
if h.TP == model.TypeOffline {
h.TP = model.TypeUGC
}
h.Mid = mid
h.Aid = aid
h.FillBusiness()
amap[aid] = h
}
return
}
// ClearCache clear the user State redis.
func (d *Dao) ClearCache(c context.Context, mid int64) (err error) {
var (
idxKey = keyIndex(mid)
key = keyHistory(mid)
conn = d.redis.Get(c)
)
defer conn.Close()
if err = conn.Send("DEL", idxKey); err != nil {
log.Error("conn.Send(DEL %s) error(%v)", idxKey, err)
return
}
if err = conn.Send("DEL", key); err != nil {
log.Error("conn.Send(DEL %s) error(%v)", key, 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
}
// DelCache delete the history redis.
func (d *Dao) DelCache(c context.Context, mid int64, aids []int64) (err error) {
var (
key1 = keyIndex(mid)
key2 = keyHistory(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
}
// AddCache add the user State to redis.
func (d *Dao) AddCache(c context.Context, mid int64, h *model.History) (err error) {
var (
b []byte
idxKey, key = keyIndex(mid), keyHistory(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.Unix, h.Aid); err != nil {
log.Error("conn.Send(ZADD %s,%d) error(%v)", key, h.Aid, err)
return
}
if err = conn.Send("HSET", key, h.Aid, string(b)); err != nil {
log.Error("conn.Send(HSET %s,%d) error(%v)", key, h.Aid, err)
return
}
if err = conn.Send("EXPIRE", idxKey, d.expire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.expire); 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
}
// AddCacheMap add the user State to redis.
func (d *Dao) AddCacheMap(c context.Context, mid int64, hm map[int64]*model.History) (err error) {
var (
b []byte
idxKey, key = keyIndex(mid), keyHistory(mid)
)
conn := d.redis.Get(c)
defer conn.Close()
for _, h := range hm {
if b, err = json.Marshal(h); err != nil {
continue
}
if err = conn.Send("ZADD", idxKey, h.Unix, h.Aid); err != nil {
log.Error("conn.Send(ZADD %s,%d) error(%v)", key, h.Aid, err)
continue
}
if err = conn.Send("HSET", key, h.Aid, string(b)); err != nil {
log.Error("conn.Send(HSET %s,%d) error(%v)", key, h.Aid, err)
continue
}
}
if err = conn.Send("EXPIRE", idxKey, d.expire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.expire); 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 < len(hm)*2+2; 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, mid int64, limit int) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
aids, err := redis.Int64s(conn.Do("ZRANGE", keyIndex(mid), 0, -limit-1))
if err != nil {
log.Error("conn.Do(ZRANGE %v) error(%v)", keyIndex(mid), err)
return
}
if len(aids) == 0 {
return
}
return d.DelCache(c, mid, aids)
}
// PingRedis check redis connection
func (d *Dao) PingRedis(c context.Context) (err error) {
return
}

View File

@@ -0,0 +1,290 @@
package history
import (
"context"
"testing"
"go-common/app/interface/main/history/model"
"github.com/smartystreets/goconvey/convey"
)
func TestHistorykeyHistory(t *testing.T) {
convey.Convey("keyHistory", t, func(ctx convey.C) {
var (
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := keyHistory(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestHistorykeyIndex(t *testing.T) {
convey.Convey("keyIndex", t, func(ctx convey.C) {
var (
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := keyIndex(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestHistorykeySwitch(t *testing.T) {
convey.Convey("keySwitch", t, func(ctx convey.C) {
var (
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := keySwitch(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestHistoryExpireIndexCache(t *testing.T) {
convey.Convey("ExpireIndexCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.ExpireIndexCache(c, mid)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestHistoryExpireCache(t *testing.T) {
convey.Convey("ExpireCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.ExpireCache(c, mid)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestHistoryexpireCache(t *testing.T) {
convey.Convey("expireCache", t, func(ctx convey.C) {
var (
c = context.Background()
key = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
ok, err := d.expireCache(c, key)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestHistoryIndexCache(t *testing.T) {
convey.Convey("IndexCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
start = int(0)
end = int(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.IndexCache(c, mid, start, end)
ctx.Convey("Then err should be nil.aids should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryIndexCacheByTime(t *testing.T) {
convey.Convey("IndexCacheByTime", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
start = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.IndexCacheByTime(c, mid, start)
ctx.Convey("Then err should be nil.aids should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistorySetShadowCache(t *testing.T) {
convey.Convey("SetShadowCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
value = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetShadowCache(c, mid, value)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryShadowCache(t *testing.T) {
convey.Convey("ShadowCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
value, err := d.ShadowCache(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)
})
})
})
}
func TestHistoryCacheMap(t *testing.T) {
convey.Convey("CacheMap", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
amap, err := d.CacheMap(c, mid)
ctx.Convey("Then err should be nil.amap should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(amap, convey.ShouldNotBeNil)
})
})
})
}
func TestHistoryCache(t *testing.T) {
convey.Convey("Cache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{14771787}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, _, err := d.Cache(c, mid, aids)
ctx.Convey("Then err should be nil.amap,miss should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryClearCache(t *testing.T) {
convey.Convey("ClearCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.ClearCache(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryDelCache(t *testing.T) {
convey.Convey("DelCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{14771787}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelCache(c, mid, aids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryAddCache(t *testing.T) {
convey.Convey("AddCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
h = &model.History{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCache(c, mid, h)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryAddCacheMap(t *testing.T) {
convey.Convey("AddCacheMap", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
hm map[int64]*model.History
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCacheMap(c, mid, hm)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryTrimCache(t *testing.T) {
convey.Convey("TrimCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
limit = int(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.TrimCache(c, mid, limit)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestHistoryPingRedis(t *testing.T) {
convey.Convey("PingRedis", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.PingRedis(c)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,57 @@
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",
"hbase_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"hbase.go",
"redis.go",
],
importpath = "go-common/app/interface/main/history/dao/toview",
tags = ["automanaged"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/hbase.v2:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/tsuna/gohbase/hrpc: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,41 @@
package toview
import (
"context"
"time"
"go-common/app/interface/main/history/conf"
"go-common/library/cache/redis"
"go-common/library/database/hbase.v2"
)
// Dao dao.
type Dao struct {
conf *conf.Config
info *hbase.Client
redis *redis.Pool
expire int
}
// New new history dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
conf: c,
info: hbase.NewClient(c.Info.Config),
redis: redis.NewPool(c.Toview.Config),
expire: int(time.Duration(c.Toview.Expire) / time.Second),
}
return
}
// Ping check connection success.
func (d *Dao) Ping(c context.Context) (err error) {
return d.PingRedis(c)
}
// Close close the redis and kafka resource.
func (d *Dao) Close() {
if d.redis != nil {
d.redis.Close()
}
}

View File

@@ -0,0 +1,33 @@
package toview
import (
"flag"
"os"
"testing"
"go-common/app/interface/main/history/conf"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.history")
flag.Set("conf_token", "01423f5b752144440e01cb8ff4432e96")
flag.Set("tree_id", "2298")
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")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,191 @@
package toview
import (
"bytes"
"context"
"crypto/md5"
"encoding/binary"
"fmt"
"strconv"
"time"
"go-common/app/interface/main/history/model"
"go-common/library/log"
"github.com/tsuna/gohbase/hrpc"
)
var (
tableInfo = "ugc:history_to_view"
familyAid = "aid"
familyAidB = []byte(familyAid)
)
// hashRowKey create rowkey(md5(mid)[:2]+mid) for histroy by mid .
func hashRowKey(mid int64) string {
var bs = make([]byte, 8)
binary.LittleEndian.PutUint64(bs, uint64(mid))
rk := md5.Sum(bs)
return fmt.Sprintf("%x%d", rk[:2], mid)
}
// Add add one toview.
func (d *Dao) Add(ctx context.Context, mid, aid, now int64) (err error) {
var (
timeB = make([]byte, 8)
key = hashRowKey(mid)
column = strconv.FormatInt(aid, 10)
)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
binary.BigEndian.PutUint64(timeB, uint64(now))
values := map[string]map[string][]byte{familyAid: map[string][]byte{column: timeB}}
if _, err = d.info.PutStr(ctx, tableInfo, key, values); err != nil {
log.Error("toview info.Put error(%v)", err)
}
return
}
// Adds add some toview.
func (d *Dao) Adds(ctx context.Context, mid int64, aids []int64, now int64) (err error) {
var (
timeB = make([]byte, 8)
key = hashRowKey(mid)
)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
binary.BigEndian.PutUint64(timeB, uint64(now))
aidValues := make(map[string][]byte, len(aids))
for _, aid := range aids {
aidValues[strconv.FormatInt(aid, 10)] = timeB
}
values := map[string]map[string][]byte{familyAid: aidValues}
if _, err = d.info.PutStr(ctx, tableInfo, key, values); err != nil {
log.Error("toview info.Put error(%v)", err)
}
return
}
// AddMap add some toview.
func (d *Dao) AddMap(ctx context.Context, mid int64, views map[int64]*model.ToView) (err error) {
var (
timeB = make([]byte, 8)
key = hashRowKey(mid)
)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
aidValues := make(map[string][]byte, len(views))
for _, v := range views {
binary.BigEndian.PutUint64(timeB, uint64(v.Unix))
aidValues[strconv.FormatInt(v.Aid, 10)] = timeB
}
values := map[string]map[string][]byte{familyAid: aidValues}
if _, err = d.info.PutStr(ctx, tableInfo, key, values); err != nil {
log.Error("toview info.Put error(%v)", err)
}
return
}
// ListInfo get all ToViews from hbase.
func (d *Dao) ListInfo(ctx context.Context, mid int64, aids []int64) (res []*model.ToView, err error) {
var (
result *hrpc.Result
key = hashRowKey(mid)
)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.ReadTimeout))
defer cancel()
var options []func(hrpc.Call) error
if len(aids) != 0 {
colunms := make([]string, 0, len(aids))
for _, aid := range aids {
colunms = append(colunms, fmt.Sprintf("%d", aid))
}
options = append(options, hrpc.Families(map[string][]string{familyAid: colunms}))
}
result, err = d.info.GetStr(ctx, tableInfo, key, options...)
if err != nil && result == nil {
log.Error("d.info.Get error(%v)", err)
return
}
res = make([]*model.ToView, 0)
for _, c := range result.Cells {
if c != nil && len(c.Value) == 8 && bytes.Equal(c.Family, familyAidB) {
aid, err := strconv.ParseInt(string(c.Qualifier), 10, 64)
if err != nil {
log.Error("strconv.ParseInt error(%v)", err)
continue
}
t := &model.ToView{Aid: aid}
t.Unix = int64(binary.BigEndian.Uint64(c.Value))
res = append(res, t)
}
}
return
}
// MapInfo get all ToViews from hbase.
func (d *Dao) MapInfo(ctx context.Context, mid int64, aids []int64) (res map[int64]*model.ToView, err error) {
var (
result *hrpc.Result
key = hashRowKey(mid)
)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.ReadTimeout))
defer cancel()
var options []func(hrpc.Call) error
if len(aids) != 0 {
colunms := make([]string, 0, len(aids))
for _, aid := range aids {
colunms = append(colunms, fmt.Sprintf("%d", aid))
}
options = append(options, hrpc.Families(map[string][]string{familyAid: colunms}))
}
result, err = d.info.GetStr(ctx, tableInfo, key, options...)
if err != nil {
log.Error("d.info.Get error(%v)", err)
return
}
res = make(map[int64]*model.ToView, len(aids))
if result == nil {
return
}
for _, c := range result.Cells {
if c != nil && len(c.Value) == 8 && bytes.Equal(c.Family, familyAidB) {
aid, err := strconv.ParseInt(string(c.Qualifier), 10, 64)
if err != nil {
log.Error("strconv.ParseInt error(%v)", err)
continue
}
t := &model.ToView{Aid: aid}
t.Unix = int64(binary.BigEndian.Uint64(c.Value))
res[aid] = t
}
}
return
}
// Del delete more toview.
func (d *Dao) Del(ctx context.Context, mid int64, aids []int64) (err error) {
key := hashRowKey(mid)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
columns := make(map[string][]byte, len(aids))
for _, aid := range aids {
columns[strconv.FormatInt(aid, 10)] = []byte{}
}
values := map[string]map[string][]byte{familyAid: columns}
if _, err = d.info.Delete(ctx, tableInfo, key, values); err != nil {
log.Error("toview info.Delete error(%v)", err)
}
return
}
// Clear clear ToView.
func (d *Dao) Clear(ctx context.Context, mid int64) (err error) {
key := hashRowKey(mid)
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.conf.Info.WriteTimeout))
defer cancel()
if _, err = d.info.Delete(ctx, tableInfo, key, nil); err != nil {
log.Error("toview info.Delete error(%v)", err)
}
return
}

View File

@@ -0,0 +1,140 @@
package toview
import (
"context"
"testing"
"time"
"go-common/app/interface/main/history/model"
"github.com/smartystreets/goconvey/convey"
)
func TestToviewhashRowKey(t *testing.T) {
convey.Convey("hashRowKey", t, func(ctx convey.C) {
var (
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := hashRowKey(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestToviewAdd(t *testing.T) {
convey.Convey("Add", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aid = int64(141787)
now = time.Now().Unix()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.Add(c, mid, aid, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewAdds(t *testing.T) {
convey.Convey("Adds", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{147717, 147787}
now = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.Adds(c, mid, aids, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewAddMap(t *testing.T) {
convey.Convey("AddMap", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
views map[int64]*model.ToView
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddMap(c, mid, views)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewListInfo(t *testing.T) {
convey.Convey("ListInfo", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{147717, 147787}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.ListInfo(c, mid, aids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestToviewMapInfo(t *testing.T) {
convey.Convey("MapInfo", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{147717, 147787}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.MapInfo(c, mid, aids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestToviewDel(t *testing.T) {
convey.Convey("Del", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{147717, 147787}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.Del(c, mid, aids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewClear(t *testing.T) {
convey.Convey("Clear", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.Clear(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,160 @@
package toview
import (
"context"
"strconv"
"go-common/app/interface/main/history/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const _key = "v_" // mid -> score:time member:aid
// keyToView return key string
func key(mid int64) string {
return _key + strconv.FormatInt(mid, 10)
}
// Expire expire toview by mid.
func (d *Dao) Expire(c context.Context, mid int64) (ok bool, err error) {
conn := d.redis.Get(c)
if ok, err = redis.Bool(conn.Do("EXPIRE", key(mid), d.expire)); err != nil {
log.Error("conn.Do(EXPIRE, %s) error(%v)", key(mid), err)
}
conn.Close()
return
}
// Cache return the user all toview from redis.
func (d *Dao) Cache(c context.Context, mid int64, start, end int) (res []*model.ToView, err error) {
conn := d.redis.Get(c)
defer conn.Close()
values, err := redis.Values(conn.Do("ZREVRANGE", key(mid), start, end, "WITHSCORES"))
if err != nil {
log.Error("dao.Do(ZREVRANGE %v) error(%v)", key(mid), err)
return
}
if len(values) == 0 {
return
}
res = make([]*model.ToView, 0, len(values)/2)
for len(values) > 0 {
t := &model.ToView{}
if values, err = redis.Scan(values, &t.Aid, &t.Unix); err != nil {
log.Error("redis.Scan(%v) error(%v)", values, err)
return
}
res = append(res, t)
}
return
}
// CacheMap return the user all toview map from redis.
func (d *Dao) CacheMap(c context.Context, mid int64) (res map[int64]*model.ToView, err error) {
conn := d.redis.Get(c)
defer conn.Close()
values, err := redis.Values(conn.Do("ZREVRANGE", key(mid), 0, -1, "WITHSCORES"))
if err != nil {
log.Error("dao.Do(ZREVRANGE %v) error(%v)", key(mid), err)
return
}
if len(values) == 0 {
return
}
res = make(map[int64]*model.ToView, len(values)/2)
for len(values) > 0 {
t := &model.ToView{}
if values, err = redis.Scan(values, &t.Aid, &t.Unix); err != nil {
log.Error("redis.Scan(%v) error(%v)", values, err)
return
}
res[t.Aid] = t
}
return
}
// CntCache return the user toview count from redis.
func (d *Dao) CntCache(c context.Context, mid int64) (count int, err error) {
conn := d.redis.Get(c)
if count, err = redis.Int(conn.Do("ZCARD", key(mid))); err != nil {
log.Error("dao.Do(ZCARD,%s) err(%v)", key(mid), err)
}
conn.Close()
return
}
// ClearCache delete the user toview redis.
func (d *Dao) ClearCache(c context.Context, mid int64) (err error) {
conn := d.redis.Get(c)
if _, err = conn.Do("DEL", key(mid)); err != nil {
log.Error("conn.Do(DEL %s) error(%v)", key(mid), err)
}
conn.Close()
return
}
// DelCaches delete the user toview redis.
func (d *Dao) DelCaches(c context.Context, mid int64, aids []int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
for _, aid := range aids {
if err = conn.Send("ZREM", key(mid), aid); err != nil {
log.Error("conn.Send(ZREM %s,%d) error(%v)", key(mid), aid, err)
return
}
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < len(aids); i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// AddCache add user toview to redis.
func (d *Dao) AddCache(c context.Context, mid, aid, now int64) error {
return d.addCache(c, key(mid), []*model.ToView{&model.ToView{Aid: aid, Unix: now}})
}
// AddCacheList add user toview to redis.
func (d *Dao) AddCacheList(c context.Context, mid int64, views []*model.ToView) error {
return d.addCache(c, key(mid), views)
}
// addCache add user toview to redis.
func (d *Dao) addCache(c context.Context, key string, views []*model.ToView) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
for _, v := range views {
if err = conn.Send("ZADD", key, v.Unix, v.Aid); err != nil {
log.Error("conn.Send(ZREM %s,%d) error(%v)", key, v.Aid, err)
return
}
}
if err = conn.Send("EXPIRE", key, d.expire); 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 < len(views)+1; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// PingRedis check redis connection
func (d *Dao) PingRedis(c context.Context) (err error) {
return
}

View File

@@ -0,0 +1,184 @@
package toview
import (
"context"
"testing"
"time"
"go-common/app/interface/main/history/model"
"github.com/smartystreets/goconvey/convey"
)
func TestToviewkey(t *testing.T) {
convey.Convey("key", t, func(ctx convey.C) {
var (
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := key(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestToviewExpire(t *testing.T) {
convey.Convey("Expire", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
ok, err := d.Expire(c, mid)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestToviewCache(t *testing.T) {
convey.Convey("Cache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
start = int(1)
end = int(2)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.Cache(c, mid, start, end)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewCacheMap(t *testing.T) {
convey.Convey("CacheMap", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.CacheMap(c, mid)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestToviewCntCache(t *testing.T) {
convey.Convey("CntCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
count, err := d.CntCache(c, mid)
ctx.Convey("Then err should be nil.count should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(count, convey.ShouldNotBeNil)
})
})
})
}
func TestToviewClearCache(t *testing.T) {
convey.Convey("ClearCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.ClearCache(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewDelCaches(t *testing.T) {
convey.Convey("DelCaches", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aids = []int64{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelCaches(c, mid, aids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewAddCache(t *testing.T) {
convey.Convey("AddCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
aid = int64(14771787)
now = time.Now().Unix()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCache(c, mid, aid, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewAddCacheList(t *testing.T) {
convey.Convey("AddCacheList", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(14771787)
views = []*model.ToView{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCacheList(c, mid, views)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewaddCache(t *testing.T) {
convey.Convey("addCache", t, func(ctx convey.C) {
var (
c = context.Background()
key = ""
views = []*model.ToView{{Aid: 1477, Unix: 11}}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.addCache(c, key, views)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestToviewPingRedis(t *testing.T) {
convey.Convey("PingRedis", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.PingRedis(c)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,46 @@
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",
"param.go",
"toview.go",
],
importpath = "go-common/app/interface/main/history/http",
tags = ["automanaged"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/model:go_default_library",
"//app/interface/main/history/service:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/log/anticheat:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/auth:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/metadata: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,560 @@
package http
import (
"encoding/json"
"strconv"
"strings"
"time"
"go-common/app/interface/main/history/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
// history return the user history for mobile app.
func history(c *bm.Context) {
var (
err error
mid int64
v = new(Histroy)
)
if err = c.Bind(v); err != nil {
return
}
if tp, ok := business(c); !ok {
return
} else if tp > 0 {
v.TP = tp
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if v.Pn < 1 {
v.Pn = 1
}
if v.Ps > cnf.History.Max || v.Ps <= 0 {
v.Ps = cnf.History.Max
}
list, err := hisSvc.Videos(c, mid, v.Pn, v.Ps, v.TP)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(list, nil)
}
// aids return the user histories.
func aids(c *bm.Context) {
var (
mid int64
)
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
list, err := hisSvc.AVHistories(c, mid)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(list, nil)
}
func managerHistory(c *bm.Context) {
var (
err error
v = new(struct {
Mid int64 `form:"mid" validate:"required,gt=0"`
OnlyAV bool `form:"only_av"`
})
)
if err = c.Bind(v); err != nil {
return
}
history, err := hisSvc.ManagerHistory(c, v.OnlyAV, v.Mid)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(history, nil)
}
// clearHistory clear the user histories.
func clearHistory(c *bm.Context) {
var (
err error
mid int64
v = new(struct {
TP int8 `form:"type"`
})
)
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if tp, ok := business(c); !ok {
return
} else if tp > 0 {
v.TP = tp
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
var tps []int8
if v.TP > 0 {
tps = append(tps, v.TP)
}
c.JSON(nil, hisSvc.ClearHistory(c, mid, tps))
}
// delHistory delete the user history by aid.
func delHistory(c *bm.Context) {
var (
err error
mid int64
v = new(struct {
TP int8 `form:"type"`
Aids []int64 `form:"aid,split"`
})
)
if err = c.Bind(v); err != nil {
return
}
if tp, ok := business(c); !ok {
return
} else if tp > 0 {
v.TP = tp
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, hisSvc.DelHistory(c, mid, v.Aids, v.TP))
}
func delete(c *bm.Context) {
var (
err error
mid int64
v = new(struct {
Hid []string `form:"hid,split"`
Bid []string `form:"bid,split"`
})
)
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
var his []*model.History
if len(v.Bid) == 0 {
for _, hid := range v.Hid {
hs := strings.Split(hid, "_")
if len(hs) == 0 {
continue
}
aid, _ := strconv.ParseInt(hs[0], 10, 0)
if aid == 0 {
continue
}
var tp int64
if len(hs) == 2 {
tp, _ = strconv.ParseInt(hs[1], 10, 0)
}
his = append(his, &model.History{
Aid: aid,
TP: int8(tp),
})
}
}
for _, bid := range v.Bid {
bs := strings.Split(bid, "_")
if len(bs) != 2 {
continue
}
aid, _ := strconv.ParseInt(bs[1], 10, 0)
if aid == 0 {
continue
}
tp, err := model.CheckBusiness(bs[0])
if err != nil {
continue
}
his = append(his, &model.History{
Aid: aid,
TP: int8(tp),
})
}
if len(his) == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, hisSvc.Delete(c, mid, his))
}
// addHistory add history into user redis set.
func addHistory(c *bm.Context) {
var (
err error
mid int64
h *model.History
now = time.Now().Unix()
v = new(AddHistory)
)
// sid cid tp, dt
// dt:devece type , sid :season_id,type:video type
// aid :aid
// cid:cid,epid
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if v.TP == model.TypeBangumi || v.TP == model.TypeMovie {
if v.Sid <= 0 || v.Epid <= 0 {
if v.Aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
}
if v.Aid <= 0 || (v.TP < model.TypeArticle && v.Cid <= 0) {
c.JSON(nil, ecode.RequestErr)
return
}
if v.DT <= 0 {
if v.Platform == model.PlatformIOS {
v.DT = model.DeviceIphone
if v.Device == model.DevicePad {
v.DT = model.DeviceIpad
}
} else if v.Platform == model.PlatformAndroid {
v.DT = model.DeviceAndroid
}
}
h = &model.History{
Aid: v.Aid,
Unix: now,
Sid: v.Sid,
Epid: v.Epid,
Cid: v.Cid,
TP: v.TP,
STP: v.SubTP,
DT: v.DT,
}
h.ConvertType()
c.JSON(nil, hisSvc.AddHistory(c, mid, 0, h))
}
// report report view progress.
func report(c *bm.Context) {
var (
err error
mid int64
h *model.History
now = time.Now().Unix()
v = new(HistoryReport)
)
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if tp, ok := business(c); !ok {
return
} else if tp > 0 {
v.Type = tp
}
if v.Progress < 0 {
v.Progress = model.ProComplete
}
if v.Aid <= 0 || (v.Type < model.TypeArticle && v.Cid <= 0) {
c.JSON(nil, ecode.RequestErr)
return
}
if v.SubType <= 0 {
v.SubType = v.SubTP
}
if v.Type == model.TypeBangumi || v.Type == model.TypeMovie {
if v.Sid <= 0 || v.Epid <= 0 {
v.Type = model.TypeUGC
} else {
v.Type = model.TypePGC
}
}
if v.DT <= 0 {
if v.Platform == model.PlatformIOS {
v.DT = model.DeviceIphone
if v.Device == model.DevicePad {
v.DT = model.DeviceIpad
}
} else if v.Platform == model.PlatformAndroid {
v.DT = model.DeviceAndroid
if v.MobileApp == model.MobileAppAndroidTV {
v.DT = model.DeviceAndroidTV
}
}
}
h = &model.History{
Aid: v.Aid,
Unix: now,
Sid: v.Sid,
Epid: v.Epid,
Cid: v.Cid,
Pro: v.Progress,
TP: v.Type,
STP: v.SubType,
DT: v.DT,
}
c.JSON(nil, hisSvc.AddHistory(c, mid, v.Realtime, h))
}
// report report view progress.
func innerReport(c *bm.Context) {
var (
err error
h *model.History
v = new(HistoryReport)
)
if err = c.Bind(v); err != nil {
return
}
if tp, ok := business(c); !ok {
return
} else if tp > 0 {
v.Type = tp
}
if v.Mid == 0 && v.Aid == 0 && v.Type == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if v.Type == model.TypeComic && v.Aid == 0 {
v.Aid = v.Sid
}
if v.PlayTime == 0 {
v.PlayTime = time.Now().Unix()
}
if v.Progress < 0 {
v.Progress = model.ProComplete
}
h = &model.History{
Aid: v.Aid,
Unix: v.PlayTime,
Sid: v.Sid,
Epid: v.Epid,
Cid: v.Cid,
Pro: v.Progress,
TP: v.Type,
STP: v.SubType,
DT: v.DT,
}
c.JSON(nil, hisSvc.AddHistory(c, v.Mid, v.Realtime, h))
}
// reports
func reports(c *bm.Context) {
var (
err error
mid int64
hs = make([]*model.History, 0)
v = new(struct {
Type int8 `form:"type"`
Data string `form:"data"`
})
)
if err = c.Bind(v); err != nil {
return
}
if tp, ok := business(c); !ok {
return
} else if tp > 0 {
v.Type = tp
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if err = json.Unmarshal([]byte(v.Data), &hs); err != nil {
log.Error("json.Unmarshal(%s),err:%v.", v.Data, err)
c.JSON(nil, ecode.RequestErr)
return
}
if err = hisSvc.AddHistories(c, mid, v.Type, metadata.String(c, metadata.RemoteIP), hs); err != nil {
c.JSON(nil, ecode.ServerErr)
return
}
c.JSON(nil, nil)
}
// shadow return the user shadow status.
func shadow(c *bm.Context) {
var (
err error
mid int64
)
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
status, err := hisSvc.Shadow(c, mid)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(status == model.ShadowOn, nil)
}
// setShadow the user shadow status.
func setShadow(c *bm.Context) {
var (
err error
mid int64
shadow = model.ShadowOff
v = new(struct {
Switch bool `form:"switch"`
})
)
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if v.Switch {
shadow = model.ShadowOn
}
c.JSON(nil, hisSvc.SetShadow(c, mid, shadow))
}
// flush flush users hisotry.
func flush(c *bm.Context) {
var (
err error
v = new(struct {
Mids []int64 `form:"mids,split" validate:"required,min=1,dive,gt=0"`
STime int64 `form:"time"`
})
)
if err = c.Bind(v); err != nil {
return
}
c.JSON(nil, hisSvc.FlushHistory(c, v.Mids, v.STime))
}
// position report report view progress.
func position(c *bm.Context) {
var (
err error
v = new(struct {
Mid int64 `form:"mid"`
Aid int64 `form:"aid"`
TP int8 `form:"type"`
})
)
if err = c.Bind(v); err != nil {
return
}
if tp, ok := business(c); !ok {
return
} else if tp > 0 {
v.TP = tp
}
if v.Mid == 0 && v.Aid == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(hisSvc.Position(c, v.Mid, v.Aid, v.TP))
}
// position report report view progress.
func resource(c *bm.Context) {
var (
err error
v = new(struct {
Mid int64 `form:"mid"`
TP int8 `form:"type"`
Pn int `form:"pn"`
Ps int `form:"ps"`
})
)
if err = c.Bind(v); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if tp, ok := business(c); !ok {
return
} else if tp > 0 {
v.TP = tp
}
if v.Mid == 0 || v.TP == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(hisSvc.Histories(c, v.Mid, v.TP, v.Pn, v.Ps))
}
// position report report view progress.
func resources(c *bm.Context) {
var (
err error
v = new(struct {
Mid int64 `form:"mid"`
TP int8 `form:"type"`
Aids []int64 `form:"aids,split"`
})
)
if err = c.Bind(v); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if tp, ok := business(c); !ok {
return
} else if tp > 0 {
v.TP = tp
}
if v.Mid == 0 || v.TP == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if len(v.Aids) == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(hisSvc.HistoryType(c, v.Mid, v.TP, v.Aids, metadata.String(c, metadata.RemoteIP)))
}

View File

@@ -0,0 +1,114 @@
package http
import (
"net/http"
"go-common/app/interface/main/history/conf"
"go-common/app/interface/main/history/model"
"go-common/app/interface/main/history/service"
"go-common/library/log"
"go-common/library/log/anticheat"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/auth"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
cnf *conf.Config
hisSvc *service.Service
collector *anticheat.AntiCheat
authSvc *auth.Auth
verifySvc *verify.Verify
)
// Init init http
func Init(c *conf.Config, s *service.Service) {
cnf = c
authSvc = auth.New(c.Auth)
verifySvc = verify.New(c.Verify)
if c.Collector != nil {
collector = anticheat.New(c.Collector)
}
hisSvc = s
engine := bm.DefaultServer(c.BM)
outerRouter(engine)
interRouter(engine)
if err := engine.Start(); err != nil {
log.Error("xhttp.Serve error(%v)", err)
panic(err)
}
}
// outerRouter init outer router api path.
func outerRouter(e *bm.Engine) {
e.GET("/monitor/ping", ping)
group := e.Group("/x/v2/history", authSvc.User)
{
group.GET("", history)
group.POST("/add", addHistory)
group.POST("/del", delHistory)
group.POST("/delete", delete)
group.POST("/clear", clearHistory)
group.POST("/report", report) //just mobile use it.
group.POST("/reports", reports) //just mobile use it.
}
toViewGroup := group.Group("/toview", authSvc.User)
{
toViewGroup.GET("", toView)
toViewGroup.GET("/web", webToView)
toViewGroup.GET("/remaining", remainingToView)
toViewGroup.POST("/add", addToView)
toViewGroup.POST("/adds", addMultiToView)
toViewGroup.POST("/del", delToView)
toViewGroup.POST("/clear", clearToView)
}
shadowGroup := group.Group("/shadow", authSvc.User)
{
shadowGroup.GET("", shadow)
shadowGroup.POST("/set", setShadow)
}
}
// interRouter init internal router api path using the outer router port
func interRouter(e *bm.Engine) {
group := e.Group("/x/internal/v2/history")
{
group.GET("/manager", verifySvc.Verify, managerHistory)
group.GET("/aids", verifySvc.VerifyUser, aids) // bangumi use it.
group.POST("/delete", verifySvc.VerifyUser, delete)
group.POST("/clear", verifySvc.VerifyUser, clearHistory)
group.POST("/flush", verifySvc.Verify, flush) // history-job use it.
group.POST("/report", verifySvc.VerifyUser, innerReport) //just mobile use it.
group.GET("/position", verifySvc.VerifyUser, position) //just mobile use it.
group.GET("/resource", verifySvc.VerifyUser, resource) //just mobile use it.
group.GET("/resource/type", verifySvc.VerifyUser, resources) //just mobile use it.
}
toviewGroup := group.Group("/toview")
{
toviewGroup.GET("/manager", verifySvc.Verify, managerToView)
toviewGroup.POST("/adds", verifySvc.VerifyUser, addMultiToView) // space use it.
}
}
// ping check server ok.
func ping(c *bm.Context) {
if err := hisSvc.Ping(c); err != nil {
log.Error("history service ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func business(c *bm.Context) (tp int8, ok bool) {
ok = true
business := c.Request.Form.Get("business")
if business == "" {
return
}
tp, err := model.CheckBusiness(business)
if err != nil {
ok = false
c.JSON(nil, err)
c.Abort()
}
return
}

View File

@@ -0,0 +1,47 @@
package http
// Histroy Histroy.
type Histroy struct {
Pn int `form:"pn"`
Ps int `form:"ps"`
TP int8 `form:"type"`
}
// AddHistory AddHistory.
type AddHistory struct {
Aid int64 `form:"aid" validate:"required,gt=0"`
Cid int64 `form:"cid"`
Epid int64 `form:"epid"`
TP int8 `form:"type"`
SubTP int8 `form:"sub_type"`
DT int8 `form:"dt"`
Sid int64 `form:"sid"`
Platform string `form:"platform"`
Device string `form:"device"`
}
// HistoryReport HistoryReport.
type HistoryReport struct {
Mid int64 `form:"mid"`
Aid int64 `form:"aid"`
Type int8 `form:"type"`
Cid int64 `form:"cid"`
Epid int64 `form:"epid"`
Sid int64 `form:"sid"`
SubTP int8 `form:"subtype"`
SubType int8 `form:"sub_type"`
DT int8 `form:"dt"`
Realtime int64 `form:"realtime"`
Source int64 `form:"source"`
Progress int64 `form:"progress"`
Platform string `form:"platform"`
Device string `form:"device"`
PlayTime int64 `form:"play_time"`
MobileApp string `form:"mobi_app"`
}
// Page Page.
type Page struct {
Pn int `form:"pn"`
Ps int `form:"ps"`
}

View File

@@ -0,0 +1,213 @@
package http
import (
"strconv"
"go-common/library/ecode"
"go-common/library/log/infoc"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
"go-common/library/xstr"
)
// toView return the user toview list
func toView(c *bm.Context) {
var (
err error
mid int64
v = new(Page)
)
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if v.Pn < 1 {
v.Pn = 1
}
if v.Ps > cnf.History.Max || v.Ps <= 0 {
v.Ps = cnf.History.Max
}
list, count, err := hisSvc.ToView(c, mid, v.Pn, v.Ps, metadata.String(c, metadata.RemoteIP))
if err != nil {
c.JSON(nil, err)
return
}
data := map[string]interface{}{
"list": list,
"count": count,
}
c.JSON(data, nil)
}
// toView return the user toview list
func webToView(c *bm.Context) {
var (
err error
mid int64
v = new(Page)
)
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if v.Pn < 1 {
v.Pn = 1
}
if v.Ps > cnf.History.Max || v.Ps <= 0 {
v.Ps = cnf.History.Max
}
list, count, err := hisSvc.WebToView(c, mid, v.Pn, v.Ps, metadata.String(c, metadata.RemoteIP))
if err != nil {
c.JSON(nil, err)
return
}
data := map[string]interface{}{
"list": list,
"count": count,
}
c.JSON(data, nil)
}
// delToView delete the user video of toview.
func delToView(c *bm.Context) {
var (
err error
mid int64
v = new(struct {
Aids []int64 `form:"aid,split"`
Viewed bool `form:"viewed"`
})
)
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, hisSvc.DelToView(c, mid, v.Aids, v.Viewed, metadata.String(c, metadata.RemoteIP)))
}
// addToView add video to the user toview list.
func addToView(c *bm.Context) {
var (
err error
mid int64
v = new(struct {
Aid int64 `form:"aid" validate:"required,gt=0"`
})
)
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if collector != nil {
collector.InfoAntiCheat2(c, "", strconv.FormatInt(v.Aid, 10), strconv.FormatInt(mid, 10), strconv.FormatInt(v.Aid, 10), infoc.ItemTypeAv, infoc.ActionToView, strconv.FormatInt(v.Aid, 10))
}
c.JSON(nil, hisSvc.AddToView(c, mid, v.Aid, metadata.String(c, metadata.RemoteIP)))
}
// addToViews add videos to the user toview list.
func addMultiToView(c *bm.Context) {
var (
err error
mid int64
v = new(struct {
Aids []int64 `form:"aids,split"`
})
)
if err = c.Bind(v); err != nil {
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if collector != nil {
collector.InfoAntiCheat2(c, "", xstr.JoinInts(v.Aids), strconv.FormatInt(mid, 10), xstr.JoinInts(v.Aids), infoc.ItemTypeAv, infoc.ActionToView, xstr.JoinInts(v.Aids))
}
c.JSON(nil, hisSvc.AddMultiToView(c, mid, v.Aids, metadata.String(c, metadata.RemoteIP)))
}
func managerToView(c *bm.Context) {
var (
err error
v = new(struct {
Mid int64 `form:"mid" validate:"required,gt=0"`
})
)
if err = c.Bind(v); err != nil {
return
}
list, err := hisSvc.ManagerToView(c, v.Mid, metadata.String(c, metadata.RemoteIP))
if err != nil {
c.JSON(nil, err)
return
}
data := map[string]interface{}{
"list": list,
}
c.JSON(data, nil)
}
// remainingToView get the quantity of user's remaining toview.
func remainingToView(c *bm.Context) {
var (
mid int64
)
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
remaining, err := hisSvc.RemainingToView(c, mid, metadata.String(c, metadata.RemoteIP))
if err != nil {
c.JSON(nil, err)
return
}
data := map[string]interface{}{
"count": remaining,
}
c.JSON(data, nil)
}
// clearToView clear the user toview list.
func clearToView(c *bm.Context) {
var (
mid int64
)
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, hisSvc.ClearToView(c, mid))
}

View File

@@ -0,0 +1,36 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"history.go",
"rpc.go",
"toview.go",
],
importpath = "go-common/app/interface/main/history/model",
tags = ["automanaged"],
deps = [
"//app/service/main/archive/model/archive:go_default_library",
"//app/service/main/history/model:go_default_library",
"//library/ecode: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,187 @@
package model
import (
"go-common/app/service/main/archive/model/archive"
"go-common/library/ecode"
)
// const .
const (
// TypeUnknown unkown
TypeUnknown = int8(-1)
// TypeOffline offline
TypeOffline = int8(0)
// TypeBangumi bangumi
TypeBangumi = int8(1)
// TypeMovie movie
TypeMovie = int8(2)
// TypeUGC UGC
TypeUGC = int8(3)
// TypePGC PGC
TypePGC = int8(4)
// TypeArticle Article
TypeArticle = int8(5)
// TypeLive Live
TypeLive = int8(6)
// TypeCorpus corpus
TypeCorpus = int8(7)
// TypeComic comic
TypeComic = int8(8)
// SubTypeOffline archive subtype
SubTypeOffline = int8(1)
// SubTypeBangumi bangumi
SubTypeBangumi = int8(1)
// SubTypeFilm film
SubTypeFilm = int8(2)
// SubTypeDoc documentary
SubTypeDoc = int8(3)
// SubTypeNation nation
SubTypeNation = int8(4)
// SubTypeTV TV
SubTypeTV = int8(5)
// DeviceUnknown unknown
DeviceUnknown = int8(0)
// DeviceIphone iphoneTV
DeviceIphone = int8(1)
// DevicePC PC
DevicePC = int8(2)
// DeviceAndroid android
DeviceAndroid = int8(3)
// DeviceAndroidTV android TV
DeviceAndroidTV = int8(33)
// DeviceIpad ipad
DeviceIpad = int8(4)
// DeviceWP8 WP8
DeviceWP8 = int8(5)
// DeviceUWP UWP
DeviceUWP = int8(6)
// ShadowUnknown unknown
ShadowUnknown = int64(-1)
// ShadowOff off
ShadowOff = int64(0)
// ShadowOn on
ShadowOn = int64(1)
// ProComplete progress complete
ProComplete = int64(-1)
// PlatformAndroid platform android.
PlatformAndroid string = "android"
// PlatformIOS platform ios.
PlatformIOS string = "ios"
// DevicePad device pad.
DevicePad string = "pad"
// MobileAppAndroidTV mobile app android tv.
MobileAppAndroidTV string = "android_tv_yst"
HistoryLog = 171
HistoryClear = "history_clear"
HistoryClearTyp = "history_clear_%s"
ToviewClear = "toview_clear"
)
var businesses = map[string]int8{
"pgc": TypePGC,
"article": TypeArticle,
"archive": TypeUGC,
"live": TypeLive,
"article-list": TypeCorpus,
"comic": TypeComic,
}
var businessIDs = map[int8]string{
TypeOffline: "archive",
TypeMovie: "pgc",
TypeBangumi: "pgc",
TypePGC: "pgc",
TypeArticle: "article",
TypeUGC: "archive",
TypeLive: "live",
TypeCorpus: "article-list",
TypeComic: "comic",
}
// BusinessByTP .
func BusinessByTP(b int8) string {
return businessIDs[b]
}
// CheckBusiness .
func CheckBusiness(bs string) (tp int8, err error) {
if bs == "" {
return
}
tp, ok := businesses[bs]
if !ok {
err = ecode.AppDenied
}
return
}
// MustCheckBusiness .
func MustCheckBusiness(bs string) (tp int8, err error) {
if bs == "" {
err = ecode.RequestErr
return
}
tp, ok := businesses[bs]
if !ok {
err = ecode.AppDenied
}
return
}
// Merge report merge in history.
type Merge struct {
Mid int64 `json:"mid"`
Now int64 `json:"now"`
}
// Video video history.
type Video struct {
*archive.Archive3
Favorite bool `json:"favorite"` // video favorite
TP int8 `json:"type"` // video type
STP int8 `json:"sub_type"` // video type
DT int8 `json:"device"` // device type
Page *archive.Page3 `json:"page,omitempty"`
Count int `json:"count,omitempty"`
BangumiInfo *Bangumi `json:"bangumi,omitempty"`
Progress int64 `json:"progress"`
ViewAt int64 `json:"view_at"`
}
// Season season.
type Season struct {
ID int64 `json:"season_id"`
Title string `json:"title"`
SeasonStatus int `json:"season_status"`
IsFinish int `json:"is_finish"`
TotalCount int32 `json:"total_count"`
NewestEpid int64 `json:"newest_ep_id"`
NewestEpIndex string `json:"newest_ep_index"`
SeasonType int `json:"season_type,omitempty"`
Mode int `json:"mode,omitempty"`
}
// Bangumi bangumi.
type Bangumi struct {
Epid int64 `json:"ep_id"`
Title string `json:"title"`
LongTitle string `json:"long_title"`
EpisodeStatus int `json:"episode_status"`
Follow int `json:"follow"`
Cover string `json:"cover"`
Season *Season `json:"season"`
}
// BangumiSeason season.
type BangumiSeason struct {
ID int64 `json:"season_id"`
Epid int64 `json:"episode_id"`
EpidType int64 `json:"season_type"`
}

View File

@@ -0,0 +1,173 @@
package model
import hismdl "go-common/app/service/main/history/model"
// History video hisotry info.
type History struct {
Mid int64 `json:"mid,omitempty"`
Aid int64 `json:"aid"`
Sid int64 `json:"sid,omitempty"`
Epid int64 `json:"epid,omitempty"`
TP int8 `json:"tp,omitempty"`
Business string `json:"business"`
STP int8 `json:"stp,omitempty"` // sub_type
Cid int64 `json:"cid,omitempty"`
DT int8 `json:"dt,omitempty"`
Pro int64 `json:"pro,omitempty"`
Unix int64 `json:"view_at"`
}
// Histories history sorted.
type Histories []*History
func (h Histories) Len() int { return len(h) }
func (h Histories) Less(i, j int) bool {
if h[i].Unix == h[j].Unix {
return h[i].Aid < h[j].Aid
}
return h[i].Unix > h[j].Unix
}
func (h Histories) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
// FillBusiness add history
func (h *History) FillBusiness() {
if h == nil {
return
}
h.Business = businessIDs[h.TP]
}
// ConvertType convert old type
func (h *History) ConvertType() {
if h == nil {
return
}
switch h.TP {
case TypeBangumi:
h.TP = TypePGC
h.STP = SubTypeBangumi
case TypeMovie:
h.TP = TypePGC
h.STP = SubTypeFilm
case TypePGC:
if h.Epid == 0 || h.Sid == 0 {
h.TP = TypeUGC
}
}
}
// ConvertServiceType .
func (h History) ConvertServiceType() (r *hismdl.History) {
switch h.TP {
case TypeOffline:
h.TP = TypeUGC
h.STP = SubTypeOffline
case TypeUnknown:
h.TP = TypeUGC
case TypeBangumi:
h.TP = TypePGC
h.STP = SubTypeBangumi
case TypeMovie:
h.TP = TypePGC
h.STP = SubTypeFilm
}
if h.TP == TypePGC && (h.Epid == 0 || h.Sid == 0) {
h.TP = TypeUGC
}
h.FillBusiness()
r = &hismdl.History{
Mid: h.Mid,
BusinessID: int64(h.TP),
Business: h.Business,
Kid: h.Aid,
Aid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
SubType: int32(h.STP),
Device: int32(h.DT),
Progress: int32(h.Pro),
ViewAt: h.Unix,
}
if h.TP == TypePGC {
r.Kid = r.Sid
}
return
}
// ArgPro arg.
type ArgPro struct {
Mid int64
RealIP string
Aids []int64
}
// ArgPos arg.
type ArgPos struct {
Mid int64
Aid int64
Business string
TP int8
RealIP string
}
// ArgDelete arg.
type ArgDelete struct {
Mid int64
RealIP string
Resources []*Resource
}
// ArgHistory arg.
type ArgHistory struct {
Mid int64
Realtime int64
RealIP string
History *History
}
// ArgHistories arg.
type ArgHistories struct {
Mid int64
TP int8
Business string
Pn int
Ps int
RealIP string
}
// ArgCursor arg.
type ArgCursor struct {
Mid int64
Max int64
TP int8
// history business
Business string
ViewAt int64
// filter business, blank means all business
Businesses []string
Ps int
RealIP string
}
// Resource video hisotry info .
type Resource struct {
Mid int64 `json:"mid,omitempty"`
Oid int64 `json:"oid"`
Sid int64 `json:"sid,omitempty"`
Epid int64 `json:"epid,omitempty"`
TP int8 `json:"tp,omitempty"`
STP int8 `json:"stp,omitempty"` // sub_type
Cid int64 `json:"cid,omitempty"`
Business string `json:"business"`
DT int8 `json:"dt,omitempty"`
Pro int64 `json:"pro,omitempty"`
Unix int64 `json:"view_at"`
}
// ArgClear .
type ArgClear struct {
Mid int64
RealIP string
Businesses []string
}

View File

@@ -0,0 +1,37 @@
package model
import (
"go-common/app/service/main/archive/model/archive"
)
// ArcToView toview video.
type ArcToView struct {
*archive.Archive3
Page *archive.Page3 `json:"page,omitempty"`
Count int `json:"count"`
Cid int64 `json:"cid"`
Progress int64 `json:"progress"`
AddTime int64 `json:"add_at"`
}
// WebArcToView toview video.
type WebArcToView struct {
*archive.View3
BangumiInfo *Bangumi `json:"bangumi,omitempty"`
Cid int64 `json:"cid"`
Progress int64 `json:"progress"`
AddTime int64 `json:"add_at"`
}
// ToView toview.
type ToView struct {
Aid int64 `json:"aid,omitempty"`
Unix int64 `json:"now,omitempty"`
}
// ToViews toview sorted.
type ToViews []*ToView
func (h ToViews) Len() int { return len(h) }
func (h ToViews) Less(i, j int) bool { return h[i].Unix > h[j].Unix }
func (h ToViews) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["client_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//app/interface/main/history/model:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/app/interface/main/history/rpc/client",
tags = ["automanaged"],
deps = [
"//app/interface/main/history/model:go_default_library",
"//library/net/rpc: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,82 @@
package client
import (
"context"
"go-common/app/interface/main/history/model"
"go-common/library/net/rpc"
)
const (
_progress = "RPC.Progress"
_position = "RPC.Position"
_add = "RPC.Add"
_delete = "RPC.Delete"
_history = "RPC.History"
_historyCursor = "RPC.HistoryCursor"
_clear = "RPC.Clear"
)
var (
_noRes = &struct{}{}
)
// Service struct info.
type Service struct {
client *rpc.Client2
}
const (
_appid = "community.service.history"
)
// New create instance of service and return.
func New(c *rpc.ClientConfig) (s *Service) {
s = &Service{}
s.client = rpc.NewDiscoveryCli(_appid, c)
return
}
// Progress return map[mid]*history.
func (s *Service) Progress(c context.Context, arg *model.ArgPro) (res map[int64]*model.History, err error) {
res = make(map[int64]*model.History)
err = s.client.Call(c, _progress, arg, &res)
return
}
// Position return map[mid]*history.
func (s *Service) Position(c context.Context, arg *model.ArgPos) (res *model.History, err error) {
res = &model.History{}
err = s.client.Call(c, _position, arg, res)
return
}
// Add add history .
func (s *Service) Add(c context.Context, arg *model.ArgHistory) (err error) {
err = s.client.Call(c, _add, arg, _noRes)
return
}
// Delete add history .
func (s *Service) Delete(c context.Context, arg *model.ArgDelete) (err error) {
err = s.client.Call(c, _delete, arg, _noRes)
return
}
// History return all histories .
func (s *Service) History(c context.Context, arg *model.ArgHistories) (res []*model.Resource, err error) {
err = s.client.Call(c, _history, arg, &res)
return
}
// HistoryCursor return all histories .
func (s *Service) HistoryCursor(c context.Context, arg *model.ArgCursor) (res []*model.Resource, err error) {
err = s.client.Call(c, _historyCursor, arg, &res)
return
}
// Clear clear history
func (s *Service) Clear(c context.Context, arg *model.ArgClear) (err error) {
err = s.client.Call(c, _clear, arg, _noRes)
return
}

View File

@@ -0,0 +1,33 @@
package client
import (
"context"
"testing"
"time"
"go-common/app/interface/main/history/model"
)
func TestHistory(t *testing.T) {
s := New(nil)
time.Sleep(1 * time.Second)
testProgress(t, s)
testAdd(t, s)
}
// testProgress test progress rpc.
func testProgress(t *testing.T, s *Service) {
if res, err := s.Progress(context.TODO(), &model.ArgPro{Mid: 88888966, Aids: []int64{5463286}}); err != nil {
t.Errorf("Service: Progress err: %v", err)
} else {
t.Logf("Service: zone res: %+v", res)
}
}
// testAdd test add rpc .
func testAdd(t *testing.T, s *Service) {
h := &model.History{Mid: 88888966, Aid: 5463286, TP: -1, Unix: 1494217922}
if err := s.Add(context.TODO(), &model.ArgHistory{Mid: 88888966, History: h}); err != nil {
t.Errorf("Service: Add err: %v", err)
}
}

View File

@@ -0,0 +1,49 @@
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/interface/main/history/model:go_default_library",
"//app/interface/main/history/rpc/client:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["rpc.go"],
importpath = "go-common/app/interface/main/history/server/gorpc",
tags = ["automanaged"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/model:go_default_library",
"//app/interface/main/history/service:go_default_library",
"//library/ecode: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,136 @@
package gorpc
import (
"go-common/app/interface/main/history/conf"
"go-common/app/interface/main/history/model"
"go-common/app/interface/main/history/service"
"go-common/library/ecode"
"go-common/library/net/rpc"
"go-common/library/net/rpc/context"
)
// RPC represent rpc server
type RPC struct {
svc *service.Service
}
// New init rpc.
func New(c *conf.Config, s *service.Service) (svr *rpc.Server) {
r := &RPC{svc: 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
}
// Progress report a user hisotry.
func (r *RPC) Progress(c context.Context, arg *model.ArgPro, res *map[int64]*model.History) (err error) {
*res, err = r.svc.Progress(c, arg.Mid, arg.Aids)
return
}
// Position report a user hisotry.
func (r *RPC) Position(c context.Context, arg *model.ArgPos, res *model.History) (err error) {
var v *model.History
tp, err := model.CheckBusiness(arg.Business)
if err != nil {
return
}
if tp > 0 {
arg.TP = tp
}
v, err = r.svc.Position(c, arg.Mid, arg.Aid, arg.TP)
if err == nil {
*res = *v
}
return
}
// Add (c context.Context, mid, src, rtime int64, ip string, h *model.History) (err error) .
func (r *RPC) Add(c context.Context, arg *model.ArgHistory, res *struct{}) (err error) {
if arg.History != nil {
var tp int8
if tp, err = model.CheckBusiness(arg.History.Business); err != nil {
return
} else if tp > 0 {
arg.History.TP = tp
}
}
err = r.svc.AddHistory(c, arg.Mid, arg.Realtime, arg.History)
return
}
// Delete delete histories
func (r *RPC) Delete(c context.Context, arg *model.ArgDelete, res *struct{}) (err error) {
if len(arg.Resources) == 0 {
err = ecode.RequestErr
return
}
histories := make([]*model.History, 0, len(arg.Resources))
for _, history := range arg.Resources {
var tp int8
if tp, err = model.MustCheckBusiness(history.Business); err != nil {
return
}
histories = append(histories, &model.History{
Aid: history.Oid,
TP: tp,
})
}
err = r.svc.Delete(c, arg.Mid, histories)
return
}
// History return all history .
func (r *RPC) History(c context.Context, arg *model.ArgHistories, res *[]*model.Resource) (err error) {
tp, err := model.CheckBusiness(arg.Business)
if err != nil {
return
}
if tp > 0 {
arg.TP = tp
}
*res, err = r.svc.Histories(c, arg.Mid, arg.TP, arg.Pn, arg.Ps)
return
}
// HistoryCursor return all history .
func (r *RPC) HistoryCursor(c context.Context, arg *model.ArgCursor, res *[]*model.Resource) (err error) {
tp, err := model.CheckBusiness(arg.Business)
if err != nil {
return
}
if tp > 0 {
arg.TP = tp
}
var tps []int8
for _, b := range arg.Businesses {
tp, err = model.CheckBusiness(b)
if err != nil {
return
}
tps = append(tps, tp)
}
*res, err = r.svc.HistoryCursor(c, arg.Mid, arg.Max, arg.ViewAt, arg.Ps, arg.TP, tps, arg.RealIP)
return
}
// Clear clear history
func (r *RPC) Clear(c context.Context, arg *model.ArgClear, res *struct{}) (err error) {
var tps []int8
for _, b := range arg.Businesses {
var tp int8
if tp, err = model.MustCheckBusiness(b); err != nil {
return
}
tps = append(tps, tp)
}
err = r.svc.ClearHistory(c, arg.Mid, tps)
return
}

View File

@@ -0,0 +1,55 @@
package gorpc
import (
"context"
"testing"
"go-common/app/interface/main/history/model"
rpcClient "go-common/app/interface/main/history/rpc/client"
. "github.com/smartystreets/goconvey/convey"
)
var (
ctx = context.TODO()
client *rpcClient.Service
)
func WithRPC(f func(client *rpcClient.Service)) func() {
return func() {
client = rpcClient.New(nil)
f(client)
}
}
func Test_Histroy_rpc(t *testing.T) {
Convey("rpc client Add", t, WithRPC(func(client *rpcClient.Service) {
arg := &model.ArgHistory{
Mid: 14771787,
History: &model.History{
Aid: 17406762,
TP: 1,
Pro: 122,
},
}
client.Add(ctx, arg)
}))
Convey("rpc client preogress", t, WithRPC(func(client *rpcClient.Service) {
arg := &model.ArgPro{
Mid: 14771787,
Aids: []int64{17406762},
}
client.Progress(ctx, arg)
}))
Convey("rpc client delete", t, WithRPC(func(client *rpcClient.Service) {
r := &model.Resource{Oid: 100, Business: "archive"}
arg := &model.ArgDelete{
Mid: 14771787,
Resources: []*model.Resource{r},
}
client.Delete(ctx, arg)
}))
Convey("rpc client history", t, WithRPC(func(client *rpcClient.Service) {
arg := &model.ArgHistories{Mid: 14771787}
client.History(ctx, arg)
}))
}

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 = ["server.go"],
importpath = "go-common/app/interface/main/history/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/history/api/grpc:go_default_library",
"//app/interface/main/history/model:go_default_library",
"//app/interface/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,211 @@
// Package grpc generate by warden_gen
package grpc
import (
"context"
pb "go-common/app/interface/main/history/api/grpc"
"go-common/app/interface/main/history/model"
service "go-common/app/interface/main/history/service"
"go-common/library/net/rpc/warden"
)
// New History warden rpc server
func New(c *warden.ServerConfig, svr *service.Service) *warden.Server {
ws := warden.NewServer(c)
pb.RegisterHistoryServer(ws.Server(), &server{svr})
ws, err := ws.Start()
if err != nil {
panic(err)
}
return ws
}
type server struct {
svr *service.Service
}
var _ pb.HistoryServer = &server{}
// AddHistory add hisotry progress into hbase.
func (s *server) AddHistory(ctx context.Context, req *pb.AddHistoryReq) (*pb.AddHistoryReply, error) {
tp, err := model.MustCheckBusiness(req.H.Business)
if err != nil {
return nil, err
}
h := &model.History{
Mid: req.H.Mid,
Aid: req.H.Aid,
Sid: req.H.Sid,
Epid: req.H.Epid,
TP: tp,
Business: req.H.Business,
STP: int8(req.H.Stp),
Cid: req.H.Cid,
DT: int8(req.H.Dt),
Pro: req.H.Pro,
Unix: req.H.Unix,
}
return nil, s.svr.AddHistory(ctx, req.Mid, req.Rtime, h)
}
// Progress get view progress from cache/hbase.
func (s *server) Progress(ctx context.Context, req *pb.ProgressReq) (*pb.ProgressReply, error) {
histories, err := s.svr.Progress(ctx, req.Mid, req.Aids)
if err != nil {
return nil, err
}
reply := &pb.ProgressReply{Res: make(map[int64]*pb.ModelHistory)}
for k, v := range histories {
reply.Res[k] = &pb.ModelHistory{
Mid: v.Mid,
Aid: v.Aid,
Sid: v.Sid,
Epid: v.Epid,
Business: v.Business,
Stp: int32(v.STP),
Cid: v.Cid,
Dt: int32(v.DT),
Pro: v.Pro,
Unix: v.Unix,
}
}
return reply, nil
}
// Position get view progress from cache/hbase.
func (s *server) Position(ctx context.Context, req *pb.PositionReq) (*pb.PositionReply, error) {
tp, err := model.MustCheckBusiness(req.Business)
if err != nil {
return nil, err
}
h, err := s.svr.Position(ctx, req.Mid, req.Aid, tp)
if err != nil {
return nil, err
}
reply := &pb.PositionReply{
Res: &pb.ModelHistory{
Mid: h.Mid,
Aid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Business: h.Business,
Stp: int32(h.STP),
Cid: h.Cid,
Dt: int32(h.DT),
Pro: h.Pro,
Unix: h.Unix,
},
}
return reply, err
}
// ClearHistory clear user's historys.
func (s *server) ClearHistory(ctx context.Context, req *pb.ClearHistoryReq) (*pb.ClearHistoryReply, error) {
var tps []int8
for _, b := range req.Businesses {
tp, err := model.MustCheckBusiness(b)
if err != nil {
return nil, err
}
tps = append(tps, tp)
}
return nil, s.svr.ClearHistory(ctx, req.Mid, tps)
}
// Histories return the user all av history.
func (s *server) Histories(ctx context.Context, req *pb.HistoriesReq) (*pb.HistoriesReply, error) {
tp, err := model.MustCheckBusiness(req.Business)
if err != nil {
return nil, err
}
resources, err := s.svr.Histories(ctx, req.Mid, tp, int(req.Pn), int(req.Ps))
if err != nil {
return nil, err
}
reply := &pb.HistoriesReply{}
for _, v := range resources {
reply.Res = append(reply.Res,
&pb.ModelResource{
Mid: v.Mid,
Oid: v.Oid,
Sid: v.Sid,
Epid: v.Epid,
Business: v.Business,
Stp: int32(v.STP),
Cid: v.Cid,
Dt: int32(v.DT),
Pro: v.Pro,
Unix: v.Unix,
})
}
return reply, nil
}
// HistoryCursor return the user all av history.
func (s *server) HistoryCursor(ctx context.Context, req *pb.HistoryCursorReq) (*pb.HistoryCursorReply, error) {
tp, err := model.MustCheckBusiness(req.Business)
if err != nil {
return nil, err
}
var tps []int8
for _, b := range req.Businesses {
t, er := model.MustCheckBusiness(b)
if er != nil {
return nil, er
}
tps = append(tps, t)
}
resources, err := s.svr.HistoryCursor(ctx, req.Mid, req.Max, req.ViewAt, int(req.Ps), tp, tps, req.Ip)
if err != nil {
return nil, err
}
reply := &pb.HistoryCursorReply{}
for _, v := range resources {
reply.Res = append(reply.Res,
&pb.ModelResource{
Mid: v.Mid,
Oid: v.Oid,
Sid: v.Sid,
Epid: v.Epid,
Business: v.Business,
Stp: int32(v.STP),
Cid: v.Cid,
Dt: int32(v.DT),
Pro: v.Pro,
Unix: v.Unix,
})
}
return reply, nil
}
// Delete .
func (s *server) Delete(ctx context.Context, req *pb.DeleteReq) (*pb.DeleteReply, error) {
var his []*model.History
for _, b := range req.His {
tp, err := model.MustCheckBusiness(b.Business)
if err != nil {
return nil, err
}
h := &model.History{
Mid: b.Mid,
Aid: b.Aid,
Sid: b.Sid,
Epid: b.Epid,
TP: tp,
Business: b.Business,
STP: int8(b.Stp),
Cid: b.Cid,
DT: int8(b.Dt),
Pro: b.Pro,
Unix: b.Unix,
}
his = append(his, h)
}
return nil, s.svr.Delete(ctx, req.Mid, his)
}
// FlushHistory flush to hbase from cache.
func (s *server) FlushHistory(ctx context.Context, req *pb.FlushHistoryReq) (*pb.FlushHistoryReply, error) {
return nil, s.svr.FlushHistory(ctx, req.Mids, req.Stime)
}

View File

@@ -0,0 +1,71 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"history_test.go",
"service_test.go",
"toview_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/model:go_default_library",
"//library/cache/redis:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"history.go",
"history_service.go",
"rpc.go",
"service.go",
"toview.go",
],
importpath = "go-common/app/interface/main/history/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/dao/history:go_default_library",
"//app/interface/main/history/dao/toview:go_default_library",
"//app/interface/main/history/model:go_default_library",
"//app/service/main/archive/api/gorpc:go_default_library",
"//app/service/main/archive/model/archive:go_default_library",
"//app/service/main/favorite/api/gorpc:go_default_library",
"//app/service/main/favorite/model:go_default_library",
"//app/service/main/history/api/grpc:go_default_library",
"//app/service/main/history/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,841 @@
package service
import (
"context"
"fmt"
"sort"
"strconv"
"time"
"go-common/app/interface/main/history/model"
arcmdl "go-common/app/service/main/archive/model/archive"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
)
var (
_countLimit = 50
_emptyVideos = []*model.Video{}
_emptyHis = []*model.History{}
_emptyHismap = make(map[int64]*model.History)
)
func (s *Service) key(aid int64, typ int8) string {
if typ < model.TypeArticle {
return strconv.FormatInt(aid, 10)
}
return fmt.Sprintf("%d_%d", aid, typ)
}
// AddHistories batch update history.
// +wd:ignore
func (s *Service) AddHistories(c context.Context, mid int64, typ int8, ip string, hs []*model.History) (err error) {
var (
ok bool
his, h *model.History
hm2 map[string]*model.History
hmc, res map[int64]*model.History
expire = time.Now().Unix() - 60*60*24*90
)
if len(hs) > _countLimit {
return ecode.TargetNumberLimit
}
s.serviceAdds(mid, hs)
if hm2, err = s.historyDao.Map(c, mid); err != nil {
return
}
if typ < model.TypeArticle {
if hmc, err = s.historyDao.CacheMap(c, mid); err != nil {
return err
}
}
res = make(map[int64]*model.History)
for _, his = range hs {
if his.Unix < expire {
continue
}
if len(hm2) > 0 {
if h, ok = hm2[s.key(his.Aid, his.TP)]; ok && his.Unix < h.Unix {
continue
}
}
if len(hmc) > 0 {
if h, ok = hmc[his.Aid]; ok && his.Unix < h.Unix {
continue
}
}
// TODO comment && merge
res[his.Aid] = his
}
if err = s.historyDao.AddMap(c, mid, res); err == nil {
return
}
if typ < model.TypeArticle {
s.historyDao.AddCacheMap(c, mid, res)
}
return nil
}
// AddHistory add hisotry progress into hbase.
func (s *Service) AddHistory(c context.Context, mid, rtime int64, h *model.History) (err error) {
if h.TP < model.TypeUnknown || h.TP > model.TypeComic {
err = ecode.RequestErr
return
}
if h.Aid == 0 {
return ecode.RequestErr
}
if h.TP == model.TypeBangumi || h.TP == model.TypeMovie || h.TP == model.TypePGC {
msg := playPro{
Type: h.TP,
SubType: h.STP,
Mid: mid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
Progress: h.Pro,
IP: metadata.String(c, metadata.RemoteIP),
Ts: h.Unix,
RealTime: rtime,
}
s.addPlayPro(&msg)
}
// NOTE if login user to save history
if mid == 0 {
return
}
return s.addHistory(c, mid, h)
}
// Progress get view progress from cache/hbase.
func (s *Service) Progress(c context.Context, mid int64, aids []int64) (res map[int64]*model.History, err error) {
if mid == 0 {
res = _emptyHismap
return
}
if s.migration(mid) {
res, err = s.servicePosition(c, mid, model.BusinessByTP(model.TypeUGC), aids)
if err == nil {
return
}
}
if res, _, err = s.historyDao.Cache(c, mid, aids); err != nil {
return
} else if len(res) == 0 {
res = _emptyHismap
}
return
}
// Position get view progress from cache/hbase.
func (s *Service) Position(c context.Context, mid int64, aid int64, typ int8) (res *model.History, err error) {
if mid == 0 {
err = ecode.NothingFound
return
}
if s.migration(mid) {
var hm map[int64]*model.History
hm, err = s.servicePosition(c, mid, model.BusinessByTP(typ), []int64{aid})
if err == nil && hm != nil {
if res = hm[aid]; res == nil {
err = ecode.NothingFound
}
return
}
}
if typ < model.TypeArticle {
var hm map[int64]*model.History
hm, _, err = s.historyDao.Cache(c, mid, []int64{aid})
if err != nil {
return
}
if len(hm) > 0 {
if h, ok := hm[aid]; ok {
res = h
}
}
if res == nil {
err = ecode.NothingFound
}
return
}
var mhis map[string]*model.History
mhis, err = s.historyDao.Map(c, mid)
if err != nil {
return
}
if len(mhis) > 0 {
key := fmt.Sprintf("%d_%d", aid, typ)
if h, ok := mhis[key]; ok {
res = h
}
}
if res == nil {
err = ecode.NothingFound
}
return
}
// addHistory add new history into set.
func (s *Service) addHistory(c context.Context, mid int64, h *model.History) (err error) {
var cmd int64
// note: the type is video to increase experience of user .
if h.TP < model.TypeArticle {
s.historyDao.PushFirstQueue(c, mid, h.Aid, h.Unix)
}
if cmd, err = s.Shadow(c, mid); err != nil {
return
}
if cmd == model.ShadowOn {
return
}
h.Mid = mid
s.serviceAdd(h)
// note: the type is video to redis`cache .
if h.TP >= model.TypeArticle {
s.addProPub(h)
if !s.conf.History.Pub {
err = s.historyDao.Add(c, mid, h)
}
return
}
// NOTE first view
if h.Pro < 30 && h.Pro != -1 {
h.Pro = 0
}
// NOTE after 30s
if err = s.historyDao.AddCache(c, mid, h); err != nil {
return
}
s.addMerge(mid, h.Unix)
return
}
// ClearHistory clear user's historys.
func (s *Service) ClearHistory(c context.Context, mid int64, tps []int8) (err error) {
s.serviceClear(mid, tps)
if len(tps) == 0 {
s.historyDao.ClearCache(c, mid)
err = s.historyDao.Clear(c, mid)
s.userActionLog(mid, model.HistoryClear)
return
}
tpsMap := make(map[int8]bool)
for _, tp := range tps {
tpsMap[tp] = true
}
var (
histories map[string]*model.History
dels []*model.History
chis map[int64]*model.History
aids []int64
)
if histories, err = s.historyDao.Map(c, mid); err != nil {
return
}
chis, _ = s.historyDao.CacheMap(c, mid)
for _, v := range chis {
histories[s.key(v.Aid, v.TP)] = v
}
logMap := make(map[int8]struct{})
for _, h := range histories {
oldType := h.TP
h.ConvertType()
if tpsMap[h.TP] {
if (h.TP == model.TypeUGC) || (h.TP == model.TypePGC) {
aids = append(aids, h.Aid)
}
h.TP = oldType
dels = append(dels, h)
logMap[oldType] = struct{}{}
}
}
if len(dels) == 0 {
return
}
if len(aids) > 0 {
s.historyDao.DelCache(c, mid, aids)
}
if err = s.historyDao.Delete(c, mid, dels); err != nil {
return
}
for k := range logMap {
s.userActionLog(mid, fmt.Sprintf(model.HistoryClearTyp, model.BusinessByTP(k)))
}
return
}
// DelHistory delete user's history del archive .
// +wd:ignore
func (s *Service) DelHistory(ctx context.Context, mid int64, aids []int64, typ int8) (err error) {
if err = s.serviceDels(ctx, mid, aids, typ); err != nil {
return
}
if err = s.historyDao.DelAids(ctx, mid, aids); err != nil {
return
}
if typ >= model.TypeArticle {
return
}
return s.historyDao.DelCache(ctx, mid, aids)
}
// Videos get videos of user view history.
// +wd:ignore
func (s *Service) Videos(c context.Context, mid int64, pn, ps int, typ int8) (res []*model.Video, err error) {
var (
arc *arcmdl.View3
ok bool
arcs map[int64]*arcmdl.View3
video *model.Video
history []*model.History
his *model.History
aids, epids []int64
aidFavs map[int64]bool
epban map[int64]*model.Bangumi
)
res = _emptyVideos
mOK := s.migration(mid)
if mOK {
businesses := []string{model.BusinessByTP(model.TypeUGC), model.BusinessByTP(model.TypePGC)}
history, epids, err = s.servicePnPsCursor(c, mid, businesses, pn, ps)
}
if !mOK || err != nil {
if history, epids, err = s.histories(c, mid, pn, ps, true); err != nil {
return
}
}
if len(history) == 0 {
return
}
for _, his = range history {
aids = append(aids, his.Aid)
}
if typ >= model.TypeArticle {
// TODO Article info .
return
}
// bangumi info
if len(epids) > 0 {
epban = s.bangumi(c, mid, epids)
}
// archive info
arcAids := &arcmdl.ArgAids2{Aids: aids}
if arcs, err = s.arcRPC.Views3(c, arcAids); err != nil {
return
} else if len(arcs) == 0 {
return
}
// favorite info
aidFavs = s.favoriteds(c, mid, aids)
res = make([]*model.Video, 0, len(aids))
for _, his = range history {
if arc, ok = arcs[his.Aid]; !ok || arc.Archive3 == nil {
continue
}
// NOTE all no pay
arc.Rights.Movie = 0
video = &model.Video{
Archive3: arc.Archive3,
ViewAt: his.Unix,
DT: his.DT,
STP: his.STP,
TP: his.TP,
Progress: his.Pro,
Count: len(arc.Pages),
}
if aidFavs != nil {
video.Favorite = aidFavs[his.Aid]
}
for n, p := range arc.Pages {
if p.Cid == his.Cid {
p.Page = int32(n + 1)
video.Page = p
break
}
}
if video.TP == model.TypeBangumi || video.TP == model.TypeMovie || video.TP == model.TypePGC {
if epban != nil {
video.BangumiInfo = epban[his.Epid]
}
video.Count = 0
}
res = append(res, video)
}
return
}
// AVHistories return the user all av history.
// +wd:ignore
func (s *Service) AVHistories(c context.Context, mid int64) (hs []*model.History, err error) {
if s.migration(mid) {
businesses := []string{model.BusinessByTP(model.TypeUGC), model.BusinessByTP(model.TypePGC)}
hs, _, err = s.servicePnPsCursor(c, mid, businesses, 1, s.conf.History.Max)
if err == nil {
return
}
}
hs, _, err = s.histories(c, mid, 1, s.conf.History.Max, true)
return
}
// Histories return the user all av history.
func (s *Service) Histories(c context.Context, mid int64, typ int8, pn, ps int) (res []*model.Resource, err error) {
var hs []*model.History
mOK := s.migration(mid)
if mOK {
var businesses []string
if typ > 0 {
businesses = []string{model.BusinessByTP(typ)}
}
hs, _, err = s.servicePnPsCursor(c, mid, businesses, pn, ps)
}
if !mOK || err != nil {
if typ >= model.TypeArticle {
hs, err = s.platformHistories(c, mid, typ, pn, ps)
} else {
hs, _, err = s.histories(c, mid, pn, ps, false)
}
}
if err != nil {
return
}
if len(hs) == 0 {
return
}
for _, h := range hs {
h.ConvertType()
r := &model.Resource{
Mid: h.Mid,
Oid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
DT: h.DT,
Pro: h.Pro,
Unix: h.Unix,
TP: h.TP,
STP: h.STP,
}
res = append(res, r)
}
return
}
// histories get aids of user view history
func (s *Service) histories(c context.Context, mid int64, pn, ps int, onlyAV bool) (his []*model.History, epids []int64, err error) {
var (
size int
ok bool
e, h *model.History
mhis map[string]*model.History
chis, ehis map[int64]*model.History
dhis []*model.History
start = (pn - 1) * ps
end = start + ps - 1
total = s.conf.History.Total
)
if mhis, err = s.historyDao.Map(c, mid); err != nil {
err = nil
mhis = make(map[string]*model.History)
}
chis, _ = s.historyDao.CacheMap(c, mid)
for _, v := range chis {
mhis[s.key(v.Aid, v.TP)] = v
}
if len(mhis) == 0 {
his = _emptyHis
return
}
ehis = make(map[int64]*model.History, len(mhis))
for _, h = range mhis {
if onlyAV && h.TP >= model.TypeArticle {
continue
}
if (h.TP == model.TypeBangumi || h.TP == model.TypeMovie || h.TP == model.TypePGC) && h.Sid != 0 {
if e, ok = ehis[h.Sid]; !ok || h.Unix > e.Unix {
ehis[h.Sid] = h
if e != nil {
dhis = append(dhis, e)
}
}
} else {
his = append(his, h)
}
}
for _, h = range ehis {
if h.Epid != 0 {
epids = append(epids, h.Epid)
}
his = append(his, h)
}
sort.Sort(model.Histories(his))
if size = len(his); size > total {
dhis = append(dhis, his[total:]...)
s.delChan.Do(c, func(ctx context.Context) {
s.historyDao.Delete(ctx, mid, dhis)
})
his = his[:total]
size = total
}
switch {
case size > start && size > end:
his = his[start : end+1]
case size > start && size <= end:
his = his[start:]
default:
his = _emptyHis
}
return
}
// platformHistories get aids of user view history
func (s *Service) platformHistories(c context.Context, mid int64, typ int8, pn, ps int) (his []*model.History, err error) {
var (
size int
h *model.History
mhis map[string]*model.History
start = (pn - 1) * ps
end = start + ps - 1
total = s.conf.History.Total
)
if mhis, err = s.historyDao.Map(c, mid); err != nil {
return
}
if len(mhis) == 0 {
his = _emptyHis
return
}
for _, h = range mhis {
if typ != h.TP {
continue
}
his = append(his, h)
}
sort.Sort(model.Histories(his))
if size = len(his); size > total {
his = his[:total]
size = total
}
switch {
case size > start && size > end:
his = his[start : end+1]
case size > start && size <= end:
his = his[start:]
default:
his = _emptyHis
}
return
}
// HistoryType get aids of user view history
// +wd:ignore
func (s *Service) HistoryType(c context.Context, mid int64, typ int8, oids []int64, ip string) (his []*model.History, err error) {
var (
mhis map[string]*model.History
)
if s.migration(mid) {
his, err = s.serviceHistoryType(c, mid, model.BusinessByTP(typ), oids)
if err == nil {
return
}
}
if mhis, err = s.historyDao.Map(c, mid); err != nil {
return
}
if len(mhis) == 0 {
his = _emptyHis
return
}
for _, oid := range oids {
key := fmt.Sprintf("%d_%d", oid, typ)
if h, ok := mhis[key]; ok && h != nil {
his = append(his, h)
}
}
return
}
// HistoryCursor return the user all av history.
func (s *Service) HistoryCursor(c context.Context, mid, max, viewAt int64, ps int, tp int8, tps []int8, ip string) (res []*model.Resource, err error) {
var hs []*model.History
if s.migration(mid) {
var businesses []string
for _, b := range tps {
businesses = append(businesses, model.BusinessByTP(b))
}
res, err = s.serviceHistoryCursor(c, mid, max, businesses, model.BusinessByTP(tp), viewAt, ps)
if err == nil {
return
}
}
hs, err = s.historyCursor(c, mid, max, viewAt, ps, tp, tps, ip)
if err != nil {
return
}
if len(hs) == 0 {
return
}
for _, h := range hs {
r := &model.Resource{
TP: h.TP,
STP: h.STP,
Mid: h.Mid,
Oid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
Business: h.Business,
DT: h.DT,
Pro: h.Pro,
Unix: h.Unix,
}
res = append(res, r)
}
return
}
// historyCursor get aids of user view history.
func (s *Service) historyCursor(c context.Context, mid, max, viewAt int64, ps int, tp int8, tps []int8, ip string) (his []*model.History, err error) {
var (
ok bool
e, h *model.History
mhis map[string]*model.History
chis, ehis map[int64]*model.History
dhis []*model.History
total = s.conf.History.Total
tpMap = make(map[int8]bool)
)
for _, tp := range tps {
tpMap[tp] = true
}
if mhis, err = s.historyDao.Map(c, mid); err != nil {
err = nil
mhis = make(map[string]*model.History)
}
for k, v := range mhis {
v.ConvertType()
if (len(tps) > 0) && !tpMap[v.TP] {
delete(mhis, k)
}
}
chis, _ = s.historyDao.CacheMap(c, mid)
for k, v := range chis {
v.ConvertType()
if (len(tps) > 0) && !tpMap[v.TP] {
delete(chis, k)
continue
}
mhis[s.key(v.Aid, v.TP)] = v
}
if len(mhis) == 0 {
his = _emptyHis
return
}
ehis = make(map[int64]*model.History, len(mhis))
for _, h = range mhis {
if (h.TP == model.TypePGC) && h.Sid != 0 {
if e, ok = ehis[h.Sid]; !ok || h.Unix > e.Unix {
ehis[h.Sid] = h
if e != nil {
dhis = append(dhis, e)
}
}
} else {
his = append(his, h)
}
}
for _, h = range ehis {
his = append(his, h)
}
sort.Sort(model.Histories(his))
if len(his) > total {
dhis = append(dhis, his[total:]...)
s.delChan.Do(c, func(ctx context.Context) {
s.historyDao.Delete(ctx, mid, dhis)
})
his = his[:total]
}
if viewAt != 0 || max != 0 && tp != 0 {
for index, h := range his {
if viewAt != 0 && h.Unix <= viewAt || h.Aid == max && h.TP == tp {
index++
if index+ps <= len(his) {
return his[index : index+ps], nil
}
return his[index:], nil
}
}
return _emptyHis, nil
}
if len(his) >= ps {
return his[:ps], nil
}
return
}
// SetShadow set the user switch.
// +wd:ignore
func (s *Service) SetShadow(c context.Context, mid, value int64) (err error) {
s.serviceHide(mid, value == model.ShadowOn)
if err = s.historyDao.SetInfoShadow(c, mid, value); err != nil {
return
}
return s.historyDao.SetShadowCache(c, mid, value)
}
// Delete .
func (s *Service) Delete(ctx context.Context, mid int64, his []*model.History) (err error) {
if err = s.serviceDel(ctx, mid, his); err != nil {
return
}
if err = s.historyDao.Delete(ctx, mid, his); err != nil {
return
}
var aids []int64
for _, h := range his {
if h.TP < model.TypeArticle {
aids = append(aids, h.Aid)
}
}
if len(aids) == 0 {
return
}
return s.historyDao.DelCache(ctx, mid, aids)
}
// Shadow return the user switch by mid.
// +wd:ignore
func (s *Service) Shadow(c context.Context, mid int64) (value int64, err error) {
var (
ok bool
cache = true
)
if s.migration(mid) {
value, err = s.serviceHideState(c, mid)
if err == nil {
return
}
}
if value, err = s.historyDao.ShadowCache(c, mid); err != nil {
err = nil
cache = false
} else if value != model.ShadowUnknown {
return
}
if value, err = s.historyDao.InfoShadow(c, mid); err != nil {
ok = true
}
if cache {
s.cache.Do(c, func(ctx context.Context) {
s.historyDao.SetShadowCache(ctx, mid, value)
if ok && value == model.ShadowOn {
s.historyDao.SetInfoShadow(ctx, mid, value)
}
})
}
return
}
// FlushHistory flush to hbase from cache.
func (s *Service) FlushHistory(c context.Context, mids []int64, stime int64) (err error) {
var (
aids, miss []int64
res map[int64]*model.History
limit = s.conf.History.Cache
)
for _, mid := range mids {
if aids, err = s.historyDao.IndexCacheByTime(c, mid, stime); err != nil {
log.Error("s.historyDao.IndexCacheByTime(%d,%v) error(%v)", mid, stime, err)
err = nil
continue
}
if len(aids) == 0 {
continue
}
if res, miss, err = s.historyDao.Cache(c, mid, aids); err != nil {
log.Error("historyDao.Cache(%d,%v) miss:%v error(%v)", mid, aids, miss, err)
err = nil
continue
}
// * typ < model.TypeArticle all can .
if err = s.historyDao.AddMap(c, mid, res); err != nil {
log.Error("historyDao.AddMap(%d,%+v) error(%v)", mid, res, err)
err = nil
}
if err = s.historyDao.TrimCache(c, mid, limit); err != nil {
log.Error("historyDao.TrimCache(%d,%d) error(%v)", mid, limit, err)
err = nil
}
}
return
}
func (s *Service) merge(hmap map[int64]int64) {
var (
size = int64(s.conf.History.Size)
merges = make(map[int64][]*model.Merge, size)
)
for k, v := range hmap {
merges[k%size] = append(merges[k%size], &model.Merge{Mid: k, Now: v})
}
for k, v := range merges {
s.historyDao.Merge(context.TODO(), int64(k), v)
}
}
func (s *Service) bangumi(c context.Context, mid int64, epids []int64) (bangumiMap map[int64]*model.Bangumi) {
var n = 50
bangumiMap = make(map[int64]*model.Bangumi, len(epids))
for len(epids) > 0 {
if n > len(epids) {
n = len(epids)
}
epban, _ := s.historyDao.Bangumis(c, mid, epids[:n])
epids = epids[n:]
for k, v := range epban {
bangumiMap[k] = v
}
}
return
}
// ManagerHistory ManagerHistory.
// +wd:ignore
func (s *Service) ManagerHistory(c context.Context, onlyAV bool, mid int64) (his []*model.History, err error) {
var (
mhis map[string]*model.History
chis, ehis map[int64]*model.History
)
if mhis, err = s.historyDao.Map(c, mid); err != nil {
err = nil
mhis = make(map[string]*model.History)
}
chis, _ = s.historyDao.CacheMap(c, mid)
for _, v := range chis {
mhis[s.key(v.Aid, v.TP)] = v
}
if len(mhis) == 0 {
his = _emptyHis
return
}
ehis = make(map[int64]*model.History, len(mhis))
for _, h := range mhis {
if onlyAV && h.TP >= model.TypeArticle {
continue
}
if (h.TP == model.TypeBangumi || h.TP == model.TypeMovie || h.TP == model.TypePGC) && h.Sid != 0 {
if e, ok := ehis[h.Sid]; !ok || h.Unix > e.Unix {
ehis[h.Sid] = h
}
} else {
his = append(his, h)
}
}
for _, h := range ehis {
his = append(his, h)
}
sort.Sort(model.Histories(his))
return
}

View File

@@ -0,0 +1,339 @@
package service
import (
"context"
"time"
"go-common/app/interface/main/history/model"
hisapi "go-common/app/service/main/history/api/grpc"
history "go-common/app/service/main/history/model"
"go-common/library/log"
"go-common/library/net/metadata"
"github.com/pkg/errors"
)
func (s *Service) serviceRun(f func()) {
select {
case s.serviceChan <- f:
default:
log.Error("serviceChan full")
}
}
func (s *Service) serviceproc() {
for {
f := <-s.serviceChan
f()
}
}
func (s *Service) serviceAdd(arg *model.History) {
s.serviceRun(func() {
h := arg.ConvertServiceType()
arg := &hisapi.AddHistoryReq{
Mid: h.Mid,
Business: h.Business,
Kid: h.Kid,
Aid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
SubType: h.SubType,
Device: h.Device,
Progress: h.Progress,
ViewAt: h.ViewAt,
}
s.hisRPC.AddHistory(context.Background(), arg)
})
}
func (s *Service) serviceAdds(mid int64, hs []*model.History) {
s.serviceRun(func() {
arg := &hisapi.AddHistoriesReq{}
for _, a := range hs {
h := a.ConvertServiceType()
arg.Histories = append(arg.Histories, &hisapi.AddHistoryReq{
Mid: mid,
Business: h.Business,
Kid: h.Kid,
Aid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
SubType: h.SubType,
Device: h.Device,
Progress: h.Progress,
ViewAt: h.ViewAt,
})
}
s.hisRPC.AddHistories(context.Background(), arg)
})
}
func (s *Service) serviceDel(ctx context.Context, mid int64, his []*model.History) error {
arg := &hisapi.DelHistoriesReq{
Mid: mid,
Records: []*hisapi.DelHistoriesReq_Record{},
}
var aids []int64
for _, v := range his {
if v.TP == model.TypePGC || v.TP == model.TypeUGC {
aids = append(aids, v.Aid)
}
}
seasonMap := make(map[int64]*model.BangumiSeason)
if len(aids) > 0 {
seasonMap, _ = s.season(ctx, mid, aids, metadata.String(ctx, metadata.RemoteIP))
}
for _, h := range his {
if value, ok := seasonMap[h.Aid]; ok && value != nil {
arg.Records = append(arg.Records, &hisapi.DelHistoriesReq_Record{
Business: model.BusinessByTP(model.TypePGC),
ID: value.ID,
})
log.Warn("seasonMap(%d,%v)season:%d", mid, h.Aid, value.ID)
}
arg.Records = append(arg.Records, &hisapi.DelHistoriesReq_Record{
Business: model.BusinessByTP(h.TP),
ID: h.Aid,
})
}
if _, err := s.hisRPC.DelHistories(ctx, arg); err != nil {
log.Error("s.hisRPC.DelHistories(%+v %+v) err:%+v", his, arg, errors.WithStack(err))
return err
}
return nil
}
func (s *Service) serviceClear(mid int64, tps []int8) {
s.serviceRun(func() {
arg := &hisapi.ClearHistoryReq{
Mid: mid,
}
for _, t := range tps {
arg.Businesses = append(arg.Businesses, model.BusinessByTP(t))
}
s.hisRPC.ClearHistory(context.Background(), arg)
})
}
func (s *Service) serviceDels(ctx context.Context, mid int64, aids []int64, typ int8) error {
arg := &hisapi.DelHistoriesReq{
Mid: mid,
Records: []*hisapi.DelHistoriesReq_Record{},
}
seasonMap := make(map[int64]*model.BangumiSeason)
if typ == 0 {
seasonMap, _ = s.season(ctx, mid, aids, metadata.String(ctx, metadata.RemoteIP))
}
b := model.BusinessByTP(typ)
for _, aid := range aids {
if value, ok := seasonMap[aid]; ok && value != nil {
arg.Records = append(arg.Records, &hisapi.DelHistoriesReq_Record{
Business: model.BusinessByTP(model.TypePGC),
ID: value.ID,
})
log.Warn("seasonMap(%d,%v)season:%d", mid, aid, value.ID)
}
arg.Records = append(arg.Records, &hisapi.DelHistoriesReq_Record{
Business: b,
ID: aid,
})
}
if _, err := s.hisRPC.DelHistories(ctx, arg); err != nil {
log.Error("s.hisRPC.DelHistories(%v %+v) err:%+v", mid, arg, errors.WithStack(err))
return err
}
return nil
}
func (s *Service) serviceHide(mid int64, hide bool) {
s.serviceRun(func() {
arg := &hisapi.UpdateUserHideReq{
Mid: mid,
Hide: hide,
}
s.hisRPC.UpdateUserHide(context.Background(), arg)
})
}
func (s *Service) serviceHistoryCursor(c context.Context, mid int64, kid int64, businesses []string, business string, viewAt int64, ps int) ([]*model.Resource, error) {
if viewAt == 0 {
viewAt = time.Now().Unix()
}
arg := &hisapi.UserHistoriesReq{
Mid: mid,
Businesses: businesses,
Business: business,
Kid: kid,
ViewAt: viewAt,
Ps: int64(ps),
}
reply, err := s.hisRPC.UserHistories(c, arg)
if err != nil {
log.Error("s.hisRPC.UserHistories(%+v) err:%+v", arg, err)
return nil, err
}
if reply == nil {
return nil, err
}
his := make([]*model.Resource, 0)
for _, v := range reply.Histories {
tp, _ := model.CheckBusiness(v.Business)
his = append(his, &model.Resource{
Mid: v.Mid,
Oid: v.Aid,
Sid: v.Sid,
Epid: v.Epid,
TP: tp,
Business: v.Business,
STP: int8(v.SubType),
Cid: v.Cid,
DT: int8(v.Device),
Pro: int64(v.Progress),
Unix: v.ViewAt,
})
}
return his, nil
}
func (s *Service) servicePnPsCursor(c context.Context, mid int64, businesses []string, pn, ps int) ([]*model.History, []int64, error) {
if pn*ps > 1000 {
return nil, nil, nil
}
arg := &hisapi.UserHistoriesReq{
Mid: mid,
Businesses: businesses,
Ps: int64(pn * ps),
ViewAt: time.Now().Unix(),
}
reply, err := s.hisRPC.UserHistories(c, arg)
if err != nil {
log.Error("s.hisRPC.UserHistories(%+v) err:%+v", arg, err)
return nil, nil, err
}
if reply == nil {
return nil, nil, err
}
size := len(reply.Histories)
start := (pn - 1) * ps
end := start + ps - 1
switch {
case size > start && size > end:
reply.Histories = reply.Histories[start : end+1]
case size > start && size <= end:
reply.Histories = reply.Histories[start:]
default:
reply.Histories = make([]*history.History, 0)
}
var epids []int64
his := make([]*model.History, 0)
for _, v := range reply.Histories {
tp, _ := model.CheckBusiness(v.Business)
if tp == model.TypePGC {
epids = append(epids, v.Epid)
}
his = append(his, &model.History{
Mid: v.Mid,
Aid: v.Aid,
Sid: v.Sid,
Epid: v.Epid,
TP: tp,
Business: v.Business,
STP: int8(v.SubType),
Cid: v.Cid,
DT: int8(v.Device),
Pro: int64(v.Progress),
Unix: v.ViewAt,
})
}
return his, epids, nil
}
func (s *Service) servicePosition(c context.Context, mid int64, business string, kids []int64) (map[int64]*model.History, error) {
arg := &hisapi.HistoriesReq{
Mid: mid,
Business: business,
Kids: kids,
}
reply, err := s.hisRPC.Histories(c, arg)
if err != nil {
log.Error("s.hisRPC.Histories(%+v) err:%+v", arg, err)
return nil, err
}
if reply == nil {
return nil, err
}
now := time.Now().Unix() - 8*60*60
his := make(map[int64]*model.History)
for _, v := range reply.Histories {
if business == model.BusinessByTP(model.TypeUGC) && v.ViewAt < now {
continue
}
tp, _ := model.CheckBusiness(v.Business)
his[v.Aid] = &model.History{
Mid: v.Mid,
Aid: v.Aid,
Sid: v.Sid,
Epid: v.Epid,
TP: tp,
Business: v.Business,
STP: int8(v.SubType),
Cid: v.Cid,
DT: int8(v.Device),
Pro: int64(v.Progress),
Unix: v.ViewAt,
}
}
return his, nil
}
func (s *Service) serviceHistoryType(c context.Context, mid int64, business string, kids []int64) ([]*model.History, error) {
arg := &hisapi.HistoriesReq{
Mid: mid,
Business: business,
Kids: kids,
}
reply, err := s.hisRPC.Histories(c, arg)
if err != nil {
log.Error("s.hisRPC.Histories(%+v) err:%+v", arg, err)
return nil, err
}
if reply == nil {
return nil, err
}
his := make([]*model.History, 0)
for _, v := range reply.Histories {
tp, _ := model.CheckBusiness(v.Business)
his = append(his, &model.History{
Mid: v.Mid,
Aid: v.Aid,
Sid: v.Sid,
Epid: v.Epid,
TP: tp,
Business: v.Business,
STP: int8(v.SubType),
Cid: v.Cid,
DT: int8(v.Device),
Pro: int64(v.Progress),
Unix: v.ViewAt,
})
}
return his, nil
}
func (s *Service) serviceHideState(c context.Context, mid int64) (int64, error) {
arg := &hisapi.UserHideReq{
Mid: mid,
}
reply, err := s.hisRPC.UserHide(c, arg)
if err != nil {
log.Error("s.hisRPC.UserHide(%d) err:%+v", mid, err)
return 0, err
}
if !reply.Hide {
return 0, nil
}
return 1, nil
}

View File

@@ -0,0 +1,97 @@
package service
import (
"context"
"testing"
"time"
"go-common/app/interface/main/history/model"
. "github.com/smartystreets/goconvey/convey"
)
// TestService_History
func TestService_History(t *testing.T) {
var (
c = context.TODO()
mid int64 = 14771787
aid int64 = 5463823
aids = []int64{5463823}
sid int64 = 5730
cid int64 = 97791
epid int64 = 97922
pro int64 = 155
rtime int64 = 1490958549
tp int8 = 1
dt int8 = 2
pn = 1
ps = 100
now = time.Now().Unix()
h = &model.History{Aid: aid, Unix: now, Sid: sid, Epid: epid, Cid: cid, Pro: pro, TP: int8(tp), DT: int8(dt)}
)
Convey("history ", t, WithService(func(s *Service) {
Convey("history AddHistory ", func() {
err := s.AddHistory(c, mid, rtime, h)
So(err, ShouldBeNil)
})
Convey("history Progress", func() {
_, err := s.Progress(c, mid, aids)
So(err, ShouldBeNil)
})
Convey("history DelHistory", func() {
err := s.DelHistory(c, mid, aids, 3)
So(err, ShouldBeNil)
})
Convey("history ClearHistory", func() {
err := s.ClearHistory(c, mid, []int8{3})
So(err, ShouldBeNil)
})
Convey("history Videos", func() {
_, err := s.Videos(c, mid, pn, ps, 3)
So(err, ShouldBeNil)
})
Convey("history AVHistories", func() {
_, err := s.AVHistories(c, mid)
So(err, ShouldBeNil)
})
Convey("history Histories", func() {
_, err := s.Histories(c, mid, 1, 2, 3)
So(err, ShouldBeNil)
})
Convey("history SetShadow", func() {
err := s.SetShadow(c, mid, 1)
So(err, ShouldBeNil)
})
Convey("history Shadow", func() {
_, err := s.Shadow(c, mid)
So(err, ShouldBeNil)
})
Convey("history Manager", func() {
_, err := s.ManagerHistory(c, false, mid)
So(err, ShouldBeNil)
})
}))
}
func TestService_AddHistory(t *testing.T) {
var (
c = context.TODO()
mid int64 = 14771787
aid int64 = 5463823
sid int64 = 5730
cid int64 = 97791
epid int64 = 97922
pro int64 = 155
rtime int64 = 1490958549
tp int8 = 1
dt int8 = 2
now = time.Now().Unix()
h = &model.History{Aid: aid, Unix: now, Sid: sid, Epid: epid, Cid: cid, Pro: pro, TP: int8(tp), DT: int8(dt)}
)
Convey("history ", t, WithService(func(s *Service) {
Convey("history AddHistory ", func() {
err := s.AddHistory(c, mid, rtime, h)
So(err, ShouldBeNil)
})
}))
}

View File

@@ -0,0 +1,36 @@
package service
import (
"context"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/log"
"go-common/library/net/metadata"
)
// favoriteds return aids is favs.
func (s *Service) favoriteds(c context.Context, mid int64, aids []int64) (res map[int64]bool) {
var n = 50
res = make(map[int64]bool, len(aids))
for len(aids) > 0 {
if n > len(aids) {
n = len(aids)
}
arg := &favmdl.ArgIsFavs{
Type: favmdl.TypeVideo,
Mid: mid,
Oids: aids[:n],
RealIP: metadata.String(c, metadata.RemoteIP),
}
favMap, err := s.favRPC.IsFavs(c, arg)
if err != nil {
log.Error("s.favRPC.IsFavs(%v) error(%v)", arg, err)
return
}
aids = aids[n:]
for k, v := range favMap {
res[k] = v
}
}
return
}

View File

@@ -0,0 +1,239 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/interface/main/history/conf"
"go-common/app/interface/main/history/dao/history"
"go-common/app/interface/main/history/dao/toview"
"go-common/app/interface/main/history/model"
arcrpc "go-common/app/service/main/archive/api/gorpc"
favrpc "go-common/app/service/main/favorite/api/gorpc"
hisrpc "go-common/app/service/main/history/api/grpc"
"go-common/library/log"
"go-common/library/queue/databus/report"
"go-common/library/sync/pipeline/fanout"
)
type playPro struct {
Type int8 `json:"type"`
SubType int8 `json:"sub_type"`
Mid int64 `json:"mid"`
Sid int64 `json:"sid"`
Epid int64 `json:"epid"`
Cid int64 `json:"cid"`
Progress int64 `json:"progress"`
IP string `json:"ip"`
Ts int64 `json:"ts"`
RealTime int64 `json:"realtime"`
}
// Service is history service.
type Service struct {
conf *conf.Config
historyDao *history.Dao
toviewDao *toview.Dao
delChan *fanout.Fanout
mergeChan chan *model.Merge
msgs chan *playPro
proChan chan *model.History
serviceChan chan func()
favRPC *favrpc.Service
arcRPC *arcrpc.Service2
hisRPC hisrpc.HistoryClient
cache *fanout.Fanout
toviewCache *fanout.Fanout
midMap map[int64]bool
}
// New new a History service.
func New(c *conf.Config) (s *Service) {
s = &Service{
conf: c,
historyDao: history.New(c),
toviewDao: toview.New(c),
mergeChan: make(chan *model.Merge, 1024),
msgs: make(chan *playPro, 1024),
proChan: make(chan *model.History, 1024),
serviceChan: make(chan func(), 10240),
delChan: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
arcRPC: arcrpc.New2(c.RPCClient2.Archive),
favRPC: favrpc.New2(c.RPCClient2.Favorite),
cache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
toviewCache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
midMap: make(map[int64]bool),
}
for _, v := range s.conf.History.Mids {
s.midMap[v] = true
}
var err error
if s.hisRPC, err = hisrpc.NewClient(c.RPCClient2.History); err != nil {
panic(err)
}
go s.playProproc()
go s.mergeproc()
go s.proPubproc()
for i := 0; i < s.conf.History.ConsumeSize; i++ {
go s.serviceproc()
}
return
}
func (s *Service) addMerge(mid, now int64) {
select {
case s.mergeChan <- &model.Merge{Mid: mid, Now: now}:
default:
log.Warn("mergeChan chan is full")
}
}
func (s *Service) addPlayPro(p *playPro) {
select {
case s.msgs <- p:
default:
log.Warn("s.msgs chan is full")
}
}
func (s *Service) addProPub(p *model.History) {
select {
case s.proChan <- p:
default:
log.Warn("s.proChan chan is full")
}
}
func (s *Service) mergeproc() {
var (
m *model.Merge
ticker = time.NewTicker(time.Duration(s.conf.History.Ticker))
mergeMap = make(map[int64]int64)
)
for {
select {
case m = <-s.mergeChan:
if m == nil {
s.merge(mergeMap)
return
}
if _, ok := mergeMap[m.Mid]; !ok {
mergeMap[m.Mid] = m.Now
}
if len(mergeMap) < s.conf.History.Page {
continue
}
case <-ticker.C:
}
s.merge(mergeMap)
mergeMap = make(map[int64]int64)
}
}
// playProproc send history to databus.
func (s *Service) playProproc() {
var (
msg *playPro
ms []*playPro
ticker = time.NewTicker(time.Second)
)
for {
select {
case msg = <-s.msgs:
if msg == nil {
if len(ms) > 0 {
s.pushPlayPro(ms)
}
return
}
ms = append(ms, msg)
if len(ms) < 100 {
continue
}
case <-ticker.C:
}
if len(ms) == 0 {
continue
}
s.pushPlayPro(ms)
ms = make([]*playPro, 0, 100)
}
}
func (s *Service) pushPlayPro(ms []*playPro) {
key := fmt.Sprintf("%d%d", ms[0].Mid, ms[0].Sid)
for j := 0; j < 3; j++ {
if err := s.historyDao.PlayPro(context.Background(), key, ms); err == nil {
return
}
}
}
// proPubroc send history to databus.
func (s *Service) proPubproc() {
for {
msg := <-s.proChan
if msg == nil {
return
}
s.proPub(msg)
}
}
func (s *Service) proPub(msg *model.History) {
key := fmt.Sprintf("%d%d", msg.Mid, msg.Aid)
for j := 0; j < 3; j++ {
if err := s.historyDao.ProPub(context.Background(), key, msg); err == nil {
break
}
}
}
func (s *Service) userActionLog(mid int64, action string) {
report.User(&report.UserInfo{
Mid: mid,
Business: model.HistoryLog,
Action: action,
Ctime: time.Now(),
})
}
func (s *Service) migration(mid int64) bool {
if !s.conf.History.Migration || mid == 0 {
return false
}
if _, ok := s.midMap[mid]; ok {
return true
}
if s.conf.History.Rate != 0 && mid%s.conf.History.Rate == 0 {
return true
}
return false
}
// Ping ping service.
// +wd:ignore
func (s *Service) Ping(c context.Context) (err error) {
if s.historyDao != nil {
err = s.historyDao.Ping(c)
}
if s.toviewDao != nil {
err = s.toviewDao.Ping(c)
}
return
}
// Close close resource.
// +wd:ignore
func (s *Service) Close() {
s.mergeChan <- nil
s.msgs <- nil
s.proChan <- nil
if s.historyDao != nil {
s.historyDao.Close()
}
if s.toviewDao != nil {
s.toviewDao.Close()
}
}

View File

@@ -0,0 +1,37 @@
package service
import (
"context"
"flag"
"path/filepath"
"time"
"go-common/app/interface/main/history/conf"
"go-common/library/cache/redis"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func CleanCache() {
c := context.TODO()
pool := redis.NewPool(conf.Conf.Redis.Config)
pool.Get(c).Do("FLUSHDB")
}
func WithService(f func(s *Service)) func() {
return func() {
Reset(func() { CleanCache() })
f(s)
}
}
func init() {
dir, _ := filepath.Abs("../cmd/history-interface-test.toml")
flag.Set("conf", dir)
conf.Init()
s = New(conf.Conf)
time.Sleep(time.Second)
}

View File

@@ -0,0 +1,464 @@
package service
import (
"context"
"sort"
"time"
"go-common/app/interface/main/history/model"
arcmdl "go-common/app/service/main/archive/model/archive"
"go-common/library/ecode"
"go-common/library/log"
)
const maxToView = 100
var (
_empToView = []*model.ToView{}
_empArcToView = []*model.ArcToView{}
_empWebArcToView = []*model.WebArcToView{}
)
// AddToView add the user a toview item.
// +wd:ignore
func (s *Service) AddToView(c context.Context, mid, aid int64, ip string) (err error) {
var (
ok bool
count int
arc *arcmdl.View3
now = time.Now().Unix()
)
arcAid := &arcmdl.ArgAid2{Aid: aid}
if arc, err = s.arcRPC.View3(c, arcAid); err != nil {
return
}
if arc == nil {
return
}
if arc.Rights.UGCPay == 1 {
return ecode.ToViewPayUGC
}
if ok, err = s.toviewDao.Expire(c, mid); err != nil {
return
}
if ok {
if count, err = s.toviewDao.CntCache(c, mid); err != nil {
return
}
} else {
if _, count, err = s.toView(c, mid, 1, maxToView, ip); err != nil {
return
}
}
if count >= maxToView {
err = ecode.ToViewOverMax
return
}
if err = s.toviewDao.Add(c, mid, aid, now); err != nil {
return
}
s.toviewCache.Do(c, func(ctx context.Context) {
if errCache := s.toviewDao.AddCache(ctx, mid, aid, now); errCache != nil {
log.Warn("s.toviewDao.AddCache(%d,%d,%d) err:%v", mid, aid, now, errCache)
}
})
return
}
// AddMultiToView add toview items to user.
// +wd:ignore
func (s *Service) AddMultiToView(c context.Context, mid int64, aids []int64, ip string) (err error) {
var (
ok bool
count int
expectedAids []int64
arcs map[int64]*arcmdl.View3
viewMap map[int64]*model.ToView
views []*model.ToView
now = time.Now().Unix()
)
arcAids := &arcmdl.ArgAids2{Aids: aids}
if arcs, err = s.arcRPC.Views3(c, arcAids); err != nil {
return
} else if len(arcs) == 0 {
return
}
for _, v := range arcs {
if v.Rights.UGCPay == 1 {
return ecode.ToViewPayUGC
}
}
if ok, err = s.toviewDao.Expire(c, mid); err != nil {
return
}
if ok {
if viewMap, err = s.toviewDao.CacheMap(c, mid); err != nil {
return
}
} else {
var list []*model.ToView
list, _, err = s.toView(c, mid, 1, maxToView, ip)
if err != nil {
return
}
viewMap = make(map[int64]*model.ToView)
for _, v := range list {
if v == nil {
continue
}
viewMap[v.Aid] = v
}
}
count = len(viewMap)
if count >= maxToView {
err = ecode.ToViewOverMax
return
}
expectedLen := maxToView - count
for _, aid := range aids {
if _, exist := viewMap[aid]; !exist {
expectedAids = append(expectedAids, aid)
expectedLen--
if expectedLen == 0 {
break
}
}
}
if err = s.toviewDao.Adds(c, mid, expectedAids, now); err != nil {
return
}
if ok {
s.toviewCache.Do(c, func(ctx context.Context) {
for _, aid := range expectedAids {
views = append(views, &model.ToView{Aid: aid, Unix: now})
}
if errCache := s.toviewDao.AddCacheList(ctx, mid, views); errCache != nil {
log.Warn("s.toviewDao.AddCacheList(%d,%v) err:%v", mid, views, errCache)
}
})
}
return
}
// RemainingToView add toview items to user.
// +wd:ignore
func (s *Service) RemainingToView(c context.Context, mid int64, ip string) (remaining int, err error) {
var (
count int
)
if _, count, err = s.toView(c, mid, 1, maxToView, ip); err != nil {
return
}
remaining = maxToView - count
return
}
// ClearToView clear the user toview items.
// +wd:ignore
func (s *Service) ClearToView(c context.Context, mid int64) (err error) {
if err = s.toviewDao.ClearCache(c, mid); err != nil {
return
}
s.userActionLog(mid, model.ToviewClear)
return s.toviewDao.Clear(c, mid)
}
// DelToView delete the user to videos.
// +wd:ignore
func (s *Service) DelToView(c context.Context, mid int64, aids []int64, viewed bool, ip string) (err error) {
var (
delAids []int64
list []*model.ToView
rhs, hs map[int64]*model.History
rs *model.History
)
// viewed del all viewed
if viewed {
if list, _, err = s.toView(c, mid, 1, maxToView, ip); err != nil {
return
}
for _, l := range list {
aids = append(aids, l.Aid)
}
if len(aids) == 0 {
return
}
if hs, err = s.historyDao.AidsMap(c, mid, aids); err != nil {
return
}
rhs, _ = s.historyDao.CacheMap(c, mid)
for _, rs = range rhs {
hs[rs.Aid] = rs
}
for k, v := range hs {
if v.Pro >= 30 || v.Pro == -1 {
delAids = append(delAids, k)
}
}
if len(delAids) == 0 {
return
}
if err = s.toviewDao.Del(c, mid, delAids); err != nil {
return
}
s.toviewCache.Do(c, func(ctx context.Context) {
s.toviewDao.DelCaches(ctx, mid, delAids)
})
return
}
if err = s.toviewDao.Del(c, mid, aids); err != nil {
return
}
s.toviewCache.Do(c, func(ctx context.Context) {
s.toviewDao.DelCaches(ctx, mid, aids)
})
return
}
// WebToView get videos of user view history.
// +wd:ignore
func (s *Service) WebToView(c context.Context, mid int64, pn, ps int, ip string) (res []*model.WebArcToView, count int, err error) {
var (
ok bool
aids, epids []int64
avs map[int64]*arcmdl.View3
views []*model.ToView
v *model.ToView
hm map[int64]*model.History
av *arcmdl.View3
epban = make(map[int64]*model.Bangumi)
seasonMap = make(map[int64]*model.BangumiSeason)
)
if views, count, err = s.toView(c, mid, pn, ps, ip); err != nil {
return
}
if len(views) == 0 {
return
}
for _, v = range views {
if v != nil {
aids = append(aids, v.Aid)
}
}
argAids := &arcmdl.ArgAids2{Aids: aids}
if avs, err = s.arcRPC.Views3(c, argAids); err != nil {
log.Error("s.arcRPC.Views3(arcAids:(%v), arcs) error(%v)", aids, err)
return
}
if len(avs) == 0 {
return
}
seasonMap, epids = s.season(c, mid, aids, ip)
// bangumi info
if len(epids) > 0 {
epban = s.bangumi(c, mid, epids)
}
if hm, err = s.toViewPro(c, mid, aids); err != nil {
err = nil
}
res = make([]*model.WebArcToView, 0, len(aids))
for _, v = range views {
if v == nil {
count--
continue
}
// NOTE compat android
if av, ok = avs[v.Aid]; !ok || av == nil {
count--
continue
}
// NOTE all no pay
av.Rights.Movie = 0
at := &model.WebArcToView{View3: av}
at.AddTime = v.Unix
if hm[v.Aid] != nil {
at.Cid = hm[v.Aid].Cid
at.Progress = hm[v.Aid].Pro
}
if season, ok := seasonMap[v.Aid]; ok && season != nil {
if bangumi, ok := epban[season.Epid]; ok && bangumi != nil {
at.BangumiInfo = bangumi
}
}
res = append(res, at)
}
if len(res) == 0 {
res = _empWebArcToView
}
return
}
// ToView get videos of user view history.
// +wd:ignore
func (s *Service) ToView(c context.Context, mid int64, pn, ps int, ip string) (res []*model.ArcToView, count int, err error) {
var (
ok bool
aids []int64
avs map[int64]*arcmdl.View3
views []*model.ToView
v *model.ToView
hm map[int64]*model.History
av *arcmdl.View3
)
res = _empArcToView
if views, count, err = s.toView(c, mid, pn, ps, ip); err != nil {
return
}
if len(views) == 0 {
return
}
for _, v = range views {
if v != nil {
aids = append(aids, v.Aid)
}
}
argAids := &arcmdl.ArgAids2{Aids: aids}
if avs, err = s.arcRPC.Views3(c, argAids); err != nil {
log.Error("s.arcRPC.Views3(%v) error(%v)", aids, err)
return
}
if len(avs) == 0 {
return
}
if hm, err = s.toViewPro(c, mid, aids); err != nil {
err = nil
}
res = make([]*model.ArcToView, 0, len(aids))
for _, v = range views {
if v == nil {
count--
continue
}
// NOTE compat android
if av, ok = avs[v.Aid]; !ok || av.Archive3 == nil {
count--
continue
}
// NOTE all no pay
av.Rights.Movie = 0
at := &model.ArcToView{
Archive3: av.Archive3,
Count: len(av.Pages),
}
at.AddTime = v.Unix
// get cid and progress
if hm[v.Aid] != nil {
at.Cid = hm[v.Aid].Cid
at.Progress = hm[v.Aid].Pro
}
for n, p := range av.Pages {
if p.Cid == at.Cid {
p.Page = int32(n + 1)
at.Page = p
break
}
}
res = append(res, at)
}
return
}
// toView return ToSee of After the paging data.
func (s *Service) toView(c context.Context, mid int64, pn, ps int, ip string) (res []*model.ToView, count int, err error) {
var (
ok bool
start = (pn - 1) * ps
end = start + ps - 1
)
if ok, err = s.toviewDao.Expire(c, mid); err != nil {
return
}
if ok {
if end > maxToView {
end = maxToView
}
if res, err = s.toviewDao.Cache(c, mid, start, end); err != nil {
return
}
count, err = s.toviewDao.CntCache(c, mid)
if count > maxToView {
count = maxToView
}
return
}
var views []*model.ToView
var viewMap = make(map[int64]*model.ToView)
if viewMap, err = s.toviewDao.MapInfo(c, mid, nil); err != nil {
return
}
if len(viewMap) == 0 {
res = _empToView
return
}
for _, v := range viewMap {
views = append(views, v)
}
sort.Sort(model.ToViews(views))
if count = len(views); count > maxToView {
count = maxToView
views = views[:count]
}
switch {
case count > start && count > end:
res = views[start : end+1]
case count > start && count <= end:
res = views[start:]
default:
res = _empToView
}
s.toviewCache.Do(c, func(ctx context.Context) {
if errCache := s.toviewDao.AddCacheList(ctx, mid, views); errCache != nil {
log.Warn("s.toviewDao.AddCacheList(%d,%v) err:%v", mid, views, errCache)
}
})
return
}
func (s *Service) toViewPro(c context.Context, mid int64, aids []int64) (res map[int64]*model.History, err error) {
var (
miss []int64
hm map[int64]*model.History
)
if res, miss, err = s.historyDao.Cache(c, mid, aids); err != nil {
err = nil
} else if len(res) == len(aids) {
return
}
if len(res) == 0 {
res = make(map[int64]*model.History)
miss = aids
}
if hm, err = s.historyDao.AidsMap(c, mid, miss); err != nil {
err = nil
}
for k, v := range hm {
res[k] = v
}
return
}
func (s *Service) season(c context.Context, mid int64, aids []int64, ip string) (seasonMap map[int64]*model.BangumiSeason, epids []int64) {
var (
n = 50
seasonM = make(map[int64]*model.BangumiSeason, n)
)
seasonMap = make(map[int64]*model.BangumiSeason, n)
for len(aids) > 0 {
if n > len(aids) {
n = len(aids)
}
seasonM, _ = s.historyDao.BangumisByAids(c, mid, aids[:n], ip)
aids = aids[n:]
for k, v := range seasonM {
epids = append(epids, v.Epid)
seasonMap[k] = v
}
}
return
}
// ManagerToView manager get mid toview list.
// +wd:ignore
func (s *Service) ManagerToView(c context.Context, mid int64, ip string) ([]*model.ToView, error) {
return s.toviewDao.ListInfo(c, mid, nil)
}

View File

@@ -0,0 +1,46 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
// TestService_AddHistory
func TestService_Toview(t *testing.T) {
var (
c = context.TODO()
mid int64 = 27515316
aids = []int64{27515316, 27515316}
ip = ""
pn = 1
ps = 10000
)
Convey("toview ", t, WithService(func(s *Service) {
Convey("toview AddMultiToView", func() {
err := s.AddMultiToView(c, mid, aids, ip)
So(err, ShouldBeNil)
})
Convey("toview RemainingToView", func() {
_, err := s.RemainingToView(c, mid, "")
So(err, ShouldBeNil)
})
Convey("toview ClearToView", func() {
err := s.ClearToView(c, mid)
So(err, ShouldBeNil)
})
Convey("toview DelToView", func() {
err := s.DelToView(c, mid, aids, true, "")
So(err, ShouldBeNil)
})
Convey("toview cache del", func() {
_, _, err := s.ToView(c, mid, pn, ps, ip)
So(err, ShouldBeNil)
})
Convey("toview Manager", func() {
_, err := s.ManagerToView(c, mid, ip)
So(err, ShouldBeNil)
})
}))
}