RESTful-API接口编程思路和注意事项:flask为例
前言:
API的设计主要包含:
API路由设计
API权限设计
API接口统计和计费设计
API计费报表设计
API请求和响应的数据格式设计和响应码设计
一 开发阶段
1 设计API路由URL规范。
要使用restful API设计API接口,绑定GET,POST,PATCH,DELETE作为查,增,改,删的方法,
比如数据集的增/查改删,假设用post创建一个数据集叫做dataset001
POST http://api.helloworld.com/datasets GET http://api.helloworld.com/datasets/dataset001 PATCH http://api.helloworld.com/datasets/dataset001 DELETE http://api.helloworld.com/datasets/dataset001
1 app.add_url_rule('/datasets', view_func=list_datasets, methods=['GET']) 2 app.add_url_rule('/datasets', view_func=create_dataset, methods=['POST']) 3 app.add_url_rule('/datasets/<dataset_name>', view_func=get_dataset, methods=['GET']) 4 app.add_url_rule('/datasets/<dataset_name>', view_func=update_dataset, methods=['PATCH']) 5 app.add_url_rule('/datasets/<dataset_name>', view_func=delete_dataset, methods=['DELETE']) 6 app.add_url_rule('/datasets/<dataset_name>/items', view_func=list_dataset_items, methods=['GET']) 7 app.add_url_rule('/datasets/<dataset_name>/items', view_func=create_dataset_item, methods=['POST']) 8 app.add_url_rule('/datasets/<dataset_name>/items', view_func=update_dataset_items, methods=['PATCH']) 9 app.add_url_rule('/datasets/<dataset_name>/items', view_func=delete_dataset_items, methods=['DELETE']) 10 app.add_url_rule('/datasets/<dataset_name>/items/<item_id>', view_func=get_dataset_item, methods=['GET']) 11 app.add_url_rule('/datasets/<dataset_name>/items/<item_id>', view_func=update_dataset_item, methods=['PATCH']) 12 app.add_url_rule('/datasets/<dataset_name>/items/<item_id>', view_func=delete_dataset_item, methods=['DELETE'])
2 前端请求规范和对前端跨域请求的设置
headers:携带必要的token和ID
json: 数据必须返回后端指定数据格式,需要和前端约定,或者直接设计好
1 from flask import Flask 2 from flask import Flask, jsonify, request, abort 3 from flask_cors import CORS 4 5 app = Flask(__name__) 6 7 CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
3 后端接口返回响应规范
(0) 敏感数据必须做脱敏处理,敏感信息不能显示在接口返回的数据中。
(1) 返回的http状态码,要符合http标准
201 创建成功,对应POST
200 查询或者修改成功,对应GET,PATCH
204 只返回http码,没有内容no content, 适合删除,对应DELETE
(2) 自定义状态码code和自定义消息message,放置于一级字段code中
40001 重复创建,message:product_id already exists
40002 字段缺乏;字段无效,比如传过来的product_id没有值, message:product_id is required
40003 类型不在指定范围内,比如type不在['product', 'image'],status不在['on','off','all']中, message:shelf must be on or off
40004 遇到未知错误产生异常,比如except Exception as e, message:unexpected error
1 @auth_required 2 def create_dataset_item(x_consumer_custom_id, dataset_name): 3 result = json_template.copy() 4 item = item_template.copy() 5 6 print(request.get_json()) 7 datasets = Dataset.objects(name=dataset_name, x_consumer_custom_id=x_consumer_custom_id, deleted_at=None) 8 9 if datasets: 10 collection_name = '_'.join([str(x_consumer_custom_id), datasets[0].name, 'items']) 11 12 if datasets[0].type == 'product': 13 with switch_collection(ProductItem, collection_name) as _ProductItem: 14 15 if not request.get_json(): 16 result['code'] = 40002 17 result['message'] = 'invalid fields' 18 elif not request.get_json().get('item').get('product_id'): 19 result['code'] = 40002 20 result['message'] = 'product_id is required' 21 elif not request.get_json().get('item').get('shelf'): 22 result['code'] = 40002 23 result['message'] = 'shelf is required' 24 elif request.get_json().get('item').get('shelf') not in ['on', 'off']: 25 result['code'] = 40003 26 result['message'] = 'shelf must be on or off' 27 else: 28 try: 29 item['item'] = _ProductItem(**request.get_json().get('item')).save().to_dict() 30 result['data'] = item 31 return jsonify(result), 201 32 except mongoengine.NotUniqueError as e: 33 result['code'] = 40001 34 result['message'] = 'product already exists' 35 return jsonify(result), 404 36 except Exception as e: 37 print(e) 38 result['code'] = 40004 39 result['message'] = 'unexpected error' 40 return jsonify(result), 404 41 42 return jsonify(result), 200 43 44 else: 45 pass 46 47 else: 48 result['code'] = 404 49 result['message'] = 'dataset not found' 50 return jsonify(result), 404
(3) 返回一级字段至少带有code,data,message三个一级字段,如果是查询多个,需要返回page,per_page,total等一级字段。
{ "code": 100, "data": { "dataset": { "created_at": "2018-09-18T11:38:09Z", "description": "description001", "id": "5ba0e3a1bbb4960010f5a62b", "name": "dataset020", "status": "created", "type": "product", "updated_at": "2018-09-18T11:38:09Z" } }, "message": "success" }
{ "code": 100, "data": { "datasets": [ { "created_at": "2018-09-18T11:38:09Z", "description": "description00000001", "id": "5ba0e3a1bbb4960010f5a62b", "name": "dataset020", "status": "created", "type": "product", "updated_at": "2018-09-18T11:38:09Z" } ] }, "message": "success", "page": 1, "per_page": 20, "total": 1 }
以上返回格式,data用dataset和datasets关键字返回给前端,以便于前端取而展现
(4) 返回二级字段data里,根据是单个返回item作为key,value是一个具体数据字典,根据多个返回items作为key,value是多个数据字典组成的列表
4 API权限设计
使用强大插件kong API做权限控制,jwt令牌模式。
5 API接口统计和计费(阶梯计费)
使用logstash,统计,接入阶梯计费策略
6 API计费报表
二 开发自测阶段
使用多种方法测API接口,校正输入格式和输出格式。
1 自己写流程化脚本,看请求和结果返回的日志是否正常,requests发送请求,unittest管理用例,HTMLTestRunner生成测试报告
2 使用postman建组,测试增查改删,保存在组中,通过workspace共享给前端使用,同时通过脚本和postman双重测试对接口比较可靠
以上两种工具都需要对数据返回做基本比对。
(1)敏感数据是否脱敏?
(2)返回数据格式一级字段是否符合?
(3)返回数据格式二级字段甚至三级字段是否符合?
(4)字段键的英文字母是否完全一致?
(5)字段值的结果是否异常?
(6)汉字编码问题!!!!!UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)
三 和前端对接阶段的后端工作
1 为前端设置跨域访问请求,如上。
2 程序代码中,每个接口的开始都要print或者log前端传输过来的, Header, json数据, 以便于前端对接接口出现问题及时回应给前端。
3 后端应主动指导前端,当前端遇到传输数据格式问题的时候,这是后端应该做的将标准请求格式使前端明晰。
四 部署生产环境测试阶段