禁用请求签名验证,解决XAmzContentSHA256Mismatch错误 强制 unsigned payload

import boto3
from botocore.client import Config
from botocore.awsrequest import AWSRequest
from botocore.auth import SigV4Auth
from botocore.exceptions import ClientError
import requests
import sys
import os
import urllib3
from PIL import Image
import io
import hashlib
import urllib.parse
import xmltodict
from datetime import datetime, timezone

# ---------------- 配置参数 ----------------
# S3服务端点配置
# 在Ceph中配置方法:
# 1. S3_ENDPOINT: 在ceph.conf中配置rgw_frontends
#    [client.rgw.gateway]
#    rgw_frontends = "civetweb port=7480"
#    或使用其他前端如nginx:
#    rgw_frontends = "beast port=7480"
#
# 2. ADMIN_ENDPOINT: 在ceph.conf中配置rgw_admin_entry
#    [client.rgw.gateway]
#    rgw_admin_entry = "/admin"
#
# 3. STS配置: 在ceph.conf中配置STS相关参数
#    [client.rgw.gateway]
#    rgw_sts_key = "your-sts-key"  # STS密钥
#    rgw_sts_token_ttl = 3600      # 令牌有效期(秒)
#    rgw_sts_max_session_duration = 43200  # 最大会话持续时间(秒)
#
# 4. STS配置命令:
#    # 创建STS用户
#    radosgw-admin user create --uid=sts-user --display-name="STS User" \
#    --access-key=your-access-key --secret-key=your-secret-key
#
#    # 配置STS策略
#    radosgw-admin user policy put --uid=sts-user --policy-name=sts-policy \
#    --policy-doc='{
#      "Version": "2012-10-17",
#      "Statement": [{
#        "Effect": "Allow",
#        "Action": ["sts:GetSessionToken"],
#        "Resource": "*"
#      }]
#    }'
#
#    # 重启RGW服务
#    systemctl restart ceph-radosgw@rgw.$(hostname -s)
#
# 注意: 如果使用HTTPS,需要额外配置SSL证书
#    rgw_frontends = "civetweb port=7480s ssl_certificate=/path/to/cert.pem"

# ETag说明:
# ETag (Entity Tag) 是HTTP协议中用于缓存验证的机制,在S3/Ceph中具有特殊用途:
# 1. 对象标识:每个对象的唯一标识符,通常是对象内容的MD5哈希值
# 2. 并发控制:用于实现乐观锁,防止并发修改冲突
# 3. 缓存验证:用于验证本地缓存的对象是否与服务器上的对象一致
# 4. 分片上传:在分片上传中,每个分片都有自己的ETag,用于验证分片完整性
# 5. 格式说明:
#    - 普通对象:通常是32位十六进制字符串,如 "d41d8cd98f00b204e9800998ecf8427e"
#    - 分片对象:可能包含额外的信息,如分片号
# 6. 使用场景:
#    - 上传验证:确保上传的对象完整性
#    - 下载验证:验证下载的对象是否完整
#    - 条件操作:用于条件性更新或删除操作
#    - 分片上传:验证分片上传的完整性
# 7. 注意事项:
#    - ETag值区分大小写
#    - 不同存储系统可能使用不同的ETag生成算法
#    - 某些情况下ETag可能不反映对象的最新状态

S3_ENDPOINT     = "https://xxxxx"  # S3服务端点,用于普通S3操作(上传、下载等)
ADMIN_ENDPOINT  = "https://xxxxx"  # 管理接口端点,用于管理操作(配额查询等)

# 认证配置
REGION          = "us-east-1"                                         # 区域设置
AK              = "xxxxx"                                # 访问密钥ID
SK              = "xxxxx"                 # 秘密访问密钥

# 存储桶配置
BUCKET          = "sample-file2327"                                        # 存储桶名称
NORMAL_KEY      = "test-object-initial.txt"                           # 普通上传对象键名
MULTIPART_KEY   = "test-object-multipart.txt"                         # 分片上传对象键名
TEST_KEY        = "permission_test_object.txt"                        # 权限测试对象键名,用于测试对象操作权限

# 文件检查配置
# 1. 检查模式配置
CHECK_MODE = {
    'mode': 'specific',  # 可选值: 'all'(检查所有文件), 'specific'(检查指定文件)
    'options': {
        # 当 mode='specific' 时使用
        'specific_files': [
            "test-object-initial.txt",
            "test-object-multipart.txt"
        ],
        'check_metadata': True,  # 是否检查元数据
        'check_content_type': True,  # 是否检查ContentType
        'check_extra_args': True,  # 是否检查ExtraArgs
        'output_format': 'console',  # 输出格式:'console'(控制台), 'json'(JSON文件), 'csv'(CSV文件)
        'output_file': 'check_results.json'  # 输出文件名,当output_format不是'console'时使用
    }
}

# 全局检查控制配置
# 控制哪些检查功能需要执行,设置为False则跳过该检查
CHECK_CONTROLS = {
    'check_upload_initial': True,        # 检查预签名PUT上传初始对象
    'check_bucket_config': True,         # 检查存储桶配置信息
    'check_content_type': False,         # 检查ContentType支持情况(默认不检查,耗时较长)
    'check_extra_args': False,           # 检查ExtraArgs支持情况(默认不检查,耗时较长)
    'check_object_stats': False,         # 检查对象统计信息(默认不检查,可能耗时)
    'check_object_encoding': True,       # 检查对象编码信息
    'check_thumbnail_support': True,     # 检查缩略图支持信息
    'check_object_permissions': True,    # 检查对象操作权限
    'check_presigned_get': True,         # 检查预签名GET URL
    'check_multipart_upload': True,      # 检查分片上传功能
    'check_thumbnail_functionality': True, # 检查缩略图功能
    'check_download': True,              # 检查下载功能
    'check_migrated_files': True         # 检查已迁移文件信息
}

# 测试数据配置
TEST_DATA_MODE = "file"  # 可选值: "file"(从文件读取), "bytes"(直接使用字节数据)
TEST_DATA = {
    'initial': {
        'file': "test_data_initial.txt",  # 初始测试数据文件路径,用于测试普通上传
        'bytes': b"initial content\n"     # 初始测试数据字节,当TEST_DATA_MODE="bytes"时使用
    },
    'multipart': {
        'file': "test_data_multipart.txt",  # 分片上传测试数据文件路径,用于测试分片上传
        'bytes': b"hello presign part\n"    # 分片上传测试数据字节,当TEST_DATA_MODE="bytes"时使用
    }
}

# 分片上传配置
MULTIPART_CHUNK_SIZE = 5 * 1024 * 1024  # 分片大小 5MB (AWS S3最小要求)

# 上传方式说明:
# 1. initial (普通上传):
#    - 适用于小文件上传(建议<5MB)
#    - 一次性上传整个文件
#    - 使用 put_object 操作
#    - 优点:简单直接,实现容易
#    - 缺点:不适合大文件,不支持断点续传
#
# 2. multipart (分片上传):
#    - 适用于大文件上传(建议>5MB)
#    - 将文件分成多个部分上传
#    - 使用 create_multipart_upload、upload_part、complete_multipart_upload 操作
#    - 分片大小说明:
#      * 最小分片大小: 5MB (AWS S3标准)
#      * 最大分片大小: 5GB
#      * 推荐分片大小: 8MB (平衡上传效率和内存使用)
#      * 最大分片数量: 10000
#    - 优点:
#      * 支持大文件上传
#      * 支持断点续传
#      * 可以并行上传分片
#      * 上传失败时可以只重传失败的分片
#    - 缺点:实现相对复杂,需要维护上传状态

# 预签名URL配置
URL_EXPIRATION  = 300                              # 预签名URL过期时间(秒)

# SSL证书验证配置
SSL_VERIFY_MODE = "default"  # 可选值: "default"(使用系统CA), "custom"(使用私有CA), "disable"(禁用验证)
CA_CERT_PATH    = "QAX-ATX-CA.crt"                # 私有CA证书路径,仅在SSL_VERIFY_MODE="custom"时使用

# 缩略图配置
THUMBNAIL_SIZES = [
    (100, 100),  # 小图
    (200, 200),  # 中图
    (400, 400)   # 大图
]
THUMBNAIL_QUALITY = 85  # JPEG质量
THUMBNAIL_FORMAT = 'JPEG'  # 缩略图格式

# 测试图片配置
TEST_IMAGE_PATH = "test_image.jpg"  # 测试图片路径,如果不存在会自动生成
TEST_IMAGE_SIZE = (800, 600)  # 测试图片尺寸 (宽, 高)

# --------- 初始化boto3会话和S3客户端 ---------
# 创建AWS会话,配置访问凭证
session = boto3.Session(
    aws_access_key_id=AK,
    aws_secret_access_key=SK,
    region_name=REGION
)

# 配置SSL验证
def configure_ssl_verification():
    """
    配置SSL证书验证
    返回: (verify_value, warning_message)
    verify_value: True(系统CA), False(禁用验证), 或证书路径(私有CA)
    warning_message: 配置相关的警告信息
    """
    if SSL_VERIFY_MODE == "disable":
        # 禁用SSL警告
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        return False, "警告: SSL验证已禁用,仅用于测试环境"
    
    elif SSL_VERIFY_MODE == "custom":
        if not CA_CERT_PATH:
            return True, "警告: 未指定私有CA证书路径,将使用系统默认证书"
        if not os.path.exists(CA_CERT_PATH):
            return True, f"警告: 未找到CA证书文件 {CA_CERT_PATH},将使用系统默认证书"
        return CA_CERT_PATH, f"使用私有CA证书: {CA_CERT_PATH}"
    
    else:  # "default"
        return True, "使用系统默认证书"

# 获取SSL验证配置
verify_value, warning_message = configure_ssl_verification()
print(warning_message)

# 配置S3客户端
s3_config = Config(
    signature_version='s3v4',
    retries={'max_attempts': 3},
    # 禁用请求签名验证,解决XAmzContentSHA256Mismatch错误
    s3={'addressing_style': 'path', 'payload_signing_enabled': False}
)

# 创建S3客户端,配置端点URL和签名版本
s3 = session.client(
    's3',
    endpoint_url=S3_ENDPOINT,
    config=s3_config,
    verify=verify_value
)

# 创建requests会话,配置CA证书验证
http_session = requests.Session()
http_session.verify = verify_value

# 创建一个不使用签名验证的S3客户端,用于权限检查
def create_unsigned_s3_client():
    """
    创建一个不使用签名验证的S3客户端,用于解决XAmzContentSHA256Mismatch错误
    """
    # 配置不使用签名验证的S3客户端
    unsigned_config = Config(
        signature_version='s3v4',
        retries={'max_attempts': 3},
        s3={
            'addressing_style': 'path',
            'payload_signing_enabled': False,
            'use_accelerate_endpoint': False,
            'use_dualstack_endpoint': False
        }
    )
    
    # 创建不使用签名验证的S3客户端
    return session.client(
        's3',
        endpoint_url=S3_ENDPOINT,
        config=unsigned_config,
        verify=verify_value
    )

# 创建一个不使用签名验证的S3客户端
unsigned_s3 = create_unsigned_s3_client()

# 添加make_s3_request函数
def make_s3_request(method, bucket, key, data=None, headers=None, params=None):
    """
    直接向S3服务发送HTTP请求
    
    参数:
        method: HTTP方法 (GET, PUT, HEAD, DELETE等)
        bucket: 存储桶名称
        key: 对象键名
        data: 请求体数据 (可选)
        headers: 请求头 (可选)
        params: URL参数 (可选)
        
    返回:
        requests.Response对象
    """
    # 构建URL
    url = f"{S3_ENDPOINT}/{bucket}"
    if key:
        url = f"{url}/{key}"
    
    # 计算请求体的SHA256哈希值
    body = data if data else b""
    content_sha256 = hashlib.sha256(body).hexdigest()
    
    # 创建AWS请求对象
    aws_req = AWSRequest(
        method=method,
        url=url,
        data=body
    )
    
    # 添加请求头
    if headers:
        for header_name, header_value in headers.items():
            aws_req.headers[header_name] = header_value
    
    # 添加Content-SHA256头
    aws_req.headers['x-amz-content-sha256'] = content_sha256
    
    # 添加认证信息
    auth = SigV4Auth(session.get_credentials(), 's3', REGION)
    auth.add_auth(aws_req)
    
    # 准备请求
    prep = aws_req.prepare()
    
    # 确保所有必要的认证头都被包含
    auth_headers = dict(prep.headers)
    if 'Authorization' not in auth_headers:
        # 如果Authorization头丢失,重新添加认证
        auth.add_auth(aws_req)
        prep = aws_req.prepare()
        auth_headers = dict(prep.headers)
    
    # 发送请求
    return http_session.request(
        method=prep.method,
        url=prep.url,
        headers=auth_headers,
        data=prep.body,
        params=params if params else {}
    )

