Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
57db518ef9 | |||
715de5e442 | |||
afcce4d649 | |||
38cf9933be | |||
![]() |
a1f5f72181 | ||
b1796dad4d | |||
e9d4ec6bc7 | |||
5352152b66 |
52
go.mod
52
go.mod
@ -1,44 +1,54 @@
|
||||
module apigo.cc/gojs/service
|
||||
|
||||
go 1.18
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
apigo.cc/gojs v0.0.7
|
||||
apigo.cc/gojs v0.0.25
|
||||
apigo.cc/gojs/console v0.0.2
|
||||
apigo.cc/gojs/http v0.0.3
|
||||
apigo.cc/gojs/util v0.0.7
|
||||
apigo.cc/gojs/file v0.0.5
|
||||
apigo.cc/gojs/http v0.0.7
|
||||
apigo.cc/gojs/runtime v0.0.3
|
||||
apigo.cc/gojs/task v0.0.6
|
||||
apigo.cc/gojs/util v0.0.13
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
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/log v1.7.7
|
||||
github.com/ssgo/redis v1.7.7
|
||||
github.com/ssgo/s v1.7.20
|
||||
github.com/ssgo/log v1.7.9
|
||||
github.com/ssgo/redis v1.7.8
|
||||
github.com/ssgo/s v1.7.24
|
||||
github.com/ssgo/standard v1.7.7
|
||||
github.com/ssgo/u v1.7.13
|
||||
github.com/ssgo/u v1.7.21
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ZZMarquis/gm v1.3.2 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/emmansun/gmsm v0.29.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/emmansun/gmsm v0.30.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.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/gomodule/redigo v1.9.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
||||
github.com/obscuren/ecies v0.0.0-20150213224233-7c0f4a9b18d9 // 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/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/ssgo/tool v0.4.27 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/ssgo/tool v0.4.29 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // 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
|
||||
golang.org/x/crypto v0.29.0 // indirect
|
||||
golang.org/x/net v0.31.0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
@ -111,7 +111,7 @@ func (r *Response) SendFile(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Val
|
||||
|
||||
func (r *Response) DownloadFile(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(3)
|
||||
r.resp.DownloadFile(args.Str(0), args.Str(1), args.Bytes(1))
|
||||
r.resp.DownloadFile(args.Str(0), args.Str(1), args.Bytes(2))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
411
service.go
411
service.go
@ -37,6 +37,8 @@ var serviceMD string
|
||||
var server *s.AsyncServer
|
||||
|
||||
var pools = map[string]*gojs.Pool{}
|
||||
var poolsMTime = map[string]int64{}
|
||||
var poolsConfig = map[string]gojs.PoolConfig{}
|
||||
|
||||
var poolExists = map[string]bool{}
|
||||
var poolActionRegistered = map[string]bool{}
|
||||
@ -71,6 +73,7 @@ type Config struct {
|
||||
LimitedMessage string
|
||||
Limiters map[string]*LimiterConfig
|
||||
LimiterRedis string
|
||||
HotLoad int
|
||||
|
||||
Proxy map[string]string
|
||||
Rewrite map[string]string
|
||||
@ -86,12 +89,12 @@ var configed = false
|
||||
func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
|
||||
configed = true
|
||||
s.InitConfig()
|
||||
if startPath, ok := vm.GoData["startPath"]; ok {
|
||||
if startPath := vm.GetData("startPath"); startPath != nil {
|
||||
s.SetWorkPath(u.String(startPath))
|
||||
}
|
||||
// 处理配置
|
||||
serviceConfig = Config{"Session", "Device", "Client", "userId", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", map[string]string{}, map[string]string{}, map[string]string{}}
|
||||
if errs := config.LoadConfig("service", &serviceConfig); errs != nil && len(errs) > 0 {
|
||||
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); len(errs) > 0 {
|
||||
panic(vm.NewGoError(errs[0]))
|
||||
}
|
||||
config.LoadConfig("service", &discover.Config)
|
||||
@ -139,20 +142,33 @@ func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
|
||||
if authAccessToken && setAuthLevel < authLevel {
|
||||
setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token"))
|
||||
}
|
||||
|
||||
authOk := false
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return false, msg
|
||||
}
|
||||
return false, msg
|
||||
}
|
||||
})
|
||||
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() {
|
||||
@ -212,25 +266,18 @@ func init() {
|
||||
}
|
||||
|
||||
// 处理Watch
|
||||
if vm.GoData["inWatch"] == true {
|
||||
if inWatch := vm.GetData("inWatch"); inWatch != nil && inWatch.(bool) {
|
||||
onWatchConn := map[string]*websocket.Conn{}
|
||||
onWatchLock := sync.RWMutex{}
|
||||
vm.GoData["onWatch"] = func(filename string) {
|
||||
conns := map[string]*websocket.Conn{}
|
||||
onWatchLock.RLock()
|
||||
onWatchLock := sync.Mutex{}
|
||||
vm.SetData("onWatch", func(filename string) {
|
||||
onWatchLock.Lock()
|
||||
defer onWatchLock.Unlock()
|
||||
for id, conn := range onWatchConn {
|
||||
conns[id] = conn
|
||||
}
|
||||
onWatchLock.RUnlock()
|
||||
for id, conn := range conns {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(filename)); err != nil {
|
||||
onWatchLock.Lock()
|
||||
delete(onWatchConn, id)
|
||||
onWatchLock.Unlock()
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
s.AddShutdownHook(func() {
|
||||
for _, conn := range onWatchConn {
|
||||
conn.Close()
|
||||
@ -244,13 +291,43 @@ func init() {
|
||||
s.SetOutFilter(func(in map[string]any, request *s.Request, response *s.Response, out any, logger *log.Logger) (newOut any, isOver bool) {
|
||||
if strings.HasPrefix(response.Header().Get("Content-Type"), "text/html") {
|
||||
outStr := u.String(out)
|
||||
if strings.Contains(outStr, "let _watchWS = null") {
|
||||
return nil, false
|
||||
}
|
||||
// 注入自动刷新的代码
|
||||
outStr = strings.ReplaceAll(outStr, "</html>", `<script>
|
||||
let _watchWS = null
|
||||
let _watchWSConnection = false
|
||||
let _watchWSIsFirst = true
|
||||
function connect() {
|
||||
_watchWSConnection = true
|
||||
let ws = new WebSocket(location.protocol.replace('http', 'ws') + '//' + location.host + '/_watch')
|
||||
ws.onmessage = () => { location.reload() }
|
||||
ws.onclose = () => { setTimeout(connect, 1000) }
|
||||
ws.onopen = () => {
|
||||
_watchWS = ws
|
||||
_watchWSConnection = false
|
||||
if( !_watchWSIsFirst ) location.reload()
|
||||
_watchWSIsFirst = false
|
||||
}
|
||||
ws.onmessage = () => {
|
||||
location.reload()
|
||||
}
|
||||
ws.onclose = () => {
|
||||
_watchWS = null
|
||||
_watchWSConnection = false
|
||||
}
|
||||
}
|
||||
setInterval(()=>{
|
||||
if(_watchWS!= null){
|
||||
try{
|
||||
_watchWS.send("ping")
|
||||
}catch(err){
|
||||
_watchWS = null
|
||||
_watchWSConnection = false
|
||||
}
|
||||
} else if(!_watchWSConnection){
|
||||
connect()
|
||||
}
|
||||
}, 1000)
|
||||
connect()
|
||||
</script>
|
||||
</html>`)
|
||||
@ -264,6 +341,7 @@ func init() {
|
||||
server = s.AsyncStart()
|
||||
waitChan = make(chan bool, 1)
|
||||
server.OnStop(func() {
|
||||
configed = false
|
||||
if onStop != nil {
|
||||
onStop(nil)
|
||||
}
|
||||
@ -285,74 +363,116 @@ func init() {
|
||||
panic(vm.NewGoError(errors.New("server not started")))
|
||||
}
|
||||
server.Stop()
|
||||
s.ResetAllSets()
|
||||
return nil
|
||||
},
|
||||
"uniqueId": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm)
|
||||
size := args.Int(0)
|
||||
var id string
|
||||
if size >= 20 {
|
||||
id = s.UniqueId20()
|
||||
} else if size >= 16 {
|
||||
id = s.UniqueId16()
|
||||
} else if size >= 14 {
|
||||
id = s.UniqueId14()
|
||||
} else if size >= 12 {
|
||||
id = s.UniqueId14()[0:12]
|
||||
} else {
|
||||
id = s.UniqueId()
|
||||
}
|
||||
return vm.ToValue(id)
|
||||
},
|
||||
"uniqueIdL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm)
|
||||
size := args.Int(0)
|
||||
var id string
|
||||
if size >= 20 {
|
||||
id = s.UniqueId20()
|
||||
} else if size >= 16 {
|
||||
id = s.UniqueId16()
|
||||
} else if size >= 14 {
|
||||
id = s.UniqueId14()
|
||||
} else if size >= 12 {
|
||||
id = s.UniqueId14()[0:12]
|
||||
} else {
|
||||
id = s.UniqueId()
|
||||
}
|
||||
return vm.ToValue(strings.ToLower(id))
|
||||
},
|
||||
// "uniqueId": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm)
|
||||
// size := args.Int(0)
|
||||
// var id string
|
||||
// if size >= 20 {
|
||||
// id = s.UniqueId20()
|
||||
// } else if size >= 16 {
|
||||
// id = s.UniqueId16()
|
||||
// } else if size >= 14 {
|
||||
// id = s.UniqueId14()
|
||||
// } else if size >= 12 {
|
||||
// id = s.UniqueId14()[0:12]
|
||||
// } else {
|
||||
// id = s.UniqueId()
|
||||
// }
|
||||
// return vm.ToValue(id)
|
||||
// },
|
||||
// "uniqueIdL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm)
|
||||
// size := args.Int(0)
|
||||
// var id string
|
||||
// if size >= 20 {
|
||||
// id = s.UniqueId20()
|
||||
// } else if size >= 16 {
|
||||
// id = s.UniqueId16()
|
||||
// } else if size >= 14 {
|
||||
// id = s.UniqueId14()
|
||||
// } else if size >= 12 {
|
||||
// id = s.UniqueId14()[0:12]
|
||||
// } else {
|
||||
// id = s.UniqueId()
|
||||
// }
|
||||
// return vm.ToValue(strings.ToLower(id))
|
||||
// },
|
||||
"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)
|
||||
size := args.Int(0)
|
||||
if size == 0 {
|
||||
size = 12
|
||||
}
|
||||
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)
|
||||
if size > 20 {
|
||||
size = 20
|
||||
}
|
||||
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年时间戳:946656000)(5位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 {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(2)
|
||||
o := args.Obj(0)
|
||||
@ -377,7 +497,7 @@ func init() {
|
||||
for i, require := range requiresObj {
|
||||
requires[i] = u.String(require)
|
||||
}
|
||||
vm.GoData[fmt.Sprint("REQUIRE_"+host, method, path)] = requires
|
||||
vm.SetData(fmt.Sprint("REQUIRE_"+host, method, path), requires)
|
||||
}
|
||||
if verifiesObj := o.Obj("verifies"); verifiesObj != nil {
|
||||
verifiesSet := map[string]func(any, *goja.Runtime) bool{}
|
||||
@ -411,29 +531,29 @@ func init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
vm.GoData[fmt.Sprint("VERIFY_"+host, method, path)] = verifiesSet
|
||||
vm.SetData(fmt.Sprint("VERIFY_"+host, method, path), verifiesSet)
|
||||
}
|
||||
|
||||
opt := s.WebServiceOptions{
|
||||
NoBody: o.Bool("noBody"),
|
||||
NoLog200: o.Bool("noLog200"),
|
||||
Host: host,
|
||||
//Ext: nil,
|
||||
Ext: o.Map("ext"),
|
||||
Limiters: usedLimiters,
|
||||
}
|
||||
|
||||
startFile := u.String(vm.GoData["startFile"])
|
||||
startFile := u.String(vm.GetData("startFile"))
|
||||
poolsLock.RLock()
|
||||
poolExist := poolExists[startFile]
|
||||
poolsLock.RUnlock()
|
||||
if poolExist {
|
||||
// 从对象调用(支持并发)
|
||||
actionKey := "REGISTER_" + host + method + path
|
||||
vm.GoData[actionKey] = action
|
||||
vm.GoData[actionKey+"This"] = args.This
|
||||
vm.SetData(actionKey, action)
|
||||
vm.SetData(actionKey+"This", args.This)
|
||||
if method == "WS" {
|
||||
vm.GoData[actionKey+"onMessage"] = o.Func("onMessage")
|
||||
vm.GoData[actionKey+"onClose"] = o.Func("onClose")
|
||||
vm.SetData(actionKey+"onMessage", o.Func("onMessage"))
|
||||
vm.SetData(actionKey+"onClose", o.Func("onClose"))
|
||||
}
|
||||
poolsLock.Lock()
|
||||
actionRegistered := poolActionRegistered[actionKey]
|
||||
@ -472,61 +592,66 @@ func init() {
|
||||
mainArgs = mainArgs1
|
||||
}
|
||||
}
|
||||
if !u.FileExists(actionFile) {
|
||||
fi := u.GetFileInfo(actionFile)
|
||||
if fi == nil {
|
||||
fullActionFile, _ := filepath.Abs(actionFile)
|
||||
panic(vm.NewGoError(errors.New("actionFile must be a js file path: " + fullActionFile)))
|
||||
}
|
||||
actionCode := u.ReadFileN(actionFile)
|
||||
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")))
|
||||
if !strings.Contains(actionCode, "function main(") { // || !strings.Contains(actionCode, ".register(")
|
||||
panic(vm.NewGoError(errors.New("actionFile must be a js file with main function")))
|
||||
}
|
||||
poolsLock.Lock()
|
||||
poolExists[actionFile] = true
|
||||
poolsLock.Unlock()
|
||||
p := gojs.NewPoolByCode(actionCode, actionFile, gojs.PoolConfig{
|
||||
poolOpt := gojs.PoolConfig{
|
||||
Min: mi,
|
||||
Max: ma,
|
||||
Idle: idle,
|
||||
Debug: debug,
|
||||
Args: mainArgs,
|
||||
}, args.Logger)
|
||||
}
|
||||
p := gojs.NewPoolByCode(actionCode, actionFile, poolOpt, args.Logger)
|
||||
//p := gojs.NewLBByCode(actionCode, actionFile, gojs.LBConfig{
|
||||
// Num: num,
|
||||
// Debug: debug,
|
||||
// Args: mainArgs,
|
||||
//}, args.Logger)
|
||||
mtime := fi.ModTime.Unix()
|
||||
poolsLock.Lock()
|
||||
pools[actionFile] = p
|
||||
poolsMTime[actionFile] = mtime
|
||||
poolsConfig[actionFile] = poolOpt
|
||||
poolsLock.Unlock()
|
||||
return nil
|
||||
},
|
||||
"task": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
taskFile := args.Path(0)
|
||||
interval := args.Int(1)
|
||||
if interval == 0 {
|
||||
interval = 1000
|
||||
}
|
||||
if interval < 100 {
|
||||
interval = 100
|
||||
}
|
||||
if !u.FileExists(taskFile) {
|
||||
panic(vm.NewGoError(errors.New("taskFile must be a js file path")))
|
||||
}
|
||||
rt := gojs.New()
|
||||
_, err := rt.RunFile(taskFile)
|
||||
if err != nil {
|
||||
panic(vm.NewGoError(err))
|
||||
}
|
||||
s.NewTimerServer(taskFile, time.Duration(interval)*time.Millisecond, func(isRunning *bool) {
|
||||
rt.RunCode("if(onRun)onRun()")
|
||||
}, func() {
|
||||
rt.RunCode("if(onStart)onStart()")
|
||||
}, func() {
|
||||
rt.RunCode("if(onStop)onStop()")
|
||||
})
|
||||
return nil
|
||||
},
|
||||
// "task": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
// taskFile := args.Path(0)
|
||||
// interval := args.Int(1)
|
||||
// if interval == 0 {
|
||||
// interval = 1000
|
||||
// }
|
||||
// if interval < 100 {
|
||||
// interval = 100
|
||||
// }
|
||||
// if !u.FileExists(taskFile) {
|
||||
// panic(vm.NewGoError(errors.New("taskFile must be a js file path")))
|
||||
// }
|
||||
// rt := gojs.New()
|
||||
// _, err := rt.RunFile(taskFile)
|
||||
// if err != nil {
|
||||
// panic(vm.NewGoError(err))
|
||||
// }
|
||||
// s.NewTimerServer(taskFile, time.Duration(interval)*time.Millisecond, func(isRunning *bool) {
|
||||
// rt.RunCode("if(onRun)onRun()")
|
||||
// }, func() {
|
||||
// rt.RunCode("if(onStart)onStart()")
|
||||
// }, func() {
|
||||
// rt.RunCode("if(onStop)onStop()")
|
||||
// })
|
||||
// return nil
|
||||
// },
|
||||
"setTplFunc": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
fnObj := args.Obj(0)
|
||||
@ -621,17 +746,17 @@ func init() {
|
||||
}
|
||||
return vm.ToValue(buf.String())
|
||||
},
|
||||
"dataSet": DataSet,
|
||||
"dataGet": DataGet,
|
||||
"dataKeys": DataKeys,
|
||||
"dataCount": DataCount,
|
||||
"dataFetch": DataFetch,
|
||||
"dataRemove": DataRemove,
|
||||
"listPop": ListPop,
|
||||
"listPush": ListPush,
|
||||
"listCount": ListCount,
|
||||
"listRemove": ListRemove,
|
||||
"newCaller": NewCaller,
|
||||
// "dataSet": DataSet,
|
||||
// "dataGet": DataGet,
|
||||
// "dataKeys": DataKeys,
|
||||
// "dataCount": DataCount,
|
||||
// "dataFetch": DataFetch,
|
||||
// "dataRemove": DataRemove,
|
||||
// "listPop": ListPop,
|
||||
// "listPush": ListPush,
|
||||
// "listCount": ListCount,
|
||||
// "listRemove": ListRemove,
|
||||
"newCaller": NewCaller,
|
||||
}
|
||||
|
||||
gojs.Register("apigo.cc/gojs/service", gojs.Module{
|
||||
@ -746,10 +871,10 @@ func runAction(action goja.Callable, vm *goja.Runtime, thisArg goja.Value, args
|
||||
defer vm.CallbackLocker.Unlock()
|
||||
|
||||
// 验证请求参数的有效性
|
||||
if verifies, ok := vm.GoData["VERIFY_"+u.String(request.Get("registerTag"))].(map[string]func(any, *goja.Runtime) bool); ok {
|
||||
if verifies, ok := vm.GetData("VERIFY_" + u.String(request.Get("registerTag"))).(map[string]func(any, *goja.Runtime) bool); ok {
|
||||
failedFields := make([]string, 0)
|
||||
// 检查必填字段
|
||||
if requires, ok := vm.GoData["REQUIRE_"+u.String(request.Get("registerTag"))].([]string); ok {
|
||||
if requires, ok := vm.GetData("REQUIRE_" + u.String(request.Get("registerTag"))).([]string); ok {
|
||||
for _, requireField := range requires {
|
||||
if _, ok := args[requireField]; !ok {
|
||||
failedFields = append(failedFields, requireField)
|
||||
|
66
service.ts
66
service.ts
@ -6,22 +6,22 @@ export default {
|
||||
stop,
|
||||
register,
|
||||
load,
|
||||
task,
|
||||
// task,
|
||||
newCaller,
|
||||
dataSet,
|
||||
dataGet,
|
||||
dataKeys,
|
||||
dataCount,
|
||||
dataFetch,
|
||||
dataRemove,
|
||||
listPush,
|
||||
listPop,
|
||||
listCount,
|
||||
listRemove,
|
||||
// dataSet,
|
||||
// dataGet,
|
||||
// dataKeys,
|
||||
// dataCount,
|
||||
// dataFetch,
|
||||
// dataRemove,
|
||||
// listPush,
|
||||
// listPop,
|
||||
// listCount,
|
||||
// listRemove,
|
||||
id,
|
||||
idL,
|
||||
uniqueId,
|
||||
uniqueIdL,
|
||||
// idL,
|
||||
// uniqueId,
|
||||
// uniqueIdL,
|
||||
setTplFunc,
|
||||
tpl,
|
||||
}
|
||||
@ -32,25 +32,26 @@ function stop(): void { }
|
||||
function register(option: RegisterOption, callback: (params: RequestParams) => void): any { return null }
|
||||
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 dataSet(scope: string, key: string, value: any): void { }
|
||||
function dataGet(scope: string, key: string): any { return null }
|
||||
function dataKeys(scope: string): string[] { return [] }
|
||||
function dataCount(scope: string): number { return 0 }
|
||||
function dataFetch(scope: string): Map<string, any> { return null as any }
|
||||
function dataRemove(scope: string, key?: string): void { }
|
||||
// function dataSet(scope: string, key: string, value: any): void { }
|
||||
// function dataGet(scope: string, key: string): any { return null }
|
||||
// function dataKeys(scope: string): string[] { return [] }
|
||||
// function dataCount(scope: string): number { return 0 }
|
||||
// function dataFetch(scope: string): Map<string, any> { return null as any }
|
||||
// function dataRemove(scope: string, key?: string): void { }
|
||||
|
||||
function listPush(scope: string, key: string, value: any): void { }
|
||||
function listPop(scope: string, key: string): any { return null }
|
||||
function listCount(scope: string): number { return 0 }
|
||||
function listRemove(scope: string): void { }
|
||||
function id(space: string, size?: number): string { return '' }
|
||||
function idL(space: string, size?: number): string { return '' }
|
||||
function uniqueId(size?: number): string { return '' }
|
||||
function uniqueIdL(size?: number): string { return '' }
|
||||
// function listPush(scope: string, key: string, value: any): void { }
|
||||
// function listPop(scope: string, key: string): any { return null }
|
||||
// function listCount(scope: string): number { return 0 }
|
||||
// function listRemove(scope: string): void { }
|
||||
function id(size?: number): string { return '' }
|
||||
// function id(space: string, size?: number): string { return '' }
|
||||
// function idL(space: string, size?: number): string { return '' }
|
||||
// function uniqueId(size?: number): string { return '' }
|
||||
// function uniqueIdL(size?: number): string { return '' }
|
||||
function setTplFunc(fnList: Object): void { }
|
||||
function tpl(file: string, data: Object): string { return '' }
|
||||
|
||||
@ -85,6 +86,8 @@ interface Config {
|
||||
cpuLimitTimes: number // CPU超过最高占用值超过次数(1-100)将报警(如果CpuMonitor开启的话),默认6(即30秒内连续6次)
|
||||
memoryLimitTimes: number // 内存超过最高占用值超过次数(1-100)将报警(如果MemoryMonitor开启的话),默认6(即30秒内连续6次)
|
||||
cookieScope: string // 启用Session时Cookie的有效范围,host|domain|topDomain,默认值为host
|
||||
sessionWithoutCookie: boolean // Session禁用Cookie保持,默认使用Cookie
|
||||
deviceWithoutCookie: boolean // 设备ID禁用Cookie保持,默认使用Cookie
|
||||
idServer: string // 用s.UniqueId、s.Id来生成唯一ID(雪花算法)时所需的redis服务器连接,如果不指定将不能实现跨服务的全局唯一
|
||||
keepKeyCase: boolean // 是否保持Key的首字母大小写?默认保持,设置为false则自动将首字母转为小写
|
||||
indexFiles: string[] // 访问静态文件时的索引文件,默认为 index.html
|
||||
@ -106,7 +109,7 @@ interface Config {
|
||||
sessionKey: string // HTTP头和Cookie中SessionID的Key,客户端没有传递时服务端自动生成,Header的优先级高于Cookie,默认为 Session, 设置为空时表示不使用
|
||||
deviceKey: string // 标识设备ID的Key,客户端没有传递时服务端自动生成,Header的优先级高于Cookie,默认为 Device, 设置为空时表示不使用
|
||||
clientKey: string // 标识客户端的Key,默认为 Client,对应的Header头为 ClientName 和 ClientVersion
|
||||
userIdKey: string // session中userID的Key,用于在日志中记录userId信息,默认为 userId
|
||||
userIdKey: string // session中userID的Key,用于在日志中记录用户ID信息,默认为 id
|
||||
sessionProvider: string // 指定一个redis连接(例如:redis://:sskey加密的密码@127.0.0.1:6379/15),默认使用内存存储
|
||||
sessionTimeout: number // session过期时间,单位 秒,默认为 3600秒
|
||||
authFieldMessage: string | Object // 身份验证失败时的消息,默认为 auth failed,可以设置对象来返回JSON,可以使用模版 {{TARGET_AUTHLEVEL}}、{{USER_AUTHLEVEL}}
|
||||
@ -114,6 +117,7 @@ interface Config {
|
||||
limitedMessage: string | Object // 访问受限时的消息,默认为 too many requests,可以设置对象来返回JSON,可以使用模版 {{LIMITED_FROM}}、{{LIMITED_VALUE}}
|
||||
limiterRedis: string // 限流器使用的Redis连接,默认使用内存存储
|
||||
limiters: Map<string, LimiterConfig> // 限流器配置,from 为数据来源,例如:ip、user、device、header.User-Agent、in.phone 等(in表示从请求参数中获取),time为时间间隔,单位ms,times为 时间间隔内允许访问的次数
|
||||
hotLoad: number // 热加载配置,单位s,默认值:0,0表示不检测热加载
|
||||
|
||||
// gateway 的配置参数
|
||||
proxy: Map<string, string> // 代理配置,key为[host][path],value为代理的目标应用或URL
|
||||
@ -158,6 +162,7 @@ interface RegisterOption {
|
||||
limiters: string[]
|
||||
verifies: Object
|
||||
requires: string[]
|
||||
ext: Object
|
||||
onMessage: (params: OnMessageParams) => void
|
||||
onClose: (params: RequestParams) => void
|
||||
}
|
||||
@ -271,6 +276,7 @@ interface Response {
|
||||
sendFile: (contentType: string, filename: string) => void
|
||||
downloadFile: (contentType: string, filename: string, data: any) => void
|
||||
location: (url: string) => void
|
||||
end: (data: any) => void
|
||||
}
|
||||
|
||||
interface Logger {
|
||||
|
83
session.go
83
session.go
@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -9,12 +10,14 @@ import (
|
||||
"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
|
||||
id string
|
||||
conn *redis.Redis
|
||||
data map[string]any
|
||||
funcAuthCache map[string]bool
|
||||
}
|
||||
|
||||
var sessionRedis *redis.Redis
|
||||
@ -40,9 +43,10 @@ func NewSession(id string, logger *log.Logger) *Session {
|
||||
}
|
||||
}
|
||||
return &Session{
|
||||
id: id,
|
||||
conn: conn,
|
||||
data: data,
|
||||
id: id,
|
||||
conn: conn,
|
||||
data: data,
|
||||
funcAuthCache: map[string]bool{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,6 +106,73 @@ func (session *Session) SetAuthLevel(argsIn goja.FunctionCall, vm *goja.Runtime)
|
||||
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()
|
||||
|
299
task.go
299
task.go
@ -1,162 +1,165 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
// type Task struct {
|
||||
// spec string
|
||||
// file string
|
||||
// vm *gojs.Runtime
|
||||
// lock sync.RWMutex
|
||||
// mtime time.Time
|
||||
// // policy string
|
||||
// }
|
||||
// var tasks []Task
|
||||
// var tasksLock = sync.RWMutex{}
|
||||
|
||||
"apigo.cc/gojs"
|
||||
"apigo.cc/gojs/goja"
|
||||
)
|
||||
// var taskData = map[string]map[string]any{}
|
||||
// var taskDataLock = sync.RWMutex{}
|
||||
|
||||
var taskData = map[string]map[string]any{}
|
||||
var taskDataLock = sync.RWMutex{}
|
||||
// var taskList = map[string]*list.List{}
|
||||
// var taskListLock = sync.RWMutex{}
|
||||
|
||||
var taskList = map[string]*list.List{}
|
||||
var taskListLock = sync.RWMutex{}
|
||||
// func DataSet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// 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 {
|
||||
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 DataGet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(2)
|
||||
// scope := args.Str(0)
|
||||
// key := args.Str(1)
|
||||
// taskDataLock.RLock()
|
||||
// defer taskDataLock.RUnlock()
|
||||
// if taskData[scope] != nil {
|
||||
// return vm.ToValue(taskData[scope][key])
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func DataGet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(2)
|
||||
scope := args.Str(0)
|
||||
key := args.Str(1)
|
||||
taskDataLock.RLock()
|
||||
defer taskDataLock.RUnlock()
|
||||
if taskData[scope] != nil {
|
||||
return vm.ToValue(taskData[scope][key])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func DataKeys(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
// scope := args.Str(0)
|
||||
// taskDataLock.RLock()
|
||||
// defer taskDataLock.RUnlock()
|
||||
// if taskData[scope] != nil {
|
||||
// keys := make([]string, len(taskData[scope]))
|
||||
// i := 0
|
||||
// for key := range taskData[scope] {
|
||||
// keys[i] = key
|
||||
// i++
|
||||
// }
|
||||
// return vm.ToValue(keys)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func DataKeys(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
scope := args.Str(0)
|
||||
taskDataLock.RLock()
|
||||
defer taskDataLock.RUnlock()
|
||||
if taskData[scope] != nil {
|
||||
keys := make([]string, len(taskData[scope]))
|
||||
i := 0
|
||||
for key := range taskData[scope] {
|
||||
keys[i] = key
|
||||
i++
|
||||
}
|
||||
return vm.ToValue(keys)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func DataCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
// scope := args.Str(0)
|
||||
// taskDataLock.RLock()
|
||||
// defer taskDataLock.RUnlock()
|
||||
// if taskData[scope] != nil {
|
||||
// return vm.ToValue(len(taskData[scope]))
|
||||
// }
|
||||
// return vm.ToValue(0)
|
||||
// }
|
||||
|
||||
func DataCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
scope := args.Str(0)
|
||||
taskDataLock.RLock()
|
||||
defer taskDataLock.RUnlock()
|
||||
if taskData[scope] != nil {
|
||||
return vm.ToValue(len(taskData[scope]))
|
||||
}
|
||||
return vm.ToValue(0)
|
||||
}
|
||||
// func DataFetch(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
// scope := args.Str(0)
|
||||
// taskDataLock.RLock()
|
||||
// defer taskDataLock.RUnlock()
|
||||
// if taskData[scope] != nil {
|
||||
// all := make(map[string]any)
|
||||
// for k, v := range taskData[scope] {
|
||||
// all[k] = v
|
||||
// }
|
||||
// return vm.ToValue(all)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func DataFetch(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
scope := args.Str(0)
|
||||
taskDataLock.RLock()
|
||||
defer taskDataLock.RUnlock()
|
||||
if taskData[scope] != nil {
|
||||
all := make(map[string]any)
|
||||
for k, v := range taskData[scope] {
|
||||
all[k] = v
|
||||
}
|
||||
return vm.ToValue(all)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func DataRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
// scope := args.Str(0)
|
||||
// key := args.Str(1)
|
||||
// taskDataLock.Lock()
|
||||
// defer taskDataLock.Unlock()
|
||||
// if taskData[scope] != nil {
|
||||
// if key != "" {
|
||||
// delete(taskData[scope], key)
|
||||
// } else {
|
||||
// delete(taskData, scope)
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func DataRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
scope := args.Str(0)
|
||||
key := args.Str(1)
|
||||
taskDataLock.Lock()
|
||||
defer taskDataLock.Unlock()
|
||||
if taskData[scope] != nil {
|
||||
if key != "" {
|
||||
delete(taskData[scope], key)
|
||||
} else {
|
||||
delete(taskData, scope)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func ListPush(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(2)
|
||||
// scope := args.Str(0)
|
||||
// value := args.Any(1)
|
||||
// fromHead := args.Bool(2)
|
||||
// taskListLock.Lock()
|
||||
// defer taskListLock.Unlock()
|
||||
// list1 := taskList[scope]
|
||||
// if list1 == nil {
|
||||
// list1 = list.New()
|
||||
// taskList[scope] = list1
|
||||
// }
|
||||
// if fromHead {
|
||||
// list1.PushFront(value)
|
||||
// } else {
|
||||
// list1.PushBack(value)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func ListPush(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(2)
|
||||
scope := args.Str(0)
|
||||
value := args.Any(1)
|
||||
fromHead := args.Bool(2)
|
||||
taskListLock.Lock()
|
||||
defer taskListLock.Unlock()
|
||||
list1 := taskList[scope]
|
||||
if list1 == nil {
|
||||
list1 = list.New()
|
||||
taskList[scope] = list1
|
||||
}
|
||||
if fromHead {
|
||||
list1.PushFront(value)
|
||||
} else {
|
||||
list1.PushBack(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func ListPop(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
// scope := args.Str(0)
|
||||
// fromEnd := args.Bool(1)
|
||||
// taskListLock.Lock()
|
||||
// var item *list.Element
|
||||
// defer taskListLock.Unlock()
|
||||
// list1 := taskList[scope]
|
||||
// if list1 != nil {
|
||||
// if fromEnd {
|
||||
// item = list1.Front()
|
||||
// } else {
|
||||
// item = list1.Back()
|
||||
// }
|
||||
// if item != nil {
|
||||
// list1.Remove(item)
|
||||
// return vm.ToValue(item.Value)
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func ListPop(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
scope := args.Str(0)
|
||||
fromEnd := args.Bool(1)
|
||||
taskListLock.Lock()
|
||||
var item *list.Element
|
||||
defer taskListLock.Unlock()
|
||||
list1 := taskList[scope]
|
||||
if list1 != nil {
|
||||
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 {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
// scope := args.Str(0)
|
||||
// taskListLock.RLock()
|
||||
// defer taskListLock.RUnlock()
|
||||
// if taskList[scope] != nil {
|
||||
// return vm.ToValue(taskList[scope].Len())
|
||||
// }
|
||||
// return vm.ToValue(0)
|
||||
// }
|
||||
|
||||
func ListCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
scope := args.Str(0)
|
||||
taskListLock.RLock()
|
||||
defer taskListLock.RUnlock()
|
||||
if taskList[scope] != nil {
|
||||
return vm.ToValue(taskList[scope].Len())
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
// }
|
||||
|
@ -1,13 +1,12 @@
|
||||
import service from "apigo.cc/gojs/service"
|
||||
import co from "apigo.cc/gojs/console"
|
||||
import u from "apigo.cc/gojs/util"
|
||||
import rt from "apigo.cc/gojs/runtime"
|
||||
|
||||
function main() {
|
||||
service.register({ path: '/echo2', noLog200: true }, ({ args, response }) => {
|
||||
// setTimeout(() => {
|
||||
// response.end(args.name)
|
||||
// }, 10)
|
||||
u.sleep(10)
|
||||
rt.sleep(10)
|
||||
return args.name
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import s from "apigo.cc/gojs/service"
|
||||
import co from "apigo.cc/gojs/console"
|
||||
import task from "apigo.cc/gojs/task"
|
||||
|
||||
function main() {
|
||||
s.register({
|
||||
@ -9,11 +10,13 @@ function main() {
|
||||
},
|
||||
onClose: ({ client }) => {
|
||||
co.info('ws closed', client.id)
|
||||
s.dataRemove('wsTest', client.id)
|
||||
// s.dataRemove('wsTest', client.id)
|
||||
task.remove('wsTest_' + client.id)
|
||||
}
|
||||
}, ({ client }) => {
|
||||
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!')
|
||||
})
|
||||
}
|
||||
|
@ -2,12 +2,14 @@ package service_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"apigo.cc/gojs"
|
||||
_ "apigo.cc/gojs/console"
|
||||
_ "apigo.cc/gojs/http"
|
||||
_ "apigo.cc/gojs/runtime"
|
||||
"apigo.cc/gojs/service"
|
||||
_ "apigo.cc/gojs/service"
|
||||
_ "apigo.cc/gojs/util"
|
||||
@ -38,6 +40,7 @@ func TestStartByPool(t *testing.T) {
|
||||
|
||||
gojs.ExportForDev()
|
||||
rt2 = gojs.New()
|
||||
u.CopyFile("api/echo.js", "api/echo_tmp.js")
|
||||
err := rt2.StartFromFile("start.js")
|
||||
if err != nil {
|
||||
t.Fatal("start failed", err)
|
||||
@ -216,7 +219,7 @@ func TestStopByPool(t *testing.T) {
|
||||
t.Fatal("stop failed", err)
|
||||
}
|
||||
gojs.WaitAll()
|
||||
|
||||
os.Remove("api/echo_tmp.js")
|
||||
runtime.GC()
|
||||
ms3 := runtime.MemStats{}
|
||||
runtime.ReadMemStats(&ms3)
|
||||
|
@ -2,12 +2,16 @@ package service_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"apigo.cc/gojs"
|
||||
_ "apigo.cc/gojs/console"
|
||||
_ "apigo.cc/gojs/file"
|
||||
_ "apigo.cc/gojs/http"
|
||||
_ "apigo.cc/gojs/runtime"
|
||||
_ "apigo.cc/gojs/service"
|
||||
_ "apigo.cc/gojs/util"
|
||||
"github.com/ssgo/httpclient"
|
||||
@ -22,6 +26,7 @@ const runTimes = 100
|
||||
func TestStart(t *testing.T) {
|
||||
gojs.ExportForDev()
|
||||
rt = gojs.New()
|
||||
u.CopyFile("api/echo.js", "api/echo_tmp.js")
|
||||
err := rt.StartFromFile("start.js")
|
||||
if err != nil {
|
||||
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) {
|
||||
hc := httpclient.GetClientH2C(0)
|
||||
for i := 0; i < runTimes; i++ {
|
||||
@ -118,6 +136,19 @@ func TestGoAsyncEcho(t *testing.T) {
|
||||
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) {
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@ -125,6 +156,7 @@ func TestStop(t *testing.T) {
|
||||
}()
|
||||
gojs.WaitAll()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
os.Remove("api/echo_tmp.js")
|
||||
// if err != nil {
|
||||
// t.Fatal("stop failed", err)
|
||||
// }
|
||||
|
@ -6,7 +6,9 @@ import (
|
||||
"apigo.cc/gojs"
|
||||
_ "apigo.cc/gojs/console"
|
||||
_ "apigo.cc/gojs/http"
|
||||
_ "apigo.cc/gojs/runtime"
|
||||
_ "apigo.cc/gojs/service"
|
||||
_ "apigo.cc/gojs/task"
|
||||
_ "apigo.cc/gojs/util"
|
||||
"github.com/ssgo/u"
|
||||
|
||||
@ -43,7 +45,7 @@ func TestWS(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStopWSByPool(t *testing.T) {
|
||||
_, err := rt3.RunCode("s.stop()")
|
||||
_, err := rt3.RunCode("s.stop();task.stop();")
|
||||
if err != nil {
|
||||
t.Fatal("stop failed", err)
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import s from "apigo.cc/gojs/service"
|
||||
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 file from "apigo.cc/gojs/file"
|
||||
|
||||
let h2c = http
|
||||
let urlPrefix
|
||||
@ -33,16 +34,17 @@ function main() {
|
||||
},
|
||||
rewrite: {
|
||||
'/echo2.js': '/echo.js'
|
||||
}
|
||||
},
|
||||
hotLoad: 1,
|
||||
})
|
||||
s.register({ path: '/echo', noLog200: true }, ({ args, response }) => {
|
||||
// setTimeout(() => {
|
||||
// response.end(args.name)
|
||||
// }, 1)
|
||||
u.sleep(1)
|
||||
rt.sleep(1)
|
||||
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')
|
||||
let host = s.start()
|
||||
h2c = http.newH2C({
|
||||
@ -89,7 +91,7 @@ function testUser() {
|
||||
if (r.statusCode != 429) {
|
||||
return r
|
||||
}
|
||||
u.sleep(100)
|
||||
rt.sleep(100)
|
||||
|
||||
// 测试限流器过期后允许的 1 次请求
|
||||
r = h2c.get('/userInfo').object()
|
||||
|
@ -1,13 +1,16 @@
|
||||
import s from "apigo.cc/gojs/service"
|
||||
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 task from "apigo.cc/gojs/task"
|
||||
|
||||
let hc = http
|
||||
let urlPrefix
|
||||
function main() {
|
||||
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()
|
||||
hc = http.new({ baseURL: 'http://' + host })
|
||||
return host
|
||||
@ -31,7 +34,7 @@ function testWS() {
|
||||
co.info('test ws abc ok')
|
||||
|
||||
// ws.ping()
|
||||
u.sleep(10)
|
||||
rt.sleep(10)
|
||||
|
||||
let pc = ws.pingCount()
|
||||
co.info('test ws ping ok', pc.pingTimes, pc.pongTimes)
|
||||
@ -51,15 +54,13 @@ function testWS() {
|
||||
return r
|
||||
}
|
||||
co.info('test ws json ok')
|
||||
|
||||
u.sleep(1000)
|
||||
rt.sleep(2000)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let j = ws.read()
|
||||
if (i !== j.data) {
|
||||
return j
|
||||
}
|
||||
}
|
||||
|
||||
ws.close()
|
||||
return true
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import s from 'apigo.cc/gojs/service'
|
||||
import co from 'apigo.cc/gojs/console'
|
||||
import task from 'apigo.cc/gojs/task'
|
||||
|
||||
function onStart() {
|
||||
co.info('task start')
|
||||
@ -7,24 +8,27 @@ function onStart() {
|
||||
|
||||
let i = 0
|
||||
function onRun() {
|
||||
let connCount = s.dataCount('wsTest')
|
||||
if (connCount > 0) {
|
||||
let conns = s.dataFetch('wsTest')
|
||||
let keys = task.keys('wsTest_')
|
||||
// let connCount = s.dataCount('wsTest')
|
||||
if (keys.length > 0) {
|
||||
// let conns = s.dataFetch('wsTest')
|
||||
let conns = task.getAll('wsTest_')
|
||||
for (let id in conns) {
|
||||
let conn = conns[id]
|
||||
try {
|
||||
conn.write(i++)
|
||||
} catch (e) {
|
||||
co.error(e)
|
||||
s.dataRemove('wsTest', id)
|
||||
task.remove(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
co.info('task run', connCount)
|
||||
co.info('task run', keys.length)
|
||||
}
|
||||
|
||||
|
||||
function onStop() {
|
||||
s.dataRemove('wsTest')
|
||||
co.info('task stop', s.dataCount('wsTest'))
|
||||
// s.dataRemove('wsTest')
|
||||
task.removeAll('wsTest_')
|
||||
co.info('task stop')
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user