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 = []  

  

 

posted @ 2018-08-16 20:32  -Learning-  阅读(241)  评论(0编辑  收藏  举报