20260519
This commit is contained in:
parent
48db31f634
commit
a4c76c576d
|
|
@ -56,7 +56,6 @@ class MySQL:
|
||||||
text(sql), connection, coerce_float=False
|
text(sql), connection, coerce_float=False
|
||||||
) # 不尝试将非整数数值转为浮点(维持DECIMAL)
|
) # 不尝试将非整数数值转为浮点(维持DECIMAL)
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
except:
|
except:
|
||||||
connection.rollback()
|
connection.rollback()
|
||||||
raise RuntimeError("执行SQL查询并返回DATAFRAME发生其它异常")
|
raise RuntimeError("执行SQL查询并返回DATAFRAME发生其它异常")
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,8 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
from typing import Any, Callable, Dict, Generator, Literal, Optional, Tuple, Union
|
from typing import Any, Dict, Generator, Literal, Optional, Tuple, Union
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, HttpUrl, model_validator
|
from pydantic import BaseModel, Field, HttpUrl, model_validator
|
||||||
|
|
@ -15,6 +14,9 @@ from requests import Response, Session
|
||||||
from requests.adapters import HTTPAdapter
|
from requests.adapters import HTTPAdapter
|
||||||
from urllib3.util.retry import Retry
|
from urllib3.util.retry import Retry
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append(Path(__file__).parent.as_posix())
|
||||||
from restrict import restrict
|
from restrict import restrict
|
||||||
from sqlite import SQLite
|
from sqlite import SQLite
|
||||||
|
|
||||||
|
|
@ -24,7 +26,9 @@ class Parameters(BaseModel):
|
||||||
请求参数模型
|
请求参数模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url: HttpUrl = Field(default=..., description="统一资源定位符,基于HttpUrl自动校验")
|
url: HttpUrl = Field(
|
||||||
|
default=..., description="统一资源定位符,基于 HttpUrl 自动校验"
|
||||||
|
)
|
||||||
params: Optional[Dict[str, Any]] = Field(default=None, description="查询参数")
|
params: Optional[Dict[str, Any]] = Field(default=None, description="查询参数")
|
||||||
headers: Optional[Dict[str, Any]] = Field(default=None, description="请求头")
|
headers: Optional[Dict[str, Any]] = Field(default=None, description="请求头")
|
||||||
data: Optional[Union[str, bytes, Dict[str, Any]]] = Field(
|
data: Optional[Union[str, bytes, Dict[str, Any]]] = Field(
|
||||||
|
|
@ -46,25 +50,28 @@ class Parameters(BaseModel):
|
||||||
default=None,
|
default=None,
|
||||||
description="上传文件",
|
description="上传文件",
|
||||||
)
|
)
|
||||||
encryption_strategy: Optional[Literal["rsa-aes-gcm"]] = Field(
|
guid: Optional[str] = Field(default=None, description="缓存唯一标识")
|
||||||
default=None, description="加密策略,默认为不加密"
|
stream: bool = Field(default=False, description="开启流式传输,默认为不开启")
|
||||||
)
|
|
||||||
stream: Optional[bool] = Field(
|
|
||||||
default=None, description="开启流式传输,默认为不开启"
|
|
||||||
)
|
|
||||||
guid: Optional[str] = Field(default=None, description="缓存全局唯一标识")
|
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def validate_data(self):
|
def validate_data_json(self):
|
||||||
"""校验:data 数据和JSON数据互斥"""
|
"""校验:data 数据和 JSON 数据不能同时传入"""
|
||||||
if self.data and self.json_:
|
if self.data and self.json_:
|
||||||
raise ValueError("data 数据和 JSON 数据不能同时使用")
|
raise ValueError("data 数据和 JSON 数据不能同时传入")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def validate_files(self):
|
def validate_files_stream(self):
|
||||||
|
"""校验:上传文件和开启流式传输不能同时传入"""
|
||||||
if self.files and self.stream:
|
if self.files and self.stream:
|
||||||
raise ValueError("上传文件和开启流式传输不能同时使用")
|
raise ValueError("上传文件和开启流式传输不能同时传入")
|
||||||
|
return self
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_guid_stream(self):
|
||||||
|
"""校验:缓存唯一标识和开启流式传输不能同时传入"""
|
||||||
|
if self.guid and self.stream:
|
||||||
|
raise ValueError("缓存唯一标识和开启流式传输不能同时传入")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -130,7 +137,7 @@ class Caches(SQLite):
|
||||||
|
|
||||||
def query(self, guid: str) -> Optional[Dict[str, Any]]:
|
def query(self, guid: str) -> Optional[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
查询并返回单条缓存
|
查询并返回缓存
|
||||||
:param guid: 缓存唯一标识
|
:param guid: 缓存唯一标识
|
||||||
:return: 缓存
|
:return: 缓存
|
||||||
"""
|
"""
|
||||||
|
|
@ -147,12 +154,12 @@ class Caches(SQLite):
|
||||||
return None if result is None else json.loads(result["cache"])
|
return None if result is None else json.loads(result["cache"])
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"查询并获取单条缓存发生异常:{str(exception)}"
|
f"查询并获取缓存发生异常:{str(exception)}"
|
||||||
) from exception
|
) from exception
|
||||||
|
|
||||||
def update(self, guid: str, cache: Dict) -> Optional[bool]:
|
def update(self, guid: str, cache: Dict) -> Optional[bool]:
|
||||||
"""
|
"""
|
||||||
新增或更新单条缓存(若无则新增缓存,若有则更新缓存)
|
新增或更新缓存(若无则新增缓存,若有则更新缓存)
|
||||||
:param guid: 缓存唯一标识
|
:param guid: 缓存唯一标识
|
||||||
:param cache: 缓存
|
:param cache: 缓存
|
||||||
:return: 成功返回True,失败返回False
|
:return: 成功返回True,失败返回False
|
||||||
|
|
@ -186,7 +193,6 @@ class Request:
|
||||||
total: int = 3,
|
total: int = 3,
|
||||||
backoff_factor: float = 0.5,
|
backoff_factor: float = 0.5,
|
||||||
timeout: int = 60,
|
timeout: int = 60,
|
||||||
encryption_strategy: Optional[Literal["rsa-aes-gcm"]] = None,
|
|
||||||
cache_enabled: bool = False,
|
cache_enabled: bool = False,
|
||||||
cache_ttl: int = 360,
|
cache_ttl: int = 360,
|
||||||
):
|
):
|
||||||
|
|
@ -195,7 +201,6 @@ class Request:
|
||||||
:param total: 最大重试次数,默认 3
|
:param total: 最大重试次数,默认 3
|
||||||
:param backoff_factor: 重试间隔退避因子,默认 0.5
|
:param backoff_factor: 重试间隔退避因子,默认 0.5
|
||||||
:param timeout: 超时时间(单位为秒),默认为 60
|
:param timeout: 超时时间(单位为秒),默认为 60
|
||||||
:param encryption_strategy: 加密策略,默认为不加密
|
|
||||||
:param cache_enabled: 使用缓存,默认 False
|
:param cache_enabled: 使用缓存,默认 False
|
||||||
:param cache_ttl: 缓存生存时间(单位为天),默认为 360
|
:param cache_ttl: 缓存生存时间(单位为天),默认为 360
|
||||||
"""
|
"""
|
||||||
|
|
@ -206,9 +211,6 @@ class Request:
|
||||||
# 初始化超时时间
|
# 初始化超时时间
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
# 初始化加密策略
|
|
||||||
self.encryption_strategy = encryption_strategy
|
|
||||||
|
|
||||||
# 实例化缓存
|
# 实例化缓存
|
||||||
self.caches = Caches(cache_ttl=cache_ttl * 86400) if cache_enabled else None
|
self.caches = Caches(cache_ttl=cache_ttl * 86400) if cache_enabled else None
|
||||||
|
|
||||||
|
|
@ -327,9 +329,9 @@ class Request:
|
||||||
if kwargs.get("json"):
|
if kwargs.get("json"):
|
||||||
kwargs["json"] = {k: v for k, v in kwargs["json"].items() if v}
|
kwargs["json"] = {k: v for k, v in kwargs["json"].items() if v}
|
||||||
|
|
||||||
# 缓存全局唯一标识
|
# 缓存唯一标识
|
||||||
guid = kwargs.pop("guid", None)
|
guid = kwargs.pop("guid", None)
|
||||||
# 若缓存非空且缓存全局唯一标识非空则查询并获取单条缓存
|
# 若缓存非空且缓存唯一标识非空则查询并获取单条缓存
|
||||||
if self.caches and guid:
|
if self.caches and guid:
|
||||||
cache = self.caches.query(guid)
|
cache = self.caches.query(guid)
|
||||||
if cache:
|
if cache:
|
||||||
|
|
@ -348,12 +350,11 @@ class Request:
|
||||||
|
|
||||||
# 处理响应对象
|
# 处理响应对象
|
||||||
response = self._process_response(response=response)
|
response = self._process_response(response=response)
|
||||||
# 若使用缓存且缓存全局唯一标识非空则新增或更新缓存
|
# 若使用缓存且缓存唯一标识非空则新增或更新缓存
|
||||||
if self.caches and guid:
|
if self.caches and guid:
|
||||||
self.caches.update(guid, response)
|
self.caches.update(guid, response)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
# 重构异常信息
|
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
response = getattr(
|
response = getattr(
|
||||||
exception, "response", None
|
exception, "response", None
|
||||||
|
|
|
||||||
110
安全加密请求/main.py
110
安全加密请求/main.py
|
|
@ -4,7 +4,111 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 列举导入模块
|
# 列举导入模块
|
||||||
import uvicorn
|
from base64 import b64encode
|
||||||
|
from json import dumps
|
||||||
|
from pathlib import Path
|
||||||
|
from secrets import token_bytes
|
||||||
|
from time import time
|
||||||
|
from typing import cast
|
||||||
|
from typing import Any, Dict
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
def request():
|
from cryptography.hazmat.backends import default_backend
|
||||||
pass
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.append(Path(__file__).parent.parent.as_posix())
|
||||||
|
from utils.request import Request
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
加密载荷
|
||||||
|
:param payload: 载荷
|
||||||
|
:return: 加密后载荷
|
||||||
|
"""
|
||||||
|
pem_path = Path(__file__).parent / "rsa_public_key.pem"
|
||||||
|
if not pem_path.exists():
|
||||||
|
raise FileNotFoundError("RSA 公钥 PEM 文件不存在")
|
||||||
|
# RSA 公钥
|
||||||
|
rsa_public_key = pem_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
# 实例 RSA 加密器
|
||||||
|
rsa_encryptor = cast(
|
||||||
|
rsa.RSAPublicKey,
|
||||||
|
load_pem_public_key(
|
||||||
|
data=rsa_public_key.encode("utf-8"), backend=default_backend()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# 生成 AES-256 密钥
|
||||||
|
aes_key = token_bytes(32)
|
||||||
|
# 使用 RSA 公钥加密 AES-256 密钥
|
||||||
|
aes_key_encrypted = rsa_encryptor.encrypt(
|
||||||
|
plaintext=aes_key,
|
||||||
|
padding=padding.OAEP(
|
||||||
|
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
||||||
|
algorithm=hashes.SHA256(),
|
||||||
|
label=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
aes_key_encoded = b64encode(aes_key_encrypted).decode() # BASE64 编码
|
||||||
|
|
||||||
|
# 初始向量
|
||||||
|
iv = token_bytes(12)
|
||||||
|
iv_encoded = b64encode(iv).decode() # BASE64 编码
|
||||||
|
|
||||||
|
# 实例 AES-GCM 加密器
|
||||||
|
aes_gcm_encryptor = Cipher(
|
||||||
|
algorithm=algorithms.AES(aes_key), mode=modes.GCM(iv), backend=default_backend()
|
||||||
|
).encryptor()
|
||||||
|
|
||||||
|
# 时间戳和随机码
|
||||||
|
timestamp, nonce = int(time()), uuid4().hex.lower()
|
||||||
|
# 附加认证
|
||||||
|
aes_gcm_encryptor.authenticate_additional_data(
|
||||||
|
data=f"timestamp={timestamp}&nonce={nonce}".encode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
ciphertext = (
|
||||||
|
aes_gcm_encryptor.update(
|
||||||
|
data=dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||||
|
)
|
||||||
|
+ aes_gcm_encryptor.finalize()
|
||||||
|
)
|
||||||
|
ciphertext_encoded = b64encode(ciphertext).decode() # BASE64 编码
|
||||||
|
|
||||||
|
tag = aes_gcm_encryptor.tag
|
||||||
|
tag_encoded = b64encode(tag).decode() # BASE64 编码
|
||||||
|
|
||||||
|
return {
|
||||||
|
"encryptedAesKey": aes_key_encoded,
|
||||||
|
"iv": iv_encoded,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"nonce": nonce,
|
||||||
|
"encryptedPayload": ciphertext_encoded,
|
||||||
|
"tag": tag_encoded,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
request = Request() # 不使用缓存
|
||||||
|
response = request.post(
|
||||||
|
url="http://192.168.3.103:30380/",
|
||||||
|
headers={
|
||||||
|
"Authorization": "Bearer C52FB4D10BC424D9F",
|
||||||
|
"Content-Type": "application/json;charset=utf-8",
|
||||||
|
},
|
||||||
|
json=encrypt(
|
||||||
|
payload={
|
||||||
|
"productId": "BANK_CARD_4",
|
||||||
|
"name": "刘弼仁",
|
||||||
|
"idNumber": "131002198705020000",
|
||||||
|
"bankCard": "1234567890123456",
|
||||||
|
"phone": "18058798752",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
print(response)
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApafHKXu1IwGJ1iQVT0susJ20MRl/6unJ+YMX6Lx9l43/+THA4VAJ8YQaZMH9+1eFeaFjwM9pT0ee+okBpKRuDWVkH8NxiKza4ohNHEtSUU37N3h8BEUBJHx74/6pDK8DwPsp4LzGhnUPof5/ZMqW2phqTrEYRwYOKGLYNnScNKkOWkQMSrUxGBkcMs1VsePChe0cJqrC03CTHJ/NRSyc62HqDZLp6G+DaR3VgWkhvhT4sN/3j/n9Ml5io/8I5ooPfBrYPNCfOaVqFWtqHqoG0uMLeROa8edqk6ZBBv2mERPllOG43f91FNmg/kF8XHij1LBNcf1WbO7rHg0Vo71j+wIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
Loading…
Reference in New Issue