DRF(Django Rest Framework)

作者:user 发布日期: 浏览量:7

一、项目初始化

1、创建项目

  • 创建虚拟环境
python -m venv drf_env

# 升级pip
python -m pip install --upgrade pip

# 查看镜像源
pip config list

# 切换镜像源(以下为默认镜像源)
pip config set global.index-url https://pypi.org/simple
# 切换镜像源(以下为清华镜像源)
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
  • 激活并使用虚拟环境 drf_env
.\drf_env\Scripts\activate
  • 安装django
pip install django
  • 创建 django 项目
django-admin startproject <项目名>

2、创建应用

  • 进入项目目录
 cd .\AdminDashboard\
  • 创建项目应用
# 创建 user 应用
python .\manage.py startapp user
# 创建 role 应用
python .\manage.py startapp role
# 创建 menu 应用
python .\manage.py startapp menu
  • 在 settings.py 文件中注册应用
# Application definition

INSTALLED_APPS = [
    # 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user.apps.UserConfig',
    'role.apps.RoleConfig',
    'menu.apps.MenuConfig'
]
  • 分别在应用下面创建 urls.py 文件
  • 然后在主配置里引入(开发一个模块放开一个模块,以免报错)
# AdminDashboard/urls.py

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

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('user/', include('user.urls'), name="user"), # 用户模块
    # path('role/', include('role.urls'), name="role"), # 角色模块
    # path('menu/', include('menu.urls'), name="menu"), # 权限模块
]
  • 在 settings.py 进行数据库配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'admin_dashboard',
        'USER': 'admin',
        'PASSWORD': 'lgk123123.',
        'HOST': '192.168.31.123',
        'PORT': '3306'
    },
}
  • 安装 mysqlclient
pip install mysqlclient

3、开发测试接口

  • 在 user/models.py 创建用户模型
from django.db import models

# Create your models here.
class SysUserModel(models.Model):
    id = models.AutoField(primary_key=True, verbose_name="序号")
    username = models.CharField(max_length=100, unique=True, verbose_name="用户名")
    password = models.CharField(max_length=100,  verbose_name="密码")
    avatar = models.CharField(max_length=255,  verbose_name="用户头像")
    email = models.CharField(max_length=100,  verbose_name="密码")
    phonenumber = models.CharField(max_length=11, null=True, verbose_name="手机号码")
    login_date = models.DateTimeField(null=True,  verbose_name="最后登录时间")
    status = models.IntegerField(null=True,  verbose_name="账号状态(0正常 1停用)")
    create_time = models.DateTimeField(null=True,  verbose_name="创建时间")
    update_time = models.DateTimeField(null=True,  verbose_name="更新时间")
    remark = models.CharField(max_length=500, null=True, verbose_name="备注")

    class Meta:
        db_table = "sys_user"
  • 在 user/views.py 创建视图
from django.http import  JsonResponse
from django.views import View

from .models import SysUserModel


# Create your views here.
class TestView(View):

    def get(self, request):
        user_queryset = SysUserModel.objects.all()
        user_dict = user_queryset.values()
        # print(user_dict)
        userList = list(user_dict)

        return JsonResponse({
            'code': 200,
            'msg': '测试!!!',
            "data": userList
        })
  • 在 user/urls.py 配置url路径
from django.urls import path

from .views import TestView

urlpatterns = [
    path('test', TestView.as_view(), name='test')
]

二、DRF(Django Rest Framework)

1、引入 JWT 前后端交互

  • JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)
  • JWT 就是一段字符串,用来进行用户身份认证的凭证,该字符串分为:头部、负载、签证
  • 前端在获取token时,一般获取的是 authorization 字段,但是在python获取时需要使用 http_authorization
token = request.META.get('HTTP_AUTHORIZATION')

创建生成Token:

  • 安装 rest_framework_jwt
pip install rest_framework_jwt
  • 在 settings.py 文件中配置
INSTALLED_APPS = [
    # 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework_jwt',
    'user.apps.UserConfig',
    'role.apps.RoleConfig',
    'menu.apps.MenuConfig'
]

# 配置token过期时间,默认为 5 分钟
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=10),  # 设置 token 有效期为 5 分钟
}
  • 引入使用生成 token
from rest_framework_jwt.settings import api_settings

