你以爲的監聽程序(消息鏈篇)

對檢驗系統來說,監聽程序是什麼?
是儀器接口?
是打印環境?
是虛擬盒子?
是導出環境?
這次結合打印解析監聽程序消息鏈,爲何打印、截圖等需要開啓着監聽。
瀏覽器是BS的網頁,打印截圖等是CS程序,首先就不得不說到BS與CS交互的問題了。檢驗採用下圖結構的多條通道進行嘗試的模式交互。後期版本交互的關鍵就是WebSockt方式。
在這裏插入圖片描述
WebSockt的服務在哪裏,當然寄宿在監聽程序裏面了。當然是爲了客戶端環境單一,其實可以寄宿在任意EXE裏的,如下圖,監聽啓動時候就把WebSockt服務起來了

在這裏插入圖片描述
以下才是本次分享的關鍵
常規寫法下在消息服務裏收到消息後寫死消息處理的邏輯不就完事了。如下面模式:

socket.OnMessage = message =>
{
	//按message內容寫處理邏輯就完事,分支多就來幾個if判斷
}

一旦按以上那麼寫了的話,那麼消息作爲通道的想法就沒用了,有新業務需要藉助消息就只能不斷加入代碼和判斷處理,也使得消息不在單純、穩定。
爲此:先抽離消息處理接口如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Fleck;

namespace LIS.MsgCore
{
    ///<summary  NoteObject="Class">
    /// [功能描述:消息處理接口,不同消息處理實現該接口] <para/>
    /// [創建者:zlz] <para/>
    /// [創建時間:2017年03月29日] <para/>
    ///<說明>
    ///  [說明:監聽程序主窗口]<para/>
    ///</說明>
    ///<修改記錄>
    ///    [修改時間:本次修改時間]<para/>
    ///    [修改內容:本次修改內容]<para/>
    ///</修改記錄>
    ///<修改記錄>
    ///    [修改時間:本次修改時間]<para/>
    ///    [修改內容:本次修改內容]<para/>
    ///</修改記錄>
    ///</summary>
    public interface IMessageDeal
    {
        /// <summary>
        /// 處理消息
        /// </summary>
        /// <param name="socket">套接字,可以獲得id,發送消息給socket</param>
        /// <param name="message">約定#分割的第一位描述消息類型,收到的消息內容</param>
        /// <returns>是否繼續往後傳遞消息,true是,false否</returns>
        bool DealMessage(IWebSocketConnection socket,string message);
    }
}

然後再定義一個消息鏈對象用來支持配置消息處理鏈

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LIS.MsgCore
{
    ///<summary  NoteObject="Class">
    /// [功能描述:消息處理鏈] <para/>
    /// [創建者:zlz] <para/>
    /// [創建時間:2017年03月28日] <para/>
    ///<說明>
    ///  [說明:路徑工具類,提供路徑操作]<para/>
    ///</說明>
    ///<修改記錄>
    ///    [修改時間:本次修改時間]<para/>
    ///    [修改內容:本次修改內容]<para/>
    ///</修改記錄>
    ///<修改記錄>
    ///    [修改時間:本次修改時間]<para/>
    ///    [修改內容:本次修改內容]<para/>
    ///</修改記錄>
    ///</summary>
    public class MsgDealLink
    {
        /// <summary>
        /// 處理連接集合
        /// </summary>
        public List<IMessageDeal> LinkList
        {
            get;
            set;
        }
    }
}

然後消息服務收到消息後就只針對消息鏈的消息接口處理。調用消息鏈的每個對象來處理消息,直到返回false或者整個消息鏈都調完了,要點如下:
在這裏插入圖片描述

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LIS.MsgCore;
using Fleck;
using System.Timers;
using System.Net.NetworkInformation;
using System.Net;

namespace LISMonitor
{
    public class MessageServer
    {
        /// <summary>
        /// 改變圖標委託
        /// </summary>
        public ExcCodeDelegate ChangeIcon
        {
            get;
            set;
        }

        /// <summary>
        /// 消息服務
        /// </summary>
        private static WebSocketServer server = null;

