分类 技术相关 下的文章

在开发微信小程序答题游戏后端的时候,遇到了很多坑。

  1. Supervisor 环境变量需要在ini里面手动设置。
  2. 用root运行Celery,建立Celery实例的时候 platforms.C_FORCE_ROOT = True 。
  3. Celery和Django结合:http://docs.jinkan.org/docs/celery/django/first-steps-with-django.html#using-celery-with-django
  4. Celery多任务默认是会阻塞的,使用 -P eventlet(并发模式) -c [线程数] 开启并发。
  5. Django Model的DateTimeField 类型在数据库中的时候为UTC时区,取出后实例中时为当前时区。
  6. 微信小程序推送的实现

主要说一下推送:

之前有个接口需要频繁地调用,所以采取了通过Websocket事件通知的形式来减少请求数量。
小程序端直接调用Websocket接口就行了,后端我是用Django写的,但是感觉Django channels和Django耦合度较高
所以我采用了Tornado,跑在另外的端口,请求通过Nginx转发,注意Nginx配置里面 proxy_set_header Connection "upgrade"; 开启支持Websocket。这里为了不阻塞其他Websocket请求,使用异步的Redis库:tornado-redis。

# coding: utf-8
import tornado.ioloop
import tornado.web
import tornado.websocket
import tornadoredis
from tornado import gen

CHANNEL = ['group_info', 'question_status']


class WSHandler(tornado.websocket.WebSocketHandler):

    def __init__(self, *args, **kwargs):
        self._redis_client = None
        super(WSHandler, self).__init__(*args, **kwargs)
        self._connect_to_redis()
        self._listen()

    def open(self):
        pass

    def _connect_to_redis(self):
        self._redis_client = tornadoredis.Client(host='localhost', port=6379, selected_db=2)
        self._redis_client.connect()

    def on_message(self, message):
        pass
        # self.write_message(message)

    @gen.coroutine
    def _on_update(self, message):
        if message.kind == 'message':
            self.write_message(message.body)

    @gen.engine
    def _listen(self):
        yield gen.Task(self._redis_client.subscribe, CHANNEL)
        self._redis_client.listen(self._on_update)

    def on_close(self):
        self._redis_client.unsubscribe(CHANNEL)
        self._redis_client.disconnect()

    def check_origin(self, origin):
        return True


