django-dailyfresh

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

單元測試css

單元測試

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

flaskhtml

回顧:
    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 dailynode

    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
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息