2023年2月

本随笔系列主要介绍从一个Windows平台从事C#开发到Mac平台苹果开发的一系列感想和体验历程,本系列文章是在起步阶段逐步积累的,希望带给大家更好,更真实的转换历程体验。本文继续上一篇随笔《
从C#到Objective-C,循序渐进学习苹果开发(3)--分类(category)和协议Protocal的理解
》,继续对比介绍它们两者之间的差异,以便我们从C#阵营过来的人员加深印象,深入了解Objective-C语言的特性。本篇随笔主要针对Objective-C里面的代码块(block)和异常处理概念的理解进行介绍。

1、Object C的代码块(block)

Objective-C的代码块从刚刚学习的时候,感觉有点奇怪,慢慢感觉它在C#里面也有点熟悉,它在Objective-C里面的引入,好像是主要用来解决代码回调和同步调用的问题的,说到这里,如果熟悉C#的特性的,可能会联想到了C#里的Action<T>和Func<T>的概念了吧,没错,他们就是一丘之貉,哈哈。

代码块本质上是和其他变量类似。不同的是,代码块存储的数据是一个函数体。使用代码块是,你可以像调用其他标准函数一样,传入参数数,并得到返回值,
字符(^)是代码块的语法标记

如下面的例子就是一个代码块的定义

void (^simpleBlock)(void) = ^{
NSLog(
@"This is a block");
};

定义后,你就可以通过类似函数的方式进行使用了,看了下面的代码是不是感觉很熟悉的样子呢。

simpleBlock();

当然,对于这样的东西,它也是可以接受参数的,即使是多个参数也没问题,这个如果是带参数的,应该就是和C#的Func<T>很相似了,下面是一个两个参数的代码块例子。

double (^multiplyTwoValues)(double, double) =
                              ^(double firstValue, doublesecondValue) {return firstValue *secondValue;
};
double result = multiplyTwoValues(2,4);

这样的代码块,它还可以获取类里面定义的局部变量,但是由于它的特殊性,好像如果不加特殊处理,它获取到的变量或者属性的值,是在它出现的那瞬间的快照。

下面一个例子,很好介绍代码块里面获取内容是快照的现实。

int anInteger = 42;void (^testBlock)(void) = ^{
NSLog(
@"Integer is: %i", anInteger);
};

anInteger
= 84;

testBlock();

上面代码块里面,打印出来的值,是42,而非84,因为它在代码块出现的那瞬间,就拿到了局部变量,之后就没有跟随大部队变化了。

那这种方式有无变通的方法,让它可以根据变量的变化而自动变化呢?当然有了,需要特殊处理即可,答案就是使用__block进行标识,它就可以跟随大部队的步伐了。

如果上面的代码块里面变量的定义使用了这个关键字,那么值就似乎84了,如下代码块所示。

 __block int anInteger = 42;void (^testBlock)(void) = ^{
NSLog(
@"Integer is: %i", anInteger);
};

anInteger
= 84;

testBlock();

这个__block功能很强大,告诉编译器,它可以获取变量的最新值,也可以在代码块里面对值进行修改(危险吗?反正你知道就好)

前面说道了,Objective-C里面的代码块类似c#里面
lambda
的Action 和 Func 那么举个例子来大致介绍下把。

对比一下下面两组代码,其一是Objective-C的代码块

typedef void (^MethodBlock)(int);- (void) fooWithBlock:(MethodBlock)block
{
int a = 5;
block(a);
}
- (void) regularFoo
{
[self fooWithBlock:
^(intval)
{
NSLog(
@"%d", val);
}];
}

接着是C#里面的代码例子,感觉它们很接近吧。在这里,你可能会感叹,编程语言这个世界里,很小,世界都趋向于大同了。

void Foo(Action<int>m)
{
int a = 5;
m(a);
}
voidRegularFoo()
{
Foo(val
=> //Or: Foo(delegate(int val) {
Console.WriteLine(val);
});
}

不过代码块的使用,你会慢慢感觉它虽然很强大,但是很多地方也不是很容易理解,毕竟对于我们这些入门没有很深根基的人来说,要慢慢消化。

再来看看下面这个例子代码,这个方法里面的代码块定义,很有意思。

- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
...
callbackBlock();
}

再来看看下面这个代码块,你可能会更晕,没事,晕了就对了,说明你是一个正常的人。

void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
...
return ^{
...
};
};

最后记得,如果是一个方法有多个参数,记得把代码块的参数放到最后来定义。

- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback;

至于代码块如何简化同步调用的问题,让给读者自己去了解研究了,我感觉也有点头晕了。哈哈。

