夏天的烦恼

导航

从立项到商品

一.立项1.在码云上创建项目目录
2.使用ssh方式把项目克隆下来
    1)生成密钥对ssh-keygen -t rsa
    2)存放在家目录下的 .ssh 目录下
    3)原来有的话,把原来的删除
    4)复制生成好的公钥
    5)在码云上的ssh公钥中添加
    6)ssh方式复制项目
    7)在本地目录下git clone 项目克隆地址
3.创建分支
4.创建新的目录存放前端资源
5.push到码云(git push origin dev:dev)
6.在码云上发起合并请求(pull Requests)

7.安装静态文件服务器live-server(node.js提供的前端工具,必须在node.js8以上的版本才能运行,和python无关)
    1)安装node.js版本控制工具-->curl -o- https://raw.githubusercontent.co ... v0.33.11/install.sh | bash
    2)重新进入终端,下载最新的node.js-->nvm install node
    3)安装live-server-->npm install -g live-server
    4)使用live-server-->在静态文件目录front_end_pc下执行:live-server
        
8.创建虚拟环境: mkvirtualenv -p python3 meiduo_mall
9.安装工具包:
    django==1.11.11
    djangorestframework
    pymysql
10.创建后端工程目录:django-admin startproject 工程名
    
11.调整工程目录
    1)添加docs目录(记录文档)
    2)logs目录(记录日志)
    3)scripts目录(放一些脚本文件的)
12.调整主要应用程序目录
    1)添加apps包(应用模块)
    2)settings包(配置文件)
    3)libs包(第三方库)
    4)utils包(工具库)
    5)在settings下创建dev(开发)时的配置文件,删除主应用程序目录的配置文件

13.apps下创建应用-->python ../../manage.py startapp users
二.配置1.配置文件中添加使用配置文件的模块
    1)import sys
    2)sys.path.insert(0,os.path.join(BASE_DIR,"apps"))
    3)直接按照原来的方式添加app配置
    4)修改manage.py中使用到配置文件的地方

2.创建数据库
3.为数据库创建新的账户专门操作美多商城数据库
    create user meiduo identified by 'meiduo'; 
    grant all on meiduo_mall.* to 'meiduo'@'%'; 
    flush privileges;
    
    第一句:创建用户账号 meiduo, 密码 meiduo (由identified by 指明)
    第二句:授权meiduo_mall数据库下的所有表(meiduo_mall.*)的所有权限(all)给用户meiduo在以任何ip访问数据库的时候('meiduo'@'%')
    第三句:刷新生效用户权限
    
4.配置数据库
    1)DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',  # 数据库主机
        'PORT': 3306,  # 数据库端口
        'USER': 'meiduo',  # 数据库用户名
        'PASSWORD': 'meiduo',  # 数据库用户密码
        'NAME': 'meiduo_mall'  # 数据库名字
    }
}
    2)记得在meiduo_mall/meiduo_mall/__init__.py(或者settings/__init__.py)文件中添加:
        import pymysql
        pymysql.install_as_MySQLdb()
        
5.安装redis并配置(给admin站点使用的session)
    1)pip install django-redis
    2)配置文件中配置:
        CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://10.211.55.5:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "session": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://10.211.55.5:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"

6.配置本地化语言和时间
7.配置日志:
    LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,  # 是否禁用已经存在的日志器
    'formatters': {  # 日志信息显示的格式
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {  # 对日志进行过滤
        'require_debug_true': {  # django在debug模式下才输出日志
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {  # 日志处理方法
        'console': {  # 向终端中输出日志
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {  # 向文件中输出日志
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(os.path.dirname(BASE_DIR), "logs/meiduo.log"),  # 日志文件的位置
            'maxBytes': 300 * 1024 * 1024,
            'backupCount': 10,
            'formatter': 'verbose'
        },
    },
    'loggers': {  # 日志器
        'django': {  # 定义了一个名为django的日志器
            'handlers': ['console', 'file'],  # 可以同时向终端与文件中输出日志
            'propagate': True,  # 是否继续传递日志信息
            'level': 'INFO',  # 日志器接收的最低日志级别
        },
    }
}
    
8.异常处理(修改Django REST framework的默认异常处理方法(默认处理了序列化器抛出的异常),补充处理数据库异常和Redis异常)
    1)utils下创建exceptions.py
    2)
    from rest_framework.views import exception_handler as drf_exception_handler
    import logging
    from django.db import DatabaseError
    from redis.exceptions import RedisError
    from rest_framework.response import Response
    from rest_framework import status

    # 获取在配置文件中定义的logger,用来记录日志
    logger = logging.getLogger('django')

    def exception_handler(exc, context):
        """
        自定义异常处理
        :param exc: 异常
        :param context: 抛出异常的上下文
        :return: Response响应对象
        """
        # 调用drf框架原生的异常处理方法
        response = drf_exception_handler(exc, context)

        if response is None:
            view = context['view']
            if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
                # 数据库异常
                logger.error('[%s] %s' % (view, exc))
                response = Response({'message': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)

        return response
        3)添加到配置文件:
            REST_FRAMEWORK = {
            # 异常处理
            'EXCEPTION_HANDLER': 'meiduo_mall.utils.exceptions.exception_handler',
        }
三.登录注册模块1.使用django认证系统中的用户模型类创建自定义的用户模型类
    from django.contrib.auth.models import AbstractUser
    
    class User(AbstractUser):
    """用户模型类"""
    mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')

        class Meta:
            db_table = 'tb_users'
            verbose_name = '用户'
            verbose_name_plural = verbose_name
2.配置中指明自定义的用户模型类
    
    
3.进行数据库迁移(第一次数据库迁移必须在配置了自定义的用户模型类之后)
    python manage.py makemigrations
    python manage.py migrate
    
4.图片验证码
    1)分析,接口设计
        1>接口设计思路:
            接口的请求方式,如GET 、POST 、PUT等
            接口的URL路径定义
            需要前端传递的数据及数据格式(如路径参数、查询字符串、请求体表单、JSON等)
            返回给前端的数据及数据格式
        2>用户注册中,需要实现的接口:
            图片验证码
            短信验证码
            用户名判断是否存在
            手机号判断是否存在
            注册保存用户数据
    2)创建新的应用:verifications
    3)注册到配置文件
    4)新建图片验证码类视图
        1>接收参数
        2>校验参数
        3>生成验证码图像
        4>保存真实值
        5>返回图片
    5)把第三方生成验证码包放入libs下
    6)添加redis保存验证码的配置
    7)在verifications下创建constants存放常量
    8)添加url,然后在全局url中包含
    
    
