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表示: 規格、顏色、款式
單元測試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
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
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
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表示: 規格、顏色、款式