luffy项目表模型与接口跨域问题解决

后台主页模块接口

根据原型图分析出来
# 首页轮播图接口
# 首页推荐课程接口
#1 创建一个app,写轮播图表
	-python ../../manage.py startapp home

路由分发

luffy_api/urls.pyluffy_api/urls.py

将不同app的路由通过主路由分发到子路由
from django.urls import path, re_path, include
urlpatterns = [
# 路由分发 127.0.0.1:8080/api/v1/home
 path('api/v1/home/', include('home.urls'))
]

编写子路由

home/urls.py

from django.urls import path
from home import views
from rest_framework.routers import SimpleRouter
from .views import BannerView

router = SimpleRouter()
router.register('banner', BannerView, 'banner')  # 第一个是路径,视图函数,反向解析用的别名
urlpatterns = [
    path('', views.IndexView.as_view()),
]
urlpatterns += router.urls

编写主页轮播图模型需要继承自己配置的公共模型表BaseModel

home/model.py

from django.db import models

# 编写首页轮播图轮播图模型

from utils.common_model import BaseModel


class Banner(BaseModel):
    # 图片地址,图片名,图片介绍,link地址
    title = models.CharField(max_length=16, unique=True, verbose_name='名称')
    image = models.ImageField(upload_to='banner', verbose_name='图片')
    link = models.CharField(max_length=64, verbose_name='跳转链接')  # /course/
    info = models.TextField(verbose_name='详情')  # 也可以用详情表,宽高出处

    class Meta:
        db_table = 'luffy_banner'
        verbose_name_plural = '轮播图表'

    def __str__(self):
        return self.title

将公共字段抽出

common_model.py

from django.db import models
from django.contrib.auth.models import AbstractUser

class BaseModel(models.Model):
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间')
    is_delete = models.BooleanField(default=False, verbose_name='是否删除')  # 软删除,不真正的删除数据,而使用字段控制
    is_show = models.BooleanField(default=True, verbose_name='是否上架')
    orders = models.IntegerField(verbose_name='优先级')
 # 如果表迁移,这个表就会生成,咱们不能再数据库生成这个表,就需要加这句
    class Meta:
        abstract = True  # 表示该表是个虚拟表不在数据库生成只用做继承使用

迁移两条命令4 迁移两条命令

python manage.py makemigrations
python manage.py migrate

轮播图视图编写

home下的view.py

from .serializers import BannerSerializer
from utils.common_mixin import CommonListMixin


class BannerView(GenericViewSet, CommonListMixin):
    # 过滤没有被删除的,并且可以显示的,按orders做排序
    queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')
    serializer_class = BannerSerializer

编写轮播图序列化类

serializer.py

from rest_framework.serializers import ModelSerializer
from .models import Banner


class BannerSerializer(ModelSerializer):
    class Meta:
        model = Banner
        fields = ['title', 'image', 'link']

由于返回格式不是我们想要的所有需要重写list方法

common_mixin.py

from rest_framework.mixins import ListModelMixin
from utils.response import APIResponse
class CommonListModelMixin(ListModelMixin):
    def list(self, request, *args, **kwargs):
        res = super().list(request, *args, **kwargs)
        return APIResponse(result=res.data)

后台管理

5 录入数据 simpleui

-下载,注册app,国际化
pip install django-simpleui
-创建超级用户
python manage.py createsuperuser
-录入数据注册admin轮播图

跨域问题详解,前后端打通

# 前后端交互会存在跨域问题

# 跨域问题出现的原因?
-同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,
如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源
策略基础之上的,浏览器只是针对同源策略的一种实现
请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同.
比如:我在本地上的域名是127.0.0.1:8000,请求另外一个域名:127.0.0.1:8001一段数据
浏览器上就会报错,这个就是同源策略的保护,如果浏览器对javascript没有同源策略的保护,
那么一些重要的机密网站将会很危险
# 解决跨域问题
	-1 前端代理
    -2 nginx代理
    -3 cors解决跨域
    
# cors:跨域资源共享,后端技术,核心就是在响应头中加入数据,允许浏览器接受数据
	CORS需要浏览器和服务器同时支持,IE浏览器不能低于IE10
# CORS基本流程
    浏览器将CORS请求分成两类:
        -简单请求(simple request)
        -非简单请求(not-so-simple request)
		
		
# 简单请求:
    浏览器发出CORS简单请求,只需要在头信息之中增加一个Access-Control-Allow-Origin字段
    
# 非简单请求
    浏览器发出CORS非简单请求,会在正式通信之前,先发送一个options请求,称为”预检”请求。
    浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,如果运行,再发真正的请求
    
