Create & Init Project...

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

View File

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

View File

@@ -0,0 +1,258 @@
#### 收藏夹job
### Version v7.4.1
> 1. fix mv bug
### Version v7.4.1
> 1. fix copy init bug
### Version v7.4.0
> 1. 播单2.0
### Version v7.3.4
> 1. copy move multiDel clean操作创建收藏列表缓存
### Version v7.3.3
> 1. 修改mysql index force
### Version v7.3.0
> 1. 移除无用代码 binlogDao, videoDao
### Version v7.2.1
> 1. 播单播放数防刷
### Version v7.1.0
> 1. 删除videoDao
> 2. 下线binlog
### Version v7.0.2
> 1. change max seq
### Version v7.0.0
> 1. medialist
##### Version 6.0.1
> 1. 播单计数
##### Version 6.0.0
> 1. 停止双写老表
##### Version 4.8.9
> 1. 拜年祭平台收藏聚合支持
##### Version 4.8.8
> 1. 拜年祭aid收藏聚合支持
##### Version 4.8.7
> 1. fix rows err
##### Version 4.8.6
> 1. del recentfavs mc
##### Version 4.8.5
> 1. fix relations slow sql
### Version 4.8.1
> 1. add db rows.error
##### Version 4.7.21
> 1. common config
##### Version 4.7.19
> 1. rm video folder redis
##### Version 4.7.18
> 1. add batch oids mc
##### Version 4.7.17
> 1. rm databus stat-t
##### Version 4.7.16
> 1. migrate bm
##### Version 4.7.15
> 1. set oid count mc
##### Version 4.7.14
> 1. fix index ix_fid_state_mtime
##### Version 4.7.13
> 1. add read mysql
##### Version 4.7.12
> 1. enhance binlog cache
##### Version 4.7.11
> 1.fix binlog cache
##### Version 4.7.9
> 1.del cover support migration
##### Version 4.7.8
> 1.add relation binlog to update stat and add user
##### Version 4.7.6
> 1.rm cache err return
##### Version 4.7.5
> 1.account v7
##### Version 4.7.4
> 1.move to main folder
> 1.log error level fix
##### Version 4.7.3
> 1.fix delFolderSQL state
##### Version 4.7.2
> 1.fix replace sql
##### Version 4.7.1
> 1.fix binlog UpdateRelationSQL
##### Version 4.7.0
> 1.migrate archive data by binlog
##### Version 4.6.8
> 1.add dao fav unit test
##### Version 4.6.7
> 1.fix cache save context
##### Version 4.6.6
> 1.fix is faved
##### Version 4.6.5
> 1.fav bit redis key mid reverse
##### Version 4.6.4
> 1.fix count -1 sql err
##### Version 4.6.3
> 1.add batch job sleeptime
##### Version 4.6.2
> 1.new stats databus
##### Version 4.6.0
> 1.platform api support
##### Version 4.5.3
> 1.fix mc folderkey
##### Version 4.5.2
> 1.fix folder nil
##### Version 4.5.1
> 1.fix 播单count更新
##### Version 4.5.0
> 1.收藏夹信息存储换用mc
##### Version 4.4.1
> 1.add redis expire
##### Version 4.4.0
> 1.support playlist
##### Version 4.3.6
> 1.archiveRPC archive3
##### Version 4.3.5
> 1.databus stats
##### Version 4.3.3
> 1.aidsByFid sql force index
##### Version 4.3.2
> 1.fix arc stats
##### Version 4.3.1
> 1.in条件语句去掉prepare
##### Version 4.3.0
> 1.一键清理失效视频
##### Version 4.2.4
> 1.fix article map nil
##### Version 4.2.3
> 1.fix mid negative number
##### Version 4.2.0
> 1.迁移大仓库
##### Version 4.1.1
> 1.fix chan close
##### Version 4.1.0
> 1.job kafka改用databus
##### Version 4.0.3
> 1.200收藏加1硬币
#### Version 4.0.2
> 1.fix 收藏计数
##### Version 4.0.1
> 1.用户收藏标志缓存写入
##### Version 4.0.0
> 1.收藏平台化
##### Version 3.3.5
> 1.修复aid计数
##### Version 3.3.4
> 1.修复多chan处理mid
##### Version 3.3.3
> 1.修复收藏计数bug
##### Version 3.3.2
> 1.去掉计数相关hbase,dedeDB,kafka
##### Version 3.3.1
> 1.monitor ping
##### Version 3.3.0
> 1.升级vendor
##### Version 3.2.6
> 1.修复bug:收藏视频批量移动后缓存不更新
##### Version 3.2.5
> 1.redis bit添加用户是否有收藏缓存
##### Version 3.2.4
> 1.添加默认收藏夹缓存
##### Version 3.2.3
> 1.升级vendor
##### Version 3.2.2
> 1.收藏计数双写到databus
##### Version 3.2.1
> 1.修复删除收藏夹收藏视屏缓存错误
##### Version 3.2.0
> 1.修复hbase计数
> 2.收藏夹维护aid全部收藏数
##### Version 3.1.0
> 1.获取视屏对应的所有收藏夹
##### Version 3.0.0
> 1.添加govendor支持
> 2.修复缓存expire
##### Version 2.0.2
> 1.修复删除收藏硬币扣除
##### Version 2.0.1
> 1.添加job监控
> 2.增加更新老稿件表收藏数
##### Version 2.0.0
> 1.收藏夹job重构

View File

@@ -0,0 +1,14 @@
# Owner
caoguoliang
zhapuyu
# Author
libingqi
chenzhihui
zhangshengchao
caoguoliang
# Reviewer
caoguoliang
chenzhihui
zhangshengchao

View File

@@ -0,0 +1,19 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- caoguoliang
- chenzhihui
- libingqi
- zhangshengchao
- zhapuyu
labels:
- job
- job/main/favorite
- main
options:
no_parent_owners: true
reviewers:
- caoguoliang
- chenzhihui
- libingqi
- zhangshengchao

View File

@@ -0,0 +1,11 @@
#### favorite job
##### 项目简介
> 1.消费favorite产生databus消息进行收藏计数等异步操作
##### 编译环境
> 请只用golang v1.7.x以上版本编译执行。
##### 依赖包
> 1.公共包go-common

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["favorite-job-test.toml"],
importpath = "go-common/app/job/main/favorite/cmd",
tags = ["automanaged"],
deps = [
"//app/job/main/favorite/conf:go_default_library",
"//app/job/main/favorite/http:go_default_library",
"//app/job/main/favorite/service:go_default_library",
"//library/log:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,188 @@
# This is a TOML document. Boom.
[bm]
addr = "0.0.0.0:6016"
timeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
[fav]
maxPageSize = 50
cleanCDTime = "10s"
sleepTime = "200ms"
greyMod = 2126
whiteMids = [48222126,88888894]
[statMerge]
business = 2
target = 1
sources = [22,33]
[db]
[db.fav]
addr = "[favorite]tcp@172.16.33.205:3308"
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_fav?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 20
idle = 10
idleTimeout ="4h"
queryTimeout = "2s"
execTimeout = "2s"
tranTimeout = "2s"
[db.fav.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[db.read]
addr = "[favorite]tcp@172.16.33.205:3308"
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_fav?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 20
idle = 10
idleTimeout ="4h"
queryTimeout = "2s"
execTimeout = "2s"
tranTimeout = "2s"
[db.read.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[redis]
name = "favorite-job/favorite"
proto = "tcp"
addr = "172.18.33.61:6808"
idle = 100
active = 100
dialTimeout = "500ms"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
expire = "10m"
ipExpire = "60s"
buvidExpire = "5m"
[memcache]
name = "favorite"
proto = "tcp"
addr = "172.18.33.61:11231"
idle = 10
active = 10
dialTimeout = "2s"
readTimeout = "2s"
writeTimeout = "2s"
idleTimeout = "7h"
expire = "15m"
[binlogDatabus]
key = "170e302355453683"
secret = "3d0e8db7bed0503949e545a469789279"
group = "Favorite-MainCommunity-S"
topic = "Favorite-T"
action = "sub"
name = "favorite-job/binlog"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 1
active = 1
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[statFavDatabus]
key = "170e302355453683"
secret= "51b66df3ebeca2b08f0017f350b6f0ce"
group= "StatFav-MainCommunity-P"
topic= "StatFav-T"
action="pub"
name = "favorite-service/stat"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[statFavDatabus.consumers]
"article"=1
"archive"=2
"playlist"=6
[jobDatabus]
key = "0QEO9F8JuuIxZzNDvklH"
secret= "0QEO9F8JuuIxZzNDvklI"
group= "FavoriteJob-UGC-S"
topic= "FavoriteJob-T"
action="sub"
name = "favorite/job"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[favStatDatabus]
key = "170e302355453683"
secret = "51b66df3ebeca2b08f0017f350b6f0ce"
group = "StatFav-MainCommunity-S"
topic = "StatFav-T"
action = "sub"
name = "favorite-job/favStat"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 1
active = 1
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[shareStatDatabus]
key = "170e302355453683"
secret = "51b66df3ebeca2b08f0017f350b6f0ce"
group = "StatShare-MainCommunity-S"
topic = "StatShare-T"
action = "sub"
name = "favorite-job/shareStat"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 1
active = 1
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[mediaListCntDatabus]
key = "170e302355453683"
secret = "51b66df3ebeca2b08f0017f350b6f0ce"
group = "MediaListCnt-MainCommunity-S"
topic = "MediaListCnt-T"
action = "sub"
name = "favorite-job/playStat"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 1
active = 1
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[rpcClient2]
[rpcClient2.coin]
timeout = "1s"
[rpcClient2.archive]
timeout = "1s"
[rpcClient2.article]
timeout = "1s"

View File

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

View File

@@ -0,0 +1,39 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/job/main/favorite/conf",
tags = ["automanaged"],
deps = [
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/rpc: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,145 @@
package conf
import (
"flag"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/rpc"
"go-common/library/queue/databus"
"go-common/library/time"
"github.com/BurntSushi/toml"
)
var (
confPath string
// Conf .
Conf *Config
)
// Config .
type Config struct {
// base
// fav
Fav *Fav
// log
Log *log.Config
// db
DB *DB
// memcache
Memcache *Memcache
// redis
Redis *Redis
// stat fav databus
StatFavDatabus *StatFavDatabus
// job databus
JobDatabus *databus.Config
// BinlogDatabus databus.
BinlogDatabus *databus.Config
// rpc client
RPCClient2 *RPC
// BM blademaster
BM *bm.ServerConfig
// StatMerge for bnj
StatMerge *StatMerge
// playlist stat
FavStatDatabus *databus.Config
ShareStatDatabus *databus.Config
MediaListCntDatabus *databus.Config
// http client
HTTPClient *bm.ClientConfig
}
// Fav favorite
type Fav struct {
Proc int64
MaxPageSize int
CleanCDTime time.Duration
SleepTime time.Duration
GreyMod int64
WhiteMids []int64
}
// StatMerge .
type StatMerge struct {
Business int
Target int64
Sources []int64
}
// RPC rpc cliens.
type RPC struct {
Archive *rpc.ClientConfig
Article *rpc.ClientConfig
Coin *rpc.ClientConfig
}
// DB mysql.
type DB struct {
// favorite db
Fav *sql.Config
Read *sql.Config
}
// Redis redis conf.
type Redis struct {
*redis.Config
Expire time.Duration
IPExpire time.Duration
BuvidExpire time.Duration
}
// Memcache mc conf.
type Memcache struct {
*memcache.Config
Expire time.Duration
}
// StatsDatabus stats.
type StatsDatabus struct {
*databus.Config
Field string
Type int8
}
// StatFavDatabus new stats.
type StatFavDatabus struct {
*databus.Config
Consumers map[string]int8
}
// init
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
}
// configCenter remote config.
func configCenter() (err error) {
var (
ok bool
value string
client *conf.Client
)
if client, err = conf.New(); err != nil {
return
}
if value, ok = client.Toml2(); !ok {
panic(err)
}
_, err = toml.Decode(value, &Conf)
return
}

View File

@@ -0,0 +1,63 @@
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",
"memcache_test.go",
"mysql_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/favorite/conf:go_default_library",
"//app/service/main/favorite/model:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"memcache.go",
"mysql.go",
"redis.go",
],
importpath = "go-common/app/job/main/favorite/dao/fav",
tags = ["automanaged"],
deps = [
"//app/job/main/favorite/conf:go_default_library",
"//app/job/main/favorite/model:go_default_library",
"//app/service/main/favorite/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,70 @@
package fav
import (
"context"
"time"
"go-common/app/job/main/favorite/conf"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/database/sql"
"go-common/library/log"
)
// Dao favorite dao.
type Dao struct {
db *sql.DB
redis *redis.Pool
mc *memcache.Pool
redisExpire int
mcExpire int32
}
// New new a dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
db: sql.NewMySQL(c.DB.Fav),
// redis
redis: redis.NewPool(c.Redis.Config),
redisExpire: int(time.Duration(c.Redis.Expire) / time.Second),
// memcache
mc: memcache.NewPool(c.Memcache.Config),
mcExpire: int32(time.Duration(c.Memcache.Expire) / time.Second),
}
return
}
// Close close all connection.
func (d *Dao) Close() (err error) {
if d.db != nil {
d.db.Close()
}
if d.redis != nil {
d.redis.Close()
}
if d.mc != nil {
d.mc.Close()
}
return
}
// Ping ping all resource.
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.pingRedis(c); err != nil {
log.Error("d.pingRedis error(%v)", err)
return
}
if err = d.pingMC(c); err != nil {
log.Error("d.pingMC error(%v)", err)
return
}
if err = d.pingMySQL(c); err != nil {
log.Error("d.pingMySQL error(%v)", err)
}
return
}
// BeginTran crate a *sql.Tx for database transaction.
func (d *Dao) BeginTran(c context.Context) (*sql.Tx, error) {
return d.db.Begin(c)
}

View File

@@ -0,0 +1,34 @@
package fav
import (
"flag"
"go-common/app/job/main/favorite/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.favorite-job")
flag.Set("conf_token", "29adace99d5c5be327ae4a6b70c6582d")
flag.Set("tree_id", "2295")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../../cmd/favorite-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,216 @@
package fav
import (
"context"
"fmt"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/cache/memcache"
"go-common/library/log"
"strconv"
)
const (
_folder = "f_%d_%d" // key:f_{mid%100}_{fid},value:{*Fodler}.pb
_relationFids = "rof_%d_%d_%d" // key:rof_{type}_{mid}_{oid},value:{[]int64}.pb
_oidCount = "oc_%d_%d" // key:oc_{type}_{oid},value:int64
_batchOids = "bo_%d_%d" // key:oc_{type}_{mid},value:{[]int64}.pb
_recentOids = "rcto_%d_%d" // key:rcto_{type}_{mid},value:{[]int64}.pb
_recentRes = "rctr_%d_%d" // key:rcto_{type}_{mid},value:{[]*Resource}.pb
)
// folderMcKey
func folderMcKey(mid, fid int64) string {
return fmt.Sprintf(_folder, mid%100, fid)
}
// relationFidsKey
func relationFidsKey(typ int8, mid, oid int64) string {
return fmt.Sprintf(_relationFids, typ, mid, oid)
}
func oidCountKey(typ int8, oid int64) string {
return fmt.Sprintf(_oidCount, typ, oid)
}
func batchOidsKey(typ int8, mid int64) string {
return fmt.Sprintf(_batchOids, typ, mid)
}
func recentOidsKey(typ int8, mid int64) string {
return fmt.Sprintf(_recentOids, typ, mid)
}
func recentResKey(typ int8, mid int64) string {
return fmt.Sprintf(_recentRes, typ, mid)
}
// pingMC ping mc is ok.
func (d *Dao) pingMC(c context.Context) error {
conn := d.mc.Get(c)
defer conn.Close()
item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: d.mcExpire}
return conn.Set(&item)
}
// SetFoldersMc add folders mc cache.
func (d *Dao) SetFoldersMc(c context.Context, vs ...*favmdl.Folder) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
for _, v := range vs {
if v == nil {
continue
}
item := &memcache.Item{Key: folderMcKey(v.Mid, v.ID), Object: v, Flags: memcache.FlagProtobuf, Expiration: d.mcExpire}
if err = conn.Set(item); err != nil {
log.Error("conn.Set(%s) error(%v)", folderMcKey(v.Mid, v.ID), err)
return
}
}
return
}
// FolderMc return one folder from mc.
func (d *Dao) FolderMc(c context.Context, typ int8, mid, fid int64) (f *favmdl.Folder, err error) {
var (
key = folderMcKey(mid, fid)
item *memcache.Item
conn = d.mc.Get(c)
)
defer conn.Close()
if item, err = conn.Get(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("memcache.Get(%s) error(%v)", key, err)
}
return
}
f = new(favmdl.Folder)
if err = conn.Scan(item, f); err != nil {
log.Error("conn.Scan(%s) error(%v)", item.Value, err)
f = nil
}
return
}
// SetRelaitonFidsMc set fids binary data to mc.
func (d *Dao) SetRelaitonFidsMc(c context.Context, typ int8, mid, oid int64, fids []int64) (err error) {
key := relationFidsKey(typ, mid, oid)
conn := d.mc.Get(c)
defer conn.Close()
bytes := favmdl.ToBytes(fids)
item := &memcache.Item{Key: key, Value: bytes, Flags: memcache.FlagRAW, Expiration: d.mcExpire}
if err = conn.Set(item); err != nil {
log.Error("conn.Set(%s) error(%v)", key, err)
return
}
return
}
// RelaitonFidsMc return fids from mc.
func (d *Dao) RelaitonFidsMc(c context.Context, typ int8, mid, oid int64) (fids []int64, err error) {
var (
key = relationFidsKey(typ, mid, oid)
item *memcache.Item
conn = d.mc.Get(c)
b []byte
)
defer conn.Close()
if item, err = conn.Get(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("memcache.Get(%s) error(%v)", key, err)
}
return
}
if err = conn.Scan(item, &b); err != nil {
log.Error("conn.Scan(%s) error(%v)", item.Value, err)
fids = nil
return
}
if fids, err = favmdl.ToInt64s(b); err != nil {
log.Error("fs.SetIndex(%v) error(%v)", b, err)
err = nil
fids = nil
}
return
}
// DelRelationFidsMc delete oid's fid mc cache.
func (d *Dao) DelRelationFidsMc(c context.Context, typ int8, mid, oid int64) (err error) {
var (
key = relationFidsKey(typ, mid, oid)
conn = d.mc.Get(c)
)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", key, err)
}
}
return
}
// SetOidCountMc return oid count from mc.
func (d *Dao) SetOidCountMc(c context.Context, typ int8, oid, count int64) (err error) {
var (
key = oidCountKey(typ, oid)
conn = d.mc.Get(c)
)
defer conn.Close()
bs := []byte(strconv.FormatInt(int64(count), 10))
item := &memcache.Item{Key: key, Value: bs, Flags: memcache.FlagRAW, Expiration: d.mcExpire}
if err = conn.Set(item); err != nil {
log.Error("conn.Set(%s) error(%v)", key, err)
return
}
return
}
// DelBatchOidsMc delete oids mc cache.
func (d *Dao) DelBatchOidsMc(c context.Context, typ int8, mid int64) (err error) {
key := batchOidsKey(typ, mid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", key, err)
}
}
return
}
// DelRecentOidsMc delete recent oids mc cache.
func (d *Dao) DelRecentOidsMc(c context.Context, typ int8, mid int64) (err error) {
key := recentOidsKey(typ, mid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", key, err)
}
}
return
}
// DelRecentResMc delete recent oids mc cache.
func (d *Dao) DelRecentResMc(c context.Context, typ int8, mid int64) (err error) {
key := recentResKey(typ, mid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", key, err)
}
}
return
}

