☻Blog("Laziji")

System.out.print("辣子鸡的博客");

0%

项目地址

https://github.com/GitHub-Laziji/js-engine

  • 支持解析js脚本生成语法树、格式化代码
  • 支持运行完整js脚本
  • 支持安全模式运行单行表达式
  • 支持设置超时时间

1. 生成语法树 并输出格式化代码

示例代码

1
2
3
4
5
6
7
class Test{
public static void main(String[] args){
Top.init();
DocNode doc = Top.compile("let a=1+2,b=3,c="string",d=a*(b+c/2),func=function(){};");
System.out.println(doc);
}
}

输出

1
2
3
let a = 1 + 2, b = 3, c = "string", d = a * (b + c / 2), func = function () {

}

2. 运行完整脚本

以下示例为运行快排算法(运行环境线程隔离)

示例代码

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
class Test{
/**
* function sort(arr, i, j) {
* if (i >= j) {
* return;
* }
* let p = i, q = j;
* let temp = arr[p];
* while (p < q) {
* while (p < q && arr[q] >= temp) {
* q-=1;
* }
* arr[p] = arr[q];
* while (p < q && arr[p] <= temp) {
* p+=1;
* }
* arr[q] = arr[p];
* }
* arr[q] = temp;
* sort(arr, i, q - 1);
* sort(arr, q + 1, j);
* }
*
* let arr = [234, 57, 12, 123, 346, 1234, 2];
*
* sort(arr, 0, arr.length - 1);
*/
public static void main(String[] args){
Top.init();
Top.eval("function sort(arr, i, j) {\n" +
" if (i >= j) {\n" +
" return;\n" +
" }\n" +
" let p = i, q = j;\n" +
" let temp = arr[p];\n" +
" while (p < q) {\n" +
" while (p < q && arr[q] >= temp) {\n" +
" q-=1;\n" +
" }\n" +
" arr[p] = arr[q];\n" +
" while (p < q && arr[p] <= temp) {\n" +
" p+=1;\n" +
" }\n" +
" arr[q] = arr[p];\n" +
" }\n" +
" arr[q] = temp;\n" +
" sort(arr, i, q - 1);\n" +
" sort(arr, q + 1, j);\n" +
"}\n" +
"\n" +
"let arr = [234, 57, 12, 123, 346, 1234, 2];\n" +
"\n" +
"sort(arr, 0, arr.length - 1);");
Top.loop();
System.out.println(Top.getThreadLocalTop().getMainContexts().getContexts().peek().toSimpleString());
}
}

输出

1
2
arr: [2, 12, 57, 123, 234, 346, 1234]
sort: [object Object]

3. 运行单行表达式

该模式下只支持单行表达式 并且无法使用for、while、function、lambda、import关键字

示例代码

1
2
3
4
5
6
class Test{
public static void main(String[] args){
Top.init();
System.out.println(Top.exprEval("'hello '+(1*2*3*4)"));
}
}

输出

1
hello 24

4. 设置超时时间

通过Top.getThreadLocalTop().setOvertime(100L);设置超时时间,单位毫秒

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test{
public static void main(String[] args){
Top.init();
Top.getThreadLocalTop().setOvertime(100L);
Top.addInternalModules("sys", new SystemModuleValue());
Top.eval("import { print } from "sys";\n" +
"\n" +
"let i=1;\n" +
"while(true){\n" +
" print(i++);\n" +
"}");
Top.loop();
}
}

输出

1
2
3
4
5
6
7
1
2
3
...

org.laziji.commons.js.exception.RunException: Run timeout.
...

原理

通过修改系统时间后 使用命令启动虚拟机

示例

1
2
3
4
sudo date 090100002021
open -a /Applications/Parallels\ Desktop.app
/usr/local/bin/prlctl start "Windows 11"
sudo sntp -sS time.apple.com

安装

https://github.com/microsoft/terminal/releases

1
PowerShell -Command "Set-ExecutionPolicy RemoteSigned -scope Process; iwr -useb https://raw.githubusercontent.com/gerardog/gsudo/master/installgsudo.ps1 | iex"

sudo管理员权限

settings.json profiles.list

1
2
3
4
5
6
7
8
9
{
"guid": "{41dd7a51-f0e1-4420-a2ec-1a7130b7e950}",
"name": "Windows PowerShell Elevated",
"commandline": "gsudo.exe powershell.exe",
"hidden": false,
"colorScheme": "Solarized Dark",
"fontFace": "Fira Code",
"icon": "C:\\shell\\icon.png"
},