5.本地域名映射
    1)vim /etc/hosts
    2)在文件中增加两条信息
        1>后端:127.0.0.1   api.meiduo.site
        2>前端:127.0.0.1   www.meiduo.site
    3)在配置文件中为ALLOWED_HOSTS添加可访问的地址.ALLOWED_HOSTS = ['api.meiduo.site', '127.0.0.1', 'localhost', 'www.meiduo.site']
    4)在front_end_ps/js下新建host.js为前端保存后端域名,添加内容:var host='http://api.meiduo.site:8000'
        
6.修改前端js代码
    1)在register.html导入host.js
    2)修改js代码
        1>创建生成uuid函数
        2>创建获取图片验证码函数
        3>添加变量存储存储uuid
        4>为图片验证码的src绑定一个变量,变量存储的是图片验证码的url(:src="")
        5>js中添加存储图片验证码url的变量
        6>添加全局的host变量
        7>拼接图片验证码的url放入存储图片验证码的变量中
        8>在js中的vue.js页面加载完就执行的方法中调用获取图片验证码函数
        9>在html为图片添加点击事件,调用获取图片验证码的函数(@click="")

7.短信验证码后端逻辑
    1)接口设计
    2)新建短信验证码类视图,GET请求,继承GenericAPIView
        1>校验参数(由序列化器完成)
        2>生成短信验证码
        3>保存短信验证码,发送记录
        4>发送短信
        5>返回
    3)添加序列化器验证参数
        1>验证是否符合格式
        2>添加校验方法
        3>获取数据
        4>查询真实的图片验证码,查询失败抛出异常
        5>解码图片验证码,比较图片验证码,错误的话raise serializer.ValidationError("")
        6>判断手机号是否在60s内(获取redis中的手机发送标志,如果有数据,说明发送过了,抛出异常)
        7>返回字典数据
    4)生成短信验证码(随机生成)
    5)保存短信验证码
    6)保存短信验证码的发送记录
    7)发送短信
        1>导入云通讯到utils下
        2>创建发送短信验证码的对象
        3>设置短信的有效分钟数:expires = 短信验证码有效时间秒//60
        4>传参(send_template_sms(手机号,[验证码,短信验证码有效分钟数],模板ID常量))
        5>为发送短信验证码捕获异常,写入日志,返回结果
    8)添加路由
    9)为保存短信验证码补充redis管道
        redis_conn = get_redis_connection('verify_codes')
        pl = redis_conn.pipeline()
        pl.setex("sms_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        pl.setex("send_flag_%s" % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
        pl.execute()
    10)序列化器中删除短信验证码(验证码输错或者输对都删除,即查询出来就从redis中删除)
        try:
            redis_conn.delete('img_%s' % image_code_id)
        except RedisError as e:
            logger.error(e)
     11)添加前端代码
    
8.CORS解决跨域请求
    1)安装扩展:
        pip install django-cors-headers
    2)配置文件中APPS添加'corsheaders'
    3)在中间件中添加中间件配置,放在最上面:'corsheaders.middleware.CorsMiddleware'
    4)添加白名单配置
        CORS_ORIGIN_WHITELIST = (
    '127.0.0.1:8080',
    'localhost:8080',
    'www.meiduo.site:8080',
    'api.meiduo.site:8000'
)
CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie

9.创建celery包(在工程目录下):celery_tasks异步任务
    1)创建main.py(celert的启动模块)和config.py
    2)为短信验证码创建包(sms),在包下创建tasks.py(固定的名字)
    3)安装Celery扩展
    4)在main.py下导入Celery并创建celery应用:app = Celery("meiduo")
    5)在config.py中添加配置:
        1>broker_url = "redis://127.0.0.1/14"(沟通的桥梁信息,存储任务的队列)
        2>result_backend = "redis://127.0.0.1/15"(保存任务运行完之后的返回结果信息,比如函数的返回值,任务id,状态等)
    6)main下导入celery配置:app.config_from_object('celery_tasks.config')
    7)main下注册任务:app.autodiscover_tasks(['celery_tasks.sms'])
    8)把发送短信验证码的代码写在sms下的tasks.py下
    9)在sms下创建utils包,把短信验证码工具包放进去
    10)修改原来发送短信验证码的代码(一些用到的变量通过传参传入),把返回值去掉
    11)导入日志,添加django的日志配置文件.logger = logging.getLogger("django")
    12)在main.py中添加celery使用django配置的代码:
        import os
        if not os.getenv('DJANGO_SETTINGS_MODULE'):
            os.environ['DJANGO_SETTINGS_MODULE'] = 'meiduo_mall.settings.dev'
    13)sms下的tasks.py下使用装饰器装饰发送短信验证码的方法:
        1>导入app:from celery_tasks.main import app
        2>装饰器装饰:
            @app.task(name="send_sms_code")
            def send_sms_code(mobile, sms_code, expires,temp_id):
                ...
            使用的是应用名.task()的方式装饰,其中装饰器的参数name是为了指明任务的名字,在运行时可以知道调用了哪个任务运行
            
    14)在需要使用到发送短信验证码的视图函数中调用发送短信验证码的方法并传参:
        1>导包:from celery_tasks.sms.tasks import send_sms_code
        2>expires = constants.SMS_CODE_REDIS_EXPIRES//60
        3>send_sms_code.delay(mobile, sms_code, expires,constants.SMS_CODE_TEMP_ID)
        4>return Response({'message':'OK'})
    15)启动celery(在 异步任务所在的目录下):celery -A celery_tasks.main worker  -l info
        -A代表一个应用,后面跟的是应用启动的文件路径
        -l info 表示显示所有级别在info以上的worker日志信息

10.判断账号
    1)判断用户是否存在
        1>users下定义类视图
        2>获取指定用户名的数量
        3>返回数据(用户名和数量)
        4>前端实现
    2)判断手机号是否存在
        1>users下定义类视图
        2>获取指定的手机号的数量
        3>返回数据(手机号和数量)
        4>前端实现
11.用户注册
    1)在users应用下创建新的视图类(继承CreateAPIView)实现用户注册:(参数:username,password,password2,sms_code,mobile,allow)
        1>视图函数中使用序列化器类(CreateUserSerializer)
    2)实现序列化器
        1>在users下新建serializers模块实现序列化器
        2>导入rest_framework的serializers
        3>创建序列化器CreateUserSerializer,继承serializers.ModelSerializer
        4>添加自定义字段(数据库模型类没有的字段)都设为校验时使用的字段(添加属性:write_only=True)
        5>声明序列化器类有哪些字段和映射哪个模型类:
            class Meta:
                model = User
                fields = (所有需要显示的字段名,包括模型类里面的以及自定义的,此时校验和返回都是这些字段)
        6>使用extra_kwargs对字段指明额外的需求.
        7>对特定的字段进行校验(validate_字段名):包括mobile,allow.
        8>对多个字段同时进行校验(validate):判断两次密码和短信验证码
         9>重写保存方法(create),增加密码加密功能
            (1).移除数据库模型类不存在的属性(allow,sms_code,password2)
            (2).调用父类的保存方法
            (3).调用django的认证系统加密密码,保存
            (4).返回user
    3)实现注册的前端js

