diff --git a/cache/abstract.go b/cache/abstract.go index 9d36c3f..1135c8a 100644 --- a/cache/abstract.go +++ b/cache/abstract.go @@ -3,6 +3,7 @@ package cache import ( "electricity_bill_calc/global" "fmt" + "math/rand" "strings" "time" @@ -23,9 +24,10 @@ const ( func Cache[T interface{}](key string, value *T, expires time.Duration) error { var err error if expires > 0 { + realExpires := expires + time.Duration(rand.Int63n(60))*time.Second setCmd := global.RedisConn.B().Set(). Key(key).Value(rueidis.JSON(value)). - ExSeconds(int64(expires.Seconds())). + ExSeconds(int64(realExpires.Seconds())). Build() err = global.RedisConn.Do(global.Ctx, setCmd).Error() } else { diff --git a/cache/count.go b/cache/count.go index 9f348bb..b1baa54 100644 --- a/cache/count.go +++ b/cache/count.go @@ -1,49 +1,58 @@ package cache import ( - "electricity_bill_calc/global" - "strconv" + "fmt" "strings" + "time" ) -func assembleCountKey(entityName string) string { - var keys = make([]string, 0) - keys = append(keys, strings.ToUpper(entityName)) - return CacheKey(TAG_COUNT, keys...) +type _CountRecord struct { + Count int64 } -func assembleCountIdentification(additional ...string) string { - return strings.Join(additional, ":") +func assembleCountKey(entityName string, additional ...string) string { + var keys = make([]string, 0) + keys = append(keys, strings.ToUpper(entityName)) + keys = append(keys, additional...) + var b strings.Builder + b.WriteString(TAG_COUNT) + for _, s := range keys { + fmt.Fprintf(&b, ":%s", s) + } + return b.String() } // 向缓存中缓存模型名称明确的包含指定条件的实体记录数量 func CacheCount(relationNames []string, entityName string, count int64, conditions ...string) error { - countKey := assembleCountKey(entityName) - identification := assembleCountIdentification(conditions...) - cmd := global.RedisConn.B().Hset().Key(countKey).FieldValue().FieldValue(identification, strconv.FormatInt(count, 10)).Build() - result := global.RedisConn.Do(global.Ctx, cmd) + countKey := assembleCountKey(entityName, conditions...) + cacheInstance := &_CountRecord{Count: count} + err := Cache(countKey, cacheInstance, 5*time.Minute) for _, relationName := range relationNames { - CacheRelation(relationName, STORE_TYPE_HASH, countKey, identification) + CacheRelation(relationName, STORE_TYPE_KEY, countKey) } - return result.Error() + return err } // 从缓存中获取模型名称明确的,包含指定条件的实体记录数量 func RetreiveCount(entityName string, condtions ...string) (int64, error) { - countKey := assembleCountKey(entityName) - identification := assembleCountIdentification(condtions...) - cmd := global.RedisConn.B().Hget().Key(countKey).Field(identification).Build() - result, err := global.RedisConn.Do(global.Ctx, cmd).AsInt64() + countKey := assembleCountKey(entityName, condtions...) + exist, err := Exists(countKey) if err != nil { return -1, err } - return result, nil + if !exist { + return -1, nil + } + instance, err := Retreive[_CountRecord](countKey) + if instance != nil && err == nil { + return instance.Count, nil + } else { + return -1, err + } } // 删除指定模型名称的数量缓存 func AbolishCountEntity(entityName string) error { - countKey := assembleCountKey(entityName) - cmd := global.RedisConn.B().Del().Key(countKey).Build() - err := global.RedisConn.Do(global.Ctx, cmd).Error() - return err + pattern := fmt.Sprintf("%s:%s:*", TAG_COUNT, strings.ToUpper(entityName)) + return DeleteAll(pattern) } diff --git a/cache/exists.go b/cache/exists.go index d3f8c32..cae7ad7 100644 --- a/cache/exists.go +++ b/cache/exists.go @@ -1,72 +1,49 @@ package cache import ( - "electricity_bill_calc/global" "fmt" "strings" + "time" + + "github.com/samber/lo" ) -func assembleExistsKey(entityName string) string { +func assembleExistsKey(entityName string, additional ...string) string { var keys = make([]string, 0) keys = append(keys, strings.ToUpper(entityName)) - return CacheKey(TAG_EXISTS, keys...) -} - -func assembleExistsIdentification(additional ...string) string { - return strings.Join(additional, ":") + keys = append(keys, additional...) + var b strings.Builder + b.WriteString(TAG_EXISTS) + for _, s := range keys { + fmt.Fprintf(&b, ":%s", s) + } + return b.String() } // 缓存模型名称明确的、包含指定ID以及一些附加条件的记录 func CacheExists(relationNames []string, entityName string, conditions ...string) error { - existskey := assembleExistsKey(entityName) - identification := assembleExistsIdentification(conditions...) - cmd := global.RedisConn.B().Sadd().Key(existskey).Member(identification).Build() - err := global.RedisConn.Do(global.Ctx, cmd).Error() + existskey := assembleExistsKey(entityName, conditions...) + err := Cache(existskey, lo.ToPtr(true), 5*time.Minute) for _, relationName := range relationNames { - CacheRelation(relationName, STORE_TYPE_SET, existskey, identification) + CacheRelation(relationName, STORE_TYPE_KEY, existskey) } return err } // 从缓存中获取模型名称明确、包含指定ID以及一些附加条件的实体是否存在的标记,函数在返回false时不保证数据库中相关记录也不存在 func CheckExists(entityName string, condtions ...string) (bool, error) { - existsKey := assembleExistsKey(entityName) - identification := assembleExistsIdentification(condtions...) - cmd := global.RedisConn.B().Sismember().Key(existsKey).Member(identification).Build() - result, err := global.RedisConn.Do(global.Ctx, cmd).AsBool() - return result, err + existsKey := assembleExistsKey(entityName, condtions...) + return Exists(existsKey) } // 从缓存中删除模型名称明确、包含指定ID的全部实体存在标记 func AbolishExists(entityName, id string) error { - existsKey := assembleExistsKey(entityName) - pattern := fmt.Sprintf("%s*", id) - var ( - err error - cursor int64 - elems = make([]string, 0) - sElem []string - ) - for { - cmd := global.RedisConn.B().Sscan().Key(existsKey).Cursor(cursor).Match(pattern).Count(20).Build() - result := global.RedisConn.Do(global.Ctx, cmd) - cursor, sElem, err = dissembleScan(result) - if err != nil { - return err - } - elems = append(elems, sElem...) - if cursor == 0 { - break - } - } - cmd := global.RedisConn.B().Srem().Key(existsKey).Member(elems...).Build() - err = global.RedisConn.Do(global.Ctx, cmd).Error() - return err + pattern := fmt.Sprintf("%s:%s:%s:*", TAG_EXISTS, strings.ToUpper(entityName), id) + return DeleteAll(pattern) } // 从缓存中删除指定模型名称的全部存在标记 func AbolishExistsEntity(entityName string) error { - existskey := assembleExistsKey(entityName) - _, err := Delete(existskey) - return err + pattern := fmt.Sprintf("%s:%s:*", TAG_EXISTS, strings.ToUpper(entityName)) + return DeleteAll(pattern) } diff --git a/cache/relation.go b/cache/relation.go index 2333dde..f50f5be 100644 --- a/cache/relation.go +++ b/cache/relation.go @@ -2,6 +2,7 @@ package cache import ( "electricity_bill_calc/global" + "fmt" "strings" "github.com/rueian/rueidis" @@ -69,3 +70,53 @@ func AbolishRelation(relationName string) error { return nil } } + +func ClearOrphanRelationItems() error { + var ( + err error + cursor int64 + keys = make([]string, 0) + sKeys []string + ) + for { + scanCmd := global.RedisConn.B().Scan().Cursor(cursor).Match(fmt.Sprintf("%s:*", TAG_RELATION)).Count(20).Build() + results := global.RedisConn.Do(global.Ctx, scanCmd) + cursor, sKeys, err = dissembleScan(results) + if err != nil { + return err + } + keys = append(keys, sKeys...) + if cursor == 0 { + break + } + } + var cmds = make(rueidis.Commands, 0) + for _, key := range keys { + relationItemsCmd := global.RedisConn.B().Smembers().Key(key).Build() + results := global.RedisConn.Do(global.Ctx, relationItemsCmd) + relationItems, err := results.AsStrSlice() + if err != nil { + return err + } + for _, item := range relationItems { + separated := strings.Split(item, ";") + exist, err := Exists(separated[1]) + if err != nil { + return err + } + if !exist { + cmd := global.RedisConn.B().Srem().Key(key).Member(item).Build() + cmds = append(cmds, cmd) + } + } + } + errs := global.RedisConn.DoMulti(global.Ctx, cmds...) + firstErr, has := lo.Find(errs, func(elem rueidis.RedisResult) bool { + return elem.Error() != nil + }) + if has { + return firstErr.Error() + } else { + return nil + } +} diff --git a/cache/repository.go b/cache/repository.go deleted file mode 100644 index 8242c0d..0000000 --- a/cache/repository.go +++ /dev/null @@ -1,33 +0,0 @@ -package cache - -import ( - "electricity_bill_calc/config" -) - -func CacheData[T interface{}](instance T, category string, key ...string) error { - var keys = make([]string, 0) - keys = append(keys, category) - keys = append(keys, key...) - cacheKey := CacheKey("cache", keys...) - if exists, _ := Exists(cacheKey); exists { - Delete(cacheKey) - } - return Cache(cacheKey, &instance, config.ServiceSettings.CacheLifeTime) -} - -func RetreiveData[T interface{}](category string, key ...string) (*T, error) { - var keys = make([]string, 0) - keys = append(keys, category) - keys = append(keys, key...) - return Retreive[T](CacheKey("cache", keys...)) -} - -func AbolishCacheData(category string, key ...string) { - var keys = make([]string, 0) - keys = append(keys, category) - keys = append(keys, key...) - cacheKey := CacheKey("cache", keys...) - if exists, _ := Exists(cacheKey); exists { - Delete(cacheKey) - } -} diff --git a/controller/park.go b/controller/park.go index 6cf64a5..ba47c95 100644 --- a/controller/park.go +++ b/controller/park.go @@ -49,7 +49,8 @@ func listAllParksUnderSessionUser(c *gin.Context) { result.Unauthorized(err.Error()) return } - parks, err := service.ParkService.ListAllParkBelongsTo(userSession.Uid) + keyword := c.DefaultQuery("keyword", "") + parks, err := service.ParkService.ListAllParkBelongsTo(userSession.Uid, keyword) if err != nil { result.Error(http.StatusInternalServerError, err.Error()) return @@ -60,7 +61,8 @@ func listAllParksUnderSessionUser(c *gin.Context) { func listAllParksUnderSpecificUser(c *gin.Context) { result := response.NewResult(c) requestUserId := c.Param("uid") - parks, err := service.ParkService.ListAllParkBelongsTo(requestUserId) + keyword := c.DefaultQuery("keyword", "") + parks, err := service.ParkService.ListAllParkBelongsTo(requestUserId, keyword) if err != nil { result.Error(http.StatusInternalServerError, err.Error()) return @@ -69,16 +71,16 @@ func listAllParksUnderSpecificUser(c *gin.Context) { } type _ParkInfoFormData struct { - Name string `json:"name" form:"name"` - Region *string `json:"region" form:"region"` - Address *string `json:"address" form:"address"` - Contact *string `json:"contact" form:"contact"` - Phone *string `json:"phone" from:"phone"` - Area decimal.NullDecimal `json:"area" from:"area"` - Capacity decimal.NullDecimal `json:"capacity" from:"capacity"` - Tenement decimal.NullDecimal `json:"tenement" from:"tenement"` - Category int `json:"category" form:"category"` - Submeter int `json:"submeter" form:"submeter"` + Name string `json:"name" form:"name"` + Region *string `json:"region" form:"region"` + Address *string `json:"address" form:"address"` + Contact *string `json:"contact" form:"contact"` + Phone *string `json:"phone" from:"phone"` + Area decimal.NullDecimal `json:"area" from:"area"` + Capacity decimal.NullDecimal `json:"capacity" from:"capacity"` + TenementQuantity decimal.NullDecimal `json:"tenement" from:"tenement"` + Category int `json:"category" form:"category"` + Submeter int `json:"submeter" form:"submeter"` } func createNewPark(c *gin.Context) { diff --git a/main.go b/main.go index e617cf8..5f10237 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/model" @@ -157,8 +158,20 @@ func DBConnectionKeepLive() { } } +func RedisOrphanCleanup() { + for range time.Tick(2 * time.Minute) { + log.Printf("[Cache] [Cleanup] Proceeding cleanup orphan keys.") + err := cache.ClearOrphanRelationItems() + if err != nil { + log.Printf("[Cache] [Cleanup] Orphan keys clear failed: %v", err) + continue + } + } +} + func main() { go DBConnectionKeepLive() + go RedisOrphanCleanup() gin.SetMode(config.ServerSettings.RunMode) r := router.Router() r.Run(fmt.Sprintf(":%d", config.ServerSettings.HttpPort)) diff --git a/service/charge.go b/service/charge.go index 3c575a2..8c31265 100644 --- a/service/charge.go +++ b/service/charge.go @@ -205,7 +205,7 @@ func (_ChargeService) ListPagedChargeRecord(keyword, beginDate, endDate string, total int64 err error ) - if cachedTotal, _ := cache.RetreiveCount("charge_with_name", condition...); cachedTotal != -1 { + if cachedTotal, err := cache.RetreiveCount("charge_with_name", condition...); cachedTotal != -1 && err == nil { total = cachedTotal } else { total, err = global.DBConn. diff --git a/service/end_user.go b/service/end_user.go index 96ae9b6..3c92ab7 100644 --- a/service/end_user.go +++ b/service/end_user.go @@ -45,7 +45,7 @@ func (_EndUserService) SearchEndUserRecord(reportId, keyword string, page int) ( total int64 err error ) - if cachedTotal, _ := cache.RetreiveCount("end_user_detail", conditions...); cachedTotal != -1 { + if cachedTotal, err := cache.RetreiveCount("end_user_detail", conditions...); cachedTotal != -1 && err == nil { total = cachedTotal } else { total, err = global.DBConn. diff --git a/service/meter04kv.go b/service/meter04kv.go index 9934cc0..1ed540f 100644 --- a/service/meter04kv.go +++ b/service/meter04kv.go @@ -36,7 +36,7 @@ func (_Meter04kVService) ListMeterDetail(park, keyword string, page int) ([]mode total int64 err error ) - if cachedTotal, _ := cache.RetreiveCount("meter_04kv", condition...); cachedTotal != -1 { + if cachedTotal, err := cache.RetreiveCount("meter_04kv", condition...); cachedTotal != -1 && err == nil { total = cachedTotal } else { total, err = global.DBConn.Where(cond).NoAutoCondition().Count(new(model.Meter04KV)) diff --git a/service/park.go b/service/park.go index 15b1d99..cbfeefc 100644 --- a/service/park.go +++ b/service/park.go @@ -73,19 +73,29 @@ func (_ParkService) DeletePark(uid, pid string) error { return nil } -func (_ParkService) ListAllParkBelongsTo(uid string) ([]model.Park, error) { - if parks, _ := cache.RetreiveSearch[[]model.Park]("park", "belong", uid); parks != nil { +func (_ParkService) ListAllParkBelongsTo(uid, keyword string) ([]model.Park, error) { + if parks, _ := cache.RetreiveSearch[[]model.Park]("park", "belong", uid, keyword); parks != nil { return *parks, nil } + cond := builder.NewCond().And(builder.Eq{"user_id": uid}) + if len(keyword) > 0 { + cond = cond.And( + builder.Like{"name", keyword}. + Or(builder.Like{"abbr", keyword}). + Or(builder.Like{"address", keyword}). + Or(builder.Like{"contact", keyword}). + Or(builder.Like{"phone", keyword}), + ) + } parks := make([]model.Park, 0) err := global.DBConn. - Where(builder.Eq{"user_id": uid}). + Where(cond). NoAutoCondition(). Find(&parks) if err != nil { return make([]model.Park, 0), err } - cache.CacheSearch(parks, []string{"park"}, "park", "belong", uid) + cache.CacheSearch(parks, []string{"park"}, "park", "belong", uid, keyword) return parks, nil } diff --git a/service/report.go b/service/report.go index b5fbc6d..08726a4 100644 --- a/service/report.go +++ b/service/report.go @@ -299,7 +299,7 @@ func (_ReportService) CalculateSummaryAndFinishStep(reportId string) error { } defer tx.Close() summary.CalculatePrices() - _, err = tx.ID(summary.ReportId).Cols("overall_price", "critical_price", "peak_price", "flat", "flat_fee", "flat_price", "valley_price").Update(summary) + _, err = tx.ID(summary.ReportId).Cols("overall_price", "critical_price", "peak_price", "flat", "flat_fee", "flat_price", "valley_price", "consumption_fee").Update(summary) if err != nil { tx.Rollback() return err @@ -461,7 +461,7 @@ func (_ReportService) SearchReport(requestUser, requestPark, requestKeyword stri total int64 err error ) - if cachedTotal, _ := cache.RetreiveCount("join_report_for_withdraw", conditions...); cachedTotal != -1 { + if cachedTotal, err := cache.RetreiveCount("join_report_for_withdraw", conditions...); cachedTotal != -1 && err == nil { total = cachedTotal } else { total, err := global.DBConn. diff --git a/service/statistics.go b/service/statistics.go index 368eba1..0b9a528 100644 --- a/service/statistics.go +++ b/service/statistics.go @@ -14,7 +14,7 @@ type _StatisticsService struct{} var StatisticsService _StatisticsService func (_StatisticsService) EnabledEnterprises() (int64, error) { - if cachedCount, _ := cache.RetreiveCount("enabled_ent"); cachedCount != -1 { + if cachedCount, err := cache.RetreiveCount("enabled_ent"); cachedCount != -1 && err == nil { return cachedCount, nil } c, err := global.DBConn. @@ -28,7 +28,7 @@ func (_StatisticsService) EnabledEnterprises() (int64, error) { } func (_StatisticsService) EnabledParks(userIds ...string) (int64, error) { - if cachedParks, _ := cache.RetreiveCount("enabled_parks", userIds...); cachedParks != -1 { + if cachedParks, err := cache.RetreiveCount("enabled_parks", userIds...); cachedParks != -1 && err == nil { return cachedParks, nil } cond := builder.NewCond().And(builder.Eq{"enabled": true}) diff --git a/service/user.go b/service/user.go index 83cc44c..3e4dc93 100644 --- a/service/user.go +++ b/service/user.go @@ -342,7 +342,7 @@ func (_UserService) ListUserDetail(keyword string, userType int, userState *bool total int64 err error ) - if cacheCounts, _ := cache.RetreiveCount("join_user_detail", cacheConditions...); cacheCounts != -1 { + if cacheCounts, err := cache.RetreiveCount("join_user_detail", cacheConditions...); cacheCounts != -1 && err == nil { total = cacheCounts } else { total, err = global.DBConn. diff --git a/service/withdraw.go b/service/withdraw.go index 39d68f3..84f4ac1 100644 --- a/service/withdraw.go +++ b/service/withdraw.go @@ -85,7 +85,7 @@ func (_WithdrawService) FetchPagedWithdrawApplies(page int, keyword string) ([]m total int64 err error ) - if cachedTotal, _ := cache.RetreiveCount("join_report_for_withdraw", conditions...); cachedTotal != -1 { + if cachedTotal, err := cache.RetreiveCount("join_report_for_withdraw", conditions...); cachedTotal != -1 && err == nil { total = cachedTotal } else { total, err = global.DBConn. @@ -134,7 +134,7 @@ func (_WithdrawService) AuditWithdraw(reportId string, granted bool) error { } func (_WithdrawService) AuditWaits() (int64, error) { - if cachedWaits, _ := cache.RetreiveCount("withdraw_waits"); cachedWaits != -1 { + if cachedWaits, err := cache.RetreiveCount("withdraw_waits"); cachedWaits != -1 && err == nil { return cachedWaits, nil } cond := builder.NewCond()