class LoginView(View):

    def get(self, request):
        # 获取前端传来的参数
        username = request.GET.get('username')
        password = request.GET.get('password')

        # 用户对象
        user: SysUserModel = SysUserModel.objects.get(username=username, password=password)
        # print("user =>", type(user))
        # 获取token
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        # 将用户对象传递过去,获取该对象的属性值
        payload = jwt_payload_handler(user)
        # 将属性值编码成jwt格式字符串
        token = jwt_encode_handler(payload)

        # 返回 token 在响应头中
        response = JsonResponse({
                'code': 200,
                'msg': 'success'
            })
        response['Authorization'] = f'Bearer {token}'  # 设置响应头
        return response

自定义中间件实现token鉴权:

  • 开发时后端会有很多接口需要进行token鉴权,如果每个接口都去判断token,就会造成代码冗余。所以利用自定义中间件来统一实现所有接口的鉴权功能
  • 在 user 目录下新建 middleware.py 文件,新建中间件类 JWTAuthenticationMiddleware 继承 MiddlewareMixin 类
from django.http import JsonResponse
from django.utils.deprecation import MiddlewareMixin
from jwt import ExpiredSignatureError, InvalidTokenError, PyJWTError
from rest_framework_jwt.settings import api_settings


class JWTAuthenticationMiddleware(MiddlewareMixin):

    def process_request(self, request):
        # 定义请求白名单
        white_list = ['/user/login', '/user/login/', '/user/jwtTest/']
        # 获取请求路径
        path = request.path

        if path not in white_list and not path.startswith('/media'):
            # 获取token
            token = request.META.get('HTTP_AUTHORIZATION')
            # print("需要进行token认证")
            try:
                # 解析token
                jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
                jwt_decode_handler(token)
            except ExpiredSignatureError:
                return JsonResponse({'code': 401, 'msg': 'Token过期,请重新登录!'})
            except InvalidTokenError:
                return JsonResponse({'code': 401, 'msg': 'Token验证失败!'})
            except PyJWTError:
                return JsonResponse({'code': 401, 'msg': 'Token验证异常!'})
        else:
            # print("不需要Token验证")
            return None
  • 在 settings.py 中注册使用 JWTAuthenticationMiddleware 中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',  # 必须在 CommonMiddleware 之前的位置
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware', # 由于是前后端分离的,可以不用这个
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'user.middleware.JWTAuthenticationMiddleware',  # 自定义的token鉴权中间件
]

2、DRF(Django Rest Framework)

  • DRF 是用于构建 API 的成熟且广泛使用的Python库。它是基于流行的Web框架Django构建的一个轻量级REST框架,旨在简化复杂的数据访问API的开发过程
  • DRF官网:https://www.django-rest-framework.org/

DRF的特点:

  • 简单易用:DRF提供了许多开箱即用的功能,如认证策略、内容协商、限流等,使得开发者能够快速地搭建出 RESTful 风格的服务端接口
  • 自动化的URL路由:它允许定义清晰的URL模式,并自动生成相应的视图,从而减少了编写常规代码的时间
  • 序列化支持:DRF内建了序列化功能,可以帮助将模型实例转换为JSON或其他格式的数据,以及反序列化请求数据到模型对象
  • 灵活的配置:虽然提供了很多默认设置,但是DRF也非常容易根据项目需求进行定制
  • 丰富的文档支持:可以自动生成API文档,帮助前端开发者或第三方更好地理解和使用API

DRF安装:

  • drf 安装
pip install djangorestframework
  • drf-jwt安装
pip install djangorestframework-jwt
  • 在 settings.py 中配置使用
INSTALLED_APPS = [
    # 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework_jwt',
    'user.apps.UserConfig',
    'role.apps.RoleConfig',
    'menu.apps.MenuConfig'
]
  • 在 views.py 中定义测试用例
from django.http import JsonResponse
from django.views import View
from rest_framework_jwt.settings import api_settings

from .models import SysUserModel


# Create your views here.
class TestView(View):

    def get(self, request):
        token = request.META.get('HTTP_AUTHORIZATION')
        if token != None and token != '':
            user_queryset = SysUserModel.objects.all()
            user_dict = user_queryset.values()
            # print(user_dict)
            userList = list(user_dict)

            return JsonResponse({
                'code': 200,
                'msg': '测试!!!',
                "data": userList
            })
        else:
            return JsonResponse({
                'code': 401,
                'msg': '没有访问权限!'
            })


class JWTTestView(View):

    def get(self, request):
        user = SysUserModel.objects.get(username='user', password='user123')
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

        # 将用户对象传递过去,获取该对象的属性值
        payload = jwt_payload_handler(user)
        # 将属性值编码成jwt格式字符串
        token = jwt_encode_handler(payload)

        return JsonResponse({
            'code': 200,
            'msg': '测试!!!',
            "token": token
        })
  • 在 user/urls.py 配置路径
