找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2703

积分

1

好友

371

主题
发表于 昨天 02:34 | 查看: 0| 回复: 0

简介

自动化框架向平台化演进时,用户系统是核心扩展功能之一。本文聚焦于使用 DjangoVue3 实现完整的前后端分离注册与登录功能。

测试工程师通常熟悉 Python、Selenium、Requests 等自动化技术栈。但当构建平台级应用时,必须掌握前后端协作机制。后端可选用 Flask、FastAPI 或 Django;前端则需结合 Vue/React 构建交互界面。

本教程基于 Django 4.2.24 + Vue3 技术栈,详细讲解从零搭建用户认证系统的全过程。涵盖以下关键技术点:

  • 后端(Django):项目初始化、虚拟环境配置、RESTful API 设计、JWT 认证集成、CORS 跨域处理。
  • 前端(Vue3):路由管理、Pinia 状态持久化、Axios 请求封装、登录态控制。

本文为《Django + Vue3 前后端分离实现自动化测试平台》系列首篇,后续将逐步扩展权限管理、接口测试模块等功能。

云栈社区 提供完整的技术支持资源和开发者交流空间。


开发前准备

2.1 基础要求

  • 掌握 Python 编程基础
  • 熟悉 Django 框架基本用法
  • 了解 Vue3 及 Element Plus 组件库

即使基础薄弱,也可跟随本文步骤完成实践。

2.2 开发环境

确保本地安装以下工具:

  • MySQL 8
  • Python 3.6+
  • Node.js
  • Django 4+
  • PyCharm 或 VSCode(推荐)

2.3 数据库准备

在 MySQL 中创建名为 apiauto 的数据库,并设置字符集与排序规则:

  • 字符集:utf8mb4
  • 排序规则:utf8mb4_general_ci

新建数据库界面,已填写 apiauto,字符集 utf8mb4,排序规则 utf8mb4_general_ci

确认数据库列表中出现 apiauto

数据库管理工具中的 apiauto 数据库列表


后端实现

3.1 Django 项目创建与环境搭建

3.1.1 创建项目

打开命令行,执行:

django-admin startproject apiauto

命令提示符窗口执行 django-admin startproject apiauto

查看项目结构:

apiauto/
├── manage.py
└── apiauto/
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

文件夹内容显示 apiauto 和 manage.py
PyCharm 中的项目结构树

3.1.2 配置虚拟环境

推荐使用 Python 内置 venv 模块创建隔离环境。

在项目根目录运行:

python -m venv atvenv

PyCharm 中创建虚拟环境 atvenv

进入 PyCharm 设置页面,修改解释器路径指向新创建的虚拟环境:

  1. File → Settings → Project → Python Interpreter
  2. 点击齿轮图标 → Add…
  3. 选择 "Existing environment"
  4. 指定路径:X:/view/hwauto/apiauto/atvenv/Scripts/python.exe

PyCharm 设置 Python 解释器为虚拟环境

重启 IDE 后,终端应显示 (atvenv) 标识。

VS Code 终端显示虚拟环境标识 (atvenv)

若使用 virtualenv 方式报错,请先安装:

pip install virtualenv
virtualenv atvenv

PowerShell 报错 'virtualenv' not recognized

3.1.3 安装依赖

在项目根目录创建 requirements.txt 文件,写入以下内容:

asgiref==3.9.1          # ASGI 支持
Django==4.2.24          # Web 框架核心
django-cors-headers==4.8.0  # 处理跨域请求
djangorestframework==3.16.1 # REST API 扩展
djangorestframework-simplejwt==5.5.1 # JWT 认证插件
mysqlclient==2.2.7      # MySQL 驱动(C 实现)
PyJWT==2.10.1           # JWT 编解码库
PyMySQL==1.1.2          # 纯 Python MySQL 驱动(备用)
sqlparse==0.5.3         # SQL 解析工具
typing_extensions==4.15.0 # 类型注解兼容
tzdata==2025.2          # 时区数据库

