浏览器实现静默打印

背景

最近有一个需求涉及到静默打印,也就是打印时不需要弹出打印配置框,而是直接调用打印机

在实际生产中,采用的是FastReport实现静默打印方案,以下仅供参考

方案

经过讨论大概是以下几种方案:

  • 把打印配置弹窗干掉、模拟点击打印事件
  • 浏览器实现静默打印
  • 本地运行一个服务,客户端返回请求状态后请求本地接口调用打印
方案1

第一种没有去尝试,所以先讲讲第二种解决方案,通过前端控制浏览器打印,但是百度一圈下来的解决方案都是通过设置浏览器参数,然后创建一个快捷程序并设置启动参数,操作很简单,但实在不符合需求,总不能给每个客户也这样配置一边吧,所以第二种也pass

操作如下:

方案3

重点讲一下方案,也是目前在探索中,后续有更好的方法会发出来,也欢迎各位提出更好的解决方案。

目前仅支持windows环境,winform最终是.exe程序

新建Winform程序

使用VS2019创建
拖入ComboBox控件,用来选择本地打印机
获取打印机列表填充下拉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// 加载打印机列表
/// </summary>
private void InitPrinterList()
{
var print = new PrintDocument();
var defaultPrinter = print.PrinterSettings.PrinterName;
for (var i = 0; i < PrinterSettings.InstalledPrinters.Count; i++)
{
var tmp = PrinterSettings.InstalledPrinters[i];
if (tmp == defaultPrinter)
{
tmp += "(默认)"; // 标记默认的打印机
}
this.comboBox1.Items.Add(tmp); // 下拉项新增
}
}
private void Form1_Load(object sender, EventArgs e)
{
InitPrinterList(); // 窗体加载事件中调用
}
ctrl+f5运行窗体查看效果

创建服务

没有使用socket,而是在winform中创建了一个http服务,并开放端口供浏览器本地请求

先新增两个依赖:

  1. BeetleX 1.5.1.7
  2. BeetleX.FastHttpApi 1.8.2.61

这两个版本是支持.net framework的,BeetleX是一个基于.Net Core开发的一个开源跨平台TCP通讯框架,它提供了完整的会话服务管理,协议分析扩展,TLS支持和缓冲区管理等完全服务基础功能。有兴趣的可以去Github参观一下。

初始化Http服务

在Load方法中调用InitHttpServer()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <summary>
/// 初始化网关服务
/// </summary>
protected void InitHttpServer()
{
_httpApiServer = new HttpApiServer(new HttpOptions()
{
Port = 9000, // 本地开放的端口 要注意不要被占用
LogToConsole = true,
LogLevel = BeetleX.EventArgs.LogType.Debug
});
_httpApiServer.IPv4Tables.AddWhite("127.0.0.1/24");
_httpApiServer.Options.CrossDomain = new OptionsAttribute() { AllowOrigin = "*", AllowMethods = "*", AllowHeaders = "*" }; // 跨域配置,这个很重要
_httpApiServer.Register(typeof(Home).Assembly);
_httpApiServer.Open();
}

注册的程序就是Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
[Controller(BaseUrl = "/home")]
public class Home
{
/// <summary>
/// Hello Word
/// </summary>
/// <param name="name">string: you name</param>
/// <returns>string</returns>
public object Hello(string name)
{
return new { Hello = "hello " + name, Time = DateTime.Now };
}
}

控制下服务的生命周期

使用Postman请求一下

http://localhost:9000/home/Hello?name=test 返回成功

打印

客户端向本地请求打印接口,传入参数

这里传入了pdf文件地址,通过请求文件并打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/// <summary>
/// 打印接口
/// </summary>
/// <param name="url"></param>
/// <param name="printCount"></param>
public void Print(string url, int printCount)
{
url = url.Replace("\n ", "");
if (!(WebRequest.Create(url) is HttpWebRequest request)) return;
if (!(request.GetResponse() is HttpWebResponse response)) return;
var stream = response.GetResponseStream();
// 创建打印机
var pdf = new InputPdf(ReadFully(stream));
var printJob = new PrintJob(Printer.Default, pdf); // 默认打印机
printJob.PrintOptions.Copies = printCount; //打印数量
printJob.Print();
}
/// <summary>
/// 读取字节流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static byte[] ReadFully(Stream input)
{
using (var ms = new MemoryStream())
{
input.CopyTo(ms);
return ms.ToArray();
}
}

打印插件调用的DynamicPdf

打印插件文档:https://www.dynamicpdf.com/docs/dotnet/print-manager-print-options

优化建议:

可以通过下拉框选择的打印机打印,需要记录选择的打印机

通过读取和设置config值实现,引入System.Configuration

ConfigHelper类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static class ConfigHelper
{
///<summary>
///返回*.exe.config文件中appSettings配置节的value项
///</summary>
///<param name="strKey"></param>
///<returns></returns>
public static string GetAppConfig(string strKey)
{
var file = System.Windows.Forms.Application.ExecutablePath;
var config = ConfigurationManager.OpenExeConfiguration(file);
return config.AppSettings.Settings.AllKeys.Any(key => key == strKey) ? config.AppSettings.Settings[strKey].Value.ToString() : null;
}

///<summary>
///在*.exe.config文件中appSettings配置节增加一对键值对
///</summary>
///<param name="newKey"></param>
///<param name="newValue"></param>
public static void UpdateAppConfig(string newKey, string newValue)
{
var file = System.Windows.Forms.Application.ExecutablePath;
var config = ConfigurationManager.OpenExeConfiguration(file);
var exist = false;
foreach (var key in config.AppSettings.Settings.AllKeys)
{
if (key == newKey)
{
exist = true;
}
}
if (exist)
{
config.AppSettings.Settings.Remove(newKey);
}
config.AppSettings.Settings.Add(newKey, newValue);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
}
}
设置下拉选择事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
// 更新setting设置
var selectedPrint = this.comboBox1.SelectedItem.ToString();
if (string.IsNullOrEmpty(selectedPrint))
{
return;
}

// 移除掉初始化打印机列表添加的默认文本
if (selectedPrint.Contains("默认"))
{
selectedPrint = selectedPrint.Replace("(默认)", "");
}
ConfigHelper.UpdateAppConfig("defaultPrint", selectedPrint);
}
优化后的打印接口
1
2
3
4
5
6
7
8
...
// 获取设置的打印机 (读取配置)
var defaultPrinter = ConfigHelper.GetAppConfig("defaultPrint");
// 创建打印机
var printer = new Printer(defaultPrinter);
var pdf = new InputPdf(ReadFully(stream));
var printJob = new PrintJob(printer, pdf);
...

这样就能实现简单的静默打印,当然还有很多欠缺之处,后续优化,不当之处请指出