diff --git a/cache/abstract.go b/cache/abstract.go index 16db547..ba48459 100644 --- a/cache/abstract.go +++ b/cache/abstract.go @@ -11,11 +11,12 @@ import ( ) const ( - TAG_ENTITY = "ENTITY" - TAG_COUNT = "COUNT" - TAG_SEARCH = "SEARCH" - TAG_EXISTS = "CHECK" - TAG_SESSION = "SESSION" + TAG_ENTITY = "ENTITY" + TAG_COUNT = "COUNT" + TAG_SEARCH = "SEARCH" + TAG_EXISTS = "CHECK" + TAG_SESSION = "SESSION" + TAG_RELATION = "RELATION" ) // 向Redis缓存中保存一个数据 @@ -26,7 +27,7 @@ func Cache[T interface{}](key string, value *T, expires time.Duration) error { if err != nil { return err } - cmd := global.RedisConn.SetEX(global.Ctx, key, serializedValue, expires) + cmd := global.RedisConn.Set(global.Ctx, key, serializedValue, expires) return cmd.Err() } diff --git a/cache/count.go b/cache/count.go index 488b892..5aa675d 100644 --- a/cache/count.go +++ b/cache/count.go @@ -2,7 +2,6 @@ package cache import ( "electricity_bill_calc/global" - "fmt" "strconv" "strings" ) @@ -14,18 +13,15 @@ func assembleCountKey(entityName string) string { } func assembleCountIdentification(additional ...string) string { - var b strings.Builder - for _, s := range additional { - fmt.Fprintf(&b, ":%s", s) - } - return b.String() + return strings.Join(additional, ":") } // 向缓存中缓存模型名称明确的包含指定条件的实体记录数量 -func CacheCount(entityName string, count int64, conditions ...string) error { +func CacheCount(relationName, entityName string, count int64, conditions ...string) error { countKey := assembleCountKey(entityName) identification := assembleCountIdentification(conditions...) result := global.RedisConn.HSet(global.Ctx, countKey, map[string]interface{}{identification: count}) + CacheRelation(relationName, STORE_TYPE_HASH, countKey, identification) return result.Err() } diff --git a/cache/entity.go b/cache/entity.go index 3a8d71d..ee99a4a 100644 --- a/cache/entity.go +++ b/cache/entity.go @@ -8,9 +8,9 @@ import ( func assembleEntityKey(entityName, id string) string { var keys = make([]string, 0) - keys = append(keys, TAG_ENTITY) keys = append(keys, strings.ToUpper(entityName), id) var b strings.Builder + b.WriteString(TAG_ENTITY) for _, s := range keys { fmt.Fprintf(&b, ":%s", s) } @@ -18,9 +18,10 @@ func assembleEntityKey(entityName, id string) string { } // 缓存模型名称明确的,使用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) err := Cache(entityKey, &instance, 0) + CacheRelation(relationName, STORE_TYPE_KEY, entityKey) return err } diff --git a/cache/exists.go b/cache/exists.go index 560b1e1..8abe9a7 100644 --- a/cache/exists.go +++ b/cache/exists.go @@ -14,18 +14,15 @@ func assembleExistsKey(entityName string) string { } func assembleExistsIdentification(additional ...string) string { - var b strings.Builder - for _, s := range additional { - fmt.Fprintf(&b, ":%s", s) - } - return b.String() + return strings.Join(additional, ":") } // 缓存模型名称明确的、包含指定ID以及一些附加条件的记录 -func CacheExists(entityName string, conditions ...string) error { +func CacheExists(relationName, entityName string, conditions ...string) error { existskey := assembleExistsKey(entityName) identification := assembleExistsIdentification(conditions...) result := global.RedisConn.SAdd(global.Ctx, existskey, identification) + CacheRelation(relationName, STORE_TYPE_SET, existskey, identification) return result.Err() } diff --git a/cache/relation.go b/cache/relation.go new file mode 100644 index 0000000..c57df8b --- /dev/null +++ b/cache/relation.go @@ -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 +} diff --git a/cache/search.go b/cache/search.go index 8376663..2819fa6 100644 --- a/cache/search.go +++ b/cache/search.go @@ -8,10 +8,10 @@ import ( func assembleSearchKey(entityName string, additional ...string) string { var keys = make([]string, 0) - keys = append(keys, TAG_SEARCH) keys = append(keys, strings.ToUpper(entityName)) keys = append(keys, additional...) var b strings.Builder + b.WriteString(TAG_SEARCH) for _, s := range keys { fmt.Fprintf(&b, ":%s", s) } @@ -19,9 +19,10 @@ func assembleSearchKey(entityName string, additional ...string) string { } // 缓存模型名称明确的,使用或者包含非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...) err := Cache(searchKey, &instance, 0) + CacheRelation(relationName, STORE_TYPE_KEY, searchKey) return err } diff --git a/service/user.go b/service/user.go index 3059392..a938a91 100644 --- a/service/user.go +++ b/service/user.go @@ -8,6 +8,7 @@ import ( "electricity_bill_calc/model" "electricity_bill_calc/tools" "fmt" + "strconv" "time" "github.com/fufuok/utils" @@ -114,8 +115,8 @@ func (u _UserService) InvalidUserPassword(uid string) (string, error) { } if affected > 0 { // ! 同一个用户在缓存中有两个键。 - cache.AbolishCacheData("user", user.Id) - cache.AbolishCacheData("user", user.Username) + cache.AbolishRelation(fmt.Sprintf("user_%s", uid)) + cache.AbolishRelation("user") return verifyCode, nil } else { return "", exceptions.NewUnsuccessfulOperationError() @@ -147,8 +148,8 @@ func (u _UserService) ResetUserPassword(username, password string) (bool, error) return false, err } if affected > 0 { - cache.AbolishCacheData("user", user.Id) - cache.AbolishCacheData("user", user.Username) + cache.AbolishRelation(fmt.Sprintf("user_%s", user.Id)) + cache.AbolishRelation("user") return true, nil } else { return false, nil @@ -156,11 +157,25 @@ func (u _UserService) ResetUserPassword(username, password 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) { - 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) { @@ -205,6 +220,7 @@ func (u _UserService) CreateUser(user *model.User, detail *model.UserDetail) (st tx.Rollback() return "", fmt.Errorf("transaction commit unsuccessful: %w", err) } + cache.AbolishRelation("user") return verifyCode, nil } @@ -219,10 +235,17 @@ func (u _UserService) SwitchUserState(uid string, enabled bool) error { newStateUser := new(model.User) newStateUser.Enabled = enabled _, err = global.DBConn.ID(uid).Cols("enabled").Update(newStateUser) + if err != nil { + cache.AbolishRelation("user") + cache.AbolishRelation(fmt.Sprintf("user_%s", uid)) + } return err } 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) err := global.DBConn. Table("user_detail").Alias("d"). @@ -239,83 +262,105 @@ func (_UserService) SearchLimitUsers(keyword string, limit int) ([]model.JoinedU if err != nil { return make([]model.JoinedUserDetail, 0), err } + cache.CacheSearch(users, "user", "join_user_detail", strconv.Itoa(limit)) return users, nil } func (_UserService) findUserByUsername(username string) (*model.User, error) { - cachedUser, _ := cache.RetreiveData[model.User]("user", username) - if cachedUser != nil { + if cachedUser, _ := cache.RetreiveSearch[model.User]("user", username); cachedUser != nil { return cachedUser, nil } user := new(model.User) has, err := global.DBConn.Where(builder.Eq{"username": username}).NoAutoCondition().Get(user) if has { - cache.CacheData(user, "user", username) + cache.CacheSearch(*user, "user", "user", username) } return _postProcessSingle(user, has, err) } func (_UserService) retreiveUserDetail(uid string) (*model.UserDetail, error) { - cachedUser, _ := cache.RetreiveData[model.UserDetail]("user", uid) - if cachedUser != nil { + if cachedUser, _ := cache.RetreiveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil { return cachedUser, nil } user := new(model.UserDetail) has, err := global.DBConn.ID(uid).NoAutoCondition().Get(user) if has { - cache.CacheData(user, "user_detail", uid) + cache.CacheEntity(*user, fmt.Sprintf("user_%s", uid), "user_detail", uid) } return _postProcessSingle(user, has, err) } 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 { return cachedUser, nil } user := new(model.User) has, err := global.DBConn.ID(uid).NoAutoCondition().Get(user) if has { - cache.CacheData(user, "user", uid) + cache.CacheEntity(*user, fmt.Sprintf("user_%s", uid), "user", uid) } return _postProcessSingle(user, has, err) } 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"}) if len(keyword) != 0 { keywordCond := builder.NewCond(). Or(builder.Like{"u.username", keyword}). Or(builder.Like{"d.name", keyword}) cond = cond.And(keywordCond) + cacheConditions = append(cacheConditions, keyword) } if userType != -1 { cond = cond.And(builder.Eq{"u.type": userType}) + cacheConditions = append(cacheConditions, strconv.Itoa(userType)) } if userState != nil { cond = cond.And(builder.Eq{"u.enabled": *userState}) + cacheConditions = append(cacheConditions, strconv.FormatBool(*userState)) } startItem := (page - 1) * config.ServiceSettings.ItemsPageSize - 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 + var ( + total int64 + err error + ) + if cacheCounts, _ := cache.RetreiveCount("join_user_detail", cacheConditions...); cacheCounts != -1 { + total = cacheCounts + } 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) + if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", cacheConditions...); cachedUsers != nil { + return *cachedUsers, total, nil + } err = global.DBConn. Table("user_detail").Alias("d"). Join("INNER", []string{"user", "u"}, "d.id=u.id"). Where(cond). Limit(config.ServiceSettings.ItemsPageSize, startItem). Find(&users) + cache.CacheSearch(users, "user", "join_user_detail", cacheConditions...) return users, total, err } 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{} has, err := global.DBConn. Table("user_detail").Alias("d"). @@ -324,6 +369,7 @@ func (_UserService) FetchUserDetail(uid string) (*model.FullJoinedUserDetail, er NoAutoCondition(). Get(user) if has { + cache.CacheEntity(*user, fmt.Sprintf("user_%s", uid), "full_join_user_detail", uid) return user, nil } return nil, err