requirements.txt 文件内容截图

安装依赖:

pip install -r requirements.txt -i https://pypi.mirrors.ustc.edu.cn/simple/

终端输出依赖安装过程
成功安装所有包

3.1.4 新增用户模块 App

创建 users 应用用于管理用户相关逻辑:

python manage.py startapp users

终端执行 python manage.py startapp users 成功

此时项目结构新增 users/ 目录。

文件资源管理器中 users 文件夹已创建

3.2 编写代码

3.2.1 项目结构说明

apiauto/
├─ manage.py                    # 项目入口脚本
│
├─ apiauto/
│   ├─ __init__.py             # 包标识
│   ├─ settings.py             # 全局配置
│   ├─ urls.py                 # 主路由分发
│   ├─ wsgi.py                 # WSGI 启动文件
│   └─ asgi.py                 # ASGI 异步支持
│
└─ users/
    ├─ __init__.py
    ├─ admin.py                # Django Admin 配置
    ├─ apps.py                 # 应用配置
    ├─ models.py               # 数据模型定义
    ├─ serializers.py          # DRF 序列化器(手动创建)
    ├─ views.py                # 视图逻辑处理
    ├─ urls.py                 # 子路由配置(手动创建)
    └─ tests.py                # 单元测试

users 目录结构包含 migrations、views.py 等文件

3.2.2 配置 settings.py

更新 settings.py 文件,添加必要的第三方库和数据库连接信息。

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = 'django-insecure-upf+a6tatnrn9*e2wrj)n7vp!cbxwro=ae267ji*pofr=r^+ns'
DEBUG = True
ALLOWED_HOSTS = []

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # 第三方 ✅ 新增
    "rest_framework",
    "rest_framework_simplejwt",
    "corsheaders",

    # 自定义 ✅ 新增
    "users",
]

MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",     # ✅ 新增,处理跨域
    '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',
]

ROOT_URLCONF = 'apiauto.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'apiauto.wsgi.application'

# 数据库配置 ✅ 新增
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'apiauto',
        'USER': 'root',
        'PASSWORD': 'qwer1234',
        'HOST': '192.168.1.122',
        'PORT': 3307,
        'OPTIONS': {'charset': 'utf8mb4'},  # 防止表情等特殊字符报错
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
    {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
    {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
    {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]

LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True

STATIC_URL = 'static/'

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# DRF + JWT 配置 ✅ 新增
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ),
    "DEFAULT_PERMISSION_CLASSES": (
        "rest_framework.permissions.IsAuthenticated",
    ),
}

# 允许跨域 ✅ 新增
CORS_ALLOW_ALL_ORIGINS = True

3.2.3 序列化器实现

users/ 目录下创建 serializers.py 文件:

from django.contrib.auth.models import User
from rest_framework import serializers
from django.contrib.auth.password_validation import validate_password

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(
        write_only=True,
        required=True,
        validators=[validate_password]
    )
    password2 = serializers.CharField(write_only=True, required=True)

    class Meta:
        model = User
        fields = ("username", "password", "password2", "email", "first_name", "last_name")

    def validate(self, attrs):
        if attrs["password"] != attrs["password2"]:
            raise serializers.ValidationError({"password": "两次输入的密码不一致"})
        return attrs

    def create(self, validated_data):
        user = User.objects.create(
            username=validated_data["username"],
            email=validated_data["email"],
            first_name=validated_data.get("first_name", ""),
            last_name=validated_data.get("last_name", "")
        )
        user.set_password(validated_data["password"])
        user.save()
        return user

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "username", "email", "first_name", "last_name")

class UpdatePasswordSerializer(serializers.Serializer):
    old_password = serializers.CharField(required=True)
    new_password = serializers.CharField(required=True)

serializers.py 文件被红色框标出