背景

setting.json profile.default

1
2
3
4
5
6
7
8
9
10
"defaults": {
"startingDirectory": ".",
"useAcrylic": true,
"acrylicOpacity": 0.6,
"colorScheme": "Dracula2",
"fontSize": 11,
"backgroundImage": "C:\\shell\\bg1.jpg",
"backgroundImageOpacity": 0.2,
"backgroundImageStrechMode": "fill",
},

##加入右键菜单
https://raw.githubusercontent.com/microsoft/terminal/main/res/terminal.ico

wt.reg

1
2
3
4
5
6
7
8
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\wt]
@="Windows Terminal here"
"Icon"="C:\\shell\\terminal.ico"

[HKEY_CLASSES_ROOT\Directory\Background\shell\wt\command]
@="C:\\Users\\laziji\\AppData\\Local\\Microsoft\\WindowsApps\\wt.exe"

settings.json profiles.default

1
"startingDirectory": "."

关键步骤

  • 加上干扰点
    1
    2
    3
    4
    for (let i = 0.05 * w * h; i > 0; i--) {
    ctx.fillStyle = randomColor(0, 256);
    ctx.fillRect(randomInt(0, w), randomInt(0, h), 1, 1);
    }
  • 显示数字
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ctx.font = `${h - 4}px Consolas`;
    ctx.fillStyle = randomColor(160, 200);
    let value = "";
    for (let i = 0; i < n; i++) {
    let x = (w - 10) / n * i + 5,
    y = h - 12;
    let r = Math.PI * randomFloat(-0.12, 0.12);
    let ch = CHARTS[randomInt(0, CHARTS.length)];
    value += ch;
    ctx.translate(x, y);
    ctx.rotate(r);
    ctx.fillText(ch, 0, 0);
    ctx.rotate(-r);
    ctx.translate(-x, -y);
    }
  • 获取验证码图片base64值
    1
    canvas.toDataURL('image/jpg')

完整实现

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
<!DOCTYPE html>
<html>

<head>
<title></title>
</head>

<body>
<img id="img">
<script type="text/javascript">
function getCaptcha(w, h, n) {
const CHARTS = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ".split("");
const randomInt = (s, e) => {
if (s > e) {
let t = s;
s = e;
e = t;
}
s = Math.ceil(s);
e = Math.floor(e);
return s + Math.floor(Math.random() * (e - s))
}
const randomFloat = (s, e) => {
return s + Math.random() * (e - s);
}
const randomColor = (s, e) => {
return `rgb(${randomInt(s,e)},${randomInt(s,e)},${randomInt(s,e)})`;
}

let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');

ctx.rect(0, 0, w, h);
ctx.clip();

ctx.fillStyle = randomColor(200, 250);
ctx.fillRect(0, 0, w, h);

for (let i = 0.05 * w * h; i > 0; i--) {
ctx.fillStyle = randomColor(0, 256);
ctx.fillRect(randomInt(0, w), randomInt(0, h), 1, 1);
}

ctx.font = `${h - 4}px Consolas`;
ctx.fillStyle = randomColor(160, 200);
let value = "";
for (let i = 0; i < n; i++) {
let x = (w - 10) / n * i + 5,
y = h - 12;
let r = Math.PI * randomFloat(-0.12, 0.12);
let ch = CHARTS[randomInt(0, CHARTS.length)];
value += ch;
ctx.translate(x, y);
ctx.rotate(r);
ctx.fillText(ch, 0, 0);
ctx.rotate(-r);
ctx.translate(-x, -y);
}

let base64Src = canvas.toDataURL('image/jpg');
return {
value,
base64Src
};
}
let res = getCaptcha(100, 40, 4);
console.log(res);
document.querySelector("#img").src = res.base64Src;
</script>
</body>

</html>

输出

1
2
3
4
{
base64Src: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASw....",
value: "Z9KF"
}

效果展示

验证码

