写在前面:整体是实现小程序的登录和注册,并接收验证码进行校验。
一、创建项目
django-admin startproject wxTest
二、注册app,进入到wxTest目录中
python manage.py startapp api
三、提前安装djangorestframework包
四、在settings.py中添加rest_framework
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'api.apps.ApiConfig'
]
# 用户信息存入mysql的wx数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'wx', # 数据库名字可更改
'PORT':'3306',
'USER':'root',
'PASSWORD':'123456',
'HOST':'localhost'
}
}
# 如果需要用到django-redis,需要添加
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS":{"max_connections":100}
# "PASSWORD": "yoursecret",
},
},
}
# ---->设置上传文件的目录和外部访问的路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL = '/media/'
五、wxTest\urls.py中
from django.contrib import admin
from django.urls import path,include
from django.conf.urls import url
from django.views.static import serve
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
# 添加这行——————允许所有的media文件被访问
url(r'media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
]
# 或者以下方法
# 添加这行——————允许所有的media文件被访问
# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
六、wxTest\api\models.py中
from django.db import models
class UserInfo(models.Model):
id = models.AutoField(primary_key=True)
# unique唯一
phone = models.CharField(max_length=11,verbose_name='手机号',unique=True)
# 用户登录后唯一标识
# null=True, 表示数据库的该字段可以为空
# blank=True,表示你的表单填写该字段的时候可以不填
token = models.CharField(max_length=64,verbose_name='用户Token',null=True,blank=True)
终端输入:python manage.py makemigrations(生成迁移文件)
终端输入:python manage.py migrate(将结构变化应用到数据库)
七、wxTest\api\views.py中
import os
import re
import random
import uuid
import chardet
from django.http import JsonResponse
from rest_framework.response import Response
from rest_framework.views import APIView
from django_redis import get_redis_connection
from . import models
from django.conf import settings
class DelView(APIView):
'''
删除本地图片功能
'''
def get(self, request, *args, **kwargs):
# 传入的文件路径列表http://127.0.0.1:8000/media/1f75e5dfcf258de22566433799ce67a1.jpg
imagePath = request.query_params.get('image', '')
MEDIA_ROOT = settings.MEDIA_ROOT
try:
imagePath = imagePath.split("/")[-1]
file_path = os.path.join(os.getcwd(),MEDIA_ROOT + imagePath)
file_path = file_path.replace('/','\\')
os.remove(file_path)
return Response({"status": True, "message": "图片删除成功"})
except Exception as e:
return Response({"status": False, "message": "图片删除失败:"+str(e)})
# 传图功能
class UploadView(APIView):
'''
上传图片功能
'''
def post(self, request, *args, **kwargs):
# 传入的文件路径列表
imageList = request.FILES.get('image', '')
# imageList = request.FILES['image']
# 文件类型,可以判断如果不是图片格式则不上传
file_type = os.path.splitext(imageList.name)[1]
if not imageList:
print("上传文件不能为空")
return JsonResponse({"status": False, "message": "上传文件不能为空"})
file_format = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp', '.tif', '.tiff']
if file_type.lower() not in file_format:
print("上传文件类型非图片格式")
return JsonResponse({"status": False, "message": "上传文件类型非图片格式"})
# 获得一个唯一名字:uuid +hash
new_name = self.get_random_str()
# 组合成文件新名字
new_file_name = new_name + file_type
MEDIA_ROOT = settings.MEDIA_ROOT
if not os.path.exists(MEDIA_ROOT):
os.makedirs(MEDIA_ROOT)
# 文件路径
file_path = os.path.join(MEDIA_ROOT, new_file_name)
# 将网络地址返回给小程序
return_img = "http://127.0.0.1:8000/media/" + str(new_file_name)
# 开始写入本地磁盘
try:
f = open(file_path, 'wb')
# 多次写入
for i in imageList.chunks():
f.write(i)
f.close()
print("上传图片成功")
# 不能用Response,result.data.status获取不到,result.data是string类型,不是object
# return Response({"status": True, "message": "上传图片成功","data": file_path})
# 如果返回的是JsonResponse,小程序要进行解析console.log(JSON.parse(res.data))
return JsonResponse({"status": True, "message": "上传图片成功", "data": return_img})
except Exception as e:
print("上传图片失败:", e)
return JsonResponse({"status": False, "message": "上传图片失败:" + str(e)})
# 获取UUID
def get_random_str(self):
import hashlib
import uuid
"""获取UUID"""
uuid_val = uuid.uuid4() # UUID('c1127b61-0714-4eab-8fb0-0138b6a84b4c')
uuid_str = str(uuid_val).encode('utf-8') # b'c1127b61-0714-4eab-8fb0-0138b6a84b4c'
md5 = hashlib.md5() # <md5 _hashlib.HASH object @ 0x00000253D50DCDB0>
md5.update(uuid_str)
return md5.hexdigest() # 'fb8dfbe57e508bc4ade13f1ac4e7212b'
# 退出
class LogoutView(APIView):
'''
退出登录功能
'''
def post(self, request, *args, **kwargs):
# 传入的token
token = request.data.get('token', '')
phone = request.data.get('phone', '')
if not token:
return Response({"status": False, "message": "退出登录失败"})
# 去数据库里
data_token = models.UserInfo.objects.filter(token=token).first()
data_token = data_token.token
if token != data_token:
return Response({"status": False, "message": "退出登录失败"})
conn = get_redis_connection()
# 成功退出时,清掉redis中的验证码,以便用户能登录成功后马上退出再次重新获取验证码
conn.delete(phone)
return Response({"status": True, "message": "已成功退出"})
class PublishView(APIView):
'''
发布心情功能
'''
def post(self, request, *args, **kwargs):
info = request.data.get('info','')
print("***",info)
return Response({"status": True, "message": "接收成功"})
class LoginView(APIView):
'''
# 登录功能,返回status
'''
def post(self, request, *args, **kwargs):
'''
1.校验手机号
2.校验验证码,redis获取验证码
2.1 无验证码
2.2 有验证码,但是输入错误
2.3 有验证码,输入正确
3.获取数据库中用户信息(创建或者获取)
4.将一些信息返回给小程序
'''
# 传入的phone
phone = request.data.get('phone', '')
if not phone:
return Response({"status": False, "message": "手机号不能为空"})
if not phone.isdecimal():
return Response({"status": False, "message": "手机号格式错误"})
if len(phone) != 11:
return Response({"status": False, "message": "手机号长度错误"})
patt = r'^1[3-9]\d{9}$'
getPhoneNum = re.match(patt, phone)
if not getPhoneNum:
return Response({"status": False, "message": "手机号格式错误"})
phone = getPhoneNum.group()
# 传入的验证码
code = request.data.get('code', '')
if not code:
return Response({"status": False, "message": "验证码不能为空"})
if len(code) != 4 or not code.isdecimal():
return Response({"status": False, "message": "验证码为4位数字"})
conn = get_redis_connection()
# redis库里存的验证码,和用户输入的验证码进行比对,redis取值是bytes类型,因为设置了60秒过期,会有取不到的情况
redis_code = conn.get(phone)
if not redis_code:
# 如果查不到,无效
return Response({"status": False, "message": "验证码无效"})
# 查看原有变量的编码类型
ret = chardet.detect(redis_code)
redis_code = redis_code.decode(ret['encoding'])
# 如果不等输入有误
if redis_code != code:
return Response({"status": False, "message": "验证码输入错误"})
# 获取数据库中用户信息(创建或者获取)
# 第一个方法
# users = models.UserInfo.objects.filter(phone=phone).first()
# print("users:",users)
# 如果没有用户,则创建
# if not users:
# models.UserInfo.objects.create(phone=phone,token=str(uuid.uuid4()))
# else:
# users.token = str(uuid.uuid4())
# users.save()
# 第二个方法flag:如果能查到返回False,查不到则创建返回True
user_obj, flag = models.UserInfo.objects.get_or_create(phone=phone)
user_obj.token = str(uuid.uuid4())
user_obj.save()
if flag:
return Response({"status": True, "message": "注册成功", "data": {"token": user_obj.token, "phone": phone}})
return Response({"status": True, "message": "登录成功", "data": {"token": user_obj.token, "phone": phone}})
# return Response({"status": True,"message": "登录成功"})
class MessageView(APIView):
def get(self, request, *args, **kwargs):
'''
接收短信验证码功能,返回四位数字fourNum
'''
# 1、获取手机号
getPhoneNum = request.query_params.get('phone', '')
# 2、手机号格式验证,否则浏览器直接输入http://127.0.0.1:8000/api/message/?phone=12000000000,也可以获取验证码
# 正则判断
patt = r'^1[3-9]\d{9}$'
getPhoneNum = re.match(patt, getPhoneNum)
if not getPhoneNum:
print("手机号格式错误")
return Response("手机号格式错误")
phone = getPhoneNum.group()
print("传递的手机号为:", phone)
# 防止用户连续点击获取验证码,加了一个判断或者防止获取完验证码一分钟内退出页面再次重新进入页面,重新获取
conn = get_redis_connection()
redis_code = conn.get(phone)
if redis_code:
# 如果查到,则代表刚刚获取过不能再次获取
return Response({"status": False, "message": "已获取过验证码"})
# 模拟发送成功和失败,只有发送成功才生成验证码,进而存入redis
# 如果发送失败,直接返回提示
simulation = random.choice([True, False])
if not simulation:
print("验证码发送失败")
return Response({"status": False, "message": "验证码发送失败"})
# 3、生成四位随机验证码
random_code = random.randint(1000, 9999)
print("生成验证码为:", random_code)
# 4、验证码发送到手机,购买服务器进行短信发送,腾讯云(可将此功能单独封装)
# 4.1 注册腾讯云,开通腾讯云短信
# 4.2 创建应用 SDK APPID=XXXXX
# 4.3 申请签名(个人可通过公众号)获取到ID 和名称
# 4.4 申请腾讯云API获得SecretID和Secretkey
# 4.5 参考以下文档:Tencent Cloud SDK 3.0 for Python
# https://cloud.tencent.com/document/product/382/38778
# https://github.com/TencentCloud/tencentcloud-sdk-python/blob/master/examples/sms/v20190711/SendSms.py
# 5、保留验证码+手机号(设置超时时间过期60秒,可以用redis)
# 5.1 搭建redis服务器(云redis)
# 5.2 自行搭建本地
# import redis
# pool = redis.ConnectionPool(host='',port=6379)
# r = redis.Redis(connection_pool=pool)
# r.set(phone,random_code,ex=60)
# 5.3 使用django-redis,首先settings中添加配置CACHES
# 需要打开redis
conn.set(phone, random_code, ex=60)
return Response({"status": True, "message": "验证码发送成功", "fourNum": random_code})
八、在wxTest\api中添加urls.py
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.LoginView.as_view()), # 登录
path('message/', views.MessageView.as_view()), # 获取验证码
path('logout/', views.LogoutView.as_view()), # 退出登录
path('upload/', views.UploadView.as_view()), # 传图功能
path('publish/', views.PublishView.as_view()), # 发布心情功能
path('del/', views.DelView.as_view()), # 删除上传的图片
]
九、运行服务
python manage.py runserver
十、打开网址login:http://127.0.0.1:8000/api/login/
打开网址message:http://127.0.0.1:8000/api/message/
打开网址:http://127.0.0.1:8000/api/logout/
打开网址:http://127.0.0.1:8000/api/upload/
如果报以下错请参考:https://blog.csdn.net/z564359805/article/details/115550473
OrderedDict‘ object has no attribute ‘register‘
以上设置完毕,但微信小程序wx.request的url中输入http://127.0.0.1:8000/api/login/,会报错:
http://127.0.0.1:8000 不在以下 request 合法域名列表中,请参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html
https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html
关闭不校验合法域名功能即可: