feat(cache):实验完成缓存控制机制。

This commit is contained in:
徐涛 2022-08-25 15:44:09 +08:00
parent f58c05d2fb
commit 47ffb5efbb
7 changed files with 146 additions and 45 deletions

13
cache/abstract.go vendored
View File

@ -11,11 +11,12 @@ import (
) )
const ( const (
TAG_ENTITY = "ENTITY" TAG_ENTITY = "ENTITY"
TAG_COUNT = "COUNT" TAG_COUNT = "COUNT"
TAG_SEARCH = "SEARCH" TAG_SEARCH = "SEARCH"
TAG_EXISTS = "CHECK" TAG_EXISTS = "CHECK"
TAG_SESSION = "SESSION" TAG_SESSION = "SESSION"
TAG_RELATION = "RELATION"
) )
// 向Redis缓存中保存一个数据 // 向Redis缓存中保存一个数据
@ -26,7 +27,7 @@ func Cache[T interface{}](key string, value *T, expires time.Duration) error {
if err != nil { if err != nil {
return err return err
} }
cmd := global.RedisConn.SetEX(global.Ctx, key, serializedValue, expires) cmd := global.RedisConn.Set(global.Ctx, key, serializedValue, expires)
return cmd.Err() return cmd.Err()
} }

10
cache/count.go vendored
View File

@ -2,7 +2,6 @@ package cache
import ( import (
"electricity_bill_calc/global" "electricity_bill_calc/global"
"fmt"
"strconv" "strconv"
"strings" "strings"
) )
@ -14,18 +13,15 @@ func assembleCountKey(entityName string) string {
} }
func assembleCountIdentification(additional ...string) string { func assembleCountIdentification(additional ...string) string {
var b strings.Builder return strings.Join(additional, ":")
for _, s := range additional {
fmt.Fprintf(&b, ":%s", s)
}
return b.String()
} }
// 向缓存中缓存模型名称明确的包含指定条件的实体记录数量 // 向缓存中缓存模型名称明确的包含指定条件的实体记录数量
func CacheCount(entityName string, count int64, conditions ...string) error { func CacheCount(relationName, entityName string, count int64, conditions ...string) error {
countKey := assembleCountKey(entityName) countKey := assembleCountKey(entityName)
identification := assembleCountIdentification(conditions...) identification := assembleCountIdentification(conditions...)
result := global.RedisConn.HSet(global.Ctx, countKey, map[string]interface{}{identification: count}) result := global.RedisConn.HSet(global.Ctx, countKey, map[string]interface{}{identification: count})
CacheRelation(relationName, STORE_TYPE_HASH, countKey, identification)
return result.Err() return result.Err()
} }

5
cache/entity.go vendored
View File