2、Object C的错误及异常处理

我们知道,在开发各种应用程序或者系统的时候,错误肯定难以避免,有效处理错误异常就是你一个很有必要的内容。在C#里面,我们如果需要抛出异常,我们使用throw方法进行,所有的错误都以异常对象Exception作为基类进行扩展,包括各种各样的异常对象,而对错误异常的捕捉是通过try {} catch(Exception ex) finally {}这样的代码或者类似处理进行的,对于Objective-C来说,它又是如何处理错误异常的呢?

其实Objective-C对错误处理的机制也差不多,
它对异常的支持包括四个编译器指令:
@try
,
@catch
,
@throw
以及
@finally。是不是又一次感觉到语言的大同了,这个东西和C#的处理几乎没有什么差别。

另外Objective-C还引入了一个NSError的东西,这个东西和NSException有什么关系呢?这个东西有点类似于我们在C#开发的时候,增加一个out的输入参数,用来把程序内部的错误信息传递出去,然后交给调用者,让它们爱怎么用就怎么用,反正我处理完成了,有无错误我都告诉你了。由于NSError可以传递的信息比较丰富,一般来说这样对程序的处理也很方便。

如网络连接的异常,你可以通过下面的代码把它传递出来。

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

下面我们来看看一个写文件的错误如何处理的,首先定义一个函数,包含了NSError的参数的,注意一般这个参数是放到最后的,这点好像和我们有些这样处理的C#约定也是一样的。

- (BOOL)writeToURL:(NSURL *)aURL
options:(NSDataWritingOptions)mask
error:(NSError
**)errorPtr;

那我们调用这个writeToURL的函数的时候,有错误发生就应该处理,错误发生的时候,它执行完毕了,并且返回一个NO的值

    NSError *anyError;
