C#开发微信门户及应用(29)--微信个性化菜单的实现

有一段时间没有接着微信的主题继续介绍里面的功能模块了,这段时间来,微信也做了不少的变化改动,针对这些特性我全面核对了一下相关的微信公众号和企业号的接口,对原有的微信API和系统管理做了全面的更新,本随笔以及后面的随笔就是基于这个工作上的总结,以期把微信涉及的功能模块,都使用C#实现的方式来介绍。本随笔主要介绍微信公众号的个性化菜单的实现,虽然目前微信公众号和企业号已经在功能上接近一致,不过在企业号上还没有个性化菜单的相关接口。

1、个性化菜单介绍

我们先了解一下个性化菜单的介绍,根据官方的资料,如下介绍。
为了帮助公众号实现灵活的业务运营,微信公众平台新增了个性化菜单接口,开发者可以通过该接口,让公众号的不同用户群体看到不一样的自定义菜单。该接口开放给已认证订阅号和已认证服务号。
开发者可以通过以下条件来设置用户看到的菜单:
1、用户分组(开发者的业务需求可以借助用户分组来完成)2、性别3、手机操作系统4、地区(用户在微信客户端设置的地区)5、语言(用户在微信客户端设置的语言)
个性化菜单接口说明:
1、个性化菜单要求用户的微信客户端版本在iPhone6.2.2,Android 6.2.4以上。2、菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。3、普通公众号的个性化菜单的新增接口每日限制次数为2000次,删除接口也是2000次,测试个性化菜单匹配结果接口为20000次4、出于安全考虑,一个公众号的所有个性化菜单,最多只能设置为跳转到3个域名下的链接5、创建个性化菜单之前必须先创建默认菜单(默认菜单是指使用普通自定义菜单创建接口创建的菜单)。如果删除默认菜单,个性化菜单也会全部删除
个性化菜单匹配规则说明:
当公众号创建多个个性化菜单时,将按照发布顺序,由新到旧逐一匹配,直到用户信息与matchrule相符合。如果全部个性化菜单都没有匹配成功,则返回默认菜单。例如公众号先后发布了默认菜单,个性化菜单1,个性化菜单2,个性化菜单3。那么当用户进入公众号页面时,将从个性化菜单3开始匹配,如果个性化菜单3匹配成功,则直接返回个性化菜单3,否则继续尝试匹配个性化菜单2,直到成功匹配到一个菜单。根据上述匹配规则,为了避免菜单生效时间的混淆,决定不予提供个性化菜单编辑API,开发者需要更新菜单时,需将完整配置重新发布一轮。
除正常的默认菜单外,个性化菜单提供了下面几个处理操作。
1 创建个性化菜单
2 删除个性化菜单
3 测试个性化菜单匹配结果
4 查询个性化菜单
5 删除所有菜单
个性化自定义菜单功能,是指商家可以根据粉丝的分组、性别、手机操作系统,甚至地区,来分别展示公众号的菜单。
比如,一家健身会所的公众号,将自定义菜单根据粉丝性别进行了分开设置。男性粉丝进入公众号会展示男生们比较感兴趣菜单内容,跆拳道、搏击等;若是女性粉丝,则会展示瑜伽、舞蹈、防身术等女性粉丝感兴趣的菜单课程等。


2、个性化菜单的C#设计处理

微信的菜单是微信处理中很重要的一环,因为它是微信给用户入口体验重要的一环,也是很直观的界面呈现之一,菜单的展示就是需要我们绞尽脑汁来精简的界面元素了,由于菜单的重要性以及功能的丰富性,我在之前好几篇随笔专门介绍了菜单的各种处理。

在《C#开发微信门户及应用(6)--微信门户菜单的管理操作》、《C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器》、《C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍》、《C#开发微信门户及应用(20)-微信企业号的菜单管理》分别对相关的菜单有过介绍,有需要可以参考了解一下。
菜单模块,在微信公众号和企业号,它们都可以共用一个模型,因此我们把这些内容都放在微信共用模块里面,包括它的实体类信息(如菜单模块),虽然有些地方,部分字段内容没有,但是我们可以通过实体类的JSON标识来进行处理,从而实现比较弹性化的实体类信息承载。
如下标识代码所示。

