部署在本地的打印服务,共享到外网远程打印 C# .NetCore

部署在本地的打印伴侣服务,将服务作为一台设备连接至阿里云物联网平台,实现远程调用本地打印服务,由打印服务发起本地打印机工作调用。

思路:

1、NetworkImageServer 实现本地打印服务,启动工程时自动启动打印服务

2、MQTTServer 连接阿里云物联网平台,实现远程调用本地打印服务,topic发布消息即可

3、选择并设置默认打印机,在ManageController.cs中实现,保存到client.config.json文件

4、本地调用打印服务可通过打印服务API,在 ImageController.cs中实现

第一步:创建网络图片打印服务

NetworkImageServer.cs

using GeekOpen.Common;
using GeekOpen.Core;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Printing;
using System.Linq;
using System.Threading.Tasks;

namespace PrintMateClient.Server
{
    /// <summary>
    /// 打印的图片信息
    /// </summary>
    public class ImageParameter
    {
        public string url { get; set; }
       
    }

    /// <summary>
    /// 打印任务数据包
    /// </summary>
    public class ImagePrintTask
    {
        /// <summary>
        /// 是否转为横向
        /// </summary>
        public bool landscape { get; set; }
        /// <summary>
        /// 纸张宽度,单位 mm
        /// </summary>
        public int width { get; set; }
        /// <summary>
        /// 纸张高度,单位 mm
        /// </summary>
        public int height { get; set; }
        /// <summary>
        /// 打印机X轴偏移量,单位 mm
        /// </summary>
        public int offsetX { get; set; }
        /// <summary>
        /// 打印机Y轴偏移量,单位 mm
        /// </summary>
        public int offsetY { get; set; }
        /// <summary>
        /// 需要打印的图片列表
        /// </summary>
        public List<ImageParameter> imageParameters { get; set; }
    }

    /// <summary>
    /// 网络图片打印服务
    /// </summary>
    public class NetworkImageServer
    {
        public List<ImageParameter> imageParameters;
        public ImageParameter currentParameter;
        PrintDocument print;
        ImagePrintTask imagePrintTask;
        string printName;

        /// <summary>
        /// 网络图片打印服务
        /// </summary>
        /// <param name="printName">本地打印机名称</param>
        /// <param name="imagePrintTask"></param>
        public NetworkImageServer(string printName,ImagePrintTask imagePrintTask)
        {
            this.imagePrintTask = imagePrintTask;
            this.imageParameters = imagePrintTask.imageParameters;
            this.printName = printName;

            bool toPrint = false;
            string[] systemPrints = { }; 
            foreach (string sPrint in PrinterSettings.InstalledPrinters)//获取所有打印机名称
            {
                if(sPrint== this.printName)
                {
                    toPrint = true;
                }
                systemPrints.Append(sPrint);
            }
            if(toPrint)
            {
                //转换纸张尺寸,如果未设定,则默认A4打印
                this.imagePrintTask.width = this.imagePrintTask.width > 0 ? P(this.imagePrintTask.width) : P(2100);
                this.imagePrintTask.height = this.imagePrintTask.height > 0 ? P(this.imagePrintTask.height) : P(2970);
                print = new PrintDocument();
                print.PrinterSettings.PrinterName = this.printName;//读取本地配置中的打印机名称,如:"Deli DL-888B(NEW)"
                print.DefaultPageSettings.Landscape = imagePrintTask.landscape; //设置横向打印,不设置默认false是纵向的  
                print.DefaultPageSettings.PaperSize = new PaperSize("Custom Size 1", this.imagePrintTask.width, this.imagePrintTask.height);
                print.PrintController = new StandardPrintController();
            }
            else
            {
                throw new BusException("Cleint没有找到打印机:"+ this.printName,true, "systemPrints="+ String.Join(";",systemPrints));
            }
        }

        /// <summary>
        /// 打印多张图片
        /// </summary>
        public void Print()
        {
            print.PrintPage += new PrintPageEventHandler(printImageList);
            print.Print();
        }

