baotian
2024-06-04 b959135a1139fb66646523d92e5bd20c5910f283
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
using iWare.Wms.Core;
using Furion;
using Furion.DatabaseAccessor;
using Furion.DatabaseAccessor.Extensions;
using Furion.DependencyInjection;
using Furion.DynamicApiController;
using Furion.FriendlyException;
using Furion.JsonSerialization;
using Furion.RemoteRequest.Extensions;
using Furion.TaskScheduler;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Reflection;
 
namespace iWare.Wms.Application
{
    /// <summary>
    /// 任务调度服务
    /// </summary>
    [ApiDescriptionSettings(Name = "Timer", Order = 100)]
    [Route("api")]
    public class SysTimerService : ISysTimerService, IDynamicApiController, IScoped
    {
        private readonly IRepository<SysTimer> _sysTimerRep;  // 任务表仓储
        private readonly ISysCacheService _cache;
 
        public SysTimerService(IRepository<SysTimer> sysTimerRep, ISysCacheService cache)
        {
            _sysTimerRep = sysTimerRep;
            _cache = cache;
        }
 
        /// <summary>
        /// 分页获取任务列表
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpGet("sysTimers/page")]
        public async Task<PageResult<JobOutput>> GetTimerPageList([FromQuery] JobPageInput input)
        {
            var workers = SpareTime.GetWorkers().ToList();
 
            var timers = await _sysTimerRep.DetachedEntities
                                           .Where(!string.IsNullOrEmpty(input.JobName?.Trim()), u => EF.Functions.Like(u.JobName, $"%{input.JobName.Trim()}%"))
                                           .ProjectToType<JobOutput>()
                                           .ToADPagedListAsync(input.PageNo, input.PageSize);
 
            timers.Rows.ToList().ForEach(u =>
            {
                var timer = workers.FirstOrDefault(m => m.WorkerName == u.JobName);
                if (timer != null)
                {
                    u.TimerStatus = timer.Status;
                    u.RunNumber = timer.Tally;
                    u.Exception = JSON.Serialize(timer.Exception);
                }
            });
            return timers;
        }
 
        /// <summary>
        /// 获取所有本地任务
        /// </summary>
        /// <returns></returns>
        [HttpGet("sysTimers/localJobList")]
        public async Task<IEnumerable<TaskMethodInfo>> GetLocalJobList()
        {
            // 获取本地所有任务方法
            return await GetTaskMethods();
        }
 
