支持 load 加载的服务热更新(需配置hotLoad) 增强唯一id获取(使用新算法,依赖Redis) session支持基于细粒度权限匹配(传入支持的功能列表匹配)
209 lines
5.1 KiB
Go
209 lines
5.1 KiB
Go
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()
|
|
}
|