最近在作 Sparrow(还在内测的一个敲好用 Mock 系统😁)的时候遇到了一个需求。Sparrow 服务器是使用 Django 2.0 编写的产品,因此本文全部的代码背景均为 Django 2.0 环境和 Python 3.6.3 语言,总体是 Vue + Django + SQLite。html
Sparrow 的操做通常都是在网页上操做,而手机客户端每每是用来同步一些简单数据的。那么这里遇到一个和日常 APP 不一样的使用场景。前端
通常来讲,一个产品的操做大可能是在手机上,那么 PC 客户端和网页版就能够经过已经登陆的移动端 APP 扫码登陆。python
而如今的状况是,Sparrow 的使用大多在网页版,那么,我须要的就是,让移动 APP 用户在网页版已经登陆的状况下免去输入用户名、密码的登陆操做,让移动 APP 用户扫描网页二维码,完成移动 APP 的登陆。数据库
##设计思路django
首先能想到的是,服务器要提供给移动 APP 能够访问的 URL(展示成二维码给 APP 扫描),这个 URL 须要包括json
那么其 URL 的大体模样就是:后端
frontend/account/quick_login?user_id=<user_id>&verification_code=<verification_code>
复制代码
验证码是从哪里来的?服务器
缘由是这样的,若是扫码登陆的 URL 永久有效,显然是不合理的,这意味着只要获得了这个 URL,任何人均可以经过这个 URL 随时登陆该用户的帐号,因此须要有验证码。app
同时,验证码须要附带生成时间,以此来达到验证码一分钟有效的 Feature。为此,设计一个外键为 user 的 Model:frontend
class QuickLoginRecord(models.Model, Dictable):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
create_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(auto_now=True)
verification_code = models.CharField(max_length=32, null=True, default='')
复制代码
经过 Django 生成的对应数据库为:
何时生成验证码,那固然是生成二维码的时候,因此,这个 URL 不是给移动端请求的,而是给前端来请求的,前端在已登陆的状况下,访问该 URL 能够直接传递 user 信息,后端经过拿到 user 信息,生成一条 QuickLoginRecord 记录。
前端访问并拿到验证码的 URL 的大体模样是:
frontend/account/request_quick_login
复制代码
那么整个流程就是(省略了细节处理):
urlpatterns = [
// ···
path('frontend/account/quick_login', AccountAction.quick_login),
path('frontend/account/request_quick_login', AccountAction.request_quick_login),
]
复制代码
class QuickLoginRecord(models.Model, Dictable):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
create_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(auto_now=True)
verification_code = models.CharField(max_length=32, null=True, default='')
复制代码
这里代码不想看的话,大概描述一下过程:
@track(AccountRequestQuickLogin)
def request_quick_login(request: HttpRequest):
if request.method != CommonData.Method.GET.value:
return HttpResponse(Response.methodInvalidResponse().toJson(), content_type='application/json')
user = request.user
r = QuickLoginRecordDao.get_record_with_user_id(user.id)
if r is not None:
r.verification_code = str(uuid.uuid1())
QuickLoginRecordDao.update_record(r)
response = Response(Success, 'Success', {'verification_code': r.verification_code})
return HttpResponse(response.toJson(), content_type='application/json')
else:
record = QuickLoginRecord()
record.user = user
record.verification_code = str(uuid.uuid1())
QuickLoginRecordDao.add_record(record)
response = Response(Success, 'Success', {'verification_code': record.verification_code})
return HttpResponse(response.toJson(), content_type='application/json')
复制代码
@track(AccountQuickLogin)
def quick_login(request: HttpRequest):
if request.method != CommonData.Method.GET.value:
return HttpResponse(Response.methodInvalidResponse().toJson(), content_type='application/json')
user_id = request.GET.get('user_id')
verification_code = request.GET.get('verification_code')
record = QuickLoginRecordDao.get_record_with_verification_code(verification_code)
if record is None:
response = Response(QuickLoginFailed, '验证码不存在或已过时', {})
return HttpResponse(response.toJson(), content_type='application/json')
now = datetime.now(timezone.utc)
offset = (now - record.update_time).seconds
if offset > 60:
response = Response(QuickLoginFailed, '验证码已过时', {})
return HttpResponse(response.toJson(), content_type='application/json')
user = AccountDao.get_user_with_id(user_id)
if user is None:
response = Response(QuickLoginFailed, '用户不存在', {})
return HttpResponse(response.toJson(), content_type='application/json')
user.backend = 'django.contrib.auth.backends.ModelBackend'
print('用户 ' + user.username + ' 尝试登陆')
auth.login(request, user)
accountInfo = User.objects.get(id=user.id)
response = Response(Success, 'Success', {'id': accountInfo.id,
'username': accountInfo.username,
'email': accountInfo.email})
return HttpResponse(response.toJson(), content_type='application/json')
复制代码
class QuickLoginRecordDao:
@staticmethod
def add_record(record):
record.save()
@staticmethod
def get_record_with_user_id(user_id):
try:
record = QuickLoginRecord.objects.get(user_id=user_id)
return record
except:
return None
@staticmethod
def update_record(record):
result = QuickLoginRecord.objects.filter(id=record.id).update(
verification_code=record.verification_code,
update_time=datetime.datetime.now())
if result > 0:
return True
else:
return False
@staticmethod
def get_record_with_verification_code(code):
try:
record = QuickLoginRecord.objects.get(verification_code=code)
return record
except:
return None
复制代码
前端的效果是这样的:
在已登陆的状态下,点击右上角的『客户端扫码登陆』按钮,弹出二维码。
动效、模态窗什么的就不过多展现代码了,只关注主流程的代码:
<p class="nav-item" v-if="account.status">
<button class="button is-primary" type="submit" @click="openModalImage">客户端扫码登陆 </button>
</p>
复制代码
openModalImage () {
const imageModal = openImageModal()
imageModal.loading = true
var baseUrl = window.location.protocol + '//' + window.location.host
request('/frontend/account/request_quick_login', {
method: 'get'
}).then((response) => {
var verificationCode = response.data.verification_code
var url = baseUrl + '/frontend/account/quick_login' + '?' +
'verification_code=' + verificationCode + '&' +
'user_id=' + this.accountInfo.id
QRCode.toDataURL(url)
.then(url => {
imageModal.imgUrl = url
imageModal.loading = false
imageModal.$children[0].active()
})
.catch(err => {
console.error(err)
})
}).catch((response) => {
notification.toast({
message: response.message,
type: 'danger',
duration: 2000
})
})
}
复制代码
iOS 代码就不展现了,就是扫码访问二维码里的 URL,再加上一些非法 URL 的判断便可。
过完整个流程后,能够感受到,相似于支付宝的扫码支付。给出一个定时刷新的二维码,供给客户端进行扫码登陆。
固然,还有能够完善的地方,好比前段在打开了二维码模态窗时,每 60 秒进行一次定时刷新。
有什么问题均可以在博文后面留言,或者微博上私信我,或者邮件我 coderfish@163.com。
博主是 iOS 妹子一枚。
但愿你们一块儿进步。
个人微博:小鱼周凌宇