        /// <summary>
        /// 存所有套接字
        /// </summary>
        private static List<IWebSocketConnection> allSockets = new List<IWebSocketConnection>();

        /// <summary>
        /// 消息處理鏈對象
        /// </summary>
        MsgDealLink dealLinks = null;

        /// <summary>
        /// 定時器
        /// </summary>
        Timer timer = new Timer();

        /// <summary>
        /// 消息端口
        /// </summary>
        public int port=8082;

        /// <summary>
        /// 初始化消息服務
        /// </summary>
        /// <returns></returns>
        public string InitServer()
        {
            timer.Interval = 5000;
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            //取消息鏈對象
            dealLinks = LIS.Core.Context.ObjectContainer.GetObject<MsgDealLink>();
            //檢查端口占用
            bool isUse = PortInUse(port);
            if (isUse == true)
            {
                port = 10210;
                isUse=PortInUse(port);
            }
            if (isUse == true)
            {
                port = 19910;
                isUse = PortInUse(port);
            }
            if (isUse == true)
            {
                port = 19902;
                isUse = PortInUse(port);
            }
            if (isUse == true)
            {

                return "8082端口和10210,19910,19902端口都被佔用!無法啓動消息服務!";
            }
            try
            {
                server = new WebSocketServer("ws://127.0.0.1:" + port);
                server.Start(socket =>
                {
                    socket.OnOpen = () =>
                    {
                        //改變圖標
                        if (ChangeIcon != null)
                        {
                            ChangeIcon("lm2");
                            timer.Start();
                        }
                        allSockets.Add(socket);
                        LIS.Core.Util.LogUtils.WriteDebugLog("#S消息服務打開連接:" + socket.ConnectionInfo.Id+socket.ConnectionInfo.Path);
                    };
                    socket.OnClose = () =>
                    {
                        //改變圖標
                        if (ChangeIcon != null)
                        {
                            ChangeIcon("lm1");
                            timer.Start();
                        }
                        allSockets.Remove(socket);
                        LIS.Core.Util.LogUtils.WriteDebugLog("#S消息服務關閉連接:" + socket.ConnectionInfo.Id + socket.ConnectionInfo.Path);
                    };
                    socket.OnMessage = message =>
                    {
                        //改變圖標
                        if (ChangeIcon != null)
                        {
                            ChangeIcon("msg");
                            timer.Start();
                        }
                        LIS.Core.Util.LogUtils.WriteDebugLog("#S消息服務收到消息:" + socket.ConnectionInfo.Id + socket.ConnectionInfo.Path + "內容:" + message);
                        LIS.Core.Util.LogUtils.WriteDebugLog("#S消息服務準備調用消息鏈...");
                        try
                        {
                            if (dealLinks != null && dealLinks.LinkList != null && dealLinks.LinkList.Count > 0)
                            {
                                foreach (IMessageDeal deal in dealLinks.LinkList)
                                {
                                    LIS.Core.Util.LogUtils.WriteDebugLog("#S調用:" + deal.GetType().FullName + "...");
                                    bool ret=deal.DealMessage(socket, message);
                                    LIS.Core.Util.LogUtils.WriteDebugLog("#S調用:" + deal.GetType().FullName + "結束...");
                                    //返回false不傳遞消息了
                                    if (ret == false)
                                    {
                                        LIS.Core.Util.LogUtils.WriteDebugLog("#S消息鏈不繼續傳遞消息...");
                                        break;
                                    }
                                }
                            }
                            LIS.Core.Util.LogUtils.WriteDebugLog("#S消息服務調用消息鏈結束...");
                        }
                        catch (Exception ex)
                        {
                            LIS.Core.Util.LogUtils.WriteExceptionLog("#S消息服務調用消息鏈異常", ex);
                        }
                        
                    };
                });
            }
            catch (Exception ex)
            {
                LIS.Core.Util.LogUtils.WriteExceptionLog("#S啓動消息服務出錯", ex);
                return "#S啓動消息服務出錯:" + ex.Message+"調用堆棧:"+ex.StackTrace;
            }
            return "";
        }

