• 微信公众号消息接入(普通消息+模板消息)


    服务号只能是企业申请,这里我用测试号复现所有功能。(测试号不能做支付功能)

    一、公众号普通消息

    1、实现目标

      1、“硅谷课堂”公众号实现根据关键字搜索相关课程,如:输入“java”,可返回java相关的一个课程;

      2、“硅谷课堂”公众号点击菜单“关于我们”,返回关于我们的介绍

      3、关注或取消关注等

    2、消息接入

      参考文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

      接入微信公众平台开发,开发者需要按照如下步骤完成:

      1、填写服务器配置

      2、验证服务器地址的有效性

      3、依据接口文档实现业务逻辑

    2.1、公众号服务器配置

      在测试管理 -> 接口配置信息,点击“修改”按钮,填写服务器地址(URL)和Token,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)

    说明:本地测试,url改为内网穿透地址

    image-20220825175054153

    2.2、验证来自微信服务器消息

    (1)概述

      开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:

    image-20220825175227648

      开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

      1、将token、timestamp、nonce三个参数进行字典序排序

      2、将三个参数字符串拼接成一个字符串进行sha1加密

      3、开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

    (2)代码实现

      创建MessageController

    @Slf4j
    @RestController
    @RequestMapping("/api/wechat/message")
    public class MessageController {
        private static final String token = "ggkt";
    
        @Autowired
        private MessageService messageService;
    
        //订单支付成功模板消息测试
        @GetMapping("/pushPayMessage")
        public Result pushPayMessage() throws WxErrorException {
            messageService.pushPayMessage(1L);
            return Result.ok(null);
        }
    
        /**
         * 服务器有效性验证
         * @param request
         * @return
         */
        @GetMapping
        public String verifyToken(HttpServletRequest request) {
            String signature = request.getParameter("signature");
            String timestamp = request.getParameter("timestamp");
            String nonce = request.getParameter("nonce");
            String echostr = request.getParameter("echostr");
            log.info("signature: {} nonce: {} echostr: {} timestamp: {}", signature, nonce, echostr, timestamp);
            if (this.checkSignature(signature, timestamp, nonce)) {
                log.info("token ok");
                return echostr;
            }
            return echostr;
        }
    
        /**
         * 接收微信服务器发送来的消息
         * @param request
         * @return
         * @throws Exception
         */
        @PostMapping
        public String receiveMessage(HttpServletRequest request) throws Exception {
    
            WxMpXmlMessage wxMpXmlMessage = WxMpXmlMessage.fromXml(request.getInputStream());
            System.out.println(JSONObject.toJSONString(wxMpXmlMessage));
            //Map param = this.parseXml(request);
            //String message=messageService.receiveMessage(param);
            //return message;
            return "success";
        }
    
        private Map<String, String> parseXml(HttpServletRequest request) throws Exception {
            Map<String, String> map = new HashMap<String, String>();
            InputStream inputStream = request.getInputStream();
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStream);
            Element root = document.getRootElement();
            List<Element> elementList = root.elements();
            for (Element e : elementList) {
                map.put(e.getName(), e.getText());
            }
            inputStream.close();
            inputStream = null;
            return map;
        }
    
        private boolean checkSignature(String signature, String timestamp, String nonce) {
            String[] str = new String[]{token, timestamp, nonce};
            //排序
            Arrays.sort(str);
            //拼接字符串
            StringBuffer buffer = new StringBuffer();
            for (int i = 0; i < str.length; i++) {
                buffer.append(str[i]);
            }
            //进行sha1加密
            String temp = SHA1.encode(buffer.toString());
            //与微信提供的signature进行匹对
            return signature.equals(temp);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

      完成之后,我们的校验接口就算是开发完成了。接下来就可以开发消息接收接口了。

    2.3、消息接收

      https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html

      接下来我们来开发消息接收接口,消息接收接口和上面的服务器校验接口地址是一样的,都是我们一开始在公众号后台配置的地址。只不过消息接收接口是一个 POST 请求。

      在公众号后台配置的时候,消息加解密方式选择了明文模式,这样在后台收到的消息直接就可以处理了。微信服务器给我发来的普通文本消息格式如下:

    <xml>
        <ToUserName>ToUserName>
        <FromUserName>FromUserName>
        <CreateTime>1348831860CreateTime>
        <MsgType>MsgType>
        <Content>Content>
        <MsgId>1234567890123456MsgId>
    xml>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    image-20220825175528113

      看到这里,大家心里大概就有数了,当我们收到微信服务器发来的消息之后,我们就进行 XML 解析,提取出来我们需要的信息,去做相关的查询操作,再将查到的结果返回给微信服务器。

      这里我们先来个简单的,我们将收到的消息解析并打印出来

       /**
         * 接收微信服务器发送来的消息
         * @param request
         * @return
         * @throws Exception
         */
        @PostMapping
        public String receiveMessage(HttpServletRequest request) throws Exception {
    
            WxMpXmlMessage wxMpXmlMessage = WxMpXmlMessage.fromXml(request.getInputStream());
            System.out.println(JSONObject.toJSONString(wxMpXmlMessage));
            return "success";
        }
    
        private Map<String, String> parseXml(HttpServletRequest request) throws Exception {
            Map<String, String> map = new HashMap<String, String>();
            InputStream inputStream = request.getInputStream();
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStream);
            Element root = document.getRootElement();
            List<Element> elementList = root.elements();
            for (Element e : elementList) {
                map.put(e.getName(), e.getText());
            }
            inputStream.close();
            inputStream = null;
            return map;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3、配置内网穿透

    3.1、注册用户

      网址:https://ngrok.cc/login/register

    image-20220302155428572

    3.2、实名认证

      (1)注册成功之后,登录系统,进行实名认证,认证费2元,认证通过后才能开通隧道

    image-20220825175728273

    3.3、开通隧道

    (1)选择隧道管理 -> 开通隧道

      最好一个是免费服务器,建议选择付费服务器,10元/月,因为免费服务器使用人数很多,经常掉线

    image-20220825175752747

    image-20220302160247603

      上面这张图是视频中的配置,我的配置稍微有点差别,不过本地映射的端口都是一样的。

    (3)开通成功后,查看开通的隧道

      这里开通了两个隧道,一个用于后端接口调用,一个用于公众号前端调用

    image-20220825175922871

    3.4、启动隧道

    (1)下载客户端工具

    image-20220825175954592

    image-20220825180007805

    (3)解压,找到bat文件,双击启动

    image-20220302160924245

    (4)输入隧道id,多个使用逗号隔开,最后回车就可以启动

    image-20220307092329552

    这张图是视频中的id,已经过期了,用你自己的id。

    image-20220825180115801

      注意,测试号中的配置可以在这个时候添加。如下图

    image-20220825180219679

       先添加下面的JS接口安全域名,再配置上面的接口。

    3.5 测试

      启动服务后,在公众号发送文本消息

    image-20220825193652902

      消息就会显示在后台日志中:

    image-20220825193724347

    4、消息业务的实现

    4.1、service_vod模块创建接口

    (1)创建CourseApiController方法,根据课程关键字查询课程信息

    image-20220825192234111

        @ApiOperation("根据关键字查询课程")
        @GetMapping("inner/findByKeyword/{keyword}")
        public List<Course> findByKeyword(
                @ApiParam(value = "关键字", required = true)
                @PathVariable String keyword){
            QueryWrapper<Course> queryWrapper = new QueryWrapper();
            queryWrapper.like("title", keyword);
            List<Course> list = courseService.list(queryWrapper);
            return list;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.2、创建模块定义接口

      这里是负责service_wechat远程调用service_vod

    (1)service_client下创建子模块service_course_client

    image-20220302142317244

    (2)定义根据关键字查询课程接口

    @FeignClient(value = "service-vod")
    public interface CourseFeignClient {
    
        @ApiOperation("根据关键字查询课程")
        @GetMapping("/api/vod/course/inner/findByKeyword/{keyword}")
        List<Course> findByKeyword(@PathVariable String keyword);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.3、service_wechat引入依赖

    <dependency>
        <groupId>com.atguigugroupId>
        <artifactId>service_course_clientartifactId>
        <version>0.0.1-SNAPSHOTversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.4、service_wechat模块实现方法

    (1)MessageService

    public interface MessageService {
    //    接收微信服务器发送来的消息
        String receiveMessage(Map<String, String> param);
    
        //订单支付成功
        void pushPayMessage(long id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)MessageServiceImpl

    @Service
    public class MessageServiceImpl implements MessageService {
    
        @Autowired
        private CourseFeignClient courseFeignClient;
    
        @Autowired
        private WxMpService wxMpService;
    
    
    //    接收微信服务器发送来的消息
        @Override
        public String receiveMessage(Map<String, String> param) {
            String content="";
            String msgType = param.get("MsgType");
            //判断什么类型消息
            switch (msgType){
                case "text":    //普通文本类型,输入关键字java、mysql等
                    content=this.search(param);
                    break;
                case "event":   //关注、取消关注、点击关于我们
                    String event = param.get("Event");
                    String eventKey = param.get("EventKey");
                    //关注
                    if("subscribe".equals(event)){  //关注
                        content=this.subscribe(param);
                    }else if("unsubscribe".equals(event)) { //取消关注
                        content=this.unsubscribe(param);
                    }else if("CLICK".equals(event) && "aboutUs".equals(eventKey)){  //关于我们
                        content=this.aboutUs(param);
                    }else{
                        content="success";
                    }
                    break;
                default:    //其他情况
                    content = "success";
            }
            return content;
        }
    
        @Override
        public void pushPayMessage(long id) {
            //微信openid
            String openid = "你的openid";
            WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
                    .toUser(openid)//要推送的用户openid
                    .templateId("8iVYZuYXGfaK2Irvdrj_sgHtOOA49QBoXRr6_97U448")//模板id
                    .url("http://ggkt2.vipgz1.91tunnel.com/#/pay/"+id)//点击模板消息要访问的网址
                    .build();
            //3,如果是正式版发送消息,,这里需要配置你的信息
            templateMessage.addData(new WxMpTemplateData("first", "亲爱的用户:您有一笔订单支付成功。", "#272727"));
            templateMessage.addData(new WxMpTemplateData("keyword1", "1314520", "#272727"));
            templateMessage.addData(new WxMpTemplateData("keyword2", "java基础课程", "#272727"));
            templateMessage.addData(new WxMpTemplateData("keyword3", "2022-01-11", "#272727"));
            templateMessage.addData(new WxMpTemplateData("keyword4", "100", "#272727"));
            templateMessage.addData(new WxMpTemplateData("remark", "感谢你购买课程,如有疑问,随时咨询!", "#272727"));
            String msg = null;
            try {
                msg = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
            } catch (WxErrorException e) {
                e.printStackTrace();
            }
            System.out.println(msg);
        }
    
        //关于我们
        private String aboutUs(Map<String, String> param) {
            return this.text(param, "硅谷课堂现开设Java、HTML5前端+全栈、大数据、" +
                    "全链路UI/UE设计、人工智能、大数据运维+Python自动化、Android+HTML5混合开" +
                    "发等多门课程;同时,通过视频分享、谷粒学苑在线课堂、大厂学苑直播课堂等多种" +
                    "方式,满足了全国编程爱好者对多样化学习场景的需求,已经为行业输送了大量" +
                    "IT技术人才。").toString();
        }
    
        //取消关注
        private String unsubscribe(Map<String, String> param) {
            return "success";
        }
    
        //关注
        private String subscribe(Map<String, String> param) {
            return this.text(param,"感谢你关注“硅谷课堂”,可以根据关键字" +
                    "搜索您想看的视频教程,如:JAVA基础、Spring boot、大数据等")
                    .toString();
        }
    
    
        /**
         * 处理关键字搜索事件
         * 图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
         * @param param
         * @return
         */
        private String search(Map<String, String> param) {
            String fromusername = param.get("FromUserName");
            String tousername = param.get("ToUserName");
            String content = param.get("Content");
            //单位为秒,不是毫秒
            Long createTime = new Date().getTime() / 1000;
            StringBuffer text = new StringBuffer();
            List<Course> courseList = courseFeignClient.findByKeyword(content);
            if(CollectionUtils.isEmpty(courseList)) {
                text = this.text(param, "请重新输入关键字,没有匹配到相关视频课程");
            } else {
                //一次只能返回一个
                Random random = new Random();
                int num = random.nextInt(courseList.size());
                Course course = courseList.get(num);
                StringBuffer articles = new StringBuffer();
                articles.append("");
                articles.append("<![CDATA["</span><span class="token operator">+</span>course<span class="token punctuation">.</span><span class="token function">getTitle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">"]]>");
                articles.append("+course.getTitle()+"]]>");
                articles.append("+course.getCover()+"]]>");
                articles.append("+course.getId()+"]]>");
                articles.append("");
    
                text.append("");
                text.append("+fromusername+"]]>");
                text.append("+tousername+"]]>");
                text.append("+createTime+"]]>");
                text.append("");
                text.append("");
                text.append("");
                text.append(articles);
                text.append("");
                text.append("");
            }
            return text.toString();
        }
    
        /**
         * 回复文本
         * @param param
         * @param content
         * @return
         */
        private StringBuffer text(Map<String, String> param, String content) {
            String fromusername = param.get("FromUserName");
            String tousername = param.get("ToUserName");
            //单位为秒,不是毫秒
            Long createTime = new Date().getTime() / 1000;
            StringBuffer text = new StringBuffer();
            text.append("");
            text.append("+fromusername+"]]>");
            text.append("+tousername+"]]>");
            text.append("+createTime+"]]>");
            text.append("");
            text.append("+content+"]]>");
            text.append("");
            return text;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152

      上面的openid如下图:

    image-20220825193151100

    4.5、更改MessageController方法

     @PostMapping
        public String receiveMessage(HttpServletRequest request) throws Exception {
    
    //        WxMpXmlMessage wxMpXmlMessage = WxMpXmlMessage.fromXml(request.getInputStream());
    //        System.out.println(JSONObject.toJSONString(wxMpXmlMessage));
            Map<String, String> param = this.parseXml(request);
            String message=messageService.receiveMessage(param);
            return message;
    //        return "success";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5、测试公众号消息

    (1)点击个人 -> 关于我们,返回关于我们的介绍

    image-20220825194048290

    image-20220825194105851

    (2)在公众号输入关键字,返回搜索的课程信息

    image-20220825194129547

    二、公众号模板消息

    1、实现目标

      购买课程支付成功微信推送消息

    2、模板消息实现

      接口文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html

    3、申请模板消息

      首先我们需要知道,模板消息是需要申请的。

      但是我们在申请时还是有一些东西要注意,这个在官方的文档有非常详细的说明。

      https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Operation_Specifications.html

    image-20220825194211999

      这个大家好好看看。选择行业的时候可要谨慎些,因为这个一个月只可以修改一次。

      下面看看在哪里申请,硅谷课堂已经申请过,忽略

    image-20220301150232206

    image-20220301150430663

    4、添加模板消息

      审核通过之后,我们就可以添加模板消息,进行开发了。

      我们点击模板消息进入后,直接在模板库中选择你需要的消息模板添加就可以了,添加之后就会在我的模板中。会有一个模板id,这个模板id在我们发送消息的时候会用到。

      模板消息如下:

    在这里插入图片描述

      我们需要模板消息:

      ​ 1、订单支付成功通知;

      模板库中没有的模板,可以自定义模板,审核通过后可以使用。

      测试号看上面这个没用,测试号看下面的部分

    5、公众号测试号申请模板消息

    5.1、新增测试模板

    image-20220825194409051

    5.2、填写信息

    (1)下载示例参考

      下载地址:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Operation_Specifications.html

    image-20220825194456484

    (2)填写模板标题和模板内容

    image-20220302172321054

    image-20220825194559916

    6、模板消息接口封装

    6.1、MessageController

      添加方法

    @GetMapping("/pushPayMessage")
    public Result pushPayMessage() throws WxErrorException {
        messageService.pushPayMessage(1L);
        return Result.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.2、service接口

      MessageService

    void pushPayMessage(Long orderId);
    
    • 1

    6.3、service接口实现

      (1)MessageServiceImpl类

      (2)openid值

    image-20220307092708923

    image-20220825194559916

      id值用你自己的,切记,老师提供的那个由于他当时申请的隧道现在已经过期了,所以用不了。

    	 @Autowired
        private WxMpService wxMpService;
     	@Override
        public void pushPayMessage(long id) {
            //微信openid
            String openid = "用你的";
            WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
                    .toUser(openid)//要推送的用户openid
                    .templateId("8iVYZuYXGfaK2Irvdrj_sgHtOOA49QBoXRr6_97U448")//模板id
                    .url("http://ggkt2.vipgz1.91tunnel.com/#/pay/"+id)//点击模板消息要访问的网址(这个隧道早已经过期了,用你的)
                    .build();
            //3,如果是正式版发送消息,,这里需要配置你的信息
            templateMessage.addData(new WxMpTemplateData("first", "亲爱的用户:您有一笔订单支付成功。", "#272727"));
            templateMessage.addData(new WxMpTemplateData("keyword1", "1314520", "#272727"));
            templateMessage.addData(new WxMpTemplateData("keyword2", "java基础课程", "#272727"));
            templateMessage.addData(new WxMpTemplateData("keyword3", "2022-01-11", "#272727"));
            templateMessage.addData(new WxMpTemplateData("keyword4", "100", "#272727"));
            templateMessage.addData(new WxMpTemplateData("remark", "感谢你购买课程,如有疑问,随时咨询!", "#272727"));
            String msg = null;
            try {
                msg = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
            } catch (WxErrorException e) {
                e.printStackTrace();
            }
            System.out.println(msg);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    6.4 swagger测试

    image-20220825200510347

    image-20220825200537043

      可以看到,点击成功之后,公众号测试号中也给我们推送了模板消息。

      接口实现大多数都是微信官方给的示例,个人开发者没有公众号,只能在测试号中完成这些功能,为了方便直接使用内网穿透实现调用,但是测试号是不能实现微信支付的。

  • 相关阅读:
    L14D6内核模块编译方法
    Crystal Ball—甲骨文水晶球风险管理软件(概念以及实战——中级案例篇)
    场景交互与场景漫游-osgGA库(5)
    Java之原子性问题的解决
    HTTP响应
    java基础10题
    微服务系列之单体架构
    STM32的命名含义
    <C++>详解list类
    macos端文件夹快速访问工具 Default Folder X 最新for mac
  • 原文地址:https://blog.csdn.net/qq_43753724/article/details/126531776