123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- package vip
- import (
- "context"
- "encoding/json"
- "fmt"
- "github.com/pkg/errors"
- "go-common/app/service/live/xuser/model"
- "go-common/library/cache/redis"
- "go-common/library/log"
- "go-common/library/net/metadata"
- "time"
- )
- // redis cache
- const (
- _userInfoRedisKey = "us:infoo_v2:%d" // 用户缓存key prefix
- _vipFieldName = "vip" // v3 vip attr field
- _levelFieldName = "level" // v2 level attr field, Todo: remove level attr
- _userExpired = 86400 // user cache expire time
- )
- type vipCache struct {
- Vip interface{} `json:"vip"`
- VipTime string `json:"vip_time"`
- Svip interface{} `json:"svip"`
- SvipTime string `json:"svip_time"`
- }
- // GetVipFromCache get user vip info from cache
- func (d *Dao) GetVipFromCache(ctx context.Context, uid int64) (info *model.VipInfo, err error) {
- conn := d.redis.Get(ctx)
- defer conn.Close()
- reply, err := redis.String(conn.Do("HGET", getUserCacheKey(uid), _vipFieldName))
- if err != nil {
- if err == redis.ErrNil {
- // key or field not exists, return nil, nil, back to db
- log.Info("[dao.vip.cache|GetVipFromCache] cache key or field not exists err(%v), uid(%d)", err, uid)
- return nil, nil
- }
- log.Error("[dao.vip.cache|GetVipFromCache] hget error(%v), uid(%d)", err, uid)
- return
- }
- if reply == "" {
- return nil, nil
- }
- // ===== begin eat others' dog food =====
- // 1.兼容缓存中vip/svip可能是int or string的问题
- rawInfo := &vipCache{}
- if err = json.Unmarshal([]byte(reply), rawInfo); err != nil {
- log.Error("[dao.vip.cache|GetVipFromCache] json.Unmarshal rawInfo error(%v), uid(%d), reply(%s)",
- err, uid, reply)
- // parse cache json error, return nil, nil, back to db and restore cache
- return nil, nil
- }
- if info, err = d.formatVipCache(rawInfo); err != nil {
- log.Error("[dao.vip.cache|GetVipFromCache] format rawInfo error(%v), uid(%d), reply(%s)", err, uid, reply)
- return nil, nil
- }
- // 2.注意!!! cache里的vip_time/svip_time不一定正确,可能含有已经过期的time
- currentTime := time.Now().Unix()
- // vip time
- if info.Vip, err = d.checkVipTime(info.VipTime, info.Vip, currentTime); err != nil {
- log.Error("[dao.vip.cache|GetVipFromCache] check vip time error(%v), uid(%d), info(%v), reply(%s)",
- err, uid, info, reply)
- return nil, nil
- }
- if info.Svip, err = d.checkVipTime(info.SvipTime, info.Svip, currentTime); err != nil {
- log.Error("[dao.vip.cache|GetVipFromCache] check svip time error(%v), uid(%d), info(%v), reply(%s)",
- err, uid, info, reply)
- return nil, nil
- }
- // ===== end =====
- return
- }
- // formatVipCache 转换vip/svip的格式
- func (d *Dao) formatVipCache(info *vipCache) (v *model.VipInfo, err error) {
- v = &model.VipInfo{
- VipTime: info.VipTime,
- SvipTime: info.SvipTime,
- }
- if v.Vip, err = toInt(info.Vip); err != nil {
- return
- }
- if v.Svip, err = toInt(info.Svip); err != nil {
- return
- }
- // format info struct
- v = d.initInfo(v)
- return
- }
- // checkVipTime 检查缓存中vip_time/svip_time是否过期
- func (d *Dao) checkVipTime(t string, f int, compare int64) (int, error) {
- if t == model.TimeEmpty {
- if f != 0 {
- return 0, errors.New("empty time with not zero flag.")
- }
- } else {
- vt, err := time.Parse(model.TimeNano, t)
- if err != nil {
- return 0, errors.New("time parse error.")
- }
- if vt.Unix() <= compare {
- return 0, nil
- }
- }
- return f, nil
- }
- // SetVipCache set vip to cache
- func (d *Dao) SetVipCache(ctx context.Context, uid int64, info *model.VipInfo) (err error) {
- var vipJson []byte
- conn := d.redis.Get(ctx)
- key := getUserCacheKey(uid)
- defer conn.Close()
- // format info struct
- info = d.initInfo(info)
- // format info json string
- vipJson, err = json.Marshal(info)
- if err != nil {
- log.Error("[dao.vip.cache|SetVipCache] json.Marshal error(%v), uid(%d), info(%v)", err, uid, info)
- // if marshal error, clear cache
- goto CLEAR
- }
- _, err = conn.Do("HSET", key, _vipFieldName, string(vipJson))
- if err != nil {
- log.Error("[dao.vip.cache|SetVipCache] HSET error(%v), uid(%d), info(%v)", err, uid, info)
- // if hset error, clear cache
- goto CLEAR
- }
- _, err = conn.Do("EXPIRE", key, _userExpired)
- if err != nil {
- log.Error("[dao.vip.cache|SetVipCache] EXPIRE error(%v), uid(%d), info(%v)", err, uid, info)
- // if set expire error, clear cache
- goto CLEAR
- }
- return
- CLEAR:
- log.Error("[dao.vip.cache|SetVipCache] set error, aysnc clear, uid(%d), info(%v)", uid, info)
- go d.ClearCache(metadata.WithContext(ctx), uid)
- return
- }
- // ClearCache clear user's vip and level field cache
- // Todo: remove level attr
- func (d *Dao) ClearCache(ctx context.Context, uid int64) (err error) {
- conn := d.redis.Get(ctx)
- defer conn.Close()
- key := getUserCacheKey(uid)
- _, err = conn.Do("HDEL", key, _vipFieldName, _levelFieldName)
- if err != nil {
- err = errors.Wrapf(err, "conn.Do(HDEL, %s, %s, %s)", key, _vipFieldName, _levelFieldName)
- log.Error("[dao.vip.cache|ClearCache] hdel uid(%d) vip and level attr err(%v)", uid, err)
- }
- return
- }
- func getUserCacheKey(uid int64) string {
- return fmt.Sprintf(_userInfoRedisKey, uid)
- }
|