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