Browse Source

打印服务

master
liufei 3 years ago
parent
commit
203b764bf2
  1. 116
      .gitignore
  2. 25
      POSCorePrint.sln
  3. 19
      POSCorePrint/Constant.cs
  4. 55
      POSCorePrint/Content.cs
  5. 196
      POSCorePrint/Jobs/PrintJob.cs
  6. 35
      POSCorePrint/POSCorePrint.csproj
  7. 73
      POSCorePrint/Program.cs
  8. 15
      POSCorePrint/Properties/PublishProfiles/FolderProfile.pubxml
  9. 52
      POSCorePrint/TaskStart.cs
  10. 12
      POSCorePrint/appsettings.json
  11. 0
      readme.txt

116
.gitignore

@ -0,0 +1,116 @@
# ---> C Sharp
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/
# mstest test results
TestResults
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.log
*.vspscc
*.vssscc
*.cache
*.dll
*.pdb
.builds
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Publish Web Output
*.Publish.xml
# NuGet Packages Directory
packages
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
[Bb]in
[Oo]bj
sql
TestResults
[Tt]est[Rr]esult*
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
/lib
.vs
/.gitattributes

25
POSCorePrint.sln

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29123.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "POSCorePrint", "POSCorePrint/POSCorePrint.csproj", "{C340E6AE-33B9-4C36-A84A-4B2E924C82AC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C340E6AE-33B9-4C36-A84A-4B2E924C82AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C340E6AE-33B9-4C36-A84A-4B2E924C82AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C340E6AE-33B9-4C36-A84A-4B2E924C82AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C340E6AE-33B9-4C36-A84A-4B2E924C82AC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {444EB795-1EE2-4EDA-8747-1FF859AA80FF}
EndGlobalSection
EndGlobal

19
POSCorePrint/Constant.cs

@ -0,0 +1,19 @@
using Microsoft.Extensions.Configuration;
using MySql.Data.MySqlClient;
using POSCorePrint.Jobs;
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
namespace POSCorePrint
{
internal class Constant
{
internal static int port = 9100;
internal static IConfigurationRoot Configuration;
internal static string ConnectionString;
internal static double PrintBackTime;//打印多久之前的数据:分钟
internal static double SaveHisDataDays;//只保留最近几天的打印数据:天
}
}

55
POSCorePrint/Content.cs

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace POSCorePrint
{
/// <summary>
/// 打印机
/// </summary>
internal class Content
{
/// <summary>
/// ID
/// </summary>
public string ID { get; set; }
/// <summary>
/// 打印的内容
/// </summary>
public string ContentVal { get; set; }
/// <summary>
/// 通过Socket方式判断打印机是否正常连接
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
/// <returns></returns>
//internal static bool IsConnect(string ip, int port)
//{
// //Task.Delay(10);
// var isConnect = false;
// IPAddress iPAddress = IPAddress.Parse(ip);
// IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, port);
// Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// try
// {
// IAsyncResult asyncResult = socket.BeginConnect(iPEndPoint, null, null);
// asyncResult.AsyncWaitHandle.WaitOne(2000, true);
// if (asyncResult.IsCompleted)
// isConnect = true;
// LogHelper.Log().Warn($"打印机 {ip}" + (isConnect ? "正常" : "离线!"));
// }
// catch (Exception ex)
// {
// ILog log = LogHelper.Log();
// Console.WriteLine($" {ip} 判断打印机是否正常连接失败:【{ex.Message}】",ex);
// }
// finally
// {
// socket.Close();
// }
// return isConnect;
//}
}
}

196
POSCorePrint/Jobs/PrintJob.cs

@ -0,0 +1,196 @@

