828 lines
34 KiB
Python
828 lines
34 KiB
Python
from rest_framework.views import APIView
|
||
from mysystem.models import Users
|
||
from apps.oauth.models import OAuthWXUser
|
||
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
|
||
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
||
from rest_framework_simplejwt.views import TokenObtainPairView
|
||
from utils.common import get_parameter_dic,REGEX_MOBILE
|
||
from config import WX_XCX_APPID,WX_XCX_APPSECRET,WX_GZH_APPID,WX_GZH_APPSECRET,WX_GZH_TOKEN,WX_GZPT_APPSECRET,WX_GZPT_APPID,TT_XCX_APPID,TT_XCX_APPSECRET,DOMAIN_HOST
|
||
import requests
|
||
import base64
|
||
import json
|
||
from Crypto.Cipher import AES
|
||
from django.utils.translation import gettext_lazy as _
|
||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||
from rest_framework.permissions import IsAuthenticated
|
||
import re
|
||
import uuid
|
||
from django.db import transaction
|
||
from django.db.models import F
|
||
from django.core.cache import cache
|
||
import logging
|
||
from django_redis import get_redis_connection
|
||
import base64
|
||
import os
|
||
import datetime
|
||
from django.conf import settings
|
||
from utils.common import renameuploadimg
|
||
import hashlib
|
||
from django.http import HttpResponse
|
||
from utils.weixinpay import WxAppPay
|
||
from django.shortcuts import redirect
|
||
|
||
logger = logging.getLogger(__name__)
|
||
# Create your views here.
|
||
# ================================================= #
|
||
# ************** 微信小程序登录 view ************** #
|
||
# ================================================= #
|
||
class XCXLoginSerializer(TokenObtainPairSerializer):
|
||
"""
|
||
登录的序列化器:
|
||
重写djangorestframework-simplejwt的序列化器
|
||
"""
|
||
|
||
@classmethod
|
||
def get_token(cls, user):
|
||
refresh = super(XCXLoginSerializer,cls).get_token(user)
|
||
data = {}
|
||
data['openid'] = user.oauthwxuser.xcx_openid
|
||
data['userId'] = user.id
|
||
data['refresh'] = str(refresh)
|
||
data['access'] = str(refresh.access_token)
|
||
return data
|
||
|
||
'''
|
||
WeChat Crypt
|
||
'''
|
||
class WeChatCrypt:
|
||
def __init__(self, appId, sessionKey):
|
||
self.appId = appId
|
||
self.sessionKey = sessionKey
|
||
|
||
def decrypt(self, encryptedData, iv):
|
||
# base64 decode
|
||
sessionKey = base64.b64decode(self.sessionKey)
|
||
encryptedData = base64.b64decode(encryptedData)
|
||
iv = base64.b64decode(iv)
|
||
cipher = AES.new(sessionKey, AES.MODE_CBC, iv)
|
||
decrypted = json.loads(self._unpad(cipher.decrypt(encryptedData)))
|
||
|
||
if decrypted['watermark']['appid'] != self.appId:
|
||
raise Exception('Invalid Buffer')
|
||
|
||
return decrypted
|
||
|
||
def _unpad(self, s):
|
||
return s[:-ord(s[len(s)-1:])]
|
||
|
||
#获取微信用户的openid等用户信息
|
||
|
||
def get_wechat_login_code_url(jscode):
|
||
api_url = 'https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code'
|
||
get_url = api_url.format(WX_XCX_APPID,WX_XCX_APPSECRET,jscode)
|
||
r = requests.get(get_url)
|
||
return r
|
||
|
||
|
||
#微信小程序登录接口
|
||
class WeChatXCXLoginAPIView(APIView):
|
||
"""
|
||
post:
|
||
微信小程序登录接口
|
||
微信小程序code获取openid
|
||
"""
|
||
permission_classes = []
|
||
authentication_classes = []
|
||
|
||
@transaction.atomic # 开启事务,当方法执行完成以后,自动提交事务
|
||
def post(self, request):
|
||
jscode = get_parameter_dic(request)['code']
|
||
# inviter = get_parameter_dic(request).get('inviter')#为推广者的userid
|
||
if not jscode:
|
||
return ErrorResponse(msg="code不能为空")
|
||
resp = get_wechat_login_code_url(jscode)
|
||
openid = None
|
||
session_key = None
|
||
unionid = None
|
||
if resp.status_code != 200:
|
||
return ErrorResponse(msg="服务器到微信网络连接失败,请重试")
|
||
# json_data = {'errcode':0,'openid':'111','session_key':'test'}
|
||
json_data =json.loads(resp.content)
|
||
if 'errcode' in json_data:#如果获取失败返回失败信息
|
||
return ErrorResponse(msg=json_data['errmsg'])
|
||
|
||
openid = json_data['openid']
|
||
session_key = json_data['session_key']
|
||
|
||
if "unionid" in json_data:
|
||
unionid = json_data['unionid']
|
||
|
||
# 判断用户是否存在
|
||
try:
|
||
wxuser = Users.objects.get(username=openid)
|
||
wxuser.oauthwxuser.session_key = session_key # 小写oauthwxuser 表示关联的外键
|
||
wxuser.oauthwxuser.xcx_openid = openid
|
||
wxuser.oauthwxuser.unionId = unionid
|
||
wxuser.oauthwxuser.save()
|
||
resdata = XCXLoginSerializer.get_token(wxuser)
|
||
return SuccessResponse(data=resdata, msg="success")
|
||
except Exception as e:
|
||
with transaction.atomic():
|
||
savepoint = transaction.savepoint()
|
||
user = Users()
|
||
user.username = openid
|
||
user.password = uuid.uuid4() # 先随机生成一个密码,防止别人获取openid直接被登录情况
|
||
user.identity = 2 # 用户身份2表示前端用户
|
||
user.save()
|
||
OAuthWXUser.objects.create(user=user,session_key=session_key,xcx_openid=openid)
|
||
# if inviter: # 如果存在邀请码
|
||
# integral = FenXiaoManage.objects.filter(type=1, status=True).values_list('content', flat=True).first()
|
||
# if integral: # 如果推广积分活动还存在
|
||
# Users.objects.filter(id=inviter).update(integral=F('integral') + int(integral))
|
||
# InviteRecord.objects.create(inv_user_id=inviter, invitee_user=user, get_integral=integral)
|
||
# IntegralRecord.objects.create(user_id=inviter,type=4,income=1,integral=integral)
|
||
# 清除保存点
|
||
transaction.savepoint_commit(savepoint)
|
||
resdata = XCXLoginSerializer.get_token(user)
|
||
return SuccessResponse(data=resdata, msg="success")
|
||
|
||
|
||
def filter_emoji(desstr, restr=''):
|
||
# 过滤表情
|
||
try:
|
||
res = re.compile(u'[\U00010000-\U0010ffff]')
|
||
except re.error:
|
||
res = re.compile(u'[\uD800-\uDBFF][\uDC00-\uDFFF]')
|
||
return res.sub(restr, desstr)
|
||
|
||
#微信小程序手机号授权登录接口
|
||
class WeChatXCXMobileLoginAPIView(APIView):
|
||
"""
|
||
post:
|
||
微信小程序手机号授权登录接口
|
||
微信小程序code获取openid,并解密前端传的手机号encryptedData加密数据
|
||
"""
|
||
permission_classes = []
|
||
authentication_classes = []
|
||
|
||
def post(self, request):
|
||
inviter = get_parameter_dic(request).get('inviter')#邀请码#为推广者的userid
|
||
jscode = get_parameter_dic(request)['code']
|
||
iv = get_parameter_dic(request)['iv']
|
||
encryptedData = get_parameter_dic(request)['encryptedData']
|
||
nickname = get_parameter_dic(request)['nickname']
|
||
avatar_url = get_parameter_dic(request)['avatar_url']
|
||
gender = get_parameter_dic(request)['gender']
|
||
nickname = filter_emoji(nickname, '')
|
||
if jscode is None:
|
||
return ErrorResponse(msg="code不能为空")
|
||
if iv is None:
|
||
return ErrorResponse(msg="iv不能为空")
|
||
if encryptedData is None:
|
||
return ErrorResponse(msg="encryptedData不能为空")
|
||
if avatar_url is None:
|
||
return ErrorResponse(msg="avatar_url不能为空")
|
||
|
||
resp = get_wechat_login_code_url(jscode)
|
||
openid = None
|
||
session_key = None
|
||
unionid = ""
|
||
if resp.status_code != 200:
|
||
return ErrorResponse(msg="服务器到微信网络连接失败,请重试")
|
||
# json_data = {'errcode':0,'openid':'111','session_key':'test'}
|
||
json_data =json.loads(resp.content)
|
||
if 'errcode' in json_data:#如果获取失败返回失败信息
|
||
return ErrorResponse(msg=json_data['errmsg'])
|
||
|
||
openid = json_data['openid']
|
||
session_key = json_data['session_key']
|
||
|
||
if "unionid" in json_data:
|
||
unionid = json_data['unionid']
|
||
|
||
wxdc = WeChatCrypt(WX_XCX_APPID, session_key)
|
||
|
||
pResult = wxdc.decrypt(encryptedData, iv)
|
||
|
||
#判断用户是否存在
|
||
try:
|
||
wxuser = Users.objects.get(username = openid)
|
||
if not wxuser.is_active:
|
||
return ErrorResponse(msg="该用户已禁用,请联系管理员")
|
||
wxuser.oauthwxuser.session_key = session_key#小写oauthwxuser 表示关联的外键
|
||
wxuser.oauthwxuser.xcx_openid = openid
|
||
wxuser.oauthwxuser.unionId = unionid
|
||
wxuser.oauthwxuser.avatarUrl=avatar_url
|
||
wxuser.oauthwxuser.sex = gender
|
||
wxuser.oauthwxuser.mobilePhoneNumber = pResult['phoneNumber']
|
||
wxuser.oauthwxuser.nick = nickname
|
||
wxuser.oauthwxuser.save()
|
||
wxuser.nickname = nickname
|
||
wxuser.avatar = avatar_url
|
||
wxuser.gender = gender
|
||
wxuser.save()
|
||
resdata = XCXLoginSerializer.get_token(wxuser)
|
||
return SuccessResponse(data=resdata,msg="success")
|
||
except Exception as e:#新用户
|
||
with transaction.atomic():
|
||
try:
|
||
savepoint = transaction.savepoint()
|
||
user = Users()
|
||
user.username = openid
|
||
user.password = uuid.uuid4() #先随机生成一个密码,防止别人获取openid直接被登录情况
|
||
user.identity = 2 # 用户身份2表示前端用户
|
||
user.nickname = nickname
|
||
user.name = nickname
|
||
user.avatar = avatar_url
|
||
user.mobile = pResult['phoneNumber']
|
||
user.save()
|
||
OAuthWXUser.objects.create(user=user,session_key=session_key,xcx_openid=openid,avatarUrl=avatar_url,sex=gender,mobilePhoneNumber=pResult['phoneNumber'],nick=nickname)
|
||
# if inviter:#如果存在邀请码
|
||
# integral = FenXiaoManage.objects.filter(type=1,status=True).values_list('content',flat=True).first()
|
||
# if integral:#如果推广积分活动还存在
|
||
# Users.objects.filter(id=inviter).update(integral=F('integral')+int(integral))
|
||
# InviteRecord.objects.create(inv_user_id=inviter,invitee_user=user,get_integral=integral)
|
||
# IntegralRecord.objects.create(user_id=inviter, type=4, income=1, integral=integral)
|
||
except Exception as e:
|
||
transaction.savepoint_rollback(savepoint)
|
||
return ErrorResponse(msg=str(e))
|
||
# 清除保存点
|
||
transaction.savepoint_commit(savepoint)
|
||
resdata = XCXLoginSerializer.get_token(user)
|
||
return SuccessResponse(data=resdata, msg="success")
|
||
|
||
|
||
|
||
|
||
|
||
#微信小程序更新(获取)用户信息wx.getUserInfo,用户解密获取的用户信息
|
||
class XCXWeChatUserInfoUpdateAPIView(APIView):
|
||
"""
|
||
post:
|
||
微信小程序更新用户信息
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [JWTAuthentication]
|
||
|
||
def post(self, request):
|
||
encryptedData = get_parameter_dic(request)['encryptedData']
|
||
iv = get_parameter_dic(request)['iv']
|
||
|
||
if not encryptedData:
|
||
return ErrorResponse(msg="encryptedData不能为空")
|
||
|
||
if not iv:
|
||
return ErrorResponse(msg="iv不能为空")
|
||
|
||
wechat_user = OAuthWXUser.objects.filter(user=request.user).first()
|
||
if not wechat_user:
|
||
return ErrorResponse(msg="无此用户")
|
||
|
||
pc = WeChatCrypt(WX_XCX_APPID, wechat_user.session_key)
|
||
|
||
user = pc.decrypt(encryptedData, iv)
|
||
wechat_user.nick = user['nickName']
|
||
wechat_user.sex = user['gender']
|
||
wechat_user.city = user['city']
|
||
wechat_user.avatarUrl = user['avatarUrl']
|
||
wechat_user.save()
|
||
myuser = request.user
|
||
myuser.nickname = user['nickName']
|
||
myuser.avatar = user['avatarUrl']
|
||
|
||
|
||
return SuccessResponse(data=user,msg="success")
|
||
|
||
|
||
# ================================================= #
|
||
# ************** 微信小程序生成推广小程序码view ************** #
|
||
# ================================================= #
|
||
#获取小程序的access_token
|
||
"""
|
||
正常返回,access_token 的有效期目前为 2 个小时,重复获取将导致上次获取的 access_token 失效
|
||
{"access_token":"ACCESS_TOKEN","expires_in":7200}
|
||
错误返回
|
||
{"errcode":40013,"errmsg":"invalid appid"}
|
||
"""
|
||
def get_wechat_xcx_access_token_url():
|
||
api_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}"
|
||
get_url = api_url.format(WX_XCX_APPID,WX_XCX_APPSECRET)
|
||
r = requests.get(get_url)
|
||
return r
|
||
|
||
#这个url生成二维码是无限个,返回的二维码是buffer类型(正式版小程序才能生成,体验版不行)
|
||
"""
|
||
正常返回
|
||
{
|
||
"errcode": 0,
|
||
"errmsg": "ok",
|
||
"contentType": "image/jpeg",
|
||
"buffer": Buffer
|
||
}
|
||
"""
|
||
def get_wechat_qrcode_url(access_token,scene,page,width=430,auto_color=True,is_hyaline=False):
|
||
if not page:
|
||
page = "pages/index/index"
|
||
api_url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={0}'
|
||
get_url = api_url.format(access_token)
|
||
headers = {"Content-type":"application/json"}
|
||
data = dict(scene=scene,page=page,width=width,auto_color=auto_color,is_hyaline=is_hyaline)
|
||
r = requests.post(url=get_url,data=json.dumps(data),headers=headers)
|
||
return r
|
||
|
||
class GetXCXShareQrcodeView(APIView):
|
||
"""
|
||
post:
|
||
微信小程序获取推广二维码
|
||
scene 分享用户的userid
|
||
page 要跳转的页面
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [JWTAuthentication]
|
||
def post(self,request):
|
||
scene = get_parameter_dic(request)['scene']#分享用户的userid
|
||
page = get_parameter_dic(request)['page']
|
||
if scene is None or page is None:
|
||
return ErrorResponse("提交参数不能为空")
|
||
restoken = get_wechat_xcx_access_token_url()
|
||
if restoken.status_code != 200:
|
||
return ErrorResponse(msg="服务器到微信网络连接失败,请重试1")
|
||
json_data = json.loads(restoken.content)
|
||
if 'errcode' in json_data and json_data['errcode'] !=0: # 如果获取失败返回失败信息
|
||
return ErrorResponse(msg=json_data['errmsg'])
|
||
access_token = json_data['access_token']
|
||
res = get_wechat_qrcode_url(access_token,scene,page)
|
||
if res.status_code != 200:
|
||
return ErrorResponse(msg="服务器到微信网络连接失败,请重试2")
|
||
curr_time = datetime.datetime.now()
|
||
time_path = curr_time.strftime("%Y-%m-%d")
|
||
img_task_dir = "xcxqrcode"
|
||
image_name = renameuploadimg('_QRcode.png')
|
||
sub_path = os.path.join(settings.MEDIA_ROOT, img_task_dir, time_path)
|
||
if not os.path.exists(sub_path):
|
||
os.makedirs(sub_path)
|
||
image_path = os.path.join(sub_path, image_name)
|
||
web_img_url = DOMAIN_HOST + settings.MEDIA_URL + img_task_dir + "/" + time_path + "/" + image_name # 绝对路径http://xxx.xxx.com/media/xxx/xxxx/xxx.png
|
||
with open(image_path, 'wb') as f:
|
||
f.write(res.content)
|
||
json_data2 = web_img_url
|
||
return SuccessResponse(data=json_data2,msg="success")
|
||
|
||
|
||
# ================================================= #
|
||
# ************** 微信小程序发送服务通知消息 view ************** #
|
||
# ================================================= #
|
||
"""
|
||
1、form_id提交表单的id(支付时用)
|
||
2、data 提交的请求体
|
||
push_data={
|
||
"keyword1":{
|
||
"value":obj.order_sn
|
||
},
|
||
"keyword2":{
|
||
"value":obj.time
|
||
},
|
||
}
|
||
"""
|
||
def send_wx_xcx_message(access_token,openid,template_id,form_id,push_data):
|
||
api_url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={0}"
|
||
get_url = api_url.format(access_token)
|
||
payload={
|
||
"touser": openid, #这里为用户的openid
|
||
"template_id": template_id, #模板id
|
||
"form_id": form_id, #表单id或者prepay_id
|
||
"data": push_data
|
||
}
|
||
r = requests.post(get_url,json=payload)
|
||
return r
|
||
|
||
def send_wx_xcx_message_cache(openid,template_id,form_id,push_data):
|
||
access_token = cache.get('xcx_access_token')
|
||
if access_token:#有缓存
|
||
res = send_wx_xcx_message(access_token,openid,template_id,form_id,push_data)
|
||
json_data = json.loads(res.content)
|
||
if 'errcode' in json_data: # 如果获取失败返回失败信息
|
||
if json_data['errcode'] == 40001:
|
||
restoken = get_wechat_xcx_access_token_url()
|
||
json_data1 = json.loads(restoken.content)
|
||
access_token1 = json_data1['access_token']
|
||
res1 = send_wx_xcx_message(access_token1, openid, template_id, form_id, push_data)
|
||
json_data2 = json.loads(res1.content)
|
||
if 'errcode' in json_data2 and json_data2['errcode'] !=0:
|
||
logger.error("微信小程序发送消息服务错误,用户openid:%s,template_id:%s,form_id:%s,data:%s,微信返回错误信息:%s"%(openid,template_id,form_id,push_data,json_data2))
|
||
return False
|
||
cache.set('xcx_access_token', access_token,7000)
|
||
return True
|
||
else:#无缓存
|
||
restoken = get_wechat_xcx_access_token_url()
|
||
json_data1 = json.loads(restoken.content)
|
||
access_token1 = json_data1['access_token']
|
||
res1 = send_wx_xcx_message(access_token1, openid, template_id, form_id, push_data)
|
||
json_data2 = json.loads(res1.content)
|
||
if 'errcode' in json_data2 and json_data2['errcode'] !=0:
|
||
logger.error("微信小程序发送消息服务错误,用户openid:%s,template_id:%s,form_id:%s,data:%s,微信返回错误信息:%s" % (
|
||
openid, template_id, form_id, push_data, json_data2))
|
||
return False
|
||
cache.set('xcx_access_token', access_token,7000)
|
||
return True
|
||
# ================================================= #
|
||
# ************** 微信公众号app授权登录 view ************** #
|
||
# ================================================= #
|
||
|
||
#通过 code 换取 access_token 和 openid,code为前端获取后传过来得
|
||
"""
|
||
正确返回
|
||
{
|
||
"access_token": "ACCESS_TOKEN", 有效期2小时
|
||
"expires_in": 7200,
|
||
"refresh_token": "REFRESH_TOKEN",有效期30天
|
||
"openid": "OPENID",
|
||
"scope": "SCOPE",
|
||
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
|
||
}
|
||
错误返回
|
||
{
|
||
"errcode": 40029,
|
||
"errmsg": "invalid code"
|
||
}
|
||
"""
|
||
def get_wechat_access_token_url(code):
|
||
api_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type=authorization_code"
|
||
get_url = api_url.format(WX_GZPT_APPID,WX_GZPT_APPSECRET,code)
|
||
r = requests.get(get_url)
|
||
return r
|
||
|
||
#获取微信用户公开个人信息
|
||
"""
|
||
正确返回
|
||
{
|
||
"openid": "OPENID",
|
||
"nickname": "NICKNAME",
|
||
"sex": 1,
|
||
"province": "PROVINCE",
|
||
"city": "CITY",
|
||
"country": "COUNTRY",
|
||
"headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
|
||
"privilege": ["PRIVILEGE1", "PRIVILEGE2"],
|
||
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
|
||
}
|
||
错误返回
|
||
{
|
||
"errcode": 40003,
|
||
"errmsg": "invalid openid"
|
||
}
|
||
"""
|
||
def getWxUserInfo(access_token,openid):
|
||
api_url = "https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang=zh_CN"
|
||
get_url = api_url.format(access_token,openid)
|
||
r = requests.get(get_url)
|
||
return r
|
||
|
||
#检验授权凭证access_token 是否有效
|
||
"""
|
||
有效返回
|
||
{
|
||
"errcode": 0,
|
||
"errmsg": "ok"
|
||
}
|
||
"""
|
||
def is_access_token_valid(access_token, openid):
|
||
api_url = "https://api.weixin.qq.com/sns/auth?access_token={0}&openid={1}"
|
||
get_url = api_url.format(access_token, openid)
|
||
r = requests.get(get_url)
|
||
return r
|
||
#通过refresh_token刷新过期的access_token
|
||
"""
|
||
有效返回
|
||
{
|
||
"access_token": "ACCESS_TOKEN",
|
||
"expires_in": 7200,
|
||
"refresh_token": "REFRESH_TOKEN",
|
||
"openid": "OPENID",
|
||
"scope": "SCOPE"
|
||
}
|
||
错误返回
|
||
{
|
||
"errcode": 40030,
|
||
"errmsg": "invalid refresh_token"
|
||
}
|
||
"""
|
||
def refresh_access_token(refresh_token):
|
||
api_url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid={0}&grant_type=refresh_token&refresh_token={1}"
|
||
get_url = api_url.format(WX_GZPT_APPID,refresh_token)
|
||
r = requests.get(get_url)
|
||
return r
|
||
|
||
#微信公众号app登录接口
|
||
class WeChatGZHLoginAPIView(APIView):
|
||
"""
|
||
post:
|
||
微信公众号登录接口
|
||
微信公众号code获取openid和access_token
|
||
"""
|
||
permission_classes = []
|
||
authentication_classes = []
|
||
|
||
def post(self, request):
|
||
jscode = get_parameter_dic(request)['code']
|
||
if not jscode:
|
||
return ErrorResponse(msg="code不能为空")
|
||
resp = get_wechat_access_token_url(jscode)
|
||
openid = ""
|
||
unionid = ""
|
||
access_token = ""
|
||
refresh_token = ""
|
||
scope = None
|
||
if resp.status_code != 200:
|
||
return ErrorResponse(msg="服务器到微信网络连接失败,请重试")
|
||
json_data =json.loads(resp.content)
|
||
if 'errcode' in json_data and json_data['errcode'] !=0:#如果获取失败返回失败信息
|
||
logger.error("微信app登录服务错误,用户提交code:%s,微信返回错误信息:%s" % (jscode, json_data))
|
||
return ErrorResponse(msg=json_data['errmsg'])
|
||
|
||
openid = json_data['openid']
|
||
access_token = json_data['access_token']
|
||
refresh_token = json_data['refresh_token']
|
||
scope = json_data['scope']
|
||
if "unionid" in json_data:
|
||
unionid = json_data['unionid']
|
||
|
||
#判断用户是否存在(根据openID判断用户是否是第一次登陆)
|
||
user = Users.objects.filter(is_active=True,oauthwxuser__gzh_openid=openid).first()
|
||
if not user:#如果不存在则提示绑定用户关系
|
||
return ErrorResponse(code=301,data={'openid':openid,'is_bind':False},msg="无此用户,请先绑定")
|
||
|
||
#返回token
|
||
resdata = XCXLoginSerializer.get_token(user)
|
||
return SuccessResponse(data=resdata,msg="success")
|
||
|
||
|
||
class WeChatGZHBindAPIView(APIView):
|
||
"""
|
||
绑定微信用户
|
||
post:
|
||
绑定微信用户
|
||
微信公众号openid、mobile(绑定手机号)、code(验证码)
|
||
"""
|
||
permission_classes = []
|
||
authentication_classes = []
|
||
|
||
def post(self,request):
|
||
openid = get_parameter_dic(request)['openid']
|
||
mobile = get_parameter_dic(request)['mobile']
|
||
code = get_parameter_dic(request)['code']
|
||
# 验证手机号是否合法
|
||
if not re.match(REGEX_MOBILE, mobile):
|
||
return ErrorResponse(msg="请输入正确手机号")
|
||
# 判断短信验证码是否正确
|
||
redis_conn = get_redis_connection('verify_codes')
|
||
send_flag = redis_conn.get('sms_%s' % mobile) # send_flag的值为bytes,需要转换成str ,send_flag.decode()
|
||
if not send_flag: # 如果取不到标记,则说明验证码过期
|
||
return ErrorResponse(msg="短信验证码已过期")
|
||
else:
|
||
if str(send_flag.decode()) != str(code):
|
||
return ErrorResponse(msg="验证码错误")
|
||
|
||
user = Users.objects.filter(is_active=True,username=mobile,identity__contains=2,oauthwxuser__isnull=True).first()
|
||
if not user:#如果不存在
|
||
return ErrorResponse(msg="无法绑定,无此用户或已绑定")
|
||
OAuthWXUser.objects.create(user=user,gzh_openid=openid)
|
||
resdata = XCXLoginSerializer.get_token(user)
|
||
return SuccessResponse(data=resdata,msg="success")
|
||
|
||
# ================================================= #
|
||
# ************** 微信公众号H5网页授权登录 view ************** #
|
||
# ================================================= #
|
||
def get_wechat_access_token_h5_url(code):
|
||
api_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type=authorization_code"
|
||
get_url = api_url.format(WX_GZH_APPID,WX_GZH_APPSECRET,code)
|
||
r = requests.get(get_url)
|
||
return r
|
||
|
||
#公众号获取js sdk调用需要的临时签名信息
|
||
class GetWeChatGZHH5JSSDKTempSignAPIView(APIView):
|
||
"""
|
||
公众号获取js sdk调用需要的临时签名信息
|
||
get:
|
||
公众号获取js sdk调用需要的临时签名信息
|
||
"""
|
||
authentication_classes = [JWTAuthentication]
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get(self, request):
|
||
url = get_parameter_dic(request)['url']#参与临时签名需要前端传递自己当前页面的url地址
|
||
temp_sign_data = WxAppPay().get_gzh_h5_js_sign(url)
|
||
if not temp_sign_data:
|
||
return ErrorResponse(msg="获取失败,请稍后再试")
|
||
return DetailResponse(data=temp_sign_data)
|
||
|
||
#接口配置:校验微信发送的验证信息
|
||
class CheckWeChatGZHH5APIView(APIView):
|
||
"""
|
||
get:
|
||
接口配置:校验微信发送的验证信息
|
||
微信会发送随机字符,校验服务器是否真实存在
|
||
"""
|
||
permission_classes = []
|
||
authentication_classes = []
|
||
|
||
def get(self, request):
|
||
signature = get_parameter_dic(request)['signature']
|
||
timestamp = get_parameter_dic(request)['timestamp']
|
||
nonce = get_parameter_dic(request)['nonce']
|
||
echostr = get_parameter_dic(request)['echostr']
|
||
token = WX_GZH_TOKEN
|
||
if not wx_h5_checkSignature(token,timestamp, nonce, signature):
|
||
return HttpResponse('fail')
|
||
return HttpResponse(echostr)
|
||
|
||
def wx_h5_checkSignature(token, timestamp, nonce, signature):
|
||
temp = [token, timestamp, nonce]
|
||
temp.sort()
|
||
res = hashlib.sha1("".join(temp).encode('utf8')).hexdigest()
|
||
return True if res == signature else False
|
||
|
||
def wx_h5_checkSignature(token, timestamp, nonce, signature):
|
||
temp = [token, timestamp, nonce]
|
||
temp.sort()
|
||
res = hashlib.sha1("".join(temp).encode('utf8')).hexdigest()
|
||
return True if res == signature else False
|
||
|
||
|
||
#微信公众号H5网页授权登录接口
|
||
class WeChatGZHH5LoginAPIView(APIView):
|
||
"""
|
||
post:
|
||
微信公众号网页授权登录接口
|
||
微信公众号code获取openid和access_token,新用户获取用户信息(昵称头像等):
|
||
引导用户访问例如如下授权链接:
|
||
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx9747c4bf89d5ce34&redirect_uri=http%3A%2F%2Fdvlyadmin.lybbn.cn%2Fapi%2Fxcx%2Fwxh5login%2F&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
|
||
也可以使用拼接
|
||
redirect_uri = parse.quote("%s/api/h5/wxh5login/"%config.DOMAIN_HOST)
|
||
#snsapi_base\snsapi_userinfo
|
||
link = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=%s#wechat_redirect"%(WX_GZH_APPID,redirect_uri,code)
|
||
"""
|
||
permission_classes = []
|
||
authentication_classes = []
|
||
|
||
def get(self, request):
|
||
jscode = get_parameter_dic(request)['code']
|
||
if not jscode:
|
||
return ErrorResponse(msg="code不能为空")
|
||
resp = get_wechat_access_token_h5_url(jscode)
|
||
openid = ""
|
||
unionid = ""
|
||
access_token = ""
|
||
refresh_token = ""
|
||
scope = None
|
||
if resp.status_code != 200:
|
||
return ErrorResponse(msg="服务器到微信网络连接失败,请重试")
|
||
json_data =json.loads(resp.content)
|
||
if 'errcode' in json_data and json_data['errcode'] !=0:#如果获取失败返回失败信息
|
||
logger.error("微信app登录服务错误,用户提交code:%s,微信返回错误信息:%s" % (jscode, json_data))
|
||
return ErrorResponse(msg=json_data['errmsg'])
|
||
|
||
openid = json_data['openid']
|
||
access_token = json_data['access_token']
|
||
refresh_token = json_data['refresh_token']
|
||
scope = json_data['scope']
|
||
if "unionid" in json_data:
|
||
unionid = json_data['unionid']
|
||
|
||
#判断用户是否存在(根据openID判断用户是否是第一次登陆)
|
||
user = Users.objects.filter(is_active=True,oauthwxuser__gzh_openid=openid).first()
|
||
if not user:#如果不存在则提示绑定用户关系
|
||
userinfo_res = getWxUserInfo(access_token, openid)
|
||
userinfo = json.loads(userinfo_res.content)
|
||
if 'errcode' in userinfo and userinfo['errcode'] != 0: # 如果获取失败返回失败信息
|
||
logger.error("微信公众号网页授权登录服务错误,用户提交code:%s,微信返回错误信息:%s" % (jscode, userinfo))
|
||
return ErrorResponse(msg=userinfo['errmsg'])
|
||
nickname = userinfo['nickname']
|
||
sex = userinfo['sex']
|
||
if "unionid" in userinfo:
|
||
unionid = userinfo['unionid']
|
||
avatar = userinfo['headimgurl']
|
||
user = Users.objects.create(username=openid,password=uuid.uuid4(),is_staff=False,is_active=True,nickname=nickname,name=nickname,avatar=avatar,gender=sex)
|
||
OAuthWXUser.objects.create(user=user, gzh_openid=openid,avatarUrl=avatar,nick=nickname,sex=sex,gzh_access_token=access_token)
|
||
|
||
#返回token
|
||
resdata = XCXLoginSerializer.get_token(user)
|
||
return SuccessResponse(data=resdata,msg="success")
|
||
|
||
# ================================================= #
|
||
# ************** 字节跳动小程序登录 view ************** #
|
||
# ================================================= #
|
||
#接口调用凭证getAccessToken,获取的access_token有效期2 小时,重复获取 access_token 会导致上次 access_token 失效。为了平滑过渡,新老 access_token 在 5 分钟内都可使用
|
||
def get_tt_access_token():
|
||
"""
|
||
正确返回信息:
|
||
{
|
||
"err_no": 0,
|
||
"err_tips": "success",
|
||
"data": {
|
||
"access_token": "0801121***********",
|
||
"expires_in": 7200
|
||
}
|
||
}
|
||
"""
|
||
api_url = "https://developer.toutiao.com/api/apps/v2/token"
|
||
headers = {'Content-Type': 'application/json'}
|
||
data = {'appid':TT_XCX_APPID,'secret':TT_XCX_APPSECRET,'grant_type':'client_credential'}
|
||
r = requests.post(url=api_url,data=json.dumps(data),headers=headers)
|
||
return r
|
||
#获取字节跳动code2Session(返回值有openid)
|
||
def get_tt_code2Session(code):
|
||
"""
|
||
code:为前端小程序通过tt.login传过来的code
|
||
小程序正确返回信息:
|
||
{
|
||
"err_no": 0,
|
||
"err_tips": "success",
|
||
"data": {
|
||
"session_key": "hZy6t19VPjFqm********",
|
||
"openid": "V3WvSshYq9******",
|
||
"anonymous_openid": "",
|
||
"unionid": "f7510d9ab***********"
|
||
}
|
||
}
|
||
"""
|
||
api_url = 'https://developer.toutiao.com/api/apps/v2/jscode2session'
|
||
headers = {'Content-Type': 'application/json'}
|
||
data = {'appid': TT_XCX_APPID, 'secret': TT_XCX_APPSECRET, 'code': code}
|
||
r = requests.post(url=api_url, data=json.dumps(data), headers=headers)
|
||
return r
|
||
|
||
#字节跳动小程序登录接口
|
||
class TTXCXLoginAPIView(APIView):
|
||
"""
|
||
post:
|
||
字节跳动小程序登录接口
|
||
字节跳动小程序code获取openid
|
||
"""
|
||
permission_classes = []
|
||
authentication_classes = []
|
||
|
||
@transaction.atomic # 开启事务,当方法执行完成以后,自动提交事务
|
||
def post(self, request):
|
||
jscode = get_parameter_dic(request)['code']
|
||
nickname = get_parameter_dic(request)['nickname']
|
||
avatar_url = get_parameter_dic(request)['avatar_url']
|
||
gender = get_parameter_dic(request)['gender']
|
||
if not jscode:
|
||
return ErrorResponse(msg="code不能为空")
|
||
resp = get_tt_code2Session(jscode)
|
||
openid = None
|
||
session_key = None
|
||
unionid = None
|
||
if resp.status_code != 200:
|
||
return ErrorResponse(msg="服务器到微信网络连接失败,请重试")
|
||
# json_data = {'errcode':0,'openid':'111','session_key':'test'}
|
||
json_data =json.loads(resp.content)
|
||
if not int(json_data['err_no']) ==0:#如果获取失败返回失败信息
|
||
print(json_data)
|
||
return ErrorResponse(msg=json_data['err_tips'])
|
||
|
||
openid = json_data['data']['openid']
|
||
session_key = json_data['data']['session_key']
|
||
|
||
if "unionid" in json_data:
|
||
unionid = json_data['data']['unionid']
|
||
|
||
# 判断用户是否存在
|
||
try:
|
||
wxuser = Users.objects.get(username=openid)
|
||
if not wxuser.is_active:
|
||
return ErrorResponse(msg="该用户已禁用,请联系管理员")
|
||
wxuser.oauthwxuser.session_key = session_key # 小写oauthwxuser 表示关联的外键
|
||
wxuser.oauthwxuser.xcx_openid = openid
|
||
wxuser.oauthwxuser.unionId = unionid
|
||
wxuser.oauthwxuser.avatarUrl = avatar_url
|
||
wxuser.oauthwxuser.sex = gender
|
||
wxuser.oauthwxuser.nick = nickname
|
||
wxuser.oauthwxuser.save()
|
||
wxuser.nickname = nickname
|
||
wxuser.avatar = avatar_url
|
||
wxuser.gender = gender
|
||
wxuser.save()
|
||
resdata = XCXLoginSerializer.get_token(wxuser)
|
||
return SuccessResponse(data=resdata, msg="success")
|
||
except Exception as e: # 新用户
|
||
with transaction.atomic():
|
||
try:
|
||
savepoint = transaction.savepoint()
|
||
user = Users()
|
||
user.username = openid
|
||
user.password = uuid.uuid4() # 先随机生成一个密码,防止别人获取openid直接被登录情况
|
||
user.identity = 2 # 用户身份2表示前端用户
|
||
user.nickname = nickname
|
||
user.name = nickname
|
||
user.avatar = avatar_url
|
||
user.save()
|
||
OAuthWXUser.objects.create(user=user, session_key=session_key, xcx_openid=openid,avatarUrl=avatar_url, sex=gender, nick=nickname)
|
||
except Exception as e:
|
||
transaction.savepoint_rollback(savepoint)
|
||
return ErrorResponse(msg=str(e))
|
||
# 清除保存点
|
||
transaction.savepoint_commit(savepoint)
|
||
resdata = XCXLoginSerializer.get_token(user)
|
||
return SuccessResponse(data=resdata, msg="success") |