wenmo8 发布的文章

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

介绍了通讯录的部门的相关操作管理,通讯录管理包括部门管理、成员管理、标签管理三个部分,本篇主要介绍成员的管理操作,包括创建、删除、更新、获取、获取部门成员几个操作要点。

1、成员的创建操作

为了方便,我们可以创建一个部门组织结构,这是开发的前提,因为我们通讯录管理,也是基于一个组织机构下的,如上篇介绍的组织结构层次一样。我这里
创建一个广州爱奇迪的根结构,然后在其中在创建一些组织机构,如下图所示。

在后台可以通过功能操作添加人员,本篇主要介绍如何调用微信企业号API进行人员管理的操作。

创建人员的API定义如下所示。

  • 请求说明

Https请求方式: POST

https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token=ACCESS_TOKEN

请求包结构体为:

{
   "userid": "zhangsan",
   "name": "张三",
   "department": [1, 2],
   "position": "产品经理",
   "mobile": "15913215421",
   "gender": 1,
   "tel": "62394",
   "email": "zhangsan@gzdev.com",
   "weixinid": "zhangsan4dev"
}
  • 参数说明
参数 必须 说明
access_token 调用接口凭证
userid 员工UserID。对应管理端的帐号,企业内必须唯一。长度为1~64个字符
name 成员名称。长度为1~64个字符
department 成员所属部门id列表。注意,每个部门的直属员工上限为1000个
position 职位信息。长度为0~64个字符
mobile 手机号码。企业内必须唯一,mobile/weixinid/email三者不能同时为空
gender 性别。gender=0表示男,=1表示女。默认gender=0
tel 办公电话。长度为0~64个字符
email 邮箱。长度为0~64个字符。企业内必须唯一
weixinid 微信号。企业内必须唯一
  • 权限说明

管理员须拥有“操作通讯录”的接口权限,以及指定部门的管理权限。

  • 返回结果
{
   "errcode": 0,
   "errmsg": "created"
}

我们在C#里面,需要定义对应给的接口,然后根据需要构造对应的传递实体信息。

这里我把人员管理的接口全部定义好,接口定义如下所示。

        #region 部门成员管理
        /// <summary>
        ///创建成员/// </summary>
        CommonResult CreateUser(stringaccessToken, CorpUserJson user);/// <summary>
        ///更新成员/// </summary>
        CommonResult UpdateUser(stringaccessToken, CorpUserUpdateJson user);/// <summary>
        ///删除成员/// </summary>
        CommonResult DeleteUser(string accessToken, stringuserid);/// <summary>
        ///根据成员id获取成员信息/// </summary>
        CorpUserGetJson GetUser(string accessToken, stringuserid);/// <summary>
        ///获取部门成员/// </summary>
        CorpUserListJson GetDeptUser(string accessToken, int department_id, int fetch_child = 0, int status = 0);#endregion

然后根据信息定义,创建一个承载人员信息的CorpUserJson实体对象,创建人员的实现操作代码如下所示。

        /// <summary>
        ///创建成员/// </summary>
        public CommonResult CreateUser(stringaccessToken, CorpUserJson user)
{
string urlFormat = "https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token={0}";var data = new{
userid
=user.userid,
name
=user.name,
department
=user.department,
position
=user.position,
mobile
=user.mobile,
gender
=user.gender,
tel
=user.tel,
email
=user.email,
weixinid
=user.weixinid
};
var url = string.Format(urlFormat, accessToken);var postData =data.ToJson();returnHelper.GetCorpExecuteResult(url, postData);
}

2、成员的更新操作

成员的数据更新和创建操作类似,它的企业号定义如下所示。

  • 请求说明

Https请求方式: POST

https://qyapi.weixin.qq.com/cgi-bin/user/update?access_token=ACCESS_TOKEN

请求包示例如下(如果非必须的字段未指定,则不更新该字段之前的设置值):

{
   "userid": "zhangsan",
   "name": "李四",
   "department": [1],
   "position": "后台工程师",
   "mobile": "15913215421",
   "gender": 1,
   "tel": "62394",
   "email": "zhangsan@gzdev.com",
   "weixinid": "lisifordev",
   "enable": 1
}

由于它的操作数据类似,因此它的实现代码也差不多,如下所示就是。

        /// <summary>
        ///更新成员/// </summary>
        public CommonResult UpdateUser(stringaccessToken, CorpUserUpdateJson user)
{
string urlFormat = "https://qyapi.weixin.qq.com/cgi-bin/user/update?access_token={0}";//string postData = user.ToJson(); var data = new{
userid
=user.userid,
name
=user.name,
department
=user.department,
position
=user.position,
mobile
=user.mobile,
gender
=user.gender,
tel
=user.tel,
email
=user.email,
weixinid
=user.weixinid,
enable
=user.enable
};
var url = string.Format(urlFormat, accessToken);var postData =data.ToJson();returnHelper.GetCorpExecuteResult(url, postData);
}

3、成员的删除、
成员的获取、
部门成员的获取操作

这些操作和上面的类似,不在赘述,主要就是根据需要定义他们对应的返回数据信息,然后解析Json数据即可转换为对应的实体。

1)删除人员的定义如下:

  • 请求说明

Https请求方式: GET

https://qyapi.weixin.qq.com/cgi-bin/user/delete?access_token=ACCESS_TOKEN&userid=lisi

  • 参数说明
