微信开发本地环境推送通知_微信推送服务通知能给其他微信推送服务器 - CSDN
  • 微信公众号搭建本地测试环境

    万次阅读 2017-03-11 09:10:18
    1、由于公司的微信公众号已经在阿里云上运营,如果有任何bug不可能去停掉服务器去调试,这就需要在本地搭建测试环境了。 2、公众号开发涉及到微信的回调,所以你本地搭建的服务器需要外网能够访问,这个时候就需要把...
    网上有很多相关的文章跟帖子,大家不妨都跟着玩一下;今天我把我的经验跟坑给填一下,记录一下,以便自己查阅:
    注意:首先你得有个微信公众号。注册的事项另外查阅,不再累赘。

    原因:
    1、由于公司的微信公众号已经在阿里云上运营,如果有任何bug不可能去停掉服务器去调试,这就需要在本地搭建测试环境了。
    2、公众号开发涉及到微信的回调,所以你本地搭建的服务器需要外网能够访问,这个时候就需要把运行在内网的服务器映射到外网去给微信访问。

    方法:
    1、搭建内网穿透环境
    如果喜欢自己搭建的话可以多找些关于内网穿透的资料看看,我是由于时间的关系,当时就选择了一个工具来把自己的电脑穿透到外网,(主要是收费不是很贵)。
    在这里可以推荐一下:natapp,https://natapp.cn/,网络感觉还不错,虽然不快,但是基本能满足我的微信开发就足够了,我使用的是付费5/月的套餐,因为这样就能固定的外网地址。

    2、映射好后,要试着访问以下映射的地址,看看是否能访问到你的服务器,可以的话就说明你的映射是成功的。如图:




    3、配置公众号的测试账号:
    1)、进入到微信公众号首页左侧导航栏下有个开发者工具,点击进去:

    2)、在右边能看到如下的测试账号,点击进入

    3)、用个人微信扫一扫进去即可登录个人的测试账号,此时会有如下的东西,这个是公众号开发的时候需要用到的参数。
    注:这些最好用配置文件的形式保存,方便以后修改为正式环境的参数!

    4)、这个url是微信那边需要发个验证请求过来的,需要你配置一个你服务器的外网地址,即第一步我们将公众号服务器映射到外网的地址,(注意端口号哦)。token的话自己随机设置,并保存到配置文件中。


    5)、当你点击提交的时候,微信会立刻试着发个请求到这个地址上去,如果请求成功的话,可以看到如下图;

    url尾部的wechat,是因为我本地用了nginx路由转向,这个转向就是转到我的本地公众号服务器。(如果还没有接触nginx的话,可以先试着直接把映射的端口直接跟你公众号服务器端口设置成一样,就是如下图的本地端口。不过一般还是建议搭配nginx使用,这样就不用总是去改端口,只需要配置好你的nginx代理服务器就可以了。


    6)、微信发送认证请求到你本地服务器的代码如下,这个是node的代码,这个代码只在微信认证的时候使用,平时注释掉即可:
    // 微信url 验证token代码
    router.get('/',function function_name(req,res) {
    // body...
    var state = req.query.state;
    //console.log(req);
    var Isign = sign.checkSignature(req.query);
    console.log('sign',Isign);
    if(!Isign){
    //如果签名不对,结束请求并返回
    res.end('signature fail');
    }
    if (req.method == "GET") {
    //如果请求是GET,返回echostr用于通过服务器有效校验
    res.end(req.query.echostr);
    }
    });
    sign文件的检查签名
    var crypto = require('crypto');
    checkSignature = function (query) {
    var signature = query.signature;
    var timestamp = query.timestamp;
    var nonce = query.nonce;

    var shasum = crypto.createHash('sha1');
    var arr = [configs.token, timestamp, nonce].sort();
    shasum.update(arr.join(''));

    return shasum.digest('hex') === signature;
    };
    7)、当你到这一步的时候,相信你微信扫一扫关注的测试账号就可以给你测试使用了,真机测试如下图:


    展开全文
  • Java后台+微信小程序实现推送 “服务通知

    万次阅读 热门讨论 2019-05-10 16:27:48
    java+微信小程序发送服务通知

    微信小程序+java后台实现,小程序推送“服务通知”给用户

    成功步骤:

    1.注册微信小程序app,然后登陆微信公众平台开通“消息推送”,配置url、token等参数
    2.平台——模板消息——我的模板 申请模板,获取模板id、配置的字段名
    3.在外网80/443的接口中放一个可以验证签名的接口(详情见下文)。
    4.Java后台写发送消息的工具类
    5.小程序写一个可以获取formId、openid的页面
    6.用真机调试中获取到的参数在java工具类中main方法测试发送通知。成功!

    0、登录微信公众平台,开启小程序“消息推送”功能

    https://mp.weixin.qq.com/cgi-bin/loginpage?t=wxm2-login&lang=zh_CN

    开发——开发设置——消息推送
    在这里插入图片描述
    注意:URL填写的是:要在外网能访问的接口(需要80或者443端口才行),该接口需要包括能够验证签名的方法,具体方法如下:

    只需要把下面接口中的WECHAT_TOKEN :改成 消息推送的"Token(令牌)"值 一样即可。

    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    /**
     * 微信授权
     */
    @Controller
    @Slf4j
    @RequestMapping("api/wx")
    public class APIWxController{
    
    	// 与接口配置信息中的Token要一致
    	private static String WECHAT_TOKEN = "WECHAT_TOKEN";
    
    	@RequestMapping("/checkToken")
    	public void get(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    		log.info("========checkToken Controller========= ");
    
    		boolean isGet = request.getMethod().toLowerCase().equals("get");
    		PrintWriter print;
    		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 && checkSignature(signature, timestamp, nonce)) {
    				try {
    					print = response.getWriter();
    					print.write(echostr);
    					print.flush();
    					log.info("========checkToken success ========= ");
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}else{
    				log.error("========checkToken failed========= ");
    			}
    		}else {
    			log.error("========checkToken failed:Only support Get Method =========");
    		}
    	}
    
    	/**
    	 * 验证签名
    	 *
    	 * @param signature
    	 * @param timestamp
    	 * @param nonce
    	 * @return
    	 */
    	public static boolean checkSignature(String signature, String timestamp, String nonce) {
    		String[] arr = new String[] { WECHAT_TOKEN, timestamp, nonce };
    		// 将token、timestamp、nonce三个参数进行字典序排序
    		// Arrays.sort(arr);
    		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 = byteToStr(digest);
    		} catch (NoSuchAlgorithmException e) {
    			e.printStackTrace();
    		}
    		content = null;
    		// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
    		return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    	}
    
    	/**
    	 * 将字节数组转换为十六进制字符串
    	 *
    	 * @param byteArray
    	 * @return
    	 */
    	private static String byteToStr(byte[] byteArray) {
    		String strDigest = "";
    		for (int i = 0; i < byteArray.length; i++) {
    			strDigest += byteToHexStr(byteArray[i]);
    		}
    		return strDigest;
    	}
    
    	/**
    	 * 将字节转换为十六进制字符串
    	 *
    	 * @param mByte
    	 * @return
    	 */
    	private static String byteToHexStr(byte mByte) {
    		char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    		char[] tempArr = new char[2];
    		tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
    		tempArr[1] = Digit[mByte & 0X0F];
    		String s = new String(tempArr);
    		return s;
    	}
    	public static void sort(String a[]) {
    		for (int i = 0; i < a.length - 1; i++) {
    			for (int j = i + 1; j < a.length; j++) {
    				if (a[j].compareTo(a[i]) < 0) {
    					String temp = a[i];
    					a[i] = a[j];
    					a[j] = temp;
    				}
    			}
    		}
    	}
    
    }
    
    

    模板消息——我的模板中创建一个模板消息,获取模板ID
    在这里插入图片描述

    1、java后台创建小程序 Vo类,用于封装传送的参数。

    TemplateDataVo .java

    import lombok.Data;
    
    /*
     * 设置推送的文字和颜色
     * */
    @Data
    public class TemplateDataVo {
        //字段值例如:keyword1:订单类型,keyword2:下单金额,keyword3:配送地址,keyword4:取件地址,keyword5备注
        private String value;//依次排下去
    //    private String color;//字段颜色(微信官方已废弃,设置没有效果)
    }
    

    WxMssVo .java

    import lombok.Data;
    import java.util.Map;
    
    /*
     * 小程序推送所需数据
     * */
    @Data
    public class WxMssVo {
        private String touser;//用户openid
        private String template_id;//模版id
        private String page = "pages/index/index";//默认跳到小程序首页
        private String form_id;//收集到的用户formid
    //    private String emphasis_keyword = "keyword1.DATA";//放大那个推送字段
        private Map<String, TemplateDataVo> data;//推送文字
    }
    

    2、java后台创建发送消息推送类

    WeChatService.java
    该类包括以下主要方法:
    1、推送通知的主要方法类:
    /**
    *
    * @param access_token app的token
    * @param openid 用户openid
    * @param formId 表单ID
    * @param templateId 模板ID
    * @param keywords {与模板字段一一对应}
    * @return
    /
    public String pushOneUser(String access_token,String openid, String formId,String templateId,String[] keywords)
    2、获取app的access_token 方法
    /

    * 获取access_token
    * appid和appsecret到小程序后台获取,当然也可以让小程序开发人员给你传过来
    * */
    public String getAccess_token()

    
    import com.alibaba.fastjson.JSONObject;
    import com.weixin.demo.entity.TemplateDataVo;
    import com.weixin.demo.entity.WxMssVo;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Service;
    import org.springframework.util.StringUtils;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @Slf4j
    @Service
    public class WeChatService {
    
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
        @Autowired
        private RestTemplate restTemplate;
    
    
    
        /**
         *
         * @param access_token  app的token
         * @param openid 用户openid
         * @param formId 表单ID
         * @param templateId 模板ID
         * @param keywords {与模板字段一一对应}
         * @return
         */
        public String pushOneUser(String access_token,String openid, String formId,String templateId,String[] keywords) {
    
            //如果access_token为空则从新获取
            if(StringUtils.isEmpty(access_token)){
                access_token = getAccess_token();
            }
    
            String url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send" +
                    "?access_token=" + access_token;
    
            //拼接推送的模版
            WxMssVo wxMssVo = new WxMssVo();
            wxMssVo.setTouser(openid);//用户openid
            wxMssVo.setForm_id(formId);//formId
            wxMssVo.setTemplate_id(templateId);//模版id
            Map<String, TemplateDataVo> m = new HashMap<>();
    
            //封装数据
            if(keywords.length>0){
                for(int i=1;i<=keywords.length;i++){
                    TemplateDataVo keyword = new TemplateDataVo();
                    keyword.setValue(keywords[i-1]);
                    m.put("keyword"+i, keyword);
                }
                wxMssVo.setData(m);
            }else{
                log.error("keywords长度为空");
                return null;
            }
    
            if(restTemplate==null){
                restTemplate = new RestTemplate();
            }
    
            ResponseEntity<String> responseEntity =
                    restTemplate.postForEntity(url, wxMssVo, String.class);
            log.error("小程序推送结果={}", responseEntity.getBody());
            return responseEntity.getBody();
        }
    
        /*
         * 获取access_token
         * appid和appsecret到小程序后台获取,当然也可以让小程序开发人员给你传过来
         * */
        public String getAccess_token() {
            //获取access_token
            String appid = "wxb1abfc1724c5ee8b";
            String appsecret = "c88a5dd3c3af8228a389d778fce3e32b";
            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
                    "&appid=" + appid + "&secret=" + appsecret;
            if(restTemplate==null){
                restTemplate = new RestTemplate();
            }
            String json = restTemplate.getForObject(url, String.class);
            JSONObject myJson = JSONObject.parseObject(json);
            return myJson.get("access_token").toString();
        }
    
        public static void main(String[] args) {
            System.out.println(new WeChatService().getAccess_token());
    
            WeChatService weChatUtil = new WeChatService();
            String values[] ={"Jack方","2019-5-8 10:10:10","xxx有限公司","JAVA开发","xx区xx广场xx号","请带好入职材料"};
            weChatUtil.pushOneUser(weChatUtil.getAccess_token()
                    ,"o_fh25E0IufW7NIpezUReODfVH68","ec76b8b81cd04cf6b464bb0adf309d3b","zv0IsYDpJxgKWLHGUy8FEv0ajtJqkfhWTsFWiM7zzSU"
            ,values);
        }
    }
    
    

    3、微信小程序(小程序申请什么的就自己去操作吧)

    我们在本地的小程序开发工具构建一个这样的页面来测试下:
    为什么是这样的页面?主要是为了方便获取openid、formId
    在这里插入图片描述

    index.wxml
    只有 元素中 加上report-submit=“true” 才能获取得到formId,formId有效期为7天,一个formId只能发送一次通知,发完就不能再用了,formId是发送推送信息的必要条件。

    <view class="container">
    
    
    <form bind:submit='getOpenIdTap' report-submit="true">
          <button formType='submit'>获取用户唯一标识openid</button>  
          <view class='widget'> 
            <text class='column'>openid:{{openid}}</text>
          </view>
          <view class='widget'> 
            <text class='column'>session_key:{{session_key}}</text>
          </view>
          <view class='widget'> 
            <text class='column'>formId:{{formId}}</text>
          </view>
          
    </form>
    
    <form bind:submit="testSubmit" report-submit="true">
          <button formType="submit">发送模板消息</button>
          <view class='widget'> 
            <text class='column'>errcode:{{errcode}}</text>
          </view>
           <view class='widget'> 
            <text class='column'>errmsg:{{errmsg}}</text>
          </view>
     </form>
    
     <view class='moto-container' bindtap='bindViewTap'> 
        <text class='moto'>获取列表</text>
     </view>
    </view>
    

    index.js
    将index.js中的下面几个参数换成你的即可
    const APP_ID = 'APP_ID ';//输入小程序appid
    const APP_SECRET = 'APP_SECRET ';//输入小程序app_secret

    //index.js
    //获取应用实例
    const app = getApp()
    const APP_ID = 'APP_ID ';//输入小程序appid  
    const APP_SECRET = 'APP_SECRET ';//输入小程序app_secret  
    var OPEN_ID = ''//储存获取到openid  
    var SESSION_KEY = ''//储存获取到session_k
    var FORM_ID = ''//储存获取到的formId
    
    Page({
      data: {
        motto: 'Hello World',
        userInfo: {},
        hasUserInfo: false,
        canIUse: wx.canIUse('button.open-type.getUserInfo')
      },
      //事件处理函数
      bindViewTap: function () {
        wx.navigateTo({
          url: '../list/list'
        })
      },
      onLoad: function () {
        if (app.globalData.userInfo) {
          this.setData({
            userInfo: app.globalData.userInfo,
            hasUserInfo: true
          })
        } else if (this.data.canIUse) {
          // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
          // 所以此处加入 callback 以防止这种情况
          app.userInfoReadyCallback = res => {
            this.setData({
              userInfo: res.userInfo,
              hasUserInfo: true
            })
          }
        } else {
          // 在没有 open-type=getUserInfo 版本的兼容处理
          wx.getUserInfo({
            success: res => {
              app.globalData.userInfo = res.userInfo
              this.setData({
                userInfo: res.userInfo,
                hasUserInfo: true
              })
            }
          })
        }
      },
      getUserInfo: function (e) {
        console.log(e)
        app.globalData.userInfo = e.detail.userInfo
        this.setData({
          userInfo: e.detail.userInfo,
          hasUserInfo: true
        })
      },
      getOpenIdTap: function (e) {
        var that = this;
        FORM_ID = e.detail.formId;//获取到formId 
        console.log("formId1:"+FORM_ID)
        that.setData({
          formId: FORM_ID
        })
        wx.login({
          success: function (res) {
            wx.request({
              //获取openid接口  
              url: 'https://api.weixin.qq.com/sns/jscode2session',
              data: {
                appid: APP_ID,
                secret: APP_SECRET,
                js_code: res.code,
                grant_type: 'authorization_code'
              },
              method: 'GET',
              success: function (res) {
                OPEN_ID = res.data.openid;//获取到的openid  
                SESSION_KEY = res.data.session_key;//获取到session_key 
                console.log("openid:" + OPEN_ID)
                console.log("session_key:" + SESSION_KEY) 
                that.setData({
                  openid: OPEN_ID,
                  session_key: SESSION_KEY
                })
              }
            })
          }
        })
      },
      testSubmit: function (e) {
        var that = this;
        wx.request({
          url: 'http://127.0.0.1:80/pushMsg',
          method: 'POST',
          data: {
            access_token:null,
            openid:OPEN_ID,
            formid:FORM_ID
          },
          success: function (res) {
            that.setData({
              errcode: res.data.errcode,
              errmsg: res.data.errmsg
            })
            console.log(res)
          },
          fail: function (err) {
            console.log('request fail ', err);
          },
          complete: function (res) {
            console.log("request completed!");
          }
    
        })
      }
    })
    

    运行小程序,点击获取openid、formId

    如果出现如下图所示:formId is a mock one。
    说明需要我们用真机测试才能获取formId
    在这里插入图片描述
    点击“真机调试”,用手机扫描二维码
    在这里插入图片描述
    拿到我们需要的openId、formId
    在这里插入图片描述

    回到java后台WeChatService.java

    用main方法测试一下。推送成功!
    在这里插入图片描述
    看下手机端展示:
    在这里插入图片描述
    在这里插入图片描述

    为什么不直接在 小程序中直接发送“模板消息”?
    答:主要是因为真机测试时,手机不能调用本地的接口,所以不好测试,我们用真机测试获取formId,在放在java后台,模拟调用接口测试
    在这里插入图片描述

    展开全文
  • 微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。其中,某些事件推送在发生后,是允许开发者回复...

    1、概述

    在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。其中,某些事件推送在发生后,是允许开发者回复用户的,某些则不允许

    我们在上一篇微信公众号开发C#系列-6、消息管理-普通消息接受处理中讲到,微信的消息可以大体分为两种类型,一种是包括:文本,语音,图片等的普通消息,另一种就是本篇要将的事件类型。包括:关注/取消关注事件,扫描带参数二维码事件,上报地理位置事件,自定义菜单相关事件等,本篇一一进行讲解。介于偏于内容过多易产生阅读疲劳,对于自定义菜单相关事件的处理我们放在下一篇中讲解。

    这里的消息指的是传统的微信公众平台消息交互,微信用户向公众号发送消息后,公众号回复消息给微信用户。包括以下类型:

    1. 关注/取消关注事件:subscribe/unsubscribe
    2. 扫描带参数二维码事件:scan
    3. 上报地理位置事件:location
    4. 自定义菜单事件
    5. 点击菜单拉取消息时的事件推送
    6. 点击菜单跳转链接时的事件推送

    本篇主要介绍前三种。

    2、实现方式

    使用Senparc.Weixin框架来快速处理各种接收事件推送,实现非常简单,自定义一个继承MessageHandler的类,重写这些类型的方法即可。注意:DefaultResponseMessage必须重写,用于返回没有处理过的消息类型(也可以用于默认消息,如帮助信息等);其中所有原OnXX的抽象方法已经都改为虚方法,可以不必每个都重写。若不重写,默认返回DefaultResponseMessage方法中的结果。

    自定义消息处理类:

    public partial class CustomMessageHandler : MessageHandler<MessageContext<IRequestMessageBase, IResponseMessageBase>>
    {
        public CustomMessageHandler(Stream inputStream, int maxRecordCount = 0)
            : base(inputStream, null, maxRecordCount)
        {
            WeixinContext.ExpireMinutes = 3;
        }
        public override void OnExecuting()
        {
            //测试MessageContext.StorageData
            if (CurrentMessageContext.StorageData == null)
            {
                CurrentMessageContext.StorageData = 0;
            }
            base.OnExecuting();
        }
        public override void OnExecuted()
        {
            base.OnExecuted();
            CurrentMessageContext.StorageData = ((int)CurrentMessageContext.StorageData) + 1;
        }
    }
    

    定义好事件处理类后,分别重写上面提到几种接收事件推送的事件即可。
    我们可以通过重写MessageHandler里的这几种类型方法来处理我们的业务,当然也可以只重写需要的部分类型,不需要的类型可以不重写,只需要定义一个统一的DefaultResponseMessage

    public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
    {
        //所有没有被处理的消息会默认返回这里的结果
        var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
        responseMessage.Content = "这条消息来自DefaultResponseMessage。";
        return responseMessage;
    }
    

    3、消息的去重的重要性

    上一篇我们就已经提到过微信服务器在5秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。如此以来,我们模拟有这样一个场景:当用户关注微信账号时,获取当前用户信息,然后将信息写到数据库中,类似网站的注册。假设这个关注事件中,我们需要处理比较复杂的业务逻辑。如送积分,写用户日志,分配用户组等等一系列的逻辑需要执行,或者网络环境比较复杂,无法保证5秒内响应当前用户的操作,那如果当操作尚未完成,微信服务器又给我们的服务器推送了一条相同的关注事件,我们将再次执行我们的那些逻辑,这样就有可能导致数据库中出现重复的数据(有的童鞋就会说了,我在插入数据之前先判断当前是否已经存在了,如果存在了就不执行插入的操作。我想说的是,我当初也是这样想的,但真实的运行环境和我们的调试环境还是有差距的,直到发现数据库中有不少重复的用户信息时,我才发现消息去重的重要性。)。

    消息的去重普通消息和事件消息是有区别的。普通消息使用msgid,而事件消息使用FromUserName + CreateTime。

    4、关注/取消关注事件

    用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。方便开发者给用户下发欢迎消息或者做帐号的解绑。

    假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。

    关注或取消事件推送XML数据包示例:

    <xml>
      <ToUserName><![CDATA[toUser]]></ToUserName>
      <FromUserName><![CDATA[FromUser]]></FromUserName>
      <CreateTime>123456789</CreateTime>
      <MsgType><![CDATA[event]]></MsgType>
      <Event><![CDATA[subscribe]]></Event>
    </xml>
    

    参数说明:

    参数				描述
    ToUserName		开发者微信号
    FromUserName	发送方帐号(一个OpenID)
    CreateTime		消息创建时间 (整型)
    MsgType			消息类型,event
    Event			事件类型,subscribe(订阅)、unsubscribe(取消订阅)
    

    4.1 关注事件

    关注事件我们只需要重写OnEvent_SubscribeRequest事件代码即可,如下我们返回了一个文本消息,实现代码参考:

    /// <summary>
    /// 订阅(关注)事件
    /// </summary>
    /// <returns></returns>
    public override IResponseMessageBase OnEvent_SubscribeRequest(RequestMessageEvent_Subscribe requestMessage)
    {
    	var responseMessage = CreateResponseMessage<ResponseMessageNews>();
        foreach (var model in messageList)
        {
            responseMessage.Articles.Add(new Article()
            {
                Title = "国思公众号",
                Description = "欢迎关注国思软件公众号,更多内容稳步官网,多谢!",
                PicUrl = "http://www.rdiframework.net/WeiXin.png",
                Url = "http://www.rdiframework.net/"
            });
        }
        return responseMessage;
    }
    

    关注事件执行后效果

    在上面的关注事件中,用户关注公众号就会自动执行上面的事件代码,我们就可以在事件代码中做相关的业务处理,如绑定用户分组、增加用户到本地等等。同时推送一条欢迎消息返回到用户手机上。

    4.2 取消关注事件

    取消关注事件与关注事件类似,主要是事件变成了unsubscribe(取消关注)。取消关注事件-unsubscribe的主要意义在于及时删除网站应用中已经记录的OpenID绑定,消除冗余数据,并且关注用户流失的情况。

    取消关注事件我们只需要重写OnEvent_UnsubscribeRequest事件代码即可,如下我们返回了一个文本消息,实现代码参考:

    /// <summary>
    /// 退订/取消关注
    /// 实际上用户无法收到非订阅账号的消息,所以这里可以随便写。
    /// unsubscribe事件的意义在于及时删除网站应用中已经记录的OpenID绑定,消除冗余数据。并且关注用户流失的情况。
    /// </summary>
    /// <returns></returns>
    public override IResponseMessageBase OnEvent_UnsubscribeRequest(RequestMessageEvent_Unsubscribe requestMessage)
    {
        int returnValue = RDIFrameworkService.Instance.WeixinBasicService.UserUnsubscribeByOpenId(Id,requestMessage.FromUserName);//退
        var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
        responseMessage.Content = "有空再来";
        return responseMessage;
    }
    

    上面的代码在用户取消公众号的关注时就会自动执行,可以看到我们有一行代码针对用户取消关注时执行的业务逻辑,同时返回了一个文本消息。实际用户已经取消关注,返回的消息也返回不到用户手机上的。

    5、扫描带参数二维码事件

    用户扫描带场景值二维码时,可能推送以下两种事件:

    1. 如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
    2. 如果用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者。

    5.1 接口展示与实现方式

    对于第一种上面已经讲了,这里就只说明下第二种。

    推送XML数据包示例:

    <xml>
      <ToUserName><![CDATA[toUser]]></ToUserName>
      <FromUserName><![CDATA[FromUser]]></FromUserName>
      <CreateTime>123456789</CreateTime>
      <MsgType><![CDATA[event]]></MsgType>
      <Event><![CDATA[SCAN]]></Event>
      <EventKey><![CDATA[SCENE_VALUE]]></EventKey>
      <Ticket><![CDATA[TICKET]]></Ticket>
    </xml>
    

    参数说明:

    参数				描述
    ToUserName		开发者微信号
    FromUserName	发送方帐号(一个OpenID)
    CreateTime		消息创建时间 (整型)
    MsgType			消息类型,event
    Event			事件类型,SCAN
    EventKey		事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id
    Ticket			二维码的ticket,可用来换取二维码图片
    

    对于生成带参数的二维码我们会在后面的文章中专门介绍,这儿我们了解一个这个概念。为了满足用户渠道推广分析和用户帐号绑定等场景的需要,公众平台提供了生成带参数二维码的接口。使用该接口可以获得多个带不同场景值的二维码,用户扫描后,公众号可以接收到事件推送。具体官方技术文档可参考:生成带参数的二维码

    目前有2种类型的二维码:

    1、临时二维码,是有过期时间的,最长可以设置为在二维码生成后的30天(即2592000秒)后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景

    2、永久二维码,是无过期时间的,但数量较少(目前为最多10万个)。永久二维码主要用于适用于帐号绑定、用户来源统计等场景。

    扫描带参数二维码事件只需要重写OnEvent_ScanRequest事件代码即可,如下我们返回了一个文本消息,实现代码参考:

    public override IResponseMessageBase OnEvent_ScanRequest(RequestMessageEvent_Scan requestMessage)
    {
        //通过扫描关注
        var responseMessage = CreateResponseMessage<ResponseMessageText>();
    
        responseMessage.Content = responseMessage.Content ?? string.Format("欢迎关注国思软件,通过扫描二维码进入,场景值:{0}", requestMessage.EventKey);
    
        return responseMessage;
    }
    

    在上面的代码中用户扫描了带场景值的二维码进入公众号后我们返回了一个提示的文本消息。这是非常有用的功能,常用途推广,可以根据不同的二维码场景值分别做不同的业务处理,如可以统计关注的每一个粉丝从哪里来的,做到渠道推广分析,但是关注的都是同一个公众号。

    5.2 生成带参数的二维码用途

    微信公众号生成带参数的二维码有何用途?

    1. 可以区分粉丝来源,只需要生成不同的带参数的二维码,把这些二维码分别投放到各个渠道,粉丝通过这些渠道二维码进来就可以区分粉丝来源,微号帮后台渠道粉丝列表中有粉丝数及明细;
    2. 粉丝通过扫描渠道二维码关注公众号,会打标签分组,比如粉丝扫商店A、B的二维码进来的, 在微信公众号后来的用户管理中可查看到商店A/B二维码名下的粉丝明细及分组情况;
    3. 可以生成多个不同的渠道二维码配置不同的营销活动,设置不同的关注回复信息,让粉丝第一时间了解活动动机,是否有兴趣参与等等;
    4. 可以利用渠道二维码生成功能,可以实现微信收款前关注公众号,间接分析粉丝后续消费情况;
    5. 考核推广员完成任务的进度,如以推广名字生成多不个同的二维码,分配给不同的推广员,每个推广员吸引了多少粉丝关注公众号,微号帮后台都可以一一明细;
    6. 带参数的二维码也叫渠道二维码或者场景二维码,生存的数量有限,且是永久二维码。当数量用完后可以删除一些不用的二维码释放出来,二次利用。
      通过扫描带场景值的二维码进入

    6、上报地理位置事件

    用户同意上报地理位置后,每次进入公众号会话时,都会在进入时上报地理位置,或在进入会话后每5秒上报一次地理位置,公众号可以在公众平台网站中修改以上设置。上报地理位置时,微信会将上报地理位置事件推送到开发者填写的URL。要获取用户地址位置,需要在微信公众平台开发者中心开启上报地理位置功能,开启之后会在用户首次进入公众号时,弹出是否允许上报地理位置选项,如果选择允许则在用户每次进入公众号会话的时候微信会以XML形式将用户的地理位置上报到你开发者中心填写的URL上。

    **注意:**用户地理位置是被动获取的,需用户同意后才会上报,微信公众平台开发不能主动获取用户地理位置。

    推送XML数据包示例:

    <xml>
      <ToUserName><![CDATA[toUser]]></ToUserName>
      <FromUserName><![CDATA[fromUser]]></FromUserName>
      <CreateTime>123456789</CreateTime>
      <MsgType><![CDATA[event]]></MsgType>
      <Event><![CDATA[LOCATION]]></Event>
      <Latitude>23.137466</Latitude>
      <Longitude>113.352425</Longitude>
      <Precision>119.385040</Precision>
    </xml>
    

    参数说明:

    参数				描述
    ToUserName		开发者微信号
    FromUserName	发送方帐号(一个OpenID)
    CreateTime		消息创建时间 (整型)
    MsgType			消息类型,event
    Event			事件类型,LOCATION
    Latitude		地理位置纬度
    Longitude		地理位置经度
    Precision		地理位置精度
    

    上报地理位置事件只需要重写OnEvent_LocationRequest事件代码即可,如下我们返回了一个文本消息,实现代码参考:

    public override IResponseMessageBase OnEvent_LocationRequest(RequestMessageEvent_Location requestMessage)
    {
        //这里是微信客户端(通过微信服务器)自动发送过来的位置信息
        var responseMessage = CreateResponseMessage<ResponseMessageText>();
        responseMessage.Content = "这里写什么都无所谓,比如:上帝爱你!";
        return responseMessage;//这里也可以返回null(需要注意写日志时候null的问题)
    }
    

    上报地理位置用处非常多,可以用维度和经度获取城市代号,调用天气Api,也可以用来监测企业员工的位置进行微信考勤。在微信运营的时候,用户地理位置还是我们进行营销策划、广告活动投放、用户精准营销的重要依据。

    参考文章

    微信公众平台技术文档-官方

    Senparc.Weixin SDK + 官网示例源代码

    RDIFramework.NET — 基于.NET的快速信息化系统开发框架 — 系列目录

    RDIFramework.NET ━ .NET快速信息化系统开发框架 ━ 工作流程组件介绍

    RDIFramework.NET框架SOA解决方案(集Windows服务、WinForm形式与IIS形式发布)-分布式应用

    RDIFramework.NET代码生成器全新V3.5版本发布-重大升级


    一路走来数个年头,感谢RDIFramework.NET框架的支持者与使用者,大家可以通过下面的地址了解详情。

    RDIFramework.NET官方网站:http://www.rdiframework.net/

    RDIFramework.NET官方博客:http://blog.rdiframework.net/

    同时需要说明的,以后的所有技术文章以官方网站为准,欢迎大家收藏!

    RDIFramework.NET框架由专业团队长期打造、一直在更新、一直在升级,请放心使用!

    欢迎关注RDIFramework.net框架官方公众微信(微信号:guosisoft),及时了解最新动态。

    扫描二维码立即关注

    微信号:guosisoft

    展开全文
  • 1.打开微信测试号申请平台 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 2.点击“登录”按钮,用微信扫码确认登录 3.确认登录后,测试号申请成功! 第二步:测试号管理 1.测试号信息 这里的...

    第一步:申请测试号

    1.打开微信测试号申请平台 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
    这里写图片描述
    2.点击“登录”按钮,用微信扫码确认登录

    3.确认登录后,测试号申请成功!

    第二步:测试号管理

    1.测试号信息
    这里的appID、appsecret在后面很多接口调用中都需要用到
    这里写图片描述

    2.接口配置信息(这里需要有自己的服务器和映射成功的域名)
    (本人用的是本地node服务器,用花生壳把本机的8080端口映射到了花生壳提供的二级免费域名的80端口)
    这里写图片描述
    url填写自己的域名,token可以随意填

    填写完后先不要提交,接下来编写nodejs服务端代码响应微信的提交请求,响应Token验证结果

    server.js
    *注意:以下用到的模块大部分需要先安装,安装步骤请在本人其他的博文中查看或自行百度安装

    const express = require('express'); //web服务框架模块
    const request = require('request'); //http请求模块
    const fs = require('fs'); //文件系统模块
    const path = require('path'); //文件路径模块
    const sha1 = require('node-sha1'); //加密模块
    const urlencode= require('urlencode'); //URL编译模块
    
    
    const hostName = '127.0.0.1'; //ip或域名
    const port = 8080; //端口
    
    
    /**
     * [开启跨域便于接口访问]
     */
    app.all('*', function(req, res, next) {
        res.header('Access-Control-Allow-Origin', '*'); //访问控制允许来源:所有
        res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); //访问控制允许报头 X-Requested-With: xhr请求
        res.header('Access-Control-Allow-Metheds', 'PUT, POST, GET, DELETE, OPTIONS'); //访问控制允许方法
        res.header('X-Powered-By', 'nodejs'); //自定义头信息,表示服务端用nodejs
        res.header('Content-Type', 'application/json;charset=utf-8');
        next();
    });
    
    
    /**
     * [设置验证微信接口配置参数]
     */
    const config = {
    	token: 'test', //对应测试号接口配置信息里填的token
    	appid: 'xxxxxxxxxxxxxx', //对应测试号信息里的appID
    	secret: 'xxxxxxxxxxxxxxxxxxxx', //对应测试号信息里的appsecret
    	grant_type: 'client_credential' //默认
    };
    
    
    /**
     * [验证微信接口配置信息,]
     */
    app.get('/', function(req, res) {
    
    	const token = config.token; //获取配置的token
    	const signature = req.query.signature; //获取微信发送请求参数signature
    	const nonce = req.query.nonce; //获取微信发送请求参数nonce
    	const timestamp = req.query.timestamp; //获取微信发送请求参数timestamp
    	
    	const str = [token, timestamp, nonce].sort().join(''); //排序token、timestamp、nonce后转换为组合字符串
    	const sha = sha1(str); //加密组合字符串
    
    	//如果加密组合结果等于微信的请求参数signature,验证通过
    	if (sha === signature) {
    		const echostr = req.query.echostr; //获取微信请求参数echostr
            res.send(echostr + ''); //正常返回请求参数echostr
    	} else {
    		res.send('验证失败');
    	}
    });
    
    
    app.listen(port, hostName, function() {
        console.log(`服务器运行在http://${hostName}:${port}`);
    });
    

    运行server.js启动node服务器,保证花生壳成功映射本地服务器至域名,外网可通过域名正常访问本地服务器

    然后点击提交,提示“配置成功”表示接口配置完成
    这里写图片描述

    3.模板消息接口
    新增测试模板,填写标题和内容
    这里写图片描述

    这里写图片描述

    4.OAuth2.0网页授权
    在网页服务 > 网页帐号 > 网页授权获取用户基本信息 > 修改
    这里写图片描述

    回调域名的填写自己的域名(注意,域名的格式不包含http://)
    这里写图片描述

    5.创建微信网页授权接口链接

    在node服务器新增授权调用接口,在微信中访问这个接口地址会创建一个可以跳转到授权页面的链接

    这步操作是为了使回调的接口得到code参数,因为发送模板消息必须要有用户的openid,而获取用户的openid必须要有code参数,code参数需要用户访问指定的链接触发授权后才会产生

    继续在server.js的基础上增加

    /**
     * [创建请求微信网页授权接口链接]
     */
     
    app.get('/authentication', function(req, res) {
    
        const appid = config.appid;
        const redirect_uri = urlencode("http://www.xxx.net/code"); //这里的url需要转为加密格式,它的作用是访问微信网页鉴权接口成功后微信会回调这个地址,并把code参数带在回调地址中
        const scope = 'snsapi_userinfo';
        const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=STATE&connect_redirect=1#wechat_redirect`;
    
    	const html =
        `<!DOCTYPE html>
        <html>
            <head>
            <meta charset="utf-8" >
            <title>微信鉴权引导</title>
            </head>
            <body><a href="${url}">跳转到鉴权页面</a></body>
        </html>`;
    	
    	res.setHeader('Content-Type', 'text/html');
        res.send(html);
    });
    

    *注意:每次修改server.js文件后需要重启node服务代码才会生效

    6.下载安装微信开发者工具调试
    安装成功后登录开发者工具,访问刚才创建的接口地址 http://www.xxx.net/authentication,
    可以看到鉴权的链接已经创建成功,但目前还没有写好获取code的接口,所以先不要点击
    这里写图片描述

    7.获取到code > 获取openid > 获取access_token > 发送模板消息
    继续在server.js的基础上增加

    /**
     * 网页授权回调接口,可以获取code
     */
    
    app.get('/code', function(req, res) {
    
        const code = req.query.code; //微信回调这个接口后会把code参数带过来
    	getOpenId(code); //把code传入getOpenId方法
        
    });
    
    
    /**
     * 获取openid
     * @param  { string } code [调用获取openid的接口需要code参数]
     */
    function getOpenId(code) {
    	const appid = config.appid;
        const secret = config.secret;
    
        const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`;
    
        request(url, function(error, response, body) {
    
            if (!error && response.statusCode == 200) {
    	       const openid =  body.openid;
    		   getAccessToken(openid);   //获取openid成功后调用getAccessToken
            }
    
        });
    }
    
    
    /**
     * 获取access_token
     *  @param  { string } openid [发送模板消息的接口需要用到openid参数]
     */
    function getAccessToken(openid) {
    	const appid = config.appid;
        const secret = config.secret;
        const grant_type = config.grant_type;
    
        const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=${grant_type}&appid=${appid}&secret=${secret}`;
    
        request(url, function(error, response, body) {
    
            if (!error && response.statusCode == 200) {
    
                const access_token= JSON.parse(body).access_token;
                sendTemplateMsg(openid, access_token); //获取access_token成功后调用发送模板消息的方法
      
            } else {
                throw 'update access_token error';
            }
        });
    	
    
    }
    
    
    /**
     * 发送模板消息
     * @param  { string } openid [发送模板消息的接口需要用到openid参数]
     * @param  { string } access_token [发送模板消息的接口需要用到access_token参数]
     */
    
    function sendTemplateMsg(openid, access_token) {
    
            const url = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${access_token}`; //发送模板消息的接口
            
            const requestData = { //发送模板消息的数据
                touser: openid,
                template_id: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
                url: 'http://weixin.qq.com/download',
                data: {
                    first: {
                        value: '身份信息',
                        color: "#173177"
                    },
                    keyword1: {
                        value: '张三',
                        color: '#1d1d1d'
                    },
                    keyword2: {
                        value: '男',
                        color: '#1d1d1d'
                    },
                    keyword3: {
                        value: '45',
                        color: '#1d1d1d'
                    },
                    remark: {
                        value: '已登记!',
                        color: '#173177'
                    }
                }
            };
    
            request({
                url: url,
                method: 'post',
                body: requestData,
            }, function(error, response, body) {
                if (!error && response.statusCode == 200) {
                    console.log('模板消息推送成功'); 
                }
            });
        }
    
    

    接口依赖关系图

    这里写图片描述

    微信网页授权文档
    https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

    获取access_token文档
    https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183

    发送模板文档
    https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277

    展开全文
  • 对于微信开发而言,它和普通的web项目其实并没什么区别。微信项目也是web项目的一种,只不过它是在微信的内置的QQ浏览器里运行,更多的在手机端运行。微信本地开发,需要注意的就是调试与测试,因为页面要做手机端...
  • 因小程序是被动触发推送消息,所以要做成推送到该用户的微信公众号上 代码推送模板消息则是通过用户的openId来进行推送,但是小程序和微信公众号的openId不同,所以只能另找办法。 在微信的官网上查询到,绑定在同一...
  • import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger;...import...
  • ——本地推送通知推送通知的作用?在App退到后台或者完全退出时,可以使用通知来告诉用户某件事情,比如推送新的聊天消息、新闻等通知对应的效果: 在主屏幕的顶端会出现通知消息 当手机锁屏时出现在锁屏界面,可以...
  • 前面写过一篇云开发实现小程序订阅消息(模板消息)推送的文章,《借助云开发实现小程序订阅消息和模板消息的推送功能》是有好多同学用的是Java写后台,所以今天就再来写一篇Java后台实现小程序订阅消息推送的文章。...
  • 如何在本地进行微信公众号的开发和调试
  • 4.给绑定小程序而且又关注微信公众号的用户推送公众号消息。 小程序消息推送机制有两种一种是统一模板消息推送一种是客服消息,本章主要介绍在Java端向指定用户推送指定模板的消息。 模板推送又分为两种一种是用小...
  • 微信打开本地app

    万次阅读 2017-09-20 17:57:44
    微信打开本地APP最近需要实现一个需求:本地智能设备检测到事件后推送给服务器。
  • 推送通知本地推送和远程推送通知。 ——— 推送流程 对于远程推送通知,我们是基于APNs与苹果设备在联网时进行长连接。开发人员需要将设备的UDID和和应用的唯一标识AppID传给APNs服务器从而换取设备的...
  • 微信小程序已经是家喻户晓了,最近和同学一起刚上线了一款应用校园懒人邦,感兴趣的朋友可以搜索一下,一款基于校园最后一百米的概念开发的快递&外卖配送平台,我是负责后台开发部分,这里给朋友们介绍下相关开发...
  • Android实现本地推送通知的解决方案

    千次阅读 2018-11-13 14:57:46
    Android实现本地推送通知的解决方案
  • 说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家...在这里可以通过微信渠道将品牌推广给上亿的微信用户,减少宣传成本,提高品牌知名度,打造更具影响力的品牌形象。 3. 公众平台 微信公众平...
  • iOS开发 - ANPs推送通知

    千次阅读 2015-05-03 14:12:46
    推送通知注意:这里说的推送...本地推送通知(Local Notification) 远程推送通知(Remote Notification)推送通知的呈现效果总结总结一下,推送通知有5种不同的呈现效果 在屏幕顶部显示一块横幅(显示具体内容) 在
  • 什么是推送通知推送通知是由应用程序和网站传输的简短信息性消息,可以随时传达给受众。 与当访客在网站或浏览器上时显示的弹出窗口不同,即使访客不在网站上,也会显示推送通知推送通知使您可以向启用了...
  • 此功能分两部分,第一部分定时器每天定点推送一条URL+图片+文字,第二部分点击URL进入页面,能看到生日祝福文字、背景图片、音乐。 第一部分定时器每天定点推送一条URL+图片+文字 ApplicationContext-elasticJob....
  • IOS开发之实现App消息推送(最新)

    万次阅读 多人点赞 2016-06-20 14:45:52
    今天就由本菜鸟给大家做一个简单的IOSApp消息推送教程吧!一切从0开始,包括XCode6, IOS8, 以及苹果开发者中心最新如何注册应用,申请证书以及下载配置概要文件,相信很多刚开始接触ios的人会很想了解一下。(ps:...
1 2 3 4 5 ... 20
收藏数 4,926
精华内容 1,970
关键字:

微信开发本地环境推送通知