from django.urls import path

from .views import TestView, JWTTestView

# 作为最后一级时,路径结尾最好别加“/”,POST请求时会报错,如果是GET请求且拼接有参数,最好加“/”
urlpatterns = [
    path('test/', TestView.as_view(), name='test'),
    path('jwtTest/', JWTTestView.as_view(), name='jwt_test'),
]

3、跨域问题解决

  • CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种跨域访问机制,可让Ajax实现跨域访问
  • 在服务器的 response header 中,加入 “Access-Control-Allow-Origin: * ”便可支持 CORS

在Django中使用 CORS-header的中间件:

  • 安装 django-cors-headers
pip install django-cors-headers
  • 在 settings.py 中添加配置
  • CorsMiddleware 必须放在 CommonMiddleware 位置之前
INSTALLED_APPS = [
    # 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework_jwt',
    'corsheaders',
    'user.apps.UserConfig',
    'role.apps.RoleConfig',
    'menu.apps.MenuConfig'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware', # 必须在 CommonMiddleware 之前的位置
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware', # 由于是前后端分离的,可以不用这个
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# 设置CORS访问白名单
# CORS_ORIGIN_ALLOW_ALL 为 True,指定所有域名(IP)都可以访问后端接口,默认为 False
CORS_ORIGIN_ALLOW_ALL = True
# 设置运行携带Cookie
CORS_ALLOW_CREDENTIALS = True
# 设置默认允许请求头方法
CORS_ALLOW_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']

4、DRF框架引入解决序列化问题

  • 在通过模型获取到的数据不是json序列化对象,所以需要把实例转为json序列化对象,最简单的方式就是通过DRF框架的序列化模块功能实现。
  • 定义一个实体对象对应的序列化对象,继承 serializers.ModelSerializer
  • 在 user/models.py 文件中
from django.db import models
from rest_framework import serializers


# Create your models here.
class SysUserModel(models.Model):
    id = models.AutoField(primary_key=True, verbose_name="序号")
    username = models.CharField(max_length=100, unique=True, verbose_name="用户名")
    password = models.CharField(max_length=100, verbose_name="密码")
    avatar = models.CharField(max_length=255, verbose_name="用户头像")
    email = models.CharField(max_length=100, verbose_name="密码")
    phonenumber = models.CharField(max_length=11, null=True, verbose_name="手机号码")
    login_date = models.DateTimeField(null=True, verbose_name="最后登录时间")
    status = models.IntegerField(null=True, verbose_name="账号状态(0正常 1停用)")
    create_time = models.DateTimeField(null=True, verbose_name="创建时间")
    update_time = models.DateTimeField(null=True, verbose_name="更新时间")
    remark = models.CharField(max_length=500, null=True, verbose_name="备注")

    class Meta:
        db_table = "sys_user"


class SysUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = SysUserModel
        fields = '__all__'
  • 在 user/views.py 文件中使用,通过 SysUserSerializer(序列化对象).data
from django.http import JsonResponse
from django.views import View
from rest_framework_jwt.settings import api_settings

from .models import SysUserModel, SysUserSerializer


# Create your views here.

class LoginView(View):

    def get(self, request):
        # 获取前端传来的参数
        username = request.GET.get('username')
        password = request.GET.get('password')
        try:
            # 用户对象
            user: SysUserModel = SysUserModel.objects.get(username=username, password=password)
            # print("user =>", type(user))
            # 获取token
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
            # 将用户对象传递过去,获取该对象的属性值
            payload = jwt_payload_handler(user)
            # 将属性值编码成jwt格式字符串
            token = jwt_encode_handler(payload)
        except Exception as e:
            print(e)
            return JsonResponse({
                'code': 500,
                'msg': '用户名或密码错误'
            })
        return JsonResponse({
            'code': 200,
            'msg': '登录成功',
            'token': token,
            'data': SysUserSerializer(user).data # 将 user 对象进行序列化
        })

三、功能开发

1、登录功能实现

  • 在 user/models.py 文件
from django.db import models
from django.utils import timezone
from rest_framework import serializers


# Create your models here.
class SysUserModel(models.Model):
    id = models.AutoField(primary_key=True, verbose_name="序号")
    username = models.CharField(max_length=100, unique=True, verbose_name="用户名")
    password = models.CharField(max_length=100, verbose_name="密码")
    avatar = models.CharField(max_length=255, verbose_name="用户头像")
    email = models.CharField(max_length=100, null=True, verbose_name="邮箱")
    phonenumber = models.CharField(max_length=11, null=True, verbose_name="手机号码")
    status = models.IntegerField(null=True, verbose_name="账号状态(0正常 1停用)")
    login_date = models.DateTimeField(verbose_name="最后登录时间", default=timezone.now())
    create_time = models.DateTimeField(verbose_name="创建时间", default=timezone.now())
    update_time = models.DateTimeField(verbose_name="更新时间", default=timezone.now())
    remark = models.CharField(max_length=500, null=True, verbose_name="备注")

    class Meta:
        db_table = "sys_user"


class SysUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = SysUserModel
        fields = '__all__'
  • 在 user/views.py 文件
from django.http import JsonResponse
from django.views import View
from rest_framework_jwt.settings import api_settings

from .models import SysUserModel


# Create your views here.

class LoginView(View):

    def get(self, request):
        # 获取前端传来的参数
        username = request.GET.get('username')
        password = request.GET.get('password')
        try:
            # 用户对象
            user: SysUserModel = SysUserModel.objects.get(username=username, password=password)
            # print("user =>", user.username)
            # 获取token
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
            # 将用户对象传递过去,获取该对象的属性值
            payload = jwt_payload_handler(user)
            # 将属性值编码成jwt格式字符串
            token = jwt_encode_handler(payload)
        except Exception as e:
            print(e)
            return JsonResponse({
                'code': 500,
                'msg': '用户名或密码错误'
            })
        return JsonResponse({
            'code': 200,
            'msg': '登录成功',
            'token': token
        })
  • 在 user/urls.py 文件
from django.urls import path

from .views import LoginView

# 作为最后一级时,路径结尾最好别加“/”,POST请求时会报错,如果是GET请求且拼接有参数,最好加“/”
urlpatterns = [
    path('login', LoginView.as_view(), name='login'),
]

2、左侧动态权限菜单API实现

  • 在用户登录时,可以把用户对应的所有权限菜单一起获取。所以用到角色表、权限表、以及用户角色关联表、角色权限关联表。
  • 在 role/models.py 文件创建模型及关联表 SysRoleModel、SysRoleSerializer、SysUserRoleModel、SysUserRoleSerializer
from django.db import models
from django.utils import timezone
from rest_framework import serializers

from user.models import SysUserModel


# Create your models here.
# 系统角色类
class SysRoleModel(models.Model):
    id = models.AutoField(primary_key=True, verbose_name="序号")
    name = models.CharField(max_length=30, unique=True, verbose_name="角色名称")
    code = models.CharField(max_length=100, null=True, verbose_name="角色权限字符")
    create_time = models.DateTimeField(verbose_name="创建时间", default=timezone.now())
    update_time = models.DateTimeField(verbose_name="更新时间", default=timezone.now())
    remark = models.CharField(max_length=500, null=True, verbose_name="备注")

    class Meta:
        db_table = "sys_role"


# 序列化类
class SysRoleSerializer(serializers.ModelSerializer):
    class Meta:
        model = SysRoleModel
        fields = '__all__'


# 系统用户角色关联类
class SysUserRoleModel(models.Model):
    id = models.AutoField(primary_key=True, verbose_name="序号")
    role = models.ForeignKey(SysRoleModel, on_delete=models.PROTECT)
    user = models.ForeignKey(SysUserModel, on_delete=models.PROTECT)

    class Meta:
        db_table = 'sys_user_role'


class SysUserRoleSerializer(serializers.ModelSerializer):
    class Meta:
        model = SysUserRoleModel
        fields = '__all__'
  • 在 menu/models.py 文件创建模型及关联表 SysMenuModel、SysMenuSerializer、SysRoleMenuModel、SysRoleMenuSerializer
from django.db import models
from django.utils import timezone
from rest_framework import serializers

from role.models import SysRoleModel


# Create your models here.
class SysMenuModel(models.Model):
    id = models.AutoField(primary_key=True, verbose_name="序号")
    name = models.CharField(max_length=50, unique=True, verbose_name="菜单名称")
    icon = models.CharField(max_length=100, null=True, verbose_name="菜单图标")
    parent_id = models.IntegerField(null=True, verbose_name="父级菜单ID")
    order_num = models.IntegerField(null=True, verbose_name="显示顺序")
    path = models.CharField(max_length=200, null=True, verbose_name="路由地址")
    component = models.CharField(max_length=255, null=True, verbose_name="组件地址")
    menu_type = models.CharField(max_length=1, null=True, verbose_name="菜单类型(M目录 C菜单 F按钮)")
    create_time = models.DateTimeField(verbose_name="创建时间", default=timezone.now())
    update_time = models.DateTimeField(verbose_name="更新时间", default=timezone.now())
    remark = models.CharField(max_length=500, null=True, verbose_name="备注")

    def __lt__(self, other):
        return self.order_num < other.order_num

    class Meta:
        db_table = "sys_menu"


# 序列化类
class SysMenuSerializer(serializers.ModelSerializer):
    children = serializers.SerializerMethodField()

    def get_children(self, obj):
        if hasattr(obj, "children"):
            serializerMenuList: list[SysMenuSerializer2] = list()
            for sysMenu in obj.children:
                serializerMenuList.append(SysMenuSerializer2(sysMenu).data)
            return serializerMenuList

    class Meta:
        model = SysMenuModel
        fields = '__all__'


class SysMenuSerializer2(serializers.ModelSerializer):
    class Meta:
        model = SysMenuModel
        fields = '__all__'


# 系统角色菜单关联类
class SysRoleMenuModel(models.Model):
    id = models.AutoField(primary_key=True, verbose_name="序号")
    role = models.ForeignKey(SysRoleModel, on_delete=models.PROTECT)
    menu = models.ForeignKey(SysMenuModel, on_delete=models.PROTECT)

    class Meta:
        db_table = 'sys_role_menu'


# 序列化类
class SysRoleMenuSerializer(serializers.ModelSerializer):
    class Meta:
        model = SysRoleMenuModel
        fields = '__all__'
  • 执行命令在数据库创建数据表
# 生成迁移文件
python manage.py makemigrations
# 将变更应用到数据库
python manage.py migrate
  • 在登录成功后返回菜单树等信息
import json
import platform
from django.core.paginator import Paginator
from django.http import JsonResponse
from django.utils import timezone
from django.views import View
from rest_framework_jwt.settings import api_settings

from .models import SysUserModel, SysUserSerializer
from role.models import SysRoleModel, SysUserRoleModel

from menu.models import SysMenuModel, SysMenuSerializer

from AdminDashboard import settings


# Create your views here.

class LoginView(View):
    """
    用户登录
    """

    def get(self, request):
        # 获取前端传来的参数
        username = request.GET.get('username')
        password = request.GET.get('password')
        try:
            # 用户对象
            user: SysUserModel = SysUserModel.objects.get(username=username, password=password)
            # print("user =>", type(user))
            # 获取token
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
            # 将用户对象传递过去,获取该对象的属性值
            payload = jwt_payload_handler(user)
            # 将属性值编码成jwt格式字符串
            token = jwt_encode_handler(payload)

            # 获取角色ID与角色名称
            role_queryset = SysRoleModel.objects.raw(
                "SELECT id ,NAME FROM sys_role WHERE id IN (SELECT role_id FROM sys_user_role WHERE user_id=" + str(
                    user.id) + ")")
            # print("role_queryset =>", role_queryset)
            # 获取当前用户所有的角色,逗号隔开
            roles = ",".join([role.name for role in role_queryset])

            # 定义菜单集合,用于去除重复数据
            menuSet: set[SysMenuModel] = set()
            for role in role_queryset:
                # print(role.id, role.name)
                # 根据角色ID查询对应的菜单信息
                menu_queryset = SysMenuModel.objects.raw(
                    "SELECT * FROM sys_menu WHERE id IN (SELECT menu_id FROM sys_role_menu WHERE role_id=" + str(
                        role.id) + ")")
                for menu in menu_queryset:
                    menuSet.add(menu)

            # 将set转换为list
            menuList: list[SysMenuModel] = list(menuSet)
            # 根据 order_num 排序,排序规则来源于 menu/models.py 中重写的 __lt__(self, other) 方法
            menuList_sort = sorted(menuList)
            # 构造菜单树
            sysMenuList: list[SysMenuModel] = self.buildTreeMenu(menuList_sort)

            # 将构建的菜单树进行json序列化处理
            serializerMenuList = list()
            for sysMenu in sysMenuList:
                serializerMenuList.append(SysMenuSerializer(sysMenu).data)
            # print("serializerMenuList =>", serializerMenuList)

            # 登录成功,更新登录时间
            user.login_date = timezone.now()
            # 保存用户信息
            user.save()

        except Exception as e:
            print(e)
            return JsonResponse({
                'code': 500,
                'msg': '用户名或密码错误'
            })
        return JsonResponse({
            'code': 200,
            'msg': '登录成功',
            'token': token,
            'data': {
                "user": SysUserSerializer(user).data,
                'roles': roles,
                "menuList": serializerMenuList
            }
        })

    # 构建菜单树
    def buildTreeMenu(self, sysMenuList):
        resultMenuList: list[SysMenuModel] = list()
        for menu in sysMenuList:
            # 寻找子节点
            for e in sysMenuList:
                if e.parent_id == menu.id:
                    if not hasattr(menu, "children"):
                        menu.children = list()
                    menu.children.append(e)
            # 判断父节点,添加到集合
            if menu.parent_id == 0:
                resultMenuList.append(menu)
        return resultMenuList

3、配置访问媒体资源

  • 在项目下新建一个 media 的媒体目录,然后在 media 目录下在创建 userAvatar 目录用于存放头像图片文件
  • 然后在 settings.py 中配置 media 路径
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/

STATIC_URL = 'static/'

MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = 'media/'
  • 在项目根 urls.py 中添加 media 映射
from django.contrib import admin
from django.urls import path, include, re_path
from django.views.static import serve

from . import settings

# 作为最后一级时,路径结尾最好别加“/”,POST请求时会报错,如果是GET请求且拼接有参数,最好加“/”
urlpatterns = [
    # path('admin/', admin.site.urls),
    path('user/', include('user.urls'), name="user"),  # 用户模块
    # path('role/', include('role.urls'), name="role"), # 角色模块
    # path('menu/', include('menu.urls'), name="menu"), # 权限模块
    # 配置媒体文件的路由地址
    re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}, name='media')
]