BOOL success
=[receivedData writeToURL:someLocalFileURL
options:
0error:&anyError];if (!success) {
NSLog(
@"Write failed with error: %@", anyError);//present error to user }

为了表示错误的了来源,NSError有一个domain的属性,约定一般以公司的名称(或特别的名称)来进行区分。

com.iqidi.appOrFrameworkName.ErrorDomain

如构造一个NSError的代码大概如下所示。

NSString *domain = @"com.iqidi.MyApplication.ErrorDomain";
NSString
*desc = NSLocalizedString(@"Unable to…", @"");
NSDictionary
*userInfo =@{ NSLocalizedDescriptionKey : desc };

NSError
*error =[NSError errorWithDomain:domain
code:
-101userInfo:userInfo];

而常规的异常,我们一般还是通过NSException进行处理,异常就是发生问题的时候,停下来第一时间请示如何处理,如果有处理的线路就按照处理的线路进行,否则就一级级往上推了。

它的处理和C#差不多,我们都很熟悉了,代码结构如下所示。

@try{//code that throws an exception
...
}
@catch (CustomException *ce) { //most specific type//handle exception ce ...
}
@catch (NSException *ne) { //less specific type//do whatever recovery is necessary at his level ...//rethrow the exception so it's handled at a higher level @throw;
}
@catch (id ue) { //least specific type//code that handles this exception ...
}
@finally{//perform tasks necessary whether exception occurred or not ...
}

异常的构造和抛出代码和C#的也很类似

NSException* myException =[NSException
exceptionWithName:
@"FileNotFoundException"reason:@"File Not Found on System"userInfo:nil];@throw myException;

如果在处理异常的时候,需要处理一些对象的内存释放,那么一般是把它放到@finally包含的代码块里面。

这个和C#类似,虽然C#不会需要处理内存的释放问题,但是对于一些耗时的操作对象,如Connection,一般最好也放到finally里面确保关闭,处理类似。

- (void)doSomething {
NSMutableArray
*anArray =nil;
array
= [[NSMutableArray alloc] initWithCapacity:0];@try{
[self doSomethingElse:anArray];
}
@finally{
[anArray release];
}
}

如果要抛出原汁原味的异常,这点也和C#相似,通过@throw;方法即可。

@try{
NSException
*e =[NSException
exceptionWithName:
@"FileNotFoundException"reason:@"File Not Found on System"userInfo:nil];@throwe;
}
@catch(NSException *e) {@throw; //rethrows e implicitly }

看到这里,发现大多数的处理机制和语法使用,和C#并无太多的不同,我们了解就好,具体碰到什么问题,在查看下帮助文档,水来土掩,洗洗睡去吧。

微信应用如火如荼,很多公司都希望搭上信息快车,这个是一个商机,也是一个技术的方向,因此,有空研究下、学习下微信的相关开发,也就成为日常计划的重要事情之一了。本系列文章希望从一个循序渐进的角度上,全面介绍微信的相关开发过程和相关经验总结,希望给大家了解一下相关的开发历程。本随笔主要针对微信开发过程的前期准备和一些初始的工作的介绍。

在写下本文的之前一周时间里,我主要就是参考一些介绍文章以及微信公众平台的相关接口说明,并结合C#的代码开发,整理了自己公司的门户界面,实现了微信工作号的初步用户交互和信息展示工作,随着工作的进一步开展,越来越多的功能可能加入,并希望从应用角度上扩展微信的接口,从而实现我对微信接口的技术探秘和了解过程。

1、微信账号

要开发使用微信的平台API,就需要到微信的公众平台(https://mp.weixin.qq.com/)去注册,拥有一个
服务号
或者
订阅号
,服务号主要面对企业和组织,订阅号主要面向组织和个人,他们之间有一定的差异,根据不同的需要自己申请对应的账号即可。

为了使用一些高级的接口,你可能需要拥有服务号和高级的认证。账号注册过程,需要下载一个申请表格,打印并盖公章,另外还需要申请人拿着身份证拍照(有点怪异,呵呵),然后上传到服务器进行审核,一般很快就能获取批复。

我以公司名义申请了服务号,账号注册后,会在主界面上显示你的相关信息,另外给你申请一个二维码的东西,扫描二维码即可进入公司的微信关注确认对话框,非常方便。如下就是我申请后的公司账号二维码,可以直接使用扫描。

2、微信菜单定义

微信有两种方式的菜单定义,一种是编辑模式,一种是开发模式,两者互斥,也就是说,一旦我们采用了开发模式,就不能使用编辑模式了,反过来也一样。编辑下的菜单,其实也是可以管理的,但是微信不支持,觉得很不爽。

一般情况下,如果我们刚刚申请了微信号码,可以使用编辑菜单测试一下,根据说明编辑一些菜单试试。虽然微信说24小时内更新,不过一般很快,最快可能一两分钟就更新了,感觉还是不错的。

使用开发者模式,你需要根据微信的要求,在服务器上放置一个页面链接,使用C#开发的,可以采用***.ashx的命名方式,使用Asp.NET的一般处理程序即可,不需要使用普通的页面。

使用开发模式的菜单,也就是可以调用微信API进行菜单创建的工作,对于调用微信的API(微信有很多API可以调用),我们需要知道,有几个参数的重要性,所以在开发模式打开的时候,会给你列出这些参数,如下所示。

3、接入微信的链接处理

上面说了,你申请开发模式对菜单或者对其他API的调用,你需要顺利通过接入微信的测试,也就是确认你填写的链接存在并能顺利经过微信的回调测试。微信提供了一个PHP的页面处理例子,如果我们是C#开发的呢,可以搜一下就会得到答案,我的处理方式如下所示。

创建一个一般处理程序,然后在其处理页面里面增加一个处理逻辑,如果是非POST方式的内容,就是表示微信进行的Get测试,你需要增加一些处理逻辑,把它给你的内容传回去即可,如果是POST方式的,就是微信服务器对接口消息的请求操作了,后面介绍。

    /// <summary>
    ///微信接口。统一接收并处理信息的入口。/// </summary>
    public classwxapi : IHttpHandler
{
public voidProcessRequest(HttpContext context)
{
string postString = string.Empty;if (HttpContext.Current.Request.HttpMethod.ToUpper() == "POST")
{
using (Stream stream =HttpContext.Current.Request.InputStream)
{
Byte[] postBytes
= newByte[stream.Length];
stream.Read(postBytes,
0, (Int32)stream.Length);
postString
=Encoding.UTF8.GetString(postBytes);
}
if (!string.IsNullOrEmpty(postString))
{
Execute(postString);
}
}
else{
Auth();
//微信接入的测试 }
}

一般来说,Auth函数里面,就是要对相关的参数进行获取,然后进行处理返回给微信服务器。

string token = "****";//你申请的时候填写的Token

            string echoString = HttpContext.Current.Request.QueryString["echoStr"];string signature = HttpContext.Current.Request.QueryString["signature"];string timestamp = HttpContext.Current.Request.QueryString["timestamp"];string nonce = HttpContext.Current.Request.QueryString["nonce"];

完整的Author函数代码如下所示,其中我把业务逻辑进行进一步抽取到了一个新的类里面,方便业务逻辑的管理。

        /// <summary>
        ///成为开发者的第一步,验证并相应服务器的数据/// </summary>
        private voidAuth()
{
string token = ConfigurationManager.AppSettings["WeixinToken"];//从配置文件获取Token if (string.IsNullOrEmpty(token))
{
LogTextHelper.Error(
string.Format("WeixinToken 配置项没有配置!"));
}
string echoString = HttpContext.Current.Request.QueryString["echoStr"];string signature = HttpContext.Current.Request.QueryString["signature"];string timestamp = HttpContext.Current.Request.QueryString["timestamp"];string nonce = HttpContext.Current.Request.QueryString["nonce"];if (newBasicApi().CheckSignature(token, signature, timestamp, nonce))
{
if (!string.IsNullOrEmpty(echoString))
{
HttpContext.Current.Response.Write(echoString);
HttpContext.Current.Response.End();
}
}
}

而对微信参数的签名并返回的操作CheckSignature,代码如下所示。

        /// <summary>
        ///验证微信签名/// </summary>
        public bool CheckSignature(string token, string signature, string timestamp, stringnonce)
{
string[] ArrTmp ={ token, timestamp, nonce };

Array.Sort(ArrTmp);
string tmpStr = string.Join("", ArrTmp);

tmpStr
= FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1");
tmpStr
=tmpStr.ToLower();if (tmpStr ==signature)
{
return true;
}
else{return false;
}
}

4、使用开发方式创建菜单

一旦你顺利通过微信的认证,那么它就让你以开发方式调用它的API,并且可以随意创建你的菜单了。

创建菜单的方式,你可以通过下面的位置进入到他的API处理界面里面。

进入后,你会发现微信把很多消息的处理,分门别类放到不同的分类里面了。

其实我们现在初步要做的就是如何看看,使用代码方式调用创建菜单,进入菜单的API调试界面里面。

你会发现里面还需要输入一个Access_Token的东西,这个是一个会话身份认证,因此你还需要到接口里面去找这个如何创建的。下面图中的两个红色部分,就是我们开始的时候,微信提示我们“开发者凭据”的两个关键参数。

弄完这些,你就可以根据获得的Access_Token进行菜单的创建工作了,根据菜单的定义,它分为几类,可以分为URL方式(View),事件方式(Click)。

click
:用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event 的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
view
:用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的url值 (即网页链接),达到打开网页的目的,建议与网页授权获取用户基本信息接口结合,获得用户的登入个人信息。

4、我创建的菜单案例

在随笔的开始,我公布了一个二维码,一旦使用微信扫一扫,进行关注服务号后,那么就可以看到我自己创建的菜单了。主菜单一般最多三列,每个主菜单还可以有子菜单,他们的文字都有所限制的。

我们来看看我公司的微信门户菜单,看起来是不是很酷呢。

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口

微信应用如火如荼,很多公司都希望搭上信息快车,这个是一个商机,也是一个技术的方向,因此,有空研究下、学习下微信的相关开发,也就成为计划的安排事情之一了。本系列文章希望从一个循序渐进的角度上,全面介绍微信的相关开发过程和相关经验总结,希望给大家了解一下相关的开发历程。本篇随笔主要基于上一篇《
C#开发微信门户及应用(1)--开始使用微信接口
》的基础上进行深入的介绍,介绍微信消息的处理和应答的过程。

1、微信的消息应答交互

我们知道,微信的服务器架起了客户手机和开发者服务器的一个桥梁,通过消息的传递和响应,实现了与用户的交互操作,下面是它的消息流程图。

微信向开发者服务器请求的消息包含了多种类型,不过基本来说,分为了文本消息处理、事件消息处理、语音消息的识别,以及成为开发者之前的那个消息认证操作基本分类,下面是我绘制的一个消息分类图,其中介绍了这几种关系,以及各自的消息细化分类。

对于这些消息的请求,我们在开发服务器端,需要编写相关的逻辑进行对应给的处理,然后给微信服务器平台回应消息即可。

在前一篇的随笔里面我贴过代码,介绍微信消息处理的入口操作,代码如下所示。

        public voidProcessRequest(HttpContext context)
{
//WHC.Framework.Commons.LogTextHelper.Info("测试记录"); string postString = string.Empty;if (HttpContext.Current.Request.HttpMethod.ToUpper() == "POST")
{
using (Stream stream =HttpContext.Current.Request.InputStream)
{
Byte[] postBytes
= newByte[stream.Length];
stream.Read(postBytes,
0, (Int32)stream.Length);
postString
=Encoding.UTF8.GetString(postBytes);
}
if (!string.IsNullOrEmpty(postString))
{
Execute(postString);
}
}
else{
Auth();
}
}

其中的Execute(postString);就是对消息的处理函数,它实现了对不同消息的分发处理过程。‘

        /// <summary>
        ///处理各种请求信息并应答(通过POST的请求)/// </summary>
        /// <param name="postStr">POST方式提交的数据</param>
        private void Execute(stringpostStr)
{
WeixinApiDispatch dispatch
= new WeixinApiDispatch();
string responseContent =
dispatch.Execute(postStr);

HttpContext.Current.Response.ContentEncoding
=Encoding.UTF8;
HttpContext.Current.Response.Write(responseContent);
}

里面的
WeixinApiDispatch
就是一个分发的管理类,它提取请求消息的内容,并构建不同类型的消息参数,传递给不同的响应函数进行处理,然后返回封装好的XML内容,作为响应。

具体的代码处理逻辑如下图所示。

这个消息处理接口,其实就是定义好一系列的对请求消息的处理操作,参数是不同给的消息对象,具体的代码定义如下所示(由于篇幅原因,省略部分接口,具体可以参考上图)。

    /// <summary>
    ///客户端请求的数据接口/// </summary>
    public interfaceIWeixinAction
{
/// <summary> ///对文本请求信息进行处理/// </summary> /// <param name="info">文本信息实体</param> /// <returns></returns> stringHandleText(RequestText info);/// <summary> ///对图片请求信息进行处理/// </summary> /// <param name="info">图片信息实体</param> /// <returns></returns> stringHandleImage(RequestImage info);

...........................
/// <summary> ///对订阅请求事件进行处理/// </summary> /// <param name="info">订阅请求事件信息实体</param> /// <returns></returns> stringHandleEventSubscribe(RequestEventSubscribe info);/// <summary> ///对菜单单击请求事件进行处理/// </summary> /// <param name="info">菜单单击请求事件信息实体</param> /// <returns></returns> stringHandleEventClick(RequestEventClick info);

..............................
}

从上面的代码可以看出,不同的消息,到处理函数这里,就以不同的消息实体类的方式传递过来了(
注意:实体类是我根据程序开发需要自己定义的,非微信本身的实体类
),这样非常方便我们处理操作,否则每次需要解析不同的消息内容,很容易出现问题,这样强类型的数据类型,提高了我们开发微信应用的强壮型和高效性。这些实体类的对象有一定的继承关系的,他们的继承关系如下所示。

2、微信的管理接口

上面的消息分类是微信服务器向开发者服务器发送的消息请求操作,还有一种消息,是我们开发者服务器向微信服务器进行的消息请求或者响应,这种这里暂且称之为微信的管理接口,它表明了我们可以通过这些接口进行相关的消息回复或者数据管理操作。它的分类图如下所示。

微信的回复消息处理,它也和上面小节的信息一样,它也是继承自BaseMessage实体类的(
同样,下图的实体类及其继承关系也是自定义的,方便程序开发
),它的关系如下所示

回复的消息,一般用的最多的是文本消息和图文消息。

文本消息的效果如下所示。

图文消息,可以增加图片,还可以增加详细的链接页面,是非常好看的一种效果,对于一些内容比较多,希望展现更好效果的,一般采用这种,效果如下所示。

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口

1、背景

为了方便获取和发送邮件,需要有一个定时服务来对人员列表的邮件内容进行收取,以及发送存储在数据库列表里面的邮件信息,并提供相应的界面管理功能,为企业员工提供相应的邮件服务。

本软件具有下面几个特点:

1)通过Windows服务驻留在服务器系统中,提供更及时、更高效的响应服务。

2)通过SMTP协议发送邮件和POP3协议获取邮件,支持大多数邮箱的邮件收发操作。

3)邮件收取和发送采用多线程进行处理,更好利用服务器资源。

4)邮件的附件通过接口获取并存储到服务器上,方便查看和管理。

5)提供对邮件内容、邮件配置和发送操作等方面,进行可视化的管理操作。

