本demo后端采用DRF框架(djangorestframeworf)框架,有对DRF不熟悉的可以先看看我之前的文章 分类 Django Restframework 下的文章
前端使用的是uni-app ,因为发现uni-app是在太好用了。就随便写了登录的例子
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-95ml1M7q-1611724545430)(/media/editor/20200824093026_20200824093037022174.jpg)]
创建项目数据
安装DRF pip install djangorestframework
创建新项目并新建app为users
这里我们直接继承django自带的user模型
from django.db import models
from django.contrib.auth.models import AbstractUser
from shortuuidfield import ShortUUIDField
# Create your models here.
class UserProfile(AbstractUser):
uid = ShortUUIDField(primary_key=True, verbose_name='用户表主键')
openid = models.CharField(unique=True,max_length=200, blank=True, null=True, verbose_name='开放id')
telephone = models.CharField(unique=True, max_length=11, null=True, verbose_name="手机号码")
username = models.CharField(unique=True, max_length=20, null=True, verbose_name='用户名')
nickname = models.CharField(max_length=128, null=True, verbose_name='昵称')
avatar = models.CharField(max_length=200, verbose_name='头像链接', default='https://dmall.wouldmissyou.com/20200822112506.jpg')
gender = models.CharField(max_length=20, default='未知', verbose_name="性别")
province = models.CharField(max_length=128, verbose_name='所在地区', null=True)
address = models.CharField(max_length=128, verbose_name='详细地址', null=True)
is_active = models.BooleanField(default=True, verbose_name="是否可用")
is_staff = models.BooleanField(default=False, verbose_name="是否是员工")
date_joined = models.DateTimeField(auto_now_add=True, verbose_name='加入时间')
然后在setting.py文件制定我们的模型
#setting.py
AUTH_USER_MODEL = 'users.UserProfile'
然后进行数据迁移
序列化用户模型
在users app中,新建serializers.py文件进行模型序列化,我们要想前端返回我们的user数据,除了密码之外的我们都要返回,具体返回的内容还是看自己实际项目中的需求,我们就直接把密码排除点,其他的都返回
from rest_framework import serializers
from .models import UserProfile
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
exclude = ['password']
使用jwt进行认证
DRF中内置了好几种认证方法,但都不是我们所需要的,我们这里要使用JWT的方式进行认证,所以先安装一个包
pip install pyjwt
然后在users APP中新建一个py文件作为我们处理认证的文件,authentications.py
#authentications.py
import jwt
import time
from django.conf import settings
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework import exceptions
from django.contrib.auth import get_user_model
from jwt.exceptions import ExpiredSignatureError
User = get_user_model()
def generate_jwt(user):
expire_time = time.time() + 60 * 60 * 24 * 7
return jwt.encode({"userid": user.pk, "exp": expire_time}, key=settings.SECRET_KEY).decode('utf-8')
class JWTAuthentication(BaseAuthentication):
keyword = 'JWT'
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != self.keyword.lower().encode():
return None
if len(auth) == 1:
msg = "不可用的JWT请求头!"
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = '不可用的JWT请求头!JWT Token中间不应该有空格!'
raise exceptions.AuthenticationFailed(msg)
try:
jwt_token = auth[1]
jwt_info = jwt.decode(jwt_token, settings.SECRET_KEY)
userid = jwt_info.get('userid')
try:
# 绑定当前user到request对象上
user = User.objects.get(pk=userid)
return user, jwt_token
except:
msg = '用户不存在!'
raise exceptions.AuthenticationFailed(msg)
except ExpiredSignatureError:
msg = "JWT Token已过期!"
raise exceptions.AuthenticationFailed(msg)
这个认证方式也是修改于DRF的内置方法BaseAuthentication,可以进源码看一下。
视图
关于微信认证的方式大家可以去看一下官方文档,这里就不阐述了
首先到微信小程序后台获取自己的appkey和appid
在setting.py中配置上
#setting.py
APP_ID = '填写你自己的小程序appid'
APP_KEY = '填写你自己的小程序appkey'
# 配置使用JWT方式认证
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ['users.authentications.JWTAuthentication']
}
#users/views.py
from rest_framework.views import APIView
from .serializers import UserProfileSerializer
from .authentications import generate_jwt
from rest_framework import status
from rest_framework.response import Response
from django.conf import settings
import requests, json
from django.contrib.auth import get_user_model
User = get_user_model()
from django.utils.timezone import now
# Create your views here.
class LoginView(APIView):
def post(self, request):
"""进行code验证"""
code = request.data.get('code')
if not code:
return Response({'message': "缺少code"}, status=status.HTTP_400_BAD_REQUEST)
url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code" \
.format(settings.APP_ID, settings.APP_KEY, code)
r = requests.get(url)
res = json.loads(r.text)
openid = res['openid'] if 'openid' in res else None
if not openid:
return Response({"message":"微信调用失败"}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
# 判断是否是第一次登陆
try:
user = User.objects.get(openid=openid)
user.last_login = now()
# print(user.last_login)
user.save()
except Exception:
# 微信用户第一次登陆,新建用户
nickname = request.data.get('nickName')
gender = request.data.get('gender')
avatar = request.data.get('avatarUrl')
province = request.data.get('province')+request.data.get('city')
user = User.objects.create(nickname=nickname, province=province, gender=gender, avatar=avatar, openid=openid,
password=openid)
serializer = UserProfileSerializer(user)
token = generate_jwt(user)
return Response({"user": serializer.data, "token": token, 'status':status.HTTP_202_ACCEPTED})
然后在配置路由
path('login/', views.LoginView.as_view(), name='login'),
至此,后台部分配置完毕
前端部分
我这边为了后期方便开发,直接封装了一个简单的request.js
const baseURL = 'http://127.0.0.1:8000/';
const http = (options) => {
return new Promise((resolve, reject) => {
uni.showLoading({
title: '加载中...',
mask: options.load || false // 默认遮罩出现可以继续操作
});
try{
uni.request({
url: (options.baseURL || baseURL) + options.url,
method: options.method || 'POST', // 默认为POST请求
data: options.data, //请求超时在manifest.json配置
header: {
'token': uni.getStorageSync('token'),
'Content-Type': options.header == 'form' ? 'application/x-www-form-urlencoded' : 'application/json'
},
success: res => {
resolve(res.data)
},
fail: (err) => {
reject(err.data);
console.log(err);
uni.showToast({
title: '请检查网络连接',
icon: 'none'
})
/*错误码处理
let code = err.data.code;
switch (code) {
case 1000:
break;
default:
break;
} */
},
complete: () => {
uni.hideLoading();
}
});
}catch(e){
uni.hideLoading();
uni.showToast({
title: '服务端异常',
icon: 'none'
})
}
})
}
export default http
然后在main.js中声明一下
import http from "@/utils/http.js"
Vue.prototype.$HTTP = http
然后编写Login.vue
<template>
<view>
<!-- #ifdef MP-WEIXIN -->
<view>
<view>
<view class='header'>
<image :src='userInfo.avatar'></image>
<text>{{userInfo.nickname}}</text>
</view>
<view class='content'>
<view>申请获取以下权限</view>
<text>获得你的公开信息(昵称,头像、地区等)</text>
</view>
<button class='bottom' type='primary' open-type="getUserInfo" withCredentials="true" lang="zh_CN" @getuserinfo="wxGetUserInfo">
授权登录
</button>
<button class='bottom' type='warn' @click="logoutBtn">退出登录</button>
</view>
</view>
<!-- #endif -->
</view>
</template>
<script>
export default {
data() {
return {
userInfo: {},
isLogin: false //默认为true
};
},
mounted(){
const getuserInfo = uni.getStorageSync("userInfo")
this.userInfo = getuserInfo
},
methods: {
wxGetUserInfo() {
let _this = this;
uni.getUserInfo({
provider: 'weixin',
lang:"zh_CN",
success: function(infoRes) {
_this.data = infoRes.userInfo
// console.log(_this.data)
uni.showLoading({
title: "登录中"
})
uni.login({
provider: "weixin",
success: (loginres) => {
if (!loginres.code) {
console.log("登录失败,请再次点击~~");
return;
}
_this.data['code'] = loginres.code;
const params = _this.data
// console.log(params)
_this.$HTTP({
method: "POST",
url: "login/",
data: params
}).then((res) => {
console.log(res)
if (res.status=='202') {
uni.setStorageSync('token', 'JWT '+res.token)
uni.setStorageSync('userInfo', res.user)
_this.userInfo = uni.getStorageSync('userInfo')
}
})
}
})
}
})
},
logoutBtn(){
// 先判断用户是否登录
const userStatus = uni.getStorageSync('token')
if(!userStatus){
uni.showToast({
icon:"none",
title:"您未登录"
})
}else{
uni.removeStorageSync('token')
this.userInfo = {}
}
}
},
}
</script>
<style>
.header {
padding: 90rpx;
border-bottom: 1px solid #ccc;
text-align: center;
width: 100%;
line-height: 60rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.header image {
width: 200rpx;
height: 200rpx;
}
.content {
margin-left: 50rpx;
margin-bottom: 90rpx;
}
.content text {
display: block;
color: #9d9d9d;
margin-top: 40rpx;
}
.bottom {
border-radius: 80rpx;
margin: 70rpx 50rpx;
font-size: 35rpx;
}
</style>
最后生成小程序看看效果吧