测试

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
public class Main {

public static void main(String[] args) throws Exception {
String text =
"{\n" +
" \"transformRequest\":\n" +
" {},\n" +
" \"transformResponse\":\n" +
" {},\n" +
" \"timeout\": 30000,\n" +
" \"xsrfHeaderName\": [\"X-XSRF-TOKEN\",\"X-XSRF-TOKEN2\",\"X-XSRF-TOKEN3\"],\n" +
" \"maxContentLength\": -1,\n" +
" \"headers\":\n" +
" {\n" +
" \"Accept\": \"application/json, text/plain, */*\",\n" +
" \"Content-Type\": \"application/json;charset=UTF-8\"\n" +
" },\n" +
" \"data\": \"{\\\"page\\\":1,\\\"limit\\\":10}\"\n" +
"}";

System.out.println(JSONUtils.formatText(text));
System.out.println("===================");
System.out.println(JSON.toJSONString(JSONUtils.parseText(text), true));
}
}

实现

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JSONUtils {

public static Map<String, Object> parseObjectText(String text) throws Exception {
Object o = parseText(text);
if (!(o instanceof Map)) {
throw new Exception();
}
return (Map<String, Object>) o;
}

public static List<Object> parseArrayText(String text) throws Exception {
Object o = parseText(text);
if (!(o instanceof List)) {
throw new Exception();
}
return (List<Object>) o;
}

public static Object parseText(String text) throws Exception {
return parseTokens(parseTextToTokens(text), 0).data;
}

public static String formatText(String text) throws Exception {
return formatTokens(parseTextToTokens(text));
}

private static Token getFirstToken(String text, TokenType type) {
Matcher matcher = type.pattern.matcher(text);
if (!matcher.find()) {
return null;
}
return new Token(type, matcher.group(0));
}

private static String transString(String text) {
StringBuilder val = new StringBuilder();
for (int i = 1; i < text.length() - 1; i++) {
if (text.charAt(i) != '\\') {
val.append(text.charAt(i));
} else {
switch (text.charAt(i + 1)) {
case 'n':
val.append('\n');
break;
case 't':
val.append('\t');
break;
default:
val.append(text.charAt(i + 1));
break;
}
i++;
}
}
return val.toString();
}

private static TokensParseResult parseTokens(List<Token> tokens, int index) throws Exception {
switch (tokens.get(index).type) {
case OBJECT_OPEN: {
index++;
Map<String, Object> data = new HashMap<>();
while (tokens.get(index).type != TokenType.OBJECT_CLOSE) {
if (tokens.get(index).type == TokenType.STRING && tokens.get(index + 1).type == TokenType.COLON) {
TokensParseResult item = parseTokens(tokens, index + 2);
data.put(transString(tokens.get(index).value), item.data);
index = item.nextIndex;
if (tokens.get(index).type == TokenType.SPLIT) {
index++;
}
} else {
throw new Exception();
}
}
return new TokensParseResult(data, index + 1);
}
case ARRAY_OPEN: {
index++;
List<Object> data = new ArrayList<>();
while (tokens.get(index).type != TokenType.ARRAY_CLOSE) {
TokensParseResult item = parseTokens(tokens, index);
data.add(item.data);
index = item.nextIndex;
if (tokens.get(index).type == TokenType.SPLIT) {
index++;
}
}
return new TokensParseResult(data, index + 1);

}
case STRING:
return new TokensParseResult(transString(tokens.get(index).value), index + 1);
case NUMBER:
return new TokensParseResult(Double.parseDouble(tokens.get(index).value), index + 1);
case BOOLEAN:
return new TokensParseResult(Boolean.parseBoolean(tokens.get(index).value), index + 1);
case NULL:
return new TokensParseResult(null, index + 1);
default:
throw new Exception();
}
}

private static String enter(int depth) {
StringBuilder val = new StringBuilder("\n");
for (int i = 0; i < depth; i++) {
val.append(" ");
}
return val.toString();
}

private static String formatTokens(List<Token> tokens) {
StringBuilder format = new StringBuilder();
int depth = 0;
for (Token token : tokens) {
if (token.type == TokenType.OBJECT_CLOSE || token.type == TokenType.ARRAY_CLOSE) {
format.append(enter(--depth));
}
format.append(token.value);
if (token.type == TokenType.OBJECT_OPEN || token.type == TokenType.ARRAY_OPEN) {
format.append(enter(++depth));
}
if (token.type == TokenType.SPLIT) {
format.append(enter(depth));
}
}
return format.toString();
}

private static List<Token> parseTextToTokens(String text) throws Exception {
List<Token> tokens = new ArrayList<>();
while (!text.isEmpty()) {
Token token = null;
for (TokenType type : TokenType.values()) {
token = getFirstToken(text, type);
if (token != null) {
break;
}
}
if (token == null) {
throw new Exception();
}
text = text.substring(token.value.length());
if (token.type == TokenType.SPACE) {
continue;
}
tokens.add(token);
}
return tokens;
}

private static class Token {
private TokenType type;
private String value;

private Token(TokenType type, String value) {
this.type = type;
this.value = value;
}
}

private static class TokensParseResult {
private Object data;
private int nextIndex;

TokensParseResult(Object data, int nextIndex) {
this.data = data;
this.nextIndex = nextIndex;
}
}

private enum TokenType {
STRING("^(\"([^\"\\\\]|\\\\.)*\")"),
NUMBER("^(-?\\d+(\\.\\d+)?)"),
BOOLEAN("^(true|false)"),
NULL("^(null)"),
OBJECT_OPEN("^(\\{)"),
OBJECT_CLOSE("^(\\})"),
ARRAY_OPEN("^(\\[)"),
ARRAY_CLOSE("^(\\])"),
COLON("^(\\:)"),
SPLIT("^(\\,)"),
SPACE("^([\\s\\r\\n]+)");

private Pattern pattern;

TokenType(String reg) {
this.pattern = Pattern.compile(reg);
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.log(parseText(`
{
"transformRequest":
{},
"transformResponse":
{},
"timeout": 10000,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": ["X-XSRF-TOKEN","X-XSRF-TOKEN2","X-XSRF-TOKEN3"],
"maxContentLength": -1,
"headers":
{
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/json;charset=UTF-8"
},
"baseURL": "/test",
"method": "post",
"url": "/test",
"data": "{\\"page\\":1,\\"limit\\":10}"
}`));

实现

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
let tokenReg = {
string: new RegExp("^(\"([^\"\\\\]|\\\\.)*\")"),
number: new RegExp("^(-?\\d+(\\.\\d+)?)"),
boolean: new RegExp("^(true|false)"),
null: new RegExp("^(null)"),
objectOpen: new RegExp("^(\\{)"),
objectClose: new RegExp("^(\\})"),
arrayOpen: new RegExp("^(\\[)"),
arrayClose: new RegExp("^(\\])"),
colon: new RegExp("^(\\:)"),
split: new RegExp("^(\\,)"),
space: new RegExp("^(\\s+)"),
}

function getFirstToken(text, type) {
if (!tokenReg[type].test(text)) {
return null;
}
return {
type,
match: text.match(tokenReg[type])[1]
};
}

function transString(text) {
let val = "";
for (let i = 1; i < text.length - 1; i++) {
if (text[i] != "\\") {
val += text[i];
} else {
switch (text[i + 1]) {
case "n":
val += "\n";
break;
case "t":
val += "\t";
break;
default:
val += text[i + 1];
break;
}
i++;
}
}
return val;
}

function parseTokens(tokens, index) {
switch (tokens[index].type) {
case "objectOpen": {
index++;
let obj = {};
while (tokens[index].type != "objectClose") {
if (tokens[index].type == "string" && tokens[index + 1].type == "colon") {
let item = parseTokens(tokens, index + 2);
obj[transString(tokens[index].match)] = item.data;
index = item.nextIndex;
if (tokens[index].type == 'split') {
index++;
}
} else {
throw new Error();
}
}
return {
nextIndex: index + 1,
data: obj
};
}
case "arrayOpen": {
index++;
let obj = [];
while (tokens[index].type != "arrayClose") {
let item = parseTokens(tokens, index);
obj.push(item.data);
index = item.nextIndex;
if (tokens[index].type == 'split') {
index++;
}
}
return {
nextIndex: index + 1,
data: obj
};

}
case "string": {
return {
nextIndex: index + 1,
data: transString(tokens[index].match)
};
}
case "number": {
return {
nextIndex: index + 1,
data: Number(tokens[index].match)
};
}
case "boolean": {
return {
nextIndex: index + 1,
data: Boolean(tokens[index].match)
};
}
case "null": {
return {
nextIndex: index + 1,
data: null
};
}
default:
throw new Error();
}
}

function formatTokens(tokens) {
let format = "";
let deepth = 0;
const enter = (dp) => format += "\n" + " ".repeat(dp);
for (let item of tokens) {
if (item.type == 'objectClose' || item.type == 'arrayClose') {
enter(--deepth);
}
format += item.match;
if (item.type == 'objectOpen' || item.type == 'arrayOpen') {
enter(++deepth);
}
if (item.type == 'split') {
enter(deepth);
}
if (item.type == 'value') {
format += " ";
}
}
return format;
}

function parseText(text) {
let tokens = [];
while (text) {
let item
for (let k in tokenReg) {
item = getFirstToken(text, k);
if (item) {
break;
}
}
tokens.push(item);
text = text.substring(item.match.length);
}
tokens = tokens.filter(o => o.type != "space");
console.log(formatTokens(tokens));
return parseTokens(tokens, 0).data;
}

安装

1
2
npm install monaco-editor@0.20.0
npm install monaco-editor-webpack-plugin@1.9.0

vue.config

1
2
3
4
5
6
7
8
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = {
configureWebpack: {
plugins: [
new MonacoWebpackPlugin()
]
}
};

文件结构

1
2
3
4
5
6
monaco-editor
|- util
|- |- javascript-completion.js
|- |- sql-completion.js
|- |- log-language.js
|- MonacoEditor.vue

monaco-editor/MonacoEditor.vue

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
123
124
125
126
127
128
129
130
131
132
<template>
<div ref="editor" class="main"></div>
</template>

<script>
import * as monaco from "monaco-editor";
import createSqlCompleter from "./util/sql-completion";
import createJavascriptCompleter from "./util/javascript-completion";
import registerLanguage from "./util/log-language";
const global = {};

const getHints = (model) => {
let id = model.id.substring(6);
return (global[id] && global[id].hints) || [];
};
monaco.languages.registerCompletionItemProvider(
"sql",
createSqlCompleter(getHints)
);
monaco.languages.registerCompletionItemProvider(
"javascript",
createJavascriptCompleter(getHints)
);
registerLanguage(monaco);
/**
* monaco options
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html
*/
export default {
props: {
options: {
type: Object,
default() {
return {};
},
},
value: {
type: String,
required: true,
},
language: {
type: String,
},
hints: {
type: Array,
default() {
return [];
},
},
},
name: "MonacoEditor",
data() {
return {
editorInstance: null,
defaultOptions: {
theme: "vs",
fontSize: 14,
},
};
},
watch: {
value() {
if (this.value !== this.editorInstance.getValue()) {
this.editorInstance.setValue(this.value);
}
},
},
mounted() {
this.initEditor();
global[this.editorInstance._id] = this;
window.addEventListener("resize", this.layout);
},
destroyed() {
this.editorInstance.dispose();
global[this.editorInstance._id] = null;
window.removeEventListener("resize", this.layout);
},
methods: {
layout() {
this.editorInstance.layout();
},
undo() {
this.editorInstance.trigger("anyString", "undo");
this.onValueChange();
},
redo() {
this.editorInstance.trigger("anyString", "redo");
this.onValueChange();
},
getOptions() {
let props = { value: this.value };
this.language !== undefined && (props.language = this.language);
let options = Object.assign({}, this.defaultOptions, this.options, props);
return options;
},
onValueChange() {
this.$emit("input", this.editorInstance.getValue());
this.$emit("change", this.editorInstance.getValue());
},
initEditor() {
this.MonacoEnvironment = {
getWorkerUrl: function () {
return "./editor.worker.bundle.js";
},
};

this.editorInstance = monaco.editor.create(
this.$refs.editor,
this.getOptions()
);
this.editorInstance.onContextMenu((e) => {
this.$emit("contextmenu", e);
});
this.editorInstance.onDidChangeModelContent(() => {
this.onValueChange();
});
this.editorInstance.addCommand(
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S,
() => {
this.$emit("save", this.editorInstance.getValue());
}
);
},
},
};
</script>

<style scoped>
.main /deep/ .view-lines * {
font-family: Consolas, "Courier New", monospace !important;
}
</style>

monaco-editor/util/javascript-completion.js

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
import * as monaco from 'monaco-editor'
// js 有内置提示
function createCompleter(getExtraHints) {
const createSuggestions = function (model, textUntilPosition) {
let text = model.getValue();
textUntilPosition = textUntilPosition.replace(/[\*\[\]@\$\(\)]/g, "").replace(/(\s+|\.)/g, " ");
let arr = textUntilPosition.split(/[\s;]/);
let activeStr = arr[arr.length - 1];
let len = activeStr.length;
let rexp = new RegExp("([^\\w]|^)" + activeStr + "\\w*", "gim");
let match = text.match(rexp);
let mergeHints = Array.from(new Set([...getExtraHints(model)]))
.sort()
.filter(ele => {
let rexp = new RegExp(ele.substr(0, len), "gim");
return (match && match.length === 1 && ele === activeStr) ||
ele.length === 1 ? false : activeStr.match(rexp);
});
return mergeHints.map(ele => ({
label: ele,
kind: monaco.languages.CompletionItemKind.Text,
documentation: ele,
insertText: ele
}));
};
return {
provideCompletionItems(model, position) {
let textUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
});
return { suggestions: createSuggestions(model, textUntilPosition) };
}
}
}
export default createCompleter;

monaco-editor/util/sql-completion.js

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
import * as monaco from 'monaco-editor'
const hints = [
"SELECT",
"INSERT",
"DELETE",
"UPDATE",
"CREATE TABLE",
"DROP TABLE",
"ALTER TABLE",
"CREATE VIEW",
"DROP VIEW",
"CREATE INDEX",
"DROP INDEX",
"CREATE PROCEDURE",
"DROP PROCEDURE",
"CREATE TRIGGER",
"DROP TRIGGER",
"CREATE SCHEMA",
"DROP SCHEMA",
"CREATE DOMAIN",
"ALTER DOMAIN",
"DROP DOMAIN",
"GRANT",
"DENY",
"REVOKE",
"COMMIT",
"ROLLBACK",
"SET TRANSACTION",
"DECLARE",
"EXPLAN",
"OPEN",
"FETCH",
"CLOSE",
"PREPARE",
"EXECUTE",
"DESCRIBE",
"FORM",
"ORDER BY"]
function createCompleter(getExtraHints) {
const createSuggestions = function (model, textUntilPosition) {
let text = model.getValue();
textUntilPosition = textUntilPosition.replace(/[\*\[\]@\$\(\)]/g, "").replace(/(\s+|\.)/g, " ");
let arr = textUntilPosition.split(/[\s;]/);
let activeStr = arr[arr.length - 1];
let len = activeStr.length;
let rexp = new RegExp("([^\\w]|^)" + activeStr + "\\w*", "gim");
let match = text.match(rexp);
let textHints = !match ? [] :
match.map(ele => {
let rexp = new RegExp(activeStr, "gim");
let search = ele.search(rexp);
return ele.substr(search);
});
let mergeHints = Array.from(new Set([...hints, ...textHints, ...getExtraHints(model)]))
.sort()
.filter(ele => {
let rexp = new RegExp(ele.substr(0, len), "gim");
return (match && match.length === 1 && ele === activeStr) ||
ele.length === 1 ? false : activeStr.match(rexp);
});
return mergeHints.map(ele => ({
label: ele,
kind: hints.indexOf(ele) > -1 ?
monaco.languages.CompletionItemKind.Keyword :
monaco.languages.CompletionItemKind.Text,
documentation: ele,
insertText: ele
}));
};
return {
provideCompletionItems(model, position) {
let textUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
});
return { suggestions: createSuggestions(model, textUntilPosition) };
}
}
}
export default createCompleter;