6)用户可以对自己的邮件进行删除,系统在服务进程进行后台处理,并执行删除操作。

本软件基于微软.NET 4.0框架技术,以C# 4.0为开发语言进行系统开发,数据库存储采用SqlServer/Sqlite/Oracle等关系型数据库进行存储。

2、软件配置安装说明

2.1数据库恢复操作

默认程序包里面有一个EmailPorter.bak文件,这个是SqlServer2005的备份文件,使用前需要把该数据库进行恢复才能使用本程序。

恢复数据库操作步骤,可以遵循下面几个步骤进行。

1)在数据库节点上右键执行还原数据库操作。

2)数据库还原操作。输入还原的数据库名称,在源设备中选择相应的备份文件,并勾选备件文件列表,然后执行还原数据库操作。

3)查看还原数据库表。数据库还原提示成功后,可以在新建的数据库里面,看到下面一些数据库表信息。

2.2 软件参数配置

软件目录下有两个.config文件,这两个文件是安装运行服务需要的配置信息,使用前,需要进行一定的配置,两个文件内容完全一致,需要统一修改。

其中的配置文件如下所示,注意其中红色标注的部分,修改相应的数据库访问地址(
connectionString
),以及发送的邮件附件基础路径(
SendAttachPath
)的配置值。

2.3 软件服务安装管理

