微信公众号对接--客服消息

当你关注公众号,然后在公众号里面发送消息,会收到回复,这个就是客服消息

参考文档:接收普通消息 接收事件推送  客服接口-发消息

想要对接客服消息,首先要获取access_token,这个可以参考我之前的文章:对接微信公众号-CSDN博客

注意点:

1.后台收到消息里面会有一个微信号,如果你的后台配置了多个公众号,可以通过这个微信号来确定是哪个公众号发的消息,测试号的微信账号在右上角,正式公众号的微信号就是原始id,以gh_开头的 

2.正式环境需要把后台的ip配置到白名单中,否则调用微信接口会报错

 回复消息的方式

回复消息的方式有两种,一种是接收到用户消息后直接在response里面回复,文档:回复文本消息 | 微信开放文档

这种方式比较简单,缺点是必须保证在5s内回复,否则会重试,不知道你有没有遇到过以下场景,当你发送一条消息,过了一会突然收到好几条相同的信息,就是卡顿重试造成的。

还有一种方式是客服消息,这种是后台主动给用户发消息, 功能更加强大,不仅仅可以回复用户消息,还可以在用户关注公众号,扫描二维码,点击菜单时回复用户消息,参考文档:客服接口-发送消息

 由于客服消息是异步发送,在发送的过程中我们可以调用客服输入状态接口,实现以下效果

用户体验更好,参考文档: 客服输入状态

客服消息规则如下,就是说如果用户发送了消息,你可以在48小时内下发最多5条消息

场景下发额度额度有效期
用户发送消息5条48小时
点击自定义菜单3条1分钟
关注公众号3条1分钟
扫描二维码3条1分钟

如何对接服务器,如何接收消息可以看我之前写的博客: 对接微信公众号-CSDN博客

 Java代码实现

以下代码实现了客服回复文本消息,图文消息和图片消息

当用户输入文本则回复你好,输入图文则返回一篇新闻,输入图片则返回一张图片

@RestController
@RequestMapping("/wx")
@Api(tags = "微信管理")
public class WxController {
    @Autowired
    private WeixinService weixinService;