using MySql.Data.MySqlClient;
using Quartz;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using System.Threading;
namespace POSCorePrint.Jobs
{
/// <summary>
/// edit by liuwz 2019-09-18 修改防止重复打印的方法
/// 打印任务
/// </summary>
[DisallowConcurrentExecution]
public class PrintJob : IJob
{
private readonly ManualResetEvent TimeoutObject = new ManualResetEvent(false);
private int Timeout = 1500;//连接打印机超时时间,毫秒。
private int TimeoutSend = 5000;//连接打印机超时时间,毫秒。
public Task Execute(IJobExecutionContext context)
{
//查询最近N分钟需要打印的数据,同时删除N天前的打印数据。
string sql = $@"select task.Id,task.content,print.PrintIP from pos_store_printtask task inner join pos_store_print print on task.code=print.Id
where task.status = 0 and print.isdelete=0 and task.CreateTime >'{DateTime.Now.AddMinutes(-Constant.PrintBackTime).ToString("yyyy-MM-dd HH:mm:ss")}';
delete from pos_store_printtask where CreateTime <'{DateTime.Now.AddDays(-Constant.SaveHisDataDays).ToString("yyyy-MM-dd HH:mm:ss")}';";
// TODO 查询数据库获取打印任务列表
DataTable dataTable;
try
{
dataTable = MySqlHelper.ExecuteDataset(Constant.ConnectionString, sql).Tables[0];
//无打印任务
if (dataTable.Rows.Count == 0)
return Task.CompletedTask;
//有打印任务,按打印机分组
var dicPrints = new Dictionary<string, List<Content>>();
foreach (DataRow row in dataTable.Rows)
{
var newContent = new Content() { ID = row["Id"].ToString(), ContentVal = row["Content"].ToString() };
if (!dicPrints.ContainsKey(row["PrintIP"].ToString()))
{
dicPrints.Add(
row["PrintIP"].ToString(),
new List<Content> { newContent }
);
}
else
dicPrints[row["PrintIP"].ToString()].Add(newContent);
}
//按打印机并行异步发送
Console.WriteLine($"{DateTime.Now.ToString()}:打印开始");
List<Task> taskList = new List<Task>();
foreach (KeyValuePair<string, List<Content>> p in dicPrints)
{
taskList.Add(Send(p));
}
//等所有异步打印完再返回
Task.WhenAll(taskList).Wait();
Console.WriteLine($"{DateTime.Now.ToString()}:打印结束");
return Task.CompletedTask;
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now.ToString()}:获取打印任务失败", ex);
return Task.CompletedTask;
}
}
/// <summary>
/// 异步发送打印任务
/// </summary>
/// <param name="p"></param>
private async Task Send(KeyValuePair<string, List<Content>> p)
{
string idSuccess = "";
string idFail = "";
await Task.Run(() =>
{
IPAddress iPAddress = IPAddress.Parse(p.Key);
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, Constant.port);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
TimeoutObject.Reset();
socket.BeginConnect(iPEndPoint, CallBackMethod,socket);
//阻塞当前线程             
if (!TimeoutObject.WaitOne(Timeout, false))
{
//处理连接不成功的动作
socket.Close();
Console.WriteLine($"{DateTime.Now.ToString()}: 打印失败:{p.Key}【连接超时】");
return;
}
if (!socket.Connected)
{
//处理连接不成功的动作
socket.Close();
Console.WriteLine($"{DateTime.Now.ToString()}: 打印失败:{p.Key} socket【连接超时】");
return;
}
{ foreach (Content content in p.Value)
{
////判断打印机状态:是否在线,是否有纸
//byte[] dataSatus = new byte[3];
//for (int i = 0; i < dataSatus.Length; i++)
//{
// dataSatus[i] = byte.Parse(content.ContentVal.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
//}
//int iRes = socket.Send(dataSatus, 0);
//打印内容
byte[] data = new byte[content.ContentVal.Length / 2];
try
{
for (int i = 0; i < data.Length; i++)
{
data[i] = byte.Parse(content.ContentVal.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
}
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now.ToString()}:转换打印数据失败 {ex.Message}", ex);
}
try
{
//byte[] mData = new byte[data.Length];
//Array.Copy(data, 0, mData, 0, data.Length);
SocketError errorCode;
//edit by liuwz 2020-04-03 增加超时
socket.SendTimeout= TimeoutSend;
int iResult = socket.Send(data, 0, out errorCode);
//add by liuwz 2020-01-13增加打印发送长度判断。
if (SocketError.Success != errorCode || iResult !=data.Length)
{
//add by liuwz 2019-11-29失败时增加打印次数。
idFail += string.IsNullOrEmpty(idFail) ? $"'{content.ID}'" : $",'{content.ID}'";
Console.WriteLine($"{DateTime.Now.ToString()}: ip:{p.Key},id:{content.ID} 打印返回失败:【{errorCode.ToString()}】");
}
else
idSuccess += string.IsNullOrEmpty(idSuccess) ? $"'{content.ID}'" : $",'{content.ID}'";
}
catch (Exception ex)
{
//add by liuwz 2019-11-29失败时增加打印次数。
idFail += string.IsNullOrEmpty(idFail) ? $"'{content.ID}'" : $",'{content.ID}'";
Console.WriteLine($"{DateTime.Now.ToString()}: ip:{p.Key},id:{content.ID} 发送打印命令失败:【{ex.Message}】", ex);
}
}
// 操作数据库更新状态
if (!string.IsNullOrEmpty(idSuccess))
{
var sql = $"update pos_store_printtask set status=1,PrintTime=NOW(),PrintTimes=ifnull(PrintTimes,0)+1 where Id in({idSuccess});";
Console.WriteLine($"{DateTime.Now.ToString()}:打印成功更新记录:{iPAddress};{idSuccess}");
MySqlHelper.ExecuteNonQueryAsync(Constant.ConnectionString, sql);
}
if (!string.IsNullOrEmpty(idFail))
{
Console.WriteLine($"{DateTime.Now.ToString()}:打失败更新记录:{iPAddress};{idFail}");
var sql = $"update pos_store_printtask set PrintTimes=ifnull(PrintTimes,0)+1 where Id in({idFail});";
MySqlHelper.ExecuteNonQueryAsync(Constant.ConnectionString, sql);
}
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now.ToString()}: 打印失败:{p.Key}【{ex.Message}】{string.Join('|',p.Value.Select(m=>new {m.ID}))}", ex);
}
finally
{
socket.Close();
}
});
}
/// <summary>
/// 异步回调方法 
/// </summary>
/// <param name="asyncresult"></param>
private void CallBackMethod(IAsyncResult asyncresult)
{
//使阻塞的线程继续          
TimeoutObject.Set();
}
}
}