12.使用JWT机制判断用户身份
    1)安装扩展:pip install djangorestframework-jwt
    2)在配置文件的REST_FRAMWORK中添加认证的配置:
        'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
            )
    3)在配置文件中添加JWT的配置token的有效期:
        JWT_AUTH={
            'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
        }
    4)在注册时的序列化创建了用户之后签发jwt的token(记录登录状态):
        1>导包:from rest_framwork_jwt.settings import api_settings 
        2>签发JWT:   
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
    5)添加返回的字段token并且把生成的token存到这个字段:比如user.token = token
    6)前端保存token:
        sessionStorage浏览器关闭即失效
        localStorage 长期有效
        1>在js中sessionStorage.clear()
        2>localStorage.clear()
        3>localStorage.token = response.data.token
        4>localStorage.user_id = response.data.user_id
        5>localStorage.username = response.data.name
    
13.登录:
    在users下的urls下添加登录签发JWT的视图
    1)导包:from rest_framework_jwt.views import obtain_jwt_token
    2)添加路由:url(r'^authorizations/$', obtain_jwt_token)
    3)在users/utils.py中创建jwt_response_payload_handler函数修改视图到的返回值.
    4)在配置中的JWT_AUTH中添加配置:
        "JWT_RESPONSE_PAYLOAD_HANDLER":"users.utils.jwt_response_payload_handler"
    5)在users/utils.py下自定义一个类(UsernameMobileAuthBackend)继承自ModelBackend,重写django认证系统ModelBackend里面的authenticate(self,request,username=None,password=None,**kwargs)方法自定义用户名或者手机号认证(因为username可能是用户名,也可能是手机号)
    6)抽出一个根据账号来获取用户对象的方法(get_user_by_account(account))放在自定义类的外面
        1>正则账号信息判断是否为手机号,根据手机号查询数据库获得用户对象
        2>否则根据用户名查询数据库获得用户对象
        3>捕获异常,因为是get查询数据库,不存在返回None,否则返回用户对象
    7)在重写的authenticate方法中调用获取用户对象的方法,传参为接收到的username,接收获取到的用户对象
    8)验证用户对象的密码,认证成功后返回用户对象:
        if user is not None and user.check_password(password):
            return user
    9)配置文件中添加认证方法的配置:
        AUTHENTICATION_BACKENDS=['users.utlis.UsernameMobileAuthBackend']
    10)修改前端代码
   