参数 必须 说明
access_token 调用接口凭证
userid 员工UserID。对应管理端的帐号
  • 返回结果
{
   "errcode": 0,
   "errmsg": "deleted"
}

2)成员的获取定义如下:

  • 请求说明

Https请求方式: GET

https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=lisi

  • 参数说明
参数 必须 说明
access_token 调用接口凭证
userid 员工UserID
  • 返回结果
{
   "errcode": 0,
   "errmsg": "ok",
   "userid": "zhangsan",
   "name": "李四",
   "department": [1, 2],
   "position": "后台工程师",
   "mobile": "15913215421",
   "gender": 1,
   "tel": "62394",
   "email": "zhangsan@gzdev.com",
   "weixinid": "lisifordev",  
   "avatar": "http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0",
   "status": 1
}

3)部门成员的获取定义如下:

  • 请求说明

Https请求方式: GET

https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=ACCESS_TOKEN&department_id=1&fetch_child=0&status=0

  • 参数说明
参数 必须 说明
access_token 调用接口凭证
department_id 获取的部门id
fetch_child 1/0:是否递归获取子部门下面的成员
status 0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加
  • 权限说明

管理员须拥有’获取部门成员’的接口权限,以及指定部门的查看权限。

  • 返回结果
{
   "errcode": 0,
   "errmsg": "ok",
   "userlist": [
           {
                  "userid": "zhangsan",
                  "name": "李四"
           }
     ]
}

这个返回值我们定义一个实体对象用来存储数据即可。

    /// <summary>
    ///获取部门成员返回的数据/// </summary>
    public classCorpUserListJson : BaseJsonResult
{
publicCorpUserListJson()
{
this.userlist = new List<CorpUserSimpleJson>();
}
/// <summary> ///返回的错误消息/// </summary> public CorpReturnCode errcode { get; set; }/// <summary> ///对返回码的文本描述内容/// </summary> public string errmsg { get; set; }/// <summary> ///成员列表/// </summary> public List<CorpUserSimpleJson> userlist { get; set; }
}

7、综合例子调用代码

上面介绍了一些企业号的接口定义和我对API的C#封装接口和部分实现代码,实现了功能后,我们就可以在代码中对它进行测试,确信是否正常使用。

        /// <summary>
        ///人员管理综合性操作(创建、修改、获取信息、删除)/// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCorpUser_Click(objectsender, EventArgs e)
{
CorpUserJson user
= newCorpUserJson();
user.userid
= "test";
user.name
="测试用户";
user.department
= new List<int>(){2};
user.email
= "test@163.com";

ICorpAddressBookApi bll
= newCorpAddressBookApi();
CommonResult result
=bll.CreateUser(token, user);if (result != null)
{
Console.WriteLine(
"创建成员:{0} {1} {2}", user.name, (result.Success ? "成功" : "失败"), result.ErrorMessage);string name = "修改测试";
user.name
=name;
CorpUserUpdateJson userUpdate
= newCorpUserUpdateJson(user);
result
=bll.UpdateUser(token, userUpdate);if (result != null)
{
Console.WriteLine(
"修改名称:{0} {1} {2}", name, (result.Success ? "成功" : "失败"), result.ErrorMessage);
}

CorpUserGetJson userGet
=bll.GetUser(token, user.userid);if (userGet != null)
{
Console.WriteLine(
"成员名称:{0} ({1} {2})", userGet.name, user.userid, user.email);
}

result
=bll.DeleteUser(token, user.userid);if (result != null)
{
Console.WriteLine(
"删除成员:{0} {1} {2}", name, (result.Success ? "成功" : "失败"), result.ErrorMessage);
}
}
}

获取部门人员的操作代码如下所示。

        /// <summary>
        ///获取部门人员/// </summary>
        private void btnCorpUserList_Click(objectsender, EventArgs e)
{
int deptId = 1;
ICorpAddressBookApi bll
= newCorpAddressBookApi();
CorpUserListJson result
=bll.GetDeptUser(token, deptId);if (result != null)
{
foreach(CorpUserSimpleJson item inresult.userlist)
{
Console.WriteLine(
"成员名称:{0} {1}", item.name, item.userid);
}
}
}

人员的管理,相对来说比较简单,主要是在一定的部门下创建人员,然后也可以给标签增加相应的人员,基本上就是这些了,不过一定需要确保有相应的权限进行操作。

如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章,系列随笔如下所示:

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)关注更安全

–只有企业通讯录的成员才能关注企业号,分级管理员、保密消息等各种特性确保企业内部信息的安全。

企业可以设置自行验证关注者身份,进行二次安全验证,保证企业信息使用和传递安全。

若员工离职,企业管理员可在通讯录中删除该成员,该成员即自动取消关注企业号,同时微信中的企业号历史记录也会被清除。

2)应用可配置

–企业可自行在企业号中可配置多个服务号,可以连接不同的企业应用系统,只有授权的企业成员才能使用相应的服务号。

3)消息无限制

–发送消息无限制,并提供完善的的管理接口及微信原生能力,以适应企业复杂、个性化的应用场景。

企业可以主动发消息给员工,
消息量不受限制

4)使用更便捷

–企业号在微信中有统一的消息入口,用户可以更方便地管理企业号消息。微信通讯录也可以直接访问企业号中的应用。

2、企业号的管理接口内容

