├── .vscode ├── solution-explorer │ ├── class.ts-template │ ├── default.ts-template │ ├── interface.ts-template │ ├── enum.cs-template │ ├── class.cs-template │ ├── interface.cs-template │ ├── class.vb-template │ ├── template-parameters.js │ └── template-list.json ├── tasks.json └── launch.json ├── src └── DotXxlJob.Core │ ├── Model │ ├── HandleCallbackParam.cs │ ├── JavaClass.cs │ ├── IdleBeatRequest.cs │ ├── KillRequest.cs │ ├── LogRequest.cs │ ├── RegistryParam.cs │ ├── RpcResponse.cs │ ├── JobExecuteContext.cs │ ├── LogResult.cs │ ├── AddressEntity.cs │ ├── ReturnT.cs │ ├── RpcRequest.cs │ └── TriggerParam.cs │ ├── Queue │ ├── RetryCallbackTaskQueue.cs │ ├── CallbackTaskQueue.cs │ └── JobTaskQueue.cs │ ├── IJobHandlerFactory.cs │ ├── IExecutorRegistry.cs │ ├── TaskExecutors │ ├── ITaskExecutor.cs │ └── BeanTaskExecutor.cs │ ├── DefaultHandlers │ ├── AbsJobHandler.cs │ └── SimpleHttpJobHandler.cs │ ├── Attributes │ └── JobHandlerAttribute.cs │ ├── Logger │ ├── IJobLogger.cs │ └── JobLogger.cs │ ├── IJobHandler.cs │ ├── Hosted │ └── JobExecuteHostedService.cs │ ├── TaskExecutorFactory.cs │ ├── Config │ └── XxlJobExecutorOptions.cs │ ├── Extensions │ ├── DateTimeExtension.cs │ └── ServiceCollectionExtensions.cs │ ├── DefaultJobHandlerFactory.cs │ ├── DotXxlJob.Core.csproj │ ├── Json │ └── ProjectDefaultResolver.cs │ ├── Internal │ ├── Constants.cs │ ├── Preconditions.cs │ └── IPUtility.cs │ ├── ExecutorRegistry.cs │ ├── AdminClient.cs │ ├── JobDispatcher.cs │ └── XxlRestfulServiceHandler.cs ├── RELEASE_NOTES.md ├── tests └── DotXxlJob.Core.Tests │ ├── UnitTest1.cs │ └── DotXxlJob.Core.Tests.csproj ├── samples └── ASPNetCoreExecutor │ ├── .config │ └── dotnet-tools.json │ ├── Properties │ ├── launchSettings.json │ └── PublishProfiles │ │ └── FolderProfile.pubxml │ ├── Extensions │ ├── ApplicationBuilderExtensions.cs │ └── XxlJobExecutorMiddleware.cs │ ├── appsettings.json │ ├── DemoJobHandler.cs │ ├── ASPNetCoreExecutor.csproj │ ├── Program.cs │ └── Startup.cs ├── scripts ├── build.sh ├── package.sh └── nuget.sh ├── .gitattributes ├── .gitignore ├── LICENSE ├── .editorconfig ├── DotXxlJob.sln └── README.md /.vscode/solution-explorer/class.ts-template: -------------------------------------------------------------------------------- 1 | export class {{name}} { 2 | 3 | } -------------------------------------------------------------------------------- /.vscode/solution-explorer/default.ts-template: -------------------------------------------------------------------------------- 1 | export default {{name}} { 2 | 3 | } -------------------------------------------------------------------------------- /.vscode/solution-explorer/interface.ts-template: -------------------------------------------------------------------------------- 1 | export interface {{name}} { 2 | 3 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/HandleCallbackParam.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuanye/DotXxlJob/HEAD/src/DotXxlJob.Core/Model/HandleCallbackParam.cs -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Queue/RetryCallbackTaskQueue.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuanye/DotXxlJob/HEAD/src/DotXxlJob.Core/Queue/RetryCallbackTaskQueue.cs -------------------------------------------------------------------------------- /.vscode/solution-explorer/enum.cs-template: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace {{namespace}} 4 | { 5 | public enum {{name}} 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/solution-explorer/class.cs-template: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace {{namespace}} 4 | { 5 | public class {{name}} 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/solution-explorer/interface.cs-template: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace {{namespace}} 4 | { 5 | public interface {{name}} 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | #### 1.0.1 Feb 19, 2019 2 | - 降低日志输出等级 3 | 4 | 5 | #### 1.0.0 Jan 19, 2019 6 | - 首次发布,实现xxl-job-execute的实现 7 | - 实现基本的Bean JobHandler的功能 8 | - 实现自动注册 -------------------------------------------------------------------------------- /.vscode/solution-explorer/class.vb-template: -------------------------------------------------------------------------------- 1 | Imports System 2 | 3 | Namespace {{namespace}} 4 | 5 | Public Class {{name}} 6 | 7 | End Class 8 | 9 | End Namespace 10 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/IJobHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | namespace DotXxlJob.Core 2 | { 3 | public interface IJobHandlerFactory 4 | { 5 | IJobHandler GetJobHandler(string handlerName); 6 | } 7 | } -------------------------------------------------------------------------------- /tests/DotXxlJob.Core.Tests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace DotXxlJob.Core.Tests 5 | { 6 | public class UnitTest1 7 | { 8 | [Fact] 9 | public void Test1() 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "8.0.7", 7 | "commands": [ 8 | "dotnet-ef" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/IExecutorRegistry.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace DotXxlJob.Core 5 | { 6 | public interface IExecutorRegistry 7 | { 8 | 9 | Task RegistryAsync(CancellationToken cancellationToken); 10 | 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | cd $(dirname $0)/../ 4 | 5 | artifactsFolder="./artifacts" 6 | 7 | if [ -d $artifactsFolder ]; then 8 | rm -R $artifactsFolder 9 | fi 10 | 11 | mkdir -p $artifactsFolder 12 | 13 | dotnet restore ./DotXxlJob.sln 14 | dotnet build ./DotXxlJob.sln -c Release 15 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/JavaClass.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace DotXxlJob.Core.Model 4 | { 5 | [DataContract(Name = Constants.JavaClassFulName)] 6 | public class JavaClass 7 | { 8 | [DataMember(Name = "name",Order = 1)] 9 | public string Name { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ASPNetCoreExecutor": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "http://localhost:6662/" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/TaskExecutors/ITaskExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using DotXxlJob.Core.Model; 4 | 5 | namespace DotXxlJob.Core 6 | { 7 | public interface ITaskExecutor 8 | { 9 | string GlueType { get; } 10 | 11 | Task Execute(TriggerParam triggerParam, CancellationToken cancellationToken); 12 | } 13 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/IdleBeatRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace DotXxlJob.Core.Model 7 | { 8 | [DataContract] 9 | public class IdleBeatRequest 10 | { 11 | 12 | [DataMember(Name = "jobId", Order = 1)] 13 | public int JobId { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/KillRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace DotXxlJob.Core.Model 7 | { 8 | 9 | [DataContract] 10 | public class KillRequest 11 | { 12 | 13 | [DataMember(Name = "jobId", Order = 1)] 14 | public int JobId { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/package.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | cd $(dirname $0)/../ 4 | 5 | artifactsFolder="./artifacts" 6 | 7 | if [ -d $artifactsFolder ]; then 8 | rm -R $artifactsFolder 9 | fi 10 | 11 | mkdir -p $artifactsFolder 12 | 13 | dotnet restore ./DotXxlJob.sln 14 | dotnet build ./DotXxlJob.sln -c Release 15 | 16 | 17 | dotnet pack ./src/DotXxlJob.Core/DotXxlJob.Core.csproj -c Release -o $artifactsFolder 18 | -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/Extensions/ApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | 3 | namespace ASPNetCoreExecutor 4 | { 5 | public static class ApplicationBuilderExtensions 6 | { 7 | public static IApplicationBuilder UseXxlJobExecutor(this IApplicationBuilder @this) 8 | { 9 | return @this.UseMiddleware(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/DefaultHandlers/AbsJobHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using DotXxlJob.Core.Model; 3 | 4 | namespace DotXxlJob.Core.DefaultHandlers 5 | { 6 | public abstract class AbsJobHandler:IJobHandler 7 | { 8 | public virtual void Dispose() 9 | { 10 | 11 | } 12 | 13 | public abstract Task Execute(JobExecuteContext context); 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information" 5 | } 6 | }, 7 | "xxlJob": { 8 | "adminAddresses": "https://jobs.xuanye.wang/xxl-job-admin", 9 | "appName": "xxl-job-executor-dotnet", 10 | "specialBindAddress": "127.0.0.1", 11 | "port": 6662, 12 | "autoRegistry": true, 13 | "accessToken": "", 14 | "logRetentionDays": 30 15 | } 16 | } -------------------------------------------------------------------------------- /scripts/nuget.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | cd $(dirname $0)/../ 4 | 5 | artifactsFolder="./artifacts" 6 | 7 | if [ -d $artifactsFolder ]; then 8 | rm -R $artifactsFolder 9 | fi 10 | 11 | mkdir -p $artifactsFolder 12 | 13 | 14 | dotnet build ./src/DotXxlJob.Core/DotXxlJob.Core.csproj -c Release 15 | 16 | dotnet pack ./src/DotXxlJob.Core/DotXxlJob.Core.csproj -c Release -o $artifactsFolder 17 | 18 | dotnet nuget push $artifactsFolder/DotXxlJob.Core.*.nupkg -k $NUGET_KEY -s https://www.nuget.org 19 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Attributes/JobHandlerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DotXxlJob.Core 4 | { 5 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 6 | public class JobHandlerAttribute:Attribute 7 | { 8 | public JobHandlerAttribute(string name) 9 | { 10 | Name = name; 11 | } 12 | 13 | public string Name { get; } 14 | 15 | /// 16 | /// set Ignore 17 | /// 18 | public bool Ignore { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Logger/IJobLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DotXxlJob.Core.Model; 3 | 4 | namespace DotXxlJob.Core 5 | { 6 | public interface IJobLogger 7 | { 8 | 9 | void SetLogFile(long logTime, long logId); 10 | 11 | void Log(string pattern, params object[] format); 12 | 13 | 14 | void LogError(Exception ex); 15 | 16 | 17 | LogResult ReadLog(long logTime, long logId, int fromLine); 18 | 19 | 20 | void LogSpecialFile(long logTime, long logId, string pattern, params object[] format); 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /.vscode/solution-explorer/template-parameters.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | 3 | module.exports = function(filename, projectPath, folderPath) { 4 | var namespace = "Unknown"; 5 | if (projectPath) { 6 | namespace = path.basename(projectPath, path.extname(projectPath)); 7 | if (folderPath) { 8 | namespace += "." + folderPath.replace(path.dirname(projectPath), "").substring(1).replace(/[\\\/]/g, "."); 9 | } 10 | } 11 | 12 | return { 13 | namespace: namespace, 14 | name: path.basename(filename, path.extname(filename)) 15 | } 16 | }; -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/LogRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace DotXxlJob.Core.Model 7 | { 8 | [DataContract] 9 | public class LogRequest 10 | { 11 | [DataMember(Name = "logDateTim", Order =1)] 12 | public long LogDateTime { get; set; } 13 | 14 | [DataMember(Name = "logId", Order = 2)] 15 | public int LogId { get; set; } 16 | 17 | [DataMember(Name = "fromLineNum", Order = 3)] 18 | public int FromLineNum { get; set; } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/RegistryParam.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace DotXxlJob.Core.Model 4 | { 5 | [DataContract(Name = Constants.RegistryParamJavaFullName)] 6 | public class RegistryParam 7 | { 8 | [DataMember(Name = "registryGroup", Order = 1)] 9 | public string RegistryGroup { get; set; } 10 | 11 | [DataMember(Name = "registryKey", Order = 2)] 12 | public string RegistryKey { get; set; } 13 | 14 | 15 | [DataMember(Name = "registryValue", Order = 3)] 16 | public string RegistryValue { get; set; } 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/RpcResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace DotXxlJob.Core.Model 4 | { 5 | [DataContract(Name = Constants.RpcResponseJavaFullName)] 6 | public class RpcResponse 7 | { 8 | [DataMember(Name = "requestId",Order = 1)] 9 | public string RequestId{ get; set; } 10 | [DataMember(Name = "errorMsg",Order = 2)] 11 | public string ErrorMsg { get; set; } 12 | [DataMember(Name = "result",Order = 3)] 13 | public object Result{ get; set; } 14 | 15 | 16 | public bool IsError => this.ErrorMsg != null; 17 | } 18 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/JobExecuteContext.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace DotXxlJob.Core.Model 4 | { 5 | public class JobExecuteContext 6 | { 7 | public JobExecuteContext(IJobLogger jobLogger, string jobParameter, CancellationToken cancellationToken) 8 | { 9 | this.JobLogger = jobLogger; 10 | this.JobParameter = jobParameter; 11 | this.cancellationToken = cancellationToken; 12 | } 13 | public string JobParameter { get; } 14 | public IJobLogger JobLogger { get; } 15 | public CancellationToken cancellationToken { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/DotXxlJob.Core.Tests/DotXxlJob.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/IJobHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using DotXxlJob.Core.Model; 4 | 5 | namespace DotXxlJob.Core 6 | { 7 | public abstract class AbstractJobHandler:IJobHandler 8 | { 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | public abstract Task Execute(JobExecuteContext context); 15 | 16 | 17 | public virtual void Dispose() 18 | { 19 | } 20 | } 21 | 22 | public interface IJobHandler:IDisposable 23 | { 24 | Task Execute(JobExecuteContext context); 25 | } 26 | } -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/DemoJobHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using DotXxlJob.Core; 3 | using DotXxlJob.Core.Model; 4 | 5 | namespace ASPNetCoreExecutor 6 | { 7 | /// 8 | /// 示例Job,只是写个日志 9 | /// 10 | [JobHandler("demoJobHandler")] 11 | public class DemoJobHandler:AbstractJobHandler 12 | { 13 | public override async Task Execute(JobExecuteContext context) 14 | { 15 | context.JobLogger.Log("receive demo job handler,parameter:{0}",context.JobParameter); 16 | context.JobLogger.Log("开始休眠10秒"); 17 | await Task.Delay(10 * 1000); 18 | context.JobLogger.Log("休眠10秒结束"); 19 | return ReturnT.SUCCESS; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Hosted/JobExecuteHostedService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace DotXxlJob.Core 6 | { 7 | /// 8 | /// NOTE: 负责启动Executor服务,和进行服务注册的宿主服务 9 | /// 10 | public class JobsExecuteHostedService:BackgroundService 11 | { 12 | private readonly IExecutorRegistry _registry; 13 | 14 | public JobsExecuteHostedService(IExecutorRegistry registry) 15 | { 16 | this._registry = registry; 17 | } 18 | 19 | protected override Task ExecuteAsync(CancellationToken stoppingToken) 20 | { 21 | return this._registry.RegistryAsync(stoppingToken); 22 | } 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 2.2.0 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <_ContentIncludedByDefault Remove="Properties\launchSettings.json" /> 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/LogResult.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace DotXxlJob.Core.Model 4 | { 5 | [DataContract(Name = Constants.LogResultJavaFullName)] 6 | public class LogResult 7 | { 8 | 9 | public LogResult(int fromLine ,int toLine,string content,bool isEnd) 10 | { 11 | this.FromLineNum = fromLine; 12 | this.ToLineNum = toLine; 13 | this.LogContent = content; 14 | this.IsEnd = isEnd; 15 | } 16 | 17 | [DataMember(Name = "fromLineNum",Order = 1)] 18 | public int FromLineNum { get; set; } 19 | [DataMember(Name = "toLineNum",Order = 2)] 20 | public int ToLineNum { get; set; } 21 | [DataMember(Name = "logContent",Order = 3)] 22 | public string LogContent { get; set; } 23 | [DataMember(Name = "isEnd",Order = 4)] 24 | public bool IsEnd { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace ASPNetCoreExecutor 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .ConfigureLogging((ctx, builder) => 23 | { 24 | builder.AddConfiguration(ctx.Configuration); 25 | builder.AddConsole(); 26 | }) 27 | .UseStartup(); 28 | } 29 | } -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | true 8 | false 9 | true 10 | Release 11 | Any CPU 12 | FileSystem 13 | bin\Release\net6.0\publish\ 14 | FileSystem 15 | <_TargetId>Folder 16 | 17 | net6.0 18 | linux-x64 19 | true 20 | dc9e5af3-18ff-4713-bdb4-672e47ada4e5 21 | false 22 | 23 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/AddressEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DotXxlJob.Core.Model 4 | { 5 | public class AddressEntry 6 | { 7 | public string RequestUri { get; set; } 8 | 9 | private DateTime? LastFailedTime { get; set; } 10 | 11 | private int FailedTimes { get; set; } 12 | 13 | public bool CheckAccessible() 14 | { 15 | if (LastFailedTime == null) 16 | return true; 17 | 18 | if (DateTime.UtcNow.Subtract(LastFailedTime.Value) > Constants.AdminServerReconnectInterval) 19 | return true; 20 | 21 | if (FailedTimes < Constants.AdminServerCircuitFailedTimes) 22 | return true; 23 | 24 | return false; 25 | } 26 | 27 | public void Reset() 28 | { 29 | LastFailedTime = null; 30 | FailedTimes = 0; 31 | } 32 | 33 | public void SetFail() 34 | { 35 | LastFailedTime = DateTime.UtcNow; 36 | FailedTimes++; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.resx text=auto 19 | *.c text=auto 20 | *.cpp text=auto 21 | *.cxx text=auto 22 | *.h text=auto 23 | *.hxx text=auto 24 | *.py text=auto 25 | *.rb text=auto 26 | *.java text=auto 27 | *.html text=auto 28 | *.htm text=auto 29 | *.css text=auto 30 | *.scss text=auto 31 | *.sass text=auto 32 | *.less text=auto 33 | *.js text=auto 34 | *.lisp text=auto 35 | *.clj text=auto 36 | *.sql text=auto 37 | *.php text=auto 38 | *.lua text=auto 39 | *.m text=auto 40 | *.asm text=auto 41 | *.erl text=auto 42 | *.fs text=auto 43 | *.fsx text=auto 44 | *.hs text=auto 45 | *.txt eol=crlf 46 | 47 | *.csproj text=auto 48 | *.vbproj text=auto 49 | *.fsproj text=auto 50 | *.dbproj text=auto 51 | *.sln text=auto eol=crlf 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Misc folders 2 | [Bb]in/ 3 | [Oo]bj/ 4 | [Pp]ackages/ 5 | 6 | # Build related 7 | tools/** 8 | !tools/packages.config 9 | 10 | ## Ignore Visual Studio temporary files, build results, and 11 | ## files generated by popular Visual Studio add-ons. 12 | 13 | # User-specific files 14 | *.suo 15 | *.user 16 | *.sln.docstates 17 | *.sln.ide/ 18 | *.userprefs 19 | *.GhostDoc.xml 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Rr]elease/ 24 | x64/ 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.log 41 | *.vspscc 42 | *.vssscc 43 | .builds 44 | 45 | # Visual Studio profiler 46 | *.psess 47 | *.vsp 48 | *.vspx 49 | 50 | # ReSharper is a .NET coding add-in 51 | _ReSharper* 52 | 53 | # NCrunch 54 | *.ncrunch* 55 | .*crunch*.local.xml 56 | _NCrunch_* 57 | 58 | artifacts/ 59 | # NuGet Packages Directory 60 | packages 61 | 62 | # Windows 63 | Thumbs.db 64 | 65 | # NUnit 66 | TestResult.xml 67 | .vs/ 68 | .idea/ 69 | 70 | doc/_site/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 xuanye wong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/TaskExecutors/BeanTaskExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using DotXxlJob.Core.Model; 4 | 5 | namespace DotXxlJob.Core.TaskExecutors 6 | { 7 | /// 8 | /// 实现 IJobHandler的执行器 9 | /// 10 | public class BeanTaskExecutor : ITaskExecutor 11 | { 12 | private readonly IJobHandlerFactory _handlerFactory; 13 | private readonly IJobLogger _jobLogger; 14 | 15 | public BeanTaskExecutor(IJobHandlerFactory handlerFactory, IJobLogger jobLogger) 16 | { 17 | this._handlerFactory = handlerFactory; 18 | this._jobLogger = jobLogger; 19 | } 20 | 21 | public string GlueType { get; } = Constants.GlueType.BEAN; 22 | 23 | public Task Execute(TriggerParam triggerParam, CancellationToken cancellationToken) 24 | { 25 | var handler = _handlerFactory.GetJobHandler(triggerParam.ExecutorHandler); 26 | 27 | if (handler == null) 28 | { 29 | return Task.FromResult(ReturnT.Failed($"job handler [{triggerParam.ExecutorHandler} not found.")); 30 | } 31 | var context = new JobExecuteContext(this._jobLogger, triggerParam.ExecutorParams, cancellationToken); 32 | return handler.Execute(context); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/TaskExecutorFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace DotXxlJob.Core 7 | { 8 | /// 9 | /// 负责响应RPC请求,调度任务执行器的工厂类 10 | /// 11 | public class TaskExecutorFactory 12 | { 13 | private readonly IServiceProvider _provider; 14 | 15 | private readonly Dictionary _cache = new Dictionary(); 16 | public TaskExecutorFactory(IServiceProvider provider) 17 | { 18 | this._provider = provider; 19 | Initialize(); 20 | } 21 | 22 | private void Initialize() 23 | { 24 | var executors = this._provider.GetServices(); 25 | 26 | var taskExecutors = executors as ITaskExecutor[] ?? executors.ToArray(); 27 | if (executors == null || !taskExecutors.Any()) return; 28 | 29 | foreach (var item in taskExecutors) 30 | { 31 | this._cache.Add(item.GlueType,item); 32 | } 33 | } 34 | 35 | public ITaskExecutor GetTaskExecutor(string glueType) 36 | { 37 | return this._cache.TryGetValue(glueType, out var executor) ? executor : null; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/ReturnT.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace DotXxlJob.Core 4 | { 5 | [DataContract(Name = Constants.ReturnTJavaFullName)] 6 | public class ReturnT 7 | { 8 | public const int SUCCESS_CODE = 200; 9 | public const int FAIL_CODE = 500; 10 | 11 | public static readonly ReturnT SUCCESS = new ReturnT(SUCCESS_CODE, null); 12 | public static readonly ReturnT FAIL = new ReturnT(FAIL_CODE, null); 13 | public static readonly ReturnT FAIL_TIMEOUT = new ReturnT(502, null); 14 | 15 | public ReturnT() { } 16 | 17 | public ReturnT(int code, string msg) 18 | { 19 | Code = code; 20 | Msg = msg; 21 | } 22 | 23 | 24 | [DataMember(Name = "code",Order = 1)] 25 | public int Code { get; set; } 26 | [DataMember(Name = "msg",Order = 2)] 27 | public string Msg { get; set; } 28 | 29 | [DataMember(Name = "content",Order = 3)] 30 | public object Content { get; set; } 31 | 32 | 33 | 34 | public static ReturnT Failed(string msg) 35 | { 36 | return new ReturnT(FAIL_CODE, msg); 37 | } 38 | public static ReturnT Success(string msg) 39 | { 40 | return new ReturnT(SUCCESS_CODE, msg); 41 | } 42 | 43 | } 44 | 45 | 46 | 47 | } -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/Extensions/XxlJobExecutorMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using DotXxlJob.Core; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace ASPNetCoreExecutor 10 | { 11 | public class XxlJobExecutorMiddleware 12 | { 13 | private readonly IServiceProvider _provider; 14 | private readonly RequestDelegate _next; 15 | 16 | private readonly XxlRestfulServiceHandler _rpcService; 17 | public XxlJobExecutorMiddleware(IServiceProvider provider, RequestDelegate next) 18 | { 19 | this._provider = provider; 20 | this._next = next; 21 | this._rpcService = _provider.GetRequiredService(); 22 | } 23 | 24 | 25 | public async Task Invoke(HttpContext context) 26 | { 27 | string contentType = context.Request.ContentType; 28 | 29 | if ("POST".Equals(context.Request.Method, StringComparison.OrdinalIgnoreCase) 30 | && !string.IsNullOrEmpty(contentType) 31 | && contentType.ToLower().StartsWith("application/json")) 32 | { 33 | 34 | await _rpcService.HandlerAsync(context.Request,context.Response); 35 | 36 | return; 37 | } 38 | 39 | await _next.Invoke(context); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /.vscode/solution-explorer/template-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | { 4 | "name": "Class", 5 | "extension": "cs", 6 | "file": "./class.cs-template", 7 | "parameters": "./template-parameters.js" 8 | }, 9 | { 10 | "name": "Interface", 11 | "extension": "cs", 12 | "file": "./interface.cs-template", 13 | "parameters": "./template-parameters.js" 14 | }, 15 | { 16 | "name": "Enum", 17 | "extension": "cs", 18 | "file": "./enum.cs-template", 19 | "parameters": "./template-parameters.js" 20 | }, 21 | { 22 | "name": "Class", 23 | "extension": "ts", 24 | "file": "./class.ts-template", 25 | "parameters": "./template-parameters.js" 26 | }, 27 | { 28 | "name": "Interface", 29 | "extension": "ts", 30 | "file": "./interface.ts-template", 31 | "parameters": "./template-parameters.js" 32 | }, 33 | { 34 | "name": "Default", 35 | "extension": "ts", 36 | "file": "./default.ts-template", 37 | "parameters": "./template-parameters.js" 38 | }, 39 | { 40 | "name": "Class", 41 | "extension": "vb", 42 | "file": "./class.vb-template", 43 | "parameters": "./template-parameters.js" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/RpcRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace DotXxlJob.Core.Model 5 | { 6 | [DataContract(Name = Constants.RpcRequestJavaFullName)] 7 | public class RpcRequest 8 | { 9 | /* 10 | requestId 11 | createMillisTime 12 | accessToken 13 | className 14 | methodName 15 | version 16 | parameterTypes 17 | parameters 18 | */ 19 | [DataMember(Name = "requestId",Order = 1)] 20 | public string RequestId { get; set; } 21 | 22 | //[DataMember(Name = "serverAddress")] 23 | //public string ServerAddress{ get; set; } 24 | 25 | [DataMember(Name = "createMillisTime" ,Order = 2)] 26 | public long CreateMillisTime{ get; set; } 27 | 28 | 29 | [DataMember(Name = "accessToken" ,Order = 3)] 30 | public string AccessToken{ get; set; } 31 | 32 | [DataMember(Name = "className" ,Order = 4)] 33 | public string ClassName{ get; set; } 34 | 35 | [DataMember(Name = "methodName" ,Order = 5)] 36 | public string MethodName{ get; set; } 37 | 38 | [DataMember(Name = "version" ,Order = 6)] 39 | public string Version{ get; set; } 40 | 41 | [DataMember(Name = "parameterTypes",Order = 7)] 42 | public IList ParameterTypes{ get; set; } 43 | 44 | 45 | [DataMember(Name = "parameters",Order = 8)] 46 | public IList Parameters{ get; set; } 47 | 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Rules in this file were initially inferred by Visual Studio IntelliCode from the NetEscapades.AspNetCore.SecurityHeaders codebase based on best match to current usage at 16/11/2018 2 | # You can modify the rules from these initially generated values to suit your own policies 3 | # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference 4 | [*.cs] 5 | 6 | #Core editorconfig formatting - indentation 7 | 8 | #use soft tabs (spaces) for indentation 9 | indent_style = space 10 | 11 | #Formatting - indentation options 12 | 13 | #indent switch case contents. 14 | csharp_indent_case_contents = true 15 | #indent switch labels 16 | csharp_indent_switch_labels = true 17 | 18 | #Formatting - new line options 19 | 20 | #require braces to be on a new line for types, object_collection, methods, control_blocks, and lambdas (also known as "Allman" style) 21 | csharp_new_line_before_open_brace = types, object_collection, methods, control_blocks, lambdas 22 | 23 | #Formatting - organize using options 24 | 25 | #sort System.* using directives alphabetically, and place them before other usings 26 | dotnet_sort_system_directives_first = true 27 | 28 | ... 29 | 30 | #Style - qualification options 31 | 32 | #prefer fields not to be prefaced with this. or Me. in Visual Basic 33 | dotnet_style_qualification_for_field = false:suggestion 34 | #prefer methods not to be prefaced with this. or Me. in Visual Basic 35 | dotnet_style_qualification_for_method = false:suggestion 36 | #prefer properties not to be prefaced with this. or Me. in Visual Basic 37 | dotnet_style_qualification_for_property = false:suggestion -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Model/TriggerParam.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace DotXxlJob.Core.Model 4 | { 5 | 6 | [DataContract] 7 | public class TriggerParam 8 | { 9 | //static readonly long SerialVersionUID = 42L; 10 | 11 | [DataMember(Name = "jobId", Order = 1)] 12 | public int JobId { get; set; } 13 | 14 | [DataMember(Name = "executorHandler", Order = 2)] 15 | public string ExecutorHandler { get; set; } 16 | [DataMember(Name = "executorParams", Order = 3)] 17 | public string ExecutorParams{ get; set; } 18 | 19 | [DataMember(Name = "executorBlockStrategy", Order = 4)] 20 | public string ExecutorBlockStrategy{ get; set; } 21 | 22 | [DataMember(Name = "executorTimeout", Order = 5)] 23 | public int ExecutorTimeout{ get; set; } 24 | 25 | [DataMember(Name = "logId",Order = 5)] 26 | public long LogId { get; set; } 27 | [DataMember(Name = "logDateTime", Order = 6)] 28 | public long LogDateTime{ get; set; } 29 | 30 | 31 | [DataMember(Name = "glueType",Order = 7)] 32 | public string GlueType{ get; set; } 33 | 34 | [DataMember(Name = "glueSource",Order = 8)] 35 | public string GlueSource{ get; set; } 36 | 37 | [DataMember(Name = "glueUpdatetime", Order = 9)] 38 | public long GlueUpdateTime{ get; set; } 39 | 40 | [DataMember(Name = "broadcastIndex",Order = 10)] 41 | public int BroadcastIndex{ get; set; } 42 | [DataMember(Name = "broadcastTotal",Order = 11)] 43 | public int BroadcastTotal{ get; set; } 44 | } 45 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Config/XxlJobExecutorOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace DotXxlJob.Core.Config 5 | { 6 | public class XxlJobExecutorOptions 7 | { 8 | 9 | /// 10 | /// 管理端地址,多个以;分隔 11 | /// 12 | public string AdminAddresses { get; set; } 13 | 14 | 15 | /// 16 | /// appName自动注册时要去管理端配置一致 17 | /// 18 | public string AppName { get; set; } = "xxl-job-executor-dotnet"; 19 | 20 | 21 | 22 | /// 23 | /// 绑定的特殊的URL,如果该项配置存在,则忽略SpecialBindAddress和Port 24 | /// 25 | public string SpecialBindUrl { get; set; } 26 | 27 | /// 28 | /// 自动注册时提交的地址,为空会自动获取内网地址 29 | /// 30 | public string SpecialBindAddress { get; set; } 31 | 32 | 33 | /// 34 | /// 绑定端口 35 | /// 36 | public int Port { get; set; } 37 | 38 | /// 39 | /// 是否自动注册 40 | /// 41 | public bool AutoRegistry { get; set; } 42 | 43 | 44 | /// 45 | /// 认证票据 46 | /// 47 | public string AccessToken { get; set; } 48 | 49 | 50 | /// 51 | /// 日志目录,默认为执行目录的logs子目录下,请配置绝对路径 52 | /// 53 | public string LogPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "./logs"); 54 | 55 | 56 | /// 57 | /// 日志保留天数 58 | /// 59 | public int LogRetentionDays { get; set; } = 30; 60 | 61 | 62 | public int CallBackInterval { get; set; } = 500; //回调时间间隔 500毫秒 63 | 64 | } 65 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Extensions/DateTimeExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace DotXxlJob.Core.Extensions 6 | { 7 | public static class DateTimeExtension 8 | { 9 | private const long Era = 62135596800000L; 10 | private const long Millis = 60000; 11 | 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | public static long GetTotalMilliseconds(this DateTime dt) 18 | { 19 | return dt.ToUniversalTime().Ticks / 10000 - Era; 20 | } 21 | 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | public static int GetTotalMinutes(this DateTime dt) 28 | { 29 | var val = GetTotalMilliseconds(dt); 30 | return (int)(val / Millis); 31 | } 32 | 33 | /// 34 | /// 35 | /// 36 | /// 37 | /// 38 | public static DateTime FromMinutes(this int value) 39 | { 40 | var ticks = (value * Millis + Era) * 10000; 41 | return new DateTime(ticks, DateTimeKind.Utc); 42 | } 43 | 44 | /// 45 | /// 46 | /// 47 | /// 48 | /// 49 | public static DateTime FromMilliseconds(this long value) 50 | { 51 | var ticks = (value + Era) * 10000; 52 | return new DateTime(ticks, DateTimeKind.Utc); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/DefaultJobHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace DotXxlJob.Core 8 | { 9 | public class DefaultJobHandlerFactory:IJobHandlerFactory 10 | { 11 | private readonly IServiceProvider _provider; 12 | private readonly Dictionary handlersCache = new Dictionary(); 13 | public DefaultJobHandlerFactory(IServiceProvider provider) 14 | { 15 | this._provider = provider; 16 | Initialize(); 17 | } 18 | 19 | private void Initialize() 20 | { 21 | var list = this._provider.GetServices(); 22 | if (list == null || !list.Any()) 23 | { 24 | throw new TypeLoadException("IJobHandlers are not found in IServiceCollection"); 25 | } 26 | 27 | foreach (var handler in list) 28 | { 29 | var jobHandlerAttr = handler.GetType().GetCustomAttribute(); 30 | var handlerName = jobHandlerAttr == null ? handler.GetType().Name : jobHandlerAttr.Name; 31 | if (handlersCache.ContainsKey(handlerName)) 32 | { 33 | throw new Exception($"same IJobHandler' name: [{handlerName}]"); 34 | } 35 | handlersCache.Add(handlerName,handler); 36 | } 37 | 38 | } 39 | 40 | public IJobHandler GetJobHandler(string handlerName) 41 | { 42 | if (handlersCache.ContainsKey(handlerName)) 43 | { 44 | return handlersCache[handlerName]; 45 | } 46 | return null; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/DotXxlJob.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | netstandard2.0 6 | $(DefineConstants);DOTNETCORE 7 | XxlJobExecutor DotNet port 8 | Xuanye @ 2019 9 | Xuanye 10 | XxlJobExecutor DotNet port 11 | DotXxlJob.Core 12 | DotXxlJob.Core 13 | $(DotXxlJobPackageVersion) 14 | Hession,xxl-job,DotXxlJob 15 | 16 | $(DotXxlJobPackageNotes) 17 | 18 | https://github.com/xuanye/DotXxlJob 19 | https://github.com/xuanye/DotXxlJob/blob/master/LICENSE 20 | false 21 | git 22 | https://github.com/xuanye/DotXxlJob 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /samples/ASPNetCoreExecutor/Startup.cs: -------------------------------------------------------------------------------- 1 | using DotXxlJob.Core; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.AspNetCore.Server.Kestrel.Core; 8 | 9 | namespace ASPNetCoreExecutor 10 | { 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | private IConfiguration Configuration { get; } 19 | 20 | // This method gets called by the runtime. Use this method to add services to the container. 21 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 22 | public void ConfigureServices(IServiceCollection services) 23 | { 24 | 25 | services.AddXxlJobExecutor(Configuration); 26 | services.AddDefaultXxlJobHandlers();// add httpHandler; 27 | 28 | services.AddSingleton(); // 添加自定义的jobHandler 29 | 30 | services.AddAutoRegistry(); // 自动注册 31 | 32 | 33 | //services.Configure(x => x.AllowSynchronousIO = true) 34 | // .Configure(x=> x.AllowSynchronousIO = true); 35 | } 36 | 37 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 38 | public void Configure(IApplicationBuilder app,IWebHostEnvironment env) 39 | { 40 | if (env.EnvironmentName !="Production") 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | 45 | //启用XxlExecutor 46 | app.UseXxlJobExecutor(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/samples/ASPNetCoreExecutor/bin/Debug/netcoreapp2.2/ASPNetCoreExecutor.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/samples/ASPNetCoreExecutor", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "${auto-detect-url}", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start ${auto-detect-url}" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ,] 46 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Json/ProjectDefaultResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Utf8Json; 3 | using Utf8Json.Formatters; 4 | using Utf8Json.Resolvers; 5 | 6 | namespace DotXxlJob.Core.Json 7 | { 8 | public class ProjectDefaultResolver : IJsonFormatterResolver 9 | { 10 | public static IJsonFormatterResolver Instance = new ProjectDefaultResolver(); 11 | 12 | // configure your resolver and formatters. 13 | static readonly IJsonFormatter[] formatters = { 14 | new DateTimeFormatter("yyyy-MM-dd HH:mm:ss"), 15 | new NullableDateTimeFormatter("yyyy-MM-dd HH:mm:ss") 16 | }; 17 | 18 | static readonly IJsonFormatterResolver[] resolvers = new[] 19 | { 20 | EnumResolver.UnderlyingValue, 21 | StandardResolver.AllowPrivateExcludeNullSnakeCase 22 | }; 23 | 24 | ProjectDefaultResolver() 25 | { 26 | } 27 | 28 | public IJsonFormatter GetFormatter() 29 | { 30 | return FormatterCache.formatter; 31 | } 32 | 33 | static class FormatterCache 34 | { 35 | public static readonly IJsonFormatter formatter; 36 | 37 | static FormatterCache() 38 | { 39 | foreach (var item in formatters) 40 | { 41 | foreach (var implInterface in item.GetType().GetTypeInfo().ImplementedInterfaces) 42 | { 43 | var ti = implInterface.GetTypeInfo(); 44 | if (ti.IsGenericType && ti.GenericTypeArguments[0] == typeof(T)) 45 | { 46 | formatter = (IJsonFormatter)item; 47 | return; 48 | } 49 | } 50 | } 51 | 52 | foreach (var item in resolvers) 53 | { 54 | var f = item.GetFormatter(); 55 | if (f != null) 56 | { 57 | formatter = f; 58 | return; 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Internal/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DotXxlJob.Core 4 | { 5 | internal static class Constants 6 | { 7 | public const string RpcRequestJavaFullName = "com.xxl.rpc.remoting.net.params.XxlRpcRequest"; 8 | public const string RpcResponseJavaFullName = "com.xxl.rpc.remoting.net.params.XxlRpcResponse"; 9 | 10 | public const string RegistryParamJavaFullName = "com.xxl.job.core.biz.model.RegistryParam"; 11 | public const string ReturnTJavaFullName = "com.xxl.job.core.biz.model.ReturnT"; 12 | 13 | public const string TriggerParamJavaFullName = "com.xxl.job.core.biz.model.TriggerParam"; 14 | public const string HandleCallbackParamJavaFullName = "com.xxl.job.core.biz.model.HandleCallbackParam"; 15 | public const string LogResultJavaFullName = "com.xxl.job.core.biz.model.LogResult"; 16 | 17 | 18 | public const string JavaClassFulName = "java.lang.Class"; 19 | public const string JavaListFulName = "java.util.List"; 20 | 21 | public const string XxlJobRetryLogsFile = "xxl-job-retry.log"; 22 | public const string HandleLogsDirectory = "HandlerLogs"; 23 | 24 | public const string DefaultHttpClientName = "DotXxlJobHttpClient"; 25 | 26 | public const int DefaultLogRetentionDays = 30; 27 | 28 | public static TimeSpan RpcRequestExpireTimeSpan = TimeSpan.FromMinutes(3); 29 | 30 | public static TimeSpan RegistryInterval = TimeSpan.FromSeconds(60); 31 | 32 | public const int MaxCallbackRetryTimes = 10; 33 | //每次回调最多发送几条记录 34 | public const int MaxCallbackRecordsPerRequest =5; 35 | public static TimeSpan CallbackRetryInterval = TimeSpan.FromSeconds(600); 36 | 37 | //Admin集群机器请求默认超时时间 38 | //public static TimeSpan AdminServerDefaultTimeout = TimeSpan.FromSeconds(15); 39 | //Admin集群中的某台机器熔断后间隔多长时间再重试 40 | public static TimeSpan AdminServerReconnectInterval = TimeSpan.FromMinutes(3); 41 | //Admin集群中的某台机器请求失败多少次后熔断 42 | public const int AdminServerCircuitFailedTimes = 3; 43 | 44 | 45 | 46 | public static class GlueType 47 | { 48 | public const string BEAN = "BEAN"; 49 | } 50 | 51 | public static class ExecutorBlockStrategy 52 | { 53 | public const string SERIAL_EXECUTION = "SERIAL_EXECUTION"; 54 | 55 | public const string DISCARD_LATER = "DISCARD_LATER"; 56 | 57 | public const string COVER_EARLY = "COVER_EARLY"; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/DefaultHandlers/SimpleHttpJobHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | using DotXxlJob.Core.Model; 7 | 8 | namespace DotXxlJob.Core.DefaultHandlers 9 | { 10 | [JobHandler("simpleHttpJobHandler")] 11 | public class SimpleHttpJobHandler:AbsJobHandler 12 | { 13 | private readonly IHttpClientFactory _httpClientFactory; 14 | 15 | private static readonly Regex UrlPattern = 16 | new Regex(@"(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"); 17 | public SimpleHttpJobHandler(IHttpClientFactory httpClientFactory) 18 | { 19 | this._httpClientFactory = httpClientFactory; 20 | } 21 | public override async Task Execute(JobExecuteContext context) 22 | { 23 | if (string.IsNullOrEmpty(context.JobParameter)) 24 | { 25 | return ReturnT.Failed("url is empty"); 26 | } 27 | 28 | string url = context.JobParameter; 29 | 30 | if (!UrlPattern.IsMatch(url)) 31 | { 32 | return ReturnT.Failed("url format is not valid"); 33 | } 34 | context.JobLogger.Log("Get Request Data:{0}",context.JobParameter); 35 | using (var client = this._httpClientFactory.CreateClient(Constants.DefaultHttpClientName)) 36 | { 37 | try 38 | { 39 | var response = await client.GetAsync(url); 40 | if (response == null) 41 | { 42 | context.JobLogger.Log("call remote error,response is null"); 43 | return ReturnT.Failed("call remote error,response is null"); 44 | } 45 | 46 | if (response.StatusCode != HttpStatusCode.OK) 47 | { 48 | context.JobLogger.Log("call remote error,response statusCode ={0}",response.StatusCode); 49 | return ReturnT.Failed("call remote error,response statusCode ="+response.StatusCode); 50 | } 51 | 52 | string body = await response.Content.ReadAsStringAsync(); 53 | context.JobLogger.Log("
call remote success ,response is :
{0}",body); 54 | return ReturnT.SUCCESS; 55 | } 56 | catch (Exception ex) 57 | { 58 | context.JobLogger.LogError(ex); 59 | return ReturnT.Failed(ex.Message); 60 | } 61 | 62 | } 63 | } 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DotXxlJob.Core.Config; 3 | using DotXxlJob.Core.DefaultHandlers; 4 | using DotXxlJob.Core.Queue; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | using Microsoft.Extensions.Hosting; 9 | 10 | namespace DotXxlJob.Core 11 | { 12 | public static class ServiceCollectionExtensions 13 | { 14 | public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services,IConfiguration configuration) 15 | { 16 | services.AddLogging(); 17 | services.AddOptions(); 18 | services.Configure(configuration.GetSection("xxlJob")) 19 | .AddXxlJobExecutorServiceDependency(); 20 | 21 | return services; 22 | } 23 | public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services,Action configAction) 24 | { 25 | services.AddLogging(); 26 | services.AddOptions(); 27 | services.Configure(configAction).AddXxlJobExecutorServiceDependency(); 28 | return services; 29 | } 30 | 31 | public static IServiceCollection AddDefaultXxlJobHandlers(this IServiceCollection services) 32 | { 33 | services.AddSingleton(); 34 | return services; 35 | } 36 | public static IServiceCollection AddAutoRegistry(this IServiceCollection services) 37 | { 38 | services.AddSingleton() 39 | .AddSingleton(); 40 | return services; 41 | } 42 | 43 | private static IServiceCollection AddXxlJobExecutorServiceDependency(this IServiceCollection services) 44 | { 45 | 46 | //可在外部提前注册对应实现,并替换默认实现 47 | services.TryAddSingleton(); 48 | services.TryAddSingleton(); 49 | services.TryAddSingleton(); 50 | 51 | services.AddHttpClient("DotXxlJobClient"); 52 | services.AddSingleton(); 53 | services.AddSingleton(); 54 | services.AddSingleton(); 55 | services.AddSingleton(); 56 | services.AddSingleton(); 57 | services.AddSingleton(); 58 | 59 | return services; 60 | } 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Internal/Preconditions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DotXxlJob.Core 4 | { 5 | /// 6 | /// Utility methods to simplify checking preconditions in the code. 7 | /// 8 | internal static class Preconditions 9 | { 10 | /// 11 | /// Throws if condition is false. 12 | /// 13 | /// The condition. 14 | public static void CheckArgument(bool condition) 15 | { 16 | if (!condition) 17 | { 18 | throw new ArgumentException(); 19 | } 20 | } 21 | 22 | /// 23 | /// Throws with given message if condition is false. 24 | /// 25 | /// The condition. 26 | /// The error message. 27 | public static void CheckArgument(bool condition, string errorMessage) 28 | { 29 | if (!condition) 30 | { 31 | throw new ArgumentException(errorMessage); 32 | } 33 | } 34 | 35 | /// 36 | /// Throws if reference is null. 37 | /// 38 | /// The reference. 39 | public static T CheckNotNull(T reference) 40 | { 41 | if (reference == null) 42 | { 43 | throw new ArgumentNullException(); 44 | } 45 | return reference; 46 | } 47 | 48 | /// 49 | /// Throws if reference is null. 50 | /// 51 | /// The reference. 52 | /// The parameter name. 53 | public static T CheckNotNull(T reference, string paramName) 54 | { 55 | if (reference == null) 56 | { 57 | throw new ArgumentNullException(paramName); 58 | } 59 | return reference; 60 | } 61 | 62 | /// 63 | /// Throws if condition is false. 64 | /// 65 | /// The condition. 66 | public static void CheckState(bool condition) 67 | { 68 | if (!condition) 69 | { 70 | throw new InvalidOperationException(); 71 | } 72 | } 73 | 74 | /// 75 | /// Throws with given message if condition is false. 76 | /// 77 | /// The condition. 78 | /// The error message. 79 | public static void CheckState(bool condition, string errorMessage) 80 | { 81 | if (!condition) 82 | { 83 | throw new InvalidOperationException(errorMessage); 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/ExecutorRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using DotXxlJob.Core.Config; 5 | using DotXxlJob.Core.Model; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | 9 | namespace DotXxlJob.Core 10 | { 11 | /// 12 | /// 执行器注册注册 13 | /// 14 | public class ExecutorRegistry : IExecutorRegistry 15 | { 16 | private readonly AdminClient _adminClient; 17 | private readonly XxlJobExecutorOptions _options; 18 | private readonly ILogger _logger; 19 | 20 | public ExecutorRegistry(AdminClient adminClient, IOptions optionsAccessor, ILogger logger) 21 | { 22 | Preconditions.CheckNotNull(optionsAccessor, "XxlJobExecutorOptions"); 23 | Preconditions.CheckNotNull(optionsAccessor.Value, "XxlJobExecutorOptions"); 24 | _adminClient = adminClient; 25 | _options = optionsAccessor.Value; 26 | if (string.IsNullOrEmpty(_options.SpecialBindAddress)) 27 | { 28 | _options.SpecialBindAddress = IPUtility.GetLocalIntranetIP().MapToIPv4().ToString(); 29 | } 30 | _logger = logger; 31 | } 32 | 33 | public async Task RegistryAsync(CancellationToken cancellationToken) 34 | { 35 | var registryParam = new RegistryParam { 36 | RegistryGroup = "EXECUTOR", 37 | RegistryKey = _options.AppName, 38 | RegistryValue = string.IsNullOrEmpty(_options.SpecialBindUrl)? 39 | $"http://{_options.SpecialBindAddress}:{_options.Port}/" : _options.SpecialBindUrl 40 | }; 41 | 42 | _logger.LogInformation(">>>>>>>> start registry to admin <<<<<<<<"); 43 | 44 | var errorTimes = 0; 45 | 46 | while (!cancellationToken.IsCancellationRequested) 47 | { 48 | try 49 | { 50 | var ret = await _adminClient.Registry(registryParam); 51 | _logger.LogDebug("registry last result:{0}", ret?.Code); 52 | errorTimes = 0; 53 | await Task.Delay(Constants.RegistryInterval, cancellationToken); 54 | } 55 | catch (TaskCanceledException) 56 | { 57 | _logger.LogInformation(">>>>> Application Stopping....<<<<<"); 58 | } 59 | catch (Exception ex) 60 | { 61 | errorTimes++; 62 | await Task.Delay(Constants.RegistryInterval, cancellationToken); 63 | _logger.LogError(ex, "registry error:{0},{1} Times", ex.Message, errorTimes); 64 | } 65 | } 66 | 67 | _logger.LogInformation(">>>>>>>> end registry to admin <<<<<<<<"); 68 | 69 | _logger.LogInformation(">>>>>>>> start remove registry to admin <<<<<<<<"); 70 | 71 | var removeRet = await this._adminClient.RegistryRemove(registryParam); 72 | _logger.LogInformation("remove registry last result:{0}", removeRet?.Code); 73 | _logger.LogInformation(">>>>>>>> end remove registry to admin <<<<<<<<"); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/AdminClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | using DotXxlJob.Core.Config; 9 | using DotXxlJob.Core.Model; 10 | using Flurl; 11 | using Flurl.Http; 12 | using Microsoft.Extensions.Logging; 13 | using Microsoft.Extensions.Options; 14 | using Newtonsoft.Json; 15 | 16 | namespace DotXxlJob.Core 17 | { 18 | public class AdminClient 19 | { 20 | 21 | private readonly XxlJobExecutorOptions _options; 22 | private readonly ILogger _logger; 23 | private List _addresses; 24 | private int _currentIndex; 25 | private static readonly string MAPPING = "/api"; 26 | public AdminClient(IOptions optionsAccessor 27 | , ILogger logger) 28 | { 29 | Preconditions.CheckNotNull(optionsAccessor?.Value, "XxlJobExecutorOptions"); 30 | 31 | this._options = optionsAccessor?.Value; 32 | this._logger = logger; 33 | InitAddress(); 34 | } 35 | 36 | private void InitAddress() 37 | { 38 | this._addresses = new List(); 39 | foreach (var item in this._options.AdminAddresses.Split(';')) 40 | { 41 | try 42 | { 43 | var entry = new AddressEntry { RequestUri = item+ MAPPING }; 44 | this._addresses.Add(entry); 45 | } 46 | catch (Exception ex) 47 | { 48 | this._logger.LogError(ex, "init admin address error."); 49 | } 50 | } 51 | } 52 | 53 | public Task Callback(List callbackParamList) 54 | { 55 | 56 | return InvokeRpcService("callback", callbackParamList); 57 | } 58 | 59 | public Task Registry(RegistryParam registryParam) 60 | { 61 | return InvokeRpcService("registry", registryParam); 62 | } 63 | 64 | public Task RegistryRemove(RegistryParam registryParam) 65 | { 66 | return InvokeRpcService("registryRemove", registryParam); 67 | } 68 | 69 | 70 | private async Task InvokeRpcService(string methodName, object jsonObject) 71 | { 72 | var triedTimes = 0; 73 | ReturnT ret = null; 74 | 75 | while (triedTimes++ < this._addresses.Count) 76 | { 77 | 78 | var address = this._addresses[this._currentIndex]; 79 | this._currentIndex = (this._currentIndex + 1) % this._addresses.Count; 80 | if (!address.CheckAccessible()) 81 | continue; 82 | try 83 | { 84 | 85 | var json = await address.RequestUri.AppendPathSegment(methodName) 86 | .WithHeader("XXL-JOB-ACCESS-TOKEN", this._options.AccessToken) 87 | .PostJsonAsync(jsonObject) 88 | .ReceiveString(); 89 | 90 | //.ReceiveJson(); 91 | ret = JsonConvert.DeserializeObject(json); 92 | address.Reset(); 93 | } 94 | catch (Exception ex) 95 | { 96 | this._logger.LogError(ex, "request admin error.{0}", ex.Message); 97 | address.SetFail(); 98 | continue; 99 | } 100 | } 101 | if(ret == null) 102 | { 103 | ret = ReturnT.Failed("call admin fail"); 104 | } 105 | return ret; 106 | } 107 | 108 | } 109 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Queue/CallbackTaskQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using DotXxlJob.Core.Config; 7 | using DotXxlJob.Core.Model; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.Options; 10 | 11 | namespace DotXxlJob.Core.Queue 12 | { 13 | public class CallbackTaskQueue:IDisposable 14 | { 15 | private readonly AdminClient _adminClient; 16 | private readonly IJobLogger _jobLogger; 17 | private readonly RetryCallbackTaskQueue _retryQueue; 18 | private readonly ILogger _logger; 19 | private readonly ConcurrentQueue taskQueue = new ConcurrentQueue(); 20 | 21 | private bool _stop; 22 | 23 | private bool _isRunning; 24 | 25 | private int _callbackInterval; 26 | 27 | private Task _runTask; 28 | public CallbackTaskQueue(AdminClient adminClient,IJobLogger jobLogger,IOptions optionsAccessor 29 | , ILoggerFactory loggerFactory) 30 | { 31 | _adminClient = adminClient; 32 | _jobLogger = jobLogger; 33 | 34 | _callbackInterval = optionsAccessor.Value.CallBackInterval; 35 | 36 | _retryQueue = new RetryCallbackTaskQueue(optionsAccessor.Value.LogPath, 37 | Push, 38 | loggerFactory.CreateLogger()); 39 | 40 | _logger = loggerFactory.CreateLogger(); 41 | } 42 | 43 | public void Push(HandleCallbackParam callbackParam) 44 | { 45 | taskQueue.Enqueue(callbackParam); 46 | StartCallBack(); 47 | } 48 | 49 | 50 | public void Dispose() 51 | { 52 | _stop = true; 53 | _retryQueue.Dispose(); 54 | _runTask?.GetAwaiter().GetResult(); 55 | } 56 | 57 | 58 | private void StartCallBack() 59 | { 60 | if ( _isRunning) 61 | { 62 | return; 63 | } 64 | 65 | _runTask = Task.Run(async () => 66 | { 67 | _logger.LogDebug("start to callback"); 68 | _isRunning = true; 69 | while (!_stop) 70 | { 71 | await DoCallBack(); 72 | if (taskQueue.IsEmpty) 73 | { 74 | await Task.Delay(TimeSpan.FromMilliseconds(_callbackInterval)); 75 | } 76 | } 77 | _logger.LogDebug("end to callback"); 78 | _isRunning = false; 79 | }); 80 | 81 | } 82 | 83 | private async Task DoCallBack() 84 | { 85 | List list = new List(); 86 | 87 | if(!taskQueue.TryDequeue(out var item)) 88 | { 89 | return; 90 | } 91 | 92 | list.Add(item); 93 | 94 | ReturnT result; 95 | try 96 | { 97 | result = await _adminClient.Callback(list).ConfigureAwait(false); 98 | } 99 | catch (Exception ex){ 100 | _logger.LogError(ex,"trigger callback error:{error}",ex.Message); 101 | result = ReturnT.Failed(ex.Message); 102 | _retryQueue.Push(list); 103 | } 104 | 105 | LogCallBackResult(result, list); 106 | } 107 | 108 | private void LogCallBackResult(ReturnT result,List list) 109 | { 110 | foreach (var param in list) 111 | { 112 | _jobLogger.LogSpecialFile(param.LogDateTime, param.LogId, result.Msg??"Success"); 113 | } 114 | } 115 | 116 | 117 | } 118 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Internal/IPUtility.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Net.NetworkInformation; 5 | using System.Net.Sockets; 6 | 7 | namespace DotXxlJob.Core 8 | { 9 | /// 10 | /// ip utility 11 | /// 12 | internal static class IPUtility 13 | { 14 | #region Private Members 15 | /// 16 | /// A类: 10.0.0.0-10.255.255.255 17 | /// 18 | private static long ipABegin, ipAEnd; 19 | /// 20 | /// B类: 172.16.0.0-172.31.255.255 21 | /// 22 | private static long ipBBegin, ipBEnd; 23 | /// 24 | /// C类: 192.168.0.0-192.168.255.255 25 | /// 26 | private static long ipCBegin, ipCEnd; 27 | #endregion 28 | 29 | #region Constructors 30 | /// 31 | /// static new 32 | /// 33 | static IPUtility() 34 | { 35 | ipABegin = ConvertToNumber("10.0.0.0"); 36 | ipAEnd = ConvertToNumber("10.255.255.255"); 37 | 38 | ipBBegin = ConvertToNumber("172.16.0.0"); 39 | ipBEnd = ConvertToNumber("172.31.255.255"); 40 | 41 | ipCBegin = ConvertToNumber("192.168.0.0"); 42 | ipCEnd = ConvertToNumber("192.168.255.255"); 43 | } 44 | #endregion 45 | 46 | #region Public Methods 47 | /// 48 | /// ip address convert to long 49 | /// 50 | /// 51 | /// 52 | private static long ConvertToNumber(string ipAddress) 53 | { 54 | return ConvertToNumber(IPAddress.Parse(ipAddress)); 55 | } 56 | /// 57 | /// ip address convert to long 58 | /// 59 | /// 60 | /// 61 | private static long ConvertToNumber(IPAddress ipAddress) 62 | { 63 | var bytes = ipAddress.GetAddressBytes(); 64 | return bytes[0] * 256 * 256 * 256 + bytes[1] * 256 * 256 + bytes[2] * 256 + bytes[3]; 65 | } 66 | /// 67 | /// true表示为内网IP 68 | /// 69 | /// 70 | /// 71 | public static bool IsIntranet(string ipAddress) 72 | { 73 | return IsIntranet(ConvertToNumber(ipAddress)); 74 | } 75 | /// 76 | /// true表示为内网IP 77 | /// 78 | /// 79 | /// 80 | private static bool IsIntranet(IPAddress ipAddress) 81 | { 82 | return IsIntranet(ConvertToNumber(ipAddress)); 83 | } 84 | /// 85 | /// true表示为内网IP 86 | /// 87 | /// 88 | /// 89 | private static bool IsIntranet(long longIP) 90 | { 91 | return ((longIP >= ipABegin) && (longIP <= ipAEnd) || 92 | (longIP >= ipBBegin) && (longIP <= ipBEnd) || 93 | (longIP >= ipCBegin) && (longIP <= ipCEnd)); 94 | } 95 | /// 96 | /// 获取本机内网IP 97 | /// 98 | /// 99 | public static IPAddress GetLocalIntranetIP() 100 | { 101 | return NetworkInterface 102 | .GetAllNetworkInterfaces() 103 | .Select(p => p.GetIPProperties()) 104 | .SelectMany(p => 105 | p.UnicastAddresses 106 | ).FirstOrDefault(p => p.Address.AddressFamily == AddressFamily.InterNetwork 107 | && !IPAddress.IsLoopback(p.Address) 108 | && IsIntranet(p.Address))?.Address; 109 | } 110 | /// 111 | /// 获取本机内网IP列表 112 | /// 113 | /// 114 | public static List GetLocalIntranetIPList() 115 | { 116 | var infList =NetworkInterface.GetAllNetworkInterfaces() 117 | .Select(p => p.GetIPProperties()) 118 | .SelectMany(p => p.UnicastAddresses) 119 | .Where(p => 120 | p.Address.AddressFamily == AddressFamily.InterNetwork 121 | && !IPAddress.IsLoopback(p.Address) 122 | && IsIntranet(p.Address) 123 | ); 124 | 125 | var result = new List(); 126 | foreach (var child in infList) 127 | { 128 | result.Add(child.Address); 129 | } 130 | 131 | return result; 132 | } 133 | #endregion 134 | } 135 | } -------------------------------------------------------------------------------- /DotXxlJob.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30104.148 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{97756BA5-1E7C-4536-A49E-AE2190C0E6A5}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotXxlJob.Core", "src\DotXxlJob.Core\DotXxlJob.Core.csproj", "{FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{352EC932-F112-4A2F-9DC3-F0761C85E068}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{E959F9B5-F3EB-48B1-B842-2CDDFDB01900}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASPNetCoreExecutor", "samples\ASPNetCoreExecutor\ASPNetCoreExecutor.csproj", "{DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotXxlJob.Core.Tests", "tests\DotXxlJob.Core.Tests\DotXxlJob.Core.Tests.csproj", "{81C60471-7C1C-48CE-98C0-F252C267AC9F}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Debug|x64 = Debug|x64 22 | Debug|x86 = Debug|x86 23 | Release|Any CPU = Release|Any CPU 24 | Release|x64 = Release|x64 25 | Release|x86 = Release|x86 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Debug|x64.ActiveCfg = Debug|Any CPU 31 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Debug|x64.Build.0 = Debug|Any CPU 32 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Debug|x86.ActiveCfg = Debug|Any CPU 33 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Debug|x86.Build.0 = Debug|Any CPU 34 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Release|x64.ActiveCfg = Release|Any CPU 37 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Release|x64.Build.0 = Release|Any CPU 38 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Release|x86.ActiveCfg = Release|Any CPU 39 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Release|x86.Build.0 = Release|Any CPU 40 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|x64.Build.0 = Debug|Any CPU 44 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|x86.ActiveCfg = Debug|Any CPU 45 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|x86.Build.0 = Debug|Any CPU 46 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|x64.ActiveCfg = Release|Any CPU 49 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|x64.Build.0 = Release|Any CPU 50 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|x86.ActiveCfg = Release|Any CPU 51 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|x86.Build.0 = Release|Any CPU 52 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|x64.ActiveCfg = Debug|Any CPU 55 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|x64.Build.0 = Debug|Any CPU 56 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|x86.ActiveCfg = Debug|Any CPU 57 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|x86.Build.0 = Debug|Any CPU 58 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|x64.ActiveCfg = Release|Any CPU 61 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|x64.Build.0 = Release|Any CPU 62 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|x86.ActiveCfg = Release|Any CPU 63 | {81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|x86.Build.0 = Release|Any CPU 64 | EndGlobalSection 65 | GlobalSection(SolutionProperties) = preSolution 66 | HideSolutionNode = FALSE 67 | EndGlobalSection 68 | GlobalSection(NestedProjects) = preSolution 69 | {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65} = {97756BA5-1E7C-4536-A49E-AE2190C0E6A5} 70 | {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5} = {E959F9B5-F3EB-48B1-B842-2CDDFDB01900} 71 | {81C60471-7C1C-48CE-98C0-F252C267AC9F} = {352EC932-F112-4A2F-9DC3-F0761C85E068} 72 | EndGlobalSection 73 | GlobalSection(ExtensibilityGlobals) = postSolution 74 | SolutionGuid = {F4A8B63E-6284-4D00-9719-BAB1D955DACF} 75 | EndGlobalSection 76 | EndGlobal 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DotXxlJob 2 | xxl-job的dotnet core 最新执行器实现,支持XXL-JOB 2.2+ 3 | > 注意XXL-JOB 2.0.1版本请使用 1.0.8的执行器实现 ,*xxl-job* 从 2.0.2 到2.2版本又使用了xxl-rpc的新协议,本执行器不做支持,确实需要的朋友请自行fork.. 4 | 5 | ## 1 XXL-JOB概述 6 | [XXL-JOB][1]是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。以下是它的架构图 7 | ![架构图](https://raw.githubusercontent.com/xuxueli/xxl-job/master/doc/images/img_Qohm.png) 8 | 9 | 10 | 11 | ## 2. 关于DotXxlJob产生 12 | 在工作中调研过多个任务调度平台,如Hangfire、基于Quatz.NET的第三方扩展,都与实际的需求有一点差距。 之前一直使用Hangfire,Hangfire的执行器在同步调用业务服务时,如果即时业务服务正在重新部署或者重启,有一定概率会出现死锁,导致CPU100%,后来全部调整为异步,但是这样就无法获得执行结果,这样的设计有蛮大问题,XxlJob的回调机制很好的解决了这个问题。本身如果通过http的方式调用,只要部署springbootd的一个执行器就可以解决问题,但是扩展性较差。所以萌生了实现DotNet版本的执行器的想法,为避免重复造轮子,开始之前也进行过调研,以下仓库[https://github.com/yuniansheng/xxl-job-dotnet][2]给了较大的启发,但是该库只支持1.9版本的xxljob,还有一些其他小问题,所以还是自力更生。 13 | 14 | ## 3. 如何使用 15 | 16 | 目前只实现了BEAN的方式,即直接实现IJobHandler调用的方式,Glue源码的方式实际上实现起来也并不复杂(有需求再说把),或者各位有需求Fork 实现一下 17 | 18 | 可参考sample 19 | 20 | 安装: 21 | 22 | > dotnet add package DotXxlJob.Core 23 | 24 | ### 3.1 在AspNetCore中使用 25 | 26 | 1. 声明一个AspNet的Middleware中间件,并扩展ApplicationBuilder,本质是拦截Post请求,解析Body中的流信息 27 | 28 | ``` 29 | public class XxlJobExecutorMiddleware 30 | { 31 | private readonly IServiceProvider _provider; 32 | private readonly RequestDelegate _next; 33 | 34 | private readonly XxlRestfulServiceHandler _rpcService; 35 | public XxlJobExecutorMiddleware(IServiceProvider provider, RequestDelegate next) 36 | { 37 | this._provider = provider; 38 | this._next = next; 39 | this._rpcService = _provider.GetRequiredService(); 40 | } 41 | 42 | 43 | public async Task Invoke(HttpContext context) 44 | { 45 | string contentType = context.Request.ContentType; 46 | 47 | if ("POST".Equals(context.Request.Method, StringComparison.OrdinalIgnoreCase) 48 | && !string.IsNullOrEmpty(contentType) 49 | && contentType.ToLower().StartsWith("application/json")) 50 | { 51 | 52 | await _rpcService.HandlerAsync(context.Request,context.Response); 53 | 54 | return; 55 | } 56 | 57 | await _next.Invoke(context); 58 | } 59 | } 60 | ``` 61 | 62 | 扩展ApplicationBuilderExtensions,可根据实际情况绑定在特殊的Url Path上 63 | 64 | ``` 65 | public static class ApplicationBuilderExtensions 66 | { 67 | public static IApplicationBuilder UseXxlJobExecutor(this IApplicationBuilder @this) 68 | { 69 | return @this.UseMiddleware(); 70 | } 71 | } 72 | ``` 73 | 74 | 在Startup中添加必要的引用,其中自动注册。 75 | 76 | ``` 77 | public class Startup 78 | { 79 | public Startup(IConfiguration configuration) 80 | { 81 | Configuration = configuration; 82 | } 83 | 84 | private IConfiguration Configuration { get; } 85 | 86 | public void ConfigureServices(IServiceCollection services) 87 | { 88 | 89 | services.AddXxlJobExecutor(Configuration); 90 | services.AddSingleton(); // 添加自定义的jobHandler 91 | services.AddAutoRegistry(); // 自动注册 92 | } 93 | 94 | 95 | public void Configure(IApplicationBuilder app,IHostingEnvironment env) 96 | { 97 | //启用XxlExecutor 98 | app.UseXxlJobExecutor(); 99 | } 100 | } 101 | ``` 102 | 103 | 编写JobHandler,继承AbstractJobHandler或者直接实现接口IJobHandler,通过context.JobLogger 记录执行过程和结果,在AdminWeb上可查看的哦 104 | ``` 105 | [JobHandler("demoJobHandler")] 106 | public class DemoJobHandler:AbstractJobHandler 107 | { 108 | public override Task Execute(JobExecuteContext context) 109 | { 110 | context.JobLogger.Log("receive demo job handler,parameter:{0}",context.JobParameter); 111 | 112 | return Task.FromResult(ReturnT.SUCCESS); 113 | } 114 | } 115 | ``` 116 | 117 | ## 3.2 配置信息 118 | 管理端地址和端口是必填信息,其他根据实际情况,选择配置,配置项说明见下代码中的注释 119 | 120 | ``` 121 | public class XxlJobExecutorOptions 122 | { 123 | 124 | /// 125 | /// 管理端地址,多个以;分隔 126 | /// 127 | public string AdminAddresses { get; set; } 128 | /// 129 | /// appName自动注册时要去管理端配置一致 130 | /// 131 | public string AppName { get; set; } = "xxl-job-executor-dotnet"; 132 | /// 133 | /// 自动注册时提交的地址,为空会自动获取内网地址 134 | /// 135 | public string SpecialBindAddress { get; set; } 136 | /// 137 | /// 绑定端口 138 | /// 139 | public int Port { get; set; } 140 | /// 141 | /// 是否自动注册 142 | /// 143 | public bool AutoRegistry { get; set; } 144 | /// 145 | /// 认证票据 146 | /// 147 | public string AccessToken { get; set; } 148 | /// 149 | /// 日志目录,默认为执行目录的logs子目录下,请配置绝对路径 150 | /// 151 | public string LogPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "./logs"); 152 | /// 153 | /// 日志保留天数 154 | /// 155 | public int LogRetentionDays { get; set; } = 30; 156 | } 157 | ``` 158 | 159 | 160 | ## 其他说明 161 | 注意XXL-JOB 2.0.1版本请使用 1.0.8的执行器实现 162 | 163 | 有任何问题,可Issue反馈 ,最后感谢 xxl-job 164 | 165 | 166 | 167 | [1]: http://www.xuxueli.com/xxl-job 168 | [2]: https://github.com/yuniansheng/xxl-job-dotnet 169 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/JobDispatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using DotXxlJob.Core.Model; 4 | using DotXxlJob.Core.Queue; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace DotXxlJob.Core 8 | { 9 | /// 10 | /// 负责实际的JOB轮询 11 | /// 12 | public class JobDispatcher 13 | { 14 | private readonly TaskExecutorFactory _executorFactory; 15 | private readonly CallbackTaskQueue _callbackTaskQueue; 16 | private readonly IJobLogger _jobLogger; 17 | 18 | private readonly ConcurrentDictionary RUNNING_QUEUE = new ConcurrentDictionary(); 19 | 20 | 21 | private readonly ILogger _jobQueueLogger; 22 | private readonly ILogger _logger; 23 | public JobDispatcher( 24 | TaskExecutorFactory executorFactory, 25 | CallbackTaskQueue callbackTaskQueue, 26 | IJobLogger jobLogger, 27 | ILoggerFactory loggerFactory 28 | ) 29 | { 30 | this. _executorFactory = executorFactory; 31 | this. _callbackTaskQueue = callbackTaskQueue; 32 | this._jobLogger = jobLogger; 33 | 34 | 35 | this._jobQueueLogger = loggerFactory.CreateLogger(); 36 | this._logger = loggerFactory.CreateLogger(); 37 | } 38 | 39 | 40 | /// 41 | /// 尝试移除JobTask 42 | /// 43 | /// 44 | /// 45 | /// 46 | public bool TryRemoveJobTask(int jobId) 47 | { 48 | if (RUNNING_QUEUE.TryGetValue(jobId, out var jobQueue)) 49 | { 50 | jobQueue.Stop(); 51 | return true; 52 | } 53 | return false; 54 | } 55 | 56 | /// 57 | /// 执行队列,并快速返回结果 58 | /// 59 | /// 60 | /// 61 | /// 62 | public ReturnT Execute(TriggerParam triggerParam) 63 | { 64 | 65 | var executor = this._executorFactory.GetTaskExecutor(triggerParam.GlueType); 66 | if (executor == null) 67 | { 68 | return ReturnT.Failed($"glueType[{triggerParam.GlueType}] is not supported "); 69 | } 70 | 71 | // 1. 根据JobId 获取 TaskQueue; 用于判断是否有正在执行的任务 72 | if (RUNNING_QUEUE.TryGetValue(triggerParam.JobId, out var taskQueue)) 73 | { 74 | if (taskQueue.Executor != executor) //任务执行器变更 75 | { 76 | return ChangeJobQueue(triggerParam, executor); 77 | } 78 | } 79 | 80 | if (taskQueue != null) //旧任务还在执行,判断执行策略 81 | { 82 | //丢弃后续的 83 | if (Constants.ExecutorBlockStrategy.DISCARD_LATER == triggerParam.ExecutorBlockStrategy) 84 | { 85 | //存在还没执行完成的任务 86 | if (taskQueue.IsRunning()) 87 | { 88 | return ReturnT.Failed($"block strategy effect:{triggerParam.ExecutorBlockStrategy}"); 89 | } 90 | //否则还是继续做 91 | } 92 | //覆盖较早的 93 | if (Constants.ExecutorBlockStrategy.COVER_EARLY == triggerParam.ExecutorBlockStrategy) 94 | { 95 | return taskQueue.Replace(triggerParam); 96 | } 97 | } 98 | 99 | return PushJobQueue(triggerParam, executor); 100 | 101 | } 102 | 103 | 104 | /// 105 | /// IdleBeat 106 | /// 107 | /// 108 | /// 109 | public ReturnT IdleBeat(int jobId) 110 | { 111 | return RUNNING_QUEUE.ContainsKey(jobId) ? 112 | new ReturnT(ReturnT.FAIL_CODE, "job thread is running or has trigger queue.") 113 | : ReturnT.SUCCESS; 114 | } 115 | 116 | private void TriggerCallback(object sender, HandleCallbackParam callbackParam) 117 | { 118 | this._callbackTaskQueue.Push(callbackParam); 119 | } 120 | 121 | private ReturnT PushJobQueue(TriggerParam triggerParam, ITaskExecutor executor) 122 | { 123 | 124 | if (RUNNING_QUEUE.TryGetValue(triggerParam.JobId,out var jobQueue)) 125 | { 126 | return jobQueue.Push(triggerParam); 127 | } 128 | 129 | //NewJobId 130 | jobQueue = new JobTaskQueue ( executor,this._jobLogger, this._jobQueueLogger); 131 | jobQueue.CallBack += TriggerCallback; 132 | if (RUNNING_QUEUE.TryAdd(triggerParam.JobId, jobQueue)) 133 | { 134 | return jobQueue.Push(triggerParam); 135 | } 136 | return ReturnT.Failed("add running queue executor error"); 137 | } 138 | 139 | private ReturnT ChangeJobQueue(TriggerParam triggerParam, ITaskExecutor executor) 140 | { 141 | 142 | if (RUNNING_QUEUE.TryRemove(triggerParam.JobId, out var oldJobTask)) 143 | { 144 | oldJobTask.CallBack -= TriggerCallback; 145 | oldJobTask.Dispose(); //释放原来的资源 146 | } 147 | 148 | JobTaskQueue jobQueue = new JobTaskQueue ( executor,this._jobLogger, this._jobQueueLogger); 149 | jobQueue.CallBack += TriggerCallback; 150 | if (RUNNING_QUEUE.TryAdd(triggerParam.JobId, jobQueue)) 151 | { 152 | return jobQueue.Push(triggerParam); 153 | } 154 | return ReturnT.Failed(" replace running queue executor error"); 155 | } 156 | 157 | } 158 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/XxlRestfulServiceHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net.Cache; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using DotXxlJob.Core.Config; 8 | using DotXxlJob.Core.Model; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | using Newtonsoft.Json; 13 | 14 | namespace DotXxlJob.Core 15 | { 16 | public class XxlRestfulServiceHandler 17 | { 18 | private readonly JobDispatcher _jobDispatcher; 19 | private readonly IJobLogger _jobLogger; 20 | private readonly ILogger _logger; 21 | private readonly XxlJobExecutorOptions _options; 22 | 23 | public XxlRestfulServiceHandler(IOptions optionsAccessor, 24 | JobDispatcher jobDispatcher, 25 | IJobLogger jobLogger, 26 | ILogger logger) 27 | { 28 | 29 | this._jobDispatcher = jobDispatcher; 30 | this._jobLogger = jobLogger; 31 | this._logger = logger; 32 | 33 | this._options = optionsAccessor.Value; 34 | if (this._options == null) 35 | { 36 | throw new ArgumentNullException(nameof(XxlJobExecutorOptions)); 37 | } 38 | 39 | } 40 | 41 | public async Task HandlerAsync(HttpRequest request,HttpResponse response) 42 | { 43 | 44 | 45 | var path = request.Path.Value ; 46 | 47 | ReturnT ret = null; 48 | var arrParts = path.Split('/'); 49 | var method = arrParts[arrParts.Length - 1].ToLower(); 50 | 51 | if (!string.IsNullOrEmpty(this._options.AccessToken)) 52 | { 53 | var reqToken = ""; 54 | if (request.Headers.TryGetValue("XXL-JOB-ACCESS-TOKEN", out var tokenValues)) 55 | { 56 | reqToken = tokenValues[0].ToString(); 57 | } 58 | if(this._options.AccessToken != reqToken) 59 | { 60 | ret = ReturnT.Failed("ACCESS-TOKEN Auth Fail"); 61 | response.ContentType = "application/json;charset=utf-8"; 62 | await response.WriteAsync(JsonConvert.SerializeObject(ret)); 63 | return; 64 | } 65 | } 66 | try 67 | { 68 | string json = await CollectBody(request.Body); 69 | switch (method) 70 | { 71 | case "beat": 72 | ret = Beat(); 73 | break; 74 | case "idlebeat": 75 | ret = IdleBeat(JsonConvert.DeserializeObject(json)); 76 | break; 77 | case "run": 78 | ret = Run(JsonConvert.DeserializeObject(json)); 79 | break; 80 | case "kill": 81 | ret = Kill(JsonConvert.DeserializeObject(json)); 82 | break; 83 | case "log": 84 | ret = Log(JsonConvert.DeserializeObject(json)); 85 | break; 86 | } 87 | } 88 | catch(Exception ex) 89 | { 90 | this._logger.LogError(ex,"响应出错"+ ex.Message); 91 | ret = ReturnT.Failed("执行器内部错误"); 92 | } 93 | 94 | 95 | if(ret == null) 96 | { 97 | ret = ReturnT.Failed($"method {method} is not impl"); 98 | } 99 | response.ContentType = "application/json;charset=utf-8"; 100 | await response.WriteAsync(JsonConvert.SerializeObject(ret, new JsonSerializerSettings() { StringEscapeHandling = StringEscapeHandling.EscapeNonAscii })); 101 | } 102 | private async Task CollectBody(Stream body) 103 | { 104 | string bodyText; 105 | using (var reader = new StreamReader(body)) 106 | { 107 | bodyText = await reader.ReadToEndAsync(); 108 | } 109 | return bodyText??string.Empty; 110 | } 111 | 112 | #region rpc service 113 | 114 | private ReturnT Beat() 115 | { 116 | return ReturnT.SUCCESS; 117 | } 118 | 119 | private ReturnT IdleBeat(IdleBeatRequest req) 120 | { 121 | if(req == null) 122 | { 123 | return ReturnT.Failed("IdleBeat Error"); 124 | } 125 | return this._jobDispatcher.IdleBeat(req.JobId); 126 | } 127 | 128 | private ReturnT Kill(KillRequest req) 129 | { 130 | if (req == null) 131 | { 132 | return ReturnT.Failed("Kill Error"); 133 | } 134 | return this._jobDispatcher.TryRemoveJobTask(req.JobId) ? 135 | ReturnT.SUCCESS 136 | : 137 | ReturnT.Success("job thread already killed."); 138 | } 139 | 140 | /// 141 | /// read Log 142 | /// 143 | /// 144 | private ReturnT Log(LogRequest req) 145 | { 146 | if (req == null) 147 | { 148 | return ReturnT.Failed("Log Error"); 149 | } 150 | var ret = ReturnT.Success(null); 151 | ret.Content = this._jobLogger.ReadLog(req.LogDateTime,req.LogId, req.FromLineNum); 152 | return ret; 153 | } 154 | 155 | /// 156 | /// 执行 157 | /// 158 | /// 159 | /// 160 | private ReturnT Run(TriggerParam triggerParam) 161 | { 162 | return this._jobDispatcher.Execute(triggerParam); 163 | } 164 | #endregion 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Queue/JobTaskQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Net.NetworkInformation; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using DotXxlJob.Core.Model; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace DotXxlJob.Core 10 | { 11 | public class JobTaskQueue : IDisposable 12 | { 13 | private readonly IJobLogger _jobLogger; 14 | private readonly ILogger _logger; 15 | private readonly ConcurrentQueue TASK_QUEUE = new ConcurrentQueue(); 16 | private readonly ConcurrentDictionary ID_IN_QUEUE = new ConcurrentDictionary(); 17 | private CancellationTokenSource _cancellationTokenSource; 18 | private Task _runTask; 19 | public JobTaskQueue(ITaskExecutor executor, IJobLogger jobLogger, ILogger logger) 20 | { 21 | this.Executor = executor; 22 | this._jobLogger = jobLogger; 23 | this._logger = logger; 24 | } 25 | 26 | public ITaskExecutor Executor { get; } 27 | 28 | 29 | public event EventHandler CallBack; 30 | 31 | 32 | 33 | 34 | public bool IsRunning() 35 | { 36 | return _cancellationTokenSource != null; 37 | } 38 | 39 | 40 | /// 41 | /// 覆盖之前的队列 42 | /// 43 | /// 44 | /// 45 | public ReturnT Replace(TriggerParam triggerParam) 46 | { 47 | while (!TASK_QUEUE.IsEmpty) 48 | { 49 | TASK_QUEUE.TryDequeue(out _); 50 | } 51 | Stop(); 52 | ID_IN_QUEUE.Clear(); 53 | 54 | return Push(triggerParam); 55 | } 56 | 57 | public ReturnT Push(TriggerParam triggerParam) 58 | { 59 | if (!ID_IN_QUEUE.TryAdd(triggerParam.LogId, 0)) 60 | { 61 | _logger.LogWarning("repeat job task,logId={logId},jobId={jobId}", triggerParam.LogId, triggerParam.JobId); 62 | return ReturnT.Failed("repeat job task!"); 63 | } 64 | 65 | //this._logger.LogWarning("add job with logId={logId},jobId={jobId}",triggerParam.LogId,triggerParam.JobId); 66 | 67 | this.TASK_QUEUE.Enqueue(triggerParam); 68 | StartTask(); 69 | return ReturnT.SUCCESS; 70 | } 71 | 72 | public void Stop() 73 | { 74 | _cancellationTokenSource?.Cancel(); 75 | _cancellationTokenSource?.Dispose(); 76 | _cancellationTokenSource = null; 77 | 78 | //wait for task completed 79 | _runTask?.GetAwaiter().GetResult(); 80 | } 81 | 82 | public void Dispose() 83 | { 84 | while (!TASK_QUEUE.IsEmpty) 85 | { 86 | TASK_QUEUE.TryDequeue(out _); 87 | } 88 | ID_IN_QUEUE.Clear(); 89 | Stop(); 90 | } 91 | 92 | private void StartTask() 93 | { 94 | if (_cancellationTokenSource != null) 95 | { 96 | return; //running 97 | } 98 | _cancellationTokenSource = new CancellationTokenSource(); 99 | var ct = _cancellationTokenSource.Token; 100 | 101 | _runTask = Task.Factory.StartNew(async () => 102 | { 103 | 104 | //ct.ThrowIfCancellationRequested(); 105 | 106 | while (!ct.IsCancellationRequested) 107 | { 108 | if (TASK_QUEUE.IsEmpty) 109 | { 110 | //_logger.LogInformation("task queue is empty!"); 111 | break; 112 | } 113 | 114 | ReturnT result = null; 115 | TriggerParam triggerParam = null; 116 | try 117 | { 118 | 119 | if (TASK_QUEUE.TryDequeue(out triggerParam)) 120 | { 121 | if (!ID_IN_QUEUE.TryRemove(triggerParam.LogId, out _)) 122 | { 123 | _logger.LogWarning("remove queue failed,logId={logId},jobId={jobId},exists={exists}" 124 | , triggerParam.LogId, triggerParam.JobId, ID_IN_QUEUE.ContainsKey(triggerParam.LogId)); 125 | } 126 | //set log file; 127 | _jobLogger.SetLogFile(triggerParam.LogDateTime, triggerParam.LogId); 128 | 129 | _jobLogger.Log("
----------- xxl-job job execute start -----------
----------- Param:{0}", triggerParam.ExecutorParams); 130 | 131 | var exectorToken = ct; 132 | CancellationTokenSource timeoutCts = null; 133 | if (triggerParam.ExecutorTimeout > 0) 134 | { 135 | timeoutCts = new CancellationTokenSource(triggerParam.ExecutorTimeout * 1000); 136 | exectorToken = CancellationTokenSource.CreateLinkedTokenSource(exectorToken, timeoutCts.Token).Token; 137 | } 138 | result = await Executor.Execute(triggerParam, exectorToken); 139 | if(timeoutCts != null && timeoutCts.IsCancellationRequested) 140 | { 141 | result = ReturnT.FAIL_TIMEOUT; 142 | timeoutCts.Dispose(); 143 | timeoutCts = null; 144 | } 145 | 146 | _jobLogger.Log("
----------- xxl-job job execute end(finish) -----------
----------- ReturnT:" + result.Code); 147 | } 148 | else 149 | { 150 | _logger.LogWarning("Dequeue Task Failed"); 151 | } 152 | } 153 | catch (Exception ex) 154 | { 155 | result = ReturnT.Failed("Dequeue Task Failed:" + ex.Message); 156 | _jobLogger.Log("
----------- JobThread Exception:" + ex.Message + "
----------- xxl-job job execute end(error) -----------"); 157 | } 158 | 159 | if (triggerParam != null) 160 | { 161 | CallBack?.Invoke(this, new HandleCallbackParam(triggerParam, result ?? ReturnT.FAIL)); 162 | } 163 | 164 | } 165 | 166 | 167 | _cancellationTokenSource?.Dispose(); 168 | _cancellationTokenSource = null; 169 | }, _cancellationTokenSource.Token); 170 | 171 | 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /src/DotXxlJob.Core/Logger/JobLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using DotXxlJob.Core.Config; 8 | using DotXxlJob.Core.Extensions; 9 | using DotXxlJob.Core.Model; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | 13 | namespace DotXxlJob.Core 14 | { 15 | public class JobLogger:IJobLogger 16 | { 17 | private readonly ILogger _logger; 18 | 19 | private readonly AsyncLocal LogFileName = new AsyncLocal(); 20 | 21 | private readonly XxlJobExecutorOptions _options; 22 | public JobLogger(IOptions optionsAccessor,ILogger logger) 23 | { 24 | this._logger = logger; 25 | this._options = optionsAccessor.Value; 26 | } 27 | 28 | public void SetLogFile(long logTime, long logId) 29 | { 30 | try 31 | { 32 | var filePath = MakeLogFileName(logTime, logId); 33 | var dir = Path.GetDirectoryName(filePath); 34 | if (!Directory.Exists(dir)) 35 | { 36 | Directory.CreateDirectory(dir); 37 | CleanOldLogs(); 38 | } 39 | LogFileName.Value = filePath; 40 | } 41 | catch (Exception ex) 42 | { 43 | _logger.LogError(ex, "SetLogFileName error."); 44 | } 45 | } 46 | 47 | 48 | public void Log(string pattern, params object[] format) 49 | { 50 | string appendLog; 51 | if (format == null || format.Length == 0) 52 | { 53 | appendLog = pattern; 54 | } 55 | else 56 | { 57 | appendLog = string.Format(pattern, format); 58 | } 59 | 60 | var callInfo = new StackTrace(true).GetFrame(1); 61 | LogDetail(GetLogFileName(), callInfo, appendLog); 62 | } 63 | 64 | public void LogError(Exception ex) 65 | { 66 | var callInfo = new StackTrace(true).GetFrame(1); 67 | LogDetail(GetLogFileName(), callInfo, ex.Message + ex.StackTrace); 68 | } 69 | 70 | public LogResult ReadLog(long logTime, long logId, int fromLine) 71 | { 72 | var filePath = MakeLogFileName(logTime, logId); 73 | if (string.IsNullOrEmpty(filePath)) 74 | { 75 | return new LogResult(fromLine, 0, "readLog fail, logFile not found", true); 76 | } 77 | if (!File.Exists(filePath)) 78 | { 79 | return new LogResult(fromLine, 0, "readLog fail, logFile not exists", true); 80 | } 81 | 82 | // read file 83 | var logContentBuffer = new StringBuilder(); 84 | int toLineNum = 0; 85 | try 86 | { 87 | using (var reader = new StreamReader(filePath, Encoding.UTF8)) 88 | { 89 | string line; 90 | while ((line = reader.ReadLine()) != null) 91 | { 92 | toLineNum++; 93 | if (toLineNum >= fromLine) 94 | { 95 | logContentBuffer.AppendLine(line); 96 | } 97 | } 98 | } 99 | } 100 | catch (Exception ex) 101 | { 102 | this._logger.LogError(ex, "ReadLog error."); 103 | } 104 | 105 | // result 106 | var logResult = new LogResult(fromLine, toLineNum, logContentBuffer.ToString(), false); 107 | return logResult; 108 | } 109 | 110 | public void LogSpecialFile(long logTime, long logId, string pattern, params object[] format) 111 | { 112 | var filePath = MakeLogFileName(logTime, logId); 113 | var callInfo = new StackTrace(true).GetFrame(1); 114 | var content = string.Format(pattern, format); 115 | LogDetail(filePath, callInfo, content); 116 | } 117 | 118 | 119 | private string GetLogFileName() 120 | { 121 | return LogFileName.Value; 122 | } 123 | private string MakeLogFileName(long logDateTime, long logId) 124 | { 125 | //log fileName like: logPath/HandlerLogs/yyyy-MM-dd/9999.log 126 | return Path.Combine(this._options.LogPath, Constants.HandleLogsDirectory, 127 | logDateTime.FromMilliseconds().ToString("yyyy-MM-dd"), $"{logId}.log"); 128 | } 129 | private void LogDetail(string logFileName, StackFrame callInfo, string appendLog) 130 | { 131 | if (string.IsNullOrEmpty(logFileName)) 132 | { 133 | return; 134 | } 135 | 136 | var stringBuffer = new StringBuilder(); 137 | stringBuffer 138 | .Append(DateTime.Now.ToString("s")).Append(" ") 139 | .Append("[" + callInfo.GetMethod().DeclaringType.FullName + "#" + callInfo.GetMethod().Name + "]").Append("-") 140 | .Append("[line " + callInfo.GetFileLineNumber() + "]").Append("-") 141 | .Append("[thread " + Thread.CurrentThread.ManagedThreadId + "]").Append(" ") 142 | .Append(appendLog ?? string.Empty) 143 | .AppendLine(); 144 | 145 | var formatAppendLog = stringBuffer.ToString(); 146 | 147 | try 148 | { 149 | File.AppendAllText(logFileName, formatAppendLog, Encoding.UTF8); 150 | } 151 | catch (Exception ex) 152 | { 153 | this._logger.LogError(ex, "LogDetail error"); 154 | } 155 | } 156 | 157 | private void CleanOldLogs() 158 | { 159 | if (this._options.LogRetentionDays <= 0) 160 | { 161 | this._options.LogRetentionDays = Constants.DefaultLogRetentionDays; 162 | } 163 | 164 | Task.Run(() => 165 | { 166 | try 167 | { 168 | var handlerLogsDir = new DirectoryInfo(Path.Combine(this._options.LogPath, Constants.HandleLogsDirectory)); 169 | if (!handlerLogsDir.Exists) 170 | { 171 | return; 172 | } 173 | 174 | var today = DateTime.UtcNow.Date; 175 | foreach (var dir in handlerLogsDir.GetDirectories()) 176 | { 177 | if (DateTime.TryParse(dir.Name, out var dirDate)) 178 | { 179 | if (today.Subtract(dirDate.Date).Days > this._options.LogRetentionDays) 180 | { 181 | dir.Delete(true); 182 | } 183 | } 184 | } 185 | } 186 | catch (Exception ex) 187 | { 188 | this._logger.LogError(ex, "CleanOldLogs error."); 189 | } 190 | }); 191 | } 192 | 193 | } 194 | } --------------------------------------------------------------------------------