/// <summary>
/// 菜单基本信息(公众号、企业号公用,其他部分一样)
/// </summary>
public class MenuJson : BaseJsonResult
{
    /// <summary>
    /// 按钮描述,既按钮名字,不超过16个字节,子菜单不超过40个字节
    /// </summary>
    public string name { get; set; }

    /// <summary>
    /// 按钮类型(click或view)
    /// </summary>
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string type { get; set; }

    /// <summary>
    /// 按钮KEY值,用于消息接口(event类型)推送,不超过128字节
    /// </summary>
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string key { get; set; }

    /// <summary>
    /// 网页链接,用户点击按钮可打开链接,不超过256字节
    /// </summary>
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string url { get; set; }

    /// <summary>
    /// media_id类型和view_limited类型必须,调用新增永久素材接口返回的合法media_id
    /// </summary>
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string media_id { get; set; }

    /// <summary>
    /// 子按钮数组,按钮个数应为2~5个
    /// </summary>
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public List<MenuJson> sub_button { get; set; }

上面这个实体类是菜单信息的基础信息,我们需要从这个基础上扩展出来几个实体类,方便在构建,获取菜单信息的时候进行信息转换,如下几个类图的设计所示。



通过这几个类的关系,我们就可以适应所有的默认菜单和个性化菜单,以及企业号的菜单信息转换处理了,通过JSON转换为对应的实体类,实现信息的强类型处理。
其中我们菜单处理的API实现类代码如下所示。