目前企业号的内容可以用下面的分层图来展示,分别包含素材管理、被动响应消息、通讯录管理、自定义菜单等内容,详细可以看下面图示。

3、企业号消息和事件的处理

企业号和公众号一样,可以分为消息处理和事件处理,下面是他们两种类型的处理操作,也就发送的消息有文本消息、图片消息、文件消息、视频消息、语音消息、地理文字消息、图文和多媒体消息等。

事件处理主要就是关注、取消关注事件,以及菜单click类型和view类型两种操作,还有就是地理位置上报事件等。

两种类型的处理图如下所示。

4、企业号消息管理

在企业的管理后台,和公众号一样,可以看到对应信息交流记录,包括文字、图片、地理位置等等,如下所示。

由于消息分为几种类型,包括文本(Text)、图片(Image)、文件(File)、语音(Voice)、视频(Video)、图文消息等(News)、MpNews等。

因此我们需要分别对它们进行一定的定义和封装处理,如下是它们的信息对象设计图。

企业号发送消息的官方定义如下:

企业可以主动发消息给员工,
消息量不受限制

调用接口时,使用Https协议、JSON数据包格式,
数据包不需做加密处理

目前支持文本、图片、语音、视频、文件、图文等消息类型。除了news类型,其它类型的消息可在发送时加上保密选项,保密消息会被打上水印,并且只有接收者才能阅读。

我们以发送的文本消息为例进行说明,它的定义如下所示。

  • text消息
{
   "touser": "UserID1|UserID2|UserID3",
   "toparty": " PartyID1 | PartyID2 ",
   "totag": " TagID1 | TagID2 ",
   "msgtype": "text",
   "agentid": "1",
   "text": {
       "content": "Holiday Request For Pony(http://xxxxx)"
   },
   "safe":"0"
}

参数 必须 说明
touser UserID列表(消息接收者,多个接收者用‘|’分隔)。特殊情况:指定为@all,则向关注该企业应用的全部成员发送
toparty PartyID列表,多个接受者用‘|’分隔。当touser为@all时忽略本参数
totag TagID列表,多个接受者用‘|’分隔。当touser为@all时忽略本参数
msgtype 消息类型,此时固定为:text
agentid 企业应用的id,整型。可在应用的设置页面查看
content 消息内容
safe 表示是否是保密消息,0表示否,1表示是,默认0

其中每种消息都会包含以下消息所示,也就是它们共同的属性:

    touser": "UserID1|UserID2|UserID3","toparty": " PartyID1 | PartyID2 ","totag": " TagID1 | TagID2 ","msgtype": "text","agentid": "1",

因此我们可以定义一个基类用来方便承载这些共同的信息。

    /// <summary>
    ///企业号发送消息的基础消息内容/// </summary>
    public classCorpSendBase
{
/// <summary> ///UserID列表(消息接收者,多个接收者用‘|’分隔)。特殊情况:指定为@all,则向关注该企业应用的全部成员发送/// </summary> public string touser { get; set; }/// <summary> ///PartyID列表,多个接受者用‘|’分隔。当touser为@all时忽略本参数/// </summary> public string toparty { get; set; }/// <summary> ///TagID列表,多个接受者用‘|’分隔。当touser为@all时忽略本参数/// </summary> public string totag { get; set; }/// <summary> ///消息类型/// </summary> public string msgtype { get; set; }/// <summary> ///企业应用的id,整型。可在应用的设置页面查看/// </summary> public string agentid { get; set; }/// <summary> ///表示是否是保密消息,0表示否,1表示是,默认0/// </summary> [JsonProperty(NullValueHandling =NullValueHandling.Ignore)]public string safe { get; set; }

}

然后其他消息逐一继承这个基类即可,如下所示。

最终会构成下面这个继承关系图。

5、消息接口的定义和实现

定义好相关的发送对象后,我们就可以定义它的统一发送接口了,如下所示。

    /// <summary>
    ///企业号消息管理接口定义/// </summary>
    public interfaceICorpMessageApi
{
/// <summary> ///发送消息。///需要管理员对应用有使用权限,对收件人touser、toparty、totag有查看权限,否则本次调用失败。/// </summary> /// <param name="accessToken"></param> /// <returns></returns> CommonResult SendMessage(stringaccessToken, CorpSendBase data);
}

最终,文本等类型的消息会根据接口定义进行实现,实现代码如下所示。
注意,发送过程
不需要
调用加密类进行加密

    /// <summary>
    ///企业号消息管理实现类/// </summary>
    public classCorpMessageApi : ICorpMessageApi
{
/// <summary> ///发送消息。///需要管理员对应用有使用权限,对收件人touser、toparty、totag有查看权限,否则本次调用失败。/// </summary> /// <param name="accessToken"></param> /// <returns></returns> public CommonResult SendMessage(stringaccessToken, CorpSendBase data)
{
CommonResult result
= newCommonResult();string urlFormat = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={0}";var url = string.Format(urlFormat, accessToken);var postData =data.ToJson();//数据不用加密发送 CorpSendResult sendResult = CorpJsonHelper<CorpSendResult>.ConvertJson(url, postData);if (sendResult != null)
{
result.Success
= (sendResult.errcode ==CorpReturnCode.请求成功);
result.ErrorMessage
= string.Format("invaliduser:{0},invalidparty:{1},invalidtag:{2}",
sendResult.invaliduser, sendResult.invalidparty, sendResult.invalidtag);
}
returnresult;
}
}

