微信公众平台开发概述 | 微信开放文档
公众号:公众号通俗说的是服务号、订阅号
微信公众平台一共有4种类型账号接入类型:服务号、订阅号、小程序、企业微信四种类型
微信小程序:用户体验上,存在很大的差别。小程序更像App,,因为小程序,也是基于H5框架开发,所以很多接口都是可以通用的。但小程序在用户体验和使用效率上更高。小程序只能使用微信官方提供的工具进行开发
微信订阅号和服务号:其实就是移动端网站。但是两者可实现的功能,基本上差不多,而微信订阅号和服务号可以使用任何开发工具进行开发
服务号与订阅号的区别如下:
服务号,旨在为用户提供服务;订阅号,旨在为用户提供信息。
服务号1个月内仅可以发送4条群发消息;订阅号每天可以发送1条群发消息。
服务号发给粉丝的消息,会显示在对方的聊天列表中,相对应微信的首页;订阅号发给粉丝的消息,将会显示在对方的“订阅号”文件夹中,点击两次才可以打开。
服务号认证成功后可以建立微信商城,进行微信支付功能 ; 订阅号无论认证与否都不能进行微信支付。
服务号无论是否认证都会有自定义菜单功能 ; 订阅号完成认证才有自定义菜单功能。
服务号支持多个客服后台在线与粉丝们进行及时交流,适合粉丝关注较多的公众号; 订阅号不支持多客服服务,适合粉丝关注少的公众号。
;
一、微信服务号流程:
1:首先注册一个服务号,并设置密钥等,通过设置的token校验接口校验
2:注意点:
1:token获取需要配置当前电脑的公网的白名单,否则无法获取token
2:发送模板消息的格式需要注意
3:验签和接受消息是同一个接口,路径相同,只是请求类型不同!验签是get,接受消息是post
@CrossOrigin
@RestController
@RequestMapping("/wx")
@Slf4j
public class SubscribeNoticeController {
@Autowired
ISubscribeNoticeService subscribeNoticeService;
/**
* token 验签
* @GetMapping 和 @PostMapping 路径名称是一样的,一个用来验证token、一个用来接收数据
*/
@GetMapping("/notice")
Long checkSignature(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr){
return subscribeNoticeService.checkSignature(signature, timestamp, nonce, echostr);
}
/**
* 接收推送的数据
*/
@PostMapping("/notice")
@ResponseBody
public String acceptMessage(HttpServletRequest request) throws Exception {
try {
return subscribeNoticeService.acceptMessage(request);
} catch (Exception e) {
log.info(e.getMessage(),e);
throw new RuntimeException(e);
}
}
/**
* 创建微信公众号菜单,只需要调用一次即可,多次也不会出问题
*/
@PostMapping("/create/ws/menu")
public Boolean createWSMenu() {
return subscribeNoticeService.addMenu();
}
/**
*@Description: 测试模板消息发送
*@Author: yangfengfan
*@Param:
*@Return:
*@Date: 2022/11/8 9:13
**/
@PostMapping("/message")
public void message() {
log.info("进入发送模板消息接口");
subscribeNoticeService.cycleSendMessage();
}
package com.haiyang.oceansite.mrv.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.haiyang.oceansite.mrv.api.RemoteWSService;
import com.haiyang.oceansite.mrv.entity.MrvConfig;
import com.haiyang.oceansite.mrv.entity.MrvNoonPaper;
import com.haiyang.oceansite.mrv.service.IMrvConfigService;
import com.haiyang.oceansite.mrv.service.IMrvNoonPaperService;
import com.haiyang.oceansite.mrv.service.ISubscribeNoticeService;
import com.haiyang.oceansite.mrv.vo.WSCustomMenu;
import com.haiyang.oceansite.mrv.vo.WSMenu;
import com.haiyang.oceansite.mrv.vo.WSTagInfo;
import com.haiyang.oceansite.mrv.vo.WSTags;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
@Service
@Slf4j
public class SubscribeNoticeServiceImpl implements ISubscribeNoticeService {
//注意:标签name就是IMO!
@Autowired
RemoteWSService remoteWSService;
@Autowired
IMrvConfigService iMrvConfigService;
@Autowired
IMrvNoonPaperService iMrvNoonPaperService;
/**
*@Description: 验签token
*@Author: yangfengfan
*@Param:
*@Return:
*@Date: 2022/11/3 18:09
**/
@Override
public Long checkSignature(String signature,String timestamp,String nonce,String echostr) {
String token = "haiyang1304";
String[] arr = {token, timestamp, nonce};
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
//sha1Hex 加密
MessageDigest md = null;
String temp = null;
try {
md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(content.toString().getBytes());
temp = this.byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
if ((temp.toLowerCase()).equals(signature)){
//重点:要把接收的这个随机码返回
return Long.valueOf(echostr);
}
return null;
}
@Override
public String acceptMessage(HttpServletRequest request) throws IOException {
BufferedReader br = request.getReader();
String str;
String paramInfo = "";
Map<String, Object> XMLmap = null;
while ((str = br.readLine()) != null) {
paramInfo += str;
}
if (StrUtil.isNotBlank(paramInfo)) {
XMLmap = XmlUtil.xmlToMap(paramInfo);
}
log.info("微信公众号接收信息:{}", XMLmap);
String userOpenId = (String) XMLmap.get("FromUserName");
// 微信账号
String userName = (String) XMLmap.get("ToUserName");
// 事件
String event = (String) XMLmap.get("Event");
// 区分消息类型
String msgType = (String) XMLmap.get("MsgType");
// 判断消息体
String msgInfo = (String) XMLmap.get("Content");
// 点击的消息key
String EventKey = (String) XMLmap.get("EventKey");
// 模板消息发送后的状态
String flag = (String) XMLmap.get("Status");
// 微信返回消息xml中的content
String content = null;
//登录获取token
String token = this.getToken();
//输入文字触发
if ("text".equals(msgType)) {
//输入绑定的imo号
if (this.checkIMO(msgInfo)) {
//获取所有的标签列表,标签已存在就不创建,不存在就创建并指定
String tagsStr = remoteWSService.getTags(token);
WSTags wsTags = JSON.parseObject(tagsStr).toJavaObject(WSTags.class);
List<WSTagInfo> tagsList = wsTags.getTags();
StringBuilder imos = new StringBuilder("");
String[] imoArray = msgInfo.trim().split(",");
for (WSTagInfo wsTagInfo : tagsList) {
String imo = wsTagInfo.getName();
imos.append(imo).append(",");
for (String imoBanding : imoArray) {
if (imo.equals(imoBanding)){
//如果公众号中已有的标签和用户输入要绑定的标签有相同时,则不用创建标签,直接绑定即可
Integer id = wsTagInfo.getId();
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
ArrayList<String> stringsList = new ArrayList<>();
stringsList.add(userOpenId);
stringObjectHashMap.put("openid_list",stringsList);
stringObjectHashMap.put("tagid",id);
remoteWSService.setUsersTag(token,stringObjectHashMap);
}
}
}
for (String imoBanding : imoArray){
//如果公众号中已存在的标签不包含用户输入的的标签时
if (!imos.toString().contains(imoBanding)){
HashMap<String, Object> data = new HashMap<>();
HashMap<String, String> stringStringHashMap = new HashMap<>();
stringStringHashMap.put("name",imoBanding);
data.put("tag",stringStringHashMap);
//创建标签,获取返回的标签id
String tagMap = remoteWSService.setUserTag(token, data);
JSONObject jsonObject = JSON.parseObject(tagMap);
Map map = jsonObject.toJavaObject(Map.class);
Map<String,Integer> tag = (Map<String, Integer>) map.get("tag");
Integer id = tag.get("id");
//绑定
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
ArrayList<String> stringsList = new ArrayList<>();
stringsList.add(userOpenId);
stringObjectHashMap.put("openid_list",stringsList);
stringObjectHashMap.put("tagid",id);
remoteWSService.setUsersTag(token,stringObjectHashMap);
}
}
return this.getMassage(userOpenId, userName,"您已成功绑定船舶IMO:"+msgInfo);
}else {
return this.getMassage(userOpenId, userName,"公众号正在学习您的指令,提示:绑定的IMO号是7位数,输入必须符合规范");
}
}
//事件触发
if ("event".equals(msgType)) {
String message = null;
if ("TEMPLATESENDJOBFINISH".equals(event)){
if (flag.equals("success")){
log.info("模板消息发送成功");
}else {
log.info("模板消息发送失败");
}
return null;
}
//点击午报触发
if (EventKey.equals("noonPaper")) {
ArrayList<String> imoNames = new ArrayList<>();
//获取用户的标签列表
List<Integer> tagidList = this.bandingIMO(token, userOpenId, userName);
if (tagidList.size() == 0) { //没有绑定IMO标签则不能获取午报
return this.getMassage(userOpenId, userName, "抱歉,未绑定IMO号不能获取午报,请先绑定IMO号");
} else {
//获取公众号已创建的所有标签
String tagsStr = remoteWSService.getTags(token);
WSTags wsTags = JSON.parseObject(tagsStr).toJavaObject(WSTags.class);
List<WSTagInfo> tagsList = wsTags.getTags();
for (WSTagInfo wsTagInfo : tagsList) {
for (Integer tagid : tagidList) {
if (wsTagInfo.getId().equals(tagid)) {
String imoName = wsTagInfo.getName();
imoNames.add(imoName);
}
}
}
//发送午报
for (String imoName : imoNames) {
QueryWrapper<MrvConfig> qw = new QueryWrapper();
qw.eq("config_key", "wx");
MrvConfig mrvConfig = iMrvConfigService.getOne(qw);
if (ObjectUtil.isEmpty(mrvConfig)) {
content = "请先设置OCEANSITE系统的微信午报模板";
} else {
String jsonStr = mrvConfig.getConfigValue();
//查询imo并选择最近一条发送
QueryWrapper<MrvNoonPaper> queryWrapper = new QueryWrapper();
queryWrapper.eq("ship_imo", imoName);
queryWrapper.orderByDesc("create_time");
queryWrapper.last("limit 1");
MrvNoonPaper mrvNoonReport = iMrvNoonPaperService.getOne(queryWrapper);
if (ObjectUtil.isEmpty(mrvNoonReport)) {
content = "系统未找到午报信息,请先填写午报";
} else {
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(mrvNoonReport));
Map map1 = JSON.toJavaObject(jsonObject, Map.class);
List<String> lisStr = JSONArray.parseArray(jsonStr, String.class);
for (String s : lisStr) {
for (Object o : map1.keySet()) {
if (StrUtil.contains(s, "$" + o + "$")) {
s = StrUtil.replace(s, "$" + o + "$", map1.get(o) + "", true);
}
}
content = StrUtil.concat(true, content, s, "
");
}
}
}
return this.getMassage(userOpenId, userName,content);
}
}
}
//点击绑定xxx触发
if (EventKey.equals("binding")){
message = "";
}
//点击联系我们
if (EventKey.equals("contact")){
message = "xxxx";
}
//订阅
if ("subscribe".equals(event)) {
message = "谢谢您的关注";
}
//取消订阅
if ("unsubscribe".equals(event)) {
message = "很遗憾,您已取消关注!";
}
return this.getMassage(userOpenId, userName,message);
}
return this.getMassage(userOpenId, userName,"公众号正在学习您的指令,抱歉");
}
/**
* 添加菜单
*/
@Override
public boolean addMenu(){
String access_token = this.getToken();
log.info("token:" + access_token);
//新增2个菜单按钮
WSMenu wsMenu = new WSMenu();
WSCustomMenu customMenu1 = new WSCustomMenu();
customMenu1.setKey("binding");
customMenu1.setName("绑定IMO");
customMenu1.setType("click");
WSCustomMenu customMenu2 = new WSCustomMenu();
customMenu2.setKey("noonPaper");
customMenu2.setName("午报");
customMenu2.setType("click");
WSCustomMenu customMenu3 = new WSCustomMenu();
customMenu3.setKey("contact");
customMenu3.setName("联系我们");
customMenu3.setType("click");
ArrayList<WSCustomMenu> WSCustomMenus = new ArrayList<>();
WSCustomMenus.add(customMenu1);
WSCustomMenus.add(customMenu2);
WSCustomMenus.add(customMenu3);
wsMenu.setButton(WSCustomMenus);
log.info(JSON.toJSONString(wsMenu));
remoteWSService.createMenu(access_token,wsMenu);
return true;
}
public String getToken(){
//获取微信的access_token
String accessToken = remoteWSService.getAccessToken("client_credential", "wxa0031d230d26e2e0", "3343ac9a40cf2e285fff4d3dda2c7671");
JSONObject jsonObject = JSON.parseObject(accessToken);
Map<String,String> map = jsonObject.toJavaObject(Map.class);
return map.get("access_token");
}
public String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
System.err.println("strDigest : "+strDigest);
return strDigest;
}
private String byteToHexStr(byte mByte) {
char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A','B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4)& 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
// 校验imo号,7位数
public boolean checkIMO (String msgInfo){
String[] split = msgInfo.split(",");
for (String s : split) {
boolean b = s.length() == 7;
if (!b) {
return false;
}
}
return true;
}
//获取符合格式的消息
public String getMassage(String userOpenId,String userName,String data){
//返回的xml消息格式
String str = "<xml>\n" +
" <ToUserName><![CDATA[" + userOpenId + "]]></ToUserName>\n" +
" <FromUserName><![CDATA[" + userName + "]]></FromUserName>\n" +
" <CreateTime>" + System.currentTimeMillis() + "</CreateTime>\n" +
" <MsgType><![CDATA[text]]></MsgType>\n" +
" <Content>" + data + "</Content>\n" +
"</xml>\n";
log.info("发送的消息格式:" + str);
return str;
}
//每天中午定时发送最新的午报
//@Scheduled(cron = "30 * * * * ?") 30s触发一次
//@Scheduled(cron = "0 0 12 * * ?") 每天中午12点触发
public void cycleSendMessage(){
String token = this.getToken();
//获取所有的标签列表
String tagsStr = remoteWSService.getTags(token);
WSTags wsTags = JSON.parseObject(tagsStr).toJavaObject(WSTags.class);
List<WSTagInfo> tagsList = wsTags.getTags();
for (WSTagInfo wsTagInfo : tagsList) {
String imo =wsTagInfo.getName();
//--根据tagId即imo号,查询获取最新午报信息
QueryWrapper<MrvNoonPaper> queryWrapper = new QueryWrapper();
queryWrapper.eq("ship_imo", imo);
queryWrapper.orderByDesc("create_time");
queryWrapper.last("limit 1");
MrvNoonPaper mrvNoonReport = iMrvNoonPaperService.getOne(queryWrapper);
if (ObjectUtil.isEmpty(mrvNoonReport)) {
//系统没有午报
log.info("oceansite系统没有该IMO船舶的午报数据,IMO:"+imo);
continue;
}
//--获取该标签下的粉丝,入参标签id
List<String> openidList = this.getTagUsers(wsTagInfo.getId(), token);
log.info("获取该标签下的粉丝:"+openidList.toString());
//--获取模板id(也可以自行进行公众号后台获取模板id)
//HashMap<String, String> stringObjectHashMap = new HashMap<>();
//stringObjectHashMap.put("template_id_short", "TM00201");
//String templateId = remoteWSService.getTemplateId(token, stringObjectHashMap);
//Map map = JSON.parseObject(templateId).toJavaObject(Map.class);
//String template_id = (String) map.get("template_id");
//log.info("模板id:"+template_id);
//--根据模板发送对应的午报给对应粉丝
for (String openid : openidList) {
HashMap<String, Object> templateMessageMap = new HashMap<>();
templateMessageMap.put("touser",openid);
templateMessageMap.put("template_id","iyCm1ygyWPEafxY4yHgOHWqwhc5u21z-Qv8OtZ6Iqes"); //模板id
//templateMessageMap.put("client_msg_id",StrUtil.uuid());防重入id。对于同一个openid + client_msg_id, 只发送一条消息,10分钟有效,超过10分钟不保证效果。若无防重入需求,可不填
HashMap<String, Object> dataInfoMap = new HashMap<>();
HashMap<String, String> data1 = new HashMap<>();
data1.put("value","详细午报数据请进入OceanSite系统查看");
data1.put("color","#173177");
dataInfoMap.put("first",data1);
HashMap<String, String> data2 = new HashMap<>();
data2.put("value","下一个港口名称:"+mrvNoonReport.getNextPortName()+",当前航次:"+mrvNoonReport.getVoyageNumber());
data2.put("color","#173177");
dataInfoMap.put("reason",data2);
HashMap<String, String> data3 = new HashMap<>();
data3.put("value", DateUtil.today());
data3.put("color","#173177");
dataInfoMap.put("time",data3);
HashMap<String, String> data4 = new HashMap<>();
data4.put("value", "感谢您关注");
data4.put("color","#173177");
dataInfoMap.put("remark",data4);
templateMessageMap.put("data",dataInfoMap);
String s = remoteWSService.sendTemplateMessage(token, templateMessageMap);
log.info(s);
}
}
}
//没有绑定IMO不能获取午报,返回用户身上的标签列表
public List<Integer> bandingIMO(String access_token, String userOpenId, String userName){
HashMap<String, Object> stringStringHashMap = new HashMap<>();
stringStringHashMap.put("openid",userOpenId);
//获取用户身上的标签列表
String userTagList = remoteWSService.getUserTagList(access_token, stringStringHashMap);
Map<String,Object> map = JSON.parseObject(userTagList).toJavaObject(Map.class);
List<Integer> tagid_list = (List<Integer>) map.get("tagid_list");
return tagid_list;
}
public List<String> getTagUsers(Integer tagid,String token){
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("tagid",tagid);
stringObjectHashMap.put("next_openid","");
String tagUsers = remoteWSService.getTagUsers(token, stringObjectHashMap);
Map map = JSON.parseObject(tagUsers).toJavaObject(Map.class);
Map<String,Object> data = (Map<String,Object>)map.get("data");
List<String> openidArrayList = (List<String>) data.get("openid");
return openidArrayList;
}
}
二、企业微信应用免登录集成流程:
注意:
1、后端构造网页授权链接
2、前端拿到code后,调用后端接口获取token和用户信息(后端调用企业微信获取userid,然后创建token等)
3、前端重定向到新的页面(也可以是构造网页授权链接时的重定向页面)
企业微信集成应用程序、服务最详细教程_企业微信工作台集成自己web应用-CSDN博客