djngo开发:小程序实现无感登录+权限分配
由来
最近开发一个预约系统,需要区分普通用户和工作人员。由于账号密码/短信验证
过于繁琐,因而选择记录openid实现无感登录。(基本小程序都这样操作)
同时事先在数据库中录入客户手机号,即可在用户登录时根据有无手机号来区分普通用户和工作人员。这样在项目交付时,工作人员和普通用户一样可以直接登录无感登录小程序
1. 开发思路
微信的openid是一种唯一标识用户身份的字符串
用户登录小程序,通过手机号快速验证组件
获取动态令牌code
,后端向微信服务器发送get请求并带上code
获取每个用户唯一的openid,然后记录到mysql中,并签发token。该openid就是登录小程序的唯一凭证。
2.简单实现
- 获取openid,如果通过openid查不用户,就自动新建用户,并返回token。
py
#####LoginView###########
code = request.data.get("code")
appid = appid # 微信小程序的appid
appsecret = "xxxxxxxx" # 微信小程序的密钥,登录微信公众平台即可获取
# 获取openid和session_token
querystring = {"appid":appid,"js_code":code,"secret":appsecret,"grant_type":"authorization_code"}
jscode2session = requests.get('https://api.weixin.qq.com/sns/jscode2session',params=querystring)
if not jscode2session.json().get("errcode"):
data = jscode2session.json()
########拿到openid#########
openid=data.get("openid")
#######去数据库比对,如果通过openid查到用户并且未被禁用,就新建##########
try:
user = models.UserInfo.objects.get(openid)
if user.is_deleted: # 检查用户是否被禁用
return ErrorResponse(msg='用户已被禁用,无法登录',data=data,code=302)
except models.UserInfo.DoesNotExist:
models.UserInfo.objects.create()
3.更进一步:通过手机号来区别普通用户和工作人员
openid虽然做到的唯一性验证,但是当用户数量庞大时,该如何区分用户角色:
-
一:手动在后台根据已有用户分配权限
-
二:登录时根据某一标识区分角色
方法一
显然不靠谱,因为用户至少会超过1000人,方法二
需要额外标识,显然手机号最合适。
3.1 前端获取手机号的动态令牌
小程序提供了手机号快速验证组件,方便我们获取手机号
把bindgetphonenumber
事件回调中的动态令牌code
传到开发者后台
html
<view class="title">欢迎来到广盈预约</view>
<view class="card">
<view class="button">快捷登录</view>
<button
style="opacity: 0"
class="bottom-button"
open-type="getPhoneNumber|agreePrivacyAuthorization"
bindgetphonenumber="getrealtimephonenumber"
bindagreeprivacyauthorization="handleAgreePrivacyAuthorization"
>
同意隐私协议并授权手机号注册
</button>
</view>
</view>
js
Page({
getPhoneNumber (e) { console.log(e.detail.code) // 动态令牌 }
})
注意 :如果你想获取用户手机号就必须添加用户授权《隐私保护协议》
bindagreeprivacyauthorization
,否则小程序无法上线
3.2 后端带着动态令牌去微信服务器获取手机号
简单来说就是用户登录时数据库中没有手机号对应的用户,后端就会自动建立一个账号,并分配权限为普通用户,然后直接登录。
这样的话,只需要第一次登录时获取手机号,以后登录就可以直接进入系统。
py
class RegisterView(APIView):
authentication_classes = []
permission_classes = []
def getmobile(self,appid,code):
"""获取用户的手机号"""
try:
appsecret = "cxxxxxxxxx"
querystring = {"appid":appid,"secret":appsecret,"grant_type":"client_credential"}
response = requests.get('https://api.weixin.qq.com/cgi-bin/token',params=querystring)
access_token = response.json().get("access_token")
querystring = {"access_token":access_token}
headers = {"content-type": "application/json"}
payload = {"code":code}
mobile =requests.post(f"https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={access_token}",json=payload,headers=headers)
return mobile.json().get('phone_info').get('phoneNumber')
except Exception as e:
return None
def post(self, request):
unionid =request.data.get("unionid")
nickname =request.data.get("nickname")
openid = request.data.get("openid")
code = request.data.get("code")
appid = request.data.get("appid")
mobile = self.getmobile(appid=appid,code=code)
if not mobile:
return ErrorResponse(msg="手机号获取失败")
defaults = {
"openid":openid,
"unionid":unionid,
"mobile":mobile,
"nickname":nickname,
}
"""这条语句将查找一个符合mobile=mobile条件的记录,如果找到就更新 defaults中的字段 ,否则就创建
注意: 查询的条件必须是唯一的,否则会造成多条数据返回而报错,这个逻辑同 get() 函数。
注意: 使用的字段,没有唯一的约束,并发的调用这个方法可能会导致多条相同的值插入。
"""
models.UserInfo.objects.update_or_create(mobile=mobile,defaults=defaults)
models.User_GZH.objects.get_or_create(unionid=unionid, defaults={'unionid':unionid})
return DetailResponse()