View File

@@ -0,0 +1,234 @@
package fav
import (
"context"
favmdl "go-common/app/service/main/favorite/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestFavfolderMcKey(t *testing.T) {
convey.Convey("folderMcKey", t, func(ctx convey.C) {
var (
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := folderMcKey(mid, fid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavrelationFidsKey(t *testing.T) {
convey.Convey("relationFidsKey", t, func(ctx convey.C) {
var (
typ = int8(0)
mid = int64(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := relationFidsKey(typ, mid, oid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavoidCountKey(t *testing.T) {
convey.Convey("oidCountKey", t, func(ctx convey.C) {
var (
typ = int8(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := oidCountKey(typ, oid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavbatchOidsKey(t *testing.T) {
convey.Convey("batchOidsKey", t, func(ctx convey.C) {
var (
typ = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := batchOidsKey(typ, mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavrecentOidsKey(t *testing.T) {
convey.Convey("recentOidsKey", t, func(ctx convey.C) {
var (
typ = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := recentOidsKey(typ, mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavPingMC(t *testing.T) {
convey.Convey("PingMC", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.pingMC(c)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavSetFoldersMc(t *testing.T) {
convey.Convey("SetFoldersMc", t, func(ctx convey.C) {
var (
c = context.Background()
vs = &favmdl.Folder{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetFoldersMc(c, vs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavFolderMc(t *testing.T) {
convey.Convey("FolderMc", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
f, err := d.FolderMc(c, typ, mid, fid)
ctx.Convey("Then err should be nil.f should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(f, convey.ShouldNotBeNil)
})
})
})
}
func TestFavSetRelaitonFidsMc(t *testing.T) {
convey.Convey("SetRelaitonFidsMc", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
oid = int64(0)
fids = []int64{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetRelaitonFidsMc(c, typ, mid, oid, fids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavRelaitonFidsMc(t *testing.T) {
convey.Convey("RelaitonFidsMc", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
fids, err := d.RelaitonFidsMc(c, typ, mid, oid)
ctx.Convey("Then err should be nil.fids should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(fids, convey.ShouldNotBeNil)
})
})
})
}
func TestFavDelRelationFidsMc(t *testing.T) {
convey.Convey("DelRelationFidsMc", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelRelationFidsMc(c, typ, mid, oid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavSetOidCountMc(t *testing.T) {
convey.Convey("SetOidCountMc", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
oid = int64(0)
count = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetOidCountMc(c, typ, oid, count)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavDelBatchOidsMc(t *testing.T) {
convey.Convey("DelBatchOidsMc", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelBatchOidsMc(c, typ, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavDelRecentOidsMc(t *testing.T) {
convey.Convey("DelRecentOidsMc", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelRecentOidsMc(c, typ, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,403 @@
package fav
import (
"context"
"database/sql"
"fmt"
favmdl "go-common/app/service/main/favorite/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
"go-common/library/xstr"
)
const (
_countSharding int64 = 50 // count by oid
_folderSharding int64 = 100 // folder by mid
_relationSharding int64 = 500 // relations in folder by mid
_usersSharding int64 = 500 // users by oid
// folder
_folderSQL = "SELECT id,type,mid,name,cover,description,count,attr,state,ctime,mtime FROM fav_folder_%s WHERE id=? AND type=? AND mid=? AND state=0"
_upFolderCntSQL = "UPDATE fav_folder_%s SET count=?,mtime=? WHERE id=?"
// relation
_relationSQL = "SELECT id,type,oid,mid,fid,state,ctime,mtime,sequence FROM fav_relation_%s WHERE type=? AND mid=? AND fid=? AND oid=? AND state=0"
_relationsSQL = "SELECT id,type,oid,mid,fid,state,ctime,mtime,sequence FROM fav_relation_%s FORCE INDEX(ix_fid_state_type_mtime) WHERE fid=? AND mtime>=? AND state=0 AND type=? ORDER BY mtime ASC LIMIT ?"
_allRelationsSQL = "SELECT id,type,oid,mid,fid,state,ctime,mtime,sequence FROM fav_relation_%s FORCE INDEX(ix_fid_state_mtime) WHERE fid=? AND mtime>=? AND state=0 ORDER BY mtime ASC LIMIT ?"
_relationOidsSQL = "SELECT oid FROM fav_relation_%s FORCE INDEX(ix_fid_state_type_mtime) WHERE fid=? AND state=0 AND type=? ORDER BY mtime DESC LIMIT ?,?"
_relationFidsByOidSQL = "SELECT fid FROM fav_relation_%s WHERE type=? AND mid=? AND oid=? and state=0"
_relationFidsSQL = "SELECT oid,fid FROM fav_relation_%s WHERE type=? AND mid=? AND state=0"
_cntRelationSQL = "SELECT COUNT(id) FROM fav_relation_%s FORCE INDEX(ix_fid_state_sequence) WHERE fid=? and state=0"
_addRelationSQL = "INSERT INTO fav_relation_%s (type,oid,mid,fid,state,ctime,mtime,sequence) VALUES(?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE state=?,mtime=?,sequence=?"
_delRelationSQL = "UPDATE fav_relation_%s SET state=1,mtime=? WHERE type=? AND fid=? AND oid=?"
_delRelationsByOidsSQL = "UPDATE fav_relation_%s SET state=1,mtime=? WHERE type=? AND fid=? AND oid in (%s)"
_selRelationsByOidsSql = "SELECT id,type,oid,mid,fid,state,ctime,mtime,sequence FROM fav_relation_%s WHERE type=? AND fid=? AND oid in (%s)"
_updateAllRelationSeq = "UPDATE fav_relation_%s SET mtime=mtime,sequence=(case id %s end) WHERE id in (%s)"
_updateRelationSeq = "UPDATE fav_relation_%s SET sequence=?,mtime=? WHERE fid=? and oid=? and type=?"
_selectMaxSequence = "SELECT id,type,oid,mid,fid,state,ctime,mtime,sequence FROM fav_relation_%s FORCE INDEX(ix_fid_state_sequence) WHERE fid=? AND state=0 order BY sequence desc limit 1"
_recentOidsSQL = "SELECT oid,type FROM fav_relation_%s FORCE INDEX(ix_fid_state_mtime) WHERE fid=? AND state=0 ORDER BY mtime DESC LIMIT 3"
// users
_addUserSQL = "INSERT INTO fav_users_%s (type,oid,mid,state,ctime,mtime) VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE state=?,mtime=?"
_delUserSQL = "UPDATE fav_users_%s SET state=1,mtime=? WHERE type=? AND oid=? AND mid=?"
// stat
_statCntSQL = "SELECT count from fav_count_%s WHERE type=? AND oid=?"
_upStatCntSQL = "INSERT INTO fav_count_%s (type,oid,count,ctime,mtime) VALUES(?,?,?,?,?) ON DUPLICATE KEY UPDATE count=count+?,mtime=?"
)
func cntHit(mid int64) string {
return fmt.Sprintf("%02d", mid%_countSharding)
}
func folderHit(mid int64) string {
return fmt.Sprintf("%02d", mid%_folderSharding)
}
func relationHit(mid int64) string {
return fmt.Sprintf("%03d", mid%_relationSharding)
}
func usersHit(oid int64) string {
return fmt.Sprintf("%03d", oid%_usersSharding)
}
// pingMySQL check mysql connection.
func (d *Dao) pingMySQL(c context.Context) error {
return d.db.Ping(c)
}
func (d *Dao) TxUpdateFavSequence(tx *xsql.Tx, mid int64, fid, oid int64, typ int8, sequence uint64, mtime xtime.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_updateRelationSeq, relationHit(mid)), sequence, mtime, fid, oid, typ)
if err != nil {
log.Error("d.db.Exec(%s,%d,%d,%d,%d,%d) error(%v)", _updateRelationSeq, sequence, mtime, fid, oid, typ, err)
return
}
return res.RowsAffected()
}
func (d *Dao) BatchUpdateSeq(c context.Context, mid int64, favs []*favmdl.Favorite) (int64, error) {
var caseStr string
var ids []int64
for _, fav := range favs {
ids = append(ids, fav.ID)
caseStr += fmt.Sprintf("when %d then %d ", fav.ID, fav.Sequence)
}
if len(ids) <= 0 {
return 0, nil
}
res, err := d.db.Exec(c, fmt.Sprintf(_updateAllRelationSeq, relationHit(mid), caseStr, xstr.JoinInts(ids)))
if err != nil {
return 0, err
}
return res.RowsAffected()
}
// RecentRes return user's three newest fav from a folder.
func (d *Dao) RecentRes(c context.Context, mid, fid int64) (res []*favmdl.Resource, err error) {
rows, err := d.db.Query(c, fmt.Sprintf(_recentOidsSQL, relationHit(mid)), fid)
if err != nil {
log.Error("d.db.Query(%d,%d) error(%v)", mid, fid, err)
return
}
defer rows.Close()
for rows.Next() {
var oid int64
var typ int32
if err = rows.Scan(&oid, &typ); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
res = append(res, &favmdl.Resource{Oid: oid, Typ: typ})
}
err = rows.Err()
return
}
// Folder get a Folder by fid from mysql.
func (d *Dao) Folder(c context.Context, tp int8, mid, fid int64) (f *favmdl.Folder, err error) {
f = &favmdl.Folder{}
row := d.db.QueryRow(c, fmt.Sprintf(_folderSQL, folderHit(mid)), fid, tp, mid)
if err = row.Scan(&f.ID, &f.Type, &f.Mid, &f.Name, &f.Cover, &f.Description, &f.Count, &f.Attr, &f.State, &f.CTime, &f.MTime); err != nil {
if err == sql.ErrNoRows {
f = nil
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// UpFolderCnt update folder count to mysql.
func (d *Dao) UpFolderCnt(c context.Context, mid, fid int64, cnt int, now xtime.Time) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_upFolderCntSQL, folderHit(mid)), cnt, now, fid)
if err != nil {
log.Error("db.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// MaxRelation get a max sequence relation from mysql.
func (d *Dao) MaxRelation(c context.Context, mid, fid int64) (m *favmdl.Favorite, err error) {
m = &favmdl.Favorite{}
row := d.db.QueryRow(c, fmt.Sprintf(_selectMaxSequence, relationHit(mid)), fid)
if err = row.Scan(&m.ID, &m.Type, &m.Oid, &m.Mid, &m.Fid, &m.State, &m.CTime, &m.MTime, &m.Sequence); err != nil {
if err == sql.ErrNoRows {
err = nil
m = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// Relation get a relation from mysql.
func (d *Dao) Relation(c context.Context, tp int8, mid, fid, oid int64) (m *favmdl.Favorite, err error) {
m = &favmdl.Favorite{}
row := d.db.QueryRow(c, fmt.Sprintf(_relationSQL, relationHit(mid)), tp, mid, fid, oid)
if err = row.Scan(&m.ID, &m.Type, &m.Oid, &m.Mid, &m.Fid, &m.State, &m.CTime, &m.MTime, &m.Sequence); err != nil {
if err == sql.ErrNoRows {
err = nil
m = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// AllRelations get favorite relations from mysql.
func (d *Dao) AllRelations(c context.Context, mid, fid int64, mtime xtime.Time, limit int) (fr []*favmdl.Favorite, err error) {
rows, err := d.db.Query(c, fmt.Sprintf(_allRelationsSQL, relationHit(mid)), fid, mtime, limit)
if err != nil {
log.Error("d.db.Query(%s,%d,%d,%d,%d) error(%v)", fmt.Sprintf(_allRelationsSQL, relationHit(mid)), mid, fid, mtime, limit, err)
return
}
defer rows.Close()
fr = make([]*favmdl.Favorite, 0)
for rows.Next() {
var r = &favmdl.Favorite{}
if err = rows.Scan(&r.ID, &r.Type, &r.Oid, &r.Mid, &r.Fid, &r.State, &r.CTime, &r.MTime, &r.Sequence); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
if r.Mid != mid {
log.Error("dirty data relations(%d,%d)", mid, fid)
continue
}
fr = append(fr, r)
}
err = rows.Err()
return
}
func (d *Dao) RelationsByOids(c context.Context, typ int8, mid, fid int64, oids []int64) (fr []*favmdl.Favorite, err error) {
if len(oids) <= 0 {
return
}
rows, err := d.db.Query(c, fmt.Sprintf(_selRelationsByOidsSql, relationHit(mid), xstr.JoinInts(oids)), typ, fid)
if err != nil {
log.Error("d.db.Query(%s,%d,%d,%v) error(%v)", fmt.Sprintf(_selRelationsByOidsSql, relationHit(mid), xstr.JoinInts(oids)), typ, fid, err)
return
}
defer rows.Close()
fr = make([]*favmdl.Favorite, 0)
for rows.Next() {
var r = &favmdl.Favorite{}
if err = rows.Scan(&r.ID, &r.Type, &r.Oid, &r.Mid, &r.Fid, &r.State, &r.CTime, &r.MTime, &r.Sequence); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
if r.Type != typ || r.Mid != mid {
log.Error("dirty data relations(%d,%d,%d)", typ, mid, fid)
continue
}
fr = append(fr, r)
}
err = rows.Err()
return
}
// Relations get favorite relations from mysql.
func (d *Dao) Relations(c context.Context, typ int8, mid, fid int64, mtime xtime.Time, limit int) (fr []*favmdl.Favorite, err error) {
rows, err := d.db.Query(c, fmt.Sprintf(_relationsSQL, relationHit(mid)), fid, mtime, typ, limit)
if err != nil {
log.Error("d.db.Query(%s,%d,%d,%d,%d,%d) error(%v)", fmt.Sprintf(_relationsSQL, relationHit(mid)), typ, mid, fid, mtime, limit, err)
return
}
defer rows.Close()
fr = make([]*favmdl.Favorite, 0)
for rows.Next() {
var r = &favmdl.Favorite{}
if err = rows.Scan(&r.ID, &r.Type, &r.Oid, &r.Mid, &r.Fid, &r.State, &r.CTime, &r.MTime, &r.Sequence); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
if r.Mid != mid {
log.Error("dirty data relations(%d,%d,%d)", typ, mid, fid)
continue
}
fr = append(fr, r)
}
err = rows.Err()
return
}
// RelationFidsByOid get favortied folders in relations by oid from mysql.
func (d *Dao) RelationFidsByOid(c context.Context, tp int8, mid, oid int64) (fids []int64, err error) {
var (
fid int64
idx = relationHit(mid)
)
rows, err := d.db.Query(c, fmt.Sprintf(_relationFidsByOidSQL, idx), tp, mid, oid)
if err != nil {
log.Error("d.db.QueryRow(%d,%d,%d) error(%v)", mid, mid, oid, err)
return
}
defer rows.Close()
for rows.Next() {
if err = rows.Scan(&fid); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
fids = append(fids, fid)
}
err = rows.Err()
return
}
// RelationCnt get favoried folders count in relation from mysql.
func (d *Dao) RelationCnt(c context.Context, mid, fid int64) (cnt int, err error) {
row := d.db.QueryRow(c, fmt.Sprintf(_cntRelationSQL, relationHit(mid)), fid)
if err = row.Scan(&cnt); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// AddRelation add a favorite relation to mysql.
func (d *Dao) AddRelation(c context.Context, fr *favmdl.Favorite) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_addRelationSQL, relationHit(fr.Mid)), fr.Type, fr.Oid, fr.Mid, fr.Fid, fr.State, fr.CTime, fr.MTime, fr.Sequence, fr.State, fr.MTime, fr.Sequence)
if err != nil {
log.Error("db.Exec error(%v)", err)
return
}
return res.LastInsertId()
}
// DelRelation delete a favorite relation to mysql.
func (d *Dao) DelRelation(c context.Context, tp int8, mid, fid, oid int64, now xtime.Time) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_delRelationSQL, relationHit(mid)), now, tp, fid, oid)
if err != nil {
log.Error("d.db.Exec(%d,%d,%d,%d) error(%v)", mid, tp, fid, oid, err)
return
}
return res.RowsAffected()
}
// UpStatCnt update stat count to mysql.
func (d *Dao) UpStatCnt(c context.Context, tp int8, oid int64, incr int, now xtime.Time) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_upStatCntSQL, cntHit(oid)), tp, oid, incr, now, now, incr, now)
if err != nil {
log.Error("db.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// StatCnt return stat count from mysql.
func (d *Dao) StatCnt(c context.Context, tp int8, oid int64) (cnt int, err error) {
row := d.db.QueryRow(c, fmt.Sprintf(_statCntSQL, cntHit(oid)), tp, oid)
if err = row.Scan(&cnt); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// RelationFids get the map [oid -> "fid,fid"] for user.
func (d *Dao) RelationFids(c context.Context, tp int8, mid int64) (rfids map[int64][]int64, err error) {
rows, err := d.db.Query(c, fmt.Sprintf(_relationFidsSQL, relationHit(mid)), tp, mid)
if err != nil {
log.Error("db.Query(%d) error(%v)", mid, err)
return
}
defer rows.Close()
rfids = make(map[int64][]int64, 128)
for rows.Next() {
var oid, fid int64
if err = rows.Scan(&oid, &fid); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
rfids[oid] = append(rfids[oid], fid)
}
err = rows.Err()
return
}
// OidsByFid get oids by fid
func (d *Dao) OidsByFid(c context.Context, typ int8, mid, fid int64, offset, limit int) (oids []int64, err error) {
rows, err := d.db.Query(c, fmt.Sprintf(_relationOidsSQL, relationHit(mid)), fid, typ, offset, limit)
if err != nil {
log.Error("d.db.Query(%d,%d,%d,%d,%d) error(%v)", typ, mid, fid, offset, limit, err)
return
}
defer rows.Close()
oid := int64(0)
oids = make([]int64, 0, limit)
for rows.Next() {
if err = rows.Scan(&oid); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
oids = append(oids, oid)
}
err = rows.Err()
return
}
// DelRelationsByOids update del state by fid and oids.
func (d *Dao) DelRelationsByOids(c context.Context, typ int8, mid, fid int64, oids []int64, now xtime.Time) (rows int64, err error) {
if len(oids) == 0 {
return
}
oidsStr := xstr.JoinInts(oids)
res, err := d.db.Exec(c, fmt.Sprintf(_delRelationsByOidsSQL, relationHit(mid), oidsStr), now, typ, fid)
if err != nil {
log.Error("d.db.Exec(%d,%d,%d,%s) error(%v)", typ, mid, fid, oidsStr, now, err)
return
}
return res.RowsAffected()
}
// AddUser add a favorite user to mysql.
func (d *Dao) AddUser(c context.Context, u *favmdl.User) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_addUserSQL, usersHit(u.Oid)), u.Type, u.Oid, u.Mid, u.State, u.CTime, u.MTime, u.State, u.MTime)
if err != nil {
log.Error("db.Exec error(%v)", err)
return
}
return res.LastInsertId()
}
// DelUser delete a favorite user.
func (d *Dao) DelUser(c context.Context, u *favmdl.User) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_delUserSQL, usersHit(u.Oid)), u.MTime, u.Type, u.Oid, u.Mid)
if err != nil {
log.Error("d.db.Exec(%d,%d,%d,%d) error(%v)", u.MTime, u.Type, u.Oid, u.Mid, err)
return
}
return res.RowsAffected()
}

View File

@@ -0,0 +1,395 @@
package fav
import (
"context"
favmdl "go-common/app/service/main/favorite/model"
xtime "go-common/library/time"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestFavcntHit(t *testing.T) {
convey.Convey("cntHit", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := cntHit(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavfolderHit(t *testing.T) {
convey.Convey("folderHit", t, func(ctx convey.C) {
var (
mid = int64(1)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := folderHit(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavrelationHit(t *testing.T) {
convey.Convey("relationHit", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := relationHit(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavusersHit(t *testing.T) {
convey.Convey("usersHit", t, func(ctx convey.C) {
var (
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := usersHit(oid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavPingMySQL(t *testing.T) {
convey.Convey("PingMySQL", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.pingMySQL(c)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestRecentFolder(t *testing.T) {
convey.Convey("Folder Recents", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.RecentRes(c, mid, fid)
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestUpdateFavSequence(t *testing.T) {
convey.Convey("Test Update Sequence", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
t, err := d.BeginTran(c)
ctx.So(err, convey.ShouldBeNil)
_, err = d.TxUpdateFavSequence(t, mid, fid, 1, 2, 123, xtime.Time(0))
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestFavFolder(t *testing.T) {
convey.Convey("Folder", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.Folder(c, tp, mid, fid)
ctx.Convey("Then err should be nil.f should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavUpFolderCnt(t *testing.T) {
convey.Convey("UpFolderCnt", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
cnt = int(0)
now xtime.Time
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rows, err := d.UpFolderCnt(c, mid, fid, cnt, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestFavRelation(t *testing.T) {
convey.Convey("Relation", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
fid = int64(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.Relation(c, tp, mid, fid, oid)
ctx.Convey("Then err should be nil.m should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavRelations(t *testing.T) {
convey.Convey("Relations", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
fid = int64(0)
mtime xtime.Time
limit = int(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
fr, err := d.Relations(c, typ, mid, fid, mtime, limit)
ctx.Convey("Then err should be nil.fr should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(fr, convey.ShouldNotBeNil)
})
})
})
}
func TestFavRelationFidsByOid(t *testing.T) {
convey.Convey("RelationFidsByOid", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.RelationFidsByOid(c, tp, mid, oid)
ctx.Convey("Then err should be nil.fids should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavRelationCnt(t *testing.T) {
convey.Convey("RelationCnt", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
cnt, err := d.RelationCnt(c, mid, fid)
ctx.Convey("Then err should be nil.cnt should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(cnt, convey.ShouldNotBeNil)
})
})
})
}
func TestFavAddRelation(t *testing.T) {
convey.Convey("AddRelation", t, func(ctx convey.C) {
var (
c = context.Background()
fr = &favmdl.Favorite{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rows, err := d.AddRelation(c, fr)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestFavDelRelation(t *testing.T) {
convey.Convey("DelRelation", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
fid = int64(0)
oid = int64(0)
now xtime.Time
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rows, err := d.DelRelation(c, tp, mid, fid, oid, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestFavUpStatCnt(t *testing.T) {
convey.Convey("UpStatCnt", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
oid = int64(0)
incr = int(0)
now xtime.Time
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rows, err := d.UpStatCnt(c, tp, oid, incr, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestFavStatCnt(t *testing.T) {
convey.Convey("StatCnt", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
cnt, err := d.StatCnt(c, tp, oid)
ctx.Convey("Then err should be nil.cnt should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(cnt, convey.ShouldNotBeNil)
})
})
})
}
func TestFavRelationFids(t *testing.T) {
convey.Convey("RelationFids", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rfids, err := d.RelationFids(c, tp, mid)
ctx.Convey("Then err should be nil.rfids should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rfids, convey.ShouldNotBeNil)
})
})
})
}
func TestFavOidsByFid(t *testing.T) {
convey.Convey("OidsByFid", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
fid = int64(0)
offset = int(0)
limit = int(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
oids, err := d.OidsByFid(c, typ, mid, fid, offset, limit)
ctx.Convey("Then err should be nil.oids should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(oids, convey.ShouldNotBeNil)
})
})
})
}
func TestFavDelRelationsByOids(t *testing.T) {
convey.Convey("DelRelationsByOids", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
fid = int64(0)
oids = []int64{}
now xtime.Time
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rows, err := d.DelRelationsByOids(c, typ, mid, fid, oids, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestFavAddUser(t *testing.T) {
convey.Convey("AddUser", t, func(ctx convey.C) {
var (
c = context.Background()
u = &favmdl.User{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rows, err := d.AddUser(c, u)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestFavDelUser(t *testing.T) {
convey.Convey("DelUser", t, func(ctx convey.C) {
var (
c = context.Background()
u = &favmdl.User{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rows, err := d.DelUser(c, u)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestRelationsByOids(t *testing.T) {
convey.Convey("RelationsByOids", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
favs, err := d.RelationsByOids(c, 2, 1501, 168, []int64{10108138})
ctx.So(err, convey.ShouldBeNil)
ctx.So(favs, convey.ShouldNotBeNil)
_, err = d.BatchUpdateSeq(c, 76, favs)
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,558 @@
package fav
import (
"context"
"encoding/json"
"fmt"
"go-common/app/job/main/favorite/model"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
// _fid list fid => ([]model.Cover)
_covers = "fcs_"
_folderKey = "fi_%d_%d" // sortedset f_type_mid value:fid,score:ctime
_oldRelationKey = "r_%d_%d_%d"
_allRelationKey = "ar_%d_%d"
_relationKey = "r_%d_%d" // sortedset r_mid_fid(mtime, oid)
_relationOidsKey = "ro_%d_%d" // set ro_type_mid value:oids
_cleanedKey = "rc_%d_%d" // hash key:rc_type_mid field:fid value:timestamp
// key fb_mid/100000 offset => mid%100000
// bit value 1 mean unfaved; bit value 0 mean faved
_favedBit = "fb_%d_%d"
_bucket = 100000
)
func favedBitKey(tp int8, mid int64) string {
return fmt.Sprintf(_favedBit, tp, mid/_bucket)
}
// folderKey return a user folder key.
func folderKey(tp int8, mid int64) string {
return fmt.Sprintf(_folderKey, tp, mid)
}
// relationKey return folder relation key.
func relationKey(mid, fid int64) string {
return fmt.Sprintf(_relationKey, mid, fid)
}
// allRelationKey return folder relation key.
func allRelationKey(mid, fid int64) string {
return fmt.Sprintf(_allRelationKey, mid, fid)
}
// oldRelationKey return folder relation key.
func oldRelationKey(typ int8, mid, fid int64) string {
return fmt.Sprintf(_oldRelationKey, typ, mid, fid)
}
// relationOidsKey return a user oids key.
func relationOidsKey(tp int8, mid int64) string {
return fmt.Sprintf(_relationOidsKey, tp, mid)
}
// cleanKey return user whether cleaned key.
func cleanedKey(tp int8, mid int64) string {
return fmt.Sprintf(_cleanedKey, tp, mid)
}
// redisKey make key for redis by prefix and mid
func coversKey(mid, fid int64) string {
return fmt.Sprintf("%s%d_%d", _covers, mid, fid)
}
// PingRedis ping connection success.
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
_, err = conn.Do("SET", "PING", "PONG")
conn.Close()
return
}
// DelNewCoverCache delete cover picture cache.
func (d *Dao) DelNewCoverCache(c context.Context, mid, fid int64) (err error) {
var (
key = coversKey(mid, fid)
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("DEL", key); err != nil {
log.Error("DEL %v failed error(%v)", key, err)
}
return
}
// SetUnFavedBit set unfaved user bit to 1
func (d *Dao) SetUnFavedBit(c context.Context, tp int8, mid int64) (err error) {
key := favedBitKey(tp, mid)
offset := mid % _bucket
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("SETBIT", key, offset, 1); err != nil {
log.Error("conn.DO(SETBIT) key(%s) offset(%d) err(%v)", key, offset, err)
}
return
}
// SetFavedBit set unfaved user bit to 0
func (d *Dao) SetFavedBit(c context.Context, tp int8, mid int64) (err error) {
key := favedBitKey(tp, mid)
offset := mid % _bucket
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("SETBIT", key, offset, 0); err != nil {
log.Error("conn.DO(SETBIT) key(%s) offset(%d) err(%v)", key, offset, err)
}
return
}
// ExpireAllRelations expire folder relations cache.
func (d *Dao) ExpireAllRelations(c context.Context, mid, fid int64) (ok bool, err error) {
key := allRelationKey(mid, fid)
conn := d.redis.Get(c)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisExpire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", key, err)
}
conn.Close()
return
}
// ExpireRelations expire folder relations cache.
func (d *Dao) ExpireRelations(c context.Context, mid, fid int64) (ok bool, err error) {
key := relationKey(mid, fid)
conn := d.redis.Get(c)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisExpire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", key, err)
}
conn.Close()
return
}
// FolderCache return a favorite folder from redis.
func (d *Dao) FolderCache(c context.Context, tp int8, mid, fid int64) (folder *favmdl.Folder, err error) {
var (
value []byte
key = folderKey(tp, mid)
conn = d.redis.Get(c)
)
defer conn.Close()
if value, err = redis.Bytes(conn.Do("HGET", key, fid)); err != nil {
if err == redis.ErrNil {
err = nil
folder = nil
} else {
log.Error("conn.Do(HGET, %v, %v) error(%v)", key, fid, err)
}
return
}
folder = &favmdl.Folder{}
if err = json.Unmarshal(value, folder); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", value, err)
}
return
}
// DefaultFolderCache return default favorite folder from redis.
func (d *Dao) DefaultFolderCache(c context.Context, tp int8, mid int64) (folder *favmdl.Folder, err error) {
var res map[int64]*favmdl.Folder
if res, err = d.foldersCache(c, tp, mid); err != nil {
return
}
if res == nil {
return
}
for _, folder = range res {
if folder.IsDefault() {
return
}
}
folder = nil
return
}
// foldersCache return the user all folders from redis.
func (d *Dao) foldersCache(c context.Context, tp int8, mid int64) (res map[int64]*favmdl.Folder, err error) {
var (
values map[string]string
key = folderKey(tp, mid)
conn = d.redis.Get(c)
)
defer conn.Close()
if values, err = redis.StringMap(conn.Do("HGETALL", key)); err != nil {
if err == redis.ErrNil {
return nil, nil
}
log.Error("conn.Do(HGETALL %s) error(%v)", key, err)
return
}
res = make(map[int64]*favmdl.Folder, len(res))
for _, data := range values {
folder := &favmdl.Folder{}
if err = json.Unmarshal([]byte(data), folder); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", data, err)
return
}
res[folder.ID] = folder
}
return
}
// RelationCntCache return the folder all relation count from redis.
func (d *Dao) RelationCntCache(c context.Context, mid, fid int64) (cnt int, err error) {
key := relationKey(mid, fid)
conn := d.redis.Get(c)
defer conn.Close()
if cnt, err = redis.Int(conn.Do("ZCARD", key)); err != nil {
if err == redis.ErrNil {
return model.CacheNotFound, nil
}
log.Error("conn.Do(ZCARD %s) error(%v)", key, err)
}
return
}
// MaxScore get the max score from sorted set
func (d *Dao) MaxScore(c context.Context, m *favmdl.Favorite) (score int64, err error) {
key := allRelationKey(m.Mid, m.Fid)
conn := d.redis.Get(c)
defer conn.Close()
values, err := redis.Values(conn.Do("ZREVRANGE", key, 0, 0, "WITHSCORES"))
if err != nil {
log.Error("conn.Do(ZREVRANGE, %s) error(%v)", key, err)
return
}
if len(values) != 2 {
err = fmt.Errorf("redis zrange items(%v) length not 2", values)
return
}
var id int64
redis.Scan(values, &id, &score)
return
}
// AddAllRelationCache add a relation to redis.
func (d *Dao) AddAllRelationCache(c context.Context, m *favmdl.Favorite) (err error) {
key := allRelationKey(m.Mid, m.Fid)
score, err := d.MaxScore(c, m)
if err != nil {
return
}
if score <= 0 {
log.Error("dao.AddAllRelationCache invalid score(%d)!%+v", d, *m)
return
}
seq := int64(score/1e10) + 1
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZADD", key, seq*1e10+int64(m.MTime), m.Oid*100+int64(m.Type)); err != nil {
log.Error("conn.Send(ZADD %s,%d) error(%v)", key, m.Oid, err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < 2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// AddRelationCache add a relation to redis.
func (d *Dao) AddRelationCache(c context.Context, m *favmdl.Favorite) (err error) {
key := relationKey(m.Mid, m.Fid)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZADD", key, m.MTime, m.Oid); err != nil {
log.Error("conn.Send(ZADD %s,%d) error(%v)", key, m.Oid, err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < 2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// AddAllRelationsCache add a relation to redis.
func (d *Dao) AddAllRelationsCache(c context.Context, mid, fid int64, fs []*favmdl.Favorite) (err error) {
key := allRelationKey(mid, fid)
conn := d.redis.Get(c)
defer conn.Close()
for _, fav := range fs {
if err = conn.Send("ZADD", key, fav.Sequence*1e10+uint64(fav.MTime), fav.Oid*100+int64(fav.Type)); err != nil {
log.Error("conn.Send(ZADD %s,%d) error(%v)", key, fav.Oid, err)
return
}
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < len(fs)+1; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// AddRelationsCache add a relation to redis.
func (d *Dao) AddRelationsCache(c context.Context, tp int8, mid, fid int64, fs []*favmdl.Favorite) (err error) {
key := relationKey(mid, fid)
conn := d.redis.Get(c)
defer conn.Close()
for _, fav := range fs {
if err = conn.Send("ZADD", key, fav.MTime, fav.Oid); err != nil {
log.Error("conn.Send(ZADD %s,%d) error(%v)", key, fav.Oid, err)
return
}
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send(EXPIRE) error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < len(fs)+1; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// DelRelationsCache delete the folder relation cache.
func (d *Dao) DelRelationsCache(c context.Context, mid, fid int64) (err error) {
key := relationKey(mid, fid)
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("DEL", key); err != nil {
log.Error("conn.Do(DEL %s) error(%v)", key, err)
}
return
}
// DelAllRelationsCache delete the folder relation cache.
func (d *Dao) DelAllRelationsCache(c context.Context, mid, fid int64) (err error) {
key := allRelationKey(mid, fid)
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("DEL", key); err != nil {
log.Error("conn.Do(DEL %s) error(%v)", key, err)
}
return
}
// DelRelationCache delete one relation cache.
func (d *Dao) DelRelationCache(c context.Context, mid, fid, oid int64) (err error) {
key := relationKey(mid, fid)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZREM", key, oid); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send error(%v)", 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
}
// DelAllRelationCache delete one relation cache.
func (d *Dao) DelAllRelationCache(c context.Context, mid, fid, oid int64, typ int8) (err error) {
key := allRelationKey(mid, fid)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZREM", key, oid*100+int64(typ)); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send error(%v)", 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
}
// DelOldRelationsCache delete the folder relation cache. TODO:del at 2018.06.08
func (d *Dao) DelOldRelationsCache(c context.Context, typ int8, mid, fid int64) (err error) {
key := oldRelationKey(typ, mid, fid)
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("DEL", key); err != nil {
log.Error("conn.Do(DEL %s) error(%v)", key, err)
}
return
}
// ExpireRelationOids set expire for faved oids.
func (d *Dao) ExpireRelationOids(c context.Context, tp int8, mid int64) (ok bool, err error) {
key := relationOidsKey(tp, mid)
var conn = d.redis.Get(c)
defer conn.Close()
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisExpire)); err != nil {
log.Error("conn.Do(EXPIRE, %s) error(%v)", key, err)
}
return
}
// AddRelationOidCache add favoured oid.
func (d *Dao) AddRelationOidCache(c context.Context, tp int8, mid, oid int64) (err error) {
var (
key = relationOidsKey(tp, mid)
conn = d.redis.Get(c)
)
defer conn.Close()
if err = conn.Send("SADD", key, oid); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send error(%v)", 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
}
// RemRelationOidCache del favoured oid.
func (d *Dao) RemRelationOidCache(c context.Context, tp int8, mid, oid int64) (err error) {
var (
key = relationOidsKey(tp, mid)
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("SREM", key, oid); err != nil {
log.Error("conn.Do(%s,%d) error(%v)", key, oid, err)
}
return
}
// SetRelationOidsCache set favoured oids .
func (d *Dao) SetRelationOidsCache(c context.Context, tp int8, mid int64, oids []int64) (err error) {
var (
key = relationOidsKey(tp, mid)
conn = d.redis.Get(c)
)
defer conn.Close()
for _, oid := range oids {
if err = conn.Send("SADD", key, oid); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
for i := 0; i < len(oids)+1; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// SetCleanedCache .
func (d *Dao) SetCleanedCache(c context.Context, typ int8, mid, fid, ftime, expire int64) (err error) {
var (
key = cleanedKey(typ, mid)
conn = d.redis.Get(c)
)
defer conn.Close()
if err = conn.Send("HSET", key, fid, ftime); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, expire); err != nil {
log.Error("conn.Send error(%v)", 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
}
// DelRelationOidsCache .
func (d *Dao) DelRelationOidsCache(c context.Context, typ int8, mid int64) (err error) {
key := relationOidsKey(typ, mid)
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("DEL", key); err != nil {
log.Error("conn.Do(DEL %s) error(%v)", key, err)
}
return
}

View File

@@ -0,0 +1,462 @@
package fav
import (
"context"
favmdl "go-common/app/service/main/favorite/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestPing(t *testing.T) {
convey.Convey("ping test", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.Ping(c)
ctx.So(err, convey.ShouldBeNil)
err = d.pingMC(c)
ctx.So(err, convey.ShouldBeNil)
err = d.pingMySQL(c)
ctx.So(err, convey.ShouldBeNil)
err = d.pingRedis(c)
ctx.So(err, convey.ShouldBeNil)
t, err := d.BeginTran(c)
ctx.So(err, convey.ShouldBeNil)
ctx.So(t, convey.ShouldNotBeNil)
t.Rollback()
})
})
}
func TestDelNewCovers(t *testing.T) {
convey.Convey("del covers", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelNewCoverCache(c, mid, fid)
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestExpireAll(t *testing.T) {
convey.Convey("ExpireAll", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.ExpireAllRelations(c, mid, fid)
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDelAll(t *testing.T) {
convey.Convey("DelAll", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelAllRelationCache(c, mid, fid, 1, 2)
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestFavfavedBitKey(t *testing.T) {
convey.Convey("favedBitKey", t, func(ctx convey.C) {
var (
tp = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := favedBitKey(tp, mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavfolderKey(t *testing.T) {
convey.Convey("folderKey", t, func(ctx convey.C) {
var (
tp = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := folderKey(tp, mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavrelationKey(t *testing.T) {
convey.Convey("relationKey", t, func(ctx convey.C) {
var (
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := relationKey(mid, fid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavoldRelationKey(t *testing.T) {
convey.Convey("oldRelationKey", t, func(ctx convey.C) {
var (
typ = int8(0)
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := oldRelationKey(typ, mid, fid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavrelationOidsKey(t *testing.T) {
convey.Convey("relationOidsKey", t, func(ctx convey.C) {
var (
tp = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := relationOidsKey(tp, mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavcleanedKey(t *testing.T) {
convey.Convey("cleanedKey", t, func(ctx convey.C) {
var (
tp = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := cleanedKey(tp, mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestFavSetUnFavedBit(t *testing.T) {
convey.Convey("SetUnFavedBit", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetUnFavedBit(c, tp, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavSetFavedBit(t *testing.T) {
convey.Convey("SetFavedBit", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetFavedBit(c, tp, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavExpireRelations(t *testing.T) {
convey.Convey("ExpireRelations", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
ok, err := d.ExpireRelations(c, mid, fid)
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 TestFavFolderCache(t *testing.T) {
convey.Convey("FolderCache", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(1)
mid = int64(1)
fid = int64(1)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.FolderCache(c, tp, mid, fid)
ctx.Convey("Then err should be nil.folder should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavDefaultFolderCache(t *testing.T) {
convey.Convey("DefaultFolderCache", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(1)
mid = int64(1)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.DefaultFolderCache(c, tp, mid)
ctx.Convey("Then err should be nil.folder should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavfoldersCache(t *testing.T) {
convey.Convey("foldersCache", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(1)
mid = int64(1)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
_, err := d.foldersCache(c, tp, mid)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavRelationCntCache(t *testing.T) {
convey.Convey("RelationCntCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
cnt, err := d.RelationCntCache(c, mid, fid)
ctx.Convey("Then err should be nil.cnt should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(cnt, convey.ShouldNotBeNil)
})
})
})
}
func TestFavAddRelationCache(t *testing.T) {
convey.Convey("AddRelationCache", t, func(ctx convey.C) {
var (
c = context.Background()
m = &favmdl.Favorite{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddRelationCache(c, m)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavAddRelationsCache(t *testing.T) {
convey.Convey("AddRelationsCache", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
fid = int64(0)
fs = []*favmdl.Favorite{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddRelationsCache(c, tp, mid, fid, fs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavDelRelationsCache(t *testing.T) {
convey.Convey("DelRelationsCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelRelationsCache(c, mid, fid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavDelRelationCache(t *testing.T) {
convey.Convey("DelRelationCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelRelationCache(c, mid, fid, oid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavDelOldRelationsCache(t *testing.T) {
convey.Convey("DelOldRelationsCache", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
fid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelOldRelationsCache(c, typ, mid, fid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavExpireRelationOids(t *testing.T) {
convey.Convey("ExpireRelationOids", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
ok, err := d.ExpireRelationOids(c, tp, 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 TestFavAddRelationOidCache(t *testing.T) {
convey.Convey("AddRelationOidCache", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddRelationOidCache(c, tp, mid, oid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavRemRelationOidCache(t *testing.T) {
convey.Convey("RemRelationOidCache", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
oid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.RemRelationOidCache(c, tp, mid, oid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavSetRelationOidsCache(t *testing.T) {
convey.Convey("SetRelationOidsCache", t, func(ctx convey.C) {
var (
c = context.Background()
tp = int8(0)
mid = int64(0)
oids = []int64{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetRelationOidsCache(c, tp, mid, oids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavSetCleanedCache(t *testing.T) {
convey.Convey("SetCleanedCache", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
fid = int64(0)
ftime = int64(0)
expire = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetCleanedCache(c, typ, mid, fid, ftime, expire)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestFavDelRelationOidsCache(t *testing.T) {
convey.Convey("DelRelationOidsCache", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelRelationOidsCache(c, typ, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,49 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["dao.go"],
importpath = "go-common/app/job/main/favorite/dao/music",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/favorite/conf:go_default_library",
"//app/job/main/favorite/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/metadata:go_default_library",
"//library/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"],
)
go_test(
name = "go_default_test",
srcs = ["dao_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/job/main/favorite/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,49 @@
package music
import (
"context"
"fmt"
"net/url"
"go-common/app/job/main/favorite/conf"
"go-common/app/job/main/favorite/model"
"go-common/library/ecode"
"go-common/library/log"
httpx "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
"go-common/library/xstr"
)
const _music = "http://api.bilibili.co/x/internal/v1/audio/songs/batch"
// Dao defeine fav Dao
type Dao struct {
httpClient *httpx.Client
}
// New return fav dao
func New(c *conf.Config) (d *Dao) {
d = &Dao{
httpClient: httpx.NewClient(c.HTTPClient),
}
return
}
// MusicMap return the music map data(all state).
func (d *Dao) MusicMap(c context.Context, musicIds []int64) (data map[int64]*model.Music, err error) {
params := url.Values{}
params.Set("level", "1")
params.Set("ids", xstr.JoinInts(musicIds))
res := new(model.MusicResult)
ip := metadata.String(c, metadata.RemoteIP)
if err = d.httpClient.Get(c, _music, ip, params, res); err != nil {
log.Error("d.HTTPClient.Get(%s?%s) error(%v)", _music, params.Encode())
return
}
if res.Code != ecode.OK.Code() {
log.Error("d.HTTPClient.Get(%s?%s) code:%d msg:%s", _music, params.Encode(), res.Code)
err = fmt.Errorf("Get Music failed!code:=%v", res.Code)
return
}
return res.Data, nil
}

View File

@@ -0,0 +1,45 @@
package music
import (
"context"
"flag"
"os"
"testing"
"go-common/app/job/main/favorite/conf"
. "github.com/smartystreets/goconvey/convey"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.favorite")
flag.Set("conf_token", "929707200cf97646d1de7116e555dcfa")
flag.Set("tree_id", "2296")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../../cmd/favorite-job.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}
func TestMusicMap(t *testing.T) {
Convey("MusicMap", t, func() {
_, err := d.MusicMap(context.Background(), []int64{0})
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"databus.go",
],
importpath = "go-common/app/job/main/favorite/dao/pub",
tags = ["automanaged"],
deps = [
"//app/job/main/favorite/conf:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus: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,24 @@
package pub
import (
"go-common/app/job/main/favorite/conf"
"go-common/library/queue/databus"
)
// Dao stat dao.
type Dao struct {
databus2 *databus.Databus
consumersMap map[int8]string
}
// New new a stat dao and return.
func New(c *conf.Config) *Dao {
consumersMap := make(map[int8]string)
for name, typ := range c.StatFavDatabus.Consumers {
consumersMap[typ] = name
}
return &Dao{
databus2: databus.New(c.StatFavDatabus.Config),
consumersMap: consumersMap,
}
}

View File

@@ -0,0 +1,32 @@
package pub
import (
"context"
"strconv"
"time"
"go-common/library/log"
)
type statMessage struct {
Type string `json:"type"`
ID int64 `json:"id"`
Count int64 `json:"count"`
TimeStamp int64 `json:"timestamp"`
}
// PubStats update object's fav count
func (d *Dao) PubStats(c context.Context, typ int8, oid int64, cnt int64) (err error) {
if name, ok := d.consumersMap[typ]; ok {
msg := &statMessage{
Type: name,
ID: oid,
Count: cnt,
TimeStamp: time.Now().Unix(),
}
if err = d.databus2.Send(c, strconv.FormatInt(oid, 10), msg); err != nil {
log.Error("d.databus2.Send(%d,%d,%v) error(%v)", typ, oid, msg, err)
}
}
return
}

View File

@@ -0,0 +1,59 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"memcache.go",
"mysql.go",
"redis.go",
],
importpath = "go-common/app/job/main/favorite/dao/stat",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/favorite/conf:go_default_library",
"//app/service/main/favorite/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"memcache_test.go",
"mysql_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/job/main/favorite/conf:go_default_library",
"//app/service/main/favorite/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,45 @@
package stat
import (
"fmt"
"time"
"go-common/app/job/main/favorite/conf"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/database/sql"
)
// Dao favorite dao.
type Dao struct {
db *sql.DB
redis *redis.Pool
mc *memcache.Pool
redisExpire int
ipExpire int
buvidExpire int
mcExpire int32
}
// New new a dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
db: sql.NewMySQL(c.DB.Fav),
// redis
redis: redis.NewPool(c.Redis.Config),
redisExpire: int(time.Duration(c.Redis.Expire) / time.Second),
ipExpire: int(time.Duration(c.Redis.IPExpire) / time.Second),
buvidExpire: int(time.Duration(c.Redis.BuvidExpire) / time.Second),
// memcache
mc: memcache.NewPool(c.Memcache.Config),
mcExpire: int32(time.Duration(c.Memcache.Expire) / time.Second),
}
return
}
// hit .
func hit(id int64) (fid int64, table string) {
fid = id / _folderStatSharding
table = fmt.Sprintf("%02d", id%_folderStatSharding)
return
}

View File

@@ -0,0 +1,35 @@
package stat
import (
"flag"
"os"
"testing"
"go-common/app/job/main/favorite/conf"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.favorite-job")
flag.Set("conf_token", "29adace99d5c5be327ae4a6b70c6582d")
flag.Set("tree_id", "2295")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../../cmd/favorite-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,57 @@
package stat
import (
"context"
"fmt"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/cache/memcache"
"go-common/library/log"
)
const (
_folderStat = "ft_%s_%d" // key:ft_{mid%100}_{fid},value:{*FodlerStat}.pb
)
// folderStatMcKey
func folderStatMcKey(table string, fid int64) string {
return fmt.Sprintf(_folderStat, table, fid)
}
// SetFolderStatMc add folder stat mc cache.
func (d *Dao) SetFolderStatMc(c context.Context, id int64, s *favmdl.Folder) (err error) {
fid, table := hit(id)
conn := d.mc.Get(c)
defer conn.Close()
item := &memcache.Item{Key: folderStatMcKey(table, fid), Object: s, Flags: memcache.FlagProtobuf, Expiration: d.mcExpire}
if err = conn.Set(item); err != nil {
log.Error("conn.Set(%s) error(%v)", folderStatMcKey(table, fid), err)
}
return
}
// FolderStatMc return one folder stat from mc.
func (d *Dao) FolderStatMc(c context.Context, id int64) (f *favmdl.Folder, err error) {
fid, table := hit(id)
var (
key = folderStatMcKey(table, fid)
item *memcache.Item
conn = d.mc.Get(c)
)
defer conn.Close()
if item, err = conn.Get(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("memcache.Get(%s) error(%v)", key, err)
}
return
}
f = new(favmdl.Folder)
if err = conn.Scan(item, f); err != nil {
log.Error("conn.Scan(%s) error(%v)", item.Value, err)
f = nil
}
return
}

View File

@@ -0,0 +1,57 @@
package stat
import (
"context"
"testing"
favmdl "go-common/app/service/main/favorite/model"
"github.com/smartystreets/goconvey/convey"
)
func TestStatfolderStatMcKey(t *testing.T) {
convey.Convey("folderStatMcKey", t, func(convCtx convey.C) {
var (
table = ""
fid = int64(111)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := folderStatMcKey(table, fid)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestStatSetFolderStatMc(t *testing.T) {
convey.Convey("SetFolderStatMc", t, func(convCtx convey.C) {
var (
c = context.Background()
id = int64(111)
s = &favmdl.Folder{}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.SetFolderStatMc(c, id, s)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestStatFolderStatMc(t *testing.T) {
convey.Convey("FolderStatMc", t, func(convCtx convey.C) {
var (
c = context.Background()
id = int64(111)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
f, err := d.FolderStatMc(c, id)
convCtx.Convey("Then err should be nil.f should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(f, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,73 @@
package stat
import (
"context"
"fmt"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/database/sql"
"go-common/library/log"
)
const (
_folderStatSharding int64 = 100
// stat
_statSQL = "SELECT play,fav,share from fav_folder_stat_%s WHERE fid=?"
_upsertPlaySQL = "INSERT INTO fav_folder_stat_%s (fid,play) VALUES(?,?) ON DUPLICATE KEY UPDATE play=?"
_upsertFavSQL = "INSERT INTO fav_folder_stat_%s (fid,fav) VALUES(?,?) ON DUPLICATE KEY UPDATE fav=?"
_upsertShareSQL = "INSERT INTO fav_folder_stat_%s (fid,share) VALUES(?,?) ON DUPLICATE KEY UPDATE share=?"
)
// UpdateFav updates stat in db.
func (d *Dao) UpdateFav(c context.Context, oid, count int64) (rows int64, err error) {
fid, table := hit(oid)
res, err := d.db.Exec(c, fmt.Sprintf(_upsertFavSQL, table), fid, count, count)
if err != nil {
log.Error("UpdateFav(%d,%d) error(%+v)", oid, count, err)
return
}
rows, err = res.RowsAffected()
return
}
// UpdateShare updates stat in db.
func (d *Dao) UpdateShare(c context.Context, oid, count int64) (rows int64, err error) {
fid, table := hit(oid)
res, err := d.db.Exec(c, fmt.Sprintf(_upsertShareSQL, table), fid, count, count)
if err != nil {
log.Error("UpdateShare(%d,%d) error(%+v)", oid, count, err)
return
}
rows, err = res.RowsAffected()
return
}
// UpdatePlay updates stat in db.
func (d *Dao) UpdatePlay(c context.Context, oid, count int64) (rows int64, err error) {
fid, table := hit(oid)
res, err := d.db.Exec(c, fmt.Sprintf(_upsertPlaySQL, table), fid, count, count)
if err != nil {
log.Error("UpdatePlay(%d) error(%+v)", oid, err)
return
}
rows, err = res.RowsAffected()
return
}
// Stat return stat count from mysql.
func (d *Dao) Stat(c context.Context, oid int64) (f *favmdl.Folder, err error) {
fid, table := hit(oid)
f = &favmdl.Folder{}
row := d.db.QueryRow(c, fmt.Sprintf(_statSQL, table), fid)
if err = row.Scan(&f.PlayCount, &f.FavedCount, &f.ShareCount); err != nil {
if err == sql.ErrNoRows {
err = nil
f = nil
return
}
log.Error("Stat(%v) error(%+v)", f, err)
}
return
}

View File

@@ -0,0 +1,75 @@
package stat
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestStatUpdateFav(t *testing.T) {
convey.Convey("UpdateFav", t, func(convCtx convey.C) {
var (
c = context.Background()
oid = int64(111)
count = int64(1)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
rows, err := d.UpdateFav(c, oid, count)
convCtx.Convey("Then err should be nil.rows should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestStatUpdateShare(t *testing.T) {
convey.Convey("UpdateShare", t, func(convCtx convey.C) {
var (
c = context.Background()
oid = int64(111)
count = int64(1)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
rows, err := d.UpdateShare(c, oid, count)
convCtx.Convey("Then err should be nil.rows should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestStatUpdatePlay(t *testing.T) {
convey.Convey("UpdatePlay", t, func(convCtx convey.C) {
var (
c = context.Background()
oid = int64(111)
count = int64(1)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
rows, err := d.UpdatePlay(c, oid, count)
convCtx.Convey("Then err should be nil.rows should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestStatStat(t *testing.T) {
convey.Convey("Stat", t, func(convCtx convey.C) {
var (
c = context.Background()
oid = int64(111)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
f, err := d.Stat(c, oid)
convCtx.Convey("Then err should be nil.f should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(f, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,103 @@
package stat
import (
"context"
"strconv"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_ipBanKey = "ipb_"
_buvidBanKey = "bvb_"
)
func ipBanKey(id int64, ip string) (key string) {
key = _ipBanKey + strconv.FormatInt(id, 10) + ip
return
}
func buvidBanKey(id, mid int64, ip, buvid string) (key string) {
key = _buvidBanKey + strconv.FormatInt(id, 10) + ip + buvid
if mid != 0 {
key += strconv.FormatInt(mid, 10)
}
return
}
// IPBan intercepts illegal views.
func (d *Dao) IPBan(c context.Context, id int64, ip string) (ban bool) {
var (
err error
exist bool
key = ipBanKey(id, ip)
conn = d.redis.Get(c)
)
defer conn.Close()
if exist, err = redis.Bool(conn.Do("EXISTS", key)); err != nil {
log.Error("conn.Do(EXISTS, %s) error(%+v)", key, err)
return
}
if exist {
ban = true
return
}
if err = conn.Send("SET", key, "1"); err != nil {
log.Error("conn.Send(EXPIRE, %s) error(%+v)", key, err)
return
}
if err = conn.Send("EXPIRE", key, d.ipExpire); err != nil {
log.Error("conn.Send(EXPIRE, %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
}
// BuvidBan intercepts illegal views.
func (d *Dao) BuvidBan(c context.Context, id, mid int64, ip, buvid string) (ban bool) {
var (
err error
exist bool
key = buvidBanKey(id, mid, ip, buvid)
conn = d.redis.Get(c)
)
defer conn.Close()
if exist, err = redis.Bool(conn.Do("EXISTS", key)); err != nil {
log.Error("conn.Do(EXISTS, %s) error(%+v)", key, err)
return
}
if exist {
ban = true
return
}
if err = conn.Send("SET", key, "1"); err != nil {
log.Error("conn.Send(EXPIRE, %s) error(%+v)", key, err)
return
}
if err = conn.Send("EXPIRE", key, d.buvidExpire); err != nil {
log.Error("conn.Send(EXPIRE, %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
}

View File

@@ -0,0 +1,74 @@
package stat
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestStatipBanKey(t *testing.T) {
convey.Convey("ipBanKey", t, func(convCtx convey.C) {
var (
id = int64(0)
ip = ""
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
key := ipBanKey(id, ip)
convCtx.Convey("Then key should not be nil.", func(convCtx convey.C) {
convCtx.So(key, convey.ShouldNotBeNil)
})
})
})
}
func TestStatbuvidBanKey(t *testing.T) {
convey.Convey("buvidBanKey", t, func(convCtx convey.C) {
var (
id = int64(0)
mid = int64(0)
ip = ""
buvid = ""
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
key := buvidBanKey(id, mid, ip, buvid)
convCtx.Convey("Then key should not be nil.", func(convCtx convey.C) {
convCtx.So(key, convey.ShouldNotBeNil)
})
})
})
}
func TestStatIPBan(t *testing.T) {
convey.Convey("IPBan", t, func(convCtx convey.C) {
var (
c = context.Background()
id = int64(0)
ip = ""
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
ban := d.IPBan(c, id, ip)
convCtx.Convey("Then ban should not be nil.", func(convCtx convey.C) {
convCtx.So(ban, convey.ShouldNotBeNil)
})
})
})
}
func TestStatBuvidBan(t *testing.T) {
convey.Convey("BuvidBan", t, func(convCtx convey.C) {
var (
c = context.Background()
id = int64(0)
mid = int64(0)
buvid = ""
ip = ""
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
ban := d.BuvidBan(c, id, mid, ip, buvid)
convCtx.Convey("Then ban should not be nil.", func(convCtx convey.C) {
convCtx.So(ban, convey.ShouldNotBeNil)
})
})
})
}

View File

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

View File

@@ -0,0 +1,38 @@
package http
import (
"net/http"
"go-common/app/job/main/favorite/conf"
"go-common/app/job/main/favorite/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
var favSvc *service.Service
// Init init http
func Init(c *conf.Config, s *service.Service) {
favSvc = s
// init external router
engineOut := bm.DefaultServer(c.BM)
outerRouter(engineOut)
// init Outer serve
if err := engineOut.Start(); err != nil {
log.Error("bm.DefaultServer error(%v)", err)
panic(err)
}
}
// outerRouter init outer router api path.
func outerRouter(e *bm.Engine) {
e.Ping(ping)
}
// ping check server ok.
func ping(c *bm.Context) {
if err := favSvc.Ping(c); err != nil {
log.Error("favorite http service ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}

View File

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

View File

@@ -0,0 +1,194 @@
package model
import (
"database/sql/driver"
"encoding/json"
"errors"
"strconv"
"time"
)
var (
// ErrFavResourceExist error this has been favoured.
ErrFavResourceExist = errors.New("error this has been favoured")
// ErrFavResourceAlreadyDel error this has been unfavoured.
ErrFavResourceAlreadyDel = errors.New("error this has been unfavoured")
)
const (
// CacheNotFound .
CacheNotFound = -1
// SyncInsert binlog action.
SyncInsert = "insert"
// SyncUpdate binlog action.
SyncUpdate = "update"
// SyncDelete binlog action.
SyncDelete = "delete"
)
// CanelMessage binlog message.
type CanelMessage struct {
Action string `json:"action"`
Table string `json:"table"`
New json.RawMessage `json:"new"`
Old json.RawMessage `json:"old"`
}
// OldCount .
type OldCount struct {
ID int64 `json:"id"`
Aid int64 `json:"aid"`
Count int64 `json:"count"`
CTime Stime `json:"ctime"`
MTime Stime `json:"mtime"`
}
// NewCount .
type NewCount struct {
ID int64 `json:"id"`
Type int8 `json:"type"`
Oid int64 `json:"oid"`
Count int64 `json:"count"`
CTime Stime `json:"ctime"`
MTime Stime `json:"mtime"`
}
// OldFolder .
type OldFolder struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Name string `json:"name"`
CurCount int `json:"cur_count"`
State int8 `json:"state"`
CTime Stime `json:"ctime"`
MTime Stime `json:"mtime"`
}
// NewFolder .
type NewFolder struct {
ID int64 `json:"id"`
Type int8 `json:"type"`
Mid int64 `json:"mid"`
Name string `json:"name"`
Count int `json:"count"`
Attr int8 `json:"attr"`
State int8 `json:"state"`
CTime Stime `json:"ctime"`
MTime Stime `json:"mtime"`
}
// VideoFolder .
type VideoFolder struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Fid int64 `json:"fid"`
VideoFid int64 `json:"video_fid"`
CTime Stime `json:"ctime"`
MTime Stime `json:"mtime"`
}
// OldVideo .
type OldVideo struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Fid int64 `json:"fid"`
Aid int64 `json:"aid"`
CTime Stime `json:"ctime"`
MTime Stime `json:"mtime"`
}
// NewRelation .
type NewRelation struct {
ID int64 `json:"id"`
Type int8 `json:"type"`
Mid int64 `json:"mid"`
Fid int64 `json:"fid"`
Oid int64 `json:"oid"`
State int8 `json:"state"`
CTime Stime `json:"ctime"`
MTime Stime `json:"mtime"`
}
// OldFolderSort .
type OldFolderSort struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Sort string `json:"sort"`
CTime Stime `json:"ctime"`
MTime Stime `json:"mtime"`
}
// NewFolderSort .
type NewFolderSort struct {
ID int64 `json:"id"`
Type int8 `json:"type"`
Mid int64 `json:"mid"`
Sort []byte `json:"sort"`
CTime Stime `json:"ctime"`
MTime Stime `json:"mtime"`
}
// Stime .
type Stime int64
// Scan scan time.
func (st *Stime) Scan(src interface{}) (err error) {
switch sc := src.(type) {
case time.Time:
*st = Stime(sc.Unix())
case string:
var i int64
i, err = strconv.ParseInt(sc, 10, 64)
*st = Stime(i)
}
return
}
// Value get time value.
func (st Stime) Value() (driver.Value, error) {
return time.Unix(int64(st), 0), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (st *Stime) UnmarshalJSON(data []byte) error {
timestamp, err := strconv.ParseInt(string(data), 10, 64)
if err == nil {
*st = Stime(timestamp)
return nil
}
t, err := time.ParseInLocation(`"2006-01-02 15:04:05"`, string(data), time.Local)
if err == nil {
*st = Stime(t.Unix())
}
return nil
}
// StatMsg .
type StatMsg struct {
Play *int64 `json:"play"`
Fav *int64 `json:"fav"`
Share *int64 `json:"share"`
Oid int64 `json:"oid"`
}
// StatCount .
type StatCount struct {
Type string `json:"type"`
ID int64 `json:"id"`
Count int64 `json:"count"`
DisLike int64 `json:"dislike_count"`
TimeStamp int64 `json:"timestamp"`
}
// PlayReport .
type PlayReport struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
LV string `json:"lv"`
IP string `json:"ip"`
Buvid string `json:"buvid"`
DeviceID string `json:"device_id"`
UA string `json:"ua"`
Refer string `json:"refer"`
TS int64 `json:"ts"`
}

View File

@@ -0,0 +1,19 @@
package model
const (
// type
FieldFav = "folder"
FieldArc = "video"
FieldResource = "resource"
// action
ActionAdd = "add"
ActionDel = "del"
ActionMove = "move"
ActionCopy = "copy"
ActionMdel = "mdel"
ActionIndef = "indef"
ActionIncol = "incol"
ActionClean = "clean"
ActionInitRelationFids = "initRelationFids"
ActionInitFolderRelations = "initFolderRelations"
)

View File

@@ -0,0 +1,12 @@
package model
type MusicResult struct {
Code int `json:"code"`
Data map[int64]*Music `json:"data"`
}
type Music struct {
ID int64 `json:"song_id"`
Cover string `json:"cover_url"`
Title string `json:"title"`
}

View File

@@ -0,0 +1,110 @@
package model
import (
"errors"
"go-common/library/time"
)
var (
// ErrFavVideoExist error video has been favoured.
ErrFavVideoExist = errors.New("error video has been favoured")
// ErrFavVideoAlreadyDel error video has been unfavoured.
ErrFavVideoAlreadyDel = errors.New("error video has been unfavoured")
)
const (
bit1 = int8(1)
bit2 = int8(1) << 1
// StateDefaultPublic default public folder.
StateDefaultPublic = int8(0) // binary 00 / int 0
// StateDefaultNoPublic default private folder.
StateDefaultNoPublic = int8(0) | bit1 // binary 01 / int 1
// StateNormalPublic nomal public folder.
StateNormalPublic = bit2 | int8(0) // binary 10 / int 2
// StateNormalNoPublic nomal private folder.
StateNormalNoPublic = bit2 | bit1 // binary 11 / int 3
// DefaultFolderName name of favorite folder.
DefaultFolderName = "默认收藏夹"
)
// Favorite .
type Favorite struct {
Fid int64 `json:"fid"`
Mid int64 `json:"mid"`
Name string `json:"name"`
MaxCount int `json:"max_count"`
CurCount int `json:"cur_count"`
AttenCount int `json:"atten_count"`
State int8 `json:"state"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"-"`
Cover []*Cover `json:"cover,omitempty"`
}
// Archive .
type Archive struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Fid int64 `json:"fid"`
Aid int64 `json:"aid"`
CTime time.Time `json:"-"`
MTime time.Time `json:"-"`
}
// IsPublic return true if folder is public.
func (f *Favorite) IsPublic() bool {
return f.State&bit1 == int8(0)
}
// IsDefault return true if folder is default.
func (f *Favorite) IsDefault() bool {
return f.State&bit2 == int8(0)
}
// StatePub return folder's public state.
func (f *Favorite) StatePub() int8 {
return f.State & bit1
}
// StateDef return folder's default state.
func (f *Favorite) StateDef() int8 {
return f.State & bit2
}
// IsDefault return true if state is default state.
func IsDefault(state int8) bool {
return (state&(int8(1)<<1) == int8(0))
}
// CheckPublic check user update public value in [0, 1].
func CheckPublic(state int8) bool {
return state == int8(0) || state == bit1
}
// Favorites .
type Favorites []*Favorite
func (f Favorites) Len() int { return len(f) }
func (f Favorites) Less(i, j int) bool {
if f[i].State < f[j].State {
return true
}
if f[i].State == f[j].State && f[i].MaxCount > f[j].MaxCount {
return true
}
if f[i].State == f[j].State && f[i].MaxCount <= f[j].MaxCount && f[i].CTime < f[j].CTime {
return true
}
return false
}
func (f Favorites) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
// Cover image
type Cover struct {
Aid int64 `json:"aid"`
Pic string `json:"pic"`
}

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 = [
"fav_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/favorite/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"fav.go",
"service.go",
"stat.go",
],
importpath = "go-common/app/job/main/favorite/service",
tags = ["automanaged"],
deps = [
"//app/interface/openplatform/article/model:go_default_library",
"//app/interface/openplatform/article/rpc/client:go_default_library",
"//app/job/main/favorite/conf:go_default_library",
"//app/job/main/favorite/dao/fav:go_default_library",
"//app/job/main/favorite/dao/music:go_default_library",
"//app/job/main/favorite/dao/pub:go_default_library",
"//app/job/main/favorite/dao/stat:go_default_library",
"//app/job/main/favorite/model:go_default_library",
"//app/service/main/archive/api:go_default_library",
"//app/service/main/archive/api/gorpc:go_default_library",
"//app/service/main/archive/model/archive:go_default_library",
"//app/service/main/coin/api/gorpc:go_default_library",
"//app/service/main/coin/model:go_default_library",
"//app/service/main/favorite/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//library/time:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,960 @@
package service
import (
"context"
"fmt"
"sort"
"time"
"go-common/app/job/main/favorite/model"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
const gap uint64 = 1 << 47
const maxSquence uint64 = 1 + gap*64000
const initSquence uint64 = 1 + gap*2000
func (s *Service) upResource(c context.Context, msg *favmdl.Message) (err error) {
if msg.Otype == 0 {
//为了上线的时候兼容老的service逻辑
msg.Otype = msg.Type
}
if msg.Type == favmdl.TypeMusicNew {
msg.Type = favmdl.TypeVideo
}
switch msg.Action {
case favmdl.ActionAdd:
if err = s.addFav(c, msg.Otype, msg.Mid, msg.Fid, msg.Oid, msg.FTime, msg.Type); err != nil {
return
}
s.delRecentOidsMc(c, msg.Type, msg.Mid)
s.upFolderCnt(c, msg.Type, msg.Mid, msg.Fid, msg.FTime)
s.upFavStat(c, msg.Otype, msg.Mid, msg.Oid, msg.FTime, true)
case favmdl.ActionDel:
if err = s.delFav(c, msg.Otype, msg.Mid, msg.Fid, msg.Oid, msg.FTime); err != nil {
return
}
s.delRecentOidsMc(c, msg.Type, msg.Mid)
s.upFolderCnt(c, msg.Type, msg.Mid, msg.Fid, msg.FTime)
s.upFavStat(c, msg.Otype, msg.Mid, msg.Oid, msg.FTime, false)
case favmdl.ActionMultiAdd:
s.batchUpdateFavSeqsByAadd(c, msg.Mid, msg.Fid, msg.Type, msg.Oids)
s.upFolderCnt(c, msg.Type, msg.Mid, msg.Fid, msg.FTime)
s.upFavStats(c, msg.Otype, msg.Mid, msg.Oids, msg.FTime, true)
s.delRecentOidsMc(c, msg.Type, msg.Mid)
case favmdl.ActionMultiDel:
s.upFolderCnt(c, msg.Type, msg.Mid, msg.Fid, msg.FTime)
s.upFavStats(c, msg.Otype, msg.Mid, msg.Oids, msg.FTime, false)
s.delRecentOidsMc(c, msg.Type, msg.Mid)
s.initFolderRelations(c, msg.Type, msg.Mid, msg.Fid)
s.initAllRelations(c, msg.Mid, msg.Fid)
case model.ActionCopy:
s.batchUpdateFavSeqsByAadd(c, msg.Mid, msg.NewFid, msg.Type, msg.Oids)
s.upFolderCnt(c, msg.Type, msg.Mid, msg.NewFid, msg.FTime)
s.upFavStats(c, msg.Otype, msg.Mid, msg.Oids, msg.FTime, true)
s.delRecentOidsMc(c, msg.Type, msg.Mid)
s.initFolderRelations(c, msg.Type, msg.Mid, msg.NewFid)
s.initAllRelations(c, msg.Mid, msg.NewFid)
case model.ActionMove:
s.batchUpdateFavSeqsByAadd(c, msg.Mid, msg.NewFid, msg.Type, msg.Oids)
s.upFolderCnt(c, msg.Type, msg.Mid, msg.NewFid, msg.FTime)
s.upFolderCnt(c, msg.Type, msg.Mid, msg.OldFid, msg.FTime)
s.delRecentOidsMc(c, msg.Type, msg.Mid)
s.initFolderRelations(c, msg.Type, msg.Mid, msg.NewFid)
s.initAllRelations(c, msg.Mid, msg.NewFid)
case model.ActionClean:
s.cleanInvalidFavs(c, msg.Type, msg.Mid, msg.Fid, msg.FTime)
s.upFolderCnt(c, msg.Type, msg.Mid, msg.Fid, msg.FTime)
s.delRecentOidsMc(c, msg.Type, msg.Mid)
s.initFolderRelations(c, msg.Type, msg.Mid, msg.Fid)
s.initAllRelations(c, msg.Mid, msg.Fid)
case favmdl.ActionFolderDel:
if err = s.delRelationsByFid(c, msg.Type, msg.Mid, msg.Fid, msg.FTime); err != nil {
log.Error("s.delRelationsByFid(%d,%d,%d) error(%v)", msg.Type, msg.Mid, msg.Fid, err)
}
if err = s.favDao.DelRelationsCache(c, msg.Mid, msg.Fid); err != nil {
log.Error("s.favDao.DelRelationsCache(%d,%d) error(%v)", msg.Mid, msg.Fid, err)
}
if err = s.favDao.DelAllRelationsCache(c, msg.Mid, msg.Fid); err != nil {
log.Error("s.favDao.DelAllRelationsCache(%d,%d) error(%v)", msg.Mid, msg.Fid, err)
}
s.delRecentOidsMc(c, msg.Type, msg.Mid)
case favmdl.ActionInitFolderRelations:
s.initFolderRelations(c, msg.Type, msg.Mid, msg.Fid)
case favmdl.ActionInitAllFolderRelations:
s.initAllRelations(c, msg.Mid, msg.Fid)
case favmdl.ActionInitRelationFids:
var ok bool
if ok, err = s.favDao.ExpireRelationOids(c, msg.Otype, msg.Mid); err != nil || ok {
return
}
var rfmap map[int64][]int64
if rfmap, err = s.favDao.RelationFids(c, msg.Otype, msg.Mid); err != nil {
log.Error("favDao.FavedFids(%d,%d) error(%v)", msg.Otype, msg.Mid, err)
return
}
if len(rfmap) == 0 {
if err = s.favDao.SetUnFavedBit(c, msg.Otype, msg.Mid); err != nil {
log.Error("s,favDao.SetUnFavedBit(type:%d,mid:%d) error(%v)", msg.Otype, msg.Mid, err)
}
} else {
var oids []int64
for oid := range rfmap {
oids = append(oids, oid)
}
if err = s.favDao.SetRelationOidsCache(c, msg.Otype, msg.Mid, oids); err != nil {
log.Error("s.favDao.SetRelationOidsCache(%d,%d,%v) error(%v)", msg.Otype, msg.Mid, oids, err)
}
}
case favmdl.ActionSortFavs:
s.sortFavs(c, msg)
}
return
}
func (s *Service) batchUpdateFavSeqsByAadd(c context.Context, mid, fid int64, typ int8, oids []int64) {
log.Info("begin batchUpdateFavSeqsByAadd(%d,%d,%d,%+v)", mid, fid, typ, oids)
favs, err := s.favDao.RelationsByOids(c, typ, mid, fid, oids)
if err != nil {
log.Error("s.RelationsByOids(%d,%d,%d,%v) failed!err:=%v", typ, mid, fid, oids, err)
return
}
next, err := s.nextSquence(c, mid, fid)
if err != nil {
log.Error("s.nextSquence(%d,%d) failed!err:=%v", mid, fid, err)
return
}
for _, fav := range favs {
fav.Sequence = next
if next >= maxSquence {
next++
} else {
next += gap
}
}
if err = s.batchUpdateSeqs(c, mid, fid, favs); err != nil {
log.Error("s.batchUpdateSeqs (%d,%+v) err:=%v", mid, favs, err)
}
if err = s.favDao.DelAllRelationsCache(c, mid, fid); err != nil {
err = nil
log.Error("s.favDao.DelAllRelationsCache(%d,%d) err(%v)", mid, fid, err)
}
}
func (s *Service) sortFavs(c context.Context, msg *favmdl.Message) {
favs, err := s.favDao.AllRelations(c, msg.Mid, msg.Fid, 0, 1024)
if err != nil {
log.Error("s.favDao.AllRelations(%d,%d,%d,%d) error(%v)", msg.Mid, msg.Fid, 0, 2000, err)
return
}
if len(favs) > 1000 || len(favs) <= 1 {
log.Warn("sortFavs invalid fav(%d,%d) length:%d", msg.Mid, msg.Fid, len(favs))
return
}
favsM := make(map[int64]*favmdl.Favorite)
for i := range favs {
favsM[favs[i].ResourceID()] = favs[i]
}
changed := make([]*favmdl.Favorite, 0)
var reCount = 0
sortFavsDesc(favs)
if favs[len(favs)-1].Sequence == 0 {
reSequence(favs)
reCount++
}
for _, req := range msg.SortFavs {
if req.Insert == nil {
return
}
target, ok := favsM[req.Insert.ResourceID()]
if !ok {
return
}
if req.Pre == nil {
max := favs[0].Sequence
if max >= maxSquence {
reCount++
reSequence(favs)
target.Sequence = favs[len(favs)-1].Sequence + gap
} else if max >= maxSquence-gap {
target.Sequence = (max + maxSquence + 1) / 2
} else {
target.Sequence = max + gap
}
} else {
pre, ok := favsM[req.Pre.ResourceID()]
if !ok {
return
}
idx := searchIdx(favs, pre.Sequence)
if idx == -1 {
return
}
if idx < len(favs)-1 {
next := favs[idx+1]
if next.Oid == req.Insert.Oid && next.Type == int8(req.Insert.Typ) {
// already sorted
continue
}
if next.Sequence-pre.Sequence <= 1 {
reCount++
reSequence(favs)
}
target.Sequence = (pre.Sequence + next.Sequence) / 2
} else {
min := pre.Sequence
if min <= 1 {
// no space , need to reidx
reCount++
reSequence(favs)
target.Sequence = favs[0].Sequence - gap
} else if min <= 1+gap {
// insert into the gap
target.Sequence = min / 2
} else {
target.Sequence = min - gap
}
}
}
changed = append(changed, target)
sortFavsDesc(favs)
}
if reCount > 0 {
s.batchUpdateSeqs(c, msg.Mid, msg.Fid, favs)
} else {
s.batchUpdateSeqs(c, msg.Mid, msg.Fid, changed)
}
for i := range favs {
favs[len(favs)-1-i].Sequence = uint64(i)
}
if err = s.favDao.AddAllRelationsCache(c, msg.Mid, msg.Fid, favs); err != nil {
err = nil
log.Error("s.favDao.AddAllRelationsCache(%d,%d,%v) err(%v)", msg.Mid, msg.Fid, favs, err)
}
}
func (s *Service) batchUpdateSeqs(c context.Context, mid int64, fid int64, favs []*favmdl.Favorite) (err error) {
for i := 0; i < len(favs); i += 10000 {
end := i + 10000
if end > len(favs) {
end = len(favs)
}
_, err = s.favDao.BatchUpdateSeq(c, mid, favs[i:end])
if err != nil {
errStr := err.Error()
if len(errStr) > 200 {
errStr = errStr[:200]
}
log.Error("s.favDao.BatchUpdateSeq(%d,%v) err(%v)", mid, favs, errStr)
_, err = s.favDao.BatchUpdateSeq(c, mid, favs[i:end])
if err != nil {
errStr := err.Error()
if len(errStr) > 200 {
errStr = errStr[:200]
}
log.Error("s.favDao.BatchUpdateSeq(%d,%v) err(%v)", mid, favs, errStr)
return err
}
}
}
return
}
func searchIdx(favs []*favmdl.Favorite, sequence uint64) int {
i := sort.Search(len(favs), func(i int) bool { return favs[i].Sequence <= sequence })
if i < len(favs) && favs[i].Sequence == sequence {
return i
}
return -1
}
func sortFavsDesc(favs []*favmdl.Favorite) {
sort.Slice(favs, func(i, j int) bool {
if favs[i].Sequence == favs[j].Sequence {
return favs[i].MTime > favs[j].MTime
}
return favs[i].Sequence > favs[j].Sequence
})
}
// 重新计算所有数据的Sequence
func reSequence(favs []*favmdl.Favorite) {
seq := initSquence
last := len(favs) - 1
for i := range favs {
favs[last-i].Sequence = seq
seq += gap
}
}
func (s *Service) nextSquence(c context.Context, mid, fid int64) (uint64, error) {
max, err := s.favDao.MaxRelation(c, mid, fid)
if err != nil {
log.Error("s.favDao.MaxRelation(%d,%d) error(%v)", mid, fid, err)
return 0, err
}
var seq uint64
if max == nil {
seq = initSquence
} else if max.Sequence == 0 {
var cnt int
cnt, err = s.favDao.RelationCnt(c, mid, fid)
if err != nil {
log.Error("s.favDao.RelationCnt(%d,%d) error(%v)", mid, fid, err)
return 0, err
}
if cnt <= 50000 {
seq = initSquence + uint64(cnt+10)*gap
} else {
log.Error("nextSquence: can't add res over 50000")
err = ecode.FavMaxVideoCount
return 0, err
}
} else if max.Sequence+gap <= maxSquence {
seq = max.Sequence + gap
} else {
seq = max.Sequence + 1
}
return seq, nil
}
func (s *Service) addFav(c context.Context, typ int8, mid, fid, oid, ftime int64, ftype int8) (err error) {
v := &favmdl.Favorite{
Type: typ,
Mid: mid,
Fid: fid,
Oid: oid,
CTime: xtime.Time(ftime),
MTime: xtime.Time(ftime),
}
v.Sequence, err = s.nextSquence(c, mid, fid)
if err != nil {
return
}
rows, err := s.favDao.AddRelation(c, v)
if err != nil {
log.Error("s.favDao.AddRelation(%v) error(%v)", v, err)
return
}
if rows < 1 {
log.Warn("type(%d) oid(%d) already favoured", typ, oid)
err = model.ErrFavResourceExist
return
}
v.ID = rows
err = s.cache.Do(c, func(c context.Context) {
if err = s.favDao.SetFavedBit(c, typ, mid); err != nil {
log.Error("s.favDao.SetFavedBit(%d, %d) error(%v)", typ, mid, err)
}
var ok bool
if ftype == typ {
if ok, err = s.favDao.ExpireRelations(c, mid, fid); err != nil {
log.Error("s.favDao.ExpireRelations(%d,%d) error(%v)", mid, fid, err)
}
if ok {
if err = s.favDao.AddRelationCache(c, v); err != nil {
log.Error("s.favDao.AddRelationCache(%d, %d,%d) error(%v)", typ, mid, fid, err)
}
}
}
if ok, err = s.favDao.ExpireAllRelations(c, mid, fid); err != nil {
log.Error("s.favDao.ExpireAllRelations(%d,%d) error(%v)", mid, fid, err)
}
if ok {
if err = s.favDao.AddAllRelationCache(c, v); err != nil {
log.Error("s.favDao.AddAllRelationCache(%d, %d,%d) error(%v)", typ, mid, fid, err)
}
}
if ok, err = s.favDao.ExpireRelationOids(c, typ, mid); err != nil {
log.Error("s.favDao.ExpireRelationOids(%d,%d) error(%v)", typ, mid, err)
}
if ok {
if err = s.favDao.AddRelationOidCache(c, typ, mid, oid); err != nil {
log.Error("s.favDao.AddRelationOidCache(%d,%d,%d) error(%v)", typ, mid, oid, err)
}
}
if err = s.favDao.DelRelationFidsMc(c, typ, mid, oid); err != nil {
log.Error("s.favDao.DelRelationFidsMc(%d,%d,%d) error(%v)", typ, mid, oid, err)
}
})
if err != nil {
log.Error("s.cache.Do error(%v)", err)
}
return
}
// DelFav delete a favorite.
func (s *Service) delFav(c context.Context, typ int8, mid, fid, oid, ftime int64) (err error) {
rows, err := s.favDao.DelRelation(c, typ, mid, fid, oid, xtime.Time(ftime))
if err != nil {
log.Error("s.favDao.DelRelation(%d,%d,%d) error(%v)", typ, oid, fid, err)
return
}
if rows < 1 {
log.Warn("s.favDao.DelRelation(%d,%d,%d,%d) have no del", typ, mid, fid, oid)
err = ecode.FavResourceAlreadyDel
return
}
err = s.cache.Do(c, func(c context.Context) {
if err1 := s.favDao.DelRelationCache(c, mid, fid, oid); err1 != nil {
log.Error("s.favDao.DelRelationCache(%d,%d,%d) error(%v)", mid, fid, oid, err1)
}
if err1 := s.favDao.DelAllRelationCache(c, mid, fid, oid, typ); err1 != nil {
log.Error("s.favDao.DelAllRelationCache(%d,%d,%d) error(%v)", mid, fid, oid, err1)
}
if err1 := s.favDao.DelRelationFidsMc(c, typ, mid, oid); err1 != nil {
log.Error("s.favDao.DelRelationFidsMc(%d,%d,%d) error(%v)", typ, mid, oid, err1)
}
})
if err != nil {
log.Error("s.cache.Do error(%v)", err)
}
return
}
func (s *Service) setAllRelationCache(c context.Context, mid, fid int64) (err error) {
var (
mtime = xtime.Time(0)
pageSize = 8000
favss []*favmdl.Favorite
)
for {
favs, err1 := s.favDao.AllRelations(c, mid, fid, mtime, pageSize)
if err1 != nil {
if err = s.favDao.DelAllRelationsCache(c, mid, fid); err != nil {
log.Error("s.favDao.DelAllRelationsCache(%d,%d) error(%v)", mid, fid, err)
}
return err1
}
if len(favs) == 0 {
break
}
mtime = favs[len(favs)-1].MTime
if mtime == favs[0].MTime {
mtime++
}
favss = append(favss, favs...)
}
if len(favss) <= 0 {
return
}
sortFavsDesc(favss)
var needUpdateSeq bool
if favss[len(favss)-1].Sequence == 0 {
needUpdateSeq = true
}
for i := range favss {
favss[len(favss)-1-i].Sequence = uint64(i)
}
if err = s.favDao.AddAllRelationsCache(c, mid, fid, favss); err != nil {
log.Error("s.favDao.AddAllRelationsCache(%d,%d,%d,%v) error(%v)", mid, fid, favss, err)
}
if needUpdateSeq {
reSequence(favss)
s.batchUpdateSeqs(c, mid, fid, favss)
}
return
}
func (s *Service) setRelationCache(c context.Context, tp int8, mid, fid int64) (err error) {
var (
mtime = xtime.Time(0)
pageSize = 8000
favss []*favmdl.Favorite
)
for {
favs, err1 := s.favDao.Relations(c, tp, mid, fid, mtime, pageSize)
if err1 != nil {
log.Error("s.favDao.Relations(%d,%d,%d,%d,%d) error(%v)", tp, mid, fid, mtime, pageSize, err)
if err = s.favDao.DelRelationsCache(c, mid, fid); err != nil {
log.Error("s.favDao.DelRelationsCache(%d,%d) error(%v)", mid, fid, err)
}
return err1
}
if len(favs) == 0 {
break
}
mtime = favs[len(favs)-1].MTime
if mtime == favs[0].MTime {
mtime++
}
favss = append(favss, favs...)
}
if err = s.favDao.AddRelationsCache(c, tp, mid, fid, favss); err != nil {
log.Error("s.favDao.AddRelationsCache(%d,%d,%d,%v) error(%v)", tp, mid, fid, favss, err)
}
return
}
func (s *Service) upFolderCnt(c context.Context, tp int8, mid, fid, ftime int64) (err error) {
cnt, err := s.favDao.RelationCnt(c, mid, fid)
if err != nil {
log.Error("s.favDao.CntRelations(%d,%d,%d) error(%v)", tp, mid, fid, err)
return
}
folder, err := s.folder(c, tp, mid, fid)
if err != nil {
return
}
if _, err = s.favDao.UpFolderCnt(c, mid, fid, cnt, xtime.Time(ftime)); err != nil {
log.Error("s.favDao.UpFolderCnt(%d,%d,%d,%d) error(%v)", mid, fid, ftime, cnt, err)
return
}
folder.Count = cnt
folder.MTime = xtime.Time(ftime)
var recent []*favmdl.Resource
if recent, err = s.favDao.RecentRes(c, mid, fid); err != nil {
log.Error(" s.favDao.RecentRes(%d,%d) error(%v) or folder is nil", mid, fid, err)
err = nil
}
folder.RecentOids = []int64{}
folder.RecentRes = []*favmdl.Resource{}
if len(recent) > 0 {
folder.RecentRes = recent
for _, res := range recent {
if res.Typ == int32(tp) {
folder.RecentOids = append(folder.RecentOids, res.Oid)
}
}
}
err = s.cache.Do(c, func(c context.Context) {
if err1 := s.favDao.SetFoldersMc(c, folder); err1 != nil {
log.Error("s.favDao.SetFolderMc(%v) error(%v)", folder, err1)
}
if err1 := s.favDao.DelNewCoverCache(c, folder.Mid, folder.ID); err1 != nil {
log.Error("s.favDao.DelNewCoverCache(%v) error(%v)", folder, err1)
}
})
if err != nil {
log.Error("s.cache.Do error(%v)", err)
}
return
}
func (s *Service) folder(c context.Context, typ int8, mid, fid int64) (folder *favmdl.Folder, err error) {
if folder, err = s.favDao.FolderMc(c, typ, mid, fid); err != nil {
log.Error("s.favDao.FolderMc(%d,%d) error(%v)", mid, fid, err)
err = nil
}
if folder == nil {
if folder, err = s.favDao.Folder(c, typ, mid, fid); err != nil {
log.Error("favDao.Folder(%d,%d) error(%v) or folder is nil", mid, fid, err)
return
}
}
if folder == nil {
err = ecode.FavFolderNotExist
}
return
}
// upFavStats update upFavStat.
func (s *Service) upFavStats(c context.Context, typ int8, mid int64, oids []int64, now int64, isAdd bool) error {
for _, oid := range oids {
if err := s.upFavStat(c, typ, mid, oid, now, isAdd); err != nil {
log.Error("s.upFavStat(%d,%d,%d,%d,%d) error(%v)", typ, mid, oid, now, isAdd, err)
return err
}
}
return nil
}
// upFavStat update update resource fav count.
func (s *Service) upFavStat(c context.Context, tp int8, mid, oid, now int64, isAdd bool) error {
fids, err := s.favDao.RelationFidsByOid(c, tp, mid, oid)
if err != nil {
log.Error("s.favDao.RelationFidsByOid(%d,%d,%d) error(%v)", tp, mid, oid, err)
return err
}
if len(fids) != 0 {
if err1 := s.favDao.SetRelaitonFidsMc(c, tp, mid, oid, fids); err != nil {
log.Error("s.favDao.SetRelaitonFidsMc(%d,%d,%d) error(%v)", tp, mid, oid, err1)
}
}
length := len(fids)
var incr int
if isAdd && length == 1 {
incr = 1
err = s.addFavOperations(c, tp, mid, oid, now)
if err != nil {
return err
}
} else if !isAdd && length == 0 {
incr = -1
err = s.delFavOperations(c, tp, mid, oid, now)
if err != nil {
return err
}
}
if incr != 0 {
cnt, err := s.favDao.StatCnt(c, tp, oid)
if err != nil {
return err
}
if (cnt + incr) < 0 {
return nil
}
rows, err := s.favDao.UpStatCnt(c, tp, oid, incr, xtime.Time(now))
if err != nil {
log.Error("s.favDao.UpStatCnt(%d,%d,%d) error(%v)", tp, oid, incr, err)
return err
}
if rows < 1 {
log.Warn("s.favDao.UpStatCnt(%d,%d,%d) rows(%d)", tp, oid, incr, rows)
return nil
}
err = s.cache.Do(c, func(c context.Context) {
if err = s.favDao.SetOidCountMc(c, tp, oid, int64(cnt+incr)); err != nil {
log.Error("s.favDao.SetOidCountMc(%d,%d,%d) error(%v)", tp, oid, int64(cnt+incr), err)
}
if err = s.favDao.DelBatchOidsMc(c, tp, mid); err != nil {
log.Error("s.favDao.SetOidCountMc(%d,%d) error(%v)", tp, mid, err)
}
})
if err != nil {
log.Error("s.cache.Do error(%v)", err)
}
s.addCoin(c, isAdd, cnt+incr, tp, oid)
s.pubDao.PubStats(c, tp, oid, int64(cnt+incr))
// bnj merge stat
if err1 := s.bnjStatMerge(c, tp, oid, incr); err1 != nil {
log.Error("s.bnjMergeStat(%d,%d,%d) error(%v)", tp, oid, incr, err1)
}
}
return nil
}
func (s *Service) bnjStatMerge(c context.Context, typ int8, oid int64, incr int) (err error) {
target := s.mergeTarget(int(typ), oid)
if target <= 0 {
return
}
cnt, err := s.favDao.StatCnt(c, typ, target)
if err != nil {
return
}
rows, err := s.favDao.UpStatCnt(c, typ, target, incr, xtime.Time(time.Now().Unix()))
if err != nil || rows < 1 {
log.Error("s.favDao.UpStatCnt(%d,%d,%d,%d) error(%v)", typ, target, incr, rows, err)
return
}
s.pubDao.PubStats(c, typ, target, int64(cnt+incr))
return
}
func (s *Service) addCoin(c context.Context, isAdd bool, count int, tp int8, oid int64) (err error) {
var (
mid int64
msgAdd, msgDel string
)
mod := count % 200
if mod != 0 && mod != 199 {
return
}
switch tp {
case favmdl.Article:
article, err := s.articleRPC(c, oid)
if err != nil {
log.Error("s.favDao.ArticleRPC error(%v)", oid, err)
return err
}
meta, ok := article[oid]
if !ok || meta == nil {
log.Error("article martmdl.Meta(%v) error(%v)", article, err)
return err
}
mid = meta.Author.Mid
msgAdd = "专栏CV%d新增200人收藏总收藏%d"
msgDel = "专栏CV%d有200人取消收藏总收藏%d"
case favmdl.TypeVideo:
archive, err := s.archiveRPC(c, oid)
if err != nil {
log.Error("s.favDao.archiveRPC error(%v)", oid, err)
return err
}
mid = archive.Author.Mid
msgAdd = "稿件AV%d新增200人收藏总收藏%d"
msgDel = "稿件AV%d有200人取消收藏总收藏%d"
default:
log.Warn("this type(%d) need not to add coin", tp)
return
}
// add money to upper
if isAdd && mod == 0 {
if err := s.addCoinRPC(c, mid, 1, fmt.Sprintf(msgAdd, oid, count)); err != nil {
log.Error("s.addCoinRPC(%d,%s) error(%v)", mid, fmt.Sprintf(msgAdd, oid, count), err)
return err
}
}
if !isAdd && mod == 199 {
if err := s.addCoinRPC(c, mid, -1, fmt.Sprintf(msgDel, oid, count)); err != nil {
log.Error("s.addCoinRPC(%d,%s) error(%v)", mid, fmt.Sprintf(msgAdd, oid, count), err)
return err
}
}
return
}
func (s *Service) delRelationsByFid(c context.Context, typ int8, mid, fid, ftime int64) (err error) {
var (
offset int
count = s.c.Fav.MaxPageSize
)
typs := []int8{typ}
if typ == 2 {
// 收藏夹type=2是混合类型的收藏夹需要删除多个type的稿件关系,现在只有music所以只要append 12
typs = append(typs, 12)
}
for _, tp := range typs {
for {
var (
rows int64
oids []int64
)
if oids, err = s.favDao.OidsByFid(c, tp, mid, fid, offset, count); err != nil {
log.Error("s.favDao.OidsByFid(%d,%d,%d,%d,%d) error(%v)", tp, mid, fid, offset, count, err)
time.Sleep(time.Millisecond * 500) // avoid endless loop
continue
}
if len(oids) == 0 {
break
}
if rows, err = s.favDao.DelRelationsByOids(c, tp, mid, fid, oids, xtime.Time(ftime)); err != nil {
log.Error("s.favDao.DelRelationsByOids(%d,%d,%d,%v) error(%v)", tp, mid, fid, oids, err)
time.Sleep(time.Millisecond * 500) // avoid endless loop
continue
}
offset += count
if rows != int64(len(oids)) {
log.Error("rows!=int64(len(oids)) rows:%d,len(aids):%d", rows, len(oids))
}
if rows > 0 {
s.upFavStats(c, tp, mid, oids, ftime, false)
}
time.Sleep(time.Duration(s.c.Fav.SleepTime)) // for binlog cunsumers
}
}
return
}
func (s *Service) addFavOperations(c context.Context, typ int8, mid, oid, now int64) (err error) {
ok, err := s.favDao.ExpireRelationOids(c, typ, mid)
if err != nil {
log.Error("s.favDao.ExpireRelationFids(%d,%d) error(%v)", typ, mid, err)
} else if ok {
if err = s.favDao.AddRelationOidCache(c, typ, mid, oid); err != nil {
log.Error("s.favDao.AddRelationOidCache(%d,%d,%d) error(%v)", typ, mid, oid, err)
}
}
if typ < favmdl.TypeBangumi {
err = nil
return
}
u := &favmdl.User{
Type: typ,
Oid: oid,
Mid: mid,
CTime: xtime.Time(now),
MTime: xtime.Time(now),
}
rows, err := s.favDao.AddUser(c, u)
if err != nil {
log.Error("s.favDao.AddUser(%+v) error(%v)", u, err)
return
}
if rows == 0 {
log.Warn("s.favDao.DelUser(%+v) rows(%v)", u, rows)
}
return
}
func (s *Service) delFavOperations(c context.Context, typ int8, mid, oid, now int64) (err error) {
if err = s.favDao.RemRelationOidCache(c, typ, mid, oid); err != nil {
log.Error("s.favDao.RemRelationOidCache(%d,%d,%d) error(%v)", typ, mid, oid, err)
err = nil
}
if typ < favmdl.TypeBangumi {
return
}
u := &favmdl.User{
Type: typ,
Oid: oid,
Mid: mid,
State: favmdl.StateIsDel,
CTime: xtime.Time(now),
MTime: xtime.Time(now),
}
rows, err := s.favDao.DelUser(c, u)
if err != nil {
log.Error("s.favDao.DelUser(%+v) error(%v)", u, err)
return err
}
if rows == 0 {
log.Warn("s.favDao.DelUser(%+v) rows(%v)", u, rows)
}
return
}
func (s *Service) delRecentOidsMc(c context.Context, typ int8, mid int64) {
if err := s.favDao.DelRecentOidsMc(c, typ, mid); err != nil {
log.Error("s.favDao.DelRecentOidsMc(%d,%d) error(%v)", typ, mid, err)
}
if err := s.favDao.DelRecentResMc(c, favmdl.TypeVideo, mid); err != nil {
log.Error("s.favDao.DelRecentResMc(%d,%d) error(%v)", typ, mid, err)
}
}
func (s *Service) cleanInvalidFavs(c context.Context, typ int8, mid, fid, ftime int64) (err error) {
if typ != favmdl.TypeVideo {
return
}
var (
mtime = xtime.Time(0)
pageSize = 8000
batchCount = s.c.Fav.MaxPageSize
)
var oids = make(map[int64]struct{})
var musicIds = make(map[int64]struct{})
var batchOids []int64
for {
favs, err := s.favDao.AllRelations(c, mid, fid, mtime, pageSize)
if err != nil {
return err
}
if len(favs) == 0 {
break
}
mtime = favs[len(favs)-1].MTime
if mtime == favs[0].MTime {
mtime++
}
for _, fav := range favs {
if fav.Type == favmdl.TypeVideo {
oids[fav.Oid] = struct{}{}
} else if fav.Type == favmdl.TypeMusicNew {
musicIds[fav.Oid] = struct{}{}
}
}
}
for oid := range oids {
if len(batchOids) >= batchCount {
s.cleanVideoFavs(c, mid, fid, ftime, batchOids)
batchOids = batchOids[:0]
}
batchOids = append(batchOids, oid)
}
if len(batchOids) > 0 {
s.cleanVideoFavs(c, mid, fid, ftime, batchOids)
}
batchOids = batchOids[:0]
for oid := range musicIds {
if len(batchOids) >= batchCount {
s.cleanMuiscFavs(c, mid, fid, ftime, batchOids)
batchOids = batchOids[:0]
}
batchOids = append(batchOids, oid)
}
if len(batchOids) > 0 {
s.cleanMuiscFavs(c, mid, fid, ftime, batchOids)
}
batchOids = batchOids[:0]
err = s.favDao.SetCleanedCache(c, typ, mid, fid, ftime, s.cleanCDTime)
s.cache.Do(c, func(c context.Context) {
if err1 := s.favDao.DelRecentOidsMc(c, typ, mid); err1 != nil {
log.Error("s.favDao.DelRecentOidsMc(%d,%d) error(%v)", typ, mid, err1)
}
if err := s.favDao.DelRecentResMc(c, favmdl.TypeVideo, mid); err != nil {
log.Error("s.favDao.DelRecentResMc(%d,%d) error(%v)", typ, mid, err)
}
if err1 := s.favDao.DelRelationOidsCache(c, typ, mid); err1 != nil {
log.Error("s.favDao.DelRelationOidsCache(%d,%d) error(%v)", typ, mid, err1)
}
if err1 := s.favDao.DelRelationsCache(c, mid, fid); err1 != nil {
log.Error("s.favDao.DelRelationsCache(%d,%d) error(%v)", mid, fid, err1)
}
if err1 := s.favDao.DelAllRelationsCache(c, mid, fid); err1 != nil {
log.Error("s.favDao.DelAllRelationsCache(%d,%d) error(%v)", mid, fid, err1)
}
})
return
}
func (s *Service) cleanMuiscFavs(c context.Context, mid, fid, ftime int64, oids []int64) (err error) {
var delOids []int64
musics, err := s.musicDao.MusicMap(c, oids)
if err != nil {
log.Error("s.ArcsRPC(%v) error(%v)", oids, err)
return
}
for _, oid := range oids {
if _, ok := musics[oid]; !ok {
delOids = append(delOids, oid)
}
}
if len(delOids) > 0 {
var rows int64
if rows, err = s.favDao.DelRelationsByOids(c, favmdl.TypeMusicNew, mid, fid, delOids, xtime.Time(ftime)); err != nil {
log.Error("s.favDao.DelRelationsByOids(%d,%d,%v) error(%v)", favmdl.TypeMusicNew, mid, fid, delOids, err)
}
if rows > 0 {
s.upFavStats(c, favmdl.TypeMusicNew, mid, delOids, ftime, false)
}
}
return
}
func (s *Service) cleanVideoFavs(c context.Context, mid, fid, ftime int64, oids []int64) (err error) {
var delOids []int64
arcs, err := s.ArcsRPC(c, oids)
if err != nil {
log.Error("s.ArcsRPC(%v) error(%v)", oids, err)
return
}
for aid, arc := range arcs {
if arc.IsNormal() {
continue
}
delOids = append(delOids, aid)
}
if len(delOids) > 0 {
var rows int64
if rows, err = s.favDao.DelRelationsByOids(c, favmdl.TypeVideo, mid, fid, delOids, xtime.Time(ftime)); err != nil {
log.Error("s.favDao.DelRelationsByOids(%d,%d,%v) error(%v)", favmdl.TypeVideo, mid, fid, delOids, err)
}
if rows > 0 {
s.upFavStats(c, favmdl.TypeVideo, mid, delOids, ftime, false)
}
}
return
}
func (s *Service) initFolderRelations(c context.Context, typ int8, mid, fid int64) (err error) {
if fid <= 0 {
log.Warn("folderID must not be zero!%d %d", mid, fid)
return
}
var ok bool
if ok, err = s.favDao.ExpireRelations(c, mid, fid); err != nil || ok {
return
}
// 顺带更新folder的count
s.setRelationCache(c, typ, mid, fid)
return
}
func (s *Service) initAllRelations(c context.Context, mid, fid int64) (err error) {
if fid <= 0 {
log.Warn("folderID must not be zero!%d %d", mid, fid)
return
}
var ok bool
if ok, err = s.favDao.ExpireAllRelations(c, mid, fid); err != nil || ok {
return
}
// 顺带更新folder的count
s.setAllRelationCache(c, mid, fid)
return
}

View File

@@ -0,0 +1,50 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_setRelationCache(t *testing.T) {
Convey("setRelationCache", t, func() {
var (
typ int8 = 2
mid int64 = 88888894
fid int64 = 289
)
err := s.setRelationCache(context.TODO(), typ, mid, fid)
t.Logf("err:%v", err)
So(err, ShouldBeNil)
})
}
func Test_folder(t *testing.T) {
Convey("folder", t, func() {
var (
typ int8 = 1
mid int64 = 88888894
fid int64 = 1
)
res, err := s.folder(context.TODO(), typ, mid, fid)
t.Logf("res:%v", res)
t.Logf("err:%v", err)
So(res, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}
func Test_addCoin(t *testing.T) {
Convey("addMoney", t, func() {
var (
isAdd = true
count = 200
typ int8 = 1
oid int64 = 123
)
err := s.addCoin(context.TODO(), isAdd, count, typ, oid)
t.Logf("err:%v", err)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,241 @@
package service
import (
"context"
"encoding/json"
"sync"
"time"
artmdl "go-common/app/interface/openplatform/article/model"
artrpc "go-common/app/interface/openplatform/article/rpc/client"
"go-common/app/job/main/favorite/conf"
favDao "go-common/app/job/main/favorite/dao/fav"
musicDao "go-common/app/job/main/favorite/dao/music"
pubDao "go-common/app/job/main/favorite/dao/pub"
statDao "go-common/app/job/main/favorite/dao/stat"
"go-common/app/service/main/archive/api"
arcrpc "go-common/app/service/main/archive/api/gorpc"
arcmdl "go-common/app/service/main/archive/model/archive"
coinrpc "go-common/app/service/main/coin/api/gorpc"
coinmdl "go-common/app/service/main/coin/model"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/log"
"go-common/library/queue/databus"
"go-common/library/sync/pipeline/fanout"
)
// Service favorite service.
type Service struct {
c *conf.Config
waiter *sync.WaitGroup
// fav
cleanCDTime int64
// dao
pubDao *pubDao.Dao
statDao *statDao.Dao
favDao *favDao.Dao
// databus
consumer *databus.Databus
playStatSub *databus.Databus
favStatSub *databus.Databus
shareStatSub *databus.Databus
procChan []chan *favmdl.Message
// rpc
coinRPC *coinrpc.Service
arcRPC *arcrpc.Service2
artRPC *artrpc.Service
// cache chan
cache *fanout.Fanout
statMerge *statMerge
musicDao *musicDao.Dao
}
type statMerge struct {
Business int
Target int64
Sources map[int64]bool
}
// New new a service and return.
func New(c *conf.Config) (s *Service) {
if c.Fav.Proc <= 0 {
c.Fav.Proc = 32
}
s = &Service{
c: c,
waiter: new(sync.WaitGroup),
// fav
cleanCDTime: int64(time.Duration(c.Fav.CleanCDTime) / time.Second),
// dao
favDao: favDao.New(c),
pubDao: pubDao.New(c),
statDao: statDao.New(c),
musicDao: musicDao.New(c),
// databus
consumer: databus.New(c.JobDatabus),
procChan: make([]chan *favmdl.Message, c.Fav.Proc),
// stat databus
playStatSub: databus.New(c.MediaListCntDatabus),
favStatSub: databus.New(c.FavStatDatabus),
shareStatSub: databus.New(c.ShareStatDatabus),
// rpc
coinRPC: coinrpc.New(c.RPCClient2.Coin),
artRPC: artrpc.New(c.RPCClient2.Article),
arcRPC: arcrpc.New2(c.RPCClient2.Archive),
// cache chan
cache: fanout.New("cache"),
}
if c.StatMerge != nil {
s.statMerge = &statMerge{
Business: c.StatMerge.Business,
Target: c.StatMerge.Target,
Sources: make(map[int64]bool),
}
for _, id := range c.StatMerge.Sources {
s.statMerge.Sources[id] = true
}
}
for i := int64(0); i < c.Fav.Proc; i++ {
ch := make(chan *favmdl.Message, 128)
s.procChan[i] = ch
s.waiter.Add(1)
go s.jobproc(ch)
}
s.waiter.Add(1)
go s.consumeStat()
s.waiter.Add(1)
go s.consumeproc()
return
}
func (s *Service) consumeproc() {
offsets := make(map[int32]int64, 9)
defer func() {
log.Info("end databus msg offsets:%v", offsets)
s.waiter.Done()
}()
for {
msg, ok := <-s.consumer.Messages()
if !ok {
log.Info("consumeproc exit")
for _, c := range s.procChan {
close(c)
}
return
}
if _, ok := offsets[msg.Partition]; !ok {
log.Info("begin databus msg offsets:%v", offsets)
}
offsets[msg.Partition] = msg.Offset
msg.Commit()
m := &favmdl.Message{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
continue
}
if m.Mid <= 0 {
log.Warn("m.Mid shuld not be equal or lesser than zerom:%+v", m)
continue
}
log.Info("consumer topic:%s, partitionId:%d, offset:%d, Key:%s, Value:%s Mid:%d Proc:%d", msg.Topic, msg.Partition, msg.Offset, msg.Key, msg.Value, m.Mid, s.c.Fav.Proc)
s.procChan[m.Mid%s.c.Fav.Proc] <- m
}
}
func (s *Service) jobproc(ch chan *favmdl.Message) {
defer s.waiter.Done()
for {
m, ok := <-ch
if !ok {
log.Info("jobproc exit")
return
}
switch m.Field {
case favmdl.FieldResource:
if err := s.upResource(context.Background(), m); err != nil {
log.Error("upResource(%v) error(%v)", m, err)
continue
}
default:
}
}
}
// Close close.
func (s *Service) Close() (err error) {
if err = s.consumer.Close(); err != nil {
log.Error("s.consumer.Close() error(%v)", err)
return
}
return s.favDao.Close()
}
// Wait wait.
func (s *Service) Wait() {
s.waiter.Wait()
}
// Ping ping method for server check
func (s *Service) Ping(c context.Context) (err error) {
if err = s.favDao.Ping(c); err != nil {
log.Error("s.favDao.Ping error(%v)", err)
return
}
return
}
// ArcRPC find archive by rpc
func (s *Service) archiveRPC(c context.Context, aid int64) (a *api.Arc, err error) {
argAid := &arcmdl.ArgAid2{
Aid: aid,
}
if a, err = s.arcRPC.Archive3(c, argAid); err != nil {
log.Error("arcRPC.Archive3(%v, archive), err(%v)", argAid, err)
}
return
}
// AddCoinRpc check user whether or not banned to post
func (s *Service) addCoinRPC(c context.Context, mid int64, coin float64, reason string) (err error) {
if _, err = s.coinRPC.ModifyCoin(c, &coinmdl.ArgModifyCoin{Mid: mid, Count: coin, Reason: reason}); err != nil {
log.Error("coinRPC.ModifyCoin(%v, %v), err(%v)", mid, coin, err)
}
return
}
// articleRPC find aritile by rpc
func (s *Service) articleRPC(c context.Context, aid int64) (a map[int64]*artmdl.Meta, err error) {
argAid := &artmdl.ArgAids{
Aids: []int64{aid},
}
if a, err = s.artRPC.ArticleMetas(c, argAid); err != nil {
log.Error("d.artRPC.ArticleMetas(%+v), error(%v)", argAid, err)
}
return
}
// ArcsRPC find archives by rpc.
func (s *Service) ArcsRPC(c context.Context, aids []int64) (as map[int64]*api.Arc, err error) {
if len(aids) == 0 {
return
}
argAids := &arcmdl.ArgAids2{
Aids: aids,
}
if as, err = s.arcRPC.Archives3(c, argAids); err != nil {
log.Error("s.arcRPC.Archives3(%v), error(%v)", argAids, err)
}
return
}
func (s *Service) mergeTarget(business int, aid int64) int64 {
if s.statMerge != nil && s.statMerge.Business == business && s.statMerge.Sources[aid] {
return s.statMerge.Target
}
return 0
}

View File

@@ -0,0 +1,52 @@
package service
import (
"context"
"flag"
"fmt"
"path/filepath"
"testing"
"go-common/app/job/main/favorite/conf"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func init() {
dir, _ := filepath.Abs("../cmd/favorite-job-test.toml")
flag.Set("conf", dir)
err := conf.Init()
if err != nil {
fmt.Printf("conf.Init() error(%v)", err)
}
s = New(conf.Conf)
}
func Test_archiveRPC(t *testing.T) {
Convey("archiveRPC", t, func() {
var (
aid int64 = 123
)
res, err := s.archiveRPC(context.TODO(), aid)
t.Logf("res:%+v", res)
t.Logf("err:%v", err)
So(res, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}
func Test_ArcsRPC(t *testing.T) {
Convey("ArcsRPC", t, func() {
var (
aids = []int64{123, 456}
)
res, err := s.ArcsRPC(context.TODO(), aids)
t.Logf("res:%+v", res)
t.Logf("err:%v", err)
So(res, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,155 @@
package service
import (
"context"
"encoding/json"
"go-common/app/job/main/favorite/model"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/log"
)
// consumeStat consumes folder's stat.
func (s *Service) consumeStat() {
defer s.waiter.Done()
for {
select {
case msg, ok := <-s.playStatSub.Messages():
if !ok {
break
}
msg.Commit()
m := &model.PlayReport{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("fav json.Unmarshal(%s) error(%+v)", msg.Value, err)
continue
}
if s.intercept(context.TODO(), m.ID, m.Mid, m.IP, m.Buvid) { // 防刷
continue
}
if err := s.updatePlayStat(context.TODO(), m.ID); err != nil {
log.Error("s.updatePlayStat(%d) error(%v)", m.ID, err)
}
log.Info("consumePlayStat key:%s partition:%d offset:%d msg: %+v)", msg.Key, msg.Partition, msg.Offset, m)
case msg, ok := <-s.favStatSub.Messages():
if !ok {
break
}
msg.Commit()
m := &model.StatCount{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("fav json.Unmarshal(%s) error(%+v)", msg.Value, err)
continue
}
if m.Type != "fav_playlist" {
continue
}
if err := s.updateFavStat(context.TODO(), m.ID, m.Count); err != nil {
log.Error("s.updateFav(%d,%d) error(%v)", m.ID, m.Count, err)
}
log.Info("consumeFavStat key:%s partition:%d offset:%d msg: %+v)", msg.Key, msg.Partition, msg.Offset, m)
case msg, ok := <-s.shareStatSub.Messages():
if !ok {
break
}
msg.Commit()
m := &model.StatCount{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("share json.Unmarshal(%s) error(%+v)", msg.Value, err)
continue
}
if m.Type != "playlist" {
continue
}
if err := s.updateShareStat(context.TODO(), m.ID, m.Count); err != nil {
log.Error("s.statDao.UpdateShare(%d,%d) error(%v)", m.ID, m.Count, err)
}
log.Info("consumeShareStat key:%s partition:%d offset:%d msg: %+v)", msg.Key, msg.Partition, msg.Offset, m)
}
}
}
func (s *Service) intercept(c context.Context, id, mid int64, ip, buvid string) (ban bool) {
if ban = s.statDao.IPBan(c, id, ip); ban {
return
}
return s.statDao.BuvidBan(c, id, mid, ip, buvid)
}
func (s *Service) updatePlayStat(c context.Context, id int64) (err error) {
f, err := s.stat(c, id)
if err != nil {
return
}
f.PlayCount = f.PlayCount + 1
rows, err := s.statDao.UpdatePlay(context.TODO(), id, int64(f.PlayCount))
if err != nil {
log.Error("s.statDao.UpdatePlay(%d,%d) error(%v)", id, f.PlayCount, err)
return
}
if rows > 0 {
if err := s.statDao.SetFolderStatMc(c, id, f); err != nil {
log.Error("s.SetFolderStatMc(%d,%+v) error(%v)", id, f, err)
}
}
return
}
func (s *Service) updateFavStat(c context.Context, id, count int64) (err error) {
f, err := s.stat(c, id)
if err != nil {
return
}
f.FavedCount = int32(count)
rows, err := s.statDao.UpdateFav(context.TODO(), id, count)
if err != nil {
log.Error("s.statDao.UpdateFav(%d,%d) error(%v)", id, count, err)
return
}
if rows > 0 {
if err := s.statDao.SetFolderStatMc(c, id, f); err != nil {
log.Error("s.SetFolderStatMc(%d,%+v) error(%v)", id, f, err)
}
}
return
}
func (s *Service) updateShareStat(c context.Context, id, count int64) (err error) {
f, err := s.stat(c, id)
if err != nil {
return
}
f.ShareCount = int32(count)
rows, err := s.statDao.UpdateShare(context.TODO(), id, count)
if err != nil {
log.Error("s.statDao.UpdateShare(%d,%d) error(%v)", id, count, err)
return
}
if rows > 0 {
if err := s.statDao.SetFolderStatMc(c, id, f); err != nil {
log.Error("s.SetFolderStatMc(%d,%+v) error(%v)", id, f, err)
}
}
return
}
func (s *Service) stat(c context.Context, id int64) (f *favmdl.Folder, err error) {
if f, err = s.statDao.FolderStatMc(c, id); err != nil {
log.Error("s.statDao.FolderStatMc(%d) error(%v)", id, err)
return
}
if f != nil {
return
}
if f, err = s.statDao.Stat(c, id); err != nil {
log.Error("s.statDao.FolderStatMc(%d) error(%v)", id, err)
return
}
if f == nil {
f = new(favmdl.Folder)
}
if err := s.statDao.SetFolderStatMc(c, id, f); err != nil {
log.Error("s.statDao.SetFolderStatMc(%d,%+v) error(%v)", id, f, err)
}
return
}