一、背景
在低代碼平台中為了擴展功能,我們在業務編排中會擴展代碼塊的功能,允許用戶直接在界面中進行代碼(Node.js、 Python)的編寫,來實現取數或者賦值的一些功能,類似某某雲效果:
二、代碼實現
.Net執行js的框架有很多,大致分為兩類1、瀏覽器內核/無頭瀏覽器,2、Js引擎/框架。主要區別是前者模擬瀏覽器去請求一個完整的網頁,后者則是單單去計算調用一個純粹的js方法,我更傾向於后者。
以下介紹幾種常見的Js引擎框架:
● Microsoft.AspNetCore.NodeServices (接口方法已標注過時,后續有可能不會在.net 新版本中支持)
● Microsoft.ClearScript (主要是使用v8引擎)
● JavaScriptEngineSwitcher.ChakraCore (使用的ChakraCore引擎)
1、NodeServices
1.1 前提
● Nodejs環境
● 腳本需要按照Nodejs的模塊導出的格式將方法寫成導出的形式
1.2 安裝
在項目中引入 NuGet 包:Microsoft.AspNetCore.NodeServices ,這里我使用的是 3.1.23版
1.3 實現
服務注入
修改 Startup 類,在 ConfigureServices 方法中添加下面代碼:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddNodeServices();
}
創建js文件
項目的根目錄創建jsfiles文件夾,文件中中創建一個名為 hello.js 的腳本文件,文件的屬性中的「復制到輸出目錄」設置為「始終復制」,文件的內容如下
module.exports = function (callback, name) {
var msg = "Hello " + name;
callback(null, msg);
};
控制器實現
控制器的構造中依賴注入INodeServices服務,使用InvokeAsync執行js文件
[ApiController]
[Route("[controller]")]
public class NodeServicesController : ControllerBase
{
private readonly INodeServices _nodeServices;
public NodeServicesController(INodeServices nodeServices)
{
_nodeServices = nodeServices;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var result = await _nodeServices.InvokeAsync<string>("./jsfiles/hello.js", "nodeServices");
return Ok(result);
}
}
1.4 運行效果
2、ClearScript
2.1 優點
● 支持原生js編寫
● 支持直接執行js字符串
2.2 安裝
在項目中引入 NuGet 包:Microsoft.ClearScript,這里我使用的是 7.2.3版
2.3 實現
調用腳本支持加載腳本文件和加載腳本字符串
加載腳本文件
調用engine.ComplieDocument方法直接加載js文件,然后調用engine.Execute將引入的腳本執行一遍,這樣后面就可以調用js方法,say就是js的方法名,調用格式與js相同。
V8Script script = engine.CompileDocument("./jsfiles/hellojs.js"); // 載入並編譯js文件, 然后Execute, 就可以直接調用。
engine.Execute(script);
var result = engine.Script.say("clearScript");
加載腳本字符串
直接調用engine.Execute將引入的腳本執行一遍
string jsText = @"function say(name){
return 'Hello '+ name;
}";
engine.Execute(jsText);
var result = engine.Script.say("clearScript");
最終代碼
[ApiController]
[Route("[controller]")]
public class ClearScriptController : ControllerBase
{
public ClearScriptController()
{
}
[HttpGet]
public async Task<IActionResult> Get()
{
string jsText = @"function say(name){
return 'Hello '+name;
}";
using (var engine = new V8ScriptEngine())
{
engine.DocumentSettings.AccessFlags = Microsoft.ClearScript.DocumentAccessFlags.EnableFileLoading;
engine.DefaultAccess = Microsoft.ClearScript.ScriptAccess.Full;
//V8Script script = engine.CompileDocument("./jsfiles/hellojs.js"); // 載入並編譯js文件。
engine.Execute(jsText); //直接執行js字符串
var result = engine.Script.say("clearScript"); //執行方法
return Ok(result);
}
}
}
2.4 運行效果
3、ChakraCore
3.1 優點
● 支持原生js編寫
● 支持直接執行js字符串
3.2 安裝
在項目中引入 NuGet 包:JavaScriptEngineSwitcher.ChakraCore
另外還需要安裝的類庫:(否則會執行報錯)
JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64 ///針對Linux-x64環境下組件;
JavaScriptEngineSwitcher.ChakraCore.Native.win-x64 //針對Wind-x64環境下組件;
JavaScriptEngineSwitcher.ChakraCore.Native.win-x32 //針對Wind-x32環境下組件;
3.3 實現
與ClearScript邏輯一樣先把js文件執行一遍,然后再去調用要使用的方法。
engine.Execute(jsText);
string result = engine.CallFunction<string>("say", "chakraCoreJs");
最終代碼
[ApiController]
[Route("[controller]")]
public class ChakraCoreController : ControllerBase
{
public ChakraCoreController()
{
}
[HttpGet]
public async Task<IActionResult> Get()
{
string jsText = @"function say(name){
return 'Hello '+name;
}";
IJsEngineSwitcher engineSwitcher = JsEngineSwitcher.Current;
engineSwitcher.EngineFactories.Add(new ChakraCoreJsEngineFactory());
engineSwitcher.DefaultEngineName = ChakraCoreJsEngine.EngineName;
using (IJsEngine engine = JsEngineSwitcher.Current.CreateDefaultEngine())
{
engine.Execute(jsText);
string result = engine.CallFunction<string>("say", "chakraCoreJs");
return Ok(result);
}
}
}
3.4 運行效果
三、總結
以上三種框架特性:
● ClearScript、ChakraCore:原始js,支持直接執行腳本字符串
● NodeServices:植入Nodejs對象原則上執行會更快,需要Nodejs語法,只支持執行腳本文件
具體使用哪一種,需要根據業務功能如何實現來定,由於NodeServices不支持讀取字符串且編寫格式有要求,對於某某雲的這種設置效果,猜測是用后面兩種方式,即特定腳本頭+代碼塊設置區域js+特定腳本尾
function dojs(input){
//中間拼接代碼塊的設置,input 獲取入參,output作為出參
return output;
}
最后程序通過調用engine.Script.dojs(表單數據對象);將表單數據傳入腳本作為input入參,並獲取output輸出。
代碼實現(ClearScript方式):
如程序執行js腳本,將用戶對象的手機號中間四位加密
[HttpGet()]
public async Task<IActionResult> Get()
{
string mobile = "13812345678";
string jsTextPrefix = "function dojs(input){"; -- 腳本頭
string jsTextPostfix = "return output;}"; -- 腳本尾
//代碼塊設置區域內容
var jsContent = @"var tel = input.Mobile; //獲取入參對象
var reg=/(\d{3})\d{4}(\d{4})/; //業務邏輯,如手機號中間四位加密
var telnew = tel.replace(reg, '$1****$2');
output = {'Mobile':telnew}; // 定義返回值格式
";
string jsText = jsTextPrefix + jsContent + jsTextPostfix;
using (var engine = new V8ScriptEngine())
{
engine.DocumentSettings.AccessFlags = Microsoft.ClearScript.DocumentAccessFlags.EnableFileLoading;
engine.DefaultAccess = Microsoft.ClearScript.ScriptAccess.Full;
engine.Execute(jsText);
//數據對象作為入參
var input = new
{
Mobile = mobile
};
var result = engine.Script.dojs(input); //執行方法
return Ok(JsonConvert.SerializeObject(result));
}
}
git地址:NetCoreExecuteJs