Files
SMANAGE/backup/_flask_backup/fsr.py
T

1016 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import uuid
import json
import re
import importlib
import importlib.util
import traceback
import threading
from pathlib import Path
from django.http import HttpResponse, JsonResponse
from django.conf import settings
from django.urls import path
_request_local = threading.local()
def get_current_request():
return getattr(_request_local, 'request', None)
def set_current_request(request):
_request_local.request = request
class _CompatQueryDict:
def __init__(self, querydict):
self._qd = querydict
def get(self, key, default=None, type=None):
val = self._qd.get(key, default)
if val is None or val == default:
return val
if type is not None:
try:
return type(val)
except (ValueError, TypeError):
return default
return val
def __getitem__(self, key):
return self._qd[key]
def __contains__(self, key):
return key in self._qd
def __iter__(self):
return iter(self._qd)
def __bool__(self):
return bool(self._qd)
def keys(self):
return self._qd.keys()
def values(self):
return self._qd.values()
def items(self):
return self._qd.items()
def getlist(self, key):
return self._qd.getlist(key)
class _CompatHeaders:
def __init__(self, meta):
self._meta = meta
def get(self, key, default=None):
meta_key = 'HTTP_' + key.upper().replace('-', '_')
val = self._meta.get(meta_key)
if val is None:
meta_key = key.upper().replace('-', '_')
val = self._meta.get(meta_key)
return val if val is not None else default
def __getitem__(self, key):
val = self.get(key)
if val is None:
raise KeyError(key)
return val
def __contains__(self, key):
return self.get(key) is not None
class _CompatRequest:
def __init__(self, django_request):
self._req = django_request
self.method = django_request.method
try:
self._body = django_request.body
except Exception:
self._body = b''
if self.method in ("PATCH", "PUT") and django_request.content_type == "application/x-www-form-urlencoded":
from django.http import QueryDict as _QD
self.POST = _CompatQueryDict(_QD(self._body, encoding="utf-8"))
else:
self.POST = _CompatQueryDict(django_request.POST)
self.GET = _CompatQueryDict(django_request.GET)
self.COOKIES = django_request.COOKIES
self.META = django_request.META
self.FILES = django_request.FILES
self.path = django_request.path
@property
def body(self):
return self._body
def build_absolute_uri(self):
return self._req.build_absolute_uri()
def get_host(self):
return self._req.get_host()
def get_json(self, silent=False, force=False):
try:
if self.body:
return json.loads(self.body.decode('utf-8'))
if silent:
return None
raise ValueError("Empty request body")
except Exception:
if not silent:
raise
return None
@property
def form(self):
return self.POST
@property
def args(self):
return self.GET
@property
def json(self):
result = self.get_json(silent=True)
return result if result is not None else {}
@property
def cookies(self):
return self.COOKIES
@property
def headers(self):
return _CompatHeaders(self.META)
@property
def files(self):
return self.FILES
def get_data(self, as_text=False):
if as_text:
return self.body.decode('utf-8')
return self.body
@property
def url(self):
return self.build_absolute_uri()
@property
def referrer(self):
return self.META.get('HTTP_REFERER', '')
@property
def host(self):
return self.get_host()
def _inject_templateapi_imports(source, filepath):
rel = os.path.relpath(filepath, settings.BASE_DIR)
parts = Path(rel).parts
has_formdata = ('import TempLateAPI.GERP.API.FormAPI.FormData' in source
or 'from TempLateAPI.GERP.API.FormAPI' in source)
if has_formdata or 'FormAPI' in parts:
source = source.replace(
'import TempLateAPI.GERP.API.FormAPI.FormData as FormData',
'# import FormData - will be loaded dynamically'
)
source = source.replace(
'from TempLateAPI.GERP.API.FormAPI import FormData',
'# import FormData - will be loaded dynamically'
)
source = source.replace(
'import TempLateAPI.GERP.API.FormAPI.FormData',
'# import FormData - will be loaded dynamically'
)
lines = source.split('\n')
insert_pos = 0
for i, line in enumerate(lines):
stripped = line.strip()
if stripped.startswith('# import FormData') or stripped.startswith('from PDAPI'):
insert_pos = i + 1
if insert_pos > 0:
compat_code = (
'\ntry:\n'
' from gvsds.fsr_compat import FormDataCompat\n'
' FormData = FormDataCompat()\n'
'except Exception as _fd_err:\n'
' import types\n'
' FormData = types.SimpleNamespace()\n'
' FormData.GetTable = lambda *a, **k: types.SimpleNamespace(\n'
' KeyToUUIDConversion=lambda *a2, **k2: "",\n'
' GetItem=lambda *a3, **k3: {},\n'
' )\n'
' FormData.TABLE_CODE_DISPLAY = {}\n'
' FormData.TABLE_ENUM_LABELS = {}\n'
' FormData.TABLE_FK_PICKER = {}\n'
' FormData.get_user_timezone = lambda: "UTC"\n'
' FormData.utc_to_user_tz = lambda dt, tz=None: dt\n'
' FormData.RegisterFunctionExtension = lambda *a, **k: None\n'
)
lines.insert(insert_pos, compat_code)
source = '\n'.join(lines)
source = source.replace('import TempLateAPI.', '# import TempLateAPI.')
source = source.replace('from TempLateAPI.', '# from TempLateAPI.')
return source
class _AbortException(Exception):
def __init__(self, response):
self.response = response
class _ExtractExpr:
def __init__(self, field_name, lookup):
self.field_name = field_name
self.lookup = lookup
def __eq__(self, other):
from django.db.models import Q
return Q(**{f'{self.field_name}__{self.lookup}': other})
def __ne__(self, other):
from django.db.models import Q
return ~Q(**{f'{self.field_name}__{self.lookup}': other})
def __gt__(self, other):
from django.db.models import Q
return Q(**{f'{self.field_name}__{self.lookup}__gt': other})
def __lt__(self, other):
from django.db.models import Q
return Q(**{f'{self.field_name}__{self.lookup}__lt': other})
def __ge__(self, other):
from django.db.models import Q
return Q(**{f'{self.field_name}__{self.lookup}__gte': other})
def __le__(self, other):
from django.db.models import Q
return Q(**{f'{self.field_name}__{self.lookup}__lte': other})
class _AggregateExpr:
def __init__(self, model, aggregate):
self.model = model
self.aggregate = aggregate
class _CompatFunc:
def sum(self, field_expr):
from django.db.models import Sum
if hasattr(field_expr, '_django_name'):
agg = Sum(field_expr._django_name)
elif hasattr(field_expr, 'field_name'):
agg = Sum(field_expr.field_name)
else:
agg = Sum(str(field_expr))
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
return _AggregateExpr(model, agg) if model else agg
def count(self, field_expr=None):
from django.db.models import Count
if field_expr is None:
return Count('*')
if hasattr(field_expr, '_django_name'):
agg = Count(field_expr._django_name)
else:
agg = Count(str(field_expr))
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
return _AggregateExpr(model, agg) if model else agg
def max(self, field_expr):
from django.db.models import Max
if hasattr(field_expr, '_django_name'):
agg = Max(field_expr._django_name)
else:
agg = Max(str(field_expr))
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
return _AggregateExpr(model, agg) if model else agg
def min(self, field_expr):
from django.db.models import Min
if hasattr(field_expr, '_django_name'):
agg = Min(field_expr._django_name)
else:
agg = Min(str(field_expr))
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
return _AggregateExpr(model, agg) if model else agg
def avg(self, field_expr):
from django.db.models import Avg
if hasattr(field_expr, '_django_name'):
agg = Avg(field_expr._django_name)
else:
agg = Avg(str(field_expr))
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
return _AggregateExpr(model, agg) if model else agg
def year(self, field_expr):
if hasattr(field_expr, '_django_name'):
return _ExtractExpr(field_expr._django_name, 'year')
return _ExtractExpr(str(field_expr), 'year')
def month(self, field_expr):
if hasattr(field_expr, '_django_name'):
return _ExtractExpr(field_expr._django_name, 'month')
return _ExtractExpr(str(field_expr), 'month')
def day(self, field_expr):
if hasattr(field_expr, '_django_name'):
return _ExtractExpr(field_expr._django_name, 'day')
return _ExtractExpr(str(field_expr), 'day')
FLASK_COMPAT_HEADER = """
from django.http import HttpResponse, JsonResponse as _DJJsonResponse, FileResponse as _FileResponse, StreamingHttpResponse as _StreamingHttpResponse
from django.db.models import Q as _QDj
import json as _json
import re as _re
import threading as _threading
import os as _os
from gvsds.fsr import _CompatRequest as _CompatRequest, _CompatQueryDict as _CompatQueryDict
from gvsds.fsr import _ExtractExpr as _ExtractExpr, _CompatFunc as _CompatFunc, _AggregateExpr as _AggregateExpr
from gvsds.fsr import _AbortException as _AbortException
from PDAPI_django import _DeleteStmt as _DeleteStmt
_request_local = _threading.local()
_api_local = _threading.local()
def _Q(model):
from gvsds.fluent import FluentQuery
return FluentQuery(model)
def _jsonify(*args, **kwargs):
if args and kwargs:
raise TypeError('jsonify() behavior undefined when passed both args and kwargs')
elif len(args) == 1:
data = args[0]
else:
data = args or kwargs
return _DJJsonResponse(data, safe=False)
def _redirect(location, code=302):
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
if code == 301:
return HttpResponsePermanentRedirect(location)
return HttpResponseRedirect(location)
def _url_for(endpoint, **kwargs):
endpoint_map = {
'MainLoginHTML': 'https://ugm.gvsds.com/login.html',
'GERP.GERPIndex': 'https://gerp.gvsds.com/',
'UGM.Index': 'https://ugm.gvsds.com/',
'main': 'https://www.gvsds.com/',
}
if endpoint in endpoint_map:
return endpoint_map[endpoint]
parts = endpoint.split('.')
if len(parts) >= 2:
subdomain = parts[0].lower()
path = '/'.join(parts[1:]).lower()
return f'https://{subdomain}.gvsds.com/{path}'
return f'/'
def _make_response(rv, status=None, headers=None):
if isinstance(rv, dict):
resp = _DJJsonResponse(rv, safe=False)
elif isinstance(rv, str):
resp = HttpResponse(rv)
elif isinstance(rv, bytes):
resp = HttpResponse(rv)
else:
resp = HttpResponse(str(rv))
if status:
resp.status_code = status
if headers:
for k, v in headers.items():
resp[k] = v
return resp
def _send_file(filepath_or_obj, mimetype=None, as_attachment=False, download_name=None, conditional=True):
if isinstance(filepath_or_obj, str):
from django.core.files import File as _DjFile
f = open(filepath_or_obj, 'rb')
resp = _FileResponse(f, content_type=mimetype or 'application/octet-stream')
elif hasattr(filepath_or_obj, 'read'):
resp = _FileResponse(filepath_or_obj, content_type=mimetype or 'application/octet-stream')
else:
resp = HttpResponse(filepath_or_obj, content_type=mimetype or 'application/octet-stream')
if as_attachment and download_name:
resp['Content-Disposition'] = f'attachment; filename="{download_name}"'
return resp
def _FlaskResponse(content=None, status=None, mimetype=None, headers=None):
content_type = mimetype or 'text/html'
if isinstance(content, bytes):
resp = HttpResponse(content, content_type=content_type, status=status or 200)
elif isinstance(content, str):
resp = HttpResponse(content, content_type=content_type, status=status or 200)
else:
resp = HttpResponse(content, content_type=content_type, status=status or 200)
if headers:
for k, v in headers.items():
resp[k] = v
return resp
class _g:
@property
def ELTUser(self):
elt_user = getattr(_api_local, 'elt_user', None)
if elt_user is not None:
return elt_user
from PDAPI_django import UGM
return UGM.GetELTUser()
class _session(dict):
pass
def _abort(code, description=None):
raise _AbortException(HttpResponse(description or '', status=code))
class _current_app:
config = {}
class _FlaskCompatObj:
@property
def request(self):
return getattr(_api_local, 'request', None)
Response = staticmethod(_FlaskResponse)
send_file = staticmethod(_send_file)
jsonify = staticmethod(_jsonify)
redirect = staticmethod(_redirect)
url_for = staticmethod(_url_for)
make_response = staticmethod(_make_response)
abort = staticmethod(_abort)
g = _g()
session = _session()
current_app = _current_app()
flash = lambda *a, **k: None
Blueprint = type('Blueprint', (), {})
Flask = type('Flask', (), {})
flask = _FlaskCompatObj()
func = _CompatFunc()
def _regex_filter(field_name, value):
from PDAPI_django import _pascal_to_snake
snake = _pascal_to_snake(field_name)
return _QDj(**{snake + '__regex': value})
class _ParameterValidator:
DEFAULT_TIMESTAMP_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
class _FSRCompat:
ParameterValidator = _ParameterValidator()
FSR = _FSRCompat()
"""
class StringLoader(importlib.util.Loader):
def __init__(self, source, fullname):
self.source = source
self.fullname = fullname
def create_module(self, spec):
return None
def exec_module(self, module):
code = compile(self.source, f"<api:{self.fullname}>", "exec")
exec(code, module.__dict__)
def _load_api_module(filepath, force_reload=False):
with open(filepath, 'r', encoding='utf-8') as f:
source = f.read()
source = source.replace('from PDAPI import *', 'from PDAPI_django import *')
source = source.replace('import PDAPI', 'import PDAPI_django as PDAPI')
source = source.replace('from AppBridge import *', '')
source = source.replace('import AppBridge', '')
source = source.replace('flask.request.method', 'request.method')
source = source.replace('flask.request.form', 'request.POST')
source = source.replace('flask.request.args', 'request.GET')
source = source.replace('flask.request.json', 'request.json')
source = source.replace('flask.request.cookies', 'request.COOKIES')
source = source.replace('flask.request.headers', 'request.headers')
source = source.replace('flask.request.files', 'request.FILES')
source = source.replace('flask.request.url', 'request.build_absolute_uri()')
source = source.replace('flask.request.path', 'request.path')
source = source.replace('flask.request.referrer', 'request.META.get("HTTP_REFERER", "")')
source = source.replace('flask.request.host', 'request.get_host()')
source = source.replace('flask.request.get_json(silent=True)', 'request.get_json(silent=True)')
source = source.replace('flask.request.get_json()', 'request.get_json()')
source = source.replace('flask.request', 'request')
source = source.replace('flask.jsonify', '_jsonify')
source = source.replace('flask.redirect', '_redirect')
source = source.replace('flask.url_for', '_url_for')
source = source.replace('flask.make_response', '_make_response')
source = source.replace('flask.g', '_g')
source = source.replace('flask.session', '_session')
source = source.replace('flask.abort', '_abort')
source = source.replace('flask.current_app', '_current_app')
source = source.replace('flask.Blueprint', 'type("Blueprint", (), {})')
source = source.replace('flask.Flask', 'type("Flask", (), {})')
source = source.replace('flask.flash', 'lambda *a, **k: None')
source = source.replace('from flask import', '# from flask import')
source = source.replace('import flask', '# import flask')
source = source.replace('flask_wtf', 'type("flask_wtf", (), {})')
source = source.replace('flask_socketio', 'type("flask_socketio", (), {})')
source = source.replace('flask_cors', 'type("flask_cors", (), {})')
source = source.replace('flask_sqlalchemy', 'type("flask_sqlalchemy", (), {})')
source = source.replace('flask_bcrypt', 'type("flask_bcrypt", (), {})')
source = source.replace('flask_limiter', 'type("flask_limiter", (), {})')
source = source.replace('from Utils.Models import', 'from gvsds.apps.gerp.models import')
source = source.replace('from Utils.Object import UUIDObj, NewBUUID', 'from PDAPI_django import UUIDObj, NewBUUID')
source = source.replace('from Utils.Object import UUIDObj', 'from PDAPI_django import UUIDObj')
source = source.replace('from Utils.Object import NewBUUID', 'from PDAPI_django import NewBUUID')
source = source.replace('from Utils.Object import Get16toBytes, GetBytesTo16', 'from PDAPI_django import Get16toBytes, GetBytesTo16')
source = source.replace('from Utils.Object import Get16toBytes', 'from PDAPI_django import Get16toBytes')
source = source.replace('from Utils.Object import GetBytesTo16', 'from PDAPI_django import GetBytesTo16')
source = source.replace('from Utils.Object import GenerateUUID', 'from PDAPI_django import GenerateUUID')
source = re.sub(r'from Utils\.Object import (.+)', r'from PDAPI_django import \1', source)
source = source.replace('from Utils.Lib import', 'from PDAPI_django import')
source = source.replace('from Utils.UGMLib import', 'from PDAPI_django import')
source = source.replace('from Utils.Browser import', 'from PDAPI_django import')
source = source.replace('from Utils.Config import', 'from PDAPI_django import')
source = source.replace('from Utils.Form import', 'from PDAPI_django import')
source = source.replace('from Utils.Safe import', 'from PDAPI_django import')
source = source.replace('from Utils.Validation import', 'from PDAPI_django import')
source = source.replace('from Utils.Email import', 'from PDAPI_django import')
source = source.replace('from Utils.Template import', 'from PDAPI_django import')
source = source.replace('from Utils.Permission import', 'from PDAPI_django import')
source = source.replace('from Utils.Constants import', 'from PDAPI_django import')
source = re.sub(r'([\w.]+)\.query\.filter\(', r'_Q(\1).filter(', source)
source = re.sub(r'([\w.]+)\.query\.order_by\(', r'_Q(\1).order_by(', source)
source = re.sub(r'([\w.]+)\.query\.all\(\)', r'_Q(\1).all()', source)
source = re.sub(r'([\w.]+)\.query\.first\(\)', r'_Q(\1).first()', source)
source = re.sub(r'([\w.]+)\.query\.count\(\)', r'_Q(\1).count()', source)
source = re.sub(r'([\w.]+)\.query\.get\(', r'_Q(\1).get(', source)
source = re.sub(r'([\w.]+)\.query\.limit\(', r'_Q(\1).limit(', source)
source = re.sub(r'([\w.]+)\.query\.join\(', r'_Q(\1).join(', source)
source = re.sub(r'([\w.]+)\.query\.paginate\(', r'_Q(\1).paginate(', source)
source = source.replace('from sqlalchemy import func', 'func = func')
source = source.replace('from sqlalchemy import', '# from sqlalchemy import')
source = source.replace('import sqlalchemy', '# import sqlalchemy')
source = source.replace('sqlalchemy.and_', 'db.and_')
source = source.replace('sqlalchemy.or_', 'db.or_')
source = source.replace('sqlalchemy.exc.IntegrityError', 'django.db.IntegrityError')
source = re.sub(r'sqlalchemy\.delete\((\w+)\)', r'_DeleteStmt(\1)', source)
source = source.replace('sqlalchemy.orm.joinedload', '# joinedload')
source = re.sub(
r'\.filter\(sqlalchemy\.text\(f"\{(\w+)\}\s+REGEXP\s+:(\w+)"\)\)\.params\(\2=(\w+)\)',
r'.filter(_regex_filter(\1, \3))',
source
)
source = re.sub(r'sqlalchemy\.text\(.+?\)(?:\.params\(.+?\))?', 'Q()', source)
source = source.replace('.filter_by(', '.filter(')
source = source.replace('.with_entities(', '.values(')
source = source.replace('.ilike(', '.like(')
source = _inject_templateapi_imports(source, filepath)
source = source.replace('from TempLateModule.', '# from TempLateModule.')
source = FLASK_COMPAT_HEADER + "\n" + source
_load_api_module._last_source = source
rel_path = os.path.relpath(filepath, settings.BASE_DIR)
module_name = f"api_dynamic.{rel_path.replace(os.sep, '.').replace('/', '.').replace('.api', '')}"
spec = importlib.util.spec_from_loader(module_name, StringLoader(source, module_name))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def _generate_route(filepath, api_dir, allow_ignore=None):
rel = os.path.relpath(filepath, api_dir)
rel = rel.replace(os.sep, '/')
parts = rel.replace('.api', '').split('/')
if allow_ignore and parts and parts[0] == allow_ignore:
parts = parts[1:]
route = '/' + '/'.join(parts)
return route
def _uuid_to_str(val):
if val is None:
return None
if isinstance(val, str):
return val
if isinstance(val, bytes) and len(val) == 16:
return str(uuid.UUID(bytes=val))
if isinstance(val, uuid.UUID):
return str(val)
return str(val)
def _uuid_to_bytes(val):
if val is None:
return None
if isinstance(val, bytes):
return val
if isinstance(val, str):
try:
return uuid.UUID(val).bytes
except ValueError:
return None
if isinstance(val, uuid.UUID):
return val.bytes
return None
class DjangoFSR:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
self.api_dir = str(Path(settings.BASE_DIR) / "TempLateAPI")
self.blueprint_map = {
"GERP": {"allow_ignore": "GERP"},
"UGM": {"allow_ignore": "UGM"},
"3CRB": {"allow_ignore": "3CRB"},
"Invite": {"allow_ignore": "Invite"},
"CCGO": {"allow_ignore": "CCGO"},
"SVE": {"allow_ignore": "SVE"},
"Verifox": {"allow_ignore": "Verifox"},
"Main": {"allow_ignore": None},
}
self._modules_cache = {}
self._routes = []
def _get_blueprint_config(self, filepath):
rel = os.path.relpath(filepath, self.api_dir)
parts = rel.replace(os.sep, '/').split('/')
if parts:
top_dir = parts[0]
for bp_name, config in self.blueprint_map.items():
if top_dir == config.get("allow_ignore") or top_dir == bp_name:
return bp_name, config
return "Main", {"allow_ignore": None}
def _validate_params(self, request, params_config, source, path_kwargs=None):
cleaned = {}
if not params_config:
return cleaned, None
if callable(params_config):
try:
params_config = params_config(**(path_kwargs or {}))
except Exception:
params_config = []
if not isinstance(params_config, list):
if isinstance(params_config, tuple) and len(params_config) == 2:
resp_body, status_code = params_config
return cleaned, (resp_body, status_code)
if isinstance(params_config, dict):
return cleaned, (params_config, 400)
params_config = []
for param in params_config:
name = param.get("Name", "")
ptype = param.get("Type", "str")
required = param.get("Required", False)
max_len = param.get("MaxLen")
allowed_empty = param.get("AllowedEmpty", False)
pass_empty = param.get("PassEmpty", False)
if source == "form":
raw = request.POST.get(name)
elif source == "args":
raw = request.GET.get(name)
elif source == "json":
try:
body = json.loads(request.body.decode('utf-8')) if request.body else {}
raw = body.get(name)
except Exception:
raw = None
elif source == "headers":
raw = request.META.get(f'HTTP_{name.upper().replace("-", "_")}')
elif source == "cookies":
raw = request.COOKIES.get(name)
else:
raw = None
if raw is None or (isinstance(raw, str) and not raw.strip()):
if pass_empty:
continue
if not required:
continue
if allowed_empty:
cleaned[name] = ""
continue
return cleaned, ({"code": 400, "msg": f"参数 {name} 缺失"}, 400)
if isinstance(raw, str):
raw = raw.strip()
if max_len and isinstance(raw, str) and len(raw) > max_len:
return cleaned, ({"code": 400, "msg": f"参数 {name} 超过最大长度 {max_len}"}, 400)
if ptype == "int":
try:
raw = int(raw)
except (ValueError, TypeError):
return cleaned, ({"code": 400, "msg": f"参数 {name} 类型错误(预期int)"}, 400)
elif ptype == "float":
try:
raw = float(raw)
except (ValueError, TypeError):
return cleaned, ({"code": 400, "msg": f"参数 {name} 类型错误(预期float)"}, 400)
elif ptype == "timestampstr":
import datetime as _dt
try:
if isinstance(raw, str):
for fmt in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S.%f"]:
try:
raw = _dt.datetime.strptime(raw, fmt).strftime("%Y-%m-%d %H:%M:%S")
break
except ValueError:
continue
else:
return cleaned, ({"code": 422, "msg": f"参数 {name} 类型错误(预期timestampstr): 时间字符串格式错误,预期格式: %Y-%m-%d %H:%M:%S,实际值: {raw}"}, 422)
except Exception as e:
return cleaned, ({"code": 422, "msg": f"参数 {name} 类型错误(预期timestampstr): {str(e)}"}, 422)
cleaned[name] = raw
return cleaned, None
def _check_permission(self, request, permission, attributes):
if not permission:
return True
elt_token = request.COOKIES.get('ELToken')
if not elt_token:
return False
try:
from gvsds.apps.core.models import User, EphemeralToken
elt_uuid = _uuid_to_bytes(elt_token)
elt = EphemeralToken.objects.filter(elt_uuid=elt_uuid, elt_status=1).first()
if not elt or elt.is_expired():
return False
user = User.objects.filter(user_uuid=elt.elt_user_uuid).first()
if not user:
return False
request.elt_user = user
request.elt = elt
if permission >= 1 and user.user_account_license != 1:
return False
if attributes and isinstance(attributes, list):
for attr in attributes:
if isinstance(attr, str):
if not user.has_permission(attr):
return False
return True
except Exception:
return False
def _create_django_view(self, filepath, module):
metadata = getattr(module, '__metadata__', {})
route_template = metadata.get("route", "{DEFAULT_ROUTE}")
methods = metadata.get("methods", ["GET"])
blueprint_name = metadata.get("blueprint", "Main")
permission = metadata.get("permission", 0)
attributes = metadata.get("attributes", [])
limit = metadata.get("limit", None)
bp_name, bp_config = self._get_blueprint_config(filepath)
default_route = _generate_route(filepath, self.api_dir, bp_config.get("allow_ignore"))
if "{DEFAULT_ROUTE}" in route_template:
route = route_template.replace("{DEFAULT_ROUTE}", default_route)
else:
route = route_template
fsr = self
def api_view(request, **kwargs):
if request.method == 'OPTIONS':
resp = HttpResponse(status=204)
origin = request.META.get('HTTP_ORIGIN', '')
if origin and '.gvsds.com' in origin:
resp['Access-Control-Allow-Origin'] = origin
resp['Access-Control-Allow-Credentials'] = 'true'
resp['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, DELETE, OPTIONS'
resp['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With'
resp['Access-Control-Max-Age'] = '86400'
return resp
from PDAPI_django import set_current_request as _set_req
compat_request = _CompatRequest(request)
_set_req(compat_request)
try:
current_mtime = os.path.getmtime(filepath)
cache_key = filepath
cached = fsr._modules_cache.get(cache_key)
if cached and cached[1] >= current_mtime:
mod = cached[0]
else:
mod = _load_api_module(filepath, force_reload=True)
fsr._modules_cache[cache_key] = (mod, current_mtime)
meta = getattr(mod, '__metadata__', {})
perm = meta.get("permission", 0)
attrs = meta.get("attributes", [])
if not fsr._check_permission(compat_request, perm, attrs):
return JsonResponse({"code": 403, "msg": "权限不足"}, status=403)
form_params = meta.get("form", [])
args_params = meta.get("args", [])
json_params = meta.get("json", [])
header_params = meta.get("headers", [])
cookie_params = meta.get("cookies", [])
form_data, form_err = fsr._validate_params(compat_request, form_params, "form", kwargs)
if form_err:
return JsonResponse(form_err[0], status=form_err[1])
args_data, args_err = fsr._validate_params(compat_request, args_params, "args", kwargs)
if args_err:
return JsonResponse(args_err[0], status=args_err[1])
json_data, json_err = fsr._validate_params(compat_request, json_params, "json", kwargs)
if json_err:
return JsonResponse(json_err[0], status=json_err[1])
header_data, header_err = fsr._validate_params(compat_request, header_params, "headers", kwargs)
if header_err:
return JsonResponse(header_err[0], status=header_err[1])
cookie_data, cookie_err = fsr._validate_params(compat_request, cookie_params, "cookies", kwargs)
if cookie_err:
return JsonResponse(cookie_err[0], status=cookie_err[1])
cleaned = {
"form": form_data,
"args": args_data,
"json": json_data,
"headers": header_data,
"cookies": cookie_data,
"params": kwargs,
}
handle = getattr(mod, 'HandleRequest', None)
if not handle or not callable(handle):
return JsonResponse({"code": 0, "msg": "ok"})
mod_api_local = getattr(mod, '_api_local', None)
if mod_api_local is not None:
mod_api_local.request = compat_request
mod_api_local.elt_user = getattr(compat_request, 'elt_user', None)
_api_local.request = compat_request
mod.request = compat_request
try:
import inspect
sig = inspect.signature(handle)
if len(sig.parameters) > 0:
result = handle(cleaned)
else:
result = handle()
except _AbortException as abort_exc:
result = abort_exc.response
except Exception as e:
tb = traceback.format_exc()
api_src = getattr(_load_api_module, '_last_source', '')
src_lines = api_src.split('\n') if api_src else []
tb_lines = tb.split('\n')
for line in tb_lines:
if line.strip().startswith('File "<api:'):
import re as _re
m = _re.search(r'line (\d+)', line)
if m:
err_lineno = int(m.group(1))
start = max(0, err_lineno - 3)
end = min(len(src_lines), err_lineno + 3)
tb += f"\n Source context:\n"
for i in range(start, end):
marker = ">>>" if i == err_lineno - 1 else " "
tb += f" {marker} {i+1}: {src_lines[i]}\n"
print(tb)
return JsonResponse({"code": 500, "msg": str(e)}, status=500)
if isinstance(result, tuple):
data, status_code = result[0], result[1] if len(result) > 1 else 200
if isinstance(data, dict):
resp = JsonResponse(data, status=status_code, safe=False)
elif isinstance(data, HttpResponse):
resp = data
else:
resp = HttpResponse(str(data), status=status_code)
elif isinstance(result, dict):
resp = JsonResponse(result, safe=False)
elif isinstance(result, HttpResponse):
resp = result
else:
resp = HttpResponse(str(result))
try:
from PDAPI_django import Cookie
Cookie._apply_to_response(resp)
except Exception:
pass
return resp
except Exception as e:
traceback.print_exc()
return JsonResponse({"code": 500, "msg": str(e)}, status=500)
api_view.methods = methods
return route, api_view
def scan_and_register(self):
urlpatterns = []
api_dir = Path(self.api_dir)
for api_file in api_dir.rglob("*.api"):
rel = api_file.relative_to(api_dir)
parts = str(rel).replace(os.sep, '/').split('/')
if 'Errors' in parts:
continue
if 'copy' in str(rel):
continue
try:
module = _load_api_module(str(api_file))
except SyntaxError as e:
print(f"[FSR] Failed to load {api_file}: {e}")
if e.lineno and hasattr(_load_api_module, '_last_source'):
_lines = _load_api_module._last_source.split('\n')
_start = max(0, e.lineno - 3)
_end = min(len(_lines), e.lineno + 3)
for _i in range(_start, _end):
_marker = ">>>" if _i == e.lineno - 1 else " "
print(f"[FSR] {_marker} {_i+1}: {_lines[_i]}")
continue
except Exception as e:
print(f"[FSR] Failed to load {api_file}: {e}")
continue
metadata = getattr(module, '__metadata__', {})
if not metadata and not hasattr(module, 'HandleRequest'):
continue
route, view_func = self._create_django_view(str(api_file), module)
methods = view_func.methods
route_path = route.lstrip('/')
if route_path:
pattern = path(route_path, view_func)
else:
pattern = path('', view_func)
urlpatterns.append(pattern)
self._routes.append((route, methods, str(api_file)))
print(f"[FSR] Registered {len(urlpatterns)} API routes")
return urlpatterns
def get_fsr_urlpatterns():
fsr = DjangoFSR()
return fsr.scan_and_register()