对柳峰博主的微信公众号后台示例的部分重构

柳峰博主的专栏(http://blog.csdn.net/column/details/wechatmp.html)和王信平博主的专栏(http://www.cnblogs.com/wangshuo1/)对微信公众号开发已经做了比较详尽的阐述,基本上照搬,就可以做出第一个微信公众号后台应用。但是在『照搬』的过程中,发现有些地方总是觉得别扭,由着完美主义者的性格使然,对以下这几个地方做个小优化吧。

一、区别消息和响应

原来的消息类(用户发给后台)和响应类(后台回给用户)有这些:

对柳峰博主的微信公众号后台示例的部分重构

我改成了这样的:

对柳峰博主的微信公众号后台示例的部分重构

我把『消息Message』和『响应Response』在命名上分开了。这样在编程时不会总是想着要去区别,这个『Message』究竟是用户发给后台的『消息』呢,还是后台回给用户的『响应』。

 

二、给消息和响应添加构造函数

原来消息和响应都只是使用缺省构造函数(详见http://blog.csdn.net/lyq8479/article/details/8949088),实际上是把这两个类当做了『结构体』来看待。基于面向对象的思想,还是觉得需要有构造函数。

2.1 BaseMessage类的构造函数

对柳峰博主的微信公众号后台示例的部分重构
 1    public BaseMessage(String toUserName, String fromUserName, String createTime, String msgType, String msgId){
 2         this.ToUserName=toUserName;
 3         this.FromUserName=fromUserName;
 4         this.CreateTime=Long.parseLong(createTime);
 5         this.MsgType=msgType;
 6         this.MsgId=Long.parseLong(msgId);
 7     }
 8 
 9     public BaseMessage(Map<String, String> mapMessage){
10         try {
11             // 开发者微信号
12             String toUserName = mapMessage.get("ToUserName");
13             // 发送方帐号(open_id)
14             String fromUserName = mapMessage.get("FromUserName");
15             // 消息类型
16             String msgType = mapMessage.get("MsgType");
17             // 建立时间
18             String createTime = mapMessage.get("CreateTime");
19             // 消息ID
20             String msgId = mapMessage.get("MsgId");
21 
22             this.ToUserName = toUserName;
23             this.FromUserName = fromUserName;
24             this.CreateTime = Long.parseLong(createTime);
25             this.MsgType = msgType;
26             this.MsgId = Long.parseLong(msgId);
27         }
28         catch (Exception e) {
29             e.printStackTrace();
30         }
31     }
View Code

2.2 ImageMessage类的构造函数

对柳峰博主的微信公众号后台示例的部分重构
 1     public ImageMessage(String toUserName,
 2                         String fromUserName,
 3                         String createTime,
 4                         String msgType,
 5                         String msgId,
 6                         String picUrl) {
 7         super(toUserName,fromUserName,createTime,msgType,msgId);
 8         assert (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE));
 9         this.PicUrl=picUrl;
10     }
11 
12     public ImageMessage(Map<String, String> mapMessage){
13         super(mapMessage);
14         assert (this.getMsgType().equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE));
15         this.PicUrl=mapMessage.get("PicUrl");
16     }
View Code

2.3 TextMessage类的构造函数

对柳峰博主的微信公众号后台示例的部分重构
 1     public TextMessage(String toUserName,
 2                        String fromUserName,
 3                        String createTime,
 4                        String msgType,
 5                        String msgId,
 6                        String content) {
 7         super(toUserName,fromUserName,createTime,msgType,msgId);
 8         assert (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT));
 9         this.Content=content;
10 
11     }
12 
13     public TextMessage(Map<String, String> mapMessage){
14         super(mapMessage);
15         assert (this.getMsgType().equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT));
16         this.Content=mapMessage.get("Content");
17     }
View Code

这里只列举了消息类的基类、图片消息、文本消息三个构造函数,其余的位置消息、声音消息、短视频、视频、链接消息的构造函数类似。

