从零搭建接口测试框架(二)——Django接口开发

二. Django接口开发

在这一小节,我们将编写一些接口服务,事实上这一部分并不计入框架项目范围内,只作为测试使用,可以选择跳过不看。直接下载gitbub上的代码就能进行测试,项目包中将这部分代码放置在 IntTestDemo/extra 目录下,以示区分。
为了控制篇幅,接口文档放置在下一小节。

1. 规划

Project名称:extra
App名称:super_tble
功能简介:设计一张表格,用于记录影视剧中超能人物的基本信息(共4项:序号、姓名、联系方式、住址),实现增添人物信息,查询人物信息功能。

id(序号) name(姓名) tel(联系方式) address(地址)
1 灭霸 666666 漫威宇宙
2 星爵 233333 漫威宇宙
...... ...... ....... ......

接口简介:

接口 功能 加密验证
/super_table/add 增添人物信息 AES
/super_table/search_by_name 查询人物信息 MD5签名

2. 创建Django项目

  • 安装Django
    一般 linux 上都自带有 python ,可以用 pip 命令安装 Django,如下:
    >>> pip install Django
  • 创建项目
    首先在终端cd到我们上一小节克隆的 IntTestDemo 文件夹中,ls查看当前文件夹下只有一个 README.md 文件,这是我们上一小节git clone操作带来的。现在我们输入如下命令指示Django创建新项目:
    >>> django-admin startproject extra
    成功后ls查看当前目录下多出一个 extra 文件夹,这就是我们刚刚新建的 project 啦。事实上这条命令还新建了一些文件,可以使用tree extra命令查看extra的目录结构:
extra/    # 根目录只是项目的容器。它的名字对Django来说无关紧要;你可以将它重命名为你喜欢的任何名字
├── extra    # extra项目的实际Python包
│   ├── __init__.py    # 一个空文件,告诉Python该目录应该被视为Python包
│   ├── settings.py    # extra项目的设置/配置
│   ├── urls.py    # 用于配置url
│   └── wsgi.py    # 兼容WSGI的Web服务器的入口,为您的项目提供服务
└── manage.py    # 一个命令行实用程序,允许您以各种方式与此Django项目进行交互

1 directory, 5 files
  • 创建App
    一个项目中可以创建多个应用(App),根据我们的需求,创建一个名为super_table 的应用:
>>> cd extra    # 进入extra项目
>>> django-admin startapp super_table    # 执行startapp命令,创建super_table
  • 访问我们的应用
    经过上一步骤后,可以看到当前目录下多出了一个名为 super_table 的文件夹,说明App创建成功。
    现在,我们可以尝试运行服务器:
>>> python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).

You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

April 19, 2019 - 06:15:03
Django version 2.1.7, using settings 'extra.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

