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,7 @@
### thumbup-job
##### Version 1.1.0
> 1. fixed bug
##### Version 1.0.0
> 1. 初始化新项目

View File

@@ -0,0 +1,10 @@
# Owner
zhapuyu
renwei
# Author
wangxu01
zhoushuguang
# Reviewer
zhapuyu

View File

@@ -0,0 +1,3 @@
# See the OWNERS docs at https://go.k8s.io/owners
options: {}

View File

@@ -0,0 +1 @@
#### thumbup-job

View File

@@ -0,0 +1,42 @@
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 = ["thumbup-job-test.toml"],
importpath = "go-common/app/job/main/thumbup/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/thumbup/conf:go_default_library",
"//app/job/main/thumbup/server/http:go_default_library",
"//app/job/main/thumbup/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,38 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"go-common/app/job/main/thumbup/conf"
"go-common/app/job/main/thumbup/server/http"
"go-common/app/job/main/thumbup/service"
"go-common/library/log"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
srv := service.New(conf.Conf)
http.Init(conf.Conf, srv)
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())
srv.Close()
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,153 @@
# thumbup job
[redis]
name = "thumbup-job"
proto = "tcp"
addr = "172.18.33.60:6981"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
statsExpire = "6h"
userLikesExpire = "1h"
itemLikesExpire = "6h"
[memcache]
name = "thumbup-job"
proto = "tcp"
addr = "172.18.33.61:11230"
idle = 10
active = 10
dialTimeout = "2s"
readTimeout = "2s"
writeTimeout = "2s"
idleTimeout = "7h"
StatsExpire = "6h"
[tidb]
addr = "172.22.34.51"
dsn = "likes:wy5taQn9CBKy9z5elbZQjnwMZOgpzk0r@tcp(172.22.34.51:4000)/bilibili_likes?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 2
idleTimeout ="4h"
queryTimeout = "1s"
execTimeout = "1s"
tranTimeout = "1s"
[tidb.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[itemTidb]
addr = "172.22.34.51"
dsn = "likes:wy5taQn9CBKy9z5elbZQjnwMZOgpzk0r@tcp(172.22.34.51:4000)/bilibili_likes?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 2
idleTimeout ="4h"
queryTimeout = "1s"
execTimeout = "1s"
tranTimeout = "1s"
[itemTidb.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[databus]
[databus.stat]
key = "9765cdac5894f2ba"
secret = "f4237d712c3ed1e7fab0137b81418b14"
group= "StatLike-MainWebSvr-P"
topic= "StatLike-T"
action="pub"
name = "thumbup-service/stat-pub"
proto = "tcp"
addr = "172.22.33.183:6205"
active = 5
idle = 1
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[databus.like]
key = "9765cdac5894f2ba"
secret = "f4237d712c3ed1e7fab0137b81418b14"
group = "Thumbup-MainWebSvr-S"
topic = "Thumbup-T"
action = "sub"
name = "thumbup-T-sub"
proto = "tcp"
addr = "172.22.33.183:6205"
idle = 1
active = 1
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[databus.itemLikes]
key = "9765cdac5894f2ba"
secret = "f4237d712c3ed1e7fab0137b81418b14"
group = "ThumbupItem-MainWebSvr-S"
topic = "ThumbupItem-T"
action = "sub"
buffer = 2048
name = "itemLikes-sub"
proto = "tcp"
addr = "172.22.33.183:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[databus.userLikes]
key = "9765cdac5894f2ba"
secret = "f4237d712c3ed1e7fab0137b81418b14"
group = "ThumbupUser-MainWebSvr-S"
topic = "ThumbupUser-T"
action = "sub"
buffer = 2048
name = "userLikes-sub"
proto = "tcp"
addr = "172.22.33.174:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[thumbup]
[merge]
maxSize = 51200
interval = "1m"
buffer = 10240
worker = 9
[likedatabusutil]
size = 1
chan = 10240
num = 1
ticker = "1s"
[itemlikesdatabusutil]
size = 100
chan = 10240
num = 10
ticker = "1s"
[userlikesdatabusutil]
size = 100
chan = 10240
num = 10
ticker = "1s"
[statMerge]
business = "archive"
target = 8
sources = [1,2,3]

View File

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

View File

@@ -0,0 +1,112 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/tidb"
"go-common/library/log"
"go-common/library/queue/databus"
"go-common/library/queue/databus/databusutil"
"go-common/library/sync/pipeline"
xtime "go-common/library/time"
"github.com/BurntSushi/toml"
)
var (
// Conf global variable.
Conf = &Config{}
client *conf.Client
confPath string
)
// Config .
type Config struct {
Redis *Redis
Tidb *tidb.Config
ItemTidb *tidb.Config
Log *log.Config
Databus *Databus
Thumbup *Thumbup
Memcache *Memcache
StatMerge *StatMerge
Merge *pipeline.Config
LikeDatabusutil *databusutil.Config
ItemLikesDatabusutil *databusutil.Config
UserLikesDatabusutil *databusutil.Config
}
// StatMerge .
type StatMerge struct {
Business string
Target int64
Sources []int64
}
// Redis .
type Redis struct {
*redis.Config
StatsExpire xtime.Duration
UserLikesExpire xtime.Duration
ItemLikesExpire xtime.Duration
}
// Memcache .
type Memcache struct {
*memcache.Config
StatsExpire xtime.Duration
}
// Databus .
type Databus struct {
Stat *databus.Config
Like *databus.Config
ItemLikes *databus.Config
UserLikes *databus.Config
}
// Thumbup .
type Thumbup struct {
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init create config instance.
func Init() (err error) {
if confPath != "" {
return local()
}
return remote()
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
err = load()
return
}
func load() (err error) {
str, ok := client.Toml2()
if !ok {
return errors.New("load config center error")
}
var tmpConf *Config
if _, err = toml.Decode(str, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}

View File

@@ -0,0 +1,68 @@
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",
"databus.go",
"item_likes_redis.go",
"memcached.go",
"redis.go",
"tidb.go",
"user_likes_redis.go",
],
importpath = "go-common/app/job/main/thumbup/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/thumbup/conf:go_default_library",
"//app/job/main/thumbup/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/tidb:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus: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"],
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"databus_test.go",
"item_likes_redis_test.go",
"memcached_test.go",
"redis_test.go",
"tidb_test.go",
"user_likes_redis_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/job/main/thumbup/conf:go_default_library",
"//app/job/main/thumbup/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,104 @@
package dao
import (
"context"
"time"
"go-common/app/job/main/thumbup/conf"
"go-common/library/cache/memcache"
xredis "go-common/library/cache/redis"
"go-common/library/database/tidb"
"go-common/library/log"
"go-common/library/queue/databus"
)
// Dao .
type Dao struct {
// config
c *conf.Config
// tidb
tidb *tidb.DB
itemTidb *tidb.DB
// memcache
mc *memcache.Pool
mcStatsExpire int32
// redis
redis *xredis.Pool
redisStatsExpire int64
redisUserLikesExpire int64
redisItemLikesExpire int64
// databus
statDbus *databus.Databus
// stmt
businessesStmt *tidb.Stmts
itemLikesStmt *tidb.Stmts
userLikesStmt *tidb.Stmts
likeStateStmt *tidb.Stmts
statStmt *tidb.Stmts
updateLikeStateStmt *tidb.Stmts
}
// New .
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
// tidb
tidb: tidb.NewTiDB(c.Tidb),
itemTidb: tidb.NewTiDB(c.ItemTidb),
// memcache
mc: memcache.NewPool(c.Memcache.Config),
mcStatsExpire: int32(time.Duration(c.Memcache.StatsExpire) / time.Second),
// redis
redis: xredis.NewPool(c.Redis.Config),
redisStatsExpire: int64(time.Duration(c.Redis.StatsExpire) / time.Second),
redisUserLikesExpire: int64(time.Duration(c.Redis.UserLikesExpire) / time.Second),
redisItemLikesExpire: int64(time.Duration(c.Redis.ItemLikesExpire) / time.Second),
// databus
statDbus: databus.New(c.Databus.Stat),
}
d.statStmt = d.tidb.Prepared(_statSQL)
d.itemLikesStmt = d.itemTidb.Prepared(_itemLikesSQL)
d.businessesStmt = d.tidb.Prepared(_businessesSQL)
d.userLikesStmt = d.tidb.Prepared(_userLikeListSQL)
d.likeStateStmt = d.tidb.Prepared(_likeStateSQL)
d.updateLikeStateStmt = d.tidb.Prepared(_updateLikeStateSQL)
return
}
// Close .
func (d *Dao) Close() {
d.mc.Close()
d.tidb.Close()
d.redis.Close()
}
// Ping .
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.pingMC(c); err != nil {
log.Error("d.pingMC error(%v)", err)
return
}
if err = d.pingRedis(c); err != nil {
log.Error("d.pingRedis error(%v)", err)
}
return
}
// pingMC ping memcache
func (d *Dao) pingMC(c context.Context) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: 100}
err = conn.Set(&item)
return
}
// pingRedis ping redis.
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
_, err = conn.Do("SET", "PING", "PONG")
return
}

