高级软件工程HW2

高级软件工程个人作业HW2

1. 单服务应用

1.1 任务目标

  • 选择合适的编程语言/框架,实现一个简单的REST风格单体Web应用:
    • 功能需求:
      • 学生信息管理,需要含有以下信息片段:
        • 学号;
        • 姓名;
        • 院系;
        • 专业;
      • 实现 4 个API:
        • 添加学生:
          • URL Path:/api/v1/student
          • http method:POST
          • 功能描述:在 RequestBody 中以 json 格式发送学生的具体信息,以向系统中添加学生;
        • 查看学生:
          • URL Path:/api/v1/student
          • http method:GET
          • 功能描述:用 json 格式,返回当前系统中的所有学生信息;
        • 修改学生信息:
          • URL Path:/api/v1/student
          • http method:PUT
          • 功能描述:用 json 格式,修改系统中某位学生的具体信息;
        • 删除学生:
          • URL Path:/api/v1/student
          • http method:DELETE
          • 功能描述:用 json 格式,修删除系统中某位学生;

1.2 功能实现

编程语言:Python

框架选择:Flask

测试工具:Apipost

1.2.1 资料参考

1.2.1.1 python 函数修饰器

参考链接

1.2.1.2 flask 框架

参考链接

1.2.1.3 apipost

参考链接

1.2.2 具体实现

  • 构建 json 类型的数据供以测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
students = [
{
'id' : 20212010106,
'name' : 'WangYingxv',
'department' : 'SCSF',
'major' : 'SF'
},
{
'id' : 20212010107,
'name' : '4396',
'department' : 'SCSF',
'major' : 'CS'
}
]

注:其中 students 的数据类型为:id:intname:stringdepartment:stringmajor:string;

  • 引入 flask 框架:
1
2
from flask import Flask, jsonify, abort, request
app = Flask(__name__)

注:这里是实例化一个 Flask 类型的对象,这里的 __name__“ 确保服务器只会在该脚本被 Python 解释器直接执行的时候才会运行;

  • GET 查看学生:
1
2
3
@app.route('/api/v1/student',methods=['GET'])
def show_information():
return jsonify({'students':students})

注:这里的修饰器 route() 告诉 Flask 什么类型的 URL 能够触发对应的 show_information() 函数,其余的操作就比较简单了,得到请求之后直接使用 jsonify 函数返回一个 json 类型的数据就可以了,测试图示如下:

GET

  • POST 添加学生:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@app.route('/api/v1/student/',methods=['POST'])
def add_students():
information = request.get_json()
print(information)
student = {
'id':request.json['id'],
'name':request.json['name'],
'department':request.json['department'],
'major':request.json['major']
}
for member in students:
if member['id'] == student['id']:
abort(400)
students.append(student)
return jsonify({'students':students}), 201

注:使用 POST 请求时候需要在 Apipost 中传入 json 格式对应的数据,在修饰器被触发之后,需要使用 request 拿到传过来的数据,另外,为了保证每个 student 的学号成为主键,我在函数里面加了一行判断,也即保证学号唯一,如果添加不成功直接抛出 400 错误码,如果添加成功返回数据即可,这里 201 代表状态码,测试图示如下(添加成功图示1,添加失败抛出错误码图示2):

图示1:

POST

图示2:

image-20200928171724079

  • PUT 修改学生信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@app.route('/api/v1/student/',methods=['PUT'])
def change_information():
information = request.get_json()
print(information)
student = {
'id':request.json['id'],
'name':request.json['name'],
'department':request.json['department'],
'major':request.json['major']
}
for member in students:
if member['id'] == student['id']:
member['name'] = student['name']
member['department'] = student['department']
member['major'] = student['major']
return jsonify({'students':student})

注:使用 PUT 请求时候需要在 Apipost 中传入 json 格式对应的数据,在修饰器被触发之后,需要使用 request 拿到传过来的数据,这些操作与POTS 方法基本一致,随后,直接遍历已经存储的学生信息即可,使用主键 id 找到对应信息并更改即可,如果没有找到对应的学生,不做改动,测试图示如下(图示1为修改20212010106 的 major前,图示2为修改20212010106 的 major后):

图示1:

PUT

图示2:

PUT

  • DELETE 删除学生信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@app.route('/api/v1/student/',methods=['DELETE'])
def delete_student():
information = request.get_json()
print(information)
student = {
'id':request.json['id'],
'name':request.json['name'],
'department':request.json['department'],
'major':request.json['major']
}
i = 0
for member in students:
if member['id'] == student['id']:
students.remove(member[i])
return jsonify({'students':students})
i = i + 1
return jsonify({'students':students})

注:使用 DELETE 请求时候需要在 Apipost 中传入 json 格式对应的数据,在修饰器被触发之后,需要使用 request 拿到传过来的数据,这些操作与POTS 方法基本一致,随后,直接遍历已经存储的学生信息即可,使用主键 id 找到对应信息并删除即可,如果没有找到对应的学生,不做改动,测试图示如下(图示1为删除信息前,图示2为删除信息后):

图示1:

DELETE

图示2:

DELETE

  • GET 查看具体 id 的学生:
1
2
3
4
5
6
7
8
9
@app.route('/api/v1/student/<int:student_id>',methods=['GET'])
def show_id_information(student_id):
student = []
for member in students:
if member['id'] == student_id:
student.append(member)
if len(student) == 0:
abort(400)
return jsonify({'students':student})

注:这里额外实现一个针对具体 id 来查找学生信息并显示的 GET 功能,如果没有找到对应学生,抛出错误码 400 ,测试图示如下(图示1为查找成功,图示2为查找失败):

图示1:

GET

图示2:

GET


  • 小技巧:
1
2
if __name__ == '__main__':
app.run(debug=True)

注:在 run 函数中加入 (debug=True),可以在编辑器改动代码保存后时候直接让代码执行程序自动重启后端服务而不用人工手动停止原任务并重新启动;

2. Docker 封装

2.1 任务目标

  • 编写 dockerfile
  • 挂载镜像
  • 将单体 web 应用在镜像上运行并完成测试

2.2 任务实现

2.2.1 资料参考

2.2.2 具体实现

  • 编写 Dockerfile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# this is an official Python runtime, used as the parent image
FROM python:3.7
# set the working directory in the container to /docker
WORKDIR /docker

# add the current directory to the container as /app
ADD . /docker

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# execute everyone's favorite pip command, pip install -r
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

# copy project
COPY . .

# execute the Flask app
CMD [ "python", "web.py"]

编写 dockerfile 有许多教程可以参考,不算是太困难,主要更改其中相关文件名与配置要求即可;

  • 构建 docker 镜像:

image-20201005165629621

dockerfile 所在的文件夹下成功构建镜像之后,使用 docker ps 指令可以查看已经构建成功的镜像:

image-20201005165727306

  • 运行镜像并完成测试:

image-20201005170232813

这里需要注意一点,由于运行指令中进行了端口的映射,也即将 80 映射到了 5000,所以在使用 apipost 进行测试时候也需要对相应的端口进行更改,测试图示如下:

1)GET 方法:

image-20201005170418818

2) POST 方法:

image-20201005170822578

image-20201005170924614

3)PUT 方法:

image-20201005171116128

image-20201005171139739

4)DELETE 方法

image-20201005171314579

image-20201005171359473

0%
undefined