什么是序列化?

  • 序列化:将对象转换为 JSON/XML 等格式,便于传输或存储。
  • 反序列化:将 JSON/XML 转换回对象。

在前后端分离中,常用于数据交换与渲染。

3.2.4 接口视图实现

编辑 users/views.py

from rest_framework.decorators import api_view, permission_classes
from rest_framework import generics, permissions
from rest_framework.response import Response
from rest_framework.views import APIView
from django.contrib.auth.models import User
from .serializers import RegisterSerializer, UserSerializer, UpdatePasswordSerializer

class RegisterView(generics.CreateAPIView):
    queryset = User.objects.all()
    serializer_class = RegisterSerializer
    permission_classes = (permissions.AllowAny,)

class UserProfileView(generics.RetrieveUpdateAPIView):
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_object(self):
        return self.request.user

class UpdatePasswordView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        serializer = UpdatePasswordSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        user = request.user
        if not user.check_password(serializer.data.get("old_password")):
            return Response({"error": "旧密码错误"}, status=400)

        user.set_password(serializer.data.get("new_password"))
        user.save()
        return Response({"message": "密码更新成功"})

@api_view(["GET"])
@permission_classes([permissions.IsAuthenticated])
def user_profile(request):
    return Response({
        "username": request.user.username,
        "email": request.user.email
    })

views.py 文件被红色框标出

3.2.5 路由配置

users/ 下创建 urls.py

from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from .views import RegisterView, UserProfileView, UpdatePasswordView

urlpatterns = [
    # JWT 认证
    path("login/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
    path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),

    # 用户相关
    path("register/", RegisterView.as_view(), name="register"),
    path("user/", UserProfileView.as_view(), name="user_profile"),
    path("user/password/", UpdatePasswordView.as_view(), name="update_password"),
]

users/urls.py 文件被红色框标出

users 路由挂载到主路由 apiauto/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path("user/", include("users.urls")),
]

主 urls.py 文件中 include users.urls

3.3 调试

3.3.1 初始化数据库

执行迁移命令:

python manage.py makemigrations
python manage.py migrate

终端输出 migrate 过程,所有操作 OK

检查数据库表是否生成:

数据库管理工具显示 auth_user、django_session 等表

3.3.2 命令行启动服务

python manage.py runserver

开发服务器启动日志,监听 127.0.0.1:8000

3.3.3 PyCharm Debug 配置

  1. 点击运行配置下拉 → Edit Configurations
    Edit Configurations 菜单项
  2. 添加新的 Python 配置
    Add New Configuration 下拉菜单
  3. 设置 Script path 为 manage.py,Parameters 为 runserver
    配置 manage.py runserver 参数
  4. 保存后点击运行或调试按钮
    运行按钮旁有 manage 配置项

调试日志输出在 Console 而非 Terminal。

PyCharm Debug 输出日志

3.3.4 接口调试

查看可用路由

访问 http://127.0.0.1:8000/

Django 404 页面列出 admin/ 和 user/ 路由

访问 http://127.0.0.1:8000/user/

404 页面列出 user/ 下的具体子路由

调试注册接口

访问 http://127.0.0.1:8000/user/register/

输入 JSON 数据:

{
  "username": "rebort",
  "password": "rebort2025",
  "password2": "rebort2025",
  "email": "rebort@163.com",
  "first_name": "陈",
  "last_name": "建"
}

点击 POST 提交。

注册接口调试页面,输入用户名密码等字段

返回 201 Created 表示成功。

{
    "username": "reborn",
    "email": "reborn@163.com",
    "first_name": "陈",
    "last_name": "建"
}

HTTP 201 Created 响应体包含用户信息

检查数据库 auth_user 表:

数据库记录显示 reborn 用户已插入

调试登录接口

访问 http://127.0.0.1:8000/user/login/

提交登录凭证:

{
  "username": "rebort",
  "password": "rebort2025"
}

登录接口返回 access 和 refresh token