        /// <summary>
        /// 增加任务
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpPost("sysTimers/add")]
        public async Task AddTimer(AddJobInput input)
        {
            var isExist = await _sysTimerRep.AnyAsync(u => u.JobName == input.JobName, false);
            if (isExist)
                throw Oops.Oh(ErrorCode.D1100);
 
            var timer = input.Adapt<SysTimer>();
            await _sysTimerRep.InsertAsync(timer);
 
            // 添加到任务调度里
            AddTimerJob(input);
        }
 
        /// <summary>
        /// 删除任务
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpPost("sysTimers/delete")]
        public async Task DeleteTimer(DeleteJobInput input)
        {
            var timer = await _sysTimerRep.FirstOrDefaultAsync(u => u.Id == input.Id);
            if (timer == null)
                throw Oops.Oh(ErrorCode.D1101);
 
            await timer.DeleteAsync();
 
            // 从调度器里取消
            SpareTime.Cancel(timer.JobName);
        }
 
        /// <summary>
        /// 修改任务
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpPost("sysTimers/edit")]
        public async Task UpdateTimber(UpdateJobInput input)
        {
            // 排除自己并且判断与其他是否相同
            var isExist = await _sysTimerRep.AnyAsync(u => u.JobName == input.JobName && u.Id != input.Id, false);
            if (isExist) throw Oops.Oh(ErrorCode.D1100);
 
            // 先从调度器里取消
            var oldTimer = await _sysTimerRep.FirstOrDefaultAsync(u => u.Id == input.Id, false);
            SpareTime.Cancel(oldTimer.JobName);
 
            var timer = input.Adapt<SysTimer>();
            await timer.UpdateAsync(ignoreNullValues: true);
            var addJobInput = input.Adapt<AddJobInput>();
            // 再添加到任务调度里
            AddTimerJob(addJobInput);
        }
 
        /// <summary>
        /// 查看任务
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpGet("sysTimers/detail")]
        public async Task<SysTimer> GetTimer([FromQuery] QueryJobInput input)
        {
            return await _sysTimerRep.DetachedEntities.FirstOrDefaultAsync(u => u.Id == input.Id);
        }
 
        /// <summary>
        /// 停止任务
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpPost("sysTimers/stop")]
        public void StopTimerJob(StopJobInput input)
        {
            SpareTime.Stop(input.JobName);
        }
 
        /// <summary>
        /// 启动任务
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpPost("sysTimers/start")]
        public void StartTimerJob(AddJobInput input)
        {
            var timer = SpareTime.GetWorkers().ToList().Find(u => u.WorkerName == input.JobName);
            if (timer == null)
                AddTimerJob(input);
 
            // 如果 StartNow 为 flase , 执行 AddTimerJob 并不会启动任务
            SpareTime.Start(input.JobName);
        }
 
        /// <summary>
        /// 新增定时任务
        /// </summary>
        /// <param name="input"></param>
        [NonAction]
        public void AddTimerJob(AddJobInput input)
        {
            Func<SpareTimer, long, Task> action = null;
 
            switch (input.RequestType)
            {
                // 创建本地方法委托
                case RequestTypeEnum.Run:
                    {
                        // 查询符合条件的任务方法
                        var taskMethod = GetTaskMethods()?.Result.FirstOrDefault(m => m.RequestUrl == input.RequestUrl);
                        if (taskMethod == null) break;
 
                        // 创建任务对象
                        var typeInstance = Activator.CreateInstance(taskMethod.DeclaringType);
 
                        // 创建委托
                        action = (Func<SpareTimer, long, Task>)Delegate.CreateDelegate(typeof(Func<SpareTimer, long, Task>), typeInstance, taskMethod.MethodName);
                        break;
                    }
                // 创建网络任务委托
                default:
                    {
                        action = async (_, _) =>
                        {
                            var requestUrl = input.RequestUrl.Trim();
                            requestUrl = requestUrl?.IndexOf("http") == 0 ? requestUrl : "http://" + requestUrl;
                            var requestParameters = input.RequestParameters;
                            var headersString = input.Headers;
                            var headers = string.IsNullOrEmpty(headersString)
                                ? null
                                : JSON.Deserialize<Dictionary<string, string>>(headersString);
 
                            switch (input.RequestType)
                            {
                                case RequestTypeEnum.Get:
                                    await requestUrl.SetHeaders(headers).GetAsync();
                                    break;
 
                                case RequestTypeEnum.Post:
                                    await requestUrl.SetHeaders(headers).SetQueries(requestParameters).PostAsync();
                                    break;
 
                                case RequestTypeEnum.Put:
                                    await requestUrl.SetHeaders(headers).SetQueries(requestParameters).PutAsync();
                                    break;
 
                                case RequestTypeEnum.Delete:
                                    await requestUrl.SetHeaders(headers).DeleteAsync();
                                    break;
                            }
                        };
                        break;
                    }
            }
 
            if (action == null) return;
 
            // 缓存任务配置参数,以供任务运行时读取
            if (input.RequestType == RequestTypeEnum.Run)
            {
                var jobParametersName = $"{input.JobName}_Parameters";
                var jobParameters = _cache.Exists(jobParametersName);
                var requestParametersIsNull = string.IsNullOrEmpty(input.RequestParameters);
 
                // 如果没有任务配置却又存在缓存,则删除缓存
                if (requestParametersIsNull && jobParameters)
                    _cache.RemoveAsync(jobParametersName);
                else if (!requestParametersIsNull)
                    _cache.SetAsync(jobParametersName, JSON.Deserialize<Dictionary<string, string>>(input.RequestParameters));
            }
 
            // 创建定时任务
            switch (input.TimerType)
            {
                case SpareTimeTypes.Interval:
                    if (input.DoOnce)
                        SpareTime.DoOnce((int)input.Interval * 1000, action, input.JobName, input.Remark, input.StartNow, executeType: input.ExecuteType);
                    else
                        SpareTime.Do((int)input.Interval * 1000, action, input.JobName, input.Remark, input.StartNow, executeType: input.ExecuteType);
                    break;
 
                case SpareTimeTypes.Cron:
                    SpareTime.Do(input.Cron, action, input.JobName, input.Remark, input.StartNow, executeType: input.ExecuteType);
                    break;
            }
        }
 
        /// <summary>
        /// 启动自启动任务
        /// </summary>
        [NonAction]
        public void StartTimerJob()
        {
            var sysTimerList = _sysTimerRep.DetachedEntities.Where(t => t.StartNow).ProjectToType<AddJobInput>().ToList();
            sysTimerList.ForEach(AddTimerJob);
        }
 
        /// <summary>
        /// 获取所有本地任务
        /// </summary>
        /// <returns></returns>
        [NonAction]
        public async Task<IEnumerable<TaskMethodInfo>> GetTaskMethods()
        {
            // 有缓存就返回缓存
            var taskMethods = await _cache.GetAsync<IEnumerable<TaskMethodInfo>>(CommonConst.CACHE_KEY_TIMER_JOB);
            if (taskMethods != null) return taskMethods;
 
            // 获取所有本地任务方法,必须有spareTimeAttribute特性
            taskMethods = App.EffectiveTypes
                .Where(u => u.IsClass && !u.IsInterface && !u.IsAbstract && typeof(ISpareTimeWorker).IsAssignableFrom(u))
                .SelectMany(u => u.GetMethods(BindingFlags.Public | BindingFlags.Instance)
                .Where(m => m.IsDefined(typeof(SpareTimeAttribute), false) &&
                       m.GetParameters().Length == 2 &&
                       m.GetParameters()[0].ParameterType == typeof(SpareTimer) &&
                       m.GetParameters()[1].ParameterType == typeof(long) && (m.ReturnType == typeof(void) || m.ReturnType == typeof(Task)))
                .Select(m =>
                {
                    // 默认获取第一条任务特性
                    var spareTimeAttribute = m.GetCustomAttribute<SpareTimeAttribute>();
                    return new TaskMethodInfo
                    {
                        JobName = spareTimeAttribute.WorkerName,
                        RequestUrl = $"{m.DeclaringType.Name}/{m.Name}",
                        Cron = spareTimeAttribute.CronExpression,
                        DoOnce = spareTimeAttribute.DoOnce,
                        ExecuteType = spareTimeAttribute.ExecuteType,
                        Interval = (int)spareTimeAttribute.Interval / 1000,
                        StartNow = spareTimeAttribute.StartNow,
                        RequestType = RequestTypeEnum.Run,
                        Remark = spareTimeAttribute.Description,
                        TimerType = string.IsNullOrEmpty(spareTimeAttribute.CronExpression) ? SpareTimeTypes.Interval : SpareTimeTypes.Cron,
                        MethodName = m.Name,
                        DeclaringType = m.DeclaringType
                    };
                }));
 
            await _cache.SetAsync(CommonConst.CACHE_KEY_TIMER_JOB, taskMethods);
            return taskMethods;
        }
    }
}