06 - 表达式引擎实现 🔍

🎯 目标: 设计灵活强大的表达式引擎,支持条件判断、数据计算和动态脚本执行

🤔 为什么需要表达式引擎?

在流程编排中,表达式引擎是实现动态逻辑的核心:

  • 🔀 条件判断: 根据运行时数据决定流程走向
  • 🧮 数据计算: 对上下文数据进行复杂计算
  • 🔄 循环控制: 动态确定循环的继续条件
  • 📊 数据转换: 在步骤间转换和映射数据
  • 🛡️ 安全执行: 在受控环境中执行用户代码

🏗️ 表达式引擎架构

graph TB subgraph "表达式接口层 🎯" A1[ExpressionEvaluator] A2[Expression] A3[ExpressionContext] end subgraph "解析层 📝" B1[ExpressionParser] B2[TokenLexer] B3[ASTBuilder] end subgraph "执行引擎层 ⚡" C1[SimpleExpressionEvaluator] C2[ScriptExpressionEvaluator] C3[SpELExpressionEvaluator] C4[GroovyExpressionEvaluator] end subgraph "函数库层 🔧" D1[BuiltinFunctions] D2[MathFunctions] D3[StringFunctions] D4[DateFunctions] D5[CollectionFunctions] end subgraph "安全层 🛡️" E1[SecurityManager] E2[SandboxEvaluator] E3[ResourceLimiter] end A1 --> B1 A1 --> C1 A1 --> C2 A1 --> C3 A1 --> C4 B1 --> B2 B1 --> B3 C1 --> D1 C2 --> D1 C3 --> D1 C4 --> D1 D1 --> D2 D1 --> D3 D1 --> D4 D1 --> D5 C1 --> E1 C2 --> E2 C3 --> E3

🎯 核心接口设计

java 复制代码
/**
 * 表达式评估器接口
 * 负责解析和执行表达式
 */
public interface ExpressionEvaluator {
    
    /**
     * 评估表达式并返回结果
     * @param expression 表达式字符串
     * @param context 执行上下文
     * @return 评估结果
     * @throws ExpressionEvaluationException 评估异常
     */
    Object evaluate(String expression, FlowContext context) throws ExpressionEvaluationException;
    
    /**
     * 评估表达式并返回布尔结果
     * @param expression 表达式字符串
     * @param context 执行上下文
     * @return 布尔结果
     * @throws ExpressionEvaluationException 评估异常
     */
    boolean evaluateBoolean(String expression, FlowContext context) throws ExpressionEvaluationException;
    
    /**
     * 评估表达式并返回指定类型的结果
     * @param expression 表达式字符串
     * @param context 执行上下文
     * @param expectedType 期望的结果类型
     * @return 指定类型的结果
     * @throws ExpressionEvaluationException 评估异常
     */
    <T> T evaluate(String expression, FlowContext context, Class<T> expectedType) 
        throws ExpressionEvaluationException;
    
    /**
     * 编译表达式(可选的优化)
     * @param expression 表达式字符串
     * @return 编译后的表达式对象
     * @throws ExpressionCompilationException 编译异常
     */
    Expression compile(String expression) throws ExpressionCompilationException;
    
    /**
     * 验证表达式语法
     * @param expression 表达式字符串
     * @return 验证结果
     */
    ValidationResult validate(String expression);
    
    /**
     * 获取支持的函数列表
     * @return 函数名称集合
     */
    Set<String> getSupportedFunctions();
    
    /**
     * 注册自定义函数
     * @param name 函数名称
     * @param function 函数实现
     */
    void registerFunction(String name, ExpressionFunction function);
}

/**
 * 编译后的表达式接口
 */
public interface Expression {
    
    /**
     * 执行表达式
     * @param context 执行上下文
     * @return 执行结果
     */
    Object execute(FlowContext context) throws ExpressionEvaluationException;
    
    /**
     * 获取表达式字符串
     * @return 原始表达式
     */
    String getExpressionString();
    
    /**
     * 获取表达式依赖的变量
     * @return 变量名称集合
     */
    Set<String> getDependentVariables();
    
    /**
     * 获取表达式类型
     * @return 表达式类型
     */
    ExpressionType getType();
}

/**
 * 表达式函数接口
 */
@FunctionalInterface
public interface ExpressionFunction {
    
    /**
     * 执行函数
     * @param args 参数数组
     * @return 函数结果
     * @throws Exception 执行异常
     */
    Object apply(Object... args) throws Exception;
}

/**
 * 表达式类型枚举
 */
public enum ExpressionType {
    SIMPLE,      // 简单表达式
    SCRIPT,      // 脚本表达式
    SPEL,        // Spring EL表达式
    GROOVY,      // Groovy表达式
    JAVASCRIPT,  // JavaScript表达式
    KOTLIN       // Kotlin表达式
}

🔧 SimpleExpressionEvaluator实现

java 复制代码
/**
 * 简单表达式评估器
 * 支持基本的算术、逻辑和比较运算
 */
public class SimpleExpressionEvaluator implements ExpressionEvaluator {
    
    private static final Logger logger = LoggerFactory.getLogger(SimpleExpressionEvaluator.class);
    
    private final Map<String, ExpressionFunction> functions = new ConcurrentHashMap<>();
    private final ExpressionParser parser = new SimpleExpressionParser();
    private final Map<String, Expression> compiledExpressions = new ConcurrentHashMap<>();
    
    public SimpleExpressionEvaluator() {
        registerBuiltinFunctions();
    }
    
    @Override
    public Object evaluate(String expression, FlowContext context) throws ExpressionEvaluationException {
        if (expression == null || expression.trim().isEmpty()) {
            return null;
        }
        
        try {
            Expression compiled = getOrCompileExpression(expression);
            return compiled.execute(context);
        } catch (Exception e) {
            throw new ExpressionEvaluationException(
                "表达式评估失败: " + expression, e);
        }
    }
    
    @Override
    public boolean evaluateBoolean(String expression, FlowContext context) 
            throws ExpressionEvaluationException {
        Object result = evaluate(expression, context);
        return convertToBoolean(result);
    }
    
