redis介绍及在购物车项目中的应用,用户认证
1.redis
2.购物车的构建
api结构:
models.py(创建完后自行添加数据)
from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType # Create your models here. class Course(models.Model): """专题课/学位课模块表""" name = models.CharField(max_length=128, unique=True) course_img = models.CharField(max_length=255) course_type_choices = ((0, '付费'), (1, 'VIP专享'), (2, '学位课程')) course_type = models.SmallIntegerField(choices=course_type_choices) brief = models.TextField(verbose_name="课程概述", max_length=2048) # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除 price_policy = GenericRelation("PricePolicy") class PricePolicy(models.Model): """价格与有课程效期表""" content_type = models.ForeignKey(ContentType) # 关联course object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') # course = models.ForeignKey("Course") valid_period_choices = ((1, '1天'), (3, '3天'), (7, '1周'), (14, '2周'), (30, '1个月'), (60, '2个月'), (90, '3个月'), (180, '6个月'), (210, '12个月'), (540, '18个月'), (720, '24个月'), ) valid_period = models.SmallIntegerField(choices=valid_period_choices) price = models.FloatField() class Meta: unique_together = ("content_type", 'object_id', "valid_period") verbose_name_plural = "15. 价格策略" def __str__(self): return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price) class Account(models.Model): username = models.CharField("用户名", max_length=64, unique=True) email = models.EmailField( verbose_name='邮箱', max_length=255, unique=True, blank=True, null=True ) password = models.CharField('密码', max_length=128) class Meta: verbose_name_plural = "22. 账户信息" class UserToken(models.Model): user = models.OneToOneField(to='Account') token = models.CharField(max_length=36) class Meta: verbose_name_plural = "34. token表"
admin.py(用于在在admin中添加数据)
from django.contrib import admin from api import models # Register your models here. admin.site.register(models.UserToken) admin.site.register(models.Course) admin.site.register(models.Account) admin.site.register(models.PricePolicy)
urls.py
from django.conf.urls import url from api.views import shoppingcar,auth urlpatterns = [ url(r'auth/$', auth.AuthView.as_view({'post': 'login'})), url(r'shoppingcar/$', shoppingcar.ShoppingCarView.as_view({'post': 'create', 'get': 'list', 'delete': 'destory', 'put': 'update'})), ]
app01下的urls.py
from django.conf.urls import url,include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^api/(?P<version>\w+)/', include('api.urls')), ]
utils/response.py
class BaseResponse(object): def __init__(self): self.code = 1000 self.data = None self.error = None @property def dict(self): return self.__dict__
utils/auth.py(重写用户认证组件)
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from api import models class AccountAuthentication(BaseAuthentication): def authenticate(self, request): """ 源码: Authenticate the request and return a two-tuple of (user, token). """ # 从前端获取用户携带的token token = request.query_params.get('token') token_obj = models.UserToken.objects.filter(token=token).first() if not token_obj: raise AuthenticationFailed({'code':1000,'error':'认证失败'}) # 认证通过 return (token_obj.user, token_obj)
views/auth.py(登录认证)
import uuid from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models from api.utils.response import BaseResponse class AuthView(ViewSetMixin,APIView): def login(self,request,*args,**kwargs): """api_usertoken 用户登录认证 """ response = BaseResponse() try: user = request.data.get('username') pwd = request.data.get('password') obj = models.Account.objects.filter(username=user,password=pwd).first() if not obj: response.code = 1000 response.error = '用户名或密码错误' else: uid = str(uuid.uuid4()) # UserToken和Account是一对一的关系,登陆成功生成uid并加入表格 models.UserToken.objects.update_or_create(user=obj,defaults={'token':uid}) response.code = 99999 response.data = uid except Exception as e: response.code = 10005 response.error = '操作异常' return Response(response.dict)
views/shoppingcar.py
import json import redis from django.conf import settings from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api.utils.auth import AccountAuthentication from api.utils import response from api import models # 注意这里的ip地址 CONN = redis.Redis(host="118.24.140.138",port=6379) class ShoppingCarView(ViewSetMixin, APIView): # 加入用户认证 authentication_classes = [AccountAuthentication,] def list(self,request,*args,**kwargs): """ 从redis中查看购物车信息 """ ret = response.BaseResponse() try: shopping_car_list = [] # 这里的request.user来自认证组件的的返回值 print(request.user) pattern = settings.LUFFY_SHOPPING_CAR % (request.user.id, '*',) user_key_list = CONN.keys(pattern) for key in user_key_list: temp = { "id":CONN.hget(key,"id").decode("utf-8"), "name":CONN.hget(key,"name").decode("utf-8"), "img":CONN.hget(key,"img").decode("utf-8"), } shopping_car_list.append(temp) ret.data = shopping_car_list except Exception as e: ret.code = 1000 ret.error = "获取数据失败" return Response(ret.dict) def create(self, request, *args, **kwargs): """ 加入购物车 """ # 需要先接收用户选中的课程id以及价格策略id course_id = request.data.get('courseid') policy_id = request.data.get('policyid') course = models.Course.objects.first(id=course_id) ret = response.BaseResponse() # 判断数据是否合法,防止非正常点击,比如利用其他软件0元购买某些商品 try: # 判断课程是否存在 if not course: ret.code = 1000 ret.error = "课程不存在" return Response(ret.dict) # 判断价格策略的合法性,也是避免蓄意修改 # 获取价格策略的所有信息 price_policy_queryset = course.price_policy.all() price_policy_dict = {} for item in price_policy_queryset: temp = { 'id':item.id, 'price':item.price, 'valid_period': item.valid_period, 'valid_period_display': item.get_valid_period_display() } price_policy_dict[item.id] = temp # 校验id是是否在字典中 if policy_id not in price_policy_dict: ret.code = 1000 ret.error = "不要乱动" return Response(ret.dict) # 设置购物车物品数 pattern = settings.LUFFY_SHOPPING_CAR % (request.user.id, '*',) keys = CONN.keys(pattern) if keys and len(keys) >=100: ret.code = 1000 ret.error = "物品栏已满" return Response(ret.dict) except Exception as e: # 进行添加操作 key = settings.LUFFY_SHOPPING_CAR % (request.user.id, course_id,) CONN.hset(key,'id',course_id) CONN.hset(key, 'name', course.name) CONN.hset(key, 'img', course.course_img) # 设置保存时长,24h CONN.expire(key,60*60*24) ret.code= 500 ret.data = "保存成功" return Response(ret.dict) def update(self,request,*args,**kwargs): """ 修改操作,这里我们只能修改我们的价格策略 """ ret = response.BaseResponse() try: # 获取用户通过操作相关字段传来的id course_id = request.data.get('courseid') policy_id = str(request.data.get('policyid')) key = settings.LUFFY_SHOPPING_CAR % (request.user.id, course_id,) if not CONN.exists(key): ret.code = 10000 ret.error = '课程不存在' return Response(ret.dict) # 取我们存的price_policy_dict数据 price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8')) # 判断价格策略是否存在 if policy_id not in price_policy_dict: ret.code = 1000 ret.error = '价格策略不存在' return Response(ret.dict) # 修改价格策略 CONN.hset(key, 'default_price_id', policy_id) set.data = '修改成功' except Exception as e: ret.code = 1000 ret.error = "修改失败" return Response(ret.dict) def destory(self,request,*args,**kwargs): """ 删除选中的某个课程 """ ret = response.BaseResponse() try: course_id = request.data.get('courseid') # 获取它的key值 key = settings.LUFFY_SHOPPING_CAR % (request.user.id, course_id,) CONN.delete(key) ret.data = "删除成功" except Exception as e: ret.code = 1000 ret.error = "删除失败" return Response(ret.dict)
setting相关配制:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', 'api.apps.ApiConfig', 'rest_framework' ] REST_FRAMEWORK = { # 版本相关信息 'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning', 'VERSION_PARAM':'version', 'DEFAULT_VERSION':'v1', 'ALLOWED_VERSIONS':['v1','v2'], # 分页相关信息,数字代表一页几条数据 # 'PAGE_SIZE':2 } LUFFY_SHOPPING_CAR = "shopping_car_%s-%s"
测试(成功登录,获取它的token)
发送一个错误请求,这里直接被认证组件拦截了,并没有到达视图
在通过携带token发送一个get请求:
3.全局配置
如果100个类,有98个视图要认证。可以加到全局rest_framework里面
'DEFAULT_AUTHENTICATION_CLASSES':['api.utils.auth.LuffyAuthentication',]
对于某些不用认证的类,可以直接定义认证类为空列表即可
authentication_classes = []