@ -8,9 +8,9 @@ import (
func assembleEntityKey(entityName, id string) string { func assembleEntityKey(entityName, id string) string {
var keys = make([]string, 0) var keys = make([]string, 0)
keys = append(keys, TAG_ENTITY)
keys = append(keys, strings.ToUpper(entityName), id) keys = append(keys, strings.ToUpper(entityName), id)
var b strings.Builder var b strings.Builder
b.WriteString(TAG_ENTITY)
for _, s := range keys { for _, s := range keys {
fmt.Fprintf(&b, ":%s", s) fmt.Fprintf(&b, ":%s", s)
} }
@ -18,9 +18,10 @@ func assembleEntityKey(entityName, id string) string {
} }
// 缓存模型名称明确的使用ID进行检索的实体内容。 // 缓存模型名称明确的使用ID进行检索的实体内容。
func CacheEntity[T any](instance T, entityName, id string) error { func CacheEntity[T any](instance T, relationName, entityName, id string) error {
entityKey := assembleEntityKey(entityName, id) entityKey := assembleEntityKey(entityName, id)
err := Cache(entityKey, &instance, 0) err := Cache(entityKey, &instance, 0)
CacheRelation(relationName, STORE_TYPE_KEY, entityKey)
return err return err
} }

9
cache/exists.go vendored
View File

@ -14,18 +14,15 @@ func assembleExistsKey(entityName string) string {
} }
func assembleExistsIdentification(additional ...string) string { func assembleExistsIdentification(additional ...string) string {
var b strings.Builder return strings.Join(additional, ":")
for _, s := range additional {
fmt.Fprintf(&b, ":%s", s)
}
return b.String()
} }
// 缓存模型名称明确的、包含指定ID以及一些附加条件的记录 // 缓存模型名称明确的、包含指定ID以及一些附加条件的记录
func CacheExists(entityName string, conditions ...string) error { func CacheExists(relationName, entityName string, conditions ...string) error {
existskey := assembleExistsKey(entityName) existskey := assembleExistsKey(entityName)
identification := assembleExistsIdentification(conditions...) identification := assembleExistsIdentification(conditions...)
result := global.RedisConn.SAdd(global.Ctx, existskey, identification) result := global.RedisConn.SAdd(global.Ctx, existskey, identification)
CacheRelation(relationName, STORE_TYPE_SET, existskey, identification)
return result.Err() return result.Err()
} }

59
cache/relation.go vendored Normal file
View File

@ -0,0 +1,59 @@
package cache
import (
"electricity_bill_calc/global"
"electricity_bill_calc/tools"
"strings"
)
const (
STORE_TYPE_KEY = "KEY"
STORE_TYPE_SET = "SET"
STORE_TYPE_HASH = "HASH"
)
func assembleRelationKey(relationName string) string {
var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(relationName))
return CacheKey(TAG_RELATION, keys...)
}
func assembleRelationIdentity(storeType, key string, field ...string) string {
var identity = make([]string, 0)
identity = append(identity, storeType, key)
identity = append(identity, field...)
return strings.Join(identity, ";")
}
// 向缓存中保存与指定关联名称相关联的键的名称以及键的类型和子字段的组成。
func CacheRelation(relationName, storeType, key string, field ...string) error {
relationKey := assembleRelationKey(relationName)
relationIdentity := assembleRelationIdentity(storeType, key, field...)
result := global.RedisConn.SAdd(global.Ctx, relationKey, relationIdentity)
return result.Err()
}
// 从缓存中清理指定的关联键
func AbolishRelation(relationName string) error {
relationKey := assembleRelationKey(relationName)
result := global.RedisConn.SMembers(global.Ctx, relationKey)
if result.Err() != nil {
return result.Err()
}
relationItems := result.Val()
pipeline := global.RedisConn.Pipeline()
for _, item := range relationItems {
separated := strings.Split(item, ";")
switch separated[0] {
case STORE_TYPE_KEY:
pipeline.Del(global.Ctx, separated[1])
case STORE_TYPE_HASH:
pipeline.HDel(global.Ctx, separated[1], separated[2:]...)
case STORE_TYPE_SET:
pipeline.SRem(global.Ctx, separated[1], tools.ConvertSliceToInterfaceSlice(separated[2:])...)
}
}
pipeline.Del(global.Ctx, relationKey)
_, err := pipeline.Exec(global.Ctx)
return err
}

5
cache/search.go vendored
View File