2.4 BaseResponse类的构造函数(这个有点意思)

 1     public BaseResponse(BaseMessage baseMessage){
 2         this.ToUserName=baseMessage.getFromUserName(); //返回的目标是当初的发送源
 3         this.FromUserName=baseMessage.getToUserName(); //发送的来源是当初的发送目标
 4         this.MsgType= MessageUtil.RESP_MESSAGE_TYPE_TEXT; // 默认返回消息为文本
 5         this.CreateTime=new Date().getTime(); //默认为当前时间
 6         this.FuncFlag=0; //默认为非星标
 7     }
 8 
 9     public BaseResponse(String toUserName,String fromUserName, String msgType){
10         this.ToUserName=toUserName;
11         this.FromUserName=fromUserName;
12         this.MsgType=msgType;
13         this.CreateTime=new Date().getTime(); //默认为当前时间
14         this.FuncFlag=0; //默认为非星标
15     }

响应类的基类的构造函数,我使用了消息类的基类作为参数,是因为响应消息的返回目标,就是当初的发送源(用户),响应消息的发送源,其实就是当初的发送目标(微信后台程序)。

2.5 TextResponse类的构造函数

对柳峰博主的微信公众号后台示例的部分重构
 1     public TextResponse(String toUserName,String fromUserName, String msgType,String content){
 2         super(toUserName,fromUserName,msgType);
 3         this.Content=content;
 4     }
 5     public TextResponse(BaseMessage baseMessage,
 6                          String content){
 7         super(baseMessage);
 8         this.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
 9         this.Content=content;
10     }
View Code

2.6 Article类的构造函数

对柳峰博主的微信公众号后台示例的部分重构
1     public Article(String title, String description, String picUrl, String url) {
2         this.Title=title;
3         this.Description=description;
4         this.PicUrl=picUrl;
5         this.Url=url;
6     }
View Code

2.7 NewsResponse类的构造函数

对柳峰博主的微信公众号后台示例的部分重构
 1 public class NewsResponse extends BaseResponse {
 2     // 图文消息个数,限制为10条以内
 3     private int ArticleCount;
 4     // 多条图文消息信息,默认第一个item为大图
 5     private List<Article> Articles;
 6 
 7     public NewsResponse(String toUserName,String fromUserName, String msgType){
 8         super(toUserName,fromUserName,msgType);
 9         ArticleCount=0;
10         this.Articles = new ArrayList<Article>();
11     }
12     public NewsResponse(BaseMessage baseMessage){
13         super(baseMessage);
14         this.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS);
15         ArticleCount=0;
16         this.Articles = new ArrayList<Article>();
17     }
18 
19     //如果已经达到10篇文章,图文消息不可以再增加新的文章。返回FALSE;
20     public boolean addArticle(Article article) {
21         if (ArticleCount>=10){
22             return false;
23         }
24         Articles.add(article);
25         ArticleCount++;
26         return true;
27     }
28     public Article getArticle(int articleIndex){
29         return Articles.get(articleIndex);
30     }
31 
32     public int getArticleCount() {
33         return ArticleCount;
34     }
35 
36 //    public void setArticleCount(int articleCount) {
37 //        ArticleCount = articleCount;
38 //    }
39 
40     public List<Article> getArticles() {
41         return Articles;
42     }
43 
44 //    public void setArticles(List<Article> articles) {
45 //        Articles = articles;
46 //    }
47 }
View Code

这个NewsResponse类也要说一下,原来的设计也够奇葩的,居然直接把List结构暴露出来对其读写,List里的Article的数量,可以单独赋值,呵呵。于是果断注释掉计数变量的读写方法,新增了addArticle和getArticle这两个方法,用以从List中添加和获取Article,计算变量,应该自动累加,不应该对其手工赋值。

MusicResponse类的改造类似,不细说了。

 

三、归一化命名

