微信支付,微信web登录功能优化

xsign
nineven 3 years ago
parent dd1fb29ba0
commit c765658fb5
  1. 24
      fir_client/src/components/user/FirUserOrders.vue
  2. 41
      fir_client/src/components/user/FirUserProfileInfo.vue
  3. 29
      fir_client/src/restful/index.js
  4. 6
      fir_ser/api/urls.py
  5. 31
      fir_ser/api/views/login_wx.py
  6. 28
      fir_ser/api/views/order.py
  7. 6
      fir_ser/common/base/magic.py
  8. 64
      fir_ser/common/libs/mp/wechat.py

@ -247,7 +247,7 @@
<script> <script>
import {my_order} from "@/restful"; import {my_order, order_sync} from "@/restful";
import {format_choices, getUserInfoFun} from '@/utils' import {format_choices, getUserInfoFun} from '@/utils'
import VueQr from 'vue-qr'; import VueQr from 'vue-qr';
@ -297,17 +297,37 @@ export default {
methods: 'PUT', data: {order_number: order.order_number, act: 'cancel'}, methods: 'PUT', data: {order_number: order.order_number, act: 'cancel'},
}) })
}, },
loop_get_order_info(order_number, c_count = 1) {
order_sync(data => {
c_count += 1;
if (c_count > 30) {
return;
}
if (data.code === 1000) {
this.$message.success(data.msg);
this.get_data_from_tabname();
this.wx_pay = false;
} else if (data.code === 1001) {
return this.loop_get_order_info(order_number, c_count)
}
}, {
"methods": "POST",
data: {"order_number": order_number}
})
},
goto_pay(order) { goto_pay(order) {
this.current_order_info = order; this.current_order_info = order;
my_order(res => { my_order(res => {
if (res.code === 1000) { if (res.code === 1000) {
let data = res.data; let data = res.data;
if (data && data.url) { if (data && data.url) {
if (data && data.type === 'WX') { if (data && data.type === 'WX') {
this.pay_code_url = data.url; this.pay_code_url = data.url;
this.wx_pay = true; this.wx_pay = true;
this.$message.success("请用微信扫描支付"); this.$message.success("请用微信扫描支付");
this.loop_get_order_info(order.order_number)
} else if (data && data.type === 'ALI') { } else if (data && data.type === 'ALI') {
let pay_url = data.url; let pay_url = data.url;
if (pay_url && pay_url.length > 10) { if (pay_url && pay_url.length > 10) {

@ -11,6 +11,22 @@
微信授权登录用户信息 微信授权登录用户信息
</span> </span>
<el-tag style="margin-bottom: 10px">授权用户将可以使用微信扫码直接登录</el-tag> <el-tag style="margin-bottom: 10px">授权用户将可以使用微信扫码直接登录</el-tag>
<el-button plain style="float: right;margin-top: 10px" @click="wx_web_login_fun"> 点击扫码同步微信用户信息</el-button>
<el-dialog
:visible.sync="show_web_visible"
append-to-body
center
title="手机微信扫码更新个人信息"
width="360px">
<vue-qr ref="qr"
:correctLevel="qrinfo.correctLevel"
:logoCornerRadius="qrinfo.logoCornerRadius"
:logoScale="qrinfo.logoScale"
:margin="qrinfo.margin"
:size="300" :text="wx_web_login_url"
class="code-wrap">
</vue-qr>
</el-dialog>
<el-table <el-table
:data="wx_user_list" :data="wx_user_list"
border border
@ -286,11 +302,15 @@
</template> </template>
<script> <script>
import {changeInfoFun, getAuthcTokenFun, userinfos, wxBindFun, wxLoginFun, wxutils} from '@/restful' import {changeInfoFun, getAuthcTokenFun, userinfos, wxBindFun, wxLoginFun, wxutils, wxWebScanFun} from '@/restful'
import {deepCopy, geetest} from "@/utils"; import {deepCopy, geetest} from "@/utils";
import VueQr from 'vue-qr';
export default { export default {
name: "FirUserProfileInfo", name: "FirUserProfileInfo",
components: {
VueQr
},
data() { data() {
return { return {
userinfo: { userinfo: {
@ -311,10 +331,29 @@ export default {
wx_visible: false, wx_visible: false,
loop_flag: false, loop_flag: false,
show_wx_visible: false, show_wx_visible: false,
show_web_visible: false,
qrinfo: {
logoScale: 0.3,
logoCornerRadius: 12,
correctLevel: 3,
margin: 20
},
wx_web_login_url: '',
wx_user_list: [], wx_user_list: [],
pagination: {"currentPage": 1, "total": 0, "pagesize": 999}, pagination: {"currentPage": 1, "total": 0, "pagesize": 999},
} }
}, methods: { }, methods: {
wx_web_login_fun() {
wxWebScanFun(data => {
if (data.code === 1000) {
this.show_web_visible = true;
this.wx_web_login_url = data.data;
} else {
this.$message.error("信息获取失败")
}
}, {methods: 'GET'})
},
delete_wx_u(wx_user_info) { delete_wx_u(wx_user_info) {
this.$prompt(`此操作将导致微信用户 “${wx_user_info.nickname}” 无法通过扫码登录, 是否继续删除?`, '警告', { this.$prompt(`此操作将导致微信用户 “${wx_user_info.nickname}” 无法通过扫码登录, 是否继续删除?`, '警告', {
confirmButtonText: '确定', confirmButtonText: '确定',

@ -254,6 +254,20 @@ export function wxBindFun(callBack, params, load = true) {
); );
} }
/**微信web扫码授权 */
export function wxWebScanFun(callBack, params, load = true) {
getData(
params.methods,
USERSEVER + '/mp.web.login',
params.data,
data => {
callBack(data);
},
load,
true,
true
);
}
/**获取验证token */ /**获取验证token */
export function getAuthTokenFun(callBack, params, load = true) { export function getAuthTokenFun(callBack, params, load = true) {
@ -560,6 +574,21 @@ export function my_order(callBack, params, load = true) {
); );
} }
/**用户订单支付同步 */
export function order_sync(callBack, params, load = true) {
getData(
params.methods,
USERSEVER + '/orders.sync',
params.data,
data => {
callBack(data);
},
load,
true,
true
);
}
/**实名认证 */ /**实名认证 */
export function user_certification(callBack, params, load = true) { export function user_certification(callBack, params, load = true) {
getData( getData(

@ -21,9 +21,9 @@ from api.views.domain import DomainCnameView, DomainInfoView
from api.views.download import ShortDownloadView, InstallView, DownloadView from api.views.download import ShortDownloadView, InstallView, DownloadView
from api.views.login import LoginView, UserInfoView, RegistView, AuthorizationView, ChangeAuthorizationView, \ from api.views.login import LoginView, UserInfoView, RegistView, AuthorizationView, ChangeAuthorizationView, \
UserApiTokenView, CertificationView, ChangeInfoView UserApiTokenView, CertificationView, ChangeInfoView
from api.views.login_wx import WeChatLoginView, WeChatLoginCheckView, WeChatBindView from api.views.login_wx import WeChatLoginView, WeChatLoginCheckView, WeChatBindView, WeChatWebLoginView
from api.views.logout import LogoutView from api.views.logout import LogoutView
from api.views.order import PriceView, OrderView, PaySuccess from api.views.order import PriceView, OrderView, PaySuccess, OrderSyncView
from api.views.report import ReportView from api.views.report import ReportView
from api.views.storage import StorageView, CleanStorageView from api.views.storage import StorageView, CleanStorageView
from api.views.thirdlogin import ValidWxChatToken, ThirdWxAccount from api.views.thirdlogin import ValidWxChatToken, ThirdWxAccount
@ -55,11 +55,13 @@ urlpatterns = [
re_path("^qrcode$", AppsQrcodeShowView.as_view()), re_path("^qrcode$", AppsQrcodeShowView.as_view()),
re_path("^package_prices$", PriceView.as_view()), re_path("^package_prices$", PriceView.as_view()),
re_path("^orders$", OrderView.as_view()), re_path("^orders$", OrderView.as_view()),
re_path("^orders.sync$", OrderSyncView.as_view()),
re_path("^certification$", CertificationView.as_view()), re_path("^certification$", CertificationView.as_view()),
re_path(r"^pay_success/(?P<name>\w+)$", PaySuccess.as_view()), re_path(r"^pay_success/(?P<name>\w+)$", PaySuccess.as_view()),
re_path("^cname_domain$", DomainCnameView.as_view()), re_path("^cname_domain$", DomainCnameView.as_view()),
re_path("^domain_info$", DomainInfoView.as_view()), re_path("^domain_info$", DomainInfoView.as_view()),
re_path("^mp.weixin$", ValidWxChatToken.as_view()), re_path("^mp.weixin$", ValidWxChatToken.as_view()),
re_path("^mp.web.login$", WeChatWebLoginView.as_view(), name="mp.web.login"),
re_path("^third.wx.login$", WeChatLoginView.as_view()), re_path("^third.wx.login$", WeChatLoginView.as_view()),
re_path("^third.wx.bind$", WeChatBindView.as_view()), re_path("^third.wx.bind$", WeChatBindView.as_view()),
re_path("^third.wx.sync$", WeChatLoginCheckView.as_view()), re_path("^third.wx.sync$", WeChatLoginCheckView.as_view()),

@ -3,7 +3,7 @@ import logging
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from api.models import UserInfo from api.models import UserInfo, ThirdWeChatUserInfo
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from api.utils.serializer import UserInfoSerializer from api.utils.serializer import UserInfoSerializer
from api.utils.utils import set_user_token from api.utils.utils import set_user_token
@ -12,7 +12,7 @@ from common.base.magic import get_pending_result
from common.core.auth import ExpiringTokenAuthentication from common.core.auth import ExpiringTokenAuthentication
from common.core.sysconfig import Config from common.core.sysconfig import Config
from common.core.throttle import VisitRegister1Throttle, VisitRegister2Throttle from common.core.throttle import VisitRegister1Throttle, VisitRegister2Throttle
from common.libs.mp.wechat import make_wx_login_qrcode, show_qrcode_url from common.libs.mp.wechat import make_wx_login_qrcode, show_qrcode_url, WxWebLogin
from common.utils.caches import set_wx_ticket_login_info_cache, get_wx_ticket_login_info_cache from common.utils.caches import set_wx_ticket_login_info_cache, get_wx_ticket_login_info_cache
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -91,3 +91,30 @@ class WeChatLoginCheckView(APIView):
else: else:
ret.code = 1006 ret.code = 1006
return Response(ret.dict) return Response(ret.dict)
class WeChatWebLoginView(APIView):
def get(self, request):
logger.error(request.query_params)
wx_login_obj = WxWebLogin()
ret = BaseResponse()
code = request.query_params.get('code')
if code:
wx_login_obj.get_wx_token(code)
wx_user_info = wx_login_obj.get_user_info()
logger.info(f'{wx_user_info}')
wx_user_info = {
'openid': wx_user_info.get('openid'),
'nickname': wx_user_info.get('nickname'),
'sex': wx_user_info.get('sex'),
'subscribe_time': wx_user_info.get('subscribe_time', 0),
'head_img_url': wx_user_info.get('headimgurl', ''),
'address': f"{wx_user_info.get('country')}-{wx_user_info.get('province')}-{wx_user_info.get('city')}",
'subscribe': wx_user_info.get('subscribe', 0),
}
logger.info(f'{wx_user_info}')
ThirdWeChatUserInfo.objects.filter(openid=wx_user_info.get('openid')).update(**wx_user_info)
return Response('更新成功')
ret.data = wx_login_obj.make_auth_uri()
return Response(ret.dict)

@ -14,6 +14,7 @@ from api.utils.modelutils import PageNumber
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from api.utils.serializer import PriceSerializer, OrdersSerializer from api.utils.serializer import PriceSerializer, OrdersSerializer
from common.base.baseutils import get_order_num, get_choices_dict from common.base.baseutils import get_order_num, get_choices_dict
from common.base.magic import get_pending_result
from common.core.auth import ExpiringTokenAuthentication from common.core.auth import ExpiringTokenAuthentication
from common.libs.pay.util import get_pay_obj_form_name, get_enable_pay_choices, get_payment_type from common.libs.pay.util import get_pay_obj_form_name, get_enable_pay_choices, get_payment_type
from common.utils.caches import update_order_status from common.utils.caches import update_order_status
@ -50,7 +51,6 @@ class OrderView(APIView):
pay_id = request.data.get("pay_id", None) pay_id = request.data.get("pay_id", None)
order_number = request.data.get("order_number", None) order_number = request.data.get("order_number", None)
if (price_id and pay_id) or order_number: if (price_id and pay_id) or order_number:
price_obj = Price.objects.filter(name=price_id).first()
order_obj = Order.objects.filter(user_id=request.user, order_number=order_number).first() order_obj = Order.objects.filter(user_id=request.user, order_number=order_number).first()
if order_obj and order_obj.status in [1, 2] and order_obj.payment_name: if order_obj and order_obj.status in [1, 2] and order_obj.payment_name:
pay_obj = get_pay_obj_form_name(order_obj.payment_name) pay_obj = get_pay_obj_form_name(order_obj.payment_name)
@ -60,6 +60,8 @@ class OrderView(APIView):
res.data = pay_url res.data = pay_url
logger.info(f"{request.user} 下单成功 {res.dict}") logger.info(f"{request.user} 下单成功 {res.dict}")
return Response(res.dict) return Response(res.dict)
price_obj = Price.objects.filter(name=price_id).first()
if price_obj: if price_obj:
try: try:
order_number = get_order_num() order_number = get_order_num()
@ -117,6 +119,30 @@ class OrderView(APIView):
return Response(res.dict) return Response(res.dict)
def get_order_obj(user_obj, order_number):
return Order.objects.filter(user_id=user_obj, order_number=order_number).first()
def except_result(result, *args, **kwargs):
if result and result.status in [0, 4, 5, 6]:
return True
class OrderSyncView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ]
def post(self, request):
res = BaseResponse()
order_number = request.data.get("order_number", None)
if order_number:
status, result = get_pending_result(get_order_obj, except_result, order_number=order_number,
locker_key=order_number, user_obj=request.user)
if not status and result:
res.code = 1001
res.msg = result.get_status_display()
return Response(res.dict)
class PriceView(APIView): class PriceView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ] authentication_classes = [ExpiringTokenAuthentication, ]

@ -65,7 +65,7 @@ def call_function_try_attempts(try_attempts=3, sleep_time=2, failed_callback=Non
return decorator return decorator
def get_pending_result(func, expect_func, timeout=10, sleep_time=3, *args, **kwargs): def get_pending_result(func, expect_func, timeout=30, sleep_time=3, *args, **kwargs):
try: try:
with cache.lock("%s_%s" % ('get_pending_result', kwargs.get('locker_key')), timeout=timeout + sleep_time): with cache.lock("%s_%s" % ('get_pending_result', kwargs.get('locker_key')), timeout=timeout + sleep_time):
count = 1 count = 1
@ -76,8 +76,8 @@ def get_pending_result(func, expect_func, timeout=10, sleep_time=3, *args, **kwa
return True, result return True, result
time.sleep(sleep_time) time.sleep(sleep_time)
if timeout < count * sleep_time: if timeout < count * sleep_time:
return False, 'timeout' return False, result
count += 1 count += 1
except Exception as e: except Exception as e:
logger.warning(f'get pending result exception: {e}') logger.warning(f'get pending result exception: {e}')
return False, 'timeout' return False, result

@ -5,14 +5,17 @@
# date: 2021/9/6 # date: 2021/9/6
import json import json
import logging import logging
import urllib
from hashlib import sha1 from hashlib import sha1
import requests import requests
from django.urls import reverse
from common.base.baseutils import get_format_time from common.base.baseutils import get_format_time
from common.cache.storage import WxTokenCache from common.cache.storage import WxTokenCache
from common.core.sysconfig import Config from common.core.sysconfig import Config
from common.libs.mp.utils import WxMsgCryptBase from common.libs.mp.utils import WxMsgCryptBase
from common.libs.storage.localApi import LocalStorage
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -401,3 +404,64 @@ class WxTemplateMsg(object):
}, },
} }
return self.send_msg(to_user, msg_id, content_data) return self.send_msg(to_user, msg_id, content_data)
class WxWebLogin(object):
"""
通过网页授权获取用户授权信息公众号已经无法获取用户的昵称头像等隐私信息
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
"""
def __init__(self):
self.openid = None
self.access_token = None
auth_info = Config.THIRDLOGINCONF.get('auth')
self.app_id = auth_info.get('app_id')
self.app_secret = auth_info.get('app_secret')
def make_auth_uri(self):
"""
第一步 用户通过微信客户端打开该URI
:return:
"""
# url = 'https://app.hehelucky.cn/api/v1/fir/server/wxweb' # 该url是前端页面,用户微信跳转,后端页面也行
local_storage = LocalStorage(**Config.IOS_PMFILE_DOWNLOAD_DOMAIN)
url = f'{local_storage.get_base_url()}{reverse("mp.web.login")}'
encode_url = urllib.parse.quote(url, safe='/', encoding=None, errors=None)
code_url = f'https://open.weixin.qq.com/connect/oauth2/authorize?appid={self.app_id}&redirect_uri={encode_url}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect'
return code_url
def get_wx_token(self, code):
"""
第二步 获取授权之后微信会携带code并且自动跳转,然后通过code获取授权token自测该token只能获取该用户信息无法获取其他用户信息
:param code:
:return:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}
"""
t_url = f'https://api.weixin.qq.com/sns/oauth2/access_token?appid={self.app_id}&secret={self.app_secret}&code={code}&grant_type=authorization_code'
req = requests.get(t_url)
if req.status_code == 200:
logger.info(f"get access token {req.status_code} {req.text}")
auth_info = req.json()
self.access_token = auth_info.get('access_token')
self.openid = auth_info.get('openid')
return auth_info
return {}
def get_user_info(self):
"""
第三步 通过token获取用户信息
:return:
"""
t_url = f'https://api.weixin.qq.com/sns/userinfo?access_token={self.access_token}&openid={self.openid}&lang=zh_CN'
req = requests.get(t_url)
if req.status_code == 200:
req.encoding = 'utf-8'
return req.json()
return {}

Loading…
Cancel
Save