    @Override
    public <T> T evaluate(String expression, FlowContext context, Class<T> expectedType) 
            throws ExpressionEvaluationException {
        Object result = evaluate(expression, context);
        return convertToType(result, expectedType);
    }
    
    @Override
    public Expression compile(String expression) throws ExpressionCompilationException {
        try {
            return parser.parse(expression, functions);
        } catch (Exception e) {
            throw new ExpressionCompilationException(
                "表达式编译失败: " + expression, e);
        }
    }
    
    @Override
    public ValidationResult validate(String expression) {
        try {
            compile(expression);
            return ValidationResult.success();
        } catch (ExpressionCompilationException e) {
            return ValidationResult.error(e.getMessage());
        }
    }
    
    @Override
    public Set<String> getSupportedFunctions() {
        return new HashSet<>(functions.keySet());
    }
    
    @Override
    public void registerFunction(String name, ExpressionFunction function) {
        functions.put(name, function);
        // 清除编译缓存,因为新函数可能影响已编译的表达式
        compiledExpressions.clear();
        logger.debug("注册表达式函数: {}", name);
    }
    
    /**
     * 获取或编译表达式
     */
    private Expression getOrCompileExpression(String expression) {
        return compiledExpressions.computeIfAbsent(expression, this::compile);
    }
    
    /**
     * 注册内置函数
     */
    private void registerBuiltinFunctions() {
        // 数学函数
        registerFunction("abs", args -> Math.abs(((Number) args[0]).doubleValue()));
        registerFunction("max", args -> Math.max(((Number) args[0]).doubleValue(), ((Number) args[1]).doubleValue()));
        registerFunction("min", args -> Math.min(((Number) args[0]).doubleValue(), ((Number) args[1]).doubleValue()));
        registerFunction("round", args -> Math.round(((Number) args[0]).doubleValue()));
        registerFunction("ceil", args -> Math.ceil(((Number) args[0]).doubleValue()));
        registerFunction("floor", args -> Math.floor(((Number) args[0]).doubleValue()));
        registerFunction("sqrt", args -> Math.sqrt(((Number) args[0]).doubleValue()));
        registerFunction("pow", args -> Math.pow(((Number) args[0]).doubleValue(), ((Number) args[1]).doubleValue()));
        
        // 字符串函数
        registerFunction("length", args -> args[0].toString().length());
        registerFunction("upper", args -> args[0].toString().toUpperCase());
        registerFunction("lower", args -> args[0].toString().toLowerCase());
        registerFunction("trim", args -> args[0].toString().trim());
        registerFunction("substring", args -> {
            String str = args[0].toString();
            int start = ((Number) args[1]).intValue();
            if (args.length > 2) {
                int end = ((Number) args[2]).intValue();
                return str.substring(start, end);
            }
            return str.substring(start);
        });
        registerFunction("contains", args -> args[0].toString().contains(args[1].toString()));
        registerFunction("startsWith", args -> args[0].toString().startsWith(args[1].toString()));
        registerFunction("endsWith", args -> args[0].toString().endsWith(args[1].toString()));
        registerFunction("replace", args -> args[0].toString().replace(args[1].toString(), args[2].toString()));
        
        // 类型转换函数
        registerFunction("toInt", args -> Integer.valueOf(args[0].toString()));
        registerFunction("toLong", args -> Long.valueOf(args[0].toString()));
        registerFunction("toDouble", args -> Double.valueOf(args[0].toString()));
        registerFunction("toString", args -> args[0].toString());
        registerFunction("toBool", args -> convertToBoolean(args[0]));
        
        // 集合函数
        registerFunction("size", args -> {
            if (args[0] instanceof Collection) {
                return ((Collection<?>) args[0]).size();
            } else if (args[0] instanceof Map) {
                return ((Map<?, ?>) args[0]).size();
            } else if (args[0].getClass().isArray()) {
                return Array.getLength(args[0]);
            }
            return args[0].toString().length();
        });
        
        registerFunction("isEmpty", args -> {
            if (args[0] == null) return true;
            if (args[0] instanceof Collection) {
                return ((Collection<?>) args[0]).isEmpty();
            } else if (args[0] instanceof Map) {
                return ((Map<?, ?>) args[0]).isEmpty();
            } else if (args[0].getClass().isArray()) {
                return Array.getLength(args[0]) == 0;
            }
            return args[0].toString().isEmpty();
        });
        
        // 日期时间函数
        registerFunction("now", args -> System.currentTimeMillis());
        registerFunction("today", args -> {
            Calendar cal = Calendar.getInstance();
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            return cal.getTimeInMillis();
        });
        
        // 条件函数
        registerFunction("if", args -> {
            boolean condition = convertToBoolean(args[0]);
            return condition ? args[1] : (args.length > 2 ? args[2] : null);
        });
        
        registerFunction("nvl", args -> args[0] != null ? args[0] : args[1]);
        
        logger.info("已注册 {} 个内置函数", functions.size());
    }
    
    /**
     * 转换为布尔值
     */
    private boolean convertToBoolean(Object value) {
        if (value == null) {
            return false;
        }
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        if (value instanceof Number) {
            return ((Number) value).doubleValue() != 0;
        }
        if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"false".equalsIgnoreCase(str) && !"0".equals(str);
        }
        if (value instanceof Collection) {
            return !((Collection<?>) value).isEmpty();
        }
        return true;
    }
    
    /**
     * 类型转换
     */
    @SuppressWarnings("unchecked")
    private <T> T convertToType(Object value, Class<T> expectedType) {
        if (value == null) {
            return null;
        }
        
        if (expectedType.isInstance(value)) {
            return (T) value;
        }
        
        // 字符串转换
        if (expectedType == String.class) {
            return (T) value.toString();
        }
        
        // 数字转换
        if (value instanceof Number) {
            Number number = (Number) value;
            if (expectedType == Integer.class || expectedType == int.class) {
                return (T) Integer.valueOf(number.intValue());
            }
            if (expectedType == Long.class || expectedType == long.class) {
                return (T) Long.valueOf(number.longValue());
            }
            if (expectedType == Double.class || expectedType == double.class) {
                return (T) Double.valueOf(number.doubleValue());
            }
            if (expectedType == Float.class || expectedType == float.class) {
                return (T) Float.valueOf(number.floatValue());
            }
        }
        
        // 布尔转换
        if (expectedType == Boolean.class || expectedType == boolean.class) {
            return (T) Boolean.valueOf(convertToBoolean(value));
        }
        
        throw new IllegalArgumentException(
            String.format("无法将 %s 转换为 %s", 
                value.getClass().getSimpleName(), expectedType.getSimpleName())
        );
    }
}