4、基本资料修改实现

  • 在 user/views.py 文件
class SaveView(View):
    """
    添加用户或修改用户信息
    """

    def post(self, request):
        data_dict = json.loads(request.body.decode('utf-8'))
        # print("data_dict =>", data_dict)
        if data_dict['id'] == -1 or data_dict['id'] == "" or data_dict['id'] is None:
            sysUser_obj = SysUserModel(username=data_dict['username'], password=data_dict['password'],
                                       email=data_dict['email'], phonenumber=data_dict['phonenumber'],
                                       status=data_dict['status'], remark=data_dict['remark'])
            # 使用 timezone.now() 获取带时区的当前时间
            sysUser_obj.create_time = timezone.now()  # 输出: 2025-01-16 12:34:56.789012+00:00
            sysUser_obj.avatar = 'default.jpg'
            sysUser_obj.password = "123456"
            # 保存用户信息
            sysUser_obj.save()

            return JsonResponse({
                'code': 200,
                'msg': '添加成功!'
            })
        else:
            sysUser_obj = SysUserModel(id=data_dict['id'], username=data_dict['username'],
                                       password=data_dict['password'],
                                       avatar=data_dict['avatar'], email=data_dict['email'],
                                       phonenumber=data_dict['phonenumber'],
                                       login_date=data_dict['login_date'], status=data_dict['status'],
                                       create_time=data_dict['create_time'],
                                       update_time=data_dict['update_time'], remark=data_dict['remark'])
            # 使用 timezone.now() 获取带时区的当前时间
            sysUser_obj.update_time = timezone.now()  # 输出: 2025-01-16 12:34:56.789012+00:00
            # 保存用户信息
            sysUser_obj.save()

            return JsonResponse({
                'code': 200,
                'msg': '修改成功!'
            })


