C#开发纽曼USB来电小秘书客户端总结
在使用c#开发完crm的来电弹屏之后,有些客户有了新的要求,他们希望不但能够实现来电弹屏,更希望能够将呼入呼出的电话录音并上传到crm服务器上,方便日后跟踪记录。于是便有了来电小秘书客户端的开发。
本文所述的来电小秘书客户端的开发是基于纽曼usb来电通客户端的基础上进行开发的,由于纽曼usb来电通的硬件没有录音功能,于是硬件上使用了纽曼的另一个硬件产品来电小秘书,虽然是同一个厂家的产品,可是它们的api却是完全不兼容,更烦的是,来电小秘书api没有来电的回调接口,无法通过回调触发程序,也没有c#的demo,很多功能只能通过一个不是那么详细的文档和一个delphi的demo摸索着做了,经历了一些挫折和困惑,终于完成了这个客户端程序。
首先,开发要做的就是与硬件的api进行沟通,依然通过c#的p/invoke来完成,以下是来电小秘书的p/invoke代码。
using system; using system.collections.generic; using system.text; using system.runtime.interopservices; namespace windowsapplication1 { class ldt1 { [dllimport("usbms.dll", entrypoint = "loaddrv")] public static extern int loaddrv(); [dllimport("usbms.dll", entrypoint = "enablecard")] public static extern int enablecard(); [dllimport("usbms.dll", entrypoint = "stopsigcheck")] public static extern int stopsigcheck(int handle); [dllimport("usbms.dll", entrypoint = "resetusb")] public static extern int resetusb(int handle); [dllimport("usbms.dll", entrypoint = "hangup")] public static extern int hangup(int handle); [dllimport("usbms.dll", entrypoint = "initdtmfbuf")] public static extern int initdtmfbuf(int handle); [dllimport("usbms.dll", entrypoint = "setdialpara")] public static extern int setdialpara(uint16 ringback1, uint16 ringback0, uint16 busylen, uint16 ringtimes, uint16 sendnosignallen); [dllimport("usbms.dll", entrypoint = "disablecard")] public static extern int disablecard(); [dllimport("usbms.dll", entrypoint = "freedrv")] public static extern int freedrv(); [dllimport("usbms.dll", entrypoint = "getdtmfcode")] public static extern int getdtmfcode(uint16 line); [dllimport("usbms.dll", entrypoint = "isring")] public static extern bool isring(uint16 line); [dllimport("usbms.dll", entrypoint = "getcalleridstr")] public static extern uint16 getcalleridstr(uint16 line, stringbuilder idstr); [dllimport("usbms.dll", entrypoint = "isoffhook")] public static extern bool isoffhook(uint16 line); [dllimport("usbms.dll", entrypoint = "startrecordfile")] public static extern bool startrecordfile(uint16 line, string filename, uint32 dwrecordlen); [dllimport("usbms.dll", entrypoint = "checkrecordend")] public static extern bool checkrecordend(uint16 line); [dllimport("usbms.dll", entrypoint = "stoprecordfile")] public static extern bool stoprecordfile(uint16 line); [dllimport("usbms.dll", entrypoint = "pcmtowave")] public static extern int pcmtowave(string sourcefilename, string targetfilename); [dllimport("usbms.dll", entrypoint = "readcheckresult")] public static extern int readcheckresult(int line, int mode); [dllimport("usbms.dll", entrypoint = "startsigcheck")] public static extern void startsigcheck(int line); [dllimport("usbms.dll", entrypoint = "readusbstate")] public static extern bool readusbstate(int line); [dllimport("usbms.dll", entrypoint = "getringnum")] public static extern int getringnum(int line); [dllimport("usbms.dll", entrypoint = "initringnum")] public static extern void initringnum(int line); [dllimport("usbms.dll", entrypoint = "readserialno")] public static extern int readserialno(int line,stringbuilder serialno); } }
然后就是关于设备状态检测了,由于没有api直接支持来电回调,所以只能自己手动的检测设备状态来判断,要实现这一部分一般有两种方式,使用timer或者使用thread,delphi的demo中使用了timer,可是timer实现的弊端需要使用异步的思考方式,不符合我们的思维模式,灵活度也不够,而且c#创建线程太方便了,而线程是通过同步方式思考的,所以使用了thread模式。
然后在特定的时刻,记录电话号码、弹屏(如果是来电)、电话结束后录音和上传文件和信息到crm服务器,其中来电号码可以很容易的获取,可是播出的号码获取就比较的麻烦了,c#中可以使用如下代码:
while (ldt1.isoffhook((ushort)this.line)) { int temp = ldt1.getdtmfcode((ushort)this.line); if (temp > 0) { phonenum = phonenum + this.convertint(temp); } thread.sleep(300); } private string convertint(int code) { string ret=""; switch (code) { case 10: ret = "0"; break; case 11: ret = "*"; break; case 12: ret = "#"; break; case 13: ret = "a"; break; case 14: ret = "b"; break; case 15: ret = "c"; break; case 16: ret = "d"; break; default: ret = code.tostring(); break; } return ret; }
下面说一下c#中的大文件上传吧,网上有很多例子了,可以参考如下文章中的代码进行开发,可是无法上传成功,于是解读了一下代码,发现他将信息中的\r\n用空字符代替了,导致服务器无法识别,于是在更改了他的代码之后,问题解决了,代码如下:
public static string uploadfileex(string uploadfile, string url, string fileformname, string contenttype, namevaluecollection querystring, cookiecontainer cookies) { if ((fileformname == null) || (fileformname.length == 0)) { fileformname = "file"; } if ((contenttype == null) || (contenttype.length == 0)) { contenttype = "application/octet-stream"; } string postdata; postdata = "?"; if (querystring != null) { foreach (string key in querystring.keys) { postdata += key + "=" + querystring.get(key) + "&"; } } uri uri = new uri(url + postdata); string boundary = "----------" + datetime.now.ticks.tostring("x"); httpwebrequest webrequest = (httpwebrequest)webrequest.create(uri); //webrequest.cookiecontainer = cookies; webrequest.contenttype = "multipart/form-data; boundary=" + boundary; webrequest.method = "post"; string huanhang = "\r\n"; byte[] huanhangbyte = encoding.utf8.getbytes(huanhang); // build up the post message header stringbuilder sb = new stringbuilder(); sb.append("--"); sb.append(boundary); sb.append("\r\n"); sb.append("content-disposition: form-data; name=\""); sb.append(fileformname); sb.append("\"; filename=\""); sb.append(path.getfilename(uploadfile)); sb.append("\""); sb.append("\r\n"); sb.append("content-type: "); sb.append(contenttype); sb.append("\r\n"); sb.append("\r\n"); string postheader = sb.tostring(); byte[] postheaderbytes = encoding.utf8.getbytes(postheader); // build the trailing boundary string as a byte array // ensuring the boundary appears on a line by itself byte[] boundarybytes = encoding.ascii.getbytes("--" + boundary + ""); filestream filestream = new filestream(uploadfile, filemode.open, fileaccess.read); long length = postheaderbytes.length + filestream.length + boundarybytes.length + huanhangbyte.length; webrequest.contentlength = length; stream requeststream = webrequest.getrequeststream(); // write out our post header requeststream.write(postheaderbytes, 0, postheaderbytes.length); // write out the file contents byte[] buffer = new byte[checked((uint)math.min(4096, (int)filestream.length))]; int bytesread = 0; while ((bytesread = filestream.read(buffer, 0, buffer.length)) != 0) requeststream.write(buffer, 0, bytesread); requeststream.write(huanhangbyte, 0, huanhangbyte.length); // write out the trailing boundary requeststream.write(boundarybytes, 0, boundarybytes.length); filestream.dispose(); requeststream.dispose(); webresponse responce = webrequest.getresponse(); stream s = responce.getresponsestream(); streamreader sr = new streamreader(s); string retval=sr.readtoend(); sr.dispose(); if (file.exists(uploadfile)) { try { file.delete(uploadfile); }catch(exception e) { } } return retval; }
crm来电小秘书客户端完成了,当然要配合这个功能,服务器端crm系统也要做一些修改,不过不是这篇文章的主要内容,关于服务器端的修改的小节,后续会有相关的报导。