1.首先需要注册,https://mp.weixin.qq.com/,然后设置公众号信息什么的,默认是使用的微信公众号自己的后台,如果只是需要做一些简单了的操作,比如固定的关键字自动回复等,没有复杂的需求,就可以直接使用微信公众号本身的功能,比较方便,如果是比较复杂的,就需要接入自己的服务器,自己后台连上微信服务器获取数据,处理得到结果后再返回给微信服务器,微信服务器再发给手机
2.注册后,在开发 -> 基本配置里配置相关信息,开发者密码要记住,在后面的获取AccessToken时要用到
如图所示,主要配置这几项,服务器地址要填写到具体的Servlet,此时的WxServlet是用于专门验证的,修改配置参数后需要验证,就访问这个Servlet看是否可用,访问方式是 get ,所以要写在doGet() 里面,而具体处理微信服务器传过来的数据是 post 方式,所以在doPost() 里面处理
package cn.ihsuzi.weixin;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
public class WxServlet extends HttpServlet
{
/**
* Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
* 比如这里我将Token设置为purple
*/
private final String TOKEN = "purple";
/**
* 处理微信服务器发来的消息
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// TODO 接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息
// 将请求、响应的编码均设置为UTF-8(防止中文乱码)
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
System.out.println("请求进入");
String result = "";
try {
Map<String,String> map = MessageHandlerUtil.parseXml(request);
System.out.println("开始构造消息");
result = MessageHandlerUtil.buildXml(map);
System.out.println(result);
if(result.equals("")){
result = "未正确响应";
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("发生异常:"+ e.getMessage());
}
response.getWriter().println(result);
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
System.out.println("开始校验签名");
/**
* 接收微信服务器发送请求时传递过来的4个参数
*/
String signature = request.getParameter("signature");// 微信加密签名signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
String timestamp = request.getParameter("timestamp");// 时间戳
String nonce = request.getParameter("nonce");// 随机数
String echostr = request.getParameter("echostr");// 随机字符串
// 排序
String sortString = sort(TOKEN, timestamp, nonce);
// 加密
String mySignature = sha1(sortString);
// 校验签名
if (mySignature != null && mySignature != ""
&& mySignature.equals(signature))
{
System.out.println("签名校验通过。");
// 如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
// response.getWriter().println(echostr);
response.getWriter().write(echostr);
} else
{
System.out.println("签名校验失败.");
}
}
/**
* 排序方法
*
* @param token
* @param timestamp
* @param nonce
* @return
*/
public String sort(String token, String timestamp, String nonce)
{
try
{
String[] strArray =
{token, timestamp, nonce};
Arrays.sort(strArray);
StringBuilder sb = new StringBuilder();
for (String str : strArray)
{
sb.append(str);
}
return sb.toString();
} catch (NullPointerException e)
{
return "";
}
}
/**
* 将字符串进行sha1加密
*
* @param str
* 需要加密的字符串
* @return 加密后的内容
*/
public String sha1(String str)
{
try
{
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(str.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++)
{
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2)
{
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
return "";
}
}
先写好 WxServlet,然后验证,只有验证成功才能正常实现其他功能
3.AccessToken处理
正常进行数据交换处理等需要AccessToken,但只有两个小时的有效期,一天最多请求2000次,所以需要专门处理,比如可以设置7000s请求一次
实现三个类:AccessToken,AccessTokenInfo,AccessTokenServlet
package cn.ihsuzi.weixin;
public class AccessToken
{
//获取到的凭证
private String accessToken;
//凭证有效时间,单位:秒
private int expiresin;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public int getExpiresin() {
return expiresin;
}
public void setExpiresin(int expiresin) {
this.expiresin = expiresin;
}
}
package cn.ihsuzi.weixin;
public class AccessTokenInfo
{
//注意是静态的
public static AccessToken accessToken = null;
}
package cn.ihsuzi.weixin;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
public class AccessTokenServlet extends HttpServlet
{
@Override
public void init() throws ServletException
{
System.out.println("启动WebServlet");
super.init();
final String appId = "xxxxxx";
final String appSecret = "xxxxxxxx";
// 开启一个新的线程
new Thread(new Runnable()
{
@Override
public void run()
{
while (true)
{
try
{
// 获取accessToken
AccessTokenInfo.accessToken = getAccessToken(appId,
appSecret);
// 获取成功
if (AccessTokenInfo.accessToken != null)
{
// 获取到access_token 休眠7000秒,大约2个小时左右
Thread.sleep(7000 * 1000);
// Thread.sleep(10 * 1000);//10秒钟获取一次
} else
{
// 获取失败
Thread.sleep(1000 * 3); // 获取的access_token为空 休眠3秒
}
} catch (Exception e)
{
System.out.println("发生异常:" + e.getMessage());
e.printStackTrace();
try
{
Thread.sleep(1000 * 10); // 发生异常休眠1秒
} catch (Exception e1)
{
}
}
}
}
}).start();
}
/**
* 获取access_token
*
* @return AccessToken
*/
private AccessToken getAccessToken(String appId, String appSecret)
{
NetWorkHelper netHelper = new NetWorkHelper();
/**
* 接口地址为https://api.weixin.qq.com/cgi-bin/token?grant_type=
* client_credential
* &appid=APPID&secret=APPSECRET,其中grant_type固定写为client_credential即可。
*/
String Url = String
.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";,
appId, appSecret);
// 此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200}
String result = netHelper.getHttpsResponse(Url, "");
System.out.println("获取到的access_token=" + result);
// 使用FastJson将Json字符串解析成Json对象
JSONObject json = JSON.parseObject(result);
AccessToken token = new AccessToken();
token.setAccessToken(json.getString("access_token"));
token.setExpiresin(json.getInteger("expires_in"));
return token;
}
}
然后就是处理微信服务器传过来的数据了,数据格式是xml格式的,所以需要解析xml,先写一个专门的类来处理请求
package cn.ihsuzi.weixin;
importjavax.net.ssl.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
importjava.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class NetWorkHelper
{
/**
* 发起Https请求
* @param reqUrl 请求的URL地址
* @param requestMethod
* @return 响应后的字符串
*/
public String getHttpsResponse(String reqUrl, String requestMethod) {
URL url;
InputStream is;
String resultData = "";
try {
url = new URL(reqUrl);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
TrustManager[] tm = {xtm};
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null);
con.setSSLSocketFactory(ctx.getSocketFactory());
con.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
con.setDoInput(true); //允许输入流,即允许下载
//在android中必须将此项设置为false
con.setDoOutput(false); //允许输出流,即允许上传
con.setUseCaches(false); //不使用缓冲
if (null != requestMethod && !requestMethod.equals("")) {
con.setRequestMethod(requestMethod); //使用指定的方式
} else {
con.setRequestMethod("GET"); //使用get请求
}
is = con.getInputStream(); //获取输入流,此时才真正建立链接
InputStreamReader isr = new InputStreamReader(is);
BufferedReader bufferReader = new BufferedReader(isr);
String inputLine;
while ((inputLine = bufferReader.readLine()) != null) {
resultData += inputLine + "\n";
}
System.out.println(resultData);
} catch (Exception e) {
e.printStackTrace();
}
return resultData;
}
X509TrustManager xtm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
};
}
然后就是对收到的消息根据类别等来处理了,下面用到了短信通知(阿里云短信服务),微信公众号订阅用户回复消息不会被通知,只能自己去后台看
package cn.ihsuzi.weixin;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 消息处理工具类 Created by xdp on 2016/1/26.
*/
public class MessageHandlerUtil
{
/*
* 电话号码
*/
private static final String PURPLE_TEL_NUMBER= "xxxx";
private static final String SUZI_TEL_NUMBER= "xxxxx";
/*
* 特权码
*/
private static final String LINSHI = "1314520001";
private static final String LATIAO = "1314520002";
private static final String SONG = "1314520003";
private static final String GOLD = "1314520004";
private static final String WHITE_GOLD = "1314520005";
private static final String COLD_CLEAN = "1314520006";
private static final String DIAMOND = "1314520";
private static final String LIWU = "礼物";
/*
* 请求次数
*/
private static int linshi = 0;
private static int latiao = 0;
private static int song = 0;
private static int gold = 0;
private static int white_gold = 0;
private static int cold_clean = 0;
private static int diamond = 0;
private static int liwu = 0;
/**
* 解析微信发来的请求(XML)
*
* @param request
* @return map
* @throws Exception
*/
public static Map<String, String> parseXml(HttpServletRequest request)
throws Exception
{
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
System.out.println("获取输入流");
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList)
{
System.out.println(e.getName() + "|" + e.getText());
map.put(e.getName(), e.getText());
}
// 释放资源
inputStream.close();
inputStream = null;
return map;
}
// 根据消息类型 构造返回消息
public static String buildXml(Map<String, String> map)
{
String result;
String msgType = map.get("MsgType").toString();
System.out.println("MsgType:" + msgType);
if (msgType.toUpperCase().equals("TEXT"))
{
/*
* 根据内容的不同回复不同的内容
*/
String content = map.get("Content");
switch (content)
{
case LINSHI :
if (linshi == 0)
{
result = buildTextMessage(map, "兑换成功,想要什么零食,可以回复具体的要求哦~~/::*/::*");
sendSMS(LINSHI, "零食即将送达~~",PURPLE_TEL_NUMBER);
sendSMS(LINSHI, "零食特权券兑换了!!!",SUZI_TEL_NUMBER);
}else {
result = buildTextMessage(map, "已经兑换了哦~~/:pig/:pig");
}
linshi ++;
break;
case LATIAO :
if (latiao == 0)
{
result = buildTextMessage(map, "辣条兑换成功,可以回复具体的要求~~/::*");
sendSMS(LATIAO, "辣条即将送达~~",PURPLE_TEL_NUMBER);
sendSMS(LATIAO, "辣条特权券兑换了!!!",SUZI_TEL_NUMBER);
}else {
result = buildTextMessage(map, "已经兑换了哦~~/:pig/:pig");
}
latiao ++;
break;
case SONG :
if (song == 0)
{
result = buildTextMessage(map, "点击下面链接:\nhttp://www.jiangsaixian.cn \n/::*/::*";);
}else {
result = buildTextMessage(map, "已经兑换了哦~~/:pig/:pig");
}
song ++;
break;
case GOLD :
if (gold == 0)
{
result = buildTextMessage(map, "【黄金特权券】兑换成功,可以实现一个小小的要求或行使其他特权,请回复要行使的特权的内容~~/::*/::*");
sendSMS(GOLD, "请尽快行使特权~~",PURPLE_TEL_NUMBER);
sendSMS(GOLD, "黄金特权券!!!",SUZI_TEL_NUMBER);
}else {
result = buildTextMessage(map, "已经兑换了哦~~/:pig/:pig");
}
gold ++;
break;
case WHITE_GOLD :
if (white_gold == 0)
{
result = buildTextMessage(map, "【铂金特权券】兑换成功,可以实现一个大一点的要求或行使其他特权,请回复要行使的特权的内容~~/::*/::*");
sendSMS(WHITE_GOLD, "请尽快行使特权~~",PURPLE_TEL_NUMBER);
sendSMS(WHITE_GOLD, "铂金特权券!!!",SUZI_TEL_NUMBER);
}else {
result = buildTextMessage(map, "已经兑换了哦~~/:pig/:pig");
}
white_gold ++;
break;
case COLD_CLEAN :
if (cold_clean == 0)
{
result = buildTextMessage(map, "兑换成功,/:li马上消除一切矛盾和不愉快!!/::*/::*/:heart/:heart");
sendSMS(GOLD, "马上消除一切矛盾和不愉快!!",PURPLE_TEL_NUMBER);
sendSMS(GOLD, "矛盾消除券!!!",SUZI_TEL_NUMBER);
}else {
result = buildTextMessage(map, "已经兑换了哦~~/:pig/:pig");
}
gold ++;
break;
case DIAMOND :
if (diamond == 0)
{
result = buildTextMessage(map, "【钻石特权券】兑换成功!!可以实现任何要求,任何要求!\n请回复要行使的特权的内容~~");
sendSMS(DIAMOND, "请尽快行使特权~~",PURPLE_TEL_NUMBER);
sendSMS(DIAMOND, "钻石特权券!!!",SUZI_TEL_NUMBER);
}else {
result = buildTextMessage(map, "已经兑换了哦~~/:pig/:pig");
}
diamond ++;
break;
case LIWU :
if (liwu <= 10)
{
result = buildTextMessage(map, "直接回复兑换券或特权券号码,兑换礼物~~/::*/::*\n(ps1:其中特权券用于行使一次特权,可以是实现一个要求,或者其他)\n(ps2:一经兑换,就此作废,请慎用)");
}else {
result = buildTextMessage(map, ":pig");
}
liwu ++;
break;
case "我爱你":
result = buildTextMessage(map, "我也爱你");
break;
case "傻逼":
result = buildTextMessage(map, "哼哼");
break;
default :
if (content.contains("傻") || content.contains("混蛋") ||content.contains("哼") ||content.contains("蠢"))
{
result = buildTextMessage(map, "哼哼");
}else {
result = "success";
}
break;
}
} else if (msgType.toUpperCase().equals("EVENT"))
{
// 如果是被关注就回复欢迎消息
result = buildTextMessage(map, "欢迎关注~~/:rose/:rose");
} else
{
// 如果是其他类型的就不回复。设置为空字符串
result = buildTextMessage(map, "/:rose");
}
return result;
}
/**
* 构造文本消息
*
* @param map
* @param content
* @return
*/
private static String buildTextMessage(Map<String, String> map,
String content)
{
// 发送方帐号
String fromUserName = map.get("FromUserName");
// 开发者微信号
String toUserName = map.get("ToUserName");
/**
* 文本消息XML数据格式 <xml> <ToUserName><![CDATA[toUser]]></ToUserName>
* <FromUserName><![CDATA[fromUser]]></FromUserName>
* <CreateTime>1348831860</CreateTime>
* <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a
* test]]></Content> <MsgId>1234567890123456</MsgId> </xml>
*/
return String.format("<xml>"
+ "<ToUserName><![CDATA[%s]]></ToUserName>"
+ "<FromUserName><![CDATA[%s]]></FromUserName>"
+ "<CreateTime>%s</CreateTime>"
+ "<MsgType><![CDATA[text]]></MsgType>"
+ "<Content><![CDATA[%s]]></Content>" + "</xml>", fromUserName,
toUserName, getUtcTime(), content);
}
private static String getUtcTime()
{
Date dt = new Date();// 如果不需要格式,可直接用dt,dt就是当前系统时间
DateFormat df = new SimpleDateFormat("yyyyMMddhhmm");// 设置显示格式
String nowTime = df.format(dt);
long dd = (long) 0;
try
{
dd = df.parse(nowTime).getTime();
} catch (Exception e)
{
}
return String.valueOf(dd);
}
/**
* 发送短信
*
* @param code
* @param content
*/
private static void sendSMS(String code, String content,String tel)
{
try
{
// 设置超时时间-可自行调整
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
// 初始化ascClient需要的几个参数
final String product = "Dysmsapi";// 短信API产品名称(短信产品名固定,无需修改)
final String domain = "dysmsapi.aliyuncs.com";// 短信API产品域名(接口地址固定,无需修改)
// 替换成你的AK
final String accessKeyId = "XXXXX";// 你的accessKeyId,参考本文档步骤2
final String accessKeySecret = "XXXXXXX";// 你的accessKeySecret,参考本文档步骤2
// 初始化ascClient,暂时不支持多region(请勿修改)
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou",
accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product,
domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
// 组装请求对象
SendSmsRequest request = new SendSmsRequest();
// 使用post提交
request.setMethod(MethodType.POST);
// 必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式
request.setPhoneNumbers(tel);
// 必填:短信签名-可在短信控制台中找到
request.setSignName("筱屋枫林");
// 必填:短信模板-可在短信控制台中找到
request.setTemplateCode("SMS_11XXXX4");
// 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
// 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败
String SMSContentString = "{\"code\":\"" + code + "\", \"content\":\"" +content+ "\"}";
request.setTemplateParam(SMSContentString);
// 可选-上行短信扩展码(扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段)
// request.setSmsUpExtendCode("90997");
// 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
request.setOutId("yourOutId");
// 请求失败这里会抛ClientException异常
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
if (sendSmsResponse.getCode() != null
&& sendSmsResponse.getCode().equals("OK"))
{
System.out.println("短信发送成功!!!");
}
} catch (Exception e)
{
}
}
}
如果不想回复,就直接回“success”,注意是将整个xml替换成“success”,而如果不做处理的话会在手机端报错,提示服务器出了问题
微信默认表情符号的代码对照表:
http://www.360doc.com/content/15/1105/11/15398874_510889274.shtml