微信公众号开发(消息推送)_微信公众号消息推送开发-程序员宅基地

技术标签: 微信  微信公众号  

微信公众号开发

代码地址

运行效果

在这里插入图片描述

微信公众号简介

微信公众号分为服务号、订阅号、企业号,订阅号可以个人申请,服务号和企业号要有企业资质才可以。

我们所说的微信公众号开发指的是订阅号和服务号。关于订阅号和服务器的区别,官方是这样解释的

  • 服务号:主要偏向于服务交互(功能类似12315,114,银行,提供绑定信息,服务交互),每月可群发4条消息;服务号**适用人群:媒体、企业、政府或其他组织。
  • 订阅号:主要偏向于为用户传达资讯,(功能类似报纸杂志,为用户提供新闻信息或娱乐趣事),每天可群发1条消息;订阅号**适用人群:个人、媒体、企业、政府或其他组织。

注册微信公众号

进入微信公众号注册页面https://mp.weixin.qq.com/点击公众号右上方的注册按钮,进入注册界面,填写基本信息,选择订阅号, 完成身份认证, 即可。

注册测试公众号

个人订阅号有一些接口是没有权限的,也就是说个人订阅号无法调用一些高级的权限接口,如生成二维码、网页授权、自定义菜单、微信支付这样的接口权限个人订阅号是没有调用权限的, 幸运的是,微信公众平台提供了测试公众账号,测试公众号有很多个人订阅号不具备的权限, 测试公众号的注册地址为:

http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

用微信扫描页面中的二维码进行登录,登录成功后,就可以看到腾讯分配给我们的测试公众号的信息了,如下图所示, 接下来我们就可以搭建环境,进行开发测试了

img

测试公众号的所拥有的接口权限如下:

image.png

image.png

搭建微信本地调试环境

开发基于微信公众号的应用最大的痛苦之处就是调试问题,每次实现一个功能后都需要部署到一个公网服务器进行测试,因为微信用户每次向公众号发起请求时,微信服务器会先接收到用户的请求,然后再转发到我们的服务器上,也就是说,微信服务器是要和我们的服务器进行网络交互,所以我们必须保证我们的服务器外网可以访问到,这种部署到公网服务器进行测试的做法对于我们开发者来说简直是噩梦。所以我们要想一个办法可以做到本地部署,本地调试代码,而要做到这一点,那么我们要解决的问题就是将内网的部署服务器映射到外网,让微信服务器可以正常访问到,幸运的是,借助于第三方软件Ngrok,我们就可以做得到。Ngrok是一个免费的软件Ngrok,使用Ngrok后,我们就可以实现内网穿透,也就是说我们可以将内网的服务器映射到外网给别人访问,这对于我们在本地开发环境中调试微信代码是以及给用户演示一些东西非常快速和有帮助的,因为可以直接使用我们自己的内网的电脑作为服务器。不过需要翻墙访问.常用的内网穿透工具有natapp,ngrok,dingding,关于微信公众号开发,这三个工具我都使用了,只有natapp可以正常开发。

关于natapp的使用网上很多,我在这里就不在介绍了。

natapp成功标志
在这里插入图片描述

可以通过访问http://xt77eg.natappfree.cc访问到我们本机的服务

微信公众号接入(校验签名)

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

在这里插入图片描述

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

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

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

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

@Controller
@RequestMapping(value = "wx")
public class WeiController{
    

    /**
     * 公众号appid
     */
    @Value("${wx.appid}")
    private  String appid;

    /**
     * 公众号appSecret
     */
    @Value("${wx.secret}")
    private  String secret;

    /**
     * 微信消息接收和token验证
     * @param request
     * @param response
     * @throws IOException
     */
    @GetMapping("/weChatToken")
    public  void weChat(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 && CheckoutUtil.checkSignature(signature, timestamp, nonce)) {
    
                try {
    
                    PrintWriter print = response.getWriter();
                    print.write(echostr);
                    print.flush();
                } catch (IOException e) {
    
                    e.printStackTrace();
                }
            }
        }
    }
}
public class CheckoutUtil {
    


