1. 程式人生 > >java開發微信公眾號接受並回復訊息[工程程式碼+圖片全解]

java開發微信公眾號接受並回復訊息[工程程式碼+圖片全解]

寫這篇部落格時猶豫了好久,因為步驟太多了,上班了也沒時間,但是我依然記得當時實現公眾號自動回覆時的場景,找個案例好

難,也沒有一個完整的案例,想了想還是寫出來吧,希望能讓實現這功能的人少走彎路。

微信公眾號平臺也有自定義回覆訊息,比如我在公眾號裡傳送關注你,我們在微信公眾號平臺設定關鍵字關注你(就是

有人傳送這個關鍵字就要回復什麼內容)設定成回覆:你好,java!適用於這種固定資訊,如果我傳送 獲取個人資訊、我的積

分這種內容就需要動態的資料了,所以要使用我們自己的介面往資料庫中進行查詢資訊。

這個專案實現了簡單的回覆文字訊息,沒有 圖片、音訊等型別的傳送、推薦看微信API文件實現,我這也有實現的案例需要的可加我QQ 930496909

首先:

先整理一下大致流程

1.編寫java程式碼,要按照微信公眾號提供的API文件來做。

2.下載ngrok工具,假設編寫的Java都不會部署到網上(本地執行專案),那我們要想微信能訪問我們的java介面我們需要一個工

具(就是將我們本地電腦變成伺服器,讓別人能來訪問我們本地執行的專案說的比較通俗具體可百度~),如果編寫的java程式

部署到伺服器上就不用啦。

3.在微信公眾號平臺註冊服務號或者訂閱號[兩者的區別在於服務號收錢功能多,其他區別可自行百度~]我註冊的是訂閱號,然

後在微信平臺進行一些配置,(其實就是讓關注公眾號的人傳送訊息後能夠對接我們的java介面)

接下來就是步驟了。

看一下java結構目錄

jar包(因為當時在學校做的還不太會用MAVEN管理,無奈啊,jar包主要就是ssm框架jar包和微信的幾個包,我把截圖發下,上面

我提供的碼雲連結上也有jar包)
這裡寫圖片描述
下面就是每個檔案了

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    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_3_0.xsd">
<!-- 配置編碼(解決中文亂碼)過濾器 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- log4j日誌檔案 --> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>classpath:log4j.properties</param-value> </context-param> <listener> <listener-class> org.springframework.web.util.Log4jConfigListener </listener-class> </listener> <!-- 建立springMvc的 dispathServlet --> <servlet> <servlet-name>springMvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup><!-- 伺服器一啟動就載入 --> </servlet> <!--攔截字尾是.do--> <servlet-mapping> <servlet-name>springMvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <!-- 啟動spring容器 spring配置檔案的位置--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext-mybatis.xml</param-value> </context-param> <!-- 監聽spring容器的啟動 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>

