🔖 发布版本1.0.0并编写readme

pull/1/head
假正经哥哥 7 years ago
parent eb762784e7
commit 76564675e0
  1. 46
      .vscode/launch.json
  2. 8
      .vscode/solution-explorer/class.cs-template
  3. 3
      .vscode/solution-explorer/class.ts-template
  4. 9
      .vscode/solution-explorer/class.vb-template
  5. 3
      .vscode/solution-explorer/default.ts-template
  6. 8
      .vscode/solution-explorer/enum.cs-template
  7. 8
      .vscode/solution-explorer/interface.cs-template
  8. 3
      .vscode/solution-explorer/interface.ts-template
  9. 46
      .vscode/solution-explorer/template-list.json
  10. 16
      .vscode/solution-explorer/template-parameters.js
  11. 15
      .vscode/tasks.json
  12. 30
      DotXxlJob.sln
  13. 170
      README.md
  14. 6
      RELEASE_NOTES.md
  15. 26
      build.cake
  16. 7
      samples/ASPNetCoreExecutor/Extensions/XxlJobExecutorMiddleware.cs
  17. 69
      src/DotXxlJob.Core/AdminClient.cs
  18. 28
      src/DotXxlJob.Core/Config/XxlJobExecutorOptions.cs
  19. 38
      src/DotXxlJob.Core/DefaultHandlers/SimpleHttpJobHandler.cs
  20. 25
      src/DotXxlJob.Core/DotXxlJob.Core.csproj
  21. 67
      src/DotXxlJob.Core/ExecutorRegistry.cs
  22. 25
      src/DotXxlJob.Core/Extensions/ServiceCollectionExtensions.cs
  23. 21
      src/DotXxlJob.Core/Hosted/JobExecuteHostedService.cs
  24. 8
      src/DotXxlJob.Core/IExecutorRegistry.cs
  25. 1
      src/DotXxlJob.Core/IJobHandlerFactory.cs
  26. 21
      src/DotXxlJob.Core/Internal/Constants.cs
  27. 12
      src/DotXxlJob.Core/Internal/HessianObjectConvert.cs
  28. 16
      src/DotXxlJob.Core/Internal/HessianSerializer.cs
  29. 132
      src/DotXxlJob.Core/Internal/IPUtility.cs
  30. 84
      src/DotXxlJob.Core/Internal/Preconditions.cs
  31. 1
      src/DotXxlJob.Core/JobDispatcher.cs
  32. 14
      src/DotXxlJob.Core/Logger/JobLogger.cs
  33. 2
      src/DotXxlJob.Core/Model/HandleCallbackParam.cs
  34. 2
      src/DotXxlJob.Core/Model/JavaClass.cs
  35. 2
      src/DotXxlJob.Core/Model/LogResult.cs
  36. 5
      src/DotXxlJob.Core/Model/RegistryParam.cs
  37. 2
      src/DotXxlJob.Core/Model/ReturnT.cs
  38. 2
      src/DotXxlJob.Core/Model/RpcRequest.cs
  39. 2
      src/DotXxlJob.Core/Model/RpcResponse.cs
  40. 2
      src/DotXxlJob.Core/Model/TriggerParam.cs
  41. 10
      src/DotXxlJob.Core/Queue/CallbackTaskQueue.cs
  42. 15
      src/DotXxlJob.Core/Queue/JobTaskQueue.cs
  43. 27
      src/DotXxlJob.Core/Queue/RetryCallbackTaskQueue.cs
  44. 16
      src/DotXxlJob.Core/TaskExecutorFactory.cs
  45. 24
      src/Hessian/Hessian.csproj
  46. 19
      tests/DotXxlJob.Core.Tests/DotXxlJob.Core.Tests.csproj
  47. 13
      tests/DotXxlJob.Core.Tests/UnitTest1.cs
  48. 33
      tests/Hessian.NET.Tests/ByteArray.cs
  49. 24
      tests/Hessian.NET.Tests/Hessian.NET.Tests.csproj
  50. 246
      tests/Hessian.NET.Tests/HessianInputReaderTests.cs
  51. 326
      tests/Hessian.NET.Tests/HessianOutputWriterTests.cs
  52. 167
      tests/Hessian.NET.Tests/HessianSerializerTests.cs
  53. 19
      tests/Hessian.Tests/Hessian.Tests.csproj
  54. 13
      tests/Hessian.Tests/UnitTest1.cs
  55. 4
      tools/packages.config

@ -0,0 +1,46 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/samples/ASPNetCoreExecutor/bin/Debug/netcoreapp2.2/ASPNetCoreExecutor.dll",
"args": [],
"cwd": "${workspaceFolder}/samples/ASPNetCoreExecutor",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart",
"launchBrowser": {
"enabled": true,
"args": "${auto-detect-url}",
"windows": {
"command": "cmd.exe",
"args": "/C start ${auto-detect-url}"
},
"osx": {
"command": "open"
},
"linux": {
"command": "xdg-open"
}
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
,]
}

@ -0,0 +1,8 @@
using System;
namespace {{namespace}}
{
public class {{name}}
{
}
}

@ -0,0 +1,3 @@
export class {{name}} {
}

@ -0,0 +1,9 @@
Imports System
Namespace {{namespace}}
Public Class {{name}}
End Class
End Namespace

@ -0,0 +1,3 @@
export default {{name}} {
}

@ -0,0 +1,8 @@
using System;
namespace {{namespace}}
{
public enum {{name}}
{
}
}

@ -0,0 +1,8 @@
using System;
namespace {{namespace}}
{
public interface {{name}}
{
}
}

@ -0,0 +1,3 @@
export interface {{name}} {
}

@ -0,0 +1,46 @@
{
"templates": [
{
"name": "Class",
"extension": "cs",
"file": "./class.cs-template",
"parameters": "./template-parameters.js"
},
{
"name": "Interface",
"extension": "cs",
"file": "./interface.cs-template",
"parameters": "./template-parameters.js"
},
{
"name": "Enum",
"extension": "cs",
"file": "./enum.cs-template",
"parameters": "./template-parameters.js"
},
{
"name": "Class",
"extension": "ts",
"file": "./class.ts-template",
"parameters": "./template-parameters.js"
},
{
"name": "Interface",
"extension": "ts",
"file": "./interface.ts-template",
"parameters": "./template-parameters.js"
},
{
"name": "Default",
"extension": "ts",
"file": "./default.ts-template",
"parameters": "./template-parameters.js"
},
{
"name": "Class",
"extension": "vb",
"file": "./class.vb-template",
"parameters": "./template-parameters.js"
}
]
}

@ -0,0 +1,16 @@
var path = require("path");
module.exports = function(filename, projectPath, folderPath) {
var namespace = "Unknown";
if (projectPath) {
namespace = path.basename(projectPath, path.extname(projectPath));
if (folderPath) {
namespace += "." + folderPath.replace(path.dirname(projectPath), "").substring(1).replace(/[\\\/]/g, ".");
}
}
return {
namespace: namespace,
name: path.basename(filename, path.extname(filename))
}
};

15
.vscode/tasks.json vendored

@ -0,0 +1,15 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

