文件预览

upload_file.py

查看 fadada-document-sign 技能包中的文件内容。

文件内容

scripts/upload_file.py

#!/usr/bin/env python3
"""上传合同文件到法大大平台。

调用接口:
1. /file/get-upload-url - 获取上传URL和法大大文件URL
2. PUT 上传到OSS
3. /file/process - 处理文件获取fileId

作用: 将本地文件上传到法大大平台,获取 fileId 用于创建签署任务

使用方式:
  python upload_file.py --file-path "/path/to/contract.pdf" --file-name "合同.pdf"

参数说明:
  --file-path (必填): 文件路径
  --file-name (必填): 文件名
  --file-format (可选): 文件格式,默认 pdf
  --debug (可选): 开启调试日志
"""

import argparse
import json
import logging
import os
import sys
from pathlib import Path

from utils import (
    FASCClient,
    FileError,
    load_config,
    print_result,
    success_response,
    error_response,
)

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    stream=sys.stderr,
)
logger = logging.getLogger(__name__)


def validate_file(file_path: str) -> Path:
    """验证文件是否存在且格式正确。"""
    path = Path(file_path)

    if not path.exists():
        raise FileError(f"文件不存在: {file_path}")

    if not path.is_file():
        raise FileError(f"路径不是文件: {file_path}")

    # 检查文件大小(最大50MB)
    file_size = path.stat().st_size
    max_size = 50 * 1024 * 1024  # 50MB
    if file_size > max_size:
        raise FileError(f"文件大小超过限制(50MB): {path.stat().st_size / 1024 / 1024:.2f}MB")

    # 检查文件格式
    suffix = path.suffix.lower()
    if suffix not in ['.pdf', '.ofd']:
        raise FileError(f"文件格式不支持: {suffix},仅支持 PDF 和 OFD 格式")

    return path


def get_upload_url_and_fdd(client: FASCClient, file_type: str = "doc") -> dict:
    """获取文件上传URL和法大大文件URL。

    根据官方API文档实现,返回 uploadUrl 和 fddFileUrl
    """
    biz_content = {"fileType": file_type}

    logger.info(f"获取上传URL, fileType={file_type}")

    result = client.request("/file/get-upload-url", biz_content)

    logger.info(f"上传URL响应: {json.dumps(result, ensure_ascii=False)}")

    return result


def upload_file_to_oss(upload_url: str, file_path: Path) -> bool:
    """上传文件到OSS(使用PUT方法)。

    Args:
        upload_url: OSS上传URL
        file_path: 本地文件路径

    Returns:
        是否上传成功
    """
    import requests

    with open(file_path, 'rb') as f:
        response = requests.put(upload_url, data=f, timeout=60)

    if response.status_code != 200:
        logger.error(f"上传失败: HTTP {response.status_code}")
        return False

    logger.info(f"文件上传成功")
    return True


def process_file_by_fdd_url(client: FASCClient, fdd_file_url: str, file_name: str, file_format: str = "pdf") -> dict:
    """通过法大大文件URL处理文件,获取fileId。

    Args:
        client: FASC客户端
        fdd_file_url: 法大大文件URL
        file_name: 文件名
        file_format: 文件格式 (pdf/ofd)

    Returns:
        包含 fileId 等信息
    """
    biz_content = {
        "fddFileUrlList": [{
            "fileType": "doc",  # 使用 doc 类型
            "fddFileUrl": fdd_file_url,
            "fileName": file_name,
            "fileFormat": file_format.lower()
        }]
    }

    logger.info(f"处理文件: fddFileUrl={fdd_file_url}")

    result = client.request("/file/process", biz_content)

    logger.info(f"文件处理响应: {json.dumps(result, ensure_ascii=False)}")

    return result


def main():
    parser = argparse.ArgumentParser(
        description="上传合同文件到法大大平台",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument("--file-path", required=True, help="文件路径")
    parser.add_argument("--file-name", required=True, help="文件名")
    parser.add_argument("--file-format", default="pdf", help="文件格式 (pdf/ofd),默认 pdf")
    parser.add_argument("--debug", action="store_true", help="开启调试日志")

    args = parser.parse_args()

    # 设置日志级别
    if args.debug:
        logger.setLevel(logging.DEBUG)

    try:
        # 1. 验证文件
        logger.info(f"验证文件: {args.file_path}")
        file_path = validate_file(args.file_path)
        file_size = file_path.stat().st_size
        logger.info(f"文件验证通过: size={file_size} bytes")

        # 2. 加载配置并创建客户端
        config = load_config()
        client = FASCClient(config)

        # 3. 获取上传URL和fddFileUrl
        upload_info = get_upload_url_and_fdd(client, "doc")

        upload_url = upload_info.get("uploadUrl")
        fdd_file_url = upload_info.get("fddFileUrl")

        if not upload_url or not fdd_file_url:
            print_result(error_response("获取上传URL失败"))
            return

        logger.info(f"获取到 uploadUrl 和 fddFileUrl")

        # 4. 上传文件到OSS(PUT方法)
        logger.info(f"上传文件到OSS...")
        if not upload_file_to_oss(upload_url, file_path):
            print_result(error_response("文件上传失败"))
            return

        # 5. 处理文件获取fileId
        logger.info("处理文件...")
        process_result = process_file_by_fdd_url(
            client,
            fdd_file_url,
            args.file_name,
            args.file_format
        )

        # 从响应中提取 fileId
        file_id_list = process_result.get("fileIdList", [])
        if file_id_list:
            file_id = file_id_list[0].get("fileId")
        else:
            file_id = process_result.get("fileId")

        if not file_id:
            print_result(error_response("获取文件ID失败"))
            return

        # 6. 返回成功结果
        result = success_response({
            "fileId": file_id,
            "fileName": args.file_name,
            "fileSize": file_size,
            "fileFormat": args.file_format,
            "fddFileUrl": fdd_file_url,
        })
        print_result(result)

    except FileError as e:
        logger.error(f"文件错误: {e}")
        print_result(error_response(str(e)))
    except Exception as e:
        logger.error(f"上传失败: {e}")
        print_result(error_response(f"上传文件失败: {e}"))


if __name__ == "__main__":
    main()