微信服务号的消息推送
- 公众号
- 已认证公众号
- 服务号
- 已认证服务号
- 企业号
基于:微信认证服务号 主动推送微信消息。
前提:关注服务号
环境:沙箱环境
微信公号平台:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
实例:
import json
import functools
import requests
from django.conf import settings
from django.shortcuts import render, redirect, HttpResponse
from django_redis import get_redis_connection
from django.http import JsonResponse
from app01 import models
# 沙箱环境地质:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
# def index(request):
# obj = models.UserInfo.objects.get(id=1)
# return render(request,'index.html',{'obj':obj})
def auth(func):
@functools.wraps(func)
def inner(request, *args, **kwargs):
user_info = request.session.get('user_info')
if not user_info:
return redirect('/login/')
return func(request, *args, **kwargs)
return inner
def login(request):
"""
用户登录
:param request:
:return:
"""
# models.UserInfo.objects.create(username='luffy',password=123)
if request.method == "POST":
user = request.POST.get('user')
pwd = request.POST.get('pwd')
obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
if obj:
request.session['user_info'] = {'id': obj.id, 'name': obj.username, 'uid': obj.uid}
return redirect('/bind/')
else:
return render(request, 'login.html')
@auth
def bind(request):
"""
用户登录后,关注公众号,并绑定个人微信(用于以后消息推送)
:param request:
:return:
"""
return render(request, 'bind.html')
@auth
def bind_qcode(request):
"""
生成二维码 (本质上就是一个url)
扫描二维码,就是向这个url发送请求
:param request:
:return:
"""
ret = {'code': 1000}
try:
access_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={appid}&redirect_uri={redirect_uri}&response_type=code&scope=snsapi_userinfo&state={state}#wechat_redirect"
access_url = access_url.format(
appid=settings.WECHAT_CONFIG["app_id"], # 'wx89085e915d351cae',
redirect_uri=settings.WECHAT_CONFIG["redirect_uri"], # 'http://47.93.4.198/test/',
# 回调地址;就是访问这个access_url后,微信会向 redirect_uri 发送请求(数据里面包含了用户的微信号,以及给用户生成的MD5值)
state=request.session['user_info']['uid'] # 为当前用户生成MD5值
)
ret['data'] = access_url
except Exception as e:
ret['code'] = 1001
ret['msg'] = str(e)
return JsonResponse(ret)
def callback(request):
"""
用户在手机微信上扫码后,微信自动调用该方法。
用于获取扫码用户的唯一ID(openid),以后用于给他推送消息。
:param request:
:return:
"""
code = request.GET.get("code")
print(code)
# 用户md5值
state = request.GET.get("state")
print(state)
# 获取该用户openId(用户唯一,用于给用户发送消息)
res = requests.get(
url="https://api.weixin.qq.com/sns/oauth2/access_token",
params={
"appid": settings.WECHAT_CONFIG["app_id"],
"secret": settings.WECHAT_CONFIG["appsecret"],
"code": code,
"grant_type": 'authorization_code',
}
).json()
# 获取的到openid表示用户授权成功
openid = res.get("openid")
print(openid)
if openid:
models.UserInfo.objects.filter(uid=state).update(wx_id=openid)
response = "<h1>授权成功 %s </h1>" % openid
else:
response = "<h1>用户扫码之后,手机上的提示</h1>"
return HttpResponse(response)
def sendmsg(request):
conn = get_redis_connection('default')
def get_access_token():
"""
获取微信全局接口的凭证(默认有效期俩个小时)
如果不每天请求次数过多, 通过设置缓存即可
"""
result = requests.get(
url="https://api.weixin.qq.com/cgi-bin/token",
params={
"grant_type": "client_credential",
"appid": settings.WECHAT_CONFIG['app_id'],
"secret": settings.WECHAT_CONFIG['appsecret'],
}
).json()
#在向微信发送信息的时候,需要先找微信拿一下token值,然后发送信息的时候,需要带上这个token值。
if result.get("access_token"):
access_token = result.get('access_token')
else:
access_token = None
return access_token
# token值 有效的时间为2小时,所以不用每次都请求。可以存放到redis(缓存中。)
access_token=conn.get("token")
if not access_token:
print("not redis")
access_token = get_access_token()
conn.set("token",access_token,ex=60*60*2)
print(access_token)
id = request.session['user_info']['id']
print(id)
openid = models.UserInfo.objects.get(id=id).wx_id
def send_custom_msg():
body = {
"touser": openid,
"msgtype": "text",
"text": {
"content": '云姐好美呀'
}
}
response = requests.post(
url="https://api.weixin.qq.com/cgi-bin/message/custom/send",
params={
'access_token': access_token
},
data=bytes(json.dumps(body, ensure_ascii=False), encoding='utf-8')
)
# 这里可根据回执code进行判定是否发送成功(也可以根据code根据错误信息)
result = response.json()
return result
def send_template_msg():
"""
发送模版消息
"""
res = requests.post(
url="https://api.weixin.qq.com/cgi-bin/message/template/send",
params={
'access_token': access_token
},
json={
"touser": openid,
"template_id": 'DyfhSDx0oL-FomFwAbfATFnpV1DihaJBdl2NT4Lzzys',
"data": {
"student": {
"value": "云姐",
"color": "#173177"
},
"homwork": {
"value": "Django项目",
"color": "#173177"
},
}
}
)
result = res.json()
return result
result = send_template_msg()
if result.get('errcode') == 0:
return HttpResponse('发送成功')
return HttpResponse('发送失败')
bind.html
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div style=" 600px;margin: 0 auto">
<h1>请关注路飞学城服务号,并绑定个人用户(用于以后的消息提醒)</h1>
<div>
<h3>第一步:关注路飞学城微信服务号</h3>
<img style="height: 100px; 100px" src="{% static "img/luffy.jpeg" %}">
</div>
<input type="button" value="下一步【获取绑定二维码】" onclick="getBindUserQcode()">
<div>
<h3>第二步:绑定个人账户</h3>
<div id="qrcode" style=" 250px;height: 250px;background-color: white;margin: 100px auto;"></div>
</div>
</div>
<script src="{% static "js/jquery.min.js" %}"></script>
<script src="{% static "js/jquery.qrcode.min.js" %}"></script>
<script src="{% static "js/qrcode.js" %}"></script>
<script>
function getBindUserQcode() {
$.ajax({
url: '/bind_qcode/',
type: 'GET',
success: function (result) {
console.log(result);
$('#qrcode').empty().qrcode({text: result.data});
}
});
}
</script>
</body>
</html>
Django中的
settings.py
# ############# 微信 ##############
WECHAT_CONFIG = {
'app_id': 'wx4f7020c38e5578bb',
'appsecret': 'd2f034a0b076156ab2007b99fcf32187',
'redirect_uri': 'http://47.98.134.86/callback/',#授权回调页面域名 没有服务器
}
# redis配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100},
"PASSWORD": "zh4350697",
}
}
}
总结:
1. 注册账号 appID:wx89085e915d351cae appsecret:64f87abfc664f1d4f11d0ac98b24c42d 网页授权获取用户基本信息:47.98.134.86 或 域名 2. 关注公众号(已认证的服务号) 3. 生成二维码,用户扫描; 将用户信息发送给微信,微信再将数据发送给设置redirect_uri地址(md5值) 4. 回调地址:47.98.134.86/callback/ - 授权 (获取wx_id 就是授权成功了) - 用户md5 - 获取wx_id 在数据库中更新设置:wx_id 5. 发送消息(模板消息) - wx_id - access_token(2小时有效期)
支付宝支付
支付宝的沙箱环境
https://openhome.alipay.com/platform/home.htm
支付宝的官网的支付接口
utils文件夹下的pay.py文件
from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes
import json
class AliPay(object):
"""
支付宝支付接口(PC端支付接口)
"""
def __init__(self, appid, app_notify_url, app_private_key_path,
alipay_public_key_path, return_url, debug=False):
self.appid = appid
self.app_notify_url = app_notify_url
self.app_private_key_path = app_private_key_path
self.app_private_key = None
self.return_url = return_url
with open(self.app_private_key_path) as fp:
self.app_private_key = RSA.importKey(fp.read())
self.alipay_public_key_path = alipay_public_key_path
with open(self.alipay_public_key_path) as fp:
self.alipay_public_key = RSA.importKey(fp.read())
if debug is True:
self.__gateway = "https://openapi.alipaydev.com/gateway.do"
else:
self.__gateway = "https://openapi.alipay.com/gateway.do"
def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
biz_content = {
"subject": subject,
"out_trade_no": out_trade_no,
"total_amount": total_amount,
"product_code": "FAST_INSTANT_TRADE_PAY",
# "qr_pay_mode":4
}
biz_content.update(kwargs)
data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
return self.sign_data(data)
def build_body(self, method, biz_content, return_url=None):
data = {
"app_id": self.appid,
"method": method,
"charset": "utf-8",
"sign_type": "RSA2",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"biz_content": biz_content
}
if return_url is not None:
data["notify_url"] = self.app_notify_url
data["return_url"] = self.return_url
return data
def sign_data(self, data):
data.pop("sign", None)
# 排序后的字符串
unsigned_items = self.ordered_data(data)
unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
sign = self.sign(unsigned_string.encode("utf-8"))
# ordered_items = self.ordered_data(data)
quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)
# 获得最终的订单信息字符串
signed_string = quoted_string + "&sign=" + quote_plus(sign)
return signed_string
def ordered_data(self, data):
complex_keys = []
for key, value in data.items():
if isinstance(value, dict):
complex_keys.append(key)
# 将字典类型的数据dump出来
for key in complex_keys:
data[key] = json.dumps(data[key], separators=(',', ':'))
return sorted([(k, v) for k, v in data.items()])
def sign(self, unsigned_string):
# 开始计算签名
key = self.app_private_key
signer = PKCS1_v1_5.new(key)
signature = signer.sign(SHA256.new(unsigned_string))
# base64 编码,转换为unicode表示并移除回车
sign = encodebytes(signature).decode("utf8").replace("
", "")
return sign
def _verify(self, raw_content, signature):
# 开始计算签名
key = self.alipay_public_key
signer = PKCS1_v1_5.new(key)
digest = SHA256.new()
digest.update(raw_content.encode("utf8"))
if signer.verify(digest, decodebytes(signature.encode("utf8"))):
return True
return False
def verify(self, data, signature):
if "sign_type" in data:
sign_type = data.pop("sign_type")
# 排序后的字符串
unsigned_items = self.ordered_data(data)
message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
return self._verify(message, signature)
支付的代码
from django.shortcuts import render, redirect, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from utils.pay import AliPay
import time
from django.conf import settings
from alipay import models
def aliPay():
obj = AliPay(
appid=settings.APPID, #支付宝appID
app_notify_url=settings.NOTIFY_URL, # 如果支付成功,支付宝会向这个地址发送POST请求(校验是否支付已经完成)
return_url=settings.RETURN_URL, # 如果支付成功,重定向回到你的网站的地址。
alipay_public_key_path=settings.PUB_KEY_PATH, # 支付宝公钥
app_private_key_path=settings.PRI_KEY_PATH, # 应用私钥
debug=True, # 默认False,
)
return obj
def index(request):
if request.method == 'GET':
return render(request, 'index.html')
alipay = aliPay()
# 对购买的数据进行加密
title = request.POST.get('shop')
money = float(request.POST.get('price'))
out_trade_no = "x2" + str(time.time())
# 1. 在数据库创建一条数据:状态(待支付)
title = title
order_num = out_trade_no
models.Order.objects.create(title=title, order_num=order_num)
query_params = alipay.direct_pay(
subject=title, # 商品简单描述
out_trade_no=out_trade_no, # 商户订单号
total_amount=money, # 交易金额(单位: 元 保留俩位小数)
)
pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)
return redirect(pay_url)
def pay_result(request):
"""
支付完成后,跳转回的地址
:param request:
:return:
"""
params = request.GET.dict()
sign = params.pop('sign', None)
alipay = aliPay()
status = alipay.verify(params, sign)
if status:
# 原则上是在下面的接口进行修改订单的操作,但是没有服务器,就无法让支付宝发送POST请求,等到公司后就可以了
# 因此现在在这里进行修改订单的操作,方便测试.
order_num = params.get("out_trade_no") # 订单号
# 支付成功后修改订单的状态
print(order_num)
models.Order.objects.update_or_create(order_num=order_num, defaults={"status": 1})
# order_obj = models.Order.objects.filter(order_num=order_num).first()
# order_obj.status = 1
return HttpResponse('支付成功')
return HttpResponse('支付失败')
# 这个接口需要挂载到服务器上,让支付宝找到这个端口,这样才能发post请求
@csrf_exempt
def update_order(request):
"""
支付成功后,支付宝向该地址发送的POST请求(用于修改订单状态)
:param request:
:return:
"""
# 发送过来的一定是POST请求
if request.method == 'POST':
from urllib.parse import parse_qs
body_str = request.body.decode('utf-8')
post_data = parse_qs(body_str)
post_dict = {}
for k, v in post_data.items():
post_dict[k] = v[0]
alipay = aliPay()
sign = post_dict.pop('sign', None)
status = alipay.verify(post_dict, sign)
if status:
# 修改订单状态
out_trade_no = post_dict.get('out_trade_no')
print(out_trade_no)
# 2. 根据订单号将数据库中的数据进行更新
return HttpResponse('success')
else:
return HttpResponse('支付失败')
return HttpResponse('')
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<form method="post">
{% csrf_token %}
购买的商品 <input type="text" name="shop">
<input type="text" name="price" placeholder="请输入要支付的金额">
<input type="submit" value="支付">
</form>
</body>
</html>
settings.py
# 支付相关配置 APPID = "2016101000655824" NOTIFY_URL = "http://127.0.0.1:8000/update_order/" RETURN_URL = "http://127.0.0.1:8000/pay_result/" PRI_KEY_PATH = "keys/app_private_2048.txt" PUB_KEY_PATH = "keys/alipay_public_2048.txt"
keys文件夹下的
alipay_public_2048.txt
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAozu/MQKIOeevh6HaYPaSTZ/dkFJQUTEi3jFiigx2ClYvivNbAlKwzdtcvBGr9SyV3oThU8mGkajv91Gk/c06iBtu968MWouygAOGamyRAUWhXjDSginM2R+WGSgaxUKrNOWqfh3IBe1YHA2N/ixaGy/Y1z/Dbl+mdBQPid0aH2sR9gvGYf4WxnUtqQu8An6zS0CGkPyKrlHyRy6nasKvWSfbXd6QgN4FHct7J4m6GbE4qIoUcyXTcUx23YJX/R+q7M0gzwQaBA5jQn7o8QUlfiCW2zN4fTU9+sutHuOEips3bzBdlcNt4Ndp5Skcv6AJcsJ+C4Md/3KwQ77urFj+nQIDAQAB -----END PUBLIC KEY-----
app_private_2048.txt
-----BEGIN RSA PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCx92fMNvfhnJJogPyLuKZFyPPggGhwQLAE9SjYNTAJ8+Tbxh6VqztRt8hSmkWCa1Al01qncncUY3zmbucmpehVd9RwM7/rUhbaoMn6YC7+9WlfUj+k6McT1zIqG8MrkB9WbKQ06hCiJGz9DuuWlh+spklsq7RKopP7a4/2VuLF2XN+i6srvrczX6BoSA4fpOvHkOz/abHIBKZI8SUVgq2fMQc2h0lZFYjOXNrnFjBrckA4FRnTzeH7hvW4MDR9dRqxa0p3vx7gdRgQyBdmlFYf7QUFRDCizIS3UBsUbZWr8FflpVSTMEfL+A4aNaElRua605YOTTkqD7HXxBojKtmbAgMBAAECggEAXlx9B9W5d0Gqi4ig34CngOb9EHNlbOePrQFEh+cjspNELJeOzfL9v+V/bPTpmC5IT9YSAij6JLBfoFr7aw2a8/5WgKufvilHfuK0VXI8WOlM1sLohgr6y3VV3ufbgzSmuQ9fvcLB0QcZVtBbb/vnjYaZ9enQ7aXoau3sZkRJd3djcYZYwY8yhOMD9cigm+pr2st2BnLWubydtaTW8ogbwY7ctNWIhq3gDeauWdFFIJmnVpOutJIcvGeo+OgIB06tfYzHvZAMftBiHwkjPH33bWHy0v9IjF9ArIcvrYjYds/nK3e3vMJma8Vp8BRCk4Z/rXDZGK8Pw3RGl+fq29pfGQKBgQDeQMzU+qwXvG3p6g6CvsndWG0e2taYvbK0i5SAspSC2i/9aq85+jt8jqPOK90t0trPygD+wnahiOOVZwSQYT2C7JsvvguXxTDLki/t1j+BKYK8kgmV2SlWMqSpkc5tez9Iq2RzeMZpi+dQWi4xWplw8XXu0E0o2CE1z19HsX6ERQKBgQDM/SMq2vV2mT3WoyT63GbsPeNq/zHhSgGeCFcicUYRxJvqTdem9k8zpH4D4Te8bNrGbLco+3vi3Ad+GwPHLr2Bpqone5NvqHGbaHJpRwFEasiNFwdVNS8gjULDMuuc6tdeXqmdujSQ0/RDF2En7zXnrsREE6zMGqX/w00dy+70XwKBgBHIpZs1I6gSj8jzzY1wrr5jYPfjEuDN7Qq9UHir0W5W/xgL/VFqUHA7Cahpoh0UjiWqSEIaVVu/lFZUE+1pmn5raE99qXfPc4QWgndJeXNgWvGzzciLw9791mcrH5VrEzlBXZxPwbCYXT30uVWBpl1/NKyTRllKUf34Ret6rGDxAoGBAMr5eGIN73Ig6M9oOczAgpU37sDasgxPGGzf+0+ac/RSBsSpkXi8ec47+Z9j2amU68gAjBhjc9c9YZnnrAUFbhY77k4sGeA9HUjx0iAWc9XIGo9CFzuy7tg/p7Ta7dwx2VGTUEZiw3wIs9vfAY/mWCzxq1txU+/CD07CltCDRzfnAoGAWmUTu3YC1cPcZk4B5S6d6scCsQMrGAdwH5HFI7uCZpwx1HcVLTpEyfRgUGL1Buyc9/sq54pxBEVVprhDK0ja72wo9tIlxXUusqkoNCqSFZcq7dMMz2eu7zZUFrl2qjqv3CJAstLV6XIWicJK1X9Lnq1cyB1m+7/VLcC+41RX1F4= -----END RSA PRIVATE KEY-----
支付宝支付的总结:
a. 去支付宝申请 - 正式:营业执照 - 测试:沙箱测试环境 APPID:2016082500309412 买家: nbjsag5718@sandbox.com 111111 111111 b. 开发程序 SDK加密方式 - 官方 - github pay.py 依赖:pip3 install pycryptodome 公钥私钥: - 应用公钥 - 支付宝公钥 - 应用私钥
- 支付成功后,断电宕机
要给支付宝返回一个success。这样支付宝才认为支付成功了。 不然会在一天内持续的给你发送post请求。
- 支付成功:return HttpResponse('success')