ff1b4142 by Gruel

swagger

1 parent f7cba4a5
...@@ -28,6 +28,7 @@ sftp-config.json ...@@ -28,6 +28,7 @@ sftp-config.json
28 28
29 *.sqlite3 29 *.sqlite3
30 conf/* 30 conf/*
31 docs/*
31 32
32 # 脚本 33 # 脚本
33 src/*.sh 34 src/*.sh
...\ No newline at end of file ...\ No newline at end of file
......
1 from django.shortcuts import render 1 from django.shortcuts import render
2 from webargs import fields 2 from webargs import fields, validate
3 from webargs.djangoparser import use_args, parser 3 from webargs.djangoparser import use_args, parser
4 from common.mixins import GenericView 4 from common.mixins import GenericView
5 from common import response 5 from common import response
...@@ -14,22 +14,22 @@ def load_data(request, schema): ...@@ -14,22 +14,22 @@ def load_data(request, schema):
14 return request.data 14 return request.data
15 15
16 16
17 application_data_args = {'applicationId': fields.Str(required=True)} 17 application_data_args = {'applicationId': fields.Str(required=True, validate=validate.Length(max=64))}
18 18
19 applicant_data_args = { 19 applicant_data_args = {
20 'mainApplicantName': fields.Str(required=True), 20 'mainApplicantName': fields.Str(required=True, validate=validate.Length(max=16)),
21 'coApplicantName': fields.Str(required=True), 21 'coApplicantName': fields.Str(required=True, validate=validate.Length(max=16)),
22 'guarantor1Name': fields.Str(required=True), 22 'guarantor1Name': fields.Str(required=True, validate=validate.Length(max=16)),
23 'guarantor2Name': fields.Str(required=True), 23 'guarantor2Name': fields.Str(required=True, validate=validate.Length(max=16)),
24 } 24 }
25 25
26 document_args = { 26 document_args = {
27 'documentName': fields.Str(required=True), 27 'documentName': fields.Str(required=True, validate=validate.Length(max=255)),
28 'documentScheme': fields.Str(required=True), 28 'documentScheme': fields.Str(required=True, validate=validate.Length(max=64)),
29 'businessType': fields.Str(required=True), 29 'businessType': fields.Str(required=True, validate=validate.Length(max=64)),
30 'uploadFinishTime': fields.DateTime(required=True), 30 'uploadFinishTime': fields.DateTime(required=True),
31 'dataSource': fields.Str(required=True), 31 'dataSource': fields.Str(required=True, validate=validate.Length(max=64)),
32 'metadataVersionId': fields.Str(required=True), 32 'metadataVersionId': fields.Str(required=True, validate=validate.Length(max=64)),
33 } 33 }
34 34
35 doc_upload_args = { 35 doc_upload_args = {
...@@ -63,3 +63,80 @@ class DocView(GenericView): ...@@ -63,3 +63,80 @@ class DocView(GenericView):
63 ) 63 )
64 self.running_log.info('[doc upload success] [args={0}]'.format(args)) 64 self.running_log.info('[doc upload success] [args={0}]'.format(args))
65 return response.ok() 65 return response.ok()
66
67 post.openapi_doc = '''
68 summary: pos上传文件信息
69 tags: [doc]
70 requestBody:
71 required: true
72 content:
73 application/json:
74 schema:
75 type: object
76 properties:
77 applicationData:
78 description: 申请信息
79 type: object
80 properties:
81 applicationId:
82 description: 申请id
83 type: string
84 example: CH-B0011010101
85 applicantData:
86 description: 申请人信息
87 type: object
88 properties:
89 mainApplicantName:
90 description: 主申请人
91 type: string
92 example: 王明阳
93 coApplicantName:
94 description: 共同申请人
95 type: string
96 example: 王明月
97 guarantor1Name:
98 description: 担保人1
99 type: string
100 example: 王明日
101 guarantor2Name:
102 description: 担保人2
103 type: string
104 example: 王明雨
105 document:
106 description: 文件信息
107 type: object
108 properties:
109 documentName:
110 description: 文件名
111 type: string
112 example: CH-B0011010101王明阳申请表
113 documentScheme:
114 description: 文件格式?
115 type: string
116 example: CO00001
117 businessType:
118 description: 业务类型
119 type: string
120 example: HIL
121 uploadFinishTime:
122 description: 上传完成时间
123 type: string
124 example: '2020-09-01 12:21:11'
125 dataSource:
126 description: 数据源
127 type: string
128 example: POS
129 metadataVersionId:
130 description: 元数据版本ID
131 type: string
132 example: '8410480'
133
134 responses:
135 200:
136 description: ok
137 content:
138 application/json:
139 schema:
140 type: object
141 $ref: '#/components/schemas/ApiResponse'
142 '''
......
1
2 base_part = '''
3 openapi: 3.0.0
4 info:
5 title: 接口文档
6 description: 宝马ocr/biz_logic接口文档
7 version: 1.0.0
8 servers:
9 - url: 'http://127.0.0.1:8000'
10 description: Development server
11 - url: 'http://127.0.0.1:8000'
12 description: sit
13 - url: 'http://127.0.0.1:8000'
14 description: uat
15 - url: 'http://127.0.0.1:8000'
16 description: prd
17 tags:
18 - name: user
19 description: 用户
20 - name: doc
21 description: 文件
22
23 security:
24 - bearerAuth: []
25 '''
26 # scheme: bearer
27 security_schemes = '''
28 bearerAuth: # arbitrary name for the security scheme
29 type: http
30 scheme: bearer
31 bearerFormat: JWT
32 description: >
33 token过期标志
34
35 1. Response Headers中的WWW-Authenticate中的status
36
37 status: -1 Invalid Authorization header
38
39 status: -2 Signature has expired beacause of expire_time
40
41 status: -3 Error decoding signature
42
43 status: -4 Signature has expired beacause new signature is generated
44
45 status: 0 valid Authorization
46
47 status: 1 new create, only login would set
48
49 2. Response Body中的meta -> status == 1
50 '''
51
52 responses = '''
53 ErrorResponse:
54 description: 调用异常, 具体情况请参考`HTTP`状态码和`meta -> status`字段
55 content:
56 application/json:
57 schema:
58 $ref: '#/components/schemas/ApiResponse'
59 NoContent:
60 description: 后台接收请求,但是没有响应内容
61 content:
62 application/json:
63 schema:
64 $ref: '#/components/schemas/ApiResponse'
65 '''
66
67 parameters = ''
68
69 schemas = '''
70 ApiResponse:
71 description: 响应对象,meta字段用于表示响应的状态; data字段用于存放响应内容
72 type: object
73 properties:
74 code:
75 type: integer
76 description: '0: success
77 1: need login
78 2: invalid params
79 3: internal error
80 4: object not exist
81 5: async wait
82 6: no permission
83 7: illegal operation'
84 msg:
85 type: string
86 nullable: true
87 data:
88 oneOf:
89 - type: object
90 nullable: true
91 - type: integer
92 nullable: true
93 - type: array
94 nullable: true
95
96 Pagination:
97 description: 分页参数
98 type: object
99 properties:
100 current:
101 description: 当前页面
102 type: integer
103 total:
104 description: 所有元素个数
105 type: integer
106 page_size:
107 description: 页面大小
108 type: integer
109 required:
110 - current
111 - total
112 - pageSize
113 '''
...\ No newline at end of file ...\ No newline at end of file
1 import collections
2 import re
3
4 from django.core.management import BaseCommand
5 from django.urls.resolvers import get_resolver
6
7 import yaml
8 from yaml.scanner import ScannerError
9
10 from common.api_doc import (base_part, security_schemes, responses, schemas)
11
12 _mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
13
14
15 def dict_representer(dumper, data):
16 return dumper.represent_dict(data.items())
17
18
19 def dict_constructor(loader, node):
20 return collections.OrderedDict(loader.construct_pairs(node))
21
22
23 yaml.add_representer(collections.OrderedDict, dict_representer)
24 yaml.add_constructor(_mapping_tag, dict_constructor)
25
26
27 def unify_url_path_format(string):
28 return '/%s' % re.sub(r'%\(([^/]+)\)s', lambda m: '{%s}' % m.group(1),
29 string)
30
31
32 DEFAULT_API_DOC = '''
33 summary: 未填写
34 responses:
35 200:
36 description: ok
37 '''
38
39
40 class Command(BaseCommand):
41 def add_arguments(self, parser):
42 parser.add_argument('-o', '--output_file', help='文件名,用于存储文档')
43
44 def handle(self, *args, **kwargs):
45 urls = get_resolver()
46 api_doc_dct = {}
47 for view, pattern in urls.reverse_dict.items():
48 view_class = view.view_class
49 url_path, path_parameters = pattern[0][0]
50 url_path = unify_url_path_format(url_path)
51 url_path_paramters = getattr(view, 'parameters_doc', None)
52 if url_path_paramters:
53 url_path_paramters = yaml.load(url_path_paramters)
54 else:
55 url_path_paramters = [{
56 'in': 'path',
57 'name': parameter,
58 'required': True,
59 'schema': {
60 'type': 'string'
61 }
62 } for parameter in path_parameters]
63 api_doc_dct[url_path] = {}
64 if url_path_paramters:
65 api_doc_dct[url_path]['parameters'] = url_path_paramters
66 for method in view_class.http_method_names:
67 method_handler = getattr(view_class, method, None)
68 doc = getattr(method_handler, 'openapi_doc', None)
69 if not method_handler or (method == 'options' and not doc):
70 continue
71 try:
72 doc = yaml.load(doc or DEFAULT_API_DOC)
73 except ScannerError as err:
74 raise Exception(
75 'failed to load doc: """%s"""\nerr: %s' % (doc, err))
76 if doc.get('parameters'):
77 for parameter in doc['parameters']:
78 if parameter['name'] in path_parameters:
79 doc['parameters'].pop(parameter)
80 api_doc_dct[url_path][method] = doc
81 doc_dct = yaml.load(base_part)
82 doc_dct['paths'] = api_doc_dct
83 doc_dct['components'] = {
84 'securitySchemes': yaml.load(security_schemes),
85 'responses': yaml.load(responses),
86 'schemas': yaml.load(schemas),
87 }
88
89 doc_str = yaml.dump(
90 doc_dct, default_flow_style=False, allow_unicode=True)
91 if kwargs.get('output_file'):
92 with open(kwargs['output_file'], 'w') as f:
93 f.write(doc_str)
94 self.stdout.write(
95 self.style.SUCCESS('api doc generated succssfully: %s' %
96 kwargs['output_file']))
97 else:
98 self.stdout.write(doc_str)
1 import os
2 import sys
3
4 from django.core.management import BaseCommand
5
6 from openapi_spec_validator import validate_spec_url, validate_v2_spec_url
7 from openapi_spec_validator.exceptions import ValidationError
8
9
10 # from openapi_spec_validator/__main__.py
11 class Command(BaseCommand):
12 def add_arguments(self, parser):
13 parser.add_argument(
14 'filename', help="Absolute or relative path to file")
15 parser.add_argument(
16 '--schema',
17 help="OpenAPI schema (default: 3.0.0)",
18 type=str,
19 choices=['2.0', '3.0.0'],
20 default='3.0.0')
21
22 def handle(self, *args, **kwargs):
23 filename = kwargs['filename']
24 filename = os.path.abspath(filename)
25 # choose the validator
26 if kwargs['schema'] == '2.0':
27 validate_url = validate_v2_spec_url
28 elif kwargs['schema'] == '3.0.0':
29 validate_url = validate_spec_url
30 # validate
31 try:
32 validate_url('file://' + filename)
33 except ValidationError as err:
34 self.stdout.write(self.style.ERROR(err))
35 sys.exit(1)
36 except Exception as err:
37 self.stdout.write(self.style.ERROR(err))
38 sys.exit(2)
39 else:
40 self.stdout.write(self.style.SUCCESS('ok'))
...@@ -4,9 +4,10 @@ from .named_enum import NamedEnum ...@@ -4,9 +4,10 @@ from .named_enum import NamedEnum
4 4
5 5
6 def res_content(meta_status, msg, data=None): 6 def res_content(meta_status, msg, data=None):
7 if data is None: 7 res = {'code': meta_status, 'message': msg}
8 data = {} 8 if data is not None:
9 return {'code': meta_status, 'message': msg, 'data': data} 9 res['data'] = data
10 return res
10 11
11 12
12 @enum.unique 13 @enum.unique
......
1 # 录题系统开发规范
2
3
4 ## 代码规范
5
6 1. 参考 [python_specification.md](python_specification.md)
7 2. 帐号、密码严禁提交到git仓中
8 3. 参与开发前请阅读[十二要素应用宣言](https://12factor.net/zh_cn/)
9
10
11 ## 接口文档
12
13 接口文档使用`openapi`规范书写, 可以参考[demo](http://editor.swagger.io/)以获取直观感受。
14
15 目前,接口文档写在代码中, 并通过脚本将代码中的文档提取出来, 示例如下。
16
17
18 1. 编写文档
19
20 ```python
21 class UserInfoView(APIView):
22 def get(self, request):
23 user_info = serializers.staff_detail.dump(request.user).data
24 return APIResponse.ok(user_info)
25
26 get.openapi_doc = '''
27 summary: 获取个人信息
28 tags:
29 - 帐号
30 responses:
31 200:
32 description: ok
33 content:
34 application/json:
35 schema:
36 type: object
37 properties:
38 meta:
39 $ref: '#/components/schemas/ApiResponseMeta'
40 data:
41 $ref: '#/components/schemas/AccountInfoDetail'
42 '''
43
44
45 ```
46
47 2. 提取接口文档
48
49 ```
50 $: python manage.py generate_api_doc -o ../docs/main.yaml
51 api doc generated succssfully: ../docs/main.yaml
52 ```
53
54 3. 校对接口文档
55
56 ```
57 $: python manage.py validate_api_doc ../docs/main.yaml
58 ok
59 ```
60
61 4. 查看接口文档
62
63 * 方法1: 粘贴到[online editor](http://editor.swagger.io/)中查看
64 * 方法2: 安装 https://bitbucket.org/abrahamL/openapi_toolset.git
65
66 ```
67 $: cd docs
68 $: ls
69 main.yaml
70 $: python -m openapi_toolset
71 * Serving Flask app "openapi_toolset" (lazy loading)
72 * Environment: production
73 WARNING: Do not use the development server in a production environment.
74 Use a production WSGI server instead.
75 * Debug mode: off
76 * Running on http://127.0.0.1:9090/ (Press CTRL+C to quit)
77 ```
78
79 浏览器访问http://127.0.0.1:9090即可查看文档
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!