14.QQ登录:
    1)注册成为QQ互联开发人员:
        http://wiki.connect.qq.com/%E6%8 ... 0%E5%8F%91%E8%80%85
    2)创建应用,获取项目对应与QQ互联的应用ID:
        http://wiki.connect.qq.com/__trashed-2
    3)根据QQ登录开发文档进行开发:文档链接-->http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0
    4)创建QQ登录模型基类(放到全局的应用的utils下)
    5)创建一个新的应用:oauth
        1>添加到App配置中
    6)创建QQ登录模型类(继承自基类)(oauth/models.py下),并且进行数据库迁移
    7)第一个接口:oauth中创建类视图(继承APIView,get方式)返回QQ登录的网址:
        1>获取next参数.如果没有,返回主页:next="/"
        2>拼接QQ登录的网址(调用拼接QQ登录网址的辅助工具类,传入参数:OAuthQQ(state=next))
        3>返回(Response({'login_url':login_url}))
    8)oauth下创建utils.py,创建QQ认证辅助工具类(OAuthQQ)拼接QQ登录网址
        1>定义方法拼接url(get_login_url)
        2>根据文档查看url以及需要发送的参数(client_id,redirect_uri,state),参数设置成字典数据类型
        3>发送的参数在__init__下定义,因为每个对象的参数值不一样.创建对象时传入.
        4>在配置文件中设置QQ登录时的默认值,在不传参时就使用默认值(因为有些值是一样的,不需要改变,就可以使用默认值.)
            QQ_CLIENT_ID = '101474184'
            QQ_CLIENT_SECRET = 'c6ce949e04e12ecc909ae6a8b09b637c'
            QQ_REDIRECT_URI = 'http://www.meiduo.site:8080/oauth_callback.html'
            QQ_STATE = '/'
        5>通过form django.conf import settings 使用django的配置文件中的内容
        6>使用urllib.parse 下的urlencode(字典名),将字典转换为url路径中的查询字符串,拼接到url后面(from urllib.parse import urlencode)
        7>返回url
    9)注册路由&在总路由中包含,设置前缀(oauth)
    10)实现QQ登录前端,在login.js中增加qq登录的方法.请求成功获取到返回的QQ登录网址,跳转到QQ登录页面
    11)前端处理QQ_REDIRECT_URI设置的oauth_callback.html
        1>创建oauth_callback.html
        2>js中创建oauth_callback.js
        
    12)第二个接口:(get)
        1>创建新的类视图:QQAuthUserView(APIView),判断QQ登录的用户
        2>获取code,如果不存在,返回信息
        3>凭借code获取access_token:创建辅助工具类对象,调用get_access_token()方法,传参数为code
            (1).在QQ认证辅助工具类中添加获得access_token的方法get_access_token()
            (2).添加url,设置参数(根据QQ互联文档)
            (3).拼接url带上参数
            (4).使用urllib.request.urlopen(url,data=None)发送请求,获得返回响应
            (5).读取响应体数据:响应调用read()读取数据,数据解码decode()
            (6).解析响应体数据:urllib.parse.parse_qs(数据),解析出来是个字典
            (7).读取access_token.(在异常抛出之后)在一个字典中获取access_token,是一个列表,然后返回access_token[0]
            (8).辅助类中捕获异常,记录日志,抛出异常:发送请求时开始捕获,解析完成后记录异常('获取access_token异常:%s'%e),并抛出异常自定义的异常OAuthQQAPIError.如果没有异常再进行读取access_token.
            (9).自定义一个异常:
                a.oauth下创建新的模块exception.py
                b.创建自定义的异常类OAuthQQAPIError,继承自Exception.不实现,直接pass
            (10)在类视图中调用get_access_token()时捕获自定义的异常:OAuthQQAPIError
            (11).直接返回Response({'message': '访问QQ接口异常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
        4>凭借access_token获取openid
            (1).走到这一步说明access_token获取成功,
            
            (2).调用QQ工具辅助类中的获取openid的方法(get_openid),参数为access_token
            (3).根据QQ互联文档编写get_openid方法
            (4).和获得access的流程类似
            (5).得到的数据不同,解析openid的方式不一样
            (6).返回的数据为: callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n;字符串
            (7).通过切片解析数据,转json字符串为字典,抛异常
        5>根据openid查询数据库OAuthQQuser,捕获异常判断数据是否存在
        6>如果数据不存在,处理openid并返回
            (1).调用QQ辅助类中定义生成access_token的方法(generate_save_user_token),参数为openid,返回access_token.(返回的这个access_token封装了openid)
            (2).辅助工具类中实现generate_save_user_token方法.
            (3).安装itsdangerous,使用itsdangerous生成access_token
            (4).使用 itsdangerous模块下的TimedJSONWebSignatureSerializer 生成带有有效期的token,名字太长,可以起别名Serializer
            (5)生成方式:定义序列化对象:serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
            (6).oauth下新建constants模块,设置SAVE_QQ_USER_TOKEN_EXPIRES=10*60
            (7).调用对象的dumps方法传入openid获得生成的token:token = serializer.dumps({'openid':openid})
            (8).返回解码后的token:token.decode()
            (9).视图函数中返回access_token用于绑定用户的身份
        7>如果数据存在,表示用户已经绑定过身份,签发JWT token
        8>返回JWT_token数据(用户id,用户名,签发的token)
        9>拼接路由
        10>修改前端oauth_callback.js中的mounted方法
            (1).获取用户id,如果有,说明已经绑定,请求state中的url
            (2).如果没有,说明没有绑定,获得服务器返回的access_token,调用生成图片验证码的函数,显示绑定用户的表单.
    
    13)第三个接口:(绑定QQ身份)
        1>在QQAuthUserView中定义post方法
        2>获取数据
        3>校验数据
        4>判断用户是否存在,如果存在,绑定,创建OAuthQQUser数据
        5>如果不存在,先创建User,再创建OAuthQQUser
        6>签发JWT token
        
        7>上面的这套流程可以直接继承CreateAPIView
        8>直接在视图函数中调用序列化器OAuthQQUserSerializer
        9>添加序列化器模块,定义序列化器OAuthQQUserSerializer
        10>实现OAuthQQUserSerializer:继承serializer.ModelSerializer
            (1).添加字段sms_code只反序列化,access_token只反序列化,token只序列化.抽出mobile字段(可序列化,可反序列化),因为模型类中的mobile设置了只能是唯一,这里需要抽出来,把只能是唯一的验证去掉,不抽出来在模型类中会抛出异常,那么后面的判断手机号是否在数据库中就没有办法判断了.
            (2).Meta中指定模型类,指定显示字段fields = ('mobile','password','sms_code','access_token','id','username','token')
            (3).为字段添加额外的条件:extra_kwargs={
                'username':{
                    'read_only':True
                },
                'password':{
                    'write_only':True,
                    'min_length':8,
                    'max_length':20,
                    'error_messages':{
                        'min_length':'仅允许8-20个字符的密码',
                        'max_length':'仅允许8-20个字符的密码'
                    }
                }
            }
            (4).定义校验数据的方法validate.
            (5).获取access_token
            (6).校验access_token,在QQ辅助工具类中添加校验access_token的静态方法,使用的是itsdangerous模块,因为生成也是这个模块.返回校验后的openid
            (7).validate中赋值openid:attrs['openid'] = openid
            (8).校验短信验证码,失败抛出异常
            (9).根据mobile获取user,捕获异常,不存在抛出异常,pass操作,没有异常则获取密码,验证码密码,调用user的密码认证系统:user.check_password(password),如果没有返回值,抛出验证错误异常,提示密码错误.
            (10)密码验证通过则把user添加到序列化器的数据字典中:attrs['user'] = user
            (11).返回attrs
            (12).修改序列化器的create方法.
            (13).获取user,openid,mobile,password
            (14).如果user不存在,在数据库中创建这个user,用户名为手机号,手机号也是手机号,密码为前端发送过来校验后的密码.调用的django认证系统的创建用户的方法create_user()
            (15).绑定,创建OAuthQQUser数据:OAuthQQUser.objects.create(user=user,openid=openid)
            (16).签发JWT token,和登录里面的一样的.
            (17).返回user
        11>修改oauth_callback.js添加点击submit时的方法
四.用户个人中心模块1.个人基本信息:
    1)在User模型类中添加邮箱是否激活的字段
    2)数据库迁移
    3)users下创建新的视图类,查询用户基本信息(GET /user/):UserDetailView,继承自RetrieveAPIView
    4)调用序列化器类
    5)users下的序列化器模块下新建序列化器,用来设置返回哪些数据
    6)重写数据源的方法:get_object()不重写时返回的是query_set的查询集,返回当前请求的用户.return self.request.user
    7)指明必须通过登录认证后才能访问
    8)前端实现user_center_info.html和user_center_info.js
    
    9)实现保存邮箱后端接口(PUT /email/)
        1>新建EmailView视图,继承UpdateAPIView
        2>指明序列化器类
        3>重写get_object(self)方法获得当前的用户对象
        4>设置权限为认证通过的用户
        5>实现序列化器(EmailSerializer),继承ModelSerializer
        6>指定模型类和显示的字段
    10)发送邮件到邮箱进行验证
        1>注册自己的邮箱
        2>django中添加配置,设置邮箱的配置信息
            (1).指明使用什么样的邮箱客户端,这里使用django自带的:EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
            (2).发送的smtp邮箱地址,这里使用163:EMAIL_HOST = 'smtp.163.com'
            (3).端口号:固定为25,EMAIL_PORT = 25
            (4).发送邮件的邮箱:EMAIL_HOST_USER = 'itcast88@163.com'
            (5).设置发送邮件的邮箱客户端授权码,没有授权码就为邮箱的密码:EMAIL_HOST_PASSWORD = 'python808'
            (6).设置收件人看到的发件人:EMAIL_FROM = 'python<itcast88@163.com>'
        3>使用django提供的django.core.mail模块的send_mail()方法发送邮件:例如send_mail(subject, message, from_email, recipient_list,html_message=None)
            subject 邮件标题
            message 普通邮件正文, 普通字符串
            from_email 发件人
            recipient_list 收件人列表
            html_message 多媒体邮件正文,可以是html字符串
        4>定义发送邮件的异步任务,实现发送邮件
        5>在邮件的序列化器中重写update方法:
            (1).取出验证后的email,然后赋值给当前对象的email,当前对象调用save()方法保存邮箱地址
            (2).在user模型类中补充一个额外的方法,生成验证邮箱的url.使用itsdangerous模块下的dumps方法,参数为user_id和email,返回的是生成后的验证邮箱URL+token
            (3).序列化器中url调用生成验证邮箱url的方法
            (4).调用异步任务中的发送验证信息到用户邮箱的方法,传入参数
            (5)返回当前用户
        6>在urls.py中添加邮箱的类视图
    11)在前端user_center_info.js中添加保存email的方法
    12)邮箱验证前后端实现
        1>添加邮箱验证成功返回的前端html页面:success_verify_email.html
        2>修改success_verify_email.html的js
        3>后端实现接口:GET /emails/verification/?token=xxx
        4>VerifyEmailView(邮箱验证类视图),继承自APIView
        5>get方法下:
            (1).获取参数token
            (2).验证token是否存在,不存在返回信息,缺少token,状态码为400
            (3).token存在,验证token
            (4).在User模型类中新建校验token的静态方法:check_verify_eamil_token,接收参数token,使用的是itsdangerous模块中的loads(),参数为token,需要捕获异常.出现异常返回None,没有出现异常,获取数据:user_id和email
            (5).通过user_id和email查询User表,捕获异常,发送异常返回NOne,否则返回user
            (6).视图函数中调用校验token的方法,获得user对象.
            (7).如果user为None,返回message:链接信息无效,状态码为400
            (8).user存在,把user.email_active设为True,使用save()提交到数据库
            (9).返回message:OK
            (10).注册路由
            (11).前端success_verify_email.html的js中使用location.serch拿到url中?以及?后面的所有内容,拼接到请求的地址中

