简介
自动化框架向平台化演进时,用户系统是核心扩展功能之一。本文聚焦于使用 Django 和 Vue3 实现完整的前后端分离注册与登录功能。
测试工程师通常熟悉 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。

后端实现
3.1 Django 项目创建与环境搭建
3.1.1 创建项目
打开命令行,执行:
django-admin startproject apiauto

查看项目结构:
apiauto/
├── manage.py
└── apiauto/
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py


3.1.2 配置虚拟环境
推荐使用 Python 内置 venv 模块创建隔离环境。
在项目根目录运行:
python -m venv atvenv

进入 PyCharm 设置页面,修改解释器路径指向新创建的虚拟环境:
- File → Settings → Project → Python Interpreter
- 点击齿轮图标 → Add…
- 选择 "Existing environment"
- 指定路径:
X:/view/hwauto/apiauto/atvenv/Scripts/python.exe

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

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

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 # 时区数据库

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


3.1.4 新增用户模块 App
创建 users 应用用于管理用户相关逻辑:
python manage.py startapp 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 # 单元测试

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)

什么是序列化?
- 序列化:将对象转换为 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
})

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 路由挂载到主路由 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")),
]

3.3 调试
3.3.1 初始化数据库
执行迁移命令:
python manage.py makemigrations
python manage.py migrate

检查数据库表是否生成:

3.3.2 命令行启动服务
python manage.py runserver

3.3.3 PyCharm Debug 配置
- 点击运行配置下拉 → Edit Configurations

- 添加新的 Python 配置

- 设置 Script path 为
manage.py,Parameters 为 runserver

- 保存后点击运行或调试按钮

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

3.3.4 接口调试
查看可用路由
访问 http://127.0.0.1:8000/

访问 http://127.0.0.1:8000/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": "建"
}

检查数据库 auth_user 表:

调试登录接口
访问 http://127.0.0.1:8000/user/login/
提交登录凭证:
{
"username": "rebort",
"password": "rebort2025"
}

成功获取 JWT token,可用于后续接口认证。
JWT 是通用的前后端分离认证方案,不仅限于 Python/Django。
前端实现
4.1 项目创建
在 apiauto 同级目录创建前端项目:
npm init vue@latest
输入项目名 apiauto-views,其余选项默认即可。



4.1.2 安装依赖
使用 pnpm 作为包管理器:
npm install -g pnpm --prefix E:\nodejs
pnpm install

4.1.3 首次运行
pnpm dev

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

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

4.3 代码实现
4.3.1 安装必要依赖
pnpm install vue-router@4 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')
}
}
})

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 调试前端
-
启动前端服务:
pnpm dev
-
浏览器访问 http://localhost:5173/#/login
-
输入账号密码并登录:

登录成功跳转至首页。

至此,注册与登录全流程调试完成。
总结预告
本文完整实现了基于 Django + Vue3 的前后端分离用户认证系统,涵盖:
- Django 项目初始化与虚拟环境配置
- JWT 认证与 REST API 设计
- Vue3 路由、状态管理与请求封装
- 前后端联调验证
通过本实践,读者可掌握平台化开发的基础架构能力。
后续章节将围绕自动化测试平台需求,逐步扩展:
- 接口测试模块设计
- 用例管理与执行引擎
- 报告生成与可视化
- 权限控制系统
更多技术细节与最佳实践,尽在 后端 & 架构 与 前端框架/工程化 板块。