# 什么是简单请求,什么是非简单请求
	-满足下面两种情况,就是简单请求
    	-1 请求方法是以下三种方法之一:
            HEAD
            GET
            POST
        -2 HTTP的请求头信息不超出以下几种字段:
            Accept
            Accept-Language
            Content-Language
            Last-Event-ID
            Content-Type:只限于三个值application/x-www-form-urlencoded
,multipart/form-data、text/plain

 # 解决跨域,使用cors技术,在响应中写东西:如果自己写,需要写个中间件,每个请求都会走,在process_response中写入下面的代码即可
def test(request):
    print(request.method)
    # 如果自己写,需要写个中间件,每个请求都会走,在process_response中写入下面的代码即可
    # 解决简单请求
    res=HttpResponse('ok')
    res['Access-Control-Allow-Origin']='*'
    # 解决非简单请求
    if request.method=='OPTIONS':
        res['Access-Control-Allow-Headers'] = 'Content-Type'
    return res

# 第三方模块,解决了这个问题,只需要集成进来,使用即可---》djagno
	-第一步:安装django-cors-headers
    -第二步:注册app
'corsheaders'
    -第三步:中间件加入
'corsheaders.middleware.CorsMiddleware'
    -第四步:配置文件配置
# 允许所有域
CORS_ORIGIN_ALLOW_ALL = True
# 允许所有请求方式
CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    'VIEW',
)
# 允许请求头,需要什么请求头就加什么
CORS_ALLOW_HEADERS = (
    'XMLHttpRequest',
    'X_FILENAME',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
    'Pragma',
    'token'
)

自定义全局配置轮播图显示两张改配置文件

# 轮播图显示条数
# 1创建公共配置文件commom_settings在内部写数据
BANNER_COUNT = 2


# 2在配置文件中导入公共配置文件
from .common_settings import *

# 3在视图中导入使用
from django.conf import settings


class BannerView(GenericViewSet, CommonListMixin):
    # 过滤没有被删除的,并且可以显示的,按orders做排序
    queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
    serializer_class = BannerSerializer

登陆接口

  • 1.编写登陆视图自动获取路透视图函数继承viewsett(ViewSetMixin, views.APIView)使用action函数装饰, @action(methods=['POST'], detail=False)将函数的方法装饰到自己写的方法上此时执行该方法的路由为http://127.0.0.1:8000/api/v1/user/user/login/
    1. 编写序列化类将登陆校验和token签发写在序列化钩子函数中视图代码精简,序列化类写好之后在视图函数中实例化,产生对象之后调校验数据方法此时会执行数据自身校验--->钩子校验
    1. 序列化类钩子中写正则判断是走哪个方法校验用户身份,校验成功手动签发token并返回用户名和头像
  • 两坑执行数据校验时会执行字段本身的校验,username字段数据中有唯一代码无法执行到钩子函数中需要重写字段的校验置为空,头像字段是对象无法直接返回需要转字符串并拼接路径返回前端使用

user/views.py

from .serializer import UserSerializer, UserMobileSerializer, RegisterSerializer

from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from utils.response import APIResponse
from .models import UserInfo
from rest_framework.exceptions import APIException
from libs.send_message import getcode, send_mobile
import re
from django.core.cache import cache

"""登陆功能"""


class UserView(ViewSet):
    login_serializer_class = None  # 可以直接写序列化类

    # 在使用时需要先加括号获取序列化类后再加括号get_login_serializer_class(data=request.data)做了一步转化
    def get_login_serializer_class(self, *args, **kwargs):  # 这是获取序列化类的方法
        login_serializer_class = self.get_serializer()
        return login_serializer_class(*args, **kwargs)

    # 封装登陆功能
    def common_login(self, request):
        ser = self.get_login_serializer_class(data=request.data)  # data是校验的数据
        ser.is_valid(raise_exception=True)  # 执行字段校验和钩子函数
        token = ser.context.get('token')
        username = ser.context.get('username')
        icon = ser.context.get('icon')  # 头像
        return APIResponse(token=token, username=username, icon=icon)  # {code:100,msg ='成功',token:11232}

    # 在使用时需要先加括号获取序列化类后再加括号get_serializer()(data=request.data)
    def get_serializer(self):
        if self.action == 'login':
            return UserSerializer
        elif self.action == 'mobile_login':
            return UserMobileSerializer

    # 多方式登陆
    @action(methods=['POST'], detail=False)
    def login(self, request):
        return self.common_login(request)

    # 查询手机号是否注册
    @action(methods=['get'], detail=False)
    def getmobile(self, request):
        mobile = request.query_params.get('mobile')
        try:
            user = UserInfo.objects.get(mobile=mobile)
            return APIResponse(msg='手机号已存在')
        except Exception as e:
            raise APIException('手机号不存在')

    # 手机短信
    @action(methods=['post'], detail=False)
    def send_sms(self, request):
        mobile = request.data.get('mobile')  # 1.将手机号放在body体中进入后端
        if re.match(r'^1[3|5|8|9][0-9]{9}$', mobile):  # 2.正则手机号如果不合法抛异常
            code = getcode()  # 3.获取随机验证码
            cache.set('sms_code_%s' % mobile, code)  # 4.将验证码保存以便后续使用利用手机号区分验证码是哪个用户的
            res = send_mobile(mobile, code)  # 5.发送随机验证码短信之后如果返回了true则返回前端发送成功
            if res:
                return APIResponse(msg='发送短信成功')
            raise APIException('发送短信失败')
        raise APIException('Illegal mobile number')

    # 手机号登陆
    @action(methods=['POST'], detail=False)
    def mobile_login(self, request):
        return self.common_login(request)

    # 注册功能
    @action(methods=['POST'], detail=False)
    def register(self, request):
        ser = RegisterSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        ser.save()
        return APIResponse(msg='注册成功,欢迎使用菠萝牛逼系统您的帐号默认为当前手机号%s' % request.data.get('mobile'))
        # 写的复杂

