service/session.go

209 lines
5.1 KiB
Go
Raw Normal View History

package service
import (
"reflect"
"strings"
"sync"
"time"
"apigo.cc/gojs"
"apigo.cc/gojs/goja"
"github.com/ssgo/log"
"github.com/ssgo/redis"
"github.com/ssgo/u"
)
type Session struct {
id string
conn *redis.Redis
data map[string]any
funcAuthCache map[string]bool
}
var sessionRedis *redis.Redis
var sessionTimeout = int64(3600)
var memorySessionData = map[string]map[string]any{}
var memorySessionDataLock = sync.RWMutex{}
var lastSessionClearTime int64
func NewSession(id string, logger *log.Logger) *Session {
data := map[string]any{}
conn := sessionRedis
if sessionRedis == nil {
memorySessionDataLock.RLock()
if data1, ok1 := memorySessionData[id]; ok1 && data1 != nil {
data = data1
}
memorySessionDataLock.RUnlock()
} else {
conn = sessionRedis.CopyByLogger(logger)
err := conn.GET("SESS_" + id).To(&data)
if err == nil {
conn.EXPIRE("SESS_"+id, int(sessionTimeout))
}
}
return &Session{
id: id,
conn: conn,
data: data,
funcAuthCache: map[string]bool{},
}
}
func (session *Session) Set(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
if args.Arguments[0].ExportType().Kind() == reflect.Map {
for key, value := range args.Map(0) {
session.data[key] = value
}
} else {
args.Check(2)
session.data[args.Str(0)] = args.Any(1)
}
return nil
}
func (session *Session) Get(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
if len(args.Arguments) == 1 {
if args.Arguments[0].ExportType().Kind() == reflect.Slice {
out := make(map[string]any)
for _, key := range args.Arr(0).StrArray(0) {
out[key] = session.data[key]
}
return vm.ToValue(out)
} else {
return vm.ToValue(session.data[args.Str(0)])
}
} else {
out := make(map[string]any)
for _, key := range args.StrArray(0) {
out[key] = session.data[key]
}
return vm.ToValue(out)
}
}
func (session *Session) Remove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
if args.Arguments[0].ExportType().Kind() == reflect.Slice {
out := make(map[string]any)
for _, key := range args.Arr(0).StrArray(0) {
delete(session.data, key)
}
return vm.ToValue(out)
} else {
for _, key := range args.StrArray(0) {
delete(session.data, key)
}
}
return nil
}
func (session *Session) SetAuthLevel(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
session.data["_authLevel"] = args.Int(0)
return nil
}
func (session *Session) authFuncs(needFuncs []string) bool {
cacheKey := strings.Join(needFuncs, "; ")
if cachedResult, ok := session.funcAuthCache[cacheKey]; ok {
return cachedResult
}
normalAuthOk := 0
requiredAuthTotal := 0
requiredAuthOk := 0
isOk := false
if userFuncs, ok := session.data["funcs"].([]string); ok && len(userFuncs) > 0 {
if u.StringIn(userFuncs, "system.superAdmin.") {
isOk = true
} else {
// 统计必须有几个权限
for _, needFunc := range needFuncs {
if needFunc[0] == '&' {
requiredAuthTotal++
}
}
// 检查是否有匹配的权限(左匹配)
for _, needFunc := range needFuncs {
isRequired := false
if needFunc[0] == '&' {
// 必须有此权限
isRequired = true
needFunc = needFunc[1:]
}
for _, userFunc := range userFuncs {
if strings.HasPrefix(userFunc, needFunc) {
// 匹配成功
if isRequired {
// 必须有此权限并且匹配成功
requiredAuthOk++
} else {
// 普通权限匹配成功
normalAuthOk++
}
break
}
}
if normalAuthOk > 0 && requiredAuthOk == requiredAuthTotal {
// 普通权限匹配成功,并且必须有权限也匹配成功,直接返回
isOk = true
break
}
}
}
}
session.funcAuthCache[cacheKey] = isOk
return isOk
}
func (session *Session) AuthFuncs(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
var needFuncs []string
if args.Arguments[0].ExportType().Kind() == reflect.Slice {
// 第一个参数是数组
needFuncs = args.Arr(0).StrArray(0)
} else {
// 平铺的参数
needFuncs = args.StrArray(0)
}
return vm.ToValue(session.authFuncs(needFuncs))
}
func (session *Session) Save(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
if session.conn == nil {
now := time.Now().Unix()
session.data["_time"] = now
memorySessionDataLock.Lock()
memorySessionData[session.id] = session.data
clearTimeDiff := now - lastSessionClearTime
if clearTimeDiff > 60 {
lastSessionClearTime = now
}
memorySessionDataLock.Unlock()
if clearTimeDiff > 60 {
go clearMemorySession()
}
} else {
session.conn.SETEX("SESS_"+session.id, int(sessionTimeout), session.data)
}
return nil
}
func clearMemorySession() {
memorySessionDataLock.Lock()
now := time.Now().Unix()
for id, data := range memorySessionData {
if data["_time"] != nil {
if now-data["_time"].(int64) > sessionTimeout {
delete(memorySessionData, id)
}
}
}
memorySessionDataLock.Unlock()
}