茶网站源码智能网站推广优化
一、申请微信公众号账号
1、先去微信公众平台 申请一个账号,账号类型,如果是个人,可以选择订阅号,如果是企业、单位,可以申请服务号(注意:服务号是要收费的),订阅号不收费,但是接口权限较少,服务号收费,但提供的功能多。
2、根据个人来申请订阅号,也可以实现部分有限功能,申请完登录进去,
找到设置与开发,点击基本配置,打开如下页面
可以把个人使用的IP白名单填写进去,多个IP换行填写。
在服务器配置处,注意
服务器(URL)填写规则:域名/个人的微信回调地址,我个人的地址是http://hvdkdx.natappfree.cc/weChat/confirmFromWx
令牌(Token):随意填写
消息加解密密钥:随机生成
举例如下:
/*** 文件名: WeChatController* 描述: 微信公众号controller*/
@Slf4j
@RequestMapping("/weChat")
@Controller
public class WeChatController {@Autowiredprivate WeChatService weChatService;@Autowiredprivate 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");//判断请求方式是否是postboolean isPost = Objects.equals("POST", request.getMethod().toUpperCase());if (isPost) {log.info("从微信服务器发过来POST请求,准备处理......");//xml转maptry {Map<String, String> map = WeChatUtil.xmlToMap(request);String msgType = map.get("MsgType");// 消息类型,有:eventString xml;//返回的xmlif (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 {@Autowiredprivate WeChatConfig weChatConfig;@Overridepublic 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 #appIdappSecret: xxxxx #appsecretsign_token: xxxx #与公众号中配置的签名sign_token保持一致server_url: xxxxx #服务器域名
3、如果个人不想申请,可以申请测试号即可,地址是微信公众平台 ,测试号的接口权限也很多,能满足大部分需要。
4、下面是部分接口权限表截图
二、有域名,没有域名的处理方法
上面提到测试号填写的URL中包含域名,如果个人有域名,则直接填上去即可,如果没有域名的解决办法,可以使用内网穿透来实现,我个人使用的 NATAPP来实现的,地址是NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 ,具体可以查看NATAPP文档,来获取域名,此处不再赘述。如果有不明白的地方,可以私我。