mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-01-11 17:04:13 +00:00
Remove TypeScript-related code and documentation
- Remove TypeScript API endpoints from PlaygroundApi - Remove TypeScript methods from DbService interface and implementation - Delete PlaygroundTypeScriptCode model class - Delete TypeScript documentation files - Clean up unused imports
This commit is contained in:
@@ -1,293 +0,0 @@
|
|||||||
# TypeScript编译器集成 - 实现总结
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
成功为JavaScript解析器演练场添加了完整的TypeScript支持。用户现在可以使用现代TypeScript语法编写解析器代码,系统会自动编译为ES5并在后端执行。
|
|
||||||
|
|
||||||
## 实现范围
|
|
||||||
|
|
||||||
### ✅ 前端实现
|
|
||||||
|
|
||||||
1. **TypeScript编译器集成**
|
|
||||||
- 添加 `typescript` npm 包依赖
|
|
||||||
- 创建 `tsCompiler.js` 编译器工具类
|
|
||||||
- 支持所有标准 TypeScript 特性
|
|
||||||
- 编译目标:ES5(与后端Nashorn引擎兼容)
|
|
||||||
|
|
||||||
2. **用户界面增强**
|
|
||||||
- 工具栏语言选择器(JavaScript ⟷ TypeScript)
|
|
||||||
- 实时编译错误提示
|
|
||||||
- TypeScript 示例模板(包含 async/await)
|
|
||||||
- 语言偏好本地存储
|
|
||||||
|
|
||||||
3. **编译逻辑**
|
|
||||||
```
|
|
||||||
用户输入TS代码 → 自动编译为ES5 → 发送到后端执行
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ 后端实现
|
|
||||||
|
|
||||||
1. **数据库模型**
|
|
||||||
- 新表:`playground_typescript_code`
|
|
||||||
- 存储原始 TypeScript 代码
|
|
||||||
- 存储编译后的 ES5 代码
|
|
||||||
- 通过 `parserId` 关联到 `playground_parser`
|
|
||||||
|
|
||||||
2. **API端点**
|
|
||||||
- `POST /v2/playground/typescript` - 保存TS代码
|
|
||||||
- `GET /v2/playground/typescript/:parserId` - 获取TS代码
|
|
||||||
- `PUT /v2/playground/typescript/:parserId` - 更新TS代码
|
|
||||||
|
|
||||||
3. **数据库服务**
|
|
||||||
- `DbService` 新增 TypeScript 相关方法
|
|
||||||
- `DbServiceImpl` 实现具体的数据库操作
|
|
||||||
- 支持自动建表
|
|
||||||
|
|
||||||
### ✅ 文档
|
|
||||||
|
|
||||||
1. **用户指南** (`TYPESCRIPT_PLAYGROUND_GUIDE.md`)
|
|
||||||
- 快速开始教程
|
|
||||||
- TypeScript 特性说明
|
|
||||||
- API 参考
|
|
||||||
- 最佳实践
|
|
||||||
- 故障排除
|
|
||||||
|
|
||||||
2. **代码示例**
|
|
||||||
- JavaScript 示例(ES5)
|
|
||||||
- TypeScript 示例(包含类型注解和 async/await)
|
|
||||||
|
|
||||||
## 架构设计
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────┐
|
|
||||||
│ 浏览器前端 (Vue 3) │
|
|
||||||
├─────────────────────────────────────────────┤
|
|
||||||
│ 1. 用户编写 TypeScript 代码 │
|
|
||||||
│ 2. TypeScript 编译器编译为 ES5 │
|
|
||||||
│ 3. 显示编译错误(如有) │
|
|
||||||
│ 4. 发送 ES5 代码到后端 │
|
|
||||||
└─────────────────────────────────────────────┘
|
|
||||||
↓
|
|
||||||
┌─────────────────────────────────────────────┐
|
|
||||||
│ 后端服务器 (Java + Vert.x) │
|
|
||||||
├─────────────────────────────────────────────┤
|
|
||||||
│ 1. 接收 ES5 代码 │
|
|
||||||
│ 2. 注入 fetch-runtime.js (已实现) │
|
|
||||||
│ 3. Nashorn 引擎执行 │
|
|
||||||
│ 4. 返回执行结果 │
|
|
||||||
└─────────────────────────────────────────────┘
|
|
||||||
↓
|
|
||||||
┌─────────────────────────────────────────────┐
|
|
||||||
│ 数据库 (SQLite) │
|
|
||||||
├─────────────────────────────────────────────┤
|
|
||||||
│ playground_parser (ES5代码) │
|
|
||||||
│ playground_typescript_code (TS源代码) │
|
|
||||||
└─────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 技术细节
|
|
||||||
|
|
||||||
### TypeScript 编译配置
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
target: 'ES5', // 目标ES5(Nashorn兼容)
|
|
||||||
module: 'None', // 不使用模块系统
|
|
||||||
noEmitOnError: true, // 有错误时不生成代码
|
|
||||||
downlevelIteration: true, // 支持迭代器降级
|
|
||||||
esModuleInterop: true, // ES模块互操作
|
|
||||||
lib: ['es5', 'dom'] // 类型库
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 支持的 TypeScript 特性
|
|
||||||
|
|
||||||
- ✅ 类型注解 (Type Annotations)
|
|
||||||
- ✅ 接口 (Interfaces)
|
|
||||||
- ✅ 类型别名 (Type Aliases)
|
|
||||||
- ✅ 枚举 (Enums)
|
|
||||||
- ✅ 泛型 (Generics)
|
|
||||||
- ✅ async/await → Promise 转换
|
|
||||||
- ✅ 箭头函数
|
|
||||||
- ✅ 模板字符串
|
|
||||||
- ✅ 解构赋值
|
|
||||||
- ✅ 可选链 (Optional Chaining)
|
|
||||||
- ✅ 空值合并 (Nullish Coalescing)
|
|
||||||
|
|
||||||
### 代码示例对比
|
|
||||||
|
|
||||||
#### 输入 (TypeScript)
|
|
||||||
```typescript
|
|
||||||
async function parse(
|
|
||||||
shareLinkInfo: any,
|
|
||||||
http: any,
|
|
||||||
logger: any
|
|
||||||
): Promise<string> {
|
|
||||||
const url: string = shareLinkInfo.getShareUrl();
|
|
||||||
logger.info(`开始解析: ${url}`);
|
|
||||||
|
|
||||||
const response = await fetch(url);
|
|
||||||
const html: string = await response.text();
|
|
||||||
|
|
||||||
return html.match(/url="([^"]+)"/)?.[1] || "";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 输出 (ES5)
|
|
||||||
```javascript
|
|
||||||
function parse(shareLinkInfo, http, logger) {
|
|
||||||
return __awaiter(this, void 0, void 0, function () {
|
|
||||||
var url, response, html, _a;
|
|
||||||
return __generator(this, function (_b) {
|
|
||||||
switch (_b.label) {
|
|
||||||
case 0:
|
|
||||||
url = shareLinkInfo.getShareUrl();
|
|
||||||
logger.info("开始解析: " + url);
|
|
||||||
return [4, fetch(url)];
|
|
||||||
case 1:
|
|
||||||
response = _b.sent();
|
|
||||||
return [4, response.text()];
|
|
||||||
case 2:
|
|
||||||
html = _b.sent();
|
|
||||||
return [2, ((_a = html.match(/url="([^"]+)"/)) === null || _a === void 0 ? void 0 : _a[1]) || ""];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 代码质量改进
|
|
||||||
|
|
||||||
基于代码审查反馈,进行了以下改进:
|
|
||||||
|
|
||||||
1. **编译器配置优化**
|
|
||||||
- ✅ `noEmitOnError: true` - 防止执行有错误的代码
|
|
||||||
|
|
||||||
2. **代码可维护性**
|
|
||||||
- ✅ 使用常量替代魔术字符串
|
|
||||||
- ✅ 添加 `LANGUAGE` 常量对象
|
|
||||||
|
|
||||||
3. **用户体验优化**
|
|
||||||
- ✅ 优先使用显式语言选择
|
|
||||||
- ✅ TypeScript语法检测作为辅助提示
|
|
||||||
- ✅ 清晰的错误消息
|
|
||||||
|
|
||||||
4. **代码清理**
|
|
||||||
- ✅ 移除无关的生成文件
|
|
||||||
|
|
||||||
## 测试结果
|
|
||||||
|
|
||||||
### 构建测试
|
|
||||||
- ✅ Maven 编译:成功
|
|
||||||
- ✅ npm 构建:成功(预期的大小警告)
|
|
||||||
- ✅ TypeScript 编译:正常工作
|
|
||||||
- ✅ 数据库模型:有效
|
|
||||||
|
|
||||||
### 功能测试(需手动验证)
|
|
||||||
- [ ] UI 语言选择器
|
|
||||||
- [ ] TypeScript 编译
|
|
||||||
- [ ] 数据库表自动创建
|
|
||||||
- [ ] API 端点
|
|
||||||
- [ ] 发布工作流(TS → 数据库 → ES5执行)
|
|
||||||
- [ ] 错误处理
|
|
||||||
|
|
||||||
## 安全性
|
|
||||||
|
|
||||||
- ✅ 输入验证(代码长度限制:128KB)
|
|
||||||
- ✅ SQL注入防护(参数化查询)
|
|
||||||
- ✅ IP日志记录(审计追踪)
|
|
||||||
- ✅ 继承现有SSRF防护
|
|
||||||
- ✅ 无新安全漏洞
|
|
||||||
|
|
||||||
## 数据库结构
|
|
||||||
|
|
||||||
### playground_typescript_code 表
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | BIGINT | 主键 |
|
|
||||||
| parser_id | BIGINT | 关联解析器ID(外键) |
|
|
||||||
| ts_code | TEXT | TypeScript源代码 |
|
|
||||||
| es5_code | TEXT | 编译后ES5代码 |
|
|
||||||
| compile_errors | VARCHAR(2000) | 编译错误 |
|
|
||||||
| compiler_version | VARCHAR(32) | 编译器版本 |
|
|
||||||
| compile_options | VARCHAR(1000) | 编译选项(JSON) |
|
|
||||||
| create_time | DATETIME | 创建时间 |
|
|
||||||
| update_time | DATETIME | 更新时间 |
|
|
||||||
| is_valid | BOOLEAN | 编译是否成功 |
|
|
||||||
| ip | VARCHAR(64) | 创建者IP |
|
|
||||||
|
|
||||||
### 关系
|
|
||||||
- `playground_typescript_code.parser_id` → `playground_parser.id` (外键)
|
|
||||||
- 一对一关系:一个解析器对应一个TypeScript代码记录
|
|
||||||
|
|
||||||
## 文件清单
|
|
||||||
|
|
||||||
### 新增文件 (3)
|
|
||||||
1. `web-front/src/utils/tsCompiler.js` - TS编译器工具
|
|
||||||
2. `web-service/src/main/java/cn/qaiu/lz/web/model/PlaygroundTypeScriptCode.java` - 数据模型
|
|
||||||
3. `parser/doc/TYPESCRIPT_PLAYGROUND_GUIDE.md` - 用户文档
|
|
||||||
|
|
||||||
### 修改文件 (5)
|
|
||||||
1. `web-front/package.json` - 添加typescript依赖
|
|
||||||
2. `web-front/src/views/Playground.vue` - UI和编译逻辑
|
|
||||||
3. `web-front/src/utils/playgroundApi.js` - TS API方法
|
|
||||||
4. `web-service/src/main/java/cn/qaiu/lz/web/service/DbService.java` - 接口定义
|
|
||||||
5. `web-service/src/main/java/cn/qaiu/lz/web/service/impl/DbServiceImpl.java` - 实现
|
|
||||||
6. `web-service/src/main/java/cn/qaiu/lz/web/controller/PlaygroundApi.java` - API端点
|
|
||||||
|
|
||||||
## 未来改进计划
|
|
||||||
|
|
||||||
- [ ] 显示编译后的ES5代码预览
|
|
||||||
- [ ] 添加专用的编译错误面板
|
|
||||||
- [ ] 提供完整的TypeScript类型定义文件(.d.ts)
|
|
||||||
- [ ] 支持代码自动补全
|
|
||||||
- [ ] TypeScript代码片段库
|
|
||||||
- [ ] 更多编译选项配置
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### 快速开始
|
|
||||||
|
|
||||||
1. **选择语言**
|
|
||||||
- 点击工具栏中的"TypeScript"按钮
|
|
||||||
|
|
||||||
2. **编写代码**
|
|
||||||
- 点击"加载示例"查看TypeScript示例
|
|
||||||
- 编写自己的TypeScript代码
|
|
||||||
|
|
||||||
3. **运行测试**
|
|
||||||
- 点击"运行"按钮
|
|
||||||
- 查看编译结果和执行结果
|
|
||||||
|
|
||||||
4. **发布脚本**
|
|
||||||
- 测试通过后点击"发布脚本"
|
|
||||||
- 系统自动保存TS源码和ES5编译结果
|
|
||||||
|
|
||||||
## 兼容性
|
|
||||||
|
|
||||||
- ✅ 与现有JavaScript功能完全兼容
|
|
||||||
- ✅ 不影响现有解析器
|
|
||||||
- ✅ 向后兼容
|
|
||||||
- ✅ 无破坏性更改
|
|
||||||
|
|
||||||
## 性能
|
|
||||||
|
|
||||||
- **编译时间**:几毫秒到几百毫秒(取决于代码大小)
|
|
||||||
- **运行时开销**:无(编译在前端完成)
|
|
||||||
- **存储开销**:额外存储TypeScript源码(TEXT类型)
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
成功实现了完整的TypeScript支持,包括:
|
|
||||||
- ✅ 前端编译器集成
|
|
||||||
- ✅ 后端数据存储
|
|
||||||
- ✅ API端点
|
|
||||||
- ✅ 用户界面
|
|
||||||
- ✅ 完整文档
|
|
||||||
- ✅ 代码质量优化
|
|
||||||
- ✅ 安全验证
|
|
||||||
|
|
||||||
**状态:生产就绪 ✅**
|
|
||||||
|
|
||||||
该功能已经过全面测试,所有代码审查问题已解决,可以安全地部署到生产环境。
|
|
||||||
@@ -1,378 +0,0 @@
|
|||||||
# TypeScript/ES6+ 浏览器编译与Fetch API实现
|
|
||||||
|
|
||||||
## 项目概述
|
|
||||||
|
|
||||||
本实现提供了**纯前端TypeScript编译 + 后端ES5引擎 + Fetch API适配**的完整解决方案,允许用户在浏览器中编写TypeScript/ES6+代码(包括async/await),编译为ES5后在后端Nashorn JavaScript引擎中执行。
|
|
||||||
|
|
||||||
## 架构图
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ 浏览器端 (计划中) │
|
|
||||||
├─────────────────────────────────────────────────────────┤
|
|
||||||
│ 用户编写 TypeScript/ES6+ 代码 (async/await) │
|
|
||||||
│ ↓ │
|
|
||||||
│ TypeScript.js 浏览器内编译为 ES5 │
|
|
||||||
│ ↓ │
|
|
||||||
│ 生成的 ES5 代码发送到后端 │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
↓
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ 后端 (已实现) │
|
|
||||||
├─────────────────────────────────────────────────────────┤
|
|
||||||
│ 1. 接收 ES5 代码 │
|
|
||||||
│ 2. 注入 fetch-runtime.js (Promise + fetch polyfill) │
|
|
||||||
│ 3. 注入 JavaFetch 桥接对象 │
|
|
||||||
│ 4. Nashorn 引擎执行 ES5 代码 │
|
|
||||||
│ 5. fetch() → JavaFetch → JsHttpClient → Vert.x │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 已实现功能
|
|
||||||
|
|
||||||
### ✅ 后端 ES5 执行环境
|
|
||||||
|
|
||||||
#### 1. Promise Polyfill (完整的 Promise/A+ 实现)
|
|
||||||
|
|
||||||
文件: `parser/src/main/resources/fetch-runtime.js`
|
|
||||||
|
|
||||||
**功能特性:**
|
|
||||||
- ✅ `new Promise(executor)` 构造函数
|
|
||||||
- ✅ `promise.then(onFulfilled, onRejected)` 链式调用
|
|
||||||
- ✅ `promise.catch(onRejected)` 错误处理
|
|
||||||
- ✅ `promise.finally(onFinally)` 清理操作
|
|
||||||
- ✅ `Promise.resolve(value)` 静态方法
|
|
||||||
- ✅ `Promise.reject(reason)` 静态方法
|
|
||||||
- ✅ `Promise.all(promises)` 并行等待
|
|
||||||
- ✅ `Promise.race(promises)` 竞速等待
|
|
||||||
|
|
||||||
**实现细节:**
|
|
||||||
- 纯 ES5 语法,无ES6+特性依赖
|
|
||||||
- 使用 `setTimeout(fn, 0)` 实现异步执行
|
|
||||||
- 支持 Promise 链式调用和错误传播
|
|
||||||
- 自动处理 Promise 嵌套和展开
|
|
||||||
|
|
||||||
#### 2. Fetch API Polyfill (标准 fetch 接口)
|
|
||||||
|
|
||||||
文件: `parser/src/main/resources/fetch-runtime.js`
|
|
||||||
|
|
||||||
**支持的 HTTP 方法:**
|
|
||||||
- ✅ GET
|
|
||||||
- ✅ POST
|
|
||||||
- ✅ PUT
|
|
||||||
- ✅ DELETE
|
|
||||||
- ✅ PATCH
|
|
||||||
- ✅ HEAD
|
|
||||||
|
|
||||||
**Request 选项支持:**
|
|
||||||
```javascript
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST', // HTTP 方法
|
|
||||||
headers: { // 请求头
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer token'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ // 请求体
|
|
||||||
key: 'value'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response 对象方法:**
|
|
||||||
- ✅ `response.text()` - 获取文本响应 (返回 Promise)
|
|
||||||
- ✅ `response.json()` - 解析 JSON 响应 (返回 Promise)
|
|
||||||
- ✅ `response.arrayBuffer()` - 获取字节数组
|
|
||||||
- ✅ `response.status` - HTTP 状态码
|
|
||||||
- ✅ `response.ok` - 请求是否成功 (2xx)
|
|
||||||
- ✅ `response.statusText` - 状态文本
|
|
||||||
- ✅ `response.headers.get(name)` - 获取响应头
|
|
||||||
|
|
||||||
#### 3. Java 桥接层
|
|
||||||
|
|
||||||
文件: `parser/src/main/java/cn/qaiu/parser/customjs/JsFetchBridge.java`
|
|
||||||
|
|
||||||
**核心功能:**
|
|
||||||
- 接收 JavaScript fetch API 调用
|
|
||||||
- 转换为 JsHttpClient 调用
|
|
||||||
- 处理请求头、请求体、HTTP 方法
|
|
||||||
- 返回 JsHttpResponse 对象
|
|
||||||
- 自动继承现有的 SSRF 防护机制
|
|
||||||
|
|
||||||
**代码示例:**
|
|
||||||
```java
|
|
||||||
public class JsFetchBridge {
|
|
||||||
private final JsHttpClient httpClient;
|
|
||||||
|
|
||||||
public JsHttpResponse fetch(String url, Map<String, Object> options) {
|
|
||||||
// 解析 method、headers、body
|
|
||||||
// 调用 httpClient.get/post/put/delete/patch
|
|
||||||
// 返回 JsHttpResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. 自动注入机制
|
|
||||||
|
|
||||||
文件:
|
|
||||||
- `parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java`
|
|
||||||
- `parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java`
|
|
||||||
|
|
||||||
**注入流程:**
|
|
||||||
1. 创建 JavaScript 引擎
|
|
||||||
2. 注入 JavaFetch 桥接对象
|
|
||||||
3. 加载 fetch-runtime.js
|
|
||||||
4. 执行用户 JavaScript 代码
|
|
||||||
|
|
||||||
**代码示例:**
|
|
||||||
```java
|
|
||||||
// 注入 JavaFetch
|
|
||||||
engine.put("JavaFetch", new JsFetchBridge(httpClient));
|
|
||||||
|
|
||||||
// 加载 fetch runtime
|
|
||||||
String fetchRuntime = loadFetchRuntime();
|
|
||||||
engine.eval(fetchRuntime);
|
|
||||||
|
|
||||||
// 现在 JavaScript 环境中可以使用 Promise 和 fetch
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### ES5 风格 (当前可用)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parse(shareLinkInfo, http, logger) {
|
|
||||||
logger.info("开始解析");
|
|
||||||
|
|
||||||
// 使用 fetch API
|
|
||||||
fetch("https://api.example.com/data")
|
|
||||||
.then(function(response) {
|
|
||||||
logger.info("状态码: " + response.status);
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
logger.info("数据: " + JSON.stringify(data));
|
|
||||||
return data.downloadUrl;
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
logger.error("错误: " + error.message);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 或者继续使用传统的 http 对象
|
|
||||||
var response = http.get("https://api.example.com/data");
|
|
||||||
return response.body();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### TypeScript/ES6+ 风格 (需前端编译)
|
|
||||||
|
|
||||||
用户在浏览器中编写:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
async function parse(
|
|
||||||
shareLinkInfo: ShareLinkInfo,
|
|
||||||
http: JsHttpClient,
|
|
||||||
logger: JsLogger
|
|
||||||
): Promise<string> {
|
|
||||||
try {
|
|
||||||
logger.info("开始解析");
|
|
||||||
|
|
||||||
// 使用标准 fetch API
|
|
||||||
const response = await fetch("https://api.example.com/data");
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
logger.info(`下载链接: ${data.downloadUrl}`);
|
|
||||||
|
|
||||||
return data.downloadUrl;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`解析失败: ${error.message}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
浏览器编译为 ES5 后:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parse(shareLinkInfo, http, logger) {
|
|
||||||
return __awaiter(this, void 0, void 0, function() {
|
|
||||||
var response, data, error_1;
|
|
||||||
return __generator(this, function(_a) {
|
|
||||||
switch(_a.label) {
|
|
||||||
case 0:
|
|
||||||
_a.trys.push([0, 3, , 4]);
|
|
||||||
logger.info("开始解析");
|
|
||||||
return [4, fetch("https://api.example.com/data")];
|
|
||||||
case 1:
|
|
||||||
response = _a.sent();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("HTTP " + response.status + ": " + response.statusText);
|
|
||||||
}
|
|
||||||
return [4, response.json()];
|
|
||||||
case 2:
|
|
||||||
data = _a.sent();
|
|
||||||
logger.info("下载链接: " + data.downloadUrl);
|
|
||||||
return [2, data.downloadUrl];
|
|
||||||
case 3:
|
|
||||||
error_1 = _a.sent();
|
|
||||||
logger.error("解析失败: " + error_1.message);
|
|
||||||
throw error_1;
|
|
||||||
case 4: return [2];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
parser/
|
|
||||||
├── src/main/
|
|
||||||
│ ├── java/cn/qaiu/parser/customjs/
|
|
||||||
│ │ ├── JsFetchBridge.java # Java 桥接层
|
|
||||||
│ │ ├── JsParserExecutor.java # 解析器执行器 (已更新)
|
|
||||||
│ │ └── JsPlaygroundExecutor.java # 演练场执行器 (已更新)
|
|
||||||
│ └── resources/
|
|
||||||
│ ├── fetch-runtime.js # Promise + fetch polyfill
|
|
||||||
│ └── custom-parsers/
|
|
||||||
│ └── fetch-demo.js # Fetch 示例解析器
|
|
||||||
├── src/test/java/cn/qaiu/parser/customjs/
|
|
||||||
│ └── JsFetchBridgeTest.java # 单元测试
|
|
||||||
└── doc/
|
|
||||||
└── TYPESCRIPT_FETCH_GUIDE.md # 详细使用指南
|
|
||||||
```
|
|
||||||
|
|
||||||
## 测试验证
|
|
||||||
|
|
||||||
### 运行测试
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 编译项目
|
|
||||||
mvn clean compile -pl parser
|
|
||||||
|
|
||||||
# 运行所有测试
|
|
||||||
mvn test -pl parser
|
|
||||||
|
|
||||||
# 运行 fetch 测试
|
|
||||||
mvn test -pl parser -Dtest=JsFetchBridgeTest
|
|
||||||
```
|
|
||||||
|
|
||||||
### 测试内容
|
|
||||||
|
|
||||||
文件: `parser/src/test/java/cn/qaiu/parser/customjs/JsFetchBridgeTest.java`
|
|
||||||
|
|
||||||
1. **testFetchPolyfillLoaded** - 验证 Promise 和 fetch 是否正确注入
|
|
||||||
2. **testPromiseBasicUsage** - 验证 Promise 基本功能
|
|
||||||
3. **示例解析器** - `fetch-demo.js` 展示完整用法
|
|
||||||
|
|
||||||
## 兼容性说明
|
|
||||||
|
|
||||||
### 支持的特性
|
|
||||||
|
|
||||||
- ✅ Promise/A+ 完整实现
|
|
||||||
- ✅ Fetch API 标准接口
|
|
||||||
- ✅ async/await (通过 TypeScript 编译)
|
|
||||||
- ✅ 所有 HTTP 方法
|
|
||||||
- ✅ Request headers 和 body
|
|
||||||
- ✅ Response 解析 (text, json, arrayBuffer)
|
|
||||||
- ✅ 错误处理和 Promise 链
|
|
||||||
- ✅ 与现有 http 对象共存
|
|
||||||
|
|
||||||
### 不支持的特性
|
|
||||||
|
|
||||||
- ❌ Blob 对象 (使用 arrayBuffer 替代)
|
|
||||||
- ❌ FormData 对象 (使用简单对象替代)
|
|
||||||
- ❌ Request/Response 构造函数
|
|
||||||
- ❌ Streams API
|
|
||||||
- ❌ Service Worker 相关 API
|
|
||||||
- ❌ AbortController (取消请求)
|
|
||||||
|
|
||||||
## 安全性
|
|
||||||
|
|
||||||
### SSRF 防护
|
|
||||||
|
|
||||||
继承自 `JsHttpClient` 的 SSRF 防护:
|
|
||||||
- ✅ 拦截内网 IP (127.0.0.1, 10.x.x.x, 192.168.x.x 等)
|
|
||||||
- ✅ 拦截云服务元数据 API (169.254.169.254 等)
|
|
||||||
- ✅ DNS 解析检查
|
|
||||||
- ✅ 危险域名黑名单
|
|
||||||
|
|
||||||
### 沙箱隔离
|
|
||||||
|
|
||||||
- ✅ SecurityClassFilter 限制类访问
|
|
||||||
- ✅ 禁用 Java 对象直接访问
|
|
||||||
- ✅ 限制文件系统操作
|
|
||||||
|
|
||||||
## 性能优化
|
|
||||||
|
|
||||||
1. **Fetch runtime 缓存**
|
|
||||||
- 首次加载后缓存在静态变量
|
|
||||||
- 避免重复读取文件
|
|
||||||
|
|
||||||
2. **Promise 异步执行**
|
|
||||||
- 使用 setTimeout(0) 实现非阻塞
|
|
||||||
- 避免阻塞 JavaScript 主线程
|
|
||||||
|
|
||||||
3. **工作线程池**
|
|
||||||
- JsParserExecutor: Vert.x 工作线程池
|
|
||||||
- JsPlaygroundExecutor: 独立线程池
|
|
||||||
- 避免阻塞 Event Loop
|
|
||||||
|
|
||||||
## 前端 TypeScript 编译 (计划中)
|
|
||||||
|
|
||||||
### 待实现步骤
|
|
||||||
|
|
||||||
1. **添加 TypeScript 编译器**
|
|
||||||
```bash
|
|
||||||
cd web-front
|
|
||||||
npm install typescript
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **创建编译工具**
|
|
||||||
```javascript
|
|
||||||
// web-front/src/utils/tsCompiler.js
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
export function compileToES5(sourceCode) {
|
|
||||||
return ts.transpileModule(sourceCode, {
|
|
||||||
compilerOptions: {
|
|
||||||
target: ts.ScriptTarget.ES5,
|
|
||||||
module: ts.ModuleKind.None,
|
|
||||||
lib: ['es5', 'dom']
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **更新 Playground UI**
|
|
||||||
- 添加语言选择器 (JavaScript / TypeScript)
|
|
||||||
- 编译前先检查语法错误
|
|
||||||
- 显示编译后的 ES5 代码 (可选)
|
|
||||||
|
|
||||||
## 相关文档
|
|
||||||
|
|
||||||
- [详细使用指南](parser/doc/TYPESCRIPT_FETCH_GUIDE.md)
|
|
||||||
- [JavaScript 解析器开发指南](parser/doc/JAVASCRIPT_PARSER_GUIDE.md)
|
|
||||||
- [自定义解析器扩展指南](parser/doc/CUSTOM_PARSER_GUIDE.md)
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
本实现成功提供了:
|
|
||||||
|
|
||||||
1. **无需 Node 环境** - 纯浏览器编译 + Java 后端执行
|
|
||||||
2. **标准 API** - 使用标准 fetch 和 Promise API
|
|
||||||
3. **向后兼容** - 现有 http 对象仍然可用
|
|
||||||
4. **安全可靠** - SSRF 防护和沙箱隔离
|
|
||||||
5. **易于使用** - 简单的 API,无学习成本
|
|
||||||
|
|
||||||
用户可以用现代 JavaScript/TypeScript 编写代码,自动编译为 ES5 后在后端安全执行,同时享受 fetch API 的便利性。
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
本项目遵循主项目的许可证。
|
|
||||||
@@ -1,451 +0,0 @@
|
|||||||
# 浏览器TypeScript编译和Fetch API支持指南
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
本项目实现了**纯前端TypeScript编译 + 后端ES5引擎 + Fetch API适配**的完整方案,允许用户在浏览器中编写TypeScript/ES6+代码,编译为ES5后在后端JavaScript引擎中执行。
|
|
||||||
|
|
||||||
## 架构设计
|
|
||||||
|
|
||||||
### 1. 浏览器端(前端编译)
|
|
||||||
|
|
||||||
```
|
|
||||||
用户编写TS/ES6+代码
|
|
||||||
↓
|
|
||||||
TypeScript.js (浏览器内编译)
|
|
||||||
↓
|
|
||||||
ES5 JavaScript代码
|
|
||||||
↓
|
|
||||||
发送到后端执行
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 后端(ES5执行环境)
|
|
||||||
|
|
||||||
```
|
|
||||||
接收ES5代码
|
|
||||||
↓
|
|
||||||
注入fetch polyfill + Promise
|
|
||||||
↓
|
|
||||||
注入JavaFetch桥接对象
|
|
||||||
↓
|
|
||||||
Nashorn引擎执行ES5代码
|
|
||||||
↓
|
|
||||||
fetch() 调用 → JavaFetch → JsHttpClient → Vert.x HTTP Client
|
|
||||||
```
|
|
||||||
|
|
||||||
## 已实现的功能
|
|
||||||
|
|
||||||
### ✅ 后端支持
|
|
||||||
|
|
||||||
1. **Promise Polyfill** (`fetch-runtime.js`)
|
|
||||||
- 完整的Promise/A+实现
|
|
||||||
- 支持 `then`、`catch`、`finally`
|
|
||||||
- 支持 `Promise.all`、`Promise.race`
|
|
||||||
- 支持 `Promise.resolve`、`Promise.reject`
|
|
||||||
|
|
||||||
2. **Fetch API Polyfill** (`fetch-runtime.js`)
|
|
||||||
- 标准fetch接口实现
|
|
||||||
- 支持所有HTTP方法(GET、POST、PUT、DELETE、PATCH)
|
|
||||||
- 支持headers、body等选项
|
|
||||||
- Response对象支持:
|
|
||||||
- `text()` - 获取文本响应
|
|
||||||
- `json()` - 解析JSON响应
|
|
||||||
- `arrayBuffer()` - 获取字节数组
|
|
||||||
- `status` - HTTP状态码
|
|
||||||
- `ok` - 请求成功标志
|
|
||||||
- `headers` - 响应头访问
|
|
||||||
|
|
||||||
3. **Java桥接** (`JsFetchBridge.java`)
|
|
||||||
- 将fetch调用转换为JsHttpClient调用
|
|
||||||
- 自动处理请求头、请求体
|
|
||||||
- 支持代理配置
|
|
||||||
- 安全的SSRF防护
|
|
||||||
|
|
||||||
4. **自动注入** (`JsParserExecutor.java` & `JsPlaygroundExecutor.java`)
|
|
||||||
- 在JavaScript引擎初始化时自动注入fetch runtime
|
|
||||||
- 提供`JavaFetch`全局对象
|
|
||||||
- 与现有http对象共存
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### ES5风格(当前支持)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parse(shareLinkInfo, http, logger) {
|
|
||||||
// 使用fetch API
|
|
||||||
fetch("https://api.example.com/data")
|
|
||||||
.then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
logger.info("数据: " + JSON.stringify(data));
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
logger.error("错误: " + error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 或者使用传统的http对象
|
|
||||||
var response = http.get("https://api.example.com/data");
|
|
||||||
return response.body();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### TypeScript风格(需要前端编译)
|
|
||||||
|
|
||||||
用户在浏览器中编写:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
async function parse(shareLinkInfo: ShareLinkInfo, http: JsHttpClient, logger: JsLogger): Promise<string> {
|
|
||||||
try {
|
|
||||||
// 使用标准fetch API
|
|
||||||
const response = await fetch("https://api.example.com/data");
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
logger.info(`获取到数据: ${data.downloadUrl}`);
|
|
||||||
return data.downloadUrl;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`解析失败: ${error.message}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
浏览器内编译后的ES5代码(简化示例):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parse(shareLinkInfo, http, logger) {
|
|
||||||
return __awaiter(this, void 0, void 0, function() {
|
|
||||||
var response, data;
|
|
||||||
return __generator(this, function(_a) {
|
|
||||||
switch(_a.label) {
|
|
||||||
case 0:
|
|
||||||
return [4, fetch("https://api.example.com/data")];
|
|
||||||
case 1:
|
|
||||||
response = _a.sent();
|
|
||||||
return [4, response.json()];
|
|
||||||
case 2:
|
|
||||||
data = _a.sent();
|
|
||||||
logger.info("获取到数据: " + data.downloadUrl);
|
|
||||||
return [2, data.downloadUrl];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 前端TypeScript编译(待实现)
|
|
||||||
|
|
||||||
### 计划实现步骤
|
|
||||||
|
|
||||||
#### 1. 添加TypeScript编译器
|
|
||||||
|
|
||||||
在前端项目中添加`typescript.js`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 下载TypeScript编译器浏览器版本
|
|
||||||
cd webroot/static
|
|
||||||
wget https://cdn.jsdelivr.net/npm/typescript@latest/lib/typescript.js
|
|
||||||
```
|
|
||||||
|
|
||||||
或者在Vue项目中:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install typescript
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 创建编译工具类
|
|
||||||
|
|
||||||
`web-front/src/utils/tsCompiler.js`:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
export function compileToES5(sourceCode, fileName = 'script.ts') {
|
|
||||||
const result = ts.transpileModule(sourceCode, {
|
|
||||||
compilerOptions: {
|
|
||||||
target: ts.ScriptTarget.ES5,
|
|
||||||
module: ts.ModuleKind.None,
|
|
||||||
lib: ['es5', 'dom'],
|
|
||||||
experimentalDecorators: false,
|
|
||||||
emitDecoratorMetadata: false,
|
|
||||||
downlevelIteration: true
|
|
||||||
},
|
|
||||||
fileName: fileName
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
js: result.outputText,
|
|
||||||
diagnostics: result.diagnostics,
|
|
||||||
sourceMap: result.sourceMapText
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 更新Playground组件
|
|
||||||
|
|
||||||
在`Playground.vue`中添加编译选项:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<!-- 语言选择 -->
|
|
||||||
<el-radio-group v-model="language">
|
|
||||||
<el-radio label="javascript">JavaScript (ES5)</el-radio>
|
|
||||||
<el-radio label="typescript">TypeScript/ES6+</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
|
|
||||||
<!-- 编辑器 -->
|
|
||||||
<monaco-editor
|
|
||||||
v-model="code"
|
|
||||||
:language="language"
|
|
||||||
@save="handleSave"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 运行按钮 -->
|
|
||||||
<el-button @click="executeCode">运行</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { compileToES5 } from '@/utils/tsCompiler';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
language: 'javascript',
|
|
||||||
code: ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async executeCode() {
|
|
||||||
let codeToExecute = this.code;
|
|
||||||
|
|
||||||
// 如果是TypeScript,先编译
|
|
||||||
if (this.language === 'typescript') {
|
|
||||||
const result = compileToES5(this.code);
|
|
||||||
|
|
||||||
if (result.diagnostics && result.diagnostics.length > 0) {
|
|
||||||
this.$message.error('TypeScript编译错误');
|
|
||||||
console.error(result.diagnostics);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
codeToExecute = result.js;
|
|
||||||
console.log('编译后的ES5代码:', codeToExecute);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送到后端执行
|
|
||||||
const response = await playgroundApi.testScript(
|
|
||||||
codeToExecute,
|
|
||||||
this.shareUrl,
|
|
||||||
this.pwd,
|
|
||||||
this.method
|
|
||||||
);
|
|
||||||
|
|
||||||
this.showResult(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Fetch Runtime详解
|
|
||||||
|
|
||||||
### Promise实现特性
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 基本用法
|
|
||||||
var promise = new SimplePromise(function(resolve, reject) {
|
|
||||||
setTimeout(function() {
|
|
||||||
resolve("成功");
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.then(function(value) {
|
|
||||||
console.log(value); // "成功"
|
|
||||||
});
|
|
||||||
|
|
||||||
// 链式调用
|
|
||||||
promise
|
|
||||||
.then(function(value) {
|
|
||||||
return value + " - 第一步";
|
|
||||||
})
|
|
||||||
.then(function(value) {
|
|
||||||
return value + " - 第二步";
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
console.error(error);
|
|
||||||
})
|
|
||||||
.finally(function() {
|
|
||||||
console.log("完成");
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fetch API特性
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// GET请求
|
|
||||||
fetch("https://api.example.com/data")
|
|
||||||
.then(function(response) {
|
|
||||||
console.log("状态码:", response.status);
|
|
||||||
console.log("成功:", response.ok);
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
console.log("数据:", data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// POST请求
|
|
||||||
fetch("https://api.example.com/submit", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ key: "value" })
|
|
||||||
})
|
|
||||||
.then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
console.log("响应:", data);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 兼容性说明
|
|
||||||
|
|
||||||
### 支持的特性
|
|
||||||
|
|
||||||
- ✅ Promise/A+ 完整实现
|
|
||||||
- ✅ Fetch API 标准接口
|
|
||||||
- ✅ async/await(编译后)
|
|
||||||
- ✅ 所有HTTP方法(GET、POST、PUT、DELETE、PATCH)
|
|
||||||
- ✅ Request headers配置
|
|
||||||
- ✅ Request body(string、JSON、FormData)
|
|
||||||
- ✅ Response.text()、Response.json()
|
|
||||||
- ✅ 与现有http对象共存
|
|
||||||
|
|
||||||
### 不支持的特性
|
|
||||||
|
|
||||||
- ❌ Blob对象(返回字节数组替代)
|
|
||||||
- ❌ FormData对象(使用简单对象替代)
|
|
||||||
- ❌ Request/Response对象构造函数
|
|
||||||
- ❌ Streams API
|
|
||||||
- ❌ Service Worker相关API
|
|
||||||
|
|
||||||
## 测试验证
|
|
||||||
|
|
||||||
### 1. 创建测试解析器
|
|
||||||
|
|
||||||
参考 `parser/src/main/resources/custom-parsers/fetch-demo.js`
|
|
||||||
|
|
||||||
### 2. 测试步骤
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 编译项目
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
|
|
||||||
# 2. 运行服务
|
|
||||||
java -jar web-service/target/netdisk-fast-download.jar
|
|
||||||
|
|
||||||
# 3. 访问演练场
|
|
||||||
浏览器打开: http://localhost:6401/playground
|
|
||||||
|
|
||||||
# 4. 加载fetch-demo.js并测试
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 验证fetch功能
|
|
||||||
|
|
||||||
在演练场中运行:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parse(shareLinkInfo, http, logger) {
|
|
||||||
logger.info("测试fetch API");
|
|
||||||
|
|
||||||
var result = null;
|
|
||||||
fetch("https://httpbin.org/get")
|
|
||||||
.then(function(response) {
|
|
||||||
logger.info("状态码: " + response.status);
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
logger.info("响应: " + JSON.stringify(data));
|
|
||||||
result = "SUCCESS";
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
logger.error("错误: " + error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 等待完成
|
|
||||||
var timeout = 5000;
|
|
||||||
var start = Date.now();
|
|
||||||
while (result === null && (Date.now() - start) < timeout) {
|
|
||||||
java.lang.Thread.sleep(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result || "https://example.com/download";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 安全性
|
|
||||||
|
|
||||||
### SSRF防护
|
|
||||||
|
|
||||||
JsHttpClient已实现SSRF防护:
|
|
||||||
- 拦截内网IP访问(127.0.0.1、10.x.x.x、192.168.x.x等)
|
|
||||||
- 拦截云服务元数据API(169.254.169.254等)
|
|
||||||
- DNS解析检查
|
|
||||||
|
|
||||||
### 沙箱隔离
|
|
||||||
|
|
||||||
- JavaScript引擎使用SecurityClassFilter
|
|
||||||
- 禁用Java对象访问
|
|
||||||
- 限制文件系统访问
|
|
||||||
|
|
||||||
## 性能优化
|
|
||||||
|
|
||||||
1. **Fetch runtime缓存**
|
|
||||||
- 首次加载后缓存在静态变量中
|
|
||||||
- 避免重复读取资源文件
|
|
||||||
|
|
||||||
2. **Promise异步执行**
|
|
||||||
- 使用setTimeout(0)实现异步
|
|
||||||
- 避免阻塞主线程
|
|
||||||
|
|
||||||
3. **工作线程池**
|
|
||||||
- JsParserExecutor使用Vert.x工作线程池
|
|
||||||
- JsPlaygroundExecutor使用独立线程池
|
|
||||||
|
|
||||||
## 相关文件
|
|
||||||
|
|
||||||
### 后端代码
|
|
||||||
- `parser/src/main/resources/fetch-runtime.js` - Fetch和Promise polyfill
|
|
||||||
- `parser/src/main/java/cn/qaiu/parser/customjs/JsFetchBridge.java` - Java桥接层
|
|
||||||
- `parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java` - 解析器执行器
|
|
||||||
- `parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java` - 演练场执行器
|
|
||||||
|
|
||||||
### 示例代码
|
|
||||||
- `parser/src/main/resources/custom-parsers/fetch-demo.js` - Fetch API演示
|
|
||||||
|
|
||||||
### 前端代码(待实现)
|
|
||||||
- `web-front/src/utils/tsCompiler.js` - TypeScript编译工具
|
|
||||||
- `web-front/src/views/Playground.vue` - 演练场界面
|
|
||||||
|
|
||||||
## 下一步计划
|
|
||||||
|
|
||||||
1. ✅ 实现后端fetch polyfill
|
|
||||||
2. ✅ 实现Promise polyfill
|
|
||||||
3. ✅ 集成到JsParserExecutor
|
|
||||||
4. ⏳ 前端添加TypeScript编译器
|
|
||||||
5. ⏳ 更新Playground UI支持TS/ES6+
|
|
||||||
6. ⏳ 添加Monaco编辑器类型提示
|
|
||||||
7. ⏳ 编写更多示例和文档
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
通过这个方案,我们实现了:
|
|
||||||
1. **无需Node环境** - 纯浏览器编译 + Java后端执行
|
|
||||||
2. **标准API** - 使用标准fetch和Promise API
|
|
||||||
3. **向后兼容** - 现有http对象仍然可用
|
|
||||||
4. **安全可靠** - SSRF防护和沙箱隔离
|
|
||||||
5. **易于使用** - 简单的API,无需学习成本
|
|
||||||
|
|
||||||
用户可以在浏览器中用现代JavaScript/TypeScript编写代码,自动编译为ES5后在后端安全执行,同时享受fetch API的便利性。
|
|
||||||
@@ -1,483 +0,0 @@
|
|||||||
# TypeScript 支持文档
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
演练场现在支持 TypeScript!您可以使用现代 TypeScript 语法编写解析器代码,系统会自动将其编译为 ES5 并在后端执行。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
### 🎯 核心功能
|
|
||||||
|
|
||||||
- ✅ **TypeScript 编译器集成**:内置 TypeScript 编译器,实时将 TS 代码编译为 ES5
|
|
||||||
- ✅ **语言选择器**:在演练场工具栏轻松切换 JavaScript 和 TypeScript
|
|
||||||
- ✅ **编译错误提示**:友好的编译错误提示和建议
|
|
||||||
- ✅ **双代码存储**:同时保存原始 TypeScript 代码和编译后的 ES5 代码
|
|
||||||
- ✅ **无缝集成**:与现有演练场功能完全兼容
|
|
||||||
|
|
||||||
### 📝 TypeScript 特性支持
|
|
||||||
|
|
||||||
支持所有标准 TypeScript 特性,包括但不限于:
|
|
||||||
|
|
||||||
- 类型注解(Type Annotations)
|
|
||||||
- 接口(Interfaces)
|
|
||||||
- 类型别名(Type Aliases)
|
|
||||||
- 枚举(Enums)
|
|
||||||
- 泛型(Generics)
|
|
||||||
- async/await(编译为 Promise)
|
|
||||||
- 箭头函数
|
|
||||||
- 模板字符串
|
|
||||||
- 解构赋值
|
|
||||||
- 可选链(Optional Chaining)
|
|
||||||
- 空值合并(Nullish Coalescing)
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
### 1. 选择语言
|
|
||||||
|
|
||||||
在演练场工具栏中,点击 **JavaScript** 或 **TypeScript** 按钮选择您要使用的语言。
|
|
||||||
|
|
||||||
### 2. 编写代码
|
|
||||||
|
|
||||||
选择 TypeScript 后,点击"加载示例"按钮可以加载 TypeScript 示例代码。
|
|
||||||
|
|
||||||
#### TypeScript 示例
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ==UserScript==
|
|
||||||
// @name TypeScript示例解析器
|
|
||||||
// @type ts_example_parser
|
|
||||||
// @displayName TypeScript示例网盘
|
|
||||||
// @description 使用TypeScript实现的示例解析器
|
|
||||||
// @match https?://example\.com/s/(?<KEY>\w+)
|
|
||||||
// @author yourname
|
|
||||||
// @version 1.0.0
|
|
||||||
// ==/UserScript==
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析单个文件下载链接
|
|
||||||
*/
|
|
||||||
async function parse(
|
|
||||||
shareLinkInfo: any,
|
|
||||||
http: any,
|
|
||||||
logger: any
|
|
||||||
): Promise<string> {
|
|
||||||
const url: string = shareLinkInfo.getShareUrl();
|
|
||||||
logger.info(`开始解析: ${url}`);
|
|
||||||
|
|
||||||
// 使用fetch API (已在后端实现polyfill)
|
|
||||||
try {
|
|
||||||
const response = await fetch(url);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`请求失败: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const html: string = await response.text();
|
|
||||||
|
|
||||||
// 解析逻辑
|
|
||||||
const match = html.match(/download-url="([^"]+)"/);
|
|
||||||
if (match) {
|
|
||||||
return match[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return "https://example.com/download/file.zip";
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error(`解析失败: ${error.message}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 运行测试
|
|
||||||
|
|
||||||
点击"运行"按钮(或按 Ctrl+Enter)。系统会:
|
|
||||||
|
|
||||||
1. 自动检测代码是否为 TypeScript
|
|
||||||
2. 将 TypeScript 编译为 ES5
|
|
||||||
3. 显示编译结果(成功/失败)
|
|
||||||
4. 如果编译成功,使用 ES5 代码执行测试
|
|
||||||
5. 显示测试结果
|
|
||||||
|
|
||||||
### 4. 发布解析器
|
|
||||||
|
|
||||||
编译成功后,点击"发布脚本"即可保存解析器。系统会自动:
|
|
||||||
|
|
||||||
- 保存原始 TypeScript 代码到 `playground_typescript_code` 表
|
|
||||||
- 保存编译后的 ES5 代码到 `playground_parser` 表
|
|
||||||
- 通过 `parserId` 关联两者
|
|
||||||
|
|
||||||
## 编译选项
|
|
||||||
|
|
||||||
TypeScript 编译器使用以下配置:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
target: 'ES5', // 目标版本:ES5
|
|
||||||
module: 'None', // 不使用模块系统
|
|
||||||
lib: ['es5', 'dom'], // 包含ES5和DOM类型定义
|
|
||||||
removeComments: false, // 保留注释
|
|
||||||
downlevelIteration: true, // 支持ES5迭代器降级
|
|
||||||
esModuleInterop: true // 启用ES模块互操作性
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 类型定义
|
|
||||||
|
|
||||||
### 可用的 API 对象
|
|
||||||
|
|
||||||
虽然 TypeScript 支持类型注解,但由于后端运行时环境的限制,建议使用 `any` 类型:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
function parse(
|
|
||||||
shareLinkInfo: any, // 分享链接信息
|
|
||||||
http: any, // HTTP客户端
|
|
||||||
logger: any // 日志对象
|
|
||||||
): Promise<string> {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 常用方法
|
|
||||||
|
|
||||||
#### shareLinkInfo 对象
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
shareLinkInfo.getShareUrl(): string // 获取分享URL
|
|
||||||
shareLinkInfo.getShareKey(): string // 获取分享Key
|
|
||||||
shareLinkInfo.getSharePassword(): string // 获取分享密码
|
|
||||||
shareLinkInfo.getOtherParam(key: string): any // 获取其他参数
|
|
||||||
```
|
|
||||||
|
|
||||||
#### logger 对象
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
logger.info(message: string): void // 记录信息日志
|
|
||||||
logger.debug(message: string): void // 记录调试日志
|
|
||||||
logger.error(message: string): void // 记录错误日志
|
|
||||||
logger.warn(message: string): void // 记录警告日志
|
|
||||||
```
|
|
||||||
|
|
||||||
#### fetch API(后端 Polyfill)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
async function fetchData(url: string): Promise<any> {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0...',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 使用类型注解
|
|
||||||
|
|
||||||
虽然后端不强制类型检查,但类型注解可以提高代码可读性:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
function parseFileList(
|
|
||||||
shareLinkInfo: any,
|
|
||||||
http: any,
|
|
||||||
logger: any
|
|
||||||
): Promise<Array<{
|
|
||||||
fileName: string;
|
|
||||||
fileId: string;
|
|
||||||
size: number;
|
|
||||||
}>> {
|
|
||||||
// 实现...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 利用 async/await
|
|
||||||
|
|
||||||
TypeScript 的 async/await 会编译为 Promise,后端已实现 Promise polyfill:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
async function parse(
|
|
||||||
shareLinkInfo: any,
|
|
||||||
http: any,
|
|
||||||
logger: any
|
|
||||||
): Promise<string> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(url);
|
|
||||||
const data = await response.json();
|
|
||||||
return data.downloadUrl;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`错误: ${error.message}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 使用模板字符串
|
|
||||||
|
|
||||||
模板字符串让代码更清晰:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
logger.info(`开始解析: ${url}, 密码: ${pwd}`);
|
|
||||||
const apiUrl = `https://api.example.com/file/${fileId}`;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 错误处理
|
|
||||||
|
|
||||||
使用类型化的错误处理:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
try {
|
|
||||||
const result = await parseUrl(url);
|
|
||||||
return result;
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error(`解析失败: ${error.message}`);
|
|
||||||
throw new Error(`无法解析链接: ${url}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 编译错误处理
|
|
||||||
|
|
||||||
### 常见编译错误
|
|
||||||
|
|
||||||
#### 1. 类型不匹配
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ 错误
|
|
||||||
const count: number = "123";
|
|
||||||
|
|
||||||
// ✅ 正确
|
|
||||||
const count: number = 123;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 缺少返回值
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ 错误
|
|
||||||
function parse(shareLinkInfo: any): string {
|
|
||||||
const url = shareLinkInfo.getShareUrl();
|
|
||||||
// 缺少 return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 正确
|
|
||||||
function parse(shareLinkInfo: any): string {
|
|
||||||
const url = shareLinkInfo.getShareUrl();
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 使用未声明的变量
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ 错误
|
|
||||||
function parse() {
|
|
||||||
console.log(unknownVariable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 正确
|
|
||||||
function parse() {
|
|
||||||
const knownVariable = "value";
|
|
||||||
console.log(knownVariable);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 查看编译错误
|
|
||||||
|
|
||||||
编译失败时,系统会显示详细的错误信息,包括:
|
|
||||||
|
|
||||||
- 错误类型(Error/Warning)
|
|
||||||
- 错误位置(行号、列号)
|
|
||||||
- 错误代码(TS错误代码)
|
|
||||||
- 错误描述
|
|
||||||
|
|
||||||
## 数据库结构
|
|
||||||
|
|
||||||
### playground_typescript_code 表
|
|
||||||
|
|
||||||
存储 TypeScript 源代码的表结构:
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | BIGINT | 主键,自增 |
|
|
||||||
| parser_id | BIGINT | 关联的解析器ID(外键) |
|
|
||||||
| ts_code | TEXT | TypeScript原始代码 |
|
|
||||||
| es5_code | TEXT | 编译后的ES5代码 |
|
|
||||||
| compile_errors | VARCHAR(2000) | 编译错误信息 |
|
|
||||||
| compiler_version | VARCHAR(32) | 编译器版本 |
|
|
||||||
| compile_options | VARCHAR(1000) | 编译选项(JSON格式) |
|
|
||||||
| create_time | DATETIME | 创建时间 |
|
|
||||||
| update_time | DATETIME | 更新时间 |
|
|
||||||
| is_valid | BOOLEAN | 编译是否成功 |
|
|
||||||
| ip | VARCHAR(64) | 创建者IP |
|
|
||||||
|
|
||||||
### 与 playground_parser 表的关系
|
|
||||||
|
|
||||||
- `playground_typescript_code.parser_id` 外键关联到 `playground_parser.id`
|
|
||||||
- 一个解析器(parser)可以有一个对应的 TypeScript 代码记录
|
|
||||||
- 编译后的 ES5 代码存储在 `playground_parser.js_code` 字段中
|
|
||||||
|
|
||||||
## API 端点
|
|
||||||
|
|
||||||
### 保存 TypeScript 代码
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /v2/playground/typescript
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"parserId": 1,
|
|
||||||
"tsCode": "...",
|
|
||||||
"es5Code": "...",
|
|
||||||
"compileErrors": null,
|
|
||||||
"compilerVersion": "5.x",
|
|
||||||
"compileOptions": "{}",
|
|
||||||
"isValid": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 获取 TypeScript 代码
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /v2/playground/typescript/:parserId
|
|
||||||
```
|
|
||||||
|
|
||||||
### 更新 TypeScript 代码
|
|
||||||
|
|
||||||
```http
|
|
||||||
PUT /v2/playground/typescript/:parserId
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"tsCode": "...",
|
|
||||||
"es5Code": "...",
|
|
||||||
"compileErrors": null,
|
|
||||||
"compilerVersion": "5.x",
|
|
||||||
"compileOptions": "{}",
|
|
||||||
"isValid": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 迁移指南
|
|
||||||
|
|
||||||
### 从 JavaScript 迁移到 TypeScript
|
|
||||||
|
|
||||||
1. **添加类型注解**:
|
|
||||||
```typescript
|
|
||||||
// JavaScript
|
|
||||||
function parse(shareLinkInfo, http, logger) {
|
|
||||||
var url = shareLinkInfo.getShareUrl();
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeScript
|
|
||||||
function parse(
|
|
||||||
shareLinkInfo: any,
|
|
||||||
http: any,
|
|
||||||
logger: any
|
|
||||||
): string {
|
|
||||||
const url: string = shareLinkInfo.getShareUrl();
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **使用 const/let 替代 var**:
|
|
||||||
```typescript
|
|
||||||
// JavaScript
|
|
||||||
var url = "https://example.com";
|
|
||||||
var count = 0;
|
|
||||||
|
|
||||||
// TypeScript
|
|
||||||
const url: string = "https://example.com";
|
|
||||||
let count: number = 0;
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **使用模板字符串**:
|
|
||||||
```typescript
|
|
||||||
// JavaScript
|
|
||||||
var message = "URL: " + url + ", Count: " + count;
|
|
||||||
|
|
||||||
// TypeScript
|
|
||||||
const message: string = `URL: ${url}, Count: ${count}`;
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **使用 async/await**:
|
|
||||||
```typescript
|
|
||||||
// JavaScript
|
|
||||||
function parse(shareLinkInfo, http, logger) {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
fetch(url).then(function(response) {
|
|
||||||
resolve(response.text());
|
|
||||||
}).catch(reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeScript
|
|
||||||
async function parse(
|
|
||||||
shareLinkInfo: any,
|
|
||||||
http: any,
|
|
||||||
logger: any
|
|
||||||
): Promise<string> {
|
|
||||||
const response = await fetch(url);
|
|
||||||
return await response.text();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### Q: TypeScript 代码会在哪里编译?
|
|
||||||
|
|
||||||
A: TypeScript 代码在浏览器前端编译为 ES5,然后发送到后端执行。这确保了后端始终执行标准的 ES5 代码。
|
|
||||||
|
|
||||||
### Q: 编译需要多长时间?
|
|
||||||
|
|
||||||
A: 通常在几毫秒到几百毫秒之间,取决于代码大小和复杂度。
|
|
||||||
|
|
||||||
### Q: 可以使用 npm 包吗?
|
|
||||||
|
|
||||||
A: 不可以。目前不支持 import/require 外部模块。所有代码必须自包含。
|
|
||||||
|
|
||||||
### Q: 类型检查严格吗?
|
|
||||||
|
|
||||||
A: 不严格。编译器配置为允许隐式 any 类型,不进行严格的 null 检查。主要目的是支持现代语法,而非严格的类型安全。
|
|
||||||
|
|
||||||
### Q: 编译后的代码可以查看吗?
|
|
||||||
|
|
||||||
A: 目前编译后的 ES5 代码存储在数据库中,但 UI 中暂未提供预览功能。这是未来的增强计划。
|
|
||||||
|
|
||||||
### Q: 原有的 JavaScript 代码会受影响吗?
|
|
||||||
|
|
||||||
A: 不会。JavaScript 和 TypeScript 模式完全独立,互不影响。
|
|
||||||
|
|
||||||
## 故障排除
|
|
||||||
|
|
||||||
### 编译失败
|
|
||||||
|
|
||||||
1. **检查语法**:确保 TypeScript 语法正确
|
|
||||||
2. **查看错误信息**:仔细阅读编译错误提示
|
|
||||||
3. **简化代码**:从简单的示例开始,逐步添加功能
|
|
||||||
4. **使用示例**:点击"加载示例"查看正确的代码结构
|
|
||||||
|
|
||||||
### 运行时错误
|
|
||||||
|
|
||||||
1. **检查 ES5 兼容性**:某些高级特性可能无法完全转换
|
|
||||||
2. **验证 API 使用**:确保正确使用 shareLinkInfo、http、logger 等对象
|
|
||||||
3. **查看日志**:使用 logger 对象输出调试信息
|
|
||||||
|
|
||||||
## 未来计划
|
|
||||||
|
|
||||||
- [ ] 显示编译后的 ES5 代码预览
|
|
||||||
- [ ] 添加专用的编译错误面板
|
|
||||||
- [ ] 支持更多 TypeScript 配置选项
|
|
||||||
- [ ] 提供完整的类型定义文件(.d.ts)
|
|
||||||
- [ ] 支持代码自动补全和智能提示
|
|
||||||
- [ ] 添加 TypeScript 代码片段库
|
|
||||||
|
|
||||||
## 反馈与支持
|
|
||||||
|
|
||||||
如遇到问题或有建议,请在 GitHub Issues 中提出:
|
|
||||||
https://github.com/qaiu/netdisk-fast-download/issues
|
|
||||||
@@ -5,7 +5,6 @@ import cn.qaiu.lz.web.config.PlaygroundConfig;
|
|||||||
import cn.qaiu.lz.web.model.PlaygroundTestResp;
|
import cn.qaiu.lz.web.model.PlaygroundTestResp;
|
||||||
import cn.qaiu.lz.web.service.DbService;
|
import cn.qaiu.lz.web.service.DbService;
|
||||||
import cn.qaiu.parser.ParserCreate;
|
import cn.qaiu.parser.ParserCreate;
|
||||||
import cn.qaiu.parser.custom.CustomParserConfig;
|
|
||||||
import cn.qaiu.parser.custom.CustomParserRegistry;
|
import cn.qaiu.parser.custom.CustomParserRegistry;
|
||||||
import cn.qaiu.parser.customjs.JsPlaygroundExecutor;
|
import cn.qaiu.parser.customjs.JsPlaygroundExecutor;
|
||||||
import cn.qaiu.parser.customjs.JsPlaygroundLogger;
|
import cn.qaiu.parser.customjs.JsPlaygroundLogger;
|
||||||
@@ -636,146 +635,6 @@ public class PlaygroundApi {
|
|||||||
return dbService.getPlaygroundParserById(id);
|
return dbService.getPlaygroundParserById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存TypeScript代码及其编译结果
|
|
||||||
*/
|
|
||||||
@RouteMapping(value = "/typescript", method = RouteMethod.POST)
|
|
||||||
public Future<JsonObject> saveTypeScriptCode(RoutingContext ctx) {
|
|
||||||
// 权限检查
|
|
||||||
if (!checkAuth(ctx)) {
|
|
||||||
return Future.succeededFuture(JsonResult.error("未授权访问").toJsonObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise<JsonObject> promise = Promise.promise();
|
|
||||||
|
|
||||||
try {
|
|
||||||
JsonObject body = ctx.body().asJsonObject();
|
|
||||||
Long parserId = body.getLong("parserId");
|
|
||||||
String tsCode = body.getString("tsCode");
|
|
||||||
String es5Code = body.getString("es5Code");
|
|
||||||
String compileErrors = body.getString("compileErrors");
|
|
||||||
String compilerVersion = body.getString("compilerVersion");
|
|
||||||
String compileOptions = body.getString("compileOptions");
|
|
||||||
Boolean isValid = body.getBoolean("isValid", true);
|
|
||||||
|
|
||||||
if (parserId == null) {
|
|
||||||
promise.complete(JsonResult.error("解析器ID不能为空").toJsonObject());
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(tsCode)) {
|
|
||||||
promise.complete(JsonResult.error("TypeScript代码不能为空").toJsonObject());
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(es5Code)) {
|
|
||||||
promise.complete(JsonResult.error("编译后的ES5代码不能为空").toJsonObject());
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 代码长度验证
|
|
||||||
if (tsCode.length() > MAX_CODE_LENGTH || es5Code.length() > MAX_CODE_LENGTH) {
|
|
||||||
promise.complete(JsonResult.error("代码长度超过限制(最大128KB)").toJsonObject());
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject tsCodeInfo = new JsonObject();
|
|
||||||
tsCodeInfo.put("parserId", parserId);
|
|
||||||
tsCodeInfo.put("tsCode", tsCode);
|
|
||||||
tsCodeInfo.put("es5Code", es5Code);
|
|
||||||
tsCodeInfo.put("compileErrors", compileErrors);
|
|
||||||
tsCodeInfo.put("compilerVersion", compilerVersion);
|
|
||||||
tsCodeInfo.put("compileOptions", compileOptions);
|
|
||||||
tsCodeInfo.put("isValid", isValid);
|
|
||||||
tsCodeInfo.put("ip", getClientIp(ctx.request()));
|
|
||||||
|
|
||||||
dbService.saveTypeScriptCode(tsCodeInfo).onSuccess(result -> {
|
|
||||||
promise.complete(result);
|
|
||||||
}).onFailure(e -> {
|
|
||||||
log.error("保存TypeScript代码失败", e);
|
|
||||||
promise.complete(JsonResult.error("保存失败: " + e.getMessage()).toJsonObject());
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("解析请求参数失败", e);
|
|
||||||
promise.complete(JsonResult.error("解析请求参数失败: " + e.getMessage()).toJsonObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据parserId获取TypeScript代码
|
|
||||||
*/
|
|
||||||
@RouteMapping(value = "/typescript/:parserId", method = RouteMethod.GET)
|
|
||||||
public Future<JsonObject> getTypeScriptCode(RoutingContext ctx, Long parserId) {
|
|
||||||
// 权限检查
|
|
||||||
if (!checkAuth(ctx)) {
|
|
||||||
return Future.succeededFuture(JsonResult.error("未授权访问").toJsonObject());
|
|
||||||
}
|
|
||||||
return dbService.getTypeScriptCodeByParserId(parserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新TypeScript代码
|
|
||||||
*/
|
|
||||||
@RouteMapping(value = "/typescript/:parserId", method = RouteMethod.PUT)
|
|
||||||
public Future<JsonObject> updateTypeScriptCode(RoutingContext ctx, Long parserId) {
|
|
||||||
// 权限检查
|
|
||||||
if (!checkAuth(ctx)) {
|
|
||||||
return Future.succeededFuture(JsonResult.error("未授权访问").toJsonObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise<JsonObject> promise = Promise.promise();
|
|
||||||
|
|
||||||
try {
|
|
||||||
JsonObject body = ctx.body().asJsonObject();
|
|
||||||
String tsCode = body.getString("tsCode");
|
|
||||||
String es5Code = body.getString("es5Code");
|
|
||||||
String compileErrors = body.getString("compileErrors");
|
|
||||||
String compilerVersion = body.getString("compilerVersion");
|
|
||||||
String compileOptions = body.getString("compileOptions");
|
|
||||||
Boolean isValid = body.getBoolean("isValid", true);
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(tsCode)) {
|
|
||||||
promise.complete(JsonResult.error("TypeScript代码不能为空").toJsonObject());
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(es5Code)) {
|
|
||||||
promise.complete(JsonResult.error("编译后的ES5代码不能为空").toJsonObject());
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 代码长度验证
|
|
||||||
if (tsCode.length() > MAX_CODE_LENGTH || es5Code.length() > MAX_CODE_LENGTH) {
|
|
||||||
promise.complete(JsonResult.error("代码长度超过限制(最大128KB)").toJsonObject());
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject tsCodeInfo = new JsonObject();
|
|
||||||
tsCodeInfo.put("tsCode", tsCode);
|
|
||||||
tsCodeInfo.put("es5Code", es5Code);
|
|
||||||
tsCodeInfo.put("compileErrors", compileErrors);
|
|
||||||
tsCodeInfo.put("compilerVersion", compilerVersion);
|
|
||||||
tsCodeInfo.put("compileOptions", compileOptions);
|
|
||||||
tsCodeInfo.put("isValid", isValid);
|
|
||||||
|
|
||||||
dbService.updateTypeScriptCode(parserId, tsCodeInfo).onSuccess(result -> {
|
|
||||||
promise.complete(result);
|
|
||||||
}).onFailure(e -> {
|
|
||||||
log.error("更新TypeScript代码失败", e);
|
|
||||||
promise.complete(JsonResult.error("更新失败: " + e.getMessage()).toJsonObject());
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("解析请求参数失败", e);
|
|
||||||
promise.complete(JsonResult.error("解析请求参数失败: " + e.getMessage()).toJsonObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取客户端IP
|
* 获取客户端IP
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
package cn.qaiu.lz.web.model;
|
|
||||||
|
|
||||||
import cn.qaiu.db.ddl.Constraint;
|
|
||||||
import cn.qaiu.db.ddl.Length;
|
|
||||||
import cn.qaiu.db.ddl.Table;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 演练场TypeScript代码实体
|
|
||||||
* 用于保存用户编写的TypeScript源代码
|
|
||||||
* 与PlaygroundParser关联,存储原始TS代码和编译后的ES5代码
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Table("playground_typescript_code")
|
|
||||||
public class PlaygroundTypeScriptCode {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@Constraint(autoIncrement = true, notNull = true)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Constraint(notNull = true)
|
|
||||||
private Long parserId; // 关联的解析器ID(外键)
|
|
||||||
|
|
||||||
@Length(varcharSize = 65535)
|
|
||||||
@Constraint(notNull = true)
|
|
||||||
private String tsCode; // TypeScript原始代码
|
|
||||||
|
|
||||||
@Length(varcharSize = 65535)
|
|
||||||
@Constraint(notNull = true)
|
|
||||||
private String es5Code; // 编译后的ES5代码
|
|
||||||
|
|
||||||
@Length(varcharSize = 2000)
|
|
||||||
private String compileErrors; // 编译错误信息(如果有)
|
|
||||||
|
|
||||||
@Length(varcharSize = 32)
|
|
||||||
private String compilerVersion; // 编译器版本
|
|
||||||
|
|
||||||
@Length(varcharSize = 1000)
|
|
||||||
private String compileOptions; // 编译选项(JSON格式)
|
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
|
||||||
private Date createTime = new Date(); // 创建时间
|
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
|
||||||
private Date updateTime; // 更新时间
|
|
||||||
|
|
||||||
private Boolean isValid = true; // 编译是否成功
|
|
||||||
|
|
||||||
@Length(varcharSize = 64)
|
|
||||||
private String ip; // 创建者IP
|
|
||||||
}
|
|
||||||
@@ -50,19 +50,4 @@ public interface DbService extends BaseAsyncService {
|
|||||||
*/
|
*/
|
||||||
Future<JsonObject> getPlaygroundParserById(Long id);
|
Future<JsonObject> getPlaygroundParserById(Long id);
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存TypeScript代码及其编译结果
|
|
||||||
*/
|
|
||||||
Future<JsonObject> saveTypeScriptCode(JsonObject tsCodeInfo);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据parserId获取TypeScript代码
|
|
||||||
*/
|
|
||||||
Future<JsonObject> getTypeScriptCodeByParserId(Long parserId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新TypeScript代码
|
|
||||||
*/
|
|
||||||
Future<JsonObject> updateTypeScriptCode(Long parserId, JsonObject tsCodeInfo);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,114 +265,4 @@ public class DbServiceImpl implements DbService {
|
|||||||
|
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<JsonObject> saveTypeScriptCode(JsonObject tsCodeInfo) {
|
|
||||||
JDBCPool client = JDBCPoolInit.instance().getPool();
|
|
||||||
Promise<JsonObject> promise = Promise.promise();
|
|
||||||
|
|
||||||
String sql = """
|
|
||||||
INSERT INTO playground_typescript_code
|
|
||||||
(parser_id, ts_code, es5_code, compile_errors, compiler_version,
|
|
||||||
compile_options, create_time, is_valid, ip)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, NOW(), ?, ?)
|
|
||||||
""";
|
|
||||||
|
|
||||||
client.preparedQuery(sql)
|
|
||||||
.execute(Tuple.of(
|
|
||||||
tsCodeInfo.getLong("parserId"),
|
|
||||||
tsCodeInfo.getString("tsCode"),
|
|
||||||
tsCodeInfo.getString("es5Code"),
|
|
||||||
tsCodeInfo.getString("compileErrors"),
|
|
||||||
tsCodeInfo.getString("compilerVersion"),
|
|
||||||
tsCodeInfo.getString("compileOptions"),
|
|
||||||
tsCodeInfo.getBoolean("isValid", true),
|
|
||||||
tsCodeInfo.getString("ip")
|
|
||||||
))
|
|
||||||
.onSuccess(res -> {
|
|
||||||
promise.complete(JsonResult.success("保存TypeScript代码成功").toJsonObject());
|
|
||||||
})
|
|
||||||
.onFailure(e -> {
|
|
||||||
log.error("saveTypeScriptCode failed", e);
|
|
||||||
promise.fail(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<JsonObject> getTypeScriptCodeByParserId(Long parserId) {
|
|
||||||
JDBCPool client = JDBCPoolInit.instance().getPool();
|
|
||||||
Promise<JsonObject> promise = Promise.promise();
|
|
||||||
|
|
||||||
String sql = "SELECT * FROM playground_typescript_code WHERE parser_id = ? ORDER BY create_time DESC LIMIT 1";
|
|
||||||
|
|
||||||
client.preparedQuery(sql)
|
|
||||||
.execute(Tuple.of(parserId))
|
|
||||||
.onSuccess(rows -> {
|
|
||||||
if (rows.size() > 0) {
|
|
||||||
Row row = rows.iterator().next();
|
|
||||||
JsonObject tsCode = new JsonObject();
|
|
||||||
tsCode.put("id", row.getLong("id"));
|
|
||||||
tsCode.put("parserId", row.getLong("parser_id"));
|
|
||||||
tsCode.put("tsCode", row.getString("ts_code"));
|
|
||||||
tsCode.put("es5Code", row.getString("es5_code"));
|
|
||||||
tsCode.put("compileErrors", row.getString("compile_errors"));
|
|
||||||
tsCode.put("compilerVersion", row.getString("compiler_version"));
|
|
||||||
tsCode.put("compileOptions", row.getString("compile_options"));
|
|
||||||
var createTime = row.getLocalDateTime("create_time");
|
|
||||||
if (createTime != null) {
|
|
||||||
tsCode.put("createTime", createTime.toString().replace("T", " "));
|
|
||||||
}
|
|
||||||
var updateTime = row.getLocalDateTime("update_time");
|
|
||||||
if (updateTime != null) {
|
|
||||||
tsCode.put("updateTime", updateTime.toString().replace("T", " "));
|
|
||||||
}
|
|
||||||
tsCode.put("isValid", row.getBoolean("is_valid"));
|
|
||||||
tsCode.put("ip", row.getString("ip"));
|
|
||||||
promise.complete(JsonResult.data(tsCode).toJsonObject());
|
|
||||||
} else {
|
|
||||||
promise.complete(JsonResult.data(null).toJsonObject());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onFailure(e -> {
|
|
||||||
log.error("getTypeScriptCodeByParserId failed", e);
|
|
||||||
promise.fail(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<JsonObject> updateTypeScriptCode(Long parserId, JsonObject tsCodeInfo) {
|
|
||||||
JDBCPool client = JDBCPoolInit.instance().getPool();
|
|
||||||
Promise<JsonObject> promise = Promise.promise();
|
|
||||||
|
|
||||||
String sql = """
|
|
||||||
UPDATE playground_typescript_code
|
|
||||||
SET ts_code = ?, es5_code = ?, compile_errors = ?, compiler_version = ?,
|
|
||||||
compile_options = ?, update_time = NOW(), is_valid = ?
|
|
||||||
WHERE parser_id = ?
|
|
||||||
""";
|
|
||||||
|
|
||||||
client.preparedQuery(sql)
|
|
||||||
.execute(Tuple.of(
|
|
||||||
tsCodeInfo.getString("tsCode"),
|
|
||||||
tsCodeInfo.getString("es5Code"),
|
|
||||||
tsCodeInfo.getString("compileErrors"),
|
|
||||||
tsCodeInfo.getString("compilerVersion"),
|
|
||||||
tsCodeInfo.getString("compileOptions"),
|
|
||||||
tsCodeInfo.getBoolean("isValid", true),
|
|
||||||
parserId
|
|
||||||
))
|
|
||||||
.onSuccess(res -> {
|
|
||||||
promise.complete(JsonResult.success("更新TypeScript代码成功").toJsonObject());
|
|
||||||
})
|
|
||||||
.onFailure(e -> {
|
|
||||||
log.error("updateTypeScriptCode failed", e);
|
|
||||||
promise.fail(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user