View File

@@ -0,0 +1,34 @@
package dao
import (
"flag"
"go-common/app/job/main/thumbup/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.web-svr.thumbup-job")
flag.Set("conf_token", "4de31fc23726f3c68a1cf74b65d61940")
flag.Set("tree_id", "82533")
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/thumbup-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,30 @@
package dao
import (
"context"
"strconv"
"time"
"go-common/app/job/main/thumbup/model"
"go-common/library/log"
)
// PubStatDatabus .
func (d *Dao) PubStatDatabus(c context.Context, business string, mid int64, s *model.Stats, upMid int64) (err error) {
msg := &model.StatMsg{
Type: business,
ID: s.ID,
Count: s.Likes,
Timestamp: time.Now().Unix(),
OriginID: s.OriginID,
DislikeCount: s.Dislikes,
Mid: mid,
UpMid: upMid,
}
if err = d.statDbus.Send(c, strconv.FormatInt(s.ID, 10), msg); err != nil {
log.Error("d.statDbus.Send error(%v)", err)
return
}
log.Info("pub stat databus success params(%+v)", msg)
return
}

View File

@@ -0,0 +1,27 @@
package dao
import (
"context"
"go-common/app/job/main/thumbup/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPubStatDatabus(t *testing.T) {
convey.Convey("PubStatDatabus", t, func(convCtx convey.C) {
var (
c = context.Background()
business = "archive"
mid = int64(2233)
s = &model.Stats{}
upMid = int64(333)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.PubStatDatabus(c, business, mid, s, upMid)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,116 @@
package dao
import (
"context"
"fmt"
"go-common/app/job/main/thumbup/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
func itemLikesKey(businessID, messageID int64, state int8) string {
return fmt.Sprintf("i2_m_%d_b_%d_%d", messageID, businessID, state)
}
// ExpireItemLikesCache .
func (d *Dao) ExpireItemLikesCache(c context.Context, messageID, businessID int64, state int8) (ok bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := itemLikesKey(businessID, messageID, state)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisItemLikesExpire)); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(EXPIRE, %s) error(%v)", key, err)))
}
return
}
// AddItemLikesCache .
func (d *Dao) AddItemLikesCache(c context.Context, businessID, messageID int64, typ int8, limit int, items []*model.UserLikeRecord) (err error) {
if len(items) == 0 {
return
}
conn := d.redis.Get(c)
defer conn.Close()
key := itemLikesKey(businessID, messageID, typ)
if err = conn.Send("DEL", key); err != nil {
log.Errorv(c, log.KV("AddCacheItemLikeList", fmt.Sprintf("AddCacheItemLikeList conn.Send(DEL, %s) error(%+v)", key, err)))
return
}
args := redis.Args{}.Add(key).Add("CH")
for _, item := range items {
args = args.Add(int64(item.Time)).Add(item.Mid)
}
if err = conn.Send("ZADD", args...); err != nil {
log.Error("zadd key(%s) args(%v) error(%v)", key, args, err)
return
}
if err = conn.Send("ZREMRANGEBYRANK", key, 0, -(limit + 1)); err != nil {
log.Error("zremrangebyrank error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisItemLikesExpire); err != nil {
log.Error("expire key(%s) error(%v)", key, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("redis flush error(%v)", err)
return
}
for i := 0; i < 4; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("redis receive error(%v)", err)
return
}
}
return
}
// AppendCacheItemLikeList .
func (d *Dao) AppendCacheItemLikeList(c context.Context, messageID int64, item *model.UserLikeRecord, businessID int64, state int8, limit int) (err error) {
if item == nil {
return
}
var count int
conn := d.redis.Get(c)
defer conn.Close()
key := itemLikesKey(businessID, messageID, state)
id := item.Mid
score := int64(item.Time)
if err = conn.Send("ZADD", key, "CH", score, id); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(ZADD, %s, %d, %v) error(%v)", key, score, id, err)))
return
}
count++
if err = conn.Send("ZREMRANGEBYRANK", key, 0, -(limit + 1)); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(ZREMRANGEBYRANK, %s, 0, %d) error(%v)", key, -(limit+1), err)))
return
}
count++
if err = conn.Send("EXPIRE", key, d.redisItemLikesExpire); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(EXPIRE, %s, %d) error(%v)", key, d.redisItemLikesExpire, err)))
return
}
count++
if err = conn.Flush(); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Flush error(%v)", err)))
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Receive error(%v)", err)))
return
}
}
return
}
// DelItemLikeCache .
func (d *Dao) DelItemLikeCache(c context.Context, messageID, businessID int64, mid int64, state int8) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := itemLikesKey(businessID, messageID, state)
if _, err = conn.Do("ZREM", key, mid); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(ZREM, %s, %v) error(%v)", key, mid, err)))
}
return
}

View File