原来的响应消息转成XML有三个函数,分别是textMessageToXml、musicMessageToXml、newsMessageToXml(详见http://blog.csdn.net/lyq8479/article/details/8949088),我把它们的命名归一为responseToXml,调用的时候,也不用区别传进去的是啥响应消息来选择不同名字的函数,省心。

对柳峰博主的微信公众号后台示例的部分重构
 1     /**
 2      * 文本消息对象转换成xml
 3      *
 4      * @param textMessage 文本消息对象
 5      * @return xml
 6      */
 7     public static String responseToXml(TextResponse textMessage) {
 8         xstream.alias("xml", textMessage.getClass());
 9         return xstream.toXML(textMessage);
10     }
11 
12     /**
13      * 音乐消息对象转换成xml
14      *
15      * @param musicMessage 音乐消息对象
16      * @return xml
17      */
18     public static String responseToXml(MusicResponse musicMessage) {
19         xstream.alias("xml", musicMessage.getClass());
20         return xstream.toXML(musicMessage);
21     }
22 
23     /**
24      * 图文消息对象转换成xml
25      *
26      * @param newsMessage 图文消息对象
27      * @return xml
28      */
29     public static String responseToXml(NewsResponse newsMessage) {
30         xstream.alias("xml", newsMessage.getClass());
31         xstream.alias("item", newsMessage.getArticle(0).getClass());
32         return xstream.toXML(newsMessage);
33     }
View Code

 

四、构造消息路由

原来的消息处理的主函数(详见http://blog.csdn.net/lyq8479/article/details/9393195),在一个超级巨大的processRequest函数中对消息进行处理,一堆if else if 下来,眼都花了,所以我简化了processRequest函数,只在这个函数里面构造消息路由,针对不同类型的消息,转去不同的处理逻辑中。

 1    //定义一个logger,用于发送日志,可以在console或者服务器上看到。存储目录在log4j.properties中定义
 2     private static final Logger logger = Logger.getLogger(CoreService.class);
 3 
 4     //处理请求
 5     public static String processRequest(HttpServletRequest request){
 6         String returnMsg=null;
 7         try{
 8             Map<String, String> requestMap = MessageUtil.parseXml(request); //解析XML
 9             String msgType = requestMap.get("MsgType"); //消息类型
10             logger.info("***step1 msgType=" + msgType);
11             switch (msgType){
12                 case MessageUtil.REQ_MESSAGE_TYPE_TEXT:
13                     returnMsg=HandleTextMsg(new TextMessage(requestMap));
14                     break;
15                 case MessageUtil.REQ_MESSAGE_TYPE_IMAGE:
16                     returnMsg=HandleImageMsg(new ImageMessage(requestMap));
17                     break;
18                 case MessageUtil.REQ_MESSAGE_TYPE_LINK:
19                     returnMsg=HandleLinkMsg(new LinkMessage(requestMap));
20                     break;
21                 case MessageUtil.REQ_MESSAGE_TYPE_LOCATION:
22                     returnMsg=HandleLocationMsg(new LocationMessage(requestMap));
23                     break;
24                 case MessageUtil.REQ_MESSAGE_TYPE_SHORT_VIDEO:
25                     returnMsg=HandleShortVideoMsg(new ShortVideoMessage(requestMap));
26                     break;
27                 case MessageUtil.REQ_MESSAGE_TYPE_VIDEO:
28                     returnMsg=HandleVideoMsg(new VideoMessage(requestMap));
29                     break;
30                 case MessageUtil.REQ_MESSAGE_TYPE_VOICE:
31                     returnMsg=HandleVoiceMsg(new VoiceMessage(requestMap));
32                     break;
33                 case MessageUtil.REQ_MESSAGE_TYPE_EVENT:
34                     // 事件类型
35                     String eventType = requestMap.get("Event");
36                     switch (eventType){
37                         case MessageUtil.EVENT_TYPE_SUBSCRIBE:
38                             returnMsg=HandleSubscribeEvent(new BaseMessage(requestMap));
39                             break;
40                         case MessageUtil.EVENT_TYPE_UNSUBSCRIBE:
41                             returnMsg=HandleUnsubscribeEvent(new BaseMessage(requestMap));
42                             break;
43                         case MessageUtil.EVENT_TYPE_CLICK:
44                             returnMsg=HandleClickEvent(new BaseMessage(requestMap));
45                             break;
46                         default:
47                             returnMsg=HandleDefaultMsgOrEvent(new BaseMessage(requestMap));
48                     }
49                     break;
50                 default:
51                     returnMsg=HandleDefaultMsgOrEvent(new BaseMessage(requestMap));
52             }
53         }catch (Exception e) {
54             e.printStackTrace();
55         }
56         return returnMsg;
57     }

 

下面是对各种消息的handle函数

对柳峰博主的微信公众号后台示例的部分重构
  1     public static String HandleTextMsg(TextMessage textMessage)
  2     {
  3         String respContent = "您发送的是文本消息!";
  4         if (textMessage.getContent().indexOf("/:") > -1)
  5             respContent="你"+textMessage.getContent()+"什么?";
  6         TextResponse textResponse=new TextResponse(textMessage, respContent);
  7         String respMsg=MessageUtil.responseToXml(textResponse);
  8         logger.info("***step2 Response=" + respMsg);
  9         return respMsg;
 10     }
 11     public static String HandleImageMsg(ImageMessage imageMessage)
 12     {
 13         String title="鉴定结果";
 14         String picUrl=imageMessage.getPicUrl();
 15         String forwardUrl= HttpTools.imagePlusPlusPath+picUrl;
 16         logger.info("***step2 image++ Url=" + forwardUrl);
 17 
 18         //从image++网站识别上传的图片的拍摄地点、拍摄物体。
 19         String imagePlusPlusReturn=getHttpResponse(forwardUrl);
 20         logger.info("***step3 image++ Response=" + imagePlusPlusReturn);
 21         String description = ReadJSONString(imagePlusPlusReturn);
 22         logger.info("***step4 image++ Response=" + description);
 23 
 24         Article article=new Article(title,description,picUrl,forwardUrl);
 25         NewsResponse newsResponse=new NewsResponse(imageMessage);
 26         newsResponse.addArticle(article);
 27         String respMsg=MessageUtil.responseToXml(newsResponse);
 28         logger.info("***step5  Weixin Response=" + respMsg);
 29         return respMsg;
 30     }
 31     public static String HandleLinkMsg(LinkMessage linkMessage)
 32     {
 33         String respContent = "您发送的是链接!";
 34         TextResponse textResponse=new TextResponse(linkMessage, respContent);
 35         String respMsg=MessageUtil.responseToXml(textResponse);
 36         logger.info("***step2 Response=" + respMsg);
 37         return respMsg;
 38     }
 39     public static String HandleLocationMsg(LocationMessage locationMessage)
 40     {
 41         String respContent = "您发送的是地理位置!";
 42         TextResponse textResponse=new TextResponse(locationMessage, respContent);
 43         String respMsg=MessageUtil.responseToXml(textResponse);
 44         logger.info("***step2 Response=" + respMsg);
 45         return respMsg;
 46     }
 47     public static String HandleShortVideoMsg(ShortVideoMessage shortVideoMessage)
 48     {
 49         String respContent = "您发送的是小视频!";
 50         TextResponse textResponse=new TextResponse(shortVideoMessage, respContent);
 51         String respMsg=MessageUtil.responseToXml(textResponse);
 52         logger.info("***step2 Response=" + respMsg);
 53         return respMsg;
 54     }
 55     public static String HandleVideoMsg(VideoMessage videoMessage)
 56     {
 57         String respContent = "您发送的是视频!";
 58         TextResponse textResponse=new TextResponse(videoMessage, respContent);
 59         String respMsg=MessageUtil.responseToXml(textResponse);
 60         logger.info("***step2 Response=" + respMsg);
 61         return respMsg;
 62     }
 63     public static String HandleVoiceMsg(VoiceMessage voiceMessage)
 64     {
 65         String respContent = "您发送的是语音!";
 66         TextResponse textResponse=new TextResponse(voiceMessage, respContent);
 67         String respMsg=MessageUtil.responseToXml(textResponse);
 68         logger.info("***step2 Response=" + respMsg);
 69         return respMsg;
 70     }
 71     public static String HandleSubscribeEvent(BaseMessage baseMessage)
 72     {
 73         String respContent = "欢迎订阅!";
 74         TextResponse textResponse=new TextResponse(baseMessage, respContent);
 75         String respMsg=MessageUtil.responseToXml(textResponse);
 76         logger.info("***step2 Response=" + respMsg);
 77         return respMsg;
 78     }
 79     public static String HandleUnsubscribeEvent(BaseMessage baseMessage)
 80     {
 81         String respContent = "再见,希望您再来!";
 82         TextResponse textResponse=new TextResponse(baseMessage, respContent);
 83         String respMsg=MessageUtil.responseToXml(textResponse);
 84         logger.info("***step2 Response=" + respMsg);
 85         return respMsg;
 86     }
 87     public static String HandleClickEvent(BaseMessage baseMessage)
 88     {
 89         String respContent = "您点击了自定义菜单!";
 90         TextResponse textResponse=new TextResponse(baseMessage, respContent);
 91         String respMsg=MessageUtil.responseToXml(textResponse);
 92         logger.info("***step2 Response=" + respMsg);
 93         return respMsg;
 94     }
 95     public static String HandleDefaultMsgOrEvent(BaseMessage baseMessage)
 96     {
 97         String respContent = "请求不可识别,请稍候尝试!";
 98         TextResponse textResponse=new TextResponse(baseMessage, respContent);
 99         String respMsg=MessageUtil.responseToXml(textResponse);
100         logger.info("***step2 Response=" + respMsg);
101         return respMsg;
102     }
View Code

五、应用:试试图像识别

我的第一个微信公众号后台应用,是识别用户上传的图片,是什么东西,在哪里拍摄的。调用的是image++网站的api,核心代码如下:

对柳峰博主的微信公众号后台示例的部分重构
 1 /*
 2 读取一个网址的所有返回内容
 3  */
 4     public static String getHttpResponse(String connectUrl) {
 5         BufferedReader in = null;
 6         StringBuffer result = null;
 7         try {
 8             URI uri = new URI(connectUrl);
 9             URL url = uri.toURL();
10             URLConnection connection = url.openConnection();
11             connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
12             connection.setRequestProperty("Charset", "utf-8");
13             connection.connect();
14 
15             result = new StringBuffer();
16             //读取URL的响应
17             in = new BufferedReader(new InputStreamReader(
18                     connection.getInputStream()));
19             String line;
20             while ((line = in.readLine()) != null) {
21                 result.append(line);
22             }
23 
24             return result.toString();
25 
26         } catch (Exception e) {
27             e.printStackTrace();
28         }finally {
29             try {
30                 if (in != null) {
31                     in.close();
32                 }
33             } catch (Exception e2) {
34                 e2.printStackTrace();
35             }
36         }
37         return null;
38     }
39 
40     /*
41     从JSON字符串中读取某些关键信息
42     JSON文件来自:http://www.imageplusplus.com/doc
43      */
44     public static String ReadJSONString(String jSonString){
45         try {
46             int imageConfidenceIndex=jSonString.indexOf("confidence",jSonString.indexOf("image"));
47             int imageConfidenceIndexEnd=jSonString.indexOf(",",imageConfidenceIndex);
48             String imageConfidence=jSonString.substring(imageConfidenceIndex+12,imageConfidenceIndexEnd);
49             int imageConfidencePercent=(int)(Float.parseFloat(imageConfidence)*100);
50             int imageValueIndex=jSonString.indexOf("value",jSonString.indexOf("image"));
51             int imageValueIndexEnd=jSonString.indexOf("}",imageValueIndex)-1;
52             String imageValue=jSonString.substring(imageValueIndex+8,imageValueIndexEnd).trim();
53 
54             int sceneConfidenceIndex=jSonString.indexOf("confidence",jSonString.indexOf("scene"));
55             int sceneConfidenceIndexEnd=jSonString.indexOf(",",sceneConfidenceIndex);
56             String sceneConfidence=jSonString.substring(sceneConfidenceIndex+12,sceneConfidenceIndexEnd);
57             int sceneConfidencePercent=(int)(Float.parseFloat(sceneConfidence)*100);
58             int sceneValueIndex=jSonString.indexOf("value",jSonString.indexOf("scene"));
59             int sceneValueIndexEnd=jSonString.indexOf("}",sceneValueIndex)-1;
60             String sceneValue=jSonString.substring(sceneValueIndex+8,sceneValueIndexEnd).trim();
61 
62             String result="这张照片有【"+imageConfidencePercent+"%】的可能是【"+imageValue+"】," +
63                     "有【"+sceneConfidencePercent+"%】的可能是在【"+sceneValue+"】拍摄的。";
64             return result;
65         } catch (Exception e) {
66             e.printStackTrace();
67         }
68         return "服务器秀逗了,请重试!";
69     }
70 }
View Code

全部的源代码,我放在了这里:20161013微信公众号后台应用示例src.zip

示例源码中用到几个外部jar lib,去官网下载,然后扔进项目里面就好。

对柳峰博主的微信公众号后台示例的部分重构

 

对柳峰博主的微信公众号后台示例的部分重构

上一篇:微信小程序组件text


下一篇:从零开始学android