소스 검색

增加动态脚本/代码模式创建作业

zuohuaijun 3 년 전
부모
커밋
6300fbf5af

+ 7 - 5
Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj

@@ -4,7 +4,8 @@
     <TargetFramework>net6.0</TargetFramework>
     <NoWarn>1701;1702;1591</NoWarn>
     <DocumentationFile>Admin.NET.Core.xml</DocumentationFile>
-	<ImplicitUsings>enable</ImplicitUsings>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <PreserveCompilationContext>true</PreserveCompilationContext>
   </PropertyGroup>
 
   <ItemGroup>
@@ -14,9 +15,10 @@
   <ItemGroup>
     <PackageReference Include="AngleSharp" Version="0.17.1" />
     <PackageReference Include="AspNetCoreRateLimit" Version="4.0.2" />
-    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.8.1.6" />
-    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.1.6" />
-    <PackageReference Include="Furion.Pure" Version="4.8.1.6" />
+    <PackageReference Include="DotNetCore.Natasha.CSharp" Version="5.0.2" />
+    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.8.1.7" />
+    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.1.7" />
+    <PackageReference Include="Furion.Pure" Version="4.8.1.7" />
     <PackageReference Include="Lazy.Captcha.Core" Version="1.1.6" />
     <PackageReference Include="Magicodes.IE.Excel" Version="2.7.0" />
     <PackageReference Include="Magicodes.IE.Pdf" Version="2.7.0" />
@@ -26,7 +28,7 @@
     <PackageReference Include="NewLife.Redis" Version="5.0.2022.1101" />
     <PackageReference Include="OnceMi.AspNetCore.OSS" Version="1.1.9" />
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="2.20.2" />
-    <PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="2.14.0" />
+    <PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="2.15.0" />
     <PackageReference Include="SqlSugarCore" Version="5.1.3.34" />
     <PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.23" />
     <PackageReference Include="UAParser" Version="3.1.47" />

+ 5 - 0
Admin.NET/Admin.NET.Core/Admin.NET.Core.xml

@@ -722,6 +722,11 @@
             更新时间
             </summary>
         </member>
+        <member name="P:Admin.NET.Core.SysJobDetail.ScriptCode">
+            <summary>
+            脚本代码
+            </summary>
+        </member>
         <member name="T:Admin.NET.Core.SysJobTrigger">
             <summary>
             系统作业触发器表

+ 6 - 0
Admin.NET/Admin.NET.Core/Entity/SysJobDetail.cs

@@ -64,4 +64,10 @@ public class SysJobDetail : BaseId
     /// </summary>
     [SugarColumn(ColumnDescription = "更新时间")]
     public DateTime? UpdatedTime { get; set; }
+
+    /// <summary>
+    /// 脚本代码
+    /// </summary>
+    [SugarColumn(ColumnDescription = "脚本代码", ColumnDataType = "longtext,text,clob")]
+    public string ScriptCode { get; set; }
 }

+ 9 - 1
Admin.NET/Admin.NET.Core/Service/Job/SysJobService.cs

@@ -53,7 +53,15 @@ public class SysJobService : IDynamicApiController, ITransient
         if (isExist)
             throw Oops.Oh(ErrorCodeEnum.D1006);
 
-        await _sysJobDetailRep.UpdateAsync(input.Adapt<SysJobDetail>());
+        // 动态创建作业
+        NatashaInitializer.Preheating();
+        var oop = new AssemblyCSharpBuilder("Admin.NET.Core");
+        oop.Domain = DomainManagement.Random();
+        oop.Add(input.ScriptCode);
+        var jobType = oop.GetTypeFromShortName(input.JobId);
+        _schedulerFactory.AddJob(JobBuilder.Create(jobType).SetIncludeAnnotations(input.IncludeAnnotations));
+
+        await _sysJobDetailRep.InsertAsync(input.Adapt<SysJobDetail>());
     }
 
     /// <summary>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 78 - 120