6、消息的发送操作和实际效果

定义好相应的发送对象后,我们就可以进行统一的消息发送操作,包括文本、图片、文件、语音等等类型的消息,注意有些消息是需要上传到服务器上,然后在根据mediaId进行发送出去的。

发送文本和图片的操作代码如下所示。

        private void btnSendText_Click(objectsender, EventArgs e)
{
//发送文本内容 ICorpMessageApi bll = newCorpMessageApi();

CorpSendText text
= new CorpSendText("API 中文测试(http://www.iqidi.com)");
text.touser
= "wuhuacong";
text.toparty
= "4";//部门ID text.totag = "0";

text.safe
= "0";
text.agentid
= "0";

CommonResult result
=bll.SendMessage(token, text);if (result != null)
{
Console.WriteLine(
"发送消息:{0} {1} {2}", text.text.content, (result.Success ? "成功" : "失败"), result.ErrorMessage);
}
}
private void btnSendImage_Click(objectsender, EventArgs e)
{
btnUpload_Click(sender, e);
if (!string.IsNullOrEmpty(image_mediaId))
{
//发送图片内容 ICorpMessageApi bll = newCorpMessageApi();

CorpSendImage image
= newCorpSendImage(image_mediaId);
CommonResult result
=bll.SendMessage(token, image);if (result != null)
{
Console.WriteLine(
"发送图片消息:{0} {1} {2}", image_mediaId, (result.Success ? "成功" : "失败"), result.ErrorMessage);
}
}
}

最后在微信企业号上截图效果如下所示,包括了文本测试、文件测试、图文测试、语音测试均正常。


如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章,系列随笔如下所示:

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)--开始使用微信接口

前面几篇陆续介绍了很多微信企业号的相关操作,企业号和公众号一样都可以自定义菜单,因此他们也可以通过API进行菜单的创建、获取列表、删除的操作,因此本篇继续探讨这个主体,介绍企业号的菜单管理操作。

菜单在很多情况下,能够给我们提供一个快速入口,也可以用来获取用户信息的主要入口,通过OAuth2验证接口,以及自定义的重定向菜单,我们就可以获取对应的用户ID,然后进一步获取到用户的相关数据,可以显示给客户。

1、菜单的总体介绍

菜单的事件处理如下所示,包括了单击和跳转两个操作,未来企业号可能会增加一些和公众号一样的扫码操作,拍照操作等功能的,目前只有两个。

官方的菜单定义接口包含了下面三种操作,菜单创建、列表获取和菜单删除,这点和公众号操作几乎一样了。

2、菜单的实体类定义和接口定义处理

我们定义菜单,包括定义它的一些属性,包含有name, type, key,url,以及一个指向自身引用的子菜单引用,因此菜单就可以循环构造多个层次,虽然严格意义上来讲,企业号的菜单和公众号菜单一样,一级三个,二级最多五个,而且没有三级菜单了。

实体类的UML图示如下所示。

菜单管理的创建操作,官方定义如下所示。

  • 请求说明

Https请求方式: POST

https://qyapi.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN&agentid=1

请求包如下:

{
   "button":[
       {	
           "type":"click",
           "name":"今日歌曲",
           "key":"V1001_TODAY_MUSIC"
       },
       {
           "name":"菜单",
           "sub_button":[
               {
                   "type":"view",
                   "name":"搜索",
                   "url":"http://www.soso.com/"
               },
               {
                   "type":"click",
                   "name":"赞一下我们",
                   "key":"V1001_GOOD"
               }
           ]
      }
   ]
}
  • 参数说明
参数 必须 说明
access_token 调用接口凭证
agentid 企业应用的id,整型。可在应用的设置页面查看
button 一级菜单数组,个数应为1~3个
sub_button 二级菜单数组,个数应为1~5个
type 菜单的响应动作类型,目前有click、view两种类型
name 菜单标题,不超过16个字节,子菜单不超过40个字节
key click类型必须 菜单KEY值,用于消息接口推送,不超过128字节
url view类型必须 网页链接,员工点击菜单可打开链接,不超过256字节
  • 权限说明

管理员须拥有应用的管理权限,并且应用必须设置在回调模式。

返回结果

{
   "errcode":0,
   "errmsg":"ok"
}

根据上面官方的定义语义,我们菜单管理的C#管理接口定义如下所示。

    /// <summary>
    ///企业号菜单管理接口定义/// </summary>
    public interfaceICorpMenuApi
{
/// <summary> ///获取菜单数据/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <returns></returns> MenuListJson GetMenu(string accessToken, stringagentid);/// <summary> ///创建菜单/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="menuJson">菜单对象</param> /// <returns></returns> CommonResult CreateMenu(string accessToken, MenuListJson menuJson, stringagentid);/// <summary> ///删除菜单/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <returns></returns> CommonResult DeleteMenu(string accessToken, stringagentid);
}

我们以创建菜单的实现为例来介绍微信企业号菜单的操作,其他的操作类似处理,都是返回一个公共的消息类,方便处理和读取,代码如下所示。

        /// <summary>
        ///创建菜单/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="menuJson">菜单对象</param>
        /// <returns></returns>
        public CommonResult CreateMenu(string accessToken, MenuListJson menuJson, stringagentid)
{
var url = string.Format("https://qyapi.weixin.qq.com/cgi-bin/menu/create?access_token={0}&agentid={1}", accessToken, agentid);string postData =menuJson.ToJson();returnHelper.GetCorpExecuteResult(url, postData);
}

