最近做QT,也涉及到了接口的调用,那我们不能每个接口都去单独写一套数据的解析逻辑,so我按照我的场景抽象了请求和解析数据的逻辑代码,抛砖引玉。

首先我列出我的接口返回的2种数据格式如下:

第一种列表格式:

{
errcode: 0,
data: [
  {
    name: "71右2",
    age: 0,
    gender: 0,
    comment: "",
    measureTime: "2024-08-22T08:30:07.587+00:00",
    fileUrls: "["http:\/\/xxxxx\/mintti-audio\/tradition-data\/2024\/08\/22\/1724315411604063.wav"]",
    ctPicUrls: null,
    patientInfoPicUrls: null,
    ecgPicUrls: null
  },
  {
    name: "71右2",
    age: 0,
    gender: 0,
    comment: "",
    measureTime: "2024-08-22T08:30:07.587+00:00",
    fileUrls: "["http:\/\/xxxxx\/mintti-audio\/tradition-data\/2024\/08\/22\/1724315411604063.wav"]",
    ctPicUrls: null,
    patientInfoPicUrls: null,
    ecgPicUrls: null
  },
  ......  
],
errmsg: "成功"
}

第二种对象格式

{
  "errcode": 0,
  "data": {
    "url": "http://****:8088/aimed/app/storage/newFile/bat656psijl81ys69jed.wav"
  },
  "errmsg": "成功"
}

首先我们定义个基类,里面包含了,解析对象中每个字段的一个方法,

baseEntity.py文件


# 基础实体类
from typing import TypeVar, Type, Dict, Any

T = TypeVar('T', bound='BaseEntity')


class BaseEntity:
    def __init__(self):
        pass

    @classmethod  # 类方法
    def from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
        """从字典动态解析类实例"""
        obj = cls()
        for key, value in data.items():
            # 设置属性
            setattr(obj, key, value)
        return obj

这里就涉及到泛型的概念,python叫协变,TypeVar就是定义个泛型类型,约束对象为BaseEntity。

新建一个列表数据格式的数据实体对象,measureDataModel.py


# 测量数据模型
from dataclasses import dataclass

from com.label.www.model.baseEntity import BaseEntity

@dataclass
class MeasureDataModel(BaseEntity):
    name:str
    age:int
    gender:int
    comment:str
    measureTime:str
    fileUrls:str
    ctPicUrls:str
    patientInfoPicUrls:str
    ecgPicUrls:str
    localFileUrls:str
    
    def __init__(self, name='', age=0, gender=0, comment='', measureTime='', fileUrls='',ctPicUrls='',patientInfoPicUrls='',ecgPicUrls='', localFileUrls=''):
        self.name = name
        self.age = age
        self.gender = gender  # 0:男 1:女
        self.comment = comment
        self.measureTime = measureTime
        self.fileUrls = fileUrls  # 源数据地址
        self.ctPicUrls = ctPicUrls
        self.patientInfoPicUrls = patientInfoPicUrls
        self.ecgPicUrls = ecgPicUrls
        self.localFileUrls = localFileUrls  # 缓存到本地数据
    def __str__(self):
        #输出各个字段的值
        return f"name:{self.name},age:{self.age},gender:{self.gender}," \
               f"comment:{self.comment},measureTime:{self.measureTime}," \
               f"fileUrls:{self.fileUrls},ctPicUrls:{self.ctPicUrls}," \
               f"patientInfoPicUrls:{self.patientInfoPicUrls},ecgPicUrls:{self.ecgPicUrls}," \
               f"localFileUrls:{self.localFileUrls}"

新建一个上传文件返回的 字典数据格式的对象 uploadFileEntity.py

from dataclasses import dataclass

from com.label.www.model.baseEntity import BaseEntity


# 上传数据返回
@dataclass
class UploadFileEntity(BaseEntity):
    url: str

    def __init__(self, url=''):
        self.url = url

    def __str__(self):
        # 输出各个字段的值
        return f"url:{self.url}"

新建返回数据reponse 基础的解析类,responseResultEntity.py


# 返回数据解析实体
from typing import Dict, Any, Type, Generic, TypeVar, Optional

from com.label.www.model.baseEntity import BaseEntity, T


# 定义类型变量
# response 基础的解析
class ResponseResultEntity(BaseEntity):

    def __init__(self):
        super().__init__()
        self.errcode = 0
        self.errmsg = ''
        self.data = Optional[Any]

    @classmethod  # 类方法
    def from_dict_base(cls, data: Dict[str, Any], clsChild: Type[T]) -> T:
        obj = super().from_dict(data)
        #data为列表
        if isinstance(data.get('data'), list):
            obj.data = []
            for i, item in enumerate(data['data']):
                _item = clsChild.from_dict(item)
                obj.data.append(_item)
        #data为字典
        elif isinstance(data.get('data'), dict):
            obj.data = {}
            obj.data = clsChild.from_dict(data['data'])
        #data为其他类型
        else:
            obj.data = data['data']
        return obj

    def __repr__(self):
        return 'ResponseResultEntity(errcode={}, errmsg={}, data={})'.format(self.errcode, self.errmsg, self.data)

