# 服务端脚本

## 概述
织信使用`javascript`语言作为脚本语言，在织信使用脚本可以实现无法配置的高级功能，在脚本中通过访问`informat`对象可以使用系统
提供的功能函数。脚本运行在服务器端，运行时状态会根据应用做隔离。对于复杂的应用场景，脚本比`自动化`程序有更加强的灵活性，并且性能上也更加优秀。
在API或者是复杂计算场景下建议使用脚本实现。

::: warning 终止脚本运行
如果脚本执行时间过长未执行完或逻辑产生了死循环，可通过应用[运行状态](/guide/app/run_log.md#终止当前任务)监控功能来终止脚本运行
:::

## 模块化

织信支持使用`javascript` `ES6`版本的语法格式。不支持`Promise` `setTimeout`等异步操作。所有的函数调用都是同步调用。需要注意的是虽然使用方式上织信的脚本引擎和`nodejs`很类似，但是两者是完全不同的实现。在织信中也不可以使用浏览器的`window` `document`对象，**织信的脚本运行在服务器端**

对于大型项目建议按照文件夹将不同功能的脚本文件分组，脚本之间使用 `import` 和 `export` 语法导入导出,以下示例展示了这种用法

::::tabs
:::tab export-func.js

```javascript
export function add(a, b) {
    return a + b;
}
```

:::tab import-func.js

```javascript
import { add } from 'export-func.js'

const result = add(1, 2);

```

:::
::::

## npm包管理

在织信中可以使用npm包管理器加载`npmjs.com`仓库中的软件包，`npm`的使用可以极大的扩充脚本的能力，除了使用社区开发的高质量的代码以外，开发者也可以将自己开发的公用脚本打包成`npm`包发布到仓库中，在多个应用中引用。
对于通过`npm`包方式引用的库需要使用`require`语法导入。下面是一个例子
1. 企业级后台管理开启NPM配置
::: info 企业级后台管理开启NPM配置
配置路径：系统信息=>参数设置

![npmSetting.png](images/npmSetting.png)

[配置说明](../../guide/admin/setting#参数说明-8)
:::

2. 在脚本根目录下新建package.json文件,管理依赖的软件包

```json
{
  "dependencies": {
    "crypto-js": "4.1.1"
  }
}
```

3. 在脚本中使用require方法引用软件包

:::: tabs
::: tab 脚本
```js
	let SHA256 = require("crypto-js/sha256");
	console.log(SHA256("Message"));
```
:::

::: tab 输出
```json
{
  "words": [
    796354186,
    -1644431147,
    -2071224597,
    1248937418,
    -1798902382,
    920954739,
    -153309275,
    321597329
  ],
  "sigBytes": 32
}
```
:::
::::


织信对于`npm`包的支持现阶段还不够完善，如果软件包中引用了`nodejs`的内建库，例如`fs` `events`等，这些包不能正常运行。另外`npm`包也不支持使用`import`语法导入。

## 使用Java对象

脚本中可以使用Java对象，以下文档向您展示如何实现与 Java 的互操作性以及可能的 JavaScript 到 Java 嵌入场景。


### 类访问
要访问 Java 类，脚本引擎 支持以下Java.type(typeName)功能：

```js
var FileClass = Java.type('java.io.File');
```
java默认情况下，Java 类不会自动映射到全局变量，例如， 脚本引擎 中没有全局属性。现有的访问代码，例如java.io.File，应该重写以使用该Java.type(name)函数：

```js
//脚本引擎 compliant syntax
var FileClass = Java.type("java.io.File");
```


### 构造 Java 对象
new Java对象可以用JavaScript的关键字构造：

```js
var FileClass = Java.type('java.io.File');
var file = new FileClass("myFile.md");
```

### 字段和方法访问
Java 类的静态字段或 Java 对象的字段可以像 JavaScript 属性一样进行访问：

```js
var JavaPI = Java.type('java.lang.Math').PI;
```

Java 方法可以像 JavaScript 函数一样被调用：

```js
var file = new (Java.type('java.io.File'))("test.md");
var fileName = file.getName();
```

### 方法参数的转换
JavaScript 被定义为对double数字类型进行操作。出于性能原因（例如类型int），脚本引擎 可能会在内部使用其他 Java 数据类型。

调用 Java 方法时，可能需要进行值转换。当 Java 方法需要一个long参数，并且int由脚本引擎提供时，就会发生这种情况。如果此转换导致有损转换，TypeError则会抛出：

```java
//Java
void longArg   (long arg1);
void doubleArg (double arg2);
void intArg    (int arg3);
```

```js
//JavaScript
javaObject.longArg(1);     //widening, OK
javaObject.doubleArg(1);   //widening, OK
javaObject.intArg(1);      //match, OK

javaObject.longArg(1.1);   //lossy conversion, TypeError!
javaObject.doubleArg(1.1); //match, OK
javaObject.intArg(1.1);    //lossy conversion, TypeError!
```


### 选择方法
Java 允许通过参数类型重载方法。当从 JavaScript 调用到 Java 时，会选择实际参数可以无损转换为的最窄可用类型的方法：

```java
//Java
void foo(int arg);
void foo(short arg);
void foo(double arg);
void foo(long arg);
```

```js
//JavaScript
javaObject.foo(1);              // will call foo(short);
javaObject.foo(Math.pow(2,16)); // will call foo(int);
javaObject.foo(1.1);            // will call foo(double);
javaObject.foo(Math.pow(2,32)); // will call foo(long);
```

要覆盖此行为，可以使用javaObject['methodName(paramTypes)']语法选择显式方法重载。参数类型需要以逗号分隔且不带空格，并且对象类型需要完全限定（例如，'get(java.lang.String,java.lang.String[])'）。

```js
javaObject['foo(int)'](1);
javaObject['foo(long)'](1);
javaObject['foo(double)'](1);
```

请注意，参数值仍然必须适合参数类型。您可以使用自定义目标类型映射来覆盖此行为。

当方法重载不明确且无法自动解析以及您想要覆盖默认选择时，显式方法选择也很有用：

```java
//Java
void sort(`List<Object>` array, Comparator<Object> callback);
void sort(`List<Integer>` array, IntBinaryOperator callback);
void consumeArray(`List<Object>` array);
void consumeArray(Object[] array);
```

```js
//JavaScript
var array = [3, 13, 3, 7];
var compare = (x, y) => (x < y) ? -1 : ((x == y) ? 0 : 1);

// throws TypeError: Multiple applicable overloads found
javaObject.sort(array, compare);
// explicitly select sort(List, Comparator)
javaObject['sort(java.util.List,java.util.Comparator)'](array, compare);

// will call consumeArray(List)
javaObject.consumeArray(array);
// explicitly select consumeArray(Object[])
javaObject['consumeArray(java.lang.Object[])'](array);
```

> 请注意，当前无法显式选择构造函数重载。


### 数组访问
脚本引擎 支持从 JavaScript 代码创建 Java 数组。支持两种模式：

```js
//模式1 pattern
var JArray = Java.type('java.lang.reflect.Array');
var JString = Java.type('java.lang.String');
var sarr = JArray.newInstance(JString, 5);

//模式2  pattern
var IntArray = Java.type("int[]");
var iarr = new IntArray(5);
```
创建的数组是 Java 类型，但可以在 JavaScript 代码中使用：

```js
iarr[0] = iarr[iarr.length] * 2;
```

### Map访问
在脚本引擎中，您可以创建和访问 Java Map，例如java.util.HashMap：

```js
var HashMap = Java.type('java.util.HashMap');
var map = new HashMap();
map.put(1, "a");
map.get(1);
```

脚本引擎 支持迭代此类映射：

```js
for (var key in map) {
    print(key);
    print(map.get(key));
}
```

### 列表访问
在脚本引擎中，您可以创建和访问 Java 列表，例如java.util.ArrayList：

```js
var ArrayList = Java.type('java.util.ArrayList');
var list = new ArrayList();
list.add(42);
list.add("23");
list.add({});

for (var idx in list) {
    print(idx);
    print(list.get(idx));
}
```

### 字符串访问
脚本引擎可以创建具有 Java 互操作性的 Java 字符串。字符串的长度可以通过属性查询length（注意length是一个值属性，不能作为函数调用）：

```js
var javaString = new (Java.type('java.lang.String'))("Java");
javaString.length === 4;
```

请注意，脚本引擎 在内部使用 Java 字符串来表示 JavaScript 字符串，因此上面的代码和 JavaScript 字符串文字"Java"实际上是无法区分的。

### 迭代属性
Java 类和 Java 对象的属性（字段和方法）可以使用 JavaScript for..in 循环进行迭代：

```js
var m = Java.type('java.lang.Math')
for (var i in m) { 
    print(i);
}
// > E
// > PI
// > abs
// > sin
// > ...
```
### 脚本里使用Java对象的完整例子

```js

//调用Java graphics画一个矩形图片，并返回图片base64编码
export function createImage() {
	let width=100;
	let height=100;
	var BufferedImage = Java.type("java.awt.image.BufferedImage");
	var ByteArrayOutputStream=Java.type("java.io.ByteArrayOutputStream");
	var ImageIO=Java.type("javax.imageio.ImageIO");
	var Base64=Java.type("java.util.Base64");
	var Color=Java.type("java.awt.Color");
	//
	var image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_BGR);
	var graphics = image.getGraphics();
	graphics.setColor(new Color(255, 0, 0));
	graphics.fillRect(1, 1, width - 1, height - 1);
	var os=new ByteArrayOutputStream();
	ImageIO.write(image, "jpg", os);
	let content=os.toByteArray();
	let base64=Base64.getEncoder().encodeToString(content);
	return base64;
}
```
## 注意事项

- 1、文件依赖时，必须包含文件`.js`后缀
- 2、建议依赖时使用相对路径引入，如果后期使用``npm``支持也无需修改已开发内容
  
::::tabs
:::tab 示例目录结构
  ![示例目录结构](images/script-import.png)
:::
:::tab 引用脚本
```javascript
import {success} from "../../../libs/index.js"

export function run(){
    return success();
}
```
:::
:::tab 被引用脚本
```javascript
export function success() {
    return "SUCCESS"
}
```
:::
::::

## 补充说明
- 使用**VsCode**或**WebStorm**开发脚本时，下载[https://next.informat.cn/types/informat.d.ts](https://next.informat.cn/types/informat.d.ts)获取调用函数描述文件来解决代码报错问题。

- 在使用git同步时，不会同步以`.`开始头的隐藏文件

- 将下载的**informat.d.ts**导入编辑器后，若依旧提示**informat**调用报错，可在工程根目录创建[**jsconfig.json**](https://code.visualstudio.com/docs/languages/jsconfig)空文件重启编辑器。文件内容设置成一个空对象，内容如下

    ```json
    {}
    ```