        /// <summary>
        /// 判斷端口是否被佔用
        /// </summary>
        /// <param name="port">端口</param>
        /// <returns></returns>
        public bool PortInUse(int port)
        {
            bool inUse = false;

            IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
            IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();

            foreach (IPEndPoint endPoint in ipEndPoints)
            {
                if (endPoint.Port == port)
                {
                    inUse = true;
                    break;
                }
            }
            return inUse;
        }  

        /// <summary>
        /// 定時方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            //改變圖標
            if (ChangeIcon != null)
            {
                ChangeIcon("lm0");
                timer.Stop();
            }
        }
    }
}

然後各種業務就只需要實現消息處理接口就行了。以打印、截圖、編輯圖片代碼爲例:
打印

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Net;
using System.Management;
using System.Xml;

namespace LIS.MsgCore
{
    /// <summary>
    /// 打印消息處理實現
    /// </summary>
    public class MessagePrintDeal:IMessageDeal
    {
        /// <summary>
        /// 處理消息
        /// </summary>
        /// <param name="socket">套接字,可以獲得id,發送消息給socket</param>
        /// <param name="message">約定#分割的第一位描述消息類型,收到的消息內容</param>
        /// <returns>是否繼續往後傳遞消息,true是,false否</returns>
        public bool DealMessage(Fleck.IWebSocketConnection socket, string message)
        {
            LIS.Core.Util.LogUtils.WriteDebugLog("識別以print#開頭的消息");
            //識別打印消息
            if (message.Split('#')[0] == "print")
            {
                LIS.Core.Util.LogUtils.WriteDebugLog("確定爲打印消息,準備處理");
                int index = message.IndexOf('#');
                string msg = message.Substring(index+1);
                string [] arrMsg=msg.Split('@');
                //清除歷史數據命令攔截
                if (arrMsg.Length == 2 && arrMsg[0].Contains("ClearHistory"))
                {
                    LIS.Core.Util.LogUtils.WriteDebugLog("攔截到清除緩存消息");
                    string chromePath = "";
                    bool IsChrome = SubKeyOperation.TryGetSoftwarePath("chrome", out chromePath);
                    if (IsChrome && chromePath.Length > 0)
                    {
                        DirectoryInfo di = new DirectoryInfo(chromePath);
                        string cachePath = di.Parent.Parent.FullName + "\\User Data\\";
                        DeleteFile(cachePath + "Default\\Cache", "");
                        DeleteFile(cachePath + "Profile 1\\Cache", "");
                        DeleteFile(cachePath + "Profile 2\\Cache", "");
                        DeleteFile(cachePath + "Profile 3\\Cache", "");
                        DeleteFile(cachePath + "Profile 4\\Cache", "");
                        DeleteFile(cachePath + "Profile 5\\Cache", "");
                        DeleteFile(cachePath + "Profile 6\\Cache", "");
                        DeleteFile(cachePath + "Profile 7\\Cache", "");
                        DeleteFile(cachePath + "Profile 8\\Cache", "");
                    }
                    //更新TRAK
                    if (arrMsg[1] != "")
                    {
                        if (System.IO.File.Exists(@"C:\TRAK\LISUpGrade.exe"))
                        {
                            Process.Start(@"C:\TRAK\LISUpGrade.exe", arrMsg[1]);
                        }
                    }
                    return false;
                }
                //報告打印消息直接處理,不驅動exe,提高速度
                if (arrMsg.Length > 5 && (!arrMsg[4].Contains("PDF#")) && (arrMsg[0] == "iMedicalLIS://0" || arrMsg[0] == "iMedicalLIS://1") && (arrMsg[4] != "ReportView"))
                {
                    string cmdLine = msg.Substring(14);
                    string[] tmpStrings = cmdLine.Split((char)64);
                    string printFlag = tmpStrings[0];
                    string connectString = tmpStrings[1].Replace("&", "&amp;");
                    if (System.IO.File.Exists("C:\\TRAK\\ResultPrint.exe.config"))
                    {
                        XmlDocument doc = new XmlDocument();
                        doc.Load("C:\\TRAK\\ResultPrint.exe.config");
                        //找出名稱爲“add”的所有元素  
                        XmlNodeList nodes = doc.GetElementsByTagName("add");
                        for (int i = 0; i < nodes.Count; i++)
                        {
                            //獲得將當前ConfDataCode屬性  
                            XmlAttribute attConf = nodes[i].Attributes["key"];
                            //根據元素的第一個屬性來判斷當前的元素是不是目標元素  
                            if (attConf != null && attConf.Value == "WebServiceAddressOne")
                            {
                                attConf = nodes[i].Attributes["value"];
                                if(attConf.Value!=null&&attConf.Value!="")
                                {
                                    connectString = attConf.Value;
                                }
                            }
                        }
                    }
                    string rowids = tmpStrings[2];
                    string userCode = tmpStrings[3];
                    //PrintOut:打印  PrintPreview打印預覽
                    string printType = tmpStrings[4];
                    //參數
                    string paramList = tmpStrings[5];    ///模塊名稱(LIS工作站,DOC醫生,SELF自助,OTH其它)

                    string clsName = "";
                    string funName = "";
                    if (tmpStrings.Length >= 8)
                    {
                        clsName = tmpStrings[6];
                        funName = tmpStrings[7];
                    }

                    //沒傳報告主鍵退出
                    if (rowids == "" && printType != "ReportView")
                    {
                        LIS.Core.Util.LogUtils.WriteDebugLog("未傳入報告主鍵");
                        return true;
                    };
                    string ip = "";
                    string hostName = Dns.GetHostName();  //本機名
                    System.Net.IPAddress[] addressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
                    for (int i = 0; i < addressList.Length; i++)
                    {
                        ip = addressList[i].ToString();
                    }

                    string mac = "";
                    //部分電腦有問題
                    //ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
                    //ManagementObjectCollection moc = mc.GetInstances();
                    //foreach (ManagementObject mo in moc)
                    //{
                    //    if (mo["IPEnabled"].ToString() == "True")
                    //    {
                    //        mac = mo["MacAddress"].ToString();
                    //    }
                    //}
                    paramList = paramList + "^HN" + hostName + "^IP" + ip + "^MAC" + mac;
                    //printFlag  0:打印所有報告 1:循環打印每一份報告
                    if (printFlag.Substring(0, 1) == "0")
                    {
                        DHCLabtrakReportPrint.ReportAccess reportPrint = new DHCLabtrakReportPrint.ReportAccess(rowids, userCode, paramList, connectString, printType, clsName, funName);
                    }
                    else
                    {
                        string[] tmpRowids = rowids.Split((char)94);
                        for (int i = 0; i < tmpRowids.Length; i++)
                        {
                            rowids = tmpRowids[i];
                            if (rowids != "")
                            {
                                DHCLabtrakReportPrint.ReportAccess reportPrint = new DHCLabtrakReportPrint.ReportAccess(rowids, userCode, paramList, connectString, printType, clsName, funName);
                            }
                        }
                    }
                }
                else
                {
                    if (System.IO.File.Exists(@"C:\TRAK\ResultPrint.exe"))
                    {

                        LIS.Core.Util.LogUtils.WriteDebugLog("調用打印程序");
                        System.Diagnostics.Process.Start(@"C:\TRAK\ResultPrint.exe", msg.Replace("\"", "\\\"").Replace("+", "%2B"));
                    }
                    else
                    {
                        LIS.Core.Util.LogUtils.WriteDebugLog(@"C:\TRAK\ResultPrint.exe");
                    }
                }
                LIS.Core.Util.LogUtils.WriteDebugLog("處理完成,截斷消息鏈");
                return false;
            }
            LIS.Core.Util.LogUtils.WriteDebugLog("不是打印消息,傳遞消息鏈");
            return true;
        }