该邮件代收代发系统,通过寄宿在Windows服务上,可以更好利用Windows的资源,软件提供一个基于Winform的程序主界面进行服务的安装、卸载、系统配置信息管理等相关的管理工作。运行定时服务控制台程序
WinServiceTool.exe
,程序图标如下所示。

定时服务控制台界面如下所示:

单击【安装服务】功能按钮,程序弹出DOS窗口进行服务安装。

安装后,系统的Windows服务列表中就会增加一个【定时服务】的服务模块了,这样就证明我们顺利安装了通用定时服务了。

服务程序安装后,定时服务控制台的界面状态有所变化,更新了定时服务的现有状态,如下所示。

2.4 性能优化配置

系统通过多线程技术,充分利用服务器资源,来提升服务器执行效率和缩短处理时间。

由于系统在发送和收取过程中,都需要占用系统相关的资源,因此将发送和收取两个模块分开,每个模块都以多线程方式进行数据处理。

为避免对发送列表频繁的轮训操作,设定最小的一个时间间隔数值,轮训频率不能小于这个时间间隔(最小为5秒)。

定时服务程序通过插件化管理各个模块,包括邮件发送、邮件接收、邮件删除操作,这几个服务插件的相关配置参考
PlugInSetting.xml
进行调整即可。

默认发送服务轮训为5秒间隔,邮件接收服务轮训为30秒间隔,邮件删除服务轮训为60秒间隔,可根据需要进行调整。