springmvc-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

   <context:component-scan base-package="com.qhk.controller"/>

   <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json</value>
                    </list>
                </property>
                <property name="features">
                    <list>
                        <value>WriteDateUseDateFormat</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
   </mvc:annotation-driven>
    <!-- 配置多檢視解析器 -->
    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="favorParameter" value="true"/> 
        <property name="defaultContentType" value="text/html" /> 
        <property name="mediaTypes">
            <map>
                <entry key="html" value="text/html; charset=UTF-8"/>
                <entry key="json" value="application/json; charset=UTF-8"/>
                <entry key="xml" value="application/xml; charset=UTF-8"/>
            </map>
        </property>
        <property name="viewResolvers">
            <list>
                <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                   <property name="prefix" value="/"/>
                   <property name="suffix" value=".jsp"/>
               </bean>
            </list>
        </property>
    </bean>

    <!-- 配置interceptors -->
    <!-- <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/main/**"/>
            <bean class="com.ktv.interceptor.SysInterceptor"/>
        </mvc:interceptor> 

    </mvc:interceptors> -->

    <!-- 配置檔案上傳  MultipartResolver-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="500000000"/>
        <property name="defaultEncoding" value="UTF-8"/>
    </bean> 
</beans>   

applicationContext-mybatis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="   
                http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd   
                http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd   
                http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd 
                http://www.springframework.org/schema/context 
                http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.qhk.service" />
    <context:annotation-config />
    <context:property-placeholder location="classpath:database.properties" />
    <!-- JNDI獲取資料來源(使用dbcp連線池) -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close" scope="singleton">
        <property name="driverClassName" value="${driver}" />
        <property name="url" value="${url}" />
        <property name="username" value="${user}" />
        <property name="password" value="${password}" />
        <property name="initialSize" value="${initialSize}" />
        <property name="maxActive" value="${maxActive}" />
        <property name="maxIdle" value="${maxIdle}" />
        <property name="minIdle" value="${minIdle}" />
        <property name="maxWait" value="${maxWait}" />
        <property name="removeAbandoned" value="${removeAbandoned}" />
        <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
        <property name="testWhileIdle" value="true" />
        <property name="timeBetweenEvictionRunsMillis" value="60000" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="validationQuery" value="select 1" />
        <property name="numTestsPerEvictionRun" value="${maxActive}" />
    </bean>

    <!-- 事務管理 -->
    <bean id="txManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 配置mybatis SqlSessionFactoryBean -->
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis-config.xml" />
    </bean>

    <aop:aspectj-autoproxy />
    <aop:config proxy-target-class="true">
        <aop:pointcut expression="execution(* *com.qhk.service..*(..))"
            id="transService" />
        <aop:advisor advice-ref="myAdvice" pointcut-ref="transService" />
    </aop:config>

    <tx:advice id="myAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="find*" read-only="true" propagation="SUPPORTS" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="update*"  propagation="REQUIRED" />
            <tx:method name="del*"  propagation="REQUIRED" />
            <tx:method name="*" />
        </tx:attributes>
    </tx:advice>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"  >
        <property name="basePackage" value="com.qhk.dao" />
    </bean>

</beans>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE configuration   
 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"   
 "http://mybatis.org/dtd/mybatis-3-config.dtd">  
<configuration>  
     <settings>  
         <!-- changes from the defaults -->  
         <setting name="lazyLoadingEnabled" value="false" />  
     </settings>  
     <typeAliases>  
         <!--這裡給實體類取別名,方便在mapper配置檔案中使用--> 
         <package name="com.qhk.entity"/>
     </typeAliases> 
</configuration>  

database.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/ktvsystem?useUnicode=true&characterEncoding=utf-8
user=root
password=admin
minIdle=45
maxIdle=50
initialSize=5
maxActive=100
maxWait=100
removeAbandonedTimeout=240
removeAbandoned=true

log4j.properties

log4j.rootLogger=debug,CONSOLE,file
#log4j.rootLogger=ERROR,ROLLING_FILE

log4j.logger.cn.smbms=debug
log4j.logger.org.apache.ibatis=debug
log4j.logger.org.mybatis.spring=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
log4j.logger.java.sql.ResultSet=debug

######################################################################################
# Console Appender  \u65e5\u5fd7\u5728\u63a7\u5236\u8f93\u51fa\u914d\u7f6e
######################################################################################
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=debug
log4j.appender.CONSOLE.DatePattern=yyyy-MM-dd
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= - (%r ms) - %d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%n



######################################################################################
# Rolling File  \u6587\u4ef6\u5927\u5c0f\u5230\u8fbe\u6307\u5b9a\u5c3a\u5bf8\u7684\u65f6\u5019\u4ea7\u751f\u4e00\u4e2a\u65b0\u7684\u6587\u4ef6
######################################################################################
#log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
#log4j.appender.ROLLING_FILE.Threshold=INFO
#log4j.appender.ROLLING_FILE.File=${baojia.root}/logs/log.log
#log4j.appender.ROLLING_FILE.Append=true
#log4j.appender.ROLLING_FILE.MaxFileSize=5000KB
#log4j.appender.ROLLING_FILE.MaxBackupIndex=100
#log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
#log4j.appender.ROLLING_FILE.layout.ConversionPattern=%d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%n

######################################################################################
# DailyRolling File  \u6bcf\u5929\u4ea7\u751f\u4e00\u4e2a\u65e5\u5fd7\u6587\u4ef6\uff0c\u6587\u4ef6\u540d\u683c\u5f0f:log2009-09-11
######################################################################################
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern=yyyy-MM-dd
log4j.appender.file.File=${AppInfoSystem.root}/logs/log.log
log4j.appender.file.Append=true
log4j.appender.file.Threshold=debug
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern= - (%r ms) - %d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%n

#DWR \u65e5\u5fd7
#log4j.logger.org.directwebremoting = ERROR

#\u663e\u793aHibernate\u5360\u4f4d\u7b26\u7ed1\u5b9a\u503c\u53ca\u8fd4\u56de\u503c
#log4j.logger.org.hibernate.type=DEBUG,CONSOLE 

#log4j.logger.org.springframework.transaction=DEBUG
#log4j.logger.org.hibernate=DEBUG
#log4j.logger.org.acegisecurity=DEBUG
#log4j.logger.org.apache.myfaces=TRACE
#log4j.logger.org.quartz=DEBUG

#log4j.logger.com.opensymphony=INFO  
#log4j.logger.org.apache.struts2=DEBUG  
log4j.logger.com.opensymphony.xwork2=debug

WeiXinController.java

package com.qhk.controller;

import java.util.Map;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.qhk.util.CheckUtil;
import com.qhk.util.MessageFormat;
import com.qhk.util.MessageUtil;

@Controller
@RequestMapping("/weixin")
public class WeiXinController {
    /**
     * <h4>功能:[微信驗證 ][2018年2月9日 下午10:01:14][建立人: HongKun.Qin]</h4>
     * <h4></h4>
     * @param request
     * @param response
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "/message.do",method =RequestMethod.GET)
    public String getMessageValidate(HttpServletRequest request, HttpServletResponse response){
        String signature = request.getParameter("signature");//微信加密簽名,signature結合了開發者填寫的token引數和請求中的timestamp引數、nonce引數。
        String timestamp = request.getParameter("timestamp");// 時間戳
        String nonce = request.getParameter("nonce");// 隨機數
        String echostr = request.getParameter("echostr");// 隨機字串

        if(CheckUtil.checkSignature(signature, timestamp, nonce)){
            return echostr;
        }

        return "";
    }

    /**
     * <h4>功能:[接受訊息,並返回訊息 ][2018年2月9日 下午10:02:00][建立人: HongKun.Qin]</h4>
     * <h4></h4>
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping(value = "/message.do",method =RequestMethod.POST)
    public String getMessage(HttpServletRequest request, HttpServletResponse response) throws Exception{
        Map<String,String> map = new MessageFormat().xmlToMap(request);

        String fromUserName = map.get("FromUserName");//公眾號
        String toUserName = map.get("ToUserName");//粉絲號
        String msgType = map.get("MsgType");//傳送的訊息型別[比如 文字,圖片,語音。。。]
        String content = map.get("Content");//傳送的訊息內容
        String message = null;

        System.out.println("fromUserName:"+fromUserName+"   ToUserName:"+toUserName+"  MsgType:"+msgType+"  "+content);

        //判斷髮送的型別是文字
        if(MessageUtil.MESSAGE_TEXT.equals(msgType)){
            //傳送的內容為???時
            if("0".equals(content)){
                message = MessageFormat.initText(toUserName, fromUserName, MessageUtil.menuText());
            }else if("1".equals(content)) {
                Random random = new Random();
                message = MessageFormat.initText(toUserName, fromUserName, String.format("您本次的驗證碼為:%s%s%s%s", random.nextInt(10),random.nextInt(10),random.nextInt(10),random.nextInt(10)));//模擬驗證碼
            }else{
                message  = MessageFormat.initText(toUserName, fromUserName, "功能正在完善中,請按提示資訊操作[回覆'0'顯示主選單]。");
            }
        }else if(MessageUtil.MESSAGE_EVENT.equals(msgType)){//驗證是關注/取消事件
            String eventType = map.get("Event");//獲取是關注還是取消
            //關注
            if(MessageUtil.MESSAGE_SUBSCRIBE.equals(eventType)){
                message = MessageFormat.initText(toUserName, fromUserName, "歡迎關注青鳥ktv,回覆[0]即可調出功能選單");
            }
        }

        return message;

    }


}

AccessToken.java

package com.qhk.entity;

public class AccessToken {

    private String token;
    private int expiresIn;

    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }
    public int getExpiresIn() {
        return expiresIn;
    }
    public void setExpiresIn(int expiresIn) {
        this.expiresIn = expiresIn;
    }
}

BaseMessage.java

package com.qhk.entity;

public class BaseMessage {
    private String ToUserName;
    private String FromUserName;
    private long CreateTime;
    private String MsgType;
    public String getToUserName() {
        return ToUserName;
    }
    public void setToUserName(String toUserName) {
        ToUserName = toUserName;
    }
    public String getFromUserName() {
        return FromUserName;
    }
    public void setFromUserName(String fromUserName) {
        FromUserName = fromUserName;
    }
    public long getCreateTime() {
        return CreateTime;
    }
    public void setCreateTime(long createTime) {
        CreateTime = createTime;
    }
    public String getMsgType() {
        return MsgType;
    }
    public void setMsgType(String msgType) {
        MsgType = msgType;
    }
}

TextMessage.java

package com.qhk.entity;

public class TextMessage extends BaseMessage{

    private String Content;
    private String MsgId;

    public String getContent() {
        return Content;
    }
    public void setContent(String content) {
        Content = content;
    }
    public String getMsgId() {
        return MsgId;
    }
    public void setMsgId(String msgId) {
        MsgId = msgId;
    }
}

CheckUtil.java(token注意,我現在是qhk後面微信公眾號平臺配置也要用這個,自己改了的話就填自己改的)

package com.qhk.util;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

public class CheckUtil {
    public static final String token = "qhk";//這個地方也要注意
    public static boolean checkSignature(String signature,String timestamp,String nonce){
        String[] arr=new String[]{token,timestamp,nonce};
        //排序
        Arrays.sort(arr);
        //生成字串
        StringBuffer content = new StringBuffer();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        //sha1加密
        String temp = getSha1(content.toString());
        return temp.equals(signature);
    }
    public static String getSha1(String str){
        if (null == str || 0 == str.length()){
            return null;
        }
        char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
                'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));

            byte[] md = mdTemp.digest();
            int j = md.length;
            char[] buf = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

}

MessageFormat.java

package com.qhk.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.qhk.entity.TextMessage;
import com.thoughtworks.xstream.XStream;

/**
 * <p>將傳送的訊息進行轉換</p>
 * @author HongKun.Qin
 */
public class MessageFormat {
    /**
     * xml 轉 map
     * 
     * @param request
     * @return
     * @throws IOException
     * @throws DocumentException
     */
    public static Map<String, String> xmlToMap(HttpServletRequest request)
            throws IOException, DocumentException {
        Map<String, String> map = new HashMap<String, String>();
        SAXReader reader = new SAXReader();
        InputStream ins = request.getInputStream();
        Document doc = reader.read(ins);
        Element root = doc.getRootElement();
        List<Element> list = root.elements();
        for (Element e : list) {
            map.put(e.getName(), e.getText());
        }
        ins.close();
        return map;
    }

    /**
     * 將文字訊息轉換為xml
     * 
     * @param textMessage
     * @return
     */
    public static String textMessageToXml(TextMessage textMessage) {
        XStream xStream = new XStream();
        xStream.alias("xml", textMessage.getClass());
        return xStream.toXML(textMessage);
    }


    public static String initText(String toUserName, String fromUserName,
            String content) {
        TextMessage text = new TextMessage();
        text.setFromUserName(toUserName);
        text.setToUserName(fromUserName);
        text.setMsgType(MessageUtil.MESSAGE_TEXT);
        text.setCreateTime(new Date().getTime());
        text.setContent(content);
        return textMessageToXml(text);
    }
}

MessageUtil.java

package com.qhk.util;

public class MessageUtil {
    /**
     * 型別
     */
    public static final String MESSAGE_TEXT = "text";//文字
    public static final String MESSAGE_NEWS = "news";
    public static final String MESSAGE_IMAGE = "image";
    public static final String MESSAGE_MUSIC = "music";
    public static final String MESSAGE_VOICE = "voice";
    public static final String MESSAGE_VIDEO = "video";
    public static final String MESSAGE_LINK = "link";
    public static final String MESSAGE_LOCATION = "location";
    public static final String MESSAGE_EVENT = "event";
    public static final String MESSAGE_SUBSCRIBE = "subscribe";
    public static final String MESSAGE_UNSUBSCRIBE = "unsubscribe";
    public static final String MESSAGE_CLICK = "CLICK";
    public static final String MESSAGE_VIEW = "VIEW";
    public static final String MESSAGE_SCANCODE = "scancode_push";


    /**
     * <h4>功能:[顯示的主選單 ][2018年2月9日 下午9:37:56][建立人: HongKun.Qin]</h4>
     * <h4>&l