        /// <summary>
        /// 刪除指定目錄指定後綴名的文件
        /// </summary>
        /// <param name="path"></param>
        /// <param name="extend"></param>
        private void DeleteFile(string path, string extend)
        {
            if (Directory.Exists(path))
            {
                DirectoryInfo di = new DirectoryInfo(path);
                FileInfo[] files = di.GetFiles();
                if (files != null && files.Length > 0)
                {
                    foreach (var v in files)
                    {
                        try
                        {
                            if (v.Extension == extend)
                            {
                                System.IO.File.Delete(v.FullName);
                            }
                        }
                        catch
                        {
                        }
                    }
                }
            }
        }
    }
}

截圖

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;
using LISScreenCapture;

namespace LIS.MsgCore
{
    /// <summary>
    /// 捕獲圖片消息處理實現
    /// </summary>
    public class MessageGetImageCommon : IMessageDeal
    {
        /// <summary>
        /// 當前消息
        /// </summary>
        private string curMsg;

        /// <summary>
        /// 當前端口
        /// </summary>
        private Fleck.IWebSocketConnection curSocket;

        /// <summary>
        /// 處理消息
        /// </summary>
        /// <param name="socket">套接字,可以獲得id,發送消息給socket</param>
        /// <param name="message">約定#分割的第一位描述消息類型,收到的消息內容</param>
        /// <returns>是否繼續往後傳遞消息,true是,false否</returns>
        public bool DealMessage(Fleck.IWebSocketConnection socket, string message)
        {
            LIS.Core.Util.LogUtils.WriteDebugLog("識別以getimage#開頭的消息");
            //識別打印消息
            if (message.Split('#')[0] == "getimage")
            {
                LIS.Core.Util.LogUtils.WriteDebugLog("確定爲捕獲圖片消息,準備處理");
                int index = message.IndexOf('#');
                string msg = message.Substring(index+1);
                if (msg.Contains("#Capture"))
                {
                    msg = msg.Replace("#Capture", "");
                    curMsg = msg;
                    curSocket = socket;
                    MethodInvoker mi = new MethodInvoker(this.ShowCaptureForm);
                    Application.OpenForms["FrmMian"].BeginInvoke(mi);
                }
                else
                {
                    curMsg = msg;
                    if (curMsg.Length > 0)
                    {
                        if (curMsg.Substring(curMsg.Length - 1) == "#")
                        {
                            curMsg = curMsg.Substring(0, curMsg.Length - 1);
                        }
                    }
                    curSocket = socket;
                    MethodInvoker mi = new MethodInvoker(this.ShowForm);
                    Application.OpenForms["FrmMian"].BeginInvoke(mi);
                }
                LIS.Core.Util.LogUtils.WriteDebugLog("處理完成,截斷消息鏈");
                return false;
            }
            LIS.Core.Util.LogUtils.WriteDebugLog("不是捕獲圖片消息,傳遞消息鏈");
            return true;
        }

