mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 04:13:03 +00:00
Add TypeScript compiler integration - core implementation
Co-authored-by: qaiu <29825328+qaiu@users.noreply.github.com>
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
"monaco-editor": "^0.45.0",
|
"monaco-editor": "^0.45.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"splitpanes": "^4.0.4",
|
"splitpanes": "^4.0.4",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
"vue": "^3.5.12",
|
"vue": "^3.5.12",
|
||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1",
|
||||||
|
|||||||
@@ -141,6 +141,56 @@ export const playgroundApi = {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '获取解析器失败');
|
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '获取解析器失败');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存TypeScript代码及其编译结果
|
||||||
|
*/
|
||||||
|
async saveTypeScriptCode(parserId, tsCode, es5Code, compileErrors, compilerVersion, compileOptions, isValid) {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/v2/playground/typescript', {
|
||||||
|
parserId,
|
||||||
|
tsCode,
|
||||||
|
es5Code,
|
||||||
|
compileErrors,
|
||||||
|
compilerVersion,
|
||||||
|
compileOptions,
|
||||||
|
isValid
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '保存TypeScript代码失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据parserId获取TypeScript代码
|
||||||
|
*/
|
||||||
|
async getTypeScriptCode(parserId) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/v2/playground/typescript/${parserId}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '获取TypeScript代码失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新TypeScript代码
|
||||||
|
*/
|
||||||
|
async updateTypeScriptCode(parserId, tsCode, es5Code, compileErrors, compilerVersion, compileOptions, isValid) {
|
||||||
|
try {
|
||||||
|
const response = await axios.put(`/v2/playground/typescript/${parserId}`, {
|
||||||
|
tsCode,
|
||||||
|
es5Code,
|
||||||
|
compileErrors,
|
||||||
|
compilerVersion,
|
||||||
|
compileOptions,
|
||||||
|
isValid
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '更新TypeScript代码失败');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
167
web-front/src/utils/tsCompiler.js
Normal file
167
web-front/src/utils/tsCompiler.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TypeScript编译器工具类
|
||||||
|
* 用于在浏览器中将TypeScript代码编译为ES5 JavaScript
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编译TypeScript代码为ES5 JavaScript
|
||||||
|
* @param {string} sourceCode - TypeScript源代码
|
||||||
|
* @param {string} fileName - 文件名(默认为script.ts)
|
||||||
|
* @returns {Object} 编译结果 { success: boolean, code: string, errors: Array }
|
||||||
|
*/
|
||||||
|
export function compileToES5(sourceCode, fileName = 'script.ts') {
|
||||||
|
try {
|
||||||
|
// 编译选项
|
||||||
|
const compilerOptions = {
|
||||||
|
target: ts.ScriptTarget.ES5, // 目标版本:ES5
|
||||||
|
module: ts.ModuleKind.None, // 不使用模块系统
|
||||||
|
lib: ['lib.es5.d.ts', 'lib.dom.d.ts'], // 包含ES5和DOM类型定义
|
||||||
|
removeComments: false, // 保留注释
|
||||||
|
noEmitOnError: false, // 即使有错误也生成代码
|
||||||
|
noImplicitAny: false, // 允许隐式any类型
|
||||||
|
strictNullChecks: false, // 不进行严格的null检查
|
||||||
|
suppressImplicitAnyIndexErrors: true, // 抑制隐式any索引错误
|
||||||
|
downlevelIteration: true, // 支持ES5迭代器降级
|
||||||
|
esModuleInterop: true, // 启用ES模块互操作性
|
||||||
|
allowJs: true, // 允许编译JavaScript文件
|
||||||
|
checkJs: false // 不检查JavaScript文件
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行编译
|
||||||
|
const result = ts.transpileModule(sourceCode, {
|
||||||
|
compilerOptions,
|
||||||
|
fileName,
|
||||||
|
reportDiagnostics: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否有诊断信息(错误/警告)
|
||||||
|
const diagnostics = result.diagnostics || [];
|
||||||
|
const errors = diagnostics.map(diagnostic => {
|
||||||
|
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
||||||
|
let location = '';
|
||||||
|
if (diagnostic.file && diagnostic.start !== undefined) {
|
||||||
|
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
||||||
|
location = `(${line + 1},${character + 1})`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
location,
|
||||||
|
category: ts.DiagnosticCategory[diagnostic.category],
|
||||||
|
code: diagnostic.code
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 过滤出真正的错误(不包括警告)
|
||||||
|
const realErrors = errors.filter(e => e.category === 'Error');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: realErrors.length === 0,
|
||||||
|
code: result.outputText || '',
|
||||||
|
errors: errors,
|
||||||
|
hasWarnings: errors.some(e => e.category === 'Warning'),
|
||||||
|
sourceMap: result.sourceMapText
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
code: '',
|
||||||
|
errors: [{
|
||||||
|
message: error.message || '编译失败',
|
||||||
|
location: '',
|
||||||
|
category: 'Error',
|
||||||
|
code: 0
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查代码是否为TypeScript代码
|
||||||
|
* 简单的启发式检查,看是否包含TypeScript特有的语法
|
||||||
|
* @param {string} code - 代码字符串
|
||||||
|
* @returns {boolean} 是否为TypeScript代码
|
||||||
|
*/
|
||||||
|
export function isTypeScriptCode(code) {
|
||||||
|
if (!code || typeof code !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeScript特有的语法模式
|
||||||
|
const tsPatterns = [
|
||||||
|
/:\s*(string|number|boolean|any|void|never|unknown|object)\b/, // 类型注解
|
||||||
|
/interface\s+\w+/, // interface声明
|
||||||
|
/type\s+\w+\s*=/, // type别名
|
||||||
|
/enum\s+\w+/, // enum声明
|
||||||
|
/<\w+>/, // 泛型
|
||||||
|
/implements\s+\w+/, // implements关键字
|
||||||
|
/as\s+(string|number|boolean|any|const)/, // as类型断言
|
||||||
|
/public|private|protected|readonly/, // 访问修饰符
|
||||||
|
/:\s*\w+\[\]/, // 数组类型注解
|
||||||
|
/\?\s*:/ // 可选属性
|
||||||
|
];
|
||||||
|
|
||||||
|
// 如果匹配任何TypeScript特有模式,则认为是TypeScript代码
|
||||||
|
return tsPatterns.some(pattern => pattern.test(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化编译错误信息
|
||||||
|
* @param {Array} errors - 错误数组
|
||||||
|
* @returns {string} 格式化后的错误信息
|
||||||
|
*/
|
||||||
|
export function formatCompileErrors(errors) {
|
||||||
|
if (!errors || errors.length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.map((error, index) => {
|
||||||
|
const prefix = `[${error.category}]`;
|
||||||
|
const location = error.location ? ` ${error.location}` : '';
|
||||||
|
const code = error.code ? ` (TS${error.code})` : '';
|
||||||
|
return `${index + 1}. ${prefix}${location}${code}: ${error.message}`;
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证编译后的代码是否为有效的ES5
|
||||||
|
* @param {string} code - 编译后的代码
|
||||||
|
* @returns {Object} { valid: boolean, error: string }
|
||||||
|
*/
|
||||||
|
export function validateES5Code(code) {
|
||||||
|
try {
|
||||||
|
// 尝试使用Function构造函数验证语法
|
||||||
|
// eslint-disable-next-line no-new-func
|
||||||
|
new Function(code);
|
||||||
|
return { valid: true, error: null };
|
||||||
|
} catch (error) {
|
||||||
|
return { valid: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取代码中的元数据注释
|
||||||
|
* @param {string} code - 代码字符串
|
||||||
|
* @returns {Object} 元数据对象
|
||||||
|
*/
|
||||||
|
export function extractMetadata(code) {
|
||||||
|
const metadata = {};
|
||||||
|
const metaRegex = /\/\/\s*@(\w+)\s+(.+)/g;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = metaRegex.exec(code)) !== null) {
|
||||||
|
const [, key, value] = match;
|
||||||
|
metadata[key] = value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
compileToES5,
|
||||||
|
isTypeScriptCode,
|
||||||
|
formatCompileErrors,
|
||||||
|
validateES5Code,
|
||||||
|
extractMetadata
|
||||||
|
};
|
||||||
@@ -5,6 +5,11 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<span class="title">JS解析器演练场</span>
|
<span class="title">JS解析器演练场</span>
|
||||||
|
<!-- 语言选择器 -->
|
||||||
|
<el-radio-group v-model="codeLanguage" size="small" style="margin-left: 15px;" @change="onLanguageChange">
|
||||||
|
<el-radio-button label="JavaScript">JavaScript</el-radio-button>
|
||||||
|
<el-radio-button label="TypeScript">TypeScript</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<!-- 主要操作 -->
|
<!-- 主要操作 -->
|
||||||
@@ -479,6 +484,7 @@ import 'splitpanes/dist/splitpanes.css';
|
|||||||
import MonacoEditor from '@/components/MonacoEditor.vue';
|
import MonacoEditor from '@/components/MonacoEditor.vue';
|
||||||
import { playgroundApi } from '@/utils/playgroundApi';
|
import { playgroundApi } from '@/utils/playgroundApi';
|
||||||
import { configureMonacoTypes, loadTypesFromApi } from '@/utils/monacoTypes';
|
import { configureMonacoTypes, loadTypesFromApi } from '@/utils/monacoTypes';
|
||||||
|
import { compileToES5, isTypeScriptCode, formatCompileErrors } from '@/utils/tsCompiler';
|
||||||
import JsonViewer from 'vue3-json-viewer';
|
import JsonViewer from 'vue3-json-viewer';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -492,6 +498,9 @@ export default {
|
|||||||
setup() {
|
setup() {
|
||||||
const editorRef = ref(null);
|
const editorRef = ref(null);
|
||||||
const jsCode = ref('');
|
const jsCode = ref('');
|
||||||
|
const codeLanguage = ref('JavaScript'); // 新增:代码语言选择
|
||||||
|
const compiledES5Code = ref(''); // 新增:编译后的ES5代码
|
||||||
|
const compileStatus = ref({ success: true, errors: [] }); // 新增:编译状态
|
||||||
const testParams = ref({
|
const testParams = ref({
|
||||||
shareUrl: 'https://lanzoui.com/i7Aq12ab3cd',
|
shareUrl: 'https://lanzoui.com/i7Aq12ab3cd',
|
||||||
pwd: '',
|
pwd: '',
|
||||||
@@ -699,6 +708,62 @@ function parseById(shareLinkInfo, http, logger) {
|
|||||||
const clearCode = () => {
|
const clearCode = () => {
|
||||||
jsCode.value = '';
|
jsCode.value = '';
|
||||||
testResult.value = null;
|
testResult.value = null;
|
||||||
|
compiledES5Code.value = '';
|
||||||
|
compileStatus.value = { success: true, errors: [] };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 语言切换处理
|
||||||
|
const onLanguageChange = (newLanguage) => {
|
||||||
|
console.log('切换语言:', newLanguage);
|
||||||
|
// 保存当前语言选择
|
||||||
|
localStorage.setItem('playground_language', newLanguage);
|
||||||
|
|
||||||
|
// 如果切换到TypeScript,尝试编译当前代码
|
||||||
|
if (newLanguage === 'TypeScript' && jsCode.value.trim()) {
|
||||||
|
compileTypeScriptCode();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编译TypeScript代码
|
||||||
|
const compileTypeScriptCode = () => {
|
||||||
|
if (!jsCode.value.trim()) {
|
||||||
|
compiledES5Code.value = '';
|
||||||
|
compileStatus.value = { success: true, errors: [] };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = compileToES5(jsCode.value);
|
||||||
|
compiledES5Code.value = result.code;
|
||||||
|
compileStatus.value = {
|
||||||
|
success: result.success,
|
||||||
|
errors: result.errors || [],
|
||||||
|
hasWarnings: result.hasWarnings
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
const errorMsg = formatCompileErrors(result.errors);
|
||||||
|
ElMessage.error({
|
||||||
|
message: '编译失败,请检查代码语法\n' + errorMsg,
|
||||||
|
duration: 5000,
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
} else if (result.hasWarnings) {
|
||||||
|
ElMessage.warning({
|
||||||
|
message: '编译成功,但存在警告',
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ElMessage.success('编译成功');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('编译错误:', error);
|
||||||
|
compileStatus.value = {
|
||||||
|
success: false,
|
||||||
|
errors: [{ message: error.message || '编译失败' }]
|
||||||
|
};
|
||||||
|
ElMessage.error('编译失败: ' + error.message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 执行测试
|
// 执行测试
|
||||||
@@ -744,9 +809,60 @@ function parseById(shareLinkInfo, http, logger) {
|
|||||||
testResult.value = null;
|
testResult.value = null;
|
||||||
consoleLogs.value = []; // 清空控制台
|
consoleLogs.value = []; // 清空控制台
|
||||||
|
|
||||||
|
// 确定要执行的代码(TypeScript需要先编译)
|
||||||
|
let codeToExecute = jsCode.value;
|
||||||
|
|
||||||
|
// 如果是TypeScript模式或代码看起来像TypeScript,先编译
|
||||||
|
if (codeLanguage.value === 'TypeScript' || isTypeScriptCode(jsCode.value)) {
|
||||||
|
try {
|
||||||
|
const compileResult = compileToES5(jsCode.value);
|
||||||
|
|
||||||
|
if (!compileResult.success) {
|
||||||
|
testing.value = false;
|
||||||
|
const errorMsg = formatCompileErrors(compileResult.errors);
|
||||||
|
ElMessage.error({
|
||||||
|
message: 'TypeScript编译失败,请修复错误后再试\n' + errorMsg,
|
||||||
|
duration: 5000,
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
testResult.value = {
|
||||||
|
success: false,
|
||||||
|
error: 'TypeScript编译失败',
|
||||||
|
stackTrace: errorMsg,
|
||||||
|
logs: [],
|
||||||
|
executionTime: 0
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用编译后的ES5代码
|
||||||
|
codeToExecute = compileResult.code;
|
||||||
|
compiledES5Code.value = compileResult.code;
|
||||||
|
compileStatus.value = {
|
||||||
|
success: true,
|
||||||
|
errors: compileResult.errors || [],
|
||||||
|
hasWarnings: compileResult.hasWarnings
|
||||||
|
};
|
||||||
|
|
||||||
|
if (compileResult.hasWarnings) {
|
||||||
|
ElMessage.warning('编译成功,但存在警告');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
testing.value = false;
|
||||||
|
ElMessage.error('TypeScript编译失败: ' + error.message);
|
||||||
|
testResult.value = {
|
||||||
|
success: false,
|
||||||
|
error: 'TypeScript编译失败: ' + error.message,
|
||||||
|
logs: [],
|
||||||
|
executionTime: 0
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await playgroundApi.testScript(
|
const result = await playgroundApi.testScript(
|
||||||
jsCode.value,
|
codeToExecute, // 使用编译后的代码或原始JS代码
|
||||||
testParams.value.shareUrl,
|
testParams.value.shareUrl,
|
||||||
testParams.value.pwd,
|
testParams.value.pwd,
|
||||||
testParams.value.method
|
testParams.value.method
|
||||||
@@ -1196,6 +1312,12 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}"</pre>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载保存的语言选择
|
||||||
|
const savedLanguage = localStorage.getItem('playground_language');
|
||||||
|
if (savedLanguage) {
|
||||||
|
codeLanguage.value = savedLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
// 监听主题变化
|
// 监听主题变化
|
||||||
if (document.documentElement) {
|
if (document.documentElement) {
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
@@ -1214,6 +1336,9 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}"</pre>
|
|||||||
return {
|
return {
|
||||||
editorRef,
|
editorRef,
|
||||||
jsCode,
|
jsCode,
|
||||||
|
codeLanguage,
|
||||||
|
compiledES5Code,
|
||||||
|
compileStatus,
|
||||||
testParams,
|
testParams,
|
||||||
testResult,
|
testResult,
|
||||||
testing,
|
testing,
|
||||||
@@ -1221,6 +1346,8 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}"</pre>
|
|||||||
editorTheme,
|
editorTheme,
|
||||||
editorOptions,
|
editorOptions,
|
||||||
onCodeChange,
|
onCodeChange,
|
||||||
|
onLanguageChange,
|
||||||
|
compileTypeScriptCode,
|
||||||
loadTemplate,
|
loadTemplate,
|
||||||
formatCode,
|
formatCode,
|
||||||
saveCode,
|
saveCode,
|
||||||
|
|||||||
@@ -422,6 +422,132 @@ public class PlaygroundApi {
|
|||||||
return dbService.getPlaygroundParserById(id);
|
return dbService.getPlaygroundParserById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存TypeScript代码及其编译结果
|
||||||
|
*/
|
||||||
|
@RouteMapping(value = "/typescript", method = RouteMethod.POST)
|
||||||
|
public Future<JsonObject> saveTypeScriptCode(RoutingContext ctx) {
|
||||||
|
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(Long parserId) {
|
||||||
|
return dbService.getTypeScriptCodeByParserId(parserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新TypeScript代码
|
||||||
|
*/
|
||||||
|
@RouteMapping(value = "/typescript/:parserId", method = RouteMethod.PUT)
|
||||||
|
public Future<JsonObject> updateTypeScriptCode(RoutingContext ctx, Long parserId) {
|
||||||
|
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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
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,4 +50,19 @@ 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,4 +265,114 @@ 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