Реализация кэширования

This commit is contained in:
Ymnuk 2023-11-16 13:44:53 +03:00
parent d39b98bc0f
commit 2243da0360
8 changed files with 191 additions and 14 deletions

View File

@ -165,6 +165,11 @@ func generateModelBase() {
}
}
// Создаем файл с функциями кэширования пользователей из JWT для их работы
if err := templ.PrepareTmplFile("tmpl/backend/db/user-cache.tmpl", Project, filepath.Join(AppConfig.OutdirBackend, "db", "user-cache.go")); err != nil {
log.Fatal(err)
}
// middleware
if err := templ.PrepareTmplFile("tmpl/backend/middleware/middleware.tmpl", Project, filepath.Join(AppConfig.OutdirBackend, "middleware", "middleware.go")); err != nil {
log.Fatal(err)

View File

@ -16,9 +16,12 @@ import (
"gorm.io/gorm"
{{ if .DB.SQLite }}"gorm.io/driver/sqlite"{{ end }}
"gorm.io/gorm/logger"
"github.com/redis/go-redis/v9"
)
var DB *gorm.DB
var RedisDB *redis.Client
var UsersCache *UserCache
func DBConnect() {
var err error
@ -111,6 +114,15 @@ func DBConnect() {
}
if lib.AppConfig.RedisAddr != "" {
RedisDB = redis.NewClient(&redis.Options{
Addr: lib.AppConfig.RedisAddr,
Password: lib.AppConfig.RedisPwd,
DB: lib.AppConfig.RedisDB,
Protocol: lib.AppConfig.RedisProtocol,
})
}
UsersCache = NewUserCache(time.Second*time.Duration(lib.AppConfig.CacheTTL), RedisDB)
}
// Получить роль по имени

View File

@ -105,8 +105,16 @@ func (user *User) BeforeCreate(scope *gorm.DB) (err error) {
}
func (user *User) GetUserByID(id uuid.UUID, scope *gorm.DB) (usr User, err error) {
if res := scope.Preload("Role").First(&usr, id); res.RowsAffected == 0 {
if res := scope.Preload("UserRole.Role").First(&usr, id); res.RowsAffected == 0 {
err = res.Error
}
if len(usr.UserRole) > 0 {
usr.Roles = &[]Role{}
var roles []Role
for i := range usr.UserRole {
roles = append(roles, usr.UserRole[i].Role)
}
usr.Roles = &roles
}
return
}

View File

@ -0,0 +1,112 @@
package db
import (
"context"
"encoding/json"
"sync"
"time"
"{{ .Name }}/db/model"
"github.com/redis/go-redis/v9"
uuid "github.com/satori/go.uuid"
"gorm.io/gorm"
)
type uCache struct {
User *model.User
DT time.Time
}
type UserCache struct {
cache map[uuid.UUID]uCache
cacheMutex sync.Mutex
ttl time.Duration
redisClient *redis.Client
ctx context.Context
}
func NewUserCache(ttl time.Duration, redisClient *redis.Client) *UserCache {
uc := &UserCache{
ttl: ttl,
redisClient: redisClient,
ctx: context.Background(),
}
if uc.redisClient == nil {
uc.cache = make(map[uuid.UUID]uCache)
go uc.cleaner()
}
return uc
}
func (uc *UserCache) cleaner() {
for {
func() {
now := time.Now()
uc.cacheMutex.Lock()
defer uc.cacheMutex.Unlock()
for k, v := range uc.cache {
if v.DT.Add(uc.ttl).Before(now) {
delete(uc.cache, k)
}
}
}()
time.Sleep(time.Second * 10)
}
}
func (uc *UserCache) Get(id uuid.UUID, tx *gorm.DB) (user *model.User, err error) {
if uc.redisClient == nil {
uc.cacheMutex.Lock()
defer uc.cacheMutex.Unlock()
if val, ok := uc.cache[id]; ok {
return val.User, nil
}
} else {
var tmp string
if tmp, err = uc.redisClient.Get(uc.ctx, id.String()).Result(); err == nil {
var u model.User
if err = json.Unmarshal([]byte(tmp), &u); err != nil {
return
}
user = &u
return
}
}
if tx == nil {
tx = BeginTransation()
defer func() {
EndTransaction(tx, err)
}()
}
var u model.User
u, err = u.GetUserByID(id, tx)
user = &u
if err != nil {
return
}
if uc.redisClient != nil {
var tmp []byte
if tmp, err = json.Marshal(u); err != nil {
return
}
uc.redisClient.Set(uc.ctx, user.ID.String(), string(tmp), uc.ttl)
} else {
uc.cache[user.ID] = uCache{
User: user,
DT: time.Now(),
}
}
return
}
func (uc *UserCache) Unset(id uuid.UUID) {
if uc.redisClient == nil {
uc.cacheMutex.Lock()
defer uc.cacheMutex.Unlock()
if _, ok := uc.cache[id]; ok {
delete(uc.cache, id)
}
} else {
uc.redisClient.Del(uc.ctx, id.String())
}
}

View File

@ -4,6 +4,7 @@ gorm.io/driver/mysql
gorm.io/driver/postgres
gorm.io/gorm/logger
gorm.io/datatypes
github.com/redis/go-redis/v9
github.com/golang-jwt/jwt
github.com/alexflint/go-arg
github.com/labstack/echo/v4

View File

@ -16,6 +16,13 @@ var AppConfig struct {
Host string `arg:"--host,env:HOST" default:"example.com" help:"Host name for display and other processes"`
Port string `arg:"--port,env:PORT" default:"3000" help:"Open port for incoming connections"`
RedisAddr string `arg:"--redis-addr,env:REDIS_ADDR" help:"Address for Redis server for caching data. If not set - not use"`
RedisPwd string `arg:"--redis-password,env:REDIS_PASSWORD" help:"Password for connect to Redis"`
RedisDB int `arg:"--redis-db,env:REDIS_DB" default:"0" help:"Number DB in Redis"`
RedisProtocol int `arg:"--redis-protocol,env:REDIS_PROTOCOL" default:"3" help:"Specify the version of the RESP protocol. Default: 3"`
CacheTTL int `arg:"--cache-ttl,env:CACHE_TTL" default:"604800" help:"Cache time to live, after this itemcan be remove from cache"`
LdapURL string `arg:"--ldap-url,env:LDAP_URL" help:"Ldap url for server"`
LdapBind string `arg:"--ldap-bind,env:LDAP_BIND" help:"Ldap bind for credential"`
LdapPassword string `arg:"--ldap-pwd,env:LDAP_PWD" help:"Ldap password for credential"`

View File

@ -9,13 +9,13 @@ import (
func InRole(idUser uuid.UUID, roleName string) bool {
var err error
tx := db.BeginTransation()
/*tx := db.BeginTransation()
defer func() {
db.EndTransaction(tx, err)
}()
}()*/
var userRoles []model.UserRole
/*var userRoles []model.UserRole
if res := tx.Joins("Role").Find(&userRoles, "id_user = ?", idUser); res.RowsAffected == 0 {
return false
}
@ -26,6 +26,17 @@ func InRole(idUser uuid.UUID, roleName string) bool {
return true
}
}
}*/
var user *model.User
if user, err = db.UsersCache.Get(idUser, nil); err == nil && user != nil {
if len(*user.Roles) > 0 {
for _, item := range *user.Roles {
if item.Name == roleName {
return true
}
}
}
}
return false
@ -33,12 +44,12 @@ func InRole(idUser uuid.UUID, roleName string) bool {
func InsRole(idUser uuid.UUID, roleNames []string) bool {
if len(roleNames) == 0{
if len(roleNames) == 0 {
return true
}
var err error
tx := db.BeginTransation()
/*tx := db.BeginTransation()
defer func() {
db.EndTransaction(tx, err)
@ -47,17 +58,29 @@ func InsRole(idUser uuid.UUID, roleNames []string) bool {
var userRoles []model.UserRole
if res := tx.Joins("Role").Find(&userRoles, "id_user = ?", idUser); res.RowsAffected == 0 {
return false
}
}*/
if len(userRoles) > 0 {
/*if len(userRoles) > 0 {
for _, item := range userRoles {
for _,item2:=range roleNames{
for _, item2 := range roleNames {
if item.Role.Name == item2 {
return true
}
}
}
}*/
var user *model.User
if user, err = db.UsersCache.Get(idUser, nil); err == nil && user != nil {
if len(*user.Roles) > 0 {
for _, item := range *user.Roles {
for _, item2 := range roleNames {
if item.Name == item2 {
return true
}
}
}
}
}
return false
}
}

View File

@ -4,6 +4,7 @@ import (
"{{ .Name }}/route/api/user/role"
"{{ .Name }}/middleware"
"{{ .Name }}/structs"
"{{ .Name }}/db"
"net/http"
"github.com/golang-jwt/jwt/v5"
@ -141,7 +142,9 @@ func put(c echo.Context) error {
Message: &[]string{"Отказано в доступе"}[0],
})
}
return restPut(c)
res := restPut(c)
db.UsersCache.Unset(uuid.FromStringOrNil(c.Param("id")))
return res
}
// LockUser lockUser
@ -170,7 +173,9 @@ func lock(c echo.Context) error {
Message: &[]string{"Отказано в доступе"}[0],
})
}
return restLock(c)
res := restLock(c)
db.UsersCache.Unset(uuid.FromStringOrNil(c.Param("id")))
return res
}
// UnlockUser unlockUser
@ -199,7 +204,9 @@ func unlock(c echo.Context) error {
Message: &[]string{"Отказано в доступе"}[0],
})
}
return restUnlock(c)
res := restUnlock(c)
db.UsersCache.Unset(uuid.FromStringOrNil(c.Param("id")))
return res
}
// DeleteUser deleteUser
@ -228,7 +235,9 @@ func delete(c echo.Context) error {
Message: &[]string{"Отказано в доступе"}[0],
})
}
return restDelete(c)
res := restDelete(c)
db.UsersCache.Unset(uuid.FromStringOrNil(c.Param("id")))
return res
}
// LdapSearch ldapSearch