当前位置:首页 > 随笔 > 正文内容

基于Django与Vue的全栈项目开发实践

访客 随笔 2026年6月3日 1

项目架构设计与环境配置

本项目采用前后端分离架构,后端基于 Django 框架构建 RESTful API,前端使用 Vue.js 实现动态交互。整体技术栈为:Python 3.8 + Django 3.2 + DRF(Django REST Framework)+ MySQL + Redis + Vue 2.x。

虚拟环境管理

为保证依赖隔离,推荐使用 virtualenvwrapper-win 管理多个 Python 虚拟环境。

# 安装虚拟环境工具
pip install virtualenv virtualenvwrapper-win

# 设置工作目录(添加至系统环境变量)
WORKON_HOME = D:\Envs

# 创建指定解释器的虚拟环境
mkvirtualenv -p python luffy_project

# 常用命令
workon                  # 查看所有环境
workon luffy_project    # 进入环境
deactivate              # 退出当前环境
rmvirtualenv env_name   # 删除环境

Django 项目结构优化

标准项目目录如下:

luffy_backend/
├── manage.py
├── scripts/            # 可执行脚本(不提交 Git)
├── logs/               # 日志存储路径
├── luffy_backend/
│   ├── __init__.py
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── dev.py      # 开发配置
│   │   └── prod.py     # 生产配置
│   ├── urls.py
│   ├── wsgi.py
│   └── apps/           # 所有业务模块集中存放
│       ├── home/
│       ├── user/
│       └── __init__.py
└── utils/              # 公共工具包
    ├── exceptions.py
    ├── response.py
    └── models.py

开发配置文件(dev.py)

通过修改 sys.path 实现模块路径简化导入。

import os
import sys
from pathlib import Path

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

# 添加 apps 和根目录到 Python 路径
sys.path.insert(0, str(BASE_DIR / 'apps'))
sys.path.insert(0, str(BASE_DIR))

SECRET_KEY = 'your-secret-key'
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',
    'corsheaders',
    'home',  # 注册子应用
    'user'
]

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 = 'luffy_backend.urls'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'luffy',
        'USER': os.getenv('DB_USER', 'luffy'),
        'PASSWORD': os.getenv('DB_PASS', 'Luffy123?'),
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'OPTIONS': {'charset': 'utf8mb4'}
    }
}

LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False

STATIC_URL = '/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

媒体资源访问路由配置

在主路由中开放 media 文件访问权限。

from django.conf import settings
from django.conf.urls.static import static
from django.urls import path, include
from django.views.static import serve

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('home.urls')),
    path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

日志系统集成

利用 Python 内置 logging 模块实现多处理器日志输出。

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'detailed': {
            'format': '{levelname} {asctime} {module}:{lineno} {message}',
            'style': '{'
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'detailed'
        },
        'file': {
            'level': 'WARNING',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(BASE_DIR.parent, 'logs', 'app.log'),
            'maxBytes': 100 * 1024 * 1024,
            'backupCount': 5,
            'formatter': 'detailed',
            'encoding': 'utf-8'
        }
    },
    'loggers': {
        'django': {
            'handlers': ['console', 'file'],
            'level': 'INFO',
            'propagate': True
        }
    }
}

封装通用日志对象:

# utils/logger.py
import logging
app_logger = logging.getLogger('django')

统一响应格式封装

继承 DRF Response 类定制返回结构。

# utils/response.py
from rest_framework.response import Response

class SuccessResponse(Response):
    def __init__(self, data=None, message='操作成功', code=200, status=None, **kwargs):
        result = {
            'code': code,
            'message': message,
            'data': data or {}
        }
        result.update(kwargs)
        super().__init__(data=result, status=status)

全局异常处理

重写 DRF 异常处理器以支持自定义错误码和日志记录。

# utils/exceptions.py
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
from .response import SuccessResponse
from .logger import app_logger

def exception_handler(exc, context):
    request = context['request']
    view = context['view']

    # 使用原生处理器先尝试处理
    response = drf_exception_handler(exc, context)

    if response is None:
        # 非 DRF 认知的异常(如数据库错误、视图错误)
        app_logger.error(
            f'未捕获异常 | 用户:{getattr(request.user,"username","匿名")}|'
            f'IP:{request.META.get("REMOTE_ADDR")}|'
            f'路径:{request.path}|错误:{str(exc)}'
        )
        return SuccessResponse(code=999, message='服务器内部错误', status=500)

    # 已知异常也记录日志
    app_logger.warning(f'客户端异常 | {request.path} | {exc}')
    return response