    /// <summary>
    /// 菜单的相关操作
    /// </summary>
    public class MenuApi : IMenuApi

这样我们在IMenuApi的基础上增加几个个性化的接口。

/// <summary>
/// 创建个性化菜单
/// </summary>
/// <param name="accessToken">调用接口凭证</param>
/// <param name="menuJson">菜单对象</param>
/// <returns></returns>
string CreateConditionalMenu(string accessToken, MenuListJson menuJson);

/// <summary>
/// 根据菜单ID删除个性化菜单
/// </summary>
/// <param name="accessToken">调用接口凭证</param>
/// <param name="menuid">菜单ID</param>
/// <returns></returns>
CommonResult DeleteConditionalMenu(string accessToken, string menuid);

/// <summary>
/// 测试个性化菜单匹配结果
/// </summary>
/// <param name="accessToken">调用接口凭证</param>
/// <returns></returns>
MenuListJson TryMatchConditionalMenu(string accessToken, string user_id);

然后在MenuApi实现类里面实现相关的逻辑处理即可。
下面是创建个性化菜单的接口说明。
创建个性化菜单

http请求方式:POST(请使用https协议)
https://api.weixin.qq.com/cgi-bin/menu/addconditional?access_token=ACCESS_TOKEN

请求示例

{
    "button":[
    {   
        "type":"click",
        "name":"今日歌曲",
        "key":"V1001_TODAY_MUSIC" 
    },
    { 
        "name":"菜单",
        "sub_button":[
        {   
            "type":"view",
            "name":"搜索",
            "url":"http://www.soso.com/"
        },
        {
            "type":"view",
            "name":"视频",
            "url":"http://v.qq.com/"
        },
        {
            "type":"click",
            "name":"赞一下我们",
            "key":"V1001_GOOD"
        }]
 }],
"matchrule":{
  "group_id":"2",
  "sex":"1",
  "country":"中国",
  "province":"广东",
  "city":"广州",
  "client_platform_type":"2"
  "language":"zh_CN"
  }
}

其中上面的信息,转换为我们的实体类对象信息就是如下所示。
**MenuListJson **menuJson

它API返回结果如下所示。

{ "menuid":"208379533"}

为了承载这个信息,我们需要定义一个实体类来承载这个信息。

/// <summary>
/// 返回MenuId的结果
/// </summary>
public class MenuIdResult
{       
    /// <summary>
    /// 菜单的ID
    /// </summary>
    public string menuid { get; set; }
}

最后我们的API接口只需要获取里面的菜单ID值,字符串类型的就可以了,API具体实现代码如下所示。

/// <summary>
/// 创建个性化菜单
/// </summary>
/// <param name="accessToken">调用接口凭证</param>
/// <param name="menuJson">菜单对象</param>
/// <returns></returns>
public string CreateConditionalMenu(string accessToken, MenuListJson menuJson)
{
    string result = "";
    var url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/addconditional?access_token={0}", accessToken);
    //matchrule不能为空
    ArgumentValidation.CheckForNullReference(menuJson.matchrule, "matchrule");

    string postData = menuJson.ToJson();
    MenuIdResult list = JsonHelper<MenuIdResult>.ConvertJson(url, postData);
    if (list != null)
    {
        result = list.menuid;
    }
    return result;
}

而删除个性化菜单的接口也是类似的处理,如下所示。

/// <summary>
/// 根据菜单ID删除个性化菜单
/// </summary>
/// <param name="accessToken">调用接口凭证</param>
/// <param name="menuid">菜单ID</param>
/// <returns></returns>
public CommonResult DeleteConditionalMenu(string accessToken, string menuid)
{
    var url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/delconditional?access_token={0}", accessToken);
    var data = new
    {
        menuid = menuid
    };

    string postData = data.ToJson();
    return Helper.GetExecuteResult(url, postData);
}

另外,对于个性化菜单来说,查询、删除所有的菜单操作接口和普通默认菜单是一样的,因此可以重用这两个接口,不过由于我们扩展了查询返回的信息类,所以如果是个性化菜单,那么它返回的JSON字符串里面,包含了conditionalmenu的属性,这个是一个集合 ,也就是说个性化菜单是多个规则的个性化菜单的集合。



菜单的JSON数据结构如下所示。


3、个性化菜单封装接口的使用

有了上面接口的封装,我们可以在创建个性化菜单的时候很方便了,构建个性化菜单的时候,我们需要把全部需要出现的菜单都需要放在一个conditionalmenu的单元里面,虽然这样的单元在集合里面出现多个。
如下面是构建一个个性化的菜单项目,和普通的默认菜单不同,它需要指定一个matchrule的对象,如下代码所示。

MenuJson conditional = new MenuJson("相关链接", new MenuJson[] { 
    new MenuJson("个性化菜单", ButtonType.click, "event_company")
});
MatchRule rule = new MatchRule()
{
    sex = "1"
};

MenuListJson menuJson = new MenuListJson();
menuJson.button.AddRange(new MenuJson[] { conditional });
menuJson.matchrule = rule;

通过上面的代码了解,我们如果需要增加多个个性化菜单,那么就是通过这个方法,每次创建一个个性化菜单的方式进行构建,当然如果我们使用查询个性化菜单的结果可以看到,如果创建多个,那么conditionalmenu集合里面是多个对象的。
调用代码每次添加一个个性化菜单的操作如下所示。

string menuid = menuBLL.CreateConditionalMenu(token, menuJson);
Console.WriteLine("创建菜单:" + (!string.IsNullOrEmpty(menuid) ? "成功:" + menuid : "失败"));

我们创建多个规则后,可以通过TryMatch的方式来检查匹配的结果,也就是对应不同用户(openid)会出现不同的个性化菜单列表了。

var myResult = menuBLL.TryMatchConditionalMenu(token, openId);
if (myResult != null)
{
    Console.WriteLine(myResult.ToJson());
}

var userid_female = "oSiLnt2J4mYkhVG3aLTdMIF1hv-s";//女性的ID
myResult = menuBLL.TryMatchConditionalMenu(token, userid_female);
if (myResult != null)
{
    Console.WriteLine(myResult.ToJson());
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容