feat: 手动执行计划作业 (#122)

This commit is contained in:
nsnail 2024-05-15 14:50:26 +08:00 committed by GitHub
parent 7214a22ea5
commit 3b8336105a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 109 additions and 8 deletions

View File

@ -23,6 +23,7 @@ XML注释文件不存在
学历不正确 学历不正确
密码不能为空 密码不能为空
已处理完毕 已处理完毕
并发冲突请稍后重试
开始事务 开始事务
性别不正确 性别不正确
手机号码不正确 手机号码不正确

View File

@ -14,6 +14,7 @@ public static class Chars
public const string FLG_CONTEXT_OWNER_DEPT_ID = nameof(FLG_CONTEXT_OWNER_DEPT_ID); public const string FLG_CONTEXT_OWNER_DEPT_ID = nameof(FLG_CONTEXT_OWNER_DEPT_ID);
public const string FLG_CONTEXT_USER_ID = nameof(FLG_CONTEXT_USER_ID); public const string FLG_CONTEXT_USER_ID = nameof(FLG_CONTEXT_USER_ID);
public const string FLG_CONTEXT_USER_INFO = nameof(FLG_CONTEXT_USER_INFO); public const string FLG_CONTEXT_USER_INFO = nameof(FLG_CONTEXT_USER_INFO);
public const string FLG_CRON_PER_SECS = "* * * * * *";
public const string FLG_DB_EXCEPTION_PRIVATE_KEY_CONFLICT = "PRIMARY KEY"; public const string FLG_DB_EXCEPTION_PRIVATE_KEY_CONFLICT = "PRIMARY KEY";
public const string FLG_DB_FIELD_TYPE_NVARCHAR = "nvarchar"; public const string FLG_DB_FIELD_TYPE_NVARCHAR = "nvarchar";
public const string FLG_DB_FIELD_TYPE_NVARCHAR_1022 = "nvarchar(1022)"; public const string FLG_DB_FIELD_TYPE_NVARCHAR_1022 = "nvarchar(1022)";

View File

@ -20,6 +20,11 @@ public interface IJobModule : ICrudModule<CreateJobReq, QueryJobRsp // 创建类
/// </summary> /// </summary>
Task<QueryJobRsp> EditAsync(UpdateJobReq req); Task<QueryJobRsp> EditAsync(UpdateJobReq req);
/// <summary>
/// 执行作业
/// </summary>
Task ExecuteAsync(QueryJobReq req);
/// <summary> /// <summary>
/// 获取作业记录条形图数据 /// 获取作业记录条形图数据
/// </summary> /// </summary>

View File

@ -1,4 +1,5 @@
using Cronos; using Cronos;
using FreeSql.Internal;
using NetAdmin.Application.Repositories; using NetAdmin.Application.Repositories;
using NetAdmin.Application.Services; using NetAdmin.Application.Services;
using NetAdmin.Domain.DbMaps.Sys; using NetAdmin.Domain.DbMaps.Sys;
@ -84,6 +85,41 @@ public sealed class JobService(DefaultRepository<Sys_Job> rpo, IJobRecordService
return (await update.ExecuteUpdatedAsync().ConfigureAwait(false))[0].Adapt<QueryJobRsp>(); return (await update.ExecuteUpdatedAsync().ConfigureAwait(false))[0].Adapt<QueryJobRsp>();
} }
/// <inheritdoc />
public async Task ExecuteAsync(QueryJobReq req)
{
req.ThrowIfInvalid();
var df = new DynamicFilterInfo {
Filters = [
new DynamicFilterInfo {
Field = nameof(QueryJobReq.Enabled)
, Operator = DynamicFilterOperators.Eq
, Value = true
}
, new DynamicFilterInfo {
Field = nameof(QueryJobReq.Status)
, Operator = DynamicFilterOperators.Eq
, Value = JobStatues.Idle
}
]
};
var job = await QueryInternal(new QueryReq<QueryJobReq> { Count = 1, Filter = req, DynamicFilter = df })
.ToOneAsync()
.ConfigureAwait(false) ?? throw new NetAdminInvalidOperationException(Ln.);
var nextExecTime = GetNextExecTime(Chars.FLG_CRON_PER_SECS);
try {
_ = await UpdateAsync(job.Adapt<UpdateJobReq>() with {
NextExecTime = nextExecTime
, NextTimeId = nextExecTime?.TimeUnixUtc()
})
.ConfigureAwait(false);
}
catch (DbUpdateVersionException) {
throw new NetAdminInvalidOperationException(Ln.);
}
}
/// <inheritdoc /> /// <inheritdoc />
public Task<bool> ExistAsync(QueryReq<QueryJobReq> req) public Task<bool> ExistAsync(QueryReq<QueryJobReq> req)
{ {

View File

@ -42,6 +42,12 @@ public sealed class JobCache(IDistributedCache cache, IJobService service)
return Service.EditAsync(req); return Service.EditAsync(req);
} }
/// <inheritdoc />
public Task ExecuteAsync(QueryJobReq req)
{
return Service.ExecuteAsync(req);
}
/// <inheritdoc /> /// <inheritdoc />
public Task<bool> ExistAsync(QueryReq<QueryJobReq> req) public Task<bool> ExistAsync(QueryReq<QueryJobReq> req)
{ {

View File

@ -60,6 +60,14 @@ public sealed class JobController(IJobCache cache) : ControllerBase<IJobCache, I
return Cache.EditAsync(req); return Cache.EditAsync(req);
} }
/// <summary>
/// 执行作业
/// </summary>
public Task ExecuteAsync(QueryJobReq req)
{
return Cache.ExecuteAsync(req);
}
/// <summary> /// <summary>
/// 计划作业是否存在 /// 计划作业是否存在
/// </summary> /// </summary>