        /// <summary>
        /// 打印多张图片
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void printImageList(object sender, PrintPageEventArgs e)
        {
            if(imageParameters.Count>0)
            {
                ImageParameter parameter = imageParameters.Take(1).SingleOrDefault();
                Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " - printImageList --> " + parameter.url);
                Image image = new Bitmap(Util.DownloadFile(parameter.url));
                e.Graphics.DrawImage(image, 0, 0, 820, 1170);
                imageParameters.Remove(parameter);
                if (imageParameters.Count>0)
                {
                    e.HasMorePages = true;
                }
                return;
            }
            e.HasMorePages = false;
        }

        /// <summary>
        /// 尺寸转换
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        private int P(int v)
        {
            return int.Parse((v * 1000/254).ToString("F0"));
        }
    }


}

第二步,将服务通过MQTT连接至阿里云物联网平台

MQTTServer.cs

using GeekOpen.Common;
using GeekOpen.Core;
using GeekOpen.Iot.Common.Values.IoT;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
using System.Runtime.InteropServices;
using System.Threading;

namespace PrintMateClient.Server
{
    public static class MQTTState
    {
        public static bool state = false; 
    }

    public class MQTTServer 
    {
        MqttClient mqttClient;
        PrintMateClientInfo printMateClientInfo;
        //MQTT消息订阅,执行打印任务
        public string printTopicReply;

        public MQTTServer()
        {
            if(!MQTTState.state)
            {
                ClientConfig clientConfig = ConfigServices.GetClientConfig();
                if (!string.IsNullOrEmpty(clientConfig.clientCode) && !string.IsNullOrEmpty(clientConfig.clientSecret))
                {
                    //根据 ClientCode 与 ClientSecret 向 IoT服务获取 productKey、deviceName、deviceSecret
                    ResultValue<PrintMateClientInfo> clientInfoResult = ConfigServices.GetPrintMateClientInfo();
                    if(clientInfoResult.success)
                    {
                        printMateClientInfo = clientInfoResult.value;
                        Console.WriteLine("SmartBird -> "+ Util.ToJson(printMateClientInfo));
                        //执行打印任务 Topic
                        printTopicReply = "/" + printMateClientInfo.productKey + "/" + printMateClientInfo.deviceName + "/user/print";
                        
                        Init();
                    }
                    else
                    {
                        Console.WriteLine("SmartBird -> 无法启动MQTTServer:" + clientInfoResult.message);
                    }
                }
                else
                {
                    Console.WriteLine("SmartBird -> 缺少ClientCode 与 ClientSecret配置,无法启动MQTTServer!");
                }
            }
            else
            {
                Console.WriteLine("SmartBird -> MQTTServer已启动,无需重复启动!");
            }

           
        }

        MqttSign sign;
        private void Init()
        {
            // 计算MQTT连接参数。
            sign = new MqttSign();
            sign.calculate(printMateClientInfo.productKey, printMateClientInfo.deviceName, printMateClientInfo.deviceSecret);
            Console.WriteLine("username: " + sign.getUsername());
            Console.WriteLine("password: " + sign.getPassword());
            Console.WriteLine("clientid: " + sign.getClientid());

            Connect();
            OnClosed();
            first();          
            Subscribe();
            Publish();
        }

        private void OnClosed()
        {
            mqttClient.ConnectionClosed += Default_ConnectionClosedEventHandler;
        }