user/urls.py

from rest_framework.routers import SimpleRouter
from .views import UserView

router = SimpleRouter()
router.register('user', UserView, 'user')  # 第一个是路径,视图函数,反向解析用的别名
urlpatterns = [

]
urlpatterns += router.urls

user/serializer.py

from .models import UserInfo
from rest_framework import serializers
import re
from rest_framework.exceptions import APIException
from django.core.cache import cache
from utils.BaseSerializer import BaseLoginSerializer


# 不做序列化不做反序列化,只做校验多方式登陆
class UserSerializer(BaseLoginSerializer):
    # 从model中反射的关系写了unique所以需要重写否则字段自身的校验无法通过
    username = serializers.CharField()

    class Meta:
        model = UserInfo
        fields = ['username', 'password']

    # 隐藏的属性和方法
    def _get_user(self, attrs):
        # attrs是校验过后的数据:字段自己的和局部钩子的规则
        # 1.通过手机邮箱或者用户名获取用户对象
        # 2.校验密码是否正确如果正确则返回当前用户否则抛异常
        username = attrs.get('username')
        password = attrs.get('password')
        # 使用正则验证username
        if re.match(r'^1[3|5|8|9][0-9]{9}$', username):  # 正则匹配用户名区分走哪个渠道登陆
            user = UserInfo.objects.filter(mobile=username).first()
        elif re.match('^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$', username):
            user = UserInfo.objects.filter(email=username).first()
        else:
            user = UserInfo.objects.filter(username=username).first()
        if user and user.check_password(password):
            return user
        else:
            raise APIException('用户名或者密码错误')

    # 全局钩子执行
    def validate(self, attrs):
        # 1.获取用户
        # 2.签发token
        # 3.将token放到序列化类中利用context上下文管理
        # 4.拼接头像路径然后返回视图使用
        user = self._get_user(attrs)
        token = self._get_token(user)  # 如果user是None则无法签发,签发拆到公共功能里
        self.context['token'] = token
        self.context['username'] = user.username
        print(type(user.icon))  # icon 是对象
        self.context['icon'] = 'http://127.0.0.1:8000/media/' + str(user.icon)
        return attrs


# 手机号登陆验证
class UserMobileSerializer(BaseLoginSerializer):
    # 由于用户表中重写手机号唯一没有code字段所以需要重写这两个字段
    mobile = serializers.CharField()
    code = serializers.CharField()

    class Meta:
        model = UserInfo
        fields = ['mobile', 'code']

    # 隐藏的属性和方法
    def _get_user(self, attrs):
        # attrs是校验过后的数据:字段自己的和局部钩子的规则
        # 1.通过手机邮箱或者用户名获取用户对象
        # 2.校验密码是否正确如果正确则返回当前用户否则抛异常
        mobile = attrs.get('mobile')
        code = attrs.get('code')
        # 校验code是否正确
        old_code = cache.get('sms_code_%s' % mobile)
        cache.set('sms_code_%s' % mobile, '')  # 验证码用过要清除
        if code == old_code or code == '666666':  # 开发万能验证码
            user = UserInfo.objects.filter(mobile=mobile).first()
            return user
        raise APIException('验证码错误')

    # 全局钩子执行
    def validate(self, attrs):
        # 1.获取用户
        # 2.签发token
        # 3.将token放到序列化类中利用context上下文管理
        # 4.拼接头像路径然后返回视图使用
        user = self._get_user(attrs)
        token = self._get_token(user)  # 如果user是None则无法签发
        self.context['token'] = token
        self.context['username'] = user.username
        print(type(user.icon))  # icon 是对象
        self.context['icon'] = 'http://127.0.0.1:8000/media/' + str(user.icon)
        return attrs