monaco-editor/util/log-language.js

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
function registerLanguage(monaco) {
monaco.languages.register({
id: "log"
});
monaco.languages.setMonarchTokensProvider("log", {
tokenizer: {
root: [
[/(^[=a-zA-Z].*|\d\s.*)/, "log-normal"],
[/\sERROR\s.*/, "log-error"],
[/\sWARN\s.*/, "log-warn"],
[/\sINFO\s.*/, "log-info"],
[
/^([0-9]{4}||[0-9]{2})-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]{3})?/,
"log-date",
],
[
/^[0-9]{2}\/[0-9]{2}\/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]{3})?/,
"log-date",
],
[/(^\*\*Waiting queue:.*)/, "log-info"],
[/(^\*\*result tips:.*)/, "log-info"],
],
},
});
monaco.editor.defineTheme("log", {
base: "vs",
inherit: true,
rules: [{
token: "log-info",
foreground: "4b71ca"
},
{
token: "log-error",
foreground: "ff0000",
fontStyle: "bold"
},
{
token: "log-warn",
foreground: "FFA500"
},
{
token: "log-date",
foreground: "008800"
},
{
token: "log-normal",
foreground: "808080"
},
],
colors: {
"editor.lineHighlightBackground": "#ffffff",
"editorGutter.background": "#f7f7f7",
},
});

}