        private void Connect(){
            try{
                // 使用Paho连接阿里云物联网平台。
                int port = 443;
                String broker = printMateClientInfo.productKey + ".iot-as-mqtt.cn-shanghai.aliyuncs.com";
                mqttClient = new MqttClient(broker, port, true, MqttSslProtocols.TLSv1_2, null, null);
                mqttClient.Connect(sign.getClientid(), sign.getUsername(), sign.getPassword());
                Console.WriteLine("Broker: " + broker + " Connected");
                MQTTState.state = true;
            }
            catch(Exception e)
            {
                Console.WriteLine("SmartBirt -> "+DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")+" Connect Error:"+e.Message);
                Thread.Sleep(30000);//等待30秒重新连接
                Connect();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void first()
        {
            String topic = "/sys/" + printMateClientInfo.productKey + "/" + printMateClientInfo.deviceName + "/thing/event/property/post";
            String message = "{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}";
            mqttClient.Publish(topic, Encoding.UTF8.GetBytes(message));
        }

        /// <summary>
        /// 客户端订阅打印消息
        /// </summary>
        private void Subscribe()
        {
            mqttClient.MqttMsgPublishReceived += MqttPostProperty_MqttMsgPublishReceived;
            mqttClient.Subscribe(new string[] { printTopicReply }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE });
        }

        /// <summary>
        /// 客户端订阅打印消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MqttPostProperty_MqttMsgPublishReceived(object sender, uPLibrary.Networking.M2Mqtt.Messages.MqttMsgPublishEventArgs e)
        {
            
            string topic = e.Topic;
            string message = Encoding.UTF8.GetString(e.Message);

            Console.WriteLine("reply topic  :" + topic);
            Console.WriteLine("reply payload:" + message);
            try
            {
                //如果topic是执行打印任务的消息
                if(topic == printTopicReply)
                {
                    ImagePrintTask imagePrintTask = Util.JsonTo<ImagePrintTask>(message);
                    NetworkImageServer imageServer = new NetworkImageServer(printMateClientInfo.printName,imagePrintTask);
                    imageServer.Print();
                    Log.Info(printMateClientInfo.clientCode + ",订阅到执行打印任务", "topic=" + topic + ";imagePrintTask="+Util.ToJson(imagePrintTask));
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine("Error:"+ex.Message);
            }
        }


        /// <summary>
        /// 客户端上报运行信息
        /// </summary>
        private void Publish()
        {
            return;
            if(MQTTState.state)
            {
                if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                {
                    ClientSystemInfo clientSystemInfo = new ClientSystemInfo()
                    {
                        temp = float.Parse(System.IO.File.ReadAllText(@"/sys/class/thermal/thermal_zone0/temp")) / 1000
                    };
                    string content = Util.ToJson(clientSystemInfo);
                    mqttClient.Publish("/" + printMateClientInfo.productKey + "/" + printMateClientInfo.deviceName + "/user/info", Encoding.UTF8.GetBytes(content));
                    Thread.Sleep(1000 * 60);
                    Publish();
                }
                else
                {
                    ClientSystemInfo clientSystemInfo = new ClientSystemInfo()
                    {
                        temp = 123
                    };
                    string content = Util.ToJson(clientSystemInfo);
                    mqttClient.Publish("/" + printMateClientInfo.productKey + "/" + printMateClientInfo.deviceName + "/user/info", Encoding.UTF8.GetBytes(content));
                    Thread.Sleep(1000 * 60);
                    Publish();
                }
            }
        }


         /// <summary>
        /// 连接发生异常,断网等
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Default_ConnectionClosedEventHandler(object sender, EventArgs e)
        {
            Console.WriteLine("SmartBirt -> ConnectionClosedEventHandler ");
            Console.WriteLine("Connect Closed error,Date: " + DateTime.Now.ToLongTimeString());
            Connect();
        }


    }




    class CryptoUtil
    {
        public static String hmacSha256(String plainText, String key)
        {
            var encoding = new System.Text.UTF8Encoding();
            byte[] plainTextBytes = encoding.GetBytes(plainText);
            byte[] keyBytes = encoding.GetBytes(key);

            HMACSHA256 hmac = new HMACSHA256(keyBytes);
            byte[] sign = hmac.ComputeHash(plainTextBytes);
            return BitConverter.ToString(sign).Replace("-", string.Empty);
        }
    }
    public class MqttSign
    {
        private String username = "";

        private String password = "";

        private String clientid = "";

        public String getUsername() { return this.username; }

        public String getPassword() { return this.password; }

        public String getClientid() { return this.clientid; }

        public bool calculate(String productKey, String deviceName, String deviceSecret)
        {
            if (productKey == null || deviceName == null || deviceSecret == null)
            {
                return false;
            }

            //MQTT用户名
            this.username = deviceName + "&" + productKey;

            //MQTT密码
            String timestamp = Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds).ToString();
            String plainPasswd = "clientId" + productKey + "." + deviceName + "deviceName" +
                    deviceName + "productKey" + productKey + "timestamp" + timestamp;
            this.password = CryptoUtil.hmacSha256(plainPasswd, deviceSecret);

            //MQTT ClientId
            this.clientid = productKey + "." + deviceName + "|" + "timestamp=" + timestamp +
                    ",_v=paho-c#-1.0.0,securemode=2,signmethod=hmacsha256|";

            return true;
        }
    }


    public class ClientSystemInfo 
    { 
        public float temp { get; set; }
        public int Memory { get; set; }


    }



}

第三步,启动服务时,自动启动打印服务与MQTT客户端订阅

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json.Converters;
using System.Threading;
using PrintMateClient.Server;

namespace PrintMateClient
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers().AddNewtonsoftJson(option =>
                option.SerializerSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" })
              );

            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();//使用IHttpContextAccessor,在controller就可以使用httpContext信息