成功获取 JWT token,可用于后续接口认证。

JWT 是通用的前后端分离认证方案,不仅限于 Python/Django。


前端实现

4.1 项目创建

apiauto 同级目录创建前端项目:

npm init vue@latest

输入项目名 apiauto-views,其余选项默认即可。

命令行创建 Vue 项目,输入项目名称 apiauto-views
初始化完成提示 cd apiauto-views 等命令

文件资源管理器中出现 apiauto-views 文件夹

4.1.2 安装依赖

使用 pnpm 作为包管理器:

npm install -g pnpm --prefix E:\nodejs
pnpm install

终端执行 pnpm install 安装依赖

4.1.3 首次运行

pnpm dev

Vite 启动成功,本地地址 http://localhost:5173/

浏览器访问该地址,看到欢迎页即表示成功。

Vue 官方欢迎页面 You did it!

4.2 代码框架

apiauto-views/
├─ src/
│  ├─ main.js
│  ├─ App.vue
│  ├─ router/index.js       # 路由配置
│  ├─ stores/user.js        # Pinia 状态管理
│  ├─ views/Login.vue       # 登录页
│  ├─ views/Home.vue        # 首页
│  └─ utils/request.js      # Axios 封装
├─ package.json

VS Code 文件结构包含 router、stores、views

4.3 代码实现

4.3.1 安装必要依赖

pnpm install vue-router@4 pinia axios

安装 vue-router、pinia、axios 成功

4.3.2 main.js 入口文件

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

4.3.3 修改 App.vue

<template>
  <router-view />
</template>

4.3.4 路由配置

src/router/index.js

import { createRouter, createWebHashHistory } from "vue-router"
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
import { useUserStore } from '../stores/user'

const routes = [
  { path: '/login', name: 'Login', component: Login },
  { path: '/', redirect: '/home' },
  { path: '/home', name: 'Home', component: Home }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes,
})

router.beforeEach((to, from, next) => {
  const userStore = useUserStore()
  if (to.path !== '/login' && !userStore.token) {
    next('/login')
  } else {
    next()
  }
})

export default router

4.3.5 状态管理(Pinia)

src/stores/user.js

import { defineStore } from 'pinia'
import router from '../router'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: localStorage.getItem('token') || '',
    refreshToken: localStorage.getItem('refreshToken') || ''
  }),
  actions: {
    setToken(access, refresh) {
      this.token = access
      this.refreshToken = refresh
      localStorage.setItem('token', access)
      localStorage.setItem('refreshToken', refresh)
    },
    logout() {
      this.token = ''
      this.refreshToken = ''
      localStorage.removeItem('token')
      localStorage.removeItem('refreshToken')
      router.push('/login')
    }
  }
})

浏览器开发者工具 Application Tab 查看 Local Storage

4.3.6 Axios 请求封装

src/utils/request.js

import axios from 'axios'
import { useUserStore } from '../stores/user'

const request = axios.create({
  baseURL: 'http://127.0.0.1:8000',
  timeout: 5000
})

let isRefreshing = false
let requestsQueue = []

request.interceptors.request.use(config => {
  const userStore = useUserStore()
  if (userStore.token) {
    config.headers.Authorization = `Bearer ${userStore.token}`
  }
  return config
})

request.interceptors.response.use(
  res => res.data,
  async err => {
    const userStore = useUserStore()
    const status = err.response?.status

    if (status === 401) {
      const refreshToken = userStore.refreshToken

      if (refreshToken) {
        if (!isRefreshing) {
          isRefreshing = true
          try {
            const resp = await request.post('/user/token/refresh/', {
              refresh: refreshToken
            })
            const newToken = resp.access
            userStore.token = newToken
            localStorage.setItem('token', newToken)
            requestsQueue.forEach(cb => cb(newToken))
            requestsQueue = []
          } catch (e) {
            userStore.logout()
            const router = (await import('../router')).default
            router.push('/login')
          } finally {
            isRefreshing = false
          }
        }

        return new Promise(resolve => {
          requestsQueue.push(token => {
            err.config.headers.Authorization = `Bearer ${token}`
            resolve(request(err.config))
          })
        })
      } else {
        userStore.logout()
        const router = (await import('../router')).default
        router.push('/login')
      }
    }

    return Promise.reject(err)
  }
)

