Hold on ,learn by myself!

    redis
    
nosql
    - 不支持sql语法
    - 存储数据都是KV形式
    - Mongodb
    - Redis
    - Hbase hadoop
    - Cassandra hadoop
    
关系型数据库
    mysql/oracle/sql server/关系型数据库
    通用的操作语言
    
关系型比非关系数据库:
    - sql适用关系特别复杂的数据查询场景
    - sql对事务支持非常完善
    - 两者不断取长补短
redis对比其他nosql产品:
    - 支持数据持久化
    - 支持多样数据结构,list,set,zset,hash等
    - 支持数据备份,即master-slave模式的数据备份
    - 所有操作都是原子性的,指多线程没有抢数据的过程
    
redis 应用场景:
    用来做缓存(echcache/memcached),redis所有数据放在内存中
    社交应用

redis-server redis
redis-cli redis
测试是否通信:
    ping  ------------> pang
默认数据库16,通过0-15标示,select n 切换

常用通用命令
    命令集  http://doc.redisfans.com
    - keys *
        keys a*   #查询以a开头的key
    - exists key1  #返回1,0
    - type key
    - del key1
    - expire key seconds #设置过期时间
    - ttl key #查看过期时间
1. string    
    - 二进制,可以接受任何格式的数据,如JPEG或JSON对象描述信息
    - set name value
        - get name
    - mset key1 python key2 linux
        - get key1  ,get key2
        - mget key1 key2
    - append a1 haha  追加字符串
        - get a1    #a1+'haha'
    - setex key seconds value
2. hash
    - 用于存储对象,对象结果是属性、值
    - 值的类型为string
    - hset user name itheima
    - hmset key field1 value1 field2 value2
    - hkeys key
    - hget key field
    - hmget key field1 field2
    - hvals key  #获取所有的属性
    - del key1 #删除整个hash键值,
    - hdel key field1 field2 #删除field1 field2的属性
3. list 
    - 列表中元素类型为string
    - lpush key value1 value2
    - lrange key 0 2  #start stop 返回列表里指定范围的元素
        - lrange key 0 -1 #查询整列元素
    - rpush key value1 value2
    - linsert key before/after b 3
        - b 现有元素
        - 3 插入元素
    - lset key index value  #设置指定元素的值
    - lrem key count value
        - count > 0 从左向右移除
        - count < 0 从右向左移除
        - count = 0 移除所有
4. set 
    - 元素为string类型
    - 无序集合
    - sadd key zhangsan lisi wangwu 
    - smembers key
    - srem key wangwu 

5. zset
    - 有序集合
    - 元素唯一性、不重复
    - 每个元素都关联一个double类型的score权重,通常
    从小到大排序
    - zadd key score1 member1 score2 member2
    - zrange key start stop
    - zrangebyscore key min max
    - zscore key member
    - zrem key member1 member2
    - zremrangebyscore key min max 

python 操作 redis

pip install redis 
from redis import StrictRedis

redis 存储session
而session默认存储在django-session表里
pip install django-redis-sessions==0.5.6

open django工程,改setting配置redis

SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_HOST = 'localhost'
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DB = 2
SESSION_REDIS_PASSWORD = ''
SESSION_REDIS_PREFIX = 'session'


通过redis-cli客户端查看
最后在Base64在线解码
    
主从配置实现读写分离
一个master可以拥有多个slave,一个slave可以有多个slave
    - 实现读写分离
    - 备份主服务、防止主服务挂掉后数据丢失
bind 192.168.26.128
slaveof 192.168.26.128 6379
port 6378
    
    
redis集群
集群:一群通过网络连接的计算机,共同对外提交服务,想一个独立的服务器
主服务、从服务
集群:
    - 软件层面
        - 只有一台电脑,在这一台电脑上启动了多个redis服务。
    - 硬件层面
        - 存在多台实体的电脑,每台电脑上都启动了一个redis或者多个redis服务。
    
集群和python交互:
    pip install redis-by-cluster
    from rediscluster import *
    if __name == 'main':
        try:
            startup_nodes = [
            {'host':'192..','port':'700'},...]
            src = StricRedisCluster(startup_nodes=startup_nodes,
            decode_response = True)
            result = src.set('name','di')
            print(result)
            name = src.geg('name')
            print(name)
        except exception as e:
            print(e)
    
    
---------------mongodb-----------

not only sql
有点:
- 易扩展
- 大数据量、高性能
- 灵活的数据模型    
缺点:
    - 占据的内存比之mysql要多
    
mongodb
mongo
show databases
use douban
db #查看当前数据路
db.dropDatabase()

不手动创建集合(类似mysql的表)
db.createCollection(name,options)
db.createCollection('sub',{capped:true,size:10})
show collections
db.xxx.drop()

Object ID
String
Boolean
Integer   false true ---类似json里的小写false
Double
Arrays
Object
Null
Timestamp
Date


-------------------------flask 单元测试------------
单元测试
    - 程序员自测
    - 一般用于测试一些实现某功能的代码
集成测试
系统测试

def num_div(num1num2):
    #断言为真、则成功,为假,则失败抛出异常/终止程序执行
    #assert num1 int
    assert isinstance(num1,int)
    assert isinstance(num2,int)
    assert num2 != 0

    print(num1/num2)
    
if __name__ == '__main__'
    num_div('a','b')  #AssertionError
        
assertEqual
assertNotEqual
assertTrue
assertFalse
assertIsNone
assertIsNotNone

必须以test_开头

classs LoginTest(unittest.TestCase):
    def test_empty_user_name_password(self):
        client = app.test_client()
        ret = client.post('/login',data={})
        resp = ret.data
        resp = json.loads(resp)
        
        self.assertIn('code',resp)
        self.assertEqual(resp['code'],1)

if __name__ == '__main__':
    unittest.main()
    
cmd
python test.python



class LoginTest(unittest.TestCase):
    def setup(self):
        #相当于 __init__
        self.client = app.test_client()
        #开启测试模式,获取错误信息
      1    app.config['TESTING'] = True 
      2 app.testing = True
    
    def test_xxxx(self):
        .....
 

 
简单单元测试
网络接口测试(视图)
数据库测试
import unittest
from author_book import Author,db,app

class DatabaseTest(unittest.TestCase):
    
    def setUp(self):
        app.testing = True
        #构建测试数据库
        app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:mysql@127.0.0.1:3306/flask_test"
        db.create_all()
    
    def test_add_user(self):
        
        author = Author(name="zhang",email='xxx'..)
        db.session.add(author)
        db.session.commit()
        
        result_author =  Author.query.filter_by(name="zhang").first()
        self.assertNotNone(result_author)
    
    def tearDown(self):
        db.session.remove()
        db.drop_all()
        
        
------------部署-------------

django  uwsgi nginx


用户
Nginx   负载均衡  提供静态文件
业务服务器 flask + Gunicorn        
mysql/redis
        
        
pip install gunicorn
gunicorn -w 4 -b 127.0.0.1:5000 --access-logfile ./logs/og main:app
 
