639ea2eb by 周伟奇

add OAuth

1 parent 5731e2bd
......@@ -11,6 +11,8 @@ tags:
schemes:
- https
- http
security:
- OAuth2: []
paths:
/api/create/v1:
post:
......@@ -32,6 +34,43 @@ paths:
description: ok
schema:
$ref: '#/definitions/ApiResponse'
/api/priority/v1:
post:
tags:
- doc
summary: GCAP提高申请单对应文件优先级
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
required: true
schema:
$ref: '#/definitions/Application'
responses:
200:
description: ok
schema:
$ref: '#/definitions/ApiResponse'
securityDefinitions:
OAuth2:
type: oauth2
flow: application
description: 'This API uses OAuth 2 with the application(clientCredentials) grant
flow.
client_id=sMlciTkppsMzARwHpCVarm5q7DP2Vucj3ny8JFhw
client_secret=WNoOilDx140ZLcenDKfsnikv7S2LIFs60DciYoqnrZaYLqYsKpcmt7mJIL69o9AEf84uQvRnS3K2UioxfjNyImjR4UOyXbDcF6qYgTLC4KDVByKFdVhKfrn2Lc4q4BNW
scopes=write
'
tokenUrl: https://staging-bmw-ocr.situdata.com/api/oauth/token/
scopes:
write: Grants write access
responses:
ErrorResponse:
description: 调用异常, 具体情况请参考`HTTP`状态码和`code`字段
......@@ -130,6 +169,54 @@ definitions:
description: 元数据版本ID
type: string
example: '8410480'
Application:
type: object
required:
- APPLICATION_INFORMATION
properties:
APPLICATION_INFORMATION:
description: 申请单信息
type: object
required:
- SUBMIT_DATETIME
- STATUS
- ENTITY
- RATING
- APPLICATION_ID
- APPLICATION_VERSION
- INTERMEDIATE_DECISION
properties:
SUBMIT_DATETIME:
description: 提交时间
type: string
example: 2020-07-08 18:33:31+08:00
STATUS:
description: 状态
type: integer
example: 42
ENTITY:
description: 业务类型
type: string
example: CO00001
enum:
- CO00001
- CO00002
RATING:
description: 排名
type: integer
example: 4
APPLICATION_ID:
description: 申请id
type: string
example: CH-B0011010101
APPLICATION_VERSION:
description: 申请版本
type: integer
example: 1
INTERMEDIATE_DECISION:
description: ''
type: string
example: MUW
ApiResponse:
description: 响应对象,code字段用于表示响应的状态; data字段用于存放响应内容
type: object
......
......@@ -9,6 +9,7 @@ certifi==2020.6.20
chardet==3.0.4
defusedxml==0.6.0
Django==2.1
django-oauth-toolkit==1.3.2
djangorestframework==3.9.0
djangorestframework-jwt==1.11.0
idna==2.9
......@@ -17,6 +18,7 @@ isodate==0.6.0
lxml==4.5.1
marshmallow==3.6.1
multidict==4.7.6
oauthlib==3.1.0
pdfminer3k==1.3.4
Pillow==7.1.2
ply==3.11
......
from django.contrib.auth import get_user_model
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
from oauth2_provider.oauth2_backends import get_oauthlib_core
class OAuth2AuthenticationWithUser(OAuth2Authentication):
"""
OAuth 2 authentication backend using `django-oauth-toolkit`
"""
www_authenticate_realm = "api"
user = get_user_model().objects.first()
def authenticate(self, request):
"""
Returns two-tuple of (user, token) if authentication succeeds,
or None otherwise.
"""
oauthlib_core = get_oauthlib_core()
valid, r = oauthlib_core.verify_request(request, scopes=[])
if valid:
return self.user, r.access_token
request.oauth2_error = getattr(r, "oauth2_error", {})
return None
import os
import fitz
import signal
from PIL import Image
from io import BytesIO
from django.core.management import BaseCommand
from common.mixins import LoggerMixin
class Command(BaseCommand, LoggerMixin):
def __init__(self):
super().__init__()
self.log_base = '[pdf to img]'
# 处理文件开关
self.switch = True
# pdf页面转图片
self.zoom_x = 2.0
self.zoom_y = 2.0
self.trans = fitz.Matrix(self.zoom_x, self.zoom_y).preRotate(0) # zoom factor 2 in each dimension
# 优雅退出信号:15
signal.signal(signal.SIGTERM, self.signal_handler)
def signal_handler(self, sig, frame):
self.switch = False # 停止处理文件
@staticmethod
def getimage(pix):
if pix.colorspace.n != 4:
return pix
tpix = fitz.Pixmap(fitz.csRGB, pix)
return tpix
def recoverpix(self, doc, item):
x = item[0] # xref of PDF image
s = item[1] # xref of its /SMask
is_rgb = True if item[5] == 'DeviceRGB' else False
# RGB
if is_rgb:
if s == 0:
return doc.extractImage(x)
# we need to reconstruct the alpha channel with the smask
pix1 = fitz.Pixmap(doc, x)
pix2 = fitz.Pixmap(doc, s) # create pixmap of the /SMask entry
# sanity check
if not (pix1.irect == pix2.irect and pix1.alpha == pix2.alpha == 0 and pix2.n == 1):
pix2 = None
return self.getimage(pix1)
pix = fitz.Pixmap(pix1) # copy of pix1, alpha channel added
pix.setAlpha(pix2.samples) # treat pix2.samples as alpha value
pix1 = pix2 = None # free temp pixmaps
return self.getimage(pix)
# GRAY/CMYK
pix1 = fitz.Pixmap(doc, x)
pix = fitz.Pixmap(pix1) # copy of pix1, alpha channel added
if s != 0:
pix2 = fitz.Pixmap(doc, s) # create pixmap of the /SMask entry
# sanity check
if not (pix1.irect == pix2.irect and pix1.alpha == pix2.alpha == 0 and pix2.n == 1):
pix2 = None
return self.getimage(pix1)
pix.setAlpha(pix2.samples) # treat pix2.samples as alpha value
pix1 = pix2 = None # free temp pixmaps
pix = fitz.Pixmap(fitz.csRGB, pix) # GRAY/CMYK to RGB
return self.getimage(pix)
@staticmethod
def get_img_data(pix):
if type(pix) is dict: # we got a raw image
ext = pix["ext"]
img_data = pix["image"]
else: # we got a pixmap
ext = 'png'
img_data = pix.getPNGData()
return ext, img_data
@staticmethod
def split_il(il):
img_il_list = []
start = 0
length = len(il)
for i in range(length):
if i == start:
if i == length - 1:
img_il_list.append(il[start: length])
continue
elif i == length - 1:
img_il_list.append(il[start: length])
continue
if il[i][2] != il[i - 1][2]:
img_il_list.append(il[start: i])
start = i
elif il[i][3] != il[i - 1][3]:
img_il_list.append(il[start: i + 1])
start = i + 1
return img_il_list
def handle(self, *args, **kwargs):
pdf_dir = '/Users/clay/Desktop/普通打印-部分无线/竖版-无表格-农业银行'
img_dir = '/Users/clay/Desktop/普通打印-部分无线_img/竖版-无表格-农业银行'
os.makedirs(img_dir, exist_ok=True)
for d in os.listdir(pdf_dir):
# if d in ['.DS_Store', 'CH-B008486764.pdf', 'CH-B008003736.pdf', 'CH-B008487476.pdf', 'CH-B006763780.pdf',
# 'CH-B009000564.pdf', 'CH-B009020488.pdf']:
if d in ['.DS_Store', '1竖版-无表格-农业银行样例.PNG']:
continue
pdf_path = os.path.join(pdf_dir, d)
# pdf_path = '/Users/clay/Desktop/普通打印part2/工商银行(标准版)/CH-B006754676.pdf'
if os.path.isfile(pdf_path):
img_save_path = os.path.join(img_dir, d)
if os.path.exists(img_save_path):
continue
os.makedirs(img_save_path, exist_ok=True)
with fitz.Document(pdf_path) as pdf:
self.cronjob_log.info('{0} [pdf_path={1}] [metadata={2}]'.format(
self.log_base, pdf_path, pdf.metadata))
# xref_list = []
for pno in range(pdf.pageCount):
il = pdf.getPageImageList(pno)
il.sort(key=lambda x: x[0])
img_il_list = self.split_il(il)
del il
print(img_il_list)
if len(img_il_list) > 3: # 单页无规律小图过多时,使用页面转图片
page = pdf.loadPage(pno)
pm = page.getPixmap(matrix=self.trans, alpha=False)
save_path = os.path.join(img_save_path, 'page_{0}_img_0.png'.format(page.number))
pm.writePNG(save_path)
# img_path_list.append(save_path)
# self.cronjob_log.info('{0} [page to img success] [doc_id={1}] [pdf_path={2}] '
# '[page={3}]'.format(self.log_base, doc_id, pdf_path, page.number))
else: # 提取图片
for img_index, img_il in enumerate(img_il_list):
if len(img_il) == 1: # 当只有一张图片时, 简化处理
pix = self.recoverpix(pdf, img_il[0])
ext, img_data = self.get_img_data(pix)
save_path = os.path.join(img_save_path, 'page_{0}_img_{1}.{2}'.format(
pno, img_index, ext))
with open(save_path, "wb") as f:
f.write(img_data)
# img_path_list.append(save_path)
# self.cronjob_log.info(
# '{0} [extract img success] [doc_id={1}] [pdf_path={2}] [page={3}] '
# '[img_index={4}]'.format(self.log_base, doc_id, pdf_path, pno, img_index))
else: # 多张图片,竖向拼接
height_sum = 0
im_list = []
width = img_il[0][2]
for img in img_il:
# xref = img[0]
# if xref in xref_list:
# continue
height = img[3]
pix = self.recoverpix(pdf, img)
ext, img_data = self.get_img_data(pix)
# xref_list.append(xref)
im = Image.open(BytesIO(img_data))
im_list.append((height, im, ext))
height_sum += height
save_path = os.path.join(img_save_path, 'page_{0}_img_{1}.{2}'.format(
pno, img_index, im_list[0][2]))
res = Image.new(im_list[0][1].mode, (width, height_sum))
h_now = 0
for h, m, _ in im_list:
res.paste(m, box=(0, h_now))
h_now += h
res.save(save_path)
# else:
# img_dir_path = os.path.join(img_dir, d)
# os.makedirs(img_dir_path, exist_ok=True)
......@@ -5,6 +5,7 @@ import datetime
from django.utils import timezone
from django.db.utils import IntegrityError
from django.db.models import Q
from rest_framework.permissions import IsAuthenticated
from webargs import fields, validate
from webargs.djangoparser import use_args, parser
from settings import conf
......@@ -15,6 +16,7 @@ from common.redis_cache import redis_handler as rh
from .models import UploadDocRecords, DocStatus, PriorityApplication, GCAPRecords
from .mixins import DocHandler
from . import consts
from apps.account.authentication import OAuth2AuthenticationWithUser
# restframework将request.body封装至request.data, webargs从request.data中获取参数
......@@ -86,7 +88,9 @@ priority_doc_args = {
class UploadDocView(GenericView, DocHandler):
permission_classes = []
permission_classes = [IsAuthenticated]
authentication_classes = [OAuth2AuthenticationWithUser]
# required_scopes = ['write']
# 上传(接收)文件接口
@use_args(doc_upload_args, location='data')
......@@ -134,8 +138,8 @@ class UploadDocView(GenericView, DocHandler):
is_priority = PriorityApplication.objects.filter(application_id=application_id, on_off=True).exists()
value = ['{0}_{1}'.format(prefix, doc.id)]
redis_res = rh.enqueue(value, is_priority)
self.running_log.info('[doc upload success] [args={0}] [record_id={1}] [is_hil={2}] [doc_id={3}] '
'[is_priority={4}] [enqueue_res={5}]'.format(args, record.id, is_hil, doc.id,
self.running_log.info('[doc upload success] [args={0}] [record_id={1}] [prefix={2}] [doc_id={3}] '
'[is_priority={4}] [enqueue_res={5}]'.format(args, record.id, prefix, doc.id,
is_priority, redis_res))
return response.ok()
......@@ -160,7 +164,8 @@ class UploadDocView(GenericView, DocHandler):
class PriorityDocView(GenericView, DocHandler):
permission_classes = []
permission_classes = [IsAuthenticated]
authentication_classes = [OAuth2AuthenticationWithUser]
# 优先级订单接口
@use_args(priority_doc_args, location='data')
......@@ -195,6 +200,25 @@ class PriorityDocView(GenericView, DocHandler):
args, task_str_list, enqueue_res))
return response.ok()
post.openapi_doc = '''
tags: [doc]
summary: GCAP提高申请单对应文件优先级
consumes: [application/json]
produces: [application/json]
parameters:
- in: body
name: body
required: true
schema:
$ref: "#/definitions/Application"
responses:
200:
description: ok
schema:
$ref: '#/definitions/ApiResponse'
'''
class DocView(GenericView, DocHandler):
......
......@@ -22,4 +22,5 @@ urlpatterns = [
path(r'api/create/', include('apps.doc.create_urls')),
path(r'api/priority/', include('apps.doc.priority_urls')),
path(r'api/doc/', include('apps.doc.internal_urls')),
path('api/oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')),
]
......
......@@ -12,10 +12,26 @@ tags:
schemes:
- "https"
- "http"
security:
- OAuth2: []
'''
# scheme: bearer
# scheme: oauth
security_definitions = '''
OAuth2:
type: oauth2
flow: application
description: >
This API uses OAuth 2 with the application(clientCredentials) grant flow.
client_id=sMlciTkppsMzARwHpCVarm5q7DP2Vucj3ny8JFhw
client_secret=WNoOilDx140ZLcenDKfsnikv7S2LIFs60DciYoqnrZaYLqYsKpcmt7mJIL69o9AEf84uQvRnS3K2UioxfjNyImjR4UOyXbDcF6qYgTLC4KDVByKFdVhKfrn2Lc4q4BNW
scopes=write
tokenUrl: https://staging-bmw-ocr.situdata.com/api/oauth/token/
scopes:
write: Grants write access
'''
responses = '''
......@@ -98,6 +114,46 @@ Doc:
description: 元数据版本ID
type: string
example: '8410480'
Application:
type: object
required: [APPLICATION_INFORMATION]
properties:
APPLICATION_INFORMATION:
description: 申请单信息
type: object
required: [SUBMIT_DATETIME, STATUS, ENTITY, RATING, APPLICATION_ID, APPLICATION_VERSION, INTERMEDIATE_DECISION]
properties:
SUBMIT_DATETIME:
description: 提交时间
type: string
example: 2020-07-08T18:33:31.000+08:00
STATUS:
description: 状态
type: integer
example: 42
ENTITY:
description: 业务类型
type: string
example: CO00001
enum: [CO00001, CO00002]
RATING:
description: 排名
type: integer
example: 4
APPLICATION_ID:
description: 申请id
type: string
example: CH-B0011010101
APPLICATION_VERSION:
description: 申请版本
type: integer
example: 1
INTERMEDIATE_DECISION:
description: ''
type: string
example: MUW
ApiResponse:
description: 响应对象,code字段用于表示响应的状态; data字段用于存放响应内容
......
......@@ -4,6 +4,7 @@ from django.core.exceptions import PermissionDenied
from django.utils.translation import ugettext_lazy as _
from django.http import Http404
from rest_framework import exceptions, status
from rest_framework.exceptions import NotAuthenticated, PermissionDenied
from rest_framework.response import Response
from marshmallow.exceptions import ValidationError
......@@ -29,7 +30,9 @@ def exception_handler(exc, context):
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if exc.status_code == 403:
if isinstance(exc, NotAuthenticated) or isinstance(exc, PermissionDenied):
data = res_content(MetaStatus.NO_PERMISSION.value, MetaStatus.NO_PERMISSION.verbose_name)
elif exc.status_code == 403:
data = res_content(MetaStatus.NEED_LOGIN.value, MetaStatus.NEED_LOGIN.verbose_name)
else:
data = res_content(MetaStatus.INTERNAL_ERROR.value, exc.detail)
......
......@@ -48,7 +48,7 @@ class Command(BaseCommand):
view_class = view.view_class
url_path, path_parameters = pattern[0][0]
url_path = unify_url_path_format(url_path)
if url_path != '/api/create/v1':
if url_path not in ['/api/create/v1', '/api/priority/v1']:
continue
url_path_paramters = getattr(view, 'parameters_doc', None)
if url_path_paramters:
......@@ -82,7 +82,7 @@ class Command(BaseCommand):
api_doc_dct[url_path][method] = doc
doc_dct = yaml.load(base_part)
doc_dct['paths'] = api_doc_dct
# doc_dct['securityDefinitions'] = yaml.load(security_definitions)
doc_dct['securityDefinitions'] = yaml.load(security_definitions)
doc_dct['responses'] = yaml.load(responses)
doc_dct['definitions'] = yaml.load(definitions)
......
......@@ -42,6 +42,7 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
# 'corsheaders',
'oauth2_provider',
'rest_framework',
'common',
'apps.account',
......@@ -145,6 +146,7 @@ REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
# 'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
),
'EXCEPTION_HANDLER': 'common.exceptions.exception_handler'
}
......@@ -173,3 +175,8 @@ JWT_AUTH = {
# 跨域设置
# CORS_ORIGIN_ALLOW_ALL = True
# CORS_ALLOW_CREDENTIALS = True
OAUTH2_PROVIDER = {
# this is the list of available scopes
'SCOPES': {'read': 'Read scope', 'write': 'Write scope'}
}
......
This diff could not be displayed because it is too large.
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!