3、企业号菜单管理接口的调用和处理效果

调用的代码和效果图如下所示。

        private void btnMenuCreate_Click(objectsender, EventArgs e)
{
MenuJson productInfo
= new MenuJson("产品介绍", newMenuJson[] {new MenuJson("软件产品介绍", ButtonType.click, "event-software")
,
new MenuJson("框架源码产品", ButtonType.click, "event-source")
,
new MenuJson("软件定制开发", ButtonType.click, "event-develop")
});

MenuJson frameworkInfo
= new MenuJson("框架产品", newMenuJson[] {new MenuJson("Win开发框架", ButtonType.click, "win"),new MenuJson("WCF开发框架", ButtonType.click, "wcf"),new MenuJson("混合式框架", ButtonType.click, "mix"),new MenuJson("Web开发框架", ButtonType.click, "web")
,
new MenuJson("代码生成工具", ButtonType.click, "database2sharp")
});

MenuJson relatedInfo
= new MenuJson("相关链接", newMenuJson[] {new MenuJson("公司介绍", ButtonType.click, "event_company"),new MenuJson("官方网站", ButtonType.view, "http://www.iqidi.com"),new MenuJson("联系我们", ButtonType.click, "event_contact"),new MenuJson("应答系统", ButtonType.click, "set-1"),new MenuJson("发邮件", ButtonType.view, "http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=S31yfX15fn8LOjplKCQm")
});

MenuListJson menuJson
= newMenuListJson();
menuJson.button.AddRange(
newMenuJson[] { productInfo, frameworkInfo, relatedInfo });//Console.WriteLine(menuJson.ToJson()); if (MessageUtil.ShowYesNoAndWarning("您确认要创建菜单吗") ==System.Windows.Forms.DialogResult.Yes)
{
ICorpMenuApi bll
= newCorpMenuApi();
CommonResult result
=bll.CreateMenu(token, menuJson, agentid);
Console.WriteLine(
"创建菜单:" + (result.Success ? "成功" : "失败:" +result.ErrorMessage));
}
}
private void btnMenuGet_Click(objectsender, EventArgs e)
{
ICorpMenuApi bll
= newCorpMenuApi();
MenuListJson menu
=bll.GetMenu(token, agentid);if (menu != null)
{
Console.WriteLine(menu.ToJson());
}
}

调用代码的测试输出如下所示。

如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章,系列随笔如下所示:

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)--开始使用微信接口

在较早时期,我写过一篇文章《
结合Control.FirefoxDialog控件,构造优秀的参数配置管理模块
》,
介绍过在我的Winform框架基础上集成的参数配置模块功能,但是参数模块的配置管理感觉还不够灵活,于是一直在寻找一个较好的替代者,用来结合FireFoxDialog界面一并展现,期间仔细研读过好几篇Codeproject网站上的关于配置管理的文章,但是总是觉得不够灵活或者简便。本文主要针对结合FireFoxDialog参数配置界面组件和SettingsProvider.net技术,实现较为美观、灵活的Winform程序参数配置管理。

在CodeProject上有两三篇文章介绍配置文件的很不错,下面给出链接大家分享下:

http://www.codeproject.com/Articles/25829/User-Settings-Applied

http://www.codeproject.com/Articles/475498/Easier-NET-settings

这两个都很不错,不过感觉不太满足我的简单、高效的需求,偶然之间在GitHub上发现一个《
SettingsProvider.net
》,做的非常不错,而且感觉扩展性也做的很好,因此就把它和FireFoxDialog界面一同整合,在框架内部实现参数管理功能。

1、SettingsProvider.net功能介绍

这个是在GitHub上的一款参数配置组件,能够基于普通配置文件、ProgramData目录文件、独立存储区文件等方式的配置文件存储,它主要是基于Json格式进行的配置保存,因此我们还可以把它存储到数据库,虽然官方没有提供例子,但是我们很容易通过扩展实现这个功能,后面我介绍我对其数据库参数保存扩展类。

它还可以通过Atrribute标识进行管理配置文件,可以把参数设置为加密、默认值、修改名称等,如下是它的一个配置参数的类的例子。

public classMySettings
{
[DefaultValue(
"Jake")]
[DisplayName(
"Your Name")]public string Name { get; set; }

[DefaultValue(
true)]
[Description(
"Should Some App Remember your name?")]public bool RememberMe { get;set; }public List<Guid> Favourites { get;set; }

[Key(
"OriginalName")]public string Renamed { get; set; }

[ProtectedString]
public string Encrypted { get; set; }
}

读取操作如下所示:

var settingsProvider = new SettingsProvider(); //By default uses IsolatedStorage for storage
var mySettings = settingsProvider.GetSettings<MySettings>();
Assert.True(mySettings.RememberMe);

保存操作如下所示:

var settingsProvider = new SettingsProvider(); //By default uses IsolatedStorage for storage
var mySettings = new MySettings { Name = "Mr Ginnivan"};
settingsProvider.Save(mySettings);

参数元数据获取操作代码如下:

var settingsProvider = newSettingsProvider();foreach (var setting in settingsProvider.ReadSettingMetadata<MySettings>())
{
Console.WriteLine(
"{0} ({1}) - {2}", setting.DisplayName, setting.Description, setting.DefaultValue);
}