class UpdatePwdView(View):
    """
    修改密码
    """

    def post(self, request):
        data_dict = json.loads(request.body.decode("utf-8"))
        id = data_dict['id']
        oldPassword = data_dict['oldPassword']
        newPassword = data_dict['newPassword']
        # 用户对象
        user: SysUserModel = SysUserModel.objects.get(id=id)
        if user.password == oldPassword:
            user.password = newPassword
            # 使用 timezone.now() 获取带时区的当前时间
            user.update_time = timezone.now()  # 输出: 2025-01-16 12:34:56.789012+00:00
            user.save()
            return JsonResponse({
                'code': 200,
                'msg': '更新成功!'
            })
        else:
            return JsonResponse({
                'code': 500,
                'msg': '原密码错误!'
            })


class UploadImageView(View):
    """
    图片上传
    """

    def post(self, request):
        # 获取二进制文件
        image_file = request.FILES.get('avatar')
        # print("image_file =>", image_file)
        if image_file:
            # 获取原文件名
            file_name = image_file.name
            # 获取文件后缀名
            suffixName = file_name[file_name.rfind("."):]
            # 获取当前时间(年月日时分秒)+ 文件后缀名 组成的新文件名
            new_file_name = timezone.now().strftime('%Y%m%d%H%M%S') + suffixName
            # 获取当前系统信息
            platform_sys = platform.system()
            # print("platform_sys =>", platform_sys)
            if platform_sys == "Windows":
                # 获取文件保存位置
                file_path = str(settings.MEDIA_ROOT) + "\\userAvatar\\" + new_file_name
            else:
                file_path = str(settings.MEDIA_ROOT) + "/userAvatar/" + new_file_name
            # print("file_path =>", file_path)
            # 将文件保存
            try:
                with open(file_path, 'wb') as f:
                    for chunk in image_file.chunks():
                        f.write(chunk)
                return JsonResponse({
                    'code': 200,
                    'msg': '上传成功!',
                    'data': {
                        'fileName': new_file_name
                    }
                })
            except:
                return JsonResponse({
                    'code': 500,
                    'msg': '上传头像失败!'
                })
        else:
            return JsonResponse({
                'code': 500,
                'msg': '文件不存在!'
            })


