package service import ( "context" "strconv" "time" artmdl "go-common/app/interface/openplatform/article/model" "go-common/app/job/openplatform/article/dao" "go-common/app/job/openplatform/article/model" "go-common/library/log" ) func (s *Service) statproc(i int64) { defer s.waiter.Done() var ( err error ls *lastTimeStat c = context.TODO() ch = s.statCh[i] last = s.statLastTime[i] ) for { stat, ok := <-ch if !ok { log.Warn("statproc(%d) quit", i) s.multiUpdateDB(i, last) return } // filter view count if stat.View != nil && *stat.View > 0 { var ban bool var reason, valid string if ban = s.intercept(stat); ban { log.Info("intercept view count (aid:%d, ip:%s, mid:%d)", stat.Aid, stat.IP, stat.Mid) dao.PromInfo("stat:访问计数拦截") reason = "访问计数拦截" } else if ban = s.dao.DupViewIntercept(c, stat.Aid, stat.Mid); ban { log.Info("dupintercept view count (aid:%d, ip:%s, mid:%d)", stat.Aid, stat.IP, stat.Mid) dao.PromInfo("stat:重复访问计数拦截") reason = "重复访问计数拦截" } else if stat.CheatInfo != nil { viewLv, _ := strconv.Atoi(stat.CheatInfo.Lv) if limitLv, ok1 := s.cheatArts[stat.Aid]; ok1 && (viewLv <= limitLv) { ban = true log.Info("lvintercept view count (aid:%d, ip:%s, mid:%d)", stat.Aid, stat.IP, stat.Mid) dao.PromInfo("stat:等级访问计数拦截") reason = "等级访问计数拦截" } } if ban { valid = "0" } else { valid = "1" } if stat.CheatInfo != nil { stat.CheatInfo.Valid = valid stat.CheatInfo.Reason = reason } s.cheatInfo(stat.CheatInfo) if ban { continue } } // get stat if ls, ok = last[stat.Aid]; !ok { var st *artmdl.StatMsg if st, err = s.dao.Stat(c, stat.Aid); err != nil { log.Error("s.dao.Stat(%d) error(%+v)", stat.Aid, err) continue } ls = &lastTimeStat{} if st == nil { ls.stat = &artmdl.StatMsg{Aid: stat.Aid, View: new(int64), Like: new(int64), Dislike: new(int64), Favorite: new(int64), Reply: new(int64), Share: new(int64), Coin: new(int64)} ls.time = 0 // NOTE: make sure update db in first. } else { ls.stat = st ls.time = time.Now().Unix() } last[stat.Aid] = ls } changed := model.Merge(ls.stat, stat) // update cache s.updateCache(c, ls.stat, 0) s.updateSortCache(c, ls.stat.Aid, changed) // update db after 120s if time.Now().Unix()-ls.time > s.updateDbInterval { s.updateDB(c, ls.stat, 0) s.updateRecheckDB(c, ls.stat) s.updateSearchStats(c, ls.stat) delete(last, stat.Aid) // NOTE: delete ensures that memory should be normal in 120s after channel has been closed. } } } // updateCache purge stat info in cache func (s *Service) updateCache(c context.Context, sm *artmdl.StatMsg, count int) (err error) { stat := &artmdl.ArgStats{ Aid: sm.Aid, Stats: &artmdl.Stats{ View: *sm.View, Like: *sm.Like, Dislike: *sm.Dislike, Favorite: *sm.Favorite, Reply: *sm.Reply, Share: *sm.Share, Coin: *sm.Coin, }, } if err = s.articleRPC.SetStat(context.TODO(), stat); err != nil { log.Error("s.articleRPC.SetStat aid(%d) view(%d) likes(%d) dislike(%d) favorite(%d) reply(%d) share(%d) coin(%d) error(%+v)", sm.Aid, *sm.View, *sm.Like, *sm.Dislike, *sm.Favorite, *sm.Reply, *sm.Share, *sm.Coin, err) dao.PromError("stat:更新计数缓存") s.dao.PushStat(c, &dao.StatRetry{ Action: dao.RetryUpdateStatCache, Count: count, Data: sm, }) return } log.Info("update cache success aid(%d) view(%d) likes(%d) dislike(%d) favorite(%d) reply(%d) share(%d) coin(%d)", sm.Aid, *sm.View, *sm.Like, *sm.Dislike, *sm.Favorite, *sm.Reply, *sm.Share, *sm.Coin) dao.PromInfo("stat:更新计数缓存") return } // updateSortCache update sort cache func (s *Service) updateSortCache(c context.Context, aid int64, changed [][2]int64) (err error) { if len(changed) == 0 { return } arg := &artmdl.ArgSort{ Aid: aid, Changed: changed, } if err = s.articleRPC.UpdateSortCache(context.TODO(), arg); err != nil { log.Error("s.articleRPC.UpdateSortCache(aid:%v arg: %+v)", aid, arg) dao.PromError("stat:更新排序缓存") return } log.Info("success s.articleRPC.UpdateSortCache(aid:%v arg: %+v)", aid, arg) dao.PromInfo("stat:更新排序缓存") return } // updateDB update stat in db. func (s *Service) updateDB(c context.Context, stat *artmdl.StatMsg, count int) (err error) { if _, err = s.dao.Update(context.TODO(), stat); err != nil { dao.PromError("stat:更新计数DB") s.dao.PushStat(c, &dao.StatRetry{ Action: dao.RetryUpdateStatDB, Count: count, Data: stat, }) return } log.Info("update db success aid(%d) view(%d) likes(%d) dislike(%d) favorite(%d) reply(%d) share(%d) coin(%d)", stat.Aid, *stat.View, *stat.Like, *stat.Dislike, *stat.Favorite, *stat.Reply, *stat.Share, *stat.Coin) return } // multiUpdateDB update some stat in db. func (s *Service) multiUpdateDB(i int64, last map[int64]*lastTimeStat) (err error) { log.Info("multiUpdateDB close(%d) start", i) c := context.TODO() for aid, ls := range last { s.updateDB(c, ls.stat, 0) log.Info("multiUpdateDB close(%d) update stats aid: %d, value: %+v", i, aid, ls.stat) } log.Info("multiUpdateDB close(%d) end", i) return } // intercept intercepts illegal views. func (s *Service) intercept(stat *artmdl.StatMsg) bool { return s.dao.Intercept(context.TODO(), stat.Aid, stat.Mid, stat.IP) } func (s *Service) cheatInfo(cheat *artmdl.CheatInfo) { if cheat == nil { return } log.Info("cheatInfo %+v", cheat) if err := s.cheatInfoc.Info(cheat.Valid, cheat.Client, cheat.Cvid, cheat.Mid, cheat.Lv, cheat.Ts, cheat.IP, cheat.UA, cheat.Refer, cheat.Sid, cheat.Buvid, cheat.DeviceID, cheat.Build, cheat.Reason); err != nil { log.Error("cheatInfo error(%+v)", err) } } func (s *Service) updateRecheckDB(c context.Context, stat *artmdl.StatMsg) (err error) { var ( publishTime int64 checkState int ) if s.setting.Recheck.View == 0 || s.setting.Recheck.Day == 0 { return } if isRecheck, _ := s.dao.GetRecheckCache(c, stat.Aid); isRecheck { return } if publishTime, checkState, err = s.dao.GetRecheckInfo(c, stat.Aid); err != nil || checkState != 0 { return } if s.dao.IsAct(c, stat.Aid) { return } if *(stat.View) > s.setting.Recheck.View { if publishTime+60*60*24*s.setting.Recheck.Day+s.updateDbInterval > time.Now().Unix() { if err = s.dao.UpdateRecheck(c, stat.Aid); err == nil { log.Info("update recheck success aid(%d)", stat.Aid) s.dao.SetRecheckCache(c, stat.Aid) } } } return }