2、参数配置功能界面截图

基于上述的SettingsProvider.net的组件,我们可以结合我前面介绍的FireFoxDialog界面效果,实现较好的参数配置管理功能,如下界面所示。

我们可以分别把不同的参数放到不同的存储介质里面去,如一些常用的,可以配置到本地目录文件里面去,一些和个人信息相关的内容,我们可以把它放到数据库里面去,这样可以在各个客户端使用都不需要重新配置,非常方便。

上面的例子,我针对性介绍两个,一个是基于本地文件参数存储,一个是基于数据库文件的参数存储。

3、基于SettingsProvider.net的整合操作的实现

介绍了基于SettingsProvider.net的功能,以及最终整合的效果,我们来看看它具体是如何整合实现不同文件类型数据的保存的。

首先,我们在设计模式下,拖动好对应的界面,由于FireFoxDialog界面本来就是用来做参数设置的,因此他们里面有一些控件就不一一介绍了,具体可以参考对应的文章(http://www.codeproject.com/KB/miscctrl/ControlFirefoxDialog.aspx?msg=1856449)。

我们看看上述效果界面的设计界面和后台代码。

设计界面就是如下所示,拖动一个参数配置用户控件到窗体上,设置好对应的内容就可以了。

后台代码如下所示。

    public partial classFrmSettings : BaseForm
{
publicFrmSettings()
{
InitializeComponent();
}
private void FrmSettings_Load(objectsender, EventArgs e)
{
this.firefoxDialog1.ImageList = this.imageList1;this.firefoxDialog1.AddPage("报表设置", new PageReport());//基于本地文件的参数存储 this.firefoxDialog1.AddPage("邮箱设置", new PageEmail());//基于数据库的参数存储//下面是陪衬的 this.firefoxDialog1.AddPage("短信设置", newPageEmail());this.firefoxDialog1.AddPage("声音设置", newPageEmail());this.firefoxDialog1.AddPage("系统设置", newPageEmail());this.firefoxDialog1.AddPage("备份设置", newPageEmail());this.firefoxDialog1.AddPage("其他设置", newPageEmail());this.firefoxDialog1.Init();
}
}

这里最有代表性的是PageReport和PageEmail两个组件对象。

1)报表模块配置管理

基于报表参数和邮件参数,我们可以定义一个参数对象类,用来方便保存和获取数据的承载对象。

报表参数对象类如下(只做了简单的一个报表路径处理):

    /// <summary>
    ///报表设置/// </summary>
    public classReportParameter
{
/// <summary> ///派车单报表文件/// </summary> [DefaultValue("WHC.CarDispatch.CarSendBill2.rdlc")]public string CarSendReportFile { get; set; }
}

对应的设计界面如下所示,用来提供一个报表文件的参数配置,很简单了。

我们来看看后台的组件,对参数是如何保存和显示的。