    @RequestMapping("receiveWeixin")
    @ApiOperation(value = "接收微信消息")
    public void receiveWeixin(String signature, String timestamp, String nonce, String echostr,
                              HttpServletRequest request, HttpServletResponse response) {
        try {
            //服务器校验
            String token = "123456";
            List<String> list = new ArrayList<>();
            list.add(token);
            list.add(timestamp);
            list.add(nonce);
            list = CollectionUtil.sort(list, (o1, o2) -> o1.compareTo(o2));
            String str = CollectionUtil.join(list, "");
            if (!signature.equals(SecureUtil.sha1(str))) {
                return;
            }
            if (StringUtils.isNotEmpty(echostr)) {
                //校验服务器
                response.getOutputStream().write(echostr.getBytes());
                response.getOutputStream().flush();
                return;
            }

            //接收消息
            Document document = XmlUtil.readXML(request.getInputStream());
            Map map = XmlUtil.xmlToMap(document);
            JSONObject json = JSONObject.parseObject(JSONObject.toJSON(map).toString()).getJSONObject("xml");
            System.out.println(json);
            //消息类型
            String msgType = json.getString("MsgType");
            //公众号微信账号
            String account = json.getString("ToUserName");
            if (msgType.equals("event")) {
                // 处理事件消息,比如当用户关注公众号可以下发个欢迎消息
            } else if (msgType.equals("text")) {
                // 处理用户消息
                WeixinReceiveText weixinReceiveText = JSONObject.parseObject(json.toJSONString(), WeixinReceiveText.class);
                ThreadUtil.execAsync(() -> weixinService.replyMessage(weixinReceiveText.getFromUserName(), weixinReceiveText.getContent()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @PostMapping(value = "/uploadMaterial")
    @ApiOperation(value = "上传资源")
    public JSONObject uploadMaterial(MultipartFile file) {
        return weixinService.addMaterial(file, "image");
    }
}
@Service
public class WeixinService {
    protected static Logger logger = LoggerFactory.getLogger(WeixinService.class);
    private final String TOKEN_KEY = "WEIXIN_TOKEN:";
    @Autowired
    private RedisTemplate redisTemplate;
    private final static String appId = "你的appid";
    private final static String appsecret = "你的appsecret";

    public WxUser getUserInfoByCode(String code) {
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={}&secret={}&code={}&grant_type=authorization_code";
        url = StrUtil.format(url, appId, appsecret, code);
        String res = HttpUtil.get(url);
        JSONObject tokenInfo = handleResult(res);
        String token = tokenInfo.getString("access_token");
        String openid = tokenInfo.getString("openid");
        String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token={}&openid={}&lang=zh_CN";
        userInfoUrl = StrUtil.format(userInfoUrl, token, openid);
        res = HttpUtil.get(userInfoUrl);
        JSONObject userInfoJson = handleResult(res);
        return JSONObject.parseObject(userInfoJson.toJSONString(), WxUser.class);
    }

    public synchronized String getToken() {
        String redisKey = TOKEN_KEY + appId;
        String token = (String) redisTemplate.opsForValue().get(redisKey);
        if (StrUtil.isNotEmpty(token)) return token;
        JSONObject params = new JSONObject();
        params.put("grant_type", "client_credential");
        params.put("appid", appId);
        params.put("secret", appsecret);
        String str = HttpUtil.post(WeixinConstants.ACCESS_TOKEN_URL, params.toJSONString());
        JSONObject result = handleResult(str);
        token = result.getString("access_token");
        redisTemplate.opsForValue().set(redisKey, token
                , result.getIntValue("expires_in"), TimeUnit.SECONDS);
        return token;
    }

    private JSONObject handleResult(String str) {
        JSONObject result = JSONObject.parseObject(str);
        if (StrUtil.isNotBlank(result.getString("errcode"))) {
            if (result.getString("errcode").equals("0")) {
                return result;
            }
            if (result.getString("errcode").equals("40014")) {
                clearToken(appId);
            }
            throw new RuntimeException(StrUtil.format("微信接口出错, errcode: {}, msg: {}"
                    , result.getString("errcode"), result.getString("errmsg")));
        }
        return result;
    }

    public void clearToken(String appId) {
        String redisKey = TOKEN_KEY + appId;
        redisTemplate.delete(redisKey);
    }

    public JSONObject getMenuInfo() {
        String token = getToken();
        String str = HttpUtil.get(StrUtil.format(WeixinConstants.GET_MENU_INFO_URL, token));
        JSONObject result = handleResult(str);
        JSONObject menuInfo = result.getJSONObject("selfmenu_info");
        if (menuInfo == null) {
            menuInfo = new JSONObject();
        }
        return menuInfo;
    }

    public void createMenu(String menu) {
        String token = getToken();
        String str = HttpUtil.post(StrUtil.format(WeixinConstants.CREATE_MENU_URL, token), menu);
        handleResult(str);
    }

    public void customSend(String openId, String message) {
        String token = getToken();
        JSONObject body = new JSONObject();
        body.put("touser", openId);
        body.put("msgtype", "text");
        JSONObject content = new JSONObject();
        body.put("text", content);
        content.put("content", message);
        String str = HttpUtil.post(StrUtil.format(WeixinConstants.CUSTOM_SEND_URL, token), body.toJSONString());
        handleResult(str);
    }

    /**
     * 发送图片消息
     */
    public void customSendPic(String openId, String mediaId) {
        String token = getToken();
        JSONObject body = new JSONObject();
        body.put("touser", openId);
        body.put("msgtype", "image");
        JSONObject image = new JSONObject();
        body.put("image", image);
        image.put("media_id", mediaId);
        String str = HttpUtil.post(StrUtil.format(WeixinConstants.CUSTOM_SEND_URL, token), body.toJSONString());
        handleResult(str);
    }

    public void customSendPicText(String openId, WeixinPicTextVO weixinPicTextVO) {
        String token = getToken();
        JSONObject body = new JSONObject();
        body.put("touser", openId);
        body.put("msgtype", "news");
        JSONObject news = new JSONObject();
        body.put("news", news);
        JSONArray articles = new JSONArray();
        news.put("articles", articles);
        articles.add(weixinPicTextVO);
        String str = HttpUtil.post(StrUtil.format(WeixinConstants.CUSTOM_SEND_URL, token), body.toJSONString());
        handleResult(str);
    }

    // type: image, voice, video, thumb
    public JSONObject addMaterial(MultipartFile file, String type) {
        File dir = null;
        try {
            String token = getToken();
            String url = StrUtil.format(WeixinConstants.ADD_METERIAL_URL, token, type);
            //获取临时文件夹目录
            String tempPath = System.getProperty("java.io.tmpdir");
            String dirName = UUID.randomUUID().toString();
            dir = new File(tempPath + dirName);
            dir.mkdirs();
            File tempFile = new File(dir.getAbsolutePath() + File.separator + file.getOriginalFilename());
            tempFile.deleteOnExit();
            file.transferTo(tempFile);
            HashMap<String, Object> paramMap = new HashMap<>();
            paramMap.put("media", tempFile);
            String str = HttpUtil.post(url, paramMap);
            return handleResult(str);
        } catch (Exception e) {
            throw new RuntimeException("上传文件出错", e);
        } finally {
            FileUtil.del(dir);
        }
    }

    // command: Typing,CancelTyping
    public void customTyping(String openId, String command) {
        String token = getToken();
        JSONObject body = new JSONObject();
        body.put("touser", openId);
        body.put("command", command);
        String str = HttpUtil.post(StrUtil.format(WeixinConstants.CUSTOM_TYPING_URL, token), body.toJSONString());
        handleResult(str);
    }

    public void replyMessage(String openId, String content) {
        try {
            customTyping(openId, "Typing");
            if ("文本".equals(content)) {
                customSend(openId, "你好");
            } else if ("图文".equals(content)) {
                WeixinPicTextVO weixinPicTextVO = new WeixinPicTextVO();
                weixinPicTextVO.setTitle("年轻人开始流行三无婚礼");
                weixinPicTextVO.setDescription("近年来,越来越多的年轻人告别繁杂冗余的俗套,简化仪式和环节,选择流程简单、时间充裕的极简婚礼");
                weixinPicTextVO.setUrl("https://www.jnnews.tv/guanzhu/p/2024-01/15/1027166.html");
                weixinPicTextVO.setPicurl("https://www.jnnews.tv/a/10001/202401/bdabe3e9925a31e867137ed95f722edc.jpeg");
                customSendPicText(openId, weixinPicTextVO);
            } else if ("图片".equals(content)) {
                // mediaId通过addMaterial获得
                customSendPic(openId, "im8rzmF--MD8vDCSnju1oif7lPzvgBMUepg0X1gh8CR6iD2L27CcxLhouIMigR2F");
            }
        } catch (Exception e) {
            logger.error("微信客服消息出错: {}", e);
        } finally {
            customTyping(openId, "CancelTyping");
        }
    }
}
public class WeixinConstants {

    /**
     * 获取token
     */
    public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/stable_token";

    /**
     * 获取菜单
     */
    public static final String GET_MENU_INFO_URL = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={}";

    /**
     * 创建菜单
     */
    public static final String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token={}";

    /**
     * 客服接口-发消息
     */
    public static final String CUSTOM_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={}";

    /**
     * 客服接口-输入状态
     */
    public static final String CUSTOM_TYPING_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token={}";

    /**
     * 添加素材
     */
    public static final String ADD_METERIAL_URL = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token={}&type={}";
}
@Data
@ApiModel(value = "WeixinReceiveText", description = "WeixinReceiveText")
public class WeixinReceiveText extends WeixinBaseVO {
    private String Content;
    private String MsgId;
    private String MsgDataId;
    private String Idx;
}
@Data
public class WeixinPicTextVO {
    private String title;
    private String description;
    private String url;
    private String picurl;
}

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值