35
POSCorePrint/POSCorePrint.csproj

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<ApplicationIcon />
<StartupObject></StartupObject>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<WarningLevel>3</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="MySql.Data" Version="8.0.17" />
<PackageReference Include="Quartz" Version="3.0.7" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ProjectExtensions><VisualStudio><UserProperties appsettings_1json__JsonSchema="" /></VisualStudio></ProjectExtensions>
</Project>

73
POSCorePrint/Program.cs

@ -0,0 +1,73 @@

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace POSCorePrint
{
class Program
{
//private static Dictionary<string, Printer> printerList = new Dictionary<string, Printer>();
static async Task Main(string[] args)
{
//add by liuwz 2019-12-26 解决linux下时间格式的问题
var culture = CultureInfo.CreateSpecificCulture("zh-CN");
var dateformat = new DateTimeFormatInfo()
{
ShortTimePattern = "HH:mm:ss",
LongTimePattern = "HH:mm:ss",
ShortDatePattern = "yyyy-MM-dd",
LongDatePattern = "yyyy-MM-dd",
FullDateTimePattern = "yyyy-MM-dd HH:mm:ss"
};
culture.DateTimeFormat = dateformat;
CultureInfo.DefaultThreadCurrentUICulture = culture;
CultureInfo.DefaultThreadCurrentCulture = culture;
var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Constant.Configuration = builder.Build();
Constant.ConnectionString = Constant.Configuration["Config:connectionString"];
Constant.PrintBackTime = double.Parse(Constant.Configuration["taskTimes:printBackTime"]);
Constant.SaveHisDataDays = double.Parse(Constant.Configuration["taskTimes:saveHisDataDays"]);
var hostBuilder = new HostBuilder()
.ConfigureServices(serviceCollection =>
{
//注册我们的服务接口
serviceCollection.AddSingleton<IHostedService, MyService>();
});
await hostBuilder.RunConsoleAsync();
}
}
class MyService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
Console.WriteLine($"{DateTime.Now.ToString()}:开启打印任务:" + DateTime.Now.ToString());
TaskStart taskStart = new TaskStart();
taskStart.Start();
});
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
Console.WriteLine($"{DateTime.Now.ToString()}:关闭打印任务:" + DateTime.Now.ToString());
});
}
}
}

15
POSCorePrint/Properties/PublishProfiles/FolderProfile.pubxml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp2.2</TargetFramework>
<PublishDir>D:\publish\core_posprint</PublishDir>
<SelfContained>false</SelfContained>
<_IsPortable>true</_IsPortable>
</PropertyGroup>
</Project>

52
POSCorePrint/TaskStart.cs

@ -0,0 +1,52 @@
using Quartz;
using Quartz.Impl;
using System;
using System.Collections.Specialized;
using System.Threading.Tasks;
using POSCorePrint.Jobs;
using System.Net;
using System.Net.Sockets;
namespace POSCorePrint
{
public class TaskStart
{
public string PrintTaskTime { get; } = Constant.Configuration["taskTimes:printTaskTime"];
public async Task Start()
{
try
{
// 从工厂中获取调度程序实例
NameValueCollection props = new NameValueCollection
{
{ "quartz.serializer.type", "binary" }
};
StdSchedulerFactory factory = new StdSchedulerFactory(props);
IScheduler scheduler = await factory.GetScheduler();
// 开启调度器
await scheduler.Start();
#region//打印任务
IJobDetail job = JobBuilder.Create<PrintJob>()
.WithIdentity("PrintJob", "group")
.Build();
// 定时获取打印任务
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("PrintTrigger", "group")
.WithCronSchedule(PrintTaskTime)
.Build();
// 告诉Quartz使用我们的触发器来安排作业
await scheduler.ScheduleJob(job, trigger);
#endregion
}
catch (SchedulerException se)
{
await Console.Error.WriteLineAsync($"{DateTime.Now.ToString()}:{se.ToString()}");
}
}
}
}

12
POSCorePrint/appsettings.json

@ -0,0 +1,12 @@
{
"Config": {
"connectionString": "server=192.168.1.221;port=3306;uid=root;pwd=dlpos;database=posclient;charset=utf8;Allow User Variables=true;"
},
"taskTimes": {
"printTaskTime": "0/3 * * * * ? *",
//
"printBackTime": 30,
//
"saveHisDataDays": 3
}
}

0
readme.txt

Loading…
Cancel
Save