        /// <summary>
        /// 顯示窗口
        /// </summary>
        private void ShowForm()
        {
            FrmGetImage frm = new FrmGetImage();
            frm.Ftp = curMsg;
            frm.Socket = curSocket;
            frm.ShowDialog();
        }

        /// <summary>
        /// 顯示窗口
        /// </summary>
        private void ShowCaptureForm()
        {
            FrmCaptureReportImg frmC = new FrmCaptureReportImg();
            frmC.Ftp = curMsg;
            frmC.Socket = curSocket;
            frmC.ShowDialog();
        }  
    }
}

編輯圖片

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using LISScreenCapture;
using System.Drawing;
using System.Windows.Forms;

namespace LIS.MsgCore
{
    /// <summary>
    /// 編輯圖片消息處理
    /// </summary>
    public class MessageEditImageDeal : IMessageDeal
    {
        /// <summary>
        /// 消息接口
        /// </summary>
        private Fleck.IWebSocketConnection cursocket = null;

        /// <summary>
        /// 窗口
        /// </summary>
        CutBord frm = null;

        /// <summary>
        /// 處理消息
        /// </summary>
        /// <param name="socket">套接字,可以獲得id,發送消息給socket</param>
        /// <param name="message">約定#分割的第一位描述消息類型,收到的消息內容</param>
        /// <returns>是否繼續往後傳遞消息,true是,false否</returns>
        public bool DealMessage(Fleck.IWebSocketConnection socket, string message)
        {
            LIS.Core.Util.LogUtils.WriteDebugLog("識別以editimage#開頭的消息");
            //識別編輯圖片消息
            if (message.Split('#')[0] == "editimage")
            {
                LIS.Core.Util.LogUtils.WriteDebugLog("確定爲編輯圖片消息,準備處理");
                int index = message.IndexOf('#');
                cursocket = socket;
                string msg = message.Substring(index + 1);
                frm = new CutBord();
                frm.Path = msg;
                frm.SaveCallBack = new SaveCallback(SaveImg);
                MethodInvoker mi = new MethodInvoker(this.ShowForm);
                Application.OpenForms["FrmMian"].BeginInvoke(mi);
                LIS.Core.Util.LogUtils.WriteDebugLog("處理完成,截斷消息鏈");
                return false;
            }
            LIS.Core.Util.LogUtils.WriteDebugLog("不是編輯圖片消息,傳遞消息鏈");
            return true;
        }

