前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Magcodes.WeiChat——自定义CustomCreationConverter之实现微信自定义菜单的序列化

Magcodes.WeiChat——自定义CustomCreationConverter之实现微信自定义菜单的序列化

作者头像
雪雁-心莱科技
发布2018-12-27 11:23:36
5710
发布2018-12-27 11:23:36
举报
文章被收录于专栏:magicodesmagicodesmagicodes

微信自定义菜单接口是一个比较麻烦的接口,往往开发的小伙伴们看到下面的这段返回JSON,整个人就会不好了:

{"menu":{"button":[{"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC","sub_button":[]},{"type":"click","name":"歌手简介","key":"V1001_TODAY_SINGER","sub_button":[]},{"name":"菜单","sub_button":[{"type":"view","name":"搜索","url":"http://www.soso.com/","sub_button":[]},{"type":"view","name":"视频","url":"http://v.qq.com/","sub_button":[]},{"type":"click","name":"赞一下我们","key":"V1001_GOOD","sub_button":[]}]}]}}

N个类型,而且每种类型都有不同的属性,而且还要sub_button!让我们去泪奔一会。

碰到这种问题,一般的小伙伴是这么玩的:

首先我们需要确认总共有哪些属性,如下所示:

image
image

那么,这就好办了,于是定义如下:

public class MenuFull_RootButton
    {
        public string type { get; set; }
        public string key { get; set; }
        public string name { get; set; }
        public string url { get; set; }
        public string media_id { get; set; }
        public List<MenuFull_RootButton> sub_button { get; set; }
    }

然后就获取到了一堆蹩脚对象。作为代码洁癖者的我,没法忍!(开始装B了)

于是就开始闷着头编码了(B装不下去了)~~~

1. 定义接口方法

先定义一个简单的接口方法,太复杂了后面自己也看不懂。

/// <summary>
    /// 自定义菜单接口
    /// http://mp.weixin.qq.com/wiki/10/0234e39a2025342c17a7d23595c6b40a.html
    /// </summary>
    public class MenuApi : ApiBase
    {
        const string APIName = "menu";
        /// <summary>
        /// 自定义菜单查询接口
        /// https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
        /// </summary>
        /// <returns>菜单返回结果</returns>
        public MenuGetApiResultModel Get()
        {
            //获取api请求url
            var url = GetAccessApiUrl("get", APIName);
            return Get<MenuGetApiResultModel>(url, new MenuButtonsCustomConverter());
        }
    }

这里值得注意的是MenuGetApiResultModel和MenuButtonsCustomConverter,就靠他俩出招了。

注意:ApiBase和Get的封装请暂时忽略。Get在这里只是用于发起Get请求并且序列化JSON而已,其定义如下:

/// <summary>
/// GET提交请求,返回ApiResult对象
/// </summary>
/// <typeparam name="T">ApiResult对象</typeparam>
/// <param name="url">请求地址</param>
/// <param name="jsonConverts">Json转换器</param>
/// <returns>ApiResult对象</returns>
protected T Get<T>(string url, params JsonConverter[] jsonConverts) where T : ApiResult

2. 定义JSON模型

先定义根:

/// <summary>
    /// 菜单返回结果
    /// </summary>
    public class MenuGetApiResultModel : ApiResult
    {
        [JsonProperty("menu")]
        public MenuInfo Menu { get; set; }
    }
    /// <summary>
    /// 菜单信息
    /// </summary>
    public class MenuInfo
    {
        [JsonProperty("button")]
        public List<MenuButtonBase> Button { get; set; }
}

然后定义一个菜单类型:

/// <summary>
    /// 菜单类型
    /// </summary>
    public enum MenuButtonTypes
    {
        /// <summary>
        /// 点击推事件
        /// </summary>
        click = 1,
        /// <summary>
        /// 跳转URL
        /// </summary>
        view = 2,
        /// <summary>
        /// 扫码推事件
        /// </summary>
        scancode_push = 3,
        /// <summary>
        /// 扫码推事件且弹出“消息接收中”提示框
        /// </summary>
        scancode_waitmsg = 4,
        /// <summary>
        /// 弹出系统拍照发图
        /// </summary>
        pic_sysphoto = 5,
        /// <summary>
        /// 弹出拍照或者相册发图
        /// </summary>
        pic_photo_or_album = 6,
        /// <summary>
        /// 弹出微信相册发图器
        /// </summary>
        pic_weixin = 7,
        /// <summary>
        /// 弹出地理位置选择器
        /// </summary>
        location_select = 8,
        /// <summary>
        /// 下发消息(除文本消息)
        /// </summary>
        media_id = 9,
        /// <summary>
        /// 跳转图文消息URL
        /// </summary>
        view_limited = 10
    }

枚举方便维护。

然后,要定义菜单基类了,开始搞基了:

/// <summary>
    /// 菜单按钮基类
    /// </summary>
    public class MenuButtonBase
    {
        /// <summary>
        /// 菜单标题,不超过16个字节,子菜单不超过40个字节
        /// </summary>
        [MaxLength(20)]
        [JsonProperty(PropertyName = "name", Required = Required.Always)]
        public virtual string Name { get; set; }
        /// <summary>
        /// 菜单类型(菜单的响应动作类型)
        /// </summary>
        [JsonConverter(typeof(StringEnumConverter))]
        [JsonProperty(PropertyName = "type")]
        public MenuButtonTypes Type { get; set; }

}

