使用 gojs/task 代替内置任务

支持 load 加载的服务热更新(需配置hotLoad)
增强唯一id获取(使用新算法,依赖Redis)
session支持基于细粒度权限匹配(传入支持的功能列表匹配)
This commit is contained in:
Star 2025-07-24 21:44:41 +08:00
parent afcce4d649
commit 715de5e442
13 changed files with 580 additions and 347 deletions

52
go.mod
View File

@ -1,44 +1,54 @@
module apigo.cc/gojs/service module apigo.cc/gojs/service
go 1.18 go 1.23.0
require ( require (
apigo.cc/gojs v0.0.12 apigo.cc/gojs v0.0.23
apigo.cc/gojs/console v0.0.2 apigo.cc/gojs/console v0.0.2
apigo.cc/gojs/http v0.0.3 apigo.cc/gojs/file v0.0.4
apigo.cc/gojs/util v0.0.8 apigo.cc/gojs/http v0.0.7
apigo.cc/gojs/runtime v0.0.3
apigo.cc/gojs/task v0.0.4
apigo.cc/gojs/util v0.0.13
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/ssgo/config v1.7.9 github.com/ssgo/config v1.7.9
github.com/ssgo/discover v1.7.9 github.com/ssgo/discover v1.7.10
github.com/ssgo/httpclient v1.7.8 github.com/ssgo/httpclient v1.7.8
github.com/ssgo/log v1.7.7 github.com/ssgo/log v1.7.9
github.com/ssgo/redis v1.7.7 github.com/ssgo/redis v1.7.8
github.com/ssgo/s v1.7.22 github.com/ssgo/s v1.7.24
github.com/ssgo/standard v1.7.7 github.com/ssgo/standard v1.7.7
github.com/ssgo/u v1.7.13 github.com/ssgo/u v1.7.21
) )
require ( require (
github.com/ZZMarquis/gm v1.3.2 // indirect github.com/ZZMarquis/gm v1.3.2 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/emmansun/gmsm v0.29.6 // indirect github.com/emmansun/gmsm v0.30.1 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-rod/rod v0.116.2 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/gomodule/redigo v1.9.2 // indirect github.com/gomodule/redigo v1.9.2 // indirect
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
github.com/obscuren/ecies v0.0.0-20150213224233-7c0f4a9b18d9 // indirect github.com/obscuren/ecies v0.0.0-20150213224233-7c0f4a9b18d9 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/ssgo/tool v0.4.28 // indirect github.com/ssgo/tool v0.4.29 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect github.com/tklauser/numcpus v0.10.0 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.9.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.40.0 // indirect
golang.org/x/net v0.32.0 // indirect golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.27.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@ -37,6 +37,8 @@ var serviceMD string
var server *s.AsyncServer var server *s.AsyncServer
var pools = map[string]*gojs.Pool{} var pools = map[string]*gojs.Pool{}
var poolsMTime = map[string]int64{}
var poolsConfig = map[string]gojs.PoolConfig{}
var poolExists = map[string]bool{} var poolExists = map[string]bool{}
var poolActionRegistered = map[string]bool{} var poolActionRegistered = map[string]bool{}
@ -71,6 +73,7 @@ type Config struct {
LimitedMessage string LimitedMessage string
Limiters map[string]*LimiterConfig Limiters map[string]*LimiterConfig
LimiterRedis string LimiterRedis string
HotLoad int
Proxy map[string]string Proxy map[string]string
Rewrite map[string]string Rewrite map[string]string
@ -90,8 +93,8 @@ func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
s.SetWorkPath(u.String(startPath)) s.SetWorkPath(u.String(startPath))
} }
// 处理配置 // 处理配置
serviceConfig = Config{"Session", "Device", "Client", "id", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", map[string]string{}, map[string]string{}, map[string]string{}} serviceConfig = Config{"Session", "Device", "Client", "id", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", 0, map[string]string{}, map[string]string{}, map[string]string{}}
if errs := config.LoadConfig("service", &serviceConfig); errs != nil && len(errs) > 0 { if errs := config.LoadConfig("service", &serviceConfig); len(errs) > 0 {
panic(vm.NewGoError(errs[0])) panic(vm.NewGoError(errs[0]))
} }
config.LoadConfig("service", &discover.Config) config.LoadConfig("service", &discover.Config)
@ -139,20 +142,33 @@ func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
if authAccessToken && setAuthLevel < authLevel { if authAccessToken && setAuthLevel < authLevel {
setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token")) setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token"))
} }
authOk := false
if setAuthLevel >= authLevel { if setAuthLevel >= authLevel {
authOk = true // 默认通过
if options != nil && options.Ext != nil && options.Ext["funcs"] != nil {
if needFuncs, ok := options.Ext["funcs"].([]string); ok && len(needFuncs) > 0 {
// 指定了细化的权限验证要求
request.Set("funcs", needFuncs)
authOk = session.authFuncs(needFuncs)
}
}
}
if authOk {
return true, session return true, session
}
msg := serviceConfig.AuthFieldMessage
if strings.Contains(msg, "{{") {
msg = strings.ReplaceAll(msg, "{{TARGET_AUTHLEVEL}}", u.String(authLevel))
msg = strings.ReplaceAll(msg, "{{USER_AUTHLEVEL}}", u.String(setAuthLevel))
}
var obj any
if json.Unmarshal([]byte(msg), &obj) == nil {
return false, obj
} else { } else {
msg := serviceConfig.AuthFieldMessage return false, msg
if strings.Contains(msg, "{{") {
msg = strings.ReplaceAll(msg, "{{TARGET_AUTHLEVEL}}", u.String(authLevel))
msg = strings.ReplaceAll(msg, "{{USER_AUTHLEVEL}}", u.String(setAuthLevel))
}
var obj any
if json.Unmarshal([]byte(msg), &obj) == nil {
return false, obj
} else {
return false, msg
}
} }
}) })
s.Init() s.Init()
@ -180,6 +196,44 @@ func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
} }
} }
} }
if serviceConfig.HotLoad > 0 {
s.NewTimerServer("hotload", time.Duration(serviceConfig.HotLoad)*time.Second, func(b *bool) {
tmpPoolsMTimes := map[string]int64{}
poolsLock.RLock()
for actionFile, mtime := range poolsMTime {
tmpPoolsMTimes[actionFile] = mtime
}
poolsLock.RUnlock()
for actionFile, mtime := range tmpPoolsMTimes {
fi := u.GetFileInfo(actionFile)
if fi == nil {
continue
}
newMtime := fi.ModTime.Unix()
if newMtime > mtime {
// 重新加载文件
actionCode := u.ReadFileN(actionFile)
if !strings.Contains(actionCode, "function main(") { // || !strings.Contains(actionCode, ".register(")
logger.Error("hotload file %s failed, must be a js file with main function", actionFile)
continue
}
poolsLock.RLock()
oldOpt := poolsConfig[actionFile]
poolsLock.RUnlock()
p := gojs.NewPoolByCode(actionCode, actionFile, oldOpt, logger)
poolsLock.Lock()
pools[actionFile] = p
poolsMTime[actionFile] = newMtime
poolsLock.Unlock()
logger.Info("hotload file %s success", actionFile, "mtime", fi.ModTime)
}
}
}, nil, nil)
}
} }
func init() { func init() {
@ -312,72 +366,113 @@ func init() {
s.ResetAllSets() s.ResetAllSets()
return nil return nil
}, },
"uniqueId": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // "uniqueId": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm) // args := gojs.MakeArgs(&argsIn, vm)
size := args.Int(0) // size := args.Int(0)
var id string // var id string
if size >= 20 { // if size >= 20 {
id = s.UniqueId20() // id = s.UniqueId20()
} else if size >= 16 { // } else if size >= 16 {
id = s.UniqueId16() // id = s.UniqueId16()
} else if size >= 14 { // } else if size >= 14 {
id = s.UniqueId14() // id = s.UniqueId14()
} else if size >= 12 { // } else if size >= 12 {
id = s.UniqueId14()[0:12] // id = s.UniqueId14()[0:12]
} else { // } else {
id = s.UniqueId() // id = s.UniqueId()
} // }
return vm.ToValue(id) // return vm.ToValue(id)
}, // },
"uniqueIdL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // "uniqueIdL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm) // args := gojs.MakeArgs(&argsIn, vm)
size := args.Int(0) // size := args.Int(0)
var id string // var id string
if size >= 20 { // if size >= 20 {
id = s.UniqueId20() // id = s.UniqueId20()
} else if size >= 16 { // } else if size >= 16 {
id = s.UniqueId16() // id = s.UniqueId16()
} else if size >= 14 { // } else if size >= 14 {
id = s.UniqueId14() // id = s.UniqueId14()
} else if size >= 12 { // } else if size >= 12 {
id = s.UniqueId14()[0:12] // id = s.UniqueId14()[0:12]
} else { // } else {
id = s.UniqueId() // id = s.UniqueId()
} // }
return vm.ToValue(strings.ToLower(id)) // return vm.ToValue(strings.ToLower(id))
}, // },
"id": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "id": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
space := args.Str(0) size := args.Int(0)
size := args.Int(1) if size == 0 {
var id string size = 12
if size >= 12 {
id = s.Id12(space)
} else if size >= 10 {
id = s.Id10(space)
} else if size >= 8 {
id = s.Id8(space)
} else {
id = s.Id6(space)
} }
return vm.ToValue(id) if size > 20 {
}, size = 20
"idL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
space := args.Str(0)
size := args.Int(1)
var id string
if size >= 12 {
id = s.Id12(space)
} else if size >= 10 {
id = s.Id10(space)
} else if size >= 8 {
id = s.Id8(space)
} else {
id = s.Id6(space)
} }
return vm.ToValue(strings.ToLower(id))
rd := sessionRedis
if rd == nil {
// 使用u.Id
return vm.ToValue(u.UniqueId()[0:size])
}
tm := time.Now()
// 计算从2000年开始到现在的索引值2000年时间戳9466560005位62进制最小值14776336可以表示901356495个即142.9年至2142年
secIndex := (tm.Unix()-946656000)/5 + 14776336
secTag := int(tm.UnixMicro() % 5)
uid := u.AppendInt(nil, uint64(secIndex))
secIndexKey := fmt.Sprintf("_SecIdx_%d", secIndex)
inSecIndex := rd.INCR(secIndexKey)
if inSecIndex == 1 {
rd.EXPIRE(secIndexKey, 6)
}
inSecIndexBytes := u.EncodeInt(uint64(inSecIndex))
uid = u.AppendInt(uid, uint64(secTag*12+len(inSecIndexBytes))) // 长度位,防止不同长度+随机值发生碰撞(用毫秒的%4*12位基础+位数可以随机表示12位长度看起来有变化
uid = append(uid, inSecIndexBytes...)
// 用随机数填充
uid = u.FillInt(uid, size)
// 交叉乱序
uid = u.ExchangeInt(uid)
// 散列乱序
uid = u.HashInt(uid)
return vm.ToValue(string(uid))
}, },
// "id": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
// space := args.Str(0)
// size := args.Int(1)
// var id string
// if size >= 12 {
// id = s.Id12(space)
// } else if size >= 10 {
// id = s.Id10(space)
// } else if size >= 8 {
// id = s.Id8(space)
// } else {
// id = s.Id6(space)
// }
// return vm.ToValue(id)
// },
// "idL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
// space := args.Str(0)
// size := args.Int(1)
// var id string
// if size >= 12 {
// id = s.Id12(space)
// } else if size >= 10 {
// id = s.Id10(space)
// } else if size >= 8 {
// id = s.Id8(space)
// } else {
// id = s.Id6(space)
// }
// return vm.ToValue(strings.ToLower(id))
// },
"register": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "register": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(2) args := gojs.MakeArgs(&argsIn, vm).Check(2)
o := args.Obj(0) o := args.Obj(0)
@ -443,7 +538,7 @@ func init() {
NoBody: o.Bool("noBody"), NoBody: o.Bool("noBody"),
NoLog200: o.Bool("noLog200"), NoLog200: o.Bool("noLog200"),
Host: host, Host: host,
//Ext: nil, Ext: o.Map("ext"),
Limiters: usedLimiters, Limiters: usedLimiters,
} }
@ -497,61 +592,66 @@ func init() {
mainArgs = mainArgs1 mainArgs = mainArgs1
} }
} }
if !u.FileExists(actionFile) { fi := u.GetFileInfo(actionFile)
if fi == nil {
fullActionFile, _ := filepath.Abs(actionFile) fullActionFile, _ := filepath.Abs(actionFile)
panic(vm.NewGoError(errors.New("actionFile must be a js file path: " + fullActionFile))) panic(vm.NewGoError(errors.New("actionFile must be a js file path: " + fullActionFile)))
} }
actionCode := u.ReadFileN(actionFile) actionCode := u.ReadFileN(actionFile)
if !strings.Contains(actionCode, "function main(") || !strings.Contains(actionCode, ".register(") { if !strings.Contains(actionCode, "function main(") { // || !strings.Contains(actionCode, ".register(")
panic(vm.NewGoError(errors.New("actionFile must be a js file with main function and call service.register"))) panic(vm.NewGoError(errors.New("actionFile must be a js file with main function")))
} }
poolsLock.Lock() poolsLock.Lock()
poolExists[actionFile] = true poolExists[actionFile] = true
poolsLock.Unlock() poolsLock.Unlock()
p := gojs.NewPoolByCode(actionCode, actionFile, gojs.PoolConfig{ poolOpt := gojs.PoolConfig{
Min: mi, Min: mi,
Max: ma, Max: ma,
Idle: idle, Idle: idle,
Debug: debug, Debug: debug,
Args: mainArgs, Args: mainArgs,
}, args.Logger) }
p := gojs.NewPoolByCode(actionCode, actionFile, poolOpt, args.Logger)
//p := gojs.NewLBByCode(actionCode, actionFile, gojs.LBConfig{ //p := gojs.NewLBByCode(actionCode, actionFile, gojs.LBConfig{
// Num: num, // Num: num,
// Debug: debug, // Debug: debug,
// Args: mainArgs, // Args: mainArgs,
//}, args.Logger) //}, args.Logger)
mtime := fi.ModTime.Unix()
poolsLock.Lock() poolsLock.Lock()
pools[actionFile] = p pools[actionFile] = p
poolsMTime[actionFile] = mtime
poolsConfig[actionFile] = poolOpt
poolsLock.Unlock() poolsLock.Unlock()
return nil return nil
}, },
"task": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // "task": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) // args := gojs.MakeArgs(&argsIn, vm).Check(1)
taskFile := args.Path(0) // taskFile := args.Path(0)
interval := args.Int(1) // interval := args.Int(1)
if interval == 0 { // if interval == 0 {
interval = 1000 // interval = 1000
} // }
if interval < 100 { // if interval < 100 {
interval = 100 // interval = 100
} // }
if !u.FileExists(taskFile) { // if !u.FileExists(taskFile) {
panic(vm.NewGoError(errors.New("taskFile must be a js file path"))) // panic(vm.NewGoError(errors.New("taskFile must be a js file path")))
} // }
rt := gojs.New() // rt := gojs.New()
_, err := rt.RunFile(taskFile) // _, err := rt.RunFile(taskFile)
if err != nil { // if err != nil {
panic(vm.NewGoError(err)) // panic(vm.NewGoError(err))
} // }
s.NewTimerServer(taskFile, time.Duration(interval)*time.Millisecond, func(isRunning *bool) { // s.NewTimerServer(taskFile, time.Duration(interval)*time.Millisecond, func(isRunning *bool) {
rt.RunCode("if(onRun)onRun()") // rt.RunCode("if(onRun)onRun()")
}, func() { // }, func() {
rt.RunCode("if(onStart)onStart()") // rt.RunCode("if(onStart)onStart()")
}, func() { // }, func() {
rt.RunCode("if(onStop)onStop()") // rt.RunCode("if(onStop)onStop()")
}) // })
return nil // return nil
}, // },
"setTplFunc": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "setTplFunc": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
fnObj := args.Obj(0) fnObj := args.Obj(0)
@ -646,17 +746,17 @@ func init() {
} }
return vm.ToValue(buf.String()) return vm.ToValue(buf.String())
}, },
"dataSet": DataSet, // "dataSet": DataSet,
"dataGet": DataGet, // "dataGet": DataGet,
"dataKeys": DataKeys, // "dataKeys": DataKeys,
"dataCount": DataCount, // "dataCount": DataCount,
"dataFetch": DataFetch, // "dataFetch": DataFetch,
"dataRemove": DataRemove, // "dataRemove": DataRemove,
"listPop": ListPop, // "listPop": ListPop,
"listPush": ListPush, // "listPush": ListPush,
"listCount": ListCount, // "listCount": ListCount,
"listRemove": ListRemove, // "listRemove": ListRemove,
"newCaller": NewCaller, "newCaller": NewCaller,
} }
gojs.Register("apigo.cc/gojs/service", gojs.Module{ gojs.Register("apigo.cc/gojs/service", gojs.Module{

View File

@ -6,22 +6,22 @@ export default {
stop, stop,
register, register,
load, load,
task, // task,
newCaller, newCaller,
dataSet, // dataSet,
dataGet, // dataGet,
dataKeys, // dataKeys,
dataCount, // dataCount,
dataFetch, // dataFetch,
dataRemove, // dataRemove,
listPush, // listPush,
listPop, // listPop,
listCount, // listCount,
listRemove, // listRemove,
id, id,
idL, // idL,
uniqueId, // uniqueId,
uniqueIdL, // uniqueIdL,
setTplFunc, setTplFunc,
tpl, tpl,
} }
@ -32,25 +32,26 @@ function stop(): void { }
function register(option: RegisterOption, callback: (params: RequestParams) => void): any { return null } function register(option: RegisterOption, callback: (params: RequestParams) => void): any { return null }
function load(serviceFile: string, poolConfig?: PoolConfig): void { } function load(serviceFile: string, poolConfig?: PoolConfig): void { }
function task(taskFile: string, interval: number = 1000): void { } // function task(taskFile: string, interval: number = 1000): void { }
function newCaller(): Caller { return null as any } function newCaller(): Caller { return null as any }
function dataSet(scope: string, key: string, value: any): void { } // function dataSet(scope: string, key: string, value: any): void { }
function dataGet(scope: string, key: string): any { return null } // function dataGet(scope: string, key: string): any { return null }
function dataKeys(scope: string): string[] { return [] } // function dataKeys(scope: string): string[] { return [] }
function dataCount(scope: string): number { return 0 } // function dataCount(scope: string): number { return 0 }
function dataFetch(scope: string): Map<string, any> { return null as any } // function dataFetch(scope: string): Map<string, any> { return null as any }
function dataRemove(scope: string, key?: string): void { } // function dataRemove(scope: string, key?: string): void { }
function listPush(scope: string, key: string, value: any): void { } // function listPush(scope: string, key: string, value: any): void { }
function listPop(scope: string, key: string): any { return null } // function listPop(scope: string, key: string): any { return null }
function listCount(scope: string): number { return 0 } // function listCount(scope: string): number { return 0 }
function listRemove(scope: string): void { } // function listRemove(scope: string): void { }
function id(space: string, size?: number): string { return '' } function id(size?: number): string { return '' }
function idL(space: string, size?: number): string { return '' } // function id(space: string, size?: number): string { return '' }
function uniqueId(size?: number): string { return '' } // function idL(space: string, size?: number): string { return '' }
function uniqueIdL(size?: number): string { return '' } // function uniqueId(size?: number): string { return '' }
// function uniqueIdL(size?: number): string { return '' }
function setTplFunc(fnList: Object): void { } function setTplFunc(fnList: Object): void { }
function tpl(file: string, data: Object): string { return '' } function tpl(file: string, data: Object): string { return '' }
@ -116,6 +117,7 @@ interface Config {
limitedMessage: string | Object // 访问受限时的消息,默认为 too many requests可以设置对象来返回JSON可以使用模版 {{LIMITED_FROM}}、{{LIMITED_VALUE}} limitedMessage: string | Object // 访问受限时的消息,默认为 too many requests可以设置对象来返回JSON可以使用模版 {{LIMITED_FROM}}、{{LIMITED_VALUE}}
limiterRedis: string // 限流器使用的Redis连接默认使用内存存储 limiterRedis: string // 限流器使用的Redis连接默认使用内存存储
limiters: Map<string, LimiterConfig> // 限流器配置from 为数据来源例如ip、user、device、header.User-Agent、in.phone 等in表示从请求参数中获取time为时间间隔单位mstimes为 时间间隔内允许访问的次数 limiters: Map<string, LimiterConfig> // 限流器配置from 为数据来源例如ip、user、device、header.User-Agent、in.phone 等in表示从请求参数中获取time为时间间隔单位mstimes为 时间间隔内允许访问的次数
hotLoad: number // 热加载配置单位s默认值00表示不检测热加载
// gateway 的配置参数 // gateway 的配置参数
proxy: Map<string, string> // 代理配置key为[host][path]value为代理的目标应用或URL proxy: Map<string, string> // 代理配置key为[host][path]value为代理的目标应用或URL
@ -160,6 +162,7 @@ interface RegisterOption {
limiters: string[] limiters: string[]
verifies: Object verifies: Object
requires: string[] requires: string[]
ext: Object
onMessage: (params: OnMessageParams) => void onMessage: (params: OnMessageParams) => void
onClose: (params: RequestParams) => void onClose: (params: RequestParams) => void
} }

View File

@ -2,6 +2,7 @@ package service
import ( import (
"reflect" "reflect"
"strings"
"sync" "sync"
"time" "time"
@ -9,12 +10,14 @@ import (
"apigo.cc/gojs/goja" "apigo.cc/gojs/goja"
"github.com/ssgo/log" "github.com/ssgo/log"
"github.com/ssgo/redis" "github.com/ssgo/redis"
"github.com/ssgo/u"
) )
type Session struct { type Session struct {
id string id string
conn *redis.Redis conn *redis.Redis
data map[string]any data map[string]any
funcAuthCache map[string]bool
} }
var sessionRedis *redis.Redis var sessionRedis *redis.Redis
@ -40,9 +43,10 @@ func NewSession(id string, logger *log.Logger) *Session {
} }
} }
return &Session{ return &Session{
id: id, id: id,
conn: conn, conn: conn,
data: data, data: data,
funcAuthCache: map[string]bool{},
} }
} }
@ -102,6 +106,73 @@ func (session *Session) SetAuthLevel(argsIn goja.FunctionCall, vm *goja.Runtime)
return nil 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 { func (session *Session) Save(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
if session.conn == nil { if session.conn == nil {
now := time.Now().Unix() now := time.Now().Unix()

299
task.go
View File

@ -1,162 +1,165 @@
package service package service
import ( // type Task struct {
"container/list" // spec string
"sync" // file string
// vm *gojs.Runtime
// lock sync.RWMutex
// mtime time.Time
// // policy string
// }
// var tasks []Task
// var tasksLock = sync.RWMutex{}
"apigo.cc/gojs" // var taskData = map[string]map[string]any{}
"apigo.cc/gojs/goja" // var taskDataLock = sync.RWMutex{}
)
var taskData = map[string]map[string]any{} // var taskList = map[string]*list.List{}
var taskDataLock = sync.RWMutex{} // var taskListLock = sync.RWMutex{}
var taskList = map[string]*list.List{} // func DataSet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
var taskListLock = sync.RWMutex{} // args := gojs.MakeArgs(&argsIn, vm).Check(3)
// scope := args.Str(0)
// key := args.Str(1)
// value := args.Any(2)
// taskDataLock.Lock()
// defer taskDataLock.Unlock()
// if taskData[scope] == nil {
// taskData[scope] = map[string]any{}
// }
// taskData[scope][key] = value
// return nil
// }
func DataSet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // func DataGet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(3) // args := gojs.MakeArgs(&argsIn, vm).Check(2)
scope := args.Str(0) // scope := args.Str(0)
key := args.Str(1) // key := args.Str(1)
value := args.Any(2) // taskDataLock.RLock()
taskDataLock.Lock() // defer taskDataLock.RUnlock()
defer taskDataLock.Unlock() // if taskData[scope] != nil {
if taskData[scope] == nil { // return vm.ToValue(taskData[scope][key])
taskData[scope] = map[string]any{} // }
} // return nil
taskData[scope][key] = value // }
return nil
}
func DataGet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // func DataKeys(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(2) // args := gojs.MakeArgs(&argsIn, vm).Check(1)
scope := args.Str(0) // scope := args.Str(0)
key := args.Str(1) // taskDataLock.RLock()
taskDataLock.RLock() // defer taskDataLock.RUnlock()
defer taskDataLock.RUnlock() // if taskData[scope] != nil {
if taskData[scope] != nil { // keys := make([]string, len(taskData[scope]))
return vm.ToValue(taskData[scope][key]) // i := 0
} // for key := range taskData[scope] {
return nil // keys[i] = key
} // i++
// }
// return vm.ToValue(keys)
// }
// return nil
// }
func DataKeys(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // func DataCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) // args := gojs.MakeArgs(&argsIn, vm).Check(1)
scope := args.Str(0) // scope := args.Str(0)
taskDataLock.RLock() // taskDataLock.RLock()
defer taskDataLock.RUnlock() // defer taskDataLock.RUnlock()
if taskData[scope] != nil { // if taskData[scope] != nil {
keys := make([]string, len(taskData[scope])) // return vm.ToValue(len(taskData[scope]))
i := 0 // }
for key := range taskData[scope] { // return vm.ToValue(0)
keys[i] = key // }
i++
}
return vm.ToValue(keys)
}
return nil
}
func DataCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // func DataFetch(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) // args := gojs.MakeArgs(&argsIn, vm).Check(1)
scope := args.Str(0) // scope := args.Str(0)
taskDataLock.RLock() // taskDataLock.RLock()
defer taskDataLock.RUnlock() // defer taskDataLock.RUnlock()
if taskData[scope] != nil { // if taskData[scope] != nil {
return vm.ToValue(len(taskData[scope])) // all := make(map[string]any)
} // for k, v := range taskData[scope] {
return vm.ToValue(0) // all[k] = v
} // }
// return vm.ToValue(all)
// }
// return nil
// }
func DataFetch(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // func DataRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) // args := gojs.MakeArgs(&argsIn, vm).Check(1)
scope := args.Str(0) // scope := args.Str(0)
taskDataLock.RLock() // key := args.Str(1)
defer taskDataLock.RUnlock() // taskDataLock.Lock()
if taskData[scope] != nil { // defer taskDataLock.Unlock()
all := make(map[string]any) // if taskData[scope] != nil {
for k, v := range taskData[scope] { // if key != "" {
all[k] = v // delete(taskData[scope], key)
} // } else {
return vm.ToValue(all) // delete(taskData, scope)
} // }
return nil // }
} // return nil
// }
func DataRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // func ListPush(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) // args := gojs.MakeArgs(&argsIn, vm).Check(2)
scope := args.Str(0) // scope := args.Str(0)
key := args.Str(1) // value := args.Any(1)
taskDataLock.Lock() // fromHead := args.Bool(2)
defer taskDataLock.Unlock() // taskListLock.Lock()
if taskData[scope] != nil { // defer taskListLock.Unlock()
if key != "" { // list1 := taskList[scope]
delete(taskData[scope], key) // if list1 == nil {
} else { // list1 = list.New()
delete(taskData, scope) // taskList[scope] = list1
} // }
} // if fromHead {
return nil // list1.PushFront(value)
} // } else {
// list1.PushBack(value)
// }
// return nil
// }
func ListPush(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // func ListPop(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(2) // args := gojs.MakeArgs(&argsIn, vm).Check(1)
scope := args.Str(0) // scope := args.Str(0)
value := args.Any(1) // fromEnd := args.Bool(1)
fromHead := args.Bool(2) // taskListLock.Lock()
taskListLock.Lock() // var item *list.Element
defer taskListLock.Unlock() // defer taskListLock.Unlock()
list1 := taskList[scope] // list1 := taskList[scope]
if list1 == nil { // if list1 != nil {
list1 = list.New() // if fromEnd {
taskList[scope] = list1 // item = list1.Front()
} // } else {
if fromHead { // item = list1.Back()
list1.PushFront(value) // }
} else { // if item != nil {
list1.PushBack(value) // list1.Remove(item)
} // return vm.ToValue(item.Value)
return nil // }
} // }
// return nil
// }
func ListPop(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // func ListCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) // args := gojs.MakeArgs(&argsIn, vm).Check(1)
scope := args.Str(0) // scope := args.Str(0)
fromEnd := args.Bool(1) // taskListLock.RLock()
taskListLock.Lock() // defer taskListLock.RUnlock()
var item *list.Element // if taskList[scope] != nil {
defer taskListLock.Unlock() // return vm.ToValue(taskList[scope].Len())
list1 := taskList[scope] // }
if list1 != nil { // return vm.ToValue(0)
if fromEnd { // }
item = list1.Front()
} else {
item = list1.Back()
}
if item != nil {
list1.Remove(item)
return vm.ToValue(item.Value)
}
}
return nil
}
func ListCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // func ListRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) // args := gojs.MakeArgs(&argsIn, vm).Check(1)
scope := args.Str(0) // scope := args.Str(0)
taskListLock.RLock() // taskListLock.Lock()
defer taskListLock.RUnlock() // defer taskListLock.Unlock()
if taskList[scope] != nil { // delete(taskList, scope)
return vm.ToValue(taskList[scope].Len()) // return nil
} // }
return vm.ToValue(0)
}
func ListRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
scope := args.Str(0)
taskListLock.Lock()
defer taskListLock.Unlock()
delete(taskList, scope)
return nil
}