📝 表达式解析器实现

java 复制代码
/**
 * 简单表达式解析器
 * 使用递归下降解析算法
 */
public class SimpleExpressionParser implements ExpressionParser {
    
    private static final Logger logger = LoggerFactory.getLogger(SimpleExpressionParser.class);
    
    @Override
    public Expression parse(String expression, Map<String, ExpressionFunction> functions) 
            throws ExpressionCompilationException {
        try {
            TokenLexer lexer = new TokenLexer(expression);
            List<Token> tokens = lexer.tokenize();
            
            if (tokens.isEmpty()) {
                return new LiteralExpression(null);
            }
            
            ASTBuilder builder = new ASTBuilder(tokens, functions);
            ASTNode ast = builder.buildAST();
            
            return new CompiledExpression(expression, ast);
        } catch (Exception e) {
            throw new ExpressionCompilationException(
                "解析表达式失败: " + expression, e);
        }
    }
    
    /**
     * 词法分析器
     */
    private static class TokenLexer {
        private final String expression;
        private int position = 0;
        
        public TokenLexer(String expression) {
            this.expression = expression;
        }
        
        public List<Token> tokenize() {
            List<Token> tokens = new ArrayList<>();
            
            while (position < expression.length()) {
                char ch = expression.charAt(position);
                
                if (Character.isWhitespace(ch)) {
                    position++;
                    continue;
                }
                
                Token token = nextToken();
                if (token != null) {
                    tokens.add(token);
                }
            }
            
            return tokens;
        }
        
