You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

277 lines
5.7 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. package jsruntime
  2. import (
  3. "crypto/md5"
  4. "fmt"
  5. "github.com/dop251/goja"
  6. "html"
  7. "io/ioutil"
  8. "log"
  9. "net/url"
  10. "path/filepath"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. )
  16. type JsRuntime struct {
  17. MainSrc string
  18. UseSrcFun func() string
  19. ExtFileDir string
  20. Relys Relys
  21. TimeoutSec int64
  22. runtime *goja.Runtime
  23. mutex sync.Mutex
  24. RenderCallBackFun func(string)
  25. IsDebugModel bool
  26. }
  27. type Rely struct {
  28. Src string
  29. }
  30. type Relys []Rely
  31. type RunMode int
  32. const (
  33. ModeSync RunMode = 1
  34. //ModeAsync RunMode = 2
  35. )
  36. func NewJsRuntime(mainScript string, extFileDir string, rels Relys, mode RunMode) (jr *JsRuntime, err error) {
  37. jr = &JsRuntime{
  38. MainSrc: mainScript,
  39. Relys: rels,
  40. ExtFileDir: extFileDir,
  41. TimeoutSec: 30,
  42. }
  43. jr.runtime = goja.New()
  44. jr.EnableTimeoutFunc()
  45. jr.EnableIntervalFun()
  46. jr.EnableRequestFunc()
  47. jr.EnableConsoleFun()
  48. jr.SetVariable("GoReadFile", func(filePath string) string {
  49. code, err := ioutil.ReadFile(filepath.Join(jr.ExtFileDir, filePath))
  50. if err != nil {
  51. jr.PrintError(fmt.Sprintln("GoRunCodeByFile Read Error:", err.Error()), string(code))
  52. }
  53. return string(code)
  54. })
  55. jr.SetVariable("GoRunCode", func(code string) {
  56. err = jr.RunCode(string(code))
  57. if err != nil {
  58. jr.PrintError(fmt.Sprintln("GoRunCode Run Error:", err.Error()), code)
  59. }
  60. })
  61. jr.SetVariable("GoRunCodeByFile", func(filePath string) {
  62. code, err := ioutil.ReadFile(filepath.Join(jr.ExtFileDir, filePath))
  63. if err != nil {
  64. jr.PrintError(fmt.Sprintln("GoRunCodeByFile Read Error:", err.Error()), string(code))
  65. }
  66. err = jr.RunCode(string(code))
  67. if err != nil {
  68. jr.PrintError(fmt.Sprintln("GoRunCodeByFile Run Error:", err.Error()), string(code))
  69. }
  70. })
  71. err = jr.InitJsCode()
  72. if err != nil {
  73. log.Println("InitJsCode", err)
  74. }
  75. return jr, nil
  76. }
  77. func (jr *JsRuntime) PrintError(msg string, code string) {
  78. log.Println(msg)
  79. if !jr.IsDebugModel {
  80. return
  81. }
  82. var errLine string
  83. var line = -1
  84. evalErr := strings.Split(msg, "<eval>")
  85. if len(evalErr) > 1 {
  86. evalErr = strings.Split(evalErr[1], ":")
  87. if len(evalErr) > 1 {
  88. line, _ = strconv.Atoi(evalErr[1])
  89. }
  90. }
  91. if line == -1 {
  92. evalErr = strings.Split(msg, ": Line ")
  93. if len(evalErr) > 1 {
  94. evalErr = strings.Split(evalErr[1], ":")
  95. if len(evalErr) > 0 {
  96. line, _ = strconv.Atoi(evalErr[0])
  97. }
  98. }
  99. }
  100. //log.Println("error line", line,evalErr)
  101. if line > -1 {
  102. lines := strings.Split(string(code), "\n")
  103. linesStart := line - 15
  104. linesEnd := line + 15
  105. if linesStart < 0 {
  106. linesStart = 0
  107. }
  108. if len(lines)-1 < linesEnd {
  109. linesEnd = len(lines)
  110. }
  111. if linesStart > linesEnd {
  112. errLine = "无法定位的报错位置"
  113. linesStart = 0
  114. } else {
  115. //
  116. //log.Println("error line linesStart", linesStart)
  117. //log.Println("error line linesEnd", linesEnd)
  118. //linesEnd = linesStart + linesEnd
  119. for e := range lines[linesStart:linesEnd] {
  120. if e+linesStart+1 == line {
  121. lines[linesStart:linesEnd][e] = fmt.Sprintf(`%2d: >>>>>> %s`, e+linesStart, lines[linesStart:linesEnd][e])
  122. } else {
  123. lines[linesStart:linesEnd][e] = fmt.Sprintf(`%2d: %s`, e+linesStart, lines[linesStart:linesEnd][e])
  124. }
  125. }
  126. errLine = strings.Join(lines[linesStart:linesEnd], "\n")
  127. }
  128. }
  129. if jr.RenderCallBackFun != nil {
  130. jr.RenderCallBackFun(fmt.Sprintf(`
  131. <body style="background: #eeeeee;padding: 5%%;width:85%%;">
  132. <h3 style="color: red;">%s</h3>
  133. <div style="background: #fff;">
  134. <pre style="width:95%%;font-size: 14px; padding: 2%%; ">%s<pre>
  135. </div>
  136. </body>
  137. `, html.EscapeString(msg), html.EscapeString(errLine)))
  138. jr.RenderCallBackFun = func(s string) {
  139. }
  140. }
  141. }
  142. func (jr *JsRuntime) InitJsCode() error {
  143. for e := range jr.Relys {
  144. //start := time.Now().UnixNano()
  145. err := jr.RunCode(jr.Relys[e].Src)
  146. //end := time.Now().UnixNano()
  147. //log.Println(e, "加载时间:", end-start)
  148. if err != nil {
  149. return err
  150. }
  151. }
  152. return nil
  153. }
  154. func (jr *JsRuntime) SetVariable(name string, value interface{}) {
  155. jr.runtime.Set(name, value)
  156. }
  157. var codeMap sync.Map
  158. func (jr *JsRuntime) md5(data string) string {
  159. return fmt.Sprintf("%x", md5.Sum([]byte(data)))
  160. }
  161. func (jr *JsRuntime) RunCode(code string) error {
  162. id := jr.md5(code)
  163. var p interface{}
  164. var ok bool
  165. p, ok = codeMap.Load(id)
  166. if !ok {
  167. pro, err := goja.Compile("", code, false)
  168. if err != nil {
  169. log.Println("Compile Error")
  170. return err
  171. }
  172. codeMap.Store(id, pro)
  173. p = pro
  174. }
  175. program := p.(*goja.Program)
  176. _, err := jr.runtime.RunProgram(program)
  177. //log.Println("RunCode result", result)
  178. return err
  179. }
  180. func (jr *JsRuntime) Render(filePath, href, tplSrc string, GoExtData interface{}, cb func(data string)) (err error) {
  181. jr.RenderCallBackFun = cb
  182. jr.mutex.Lock()
  183. defer jr.mutex.Unlock()
  184. runtime := jr.runtime
  185. mainSrc := jr.MainSrc
  186. isLock := true
  187. //timeoutSec := jr.TimeoutSec
  188. defer func() {
  189. isLock = false
  190. jr.runtime.ClearInterrupt()
  191. }()
  192. var useSrc string
  193. if jr.UseSrcFun != nil {
  194. useSrc = jr.UseSrcFun()
  195. }
  196. if useSrc != "" {
  197. err = jr.RunCode(useSrc)
  198. if err != nil {
  199. return err
  200. }
  201. }
  202. url, err := url.Parse(href)
  203. if err != nil {
  204. return err
  205. }
  206. url2, err := url.Parse(strings.Replace(filePath, `\`, "/", -1))
  207. if err != nil {
  208. return err
  209. }
  210. runtime.Set("GoExtData", GoExtData)
  211. runtime.Set("GoHtmlSrc", tplSrc)
  212. runtime.Set("GoHref", href)
  213. runtime.Set("GoQuery", url.Query().Encode())
  214. runtime.Set("GoPath", url2.Path)
  215. runtime.Set("GoReturn", func(data string) {
  216. jr.RenderCallBackFun(data)
  217. })
  218. t := time.AfterFunc(time.Duration(jr.TimeoutSec), func() {
  219. jr.runtime.Interrupt("time out")
  220. jr.runtime.ClearInterrupt()
  221. })
  222. defer t.Stop()
  223. err = jr.RunCode(mainSrc)
  224. if err != nil {
  225. jr.PrintError(err.Error(), "")
  226. return err
  227. }
  228. return
  229. }