    public static  String token  = "999";

    /**
     * 验证签名
     * @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;
    }

进入微信测试公众号管理界面,在接口配置信息中填入映射的外网地址和代码中声明的token,如下图所示:

点击提交,会显示配置成功,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bPMvNxRI-1608702695943)(typora-user-images\image-20201214105001081.png)]

到此,我们的公众号应用已经能够和微信服务器正常通信了,也就是说我们的公众号已经接入到微信公众平台了。

给指定用户推送消息

网页授权获取用户openid

如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息(openId),进而实现业务逻辑。

关于网页授权回调域名的说明:

  • 1、在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;

  • 2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com 无法进行OAuth2.0鉴权

  • 3、如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可

获取用户openId步骤:

  • 1、引导用户进入授权页面同意授权,获取code

  • 2、通过code换取openId

代码如下:

@Controller
@RequestMapping(value = "wx")
public class WeiController{
    


    private  String appid="微信公众号的appid";
    private  String secret="微信公众号的secret";

    /**
     * 获取微信用户code,并重定向获取用户openId
     * @return
     */
    @GetMapping("/getUserCode")
    public String  getUserCode(){
    
        String backUrl = "http://xt77eg.natappfree.cc/wx/getUserOpenId";
        String getOpenIdUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri="+ backUrl+"&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect";
        getOpenIdUrl = getOpenIdUrl.replace("APPID",appid);
        return "redirect:" + getOpenIdUrl;
    }

    /**
     * 获取用户openId
     * @return
     * @throws IOException
     */
    @GetMapping("/getUserOpenId")
    @ResponseBody
    public  String getUserOpenId()throws IOException{
    
        //获取code
        String code = request.getParameter("code");
        //换取用户openid
        String url="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
        url=url.replace("APPID", appid).replace("SECRET", secret).replace("CODE", code);
        JSONObject result = Util.doGetJson(url);
        JSONObject jSONObject = JSONObject.parseObject(String.valueOf(result));
        String openid = jSONObject.getString("openid");
        return openid;
    }

给指定用户发送模板信息

首先要准备一个模板,测试号可自定义模板,但在正式公众号我们要申请,或者使用别人已经申请过的模板。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KtE1NG6h-1608702695950)(typora-user-images\image-20201214111214457.png)]

pom:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--微信模版消息推送三方sdk-->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-mp</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <version>2.1.8.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

Controller:

@Controller
@RequestMapping(value = "wx")
public class WeiController{
    
    @Resource
    PushMessageService pushMessageService;

    /**
     * 向每个用户推送消息
     * @return
     */
    @GetMapping("/sendMessage")
    @ResponseBody
    public String sendMessage(){
    
        String openId =  "用户openId";
        if(!"".equals(openId)){
    
            AlarmParamsDTO dto = new AlarmParamsDTO("申请进度", "国家奖学金", "申请通过", time, "成功");
            dto.setOpenId(openId);
            pushMessageService.pushMessage(dto);
        }
        return "success";
    }

Service:

@Service
@Slf4j
public class PushMessageServiceImpl implements PushMessageService{
    

    private  String appid="微信公众号appid";
    private  String secret="微信公众号secret";