PlugInSetting.xml
文件如下所示。

2.5 关于系统操作日志

与服务有关的一些操作记录,建议写回日志文件中,包括邮件的收取与发送日志,服务程序异常日志,以便于日后维护。

日志建议按yyyymmdd.txt 命名方式。

3、系统界面操作

为了实现以上的邮件收发和删除的服务操作,我们需要涉及下面几个业务场景,内容如下:
1) 邮件账号的配置
2)待发送的邮件存储,发送后邮件存储到发送历史里面。
3)指定接收账号的配置信息
4)接收的邮件存储
5)发送和接收的邮件的附件存储

由于邮件代收代发模块的【定时发送服务】,一般情况下是运行在服务器上,因此也要求软件的相关信息也存储在服务器上。基于这个原因,软件管理的数据应该存储在服务器上,Winform程序可以通过WCF的分布式数据访问获取服务器的数据(可以利用我的《WCF开发框架》、《混合式Winform开发框》架构造应用)。
本文的软件操作,是基于《混合式Winform开发框架》的界面操作方式,对邮件的账号进行配置,创建邮件内容,以及创建邮件发送列表等操作。
下面分别对这几个模块的界面进行一个大致的介绍。

3.1 邮件账号的配置

要发送和接收用户的邮件内容,首先需要配置相关的用户账号,用户账号的配置信息,以列表方式进行展示,如果需要添加新的账号,在界面上单击【新建】即可创建新的账号,如下界面所示。

在软件界面上填入相关的账号信息,然后单击【测试登陆】,系统会根据SMTP接口对账号进行测试,在使用账号的时候,只有【测试登陆】通过的账号,才能正常获取邮件和发送邮件。

如果对配置的邮箱不太清楚,系统提供了一个账号信息的对照表,方便查阅,在新建账号界面的下方有一个【常见配置信息】的功能,单击后弹出一个界面,介绍相关的邮箱账号配置信息,如下所示。

3.2 待发送的邮件存储

待发送的邮件存储在一个表里面,后台【定时服务】会根据这个表的数据进行循环发送,随着【定时服务】对列表的邮件进行不断的处理,待发送的邮件列表可能越来越少,最后可能剩下空白的列表记录。邮件发送列表界面如下所示。

待发送的邮件,一般来说每封邮件内容可以不同,也可以设计为批量发送,邮件引用不同的内容即可,看具体的需要。