2.收货地址
    1)新增收货地址
        1>新建应用:areas
        2>配置文件中注册app
        3>models新建Area模型类
            (1).name
            (2).parent = models.Foreignkey('self',on_delete=models.SET_NULL,related_name='subs',null=True,blank=True)
            (3).定义__str__方法,返回name
        4>执行数据库迁移命令
        5>补充测试数据:
            (1).把测试数据areas.sql放入scripts目录下
            (2).新建脚本命令的文件:import_area_data_to_db.sh
            (3).输入命令:mysql -uroot -pmysql meiduo_mall <./areas.sql
            (4).在首行添加运行环境:#!/bin/bash
            (5).为import_area_data_to_db.sh添加可执行权限:chmod +x import_area_data_to_db.sh
            (6).执行脚本文件:./import_area_data_to_db.sh
        6> areas下创建新的类视图AreasViewSet实现接口,继承自ReadOnlyModelViewSet
            (1).重写get_queryset方法:
                a.如果action=='list',返回过滤后的数据(parent=None)
                b.否则返回所有area数据对象
            (2).创建序列化器模块,定义序列化器.
            (3).创建AreaSerializer序列化器类,继承自ModelSerializer.设置模型类为Area,显示的字段为id和name
            (4).创建SubAreaSerializer序列化器类,继承自Model.Serializer.设置模型类为Area,显示字段为id,name,subs,subs交给AreaSerializer(many=True,read_only=True)处理
            (5).重写get_serializer_class方法,选择对应的序列化器:
                a.如果action=='list',返回AreaSerializer序列化器
                b.否则返回SubAreaSerializer
        7>在urls中使用DefaultRouter注册视图:
            (1).router = DefaultRouter()
            (2).router.register('areas',views.AreasViewSet,base_name='areas')
            (3).urlpatterns+=router.urls
        8>在总路由中包含分路由
        9>修改前端代码
    2)使用redis缓存地址信息(因为地址信息除了查询没有其他的操作)
        1>安装扩展:pip install drf-extensions
        2>类视图继承CacheResponseMixin
        3>在配置文件中进行全局的缓存配置: 
            # DRF扩展
            REST_FRAMEWORK_EXTENSIONS = {
                # 缓存时间
                'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 60,
                # 缓存存储
                'DEFAULT_USE_CACHE': 'default',
            }
    3)关闭继承的视图中list方法的分页:配置类属性-->pagination_class=None
    4)实现前端代码:user_center_site.html和user_center_site.js
        
    5)用户地址管理
        1>在users的models模块新建用户地址模型类,继承自BaseModel:
            class Address(BaseModel):
                """
                用户地址
                """
                user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='addresses', verbose_name='用户')
                title = models.CharField(max_length=20, verbose_name='地址名称')
                receiver = models.CharField(max_length=20, verbose_name='收货人')
                province = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='province_addresses', verbose_name='省')
                city = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='city_addresses', verbose_name='市')
                district = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='district_addresses', verbose_name='区')
                place = models.CharField(max_length=50, verbose_name='地址')
                mobile = models.CharField(max_length=11, verbose_name='手机')
                tel = models.CharField(max_length=20, null=True, blank=True, default='', verbose_name='固定电话')
                email = models.CharField(max_length=30, null=True, blank=True, default='', verbose_name='电子邮箱')
                is_deleted = models.BooleanField(default=False, verbose_name='逻辑删除')

                class Meta:
                    db_table = 'tb_address'
                    verbose_name = '用户地址'
                    verbose_name_plural = verbose_name
                    ordering = ['-update_time']  # 指明查询是复数时的默认排序
            
        2>为User模型类添加默认地址:
            default_address = models.ForeignKey('Address', related_name='users', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='默认地址')
        3>数据库迁移
        4>users中添加新的类视图,继承自增加和更新扩展类以及GenericAPIView
            (1).增加:重写create方法,保存用户地址数据,保存之前检查用户地址数目,如果超出上限,返回响应信息,如果没有,返回父类CreateModelMixin的create方法
            (2).更新:重写get_queryset方法,返回更新使用的数据集为当前用户的地址中没有被删除的数据.更新使用的是UpdateModelMixin.查询集在这里指明
            (3).查询:
                a.自己实现list方法
                b.指明queryset为当前对象的get_queryset()
                c.指明序列化器为全局使用的序列化器:serializer=self.get_serializer(queryset,many=True)
                d.获得当前用户对象:user = self.request.user
                e.返回响应,参数为字典,包含用户id,用户的默认地址id,地址的限制条数,用户所有的地址信息(serializer.data)
            (4).删除:由于扩展类中是在真的删除,所以这里不使用.这里自己实现destroy方法来处理删除.
                a.获取删除的地址对象:address=self.get_object()
                b.进行逻辑删除(修改字段的值为True)
                c.调用save()方法保存
                d.直接只返回状态码:204
            (5).设置默认地址状态:(只需要pk,通过pk在用户的地址列表中找到当前的地址)
                a.装饰器装饰方法status(),需要添加参数pk=None.以PUT方式进行访问,单一资源(detail=True)
                b.获取当前请求的地址对象.get_object()使用Pk在queryset中获取
                c.设置当前请求的用户对象的默认地址为当前请求的地址对象
                d.调用save()方法保存
                e.返回响应信息Ok,状态码为200
            (6).设置title:需要请求体的参数title
                a.和设置默认地址采用同样的方法.使用action装饰器
                b.获取当前地址对象.get_object()使用Pk在queryset中获取
                c.调用修改地址标题的序列化器,传入参数为当前地址对象,数据为请求体中的参数
        5>serializer模块新建用户地址序列化器:
            (1).添加字段省市区的返回字符串以及接收和返回的省市区的id
            (2).指定模型类,和排除不需要显示的字段:exclude=('user','id_deleted','create_time','update_time')
            (3).验证手机号
            (4).重写序列化器保存数据的方法,保存数据时把user加上:从序列化器的参数context中的request中取出user赋值给验证后的数据字典.返回父类的create方法,参数为验证后的数据字典 
            (5).添加修改标题的序列化器,继承自ModelSerializer,指明模型类为Address,显示字段为title
        6>在users/urls.py中添加路由,使用DefaultRouter()
            (1).router = DefaultRouter()
            (2).router.register(r'addresses',views.AddressViewSet,base_name='addresses')
            (3).urlpatterns += router.urls
        7>修改前端user_center_site.js
       