export default registerLanguage;

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<monaco-editor
v-model="value"
language="sql"
:options="{}"
:hints="['table_name']"
style="width:400px;height:300px"
/>
</template>
<script>
import MonacoEditor from "@/components/monaco-editor/MonacoEditor";
export default {
components: {
MonacoEditor,
},
data() {
return {
value: "xxx",
};
},
};
</script>

实现

对值进行分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getObjectType(object) {
if (object === undefined) {
return "undefined";
}
if (object === null) {
return "null";
}
if (typeof object !== "object") {
return "unit";
}
if (object instanceof Array) {
return "array";
}
return "object";
}

arrayobject的值进行递归合并

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
function deepMergeObject(...objects) {
let subs = [];
for (let obj of objects) {
if (obj === undefined) {
continue;
}
if (subs.length > 0 && getObjectType(obj) !== getObjectType(subs[0])) {
subs = [obj];
} else {
subs.push(obj);
}
}
if (subs.length === 0) {
return undefined;
}
let type = getObjectType(subs[0]);
if (subs.length === 1 || (type !== "object" && type !== "array")) {
return subs[subs.length - 1];
}
let keySet = new Set();
for (let obj of objects) {
for (let k in obj) {
keySet.add(k);
}
}
let newObj = type === "object" ? {} : [];
for (let k of keySet) {
let child = [];
for (let obj of subs) {
child.push(obj[k]);
}
newObj[k] = deepMergeObject(...child);
}
return newObj;
}

