路飞项目接口
目录
路飞项目接口
轮播图接口
# model.py
class Banner(BaseModel):
name = models.CharField(max_length=32, verbose_name='图片名字')
img = models.ImageField(upload_to='banner', verbose_name='图片', blank=True)
link = models.CharField(max_length=32, verbose_name='跳转链接')
info = models.TextField(verbose_name='图片介绍')
class Meta:
verbose_name = '轮播图'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
# ser.py
class BannerModelSerializer(serializers.ModelSerializer):
class Meta:
model = Banner
fields = ['name', 'img', 'link']
#views.py
class BannerView(GenericViewSet, ListModelMixin):
queryset = Banner.objects.filter(is_delete=False, is_show=True).order_by('display_order')[:settings.BANNER_COUNTER]
serializer_class = BannerModelSerializer
# 把data的数据加到缓存
# 1. 先去缓存拿数据库
def list(self, request, *args, **kwargs):
banner_list = cache.get('banner_list')
if not banner_list:
print('走数据库了')
# 缓存中没有,去数据库拿
respnose = super().list(request, *args, **kwargs)
cache.set('banner_list', respnose.data, 60 * 60 * 24)
return respnose
return Response(data=banner_list)
我们可以把常用的接口放到redis中,有到redis里面去找,没有到mysql去找,在用celery做个定时任务,每隔一天到缓存中查找一下,刷新
登录接口
ser.py
class UserModelSerializer(serializers.ModelSerializer):
username = serializers.CharField()
class Meta:
model = User
fields = ['username', 'password']
def validate(self, attrs):
# 多种方式登录
user = self._get_user(attrs)
token = self._get_token(user)
self.context['token'] = token
self.context['user'] = user
return attrs
def _get_user(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
# user = User.objects.filter(username=username).first()
if re.match('0?(13|14|15|16|17|18|19)[0-9]{9}$', username):
user = User.objects.filter(telephone=username).first()
elif re.match('\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}$', username):
user = User.objects.filter(email=username).first()
else:
user = User.objects.filter(username=username).first()
if user:
if user.check_password(password):
return user
else:
raise ValidationError('密码错误')
else:
raise ValidationError('账号不存在')
def _get_token(self, user):
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return token
#views.py
class LoginView(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
user_ser = ser.UserModelSerializer(data=request.data)
if user_ser.is_valid():
token = user_ser.context.get('token')
username = user_ser.context.get('user').username
return APIResponse(token=token, username=username)
else:
return APIResponse(code=0, msg=user_ser.errors)
手机登录接口
# ser.py
class CodeLogin(serializers.ModelSerializer):
code = serializers.CharField()
class Meta:
model = User
fields = ['telephone', 'code', ]
def validate(self, attrs):
user = self._get_user(attrs)
token = self._get_token(user)
self.context['token'] = token
self.context['user'] = user
return attrs
def _get_user(self, attrs):
telephone = attrs.get('telephone')
code = attrs.get('code')
if re.match('0?(13|14|15|16|17|18|19)[0-9]{9}$', telephone):
user = User.objects.filter(telephone=telephone).first()
if user:
from django.core.cache import cache
from django.conf import settings
# 从缓存中取出telephone
cache_code = cache.get(settings.PHONE_CACHE_KEY % telephone)
if code == cache_code:
# 存在取出code比较,相等把过期的验证码删除
cache.set(settings.PHONE_CACHE_KEY % telephone, code)
return user
else:
raise ValidationError('验证码不正确')
else:
raise ValidationError('用户不存在')
else:
raise ValidationError('手机号不合法')
def _get_token(self, user):
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return token
#view.py
@action(methods=['POST'], detail=False)
def code_login(self, request, *args, **kwargs):
user_ser = ser.CodeLogin(data=request.data)
if user_ser.is_valid():
token = user_ser.context.get('token')
username = user_ser.context.get('user').username
return APIResponse(token=token, username=username)
else:
return APIResponse(code=0, msg=user_ser.errors)
# 验证手机是否存在
@action(methods=['GET'], detail=False)
def check_telephone(self, request, *args, **kwargs):
telephone = request.query_params.get('telephone')
import re
if not re.match('0?(13|14|15|16|17|18|19)[0-9]{9}$', telephone):
return APIResponse(code=0, msg='手机号不合法')
try:
User.objects.get(telephone=telephone)
return APIResponse(code=1)
except:
return APIResponse(code=0, msg='手机号不存在')
# 发送验证码
@action(methods=['GET'], detail=False, throttle_classes=[SMSThrottling])
def send(self, request, *args, **kwargs):
import re
from django.core.cache import cache
from django.conf import settings
telephone = request.query_params.get('telephone')
if not re.match('0?(13|14|15|16|17|18|19)[0-9]{9}$', telephone):
return APIResponse(code=0, msg='手机号不合法')
code = get_code()
result = send_single_sms(code, telephone)
# 验证码保存(保存到哪?)
cache.set(settings.PHONE_CACHE_KEY % telephone, code, 180)
print(result)
if result:
return APIResponse(code=1, msg='验证码发送成功')
else:
return APIResponse(code=0, msg='验证码发送失败')
注册接口
#ser.py
class RegisterModelSerializer(serializers.ModelSerializer):
code = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ['code', 'password', 'telephone', 'username']
extra_kwargs = {
'password': {'max_length': 16, 'min_length': 8},
'username': {'read_only': True}
}
def validate(self, attrs):
telephone = attrs.get('telephone')
code = attrs.get('code')
if re.match('0?(13|14|15|16|17|18|19)[0-9]{9}$', telephone):
user = User.objects.filter(telephone=telephone).first()
if not user:
# 从缓存中取出telephone
cache_code = cache.get(settings.PHONE_CACHE_KEY % telephone)
if code == cache_code:
# 存在取出code比较,相等把过期的验证码删除
cache.set(settings.PHONE_CACHE_KEY % telephone, code)
attrs['username'] = telephone
attrs.pop('code')
return attrs
else:
raise ValidationError('验证码不正确')
else:
raise ValidationError('此用户已注册存在')
else:
raise ValidationError('手机号不合法')
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
#view.py
class RegisterView(GenericViewSet,CreateModelMixin):
queryset = User.objects.all()
serializer_class = ser.RegisterModelSerializer
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
username = response.data.get('username')
return APIResponse(code=1,msg='注册成功',username=username)
云片网接口
import json
import requests
from luffyapi.utils.logger import log
def get_code():
import random
s_code = ''
for i in range(4):
s_code += str(random.randint(0,9))
print(s_code)
return s_code
def send_single_sms(code, mobile):
from . import settings
# 发送单条短信
url = settings.url
text = f"您的验证码是{code}。如非本人操作,请忽略本短信"
apikey = settings.apikey
try:
res = requests.post(url, data={
"apikey": apikey,
"mobile": mobile,
"text": text
})
re_json = json.loads(res.text)
print(re_json)
re = re_json['code']
if re == 53:
return False
else:
return True
except Exception as e:
log.error('手机号:%s,短信发送失败,错误为:%s' % (mobile, str(e)))
轮播图接口缓存redis缓存
# 首页轮播图数据缓存到redis中
def list(self, request, *args, **kwargs):
# response=super().list(request, *args, **kwargs)
# 把data的数据加缓存
# 1 先去缓存拿数据
banner_list=cache.get('banner_list')
if not banner_list:
print('走数据库了')
# 缓存中没有,去数据库拿
response = super().list(request, *args, **kwargs)
# 加到缓存
cache.set('banner_list',response.data,60*60*24)
return response
return Response(data=banner_list)
轮播图定时任务刷新
# celery_task/celery.py
# 三步走,加载django的环境,执行定时任务,任务的定时配置,
from celery import Celery
# 加载django的环境
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev')
django.setup()
broker = 'redis://127.0.0.1:6379/1'
banckend = 'redis://127.0.0.1:6379/2'
app = Celery(__name__,broker=broker,banckend=banckend,include=['celery_task.home_task',])
# 执行定时任务 设置时区
app.conf.timezone='Asia/Shanghai'
# UTC时间
app.conf.enable_utc=False
# 任务的定时配置
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'add_task': {
'task': 'celery_task.home_task.banner_update',
# 'schedule': timedelta(seconds=30),
}
}
# celery_task/home_task.py
from .celery import app
from home import ser
from home import models
from django.conf import settings
from django.core.cache import cache
@app.task
def banner_update():
queryset_banner = models.Banner.objects.filter(is_show=True, is_delete=False).order_by('orders')[
0:settings.BANNER_COUNTER]
serializer_banner = ser.BannerModelSerializer(instance=queryset_banner,many=True)
for banner in serializer_banner.data:
banner['img'] = 'http://127.0.0.1:8000'+banner['img']
cache.set('banner_list',serializer_banner.data)
return True
必须建立一个celery.py的文件
课程分类群查接口
class CourseCategoryView(GenericViewSet, ListModelMixin):
queryset = models.CourseCategory.objects.filter(is_delete=False, is_show=True).order_by('orders')
serializer_class = serializer.CourseCategoryModelSerializer
课程群查接口
class CourseView(GenericViewSet, ListModelMixin,RetrieveModelMixin):
queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('orders')
serializer_class = serializer.CourseModelSerializer
filter_backends = [DjangoFilterBackend, OrderingFilter]
ordering_fields = ['id', 'price', 'students']
filter_fields = ['course_category', 'students']
# ser.py
class TeacherModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Teacher
fields = ['name', 'role_name', 'title', 'signature', 'image', 'brief']
# 展示课程
class CourseModelSerializer(serializers.ModelSerializer):
teacher = TeacherModelSerializer()
class Meta:
model = models.Course
fields = [
'id',
'name',
'course_img',
'brief',
'attachment_path',
'pub_sections',
'price',
'students',
'period',
'sections',
'course_type_name',
'level_name',
'status_name',
'teacher',
'section_list',
]
# models.py
def course_type_name(self):
return self.get_course_type_display()
def level_name(self):
return self.get_level_display()
def status_name(self):
return self.get_status_display()
@property
def section_list(self):
ll = []
# 根据课程取出所有章节(正向查询,字段名.all())
course_chapter_list = self.coursechapters.all()
for course_chapter in course_chapter_list:
# 通过章节对象,取到章节下所有的课时(反向查询)
# course_chapter.表名小写_set.all() 现在变成了course_chapter.coursesections.all()
course_sections_list = course_chapter.coursesections.all()
for course_section in course_sections_list:
ll.append({
'name': course_section.name,
'section_link': course_section.section_link,
'duration': course_section.duration,
'free_trail': course_section.free_trail,
})
if len(ll) >= 4:
return ll
return ll
搜索组件和过滤组件
排序:
按id正序倒叙排序,按price正序倒叙排列
使用:http://127.0.0.1:8000/course/free/?ordering=-id
配置类:
filter_backends=[OrderingFilter]
配置字段:
ordering_fields=['id','price']
内置过滤:
使用:http://127.0.0.1:8000/course/free/?search=39
按照price过滤(表自有的字段直接过滤)
配置类:
filter_backends=[SearchFilter]
配置字段:
search_fields=['price']
扩展:django-filter
安装:
支持自由字段的过滤还支持外键字段的过滤
http://127.0.0.1:8000/course/free/?course_category=1 # 过滤分类为1 (python的所有课程)
配置类:
filter_backends=[DjangoFilterBackend]
配置字段:
filter_fields=['course_category']
自定义过滤组件
# filters.py
# 自定义过滤规则
from rest_framework.filters import BaseFilterBackend
class MyFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
# 真正的过滤规则
# params=request.GET.get('teacher')
# queryset.filter('''''')
return queryset[:1]
# 使用:在视图类中配置
filter_backends=[DjangoFilterBackend,OrderingFilter,MyFilter]
区间过滤
# 借助django-filter实现区间过滤
# 实现区间过滤
##########1 filters.py
class CourseFilterSet(FilterSet):
# 课程的价格范围要大于min_price,小于max_price
min_price = filters.NumberFilter(field_name='price', lookup_expr='gt')
max_price = filters.NumberFilter(field_name='price', lookup_expr='lt')
class Meta:
model=models.Course
fields=['course_category']
#####2 视图类中配置
-filter_backends=[DjangoFilterBackend]
# 配置类:(自己写的类)
-filter_class = CourseFilterSet
课程单查接口
class CouresView(GenericViewSet,ListModelMixin,RetrieveModelMixin):
queryset = models.Course.objects.filter(is_delete=False,is_show=True).order_by('orders')
serializer_class = serializer.CourseModelSerializer
pagination_class = PageNumberPagination
# 过滤和排序
# filter_backends=[DjangoFilterBackend,OrderingFilter,SearchFilter]
# filter_backends=[DjangoFilterBackend,OrderingFilter,MyFilter]
filter_backends=[DjangoFilterBackend,OrderingFilter]
# # filter_backends=OrderingFilter
ordering_fields=['id', 'price', 'students']
# # search_fields=['course_category']
filter_fields=['course_category']
只需要在课程视图上继承RetrieveModelMixin就可完成单查
章节分类接口
#1 urls.py
router.register('chapters', views.CourseChapterView, 'coursechapter')
# 2 views.py
class CourseChapterView(GenericViewSet,ListModelMixin):
queryset = models.CourseChapter.objects.filter(is_delete=False,is_show=True)
serializer_class = serializer.CourseChapterSerializer
# 可以按照课程id来查
filter_backends = [DjangoFilterBackend]
filter_fields = ['course']
# 3 serializer.py
class CourseSectionSerializer(serializers.ModelSerializer):
class Meta:
model=models.CourseSection
fields=['name','orders','duration','free_trail','section_link','section_type_name']
class CourseChapterSerializer(serializers.ModelSerializer):
# 子序列化的方式
coursesections=CourseSectionSerializer(many=True)
class Meta:
model=models.CourseChapter
fields=['name','summary','chapter','coursesections']
后台搜索接口
# urls.py
router.register('search', views.CouresSearchView, 'search')
# views.py
class CouresSearchView(GenericViewSet,ListModelMixin):
queryset = models.Course.objects.filter(is_delete=False,is_show=True)
serializer_class = serializer.CourseModelSerializer
pagination_class = PageNumberPagination
filter_backends=[SearchFilter]
search_fields=['name']
支付宝接口
#libs/alipay/pem/ali_pulic_key.pem和ali_pulic_key.pem这里配置你的私钥和支付宝的公钥
# __init__.py这样容易导
from .ali_pay import alipay, gateway
# alipay/settings.py
import os
APP_PRIVATE_KEY_STRING = open(os.path.join(os.path.dirname(__file__), 'pem', 'private_key.pem')).read()
ALIPAY_PUBLIC_KEY_STRING = open(os.path.join(os.path.dirname(__file__), 'pem', 'ali_pulic_key.pem')).read()
APPID = 2021000116665495
SIGN_TYPE = 'RSA2'
DEBUG = True
GATEWAY = 'https://openapi.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'
# alipay/ali_pay.py
from alipay import AliPay
from . import settings
alipay = AliPay(
appid=settings.APPID,
app_notify_url='http://127.0.0.1:8000/home/',
app_private_key_string=settings.APP_PRIVATE_KEY_STRING,
alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING,
sign_type=settings.SIGN_TYPE,
debug=settings.DEBUG
)
gateway = settings.GATEWAY
订单接口
from rest_framework.mixins import CreateModelMixin
from . import models
from . import serializer
from utils.apitoken import JWTJSONWebTokenAuthentication
from rest_framework.response import Response
from rest_framework.views import APIView
# Create your views here.
class PayView(GenericViewSet, CreateModelMixin):
authentication_classes = [JWTJSONWebTokenAuthentication]
queryset = models.Order.objects.all()
serializer_class = serializer.OrderModelSerializer
"""因为卖家要付款,我们要在request.data里面取出user,重写create方法"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(serializer.context.get('pay_url'))
serializer.py
from . import models
from course.models import Course
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
import uuid
from django.conf import settings
class OrderModelSerializer(serializers.ModelSerializer):
course = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), many=True, write_only=True)
'''
前端传过来的数据形式:{course:[1,2,3],total_amount:1111,out_trade_no:1223334,pay_type:1}
这里前端传过来的是课程的一个个对象,我们需要处理course:[obj1,obj2]
'''
class Meta:
model = models.Order
fields = ['total_amount', 'subject', 'pay_type', 'course']
extra_kwargs = {
'pay_type': {'required': True},
'total_amount': {'required': True}
}
def validate(self, attrs):
total_amount = self._check_price(attrs) # 校验价钱
out_trade_no = self._get_out_trade_no() # 生成订单号
user = self._get_user() # 需要request对象(需要视图通过context把request对象传入。重写create方法)
pay_url = self._get_pay_url(out_trade_no, total_amount, attrs.get('subject'))
self._before_create(attrs, user, pay_url, out_trade_no)
return attrs
def _check_price(self, attrs):
total_amount = attrs.get('total_amount') # 总价钱
course_list = attrs.get('course')
course_price = 0
for course in course_list: # 取出课程里面的价钱相加
course_price += course.price
if total_amount != course_price:
raise ValidationError('价钱不一致') # 返回错误信息
# print(attrs)
return total_amount
def _get_out_trade_no(self): # 生成订单号
out_trade_no = uuid.uuid4()
return str(out_trade_no).replace('-', '')
def _get_user(self):
user = self.context.get('request').user
return user
def _get_pay_url(self, out_trade_no, total_amount, subject):
from libs.alipay import alipay, gateway
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=out_trade_no,
total_amount=float(total_amount),
subject=subject,
return_url=settings.RETURN_URL,
notify_url=settings.NOTIFY_URL
)
return gateway + order_string
def _before_create(self,attrs,user,pay_url,out_trade_no):
attrs['user'] = user
self.context['pay_url'] = pay_url
attrs['out_trade_no'] = out_trade_no
def create(self, validated_data):
course_list = validated_data.pop('course')
order = models.Order.objects.create(**validated_data)
for course in course_list:
models.OrderDetail.objects.create(order=order,course=course,price=course.price,real_price=course.price)
print(order)
return order
总结:
- 前端传过来的订单里面的course是一个个课程的对象,前端传过来的数据形式:{course:[1,2,3],total_amount:1111,out_trade_no:1223334,pay_type:1}这里前端传过来的是课程的一个个对象,我们需要处理course:[obj1,obj2],我们只需要用PrimaryKeyRelatedField指定关联的Course表就可以了
- 生成订单里面需要传入user字段,订单和user是一对多,我们需要在view视图里面重新写create方法,把request传过来,取出user
- 支付接口五步走:
- 校验价钱
- 生成订单号
- 需要request对象(需要视图通过context把request对象传入。重写create方法)
- 生成支付宝支付url
- 为保存做准备,把支付宝的支付的url传给view
- 把课程pop出来,保存两张表,订单详情表和订单表
支付宝回调接口
# 支付宝回调地址
class PaySuccessView(APIView):
def get(self,request, *args,**kwargs):
# 接收支付宝发送到后端的get请求,改变支付状态,查询订单号是否一致
out_trade_no = request.query_params.get('out_trade_no')
# 查询订单的状态
order = models.Order.objects.filter(out_trade_no=out_trade_no).first()
if order.order_status ==1:
return Response(True)
return Response(False)
def post(self, request, *args, **kwargs):
from libs.alipay.ali_pay import alipay
from utils.logger import log
'''支付宝调回接口'''
data = request.data
out_trade_no = data.get('out_trade_no', None)
gmt_payment = data.get('gmt_payment', None)
signature = data.pop('sign')
success = alipay.verify(data, signature)
if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1, pay_time=gmt_payment)
log.info('%s订单支付成功' % out_trade_no)
return Response('success')
else:
log.info('%s订单有问题' % out_trade_no)
return Response('error')