Files
shanxia2.0/backend/app/services/pl_quota_attachments.py
T

68 lines
2.7 KiB
Python

"""证件附件授权:仅接受当前租户有数据权限的 MinIO 登记对象。"""
from urllib.parse import unquote, urlsplit
from fastapi import HTTPException
from app.core.config import settings
from app.models.crm_models import SysOss
from app.services.pl_data_scope import assert_pl_row_in_scope
def storage_base_url():
if settings.MINIO_BASE_URL:
return settings.MINIO_BASE_URL.rstrip('/')
scheme = 'https' if settings.MINIO_SECURE else 'http'
return f'{scheme}://{settings.MINIO_ENDPOINT}/{settings.MINIO_BUCKET}'
def object_key(value, base_url=None):
raw = (value or '').strip()
base = (base_url or storage_base_url()).rstrip('/')
if raw.startswith(base + '/'):
raw = raw[len(base) + 1:]
parsed = urlsplit(raw)
if (not raw or parsed.scheme or parsed.netloc or parsed.query or parsed.fragment
or raw.startswith('/') or '\\' in raw or ',' in raw
or any(part in ('', '.', '..') for part in unquote(raw).split('/'))
or unquote(raw) != raw or any(ord(c) < 32 for c in raw)):
raise HTTPException(400, '附件必须是本系统上传的MinIO对象')
return raw
def authorize_attachment(db, user, value, base_url=None):
base = (base_url or storage_base_url()).rstrip('/')
key = object_key(value, base)
row = db.query(SysOss).filter(
SysOss.tenant_no == user.tenant_no, SysOss.del_flag == 0,
SysOss.service == 'minio', SysOss.url.in_((key, base + '/' + key)),
).first()
if row is None:
raise HTTPException(403, '附件不存在或不属于当前租户')
# 统一上传的历史记录未写创建部门;本人上传是明确的对象授权依据。
# 其他人的文件仍需全角色数据范围,租户与有效状态已在查询中强制限定。
if not user.id or row.create_by != user.id:
assert_pl_row_in_scope(db, row, SysOss, user, user.tenant_no)
return key
def read_image(db, user, value):
from app.core.storage import MinIOStorage, get_storage
from app.services.pl_quota_recognition import MAX_IMAGE_BYTES
key = authorize_attachment(db, user, value)
store = get_storage()
if not isinstance(store, MinIOStorage):
raise HTTPException(503, '证件识别需要MinIO存储')
response = None
try:
response = store.client.get_object(store.bucket_name, key)
content = response.read(MAX_IMAGE_BYTES + 1)
except Exception:
raise HTTPException(502, '读取证件图片失败,请重试或手工录入') from None
finally:
if response is not None:
response.close()
response.release_conn()
if len(content) > MAX_IMAGE_BYTES:
raise HTTPException(400, '证件图片过大')
return content