# 網頁前後端程式設計 (2021.06.05) ## 後端理論 (以 Django 為例) ### MVC architecture MVC 是由三個字組成,分別為 > **M**odel > **V**iew > **C**ontroller **在後端裡面** > *Model*: DB, DBMS, 操作 DB 的 algorithm, etc. > > *View*: 在以前前後端混和的時代裡,view 代表 HTML 的 template。但是在前後端分離情況下,view 會到前端實作,所以,在後端就沒有 view 了 > > *Controller*: 使用者在前端會輸入一個 URL (http://.../.../),此時,後端的 router 接收到 URL 之後,會解析這個 URL,交給對應的 controller 處理,controller 就要負責從 model 取出資料給前端,如果回傳資料量較大,則會需要套用模板回傳給前端。而在前後端分離的情況,後端就會使用 **JSON** 格式回傳資料 JSON (JavaScript Object Notation) 為將結構化資料 (structured data) 呈現為 JavaScript 物件的標準格式,常用於網站上的資料呈現、傳輸 (例如將資料從 server 送至 client 端,以利顯示網頁)。 簡單來說,JSON 就是一個 key 對應一個 value,例如 ```json= { "firstName": "John", "lastName": "Smith", "sex": "male", "age": 25, "address": { "streetAddress": "21 2nd Street", "city": "New York", "state": "NY", "postalCode": "10021" }, "phoneNumber": [ { "type": "home", "number": "212 555-1234" }, { "type": "fax", "number": "646 555-4567" } ] } ``` 以上面為例,firstname 就是一個 key,其對應的 value 就是 John JSON 的概念可以對應到 python 的 dictionary ### MVC 在 Django 裡的概念 這點須特別注意,在 Django 裡 **controller 叫做 view view 叫做 template** ### Controller 的一些細節 ```graphviz digraph "details of controller" { URL -> middleware; middleware -> Authentication; middleware -> Authorization; Authorization -> controller Authentication -> Login; Authentication -> Captcha; Authentication -> Log; Login, Captcha,Log -> controller; } ``` Server 會記錄 Log,大致流程如下 ```graphviz digraph "log" { Server -> Session, cookie; Session -> SessionID; cookie -> key; key -> SessionID; SessionID -> Authentication; Authentication -> Data; } ``` Server 會創造一個 session,和一個 cookie 回傳給 client。當 client 要求資料的時候,server 會看這個 session ID 與 cookie 有沒有符合 Session 是一個很大的 Hashtable,裡面會存著每位 client 的 session ID,每個 session ID 就會記錄著 user ID, last login time, etc. 大概像這樣 ```json= { "sujkfrukhfakhfukuahfl": { // session ID "uid": 0; "name": "otischung"; ... } } ``` 在前後端分離的實作中,server 需要有一個 **Authentication 的 model 處理 client 的驗證**,client 使用 POST 對 server 發出 login 的請求,如果驗證成功,server 就會回傳 **token**,client 之後每次請求就要帶著這個 token 對於 server 發出請求,server 也會對於每次請求驗證 token 之後回應 client ## 後端實作 (Django) (使用 pycharm 開發) ### 安裝 Django 暨初始化專案 首先安裝 Django ```bash= pip install django ``` 安裝完成之後,就會有 `django-admin` 這個指令,可以查看有哪些指令可用 接下來,我們就要使用 Django 開發後端 ```bash= django-admin startproject <project_name> <location> ``` 例如: ```bash= django-admin startproject tmp_project ./ ``` 這樣 Django 就會在這個這個目錄下新增目錄名為 tmp_project,並且會在專案目錄下新增 **manage.py**,之後我們會一直使用 **manage.py** 操作後端 我們可以用以下指令啟動後端 ```bash= python manage.py runserver ``` 此時會看到 server 運作在 http://127.0.0.1:8000/ 裡面,到瀏覽器輸入就能看到 Django 的 Hello World 畫面 ### Django 預設的管理介面 可以發現在 tmp_project 目錄下,有一個 urls.py,其中有一項 ```python= urlpatterns = [ path('admin/', admin.site.urls), ] ``` 這是 Django 預設的管理介面,我們實際去 http://127.0.0.1:8000/admin/ ,就能看到 Django 的 admin 登入頁面 ### SQLite 與 Django 的關聯 在我們第一次 runserver 之後,就會在專案目錄下新增 db.sqlite3 的檔案,**sqlite 會將 DB 存在一個檔案裡**,所以不需要另外架設 SQL server 我們看一下 tmp_project 目錄下的 settings.py,會看到 Django 預設使用的 DB 是 SQLite3 ```python= DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } ``` #### 同步 DB 與 program: Migrate 一般來說,要架設一個 server,首先就是要安裝 server,create DB,然後在上述的地方輸入一些例如帳號密碼的驗證規則。再來就是要寫一些 SQL 的指令來保證 DB 的資料和程式上的資料是一致的,雖然比較麻煩,但是在一些大型專案,還是免不了人工設定 目前主流的 Relative Database 是 PostgreSQL,MySQL 已經有點過時了, 而 Django 有一個東西稱為 **Migration**,當你寫完 model 之後,migration 會自動產生 SQL 語句,讓 DB 的資料和程式上的資料是一致的,優點是很方便,缺點就是沒有彈性 執行以下指令 ```bash= python manage.py makemigrations ``` 第一次會顯示 No changes detected **<font color=#FF0000>之後如果有改到 models.py 的話,一定要記得執行以上指令</font>** 接著執行 ```bash= python manage.py migrate ``` 這樣就會把所有變更寫到 db.sqlite3 裡面 ### 新增管理員 Django 的框架是自帶後台的,所以我們可以執行以下指令新增管理員 ```bash= python manage.py createsuperuser ``` 接著輸入帳號密碼,即設定完成,這樣我們就可以登入進去 http://127.0.0.1:8000/admin/ 裡面了 我們可以發現,Django 預設有 Groups 和 Users 這兩個 DB ## 後端新增功能 (留言板 comment) 後端的架構大致上是如下圖所示 ```graphviz digraph "details of controller" { Site -> Application; Application -> MVC; } ``` 所以,我們要新增一個 app 名為 comment ```bash= python manage.py startapp comment ``` 我們就能看到 Django 為我們新增了名為 comment 的目錄,也在這個目錄底下新增了一些檔案 ### 連結 DB models.py 這裡的寫法就是用 class 將 models 包進來 ```python= from django.contrib.auth.models import User from django.db import models # Create your models here. class Comment(models.Model): author = models.ForeignKey(User, on_delete=models.CASCADE) # ForeignKey: 關聯到另一張表; CASCADE: 連動刪除 created_at = models.DateTimeField(auto_now_add=True) content = models.TextField(max_length=300) def __str__(self): return f"{self.author.username}: {self.content[:20]}" ``` #### User Django 裡有一個 field (model) 名為 User,將其 import 進來即可使用 User 裡面有一個 field 名為 Username #### <font color=#FF0000>正規化 (Normalization)</font> use <font color=#FF0000>ForeignKey</font> 假設有一張表叫做 Comments,這張表有一個欄位叫做 Author,裡面會儲存很多關於這個 Author 的訊息。當我們想要存取、新增或修改 Author 裡的某一個訊息的時候,我們不希望走訪所有欄位 (linear search) Comments | Author | Sudoer | Contents | Write Time | | ---------- | ------ | ----------------- | ----------- | | Otis Chung | yes | text1, text2, ... | t1, t2, ... | | Ray Chung | no | text3, text4, ... | t3, t4, ... | ##### 第一正規化 NF1 我們會將所有含多筆資料的欄位展開,使 key 與 value 形成 one-to-one 的形式;並且給 Author 這張表所有欄位一個 id 為主鍵值 (primary key),隱含著id為這張表中不可重複且具代表性的**唯一**值 Comments | ID | Author | Sudoer | Contents | Write Time | | -- | ---------- | ------ | --------- | ----------- | | 1 | Otis Chung | yes | text1 | t1 | | 2 | Otis Chung | yes | text2 | t2 | | 3 | Ray Chung | no | text3 | t3 | | 4 | Ray Chung | no | text4 | t4 | 如此一來,client 要查詢的時候,只需要輸入 ID 即可搜尋到想要的資料 ##### 第二正規化 NF2 隨著留言數越來越多,我們會發現為了製造 one-to-one 的形式,形成空間浪費 Comments | ID | Author | Sudoer | Contents | Write Time | | -- | ---------- | ------ | --------- | ----------- | | 1 | Otis Chung | yes | text1 | t1 | | 2 | Otis Chung | yes | text2 | t2 | | 3 | Ray Chung | no | text3 | t3 | | 4 | Ray Chung | no | text4 | t4 | | 5 | Otis Chung | yes | text5 | t5 | | 6 | Otis Chung | yes | text6 | t6 | | 7 | Ray Chung | no | text7 | t7 | | 8 | Ray Chung | no | text8 | t8 | 所以,我們會將 Author 獨立出來建表,如此一來就能省下空間 Comments | ID | Author_ID | Contents | Write Time | | -- | ---------- | --------- | ----------- | | 1 | A1 | text1 | t1 | | 2 | A1 | text2 | t2 | | 3 | A2 | text3 | t3 | | 4 | A2 | text4 | t4 | | 5 | A1 | text5 | t5 | | 6 | A1 | text6 | t6 | | 7 | A2 | text7 | t7 | | 8 | A2 | text8 | t8 | Author | Author_ID | Author | Sudoer | | --------- | ---------- | ------ | | A1 | Otis Chung | yes | | A2 | Ray Chung | no | 如此一來,client 要查詢的時候,只需要輸入 Author_ID 和 ID 即可快速又簡單得搜尋到想要的資料 <font color=#FF0000>ForeignKey</font> 的意思就是參考到另一張表 ### 新增寫好的 app: comment 將寫好的 models 註冊到管理頁面 在 admin.py 裡面這樣寫 ```python= from django.contrib import admin from .models import Comment # Register your models here. admin.site.register(Comment) ``` 在 tmp_project 裡面的 settings.py 裡面的 INSTALLED_APPS 加上我們所新增的 app: comment ```python= INSTALLED_APPS = [ ..., 'comment' ] ``` **記得,我們已經修改了 models.py,所以要重新 migrate** ```bash= python manage.py makemigrations python manage.py migrate ```