Spring提供StopWatch計時器
Django框架相關
推導模擬實現Django框架
程式碼編寫web框架
web框架的本質
從上圖來看,web框架就是連線前端與資料庫的中間介質,負責對資料進行處理,以主要的業務邏輯為支援
編寫思路
如果是從程式碼角度來看,web框架就是我們之前所寫的socket服務端,對客戶端傳來的資料與請求進行處理,將資料庫的資料反饋到客戶端
那麼我們使用程式碼編寫web框架的思路大概如下
1.編寫socket服務端程式碼 2.瀏覽器訪問響應無效>>>:HTTP協議 sock.send(b'HTTP/1.1 200 OK\r\n\r\n') # 新增相應的資料打包格式 3.根據網址字尾的不同獲取不同的頁面內容 /index、/login、 4.想辦法獲取到使用者輸入的字尾>>>:請求資料 5.找到網址字尾所在位置 GET /login HTTP/1.1 請求檔案的首行位置 請求的兩種型別 GET請求 索要資料 POST請求 提交資料 6.對請求資料進行處理獲取網址字尾 利用字串的切割屬性,用空格對資料進行切割,索引1 data.split(' ')[1]
整體程式碼
import socket server = socket.socket() # TCP UDP server.bind(('127.0.0.1', 8080)) # IP PORT server.listen(5) # 半連線池 while True: sock, address = server.accept() # 等待連線 data = sock.recv(1024) # 位元組(bytes) # print(data.decode('utf8')) # 解碼列印 sock.send(b'HTTP/1.1 200 OK\r\n\r\n') data_str = data.decode('utf8') # 先轉換成字串 target_url = data_str.split(' ')[1] # 按照空格切割字串並取索引1對應的資料 # print(target_url) # /index /login /reg if target_url == '/index': # sock.send(b'index page') with open(r'myhtml01.html','rb') as f: sock.send(f.read()) elif target_url == '/login': sock.send(b'login page') else: sock.send(b'home page!')
程式碼初步優化
實際的程式碼寫出來之後,只是初步具備了一些功能,但是還是存在一些問題
1.socket程式碼過於重複
2.針對請求資料處理繁瑣
3.字尾匹配邏輯過於低端(if...elif...分支重複)
優化思路:
藉助於wsgiref模組對程式碼進行優化
作為python的內建模組,它有很強大的功能,是大多數web框架底層使用的程式碼,對於socket程式碼進行了封裝,對資料的處理方法也進行了封裝,極大地簡化了我們的程式碼編寫
from wsgiref.simple_server import make_server def run(request, response): """ :param request: 請求相關資料 :param response: 響應相關資料 :return: 返回給客戶端的真實資料 """ response('200 OK', []) # 固定格式 不用管它 # print(request) 是一個處理之後的大字典 path_info = request.get('PATH_INFO') if path_info == '/index': return [b'index'] elif path_info == '/login': return [b'login'] return [b'hello wsgiref module'] if __name__ == '__main__': server = make_server('127.0.0.1', 8080, run) # 實時監聽127.0.0.1:8080 一旦有請求過來自動給第三個引數加括號並傳引數呼叫 server.serve_forever() # 啟動服務端
1.固定程式碼啟動服務端
2.檢視處理之後的request,資料是以字典的形式組成的
3.根據不同的網址字尾返回不同的內容>>>:研究大字典鍵值對
PATH_INFO對應的就是網址的字尾,直接用.get方法取出
根據不同結果進行判定,決定返回資料內容
4.立刻解決上述純手寫的問題一與問題二
函式封裝優化
那麼,最後的問題,關於條件的篩選,我們可以參考以往的內容
將程式碼進行函式封裝,將他們以網頁的字尾作為鍵,函式名作為值封裝為功能字典,當字尾名符合時直接在字典中取值進行函式呼叫即可
views.py 儲存核心業務邏輯(功能函式)
urls.py 儲存網址字尾與函式名對應關係
templates目錄 儲存html頁面檔案
如果僅僅是這樣還是不夠的,request中包含了所有使用者請求資訊,那麼如果在之後的業務拓展中我們需要使用達到資料,那麼我們就需要在傳遞資料時將這個字典傳遞出去
那麼我們在拆分封裝函式時,就可以將這個字典當做引數傳遞給函式,這樣在之後拓展時我們就可以呼叫字典中的資料
到這裡我們的框架基本已經趨於完善
但是網頁中的內容也決定了網頁分為動態網頁與靜態網頁兩種
動靜態網頁
動態網頁
頁面資料來源於後端,實時從後端獲取資料並進行展示
靜態網頁
頁面資料直接寫在前端,不會與後端產生互動
我們可以在後端定義一個函式,當用戶訪問某個指定的網頁時,後端程式碼獲取當前時間,並將時間傳遞到網頁進行展示
這樣的話,我們需要將獲取到的時間傳遞到HTML文件中指定的標籤中,首先要先讀取到HTML內的內容,將指定標籤的內容更換為我們所獲取到的時間然後返回到瀏覽器頁面
字串型別的資料我們可以使用replace進行替換,那麼如果是其他型別的資料呢,這個時候就需要使用到另一個內建模組:jinja2
jinja2模組
它同樣也是python的內建模組,直接使用pip工具進行下載或者在pycharm中進行下載即可
pip3 install jinja2
from jinja2 import Template
def get_dict_func(request):
user_dict = {'name': 'jason', 'age': 18, 'person_list': ['阿珍', '阿強', '阿香', '阿紅']}
with open(r'templates/get_dict_page.html', 'r', encoding='utf8') as f:
data = f.read()
temp_obj = Template(data) # 將頁面資料交給模板處理
res = temp_obj.render({'d1': user_dict}) # 給頁面傳了一個 變數名是d1值是字典資料的資料
return res
<p>{{ d1 }}</p>
<p>{{ d1.name }}</p>
<p>{{ d1['age'] }}</p>
<p>{{ d1.get('person_list') }}</p>
jinja2模組最大的作用就是可以讓我們在HTML文件中以類似於在python中操作資料一樣去操作我們傳遞到HTML中的資料
{% for user_dict in user_data_list %}
<tr>
<td>{{ user_dict.id }}</td>
<td>{{ user_dict.name }}</td>
<td>{{ user_dict.age }}</td>
</tr>
{% endfor %}
它還支援進行for迴圈,但是我們需要搞清楚的是,所有資料的處理都是在jinja2模組處理好之後才傳遞到HTML檔案中的,並不是真的在HTML文件中操作資料
那麼對於前端、後端以及資料庫三者的聯動,真正實現web框架我們也就可以實現了
服務端主體程式碼
from wsgiref.simple_server import make_server
from views import *
from urls import urls
def run(request, response):
"""
:param request: 請求相關資料
:param response: 響應相關資料
:return: 返回給客戶端的真實資料
"""
response('200 OK', []) # 固定格式 不用管它
path_info = request.get('PATH_INFO') # /index
func_name = None # 定義一個用於後續儲存函式名的變數
for url_tuple in urls: # (字尾,函式名)
if path_info == url_tuple[0]:
func_name = url_tuple[1]
break # 一旦匹配成功 後續無序匹配直接結束
if func_name:
res = func_name(request)
else:
res = error_func(request)
return [res.encode('utf8')]
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run) # 實時監聽127.0.0.1:8080 一旦有請求過來自動給第三個引數加括號並傳引數呼叫
server.serve_forever() # 啟動服務端
urls.py
from views import *
urls = [
('/index', index_func),
('/login', login_func),
('/reg', reg_func),
('/logout', logout_func),
('/get_time', get_time_func),
('/get_dict', get_dict_func),
('/get_user', get_user_func)
]
views.py
def index_func(request):
return 'index function'
def login_func(request):
with open(r'templates/myhtml02.html', 'r', encoding='utf8') as f:
return f.read()
# return 'login function'
def reg_func(request):
return 'reg function'
def error_func(request):
return '404 Not Found'
def logout_func(request):
return 'logout function'
def get_time_func(request):
import time
ctime = time.strftime('%Y-%m-%d %X')
with open(r'templates/get_time_page.html', 'r', encoding='utf8') as f:
data = f.read()
# 在後端先把資料傳遞到檔案內容上 之後再發送給瀏覽器 字串的替換
res = data.replace('sadkasjdklad', ctime)
return res
from jinja2 import Template
def get_dict_func(request):
user_dict = {'name': 'jason', 'age': 18, 'person_list': ['阿珍', '阿強', '阿香', '阿紅']}
with open(r'templates/get_dict_page.html', 'r', encoding='utf8') as f:
data = f.read()
temp_obj = Template(data) # 將頁面資料交給模板處理
res = temp_obj.render({'d1': user_dict}) # 給頁面傳了一個 變數名是d1值是字典資料的資料
return res
import pymysql
def get_user_func(request):
# 連線資料庫操作資料
conn = pymysql.connect(
user='root',
password='123',
host='127.0.0.1',
port=3306,
database='day51',
charset='utf8',
autocommit=True
)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
sql1 = "select * from userinfo;"
cursor.execute(sql1)
user_data = cursor.fetchall() # [{},{},{},{}]
# 讀取頁面資料
with open(r'templates/get_user_page.html', 'r', encoding='utf8') as f:
data = f.read()
temp_obj = Template(data)
res = temp_obj.render({'user_data_list': user_data})
return res
templates目錄內檔案
user.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row">
<h1 class="text-center">資料展示</h1>
<a href="/index">點我試試</a>
<div class="col-md-8 col-md-offset-2">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<!-- [{},{},{},{} ]-->
{% for user_dict in user_data_list %}
<tr>
<td>{{ user_dict.id }}</td>
<td>{{ user_dict.name }}</td>
<td>{{ user_dict.age }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
這樣我們也就實現了整個web框架的手寫工作
Python中主流web框架
所謂網路框架是指這樣的一組Python包,它能夠使開發者專注於網站應用業務邏輯的開發,而無須處理網路應用底層的協議、執行緒、程序等方面。這樣能大大提高開發者的工作效率,同時提高網路應用程式的質量
對於最初接觸框架的程式設計師,建議不要混著學習,否則對於框架的把我不夠熟練容易徹底搞混而導致任何框架都不能熟練掌握
大而全,自身自帶的功能元件非常的多,類似於航空母艦
非同步非阻塞,速度極快效率極高
小而精 自身自帶的功能元件非常的少 類似於遊騎兵
幾乎所有的功能都需要依賴於第三方模組
還有sanic、fastapi等比較小眾的框架
Django簡介
到目前為止,Django的最新版本發展到了4.0,其中1.X與2.X屬於同步架構,3.X與4.X都已經支援非同步架構,而在上圖中,LTS版本是歷代Django框架中最為穩定的版本
對於版本而言,個版本之間的底層差異並不大,可能只是在舊版的基礎上添加了新的功能,而在學習過程中,建議使用2.2版本
執行Django的注意事項
1.django專案中所有的檔名目錄名不要出現中文
2.計算機名稱儘量也不要出現中文
3.一個pycharm視窗儘量是一個完整的專案(不要巢狀,不要疊加)
4.不同版本的python直譯器與不同版本的django可能會出現小問題(例如配置檔案中的路徑拼接問題)
解決方法:
os.path.join(BASE_DIR,'templates')
上圖中的問題只需根據提示查詢widgets.py原始碼第152行,刪除掉最後的逗號
Django基本使用
下載
pip3 install django 預設最新版
pip3 install django==版本號 指定版本
pip3 install django==2.2.22
pip下載模組會自動解決依賴問題(會把關聯需要用到的模組一起下了)
驗證是否下載成功可以在cmd中輸入以下指令
django-admin
常用命令
1.建立django專案
django-admin startproject 專案名
2.啟動django專案
cd 專案名
python38 manage.py runserver ip:port
pycharm自動建立Django專案
會自動建立templates資料夾 但是配置檔案中可能會報錯
os.path.join(BASE_DIR,'templates')
Django app的概念
這裡的APP並不是我們日常使用的軟體,我們可以用類比方法來進行理解
django類似於是一所大學 app類似於大學裡面的各個學院
django裡面的app類似於某個具體的功能模組
user app 所有使用者相關的都寫在user app下
goods app 所有商品相關的都寫在goods app下
命令列建立應用
python38 manage.py startapp 應用名
pycharm建立應用
新建django專案可以預設建立一個 並且自動註冊
app註冊機制
建立的app一定要去settings.py中註冊
INSTALLED_APPS = [
'app01.apps.App01Config', # 完整寫法
'app02'] # 簡寫
django主要目錄結構
django專案目錄名
django專案同名目錄
settings.py 配置檔案
urls.py 儲存網址字尾與函式名對應關係(不嚴謹)
wsgi.py wsgiref閘道器檔案
db.sqlite3檔案 django自帶的小型資料庫(專案啟動之後才會出現)
manage.py 入口檔案(命令提供)
應用目錄
migrations目錄 儲存資料庫相關記錄
admin.py django內建的admin後臺管理功能
apps.py 註冊app相關
models.py 與資料庫打交道的(非常重要)
tests.py 測試檔案
views.py 儲存功能函式(不嚴謹)
templates目錄
儲存html檔案(命令列不會自動建立 pycharm會)
配置檔案中還需要配置路徑
[os.path.join(BASE_DIR,'templates'),]
名詞講解
網址字尾 路由
函式 檢視函式
類 檢視類
重要名詞講解
urls.py 路由層
views.py 檢視層
models.py 模型層
templates 模板層
Django必懂三功能
from django.shortcuts import render,HttpResponse,redirect
HttpResponse 返回字串型別的資料
render 返回html頁面並且支援傳值
redirect 重定向
靜態檔案
當我們使用Django框架寫一些專案時,就避免不了要使用html檔案,但是當我們啟動框架訪問指定頁面,我們在HTML中引入的本地的一些CSS\JS檔案是無法訪問到的,而這些CSS\JS檔案,我們就可以理解為靜態檔案
靜態檔案概念
- 靜態檔案指的是不怎麼經常變化的檔案,主要針對html檔案所使用的到的各種資源
如:css檔案、js檔案、img檔案、第三方框架檔案
- django針對靜態檔案資源需要單獨開始一個目錄統一存放
目錄名稱:static目錄
該目錄下如果各種型別的檔案都多,還可以繼續建立目錄
css目錄(存放css的檔案)
js目錄(存放js的檔案)
img目錄(存放圖片)
存放外掛:utils目錄/plugins目錄/libs目錄/others目錄
資源訪問
當我們訪問網站時,指定的路由會返回對應的介面,且靜態檔案可以成功載入,網頁的樣式不會受到影響的原因在於,編寫該頁面的程式設計師已經提前用程式碼將檔案的介面開放,相當於高速公路上的關卡和收費站已經提前打過招呼,可以直接通過
靜態檔案資源訪問
·靜態檔案預設情況下是無法訪問的,前端的內容是無法直接獲取後端資料的,如果想要實現該功能,我們可以在後端開放一個介面,使前端可以訪問到固定的靜態檔案
靜態檔案相關配置
想讓前端能訪問後端的檔案需要設定配置資訊開放許可權
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
ps:Django所有配置檔案中的名稱需要純大寫,否則不會執行
介面字首
STATIC_URL = '/xxx/'
# 訪問靜態檔案資源的介面字首(相當於通行證)
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'), # 儲存靜態檔案資源的目錄名稱
os.path.join(BASE_DIR, 'static1'), # 儲存靜態檔案資源的目錄名稱,可以同時建立多個目錄
os.path.join(BASE_DIR, 'static2'), # 儲存靜態檔案資源的目錄名稱
]
介面字首正確之後,會拿著後面的路徑依次去到列表中自上而下查詢,一旦找到就返回
介面字首動態匹配
當我們在配置檔案中開放了介面後,如果我們想要修改介面名稱就需要在html檔案中同時進行修改,當網頁很多的時候工作量就會很大,這時候就需要設定介面字首的動態匹配
<head>
<meta charset="UTF-8">
<title>blogs</title>
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'blog.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
form表單
action屬性
我們給表單標籤form新增屬性action控制資料提交的地址
<form action="/login/" method="post">
action屬性的三種配置
1.action="" 資料預設提交給當前頁面所在的地址
2.action="https://www.baidu.com/" 完整地址
3.action="/index/" 朝當前服務端的index地址提交
method屬性
method屬性用來控制資料提交的方法,預設為get獲取資料,post為傳遞資料
<form action="/login/" method="post">
請求方法補充
get
- 朝服務端索要資料,也可以攜帶一些額外的要求
攜帶額外資料的方式: URL?xxx=yyy&uuu=zzz
也就是說網址後面用?隔開資料,資料之間用&符號分隔
問號後面攜帶資料的大小是有限制(2KB)的並且不能攜帶敏感資料
- 當我們開啟瀏覽器訪問網頁的時候就是get方式
post
- 朝服務端提交資料
攜帶額外資料的方式: 請求體
請求體攜帶資料安全性較高並且沒有大小限制
- 當我們使用按鈕提交新資料的時候使用的就是post方式
前期傳送post請求需要註釋掉配置檔案中的某一行
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
request物件
request物件相關方法
request.method 獲取請求方式 結果是純大寫的字串資料
結果:GET\POST
request.POST 獲取post請求請求體裡面攜帶的資料
request.POST.get() 獲取列表最後一個數據值
request.POST.getlist() 獲取整個列表資料,獲取了列表後可以使用索引獲取列表中的值
request.GET 獲取網址問號後面攜帶的資料
request.GET.get() 獲取列表最後一個數據值
request.GET.getlist() 獲取整個列表資料,跟POST一樣,獲取了列表後可以使用索引獲取列表中的值
"""
在檢視函式中針對不同的請求程式碼編寫套路
if request.method == 'POST':
return HttpResponse()
return HttpResponse()
"""
Django連線資料庫
Django自帶的sqlite3是一個小型的資料庫,功能比較少,主要用於本地測試
我們實際專案中都會替換掉它
預設配置sqlite3
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
我們在學習過程中使用mysqlclient模組替換預設的資料庫
需要指定模組
django1.X版本需要在專案目錄下或者app目錄下的__init__.py編寫程式碼
import pymysql
pymysql.install_as_MySQLdb()
django2.X及以上都可以直接通過下載mysqlclient模組解決
pip3.8 install mysqlclient
或是pycharm中下載
ps:mac電腦下載該檔案可能會報錯
修改配置檔案
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'day51',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '123',
'CHARSET': 'utf8'
}
}
app檔案目錄中的雙下init檔案也需要新增配置
import pymysql
pymysql.install_as_MySQLdb()
pycharm連線MySQL資料庫
方式一:
在pycharm右上角點選圖示後建立連線
之後彈出一個彈窗,輸入資料庫的資訊
第一次連線的時候會要求下載外掛,這裡點選下載就好了,很快的
接著點選下方的Test Connection測試連線,成功了就可以點選ok儲存退出了
方式二:
使用左下角的圖示連線資料庫
ORM簡介
ORM概念
物件關係對映(Object Relational Mapping,簡稱ORM)模式是一種為了解決面向物件與關係資料庫存在的互不匹配的現象的技術
簡單的說,ORM是通過使用描述物件和資料庫之間對映的元資料,將程式中的物件自動持久化到關係資料庫中。因此實現了讓不會SQL語句的python程式設計師,使用python面向物件的語法來操作資料庫的目的
ORM在業務邏輯層和資料庫層之間充當了橋樑的作用
我們可以簡單的進行以下類比
類 表
物件 一條條資料
物件點名字 資料獲取欄位對應的值
ps:ORM由於高度封裝了SQL,所以有時候效率較低,我們需要自己寫SQL
ORM由來
讓我們從O/R開始。字母O起源於"物件"(Object),而R則來自於"關係"(Relational)
幾乎所有的軟體開發過程中都會涉及到物件和關係資料庫。在使用者層面和業務邏輯層面,我們是面向物件的。當物件的資訊發生變化的時候,我們就需要把物件的資訊儲存在關係資料庫中
按照之前的方式來進行開發就會出現程式設計師會在自己的業務邏輯程式碼中夾雜很多SQL語句用來增加、讀取、修改、刪除相關資料,而這些程式碼通常都是重複的
ORM的優勢和劣勢
ORM的優勢
ORM解決的主要問題是物件和關係的對映
它通常把一個類和一個表一一對應,類的每個例項對應表中的一條記錄,類的每個屬性對應表中的每個欄位
ORM提供了對資料庫的對映,不用直接編寫SQL程式碼,只需像操作物件一樣從資料庫操作資料
讓軟體開發人員專注於業務邏輯的處理,提高了開發效率
ORM的劣勢
ORM的缺點是會在一定程度上犧牲程式的執行效率
ORM長時間使用導致sql語句使用的不熟練
ORM基本操作
因為ORM相當於是對資料庫進行對映操作,所以我們需要跟資料庫中的表建立關係
在app中的models.py中編寫模型類
class GirlsInfo(models.Model):
# 欄位名 = 欄位型別 + 約束條件
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()
執行資料庫遷移相關命令
python38 manage.py makemigrations 將操作記錄到小本本上(migrations)
執行成功後migrations資料夾下會多出一個py檔案
python38 manage.py migrate 將操作同步到資料庫上
'''注意每次在models.py修改了與資料庫相關的程式碼 都需要再次執行上述命令'''
在下圖位置輸入以上程式碼
如果出現下方問題
解決方案是修改配置檔案
成功的結果如下圖
ORM基本語句
from app01 import models
models.類名.objects.create() 建立記錄
models.UserInfor.objects.create(name='zzh',pwd='123')
models.類名.objects.filter() 檢視記錄
res = models.UserInfor.objects.filter(name=name)
print(res)
print(res[0])
print(res[0].id)
print(res[0].name)
print(res[0].pwd)
models.類名.objects.update() 修改記錄
models.UserInfor.objects.filter(id=2).update(name='xiaozhu',pwd='666')
models.類名.objects.delete() 刪除記錄
models.UserInfor.objects.filter(id=2).delete()