Stories

Detail Return Return

Django學習(4)——前後端分離 - Stories Detail

1. Django的設計模式

Django是基於pyton語言的一個比較全面的框架,採用了MVC設計模式,但是Django更關注於模型(Model)、模板(Template)和視圖(Views),稱為 MTV模式。各自職責如下:

層次 職責
模型(Model) 即數據存取層。處理與數據相關的所有事務: 如何存取、如何驗證有效性、包含哪些行為以及數據之間的關係等。
視圖(View) 即表現層。處理與表現相關的決定: 如何在頁面或其他類型文檔中進行顯示。模型與模板的橋樑。
模板(Template) 即業務邏輯層。存取模型及調取恰當模板的相關邏輯。

從以上表述可以看出Django 視圖不處理用户輸入,而僅僅決定要展現哪些數據給用户,而Django 模板 僅僅決定如何展現Django視圖指定的數據。或者説, Django將MVC中的視圖進一步分解為 Django視圖 和 Django模板兩個部分,分別決定 “展現哪些數據” 和 “如何展現”,使得Django的模板可以根據需要隨時替換,而不僅僅限制於內置的模板。至於MVC控制器部分,由Django框架的URLconf來實現。URLconf機制是使用正則表達式匹配URL,然後調用合適的Python函數。URLconf對於URL的規則沒有任何限制,你完全可以設計成任意的URL風格,不管是傳統的,RESTful的,或者是另類的。框架把控制層給封裝了,無非與數據交互這層都是數據庫表的讀,寫,刪除,更新的操作.在寫程序的時候,只要調用相應的方法就行了,感覺很方便。程序員把控制層東西交給Django自動完成了。 只需要編寫非常少的代碼完成很多的事情。所以,它比MVC框架考慮的問題要深一步,因為我們程序員大都在寫控制層的程序。現在這個工作交給了框架,僅需寫很少的調用代碼,大大提高了工作效率。

2. 前後端分離

在Django中實現前後端分離,通常意味着後端(使用Django)只提供API接口,而前端(可能是Web頁面、移動應用或其他客户端)通過HTTP請求來獲取和提交數據。前後端分離的思想,就是前端負責界面交互和美觀,後端負責數據管理和數據輸出。前端和後端的通信,完全基於 API 來處理。
首先,説明下格式問題。前端找後端要數據,後端給數據,前端拿數據,都是有特定格式的,這種格式是前後端兩個規則好的。所以在這裏,以 json 格式為例,json 也是前端編程語言 javascript 的對象結構。

3. 使用Django和Vue構建前後端分離的WEB應用

3.1 後端定義API

在app1.urls.py中定義4個API,分別實現提交註冊表單、提交登錄表單、訪問主頁,驗證是否登錄和刷新圖片驗證碼功能。

#文件名:app1.urls.py
from django.urls import path
from app1 import views

urlpatterns = [
    path("register", views.RegisterResponse.as_view()),  # 提交註冊表單
    path("login", views.LoginResponse.as_view(), name="app1-login"),  # 提交登錄表單
    path("home", views.home, name="home"),  # 訪問主頁,驗證是否登錄
    path("get_image", views.VerificationCode.as_view()),  # 刷新圖片驗證碼
]

3.2 後端實現視圖函數

4個視圖函數與3.1中的4個API一一對應。

#文件名:app1.views.py
import json
from django.http import HttpResponse, JsonResponse

from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.views import View

from .form import RegisterForm, LoginForm
from verification_code import load_image


class RegisterResponse(View):
    def post(self, request):
        data = json.loads(request.body)
        form = RegisterForm(data)
        if form.is_valid:
            User.objects.create_user(username=form.username, password=form.password)
            form.info = form.register_success
            form.status = 1
        else:
            form.info = "\n".join(form.error)
            form.status = -1
        return JsonResponse(form.response)


class LoginResponse(View):
    def post(self, request):
        data = json.loads(request.body)
        form = LoginForm(data)
        if form.code != request.session["code"]:
            form.info = form.authentication_failure
            form.status = -1
            return JsonResponse(form.response)
        if form.is_valid:
            user = authenticate(request, username=form.username, password=form.password)
            if user:
                login(request, user) if user.is_active else None
                form.info = form.login_success
                form.status = 1
            else:
                form.info = form.login_failure
                form.status = -1
        else:
            error = "\n".join(form.error)
            form.info = error
            form.status = -1
        return JsonResponse(form.response)


def home(request):
    if request.user.is_authenticated:
        return JsonResponse({"view": "home"})
    else:
        return JsonResponse({"view": "login"})
    

class VerificationCode(View):
    def get(self, request):
        code, data = load_image()
        if code and data:
            request.session["code"] = code
            return HttpResponse(data)
        else:
            return HttpResponse("")