View File

@ -1,13 +1,12 @@
import service from "apigo.cc/gojs/service" import service from "apigo.cc/gojs/service"
import co from "apigo.cc/gojs/console" import rt from "apigo.cc/gojs/runtime"
import u from "apigo.cc/gojs/util"
function main() { function main() {
service.register({ path: '/echo2', noLog200: true }, ({ args, response }) => { service.register({ path: '/echo2', noLog200: true }, ({ args, response }) => {
// setTimeout(() => { // setTimeout(() => {
// response.end(args.name) // response.end(args.name)
// }, 10) // }, 10)
u.sleep(10) rt.sleep(10)
return args.name return args.name
}) })
} }

View File

@ -1,5 +1,6 @@
import s from "apigo.cc/gojs/service" import s from "apigo.cc/gojs/service"
import co from "apigo.cc/gojs/console" import co from "apigo.cc/gojs/console"
import task from "apigo.cc/gojs/task"
function main() { function main() {
s.register({ s.register({
@ -9,11 +10,13 @@ function main() {
}, },
onClose: ({ client }) => { onClose: ({ client }) => {
co.info('ws closed', client.id) co.info('ws closed', client.id)
s.dataRemove('wsTest', client.id) // s.dataRemove('wsTest', client.id)
task.remove('wsTest_' + client.id)
} }
}, ({ client }) => { }, ({ client }) => {
co.info('ws connected', client.id) co.info('ws connected', client.id)
s.dataSet('wsTest', client.id, client) // s.dataSet('wsTest', client.id, client)
task.set('wsTest_' + client.id, client)
client.write('Hello, World!') client.write('Hello, World!')
}) })
} }