# --------- 1. 存储桶和对象配置查询函数 ---------
def query_acl(bucket):
    """
    查询存储桶的访问控制列表(ACL)
    返回存储桶的ACL配置信息
    """
    return s3.get_bucket_acl(Bucket=bucket)

def check_supported_content_types(bucket, test_key_prefix="content_type_test"):
    """
    检查Ceph支持的ContentType类型
    
    功能说明:
    1. 测试各种ContentType的上传和存储支持情况
    2. 验证Ceph是否正确保存和返回ContentType信息
    3. 检查不同类型内容的处理能力
    
    参数:
        bucket: 存储桶名称
        test_key_prefix: 测试用的对象键名前缀,用于生成唯一的测试对象名
    
    返回:
        字典格式的结果,包含每种ContentType的测试结果:
        {
            'content_type': {
                'supported': bool,      # 是否支持
                'stored_type': str,     # 实际存储的类型
                'note': str,            # 说明信息
                'error': str            # 错误信息(如果有)
            },
            ...
        }
    
    测试的ContentType类型说明:
    1. 文本类型 (Text Types):
       - text/plain: 纯文本文件,如.txt文件
       - text/html: HTML文档,用于网页内容
       - text/css: CSS样式表
       - text/javascript: JavaScript代码
       - text/xml: XML文档
       - text/csv: CSV文件,表格数据
       - text/markdown: Markdown文档
    
    2. 图片类型 (Image Types):
       - image/jpeg: JPEG图片,有损压缩
       - image/png: PNG图片,无损压缩,支持透明度
       - image/gif: GIF图片,支持简单动画
       - image/webp: WebP图片,现代图像格式
       - image/svg+xml: SVG矢量图
    
    3. 音频类型 (Audio Types):
       - audio/mpeg: MP3音频
       - audio/wav: WAV音频,无损格式
       - audio/ogg: OGG音频,开放格式
       - audio/webm: WebM音频
    
    4. 视频类型 (Video Types):
       - video/mp4: MP4视频,常用格式
       - video/webm: WebM视频,开放格式
       - video/ogg: OGG视频
       - video/quicktime: QuickTime视频
    
    5. 应用类型 (Application Types):
       - application/json: JSON数据
       - application/xml: XML文档
       - application/pdf: PDF文档
       - application/zip: ZIP压缩文件
       - application/x-rar-compressed: RAR压缩文件
       - application/x-7z-compressed: 7Z压缩文件
       - application/x-tar: TAR归档文件
       - application/gzip: GZIP压缩文件
       - application/x-www-form-urlencoded: URL编码的表单数据
       - application/octet-stream: 二进制数据流
    
    6. 字体类型 (Font Types):
       - font/ttf: TrueType字体
       - font/woff: WOFF字体
       - font/woff2: WOFF2字体
    
    7. 其他类型 (Other Types):
       - multipart/form-data: 多部分表单数据
       - message/rfc822: RFC822邮件格式
       - chemical/x-mdl-molfile: MDL Molfile格式
    
    测试流程:
    1. 对每种ContentType:
       - 生成测试数据
       - 上传到Ceph
       - 获取对象元数据
       - 验证ContentType是否正确保存
       - 清理测试对象
    
    2. 结果分析:
       - 检查上传是否成功
       - 验证存储的ContentType是否匹配
       - 记录任何错误或异常情况
    
    注意事项:
    1. 测试数据会根据ContentType生成合适的格式
    2. 所有测试对象会在测试后自动清理
    3. 错误处理确保单个测试失败不影响其他测试
    4. 支持详细的错误信息记录
    """
    # 定义要测试的ContentType类型
    content_types = {
        # 文本类型 (Text Types)
        'text/plain': {
            'data': b'Plain text content',
            'desc': '纯文本文件,如.txt文件,不包含任何格式化标记'
        },
        'text/html': {
            'data': b'<html><body>HTML content</body></html>',
            'desc': 'HTML文档,用于网页内容,包含标记语言'
        },
        'text/css': {
            'data': b'body { color: black; }',
            'desc': 'CSS样式表,用于定义HTML元素的样式'
        },
        'text/javascript': {
            'data': b'console.log("JavaScript");',
            'desc': 'JavaScript代码,用于网页交互和动态功能'
        },
        'text/xml': {
            'data': b'<?xml version="1.0"?><root>XML content</root>',
            'desc': 'XML文档,用于结构化数据存储和交换'
        },
        'text/csv': {
            'data': b'name,age\nJohn,30',
            'desc': 'CSV文件,用于表格数据,以逗号分隔的值'
        },
        'text/markdown': {
            'data': b'# Markdown content',
            'desc': 'Markdown文档,用于简单格式化的文本'
        },
        
        # 图片类型 (Image Types)
        'image/jpeg': {
            'data': b'\xFF\xD8\xFF',
            'desc': 'JPEG图片,有损压缩的图像格式,适合照片'
        },
        'image/png': {
            'data': b'\x89PNG\r\n\x1a\n',
            'desc': 'PNG图片,无损压缩的图像格式,支持透明度'
        },
        'image/gif': {
            'data': b'GIF87a',
            'desc': 'GIF图片,支持简单动画和透明度'
        },
        'image/webp': {
            'data': b'RIFF....WEBP',
            'desc': 'WebP图片,Google开发的现代图像格式,提供更好的压缩率'
        },
        'image/svg+xml': {
            'data': b'<?xml version="1.0"?><svg>SVG content</svg>',
            'desc': 'SVG矢量图,可缩放的矢量图形格式'
        },
        
        # 音频类型 (Audio Types)
        'audio/mpeg': {
            'data': b'ID3',
            'desc': 'MP3音频,常用的有损压缩音频格式'
        },
        'audio/wav': {
            'data': b'RIFF....WAVE',
            'desc': 'WAV音频,无损音频格式,常用于专业音频'
        },
        'audio/ogg': {
            'data': b'OggS',
            'desc': 'OGG音频,开放的有损压缩音频格式'
        },
        'audio/webm': {
            'data': b'\x1A\x45\xDF\xA3',
            'desc': 'WebM音频,Google开发的开放媒体格式'
        },
        
        # 视频类型 (Video Types)
        'video/mp4': {
            'data': b'\x00\x00\x00\x18ftyp',
            'desc': 'MP4视频,常用的视频容器格式,支持H.264编码'
        },
        'video/webm': {
            'data': b'\x1A\x45\xDF\xA3',
            'desc': 'WebM视频,开放的视频容器格式,支持VP8/VP9编码'
        },
        'video/ogg': {
            'data': b'OggS',
            'desc': 'OGG视频,开放的视频容器格式'
        },
        'video/quicktime': {
            'data': b'\x00\x00\x00\x14moov',
            'desc': 'QuickTime视频,Apple开发的视频容器格式'
        },
        
        # 应用类型 (Application Types)
        'application/json': {
            'data': b'{"key": "value"}',
            'desc': 'JSON数据,用于结构化数据交换'
        },
        'application/xml': {
            'data': b'<?xml version="1.0"?><root>XML content</root>',
            'desc': 'XML文档,用于结构化数据存储'
        },
        'application/pdf': {
            'data': b'%PDF-1.4',
            'desc': 'PDF文档,用于固定布局的文档格式'
        },
        'application/zip': {
            'data': b'PK\x03\x04',
            'desc': 'ZIP压缩文件,常用的压缩格式'
        },
        'application/x-rar-compressed': {
            'data': b'Rar!\x1a\x07',
            'desc': 'RAR压缩文件,WinRAR专有压缩格式'
        },
        'application/x-7z-compressed': {
            'data': b'7z\xbc\xaf\x27\x1c',
            'desc': '7Z压缩文件,高压缩率的开放格式'
        },
        'application/x-tar': {
            'data': b'',
            'desc': 'TAR归档文件,Unix系统的标准归档格式'
        },
        'application/gzip': {
            'data': b'\x1f\x8b\x08',
            'desc': 'GZIP压缩文件,常用的单文件压缩格式'
        },
        'application/x-www-form-urlencoded': {
            'data': b'key1=value1&key2=value2',
            'desc': 'URL编码的表单数据,用于HTTP请求'
        },
        'application/octet-stream': {
            'data': b'Binary content',
            'desc': '二进制数据流,通用二进制文件类型'
        },
        
        # 字体类型 (Font Types)
        'font/ttf': {
            'data': b'\x00\x01\x00\x00',
            'desc': 'TrueType字体,常用的字体格式'
        },
        'font/woff': {
            'data': b'wOFF',
            'desc': 'WOFF字体,Web开放字体格式'
        },
        'font/woff2': {
            'data': b'wOF2',
            'desc': 'WOFF2字体,改进的Web开放字体格式'
        },
        
        # 其他类型 (Other Types)
        'multipart/form-data': {
            'data': b'--boundary\r\nContent-Type: text/plain\r\n\r\ncontent\r\n--boundary--',
            'desc': '多部分表单数据,用于文件上传'
        },
        'message/rfc822': {
            'data': b'From: sender@example.com\r\nTo: recipient@example.com\r\n\r\nEmail content',
            'desc': 'RFC822邮件格式,标准电子邮件格式'
        },
        'chemical/x-mdl-molfile': {
            'data': b'\n\n\n  0  0  0  0  0  0  0  0  0  0999 V3000\nM  V30 BEGIN CTAB\nM  V30 END CTAB\nM  END\n',
            'desc': 'MDL Molfile格式,用于化学结构数据'
        }
    }
    
    results = {}
    
    try:
        print("\n测试ContentType支持情况:")
        for content_type, type_info in content_types.items():
            try:
                # 构建测试对象的键名
                test_key = f"{test_key_prefix}_{content_type.replace('/', '_')}"
                
                # 构建请求头
                headers = {'Content-Type': content_type}
                
                # 尝试上传对象
                response = make_s3_request(
                    method='PUT',
                    bucket=bucket,
                    key=test_key,
                    data=type_info['data'],
                    headers=headers
                )
                
                # 检查上传是否成功
                if response.status_code not in (200, 201):
                    results[content_type] = {
                        'supported': False,
                        'error': f'HTTP {response.status_code}',
                        'note': f'不支持: HTTP {response.status_code} - {type_info["desc"]}'
                    }
                    continue
                
                # 获取对象元数据以验证ContentType
                head_response = make_s3_request(
                    method='HEAD',
                    bucket=bucket,
                    key=test_key
                )
                
                # 检查ContentType是否匹配
                stored_content_type = head_response.headers.get('Content-Type', '')
                if stored_content_type == content_type:
                    results[content_type] = {
                        'supported': True,
                        'stored_type': stored_content_type,
                        'note': f'完全支持 - {type_info["desc"]}'
                    }
                else:
                    results[content_type] = {
                        'supported': True,
                        'stored_type': stored_content_type,
                        'note': f'支持但存储类型不同 (存储为: {stored_content_type}) - {type_info["desc"]}'
                    }
                    
            except Exception as e:
                results[content_type] = {
                    'supported': False,
                    'error': str(e),
                    'note': f'不支持: {str(e)} - {type_info["desc"]}'
                }
            
            # 清理测试对象
            try:
                make_s3_request(
                    method='DELETE',
                    bucket=bucket,
                    key=test_key
                )
            except:
                pass
        
        return results
        
    except Exception as e:
        print(f"检查ContentType支持时出错: {e}")
        return None

