• 相信不少朋友都遇到过这样的问题:当发送的文本消息内容过长时,微信将不做任何响应。那么到底微信允许的文本消息的最大长度是多少呢?我们又该如何计算文本的长度呢?为什么还有些人反应微信好像支持的文本消息最大...

    相信不少朋友都遇到过这样的问题:当发送的文本消息内容过长时,微信将不做任何响应。那么到底微信允许的文本消息的最大长度是多少呢?我们又该如何计算文本的长度呢?为什么还有些人反应微信好像支持的文本消息最大长度在1300多呢?这篇文章会彻底解除大家的疑问。


    接口文档中对消息长度限制为2048

    以看到,接口文档中写的很明确:回复的消息内容长度不超过2048字节。那为什么很多人测试反应消息内容长度在1300多字节时,微信就不响应了呢?我想这问题应该在这部分人没有搞清楚到底该如何计算文本的字节数。


    如何正确计算文本所占字节数

    计算文本(字符串)所占字节数,大家第一个想到的应该就是String类的getBytes()方法,该方法返回的是字符串对应的字节数组,再计算数组的length就能够得到字符串所占字节数。例如:

    [java] view plain copy
    1. public static void main(String []args)  {  
    2.     // 运行结果:4  
    3.     System.out.println("柳峰".getBytes().length);  
    4. }  
    上面的示例中计算了两个中文所占的字节数为4,即一个汉字占2个字节。真的是这样吗?其实我们忽略了一个问题:对于不同的编码方式,中文所占的字节数也不一样!这到底要怎么呢?在上面的例子中,我们并没有指定编码方式,那么会使用操作系统所默认的编码方式。先来看我得出的三条结论:

    1)如果上面的例子运行在默认编码方式为ISO8859-1的操作系统平台上,计算结果是2;

    2)如果上面的例子运行在默认编码方式为gb2312或gbk的操作系统平台上,计算结果是4;

    3)如果上面的例子运行在默认编码方式为utf-8的操作系统平台上,计算结果是6;

    如果真的是这样,是不是意味着String.getBytes()方法在我们的系统平台上默认采用的是gb2312或gbk编码方式呢?我们再来看一个例子:

    [java] view plain copy
    1. public static void main(String []args) throws UnsupportedEncodingException  {  
    2.     // 运行结果:2  
    3.     System.out.println("柳峰".getBytes("ISO8859-1").length);  
    4.     // 运行结果:4  
    5.     System.out.println("柳峰".getBytes("GB2312").length);  
    6.     // 运行结果:4  
    7.     System.out.println("柳峰".getBytes("GBK").length);  
    8.     // 运行结果:6  
    9.     System.out.println("柳峰".getBytes("UTF-8").length);  
    10. }  
    这个例子是不是很好地证明了我上面给出的三条结论呢?也就是说采用ISO8859-1编码方式时,一个中/英文都只占一个字节;采用GB2312或GBK编码方式时,一个中文占两个字节;而采用UTF-8编码方式时,一个中文占三个字节。


    微信平台采用的编码方式及字符串所占字节数的计算

    那么,在向微信服务器返回消息时,该采用什么编码方式呢?当然是UTF-8,因为我们已经在doPost方法里采用了如下代码来避免中文乱码了:

    [java] view plain copy
    1. // 将请求、响应的编码均设置为UTF-8(防止中文乱码)  
    2. request.setCharacterEncoding("UTF-8");  
    3. response.setCharacterEncoding("UTF-8");  
    为了验证我所说了,我写了个例子来测试:

    [java] view plain copy
    1. private static String getMsgContent() {  
    2.     StringBuffer buffer = new StringBuffer();  
    3.     // 每行70个汉字,共682个汉字加1个英文的感叹号  
    4.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");  
    5.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");  
    6.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");  
    7.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");  
    8.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");  
    9.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");  
    10.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");  
    11.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");  
    12.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");  
    13.     buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵!");  
    14.     return buffer.toString();  
    15. }  
    16.   
    17. public static void main(String []args) throws Exception  {  
    18.     // 采用gb2312编码方式时占1365个字节  
    19.     System.out.println(getMsgContent().getBytes("gb2312").length);  
    20.     // 采用utf-8编码方式时占2047个字节  
    21.     System.out.println(getMsgContent().getBytes("utf-8").length);  
    22. }  

    getMsgContent()方法返回的内容正是微信的文本消息最长能够支持的,即采用UTF-8编码方式时,文本消息内容最多支持2047个字节,也就是微信公众平台接口文档里所说的回复的消息内容长度不超过2048字节,即使是等于2048字节也不行,你可以试着将getMsgContent()方法里的内容多加一个英文符号,这个时候微信就不响应了。

    同时,我们也发现,如果采用gb2312编码方式来计算getMsgContent()方法返回的文本所占字节数的结果是1365,这就是为什么很多朋友都说微信的文本消息最大长度好像只支持1300多字节,并不是接口文档中所说的2048字节,其实是忽略了编码方式,只是简单的使用了String类的getBytes()方法而不是getBytes("utf-8")方法去计算所占字节数。


    Java中utf-8编码方式时所占字节数的计算方法封装

    [java] view plain copy
    1. /** 
    2.  * 计算采用utf-8编码方式时字符串所占字节数 
    3.  *  
    4.  * @param content 
    5.  * @return 
    6.  */  
    7. public static int getByteSize(String content) {  
    8.     int size = 0;  
    9.     if (null != content) {  
    10.         try {  
    11.             // 汉字采用utf-8编码时占3个字节  
    12.             size = content.getBytes("utf-8").length;  
    13.         } catch (UnsupportedEncodingException e) {  
    14.             e.printStackTrace();  
    15.         }  
    16.     }  
    17.     return size;  
    18. }  

    好了,本章节的内容就讲到这里,我想大家通过这篇文章所学到的应该不仅仅是2047这个数字,还应该对字符编码方式有一个新的认识。

    展开全文
  • 本文节选自苏震巍撰写的《微信开发深度解析:微信公众号、小程序高效开发秘籍》一书,由电子工业出版社出版。 责编:陈秋歌,关注微信开发等领域,寻求报道或者投稿请发邮件至chenqg#csdn.net。 MessageHandler 是...

    本文节选自苏震巍撰写的《微信开发深度解析:微信公众号、小程序高效开发秘籍》一书,由电子工业出版社出版。
    责编:陈秋歌,关注微信开发等领域,寻求报道或者投稿请发邮件至chenqg#csdn.net。

    MessageHandler 是一个微信消息的处理模块,也是整个微信开发过程中不可缺少的一部分。在 MessageHandler 中,开发者可以非常轻松地处理所有类型的微信消息。

    本文将介绍 MessageHandler 的原理以及使用方法,包括支撑MessageHandler 运行所必需的实体类型、工厂方法等相关知识的介绍。

    设计思想

    如果你已经了解微信消息的基本通信原理,那我们现在可以非常方便地构造出一个简单的消息处理功能,如下:

    
    StreamReader str = new StreamReader(Request.InputStream, System.Text.Encoding. UTF8);
    XmlDocument xml = new XmlDocument();
    xml.Load(str);
    var wx = new WeixinRequest();
    wx.ToUserName = xml.SelectSingleNode("xml").SelectSingleNode("ToUserName").InnerText;
    wx.FromUserName = xml.SelectSingleNode("xml").SelectSingleNode("FromUserName"). InnerText;
    wx.MsgType = xml.SelectSingleNode("xml").SelectSingleNode("MsgType").InnerText;
    if (wx.MsgType.Trim() == "event")
    {
        wx.EventName = xml.SelectSingleNode("xml").SelectSingleNode("Event").InnerText;
        //  WriteLog(wx.EventName);
        if (wx.EventName.ToUpper() == "LOCATION")
        {
            wx.Latitude = xml.SelectSingleNode("xml").SelectSingleNode("Latitude").InnerText;
            wx.Longitude = xml.SelectSingleNode("xml").SelectSingleNode("Longitude"). InnerText;
            wx.Precision = xml.SelectSingleNode("xml").SelectSingleNode("Precision"). InnerText;
        }
        else
        {
            wx.EventKey = xml.SelectSingleNode("xml").SelectSingleNode("EventKey"). InnerText;
        }
    }
    else if (wx.MsgType.Trim() == "text")
    {
        wx.Content= xml.SelectSingleNode("xml").SelectSingleNode("Content").InnerText;
    } else if (...)
    {
        //...
    }

    这个方法也是目前很多其他框架甚至微信官方的Demo使用的,但是这种方法我可以用“不美好”来形容。

    不美——首先使用字符串拼接的方式非常丑陋,其次哪怕使用 XmlDocument 或 XDocument 等面向对象的方式去处理,面对几十种不同的微信消息类型以及一一对应的不同的格式,代码将变得非常冗长而且难以维护。这样的代码你的老板或客户会喜欢吗?

    不好——这样的写法坏处太多:

    • 可移植性差
    • 并没有做到很好地分离(无论是和整个应用程序还是不同请求类型之间)
    • 如果要做单元测试就必须整体代码一起上
    • 基本上不具备可扩展性
    • 容错能力很差,即使做到了,代码已经无法直视
    • 正常人用多了会心情不好

    那么,“美好”的消息处理方式应该是怎么样的呢?

    下面就将 Senparc.Weixin.MP.MessageHandler 介绍给你。

    首先,美好的 MessageHandler 必须具有对消息类型的自动识别和分类能力。

    第二,美好的 MessageHandler 必须能够同时、自动处理“明文”“兼容模式”“加密模式”三种(所有)消息加密类型,并且让开发者忘掉加密这回事情的存在。

    第三,美好的 MessageHandler 必须能够提供很好的消息容器以及储存容器,来解决消息去重、Session 等一系列的问题。

    第四,美好的 MessageHandler 必须能够兼容 MVC 和 WebFroms 不同的请求处理方式。

    第五,美好的 MessageHandler 必须能够提供统一逻辑处理的接口,方便在特定的环节对消息进行统一处理。

    第六,美好的 MessageHandler 必须具备优秀的可测试性和扩展能力。

    第七,美好的 MessageHandler 必须能做到很好的逻辑分离。

    第八,美好的 MessageHandler 必须让你用起来心情好。

    第九,美好的 MessageHandler 不能保证你能在 10 分钟内,完成一个满足以上八条的简单微信应用从开发到上线、发布的全过程。但是我们做到了。

    消息类型

    概述

    微信的互动消息包含请求消息响应消息两类:

    • 请求消息:由微信服务器发送到应用服务器的消息(通常由用户微信操作触发或用户主动发送)。
      -响应消息:应用服务器收到请求消息之后,返回给微信服务器的消息。响应消息可以被转发到用户微信,也可以采用“沉默”的方式不给予响应。

    所有的消息类型如图1所示(至本文发布,消息类型又丰富了许多)。

    图片描述

    图1

    无论是请求消息还是响应消息,各自还包括不同的消息类型,其中每一种消息类型,又都有不同的参数和对应的格式要求。

    对应每一种消息具体的参数和 XML 格式可以参考微信官方Wiki,这里不再赘述。本书将重点针对面向对象的类展开介绍,使用 Senparc.Weixin SDK 的开发者基本上可以忘掉微信的 XML 格式和要求,只需要了解如何面向对象地处理消息。当然,对于 XML 的了解将帮助你更加从容地处理一些问题,例如测试和调试过程都可能需要用到 XML。

    命名规则

    为了可以使用 C# 面向对象地处理问题,同时也更加规范地进行编程,我们决定在一些地方改变微信原有的命名规则(全部使用小写,用下划线_分隔不同单词),而使用 C# 推荐的命名方式(Pascal 大小写命名法)来作为类名或属性名称。如微信文档中的属性或名称可能为 access_token,在 Senparc.Weixin 中将被命名为 AccessToken。当阅读微信官方的文档时理解这样的变化举一反三即可。

    当然,这种改变是灵活的,有一些地方我们仍然需要保持参数名称的“原貌”来确保 JSON 和 XML 自动转换的准确性和稳定性。

    这样的改变不仅出现在消息类型中,对本书后面会介绍的 API 和其他功能同样适用。
    全局消息基类

    在所有这些消息类型中,都具有一些相同的属性,因此在 MessageHandler 体系中,我们为所有的消息创建了一个基类 MessageBase

        /// <summary>
        /// 所有Request和Response消息的基类
        /// </summary>
        public abstract class MessageBase : IMessageBase
        {
            public string ToUserName { get; set; }
            public string FromUserName { get; set; }
            public DateTime CreateTime { get; set; }
        }

    MessageBase将作为所有消息的基类,无论是请求消息还是响应消息,也无论是微信公众号还是企业号或开放平台等,MessageBase 对微信消息进行了非常高度的抽象和概括。

    下面我们将分别介绍的请求消息和响应消息都是以 MessageBase 作为基类的。

    请求消息

    请求消息又分为两种类型:普通消息事件推送消息。无论是哪一种消息,它们都具有相同的基础参数,如表1所示。

    图片描述

    为此,在 MessageBase 的基础上,我们为所有的请求消息设置了一个基类 RequestMessageBase

    图片描述

    请注意 RequestMessageBase 是一个抽象类,因此不可以被直接实例化。事实上,这个类也不应该被实例化,因为我们没有办法通过实例化这个类达到操作一个具体的请求类型消息的目的,我们仍然需要创建基于 RequestMessageBase 的对应每一个消息类型的实体来对其进行操作(作为全局考虑,不光微信公众号的请求消息需要用到 RequestMessageBase,企业号、开放平台等其他模块也需要用到)。

    以上的基类都定义在 Senparc.Weixin.dll 中,普通消息的定义在Senparc.Weixin.MP.dll 中,直接使用 RequestMessageBase 作为基类,对应关系如表2所示。

    图片描述

    事件推送消息比普通消息多了 Event 这个属性,因此我们为所有的事件推送消息定义了一个统一的基类 RequestMessageEventBase

        public class RequestMessageEventBase : RequestMessageBase, IRequestMessageEventBase
        {
            public override RequestMsgType MsgType
            {
                get { return RequestMsgType.Event; }
            }
    
            /// <summary>
            /// 事件类型
            /// </summary>
            public virtual Event Event
            {
                get { return Event.ENTER; }
            }
        }

    可以看到 RequestMessageEventBaseRequestMessageBase 多了一个 Event 属性,并且对MsgType 属性进行了强制的规定,默认情况下所有继承 RequestMessageEventBase 的类型都将返回 RequestMsgType.Event,这样 MessageHandler 将可以对事件推送消息消息类型进行特殊的处理。

    由于事件推送消息类型比较多,我们将其分为 3 大类:

    • 常规事件(公众号基础功能返回事件)
    • 菜单事件(各种类型的公众号菜单返回事件)
    • 应用事件(应用模块返回事件,例如卡券、多客服等)

    对应如表3所示。

    图片描述

    以上的类型中,都具有从 RequestMessageEventBase 继承而来的 Event 属性,有部分类型都具有 EventKey 属性,说明如表4所示。

    图片描述

    并不是所有的事件都具有EventKey,因此我们提供了IRequestMessageEventKey 接口,所有实现此接口的消息类型必须提供 EventKey,例如订阅事件:

        /// <summary>
        /// 事件之订阅
        /// </summary>
        public class RequestMessageEvent_Subscribe : RequestMessageEventBase, IRequestMessageEventBase, IRequestMessageEventKey
        {
            /// <summary>
            /// 事件类型
            /// </summary>
            public override Event Event
            {
                get { return Event.subscribe; }
            }
    
            /// <summary>
            /// 事件KEY值,qrscene_为前缀,后面为二维码的参数值(如果不是扫描场景二维码,此参数为空)
            /// </summary>
            public string EventKey { get; set; }
            /// <summary>
            /// 二维码的ticket,可用来换取二维码图片(如果不是扫描场景二维码,此参数为空)
            /// </summary>
            public string Ticket { get; set; }
        }

    有些事件则不需要 EventKey,如取消订阅事件:

        /// <summary>
        /// 事件之取消订阅
        /// </summary>
        public class RequestMessageEvent_Unsubscribe : RequestMessageEventBase, IRequestMessageEventBase
        {
            /// <summary>
            /// 事件类型
            /// </summary>
            public override Event Event
            {
                get { return Event.unsubscribe; }
            }
        }

    注意:实现 EventKey 参数类型都是字符串,但是在不同的事件中会具有不同的含义,有些官方已经规定了参数格式(如:扫描带参数二维码事件,都是以 qrscene_ 为前缀),而有些可以自己定义(如:菜单点击事件),在开发的过程中需要注意。


    注意:事件推送消息的触发大部分是通过用户的某个微信界面的操作触发的(如:菜单点击、发送语音等),也有通过其他操作触发的(如:微小店订单付款通知、多客服转接会话等),因此在“人肉测试”的时候要格外注意结合参考微信官方的文档,了解其触发流程,避免误认为程序问题而没有触发事件的错误判断。

    响应消息
    响应消息和请求消息的设计原理类似,所有响应消息的类型都继承自ResponseMessageBase,ResponseMessageBase 同样继承自 MessageBase。 Senparc.Weixin.dll 下的 ResponseMessageBase (Weixin.Entities.ResponseMessageBase)代码如下:

     /// <summary>
     /// 响应回复消息
     /// </summary>
     public abstract class ResponseMessageBase : MessageBase, IResponseMessageBase
     {
     }

    以上 Weixin.Entities.ResponseMessageBase 适用于包含微信公众号、企业号、开发平台等在内的多个平台,作为响应消息的基类。

    为了增强 MessageHandler 的便捷性,Senparc.Weixin.MP.dll 提供了专门用于微信公众号的 ResponseMessageBase (Senparc.Weixin.MP.Entities. ResponseMessageBase,继承自 Senparc.Weixin. Entities.ResponseMessageBase),代码如下:

        /// <summary>
        /// 微信公众号响应回复消息基类
        /// </summary>
        public class ResponseMessageBase : Weixin.Entities.ResponseMessageBase, IResponseMessageBase
        {
            public virtual ResponseMsgType MsgType
            {
                get { return ResponseMsgType.Text; }
            }
    
            /// <summary>
            /// 获取响应类型实例,并初始化
            /// </summary>
            /// <param name="requestMessage">请求</param>
            /// <param name="msgType">响应类型</param>
            /// <returns></returns>
            [Obsolete("建议使用CreateFromRequestMessage<T>(IRequestMessageBase requestMessage)取代此方法")]
            private static ResponseMessageBase CreateFromRequestMessage(IRequestMessageBase requestMessage, ResponseMsgType msgType)
            {
                //略
            }
    
            /// <summary>
            /// 获取响应类型实例,并初始化
            /// </summary>
            /// <typeparam name="T">需要返回的类型</typeparam>
            /// <param name="requestMessage">请求数据</param>
            /// <returns></returns>
            public static T CreateFromRequestMessage<T>(IRequestMessageBase requestMessage) where T : ResponseMessageBase
            {
                //略        }
    
            /// <summary>
            /// 从返回结果XML转换成IResponseMessageBase实体类
            /// </summary>
            /// <param name="xml">返回给服务器的Response Xml</param>
            /// <returns></returns>
            public static IResponseMessageBase CreateFromResponseXml(string xml)
            {
                //略        }
        }

    通过上述的两个 ResponseMessageBase,我们可以发现,和 RequestMessageBase 不同的是,ResponseMessageBase 没有提供 MsgId,这就意味着:应用程序服务器回复微信服务器的消息,必须一次成功,微信没有提供和响应消息一样的容错机制(数秒内收不到响应则连续发送几条带有 MsgId 的消息到应用服务器,确保消息可以到达)。也就是说,如果这条响应消息因为各种原因没有发送成功(网络问题或格式错误),客户端将收不到正确的消息回复,通常还会显示一条“该公众号暂时无法提供服务,请稍后再试(Official account services unavailable. Try again later)”的错误信息,如图2所示。

    图片描述

    图2

    此错误可以在“盛派网络小助手”发送文字“错误”进行测试。

    注意:对于多条带有相同 MsgId 的请求消息进行多次回复,客户端也只能收到微信服务器最后一次重发所对应的这条响应消息。

    例如由于应用程序服务器没有及时响应,微信服务器连续发送了 3 条MsgId都为 6224799151644543291 的消息到应用程序服务器,这三次请求分别为 A、B、C,应用程序服务器由于没有对消息去重,分别响应了 A1、B1、C1,此时,客户端只会收到一条 C1 的回复。此错误可以在“盛派网络小助手”发送文字“容错”进行测试。

    响应消息的类型比请求消息要少许多,如表5所示。

    图片描述

    上述响应类型中,有部分包含复杂的属性,我们将其独立创建类,分别有:Image、Voice、Video、Music、Article 这些类型。
    以比较常用的图文消息为例,ResponseMessageNews 定义如下:

    /// <summary>
        /// 图文消息
        /// </summary>
        public class ResponseMessageNews : ResponseMessageBase, IResponseMessageBase
        {
            new public virtual ResponseMsgType MsgType
            {
                get { return ResponseMsgType.News; }
            }
    
            public int ArticleCount
            {
                get
                {
                    return Articles == null ? 0 : Articles.Count;
                }
                set
                {
                    //这里开放set只为了逆向从Response的XML转成实体的操作一致性,没有实际意义。
                }
            }
    
            /// <summary>
            /// 文章列表,微信客户端只能输出前10条(可能未来数字会有变化,出于视觉效果考虑,建议控制在8条以内)
            /// </summary>
            public List<Article> Articles { get; set; }
    
            public ResponseMessageNews()
            {
                Articles = new List<Article>();
            }
        }

    由于工厂模式自动初始化的需要,必须提供一个不带参数的构造函数,因此Articles参数无法通过构造函数设置,必须分成两步:

        var reponseMessage = CreateResponseMessage<ResponseMessageNews>();
        reponseMessage.Articles.Add(new Article()
        {
            Title = "你点击了子菜单图文按钮",
            Description = "你点击了子菜单图文按钮,这是一条图文信息。",
            PicUrl = "http://weixin.senparc.com/Images/qrcode.jpg",
            Url = "http://weixin.senparc.com"
        });

    当 Articles 列表中只有 1 个 Article 对象的时候,显示为单图文,如图3所示。当 Article 大于 1 个时,显示为多图文,如图4所示。Articles 中最多允许有 10 个 Article 对象,即 10 条图文信息。

    图片描述

    使用MessageHandler

    下面来看一下 MessageHandler 是如何让你爱上微信开发的。

    在上述例子中,你只需要做三步:

    • 第一步:通过 Nuget 安装 Senparc.Weixin.MP。
    • 第二步:创建你自己的 MessageHandler(大部分代码只需要复制)。
    • 第三步:写 3 行关键代码(同样只需要复制)。

    下面我们来跟随这三步,略为详细地展开一下 MessageHandler 最基础的一些用法,随后我们将学习 MessageHandler 的内部实现。

    第一步:通过Nuget安装Senparc.Weixin.MP

    第二步:创建你自己的MessageHandler

    创建新文件 CustomMessageHandler.cs。CustomMessageHandle.cs 需要继承 Senparc.Weixin.MP. MessageHandlers 这个抽象类,并实现部分方法。最初步的 CustomMessageHandle.cs 代码可以如下。

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Web;
    using Senparc.Weixin.MP.Context;
    using Senparc.Weixin.MP.Entities;
    using Senparc.Weixin.MP.MessageHandlers;
    
    namespace Senparc.Weixin.MP.Sample.Weixin
    {
        public class CustomMessageHandler : MessageHandler<CustomMessageContext>
        {
            public CustomMessageHandler(Stream inputStream, PostModel postModel)
                : base(inputStream, postModel)
            {
    
            }
    
            public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
            {
                var responseMessage = base.CreateResponseMessage<ResponseMessageText>(); //ResponseMessageText也可以是News等其他类型
                responseMessage.Content = "这条消息来自DefaultResponseMessage。";
                return responseMessage;
            }
        }
    }

    我们可以看到必须要重写实现的抽象方法名为 DefaultResponseMessage(),这一条信息用于返回一条默认(替补)消息,假如对应类型(如语音)的微信消息没有被代码处理,那么默认会返回这里的结果。

    在 DefaultResponseMessage() 方法中,有这样一句:

    var responseMessage = base.CreateResponseMessage<ResponseMessageText>(); //ResponseMessageText也可以是News等其他类型

    这里的 CreateResponseMessage 方法即创建一个返回对象,T 可以为以下类型的任意一个,分别对应了不同的返回类型,具体类型及说明请参考表5。

    关于上述所有类型参数的设置方法,可以看 Senparc.Weixin SDK 的官方 Demo,这里不再重复。

    接下来,我们了解下如何处理微信服务器发过来的文字信息。

    很简单——在 CustomMessageHandler 里面重写一个 OnTextRequest 方法即可 :

    
    public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage)
    {
        if (requestMessage.Content == "你好")
        {
            var responseMessage = base.CreateResponseMessage<ResponseMessageNews>();
            var title = "Title";
            var description = "Description";
            var picUrl = "PicUrl";
            var url = "Url";
            responseMessage.Articles.Add(new Article()
            {
                Title = title,
                Description = description,
                PicUrl = picUrl,
                Url = url
            });
            return responseMessage;
        }
        else if (requestMessage.Content == "Senparc")
        {
            //相似处理逻辑
        }
        else
        {
            //...
        }
    }

    这个方法中可以自由发挥,比如读取数据库、判断关键字,甚至返回不同的ResponseMessageXX 类型(只要最终的类型都是在 IResponseMessageBase 接口下的即可)。

    与 OnTextRequest 对应,如果我们要处理语音、地理位置、菜单等类型的消息,只需要重写对应的方法,可以重写的方法如下:

    • 接收消息方法
    #region 接收消息方法
    
    /// <summary>
    /// 默认返回消息(当任何OnXX消息没有被重写,都将自动返回此默认消息)
    /// </summary>
    public abstract IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage);
    
    /// <summary>
    /// 预处理文字或事件类型请求。
    /// 这个请求是一个比较特殊的请求,通常用于统一处理来自文字或菜单按钮的同一个执行逻辑,
    /// 会在执行OnTextRequest或OnEventRequest之前触发,具有以下一些特征:
    /// 1、如果返回null,则继续执行OnTextRequest或OnEventRequest
    /// 2、如果返回不为null,则终止执行OnTextRequest或OnEventRequest,返回最终ResponseMessage
    /// 3、如果是事件,则会将RequestMessageEvent自动转为RequestMessageText类型,其中RequestMessageText.Content就是RequestMessageEvent.EventKey
    /// </summary>
    public virtual IResponseMessageBase OnTextOrEventRequest(RequestMessageText requestMessage){...}
    
    /// <summary>
    /// 文字类型请求
    /// </summary>
    public virtual IResponseMessageBase OnTextRequest(RequestMessageText requestMessage) {...}
    
    /// <summary>
    /// 位置类型请求
    /// </summary>
    public virtual IResponseMessageBase OnLocationRequest(RequestMessageLocation requestMessage) {...}
    
    /// <summary>
    /// 图片类型请求
    /// </summary>
    public virtual IResponseMessageBase OnImageRequest(RequestMessageImage requestMessage) {...}
    
    /// <summary>
    /// 语音类型请求
    /// </summary>
    public virtual IResponseMessageBase OnVoiceRequest(RequestMessageVoice requestMessage) {...}
    
    /// <summary>
    /// 视频类型请求
    /// </summary>
    public virtual IResponseMessageBase OnVideoRequest(RequestMessageVideo requestMessage) {...}
    
    /// <summary>
    /// 链接消息类型请求
    /// </summary>
    public virtual IResponseMessageBase OnLinkRequest(RequestMessageLink requestMessage) {...}
    
    /// <summary>
    /// 小视频类型请求
    /// </summary>
    public virtual IResponseMessageBase OnShortVideoRequest(RequestMessageShortVideo requestMessage) {...}
    
    #endregion
    • 接收事件方法
    #region Event下属分类,接收事件方法
    
    /// <summary>
    /// Event事件类型请求之ENTER
    /// </summary>
    public virtual IResponseMessageBase OnEvent_EnterRequest(RequestMessageEvent_ Enter requestMessage){...}
    
    /// <summary>
    /// Event事件类型请求之LOCATION
    /// </summary>
    public virtual IResponseMessageBase OnEvent_LocationRequest(RequestMessageEvent_ Location requestMessage) {...}
    
    //依此类推还有:
    // Event事件类型请求之subscribe:OnEvent_SubscribeRequest()
    // Event事件类型请求之unsubscribe:OnEvent_UnsubscribeRequest()
    // Event事件类型请求之CLICK:OnEvent_ClickRequest()
    // Event事件类型请求之scan:OnEvent_ScanRequest()
    // 事件之URL跳转视图(View):OnEvent_ViewRequest()
    // 事件推送群发结果:OnEvent_MassSendJobFinishRequest()
    // 发送模板消息返回结果:OnEvent_TemplateSendJobFinishRequest()
    // 弹出拍照或者相册发图:OnEvent_PicPhotoOrAlbumRequest()
    // 扫码推事件:OnEvent_ScancodePushRequest()
    // 扫码推事件且弹出“消息接收中”提示框:OnEvent_ScancodeWaitmsgRequest()
    // 弹出地理位置选择器:OnEvent_LocationSelectRequest()
    // 弹出微信相册发图器: OnEvent_PicWeixinRequest()
    // 弹出系统拍照发图:OnEvent_PicSysphotoRequest()
    // 卡券通过审核:OnEvent_Card_Pass_CheckRequest()
    // 卡券未通过审核: OnEvent_Card_Not_Pass_CheckRequest()
    // 领取卡券:OnEvent_User_Get_CardRequest()
    // 删除卡券:OnEvent_User_Del_CardRequest()
    // 多客服接入会话:OnEvent_Kf_Create_SessionRequest()
    // 多客服关闭会话:OnEvent_Kf_Close_SessionRequest()
    // 多客服转接会话:OnEvent_Kf_Switch_SessionRequest()
    // Event事件类型请求之审核结果事件推送:OnEvent_Poi_Check_NotifyRequest()
    // Event事件类型请求之Wi-Fi连网成功:OnEvent_WifiConnected()
    // Event事件类型请求之卡券核销:OnEvent_User_Consume_Card()
    // Event事件类型请求之从卡券进入公众号会话:OnEvent_User_Enter_Session_From_Card()
    // Event事件类型请求之进入会员卡:OnEvent_User_View_Card()
    // Event事件类型请求之微小店订单付款通知:OnEvent_Merchant_Order()
    // Event事件类型请求之接收会员信息事件通知:OnEvent_Submit_Membercard_User_Info()
    // Event事件类型请求之摇一摇事件通知:OnEvent_ShakearoundUserShake()
    
    #endregion

    其中 OnEvent_XX 对应的都是 Event 请求的子类型。

    第三步:写3行关键代码

    在已经创建好的 WeixinController.cs 中,加入如下代码:

    [HttpPost]
    [ActionName("Index")]
    public ActionResult Post(PostModel postModel)
    {
        if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel. Nonce, Token))
        {
            return Content("参数错误!");
        }
    
        postModel.Token = Token;
        postModel.EncodingAESKey = EncodingAESKey;//根据自己后台的设置保持一致
        postModel.AppId = AppId;//根据自己后台的设置保持一致
    
        var messageHandler = new CustomMessageHandler(Request.InputStream, postModel); //接收消息(第一步)
    
        messageHandler.Execute();//执行微信处理过程(第二步)
    
        return new FixWeixinBugWeixinResult(messageHandler);//返回(第三步)
    }

    如果你不需要进行保存消息日志等操作,这一步也几乎可以通过复制完成,不需要修改任何东西。当然即使需要保存日志,我们的 Demo 中也已经有相关案例可以直接使用。

    上述代码中的 FixWeixinBugWeixinResult 用于提供给 MVC 一个处理一些微信官方疏忽的问题或者由于跨平台导致的字符识别问题的修正方法,使所有手机平台都可以得到一致、稳定的结果。这个方法需要使用到 Senparc.Weixin.MP.MvcExtension.dll。

    至此我们已经使用 MassageHandler 处理所有微信用户发送过来的请求。

    点击订购:《微信开发深度解析:微信公众号、小程序高效开发秘籍

    图片描述

    展开全文
  • 本文来自作者 jerry 在 GitChat 上分享「如何通过微信开发实现财富自由」,「阅读原文」查看交流实录 「文末高能」 编辑 | 嘉仔 相信很多人看到这个标题会想,简直是哗众取宠,标题党。 为什么...

    640.gif?wxfrom=5&wx_lazy=1

    本文来自作者 jerry 在 GitChat 上分享「如何通过微信开发实现财富自由」,阅读原文」查看交流实录

    文末高能

    编辑 | 嘉仔

    相信很多人看到这个标题会想,简直是哗众取宠,标题党。

    为什么李笑来老师可以起这个名字的专栏?我不可以。相比而言,李笑来的专栏实际上更多在讲时间管理。

    而我今天的文章,是实际、通用、可行、被证明过的方法教程序员通过微信开发赚到很多钱。当然,李笑来老师是我非常尊敬的人,看他的文章我学会了很多有用的知识。

    是不是标题党,其实不重要,我们在网络上花费时间,最重要的是看能否学到对我们有用的东西,有启发的东西,学习到优点即可,其他糟粕忽略即可。下面回到正题:

    这个世界上,所有人的目标只有3个:变强、变美、变富。

    变富,就是通过个人努力能赚很多钱,像巴菲特、比尔盖茨一样富有。

    没有人不想赚很多很多钱,因为只有真正实现财富自由,才能时间自由和精神自由。

    那么今天,我这篇文章和接下来的Chat,将为大家介绍如何把自己的技术进行高效的商业变现,赚到很多钱。

    一、赚钱的逻辑

    其实赚钱的逻辑一点都不复杂。简单说就是:搞一个什么东西出来,卖给别人。再复杂的商业模式,都是如此,花1块钱搞一个什么东西出来,2块钱卖给别人,赚了1块的毛利。

    举例:

    一个作家花了1年的工时,写了一本书,每卖1本赚5块,卖了10万本,赚了50万毛利。

    一个程序员业余写了一个小插件,每个插件卖300块,卖了1万个,赚了300万毛利。

    一个程序员业余在微信上开发一个小工具,每天有几万人使用,引流出来给别人做淘宝客,赚了100多万毛利。

    总结赚钱的逻辑:

    a. 搞一个可复制的什么东西出来。b. 想办法卖给很多人。

    不管你这个东西是怎么搞出来的,怎么卖给别人的。所有的商业都是如此。所以,赚钱没那么难,只要记住这个原理,不断的去测试、执行和优化即可。

    我之前在知乎上写过一篇文章《程序员财富自有赚钱指南》 大概是下面几个方法:

    1)想尽一切办法把自己的时间(撸代码)复制很多很多份卖给别人

    • 录制教别人写程序的课程

    • 写教别人写程序的书

    • 写大平台上的各种插件卖给别人(非常多的平台,非常客观的收入)

    • 开发不用写代码就能生成产品的工具,卖会员给别人

    • 组织身边更多能写代码的人,然后把他们的时间复制很多很多份卖给别人

    ……

    2)开发能获取流量的工具,把流量卖给别人

    虽说互联网线上流量越来越贵,但还是大把的流量等着被你去获取。

    有人的地方就有需求、有需求的地方就有流量,有需求的地方就有待优化的功能体验。

    很多时候,我们也不需要多么牛逼的创新创造,只需要做好优化即可。

    3)开发一些小工具给大量有需求的B端人员

    有很多每年产值几千亿、从业人员几百万的行业我们可能自己没听说过,前几天有2个制衣、绣花行业的老板找我咨询问题,他给我的分析报告,让我吓一跳,原来有如此庞大的互联网化的需求。

    其实开发这种小工具也不需要一个搞个商业计划融资啥的,攻城师只需要多点业余时间就能做出来。

    http://W3shool.com 这个网站不就是一个工具么,它也仅仅是把做开发手册做进去,持续优化维护的结果而已。

    不要听那些大佬说互联网什么进入下半场了,对于我们普通人来说随便一个小机会抓住都可能让我们财富快速增长。况且,传统行业的人实在是太不懂互联网了,他们能把手里的金子扔掉不要。所以,现在还是有很多机会的。

    二、回到今天的主题:为什么通过微信开发能赚到钱?

    再次深度思考可能你认为已经懂的道理。

    1)首先问大家一个问题,中国市场上排名前2的线上电商平台是哪2个?(请注意是线上)

    我想你们肯定会回答是:淘宝天猫第1,京东第2。

    是这样吗?

    这其实是个错误答案。排名的第2的应该是微信。我简单说几个数字:

    阿里平台上10%的交易额来源于淘宝客,差不多有3000多亿的交易额,其中超过2000亿来自微信。

    仅仅我知道的做微商的大佬,年销售超过5亿的都有10个以上,除此之外还有各种大小微商。

    仅仅我知道的通过公众号卖货,年销售额超过5亿的也有10个以上,除此之外还有各种大号小号。

    仅仅我知道的依托微信的知识付费工具小鹅通上月交易额很早就破4000万,语音直播平台千聊的月交易额超过4000万,除此之外还有类似很多平台。

    依靠微信的入口和流量场景,京东(腾讯第一大股东)、蘑菇街、拼多多等等线上电商的交易量也是非常之大。

    上述这些仅仅是纯线上电商,微信也已经是第2名。

    2)那么线下的部分呢?

    微信突破月活10亿用户只是时间问题,微信就是华人互联网世界的最大连接器,从某种程度上讲,微信就是移动互联网本人。

    由于每个人的微信账户具备扫码、支付、定位、通知等属性,连接线下变的比过去容易许多。从现在到接下来的很长时间内,连接线下都会是红利期。

    下面举几个线下的例子:

    例如:摩拜单车的小程序,扫码开锁、关锁扣费通知。就是内嵌在里面非常方便的实现了人与物的连接、人与组织的连接。

    例如:我们到餐厅用微信扫码打开一个程序,排队、点餐、支付是不是非常高效的实现了以前的餐厅流程。

    例如:我们到商场看到天使之橙自动榨橙汁的设备,扫码支付后,等一会儿橙汁出来就可以拿走了。

    例如:我一个朋友是卖电线的,我给他开发了一个微信分销功能,每个合作的装修师傅,完成订单推荐后,扫码即可实时收到佣金增加通知,到一定金额,通过微信体现即可自动到微信钱包。

    例如:我一个朋友是开游乐场的,我给他开发了一个微信功能,替换掉了过去的会员系统,从客户注册到开卡购买、到消费核验都全自动化了。

    以前会员用户每次到店需要报电话,手动扣费。现在会员到店扫码核销,会员和商家双向通知。

    例如:我一个朋友现在面向建筑施工行业做微信扫码管理系统,已经部署了很多家。让每个施工人员都通过微信,把设备管理、安全隐患排查、质量管理等进行方便的流程交互连接操作。

    继续回到刚才的话题,线下电商(商务)最大的平台是什么呢?一定是微信。

    大家看下上面的截图,微信支付超越支付宝也只是时间问题。线下微信支付的交易额现在已经超过支付宝了。

    支付是交易最关键的环节。微信不仅是把人与人进行了连接,更加重要的是通过微信支付把所有人与人、人与商业连接起来。

    所以有人说:微信现在价值2000亿,但是如果让政府投资2000亿,不一定能做出一个现在的微信出来。

    3)回答主题:上面这些我都明白,但和我(程序员)有什么关系?我为什么可以通过微信开发能赚到钱?

    刚刚我们讲了微信已经在快速和各类商业(TO B)以及政府机构(TO G)在高效连接进行中。

    但这个过程不像做一个 APP 快速的把人引上来黏住就行。商业往往是很复杂的,这些事情都需要更多的创业开发者加入进来,去和相关的行业从业者进行对接。

    事实上微信也尽最大能力的支持开发者这样做。截止现在微信事实上已成为中国最大的互联网开放平台。

    目前这个开放平台正在走向最繁荣的阶段。

    为什么这样说?

    因为:这个开放平台不缺用户、不缺消费场景(流量)和工具,更重要的是所有人都连接一起,有一个比手机号还重要的账号,平台赋予了这个账号很多生活消费、工作、金融等相关功能。

    综合来讲:这个平台已经形成了,跟一个国家一样,上面有人和人之间的关系、民营企业、政府、银行、石油、保险等需要审批才能开展业务的国企。

    但是目前还不够丰富,有大量的行业可以借助微信实现线上线下融合。我拿我现在的一个大客户举例:一个月线下交易笔数超过10万笔、线下付费会员卡用户50万的连锁图书零售企业,日渐衰败,企业内没有一个懂互联网的人,把线下的流量每天浪费掉,却投入大量金钱赔钱搞天猫店和京东店,自己原本的会员流失殆尽,都没有建立一个自己的通道做会员通知。

    类似的传统企业非常之多,他们不懂互联网,不懂技术,把自己的企业资源当做石头一样扔掉。

    描述完上面内容,继续回到小标题:我为什么可以通过微信开发赚到钱?

    因为有需求。大量的传统企业有需求,这个体量很大很大。除了这些企业,还有大量的微信生态从业者也需要。

    除了大量需求的原因,还有一个最重要的原因:微信变成了一个开放平台,在这个平台上开发的东西可变成通用产品或者组件化。

    举例:曾经的很火爆的三级分销商城、投票系统、0元购等等,开发团队都是赚很多啊,仅一个投票系统,赚个几百万的工作室太多太多了。

    需求量大,又可产品组件化,可大可小,这难道不是一个快速赚钱的机会吗?

    三、常见的基于微信开发的产品有哪些?

    这部分内容我先在文章中大概描述一些,更多内容我们在语音直播课中细聊。

    主要从2个维度来分类:

    1)面向客户的类型分为:

    1. 行业客户的行业需求,例如:银行、政府、餐饮、零售等等细分的行业

    2. 自媒体从业者吸粉和变现需求,例如:公众号自媒体个人或公司

    3. 淘宝客相关工具

    4. 微商类相关工具

    5. 面向内容制作者的相关工具

    6. 面向微信开发者的相关工具

    7. 面向普通微信用户的增强工具

    2)面向具体应用的类型

    1. 围绕小程序生成的平台或组件,小程序推广工具、小程序变现工具等

    2. 围绕公众号涨粉的各种工具、围绕公众号变现的各种工具

    3. 围绕微信群管理的各种工具

    4. 围绕微信个人号及朋友圈的各种工具

    5. 围绕二维码的各种工具

    6. 围绕红包类的各种工具

    7. 围绕分销类的各种工具

    8. 围绕电商类的各种工具

    9. 其他

    基于微信的开发核心是要理解微信的账号和消息属性。我在我的知乎专栏写过一篇文章《写在小程序爆发前夜》,在那篇文章中详细描述了账号和消息属性的逻辑。

    感兴趣的可以在知乎专栏《微信商业变现和小程序指南》中查看。

    四、当前形态下开发哪几种产品可持续赚到钱?

    这个其实有太多了,我朋友开发微信群管理软件,截止现在都管理几万个微信群了。

    这部分我们可以放到语音直播中详细探讨。

    五、如何抓住基于微信的2B产品爆发红利期

    我本人前8年职业生涯都是在给电信、银行等大型企业做各种IT系统,对2B的产品有很多丰富的理解和经验。

    我认为,微信现在核心推广的小程序战略,对整个2B产品是有巨大的红利期。这块我们也放到语音直播中详细探讨。

    六、能不能赚到大钱的最关键的2个因素?

    • 核心因素1:开发、测试并优化变现模型

    • 核心因素2:用所有的力量去复制、复制,放大1万倍

    对于大多数普通人来说,实现财富自由的最好方式还是创业。我喜欢认识各种类型的创业者,尤其是草根创业者,与他们交流,了解他们的创业方法是很有意思的事情。

    其实我们大多数创业者都不可能像科技媒体报道的创业英雄那样,像滴滴、今日头条那样做一个大平台,疯狂受到资本和用户热捧。相反找到一个平台微创业的机会,通过巧妙的设计和运营,抓住红利机更容易成功,并逐步积累可能做成一个大生意。

    即使不能做成一个大生意,但能通过创业获得比打工多数倍的收入,实现在想生活的城市买房,拥有不一样的人生经历。这样的创业也也是值得的。

    这几年认识了很多通过互联网创业赚到钱的90后年轻人。他们有很多共同点,就是学习和执行力强,做的事情接地气,抓住一个平台机会拼命做到最好。

    小虎同学是个很典型的案例,他在我搞的共同成长联盟的成员中是比较低调的。14年的时候他发现了微信搜索公众号的机会,批量注册上百个入口型名称的公众号,半年时间轻松获得了上百万的粉丝。最近他公众号矩阵中排名中等的一个公众号,被行业内一个公司以报价300万的价格发起收购要约,被他拒绝了,因为他想用这个公众号注册同名的小程序,以实现更快速度的用户裂变和品牌价值。

    我有一个90后的朋友去年10月开始在微信群里做淘宝客,截止现在每个月都能做到500万毛利的规模。

    我还认识一个女生是做地方美食自媒体的,主要为粉丝提供本地吃喝玩乐的推荐。团队3人,仅单月广告收入已超过15万。在这个三线城市他们团队的日子过的很滋润。

    她告诉我美食自媒体的事业是当做百年品牌的目标做的,吃什么是人类永恒的主题。她只是借助微信、微博平台实现了更好的传播和粉丝互动。

    如果哪天这些平台不存在了,只要粉丝的需求还存在,我们就能生存的很好。像朱小莉这样的自媒体,全国至少有3000个类似的美食自媒体,已经逐步演变成为一个稳定的商业模式。

    还有一个扬州的开发者,在DZ上开发投票等营销插件,去年做了500多万的销售额。

    类似的案例有很多。为什么他们能通过创业赚到钱?很多创业公司却在极短的时间消失了。

    因为他们依托微信大平台抓住了红利,创业成本更低、获客效率更高、变现模式更成熟。

    很多人问为什么他们抓住了红利,我们没有抓住呢?

    我总结的原因主要有两个:1. 发现机会后立刻开始实践,在实践中成长。2. 认识了此领域的更多优秀同行,与同行学习和合作。

    最后是送给创业者最真诚的3个衷告:

    1)机会来临时,要实践实践再实践,动手动手再动手,看再多的道理,做再多的调查分析,都不如先甩开袖子干起来。

    2)不要用战术的勤奋掩盖战略的懒惰。互联网创业的本质是发现和掌握一套可复制的赚钱商业模型,不断优化模型提高转化概率,然后放大去操作。

    3)要认识到人脉和圈子的重要性。人脉会建立合作机会并提高你的商业认知水平。

    从我自己的判断来看,程序员基于微信平台,还有大量非常多的红利机会,只是看我们愿不愿意去寻找和实践。

      近期热文

    机器人的「语料」,如何获取?

    一页纸,梳理你的商业模式 ,奇妙的「精益画布」

    轻松几招你也可以架构高性能网站

    突破技术发展瓶颈、成功转型的重要因素

    沉迷前端,无法自拔的人,如何规划职业生涯?


    《GitChat 达人课程序员跨越式成长指南

    0?wx_fmt=jpeg

    「阅读原文」看交流实录,你想知道的都在这里

    展开全文
  • 在做微信高级接口开发中,或许总会碰到很多神奇的错误码,而这些错误码在官方文档中还是找不到原因,因此贴出自己开发过程中用的一些demo,希望能够给一些小伙伴指点迷津。{"errcode":45028,"errmsg":"has no ...

    在做微信高级接口开发中,或许总会碰到很多神奇的错误码,而这些错误码在官方文档中还是找不到原因,因此贴出自己开发过程中用的一些demo,希望能够给一些小伙伴指点迷津。{"errcode":45028,"errmsg":"has no masssend quota hint: [c3ZjkA0323age9]"}如遇到这个错误码,官方文档是没有查询的,这个是因为测试号没有大型数据群发配额导致,解决办法申请一个认证的订阅号或者公众号,或者测试号模式下通过openid来测试群发,最后在公众号上更换成正确的post地址,就可以实现了,只是测试号无法用分组的那个post接口而已。


    群发的步骤:


    第一步,获取access_token,这部分就不写代码了,可以参照柳峰的博客专栏
    http://blog.csdn.net/lyq8479/article/details/25076223点击打开链接
    获取到的ACCESS_TOKEN

    第二部,发送消息

    首先是准备post接口地址:

    String groupUrl = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=ACCESS_TOKEN"; //这个地址是根据分组id来群发消息
    
    String groupUrl1 = "https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token=ACCESS_TOKEN"; //这个地址是根据openid来群发消息
    由于接口调用有次数限制,测试号是200次一天,请珍惜。

    再是准备post数据:

    ①文本消息

    String group1data = "{\"filter\":{\"is_to_all\":false,\"group_id\":\"2\"},\"text\":{\"content\":\"群发消息测试\"},\"msgtype\":\"text\"}\";"; //这个是通过分组id发送的普通文本消息
    
    String openid1data = "{\"touser\":[\"obGXiwHTGN_4HkR2WToFj_3ua\",\"obGXiwNu0z2o_RRWaODvaZctd\"],\"msgtype\": \"text\",\"text\": {\"content\": \"测试文本消息\"}}";//这个是通过openid发送的普通文本消息//发送给  OPENID   为obGXiwHTGN_4HkR2WToFj_3ua和obGXiwNu0z2o_RRWaODvaZctd的两位关注公众号的微信用户,可替换成开发测试号中任意的一个用户或多个用户均可。如下图的json格式就可以,分组群发的数据格式可以查看官方文档,自己拼成java字符串就好了

    消息格式严格如上,可以参照官方文档微信官方文档,可以用JSONObject.from(Objec obj)这个来进行对象转json字符串,具体可以百度,红色字(由于颜色会影响代码美观,所以删除了)是关注当前微信公众号用户的openid

    至于如何获取用户openid这里就不赘述,参照官方文档,或者参照博主其他文章。

    ②图片消息图片消息数据准备又要分两步,关键在于     获取图片     或者说    获取media_id

    博主采用模拟表单上传方式来先上传一个临时素材文件并获取其id,代码如下:

    import java.io.BufferedReader;
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    public class FileUpload {
    
    	/**
    	 * 模拟form表单的形式 ,上传文件 以输出流的形式把文件写入到url中,然后用输入流来获取url的响应
    	 * @param url
    	 *            请求地址 form表单url地址
    	 * @param filePath
    	 *            文件在服务器保存路径
    	 * @return String url的响应信息返回值
    	 * @throws IOException
    	 */
    	public String send(String url, String filePath) throws IOException {
    		String result = null;
    		File file = new File(filePath);
    		if (!file.exists() || !file.isFile()) {
    			throw new IOException("文件不存在");
    		}
    		/**
    		 * 第一部分
    		 */
    		URL urlObj = new URL(url);
    		// 连接
    		HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();
    		/**
    		 * 设置关键值
    		 */
    		con.setRequestMethod("POST"); // 以Post方式提交表单,默认get方式
    		con.setDoInput(true);
    		con.setDoOutput(true);
    		con.setUseCaches(false); // post方式不能使用缓存
    		// 设置请求头信息
    		con.setRequestProperty("Connection", "Keep-Alive");
    		con.setRequestProperty("Charset", "UTF-8");
    		// 设置边界
    		String BOUNDARY = "---------------------------" + System.currentTimeMillis();
    		con.setRequestProperty("Content-Type", "multipart/form-data; boundary="
    				+ BOUNDARY);
    		// 请求正文信息
    		// 第一部分:
    		StringBuilder sb = new StringBuilder();
    		sb.append("--"); // 必须多两道线
    		sb.append(BOUNDARY);
    		sb.append("\r\n");
    		sb.append("Content-Disposition: form-data;name=\"media\";filename=\""
    				+ file.getName() + "\"\r\n");
    		sb.append("Content-Type:application/octet-stream\r\n\r\n");
    		byte[] head = sb.toString().getBytes("utf-8");
    		// 获得输出流
    		OutputStream out = new DataOutputStream(con.getOutputStream());
    		// 输出表头
    		out.write(head);
    		// 文件正文部分
    		// 把文件以 流文件 的方式 推入到url中
    		DataInputStream in = new DataInputStream(new FileInputStream(file));
    		int bytes = 0;
    		byte[] bufferOut = new byte[1024];
    		while ((bytes = in.read(bufferOut)) != -1) {
    			out.write(bufferOut, 0, bytes);
    		}
    		in.close();
    		// 结尾部分
    		byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 最数据分隔线
    		out.write(foot);
    
    		out.flush();
    		out.close();
    
    		StringBuffer buffer = new StringBuffer();
    		BufferedReader reader = null;
    		try {
    			// 定义BufferedReader输入流来读取URL的响应
    			reader = new BufferedReader(new InputStreamReader(
    					con.getInputStream()));
    			String line = null;
    			while ((line = reader.readLine()) != null) {
    				buffer.append(line);
    			}
    			if (result == null) {
    				result = buffer.toString();
    			}
    		} catch (IOException e) {
    			System.out.println("发送POST请求出现异常!" + e);
    			e.printStackTrace();
    			throw new IOException("数据读取异常");
    		} finally {
    			if (reader != null) {
    				reader.close();
    			}
    		}
    		return result;
    	}
    
    	public static void main(String[] args) throws IOException {
    		String filePath = "C:/Users/Administrator/Desktop/1.jpg";//本地或服务器文件路径
    		String sendUrl = "http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=image";//ACCESS_TOKEN是获取到的access_token
    		String result = null;
    		FileUpload fileUpload = new FileUpload();
    		result = fileUpload.send(sendUrl, filePath);
    		System.out.println(result);
    
    	}
    }
    输出如下

    {"type":"image","media_id":"1Le_MCBJShdaL1nLirSFRkYYiEXtPTUD_uOk0D9QtNLchWiQ2MHOtkT4Rm6T9Ciy","created_at":1447392539}

    通过获取media_id,这个值也是就是后续的图文素材上传中的 thumb_media_id,这是后话。上传的图片素材是临时素材不占用永久素材容量。

    数据格式如下:

    String openid3data = "{\"touser\":[\"obGXiwHTGN_4HkR2WToFj_3ua\",\"obGXiwNu0z2o_RRWaODvaZctd\"], \"image\": {\"media_id\":\"1Le_MCBJShdaL1nLirSFRkYYiEXtPTUD_uOk0D9QtNLchWiQ2MHOtkT4Rm6T9Ciy\"},\"msgtype\":\"image\"}";
    
    
    消息格式严格如上,可以参照官方文档微信官方文档,可以用JSONObject.from(Objec obj)这个来进行对象转json字符串,具体可以百度,红色字(由于颜色会影响代码美观,所以删除了,字符串中["obGXiwHTGN_4HkR2WToFj_3ua\",\"obGXiwNu0z2o_RRWaODvaZctd\"]是关注当前微信公众号用户的openid
    ③图文消息

    图文消息需要先上传图文消息素材,

    (一)上传素材

    Post接口地址:https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token=ACCESS_TOKEN

    post数据data格式:如是java编写需要注意特殊符号转义,这个就不多讲了,用\这个转义应该都知道。
    {
       "articles": [
    		 {
                            "thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
                            "author":"xxx",
    			 "title":"Happy Day",
    			 "content_source_url":"www.qq.com",
    			 "content":"content",
    			 "digest":"digest",
                            "show_cover_pic":"1"
    		 },
    		 {
                            "thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
                            "author":"xxx",
    			 "title":"Happy Day",
    			 "content_source_url":"www.qq.com",
    			 "content":"content",
    			 "digest":"digest",
                            "show_cover_pic":"0"
    		 }
       ]
    }
    thumb_media_id是上一步中图片消息上传的临时素材media_id,这一部分代码如下:
    import net.sf.json.JSONObject;
    
    public class ImageArticleUpload {
    	public String upload(){
    		String url = "https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token=NkwaEYtrewLfkgMphjwUmnZ0l6vguB794RBhstvHUT58nLk2HMrQ3Ji9EJTD8aOyViteTyV6H1xrVLx2Myee1MUdwz0xEcw0gmlBpf6VkFoMQRfAAAQUV";//ACCESS_TOKEN是获取到的access_token
    		//上传的图文消息数据,其中thumb_media_id是文件上传图片上传的id
    		String data = "{\"articles\": [{\"thumb_media_id\":\"BW4eDIdYSvO7AFjfsZKsQ9ujNma_TkCj3VSo3JNTQkYmk_iPuhpUKm48oZ4umHED\",\"author\":\"xxx\",\"title\":\"Happy Day\",\"content_source_url\":\"www.qq.com\",\"content\":\"content\",\"digest\":\"digest\",\"show_cover_pic\":\"0\"}]}";
    		String data1 = "{\"articles\":[{\"author\":\"王传清|毕强|Wang Chuanqing|Bi Qiang\",\"content\":\"基于关联关系维度的数字资源聚合是数字资源知识发现的重要基础和工具。超网络是由多个类型的同质和异质子网络组成的网络,通过多种关联维度聚合的数字资源即形成了拥有相同以及不同性质的结点和关系的数字资源超网络,这些不同性质的关联与链接是知识关联、挖掘、发现与创新的脉络线索。结合超网络理论,构建和描述数字资源超网络,并分析超网络中不同性质的关系类型,如引用关系、共现关系、耦合关系等,从关联维度探讨数字资源深度聚合的模式,进而分析利用数字资源超网络进行知识发现的具体应用方法,最后构建数字资源超网络聚合系统模型。\",\"content_source_url\":\"http://d.g.wanfangdata.com.cn/Periodical_qbxb201501002.aspx\",\"digest\":\"测试\",\"show_cover_pic\":1,\"thumb_media_id\":\"BW4eDIdYSvO7AFjfsZKsQ9ujNma_TkCj3VSo3JNTQkYmk_iPuhpUKm48oZ4umHED\",\"title\":\"超网络视域下的数字资源深度聚合研究\"}]}";
    		JSONObject json = CommUtil.httpRequest(url, "POST", data1);
    		return json.toString();
    	}
    	public static void main(String[] args) {
    		System.out.println(new ImageArticleUpload().upload());
    	}
    }
    输出如下:

    {"type":"news","media_id":"Sf3S9D8mCM7T5TIwphe3CSAgWnXQzaDnuA7mdgX6SV1JKRGRv9Je3e7AXB8St7yy","created_at":1447394345}

    这个时候获取到的media_id就是我们要进行图文消息群发的media_id

    (二)组装数据:

    数据格式如下:

    String openid4data = "{\"touser\":[\"obGXiwHTGN_4HkR2WToFj_3ua\",\"obGXiwNu0z2o_RRWaODvaZctd\"], \"mpnews\": {\"media_id\":\"Sf3S9D8mCM7T5TIwphe3CSAgWnXQzaDnuA7mdgX6SV1JKRGRv9Je3e7AXB8St7yy\"},\"msgtype\":\"mpnews\"}";

    详情参照官方文档。微信官方文档


    接下来是voice群发,视频群发,操作差不多,官方文档比较垃圾简单的说明了如何处理数据,我就不过多介绍了。


    准备好数据后就是进行群发操作了。

    代码如下:直接拷贝的注意更改token和media_id

    import net.sf.json.JSONObject;
    
    public class SendGroupMessage {
    	public String sendGroupMessage(){
    		String groupUrl = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=ACCESS_TOKEN";//ACCESS_TOKEN是获取到的access_token,根据分组id发群发消息地址
    		String groupUrl1 = "https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token=NkwaEYtrewLfkgMphjwUmnZ0l6vguB794RBhstvHUT58nLk2HMrQ3Ji9EJTD8aOyViteTyV6H1xrVLx2Myee1MUdwz0xEcw0gmlBpf6VkFoMQRfAAAQUV";//根据openid发群发消息地址
    		String group1data = "{\"filter\":{\"is_to_all\":false,\"group_id\":\"2\"},\"text\":{\"content\":\"群发消息测试\"},\"msgtype\":\"text\"}\";";
    		String openid1data = "{\"touser\":[\"obGXiwHTGN_4HkR2WToFj_3uaEKY\",\"obGXiwNu0z2o_RRWaODvaZctdWEM\"],\"msgtype\": \"text\",\"text\": {\"content\": \"测试文本消息\"}}";
    		String openid2data = "{\"touser\":[\"obGXiwHTGN_4HkR2WToFj_3uaEKY\",\"obGXiwNu0z2o_RRWaODvaZctdWEM\"], \"voice\": {\"media_id\":\"UfMRvSiXAD5_iUS8u0Gc3JrKGWOABE9ivQbgrX6i-mVrKGBRL9KnKlioK1BxTPc3\"},\"msgtype\":\"voice\"}";
    		String openid3data = "{\"touser\":[\"obGXiwHTGN_4HkR2WToFj_3uaEKY\",\"obGXiwNu0z2o_RRWaODvaZctdWEM\"], \"image\": {\"media_id\":\"fNUzGbYzTRui4N7-eyx9e3viP8uJuzztAvA32lIdjX4Cucj7mGN_1jpWjn7O80c8\"},\"msgtype\":\"image\"}";
    		String openid4data = "{\"touser\":[\"obGXiwHTGN_4HkR2WToFj_3uaEKY\",\"obGXiwNu0z2o_RRWaODvaZctdWEM\"], \"mpnews\": {\"media_id\":\"6I8DOB-7rJsY_zdOCe6YJKJ59MwXWPb2iYBKVqb22cBHPtECYdRgiWIULfCW-hcF\"},\"msgtype\":\"mpnews\"}";
    		JSONObject json = CommUtil.httpRequest(groupUrl1, "POST", openid4data);
    		return json.toString();
    	}
    	public static void main(String[] args) {
    		System.out.println(new SendGroupMessage().sendGroupMessage());
    	}
    }
    运行代码如下:

    {"errcode":0,"errmsg":"send job submission success","msg_id":2548581905,"msg_data_id":401097527}

    这个时候表示群发成功,大功告成。

    当然,博主不会漏掉那个工具类的代码的,代码如下:

    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.ConnectException;
    import java.net.URL;
    
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    
    import net.sf.json.JSONException;
    import net.sf.json.JSONObject;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import com.pojo.AccessToken;
    import com.pojo.BindPhone;
    import com.pojo.JsapiTicket;
    import com.pojo.Menu;
    import com.thread.TokenThread;
    
    /**
     * 
     * @Description: 公众平台通用接口工具类
     * @Package Name: com.util
     * @ClassName: CommUtil
     * @author heboy
     * @date 2015年9月8日 下午2:21:56
     */
    public class CommUtil {
    	private static Logger log = LoggerFactory.getLogger(CommUtil.class);
    	// 获取access_token的接口地址(GET) 限200(次/天)
    	public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
    	public final static String jsapi_ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
    	
    	/**
    	 * 发起https请求并获取结果
    	 * 
    	 * @param requestUrl 请求地址
    	 * @param requestMethod 请求方式(GET、POST)
    	 * @param outputStr 提交的数据
    	 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
    	 */
    	public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
    		JSONObject jsonObject = null;
    		StringBuffer buffer = new StringBuffer();
    		try {
    			// 创建SSLContext对象,并使用我们指定的信任管理器初始化
    			TrustManager[] tm = { new MyX509TrustManager() };
    			SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
    			sslContext.init(null, tm, new java.security.SecureRandom());
    			// 从上述SSLContext对象中得到SSLSocketFactory对象
    			SSLSocketFactory ssf = sslContext.getSocketFactory();
    
    			URL url = new URL(requestUrl);
    			HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
    			httpUrlConn.setSSLSocketFactory(ssf);
    
    			httpUrlConn.setDoOutput(true);
    			httpUrlConn.setDoInput(true);
    			httpUrlConn.setUseCaches(false);
    			// 设置请求方式(GET/POST)
    			httpUrlConn.setRequestMethod(requestMethod);
    
    			if ("GET".equalsIgnoreCase(requestMethod))
    				httpUrlConn.connect();
    
    			// 当有数据需要提交时
    			if (null != outputStr) {
    				OutputStream outputStream = httpUrlConn.getOutputStream();
    				// 注意编码格式,防止中文乱码
    				outputStream.write(outputStr.getBytes("UTF-8"));
    				outputStream.close();
    			}
    
    			// 将返回的输入流转换成字符串
    			InputStream inputStream = httpUrlConn.getInputStream();
    			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
    			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
    
    			String str = null;
    			while ((str = bufferedReader.readLine()) != null) {
    				buffer.append(str);
    			}
    			bufferedReader.close();
    			inputStreamReader.close();
    			// 释放资源
    			inputStream.close();
    			inputStream = null;
    			httpUrlConn.disconnect();
    			jsonObject = JSONObject.fromObject(buffer.toString());
    		} catch (ConnectException ce) {
    			log.error("Weixin server connection timed out.");
    		} catch (Exception e) {
    			log.error("https request error:{}", e);
    		}
    		return jsonObject;
    	}
    
    	/**
    	 * 
    	 * @Description: 获取access_token
    	 * @param appid 
    	 * @param appsecret 
    	 * @Title: getAccessToken
    	 * @return AccessToken    
    	 * @date: 2015年9月8日 下午2:23:01
    	 * @author heboy
    	 */
    	public static AccessToken getAccessToken(String appid, String appsecret) {
    		AccessToken accessToken = null;
    
    		String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
    		JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
    		// 如果请求成功
    		if (null != jsonObject) {
    			try {
    				accessToken = new AccessToken();
    				accessToken.setToken(jsonObject.getString("access_token"));
    				accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
    			} catch (JSONException e) {
    				accessToken = null;
    				// 获取token失败
    				log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
    			}
    		}
    		return accessToken;
    	}
    	
    	// 菜单创建(POST) 限100(次/天)
    	public static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
    
    	/**
    	 * 创建菜单
    	 * 
    	 * @param menu 菜单实例
    	 * @param accessToken 有效的access_token
    	 * @return 0表示成功,其他值表示失败
    	 */
    	public static int createMenu(Menu menu, String accessToken) {
    		int result = 0;
    
    		// 拼装创建菜单的url
    		String url = menu_create_url.replace("ACCESS_TOKEN", accessToken);
    		// 将菜单对象转换成json字符串
    		String jsonMenu = JSONObject.fromObject(menu).toString();
    		// 调用接口创建菜单
    		JSONObject jsonObject = httpRequest(url, "POST", jsonMenu);
    
    		if (null != jsonObject) {
    			if (0 != jsonObject.getInt("errcode")) {
    				result = jsonObject.getInt("errcode");
    				log.error("创建菜单失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
    			}
    		}
    
    		return result;
    	}
    	/**
    	 * 
    	 * @Description: 获取js票据
    	 * @Title: getJsapiTicket
    	 * @return JsapiTicket    
    	 * @date: 2015年9月11日 上午10:48:07
    	 * @author heboy
    	 */
    	public static JsapiTicket getJsapiTicket(String tocken){
    		JsapiTicket jsapiTicket = null;
    		String requestUrl = jsapi_ticket_url.replace("ACCESS_TOKEN", tocken);
    		JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
    		// 如果请求成功
    		if (null != jsonObject) {
    			try {
    				jsapiTicket = new JsapiTicket();
    				jsapiTicket.setTicket(jsonObject.getString("ticket"));
    				jsapiTicket.setExpiresIn(jsonObject.getInt("expires_in"));
    			} catch (JSONException e) {
    				jsapiTicket = null;
    				// 获取token失败
    				log.error("获取jsapi_ticket失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
    			}
    		}
    		return jsapiTicket;
    	}
    }


    展开全文
  • 微信小程序开发实战第三季 7年以上研发经验,精通java语言、深入研究SSH...

    订阅后:请点击此处观看视频课程

     

    视频教程-微信小程序开发实战第三季-微信开发

    学习有效期:永久观看

    学习时长:200分钟

    学习计划:4天

    难度:

     

    口碑讲师带队学习,让你的问题不过夜」

    讲师姓名:邹积超

    CEO/董事长/总经理

    讲师介绍:7年以上研发经验,精通java语言、深入研究SSH等主流框架,对javaweb开发有深度认识善于编写web框架,对SaaS云模式有深度实践。

    ☛点击立即跟老师学习☚

     

    「你将学到什么?」

    本季完成一个辩论赛计时器APP,涉及到动画使用、声音播放、action-sheet、tabBar、表单元素form、switch、slider、radio-group、button等,涉及到面向对象的编程思路

     

    「课程学习目录」

    第1章:实战第三季
    1.辩论赛计时APP介绍
    2.项目搭建及tabBar实现
    3.业务分析及设置表单设计
    4.面向对象设计实现数据储存
    5.Action-Sheet实现弹出
    6.加载默认设置、缓存读取控制Action-Sheet
    7.ActionSheet事件及占位符正则替换
    8.CSS环形动画原理及实现
    9.JS定时器是实现自动动画
    10.实现数字计时及语音提示

     

    7项超值权益,保障学习质量」

    • 大咖讲解

    技术专家系统讲解传授编程思路与实战。

    • 答疑服务

    专属社群随时沟通与讲师答疑,扫清学习障碍,自学编程不再难。

    • 课程资料+课件

    超实用资料,覆盖核心知识,关键编程技能,方便练习巩固。(部分讲师考虑到版权问题,暂未上传附件,敬请谅解)

    • 常用开发实战

    企业常见开发实战案例,带你掌握Python在工作中的不同运用场景。

    • 大牛技术大会视频

    2019Python开发者大会视频免费观看,送你一个近距离感受互联网大佬的机会。

    • APP+PC随时随地学习

    满足不同场景,开发编程语言系统学习需求,不受空间、地域限制。

     

    「什么样的技术人适合学习?」

    • 想进入互联网技术行业,但是面对多门编程语言不知如何选择,0基础的你
    • 掌握开发、编程技术单一、冷门,迫切希望能够转型的你
    • 想进入大厂,但是编程经验不够丰富,没有竞争力,程序员找工作难。

     

    「悉心打造精品好课,4天学到大牛3年项目经验」

    【完善的技术体系】

    技术成长循序渐进,帮助用户轻松掌握

    掌握微信开发知识,扎实编码能力

    【清晰的课程脉络】

    浓缩大牛多年经验,全方位构建出系统化的技术知识脉络,同时注重实战操作。

    【仿佛在大厂实习般的课程设计】

    课程内容全面提升技术能力,系统学习大厂技术方法论,可复用在日后工作中。

     

    「你可以收获什么?」

    通过实战开发掌握组件和接口的使用,了解微信小程序开发的整个流程,以达到自主开发的目的

     

    展开全文
  • 微信开发流程(一)

    2019-08-12 03:37:54
    因为微信公众号,订阅号,服务号,是否认证原因,接口权限不同,所以使用微信测试号可以测试使用大多数的接口实现功能,和学习微信开发的相关知识。 1.测试号信息,appID和appsecret是获取验证的重要凭证 2.接口...
  • 微信公众账号开发

    2014-02-27 15:00:52
    微信开发 微信公众平台开发(82) 天气预报 摘要: 在这篇教程中,我们将介绍如何在微信公众平台上开发天气预报功能。我们将使用中国天气网的气象数据接口来获取天气信息。这篇教程将介绍以下内容:获取...
  • 微信公众平台开发

    2014-01-18 11:18:26
    微信公众平台开发(80) 上传下载多媒体文件 摘要: 微信公众账号在回复图片、语音、视频的时候,将使用media_id来调用相关文件,很多朋友咨询这个如何开发实现。本文将介绍在微信公众平台开发过程中,如何上传...
  • 坐拥7亿日活的微信极其成功,有人说微信的成功在于赛道的成功,然而即便把微信和国际上其他地区的同类应用WhatsApp、Line等相比,微信所取得的成绩依然鹤立鸡群,不仅因为其庞大的用户量,更因为微信枝繁叶茂的生态...
  • 微信小游戏开发

    2018-12-14 10:58:51
    目录 知识收集:  1.资源文件导入  2.this.addChild(circle2)  3.层级关系  4.多次添加显示对象到显示列表  5.删除操作的注意点 ... 6.容器深度相关 ...4)交换深度 ...5)设置深度(自带容...
  • *说明:普通红包是指金额每份金额固定的红包包括群普通红包和个人普通红包,个人普通红包也就是红包个数为1的群普通红包。1 需求分析一个字:钱;两个字:消遣1.1用户为什么要发红包?(1)逗别人玩自己开心有些人...
  • 本文分析基于微信硬件平台的物联网架构,将从物联网的核心要素、物联网的关键场景、微信硬件平台的通信协议分析三个维度去分析。更多的微信硬件平台开发深度技术原创分享请订阅微信公众号:嵌入式企鹅圈。 微信...
  • 智慧商业服务提供商微盟近日发布行业首份《2018微信小程序行业应用发展研究报告》,从市场环境、行业背景、商户调研等维度深度剖析了零售、电商、餐饮及生活服务四大行业应用微信小程序的现况,并对小程序未来发展趋势...
  • 导读 | 特殊时期,很多企业都在思考一个问题——如何将线下迁移线上?而微信发布的全新企业微信3.0,提出对内让信息流转高效,对外连接11亿微信用户。那么,如何在企业微信上创建一个连接客...
  • 笔者发现很多刚入门的微信公众号从业者对微信没有一个基本的了解,都是自己一步一步误打误撞的走过来。而现在很多文章没有那么全面的介绍微信运营的基础建设,所以这篇文章就会将微信公众号的基础建设与规划做一个...
  • 责编:陈秋歌,关注微信开发等领域,寻求报道或者投稿请发邮件chenqg#csdn.net。研发心得、项目实战、前沿技术、外文翻译……,只要是技术干货,十分欢迎投稿至chenqg#csdn.net。人人都是主编,这里就是你的舞台。 ...
  • 由于笔者没有获取到微信官方提供的小程序实现原理图,很多内容都是通过阅读文档资料反推和理解所得,如有误解之处,望指正。本文建议阅读时间: 5min 目录 小程序SDK 定义 JS-SDK 小程序基础库与JS-SDK的共同点 小...
  • 每一个企业级的人 都置顶了 中国软件网中国软件网 为...企业进行企业微信微信互通功能正式开放内测,本次内测将支持企业微信用户和微信用户互相添加为好友,并支持单聊消息互通;还支持企业使用企业微信提供的API接口
  • 全文阅读时间:12分钟前言微信拍一拍功能上线之后,其用户评价褒贬不一,这里我们不去凑热闹讨论这个功能的应用场景是否真的如部分网友说的毫无卵用,毕竟这是产品经理们应该考虑的事,但我相信作为...
1 2 3 4 5 ... 20
收藏数 7,709
精华内容 3,083