@ -17,6 +17,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HessianReader", "samples\He
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hessian", "src\Hessian\Hessian.csproj", "{BD9B8108-6528-430F-AD28-6F8434A29F55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hessian.Tests", "tests\Hessian.Tests\Hessian.Tests.csproj", "{A51FACF0-C90F-43D1-898D-CD171977C1A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotXxlJob.Core.Tests", "tests\DotXxlJob.Core.Tests\DotXxlJob.Core.Tests.csproj", "{81C60471-7C1C-48CE-98C0-F252C267AC9F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -75,6 +79,30 @@ Global
{BD9B8108-6528-430F-AD28-6F8434A29F55}.Release|x64.Build.0 = Release|Any CPU
{BD9B8108-6528-430F-AD28-6F8434A29F55}.Release|x86.ActiveCfg = Release|Any CPU
{BD9B8108-6528-430F-AD28-6F8434A29F55}.Release|x86.Build.0 = Release|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Debug|x64.ActiveCfg = Debug|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Debug|x64.Build.0 = Debug|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Debug|x86.ActiveCfg = Debug|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Debug|x86.Build.0 = Debug|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Release|Any CPU.Build.0 = Release|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Release|x64.ActiveCfg = Release|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Release|x64.Build.0 = Release|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Release|x86.ActiveCfg = Release|Any CPU
{A51FACF0-C90F-43D1-898D-CD171977C1A1}.Release|x86.Build.0 = Release|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|x64.ActiveCfg = Debug|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|x64.Build.0 = Debug|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|x86.ActiveCfg = Debug|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Debug|x86.Build.0 = Debug|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|Any CPU.Build.0 = Release|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|x64.ActiveCfg = Release|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|x64.Build.0 = Release|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|x86.ActiveCfg = Release|Any CPU
{81C60471-7C1C-48CE-98C0-F252C267AC9F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -84,6 +112,8 @@ Global
{DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5} = {E959F9B5-F3EB-48B1-B842-2CDDFDB01900}
{F822B528-95FD-40B4-9EE0-3AE8878075AC} = {E959F9B5-F3EB-48B1-B842-2CDDFDB01900}
{BD9B8108-6528-430F-AD28-6F8434A29F55} = {97756BA5-1E7C-4536-A49E-AE2190C0E6A5}
{A51FACF0-C90F-43D1-898D-CD171977C1A1} = {352EC932-F112-4A2F-9DC3-F0761C85E068}
{81C60471-7C1C-48CE-98C0-F252C267AC9F} = {352EC932-F112-4A2F-9DC3-F0761C85E068}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F4A8B63E-6284-4D00-9719-BAB1D955DACF}

@ -1,2 +1,168 @@
# DotXxlJob
xxl-job的dotnet core 执行器实现
# DotXxlJob
xxl-job的dotnet core 执行器实现,支持XXL-JOB 2.0+
## 1 XXL-JOB概述
[XXL-JOB][1]是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。以下是它的架构图
![架构图](https://raw.githubusercontent.com/xuxueli/xxl-job/master/doc/images/img_Qohm.png)
## 2. 关于DotXxlJob产生
在工作中调研过多个任务调度平台,如Hangfire、基于Quatz.NET的第三方扩展,都与实际的需求有一点差距。 之前一直使用Hangfire,Hangfire的执行器在同步调用业务服务时,如果即时业务服务正在重新部署或者重启,有一定概率会出现死锁,导致CPU100%,后来全部调整为异步,但是这样就无法获得执行结果,这样的设计有蛮大问题,XxlJob的回调机制很好的解决了这个问题。本身如果通过http的方式调用,只要部署springbootd的一个执行器就可以解决问题,但是扩展性较差。所以萌生了实现DotNet版本的执行器的想法,为避免重复造轮子,开始之前也进行过调研,以下仓库[https://github.com/yuniansheng/xxl-job-dotnet][2]给了较大的启发,但是该库只支持1.9版本的xxljob,还有一些其他小问题,所以还是自力更生。
## 3. 如何使用
目前只实现了BEAN的方式,即直接实现IJobHandler调用的方式,Glue源码的方式实际上实现起来也并不复杂(有需求再说把),或者各位有需求Fork 实现一下
可参考sample
安装:
> dotnet add package DotXxlJob.Core
### 3.1 在AspNetCore中使用
1. 生命一个AspNet的Middleware中间件,并扩展ApplicationBuilder,本质是拦截Post请求,解析Body中的流信息
```
public class XxlJobExecutorMiddleware
{
private readonly IServiceProvider _provider;
private readonly RequestDelegate _next;
private readonly XxlRpcServiceHandler _rpcService;
public XxlJobExecutorMiddleware(IServiceProvider provider, RequestDelegate next)
{
this._provider = provider;
this._next = next;
this._rpcService = _provider.GetRequiredService<XxlRpcServiceHandler>();
}
public async Task Invoke(HttpContext context)
{
if ("POST".Equals(context.Request.Method, StringComparison.OrdinalIgnoreCase) &&
"application/octet-stream".Equals(context.Request.ContentType, StringComparison.OrdinalIgnoreCase))
{
var rsp = await _rpcService.HandlerAsync(context.Request.Body);
context.Response.StatusCode = (int) HttpStatusCode.OK;
context.Response.ContentType = "text/plain;utf-8";
await context.Response.Body.WriteAsync(rsp,0,rsp.Length);
return;
}
await _next.Invoke(context);
}
}
```
扩展ApplicationBuilderExtensions,可根据实际情况绑定在特殊的Url Path上
```
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseXxlJobExecutor(this IApplicationBuilder @this)
{
return @this.UseMiddleware<XxlJobExecutorMiddleware>();
}
}
```
在Startup中添加必要的引用,其中自动注册。
```
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
private IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddXxlJobExecutor(Configuration);
services.AddSingleton<IJobHandler, DemoJobHandler>(); // 添加自定义的jobHandler
services.AddAutoRegistry(); // 自动注册
}
public void Configure(IApplicationBuilder app,IHostingEnvironment env)
{
//启用XxlExecutor
app.UseXxlJobExecutor();
}
}
```
编写JobHandler,继承AbstractJobHandler或者直接实现接口IJobHandler,通过context.JobLogger 记录执行过程和结果,在AdminWeb上可查看的哦
```
[JobHandler("demoJobHandler")]
public class DemoJobHandler:AbstractJobHandler
{
public override Task<ReturnT> Execute(JobExecuteContext context)
{
context.JobLogger.Log("receive demo job handler,parameter:{0}",context.JobParameter);
return Task.FromResult(ReturnT.SUCCESS);
}
}
```
## 3.2 配置信息
管理端地址和端口是必填信息,其他根据实际情况,选择配置,配置项说明见下代码中的注释
```
public class XxlJobExecutorOptions
{
/// <summary>
/// 管理端地址,多个以;分隔
/// </summary>
public string AdminAddresses { get; set; }
/// <summary>
/// appName自动注册时要去管理端配置一致
/// </summary>
public string AppName { get; set; } = "xxl-job-executor-dotnet";
/// <summary>
/// 自动注册时提交的地址,为空会自动获取内网地址
/// </summary>
public string SpecialBindAddress { get; set; }
/// <summary>
/// 绑定端口
/// </summary>
public int Port { get; set; }
/// <summary>
/// 是否自动注册
/// </summary>
public bool AutoRegistry { get; set; }
/// <summary>
/// 认证票据
/// </summary>
public string AccessToken { get; set; }
/// <summary>
/// 日志目录,默认为执行目录的logs子目录下,请配置绝对路径
/// </summary>
public string LogPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "./logs");
/// <summary>
/// 日志保留天数
/// </summary>
public int LogRetentionDays { get; set; } = 30;
}
```
## 在其他Http服务中使用
只需要实现Http请求的拦截,并判断post请求中content-Type="application/octet-stream",并使用XxlRpcServiceHandler来处理流 即可。
## 其他说明
XXL-JOB内置的RPC是使用Hessian协议,这个有点坑。很多都是java特有的属性和标识,比如类名什么的。在本项目中,并没有实现完整的Hessian2协议,只实现了使用到的类型,当然扩展起来也非常方便。如果有人要单独使用Hessian 这个类库的话,要特别注意这个问题。
有任何问题,可Issue反馈 ,最后感谢 xxl-job
如果觉得对你有帮助,可以请我吃包肥仔快乐薯片
![](http://ww1.sinaimg.cn/large/697065c1gy1fzc2pfj071j207s09swfg.jpg) ![](http://ww1.sinaimg.cn/large/697065c1gy1fzc2popzirj208h0audgp.jpg)
[1]: http://www.xuxueli.com/xxl-job
[2]: https://github.com/yuniansheng/xxl-job-dotnet

@ -1,2 +1,4 @@
#### 0.1.0 Jan 10, 2019
- 首次发布,实现xxl-job-execute的实现
#### 1.0.0 Jan 19, 2019
- 首次发布,实现xxl-job-execute的实现
- 实现基本的Bean JobHandler的功能
- 实现自动注册

@ -36,8 +36,8 @@ Information($"Running target {target} in {configuration} configuration, version
var artifactsDirectory = Directory("./artifacts");
var projName = "./src/Peach/Peach.csproj";
var projName = "./src/DotXxlJob.Core/DotXxlJob.Core.csproj";
var hessionProjName = "./src/Hessian/Hessian.csproj";
//////////////////////////////////////////////////////////////////////
// TASKS
//////////////////////////////////////////////////////////////////////
@ -55,7 +55,7 @@ Task("Restore")
.IsDependentOn("Clean")
.Does(() =>
{
DotNetCoreRestore("./Peach.sln");
DotNetCoreRestore("./DotXxlJob.sln");
});
@ -105,27 +105,39 @@ Task("Package")
NoBuild = true,
ArgumentCustomization = args => args.Append($"/p:PackageVersion={packageVersion}"),
});
DotNetCorePack(
hessionProjName,
new DotNetCorePackSettings()
{
Configuration = configuration,
OutputDirectory = artifactsDirectory,
NoBuild = true,
ArgumentCustomization = args => args.Append($"/p:PackageVersion={packageVersion}"),
});
});
Task("Publish")
  .IsDependentOn("Package")
  .Does(()=>
{
   var settings = new DotNetCoreNuGetPushSettings
{
Source = "https://www.nuget.org",
ApiKey = EnvironmentVariable("NUGET_KEY")
Source = "https://www.nuget.org",
ApiKey = EnvironmentVariable("NUGET_KEY")
};
foreach(var file in GetFiles($"{artifactsDirectory}/*.nupkg"))
{
DotNetCoreNuGetPush(file.FullPath, settings);
DotNetCoreNuGetPush(file.FullPath, settings);
}
});
//////////////////////////////////////////////////////////////////////
// TASK TARGETS
//////////////////////////////////////////////////////////////////////

@ -16,10 +16,9 @@ namespace ASPNetCoreExecutor
private readonly XxlRpcServiceHandler _rpcService;
public XxlJobExecutorMiddleware(IServiceProvider provider, RequestDelegate next)
{
_provider = provider;
_next = next;
_rpcService = _provider.GetRequiredService<XxlRpcServiceHandler>();
this._provider = provider;
this._next = next;
this._rpcService = _provider.GetRequiredService<XxlRpcServiceHandler>();
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.IO;
using System.Linq;
using System.Net.Http;
@ -28,7 +29,10 @@ namespace DotXxlJob.Core
,IHttpClientFactory clientFactory
,ILogger<AdminClient> logger)
{
this._options = optionsAccessor.Value;
Preconditions.CheckNotNull(optionsAccessor?.Value, "XxlJobExecutorOptions");
this._options = optionsAccessor?.Value;
this._clientFactory = clientFactory;
this._logger = logger;
InitAddress();
@ -61,16 +65,16 @@ namespace DotXxlJob.Core
public Task<ReturnT> Registry(RegistryParam registryParam)
{
return InvokeRpcService("callback", new List<object> {new JavaClass {Name = Constants.JavaClassFulName}}, registryParam);
return InvokeRpcService("registry", new List<object> {new JavaClass {Name = "com.xxl.job.core.biz.model.RegistryParam"}}, registryParam,true);
}
public Task<ReturnT> RegistryRemove(RegistryParam registryParam)
{
return InvokeRpcService("callback", new List<object> {new JavaClass {Name = Constants.JavaClassFulName}}, registryParam);
return InvokeRpcService("registryRemove", new List<object> {new JavaClass {Name = "com.xxl.job.core.biz.model.RegistryParam"}}, registryParam,true);
}
private async Task<ReturnT> InvokeRpcService(string methodName, List<object> parameterTypes,
object parameters)
object parameters,bool polling=false)
{
var request = new RpcRequest {
RequestId = Guid.NewGuid().ToString("N"),
@ -86,14 +90,14 @@ namespace DotXxlJob.Core
{
HessianSerializer.SerializeRequest(stream,request);
postBuf =stream.ToArray();
postBuf = stream.ToArray();
}
int triedTimes = 0;
var triedTimes = 0;
var retList = new List<ReturnT>();
using (var client = this._clientFactory.CreateClient("DotXxlJobClient"))
using (var client = this._clientFactory.CreateClient(Constants.DefaultHttpClientName))
{
while (triedTimes++ < this._addresses.Count)
{
var address = this._addresses[this._currentIndex];
@ -106,43 +110,66 @@ namespace DotXxlJob.Core
{
resStream = await DoPost(client, address, postBuf);
address.Reset();
}
catch (Exception ex)
{
this._logger.LogError(ex, "request admin error.");
this._logger.LogError(ex, "request admin error.{0}",ex.Message);
address.SetFail();
continue;
}
RpcResponse res = null;
try
{
res = HessianSerializer.DeserializeResponse(resStream);
{
/*
using (StreamReader reader = new StreamReader(resStream))
{
string content = await reader.ReadToEndAsync();
this._logger.LogWarning(content);
}
*/
res = HessianSerializer.DeserializeResponse(resStream);
}
catch (Exception ex)
{
this._logger.LogError(ex,"DeserializeResponse error:"+ex.Message);
this._logger.LogError(ex,"DeserializeResponse error:{errorMessage}",ex.Message);
}
if (res == null)
{
return ReturnT.Failed("response is null");
retList.Add(ReturnT.Failed("response is null"));
}
if (res.IsError)
else if (res.IsError)
{
retList.Add(ReturnT.Failed(res.ErrorMsg));
}
else if(res.Result is ReturnT ret)
{
retList.Add(ret);
}
else
{
return ReturnT.Failed(res.ErrorMsg);
retList.Add(ReturnT.Failed("response is null"));
}
return res.Result as ReturnT;
if (!polling)
{
return retList[0];
}
}
if (retList.Count > 0)
{
return retList.Last();
}
}
throw new Exception("xxl-rpc server address not accessible.");
}
private async Task<Stream> DoPost(HttpClient client,AddressEntry address, byte[] postBuf)

@ -6,24 +6,50 @@ namespace DotXxlJob.Core.Config
public class XxlJobExecutorOptions
{
/// <summary>
/// 管理端地址,多个以;分隔
/// </summary>
public string AdminAddresses { get; set; }
public string AppName { get; set; } = "DotXxlJob";
/// <summary>
/// appName自动注册时要去管理端配置一致
/// </summary>
public string AppName { get; set; } = "xxl-job-executor-dotnet";
/// <summary>
/// 自动注册时提交的地址,为空会自动获取内网地址
/// </summary>
public string SpecialBindAddress { get; set; }
/// <summary>
/// 绑定端口
/// </summary>
public int Port { get; set; }
/// <summary>
/// 是否自动注册
/// </summary>
public bool AutoRegistry { get; set; }
/// <summary>
/// 认证票据
/// </summary>
public string AccessToken { get; set; }
/// <summary>
/// 日志目录,默认为执行目录的logs子目录下,请配置绝对路径
/// </summary>
public string LogPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "./logs");
/// <summary>
/// 日志保留天数
/// </summary>
public int LogRetentionDays { get; set; } = 30;
}

@ -1,3 +1,5 @@
using System;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -16,7 +18,7 @@ namespace DotXxlJob.Core.DefaultHandlers
{
this._httpClientFactory = httpClientFactory;
}
public async override Task<ReturnT> Execute(JobExecuteContext context)
public override async Task<ReturnT> Execute(JobExecuteContext context)
{
if (string.IsNullOrEmpty(context.JobParameter))
{
@ -29,15 +31,35 @@ namespace DotXxlJob.Core.DefaultHandlers
{
return ReturnT.Failed("url format is not valid");
}
using (var client = _httpClientFactory.CreateClient(Constants.DefaultHttpClientName))
context.JobLogger.Log("Get Request Data:{0}",context.JobParameter);
using (var client = this._httpClientFactory.CreateClient(Constants.DefaultHttpClientName))
{
var responseMessage = await client.GetAsync(url);
try
{
var response = await client.GetAsync(url);
if (response == null)
{
context.JobLogger.Log("call remote error,response is null");
return ReturnT.Failed("call remote error,response is null");
}
if (response.StatusCode != HttpStatusCode.OK)
{
context.JobLogger.Log("call remote error,response statusCode ={0}",response.StatusCode);
return ReturnT.Failed("call remote error,response statusCode ="+response.StatusCode);
}
string body = await response.Content.ReadAsStringAsync();
context.JobLogger.Log("<br/> call remote success ,response is : <br/> {0}",body);
return ReturnT.SUCCESS;
}
catch (Exception ex)
{
context.JobLogger.LogError(ex);
return ReturnT.Failed(ex.Message);
}
}
//判断是否为单URL
context.JobLogger.Log("Get Request Data:{0}",context.JobParameter);
return ReturnT.SUCCESS;
}
}

@ -1,13 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>$(DefineConstants);DOTNETCORE</DefineConstants>
<Description>XxlJobExecutor DotNet port</Description>
<Copyright>Xuanye @ 2019</Copyright>
<Authors>Xuanye</Authors>
<AssemblyTitle>XxlJobExecutor DotNet port</AssemblyTitle>
<AssemblyName>DotXxlJob.Core</AssemblyName>
<PackageId>DotXxlJob.Core</PackageId>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<PackageTags>Hession,xxl-job,DotXxlJob</PackageTags>
<PackageReleaseNotes>XxlJobExecutor的 DOTNETCORE 实现 </PackageReleaseNotes>
<PackageProjectUrl>https://github.com/xuanye/DotXxlJob</PackageProjectUrl>
<PackageLicense>https://github.com/xuanye/DotXxlJob/blob/master/LICENSE</PackageLicense>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/xuanye/DotXxlJob</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.0" />
<PackageReference Include="Utf8Json" Version="1.3.7" />
</ItemGroup>

@ -1,3 +1,11 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using DotXxlJob.Core.Config;
using DotXxlJob.Core.Model;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DotXxlJob.Core
{
/// <summary>
@ -5,9 +13,64 @@ namespace DotXxlJob.Core
/// </summary>
public class ExecutorRegistry:IExecutorRegistry
{
public void Start()
private readonly AdminClient _adminClient;
private readonly XxlJobExecutorOptions _options;
private readonly ILogger<ExecutorRegistry> _logger;
public ExecutorRegistry(AdminClient adminClient,IOptions<XxlJobExecutorOptions> optionsAccessor,ILogger<ExecutorRegistry> logger)
{
throw new System.NotImplementedException();
Preconditions.CheckNotNull(optionsAccessor, "XxlJobExecutorOptions");
Preconditions.CheckNotNull(optionsAccessor.Value, "XxlJobExecutorOptions");
this._adminClient = adminClient;
this._options = optionsAccessor.Value;
if (string.IsNullOrEmpty(this._options.SpecialBindAddress))
{
this._options.SpecialBindAddress = IPUtility.GetLocalIntranetIP().MapToIPv4().ToString();
}
this._logger = logger;
}
public async Task RegistryAsync(CancellationToken cancellationToken)
{
var registryParam = new RegistryParam {
RegistryGroup = "EXECUTOR",
RegistryKey = this._options.AppName,
RegistryValue = $"{this._options.SpecialBindAddress}:{this._options.Port}"
};
this._logger.LogInformation(">>>>>>>> start registry to admin <<<<<<<<");
var errorTimes = 0;
while (!cancellationToken.IsCancellationRequested)
{
try
{
var ret = await this._adminClient.Registry(registryParam);
this._logger.LogInformation("registry last result:{0}", ret?.Code);
errorTimes = 0;
await Task.Delay(Constants.RegistryInterval, cancellationToken);
}
catch (TaskCanceledException)
{
this._logger.LogInformation(">>>>> Application Stopping....<<<<<");
}
catch (Exception ex)
{
errorTimes++;
this._logger.LogError(ex,"registry error:{0},{1} Times",ex.Message,errorTimes);
}
}
this._logger.LogInformation(">>>>>>>> end registry to admin <<<<<<<<");
this._logger.LogInformation(">>>>>>>> start remove registry to admin <<<<<<<<");
var removeRet = await this._adminClient.RegistryRemove(registryParam);
this._logger.LogInformation("remove registry last result:{0}",removeRet?.Code);
this._logger.LogInformation(">>>>>>>> end remove registry to admin <<<<<<<<");
}
}
}

@ -1,8 +1,11 @@
using System;
using DotXxlJob.Core.Config;
using DotXxlJob.Core.DefaultHandlers;
using DotXxlJob.Core.Queue;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
namespace DotXxlJob.Core
{
@ -27,23 +30,31 @@ namespace DotXxlJob.Core
public static IServiceCollection AddDefaultXxlJobHandlers(this IServiceCollection services)
{
services.AddSingleton<IJobHandler,HttpJobHandler >();
services.AddSingleton<IJobHandler,SimpleHttpJobHandler>();
return services;
}
public static IServiceCollection AddAutoRegistry(this IServiceCollection services)
{
services.AddSingleton<IExecutorRegistry,ExecutorRegistry>()
.AddSingleton<IHostedService,JobsExecuteHostedService>();
return services;
}
private static IServiceCollection AddXxlJobExecutorServiceDependency(this IServiceCollection services)
{
{
//可在外部提前注册对应实现,并替换默认实现
services.TryAddSingleton<IJobLogger, JobLogger>();
services.TryAddSingleton<IJobHandlerFactory,DefaultJobHandlerFactory >();
services.TryAddSingleton<IExecutorRegistry, ExecutorRegistry>();
services.AddHttpClient("DotXxlJobClient");
services.AddSingleton<IJobLogger, JobLogger>();
services.AddSingleton<ITaskExecutor, TaskExecutors.BeanTaskExecutor>();
services.AddSingleton<IJobHandlerFactory,DefaultJobHandlerFactory >();
services.AddSingleton<JobDispatcher>();
services.AddSingleton<TaskExecutorFactory>();
services.AddSingleton<XxlRpcServiceHandler>();
services.AddSingleton<CallbackTaskQueue>();
services.AddSingleton<AdminClient>();
services.AddSingleton<IExecutorRegistry, ExecutorRegistry>();
services.AddSingleton<ITaskExecutor, TaskExecutors.BeanTaskExecutor>();
return services;
}

@ -7,24 +7,19 @@ namespace DotXxlJob.Core
/// <summary>
/// NOTE: 负责启动Executor服务,和进行服务注册的宿主服务
/// </summary>
public class JobsExecuteHostedService:IHostedService
public class JobsExecuteHostedService:BackgroundService
{
public Task StartAsync(CancellationToken cancellationToken)
private readonly IExecutorRegistry _registry;
public JobsExecuteHostedService(IExecutorRegistry registry)
{
//1 初始化服务注册
//2 初始化adminClient
//3 初始化执行线程
//4 初始化XXL_RPC服务端口,HTTP服务
throw new System.NotImplementedException();
this._registry = registry;
}
public Task StopAsync(CancellationToken cancellationToken)
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
//清理Start中启动的资源
throw new System.NotImplementedException();
return this._registry.RegistryAsync(stoppingToken);
}
}
}

@ -1,7 +1,13 @@
using System.Threading;
using System.Threading.Tasks;
namespace DotXxlJob.Core
{
public interface IExecutorRegistry
{
void Start();
Task RegistryAsync(CancellationToken cancellationToken);
}
}

@ -2,7 +2,6 @@ namespace DotXxlJob.Core
{
public interface IJobHandlerFactory
{
//TODO: 获取实际执行的JobHandler
IJobHandler GetJobHandler(string handlerName);
}
}

@ -6,15 +6,28 @@ namespace DotXxlJob.Core
{
public const string RpcRequestJavaFullName = "com.xxl.rpc.remoting.net.params.XxlRpcRequest";
public const string RpcResponseJavaFullName = "com.xxl.rpc.remoting.net.params.XxlRpcResponse";
public const string JavaClassFulName = "java.lang.Clas";
public const string RegistryParamJavaFullName = "com.xxl.job.core.biz.model.RegistryParam";
public const string ReturnTJavaFullName = "com.xxl.job.core.biz.model.ReturnT";
public const string TriggerParamJavaFullName = "com.xxl.job.core.biz.model.TriggerParam";
public const string HandleCallbackParamJavaFullName = "com.xxl.job.core.biz.model.HandleCallbackParam";
public const string LogResultJavaFullName = "com.xxl.job.core.biz.model.LogResult";
public const string JavaClassFulName = "java.lang.Class";
public const string JavaListFulName = "java.util.List";
public const string XxlLogsDefaultRootDirectory = "xxl-job-logs";
public const string XxlJobRetryLogsFile = "xxl-job-retry.log";
public const string HandleLogsDirectory = "HandlerLogs";
public const string LogFileNameCallContextKey = "XxlJob.LogFileName";
public const string DefaultHttpClientName = "DotXxlJobHttpClient";
public const int DefaultLogRetentionDays = 30;
public static TimeSpan RpcRequestExpireTimeSpan = TimeSpan.FromMinutes(3);
public static TimeSpan RegistryInterval = TimeSpan.FromSeconds(60);
public const int MaxCallbackRetryTimes = 10;
//每次回调最多发送几条记录
@ -22,7 +35,7 @@ namespace DotXxlJob.Core
public static TimeSpan CallbackRetryInterval = TimeSpan.FromSeconds(600);
//Admin集群机器请求默认超时时间
public static TimeSpan AdminServerDefaultTimeout = TimeSpan.FromSeconds(15);
//public static TimeSpan AdminServerDefaultTimeout = TimeSpan.FromSeconds(15);
//Admin集群中的某台机器熔断后间隔多长时间再重试
public static TimeSpan AdminServerReconnectInterval = TimeSpan.FromMinutes(3);
//Admin集群中的某台机器请求失败多少次后熔断

@ -64,7 +64,7 @@ namespace DotXxlJob.Core
TransferObjCache.Add(classAttr.Name,propertyInfos);
}
public static object GetRealObjectValue(object value)
public static object GetRealObjectValue(Deserializer deserializer,object value)
{
if (value == null || IsSimpleType(value.GetType()))
{
@ -80,7 +80,7 @@ namespace DotXxlJob.Core
{
if (properties.TryGetValue(k, out var p))
{
p.SetValue(instance,GetRealObjectValue(v));
p.SetValue(instance,GetRealObjectValue(deserializer,v));
}
}
@ -88,6 +88,11 @@ namespace DotXxlJob.Core
}
}
if (value is ClassDef)
{
return GetRealObjectValue(deserializer, deserializer.ReadValue());
}
if (IsListType(value.GetType()))
{
var listData = new List<object>();
@ -95,11 +100,12 @@ namespace DotXxlJob.Core
var cList = value as List<object>;
foreach (var cItem in cList)
{
listData.Add(GetRealObjectValue(cItem));
listData.Add(GetRealObjectValue(deserializer,cItem));
}
return listData;
}
throw new HessianException($"unknown item:{value.GetType()}");
}

@ -1,3 +1,4 @@
using System;
using System.IO;
using DotXxlJob.Core.Model;
using Hessian;
@ -19,7 +20,7 @@ namespace DotXxlJob.Core
{
throw new HessianException($"unknown class :{classDef.Name}");
}
request = HessianObjectHelper.GetRealObjectValue(deserializer.ReadValue()) as RpcRequest;
request = HessianObjectHelper.GetRealObjectValue(deserializer,deserializer.ReadValue()) as RpcRequest;
}
catch (EndOfStreamException)
{
@ -50,18 +51,23 @@ namespace DotXxlJob.Core
try
{
var deserializer = new Deserializer(resStream);
var classDef = deserializer.ReadValue() as ClassDef;
var classDef = deserializer.ReadValue() as ClassDef;
if (!Constants.RpcResponseJavaFullName.Equals(classDef.Name))
{
throw new HessianException($"unknown class :{classDef.Name}");
throw new HessianException($"unknown class :{classDef.Name}");
}
rsp = HessianObjectHelper.GetRealObjectValue(deserializer.ReadValue()) as RpcResponse;
rsp = HessianObjectHelper.GetRealObjectValue(deserializer,deserializer.ReadValue()) as RpcResponse;
}
catch (EndOfStreamException)
{
//没有数据可读了
}
catch (Exception)
{
//TODO: do something?
}
return rsp;
}

@ -1,7 +1,135 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
namespace DotXxlJob.Core
{
public class IPUtility
/// <summary>
/// ip utility
/// </summary>
internal static class IPUtility
{
#region Private Members
/// <summary>
/// A类: 10.0.0.0-10.255.255.255
/// </summary>
private static long ipABegin, ipAEnd;
/// <summary>
/// B类: 172.16.0.0-172.31.255.255
/// </summary>
private static long ipBBegin, ipBEnd;
/// <summary>
/// C类: 192.168.0.0-192.168.255.255
/// </summary>
private static long ipCBegin, ipCEnd;
#endregion
#region Constructors
/// <summary>
/// static new
/// </summary>
static IPUtility()
{
ipABegin = ConvertToNumber("10.0.0.0");
ipAEnd = ConvertToNumber("10.255.255.255");
ipBBegin = ConvertToNumber("172.16.0.0");
ipBEnd = ConvertToNumber("172.31.255.255");
ipCBegin = ConvertToNumber("192.168.0.0");
ipCEnd = ConvertToNumber("192.168.255.255");
}
#endregion
#region Public Methods
/// <summary>
/// ip address convert to long
/// </summary>
/// <param name="ipAddress"></param>
/// <returns></returns>
private static long ConvertToNumber(string ipAddress)
{
return ConvertToNumber(IPAddress.Parse(ipAddress));
}
/// <summary>
/// ip address convert to long
/// </summary>
/// <param name="ipAddress"></param>
/// <returns></returns>
private static long ConvertToNumber(IPAddress ipAddress)
{
var bytes = ipAddress.GetAddressBytes();
return bytes[0] * 256 * 256 * 256 + bytes[1] * 256 * 256 + bytes[2] * 256 + bytes[3];
}
/// <summary>
/// true表示为内网IP
/// </summary>
/// <param name="ipAddress"></param>
/// <returns></returns>
public static bool IsIntranet(string ipAddress)
{
return IsIntranet(ConvertToNumber(ipAddress));
}
/// <summary>
/// true表示为内网IP
/// </summary>
/// <param name="ipAddress"></param>
/// <returns></returns>
private static bool IsIntranet(IPAddress ipAddress)
{
return IsIntranet(ConvertToNumber(ipAddress));
}
/// <summary>
/// true表示为内网IP
/// </summary>
/// <param name="longIP"></param>
/// <returns></returns>
private static bool IsIntranet(long longIP)
{
return ((longIP >= ipABegin) && (longIP <= ipAEnd) ||
(longIP >= ipBBegin) && (longIP <= ipBEnd) ||
(longIP >= ipCBegin) && (longIP <= ipCEnd));
}
/// <summary>
/// 获取本机内网IP
/// </summary>
/// <returns></returns>
public static IPAddress GetLocalIntranetIP()
{
return NetworkInterface
.GetAllNetworkInterfaces()
.Select(p => p.GetIPProperties())
.SelectMany(p =>
p.UnicastAddresses
).FirstOrDefault(p => p.Address.AddressFamily == AddressFamily.InterNetwork
&& !IPAddress.IsLoopback(p.Address)
&& IsIntranet(p.Address))?.Address;
}
/// <summary>
/// 获取本机内网IP列表
/// </summary>
/// <returns></returns>
public static List<IPAddress> GetLocalIntranetIPList()
{
var infList =NetworkInterface.GetAllNetworkInterfaces()
.Select(p => p.GetIPProperties())
.SelectMany(p => p.UnicastAddresses)
.Where(p =>
p.Address.AddressFamily == AddressFamily.InterNetwork
&& !IPAddress.IsLoopback(p.Address)
&& IsIntranet(p.Address)
);
var result = new List<IPAddress>();
foreach (var child in infList)
{
result.Add(child.Address);
}
return result;
}
#endregion
}
}

@ -1,7 +1,87 @@
using System;
namespace DotXxlJob.Core
{
public class Preconditions
/// <summary>
/// Utility methods to simplify checking preconditions in the code.
/// </summary>
internal static class Preconditions
{
/// <summary>
/// Throws <see cref="ArgumentException"/> if condition is false.
/// </summary>
/// <param name="condition">The condition.</param>
public static void CheckArgument(bool condition)
{
if (!condition)
{
throw new ArgumentException();
}
}
/// <summary>
/// Throws <see cref="ArgumentException"/> with given message if condition is false.
/// </summary>
/// <param name="condition">The condition.</param>
/// <param name="errorMessage">The error message.</param>
public static void CheckArgument(bool condition, string errorMessage)
{
if (!condition)
{
throw new ArgumentException(errorMessage);
}
}
/// <summary>
/// Throws <see cref="ArgumentNullException"/> if reference is null.
/// </summary>
/// <param name="reference">The reference.</param>
public static T CheckNotNull<T>(T reference)
{
if (reference == null)
{
throw new ArgumentNullException();
}
return reference;
}
/// <summary>
/// Throws <see cref="ArgumentNullException"/> if reference is null.
/// </summary>
/// <param name="reference">The reference.</param>
/// <param name="paramName">The parameter name.</param>
public static T CheckNotNull<T>(T reference, string paramName)
{
if (reference == null)
{
throw new ArgumentNullException(paramName);
}
return reference;
}
/// <summary>
/// Throws <see cref="InvalidOperationException"/> if condition is false.
/// </summary>
/// <param name="condition">The condition.</param>
public static void CheckState(bool condition)
{
if (!condition)
{
throw new InvalidOperationException();
}
}
/// <summary>
/// Throws <see cref="InvalidOperationException"/> with given message if condition is false.
/// </summary>
/// <param name="condition">The condition.</param>
/// <param name="errorMessage">The error message.</param>
public static void CheckState(bool condition, string errorMessage)
{
if (!condition)
{
throw new InvalidOperationException(errorMessage);
}
}
}
}

@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using DotXxlJob.Core.Model;
using DotXxlJob.Core.Queue;
using Microsoft.Extensions.Logging;
namespace DotXxlJob.Core

@ -90,7 +90,7 @@ namespace DotXxlJob.Core
}
catch (Exception ex)
{
_logger.LogError(ex, "ReadLog error.");
this._logger.LogError(ex, "ReadLog error.");
}
// result
@ -114,7 +114,7 @@ namespace DotXxlJob.Core
private string MakeLogFileName(long logDateTime, int logId)
{
//log fileName like: logPath/HandlerLogs/yyyy-MM-dd/9999.log
return Path.Combine(_options.LogPath, Constants.HandleLogsDirectory,
return Path.Combine(this._options.LogPath, Constants.HandleLogsDirectory,
logDateTime.FromMilliseconds().ToString("yyyy-MM-dd"), $"{logId}.log");
}
private void LogDetail(string logFileName, StackFrame callInfo, string appendLog)
@ -147,16 +147,16 @@ namespace DotXxlJob.Core
private void CleanOldLogs()
{
if (_options.LogRetentionDays <= 0)
if (this._options.LogRetentionDays <= 0)
{
return;
this._options.LogRetentionDays = Constants.DefaultLogRetentionDays;
}
Task.Run(() =>
{
try
{
var handlerLogsDir = new DirectoryInfo(Path.Combine(_options.LogPath, Constants.HandleLogsDirectory));
var handlerLogsDir = new DirectoryInfo(Path.Combine(this._options.LogPath, Constants.HandleLogsDirectory));
if (!handlerLogsDir.Exists)
{
return;
@ -167,7 +167,7 @@ namespace DotXxlJob.Core
{
if (DateTime.TryParse(dir.Name, out var dirDate))
{
if (today.Subtract(dirDate.Date).Days > _options.LogRetentionDays)
if (today.Subtract(dirDate.Date).Days > this._options.LogRetentionDays)
{
dir.Delete(true);
}
@ -176,7 +176,7 @@ namespace DotXxlJob.Core
}
catch (Exception ex)
{
_logger.LogError(ex, "CleanOldLogs error.");
this._logger.LogError(ex, "CleanOldLogs error.");
}
});
}

@ -2,7 +2,7 @@ using System.Runtime.Serialization;
namespace DotXxlJob.Core.Model
{
[DataContract(Name = "com.xxl.job.core.biz.model.HandleCallbackParam")]
[DataContract(Name = Constants.HandleCallbackParamJavaFullName)]
public class HandleCallbackParam
{
public HandleCallbackParam()

@ -2,7 +2,7 @@ using System.Runtime.Serialization;
namespace DotXxlJob.Core.Model
{
[DataContract(Name = "java.lang.Class")]
[DataContract(Name = Constants.JavaClassFulName)]
public class JavaClass
{
[DataMember(Name = "name",Order = 1)]

@ -2,7 +2,7 @@ using System.Runtime.Serialization;
namespace DotXxlJob.Core.Model
{
[DataContract(Name = "com.xxl.job.core.biz.model.LogResult")]
[DataContract(Name = Constants.LogResultJavaFullName)]
public class LogResult
{

@ -2,13 +2,16 @@ using System.Runtime.Serialization;
namespace DotXxlJob.Core.Model
{
[DataContract(Name = "com.xxl.job.core.biz.model.RegistryParam")]
[DataContract(Name = Constants.RegistryParamJavaFullName)]
public class RegistryParam
{
[DataMember(Name = "registGroup",Order = 1)]
public string RegistryGroup { get; set; }
[DataMember(Name = "registryKey",Order = 2)]
public string RegistryKey { get; set; }
[DataMember(Name = "registryValue",Order = 3)]
public string RegistryValue { get; set; }

@ -2,7 +2,7 @@ using System.Runtime.Serialization;
namespace DotXxlJob.Core
{
[DataContract(Name = "com.xxl.job.core.biz.model.ReturnT")]
[DataContract(Name = Constants.ReturnTJavaFullName)]
public class ReturnT
{
public const int SUCCESS_CODE = 200;

@ -3,7 +3,7 @@ using System.Runtime.Serialization;
namespace DotXxlJob.Core.Model
{
[DataContract(Name = "com.xxl.rpc.remoting.net.params.XxlRpcRequest")]
[DataContract(Name = Constants.RpcRequestJavaFullName)]
public class RpcRequest
{
/*

@ -2,7 +2,7 @@ using System.Runtime.Serialization;
namespace DotXxlJob.Core.Model
{
[DataContract(Name = "com.xxl.rpc.remoting.net.params.XxlRpcResponse")]
[DataContract(Name = Constants.RpcResponseJavaFullName)]
public class RpcResponse
{
[DataMember(Name = "requestId",Order = 1)]

@ -3,7 +3,7 @@ using System.Runtime.Serialization;
namespace DotXxlJob.Core.Model
{
[DataContract(Name = "com.xxl.job.core.biz.model.TriggerParam")]
[DataContract(Name = Constants.TriggerParamJavaFullName)]
public class TriggerParam
{
//static readonly long SerialVersionUID = 42L;

@ -8,7 +8,7 @@ using DotXxlJob.Core.Model;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DotXxlJob.Core
namespace DotXxlJob.Core.Queue
{
public class CallbackTaskQueue:IDisposable
{
@ -16,7 +16,7 @@ namespace DotXxlJob.Core
private readonly IJobLogger _jobLogger;
private readonly RetryCallbackTaskQueue _retryQueue;
private readonly ILogger<CallbackTaskQueue> _logger;
private readonly ConcurrentQueue<HandleCallbackParam> TASK_QUEUE = new ConcurrentQueue<HandleCallbackParam>();
private readonly ConcurrentQueue<HandleCallbackParam> taskQueue = new ConcurrentQueue<HandleCallbackParam>();
private bool _stop;
@ -38,7 +38,7 @@ namespace DotXxlJob.Core
public void Push(HandleCallbackParam callbackParam)
{
TASK_QUEUE.Enqueue(callbackParam);
this.taskQueue.Enqueue(callbackParam);
StartCallBack();
}
@ -76,7 +76,7 @@ namespace DotXxlJob.Core
{
List<HandleCallbackParam> list = new List<HandleCallbackParam>();
while (list.Count < Constants.MaxCallbackRecordsPerRequest && this.TASK_QUEUE.TryDequeue(out var item))
while (list.Count < Constants.MaxCallbackRecordsPerRequest && this.taskQueue.TryDequeue(out var item))
{
list.Add(item);
}
@ -104,7 +104,7 @@ namespace DotXxlJob.Core
{
foreach (var param in list)
{
this._jobLogger.LogSpecialFile(param.LogDateTime, param.LogId, result.Msg??"Empty");
this._jobLogger.LogSpecialFile(param.LogDateTime, param.LogId, result.Msg??"Success");
}
}

@ -52,9 +52,12 @@ namespace DotXxlJob.Core
{
if(!ID_IN_QUEUE.TryAdd(triggerParam.LogId,0))
{
_logger.LogWarning("repeat job task,logId={logId},jobId={jobId}",triggerParam.LogId,triggerParam.JobId);
this._logger.LogWarning("repeat job task,logId={logId},jobId={jobId}",triggerParam.LogId,triggerParam.JobId);
return ReturnT.Failed("repeat job task!");
}
//this._logger.LogWarning("add job with logId={logId},jobId={jobId}",triggerParam.LogId,triggerParam.JobId);
this.TASK_QUEUE.Enqueue(triggerParam);
StartTask();
return ReturnT.SUCCESS;
@ -86,8 +89,8 @@ namespace DotXxlJob.Core
{
return; //running
}
this._cancellationTokenSource =new CancellationTokenSource();
CancellationToken ct = _cancellationTokenSource.Token;
this._cancellationTokenSource = new CancellationTokenSource();
var ct = this._cancellationTokenSource.Token;
this._runTask = Task.Factory.StartNew(async () =>
{
@ -108,10 +111,10 @@ namespace DotXxlJob.Core
if (TASK_QUEUE.TryDequeue(out triggerParam))
{
if (ID_IN_QUEUE.TryRemove(triggerParam.LogId,out _))
if (!ID_IN_QUEUE.TryRemove(triggerParam.LogId,out _))
{
this._logger.LogWarning("remove id in queue failed,logId={logId},jobId={jobId}"
,triggerParam.LogId,triggerParam.JobId);
this._logger.LogWarning("remove queue failed,logId={logId},jobId={jobId},exists={exists}"
,triggerParam.LogId,triggerParam.JobId,ID_IN_QUEUE.ContainsKey(triggerParam.LogId));
}
//set log file;
this._jobLogger.SetLogFile(triggerParam.LogDateTime,triggerParam.LogId);

@ -3,12 +3,13 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DotXxlJob.Core.Json;
using DotXxlJob.Core.Model;
using Microsoft.Extensions.Logging;
namespace DotXxlJob.Core
namespace DotXxlJob.Core.Queue
{
public class RetryCallbackTaskQueue:IDisposable
{
@ -16,7 +17,7 @@ namespace DotXxlJob.Core
private readonly Action<HandleCallbackParam> _actionDoCallback;
private readonly ILogger<RetryCallbackTaskQueue> _logger;
private bool _stop;
private CancellationTokenSource _cancellation;
private Task _runTask;
private readonly string _backupFile;
public RetryCallbackTaskQueue(string backupPath,Action<HandleCallbackParam> actionDoCallback,ILogger<RetryCallbackTaskQueue> logger)
@ -24,11 +25,11 @@ namespace DotXxlJob.Core
this._actionDoCallback = actionDoCallback;
this._logger = logger;
this._backupFile = Path.Combine(backupPath, "xxl-job-callback.log");
this._backupFile = Path.Combine(backupPath, Constants.XxlJobRetryLogsFile);
var dir = Path.GetDirectoryName(backupPath);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
Directory.CreateDirectory(dir ?? throw new Exception("logs path is empty"));
}
StartQueue();
@ -36,12 +37,14 @@ namespace DotXxlJob.Core
private void StartQueue()
{
this._cancellation = new CancellationTokenSource();
var stopToken = this._cancellation.Token;
this._runTask = Task.Factory.StartNew(async () =>
{
while (!this._stop)
while (!stopToken.IsCancellationRequested)
{
await LoadFromFile();
await Task.Delay(Constants.CallbackRetryInterval);
await Task.Delay(Constants.CallbackRetryInterval,stopToken);
}
}, TaskCreationOptions.LongRunning);
@ -51,14 +54,14 @@ namespace DotXxlJob.Core
{
var list = new List<HandleCallbackParam>();
if (!File.Exists(_backupFile))
if (!File.Exists(this._backupFile))
{
return;
}
var nextLine = string.Empty;
using (StreamReader reader = new StreamReader(this._backupFile))
{
string nextLine;
while ((nextLine = await reader.ReadLineAsync()) != null)
{
try
@ -67,7 +70,7 @@ namespace DotXxlJob.Core
}
catch(Exception ex)
{
this._logger.LogError(ex,"de error:{error}",ex.Message);
this._logger.LogError(ex,"read backup file error:{error}",ex.Message);
}
}
@ -99,7 +102,7 @@ namespace DotXxlJob.Core
{
if (item.CallbackRetryTimes >= Constants.MaxCallbackRetryTimes)
{
_logger.LogInformation("callback too many times and will be abandon,logId {logId}", item.LogId);
this._logger.LogInformation("callback too many times and will be abandon,logId {logId}", item.LogId);
}
else
{
@ -112,13 +115,13 @@ namespace DotXxlJob.Core
}
catch (Exception ex)
{
_logger.LogError(ex, "SaveCallbackParams error.");
this._logger.LogError(ex, "SaveCallbackParams error.");
}
}
public void Dispose()
{
this._stop = true;
this._cancellation.Cancel();
this._runTask?.GetAwaiter().GetResult();
}
}

@ -15,26 +15,26 @@ namespace DotXxlJob.Core
private readonly Dictionary<string, ITaskExecutor> _cache = new Dictionary<string, ITaskExecutor>();
public TaskExecutorFactory(IServiceProvider provider)
{
_provider = provider;
this._provider = provider;
Initialize();
}
private void Initialize()
{
var executors = _provider.GetServices<ITaskExecutor>();
var executors = this._provider.GetServices<ITaskExecutor>();
if (executors != null && executors.Any())
var taskExecutors = executors as ITaskExecutor[] ?? executors.ToArray();
if (executors == null || !taskExecutors.Any()) return;
foreach (var item in taskExecutors)
{
foreach (var item in executors)
{
_cache.Add(item.GlueType,item);
}
this._cache.Add(item.GlueType,item);
}
}
public ITaskExecutor GetTaskExecutor(string glueType)
{
return _cache.TryGetValue(glueType, out var executor) ? executor : null;
return this._cache.TryGetValue(glueType, out var executor) ? executor : null;
}
}
}

@ -1,7 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>$(DefineConstants);DOTNETCORE</DefineConstants>
<Description>Hession的编解码实现,并不完全哦</Description>
<Copyright>Xuanye @ 2018</Copyright>
<Authors>Xuanye</Authors>
<AssemblyTitle>Hession DotNet port</AssemblyTitle>
<AssemblyName>Hession</AssemblyName>
<PackageId>Hession</PackageId>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<PackageTags>Hession,xxl-job,protocol</PackageTags>
<PackageReleaseNotes>Hession的编解码实现,并不完全哦</PackageReleaseNotes>
<PackageProjectUrl>https://github.com/xuanye/DotXxlJob</PackageProjectUrl>
<PackageLicense>https://github.com/xuanye/DotXxlJob/blob/master/LICENSE</PackageLicense>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/xuanye/DotXxlJob</RepositoryUrl>
</PropertyGroup>
</Project>

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotXxlJob.Core\DotXxlJob.Core.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,13 @@
using System;
using Xunit;
namespace DotXxlJob.Core.Tests
{
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
}

@ -1,33 +0,0 @@
namespace Hessian.NET.Tests
{
internal static class ByteArray
{
public static bool Equals(byte[] expect, byte[] value)
{
if (null == expect)
{
return null == value;
}
if (null == value)
{
return false;
}
if (expect.Length != value.Length)
{
return false;
}
for (var index = 0; index < expect.Length; index++)
{
if (!expect[index].Equals(value[index]))
{
return false;
}
}
return true;
}
}
}

@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Hessian.NET\Hessian.NET.csproj" />
</ItemGroup>
</Project>

@ -1,246 +0,0 @@
using System;
using System.IO;
using Hessian.Net;
using Xunit;
namespace Hessian.NET.Tests
{
public class HessianInputReaderTests
{
[Fact]
public void BooleanTrue()
{
AssertInput(true, new[] { (byte)'T' }, reader => reader.ReadBoolean());
}
[Fact]
public void BooleanFalse()
{
AssertInput(false, new[] { (byte)'F' }, reader => reader.ReadBoolean());
}
[Fact]
public void BytesCompactEmpty()
{
AssertInput(null, new[] { (byte)'N' }, reader => reader.ReadBytes());
}
[Fact]
public void BytesCompactOne()
{
AssertInput(new[] { (byte)0x01 }, new[] { (byte)0x21, (byte)0x01 }, reader => reader.ReadBytes());
}
[Fact]
public void BytesCompact15()
{
AssertInput(
new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A},
new byte[] {0x2A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A},
reader => reader.ReadBytes()
);
}
[Fact]
public void BytesSingleChunk()
{
AssertInput(
new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x012, 0x13, 0x14, 0x15 },
new byte[] {(byte)'B', 0x00, 0x15, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x012, 0x13, 0x14, 0x15 },
reader => reader.ReadBytes()
);
}
[Fact]
public void BytesMultiChunk()
{
AssertInput(
new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x012, 0x13, 0x14, 0x15 },
new byte[] { (byte)'b', 0x00, 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, (byte)'B', 0x00, 0x05, 0x11, 0x012, 0x13, 0x14, 0x15 },
reader => reader.ReadBytes()
);
}
[Fact]
public void Int32SingleOctet()
{
AssertInput(0, new[] { (byte)0x90 }, reader => reader.ReadInt32());
AssertInput(-16, new[] { (byte)0x80 }, reader => reader.ReadInt32());
AssertInput(47, new[] { (byte)0xBF }, reader => reader.ReadInt32());
}
[Fact]
public void Int32TwoOctets()
{
AssertInput(-2048,new byte[] { 0xC0, 0x00 }, reader => reader.ReadInt32());
AssertInput(-256, new byte[] { 0xC7, 0x00 }, reader => reader.ReadInt32());
AssertInput(2047, new byte[] { 0xCF, 0xFF }, reader => reader.ReadInt32());
}
[Fact]
public void Int32ThreeOctets()
{
AssertInput(-262144, new byte[] { 0xD0, 0x00, 0x00 }, reader => reader.ReadInt32());
AssertInput(262143, new byte[] { 0xD7, 0xFF, 0xFF }, reader => reader.ReadInt32());
}
[Fact]
public void Int32Full()
{
AssertInput(262144, new byte[] { 0x49, 0x00, 0x04, 0x00, 0x00 }, reader => reader.ReadInt32());
}
[Fact]
public void Int64SingleOctet()
{
AssertInput(0L, new byte[] { 0xE0 }, reader => reader.ReadInt64());
AssertInput(-8L, new byte[] { 0xD8 }, reader => reader.ReadInt64());
AssertInput(15L, new byte[] { 0xEF }, reader => reader.ReadInt64());
}
[Fact]
public void Int64TwoOctet()
{
AssertInput(-256L, new byte[] { 0xF7, 0x00 }, reader => reader.ReadInt64());
AssertInput(-2048L, new byte[] { 0xF0, 0x00 }, reader => reader.ReadInt64());
AssertInput(2047L, new byte[] { 0xFF, 0xFF }, reader => reader.ReadInt64());
}
[Fact]
public void Int64ThreeOctet()
{
AssertInput(-262144L, new byte[] { 0x38, 0x00, 0x00 }, reader => reader.ReadInt64());
AssertInput(262143L, new byte[] { 0x3F, 0xFF, 0xFF }, reader => reader.ReadInt64());
}
[Fact]
public void Int64Full()
{
AssertInput(0x80000000L, new byte[] { 0x4C, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 }, reader => reader.ReadInt64());
}
[Fact]
public void DoubleZero()
{
AssertInput(0.0d, new byte[] { 0x5B }, reader => reader.ReadDouble());
}
[Fact]
public void DoubleOne()
{
AssertInput(1.0d, new byte[] { 0x5C }, reader => reader.ReadDouble());
}
[Fact]
public void DoubleOctet()
{
AssertInput(123.0d, new byte[] { 0x5D, 0x7B }, reader => reader.ReadDouble());
}
[Fact]
public void DoubleShort()
{
AssertInput(-32768.0d, new byte[] { 0x5E, 0x80, 0x00 }, reader => reader.ReadDouble());
AssertInput(32767.0d, new byte[] { 0x5E, 0x7F, 0xFF }, reader => reader.ReadDouble());
}
[Fact]
public void DoubleFloat()
{
AssertInput(value => Assert.Equal(Single.MinValue, Convert.ToDouble(value)), new byte[] {0x5F, 0xFF, 0x7F, 0xFF, 0xFF}, reader => reader.ReadDouble());
AssertInput(value => Assert.Equal(Single.MaxValue, Convert.ToDouble(value)), new byte[] {0x5F, 0x7F, 0x7F, 0xFF, 0xFF}, reader => reader.ReadDouble());
}
[Fact]
public void DoubleFull()
{
AssertInput(Double.MaxValue - 1, new byte[] { 0x5A, 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, reader => reader.ReadDouble());
}
[Fact]
public void StringEmpty()
{
AssertInput(String.Empty, new byte[] {0x00}, reader => reader.ReadString());
}
[Fact]
public void StringShort()
{
AssertInput("\u00c3", new byte[] {0x01, 0xC3, 0x83}, reader => reader.ReadString());
AssertInput("hello", new byte[] {0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F}, reader => reader.ReadString());
}
[Fact]
public void StringCompact()
{
const string expected = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat";
var data = new byte[]
{
0x30, 0x8f, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f,
0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f,
0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x65, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69,
0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2c, 0x20, 0x73, 0x65, 0x64, 0x20,
0x64, 0x69, 0x61, 0x6d, 0x20, 0x6e, 0x6f, 0x6e, 0x75, 0x6d, 0x6d, 0x79, 0x20, 0x6e, 0x69, 0x62,
0x68, 0x20, 0x65, 0x75, 0x69, 0x73, 0x6d, 0x6f, 0x64, 0x20, 0x74, 0x69, 0x6e, 0x63, 0x69, 0x64,
0x75, 0x6e, 0x74, 0x20, 0x75, 0x74, 0x20, 0x6c, 0x61, 0x6f, 0x72, 0x65, 0x65, 0x74, 0x20, 0x64,
0x6f, 0x6c, 0x6f, 0x72, 0x65, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x61, 0x20, 0x61, 0x6c, 0x69, 0x71,
0x75, 0x61, 0x6d, 0x20, 0x65, 0x72, 0x61, 0x74, 0x20, 0x76, 0x6f, 0x6c, 0x75, 0x74, 0x70, 0x61,
0x74
};
AssertInput(expected, data, reader => reader.ReadString());
}
[Fact]
public void DateTimeShort()
{
var data = new byte[] { 0x4B, 0x00, 0xE3, 0x83, 0x8F };
AssertInput(new DateTime(1998, 5, 8, 9, 51, 00, DateTimeKind.Utc), data, reader => reader.ReadDateTime());
}
[Fact]
public void DateTimeFull()
{
var data = new byte[] { 0x4A, 0x00, 0x00, 0x00, 0xD0, 0x4B, 0x92, 0x84, 0xB8 };
AssertInput(new DateTime(1998, 5, 8, 9, 51, 31, DateTimeKind.Utc), data, reader => reader.ReadDateTime());
}
private static void AssertInput(Action<object> validator, byte[] input, Func<HessianInputReader, object> action)
{
var memoryStream = new MemoryStream(input);
var reader = new HessianInputReader(memoryStream);
validator(action(reader));
}
private static void AssertInput(object expected, byte[] input, Func<HessianInputReader, object> action)
{
var memoryStream = new MemoryStream(input);
var reader = new HessianInputReader(memoryStream);
Assert.Equal(expected, action(reader));
}
private static void AssertInput(byte[] expected, byte[] input, Func<HessianInputReader, byte[]> action)
{
var memoryStream = new MemoryStream(input);
var reader = new HessianInputReader(memoryStream);
var temp = action(reader);
if (!ReferenceEquals(expected, temp))
{
Assert.Equal(expected.Length,temp.Length);
for (var index = 0; index < expected.Length; index++)
{
Assert.Equal(expected[index], temp[index]);
}
}
}
}
}

@ -1,326 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using Hessian.Net;
using Xunit;
namespace Hessian.NET.Tests
{
//
// http://hessian.caucho.com/doc/hessian-serialization.html##date
//
public class HessianOutputWriterTests
{
[Fact]
public void BooleanTrue()
{
AssertOutput(new[] { (byte)'T' }, writer => writer.WriteBoolean(true));
}
[Fact]
public void BooleanFalse()
{
AssertOutput(new[] { (byte)'F' }, writer => writer.WriteBoolean(false));
}
[Fact]
public void BytesCompactEmpty()
{
AssertOutput(new[] { (byte)0x20 }, writer => writer.WriteBytes(new byte[0]));
}
[Fact]
public void BytesCompactOne()
{
AssertOutput(new[] { (byte)0x21, (byte)0x01 }, writer => writer.WriteBytes(new byte[] { 1 }));
}
[Fact]
public void BytesCompact15()
{
var value = new byte[15];
for (var index = 0; index < value.Length; index++)
{
value[index] = (byte)index;
}
var expected = new byte[16];
expected[0] = (byte)(0x20 + value.Length);
value.CopyTo(expected, 1);
AssertOutput(expected, writer => writer.WriteBytes(value));
}
[Fact]
public void BytesSingleChunk()
{
var data = new byte[255];
for (var index = 0; index < data.Length; index++)
{
data[index] = (byte)index;
}
var expected = new byte[3 + data.Length];
WriteBytesLengthPrefix(expected, 0, data.Length, true);
data.CopyTo(expected, 3);
AssertOutput(expected, writer => writer.WriteBytes(data));
}
[Fact]
public void BytesMultiChunk()
{
const int sigLength = 3;
const int chunkSize = 0x8000;
const int appendixSize = 3;
var buffer = new byte[chunkSize + appendixSize];
for (var index = 0; index < buffer.Length; index++)
{
buffer[index] = (byte)(index + 1);
}
var expected = new byte[sigLength + chunkSize + sigLength + appendixSize];
WriteBytesLengthPrefix(expected, 0, chunkSize, false);
CopyBytes(buffer, 0, expected, sigLength, chunkSize);
WriteBytesLengthPrefix(expected, sigLength + chunkSize, appendixSize, true);
CopyBytes(buffer, chunkSize, expected, sigLength + chunkSize + sigLength, appendixSize);
AssertOutput(expected, writer => writer.WriteBytes(buffer));
}
[Fact]
public void DateTimeShort()
{
var expected = new byte[] { 0x4B, 0x00, 0xE3, 0x83, 0x8F };
AssertOutput(expected, writer => writer.WriteDateTime(new DateTime(1998, 5, 8, 9, 51, 00, DateTimeKind.Utc)));
}
[Fact]
public void DateTimeFull()
{
var expected = new byte[] { 0x4A, 0x00, 0x00, 0x00, 0xD0, 0x4B, 0x92, 0x84, 0xB8 };
AssertOutput(expected, writer => writer.WriteDateTime(new DateTime(1998, 5, 8, 9, 51, 31, DateTimeKind.Utc)));
}
[Fact]
public void DoubleZero()
{
AssertOutput(new byte[] { 0x5B }, writer => writer.WriteDouble(0.0d));
}
[Fact]
public void DoubleOne()
{
AssertOutput(new byte[] { 0x5C }, writer => writer.WriteDouble(1.0d));
}
[Fact]
public void DoubleOctet()
{
var expected = new byte[] { 0x5D, 0x7B };
AssertOutput(expected, writer => writer.WriteDouble(123.0d));
}
[Fact]
public void DoubleShort()
{
AssertOutput(new byte[] { 0x5E, 0x80, 0x00 }, writer => writer.WriteDouble(-32768d));
AssertOutput(new byte[] { 0x5E, 0x7F, 0xFF }, writer => writer.WriteDouble(32767d));
}
[Fact]
public void DoubleFloat()
{
AssertOutput(new byte[] { 0x5F, 0xFF, 0x7F, 0xFF, 0xFF }, writer => writer.WriteDouble(Single.MinValue));
AssertOutput(new byte[] { 0x5F, 0x7F, 0x7F, 0xFF, 0xFF }, writer => writer.WriteDouble(Single.MaxValue));
}
[Fact]
public void DoubleFull()
{
// var expected = new byte[] { 0x5A, 0x40, 0x28, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 };
var expected = new byte[] { 0x5A, 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
AssertOutput(expected, writer => writer.WriteDouble(Double.MaxValue - 1));
}
[Fact]
public void Int32SingleOctet()
{
AssertOutput(new byte[] { 0x80 }, writer => writer.WriteInt32(-16));
AssertOutput(new byte[] { 0x90 }, writer => writer.WriteInt32(0));
AssertOutput(new byte[] { 0xBF }, writer => writer.WriteInt32(47));
}
[Fact]
public void Int32TwoOctets()
{
AssertOutput(new byte[] { 0xC0, 0x00 }, writer => writer.WriteInt32(-2048));
AssertOutput(new byte[] { 0xC7, 0x00 }, writer => writer.WriteInt32(-256));
AssertOutput(new byte[] { 0xCF, 0xFF }, writer => writer.WriteInt32(2047));
}
[Fact]
public void Int32ThreeOctets()
{
AssertOutput(new byte[] { 0xD0, 0x00, 0x00 }, writer => writer.WriteInt32(-262144));
AssertOutput(new byte[] { 0xD7, 0xFF, 0xFF }, writer => writer.WriteInt32(262143));
}
[Fact]
public void Int32Full()
{
AssertOutput(new byte[] { 0x49, 0x00, 0x04, 0x00, 0x00 }, writer => writer.WriteInt32(262144));
}
[Fact]
public void Int64SingleOctet()
{
AssertOutput(new byte[] { 0xD8 }, writer => writer.WriteInt64(-8));
AssertOutput(new byte[] { 0xE0 }, writer => writer.WriteInt64(0));
AssertOutput(new byte[] { 0xEF }, writer => writer.WriteInt64(15));
}
[Fact]
public void Int64TwoOctets()
{
AssertOutput(new byte[] { 0xF0, 0x00 }, writer => writer.WriteInt64(-2048));
AssertOutput(new byte[] { 0xF7, 0x00 }, writer => writer.WriteInt64(-256));
AssertOutput(new byte[] { 0xFF, 0xFF }, writer => writer.WriteInt64(2047));
}
[Fact]
public void Int64ThreeOctets()
{
AssertOutput(new byte[] { 0x38, 0x00, 0x00 }, writer => writer.WriteInt64(-262144));
AssertOutput(new byte[] { 0x3F, 0xFF, 0xFF }, writer => writer.WriteInt64(262143));
}
[Fact]
public void Int64Full()
{
var expected = new byte[] { 0x4C, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 };
AssertOutput(expected, writer => writer.WriteInt64(0x80000000L));
}
[Fact]
public void StringEmpty()
{
AssertOutput(new byte[] {0x00}, writer => writer.WriteString(null));
AssertOutput(new byte[] {0x00}, writer => writer.WriteString(String.Empty));
}
[Fact]
public void StringShort()
{
AssertOutput(new byte[] { 0x01, 0xC3, 0x83 }, writer => writer.WriteString("\u00c3"));
AssertOutput(new byte[] { 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F }, writer => writer.WriteString("hello"));
}
[Fact]
public void StringCompact()
{
const string text = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat";
var expected = new byte[]
{
0x30, 0x8f, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f,
0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f,
0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x65, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69,
0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2c, 0x20, 0x73, 0x65, 0x64, 0x20,
0x64, 0x69, 0x61, 0x6d, 0x20, 0x6e, 0x6f, 0x6e, 0x75, 0x6d, 0x6d, 0x79, 0x20, 0x6e, 0x69, 0x62,
0x68, 0x20, 0x65, 0x75, 0x69, 0x73, 0x6d, 0x6f, 0x64, 0x20, 0x74, 0x69, 0x6e, 0x63, 0x69, 0x64,
0x75, 0x6e, 0x74, 0x20, 0x75, 0x74, 0x20, 0x6c, 0x61, 0x6f, 0x72, 0x65, 0x65, 0x74, 0x20, 0x64,
0x6f, 0x6c, 0x6f, 0x72, 0x65, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x61, 0x20, 0x61, 0x6c, 0x69, 0x71,
0x75, 0x61, 0x6d, 0x20, 0x65, 0x72, 0x61, 0x74, 0x20, 0x76, 0x6f, 0x6c, 0x75, 0x74, 0x70, 0x61,
0x74
};
AssertOutput(expected, writer => writer.WriteString(text));
}
private static void AssertOutput(byte[] expected, Action<HessianOutputWriter> action)
{
var stream = new MemoryStream();
byte[] bytes;
using (var writer = new HessianOutputWriter(stream))
{
action(writer);
bytes = stream.ToArray();
}
//WriteOutput(bytes);
Assert.True(ByteArray.Equals(expected, bytes));
}
private static void WriteOutput(byte[] bytes)
{
for (var offset = 0; offset < bytes.Length;)
{
var count = Math.Min(bytes.Length - offset, 16);
var line = new StringBuilder();
line.AppendFormat("{0:X08}: ", offset);
for (var position = 0; position < count; position++)
{
line.AppendFormat("{0:x02} ", bytes[offset]);
offset++;
}
Debug.WriteLine(line.ToString());
}
}
private static void WriteBytesLengthPrefix(byte[] buffer, int offset, int length, bool final)
{
buffer[offset] = final ? (byte)'B' : (byte)'b';
buffer[offset + 1] = (byte) (length >> 8);
buffer[offset + 2] = (byte) length;
}
private static void CopyBytes(byte[] source, int srcoffset, byte[] dest, int destoffset, int count)
{
for (var index = 0; index < count; index++)
{
dest[destoffset + index] = source[srcoffset + index];
}
}
/*private static void AssertOutput(bool value, byte[] expected)
{
var stream = new MemoryStream();
byte[] bytes;
using (var writer = new HessianOutputWriter(stream))
{
writer.WriteBoolean(value);
bytes = stream.ToArray();
}
Assert.IsTrue(ByteArray.Equals(expected, bytes));
}*/
/*private static void AssertOutput(byte[] value, byte[] expected)
{
var stream = new MemoryStream();
byte[] bytes;
using (var writer = new HessianOutputWriter(stream))
{
writer.WriteBytes(value);
bytes = stream.ToArray();
}
Assert.IsTrue(ByteArray.Equals(expected, bytes));
}*/
}
}

@ -1,167 +0,0 @@
using System.IO;
using System.Runtime.Serialization;
using Hessian.Net;
using Xunit;
namespace Hessian.NET.Tests
{
public class HessianSerializerTests
{
[Fact]
public void SimpleSerialize()
{
byte[] expected =
{
0x43, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x31, 0x92, 0x06, 0x43, 0x6c,
0x61, 0x73, 0x73, 0x32, 0x04, 0x4e, 0x65, 0x78, 0x74, 0x60, 0x43, 0x0a, 0x54, 0x65, 0x73, 0x74,
0x43, 0x6c, 0x61, 0x73, 0x73, 0x32, 0x93, 0x03, 0x49, 0x6e, 0x74, 0x06, 0x53, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x61, 0x91, 0x0b, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20,
0x49, 0x70, 0x73, 0x75, 0x6d, 0x51, 0x90, 0x51, 0x90
};
var graph = new TestClass1
{
Class2 = new TestClass2
{
IntValue = 1,
StringValue = "Lorem Ipsum"
}
};
graph.Next = graph;
graph.Class2.Parent = graph;
using (var stream = new MemoryStream())
{
var serializer = new DataContractHessianSerializer(typeof (TestClass1));
serializer.WriteObject(stream, graph);
stream.Flush();
Assert.True(ByteArray.Equals(expected, stream.ToArray()));
}
}
[Fact]
public void SimpleDeserialize()
{
byte[] expected =
{
0x43, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x31, 0x92, 0x06, 0x43, 0x6c,
0x61, 0x73, 0x73, 0x32, 0x04, 0x4e, 0x65, 0x78, 0x74, 0x60, 0x43, 0x0a, 0x54, 0x65, 0x73, 0x74,
0x43, 0x6c, 0x61, 0x73, 0x73, 0x32, 0x93, 0x03, 0x49, 0x6e, 0x74, 0x06, 0x53, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x61, 0x91, 0x0b, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20,
0x49, 0x70, 0x73, 0x75, 0x6d, 0x51, 0x90, 0x51, 0x90
};
var graph = new TestClass1
{
Class2 = new TestClass2
{
IntValue = 1,
StringValue = "Lorem Ipsum"
}
};
graph.Next = graph;
graph.Class2.Parent = graph;
using (var stream = new MemoryStream(expected))
{
var serializer = new DataContractHessianSerializer(typeof (TestClass1));
var instance = serializer.ReadObject(stream) as TestClass1;
Assert.NotNull(instance);
Assert.Equal(graph.GetType(), instance.GetType());
Assert.Equal(instance, instance.Next);
Assert.Equal(typeof (TestClass2), instance.Class2.GetType());
Assert.Equal(instance, instance.Class2.Parent);
Assert.Equal(graph.Class2.IntValue, instance.Class2.IntValue);
Assert.Equal(graph.Class2.StringValue, instance.Class2.StringValue);
}
}
/*
private static void WriteOutput(IReadOnlyList<byte> bytes)
{
const int size = 16;
var pattern = new String(' ', 3);
for (var offset = 0; offset < bytes.Count; )
{
var count = Math.Min(bytes.Count - offset, size);
var line = new StringBuilder();
line.AppendFormat("{0:X08}: ", offset);
for (var position = 0; position < size; position++)
{
if (position < count)
{
line.AppendFormat("{0:x02} ", bytes[position + offset]);
}
else
{
line.Append(pattern);
}
}
line.Append(new string(' ', 2));
for (var position = 0; position < count; position++)
{
var ch = (char) bytes[position + offset];
line.Append(Char.IsLetterOrDigit(ch) ? ch : '.');
}
Debug.WriteLine(line.ToString());
offset += count;
}
}
*/
[DataContract(Name = "TestClass1")]
public class TestClass1
{
[DataMember(Name = "Class2")]
public TestClass2 Class2
{
get;
set;
}
[DataMember(Name = "Next")]
public TestClass1 Next
{
get;
set;
}
}
[DataContract(Name = "TestClass2")]
public class TestClass2
{
[DataMember(Name = "Int", Order = 1)]
public int IntValue
{
get;
set;
}
[DataMember(Name = "String", Order = 2)]
public string StringValue
{
get;
set;
}
[DataMember(Name = "Link", Order = 3)]
public TestClass1 Parent
{
get;
set;
}
}
}
}

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Hessian\Hessian.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,13 @@
using System;
using Xunit;
namespace Hessian.Tests
{
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Cake" version="0.32.1" />
</packages>
Loading…
Cancel
Save