百度wwW33xxjj我是杀毒软件,点回车了33xxjjcom怎么出现

微信公众账号开发教程
时间: 08:30:49
&&&& 阅读:1605
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&& & & &微信公众账号开发教程
本文转载来自柳峰老师的博客,在这里非常感谢柳峰老师的分享和贡献!
内容方面,大概会涉及到:
1)前沿知识:微信公众帐号的分类、两种模式各自的特点和区别、开发模式的配置使用等;
2)API中各类消息的使用(我已经对api进行封装并打成了jar包,到时候会考虑分享出来);
3)微信公众帐号开发中的小技巧(如换行、通过代码发送表情、屏幕飘雪花、表情的接收识别、在和上表现不一致等等);
4)与业务系统对接的方法(链接、短信等,除了技术讲解还会做一定的分析对比);
5)微信公众平台上常见功能的开发(如像小黄鸡那样的人机对话、天气预报、精确的定位及百度地图的使用、音乐搜索、语音识别解析等)
在微信公众平台开发前首先需要注册微信公众号,微信公众账号类型分为以下几种:
&& A、订阅号:主要偏于为用户传达咨询,认证前后每天可群发一条消息。可以适用于个人和企业。
&& B、服务号:主要偏于服务交互(类似银行、114、提供服务查询),认证前后每月可群发4条消息。适用于企业。
&& C、企业号:主要用于公司内部通讯使用,要先有成员通讯信息验证才可以关注成功企业号。适用于企业。
&& D、小程序:适合有服务内容的企业和组织注册。
注:注册公众号时,微信号必须绑定本人银行卡
编辑模式与开发模式
编辑模式:主要针对非编程人员及信息发布类公众帐号使用。开启该模式后,可以方便地通过界面配置“自定义菜单”和“自动回复的消息”。
开发模式:主要针对具备开发能力的人使用。开启该模式后,能够使用微信公众平台开放的接口,通过编程方式实现自定义菜单的创建、用户消息的接收/处理/响应。这种模式更加灵活,建议有开发能力的公司或个人都采用该模式。
启用开发模式(上)
微信公众帐号注册完成后,默认开启的是编辑模式。那么该如何开启开发模式呢?操作步骤如下:
1)登录公众号,进入公众平台页面,选择“开发”--“基本配置”,进入如下图的页面,点击“我同意”成为开发者。
点击同意成为开发者之后,跳转到如下页面:
2)修改服务器配置,启用开发模式
成为开发者之后,点击“修改配置”按钮,填写服务器地址(URL,URL指的是能够接收处理微信服务器发送的GET/POST请求的地址,并且是已经存在的,现在就能够在浏览器访问到的地址,这就要求我们先把公众帐号后台处理程序开发好(至少应该完成了对GET请求的处理)并部署在公网服务器上。另外,这里的URL必须以http或https开头,分别支持80端口和443端口。)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性。两个token要保持一致。)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下图所示:
上面写的很清楚,其实你只要能理解上面在说什么就OK了,至于怎么编写相关代码,请继续往下看。
创建公众帐号后台接口程序
创建一个Web工程,并新建一个能够处理请求的Servlet,命名任意,我在这里将其命名为org.liufeng.course.servlet.CoreServlet,代码如下:
package&org.liufeng.course.&&
import&java.io.IOE&&
import&java.io.PrintW&&
import&javax.servlet.ServletE&&
import&javax.servlet.http.HttpS&&
import&javax.servlet.http.HttpServletR&&
import&javax.servlet.http.HttpServletR&&
import&org.liufeng.course.util.SignU&&
&*&核心请求处理类&
&*&@author&liufeng&
&*&@date&&
public&class&CoreServlet&extends&HttpServlet&{&&
&&&&private&static&final&long&serialVersionUID&=&4821986L;&&
&&&&&*&确认请求来自微信服务器&
&&&&public&void&doGet(HttpServletRequest&request,&HttpServletResponse&response)&throws&ServletException,&IOException&{&&
&&&&&&&&//&微信加密签名&&
&&&&&&&&String&signature&=&request.getParameter("signature");&&
&&&&&&&&//&时间戳&&
&&&&&&&&String&timestamp&=&request.getParameter("timestamp");&&
&&&&&&&&//&随机数&&
&&&&&&&&String&nonce&=&request.getParameter("nonce");&&
&&&&&&&&//&随机字符串&&
&&&&&&&&String&echostr&=&request.getParameter("echostr");&&
&&&&&&&&PrintWriter&out&=&response.getWriter();&&
&&&&&&&&//&通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败&&
&&&&&&&&if&(SignUtil.checkSignature(signature,&timestamp,&nonce))&{&&
&&&&&&&&&&&&out.print(echostr);&&
&&&&&&&&}&&
&&&&&&&&out.close();&&
&&&&&&&&out&=&&&
&&&&&*&处理微信服务器发来的消息&
&&&&public&void&doPost(HttpServletRequest&request,&HttpServletResponse&response)&throws&ServletException,&IOException&{&&
&&&&&&&&//&TODO&消息的接收、处理、响应&&
可以看到,代码中只完成了doGet方法,它的作用正是确认请求是否来自于微信服务器;而doPost方法不是我们这次要讲的内容,并且完成接口配置也不需要管doPost方法,就先空在那里。
在doGet方法中调用了org.liufeng.course.util.SignUtil.checkSignature方法,SignUtil.的实现如下:
package&org.liufeng.course.&&
import&java.security.MessageD&&
import&java.security.NoSuchAlgorithmE&&
import&java.util.A&&
&*&请求校验工具类&
&*&@author&liufeng&
&*&@date&&
public&class&SignUtil&{&&
&&&&//&与接口配置信息中的Token要一致&&
&&&&private&static&String&token&=&"weixinCourse";&&
&&&&&*&验证签名&
&&&&&*&@param&signature&
&&&&&*&@param&timestamp&
&&&&&*&@param&nonce&
&&&&&*&@return&
&&&&public&static&boolean&checkSignature(String&signature,&String&timestamp,&String&nonce)&{&&
&&&&&&&&String[]&arr&=&new&String[]&{&token,&timestamp,&nonce&};&&
&&&&&&&&//&将token、timestamp、nonce三个参数进行字典序排序&&
&&&&&&&&Arrays.sort(arr);&&
&&&&&&&&StringBuilder&content&=&new&StringBuilder();&&
&&&&&&&&for&(int&i&=&0;&i&&&arr.&i++)&{&&
&&&&&&&&&&&&content.append(arr[i]);&&
&&&&&&&&}&&
&&&&&&&&MessageDigest&md&=&&&
&&&&&&&&String&tmpStr&=&&&
&&&&&&&&try&{&&
&&&&&&&&&&&&md&=&MessageDigest.getInstance("SHA-1");&&
&&&&&&&&&&&&//&将三个参数字符串拼接成一个字符串进行sha1加密&&
&&&&&&&&&&&&byte[]&digest&=&md.digest(content.toString().getBytes());&&
&&&&&&&&&&&&tmpStr&=&byteToStr(digest);&&
&&&&&&&&}&catch&(NoSuchAlgorithmException&e)&{&&
&&&&&&&&&&&&e.printStackTrace();&&
&&&&&&&&}&&
&&&&&&&&content&=&&&
&&&&&&&&//&将sha1加密后的字符串可与signature对比,标识该请求来源于微信&&
&&&&&&&&return&tmpStr&!=&null&?&tmpStr.equals(signature.toUpperCase())&:&&&
&&&&&*&将字节数组转换为十六进制字符串&
&&&&&*&@param&byteArray&
&&&&&*&@return&
&&&&private&static&String&byteToStr(byte[]&byteArray)&{&&
&&&&&&&&String&strDigest&=&"";&&
&&&&&&&&for&(int&i&=&0;&i&&&byteArray.&i++)&{&&
&&&&&&&&&&&&strDigest&+=&byteToHexStr(byteArray[i]);&&
&&&&&&&&}&&
&&&&&&&&return&strD&&
&&&&&*&将字节转换为十六进制字符串&
&&&&&*&@param&mByte&
&&&&&*&@return&
&&&&private&static&String&byteToHexStr(byte&mByte)&{&&
&&&&&&&&char[]&Digit&=&{&‘0‘,&‘1‘,&‘2‘,&‘3‘,&‘4‘,&‘5‘,&‘6‘,&‘7‘,&‘8‘,&‘9‘,&‘A‘,&‘B‘,&‘C‘,&‘D‘,&‘E‘,&‘F‘&};&&
&&&&&&&&char[]&tempArr&=&new&char[2];&&
&&&&&&&&tempArr[0]&=&Digit[(mByte&&&&&4)&&&0X0F];&&
&&&&&&&&tempArr[1]&=&Digit[mByte&&&0X0F];&&
&&&&&&&&String&s&=&new&String(tempArr);&&
&&&&&&&&return&s;&&
这里唯一需要注意的就是SignUtil类中的成员变量token,这里赋予什么值,在接口配置信息中的Token就要填写什么值,两边保持一致即可,没有其他要求,建议用项目名称、公司名称缩写等,我在这里用的是项目名称weixinCourse。
最后再来看一下web.xml中,CoreServlet是怎么配置的,web.xml中的配置代码如下:
&?xml&version="1.0"&encoding="UTF-8"?&&&
&web-app&version="2.5"&xmlns="http://java.sun.com/xml/ns/javaee"&&
&&&&xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&&
&&&&xsi:schemaLocation="http://java.sun.com/xml/ns/javaee&&&
&&&&http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"&&&
&&&&&servlet&&&
&&&&&&&&&servlet-name&coreServlet&/servlet-name&&&
&&&&&&&&&servlet-class&&&
&&&&&&&&&&&&org.liufeng.course.servlet.CoreServlet&&
&&&&&&&&&/servlet-class&&&
&&&&&/servlet&&&
&&&&&!--&url-pattern中配置的/coreServlet用于指定该Servlet的访问路径&--&&&
&&&&&servlet-mapping&&&
&&&&&&&&&servlet-name&coreServlet&/servlet-name&&&
&&&&&&&&&url-pattern&/coreServlet&/url-pattern&&&
&&&&&/servlet-mapping&&&
&&&&&welcome-file-list&&&
&&&&&&&&&welcome-file&index.jsp&/welcome-file&&&
&&&&&/welcome-file-list&&&
&/web-app&&&
到这里,所有编码都完成了,就是这么简单。接下来就是将工程发布到公网服务器上,如果没有公网服务器环境,可以去了解下花生壳。发布到服务器上后,我们在浏览器里访问CoreServlet,如果看到如下界面就表示我们的代码没有问题:
啊,代码都报空指针异常了还说证明没问题?那当然了,因为直接在地址栏访问coreServlet,就相当于提交的是GET请求,而我们什么参数都没有传,在验证的时候当然会报空指针异常。
接下来,把coreServlet的访问路径拷贝下来,再回到微信公众平台的接入配置信息界面,将coreServlet的访问路径粘贴到URL中,并将SignUtil类中指定的token值weixinCourse填入到Token中,填写后的结果如下图所示:
接着点击“提交”,如果程序写的没问题,并且URL、Token都填写正确,可以在页面最上方看到“提交成功”的提示。
启用开发模式(下)
这个时候就已经成为开发者了,百般周折啊,哈哈,到这里还没有完哦,还有最后一步工作就是将开发模式开启。点击右上角的“启用”按钮,如下图所示:
提示“操作成功”,即表示已成功开启开发模式。
到这里,接口配置、开发模式的开启就都完成了,本章节的内容也就讲到这里。接下来要章节要讲的就是如何接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息,也就是完成CoreServlet中doPost方法的编写。
工欲善其事必先利其器!本篇内容主要讲解如何将公众平台定义的消息及消息相关的操作封装成工具类,方面后期的使用。这里需要明确的是消息其实是由用户发给你的公众帐号的,消息先被微信平台接收到,然后微信平台会将该消息转给你在开发模式接口配置中指定的URL地址。
微信公众平台消息接口
要接收微信平台发送的消息,我们需要先熟悉微信公众平台API中消息接口部分,,点击后将进入到消息接口指南部分,如下图所示:
在上图左侧可以看到微信公众平台目前开放的接口有三种:消息接口、通用接口和自定义菜单接口。通用接口和自定义菜单接口只有拿到内测资格才能调用,而内测资格的申请也已经关闭了,我们只有期待将来某一天微信会对大众用户开放吧,所以没有内测资格的用户就不要再浪费时间在这两个接口上,只需要用好消息接口就可以了。
消息推送和消息回复
下面将主要介绍消息接口。对于消息的接收、响应我们只需要关注上图中的“4 消息推送”和“5 消息回复”就足够了。
我们先来了解接口中的“消息推送”指的是什么,点击“4 消息推送”,可以看到接口中的“消息推送”指的是“当普通用户向公众帐号发消息时,微信服务器将POST该消息到填写的URL上”,即这里定义的是用户能够发送哪些类型的消息、消息有哪些字段、消息被微信服务器以什么方式转发给我们的公众帐号后台。
消息推送中定义了我们将会接收到的消息类型有5种:文本消息、图片消息、地理位置消息、链接消息和事件推送,其实语音消息我们也能够接收到的,只不过拿不到具体的语音文件而以(需要内测资格才能够获取语音文件)。
接口中的“消息回复”定义了我们能回复给用户的消息类型、消息字段和消息格式,微信公众平台的接口指南中是这样描述的:
上面说到我们能回复给用户的消息有5种,但目前在开发模式下能回复的消息只有3种:文本消息、音乐消息和图文消息,而语音消息和视频消息目前只能在编辑模式下使用。
消息的封装
接下来要做的就是将消息推送(请求)、消息回复(响应)中定义的消息进行封装,建立与之对应的类(Java是一门面向对象的编程语言,封装后使用起来更方便),下面的请求消息是指消息推送中定义的消息,响应消息指消息回复中定义的消息。
请求消息的基类
把消息推送中定义的所有消息都有的字段提取出来,封装成一个基类,这些公有的字段包括:ToUserName(开发者微信号)、FromUserName(发送方帐号,OPEN_ID)、CreateTime(消息的创建时间)、MsgType(消息类型)、MsgId(消息ID),封装后基类org.liufeng.course.message.req.BaseMessage的代码如下:
package&org.liufeng.course.message.&&
&*&消息基类(普通用户&-&&公众帐号)&
&*&@author&liufeng&
&*&@date&&
public&class&BaseMessage&{&&
&&&&//&开发者微信号&&
&&&&private&String&ToUserN&&
&&&&//&发送方帐号(一个OpenID)&&
&&&&private&String&FromUserN&&
&&&&//&消息创建时间&(整型)&&
&&&&private&long&CreateT&&
&&&&//&消息类型(text/image/location/link)&&
&&&&private&String&MsgT&&
&&&&//&消息id,64位整型&&
&&&&private&long&MsgId;&&
&&&&public&String&getToUserName()&{&&
&&&&&&&&return&ToUserN&&
&&&&public&void&setToUserName(String&toUserName)&{&&
&&&&&&&&ToUserName&=&toUserN&&
&&&&public&String&getFromUserName()&{&&
&&&&&&&&return&FromUserN&&
&&&&public&void&setFromUserName(String&fromUserName)&{&&
&&&&&&&&FromUserName&=&fromUserN&&
&&&&public&long&getCreateTime()&{&&
&&&&&&&&return&CreateT&&
&&&&public&void&setCreateTime(long&createTime)&{&&
&&&&&&&&CreateTime&=&createT&&
&&&&public&String&getMsgType()&{&&
&&&&&&&&return&MsgT&&
&&&&public&void&setMsgType(String&msgType)&{&&
&&&&&&&&MsgType&=&msgT&&
&&&&public&long&getMsgId()&{&&
&&&&&&&&return&MsgId;&&
&&&&public&void&setMsgId(long&msgId)&{&&
&&&&&&&&MsgId&=&msgId;&&
请求消息之文本消息
package&org.liufeng.course.message.&&
&*&文本消息&
&*&@author&liufeng&
&*&@date&&
public&class&TextMessage&extends&BaseMessage&{&&
&&&&//&消息内容&&
&&&&private&String&C&&
&&&&public&String&getContent()&{&&
&&&&&&&&return&C&&
&&&&public&void&setContent(String&content)&{&&
&&&&&&&&Content&=&&&
请求消息之图片消息
package&org.liufeng.course.message.&&
&*&图片消息&
&*&@author&liufeng&
&*&@date&&
public&class&ImageMessage&extends&BaseMessage&{&&
&&&&//&图片链接&&
&&&&private&String&PicU&&
&&&&public&String&getPicUrl()&{&&
&&&&&&&&return&PicU&&
&&&&public&void&setPicUrl(String&picUrl)&{&&
&&&&&&&&PicUrl&=&picU&&
请求消息之地理位置消息
package&org.liufeng.course.message.&&
&*&地理位置消息&
&*&@author&liufeng&
&*&@date&&
public&class&LocationMessage&extends&BaseMessage&{&&
&&&&//&地理位置维度&&
&&&&private&String&Location_X;&&
&&&&//&地理位置经度&&
&&&&private&String&Location_Y;&&
&&&&//&地图缩放大小&&
&&&&private&String&S&&
&&&&//&地理位置信息&&
&&&&private&String&L&&
&&&&public&String&getLocation_X()&{&&
&&&&&&&&return&Location_X;&&
&&&&public&void&setLocation_X(String&location_X)&{&&
&&&&&&&&Location_X&=&location_X;&&
&&&&public&String&getLocation_Y()&{&&
&&&&&&&&return&Location_Y;&&
&&&&public&void&setLocation_Y(String&location_Y)&{&&
&&&&&&&&Location_Y&=&location_Y;&&
&&&&public&String&getScale()&{&&
&&&&&&&&return&S&&
&&&&public&void&setScale(String&scale)&{&&
&&&&&&&&Scale&=&&&
&&&&public&String&getLabel()&{&&
&&&&&&&&return&L&&
&&&&public&void&setLabel(String&label)&{&&
&&&&&&&&Label&=&&&
请求消息之链接消息
package&org.liufeng.course.message.&&
&*&链接消息&
&*&@author&liufeng&
&*&@date&&
public&class&LinkMessage&extends&BaseMessage&{&&
&&&&//&消息标题&&
&&&&private&String&T&&
&&&&//&消息描述&&
&&&&private&String&D&&
&&&&//&消息链接&&
&&&&private&String&U&&
&&&&public&String&getTitle()&{&&
&&&&&&&&return&T&&
&&&&public&void&setTitle(String&title)&{&&
&&&&&&&&Title&=&&&
&&&&public&String&getDescription()&{&&
&&&&&&&&return&D&&
&&&&public&void&setDescription(String&description)&{&&
&&&&&&&&Description&=&&&
&&&&public&String&getUrl()&{&&
&&&&&&&&return&U&&
&&&&public&void&setUrl(String&url)&{&&
&&&&&&&&Url&=&&&
请求消息之语音消息
package&org.liufeng.course.message.&&
&*&音频消息&
&*&@author&liufeng&
&*&@date&&
public&class&VoiceMessage&extends&BaseMessage&{&&
&&&&//&媒体ID&&
&&&&private&String&MediaId;&&
&&&&//&语音格式&&
&&&&private&String&F&&
&&&&public&String&getMediaId()&{&&
&&&&&&&&return&MediaId;&&
&&&&public&void&setMediaId(String&mediaId)&{&&
&&&&&&&&MediaId&=&mediaId;&&
&&&&public&String&getFormat()&{&&
&&&&&&&&return&F&&
&&&&public&void&setFormat(String&format)&{&&
&&&&&&&&Format&=&&&
响应消息的基类
同样,把消息回复中定义的所有消息都有的字段提取出来,封装成一个基类,这些公有的字段包括:ToUserName(接收方帐号,用户的OPEN_ID)、FromUserName(开发者的微信号)、CreateTime(消息的创建时间)、MsgType(消息类型)、FuncFlag(消息的星标标识),封装后基类org.liufeng.course.message.resp.BaseMessage的代码如下:
package&org.liufeng.course.message.&&
&*&消息基类(公众帐号&-&&普通用户)&
&*&@author&liufeng&
&*&@date&&
public&class&BaseMessage&{&&
&&&&//&接收方帐号(收到的OpenID)&&
&&&&private&String&ToUserN&&
&&&&//&开发者微信号&&
&&&&private&String&FromUserN&&
&&&&//&消息创建时间&(整型)&&
&&&&private&long&CreateT&&
&&&&//&消息类型(text/music/news)&&
&&&&private&String&MsgT&&
&&&&//&位0x0001被标志时,星标刚收到的消息&&
&&&&private&int&FuncF&&
&&&&public&String&getToUserName()&{&&
&&&&&&&&return&ToUserN&&
&&&&public&void&setToUserName(String&toUserName)&{&&
&&&&&&&&ToUserName&=&toUserN&&
&&&&public&String&getFromUserName()&{&&
&&&&&&&&return&FromUserN&&
&&&&public&void&setFromUserName(String&fromUserName)&{&&
&&&&&&&&FromUserName&=&fromUserN&&
&&&&public&long&getCreateTime()&{&&
&&&&&&&&return&CreateT&&
&&&&public&void&setCreateTime(long&createTime)&{&&
&&&&&&&&CreateTime&=&createT&&
&&&&public&String&getMsgType()&{&&
&&&&&&&&return&MsgT&&
&&&&public&void&setMsgType(String&msgType)&{&&
&&&&&&&&MsgType&=&msgT&&
&&&&public&int&getFuncFlag()&{&&
&&&&&&&&return&FuncF&&
&&&&public&void&setFuncFlag(int&funcFlag)&{&&
&&&&&&&&FuncFlag&=&funcF&&
响应消息之文本消息
package&org.liufeng.course.message.&&
&*&文本消息&
&*&@author&liufeng&
&*&@date&&
public&class&TextMessage&extends&BaseMessage&{&&
&&&&//&回复的消息内容&&
&&&&private&String&C&&
&&&&public&String&getContent()&{&&
&&&&&&&&return&C&&
&&&&public&void&setContent(String&content)&{&&
&&&&&&&&Content&=&&&
响应消息之音乐消息
package&org.liufeng.course.message.&&
&*&音乐消息&
&*&@author&liufeng&
&*&@date&&
public&class&MusicMessage&extends&BaseMessage&{&&
&&&&//&音乐&&
&&&&private&Music&M&&
&&&&public&Music&getMusic()&{&&
&&&&&&&&return&M&&
&&&&public&void&setMusic(Music&music)&{&&
&&&&&&&&Music&=&&&
音乐消息中Music类的定义
package&org.liufeng.course.message.&&
&*&音乐model&
&*&@author&liufeng&
&*&@date&&
public&class&Music&{&&
&&&&//&音乐名称&&
&&&&private&String&T&&
&&&&//&音乐描述&&
&&&&private&String&D&&
&&&&//&音乐链接&&
&&&&private&String&MusicU&&
&&&&//&高质量音乐链接,WIFI环境优先使用该链接播放音乐&&
&&&&private&String&HQMusicU&&
&&&&public&String&getTitle()&{&&
&&&&&&&&return&T&&
&&&&public&void&setTitle(String&title)&{&&
&&&&&&&&Title&=&&&
&&&&public&String&getDescription()&{&&
&&&&&&&&return&D&&
&&&&public&void&setDescription(String&description)&{&&
&&&&&&&&Description&=&&&
&&&&public&String&getMusicUrl()&{&&
&&&&&&&&return&MusicU&&
&&&&public&void&setMusicUrl(String&musicUrl)&{&&
&&&&&&&&MusicUrl&=&musicU&&
&&&&public&String&getHQMusicUrl()&{&&
&&&&&&&&return&HQMusicU&&
&&&&public&void&setHQMusicUrl(String&musicUrl)&{&&
&&&&&&&&HQMusicUrl&=&musicU&&
响应消息之图文消息
package&org.liufeng.course.message.&&
import&java.util.L&&
&*&文本消息&
&*&@author&liufeng&
&*&@date&&
public&class&NewsMessage&extends&BaseMessage&{&&
&&&&//&图文消息个数,限制为10条以内&&
&&&&private&int&ArticleC&&
&&&&//&多条图文消息信息,默认第一个item为大图&&
&&&&private&List&Article&&A&&
&&&&public&int&getArticleCount()&{&&
&&&&&&&&return&ArticleC&&
&&&&public&void&setArticleCount(int&articleCount)&{&&
&&&&&&&&ArticleCount&=&articleC&&
&&&&public&List&Article&&getArticles()&{&&
&&&&&&&&return&A&&
&&&&public&void&setArticles(List&Article&&articles)&{&&
&&&&&&&&Articles&=&&&
图文消息中Article类的定义
package&org.liufeng.course.message.&&
&*&图文model&
&*&@author&liufeng&
&*&@date&&
public&class&Article&{&&
&&&&//&图文消息名称&&
&&&&private&String&T&&
&&&&//&图文消息描述&&
&&&&private&String&D&&
&&&&//&图片链接,支持JPG、PNG格式,较好的效果为大图640*320,小图80*80,限制图片链接的域名需要与开发者填写的基本资料中的Url一致&&
&&&&private&String&PicU&&
&&&&//&点击图文消息跳转链接&&
&&&&private&String&U&&
&&&&public&String&getTitle()&{&&
&&&&&&&&return&T&&
&&&&public&void&setTitle(String&title)&{&&
&&&&&&&&Title&=&&&
&&&&public&String&getDescription()&{&&
&&&&&&&&return&null&==&Description&?&""&:&D&&
&&&&public&void&setDescription(String&description)&{&&
&&&&&&&&Description&=&&&
&&&&public&String&getPicUrl()&{&&
&&&&&&&&return&null&==&PicUrl&?&""&:&PicU&&
&&&&public&void&setPicUrl(String&picUrl)&{&&
&&&&&&&&PicUrl&=&picU&&
&&&&public&String&getUrl()&{&&
&&&&&&&&return&null&==&Url&?&""&:&U&&
&&&&public&void&setUrl(String&url)&{&&
&&&&&&&&Url&=&&&
全部消息封装完成后,Eclipse工程中关于消息部分的结构应该与下图保持一致,如果不一致的(类名、属性名称不一致的)请检查后调整一致,因为后面的章节还要介绍如何将中通用的类方法、与业务无关的工具类封装打成jar包,以后再做微信项目只需要引入该jar包即可,这种工作做一次就可以了。
如何解析请求消息?
接下来解决请求消息的解析问题。微信服务器会将用户的请求通过doPost方法发送给我们,让我们再来回顾下上一章节已经写好的doPost方法的定义:
&&&&*&处理微信服务器发来的消息&&
&&&&*/&&&&
&&&public&void&doPost(HttpServletRequest&request,&HttpServletResponse&response)&throws&ServletException,&IOException&{&&&&
&&&&&&&//&TODO&消息的接收、处理、响应&&&&
doPost方法有两个参数,request中封装了请求相关的所有内容,可以从request中取出微信服务器发来的消息;而通过response我们可以对接收到的消息进行响应,即发送消息。
那么如何解析请求消息的问题也就转化为如何从request中得到微信服务器发送给我们的xml格式的消息了。这里我们借助于开源框架dom4j去解析xml(这里使用的是dom4j-1.6.1.jar),然后将解析得到的结果存入HashMap,解析请求消息的方法如下:
&*&解析微信发来的请求(XML)&
&*&@param&request&
&*&@return&
&*&@throws&Exception&
@SuppressWarnings("unchecked")&&
public&static&Map&String,&String&&parseXml(HttpServletRequest&request)&throws&Exception&{&&
&&&&//&将解析结果存储在HashMap中&&
&&&&Map&String,&String&&map&=&new&HashMap&String,&String&();&&
&&&&//&从request中取得输入流&&
&&&&InputStream&inputStream&=&request.getInputStream();&&
&&&&//&读取输入流&&
&&&&SAXReader&reader&=&new&SAXReader();&&
&&&&Document&document&=&reader.read(inputStream);&&
&&&&//&得到xml根元素&&
&&&&Element&root&=&document.getRootElement();&&
&&&&//&得到根元素的所有子节点&&
&&&&List&Element&&elementList&=&root.elements();&&
&&&&//&遍历所有子节点&&
&&&&for&(Element&e&:&elementList)&&
&&&&&&&&map.put(e.getName(),&e.getText());&&
&&&&//&释放资源&&
&&&&inputStream.close();&&
&&&&inputStream&=&&&
&&&&return&&&
如何将响应消息转换成xml返回?
我们先前已经将响应消息封装成了Java类,方便我们在代码中使用。那么,请求接收成功、处理完成后,该如何将消息返回呢?这里就涉及到如何将响应消息转换成xml返回的问题,这里我们将采用开源框架xstream来实现Java类到xml的转换(这里使用的是xstream-1.3.1.jar),代码如下:
&*&文本消息对象转换成xml&
&*&@param&textMessage&文本消息对象&
&*&@return&xml&
public&static&String&textMessageToXml(TextMessage&textMessage)&{&&
&&&&xstream.alias("xml",&textMessage.getClass());&&
&&&&return&xstream.toXML(textMessage);&&
&*&音乐消息对象转换成xml&
&*&@param&musicMessage&音乐消息对象&
&*&@return&xml&
public&static&String&musicMessageToXml(MusicMessage&musicMessage)&{&&
&&&&xstream.alias("xml",&musicMessage.getClass());&&
&&&&return&xstream.toXML(musicMessage);&&
&*&图文消息对象转换成xml&
&*&@param&newsMessage&图文消息对象&
&*&@return&xml&
public&static&String&newsMessageToXml(NewsMessage&newsMessage)&{&&
&&&&xstream.alias("xml",&newsMessage.getClass());&&
&&&&xstream.alias("item",&new&Article().getClass());&&
&&&&return&xstream.toXML(newsMessage);&&
&*&扩展xstream,使其支持CDATA块&
&*&@date&&
private&static&XStream&xstream&=&new&XStream(new&XppDriver()&{&&
&&&&public&HierarchicalStreamWriter&createWriter(Writer&out)&{&&
&&&&&&&&return&new&PrettyPrintWriter(out)&{&&
&&&&&&&&&&&&//&对所有xml节点的转换都增加CDATA标记&&
&&&&&&&&&&&&boolean&cdata&=&&&
&&&&&&&&&&&&@SuppressWarnings("unchecked")&&
&&&&&&&&&&&&public&void&startNode(String&name,&Class&clazz)&{&&
&&&&&&&&&&&&&&&&super.startNode(name,&clazz);&&
&&&&&&&&&&&&}&&
&&&&&&&&&&&&protected&void&writeText(QuickWriter&writer,&String&text)&{&&
&&&&&&&&&&&&&&&&if&(cdata)&{&&
&&&&&&&&&&&&&&&&&&&&writer.write("&![CDATA[");&&
&&&&&&&&&&&&&&&&&&&&writer.write(text);&&
&&&&&&&&&&&&&&&&&&&&writer.write("]]&");&&
&&&&&&&&&&&&&&&&}&else&{&&
&&&&&&&&&&&&&&&&&&&&writer.write(text);&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&}&&
&&&&&&&&};&&
说明:由于xstream框架本身并不支持CDATA块的生成,40~62行代码是对xtream做了扩展,使其支持在生成xml各元素值时添加CDATA块。
消息处理工具的封装
知道怎么解析请求消息,也知道如何将响应消息转化成xml了,接下来就是将消息相关的处理方法全部封装到工具类MessageUtil中,该类的完整代码如下:
package&org.liufeng.course.&&
import&java.io.InputS&&
import&java.io.W&&
import&java.util.HashM&&
import&java.util.L&&
import&java.util.M&&
import&javax.servlet.http.HttpServletR&&
import&org.dom4j.D&&
import&org.dom4j.E&&
import&org.dom4j.io.SAXR&&
import&org.liufeng.course.message.resp.A&&
import&org.liufeng.course.message.resp.MusicM&&
import&org.liufeng.course.message.resp.NewsM&&
import&org.liufeng.course.message.resp.TextM&&
import&com.thoughtworks.xstream.XS&&
import&com.thoughtworks.xstream.core.util.QuickW&&
import&com.thoughtworks.xstream.io.HierarchicalStreamW&&
import&com.thoughtworks.xstream.io.xml.PrettyPrintW&&
import&com.thoughtworks.xstream.io.xml.XppD&&
&*&消息工具类&
&*&@author&liufeng&
&*&@date&&
public&class&MessageUtil&{&&
&&&&&*&返回消息类型:文本&
&&&&public&static&final&String&RESP_MESSAGE_TYPE_TEXT&=&"text";&&
&&&&&*&返回消息类型:音乐&
&&&&public&static&final&String&RESP_MESSAGE_TYPE_MUSIC&=&"music";&&
&&&&&*&返回消息类型:图文&
&&&&public&static&final&String&RESP_MESSAGE_TYPE_NEWS&=&"news";&&
&&&&&*&请求消息类型:文本&
&&&&public&static&final&String&REQ_MESSAGE_TYPE_TEXT&=&"text";&&
&&&&&*&请求消息类型:图片&
&&&&public&static&final&String&REQ_MESSAGE_TYPE_IMAGE&=&"image";&&
&&&&&*&请求消息类型:链接&
&&&&public&static&final&String&REQ_MESSAGE_TYPE_LINK&=&"link";&&
&&&&&*&请求消息类型:地理位置&
&&&&public&static&final&String&REQ_MESSAGE_TYPE_LOCATION&=&"location";&&
&&&&&*&请求消息类型:音频&
&&&&public&static&final&String&REQ_MESSAGE_TYPE_VOICE&=&"voice";&&
&&&&&*&请求消息类型:推送&
&&&&public&static&final&String&REQ_MESSAGE_TYPE_EVENT&=&"event";&&
&&&&&*&事件类型:subscribe(订阅)&
&&&&public&static&final&String&EVENT_TYPE_SUBSCRIBE&=&"subscribe";&&
&&&&&*&事件类型:unsubscribe(取消订阅)&
&&&&public&static&final&String&EVENT_TYPE_UNSUBSCRIBE&=&"unsubscribe";&&
&&&&&*&事件类型:CLICK(自定义菜单点击事件)&
&&&&public&static&final&String&EVENT_TYPE_CLICK&=&"CLICK";&&
&&&&&*&解析微信发来的请求(XML)&
&&&&&*&@param&request&
&&&&&*&@return&
&&&&&*&@throws&Exception&
&&&&@SuppressWarnings("unchecked")&&
&&&&public&static&Map&String,&String&&parseXml(HttpServletRequest&request)&throws&Exception&{&&
&&&&&&&&//&将解析结果存储在HashMap中&&
&&&&&&&&Map&String,&String&&map&=&new&HashMap&String,&String&();&&
&&&&&&&&//&从request中取得输入流&&
&&&&&&&&InputStream&inputStream&=&request.getInputStream();&&
&&&&&&&&//&读取输入流&&
&&&&&&&&SAXReader&reader&=&new&SAXReader();&&
&&&&&&&&Document&document&=&reader.read(inputStream);&&
&&&&&&&&//&得到xml根元素&&
&&&&&&&&Element&root&=&document.getRootElement();&&
&&&&&&&&//&得到根元素的所有子节点&&
&&&&&&&&List&Element&&elementList&=&root.elements();&&
&&&&&&&&//&遍历所有子节点&&
&&&&&&&&for&(Element&e&:&elementList)&&
&&&&&&&&&&&&map.put(e.getName(),&e.getText());&&
&&&&&&&&//&释放资源&&
&&&&&&&&inputStream.close();&&
&&&&&&&&inputStream&=&&&
&&&&&&&&return&&&
&&&&&*&文本消息对象转换成xml&
&&&&&*&@param&textMessage&文本消息对象&
&&&&&*&@return&xml&
&&&&public&static&String&textMessageToXml(TextMessage&textMessage)&{&&
&&&&&&&&xstream.alias("xml",&textMessage.getClass());&&
&&&&&&&&return&xstream.toXML(textMessage);&&
&&&&&*&音乐消息对象转换成xml&
&&&&&*&@param&musicMessage&音乐消息对象&
&&&&&*&@return&xml&
&&&&public&static&String&musicMessageToXml(MusicMessage&musicMessage)&{&&
&&&&&&&&xstream.alias("xml",&musicMessage.getClass());&&
&&&&&&&&return&xstream.toXML(musicMessage);&&
&&&&&*&图文消息对象转换成xml&
&&&&&*&@param&newsMessage&图文消息对象&
&&&&&*&@return&xml&
&&&&public&static&String&newsMessageToXml(NewsMessage&newsMessage)&{&&
&&&&&&&&xstream.alias("xml",&newsMessage.getClass());&&
&&&&&&&&xstream.alias("item",&new&Article().getClass());&&
&&&&&&&&return&xstream.toXML(newsMessage);&&
&&&&&*&扩展xstream,使其支持CDATA块&
&&&&&*&@date&&
&&&&private&static&XStream&xstream&=&new&XStream(new&XppDriver()&{&&
&&&&&&&&public&HierarchicalStreamWriter&createWriter(Writer&out)&{&&
&&&&&&&&&&&&return&new&PrettyPrintWriter(out)&{&&
&&&&&&&&&&&&&&&&//&对所有xml节点的转换都增加CDATA标记&&
&&&&&&&&&&&&&&&&boolean&cdata&=&&&
&&&&&&&&&&&&&&&&@SuppressWarnings("unchecked")&&
&&&&&&&&&&&&&&&&public&void&startNode(String&name,&Class&clazz)&{&&
&&&&&&&&&&&&&&&&&&&&super.startNode(name,&clazz);&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&&&&&protected&void&writeText(QuickWriter&writer,&String&text)&{&&
&&&&&&&&&&&&&&&&&&&&if&(cdata)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&writer.write("&![CDATA[");&&
&&&&&&&&&&&&&&&&&&&&&&&&writer.write(text);&&
&&&&&&&&&&&&&&&&&&&&&&&&writer.write("]]&");&&
&&&&&&&&&&&&&&&&&&&&}&else&{&&
&&&&&&&&&&&&&&&&&&&&&&&&writer.write(text);&&
&&&&&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&};&&
&&&&&&&&}&&
OK,到这里关于消息及消息处理工具的封装就讲到这里,其实就是对请求消息/响应消息建立了与之对应的Java类、对xml消息进行解析、将响应消息的Java对象转换成xml。下一篇讲会介绍如何利用上面封装好的工具识别用户发送的消息类型,并做出正确的响应。
前一篇文章里我们已经把公众平台接口中消息及相关操作都进行了封装,本章节将主要介绍如何接收微信服务器发送的消息并做出响应。
明确在哪接收消息
从微信公众平台接口消息指南中可以了解到,当用户向公众帐号发消息时,微信服务器会将消息通过POST方式提交给我们在接口配置信息中填写的URL,而我们就需要在URL所指向的请求处理类CoreServlet的doPost方法中接收消息、处理消息和响应消息。
接收、处理、响应消息
下面先来看我已经写好的CoreServlet的完整代码:
package&org.liufeng.course.&&
import&java.io.IOE&&
import&java.io.PrintW&&
import&javax.servlet.ServletE&&
import&javax.servlet.http.HttpS&&
import&javax.servlet.http.HttpServletR&&
import&javax.servlet.http.HttpServletR&&
import&org.liufeng.course.service.CoreS&&
import&org.liufeng.course.util.SignU&&
&*&核心请求处理类&
&*&@author&liufeng&
&*&@date&&
public&class&CoreServlet&extends&HttpServlet&{&&
&&&&private&static&final&long&serialVersionUID&=&4821986L;&&
&&&&&*&确认请求来自微信服务器&
&&&&public&void&doGet(HttpServletRequest&request,&HttpServletResponse&response)&throws&ServletException,&IOException&{&&
&&&&&&&&//&微信加密签名&&
&&&&&&&&String&signature&=&request.getParameter("signature");&&
&&&&&&&&//&时间戳&&
&&&&&&&&String&timestamp&=&request.getParameter("timestamp");&&
&&&&&&&&//&随机数&&
&&&&&&&&String&nonce&=&request.getParameter("nonce");&&
&&&&&&&&//&随机字符串&&
&&&&&&&&String&echostr&=&request.getParameter("echostr");&&
&&&&&&&&PrintWriter&out&=&response.getWriter();&&
&&&&&&&&//&通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败&&
&&&&&&&&if&(SignUtil.checkSignature(signature,&timestamp,&nonce))&{&&
&&&&&&&&&&&&out.print(echostr);&&
&&&&&&&&}&&
&&&&&&&&out.close();&&
&&&&&&&&out&=&&&
&&&&&*&处理微信服务器发来的消息&
&&&&public&void&doPost(HttpServletRequest&request,&HttpServletResponse&response)&throws&ServletException,&IOException&{&&
&&&&&&&&//&将请求、响应的编码均设置为UTF-8(防止中文乱码)&&
&&&&&&&&request.setCharacterEncoding("UTF-8");&&
&&&&&&&&response.setCharacterEncoding("UTF-8");&&
&&&&&&&&//&调用核心业务类接收消息、处理消息&&
&&&&&&&&String&respMessage&=&CoreService.processRequest(request);&&
&&&&&&&&&&
&&&&&&&&//&响应消息&&
&&&&&&&&PrintWriter&out&=&response.getWriter();&&
&&&&&&&&out.print(respMessage);&&
&&&&&&&&out.close();&&
代码说明:
1)第51行代码:微信服务器POST消息时用的是UTF-8编码,在接收时也要用同样的编码,否则中文会乱码;
2)第52行代码:在响应消息(回复消息给用户)时,也将编码方式设置为UTF-8,原理同上;
3)第54行代码:调用CoreService类的processRequest方法接收、处理消息,并得到处理结果;
4)第57~59行:调用response.getWriter().write()方法将消息的处理结果返回给用户
从doPost方法的实现可以看到,它是通过调用CoreService类的processRequest方法接收、处理消息的,这样做的目的是为了解耦,即业务相关的操作都不在Servlet里处理,而是完全交由业务核心类CoreService去做。下面来看CoreService类的代码实现:
package&org.liufeng.course.&&
import&java.util.D&&
import&java.util.M&&
import&javax.servlet.http.HttpServletR&&
import&org.liufeng.course.message.resp.TextM&&
import&org.liufeng.course.util.MessageU&&
&*&核心服务类&
&*&@author&liufeng&
&*&@date&&
public&class&CoreService&{&&
&&&&&*&处理微信发来的请求&
&&&&&*&@param&request&
&&&&&*&@return&
&&&&public&static&String&processRequest(HttpServletRequest&request)&{&&
&&&&&&&&String&respMessage&=&&&
&&&&&&&&try&{&&
&&&&&&&&&&&&//&默认返回的文本消息内容&&
&&&&&&&&&&&&String&respContent&=&"请求处理异常,请稍候尝试!";&&
&&&&&&&&&&&&//&xml请求解析&&
&&&&&&&&&&&&Map&String,&String&&requestMap&=&MessageUtil.parseXml(request);&&
&&&&&&&&&&&&//&发送方帐号(open_id)&&
&&&&&&&&&&&&String&fromUserName&=&requestMap.get("FromUserName");&&
&&&&&&&&&&&&//&公众帐号&&
&&&&&&&&&&&&String&toUserName&=&requestMap.get("ToUserName");&&
&&&&&&&&&&&&//&消息类型&&
&&&&&&&&&&&&String&msgType&=&requestMap.get("MsgType");&&
&&&&&&&&&&&&//&回复文本消息&&
&&&&&&&&&&&&TextMessage&textMessage&=&new&TextMessage();&&
&&&&&&&&&&&&textMessage.setToUserName(fromUserName);&&
&&&&&&&&&&&&textMessage.setFromUserName(toUserName);&&
&&&&&&&&&&&&textMessage.setCreateTime(new&Date().getTime());&&
&&&&&&&&&&&&textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);&&
&&&&&&&&&&&&textMessage.setFuncFlag(0);&&
&&&&&&&&&&&&//&文本消息&&
&&&&&&&&&&&&if&(msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT))&{&&
&&&&&&&&&&&&&&&&respContent&=&"您发送的是文本消息!";&&
&&&&&&&&&&&&}&&
&&&&&&&&&&&&//&图片消息&&
&&&&&&&&&&&&else&if&(msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE))&{&&
&&&&&&&&&&&&&&&&respContent&=&"您发送的是图片消息!";&&
&&&&&&&&&&&&}&&
&&&&&&&&&&&&//&地理位置消息&&
&&&&&&&&&&&&else&if&(msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION))&{&&
&&&&&&&&&&&&&&&&respContent&=&"您发送的是地理位置消息!";&&
&&&&&&&&&&&&}&&
&&&&&&&&&&&&//&链接消息&&
&&&&&&&&&&&&else&if&(msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK))&{&&
&&&&&&&&&&&&&&&&respContent&=&"您发送的是链接消息!";&&
&&&&&&&&&&&&}&&
&&&&&&&&&&&&//&音频消息&&
&&&&&&&&&&&&else&if&(msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE))&{&&
&&&&&&&&&&&&&&&&respContent&=&"您发送的是音频消息!";&&
&&&&&&&&&&&&}&&
&&&&&&&&&&&&//&事件推送&&
&&&&&&&&&&&&else&if&(msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT))&{&&
&&&&&&&&&&&&&&&&//&事件类型&&
&&&&&&&&&&&&&&&&String&eventType&=&requestMap.get("Event");&&
&&&&&&&&&&&&&&&&//&订阅&&
&&&&&&&&&&&&&&&&if&(eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE))&{&&
&&&&&&&&&&&&&&&&&&&&respContent&=&"谢谢您的关注!";&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&&&&&//&取消订阅&&
&&&&&&&&&&&&&&&&else&if&(eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE))&{&&
&&&&&&&&&&&&&&&&&&&&//&TODO&取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&&&&&//&自定义菜单点击事件&&
&&&&&&&&&&&&&&&&else&if&(eventType.equals(MessageUtil.EVENT_TYPE_CLICK))&{&&
&&&&&&&&&&&&&&&&&&&&//&TODO&自定义菜单权没有开放,暂不处理该类消息&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&}&&
&&&&&&&&&&&&textMessage.setContent(respContent);&&
&&&&&&&&&&&&respMessage&=&MessageUtil.textMessageToXml(textMessage);&&
&&&&&&&&}&catch&(Exception&e)&{&&
&&&&&&&&&&&&e.printStackTrace();&&
&&&&&&&&}&&
&&&&&&&&return&respM&&
代码说明:
1)第29行:调用消息工具类MessageUtil解析微信发来的xml格式的消息,解析的结果放在HashMap里;
2)32~36行:从HashMap中取出消息中的字段;
3)39-44、84行:组装要返回的文本消息对象;
4)47~82行:演示了如何接收微信发送的各类型的消息,根据MsgType判断属于哪种类型的消息;
5)85行:调用消息工具类MessageUtil将要返回的文本消息对象TextMessage转化成xml格式的字符串;
关于事件推送(关注、取消关注、菜单点击)
对于消息类型的判断,像文本消息、图片消息、地理位置消息、链接消息和语音消息都比较好理解,有很多刚接触的朋友搞不懂事件推送消息有什么用,或者不清楚该如何判断用户关注的消息。那我们就专门来看下事件推送,下图是官方消息接口文档中关于事件推送的说明:
这里我们只要关心两个参数:MsgType和Event。当MsgType=event时,就表示这是一条事件推送消息;而Event表示事件类型,包括订阅、取消订阅和自定义菜单点击事件。也就是说,无论用户是关注了公众帐号、取消对公众帐号的关注,还是在使用公众帐号的菜单,微信服务器都会发送一条MsgType=event的消息给我们,而至于具体这条消息表示关注、取消关注,还是菜单的点击事件,就需要通过Event的值来判断了。(注意区分Event和event)
连载五篇教程总结
经过5篇的讲解,已经把开发模式启用,接口配置,消息相关工具类的封装,消息的接收与响应全部讲解完了,而且贴上了完整的源代码,相信有一定基础的朋友可以看的明白,能够通过系列文章基本掌握微信公众平台开发的相关技术知识。下面我把目前项目的完整结构贴出,方便大家对照:
相信不少朋友都遇到过这样的问题:当发送的文本消息内容过长时,将不做任何响应。那么到底微信允许的文本消息的最大长度是多少呢?我们又该如何计算文本的长度呢?为什么还有些人反应微信好像支持的文本消息最大长度在1300多呢?这篇文章会彻底解除大家的疑问。
接口文档中对消息长度限制为2048
可以看到,接口文档中写的很明确:回复的消息内容长度不超过2048字节。那为什么很多人测试反应消息内容长度在1300多字节时,微信就不响应了呢?我想这问题应该在这部分人没有搞清楚到底该如何计算文本的字节数。
如何正确计算文本所占字节数
计算文本(字符串)所占字节数,大家第一个想到的应该就是String类的getBytes()方法,该方法返回的是字符串对应的字节数组,再计算数组的length就能够得到字符串所占字节数。例如:
public&static&void&main(String&[]args)&&{&&
&&&&//&运行结果:4&&
&&&&System.out.println("柳峰".getBytes().length);&&
上面的示例中计算了两个中文所占的字节数为4,即一个汉字占2个字节。真的是这样吗?其实我们忽略了一个问题:对于不同的编码方式,中文所占的字节数也不一样!这到底要怎么呢?在上面的例子中,我们并没有指定编码方式,那么会使用操作系统所默认的编码方式。先来看我得出的三条结论:
1)如果上面的例子运行在默认编码方式为ISO8859-1的平台上,计算结果是2;
2)如果上面的例子运行在默认编码方式为gb2312或gbk的操作系统平台上,计算结果是4;
3)如果上面的例子运行在默认编码方式为utf-8的操作系统平台上,计算结果是6;
如果真的是这样,是不是意味着String.getBytes()方法在我们的系统平台上默认采用的是gb2312或gbk编码方式呢?我们再来看一个例子:
public&static&void&main(String&[]args)&throws&UnsupportedEncodingException&&{&&
&&&&//&运行结果:2&&
&&&&System.out.println("柳峰".getBytes("ISO8859-1").length);&&
&&&&//&运行结果:4&&
&&&&System.out.println("柳峰".getBytes("GB2312").length);&&
&&&&//&运行结果:4&&
&&&&System.out.println("柳峰".getBytes("GBK").length);&&
&&&&//&运行结果:6&&
&&&&System.out.println("柳峰".getBytes("UTF-8").length);&&
这个例子是不是很好地证明了我上面给出的三条结论呢?也就是说采用ISO8859-1编码方式时,一个中/英文都只占一个字节;采用GB2312或GBK编码方式时,一个中文占两个字节;而采用UTF-8编码方式时,一个中文占三个字节。
微信平台采用的编码方式及字符串所占字节数的计算
那么,在向微信服务器返回消息时,该采用什么编码方式呢?当然是UTF-8,因为我们已经在doPost方法里采用了如下代码来避免中文乱码了:
//&将请求、响应的编码均设置为UTF-8(防止中文乱码)&&
request.setCharacterEncoding("UTF-8");&&
response.setCharacterEncoding("UTF-8");&&
为了验证我所说了,我写了个例子来测试:
private&static&String&getMsgContent()&{&&
&&&&StringBuffer&buffer&=&new&StringBuffer();&&
&&&&//&每行70个汉字,共682个汉字加1个英文的感叹号&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵你的手走过风风雨雨有什么困难我都陪你");&&
&&&&buffer.append("不知道什么时候开始喜欢这里每个夜里都会来这里看你你长得多么美丽叫我不能不看你看不到你我就迷失了自己好想牵!");&&
&&&&return&buffer.toString();&&
public&static&void&main(String&[]args)&throws&Exception&&{&&
&&&&//&采用gb2312编码方式时占1365个字节&&
&&&&System.out.println(getMsgContent().getBytes("gb2312").length);&&
&&&&//&采用utf-8编码方式时占2047个字节&&
&&&&System.out.println(getMsgContent().getBytes("utf-8").length);&&
getMsgContent()方法返回的内容正是微信的文本消息最长能够支持的,即采用UTF-8编码方式时,文本消息内容最多支持2047个字节,也就是微信公众平台接口文档里所说的回复的消息内容长度不超过2048字节,即使是等于2048字节也不行,你可以试着将getMsgContent()方法里的内容多加一个英文符号,这个时候微信就不响应了。
同时,我们也发现,如果采用gb2312编码方式来计算getMsgContent()方法返回的文本所占字节数的结果是1365,这就是为什么很多朋友都说微信的文本消息最大长度好像只支持1300多字节,并不是接口文档中所说的2048字节,其实是忽略了编码方式,只是简单的使用了String类的getBytes()方法而不是getBytes("utf-8")方法去计算所占字节数。
中utf-8编码方式时所占字节数的计算方法封装
&*&计算采用utf-8编码方式时字符串所占字节数&
&*&@param&content&
&*&@return&
public&static&int&getByteSize(String&content)&{&&
&&&&int&size&=&0;&&
&&&&if&(null&!=&content)&{&&
&&&&&&&&try&{&&
&&&&&&&&&&&&//&汉字采用utf-8编码时占3个字节&&
&&&&&&&&&&&&size&=&content.getBytes("utf-8").&&
&&&&&&&&}&catch&(UnsupportedEncodingException&e)&{&&
&&&&&&&&&&&&e.printStackTrace();&&
&&&&&&&&}&&
&&&&return&&&
好了,本章节的内容就讲到这里,我想大家通过这篇文章所学到的应该不仅仅是2047这个数字,还应该对字符编码方式有一个新的认识。
本篇文章主要介绍在文本消息中使用换行符的好处以及如何使用换行符。
最近一个月虽然抽不出时间写博客,但却一直在认真答复大家提出的问题。收到这么多的回复、关注和答谢,还是蛮有成就感的,让我觉得做这件事越来越有意义,更加坚定了我继续写下去的决心。经过前面六篇文章的讲解,相信在看文章的你,已经掌握了公众帐号的基础开发知识(基于),如框架搭建、API封装、消息接收与回复等;接下来的系列文章将专注于讲解公众帐号开发中的技巧及实用功能的开发(如天气查询、周边搜索、人机对话等)。
使用换行的好处及示例
使用换行的好处无非就是让信息的呈现更加整齐、美观和直观,适当的在文本消息中使用换行符,会让人看了之后感觉很舒服、清晰、明了。下面是公众帐号xiaoqrobot的主菜单示例,就是合理地使用了换行符,看上去是不是很直观、清爽呢?(什么?觉得很丑?呃,那就算是我自恋吧...)
你可以试想一下,如果这个文本菜单没有使用一个换行符,那会长什么样?
如何在文本消息中使用换行符?
在微信公众帐号的文本消息中,换行符仍然是“\n”,下面就通过代码来讲解xiaoqrobot的文本菜单是如何实现的?
&*&xiaoqrobot的主菜单&
&*&@return&
public&static&String&getMainMenu()&{&&
&&&&StringBuffer&buffer&=&new&StringBuffer();&&
&&&&buffer.append("您好,我是小q,请回复数字选择服务:").append("\n\n");&&
&&&&buffer.append("1&&天气预报").append("\n");&&
&&&&buffer.append("2&&公交查询").append("\n");&&
&&&&buffer.append("3&&周边搜索").append("\n");&&
&&&&buffer.append("4&&歌曲点播").append("\n");&&
&&&&buffer.append("5&&经典游戏").append("\n");&&
&&&&buffer.append("6&&美女电台").append("\n");&&
&&&&buffer.append("7&&人脸识别").append("\n");&&
&&&&buffer.append("8&&聊天唠嗑").append("\n\n");&&
&&&&buffer.append("回复“?”显示此帮助菜单");&&
&&&&return&buffer.toString();&&
怎么样,实现起来是不是很简单呢?
1)9-16行就是菜单项,菜单项之间都是用一个换行符分隔;
2)第8行、第16号末尾都使用了两个换行符,这样可以把菜单项与其他内容分隔开,更有层次感,看上去也会舒服、直观一点。
可能细心的朋友已经发现了:在截图上,“周边搜索”和“美女电台”后边都有一个“礼物”表情,而代码中并没有看到,这是我专门去掉的,因为我打算后面专门用一篇文章把QQ表情的发送、处理、接收讲清楚。
细节决定成败!
本文主要介绍网页超链接的作用以及如何在文本消息中使用网页超链接。
网页超链接的作用
我想但凡是熟悉HTML的朋友,对超链接一定不会陌生。而今天我们要讨论和使用的只是超链接中的其中一种---网页超链接,即使用HTML中的&a&标签将某段文字链接到其他网页上去,示例如下:
&a&href="http://blog.csdn.net/lyq8479"&柳峰的博客&/a&&&
上面是一段标准的HTML代码,实现了一个网页超链接,即将“”5个字链接到了博客主页URL,当“”5个字时,会打开所指向的网页。
如何在文本消息中使用网页超链接
其实,不知道如何在文本消息中使用网页超链接的开发者几乎100%都熟悉HTML,特别是对HTML中的&a&标签再熟悉不过了。那到底在公众帐号的文本消息中使用超链接有什么特别之处呢?为什么如此多的朋友都曾经在这个问题上栽过跟头?我们先来看在微信中两种错误使用超链接的方法:
错误用法1(a标签的href属性值未被引号引起):
&a&href=http://blog.csdn.net/lyq8479&柳峰的博客&/a&&&
错误用法2(a标签的href属性值被单引号引起):
&a&href=‘http://blog.csdn.net/lyq8479‘&柳峰的博客&/a&&&
在做Web开发时,以上两种写法都是可以的,但是放在微信公众帐号的文本消息中,这两种写法都是错误的,网页超链接并不会起作用,而且在Android手机上还会将HTML代码原样显示出来,如下图所示:
手机上的效果:
iPhone手机上的效果:
可以看出,在微信上,HTML的a标签属性值不用引号引起,或者使用单引号引起,都是错误的写法(在iPhone上,a标签属性href的值用单引号是正常的)。正确的用法是将a标签href属性的值用双引号引起,代码如下:
&a&href="http://blog.csdn.net/lyq8479"&柳峰的博客&/a&&&
这样在Android和iPhone手机上,都可以正确显示超链接,并且点击该超链接,会使用微信内置浏览器打开。
提示:在测试微信公众帐号时,不要只是在自己的手机上测试通过就认为完全没问题了,因为目前微信公众帐号上有好几处在Android和平台上表现不一致。
我想大家对QQ表情一定不会陌生,一个个小头像极大丰富了聊天的乐趣,使得聊天不再是简单的文字叙述,还能够配上喜、怒、哀、乐等表达人物心情的小图片。本文重点要介绍的内容就是如何在公众平台使用QQ表情,即在微信公众帐号开发模式下,如何发送QQ表情给用户,以及如何识别用户发来的是QQ表情。
QQ表情代码表
首先需要明确的是:QQ表情虽然呈现为一张张动态的表情图片,但在微信公众平台的消息接口中却是属于文本消息;也就是说当用户向公众帐号发送QQ表情时,公众帐号后台程序接收到的消息类型MsgType的值为text。只要上面这点能理解了,下面的工作就好开展了。
对于QQ表情,发送的是文本消息,而呈现出来却是表情图片,那么每一个QQ表情图片一定会有与之相对应的表情代码。下面是我已经整理好的微信公众帐号中使用的QQ表情代码对照表:
上面一共列出了105个QQ表情,每个表情都给出了与之相对应的文字代码与符号代码(也许这两种叫法并不恰当),至于这两种代码怎么来的以及如何使用,下面马上会讲到。
用户向公众帐号发送QQ表情
在微信上使用公众帐号时,如何发送QQ表情,我想这个很少有人不会的。在输入框旁边有一个笑脸的图片按钮,点击它将会弹出表情选择界面,可选择的表情依次为“QQ表情”、“符号表情”和“动画表情”。当我们点击选择了某个QQ表情后,发现在输入框中会显示该表情的文字代码,这里是用一对中括号引起的,如下图所示:
其实,当我们很熟悉要使用QQ表情的文字代码时,也可以直接在输入框中输入表情的代码,而不需要弹出表情选择框。如下图所示:
从上图可以看出,在输入框中输入“[呲牙]”、“/呲牙”和“/::D”这三种代码的作用一样,都是发送呲牙的QQ表情。这个时候,大家再回过头去看文章最开始的QQ表情代码对照表,就明白是怎么回事了。
公众帐号向用户发送QQ表情
与用户向公众帐号发送QQ表情一样,在开发模式下,公众帐号也可以用同样的表情代码(文字代码或符号代码)向用户回复QQ表情。代码片段如下:
//&文本消息&&
if&(msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT))&{&&
&&&&//&回复文本消息&&
&&&&TextMessage&textMessage&=&new&TextMessage();&&
&&&&textMessage.setToUserName(fromUserName);&&
&&&&textMessage.setFromUserName(toUserName);&&
&&&&textMessage.setCreateTime(new&Date().getTime());&&
&&&&textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);&&
&&&&textMessage.setFuncFlag(0);&&
&&&&textMessage.setContent("[难过]&/难过&/::(");&&
&&&&//&文本消息对象转换成xml字符串&&
&&&&respMessage&=&MessageUtil.textMessageToXml(textMessage);&&
上面代码片段的作用是:判断发送的消息类型,如果是文本消息(MsgType=text),则回复三个难过的QQ表情给用户。可以看出,不管是用户发给公众帐号,还是公众帐号发给用户,都可以使用QQ表情的文字代码(如:[难过] &/难过)和符号代码(如 /::()。
公众帐号识别用户发送的QQ表情
在掌握了如何发送QQ表情后,我们再来看看公众帐号如何识别用户发送的是QQ表情。这是什么意思呢?当用户向公众帐号发送一个QQ表情,在后台程序中接收到的会是什么值,我们又怎么知道这个值就是一个QQ表情。
其实,只要做个简单的测试,比如:将接收到的文本消息输出到日志中(可以用log4j或者System.out.print),不难发现:向公众帐号发送一个QQ表情,在后台程序中接收到的是QQ表情的符号代码。
下面是我简单封装的一个方法,通过正则表达式实现的,用于判断用户发送的是否是单个QQ表情。
&*&判断是否是QQ表情&
&*&@param&content&
&*&@return&
public&static&boolean&isQqFace(String&content)&{&&
&&&&boolean&result&=&&&
&&&&//&判断QQ表情的正则表达式&&
&&&&String&qqfaceRegex&=&"/::\\)|/::~|/::B|/::\\||/:8-\\)|/::&|/::$|/::X|/::Z|/::‘\\(|/::-\\||/::@|/::P|/::D|/::O|/::\\(|/::\\+|/:--b|/::Q|/::T|/:,@P|/:,@-D|/::d|/:,@o|/::g|/:\\|-\\)|/::!|/::L|/::&|/::,@|/:,@f|/::-S|/:\\?|/:,@x|/:,@@|/::8|/:,@!|/:!!!|/:xx|/:bye|/:wipe|/:dig|/:handclap|/:&-\\(|/:B-\\)|/:&@|/:@&|/::-O|/:&-\\||/:P-\\(|/::‘\\||/:X-\\)|/::\\*|/:@x|/:8\\*|/:pd|/:&W&|/:beer|/:basketb|/:oo|/:coffee|/:eat|/:pig|/:rose|/:fade|/:showlove|/:heart|/:break|/:cake|/:li|/:bome|/:kn|/:footb|/:ladybug|/:shit|/:moon|/:sun|/:gift|/:hug|/:strong|/:weak|/:share|/:v|/:@\\)|/:jj|/:@@|/:bad|/:lvu|/:no|/:ok|/:love|/:&L&|/:jump|/:shake|/:&O&|/:circle|/:kotow|/:turn|/:skip|/:oY|/:#-0|/:hiphot|/:kiss|/:&&|/:&&";&&
&&&&Pattern&p&=&Pattern.compile(qqfaceRegex);&&
&&&&Matcher&m&=&p.matcher(content);&&
&&&&if&(m.matches())&{&&
&&&&&&&&result&=&&&
&&&&return&&&
下面是方法的使用,实现了这样一个简单的功能:用户发什么QQ表情给公众帐号,公众帐号就回复什么QQ表情给用户(xiaoqrobot就是这么做的)。实现代码如下:
//&文本消息&&
if&(msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT))&{&&
&&&&//&文本消息内容&&
&&&&String&content&=&requestMap.get("Content");&&
&&&&//&判断用户发送的是否是单个QQ表情&&
&&&&if(XiaoqUtil.isQqFace(content))&{&&
&&&&&&&&//&回复文本消息&&
&&&&&&&&TextMessage&textMessage&=&new&TextMessage();&&
&&&&&&&&textMessage.setToUserName(fromUserName);&&
&&&&&&&&textMessage.setFromUserName(toUserName);&&
&&&&&&&&textMessage.setCreateTime(new&Date().getTime());&&
&&&&&&&&textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);&&
&&&&&&&&textMessage.setFuncFlag(0);&&
&&&&&&&&//&用户发什么QQ表情,就返回什么QQ表情&&
&&&&&&&&textMessage.setContent(content);&&
&&&&&&&&&&
&&&&&&&&//&将文本消息对象转换成xml字符串&&
&&&&&&&&respMessage&=&MessageUtil.textMessageToXml(textMessage);&&
好了,关于微信公众帐号中QQ表情的使用就介绍这么多。其实,我并不希望初学者上来只是简单拷贝我贴出的代码,实现了自己想要的功能就完事了,更希望初学的朋友能够通过此文章学会一种思考问题和解决问题的方法。
从公众平台的中可以看出,每种类型的消息定义中,都包含有CreateTime参数,它表示消息的创建时间,如下图所示:
上图是消息接口指南中4.1-文本消息的定义。注意CreateTime的描述:消息创建时间(整型),重点在于这是一个整型的时间,而不是我们大家所熟悉的类似于"yyyy-MM-dd HH:mm:ss"的标准格式时间。本文主要想介绍的就是微信消息接口中定义的整型消息创建时间CreateTime的含义,以及如何将CreateTime转换成我们所熟悉的时间格式。
整型CreateTime的含义
消息接口中定义的消息创建时间CreateTime,它表示1970年1月1日0时0分0秒至消息创建时所间隔的秒数,注意是间隔的秒数,不是毫秒数!
整型CreateTime的转换
在中,我们也经常会通过下面两种方式获取long类型的时间,先上代码:
&*&演示Java中常用的获取long类型时间的两种方式&
public&static&void&main(String[]&args)&{&&
&&&&long&longTime1&=&System.currentTimeMillis();&&
&&&&//&8&&
&&&&System.out.println(longTime1);&&
&&&&long&longTime2&=&new&java.util.Date().getTime();&&
&&&&//&1&&
&&&&System.out.println(longTime2);&&
上面两种获取long类型时间的方法是等价的,获取到的结果表示当时时间距离日0时0分0秒0毫秒的毫秒数,注意这里是毫秒数!那么这里获取到的long类型的时间如何转换成标准格式的时间呢?方法如下:
&*&演示Java中常用的获取long类型时间的两种方式&
public&static&void&main(String[]&args)&{&&
&&&&//&当前时间(距离日0时0分0秒0毫秒的毫秒数)&&
&&&&long&longTime&=&8L;&&
&&&&String&stdFormatTime&=&formatTime(longTime);&&
&&&&//&输出:&22:09:03&&
&&&&System.out.println(stdFormatTime);&&
&*&将long类型的时间转换成标准格式(yyyy-MM-dd&HH:mm:ss)&
&*&@param&longTime&
&*&@return&
public&static&String&formatTime(long&longTime)&{&&
&&&&DateFormat&format&=&new&SimpleDateFormat("yyyy-MM-dd&HH:mm:ss");&&
&&&&return&format.format(new&Date(longTime));&&
上面演示了将一个long类型的时间转换成标准格式的时间,只是简单的运用了SimpleDateFormat类,比较好懂的。那么再回到今天的主题上来,如何将CreateTime转换成标准格式的时间。
微信消息接口中的CreateTime表示距离1970年的秒数,而System.currentTimeMillis()表示距离1970年的毫秒数,它们之间的换算就相当于:1秒=1000毫秒,即将CreateTime乘以1000,就变成了距离1970年的毫秒数了,就可以使用上面的formatTime()方法来处理了,是不是很简单呢?
下面,我还是单另封装一个方法,用于将微信消息中的整型的消息创建时间CreateTime转换成标准格式的时间,如下:
&*&将微信消息中的CreateTime转换成标准格式的时间(yyyy-MM-dd&HH:mm:ss)&
&*&@param&createTime&消息创建时间&
&*&@return&
public&static&String&formatTime(String&createTime)&{&&
&&&&//&将微信传入的CreateTime转换成long类型,再乘以1000&&
&&&&long&msgCreateTime&=&Long.parseLong(createTime)&*&1000L;&&
&&&&DateFormat&format&=&new&SimpleDateFormat("yyyy-MM-dd&HH:mm:ss");&&
&&&&return&format.format(new&Date(msgCreateTime));&&
题外话(可以略过)
相信这篇文章已经让大家等的太久了,不是我故弄玄虚、吊大家胃口,而是写一篇文章真的需要花太多的时间。也许一篇文章,你们花3-5分钟就看完了、就学会掌握了,而我却要花2-3个小时的时间来完成,也许只有用心写过文章的人才能体会,希望大家能够相互体谅!
也曾经有人对我说,我写的东西太初级,都是入门级的东西。好吧,我承认众口难调,很难满足所有的读者,再加上我自己也只是个新手,一个4月前才听说公众平台这个词的初学者,谢谢你们以不同方式对我的激励,我会更加努力的!
第9篇文章介绍了。在此之后,很多朋友问我如何发emoji表情(微信上叫符号表情),也就让我有了写这篇文章的决心。在此之前,我在网上进行了大量的搜索,发现根本没有介绍这方面的文章,并且在微信公众帐号开发官方交流群里提问,也少有人知道该如何发送emoji表情。今天,就让我们一起来揭开它的神秘面纱!
本文重点介绍如何在微信公众帐号开发模式下,通过程序代码向用户发送符号表情。至于如何识别用户发送的是符号表情,就不在此讲解了,留给大家一点学习思考的空间。我只是给大家一个提示:用户向公众帐号发送符号表情,其实也是一条文本消息,这与QQ表现是一样的,即然是文本消息,将接收的符号表情内容打印到日志,不就知道每个表情对应的文本了吗?呵呵,当然也没有这么简单,并不是像其他文本消息,这里需要对接收到符号表情消息先做编码的转换。好了,就提示这么多。
认识符号表情
在公众帐号的主交互界面,窗口底部的输入框旁边有一个笑脸的图片按钮,点击它将会弹出表情选择界面,可选择的表情依次为“QQ表情”、“符号表情”和“动画表情”,我们选择“符号表情”,将会看到如下图所示界面:
可以持看出,相比QQ表情,符号表情要更加实用。为什么这么说呢?因为QQ表情大都是脸部表情,而符号表情除了脸部表情外,还有很多与生活息息相关的表情,例如:动物、花朵、树木、电视、电话、电脑、吉它、球类、交通工具等等。如果能在消息中使用符号表情,会不会显得更加生动、有趣呢?
再来看看小q机器人中使用符号表情的效果,先上两张图:
左边截图是小q机器人的主菜单,在Q友圈文字旁边的那个表情就是符号表情,是一女一男两人小朋友,示意着在Q友圈里可以结识到更多的朋友,不要想歪了,^_^。右边截图是人脸识别功能的使用指南,里面的“相机”、“鬼脸”也是符号表情,这样看上去是不是更加有趣味性呢?如果是纯文本,一定会显得太单调、太枯燥了。
Emoji表情的分类
Emoji表情有很多种版本,包括Unified、DoCoMo、KDDI、Softbank和Google,而且不同版本的表情代码也不一样,更可恶的是:不同的手机、甚至是同一操作系统的不同版本所支持的emoji表情又不一样。所以,完美主义者可以止步了,因为目前emoji表情并不能保证在所有终端上都能正常使用。
庆幸的是,我已经在超过10余部终端上测试过emoji表情的使用,这其中包括iPhone 4S、iPhone 5、 2.2、Android 4.0+、Win8、iPad2,只有极个别终端上显示不出来或显示为一个小方格,所以并没有什么太大的影响,也就可以放心使用了!
Emoji表情代码表之Unified版本
上面介绍的几种版本的emoji表情,都是通过unicode编码来表示的。换言之,不同版本的emoji表情对应的unicode编码值也不一样。本篇文章,我先给出Unified版本emoji表情的代码表,如下图所示:
公众帐号如何向用户发送emoji表情
上面已经给出了emoji表情的unified unicode代码对照表,那么这些代码要如何使用,才能发送出对应的emoji表情呢?如果你只是简单的像使用QQ表情代码那样,直接在文本消息的Content里写emoji表情代码,一定是会原样显示的。
这里需要用到一个方法做转换处理,方法的代码如下:
&*&emoji表情转换(hex&-&&utf-16)&
&*&@param&hexEmoji&
&*&@return&
public&static&String&emoji(int&hexEmoji)&{&&
&&&&return&String.valueOf(Character.toChars(hexEmoji));&&
方法说明:例如,“自行车”的unicode编码值为U+1F6B2,如果我们要在程序代码中使用“自行车”这个emoji表情,需要这样使用:
String&bike&=&String.valueOf(Character.toChars(0x1F6B2));&&
其实前面那个emoji()方法就是对上面这行代码做了个简单的封装而以。现在知道如何使用emoji表情代码了吧,其实就是将代码表中的U+替换为0x,再调用emoji方法进行转换,将转换后的结果放在文本消息的Content中,返回给用户就会显示emoji表情了。
下面,我给出一个使用emoji表情的完整示例,如下:
package&org.liufeng.course.&&
import&java.util.D&&
import&java.util.M&&
import&javax.servlet.http.HttpServletR&&
import&org.liufeng.course.message.resp.TextM&&
import&org.liufeng.course.util.MessageU&&
&*&核心服务类&
&*&@author&liufeng&
&*&@date&&
public&class&CoreService&{&&
&&&&&*&处理微信发来的请求&
&&&&&*&@param&request&
&&&&&*&@return&
&&&&public&static&String&processRequest(HttpServletRequest&request)&{&&
&&&&&&&&String&respMessage&=&&&
&&&&&&&&try&{&&
&&&&&&&&&&&&//&xml请求解析&&
&&&&&&&&&&&&Map&String,&String&&requestMap&=&MessageUtil.parseXml(request);&&
&&&&&&&&&&&&//&发送方帐号(open_id)&&
&&&&&&&&&&&&String&fromUserName&=&requestMap.get("FromUserName");&&
&&&&&&&&&&&&//&公众帐号&&
&&&&&&&&&&&&String&toUserName&=&requestMap.get("ToUserName");&&
&&&&&&&&&&&&//&回复文本消息&&
&&&&&&&&&&&&TextMessage&textMessage&=&new&TextMessage();&&
&&&&&&&&&&&&textMessage.setToUserName(fromUserName);&&
&&&&&&&&&&&&textMessage.setFromUserName(toUserName);&&
&&&&&&&&&&&&textMessage.setCreateTime(new&Date().getTime());&&
&&&&&&&&&&&&textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);&&
&&&&&&&&&&&&textMessage.setFuncFlag(0);&&
&&&&&&&&&&&&textMessage.setContent("自行车"&+&emoji(0x1F6B2)&+&"&男性"&+&emoji(0x1F6B9)&+&"&钱袋"&+&emoji(0x1F4B0));&&
&&&&&&&&&&&&respMessage&=&MessageUtil.textMessageToXml(textMessage);&&
&&&&&&&&}&catch&(Exception&e)&{&&
&&&&&&&&&&&&e.printStackTrace();&&
&&&&&&&&}&&
&&&&&&&&return&respM&&
&&&&&*&emoji表情转换(hex&-&&utf-16)&
&&&&&*&@param&hexEmoji&
&&&&&*&@return&
&&&&public&static&String&emoji(int&hexEmoji)&{&&
&&&&&&&&return&String.valueOf(Character.toChars(hexEmoji));&&
上面代码的作用是:不管用户发送什么类型的消息,都返回包含三个emoji表情的文本消息。如果不明白CoreService类怎么回事,请查看本系列教程的第5篇,或者你只需要认真看第42行代码,就知道怎么样把emoji表情代码放在文本消息的Content中了。最后再来看下运行效果截图:
本篇文章要讲的内容就至此结束了,但关于emoji表情的讲解还没有结束,为什么这么说呢?请仔细看本篇文章的第二张截图,也就是小q机器人的文本菜单,里面用到的emoji表情在本文给出的emoji代码表里根本找不到(微信上的emoji表情与代码表中完全一致),那这个emoji表情又是如何发送的呢,请听下回分解!
引言及文章概要
给出了Unified版本的符号表情(emoji表情)代码表,并且介绍了如何在公众帐号开发模式下发送emoji表情,还在文章结尾出,卖了个关子:“小q机器人中使用的一些符号表情,在微信的符号表情选择栏里根本找不到,并且在上篇文章给出的符号表情代码表(Unified版)中也没有,那这些表情是如何发送的呢?”如下面两张图所示的符号表情“情侣”和“公共汽车”。
本文主要介绍以下内容:1)如何在微信上使用更多的符号表情(即如何发送在微信符号表情选择栏中不存在的emoji表情);2)给出SoftBank版符号表情的代码对照表;3)介绍及演示如何发送SoftBank版本的符号表情。让大家彻底玩转微信公众帐号的emoji表情!
如何在微信上使用更多的符号表情
我们先来看下,作为一个微信用户,如何向好友或微信公众帐号发送一些微信符号表情选择栏中没有列出的符号表情。例如:小q机器人中使用的“情侣”、“公共汽车”两个符号表情,如果我想在与朋友微信聊天时使用,该怎么办呢?请先看下面的两张截图:
可以看出,当我们在输入框中输入“情侣”的全拼“qinglv”、“公共汽车”的全拼“gonggongqiche”时,输入法的文本提示列表中就会自动显示对应的符号表情,怎么样,是不是很容易呢?这类表情还有很多,例如:马桶、厕所、取款机等。
说明:笔者使用的是iPhone 4S手机系统自带的输入法做的测试,如果你用的是、或者是第三方输入法,那就另当别论了。
Emoji表情代码表之SoftBank版本
讲过,emoji表情有很多种版本,其中包括Unified、DoCoMo、KDDI、Softbank和Google,并且不同版本用于表示同一符号表情的Unicode代码也不相同。本篇文章,给出SoftBank(日本软银集团)版本的emoji表情代码表(网上一般称之为SB Unicode,指的就是它),如下图所示:
公众帐号如何向用户发送SoftBank版本的符号表情
在微信公众帐号开发模式下,发送SoftBank版的符号表情要比发送Unified版的符号表情简单的多,直接将符号表情对应的SoftBank Unicode值写在程序代码中返回给用户即可,无需做任何处理。
下面,我给出一个发送SoftBank版符号表情的示例,代码如下:
package&org.liufeng.course.&&
import&java.util.D&&
import&java.util.M&&
import&javax.servlet.http.HttpServletR&&
import&org.liufeng.course.message.resp.TextM&&
import&org.liufeng.course.util.MessageU&&
&*&核心服务类&
&*&@author&liufeng&
&*&@date&&
public&class&CoreService&{&&
&&&&&*&处理微信发来的请求&
&&&&&*&@param&request&
&&&&&*&@return&
&&&&public&static&String&processRequest(HttpServletRequest&request)&{&&
&&&&&&&&String&respMessage&=&&&
&&&&&&&&try&{&&
&&&&&&&&&&&&//&xml请求解析&&
&&&&&&&&&&&&Map&String,&String&&requestMap&=&MessageUtil.parseXml(request);&&
&&&&&&&&&&&&//&发送方帐号(open_id)&&
&&&&&&&&&&&&String&fromUserName&=&requestMap.get("FromUserName");&&
&&&&&&&&&&&&//&公众帐号&&
&&&&&&&&&&&&String&toUserName&=&requestMap.get("ToUserName");&&
&&&&&&&&&&&&//&回复文本消息&&
&&&&&&&&&&&&TextMessage&textMessage&=&new&TextMessage();&&
&&&&&&&&&&&&textMessage.setToUserName(fromUserName);&&
&&&&&&&&&&&&textMessage.setFromUserName(toUserName);&&
&&&&&&&&&&&&textMessage.setCreateTime(new&Date().getTime());&&
&&&&&&&&&&&&textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);&&
&&&&&&&&&&&&textMessage.setFuncFlag(0);&&
&&&&&&&&&&&&textMessage.setContent("自行车\ue136&男人\ue138&钱袋\ue12f&情侣\ue428&公共汽车\ue159");&&
&&&&&&&&&&&&respMessage&=&MessageUtil.textMessageToXml(textMessage);&&
&&&&&&&&}&catch&(Exception&e)&{&&
&&&&&&&&&&&&e.printStackTrace();&&
&&&&&&&&}&&
&&&&&&&&return&respM&&
上面代码的作用是:不管用户发送什么类型的消息,都返回包含5个emoji表情的文本消息。如果不明白CoreService类怎么回事,请查看,或者你只需要认真看第42行代码,就知道怎么样把SoftBank版emoji表情代码放在文本消息的Content中了。最后再来看下运行效果截图:
说明:每一个符号表情都有与之对应的Unified unicode、Softbank unicode代码,并不是说“情侣”、“公共汽车”这类在微信的符号表情栏中找不到的emoji表情只能通过本文的方式发送,只要你拿到与之对应的Unified unicode代码,一样可以使用所讲的方法发送这类符号表情。
好了,关于微信公众帐号向用户发送符号表情的讲解就此结束了,相信有些朋友看完教程已经开始在帐号中使用符号表情了。其实,我更希望大家在拷贝我粘出的Unified版、SoftBank版符号表情代码表的同时,也能去了解下符号表情各种版本、Unicode编码及增补码的相关知识,不断拓展自己的知识面,触类旁通,这样才能真正地把我讲解的知识变成你自己的,才能做到以不变应万变。
引言及内容概要
已经有几位读者抱怨“柳峰只用到文本消息作为示例,从来不提图文消息,都不知道图文消息该如何使用”,好吧,我错了,原本以为把基础API封装完、框架搭建好,再给出一个文本消息的使用示例,大家就能够照猫画虎的,或许是因为我的绘画功底太差,画出的那只猫本来就不像猫吧……
本篇主要介绍公众帐号开发中图文消息的使用,以及图文消息的几种表现形式。标题取名为“图文消息全攻略”,这绝对不是标题党,是想借此机会把大家对图文消息相关的问题、疑虑、障碍全部清除掉。
图文消息的主要参数说明
通过微信官方的,可以看到对图文消息的参数介绍,如下图所示:
从图中可以了解到:
1)图文消息的个数限制为10,也就是图中ArticleCount的值(图文消息的个数,限制在10条以内);
2)对于多图文消息,第一条图文的图片显示为大图,其他图文的图片显示为小图;
3)第一条图文的图片大小建议为640*320,其他图文的图片大小建议为80*80;
好了,了解这些,再结合第4篇文章所讲的,想要回复图文消息给用户也就不是什么难事了。
图文消息的多种表现形式
下面直接通过代码演示图文消息最主要的五种表现形式的用法,源代码如下:
package&org.liufeng.course.&&
import&java.util.ArrayL&&
import&java.util.D&&
import&java.util.L&&
import&java.util.M&&
import&javax.servlet.http.HttpServletR&&
import&org.liufeng.course.message.resp.A&&
import&org.liufeng.course.message.resp.NewsM&&
import&org.liufeng.course.message.resp.TextM&&
import&org.liufeng.course.util.MessageU&&
&*&核心服务类&
&*&@author&liufeng&
&*&@date&&
public&class&CoreService&{&&
&&&&&*&处理微信发来的请求&
&&&&&*&@param&request&
&&&&&*&@return&
&&&&public&static&String&processRequest(HttpServletRequest&request)&{&&
&&&&&&&&String&respMessage&=&&&
&&&&&&&&try&{&&
&&&&&&&&&&&&//&xml请求解析&&
&&&&&&&&&&&&Map&String,&String&&requestMap&=&MessageUtil.parseXml(request);&&
&&&&&&&&&&&&//&发送方帐号(open_id)&&
&&&&&&&&&&&&String&fromUserName&=&requestMap.get("FromUserName");&&
&&&&&&&&&&&&//&公众帐号&&
&&&&&&&&&&&&String&toUserName&=&requestMap.get("ToUserName");&&
&&&&&&&&&&&&//&消息类型&&
&&&&&&&&&&&&String&msgType&=&requestMap.get("MsgType");&&
&&&&&&&&&&&&//&默认回复此文本消息&&
&&&&&&&&&&&&TextMessage&textMessage&=&new&TextMessage();&&
&&&&&&&&&&&&textMessage.setToUserName(fromUserName);&&
&&&&&&&&&&&&textMessage.setFromUserName(toUserName);&&
&&&&&&&&&&&&textMessage.setCreateTime(new&Date().getTime());&&
&&&&&&&&&&&&textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);&&
&&&&&&&&&&&&textMessage.setFuncFlag(0);&&
&&&&&&&&&&&&//&由于href属性值必须用双引号引起,这与字符串本身的双引号冲突,所以要转义&&
&&&&&&&&&&&&textMessage.setContent("欢迎访问&a&href=\"http://blog.csdn.net/lyq8479\"&柳峰的博客&/a&!");&&
&&&&&&&&&&&&//&将文本消息对象转换成xml字符串&&
&&&&&&&&&&&&respMessage&=&MessageUtil.textMessageToXml(textMessage);&&
&&&&&&&&&&&&//&文本消息&&
&&&&&&&&&&&&if&(msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT))&{&&
&&&&&&&&&&&&&&&&//&接收用户发送的文本消息内容&&
&&&&&&&&&&&&&&&&String&content&=&requestMap.get("Content");&&
&&&&&&&&&&&&&&&&//&创建图文消息&&
&&&&&&&&&&&&&&&&NewsMessage&newsMessage&=&new&NewsMessage();&&
&&&&&&&&&&&&&&&&newsMessage.setToUserName(fromUserName);&&
&&&&&&&&&&&&&&&&newsMessage.setFromUserName(toUserName);&&
&&&&&&&&&&&&&&&&newsMessage.setCreateTime(new&Date().getTime());&&
&&&&&&&&&&&&&&&&newsMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS);&&
&&&&&&&&&&&&&&&&newsMessage.setFuncFlag(0);&&
&&&&&&&&&&&&&&&&List&Article&&articleList&=&new&ArrayList&Article&();&&
&&&&&&&&&&&&&&&&//&单图文消息&&
&&&&&&&&&&&&&&&&if&("1".equals(content))&{&&
&&&&&&&&&&&&&&&&&&&&Article&article&=&new&Article();&&
&&&&&&&&&&&&&&&&&&&&article.setTitle("微信公众帐号开发教程Java

我要回帖

更多关于 百度杀毒 的文章

 

随机推荐