Web/package-lock.json


+ 2 - 1
Web/package.json

@@ -39,7 +39,8 @@
 		"vue-json-pretty": "^2.2.2",
 		"vue-signature-pad": "^3.0.2",
 		"vue3-tree-org": "^4.1.1",
-		"animate.css": "^4.1.1"
+		"animate.css": "^4.1.1",
+		"monaco-editor": "^0.34.1"
 	},
 	"devDependencies": {
 		"@types/node": "^18.11.9",

+ 6 - 0
Web/src/api-services/models/add-job-detail-input.ts

@@ -71,6 +71,12 @@ export interface AddJobDetailInput {
      * @memberof AddJobDetailInput
      */
     updatedTime?: Date | null;
+    /**
+     * 脚本代码
+     * @type {string}
+     * @memberof AddJobDetailInput
+     */
+    scriptCode?: string | null;
     /**
      * 作业Id
      * @type {string}

+ 6 - 0
Web/src/api-services/models/sys-job-detail.ts

@@ -77,4 +77,10 @@ export interface SysJobDetail {
      * @memberof SysJobDetail
      */
     updatedTime?: Date | null;
+    /**
+     * 脚本代码
+     * @type {string}
+     * @memberof SysJobDetail
+     */
+    scriptCode?: string | null;
 }

+ 6 - 0
Web/src/api-services/models/update-job-detail-input.ts

@@ -71,6 +71,12 @@ export interface UpdateJobDetailInput {
      * @memberof UpdateJobDetailInput
      */
     updatedTime?: Date | null;
+    /**
+     * 脚本代码
+     * @type {string}
+     * @memberof UpdateJobDetailInput
+     */
+    scriptCode?: string | null;
     /**
      * 作业Id
      * @type {string}

+ 25 - 0
Web/src/views/system/job/component/JobScriptCode.ts

@@ -0,0 +1,25 @@
+export const JobScriptCode = `namespace Admin.NET.Core;
+
+/// <summary>
+/// XXX作业任务
+/// </summary>
+[JobDetail("job_XXX", Description = "XXX", GroupName = "default", Concurrent = false)]
+[Daily(TriggerId = "trigger_XXX", Description = "XXX")]
+public class XXXJob : IJob
+{
+    private readonly IServiceProvider _serviceProvider;
+
+    public XXXJob(IServiceProvider serviceProvider)
+    {
+        _serviceProvider = serviceProvider;
+    }
+
+    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
+    {
+        using var serviceScope = _serviceProvider.CreateScope();
+        var db = serviceScope.ServiceProvider.GetService<ISqlSugarClient>();
+
+        自己的逻辑代码。。。
+
+    }
+}`;

+ 106 - 41
Web/src/views/system/job/component/editJobDetail.vue

@@ -1,52 +1,59 @@
 <template>
 	<div class="sys-jobDetail-container">
-		<el-dialog v-model="isShowDialog" draggable :close-on-click-modal="false" width="769px">
+		<el-dialog v-model="isShowDialog" draggable :close-on-click-modal="false" width="1000px">
 			<template #header>
 				<div style="color: #fff">
 					<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Edit /> </el-icon>
 					<span> {{ title }} </span>
 				</div>
 			</template>
-			<el-form :model="ruleForm" ref="ruleFormRef" size="default" label-width="110px">
-				<el-row :gutter="35">
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="作业编号" prop="jobId" :rules="[{ required: true, message: '作业编号不能为空', trigger: 'blur' }]">
-							<el-input v-model="ruleForm.jobId" placeholder="作业编号" clearable />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="组名称" prop="groupName" :rules="[{ required: true, message: '组名称不能为空', trigger: 'blur' }]">
-							<el-input v-model="ruleForm.groupName" placeholder="组名称" clearable value="default" />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="执行方式" prop="concurrent">
-							<el-radio-group v-model="ruleForm.concurrent">
-								<el-radio :label="true">并行</el-radio>
-								<el-radio :label="false">串行</el-radio>
-							</el-radio-group>
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="扫描特性触发器" prop="includeAnnotations">
-							<el-radio-group v-model="ruleForm.includeAnnotations">
-								<el-radio :label="true">是</el-radio>
-								<el-radio :label="false">否</el-radio>
-							</el-radio-group>
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="额外数据" prop="properties">
-							<el-input v-model="ruleForm.properties" placeholder="额外数据" clearable type="textarea" />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="描述信息" prop="description">
-							<el-input v-model="ruleForm.description" placeholder="描述信息" clearable type="textarea" />
-						</el-form-item>
-					</el-col>
-				</el-row>
-			</el-form>
+			<el-tabs>
+				<el-tab-pane label="作业信息">
+					<el-form :model="ruleForm" ref="ruleFormRef" size="default" label-width="110px">
+						<el-row :gutter="35">
+							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="作业编号" prop="jobId" :rules="[{ required: true, message: '作业编号不能为空', trigger: 'blur' }]">
+									<el-input v-model="ruleForm.jobId" placeholder="作业编号" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="组名称" prop="groupName" :rules="[{ required: true, message: '组名称不能为空', trigger: 'blur' }]">
+									<el-input v-model="ruleForm.groupName" placeholder="组名称" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="执行方式" prop="concurrent">
+									<el-radio-group v-model="ruleForm.concurrent">
+										<el-radio :label="true">并行</el-radio>
+										<el-radio :label="false">串行</el-radio>
+									</el-radio-group>
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="扫描特性触发器" prop="includeAnnotations">
+									<el-radio-group v-model="ruleForm.includeAnnotations">
+										<el-radio :label="true">是</el-radio>
+										<el-radio :label="false">否</el-radio>
+									</el-radio-group>
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="额外数据" prop="properties">
+									<el-input v-model="ruleForm.properties" placeholder="额外数据" clearable type="textarea" />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="描述信息" prop="description">
+									<el-input v-model="ruleForm.description" placeholder="描述信息" clearable type="textarea" />
+								</el-form-item>
+							</el-col>
+						</el-row>
+					</el-form>
+				</el-tab-pane>
+				<el-tab-pane label="作业代码">
+					<div ref="monacoEditorRef" style="width: 100%; height: 500px"></div>
+				</el-tab-pane>
+			</el-tabs>
 			<template #footer>
 				<span class="dialog-footer">
 					<el-button @click="cancel" size="default">取 消</el-button>
@@ -60,6 +67,8 @@
 <script lang="ts">
 import { reactive, toRefs, defineComponent, ref } from 'vue';
 import mittBus from '/@/utils/mitt';
+import * as monaco from 'monaco-editor';
+import { JobScriptCode } from './JobScriptCode';
 
 import { getAPI } from '/@/utils/axios-utils';
 import { SysJobApi } from '/@/api-services/api';
@@ -76,14 +85,68 @@ export default defineComponent({
 	},
 	setup() {
 		const ruleFormRef = ref();
+		const monacoEditorRef = ref();
 		const state = reactive({
 			isShowDialog: false,
 			ruleForm: {} as UpdateJobDetailInput,
+			monacoEditor: null as any,
 		});
+		// 初始化monacoEditor对象
+		var monacoEditor: any = null;
+		const initMonacoEditor = () => {
+			// self.MonacoEnvironment = {
+			// 	getWorkerUrl: function (moduleId, label) {
+			// 		if (label === 'json') {
+			// 			return './json.worker.bundle.js';
+			// 		}
+			// 		if (label === 'css') {
+			// 			return './css.worker.bundle.js';
+			// 		}
+			// 		if (label === 'html') {
+			// 			return './html.worker.bundle.js';
+			// 		}
+			// 		if (label === 'typescript' || label === 'javascript') {
+			// 			return './ts.worker.bundle.js';
+			// 		}
+			// 		return './editor.worker.bundle.js';
+			// 	},
+			// };
+			monacoEditor = monaco.editor.create(monacoEditorRef.value, {
+				theme: 'vs-dark', // 主题 vs vs-dark hc-black
+				value: JobScriptCode, // 默认显示的值
+				language: 'csharp',
+				formatOnPaste: true,
+				wordWrap: 'on', //自动换行,注意大小写
+				wrappingIndent: 'indent',
+				folding: true, // 是否折叠
+				foldingHighlight: true, // 折叠等高线
+				foldingStrategy: 'indentation', // 折叠方式  auto | indentation
+				showFoldingControls: 'always', // 是否一直显示折叠 always | mouSEOver
+				disableLayerHinting: true, // 等宽优化
+				emptySelectionClipboard: false, // 空选择剪切板
+				selectionClipboard: false, // 选择剪切板
+				automaticLayout: true, // 自动布局
+				codeLens: false, // 代码镜头
+				scrollBeyondLastLine: false, // 滚动完最后一行后再滚动一屏幕
+				colorDecorators: true, // 颜色装饰器
+				accessibilitySupport: 'auto', // 辅助功能支持  "auto" | "off" | "on"
+				lineNumbers: 'on', // 行号 取值: "on" | "off" | "relative" | "interval" | function
+				lineNumbersMinChars: 5, // 行号最小字符   number
+				//enableSplitViewResizing: false,
+				readOnly: false, //是否只读  取值 true | false
+			});
+		};
 		// 打开弹窗
 		const openDialog = (row: any) => {
 			state.ruleForm = JSON.parse(JSON.stringify(row));
 			state.isShowDialog = true;
+
+			// 延迟拿值防止取不到
+			setTimeout(() => {
+				console.log(monacoEditor)
+				if (monacoEditor == null) initMonacoEditor();
+				else monacoEditor.setValue(row.id == undefined ? JobScriptCode : state.ruleForm.scriptCode);
+			}, 1);
 		};
 		// 关闭弹窗
 		const closeDialog = () => {
@@ -98,6 +161,7 @@ export default defineComponent({
 		const submit = () => {
 			ruleFormRef.value.validate(async (valid: boolean) => {
 				if (!valid) return;
+				state.ruleForm.scriptCode = monacoEditor.getValue();
 				if (state.ruleForm.id != undefined && state.ruleForm.id > 0) {
 					await getAPI(SysJobApi).sysJobDetailUpdatePost(state.ruleForm);
 				} else {
@@ -108,6 +172,7 @@ export default defineComponent({
 		};
 		return {
 			ruleFormRef,
+			monacoEditorRef,
 			openDialog,
 			closeDialog,
 			cancel,

+ 1 - 0
Web/src/views/system/job/component/editJobTrigger.vue

@@ -115,6 +115,7 @@ export default defineComponent({
 		});
 		// 打开弹窗
 		const openDialog = (row: any) => {
+			console.log(row)
 			state.ruleForm = JSON.parse(JSON.stringify(row));
 			state.isShowDialog = true;
 		};

+ 1 - 2
Web/src/views/system/job/index.vue

@@ -238,7 +238,7 @@ export default defineComponent({
 		// 打开新增作业页面
 		const openAddJobDetail = () => {
 			state.editJobDetailTitle = '添加作业';
-			editJobDetailRef.value.openDialog({});
+			editJobDetailRef.value.openDialog({ concurrent: true, includeAnnotations: true, groupName: 'default' });
 		};
 		// 打开编辑作业页面
 		const openEditJobDetail = (row: any) => {
@@ -266,7 +266,6 @@ export default defineComponent({
 		};
 		// 打开编辑触发器页面
 		const openEditJobTrigger = (row: any) => {
-			console.log(row);
 			state.editJobTriggerTitle = '编辑触发器';
 			editJobTriggerRef.value.openDialog(row);
 		};

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.