复制输出的地址信息(我这里是:http://127.0.0.1:8000/)粘贴到浏览器中就可以访问啦:

成功创建Django项目.png

  • 将App注册到项目中
    编辑 extra/extra/settings.py ,把我们刚才创建的App注册到项目中:
 32 ......
 33 INSTALLED_APPS = [
 34     'django.contrib.admin',
 35     'django.contrib.auth',
 36     'django.contrib.contenttypes',
 37     'django.contrib.sessions',
 38     'django.contrib.messages',
 39     'django.contrib.staticfiles',
 40     'super_table',    # 我们刚刚创建的App
 41 ]
 42 ......
  • 推送代码到github
    嗯,到这里咱们把代码推送一下:
>>> cd ../    # 进入IntTestDemo目录
>>> git add .    # 将项目改动放入暂存区
>>> git commit -m "New a Django App called super_table."    # 将暂存区的的修改提交到当前分支,m参数表示添加注释
>>> git push origin master    # 推送到远程服务器(github)

3. 数据模型(Model)

  • 建立数据表
    cdsuper_table 目录下,编辑 models.py 文件:
  1 from django.db import models
  2 
  3 # Create your models here.
  4 # super_table表
  5 class Super_Table(models.Model):
  6     id = models.AutoField(primary_key=True)      # 序号,自增
  7     name = models.CharField(max_length=64)      # 姓名
  8     tel = models.CharField(max_length=16)       # 联系方式
  9     address = models.CharField(max_length=200)      # 地址
  • 执行数据库迁移
    编辑完成后执行数据库迁移操作:
>>> cd ../    # 回到上级目录,即extra目录
>>> python manage.py makemigrations super_table    # 基于当前的model创建新的迁移策略文件
Migrations for 'super_table':
  super_table/migrations/0001_initial.py
    - Create model Super_Table
>>> python manage.py migrate    # 执行数据库迁移
  • 数据库管理
    我们启用Django自带的 Admin工具 来管理数据库,
    首先创建admin后台管理员帐号:
>>> python manage.py createsuperuser
Username (leave blank to use 'arch'): admin    # 这里创建帐号
Email address: [email protected]    # 这里创建邮箱
Password:     # 这里创建密码
Password (again):     # 再次确认密码
The password is too similar to the email address.
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

我填写的帐号和密码都是:admin,导致执行期间出现提示信息:密码信息与email地址太相似、密码至少需要8个字符等。这个时候要强势点哈,输入y回车就可以啦。
想要用 admin 来管理我们的数据表的话,还要再编辑一下 super_table/admin.py 文件:

  1 from django.contrib import admin
  2 from super_table.models import Super_Table      # 引入Super_Table
  3 
  4 # Register your models here.
  5 class STAdmin(admin.ModelAdmin):
  6     list_display = ['id', 'name', 'tel', 'address']     # 定制Admin显示栏目
  7     search_fields = ['name']        # 启用Admin搜索栏
  8 
  9 admin.site.register(Super_Table, STAdmin)       # 完成注册

完成这一步后访问:http://127.0.0.1:8000/admin/ ,输入刚才创建的帐号密码进入admin后台管理,通过这个页面就可以直接操作 Super_Table 表的增删查改了:

Admin后台管理数据表.png

4. 开发接口

  • 配置url路径
    如果一个项目中创建有多个App,可以在项目的url配置中 包含(include) App的url配置。首先来到 extra 目录下,编辑 urls.py 文件,使其包含super_table应用的url配置:
 16  from django.contrib import admin
 17 from django.urls import path
 18 from django.urls import include    # 引入新的包
 19 
 20 urlpatterns = [
 21     path('admin/', admin.site.urls),
 22     path('super_table/', include(('super_table.urls', 'super_table'),namespace='super_table')),    # 包含super_table中的url配置
 23 ]

接下来,进入 super_table 目录,创建 urls.py 文件并编辑:

  1 from django.conf.urls import url
  2 from super_table import views
  3 from django.urls import path
  4 
  5 
  6 urlpatterns = [
  7     # super_table interface:
  8     path('add/', views.add, name='add'),    # 添加人物接口路径
  9     path('search_by_name/', views.search_by_name, name='search_by_name'),    # 查询人物接口路径
 10 ]
  • 接口加密验证
    我们需要用到 PyCrypto 来对接口进行AES加密,这是一个密码库,它提供安全的哈希函数和各种加密算法。用pip安装:
    >>> pip install pycrypto
    我们的接口涉及两种加密形式:

    接口 功能 加密验证
    /super_table/add 增添人物信息 AES
    /super_table/search_by_name 查询人物信息 MD5签名

    在这里将本项目中涉及的两种加密方式进行简单叙述:

    加密方式 客户端操作 服务端操作
    AES 一. 定义16位的key(密匙)和iv(初始化向量),用于AES加密。

    二. 将待加密的数据进行填充补位,使其长度变为16的倍数,否则加密过程会报错。

    三. 将填充厚的数据进行AES加密,为了便于传输再用base64模块处理一次。

    四. 向服务端发送请求,传送加密数据。
    一. 定义与客户端相同的key和iv,用于AES解密。

    二.接受客户端传输, 用base64反向解密,接着AES反向解密,最后消除填充补位。
    MD5签名 一. 定义一个key(密匙),取得当前客户端时间戳。

    二. 将key与时间戳拼接成字符串,将字符串进行MD5加密处理生成加密字符串;

    三. 将加密字符传与时间戳拼接,拼接后的字符串作为签名信息发送给服务端进行验证。
    一. 定义与客户端相同的key(密匙)。

    二. 接受客户端传输的签名,分离出客户端时间戳并与服务端时间比较,如果相差超过60秒,宣布超时,验证失败。

    三. 如果未超时,将服务端key与客户端时间戳拼接并生成MD5加密字符串,比较该加密字符串与客户端传来的加密字符串是否相符,相符则验证成功,否则验证失败。
  • 编写视图函数
    我们将要编辑的是super_table目录下的views.py文件,即:extra/super_table/views.py,如下:

  1 import time
  2 import json
  3 import hashlib
  4 import base64
  5 from django.http import JsonResponse
  6 from django.core.exceptions import ObjectDoesNotExist
  7 from Crypto.Cipher import AES    # 用于加密解密
  8 from super_table.models import Super_Table
  9 
 10 # 自定义的装饰器,用于MD5解密
 11 def md5_sign(func):
 12     def wrapper(request, *args, **kwargs):
 13         #获取客户端传来的验证信息,格式为:(客户端MD5签名)|(客户端时间戳)
 14         auth = request.GET.get('md5_sign', '')
 15         # 验证信息不能为空
 16         if auth == '':
 17             return JsonResponse({'status': 401, 'message': 'sign is empty'})
 18         client_md5 = auth.split('|', maxsplit=1)[0]     # 客户端传送的MD5签名
 19         client_time = auth.split('|', maxsplit=1)[1]        # 客户端时间戳
 20 
 21         now_time = time.time()      # 获取当前时间
 22         server_time = str(now_time).split('.')[0]  # 截取小数点前的时间,转化为字符串
 23 
 24         # 如果客户端请求时间与服务端手里时间超过60秒,需重新申请验证
 25         if int(server_time) - int(client_time) > 60:
 26             return JsonResponse({'status': 408, 'message': 'timeout, please try           again'})
 27 
 28         # 利用客户端传送的时间戳,重新计算MD5签名,生成服务端MD5签名 
 29         hash = hashlib.md5()
 30         sign = client_time + "wahaha"       # 签名密匙为:wahaha
 31         sign_utf8 = sign.encode(encoding='utf-8')
 32         hash.update(sign_utf8)
 33         server_md5 = hash.hexdigest()
 34 
 35         # 验证客户端MD5签名与服务端MD5签名是否相符
 36         if server_md5 != client_md5:
 37             return JsonResponse({'status': 401, 'message': 'sign check fail'})
 38         else:
 39             return func(request, *args, **kwargs)
 40 
 41     return wrapper
 42 
 43 
 44 # Create your views here.
 45 def add(request):
 46     '''
 47     接收POST请求,
 48     添加人物信息
 49     '''
 50     key = "qwertyuiopasdfgh"  # 密匙
 51     iv = b"1234567890123456"    #初始化向量
 52 
 53     # 只接受POST请求
 54     if request.method == 'POST':
 55         data = request.POST.get("data", "")
 56     else:
 57         return JsonResponse({'status': 405, 'message': 'method not allowed'})
 58 
 59 #*********************** AES解密开始 ***********************************#
 60     data = base64.urlsafe_b64decode(data)      # 反向客户端base64.                        urlsafe_b64encode()操作
 61     aes = AES.new(key, AES.MODE_CBC, iv)    # 初始化加密器
 62     data = aes.decrypt(data).decode()    # 逆向解密
 63     unpad = lambda s: s[0: - ord(s[-1])]      # 消除补位填充,填充模式:PKCS#5/PKCS7
 64     data = json.loads(unpad(data))    # 转换为字典型数据
 65 #*********************** AES解密完成 ***********************************# 
 66 
 67     name = data.get('name', '')     # 人物姓名
 68     tel = data.get('tel', '')     # 人物联系方式
 69     address = data.get('address', '')     # 人物地址
 70 
 71     # 如果name、tel、address中某项为空,则返回“10010,参数错误”
 72     if name == '' or tel == '' or address == '':
 73         return JsonResponse({'status': 400, 'message': 'parameter error'})
 74 
 75     # 如果name已经存在于数据表中,返回“10020,名字已经存在”
 76     result = Super_Table.objects.filter(name=name)
 77     if result:
 78         return JsonResponse({'status': 400, 'message': 'name already exists'})
 79 
 80     # 添加成功
 81     Super_Table.objects.create(name=name, tel=int(tel), address=address)
 82     return JsonResponse({'status': 200, 'message': 'add success'})
 83 
 84 
 85 @md5_sign    # 自定义的装饰器,用于MD5解密
 86 def search_by_name(request):
 87     '''
 88     接收GET请求,
 89     查询人物信息
 90     '''
 91     name = request.GET.get('name', '')      # 想要查询的名字
 92 
 93     # 查询栏不能为空
 94     if name == '':
 95         return JsonResponse({'status': 400, 'message': 'please input a name'})
 96     else:
 97         info = {}       # 查询成功时装载人物信息
 98 
 99         # 查询失败,姓名不存在,拋出异常
100         try:
101             result = Super_Table.objects.get(name=name)
102         except ObjectDoesNotExist:
103             return JsonResponse({'status': 204, 'message': 'result is empty!'})
104 
105         # 查询成功,装载信息到info,返回
106         else:
107             info['name'] = result.name
108             info['tel'] = result.tel
109             info['address'] = result.address
110             return JsonResponse({'status': 200, 'message': 'success', 'data': info})

  • 关闭Dajngo的CSRF验证
    为了方便后面的测试,我们把项目的CSRF验证功能关闭,否则很容易在调用接口时报错,编辑 extra/extra/settings.py 文件,注释掉 MIDDLEWARE 中的一行,如下:
 43 MIDDLEWARE = [
 44     'django.middleware.security.SecurityMiddleware',
 45     'django.contrib.sessions.middleware.SessionMiddleware',
 46     'django.middleware.common.CommonMiddleware',
 47    # 'django.middleware.csrf.CsrfViewMiddleware',
 48     'django.contrib.auth.middleware.AuthenticationMiddleware',
 49     'django.contrib.messages.middleware.MessageMiddleware',
 50     'django.middleware.clickjacking.XFrameOptionsMiddleware',
 51 ]
  • 别忘记推送代码
>>> cd ../    # 进入IntTestDemo目录
>>> git add .    # 将项目改动放入暂存区
>>> git commit -m "super_table completed"    # 将暂存区的的修改提交到当前分支,m参数表示添加注释
>>> git push origin master    # 推送到远程服务器(github)

你可能感兴趣的:(从零搭建接口测试框架(二)——Django接口开发)