如果用于营销推广方面,邮件的内容可能很多相同,如果每次发送都存储一样的邮件正文,则需要很大的存储空间,我们可以设计为只存储邮件明细的ID,把邮件标题和内容抽离进行独立维护。

单击【新建】功能,弹出一个界面用来新建一个发送的邮件。

上图红色区域为邮件的内容编写区域,里面可以通过图文的方式创建一个丰富多彩的邮件内容,邮件的图片采用网络地址方式进行插入,这样使邮件的接收人能够顺利打开相关的图片进行阅读。

准备好邮件内容后,我们接着在【邮件发送列表】界面中,通过【新建】操作,创建一个待发送的邮件,弹出下面的操作界面。

开始的时候,软件标题,软件接收者名单,都是空白的,需要我们根据需要填入相关的信息,这里的邮件正文,是通过按钮

进行引用刚才创建的邮件正文,以达到重用的目的。软件弹出选择邮件的界面如下所示,选择合适的邮件,然后在下方【选择】确认即可返回。

返回的界面上输入相关的接受者名单,如下所示。

发送方式有两种,一种是采用默认账号的方式发送,且邮件的多个接收者会出现在邮件的发送人中;一种是使用配置的所有账号轮流发送,这种方式,充分利用邮件账号的资源,把接收者分拆成多封邮件进行发送。

最后邮件待发送列表里面出现两封邮件(轮流发送方式,对邮件接收者进行拆分发送)。

如果【定时服务】在后台运行,那么很快就可以看到邮件的发送操作了,同时我本地的QQ已经有收到邮件的提示窗口了。

打开QQ邮箱,我们可以看到刚才发送的邮件内容。

打开接收者的163邮箱,可以看到另外一封邮件也顺利收到了,界面如下所示。

这个时候刷新待发送的邮件列表,我们可以看到,邮件列表已经清空了。另外发送成功的邮件已经移到历史列表里面去了。

3.3 指定接收账号的配置信息

如果用户已经配置了接收的账号,那么邮件会通过【定时服务】进行定时的获取,并把数据存储在邮件接收表里面。

开始的时候,这个列表是空的,界面左边是列出相关的邮件账号。如果我们需要进行邮件的收取,我们需要通过【配置收取账号】的界面进行处理,单击【配置收取账号】按钮,弹出以下界面,选择需要收取邮件,并确定保存即可。

3.4 接收的邮件存储

上面小节介绍了邮件收取账号的配置,当配置好账号后,返回主界面,等待一会功夫,刷新收取的邮件列表,即可看到已经很多邮件被收取下来存储到服务器上来,我们通过界面查看即可。

收取到的邮件,通过双击列表可以打开进行查看,我们打开其中一封邮件,看到的界面如下所示。

3.5 邮件的附件存储

邮件的附件是存放在服务器上的,如果需要获取,需要程序通过WCF服务的方式对附件进行远程获取查看。

对于一些邮件带有附件的,软件提供了相关的界面进行查看,操作界面如下所示。

软件如果是图片,可以双击进行查看,如果是其他文件,下载到本地进行打开。

微信应用如火如荼,很多公司都希望搭上信息快车,这个是一个商机,也是一个技术的方向,因此,有空研究下、学习下微信的相关开发,也就成为计划的安排事情之一了。本系列文章希望从一个循序渐进的角度上,全面介绍微信的相关开发过程和相关经验总结,希望给大家了解一下相关的开发历程。

在前面两篇两篇随笔《
C#开发微信门户及应用(1)--开始使用微信接口
》和《
C#开发微信门户及应用(2)--微信消息的处理和应答
》里面,大致介绍了我微信应用的框架构建,本随笔继续介绍这一主题,介绍消息应答里面的文本应答和图文应答的过程。

我们知道,给手机用户发送响应消息,它可以分为好多种方式,如回复文本消息、回复图片消息、回复语音消息、回复视频消息、回复音乐消息、回复图文消息等,如下所示。

而其中图片、视频、语音这三种方式,是需要开通微信认证才可以向用户发送存在微信服务器上的媒体信息,一般没有认证的公众号或者服务号,是不能发送这几种内容的。

1、实体信息关系及定义

在上一篇微信开发的随笔中,我展示了对接收消息和回复消息的应用实体类,这些实体类是我根据需要,根据开发需要,在应用层面对它们进行了封装,如回复的消息关系如下所示。

