package jsruntime import ( "crypto/md5" "encoding/base64" "fmt" "github.com/dop251/goja" "html" "io/ioutil" "log" "net/url" "path/filepath" "strconv" "strings" "sync" "time" ) type JsRuntime struct { MainSrc string UseSrcFun func() string ExtFileDir string Relys Relys TimeoutSec int64 runtime *goja.Runtime mutex sync.Mutex RenderCallBackFun func(string) IsDebugModel bool isRenderIng bool } type Rely struct { Src string } type Relys []Rely type RunMode int const ( ModeSync RunMode = 1 //ModeAsync RunMode = 2 ) func NewJsRuntime(mainScript string, extFileDir string, rels Relys, mode RunMode) (jr *JsRuntime, err error) { jr = &JsRuntime{ MainSrc: mainScript, Relys: rels, ExtFileDir: extFileDir, TimeoutSec: 30, } jr.runtime = goja.New() jr.EnableTimeoutFunc() jr.EnableIntervalFun() jr.EnableRequestFunc() jr.EnableConsoleFun() jr.SetVariable("Base64Decode", func(text string) string { code, _ := base64.StdEncoding.DecodeString(text) return string(code) }) jr.SetVariable("Base64Encode", func(text string) string { code := base64.StdEncoding.EncodeToString([]byte(text)) return code }) jr.SetVariable("GoReadFile", func(filePath string) string { code, err := ioutil.ReadFile(filepath.Join(jr.ExtFileDir, filePath)) if err != nil { jr.PrintError(fmt.Sprintln("GoRunCodeByFile Read Error:", err.Error()), string(code)) } return string(code) }) jr.SetVariable("GoRunCode", func(code string) { err = jr.RunCode(string(code)) if err != nil { jr.PrintError(fmt.Sprintln("GoRunCode Run Error:", err.Error()), code) } }) jr.SetVariable("GoRunCodeByFile", func(filePath string) { code, err := ioutil.ReadFile(filepath.Join(jr.ExtFileDir, filePath)) if err != nil { jr.PrintError(fmt.Sprintln("GoRunCodeByFile Read Error:", err.Error()), string(code)) } err = jr.RunCode(string(code)) if err != nil { jr.PrintError(fmt.Sprintln("GoRunCodeByFile Run Error:", err.Error()), string(code)) } }) err = jr.InitJsCode() if err != nil { log.Println("InitJsCode", err) } return jr, nil } func (jr *JsRuntime) PrintError(msg string, code string) { log.Println(msg) if !jr.IsDebugModel { return } var errLine string var line = -1 evalErr := strings.Split(msg, "") if len(evalErr) > 1 { evalErr = strings.Split(evalErr[1], ":") if len(evalErr) > 1 { line, _ = strconv.Atoi(evalErr[1]) } } if line == -1 { evalErr = strings.Split(msg, ": Line ") if len(evalErr) > 1 { evalErr = strings.Split(evalErr[1], ":") if len(evalErr) > 0 { line, _ = strconv.Atoi(evalErr[0]) } } } //log.Println("error line", line,evalErr) if line > -1 { lines := strings.Split(string(code), "\n") linesStart := line - 15 linesEnd := line + 15 if linesStart < 0 { linesStart = 0 } if len(lines)-1 < linesEnd { linesEnd = len(lines) } if linesStart > linesEnd { errLine = "无法定位的报错位置" linesStart = 0 } else { // //log.Println("error line linesStart", linesStart) //log.Println("error line linesEnd", linesEnd) //linesEnd = linesStart + linesEnd for e := range lines[linesStart:linesEnd] { if e+linesStart+1 == line { lines[linesStart:linesEnd][e] = fmt.Sprintf(`%2d: >>>>>> %s`, e+linesStart, lines[linesStart:linesEnd][e]) } else { lines[linesStart:linesEnd][e] = fmt.Sprintf(`%2d: %s`, e+linesStart, lines[linesStart:linesEnd][e]) } } errLine = strings.Join(lines[linesStart:linesEnd], "\n") } } if jr.RenderCallBackFun != nil { jr.RenderCallBackFun(fmt.Sprintf(`

%s

%s
		
`, html.EscapeString(msg), html.EscapeString(errLine))) jr.RenderCallBackFun = func(s string) { } } } func (jr *JsRuntime) InitJsCode() error { for e := range jr.Relys { //start := time.Now().UnixNano() err := jr.RunCode(jr.Relys[e].Src) //end := time.Now().UnixNano() //log.Println(e, "加载时间:", end-start) if err != nil { return err } } return nil } func (jr *JsRuntime) SetVariable(name string, value interface{}) { jr.runtime.Set(name, value) } var codeMap sync.Map func (jr *JsRuntime) md5(data string) string { return fmt.Sprintf("%x", md5.Sum([]byte(data))) } func (jr *JsRuntime) RunCode(code string) error { id := jr.md5(code) var p interface{} var ok bool p, ok = codeMap.Load(id) if !ok { pro, err := goja.Compile("", code, false) if err != nil { log.Println("Compile Error") return err } codeMap.Store(id, pro) p = pro } program := p.(*goja.Program) _, err := jr.runtime.RunProgram(program) //log.Println("RunCode result", result) return err } func (jr *JsRuntime) Render(filePath, href, tplSrc string, GoExtData interface{}, cb func(data string)) (err error) { jr.RenderCallBackFun = cb jr.mutex.Lock() defer jr.mutex.Unlock() runtime := jr.runtime mainSrc := jr.MainSrc isLock := true jr.isRenderIng = true //timeoutSec := jr.TimeoutSec defer func() { jr.isRenderIng = false isLock = false jr.runtime.ClearInterrupt() }() var useSrc string if jr.UseSrcFun != nil { useSrc = jr.UseSrcFun() } if useSrc != "" { err = jr.RunCode(useSrc) if err != nil { return err } } url, err := url.Parse(href) if err != nil { return err } url2, err := url.Parse(strings.Replace(filePath, `\`, "/", -1)) if err != nil { return err } runtime.Set("GoExtData", GoExtData) runtime.Set("GoHtmlSrc", tplSrc) runtime.Set("GoHref", href) runtime.Set("GoQuery", url.Query().Encode()) runtime.Set("GoPath", url2.Path) runtime.Set("GoReturn", func(data string) { jr.RenderCallBackFun(data) }) t := time.AfterFunc(time.Duration(jr.TimeoutSec)*time.Second, func() { if jr.isRenderIng { jr.runtime.Interrupt("time out") jr.runtime.ClearInterrupt() } }) defer t.Stop() err = jr.RunCode(mainSrc) if err != nil { jr.PrintError(err.Error(), "") return err } return }