if self.server_version_info < (5,7,20):
    cursor.execute(”SELECT @@tx_isolation")
else:
    cursor.execute("SELECT @@transaction_isolation")
        
        
--------------- 测试-----------------

jekins
    - Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目
jmeter
    - 软件做压力测试
postman
    - 


        创建天天生鲜 B2C 大型网站

1. 电商概念
B2B    Alibaba
C2C    瓜子二手车、淘宝、易趣
B2C    唯品会
C2B    尚品宅配
O2O    美团
F2C    戴尔

2. 开发流程
产品原型的设计 --- 产品经理-----axure

非常关键:
    - 架构设计
    - 数据库设计

3. 数据库分析
mysql
    - 
redis 
    - 若用户多,session服务器     
    - 对于经常访问的如首页,则用缓存服务器
xxx    
    -异步任务处理celery (注册页面发邮件之类的)

分布式文件存储系统fastdfs(不用django默认的media上传文件方式)
    - 


4. 数据库设计:

a.用户模块、商品模块
用户表
    - ID
    - 用户名
    - 密码
    - 邮箱
    - 激活标识
    - 权限标识
地址表(一个用户可能有多个地址)
    - ID
    - 收件人
    - 地址
    - 邮编
    - 联系方式
    - 是否默认
    - 用户ID
商品SKU表
    - ID
    - 名称
    - 简介
    - 价格
    - 单位
    - 库存
    - 销量
    - 详情
    - *图片(就放一张,以空间换取时间)
    - 状态
    - 种类ID
    - sup ID
商品SPU表
    - ID
    - 名称
    - 详情
商品种类表
    - ID
    - 种类名称
    - logo
    - 图片
商品图片表
    - ID
    - 图片
    - sku ID
首页轮播商品表
    - ID
    - sku 
    - 图片
    - index
首页促销表
    - ID
    - 图片
    - 活动url
    - index
首页分类商品展示表
    - ID
    - sku ID
    - 种类ID
    - 展示标识
    - index
    
b. 购物车模块  redis实现
    - redis保存用户历史浏览记录
c. 订单模块

订单信息表
    - 订单ID
    - 地址ID
    - 用户ID
    - 支付方式
    - *总金额
    - *总数目  
    - 运费
    - 支付状态
    - 创建时间
订单商品表
    - ID
    - sku ID
    - 商品数量
    - 商品价格

健表时须知:
    - 此时用的是Ubanto的mysql数据库,需要
        - grant all on test2.* to 'root'@'1.2.3.4' identified
by 'root'
        - flush privileges    
        - migrate
    - choices
    - 富文本编辑器
        - tinymce
        - pip install django-tinymce==2.6.0
    - LANGUAGE_CODE = 'zh-hans'
    - TIME_ZONE = 'Asia/Shanghai'
    - url(r'^',include('goods.urls',namespace='goods'))
    - vervose_name
    - 项目框架搭建
        - 四个app
        - user
        - goods
        - cart
        - order
    - 使用Abstractuser时,settings里需要
    AUTH_USER_MODEL = 'user.User'
    
    
    
class BaseModel(models.Model):
    '''模型类抽象基类’''
    create_time = models.DatetimeField(auto_now_add=True,verbose_name='创建时间')
    cupdate_time = models.DatetimeField(auto_now=True,verbose_name='更新时间')
    is_delete = models.BooleanField(default=False,verbose_name='删除标记')
    
    class Meta:
        #说明是一个抽象类模型
        abstract = True


开始设计前后端
1. 如何设计四个app
2. register.html
    - 动态导入静态文件,{% load staticfiles %}
        link ....  href="{% static 'css/reset.css' %}"
    - 前端post一个勾选按钮(阅读同意),后端收到是
        if allow !== 'on':
            pass
    -  几乎每一个URL都有namespace,注册成功后跳转首页用
        反向解析 return redirect(reverse('goods:index'))
            - goods 是app域名的别名
            - index 是goods里面的别名
    - 在mysql里输入 select * from df_user \G
        信息会竖排显示
    - 数据完整性校验
        if not all([username,password,email]):
            pass
    - 在表里发现 is_active 已经为1激活了,但是我们不想注册即激活,
        需要,在创建用户的时候加入如下:
        user=User.objects.create_user(username,email,password)
        user.is_active = 0
        user.save()
    - 在注册之前先进性校验,register_handle
        判断用户名是否重复,
        try:
        #get有的话只返回一个,没有的话会包异常
            user=User.objects.get(username=username)
        except User.DoesNotExist:
            user = None
        if user:
            return ...
    - 类视图的使用
        - 原理关键在dispatch,getattr
        - from django.view.generic import View
         class RegisterView(View):
            def get(self,request):
                pass
            def post(self,request):
                pass
        url(r'^register$', RegisterView.as_view(), name='register'), # 注册
    - 发送激活邮件,包含激活连接
        - 激活连接中需包含用户身份信息,并加密
        - pip install itsdangerous
        from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
        from itsdangerous import SignatureExpired
        
         # 加密用户的身份信息,生成激活token
            serializer = Serializer(settings.SECRET_KEY, 3600)
            info = {'confirm':user.id}
            token = serializer.dumps(info) # bytes
            token = token.decode()
        #发邮件
            1. django本身有秘钥、
                subject
                message
                sender
                receiver
                html_message
                dend_mail(subject,message,sender,receiver,html_message
                =html_message)  #发送html格式的
        - django网站 -(阻塞执行)-->SMTP服务器 -->目的邮箱
            2.celery使用
                任务发出者 -- 任务队列(broker)-- 任务处理者(worker)        
                        发出任务             监听任务队列
                pip install celery
                任务队列是一种跨线程、跨机器工作的一种机制.
                celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者)
                和worker(任务的处理者). clients发出消息到队列中,broker将队列中的信息派发给
                worker来处理用于处理些IO操作耗时的事务,如上传下载文件、发邮件
                
                from celery import Celery
                # 创建一个Celery类的实例对象
                app = Celery('celery_tasks.tasks', broker='redis://172.16.179.130:6379/8')
                # 定义任务函数
                @app.task
            i.    def send_register_active_email(to_email, username, token):
                    '''发送激活邮件'''
                    # 组织邮件信息
                    subject = '天天生鲜欢迎信息'
                    message = ''
                    sender = settings.EMAIL_FROM
                    receiver = [to_email]
                    html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token)

                    send_mail(subject, message, sender, receiver, html_message=html_message)
                    time.sleep(5)
                
            ii.    # 发邮件
                send_register_active_email.delay(email, username, token)
                
            iii.# Ubuntu虚拟机启动worker
                1.只是启动celery里的worker进程,配置信息需要与django里的task.py文件
                一样,否则django里的变动(time.sleep),Ubuntu不会执行,以当前为准
                2.vi celery_tasks/tasks.py
                    django环境的初始化
                    # 在任务处理者一端加这几句
                    # import os
                    # import django
                    # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
                    # django.setup()
                3.启动
                    celery -A celery_tasks.tasks worker -l info
    -  激活成功后返回登陆页面
        class ActiveView(View):
            '''用户激活'''
            def get(self, request, token):
                '''进行用户激活'''
                # 进行解密,获取要激活的用户信息
                serializer = Serializer(settings.SECRET_KEY, 3600)
                try:
                    info = serializer.loads(token)
                    # 获取待激活用户的id
                    user_id = info['confirm']

                    # 根据id获取用户信息
                    user = User.objects.get(id=user_id)
                    user.is_active = 1
                    user.save()

                    # 跳转到登录页面
                    return redirect(reverse('user:login'))
                except SignatureExpired as e:
                    # 激活链接已过期
                    return HttpResponse('激活链接已过期')

3. login
    - 因为用户多,不能经常调用数据库,使用redis存储session
        https://django-redis-chs.readthedocs.io/zh_CN/latest/
        - pip install django-redis 
        - django缓存配置
            - 指定ip的redis数据库
        - 配置session存储
        - redis-cli -h 192.169.12.1
    - 是否记住用户名    
    i.     class LoginView(View):
        '''登录'''
        def get(self, request):
            '''显示登录页面'''
            # 判断是否记住了用户名
            if 'username' in request.COOKIES:
                username = request.COOKIES.get('username')
                checked = 'checked'
            else:
                username = ''
                checked = ''

            # 使用模板
            return render(request, 'login.html', {'username':username, 'checked':checked})

    ii.    def post(self, request):
            '''登录校验'''
            # 接收数据
            username = request.POST.get('username')
            password = request.POST.get('pwd')
            # 校验数据
            if not all([username, password]):
                return render(request, 'login.html', {'errmsg':'数据不完整'})
            # 业务处理:登录校验
            user = authenticate(username=username, password=password)
            if user is not None:
                # 用户名密码正确
                if user.is_active:
                    # 用户已激活
                    # 记录用户的登录状态
                    login(request, user)
                    # 跳转到首页
                    response = redirect(reverse('goods:index')) # HttpResponseRedirect
                    # 判断是否需要记住用户名
                    remember = request.POST.get('remember')
                    if remember == 'on':
                        # 记住用户名
                        response.set_cookie('username', username, max_age=7*24*3600)
                    else:
                        response.delete_cookie('username')
                    # 返回response
                    return response
                else:
                    # 用户未激活
                    return render(request, 'login.html', {'errmsg':'账户未激活'})
            else:
                # 用户名或密码错误
                return render(request, 'login.html', {'errmsg':'用户名或密码错误'})
    
    iii.<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名">    
        <input type="checkbox" name="remember" {{ checked }}>
4. 用户中心
    - base模板的设计,分base.html  base_no_cart.html,非常重要
        {# 首页 注册 登录 #}
        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
        <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
        {% load staticfiles %}
        <head>
            <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
            {# 网页标题内容块 #}
            <title>{% block title %}{% endblock title %}</title>
            <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
            <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
            {# 网页顶部引入文件块 #}
            {% block topfiles %}{% endblock topfiles %}
        </head>
        。。。。。。

    - 一个用户中心页面可以点三个页面  (用户/订单/收货地址)
        <li><a href="{% url 'user:user' %}" {% if page == 'user' %}class="active"{% endif %}>· 个人信息</a></li>
    - 登录装饰器(如用户界面需要登录)
        i.    login_required   ?next=xxxx
                from django.contrib.auth.decorators import login_required
            settings
                # 配置登录url地址
                LOGIN_URL='/user/login' # /accounts/login
            login.html
                <div class="form_input">
                {# 不设置表单action时,提交表单时,会向浏览器地址栏中的地址提交数据 #}
                .....
            url
                url(r'^$', login_required(UserInfoView.as_view()), name='user'), # 用户中心-信息页
                url(r'^order$', login_required(UserOrderView.as_view()), name='order'), # 用户中心-订单页
                url(r'^address$', login_required(AddressView.as_view()), name='address'), # 用户中心-地址页
            登录视图里的logic处理
                if user.is_active:
                    # 用户已激活
                    # 记录用户的登录状态
                    login(request, user)
                    # 获取登录后所要跳转到的地址
                    # 默认跳转到首页
                    next_url = request.GET.get('next', reverse('goods:index'))
                    # 跳转到next_url
                    response = redirect(next_url) # HttpResponseRedirect

                    # 判断是否需要记住用户名
                    remember = request.POST.get('remember')
                    if remember == 'on':
                        # 记住用户名
                        response.set_cookie('username', username, max_age=7*24*3600)
                    else:
                        response.delete_cookie('username')
                    # 返回response
                    return response
        ii. login_required    
                - 一些经常用的python_package放在utils文件夹里,如mixin.py
                        from django.contrib.auth.decorators import login_required
                        class LoginRequiredMixin(object):
                            @classmethod
                            def as_view(cls, **initkwargs):
                                # 调用父类的as_view
                                view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
                                return login_required(view)
                - 同时类视图调用
                    from utils.mixin import LoginRequiredMixin
                    
                    class UserInfoView(LoginRequiredMixin, View):pass
                    class UserOrderView(LoginRequiredMixin, View):pass

            settings(同上)        
            url     (不需要做处理了)
                    url(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页
                    url(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
                    url(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页
            登录视图里的logic处理(同上)    
    
    - 用户登录欢迎信息(head)
        - 考点  
          # Django会给request对象添加一个属性request.user
          # 如果用户未登录->user是AnonymousUser类的一个实例对象
          # 如果用户登录->user是User类的一个实例对象
          # request.user.is_authenticated()
        - base.html
          {% if user.is_authenticated %}
            <div class="login_btn fl">
                欢迎您:<em>{{ user.username }}</em>
                <span>|</span>
                <a href="{% url 'user:logout' %}">退出</a>
            </div>
            {% else %}
            <div class="login_btn fl">
                <a href="{% url 'user:login' %}">登录</a>
                <span>|</span>
                <a href="{% url 'user:register' %}">注册</a>
            </div>
            {% endif %}
    - logout
         url
            url(r'^logout$', LogoutView.as_view(), name='logout'), # 注销登录
        views
            from django.contrib.auth import authenticate, login, logout
            
            class LogoutView(View):
            '''退出登录'''
                def get(self, request):
                    '''退出登录'''
                    # 清除用户的session信息
                    logout(request)

                    # 跳转到首页
                    return redirect(reverse('goods:index'))    
                
    - 用户中心地址页(默认地址和新添地址的设计)
        i.  post 新上传地址数据,从数据库里查找是否有默认地址
            get 在页面上显示是否有默认地址
            class AddressView(LoginRequiredMixin, View):
                '''用户中心-地址页'''
                def get(self, request):
                    '''显示'''
                    # 获取登录用户对应User对象
                    user = request.user

                    # 获取用户的默认收货地址
                    # try:
                    #     address = Address.objects.get(user=user, is_default=True) # models.Manager
                    # except Address.DoesNotExist:
                    #     # 不存在默认收货地址
                    #     address = None
                    address = Address.objects.get_default_address(user)

                    # 使用模板
                    return render(request, 'user_center_site.html', {'page':'address', 'address':address})

                def post(self, request):
                    '''地址的添加'''
                    # 接收数据
                    receiver = request.POST.get('receiver')
                    addr = request.POST.get('addr')
                    zip_code = request.POST.get('zip_code')
                    phone = request.POST.get('phone')

                    # 校验数据
                    if not all([receiver, addr, phone]):
                        return render(request, 'user_center_site.html', {'errmsg':'数据不完整'})

                    # 校验手机号
                    if not re.match(r'^1[3|4|5|7|8][0-9]{9}$', phone):
                        return render(request, 'user_center_site.html', {'errmsg':'手机格式不正确'})

                    # 业务处理:地址添加
                    # 如果用户已存在默认收货地址,添加的地址不作为默认收货地址,否则作为默认收货地址
                    # 获取登录用户对应User对象
                    user = request.user

                    # try:
                    #     address = Address.objects.get(user=user, is_default=True)
                    # except Address.DoesNotExist:
                    #     # 不存在默认收货地址
                    #     address = None

                    address = Address.objects.get_default_address(user)

                    if address:
                        is_default = False
                    else:
                        is_default = True

                    # 添加地址
                    Address.objects.create(user=user,
                                           receiver=receiver,
                                           addr=addr,
                                           zip_code=zip_code,
                                           phone=phone,
                                           is_default=is_default)

                    # 返回应答,刷新地址页面
                    return redirect(reverse('user:address')) # get请求方式        
        ii. 因为get.post里都用到去models里查询默认数据,可以优化
            - 模型管理器类方法封装
                每个models里都有models.Manager
                            
            1. class AddressManager(models.Manager):
                '''地址模型管理器类'''
                # 1.改变原有查询的结果集:all()
                # 2.封装方法:用户操作模型类对应的数据表(增删改查)
                def get_default_address(self, user):
                    '''获取用户默认收货地址'''
                    # self.model:获取self对象所在的模型类
                    try:
                        address = self.get(user=user, is_default=True)  # models.Manager
                    except self.model.DoesNotExist:
                        # 不存在默认收货地址
                        address = None

                    return address
            2. class Address(BaseModel):
                '''地址模型类'''
                ....
                # 自定义一个模型管理器对象
                objects = AddressManager()
                ..
            3. views调用
                address = Address.objects.get_default_address(user)
                
    - 用户中心个人信息页历史浏览记录            
        - 在用户访问详情页面(SKU),需要添加历史浏览记录
        - 存在表中要经常增删改查不放方便,所以存redis
            - redis数据库->内存性的数据库
            - 表格设计
                1. 所有用户历史记录用一条数据保存
                    hash
                    history:'user_id':'1,2,3'
                2. 一个用户历史记录用一条数据保存
                    list
                    history_user_id:1,2,3
                    添加记录时,用户最新浏览的商品id从列表左侧插入
        - 实际使用
            1. StrictRedis
              i.# Django的缓存配置
                    CACHES = {
                        "default": {
                            "BACKEND": "django_redis.cache.RedisCache",
                            "LOCATION": "redis://172.16.179.130:6379/9",
                            "OPTIONS": {
                                "CLIENT_CLASS": "django_redis.client.DefaultClient",
                            }
                        }
                    }
              ii.form redis import StricRedis
                # 获取用户的历史浏览记录
                # from redis import StrictRedis
                # sr = StrictRedis(host='172.16.179.130', port='6379', db=9)
                  history_key = 'history_%d'%user.id

                # 获取用户最新浏览的5个商品的id
                sku_ids = con.lrange(history_key, 0, 4) # [2,3,1]
        
********     # 从数据库中查询用户浏览的商品的具体信息
                # goods_li = GoodsSKU.objects.filter(id__in=sku_ids)
                # 数据库查询时按遍历的方式,只要id in里面,则查询出来
                #这样就违背了用户真实历史浏览记录了
             i.    # goods_res = []
                # for a_id in sku_ids:
                #     for goods in goods_li:
                #         if a_id == goods.id:
                #             goods_res.append(goods)

                # 遍历获取用户浏览的商品信息
             ii.goods_li = []
                for id in sku_ids:
                    goods = GoodsSKU.objects.get(id=id)
                    goods_li.append(goods)
            
            iii.user_info.html
                    使用{% empty %}标签  ,相当于elseif
                        {% for athlete in athlete_list %}
                            <p>{{ athlete.name }}</p>
                        {% empty %}
                            <p>There are no athletes. Only computer programmers.</p>
                        {% endfor %}
                                    
            2. from django_redis import get_redis_connection
                    con = get_redis_connection('default')    
                    其它同上    
                    
5. fastdfs
    - 概念
        - 分布式文件系统,使用 FastDFS 很容易搭建一套高性能的
          文件服务器集群提供文件上传、下载等服务。 
        - 架构包括 Tracker server 和 Storage server。        
        - 客户端请求 Tracker server 进行文件上传、下载,通过
          Tracker server 调度最终由 Storage server 完成文件上传和下载。         
        - Tracker server 作用是负载均衡和调度
        - Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上
        - 优势:海量存储、存储容量扩展方便、文件内容重复          
                
6. 商品搜索引擎            
    - 搜索引擎
        1. 可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据
            - 如 select * from xxx where name like '%草莓%' or desc like
                '%草莓%'
            - 很
              好吃
              的
              草莓:sku_id1 sku_id2 sku_id5
              字典
        2. 全文检索框架
            可以帮助用户使用搜索引擎
                用户->全文检索框架(haystack)->搜索引擎(whoosh)
    - 安装即使用
        - pip isntall django-haystack
          pip install whoosh
        - 在settings里注册haystack并配置
            - INSTALLED_APPS = (
                    'django.contrib.admin',
                    ...
                    'tinymce', # 富文本编辑器
                    'haystack', # 注册全文检索框架
                    'user', # 用户模块
                    'goods', # 商品模块
                    'cart', # 购物车模块
                    'order', # 订单模块
                )
            - # 全文检索框架的配置
                    HAYSTACK_CONNECTIONS = {
                        'default': {
                            # 使用whoosh引擎
                            # 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
                            'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine',
                            # 索引文件路径
                            'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
                        }
                    }

                    # 当添加、修改、删除数据时,自动生成索引
                    HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
            - 索引文件的生成
                - 在goods应用目录下新建一个search_indexes.py文件,在其中定义一个商品索引类
                    # 定义索引类
                    from haystack import indexes
                    # 导入你的模型类
                    from goods.models import GoodsSKU

                    # 指定对于某个类的某些数据建立索引
                    # 索引类名格式:模型类名+Index
                    class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable):
                        # 索引字段 use_template=True指定根据表中的哪些字段建立索引文件的说明放在一个文件中
                        text = indexes.CharField(document=True, use_template=True)

                        def get_model(self):
                            # 返回你的模型类
                            return GoodsSKU

                        # 建立索引的数据
                        def index_queryset(self, using=None):
                            return self.get_model().objects.all()    
                - 在templates下面新建目录search/indexes/goods。
                    templates
                        search        固定
                            indexes      固定
                                goods        模型类所在应用app
                                    goodssku_text.txt        模型类名小写_text.txt
                - 在此目录下面新建一个文件goodssku_text.txt并编辑内容如下。
                    # 指定根据表中的哪些字段建立索引数据
                    {{ object.name }} # 根据商品的名称建立索引
                    {{ object.desc }} # 根据商品的简介建立索引
                    {{ object.goods.detail }} # 根据商品的详情建立索引,根据外键跨表查询
                - 使用命令生成索引文件    
                    python manage.py rebuild_index        
                        - 会在whoosh_index文件夹里建立xx个商品索引(所有)
            - 全文检索的使用
                - 配置url
                    url(r'^search', include('haystack.urls')), # 全文检索框架
                - 表单搜索时设置表单内容如下
                    base.html    get和q固定
                        <div class="search_con fl">
                            <form method="get" action="/search">
                                <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                                <input type="submit" class="input_btn fr" name="" value="搜索">
                            </form>
                        </div>
                - 全文检索结果
                    搜索出结果后,haystack会把搜索出的结果传递给templates/search
                    目录下的search.html,传递的上下文包括:
                        query:搜索关键字
                        page:当前页的page对象 –>遍历page对象,获取到的是SearchResult类的实例对象,
                                对象的属性object才是模型类的对象。
                        paginator:分页paginator对象
                    通过HAYSTACK_SEARCH_RESULTS_PER_PAGE 可以控制每页显示数量。
                - search.html分页器的经典使用
                     <div class="pagenation">
                        {% if page.has_previous %}
                        <a href="/search?q={{ query }}&page={{ page.previous_page_number }}"><上一页</a>
                        {% endif %}
                        {% for pindex in paginator.page_range %}
                            {% if pindex == page.number %}
                                <a href="/search?q={{ query }}&page={{ pindex }}" class="active">{{ pindex }}</a>
                            {% else %}
                                <a href="/search?q={{ query }}&page={{ pindex }}">{{ pindex }}</a>
                            {% endif %}
                        {% endfor %}
                        {% if spage.has_next %}
                        <a href="/search?q={{ query }}&page={{ page.next_page_number }}">下一页></a>
                        {% endif %}
                        </div>
                    </div>
            
                - 完成上面步奏之后,可以简单搜索,但是无法根据商品详情里的中文字段搜索
                    需要优化,商品搜索、改变分词方式
                    - 安装jieba分词模块
                        pip install jieba
                            str = '很不错的草莓'
                            res = jieba.cut(str,cut_all=True)
                            for val in res:
                                print(val)
                    - 。。。。。
                    - settings里配置需要更改
                    - 最后重新创建索引数据
                        python manage.py rebuild_index

7. 订单并发处理
    - 悲观锁
        - select * from xx where id=2 for update
        - 应用场景:
            try:
                #select * from df_goods_sku where id=sku_id for update;
                sku=GoodsSKU.object.select_for_update().get(id=sku_id)
            except:
                transaction.savepoint_rollback(save_id)
                return ...                                                
    - 乐观锁
        - 查询数据时不加锁,在更新时进行判断
        - 判断更新时的库存和之前查出的库存是否一致
        - 操作
            -
            for i in range(3):
                #update df_goods_sku set tock=stock,sales=new_sales where id=
                    sku_id and stock=origin_stock
                  res=GoodsSKU.object.filter(id=sku_id,stock=stock).update(
                  stock=new_stock,sales=new_sales)
                  if res==0:  库存为0
                    if i==2:    #尝试第3次查询
                        transaction.savepoint_rollback(save_id)
            - mysql事务隔离性
                事务隔离级别
                    - Read Uncommitted
                    - Read Committed(大多数数据库默认)
                    - Repeatable Read(mysql默认,产生幻读)
                    - serializable(可串行化,解决幻读问题,但容易引发竞争)
            - 重新配置mysql.conf为read committed
    总结:
        - 在冲突较少的时候使用乐观锁,因为省去了加锁、减锁的时间
        - 在冲突多的时候、乐观锁重复操作的代价比较大时使用悲观锁
        - 判断的时候,更新失败不一定是库存不足,需要再去尝试


                    
SKU & SPU
    - SPU
        - Standard product unittest
        - 商品信息聚合的最小单位
        - 如iphone,
    - SKU
        - STOCK keeping unittest
        - 库存量进出计量单位
        - 如纺织品中一个SKU表示:
            规格、颜色、款式
        











    
    
    
    
View Code

单元测试

单元测试

pip install unittest
assert xxx xxx
false  true

xitong
jicheng 

一些功能基础测试
网络报文测试
数据库测试

def test_div(num1,num2):
    assert num1 int
    assert num1 int
    assert isinstance(num1,int)
    assert num2 != 0
    
    AssertionError

assertEqual
assertNotEqual
assertIn
assertNotIn

assertTrue
assertFalse
assertNone
assertNotNone

class LoginTest(unittest.TestCase):
    
    def test_empty(self):
        client = app.test_client()
        rep = client.post('/login',data={})
        rep = rep.data
        rep = json.loads(rep)
        
        self.assertIn('code',resp)
        self.assertEqual(rep['code'],1)
        
if __name__ == '__main__':
    unittest.main()

python test.py

class LoginTest(unittest.TestCase):
    
    def setUp(self):
        self.client = app.test_client()
        app.testing = True
    
    def test_empty_user(self):
    
    
    def tearDown(self):
        sss
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
View Code

flask

回顾:
    1.谈谈你对django和flask的认识。
    
    2.flask和django最大的不同点:request/session 
    
    3.flask知识点
        - 模板+静态文件,app= Flask(__name__,....)
        - 路由 
            @app.route('/index',methods=["GET"])
        - 请求 
            request.form 
            request.args
            request.method 
        - 响应 
            ""
            render
            redirect
        - session 
            session['xx'] = 123
            session.get('xx')
    4. 路飞总共有几个项目
        - 管理后台
        - 导师后台
        - 主站
        
    5. 路飞主站业务
        - 课程
            - 课程列表
            - 课程详细
            - 大纲、导师、推荐课程
            - 价格策略
            - 章节和课时
            - 常见问题
        - 深科技
            - 文章列表
            - 文章详细
            - 收藏
            - 评论
            - 点赞
        - 支付
            - 购物车(4- 结算中心(3- 立即支付(1)
            知识点:
                - redis
                - 支付宝
                - 消息推送
                - 构建数据结构
                - 优惠券+贝里+支付宝
        - 个人中心
        - 课程中心
        
    6. 播放视频:CC视频
        - 加密
        - 非加密


今日内容:
    1. 配置文件
    2. 路由系统 
    3. 视图
    4. 请求相关
    5. 响应 
    6. 模板渲染
    7. session 
    8. 闪现
    9. 中间件 
    10. 蓝图(blueprint)
    11. 特殊装饰器
    
内容详细:
    知识点:
        - 给你一个路径 “settings.Foo”,可以找到类并获取去其中的大写的静态字段。
        
            settings.py
                class Foo:
                    DEBUG = True
                    TEST = True
                
            xx.py 
                import importlib

                path = "settings.Foo"

                p,c = path.rsplit('.',maxsplit=1)
                m = importlib.import_module(p)
                cls = getattr(m,c)

                # 如果找到这个类?
                for key in dir(cls):
                    if key.isupper():
                        print(key,getattr(cls,key))
    1. 配置文件
        
        app.config.from_object("settings.DevelopmentConfig")
        
        
        class Config(object):
            DEBUG = False
            TESTING = False
            DATABASE_URI = 'sqlite://:memory:'


        class ProductionConfig(Config):
            DATABASE_URI = 'mysql://user@localhost/foo'


        class DevelopmentConfig(Config):
            DEBUG = True


        class TestingConfig(Config):
            TESTING = True
    2. 路由系统 
        - endpoint,反向生成URL,默认函数名
        - url_for('endpoint') / url_for("index",nid=777)
        - 动态路由:
            @app.route('/index/<int:nid>',methods=['GET','POST'])
            def index(nid):
                print(nid)
                return "Index"
        
    3. FBV
    
    
    4. 请求相关 
        # 请求相关信息
        # request.method
        # request.args
        # request.form
        # request.values
        # request.cookies
        # request.headers
        # request.path
        # request.full_path
        # request.script_root
        # request.url
        # request.base_url
        # request.url_root
        # request.host_url
        # request.host
        # request.files
        # obj = request.files['the_file_name']
        # obj.save('/var/www/uploads/' + secure_filename(f.filename))
        
    5. 响应:
            响应体:
                return “asdf”
                return jsonify({'k1':'v1'})
                return render_template('xxx.html')
                return redirect()
            
            定制响应头:   
                obj = make_response("asdf")
                obj.headers['xxxxxxx'] = '123'
                obj.set_cookie('key', 'value')
                return obj
        
        
    示例程序:学生管理
        
        版本一:
            @app.route('/index')
            def index():
                if not session.get('user'):
                    return redirect(url_for('login'))
                return render_template('index.html',stu_dic=STUDENT_DICT)
        版本二:
            import functools
            def auth(func):
                @functools.wraps(func)
                def inner(*args,**kwargs):
                    if not session.get('user'):
                        return redirect(url_for('login'))
                    ret = func(*args,**kwargs)
                    return ret
                return inner
        
            @app.route('/index')
            @auth
            def index():
                return render_template('index.html',stu_dic=STUDENT_DICT)
        
            应用场景:比较少的函数中需要额外添加功能。
            
        版本三:before_request
            @app.before_request
            def xxxxxx():
                if request.path == '/login':
                    return None

                if session.get('user'):
                    return None

                return redirect('/login')

        
    6. 模板渲染 
        - 基本数据类型:可以执行python语法,如:dict.get()  list['xx']
        - 传入函数
            - django,自动执行
            - flask,不自动执行
        - 全局定义函数
            @app.template_global()
            def sb(a1, a2):
                # {{sb(1,9)}}
                return a1 + a2

            @app.template_filter()
            def db(a1, a2, a3):
                # {{ 1|db(2,3) }}
                return a1 + a2 + a3
        - 模板继承
            layout.html
                <!DOCTYPE html>
                <html lang="zh-CN">
                <head>
                    <meta charset="UTF-8">
                    <title>Title</title>
                    <meta name="viewport" content="width=device-width, initial-scale=1">
                </head>
                <body>
                    <h1>模板</h1>
                    {% block content %}{% endblock %}
                </body>
                </html>
            
            tpl.html
                {% extends "layout.html"%}


                {% block content %}
                    {{users.0}}
                    

                {% endblock %}    
        - include 
    
    
            {% include "form.html" %}
            
            
            form.html 
                <form>
                    asdfasdf
                    asdfasdf
                    asdf
                    asdf
                </form>
        - 宏
            {% macro ccccc(name, type='text', value='') %}
                <h1>宏</h1>
                <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
                <input type="submit" value="提交">
            {% endmacro %}

            {{ ccccc('n1') }}

            {{ ccccc('n2') }}
            
        - 安全
            - 前端: {{u|safe}}
            - 前端: MarkUp("asdf")
        
    
    7. session 
        当请求刚到来:flask读取cookie中session对应的值:eyJrMiI6NDU2LCJ1c2VyIjoib2xkYm95,将该值解密并反序列化成字典,放入内存以便视图函数使用。
        视图函数:
            @app.route('/ses')
            def ses():
                session['k1'] = 123
                session['k2'] = 456
                del session['k1']

                return "Session"

                    
                        
                    session['xxx'] = 123
                    session['xxx']
                    
        当请求结束时,flask会读取内存中字典的值,进行序列化+加密,写入到用户cookie中。
    
    
    8. 闪现,在session中存储一个数据,读取时通过pop将数据移除。
        from flask import Flask,flash,get_flashed_messages
        @app.route('/page1')
        def page1():

            flash('临时数据存储','error')
            flash('sdfsdf234234','error')
            flash('adasdfasdf','info')

            return "Session"

        @app.route('/page2')
        def page2():
            print(get_flashed_messages(category_filter=['error']))
            return "Session"
        
    
    9. 中间件 
        - call方法什么时候出发?
            - 用户发起请求时,才执行。
        - 任务:在执行call方法之前,做一个操作,call方法执行之后做一个操作。
            class Middleware(object):
                def __init__(self,old):
                    self.old = old

                def __call__(self, *args, **kwargs):
                    ret = self.old(*args, **kwargs)
                    return ret


            if __name__ == '__main__':
                app.wsgi_app = Middleware(app.wsgi_app)
                app.run()
            
    
    10. 特殊装饰器 
    
        1. before_request
        
        2. after_request
        
            示例:
                from flask import Flask
                app = Flask(__name__)


                @app.before_request
                def x1():
                    print('before:x1')
                    return ''

                @app.before_request
                def xx1():
                    print('before:xx1')


                @app.after_request
                def x2(response):
                    print('after:x2')
                    return response

                @app.after_request
                def xx2(response):
                    print('after:xx2')
                    return response



                @app.route('/index')
                def index():
                    print('index')
                    return "Index"


                @app.route('/order')
                def order():
                    print('order')
                    return "order"


                if __name__ == '__main__':

                    app.run()
        
        3. before_first_request
        
            from flask import Flask
            app = Flask(__name__)

            @app.before_first_request
            def x1():
                print('123123')


            @app.route('/index')
            def index():
                print('index')
                return "Index"


            @app.route('/order')
            def order():
                print('order')
                return "order"


            if __name__ == '__main__':

                app.run()

        
        4. template_global
        
        5. template_filter
        
        6. errorhandler
            @app.errorhandler(404)
            def not_found(arg):
                print(arg)
                return "没找到"

    
总结:
    - 配置文件
    - 路由 
    - 视图:FBV
    - 请求 
    - 响应 
        obj = make_response("adfasdf")
        obj.headers['x'] = asdfasdf 
        return obj 
    - 模板 
    - session
    - flash 
    - 中间件
    - 特殊装饰器 


1. 上下文
    - request在django和flask中不一样
      全局变量-->线程局部变量,使用起来就像线程的局部
        变量一样
    - 如用户A,B..同时访问/index,name=XX,在视图里
    request.form.get('name')是多少呢,
        由此才有上下文的概念将之隔开处理
            {
                “线程A”:{
                    form:{'name':"zhangsan"}
                    args:
                },
                “线程A”:{
                        form:{'name':"lisi"}
                        args:
                    },                    
            }
        - 并发(处理多少个线程资源)
        
    - 请求上下文 
        - request/session(每个用户独有的session信息)
    - 应用上下文
        - current_app
            表示当前运行程序文件的程序实例
        - g
            处理请求时,用于临时存储的对象,每次
            请求都会重设这个变量
        
2. 请求钩子
        - before_first_request
            在第一次请求处理之前先被执行
        - before request
            在每次请求之前都被执行
        - after_request
            在每次请求之后执行前提是视图无异常
        - teaddown_request
            在每次请求之后都被执行

3. flask_script  类似django的manage.py起管理作用
    pipn install Flask-Script
    使用
        - 在xx.py 里需要
            from flask_script import Manager  #启动命令的管理类
            app = Flask(__name__)
            manager = Manager(app)    
            
            @app.route()"/index"
            def index():
                return ccccc
                
            if __name__=='__main__':
                #通过管理对象来启动app
                manager.run()
        - python xx.py runserver -h -p....
        - python xx.py shell
4.sqlalchemy
    - 其它框架都能用的关系型数据库
    - pip install flask-sqlalchemy
View Code

 flask-sqlacchemy

4.sqlalchemy
    - 其它框架都能用的关系型数据库
    - pip install flask-sqlalchemy
    - 要连接mysql数据库,仍需要安装flask-mysqldb
        pip install flask-mysqldb    
    - 初始化配置
        from flask import Flask
        from flask_sqlalchemy import SQLAlchemy

        app = Flask(__name__)
        class Config(object):
            """配置参数"""
            # sqlalchemy的配置参数
            SQLALCHEMY_DATABASE_URI = "mysql://root:mysql@127.0.0.1:3306/db_python04"
            # 设置sqlalchemy自动更跟踪数据库
            SQLALCHEMY_TRACK_MODIFICATIONS = True

        app.config.from_object(Config)
        # 创建数据库sqlalchemy工具对象
        db = SQLAlchemy(app)
        。。。。
        
    - 创建数据库模型表models
        表名常见规范
            数据库缩写_表名  ihome--> ih_user
            class Role(db.Model):
                """用户角色/身份表"""
                __tablename__ = "tbl_roles"

                id = db.Column(db.Integer, primary_key=True)
                name = db.Column(db.String(32), unique=True)
                users = db.relationship("User", backref="role")    
            class User(db.Model):
                """用户表"""
                __tablename__ = "tbl_users"  # 指明数据库的表名

                id = db.Column(db.Integer, primary_key=True)  # 整型的主键,会默认设置为自增主键
                name = db.Column(db.String(64), unique=True)
                email = db.Column(db.String(128), unique=True)
                password = db.Column(db.String(128))
                role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id"))        
        - 分析
            - db.Column在表中都是真实存在的数据,relationship非真
            - 有了外键,可以User.role_id.name正向查询,但不能表名小写_set反向查询
            - 有了relationship,可以直接Role.users.name,对象查询
            - 但是只有外键的话,如User.role_id仅仅是数字,若想为对象,需要加个
                backref的方法,则User.role.name就可以直接查询了
                
        -  # 清除数据库里的所有数据(第一次才用)
            db.drop_all()

            # 创建所有的表
            db.create_all()    
    - 保存(增加)数据
        - 增加单条数据
              role1 = Role(name="admin")
              # session记录对象任务
              db.session.add(role1)
              # 提交任务到数据库中
              db.session.commit()
        
        - 增加多条数据        
              us1 = User(name='wang', email='wang@163.com', password='123456', role_id=role1.id)
              us2 = User(name='zhang', email='zhang@189.com', password='201512', role_id=role2.id)
              us3 = User(name='chen', email='chen@126.com', password='987654', role_id=role2.id)
              us4 = User(name='zhou', email='zhou@163.com', password='456789', role_id=role1.id)
              
              # 一次保存多条数据
              db.session.add_all([us1, us2, us3, us4])
              db.session.commit()

    - 查询数据
        - 查询多条、查询一条
            Role.query.all()
            Out[2]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]

            In [3]: li = Role.query.all()

            In [4]: li
            Out[4]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]

            In [5]: r = li[0]

            In [6]: type(r)
            Out[6]: db_demo.Role

            In [7]: r.name
            Out[7]: u'admin'

            In [8]: Role.query.first()
            Out[8]: <db_demo.Role at 0x10388d190>

            In [9]: r = Role.query.first()

            In [10]: r.name
            Out[10]: u'admin'

            #  根据主键id获取对象
            In [11]: r = Role.query.get(2)

            In [12]: r
            Out[12]: <db_demo.Role at 0x10388d310>

            In [13]: r.name
            Out[13]: u'stuff'

            In [14]:


            # 另一种查询方式
            In [15]: db.session.query(Role).all()
            Out[15]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]

            In [16]: db.session.query(Role).get(2)
            Out[16]: <db_demo.Role at 0x10388d310>

            In [17]: db.session.query(Role).first()
            Out[17]: <db_demo.Role at 0x10388d190>

            In [18]:

            In [18]: User.query.filter_by(name="wang")
            Out[18]: <flask_sqlalchemy.BaseQuery at 0x1038c90d0>

            In [19]: User.query.filter_by(name="wang").all()
            Out[19]: [<db_demo.User at 0x1038c87d0>]

            In [20]: User.query.filter_by(name="wang").first()
            Out[20]: <db_demo.User at 0x1038c87d0>

            In [21]: user = User.query.filter_by(name="wang").first()

            In [22]: user.name
            Out[22]: u'wang'

            In [23]: user.email
            Out[23]: u'wang@163.com'

            In [24]: User.query.filter_by(name="wang", role_id=1).first()
            Out[24]: <db_demo.User at 0x1038c87d0>

            In [25]: User.query.filter_by(name="wang", role_id=2).first()

            In [26]: user = User.query.filter_by(name="wang", role_id=2).first()

            In [27]: type(user)
            Out[27]: NoneType

            In [28]:


            In [28]: user = User.query.filter(User.name=="wang", User.role_id==1).first
                ...: ()

            In [29]: user
            Out[29]: <db_demo.User at 0x1038c87d0>

            In [30]: user.name
            Out[30]: u'wang'

            In [31]: from sqlalchemy import or_

            In [32]: User.query.filter(or_(User.name=="wang", User.email.endswith("163.com")
                ...: )).all()
            Out[32]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>]

            In [33]: li = User.query.filter(or_(User.name=="wang", User.email.endswith("163.
                ...: com"))).all()

            In [34]: li[0].name
            Out[34]: u'wang'

            In [35]: li[1].name
            Out[35]: u'zhou'

            In [36]:


            # offset偏移  跳过几条
            In [36]: User.query.offset(2).all()
            Out[36]: [<db_demo.User at 0x1038c0950>, <db_demo.User at 0x1038ef310>]

            In [37]: li = User.query.offset(2).all()

            In [38]: li[0].name
            Out[38]: u'chen'

            In [39]: li[1].name
            Out[39]: u'zhou'

            In [40]:


            In [42]: li = User.query.offset(1).limit(2).all()

            In [43]: li
            Out[43]: [<db_demo.User at 0x1038fd990>, <db_demo.User at 0x1038c0950>]

            In [44]: li[0].name
            Out[44]: u'zhang'

            In [45]: li[1].name
            Out[45]: u'chen'

            In [46]:

            In [50]: User.query.order_by("-id").all()
            Out[50]:
            [<db_demo.User at 0x1038ef310>,
             <db_demo.User at 0x1038c0950>,
             <db_demo.User at 0x1038fd990>,
             <db_demo.User at 0x1038c87d0>]

            In [51]:

            In [51]: li = User.query.order_by(User.id.desc()).all()

            In [52]: li
            Out[52]:
            [<db_demo.User at 0x1038ef310>,
             <db_demo.User at 0x1038c0950>,
             <db_demo.User at 0x1038fd990>,
             <db_demo.User at 0x1038c87d0>]

            In [53]: li[0].name
            Out[53]: u'zhou'

            In [54]: li[3].name
            Out[54]: u'wang'

            In [55]:


            In [55]: from sqlalchemy import func

            In [56]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
                ...: d)
            Out[56]: <flask_sqlalchemy.BaseQuery at 0x103a38050>

            In [57]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
                ...: d).all()
            Out[57]: [(1L, 2L), (2L, 2L)]

            In [58]:

        - 跨表查询数据    
            - 关联查询
            - 定义显示信息
                def __repr__(self):
                    return "User object: name=%s" % self.name

            In [61]: ro = Role.query.get(1)

            In [62]: type(ro)
            Out[62]: db_demo.Role

            In [63]: ro.users
            Out[63]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>]

            In [64]: ro.users[0].name
            Out[64]: u'wang'

            In [65]: ro.users[1].name
            Out[65]: u'zhou'

            In [66]:



            In [67]: user
            Out[67]: <db_demo.User at 0x1038c87d0>

            In [68]: user.role_id
            Out[68]: 1L

            In [69]: Role.query.get(user.role_id)
            Out[69]: <db_demo.Role at 0x10388d190>

            In [70]: user.role
            Out[70]: <db_demo.Role at 0x10388d190>

            In [71]: user.role.name
            Out[71]: u'admin'

            In [72]:

        - 数据的修改与删除
            # 更新
            In [14]: User.query.filter_by(name="zhou").update({"name": "python", "emai
                ...: l": "python@itast.cn"})
            Out[14]: 1L

            In [15]: db.session.commit()

            In [16]:
            # 删除
            In [16]: user = User.query.get(3)

            In [17]: db.session.delete(user)

            In [18]: db.session.commit()

            In [19]:
             
5.flask migrate  数据库迁移
    - 安装
        pip install flask-migrate
View Code

 add daily

    redis
    
nosql
    - 不支持sql语法
    - 存储数据都是KV形式
    - Mongodb
    - Redis
    - Hbase hadoop
    - Cassandra hadoop
    
关系型数据库
    mysql/oracle/sql server/关系型数据库
    通用的操作语言
    
关系型比非关系数据库:
    - sql适用关系特别复杂的数据查询场景
    - sql对事务支持非常完善
    - 两者不断取长补短
redis对比其他nosql产品:
    - 支持数据持久化
    - 支持多样数据结构,list,set,zset,hash等
    - 支持数据备份,即master-slave模式的数据备份
    - 所有操作都是原子性的,指多线程没有抢数据的过程
    
redis 应用场景:
    用来做缓存(echcache/memcached),redis所有数据放在内存中
    社交应用

redis-server redis
redis-cli redis
测试是否通信:
    ping  ------------> pang
默认数据库16,通过0-15标示,select n 切换

常用通用命令
    命令集  http://doc.redisfans.com
    - keys *
        keys a*   #查询以a开头的key
    - exists key1  #返回1,0
    - type key
    - del key1
    - expire key seconds #设置过期时间
    - ttl key #查看过期时间
1. string    
    - 二进制,可以接受任何格式的数据,如JPEG或JSON对象描述信息
    - set name value
        - get name
    - mset key1 python key2 linux
        - get key1  ,get key2
        - mget key1 key2
    - append a1 haha  追加字符串
        - get a1    #a1+'haha'
    - setex key seconds value
2. hash
    - 用于存储对象,对象结果是属性、值
    - 值的类型为string
    - hset user name itheima
    - hmset key field1 value1 field2 value2
    - hkeys key
    - hget key field
    - hmget key field1 field2
    - hvals key  #获取所有的属性
    - del key1 #删除整个hash键值,
    - hdel key field1 field2 #删除field1 field2的属性
3. list 
    - 列表中元素类型为string
    - lpush key value1 value2
    - lrange key 0 2  #start stop 返回列表里指定范围的元素
        - lrange key 0 -1 #查询整列元素
    - rpush key value1 value2
    - linsert key before/after b 3
        - b 现有元素
        - 3 插入元素
    - lset key index value  #设置指定元素的值
    - lrem key count value
        - count > 0 从左向右移除
        - count < 0 从右向左移除
        - count = 0 移除所有
4. set 
    - 元素为string类型
    - 无序集合
    - sadd key zhangsan lisi wangwu 
    - smembers key
    - srem key wangwu 

5. zset
    - 有序集合
    - 元素唯一性、不重复
    - 每个元素都关联一个double类型的score权重,通常
    从小到大排序
    - zadd key score1 member1 score2 member2
    - zrange key start stop
    - zrangebyscore key min max
    - zscore key member
    - zrem key member1 member2
    - zremrangebyscore key min max 

python 操作 redis

pip install redis 
from redis import StrictRedis

redis 存储session
而session默认存储在django-session表里
pip install django-redis-sessions==0.5.6

open django工程,改setting配置redis

SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_HOST = 'localhost'
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DB = 2
SESSION_REDIS_PASSWORD = ''
SESSION_REDIS_PREFIX = 'session'


通过redis-cli客户端查看
最后在Base64在线解码
    
主从配置实现读写分离
一个master可以拥有多个slave,一个slave可以有多个slave
    - 实现读写分离
    - 备份主服务、防止主服务挂掉后数据丢失
bind 192.168.26.128
slaveof 192.168.26.128 6379
port 6378
    
    
redis集群
集群:一群通过网络连接的计算机,共同对外提交服务,想一个独立的服务器
主服务、从服务
集群:
    - 软件层面
        - 只有一台电脑,在这一台电脑上启动了多个redis服务。
    - 硬件层面
        - 存在多台实体的电脑,每台电脑上都启动了一个redis或者多个redis服务。
    
集群和python交互:
    pip install redis-by-cluster
    from rediscluster import *
    if __name == 'main':
        try:
            startup_nodes = [
            {'host':'192..','port':'700'},...]
            src = StricRedisCluster(startup_nodes=startup_nodes,
            decode_response = True)
            result = src.set('name','di')
            print(result)
            name = src.geg('name')
            print(name)
        except exception as e:
            print(e)
    
    
---------------mongodb-----------

not only sql
有点:
- 易扩展
- 大数据量、高性能
- 灵活的数据模型    
缺点:
    - 占据的内存比之mysql要多
    
mongodb
mongo
show databases
use douban
db #查看当前数据路
db.dropDatabase()

不手动创建集合(类似mysql的表)
db.createCollection(name,options)
db.createCollection('sub',{capped:true,size:10})
show collections
db.xxx.drop()

Object ID
String
Boolean
Integer   false true ---类似json里的小写false
Double
Arrays
Object
Null
Timestamp
Date


-------------------------flask 单元测试------------
单元测试
    - 程序员自测
    - 一般用于测试一些实现某功能的代码
集成测试
系统测试

def num_div(num1num2):
    #断言为真、则成功,为假,则失败抛出异常/终止程序执行
    #assert num1 int
    assert isinstance(num1,int)
    assert isinstance(num2,int)
    assert num2 != 0

    print(num1/num2)
    
if __name__ == '__main__'
    num_div('a','b')  #AssertionError
        
assertEqual
assertNotEqual
assertTrue
assertFalse
assertIsNone
assertIsNotNone

必须以test_开头

classs LoginTest(unittest.TestCase):
    def test_empty_user_name_password(self):
        client = app.test_client()
        ret = client.post('/login',data={})
        resp = ret.data
        resp = json.loads(resp)
        
        self.assertIn('code',resp)
        self.assertEqual(resp['code'],1)

if __name__ == '__main__':
    unittest.main()
    
cmd
python test.python



class LoginTest(unittest.TestCase):
    def setup(self):
        #相当于 __init__
        self.client = app.test_client()
        #开启测试模式,获取错误信息
      1    app.config['TESTING'] = True 
      2 app.testing = True
    
    def test_xxxx(self):
        .....
 

 
简单单元测试
网络接口测试(视图)
数据库测试
import unittest
from author_book import Author,db,app

class DatabaseTest(unittest.TestCase):
    
    def setUp(self):
        app.testing = True
        #构建测试数据库
        app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:mysql@127.0.0.1:3306/flask_test"
        db.create_all()
    
    def test_add_user(self):
        
        author = Author(name="zhang",email='xxx'..)
        db.session.add(author)
        db.session.commit()
        
        result_author =  Author.query.filter_by(name="zhang").first()
        self.assertNotNone(result_author)
    
    def tearDown(self):
        db.session.remove()
        db.drop_all()
        
        
------------部署-------------

django  uwsgi nginx


用户
Nginx   负载均衡  提供静态文件
业务服务器 flask + Gunicorn        
mysql/redis
        
        
pip install gunicorn
gunicorn -w 4 -b 127.0.0.1:5000 --access-logfile ./logs/og main:app
 
if self.server_version_info < (5,7,20):
    cursor.execute(”SELECT @@tx_isolation")
else:
    cursor.execute("SELECT @@transaction_isolation")
        
        
--------------- 测试-----------------

jekins
    - Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目
jmeter
    - 软件做压力测试
postman
    - 


        创建天天生鲜 B2C 大型网站

1. 电商概念
B2B    Alibaba
C2C    瓜子二手车、淘宝、易趣
B2C    唯品会
C2B    尚品宅配
O2O    美团
F2C    戴尔

2. 开发流程
产品原型的设计 --- 产品经理-----axure

非常关键:
    - 架构设计
    - 数据库设计

3. 数据库分析
mysql
    - 
redis 
    - 若用户多,session服务器     
    - 对于经常访问的如首页,则用缓存服务器
xxx    
    -异步任务处理celery (注册页面发邮件之类的)

分布式文件存储系统fastdfs(不用django默认的media上传文件方式)
    - 


4. 数据库设计:

a.用户模块、商品模块
用户表
    - ID
    - 用户名
    - 密码
    - 邮箱
    - 激活标识
    - 权限标识
地址表(一个用户可能有多个地址)
    - ID
    - 收件人
    - 地址
    - 邮编
    - 联系方式
    - 是否默认
    - 用户ID
商品SKU表
    - ID
    - 名称
    - 简介
    - 价格
    - 单位
    - 库存
    - 销量
    - 详情
    - *图片(就放一张,以空间换取时间)
    - 状态
    - 种类ID
    - sup ID
商品SPU表
    - ID
    - 名称
    - 详情
商品种类表
    - ID
    - 种类名称
    - logo
    - 图片
商品图片表
    - ID
    - 图片
    - sku ID
首页轮播商品表
    - ID
    - sku 
    - 图片
    - index
首页促销表
    - ID
    - 图片
    - 活动url
    - index
首页分类商品展示表
    - ID
    - sku ID
    - 种类ID
    - 展示标识
    - index
    
b. 购物车模块  redis实现
    - redis保存用户历史浏览记录
c. 订单模块

订单信息表
    - 订单ID
    - 地址ID
    - 用户ID
    - 支付方式
    - *总金额
    - *总数目  
    - 运费
    - 支付状态
    - 创建时间
订单商品表
    - ID
    - sku ID
    - 商品数量
    - 商品价格

健表时须知:
    - 此时用的是Ubanto的mysql数据库,需要
        - grant all on test2.* to 'root'@'1.2.3.4' identified
by 'root'
        - flush privileges    
        - migrate
    - choices
    - 富文本编辑器
        - tinymce
        - pip install django-tinymce==2.6.0
    - LANGUAGE_CODE = 'zh-hans'
    - TIME_ZONE = 'Asia/Shanghai'
    - url(r'^',include('goods.urls',namespace='goods'))
    - vervose_name
    - 项目框架搭建
        - 四个app
        - user
        - goods
        - cart
        - order
    - 使用Abstractuser时,settings里需要
    AUTH_USER_MODEL = 'user.User'
    
    
    
class BaseModel(models.Model):
    '''模型类抽象基类’''
    create_time = models.DatetimeField(auto_now_add=True,verbose_name='创建时间')
    cupdate_time = models.DatetimeField(auto_now=True,verbose_name='更新时间')
    is_delete = models.BooleanField(default=False,verbose_name='删除标记')
    
    class Meta:
        #说明是一个抽象类模型
        abstract = True


开始设计前后端
1. 如何设计四个app
2. register.html
    - 动态导入静态文件,{% load staticfiles %}
        link ....  href="{% static 'css/reset.css' %}"
    - 前端post一个勾选按钮(阅读同意),后端收到是
        if allow !== 'on':
            pass
    -  几乎每一个URL都有namespace,注册成功后跳转首页用
        反向解析 return redirect(reverse('goods:index'))
            - goods 是app域名的别名
            - index 是goods里面的别名
    - 在mysql里输入 select * from df_user \G
        信息会竖排显示
    - 数据完整性校验
        if not all([username,password,email]):
            pass
    - 在表里发现 is_active 已经为1激活了,但是我们不想注册即激活,
        需要,在创建用户的时候加入如下:
        user=User.objects.create_user(username,email,password)
        user.is_active = 0
        user.save()
    - 在注册之前先进性校验,register_handle
        判断用户名是否重复,
        try:
        #get有的话只返回一个,没有的话会包异常
            user=User.objects.get(username=username)
        except User.DoesNotExist:
            user = None
        if user:
            return ...
    - 类视图的使用
        - 原理关键在dispatch,getattr
        - from django.view.generic import View
         class RegisterView(View):
            def get(self,request):
                pass
            def post(self,request):
                pass
        url(r'^register$', RegisterView.as_view(), name='register'), # 注册
    - 发送激活邮件,包含激活连接
        - 激活连接中需包含用户身份信息,并加密
        - pip install itsdangerous
        from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
        from itsdangerous import SignatureExpired
        
         # 加密用户的身份信息,生成激活token
            serializer = Serializer(settings.SECRET_KEY, 3600)
            info = {'confirm':user.id}
            token = serializer.dumps(info) # bytes
            token = token.decode()
        #发邮件
            1. django本身有秘钥、
                subject
                message
                sender
                receiver
                html_message
                dend_mail(subject,message,sender,receiver,html_message
                =html_message)  #发送html格式的
        - django网站 -(阻塞执行)-->SMTP服务器 -->目的邮箱
            2.celery使用
                任务发出者 -- 任务队列(broker)-- 任务处理者(worker)        
                        发出任务             监听任务队列
                pip install celery
                任务队列是一种跨线程、跨机器工作的一种机制.
                celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者)
                和worker(任务的处理者). clients发出消息到队列中,broker将队列中的信息派发给
                worker来处理用于处理些IO操作耗时的事务,如上传下载文件、发邮件
                
                from celery import Celery
                # 创建一个Celery类的实例对象
                app = Celery('celery_tasks.tasks', broker='redis://172.16.179.130:6379/8')
                # 定义任务函数
                @app.task
            i.    def send_register_active_email(to_email, username, token):
                    '''发送激活邮件'''
                    # 组织邮件信息
                    subject = '天天生鲜欢迎信息'
                    message = ''
                    sender = settings.EMAIL_FROM
                    receiver = [to_email]
                    html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token)

                    send_mail(subject, message, sender, receiver, html_message=html_message)
                    time.sleep(5)
                
            ii.    # 发邮件
                send_register_active_email.delay(email, username, token)
                
            iii.# Ubuntu虚拟机启动worker
                1.只是启动celery里的worker进程,配置信息需要与django里的task.py文件
                一样,否则django里的变动(time.sleep),Ubuntu不会执行,以当前为准
                2.vi celery_tasks/tasks.py
                    django环境的初始化
                    # 在任务处理者一端加这几句
                    # import os
                    # import django
                    # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
                    # django.setup()
                3.启动
                    celery -A celery_tasks.tasks worker -l info
    -  激活成功后返回登陆页面
        class ActiveView(View):
            '''用户激活'''
            def get(self, request, token):
                '''进行用户激活'''
                # 进行解密,获取要激活的用户信息
                serializer = Serializer(settings.SECRET_KEY, 3600)
                try:
                    info = serializer.loads(token)
                    # 获取待激活用户的id
                    user_id = info['confirm']

                    # 根据id获取用户信息
                    user = User.objects.get(id=user_id)
                    user.is_active = 1
                    user.save()

                    # 跳转到登录页面
                    return redirect(reverse('user:login'))
                except SignatureExpired as e:
                    # 激活链接已过期
                    return HttpResponse('激活链接已过期')

