我是一名Android API Player,最近公司需要做微信公众号二次开发,我跟着学学,公司后台.net。
我mac安装windows之后用vs感觉太差了,可能是我的mac要淘汰了吧。
所以我决定用java后台来跟着做。
仔细一想我没有服务器啊。
再仔细一想我没有公众号啊或者服务号也行啊,申请太麻烦还要提交证明还要花钱,用公司的怕给玩坏了。
不过这都不是问题,解决方法总比问题多。
下面一步一步来记录下这些问题的解决,可能文笔不好,有的点会漏掉,只能慢慢来优化了。
第一步首先我们来解决没有服务器的问题:
大概思路就是将我们的本地的服务器地址映射到公网上,这样外网就可以访问我们的电脑指定路径了。
思路确定之后开始找软件了,试了几个之后,最后选定Sunny-Ngrok(免费的版本就够用了,放心吧,我也是穷人)。
本来一开始看博客有人推荐了Ngrok,试了以后发现他服务器在国外,映射的网址访问慢的不行。
下面是Sunny-Ngrok的官网:
首先主页下拉下载你电脑对应的版本:
之后你要注册一个账号,登陆。(这个网站我发现了几个Bug,等我给他们反馈一下)
登陆之后文档里有一篇《隧道开通》的,我就是照着弄的:
http://www.sunnyos.com/article-show-67.html
写到这里忘了说了,之后我们会用到Tomcat,不会配置的同学可以参考下面这篇漏文:
http://blog.youkuaiyun.com/geanwen/article/details/78410595
都是基本操作,都坐好。
上面继续Sunny-Ngrok网站,登陆之后就可以到了具体操作界面了:
右侧有个开通隧道,点进去新建一个隧道:
选择免费的就可以:
新建填一些配置:
这些上面贴出的文档里应该都有,跟着走就可以,不过这里微信开发需要80端口需要注意一下。
这里填的除了前置域名固定的,其他的都可以后来修改。
创建之后来到了隧道管理界面,你创建的条目里有一个隧道id,
这时候回到你上面下载的文件,通过终端进入文件所在的文件夹,输入下面的命令:
- ./sunny clientid 隧道id
其中:http://aool.ngrok.cc就是你本地路径映射后的结果。
web界面127.0.0.1:4040就是web界面的展示,到时候调试请求出问题了可以通过这里查看详细的错误或日志。
到这里,没有服务器的问题解决了。
接下来先看看Java后台的代码,很简单,按照微信官方文档需要验证。
代码也是我看文章找的:
package servlet;
import bean.TextMessage;
import util.CheckUtil;
import util.MessageUtils;
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.io.PrintWriter;
import java.util.Date;
import java.util.Map;
public class WechatServlet extends HttpServlet{
/**
* 接收微信服务器发送的4个参数并返回echostr
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 接收微信服务器以Get请求发送的4个参数
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
PrintWriter out = response.getWriter();
if (CheckUtil.checkSignature(signature, timestamp, nonce)) {
out.print(echostr); // 校验通过,原样返回echostr参数内容
}
}
/**
* 接收并处理微信客户端发送的请求
*/
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/xml;charset=utf-8");
PrintWriter out = response.getWriter();
Map<String, String> map = MessageUtils.xmlToMap(request);
String toUserName = map.get("ToUserName");
String fromUserName = map.get("FromUserName");
String msgType = map.get("MsgType");
String content = map.get("Content");
String message = null;
if ("text".equals(msgType)) { // 对文本消息进行处理
TextMessage text = new TextMessage();
text.setFromUserName(toUserName); // 发送和回复是反向的
text.setTouserName(fromUserName);
text.setMsgType("text");
text.setCreateTime(String.valueOf(new Date().getTime()));
text.setContent("你发送的消息是:" + content);
message = MessageUtils.textMessageToXML(text);
System.out.println(message);
}
out.print(message); // 将回应发送给微信服务器
}
}
里面的工具类CheckUtils:
package util;
import java.security.MessageDigest;
import java.util.Arrays;
public class CheckUtil {
private static final String token = "geanwen";
public static boolean checkSignature(String signature, String timestamps, String nonce){
String[] arr = new String[]{token, timestamps, nonce};
// 排序
Arrays.sort(arr);
// 生成字符串
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
// sha1加密
String temp = encode(content.toString());
return temp.equals(signature); // 与微信传递过来的签名进行比较
}
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);
}
}
}
MessageUtils:
package util;
import bean.TextMessage;
import com.thoughtworks.xstream.XStream;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MessageUtils {
/**
* xml转为map集合
*/
public static Map<String, String> xmlToMap(HttpServletRequest request){
Map<String, String> map = new HashMap<>();
SAXReader reader = new SAXReader();
try {
// 从request中获取输入流
InputStream ins = request.getInputStream();
Document doc = reader.read(ins);
// 获取xml中的跟元素
Element root = doc.getRootElement();
// 获取跟元素所有节点放到list中
List<Element> list = root.elements();
// 遍历
for (Element e : list){
map.put(e.getName(), e.getText());
}
ins.close();
return map;
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (DocumentException e) {
e.printStackTrace();
return null;
}
}
/**
* 将文本消息对象转换成XML
*/
public static String textMessageToXML(TextMessage textMessage){
XStream xstream = new XStream(); // 使用XStream将实体类的实例转换成xml格式
xstream.alias("xml", textMessage.getClass()); // 将xml的默认根节点替换成“xml”
return xstream.toXML(textMessage);
}
}
按照微信要求的实体类TextMessage:
package bean;
public class TextMessage {
private String TouserName;
private String FromUserName;
private String CreateTime;
private String MsgType;
private String Content;
private String MsgId;
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 String getCreateTime() {
return CreateTime;
}
public void setCreateTime(String createTime) {
CreateTime = createTime;
}
public String getMsgType() {
return MsgType;
}
public void setMsgType(String msgType) {
MsgType = msgType;
}
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
public String getMsgId() {
return MsgId;
}
public void setMsgId(String msgId) {
MsgId = msgId;
}
}
Web.xml中添加配置Servlet:
<servlet>
<servlet-name>wechatServlet</servlet-name>
<servlet-class>servlet.WechatServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>wechatServlet</servlet-name>
<url-pattern>/wx.do</url-pattern>
</servlet-mapping>
接下来配置Tomcat运行:
服务启动,这时候回去看看Sunny Ngrok的隧道的属性是否正确,没问题了就可以进入下一步:
微信测试号
一般我们的公众号开发都是在公众号已经开始运营的时候,贸然直接与后台连接可能会影响关注的粉丝们在公众号正常的使用;
即使我们没有多余的测试服务号,没有多余的测试的公众号。也可以
下面一步一步来
微信提供给我们开发者测试号的功能,具体是什么意思呢,就是我们申请一个测试账号,进入测试管理系统,就可以使用差不多公众号所有的功能;
测试账号网址:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
只需要点击登陆扫码就好了:
登陆之后就可以进行配置:
配置URL--刚才我们写的微信get接口。
Token--我们上面代码中Token设置的是geanwen,所以这里也要一样。
我们将我们上面代码的服务启动,使用微信在线测试接口:
输入对应参数,检查:
好了,这里潦草结尾,感觉写的太长了,后续在另起一张吧。
欢迎一起讨论,主要是记录一下大概思路,详细内容都可以针对去查。