注意:这里的命名我还是喜欢骆驼命名法,胸大有感觉!所以,JsonProperty是个好东西。另外,JsonConverter用于设置转换器,这里使用了StringEnumConverter,用于将字符串转换为相应的枚举类型。那个MaxLength请暂时忽略,我是为将来接口自定义验证预留的,当然你也可以当成我顺手撸上的,不过当前我们不是来做验证的,我们是来做接口滴。

好了,开始搞基。我们先来定义一级按钮类型。比如含子菜单的情况:

/// <summary>
    /// 子菜单按钮
    /// </summary>
    public class SubMenuButton : MenuButtonBase
    {
        /// <summary>
        /// 菜单标题,不超过16个字节,子菜单不超过40个字节
        /// </summary>
        [MaxLength(8)]
        [JsonProperty(PropertyName = "name", Required = Required.Always)]
        public override string Name { get; set; }
        /// <summary>
        /// 子菜单(二级菜单数组,个数应为1~5个)
        /// </summary>
        [JsonProperty(PropertyName = "sub_button")]
        public List<MenuButtonBase> SubButtons { get; set; }
   }

以及其他的类型,这里举例说明,篇幅所限:

/// <summary>
    /// Click按钮(点击推事件)
    /// 用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
    /// </summary>
    public class ClickButton : MenuButtonBase
    {
        public ClickButton()
        {
            this.Type = MenuButtonTypes.click;
        }
        /// <summary>
        /// 菜单KEY值,用于消息接口推送,不超过128字节
        /// </summary>
        [JsonProperty(PropertyName = "key", Required = Required.Always)]
        public string Key { get; set; }
 }
    /// <summary>
    /// 下发消息(除文本消息)
    /// 用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
    /// </summary>
    public class MediaIdButton : MenuButtonBase
    {
        public MediaIdButton()
        {
            this.Type = MenuButtonTypes.media_id;
        }
        /// <summary>
        /// 调用新增永久素材接口返回的合法media_id
        /// </summary>
        [JsonProperty(PropertyName = "media_id", Required = Required.Always)]
        public string MediaId { get; set; }
}

下面省略一千行代码…

好了,JSON模型初步定义好了,看着像模像样的,然并卵!好吧,不装B了,我们继续。

3. 定义自定义对象创建转换器(CustomCreationConverter)

这B又可以快乐的装下去了,真开心。

我们先来看看其定义:

image
image

从定义中可以看出来,Create是充话费送的,必须实现,然并卵,这玩意儿没法实现我们上述的需求。objectType是拿不到多少有价值的信息的,看看源码就清楚,这货是给ReadJson用的。哎哟,这B又装不下去了,这怎么行呢。他搞不定,我们搞他姥姥。就酱:

/// <summary>
    /// 菜单按钮自定义对象创建转换器
    /// 根据菜单类型自定义创建
    /// </summary>
    public class MenuButtonsCustomConverter : CustomCreationConverter<MenuButtonBase>
    {
        /// <summary>
        /// 读取目标对象的JSON表示
        /// </summary>
        /// <param name="reader">JsonReader</param>
        /// <param name="objectType">对象类型</param>
        /// <param name="existingValue"></param>
        /// <param name="serializer"></param>
        /// <returns>对象</returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null) return null;
            var jObject = JObject.Load(reader);
            MenuButtonBase target = default(MenuButtonBase);
            //获取type属性
            var type = jObject.Property("type");
            if (type != null && type.Count > 0)
            {
                var typeValue = type.Value.ToString();
                var menuButtonType = (MenuButtonTypes)Enum.Parse(typeof(MenuButtonTypes), typeValue);
                #region 根据类型返回相应菜单类型
                switch (menuButtonType)
                {
                    case MenuButtonTypes.click:
                        target = new ClickButton();
                        break;
                    case MenuButtonTypes.view:
                        target = new ViewButton();
                        break;
                    case MenuButtonTypes.scancode_push:
                        target = new ScancodePushButton();
                        break;
                    case MenuButtonTypes.scancode_waitmsg:
                        target = new ScancodeWaitmsgButton();
                        break;
                    case MenuButtonTypes.pic_sysphoto:
                        target = new PicSysphotoButton();
                        break;
                    case MenuButtonTypes.pic_photo_or_album:
                        target = new PicPhotoOrAlbumButton();
                        break;
                    case MenuButtonTypes.pic_weixin:
                        target = new PicWeixinButton();
                        break;
                    case MenuButtonTypes.location_select:
                        target = new LocationSelectButton();
                        break;
                    case MenuButtonTypes.media_id:
                        target = new MediaIdButton();
                        break;
                    case MenuButtonTypes.view_limited:
                        target = new ViewLimitedButton();
                        break;
                    default:
                        throw new NotSupportedException("不支持此类型的菜单按钮:" + menuButtonType);
                } 
                #endregion
            }
            else
            {
                target = new SubMenuButton();
            }
            serializer.Populate(jObject.CreateReader(), target);
            return target;
        }
        /// <summary>
        /// 创建对象(会被填充)
        /// </summary>
        /// <param name="objectType">对象类型</param>
        /// <returns>对象</returns>
        public override MenuButtonBase Create(Type objectType)
        {
            return new SubMenuButton();
        }
    }

至此,整个是OK的。那么我们来看看结果:

image
image
image
image

这B总算装完了。

这是Magicodes.WeiChat.Framework中,MenuApi的设计,上面只是介绍其原理,后续会完善个性化菜单以及相关接口。

Magicodes.WeiChat.Framework为本人轻量设计的微信SDK,框架基本成型后,会将此部分剥离Magicodes.WeiChat并且开源。希望能够得到各位热心观众的支持。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-03-02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 定义接口方法
  • 2. 定义JSON模型
  • 3. 定义自定义对象创建转换器(CustomCreationConverter)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档