@@ -0,0 +1,100 @@
package dao
import (
"context"
"testing"
"go-common/app/job/main/thumbup/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoitemLikesKey(t *testing.T) {
convey.Convey("itemLikesKey", t, func(convCtx convey.C) {
var (
businessID = int64(33)
messageID = int64(5566)
state = int8(1)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := itemLikesKey(businessID, messageID, state)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoExpireItemLikesCache(t *testing.T) {
convey.Convey("ExpireItemLikesCache", t, func(convCtx convey.C) {
var (
c = context.Background()
messageID = int64(5566)
businessID = int64(33)
state = int8(1)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
ok, err := d.ExpireItemLikesCache(c, messageID, businessID, state)
convCtx.Convey("Then err should be nil.ok should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAddItemLikesCache(t *testing.T) {
convey.Convey("AddItemLikesCache", t, func(convCtx convey.C) {
var (
c = context.Background()
businessID = int64(33)
messageID = int64(5566)
typ = int8(1)
limit = int(100)
items = []*model.UserLikeRecord{}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.AddItemLikesCache(c, businessID, messageID, typ, limit, items)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoAppendCacheItemLikeList(t *testing.T) {
convey.Convey("AppendCacheItemLikeList", t, func(convCtx convey.C) {
var (
c = context.Background()
messageID = int64(5566)
item = &model.UserLikeRecord{}
businessID = int64(33)
state = int8(1)
limit = int(100)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.AppendCacheItemLikeList(c, messageID, item, businessID, state, limit)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelItemLikeCache(t *testing.T) {
convey.Convey("DelItemLikeCache", t, func(convCtx convey.C) {
var (
c = context.Background()
messageID = int64(5566)
businessID = int64(33)
mid = int64(2233)
state = int8(1)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.DelItemLikeCache(c, messageID, businessID, mid, state)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,37 @@
package dao
import (
"context"
"fmt"
"go-common/app/job/main/thumbup/model"
"go-common/library/cache/memcache"
"go-common/library/log"
"go-common/library/xstr"
)
func statsKey(businessID, messageID int64) string {
return fmt.Sprintf("m_%d_b_%d", messageID, businessID)
}
// AddStatsCache .
func (d *Dao) AddStatsCache(c context.Context, businessID int64, ms ...*model.Stats) (err error) {
if len(ms) == 0 {
return
}
conn := d.mc.Get(c)
defer conn.Close()
for _, m := range ms {
if m == nil {
continue
}
key := statsKey(businessID, m.ID)
bs := xstr.JoinInts([]int64{m.Likes, m.Dislikes})
item := memcache.Item{Key: key, Value: []byte(bs), Expiration: d.mcStatsExpire}
if err = conn.Set(&item); err != nil {
log.Error("conn.Set(%s) error(%v)", key, err)
return
}
}
return
}

View File

@@ -0,0 +1,41 @@
package dao
import (
"context"
"testing"
"go-common/app/job/main/thumbup/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaostatsKey(t *testing.T) {
convey.Convey("statsKey", t, func(convCtx convey.C) {
var (
businessID = int64(33)
messageID = int64(2233)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := statsKey(businessID, messageID)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAddStatsCache(t *testing.T) {
convey.Convey("AddStatsCache", t, func(convCtx convey.C) {
var (
c = context.Background()
businessID = int64(33)
ms = &model.Stats{}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.AddStatsCache(c, businessID, ms)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,44 @@
package dao
import (
"context"
"fmt"
"go-common/app/job/main/thumbup/model"
"go-common/library/cache/redis"
"go-common/library/log"
"go-common/library/xstr"
)
func hashStatsKey(businessID, originID int64) string {
return fmt.Sprintf("stats_o_%d_b_%d", originID, businessID)
}
// ExpireHashStatsCache .
func (d *Dao) ExpireHashStatsCache(c context.Context, businessID, originID int64) (ok bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := hashStatsKey(businessID, originID)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisStatsExpire)); err != nil {
log.Error("conn.Do(EXPIRE, %s, %d) error(%v)", key, d.redisStatsExpire, err)
}
return
}
// AddHashStatsCache .
func (d *Dao) AddHashStatsCache(c context.Context, businessID, originID int64, stats ...*model.Stats) (err error) {
if len(stats) == 0 {
return
}
conn := d.redis.Get(c)
defer conn.Close()
key := hashStatsKey(businessID, originID)
var commonds = []interface{}{key}
for _, stat := range stats {
commonds = append(commonds, stat.ID, xstr.JoinInts([]int64{stat.Likes, stat.Dislikes}))
}
if _, err = conn.Do("HMSET", commonds...); err != nil {
log.Error("conn.DO(HMSET, %s, %v) error(%v)", key, commonds, err)
}
return
}

View File

@@ -0,0 +1,58 @@
package dao
import (
"context"
"go-common/app/job/main/thumbup/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaohashStatsKey(t *testing.T) {
convey.Convey("hashStatsKey", t, func(convCtx convey.C) {
var (
businessID = int64(33)
originID = int64(7788)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := hashStatsKey(businessID, originID)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoExpireHashStatsCache(t *testing.T) {
convey.Convey("ExpireHashStatsCache", t, func(convCtx convey.C) {
var (
c = context.Background()
businessID = int64(33)
originID = int64(7788)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
ok, err := d.ExpireHashStatsCache(c, businessID, originID)
convCtx.Convey("Then err should be nil.ok should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAddHashStatsCache(t *testing.T) {
convey.Convey("AddHashStatsCache", t, func(convCtx convey.C) {
var (
c = context.Background()
businessID = int64(33)
originID = int64(7788)
stats = &model.Stats{}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.AddHashStatsCache(c, businessID, originID, stats)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,226 @@
package dao
import (
"context"
"fmt"
"time"
"go-common/app/job/main/thumbup/model"
sql "go-common/library/database/tidb"
"go-common/library/log"
xtime "go-common/library/time"
)
const (
_statSQL = "SELECT message_id, likes_count, dislikes_count, origin_id, likes_change, dislikes_change FROM counts WHERE business_id = ? AND origin_id = ? AND message_id = ?"
_itemLikesSQL = "SELECT mid, mtime FROM likes WHERE business_id =? AND origin_id =? AND message_id =? AND type = ? ORDER BY mtime desc LIMIT ?"
_likeStateSQL = "SELECT type FROM likes WHERE mid=? AND business_id=? AND origin_id=? AND message_id=?"
_businessesSQL = "SELECT id, name, message_list_type, user_likes_limit, message_likes_limit, enable_originid, user_list_type FROM business WHERE dtime = '0000-00-00 00:00:00'"
_updateCountsSQL = "INSERT INTO counts (business_id, origin_id, message_id, likes_count, dislikes_count, up_mid) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE "
_userLikeListSQL = "SELECT message_id, mtime FROM likes WHERE mid = ? AND business_id =? AND type = ? ORDER BY mtime desc LIMIT ?"
_updateLikeStateSQL = "INSERT INTO likes (business_id, origin_id, message_id, mid, type, mtime) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE type=?, mtime=?"
)
// Business .
func (d *Dao) Business(c context.Context) (res []*model.Business, err error) {
var rows *sql.Rows
if rows, err = d.businessesStmt.Query(c); err != nil {
return
}
defer rows.Close()
for rows.Next() {
b := &model.Business{}
if err = rows.Scan(&b.ID, &b.Name, &b.MessageListType, &b.UserLikesLimit, &b.MessageLikesLimit, &b.EnableOriginID, &b.UserListType); err != nil {
return
}
res = append(res, b)
}
err = rows.Err()
return
}
// UserLikes .
func (d *Dao) UserLikes(c context.Context, mid, businessID int64, typ int8, limit int) (res []*model.ItemLikeRecord, err error) {
var rows *sql.Rows
if rows, err = d.userLikesStmt.Query(c, mid, businessID, typ, limit); err != nil {
log.Error("d.tidb.userList error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
b := &model.ItemLikeRecord{}
var t time.Time
if err = rows.Scan(&b.MessageID, &t); err != nil {
log.Error("tidb.rows.userList error(%v)", err)
return
}
b.Time = xtime.Time(t.Unix())
res = append(res, b)
}
err = rows.Err()
return
}
// ItemLikes .
func (d *Dao) ItemLikes(c context.Context, businessID, originID, messageID int64, typ int8, limit int) (res []*model.UserLikeRecord, err error) {
var rows *sql.Rows
if rows, err = d.itemLikesStmt.Query(c, businessID, originID, messageID, typ, limit); err != nil {
return
}
defer rows.Close()
for rows.Next() {
b := &model.UserLikeRecord{}
var t time.Time
if err = rows.Scan(&b.Mid, &t); err != nil {
return
}
b.Time = xtime.Time(t.Unix())
res = append(res, b)
}
err = rows.Err()
return
}
// LikeState .
func (d *Dao) LikeState(c context.Context, mid, businessID, originID, messageID int64) (res int8, err error) {
if err = d.likeStateStmt.QueryRow(c, mid, businessID, originID, messageID).Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
log.Error("tidbLikeState(%d,%d,%d,%d) error(%v)", mid, businessID, originID, messageID, err)
}
return
}
// UpdateLikeState .
func (d *Dao) UpdateLikeState(c context.Context, mid, businessID, originID, messageID int64, state int8, likeTime time.Time) (res model.Stats, err error) {
var tx *sql.Tx
if tx, err = d.tidb.Begin(c); err != nil {
log.Error("d.tidb.Begin() error(%v)", err)
return
}
if _, err = tx.Stmts(d.updateLikeStateStmt).Exec(c, businessID, originID, messageID, mid, state, likeTime, state, likeTime); err != nil {
log.Error("d.tidbUpdateLikeState error(%v)", err)
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback error(%v)", err)
}
return
}
if res, err = d.tidbTxStat(c, tx, businessID, originID, messageID); err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
return
}
// UpdateCounts .
func (d *Dao) UpdateCounts(c context.Context, businessID, originID, messageID, likeCounts, dislikeCounts, upMid int64) (err error) {
var tx *sql.Tx
if tx, err = d.tidb.Begin(c); err != nil {
log.Error("tx.tidb.Begin() error(%v)", err)
return
}
var stat model.Stats
if stat, err = d.tidbTxStat(c, tx, businessID, originID, messageID); err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if stat.Likes+likeCounts < 0 {
likeCounts = -stat.Likes
}
if stat.Dislikes+dislikeCounts < 0 {
dislikeCounts = -stat.Dislikes
}
if err = d.tidbTxUpdateCounts(c, tx, businessID, originID, messageID, likeCounts, dislikeCounts, upMid); err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
return
}
// tidbTxStat .
func (d *Dao) tidbTxStat(c context.Context, tx *sql.Tx, businessID, originID, messageID int64) (res model.Stats, err error) {
var likeChange, dislikeChange int64
if err = tx.Stmts(d.statStmt).QueryRow(c, businessID, originID, messageID).Scan(&res.ID, &res.Likes, &res.Dislikes, &res.OriginID, &likeChange, &dislikeChange); err != nil {
if err == sql.ErrNoRows {
err = nil
res.ID = messageID
res.OriginID = originID
return
}
log.Error("tidb: Tx db.Stat.Query error(%v,%v,%v,%v)", businessID, originID, messageID, err)
return
}
res.Likes += likeChange
res.Dislikes += dislikeChange
return
}
// TxUpdateCounts .
func (d *Dao) tidbTxUpdateCounts(c context.Context, tx *sql.Tx, businessID, originID, messageID int64, likesCount, dislikesCount, upMid int64) (err error) {
likeSQL := _updateCountsSQL
if (likesCount == 0) && (dislikesCount == 0) {
return
}
if (likesCount != 0) && (dislikesCount == 0) {
if likesCount > 0 {
likeSQL += fmt.Sprintf("likes_count = likes_count + %v", likesCount)
} else {
likeSQL += fmt.Sprintf("likes_count = likes_count %v", likesCount)
}
} else if (likesCount == 0) && (dislikesCount != 0) {
if dislikesCount > 0 {
likeSQL += fmt.Sprintf("dislikes_count = dislikes_count + %v", dislikesCount)
} else {
likeSQL += fmt.Sprintf("dislikes_count = dislikes_count %v", dislikesCount)
}
} else {
if likesCount > 0 {
likeSQL += fmt.Sprintf("likes_count = likes_count + %v", likesCount)
} else {
likeSQL += fmt.Sprintf("likes_count = likes_count %v", likesCount)
}
if dislikesCount > 0 {
likeSQL += fmt.Sprintf(", dislikes_count = dislikes_count + %v", dislikesCount)
} else {
likeSQL += fmt.Sprintf(", dislikes_count = dislikes_count %v", dislikesCount)
}
}
if upMid > 0 {
likeSQL += fmt.Sprintf(", up_mid = %d", upMid)
}
if _, err = tx.Exec(likeSQL, businessID, originID, messageID, likesCount, dislikesCount, upMid); err != nil {
log.Error("dao.tidbTxUpdateCounts tx.Exec(%s, %v,%v,%v) error(%v)", likeSQL, businessID, originID, messageID, err)
}
return
}
// Stat .
func (d *Dao) Stat(c context.Context, businessID, originID, messageID int64) (res model.Stats, err error) {
var likeChange, dislikeChange int64
if err = d.statStmt.QueryRow(c, businessID, originID, messageID).Scan(&res.ID, &res.Likes, &res.Dislikes, &res.OriginID, &likeChange, &dislikeChange); err != nil {
if err == sql.ErrNoRows {
err = nil
res.ID = messageID
res.OriginID = originID
return
}
log.Error("tidb.Stat.Query error(%v)", err)
}
res.Likes += likeChange
res.Dislikes += dislikeChange
return
}

View File

@@ -0,0 +1,122 @@
package dao
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoBusiness(t *testing.T) {
convey.Convey("Business", t, func(convCtx convey.C) {
var (
c = context.Background()
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := d.Business(c)
convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoUserLikes(t *testing.T) {
convey.Convey("UserLikes", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(2233)
businessID = int64(33)
typ = int8(1)
limit = int(100)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := d.UserLikes(c, mid, businessID, typ, limit)
convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoItemLikes(t *testing.T) {
convey.Convey("ItemLikes", t, func(convCtx convey.C) {
var (
c = context.Background()
businessID = int64(33)
originID = int64(7788)
messageID = int64(5566)
typ = int8(1)
limit = int(100)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := d.ItemLikes(c, businessID, originID, messageID, typ, limit)
convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoUpdateLikeState(t *testing.T) {
convey.Convey("UpdateLikeState", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(2233)
businessID = int64(33)
originID = int64(7788)
messageID = int64(5566)
state = int8(1)
likeTime = time.Now()
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := d.UpdateLikeState(c, mid, businessID, originID, messageID, state, likeTime)
convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoUpdateCounts(t *testing.T) {
convey.Convey("UpdateCounts", t, func(convCtx convey.C) {
var (
c = context.Background()
businessID = int64(33)
originID = int64(7788)
messageID = int64(5566)
likeCounts = int64(100)
dislikeCounts = int64(100)
upMid = int64(999)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.UpdateCounts(c, businessID, originID, messageID, likeCounts, dislikeCounts, upMid)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoStat(t *testing.T) {
convey.Convey("Stat", t, func(convCtx convey.C) {
var (
c = context.Background()
businessID = int64(33)
originID = int64(7788)
messageID = int64(5566)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := d.Stat(c, businessID, originID, messageID)
convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,116 @@
package dao
import (
"context"
"fmt"
"go-common/app/job/main/thumbup/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
func userLikesKey(businessID, mid int64, state int8) string {
return fmt.Sprintf("u_m_%d_b_%d_%d", mid, businessID, state)
}
// ExpireUserLikesCache .
func (d *Dao) ExpireUserLikesCache(c context.Context, mid, businessID int64, state int8) (ok bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := userLikesKey(businessID, mid, state)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisUserLikesExpire)); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(EXPIRE, %s) error(%v)", key, err)))
}
return
}
// AppendCacheUserLikeList .
func (d *Dao) AppendCacheUserLikeList(c context.Context, mid int64, item *model.ItemLikeRecord, businessID int64, state int8, limit int) (err error) {
if item == nil {
return
}
var count int
conn := d.redis.Get(c)
defer conn.Close()
key := userLikesKey(businessID, mid, state)
id := item.MessageID
score := int64(item.Time)
if err = conn.Send("ZADD", key, "CH", score, id); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(ZADD, %s, %d, %v) error(%v)", key, score, id, err)))
return
}
count++
if err = conn.Send("ZREMRANGEBYRANK", key, 0, -(limit + 1)); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(ZREMRANGEBYRANK, %s, 0, %d) error(%v)", key, -(limit+1), err)))
return
}
count++
if err = conn.Send("EXPIRE", key, d.redisUserLikesExpire); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(EXPIRE, %s, %d) error(%v)", key, d.redisUserLikesExpire, err)))
return
}
count++
if err = conn.Flush(); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Flush error(%v)", err)))
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Receive error(%v)", err)))
return
}
}
return
}
// AddUserLikesCache .
func (d *Dao) AddUserLikesCache(c context.Context, mid, businessID int64, items []*model.ItemLikeRecord, typ int8, limit int) (err error) {
if len(items) == 0 {
return
}
conn := d.redis.Get(c)
defer conn.Close()
key := userLikesKey(businessID, mid, typ)
if err = conn.Send("DEL", key); err != nil {
log.Errorv(c, log.KV("AddCacheUserLikeList", fmt.Sprintf("conn.Send(DEL, %s) error(%+v)", key, err)))
return
}
args := redis.Args{}.Add(key).Add("CH")
for _, item := range items {
args = args.Add(int64(item.Time)).Add(item.MessageID)
}
if err = conn.Send("ZADD", args...); err != nil {
log.Error("zadd key(%s) args(%v) error(%v)", key, args, err)
return
}
if err = conn.Send("ZREMRANGEBYRANK", key, 0, -(limit + 1)); err != nil {
log.Error("zremrangebyrank error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisUserLikesExpire); err != nil {
log.Error("expire key(%s) error(%v)", key, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("redis flush error(%v)", err)
return
}
for i := 0; i < 4; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("redis receive error(%v)", err)
return
}
}
return
}
// DelUserLikeCache .
func (d *Dao) DelUserLikeCache(c context.Context, mid, businessID int64, messageID int64, state int8) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := userLikesKey(businessID, mid, state)
if _, err = conn.Do("ZREM", key, messageID); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("conn.Send(ZREM, %s, %v) error(%v)", key, messageID, err)))
}
return
}

View File

@@ -0,0 +1,99 @@
package dao
import (
"context"
"go-common/app/job/main/thumbup/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaouserLikesKey(t *testing.T) {
convey.Convey("userLikesKey", t, func(convCtx convey.C) {
var (
businessID = int64(33)
mid = int64(2233)
state = int8(1)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1 := userLikesKey(businessID, mid, state)
convCtx.Convey("Then p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoExpireUserLikesCache(t *testing.T) {
convey.Convey("ExpireUserLikesCache", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
businessID = int64(0)
state = int8(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
ok, err := d.ExpireUserLikesCache(c, mid, businessID, state)
convCtx.Convey("Then err should be nil.ok should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAppendCacheUserLikeList(t *testing.T) {
convey.Convey("AppendCacheUserLikeList", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
item = &model.ItemLikeRecord{}
businessID = int64(0)
state = int8(0)
limit = int(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.AppendCacheUserLikeList(c, mid, item, businessID, state, limit)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoAddUserLikesCache(t *testing.T) {
convey.Convey("AddUserLikesCache", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
businessID = int64(0)
items = []*model.ItemLikeRecord{}
typ = int8(0)
limit = int(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.AddUserLikesCache(c, mid, businessID, items, typ, limit)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelUserLikeCache(t *testing.T) {
convey.Convey("DelUserLikeCache", t, func(convCtx convey.C) {
var (
c = context.Background()
mid = int64(0)
businessID = int64(0)
messageID = int64(0)
state = int8(0)
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := d.DelUserLikeCache(c, mid, businessID, messageID, state)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"model.go",
"thumbup.go",
],
importpath = "go-common/app/job/main/thumbup/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
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 @@
package model

View File

@@ -0,0 +1,104 @@
package model
import (
xtime "go-common/library/time"
)
// type and states
const (
StateBlank = 0
StateLike = 1
StateDislike = 2
TypeLike = 1
TypeCancelLike = 2
TypeDislike = 3
TypeCancelDislike = 4
TypeLikeReverse = 5
TypeDislikeReverse = 6
ItemListLike = 1
ItemListDislike = 2
ItemListAll = 3
UserListLike = 1
UserListDislike = 2
UserListAll = 3
)
// Business .
type Business struct {
ID int64 `json:"id"`
Name string `json:"name"`
MessageListType uint8 `json:"message_list_type"`
UserListType uint8 `json:"user_list_type"`
UserLikesLimit int `json:"user_likes_limit"`
MessageLikesLimit int `json:"message_likes_limit"`
EnableOriginID int `json:"enable_origin_id"`
}
// EnableItemLikeList .
func (b *Business) EnableItemLikeList() bool {
return (b.MessageListType == ItemListLike) || (b.MessageListType == ItemListAll)
}
// EnableItemDislikeList .
func (b *Business) EnableItemDislikeList() bool {
return (b.MessageListType == ItemListDislike) || (b.MessageListType == ItemListAll)
}
// EnableUserLikeList .
func (b *Business) EnableUserLikeList() bool {
return (b.UserListType == UserListLike) || (b.UserListType == UserListAll)
}
// EnableUserDislikeList .
func (b *Business) EnableUserDislikeList() bool {
return (b.UserListType == UserListDislike) || (b.UserListType == UserListAll)
}
// UserLikeRecord .
type UserLikeRecord struct {
Mid int64 `json:"mid"`
Time xtime.Time `json:"time"`
}
// LikeItem .
type LikeItem struct {
Business string
OriginID int64
MessageID int64
}
// LikeCounts .
type LikeCounts struct {
Like int64
Dislike int64
UpMid int64
}
// Stats .
type Stats struct {
OriginID int64 `json:"origin_id"`
ID int64 `json:"id"`
Likes int64 `json:"likes"`
Dislikes int64 `json:"dislikes"`
}
// ItemLikeRecord .
type ItemLikeRecord struct {
MessageID int64 `json:"message_id"`
Time xtime.Time `json:"time"`
}
// StatMsg .
type StatMsg struct {
Type string `json:"type"`
ID int64 `json:"id"`
Count int64 `json:"count"`
Timestamp int64 `json:"timestamp"`
OriginID int64 `json:"origin_id,omitempty"`
DislikeCount int64 `json:"dislike_count,omitempty"`
Mid int64 `json:"mid,omitempty"`
UpMid int64 `json:"up_mid,omitempty"`
}

View File

@@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["http.go"],
importpath = "go-common/app/job/main/thumbup/server/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/thumbup/conf:go_default_library",
"//app/job/main/thumbup/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,34 @@
package http
import (
"net/http"
"go-common/app/job/main/thumbup/conf"
"go-common/app/job/main/thumbup/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
var srv *service.Service
// Init init http server.
func Init(c *conf.Config, s *service.Service) {
srv = s
engine := bm.DefaultServer(nil)
route(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
func route(engine *bm.Engine) {
engine.Ping(ping)
}
func ping(ctx *bm.Context) {
if err := srv.Ping(ctx); err != nil {
log.Error("thumbup-job ping error(%v)", err)
ctx.AbortWithStatus(http.StatusServiceUnavailable)
}
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"item_likes.go",
"like.go",
"service.go",
"user_likes.go",
],
importpath = "go-common/app/job/main/thumbup/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/thumbup/conf:go_default_library",
"//app/job/main/thumbup/dao:go_default_library",
"//app/job/main/thumbup/model:go_default_library",
"//app/service/main/thumbup/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/databusutil:go_default_library",
"//library/sync/pipeline: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,85 @@
package service
import (
"context"
"encoding/json"
"go-common/app/job/main/thumbup/model"
xmdl "go-common/app/service/main/thumbup/model"
"go-common/library/log"
"go-common/library/queue/databus"
)
func newItemLikeMsg(msg *databus.Message) (res interface{}, err error) {
itemLikesMsg := new(xmdl.ItemMsg)
if err = json.Unmarshal(msg.Value, &itemLikesMsg); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", msg.Value, err)
return
}
log.Info("get item like msg: %+v", itemLikesMsg)
res = itemLikesMsg
return
}
func itemLikesSplit(msg *databus.Message, data interface{}) int {
im, ok := data.(*xmdl.ItemMsg)
if !ok {
log.Error("user item msg err: message_id: 0 %s", msg.Value)
return 0
}
return int(im.MessageID)
}
func (s *Service) itemLikesDo(ms []interface{}) {
for _, m := range ms {
im, ok := m.(*xmdl.ItemMsg)
if !ok {
continue
}
var (
err error
businessID int64
ctx = context.Background()
)
if businessID, err = s.checkBusinessOrigin(im.Business, im.OriginID); err != nil {
continue
}
var exist bool
if exist, _ = s.dao.ExpireItemLikesCache(ctx, im.MessageID, businessID, im.State); exist {
continue
}
for i := 0; i < _retryTimes; i++ {
if err = s.addItemLikesCache(ctx, im); err == nil {
break
}
}
log.Info("itemLikes success params(%+v)", m)
}
}
// addCacheItemLikes .
func (s *Service) addItemLikesCache(c context.Context, p *xmdl.ItemMsg) (err error) {
var businessID int64
if businessID, err = s.checkBusinessOrigin(p.Business, p.OriginID); err != nil {
log.Error("s.checkBusinessOrigin business(%s) originID(%s)", p.Business, p.OriginID, err)
return
}
var items []*model.UserLikeRecord
var limit = s.businessIDMap[businessID].MessageLikesLimit
if items, err = s.dao.ItemLikes(c, businessID, p.OriginID, p.MessageID, p.State, limit); err != nil {
log.Error("s.dao.ItemLikes businessID(%d) originID(%d) messageID(%d) type(%d) error(%v)", businessID, p.OriginID, p.MessageID, p.State, err)
return
}
err = s.dao.AddItemLikesCache(c, businessID, p.MessageID, p.State, limit, items)
return
}
func (s *Service) addItemlikeRecord(c context.Context, businessID, messageID int64, state int8, item *model.UserLikeRecord) (err error) {
var exist bool
if exist, err = s.dao.ExpireItemLikesCache(c, messageID, businessID, state); (err != nil) || !exist {
return
}
limit := s.businessIDMap[businessID].MessageLikesLimit
err = s.dao.AppendCacheItemLikeList(c, messageID, item, businessID, state, limit)
return
}

View File

@@ -0,0 +1,330 @@
package service
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"go-common/app/job/main/thumbup/model"
xmdl "go-common/app/service/main/thumbup/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/queue/databus"
xtime "go-common/library/time"
)
func newLikeMsg(msg *databus.Message) (res interface{}, err error) {
likeMsg := new(xmdl.LikeMsg)
if err = json.Unmarshal(msg.Value, &likeMsg); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", msg.Value, err)
return
}
log.Info("get like event msg: %+v", likeMsg)
res = likeMsg
return
}
func likeSplit(msg *databus.Message, data interface{}) int {
lm, ok := data.(*xmdl.LikeMsg)
if !ok {
log.Error("get like event msg: not ok %s", msg.Value)
return 0
}
return int(lm.Mid)
}
func (s *Service) likeDo(ms []interface{}) {
for _, m := range ms {
lm, ok := m.(*xmdl.LikeMsg)
if !ok {
log.Error("get like event msg: not ok %+v", m)
continue
}
var (
oldState int8
err error
businessID int64
ctx = context.Background()
)
if businessID, err = s.checkBusinessOrigin(lm.Business, lm.OriginID); err != nil {
log.Warn("like event: business(%v, %v) err: +v", lm.Business, lm.OriginID)
continue
}
if oldState, err = s.dao.LikeState(ctx, lm.Mid, businessID, lm.OriginID, lm.MessageID); err != nil {
log.Warn("like event: likeState(%+v) err: +v", lm)
time.Sleep(time.Millisecond * 50)
continue
}
var newState, likeType int8
if newState, likeType, err = s.checkState(ctx, oldState, lm); err != nil {
log.Warn("repeat like mid(%d) likeType(%d) oldState(%d) newState(%d) bid(%d) oid(%d) messageID(%d)",
lm.Mid, likeType, oldState, newState, businessID, lm.OriginID, lm.MessageID)
continue
}
var stat model.Stats
if stat, err = s.dao.UpdateLikeState(ctx, lm.Mid, businessID, lm.OriginID, lm.MessageID, newState, lm.LikeTime); err != nil {
log.Warn("like event: UpdateLikeState(%+v) err: +v", lm)
time.Sleep(time.Millisecond * 50)
continue
}
// 聚合数据
key := fmt.Sprintf("%d-%d-%d", businessID, lm.OriginID, lm.MessageID)
s.merge.Add(ctx, key, lm)
stat = calculateCount(stat, likeType)
s.updateCache(ctx, lm.Mid, businessID, lm.OriginID, lm.MessageID, likeType, lm.LikeTime, &stat)
s.dao.PubStatDatabus(ctx, lm.Business, lm.Mid, &stat, lm.UpMid)
log.Info("like event: like success params(%+v)", m)
// 拜年祭
target := s.mergeTarget(lm.Business, lm.MessageID)
if target <= 0 {
continue
}
if stat, err = s.dao.Stat(ctx, businessID, 0, target); err != nil {
continue
}
lm.MessageID = target
key = fmt.Sprintf("%d-%d-%d", businessID, 0, target)
s.merge.Add(ctx, key, lm)
stat = calculateCount(stat, likeType)
s.updateStatCache(ctx, businessID, 0, &stat)
s.dao.PubStatDatabus(ctx, lm.Business, lm.Mid, &stat, 0)
log.Info("like success params(%+v)", m)
}
}
func (s *Service) countsSplit(key string) int {
messageIDStr := strings.Split(key, "-")[2]
messageID, _ := strconv.Atoi(messageIDStr)
return messageID % s.c.Merge.Worker
}
func (s *Service) updateCountsDo(c context.Context, ch int, values map[string][]interface{}) {
mItem := make(map[model.LikeItem]*model.LikeCounts)
for _, vs := range values {
for _, v := range vs {
item := v.(*xmdl.LikeMsg)
stat := calculateCount(model.Stats{}, item.Type)
likesCount := stat.Likes
dislikesCount := stat.Dislikes
likeItem := model.LikeItem{
Business: item.Business,
OriginID: item.OriginID,
MessageID: item.MessageID,
}
if mItem[likeItem] == nil {
mItem[likeItem] = &model.LikeCounts{Like: likesCount, Dislike: dislikesCount, UpMid: item.UpMid}
} else {
mItem[likeItem].Like += likesCount
mItem[likeItem].Dislike += dislikesCount
if item.UpMid > 0 {
mItem[likeItem].UpMid = item.UpMid
}
}
}
}
for item, count := range mItem {
for i := 0; i < _retryTimes; i++ {
if err := s.dao.UpdateCounts(context.Background(), s.businessMap[item.Business].ID, item.OriginID, item.MessageID, count.Like, count.Dislike, count.UpMid); err == nil {
break
}
}
}
}
// checkBusiness .
func (s *Service) checkBusiness(business string) (id int64, err error) {
b := s.businessMap[business]
if b == nil {
err = ecode.ThumbupBusinessBlankErr
return
}
id = b.ID
return
}
// checkBusinessOrigin .
func (s *Service) checkBusinessOrigin(business string, originID int64) (id int64, err error) {
b := s.businessMap[business]
if b == nil {
err = ecode.ThumbupBusinessBlankErr
return
}
if (b.EnableOriginID == 1 && originID == 0) || (b.EnableOriginID == 0 && originID != 0) {
err = ecode.ThumbupOriginErr
return
}
id = b.ID
return
}
// updateCache .
func (s *Service) updateCache(c context.Context, mid, businessID, originID, messageID int64, likeType int8, likeTime time.Time, stat *model.Stats) {
if stat != nil {
s.updateStatCache(c, businessID, originID, stat)
}
business := s.businessIDMap[businessID]
likeRecord := &model.ItemLikeRecord{MessageID: messageID, Time: xtime.Time(likeTime.Unix())}
userRecord := &model.UserLikeRecord{Mid: mid, Time: xtime.Time(likeTime.Unix())}
switch likeType {
case model.TypeLike:
if business.EnableUserLikeList() {
s.addUserlikeRecord(c, mid, businessID, model.StateLike, likeRecord)
}
if business.EnableItemLikeList() {
s.addItemlikeRecord(c, businessID, messageID, model.StateLike, userRecord)
}
case model.TypeCancelLike:
if business.EnableUserLikeList() {
s.dao.DelUserLikeCache(c, mid, businessID, messageID, model.StateLike)
}
if business.EnableItemLikeList() {
s.dao.DelItemLikeCache(c, messageID, businessID, mid, model.StateLike)
}
case model.TypeDislike:
if business.EnableUserDislikeList() {
s.addUserlikeRecord(c, mid, businessID, model.StateDislike, likeRecord)
}
if business.EnableItemDislikeList() {
s.addItemlikeRecord(c, businessID, messageID, model.StateDislike, userRecord)
}
case model.TypeCancelDislike:
if business.EnableUserDislikeList() {
s.dao.DelUserLikeCache(c, mid, businessID, messageID, model.StateDislike)
}
if business.EnableItemDislikeList() {
s.dao.DelItemLikeCache(c, messageID, businessID, mid, model.StateDislike)
}
case model.TypeLikeReverse:
if business.EnableUserLikeList() {
s.dao.DelUserLikeCache(c, mid, businessID, messageID, model.StateLike)
}
if business.EnableItemLikeList() {
s.dao.DelItemLikeCache(c, messageID, businessID, mid, model.StateLike)
}
if business.EnableUserDislikeList() {
s.addUserlikeRecord(c, mid, businessID, model.StateDislike, likeRecord)
}
if business.EnableItemDislikeList() {
s.addItemlikeRecord(c, businessID, messageID, model.StateDislike, userRecord)
}
case model.TypeDislikeReverse:
if business.EnableUserDislikeList() {
s.dao.DelUserLikeCache(c, mid, businessID, messageID, model.StateDislike)
}
if business.EnableItemDislikeList() {
s.dao.DelItemLikeCache(c, messageID, businessID, mid, model.StateDislike)
}
if business.EnableUserLikeList() {
s.addUserlikeRecord(c, mid, businessID, model.StateLike, likeRecord)
}
if business.EnableItemLikeList() {
s.addItemlikeRecord(c, businessID, messageID, model.StateLike, userRecord)
}
}
}
// updateStateCache .
func (s *Service) updateStatCache(c context.Context, businessID, originID int64, stat *model.Stats) (err error) {
if stat == nil {
return
}
var ok bool
if originID == 0 {
err = s.dao.AddStatsCache(c, businessID, stat)
} else {
if ok, err = s.dao.ExpireHashStatsCache(c, businessID, originID); ok {
err = s.dao.AddHashStatsCache(c, businessID, originID, stat)
}
}
return
}
func calculateCount(stat model.Stats, typ int8) model.Stats {
var likesCount, dislikeCount int64
switch typ {
case model.TypeLike:
likesCount = 1
case model.TypeCancelLike:
likesCount = -1
case model.TypeDislike:
dislikeCount = 1
case model.TypeCancelDislike:
dislikeCount = -1
case model.TypeLikeReverse:
likesCount = -1
dislikeCount = 1
case model.TypeDislikeReverse:
likesCount = 1
dislikeCount = -1
}
stat.Likes += likesCount
stat.Dislikes += dislikeCount
return stat
}
// checkState .
func (s *Service) checkState(c context.Context, oldState int8, lm *xmdl.LikeMsg) (newState, likeType int8, err error) {
likeType = lm.Type
if oldState == model.StateBlank {
switch lm.Type {
case model.TypeLike:
newState = model.StateLike
case model.TypeCancelLike:
err = ecode.ThumbupCancelLikeErr
case model.TypeDislike:
newState = model.StateDislike
case model.TypeCancelDislike:
err = ecode.ThumbupCancelDislikeErr
}
} else if oldState == model.StateLike {
switch lm.Type {
case model.TypeLike:
err = ecode.ThumbupDupLikeErr
limit := s.businessMap[lm.Business].UserLikesLimit
bid := s.businessMap[lm.Business].ID
likeRecord := &model.ItemLikeRecord{MessageID: lm.MessageID, Time: xtime.Time(lm.LikeTime.Unix())}
if exists, err1 := s.dao.ExpireUserLikesCache(c, lm.Mid, bid, model.StateLike); err1 == nil && exists {
s.dao.AppendCacheUserLikeList(c, lm.Mid, likeRecord, bid, model.StateLike, limit)
}
case model.TypeCancelLike:
newState = model.StateBlank
case model.TypeDislike:
likeType = model.TypeLikeReverse
newState = model.StateDislike
case model.TypeCancelDislike:
err = ecode.ThumbupCancelDislikeErr
}
} else if oldState == model.StateDislike {
switch lm.Type {
case model.TypeLike:
likeType = model.TypeDislikeReverse
newState = model.StateLike
case model.TypeCancelLike:
err = ecode.ThumbupCancelLikeErr
case model.TypeDislike:
err = ecode.ThumbupDupDislikeErr
limit := s.businessMap[lm.Business].UserLikesLimit
bid := s.businessMap[lm.Business].ID
likeRecord := &model.ItemLikeRecord{MessageID: lm.MessageID, Time: xtime.Time(lm.LikeTime.Unix())}
if exists, err1 := s.dao.ExpireUserLikesCache(c, lm.Mid, bid, model.StateDislike); err1 == nil && exists {
s.dao.AppendCacheUserLikeList(c, lm.Mid, likeRecord, bid, model.StateDislike, limit)
}
case model.TypeCancelDislike:
newState = model.StateBlank
}
} else {
log.Warn("oldState abnormal mid:%d business:%v oid:%d messageID:%d oldState:%d", lm.Mid, lm.Business, lm.OriginID, lm.MessageID, oldState)
}
return
}
func (s *Service) mergeTarget(business string, aid int64) int64 {
if s.statMerge != nil && s.statMerge.Business == business && s.statMerge.Sources[aid] {
return s.statMerge.Target
}
return 0
}

View File

@@ -0,0 +1,144 @@
package service
import (
"context"
"sync"
"time"
"go-common/app/job/main/thumbup/conf"
"go-common/app/job/main/thumbup/dao"
"go-common/app/job/main/thumbup/model"
"go-common/library/queue/databus"
"go-common/library/queue/databus/databusutil"
"go-common/library/sync/pipeline"
)
const (
_retryTimes = 3
)
// Service .Service
type Service struct {
c *conf.Config
dao *dao.Dao
waiter *sync.WaitGroup
merge *pipeline.Pipeline
likeGroup *databusutil.Group
itemLikesGroup *databusutil.Group
userLikesGroup *databusutil.Group
// businessMap
businessMap map[string]*model.Business
businessIDMap map[int64]*model.Business
// for 拜年祭
statMerge *statMerge
}
type statMerge struct {
Business string
Target int64
Sources map[int64]bool
}
// New create service instance and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
// waitGroup
waiter: new(sync.WaitGroup),
}
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
}
}
s.loadBusiness()
s.initLikeGroup(c)
s.initItemLikesGroup(c)
s.initUserLikesGroup(c)
s.initCountsMerge()
go s.loadBusinessproc()
return
}
func (s *Service) loadBusinessproc() {
for {
s.loadBusiness()
time.Sleep(time.Minute * 5)
}
}
func (s *Service) initItemLikesGroup(c *conf.Config) {
ig := databusutil.NewGroup(c.ItemLikesDatabusutil, databus.New(c.Databus.ItemLikes).Messages())
ig.New = newItemLikeMsg
ig.Split = itemLikesSplit
ig.Do = s.itemLikesDo
ig.Start()
s.itemLikesGroup = ig
}
func (s *Service) initUserLikesGroup(c *conf.Config) {
ug := databusutil.NewGroup(c.UserLikesDatabusutil, databus.New(c.Databus.UserLikes).Messages())
ug.New = newUserLikeMsg
ug.Split = userLikesSplit
ug.Do = s.userLikesDo
ug.Start()
s.userLikesGroup = ug
}
func (s *Service) initLikeGroup(c *conf.Config) {
lg := databusutil.NewGroup(c.LikeDatabusutil, databus.New(c.Databus.Like).Messages())
lg.New = newLikeMsg
lg.Split = likeSplit
lg.Do = s.likeDo
lg.Start()
s.likeGroup = lg
}
func (s *Service) initCountsMerge() {
s.merge = pipeline.NewPipeline(s.c.Merge)
s.merge.Split = s.countsSplit
s.merge.Do = s.updateCountsDo
s.merge.Start()
}
func (s *Service) loadBusiness() {
var (
err error
business []*model.Business
)
businessMap := make(map[string]*model.Business)
businessIDMap := make(map[int64]*model.Business)
for {
if business, err = s.dao.Business(context.TODO()); err != nil {
time.Sleep(time.Second)
continue
}
for _, b := range business {
businessMap[b.Name] = b
businessIDMap[b.ID] = b
}
s.businessMap = businessMap
s.businessIDMap = businessIDMap
return
}
}
// Ping .
func (s *Service) Ping(ctx context.Context) error {
return s.dao.Ping(ctx)
}
// Close .
func (s *Service) Close() {
s.waiter.Wait()
s.itemLikesGroup.Close()
s.userLikesGroup.Close()
s.likeGroup.Close()
s.dao.Close()
}

View File

@@ -0,0 +1,91 @@
package service
import (
"context"
"encoding/json"
"go-common/app/job/main/thumbup/model"
xmdl "go-common/app/service/main/thumbup/model"
"go-common/library/log"
"go-common/library/queue/databus"
)
func newUserLikeMsg(msg *databus.Message) (res interface{}, err error) {
userLikesMsg := new(xmdl.UserMsg)
if err = json.Unmarshal(msg.Value, &userLikesMsg); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", msg.Value, err)
return
}
log.Info("get user like msg: %+v", userLikesMsg)
res = userLikesMsg
return
}
func userLikesSplit(msg *databus.Message, data interface{}) int {
um, ok := data.(*xmdl.UserMsg)
if !ok {
log.Error("user like msg err: mid: 0 %s", msg.Value)
return 0
}
return int(um.Mid)
}
func (s *Service) userLikesDo(ms []interface{}) {
for _, m := range ms {
um, ok := m.(*xmdl.UserMsg)
if !ok {
continue
}
var (
err error
businessID int64
ctx = context.Background()
)
if businessID, err = s.checkBusiness(um.Business); err != nil {
log.Warn("userlikes: checkBusiness(%s) err:%+v", um.Business, err)
continue
}
var exist bool
if exist, _ = s.dao.ExpireUserLikesCache(ctx, um.Mid, businessID, um.State); exist {
log.Warn("userlikes: ExpireUserLikesCache(%+v) exist ignore", um, err)
continue
}
for i := 0; i < _retryTimes; i++ {
if err = s.addUserLikesCache(context.Background(), um); err == nil {
break
}
}
if err != nil {
log.Error("userLikes fail params(%+v) err: %+v", m, err)
} else {
log.Info("userLikes success params(%+v)", m)
}
}
}
// addUserLikesCache .
func (s *Service) addUserLikesCache(c context.Context, p *xmdl.UserMsg) (err error) {
var businessID int64
if businessID, err = s.checkBusiness(p.Business); err != nil {
log.Error("s.checkBusiness business(%s) error(%v)", p.Business, err)
return
}
var items []*model.ItemLikeRecord
var limit = s.businessIDMap[businessID].UserLikesLimit
if items, err = s.dao.UserLikes(c, p.Mid, businessID, p.State, limit); err != nil {
log.Error("s.dao.UserLikes mid(%d) businessID(%d)(%d) type(%d) error(%v)", p.Mid, businessID, p.State, err)
return
}
err = s.dao.AddUserLikesCache(c, p.Mid, businessID, items, p.State, limit)
return
}
func (s *Service) addUserlikeRecord(c context.Context, mid, businessID int64, state int8, item *model.ItemLikeRecord) (err error) {
var exist bool
if exist, err = s.dao.ExpireUserLikesCache(c, mid, businessID, state); (err != nil) || !exist {
return
}
limit := s.businessIDMap[businessID].UserLikesLimit
err = s.dao.AppendCacheUserLikeList(c, mid, item, businessID, state, limit)
return
}