class UpdateImageView(View):
    """
    图片保存
    """

    def post(self, request):
        data = json.loads(request.body.decode("utf-8"))
        id = data['id']
        avatar = data['avatar']
        try:
            user_dict = SysUserModel.objects.get(id=id)
            user_dict.avatar = avatar
            user_dict.save()
            return JsonResponse({
                'code': 200,
                'msg': '头像更换成功!'
            })
        except Exception as e:
            return JsonResponse({'code': 500, 'msg': str(e)})
  • 在 user/urls.py 文件
from django.urls import path

from .views import LoginView, SaveView, UpdatePwdView, UploadImageView, UpdateImageView

# 作为最后一级时,路径结尾最好别加“/”,POST请求时会报错,如果是GET请求且拼接有参数,最好加“/”
urlpatterns = [
    path('login', LoginView.as_view(), name='login'),
    path('save', SaveView.as_view(), name='save'),
    path('updateUserPwd', UpdatePwdView.as_view(), name='updateUserPwd'),
    path('uploadImage', UploadImageView.as_view(), name='upload_image'),
    path('updateImage', UpdateImageView.as_view(), name='updateImage'),
]

5、用户管理实现

  • 在 user/views.py 文件
class SearchUserView(View):
    """
    用户信息分页查询
    """

    def post(self, request):
        data_dict = json.loads(request.body.decode('utf-8'))
        # print("data_dict =>", data_dict)
        page_num = data_dict.get('pageNum', 1)  # 当前页,默认为 1
        page_size = data_dict.get('pageSize', 10)  # 每页大小,默认为 10
        query = data_dict.get('query', '')  # 搜索的内容
        # print("userListPage =>", page_num, page_size)
        # 根据分页信息查询数据
        userListPage = Paginator(SysUserModel.objects.filter(username__icontains=query), page_size).page(page_num)
        # 将数据转为字典类型
        user_dict = userListPage.object_list.values()
        # 将外层的字典转为列表
        # userList = list(user_dict)
        # 将时间字段从 UTC 转换为本地时间
        userList = []
        for user in user_dict:
            user = dict(user)  # 将 QueryDict 转换为普通字典
            # 转换时间字段
            for field in ['login_date', 'create_time', 'update_time']:
                if user[field]:  # 检查字段是否存在且不为空
                    user[field] = timezone.localtime(user[field]).strftime("%Y-%m-%d %H:%M:%S")
            userList.append(user)
        # 获取总条数
        total = SysUserModel.objects.filter(username__icontains=query).count()

        for user in userList:
            userId = user['id']
            # 根据用户ID查询拥有的角色
            roleList = SysRoleModel.objects.raw(
                "SELECT id,NAME FROM sys_role WHERE id IN (SELECT role_id FROM sys_user_role WHERE user_id=" + str(
                    userId) + ")")
            roleListDict = []
            for role in roleList:
                roleDict = {}
                roleDict["id"] = role.id
                roleDict["name"] = role.name
                roleListDict.append(roleDict)
            user['roleList'] = roleListDict

        return JsonResponse({
            'code': 200,
            'msg': '用户信息查询成功!',
            'data': {
                'pageNum': page_num,
                'pageSize': page_size,
                'total': total,
                'userList': userList
            }
        })


