forked from free-lancers/electricity_bill_calc_service
		
	feat(cache):实验完成缓存控制机制。
This commit is contained in:
		
							
								
								
									
										3
									
								
								cache/abstract.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								cache/abstract.go
									
									
									
									
										vendored
									
									
								
							| @@ -16,6 +16,7 @@ const ( | ||||
| 	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() | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										10
									
								
								cache/count.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								cache/count.go
									
									
									
									
										vendored
									
									
								
							| @@ -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() | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										5
									
								
								cache/entity.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								cache/entity.go
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								cache/exists.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								cache/exists.go
									
									
									
									
										vendored
									
									
								
							| @@ -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() | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										59
									
								
								cache/relation.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								cache/relation.go
									
									
									
									
										vendored
									
									
										Normal 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
									
									
								
							
							
						
						
									
										5
									
								
								cache/search.go
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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,65 +262,78 @@ 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. | ||||
| 	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). | ||||
| @@ -305,17 +341,26 @@ func (_UserService) ListUserDetail(keyword string, userType int, userState *bool | ||||
| 		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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user