3. login
    - 因为用户多,不能经常调用数据库,使用redis存储session
        https://django-redis-chs.readthedocs.io/zh_CN/latest/
        - pip install django-redis 
        - django缓存配置
            - 指定ip的redis数据库
        - 配置session存储
        - redis-cli -h 192.169.12.1
    - 是否记住用户名    
    i.     class LoginView(View):
        '''登录'''
        def get(self, request):
            '''显示登录页面'''
            # 判断是否记住了用户名
            if 'username' in request.COOKIES:
                username = request.COOKIES.get('username')
                checked = 'checked'
            else:
                username = ''
                checked = ''

            # 使用模板
            return render(request, 'login.html', {'username':username, 'checked':checked})

    ii.    def post(self, request):
            '''登录校验'''
            # 接收数据
            username = request.POST.get('username')
            password = request.POST.get('pwd')
            # 校验数据
            if not all([username, password]):
                return render(request, 'login.html', {'errmsg':'数据不完整'})
            # 业务处理:登录校验
            user = authenticate(username=username, password=password)
            if user is not None:
                # 用户名密码正确
                if user.is_active:
                    # 用户已激活
                    # 记录用户的登录状态
                    login(request, user)
                    # 跳转到首页
                    response = redirect(reverse('goods:index')) # HttpResponseRedirect
                    # 判断是否需要记住用户名
                    remember = request.POST.get('remember')
                    if remember == 'on':
                        # 记住用户名
                        response.set_cookie('username', username, max_age=7*24*3600)
                    else:
                        response.delete_cookie('username')
                    # 返回response
                    return response
                else:
                    # 用户未激活
                    return render(request, 'login.html', {'errmsg':'账户未激活'})
            else:
                # 用户名或密码错误
                return render(request, 'login.html', {'errmsg':'用户名或密码错误'})
    
    iii.<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名">    
        <input type="checkbox" name="remember" {{ checked }}>