注册到 settings:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler'
}

数据库初始化

创建专用数据库用户并授权:

CREATE DATABASE luffy CHARACTER SET utf8mb4;
CREATE USER 'luffy_user'@'%' IDENTIFIED BY 'SecurePass123!';
GRANT ALL PRIVILEGES ON luffy.* TO 'luffy_user'@'%';
FLUSH PRIVILEGES;

安装数据库驱动:

pip install mysqlclient

扩展用户模型

继承 AbstractUser 添加手机号与头像字段。

# apps/user/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')
    avatar = models.ImageField(upload_to='avatars/', default='default.png', verbose_name='头像')

    class Meta:
        db_table = 'luffy_user'
        verbose_name = '用户信息'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.username

前端基础搭建(Vue)

全局样式与配置

清除默认样式:

/* assets/css/reset.css */
* { margin: 0; padding: 0; box-sizing: border-box; }
ul, ol { list-style: none; }
a { text-decoration: none; color: inherit; }
img { vertical-align: middle; }
table { border-collapse: collapse; }

配置基地址:

// assets/js/config.js
export default {
  API_BASE: process.env.NODE_ENV === 'development'
    ? 'http://127.0.0.1:8001/api/v1'
    : 'https://api.luffy.com/v1'
}

挂载为 Vue 原型属性:

// main.js
import Vue from 'vue'
import config from '@/assets/js/config'

Vue.prototype.$config = config

常用插件集成

npm install axios vue-cookies element-ui --save
import axios from 'axios'
import cookies from 'vue-cookies'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI)
Vue.prototype.$http = axios
Vue.prototype.$cookies = cookies

核心功能实现

轮播图接口开发

抽象公共模型:

# utils/models.py
from django.db import models

class BaseModel(models.Model):
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
    is_active = models.BooleanField(default=True, verbose_name='是否激活')
    order = models.PositiveSmallIntegerField(default=1, verbose_name='排序')

    class Meta:
        abstract = True

轮播图表定义:

# apps/home/models.py
from django.db import models
from utils.models import BaseModel

class Banner(BaseModel):
    title = models.CharField(max_length=32, verbose_name='标题')
    description = models.TextField(blank=True, verbose_name='描述')
    link = models.URLField(verbose_name='跳转链接')
    image = models.ImageField(upload_to='banners/', verbose_name='图片')

    class Meta:
        db_table = 'home_banner'
        ordering = ['-order']

    def __str__(self):
        return self.title

序列化与视图:

# serializers.py
from rest_framework import serializers
from .models import Banner

class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = ['id', 'title', 'description', 'link', 'image', 'order']

# views.py
from rest_framework.generics import ListAPIView
from .models import Banner
from .serializers import BannerSerializer
from utils.response import SuccessResponse

class BannerListView(ListAPIView):
    queryset = Banner.objects.filter(is_active=True)
    serializer_class = BannerSerializer

    def list(self, request, *args, **kwargs):
        response = super().list(request, *args, **kwargs)
        return SuccessResponse(data=response.data, message='获取轮播图成功')

登录注册体系

支持多方式登录与短信验证注册。

# serializers.py
from rest_framework_simplejwt.tokens import RefreshToken
from django.core.cache import cache
import re

class BaseLoginSerializer(serializers.Serializer):
    def validate(self, attrs):
        self._authenticate(attrs)
        self._generate_token()
        return attrs

    def _authenticate(self, attrs):
        raise NotImplementedError

    def _generate_token(self):
        user = getattr(self, 'user', None)
        refresh = RefreshToken.for_user(user)
        self.context['token'] = str(refresh.access_token)
        self.context['refresh'] = str(refresh)

class MultiFieldLoginSerializer(BaseLoginSerializer):
    account = serializers.CharField()
    password = serializers.CharField()

    def _authenticate(self, attrs):
        account = attrs['account']
        password = attrs['password']
        user = User.objects.filter(
            models.Q(username=account) |
            models.Q(mobile=account) |
            models.Q(email=account)
        ).first()
        if user and user.check_password(password):
            self.user = user
        else:
            raise serializers.ValidationError('账号或密码错误')

class SMSCodeLoginSerializer(BaseLoginSerializer):
    mobile = serializers.CharField()
    code = serializers.CharField()

    def _authenticate(self, attrs):
        mobile = attrs['mobile']
        code = attrs['code']
        cached_code = cache.get(f"sms_code_{mobile}")
        if code != cached_code:
            raise serializers.ValidationError('验证码无效')
        self.user = User.objects.get(mobile=mobile)