首先需要初始化相应的对象,我们这里使用了在程序运行目录下,创建一个文件用来保存这些设置。

    public partial classPageReport : PropertyPage
{
privateSettingsProvider settings;privateISettingsStorage store;publicPageReport()
{
InitializeComponent();
if(!this.DesignMode)
{
//PortableStorage: 在运行程序目录创建一个setting的文件记录参数数据 store = newPortableStorage();
settings
= newSettingsProvider(store);
}
}

注意:PortableStorage是在运行程序目录创建一个setting的文件记录参数数据,运行后,最终会在目录下生成一个类似“ReportParameter.settings”的文件,它的内容格式如下所示。

[{"Key":"CarSendReportFile","Value":"\"WHC.CarDispatch.CarSendBill2.rdlc\""}]

我们看看报表组件的参数是如何初始化的:

        public override voidOnInit()
{
ReportParameter parameter
= settings.GetSettings<ReportParameter>();if (parameter != null)
{
EnableOtherReport(
false);string reportFile =parameter.CarSendReportFile;if (reportFile == "WHC.CarDispatch.CarSendBill2.rdlc")
{
this.radReport.SelectedIndex = 0;
}
else if (reportFile == "WHC.CarDispatch.CarSendBill.rdlc")
{
this.radReport.SelectedIndex = 1;
}
else{
EnableOtherReport(
true);this.radReport.SelectedIndex = 2;this.txtOtherReport.Text =reportFile;
}
}
}

通过参数的存储对象处理,这样我们就可以通过ReportParameter 进行数据获取了。

保存参数的时候,同样也是先获取到一个参数对象,并设置它的值后,然后进行保存就可以了,具体代码如下所示。

        public override boolOnApply()
{
bool result = false;try{
ReportParameter parameter
= settings.GetSettings<ReportParameter>();if (parameter != null)
{
int otherType = 2;//2代表其他类型 if (this.radReport.SelectedIndex <otherType)
{
parameter.CarSendReportFile
= this.radReport.Properties.Items[this.radReport.SelectedIndex].Value.ToString();
}
else{
parameter.CarSendReportFile
= this.txtOtherReport.Text;
}
settings.SaveSettings
<ReportParameter>(parameter);
}
result
= true;
}
catch(Exception ex)
{
LogTextHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
returnresult;
}

2)邮件参数配置管理

邮件参数对象类如下:

    /// <summary>
    ///邮箱设置/// </summary>
    public classEmailParameter
{
/// <summary> ///邮件账号/// </summary> //[DefaultValue("wuhuacong@163.com")] public string Email { get; set; }/// <summary> ///POP3服务器/// </summary> [DefaultValue("pop.163.com")]public string Pop3Server { get; set; }/// <summary> ///POP3端口/// </summary> [DefaultValue(110)]public int Pop3Port { get; set; }/// <summary> ///SMTP服务器/// </summary> [DefaultValue("smtp.163.com")]public string SmtpServer { get; set; }/// <summary> ///SMTP端口/// </summary> [DefaultValue(25)]public int SmtpPort { get; set; }/// <summary> ///登陆账号/// </summary> public string LoginId { get; set; }/// <summary> ///登陆密码/// </summary> [ProtectedString]public string Password { get; set; }/// <summary> ///使用SSL加密/// </summary> [DefaultValue(false)]public bool UseSSL { get; set; }
}

参数显示和保存的界面设计如下所示。

界面的初始化,同样和上面的差不多,不过这里使用了数据库的存储类DatabaseStorage,内容将保存在数据库里面,而且我们通过用户的标识Create进行路径的针对性处理,可以使每个用户的配置文件不同。

    public partial classPageEmail : PropertyPage
{
privateSettingsProvider settings;privateISettingsStorage store;publicPageEmail()
{
InitializeComponent();
if (!this.DesignMode)
{
//DatabaseStorage:在数据库里面,以指定用户标识保存参数数据 string creator =Portal.gc.LoginUserInfo.Name;
store
= newDatabaseStorage(creator);
settings
= newSettingsProvider(store);
}
}

参数的加载代码如下所示,也就是把数据获取后,显示在界面上即可。

        public override voidOnInit()
{
EmailParameter parameter
= settings.GetSettings<EmailParameter>();if (parameter != null)
{
this.txtEmail.Text =parameter.Email;this.txtLoginId.Text =parameter.LoginId;this.txtPassword.Text =parameter.Password;this.txtPassword.Tag =parameter.Password;this.txtPop3Port.Value =parameter.Pop3Port;this.txtPop3Server.Text =parameter.Pop3Server;this.txtSmtpPort.Value =parameter.SmtpPort;this.txtSmtpServer.Text =parameter.SmtpServer;this.txtUseSSL.Checked =parameter.UseSSL;
}
}

数据的保存操作也很简单,和前面的操作差不多,如下所示。

                EmailParameter parameter = settings.GetSettings<EmailParameter>();if (parameter != null)
{
parameter.Email
= this.txtEmail.Text;
parameter.LoginId
= this.txtLoginId.Text;
parameter.Password
= this.txtPassword.Text;
parameter.Pop3Port
= Convert.ToInt32(this.txtPop3Port.Value);
parameter.Pop3Server
= this.txtPop3Server.Text;
parameter.SmtpPort
= Convert.ToInt32(this.txtSmtpPort.Value);
parameter.SmtpServer
= this.txtSmtpServer.Text;
parameter.UseSSL
= this.txtUseSSL.Checked;

settings.SaveSettings
<EmailParameter>(parameter);
}

最终,我们在数据库表里面,可以看到对应记录已经保存起来了,并且用户密码部分也进行了加密处理。

这样,我们整合两者的特点,就可以实现比较不错的参数配置界面的显示和后台存储处理了,针对性的,使用不同的存储介质,以满足不同的需要。

我们开发程序的时候,经常会碰到一些报表,如果是Winform的报表,一般可以采用DevExpress控件组的XtraReport,或者微软的RDLC报表,当然还有一些其他的,在此不再赘述。由于本人在Winform开发中经常使用到一些报表,有时候使用XtraReport报表,有时候也使用RDLC报表,因此本篇主要介绍这两种不同类型报表的使用,以及对比它们的差异性,以便我们更好的掌握了解。

1、XtraReport报表的使用

1)报表界面展示

在我的派车管理系统里面,我需要根据数据库的记录,把相关的派车历史信息做成一个报表统计的模块,需要列出报表的明细(派车明细)和统计值(记录数、公里数),这种类型的报表是很多情况下需要碰到的。

我需要实现一个基于XtraReport报表技术实现的功能界面,如下面图示所示。

这里面涉及的字段,包括字符型、日期型,数值型、枚举类型等,还有统计值、打印时间(参数),因此也算一个比较完整的报表展示了。

2)创建新报表

为了实现这个报表模块,我们先使用VS的工具创建一个报表组件对象。

在使用前,我们需要设置好报表组件的各个显示模块,报表报表上下空间部分(TopMargin/BottomMargin),这两个部分用来调整上下的边线的;

然后每个报表有头部内容和底部内容的区分,ReportHeader一般用来放置表格头部字段显示的,明细的内容包括展示列表的明细信息,大致调整如下所示。

3)设计报表控件和数据源管理

在VS左边的工具箱里面有响应的XtraReport控件,拖动到报表界面里面就可以进行绘制设计了。

为了实现表格统计和展示,我们需要拖动两个表格到报表界面里面去,一个是报表头字段,一个用来显示报表明细的列表内容,两个单元格的宽度设置一致,并且上下空间要挨着,否则显示出来就有空间,不好看。

表格拖动过去后,可以使用右键添加相应的单元格,然后统一设置他们的背景颜色,高度宽度,边框等属性即可。

表格头我们可以填写相关的文字替代,但是报表的明细信息,我们需要绑定相应的字段内容,因此需要增加一个数据源用来绑定到报表上去。

