331 lines
10 KiB
Go
331 lines
10 KiB
Go
|
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
|
||
|
}
|