20260519
This commit is contained in:
parent
48db31f634
commit
a4c76c576d
|
|
@ -56,7 +56,6 @@ class MySQL:
|
|||
text(sql), connection, coerce_float=False
|
||||
) # 不尝试将非整数数值转为浮点(维持DECIMAL)
|
||||
return dataframe
|
||||
|
||||
except:
|
||||
connection.rollback()
|
||||
raise RuntimeError("执行SQL查询并返回DATAFRAME发生其它异常")
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@
|
|||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
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 pydantic import BaseModel, Field, HttpUrl, model_validator
|
||||
|
|
@ -15,6 +14,9 @@ from requests import Response, Session
|
|||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.append(Path(__file__).parent.as_posix())
|
||||
from restrict import restrict
|
||||
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="查询参数")
|
||||
headers: Optional[Dict[str, Any]] = Field(default=None, description="请求头")
|
||||
data: Optional[Union[str, bytes, Dict[str, Any]]] = Field(
|
||||
|
|
@ -46,25 +50,28 @@ class Parameters(BaseModel):
|
|||
default=None,
|
||||
description="上传文件",
|
||||
)
|
||||
encryption_strategy: Optional[Literal["rsa-aes-gcm"]] = Field(
|
||||
default=None, description="加密策略,默认为不加密"
|
||||
)
|
||||
stream: Optional[bool] = Field(
|
||||
default=None, description="开启流式传输,默认为不开启"
|
||||
)
|
||||
guid: Optional[str] = Field(default=None, description="缓存全局唯一标识")
|
||||
guid: Optional[str] = Field(default=None, description="缓存唯一标识")
|
||||
stream: bool = Field(default=False, description="开启流式传输,默认为不开启")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_data(self):
|
||||
"""校验:data 数据和JSON数据互斥"""
|
||||
def validate_data_json(self):
|
||||
"""校验:data 数据和 JSON 数据不能同时传入"""
|
||||
if self.data and self.json_:
|
||||
raise ValueError("data 数据和 JSON 数据不能同时使用")
|
||||
raise ValueError("data 数据和 JSON 数据不能同时传入")
|
||||
return self
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_files(self):
|
||||
def validate_files_stream(self):
|
||||
"""校验:上传文件和开启流式传输不能同时传入"""
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -130,7 +137,7 @@ class Caches(SQLite):
|
|||
|
||||
def query(self, guid: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
查询并返回单条缓存
|
||||
查询并返回缓存
|
||||
:param guid: 缓存唯一标识
|
||||
:return: 缓存
|
||||
"""
|
||||
|
|
@ -147,12 +154,12 @@ class Caches(SQLite):
|
|||
return None if result is None else json.loads(result["cache"])
|
||||
except Exception as exception:
|
||||
raise RuntimeError(
|
||||
f"查询并获取单条缓存发生异常:{str(exception)}"
|
||||
f"查询并获取缓存发生异常:{str(exception)}"
|
||||
) from exception
|
||||
|
||||
def update(self, guid: str, cache: Dict) -> Optional[bool]:
|
||||
"""
|
||||
新增或更新单条缓存(若无则新增缓存,若有则更新缓存)
|
||||
新增或更新缓存(若无则新增缓存,若有则更新缓存)
|
||||
:param guid: 缓存唯一标识
|
||||
:param cache: 缓存
|
||||
:return: 成功返回True,失败返回False
|
||||
|
|
@ -186,7 +193,6 @@ class Request:
|
|||
total: int = 3,
|
||||
backoff_factor: float = 0.5,
|
||||
timeout: int = 60,
|
||||
encryption_strategy: Optional[Literal["rsa-aes-gcm"]] = None,
|
||||
cache_enabled: bool = False,
|
||||
cache_ttl: int = 360,
|
||||
):
|
||||
|
|
@ -195,7 +201,6 @@ class Request:
|
|||
:param total: 最大重试次数,默认 3
|
||||
:param backoff_factor: 重试间隔退避因子,默认 0.5
|
||||
:param timeout: 超时时间(单位为秒),默认为 60
|
||||
:param encryption_strategy: 加密策略,默认为不加密
|
||||
:param cache_enabled: 使用缓存,默认 False
|
||||
:param cache_ttl: 缓存生存时间(单位为天),默认为 360
|
||||
"""
|
||||
|
|
@ -206,9 +211,6 @@ class Request:
|
|||
# 初始化超时时间
|
||||
self.timeout = timeout
|
||||
|
||||
# 初始化加密策略
|
||||
self.encryption_strategy = encryption_strategy
|
||||
|
||||
# 实例化缓存
|
||||
self.caches = Caches(cache_ttl=cache_ttl * 86400) if cache_enabled else None
|
||||
|
||||
|
|
@ -327,9 +329,9 @@ class Request:
|
|||
if kwargs.get("json"):
|
||||
kwargs["json"] = {k: v for k, v in kwargs["json"].items() if v}
|
||||
|
||||
# 缓存全局唯一标识
|
||||
# 缓存唯一标识
|
||||
guid = kwargs.pop("guid", None)
|
||||
# 若缓存非空且缓存全局唯一标识非空则查询并获取单条缓存
|
||||
# 若缓存非空且缓存唯一标识非空则查询并获取单条缓存
|
||||
if self.caches and guid:
|
||||
cache = self.caches.query(guid)
|
||||
if cache:
|
||||
|
|
@ -348,12 +350,11 @@ class Request:
|
|||
|
||||
# 处理响应对象
|
||||
response = self._process_response(response=response)
|
||||
# 若使用缓存且缓存全局唯一标识非空则新增或更新缓存
|
||||
# 若使用缓存且缓存唯一标识非空则新增或更新缓存
|
||||
if self.caches and guid:
|
||||
self.caches.update(guid, response)
|
||||
|
||||
return response
|
||||
# 重构异常信息
|
||||
except Exception as exception:
|
||||
response = getattr(
|
||||
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():
|
||||
pass
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
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