手把手教你撸一个接口自动化测试平台(二)

  • 一、用户登录功能

准备:

pip3 install djangorestframework
pip3 install djangorestframework-simplejwt

1、进入setting.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api_autotest_app',  #注册app
    'rest_framework.authtoken', #添加token模块
    'rest_framework',  #注册rest_framework
]



# REST_FRAMEWORK、simplejwt相关配置
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated', # 使用JWT进行权限验证
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',  # 通过 JWT 进行用户授权,验证过程需要访问数据库
        'rest_framework_simplejwt.authentication.JWTTokenUserAuthentication', # 通过 JWT 的 Token 进行用户验证,验证过程不需要访问数据库
    ),
    'DEFAULT_PAGINATION_CLASS': (
        'rest_framework.pagination.PageNumberPagination',
    ),
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',  # 指定支持coreapi的Schema
}


AUTH_USER_MODEL = 'api_autotest_app.mUser'

# simplejwt 配置
JWT_SIGNING_KEY = '123456'
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(days=15),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=15),
    'ROTATE_REFRESH_TOKENS': False,
    'BLACKLIST_AFTER_ROTATION': True,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': JWT_SIGNING_KEY,
    'VERIFYING_KEY': None,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(days=15),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=15),
}

2、重写User模块

models.py

from django.db import models
import hashlib

from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _

# Create your models here.

class mUser(AbstractUser):
    """
    自定义的用户模块
    """

    # 微信同步的用户信息
    openid = models.CharField(
        verbose_name=_('钉钉OpenID'), help_text=_('钉钉OpenID'), max_length=100, unique=True, null=True, blank=True)
    avatar_url = models.URLField(
        verbose_name=_('头像'), help_text=_('头像'), null=True, blank=True)
    nick_name = models.CharField(
        verbose_name=_('昵称'), help_text=_('昵称'), max_length=100, null=True, blank=True, unique=True)
    phone = models.CharField(
        verbose_name=_('手机号'), help_text=_('手机号'), max_length=100, null=True, blank=True)

    def create_username_password(self):
        '''
        自动通过openid创建用户名 username 和密码 password。
        '''
        if not self.username and not self.password and self.openid:
            key = settings.SECRET_KEY
            self.username = hashlib.pbkdf2_hmac(
                "sha256", getattr(self, 'openid').encode(encoding='utf-8'), key.encode(encoding='utf-8'), 10).hex()

    def save(self, *args, **kwargs):
        self.create_username_password()
        super().save(*args, **kwargs)

    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_dMODEL'

3、实现用户登录功能

(1)、使用rest_framework模块的serializers序列化

api_autotest_app目录下新建serializers.py

from rest_framework import serializers
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from .models import *

class JfwTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super(JfwTokenObtainPairSerializer, cls).get_token(user)
        token['username'] = user.username
        return token

(2)、重写rest_framework 接口返回格式

api_autotest_app->commom目录下新建api_response.py

from rest_framework.response import Response
from rest_framework.serializers import Serializer


class JsonResponse(Response):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """

    def __init__(self, data=None, code=None, msg=None,
                 status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None):
        """
        Alters the init arguments slightly.
        For example, drop 'template_name', and instead use 'data'.

        Setting 'renderer' and 'media_type' will typically be deferred,
        For example being set automatically by the `APIView`.
        """
        super().__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)

        self.data = {"code": code, "msg": msg, "data": data}
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in headers.items():
                self[name] = value

(3)、实现login接口

api_autotest_app->api目录下新建login.py

from rest_framework.views import APIView
from api_autotest_app.serializers import JfwTokenObtainPairSerializer
from api_autotest_app.common.api_response import JsonResponse
from django.contrib.auth import authenticate
class Login(APIView):
    """
    post:
    管理登录接口

    """
    authentication_classes = []
    permission_classes = []

    def post(self, request):

        username = request.data.get('username')
        password = request.data.get('password')

        if not username or not password:
            return JsonResponse(code=999995, msg="参数有误")

        user = authenticate(username=username, password=password)
        if user and user.is_active and user.is_superuser:
            token = JfwTokenObtainPairSerializer.get_token(user).access_token
            data = {
                'access_token': str(token)
            }

            return JsonResponse(data=data, code=0, msg="成功!")
        else:
            return JsonResponse(code=99995, msg="账号或密码错误!")

(4)、配置路由

api_autotest_app目录新建urls.py

from django.conf.urls import url
from api_autotest_app.api import login


urlpatterns = [
    url(r'^user/login$', login.Login.as_view()),
]

api_autotest_admin->urls.py

"""api_autotest_admin URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from django.urls import path, include
from api_autotest_app import urls
urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^api/', include(urls)),
]

创建管理员:

python3 manage.py createsuperuser

启动项目:

python3 manage.py runserver 8005

(5)、实现登录页面
克隆项目
git clone https://github.com/PanJiaChen/vue-admin-template.git

进入根目录:

npm install

基础依赖包添加成功后执行

npm run dev

启动成功后可以看到进入登录页面

image.png
  • 对接登录功能

修改文件将mock改为我们自己的后端接口


image.png

配置代理:

image.png

修改接口:
src -> api->user.js

import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/api/user/login',
    method: 'post',
    data
  })
}

export function getInfo(token) {
  return request({
    url: '/api/internal/admin/getUser',
    method: 'get',
    params: { }
  })
}

src->utils->request.js

response => {
  const res = response.data

// if the custom code is not 20000, it is judged as an error.
if (res.code !== 0) {
  Message({
    message: res.msg || 'Error',
    type: 'error',
    duration: 5 * 1000
  })

登录页面

src->views->login-index.vue 去掉一些不要的东西








使用管理员账号登录成功后进入首页

image.png
  • 实现项目模块
image.png

编写后台接口

1、创建project表 model.py

class Project(models.Model):
    """
    项目表
    """
    ProjectType = (
        ('prd', '生产'),
        ('test', '测试')
    )
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=50, verbose_name='项目名称')
    version = models.CharField(max_length=50, verbose_name='版本')
    type = models.CharField(max_length=50, verbose_name='类型', choices=ProjectType)
    description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
    status = models.BooleanField(default=True, verbose_name='状态')
    LastUpdateTime = models.DateTimeField(auto_now=True, verbose_name='最近修改时间')
    createTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    user = models.ForeignKey(mUser, on_delete=models.SET_NULL, null=True, max_length=1024, verbose_name='创建人')

    def __unicode__(self):
        return self.name

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '项目'
        verbose_name_plural = '项目'

2、序列化信息

class ProjectDeserializer(serializers.ModelSerializer):
    """
    项目信息反序列化
    """
    class Meta:
        model = Project
        fields = "__all__"

class ProjectSerializer(serializers.ModelSerializer):
    """
    项目信息序列化
    """
    LastUpdateTime = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True)
    createTime = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True)
    user = serializers.CharField(source='user.username')

    class Meta:
        model = Project
        fields = "__all__"

3、接口实现

class ProjectList(APIView):

    def get(self, request):
        """
        获取项目列表
        :param request:
        :return:
        """
        try:
            page_size = int(request.GET.get("page_size", 20)) #每页20条
            page = int(request.GET.get("page", 1))  # 默认从第一页开始
        except (TypeError, ValueError):
            return JsonResponse(code=999995, msg="page字段和page_size字段 必须为整数!")
        param = {}
        name = request.GET.get("name")
        status = request.GET.get("status")

        if name:
            param['name'] = name
        if status:
            param['status'] = True if status=="true" else False
        if param:
            obi = Project.objects.filter(**param).order_by("id")
        else:
            obi = Project.objects.all().order_by("id")
        paginator = Paginator(obi, page_size)
        total = paginator.num_pages
        try:
            obm = paginator.page(page)
        except PageNotAnInteger:
            obm = paginator.page(1)
        except EmptyPage:
            obm = []
        serialize = ProjectSerializer(obm, many=True)
        return JsonResponse(data={"data": serialize.data,
                                  "page": page,
                                  "total": total
                                  }, code=Code.SUCCESS, msg="成功")

class AddProject(APIView):

    def parameter_check(self, data):
        """
        验证参数
        :param data:
        :return:
        """
        try:
            # 必传参数 name, version, type
            if not data["name"] or not data["version"] or not data["type"]:
                return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")

            if data["type"] not in ["pro", "test"]:
                return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")
        except KeyError:
            return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")


    def post(self, request):
        """
        新增项目
        :param request:
        :return:
        """
        data = JSONParser().parse(request)
        result = self.parameter_check(data)
        if result:
            return result
        data["user"] = request.user.pk
        project_serializer = ProjectDeserializer(data=data)
        try:
            Project.objects.get(name=data["name"])
            return JsonResponse(code=Code.DATA_HAS_EXISTED, msg="存在相同名称")
        except ObjectDoesNotExist:
            if project_serializer.is_valid():
                project_serializer.save()
                return JsonResponse(data={
                        "project_id": project_serializer.data.get("id")
                    }, code=Code.SUCCESS, msg="成功")
            else:
                return JsonResponse(code=Code.ERROR, msg="失败")