def check_supported_extra_args(bucket, test_key="extra_args_test.txt"):
    """
    检查Ceph支持的ExtraArgs参数
    
    功能说明:
    1. 测试S3上传时支持的各种额外参数
    2. 验证参数是否正确应用和保存
    3. 检查Ceph对S3标准参数的支持程度
    
    参数:
        bucket: 存储桶名称
        test_key: 测试用的对象键名
    
    返回:
        字典格式的结果,包含每个参数的测试结果:
        {
            'parameter_name': {
                'supported': bool,      # 是否支持
                'value': any,         # 实际值
                'note': str,          # 说明信息
                'error': str          # 错误信息(如果有)
            },
            ...
        }
    
    测试的参数说明:
    1. 内容相关参数:
       - ContentType: 指定对象的内容类型
         作用:告诉浏览器或客户端如何处理文件内容
         场景:
         * 网页文件上传时指定文件类型
         * 确保文件以正确的方式打开或显示
         * 控制文件的MIME类型
         示例:'text/plain', 'image/jpeg', 'application/pdf'
       
       - ContentEncoding: 内容编码方式
         作用:指定内容的压缩或编码方式
         场景:
         * 传输压缩文件(如gzip)
         * 优化传输大小
         * 指定特殊编码格式
         示例:'gzip', 'deflate', 'br'
       
       - ContentLanguage: 内容语言
         作用:指定内容的语言
         场景:
         * 多语言网站内容
         * 文档的语言标识
         * 搜索引擎优化
         示例:'zh-CN', 'en-US', 'ja-JP'
       
       - CacheControl: 缓存控制指令
         作用:控制浏览器和CDN的缓存行为
         场景:
         * 静态资源缓存策略
         * CDN缓存配置
         * 内容更新控制
         示例:'max-age=3600', 'no-cache', 'public'
       
       - ContentDisposition: 内容展示方式
         作用:控制浏览器如何处理文件
         场景:
         * 强制下载而不是预览
         * 指定下载文件名
         * 控制文件展示方式
         示例:'attachment; filename="file.pdf"'
       
       - ContentMD5: 内容的MD5校验值
         作用:验证文件完整性
         场景:
         * 大文件传输验证
         * 确保文件未被修改
         * 断点续传校验
         示例:'d41d8cd98f00b204e9800998ecf8427e'
       
       - ContentLength: 内容长度
         作用:指定内容的字节大小
         场景:
         * 预分配存储空间
         * 传输进度显示
         * 断点续传
         示例:1024, 1048576 (1MB)
    
    2. 元数据参数:
       - Metadata: 自定义元数据
         作用:存储对象的自定义属性
         场景:
         * 存储文件属性(如作者、创建时间)
         * 业务相关标签
         * 文件分类信息
         * 版本控制信息
         测试值类型:
         - 基本键值对:'key': 'value'
         - 数字值:'size': '1024'
         - 布尔值:'processed': 'true'
         - 特殊字符:'tags': '!@#$%^&*()'
         - Unicode字符:'description': '测试中文'
         - 空值:'comment': ''
         - 长字符串:'content': 'x' * 1000
    
    3. 存储相关参数:
       - ACL: 访问控制列表
         作用:控制对象的访问权限
         场景:
         * 设置对象公开访问
         * 限制特定用户访问
         * 设置对象所有权
         示例:'private', 'public-read', 'public-read-write'
       
       - StorageClass: 存储类型
         作用:指定对象的存储类别
         场景:
         * 成本优化存储
         * 性能优化存储
         * 归档存储
         示例:'STANDARD', 'STANDARD_IA', 'GLACIER'
       
       - ServerSideEncryption: 服务器端加密
         作用:在服务器端加密存储数据
         场景:
         * 敏感数据存储
         * 合规要求
         * 数据安全保护
         示例:'AES256', 'aws:kms'
    
    4. 其他参数:
       - WebsiteRedirectLocation: 网站重定向位置
         作用:设置对象访问时的重定向URL
         场景:
         * 网站重定向
         * 临时文件访问
         * 资源迁移
         示例:'/new-location', 'https://example.com'
       
       - Expires: 过期时间
         作用:设置对象的过期时间
         场景:
         * 临时文件管理
         * 自动清理策略
         * 内容生命周期管理
         示例:'Thu, 01 Dec 2024 16:00:00 GMT'
       
       - Tagging: 对象标签
         作用:为对象添加标签
         场景:
         * 成本分配
         * 资源管理
         * 权限控制
         * 自动化操作
         示例:'key1=value1&key2=value2'
    
    测试流程:
    1. 对每个参数:
       - 构建适当的请求头
       - 上传测试对象
       - 验证参数是否生效
       - 清理测试对象
    
    2. 特殊处理:
       - Metadata参数使用URL编码处理Unicode字符
       - 验证响应头中的参数值
       - 检查参数是否正确应用
    
    3. 结果分析:
       - 检查上传是否成功
       - 验证参数是否正确保存
       - 记录任何错误或异常
    
    注意事项:
    1. 所有测试对象会在测试后自动清理
    2. 错误处理确保单个测试失败不影响其他测试
    3. 支持详细的错误信息记录
    4. 对Unicode字符进行特殊处理
    5. 验证参数的实际效果而不仅仅是接受参数
    """
    # 定义要测试的ExtraArgs参数
    test_args = {
        'ContentType': 'text/plain',
        'ContentEncoding': 'gzip',
        'ContentLanguage': 'zh-CN',
        'CacheControl': 'max-age=3600',
        'ContentDisposition': 'attachment; filename="test.txt"',
        'Metadata': {
            'test-key': 'test-value',
            'custom-field': 'custom-value',
            'number': '123',
            'boolean': 'true',
            'special-chars': '!@#$%^&*()',
            'unicode': '测试中文',
            'empty': '',
            'long-value': 'x' * 1000  # 测试长值
        },
        'ACL': 'private',
        'StorageClass': 'STANDARD',
        'ServerSideEncryption': 'AES256',
        'WebsiteRedirectLocation': '/redirect',
        'ContentMD5': 'd41d8cd98f00b204e9800998ecf8427e',
        'Expires': 'Thu, 01 Dec 2024 16:00:00 GMT',
        'ContentLength': 1024,
        'Tagging': 'key1=value1&key2=value2'
    }
    
    results = {}
    test_data = b"Test data for ExtraArgs testing"
    
    try:
        # 测试上传对象时的ExtraArgs
        print("\n测试上传对象时的ExtraArgs支持:")
        for arg_name, arg_value in test_args.items():
            try:
                # 构建请求头
                headers = {}
                
                # 根据参数类型设置请求头
                if arg_name == 'ContentType':
                    headers['Content-Type'] = arg_value
                elif arg_name == 'ContentEncoding':
                    headers['Content-Encoding'] = arg_value
                elif arg_name == 'ContentLanguage':
                    headers['Content-Language'] = arg_value
                elif arg_name == 'CacheControl':
                    headers['Cache-Control'] = arg_value
                elif arg_name == 'ContentDisposition':
                    headers['Content-Disposition'] = arg_value
                elif arg_name == 'Metadata':
                    # 元数据需要特殊处理,每个键值对都需要添加x-amz-meta-前缀
                    # 对Unicode字符进行URL编码
                    for meta_key, meta_value in arg_value.items():
                        # 对键和值都进行URL编码
                        encoded_key = urllib.parse.quote(meta_key, safe='')
                        encoded_value = urllib.parse.quote(str(meta_value), safe='')
                        headers[f'x-amz-meta-{encoded_key}'] = encoded_value
                elif arg_name == 'ACL':
                    headers['x-amz-acl'] = arg_value
                elif arg_name == 'StorageClass':
                    headers['x-amz-storage-class'] = arg_value
                elif arg_name == 'ServerSideEncryption':
                    headers['x-amz-server-side-encryption'] = arg_value
                elif arg_name == 'WebsiteRedirectLocation':
                    headers['x-amz-website-redirect-location'] = arg_value
                elif arg_name == 'ContentMD5':
                    headers['Content-MD5'] = arg_value
                elif arg_name == 'Expires':
                    headers['Expires'] = arg_value
                elif arg_name == 'ContentLength':
                    headers['Content-Length'] = str(arg_value)
                elif arg_name == 'Tagging':
                    headers['x-amz-tagging'] = arg_value
                
                # 尝试上传对象
                response = make_s3_request(
                    method='PUT',
                    bucket=bucket,
                    key=f"{test_key}_{arg_name}",
                    data=test_data,
                    headers=headers
                )
                
                # 检查上传是否成功
                if response.status_code not in (200, 201):
                    results[arg_name] = {
                        'supported': False,
                        'error': f'HTTP {response.status_code}',
                        'note': f'参数不支持: HTTP {response.status_code}'
                    }
                    continue
                
                # 获取对象元数据以验证参数是否生效
                head_response = make_s3_request(
                    method='HEAD',
                    bucket=bucket,
                    key=f"{test_key}_{arg_name}"
                )
                
                # 检查参数是否在响应中
                if arg_name == 'ContentType' and 'Content-Type' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['Content-Type'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'ContentEncoding' and 'Content-Encoding' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['Content-Encoding'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'ContentLanguage' and 'Content-Language' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['Content-Language'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'CacheControl' and 'Cache-Control' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['Cache-Control'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'ContentDisposition' and 'Content-Disposition' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['Content-Disposition'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'Metadata':
                    # 检查元数据是否在响应中
                    metadata_results = {}
                    all_metadata_found = True
                    for meta_key, meta_value in arg_value.items():
                        # 对键进行URL编码以匹配请求头
                        encoded_key = urllib.parse.quote(meta_key, safe='')
                        header_key = f'x-amz-meta-{encoded_key}'
                        if header_key in head_response.headers:
                            # 对存储的值进行URL解码以进行比较
                            stored_value = urllib.parse.unquote(head_response.headers[header_key])
                            metadata_results[meta_key] = {
                                'found': True,
                                'value': stored_value,
                                'matches': stored_value == str(meta_value)
                            }
                        else:
                            all_metadata_found = False
                            metadata_results[meta_key] = {
                                'found': False,
                                'value': None,
                                'matches': False
                            }
                    
                    results[arg_name] = {
                        'supported': all_metadata_found,
                        'value': metadata_results,
                        'note': '元数据测试结果详情'
                    }
                elif arg_name == 'ACL' and 'x-amz-acl' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['x-amz-acl'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'StorageClass' and 'x-amz-storage-class' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['x-amz-storage-class'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'ServerSideEncryption' and 'x-amz-server-side-encryption' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['x-amz-server-side-encryption'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'WebsiteRedirectLocation' and 'x-amz-website-redirect-location' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['x-amz-website-redirect-location'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'ContentMD5' and 'Content-MD5' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['Content-MD5'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'Expires' and 'Expires' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['Expires'],
                        'note': '参数支持且已生效'
                    }
                elif arg_name == 'ContentLength':
                    # ContentLength通常不会在HEAD响应中返回
                    results[arg_name] = {
                        'supported': True,
                        'value': None,
                        'note': '参数支持但未在响应中返回'
                    }
                elif arg_name == 'Tagging' and 'x-amz-tagging' in head_response.headers:
                    results[arg_name] = {
                        'supported': True,
                        'value': head_response.headers['x-amz-tagging'],
                        'note': '参数支持且已生效'
                    }
                
                # 清理测试对象
                try:
                    make_s3_request(
                        method='DELETE',
                        bucket=bucket,
                        key=f"{test_key}_{arg_name}"
                    )
                except:
                    pass
                    
            except Exception as e:
                results[arg_name] = {
                    'supported': False,
                    'error': str(e),
                    'note': f'参数不支持: {str(e)}'
                }
        
        return results
        
    except Exception as e:
        print(f"检查ExtraArgs支持时出错: {e}")
        return None

def get_object_count(bucket):
    """
    获取存储桶中的对象数量
    参数:
        bucket: 存储桶名称
    返回:
        {
            'total_count': 对象总数,
            'size': 总大小(字节),
            'details': {
                'files': 文件数量,
                'folders': 文件夹数量,
                'total_size': 总大小(字节)
            }
        }
    """
    try:
        # 初始化计数器
        total_count = 0
        total_size = 0
        file_count = 0
        folder_count = 0
        
        # 使用分页方式获取所有对象
        paginator = s3.get_paginator('list_objects_v2')
        for page in paginator.paginate(Bucket=bucket):
            if 'Contents' in page:
                for obj in page['Contents']:
                    total_count += 1
                    total_size += obj['Size']
                    # 检查是否为文件夹(以'/'结尾)
                    if obj['Key'].endswith('/'):
                        folder_count += 1
                    else:
                        file_count += 1
        
        return {
            'total_count': total_count,
            'size': total_size,
            'details': {
                'files': file_count,
                'folders': folder_count,
                'total_size': total_size
            }
        }
    except ClientError as e:
        print(f"获取对象数量时出错: {e}")
        return None

def query_policy(bucket):
    """
    查询存储桶策略
    返回存储桶的策略配置,如果不存在则返回None
    """
    try:
        return s3.get_bucket_policy(Bucket=bucket)['Policy']
    except ClientError as e:
        if e.response.get('Error', {}).get('Code') == 'NoSuchBucketPolicy':
            return None
        raise

def query_cors(bucket):
    """
    查询存储桶的CORS配置
    返回CORS规则列表,如果不存在则返回空列表
    """
    try:
        return s3.get_bucket_cors(Bucket=bucket)['CORSRules']
    except ClientError as e:
        code = e.response.get('Error', {}).get('Code', '')
        if code in ('NoSuchCORSConfiguration', 'InvalidRequest'):
            return []
        raise

def get_temp_sts(duration_seconds=3600):
    """
    获取临时安全凭证(STS)
    返回临时访问凭证,如果STS未启用则返回None
    """
    try:
        sts = session.client('sts', 
                           endpoint_url=S3_ENDPOINT, 
                           config=Config(signature_version='s3v4'),
                           verify=verify_value)
        creds = sts.get_session_token(DurationSeconds=duration_seconds)['Credentials']
        return creds
    except ClientError:
        return None  # STS未支持或未启用

def query_quota(bucket):
    """
    查询存储桶配额信息
    参数:
        bucket: 存储桶名称
    返回:
        存储桶配额信息
    """
    try:
        response = make_s3_request('GET', bucket, '', params={'quota': None})
        
        if response.status_code == 200:
            quota_info = xmltodict.parse(response.text)
            quota = quota_info.get('Quota', {})
            max_size = quota.get('MaxSize', '0')
            max_objects = quota.get('MaxObjects', '0')
            
            if max_size == '0' and max_objects == '0':
                print(f"\n存储桶 {bucket} 未设置配额限制")
                print("提示: 该存储桶当前没有配置任何配额限制")
                return None
            
            print(f"\n存储桶 {bucket} 的配额信息:")
            print(f"最大存储空间: {max_size} bytes")
            print(f"最大对象数量: {max_objects}")
            
            return {
                'MaxSize': int(max_size),
                'MaxObjects': int(max_objects)
            }
        elif response.status_code == 404:
            print(f"\n存储桶 {bucket} 未设置配额限制")
            print("提示: 该存储桶当前没有配置任何配额限制")
            return None
        else:
            print(f"\n查询存储桶 {bucket} 配额时出错: HTTP {response.status_code}")
            return None
            
    except Exception as e:
        print(f"\n查询存储桶 {bucket} 配额时出错: {e}")
        return None

def query_lifecycle(bucket):
    """
    查询存储桶的生命周期规则
    返回生命周期规则列表,如果不存在则返回空列表
    """
    try:
        return s3.get_bucket_lifecycle_configuration(Bucket=bucket)['Rules']
    except ClientError as e:
        if e.response.get('Error', {}).get('Code') == 'NoSuchLifecycleConfiguration':
            return []
        raise

def query_tagging(bucket):
    """
    查询存储桶的标签
    返回标签集合,如果不存在则返回空列表
    """
    try:
        response = s3.get_bucket_tagging(Bucket=bucket)
        return response.get('TagSet', [])
    except ClientError as e:
        error_code = e.response.get('Error', {}).get('Code', '')
        if error_code in ('NoSuchTagSet', 'NoSuchTagSetError'):
            print(f"Bucket {bucket} has no tags set")
            return []
        print(f"Error querying tags: {e}")
        raise

def check_object_encoding(bucket, key):
    """
    检查Ceph对象的字符编码
    返回对象的ContentType、字符集和编码信息
    """
    try:
        response = s3.head_object(Bucket=bucket, Key=key)
        content_type = response.get('ContentType', 'Not specified')
        metadata = response.get('Metadata', {})
        
        # 从Content-Type中提取字符集信息
        charset = None
        if 'charset=' in content_type:
            charset = content_type.split('charset=')[-1].strip()
        
        # 获取Content-Encoding头信息
        content_encoding = response.get('ContentEncoding', 'Not specified')
        
        return {
            'ContentType': content_type,
            'Charset': charset,
            'ContentEncoding': content_encoding,
            'Metadata': metadata
        }
    except ClientError as e:
        if e.response.get('Error', {}).get('Code') == 'NoSuchKey':
            return {'Error': f'Object {key} does not exist in bucket {bucket}'}
        raise

# --------- 2. 预签名URL和分片预签名测试函数 ---------
def generate_presign_get_url(bucket, key, expires_in=URL_EXPIRATION):
    """
    生成用于下载对象的预签名URL
    参数:
        bucket: 存储桶名称
        key: 对象键名(文件名)
        expires_in: URL有效期(秒),默认300秒
    返回:
        预签名URL,如果生成失败则返回None
    用途:
        - 生成临时下载链接
        - 允许无凭证访问对象
        - 控制文件访问时间
    """
    try:
        url = s3.generate_presigned_url(
            ClientMethod='get_object',
            Params={
                'Bucket': bucket,
                'Key': key,
            },
            ExpiresIn=expires_in,
            HttpMethod='GET'
        )
        print(f"生成的预签名URL过期时间: {expires_in} 秒")
        return url
    except ClientError as e:
        print(f"生成预签名URL时出错: {e}")
        return None

def generate_presign_put_url(bucket, key, expires_in=URL_EXPIRATION):
    """
    生成用于上传对象的预签名URL
    参数:
        bucket: 存储桶名称
        key: 对象键名(文件名)
        expires_in: URL有效期(秒),默认300秒
    返回:
        预签名URL
    用途:
        - 生成临时上传链接
        - 允许无凭证上传对象
        - 控制上传时间窗口
    """
    return s3.generate_presigned_url(
        ClientMethod='put_object',
        Params={'Bucket': bucket, 'Key': key},
        ExpiresIn=expires_in
    )

def check_presign_get(url):
    """
    Check presigned GET URL accessibility
    Returns HTTP status code, or None if request fails
    """
    try:
        # First try HEAD request
        head_response = http_session.head(url)
        print(f"HEAD请求状态: {head_response.status_code}")
        print(f"HEAD响应头: {dict(head_response.headers)}")
        
        # Try GET request regardless of HEAD status
        get_response = http_session.get(url)
        print(f"\nGET请求状态: {get_response.status_code}")
        print(f"GET响应头: {dict(get_response.headers)}")
        
        if get_response.status_code == 200:
            print("\nGET请求成功:")
            print(f"内容类型: {get_response.headers.get('Content-Type', 'Not specified')}")
            print(f"内容长度: {get_response.headers.get('Content-Length', 'Not specified')} bytes")
            print(f"最后修改: {get_response.headers.get('Last-Modified', 'Not specified')}")
            print(f"ETag: {get_response.headers.get('ETag', 'Not specified')}")
            
            # Check if content is accessible
            content = get_response.content
            print(f"内容预览: {content[:100]}")
        
        # If HEAD failed but GET succeeded, this might indicate a permission configuration issue
        if head_response.status_code == 403 and get_response.status_code == 200:
            print("\n注意: HEAD请求被拒绝但GET请求成功")
            print("这可能表明:")
            print("1. 存储桶策略可能限制了HEAD操作")
            print("2. CORS配置可能需要调整")
            print("3. 预签名URL的权限配置可能需要更新")
        
        return get_response.status_code
    except requests.exceptions.RequestException as e:
        print(f"请求失败: {e}")
        return None

def multipart_presign_and_upload(bucket, key, part_data, chunk_size=None, expires_in=URL_EXPIRATION):
    """
    生成分片上传的预签名URL
    参数:
        bucket: 存储桶名称
        key: 对象键名
        part_data: 要上传的数据
        chunk_size: 分片大小,默认使用MULTIPART_CHUNK_SIZE
        expires_in: 预签名URL过期时间
    返回:
        {
            'upload_id': 上传ID,
            'part_urls': [分片1的URL, 分片2的URL, ...],
            'part_sizes': [分片1的大小, 分片2的大小, ...],
            'total_parts': 总分片数
        }
    """
    # 使用指定的分片大小或默认值
    if chunk_size is None:
        chunk_size = MULTIPART_CHUNK_SIZE

    # 初始化分片上传
    init = s3.create_multipart_upload(Bucket=bucket, Key=key)
    upload_id = init['UploadId']
    
    # 计算需要多少个分片
    total_size = len(part_data)
    num_parts = (total_size + chunk_size - 1) // chunk_size
    
    part_urls = []
    part_sizes = []
    
    try:
        # 为每个分片生成预签名URL
        for part_num in range(1, num_parts + 1):
            # 计算当前分片的起始和结束位置
            start = (part_num - 1) * chunk_size
            end = min(start + chunk_size, total_size)
            part_size = end - start
            
            # 生成上传分片的预签名URL
            presign_url = s3.generate_presigned_url(
                ClientMethod='upload_part',
                Params={
                    'Bucket': bucket,
                    'Key': key,
                    'UploadId': upload_id,
                    'PartNumber': part_num
                },
                ExpiresIn=expires_in
            )
            part_urls.append(presign_url)
            part_sizes.append(part_size)
            
            print(f"Generated presigned URL for part {part_num}/{num_parts}")
        
        return {
            'upload_id': upload_id,
            'part_urls': part_urls,
            'part_sizes': part_sizes,
            'total_parts': num_parts
        }
        
    except Exception as e:
        # 如果发生错误,中止上传
        try:
            s3.abort_multipart_upload(
                Bucket=bucket,
                Key=key,
                UploadId=upload_id
            )
        except:
            pass
            
        raise RuntimeError(f"生成预签名URL失败: {str(e)}")

def upload_part(url, part_data):
    """
    使用预签名URL上传分片
    参数:
        url: 预签名URL
        part_data: 分片数据
    返回:
        ETag
    """
    put_resp = http_session.put(url, data=part_data)
    if put_resp.status_code not in (200, 201):
        raise RuntimeError(f"分片上传失败: {put_resp.status_code}")
    return put_resp.headers.get('ETag')

def complete_multipart_upload(bucket, key, upload_id, parts):
    """
    完成分片上传
    参数:
        bucket: 存储桶名称
        key: 对象键名
        upload_id: 上传ID
        parts: 分片信息列表,每个元素包含 ETag 和 PartNumber
    """
    s3.complete_multipart_upload(
        Bucket=bucket,
        Key=key,
        UploadId=upload_id,
        MultipartUpload={'Parts': parts}
    )

def check_thumbnail_support(bucket, key):
    """
    检查对象是否支持缩略图功能
    参数:
        bucket: 存储桶名称
        key: 对象键名
    返回:
        包含缩略图支持信息的字典,包括:
        - ContentType: 内容类型
        - IsImage: 是否为图片
        - HasThumbnail: 是否已有缩略图
        - ThumbnailMetadata: 缩略图相关元数据
        - ImageProcessingHeaders: 图片处理相关头信息
        如果对象不存在则返回错误信息
    """
    try:
        # Check object metadata for image-related information
        response = s3.head_object(Bucket=bucket, Key=key)
        content_type = response.get('ContentType', '')
        metadata = response.get('Metadata', {})
        
        # Check if object is an image
        is_image = content_type.startswith('image/')
        
        # Check for thumbnail-related metadata
        has_thumbnail = any(key.lower().startswith(('thumbnail', 'thumb')) for key in metadata.keys())
        
        # Check for image processing headers
        image_processing = {
            'ContentType': content_type,
            'IsImage': is_image,
            'HasThumbnail': has_thumbnail,
            'ThumbnailMetadata': {k: v for k, v in metadata.items() if k.lower().startswith(('thumbnail', 'thumb'))},
            'ImageProcessingHeaders': {k: v for k, v in response.items() if k.lower().startswith(('x-amz-image', 'x-amz-thumb'))}
        }
        
        return image_processing
    except ClientError as e:
        if e.response.get('Error', {}).get('Code') == 'NoSuchKey':
            return {'Error': f'Object {key} does not exist in bucket {bucket}'}
        raise

def generate_thumbnail(image_data, size, quality=THUMBNAIL_QUALITY, format=THUMBNAIL_FORMAT):
    """
    生成缩略图
    参数:
        image_data: 原始图片数据
        size: 目标尺寸 (width, height)
        quality: JPEG质量 (1-100)
        format: 输出格式
    返回:
        缩略图数据,失败时返回None
    """
    try:
        # 打开图片
        img = Image.open(io.BytesIO(image_data))
        
        # 转换为RGB模式(如果是RGBA)
        if img.mode in ('RGBA', 'LA'):
            background = Image.new('RGB', img.size, (255, 255, 255))
            background.paste(img, mask=img.split()[-1])
            img = background
        
        # 保持宽高比进行缩放
        img.thumbnail(size, Image.Resampling.LANCZOS)
        
        # 保存缩略图
        output = io.BytesIO()
        img.save(output, format=format, quality=quality, optimize=True)
        return output.getvalue()
    except Exception as e:
        print(f"Failed to generate thumbnail: {e}")
        return None

def upload_thumbnails(bucket, key, image_data):
    """
    上传图片的多个尺寸缩略图
    参数:
        bucket: 存储桶名称
        key: 原始图片键名
        image_data: 原始图片数据
    返回:
        缩略图信息列表,包含每个缩略图的尺寸、键名、内容类型和大小
    """
    thumbnails = []
    base_name = os.path.splitext(key)[0]
    
    for size in THUMBNAIL_SIZES:
        # 生成缩略图
        thumb_data = generate_thumbnail(image_data, size)
        if not thumb_data:
            continue
            
        # 构建缩略图键名
        thumb_key = f"{base_name}_{size[0]}x{size[1]}.{THUMBNAIL_FORMAT.lower()}"
        
        try:
            # 计算缩略图数据的 SHA256 校验和
            content_sha256 = hashlib.sha256(thumb_data).hexdigest()

            # 构建请求头
            headers = {
                'Content-Type': f'image/{THUMBNAIL_FORMAT.lower()}',
                'x-amz-checksum-algorithm': 'SHA256',
                'x-amz-checksum-sha256': content_sha256
            }

            # 上传缩略图
            response = make_s3_request(
                method='PUT',
                bucket=bucket,
                key=thumb_key,
                data=thumb_data,
                headers=headers
            )
            
            if response.status_code not in (200, 201):
                raise Exception(f"Upload failed with status code: {response.status_code}")
            
            # 记录缩略图信息
            thumbnails.append({
                'size': size,
                'key': thumb_key,
                'content_type': f'image/{THUMBNAIL_FORMAT.lower()}',
                'length': len(thumb_data),
                'sha256': content_sha256
            })
            
            print(f"Uploaded thumbnail: {thumb_key} ({size[0]}x{size[1]})")
        except Exception as e:
            print(f"Failed to upload thumbnail {thumb_key}: {e}")
    
    return thumbnails

def process_image_upload(bucket, key, image_data):
    """
    处理图片上传,包括原图和缩略图
    参数:
        bucket: 存储桶名称
        key: 图片键名
        image_data: 图片数据
    返回:
        上传结果信息,包含原图和所有缩略图的信息,失败时返回None
    """
    try:
        # 计算图片数据的 SHA256 校验和
        content_sha256 = hashlib.sha256(image_data).hexdigest()

        # 上传原图
        s3.put_object(
            Bucket=bucket,
            Key=key,
            Body=image_data,
            ContentType='image/jpeg',  # 根据实际图片类型设置
            ChecksumAlgorithm='SHA256',
            ChecksumSHA256=content_sha256
        )
        print(f"Uploaded original image: {key}")
        
        # 生成并上传缩略图
        thumbnails = upload_thumbnails(bucket, key, image_data)
        
        return {
            'original': {
                'key': key,
                'content_type': 'image/jpeg',
                'length': len(image_data),
                'sha256': content_sha256
            },
            'thumbnails': thumbnails
        }
    except Exception as e:
        print(f"Failed to process image upload: {e}")
        return None

def verify_thumbnail(bucket, key):
    """
    验证缩略图是否存在且可访问
    参数:
        bucket: 存储桶名称
        key: 缩略图键名
    返回:
        验证结果字典,包含是否存在、大小、内容类型等信息
    """
    try:
        response = s3.head_object(Bucket=bucket, Key=key)
        return {
            'exists': True,
            'size': response['ContentLength'],
            'content_type': response['ContentType'],
            'last_modified': response['LastModified']
        }
    except ClientError as e:
        if e.response['Error']['Code'] == '404':
            return {'exists': False}
        raise

def check_thumbnail_functionality(bucket, test_image_path):
    """
    检查缩略图功能的完整性
    参数:
        bucket: 存储桶名称
        test_image_path: 测试图片路径
    返回:
        检查结果字典,包含各项功能的测试结果
    """
    results = {
        'image_upload': False,
        'thumbnail_generation': False,
        'thumbnail_access': False,
        'errors': []
    }
    
    try:
        # 1. 检查测试图片是否存在
        if not os.path.exists(test_image_path):
            results['errors'].append(f"Test image not found: {test_image_path}")
            return results
            
        # 2. 读取并上传测试图片
        with open(test_image_path, 'rb') as f:
            image_data = f.read()
            upload_result = process_image_upload(bucket, "test_image.jpg", image_data)
            
            if not upload_result:
                results['errors'].append("Failed to upload test image")
                return results
                
            results['image_upload'] = True
            
            # 3. 验证缩略图生成
            if not upload_result['thumbnails']:
                results['errors'].append("No thumbnails were generated")
                return results
                
            results['thumbnail_generation'] = True
            
            # 4. 验证缩略图访问
            for thumb in upload_result['thumbnails']:
                verify_result = verify_thumbnail(bucket, thumb['key'])
                if not verify_result['exists']:
                    results['errors'].append(f"Thumbnail not accessible: {thumb['key']}")
                    continue
                    
                print(f"Verified thumbnail: {thumb['key']}")
                print(f"Size: {verify_result['size']} bytes")
                print(f"Content-Type: {verify_result['content_type']}")
                print(f"Last Modified: {verify_result['last_modified']}")
            
            results['thumbnail_access'] = True
            
        return results
        
    except Exception as e:
        results['errors'].append(f"Unexpected error: {str(e)}")
        return results

def generate_test_image(filename="test_image.jpg", size=(800, 600)):
    """
    生成测试用图片
    参数:
        filename: 输出文件名
        size: 图片尺寸 (宽, 高)
    返回:
        生成的图片路径
    """
    try:
        # 创建一个新的图片,使用RGB模式
        img = Image.new('RGB', size, color='white')
        
        # 创建绘图对象
        from PIL import ImageDraw
        draw = ImageDraw.Draw(img)
        
        # 绘制一些测试图形
        # 1. 绘制矩形边框
        draw.rectangle([(50, 50), (size[0]-50, size[1]-50)], outline='blue', width=5)
        
        # 2. 绘制对角线
        draw.line([(0, 0), (size[0], size[1])], fill='red', width=3)
        draw.line([(0, size[1]), (size[0], 0)], fill='green', width=3)
        
        # 3. 绘制一些文字
        from PIL import ImageFont
        try:
            # 尝试使用系统字体
            font = ImageFont.truetype("arial.ttf", 40)
        except:
            # 如果找不到字体,使用默认字体
            font = ImageFont.load_default()
            
        draw.text((size[0]/2-100, size[1]/2-20), "Test Image", fill='black', font=font)
        
        # 保存图片
        img.save(filename, quality=95)
        print(f"Generated test image: {filename}")
        return filename
    except Exception as e:
        print(f"Failed to generate test image: {e}")
        return None

def load_test_data(data_type):
    """
    根据配置模式加载测试数据
    参数:
        data_type: 数据类型 ('initial' 或 'multipart')
    返回:
        测试数据字节
    异常:
        当文件模式且文件不存在时,会回退到使用字节数据
    """
    if TEST_DATA_MODE == "file":
        file_path = TEST_DATA[data_type]['file']
        try:
            # 获取脚本所在目录的绝对路径
            script_dir = os.path.dirname(os.path.abspath(__file__))
            # 构建文件的绝对路径
            abs_file_path = os.path.join(script_dir, file_path)
            
            with open(abs_file_path, 'rb') as f:
                return f.read()
        except Exception as e:
            print(f"Failed to load test data from file {file_path}: {e}")
            print("Falling back to byte data...")
            return TEST_DATA[data_type]['bytes']
    else:
        return TEST_DATA[data_type]['bytes']

def test_presign_put_upload(bucket, key, data):
    """
    测试使用预签名URL进行简单上传(非分片上传)
    参数:
        bucket: 存储桶名称
        key: 对象键名(文件名)
        data: 要上传的数据(字节数据)
    返回:
        上传结果信息字典,包含:
        - success: 是否成功
        - status_code: HTTP状态码
        - headers: 响应头
        - etag: 对象的ETag(如果成功)
        - error: 错误信息(如果失败)
    使用场景:
        - 小文件上传(建议<5MB)
        - 不需要断点续传的场景
        - 简单的单次上传操作
    优点:
        - 实现简单
        - 无需维护上传状态
        - 适合小文件
    缺点:
        - 不支持大文件
        - 不支持断点续传
        - 上传失败需要重传整个文件
    """
    try:
        # 1. 生成预签名PUT URL
        put_url = generate_presign_put_url(bucket, key)
        print(f"\n生成的预签名PUT URL:")
        print(put_url)
        
        # 2. 使用http_session.put上传数据
        print("\n开始上传...")
        resp = http_session.put(put_url, data=data)
        
        # 3. 检查上传结果
        if resp.status_code in (200, 201):
            print("\n上传成功")
            print("响应状态码:", resp.status_code)
            print("响应头:", dict(resp.headers))
            return {
                'success': True,
                'status_code': resp.status_code,
                'headers': dict(resp.headers),
                'etag': resp.headers.get('ETag')
            }
        else:
            print("\n上传失败")
            print("状态码:", resp.status_code)
            print("错误信息:", resp.text)
            return {
                'success': False,
                'status_code': resp.status_code,
                'error': resp.text
            }
    except Exception as e:
        print(f"上传过程中发生错误: {str(e)}")
        return {
            'success': False,
            'error': str(e)
        }

def download_object(bucket, key, local_path, chunk_size=1024*1024):
    """
    使用预签名URL下载对象(普通下载)
    参数:
        bucket: 存储桶名称
        key: 对象键名
        local_path: 本地保存路径
        chunk_size: 下载时的分块大小(字节)
    返回:
        下载结果信息字典,包含:
        - success: 是否成功
        - size: 下载的文件大小
        - etag: 对象的ETag
        - error: 错误信息(如果失败)
    使用场景:
        - 小文件下载
        - 不需要断点续传的场景
    """
    try:
        # 1. 生成预签名下载URL
        download_url = generate_presign_get_url(bucket, key)
        if not download_url:
            return {'success': False, 'error': '生成预签名URL失败'}
            
        print(f"\n开始下载对象: {key}")
        print(f"保存到: {local_path}")
        
        # 2. 使用http_session.get下载文件
        with http_session.get(download_url, stream=True) as response:
            response.raise_for_status()
            
            # 3. 获取文件信息
            total_size = int(response.headers.get('content-length', 0))
            etag = response.headers.get('ETag', '')
            
            # 4. 保存文件
            with open(local_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=chunk_size):
                    if chunk:
                        f.write(chunk)
                        
            print(f"下载完成: {total_size} 字节")
            return {
                'success': True,
                'size': total_size,
                'etag': etag
            }
            
    except Exception as e:
        print(f"下载失败: {str(e)}")
        return {
            'success': False,
            'error': str(e)
        }

def multipart_download(bucket, key, local_path, part_size=5*1024*1024):
    """
    使用预签名URL分片下载对象
    参数:
        bucket: 存储桶名称
        key: 对象键名
        local_path: 本地保存路径
        part_size: 每个分片的大小(字节),默认5MB
    返回:
        下载结果信息字典,包含:
        - success: 是否成功
        - total_size: 总下载大小
        - parts: 下载的分片数
        - error: 错误信息(如果失败)
    使用场景:
        - 大文件下载
        - 需要断点续传的场景
        - 需要显示下载进度的场景
    """
    try:
        # 1. 获取对象信息
        head_response = s3.head_object(Bucket=bucket, Key=key)
        total_size = head_response['ContentLength']
        etag = head_response['ETag']
        
        print(f"\n开始分片下载对象: {key}")
        print(f"总大小: {total_size} 字节")
        print(f"分片大小: {part_size} 字节")
        
        # 2. 计算分片数量
        num_parts = (total_size + part_size - 1) // part_size
        print(f"总分片数: {num_parts}")
        
        # 3. 创建临时目录存储分片
        temp_dir = f"{local_path}.parts"
        os.makedirs(temp_dir, exist_ok=True)
        
        # 4. 下载每个分片
        for part_num in range(1, num_parts + 1):
            # 计算当前分片的范围
            start = (part_num - 1) * part_size
            end = min(start + part_size - 1, total_size - 1)
            range_header = f'bytes={start}-{end}'
            
            try:
                # 生成预签名URL
                part_url = s3.generate_presigned_url(
                    'get_object',
                    Params={
                        'Bucket': bucket,
                        'Key': key
                    },
                    ExpiresIn=URL_EXPIRATION,
                    HttpMethod='GET'
                )
                
                # 添加Range头到请求中
                headers = {'Range': range_header}
                
                print(f"\n下载分片 {part_num}/{num_parts}")
                print(f"范围: {range_header}")
                print(f"预签名URL: {part_url}")
                
                # 下载分片
                part_path = os.path.join(temp_dir, f"part_{part_num}")
                with http_session.get(part_url, headers=headers, stream=True) as response:
                    response.raise_for_status()
                    with open(part_path, 'wb') as f:
                        for chunk in response.iter_content(chunk_size=1024*1024):
                            if chunk:
                                f.write(chunk)
                
                print(f"分片 {part_num} 下载完成")
                
            except Exception as e:
                print(f"分片 {part_num} 下载失败: {str(e)}")
                # 如果某个分片下载失败,尝试清理并退出
                try:
                    for i in range(1, part_num + 1):
                        part_path = os.path.join(temp_dir, f"part_{i}")
                        if os.path.exists(part_path):
                            os.remove(part_path)
                    os.rmdir(temp_dir)
                except:
                    pass
                raise RuntimeError(f"分片 {part_num} 下载失败: {str(e)}")
        
        # 5. 合并分片
        print("\n合并分片...")
        with open(local_path, 'wb') as f:
            for part_num in range(1, num_parts + 1):
                part_path = os.path.join(temp_dir, f"part_{part_num}")
                with open(part_path, 'rb') as part_file:
                    f.write(part_file.read())
                os.remove(part_path)
        
        # 6. 清理临时目录
        os.rmdir(temp_dir)
        
        print(f"\n下载完成: {local_path}")
        return {
            'success': True,
            'total_size': total_size,
            'parts': num_parts,
            'etag': etag
        }
        
    except Exception as e:
        print(f"分片下载失败: {str(e)}")
        return {
            'success': False,
            'error': str(e)
        }

def check_object_permissions(bucket, test_key="permission_test_object.txt"):
    """
    检查存储桶中对象的常见操作权限
    参数:
        bucket: 存储桶名称
        test_key: 用于测试的对象键名
    返回:
        包含各项权限检查结果的字典
    """
    results = {
        'put_object': False,
        'get_object': False,
        'head_object': False,
        'delete_object': False,
        'list_objects': False,
        'copy_object': False,
        'get_object_acl': False,
        'put_object_acl': False,
        'errors': []
    }
    
    test_data = b"Permission test data"
    copy_key = f"{test_key}.copy"
    
    try:
        # 首先检查存储桶权限
        print("\n检查存储桶权限:")
        try:
            # 检查存储桶是否存在
            s3.head_bucket(Bucket=bucket)
            print(f"✓ 存储桶 {bucket} 存在且可访问")
        except ClientError as e:
            error_code = e.response.get('Error', {}).get('Code', '')
            if error_code == '404':
                print(f"✗ 存储桶 {bucket} 不存在")
                results['errors'].append(f"存储桶 {bucket} 不存在")
            elif error_code == '403':
                print(f"✗ 没有访问存储桶 {bucket} 的权限")
                results['errors'].append(f"没有访问存储桶 {bucket} 的权限")
            else:
                print(f"✗ 检查存储桶时出错: {e}")
                results['errors'].append(f"检查存储桶时出错: {e}")
        
        # 检查用户权限
        print("\n检查用户权限:")
        try:
            # 获取用户信息
            sts = session.client('sts', endpoint_url=S3_ENDPOINT, config=s3_config, verify=verify_value)
            user_info = sts.get_caller_identity()
            print(f"✓ 当前用户: {user_info.get('Arn', 'Unknown')}")
            print(f"✓ 用户ID: {user_info.get('UserId', 'Unknown')}")
            print(f"✓ 账户ID: {user_info.get('Account', 'Unknown')}")
        except ClientError as e:
            # 记录错误但不中断执行
            print(f"✗ 获取用户信息失败: {e}")
            print("  注意: 这可能是由于Ceph未完全实现STS服务,不影响基本操作")
            # 不将此错误添加到results['errors']中,因为它不影响功能
        except Exception as e:
            # 处理其他异常
            print(f"✗ 获取用户信息时发生未预期的错误: {e}")
        
        # 1. 测试上传对象权限 (put_object)
        try:
            # 尝试使用预签名URL上传
            print(f"\n尝试使用预签名URL上传对象...")
            # 生成预签名URL
            presigned_url = s3.generate_presigned_url(
                'put_object',
                Params={
                    'Bucket': bucket,
                    'Key': test_key,
                    'ContentType': 'text/plain'
                },
                ExpiresIn=300
            )
            
            # 使用预签名URL上传
            resp = http_session.put(
                presigned_url,
                data=test_data,
                headers={'Content-Type': 'text/plain'}
            )
            
            if resp.status_code in (200, 201):
                results['put_object'] = True
                print(f"✓ 使用预签名URL上传对象成功")
            else:
                results['errors'].append(f"使用预签名URL上传对象失败: {resp.status_code} {resp.text}")
                print(f"✗ 使用预签名URL上传对象失败: {resp.status_code} {resp.text}")
                
                # 尝试使用不使用签名验证的S3客户端
                try:
                    print(f"尝试使用不使用签名验证的S3客户端上传对象...")
                    unsigned_s3.put_object(
                    Bucket=bucket,
                    Key=test_key,
                        Body=test_data,
                        ContentType='text/plain'
                    )
                    results['put_object'] = True
                    print(f"✓ 使用不使用签名验证的S3客户端上传对象成功")
                except ClientError as e2:
                    # 尝试使用requests直接上传
                    try:
                        print(f"尝试使用requests直接上传对象...")
                        # 构建上传URL
                        upload_url = f"{S3_ENDPOINT}/{bucket}/{test_key}"
                        
                        # 创建请求
                        aws_req = AWSRequest(method='PUT', url=upload_url, data=test_data)
                        SigV4Auth(session.get_credentials(), 's3', REGION).add_auth(aws_req)
                        prep = aws_req.prepare()
                        
                        # 发送请求
                        resp = http_session.request(
                            method=prep.method,
                            url=prep.url,
                            headers=dict(prep.headers),
                            data=prep.body
                        )
                        
                        if resp.status_code in (200, 201):
                            results['put_object'] = True
                            print(f"✓ 使用requests直接上传对象成功")
                        else:
                            results['errors'].append(f"使用requests直接上传对象失败: {resp.status_code} {resp.text}")
                            print(f"✗ 使用requests直接上传对象失败: {resp.status_code} {resp.text}")
                    except Exception as e3:
                        results['errors'].append(f"上传对象失败 (requests方法): {e3}")
                        print(f"✗ 使用requests直接上传对象失败: {e3}")
        except Exception as e:
            results['errors'].append(f"上传对象失败: {e}")
            print(f"✗ 上传对象失败: {e}")
        
        # 如果上传成功,继续测试其他权限
        if results['put_object']:
            # 2. 测试获取对象权限 (get_object)
            try:
                # 尝试使用预签名URL获取
                print(f"\n尝试使用预签名URL获取对象...")
                presigned_url = s3.generate_presigned_url(
                    'get_object',
                    Params={
                        'Bucket': bucket,
                        'Key': test_key
                    },
                    ExpiresIn=300
                )
                
                resp = http_session.get(presigned_url)
                if resp.status_code == 200:
                    data = resp.content
                    if data == test_data:
                        results['get_object'] = True
                        print(f"✓ 使用预签名URL获取对象成功")
                    else:
                        results['errors'].append("获取对象数据不匹配")
                        print(f"✗ 获取对象数据不匹配")
                else:
                    results['errors'].append(f"使用预签名URL获取对象失败: {resp.status_code} {resp.text}")
                    print(f"✗ 使用预签名URL获取对象失败: {resp.status_code} {resp.text}")
                    
                    # 尝试使用不使用签名验证的S3客户端
                    try:
                        response = unsigned_s3.get_object(Bucket=bucket, Key=test_key)
                        data = response['Body'].read()
                        if data == test_data:
                            results['get_object'] = True
                            print(f"✓ 使用不使用签名验证的S3客户端获取对象成功")
                        else:
                            results['errors'].append("获取对象数据不匹配")
                            print(f"✗ 获取对象数据不匹配")
                    except ClientError as e2:
                        results['errors'].append(f"获取对象失败: {e2}")
                        print(f"✗ 获取对象权限异常: {e2}")
            except Exception as e:
                results['errors'].append(f"获取对象失败: {e}")
                print(f"✗ 获取对象失败: {e}")
            
            # 3. 测试获取对象元数据权限 (head_object)
            try:
                unsigned_s3.head_object(Bucket=bucket, Key=test_key)
                results['head_object'] = True
                print(f"✓ 获取对象元数据权限正常")
            except ClientError as e:
                results['errors'].append(f"获取对象元数据失败: {e}")
                print(f"✗ 获取对象元数据权限异常: {e}")
            
            # 4. 测试复制对象权限 (copy_object)
            try:
                unsigned_s3.copy_object(
                    CopySource={'Bucket': bucket, 'Key': test_key},
                    Bucket=bucket,
                    Key=copy_key
                )
                results['copy_object'] = True
                print(f"✓ 复制对象权限正常")
            except ClientError as e:
                results['errors'].append(f"复制对象失败: {e}")
                print(f"✗ 复制对象权限异常: {e}")
            
            # 6. 测试获取对象ACL权限 (get_object_acl)
            try:
                unsigned_s3.get_object_acl(Bucket=bucket, Key=test_key)
                results['get_object_acl'] = True
                print(f"✓ 获取对象ACL权限正常")
            except ClientError as e:
                results['errors'].append(f"获取对象ACL失败: {e}")
                print(f"✗ 获取对象ACL权限异常: {e}")
            
            # 7. 测试设置对象ACL权限 (put_object_acl)
            try:
                unsigned_s3.put_object_acl(
                    Bucket=bucket,
                    Key=test_key,
                    ACL='private'
                )
                results['put_object_acl'] = True
                print(f"✓ 设置对象ACL权限正常")
            except ClientError as e:
                results['errors'].append(f"设置对象ACL失败: {e}")
                print(f"✗ 设置对象ACL权限异常: {e}")
        
        # 5. 测试列出对象权限 (list_objects) - 无论上传是否成功都可以测试
        try:
            response = unsigned_s3.list_objects_v2(Bucket=bucket, MaxKeys=10)
            results['list_objects'] = True
            print(f"✓ 列出对象权限正常")
        except ClientError as e:
            results['errors'].append(f"列出对象失败: {e}")
            print(f"✗ 列出对象权限异常: {e}")
        
        # 8. 测试删除对象权限 (delete_object)
        try:
            # 如果对象存在,尝试删除
            if results['put_object']:
                unsigned_s3.delete_object(Bucket=bucket, Key=test_key)
                results['delete_object'] = True
                print(f"✓ 删除对象权限正常")
                
                # 如果复制对象成功,也删除它
                if results['copy_object']:
                    try:
                        unsigned_s3.delete_object(Bucket=bucket, Key=copy_key)
                        print(f"✓ 删除复制的对象成功")
                    except ClientError as e:
                        print(f"✗ 删除复制的对象失败: {e}")
            else:
                # 如果对象不存在,尝试删除一个不存在的对象来测试权限
                try:
                    unsigned_s3.delete_object(Bucket=bucket, Key="non_existent_object")
                    results['delete_object'] = True
                    print(f"✓ 删除对象权限正常 (测试删除不存在的对象)")
                except ClientError as e:
                    if e.response.get('Error', {}).get('Code') == 'NoSuchKey':
                        # 如果错误是对象不存在,这实际上是正常的
                        results['delete_object'] = True
                        print(f"✓ 删除对象权限正常 (对象不存在)")
                    else:
                        results['errors'].append(f"删除对象失败: {e}")
                        print(f"✗ 删除对象权限异常: {e}")
        except ClientError as e:
            results['errors'].append(f"删除对象失败: {e}")
            print(f"✗ 删除对象权限异常: {e}")
        
        # 检查存储桶策略
        print("\n检查存储桶策略:")
        try:
            policy = query_policy(bucket)
            if policy:
                print(f"✓ 存储桶策略存在")
                # 解析策略以检查权限
                import json
                try:
                    policy_json = json.loads(policy)
                    statements = policy_json.get('Statement', [])
                    print(f"  策略包含 {len(statements)} 条语句")
                    for i, stmt in enumerate(statements):
                        effect = stmt.get('Effect', 'Unknown')
                        actions = stmt.get('Action', [])
                        if not isinstance(actions, list):
                            actions = [actions]
                        resources = stmt.get('Resource', [])
                        if not isinstance(resources, list):
                            resources = [resources]
                        print(f"  语句 {i+1}: {effect} 对 {', '.join(resources)} 执行 {', '.join(actions)}")
                except json.JSONDecodeError:
                    print(f"  无法解析策略JSON")
            else:
                print(f"✗ 存储桶没有策略")
        except Exception as e:
            print(f"✗ 检查存储桶策略失败: {e}")
        
        # 检查存储桶ACL
        print("\n检查存储桶ACL:")
        try:
            acl = query_acl(bucket)
            if acl:
                print(f"✓ 存储桶ACL存在")
                grants = acl.get('Grants', [])
                print(f"  ACL包含 {len(grants)} 条授权")
                for i, grant in enumerate(grants):
                    grantee = grant.get('Grantee', {})
                    permission = grant.get('Permission', 'Unknown')
                    grantee_type = grantee.get('Type', 'Unknown')
                    grantee_id = grantee.get('ID', 'Unknown')
                    grantee_uri = grantee.get('URI', 'Unknown')
                    print(f"  授权 {i+1}: {permission} 授予 {grantee_type} {grantee_id or grantee_uri}")
            else:
                print(f"✗ 存储桶没有ACL")
        except Exception as e:
            print(f"✗ 检查存储桶ACL失败: {e}")
        
        return results
        
    except Exception as e:
        results['errors'].append(f"权限检查过程中发生未预期的错误: {e}")
        print(f"✗ 权限检查过程中发生未预期的错误: {e}")
        return results

def check_migrated_files_content_type_and_extra_args(bucket, check_mode_config=None):
    """
    检查已迁移文件的ContentType和ExtraArgs信息,并检查时间同步问题
    参数:
        bucket: 存储桶名称
        check_mode_config: 检查模式配置,如果为None则使用全局配置
    返回:
        包含每个文件ContentType、ExtraArgs信息和时间同步检查结果的字典
    时间同步检查说明:
        1. 检查客户端和服务器时间差
        2. 检查预签名URL的有效期
        3. 检查文件的时间戳
        4. 检查缓存控制时间
        5. 检查过期时间
    """
    try:
        # 使用传入的配置或全局配置
        config = check_mode_config or CHECK_MODE
        mode = config['mode']
        options = config['options']
        
        results = {}
        
        def check_file(key):
            """检查单个文件的元数据和时间同步"""
            try:
                # 获取对象的元数据
                response = s3.head_object(Bucket=bucket, Key=key)
                
                # 获取服务器时间
                server_time = response.get('LastModified')
                client_time = datetime.now(timezone.utc)
                
                # 计算时间差(秒)
                time_diff = abs((server_time - client_time).total_seconds())
                
                # 获取缓存控制和过期时间
                cache_control = response.get('CacheControl', '')
                expires = response.get('Expires', '')
                
                # 解析缓存控制时间
                cache_max_age = 0
                if cache_control:
                    for directive in cache_control.split(','):
                        if 'max-age=' in directive:
                            try:
                                cache_max_age = int(directive.split('=')[1])
                            except (ValueError, IndexError):
                                pass
                
                # 检查时间同步状态
                time_sync_status = {
                    'ServerTime': server_time.isoformat() if server_time else 'Not available',
                    'ClientTime': client_time.isoformat(),
                    'TimeDifference': time_diff,
                    'Status': 'OK' if time_diff <= 300 else 'Warning',  # 5分钟阈值
                    'CacheControl': {
                        'Value': cache_control,
                        'MaxAge': cache_max_age,
                        'Status': 'OK' if cache_max_age > 0 else 'Not set'
                    },
                    'Expires': {
                        'Value': expires.isoformat() if expires else 'Not set',
                        'Status': 'OK' if expires else 'Not set'
                    },
                    'Warnings': []
                }
                
                # 添加警告信息
                if time_diff > 300:
                    time_sync_status['Warnings'].append(
                        f"客户端和服务器时间差异过大: {time_diff:.2f}秒"
                    )
                if time_diff > 3600:
                    time_sync_status['Warnings'].append(
                        "时间差异超过1小时,可能影响预签名URL的有效性"
                    )
                if not cache_control:
                    time_sync_status['Warnings'].append(
                        "未设置缓存控制时间"
                    )
                if not expires:
                    time_sync_status['Warnings'].append(
                        "未设置过期时间"
                    )
                
                # 提取必要的元数据属性
                file_info = {
                    'Key': key,
                    'ContentType': response.get('ContentType', 'Not specified') if options['check_content_type'] else 'Not checked',
                    'Metadata': response.get('Metadata', {}) if options['check_metadata'] else {},
                    'Size': response.get('ContentLength', 0),
                    'TimeSync': time_sync_status
                }
                
                # 如果检查ExtraArgs,添加额外属性
                if options['check_extra_args']:
                    file_info.update({
                        'ContentEncoding': response.get('ContentEncoding', 'Not specified'),
                        'ContentLanguage': response.get('ContentLanguage', 'Not specified'),
                        'CacheControl': response.get('CacheControl', 'Not specified'),
                        'ContentDisposition': response.get('ContentDisposition', 'Not specified'),
                        'StorageClass': response.get('StorageClass', 'Not specified'),
                        'ServerSideEncryption': response.get('ServerSideEncryption', 'Not specified'),
                        'WebsiteRedirectLocation': response.get('WebsiteRedirectLocation', 'Not specified'),
                        'Expires': response.get('Expires', 'Not specified')
                    })
                
                # 打印检查结果
                print(f"\n检查文件: {key}")
                if options['check_content_type']:
                    print(f"ContentType: {file_info['ContentType']}")
                if options['check_metadata']:
                    print(f"Metadata: {file_info['Metadata']}")
                if options['check_extra_args']:
                    print(f"ContentEncoding: {file_info['ContentEncoding']}")
                    print(f"ContentLanguage: {file_info['ContentLanguage']}")
                    print(f"CacheControl: {file_info['CacheControl']}")
                    print(f"ContentDisposition: {file_info['ContentDisposition']}")
                    print(f"StorageClass: {file_info['StorageClass']}")
                    print(f"ServerSideEncryption: {file_info['ServerSideEncryption']}")
                    print(f"WebsiteRedirectLocation: {file_info['WebsiteRedirectLocation']}")
                    print(f"Expires: {file_info['Expires']}")
                print(f"Size: {file_info['Size']} bytes")
                
                # 打印时间同步信息
                print("\n时间同步检查:")
                print(f"服务器时间: {time_sync_status['ServerTime']}")
                print(f"客户端时间: {time_sync_status['ClientTime']}")
                print(f"时间差: {time_sync_status['TimeDifference']:.2f} 秒")
                print(f"状态: {time_sync_status['Status']}")
                print(f"缓存控制: {time_sync_status['CacheControl']['Value']}")
                print(f"过期时间: {time_sync_status['Expires']['Value']}")
                
                # 打印警告信息
                if time_sync_status['Warnings']:
                    print("\n警告信息:")
                    for warning in time_sync_status['Warnings']:
                        print(f"- {warning}")
                
                return file_info
                
            except ClientError as e:
                print(f"获取对象 {key} 的元数据时出错: {e}")
                return {'error': str(e)}
        
        if mode == 'specific':
            # 检查指定文件
            for key in options['specific_files']:
                results[key] = check_file(key)
        else:  # mode == 'all'
            # 检查所有文件
            paginator = s3.get_paginator('list_objects_v2')
            for page in paginator.paginate(Bucket=bucket):
                if 'Contents' in page:
                    for obj in page['Contents']:
                        key = obj['Key']
                        # 跳过目录
                        if key.endswith('/'):
                            continue
                        results[key] = check_file(key)
        
        # 输出结果
        if options['output_format'] != 'console':
            output_results(results, options['output_format'], options['output_file'])
            
        return results
        
    except Exception as e:
        print(f"检查已迁移文件时出错: {e}")
        return None

def output_results(results, format, filename):
    """将检查结果输出到文件"""
    try:
        if format == 'json':
            import json
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(results, f, ensure_ascii=False, indent=2)
        elif format == 'csv':
            import csv
            with open(filename, 'w', newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                # 写入表头
                headers = ['Key', 'ContentType', 'Metadata', 'Size']
                if CHECK_MODE['options']['check_extra_args']:
                    headers.extend([
                        'ContentEncoding', 'ContentLanguage', 'CacheControl',
                        'ContentDisposition', 'StorageClass', 'ServerSideEncryption',
                        'WebsiteRedirectLocation', 'Expires'
                    ])
                writer.writerow(headers)
                # 写入数据
                for key, info in results.items():
                    if 'error' not in info:
                        row = [key] + [info.get(h, '') for h in headers[1:]]
                        writer.writerow(row)
    except Exception as e:
        print(f"输出结果到文件时出错: {e}")

# --------- 检查功能函数 ---------
def check_upload_initial_object(bucket, key):
    """检查预签名PUT上传初始对象"""
    print("\n" + "="*60)
    print("1. 检查预签名PUT上传初始对象")
    print("="*60)
    
    initial_data = load_test_data('initial')
    print("\n测试预签名PUT URL上传:")
    result = test_presign_put_upload(bucket, key, initial_data)
    if result['success']:
        print("上传成功")
        print("ETag:", result['etag'])
    else:
        print("上传失败")
        print("错误信息:", result['error'])
    
    return result

def check_bucket_configuration(bucket):
    """检查存储桶配置信息"""
    print("\n" + "="*60)
    print("2. 检查存储桶配置信息")
    print("="*60)
    
    print("\n存储桶配置:")
    print("访问控制列表(ACL):",      query_acl(bucket))
    print("存储桶策略:",   query_policy(bucket))
    print("跨域配置(CORS):",     query_cors(bucket))
    sts_creds = get_temp_sts()
    print("临时安全凭证(STS):",      sts_creds if sts_creds else "<不支持>")
    print("配额配置:",    query_quota(bucket))
    print("生命周期规则:",query_lifecycle(bucket))
    print("标签配置:",  query_tagging(bucket))

def check_content_type_support(bucket):
    """检查ContentType支持情况"""
    print("\n" + "="*60)
    print("3. 检查ContentType支持情况")
    print("="*60)
    
    print("\n检查ContentType支持情况:")
    content_type_results = check_supported_content_types(bucket)
    if content_type_results:
        print("\nContentType支持情况汇总:")
        categories = {
            '文本类型': ['text/'],
            '图片类型': ['image/'],
            '音频类型': ['audio/'],
            '视频类型': ['video/'],
            '应用类型': ['application/'],
            '字体类型': ['font/'],
            '其他类型': ['multipart/', 'message/', 'chemical/']
        }
        
        for category, prefixes in categories.items():
            print(f"\n{category}:")
            for content_type, result in content_type_results.items():
                if any(content_type.startswith(prefix) for prefix in prefixes):
                    status = "✓ 支持" if result['supported'] else "✗ 不支持"
                    stored = f", 存储为: {result['stored_type']}" if result.get('stored_type') else ""
                    error = f", 错误: {result['error']}" if result.get('error') else ""
                    print(f"  {content_type}: {status}{stored}{error}")
                    if result.get('note'):
                        print(f"    说明: {result['note']}")
    else:
        print("检查ContentType支持情况失败")
    
    return content_type_results

def check_extra_args_support(bucket):
    """检查ExtraArgs支持情况"""
    print("\n" + "="*60)
    print("4. 检查ExtraArgs支持情况")
    print("="*60)
    
    print("\n检查ExtraArgs支持情况:")
    extra_args_results = check_supported_extra_args(bucket)
    if extra_args_results:
        print("\nExtraArgs支持情况汇总:")
        for arg_name, result in extra_args_results.items():
            status = "✓ 支持" if result['supported'] else "✗ 不支持"
            value = f", 值: {result['value']}" if result.get('value') else ""
            error = f", 错误: {result['error']}" if result.get('error') else ""
            print(f"{arg_name}: {status}{value}{error}")
            if result.get('note'):
                print(f"  说明: {result['note']}")
    else:
        print("检查ExtraArgs支持情况失败")
    
    return extra_args_results

def check_object_statistics(bucket):
    """检查对象统计信息"""
    print("\n" + "="*60)
    print("5. 检查对象统计信息")
    print("="*60)
    
    print("\n对象统计信息:")
    object_stats = get_object_count(bucket)
    if object_stats:
        print(f"对象总数: {object_stats['total_count']}")
        print(f"总大小: {object_stats['size'] / 1024 / 1024:.2f} MB")
        print(f"文件数量: {object_stats['details']['files']}")
        print(f"文件夹数量: {object_stats['details']['folders']}")
    else:
        print("获取对象统计信息失败")
    
    return object_stats

def check_object_encoding_info(bucket, key):
    """检查对象编码信息"""
    print("\n" + "="*60)
    print("6. 检查对象编码信息")
    print("="*60)
    
    print("\n对象编码信息:")
    encoding_info = check_object_encoding(bucket, key)
    for key, value in encoding_info.items():
        print(f"{key}: {value}")
    
    return encoding_info

def check_thumbnail_support_info(bucket, key):
    """检查缩略图支持信息"""
    print("\n" + "="*60)
    print("7. 检查缩略图支持信息")
    print("="*60)
    
    print("\n缩略图支持信息:")
    thumbnail_info = check_thumbnail_support(bucket, key)
    for key, value in thumbnail_info.items():
        print(f"{key}: {value}")
    
    return thumbnail_info

def check_object_permissions_info(bucket):
    """检查对象操作权限"""
    print("\n" + "="*60)
    print("8. 检查对象操作权限")
    print("="*60)
    
    print("\n检查对象操作权限:")
    permission_results = check_object_permissions(bucket)
    print("\n权限检查结果摘要:")
    for permission, status in permission_results.items():
        if permission != 'errors':
            print(f"{permission}: {'✓ 正常' if status else '✗ 异常'}")
    
    if permission_results['errors']:
        print("\n权限检查错误详情:")
        for error in permission_results['errors']:
            print(f"- {error}")
    
    return permission_results

def check_presigned_get_url(bucket, key):
    """检查预签名GET URL"""
    print("\n" + "="*60)
    print("9. 检查预签名GET URL")
    print("="*60)
    
    print("\n测试预签名GET URL:")
    get_url = generate_presign_get_url(bucket, key)
    if get_url:
        print("生成的URL:", get_url)
        
        response = http_session.get(get_url)
        if response.status_code == 200:
            with open('downloaded_file.txt', 'wb') as f:
                f.write(response.content)
            print("下载成功")
        else:
            print(f"下载失败: HTTP {response.status_code}")
    else:
        print("生成预签名URL失败")
    
    return get_url

def check_multipart_upload(bucket, key):
    """检查分片上传功能"""
    print("\n" + "="*60)
    print("10. 检查分片上传功能")
    print("="*60)
    
    try:
        file_data = load_test_data('multipart')
        presign_result = multipart_presign_and_upload(bucket, key, file_data)
        print("\n生成的预签名URL列表:")
        for i, url in enumerate(presign_result['part_urls'], 1):
            print(f"\n分片 {i}/{presign_result['total_parts']}:")
            print(f"URL: {url}")
            print(f"大小: {presign_result['part_sizes'][i-1] / 1024 / 1024:.2f} MB")
        
        parts = []
        for i, (url, size) in enumerate(zip(presign_result['part_urls'], presign_result['part_sizes'])):
            start = i * presign_result['part_sizes'][0]
            end = start + size
            part_data = file_data[start:end]
            etag = upload_part(url, part_data)
            parts.append({
                'ETag': etag,
                'PartNumber': i + 1
            })
            print(f"\n上传分片 {i+1}/{presign_result['total_parts']} 完成")
            print(f"ETag: {etag}")
        
        complete_multipart_upload(bucket, key, presign_result['upload_id'], parts)
        print("\n分片上传完成\n")
        return True
    except Exception as e:
        print(f"Multipart presign/upload error: {e}")
        return False

def check_thumbnail_functionality(bucket, test_image_path):
    """检查缩略图功能"""
    print("\n" + "="*60)
    print("11. 检查缩略图功能")
    print("="*60)
    
    if not os.path.exists(test_image_path):
        print("Test image not found, generating one...")
        test_image_path = generate_test_image(test_image_path, TEST_IMAGE_SIZE)
        if not test_image_path:
            print("Failed to generate test image")
            return None
    
    print("\nTesting thumbnail functionality...")
    
    # 执行缩略图功能测试
    results = {
        'image_upload': False,
        'thumbnail_generation': False,
        'thumbnail_access': False,
        'errors': []
    }
    
    try:
        # 1. 检查测试图片是否存在
        if not os.path.exists(test_image_path):
            results['errors'].append(f"Test image not found: {test_image_path}")
            return results
            
        # 2. 读取并上传测试图片
        with open(test_image_path, 'rb') as f:
            image_data = f.read()
            upload_result = process_image_upload(bucket, "test_image.jpg", image_data)
            
            if not upload_result:
                results['errors'].append("Failed to upload test image")
                return results
                
            results['image_upload'] = True
            
            # 3. 验证缩略图生成
            if not upload_result['thumbnails']:
                results['errors'].append("No thumbnails were generated")
                return results
                
            results['thumbnail_generation'] = True
            
            # 4. 验证缩略图访问
            for thumb in upload_result['thumbnails']:
                verify_result = verify_thumbnail(bucket, thumb['key'])
                if not verify_result['exists']:
                    results['errors'].append(f"Thumbnail not accessible: {thumb['key']}")
                    continue
                    
                print(f"Verified thumbnail: {thumb['key']}")
                print(f"Size: {verify_result['size']} bytes")
                print(f"Content-Type: {verify_result['content_type']}")
                print(f"Last Modified: {verify_result['last_modified']}")
            
            results['thumbnail_access'] = True
            
    except Exception as e:
        results['errors'].append(f"Unexpected error: {str(e)}")
    
    print("\nTest Results:")
    print(f"Image Upload: {'Success' if results['image_upload'] else 'Failed'}")
    print(f"Thumbnail Generation: {'Success' if results['thumbnail_generation'] else 'Failed'}")
    print(f"Thumbnail Access: {'Success' if results['thumbnail_access'] else 'Failed'}")
    
    if results['errors']:
        print("\nErrors encountered:")
        for error in results['errors']:
            print(f"- {error}")
    
    return results

def check_download_functionality(bucket, normal_key, multipart_key):
    """检查下载功能"""
    print("\n" + "="*60)
    print("12. 检查下载功能")
    print("="*60)
    
    print("\n测试下载功能:")
    
    print("\n1. 测试普通下载:")
    download_result = download_object(bucket, normal_key, "downloaded_file.txt")
    if download_result['success']:
        print("普通下载成功")
        print(f"文件大小: {download_result['size']} 字节")
        print(f"ETag: {download_result['etag']}")
    else:
        print("普通下载失败")
        print(f"错误信息: {download_result['error']}")
    
    print("\n2. 测试分片下载:")
    multipart_result = multipart_download(bucket, multipart_key, "downloaded_file_multipart.txt")
    if multipart_result['success']:
        print("分片下载成功")
        print(f"总大小: {multipart_result['total_size']} 字节")
        print(f"分片数: {multipart_result['parts']}")
    else:
        print("分片下载失败")
        print(f"错误信息: {multipart_result['error']}")
    
    return {
        'normal_download': download_result,
        'multipart_download': multipart_result
    }

def check_migrated_files_info(bucket, check_mode_config=None):
    """检查已迁移文件的ContentType和ExtraArgs信息"""
    print("\n" + "="*60)
    print("13. 检查已迁移文件信息")
    print("="*60)
    
    print("\n检查已迁移文件的ContentType和ExtraArgs信息:")
    print(f"检查模式: {CHECK_MODE['mode']}")
    if CHECK_MODE['mode'] == 'specific':
        print(f"指定文件: {CHECK_MODE['options']['specific_files']}")
    
    migrated_files_info = check_migrated_files_content_type_and_extra_args(bucket, check_mode_config)
    
    if migrated_files_info:
        print(f"\n共检查了 {len(migrated_files_info)} 个文件")
        if CHECK_MODE['options']['output_format'] == 'console':
            print("\n文件信息汇总:")
            for key, info in migrated_files_info.items():
                if 'error' not in info:
                    print(f"\n文件: {key}")
                    if CHECK_MODE['options']['check_content_type']:
                        print(f"ContentType: {info['ContentType']}")
                    if CHECK_MODE['options']['check_metadata']:
                        print(f"Metadata: {info['Metadata']}")
                    if CHECK_MODE['options']['check_extra_args']:
                        print(f"ContentEncoding: {info['ContentEncoding']}")
                        print(f"ContentLanguage: {info['ContentLanguage']}")
                        print(f"CacheControl: {info['CacheControl']}")
                        print(f"ContentDisposition: {info['ContentDisposition']}")
                        print(f"StorageClass: {info['StorageClass']}")
                        print(f"ServerSideEncryption: {info['ServerSideEncryption']}")
                        print(f"WebsiteRedirectLocation: {info['WebsiteRedirectLocation']}")
                        print(f"Expires: {info['Expires']}")
                    print(f"Size: {info['Size']} bytes")
                else:
                    print(f"\n文件: {key}")
                    print(f"错误: {info['error']}")
        else:
            print(f"\n结果已保存到文件: {CHECK_MODE['options']['output_file']}")
    else:
        print("检查已迁移文件失败")
    
    return migrated_files_info

def run_all_checks(bucket, normal_key, multipart_key, test_image_path):
    """运行所有检查功能,根据全局控制配置决定是否执行"""
    print("="*60)
    print("开始执行Ceph S3功能检查")
    print("="*60)
    
    results = {}
    
    # 1. 检查预签名PUT上传初始对象
    if CHECK_CONTROLS['check_upload_initial']:
        results['upload_initial'] = check_upload_initial_object(bucket, normal_key)
    else:
        print("\n" + "="*60)
        print("1. 检查预签名PUT上传初始对象 - 已跳过")
        print("="*60)
    
    # 2. 检查存储桶配置信息
    if CHECK_CONTROLS['check_bucket_config']:
        check_bucket_configuration(bucket)
    else:
        print("\n" + "="*60)
        print("2. 检查存储桶配置信息 - 已跳过")
        print("="*60)
    
    # 3. 检查ContentType支持情况
    if CHECK_CONTROLS['check_content_type']:
        results['content_type'] = check_content_type_support(bucket)
    else:
        print("\n" + "="*60)
        print("3. 检查ContentType支持情况 - 已跳过(默认不检查,耗时较长)")
        print("="*60)
    
    # 4. 检查ExtraArgs支持情况
    if CHECK_CONTROLS['check_extra_args']:
        results['extra_args'] = check_extra_args_support(bucket)
    else:
        print("\n" + "="*60)
        print("4. 检查ExtraArgs支持情况 - 已跳过(默认不检查,耗时较长)")
        print("="*60)
    
    # 5. 检查对象统计信息
    if CHECK_CONTROLS['check_object_stats']:
        results['object_stats'] = check_object_statistics(bucket)
    else:
        print("\n" + "="*60)
        print("5. 检查对象统计信息 - 已跳过(默认不检查,可能耗时)")
        print("="*60)
    
    # 6. 检查对象编码信息
    if CHECK_CONTROLS['check_object_encoding']:
        results['object_encoding'] = check_object_encoding_info(bucket, normal_key)
    else:
        print("\n" + "="*60)
        print("6. 检查对象编码信息 - 已跳过")
        print("="*60)
    
    # 7. 检查缩略图支持信息
    if CHECK_CONTROLS['check_thumbnail_support']:
        results['thumbnail_support'] = check_thumbnail_support_info(bucket, normal_key)
    else:
        print("\n" + "="*60)
        print("7. 检查缩略图支持信息 - 已跳过")
        print("="*60)
    
    # 8. 检查对象操作权限
    if CHECK_CONTROLS['check_object_permissions']:
        results['object_permissions'] = check_object_permissions_info(bucket)
    else:
        print("\n" + "="*60)
        print("8. 检查对象操作权限 - 已跳过")
        print("="*60)
    
    # 9. 检查预签名GET URL
    if CHECK_CONTROLS['check_presigned_get']:
        results['presigned_get'] = check_presigned_get_url(bucket, normal_key)
    else:
        print("\n" + "="*60)
        print("9. 检查预签名GET URL - 已跳过")
        print("="*60)
    
    # 10. 检查分片上传功能
    if CHECK_CONTROLS['check_multipart_upload']:
        results['multipart_upload'] = check_multipart_upload(bucket, multipart_key)
    else:
        print("\n" + "="*60)
        print("10. 检查分片上传功能 - 已跳过")
        print("="*60)
    
    # 11. 检查缩略图功能
    if CHECK_CONTROLS['check_thumbnail_functionality']:
        results['thumbnail_functionality'] = check_thumbnail_functionality(bucket, test_image_path)
    else:
        print("\n" + "="*60)
        print("11. 检查缩略图功能 - 已跳过")
        print("="*60)
    
    # 12. 检查下载功能
    if CHECK_CONTROLS['check_download']:
        results['download'] = check_download_functionality(bucket, normal_key, multipart_key)
    else:
        print("\n" + "="*60)
        print("12. 检查下载功能 - 已跳过")
        print("="*60)
    
    # 13. 检查已迁移文件信息
    if CHECK_CONTROLS['check_migrated_files']:
        results['migrated_files'] = check_migrated_files_info(bucket)
    else:
        print("\n" + "="*60)
        print("13. 检查已迁移文件信息 - 已跳过")
        print("="*60)
    
    print("\n" + "="*60)
    print("所有检查执行完成")
    print("="*60)
    
    return results

# --------- 主程序执行 ---------
if __name__ == '__main__':
    run_all_checks(BUCKET, NORMAL_KEY, MULTIPART_KEY, TEST_IMAGE_PATH)

  

posted on 2026-06-29 13:50  小镇-做题家  阅读(1)  评论(0)    收藏  举报

导航