class CheckUserView(View):
    """
    检查用户名是否重复
    """

    def post(self, request):
        data = json.loads(request.body.decode("utf-8"))
        username = data['username']
        # print("username=", username)
        if SysUserModel.objects.filter(username=username).exists():
            return JsonResponse({'code': 500, 'msg': '该用户名已存在!'})
        else:
            return JsonResponse({'code': 200, 'msg': 'Success'})


class ActionView(View):
    """
    操作
    """

    def get(self, request):
        """
        根据id获取用户信息
        :param request:
        :return:
        """
        id = request.GET.get("id")
        user_dict = SysUserModel.objects.get(id=id)
        return JsonResponse({
            'code': 200,
            'msg': '成功!',
            'data': {
                'role': SysRoleSerializer(role_dict).data
            },
        })

    def delete(self, request):
        """
        删除操作
        :param request:
        :return:
        """
        userIdList = json.loads(request.body.decode("utf-8"))
        try:
            SysUserRoleModel.objects.filter(user_id__in=userIdList).delete()
            SysUserModel.objects.filter(id__in=userIdList).delete()
            return JsonResponse({'code': 200, 'msg': '删除成功!'})
        except:
            return JsonResponse({'code': 500, 'msg': '删除失败!'})


class ResetPasswordView(View):
    """
    重置密码
    """

    def get(self, request):
        id = request.GET.get("id")
        try:
            user_dict = SysUserModel.objects.get(id=id)
            user_dict.password = "123456"
            user_dict.update_time = timezone.now()
            user_dict.save()
            return JsonResponse({'code': 200, 'msg': '密码重置成功!'})
        except:
            return JsonResponse({'code': 500, 'msg': '密码重置失败!'})