        /// <summary>
        /// 顯示窗口
        /// </summary>
        private void ShowForm()
        {
            frm.TopMost = true;
            frm.ShowDialog();
        }

        /// <summary>
        /// 保存圖片
        /// </summary>
        /// <param name="img">圖片</param>
        /// <param name="path">路徑</param>
        public void SaveImg(Bitmap img, string path)
        {
            if (MessageBox.Show("是否要保存修改到服務器?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Information) == DialogResult.OK)
            {
                Upload(img, path);
            }
        }

        /// <summary>
        /// 上傳圖片
        /// </summary>
        /// <param name="img">圖片</param>
        /// <param name="path">路徑</param>
        public void Upload(Bitmap img, string path)
        {
            Stream stream = new MemoryStream();
            System.Drawing.Imaging.ImageFormat format = System.Drawing.Imaging.ImageFormat.Bmp;
            //擴展名
            string aLastName = path.Substring(path.LastIndexOf(".") + 1);
            aLastName = aLastName.ToUpper();
            if (aLastName == "JPG")
            {
                format = System.Drawing.Imaging.ImageFormat.Jpeg;
            }
            else if (aLastName == "EMF")
            {
                format = System.Drawing.Imaging.ImageFormat.Emf;
            }
            else if (aLastName == "EXIF")
            {
                format = System.Drawing.Imaging.ImageFormat.Exif;
            }
            else if (aLastName == "GIF")
            {
                format = System.Drawing.Imaging.ImageFormat.Gif;
            }
            else if (aLastName == "ICON")
            {
                format = System.Drawing.Imaging.ImageFormat.Icon;
            }
            else if (aLastName == "JPEG")
            {
                format = System.Drawing.Imaging.ImageFormat.Jpeg;
            }
            else if (aLastName == "PNG")
            {
                format = System.Drawing.Imaging.ImageFormat.Png;
            }
            else if (aLastName == "TIFF")
            {
                format = System.Drawing.Imaging.ImageFormat.Tiff;
            }
            else if (aLastName == "WMF")
            {
                format = System.Drawing.Imaging.ImageFormat.Wmf;
            }
            try
            {
                img.Save(stream, format);
                LIS.File.Core.FileService fileService = new File.Core.FileService();
                fileService.Upload(path.Substring(0, path.LastIndexOf("/") + 1), stream, path.Substring(path.LastIndexOf("/") + 1), "");
                stream.Close();
                cursocket.Send("1");
            }
            catch (Exception ex)
            {
                MessageBox.Show("保存失敗:" + ex.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

然後把消息處理實現類配置到消息鏈對象即可,依賴容器配置,也能自己反射
在這裏插入圖片描述

至此一個有充分開放性的消息鏈通道就開發完成了。使得監聽具有不光是檢驗打印的監聽,也能是截圖的監聽,也能是編輯文件的、CA的等等,也能是非檢驗的BS-CS交互通道,只需要你使用消息處理接口配置到消息鏈中。這就是設計模式帶來的魅力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章