相信很多做前端的朋友都已经注意到了微信今年的一系列大动作,当然,对于前端开发工作来讲,我觉得这本身就是一种非常不错的开放机会,能够让我们更好地利用好微信这个平台,每次写博客都要废话一段前言…… 好吧,下面说说最近的 JS-SDK 那些事儿。

前端部分

在前端部分,我们重点来看下 JS-SDK 的接入流程,从前端角度而言,大致是如下三个步骤: wx.pre-wx.ready-wx.apicall (实际上没有这个 apicall 方法)。 我把它翻译成:接入 - 就绪 - 调用。 OK,如果这么说的话,嗯……So easy……But,当我们用代码的方式写出来,可能是这样一种情况:

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
var jsTool = {};

// Get Access Token
// Need APPID & APPSECRET
jsTool.getAccessToken = function (callback){
    $.ajax ({}, function (result){
         callback (result);
    });
};

// Get jsapi_ticket
jsTool.getApiTicket = function (callback){
    this.getAccessToken (function (acCode){
            $.ajax ({}, function (result){
                callback (result);
                // 立即存下 ticket
            });
    });
};

// Throw args to backend to checksign
jsTool.getSignFromBackEnd = function (callback){
     $ajax ({
          // 扔给后端参数中的 timestamp 和 nonceStr 必须跟 wx.config 一致
          timestamp: 145672831,
          noncestr: "Wm3WZYTPz0wzccnW"
          jsapi_ticket: "sM4AOVdWfPE4DxkX" // 上面存下的 ticket
          url: "https://mp.weixin.qq.com"
     }, function (sign){
         wx.config ({
            debug: true, // 开启调试模式
            appId: '', // 必填,公众号的唯一标识
            timestamp: , // 必填,生成签名的时间戳
            nonceStr: '', // 必填,生成签名的随机串
            signature: sign,// 必填,签名
            jsApiList: [xxx,xxx,xxx,xxx,xxx] // 必填,需要使用的 JS 接口列表
         });
     });
}

没错,如果按照官方文档的说明进行开发还是稍微显得有些复杂,主要是因为微信这次加入了签名策略。 那么,我们再来梳理一遍带签名的处理流程:

  1. 拿下全局 token
  2. 根据全局 token 拿到 ticket
  3. 一顿签名规则
  4. 拿到签名
  5. wx.pre
  6. wx.ready
  7. wx.apicall

我们只要想办法把 wx.config 需要的参数直接扔给前端,让他继续处理下面的事情就可以了,官方推荐的做法也是如此: token 和 ticket 建议存储,必要时签名等做成中置服务,让所有应用服务器都只用一个全局 token。

说干就干,上 flask 版的代码: 我最终的思路是:让前端专心处理前端,让后端专心处理后端,通过 restful 风格的 /all 接口,我可以将 wx.config 需要的一系列参数全部直接返回给前端使用。 全局的 token 和 ticket 使用 job 的方式去单独处理他们的更新。 与官方推荐做法基本吻合:

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
# -*- coding:utf-8 -*-
from flask import Flask,  make_response, request
from config import CONFIG
import json
import random
import string
import hashlib
import time
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask (__name__)
app.config ['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///main.db'  # 相对路径写法
db = SQLAlchemy (app)


class TokenAndTicket (db.Model):

    id = db.Column (db.Integer, primary_key=True)
    token = db.Column (db.String (200), unique=True)
    ticket = db.Column (db.String (200), unique=True)

    def __init__(self, token, ticket):
        self.token = token
        self.ticket = ticket

    def __repr__(self):
        return "<TICKET % r>" % self.ticket


current_token_ticket = TokenAndTicket.query.first ()

# error code
DONE = 200

# weixin token
WX_TOKEN_REQ = (
    "https://api.weixin.qq.com/cgi-bin/token?grant_type=% s&appid=% s&secret=% s"
    % (CONFIG ['client_credential'], CONFIG ["app_id"], CONFIG ["secret"])
)

# weixin js_ticket
WX_TICKET_REQ = lambda x: "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=% s&type=jsapi" % x


@app.route ('/nonce', methods=['GET', 'POST'])
def ge_nonce_str ():
    """ 随机串单拿接口
    """
    return json_response (
        {
            "nonce_str": ''.join (random.sample (string.ascii_letters, 12))
        }
    )


def _ge_nonce_str ():
    """all in one 接口用
    """
    return ''.join (random.sample (string.ascii_letters, 12))


def error_res_obj (code, msg):
    """ 错误响应
    """
    return {
        "errcode": code,
        "errmsg": msg
    }


def json_response (res):
    """ 包装 response 为 json
    """
    if isinstance (res, dict) or isinstance (res, list):
        json_response_body = make_response (json.dumps (res))
    else:
        json_response_body = make_response (res)
    json_response_body.headers ['Content-Type'] = "application/json;charset=UTF-8"  # 设置
    # json_response_body.headers ['Access-Control-Allow-Origin'] = "*"  # 跨域
    return json_response_body


@app.route ('/token', methods=['POST', 'GET'])
def get_access_token ():
    """token 单拿接口
    """
    token = current_token_ticket.token
    return json_response ({
        "token": token
    })


@app.route ('/ticket', methods=['POST', 'GET'])
def js_ticket ():
    """ticket 单拿接口
    """
    ticket = current_token_ticket.ticket
    return json_response ({
        "ticket": ticket
    })


def _sign_ticket (nonce_str, j_ticket, timestamp, url):
    """ 私有签名方法
    """
    sign_dict = {
        "jsapi_ticket": j_ticket,
        "noncestr": nonce_str,
        "timestamp": timestamp,
        "url": url
    }

    tmp = []

    # 字典排序
    sign_sorted_list = sorted (sign_dict.iteritems (), key=lambda x: x [0], reverse=False)
    for item in sign_sorted_list:
        tmp.append (str (item [0]) + "=" + str (item [1]))

    _url_string = '&'.join (tmp)
    final_sign = hashlib.sha1 (_url_string).hexdigest ()
    return final_sign


@app.route ('/all', methods=['POST', 'GET'])
def all_in_one ():
    """all in one 数据返回
    """
    _time_stamp = int (time.time ())
    _random_str = _ge_nonce_str ()
    _js_ticket = current_token_ticket.ticket
    _url = request.host_url [:-1]  # 去除 /

    return json_response ({
        'appId': CONFIG ['app_id'],
        'timestamp': _time_stamp,
        'nonceStr': _random_str,
        'signature': _sign_ticket (_random_str, _js_ticket, _time_stamp, _url),
        'url': _url  # POST 过来什么,返回什么
    })


def _detect_request ():
    with app.test_request_context ('/all', method=['POST', 'GET']):
        print dir (request)
        print request.host_url


if __name__ == '__main__':
    app.run (debug=True, host="0.0.0.0")
    # _detect_request ()

冬天写博客手真冷……

Github

Weixin-Flask

留下评论