@ -8,10 +8,10 @@ import (
func assembleSearchKey(entityName string, additional ...string) string { func assembleSearchKey(entityName string, additional ...string) string {
var keys = make([]string, 0) var keys = make([]string, 0)
keys = append(keys, TAG_SEARCH)
keys = append(keys, strings.ToUpper(entityName)) keys = append(keys, strings.ToUpper(entityName))
keys = append(keys, additional...) keys = append(keys, additional...)
var b strings.Builder var b strings.Builder
b.WriteString(TAG_SEARCH)
for _, s := range keys { for _, s := range keys {
fmt.Fprintf(&b, ":%s", s) fmt.Fprintf(&b, ":%s", s)
} }
@ -19,9 +19,10 @@ func assembleSearchKey(entityName string, additional ...string) string {
} }
// 缓存模型名称明确的使用或者包含非ID检索条件的实体内容。 // 缓存模型名称明确的使用或者包含非ID检索条件的实体内容。
func CacheSearch[T any](instance T, entityName string, conditions ...string) error { func CacheSearch[T any](instance T, relationName, entityName string, conditions ...string) error {
searchKey := assembleSearchKey(entityName, conditions...) searchKey := assembleSearchKey(entityName, conditions...)
err := Cache(searchKey, &instance, 0) err := Cache(searchKey, &instance, 0)
CacheRelation(relationName, STORE_TYPE_KEY, searchKey)
return err return err
} }

View File

@ -8,6 +8,7 @@ import (
"electricity_bill_calc/model" "electricity_bill_calc/model"
"electricity_bill_calc/tools" "electricity_bill_calc/tools"
"fmt" "fmt"
"strconv"
"time" "time"
"github.com/fufuok/utils" "github.com/fufuok/utils"
@ -114,8 +115,8 @@ func (u _UserService) InvalidUserPassword(uid string) (string, error) {
} }
if affected > 0 { if affected > 0 {
// ! 同一个用户在缓存中有两个键。 // ! 同一个用户在缓存中有两个键。
cache.AbolishCacheData("user", user.Id) cache.AbolishRelation(fmt.Sprintf("user_%s", uid))
cache.AbolishCacheData("user", user.Username) cache.AbolishRelation("user")
return verifyCode, nil return verifyCode, nil
} else { } else {
return "", exceptions.NewUnsuccessfulOperationError() return "", exceptions.NewUnsuccessfulOperationError()
@ -147,8 +148,8 @@ func (u _UserService) ResetUserPassword(username, password string) (bool, error)
return false, err return false, err
} }
if affected > 0 { if affected > 0 {
cache.AbolishCacheData("user", user.Id) cache.AbolishRelation(fmt.Sprintf("user_%s", user.Id))
cache.AbolishCacheData("user", user.Username) cache.AbolishRelation("user")
return true, nil return true, nil
} else { } else {
return false, nil return false, nil
@ -156,11 +157,25 @@ func (u _UserService) ResetUserPassword(username, password string) (bool, error)
} }
func (_UserService) IsUserExists(uid string) (bool, error) { func (_UserService) IsUserExists(uid string) (bool, error) {
return global.DBConn.ID(uid).Exist(&model.User{}) if has, _ := cache.CheckExists("user", uid); has {
return has, nil
}
has, err := global.DBConn.ID(uid).Exist(&model.User{})
if has {
cache.CacheExists(fmt.Sprintf("user_%s", uid), "user", uid)
}
return has, err
} }
func (_UserService) IsUsernameExists(username string) (bool, error) { func (_UserService) IsUsernameExists(username string) (bool, error) {
return global.DBConn.Where(builder.Eq{"username": username}).Exist(&model.User{}) if has, _ := cache.CheckExists("user", username); has {
return has, nil
}
has, err := global.DBConn.Where(builder.Eq{"username": username}).Exist(&model.User{})
if has {
cache.CacheExists("user", "user", username)
}
return has, err
} }
func (u _UserService) CreateUser(user *model.User, detail *model.UserDetail) (string, error) { func (u _UserService) CreateUser(user *model.User, detail *model.UserDetail) (string, error) {
@ -205,6 +220,7 @@ func (u _UserService) CreateUser(user *model.User, detail *model.UserDetail) (st
tx.Rollback() tx.Rollback()
return "", fmt.Errorf("transaction commit unsuccessful: %w", err) return "", fmt.Errorf("transaction commit unsuccessful: %w", err)
} }
cache.AbolishRelation("user")
return verifyCode, nil return verifyCode, nil
} }
@ -219,10 +235,17 @@ func (u _UserService) SwitchUserState(uid string, enabled bool) error {
newStateUser := new(model.User) newStateUser := new(model.User)
newStateUser.Enabled = enabled newStateUser.Enabled = enabled
_, err = global.DBConn.ID(uid).Cols("enabled").Update(newStateUser) _, err = global.DBConn.ID(uid).Cols("enabled").Update(newStateUser)
if err != nil {
cache.AbolishRelation("user")
cache.AbolishRelation(fmt.Sprintf("user_%s", uid))
}
return err return err
} }
func (_UserService) SearchLimitUsers(keyword string, limit int) ([]model.JoinedUserDetail, error) { func (_UserService) SearchLimitUsers(keyword string, limit int) ([]model.JoinedUserDetail, error) {
if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", keyword, strconv.Itoa(limit)); cachedUsers != nil {
return *cachedUsers, nil
}
var users = make([]model.JoinedUserDetail, 0) var users = make([]model.JoinedUserDetail, 0)
err := global.DBConn. err := global.DBConn.
Table("user_detail").Alias("d"). Table("user_detail").Alias("d").
@ -239,83 +262,105 @@ func (_UserService) SearchLimitUsers(keyword string, limit int) ([]model.JoinedU
if err != nil { if err != nil {
return make([]model.JoinedUserDetail, 0), err return make([]model.JoinedUserDetail, 0), err
} }
cache.CacheSearch(users, "user", "join_user_detail", strconv.Itoa(limit))
return users, nil return users, nil
} }
func (_UserService) findUserByUsername(username string) (*model.User, error) { func (_UserService) findUserByUsername(username string) (*model.User, error) {
cachedUser, _ := cache.RetreiveData[model.User]("user", username) if cachedUser, _ := cache.RetreiveSearch[model.User]("user", username); cachedUser != nil {
if cachedUser != nil {
return cachedUser, nil return cachedUser, nil
} }
user := new(model.User) user := new(model.User)
has, err := global.DBConn.Where(builder.Eq{"username": username}).NoAutoCondition().Get(user) has, err := global.DBConn.Where(builder.Eq{"username": username}).NoAutoCondition().Get(user)
if has { if has {
cache.CacheData(user, "user", username) cache.CacheSearch(*user, "user", "user", username)
} }
return _postProcessSingle(user, has, err) return _postProcessSingle(user, has, err)
} }
func (_UserService) retreiveUserDetail(uid string) (*model.UserDetail, error) { func (_UserService) retreiveUserDetail(uid string) (*model.UserDetail, error) {
cachedUser, _ := cache.RetreiveData[model.UserDetail]("user", uid) if cachedUser, _ := cache.RetreiveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil {
if cachedUser != nil {
return cachedUser, nil return cachedUser, nil
} }
user := new(model.UserDetail) user := new(model.UserDetail)
has, err := global.DBConn.ID(uid).NoAutoCondition().Get(user) has, err := global.DBConn.ID(uid).NoAutoCondition().Get(user)
if has { if has {
cache.CacheData(user, "user_detail", uid) cache.CacheEntity(*user, fmt.Sprintf("user_%s", uid), "user_detail", uid)
} }
return _postProcessSingle(user, has, err) return _postProcessSingle(user, has, err)
} }
func (_UserService) findUserByID(uid string) (*model.User, error) { func (_UserService) findUserByID(uid string) (*model.User, error) {
cachedUser, _ := cache.RetreiveData[model.User]("user", uid) cachedUser, _ := cache.RetreiveEntity[model.User]("user", uid)
if cachedUser != nil { if cachedUser != nil {
return cachedUser, nil return cachedUser, nil
} }
user := new(model.User) user := new(model.User)
has, err := global.DBConn.ID(uid).NoAutoCondition().Get(user) has, err := global.DBConn.ID(uid).NoAutoCondition().Get(user)
if has { if has {
cache.CacheData(user, "user", uid) cache.CacheEntity(*user, fmt.Sprintf("user_%s", uid), "user", uid)
} }
return _postProcessSingle(user, has, err) return _postProcessSingle(user, has, err)
} }
func (_UserService) ListUserDetail(keyword string, userType int, userState *bool, page int) ([]model.JoinedUserDetail, int64, error) { func (_UserService) ListUserDetail(keyword string, userType int, userState *bool, page int) ([]model.JoinedUserDetail, int64, error) {
var cond = builder.NewCond() var (
cond = builder.NewCond()
cacheConditions = make([]string, 0)
)
cacheConditions = append(cacheConditions, strconv.Itoa(page))
cond = cond.And(builder.Neq{"d.id": "000"}) cond = cond.And(builder.Neq{"d.id": "000"})
if len(keyword) != 0 { if len(keyword) != 0 {
keywordCond := builder.NewCond(). keywordCond := builder.NewCond().
Or(builder.Like{"u.username", keyword}). Or(builder.Like{"u.username", keyword}).
Or(builder.Like{"d.name", keyword}) Or(builder.Like{"d.name", keyword})
cond = cond.And(keywordCond) cond = cond.And(keywordCond)
cacheConditions = append(cacheConditions, keyword)
} }
if userType != -1 { if userType != -1 {
cond = cond.And(builder.Eq{"u.type": userType}) cond = cond.And(builder.Eq{"u.type": userType})
cacheConditions = append(cacheConditions, strconv.Itoa(userType))
} }
if userState != nil { if userState != nil {
cond = cond.And(builder.Eq{"u.enabled": *userState}) cond = cond.And(builder.Eq{"u.enabled": *userState})
cacheConditions = append(cacheConditions, strconv.FormatBool(*userState))
} }
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
total, err := global.DBConn. var (
Table("user_detail").Alias("d"). total int64
Join("INNER", []string{"user", "u"}, "d.id=u.id"). err error
Where(cond). )
Count(&model.User{}) if cacheCounts, _ := cache.RetreiveCount("join_user_detail", cacheConditions...); cacheCounts != -1 {
if err != nil { total = cacheCounts
return nil, -1, err } else {
total, err = global.DBConn.
Table("user_detail").Alias("d").
Join("INNER", []string{"user", "u"}, "d.id=u.id").
Where(cond).
Count(&model.User{})
if err != nil {
return nil, -1, err
}
cache.CacheCount("user", "join_user_detail", total, cacheConditions...)
} }
users := make([]model.JoinedUserDetail, 0) users := make([]model.JoinedUserDetail, 0)
if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", cacheConditions...); cachedUsers != nil {
return *cachedUsers, total, nil
}
err = global.DBConn. err = global.DBConn.
Table("user_detail").Alias("d"). Table("user_detail").Alias("d").
Join("INNER", []string{"user", "u"}, "d.id=u.id"). Join("INNER", []string{"user", "u"}, "d.id=u.id").
Where(cond). Where(cond).
Limit(config.ServiceSettings.ItemsPageSize, startItem). Limit(config.ServiceSettings.ItemsPageSize, startItem).
Find(&users) Find(&users)
cache.CacheSearch(users, "user", "join_user_detail", cacheConditions...)
return users, total, err return users, total, err
} }
func (_UserService) FetchUserDetail(uid string) (*model.FullJoinedUserDetail, error) { func (_UserService) FetchUserDetail(uid string) (*model.FullJoinedUserDetail, error) {
if cachedUser, _ := cache.RetreiveEntity[model.FullJoinedUserDetail]("full_join_user_detail", uid); cachedUser != nil {
return cachedUser, nil
}
user := &model.FullJoinedUserDetail{} user := &model.FullJoinedUserDetail{}
has, err := global.DBConn. has, err := global.DBConn.
Table("user_detail").Alias("d"). Table("user_detail").Alias("d").
@ -324,6 +369,7 @@ func (_UserService) FetchUserDetail(uid string) (*model.FullJoinedUserDetail, er
NoAutoCondition(). NoAutoCondition().
Get(user) Get(user)
if has { if has {
cache.CacheEntity(*user, fmt.Sprintf("user_%s", uid), "full_join_user_detail", uid)
return user, nil return user, nil
} }
return nil, err return nil, err