五.商品模块1.设计首页广告数据表结构
    1)广告类别表包含三个字段:广告类别id,广告类别的名称:name,广告类别识别键名:key
    2)广告内容表包含八个字段:广告的id,广告类别的id(外键):category,广告的标题:title,广告的内容页面链接地址:url,广告图片的地址:image,广告的文本内容:text,同类别广告内的顺序:sequence,是否显示:status
2.设计商品数据表结构
    1)商品类别表
        1>id(类别id):INTEGER
        2>name(类别名称):VARCHAR(10)
        3>parent(父类别):FK,INTEGER
    2)商品频道表(一级类别)
        1>id(频道id):INTEGER
        2>group_id(所属组):INTEGER
        3>category_id(类别id):FK,INTEGER
        4>url(频道页链接地址):VARCHAR
        5>sequence(同组内的顺序):INTEGER
    3)商品SKU表
        1>id(sku id):INTEGER
        2>name(sku 名称):VARCHAR
        3>caption(副标题):VARCHAR
        4>goods_id(商品id):FK,INTEGER
        5>category_id(三级类别id):FK,INTEGER
        6>price(单价):DECIMAL
        7>cost_price(进价):DECIMAL
        8>market_price(市场价格):DECIMAL
        9>stock(库存):INTEGER
        10>sales(销量):INTEGER
        11>comments(评论量):INTEGER
        12>is_launched(是否上架):BOOLEAN
        13>default_image_url(默认图片链接地址):VARCHAR
    4)商品(SPU)表
        1>id(商品id):INTEGER
        2>name(商品名称):VARCHAR
        3>brand_id(品牌id):FK,INTEGER
        4>category1_id(一级类别id):FK,INTEGER
        5>category2_id(二级类别id):FK,INTEGER
        6>category3_id(三级类别id):FK,INTEGER
        7>sales(销量):INTEGER
        8>comments(评论量):INTEGER
        9>desc_detail(详细介绍):VARCHAR
        10>desc_pack(包装信息):VARCHAR
        11>desc_service(售后服务):VARCHAR
    5)品牌表(brand)
        1>id(品牌id):INTEGER
        2>name(品牌名称):VARCHAR
        3>logo(logo图片链接):VARCHAR
        4>first_letter(品牌首字母):CHAR(1)
    6)SKU图片表
        1>id(图片id):INTEGER
        2>sku_id(所属sku id):FK,INTEGER
        3>image(图片链接地址):VARCHAR
    7)商品规格表(goods_specification)
        1>id(规格id):INTEGER
        2>goods_id(商品id):FK,INTEGER
        3>name(规格名称):VARCHAR
    8)规格选项表(specification_option)
        1>id(选项id):INTEGER
        2>spec_id(规格id):FK,INTEGER
        3>value(选项值):VARCHAR
    9)规格信息表(sku_specification)
        1>id(规格信息id):INTEGER
        2>sku_id(所属sku id):FK,INTEGER
        3>spec_id(规格id):FK,INTEGER
        4>option_id(规格选项id)FK,INTEGER

3.创建广告应用(contents)
    1)注册到apps中
    2)模型类中根据设计的数据库表添加首页广告数据模型类(继承自BaseModel)
4.创建商品应用(goods)
    1)新建goods应用
    2)添加到配置的apps中
    3)模型类中根据设计的数据库表添加商品数据模型类(继承自BaseModel)
5.数据库迁移

6.使用FastDFS(快速分布式文件系统)分布式文件系统保存图片
    1)使用Docker安装FastDFS
        1>安装Docker
            (1)更新apt源:sudo apt-get update
            (2)安装依赖的工具:
            sudo apt-get install \
            apt-transport-https \
            ca-certificates \
            curl \
            software-properties-common
            (3)添加Docker官方GPG key:curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
            (4)设置Docker稳定版仓库:
            sudo add-apt-repository \
            "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
            $(lsb_release -cs) \
            stable"
            (5)添加仓库后,更新apt源索引:sudo apt-get update
            (6)安装最新版Docker CE(社区版):sudo apt-get install docker-ce
            (7)检查Docker CE是否安装正确:sudo docker run hello-world
            (8)为了避免每次命令都输入sudo,设置用户权限:sudo usermod -a -G docker $USER
            (9)注销重新登录
            (10)启动与停止Docker:
                # 启动docker
                sudo service docker start

                # 停止docker
                sudo service docker stop

                # 重启docker
                sudo service docker restart
                
            
        2>获取FastDFS的镜像
            (1)使用提供的镜像备份文件:docker load -i 文件路径/fastdfs_docker.tar(或者镜像源:docker image pull delron/fastdfs)
            (2)运行tracker:docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker   
            (3)运行storage:docker run -dti --network=host --name storage -e TRACKER_SERVER=10.211.55.5:22122 -v /var/fdfs/storage:/var/fdfs delron/fastdfs storage
        3>pyhton中使用FastDFS客户端
            (1)下载fastDFS开源包,并放到项目的scripts目录下
            (2)安装:
                pip install                     fdfs_client-py-master.zip
                pip install mutagen
                pip isntall requests
            (3)在utils下新建fastdfs包,把配置文件放入包下,修改配置文件的log位置以及tracker_server的地址
            
   2)自定义django文件存储系统
        1>在全局的utils下的fastdfs包中新建fdfs_storage模块,导包:
            from django.conf import settings
            from django.core.files.storage import Storage
            from django.utils.deconstruct import deconstructible
            from fdfs_client.client import Fdfs_client
        2>新建FastDFSStorage()类,继承自Storage,使用deconstructible装饰
        3>初始化时传参,加上默认值
        4>实现_open()方法直接pass和_save()方法.
        5>重写_save()方法.参数name(传入的文件名),content(文件对象),返回保存到数据库中的FastDFS的文件名.
        6>重写exists()方法,参数为name,在调用_save()方法之前调用,判断文件是否存在文件系统中.直接pass.
        7>重写url()方法,参数为name.返回值为配置的url+name.在调用数据库的image.url时就可以取到这个值.
        8>配置文件中配置django的文件存储为自定义的FastDFSStorage类:
            # django文件存储
            DEFAULT_FILE_STORAGE = 'meiduo_mall.utils.fastdfs.fdfs_storage.FastDFSStorage'
        9>配置文件中配置FastDFS的url和配置文件:
            # FastDFS
            FDFS_URL = 'http://image.meiduo.site:8888/'  
            FDFS_CLIENT_CONF = os.path.join(BASE_DIR, 'utils/fastdfs/client.conf')
        10>在/etc/hosts中添加方法FastDFS storage服务器的域名:127.0.0.1 image.meiduo.site
            