消息基类BaseMessage的实体类定义如下所示,它对日期构造了一个整形数值,并具备了一些常规的属性,并且还有一个重要的ToXML方法,用来给方法传递这些XML数据的。

    /// <summary>
    ///基础消息内容/// </summary>
    [XmlRoot(ElementName = "xml")]public classBaseMessage
{
/// <summary> ///初始化一些内容,如创建时间为整形,/// </summary> publicBaseMessage()
{
this.CreateTime =DateTime.Now.DateTimeToInt();
}
/// <summary> ///开发者微信号/// </summary> public string ToUserName { get; set; }/// <summary> ///发送方帐号(一个OpenID)/// </summary> public string FromUserName { get; set; }/// <summary> ///消息创建时间 (整型)/// </summary> public int CreateTime { get; set; }/// <summary> ///消息类型/// </summary> public string MsgType { get; set; }public virtual stringToXml()
{
this.CreateTime = DateTime.Now.DateTimeToInt();//重新更新 return MyXmlHelper.ObjectToXml(this);
}

}

回复的文本消息实体类代码如下所示,我们可以看到,它继承了很多通用的实体属性,并且还具备了一个ToXml的通用方法,我们需要把它转换为响应的XML的时候,就使用这个方法就可以了。

    /// <summary>
    ///回复文本消息/// </summary>
    [System.Xml.Serialization.XmlRoot(ElementName = "xml")]public classResponseText : BaseMessage
{
publicResponseText()
{
this.MsgType =ResponseMsgType.Text.ToString().ToLower();
}
public ResponseText(BaseMessage info) : this()
{
this.FromUserName =info.ToUserName;this.ToUserName =info.FromUserName;
}
/// <summary> ///内容/// </summary> public string Content { get; set; }
}

而图文消息对象类ResponseNews,它包含更多的信息定义

    /// <summary>
    ///回复图文消息/// </summary>
    [System.Xml.Serialization.XmlRoot(ElementName = "xml")]public classResponseNews : BaseMessage
{
publicResponseNews()
{
this.MsgType =ResponseMsgType.News.ToString().ToLower();this.Articles = new List<ArticleEntity>();
}
public ResponseNews(BaseMessage info) : this()
{
this.FromUserName =info.ToUserName;this.ToUserName =info.FromUserName;
}
/// <summary> ///图文消息个数,限制为10条以内/// </summary> public intArticleCount
{
get{return this.Articles.Count;
}
set{
;
//增加这个步骤才出来XML内容 }
}
/// <summary> ///图文列表。///多条图文消息信息,默认第一个item为大图,注意,如果图文数超过10,则将会无响应/// </summary> [System.Xml.Serialization.XmlArrayItem("item")]public List<ArticleEntity> Articles { get; set; }

}

而其中的图文列表集合中的对象,它也是一个实体类型,包含了一些图文的链接,标题等信息,不在赘述。

2、消息的回复处理

如对于文本消息,我们可以用以下的方式进行处理。

                ResponseText response = newResponseText(info);
response.Content
= "抱歉,此功能暂未开通。";
result
= response.ToXml();

对于图文消息,我们可能需要录入更多的消息才能返回更好的效果。

注意图文的消息,图片的尺寸最好按照官方的标准,否则在手机上看起来不好看,官方的标准好像是宽高是(360,200)像素

        /// <summary>
        ///订阅或者显示公司信息/// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private stringShowCompanyInfo(BaseMessage info)
{
string result = "";//使用在微信平台上的图文信息(单图文信息) ResponseNews response = newResponseNews(info);
ArticleEntity entity
= newArticleEntity();
entity.Title
= "广州爱奇迪软件科技有限公司";
entity.Description
= "欢迎关注广州爱奇迪软件--专业的单位信息化软件和软件开发框架提供商,我们立志于为客户提供最好的软件及服务。\r\n";
entity.Description
+= "我们是一家极富创新性的软件科技公司,从事研究、开发并销售最可靠的、安全易用的技术产品及优质专业的服务,帮助全球客户和合作伙伴取得成功。\r\n......(此处省略1000字,哈哈)";entity.PicUrl= "http://www.iqidi.com/WeixinImage/company.png";
entity.Url
= "http://www.iqidi.com";

response.Articles.Add(entity);
result
=response.ToXml();returnresult;
}

我们来看看我公司的微信门户菜单,看起来是不是很酷呢。

对于这两种(文本消息、图文消息)用的地方是最多,很多微信门户,都主要是使用这两种方式进行响应。当然,我们还可以根据客户手机提交上来的各种消息进行不同的处理,请求消息的类型我在上一篇的随笔有介绍,如下所示。

需要关注了解整体效果,可以使用微信直接扫描二维码即可。

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口