class GrantRoleView(View):
    """
    用户角色授权
    """

    def post(self, request):
        data = json.loads(request.body.decode("utf-8"))
        user_id = data['id']
        roleIdList = data['roleIds']
        # 先删除用户角色关联表中该用户对应的所有角色数据
        SysUserRoleModel.objects.filter(user_id=user_id).delete()
        # 然后在遍历将复选框选中的角色保存到用户角色关联表中
        for roleId in roleIdList:
            userRole = SysUserRoleModel(user_id=user_id, role_id=roleId)
            userRole.save()
        return JsonResponse({'code': 200, 'msg': '用户角色授权成功!'})


class UpdateStatusView(View):
    """
    用户状态修改
    """

    def post(self, request):
        data = json.loads(request.body.decode("utf-8"))
        id = data['id']
        status = data['status']
        try:
            user_dict = SysUserModel.objects.get(id=id)
            user_dict.status = status
            user_dict.save()
            return JsonResponse({'code': 200, 'msg': '状态修改成功!'})
        except:
            return JsonResponse({'code': 500, 'msg': '状态修改失败!'})
  • 在 user/urls.py 文件
from django.urls import path

from .views import LoginView, SaveView, UpdatePwdView, UploadImageView, UpdateImageView, SearchUserView, CheckUserView, \
    ActionView, ResetPasswordView, GrantRoleView, UpdateStatusView

# 作为最后一级时,路径结尾最好别加“/”,POST请求时会报错,如果是GET请求且拼接有参数,最好加“/”
urlpatterns = [
    path('login/', LoginView.as_view(), name='login'),
    path('save', SaveView.as_view(), name='save'),
    path('updateUserPwd', UpdatePwdView.as_view(), name='updateUserPwd'),
    path('uploadImage', UploadImageView.as_view(), name='upload_image'),
    path('updateImage', UpdateImageView.as_view(), name='updateImage'),
    path('search', SearchUserView.as_view(), name='search'),
    path('check', CheckUserView.as_view(), name='check'),
    path('action', ActionView.as_view(), name='action'),
    path('resetPassword', ResetPasswordView.as_view(), name='resetPassword'),
    path('grantRole', GrantRoleView.as_view(), name='grantRole'),
    path('updateStatus', UpdateStatusView.as_view(), name='updateStatus'),
]
  • 在 role/views.py 文件
from django.http import JsonResponse
from django.views import View

from role.models import SysRoleModel


# Create your views here.
class SearchRoleView(View):
    """
    查询所有角色信息
    """

    def get(self, request):
        # 查询所有角色信息并转为字典类型
        role_dict = SysRoleModel.objects.all().values()
        # 将字典类型转为列表类型
        roleList = list(role_dict)

        return JsonResponse({'code': 200, 'msg': '查询成功!', 'data': {'roleList': roleList}})
  • 在 user/urls.py 文件
from django.urls import path

from .views import SearchRoleView

# 作为最后一级时,路径结尾最好别加“/”,POST请求时会报错,如果是GET请求且拼接有参数,最好加“/”
urlpatterns = [
    path('search', SearchRoleView.as_view(), name='search'),
]

6、角色管理实现

  • 在 role/views.py 文件
  • 在 role/urls.py 文件