class UpdateProject(APIView):

    def parameter_check(self, data):
        """
        校验参数
        :param data:
        :return:
        """
        try:
            # 校验project_id类型为int
            if not isinstance(data["id"], int):
                return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")
            # 必传参数 name, version , type
            if not data["name"] or not data["version"] or not data["type"]:
                return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")
            # type 必为pro, test
            if data["type"] not in ["pro", "test"]:
                return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")
        except KeyError:
            return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")

    def post(self, request):
        """
        修改项目
        :param request:
        :return:
        """
        data = JSONParser().parse(request)
        result = self.parameter_check(data)
        if result:
            return result

        del data['user']
        # 查找项目是否存在
        try:
            obj = Project.objects.get(id=data["id"])
            if not request.user.is_superuser and obj.user.is_superuser:
                return JsonResponse(code=Code.USER_NOT_PERMISSION, msg="无操作权限!")
        except ObjectDoesNotExist:
            return JsonResponse(code=Code.DATA_NOT_EXISTED, msg="项目不存在!")
        # 查找是否相同名称的项目
        pro_name = Project.objects.filter(name=data["name"]).exclude(id=data["id"])
        if len(pro_name):
            return JsonResponse(code=Code.DATA_HAS_EXISTED, msg="存在相同名称")
        else:
            serializer = ProjectDeserializer(data=data)
            with transaction.atomic():
                if serializer.is_valid(raise_exception=True):
                    # 修改项目
                    serializer.update(instance=obj, validated_data=data)

                    return JsonResponse(code=Code.SUCCESS, msg="成功")
                else:
                    return JsonResponse(code=Code.ERROR, msg="失败")


class DelProject(APIView):

    def parameter_check(self, data):
        """
        校验参数
        :param data:
        :return:
        """
        try:
            if not isinstance(data["ids"], list):
                return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")
            for i in data["ids"]:
                if not isinstance(i, int):
                    return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")
        except KeyError:
            return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")

    def post(self, request):
        """
        删除项目
        :param request:
        :return:
        """
        data = JSONParser().parse(request)
        result = self.parameter_check(data)
        if result:
            return result
        try:
            for i in data["ids"]:
                try:
                    obj = Project.objects.get(id=i)
                    if not request.user.is_superuser and obj.user.is_superuser:
                        return JsonResponse(code=Code.USER_NOT_PERMISSION, msg=str(obj)+"无操作权限!")
                except ObjectDoesNotExist:
                    return JsonResponse(code=Code.DATA_NOT_EXISTED, msg="项目不存在!")
            for j in data["ids"]:
                obj = Project.objects.filter(id=j)
                obj.delete()
            return JsonResponse(code=Code.SUCCESS, msg="成功")
        except ObjectDoesNotExist:
            return JsonResponse(code=Code.DATA_NOT_EXISTED, msg="项目不存在!")




class DisableProject(APIView):

    def parameter_check(self, data):
        """
        校验参数
        :param data:
        :return:
        """
        try:
            # 校验project_id类型为int
            if not isinstance(data["id"], int):
                return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")
        except KeyError:
            return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")

    def post(self, request):
        """
        禁用项目
        :param request:
        :return:
        """
        data = JSONParser().parse(request)
        result = self.parameter_check(data)
        if result:
            return result
        try:
            obj = Project.objects.get(id=data["id"])
            if not request.user.is_superuser and obj.user.is_superuser:
                return JsonResponse(code=Code.USER_NOT_PERMISSION, msg=str(obj) + "无操作权限!")
            obj.status = False
            obj.save()
            return JsonResponse(code=Code.SUCCESS, msg="成功")
        except ObjectDoesNotExist:
            return JsonResponse(code=Code.DATA_NOT_EXISTED, msg="项目不存在!")


class EnableProject(APIView):
    def parameter_check(self, data):
        """
        校验参数
        :param data:
        :return:
        """
        try:
            if not isinstance(data["id"], int):
                return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")
        except KeyError:
            return JsonResponse(code=Code.PARAM_IS_INVALID, msg="参数有误!")

    def post(self, request):
        """
        启用项目
        :param request:
        :return:
        """
        data = JSONParser().parse(request)
        result = self.parameter_check(data)
        if result:
            return result
        # 查找项目是否存在
        try:
            obj = Project.objects.get(id=data["id"])
            if not request.user.is_superuser and obj.user.is_superuser:
                return JsonResponse(code=Code.USER_NOT_PERMISSION, msg=str(obj) + "无操作权限!")
            obj.status = True
            obj.save()
            return JsonResponse(code=Code.SUCCESS, msg="成功")
        except ObjectDoesNotExist:
            return JsonResponse(code=Code.DATA_NOT_EXISTED, msg="项目不存在!")

4、配置接口路由

  • 实现前端页面

dashoard->index




具体一些配置就不一一说明了

实现项目详情页面

你可能感兴趣的:(手把手教你撸一个接口自动化测试平台(二))