4. 用户中心
    - base模板的设计,分base.html  base_no_cart.html,非常重要
        {# 首页 注册 登录 #}
        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
        <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
        {% load staticfiles %}
        <head>
            <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
            {# 网页标题内容块 #}
            <title>{% block title %}{% endblock title %}</title>
            <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
            <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
            {# 网页顶部引入文件块 #}
            {% block topfiles %}{% endblock topfiles %}
        </head>
        。。。。。。

    - 一个用户中心页面可以点三个页面  (用户/订单/收货地址)
        <li><a href="{% url 'user:user' %}" {% if page == 'user' %}class="active"{% endif %}>· 个人信息</a></li>
    - 登录装饰器(如用户界面需要登录)
        i.    login_required   ?next=xxxx
                from django.contrib.auth.decorators import login_required
            settings
                # 配置登录url地址
                LOGIN_URL='/user/login' # /accounts/login
            login.html
                <div class="form_input">
                {# 不设置表单action时,提交表单时,会向浏览器地址栏中的地址提交数据 #}
                .....
            url
                url(r'^$', login_required(UserInfoView.as_view()), name='user'), # 用户中心-信息页
                url(r'^order$', login_required(UserOrderView.as_view()), name='order'), # 用户中心-订单页
                url(r'^address$', login_required(AddressView.as_view()), name='address'), # 用户中心-地址页
            登录视图里的logic处理
                if user.is_active:
                    # 用户已激活
                    # 记录用户的登录状态
                    login(request, user)
                    # 获取登录后所要跳转到的地址
                    # 默认跳转到首页
                    next_url = request.GET.get('next', reverse('goods:index'))
                    # 跳转到next_url
                    response = redirect(next_url) # HttpResponseRedirect

                    # 判断是否需要记住用户名
                    remember = request.POST.get('remember')
                    if remember == 'on':
                        # 记住用户名
                        response.set_cookie('username', username, max_age=7*24*3600)
                    else:
                        response.delete_cookie('username')
                    # 返回response
                    return response
        ii. login_required    
                - 一些经常用的python_package放在utils文件夹里,如mixin.py
                        from django.contrib.auth.decorators import login_required
                        class LoginRequiredMixin(object):
                            @classmethod
                            def as_view(cls, **initkwargs):
                                # 调用父类的as_view
                                view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
                                return login_required(view)
                - 同时类视图调用
                    from utils.mixin import LoginRequiredMixin
                    
                    class UserInfoView(LoginRequiredMixin, View):pass
                    class UserOrderView(LoginRequiredMixin, View):pass

            settings(同上)        
            url     (不需要做处理了)
                    url(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页
                    url(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
                    url(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页
            登录视图里的logic处理(同上)    
    
    - 用户登录欢迎信息(head)
        - 考点  
          # Django会给request对象添加一个属性request.user
          # 如果用户未登录->user是AnonymousUser类的一个实例对象
          # 如果用户登录->user是User类的一个实例对象
          # request.user.is_authenticated()
        - base.html
          {% if user.is_authenticated %}
            <div class="login_btn fl">
                欢迎您:<em>{{ user.username }}</em>
                <span>|</span>
                <a href="{% url 'user:logout' %}">退出</a>
            </div>
            {% else %}
            <div class="login_btn fl">
                <a href="{% url 'user:login' %}">登录</a>
                <span>|</span>
                <a href="{% url 'user:register' %}">注册</a>
            </div>
            {% endif %}
    - logout
         url
            url(r'^logout$', LogoutView.as_view(), name='logout'), # 注销登录
        views
            from django.contrib.auth import authenticate, login, logout
            
            class LogoutView(View):
            '''退出登录'''
                def get(self, request):
                    '''退出登录'''
                    # 清除用户的session信息
                    logout(request)

                    # 跳转到首页
                    return redirect(reverse('goods:index'))    
                
    - 用户中心地址页(默认地址和新添地址的设计)
        i.  post 新上传地址数据,从数据库里查找是否有默认地址
            get 在页面上显示是否有默认地址
            class AddressView(LoginRequiredMixin, View):
                '''用户中心-地址页'''
                def get(self, request):
                    '''显示'''
                    # 获取登录用户对应User对象
                    user = request.user

                    # 获取用户的默认收货地址
                    # try:
                    #     address = Address.objects.get(user=user, is_default=True) # models.Manager
                    # except Address.DoesNotExist:
                    #     # 不存在默认收货地址
                    #     address = None
                    address = Address.objects.get_default_address(user)

                    # 使用模板
                    return render(request, 'user_center_site.html', {'page':'address', 'address':address})

                def post(self, request):
                    '''地址的添加'''
                    # 接收数据
                    receiver = request.POST.get('receiver')
                    addr = request.POST.get('addr')
                    zip_code = request.POST.get('zip_code')
                    phone = request.POST.get('phone')

                    # 校验数据
                    if not all([receiver, addr, phone]):
                        return render(request, 'user_center_site.html', {'errmsg':'数据不完整'})

                    # 校验手机号
                    if not re.match(r'^1[3|4|5|7|8][0-9]{9}$', phone):
                        return render(request, 'user_center_site.html', {'errmsg':'手机格式不正确'})

                    # 业务处理:地址添加
                    # 如果用户已存在默认收货地址,添加的地址不作为默认收货地址,否则作为默认收货地址
                    # 获取登录用户对应User对象
                    user = request.user

                    # try:
                    #     address = Address.objects.get(user=user, is_default=True)
                    # except Address.DoesNotExist:
                    #     # 不存在默认收货地址
                    #     address = None

                    address = Address.objects.get_default_address(user)

                    if address:
                        is_default = False
                    else:
                        is_default = True

                    # 添加地址
                    Address.objects.create(user=user,
                                           receiver=receiver,
                                           addr=addr,
                                           zip_code=zip_code,
                                           phone=phone,
                                           is_default=is_default)

                    # 返回应答,刷新地址页面
                    return redirect(reverse('user:address')) # get请求方式        
        ii. 因为get.post里都用到去models里查询默认数据,可以优化
            - 模型管理器类方法封装
                每个models里都有models.Manager
                            
            1. class AddressManager(models.Manager):
                '''地址模型管理器类'''
                # 1.改变原有查询的结果集:all()
                # 2.封装方法:用户操作模型类对应的数据表(增删改查)
                def get_default_address(self, user):
                    '''获取用户默认收货地址'''
                    # self.model:获取self对象所在的模型类
                    try:
                        address = self.get(user=user, is_default=True)  # models.Manager
                    except self.model.DoesNotExist:
                        # 不存在默认收货地址
                        address = None

                    return address
            2. class Address(BaseModel):
                '''地址模型类'''
                ....
                # 自定义一个模型管理器对象
                objects = AddressManager()
                ..
            3. views调用
                address = Address.objects.get_default_address(user)
                
    - 用户中心个人信息页历史浏览记录            
        - 在用户访问详情页面(SKU),需要添加历史浏览记录
        - 存在表中要经常增删改查不放方便,所以存redis
            - redis数据库->内存性的数据库
            - 表格设计
                1. 所有用户历史记录用一条数据保存
                    hash
                    history:'user_id':'1,2,3'
                2. 一个用户历史记录用一条数据保存
                    list
                    history_user_id:1,2,3
                    添加记录时,用户最新浏览的商品id从列表左侧插入
        - 实际使用
            1. StrictRedis
              i.# Django的缓存配置
                    CACHES = {
                        "default": {
                            "BACKEND": "django_redis.cache.RedisCache",
                            "LOCATION": "redis://172.16.179.130:6379/9",
                            "OPTIONS": {
                                "CLIENT_CLASS": "django_redis.client.DefaultClient",
                            }
                        }
                    }
              ii.form redis import StricRedis
                # 获取用户的历史浏览记录
                # from redis import StrictRedis
                # sr = StrictRedis(host='172.16.179.130', port='6379', db=9)
                  history_key = 'history_%d'%user.id

                # 获取用户最新浏览的5个商品的id
                sku_ids = con.lrange(history_key, 0, 4) # [2,3,1]
        
********     # 从数据库中查询用户浏览的商品的具体信息
                # goods_li = GoodsSKU.objects.filter(id__in=sku_ids)
                # 数据库查询时按遍历的方式,只要id in里面,则查询出来
                #这样就违背了用户真实历史浏览记录了
             i.    # goods_res = []
                # for a_id in sku_ids:
                #     for goods in goods_li:
                #         if a_id == goods.id:
                #             goods_res.append(goods)

                # 遍历获取用户浏览的商品信息
             ii.goods_li = []
                for id in sku_ids:
                    goods = GoodsSKU.objects.get(id=id)
                    goods_li.append(goods)
            
            iii.user_info.html
                    使用{% empty %}标签  ,相当于elseif
                        {% for athlete in athlete_list %}
                            <p>{{ athlete.name }}</p>
                        {% empty %}
                            <p>There are no athletes. Only computer programmers.</p>
                        {% endfor %}
                                    
            2. from django_redis import get_redis_connection
                    con = get_redis_connection('default')    
                    其它同上    
                    
5. fastdfs
    - 概念
        - 分布式文件系统,使用 FastDFS 很容易搭建一套高性能的
          文件服务器集群提供文件上传、下载等服务。 
        - 架构包括 Tracker server 和 Storage server。        
        - 客户端请求 Tracker server 进行文件上传、下载,通过
          Tracker server 调度最终由 Storage server 完成文件上传和下载。         
        - Tracker server 作用是负载均衡和调度
        - Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上
        - 优势:海量存储、存储容量扩展方便、文件内容重复,结合nginx提高网站提供图片效率         
    - 项目上传图片和使用图片的过程
        (通过admin页面上传图片)浏览器 --请求上传文件----django服务器(修改django默认的上传行为)
        --->Fastdfs文件存储服务器
        随后返回文件/group/文件id--->在django上保存对应的image表
        
        用户请求页面时,显示页面,<img src='127.2.2.2:80'>,浏览器请求nginx获取图片
        返回图片内容到浏览器上
****- 修改django默认文件上传方式,
        - python.usyiyi.cn    
        - 自定义文件存储类,使得django存储在fastdfs存储器上
            class FDFSStorage(Storage):
                '''fast dfs文件存储类'''
                def __init__(self, client_conf=None, base_url=None):
                    '''初始化'''
                    if client_conf is None:
                        client_conf = settings.FDFS_CLIENT_CONF
                    self.client_conf = client_conf

                    if base_url is None:
                        base_url = settings.FDFS_URL
                    self.base_url = base_url

                def _open(self, name, mode='rb'):
                    '''打开文件时使用'''
                    pass

                def _save(self, name, content):
                    '''保存文件时使用'''
                    # name:你选择上传文件的名字
                    # content:包含你上传文件内容的File对象

                    # 创建一个Fdfs_client对象
                    client = Fdfs_client(self.client_conf)

                    # 上传文件到fast dfs系统中
                    res = client.upload_by_buffer(content.read())

                    # dict
                    # {
                    #     'Group name': group_name,
                    #     'Remote file_id': remote_file_id,
                    #     'Status': 'Upload successed.',
                    #     'Local file name': '',
                    #     'Uploaded size': upload_size,
                    #     'Storage IP': storage_ip
                    # }
                    if res.get('Status') != 'Upload successed.':
                        # 上传失败
                        raise Exception('上传文件到fast dfs失败')

                    # 获取返回的文件ID
                    filename = res.get('Remote file_id')

                    return filename

                def exists(self, name):
                    '''Django判断文件名是否可用'''
                    return False

                def url(self, name):
                    '''返回访问文件的url路径'''
                    return self.base_url+name


        
        
        
        
6. 商品搜索引擎            
    - 搜索引擎
        1. 可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据
            - 如 select * from xxx where name like '%草莓%' or desc like
                '%草莓%'
            - 很
              好吃
              的
              草莓:sku_id1 sku_id2 sku_id5
              字典
        2. 全文检索框架
            可以帮助用户使用搜索引擎
                用户->全文检索框架(haystack)->搜索引擎(whoosh)
    - 安装即使用
        - pip isntall django-haystack
          pip install whoosh
        - 在settings里注册haystack并配置
            - INSTALLED_APPS = (
                    'django.contrib.admin',
                    ...
                    'tinymce', # 富文本编辑器
                    'haystack', # 注册全文检索框架
                    'user', # 用户模块
                    'goods', # 商品模块
                    'cart', # 购物车模块
                    'order', # 订单模块
                )
            - # 全文检索框架的配置
                    HAYSTACK_CONNECTIONS = {
                        'default': {
                            # 使用whoosh引擎
                            # 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
                            'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine',
                            # 索引文件路径
                            'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
                        }
                    }

                    # 当添加、修改、删除数据时,自动生成索引
                    HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
            - 索引文件的生成
                - 在goods应用目录下新建一个search_indexes.py文件,在其中定义一个商品索引类
                    # 定义索引类
                    from haystack import indexes
                    # 导入你的模型类
                    from goods.models import GoodsSKU

                    # 指定对于某个类的某些数据建立索引
                    # 索引类名格式:模型类名+Index
                    class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable):
                        # 索引字段 use_template=True指定根据表中的哪些字段建立索引文件的说明放在一个文件中
                        text = indexes.CharField(document=True, use_template=True)

                        def get_model(self):
                            # 返回你的模型类
                            return GoodsSKU

                        # 建立索引的数据
                        def index_queryset(self, using=None):
                            return self.get_model().objects.all()    
                - 在templates下面新建目录search/indexes/goods。
                    templates
                        search        固定
                            indexes      固定
                                goods        模型类所在应用app
                                    goodssku_text.txt        模型类名小写_text.txt
                - 在此目录下面新建一个文件goodssku_text.txt并编辑内容如下。
                    # 指定根据表中的哪些字段建立索引数据
                    {{ object.name }} # 根据商品的名称建立索引
                    {{ object.desc }} # 根据商品的简介建立索引
                    {{ object.goods.detail }} # 根据商品的详情建立索引,根据外键跨表查询
                - 使用命令生成索引文件    
                    python manage.py rebuild_index        
                        - 会在whoosh_index文件夹里建立xx个商品索引(所有)
            - 全文检索的使用
                - 配置url
                    url(r'^search', include('haystack.urls')), # 全文检索框架
                - 表单搜索时设置表单内容如下
                    base.html    get和q固定
                        <div class="search_con fl">
                            <form method="get" action="/search">
                                <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                                <input type="submit" class="input_btn fr" name="" value="搜索">
                            </form>
                        </div>
                - 全文检索结果
                    搜索出结果后,haystack会把搜索出的结果传递给templates/search
                    目录下的search.html,传递的上下文包括:
                        query:搜索关键字
                        page:当前页的page对象 –>遍历page对象,获取到的是SearchResult类的实例对象,
                                对象的属性object才是模型类的对象。
                        paginator:分页paginator对象
                    通过HAYSTACK_SEARCH_RESULTS_PER_PAGE 可以控制每页显示数量。
                - search.html分页器的经典使用
                     <div class="pagenation">
                        {% if page.has_previous %}
                        <a href="/search?q={{ query }}&page={{ page.previous_page_number }}"><上一页</a>
                        {% endif %}
                        {% for pindex in paginator.page_range %}
                            {% if pindex == page.number %}
                                <a href="/search?q={{ query }}&page={{ pindex }}" class="active">{{ pindex }}</a>
                            {% else %}
                                <a href="/search?q={{ query }}&page={{ pindex }}">{{ pindex }}</a>
                            {% endif %}
                        {% endfor %}
                        {% if spage.has_next %}
                        <a href="/search?q={{ query }}&page={{ page.next_page_number }}">下一页></a>
                        {% endif %}
                        </div>
                    </div>
            
                - 完成上面步奏之后,可以简单搜索,但是无法根据商品详情里的中文字段搜索
                    需要优化,商品搜索、改变分词方式
                    - 安装jieba分词模块
                        pip install jieba
                            str = '很不错的草莓'
                            res = jieba.cut(str,cut_all=True)
                            for val in res:
                                print(val)
                    - 。。。。。
                    - settings里配置需要更改
                    - 最后重新创建索引数据
                        python manage.py rebuild_index

7. 订单并发处理
    - 悲观锁
        - select * from xx where id=2 for update
        - 应用场景:
            try:
                #select * from df_goods_sku where id=sku_id for update;
                sku=GoodsSKU.object.select_for_update().get(id=sku_id)
            except:
                transaction.savepoint_rollback(save_id)
                return ...                                                
    - 乐观锁
        - 查询数据时不加锁,在更新时进行判断
        - 判断更新时的库存和之前查出的库存是否一致
        - 操作
            -
            for i in range(3):
                #update df_goods_sku set tock=stock,sales=new_sales where id=
                    sku_id and stock=origin_stock
                  res=GoodsSKU.object.filter(id=sku_id,stock=stock).update(
                  stock=new_stock,sales=new_sales)
                  if res==0:  库存为0
                    if i==2:    #尝试第3次查询
                        transaction.savepoint_rollback(save_id)
            - mysql事务隔离性
                事务隔离级别
                    - Read Uncommitted
                    - Read Committed(大多数数据库默认)
                    - Repeatable Read(mysql默认,产生幻读)
                    - serializable(可串行化,解决幻读问题,但容易引发竞争)
            - 重新配置mysql.conf为read committed
    总结:
        - 在冲突较少的时候使用乐观锁,因为省去了加锁、减锁的时间
        - 在冲突多的时候、乐观锁重复操作的代价比较大时使用悲观锁
        - 判断的时候,更新失败不一定是库存不足,需要再去尝试

8. 首页静态页面优化
    - 将动态请求的数据保存为静态的HTML文本,供访问用户下次直接返回。
    - celery
    - 什么时候首页的静态页面需要重新生成?
        当管理员后台修改了首页信息对应的表格中的数据的时候,需要重新生成首页静态页
    - 配置nginx提交静态页面
        - ps aux | grep nginx
     
    - admin管理更新数据表时重新生成index静态页面
        后台管理员操作(重写类,admin.ModelAdmin)-修改首页中表的数--》celery任务函数
            -----》celery服务器生成index.html
        用户访问时直接访问aginx的80端口,即index.html
    - 页面数据的缓存
        把页面中使用的数据存储在缓存中,当再次使用这些数据时,先从缓存中获取,若获取
        不到,再去数据库查询。减少数据库查询的次数
            - 设置缓存
                - 先判断缓存中是否有数据,cache.get('xx')
                - views里设置cache.set(key,zidian,time)
            - 获取缓存
        - 什么时候需要更新首页的缓存数据
            当管理员修改首页信息对应的表格中的数据的时候,需要
            - 在BaseModelAdmin里清除cache
****- 网站本身性能的优化,减少数据查询的次数,防止恶意的攻击,
        DDOS功能
        nginx在提供静态文件方面效率很高,采用epoll



            
SKU & SPU
    - SPU
        - Standard product unittest
        - 商品信息聚合的最小单位
        - 如iphone,
    - SKU
        - STOCK keeping unittest
        - 库存量进出计量单位
        - 如纺织品中一个SKU表示:
            规格、颜色、款式
        











    
    
    
    
View Code