視圖函數中用到的用户名密碼格式校驗、隨機驗證碼圖片生成見app1.form.py和django_study.verification_code.__init__.py。

#文件名:app1.form.py
from django.contrib.auth.models import User
from string import ascii_lowercase, ascii_uppercase


class AbstractForm:
    def __init__(self, data):
        self.error = list()
        self.info = ""
        self.status = 0
        self.username = data.get("username") if "username" in data.keys() else ""
        self.password = data.get("password") if "password" in data.keys() else ""
        self.code = data.get("code") if "username" in data.keys() else ""  # 驗證碼
        self.username_valid = False
        self.password_valid = False
        # self.is_valid = self.username_valid and self.password_valid

    @property
    def is_valid(self):
        return self.username_valid and self.password_valid

    @property
    def response(self):
        return {"info": self.info, "status": self.status}


class RegisterForm(AbstractForm):
    def __init__(self, data: dict):
        super(RegisterForm, self).__init__(data)
        self.username_valid = self.username_strategy()
        self.password_valid = self.password_strategy()
        self.register_success = "註冊成功"
        self.register_failure = "\n".join(self.error)

    def username_strategy(self):
        res = True
        query = User.objects.filter(username=self.username)
        if query:
            self.error.append("用户{}已存在".format(self.username))
            res = False
        if len(self.username) > 30:
            self.error.append("用户名長度超過30")
            res = False
        if len(self.username) < 6:
            self.error.append("用户名長度小於6")
            res = False
        if len(self.username) < 1:
            self.error.append("用户名不能為空")
            res = False
        return res

    def password_strategy(self):
        res = True
        upper_exists = False
        lower_exists = False
        for letter in self.password:
            if letter in ascii_uppercase:
                upper_exists = True
                break
        for letter in self.password:
            if letter in ascii_lowercase:
                lower_exists = True
                break
        if len(self.password) < 8:
            self.error.append("密碼長度小於8")
            res = False
        if not upper_exists:
            self.error.append("密碼中不包含大寫字母")
            res = False
        if not lower_exists:
            self.error.append("密碼中不包含小寫字母")
            res = False
        return res


class LoginForm(AbstractForm):
    def __init__(self, data: dict):
        super(LoginForm, self).__init__(data)
        self.username_valid = self.username or None
        self.password_valid = self.password or None
        if not self.username:
            self.error.append("用户名不能為空")
        if not self.password:
            self.error.append("密碼不能為空")
        self.login_success = "登錄成功"
        self.login_failure = "\n".join(self.error)
        self.authentication_failure = "驗證碼錯誤"
#文件名:django_study.verification_code.__init__.py
import numpy as np
import cv2
import os
import base64


def randcolor(lower=0, upper=255):
    return np.random.randint(lower, upper), np.random.randint(lower, upper), np.random.randint(lower, upper)


def randchar():
    lower = chr(np.random.randint(97, 122))
    upper = chr(np.random.randint(65, 90))
    number = str(np.random.randint(0, 9))
    return np.random.choice([lower, upper, number])


def randpos(x0, x1, y0, y1):
    return np.random.randint(x0, x1), np.random.randint(y0, y1)


def load_image(image_width = 120, image_height = 30, num = 4, path = "./"):
    # 創建背景圖片
    img = np.random.randint(181, 255, (image_height, image_width, 3), np.uint8)
    image_name = ""
    x_pos = 0
    y_pos = int(image_height / 2)
    for i in range(num):  # num:驗證碼字符個數
        ch = randchar()
        image_name += ch
        # 添加文字
        cv2.putText(
            img,
            ch,
            (np.random.randint(x_pos, x_pos + int(image_width / (num + 1))),
             np.random.randint(y_pos, y_pos + int(image_height / 2))),  # 座標
            cv2.FONT_HERSHEY_SIMPLEX,
            0.8,  # 字號
            randcolor(0, 180),
            2,
            cv2.LINE_AA
        )
        # 添加直線作為干擾信息
        x_pos += int(image_width / (num + 1))
        img = cv2.line(
            img,
            randpos(0, image_width, 0, image_height),
            randpos(0, image_width, 0, image_height),
            randcolor(),
            np.random.randint(1, 2)
        )
    file = os.path.join(path, image_name + ".jpg")
    try:
        cv2.imwrite(file, img)
        if os.path.isfile(file):
            with open(file, "rb") as f:
                data = base64.b64encode(f.read()).decode('utf-8')
            os.unlink(file)
            return image_name, data  # 返回驗證碼字符串和base64編碼的圖像數據
        else:
            return None, None
    except:
        return None, None

3.3 前端路由定義

//文件名:./src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter);

