測試平臺系列(90) 編寫oss客戶端

米洛丶發表於2022-03-10

大家好~我是米洛

我正在從0到1打造一個開源的介面測試平臺, 也在編寫一套與之對應的教程,希望大家多多支援。

歡迎關注我的公眾號米洛的測開日記,獲取最新文章教程!

回顧

上一節我們編寫了線上執行測試計劃功能,並稍微改了下報告頁面。那其實我們之前的內容都是有很多在裡面的。

比如http請求只支援了json和form,沒有支援檔案上傳的請求,甚至有一些crud的功能都沒有太完善。

不過不要緊,我的想法還是先創造,再完善。當然也不是盲目創造,也得提前預判好後面的走向。

為什麼要用oss

pity裡面oss打算用在2個地方,第一個就是一些靜態資源圖片。比方說專案圖片使用者頭像

另一個地方就是上文說的,測試檔案上傳介面的時候,我們需要測試檔案。這些檔案怎麼來,怎麼管理?都得藉助oss來完成。

目前以我熟悉的oss為例,打算支援以下幾種oss:

  • 騰訊雲cos

  • 其中阿里雲和騰訊雲由於都是付費產品,博主還是買不起的。所以可能需要有對應的測試賬號,在此感謝小右(Mini-Right)的幫助,給了我一個阿里雲的測試賬號,保證了crud能正常進行。

    如果需要我實現其他oss客戶端,請帶上對應的測試賬號私信我哈。

    測試網站的資料估計會用gitee或者七牛雲

說明

關於檔案管理這塊,由於時間關係,我暫時不會落一張表與oss資料進行關聯,主要目的是為了節省時間

表關聯可以作為二期工程。

編寫oss基類

新建app.middleware.oss.oss_file.py

由於我們們支援多種oss客戶端,所以包裝好一個抽象類(類似go的interface)等著各個oss客戶端去實現之。

from abc import ABC, abstractmethod
from typing import ByteString


class OssFile(ABC):

    @abstractmethod
    def create_file(self, filepath: str, content: ByteString):
        pass

    @abstractmethod
    def update_file(self, filepath: str, content: ByteString):
        pass

    @abstractmethod
    def delete_file(self, filepath: str):
        pass

    @abstractmethod
    def list_file(self):
        pass

    @abstractmethod
    def download_file(self, filepath):
        pass

抽象類沒有具體的實現,只有方法的定義,目的是為了限制子類的方法,即必須實現基類中定義的方法。

總結了一下,必須有增刪改查下載5種方法。

編寫AliyunOss實現

新建app.middleware.oss.aliyun.py

from typing import ByteString

import oss2

from app.middleware.oss.files import OssFile


class AliyunOss(OssFile):

    def __init__(self, access_key_id: str, access_key_secret: str, endpoint: str, bucket: str):
        auth = oss2.Auth(access_key_id=access_key_id,
                         access_key_secret=access_key_secret)
        # auth = oss2.AnonymousAuth()
        self.bucket = oss2.Bucket(auth, endpoint, bucket)

    def create_file(self, filepath: str, content: ByteString):
        self.bucket.put_object(filepath, content)

    def update_file(self, filepath: str, content: ByteString):
        self.bucket.put_object(filepath, content)

    def delete_file(self, filepath: str):
        self.bucket.delete_object(filepath)

    def list_file(self):
        ans = []
        for obj in oss2.ObjectIteratorV2(self.bucket):
            ans.append(dict(key=obj.key, last_modified=obj.last_modified,
                            size=obj.size, owner=obj.owner))
        return ans

    def download_file(self, filepath):
        if not self.bucket.object_exists(filepath):
            raise Exception(f"oss檔案: {filepath}不存在")
        return self.bucket.get_object(filepath)

AliyunOss繼承了OssFile,構造方法獲取阿里雲的身份資訊,並驗證。最後讀取bucket,這bucket我理解的是一塊區域,你的檔案都儲存在這塊區域裡面,我們就叫他F盤吧。

其他的方法很簡單,基本上是呼叫對應的api,去做crud操作。oss沒有資料夾的概念,都是統一用路徑來儲存檔案地址的,比如:

woody/github.txt

這個檔案路徑可以理解為,woody目錄下的github.txt檔案。

編寫獲取客戶端方法

app.middleware.oss._\_init__.py

from app.core.configuration import SystemConfiguration
from app.middleware.oss.aliyun import AliyunOss
from app.middleware.oss.files import OssFile


class OssClient(object):
    _client = None

    @classmethod
    def get_oss_client(cls) -> OssFile:
        """
        通過oss配置拿到oss客戶端
        :return:
        """
        if OssClient._client is None:
            cfg = SystemConfiguration.get_config()
            oss_config = cfg.get("oss")
            access_key_id = oss_config.get("access_key_id")
            access_key_secret = oss_config.get("access_key_secret")
            bucket = oss_config.get("bucket")
            endpoint = oss_config.get("endpoint")
            if oss_config is None:
                raise Exception("伺服器未配置oss資訊, 請在configuration.json中新增")
            if oss_config.get("type").lower() == "aliyun":
                return AliyunOss(access_key_id, access_key_secret, endpoint, bucket)
            raise Exception("不支援的oss型別")
        return OssClient._client

我們在configuration.json配置oss資訊,接著每次都從OssClient獲取客戶端即可。

我們根據這個型別來判斷oss的型別

編寫後端介面

from fastapi import APIRouter, File, UploadFile

from app.handler.fatcory import PityResponse
from app.middleware.oss import OssClient

router = APIRouter(prefix="/oss")


@router.post("/upload")
async def create_oss_file(filepath: str, file: UploadFile = File(...)):
    try:
        file_content = await file.read()
        # 獲取oss客戶端
        client = OssClient.get_oss_client()
        client.create_file(filepath, file_content)
        return PityResponse.success()
    except Exception as e:
        return PityResponse.failed(f"上傳失敗: {e}")


@router.get("/list")
async def list_oss_file():
    try:
        client = OssClient.get_oss_client()
        files = client.list_file()
        return PityResponse.success(files)
    except Exception as e:
        return PityResponse.failed(f"獲取失敗: {e}")


@router.get("/delete")
async def delete_oss_file(filepath: str):
    try:
        client = OssClient.get_oss_client()
        client.delete_file(filepath)
        return PityResponse.success()
    except Exception as e:
        return PityResponse.failed(f"刪除失敗: {e}")


@router.post("/update")
async def update_oss_file(filepath: str, file: UploadFile = File(...)):
    """
    更新oss檔案,路徑不能變化
    :param filepath:
    :param file:
    :return:
    """
    try:
        client = OssClient.get_oss_client()
        file_content = await file.read()
        client.update_file(filepath, file_content)
        return PityResponse.success()
    except Exception as e:
        return PityResponse.failed(f"刪除失敗: {e}")

方法很簡單,檔案路徑在url引數裡邊,檔案的話,利用fastapi裡面的File和UploadFile皆可獲取到上傳的檔案。

注意,必須先安裝python-multipart庫配合檔案上傳

測試一下

成功讀取到了oss的檔案資訊,但下載介面

好像忘記下載檔案相關內容了,那麼參考我的上一篇FsatApi下載檔案的文章吧。

FastApi下載檔案

或者直接去github檢視原始碼~

今天的內容就介紹到這裡了,下一節卷oss的用途。

相關文章