update id
add tpl support reload on watched file changes
This commit is contained in:
parent
d416c61a6a
commit
7bb6938cb7
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,8 +2,6 @@
|
|||||||
!.gitignore
|
!.gitignore
|
||||||
go.sum
|
go.sum
|
||||||
/build
|
/build
|
||||||
/node_modules
|
|
||||||
/package.json
|
|
||||||
/bak
|
/bak
|
||||||
node_modules
|
node_modules
|
||||||
package.json
|
package.json
|
||||||
|
24
go.mod
24
go.mod
@ -3,29 +3,32 @@ module apigo.cc/gojs/service
|
|||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/gojs v0.0.4
|
apigo.cc/gojs v0.0.7
|
||||||
apigo.cc/gojs/console v0.0.1
|
apigo.cc/gojs/console v0.0.2
|
||||||
apigo.cc/gojs/http v0.0.3
|
apigo.cc/gojs/http v0.0.3
|
||||||
apigo.cc/gojs/util v0.0.3
|
apigo.cc/gojs/util v0.0.7
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/ssgo/config v1.7.8
|
github.com/ssgo/config v1.7.9
|
||||||
github.com/ssgo/discover v1.7.9
|
github.com/ssgo/discover v1.7.9
|
||||||
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.7
|
||||||
github.com/ssgo/redis v1.7.7
|
github.com/ssgo/redis v1.7.7
|
||||||
github.com/ssgo/s v1.7.18
|
github.com/ssgo/s v1.7.20
|
||||||
github.com/ssgo/standard v1.7.7
|
github.com/ssgo/standard v1.7.7
|
||||||
github.com/ssgo/u v1.7.9
|
github.com/ssgo/u v1.7.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/ZZMarquis/gm v1.3.2 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/emmansun/gmsm v0.29.4 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.8.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-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-20230207041349-798e818bf904 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // 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/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
|
||||||
@ -33,8 +36,9 @@ require (
|
|||||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
golang.org/x/crypto v0.29.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/net v0.31.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/sys v0.27.0 // indirect
|
||||||
|
golang.org/x/text v0.20.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
432
service.go
432
service.go
@ -1,15 +1,19 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"apigo.cc/gojs"
|
"apigo.cc/gojs"
|
||||||
@ -39,6 +43,16 @@ var poolActionRegistered = map[string]bool{}
|
|||||||
var poolsLock = sync.RWMutex{}
|
var poolsLock = sync.RWMutex{}
|
||||||
var waitChan chan bool
|
var waitChan chan bool
|
||||||
|
|
||||||
|
type TplCache struct {
|
||||||
|
FileModTime map[string]int64
|
||||||
|
Tpl *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
var tplFunc = map[string]any{}
|
||||||
|
var tplFuncLock = sync.RWMutex{}
|
||||||
|
var tplCache = map[string]*TplCache{}
|
||||||
|
var tplCacheLock = sync.RWMutex{}
|
||||||
|
|
||||||
type LimiterConfig struct {
|
type LimiterConfig struct {
|
||||||
From string
|
From string
|
||||||
Time int
|
Time int
|
||||||
@ -69,112 +83,117 @@ var onStop goja.Callable
|
|||||||
var limiters = map[string]*s.Limiter{}
|
var limiters = map[string]*s.Limiter{}
|
||||||
var configed = false
|
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 {
|
||||||
|
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 {
|
||||||
|
panic(vm.NewGoError(errs[0]))
|
||||||
|
}
|
||||||
|
config.LoadConfig("service", &discover.Config)
|
||||||
|
// var auth goja.Callable
|
||||||
|
if opt != nil {
|
||||||
|
u.Convert(opt.O.Export(), &serviceConfig)
|
||||||
|
u.Convert(opt.O.Export(), &s.Config)
|
||||||
|
u.Convert(opt.O.Export(), &discover.Config)
|
||||||
|
// auth = conf.Func("auth")
|
||||||
|
onStop = opt.Func("onStop")
|
||||||
|
|
||||||
|
if serviceConfig.SessionProvider != "" {
|
||||||
|
sessionRedis = redis.GetRedis(serviceConfig.SessionProvider, logger)
|
||||||
|
}
|
||||||
|
sessionTimeout = serviceConfig.SessionTimeout
|
||||||
|
if sessionTimeout < 0 {
|
||||||
|
sessionTimeout = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 身份验证和Session
|
||||||
|
authAccessToken := len(s.Config.AccessTokens) > 0
|
||||||
|
s.SetClientKeys(serviceConfig.DeviceKey, serviceConfig.ClientKey, serviceConfig.SessionKey)
|
||||||
|
if serviceConfig.SessionKey != "" {
|
||||||
|
s.SetAuthChecker(func(authLevel int, logger *log.Logger, url *string, args map[string]any, request *s.Request, response *s.Response, options *s.WebServiceOptions) (pass bool, object any) {
|
||||||
|
var session *Session
|
||||||
|
setAuthLevel := 0
|
||||||
|
if serviceConfig.SessionKey != "" {
|
||||||
|
sessionID := request.GetSessionId()
|
||||||
|
|
||||||
|
if sessionID != "" {
|
||||||
|
session = NewSession(sessionID, logger)
|
||||||
|
}
|
||||||
|
if userId, ok := session.data[serviceConfig.UserIdKey]; ok {
|
||||||
|
request.SetUserId(u.String(userId))
|
||||||
|
}
|
||||||
|
// 优先使用session中的authLevel
|
||||||
|
if authLevel > 0 {
|
||||||
|
if authLevelBySession, ok := session.data["_authLevel"]; ok {
|
||||||
|
setAuthLevel = u.Int(authLevelBySession)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果没有session中的authLevel验证失败,则使用Access-Token中的authLevel(服务间调用)
|
||||||
|
if authAccessToken && setAuthLevel < authLevel {
|
||||||
|
setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token"))
|
||||||
|
}
|
||||||
|
if setAuthLevel >= authLevel {
|
||||||
|
return true, session
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
s.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限流器
|
||||||
|
if serviceConfig.Limiters != nil {
|
||||||
|
var limiterRedis *redis.Redis
|
||||||
|
if serviceConfig.LimiterRedis != "" {
|
||||||
|
limiterRedis = redis.GetRedis(serviceConfig.LimiterRedis, logger)
|
||||||
|
}
|
||||||
|
for name, limiter := range serviceConfig.Limiters {
|
||||||
|
switch limiter.From {
|
||||||
|
case "ip":
|
||||||
|
limiter.From = "header." + standard.DiscoverHeaderClientIp
|
||||||
|
case "user":
|
||||||
|
limiter.From = "header." + standard.DiscoverHeaderUserId
|
||||||
|
case "device":
|
||||||
|
limiter.From = "header." + standard.DiscoverHeaderDeviceId
|
||||||
|
}
|
||||||
|
if limiterRedis != nil {
|
||||||
|
limiters[name] = s.NewLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times, limiterRedis)
|
||||||
|
} else {
|
||||||
|
limiters[name] = s.NewLocalLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
s.Config.KeepKeyCase = true
|
s.Config.KeepKeyCase = true
|
||||||
obj := map[string]any{
|
obj := map[string]any{
|
||||||
"config": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
"config": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||||
configed = true
|
|
||||||
s.InitConfig()
|
|
||||||
if startPath, ok := vm.GoData["startPath"]; ok {
|
|
||||||
s.SetWorkPath(u.String(startPath))
|
|
||||||
}
|
|
||||||
// 处理配置
|
|
||||||
args := gojs.MakeArgs(&argsIn, vm)
|
args := gojs.MakeArgs(&argsIn, vm)
|
||||||
serviceConfig = Config{"Session", "Device", "Client", "userId", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", map[string]string{}, map[string]string{}, map[string]string{}}
|
initConfig(args.Obj(0), args.Logger, vm)
|
||||||
if errs := config.LoadConfig("service", &serviceConfig); errs != nil && len(errs) > 0 {
|
|
||||||
panic(vm.NewGoError(errs[0]))
|
|
||||||
}
|
|
||||||
config.LoadConfig("service", &discover.Config)
|
|
||||||
// var auth goja.Callable
|
|
||||||
if conf := args.Obj(0); conf != nil {
|
|
||||||
u.Convert(conf.O.Export(), &serviceConfig)
|
|
||||||
u.Convert(conf.O.Export(), &s.Config)
|
|
||||||
u.Convert(conf.O.Export(), &discover.Config)
|
|
||||||
// auth = conf.Func("auth")
|
|
||||||
onStop = conf.Func("onStop")
|
|
||||||
|
|
||||||
if serviceConfig.SessionProvider != "" {
|
|
||||||
sessionRedis = redis.GetRedis(serviceConfig.SessionProvider, args.Logger)
|
|
||||||
}
|
|
||||||
sessionTimeout = serviceConfig.SessionTimeout
|
|
||||||
if sessionTimeout < 0 {
|
|
||||||
sessionTimeout = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 身份验证和Session
|
|
||||||
authAccessToken := len(s.Config.AccessTokens) > 0
|
|
||||||
s.SetClientKeys(serviceConfig.DeviceKey, serviceConfig.ClientKey, serviceConfig.SessionKey)
|
|
||||||
if serviceConfig.SessionKey != "" {
|
|
||||||
s.SetAuthChecker(func(authLevel int, logger *log.Logger, url *string, args map[string]any, request *s.Request, response *s.Response, options *s.WebServiceOptions) (pass bool, object any) {
|
|
||||||
var session *Session
|
|
||||||
setAuthLevel := 0
|
|
||||||
if serviceConfig.SessionKey != "" {
|
|
||||||
sessionID := request.GetSessionId()
|
|
||||||
|
|
||||||
if sessionID != "" {
|
|
||||||
session = NewSession(sessionID, logger)
|
|
||||||
}
|
|
||||||
if userId, ok := session.data[serviceConfig.UserIdKey]; ok {
|
|
||||||
request.SetUserId(u.String(userId))
|
|
||||||
}
|
|
||||||
// 优先使用session中的authLevel
|
|
||||||
if authLevel > 0 {
|
|
||||||
if authLevelBySession, ok := session.data["_authLevel"]; ok {
|
|
||||||
setAuthLevel = u.Int(authLevelBySession)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 如果没有session中的authLevel验证失败,则使用Access-Token中的authLevel(服务间调用)
|
|
||||||
if authAccessToken && setAuthLevel < authLevel {
|
|
||||||
setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token"))
|
|
||||||
}
|
|
||||||
if setAuthLevel >= authLevel {
|
|
||||||
return true, session
|
|
||||||
} 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
s.Init()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 限流器
|
|
||||||
if serviceConfig.Limiters != nil {
|
|
||||||
var limiterRedis *redis.Redis
|
|
||||||
if serviceConfig.LimiterRedis != "" {
|
|
||||||
limiterRedis = redis.GetRedis(serviceConfig.LimiterRedis, args.Logger)
|
|
||||||
}
|
|
||||||
for name, limiter := range serviceConfig.Limiters {
|
|
||||||
switch limiter.From {
|
|
||||||
case "ip":
|
|
||||||
limiter.From = "header." + standard.DiscoverHeaderClientIp
|
|
||||||
case "user":
|
|
||||||
limiter.From = "header." + standard.DiscoverHeaderUserId
|
|
||||||
case "device":
|
|
||||||
limiter.From = "header." + standard.DiscoverHeaderDeviceId
|
|
||||||
}
|
|
||||||
if limiterRedis != nil {
|
|
||||||
limiters[name] = s.NewLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times, limiterRedis)
|
|
||||||
} else {
|
|
||||||
limiters[name] = s.NewLocalLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
"start": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
"start": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||||
if !configed {
|
if !configed {
|
||||||
panic(vm.NewGoError(errors.New("must run service.config frist")))
|
initConfig(nil, gojs.GetLogger(vm), vm)
|
||||||
|
// panic(vm.NewGoError(errors.New("must run service.config frist")))
|
||||||
}
|
}
|
||||||
if server != nil {
|
if server != nil {
|
||||||
panic(vm.NewGoError(errors.New("server already started")))
|
panic(vm.NewGoError(errors.New("server already started")))
|
||||||
@ -192,6 +211,55 @@ func init() {
|
|||||||
s.SetProxyBy(proxy)
|
s.SetProxyBy(proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理Watch
|
||||||
|
if vm.GoData["inWatch"] == true {
|
||||||
|
onWatchConn := map[string]*websocket.Conn{}
|
||||||
|
onWatchLock := sync.RWMutex{}
|
||||||
|
vm.GoData["onWatch"] = func(filename string) {
|
||||||
|
conns := map[string]*websocket.Conn{}
|
||||||
|
onWatchLock.RLock()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
s.RegisterSimpleWebsocket(0, "/_watch", func(request *s.Request, conn *websocket.Conn) {
|
||||||
|
onWatchLock.Lock()
|
||||||
|
onWatchConn[request.Id] = conn
|
||||||
|
onWatchLock.Unlock()
|
||||||
|
}, "")
|
||||||
|
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)
|
||||||
|
// 注入自动刷新的代码
|
||||||
|
outStr = strings.ReplaceAll(outStr, "</html>", `<script>
|
||||||
|
function connect() {
|
||||||
|
let ws = new WebSocket(location.protocol.replace('http', 'ws') + '//' + location.host + '/_watch')
|
||||||
|
ws.onmessage = () => { location.reload() }
|
||||||
|
ws.onclose = () => { setTimeout(connect, 1000) }
|
||||||
|
}
|
||||||
|
connect()
|
||||||
|
</script>
|
||||||
|
</html>`)
|
||||||
|
return []byte(outStr), false
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 启动服务
|
// 启动服务
|
||||||
server = s.AsyncStart()
|
server = s.AsyncStart()
|
||||||
waitChan = make(chan bool, 1)
|
waitChan = make(chan bool, 1)
|
||||||
@ -203,6 +271,8 @@ func init() {
|
|||||||
server.OnStopped(func() {
|
server.OnStopped(func() {
|
||||||
ClearRewritesAndProxies()
|
ClearRewritesAndProxies()
|
||||||
pools = map[string]*gojs.Pool{}
|
pools = map[string]*gojs.Pool{}
|
||||||
|
poolExists = map[string]bool{}
|
||||||
|
poolActionRegistered = map[string]bool{}
|
||||||
server = nil
|
server = nil
|
||||||
if waitChan != nil {
|
if waitChan != nil {
|
||||||
waitChan <- true
|
waitChan <- true
|
||||||
@ -217,6 +287,72 @@ func init() {
|
|||||||
server.Stop()
|
server.Stop()
|
||||||
return nil
|
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))
|
||||||
|
},
|
||||||
|
"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)
|
||||||
@ -391,6 +527,100 @@ func init() {
|
|||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
"setTplFunc": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||||
|
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||||
|
fnObj := args.Obj(0)
|
||||||
|
if fnObj != nil {
|
||||||
|
fnList := map[string]any{}
|
||||||
|
for _, k := range fnObj.O.Keys() {
|
||||||
|
if jsFunc := fnObj.Func(k); jsFunc != nil {
|
||||||
|
fn := func(args ...any) any {
|
||||||
|
jsArgs := make([]goja.Value, len(args))
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
jsArgs[i] = vm.ToValue(args[i])
|
||||||
|
}
|
||||||
|
if r, err := jsFunc(argsIn.This, jsArgs...); err == nil {
|
||||||
|
return r.Export()
|
||||||
|
} else {
|
||||||
|
panic(vm.NewGoError(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fnList[k] = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(fnList) > 0 {
|
||||||
|
tplFuncLock.Lock()
|
||||||
|
for k, v := range fnList {
|
||||||
|
tplFunc[k] = v
|
||||||
|
}
|
||||||
|
tplFuncLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
"tpl": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||||
|
args := gojs.MakeArgs(&argsIn, vm).Check(2)
|
||||||
|
filename := args.Path(0)
|
||||||
|
info := u.GetFileInfo(filename)
|
||||||
|
if info == nil {
|
||||||
|
panic(vm.NewGoError(errors.New("tpl file " + filename + " not exists")))
|
||||||
|
}
|
||||||
|
|
||||||
|
data := args.Any(1)
|
||||||
|
tplCacheLock.RLock()
|
||||||
|
t := tplCache[filename]
|
||||||
|
tplCacheLock.RUnlock()
|
||||||
|
if t != nil {
|
||||||
|
for f, tm := range t.FileModTime {
|
||||||
|
info := u.GetFileInfo(f)
|
||||||
|
if info == nil || info.ModTime.UnixMilli() != tm {
|
||||||
|
t = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t == nil {
|
||||||
|
tpl := template.New("main")
|
||||||
|
if len(tplFunc) > 0 {
|
||||||
|
tpl = tpl.Funcs(tplFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileModTime := map[string]int64{
|
||||||
|
filename: info.ModTime.UnixMilli(),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
for _, m := range tplIncludeMatcher.FindAllStringSubmatch(u.ReadFileN(filename), -1) {
|
||||||
|
includeFilename := m[1]
|
||||||
|
info2 := u.GetFileInfo(includeFilename)
|
||||||
|
if info2 == nil {
|
||||||
|
includeFilename = filepath.Join(filepath.Dir(filename), m[1])
|
||||||
|
info2 = u.GetFileInfo(includeFilename)
|
||||||
|
}
|
||||||
|
if info2 != nil {
|
||||||
|
tpl, err = tpl.Parse(`{{ define "` + m[1] + `" }}` + u.ReadFileN(includeFilename) + `{{ end }}`)
|
||||||
|
if err != nil {
|
||||||
|
panic(vm.NewGoError(err))
|
||||||
|
}
|
||||||
|
fileModTime[includeFilename] = info2.ModTime.UnixMilli()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tpl, err = tpl.ParseFiles(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(vm.NewGoError(err))
|
||||||
|
}
|
||||||
|
t = &TplCache{
|
||||||
|
Tpl: tpl,
|
||||||
|
FileModTime: fileModTime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0))
|
||||||
|
err := t.Tpl.ExecuteTemplate(buf, filepath.Base(filename), data)
|
||||||
|
if err != nil {
|
||||||
|
panic(vm.NewGoError(err))
|
||||||
|
}
|
||||||
|
return vm.ToValue(buf.String())
|
||||||
|
},
|
||||||
"dataSet": DataSet,
|
"dataSet": DataSet,
|
||||||
"dataGet": DataGet,
|
"dataGet": DataGet,
|
||||||
"dataKeys": DataKeys,
|
"dataKeys": DataKeys,
|
||||||
@ -413,6 +643,12 @@ func init() {
|
|||||||
server.Stop()
|
server.Stop()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
OnSignal: func(sig os.Signal) {
|
||||||
|
switch sig {
|
||||||
|
case syscall.SIGUSR1:
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
WaitForStop: func() {
|
WaitForStop: func() {
|
||||||
if waitChan != nil {
|
if waitChan != nil {
|
||||||
<-waitChan
|
<-waitChan
|
||||||
@ -421,6 +657,8 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tplIncludeMatcher = regexp.MustCompile(`{{\s*template\s+"([^"]+)"`)
|
||||||
|
|
||||||
func verifyRegexp(regexpStr string) func(any, *goja.Runtime) bool {
|
func verifyRegexp(regexpStr string) func(any, *goja.Runtime) bool {
|
||||||
if rx, err := regexp.Compile(regexpStr); err != nil {
|
if rx, err := regexp.Compile(regexpStr); err != nil {
|
||||||
return func(value any, vm *goja.Runtime) bool {
|
return func(value any, vm *goja.Runtime) bool {
|
||||||
|
13
service.ts
13
service.ts
@ -18,6 +18,12 @@ export default {
|
|||||||
listPop,
|
listPop,
|
||||||
listCount,
|
listCount,
|
||||||
listRemove,
|
listRemove,
|
||||||
|
id,
|
||||||
|
idL,
|
||||||
|
uniqueId,
|
||||||
|
uniqueIdL,
|
||||||
|
setTplFunc,
|
||||||
|
tpl,
|
||||||
}
|
}
|
||||||
|
|
||||||
function config(config?: Config): void { }
|
function config(config?: Config): void { }
|
||||||
@ -41,7 +47,12 @@ 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 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 '' }
|
||||||
|
|
||||||
interface Config {
|
interface Config {
|
||||||
// github.com/ssgo/s 的配置参数
|
// github.com/ssgo/s 的配置参数
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package service_test
|
package service_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
_ "apigo.cc/gojs/http"
|
_ "apigo.cc/gojs/http"
|
||||||
_ "apigo.cc/gojs/service"
|
_ "apigo.cc/gojs/service"
|
||||||
_ "apigo.cc/gojs/util"
|
_ "apigo.cc/gojs/util"
|
||||||
|
"github.com/ssgo/httpclient"
|
||||||
"github.com/ssgo/u"
|
"github.com/ssgo/u"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,80 +43,80 @@ func TestStatic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func TestJsEcho(t *testing.T) {
|
func TestJsEcho(t *testing.T) {
|
||||||
// for i := 0; i < runTimes; i++ {
|
for i := 0; i < runTimes; i++ {
|
||||||
// name := u.UniqueId()
|
name := u.UniqueId()
|
||||||
// r, err := rt.RunCode("test('" + name + "')")
|
r, err := rt.RunCode("test('" + name + "')")
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// t.Fatal("test js get failed, got error", err)
|
t.Fatal("test js get failed, got error", err)
|
||||||
// } else if r != name {
|
} else if r != name {
|
||||||
// t.Fatal("test js get failed, name not match", r, name)
|
t.Fatal("test js get failed, name not match", r, name)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// 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++ {
|
||||||
// name := u.UniqueId()
|
name := u.UniqueId()
|
||||||
// r := hc.Get("http://" + addr + "/echo?name=" + name)
|
r := hc.Get("http://" + addr + "/echo?name=" + name)
|
||||||
// if r.Error != nil {
|
if r.Error != nil {
|
||||||
// t.Fatal("test go get failed, got error", r.Error)
|
t.Fatal("test go get failed, got error", r.Error)
|
||||||
// } else if r.String() != name {
|
} else if r.String() != name {
|
||||||
// t.Fatal("test go get failed, name not match", r, name)
|
t.Fatal("test go get failed, name not match", r, name)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// func TestJsAsyncEcho(t *testing.T) {
|
func TestJsAsyncEcho(t *testing.T) {
|
||||||
// ch := make(chan bool, runTimes)
|
ch := make(chan bool, runTimes)
|
||||||
// t1 := time.Now().UnixMilli()
|
t1 := time.Now().UnixMilli()
|
||||||
// for i := 0; i < runTimes; i++ {
|
for i := 0; i < runTimes; i++ {
|
||||||
// go func() {
|
go func() {
|
||||||
// name := u.UniqueId()
|
name := u.UniqueId()
|
||||||
// r, err := rt.RunCode("test('" + name + "')")
|
r, err := rt.RunCode("test('" + name + "')")
|
||||||
// ch <- true
|
ch <- true
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// t.Fatal("test js async get failed, got error", err)
|
t.Fatal("test js async get failed, got error", err)
|
||||||
// } else if r != name {
|
} else if r != name {
|
||||||
// t.Fatal("test js async get failed, name not match", r, name)
|
t.Fatal("test js async get failed, name not match", r, name)
|
||||||
// }
|
}
|
||||||
// }()
|
}()
|
||||||
// }
|
}
|
||||||
// for i := 0; i < runTimes; i++ {
|
for i := 0; i < runTimes; i++ {
|
||||||
// <-ch
|
<-ch
|
||||||
// }
|
}
|
||||||
// t2 := time.Now().UnixMilli() - t1
|
t2 := time.Now().UnixMilli() - t1
|
||||||
// fmt.Println(u.BGreen("js async test time:"), t2, "ms")
|
fmt.Println(u.BGreen("js async test time:"), t2, "ms")
|
||||||
// }
|
}
|
||||||
|
|
||||||
// func TestGoAsyncEcho(t *testing.T) {
|
func TestGoAsyncEcho(t *testing.T) {
|
||||||
// hc := httpclient.GetClientH2C(0)
|
hc := httpclient.GetClientH2C(0)
|
||||||
// ch := make(chan bool, runTimes*10)
|
ch := make(chan bool, runTimes*10)
|
||||||
// t1 := time.Now().UnixMilli()
|
t1 := time.Now().UnixMilli()
|
||||||
// lastName := ""
|
lastName := ""
|
||||||
// lastResult := ""
|
lastResult := ""
|
||||||
// for i := 0; i < runTimes*10; i++ {
|
for i := 0; i < runTimes*10; i++ {
|
||||||
// name := fmt.Sprint("N", i)
|
name := fmt.Sprint("N", i)
|
||||||
// lastName = name
|
lastName = name
|
||||||
// go func() {
|
go func() {
|
||||||
// r := hc.Get("http://" + addr + "/echo?name=" + name)
|
r := hc.Get("http://" + addr + "/echo?name=" + name)
|
||||||
// lastResult = r.String()
|
lastResult = r.String()
|
||||||
// ch <- true
|
ch <- true
|
||||||
// if r.Error != nil {
|
if r.Error != nil {
|
||||||
// t.Fatal("test go async get failed, got error", r.Error)
|
t.Fatal("test go async get failed, got error", r.Error)
|
||||||
// } else if r.String() != name {
|
} else if r.String() != name {
|
||||||
// t.Fatal("test go async get failed, name not match", r, name)
|
t.Fatal("test go async get failed, name not match", r, name)
|
||||||
// }
|
}
|
||||||
// }()
|
}()
|
||||||
// }
|
}
|
||||||
// for i := 0; i < runTimes*10; i++ {
|
for i := 0; i < runTimes*10; i++ {
|
||||||
// <-ch
|
<-ch
|
||||||
// }
|
}
|
||||||
// t2 := time.Now().UnixMilli() - t1
|
t2 := time.Now().UnixMilli() - t1
|
||||||
// fmt.Println(u.BGreen("go async test time:"), t2, "ms")
|
fmt.Println(u.BGreen("go async test time:"), t2, "ms")
|
||||||
// fmt.Println(u.BGreen("last name:"), lastName, lastResult)
|
fmt.Println(u.BGreen("last name:"), lastName, lastResult)
|
||||||
// }
|
}
|
||||||
|
|
||||||
func TestStop(t *testing.T) {
|
func TestStop(t *testing.T) {
|
||||||
go func() {
|
go func() {
|
||||||
|
8
tests/tpl.js
Normal file
8
tests/tpl.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import s from "apigo.cc/gojs/service"
|
||||||
|
|
||||||
|
function main(args) {
|
||||||
|
s.setTplFunc({
|
||||||
|
bb: text => { return '<b>' + text + '</b>' }
|
||||||
|
})
|
||||||
|
return s.tpl('tpl/page.html', { title: 'Abc' })
|
||||||
|
}
|
3
tests/tpl/header.html
Normal file
3
tests/tpl/header.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<header>
|
||||||
|
<h1>Welcome to {{bb .title}}</h1>
|
||||||
|
</header>
|
18
tests/tpl/page.html
Normal file
18
tests/tpl/page.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>{{.title}}</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{template "header.html" .}}
|
||||||
|
<pre>
|
||||||
|
hello world
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
20
tests/tpl_test.go
Normal file
20
tests/tpl_test.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package service_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"apigo.cc/gojs"
|
||||||
|
_ "apigo.cc/gojs/service"
|
||||||
|
"github.com/ssgo/u"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTpl(t *testing.T) {
|
||||||
|
r, err := gojs.RunFile("tpl.js")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("test static failed, got error", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(u.String(r), "<h1>Welcome to <b>Abc</b></h1>") {
|
||||||
|
t.Fatal("test tpl failed, name not match", r)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user