            //跨域,添加策略
            services.AddCors(options => {
                options.AddPolicy("any", builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); });
            });

            services.AddMvc(
              options =>
              {
                  options.Filters.Add<ExceptionFilter>(); //加入全局异常类
                  options.Filters.Add<ActionFilter>(); //加入全局监测类                    
              });
            new Thread(() =>
            {
                new MQTTServer();
            }).Start();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCors("any");//跨域,启用策略
            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            //每次服务启动时,添加到日志
            Log.Startup("Startup", "");
        }
    }
}

第四步,实现本地打印服务API

ImageController.cs

using GeekOpen.Common;
using GeekOpen.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using PrintMateClient.Server;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Printing;
using System.Linq;
using System.Threading.Tasks;

namespace PrintMateClient.Controllers
{
    [ApiController]
    [Route("[controller]/[action]")]
    public class ImageController : AuthorizeController
    {
        public ImageController(IHttpContextAccessor accessor) : base(accessor)
        {

        }


        /// <summary>
        /// 打印图片,一般手动调用,自动打印在 MQTTServer
        /// </summary>
        /// <param name="clientCode"></param>
        /// <param name="clientSecret"></param>
        /// <param name="imagePrintTask"></param>
        /// <returns></returns>
        [HttpPost("{clientCode}/{clientSecret}")]
        public ResultValue Print(string clientCode,string clientSecret, [FromBody] ImagePrintTask imagePrintTask)
        {
            if(!string.IsNullOrEmpty(clientCode) && !string.IsNullOrEmpty(clientSecret))
            {
                if (clientCode == ConfigServices.GetClientConfig().clientCode && clientSecret==ConfigServices.GetClientConfig().clientSecret)
                {
                    NetworkImageServer imageServer =  new NetworkImageServer(ConfigServices.GetClientConfig().printName,imagePrintTask);
                    imageServer.Print();
                    return new ResultValue();
                }
                else
                {
                    throw new BusException("clientCode或clientSecret错误!");
                }
            }
            else
            {
                throw new BusException("clientCode与clientSecret不能为空!");
            }

        }


    }
}

第五步,实现本地打印机管理视图页面

ManageController.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.Linq;
using System.Threading.Tasks;

namespace PrintMateClient.Controllers
{
    [ApiController]
    [Route("")]
    public class ManageController : AuthorizeController
    {
        public ManageController(IHttpContextAccessor accessor) : base(accessor)
        {

        }

        [HttpGet]
        public IActionResult Index()
        {
            List<PrintInfo> printInfos = new List<PrintInfo>();
            foreach (string sPrint in PrinterSettings.InstalledPrinters)//获取所有打印机名称
            {
                printInfos.Add(new PrintInfo() { printName = sPrint});
            }

            string message = "";
            if (!string.IsNullOrEmpty(Request.Query["success"]))
            {
                if (Request.Query["success"] == "true")
                {
                    message = "设置成功:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
                }
            }
            ClientConfigView clientConfigView = new ClientConfigView()
            {
                message = message,
                printInfos = printInfos,
                clientConfig = ConfigServices.GetClientConfig()
            };

            return View(clientConfigView);
        }

    }
}