    /**
     * 给微信公众号某个用户推送信息
     * @param alarmParamsDTO
     */
    @Override
    public void pushMessage(AlarmParamsDTO alarmParamsDTO) {
    
        //1,配置
        WxMpInMemoryConfigStorage wxStorage = new WxMpInMemoryConfigStorage();
        wxStorage.setAppId(appid);
        wxStorage.setSecret(secret);
        WxMpService wxMpService = new WxMpServiceImpl();
        wxMpService.setWxMpConfigStorage(wxStorage);
        List<WxMpTemplateData> wxMpTemplateData = Arrays.asList(
                new WxMpTemplateData("first",alarmParamsDTO.getFirst(),"#000000"),
                new WxMpTemplateData("keyword1",alarmParamsDTO.getKeyword1(),"#000080"),
                new WxMpTemplateData("keyword2",alarmParamsDTO.getKeyword2(),"#0000FF"),
                new WxMpTemplateData("keyword3",alarmParamsDTO.getKeyword3(),"#FFD700"),
                new WxMpTemplateData("remark",alarmParamsDTO.getRemark(),"#00FF00")
        );
        //2,推送消息
        WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
                .toUser(alarmParamsDTO.getOpenId())
                .templateId("tIDrdFcqFGMsTnc462H49_DbjgXUuIjsqIlQttq7VDE")
                .data(wxMpTemplateData)
                .url("http://www.baidu.com")
                .build();
        try {
    
            wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
        } catch (Exception e) {
    
            System.out.println("推送失败:" + e.getMessage());
        }

    }
}

entity:

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class AlarmParamsDTO {
    


    /**
     * 推送信息小标题
     */
    private String first;

    /**
     * 学生姓名
     */
    private String keyword1;

    /**
     * 申请资助类型
     */
    private String keyword2;

    /**
     * 申请状态
     */
    private String keyword3;

    /**
     * 申请结果
     */
    private String remark;

    /**
     * 用户微信openId,唯一标识
     */
    private String openId;

    public AlarmParamsDTO(String first, String keyword1, String keyword2, String keyword3, String remark) {
    
        this.first = first;
        this.keyword1 = keyword1;
        this.keyword2 = keyword2;
        this.keyword3 = keyword3;
        this.remark = remark;
    }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_47740092/article/details/111588455

智能推荐

OpenCart 官方开发指南翻译一 —— 模块开发_opencart主题开发-程序员宅基地

文章浏览阅读1k次。模块开发  编写 OpenCart 模块可以很好地了解 OpenCart 如何运作的基本原理。就像 OpenCart 的其余部分一样,模块遵循 MVCL 设计模式。本文档指南将介绍如何使用 MVC-L 的每个组件来创建模块的后台和前台部分。创建模块的最简单方法是从 HostJars 下载 DIY Module Builder 框架。该模块包含如何理解和构建自己的模块的目录结构、文件和说明。本页面是..._opencart主题开发

C语言——printf()函数参数传递问题_传递给printf的额外参数怎么解决-程序员宅基地

文章浏览阅读4.6k次,点赞5次,收藏18次。C语言——printf( ) 函数参数传递问题一、参数说明printf() 函数是输出函数,可以实现和用户之间的交流,该函数的参数由两部分组成,即格式化字符串、待打印项 0-n 项,函数的格式如下:printf(格式化字符串, 待打印项1, 待打印项2, ...);二、工作原理printf() 函数的调用是告诉计算机把变量的值传递给程序,首先,程序把传入的值按照变量类型放入“栈”内存区;然后控制权交给 printf() 函数,该函数根据转换说明从栈中读取数据。三、举例说明参数的传递过程_传递给printf的额外参数怎么解决

JSTL标签库http://java.sun.com/jsp/jstl/fmt 报红怎么解决_导入jstl标签库报红-程序员宅基地

文章浏览阅读341次。http://archive.apache.org/dist/jakarta/taglibs/standard/binaries/,找到 "jakarta-taglibs-standard-1.1.2.zip 下载完成后进行解压。_导入jstl标签库报红

Windows10 Hyper-v 虚拟机安装点心云,实现闲置宽带共享。_pve安装点心云-程序员宅基地

文章浏览阅读1w次,点赞4次,收藏38次。点心云是一个可以利用闲置设备宽带变现的新平台,用闲置的宽带和设备上传各大新媒体的数据,让你的设备成为加速上传的节点,为业务方的内容提供加速,从而获取现金奖励。点心云全网单价最高,100兆上行网速可达每日8元收益,欢迎各大网友来点心创造自己的财富。_pve安装点心云

图片照片处理-程序员宅基地

文章浏览阅读172次。证件照换背景颜色、证件照制作、证件照排版打印。:去除背景抠图、换背景、动画、照片讲故事。小程序:佐糖证件照(wx)、照侠(zfb):去底色、去水印、去除背景抠图。:常用证件照大小排版打印。:生成油画风格的肖像画。

React 组件学习(函数组件、 类组件)-程序员宅基地

文章浏览阅读4.2k次,点赞10次,收藏40次。目录1. React 组件介绍2. 函数组件1)什么是函数组件?2)定义函数组件3)使用组件总结:3. 类组件-复习 class 语法1.掌握 class 定义类,定义属性,定义函数2.掌握 extends 继承父类4. 类组件-基本使用步骤1.什么是类组件?2.定义类组件3.使用类组件总结:5. 类组件-组件抽离6. 类组件-无状态组件和有状态组件和1.无状态组件2.有状态组件3.它们的区别4.如何去选择..._函数组件