基础类创建好后,我们新建一个发起请求的工具类,并单利模式实例化,代码如下,requestUtils.py


import json
import mimetypes
import os
from typing import Type

import requests

from com.label.www.model.baseEntity import BaseEntity, T
from com.label.www.model.measureDataModel import MeasureDataModel
from com.label.www.model.responseResultEntity import ResponseResultEntity
from com.label.www.model.uploadFileEntity import UploadFileEntity
from com.label.www.network.api import host, dataList, file_upload

# 定义 headers
headers = {
    # 'User-Agent': 'MyApp/1.0',
    'Accept-Encoding': 'gzip,deflate,br',
    'Authorization': 'Bearer your_token_here',
    'Content-Type': 'application/json',
}

#统一错误返回
def _error(msg='系统错误', errcode=500):
    responseResultEntity = ResponseResultEntity()
    responseResultEntity.errcode = errcode
    responseResultEntity.errmsg = msg
    responseResultEntity.data = []
    return responseResultEntity


# 请求类url配置
class RequestUtils:
    _instance = None
    _host = ""

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(RequestUtils, cls).__new__(cls)
            cls._host = host
        return cls._instance

    @staticmethod
    def getHeaders():
        # 设置token
        headers['Authorization'] = 'Bearer *******'
        return headers

    @staticmethod
    def get(url, cls: Type[T], params=None) -> ResponseResultEntity:
        url = f"{RequestUtils._host}{url}"
        print(f"请求开始: {url}")
        try:
            response = requests.get(url, params=params, headers=RequestUtils.getHeaders())
            print(f"请求结束: {response.url}")
            if response.status_code != 200:
                return _error(msg=f"请求失败: {response.status_code},{response.text}", errcode=response.status_code)
            # response.raise_for_status()  # 检查请求是否成功
            content = response.content.decode('utf-8-sig')
            # data = response.json()  # 直接使用 response.json() 也可以
            data = json.loads(content)  # 这样确保处理了 BOM
            print(f"data: {data}", type(data))
            return ResponseResultEntity.from_dict_base(data, cls)  # 动态解析为 ResponseEntity 实体
        except requests.exceptions.RequestException as e:
            print(f"请求失败: {e}")
            return _error(msg=str(e))

    @staticmethod
    def post(url, cls: Type[T], params=None, data=None, pfiles=None) -> ResponseResultEntity:
        url = f"{RequestUtils._host}{url}"
        try:
            # timeout 请求超时时间
            response = requests.post(url,
                                     params=params,
                                     json=data,
                                     files=pfiles
                                     # headers=RequestUtils.getHeaders()
                                     )
            print(f"请求url: {response.url}")
            if response.status_code != 200:
                return _error(msg=f"请求失败: {response.status_code},{response.text}", errcode=response.status_code)
            # response.raise_for_status()  # 检查请求是否成功
            content = response.content.decode('utf-8-sig')
            # data = response.json()  # 直接使用 response.json() 也可以
            data = json.loads(content)  # 这样确保处理了 BOM
            print(f"data: {data}", type(data))
            return ResponseResultEntity.from_dict_base(data, cls)  # 动态解析为 ResponseEntity 实体
        except requests.exceptions.RequestException as e:
            print(f"请求失败: {e}")
            return _error(msg=str(e))


if __name__ == '__main__':
    #get请求
    requestUtils = RequestUtils()
    res: ResponseResultEntity = requestUtils.get(dataList, MeasureDataModel, {'source': 2, 'page': 1, 'limit': 1000})
    print(res)

    print("#############")


    # 定义要上传的多个文件
    path = '/Users/*****/Documents/LabelSystem/local-import/2024/10/15/2022-07-06_16-36-42.wav'
    with open(path, 'rb') as f:
        filename = os.path.basename(path)
        # 获取 MIME 类型
        mime_type, _ = mimetypes.guess_type(path)
        print(mime_type)
        files = {'file': (filename, f, mime_type)}
        res2: ResponseResultEntity = requestUtils.post(file_upload, UploadFileEntity, pfiles=files)
        print(res2)

    pass

上传文件中的 host, 接口地址我定义到我的api.py 文件中,结构如下:

#api接口配置

host = "http://192.168.1.55:8088"

#获取数据列表
dataList = "/aimed/client/list"
#文件上传
file_upload = "/aimed/storage/upload/newFile"
#保存标注信息
saveAnnotationDate = "/aimed/annotation/saveAnnotationDate"
#根据标注id查询标注记录
getAnnotationData = "/aimed/annotation/getAnnotationData"

ok,这就是整个抽象的网络请求框架,接下来运行main函数,看下效果:

图片

在请求的时候根据不同接口传递相应的对象 MeasureDataModel 和 UploadFileEntity,解析到相应的数据,是不是这样会简写很多代码。好啦!就这样吧,可以根据自己需求更改相关字段或结构。

作者blog:blog.codeceo.net