微信公众号开发官方网站,建议开发前详细读其中内容。https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
-
申请个人微信公众号开发平台
个人用户选择申请订阅号。
申请成功后在开发->基本配置->服务器配置中启用服务。
-
内网穿透
通过NatApp将内网地址映射到外网上,可以在外网访问你本地服务器。
NatApp网址:https://natapp.cn/。
进行内网穿透是因为微信公众号开发需要通过域名通讯(微信会访问我们配置的域名地址:服务器基本配置中的URL),也就是我们各自开发环境需要拥有独立的域名,微信就能通过这个域名请求到我们的本地开发服务,各自进行开发测试。
从NatApp官网将对应电脑版本的natapp下载,解压到某个文件夹,将该文件夹的路径复制好之后配置path环境变量。
在natapp上购买免费的隧道,购买成功后会出现authtoken。
配置环境变量成功后,双击natapp.exe,输入命令natapp -authtoken yourtoken出现如下图路径即可。
yourtoken:指的是购买成功的authtoken 。
此时外网的用户可以直接使用这个域名访问到我内网的127.0.0.1:8080服务器了。
- 此时内网穿透已经好了,简单的创建一个JavaWeb项目,新建一个index.jsp测试是否可以访问。
在body里输入hello lijing,运行项目,在游览器中输入127.0.0.1:8080/weChatProject,以及内网穿透地址**http://aj3vqi.natappfree.cc/weChatProject/**都可以访问到,此时成功。
- 通过Eclipse新建JavaWeb项目,新建一个servlet。
package cn.lijingCute.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.lijingCute.service.WxService;
@WebServlet("/wxServlet")
public class wxServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public wxServlet() {
super();
// TODO Auto-generated constructor stub
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*
* signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串
*/
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
// System.out.println(signature);
// System.out.println(timestamp);
// System.out.println(nonce);
// System.out.println(echostr);
//校验签名
if(WxService.check(timestamp,nonce,signature)){
PrintWriter pw = response.getWriter();
//原样返回echostr参数
pw.print(echostr);
pw.flush();
pw.close();
// System.out.println("接入成功");
}else {
System.out.println("接入失败");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("post");
}
}
新建一个service。
package cn.lijingCute.service;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
public class WxService {
private static final String TOKEN = "nice";
//验证签名
public static boolean check(String timestamp,String nonce,String signature) {
//1)将token、timestamp、nonce三个参数进行字典序排序
String[] strs = new String[] {TOKEN,timestamp,nonce};
Arrays.sort(strs);
//2)将三个参数字符串拼接成一个字符串进行sha1加密
String str = strs[0]+strs[1]+strs[2];
String mysig = sha1(str);
//3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
return mysig.equalsIgnoreCase(signature);
}
//sha1加密
private static String sha1(String src){
try {
//获取一个加密对象
MessageDigest md = MessageDigest.getInstance("sha1");
//加密
byte[] digest = md.digest(src.getBytes());
char[] chars= {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
StringBuilder sb = new StringBuilder();
//处理加密结果
for(byte b:digest) {
sb.append(chars[(b>>4)&15]);
sb.append(chars[b&15]);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
在微信公众号开发的测试平台进行测试,Token需要和Service中的Token一致。
微信公众号开发的测试平台:https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
在微信公众号测试平台点击测试,后台控制台报接入成功,此时接入正确。
- 文本消息
在servlet中写doPost代码如下。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletInputStream sis = request.getInputStream();
byte[] b= new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len=sis.read(b))!=-1) {
sb.append(new String(b,0,len));
}
System.out.println(sb.toString());
}
此时在手机测试公众号中发送信息“你好”给公众号,会在后台打印如下内容。
<xml><ToUserName><![CDATA[gh_cad3a83e72dc]]></ToUserName>
<FromUserName><![CDATA[oEmP3vm8jWeg875M2p-eknt5HPj8]]></FromUserName>
<CreateTime>1582871620</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[浣犲ソ]]></Content>
<MsgId>22661272279269388</MsgId>
</xml>
字段详情可以对应官方文档。
- 聊天机器人
聊天机器人的接口通过调用第三方聚合数据的接口:https://www.juhe.cn/docs/api/id/377
/**
* WxService中调用聊天机器人
*/
private static String chat(String msg) {
String result =null;
String url ="http://op.juhe.cn/robot/index";//请求接口地址
Map params = new HashMap();//请求参数
params.put("key",APPKEY);//您申请到的本接口专用的APPKEY
params.put("info",msg);//要发送给机器人的内容,不要超过30个字符
params.put("dtype","");//返回的数据的格式,json或xml,默认为json
params.put("loc","");//地点,如北京中关村
params.put("lon","");//经度,东经116.234632(小数点后保留6位),需要写为116234632
params.put("lat","");//纬度,北纬40.234632(小数点后保留6位),需要写为40234632
params.put("userid","");//1~32位,此userid针对您自己的每一个用户,用于上下文的关联
try {
result =Util.net(url, params, "GET");
//解析json
JSONObject jsonObject = JSONObject.fromObject(result);
//取出error_code
int code = jsonObject.getInt("error_code");
if(code!=0) {
return null;
}
//取出返回的消息的内容
String resp = jsonObject.getJSONObject("result").getString("text");
return resp;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
调用聚合数据的聊天机器人。
package cn.lijingCute.util;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import net.sf.json.JSONObject;
import cn.lijingCute.service.WxService;
public class Util {
public static final String DEF_CHATSET = "UTF-8";
public static final int DEF_CONN_TIMEOUT = 30000;
public static final int DEF_READ_TIMEOUT = 30000;
public static String userAgent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36";
// 配置申请的KEY
public static final String APPKEY = "*************************";
/**
* 向指定的地址发送一个post请求,带着data数据
*
* @param url
* @param data
*/
public static String post(String url, String data) {
try {
URL urlObj = new URL(url);
URLConnection connection = urlObj.openConnection();
// 要发送数据出去,必须要设置为可发送数据状态
connection.setDoOutput(true);
// 获取输出流
OutputStream os = connection.getOutputStream();
// 写出数据
os.write(data.getBytes());
os.close();
// 获取输入流
InputStream is = connection.getInputStream();
byte[] b = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = is.read(b)) != -1) {
sb.append(new String(b, 0, len));
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 向指定的地址发送get请求
*
* @param url
*/
public static String get(String url) {
try {
URL urlObj = new URL(url);
// 开连接
URLConnection connection = urlObj.openConnection();
InputStream is = connection.getInputStream();
byte[] b = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = is.read(b)) != -1) {
sb.append(new String(b, 0, len));
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
*
* @param strUrl
* 请求地址
* @param params
* 请求参数
* @param method
* 请求方法
* @return 网络请求字符串
* @throws Exception
*/
public static String net(String strUrl, Map params, String method) throws Exception {
HttpURLConnection conn = null;
BufferedReader reader = null;
String rs = null;
try {
StringBuffer sb = new StringBuffer();
if (method == null || method.equals("GET")) {
strUrl = strUrl + "?" + urlencode(params);
}
URL url = new URL(strUrl);
conn = (HttpURLConnection) url.openConnection();
if (method == null || method.equals("GET")) {
conn.setRequestMethod("GET");
} else {
conn.setRequestMethod("POST");
conn.setDoOutput(true);
}
conn.setRequestProperty("User-agent", userAgent);
conn.setUseCaches(false);
conn.setConnectTimeout(DEF_CONN_TIMEOUT);
conn.setReadTimeout(DEF_READ_TIMEOUT);
conn.setInstanceFollowRedirects(false);
conn.connect();
if (params != null && method.equals("POST")) {
try {
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
out.writeBytes(urlencode(params));
} catch (Exception e) {
// TODO: handle exception
}
}
InputStream is = conn.getInputStream();
reader = new BufferedReader(new InputStreamReader(is, DEF_CHATSET));
String strRead = null;
while ((strRead = reader.readLine()) != null) {
sb.append(strRead);
}
rs = sb.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
reader.close();
}
if (conn != null) {
conn.disconnect();
}
}
return rs;
}
// 将map型转为请求参数型
public static String urlencode(Map<String, Object> data) {
StringBuilder sb = new StringBuilder();
for (Map.Entry i : data.entrySet()) {
try {
sb.append(i.getKey()).append("=").append(URLEncoder.encode(i.getValue() + "", "UTF-8")).append("&");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return sb.toString();
}
}
此时在微信公众号测试,可以自动回复消息。
控制台可以打印出来。
- 获取Token
//微信公众号
private static final String APPID="*********";
private static final String APPSECRET="*********";
//用于存储token
private static AccessToken at;
//获取token
private static void getToken() {
String url = GET_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
String tokenStr = Util.get(url);
JSONObject jsonObject = JSONObject.fromObject(tokenStr);
String token = jsonObject.getString("access_token");
String expireIn = jsonObject.getString("expires_in");
//创建token对象,并存起来。
at = new AccessToken(token, expireIn);
}
/**
* 向处暴露的获取token的方法
*/
public static String getAccessToken() {
if(at==null||at.isExpired()) {
getToken();
}
return at.getAccessToken();
}
- 创建菜单
public class CreateMenu {
public static void main(String[] args) {
//菜单对象
Button btn = new Button();
//第一个一级菜单
btn.getButton().add(new ClickButton("一级点击", "1"));
//第二个一级菜单
btn.getButton().add(new ViewButton("一级跳转", "http://www.baidu.com"));
//创建第三个一级菜单
SubButton sb = new SubButton("有子菜单");
//为第三个一级菜单增加子菜单
sb.getSub_button().add(new PhotoOrAlbumButton("传图", "31"));
sb.getSub_button().add(new ClickButton("点击", "32"));
sb.getSub_button().add(new ViewButton("网易新闻", "http://news.163.com"));
//加入第三个一级菜单
btn.getButton().add(sb);
//转为json
JSONObject jsonObject = JSONObject.fromObject(btn);
//准备url
String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
//发送请求
String result = Util.post(url, jsonObject.toString());
System.out.println(result);
}
}
自定义菜单的响应
/**
* 处理click菜单
* @param requestMap
*/
private static BaseMessage dealClick(Map<String, String> requestMap) {
String key = requestMap.get("EventKey");
switch (key) {
//点击一菜单点
case "1":
//处理点击了第一个一级菜单
return new TextMessage(requestMap, "你点了一点第一个一级菜单");
case "32":
//处理点击了第三个一级菜单的第二个子菜单
break;
default:
break;
}
return null;
}
- 通过百度AI实现图片识别
申请百度AI账号,登录成功后创建应用。
百度AI的地址:http://ai.baidu.com/。
下载JavaSDK,并将下载好的jar包放入WEB-INF的lib中。
//百度AI
public static final String APP_ID = "你申请账号的APP_ID";
public static final String API_KEY = "你申请账号的API_KEY";
public static final String SECRET_KEY = "你申请账号的SECRET_KEY";
/**
* 进行图片识别
* @param requestMap
*/
private static BaseMessage dealImage(Map<String, String> requestMap) {
// 初始化一个AipOcr
AipOcr client = new AipOcr(APP_ID, API_KEY, SECRET_KEY);
// 可选:设置网络连接参数
client.setConnectionTimeoutInMillis(2000);
client.setSocketTimeoutInMillis(60000);
// 调用接口
String path = requestMap.get("PicUrl");
//进行网络图片的识别
org.json.JSONObject res = client.generalUrl(path, new HashMap<String,String>());
String json = res.toString();
//转为jsonObject
JSONObject jsonObject = JSONObject.fromObject(json);
JSONArray jsonArray = jsonObject.getJSONArray("words_result");
Iterator<JSONObject> it = jsonArray.iterator();
StringBuilder sb = new StringBuilder();
while(it.hasNext()) {
JSONObject next = it.next();
sb.append(next.getString("words"));
}
return new TextMessage(requestMap, sb.toString());
}
10. 获取行业与查询,发送模板消息
可以在微信测试账号中直接新增测试模板,但是需要注意,测试模板必须严格依照微信官方公众号开发中要求的模板样式。
例如:
简历反馈提醒
{{first.DATA}}
公司名:{{company.DATA}}
投递时间:{{time.DATA}}
反馈结果:{{result.DATA}}
{{remark.DATA}}
public class TemplateMessageManager {
/**
* 设置行业
*/
@Test
public void set() {
String at = WxService.getAccessToken();
String url="https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token="+at;
String data="{\n" +
" \"industry_id1\":\"1\",\n" +
" \"industry_id2\":\"4\"\n" +
" }";
String result = Util.post(url, data);
System.out.println(result);
}
/**
* 获取行业
*/
@Test
public void get() {
String at = WxService.getAccessToken();
String url="https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token="+at;
String result = Util.get(url);
System.out.println(result);
}
/**
* 发送模板消息
*/
@Test
public void sendTemplateMessage() {
String at = WxService.getAccessToken();
String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="+at;
String data="{\n" +
" \"touser\":\"oEmP3vm8jWeg875M2p-eknt5HPj8\",\n" +
" \"template_id\":\"P5uLRraltasI25g8rRXu7qPOWVc90CziX4Ub513OQeg\", \n" +
" \"data\":{\n" +
" \"first\": {\n" +
" \"value\":\"您有新的反馈信息啦!\",\n" +
" \"color\":\"#abcdef\"\n" +
" },\n" +
" \"company\":{\n" +
" \"value\":\"Lijing未来的公司\",\n" +
" \"color\":\"#fff000\"\n" +
" },\n" +
" \"time\": {\n" +
" \"value\":\"2020年3月4日\",\n" +
" \"color\":\"#1f1f1f\"\n" +
" },\n" +
" \"result\": {\n" +
" \"value\":\"一切顺顺利利\",\n" +
" \"color\":\"#173177\"\n" +
" },\n" +
" \"remark\":{\n" +
" \"value\":\"请和Lijing联系哦\",\n" +
" \"color\":\"#173177\"\n" +
" }\n" +
" }\n" +
" }";
String result = Util.post(url, data);
System.out.println(result);
}
}
- 处理图文信息
/**
* 处理文本消息
*/
private static BaseMessage dealTextMessage(Map<String, String> requestMap) {
//用户发来的内容
String msg = requestMap.get("Content");
if(msg.equals("李静")) {
List<Article> articles = new ArrayList<>();
articles.add(new Article("李静是个仙女", "李静不用减肥,一点都不胖!", "http://mmbiz.qpic.cn/mmbiz_jpg/RwRicImz9c1B7KXrlnYm5HRWPBE6VgvBnuicONnhc8S9rSIbdT3hOPiavz74efYmW4EvYczo2g1wZxqRE6K6pSzgQ/0", "http://www.baidu.com"));
NewsMessage nm = new NewsMessage(requestMap, articles);
return nm;
}
//调用方法返回聊天的内容
String resp = chat(msg);
TextMessage tm = new TextMessage(requestMap, resp);
return tm;
}
当输入李静时,可自动回复图文消息。
- 二维码生成与扫描事件
/**
* 获取带参数二维码的ticket
*/
public static String getQrCodeTicket() {
String at = getAccessToken();
String url="https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token="+at;
//生成临时字符二维码
String data="{\"expire_seconds\": 600, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"lijingCute\"}}}";
String result = Util.post(url, data);
String ticket = JSONObject.fromObject(result).getString("ticket");
return ticket;
}
@Test
public void testQrCode() {
String ticket = WxService.getQrCodeTicket();
System.out.println(ticket);
}
测试类里运行得到ticket:gQH87jwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAySmlEdWd3aXpiVC0xcFdrdk51Y1MAAgQikl9eAwRYAgAA。
通过请求https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET 可以得到二维码,将TICKET换为刚刚生成的ticket即可。
https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQH87jwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAySmlEdWd3aXpiVC0xcFdrdk51Y1MAAgQikl9eAwRYAgAA
也可以通过jsp页面获取到二维码。
@WebServlet("/GetTicket")
public class GetTicket extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
String ticket = WxService.getQrCodeTicket();
out.print(ticket);
out.flush();
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="jquery-3.2.1.min.js"></script>
<script type="text/javascript">
$(function(){
$("button").click(function(){
var url = "GetTicket";
$.get(url,function(ticket){
var src="https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="+ticket;
$("img").attr("src",src);
});
});
});
</script>
</head>
<body>
<button>生成二维码</button><br>
<img alt="" src="">
</body>
</html>
- 获取用户的基本信息
只可以获取关注公众号的用户信息。
/**
* 获取用户的基本信息
*/
public static String getUserInfo(String openid) {
String url="https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
url = url.replace("ACCESS_TOKEN", getAccessToken()).replace("OPENID", openid);
String result = Util.get(url);
return result;
}
写一个测试类进行测试。
@Test
public void testGetUserInfo() {
String user="用户的微信号";
String info = WxService.getUserInfo(user);
System.out.println(info);
}
可以获得用户的信息
{"subscribe":1,
"nickname":"Kikyo.",
"sex":2,
"language":"zh_CN",
"city":"",
"province":"",
"country":"圣多美和普林西比"}等信息