7.CKEditor富文本编辑器
    1)安装:pip install django-ckeditor
    2)配置中注册应用:
            'ckeditor',  # 富文本编辑器
            'ckeditor_uploader',  # 富文本编辑器上传图片模块
    3)配置文件中添加CKEditor设置:
        # 富文本编辑器ckeditor配置
        CKEDITOR_CONFIGS = {
            'default': {
                'toolbar': 'full',  # 工具条功能
                'height': 300,  # 编辑器高度
                # 'width': 300,  # 编辑器宽
            },
        }
        CKEDITOR_UPLOAD_PATH = ''  # 上传图片保存路径,使用了FastDFS,所以此处设为''
    4)在总路由中添加ckeditor路由:
        url(r'^ckeditor/', include('ckeditor_uploader.urls')),
    5)修改模型类SPU中的desc_detail,desc_pack,desc_service,]
        desc_detail = RichTextUploadingField(default='', verbose_name='详细介绍')
        desc_pack = RichTextField(default='', verbose_name='包装信息')
        desc_service = RichTextUploadingField(default='', verbose_name='售后服务')
    6)数据库迁移
    7)修改bug:通过Django上传的图片保存到了FastDFS中,而保存在FastDFS中的文件名没有后缀名,ckeditor在处理上传后的文件名按照有后缀名来处理,所以会出现bug错误.
        修改方法:
            1>找到虚拟环境目录中的ckeditor_uploader/views.py
            2>在第95行增加一行代码进行判断,切割完之后长度是否大于1,大于1时再进行判断后缀.      
    8)添加测试数据:
        1>在contents应用和goods应用的admin中注册模型类:
            from . import models
            admin.site.register(models.ContentCategory)
            ...
        2>添加FastDFS保存的测试图片数据
            (1).先把之前storage配置中被映射的宿主目录将/var/fdfs/storage中的data目录删除,再把测试数据压缩包解压进去
        3>添加对应的数据库测试数据,把数据放入scripts目录下,使用脚本文件导入
        4>创建超级管理员用户:
            (1).python manage.py createsuperuser
            (2).name:admin
            (3).password:admin123
                
8.页面静态化:使用模板,把数据读取出来渲染到模板页面(设置定时任务,比如每五分钟生成一次,或者设置修改数据的时候生成),生成的页面存到固定的地方(静态文件目录),前端访问时就从存储页面的地方加载即可.)可以不用频繁的操作数据库,提高加载页面的速度.
     1)配置文件中添加保存静态文件的目录:
            # 生成的静态html文件保存目录
            GENERATED_STATIC_HTML_FILES_DIR = os.path.join(os.path.dirname(os.path.dirname(BASE_DIR)), 'front_end_pc')
    2)在全局Meiduo_mall下新建templates模板目录
    3)在配置文件中的为模板配置中的DIRS配置templates的路径:'DIRS': [os.path.join(BASE_DIR, 'templates')],
    
    5)首页静态化
        1>在contents应用下新建crons模块,定义一个函数(generate_static_index_html()),生成静态的主页html文件
        2>设置静态文件的保存目录,使用配置文件中配置的:file_path = os.path.jion(settings.GENERATED_STATIC_HTML_FILES_DIR,'index.html')
        3>在templates下新建index.html的模板文件
        4>在函数中把生成好的html文件保存到配置的路径,'w'模式打开,设置编码为encoding='utf-8'.把生成的静态html字符串写入到文件.
        5>新建index.js
        6>在index.js中声明Vue使用的模板变量语法,在el后面声明:delimiters: ['[[', ']]']

    6)定时任务实现(操作系统完成的):
        1>安装扩展:pip install django-crontab
        2>在配置文件中注册:django-crontab  # 定时任务
        3>配置文件中设置定时任务列表:
            CRONJOBS=[
                # 每5分钟执行一次生成主页静态文件
                ('*/5****','contents.crons.generate_static_index_html','>>'+os.path.join(os.path.dirname(BASE_DIR),"logs/crontab.log")),
            ]
        4>添加定时任务到操作系统:python manage.py crontab add
            (显示已经激活的定时任务:python manage.py crontab show
            移除定时任务:python manage.py crontab remove)
        5>配置文件中配置解决crontab中文问题:CRONTAB_COMMAND_PREFIX = 'LANG_ALL=zh_cn.UTF-8'
       
    7)商品详情页静态化
        1>异步任务包新建html异步任务包
        2>提出商品分类,放到商品的utils下
        3>新建异步任务函数,生成静态商品详情页面(查询数据库)
        4>main中添加异步任务
        5>goods中admin.py中新建自定义的admin管理类
        6>重写admin管理类中修改时就触发定时任务重新生成html静态页面的模型类中的save_model(self,request,obj,from,change)和delete_model(request,obj),在其中添加异步任务.什么时候使用什么时候导入.
        7>在sku图片的自定义amdin管理类中设置默认图片.
        8>修改detail.html放到模板文件夹
        9>修改新页面的detail.js
        10>在前端文件夹中新建goods文件夹用来存放生成好的商品详情页html文件
    8)为了方便开发,随时生成静态页面,在scripts中新建生成页面静态化的python脚本文件(regenerate_static_index_html.py)
        1>添加django的配置文件
        2>初始化django:(django运行脚本的时候配置)
            import django
            django.setup()
        3>导入生成静态html页面的函数
        4>在函数运行时调用函数(__main__中)
        5>在最上面添加导包路径(sys可以添加相对路径,一般在脚本中使用相对路径,因为前提是知道了脚本文件在哪运行):
            import sys
            sys.path.insert(0,'../')
        6>还可为文件指明运行的shell:首行添加#!/user/bin/env python,再为文件添加可执行权限
        7>新建生成详情页的脚本文件(regenerate_static_detail_html.py)
        8>配置,把异步任务中的生成商品详情页的代码复制过来
        9>在__main__中查询所有的SKU,循环遍历,调用生成商品详情页的方法,参数为每个对象的id
        
