实现语言 java
实现的前提:已经实现了公众号的其他一些基本功能,比如访问菜单。
1、微信公众号中添加客服功能
2、添加客服
3、接口编写。
3-1、 我找了好久才找到。微信公众号接口文档中--自定义菜单中,创建一个类型为click的菜单(如下图1,2)
图1
这里会去访问 创建客服会话的- 新版客服功能 - 创建会话接口,微信文档中都有(https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN)
然后点击立即咨询 客服连接成功 回复消息。
图2
{
"type":"click",
"name":"立即咨询",
"key":"kf"
},
kf是自定义的。
* 1、click:点击推事件用户点击click类型按钮后,微信会会向你的接口工程发送消息(你自己接口工程的地址是公众号配置中的验证地址 如图3)消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
这个button 会跟关注和取消关注一样去发送给后端服务器请求,就是配置的地址。 (公众号中配置的地址位置如下)
图3
3-2 、java代码 ,
controller
/** * 处理消息请求(包括关注和取消关注) 包好所有微信发给服务器的通知 * * @param msg * @return */ @RequestMapping(value = "validate", method = RequestMethod.POST, produces = {MediaType.TEXT_XML_VALUE}) @ResponseBody public Object handleMesg(@RequestBody RecieveMsg msg) throws Exception { Object out = weiXinService.handleMessage(msg); System.out.println(String.valueOf(msg)); return out; }
service
/** * 取消和关注操作 推送 也有客服按钮请求的操作(kf为自定义) * * @param msg * @return */ public Object handleMessage(RecieveMsg msg) { if(Objects.equals(msg.getEvent(),"CLICK") &&Objects.equals(msg.getEventKey(),"kf")){ String kf = kfseesion(msg.getFromUserName()); PushMsg out = new PushMsg(); out.setFromUserName(msg.getToUserName()); out.setToUserName(msg.getFromUserName()); out.setMsgType("text"); out.setCreateTime(System.currentTimeMillis()); out.setContent(kf); return out; } String url = "/handleValidate"; JSONObject obj = HttpClientUtils.httpRequest(weiXinBean.getHttpUrl() + url, "POST", JSONUtils.beanToJson(msg)); PushMsg out = new PushMsg(); out.setContent(String.valueOf(obj.get("content")).replace("APPID",weiXinBean.getAppId()).replace("REDIRECT_URL",weiXinBean.getYuMing())); out.setFromUserName(String.valueOf(obj.get("fromUserName"))); out.setMsgType(String.valueOf(obj.get("msgType"))); out.setToUserName(String.valueOf(obj.get("toUserName"))); out.setCreateTime(obj.getLong("createTime")); return out; }
/**
* 功能描述:创建会话
* @param:
* @return:
* @author: syl
* @date: 2019/2/23 0023 下午 4:02
*/
public String kfseesion(String openId) {
String accessToken = Tools.getAccessToken(getAccessTokenUrl());
String urlToken = WxContstants.kfSessionUrl.replace("ACCESS_TOKEN",accessToken);JSONObject kfInfo = getonlinekflist();
if(!StringUtils.isEmpty(kfInfo.get("kf_online_list"))){
JSONArray kfOnlineList = kfInfo.getJSONArray("kf_online_list");
if(kfOnlineList.size()==0){
return "服务人员未在线";
}
Random random = new Random();
int i = random.nextInt(kfOnlineList.size());
JSONObject pj = new JSONObject();
JSONObject item = (JSONObject) kfOnlineList.get(i);
pj.put("kf_account",String.valueOf(item.get("kf_account")));
//pj.put("kf_account","kf2002@gh_02e32b1d4ea2");
pj.put("openid",openId);
JSONObject jsonObject = HttpClientUtils.httpRequest(urlToken,"POST", String.valueOf(pj));
Integer errcode = jsonObject.getInteger("errcode");
String errmsg = jsonObject.getString("errmsg");
if (Objects.equals(0, errcode)) {
return "客服连接成功,请问有什么可以帮助您";
} else if(Objects.equals(65415, errcode)) {
return "服务人员未在线";
}else{
return errmsg;
}
}else{
return "服务人员未在线";
}}
/** * 功能描述:获取在线客服列表 * @param: * @return: * @author: syl * @date: 2019/8/13 0013 上午 10:31 */ public JSONObject getonlinekflist() { String accessToken = Tools.getAccessToken(getAccessTokenUrl()); String urlToken = WxContstants.getonlinekflist.replace("ACCESS_TOKEN",accessToken); JSONObject jsonObject = HttpClientUtils.httpRequest(urlToken, "GET", ""); return jsonObject; }
工具类
RecieveMsg.java
package com.navitek.maternal.apiweb.bean;
import lombok.Getter;
import lombok.Setter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @author 26968
*/
@Setter
@Getter
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class RecieveMsg {
/**
* 开发者微信号
*/
protected String FromUserName;
/**
* 发送方帐号(一个OpenID)
*/
protected String ToUserName;
/**
* 消息创建时间
*/
protected Long CreateTime;
/**
* 消息类型
* text 文本消息
* image 图片消息
* voice 语音消息
* video 视频消息
* music 音乐消息
*/
protected String MsgType;
/**
* 消息id
*/
protected Long MsgId;
/**
* 文本内容
*/
private String Content;
/**
* 图片链接地址 有系统生成
*/
private String PicUrl;
/**
* 多媒体
*/
private String MediaId;
/**
* 事件
*/
private String Event;
/**
* 菜单的KEY值
*/
private String EventKey;
/**
*
*/
private String Ticket;
}
WxContstants.java
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author 26968
*/
public class WxContstants {
//保存用户验证码
public static ConcurrentMap<String, Map<String, Object>> VERIFY_CODE = new ConcurrentHashMap<String, Map<String, Object>>();
//验证码超时时间
public static long maxTime = 300;
/**
* 菜单类型
*/
public static String VIEW = "view";
/**
* 网页授权后根据code 获取用access_token
*/
public static final String WEB_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
/**
* 获取用户基本信息接口
*/
public static final String GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
/**
* 暂时存储accesstoken
*/
public static final Map<String, Long> currentHashMap = new HashMap<>();
/**
* 放token值
*/
public static final Map<String, Object> TOKEN_MAP = new HashMap<>();
public static final String ACCESS_TOKEN = "access_token";
/**
* 消息类型 (文本类型)
*/
public static final String TEXT = "text";
/**
* image类型
*/
public static final String IMAGE = "image";
/**
* event 事件类型
*/
public static final String Event = "event";
/**
* subscribe 关注事件
*/
public static final String SUBSCRIBE = "subscribe";
/**
* unsubscribe 取消关注事件
*/
public static final String UNSUBSCRIBE = "unsubscribe";
/**
* 菜单点击事件
*/
public static final String CLICK = "CLICK";
/**
* news 图文消息
*/
public static final String NEWS = "news";
/**
* 获取access_token的Url
*/
public static final String ACCESSTOKENURL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET";
/**
* 创建自定义菜单接口
*/
public static final String ButtonUrl = " https://api.weixin.qq.com/cgi-bin/menu/create?access_token=";
/**
* 删除菜单
*/
public static final String DELETEBUTTON = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=";
/**
* 网页授权url
*/
public static final String AUTHORIZE = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT&response_type=code&scope=snsapi_userinfo&state=22#wechat_redirect";
/**
* 客服相关 新增 获取 删除 绑定
*/
public static final String addKfUrl = " https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN";
public static final String getKfListUrl = " https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN";
public static final String getonlinekflist = " https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist?access_token=ACCESS_TOKEN";
public static final String delKfUrl = "https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT";
public static final String bbKfUrl = " https://api.weixin.qq.com/customservice/kfaccount/inviteworker?access_token=ACCESS_TOKEN";
public static final String kfSessionUrl = " https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN";
public static final String actSessionUrl = " https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN";
public static final String templateSend = " https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN";
}
tools.java
import com.alibaba.fastjson.JSONObject;
import com.navitek.maternalweb.common.WxContstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
/**
* @author 26968
*/
@Slf4j
public class Tools {
/**
* 将 map转化为xml格式
*
* @param params
* @return
*/
public static String toXml(Map<String, String> params) {
StringBuilder xml = new StringBuilder();
xml.append("<xml>");
Iterator iterator = params.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = (Map.Entry) iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (!StringUtils.isEmpty(value)) {
xml.append("<").append(key).append(">");
xml.append((String) entry.getValue());
xml.append("</").append(key).append(">");
}
}
xml.append("</xml>");
return xml.toString();
}
/**
* 将xml格式的字符串转化为 map
*
* @param xmlStr
* @return
*/
public static Map<String, String> xmlToMap(String xmlStr) {
XmlHelper xmlHelper = XmlHelper.of(xmlStr);
return xmlHelper.toMap();
}
public static String getRealIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
public static String getRealIpV2(HttpServletRequest request) {
String accessIP = request.getHeader("x-forwarded-for");
return null == accessIP ? request.getRemoteAddr() : accessIP;
}
/**
* 获取access_Token
*
* @return
*/
public static String getAccessToken(String ACCESSTOKENURL) {
/**
* @Todo 暂时存在静态 map中
*/
String accessToken = null;
//先判断时间
Long tokenTime = StringUtils.isEmpty((Long) WxContstants.currentHashMap.get(WxContstants.ACCESS_TOKEN)) ? 0:(Long) WxContstants.currentHashMap.get(WxContstants.ACCESS_TOKEN);
//如果当前时间大于设置的时间则进行重新获取accessToken
if (Instant.now().getEpochSecond() > tokenTime) {
JSONObject result = HttpClientUtils.httpRequest(ACCESSTOKENURL, "GET", "");
if (StringUtils.isEmpty(result.get(WxContstants.ACCESS_TOKEN))){
log.info("调取微信接口获取access_token失败=="+result);
}
accessToken = String.valueOf(result.get(WxContstants.ACCESS_TOKEN));
WxContstants.TOKEN_MAP.put(WxContstants.ACCESS_TOKEN,accessToken);
WxContstants.currentHashMap.put(WxContstants.ACCESS_TOKEN,Instant.now().getEpochSecond()+6000);
}
accessToken = String.valueOf(WxContstants.TOKEN_MAP.get(WxContstants.ACCESS_TOKEN));
return accessToken;
}
public static String getRandCode(){
Random ne=new Random();
return String.valueOf(ne.nextInt(9999-1000+1)+1000);
}
public static String getUUID(){
UUID uuid=UUID.randomUUID();
String str = uuid.toString();
String uuidStr=str.replace("-", "");
return uuidStr;
}
}
4、效果。这样就可以实现简单的客服功能了,客服必须在线。 不在线就回复代码中写的反馈信息。
(如图4 ,5)
图4
图5