# 用户注册序列化
class RegisterSerializer(serializers.ModelSerializer):
    # 1.由于表中没有code重写code字段
    # 2.写好序列化字段写的字段必须传不传报错
    # 3.局部钩子校验正则
    # 4.全局钩子函数校验验证码是否正确
    # 5.数据正确返回数据
    # 6.重写create方法密码加密否则密码不加密需要自行加密且登陆时会有问题
    # 7.校验过的数据后调save方法---->create保存数据库
    code = serializers.CharField()

    class Meta:
        model = UserInfo
        fields = ['mobile', 'password', 'code']
    def validate(self, attrs):
        print(attrs)
        code = attrs.get('code')
        mobile = attrs.get('mobile')
        # 校验code是否正确
        old_code = cache.get('sms_code_%s' % mobile)
        cache.set('sms_code_%s' % mobile, '')  # 验证码用过要清除
        if code == old_code or code == '666666':  # 开发万能验证码
            attrs.pop('code')  # 剔除不需要的数据
            attrs['username'] = mobile
            return attrs
        raise APIException('验证码已失效,请重新获取')

    def create(self, validated_data):
        # UserInfo.objects.create_user(**validated_data)  如果不修改员工状态则可以直接**创建
        user = UserInfo.objects.create_user(username=validated_data.get('username'),
                                            password=validated_data.get('password'),
                                            mobile=validated_data.get('mobile'),
                                            is_staff=True
                                            )
        return user

utils/BaseSerializer.py

from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
from rest_framework.exceptions import APIException

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


# 封装签发token🐔类,由于钩子有获取用户无法进行封装
class BaseLoginSerializer(serializers.ModelSerializer):
    def _get_token(self, user):
        # 手动签发token 如果出错django错会被捕获使用jwt模块的方法
        try:
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return token
        except Exception as e:
            raise APIException(str(e))

封装短信功能

# 1.在libs中创建包send_message
# 2.创建自己的配置文件send_msg.py文件
"""send_msg.py"""
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
# 导入对应产品模块的client models。
from tencentcloud.sms.v20210111 import sms_client, models

# 导入可选配置类
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile

import random
from . import settings

"""随机获取验证码"""


def getcode(num=settings.CODE_NUM):
    code = ''
    for i in range(num):
        random_num = random.randint(0, 9)
        code += str(random_num)
    return code


"""发送短信函数"""


def send_mobile(mobile, code):
    try:
        # 必要步骤:
        # 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
        # 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
        # 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
        # 以免泄露密钥对危及你的财产安全。
        # SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi
        cred = credential.Credential(settings.SECRET_ID, settings.SECRET_KEY)
        httpProfile = HttpProfile()
        httpProfile.reqMethod = "POST"  # post请求(默认为post请求)
        httpProfile.reqTimeout = 30  # 请求超时时间,单位为秒(默认60秒)
        httpProfile.endpoint = "sms.tencentcloudapi.com"  # 指定接入地域域名(默认就近接入)
        clientProfile = ClientProfile()
        clientProfile.signMethod = "TC3-HMAC-SHA256"  # 指定签名算法
        clientProfile.language = "en-US"
        clientProfile.httpProfile = httpProfile
        client = sms_client.SmsClient(cred, "ap-guangzhou", clientProfile)
        req = models.SendSmsRequest()
        # 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666
        # 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看
        req.SmsSdkAppId = settings.APP_ID
        req.SignName = settings.SIGN_NAME
        req.TemplateId = settings.TEMPLATE_ID
        req.TemplateParamSet = [code, '5']
        # 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
        req.PhoneNumberSet = ["+86" + mobile]
        req.SessionContext = ""
        req.ExtendCode = ""
        req.SenderId = ""
        resp = client.SendSms(req)
        import json
        # 输出json格式的字符串回包
        print(resp.to_json_string(indent=2))
        res = json.loads(resp.to_json_string(indent=2))
        is_code = res.get("SendStatusSet")[0].get('Code')  # {"SendStatusSet": [{···,Code:OK,···}]}
        if is_code == 'Ok':  # Code:OK
            print(code, is_code)
            return True
        return False
    except TencentCloudSDKException as err:
        return False
--------------------------------------------------------------------------------------------------------------
"""settings.py"""
CODE_NUM = 6

SECRET_ID = 'AKIDKfzlOuInDB3pTxKE3we8MK2Wlk6LTM9I'  
SECRET_KEY = 'AFD3nPvK2dAcxEOPLFC5c7Vo0iMZ6Zhp'
APP_ID = "1400763801"
SIGN_NAME = "如果晚安公众号"
TEMPLATE_ID = "1604100"

轮播图定时更新加缓存流程点链接

posted @ 2022-11-23 22:54  懒羊羊A  阅读(41)  评论(0)    收藏  举报