基於之前的文章SSM配置的項目:http://www.cnblogs.com/mangyang/p/5168291.html
來進行微信第三方開發,
微信二次開發,官方還是網上有很多介紹了,這里就不在進行講述了 直接上干貨。

首先 與微信對接,服務器配置,需要80端口和443端口開放的服務器,這里推薦 使用 python 的pagekite,一款反向代理的工具,具體安裝百度搜,提供下配置放方法:http://jingyan.baidu.com/article/0eb457e52ca0af03f0a90568.html
配置好了之后,使用maven 或者 tomcat 等中間件 啟動咱們的項目,訪問之前注冊的 pagekite地址,試一下,正常訪問就進行下一步,
一、服務器配置認證
首先根據官方文檔得知,
1)將token、timestamp、nonce三個參數進行字典序排序
2)將三個參數字符串拼接成一個字符串進行sha1加密
3)開發者獲得加密后的字符串可與signature對比,標識該請求來源於微信
SHA1.java
package com.wx.util;
/*
* 微信公眾平台(JAVA) SDK
*
* Copyright (c) 2014, Ansitech Network Technology Co.,Ltd All rights reserved.
* http://www.ansitech.com/weixin/sdk/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.security.MessageDigest;
/**
* <p>
* Title: SHA1算法
* </p>
*
*/
public final class SHA1 {
private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* Takes the raw bytes from the digest and formats them correct.
*
* @param bytes
* the raw bytes from the digest.
* @return the formatted bytes.
*/
private static String getFormattedText(byte[] bytes) {
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
// 把密文轉換成十六進制的字符串形式
for (int j = 0; j < len; j++) {
buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
}
return buf.toString();
}
public static String encode(String str) {
if (str == null) {
return null;
}
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
messageDigest.update(str.getBytes());
return getFormattedText(messageDigest.digest());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
然后編寫controller 來接收 微信的 認證請求
WxManagerController.java
package com.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.wx.util.SHA1;
@Controller
@RequestMapping( "/wx" )
public class WxManagerController {
private String Token = "testfortoken";
@RequestMapping(value = "", method = { RequestMethod.GET, RequestMethod.POST })
@ResponseBody
public void load(Model model, HttpServletRequest request, HttpServletResponse response) {
//判斷訪問方式
boolean isGet = request.getMethod().toLowerCase().equals("get");
if (isGet) {
//進行認證
access(request, response);
} else {
//處理微信post請求
}
}
/**
* 驗證URL真實性
*
* @param request
* @param response
* @return String
*/
private String access(HttpServletRequest request, HttpServletResponse response) {
// 驗證URL真實性
String signature = request.getParameter("signature");// 微信加密簽名
String timestamp = request.getParameter("timestamp");// 時間戳
String nonce = request.getParameter("nonce");// 隨機數
String echostr = request.getParameter("echostr");// 隨機字符串
List<String> params = new ArrayList<String>();
params.add(Token);
params.add(timestamp);
params.add(nonce);
// 1. 將token、timestamp、nonce三個參數進行字典序排序
Collections.sort(params, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
// 2. 將三個參數字符串拼接成一個字符串進行sha1加密
String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2));
if (temp.equals(signature)) {
try {
response.getWriter().write(echostr);
return echostr;
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
OK,到微信公眾號后台,開發-基本配置-修改配置

token是項目里自己填寫的參數,一致即可,
EncodingAESKey 隨機生成即可,
點擊提交。、

成功!
下面做一些功能,用戶消息處理以及事件處理,
二、消息,事件處理
根據官方文檔得知,處理微信的post請求,微信是以xml格式發送的,所以要解析xml並做處理,這里使用XStream
maven 使用的 oschina庫 搜不到 xstream-1.4.8 版本 手動下載了一個 下載鏈接:http://pan.baidu.com/s/1dEAdDVb
其他的maven 可直接pom里配置。
pom.xml
<dependency>
<groupId>xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.8</version>
<scope>system</scope>
<systemPath>${project.basedir}/WebContent/WEB-INF/lib/xstream-1.4.8.jar</systemPath>
</dependency>
<dependency>
<groupId>xmlpull</groupId>
<artifactId>xmlpull</artifactId>
<version>1.1.3.1</version>
</dependency>
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3</artifactId>
<version>1.1.4c</version>
</dependency>
xstream 下的 systemPath 填寫項目里對應的jar位置。
開始編寫代碼
加入 CDATA 驗證創建的@interface類
XStreamCDATA.java
package com.penuel.mythopoet.wx.manager;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface XStreamCDATA {
}
XStream工具類
SerializeXmlUtil.java
package com.wx.util;
import java.io.Writer;
import java.lang.reflect.Field;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
/**
* xml 轉換工具類
*/
public class SerializeXmlUtil {
public static XStream createXstream() {
return new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
boolean cdata = false;
Class<?> targetClass = null;
@Override
public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
super.startNode(name, clazz);
// 業務處理,對於用XStreamCDATA標記的Field,需要加上CDATA標簽
if (!name.equals("xml")) {
cdata = needCDATA(targetClass, name);
} else {
targetClass = clazz;
}
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
}
private static boolean needCDATA(Class<?> targetClass, String fieldAlias) {
boolean cdata = false;
// first, scan self
cdata = existsCDATA(targetClass, fieldAlias);
if (cdata)
return cdata;
// if cdata is false, scan supperClass until java.lang.Object
Class<?> superClass = targetClass.getSuperclass();
while (!superClass.equals(Object.class)) {
cdata = existsCDATA(superClass, fieldAlias);
if (cdata)
return cdata;
superClass = superClass.getClass().getSuperclass();
}
return false;
}
private static boolean existsCDATA(Class<?> clazz, String fieldAlias) {
if ("MediaId".equals(fieldAlias)) {
return true; // 特例添加 morning99
}
// scan fields
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 1. exists XStreamCDATA
if (field.getAnnotation(XStreamCDATA.class) != null) {
XStreamAlias xStreamAlias = field.getAnnotation(XStreamAlias.class);
// 2. exists XStreamAlias
if (null != xStreamAlias) {
if (fieldAlias.equals(xStreamAlias.value()))// matched
return true;
} else {// not exists XStreamAlias
if (fieldAlias.equals(field.getName()))
return true;
}
}
}
return false;
}
}
微信誤參數字典
WxErrorCode.java
package com.wx.util;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
public class WxErrorCode {
@SuppressWarnings("rawtypes")
public static final Map ERRORCODE=new HashMap<Integer,String>();
static{
ERRORCODE.put(-1,"系統繁忙");
ERRORCODE.put(0,"請求成功");
ERRORCODE.put(40001,"獲取access_token時AppSecret錯誤,或者access_token無效");
ERRORCODE.put(40002,"不合法的憑證類型");
ERRORCODE.put(40003,"不合法的OpenID");
ERRORCODE.put(40004,"不合法的媒體文件類型");
ERRORCODE.put(40005,"不合法的文件類型");
ERRORCODE.put(40006,"不合法的文件大小");
ERRORCODE.put(40007,"不合法的媒體文件id");
ERRORCODE.put(40008,"不合法的消息類型");
ERRORCODE.put(40009,"不合法的圖片文件大小");
ERRORCODE.put(40010,"不合法的語音文件大小");
ERRORCODE.put(40011,"不合法的視頻文件大小");
ERRORCODE.put(40012,"不合法的縮略圖文件大小");
ERRORCODE.put(40013,"不合法的APPID");
ERRORCODE.put(40014,"不合法的access_token");
ERRORCODE.put(40015,"不合法的菜單類型");
ERRORCODE.put(40016,"不合法的按鈕個數");
ERRORCODE.put(40017,"不合法的按鈕個數");
ERRORCODE.put(40018,"不合法的按鈕名字長度");
ERRORCODE.put(40019,"不合法的按鈕KEY長度");
ERRORCODE.put(40020,"不合法的按鈕URL長度");
ERRORCODE.put(40021,"不合法的菜單版本號");
ERRORCODE.put(40022,"不合法的子菜單級數");
ERRORCODE.put(40023,"不合法的子菜單按鈕個數");
ERRORCODE.put(40024,"不合法的子菜單按鈕類型");
ERRORCODE.put(40025,"不合法的子菜單按鈕名字長度");
ERRORCODE.put(40026,"不合法的子菜單按鈕KEY長度");
ERRORCODE.put(40027,"不合法的子菜單按鈕URL長度");
ERRORCODE.put(40028,"不合法的自定義菜單使用用戶");
ERRORCODE.put(40029,"不合法的oauth_code");
ERRORCODE.put(40030,"不合法的refresh_token");
ERRORCODE.put(40031,"不合法的openid列表");
ERRORCODE.put(40032,"不合法的openid列表長度");
ERRORCODE.put(40033,"不合法的請求字符,不能包含\\uxxxx格式的字符");
ERRORCODE.put(40035,"不合法的參數");
ERRORCODE.put(40038,"不合法的請求格式");
ERRORCODE.put(40039,"不合法的URL長度");
ERRORCODE.put(40050,"不合法的分組id");
ERRORCODE.put(40051,"分組名字不合法");
ERRORCODE.put(41001,"缺少access_token參數");
ERRORCODE.put(41002,"缺少appid參數");
ERRORCODE.put(41003,"缺少refresh_token參數");
ERRORCODE.put(41004,"缺少secret參數");
ERRORCODE.put(41005,"缺少多媒體文件數據");
ERRORCODE.put(41006,"缺少media_id參數");
ERRORCODE.put(41007,"缺少子菜單數據");
ERRORCODE.put(41008,"缺少oauth code");
ERRORCODE.put(41009,"缺少openid");
ERRORCODE.put(42001,"access_token超時");
ERRORCODE.put(42002,"refresh_token超時");
ERRORCODE.put(42003,"oauth_code超時");
ERRORCODE.put(43001,"需要GET請求");
ERRORCODE.put(43002,"需要POST請求");
ERRORCODE.put(43003,"需要HTTPS請求");
ERRORCODE.put(43004,"需要接收者關注");
ERRORCODE.put(43005,"需要好友關系");
ERRORCODE.put(44001,"多媒體文件為空");
ERRORCODE.put(44002,"POST的數據包為空");
ERRORCODE.put(44003,"圖文消息內容為空");
ERRORCODE.put(44004,"文本消息內容為空");
ERRORCODE.put(45001,"多媒體文件大小超過限制");
ERRORCODE.put(45002,"消息內容超過限制");
ERRORCODE.put(45003,"標題字段超過限制");
ERRORCODE.put(45004,"描述字段超過限制");
ERRORCODE.put(45005,"鏈接字段超過限制");
ERRORCODE.put(45006,"圖片鏈接字段超過限制");
ERRORCODE.put(45007,"語音播放時間超過限制");
ERRORCODE.put(45008,"圖文消息超過限制");
ERRORCODE.put(45009,"接口調用超過限制");
ERRORCODE.put(45010,"創建菜單個數超過限制");
ERRORCODE.put(45015,"回復時間超過限制");
ERRORCODE.put(45016,"系統分組,不允許修改");
ERRORCODE.put(45017,"分組名字過長");
ERRORCODE.put(45018,"分組數量超過上限");
ERRORCODE.put(46001,"不存在媒體數據");
ERRORCODE.put(46002,"不存在的菜單版本");
ERRORCODE.put(46003,"不存在的菜單數據");
ERRORCODE.put(46004,"不存在的用戶");
ERRORCODE.put(47001,"解析JSON/XML內容錯誤");
ERRORCODE.put(48001,"api功能未授權");
ERRORCODE.put(50001,"用戶未授權該api");
}
}
消息工具類
WxMessageUtil.java
package com.wx.util;
/**
* 消息工具類
*
*/
public class WxMessageUtil {
/**
* 返回消息類型:文本
*/
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";
}
微信POST的XML數據包轉換為消息接受對象
InputMessage.java
package com.penuel.mythopoet.wx.manager;
/*
* 微信公眾平台(JAVA) SDK
*
* Copyright (c) 2014, Ansitech Network Technology Co.,Ltd All rights reserved.
* http://www.ansitech.com/weixin/sdk/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.Serializable;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* POST的XML數據包轉換為消息接受對象
*
* <p>
* 由於POST的是XML數據包,所以不確定為哪種接受消息,<br/>
* 所以直接將所有字段都進行轉換,最后根據<tt>MsgType</tt>字段來判斷取何種數據
* </p>
*
*/
@XStreamAlias("xml")
public class InputMessage implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
@XStreamAlias("ToUserName")
private String ToUserName;
@XStreamAlias("FromUserName")
private String FromUserName;
@XStreamAlias("CreateTime")
private Long CreateTime;
@XStreamAlias("MsgType")
private String MsgType = "text";
@XStreamAlias("MsgId")
private Long MsgId;
// 文本消息
@XStreamAlias("Content")
private String Content;
// 圖片消息
@XStreamAlias("PicUrl")
private String PicUrl;
// 位置消息
@XStreamAlias("LocationX")
private String LocationX;
@XStreamAlias("LocationY")
private String LocationY;
@XStreamAlias("Scale")
private Long Scale;
@XStreamAlias("Label")
private String Label;
// 鏈接消息
@XStreamAlias("Title")
private String Title;
@XStreamAlias("Description")
private String Description;
@XStreamAlias("Url")
private String URL;
// 語音信息
@XStreamAlias("MediaId")
private String MediaId;
@XStreamAlias("Format")
private String Format;
@XStreamAlias("Recognition")
private String Recognition;
// 事件
@XStreamAlias("Event")
private String Event;
@XStreamAlias("EventKey")
private String EventKey;
@XStreamAlias("Ticket")
private String Ticket;
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;
}
public Long getMsgId() {
return MsgId;
}
public void setMsgId(Long msgId) {
MsgId = msgId;
}
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
public String getPicUrl() {
return PicUrl;
}
public void setPicUrl(String picUrl) {
PicUrl = picUrl;
}
public String getLocationX() {
return LocationX;
}
public void setLocationX(String locationX) {
LocationX = locationX;
}
public String getLocationY() {
return LocationY;
}
public void setLocationY(String locationY) {
LocationY = locationY;
}
public Long getScale() {
return Scale;
}
public void setScale(Long scale) {
Scale = scale;
}
public String getLabel() {
return Label;
}
public void setLabel(String label) {
Label = label;
}
public String getTitle() {
return Title;
}
public void setTitle(String title) {
Title = title;
}
public String getDescription() {
return Description;
}
public void setDescription(String description) {
Description = description;
}
public String getURL() {
return URL;
}
public void setURL(String uRL) {
URL = uRL;
}
public String getEvent() {
return Event;
}
public void setEvent(String event) {
Event = event;
}
public String getEventKey() {
return EventKey;
}
public void setEventKey(String eventKey) {
EventKey = eventKey;
}
public String getMediaId() {
return MediaId;
}
public void setMediaId(String mediaId) {
MediaId = mediaId;
}
public String getFormat() {
return Format;
}
public void setFormat(String format) {
Format = format;
}
public String getRecognition() {
return Recognition;
}
public void setRecognition(String recognition) {
Recognition = recognition;
}
public String getTicket() {
return Ticket;
}
public void setTicket(String ticket) {
Ticket = ticket;
}
}
xml輸出配置
OutputMessage.java
package com.wx.util;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("xml")
public class OutputMessage {
@XStreamAlias("ToUserName")
@XStreamCDATA
private String ToUserName;
@XStreamAlias("FromUserName")
@XStreamCDATA
private String FromUserName;
@XStreamAlias("CreateTime")
private Long CreateTime;
@XStreamAlias("MsgType")
@XStreamCDATA
private String MsgType = "text";
private ImageMessage Image;
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;
}
public ImageMessage getImage() {
return Image;
}
public void setImage(ImageMessage image) {
Image = image;
}
}
圖片消息支持
MediaIdMessage.java
package com.wx.util;
import com.thoughtworks.xstream.annotations.XStreamAlias;
public class MediaIdMessage {
@XStreamAlias("MediaId")
@XStreamCDATA
private String MediaId;
public String getMediaId() {
return MediaId;
}
public void setMediaId(String mediaId) {
MediaId = mediaId;
}
}
ImageMessage.java
package com.wx.util;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("Image")
public class ImageMessage extends MediaIdMessage {
}
ok 以上配置完成后即可 編寫post處理controller
WxManagerController.java
修改后最終
package com.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.thoughtworks.xstream.XStream;
import com.wx.util.ImageMessage;
import com.wx.util.InputMessage;
import com.wx.util.OutputMessage;
import com.wx.util.SHA1;
import com.wx.util.SerializeXmlUtil;
import com.wx.util.WxMessageUtil;
@Controller
@RequestMapping( "/wx" )
public class WxManagerController {
private String Token = "testfortoken";
@RequestMapping(value = "", method = { RequestMethod.GET, RequestMethod.POST })
@ResponseBody
public void load(Model model, HttpServletRequest request, HttpServletResponse response) {
//判斷訪問方式
boolean isGet = request.getMethod().toLowerCase().equals("get");
if (isGet) {
//進行認證
access(request, response);
} else {
try {
//轉碼UTF-8,防止亂碼
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding( "utf-8" );
//處理微信post請求
acceptMessage(request,response);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 驗證URL真實性
*
* @param request
* @param response
* @return String
*/
private String access(HttpServletRequest request, HttpServletResponse response) {
// 驗證URL真實性
String signature = request.getParameter("signature");// 微信加密簽名
String timestamp = request.getParameter("timestamp");// 時間戳
String nonce = request.getParameter("nonce");// 隨機數
String echostr = request.getParameter("echostr");// 隨機字符串
List<String> params = new ArrayList<String>();
params.add(Token);
params.add(timestamp);
params.add(nonce);
// 1. 將token、timestamp、nonce三個參數進行字典序排序
Collections.sort(params, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
// 2. 將三個參數字符串拼接成一個字符串進行sha1加密
String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2));
if (temp.equals(signature)) {
try {
response.getWriter().write(echostr);
return echostr;
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private void acceptMessage(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 處理接收消息
ServletInputStream in = request.getInputStream();
// 將POST流轉換為XStream對象
XStream xs = SerializeXmlUtil.createXstream();
xs.processAnnotations(InputMessage.class);
xs.processAnnotations(OutputMessage.class);
// 將指定節點下的xml節點數據映射為對象
xs.alias("xml", InputMessage.class);
// 將流轉換為字符串
StringBuilder xmlMsg = new StringBuilder();
byte[] b = new byte[4096];
for (int n; (n = in.read(b)) != -1;) {
xmlMsg.append(new String(b, 0, n, "UTF-8"));
}
// 將xml內容轉換為InputMessage對象
InputMessage inputMsg = (InputMessage) xs.fromXML(xmlMsg.toString());
String servername = inputMsg.getToUserName();// 服務端
String custermname = inputMsg.getFromUserName();// 客戶端
long createTime = inputMsg.getCreateTime();// 接收時間
Long returnTime = Calendar.getInstance().getTimeInMillis() / 1000;// 返回時間
// 取得消息類型
String msgType = inputMsg.getMsgType();
// 根據消息類型獲取對應的消息內容
if (msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
// 文本消息
StringBuffer str = new StringBuffer();
str.append("<xml>");
str.append("<ToUserName><![CDATA[" + custermname + "]]></ToUserName>");
str.append("<FromUserName><![CDATA[" + servername + "]]></FromUserName>");
str.append("<CreateTime>" + returnTime + "</CreateTime>");
str.append("<MsgType><![CDATA[" + msgType + "]]></MsgType>");
str.append("<Content><![CDATA[您發送的是:" + inputMsg.getContent() + "?]]></Content>");
str.append("</xml>");
response.getWriter().write(str.toString());
}
// 獲取並返回多圖片消息
else if(msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {
System.out.println("獲取多媒體信息");
String mediaId = inputMsg.getMediaId();//多媒體文件id
String picUrl = inputMsg.getPicUrl();//圖片鏈接
long msgId = inputMsg.getMsgId();//消息id,64位整型
OutputMessage outputMsg = new OutputMessage();
outputMsg.setFromUserName(servername);
outputMsg.setToUserName(custermname);
outputMsg.setCreateTime(returnTime);
outputMsg.setMsgType(msgType);
ImageMessage images = new ImageMessage();
images.setMediaId(mediaId);
outputMsg.setImage(images);
response.getWriter().write(xs.toXML(outputMsg));
}
//事件
else if (msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
// 事件類型
String eventType = inputMsg.getEvent();
if (eventType.equals(WxMessageUtil.EVENT_TYPE_SUBSCRIBE)) {
// 關注
}else if (eventType.equals(WxMessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {
//取消關注
}else if(eventType.equals(WxMessageUtil.EVENT_TYPE_CLICK)){
//點擊
}
}
}
}
啟動測試,重新提交一下服務器認證。
效果:

完成,進行下一個功能 素材庫管理
三、素材庫
操作素材庫需要 access_token 接口調用憑據,
下面編寫 access_token 方法。
創建 AccessToken model
AccessToken.java
package com.wx.model;
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;
}
}
想微信發起post請求必須使用https方式,所以需要編寫配置https請求方式,證書配置如下,
MyX509TrustManager.java
package com.wx.util;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class MyX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
配置后編寫處理https發起以及處理,和獲取accesstoken方法
WxManagerUtil.java
package com.wx.util; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONObject;
import com.wx.model.AccessToken; public class WxManagerUtil { // 獲取access_token的接口地址(GET) 限200(次/天) public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; /** * 發起https請求並獲取結果 * * @param requestUrl 請求地址 * @param requestMethod 請求方式(GET、POST) * @param outputStr 提交的數據 * @return JSONObject(通過JSONObject.get(key)的方式獲取json對象的屬性值) */ public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; StringBuffer buffer = new StringBuffer(); try { // 創建SSLContext對象,並使用我們指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從上述SSLContext對象中得到SSLSocketFactory對象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); // 設置請求方式(GET/POST) httpUrlConn.setRequestMethod(requestMethod); if ("GET".equalsIgnoreCase(requestMethod)) httpUrlConn.connect(); // 當有數據需要提交時 if (null != outputStr) { OutputStream outputStream = httpUrlConn.getOutputStream(); // 注意編碼格式,防止中文亂碼 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 將返回的輸入流轉換成字符串 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 釋放資源 inputStream.close(); inputStream = null; httpUrlConn.disconnect(); jsonObject = JSONObject.parseObject(buffer.toString()); } catch (ConnectException ce) { System.out.println("微信服務器連接超時!"); } catch (Exception e) { System.out.println("HTTPS請求錯誤,錯誤信息:\n" + e.getMessage()); } return jsonObject; } /** * 生成AccessToken * @param appid * @param appsecret * @return */ public static AccessToken getAccessToken(String appid, String appsecret) { AccessToken accessToken = null; String requestUrl = access_token_url.replace("APPID", appid).replace( "APPSECRET", appsecret); JSONObject jsonObject = httpRequest(requestUrl, "GET", null); // 如果請求成功 if (null != jsonObject) { try { accessToken = new AccessToken(); accessToken.setToken(jsonObject.getString("access_token")); accessToken.setExpiresIn(jsonObject.getIntValue("expires_in")); } catch (JSONException e) { accessToken = null; // 獲取token失敗 System.out.println("獲取TOKEN失敗("+jsonObject.getString("errcode")+")"); } } return accessToken; } }
創建素材庫model
WxArticles.java
package com.wx.model;
public class WxArticles {
private String title; //標題
private String thumb_media_id;//圖文消息的封面圖片素材id
private String author;//作者
private String digest;//圖文消息的摘要,僅有單圖文消息才有摘要,多圖文此處為空
private String show_cover_pic;//是否顯示封面,0為false,即不顯示,1為true,即顯示
private String content;// 圖文消息的具體內容,支持HTML標簽,必須少於2萬字符,小於1M,且此處會去除JS
private String content_source_url;//圖文消息的原文地址,即點擊“閱讀原文”后的URL
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getThumb_media_id() {
return thumb_media_id;
}
public void setThumb_media_id(String thumb_media_id) {
this.thumb_media_id = thumb_media_id;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getDigest() {
return digest;
}
public void setDigest(String digest) {
this.digest = digest;
}
public String getShow_cover_pic() {
return show_cover_pic;
}
public void setShow_cover_pic(String show_cover_pic) {
this.show_cover_pic = show_cover_pic;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getContent_source_url() {
return content_source_url;
}
public void setContent_source_url(String content_source_url) {
this.content_source_url = content_source_url;
}
}
完成以上基本OK。
先寫一個測試方法,提交圖文素材 需要thumb_media_id 這個參數,即:圖文消息的封面圖片素材id(必須是永久mediaID)
我們現在公眾號里上傳一個圖片素材,然后去獲取他的thumb_media_id后再 提交新的圖文素材,這里使用用戶發送消息來觸發測試
最終代碼:
WxManagerController.java
package com.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jdt.internal.compiler.batch.Main;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;
import com.thoughtworks.xstream.XStream;
import com.wx.model.AccessToken;
import com.wx.model.WxArticles;
import com.wx.util.ImageMessage;
import com.wx.util.InputMessage;
import com.wx.util.OutputMessage;
import com.wx.util.SHA1;
import com.wx.util.SerializeXmlUtil;
import com.wx.util.WxManagerUtil;
import com.wx.util.WxMessageUtil;
@Controller
@RequestMapping( "/wx" )
public class WxManagerController {
private String Token = "CL0WQY79GJ12XV643BEZKMF5PHTAN";
@RequestMapping(value = "", method = { RequestMethod.GET, RequestMethod.POST })
@ResponseBody
public void load(Model model, HttpServletRequest request, HttpServletResponse response) {
//判斷訪問方式
boolean isGet = request.getMethod().toLowerCase().equals("get");
if (isGet) {
//進行認證
access(request, response);
} else {
try {
//轉碼UTF-8,防止亂碼
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding( "utf-8" );
//處理微信post請求
acceptMessage(request,response);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 驗證URL真實性
*
* @param request
* @param response
* @return String
*/
private String access(HttpServletRequest request, HttpServletResponse response) {
// 驗證URL真實性
String signature = request.getParameter("signature");// 微信加密簽名
String timestamp = request.getParameter("timestamp");// 時間戳
String nonce = request.getParameter("nonce");// 隨機數
String echostr = request.getParameter("echostr");// 隨機字符串
List<String> params = new ArrayList<String>();
params.add(Token);
params.add(timestamp);
params.add(nonce);
// 1. 將token、timestamp、nonce三個參數進行字典序排序
Collections.sort(params, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
// 2. 將三個參數字符串拼接成一個字符串進行sha1加密
String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2));
if (temp.equals(signature)) {
try {
response.getWriter().write(echostr);
return echostr;
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private void acceptMessage(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 處理接收消息
ServletInputStream in = request.getInputStream();
// 將POST流轉換為XStream對象
XStream xs = SerializeXmlUtil.createXstream();
xs.processAnnotations(InputMessage.class);
xs.processAnnotations(OutputMessage.class);
// 將指定節點下的xml節點數據映射為對象
xs.alias("xml", InputMessage.class);
// 將流轉換為字符串
StringBuilder xmlMsg = new StringBuilder();
byte[] b = new byte[4096];
for (int n; (n = in.read(b)) != -1;) {
xmlMsg.append(new String(b, 0, n, "UTF-8"));
}
// 將xml內容轉換為InputMessage對象
InputMessage inputMsg = (InputMessage) xs.fromXML(xmlMsg.toString());
String servername = inputMsg.getToUserName();// 服務端
String custermname = inputMsg.getFromUserName();// 客戶端
long createTime = inputMsg.getCreateTime();// 接收時間
Long returnTime = Calendar.getInstance().getTimeInMillis() / 1000;// 返回時間
// 取得消息類型
String msgType = inputMsg.getMsgType();
// 根據消息類型獲取對應的消息內容
if (msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
// 文本消息
// StringBuffer str = new StringBuffer();
// str.append("<xml>");
// str.append("<ToUserName><![CDATA[" + custermname + "]]></ToUserName>");
// str.append("<FromUserName><![CDATA[" + servername + "]]></FromUserName>");
// str.append("<CreateTime>" + returnTime + "</CreateTime>");
// str.append("<MsgType><![CDATA[" + msgType + "]]></MsgType>");
// str.append("<Content><![CDATA[您發送的是:" + inputMsg.getContent() + "?]]></Content>");
// str.append("</xml>");
// response.getWriter().write(str.toString());
AccessToken at = WxManagerUtil.getAccessToken("你的AppID", "你的AppSecret");
String mediaId = getImgsFor(at.getToken());
addImgTest(at.getToken(),mediaId);
}
// 獲取並返回多圖片消息
else if(msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {
System.out.println("獲取多媒體信息");
String mediaId = inputMsg.getMediaId();//多媒體文件id
String picUrl = inputMsg.getPicUrl();//圖片鏈接
long msgId = inputMsg.getMsgId();//消息id,64位整型
OutputMessage outputMsg = new OutputMessage();
outputMsg.setFromUserName(servername);
outputMsg.setToUserName(custermname);
outputMsg.setCreateTime(returnTime);
outputMsg.setMsgType(msgType);
ImageMessage images = new ImageMessage();
images.setMediaId(mediaId);
outputMsg.setImage(images);
response.getWriter().write(xs.toXML(outputMsg));
}
//事件
else if (msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
// 事件類型
String eventType = inputMsg.getEvent();
if (eventType.equals(WxMessageUtil.EVENT_TYPE_SUBSCRIBE)) {
// 關注
}else if (eventType.equals(WxMessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {
//取消關注
}else if(eventType.equals(WxMessageUtil.EVENT_TYPE_CLICK)){
//點擊
}
}
}
public String getImgsFor(String token){
//取第一個圖片素材
String geturls = "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token="+token;
String jsonval="{\"type\":\"image\",\"offset\":\"0\",\"count\":\"1\"}";
JSONObject jsonObject = WxManagerUtil.httpRequest(geturls, "POST", jsonval);
String result=null;
if (null != jsonObject) {
JSONObject josns =(JSONObject) jsonObject.getJSONArray("item").get(0);
result = josns.getString("media_id");
}
return result;
}
public void addImgTest(String token,String mediaId){
List<WxArticles> list = new ArrayList<WxArticles>();
WxArticles wxArticles = new WxArticles();
wxArticles.setTitle("a title");
wxArticles.setAuthor("a author");
wxArticles.setContent("a content");
wxArticles.setContent_source_url("a content_source_url");
wxArticles.setDigest("a digest");
wxArticles.setShow_cover_pic("a show_cover_pic");
wxArticles.setThumb_media_id(mediaId);
list.add(wxArticles);
Map<String,List<WxArticles>> maplist = new HashMap<String,List<WxArticles>>();
maplist.put("articles", list);
String urls= "https://api.weixin.qq.com/cgi-bin/material/add_news?access_token="+token;
String jsons = JSONObject.toJSONString(maplist);
JSONObject jsonObject = WxManagerUtil.httpRequest(urls, "POST", jsons);
String result=null;
if (null != jsonObject) {
result = jsonObject.getString("media_id");
System.out.println("返回("+result+")");
}
}
}

上傳永久素材成功!
別忘了 填寫自己的 appID 和appSecret;
AccessToken at = WxManagerUtil.getAccessToken("你的AppID", "你的AppSecret");
補充: 自定義菜單 功能;
WxManagerController.java 下添加此方法
private void accessMenu(String token){
String menu = "{\"button\":[{\"type\":\"click\",\"name\":\"項目管理\",\"key\":\"20_PROMANAGE\"},{\"type\":\"click\",\"name\":\"機構運作\",\"key\":\"30_ORGANIZATION\"},{\"name\":\"日常工作\",\"sub_button\":[{\"type\":\"click\",\"name\":\"待辦工單\",\"key\":\"01_WAITING\"},{\"type\":\"click\",\"name\":\"已辦工單\",\"key\":\"02_FINISH\"},{\"type\":\"click\",\"name\":\"我的工單\",\"key\":\"03_MYJOB\"},{\"type\":\"click\",\"name\":\"公告消息箱\",\"key\":\"04_MESSAGEBOX\"},{\"type\":\"click\",\"name\":\"簽到\",\"key\":\"05_SIGN\"}]}]}";
String requestUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token="+token;
int result = 0 ;
JSONObject jsonObject = WxManagerUtil.httpRequest(requestUrl, "POST", menu);
if (null != jsonObject) {
if (0 != jsonObject.getIntValue("errcode")) {
result = jsonObject.getIntValue("errcode");
System.out.println("創建菜單失敗("+result+")");
}
System.out.println("創建成功("+result+")");
}
}
完成!