const routes = [
  {
    path: '/',
    component: () => import('../views/home'),
    redirect: '/home',
    meta: {
      title: 'Django Study'
    }
  },
  {
    path: '/home',
    component: () => import('../views/home'),
    meta: {
      title: 'Django Study'
    }
  },
  {
    path: '/login',
    component: () => import('../views/login'),
    meta: {
      title: 'Login'
    }
  },
  {
    path: '/register',
    component: () => import('../views/register'),
    meta: {
      title: 'Register'
    }
  },
];

const router = new VueRouter({
  mode: 'history',
  routes
});

export default router

3.4 註冊頁面

<!--文件名:./src/view/register.vue-->
<template>
    <div>
        <div>
            <span>新用户</span>
            <label>
                <input v-model="username" type="text"/>
            </label>
        </div>
        <div>
            <span>請輸入新密碼</span>
            <label>
                <input v-model="password" type="text"/>
            </label>
        </div>
        <div>
            <span>請確認新密碼</span>
            <label>
                <input v-model="password2" type="text"/>
            </label>
        </div>
        <div>
            <button @click="registerSubmit">提交</button>
            <span id="tologin" @click="toLogin">已有賬户?</span>
        </div>
    </div>
</template>

<script>
    export default {
        name: "register",
        data() {
            return {
                username: "",
                password: "",
                password2: "",
                info: "",
                status: 0,
            }
        },
        methods: {
            registerSubmit () {
                if (this.password !== this.password2) {
                    console.log("密碼不一致")
                }
                else {
                    this.$axios({
                        method: "POST",
                        url: "/api/app1/register",
                        data: {
                            username: this.username,
                            password: this.password,
                        }
                    }).then((res) => {
                        const data = res.data;
                        this.info = data.info;
                        this.status = data.status;
                        if (this.status === 1) {
                            alert("註冊成功:" + this.username);
                        } else if (this.status === -1) {
                            alert("註冊失敗:" + this.info);
                        }
                        else {}
                    })
                }
            },
            toLogin () {
                this.$router.push("/login")
            }
        }
    }

</script>

<style scoped>
#tologin {
    color: #f00;
}
    #tologin:hover {
        cursor: pointer;
    }
</style>

3.5 登錄頁面

<!--文件名:./src/view/login.vue-->
<template>
    <div>
        <div>
            <span>用户名</span>
            <label>
                <input v-model="username" type="text"/>
            </label>
        </div>
        <div>
            <span>密碼</span>
            <label>
                <input v-model="password" type="password"/>
            </label>
        </div>
        <div>
            <span>驗證碼</span>
            <label>
                <input v-model="verificationCode" type="text"/>
            </label>
            <img
                style="width: 120px; height: 30px"
                :src="'data:image/png;base64,' + imageCode"
                @click="getImage"
                alt=""
            >
        </div>
        <div>
            <button @click="loginSubmit">確認</button>
            <button @click="toRegister">註冊</button>
        </div>
    </div>
</template>

<script>
    export default {
        name: "login",
        mounted() {
            this.getImage();
        },
        data() {
            return {
                username: "",
                password: "",
                verificationCode: "",  //驗證碼
                imageCode: "",         // 圖片
            }
        },
        methods: {
            loginSubmit () {
                this.$axios({
                    method: "POST",
                    url: "/api/app1/login",
                    data: {
                        username: this.username,
                        password: this.password,
                        code: this.verificationCode,
                    }
                }).then((res) => {
                    const data = res.data;
                    this.info = data.info;
                    this.status = data.status;
                    if (this.status === 1) {
                        alert("登錄成功:" + this.username);
                        this.$router.back();
                    } else if (this.status === -1) {
                        alert("登錄失敗:" + this.info);
                        this.getImage();
                    }
                    else {}
                })
            },
            getImage () {
                this.$axios({
                    method: "get",
                    url: "/api/app1/get_image",
                }).then(res => {
                    this.imageCode = res.data;
                    console.log(res.data);
                })
            },
            toRegister () {
                this.$router.push("/register")
            }
        }
    }
</script>

<style scoped>

</style>

3.6 主頁

<!--文件名:./src/view/home.vue-->
<template>
  <div style="overflow-y: auto; overflow-x: auto; height: 100%">
    home
  </div>
</template>

<script>

  export default {
    name: "home",
    mounted() {
      this.$axios({
        method: "get",
        url: "/api/app1/home"
      }).then(res => {
        const view = res.data.view;
        console.log("/" + view)
        this.$router.push("/" + view);
      })
    },
    components: {
    },
    data() {
      return {

      }

    },
  }
</script>

<style scoped>
</style>

4. 測試

user avatar u_14540126 Avatar lizhuo6 Avatar haoqidedalianmao Avatar zengjingaiguodekaomianbao Avatar kuanrongdeshanyang Avatar lintp Avatar k8scat Avatar yanyingjie Avatar codists Avatar taoqix Avatar liuhuzidebanli_edpemy Avatar fengliudeshanghen Avatar
Favorites 12 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.