export default request

4.3.7 登录页实现

src/views/Login.vue

<template>
  <div class="login-container">
    <h2 class="title">用户登录</h2>
    <form @submit.prevent="handleLogin" class="login-form">
      <div class="form-item">
        <label for="username">用户名</label>
        <input v-model="username" id="username" type="text" placeholder="请输入用户名" />
      </div>
      <div class="form-item">
        <label for="password">密码</label>
        <input v-model="password" id="password" type="password" placeholder="请输入密码" />
      </div>
      <button type="submit" class="login-btn">登录</button>
    </form>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useUserStore } from '../stores/user'
import { useRouter } from 'vue-router'
import request from '../utils/requests'

const username = ref('')
const password = ref('')
const userStore = useUserStore()
const router = useRouter()

const handleLogin = async () => {
  if (!username.value || !password.value) {
    alert('请输入用户名和密码')
    return
  }

  try {
    const resp = await request.post('/user/login/', {
      username: username.value,
      password: password.value
    })
    const { access, refresh } = resp
    userStore.setToken(access, refresh)
    router.push('/')
  } catch (err) {
    alert('登录失败,请检查用户名或密码')
    console.error(err)
  }
}
</script>

<style scoped>
.login-container {
  max-width: 400px;
  margin: 100px auto;
  padding: 20px;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.title { text-align: center; margin-bottom: 20px; }
.login-form { display: flex; flex-direction: column; }
.form-item { margin-bottom: 15px; }
.form-item label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-item input {
  width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 6px;
}
.login-btn {
  padding: 10px; background-color: #42b983; color: #fff; font-weight: bold;
  border: none; border-radius: 6px; cursor: pointer;
}
.login-btn:hover { background-color: #36976a; }
</style>

4.3.8 首页实现

src/views/Home.vue

<template>
  <div style="padding: 40px;">
    <h2>Home Page</h2>
    <p>欢迎,你已经登录成功 🎉</p>
    <button @click="logout">退出登录</button>
  </div>
</template>

<script setup>
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'

const userStore = useUserStore()
const router = useRouter()

const logout = () => {
  userStore.logout()
  router.push('/login')
}
</script>

4.4 调试前端

  1. 启动前端服务:

    pnpm dev
  2. 浏览器访问 http://localhost:5173/#/login

  3. 输入账号密码并登录:

    • 用户名:rebort
    • 密码:rebort2025

Vue 登录页面,已填入用户名 reboot

登录成功跳转至首页。

仪表板页面显示“欢迎回来!很高兴再次见到您”

至此,注册与登录全流程调试完成。


总结预告

本文完整实现了基于 Django + Vue3 的前后端分离用户认证系统,涵盖:

  • Django 项目初始化与虚拟环境配置
  • JWT 认证与 REST API 设计
  • Vue3 路由、状态管理与请求封装
  • 前后端联调验证

通过本实践,读者可掌握平台化开发的基础架构能力。

后续章节将围绕自动化测试平台需求,逐步扩展:

  • 接口测试模块设计
  • 用例管理与执行引擎
  • 报告生成与可视化
  • 权限控制系统

更多技术细节与最佳实践,尽在 后端 & 架构前端框架/工程化 板块。




上一篇:主流LLM在金融情绪分析中的关键盲点:为何语气不等于情绪得分?
下一篇:Ubuntu 24.04实战:Odoo 19与PostgreSQL 16企业ERP系统部署指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 01:48 , Processed in 0.333716 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表