前端文件開發(fā)預(yù)覽
可以使用前端node.js 提供的服務(wù)器live-server作為前端開發(fā)服務(wù)器使用。安裝node.js的版本控制工具nvm啃洋,在終端中執(zhí)行
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
重新進(jìn)入終端,使用nvm安裝最新版本的node.js
nvm install node
安裝live-server
npm install -g live-server
使用:在靜態(tài)文件目錄front_end_pc下執(zhí)行
live-server
live-server運(yùn)行在8080端口下掐暮,可以通過(guò)127.0.0.1:8080
來(lái)訪問(wèn)靜態(tài)頁(yè)面芹助。
修改manage.py文件洪囤,在開發(fā)和生產(chǎn)環(huán)境中指定使用不同的配置:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "meiduo_mall.settings.dev")
為本項(xiàng)目創(chuàng)建數(shù)據(jù)庫(kù)用戶(不再使用root賬戶)
create user meiduo identified by 'meiduo';
grant all on meiduo_mall.* to 'meiduo'@'%';
flush privileges;
說(shuō)明:
第一句:創(chuàng)建用戶賬號(hào) meiduo, 密碼 meiduo (由identified by 指明)
第二句:授權(quán)meiduo_mall數(shù)據(jù)庫(kù)下的所有表(meiduo_mall.*)的所有權(quán)限(all)給用戶meiduo在以任何ip訪問(wèn)數(shù)據(jù)庫(kù)的時(shí)候('meiduo'@'%')
第三句:刷新生效用戶權(quán)限
根據(jù)配置文件路徑,來(lái)設(shè)置項(xiàng)目的工程路徑,便于導(dǎo)包
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(file)))
添加導(dǎo)包路徑,將apps路徑添加進(jìn)去
import sys
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
在INSTALLED_APPS中添加rest_framework框架
INSTALLED_APPS = [
'rest_framework',
]
3. 數(shù)據(jù)庫(kù)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1', # 數(shù)據(jù)庫(kù)主機(jī)
'PORT': 3306, # 數(shù)據(jù)庫(kù)端口
'USER': 'name', # 數(shù)據(jù)庫(kù)用戶名
'PASSWORD': 'mima', # 數(shù)據(jù)庫(kù)用戶密碼
'NAME': 'database_name' # 數(shù)據(jù)庫(kù)名字
}
}
注意:在項(xiàng)目包init中添加mysql轉(zhuǎn)換操作:
import pymysql
pymysql.install_as_MySQLdb()
4. Redis
安裝django-redis跑慕,并配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://10.211.55.5:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"session": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://10.211.55.5:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"
除了名為default的redis配置外万皿,還補(bǔ)充了名為session的redis配置,分別使用兩個(gè)不同的redis庫(kù)相赁。同時(shí)修改了Django的Session機(jī)制使用redis保存相寇,且使用名為'session'的redis配置慰于。
關(guān)于django-redis 的使用钮科,說(shuō)明文檔可見http://django-redis-chs.readthedocs.io/zh_CN/latest/
5. 本地化語(yǔ)言與時(shí)區(qū)
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
6. 日志
LOGGING = {
'version': 1,
'disable_existing_loggers': False, # 是否禁用已經(jīng)存在的日志器
'formatters': { # 日志信息顯示的格式
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
},
},
'filters': { # 對(duì)日志進(jìn)行過(guò)濾
'require_debug_true': { # django在debug模式下才輸出日志
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': { # 日志處理方法
'console': { # 向終端中輸出日志
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': { # 向文件中輸出日志
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(os.path.dirname(BASE_DIR), "logs/meiduo.log"), # 日志文件的位置
'maxBytes': 300 * 1024 * 1024,
'backupCount': 10,
'formatter': 'verbose'
},
},
'loggers': { # 日志器
'django': { # 定義了一個(gè)名為django的日志器
'handlers': ['console', 'file'], # 可以同時(shí)向終端與文件中輸出日志
'propagate': True, # 是否繼續(xù)傳遞日志信息
'level': 'INFO', # 日志器接收的最低日志級(jí)別
},
}
}
7. 異常處理
修改Django REST framework的默認(rèn)異常處理方法,補(bǔ)充處理數(shù)據(jù)庫(kù)異常和Redis異常婆赠。會(huì)抓取一般的鏈接數(shù)據(jù)庫(kù)的(查詢過(guò)程中斷開的)異常.但無(wú)法抓取具體的數(shù)據(jù)庫(kù)(查詢:不存在,插入:已存在)的異常新建utils/exceptions.py
from rest_framework.views import exception_handler as drf_exception_handler
import logging
from django.db import DatabaseError
from redis.exceptions import RedisError
from rest_framework.response import Response
from rest_framework import status
# 獲取在配置文件中定義的logger绵脯,用來(lái)記錄日志
logger = logging.getLogger('django')
def exception_handler(exc, context):
"""
自定義異常處理
:param exc: 異常
:param context: 拋出異常的上下文
:return: Response響應(yīng)對(duì)象
"""
# 調(diào)用drf框架原生的異常處理方法
response = drf_exception_handler(exc, context)
if response is None:
view = context['view']
if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
# 數(shù)據(jù)庫(kù)異常
logger.error('[%s] %s' % (view, exc))
response = Response({'message': '服務(wù)器內(nèi)部錯(cuò)誤'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
return response
配置文件中添加
REST_FRAMEWORK = {
# 異常處理
'EXCEPTION_HANDLER': 'meiduo_mall.utils.exceptions.exception_handler',
}
用戶模型類
Django提供了認(rèn)證系統(tǒng)佳励,文檔資料可參考此鏈接https://yiyibooks.cn/xx/Django_1.11.6/topics/auth/index.html
Django認(rèn)證系統(tǒng)同時(shí)處理認(rèn)證和授權(quán)。簡(jiǎn)單地講蛆挫,認(rèn)證驗(yàn)證一個(gè)用戶是否它們聲稱的那個(gè)人赃承,授權(quán)決定一個(gè)通過(guò)了認(rèn)證的用戶被允許做什么。 這里的詞語(yǔ)“認(rèn)證”同時(shí)指代這兩項(xiàng)任務(wù)悴侵,即Django的認(rèn)證系統(tǒng)同時(shí)提供了認(rèn)證機(jī)制和權(quán)限機(jī)制瞧剖。
Django的認(rèn)證系統(tǒng)包含:
- 用戶
- 權(quán)限:二元(是/否)標(biāo)志指示一個(gè)用戶是否可以做一個(gè)特定的任務(wù)。
- 組:對(duì)多個(gè)用戶運(yùn)用標(biāo)簽和權(quán)限的一種通用的方式可免。
- 一個(gè)可配置的密碼哈希系統(tǒng)
- 用戶登錄或內(nèi)容顯示的表單和視圖
- 一個(gè)可插拔的后臺(tái)系統(tǒng)
Django用戶模型類
Django認(rèn)證系統(tǒng)中提供了用戶模型類User保存用戶的數(shù)據(jù)抓于,默認(rèn)的User包含以下常見的基本字段:
-
username
必選。 150個(gè)字符以內(nèi)浇借。 用戶名可能包含字母數(shù)字捉撮,_
,@
妇垢,+
.
和-
個(gè)字符巾遭。在Django更改1.10:max_length
從30個(gè)字符增加到150個(gè)字符。 -
first_name
可選(blank=True
)闯估。 少于等于30個(gè)字符灼舍。 -
last_name
可選(blank=True
)。 少于等于30個(gè)字符涨薪。 -
email
可選(blank=True
)片仿。 郵箱地址。 -
password
必選尤辱。 密碼的哈希及元數(shù)據(jù)砂豌。 (Django 不保存原始密碼)。 原始密碼可以無(wú)限長(zhǎng)而且可以包含任意字符光督。 -
groups
與Group
之間的多對(duì)多關(guān)系阳距。 -
user_permissions
與Permission
之間的多對(duì)多關(guān)系。 -
is_staff
布爾值结借。 指示用戶是否可以訪問(wèn)Admin 站點(diǎn)筐摘。 -
is_active
布爾值。 指示用戶的賬號(hào)是否激活船老。 我們建議您將此標(biāo)志設(shè)置為False
而不是刪除帳戶咖熟;這樣,如果您的應(yīng)用程序?qū)τ脩粲腥魏瓮怄I柳畔,則外鍵不會(huì)中斷馍管。它不是用來(lái)控制用戶是否能夠登錄。 在Django更改1.10:在舊版本中薪韩,默認(rèn)is_active為False不能進(jìn)行登錄确沸。 -
is_superuser
布爾值捌锭。 指定這個(gè)用戶擁有所有的權(quán)限而不需要給他們分配明確的權(quán)限。 -
last_login
用戶最后一次登錄的時(shí)間罗捎。 -
date_joined
賬戶創(chuàng)建的時(shí)間观谦。 當(dāng)賬號(hào)創(chuàng)建時(shí),默認(rèn)設(shè)置為當(dāng)前的date/time桨菜。
常用方法:
-
set_password
(raw_password)
設(shè)置用戶的密碼為給定的原始字符串豁状,并負(fù)責(zé)密碼的。 不會(huì)保存User
對(duì)象倒得。當(dāng)None
為raw_password
時(shí)替蔬,密碼將設(shè)置為一個(gè)不可用的密碼。 -
check_password
(raw_password)
如果給定的raw_password是用戶的真實(shí)密碼屎暇,則返回True承桥,可以在校驗(yàn)用戶密碼時(shí)使用。
管理器方法:
管理器方法即可以通過(guò)User.objects.
進(jìn)行調(diào)用的方法根悼。
-
create_user
(username, email=None, password=None, **extra_fields)
創(chuàng)建凶异、保存并返回一個(gè)User
對(duì)象。 -
create_superuser
(username, email, password, **extra_fields)
與create_user()
相同挤巡,但是設(shè)置is_staff
和is_superuser
為True
剩彬。
創(chuàng)建自定義的用戶模型類
Django認(rèn)證系統(tǒng)中提供的用戶模型類及方法很方便,我們可以使用這個(gè)模型類矿卑,但是字段有些無(wú)法滿足項(xiàng)目需求喉恋,如本項(xiàng)目中需要保存用戶的手機(jī)號(hào),需要給模型類添加額外的字段母廷。
Django提供了django.contrib.auth.models.AbstractUser
用戶抽象模型類允許我們繼承轻黑,擴(kuò)展字段來(lái)使用Django認(rèn)證系統(tǒng)的用戶模型類。
我們現(xiàn)在在meiduo/meiduo_mall/apps
中創(chuàng)建Django應(yīng)用users琴昆,并在配置文件中注冊(cè)u(píng)sers應(yīng)用氓鄙。
在創(chuàng)建好的應(yīng)用models.py中定義用戶的用戶模型類。
class User(AbstractUser):
"""用戶模型類"""
mobile = models.CharField(max_length=11, unique=True, verbose_name='手機(jī)號(hào)')
class Meta:
db_table = 'tb_users'
verbose_name = '用戶'
verbose_name_plural = verbose_name
我們自定義的用戶模型類還不能直接被Django的認(rèn)證系統(tǒng)所識(shí)別业舍,需要在配置文件中告知Django認(rèn)證系統(tǒng)使用我們自定義的模型類抖拦。
在配置文件中進(jìn)行設(shè)置
AUTH_USER_MODEL = 'users.User'
AUTH_USER_MODEL
參數(shù)的設(shè)置以點(diǎn).
來(lái)分隔,表示應(yīng)用名.模型類名
舷暮。
注意:Django建議我們對(duì)于AUTH_USER_MODEL
參數(shù)的設(shè)置一定要在第一次數(shù)據(jù)庫(kù)遷移之前就設(shè)置好态罪,否則后續(xù)使用可能出現(xiàn)未知錯(cuò)誤。
執(zhí)行數(shù)據(jù)庫(kù)遷移
python manage.py makemigrations
python manage.py migrate
跨域CORS
我們?yōu)榍岸撕秃蠖朔謩e設(shè)置了兩個(gè)不同的域名
現(xiàn)在下面,前端與后端分處不同的域名复颈,我們需要為后端添加跨域訪問(wèn)的支持。
我們使用CORS來(lái)解決后端對(duì)跨域訪問(wèn)的支持诸狭。
使用django-cors-headers擴(kuò)展
參考文檔https://github.com/ottoyiu/django-cors-headers/
安裝
pip install django-cors-headers
添加應(yīng)用
INSTALLED_APPS = (
...
'corsheaders',
...
)
中間層設(shè)置
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
]
添加白名單
# CORS
CORS_ORIGIN_WHITELIST = (
'127.0.0.1:8080',
'localhost:8080',
........
)
CORS_ALLOW_CREDENTIALS = True # 允許攜帶cookie
- 凡是出現(xiàn)在白名單中的域名券膀,都可以訪問(wèn)后端接口
- CORS_ALLOW_CREDENTIALS 指明在跨域訪問(wèn)中君纫,后端是否支持對(duì)cookie的操作驯遇。
Celery使用
項(xiàng)目目錄下創(chuàng)建celery_tasks用于保存celery異步任務(wù)芹彬。
在celery_tasks目錄下創(chuàng)建config.py文件,用于保存celery的配置信息
broker_url = "redis://127.0.0.1/14"
在celery_tasks目錄下創(chuàng)建main.py文件叉庐,用于作為celery的啟動(dòng)文件
from celery import Celery
為celery使用django配置文件進(jìn)行設(shè)置
import os
if not os.getenv('DJANGO_SETTINGS_MODULE'):
os.environ['DJANGO_SETTINGS_MODULE'] = 'meiduo_mall.settings.dev'
創(chuàng)建celery應(yīng)用
app = Celery('異步任務(wù)名')
導(dǎo)入celery配置
app.config_from_object('celery_tasks.config')
自動(dòng)注冊(cè)celery任務(wù)
app.autodiscover_tasks(['celery_tasks.sms'])
在celery_tasks目錄下創(chuàng)建sms目錄舒帮,用于放置發(fā)送短信的異步任務(wù)相關(guān)代碼。
將提供的發(fā)送短信的云通訊SDK放到celery_tasks/sms/目錄下陡叠。
在celery_tasks/sms/目錄下創(chuàng)建tasks.py文件玩郊,用于保存發(fā)送短信的異步任務(wù)
import logging
from celery_tasks.main import app
from .yuntongxun.sms import CCP
logger = logging.getLogger("django")
驗(yàn)證碼短信模板
SMS_CODE_TEMP_ID = 1
@app.task(name='send_sms_code')
def send_sms_code(mobile, code, expires):
"""
發(fā)送短信驗(yàn)證碼
:param mobile: 手機(jī)號(hào)
:param code: 驗(yàn)證碼
:param expires: 有效期
:return: None
"""
try:
ccp = CCP()
result = ccp.send_template_sms(mobile, [code, expires], SMS_CODE_TEMP_ID)
except Exception as e:
logger.error("發(fā)送驗(yàn)證碼短信[異常][ mobile: %s, message: %s ]" % (mobile, e))
else:
if result == 0:
logger.info("發(fā)送驗(yàn)證碼短信[正常][ mobile: %s ]" % mobile)
else:
logger.warning("發(fā)送驗(yàn)證碼短信[失敗][ mobile: %s ]" % mobile)
在需要發(fā)送短信的視圖中,使用celery異步任務(wù)發(fā)送短信
from celery_tasks.sms import tasks as sms_tasks
class SMSCodeView(GenericAPIView):
# 發(fā)送短信驗(yàn)證碼,使用delay將任務(wù)加入到異步隊(duì)列
sms_tasks.send_sms_code.delay(mobile, sms_code, sms_code_expires)
JWT
JWT的構(gòu)成
第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似于飛機(jī)上承載的物品)枉阵,第三部分是簽證(signature).
注意:secret是保存在服務(wù)器端的译红,jwt的簽發(fā)生成也是在服務(wù)器端的,secret就是用來(lái)進(jìn)行jwt的簽發(fā)和jwt的驗(yàn)證兴溜,所以侦厚,它就是你服務(wù)端的私鑰,在任何場(chǎng)景都不應(yīng)該流露出去拙徽。一旦客戶端得知這個(gè)secret, 那就意味著客戶端是可以自我簽發(fā)jwt了刨沦。
Django REST framework JWT
我們?cè)隍?yàn)證完用戶的身份后(檢驗(yàn)用戶名和密碼),需要向用戶簽發(fā)JWT膘怕,在需要用到用戶身份信息的時(shí)候想诅,還需核驗(yàn)用戶的JWT。
關(guān)于簽發(fā)和核驗(yàn)JWT岛心,我們可以使用Django REST framework JWT擴(kuò)展來(lái)完成来破。
文檔網(wǎng)站http://getblimp.github.io/django-rest-framework-jwt/
安裝配置
安裝
pip install djangorestframework-jwt
配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
- JWT_EXPIRATION_DELTA 指明token的有效期
使用
Django REST framework JWT 擴(kuò)展的說(shuō)明文檔中提供了手動(dòng)簽發(fā)JWT的方法
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
前端保存token
我們可以將JWT保存在cookie中,也可以保存在瀏覽器的本地存儲(chǔ)里忘古,我們保存在瀏覽器本地存儲(chǔ)中
瀏覽器的本地存儲(chǔ)提供了sessionStorage 和 localStorage 兩種:
sessionStorage 瀏覽器關(guān)閉即失效
localStorage 長(zhǎng)期有效
- 后端實(shí)現(xiàn)
Django REST framework JWT提供了登錄簽發(fā)JWT的視圖讳癌,可以直接使用
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
url(r'^authorizations/$', obtain_jwt_token),
]
但是默認(rèn)的返回值僅有token,我們還需在返回值中增加username和user_id存皂。
通過(guò)修改該視圖的返回值可以完成我們的需求晌坤。
在users/utils.py 中,創(chuàng)建
def jwt_response_payload_handler(token, user=None, request=None):
"""
自定義jwt認(rèn)證成功返回?cái)?shù)據(jù)
"""
return {
'token': token,
'user_id': user.id,
'username': user.username
}
修改配置文件
JWT
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
}
- 增加支持用戶名與手機(jī)號(hào)均可作為登錄賬號(hào)
JWT擴(kuò)展的登錄視圖旦袋,在收到用戶名與密碼時(shí)骤菠,也是調(diào)用Django的認(rèn)證系統(tǒng)中提供的authenticate()來(lái)檢查用戶名與密碼是否正確。
我們可以通過(guò)修改Django認(rèn)證系統(tǒng)的認(rèn)證后端(主要是authenticate方法)來(lái)支持登錄賬號(hào)既可以是用戶名也可以是手機(jī)號(hào)疤孕。修改Django認(rèn)證系統(tǒng)的認(rèn)證后端需要繼承django.contrib.auth.backends.ModelBackend商乎,并重寫authenticate方法。
authenticate(self, request, username=None, password=None, **kwargs)方法的參數(shù)說(shuō)明:
request 本次認(rèn)證的請(qǐng)求對(duì)象
username 本次認(rèn)證提供的用戶賬號(hào)
password 本次認(rèn)證提供的密碼
我們想要讓用戶既可以以用戶名登錄祭阀,也可以以手機(jī)號(hào)登錄鹉戚,那么對(duì)于authenticate方法而言鲜戒,username參數(shù)即表示用戶名或者手機(jī)號(hào)。
重寫authenticate方法的思路:
根據(jù)username參數(shù)查找用戶User對(duì)象抹凳,username參數(shù)可能是用戶名遏餐,也可能是手機(jī)號(hào)
若查找到User對(duì)象,調(diào)用User對(duì)象的check_password方法檢查密碼是否正確
在users/utils.py中編寫:
def get_user_by_account(account):
"""
根據(jù)帳號(hào)獲取user對(duì)象
:param account: 賬號(hào)赢底,可以是用戶名失都,也可以是手機(jī)號(hào)
:return: User對(duì)象 或者 None
"""
try:
if re.match('^1[3-9]\d{9}$', account):
# 帳號(hào)為手機(jī)號(hào)
user = User.objects.get(mobile=account)
else:
# 帳號(hào)為用戶名
user = User.objects.get(username=account)
except User.DoesNotExist:
return None
else:
return user
class UsernameMobileAuthBackend(ModelBackend):
"""
自定義用戶名或手機(jī)號(hào)認(rèn)證
"""
def authenticate(self, request, username=None, password=None, **kwargs):
user = get_user_by_account(username)
if user is not None and user.check_password(password):
return user
在配置文件中告知Django使用我們自定義的認(rèn)證后端
AUTHENTICATION_BACKENDS = [
'users.utils.UsernameMobileAuthBackend',
]
使用itsdangerous生成憑據(jù)access_token
itsdangerous模塊的參考資料連接http://itsdangerous.readthedocs.io/en/latest/
安裝
pip install itsdangerous
TimedJSONWebSignatureSerializer
的使用
使用TimedJSONWebSignatureSerializer可以生成帶有有效期的token
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from django.conf import settings
# serializer = Serializer(秘鑰, 有效期秒)
serializer = Serializer(settings.SECRET_KEY, 300)
# serializer.dumps(數(shù)據(jù)), 返回bytes類型
token = serializer.dumps({'mobile': '18512345678'})
token = token.decode()
# 檢驗(yàn)token
# 驗(yàn)證失敗,會(huì)拋出itsdangerous.BadData異常
serializer = Serializer(settings.SECRET_KEY, 300)
try:
data = serializer.loads(token)
except BadData:
return None