Merge branch 'master' into god-mode

This commit is contained in:
徐涛 2022-09-06 16:08:45 +08:00
commit 0ec7f5fba1
15 changed files with 157 additions and 126 deletions

4
cache/abstract.go vendored
View File

@ -3,6 +3,7 @@ package cache
import ( import (
"electricity_bill_calc/global" "electricity_bill_calc/global"
"fmt" "fmt"
"math/rand"
"strings" "strings"
"time" "time"
@ -23,9 +24,10 @@ const (
func Cache[T interface{}](key string, value *T, expires time.Duration) error { func Cache[T interface{}](key string, value *T, expires time.Duration) error {
var err error var err error
if expires > 0 { if expires > 0 {
realExpires := expires + time.Duration(rand.Int63n(60))*time.Second
setCmd := global.RedisConn.B().Set(). setCmd := global.RedisConn.B().Set().
Key(key).Value(rueidis.JSON(value)). Key(key).Value(rueidis.JSON(value)).
ExSeconds(int64(expires.Seconds())). ExSeconds(int64(realExpires.Seconds())).
Build() Build()
err = global.RedisConn.Do(global.Ctx, setCmd).Error() err = global.RedisConn.Do(global.Ctx, setCmd).Error()
} else { } else {

55
cache/count.go vendored
View File

@ -1,49 +1,58 @@
package cache package cache
import ( import (
"electricity_bill_calc/global" "fmt"
"strconv"
"strings" "strings"
"time"
) )
func assembleCountKey(entityName string) string { type _CountRecord struct {
var keys = make([]string, 0) Count int64
keys = append(keys, strings.ToUpper(entityName))
return CacheKey(TAG_COUNT, keys...)
} }
func assembleCountIdentification(additional ...string) string { func assembleCountKey(entityName string, additional ...string) string {
return strings.Join(additional, ":") 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 { func CacheCount(relationNames []string, entityName string, count int64, conditions ...string) error {
countKey := assembleCountKey(entityName) countKey := assembleCountKey(entityName, conditions...)
identification := assembleCountIdentification(conditions...) cacheInstance := &_CountRecord{Count: count}
cmd := global.RedisConn.B().Hset().Key(countKey).FieldValue().FieldValue(identification, strconv.FormatInt(count, 10)).Build() err := Cache(countKey, cacheInstance, 5*time.Minute)
result := global.RedisConn.Do(global.Ctx, cmd)
for _, relationName := range relationNames { 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) { func RetreiveCount(entityName string, condtions ...string) (int64, error) {
countKey := assembleCountKey(entityName) countKey := assembleCountKey(entityName, condtions...)
identification := assembleCountIdentification(condtions...) exist, err := Exists(countKey)
cmd := global.RedisConn.B().Hget().Key(countKey).Field(identification).Build()
result, err := global.RedisConn.Do(global.Ctx, cmd).AsInt64()
if err != nil { if err != nil {
return -1, err 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 { func AbolishCountEntity(entityName string) error {
countKey := assembleCountKey(entityName) pattern := fmt.Sprintf("%s:%s:*", TAG_COUNT, strings.ToUpper(entityName))
cmd := global.RedisConn.B().Del().Key(countKey).Build() return DeleteAll(pattern)
err := global.RedisConn.Do(global.Ctx, cmd).Error()
return err
} }

63
cache/exists.go vendored
View File

@ -1,72 +1,49 @@
package cache package cache
import ( import (
"electricity_bill_calc/global"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/samber/lo"
) )
func assembleExistsKey(entityName string) string { func assembleExistsKey(entityName string, additional ...string) string {
var keys = make([]string, 0) var keys = make([]string, 0)
keys = append(keys, strings.ToUpper(entityName)) keys = append(keys, strings.ToUpper(entityName))
return CacheKey(TAG_EXISTS, keys...) keys = append(keys, additional...)
} var b strings.Builder
b.WriteString(TAG_EXISTS)
func assembleExistsIdentification(additional ...string) string { for _, s := range keys {
return strings.Join(additional, ":") fmt.Fprintf(&b, ":%s", s)
}
return b.String()
} }
// 缓存模型名称明确的、包含指定ID以及一些附加条件的记录 // 缓存模型名称明确的、包含指定ID以及一些附加条件的记录
func CacheExists(relationNames []string, entityName string, conditions ...string) error { func CacheExists(relationNames []string, entityName string, conditions ...string) error {
existskey := assembleExistsKey(entityName) existskey := assembleExistsKey(entityName, conditions...)
identification := assembleExistsIdentification(conditions...) err := Cache(existskey, lo.ToPtr(true), 5*time.Minute)
cmd := global.RedisConn.B().Sadd().Key(existskey).Member(identification).Build()
err := global.RedisConn.Do(global.Ctx, cmd).Error()
for _, relationName := range relationNames { for _, relationName := range relationNames {
CacheRelation(relationName, STORE_TYPE_SET, existskey, identification) CacheRelation(relationName, STORE_TYPE_KEY, existskey)
} }
return err return err
} }
// 从缓存中获取模型名称明确、包含指定ID以及一些附加条件的实体是否存在的标记函数在返回false时不保证数据库中相关记录也不存在 // 从缓存中获取模型名称明确、包含指定ID以及一些附加条件的实体是否存在的标记函数在返回false时不保证数据库中相关记录也不存在
func CheckExists(entityName string, condtions ...string) (bool, error) { func CheckExists(entityName string, condtions ...string) (bool, error) {
existsKey := assembleExistsKey(entityName) existsKey := assembleExistsKey(entityName, condtions...)
identification := assembleExistsIdentification(condtions...) return Exists(existsKey)
cmd := global.RedisConn.B().Sismember().Key(existsKey).Member(identification).Build()
result, err := global.RedisConn.Do(global.Ctx, cmd).AsBool()
return result, err
} }
// 从缓存中删除模型名称明确、包含指定ID的全部实体存在标记 // 从缓存中删除模型名称明确、包含指定ID的全部实体存在标记
func AbolishExists(entityName, id string) error { func AbolishExists(entityName, id string) error {
existsKey := assembleExistsKey(entityName) pattern := fmt.Sprintf("%s:%s:%s:*", TAG_EXISTS, strings.ToUpper(entityName), id)
pattern := fmt.Sprintf("%s*", id) return DeleteAll(pattern)
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
} }
// 从缓存中删除指定模型名称的全部存在标记 // 从缓存中删除指定模型名称的全部存在标记
func AbolishExistsEntity(entityName string) error { func AbolishExistsEntity(entityName string) error {
existskey := assembleExistsKey(entityName) pattern := fmt.Sprintf("%s:%s:*", TAG_EXISTS, strings.ToUpper(entityName))
_, err := Delete(existskey) return DeleteAll(pattern)
return err
} }

51
cache/relation.go vendored
View File

@ -2,6 +2,7 @@ package cache
import ( import (
"electricity_bill_calc/global" "electricity_bill_calc/global"
"fmt"
"strings" "strings"
"github.com/rueian/rueidis" "github.com/rueian/rueidis"
@ -69,3 +70,53 @@ func AbolishRelation(relationName string) error {
return nil 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
}
}

33
cache/repository.go vendored
View File

@ -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)
}
}

View File

@ -49,7 +49,8 @@ func listAllParksUnderSessionUser(c *gin.Context) {
result.Unauthorized(err.Error()) result.Unauthorized(err.Error())
return return
} }
parks, err := service.ParkService.ListAllParkBelongsTo(userSession.Uid) keyword := c.DefaultQuery("keyword", "")
parks, err := service.ParkService.ListAllParkBelongsTo(userSession.Uid, keyword)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) result.Error(http.StatusInternalServerError, err.Error())
return return
@ -60,7 +61,8 @@ func listAllParksUnderSessionUser(c *gin.Context) {
func listAllParksUnderSpecificUser(c *gin.Context) { func listAllParksUnderSpecificUser(c *gin.Context) {
result := response.NewResult(c) result := response.NewResult(c)
requestUserId := c.Param("uid") requestUserId := c.Param("uid")
parks, err := service.ParkService.ListAllParkBelongsTo(requestUserId) keyword := c.DefaultQuery("keyword", "")
parks, err := service.ParkService.ListAllParkBelongsTo(requestUserId, keyword)
if err != nil { if err != nil {
result.Error(http.StatusInternalServerError, err.Error()) result.Error(http.StatusInternalServerError, err.Error())
return return
@ -69,16 +71,16 @@ func listAllParksUnderSpecificUser(c *gin.Context) {
} }
type _ParkInfoFormData struct { type _ParkInfoFormData struct {
Name string `json:"name" form:"name"` Name string `json:"name" form:"name"`
Region *string `json:"region" form:"region"` Region *string `json:"region" form:"region"`
Address *string `json:"address" form:"address"` Address *string `json:"address" form:"address"`
Contact *string `json:"contact" form:"contact"` Contact *string `json:"contact" form:"contact"`
Phone *string `json:"phone" from:"phone"` Phone *string `json:"phone" from:"phone"`
Area decimal.NullDecimal `json:"area" from:"area"` Area decimal.NullDecimal `json:"area" from:"area"`
Capacity decimal.NullDecimal `json:"capacity" from:"capacity"` Capacity decimal.NullDecimal `json:"capacity" from:"capacity"`
Tenement decimal.NullDecimal `json:"tenement" from:"tenement"` TenementQuantity decimal.NullDecimal `json:"tenement" from:"tenement"`
Category int `json:"category" form:"category"` Category int `json:"category" form:"category"`
Submeter int `json:"submeter" form:"submeter"` Submeter int `json:"submeter" form:"submeter"`
} }
func createNewPark(c *gin.Context) { func createNewPark(c *gin.Context) {

13
main.go
View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"electricity_bill_calc/cache"
"electricity_bill_calc/config" "electricity_bill_calc/config"
"electricity_bill_calc/global" "electricity_bill_calc/global"
"electricity_bill_calc/model" "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() { func main() {
go DBConnectionKeepLive() go DBConnectionKeepLive()
go RedisOrphanCleanup()
gin.SetMode(config.ServerSettings.RunMode) gin.SetMode(config.ServerSettings.RunMode)
r := router.Router() r := router.Router()
r.Run(fmt.Sprintf(":%d", config.ServerSettings.HttpPort)) r.Run(fmt.Sprintf(":%d", config.ServerSettings.HttpPort))

View File

@ -205,7 +205,7 @@ func (_ChargeService) ListPagedChargeRecord(keyword, beginDate, endDate string,
total int64 total int64
err error 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 total = cachedTotal
} else { } else {
total, err = global.DBConn. total, err = global.DBConn.

View File

@ -45,7 +45,7 @@ func (_EndUserService) SearchEndUserRecord(reportId, keyword string, page int) (
total int64 total int64
err error 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 total = cachedTotal
} else { } else {
total, err = global.DBConn. total, err = global.DBConn.

View File

@ -36,7 +36,7 @@ func (_Meter04kVService) ListMeterDetail(park, keyword string, page int) ([]mode
total int64 total int64
err error 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 total = cachedTotal
} else { } else {
total, err = global.DBConn.Where(cond).NoAutoCondition().Count(new(model.Meter04KV)) total, err = global.DBConn.Where(cond).NoAutoCondition().Count(new(model.Meter04KV))

View File

@ -73,19 +73,29 @@ func (_ParkService) DeletePark(uid, pid string) error {
return nil return nil
} }
func (_ParkService) ListAllParkBelongsTo(uid string) ([]model.Park, error) { func (_ParkService) ListAllParkBelongsTo(uid, keyword string) ([]model.Park, error) {
if parks, _ := cache.RetreiveSearch[[]model.Park]("park", "belong", uid); parks != nil { if parks, _ := cache.RetreiveSearch[[]model.Park]("park", "belong", uid, keyword); parks != nil {
return *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) parks := make([]model.Park, 0)
err := global.DBConn. err := global.DBConn.
Where(builder.Eq{"user_id": uid}). Where(cond).
NoAutoCondition(). NoAutoCondition().
Find(&parks) Find(&parks)
if err != nil { if err != nil {
return make([]model.Park, 0), err 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 return parks, nil
} }

View File

@ -299,7 +299,7 @@ func (_ReportService) CalculateSummaryAndFinishStep(reportId string) error {
} }
defer tx.Close() defer tx.Close()
summary.CalculatePrices() 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 { if err != nil {
tx.Rollback() tx.Rollback()
return err return err
@ -461,7 +461,7 @@ func (_ReportService) SearchReport(requestUser, requestPark, requestKeyword stri
total int64 total int64
err error 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 total = cachedTotal
} else { } else {
total, err := global.DBConn. total, err := global.DBConn.

View File

@ -14,7 +14,7 @@ type _StatisticsService struct{}
var StatisticsService _StatisticsService var StatisticsService _StatisticsService
func (_StatisticsService) EnabledEnterprises() (int64, error) { 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 return cachedCount, nil
} }
c, err := global.DBConn. c, err := global.DBConn.
@ -28,7 +28,7 @@ func (_StatisticsService) EnabledEnterprises() (int64, error) {
} }
func (_StatisticsService) EnabledParks(userIds ...string) (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 return cachedParks, nil
} }
cond := builder.NewCond().And(builder.Eq{"enabled": true}) cond := builder.NewCond().And(builder.Eq{"enabled": true})

View File

@ -342,7 +342,7 @@ func (_UserService) ListUserDetail(keyword string, userType int, userState *bool
total int64 total int64
err error 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 total = cacheCounts
} else { } else {
total, err = global.DBConn. total, err = global.DBConn.

View File

@ -85,7 +85,7 @@ func (_WithdrawService) FetchPagedWithdrawApplies(page int, keyword string) ([]m
total int64 total int64
err error 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 total = cachedTotal
} else { } else {
total, err = global.DBConn. total, err = global.DBConn.
@ -134,7 +134,7 @@ func (_WithdrawService) AuditWithdraw(reportId string, granted bool) error {
} }
func (_WithdrawService) AuditWaits() (int64, 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 return cachedWaits, nil
} }
cond := builder.NewCond() cond := builder.NewCond()