精华内容
下载资源
问答
  • 为了满足微信公众号点击菜单自动回复文字信息的需要,第三方平台微号帮提供了粉丝点菜单定时推送功能实现,粉丝点击公众号菜单栏可以自动回复文字信息,可以更改回复信息的昵称和头像,回复信息内容可以显示粉丝昵称...

    为了满足微信公众号点击菜单自动回复文字信息的需要,第三方平台微号帮提供了粉丝点菜单定时推送功能实现,粉丝点击公众号菜单栏可以自动回复文字信息,可以更改回复信息的昵称和头像,回复信息内容可以显示粉丝昵称、粉丝头像、粉丝openID,可以设置粉丝点击菜单自动回复多条信息等;如果担心打扰点击公众号菜单栏的粉丝,可以设置触发频率和免打扰时段;通过功能快速让公众号与点击菜单栏的粉丝互动,给粉丝推送信息,为公众号制造更多转化。

    微信公众号粉丝点击菜单栏后延迟自动推送信息功能,可以给粉丝推送文字、图文、图片、视频、语音、小程序、H5页面、小游戏、直播、在线课程、微商城、团购、文档文件、网盘地址、美食信息、美妆信息、旅游信息、产品信息、在线刷题等,可以按粉丝标签分组定向推送信息,帮助公众号更好地发挥其商业价值。

    1、微号帮:微信公众号粉丝点击菜单后延迟回复功能

    2、编辑公众号粉丝点菜单后延迟推送内容

    3、微号帮:开启公众号粉丝点菜单后延迟推送任务

    微号帮,微信公众号第三方工具助手平台,现全部功能注册登录授权添加公众号,免费试用7天!

    展开全文
  • java微信公众号自动回复文字加图片

    千次阅读 2019-06-29 20:23:39
    java微信公众号自动回复文字加图片开发流程详细流程,附上代码:第一步服务器(url)接口配置服务器(url)接口配置,此步骤就是微信授权接口的过程,如果域名都不改变,微信只会校验一次。此请求微信文档中说明了是...

    前提:第一次发布文章,如有写的不好的地方请指正或代码上可以优化的地方也请各路大神多多指教。顺带想吐槽下微信开发文档真的太恶心了,第一次接触下来让人头晕眼花,通过查阅各种资料后理清了思路,在此写上总结。

    开发流程

    1. 服务器(url)接口配置
    2. 发送文字(根据微信公众号文档“接收事件推送”事件接口编写代码)
    3. 生成二维码(账号管理下的“生成带参数的二维码”事件接口编写代码)
    4. 根据UI的图组合微信头像、微信名字、公众号二维码并调用素材管理下的“新增临时素材”接口上传图片
    5. 发送图片(调用消息管理下的客服消息下的客服接口-发消息(图片)接口)
    6. 进行裂变(调用“扫描带参数二维码事件”)(此步骤还未开发完,需根据业务需求变动)

    详细流程,附上代码:

    第一步服务器(url)接口配置

    服务器(url)接口配置,此步骤就是微信授权接口的过程,如果域名都不改变,微信只会校验一次。此请求微信文档中说明了是get请求。
    /**
         * 微信消息接收和token验证
         * @param request
         * @param response
         */
        @RequestMapping(value = "/weChatToken", method = RequestMethod.GET)
        public void weChatToken(HttpServletRequest request, HttpServletResponse response){
            boolean isGet = request.getMethod().toLowerCase().equals("get");
            if (isGet) {
                // 微信加密签名
                String signature = request.getParameter("signature");
                // 时间戳
                String timestamp = request.getParameter("timestamp");
                // 随机数
                String nonce = request.getParameter("nonce");
                // 随机字符串
                String echostr = request.getParameter("echostr");
    
                // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
                if (signature != null && WeChatUtil.checkSignature(signature, timestamp, nonce)) {
                    try {
                        PrintWriter print = response.getWriter();
                        print.write(echostr);
                        print.flush();
                    } catch (IOException e) {
                        log.error("微信消息接收和token验证异常",e);
                    }
                }
            }
        }
    

    第二步发送文字

    我这边的需求是发送的文字中要带上微信名称,所以还需要调用微信的获取accessToken接口跟获取用户信息接口。获取用户信息接口中需要openId,在用户点击关注微信公众号时,微信会自动发送过来。微信为什么会知道咱们的接口呢,因为咱们已经在第一步授权了接口,微信会以默认post请求的方式调用相同的路由,所以我们还得写一个post请求的接口。注:finally中是发送图片的动作。调用事件中运用了抽象工厂的设计模式,此处还有待优化。

    2.1 接口详解

    /**
         * 微信公众号 -->
         *   消息管理 -->
         *     接收普通消息、接收事件推送、被动回复用户消息
         *
         * @param request
         * @return
         */
        @ResponseBody
        @RequestMapping(value = "/weChatToken", method = RequestMethod.POST)
        public String responseEvent(HttpServletRequest request){
            WeChatMsgService weChatMsgService = null;
            Map<String, String> requestMessageMap = null;
            try {
                // 获取到请求参数
                requestMessageMap = XMLUtil.xmlToMap(request);
                log.info("获取到参数为:{}",requestMessageMap);
    
                // 判断请求参数中 MsgType的值或判断Event值
                String returnMessage = "";
                if (WeChatUtil.REQ_MESSAGE_TYPE_EVENT.equals(requestMessageMap.get("MsgType"))) {
                    // 如果后期有取消订阅事件,此处方法可以去除if
                    if (WeChatUtil.EVENT_TYPE_SUBSCRIBE.equals(requestMessageMap.get("Event"))) {
                        weChatMsgService = getType(requestMessageMap.get("Event"));
                        returnMessage = weChatMsgService.getReturnMessage(requestMessageMap);
                    }
                }
    
                return returnMessage;
            } catch (Exception e) {
                log.error("微信公众号自动回复异常",e);
            }finally {
                if (WeChatUtil.EVENT_TYPE_SUBSCRIBE.equals(requestMessageMap.get("Event"))) {
                    weChatMsgService = weChatCustomerServiceImgMessageServiceImpl;
                    weChatMsgService.getReturnMessage(requestMessageMap);
                }
    
            }
    
            return "";
        }
    

    2.1.2 使用设计模式调用不同的业务逻辑(default有待修改)
    此处注入了业务层
    @Autowired
    private WeChatMsgService weChatTextMessageServiceImpl;

    /**
         *  根据类型选择所需的业务处理逻辑
         * @param type
         */
        private WeChatMsgService getType(String type){
            switch (type) {
                case WeChatUtil.EVENT_TYPE_SUBSCRIBE:
                    return weChatTextMessageServiceImpl;
                default:
                    return null;
            }
        }
    

    2.1.3 调用业务层组合返回的信息(微信文档要求返回的信息为xml的形式)

    @Service
    @Slf4j
    public class WeChatTextMessageServiceImpl implements WeChatMsgService {
    
        @Autowired
        private WeiXinService weiXinService;
    
        @Override
        public String getReturnMessage(Map<String,String> requestMessageMap) {
    
            // 获取access_token
            WeiXinAccessTokenDto accessToken = weiXinService.getAccessToken();
            //获取用户信息
            WeiXinUserInfoDto userInfo = null;
            if (accessToken.getAccess_token() != null) {
                userInfo = weiXinService.getUnionID(accessToken.getAccess_token(), requestMessageMap.get("FromUserName"));
            }
    
            TextMessageResponseVo textMessageResponseVo = new TextMessageResponseVo();
            textMessageResponseVo.setFromUserName(requestMessageMap.get("ToUserName"));
            textMessageResponseVo.setToUserName(requestMessageMap.get("FromUserName"));
            textMessageResponseVo.setMsgType("text");
            textMessageResponseVo.setCreateTime(System.currentTimeMillis());
            textMessageResponseVo.setContent("欢迎" + userInfo.getNickname() + "11111");
    
            String result = WeChatUtil.messageToXml(textMessageResponseVo);
            return result;
        }
    
    }
    

    2.2 调用微信获取accessToken 的接口(业务层涉及到其他逻辑,所以就不全部粘出来了)

    @Override
        public WeiXinAccessTokenDto getAccessToken() {
            Map<String,String> reqParams = new HashMap<>();
            reqParams.put("appid","appid");
            reqParams.put("secret","secret");
            reqParams.put("grant_type","client_credential");
    
            String result = HttpUtil.sendGetJoint(accessTokenUrl, reqParams);
            // 将返回的字符串转为对象
            WeiXinAccessTokenDto weiXinAccessTokenDto = JSON.parseObject(result, WeiXinAccessTokenDto.class);
            return weiXinAccessTokenDto;
        }
    

    2.3 通过微信接口获取用户信息(微信获取用户信息的接口有三种,此处只举例一种)

    @Override
        public WeiXinUserInfoDto getUnionID(String accessToken, String openId) {
            Map<String,String> reqParams = new HashMap<>();
            reqParams.put("lang","zh_CN");
            reqParams.put("access_token",accessToken);
            reqParams.put("openid",openId);
    
            String result = HttpUtil.sendGetJoint(unionIDUrl, reqParams);
            log.info("获取微信用户信息为:{}",result);
            // 将返回的字符串转为对象
            WeiXinUserInfoDto weiXinUserInfoDto = JSON.parseObject(result, WeiXinUserInfoDto.class);
            return weiXinUserInfoDto;
        }
    

    2.4 返回信息实体类(此处用了Swagger,不用的童鞋可以删除注解。还要引入lombok)

    /**
     * @author: hugh_lin
     * @date: 2019/06/26 18:53
     * @Description: 微信公众号返回消息公共实体类
     * @Modifed:
     **/
    @Getter
    @Setter
    public class BaseWeChatResponseVo {
    
        @ApiModelProperty("开发者微信号")
        private String ToUserName;
    
        @ApiModelProperty("发送方帐号(一个OpenID)")
        private String FromUserName;
    
        @ApiModelProperty("消息创建时间 (整型)")
        private Long CreateTime;
    
        @ApiModelProperty("消息类型,event或text、image、voice、video、music、news")
        private String MsgType;
    
    }
    
    /**
     * @author: hugh_lin
     * @date: 2019/06/26 18:58
     * @Description: 微信公众号返回消息实体
     * @Modifed:
     **/
    @Getter
    @Setter
    public class TextMessageResponseVo extends BaseWeChatResponseVo{
    
        @ApiModelProperty("文本消息内容")
        private String Content;
    
    }
    

    2.5 微信工具类

    import com.thoughtworks.xstream.XStream;
    import com.thoughtworks.xstream.core.util.QuickWriter;
    import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
    import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
    import com.thoughtworks.xstream.io.xml.XppDriver;
    import com.youchengjvhe.weishangketang.model.vo.wechat.TextMessageResponseVo;
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.*;
    import java.net.URL;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    import java.util.Formatter;
    
    /**
     * @author: hugh_lin
     * @date: 2019/06/26 15:45
     * @Description: 微信消息处理工具类
     * @Modifed:
     **/
    @Slf4j
    public class WeChatUtil {
    
        //请求消息的类型------------------------------------------
        /**
         *  文本
         */
        public static final String REQ_MESSAGE_TYPE_TEXT = "text";
    
        /**
         *  图片
         */
        public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
    
        /**
         *  语音
         */
        public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
    
        /**
         *  视频
         */
        public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
    
        /**
         *  小视频
         */
        public static final String REQ_MESSAGE_TYPE_SHORTVIDEO = "shortvideo";
    
        /**
         *  地理位置
         */
        public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
    
        /**
         *  链接
         */
        public static final String REQ_MESSAGE_TYPE_LINK = "link";
    
        /**
         *  事件推送
         */
        public static final String REQ_MESSAGE_TYPE_EVENT = "event";
    
        /**
         *  事件类型--订阅
         */
        public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
    
        /**
         *  取消订阅
         */
        public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
    
        /**
         *  已关注用户扫描带参数二维码
         */
        public static final String EVENT_TYPE_SCAN = "SCAN";
    
        /**
         *  上报地理位置
         */
        public  static final String EVENT_TYPE_LOCATION = "LOCATION";
    
        /**
         *  自定义菜单事件
         */
        public static final String EVENT_TYPE_CLICK = "CLICK";
    
        //响应消息类型
        /**
         *  文本
         */
        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_ARTICLE = "news";
    
        //---------------------------------------------------------------------
    
        /**
         *  与测试账号接口配置信息中的Token要一致
         *  测试token,微信公众号内可自行配置
         */
        private static String token = "******";
    
        /**
         * 验证签名
         * @param signature
         * @param timestamp
         * @param nonce
         * @return
         */
        public static boolean checkSignature(String signature, String timestamp, String nonce) {
            String[] arr = new String[] { token, timestamp, nonce };
            // 将token、timestamp、nonce三个参数进行字典序排序
            Arrays.sort(arr);
            StringBuilder content = new StringBuilder();
            for (int i = 0; i < arr.length; i++) {
                content.append(arr[i]);
            }
            MessageDigest md = null;
            String tmpStr = null;
    
            try {
                md = MessageDigest.getInstance("SHA-1");
                // 将三个参数字符串拼接成一个字符串进行sha1加密
                byte[] digest = md.digest(content.toString().getBytes());
                tmpStr = byteToHex(digest );
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
            return tmpStr != null ? tmpStr.equals(signature) : false;
        }
    
        /**
         * 十六进制字节数组转为字符串
         * @param hash
         * @return
         */
        private static String byteToHex(final byte[] hash) {
            Formatter formatter = new Formatter();
            for (byte b : hash) {
                formatter.format("%02x", b);
            }
            String result = formatter.toString();
            formatter.close();
            return result;
        }
    
        /**
         * 扩展xstream使其支持CDATA
         */
        private static XStream xstream = new XStream(new XppDriver() {
            @Override
            public HierarchicalStreamWriter createWriter(Writer out) {
                return new PrettyPrintWriter(out) {
                    // 对所有xml节点的转换都增加CDATA标记
                    boolean cdata = true;
    
                    @Override
                    @SuppressWarnings("unchecked")
                    public void startNode(String name, Class clazz) {
                        super.startNode(name, clazz);
                    }
    
                    @Override
                    protected void writeText(QuickWriter writer, String text) {
                        if (cdata) {
                            writer.write("<![CDATA[");
                            writer.write(text);
                            writer.write("]]>");
                        } else {
                            writer.write(text);
                        }
                    }
                };
            }
        });
    
        /**
         * 文本消息转换成xml
         */
        public static String messageToXml(TextMessageResponseVo textMessageResponseVo) {
            xstream.alias("xml", textMessageResponseVo.getClass());
            return xstream.toXML(textMessageResponseVo);
        }
    
        /**
         * 链接url下载图片
         * @param urlList
         * @param path
         */
        public static void downloadPicture(String urlList,String path) {
            URL url = null;
            try {
                url = new URL(urlList);
                DataInputStream dataInputStream = new DataInputStream(url.openStream());
    
                FileOutputStream fileOutputStream = new FileOutputStream(new File(path));
                ByteArrayOutputStream output = new ByteArrayOutputStream();
    
                byte[] buffer = new byte[1024];
                int length;
    
                while ((length = dataInputStream.read(buffer)) > 0) {
                    output.write(buffer, 0, length);
                }
                fileOutputStream.write(output.toByteArray());
                dataInputStream.close();
                fileOutputStream.close();
            } catch (Exception e) {
                log.error("根据url下载图片异常",e);
            }
        }
    }
    
    

    第三步组合图片与文字信息并返回

    3、4、5步骤是在一个代码块中,所以此处进行详细分解。注:调用方式已在第二步说明

    3.1 整体方法(一些注入的路径,可以自行查阅微信公众号文档)

    /**
     * @author: hugh_lin
     * @date: 2019/06/28 16:37
     * @Description: 公众号客服消息(回复图片)
     * @Modifed:
     **/
    @Service
    public class WeChatCustomerServiceImgMessageServiceImpl implements WeChatMsgService {
    
        /**
         * 图片请求路径
         */
        @Value("${test.imgFileUrl}")
        private String imgFileUrl;
        /**
         * 图片生成路径
         */
        @Value("${test.imgFilePath}")
        private String imgFilePath;
        @Value("${weixin.getDownloadQrCodeUrl}")
        private String downloadQrCodeUrl;
    
        @Autowired
        private WeiXinService weiXinService;
    
        @Override
        public String getReturnMessage(Map<String, String> requestMessageMap) {
    
            // 获取accessToken
            WeiXinAccessTokenDto accessToken = weiXinService.getAccessToken();
            //获取用户信息
            WeiXinUserInfoDto userInfo = null;
            if (accessToken.getAccess_token() != null) {
                userInfo = weiXinService.getUnionID(accessToken.getAccess_token(), requestMessageMap.get("FromUserName"));
            }
    
            // 生成微信公众号二维码
            WeChatQrCodeDto qrCode = weiXinService.getQrCode(accessToken.getAccess_token(), userInfo.getOpenid());
    
            /* 下载二维码图片 */
            // 1.生成下载地址
            StringBuffer qrCodeUrl = new StringBuffer();
            qrCodeUrl.append(downloadQrCodeUrl);
            qrCodeUrl.append("?ticket=");
            qrCodeUrl.append(qrCode.getTicket());
            // 2.生成文件名并生成下载到本地的路径
            StringBuffer imgPath = new StringBuffer();
            imgPath.append("D:\\");
            imgPath.append(UUID.randomUUID());
            imgPath.append(".jpg");
            WeChatUtil.downloadPicture(qrCodeUrl.toString(),imgPath.toString());
    
            // 合成图片
            ImageUtil.generateCompositeImages("D:\\aaa.jpg",userInfo.getHeadimgurl(),imgPath.toString(),userInfo.getNickname());
    
            // 上传到微信公众号素材库,将数据转成json格式并取出需要的数据
            String weChatMedia = weiXinService.getWeChatMediaId(accessToken.getAccess_token(), imgPath.toString(), "image");
            JSONObject media = JSONObject.parseObject(weChatMedia);
    
            // 发送客服消息
            weiXinService.sendCustomerServiceMessage(accessToken.getAccess_token(),userInfo.getOpenid(),"image","media_id",media.getString("media_id"));
    
            return null;
        }
    }
    
    

    3.2 生成公众号二维码(此处为什么要调用微信接口生成呢,因为如果进行裂变的话,需要调用微信扫描二维码事件,所以更方便)

    @Override
        public WeChatQrCodeDto getQrCode(String accessToken, String content) {
    
            // 拼接url
            StringBuffer sb = new StringBuffer();
            sb.append(qrCodeUrl);
            sb.append("?access_token=");
            sb.append(accessToken);
    
            // 生成json数据进行请求
            JSONObject sceneStrJson = new JSONObject();
            sceneStrJson.put("scene_str",content);
            JSONObject sceneJson = new JSONObject();
            sceneJson.put("scene",sceneStrJson);
    
            JSONObject reqJson = new JSONObject();
            reqJson.put("action_name","QR_LIMIT_STR_SCENE");
            reqJson.put("action_info",sceneJson);
    
            String result = HttpUtil.sendPost(sb.toString(), reqJson.toString());
            log.info("获取微信公众号二维码信息为:{}",result);
            // 将返回的字符串转为对象
            WeChatQrCodeDto weChatQrCodeDto = JSON.parseObject(result, WeChatQrCodeDto.class);
            return weChatQrCodeDto;
        }
    

    3.3下载二维码图片到本地(因为要组合图片,需要用到。如何获取微信开发文档中有提及,此处就不多说了。此处调用的是WeChatUtil,在第二步中已写上)

    3.4合成图片(因为调用了Graphics画图工具,组合的图片大小及位置可自行根据UI图进行调整)

    /**
     * @author: hugh_lin
     * @date: 2019/06/28 09:52
     * @Description: 图片工具类
     * @Modifed:
     **/
    @Slf4j
    public class ImageUtil {
    
        /**
         * 生成组合图片(此处根据需求需要组成三张图片)
         * @param args
         *         args[0] backgroundImage 背景图路径
         *         args[1] weChatAvatar 微信头像路径
         *         args[2] qrCode 二维码路径
         *         args[3] 微信名
         */
        public static void generateCompositeImages(String... args){
            try {
                //创建初始图片
                BufferedImage img = new BufferedImage(1242, 2208, BufferedImage.TYPE_INT_RGB);
                //读取本地图片
                BufferedImage backgroundImage = ImageIO.read(new File(args[0]));
                //读取微信头像图片
                BufferedImage weChatAvatar = ImageIO.read(new URL(args[1]));
                //读取本地图片
                BufferedImage qrCode = ImageIO.read(new File(args[2]));
    
                //开启画图
                Graphics g = img.getGraphics();
    
                //将微信头像跟二维码设置为圆角(根据需求变更)
                weChatAvatar = setClip(weChatAvatar,100);
                qrCode = setClip(qrCode, 100);
    
                // 绘制缩小后的图
                g.drawImage(backgroundImage.getScaledInstance(1242,2208, Image.SCALE_DEFAULT), 0, 0, null);
                g.drawImage(weChatAvatar.getScaledInstance(85, 85, Image.SCALE_DEFAULT), 93, 35, null);
                g.drawImage(qrCode.getScaledInstance(370, 370, Image.SCALE_DEFAULT), 203, 1800, null);
    
                g.setColor(Color.black);
                g.setFont(new Font("微软雅黑", Font.PLAIN, 24));
                //绘制文字
                g.drawString(args[3], 214, 58);
    
                g.dispose();
                ImageIO.write(img, "jpg", new File(args[2]));
    
            } catch (Exception e) {
                log.error("生成组合图片异常",e);
            }
        }
    
        /**
         * 图片设置圆角
         * @param srcImage
         * @return
         * @throws IOException
         */
        private static BufferedImage setRadius(BufferedImage srcImage) throws IOException{
            int radius = (srcImage.getWidth() + srcImage.getHeight()) / 6;
            return setRadius(srcImage, radius, 2, 5);
        }
    
        /**
         * 图片设置圆角
         * @param srcImage
         * @param radius
         * @param border
         * @param padding
         * @return
         * @throws IOException
         */
        private static BufferedImage setRadius(BufferedImage srcImage, int radius, int border, int padding) throws IOException{
            int width = srcImage.getWidth();
            int height = srcImage.getHeight();
            int canvasWidth = width + padding * 2;
            int canvasHeight = height + padding * 2;
    
            BufferedImage image = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D gs = image.createGraphics();
            gs.setComposite(AlphaComposite.Src);
            gs.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            gs.setColor(Color.WHITE);
            gs.fill(new RoundRectangle2D.Float(0, 0, canvasWidth, canvasHeight, radius, radius));
            gs.setComposite(AlphaComposite.SrcAtop);
            gs.drawImage(setClip(srcImage, radius), padding, padding, null);
            if(border !=0){
                gs.setColor(Color.GRAY);
                gs.setStroke(new BasicStroke(border));
                gs.drawRoundRect(padding, padding, canvasWidth - 2 * padding, canvasHeight - 2 * padding, radius, radius);
            }
            gs.dispose();
            return image;
        }
    
        /**
         * 图片切圆角
         * @param srcImage
         * @param radius
         * @return
         */
        private static BufferedImage setClip(BufferedImage srcImage, int radius){
            int width = srcImage.getWidth();
            int height = srcImage.getHeight();
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D gs = image.createGraphics();
    
            gs.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            gs.setClip(new RoundRectangle2D.Double(0, 0, width, height, radius, radius));
            gs.drawImage(srcImage, 0, 0, null);
            gs.dispose();
            return image;
        }
    
    }
    
    

    3.5上传到微信公众号素材库(因为调用发送客服图片信息接口时需要用到参数,所以只能先上传到素材库)

     @Override
        public String getWeChatMediaId(String accessToken, String imgPath, String type) {
            try {
                File file=new File(imgPath);
                if (!file.exists() || !file.isFile()) {
                    return null;
                }
    
                // 拼接请求地址
                StringBuffer sb = new StringBuffer();
                sb.append(mediaUrl);
                sb.append("?access_token=");
                sb.append(accessToken);
                sb.append("&type=");
                sb.append(type);
    
                String result = HttpUtil.weChatFodderPost(sb.toString(), file);
    
                return result;
            } catch (Exception e) {
                log.error("素材流关闭异常",e);
            }
    
            return null;
        }
    

    3.6发送客服图片消息

    /**
         *  客服接口-发消息(此方法目前只支持:
         *                               发送文本消息、发送图片消息、发送语音消息、发送视频消息、发送音乐消息)
         * @param args
         * args[0] accessToken
         * args[1] OPENID
         * args[2] msgtype
         * args[3] content、media_id.....
         * args[4] 内容
         */
    @Override
        public void sendCustomerServiceMessage(String... args) {
            // 拼接url
            StringBuffer sb = new StringBuffer();
            sb.append(customerServiceUrl);
            sb.append("?access_token=");
            sb.append(args[0]);
    
            // 生成json数据进行请求
            JSONObject contentJson = new JSONObject();
            contentJson.put(args[3],args[4]);
    
            JSONObject reqJson = new JSONObject();
            reqJson.put("touser",args[1]);
            reqJson.put("msgtype",args[2]);
            reqJson.put(args[2],contentJson);
    
            HttpUtil.sendPost(sb.toString(), reqJson.toString());
    
        }
    

    最后,遇到个bug,因为需求是先返回文字再返回图片。测试的时候,偶然发生了先返回图片再返回文字的情况。从我的认知角度讲,会先执行finally块,再执行return,也是先返回return的消息,再返回finally中的消息。遇到此问题是否由网络延迟或等原因造成的还有待考究。

    最后附上个人微信:a373011739 ,谢谢各位大佬的观看,鞠躬。

    展开全文
  • 微信公众平台接口测试帐号申请: http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 内网穿透 微信官方文档:...

    本教程按我自己方式写的,不一定对,但是是可以实现的,为了方便逻辑都写在了Controller层,我数据是静态的,可以自行用数据库动态生成。

    微信公众平台接口测试帐号申请:
    http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

    在这里插入图片描述

    内网穿透

    在这里插入图片描述
    微信官方文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html

    当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
    请注意:
    1,关于重试的消息排重,推荐使用msgid排重。
    2,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。详情请见“发送消息-被动回复消息”。
    3,如果开发者需要对用户消息在5秒内立即做出回应,即使用“发送消息-被动回复消息”接口向用户被动回复消息时,可以在公众平台官网的开发者中心处设置消息加密。开启加密后,用户发来的消息和开发者回复的消息都会被加密(但开发者通过客服接口等API调用形式向用户发送消息,则不受影响)。关于消息加解密的详细说明,请见“发送消息-被动回复消息加解密说明”。 各消息类型的推送XML数据包结构如下:

    文本消息
    <xml>
      <ToUserName><![CDATA[toUser]]></ToUserName>
      <FromUserName><![CDATA[fromUser]]></FromUserName>
      <CreateTime>1348831860</CreateTime>
      <MsgType><![CDATA[text]]></MsgType>
      <Content><![CDATA[this is a test]]></Content>
      <MsgId>1234567890123456</MsgId>
    </xml>
    

    在这里插入图片描述

    Controller-------代码注释详细,不做多解释

    @RestController
    @RequestMapping("/a")
    public class TemplateController {
    
        private static final Logger logger = Logger.getLogger(TemplateController.class);
        private static final org.slf4j.Logger logger2 = LoggerFactory.getLogger(TemplateController.class);
    
        //url校验
        @RequestMapping(value = "/c", method = RequestMethod.GET)
        public String weiXinGet(HttpServletRequest req, HttpServletResponse resp) throws Exception {
            logger.info("微信请求接入>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            //获取微信加密签名
            String signature = req.getParameter("signature");
            logger2.info("微信签名是: {}", signature);
            //获取时间戳
            String timestamp = req.getParameter("timestamp");
            //获取随机数
            String nonce = req.getParameter("nonce");
            //获取随机字符串
            String echostr = req.getParameter("echostr");
            //判断加密后的字符串和签名是否一样.如果一样表示接入成功
            if (InspectionUtil.checkSignature(signature, timestamp, nonce)) {
                logger.info("已成功发送———————" + echostr);
                return echostr;
            }
            return null;
        }
    
        //消息处理
        @RequestMapping(value = "/c", method = RequestMethod.POST)
        public String weiXinPost(@RequestBody InWeiXinMessage weiXinMessage) throws Exception {
            OutWeiXinMessage outWeiXinMessage = new OutWeiXinMessage();
            //发送方
            outWeiXinMessage.setFromUserName(weiXinMessage.getToUserName());
            //接受方
            outWeiXinMessage.setToUserName(weiXinMessage.getFromUserName());
            //str = weiXinMessage.getFromUserName();
            //消息创建时间
            outWeiXinMessage.setCreateTime(new Date().getTime());
            //消息类型
            outWeiXinMessage.setMsgType("text");
            System.out.println(weiXinMessage.getContent());
            //消息内容
            String content = weiXinMessage.getContent();
            switch (content) {
                case "一级菜单<1>":
                    outWeiXinMessage.setContent("<a href=\"weixin://bizmsgmenu?msgmenucontent=二级菜单<1>&msgmenuid=102\">二级菜单<1></a>\n" +
                            "<a href=\"weixin://bizmsgmenu?msgmenucontent=二级菜单<2>&msgmenuid=103\">二级菜单<2></a>\n" +
                            "<a href=\"weixin://bizmsgmenu?msgmenucontent=二级菜单<3>&msgmenuid=104\">二级菜单<3></a>");
                    break;
                case "二级菜单<1>":
                    outWeiXinMessage.setContent("二级菜单<1>");
                    break;
                case "一级菜单<2>":
                    outWeiXinMessage.setContent("我是一级菜单<2>");
                    break;
                case "一级菜单<3>":
                    outWeiXinMessage.setContent("我是一级菜单<3>");
                    break;
                default:
                    outWeiXinMessage.setContent("<a href=\"weixin://bizmsgmenu?msgmenucontent=一级菜单<1>&msgmenuid=102\">一级菜单<1></a>\n" +
                            "<a href=\"weixin://bizmsgmenu?msgmenucontent=一级菜单<2>&msgmenuid=103\">一级菜单<2></a>\n" +
                            "<a href=\"weixin://bizmsgmenu?msgmenucontent=一级菜单<3>&msgmenuid=104\">一级菜单<3></a>");
            }
            XStream xStream = new XStream();
            //设置别名
            xStream.alias("xml", OutWeiXinMessage.class);
            String xml = xStream.toXML(outWeiXinMessage);
            return xml;
        }
    }
    

    XStream的pom.xml,其他常用的不做多阐述

            <dependency>
                <groupId>com.thoughtworks.xstream</groupId>
                <artifactId>xstream</artifactId>
                <version>1.4.9</version>
            </dependency>
    

    InspectionUtil

    public class InspectionUtil {
    
        public static boolean checkSignature(String signature, String timestamp, String nonce) {
            String[] strArrays = {Constast.Token, timestamp, nonce};
            Arrays.sort(strArrays);
            //拼接字符串
            String str = new StringBuffer().append(strArrays[0]).append((strArrays[1])).append((strArrays[2])).toString();
            //加密的字符串跟微信加密签名是否一样
            return InspectionUtil.sha1(str).equals(signature);
        }
    
        //将字符串进行sha1加密
        public static String sha1(String str) {
            try {
                MessageDigest digest = MessageDigest.getInstance("SHA-1");
                digest.update(str.getBytes());
                byte messageDigest[] = digest.digest();
                // Create Hex String
                StringBuffer hexString = new StringBuffer();
                // 字节数组转换为十六进制数
                for (int i = 0; i < messageDigest.length; i++) {
                    String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                    if (shaHex.length() < 2) {
                        hexString.append(0);
                    }
                    hexString.append(shaHex);
                }
                return hexString.toString();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
    

    InWeiXinMessage-----这里接收xml格式并转换,用的注解实现的

    @Data
    @XmlRootElement(name = "xml")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class InWeiXinMessage {
        @XmlElement(name = "ToUserName")
        private String toUserName;	    //开发者微信号
        private String FromUserName;    //发送方帐号(一个OpenID)
        private Long CreateTime;	    //消息创建时间 (整型)
        private String  MsgType;	    //消息类型,text/image/voice
        private Long MsgId;	            //消息id,64位整型
    
        private String Content;	        //文本消息内容
    
        private String PicUrl;	        //图片链接(由系统生成)
        private String MediaId;	        //语音消息媒体id,可以调用获取临时素材接口拉取数据。
    }
    

    OutWeiXinMessage-----这里本来想用注解实现,无法实现改用XStream

    @Data
    public class OutWeiXinMessage {
        private String ToUserName;	    //用户微信号
        private String FromUserName;    //用户帐号(一个OpenID)
        private Long CreateTime;	    //消息创建时间 (整型)
        private String  MsgType;	    //消息类型,text/image/voice
    
        private String Content;	        //文本消息内容
    }
    

    启动项目,扫码关注,查看结果

    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 微信开发自定义菜单点击文本回复

    千次阅读 2018-04-19 08:01:09
    微信开发自定义回复文本&lt;?php namespace Home\Controller; use Think\Controller; use LaneWeChat\Core\Curl; class AutoreplyController extends Controller { private $mp; public function _initialize...

    微信开发自定义回复文本

    <?php
    namespace Home\Controller;
    use Think\Controller;
    use LaneWeChat\Core\Curl;
    
    class AutoreplyController extends Controller
    {
    	private $mp;
    	public function _initialize(){
    		$mp = getCurrentMp();
    		if(empty($mp)){
    			$this->error('无使用的公众号',U('mp/index'));
    			exit;
    		}else{
    			$this->mp = $mp;
    		}
    	}
    
    	public function index(){
             $this->display('replytext');
    	}
    	//回复文本消息
    	public function replytext(){
    		$data['content'] = I('content');
    		$replyid = M('mp_reply_text')->add($data);
    		if($replyid){
    		$mp = $this->mp;
    		$arr['mp_id'] = $mp['id'];
    		$arr['type'] = 'text';
    		$arr['keyword'] = I('keyword');
    		$arr['reply_id'] = $replyid;
    
    		$ret = M('mp_rule')->add($arr);
    		if($ret){
    			$this->ajaxReturn(array('msg'=>'添加成功','url'=>U('keyword',['type'=>'text'])));
    		}else{
    				$this->ajaxReturn(array('msg'=>$ret));
    			
    		}
    		}else{
    				$this->ajaxReturn(array('msg'=>$ret));
    		}
    	}
    }

    自定义菜单点击文本回复在wechatquest.php中设置为

     public static function eventClick(&$request){
            //获取该分类的信息
            $eventkey = $request['eventkey'];
            $request['content'] = $eventkey;
            return self::text($request);
            // $content = '收到点击菜单事件,您设置的key是' . $eventKey;
            // return ResponsePassive::text($request['fromusername'], $request['tousername'], $content);
        }

    展开全文
  • 微信公众号菜单教程

    2020-12-17 10:40:06
    鼠标选中单个菜单后可以编辑选中菜单内容,选中发送消息后,如果用户在使用中点击公众号的菜单,公众号会自动回复之前设置的内容。 单击自建图文进入图文消息编辑界面,按照提示输入内容,上方的控件可以插入图片...
  • 微信自定义菜单以及自动回复

    千次阅读 2016-09-28 12:56:08
    一:自动回复  1:在开发选项里有接口权限可以调用接口 / 开发者工具 (接口权限都是无上限的) (这两个是使用不同的后台,所以需要分别登录)    2:接口配置信息,打开公众平台开发者文档,里面有一些...
  • 微信自定义菜单

    2018-04-12 19:41:38
    微信自定义菜单是用户请求微信端,所有不需要把代码保存到自己的服务器上,但是注意的是必须有access_token,而且access_token两个小时之后就会失效,所有在没有到两个小时的时候就要请求微信端生成新的access_token...
  • 在测试账号申请时,我们还...这篇文章将讲解如何点击底部菜单,返回文字信息。 官方文档https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html <xml> ...
  • 当下,微信公众号自动回复消息设置文字超链接,微号帮提供了功能粉丝对话定时推送实现,可以给微信公众号自动回复消息设置文字超链接,同时消息内容还可以显示粉丝昵称、头像、openID,通过功能可以让公众号快速连接...
  • 回复数据格式如下: <xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</...
  • 这个发送图片,并不是点击输入框右侧的“➕”,去选择本地照片,而是要把图片上传的公众号的服务器。 按照开发文档...
  • 回复代码,先看一条的情况。 else if (wxMessgeBean.getEventKey().equals("mh005")){ bean.setMsgType("news"); bean.setArticleCount(1); bean.setArticles(new WXMessgeBean.ArticleMessage()); ...
  • 目录 实现的效果图 #图1:django的后台管理系统之自定义菜单 ...1、看微信的文档 2、用到的工具 wechatpy 3、顺一下思路 二、代码实现 wechat/models.py: wechat/utils.py: wechat/admin.py: 由...
  • 1.  权限管理:点开二级菜单进入三级菜单显示 角色(基础权限)和按钮权限 ...3. 菜单管理:无限级别自定义菜单,自定义菜单图标,业务菜单和系统菜单分离,菜单状态显示隐藏(递归处理) 4. 数据
  • 好多人都问我:“微信自定义菜单如何集成多客服功能” 答案就在文章里。做的很巧妙。
  • 微信公众号开发者模式下的自定义菜单回复图片和图文1 注意的是,区分好个人订阅号和测试号;2.先获取token值;自定义菜单代码(php)讲下重点代码: 话不多说,我分析我遇到的问题和解决方法。 1 注意的是,区分好...
  • 微信内部客服消息 1. 这里要讲的核心内容是基于微信浏览器的URL Schemes来实现微信内部消息的控制,就是weixin://,这个是不是和http://有一样的格式,表达的意思类似,就是告知浏览器,采取什么协议解析URL内容...
  • 我们在关注微信公众号、点击菜单回复关键词或者执行程序中的某些事件(比如,订单提交成功等。)的时候,可以看到,有些公众号同时给我们回复了两条或两条以上的消息内容。 内容形式不限于文字,可能是这些消息...
  • 关注回复微信用户关注公众号回复 35. 文本回复:匹配关键词进行文本回复 36. 图文回复:匹配关键词进行图文回复 37. 应用命令:匹配关键词进行命令操作,例如微信发送命令,执行服务器重启、关机...
  • 有时我们需要点击菜单就调出扫一扫,然后将值传给自己的程序进行处理,返回一个结果给用户。比如打开一个网页,返回一个文字或者图文消息。笔者之前对微信上的开发也是一窍不通,一顿浑水摸鱼后大概明白了,网页上...
  • 微信关键字自动回复功能实现

    万次阅读 2015-03-10 19:12:44
    微信自动回复可以分为三种,一种是首次关注的欢迎语,一种是关键字自动回复,还有就是无法识别的回复。这三种模式在实现上是完全一样的,只是基于的事件不同而已。要想实现自动回复功能,首先要对微信相关API进行...
  • 关注回复微信用户关注公众号回复 35. 文本回复:匹配关键词进行文本回复 36. 图文回复:匹配关键词进行图文回复 37. 应用命令:匹配关键词进行命令操作,例如微信发送命令,执行服务器重启、关机...
  • 这个系列会介绍如何快速上手进行公众号、WEB/小程序、钉钉等平台的开发接入。我肯定不敢说自己写的东西比腾讯、阿里官方文档更...从微信服务的业务结构上来讲,订阅号和服务号属于同一类应用,小程序、WEB属于另一类...
  • 微信公众号自定义菜单获取不到信息回复 自定义菜单设置类型click,且返回信息为回复信息,但是获取不到回复信息,原因可能是网站使用公众号原始ID与微信公众号设置的公众原始ID一致,为了不出其它意外问题,网站...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,043
精华内容 1,217
关键字:

微信点击菜单回复文字