在报表属性里面,找到DataSource进行配置,如下所示。

拖动相关的表格,然后设计其界面,并绑定明细单元格的内容为[ABC]这样的格式,就标识绑定了一个ABC的字段属性了。如下所示是绑定了一个HandNo的字段显示。

最终设计的报表界面如下所示。

4)特殊字段的处理

在报表的设计过程中,我们一般会碰到各种不同的字段,它不是简单的文本信息,可能是日期,也可以是统计值,计算值等。

打印时间,只需要增加一个XRPageInfo,然后设置里面的值就可以了,这个有点类似RDLC的参数值。

从上面我们可以看到有很多项目可以选的,除了有时间,还有记录数、总记录数、用户名、总数等等。

记录数的统计设计如下所示,主要是通过千制的方式显示,调用Count函数处理。

同样,总公里数,是统计整个报表里面记录的公里数操作,调用SUM函数处理,它的设计界面如下。

设计好Xtra报表后,我们就可以程序里面,直接调用进行加载处理即可,代码如下所示。

        private voidPrintReport()
{
string where =GetConditionSql();
List
<CarApplyInfo> list = BLLFactory<CarApply>.Instance.Find(where);

FrmHistoryReport report
= newFrmHistoryReport();
report.DataSource
=list;

ReportPrintTool tool
= newReportPrintTool(report);
tool.ShowPreview();
}

我们主要获取数据源,并绑定数据源到对应的报表组件上,然后调用ReportPrintTool 进行展现即可,代码非常简洁,只是设计过程麻烦一些。

2、RDLC报表的使用

在我《
会员管理系统的设计和开发(2)-- RDLC报表的设计及动态加载
》里面,有对RDLC报表设计做了一些介绍,RDLC是微软的报表格式,它是一个相对比较独立的文件,是一个基于XML描述性的文件,除了可以用在Winform上外,也可以用在Web上,通用性也非常不错。

基于对比的考虑,我也为同样的模块设计了一个一样的报表,RDLC报表最终展示界面如下所示。

和前面XtraReport的报表界面做一个对比。

两者的展现效果都差不多,只是界面效果有一些差异而已。

1)RDLC的设计效果

由于RDLC它是一个独立的XML文件,它的描述全部在一个XML里面,因此我们设计的时候,只是对XML文件进行处理,VS给我们提供了一个设计器及进行设计而已。

在RDLC的设计视图里面,我们可以看到报表数据里面,包括一些预定义的数据,以及我们添加进去的数据源。

当然报表设计少不了相关的控件,因此在工具栏里面,可以看到可以用的报表控件元素,如下所示。

2)报表特殊元素的处理

RDLC报表,也有一些如打印时间,分页数据等,是需要特殊的处理了,打印时间是使用报表数据里面的【内置字段】项目,如下所示。

对于记录总数和统计值,我们可以通过自定义的格式内容进行处理,如下所示。

统计总数的操作设计如下,调用SUM函数即可,如果忘记是那个,可以在下面类别里面进行查询,里面有很多运算符、常见函数等内容可以使用。

另外,对于一些特殊的显示格式,我们需要正确设置,如下是日期的格式设置。

3)
报表的调用处理

由于报表的展现模块,我已经放到了一个独立的通用模块里面,因此对于报表的加载,只需要简单的设置属性,然后调用即可,如下所示。

        private voidPrintRDLCReport()
{
string where =GetConditionSql();
List
<CarApplyInfo> list = BLLFactory<CarApply>.Instance.Find(where);foreach (CarApplyInfo info inlist)
{
info.Data1
= info.Status.ToString();//转换枚举类型描述 }

ReportViewerDialog dlg
= newReportViewerDialog();
dlg.Text
= "历史记录报表";
dlg.DataSourceDict.Add(
"CarApplyInfo", list);
dlg.ReportName
= "WHC.CarDispatch.HistoryReport";
dlg.IsPrintLayout
= true;

dlg.ShowDialog();
}

上面主要也是构建数据源,指定报表路径和布局,然后绑定报表展示模块窗口即可。

下面附上几个系统功能截图,供参考学习。

1)主界面列表,基于GridControl的展示,并增加统计栏目。

2)历史记录报表展示(XtraReport报表)

3)两种不同格式的派车单报表展示(RDLC报表)。

3、XtraReport报表和RDLC报的差异

这两者的报表都能实现相似的效果,不过XtraReport是把它集成到DLL里面,类似编译的窗体文件一样了;而RDLC报表本身是一个独立的文件,我们发布的时候,要把它带上,但是又有较好的通用性。

对于XtraReport的字段内容展示,默认就是调用了ToString()的函数处理,因此类似日期、枚举类型,都不需要怎么样设置,就可以正常显示,但是RDLC报表设计里面,我们需要为每种字段设置好对应的格式,枚举类型好像解析还是会转换为整形,因此需要在数据源级别进行转换,否则显示不出来。

对于自定义函数方面,XtraReport提供了较好的设计界面,能够很大程度减少出错的机会;RDLC则提供了弹性化的操作,提供了很多函数进行参考,需要自己添加表达式,不过在编译的时候,能够检查出现的错误。

如果需要动态配置、加载或者开发一些新的报表展示,可能相对来说,RDLC更方便,可以增量发布一些报表,然后在后台配置即可展示,不需要改动源码编译。