说明

数据分为undefinednullunitobjectarray,
deepMergeObject(a,b,c,d)调用过程中, 不同类型后者覆盖前者,
忽略undefined, objectarray进行keyindex遍历对子值进行递归合并

1. 下载Apache HTTP Server

http://httpd.apache.org/download.cgi 选择对应系统的版本下载

Windows下载 https://www.apachehaus.com/cgi-bin/download.plx

2. 下载PHP

https://www.php.net/downloads 选择对应版本下载

Windows下载 https://windows.php.net/download

注意选择 Thread Safe 版本下载

3. 配置Apache HTTP Server

解压下载下来的 Apache HTTP ServerPHP 打开 Apache24/conf/httpd.conf

搜索 Define SRVROOT, 填入 Apache HTTP Server 文件的绝对路径

1
Define SRVROOT "/httpd/Apache24"

找到 LoadModule 列表 加入一行配置, 这里的 php 为刚才解压 PHP 文件的绝对路径

1
LoadModule php7_module "/php/php7apache2_4.dll"

若目录下没有 php7apache2_4.dll 文件, 检查一下是否下载的是 Non Thread Safe 的版本

搜索 DirectoryIndex, 改为

1
DirectoryIndex index.php index.html

到此就配置完了

4. 测试PHP

Apache24/htdocs 下创建 info.php

1
<?php phpinfo(); ?>

打开 http://localhost/info.php

5. PHP扩展配置

PHP 目录下复制 php.ini-development 改名为 php.ini

配置扩展目录路径

1
extension_dir = /php/ext

在需要的扩展前去掉;extension=xxx分号注释

或者增加 ext 目录下的扩展例如

1
extension=php_mysqli

身份证前六位为区号, 中间八位为出生日期, 再后三位为顺序码, 偶数分配给女性, 奇数给男性, 最后一位为校验位, 值为身份证前十七位 加权求和 然后对11取模 进行映射

权值为 [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]

映射为 ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"]

示例代码(JS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function verify(idCardNumber) {
const REG = /\d{17}[\dX]/;
const W = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const V = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"];
const C0 = "0".charCodeAt(0);

if (!REG.test(idCardNumber)) {
return false;
}

let sum = 0;
for (let i = 0; i < idCardNumber.length - 1; i++) {
sum += ((idCardNumber.charCodeAt(i) - C0) * W[i]);
}

let v = V[sum % 11];
return idCardNumber.charAt(idCardNumber.length - 1) == v;
}