9.用户浏览历史记录(把用户浏览的sku_id(这里设置为五个)保存到redis里面),采用list的类型
    1)配置文件中配置history的redis.使用3号库,因为这里只有一台电脑,真实情况是配置另一台电脑上的redis
    2)用户视图下添加接口(POSt /browse_histories/)
        1>新建用户浏览历史记录类视图(UserBrowsingHistoryView()),继承自CreateAPIView
        2>指定序列化器,指定权限
        3>实现序列化器(AddUserBrowsingHistorySerializer),继承自Serializer
        4>定义字段:sku_id = serialziers.IntegerField(label="商品编号",min_value=1)
        5>实现校验字段的方法,检验sku_id是否存在.
        6>重写create方法:
            (1)获得校验后的sku_id
            (2)获得当前用户的user:
                self.context['request'].user
            (3)链接redis
            (4)使用管道pipeline()
            (5)设置redis_key:'history_%s',%user.id
            (6)去重:lrem(redis_key,0,sku_id)
            (7)保存,增加:lpush(redis_key,sku_id)
            (8)截断:ltrim(redis_key,0,用户浏览历史记录常量-1)
            (9)提交到redis
            (10)返回验证后的数据
    3)实现接口(GET /browse_histories):
        1>在UserBrowsingHistoryView视图中实现get方法
        2>获得user_id:user_id=request.user.id
        3>链接redis
        4>查询redis,获得redis列表:lrange('history_%s'%user_id,0,自定义的用户历史记录常量)
        5>查询数据库,遍历的方式:
            for sku_id in sku_id_list:
                sku = SKU.objects.get(id=sku_id)
                skus.append(sku)
        6>实现序列化器,继承ModelSerializer
        7>指定模型类为SKU,指明显示的字段(接口文档上的五个字段)
        8>视图中调用序列化器,传入参数为skus和many=True
        9>返回响应,参数为序列化器对象的.data
        10>修改前端的detail.js和user_center_info.html以及user_center_info.js
        11>添加路由

10.商品列表页
    1)获取商品列表数据
        1>后端接口设计: GET /categories/(?P<category_id>\d+)/skus?page=xxx&page_size=xxx&ordering=xxx
        2>请求参数:路径参数+查询字符串参数
        3>返回数据:JSON
        4>商品应用下视图模块创建类视图(SKUListView()),继承自ListAPIView
        5>指明序列化器SKUSerializer:
            (1).新建序列化器
            (2).继承自ModelSerializer
            (3).指明模型类为SKU
            (4).指明显示的字段(根据接口文档)
        6>指明查询数据集,通过重写get_queryset,根据category_id,和is_launchend过滤查询SKU,把查询结果返回
        7>排序:
            filter_backends=[OrderingFilter]
            ordering_fields=("create_time","peice","sale")
        8>分页:(进行分页查询时DRF默认返回了count,next,previous,results字段),ListModelMixin的list方法会对数据进行过滤和分页
            (1).在全局的utils中自己实现分页的设置(StandarResultsSetPagination),继承自PageNumberPagination
            (2).设置默认每页条数:page_size=2,前端访问指明每页数量的参数名:page_size_query_param="page_size",限制前端指明每页数量的最大限制:max_page_size=20
            (3).全局配置中设置分页为自定义的分页器
        9>添加路由,全局包含路由
        10>修改前端list.html
        11>添加list.js

    2)商品搜索(使用Elasticsearch搜索引擎)
        1>使用Docker安装Elasticsearch
            (1).获取镜像:docker image pull delron/elasticsearch-ik:2.4.6-1.0或者使用源码包docker load -i elasticsearch-ik-2.4.6_docker.tar
            (2).把配置文件目录复制到elasticsearch-2.4.6下
            (3).修改elasticsearch的配置文件 elasticsearc-2.4.6/config/elasticsearch.yml第54行,更改ip地址为本机ip地址:network.host: 10.211.55.5
            
            (4).创建docker容器运行:docker run -dti --network=host --name=elasticsearch -v /home/python/elasticsearch-2.4.6/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0
              
        2>使用haystack对接Elasticsearch:
            (1).安装django中支持drf的扩展类:
                    pip install drf-haystack
                    pip install elasticsearch==2.4.1
                
            (2).配置文件中注册应用:haystack
            (3).配置haystack使用的搜索引擎后端:
                # Haystack
                HAYSTACK_CONNECTIONS = {
                    'default': {
                        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
                            'URL': 'http://10.211.55.5:9200/',  # 此处为elasticsearch运行的服务器ip地址,端口号固定为9200
                            'INDEX_NAME': 'meiduo',  # 指定elasticsearch建立的索引库的名称
    },
}

                # 当添加、修改、删除数据时,自动生成索引
                HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
            
            (4).在goods应用中新建search_indexes.py文件,用于存放索引类(继承自indexes.SearchIndex,indexes.Indexable)
            (5).在templates目录下创建text字段使用的模板文件:templates/search/indexes/goods/sku_text.txt
            (6).在索引类中实现get_model方法,返回值为对应的模型类类名
            (7).模板文件中使用django的模板语法指明建立索引的字段,当通过text字段传参时索引查询的字段(跨字段查询):{{object.id}}{{object.name}}{{object.caption}}
            (8).索引类中新建其他字段,可以通过单独的字段传参查询
            (9).索引类中实现index_queryset方法,指明返回要建立索引的数据查询集,返回的是self.get_model().objects.filter(is_launched=True)
            (10).手动生成初始索引:python manage.py rebuild_index
            (11).在goods/views.py中创建视图SKUSearchViewSet,继承自HaystackViewSet.指明index_models=[SKU],serializer_class=SKUIndexSerializer
             (12).在goods/serializers.py中创建haystack序列化器SKUIndexSerializer,继承自HaystackSerializer.指明索引类和显示的字段:index_classes=[SKUIndex],fields= ('text', 'id', 'name', 'price', 'default_image_url', 'comments')
            (13).注册路由
            (14).测试数据
            (15).前端实现,新建search.html和search.js

posted on 2019-12-17 09:48  夏天的烦恼  阅读(108)  评论(0编辑  收藏  举报