# 脚本语言 (19_JavaScript)

最后更新: 2024-09-20


# 📚 概述

DarkM 框架的 JavaScript 模块提供了在 .NET 中执行 JavaScript 代码的能力。支持多种 JavaScript 引擎(ChakraCore、Node.js),实现动态脚本执行、规则引擎等功能。

源代码位置: DarkM/src/Framework/JavaScript


# 🏗️ 模块架构

# 项目结构

JavaScript/
├── JavaScript.Abstractions/           # 抽象层(接口、配置、枚举)
│   ├── IJavaScriptHandler.cs          # JavaScript 处理接口
│   ├── JavaScriptConfig.cs            # JavaScript 配置类
│   └── JavaScriptProvider.cs          # JavaScript 提供器枚举
│
├── JavaScript.Integration/            # 集成层(DI 注册)
│   └── ServiceCollectionExtensions.cs  # DI 扩展
│
├── JavaScript.ChakraCore/             # ChakraCore 实现
│   ├── ChakraCoreHandler.cs           # ChakraCore 处理器
│   └── ChakraCoreHelper.cs            # ChakraCore 帮助类
│
└── JavaScript.NodeJs/                 # Node.js 实现
    ├── NodeJsHandler.cs               # Node.js 处理器
    └── NodeJsHelper.cs                # Node.js 帮助类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 🔧 核心接口

# IJavaScriptHandler(JavaScript 处理)

/// <summary>
/// JavaScript 处理类
/// </summary>
public interface IJavaScriptHandler
{
    /// <summary>
    /// 异步执行 Js 方法
    /// </summary>
    /// <param name="module">Js 模块名</param>
    /// <param name="functionName">Js 函数名</param>
    /// <param name="args">Js 函数的参数</param>
    /// <returns>执行结果</returns>
    Task<T> ExecuteAsync<T>(
        string module,
        string functionName,
        params object[] args);

    /// <summary>
    /// 执行 js 代码
    /// </summary>
    /// <typeparam name="T">返回类型</typeparam>
    /// <param name="jsSourceCode">需要执行的 js 代码</param>
    /// <returns>执行结果</returns>
    Task<T> ExecuteAsync<T>(string jsSourceCode);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

功能说明:

  • ExecuteAsync<T>(module, functionName, args) - 执行指定模块的 JS 函数
  • ExecuteAsync<T>(jsSourceCode) - 直接执行 JS 代码字符串

# JavaScriptProvider(JavaScript 提供器)

[Description("JavaScript 提供器")]
public enum JavaScriptProvider
{
    /// <summary>
    /// Microsoft ChakraCore 引擎
    /// </summary>
    ChakraCore,