View File

@ -2,12 +2,14 @@ package service_test
import ( import (
"fmt" "fmt"
"os"
"sync" "sync"
"time" "time"
"apigo.cc/gojs" "apigo.cc/gojs"
_ "apigo.cc/gojs/console" _ "apigo.cc/gojs/console"
_ "apigo.cc/gojs/http" _ "apigo.cc/gojs/http"
_ "apigo.cc/gojs/runtime"
"apigo.cc/gojs/service" "apigo.cc/gojs/service"
_ "apigo.cc/gojs/service" _ "apigo.cc/gojs/service"
_ "apigo.cc/gojs/util" _ "apigo.cc/gojs/util"
@ -38,6 +40,7 @@ func TestStartByPool(t *testing.T) {
gojs.ExportForDev() gojs.ExportForDev()
rt2 = gojs.New() rt2 = gojs.New()
u.CopyFile("api/echo.js", "api/echo_tmp.js")
err := rt2.StartFromFile("start.js") err := rt2.StartFromFile("start.js")
if err != nil { if err != nil {
t.Fatal("start failed", err) t.Fatal("start failed", err)
@ -216,7 +219,7 @@ func TestStopByPool(t *testing.T) {
t.Fatal("stop failed", err) t.Fatal("stop failed", err)
} }
gojs.WaitAll() gojs.WaitAll()
os.Remove("api/echo_tmp.js")
runtime.GC() runtime.GC()
ms3 := runtime.MemStats{} ms3 := runtime.MemStats{}
runtime.ReadMemStats(&ms3) runtime.ReadMemStats(&ms3)

View File

@ -2,12 +2,16 @@ package service_test
import ( import (
"fmt" "fmt"
"os"
"strings"
"testing" "testing"
"time" "time"
"apigo.cc/gojs" "apigo.cc/gojs"
_ "apigo.cc/gojs/console" _ "apigo.cc/gojs/console"
_ "apigo.cc/gojs/file"
_ "apigo.cc/gojs/http" _ "apigo.cc/gojs/http"
_ "apigo.cc/gojs/runtime"
_ "apigo.cc/gojs/service" _ "apigo.cc/gojs/service"
_ "apigo.cc/gojs/util" _ "apigo.cc/gojs/util"
"github.com/ssgo/httpclient" "github.com/ssgo/httpclient"
@ -22,6 +26,7 @@ const runTimes = 100
func TestStart(t *testing.T) { func TestStart(t *testing.T) {
gojs.ExportForDev() gojs.ExportForDev()
rt = gojs.New() rt = gojs.New()
u.CopyFile("api/echo.js", "api/echo_tmp.js")
err := rt.StartFromFile("start.js") err := rt.StartFromFile("start.js")
if err != nil { if err != nil {
t.Fatal("start failed", err) t.Fatal("start failed", err)
@ -55,6 +60,19 @@ func TestJsEcho(t *testing.T) {
} }
} }
func TestJsEcho2(t *testing.T) {
for i := 0; i < runTimes; i++ {
name := u.UniqueId()
r, err := rt.RunCode("test2('" + name + "')")
if err != nil {
t.Fatal("test2 js get failed, got error", err)
} else if r != name {
t.Fatal("test2 js get failed, name not match", r, name)
}
}
u.WriteFile("api/echo_tmp.js", strings.Replace(u.ReadFileN("api/echo.js"), "return args.name", "return args.name+'!!'", 1))
}
func TestGoEcho(t *testing.T) { func TestGoEcho(t *testing.T) {
hc := httpclient.GetClientH2C(0) hc := httpclient.GetClientH2C(0)
for i := 0; i < runTimes; i++ { for i := 0; i < runTimes; i++ {
@ -118,6 +136,19 @@ func TestGoAsyncEcho(t *testing.T) {
fmt.Println(u.BGreen("last name:"), lastName, lastResult) fmt.Println(u.BGreen("last name:"), lastName, lastResult)
} }
func TestJsEcho2WithHotLoad(t *testing.T) {
time.Sleep(time.Second)
for i := 0; i < runTimes; i++ {
name := u.UniqueId()
r, err := rt.RunCode("test2('" + name + "')")
if err != nil {
t.Fatal("test2 js get failed, got error", err)
} else if r != name+"!!" {
t.Fatal("test2 js get failed, name not match", r, name)
}
}
}
func TestStop(t *testing.T) { func TestStop(t *testing.T) {
go func() { go func() {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@ -125,6 +156,7 @@ func TestStop(t *testing.T) {
}() }()
gojs.WaitAll() gojs.WaitAll()
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
os.Remove("api/echo_tmp.js")
// if err != nil { // if err != nil {
// t.Fatal("stop failed", err) // t.Fatal("stop failed", err)
// } // }

View File

@ -6,7 +6,9 @@ import (
"apigo.cc/gojs" "apigo.cc/gojs"
_ "apigo.cc/gojs/console" _ "apigo.cc/gojs/console"
_ "apigo.cc/gojs/http" _ "apigo.cc/gojs/http"
_ "apigo.cc/gojs/runtime"
_ "apigo.cc/gojs/service" _ "apigo.cc/gojs/service"
_ "apigo.cc/gojs/task"
_ "apigo.cc/gojs/util" _ "apigo.cc/gojs/util"
"github.com/ssgo/u" "github.com/ssgo/u"
@ -43,7 +45,7 @@ func TestWS(t *testing.T) {
} }
func TestStopWSByPool(t *testing.T) { func TestStopWSByPool(t *testing.T) {
_, err := rt3.RunCode("s.stop()") _, err := rt3.RunCode("s.stop();task.stop();")
if err != nil { if err != nil {
t.Fatal("stop failed", err) t.Fatal("stop failed", err)
} }

View File

@ -1,7 +1,8 @@
import s from "apigo.cc/gojs/service" import s from "apigo.cc/gojs/service"
import http from "apigo.cc/gojs/http" import http from "apigo.cc/gojs/http"
import u from "apigo.cc/gojs/util" import rt from "apigo.cc/gojs/runtime"
import co from "apigo.cc/gojs/console" import co from "apigo.cc/gojs/console"
import file from "apigo.cc/gojs/file"
let h2c = http let h2c = http
let urlPrefix let urlPrefix
@ -33,16 +34,17 @@ function main() {
}, },
rewrite: { rewrite: {
'/echo2.js': '/echo.js' '/echo2.js': '/echo.js'
} },
hotLoad: 1,
}) })
s.register({ path: '/echo', noLog200: true }, ({ args, response }) => { s.register({ path: '/echo', noLog200: true }, ({ args, response }) => {
// setTimeout(() => { // setTimeout(() => {
// response.end(args.name) // response.end(args.name)
// }, 1) // }, 1)
u.sleep(1) rt.sleep(1)
return args.name return args.name
}) })
s.load('api/echo.js', { min: 20, max: 1000, idle: 100 }) s.load('api/echo_tmp.js', { min: 20, max: 1000, idle: 100 })
s.load('api/user.js') s.load('api/user.js')
let host = s.start() let host = s.start()
h2c = http.newH2C({ h2c = http.newH2C({
@ -89,7 +91,7 @@ function testUser() {
if (r.statusCode != 429) { if (r.statusCode != 429) {
return r return r
} }
u.sleep(100) rt.sleep(100)
// 测试限流器过期后允许的 1 次请求 // 测试限流器过期后允许的 1 次请求
r = h2c.get('/userInfo').object() r = h2c.get('/userInfo').object()

View File

@ -1,13 +1,16 @@
import s from "apigo.cc/gojs/service" import s from "apigo.cc/gojs/service"
import http from "apigo.cc/gojs/http" import http from "apigo.cc/gojs/http"
import u from "apigo.cc/gojs/util" import rt from "apigo.cc/gojs/runtime"
import co from "apigo.cc/gojs/console" import co from "apigo.cc/gojs/console"
import task from "apigo.cc/gojs/task"
let hc = http let hc = http
let urlPrefix let urlPrefix
function main() { function main() {
s.load('api/ws.js') s.load('api/ws.js')
s.task('task.js', 100) // s.task('task.js', 100)
task.addTask("@every 1s", 'task.js')
task.start()
let host = s.start() let host = s.start()
hc = http.new({ baseURL: 'http://' + host }) hc = http.new({ baseURL: 'http://' + host })
return host return host
@ -31,7 +34,7 @@ function testWS() {
co.info('test ws abc ok') co.info('test ws abc ok')
// ws.ping() // ws.ping()
u.sleep(10) rt.sleep(10)
let pc = ws.pingCount() let pc = ws.pingCount()
co.info('test ws ping ok', pc.pingTimes, pc.pongTimes) co.info('test ws ping ok', pc.pingTimes, pc.pongTimes)
@ -51,15 +54,13 @@ function testWS() {
return r return r
} }
co.info('test ws json ok') co.info('test ws json ok')
rt.sleep(2000)
u.sleep(1000)
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
let j = ws.read() let j = ws.read()
if (i !== j.data) { if (i !== j.data) {
return j return j
} }
} }
ws.close() ws.close()
return true return true
} }

View File

@ -1,5 +1,6 @@
import s from 'apigo.cc/gojs/service' import s from 'apigo.cc/gojs/service'
import co from 'apigo.cc/gojs/console' import co from 'apigo.cc/gojs/console'
import task from 'apigo.cc/gojs/task'
function onStart() { function onStart() {
co.info('task start') co.info('task start')
@ -7,24 +8,27 @@ function onStart() {
let i = 0 let i = 0
function onRun() { function onRun() {
let connCount = s.dataCount('wsTest') let keys = task.keys('wsTest_')
if (connCount > 0) { // let connCount = s.dataCount('wsTest')
let conns = s.dataFetch('wsTest') if (keys.length > 0) {
// let conns = s.dataFetch('wsTest')
let conns = task.getAll('wsTest_')
for (let id in conns) { for (let id in conns) {
let conn = conns[id] let conn = conns[id]
try { try {
conn.write(i++) conn.write(i++)
} catch (e) { } catch (e) {
co.error(e) co.error(e)
s.dataRemove('wsTest', id) task.remove(id)
} }
} }
} }
co.info('task run', connCount) co.info('task run', keys.length)
} }
function onStop() { function onStop() {
s.dataRemove('wsTest') // s.dataRemove('wsTest')
co.info('task stop', s.dataCount('wsTest')) task.removeAll('wsTest_')
co.info('task stop')
} }