随便推点

SpringCloud优点、缺点_springcloud优缺点-程序员宅基地

文章浏览阅读2.2w次,点赞3次,收藏24次。优点:1、服务拆分粒度更细,有利于资源重复利用,有利于提高开发效率2、可以更精准的制定优化服务方案,提高系统的可维护性3、微服务架构采用去中心化思想,服务之间采用Restful等轻量级通讯,比ESB更轻量4、适于互联网时代,产品迭代周期更短缺点:1、微服务过多,治理成本高,不利于维护系统2、分布式系统开发的成本高(容错,分布式事务等)对团队挑战大总的来说优点大过于缺..._springcloud优缺点

Linux平台nginx+fastDFS插件安装_linux搭建nginx+fastdfs-程序员宅基地

文章浏览阅读396次。Linux平台nginx+fastDFS插件安装_linux搭建nginx+fastdfs

分布式基础1:分布式概念性内容简述;(什么是分布式;分布式和单体结构的对比;CAP定理;集群、分布式、微服务的区别;)_分布式入门csdn-程序员宅基地

文章浏览阅读540次,点赞5次,收藏6次。说明:(1)对分布式的基本内容,进行一个入门级介绍;(2)该篇博客参考的文章有: ● 【分布式与集群的区别是什么?】专栏中的一条回答,答主是【大闲人柴毛毛】;目录1.什么是分布式;2.分布式的作用;3.分布式和单体结构的对比;4.CAP定理;5.集群、分布式、微服务的差别;1.什么是分布式;(1)分布式这个概念是比较新的,发展时间也不长,而且分布式还正在发展,比如很多新的概念、新的技术也还在不断诞生;(2)分布式目前为止并没有有个官方..._分布式入门csdn

场景交互与场景漫游-路径漫游(7)_简述一下指定路径漫游的操作步骤-程序员宅基地

文章浏览阅读524次。osg 场景漫游 路径漫游_简述一下指定路径漫游的操作步骤

beanstalkd基础使用(C/C++语言)_beanstalkd c++-程序员宅基地

文章浏览阅读576次。保证Linux下已有beanstalkd环境,在beanstalkd目录下./beanstalkd -F运行程序1.在https://github.com/deepfryed/beanstalk-client下载相关的客户端程序 下载后解压,在解压后的目录下进行安装sudo make install在/etc/ld.so.conf目录下添加解压后的目录,如:/home/beanstalk-clie_beanstalkd c++

终于有人把 ZFS 文件系统讲明白了_zfs文件系统-程序员宅基地

文章浏览阅读2.2k次。本文主要介绍高级文件系统 ZFS,将讨论它的来源、它是什么以及为什么它在技术人员和企业中如此受欢迎。注意: 本文多次提到 ZFS ,当谈到 ZFS 功能和安装时,其实说的是 OpenZFS。自从 Oracle 停止对 OpenSolaris 更新代码之后[1],ZFS(由 Oracle 开发)和 OpenZFS 遵循了不同的路径。ZFS 的历史ZFS 文件系统在 2001 年由 Matthew Ahrens 和 Jeff Bonwick[2] 创建。ZFS 旨在成为 Sun Microsystems _zfs文件系统

推荐文章

热门文章

相关标签