    /// <summary>
    /// Node.js 引擎
    /// </summary>
    NodeJs
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# JavaScriptConfig(JavaScript 配置)

public class JavaScriptConfig
{
    /// <summary>
    /// 提供器
    /// </summary>
    public JavaScriptProvider Provider { get; set; } 
        = JavaScriptProvider.ChakraCore;
}
1
2
3
4
5
6
7
8

# 🏗️ 依赖注入

# 自动注册机制

// JavaScript.Integration/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// 添加 JavaScript 功能
    /// </summary>
    public static IServiceCollection AddJavaScript(
        this IServiceCollection services, 
        IConfiguration cfg)
    {
        var config = new JavaScriptConfig();
        var section = cfg.GetSection("JavaScript");
        
        if (section != null)
        {
            section.Bind(config);
        }

        services.AddSingleton(config);

        // 根据配置加载对应的实现程序集
        var assembly = AssemblyHelper.LoadByNameEndString(
            $".Lib.JavaScript.{config.Provider.ToString()}");
        
        if (assembly == null)
            return services;

        // 注册 JavaScript 处理器
        var handlerType = assembly.GetTypes()
            .FirstOrDefault(m => m.Name.EndsWith("Handler"));
        
        if (handlerType != null)
        {
            services.AddSingleton(typeof(IJavaScriptHandler), handlerType);
        }

        return services;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 💡 使用示例

# 1. 配置 JavaScript 模块

// appsettings.json
{
  "JavaScript": {
    "Provider": "ChakraCore"  // 或 NodeJs
  }
}
1
2
3
4
5
6

# 2. 注册服务

// Program.cs 或 Startup.cs
var builder = WebApplication.CreateBuilder(args);

// 添加 JavaScript 服务
builder.Services.AddJavaScript(builder.Configuration);

var app = builder.Build();
1
2
3
4
5
6
7

# 3. 执行简单 JS 代码

public class JavaScriptExecutionService
{
    private readonly IJavaScriptHandler _jsHandler;
    private readonly ILogger<JavaScriptExecutionService> _logger;

    public JavaScriptExecutionService(
        IJavaScriptHandler jsHandler,
        ILogger<JavaScriptExecutionService> logger)
    {
        _jsHandler = jsHandler;
        _logger = logger;
    }

    /// <summary>
    /// 执行简单计算
    /// </summary>
    public async Task<int> CalculateAsync()
    {
        var jsCode = @"
            var a = 10;
            var b = 20;
            a + b;
        ";

        var result = await _jsHandler.ExecuteAsync<int>(jsCode);
        
        _logger.LogInformation("计算结果:{Result}", result);
        
        return result; // 30
    }

    /// <summary>
    /// 执行字符串处理
    /// </summary>
    public async Task<string> ProcessStringAsync()
    {
        var jsCode = @"
            var str = 'Hello, World!';
            str.toUpperCase();
        ";

        var result = await _jsHandler.ExecuteAsync<string>(jsCode);
        
        return result; // "HELLO, WORLD!"
    }

    /// <summary>
    /// 执行 JSON 处理
    /// </summary>
    public async Task<JsonObject> ProcessJsonAsync()
    {
        var jsCode = @"
            var data = { name: 'John', age: 30 };
            data.city = 'New York';
            data;
        ";

        var result = await _jsHandler.ExecuteAsync<JsonObject>(jsCode);
        
        return result; // { name: 'John', age: 30, city: 'New York' }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# 4. 执行 JS 模块函数

public class JsModuleService
{
    private readonly IJavaScriptHandler _jsHandler;

    public JsModuleService(IJavaScriptHandler jsHandler)
    {
        _jsHandler = jsHandler;
    }

    /// <summary>
    /// 调用 JS 模块函数
    /// </summary>
    public async Task<T> CallJsFunctionAsync<T>(
        string moduleName,
        string functionName,
        params object[] args)
    {
        return await _jsHandler.ExecuteAsync<T>(
            moduleName,
            functionName,
            args);
    }
}

// 使用示例
public class OrderCalculator
{
    private readonly JsModuleService _jsService;

    public OrderCalculator(JsModuleService jsService)
    {
        _jsService = jsService;
    }

    /// <summary>
    /// 计算订单折扣
    /// </summary>
    public async Task<decimal> CalculateDiscountAsync(
        decimal amount,
        string customerLevel,
        int points)
    {
        var discount = await _jsService.CallJsFunctionAsync<decimal>(
            "discount-rules",      // JS 模块名
            "calculateDiscount",    // 函数名
            amount,                 // 参数 1
            customerLevel,          // 参数 2
            points                  // 参数 3
        );

        return discount;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 5. 动态规则引擎

public class RuleEngineService
{
    private readonly IJavaScriptHandler _jsHandler;
    private readonly ILogger<RuleEngineService> _logger;
    private readonly IMemoryCache _cache;

    public RuleEngineService(
        IJavaScriptHandler jsHandler,
        ILogger<RuleEngineService> logger,
        IMemoryCache cache)
    {
        _jsHandler = jsHandler;
        _logger = logger;
        _cache = cache;
    }

    /// <summary>
    /// 执行动态规则
    /// </summary>
    public async Task<bool> ExecuteRuleAsync(
        string ruleCode,
        Dictionary<string, object> context)
    {
        // 从缓存获取编译后的规则
        var cacheKey = $"rule_{ruleCode}";
        
        var ruleScript = await _cache.GetOrCreateAsync(
            cacheKey,
            async entry =>
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
                
                // 从数据库或配置加载规则脚本
                var rule = await _ruleRepository.GetByCodeAsync(ruleCode);
                return rule.Script;
            });

        // 构建执行上下文
        var contextJson = JsonSerializer.Serialize(context);
        
        var jsCode = $@"
            var context = {contextJson};
            {ruleScript}
        ";

        try
        {
            var result = await _jsHandler.ExecuteAsync<bool>(jsCode);
            
            _logger.LogInformation(
                "规则 {RuleCode} 执行结果:{Result}",
                ruleCode,
                result);
            
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(
                ex,
                "规则 {RuleCode} 执行失败",
                ruleCode);
            
            throw;
        }
    }

    /// <summary>
    /// 执行价格计算规则
    /// </summary>
    public async Task<decimal> CalculatePriceAsync(
        string ruleCode,
        decimal basePrice,
        Dictionary<string, object> factors)
    {
        factors["basePrice"] = basePrice;
        
        var contextJson = JsonSerializer.Serialize(factors);
        
        var ruleScript = await _ruleRepository.GetByCodeAsync(ruleCode);
        
        var jsCode = $@"
            var context = {contextJson};
            {ruleScript}
        ";

        var result = await _jsHandler.ExecuteAsync<decimal>(jsCode);
        
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

# 6. JS 控制器示例

[Route("api/[controller]")]
[ApiController]
public class JavaScriptController : ModuleController
{
    private readonly IJavaScriptHandler _jsHandler;
    private readonly ILogger<JavaScriptController> _logger;

    public JavaScriptController(
        IJavaScriptHandler jsHandler,
        ILogger<JavaScriptController> logger)
    {
        _jsHandler = jsHandler;
        _logger = logger;
    }

    /// <summary>
    /// 执行 JS 代码
    /// </summary>
    [HttpPost("execute")]
    public async Task<IResultModel> ExecuteJs(
        [FromBody] ExecuteJsRequest request)
    {
        try
        {
            var result = await _jsHandler.ExecuteAsync<object>(request.Code);
            
            return ResultModel.Success(new
            {
                success = true,
                result = result
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "JS 执行失败");
            
            return ResultModel.Error($"JS 执行失败:{ex.Message}");
        }
    }

    /// <summary>
    /// 调用 JS 模块函数
    /// </summary>
    [HttpPost("call")]
    public async Task<IResultModel> CallJsFunction(
        [FromBody] CallJsFunctionRequest request)
    {
        try
        {
            var result = await _jsHandler.ExecuteAsync<object>(
                request.Module,
                request.Function,
                request.Args);
            
            return ResultModel.Success(new
            {
                success = true,
                result = result
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "JS 函数调用失败");
            
            return ResultModel.Error($"JS 函数调用失败:{ex.Message}");
        }
    }

    /// <summary>
    /// 验证 JS 代码
    /// </summary>
    [HttpPost("validate")]
    public async Task<IResultModel> ValidateJs(
        [FromBody] ValidateJsRequest request)
    {
        try
        {
            // 尝试执行简单的测试
            var testCode = $@"
                (function() {{
                    try {{
                        {request.Code}
                        return {{ valid: true }};
                    }} catch (e) {{
                        return {{ valid: false, error: e.message }};
                    }}
                }})();
            ";

            var result = await _jsHandler.ExecuteAsync<JsonElement>(testCode);
            
            return ResultModel.Success(new
            {
                valid = result.GetProperty("valid").GetBoolean(),
                error = result.TryGetProperty("error", out var error)
                    ? error.GetString()
                    : null
            });
        }
        catch (Exception ex)
        {
            return ResultModel.Error($"验证失败:{ex.Message}");
        }
    }
}

public class ExecuteJsRequest
{
    public string Code { get; set; }
}

public class CallJsFunctionRequest
{
    public string Module { get; set; }
    public string Function { get; set; }
    public object[] Args { get; set; }
}

public class ValidateJsRequest
{
    public string Code { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

# 🔧 引擎提供器对比

# JavaScript.ChakraCore(ChakraCore 引擎)

特点:

  • ✅ Microsoft 官方维护(已停止更新)
  • ✅ 轻量级,无需外部依赖
  • ✅ 执行速度快
  • ✅ 支持现代 JavaScript 语法(ES6+)
  • ⚠️ 微软已停止维护(2020 年)
  • ⚠️ 不支持 Node.js 模块

适用场景:

  • 简单脚本执行
  • 规则引擎
  • 表达式计算
  • 离线环境

安装:

# NuGet 包
Install-Package Microsoft.ChakraCore
1
2

# JavaScript.NodeJs(Node.js 引擎)

特点:

  • ✅ 完整的 Node.js 运行时
  • ✅ 支持所有 Node.js 模块(npm)
  • ✅ 活跃的社区维护
  • ✅ 支持异步编程
  • ⚠️ 需要安装 Node.js
  • ⚠️ 资源占用较大

适用场景:

  • 复杂业务逻辑
  • 需要 npm 模块
  • 与现有 Node.js 代码集成
  • 需要最新 ES 特性

安装:

# 安装 Node.js
# macOS
brew install node

# Windows
# 访问 https://nodejs.org/ 下载安装

# 验证安装
node --version
npm --version
1
2
3
4
5
6
7
8
9
10

# 💡 最佳实践

# 1. 脚本缓存

public class CachedJavaScriptService
{
    private readonly IJavaScriptHandler _jsHandler;
    private readonly IMemoryCache _cache;

    public CachedJavaScriptService(
        IJavaScriptHandler jsHandler,
        IMemoryCache cache)
    {
        _jsHandler = jsHandler;
        _cache = cache;
    }

    /// <summary>
    /// 执行带缓存的脚本
    /// </summary>
    public async Task<T> ExecuteCachedAsync<T>(
        string scriptKey,
        Func<Task<string>> scriptLoader,
        Dictionary<string, object> context)
    {
        var cacheKey = $"js_script_{scriptKey}";
        
        var script = await _cache.GetOrCreateAsync(
            cacheKey,
            async entry =>
            {
                // 脚本缓存 1 小时
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
                
                return await scriptLoader();
            });

        var contextJson = JsonSerializer.Serialize(context);
        var jsCode = $@"
            var context = {contextJson};
            {script}
        ";

        return await _jsHandler.ExecuteAsync<T>(jsCode);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 2. 脚本超时控制

public class TimeoutJavaScriptService
{
    private readonly IJavaScriptHandler _jsHandler;
    private readonly ILogger<TimeoutJavaScriptService> _logger;

    public TimeoutJavaScriptService(
        IJavaScriptHandler jsHandler,
        ILogger<TimeoutJavaScriptService> logger)
    {
        _jsHandler = jsHandler;
        _logger = logger;
    }

    /// <summary>
    /// 执行带超时的脚本
    /// </summary>
    public async Task<T> ExecuteWithTimeoutAsync<T>(
        string jsCode,
        TimeSpan timeout)
    {
        var cts = new CancellationTokenSource(timeout);
        
        try
        {
            var task = _jsHandler.ExecuteAsync<T>(jsCode);
            
            if (await Task.WhenAny(task, Task.Delay(timeout, cts.Token)) == task)
            {
                cts.Cancel();
                return await task;
            }
            
            throw new TimeoutException(
                $"JS 执行超时({timeout.TotalSeconds}秒)");
        }
        catch (OperationCanceledException)
        {
            throw new TimeoutException(
                $"JS 执行超时({timeout.TotalSeconds}秒)");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 3. 脚本沙箱

public class SandboxJavaScriptService
{
    private readonly IJavaScriptHandler _jsHandler;
    private readonly ILogger<SandboxJavaScriptService> _logger;

    public SandboxJavaScriptService(
        IJavaScriptHandler jsHandler,
        ILogger<SandboxJavaScriptService> logger)
    {
        _jsHandler = jsHandler;
        _logger = logger;
    }

    /// <summary>
    /// 在沙箱中执行脚本(限制危险操作)
    /// </summary>
    public async Task<T> ExecuteInSandboxAsync<T>(
        string userCode,
        Dictionary<string, object> allowedGlobals)
    {
        // 构建沙箱环境
        var sandboxCode = @"
            (function(sandbox) {
                'use strict';
                
                // 禁止访问危险对象
                var require = undefined;
                var process = undefined;
                var global = undefined;
                var eval = undefined;
                var Function = undefined;
                
                // 注入允许的全局对象
                " + string.Join(";", 
                    allowedGlobals.Select(kvp => 
                        $"var {kvp.Key} = sandbox.{kvp.Key};")) + @"
                
                // 执行用户代码
                try {
                    return (function() {
                        " + userCode + @"
                    })();
                } catch (e) {
                    throw new Error('脚本执行错误:' + e.message);
                }
            })(this);
        ";

        return await _jsHandler.ExecuteAsync<T>(sandboxCode);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 4. 脚本版本管理

public class VersionedScriptService
{
    private readonly IJavaScriptHandler _jsHandler;
    private readonly IScriptRepository _scriptRepo;

    public VersionedScriptService(
        IJavaScriptHandler jsHandler,
        IScriptRepository scriptRepo)
    {
        _jsHandler = jsHandler;
        _scriptRepo = scriptRepo;
    }

    /// <summary>
    /// 执行指定版本的脚本
    /// </summary>
    public async Task<T> ExecuteVersionAsync<T>(
        string scriptCode,
        string version,
        Dictionary<string, object> context)
    {
        // 获取指定版本的脚本
        var script = await _scriptRepo.GetByVersionAsync(
            scriptCode, 
            version);
        
        if (script == null)
            throw new ScriptNotFoundException(
                $"脚本 {scriptCode} 版本 {version} 不存在");

        var contextJson = JsonSerializer.Serialize(context);
        var jsCode = $@"
            var context = {contextJson};
            var version = '{version}';
            {script.Content}
        ";

        return await _jsHandler.ExecuteAsync<T>(jsCode);
    }

    /// <summary>
    /// 执行最新版本脚本
    /// </summary>
    public async Task<T> ExecuteLatestAsync<T>(
        string scriptCode,
        Dictionary<string, object> context)
    {
        var script = await _scriptRepo.GetLatestAsync(scriptCode);
        
        var contextJson = JsonSerializer.Serialize(context);
        var jsCode = $@"
            var context = {contextJson};
            var version = '{script.Version}';
            {script.Content}
        ";

        return await _jsHandler.ExecuteAsync<T>(jsCode);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 🔍 常见问题

# Q1: ChakraCore 不支持某些 ES6 特性?

解决方案:

  1. ✅ 使用 Babel 转译代码为 ES5
  2. ✅ 切换到 Node.js 引擎
  3. ✅ 避免使用最新 ES 特性
// 使用 Babel 转译
public class BabelJavaScriptService
{
    public async Task<string> TranspileAsync(string es6Code)
    {
        // 调用 Babel API 转译
        var transpiled = await _babelApi.TranspileAsync(es6Code);
        return transpiled;
    }
}
1
2
3
4
5
6
7
8
9
10

# Q2: 如何调试 JS 代码?

解决方案:

public class DebugJavaScriptService
{
    private readonly IJavaScriptHandler _jsHandler;
    private readonly ILogger<DebugJavaScriptService> _logger;

    public async Task<T> ExecuteWithDebugAsync<T>(
        string jsCode,
        Dictionary<string, object> context)
    {
        // 记录输入的 JS 代码
        _logger.LogInformation("执行 JS 代码:\n{Code}", jsCode);
        
        // 记录上下文
        _logger.LogInformation("上下文:{Context}", 
            JsonSerializer.Serialize(context));
        
        try
        {
            var result = await _jsHandler.ExecuteAsync<T>(jsCode);
            
            _logger.LogInformation("执行结果:{Result}", 
                JsonSerializer.Serialize(result));
            
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "JS 执行失败");
            throw;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# Q3: 如何处理异步 JS 代码?

解决方案:

// 确保 JS 代码返回 Promise
public async Task<T> ExecuteAsyncCodeAsync<T>(string jsCode)
{
    var wrappedCode = $@"
        (async function() {{
            try {{
                {jsCode}
            }} catch (e) {{
                throw new Error('异步代码执行失败:' + e.message);
            }}
        }})();
    ";

    return await _jsHandler.ExecuteAsync<T>(wrappedCode);
}

// 使用示例
var jsCode = @"
    await new Promise(resolve => setTimeout(resolve, 1000));
    return '完成';
";

var result = await ExecuteAsyncCodeAsync<string>(jsCode);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 📚 相关文档


# 🔗 参考链接


最后更新: 2024-09-20