client.config.json

{"printName":"HPF439095CA4C7 (HP Laser MFP 131 133 135-138)","clientName":"T490笔记本开发","clientCode":"PrintMateClient202106103819","clientSecret":"Ah0192xW8hS2lo301vdSpKr10DG10375"}

Views/Manage/Index.cshtml

@using PrintMateClient
@model ClientConfigView
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>
        打印服务设置 - PrintMateClient 1.0
    </title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="Cache-Control" content="no-cache" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no" />
    <link rel="shortcut icon" href="/favicon.ico" />
    <style>
        body {
            font-family: '微软雅黑',Arial, Helvetica, sans-serif;
            margin: 0px;
            padding: 0px;
            background-color: #f2f5f8;
            min-height: 100%;
        }

        * {
            table-layout: fixed;
            word-break: break-all;
            font-family: '微软雅黑',Arial, Helvetica, sans-serif;
            box-sizing: border-box;
            font-size: 14px;
            line-height: 18px;
        }

        .hidden {
            display: none;
        }

        span {
            display: inline-block;
            vertical-align: middle;
        }

        input {
            height: 35px;
        }

            input[type="submit"] {
                background-color: #202125;
                color: #fff;
            }

        select {
            height: 35px;
            padding: 5px;
        }
    </style>

</head>
<body style="background-color:#f2f5f8;">
    <div style="padding:50px;margin:auto;width:480px;">
        <form name="myform" method="post" enctype="multipart/form-data" action="/api/ClientConfig/Update">
            <div style="display:flex;align-items:center;padding:0px 0px 20px 0px;">
                <div style="flex-grow:1; padding:0px 0px 0px 0px;">
                    <a href="/"><img src="https://oss-smart-bird.oss-cn-qingdao.aliyuncs.com/geek-open/GeekOpen-logo.png" style="height:40px;" title="理工智控-GeekOpen" /></a>
                </div>
                <div style="flex-grow:1;font-size:14px;font-weight:bold;text-align:right;padding:3px 0px 0px 0px;">
                    PrintMateClient 1.0
                </div>
            </div>

            <div style="padding:20px 0px 5px 0px;">打印机名称</div>
            <select name="printName" id="printName" style="width: 100%;">
                @for (int i = 0; i < Model.printInfos.Count; i++)
                {
                    <option value="@Model.printInfos[i].printName" @if(Model.printInfos[i].printName == Model.clientConfig.printName) { Write(" selected"); }>@Model.printInfos[i].printName</option>
                }
            </select>
            <div style="padding:20px 0px 5px 0px;">打印机描述</div>
            <div>
                <input type="text" name="clientName" id="clientName" value="@Model.clientConfig.clientName" style="width:100%;" />
            </div>
            <div style="padding:20px 0px 5px 0px;">ClientCode</div>
            <div>
                <input type="text" name="clientCode" id="clientCode" value="@Model.clientConfig.clientCode" style="width:100%;" />
            </div>
            <div style="padding:20px 0px 5px 0px;">ClientSecret</div>
            <div>
                <input type="text" name="clientSecret" id="clientSecret" value="@Model.clientConfig.clientSecret" style="width:100%;" />
            </div>
            <div style="font-size:12px;padding:15px 0px 0px 0px;color:#0094ff;text-align:center;">
                @Model.message
            </div>
            <div style="padding:35px 0px 0px 0px;">
                <input type="submit" name="mysubmit" value="保存设置" style="width:100%;" />
            </div>

            <div style="font-size:12px;color:#707275;text-align:center;padding:30px 0px 0px 0px;line-height:25px;">
                <a href="http://www.geek-open.com" target="_blank" style="color:#707275;">www.geek-open.com</a>
                <br />Copyright © 2020-2023 All rights reserved
            </div>
        </form>
    </div>


</body>
</html>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值