微信ウィジェット:テンプレートメッセージプッシュ実装

14866 ワード

テンプレートメッセージは、マイクロ信に基づく通知チャネルであり、開発者に効率的にユーザーに触れることができるテンプレートメッセージ能力を提供し、サービスの閉ループを実現し、より良い体験を提供する.
テンプレートメッセージをプッシュするには、いくつかの前提条件を満たす必要があります.
  • ユーザがウィジェットで支払いを完了すると、ウィジェットはユーザにテンプレートメッセージを送信することができる.
  • ユーザは、テンプレートメッセージをユーザに送信することができるウィジェット内でフォームをコミットする動作を有する.

  • 例:
  • ユーザーはウィジェットで商品を購入し、ウィジェットは商品の物流状況をリアルタイムでユーザーに送信することができる.
  • ユーザーがウィジェットに活動申込書を記入した後、ウィジェットは申込状況(成功または失敗)をユーザーにプッシュすることができる.
  • なお、条件が成立しても、ウィジェットはテンプレートメッセージを無制限に送信することはできない.
    具体的な送信数の制限は次のとおりです.
  • ユーザは1回の支払いを完了し、ウィジェットはテンプレートメッセージを3回送信する機会を得ることができる.
  • ユーザはフォームを1回発行し、ウィジェットはテンプレートメッセージを1回送信する機会を得ることができる.
  • テンプレートメッセージを送信する機会は、ユーザが操作を完了してから7日以内に有効である.7日を超えると、これらの送信資格は自動的に失効します.

  • 前準備作業
    内部ネットワーク貫通(80ポート、バインド SSL をサポートする必要がある)は、開発時にバックエンドインタフェースをデバッグするために使用されます.
    このツールは、ソースコードですでに提供されています.
    ウィジェットアカウントを登録し、 に対応するテンプレートメッセージを申請し、テンプレートIDとテンプレート構造の予備を取得します.
    https://mp.weixin.qq.com/wxop...
    テンプレート・メッセージ・フォーマットは、独自にカスタマイズできますが、最終的には微信の監査が必要になります.ここでテストすると、テンプレート・ライブラリから任意に選択し、最終的にテンプレート・メッセージ・フォーマットは次のようになります.
         {{keyword1.DATA}}
         {{keyword2.DATA}}
         {{keyword3.DATA}}
         {{keyword4.DATA}}

    信頼できるサーバドメイン名の設定
    ここの信頼できるドメイン名は、最終的にはイントラネットがマッピングしたドメイン名であり、ウィジェットがローカルバックエンドインタフェースにHTTP要求を送信するために使用されます.
    関連する微信API
    アクセスポイントの取得[GET]
    https://api.weixin.qq.com/cgi...
    パラメータ
    必要かどうか
    説明
    grant_type
    はい
    アクセスの取得tokenクライアントに記入_credential
    appid
    はい
    サードパーティユーザー固有の資格証明
    secret
    はい
    サードパーティユーザー固有の証明鍵、すなわちappsecret
    通常、微信は次のJSONパケットを公衆番号に返します.
    {"access_token":"ACCESS_TOKEN","expires_in":7200}

    ログイン証明書検証:js_によるcodeは現在のユーザのopenIdを取り替える[GET]
    まず、現在のユーザのjs_をウィジェットで取得します.code,再呼び出し関連インタフェースインタフェースopenIdwx.login(OBJECT)
    インタフェースwxを呼び出す.login()一時ログイン証明書の取得(js_code)
    wx.login({
      success: function(res) {
        if (res.code) {
          //    js_code,          openId
        } else {
          console.log('    !' + res.errMsg)
        }
      }
    });

    https://api.weixin.qq.com/sns...{}&secret={}&js_code={}&grant_type=authorization_code
    パラメータ
    必要かどうか
    説明
    appid
    はい
    アプレット固有ID
    secret
    はい
    ウィジェットのapp secret
    js_code
    はい
    ログイン時に取得したコード
    grant_type
    はい
    authorizationとして記入code
    //     JSON   
    {
        "openid": "OPENID",
        "session_key": "SESSIONKEY",
    }
    
    //  UnionID     ,   JSON   
    {
        "openid": "OPENID",
        "session_key": "SESSIONKEY",
        "unionid": "UNIONID"
    }
    //     JSON   (   Code  )
    {
        "errcode": 40029,
        "errmsg": "invalid code"
    }

    テンプレートメッセージの送信[POST]
    https://api.weixin.qq.com/cgi...
    パラメータ
    必要かどうか
    説明
    touser
    はい
    受信者(ユーザ)のopenid
    template_id
    はい
    必要なテンプレートメッセージのid
    page
    いいえ
    テンプレートカードをクリックした後のジャンプページは、このウィジェット内のページのみです.パラメータ付き(例index?foo=bar)をサポートします.このフィールドに記入しないとテンプレートはジャンプしません.
    form_id
    はい
    フォームコミットシーンでは、submitイベントバンドのformIdです.支払いシーンでは、今回の支払いのprepay_id
    data
    はい
    テンプレートの内容、記入しないと空のテンプレートを発行します
    emphasis_keyword
    いいえ
    テンプレートは拡大するキーワードが必要で、記入しないとデフォルトは拡大しません
    要求の例:
    {
      "touser": "OPENID",
      "template_id": "TEMPLATE_ID",
      "page": "index",
      "form_id": "FORMID",
      "data": {
          "keyword1": {
              "value": "339208499"
          },
          "keyword2": {
              "value": "2015 01 05  12:30"
          },
          "keyword3": {
              "value": "       "
          } ,
          "keyword4": {
              "value": "         208 "
          }
      },
      "emphasis_keyword": "keyword1.DATA"
    }

    コード実装
    注意:次のコードはテストコードであり、厳格性は考慮されず、実現機能のみです.
    しょうプログラムたんまつ
    
    
      
        
        
          
          {{userInfo.nickName}}
          {{openId}}
        
      
      
        

    {{logMessage}}


    ここで、フォームにはreport-submit="true"のプロパティが追加され、テンプレートメッセージをプッシュするために使用できるformIdの機会が得られることを示すプロパティを識別する必要があります.以下はコントローラ関連のコードです.
    //index.js
    //      
    const app = getApp();
    const requestHost = "https://wuwz.guyubao.com/wx_small_app";
    
    Page({
      data: {
        userInfo: {},
        openId: null,
        hasUserInfo: false,
        hasOpenId: false,
        logMessage: null
      },
      getUserInfo: function(e) {
        app.globalData.userInfo = e.detail.userInfo
        this.setData({
          userInfo: e.detail.userInfo,
          hasUserInfo: true,
          logMessage: '       ..'
        })
        this.getOpenId();
      },
      getOpenId: function() {
        var _this = this;
        wx.login({
          success: function(res) {
            if (res.code) {
              //   openid
              wx.request({
                url: requestHost + "/get_openid_by_js_code",
                data: {
                  js_code: res.code
                },
                method: 'GET',
                success: function(res) {
                  if (res.data.openid) {
                    _this.setData({
                      openId: res.data.openid,
                      hasOpenId: true,
                      logMessage: '        '
                    });
                  }
                },
                fail: function (err) {
                  _this.setData({
                    logMessage: '[fail]' + JSON.stringify(err)
                  });
                }
              });
            }
          }
        })
      },
      templateSend: function(e) {
        var _this = this;
        var openId = _this.data.openId;
        //      report-submit="true"
        var formId = e.detail.formId;
    
        if (!formId || 'the formId is a mock one' === formId) {
          _this.setData({
            logMessage: '[fail]       ,      formId'
          });
          return;
        }
    
        //         
        wx.request({
          url: requestHost + "/template_send",
          data: {
            openId: openId,
            formId: formId
          },
          method: 'POST',
          success: function(res) {
            if (res.data.status === 0) {
              _this.setData({
                logMessage: '        [' + new Date().getTime()+']'
              });
            }
          },
          fail: function(err) {
            _this.setData({
              logMessage: '[fail]' + JSON.stringify(err)
            });
          }
        });
      }
    })

    バックエンドインタフェース
    まず、使用する必要がある微信APIについて簡単なパッケージを作成します.
    package com.wuwenze.wechatsmallapptmplmsg.wechat;
    
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.http.HttpRequest;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.google.common.cache.CacheBuilder;
    import com.google.common.cache.CacheLoader;
    import com.google.common.cache.LoadingCache;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author wwz
     * @version 1 (2018/8/20)
     * @since Java7
     */
    @Slf4j
    public class WechatApi {
        private final static LoadingCache mAccessTokenCache =
                CacheBuilder.newBuilder()
                        .expireAfterWrite(7200, TimeUnit.SECONDS)
                        .build(new CacheLoader() {
                            @Override
                            public String load(String key) {
                                // key: appId#appSecret
                                String[] array = key.split("#");
                                if (null == array || array.length != 2) {
                                    throw new IllegalArgumentException("load access_token error, key = " + key);
                                }
                                return getAccessToken(array[0], array[1]);
                            }
                        });
    
        public static String getAccessToken() {
            String cacheKey = WechatConf.appId + "#" + WechatConf.appSecrct;
            try {
                return mAccessTokenCache.get(cacheKey);
            } catch (ExecutionException e) {
                log.error("#getAccessToken error, cacheKey=" + cacheKey, e);
            }
            return null;
        }
    
        private static String getAccessToken(String appId, String appSecret) {
            String apiUrl = StrUtil.format(//
                    "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}",//
                    appId, appSecret
            );
            String body = HttpRequest.get(apiUrl).execute().body();
            return throwErrorMessageIfExists(body).getString("access_token");
        }
    
        public static void templateSend(String accessToken, WechatTemplate template) {
            String apiUrl = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token="//
                    + (StrUtil.isEmpty(accessToken) ? getAccessToken() : accessToken);
            String body = HttpRequest.post(apiUrl).body(JSON.toJSONString(template)).execute().body();
            throwErrorMessageIfExists(body);
        }
    
        public static JSONObject getOpenIdByJSCode(String js_code) {
            String apiUrl = StrUtil.format(//
                    "https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code",//
                    WechatConf.appId, WechatConf.appSecrct, js_code
            );
            String body = HttpRequest.get(apiUrl).execute().body();
            return throwErrorMessageIfExists(body);
        }
    
        private static JSONObject throwErrorMessageIfExists(String body) {
            String callMethodName = (new Throwable()).getStackTrace()[1].getMethodName();
            log.info("#0820 {} body={}", callMethodName, body);
            JSONObject jsonObject = JSON.parseObject(body);
            if (jsonObject.containsKey("errcode") && jsonObject.getIntValue("errcode") > 0) {
                throw new RuntimeException(StrUtil.format("#WechatApi[{}] call error: {}", callMethodName, body));
            }
            return jsonObject;
        }
    }

    対外開放関連インタフェース:
    package com.wuwenze.wechatsmallapptmplmsg.controller;
    
    import cn.hutool.core.date.DateUtil;
    import cn.hutool.core.util.RandomUtil;
    import cn.hutool.core.util.StrUtil;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.wuwenze.wechatsmallapptmplmsg.util.MapUtil;
    import com.wuwenze.wechatsmallapptmplmsg.wechat.WechatApi;
    import com.wuwenze.wechatsmallapptmplmsg.wechat.WechatConf;
    import com.wuwenze.wechatsmallapptmplmsg.util.SecurityUtil;
    import com.wuwenze.wechatsmallapptmplmsg.util.WebUtil;
    import com.wuwenze.wechatsmallapptmplmsg.wechat.WechatTemplate;
    import com.wuwenze.wechatsmallapptmplmsg.wechat.WechatTemplateItem;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Map;
    import java.util.stream.Stream;
    
    /**
     * @author wwz
     * @version 1 (2018/8/16)
     * @since Java7
     */
    @Slf4j
    @RestController
    @RequestMapping("/wx_small_app")
    public class WechatController {
    
        @GetMapping("/get_openid_by_js_code")
        public Map getOpenIdByJSCode(String js_code) {
            return WechatApi.getOpenIdByJSCode(js_code);
        }
    
        @PostMapping("/template_send")
        public Map templateSend() {
            String accessToken = WechatApi.getAccessToken();
            JSONObject body = JSON.parseObject(WebUtil.getBody());
    
            //        (    ,  )
            WechatTemplate wechatTemplate = new WechatTemplate()
                    .setTouser(body.getString("openId"))
                    .setTemplate_id(WechatConf.templateId)
                    //         formid,      prepay_id
                    .setForm_id(body.getString("formId"))
                    //     
                    .setPage("index")
                    /**
                     *       :    
                     *      {{keyword1.DATA}}
                     *      {{keyword2.DATA}}
                     *      {{keyword3.DATA}}
                     *      {{keyword4.DATA}}
                     * -> {"keyword1": {"value":"xxx"}, "keyword2": ...}
                     */
                    .setData(MapUtil.newHashMap(//
                            "keyword1", new WechatTemplateItem(RandomUtil.randomString(10)),//
                            "keyword2", new WechatTemplateItem(DateUtil.now()),//
                            "keyword3", new WechatTemplateItem(RandomUtil.randomString(10)),//
                            "keyword4", new WechatTemplateItem(RandomUtil.randomNumbers(10)) //
                    ));
            WechatApi.templateSend(accessToken, wechatTemplate);
            return MapUtil.newHashMap("status", 0);
        }
    
        @GetMapping("/validate")
        public void validate(String signature, String timestamp, String nonce, String echostr) {
            final StringBuilder attrs = new StringBuilder();
            Stream.of(WechatConf.token, timestamp, nonce)//
                    .sorted()//
                    .forEach((item) -> attrs.append(item));
            String sha1 = SecurityUtil.getSha1(attrs.toString());
            if (StrUtil.equalsIgnoreCase(sha1, signature)) {
                WebUtil.write(echostr);
                return;
            }
            log.error("#0820 WechatController.validate() error, attrs = {}", attrs);
        }
    }

    ファイナルエフェクト
    ウィジェットインタフェース
    受信したテンプレートメッセージ
    その他:テンプレートメッセージの送信制限を突破
    必要でなければ、できるだけそうしないでください.小さなプログラムがテンプレートメッセージを乱用していることを発見したら、微信は閉鎖する権利があります.
    簡単に言えば、ウィジェットのフォームコンポーネントをカプセル化し、ウィジェットの他の機能ボタンを偽装することができます.ユーザーがボタンをクリックすると、フォームコンポーネントは自動的にformIdをサーバーにアップロードして保存(7日後に期限切れ)し、一定のユーザーがイベントをクリックしたことを収集すると、持ってきて使用することができます(アクティブメッセージプッシュグループ送信)、ハハハ.
    ソースアドレス
    使用するイントラネット貫通ツールを含める
    https://gitee.com/wuwenze/wec...https://github.com/wuwz/wecha...