发送短信验证码:

# utils/sms.py
import random
from tencentcloud.sms.v20210111 import sms_client, models
from django.core.cache import cache

class TencentSMS:
    def __init__(self, mobile, template_id='1871234'):
        self.mobile = mobile
        self.template_id = template_id

    def send(self):
        code = ''.join([str(random.randint(0, 9)) for _ in range(4)])
        cache.set(f"sms_code_{self.mobile}", code, timeout=60 * 5)  # 5分钟有效

        # 实际调用腾讯云 SDK 发送
        # client.SendSms(...)
        return {'status': 'success', 'code': code}

注册逻辑:

class RegisterSerializer(serializers.ModelSerializer):
    code = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = ['mobile', 'password', 'code']
        extra_kwargs = {'password': {'write_only': True}}

    def validate_mobile(self, value):
        if not re.match(r'^1[3-9]\d{9}$', value):
            raise serializers.ValidationError('手机号格式不正确')
        if User.objects.filter(mobile=value).exists():
            raise serializers.ValidationError('该手机号已注册')
        return value

    def validate(self, attrs):
        code = attrs.pop('code')
        cached = cache.get(f"sms_code_{attrs['mobile']}")
        if code != cached:
            raise serializers.ValidationError('验证码错误')
        attrs['username'] = f"user_{attrs['mobile'][-4:]}"
        return attrs

    def create(self, validated_data):
        return User.objects.create_user(**validated_data)

相关文章

可以按小时收费的VPS

很多 VPS 提供商都支持 按小时计费(hourly billing),想短期试用 / 临时搭建节点、测试网络、短期项目等场景非常合适。下面是当前最主流且靠谱的按小时 VPS 选项,分别按不同需求场景整理: 1. Vultr(全球节点,包括日本) 按小时计费 可选机房:东京 / 大阪 / 洛杉矶 / 法兰克福 / 伦敦 … 支持 PayPal(部分情况),但更常用信用卡/PayPal+卡价格参考$...

在 iPhone 上下载国外App

地区/国家限制App Store 会根据 Apple ID 的国家或地区限制应用下载。如果你的 Apple ID 绑定的是中国大陆,就可能无法下载 OpenAI 官方的 ChatGPT 应用,因为它在大陆 App Store 不上架。解决办法:换成美国、加拿大、香港等地区的 Apple ID。或者在现有 Apple ID 上更改地区。注册一个国外 Apple ID(推荐)比如注册 美国区 Appl...

Node.js 中的异步编程:回调与 Promise

Node.js 是一个基于 JavaScript 构建的单线程、非阻塞运行环境,它通过异步编程机制来高效处理多个操作。在执行如文件读取、API 请求或数据库查询等任务时,Node.js 不会等待这些操作完成,而是使用回调函数和 Promise 来避免阻塞主线程。 回调方式实现异步 那么当异步操作完成后,Node.js 如何知道接下来要做什么呢?这就要用到 回调函数(callback)。 回调本质上...

Selenium自动化测试入门指南

Selenium自动化测试入门指南

什么是自动化测试? 自动化测试是指利用软件工具自动执行测试用例,模拟用户操作,如打开网页、点击链接、输入文本等,并验证结果是否符合预期。 其主要优点包括: 大幅减少人工成本 测试速度快 可以在非工作时间运行 支持持续集成和交付 然而,它也存在一些局限性,例如开发成本较高、不适合快速变化的项目、依赖稳定的UI界面等。 自动化测试的应用条件 适合引入自动化测试的情况包括: 手动测试耗时且需要大量...

MariaDB Galera集群故障快速恢复指南

OpenStack控制节点采用三节点MariaDB Galera集群架构。当数据库集群因故障重启时,有时会出现Galera集群无法正常启动的问题。虽然有多种方法可以恢复数据库服务,但如何实现快速启动同时确保数据完整性呢? 通过分析日志发现,MariaDB Galera集群节点宕机时会在日志中输出以下信息: [Note] WSREP: 新集群视图:全局状态: 874d8e7e-5980-11e8-8...

Android 中 EventBus 的通信机制与实现原理深度解析

EventBus 核心设计思想 EventBus 是一个基于观察者模式的事件总线框架,广泛应用于 Android 平台以实现组件解耦。它通过中心化的消息分发机制,使不同层级、不同线程的对象能够以"发布-订阅"方式通信,避免了传统接口回调或广播带来的强依赖问题。 核心角色说明 事件(Event):任意 Java 对象,作为数据载体,如网络状态变更通知、用户登录信息等。 发布者(Publi...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。