        private Token nextToken() {
            if (position >= expression.length()) {
                return null;
            }
            
            char ch = expression.charAt(position);
            
            // 数字
            if (Character.isDigit(ch) || ch == '.') {
                return readNumber();
            }
            
            // 字符串
            if (ch == '"' || ch == ''') {
                return readString();
            }
            
            // 标识符或关键字
            if (Character.isLetter(ch) || ch == '_') {
                return readIdentifier();
            }
            
            // 运算符
            return readOperator();
        }
        
        private Token readNumber() {
            int start = position;
            boolean hasDecimal = false;
            
            while (position < expression.length()) {
                char ch = expression.charAt(position);
                if (Character.isDigit(ch)) {
                    position++;
                } else if (ch == '.' && !hasDecimal) {
                    hasDecimal = true;
                    position++;
                } else {
                    break;
                }
            }
            
            String numberStr = expression.substring(start, position);
            Number value = hasDecimal ? Double.valueOf(numberStr) : Long.valueOf(numberStr);
            return new Token(TokenType.NUMBER, numberStr, value);
        }
        
        private Token readString() {
            char quote = expression.charAt(position);
            position++; // 跳过开始引号
            
            StringBuilder sb = new StringBuilder();
            
            while (position < expression.length()) {
                char ch = expression.charAt(position);
                
                if (ch == quote) {
                    position++; // 跳过结束引号
                    break;
                } else if (ch == '\' && position + 1 < expression.length()) {
                    // 转义字符
                    position++;
                    char escaped = expression.charAt(position);
                    switch (escaped) {
                        case 'n': sb.append('\n'); break;
                        case 't': sb.append('\t'); break;
                        case 'r': sb.append('\r'); break;
                        case '\': sb.append('\'); break;
                        case '"': sb.append('"'); break;
                        case ''': sb.append('''); break;
                        default: sb.append(escaped); break;
                    }
                    position++;
                } else {
                    sb.append(ch);
                    position++;
                }
            }
            
            return new Token(TokenType.STRING, sb.toString(), sb.toString());
        }
        
        private Token readIdentifier() {
            int start = position;
            
            while (position < expression.length()) {
                char ch = expression.charAt(position);
                if (Character.isLetterOrDigit(ch) || ch == '_' || ch == '.') {
                    position++;
                } else {
                    break;
                }
            }
            
            String identifier = expression.substring(start, position);
            
            // 检查是否为关键字
            TokenType type = getKeywordType(identifier);
            if (type != null) {
                return new Token(type, identifier, identifier);
            }
            
            return new Token(TokenType.IDENTIFIER, identifier, identifier);
        }
        
        private Token readOperator() {
            char ch = expression.charAt(position);
            
            // 双字符运算符
            if (position + 1 < expression.length()) {
                String twoChar = expression.substring(position, position + 2);
                TokenType type = getOperatorType(twoChar);
                if (type != null) {
                    position += 2;
                    return new Token(type, twoChar, twoChar);
                }
            }
            
            // 单字符运算符
            String oneChar = String.valueOf(ch);
            TokenType type = getOperatorType(oneChar);
            if (type != null) {
                position++;
                return new Token(type, oneChar, oneChar);
            }
            
            throw new IllegalArgumentException("未知的字符: " + ch);
        }
        
        private TokenType getKeywordType(String keyword) {
            switch (keyword.toLowerCase()) {
                case "true": return TokenType.BOOLEAN;
                case "false": return TokenType.BOOLEAN;
                case "null": return TokenType.NULL;
                case "and": return TokenType.AND;
                case "or": return TokenType.OR;
                case "not": return TokenType.NOT;
                case "in": return TokenType.IN;
                default: return null;
            }
        }
        
        private TokenType getOperatorType(String operator) {
            switch (operator) {
                case "+": return TokenType.PLUS;
                case "-": return TokenType.MINUS;
                case "*": return TokenType.MULTIPLY;
                case "/": return TokenType.DIVIDE;
                case "%": return TokenType.MODULO;
                case "==": return TokenType.EQUALS;
                case "!=": return TokenType.NOT_EQUALS;
                case "<": return TokenType.LESS_THAN;
                case "<=": return TokenType.LESS_EQUALS;
                case ">": return TokenType.GREATER_THAN;
                case ">=": return TokenType.GREATER_EQUALS;
                case "&&": return TokenType.AND;
                case "||": return TokenType.OR;
                case "!": return TokenType.NOT;
                case "(": return TokenType.LEFT_PAREN;
                case ")": return TokenType.RIGHT_PAREN;
                case "[": return TokenType.LEFT_BRACKET;
                case "]": return TokenType.RIGHT_BRACKET;
                case ",": return TokenType.COMMA;
                case ".": return TokenType.DOT;
                default: return null;
            }
        }
    }
    
    /**
     * AST构建器
     */
    private static class ASTBuilder {
        private final List<Token> tokens;
        private final Map<String, ExpressionFunction> functions;
        private int position = 0;
        
        public ASTBuilder(List<Token> tokens, Map<String, ExpressionFunction> functions) {
            this.tokens = tokens;
            this.functions = functions;
        }
        
        public ASTNode buildAST() {
            return parseOrExpression();
        }
        
        private ASTNode parseOrExpression() {
            ASTNode left = parseAndExpression();
            
            while (match(TokenType.OR)) {
                Token operator = previous();
                ASTNode right = parseAndExpression();
                left = new BinaryOpNode(left, operator, right);
            }
            
            return left;
        }
        
        private ASTNode parseAndExpression() {
            ASTNode left = parseEqualityExpression();
            
            while (match(TokenType.AND)) {
                Token operator = previous();
                ASTNode right = parseEqualityExpression();
                left = new BinaryOpNode(left, operator, right);
            }
            
            return left;
        }
        
        private ASTNode parseEqualityExpression() {
            ASTNode left = parseComparisonExpression();
            
            while (match(TokenType.EQUALS, TokenType.NOT_EQUALS)) {
                Token operator = previous();
                ASTNode right = parseComparisonExpression();
                left = new BinaryOpNode(left, operator, right);
            }
            
            return left;
        }
        
        private ASTNode parseComparisonExpression() {
            ASTNode left = parseAdditiveExpression();
            
            while (match(TokenType.LESS_THAN, TokenType.LESS_EQUALS, 
                        TokenType.GREATER_THAN, TokenType.GREATER_EQUALS)) {
                Token operator = previous();
                ASTNode right = parseAdditiveExpression();
                left = new BinaryOpNode(left, operator, right);
            }
            
            return left;
        }
        
        private ASTNode parseAdditiveExpression() {
            ASTNode left = parseMultiplicativeExpression();
            
            while (match(TokenType.PLUS, TokenType.MINUS)) {
                Token operator = previous();
                ASTNode right = parseMultiplicativeExpression();
                left = new BinaryOpNode(left, operator, right);
            }
            
            return left;
        }
        
        private ASTNode parseMultiplicativeExpression() {
            ASTNode left = parseUnaryExpression();
            
            while (match(TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.MODULO)) {
                Token operator = previous();
                ASTNode right = parseUnaryExpression();
                left = new BinaryOpNode(left, operator, right);
            }
            
            return left;
        }
        
        private ASTNode parseUnaryExpression() {
            if (match(TokenType.NOT, TokenType.MINUS)) {
                Token operator = previous();
                ASTNode operand = parseUnaryExpression();
                return new UnaryOpNode(operator, operand);
            }
            
            return parsePostfixExpression();
        }
        
        private ASTNode parsePostfixExpression() {
            ASTNode left = parsePrimaryExpression();
            
            while (true) {
                if (match(TokenType.DOT)) {
                    Token property = consume(TokenType.IDENTIFIER, "期望属性名");
                    left = new PropertyAccessNode(left, property);
                } else if (match(TokenType.LEFT_BRACKET)) {
                    ASTNode index = parseOrExpression();
                    consume(TokenType.RIGHT_BRACKET, "期望 ']'");
                    left = new IndexAccessNode(left, index);
                } else if (match(TokenType.LEFT_PAREN) && left instanceof IdentifierNode) {
                    // 函数调用
                    List<ASTNode> arguments = new ArrayList<>();
                    if (!check(TokenType.RIGHT_PAREN)) {
                        do {
                            arguments.add(parseOrExpression());
                        } while (match(TokenType.COMMA));
                    }
                    consume(TokenType.RIGHT_PAREN, "期望 ')')");
                    left = new FunctionCallNode(((IdentifierNode) left).getName(), arguments, functions);
                } else {
                    break;
                }
            }
            
            return left;
        }
        
        private ASTNode parsePrimaryExpression() {
            if (match(TokenType.BOOLEAN)) {
                return new LiteralNode(Boolean.valueOf(previous().getLexeme()));
            }
            
            if (match(TokenType.NULL)) {
                return new LiteralNode(null);
            }
            
            if (match(TokenType.NUMBER)) {
                return new LiteralNode(previous().getValue());
            }
            
            if (match(TokenType.STRING)) {
                return new LiteralNode(previous().getValue());
            }
            
            if (match(TokenType.IDENTIFIER)) {
                return new IdentifierNode(previous().getLexeme());
            }
            
            if (match(TokenType.LEFT_PAREN)) {
                ASTNode expression = parseOrExpression();
                consume(TokenType.RIGHT_PAREN, "期望 ')')");
                return expression;
            }
            
            throw new RuntimeException("意外的标记: " + peek().getLexeme());
        }
        
        private boolean match(TokenType... types) {
            for (TokenType type : types) {
                if (check(type)) {
                    advance();
                    return true;
                }
            }
            return false;
        }
        
        private boolean check(TokenType type) {
            if (isAtEnd()) return false;
            return peek().getType() == type;
        }
        
        private Token advance() {
            if (!isAtEnd()) position++;
            return previous();
        }
        
        private boolean isAtEnd() {
            return position >= tokens.size();
        }
        
        private Token peek() {
            return tokens.get(position);
        }
        
        private Token previous() {
            return tokens.get(position - 1);
        }
        
        private Token consume(TokenType type, String message) {
            if (check(type)) return advance();
            throw new RuntimeException(message + ", 但得到: " + peek().getLexeme());
        }
    }
}

⚡ ScriptExpressionEvaluator实现

java 复制代码
/**
 * 脚本表达式评估器
 * 支持多种脚本语言的动态执行
 */
public class ScriptExpressionEvaluator implements ExpressionEvaluator {
    
    private static final Logger logger = LoggerFactory.getLogger(ScriptExpressionEvaluator.class);
    
    private final Map<String, ScriptEngine> scriptEngines = new ConcurrentHashMap<>();
    private final Map<String, ExpressionFunction> functions = new ConcurrentHashMap<>();
    private final SecurityManager securityManager;
    
    public ScriptExpressionEvaluator() {
        this(new DefaultSecurityManager());
    }
    
    public ScriptExpressionEvaluator(SecurityManager securityManager) {
        this.securityManager = securityManager;
        initializeScriptEngines();
        registerBuiltinFunctions();
    }
    
    @Override
    public Object evaluate(String expression, FlowContext context) throws ExpressionEvaluationException {
        ScriptExpression scriptExpr = parseScriptExpression(expression);
        
        try {
            return securityManager.executeSecurely(() -> {
                ScriptEngine engine = getScriptEngine(scriptExpr.getLanguage());
                setupScriptContext(engine, context);
                return engine.eval(scriptExpr.getScript());
            });
        } catch (Exception e) {
            throw new ExpressionEvaluationException(
                "脚本执行失败: " + expression, e);
        }
    }
    
    @Override
    public boolean evaluateBoolean(String expression, FlowContext context) 
            throws ExpressionEvaluationException {
        Object result = evaluate(expression, context);
        return convertToBoolean(result);
    }
    
    @Override
    public <T> T evaluate(String expression, FlowContext context, Class<T> expectedType) 
            throws ExpressionEvaluationException {
        Object result = evaluate(expression, context);
        return convertToType(result, expectedType);
    }
    
    @Override
    public Expression compile(String expression) throws ExpressionCompilationException {
        try {
            ScriptExpression scriptExpr = parseScriptExpression(expression);
            ScriptEngine engine = getScriptEngine(scriptExpr.getLanguage());
            
            // 尝试编译脚本(如果引擎支持)
            if (engine instanceof Compilable) {
                CompiledScript compiled = ((Compilable) engine).compile(scriptExpr.getScript());
                return new CompiledScriptExpression(expression, scriptExpr.getLanguage(), compiled);
            } else {
                return new InterpretedScriptExpression(expression, scriptExpr.getLanguage(), scriptExpr.getScript());
            }
        } catch (Exception e) {
            throw new ExpressionCompilationException(
                "脚本编译失败: " + expression, e);
        }
    }
    
    @Override
    public ValidationResult validate(String expression) {
        try {
            compile(expression);
            return ValidationResult.success();
        } catch (ExpressionCompilationException e) {
            return ValidationResult.error(e.getMessage());
        }
    }
    
    @Override
    public Set<String> getSupportedFunctions() {
        return new HashSet<>(functions.keySet());
    }
    
    @Override
    public void registerFunction(String name, ExpressionFunction function) {
        functions.put(name, function);
        logger.debug("注册脚本函数: {}", name);
    }
    
    /**
     * 初始化脚本引擎
     */
    private void initializeScriptEngines() {
        ScriptEngineManager manager = new ScriptEngineManager();
        
        // JavaScript引擎
        ScriptEngine jsEngine = manager.getEngineByName("javascript");
        if (jsEngine != null) {
            scriptEngines.put("javascript", jsEngine);
            scriptEngines.put("js", jsEngine);
        }
        
        // Groovy引擎
        ScriptEngine groovyEngine = manager.getEngineByName("groovy");
        if (groovyEngine != null) {
            scriptEngines.put("groovy", groovyEngine);
        }
        
        // Kotlin引擎
        ScriptEngine kotlinEngine = manager.getEngineByName("kotlin");
        if (kotlinEngine != null) {
            scriptEngines.put("kotlin", kotlinEngine);
            scriptEngines.put("kts", kotlinEngine);
        }
        
        logger.info("已初始化脚本引擎: {}", scriptEngines.keySet());
    }
    
    /**
     * 解析脚本表达式
     */
    private ScriptExpression parseScriptExpression(String expression) {
        // 格式: language:script 或者 直接的脚本代码(默认JavaScript)
        int colonIndex = expression.indexOf(':');
        if (colonIndex > 0 && colonIndex < 20) { // 语言名不应该太长
            String language = expression.substring(0, colonIndex).trim();
            String script = expression.substring(colonIndex + 1).trim();
            return new ScriptExpression(language, script);
        } else {
            // 默认使用JavaScript
            return new ScriptExpression("javascript", expression);
        }
    }
    
    /**
     * 获取脚本引擎
     */
    private ScriptEngine getScriptEngine(String language) {
        ScriptEngine engine = scriptEngines.get(language.toLowerCase());
        if (engine == null) {
            throw new IllegalArgumentException(
                "不支持的脚本语言: " + language + ", 可用语言: " + scriptEngines.keySet());
        }
        return engine;
    }
    
    /**
     * 设置脚本上下文
     */
    private void setupScriptContext(ScriptEngine engine, FlowContext context) {
        Bindings bindings = engine.createBindings();
        
        // 添加上下文数据
        bindings.put("context", context);
        
        // 添加所有上下文变量
        for (Map.Entry<String, Object> entry : context.getAll().entrySet()) {
            bindings.put(entry.getKey(), entry.getValue());
        }
        
        // 添加自定义函数
        for (Map.Entry<String, ExpressionFunction> entry : functions.entrySet()) {
            bindings.put(entry.getKey(), entry.getValue());
        }
        
        // 添加工具对象
        bindings.put("logger", logger);
        bindings.put("System", System.class);
        bindings.put("Math", Math.class);
        
        engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
    }
    
    /**
     * 注册内置函数
     */
    private void registerBuiltinFunctions() {
        // 这里可以注册一些脚本专用的函数
        registerFunction("print", args -> {
            for (Object arg : args) {
                System.out.print(arg);
            }
            return null;
        });
        
        registerFunction("println", args -> {
            for (Object arg : args) {
                System.out.print(arg);
            }
            System.out.println();
            return null;
        });
    }
    
    /**
     * 转换为布尔值
     */
    private boolean convertToBoolean(Object value) {
        if (value == null) return false;
        if (value instanceof Boolean) return (Boolean) value;
        if (value instanceof Number) return ((Number) value).doubleValue() != 0;
        if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"false".equalsIgnoreCase(str);
        }
        return true;
    }
    
    /**
     * 类型转换
     */
    @SuppressWarnings("unchecked")
    private <T> T convertToType(Object value, Class<T> expectedType) {
        if (value == null) return null;
        if (expectedType.isInstance(value)) return (T) value;
        
        // 基本的类型转换逻辑(与SimpleExpressionEvaluator类似)
        // ...
        
        throw new IllegalArgumentException(
            String.format("无法将 %s 转换为 %s", 
                value.getClass().getSimpleName(), expectedType.getSimpleName())
        );
    }
    
    /**
     * 脚本表达式数据结构
     */
    private static class ScriptExpression {
        private final String language;
        private final String script;
        
        public ScriptExpression(String language, String script) {
            this.language = language;
            this.script = script;
        }
        
        public String getLanguage() { return language; }
        public String getScript() { return script; }
    }
    
    /**
     * 编译后的脚本表达式
     */
    private class CompiledScriptExpression implements Expression {
        private final String originalExpression;
        private final String language;
        private final CompiledScript compiledScript;
        
        public CompiledScriptExpression(String originalExpression, String language, CompiledScript compiledScript) {
            this.originalExpression = originalExpression;
            this.language = language;
            this.compiledScript = compiledScript;
        }
        
        @Override
        public Object execute(FlowContext context) throws ExpressionEvaluationException {
            try {
                return securityManager.executeSecurely(() -> {
                    setupScriptContext(compiledScript.getEngine(), context);
                    return compiledScript.eval();
                });
            } catch (Exception e) {
                throw new ExpressionEvaluationException(
                    "编译脚本执行失败: " + originalExpression, e);
            }
        }
        
        @Override
        public String getExpressionString() {
            return originalExpression;
        }
        
        @Override
        public Set<String> getDependentVariables() {
            // 脚本的依赖变量分析比较复杂,这里简化处理
            return Collections.emptySet();
        }
        
        @Override
        public ExpressionType getType() {
            return ExpressionType.valueOf(language.toUpperCase());
        }
    }
    
    /**
     * 解释执行的脚本表达式
     */
    private class InterpretedScriptExpression implements Expression {
        private final String originalExpression;
        private final String language;
        private final String script;
        
        public InterpretedScriptExpression(String originalExpression, String language, String script) {
            this.originalExpression = originalExpression;
            this.language = language;
            this.script = script;
        }
        
        @Override
        public Object execute(FlowContext context) throws ExpressionEvaluationException {
            try {
                return securityManager.executeSecurely(() -> {
                    ScriptEngine engine = getScriptEngine(language);
                    setupScriptContext(engine, context);
                    return engine.eval(script);
                });
            } catch (Exception e) {
                throw new ExpressionEvaluationException(
                    "解释脚本执行失败: " + originalExpression, e);
            }
        }
        
        @Override
        public String getExpressionString() {
            return originalExpression;
        }
        
        @Override
        public Set<String> getDependentVariables() {
            return Collections.emptySet();
        }
        
        @Override
        public ExpressionType getType() {
            return ExpressionType.valueOf(language.toUpperCase());
        }
    }
}

🛡️ 安全管理器实现

java 复制代码
/**
 * 表达式安全管理器
 * 提供沙箱环境和资源限制
 */
public class DefaultSecurityManager implements SecurityManager {
    
    private static final Logger logger = LoggerFactory.getLogger(DefaultSecurityManager.class);
    
    private final long maxExecutionTime;
    private final int maxMemoryUsage;
    private final Set<String> allowedClasses;
    private final Set<String> forbiddenMethods;
    
    public DefaultSecurityManager() {
        this(5000, 10 * 1024 * 1024); // 默认5秒超时,10MB内存限制
    }
    
    public DefaultSecurityManager(long maxExecutionTime, int maxMemoryUsage) {
        this.maxExecutionTime = maxExecutionTime;
        this.maxMemoryUsage = maxMemoryUsage;
        this.allowedClasses = initializeAllowedClasses();
        this.forbiddenMethods = initializeForbiddenMethods();
    }
    
    @Override
    public <T> T executeSecurely(Callable<T> task) throws Exception {
        // 创建独立的线程执行任务
        ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
            Thread thread = new Thread(r, "expression-executor");
            thread.setDaemon(true);
            return thread;
        });
        
        try {
            Future<T> future = executor.submit(() -> {
                // 设置安全管理器
                SecurityManager oldSecurityManager = System.getSecurityManager();
                System.setSecurityManager(new RestrictiveSecurityManager());
                
                try {
                    return task.call();
                } finally {
                    System.setSecurityManager(oldSecurityManager);
                }
            });
            
            return future.get(maxExecutionTime, TimeUnit.MILLISECONDS);
            
        } catch (TimeoutException e) {
            throw new ExpressionEvaluationException("表达式执行超时: " + maxExecutionTime + "ms");
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof Exception) {
                throw (Exception) cause;
            } else {
                throw new ExpressionEvaluationException("表达式执行异常", cause);
            }
        } finally {
            executor.shutdownNow();
        }
    }
    
    /**
     * 初始化允许的类
     */
    private Set<String> initializeAllowedClasses() {
        Set<String> allowed = new HashSet<>();
        
        // 基本类型
        allowed.add("java.lang.String");
        allowed.add("java.lang.Integer");
        allowed.add("java.lang.Long");
        allowed.add("java.lang.Double");
        allowed.add("java.lang.Float");
        allowed.add("java.lang.Boolean");
        allowed.add("java.lang.Character");
        allowed.add("java.lang.Byte");
        allowed.add("java.lang.Short");
        
        // 数学类
        allowed.add("java.lang.Math");
        allowed.add("java.math.BigDecimal");
        allowed.add("java.math.BigInteger");
        
        // 日期时间类
        allowed.add("java.util.Date");
        allowed.add("java.util.Calendar");
        allowed.add("java.time.*");
        
        // 集合类
        allowed.add("java.util.List");
        allowed.add("java.util.Map");
        allowed.add("java.util.Set");
        allowed.add("java.util.ArrayList");
        allowed.add("java.util.HashMap");
        allowed.add("java.util.HashSet");
        
        return allowed;
    }
    
    /**
     * 初始化禁止的方法
     */
    private Set<String> initializeForbiddenMethods() {
        Set<String> forbidden = new HashSet<>();
        
        // 系统操作
        forbidden.add("System.exit");
        forbidden.add("System.gc");
        forbidden.add("Runtime.exec");
        forbidden.add("Runtime.getRuntime");
        
        // 文件操作
        forbidden.add("File.*");
        forbidden.add("FileInputStream.*");
        forbidden.add("FileOutputStream.*");
        
        // 网络操作
        forbidden.add("Socket.*");
        forbidden.add("ServerSocket.*");
        forbidden.add("URL.*");
        forbidden.add("URLConnection.*");
        
        // 反射操作
        forbidden.add("Class.forName");
        forbidden.add("Method.invoke");
        forbidden.add("Constructor.newInstance");
        
        return forbidden;
    }
    
    /**
     * 限制性安全管理器
     */
    private class RestrictiveSecurityManager extends java.lang.SecurityManager {
        
        @Override
        public void checkPermission(Permission perm) {
            // 检查是否为禁止的操作
            String permName = perm.getName();
            
            if (permName.startsWith("exitVM")) {
                throw new SecurityException("禁止退出虚拟机");
            }
            
            if (permName.startsWith("createClassLoader")) {
                throw new SecurityException("禁止创建类加载器");
            }
            
            if (permName.startsWith("setSecurityManager")) {
                throw new SecurityException("禁止设置安全管理器");
            }
            
            if (perm instanceof FilePermission) {
                throw new SecurityException("禁止文件操作: " + permName);
            }
            
            if (perm instanceof SocketPermission) {
                throw new SecurityException("禁止网络操作: " + permName);
            }
            
            if (perm instanceof RuntimePermission) {
                if (permName.startsWith("createClassLoader") || 
                    permName.startsWith("setContextClassLoader") ||
                    permName.startsWith("enableContextClassLoaderOverride")) {
                    throw new SecurityException("禁止类加载器操作: " + permName);
                }
            }
        }
        
        @Override
        public void checkExec(String cmd) {
            throw new SecurityException("禁止执行外部命令: " + cmd);
        }
        
        @Override
        public void checkRead(String file) {
            throw new SecurityException("禁止读取文件: " + file);
        }
        
        @Override
        public void checkWrite(String file) {
            throw new SecurityException("禁止写入文件: " + file);
        }
        
        @Override
        public void checkDelete(String file) {
            throw new SecurityException("禁止删除文件: " + file);
        }
        
        @Override
        public void checkConnect(String host, int port) {
            throw new SecurityException("禁止网络连接: " + host + ":" + port);
        }
    }
}

🧪 表达式引擎测试

java 复制代码
public class ExpressionEvaluatorTest {
    
    private ExpressionEvaluator simpleEvaluator;
    private ExpressionEvaluator scriptEvaluator;
    private FlowContext context;
    
    @Before
    public void setUp() {
        simpleEvaluator = new SimpleExpressionEvaluator();
        scriptEvaluator = new ScriptExpressionEvaluator();
        context = new DefaultFlowContext();
        
        // 设置测试数据
        context.put("name", "张三");
        context.put("age", 25);
        context.put("score", 85.5);
        context.put("active", true);
        context.put("tags", Arrays.asList("java", "spring", "mysql"));
        
        Map<String, Object> user = new HashMap<>();
        user.put("id", 1001);
        user.put("name", "李四");
        user.put("email", "lisi@example.com");
        context.put("user", user);
    }
    
    @Test
    public void testSimpleArithmetic() {
        // 算术运算
        assertEquals(30, simpleEvaluator.evaluate("age + 5", context));
        assertEquals(20, simpleEvaluator.evaluate("age - 5", context));
        assertEquals(50, simpleEvaluator.evaluate("age * 2", context));
        assertEquals(12.5, simpleEvaluator.evaluate("age / 2", context));
        assertEquals(1, simpleEvaluator.evaluate("age % 3", context));
        
        // 复合运算
        assertEquals(95.5, simpleEvaluator.evaluate("score + age / 2.5", context));
    }
    
    @Test
    public void testSimpleComparison() {
        // 比较运算
        assertTrue(simpleEvaluator.evaluateBoolean("age > 18", context));
        assertFalse(simpleEvaluator.evaluateBoolean("age < 18", context));
        assertTrue(simpleEvaluator.evaluateBoolean("age >= 25", context));
        assertTrue(simpleEvaluator.evaluateBoolean("age <= 25", context));
        assertTrue(simpleEvaluator.evaluateBoolean("age == 25", context));
        assertFalse(simpleEvaluator.evaluateBoolean("age != 25", context));
    }
    
    @Test
    public void testSimpleLogical() {
        // 逻辑运算
        assertTrue(simpleEvaluator.evaluateBoolean("age > 18 && active", context));
        assertTrue(simpleEvaluator.evaluateBoolean("age < 18 || active", context));
        assertFalse(simpleEvaluator.evaluateBoolean("!active", context));
        
        // 复合逻辑
        assertTrue(simpleEvaluator.evaluateBoolean(
            "(age > 18 && score > 80) || name == '张三'", context));
    }
    
    @Test
    public void testSimpleFunctions() {
        // 字符串函数
        assertEquals(2, simpleEvaluator.evaluate("length(name)", context));
        assertEquals("张三", simpleEvaluator.evaluate("upper(name)", context));
        assertEquals("张三", simpleEvaluator.evaluate("lower(name)", context));
        
        // 数学函数
        assertEquals(25.0, simpleEvaluator.evaluate("abs(age)", context));
        assertEquals(85.5, simpleEvaluator.evaluate("max(age, score)", context));
        assertEquals(25.0, simpleEvaluator.evaluate("min(age, score)", context));
        
        // 类型转换函数
        assertEquals("25", simpleEvaluator.evaluate("toString(age)", context));
        assertEquals(85, simpleEvaluator.evaluate("toInt(score)", context));
        
        // 条件函数
        assertEquals("成年", simpleEvaluator.evaluate(
            "if(age >= 18, '成年', '未成年')", context));
    }
    
    @Test
    public void testPropertyAccess() {
        // 属性访问
        assertEquals(1001, simpleEvaluator.evaluate("user.id", context));
        assertEquals("李四", simpleEvaluator.evaluate("user.name", context));
        assertEquals("lisi@example.com", simpleEvaluator.evaluate("user.email", context));
        
        // 数组/列表访问
        assertEquals("java", simpleEvaluator.evaluate("tags[0]", context));
        assertEquals("spring", simpleEvaluator.evaluate("tags[1]", context));
        assertEquals(3, simpleEvaluator.evaluate("size(tags)", context));
    }
    
    @Test
    public void testScriptEvaluation() {
        // JavaScript脚本
        assertEquals(30, scriptEvaluator.evaluate(
            "javascript: age + 5", context));
        
        assertEquals("张三-25", scriptEvaluator.evaluate(
            "javascript: name + '-' + age", context));
        
        // 复杂脚本
        String script = "javascript: " +
            "var result = 0; " +
            "for (var i = 0; i < age; i++) { " +
            "  result += i; " +
            "} " +
            "result;";
        
        assertEquals(300, scriptEvaluator.evaluate(script, context)); // 0+1+2+...+24 = 300
        
        // Groovy脚本
        if (scriptEvaluator.getSupportedFunctions().contains("groovy")) {
            assertEquals("Hello 张三!", scriptEvaluator.evaluate(
                "groovy: 'Hello ' + name + '!'", context));
        }
    }
    
    @Test
    public void testExpressionCompilation() {
        // 编译简单表达式
        Expression expr1 = simpleEvaluator.compile("age * 2 + score");
        assertEquals(135.5, expr1.execute(context));
        
        // 编译脚本表达式
        Expression expr2 = scriptEvaluator.compile("javascript: Math.max(age, score)");
        assertEquals(85.5, expr2.execute(context));
    }
    
    @Test
    public void testExpressionValidation() {
        // 有效表达式
        ValidationResult valid = simpleEvaluator.validate("age + score");
        assertTrue(valid.isValid());
        
        // 无效表达式
        ValidationResult invalid = simpleEvaluator.validate("age +");
        assertFalse(invalid.isValid());
        assertNotNull(invalid.getErrorMessage());
    }
    
    @Test
    public void testCustomFunctions() {
        // 注册自定义函数
        simpleEvaluator.registerFunction("double", args -> 
            ((Number) args[0]).doubleValue() * 2);
        
        assertEquals(50.0, simpleEvaluator.evaluate("double(age)", context));
        
        // 注册复杂函数
        simpleEvaluator.registerFunction("formatUser", args -> {
            String name = args[0].toString();
            int age = ((Number) args[1]).intValue();
            return String.format("%s(%d岁)", name, age);
        });
        
        assertEquals("张三(25岁)", simpleEvaluator.evaluate(
            "formatUser(name, age)", context));
    }
    
    @Test
    public void testSecurityRestrictions() {
        // 测试安全限制
        try {
            scriptEvaluator.evaluate(
                "javascript: java.lang.System.exit(0)", context);
            fail("应该抛出安全异常");
        } catch (ExpressionEvaluationException e) {
            assertTrue(e.getCause() instanceof SecurityException);
        }
        
        try {
            scriptEvaluator.evaluate(
                "javascript: new java.io.File('/etc/passwd')", context);
            fail("应该抛出安全异常");
        } catch (ExpressionEvaluationException e) {
            assertTrue(e.getCause() instanceof SecurityException);
        }
    }
    
    @Test
    public void testPerformance() {
        // 性能测试
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < 1000; i++) {
            simpleEvaluator.evaluate("age * 2 + score - 10", context);
        }
        
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        System.out.println("1000次简单表达式评估耗时: " + duration + "ms");
        assertTrue("性能测试失败,耗时过长", duration < 1000); // 应该在1秒内完成
    }
}

🎯 设计亮点

1. 🏗️ 分层架构设计

  • 接口层: 统一的表达式评估接口
  • 解析层: 灵活的语法解析和AST构建
  • 执行层: 多种执行引擎支持
  • 函数层: 丰富的内置函数库
  • 安全层: 完善的安全控制机制

2. 🔧 多引擎支持

  • SimpleExpressionEvaluator: 轻量级,适合简单表达式
  • ScriptExpressionEvaluator: 功能强大,支持多种脚本语言
  • SpELExpressionEvaluator: 集成Spring表达式语言
  • GroovyExpressionEvaluator: 支持Groovy动态语言

3. 🛡️ 安全机制

  • 沙箱执行: 限制危险操作
  • 资源控制: 防止资源耗尽
  • 超时保护: 避免无限循环
  • 权限检查: 细粒度的安全控制

4. ⚡ 性能优化

  • 表达式编译: 预编译提高执行效率
  • 结果缓存: 避免重复计算
  • 惰性求值: 按需计算表达式
  • 并发安全: 支持多线程环境

📚 本章小结

在本章中,我们实现了一个功能强大且安全的表达式引擎:

  1. 🎯 核心功能: 支持算术、逻辑、比较运算和函数调用
  2. 🔧 多语言支持: JavaScript、Groovy、Kotlin等脚本语言
  3. 🛡️ 安全保障: 沙箱执行和资源限制
  4. ⚡ 高性能: 表达式编译和缓存优化
  5. 🔌 可扩展: 自定义函数和运算符支持

表达式引擎为流程编排提供了强大的动态计算能力,使得条件判断、数据转换和业务逻辑变得更加灵活和强大。

下一章预告: 我们将探讨YAML配置解析器的实现,让流程定义更加直观和易于维护。 🚀

相关推荐
红尘散仙1 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记2 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆3 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪3 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6163 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364573 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao4 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒5 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰6 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理
Apifox6 小时前
Apifox 5 月更新|Postman 导入优化、Runner 支持非 root 运行、请求代码自动带鉴权
前端·后端·安全