@@ -0,0 +1,30 @@ | |||
**/.classpath | |||
**/.dockerignore | |||
**/.env | |||
**/.git | |||
**/.gitignore | |||
**/.project | |||
**/.settings | |||
**/.toolstarget | |||
**/.vs | |||
**/.vscode | |||
**/*.*proj.user | |||
**/*.dbmdl | |||
**/*.jfm | |||
**/azds.yaml | |||
**/bin | |||
**/charts | |||
**/docker-compose* | |||
**/Dockerfile* | |||
**/node_modules | |||
**/npm-debug.log | |||
**/obj | |||
**/secrets.dev.yaml | |||
**/values.dev.yaml | |||
LICENSE | |||
README.md | |||
!**/.gitignore | |||
!.git/HEAD | |||
!.git/config | |||
!.git/packed-refs | |||
!.git/refs/heads/** |
@@ -0,0 +1,9 @@ | |||
*.user | |||
*.suo | |||
.vs/ | |||
*.log | |||
obj/ | |||
bin/ | |||
.idea/ | |||
.vscode/ | |||
publish/ |
@@ -0,0 +1,7 @@ | |||
namespace NearCardAttendance.Common | |||
{ | |||
public class Class1 | |||
{ | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace NearCardAttendance.Common | |||
{ | |||
public class Consts | |||
{ | |||
/// <summary> | |||
/// HttpClient常用配置分组名称 | |||
/// </summary> | |||
public const string DEFAULT_HTTPCLIENT_NAME = "DEFAULT_HTTP"; | |||
/// <summary> | |||
/// 纪元时间(UTC时间戳起始计算时间) | |||
/// </summary> | |||
public static DateTime EraUtcTime = DateTime.Parse("1970/01/01"); | |||
/// <summary> | |||
/// 有效定位的半径阈值(大于该值则为无效定位) | |||
/// </summary> | |||
public static int RadiusThreshold = 150; | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
<ImplicitUsings>enable</ImplicitUsings> | |||
<Nullable>enable</Nullable> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" /> | |||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,128 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net.Http.Headers; | |||
using System.Net.Security; | |||
using System.Net; | |||
using System.Security.Cryptography.X509Certificates; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Newtonsoft.Json; | |||
using Microsoft.Extensions.Logging; | |||
namespace NearCardAttendance.Common.helper | |||
{ | |||
public class HttpHelper | |||
{ | |||
private readonly IHttpClientFactory _httpClientFactory; | |||
private readonly ILogger<HttpHelper> _logger; | |||
public HttpHelper(IHttpClientFactory httpClientFactory, ILogger<HttpHelper> logger) | |||
{ | |||
_httpClientFactory = httpClientFactory; | |||
_logger = logger; | |||
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult!); | |||
} | |||
public static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) | |||
{ | |||
return true; | |||
} | |||
public async Task<string?> HttpToGetAsync(string url) | |||
{ | |||
return await HttpToGetAsync(url, new List<KeyValuePair<string, string>>()); | |||
} | |||
public async Task<string?> HttpToGetAsync(string url, List<KeyValuePair<string, string>> headers) | |||
{ | |||
var client = _httpClientFactory.CreateClient(Consts.DEFAULT_HTTPCLIENT_NAME); | |||
if (headers != null && headers.Count > 0) //指定请求头 | |||
{ | |||
headers.ForEach(e => | |||
{ | |||
client.DefaultRequestHeaders.Add(e.Key, e.Value); | |||
}); | |||
} | |||
try | |||
{ | |||
using (var response = await client.GetAsync(url).ConfigureAwait(false)) | |||
{ | |||
if (!response.IsSuccessStatusCode) return null; | |||
else | |||
{ | |||
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); | |||
return result; | |||
} | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogError($"HttpToGetAsync发生异常, Msg:{ex.Message}\n{ex.StackTrace}"); | |||
return null; | |||
} | |||
} | |||
public async Task<string?> HttpToPostAsync(string url, object data) | |||
{ | |||
return await HttpToPostAsync(url, data, new List<KeyValuePair<string, string>>()); | |||
} | |||
public async Task<string?> HttpToPostAsync(string url, object data, List<KeyValuePair<string, string>> headers) | |||
{ | |||
if (data == null) return null; | |||
var client = _httpClientFactory.CreateClient(Consts.DEFAULT_HTTPCLIENT_NAME); | |||
if (headers != null && headers.Count > 0) //指定请求头 | |||
{ | |||
headers.ForEach(e => | |||
{ | |||
client.DefaultRequestHeaders.Add(e.Key, e.Value); | |||
}); | |||
} | |||
string? body; | |||
if (data is string) body = data as string; | |||
else body = JsonConvert.SerializeObject(data); | |||
try | |||
{ | |||
using (var content = new StringContent(body!)) | |||
{ | |||
content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); | |||
using (var response = await client.PostAsync(url, content).ConfigureAwait(false)) | |||
{ | |||
if (!response.IsSuccessStatusCode) return null; | |||
else | |||
{ | |||
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); | |||
return result; | |||
} | |||
} | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogError($"HttpToPostAsync发生异常, Msg:{ex.Message}\n{ex.StackTrace}"); | |||
return null; | |||
} | |||
} | |||
/// <summary> | |||
/// Authorization 的 Base64 加密 | |||
/// </summary> | |||
/// <param name="username"></param> | |||
/// <param name="password"></param> | |||
/// <returns></returns> | |||
public string GetEncodedCredentials(string username, string password) | |||
{ | |||
string mergedCredentials = string.Format("{0}:{1}", username, password); | |||
byte[] byteCredentials = UTF8Encoding.UTF8.GetBytes(mergedCredentials); | |||
return Convert.ToBase64String(byteCredentials); | |||
} | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
namespace NearCardAttendance.Model | |||
{ | |||
public class Class1 | |||
{ | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
<ImplicitUsings>enable</ImplicitUsings> | |||
<Nullable>enable</Nullable> | |||
</PropertyGroup> | |||
</Project> |
@@ -0,0 +1,7 @@ | |||
namespace NearCardAttendance.Service | |||
{ | |||
public class Class1 | |||
{ | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
<ImplicitUsings>enable</ImplicitUsings> | |||
<Nullable>enable</Nullable> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" /> | |||
<PackageReference Include="DotNetty.Transport" Version="0.7.5" /> | |||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\NearCardAttendance.Common\NearCardAttendance.Common.csproj" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,91 @@ | |||
using DotNetty.Buffers; | |||
using DotNetty.Transport.Channels; | |||
using Microsoft.Extensions.Logging; | |||
using NearCardAttendance.Service.TcpServer.Protocol; | |||
using System; | |||
using System.Text; | |||
namespace NearCardAttendance.Service.TcpServer.Handler | |||
{ | |||
public class ProtocolHandler : SimpleChannelInboundHandler<IByteBuffer> | |||
{ | |||
private IByteBuffer? buffer; | |||
private readonly ILogger<ProtocolHandler> _logger; | |||
public ProtocolHandler(ILogger<ProtocolHandler> logger) | |||
{ | |||
_logger = logger; | |||
} | |||
public override async void ChannelActive(IChannelHandlerContext context) | |||
{ | |||
// Handle channel active event | |||
} | |||
public override void ChannelInactive(IChannelHandlerContext context) | |||
{ | |||
ReleaseBuffer(); | |||
base.ChannelInactive(context); | |||
} | |||
protected override void ChannelRead0(IChannelHandlerContext context, IByteBuffer message) | |||
{ | |||
try | |||
{ | |||
string content = message.ToString(Encoding.ASCII); | |||
_logger.LogInformation($"{nameof(ProtocolHandler)} -- {nameof(ChannelRead0)} -- 最开始接受内容:{content}"); | |||
ProcessMessage(context, content); | |||
} | |||
catch (Exception ex) | |||
{ | |||
ReleaseBuffer(); | |||
_logger.LogInformation($"{nameof(ProtocolHandler)} --- {nameof(ChannelRead0)} 处理出错\n{ex.Message}\n{ex.StackTrace}"); | |||
} | |||
} | |||
private void ProcessMessage(IChannelHandlerContext context, string content) | |||
{ | |||
buffer ??= Unpooled.Buffer(); | |||
byte[] bytes = Encoding.ASCII.GetBytes(content); | |||
buffer.WriteBytes(Unpooled.WrappedBuffer(bytes)); | |||
if (int.TryParse(buffer.ToString(Encoding.ASCII).Substring(0, 4), out int messageLength)) | |||
{ | |||
Console.WriteLine(buffer.ToString(Encoding.ASCII)); | |||
if (buffer.ToString(Encoding.ASCII).Length == messageLength) | |||
{ | |||
//var parser = buffer.ToString(Encoding.ASCII); | |||
ProtocolParser parser = new(buffer.ToString(Encoding.ASCII)); | |||
context.FireChannelRead(parser); | |||
Console.WriteLine($"发送正常:{parser}"); | |||
//ProtocolWrapper wrapper = new ProtocolWrapper("82", parser.SeqNo, DateTime.Now.ToString("yyyyMMddHHmmss")); | |||
//Console.WriteLine(wrapper.GenerateProtocolString()); | |||
Console.WriteLine($"length:{parser.MessageLength},func_no:{parser.FuncNo},seq_no:{parser.SeqNo},data:{ parser.Data}"); | |||
ReleaseBuffer(); | |||
} | |||
else if (buffer.ToString(Encoding.ASCII).Length > messageLength) | |||
{ | |||
// var parser = buffer.ToString(Encoding.ASCII).Substring(0, messageLength); | |||
ProtocolParser parser = new(buffer.ToString(Encoding.ASCII).Substring(0, messageLength)); | |||
context.FireChannelRead(parser); | |||
//ReleaseBuffer(); | |||
var overLongbuffer = Unpooled.Buffer(); | |||
Console.WriteLine($"过长消息:{buffer.ToString(Encoding.ASCII).Substring(messageLength)}"); | |||
overLongbuffer.WriteBytes(Unpooled.WrappedBuffer(Encoding.ASCII.GetBytes(buffer.ToString(Encoding.ASCII).Substring(messageLength)))); | |||
ReleaseBuffer(); | |||
buffer = overLongbuffer; | |||
Console.WriteLine($"剩余消息{buffer.ToString(Encoding.ASCII)}"); | |||
} | |||
} | |||
} | |||
private void ReleaseBuffer() | |||
{ | |||
buffer?.Release(); | |||
buffer = null; | |||
} | |||
} | |||
} |
@@ -0,0 +1,142 @@ | |||
using DotNetty.Buffers; | |||
using DotNetty.Transport.Channels; | |||
using Microsoft.Extensions.Logging; | |||
using NearCardAttendance.Common.helper; | |||
using NearCardAttendance.Service.TcpServer.Mapper; | |||
using NearCardAttendance.Service.TcpServer.Protocol; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace NearCardAttendance.Service.TcpServer.Handler | |||
{ | |||
public class RegisterHandler : ChannelHandlerAdapter | |||
{ | |||
private readonly ILogger<RegisterHandler> _logger; | |||
private readonly HttpHelper _httpHelper = default!; | |||
private readonly IDisposable _loggerScope = default!; | |||
private readonly TcpClientsManager _managerTcpClients; | |||
private readonly ScheduleResendManager _managerScheduleResend; | |||
public RegisterHandler(ILogger<RegisterHandler> logger,HttpHelper httpHelper) | |||
{ | |||
_logger = logger; | |||
_httpHelper = httpHelper; | |||
} | |||
public override void ChannelActive(IChannelHandlerContext context) | |||
{ | |||
base.ChannelActive(context); // Pass the event to the next handler | |||
} | |||
public override async void ChannelInactive(IChannelHandlerContext context) | |||
{ | |||
try | |||
{ | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogInformation($"{nameof(RegisterHandler)} --- {nameof(ChannelInactive)} 出错\n{ex.Message}\n{ex.StackTrace}"); | |||
} | |||
} | |||
public override void HandlerRemoved(IChannelHandlerContext context) | |||
{ | |||
base.HandlerRemoved(context); | |||
} | |||
public override async void ChannelRead(IChannelHandlerContext context, object message) | |||
{ | |||
if (message is ProtocolParser parser) | |||
{ | |||
try | |||
{ | |||
using (_logger.BeginScope(new Dictionary<string, object> { ["RequestId"] = parser.SeqNo })) | |||
{ | |||
//认证 | |||
if (parser.FuncNo.Equals("10")) | |||
{ | |||
#region 认证业务 | |||
// PHONE_AUTHEN | |||
ProtocolWrapper phoneAuthWrapper = new(parser.FuncNo, parser.SeqNo, "1"); | |||
await SendToTcpClientAsync(phoneAuthWrapper, context.Channel); | |||
// ABT_STATUS 延时3秒 给终端同步时间 | |||
await context.Channel.EventLoop.Schedule(async () => | |||
{ | |||
ProtocolWrapper abtStatusWrapper = new("82", parser.SeqNo, DateTime.Now.ToString("yyyyMMddHHmmss")); | |||
await SendToTcpClientAsync(abtStatusWrapper, context.Channel); | |||
},TimeSpan.FromSeconds(3)); | |||
#endregion | |||
} | |||
else if (parser.FuncNo.Equals("82")) | |||
{ | |||
// 认证成功 | |||
_logger.LogInformation($"认证成功{parser.Data}."); | |||
} | |||
// 心跳 | |||
else if (parser.FuncNo.Equals("05")) | |||
{ | |||
ProtocolWrapper stdtSignRecsWrapper = new(parser.FuncNo, parser.SeqNo,""); | |||
await SendToTcpClientAsync(stdtSignRecsWrapper, context.Channel); | |||
} // STDT_SIGN_RECS | |||
else if (parser.FuncNo.Equals("04")) | |||
{ | |||
// 回应设备 | |||
ProtocolWrapper stdtSignRecsWrapper = new(parser.FuncNo, parser.SeqNo,"1"); | |||
await SendToTcpClientAsync(stdtSignRecsWrapper, context.Channel); | |||
// 刷卡考勤信息,需要推送给第三方平台 | |||
//var url = ""; | |||
//await _httpHelper.HttpToPostAsync(url, new object()); | |||
} | |||
//switch (parser.FuncNo) | |||
//{ | |||
// case "10": | |||
// break; | |||
// case "82": | |||
// break; | |||
// default: | |||
// break; | |||
//} | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogInformation($"{nameof(RegisterHandler)} --- {nameof(ChannelRead)} 处理消息 {parser.SeqNo} 出错\n{ex.Message}\n{ex.StackTrace}"); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// 发送到TCP客户端 | |||
/// </summary> | |||
/// <param name="wrapper"></param> | |||
/// <param name="channel"></param> | |||
private async Task SendToTcpClientAsync(ProtocolWrapper wrapper, IChannel channel) | |||
{ | |||
string protocolMsg = wrapper.GenerateProtocolString(); | |||
// 发送protocolMsg到tcp客户端 | |||
await channel | |||
.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(protocolMsg))) | |||
.ContinueWith(Action => { | |||
_logger.LogInformation($"{nameof(RegisterHandler)} -- {nameof(SendToTcpClientAsync)} -- 下发设备内容:\n{protocolMsg}"); | |||
}); | |||
} | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
using DotNetty.Common.Concurrency; | |||
using Microsoft.Extensions.Logging; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace NearCardAttendance.Service.TcpServer.Mapper | |||
{ | |||
public class ScheduleResendManager : ConcurrentDictionary<string, IScheduledTask> | |||
{ | |||
private readonly ILogger<ScheduleResendManager> _logger; | |||
public ScheduleResendManager(ILogger<ScheduleResendManager> logger) | |||
{ | |||
_logger = logger; | |||
} | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
using DotNetty.Transport.Channels; | |||
using Microsoft.Extensions.Logging; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace NearCardAttendance.Service.TcpServer.Mapper | |||
{ | |||
public class TcpClientsManager : ConcurrentDictionary<string, IChannel> | |||
{ | |||
private readonly ILogger<TcpClientsManager> _logger; | |||
public TcpClientsManager(ILogger<TcpClientsManager> logger) | |||
{ | |||
_logger = logger; | |||
} | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace NearCardAttendance.Service.TcpServer.Protocol | |||
{ | |||
public class ProtocolParser | |||
{ | |||
public int MessageLength { get; private set; } = default!; | |||
public string FuncNo { get; private set; } = default!; | |||
public string SeqNo { get; private set; } = default!; | |||
public string Data { get; private set; } = default!; | |||
//private string _protocolString { get; set; } = default!; | |||
public ProtocolParser(string protocolString) | |||
{ | |||
ParseProtocolString(protocolString); | |||
//_protocolString = protocolString; | |||
} | |||
private void ParseProtocolString(string protocolString) | |||
{ | |||
try | |||
{ | |||
_ = int.TryParse(protocolString.AsSpan(0, 4), out int messageLength); | |||
MessageLength = messageLength; | |||
FuncNo = protocolString.Substring(4,2); | |||
SeqNo = protocolString.Substring(6,4); | |||
Data = protocolString.Substring(10).TrimEnd(); | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine($"Error: {e.Message}"); | |||
// throw new ArgumentException("Invalid protocol string format."); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace NearCardAttendance.Service.TcpServer.Protocol | |||
{ | |||
public class ProtocolWrapper | |||
{ | |||
private string Length { get; } | |||
private string FuncNo { get; } | |||
private string SeqNo { get; } | |||
private string Data { get; } | |||
public ProtocolWrapper(string funcNo,string seqNo,string data) | |||
{ | |||
FuncNo =funcNo; | |||
SeqNo=seqNo; | |||
Data=data; | |||
Length = CalculateMessageLength(); | |||
} | |||
private string CalculateMessageLength() | |||
{ | |||
return string.Format("{0:D4}", GenerateProtocolString().Length + 4); | |||
} | |||
public string GenerateProtocolString() | |||
{ | |||
return $"{Length}{FuncNo}{SeqNo}{Data}"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
using Serilog.Configuration; | |||
using Serilog.Core; | |||
using Serilog.Events; | |||
using Serilog; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace NearCardAttendance.TcpServer.Config | |||
{ | |||
public class ThreadInfoEnricher : ILogEventEnricher | |||
{ | |||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) | |||
{ | |||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadId", Thread.CurrentThread.ManagedThreadId)); | |||
} | |||
} | |||
public static class EnricherExtensions | |||
{ | |||
public static LoggerConfiguration WithThreadInfo(this LoggerEnrichmentConfiguration enrich) | |||
{ | |||
if (enrich == null) | |||
throw new ArgumentNullException(nameof(enrich)); | |||
return enrich.With<ThreadInfoEnricher>(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. | |||
#FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base | |||
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base | |||
WORKDIR /app | |||
EXPOSE 16662 | |||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build | |||
ARG BUILD_CONFIGURATION=Release | |||
WORKDIR /src | |||
COPY ["NearCardAttendance.TcpServer/NearCardAttendance.TcpServer.csproj", "NearCardAttendance.TcpServer/"] | |||
COPY ["NearCardAttendance.Common/NearCardAttendance.Common.csproj", "NearCardAttendance.Common/"] | |||
COPY ["NearCardAttendance.Service/NearCardAttendance.Service.csproj", "NearCardAttendance.Service/"] | |||
RUN dotnet restore "./NearCardAttendance.TcpServer/./NearCardAttendance.TcpServer.csproj" | |||
COPY . . | |||
WORKDIR "/src/NearCardAttendance.TcpServer" | |||
RUN dotnet build "./NearCardAttendance.TcpServer.csproj" -c $BUILD_CONFIGURATION -o /app/build | |||
FROM build AS publish | |||
ARG BUILD_CONFIGURATION=Release | |||
RUN dotnet publish "./NearCardAttendance.TcpServer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false | |||
FROM base AS final | |||
WORKDIR /app | |||
COPY --from=publish /app/publish . | |||
ENV environment=Development | |||
ENV TimeZone=Asia/Shanghai | |||
ENV LANG C.UTF-8 | |||
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone | |||
#ENTRYPOINT ["dotnet", "NearCardAttendance.TcpServer.dll"] | |||
ENTRYPOINT ["sh", "-c", "dotnet NearCardAttendance.TcpServer.dll --environment=$environment"] |
@@ -0,0 +1,44 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<OutputType>Exe</OutputType> | |||
<TargetFramework>net6.0</TargetFramework> | |||
<ImplicitUsings>enable</ImplicitUsings> | |||
<Nullable>enable</Nullable> | |||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<None Remove="appsettings.debug.json" /> | |||
<None Remove="appsettings.json" /> | |||
<None Remove="appsettings.test.json" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Content Include="appsettings.test.json"> | |||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |||
</Content> | |||
<Content Include="appsettings.debug.json"> | |||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |||
</Content> | |||
<Content Include="appsettings.json"> | |||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |||
</Content> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" /> | |||
<PackageReference Include="DotNetty.Transport" Version="0.7.5" /> | |||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> | |||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" /> | |||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" /> | |||
<PackageReference Include="Serilog.Expressions" Version="3.4.0" /> | |||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\NearCardAttendance.Common\NearCardAttendance.Common.csproj" /> | |||
<ProjectReference Include="..\NearCardAttendance.Service\NearCardAttendance.Service.csproj" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,112 @@ | |||
using DotNetty.Codecs; | |||
using DotNetty.Transport.Bootstrapping; | |||
using DotNetty.Transport.Channels; | |||
using DotNetty.Transport.Channels.Sockets; | |||
using Microsoft.Extensions.Configuration; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Hosting; | |||
using NearCardAttendance.Common; | |||
using NearCardAttendance.Common.helper; | |||
using NearCardAttendance.Service.TcpServer.Handler; | |||
using NearCardAttendance.Service.TcpServer.Mapper; | |||
using NearCardAttendance.TcpServer.Config; | |||
using Serilog; | |||
using System.Text; | |||
namespace NearCardAttendance.TcpServer | |||
{ | |||
internal class Program | |||
{ | |||
static void Main(string[] args) | |||
{ | |||
var config = new ConfigurationBuilder() | |||
.SetBasePath(Directory.GetCurrentDirectory()) | |||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) | |||
.Build(); | |||
Log.Logger = new LoggerConfiguration() | |||
.ReadFrom.Configuration(config).Enrich.WithThreadInfo() | |||
.Filter.ByExcluding(logEvent => logEvent.Level == Serilog.Events.LogEventLevel.Verbose) // 过滤掉VRB级别的日志 | |||
.CreateLogger(); | |||
try | |||
{ | |||
Log.Information("Starting up"); | |||
CreateHostBuilder(args).Build().Run(); | |||
} | |||
catch (Exception ex) | |||
{ | |||
Log.Fatal(ex, "Application start-up failed"); | |||
} | |||
finally | |||
{ | |||
Log.CloseAndFlush(); | |||
} | |||
} | |||
public static IHostBuilder CreateHostBuilder(string[] args) => | |||
Host.CreateDefaultBuilder(args) | |||
.UseSerilog() | |||
.ConfigureServices((hostContext, services) => { | |||
var configuration = hostContext.Configuration; | |||
#region 配置信息 | |||
//services | |||
// .Configure<ServiceConfig>(configuration.GetSection("ServiceConfig")) | |||
// ; | |||
#endregion | |||
#region Http请求 | |||
services | |||
.AddSingleton<HttpHelper>() | |||
.AddHttpClient(Consts.DEFAULT_HTTPCLIENT_NAME, c => | |||
{ | |||
c.Timeout = TimeSpan.FromSeconds(10); //超时限制 | |||
c.DefaultRequestHeaders.Add("Accept", "application/json"); | |||
}) | |||
; | |||
#endregion | |||
#region TcpService | |||
services | |||
.AddSingleton<ServerBootstrap>(provider => | |||
{ | |||
var bossGroup = new MultithreadEventLoopGroup(); | |||
var workerGroup = new MultithreadEventLoopGroup(); | |||
var bootstrap = new ServerBootstrap(); | |||
bootstrap.Group(bossGroup, workerGroup) | |||
.Channel<TcpServerSocketChannel>() | |||
.Option(ChannelOption.SoBacklog, 100) | |||
.ChildOption(ChannelOption.TcpNodelay, true) // 低延迟 | |||
.ChildHandler(new ActionChannelInitializer<IChannel>(channel => | |||
{ | |||
// var handler = provider.GetRequiredService<ProtocolHandler>(); | |||
// var handler = provider.GetRequiredService<SomeServerHandler>(); | |||
var pipeline = channel.Pipeline; | |||
pipeline.AddLast(new StringEncoder(Encoding.ASCII)); | |||
pipeline.AddLast( | |||
provider.GetRequiredService<ProtocolHandler>(), | |||
provider.GetRequiredService<RegisterHandler>() | |||
//provider.GetRequiredService<HeartBeatHandler>(), | |||
//provider.GetRequiredService<PassThroughHandler>() | |||
); // Add the injected handler | |||
})); | |||
return bootstrap; | |||
}) | |||
.AddSingleton<TcpClientsManager>() | |||
.AddSingleton<ScheduleResendManager>() | |||
.AddTransient<ProtocolHandler>() | |||
.AddTransient<RegisterHandler>() | |||
//.AddTransient<HeartBeatHandler>() | |||
//.AddTransient<PassThroughHandler>() | |||
.AddHostedService<Server>() | |||
; | |||
#endregion | |||
}); | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
{ | |||
"profiles": { | |||
"NearCardAttendance.TcpServer": { | |||
"commandName": "Project", | |||
"environmentVariables": { | |||
"DOTNET_ENVIRONMENT": "debug" | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,77 @@ | |||
using DotNetty.Transport.Bootstrapping; | |||
using DotNetty.Transport.Channels; | |||
using Microsoft.Extensions.Hosting; | |||
using Microsoft.Extensions.Logging; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace NearCardAttendance.TcpServer | |||
{ | |||
public class Server : BackgroundService | |||
{ | |||
private readonly ILogger<Server> _logger; | |||
private readonly IServiceProvider _serviceProvider; | |||
private readonly ServerBootstrap _serverBootstrap; | |||
private IChannel _serverChannel = default!; | |||
private CancellationTokenSource _tokenSource = null!; | |||
public Server( | |||
ILogger<Server> logger, | |||
ServerBootstrap serverBootstrap, | |||
IServiceProvider serviceProvider) | |||
{ | |||
_logger = logger; | |||
_serviceProvider = serviceProvider; | |||
_serverBootstrap = serverBootstrap; | |||
} | |||
public override Task StartAsync(CancellationToken cancellationToken) | |||
{ | |||
_logger.LogInformation("------StartAsync"); | |||
_tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); | |||
return base.StartAsync(cancellationToken); | |||
} | |||
public override Task StopAsync(CancellationToken cancellationToken) | |||
{ | |||
_logger.LogInformation("------StopAsync"); | |||
_tokenSource.Cancel(); //停止工作线程 | |||
return base.StopAsync(cancellationToken); | |||
} | |||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) | |||
{ | |||
_logger.LogInformation("DotNetty server starting..."); | |||
//var address = new IPEndPoint(IPAddress.Any, 12345); | |||
//IChannel _serverChannel = await _serverBootstrap.BindAsync(address); | |||
string ipAddress = "0.0.0.0"; | |||
int port = 16662; | |||
var endPoint = new IPEndPoint(IPAddress.Parse(ipAddress), port); | |||
IChannel _serverChannel = await _serverBootstrap.BindAsync(endPoint); | |||
// _serverChannel.GetAttribute(MessageIdAttribute.Key).Set("SomeRequestId"); | |||
//_logger.LogInformation("DotNetty server started on {0}.", address); | |||
_logger.LogInformation("DotNetty server started on {0}.", endPoint); | |||
// Wait until the service is stopped | |||
stoppingToken.WaitHandle.WaitOne(); | |||
_logger.LogInformation("DotNetty server stopping..."); | |||
// Close the server channel and release resources | |||
await _serverChannel.CloseAsync(); | |||
_logger.LogInformation("DotNetty server stopped."); | |||
} | |||
} | |||
} |
@@ -0,0 +1,4 @@ | |||
{ | |||
"AllowedHosts": "*" | |||
} |
@@ -0,0 +1,121 @@ | |||
{ | |||
"Logging": { | |||
"LogLevel": { | |||
"Default": "Information", | |||
"Microsoft": "Warning", | |||
"Microsoft.Hosting.Lifetime": "Information", | |||
"HttpClient": "Information" | |||
} | |||
}, | |||
"Serilog": { | |||
"Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Async", "Serilog.Sinks.Console", "Serilog.Expressions" ], | |||
"MinimumLevel": { | |||
"Default": "Verbose", | |||
"Override": { | |||
"Microsoft": "Warning", | |||
"Microsoft.Hosting.Lifetime": "Information", | |||
"HttpClient": "Information" | |||
} | |||
}, | |||
"WriteTo": [ | |||
{ | |||
"Name": "Console", | |||
"Args": { | |||
"restrictedToMinimumLevel": "Verbose", | |||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff }[{Level:u3}] [Thread-{ThreadId}] [{SourceContext:l}] [{RequestId}] {Message:lj}{NewLine}{Exception}", | |||
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console" | |||
} | |||
}, | |||
{ | |||
"Name": "Logger", | |||
"Args": { | |||
"ConfigureLogger": { | |||
"WriteTo": [ | |||
{ | |||
"Name": "File", | |||
"Args": { | |||
"RestrictedToMinimumLevel": "Information", | |||
"RollingInterval": "Day", | |||
"RollOnFileSizeLimit": "true", | |||
"OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff }[{Level:u3}] [Thread-{ThreadId}] [{SourceContext:l}] [{RequestId}] {Message:lj}{NewLine}{Exception}", | |||
"Path": "/var/near_card_attendance/logs/infos/info.log", | |||
"RetainedFileCountLimit": 10 // "--设置日志文件个数最大值,默认31,意思就是只保留最近的31个日志文件", "等于null时永远保留文件": null | |||
//"FileSizeLimitBytes": 20971520, //设置单个文件大小为3M 默认1G | |||
//"RollOnFileSizeLimit": true //超过文件大小后创建新的 | |||
} | |||
} | |||
], | |||
"Filter": [ | |||
{ | |||
"Name": "ByIncludingOnly", | |||
"Args": { | |||
"Expression": "@l = 'Information'" | |||
} | |||
} | |||
] | |||
} | |||
} | |||
}, | |||
{ | |||
"Name": "Logger", | |||
"Args": { | |||
"ConfigureLogger": { | |||
"WriteTo": [ | |||
{ | |||
"Name": "File", | |||
"Args": { | |||
"RestrictedToMinimumLevel": "Warning", | |||
"RollingInterval": "Day", | |||
"RollOnFileSizeLimit": "true", | |||
"OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff }[{Level:u3}] [Thread-{ThreadId}] [{SourceContext:l}] [{RequestId}] {Message:lj}{NewLine}", | |||
"Path": "/var/near_card_attendance/logs/warnings/warn.log", | |||
"RetainedFileCountLimit": 10 | |||
} | |||
} | |||
], | |||
"Filter": [ | |||
{ | |||
"Name": "ByIncludingOnly", | |||
"Args": { | |||
"Expression": "@l = 'Warning'" | |||
} | |||
} | |||
] | |||
} | |||
} | |||
}, | |||
{ | |||
"Name": "Logger", | |||
"Args": { | |||
"ConfigureLogger": { | |||
"WriteTo": [ | |||
{ | |||
"Name": "File", | |||
"Args": { | |||
"RestrictedToMinimumLevel": "Error", | |||
"RollingInterval": "Day", | |||
"RollOnFileSizeLimit": "true", | |||
"OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff }[{Level:u3}] [Thread-{ThreadId}][{SourceContext:l}] [{RequestId}] {Message:lj}{NewLine}{Exception}", | |||
"Path": "/var/near_card_attendance/logs/errors/error.log", | |||
"RetainedFileCountLimit": 15 // "--设置日志文件个数最大值,默认31,意思就是只保留最近的31个日志文件", "等于null时永远保留文件": null | |||
//"FileSizeLimitBytes": 20971520, //设置单个文件大小为3M 默认1G | |||
//"RollOnFileSizeLimit": true //超过文件大小后创建新的 | |||
} | |||
} | |||
], | |||
"Filter": [ | |||
{ | |||
"Name": "ByIncludingOnly", | |||
"Args": { | |||
"Expression": "@l = 'Error'" | |||
} | |||
} | |||
] | |||
} | |||
} | |||
} | |||
] | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
{ | |||
"AllowedHosts": "*" | |||
} |
@@ -0,0 +1,43 @@ | |||
| |||
Microsoft Visual Studio Solution File, Format Version 12.00 | |||
# Visual Studio Version 17 | |||
VisualStudioVersion = 17.8.34330.188 | |||
MinimumVisualStudioVersion = 10.0.40219.1 | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NearCardAttendance.TcpServer", "NearCardAttendance.TcpServer\NearCardAttendance.TcpServer.csproj", "{46A2FEAF-7333-4D41-83F2-5ACDDDD850F5}" | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NearCardAttendance.Service", "NearCardAttendance.Service\NearCardAttendance.Service.csproj", "{2394B5EA-8A6A-4FC4-9218-2EED87E3FC14}" | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NearCardAttendance.Common", "NearCardAttendance.Common\NearCardAttendance.Common.csproj", "{CC1A7D30-569C-497A-8D94-C23BA11C870D}" | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NearCardAttendance.Model", "NearCardAttendance.Model\NearCardAttendance.Model.csproj", "{10C81D50-6839-4F3C-92C9-C4E6554B8566}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
Debug|Any CPU = Debug|Any CPU | |||
Release|Any CPU = Release|Any CPU | |||
EndGlobalSection | |||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||
{46A2FEAF-7333-4D41-83F2-5ACDDDD850F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{46A2FEAF-7333-4D41-83F2-5ACDDDD850F5}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{46A2FEAF-7333-4D41-83F2-5ACDDDD850F5}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{46A2FEAF-7333-4D41-83F2-5ACDDDD850F5}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{2394B5EA-8A6A-4FC4-9218-2EED87E3FC14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{2394B5EA-8A6A-4FC4-9218-2EED87E3FC14}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{2394B5EA-8A6A-4FC4-9218-2EED87E3FC14}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{2394B5EA-8A6A-4FC4-9218-2EED87E3FC14}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{CC1A7D30-569C-497A-8D94-C23BA11C870D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{CC1A7D30-569C-497A-8D94-C23BA11C870D}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{CC1A7D30-569C-497A-8D94-C23BA11C870D}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{CC1A7D30-569C-497A-8D94-C23BA11C870D}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{10C81D50-6839-4F3C-92C9-C4E6554B8566}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{10C81D50-6839-4F3C-92C9-C4E6554B8566}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{10C81D50-6839-4F3C-92C9-C4E6554B8566}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{10C81D50-6839-4F3C-92C9-C4E6554B8566}.Release|Any CPU.Build.0 = Release|Any CPU | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
EndGlobalSection | |||
GlobalSection(ExtensibilityGlobals) = postSolution | |||
SolutionGuid = {2A15CF2F-2E7D-42D5-B8D7-9A22592049A0} | |||
EndGlobalSection | |||
EndGlobal |
@@ -0,0 +1,29 @@ | |||
#!/bin/bash | |||
environment=$1 | |||
version=$2 | |||
echo "环境变量为${environment},版本为$version!" | |||
if [[ ${environment} == 'production' ]]; then | |||
echo "开始远程构建容器" | |||
docker stop near_card_attendance || true | |||
docker rm near_card_attendance || true | |||
docker rmi -f $(docker images | grep registry.cn-shanghai.aliyuncs.com/gps_card/near_card_attendance | awk '{print $3}') | |||
#docker login --username=telpo_linwl@1111649216405698 --password=telpo#1234 registry.cn-shanghai.aliyuncs.com | |||
docker login --username=rzl_wangjx@1111649216405698 --password=telpo.123 registry.cn-shanghai.aliyuncs.com | |||
docker pull registry.cn-shanghai.aliyuncs.com/gps_card/near_card_attendance:$version | |||
docker run --network=host -p 16662:16662 -d -e environment=production -v /home/data/near_card_attendance/log:/var/near_card_attendance/logs --restart=always --name near_card_attendance registry.cn-shanghai.aliyuncs.com/gps_card/near_card_attendance:$version; | |||
#删除产生的None镜像 | |||
docker rmi -f $(docker images | grep none | awk '{print $3}') | |||
docker ps -a | |||
elif [[ ${environment} == 'test' || ${environment} == 'presure' ]]; then | |||
echo "开始在测试环境远程构建容器" | |||
docker stop near_card_attendance || true | |||
docker rm near_card_attendance || true | |||
docker rmi -f $(docker images | grep 139.224.254.18:5000/near_card_attendance | awk '{print $3}') | |||
docker pull 139.224.254.18:5000/near_card_attendance:$version | |||
docker run --network=host -d -p 16662:16662 -e environment=${environment} -v /home/data/near_card_attendance/log:/var/near_card_attendance/logs --restart=always --name near_card_attendance 139.224.254.18:5000/near_card_attendance:$version; | |||
#删除产生的None镜像 | |||
docker rmi -f $(docker images | grep none | awk '{print $3}') | |||
docker ps -a | |||
fi |
@@ -0,0 +1,17 @@ | |||
#!/usr/bin/env bash | |||
image_version=$version | |||
# 删除镜像 | |||
docker rmi -f $( | |||
docker images | grep 139.224.254.18:5000/near_card_attendance | awk '{print $3}' | |||
) | |||
# 构建telpo/mrp:$image_version镜像 | |||
docker build -f ./NearCardAttendance.TcpServer/Dockerfile . -t telpo/near_card_attendance:$image_version | |||
#TODO:推送镜像到私有仓库 | |||
echo '=================开始推送镜像=======================' | |||
docker tag telpo/near_card_attendance:$image_version 139.224.254.18:5000/near_card_attendance:$image_version | |||
docker push 139.224.254.18:5000/near_card_attendance:$image_version | |||
echo '=================推送镜像完成=======================' | |||
#删除产生的None镜像 | |||
docker rmi -f $(docker images | grep none | awk '{print $3}') | |||
# 查看镜像列表 | |||
docker images |