赞
踩
本文将介绍采用 Django 开发微信小程序后端,通过将用户模块进行重构,并采用JWT来进行用户认证,来解决以下问题:
希望通过此文可以帮助大家快速搭建小程序的后端服务。
在 requirements.txt 里面添加以下内容
certifi==2019.11.28 chardet==3.0.4 Click==7.0 coreapi==2.3.3 coreapi-cli==1.0.9 coreschema==0.0.4 Django==2.2.10 django-filter==2.2.0 djangorestframework==3.11.0 djangorestframework-simplejwt==4.4.0 idna==2.9 itypes==1.1.0 Jinja2==2.11.1 Markdown==3.2.1 MarkupSafe==1.1.1 Pillow==7.0.0 psycopg2-binary==2.8.4 pyasn1==0.4.8 pycryptodome==3.9.6 Pygments==2.5.2 PyJWT==1.7.1 python-weixin==0.5.0 pytz==2019.3 requests==2.23.0 rsa==4.0 simplejson==3.17.0 six==1.14.0 sqlparse==0.3.0 uritemplate==3.0.1 urllib3==1.25.8 xmltodict==0.12.0
然后运行 pip install -r requirements.txt 进行安装
Django 默认的用户模块是 django.contrib.auth.models 内的AbstractUser,在完成以下配置以前不要对数据进行 migrate,否则会导致用户模块重构失败。
假设我们将重构的用户模块放置在 wx 应用内,命名为 WxUser。
python3 manage.py startapp wx
在 INSTALLED_APPS 里面注册 wx 应用,通过 AUTH_USER_MODEL 指定用户模块。
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ..., 'rest_framework', 'django_filters', 'wx', ] # 微信小程序 AppID 和 AppSecret WX_APP_ID = '***' WX_APP_SECRET = '*****' # 指定用户模块 AUTH_USER_MODEL = 'wx.WxUser' MIDDLEWARE = [ 'wx.middlewares.MiddlewareHead', # 允许进行跨域访问 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework_simplejwt.authentication.JWTAuthentication', # 通过 JWT 进行用户验证,验证过程需要访问数据库 'rest_framework_simplejwt.authentication.JWTTokenUserAuthentication', # 通过 JWT 的 Token 进行用户验证,验证过程不需要访问数据库 ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), 'PAGE_SIZE': 10, 'DEFAULT_FILTER_BACKENDS': ( 'rest_framework.filters.SearchFilter', 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.OrderingFilter' ), 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' } JWT_SIGNING_KEY = '******' SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(days=15), 'REFRESH_TOKEN_LIFETIME': timedelta(days=15), 'ROTATE_REFRESH_TOKENS': False, 'BLACKLIST_AFTER_ROTATION': True, 'ALGORITHM': 'HS256', 'SIGNING_KEY': JWT_SIGNING_KEY, 'VERIFYING_KEY': None, 'AUTH_HEADER_TYPES': ('Bearer',), 'USER_ID_FIELD': 'id', 'USER_ID_CLAIM': 'user_id', 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), 'TOKEN_TYPE_CLAIM': 'token_type', 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', 'SLIDING_TOKEN_LIFETIME': timedelta(days=15), 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=15), }
对 AbstractUser 进行重写
import hashlib from django.conf import settings from django.contrib.auth.models import AbstractUser from django.db import models from django.utils.translation import gettext_lazy as _ class WxUser(AbstractUser): """ 自定义的用户模块 """ # 微信同步的用户信息 openid = models.CharField( verbose_name=_('微信OpenID'), help_text=_('微信OpenID'), max_length=100, unique=True, null=True, blank=True) avatar_url = models.URLField( verbose_name=_('头像'), help_text=_('头像'), null=True, blank=True) nick_name = models.CharField( verbose_name=_('昵称'), help_text=_('昵称'), max_length=100, null=True, blank=True, unique=True) gender = models.SmallIntegerField( verbose_name=_('性别'), help_text=_('性别'), choices=((1, '男'), (2, '女'), (0, '未知')), null=True, blank=True) language = models.CharField( verbose_name=_('语言'), help_text=_('语言'), max_length=100, null=True, blank=True) city = models.CharField( verbose_name=_('城市'), help_text=_('城市'), max_length=200, null=True, blank=True) province = models.CharField( verbose_name=_('省份'), help_text=_('省份'), max_length=200, null=True, blank=True) country = models.CharField( verbose_name=_('国家'), help_text=_('国家'), max_length=200, null=True, blank=True) date_of_birth = models.DateField(verbose_name=_('出生日期'), help_text=_('出生日期'), null=True, blank=True) desc = models.TextField(verbose_name=_('描述'), help_text=_('描述'), max_length=2000, null=True, blank=True) def create_username_password(self): if not self.username and not self.password and self.openid: key = settings.SECRET_KEY self.username = hashlib.pbkdf2_hmac( "sha256", getattr(self, 'openid').encode(encoding='utf-8'), key.encode(encoding='utf-8'), 10).hex() self.password = hashlib.pbkdf2_hmac( "sha256", self.username.encode(), getattr(self, 'openid').encode(encoding='utf-8'), 10).hex() def save(self, *args, **kwargs): self.create_username_password() super().save(*args, **kwargs) class Meta(AbstractUser.Meta): swappable = 'AUTH_USER_MODEL'
create_username_password() 方法将自动通过openid创建用户名 username 和密码 password。
from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.utils.translation import gettext_lazy as _ from wx.models import * @admin.register(WxUser) class WxUserAdmin(UserAdmin): readonly_fields = ( 'last_login', 'date_joined', 'nick_name', 'city', 'province', 'country', 'china_district', 'avatar_url' ) search_fields = [ 'username', 'openid', 'email', 'first_name', 'last_name', 'nick_name' ] fieldsets = ( (_('基础信息'), {'fields': ('username', 'password', 'openid')}), (_('个人信息'), {'fields': ( 'nick_name', 'first_name', 'last_name', 'avatar_url', 'gender', 'date_of_birth', 'desc')}), (_('联络信息'), {'fields': ('email',)}), (_('地址信息'), {'fields': ('city', 'province', 'country', 'china_district')}), (_('登录信息'), {'fields': ('last_login', 'date_joined')}), )
通过以上步骤,我们已经将 Django 的用户模块进行了重构。
在 wx 模块下面添加 serializers.py
from rest_framework import serializers from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from .models import * class JfwTokenObtainPairSerializer(TokenObtainPairSerializer): @classmethod def get_token(cls, user): token = super(JfwTokenObtainPairSerializer, cls).get_token(user) token['username'] = 'wx_{0}'.format(user.username) return token class WxUserSerializer(serializers.ModelSerializer): class Meta: model = WxUser fields = ['id', 'nick_name', 'avatar_url', 'gender']
在 wx 模块添加 views.py
import json import logging from django.forms import model_to_dict from rest_framework.response import Response from rest_framework.status import * from rest_framework.views import APIView from weixin import WXAPPAPI from weixin.oauth2 import OAuth2AuthExchangeError from .serializers import * logger = logging.getLogger('django') def create_or_update_user_info(openid, user_info): """ 创建或者更新用户信息 :param openid: 微信 openid :param user_info: 微信用户信息 :return: 返回用户对象 """ if openid: if user_info: user, created = WxUser.objects.update_or_create(openid=openid, defaults=user_info) else: user, created = WxUser.objects.get_or_create(openid=openid) return user return None class WxLoginView(APIView): """ post: 微信登录接口 """ authentication_classes = [] permission_classes = [] fields = { 'nick_name': 'nickName', 'gender': 'gender', 'language': 'language', 'city': 'city', 'province': 'province', 'country': 'country', 'avatar_url': 'avatarUrl', } def post(self, request): user_info = dict() code = request.data.get('code') logger.info("Code: {0}".format(code)) user_info_raw = request.data.get('user_info', {}) if isinstance(user_info_raw, str): user_info_raw = json.loads(user_info_raw) if not isinstance(user_info_raw, dict): user_info_raw = {} logger.info("user_info: {0}".format(user_info_raw)) if code: api = WXAPPAPI(appid=settings.WX_APP_ID, app_secret=settings.WX_APP_SECRET) try: session_info = api.exchange_code_for_session_key(code=code) except OAuth2AuthExchangeError: session_info = None if session_info: openid = session_info.get('openid', None) if openid: if user_info_raw: for k, v in self.fields.items(): user_info[k] = user_info_raw.get(v) user = create_or_update_user_info(openid, user_info) if user: token = JfwTokenObtainPairSerializer.get_token(user).access_token return Response( { 'jwt': str(token), 'user': model_to_dict( user, fields=[ 'company', 'restaurant', 'current_role', 'is_owner', 'is_client', 'is_manager' ]) }, status=HTTP_200_OK) return Response({'jwt': None, 'user': {}}, status=HTTP_204_NO_CONTENT)
在 wx 模块下面添加 middlewares.py
from django.utils.deprecation import MiddlewareMixin
class MiddlewareHead(MiddlewareMixin):
@staticmethod
def process_response(request, response):
if request:
response['Access-Control-Allow-Origin'] = '*'
return response
在 wx 模块下面添加 urls.py
from django.urls import re_path from rest_framework.documentation import include_docs_urls from rest_framework.urlpatterns import format_suffix_patterns from wx.apps import WxConfig from .views import * API_TITLE = 'API Documents' API_DESCRIPTION = 'API Information' app_name = WxConfig.name urlpatterns = format_suffix_patterns([ re_path(r'^wx_login/$', WxLoginView.as_view(), name='wx_login'), # 其他接口 ])
在项目目录的 urls.py 添加以下内容
from django.conf import settings from django.contrib import admin from django.views.static import serve from django.urls import re_path, include from rest_framework.documentation import include_docs_urls API_TITLE = 'API Documents' API_DESCRIPTION = 'API Information' urlpatterns = [ re_path(r'^admin/', admin.site.urls), re_path(r'^api/', include('wx.urls', namespace='api')), re_path(r'^api-auth/', include('rest_framework.urls')), re_path(r'^docs/', include_docs_urls(title=API_TITLE, description=API_DESCRIPTION)), ]
用户登录的接口已经配置完成,请求的时候通过 POST 方法传递 code 和 user_info 参数。
在 uniapp 里面可以用以下方式进行调用
export default { data() { return { logining: false }; }, onLoad() {}, methods: { wxLogin(e) { const that = this; that.logining = true; let userInfo = e.detail.userInfo; uni.login({ provider:"weixin", success:(login_res => { let code = login_res.code; uni.getUserInfo({ success(info_res) { console.log(info_res) console.log(urls.login) uni.request({ url: 'your.domain/api/wx_login/', method:"POST", header: {'content-type': 'application/x-www-form-urlencoded'}, data:{ code : code, user_info : info_res.rawData }, success(res) { if(res.statusCode == 200){ console.log(' 登录成功') that.$store.commit('login',userInfo); // uni.setStorageSync("userInfo",userInfo); // uni.setStorageSync("skey", res.data.data); }else{ console.log('登录失败') console.log(res) } }, fail(error) { console.log(error) } }) uni.hideLoading() uni.navigateBack() } }) }) }) } } };
基于 rest_framework.generics 里面的 ListAPIView, RetrieveAPIView, GenericAPIView, RetrieveUpdateAPIView, CreateAPIView, ListCreateAPIView, RetrieveUpdateDestroyAPIView 可以快速开发接口。
from rest_framework.generics import ListAPIView, RetrieveAPIView, GenericAPIView, RetrieveUpdateAPIView, \ CreateAPIView, ListCreateAPIView, RetrieveUpdateDestroyAPIView class GameTokenView(GenericAPIView): """ :get 获取用户当前的思维点 :post 添加和减少思维点 """ serializer_class = GameTokenSerializer def get(self, request): user_id = request.user.id # 从 request中直接获取用户 data = get_current_game_point(user_id) return Response(data) def post(self, request): user_id = request.user.id raw_data = request.data raw_data['user_id'] = user_id serializer = self.get_serializer(data=raw_data) try: serializer.is_valid(raise_exception=True) serializer.create(serializer.validated_data) status = 200 except ValidationError: status = 400 data = get_current_game_point(user_id) return Response(data=data, status=status)
如果有的接口开发时,你不想走默认的权限配置。你可以自己定义里面的 authentication_classes 和 permission_classes。
class PublicGenericAPIView(GenericAPIView):
authentication_classes = () # 在此重新定义认证方式
permission_classes = () # 在此重新定义权限
def get(self, request):
return Response()
def post(self, request):
return Response()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。