java开发微信公众号详细步骤
个人开发微信公众号步骤,没有域名的处理方法
·
一、申请微信公众号账号
1、先去微信公众平台 申请一个账号,账号类型,如果是个人,可以选择订阅号,如果是企业、单位,可以申请服务号(注意:服务号是要收费的),订阅号不收费,但是接口权限较少,服务号收费,但提供的功能多。
2、根据个人来申请订阅号,也可以实现部分有限功能,申请完登录进去,
找到设置与开发,点击基本配置,打开如下页面
可以把个人使用的IP白名单填写进去,多个IP换行填写。
在服务器配置处,注意
服务器(URL)填写规则:域名/个人的微信回调地址,我个人的地址是http://hvdkdx.natappfree.cc/weChat/confirmFromWx
令牌(Token):随意填写
消息加解密密钥:随机生成
举例如下:
/**
* 文件名: WeChatController
* 描述: 微信公众号controller
*/
@Slf4j
@RequestMapping("/weChat")
@Controller
public class WeChatController {
@Autowired
private WeChatService weChatService;
@Autowired
private WeChatConfig weChatConfig;
/**
* 验证消息是否来自微信服务器
*
* @param request
* @param response
* @return
* @throws Exception
*/
@RequestMapping(value = "/confirmFromWx", method = {RequestMethod.GET, RequestMethod.POST})
public void confirmFromWx(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("****************来自微信服务器的请求:{}", request.getMethod().toUpperCase());
//微信服务器POST请求时,用的是UTF-8编码,在接收时也要用同样的编码,否则中文乱码
request.setCharacterEncoding("UTF-8");
//响应消息时,也要设置同样的编码
response.setCharacterEncoding("UTF-8");
//判断请求方式是否是post
boolean isPost = Objects.equals("POST", request.getMethod().toUpperCase());
if (isPost) {
log.info("从微信服务器发过来POST请求,准备处理......");
//xml转map
try {
Map<String, String> map = WeChatUtil.xmlToMap(request);
String msgType = map.get("MsgType");// 消息类型,有:event
String xml;//返回的xml
if (MessageType.REQ_MESSAGE_TYPE_EVENT.equals(msgType)) {//事件类型
xml = weChatService.parseEvent(map);
} else {//消息类型
xml = weChatService.parseMessage(map);
}
response.setContentType("text/xml");
if (StringUtils.isNotBlank(xml)) {
response.getWriter().write(xml);
} else {
response.getWriter().write("");
}
} catch (Exception e) {
e.printStackTrace();
response.getWriter().write("");
}
log.info("从微信服务器发过来POST请求,处理结束......");
} else {
log.info("********************验证微信服务器信息开始********************");
//微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
String signature = request.getParameter("signature");
//时间戳
String timestamp = request.getParameter("timestamp");
//随机数
String nonce = request.getParameter("nonce");
//随机字符串
String echostr = request.getParameter("echostr");
if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce) || StringUtils.isEmpty(echostr)) {
return;
}
log.info("weChat: signature is: " + signature + ",timestamp is: " + timestamp + ",nonce is:" + nonce + ",echostr is:" + echostr);
String check = weChatService.checkSignature(signature, timestamp, nonce, echostr);
if (StringUtils.isNotBlank(check)) {
log.info("********************校验成功,确实来自微信服务器,验证结束********************");
response.getWriter().write(check);
}
log.info("********************验证微信服务器信息结束********************");
response.getWriter().write("");
}
}
}
service实现类
@Service
@Slf4j
public class WeChatServiceImpl implements WeChatService {
@Autowired
private WeChatConfig weChatConfig;
@Override
public String checkSignature(String signature, String timestamp, String nonce, String echostr) {
String sign_token = weChatConfig.getSign_token();
// 1.将token、timestamp、nonce三个参数进行字典序排序
log.info("signature:{},sign_token:{},timestamp:{},nonce:{}", signature, sign_token, timestamp, nonce);
String sha1 = WeChatUtil.getSha1(sign_token, timestamp, nonce);
// 2.进行对比
log.info("随机字符串echostr:{}", echostr);
log.info("sha1算法得到的字符串:{}", sha1);
if (sha1.equals(signature.toUpperCase())) {
return echostr;
}
return null;
}
}
MessageType类如下:
/**
* 文件名: MessageType
* 描述: 消息类型
*/
public class MessageType {
/**
* 文本消息
*/
public static final String TEXT_MESSAGE = "text";
/**
* 图片消息
*/
public static final String IMAGE_MESSAGE = "image";
/**
* 语音消息
*/
public static final String VOICE_MESSAGE = "voice";
/**
* 视频消息
*/
public static final String VIDEO_MESSAGE = "video";
/**
* 小视频消息
*/
public static final String SHORTVIDEO_MESSAGE = "shortvideo";
/**
* 地理位置消息
*/
public static final String POSOTION_MESSAGE = "location";
/**
* 链接消息
*/
public static final String LINK_MESSAGE = "link";
/**
* 音乐消息
*/
public static final String MUSIC_MESSAGE = "music";
/**
* 图文消息
*/
public static final String IMAGE_TEXT_MESSAGE = "news";
/**
* 请求消息类型:事件推送
*/
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";
/**
* 事件类型:scan(用户已关注时的扫描带参数二维码)
*/
public static final String EVENT_TYPE_SCAN = "scan";
/**
* 事件类型:LOCATION(上报地理位置)
*/
public static final String EVENT_TYPE_LOCATION = "location";
/**
* 事件类型:CLICK(自定义菜单)
*/
public static final String EVENT_TYPE_CLICK = "click";
/**
* 事件类型: VIEW(点击菜单跳转链接时的事件推送)
*/
public static final String EVENT_TYPE_VIEW = "view";
/**
* 响应消息类型:文本
*/
public static final String RESP_MESSAGE_TYPE_TEXT = "text";
/**
* 响应消息类型:图片
*/
public static final String RESP_MESSAGE_TYPE_IMAGE = "image";
/**
* 响应消息类型:语音
*/
public static final String RESP_MESSAGE_TYPE_VOICE = "voice";
/**
* 响应消息类型:视频
*/
public static final String RESP_MESSAGE_TYPE_VIDEO = "video";
/**
* 响应消息类型:音乐
*/
public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
/**
* 响应消息类型:图文
*/
public static final String RESP_MESSAGE_TYPE_NEWS = "news";
/**
* 公众号回复消息样式
* @return
*/
public static String menuText() {
StringBuilder sb = new StringBuilder();
sb.append("感谢关注");
sb.append("该公众号已实现以下功能:\n");
sb.append("如您在使用该公众号时,有宝贵意见,欢迎反馈!\n\n");
sb.append("反馈邮箱:XXXXXX");
return sb.toString();
}
}
WeChatUtil工具类如下:
import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
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.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 文件名: WeChatUtil
* 描述: 微信工具类
*/
@Slf4j
public class WeChatUtil {
public static void main(String[] args) {
long currentSeconds = DateUtil.currentSeconds();
System.out.println("当前时间戳是:"+currentSeconds);
}
/**
* 用sha-1算法验证token
*
* @param sign_token 与公众号中配置的签名token保持一致
* @param timestamp
* @param nonce
* @return
*/
public static String getSha1(String sign_token, String timestamp, String nonce) {
String[] strings = new String[]{sign_token, timestamp, nonce};
Arrays.sort(strings);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < strings.length; i++) {
stringBuilder.append(strings[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
//将三个字符串拼到一起,进行sha1加密
byte[] digest = md.digest(stringBuilder.toString().getBytes());
tmpStr = byteArrToHexStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
log.error("错误信息:{}", e.getMessage());
}
return tmpStr;
}
/**
* 将字节转换成十六进制字符串
*
* @param b
* @return
*/
private static String byteToHexStr(byte b) {
char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] tempChar = new char[2];
tempChar[0] = chars[(b >>> 4) & 0X0F];
tempChar[1] = chars[b & 0X0F];
return new String(tempChar);
}
/**
* 将字节数组转换成十六进制字符串
*
* @param byteArr
* @return
*/
private static String byteArrToHexStr(byte[] byteArr) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < byteArr.length; i++) {
stringBuilder.append(byteToHexStr(byteArr[i]));
}
return stringBuilder.toString();
}
/**
* xml转map
*
* @param request
* @return
* @throws IOException
* @throws DocumentException
*/
public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<>();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element element = document.getRootElement();
// 得到根元素的所有子节点
List<Element> list = element.elements();
// 遍历所有子节点
for (Element e : list) {
map.put(e.getName(), e.getText());
}
// 释放资源
inputStream.close();
return map;
}
}
WeChatConfig配置类如下:
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 文件名: WeChatConfig
* 描述: 微信配置类
*/
@Component
@Data
public class WeChatConfig {
private String appId;
private String appSecret;
private String sign_token;
private String server_url;
@Value("${weChat.appId}")
public void setAppId(String appId) {
this.appId = appId;
}
@Value("${weChat.appSecret}")
public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
}
@Value("${weChat.sign_token}")
public void setSign_token(String sign_token) {
this.sign_token = sign_token;
}
@Value("${weChat.server_url}")
public void setServer_url(String server_url) {
this.server_url = server_url;
}
/**
* 1. 获取access_token.
*/
String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?" +
"grant_type=client_credential&appid=APPID&secret=APPSECRET";
/**
* 2. 获得各种类型的ticket.
*/
String TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=";
/**
* 3. 长链接转短链接接口.
*/
String SHORTURL_API_URL = "https://api.weixin.qq.com/cgi-bin/shorturl";
/**
* 4. 语义查询接口.
*/
String SEMANTIC_SEMPROXY_SEARCH_URL = "https://api.weixin.qq.com/semantic/semproxy/search";
/**
* 5. 用code换取oauth2的access token.
*/
String OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code";
/**
* 6. 刷新oauth2的access token.
*/
String OAUTH2_REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?" +
"appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
/**
* 7. 用oauth2获取用户信息.
*/
String OAUTH2_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=LANG";
/**
* 8. 验证oauth2的access token是否有效.
*/
String OAUTH2_VALIDATE_TOKEN_URL = "https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID";
/**
* 9. 获取微信服务器IP地址.
*/
String CALLBACK_IP_URL = "https://api.weixin.qq.com/cgi-bin/getcallbackip";
/**
* 10. 第三方使用网站应用授权登录的url.
*/
String QRCONNECT_URL = "https://open.weixin.qq.com/connect/qrconnect?" +
"appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
/**
* 11. oauth2授权的url连接.
*/
String CONNECT_OAUTH2_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
"appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=SCOPE&" +
"state=STATE#wechat_redirect";
/**
* 12. 获取公众号的自动回复规则.
*/
String CURRENT_AUTOREPLY_INFO_URL = "https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info";
/**
* 13.公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零.
*/
String CLEAR_QUOTA_URL = "https://api.weixin.qq.com/cgi-bin/clear_quota";
/**
* 14.获取微信公众号用户的基本信息(UnionID机制)
*/
String USERINFO_URL="https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
/**
* 15.生成带参数的二维码
*/
String qrCode_URL="https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN";
/**
* 16.显示二维码图片的URL
*/
String showQrCode_URL="https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET";
/**
* 17.微信公众号长链接转短链接接口,POST请求
*/
String shortURL="https://api.weixin.qq.com/cgi-bin/shorturl?access_token=ACCESS_TOKEN";
/**
* 18.自定义菜单查询接口
*/
String findMenu_URL="https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
/**
* 19.删除自定义菜单接口
*/
String deleteMenu_URL="https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
/**
* 20.微信 新增临时素材
*/
String uploadTempMedia_URL="https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE";
}
application.yml配置微信的如下
weChat:
appId: xxxxxxxx #appId
appSecret: xxxxx #appsecret
sign_token: xxxx #与公众号中配置的签名sign_token保持一致
server_url: xxxxx #服务器域名
3、如果个人不想申请,可以申请测试号即可,地址是微信公众平台 ,测试号的接口权限也很多,能满足大部分需要。
4、下面是部分接口权限表截图
二、有域名,没有域名的处理方法
上面提到测试号填写的URL中包含域名,如果个人有域名,则直接填上去即可,如果没有域名的解决办法,可以使用内网穿透来实现,我个人使用的 NATAPP来实现的,地址是NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 ,具体可以查看NATAPP文档,来获取域名,此处不再赘述。如果有不明白的地方,可以私我。
更多推荐
所有评论(0)