You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
5.0 KiB
168 lines
5.0 KiB
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) |
|
}
|
|
|