View File

@ -60,6 +60,17 @@ export default {
}, },
}, },
/**
* 执行作业
*/
execute: {
url: `${config.API_URL}/api/sys/job/execute`,
name: `执行作业`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/** /**
* 计划作业是否存在 * 计划作业是否存在
*/ */

View File

@ -137,13 +137,19 @@
</el-table-column> </el-table-column>
<na-col-operation <na-col-operation
:buttons=" :buttons="
naColOperation.buttons.concat({ naColOperation.buttons.concat(
{
icon: 'el-icon-video-play',
click: execute,
},
{
icon: 'el-icon-delete', icon: 'el-icon-delete',
confirm: true, confirm: true,
type: 'danger', type: 'danger',
title: $t('删除作业'), title: $t('删除作业'),
click: rowDel, click: rowDel,
}) },
)
" "
:vue="this" /> :vue="this" />
</sc-table> </sc-table>
@ -171,6 +177,7 @@ export default {
inject: ['reload'], inject: ['reload'],
data() { data() {
return { return {
timer: null,
loading: false, loading: false,
query: { query: {
dynamicFilter: { dynamicFilter: {
@ -220,6 +227,31 @@ export default {
} }
this.$refs.table.refresh() this.$refs.table.refresh()
}, },
async execute(row) {
try {
await this.$API.sys_job.execute.post({ id: row.id })
this.$refs.table.refresh()
this.$notify.success({
dangerouslyUseHTMLString: true,
message: `<div id="countdown">已发起执行请求5 秒后弹出执行结果</div>`,
onClose: async () => {
clearInterval(this.timer)
this.loading = true
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open('view', row, 1)
this.loading = false
},
})
this.timer = setInterval(() => {
const countdown = new RegExp('\\d+').exec(document.getElementById('countdown').innerText)[0]
document.getElementById('countdown').innerText = document
.getElementById('countdown')
.innerText.replace(countdown, `${parseInt(countdown) - 1}`)
}, 1000)
} catch {}
},
// //
async rowDel(row) { async rowDel(row) {
try { try {

View File

@ -189,7 +189,7 @@ export default {
mounted() {}, mounted() {},
methods: { methods: {
// //
async open(mode = 'add', data) { async open(mode = 'add', data, tabIndex = 0) {
this.visible = true this.visible = true
this.loading = true this.loading = true
this.mode = mode this.mode = mode
@ -198,6 +198,7 @@ export default {
Object.assign(this.form, res.data) Object.assign(this.form, res.data)
} }
this.loading = false this.loading = false
this.tabIndex = tabIndex
return this return this
}, },