소스 검색

重构分页、完善debug

mxd 5 년 전
부모
커밋
ce161a90f3

+ 38 - 15
src/main/java/org/ssssssss/magicapi/config/WebUIController.java

@@ -9,13 +9,11 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.ssssssss.magicapi.model.JsonBean;
-import org.ssssssss.script.MagicScriptContext;
 import org.ssssssss.script.MagicScriptDebugContext;
 import org.ssssssss.script.MagicScriptEngine;
 import org.ssssssss.script.MagicScriptError;
 import org.ssssssss.script.parsing.Span;
 
-import java.lang.ref.ReferenceQueue;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -27,8 +25,20 @@ public class WebUIController {
 
 	private JdbcTemplate template;
 
-	public WebUIController(JdbcTemplate template) {
+	private int debugTimeout;
+
+	public WebUIController(JdbcTemplate template,int debugTimeout) {
 		this.template = template;
+		this.debugTimeout = debugTimeout;
+	}
+
+	public void printBanner(){
+		System.out.println("  __  __                _           _     ____  ___ ");
+		System.out.println(" |  \\/  |  __ _   __ _ (_)  ___    / \\   |  _ \\|_ _|");
+		System.out.println(" | |\\/| | / _` | / _` || | / __|  / _ \\  | |_) || | ");
+		System.out.println(" | |  | || (_| || (_| || || (__  / ___ \\ |  __/ | | ");
+		System.out.println(" |_|  |_| \\__,_| \\__, ||_| \\___|/_/   \\_\\|_|   |___|");
+		System.out.println("                  |___/                        " + WebUIController.class.getPackage().getImplementationVersion());
 	}
 
 	@RequestMapping("/delete")
@@ -61,6 +71,9 @@ public class WebUIController {
 	@ResponseBody
 	public JsonBean<Object> debugContinue(String id){
 		MagicScriptDebugContext context = MagicScriptDebugContext.getDebugContext(id);
+		if(context == null){
+			return new JsonBean<>(0,"debug session not found!");
+		}
 		try {
 			context.singal();
 		} catch (InterruptedException e) {
@@ -68,6 +81,8 @@ public class WebUIController {
 		}
 		if(context.isRunning()){
 			return new JsonBean<>(1000,context.getId(),context.getDebugInfo());
+		}else if(context.isException()){
+			return resolveThrowable((Throwable) context.getReturnValue());
 		}
 		return new JsonBean<>(context.getReturnValue());
 	}
@@ -83,29 +98,37 @@ public class WebUIController {
 			MagicScriptDebugContext context = new MagicScriptDebugContext(request);
 			try {
 				context.setBreakpoints((List<Integer>) breakpoints);
+				context.setTimeout(this.debugTimeout);
 				Object result = MagicScriptEngine.execute(script.toString(), context);
 				if(context.isRunning()){
 					return new JsonBean<>(1000,context.getId(),result);
+				}else if(context.isException()){
+					return resolveThrowable((Throwable) context.getReturnValue());
 				}
 				return new JsonBean<>(result);
-			} catch (MagicScriptError.ScriptException se) {
-				logger.error("测试脚本出错",se);
-				Throwable parent = se;
-				while((parent = parent.getCause()) != null){
-					if(parent instanceof MagicScriptError.ScriptException){
-						se = (MagicScriptError.ScriptException)parent;
-					}
-				}
-				Span.Line line = se.getLine();
-				return new JsonBean<>(-1000, se.getSimpleMessage(), line == null ? null : Arrays.asList(line.getLineNumber(), line.getEndLineNumber(),line.getStartCol(), line.getEndCol()));
 			} catch (Exception e) {
-				logger.error("测试脚本出错",e);
-				return new JsonBean<>(-1, e.getMessage());
+				return resolveThrowable(e);
 			}
 		}
 		return new JsonBean<>(0, "脚本不能为空");
 	}
 
+	private JsonBean<Object> resolveThrowable(Throwable root){
+		MagicScriptError.ScriptException se = null;
+		Throwable parent = root;
+		do{
+			if(parent instanceof MagicScriptError.ScriptException){
+				se = (MagicScriptError.ScriptException)parent;
+			}
+		}while((parent = parent.getCause()) != null);
+		logger.error("测试脚本出错",root);
+		if(se != null){
+			Span.Line line = se.getLine();
+			return new JsonBean<>(-1000, se.getSimpleMessage(), line == null ? null : Arrays.asList(line.getLineNumber(), line.getEndLineNumber(),line.getStartCol(), line.getEndCol()));
+		}
+		return new JsonBean<>(-1,root.getMessage());
+	}
+
 	@RequestMapping("/get")
 	@ResponseBody
 	public JsonBean<Map<String, Object>> get(String id) {

+ 4 - 4
src/main/java/org/ssssssss/magicapi/dialect/DB2Dialect.java

@@ -1,12 +1,12 @@
 package org.ssssssss.magicapi.dialect;
 
-import org.ssssssss.magicapi.context.RequestContext;
+import org.ssssssss.script.functions.DatabaseQuery;
 
 public class DB2Dialect implements Dialect {
     @Override
-    public String getPageSql(String sql, RequestContext context, long offset, long limit) {
-        context.addParameter(offset + 1);
-        context.addParameter(offset + limit);
+    public String getPageSql(String sql, DatabaseQuery.BoundSql boundSql, long offset, long limit) {
+        boundSql.addParameter(offset + 1);
+        boundSql.addParameter(offset + limit);
         return "SELECT * FROM (SELECT TMP_PAGE.*,ROWNUMBER() OVER() AS ROW_ID FROM ( " + sql +
                 " ) AS TMP_PAGE) TMP_PAGE WHERE ROW_ID BETWEEN ? AND ?";
     }

+ 2 - 2
src/main/java/org/ssssssss/magicapi/dialect/Dialect.java

@@ -1,6 +1,6 @@
 package org.ssssssss.magicapi.dialect;
 
-import org.ssssssss.magicapi.context.RequestContext;
+import org.ssssssss.script.functions.DatabaseQuery;
 
 public interface Dialect {
 
@@ -14,5 +14,5 @@ public interface Dialect {
     /**
      * 获取分页sql
      */
-    String getPageSql(String sql, RequestContext context, long offset, long limit);
+    String getPageSql(String sql, DatabaseQuery.BoundSql boundSql, long offset, long limit);
 }

+ 4 - 4
src/main/java/org/ssssssss/magicapi/dialect/MySQLDialect.java

@@ -1,13 +1,13 @@
 package org.ssssssss.magicapi.dialect;
 
-import org.ssssssss.magicapi.context.RequestContext;
+import org.ssssssss.script.functions.DatabaseQuery;
 
 public class MySQLDialect implements Dialect {
 
     @Override
-    public String getPageSql(String sql, RequestContext context, long offset, long limit) {
-        context.addParameter(offset);
-        context.addParameter(limit);
+    public String getPageSql(String sql, DatabaseQuery.BoundSql boundSql, long offset, long limit) {
+        boundSql.addParameter(offset);
+        boundSql.addParameter(limit);
         return sql + " limit ?,?";
     }
 }

+ 4 - 4
src/main/java/org/ssssssss/magicapi/dialect/OracleDialect.java

@@ -1,14 +1,14 @@
 package org.ssssssss.magicapi.dialect;
 
-import org.ssssssss.magicapi.context.RequestContext;
+import org.ssssssss.script.functions.DatabaseQuery;
 
 public class OracleDialect implements Dialect {
 
     @Override
-    public String getPageSql(String sql, RequestContext context, long offset, long limit) {
+    public String getPageSql(String sql, DatabaseQuery.BoundSql boundSql, long offset, long limit) {
         limit = (offset >= 1) ? (offset + limit) : limit;
-        context.addParameter(limit);
-        context.addParameter(offset);
+        boundSql.addParameter(limit);
+        boundSql.addParameter(offset);
         return "SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( " +
                 sql + " ) TMP WHERE ROWNUM <= ? ) WHERE ROW_ID > ?";
     }

+ 4 - 4
src/main/java/org/ssssssss/magicapi/dialect/PostgreSQLDialect.java

@@ -1,12 +1,12 @@
 package org.ssssssss.magicapi.dialect;
 
-import org.ssssssss.magicapi.context.RequestContext;
+import org.ssssssss.script.functions.DatabaseQuery;
 
 public class PostgreSQLDialect implements Dialect {
     @Override
-    public String getPageSql(String sql, RequestContext context, long offset, long limit) {
-        context.addParameter(limit);
-        context.addParameter(offset);
+    public String getPageSql(String sql, DatabaseQuery.BoundSql boundSql, long offset, long limit) {
+        boundSql.addParameter(limit);
+        boundSql.addParameter(offset);
         return sql + " limit ? offset ?";
     }
 }

+ 4 - 4
src/main/java/org/ssssssss/magicapi/dialect/SQLServer2005Dialect.java

@@ -1,11 +1,11 @@
 package org.ssssssss.magicapi.dialect;
 
 import org.apache.commons.lang3.StringUtils;
-import org.ssssssss.magicapi.context.RequestContext;
+import org.ssssssss.script.functions.DatabaseQuery;
 
 public class SQLServer2005Dialect implements Dialect {
     @Override
-    public String getPageSql(String sql, RequestContext context, long offset, long limit) {
+    public String getPageSql(String sql, DatabaseQuery.BoundSql boundSql, long offset, long limit) {
         StringBuilder pagingBuilder = new StringBuilder();
         String orderby = getOrderByPart(sql);
         String distinctStr = "";
@@ -37,8 +37,8 @@ public class SQLServer2005Dialect implements Dialect {
                 .append(pagingBuilder)
                 .append(") SELECT * FROM query WHERE __row_number__ BETWEEN ? AND ?")
                 .append(" ORDER BY __row_number__");
-        context.addParameter(offset + 1);
-        context.addParameter(offset + limit);
+        boundSql.addParameter(offset + 1);
+        boundSql.addParameter(offset + limit);
         return result.toString();
     }
 

+ 4 - 4
src/main/java/org/ssssssss/magicapi/dialect/SQLServerDialect.java

@@ -1,12 +1,12 @@
 package org.ssssssss.magicapi.dialect;
 
-import org.ssssssss.magicapi.context.RequestContext;
+import org.ssssssss.script.functions.DatabaseQuery;
 
 public class SQLServerDialect implements Dialect {
     @Override
-    public String getPageSql(String sql, RequestContext context, long offset, long limit) {
-        context.addParameter(offset);
-        context.addParameter(limit);
+    public String getPageSql(String sql, DatabaseQuery.BoundSql boundSql, long offset, long limit) {
+        boundSql.addParameter(offset);
+        boundSql.addParameter(limit);
         return sql + " OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
     }
 }

+ 2 - 3
src/main/java/org/ssssssss/magicapi/provider/PageProvider.java

@@ -1,13 +1,12 @@
 package org.ssssssss.magicapi.provider;
 
 import org.ssssssss.magicapi.model.Page;
-
-import javax.servlet.http.HttpServletRequest;
+import org.ssssssss.script.MagicScriptContext;
 
 /**
  * 分页对象提取接口
  */
 public interface PageProvider {
 
-    public Page getPage(HttpServletRequest request);
+    public Page getPage(MagicScriptContext context);
 }

+ 4 - 5
src/main/java/org/ssssssss/magicapi/provider/impl/DefaultPageProvider.java

@@ -3,8 +3,7 @@ package org.ssssssss.magicapi.provider.impl;
 import org.apache.commons.lang3.math.NumberUtils;
 import org.ssssssss.magicapi.model.Page;
 import org.ssssssss.magicapi.provider.PageProvider;
-
-import javax.servlet.http.HttpServletRequest;
+import org.ssssssss.script.MagicScriptContext;
 
 /**
  * 分页对象默认提取接口
@@ -45,10 +44,10 @@ public class DefaultPageProvider implements PageProvider {
 
 
     @Override
-    public Page getPage(HttpServletRequest request) {
+    public Page getPage(MagicScriptContext context) {
         // 从Request中提取page以及pageSize
-        long page = NumberUtils.toLong(request.getParameter(this.pageName), this.defaultPage);
-        long pageSize = NumberUtils.toLong(request.getParameter(this.pageSize), this.defaultPageSize);
+        long page = NumberUtils.toLong(context.getString(this.pageName), this.defaultPage);
+        long pageSize = NumberUtils.toLong(context.getString(this.pageSize), this.defaultPageSize);
         // 计算limit以及offset
         return new Page(pageSize,(page - 1) * pageSize);
 

+ 120 - 108
src/main/java/org/ssssssss/script/MagicScriptContext.java

@@ -2,7 +2,10 @@ package org.ssssssss.script;
 
 import org.ssssssss.script.interpreter.AstInterpreter;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 
 /**
@@ -17,111 +20,120 @@ import java.util.*;
  * </p>
  */
 public class MagicScriptContext {
-    private final static ThreadLocal<MagicScriptContext> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
-    private final List<Map<String, Object>> scopes = new ArrayList<Map<String, Object>>();
-
-    /**
-     * Keeps track of previously allocated, unused scopes. New scopes are first tried to be retrieved from this pool to avoid
-     * generating garbage.
-     **/
-    private final List<Map<String, Object>> freeScopes = new ArrayList<Map<String, Object>>();
-
-    public MagicScriptContext() {
-        push();
-    }
-
-    public MagicScriptContext(Map<String, Object> variables) {
-        this();
-        if (variables != null) {
-            for (Map.Entry<String, Object> entry : variables.entrySet()) {
-                set(entry.getKey(), entry.getValue());
-            }
-        }
-    }
-
-    public static MagicScriptContext get() {
-        return CONTEXT_THREAD_LOCAL.get();
-    }
-
-    public static void remove() {
-        CONTEXT_THREAD_LOCAL.remove();
-    }
-
-    public static void set(MagicScriptContext context) {
-        CONTEXT_THREAD_LOCAL.set(context);
-    }
-
-    /**
-     * Sets the value of the variable with the given name. If the variable already exists in one of the scopes, that variable is
-     * set. Otherwise the variable is set on the last pushed scope.
-     */
-    public MagicScriptContext set(String name, Object value) {
-        for (int i = scopes.size() - 1; i >= 0; i--) {
-            Map<String, Object> ctx = scopes.get(i);
-            if (ctx.isEmpty()) {
-                continue;
-            }
-            if (ctx.containsKey(name)) {
-                ctx.put(name, value);
-                return this;
-            }
-        }
-
-        scopes.get(scopes.size() - 1).put(name, value);
-        return this;
-    }
-
-    /**
-     * Sets the value of the variable with the given name on the last pushed scope
-     **/
-    public MagicScriptContext setOnCurrentScope(String name, Object value) {
-        scopes.get(scopes.size() - 1).put(name, value);
-        return this;
-    }
-
-    /**
-     * Internal. Returns the value of the variable with the given name, walking the scope stack from top to bottom, similar to how
-     * scopes in programming languages are searched for variables.
-     */
-    public Object get(String name) {
-        for (int i = scopes.size() - 1; i >= 0; i--) {
-            Map<String, Object> ctx = scopes.get(i);
-            if (ctx.isEmpty()) {
-                continue;
-            }
-            Object value = ctx.get(name);
-            if (value != null) {
-                return value;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Internal. Returns all variables currently defined in this context.
-     */
-    public Map<String,Object> getVariables() {
-        Map<String,Object> variables = new HashMap<>();
-        for (Map<String, Object> scope : scopes) {
-            variables.putAll(scope);
-        }
-        return variables;
-    }
-
-    /**
-     * Internal. Pushes a new "scope" onto the stack.
-     **/
-    public void push() {
-        Map<String, Object> newScope = freeScopes.size() > 0 ? freeScopes.remove(freeScopes.size() - 1) : new HashMap<String, Object>();
-        scopes.add(newScope);
-    }
-
-    /**
-     * Internal. Pops the top of the "scope" stack.
-     **/
-    public void pop() {
-        Map<String, Object> oldScope = scopes.remove(scopes.size() - 1);
-        oldScope.clear();
-        freeScopes.add(oldScope);
-    }
+	private final static ThreadLocal<MagicScriptContext> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
+	private final List<Map<String, Object>> scopes = new ArrayList<Map<String, Object>>();
+
+	/**
+	 * Keeps track of previously allocated, unused scopes. New scopes are first tried to be retrieved from this pool to avoid
+	 * generating garbage.
+	 **/
+	private final List<Map<String, Object>> freeScopes = new ArrayList<Map<String, Object>>();
+
+	public MagicScriptContext() {
+		push();
+	}
+
+	public MagicScriptContext(Map<String, Object> variables) {
+		this();
+		if (variables != null) {
+			for (Map.Entry<String, Object> entry : variables.entrySet()) {
+				set(entry.getKey(), entry.getValue());
+			}
+		}
+	}
+
+	public static MagicScriptContext get() {
+		return CONTEXT_THREAD_LOCAL.get();
+	}
+
+	public static void remove() {
+		CONTEXT_THREAD_LOCAL.remove();
+	}
+
+	public static void set(MagicScriptContext context) {
+		CONTEXT_THREAD_LOCAL.set(context);
+	}
+
+	/**
+	 * Sets the value of the variable with the given name. If the variable already exists in one of the scopes, that variable is
+	 * set. Otherwise the variable is set on the last pushed scope.
+	 */
+	public MagicScriptContext set(String name, Object value) {
+		for (int i = scopes.size() - 1; i >= 0; i--) {
+			Map<String, Object> ctx = scopes.get(i);
+			if (ctx.isEmpty()) {
+				continue;
+			}
+			if (ctx.containsKey(name)) {
+				ctx.put(name, value);
+				return this;
+			}
+		}
+
+		scopes.get(scopes.size() - 1).put(name, value);
+		return this;
+	}
+
+	/**
+	 * Sets the value of the variable with the given name on the last pushed scope
+	 **/
+	public MagicScriptContext setOnCurrentScope(String name, Object value) {
+		scopes.get(scopes.size() - 1).put(name, value);
+		return this;
+	}
+
+	/**
+	 * Internal. Returns the value of the variable with the given name, walking the scope stack from top to bottom, similar to how
+	 * scopes in programming languages are searched for variables.
+	 */
+	public Object get(String name) {
+		for (int i = scopes.size() - 1; i >= 0; i--) {
+			Map<String, Object> ctx = scopes.get(i);
+			if (ctx.isEmpty()) {
+				continue;
+			}
+			Object value = ctx.get(name);
+			if (value != null) {
+				return value;
+			}
+		}
+		return null;
+	}
+
+	public String getString(String name) {
+		return getString(name, null);
+	}
+
+	public String getString(String name, String defaultValue) {
+		Object value = get(name);
+		return value == null ? defaultValue : value.toString();
+	}
+
+	/**
+	 * Internal. Returns all variables currently defined in this context.
+	 */
+	public Map<String, Object> getVariables() {
+		Map<String, Object> variables = new HashMap<>();
+		for (Map<String, Object> scope : scopes) {
+			variables.putAll(scope);
+		}
+		return variables;
+	}
+
+	/**
+	 * Internal. Pushes a new "scope" onto the stack.
+	 **/
+	public void push() {
+		Map<String, Object> newScope = freeScopes.size() > 0 ? freeScopes.remove(freeScopes.size() - 1) : new HashMap<String, Object>();
+		scopes.add(newScope);
+	}
+
+	/**
+	 * Internal. Pops the top of the "scope" stack.
+	 **/
+	public void pop() {
+		Map<String, Object> oldScope = scopes.remove(scopes.size() - 1);
+		oldScope.clear();
+		freeScopes.add(oldScope);
+	}
 }

+ 48 - 19
src/main/java/org/ssssssss/script/MagicScriptDebugContext.java

@@ -1,15 +1,18 @@
 package org.ssssssss.script;
 
+import org.ssssssss.script.parsing.Span;
+
 import java.util.*;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
-public class MagicScriptDebugContext extends MagicScriptContext{
+public class MagicScriptDebugContext extends MagicScriptContext {
 
-	private String id = UUID.randomUUID().toString().replace("-","");
+	private String id = UUID.randomUUID().toString().replace("-", "");
 
-	private static Map<String,MagicScriptDebugContext> contextMap = new ConcurrentHashMap<>();
+	private static Map<String, MagicScriptDebugContext> contextMap = new ConcurrentHashMap<>();
 
 	public List<Integer> breakpoints;
 
@@ -19,11 +22,21 @@ public class MagicScriptDebugContext extends MagicScriptContext{
 
 	private Object returnValue;
 
+	private Span.Line line;
+
 	private boolean running = true;
 
+	private boolean exception = false;
+
+	private int timeout = 60;
+
 	public MagicScriptDebugContext(Map<String, Object> variables) {
 		super(variables);
-		contextMap.put(this.id,this);
+		contextMap.put(this.id, this);
+	}
+
+	public void setTimeout(int timeout) {
+		this.timeout = timeout;
 	}
 
 	public List<Integer> getBreakpoints() {
@@ -34,14 +47,16 @@ public class MagicScriptDebugContext extends MagicScriptContext{
 		this.breakpoints = breakpoints;
 	}
 
-	public void pause() throws InterruptedException {
+	public String pause(Span.Line line) throws InterruptedException {
+		this.line = line;
 		consumer.offer(this.id);
-		producer.take();
+		return producer.poll(timeout, TimeUnit.SECONDS);
 	}
 
-	public  void await() throws InterruptedException {
+	public void await() throws InterruptedException {
 		consumer.take();
 	}
+
 	public void singal() throws InterruptedException {
 		producer.offer(this.id);
 		await();
@@ -62,28 +77,42 @@ public class MagicScriptDebugContext extends MagicScriptContext{
 		return running;
 	}
 
-	public List<Map<String,Object>> getDebugInfo(){
-		Map<String, Object> variables = super.getVariables();
-		List<Map<String,Object>> result = new ArrayList<>();
-		Set<Map.Entry<String, Object>> entries = variables.entrySet();
+	public Map<String, Object> getDebugInfo() {
+		List<Map<String, Object>> varList = new ArrayList<>();
+		Set<Map.Entry<String, Object>> entries = super.getVariables().entrySet();
 		for (Map.Entry<String, Object> entry : entries) {
 			Object value = entry.getValue();
-			Map<String,Object> variable = new HashMap<>();
-			variable.put("name",entry.getKey());
-			variable.put("value",value);
-			if(value != null){
-				variable.put("type",value.getClass());
+			Map<String, Object> variable = new HashMap<>();
+			variable.put("name", entry.getKey());
+			variable.put("value", value);
+			if (value != null) {
+				variable.put("type", value.getClass());
 			}
-			result.add(variable);
+			varList.add(variable);
 		}
-		return result;
+		Map<String, Object> info = new HashMap<>();
+		info.put("variables", varList);
+		info.put("range", Arrays.asList(line.getLineNumber(), line.getStartCol(), line.getEndLineNumber(), line.getEndCol()));
+		return info;
+	}
+
+	public Span.Line getLine() {
+		return line;
 	}
 
 	public String getId() {
 		return id;
 	}
 
-	public static MagicScriptDebugContext getDebugContext(String id){
+	public boolean isException() {
+		return exception;
+	}
+
+	public void setException(boolean exception) {
+		this.exception = exception;
+	}
+
+	public static MagicScriptDebugContext getDebugContext(String id) {
 		return contextMap.get(id);
 	}
 }

+ 31 - 22
src/main/java/org/ssssssss/script/MagicScriptEngine.java

@@ -1,6 +1,8 @@
 package org.ssssssss.script;
 
-import javax.xml.crypto.dsig.keyinfo.RetrievalMethod;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.Iterator;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -14,42 +16,49 @@ public class MagicScriptEngine {
 
 	private static ExecutorService service = Executors.newCachedThreadPool();
 
-	static{
-        addDefaultImport("range",(BiFunction<Integer, Integer, Iterator<Integer>>) (from, to) -> new Iterator<Integer>() {
-            int idx = from;
+	private static Logger logger = LoggerFactory.getLogger(MagicScriptEngine.class);
+
+	static {
+		addDefaultImport("range", (BiFunction<Integer, Integer, Iterator<Integer>>) (from, to) -> new Iterator<Integer>() {
+			int idx = from;
 
-            public boolean hasNext() {
-                return idx <= to;
-            }
+			public boolean hasNext() {
+				return idx <= to;
+			}
 
-            public Integer next() {
-                return idx++;
-            }
-        });
-    }
+			public Integer next() {
+				return idx++;
+			}
+		});
+	}
 
 	public static void addDefaultImport(String name, Object target) {
 		defaultImports.put(name, target);
 	}
 
 	public static Object execute(String script, MagicScriptContext context) {
-        Iterator<Map.Entry<String, Object>> iterator = defaultImports.entrySet().iterator();
-        while(iterator.hasNext()){
-            Map.Entry<String, Object> entry = iterator.next();
-            context.set(entry.getKey(),entry.getValue());
-        }
+		Iterator<Map.Entry<String, Object>> iterator = defaultImports.entrySet().iterator();
+		while (iterator.hasNext()) {
+			Map.Entry<String, Object> entry = iterator.next();
+			context.set(entry.getKey(), entry.getValue());
+		}
 		MagicScript magicScript = MagicScript.create(script);
-        if(context instanceof MagicScriptDebugContext){
+		if (context instanceof MagicScriptDebugContext) {
 			MagicScriptDebugContext debugContext = (MagicScriptDebugContext) context;
-			service.submit(()->{
-				debugContext.setReturnValue(magicScript.execute(debugContext));
+			service.submit(() -> {
+				try {
+					debugContext.setReturnValue(magicScript.execute(debugContext));
+				} catch (Exception e) {
+					debugContext.setException(true);
+					debugContext.setReturnValue(e);
+				}
 			});
 			try {
 				debugContext.await();
 			} catch (InterruptedException e) {
-				e.printStackTrace();
+				throw new MagicScriptError.DebugTimeoutException(e);
 			}
-			return debugContext.isRunning() ? debugContext.getDebugInfo(): debugContext.getReturnValue();
+			return debugContext.isRunning() ? debugContext.getDebugInfo() : debugContext.getReturnValue();
 		}
 		return magicScript.execute(context);
 	}

+ 11 - 0
src/main/java/org/ssssssss/script/MagicScriptError.java

@@ -59,6 +59,17 @@ public class MagicScriptError {
 		error(message, location, null);
 	}
 
+	public static class DebugTimeoutException extends RuntimeException{
+
+		public DebugTimeoutException() {
+			super("debug超时");
+		}
+
+		public DebugTimeoutException(Throwable cause) {
+			super(cause);
+		}
+	}
+
 	public static class ScriptException extends RuntimeException {
 		private static final long serialVersionUID = 1L;
 		private final String errorMessage;

+ 20 - 13
src/main/java/org/ssssssss/script/functions/DatabaseQuery.java

@@ -3,12 +3,12 @@ package org.ssssssss.script.functions;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.datasource.DataSourceUtils;
 import org.ssssssss.magicapi.config.DynamicDataSource;
-import org.ssssssss.magicapi.context.RequestContextHolder;
 import org.ssssssss.magicapi.dialect.Dialect;
 import org.ssssssss.magicapi.dialect.DialectUtils;
 import org.ssssssss.magicapi.exception.MagicAPIException;
 import org.ssssssss.magicapi.model.Page;
 import org.ssssssss.magicapi.model.PageResult;
+import org.ssssssss.magicapi.provider.PageProvider;
 import org.ssssssss.script.MagicScriptContext;
 import org.ssssssss.script.parsing.GenericTokenParser;
 import org.ssssssss.script.parsing.Parser;
@@ -26,22 +26,27 @@ public class DatabaseQuery extends HashMap<String,DatabaseQuery> {
 
 	private JdbcTemplate template;
 
-	public DatabaseQuery(JdbcTemplate template,DynamicDataSource dataSource) {
+	private PageProvider pageProvider;
+
+	public DatabaseQuery(JdbcTemplate template,DynamicDataSource dataSource,PageProvider pageProvider) {
 		this.template = template;
 		this.dataSource = dataSource;
+		this.pageProvider = pageProvider;
 	}
 
-	public DatabaseQuery(DynamicDataSource dataSource) {
+	public DatabaseQuery(DynamicDataSource dataSource,PageProvider pageProvider) {
 		this.dataSource = dataSource;
+		this.pageProvider = pageProvider;
 		this.template = dataSource.getJdbcTemplate(null);
+
 	}
 
 	@Override
 	public DatabaseQuery get(Object key) {
 		if(key == null){
-			return new DatabaseQuery(dataSource.getJdbcTemplate(null),this.dataSource);
+			return new DatabaseQuery(dataSource.getJdbcTemplate(null),this.dataSource,this.pageProvider);
 		}
-		return new DatabaseQuery(dataSource.getJdbcTemplate(key.toString()),this.dataSource);
+		return new DatabaseQuery(dataSource.getJdbcTemplate(key.toString()),this.dataSource,this.pageProvider);
 	}
 
 
@@ -50,7 +55,7 @@ public class DatabaseQuery extends HashMap<String,DatabaseQuery> {
 		return template.queryForList(boundSql.getSql(),boundSql.getParameters());
 	}
 	public Object page(String sql){
-		Page page = RequestContextHolder.get().getPage();
+		Page page = pageProvider.getPage(MagicScriptContext.get());
 		return page(sql,page.getLimit(),page.getOffset());
 	}
 	public Object page(String sql,long limit,long offset){
@@ -70,7 +75,7 @@ public class DatabaseQuery extends HashMap<String,DatabaseQuery> {
 			DataSourceUtils.releaseConnection(connection,template.getDataSource());
 		}
 		if(count > 0){
-			result.setList(template.queryForList(dialect.getPageSql(boundSql.getSql(), RequestContextHolder.get(),offset,limit)));
+			result.setList(template.queryForList(dialect.getPageSql(boundSql.getSql(), boundSql,offset,limit),boundSql.getParameters()));
 		}
 		return result;
 	}
@@ -100,11 +105,10 @@ public class DatabaseQuery extends HashMap<String,DatabaseQuery> {
 
 	private static GenericTokenParser ifParamTokenParser = new GenericTokenParser("?{",",",true);
 
-	static class BoundSql{
+	public static class BoundSql{
 		private String sql;
-		private Object[] parameters;
+		private List<Object> parameters = new ArrayList<>();
 		BoundSql(String sql){
-			List<Object> paramList = new ArrayList<>();
 			MagicScriptContext context = MagicScriptContext.get();
 			this.sql = ifTokenParser.parse(sql,text->{
 				AtomicBoolean ifTrue = new AtomicBoolean(false);
@@ -120,10 +124,13 @@ public class DatabaseQuery extends HashMap<String,DatabaseQuery> {
 			});
 			this.sql = concatTokenParser.parse(this.sql,text->String.valueOf(Parser.parseExpression(new TokenStream(tokenizer.tokenize(text))).evaluate(context)));
 			this.sql = replaceTokenParser.parse(this.sql,text->{
-				paramList.add(Parser.parseExpression(new TokenStream(tokenizer.tokenize(text))).evaluate(context));
+				parameters.add(Parser.parseExpression(new TokenStream(tokenizer.tokenize(text))).evaluate(context));
 				return "?";
 			});
-			this.parameters = paramList.toArray();
+		}
+
+		public void addParameter(Object value){
+			parameters.add(value);
 		}
 
 		public String getSql() {
@@ -131,7 +138,7 @@ public class DatabaseQuery extends HashMap<String,DatabaseQuery> {
 		}
 
 		public Object[] getParameters() {
-			return parameters;
+			return parameters.toArray();
 		}
 	}
 

+ 10 - 5
src/main/java/org/ssssssss/script/interpreter/AstInterpreter.java

@@ -5,6 +5,7 @@ import org.ssssssss.script.MagicScriptContext;
 import org.ssssssss.script.MagicScriptDebugContext;
 import org.ssssssss.script.MagicScriptError;
 import org.ssssssss.script.MagicScriptError.ScriptException;
+import org.ssssssss.script.parsing.Span;
 import org.ssssssss.script.parsing.ast.Break;
 import org.ssssssss.script.parsing.ast.Continue;
 import org.ssssssss.script.parsing.ast.Node;
@@ -34,8 +35,8 @@ public class AstInterpreter {
             }
             return null;
         } catch (Throwable t) {
-            if (t instanceof ScriptException) {
-                throw (ScriptException) t;
+            if (t instanceof ScriptException || t instanceof MagicScriptError.DebugTimeoutException) {
+                throw t;
             } else {
                 MagicScriptError.error("执行表达式出错 " + t.getMessage(), magicScript.getNodes().get(0).getSpan(), t);
                 return null; // never reached
@@ -52,11 +53,15 @@ public class AstInterpreter {
                 Node node = nodes.get(i);
                 if(context instanceof MagicScriptDebugContext){
                     MagicScriptDebugContext debugContext = (MagicScriptDebugContext) context;
-                    if(debugContext.getBreakpoints().contains(node.getSpan().getLine().getLineNumber())){
+                    Span.Line line = node.getSpan().getLine();
+                    if(debugContext.getBreakpoints().contains(line.getLineNumber())){
                         try {
-                            debugContext.pause();
+                            if(debugContext.pause(line) == null){
+                                debugContext.setReturnValue(null);
+                                throw new MagicScriptError.DebugTimeoutException();
+                            }
                         } catch (InterruptedException e) {
-                            e.printStackTrace();
+                            throw new MagicScriptError.DebugTimeoutException(e);
                         }
                     }
                 }

+ 5 - 0
src/main/resources/magicapi-support/css/index.css

@@ -82,6 +82,7 @@ html,body{
     top : 400px;
     bottom : 0px;
     width : 100%;
+    border-top: 1px solid #eee;
 }
 .layui-side{
     width : 225px;
@@ -169,4 +170,8 @@ html,body{
 }
 .breakpoint-line{
     background: #faeae6;
+}
+.debug-line{
+    background: #2154A6;
+    color: #fff !important;
 }

+ 18 - 10
src/main/resources/magicapi-support/js/index.js

@@ -109,7 +109,7 @@ $(function(){
         });
         resetEditor();
         editor.onMouseDown(function(e){
-            if (e.target.detail && e.target.detail.offsetX && e.target.detail.offsetX >= 0 && e.target.detail.offsetX <= 50) {
+            if (e.target.detail && e.target.detail.offsetX && e.target.detail.offsetX >= 0 && e.target.detail.offsetX <= 60) {
                 var line = e.target.position.lineNumber;
                 if (editor.getModel().getLineContent(line).trim() === '') {
                     return
@@ -124,26 +124,33 @@ $(function(){
     });
 
     var $tbody = $('#debug-tbody');
+    var debugDecorations;
     var debugIn = function(id,data){
         debugSessionId = id;
-        for(var i =0,len = data.length;i<len;i++){
-            var item = data[i];
+        for(var i =0,len = data.variables.length;i<len;i++){
+            var item = data.variables[i];
             var $tr = $('<tr/>');
             $tr.append($('<td/>').html(item.name))
             $tr.append($('<td/>').html(JSON.stringify(item.value)))
             $tr.append($('<td/>').html(item.type))
             $tbody.append($tr);
         }
+        debugDecorations = [editor&&editor.deltaDecorations([],[{
+            range :  new monaco.Range(data.range[0],1,data.range[0],1),
+            options: {
+                isWholeLine: true,
+                inlineClassName : 'debug-line',
+                className : 'debug-line',
+            }
+        }])];
     }
 
     var convertResult = function(code,message,json){
         debugSessionId = null;
         $tbody.html('');
-        if(code == 1){
-            layui.element.tabChange('output-container', 'output');
-            outputEditor.setValue(JSON.stringify(json,null,4));
-            return;
-        }else if(code == -1000){
+        debugDecorations&&editor&&editor.deltaDecorations(debugDecorations,[]);
+        debugDecorations = null;
+        if(code === -1000){
             layui.element.tabChange('output-container', 'output');
             if(json.data){
                 var data = json.data;
@@ -159,13 +166,14 @@ $(function(){
                 }])
                 setTimeout(function(){
                     editor&&editor.deltaDecorations(decorations,[])
-                },5000)
+                },10000)
             }
-        }else if(code == 1000){ // debug断点
+        }else if(code === 1000){ // debug断点
             layui.element.tabChange('output-container', 'debug');
             debugIn(message,json.data);
             return;
         }
+        layui.element.tabChange('output-container', 'output');
         outputEditor.setValue(JSON.stringify(json,null,4))
     }
     // 窗口改变大小时,刷新编辑器