def make_app():
    return tornado.web.Application([
        (r"/ws", WSHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

这里主要是通过Redis的订阅发布来实现。在HTTP请求中的一些操作触发了通知,比如新成员加入了群组后
直接操作redis发布消息到某个频道。

publish group_info somestring

具体的内容(somestring)可以自己和客户端商量一个协议,比如通过JSON等等

截图

一开始使用Rails的时候,我就很好奇:a链接跳转的时候Chrome F12打开审核工具,发现只是资源请求,页面并没有实际的跳转,类似SPA。后来才知道是由于Turbolink

其中的坑在Rails文档里面也说了,很多JS以前用$(docuemnt).ready()的地方要改成监听 "turbolinks:load" 事件。

以下为转载内容:

正常加载页面顺序:

1.下载 index.html
2.解析 head 标签中的 link 与 script 标签, 如果是带有 src 属性, 阻塞其他逻辑执行, 继续去下载对应的资源并执行. 如果没带, 则直接执行其中的代码逻辑.
3.渲染 body 标签的内容, 并解析执行 body 中的 script 标签.
4.全部执行完毕, 执行 DOMContentLoaded 事件绑定的逻辑.
5.第一次加载时网页执行跟上述是一致, 之后 Turbolinks 会绑定 Body 下所有的 a 元素的 click 事件, 切换页面时, Turbolinks 将会接管浏览器的页面加载过程, 采用以下方式:

Turbolinks:

1.异步加载新页面的 index.html
2.解析 head 标签中的 link 与 script 标签, 识别其中带有 data-turbolinks-track 的属性, 如果 src 有变化( 可能性很小 ), 则重载所有页面. 如果没有变化, 则不进行任何操作.
3.解析 head 标签中新的 link 与 script 标签, 加载并执行.
4.用新页面的 body 替换老的 body 中的内容, 并执行其中的 script 脚本.

Turbolinks5 概述及实现原理

turbolinks 實際利弊

本文时间为2017年04月16日,如果微信支付官方文档有变化请注意。

h5页面用JS调起支付。

文档里面的参数一定要分清楚大小写!有些地方是大写,有些小写,有些又是下划线。真的巨坑

noncestr nonce_str nonceStr

timestamp timeStamp

1.微信JS SDK

文档地址

有一个坑,因为我是在微信服务号中调用的,有一步需要获取用户的openid,导致最后的url变化为/?code=xxxx&state=xxx。

函数说明:
create_noncestr 生成16位随机字符串

create_timestamp 生成10位时间戳

get_jsapi_ticket 根据 access_token 获取jsapi_ticket

js_url 为当前的url!即调用该JS页面的完整url

noncestr = create_noncestr()
timestamp = create_timestamp()
jsapi = get_jsapi_ticket()
js_url = JSAPI_URL + '?code={code}&state={state}'.format(code=code, state=state)

开启debug:true,如果提示 "errMsg":"config ok" 则表示JSSDK初始化成功。(成功提示居然是errMsg。。)

2.统一下单

文档地址

坑一:按照文档的步骤走,但是有一点需要注意,文档里面没有timeStamp这一项,需要自己加!

坑二:spbill_create_ip

这个参数表示客服端ip,由于我是用Django进行开发,Nginx反向代理,所以这里如果用 request.META['REMOTE_ADDR'] 为127.0.0.1

应该使用 request.META['HTTP_X_FORWARDED_FOR'].split(',')[0]

坑三:openid 为必填(JS SDK支付为必填)

坑四:openssl 给微信服务器发送xml数据的地址是https,Linux下需要安装一些东西。

sudo apt-get install openssl
sudo apt-get install libssl-dev

拿到 perpay_id 后,里面同时有个sign,注意这个sign不是前端所需要的paySign

这句话在JS SDK文档里面:

paySign 采用统一的微信支付 Sign 签名生成方法,注意这里 appId 也要参与签名,appId 与 config 中传入的 appId 一致,即最后参与签名的参数有appId, timeStamp, nonceStr, package, signType。

用同样的签名方式

# 拿到prepay_id后继续获取前端所需要的 paySign
d1 = {
    'appId': APP_ID,
    'timeStamp': 2步骤发送的timestamp,
    'nonceStr': 2步骤返回的nonce_str,
    'package': 'prepay_id='+ 2步骤返回的perpay_id,
    'signType': 'MD5',
}
s_d1 = '&'.join(['%s=%s' % (key, d1[key]) for key in sorted(d1)])
# 这里注意必须在最后加上 &key=商户key(在微信支付商户设置里面设置的32位随机字符串)
s_d1 += '&key={key}'.format(key=API_KEY)
paySign = hashlib.md5(s_d1.encode('utf-8')).hexdigest().upper()

3.前端调用

真的无语,微信官方有两套方法。微信支付有一套,JSSDK里面又有一种。最后用的微信支付文档里面的方法

文档地址

function onBridgeReady(){
    WeixinJSBridge.invoke(
        'getBrandWCPayRequest', {
            "appId":"wx2421b1c4370ec43b",     //公众号名称,由商户传入     
            "timeStamp":"1395712654",         //时间戳,自1970年以来的秒数     
            "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串     
            "package":"prepay_id=u802345jgfjsdfgsdg888",     
            "signType":"MD5",         //微信签名方式:     
            "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 
        },
        function(res){     
            if(res.err_msg == "get_brand_wcpay_request:ok" ) {}     // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。 
        }
    ); 
}
if (typeof WeixinJSBridge == "undefined"){
    if( document.addEventListener ){
        document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
    }else if (document.attachEvent){
        document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
        document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
    }
    }else{
        onBridgeReady();
}

4.notify地址返回success或者fail

2017年05月26日补充:这里和之前一样,拿到request.body xml解析后操作,返回也是xml格式。

付款成功后的操作就在这里进行。

5.需要用到的key/id

1.公众账号ID APPID

2.商户号 mch_id

3.商户key (商户平台后台设置的32位随机字符串)