package gojs import ( "apigo.cloud/git/apigo/plugin" "apigo.cloud/git/apigo/qjs" "errors" "fmt" "github.com/ssgo/log" "github.com/ssgo/u" "regexp" "strings" ) var pluginNameMatcher = regexp.MustCompile(`(\w+?)\.`) type JSRuntime struct { freeJsValues []quickjs.Value rt quickjs.Runtime JsCtx *quickjs.Context GoCtx *plugin.Context logger *log.Logger plugins map[string]*plugin.Plugin } func (rt *JSRuntime) Close() { for _, v := range rt.freeJsValues { v.Free() } rt.freeJsValues = make([]quickjs.Value, 0) rt.JsCtx.Close() rt.rt.Close() } func (rt *JSRuntime) initCode(code string) { tryPlugins := map[string]bool{} for _, m := range pluginNameMatcher.FindAllStringSubmatch(code, 1024) { tryPlugins[m[1]] = true } for _, plg := range plugin.List() { if tryPlugins[plg.Id] && rt.plugins[plg.Id] == nil { rt.plugins[plg.Id] = &plg rt.JsCtx.Globals().Set(plg.Id, MakeJsValueForPlugin(rt.GoCtx, plg.Objects, plg.Id, false)) if plg.JsCode != "" { if result, err := rt.JsCtx.Eval(plg.JsCode); err != nil { stack := GetJSError(err, plg.JsCode) rt.logger.Error(err.Error(), "stack", stack) } else { result.Free() } } } } } func (rt *JSRuntime) run(code string) (out interface{}, err error, stack string) { if r, err := rt.JsCtx.Eval(code); err == nil { result := MakeFromJsValue(r) r.Free() return result, nil, "" } else { // 检查错误 stack := GetJSError(err, code) rt.logger.Error(err.Error(), "stack", stack) return nil, err, stack } } func (rt *JSRuntime) Exec(code string) (err error, stack string) { rt.initCode(code) _, err, stack = rt.run(code) return err, stack } func (rt *JSRuntime) Run(code string) (out interface{}, err error, stack string) { rt.initCode(code) return rt.run("(function(){" + code + "})()") } func SetPluginsConfig(conf map[string]plugin.Config) { for _, plg := range plugin.List() { if plg.Init != nil { plg.Init(conf[plg.Id]) } } } func New(globals map[string]interface{}, logger *log.Logger) *JSRuntime { if logger == nil { logger = log.DefaultLogger } // 初始化JS虚拟机 jsRt := quickjs.NewRuntime() jsCtx := jsRt.NewContext() goCtx := plugin.NewContext(map[string]interface{}{ "*log.Logger": logger, "*quickjs.Context": jsCtx, }) rt := &JSRuntime{ freeJsValues: make([]quickjs.Value, 0), rt: jsRt, JsCtx: jsCtx, GoCtx: goCtx, logger: logger, plugins: map[string]*plugin.Plugin{}, } rt.GoCtx.SetData("_freeJsValues", &rt.freeJsValues) // 全局变量 if globals != nil { for k, obj := range globals { rt.JsCtx.Globals().Set(k, MakeJsValue(rt.GoCtx, obj, false)) } } // 注入 console rt.JsCtx.Globals().Set("console", MakeJsValue(rt.GoCtx, map[string]interface{}{ "log": func(args ...interface{}) { fmt.Println(args...) }, "info": func(args ...interface{}) { fmt.Println(u.Cyan(fmt.Sprintln(args...))) }, "warn": func(args ...interface{}) { fmt.Println(u.BYellow(fmt.Sprintln(args...))) }, "error": func(args ...interface{}) { fmt.Println(u.BRed(fmt.Sprintln(args...))) }, }, false)) return rt } func Run(code string, globals map[string]interface{}, logger *log.Logger) (out interface{}, err error, stack string) { rt := New(globals, logger) defer func() { if err := recover(); err != nil { rt.logger.Error(u.String(err)) } rt.Close() }() return rt.Run(code) } var jsErrorCodeMatcher = regexp.MustCompile(`code:(\d+)`) func GetJSError(err error, code string) string { if err != nil { var jsErr *quickjs.Error if errors.As(err, &jsErr) { // 在错误信息中加入代码 codeLines := strings.Split(code, "\n") return jsErrorCodeMatcher.ReplaceAllStringFunc(jsErr.Stack, func(s2 string) string { errorLineNumber := u.Int(jsErrorCodeMatcher.FindStringSubmatch(s2)[1]) errorLine := "" if len(codeLines) >= errorLineNumber { errorLine = codeLines[errorLineNumber-1] } return s2 + " ```" + errorLine + "```" }) } else { return err.Error() } } else { return "" } }