diff --git a/.env b/.env index edf2747..eef54bc 100644 --- a/.env +++ b/.env @@ -1,68 +1,26 @@ -# ========== AI服务提供商配置 ========== -# 选择使用的AI服务提供商 -# 可选值: 'huawei' 或 'siliconflow' -# 默认值: 'siliconflow' +# MinIO配置 +MINIO_ENDPOINT=10.100.31.21:9000 +MINIO_ACCESS_KEY=minio_PC8dcY +MINIO_SECRET_KEY=minio_7k7RNJ +MINIO_BUCKET=finyx +MINIO_SECURE=false # 重要:新服务器使用HTTP,必须是false + +# 其他配置 AI_PROVIDER=siliconflow - -# ========== 华为大模型API配置 ========== -# 当 AI_PROVIDER=huawei 时使用以下配置 - -# API端点地址 -HUAWEI_API_ENDPOINT=http://10.100.31.26:3001/v1/chat/completions - -# API密钥 -HUAWEI_API_KEY=sk-PoeiV3qwyTIRqcVc84E8E24cD2904872859a87922e0d9186 - -# 模型名称 -HUAWEI_MODEL=DeepSeek-R1-Distill-Llama-70B - -# API超时配置(秒) -# 开启思考模式时,响应时间会显著增加,需要更长的超时时间 -# 默认180秒(3分钟) -HUAWEI_API_TIMEOUT=180 - -# API最大token数配置 -# 开启思考模式时,模型可能生成更长的响应,需要更多的token -# 默认12000 -HUAWEI_API_MAX_TOKENS=12000 - -# ========== 硅基流动API配置 ========== -# 当 AI_PROVIDER=siliconflow 时使用以下配置 - -# API端点地址(默认值,通常不需要修改) -SILICONFLOW_URL=https://api.siliconflow.cn/v1/chat/completions - -# API密钥(必需) -SILICONFLOW_API_KEY=sk-pgujibohpenkomkwlufexmqzyckglgogdiubfplgqxkfqgfu - -# 模型名称(默认值,通常不需要修改) -SILICONFLOW_MODEL=Qwen/Qwen2.5-72B-Instruct - -# API超时配置(秒) -# 默认120秒 -SILICONFLOW_API_TIMEOUT=120 - -# API最大token数配置 -# 默认2000 -SILICONFLOW_API_MAX_TOKENS=2000 - -# ========== 数据库配置 ========== DB_HOST=152.136.177.240 +DB_NAME=finyx +DB_PASSWORD=6QsGK6MpePZDE57Z DB_PORT=5012 DB_USER=finyx -DB_PASSWORD=6QsGK6MpePZDE57Z -DB_NAME=finyx - -# ========== MinIO配置(可选,文档生成功能需要) ========== -MINIO_ENDPOINT=minio.datacubeworld.com:9000 -MINIO_ACCESS_KEY=JOLXFXny3avFSzB0uRA5 -MINIO_SECRET_KEY=G1BR8jStNfovkfH5ou39EmPl34E4l7dGrnd3Cz0I -MINIO_BUCKET=finyx -MINIO_SECURE=true - -# ========== 服务配置 ========== -# 服务端口 -PORT=7500 - -# 调试模式(true/false) DEBUG=False +HUAWEI_API_ENDPOINT=http://10.100.31.26:3001/v1/chat/completions +HUAWEI_API_KEY=sk-PoeiV3qwyTIRqcVc84E8E24cD2904872859a87922e0d9186 +HUAWEI_API_MAX_TOKENS=12000 +HUAWEI_API_TIMEOUT=180 +HUAWEI_MODEL=DeepSeek-R1-Distill-Llama-70B +PORT=7500 +SILICONFLOW_API_KEY=sk-pgujibohpenkomkwlufexmqzyckglgogdiubfplgqxkfqgfu +SILICONFLOW_API_MAX_TOKENS=2000 +SILICONFLOW_API_TIMEOUT=120 +SILICONFLOW_MODEL=Qwen/Qwen2.5-72B-Instruct +SILICONFLOW_URL=https://api.siliconflow.cn/v1/chat/completions diff --git a/MinIO迁移完成总结.md b/MinIO迁移完成总结.md new file mode 100644 index 0000000..40d9449 --- /dev/null +++ b/MinIO迁移完成总结.md @@ -0,0 +1,136 @@ +# MinIO迁移完成总结 + +## ✅ 完成状态 + +所有任务已成功完成! + +## 执行的操作 + +### 1. 环境变量配置 ✅ + +已更新 `.env` 文件,配置如下: +``` +MINIO_ENDPOINT=10.100.31.21:9000 +MINIO_ACCESS_KEY=minio_PC8dcY +MINIO_SECRET_KEY=minio_7k7RNJ +MINIO_BUCKET=finyx +MINIO_SECURE=false # 重要:新服务器使用HTTP,必须是false +``` + +**注意**:虽然您提到 `MINIO_SECURE=true`,但根据实际测试,新MinIO服务器使用HTTP协议,不支持HTTPS,所以实际使用的是 `false`。脚本已自动处理此问题。 + +### 2. 模板文件上传 ✅ + +成功上传了 **20个模板文件** 到新MinIO服务器: + +- ✅ 8-1请示报告卡(初核报告结论) .docx +- ✅ 谈话通知书第三联.docx +- ✅ 谈话通知书第一联.docx +- ✅ 谈话通知书第二联.docx +- ✅ 2谈话审批表.docx +- ✅ 2.初步核实审批表(XXX).docx +- ✅ 3.附件初核方案(XXX).docx +- ✅ 4.谈话方案.docx +- ✅ 1.谈话笔录.docx +- ✅ 3.谈话前安全风险评估表.docx +- ✅ 2.谈话询问对象情况摸底调查30问.docx +- ✅ 5.谈话后安全风险评估表.docx +- ✅ 5.陪送交接单(新).docx +- ✅ 4.点对点交接单.docx +- ✅ 7.办案人员-办案安全保密承诺书.docx +- ✅ 3.被谈话人权利义务告知书.docx +- ✅ 1.请示报告卡(XXX).docx +- ✅ 8.XXX初核情况报告.docx +- ✅ 1.请示报告卡(初核谈话).docx +- ✅ 6.1保密承诺书(谈话对象使用-非中共党员用).docx + +**上传统计**: +- 数据库模板数:20 +- 本地文件数:21 +- 成功匹配:20 +- 成功上传:20 +- 上传失败:0 + +**未匹配的文件**: +- 本地有1个文件在数据库中没有找到对应配置: + - `6.2保密承诺书(谈话对象使用-中共党员用).docx` + +### 3. 路径验证 ✅ + +所有文件的上传路径都与数据库中的 `file_path` 字段完全一致: +- 格式:`/615873064429507639/TEMPLATE/2025/12/{文件名}.docx` +- 所有文件都已验证上传成功 + +## 诊断测试结果 + +运行诊断脚本,所有测试通过(6/6,100%): + +- ✅ 环境变量配置正确 +- ✅ MinIO连接成功 +- ✅ 模板下载成功 +- ✅ 文件上传成功 +- ✅ 预签名URL生成成功 +- ✅ 目录结构正常 + +## 重要提示 + +### 关于 MINIO_SECURE + +虽然您提到 `MINIO_SECURE=true`,但新MinIO服务器 `10.100.31.21:9000` 实际使用HTTP协议,不支持HTTPS。脚本已自动检测并使用了正确的配置(`false`)。 + +如果将来需要启用HTTPS,需要: +1. 在MinIO服务器上配置SSL证书 +2. 然后将 `MINIO_SECURE` 设置为 `true` + +### 关于目录结构 + +MinIO是对象存储,**不需要创建目录**。对象名称可以包含路径分隔符(如 `/`),MinIO会自动处理。例如: +- `615873064429507639/TEMPLATE/2025/12/template.docx` + +## 下一步操作 + +### 1. 重启应用服务 ⚠️ + +**重要**:更新环境变量后,必须重启应用服务才能生效! + +### 2. 测试文档生成功能 + +重启后,可以测试文档生成接口,验证功能是否正常。 + +### 3. 处理未匹配的文件(可选) + +如果需要在数据库中添加 `6.2保密承诺书(谈话对象使用-中共党员用).docx` 的配置,可以: +1. 在数据库中创建对应的 `f_polic_file_config` 记录 +2. 设置正确的 `file_path` +3. 重新运行上传脚本 + +## 相关文件 + +- `upload_templates_to_new_minio.py` - 批量上传脚本(已执行) +- `diagnose_minio_document_generation.py` - 诊断脚本(已验证) +- `fix_minio_config.py` - 配置修复脚本(已执行) +- `.env` - 环境变量配置文件(已更新) + +## 验证命令 + +如果需要再次验证,可以运行: + +```bash +# 诊断MinIO配置和功能 +python diagnose_minio_document_generation.py + +# 检查模板文件状态 +python fix_minio_config.py +``` + +## 总结 + +✅ **所有问题已解决!** + +1. ✅ 环境变量已正确配置 +2. ✅ 所有模板文件已成功上传到新MinIO服务器 +3. ✅ 上传路径与数据库中的 `file_path` 完全一致 +4. ✅ 所有功能测试通过 + +**现在可以重启应用服务并测试文档生成功能了!** + diff --git a/MinIO远程服务器测试结果.md b/MinIO远程服务器测试结果.md new file mode 100644 index 0000000..2c53ce9 --- /dev/null +++ b/MinIO远程服务器测试结果.md @@ -0,0 +1,78 @@ +# MinIO远程服务器测试结果 + +## 测试时间 +2025-12-12 09:53:05 + +## 服务器配置信息 +- **端点**: 10.100.31.21:9000 +- **存储桶**: finyx +- **访问密钥**: minio_PC8dcY +- **密钥**: minio_7k7RNJ + +## 测试结果 + +### ✅ 所有测试通过 (5/5, 100%) + +1. **连接测试**: ✅ 通过 + - HTTPS连接失败(服务器不支持HTTPS) + - HTTP连接成功 + - 成功列出存储桶 + +2. **存储桶检查**: ✅ 通过 + - 存储桶 `finyx` 存在 + - 创建时间: 2025-11-04 08:55:15 + +3. **文件上传**: ✅ 通过 + - 测试文件成功上传 + - 上传路径: `/615873064429507639/TEST/2025/12/test_file_20251212095305.txt` + +4. **URL生成**: ✅ 通过 + - 预签名URL生成成功(7天有效期) + +5. **URL下载**: ✅ 通过 + - 下载测试成功 + - 状态码: 200 + - 内容验证正确 + +## 重要发现 + +### ⚠️ SSL/TLS配置 +- **用户配置**: `MINIO_SECURE=true` +- **实际需要**: `MINIO_SECURE=false` +- **原因**: 服务器使用HTTP协议,不支持HTTPS + +### 正确的配置 +```bash +MINIO_ENDPOINT=10.100.31.21:9000 +MINIO_ACCESS_KEY=minio_PC8dcY +MINIO_SECRET_KEY=minio_7k7RNJ +MINIO_BUCKET=finyx +MINIO_SECURE=false # 注意:应该是false,不是true +``` + +## 测试文件信息 +- **上传路径**: `/615873064429507639/TEST/2025/12/test_file_20251212095305.txt` +- **文件大小**: 188 字节 +- **预签名URL**: 已生成(7天有效) + +## 存储桶内容 +存储桶中已存在多个文件,包括: +- AI应用头像文件 +- 文档文件(.docx, .txt) +- 按日期组织的文件结构 + +## 结论 + +✅ **MinIO远程服务器连接正常,可以正常使用!** + +### 使用建议 +1. 在环境变量或配置文件中,将 `MINIO_SECURE` 设置为 `false` +2. 服务器地址 `10.100.31.21:9000` 可以正常访问 +3. 存储桶 `finyx` 已存在且可正常读写 +4. 文件上传和下载功能正常 + +### 注意事项 +- 服务器使用HTTP协议,不是HTTPS +- 如果需要在生产环境使用HTTPS,需要配置MinIO服务器的SSL证书 +- 当前配置可以正常使用,但建议在生产环境中使用HTTPS以确保安全性 + diff --git a/MinIO问题分析和解决方案.md b/MinIO问题分析和解决方案.md new file mode 100644 index 0000000..bab0b48 --- /dev/null +++ b/MinIO问题分析和解决方案.md @@ -0,0 +1,138 @@ +# MinIO文档生成失败问题分析和解决方案 + +## 问题诊断结果 + +根据诊断脚本的结果,发现了以下问题: + +### ✅ 正常的功能 +1. **MinIO连接** - 使用 `secure=false` 可以正常连接 +2. **存储桶存在** - `finyx` 存储桶已存在 +3. **文件上传** - 上传功能正常 +4. **预签名URL生成** - URL生成功能正常 + +### ❌ 发现的问题 + +#### 1. 环境变量配置错误(关键问题) + +当前环境变量还是旧配置: +- `MINIO_ENDPOINT`: `minio.datacubeworld.com:9000` ❌ + - 应该是: `10.100.31.21:9000` +- `MINIO_ACCESS_KEY`: `JOLXFXny3avFSzB0uRA5` ❌ + - 应该是: `minio_PC8dcY` +- `MINIO_SECURE`: `true` ❌ + - 应该是: `false` (**重要:新服务器使用HTTP,不是HTTPS**) + +#### 2. 模板文件缺失(关键问题) + +数据库中的模板文件在新MinIO服务器上不存在: +- 例如:`/615873064429507639/TEMPLATE/2025/12/8-1请示报告卡(初核报告结论) .docx` +- 错误信息:`Object does not exist` + +## 解决方案 + +### 步骤1:更新环境变量配置 + +#### 方法A:使用修复脚本(推荐) + +运行修复脚本自动创建/更新 `.env` 文件: + +```bash +python fix_minio_config.py +``` + +#### 方法B:手动创建/更新 .env 文件 + +在项目根目录创建或更新 `.env` 文件,内容如下: + +```bash +# MinIO配置 +MINIO_ENDPOINT=10.100.31.21:9000 +MINIO_ACCESS_KEY=minio_PC8dcY +MINIO_SECRET_KEY=minio_7k7RNJ +MINIO_BUCKET=finyx +MINIO_SECURE=false # 重要:新服务器使用HTTP,必须是false +``` + +**⚠️ 重要提示:`MINIO_SECURE` 必须设置为 `false`,不是 `true`!** + +### 步骤2:迁移模板文件 + +模板文件需要从旧MinIO服务器迁移到新服务器,或者重新上传。 + +#### 方法A:从旧服务器迁移(如果有访问权限) + +1. 从旧MinIO服务器下载所有模板文件 +2. 上传到新MinIO服务器 + +#### 方法B:重新上传模板文件 + +1. 从本地模板目录重新上传模板文件 +2. 确保文件路径与数据库中的 `file_path` 字段匹配 + +### 步骤3:验证配置 + +运行诊断脚本验证配置是否正确: + +```bash +python diagnose_minio_document_generation.py +``` + +应该看到所有测试都通过。 + +### 步骤4:重启应用 + +**重要:更新环境变量后,必须重启应用服务才能生效!** + +## 关于目录结构 + +**MinIO是对象存储,不需要创建目录。** + +- 对象名称可以包含路径分隔符(如 `/`),MinIO会自动处理 +- 例如:`615873064429507639/TEMPLATE/2024/12/template.docx` +- 上传文件时,MinIO会自动创建"虚拟目录"结构 + +## 常见问题 + +### Q1: 为什么 `MINIO_SECURE` 必须是 `false`? + +A: 新MinIO服务器 `10.100.31.21:9000` 使用HTTP协议,不支持HTTPS。如果设置为 `true`,客户端会尝试使用HTTPS连接,导致连接失败。 + +### Q2: 如何确认服务器使用HTTP还是HTTPS? + +A: 可以尝试访问 `http://10.100.31.21:9000` 和 `https://10.100.31.21:9000`,看哪个能正常访问。根据测试结果,该服务器只支持HTTP。 + +### Q3: 模板文件路径格式是什么? + +A: 模板文件路径格式为:`/{tenant_id}/TEMPLATE/{year}/{month}/{filename}.docx` +- 例如:`/615873064429507639/TEMPLATE/2025/12/template.docx` +- 这个路径存储在数据库的 `f_polic_file_config.file_path` 字段中 + +### Q4: 如何检查模板文件是否存在? + +A: 运行诊断脚本 `diagnose_minio_document_generation.py`,它会自动检查所有模板文件是否存在。 + +## 测试清单 + +完成修复后,请验证以下功能: + +- [ ] MinIO连接正常 +- [ ] 存储桶存在 +- [ ] 模板文件可以下载 +- [ ] 生成的文档可以上传 +- [ ] 预签名URL可以正常生成 +- [ ] 文档生成接口可以正常调用 + +## 相关文件 + +- `diagnose_minio_document_generation.py` - 诊断脚本 +- `fix_minio_config.py` - 配置修复脚本 +- `test_minio_remote_server.py` - MinIO连接测试脚本 +- `MinIO远程服务器测试结果.md` - 之前的测试结果 + +## 联系支持 + +如果问题仍然存在,请提供: +1. 诊断脚本的完整输出 +2. 应用日志中的错误信息 +3. 具体的错误场景(哪个接口、哪个文件ID等) + diff --git a/diagnose_minio_document_generation.py b/diagnose_minio_document_generation.py new file mode 100644 index 0000000..f4104f2 --- /dev/null +++ b/diagnose_minio_document_generation.py @@ -0,0 +1,482 @@ +""" +诊断MinIO文档生成问题 +测试新MinIO服务器配置下的文档生成流程 +""" +import os +import sys +from minio import Minio +from minio.error import S3Error +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + +# 新MinIO配置(用户提供) +NEW_MINIO_CONFIG = { + 'endpoint': '10.100.31.21:9000', + 'access_key': 'minio_PC8dcY', + 'secret_key': 'minio_7k7RNJ', + 'secure': False # 重要:根据测试结果,应该是false +} +BUCKET_NAME = 'finyx' +TENANT_ID = 615873064429507639 + +def print_section(title): + """打印章节标题""" + print("\n" + "="*70) + print(f" {title}") + print("="*70) + +def print_result(success, message): + """打印测试结果""" + status = "[OK]" if success else "[FAIL]" + print(f"{status} {message}") + +def check_environment_variables(): + """检查环境变量配置""" + print_section("1. 检查环境变量配置") + + env_vars = { + 'MINIO_ENDPOINT': os.getenv('MINIO_ENDPOINT'), + 'MINIO_ACCESS_KEY': os.getenv('MINIO_ACCESS_KEY'), + 'MINIO_SECRET_KEY': os.getenv('MINIO_SECRET_KEY'), + 'MINIO_BUCKET': os.getenv('MINIO_BUCKET'), + 'MINIO_SECURE': os.getenv('MINIO_SECURE') + } + + print("\n当前环境变量配置:") + for key, value in env_vars.items(): + if key == 'MINIO_SECRET_KEY' and value: + # 隐藏密钥的部分内容 + masked_value = value[:8] + '***' if len(value) > 8 else '***' + print(f" {key}: {masked_value}") + else: + print(f" {key}: {value}") + + # 检查配置是否正确 + issues = [] + if env_vars['MINIO_ENDPOINT'] != NEW_MINIO_CONFIG['endpoint']: + issues.append(f"MINIO_ENDPOINT 应该是 '{NEW_MINIO_CONFIG['endpoint']}',当前是 '{env_vars['MINIO_ENDPOINT']}'") + + if env_vars['MINIO_ACCESS_KEY'] != NEW_MINIO_CONFIG['access_key']: + issues.append(f"MINIO_ACCESS_KEY 应该是 '{NEW_MINIO_CONFIG['access_key']}',当前是 '{env_vars['MINIO_ACCESS_KEY']}'") + + secure_value = env_vars['MINIO_SECURE'] + if secure_value and secure_value.lower() == 'true': + issues.append(f"[WARN] MINIO_SECURE 设置为 'true',但新服务器使用HTTP,应该设置为 'false'") + + if issues: + print("\n[WARN] 发现配置问题:") + for issue in issues: + print(f" - {issue}") + print_result(False, "环境变量配置需要更新") + return False + else: + print_result(True, "环境变量配置正确") + return True + +def test_minio_connection(): + """测试MinIO连接""" + print_section("2. 测试MinIO连接") + + # 先尝试用户配置的secure值 + secure_values = [False, True] # 优先尝试false(根据测试结果) + + for secure in secure_values: + try: + print(f"\n尝试连接(secure={secure})...") + client = Minio( + NEW_MINIO_CONFIG['endpoint'], + access_key=NEW_MINIO_CONFIG['access_key'], + secret_key=NEW_MINIO_CONFIG['secret_key'], + secure=secure + ) + + # 测试连接:列出存储桶 + buckets = client.list_buckets() + print_result(True, f"MinIO连接成功(secure={secure})") + + print(f"\n 连接信息:") + print(f" 端点: {NEW_MINIO_CONFIG['endpoint']}") + print(f" 使用HTTPS: {secure}") + print(f" 访问密钥: {NEW_MINIO_CONFIG['access_key']}") + + print(f"\n 可用存储桶:") + for bucket in buckets: + print(f" - {bucket.name} (创建时间: {bucket.creation_date})") + + # 检查目标存储桶 + bucket_exists = client.bucket_exists(BUCKET_NAME) + if bucket_exists: + print_result(True, f"存储桶 '{BUCKET_NAME}' 存在") + else: + print_result(False, f"存储桶 '{BUCKET_NAME}' 不存在") + print(f" 建议:需要创建存储桶 '{BUCKET_NAME}'") + return None, False + + return client, True + + except Exception as e: + error_msg = str(e) + if secure == True: + print_result(False, f"使用HTTPS连接失败: {error_msg}") + print(f" 将尝试使用HTTP连接...") + continue + else: + print_result(False, f"MinIO连接失败: {error_msg}") + import traceback + traceback.print_exc() + return None, False + + return None, False + +def test_template_download(client): + """测试模板下载功能""" + print_section("3. 测试模板下载功能") + + if not client: + print_result(False, "MinIO客户端未连接,跳过测试") + return False + + try: + # 查询数据库获取一个模板文件路径 + import pymysql + + db_config = { + 'host': os.getenv('DB_HOST', '152.136.177.240'), + 'port': int(os.getenv('DB_PORT', 5012)), + 'user': os.getenv('DB_USER', 'finyx'), + 'password': os.getenv('DB_PASSWORD', '6QsGK6MpePZDE57Z'), + 'database': os.getenv('DB_NAME', 'finyx'), + 'charset': 'utf8mb4' + } + + conn = pymysql.connect(**db_config) + cursor = conn.cursor(pymysql.cursors.DictCursor) + + # 查询一个启用的模板 + sql = """ + SELECT id, name, file_path + FROM f_polic_file_config + WHERE tenant_id = %s + AND state = 1 + AND file_path IS NOT NULL + AND file_path != '' + LIMIT 1 + """ + cursor.execute(sql, (TENANT_ID,)) + template = cursor.fetchone() + + cursor.close() + conn.close() + + if not template: + print_result(False, "数据库中没有找到可用的模板文件") + print(" 建议:检查数据库中的 f_polic_file_config 表") + return False + + print(f"\n找到模板:") + print(f" ID: {template['id']}") + print(f" 名称: {template['name']}") + print(f" 文件路径: {template['file_path']}") + + # 尝试下载模板 + object_name = template['file_path'].lstrip('/') + print(f"\n尝试下载模板...") + print(f" 对象名称: {object_name}") + + # 检查文件是否存在 + try: + stat = client.stat_object(BUCKET_NAME, object_name) + print_result(True, f"模板文件存在(大小: {stat.size:,} 字节)") + except S3Error as e: + if e.code == 'NoSuchKey': + print_result(False, f"模板文件不存在: {object_name}") + print(f" 错误: {str(e)}") + print(f" 建议:检查MinIO服务器上是否存在该文件") + return False + else: + raise + + # 尝试下载(使用临时文件) + import tempfile + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.docx') + temp_file.close() + + try: + client.fget_object(BUCKET_NAME, object_name, temp_file.name) + file_size = os.path.getsize(temp_file.name) + print_result(True, f"模板下载成功(大小: {file_size:,} 字节)") + + # 清理临时文件 + os.unlink(temp_file.name) + return True + except Exception as e: + print_result(False, f"模板下载失败: {str(e)}") + # 清理临时文件 + if os.path.exists(temp_file.name): + os.unlink(temp_file.name) + return False + + except Exception as e: + print_result(False, f"测试模板下载时出错: {str(e)}") + import traceback + traceback.print_exc() + return False + +def test_file_upload(client): + """测试文件上传功能""" + print_section("4. 测试文件上传功能") + + if not client: + print_result(False, "MinIO客户端未连接,跳过测试") + return False + + try: + # 创建一个测试文件 + import tempfile + from datetime import datetime + + test_content = b"Test document content for MinIO upload test" + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.docx') + temp_file.write(test_content) + temp_file.close() + + print(f"\n创建测试文件: {temp_file.name}") + + # 生成上传路径 + now = datetime.now() + timestamp = f"{now.strftime('%Y%m%d%H%M%S')}{now.microsecond:06d}" + object_name = f"{TENANT_ID}/TEST/{timestamp}/test_upload.docx" + + print(f"\n尝试上传文件...") + print(f" 对象名称: {object_name}") + + # 上传文件 + client.fput_object( + BUCKET_NAME, + object_name, + temp_file.name, + content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document' + ) + + print_result(True, "文件上传成功") + + # 验证文件是否存在 + stat = client.stat_object(BUCKET_NAME, object_name) + print(f" 上传的文件大小: {stat.size:,} 字节") + + # 清理测试文件 + os.unlink(temp_file.name) + + # 可选:删除测试文件 + try: + client.remove_object(BUCKET_NAME, object_name) + print(f" 已清理测试文件: {object_name}") + except: + pass + + return True + + except Exception as e: + print_result(False, f"文件上传失败: {str(e)}") + import traceback + traceback.print_exc() + # 清理临时文件 + if 'temp_file' in locals() and os.path.exists(temp_file.name): + os.unlink(temp_file.name) + return False + +def test_presigned_url(client): + """测试预签名URL生成""" + print_section("5. 测试预签名URL生成") + + if not client: + print_result(False, "MinIO客户端未连接,跳过测试") + return False + + try: + # 使用一个测试对象名称 + from datetime import datetime, timedelta + now = datetime.now() + timestamp = f"{now.strftime('%Y%m%d%H%M%S')}{now.microsecond:06d}" + test_object_name = f"{TENANT_ID}/TEST/{timestamp}/test_url.docx" + + # 先创建一个测试文件 + import tempfile + test_content = b"Test content" + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.docx') + temp_file.write(test_content) + temp_file.close() + + # 上传测试文件 + client.fput_object( + BUCKET_NAME, + test_object_name, + temp_file.name, + content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document' + ) + os.unlink(temp_file.name) + + print(f"\n生成预签名URL...") + print(f" 对象名称: {test_object_name}") + + # 生成预签名URL + url = client.presigned_get_object( + BUCKET_NAME, + test_object_name, + expires=timedelta(days=7) + ) + + print_result(True, "预签名URL生成成功") + print(f"\n URL: {url[:100]}...") + + # 清理测试文件 + try: + client.remove_object(BUCKET_NAME, test_object_name) + except: + pass + + return True + + except Exception as e: + print_result(False, f"预签名URL生成失败: {str(e)}") + import traceback + traceback.print_exc() + return False + +def check_directory_structure(client): + """检查目录结构(MinIO是对象存储,不需要创建目录)""" + print_section("6. 检查目录结构") + + if not client: + print_result(False, "MinIO客户端未连接,跳过测试") + return False + + print("\n说明:MinIO是对象存储,不需要创建目录。") + print("对象名称可以包含路径分隔符(如 '/'),MinIO会自动处理。") + print("\n检查存储桶中的对象结构...") + + try: + # 列出一些对象,查看目录结构 + objects = client.list_objects(BUCKET_NAME, prefix=f"{TENANT_ID}/", recursive=False) + + prefixes = set() + count = 0 + for obj in objects: + count += 1 + if count <= 20: # 只显示前20个 + # 提取前缀(目录) + parts = obj.object_name.split('/') + if len(parts) > 1: + prefix = '/'.join(parts[:-1]) + prefixes.add(prefix) + + if prefixes: + print(f"\n发现的前缀(目录)结构(前20个对象):") + for prefix in sorted(prefixes): + print(f" - {prefix}/") + + print_result(True, f"存储桶结构正常(已检查 {count} 个对象)") + return True + + except Exception as e: + print_result(False, f"检查目录结构失败: {str(e)}") + import traceback + traceback.print_exc() + return False + +def print_recommendations(): + """打印修复建议""" + print_section("修复建议") + + print("\n根据诊断结果,请执行以下步骤:") + print("\n1. 更新环境变量配置(.env文件或系统环境变量):") + print(" MINIO_ENDPOINT=10.100.31.21:9000") + print(" MINIO_ACCESS_KEY=minio_PC8dcY") + print(" MINIO_SECRET_KEY=minio_7k7RNJ") + print(" MINIO_BUCKET=finyx") + print(" MINIO_SECURE=false # [IMPORTANT] 重要:必须是false,不是true") + + print("\n2. 确保存储桶存在:") + print(f" 存储桶名称: {BUCKET_NAME}") + print(" 如果不存在,需要创建存储桶") + + print("\n3. 确保模板文件已上传到MinIO:") + print(" 检查数据库中的 f_polic_file_config 表的 file_path 字段") + print(" 确保对应的文件在MinIO服务器上存在") + + print("\n4. 关于目录:") + print(" MinIO是对象存储,不需要创建目录") + print(" 对象名称可以包含路径分隔符(如 '/'),MinIO会自动处理") + print(" 例如: 615873064429507639/TEMPLATE/2024/12/template.docx") + + print("\n5. 重启应用:") + print(" 更新环境变量后,需要重启应用服务才能生效") + + print("\n[IMPORTANT] MINIO_SECURE=false # 注意:必须是false,不是true") + +def main(): + """主函数""" + print("\n" + "="*70) + print(" MinIO文档生成问题诊断工具") + print("="*70) + print(f"\n新MinIO服务器配置:") + print(f" 端点: {NEW_MINIO_CONFIG['endpoint']}") + print(f" 存储桶: {BUCKET_NAME}") + print(f" 访问密钥: {NEW_MINIO_CONFIG['access_key']}") + print(f" 使用HTTPS: {NEW_MINIO_CONFIG['secure']}") + + results = {} + + try: + # 1. 检查环境变量 + results['环境变量'] = check_environment_variables() + + # 2. 测试MinIO连接 + client, bucket_exists = test_minio_connection() + results['MinIO连接'] = client is not None and bucket_exists + + if client and bucket_exists: + # 3. 测试模板下载 + results['模板下载'] = test_template_download(client) + + # 4. 测试文件上传 + results['文件上传'] = test_file_upload(client) + + # 5. 测试预签名URL + results['预签名URL'] = test_presigned_url(client) + + # 6. 检查目录结构 + results['目录结构'] = check_directory_structure(client) + + # 总结 + print_section("诊断总结") + + print("\n测试结果:") + for test_name, success in results.items(): + status = "[OK] 通过" if success else "[FAIL] 失败" + print(f" {test_name}: {status}") + + passed = sum(1 for v in results.values() if v) + total = len(results) + + print(f"\n通过率: {passed}/{total} ({passed*100//total if total > 0 else 0}%)") + + if passed == total: + print("\n[OK] 所有测试通过!MinIO配置正确,文档生成应该可以正常工作。") + else: + print("\n[WARN] 部分测试失败,请查看上面的错误信息并按照建议进行修复。") + print_recommendations() + + except KeyboardInterrupt: + print("\n\n诊断已中断") + except Exception as e: + print(f"\n[ERROR] 诊断过程中发生错误: {e}") + import traceback + traceback.print_exc() + print_recommendations() + +if __name__ == '__main__': + main() + diff --git a/fix_minio_config.py b/fix_minio_config.py new file mode 100644 index 0000000..64f2b0d --- /dev/null +++ b/fix_minio_config.py @@ -0,0 +1,209 @@ +""" +修复MinIO配置 +1. 创建或更新.env文件 +2. 检查并迁移模板文件 +""" +import os +from pathlib import Path + +# 新MinIO配置 +NEW_MINIO_CONFIG = { + 'endpoint': '10.100.31.21:9000', + 'access_key': 'minio_PC8dcY', + 'secret_key': 'minio_7k7RNJ', + 'secure': 'false', # 重要:必须是false + 'bucket': 'finyx' +} + +def create_env_file(): + """创建或更新.env文件""" + env_file = Path('.env') + + print("="*70) + print("创建/更新 .env 文件") + print("="*70) + + # 读取现有.env文件(如果存在) + existing_vars = {} + if env_file.exists(): + print(f"\n发现现有 .env 文件,将更新MinIO相关配置...") + with open(env_file, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line and not line.startswith('#') and '=' in line: + key, value = line.split('=', 1) + existing_vars[key.strip()] = value.strip() + else: + print(f"\n创建新的 .env 文件...") + + # 更新MinIO配置 + existing_vars['MINIO_ENDPOINT'] = NEW_MINIO_CONFIG['endpoint'] + existing_vars['MINIO_ACCESS_KEY'] = NEW_MINIO_CONFIG['access_key'] + existing_vars['MINIO_SECRET_KEY'] = NEW_MINIO_CONFIG['secret_key'] + existing_vars['MINIO_BUCKET'] = NEW_MINIO_CONFIG['bucket'] + existing_vars['MINIO_SECURE'] = NEW_MINIO_CONFIG['secure'] + + # 写入.env文件 + with open(env_file, 'w', encoding='utf-8') as f: + f.write("# MinIO配置\n") + f.write(f"MINIO_ENDPOINT={NEW_MINIO_CONFIG['endpoint']}\n") + f.write(f"MINIO_ACCESS_KEY={NEW_MINIO_CONFIG['access_key']}\n") + f.write(f"MINIO_SECRET_KEY={NEW_MINIO_CONFIG['secret_key']}\n") + f.write(f"MINIO_BUCKET={NEW_MINIO_CONFIG['bucket']}\n") + f.write(f"MINIO_SECURE={NEW_MINIO_CONFIG['secure']} # 重要:新服务器使用HTTP,必须是false\n") + f.write("\n") + + # 保留其他配置(如果有) + other_keys = set(existing_vars.keys()) - { + 'MINIO_ENDPOINT', 'MINIO_ACCESS_KEY', 'MINIO_SECRET_KEY', + 'MINIO_BUCKET', 'MINIO_SECURE' + } + + if other_keys: + f.write("# 其他配置\n") + for key in sorted(other_keys): + f.write(f"{key}={existing_vars[key]}\n") + + print(f"\n[OK] .env 文件已更新") + print(f"\n更新的配置:") + print(f" MINIO_ENDPOINT={NEW_MINIO_CONFIG['endpoint']}") + print(f" MINIO_ACCESS_KEY={NEW_MINIO_CONFIG['access_key']}") + print(f" MINIO_SECRET_KEY={NEW_MINIO_CONFIG['secret_key'][:8]}***") + print(f" MINIO_BUCKET={NEW_MINIO_CONFIG['bucket']}") + print(f" MINIO_SECURE={NEW_MINIO_CONFIG['secure']} # [IMPORTANT] 必须是false") + + return True + +def check_template_files(): + """检查模板文件是否存在""" + print("\n" + "="*70) + print("检查模板文件") + print("="*70) + + try: + from minio import Minio + from minio.error import S3Error + import pymysql + from dotenv import load_dotenv + + load_dotenv() + + # 连接新MinIO + client = Minio( + NEW_MINIO_CONFIG['endpoint'], + access_key=NEW_MINIO_CONFIG['access_key'], + secret_key=NEW_MINIO_CONFIG['secret_key'], + secure=False + ) + + # 连接数据库 + db_config = { + 'host': os.getenv('DB_HOST', '152.136.177.240'), + 'port': int(os.getenv('DB_PORT', 5012)), + 'user': os.getenv('DB_USER', 'finyx'), + 'password': os.getenv('DB_PASSWORD', '6QsGK6MpePZDE57Z'), + 'database': os.getenv('DB_NAME', 'finyx'), + 'charset': 'utf8mb4' + } + + conn = pymysql.connect(**db_config) + cursor = conn.cursor(pymysql.cursors.DictCursor) + + # 查询所有模板 + sql = """ + SELECT id, name, file_path + FROM f_polic_file_config + WHERE tenant_id = %s + AND state = 1 + AND file_path IS NOT NULL + AND file_path != '' + """ + cursor.execute(sql, (615873064429507639,)) + templates = cursor.fetchall() + + print(f"\n数据库中找到 {len(templates)} 个模板文件") + + missing_files = [] + existing_files = [] + + for template in templates: + object_name = template['file_path'].lstrip('/') + try: + stat = client.stat_object(NEW_MINIO_CONFIG['bucket'], object_name) + existing_files.append(template) + print(f" [OK] {template['name']} - 存在 ({stat.size:,} 字节)") + except S3Error as e: + if e.code == 'NoSuchKey': + missing_files.append(template) + print(f" [FAIL] {template['name']} - 不存在") + print(f" 路径: {object_name}") + + cursor.close() + conn.close() + + print(f"\n总结:") + print(f" 存在的文件: {len(existing_files)}") + print(f" 缺失的文件: {len(missing_files)}") + + if missing_files: + print(f"\n[WARN] 发现 {len(missing_files)} 个模板文件在新MinIO服务器上不存在") + print(f"\n需要执行以下操作之一:") + print(f" 1. 从旧MinIO服务器迁移这些文件到新服务器") + print(f" 2. 重新上传这些模板文件到新MinIO服务器") + print(f"\n缺失的文件列表:") + for template in missing_files: + print(f" - {template['name']}") + print(f" 路径: {template['file_path']}") + + return len(missing_files) == 0 + + except Exception as e: + print(f"\n[ERROR] 检查模板文件时出错: {str(e)}") + import traceback + traceback.print_exc() + return False + +def main(): + """主函数""" + print("\n" + "="*70) + print("MinIO配置修复工具") + print("="*70) + + try: + # 1. 创建/更新.env文件 + create_env_file() + + # 2. 检查模板文件 + all_files_exist = check_template_files() + + # 总结 + print("\n" + "="*70) + print("修复总结") + print("="*70) + + print("\n[OK] .env 文件已更新") + + if all_files_exist: + print("[OK] 所有模板文件都存在") + print("\n下一步:") + print(" 1. 重启应用服务以使新的环境变量生效") + print(" 2. 测试文档生成功能") + else: + print("[WARN] 部分模板文件缺失") + print("\n下一步:") + print(" 1. 迁移或上传缺失的模板文件到新MinIO服务器") + print(" 2. 重启应用服务以使新的环境变量生效") + print(" 3. 测试文档生成功能") + + print("\n重要提示:") + print(" - MINIO_SECURE 必须设置为 false(新服务器使用HTTP)") + print(" - 更新环境变量后必须重启应用才能生效") + + except Exception as e: + print(f"\n[ERROR] 修复过程中发生错误: {e}") + import traceback + traceback.print_exc() + +if __name__ == '__main__': + main() + diff --git a/test_minio_remote_server.py b/test_minio_remote_server.py new file mode 100644 index 0000000..90814d8 --- /dev/null +++ b/test_minio_remote_server.py @@ -0,0 +1,398 @@ +""" +测试MinIO远程服务器连接和上传功能 +使用用户提供的远程服务器配置 +""" +import os +import tempfile +from minio import Minio +from minio.error import S3Error +from datetime import datetime, timedelta +import requests + +# MinIO远程服务器连接配置(用户提供) +MINIO_CONFIG = { + 'endpoint': '10.100.31.21:9000', # 注意:去掉协议前缀 + 'access_key': 'minio_PC8dcY', + 'secret_key': 'minio_7k7RNJ', + 'secure': True # 用户指定为true,但如果连接失败可以尝试false +} + +BUCKET_NAME = 'finyx' +TENANT_ID = '615873064429507639' + +def print_section(title): + """打印章节标题""" + print("\n" + "="*60) + print(f" {title}") + print("="*60) + +def print_result(success, message): + """打印测试结果""" + status = "[OK]" if success else "[FAIL]" + print(f"{status} {message}") + +def test_connection(): + """测试MinIO连接""" + print_section("1. 测试MinIO连接") + + # 先尝试secure=True(用户配置) + for secure in [True, False]: + try: + print(f"\n尝试连接(secure={secure})...") + # 创建MinIO客户端 + client = Minio( + MINIO_CONFIG['endpoint'], + access_key=MINIO_CONFIG['access_key'], + secret_key=MINIO_CONFIG['secret_key'], + secure=secure + ) + + # 列出所有存储桶(测试连接) + buckets = client.list_buckets() + print_result(True, f"MinIO连接成功!") + print(f"\n 连接信息:") + print(f" 端点: {MINIO_CONFIG['endpoint']}") + print(f" 使用HTTPS: {secure}") + print(f" 访问密钥: {MINIO_CONFIG['access_key']}") + + print(f"\n 可用存储桶:") + for bucket in buckets: + print(f" - {bucket.name} (创建时间: {bucket.creation_date})") + + # 检查目标存储桶是否存在 + bucket_exists = client.bucket_exists(BUCKET_NAME) + if bucket_exists: + print_result(True, f"目标存储桶 '{BUCKET_NAME}' 存在") + else: + print_result(False, f"目标存储桶 '{BUCKET_NAME}' 不存在") + print(f" 建议:需要创建存储桶 '{BUCKET_NAME}'") + + # 更新配置中的secure值 + MINIO_CONFIG['secure'] = secure + return client, bucket_exists + + except Exception as e: + error_msg = str(e) + if secure == True: + print_result(False, f"使用HTTPS连接失败: {error_msg}") + print(f" 将尝试使用HTTP连接...") + continue + else: + print_result(False, f"MinIO连接失败: {error_msg}") + import traceback + traceback.print_exc() + return None, False + + return None, False + +def create_test_file(): + """创建测试文件""" + print_section("2. 创建测试文件") + + # 创建临时测试文件 + test_content = f""" +这是一个MinIO远程服务器连接测试文件 +创建时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} +服务器地址: {MINIO_CONFIG['endpoint']} +测试内容: 测试MinIO远程服务器上传和下载功能 +""" + + # 创建临时文件 + temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') + temp_file.write(test_content) + temp_file.close() + + file_size = os.path.getsize(temp_file.name) + print_result(True, "测试文件创建成功") + print(f"\n 文件路径: {temp_file.name}") + print(f" 文件大小: {file_size} 字节") + + return temp_file.name + +def upload_file(client, file_path, bucket_exists): + """上传文件到MinIO""" + print_section("3. 上传文件到MinIO") + + if not bucket_exists: + print("[WARN] 存储桶不存在,尝试创建存储桶...") + try: + client.make_bucket(BUCKET_NAME) + print_result(True, f"存储桶 '{BUCKET_NAME}' 创建成功") + bucket_exists = True + except Exception as e: + print_result(False, f"创建存储桶失败: {str(e)}") + print("[WARN] 无法创建存储桶,跳过上传测试") + return None, None + + try: + # 生成对象名称(相对路径) + timestamp = datetime.now().strftime('%Y%m%d%H%M%S') + object_name = f"{TENANT_ID}/TEST/{datetime.now().year}/{datetime.now().month:02d}/test_file_{timestamp}.txt" + + print(f"\n 上传信息:") + print(f" 存储桶: {BUCKET_NAME}") + print(f" 对象名称: {object_name}") + print(f" 源文件: {file_path}") + + # 上传文件 + print(f"\n 正在上传...") + client.fput_object( + BUCKET_NAME, + object_name, + file_path, + content_type='text/plain; charset=utf-8' + ) + + print_result(True, "文件上传成功!") + + # 生成相对路径(用于数据库存储) + relative_path = f"/{object_name}" + print(f"\n 相对路径(数据库存储): {relative_path}") + + return object_name, relative_path + + except S3Error as e: + print_result(False, f"MinIO上传错误: {str(e)}") + import traceback + traceback.print_exc() + return None, None + except Exception as e: + print_result(False, f"上传失败: {str(e)}") + import traceback + traceback.print_exc() + return None, None + +def generate_access_url(client, object_name): + """生成可访问的URL""" + print_section("4. 生成访问URL") + + if not object_name: + print("[WARN] 没有上传文件,跳过URL生成") + return None + + try: + # 生成预签名URL(7天有效期) + expires = timedelta(days=7) + url = client.presigned_get_object( + BUCKET_NAME, + object_name, + expires=expires + ) + + print_result(True, "URL生成成功") + print(f"\n 预签名URL(7天有效):") + print(f" {url}") + + # 生成公共URL(如果存储桶是公共的) + protocol = "https" if MINIO_CONFIG['secure'] else "http" + public_url = f"{protocol}://{MINIO_CONFIG['endpoint']}/{BUCKET_NAME}/{object_name}" + + print(f"\n 公共URL(如果存储桶是公共的):") + print(f" {public_url}") + + return url + + except Exception as e: + print_result(False, f"URL生成失败: {str(e)}") + import traceback + traceback.print_exc() + return None + +def test_download(url): + """测试下载URL""" + print_section("5. 测试URL下载") + + if not url: + print("[WARN] 没有URL,跳过下载测试") + return False + + try: + print(f" 正在测试下载...") + print(f" URL: {url[:100]}...") + + # 禁用SSL验证(如果是自签名证书) + response = requests.get(url, timeout=10, verify=False) + + if response.status_code == 200: + print_result(True, f"下载成功!状态码: {response.status_code}") + print(f"\n 响应信息:") + print(f" 内容长度: {len(response.content)} 字节") + print(f" 内容类型: {response.headers.get('Content-Type', 'N/A')}") + + # 显示内容预览 + try: + content_preview = response.text[:200] + print(f"\n 内容预览:") + print(f" {content_preview}...") + except: + print(f" (无法显示文本预览)") + + return True + else: + print_result(False, f"下载失败!状态码: {response.status_code}") + print(f" 响应内容: {response.text[:200]}") + return False + + except requests.exceptions.Timeout: + print_result(False, "下载超时") + return False + except requests.exceptions.SSLError as e: + print_result(False, f"SSL错误: {str(e)}") + print(" 提示:可能需要验证SSL证书或使用HTTP") + return False + except Exception as e: + print_result(False, f"下载失败: {str(e)}") + import traceback + traceback.print_exc() + return False + +def test_list_objects(client): + """测试列出对象""" + print_section("6. 测试列出存储桶中的对象") + + if not client: + print("[WARN] 没有客户端连接,跳过列表测试") + return + + try: + # 检查存储桶是否存在 + if not client.bucket_exists(BUCKET_NAME): + print(f"[WARN] 存储桶 '{BUCKET_NAME}' 不存在") + return + + print(f"\n 列出存储桶 '{BUCKET_NAME}' 中的对象(最多10个):") + + objects = client.list_objects(BUCKET_NAME, recursive=True) + count = 0 + + for obj in objects: + count += 1 + if count <= 10: + print(f" {count}. {obj.object_name} ({obj.size} 字节, 修改时间: {obj.last_modified})") + else: + break + + if count == 0: + print(" (存储桶为空)") + elif count > 10: + print(f" ... 还有更多对象(总共可能超过10个)") + + print_result(True, f"成功列出对象(显示前10个)") + + except Exception as e: + print_result(False, f"列出对象失败: {str(e)}") + import traceback + traceback.print_exc() + +def cleanup_test_file(local_file_path): + """清理测试文件""" + try: + if local_file_path and os.path.exists(local_file_path): + os.unlink(local_file_path) + print(f"\n[OK] 已清理测试文件: {local_file_path}") + except Exception as e: + print(f"\n[WARN] 清理测试文件失败: {e}") + +def main(): + """主函数""" + print("\n" + "="*60) + print(" MinIO远程服务器连接测试") + print("="*60) + print(f"\n配置信息:") + print(f" 端点: {MINIO_CONFIG['endpoint']}") + print(f" 存储桶: {BUCKET_NAME}") + print(f" 访问密钥: {MINIO_CONFIG['access_key']}") + print(f" 初始secure设置: {MINIO_CONFIG['secure']}") + + test_file_path = None + object_name = None + + try: + # 1. 测试连接 + client, bucket_exists = test_connection() + + if not client: + print("\n[FAIL] 连接失败,无法继续测试") + print("\n可能的原因:") + print(" 1. 网络连接问题(无法访问 10.100.31.21:9000)") + print(" 2. 防火墙阻止连接") + print(" 3. MinIO服务器未运行") + print(" 4. 访问密钥或密钥错误") + print(" 5. SSL/TLS配置问题(尝试使用HTTP而不是HTTPS)") + return + + # 2. 列出对象 + test_list_objects(client) + + # 3. 创建测试文件 + test_file_path = create_test_file() + + # 4. 上传文件 + object_name, relative_path = upload_file(client, test_file_path, bucket_exists) + + # 5. 生成访问URL + access_url = generate_access_url(client, object_name) + + # 6. 测试下载 + download_success = test_download(access_url) + + # 总结 + print_section("测试总结") + + results = { + '连接': client is not None, + '存储桶存在': bucket_exists, + '文件上传': object_name is not None, + 'URL生成': access_url is not None, + 'URL下载': download_success + } + + print("\n测试结果:") + for test_name, success in results.items(): + status = "[OK] 通过" if success else "[FAIL] 失败" + print(f" {test_name}: {status}") + + passed = sum(1 for v in results.values() if v) + total = len(results) + + print(f"\n通过率: {passed}/{total} ({passed*100//total if total > 0 else 0}%)") + + if passed == total: + print("\n[OK] 所有测试通过!MinIO远程服务器配置正确,可以正常使用。") + else: + print("\n[WARN] 部分测试失败,请检查配置和网络连接。") + + # 显示使用建议 + if object_name: + print("\n" + "="*60) + print(" 使用建议") + print("="*60) + print(f"\n上传的文件路径(用于数据库存储):") + print(f" {relative_path}") + print(f"\n访问URL(预签名,7天有效):") + print(f" {access_url}") + print(f"\n最终使用的连接配置:") + print(f" MINIO_ENDPOINT={MINIO_CONFIG['endpoint']}") + print(f" MINIO_ACCESS_KEY={MINIO_CONFIG['access_key']}") + print(f" MINIO_SECRET_KEY={MINIO_CONFIG['secret_key']}") + print(f" MINIO_BUCKET={BUCKET_NAME}") + print(f" MINIO_SECURE={MINIO_CONFIG['secure']}") + + except KeyboardInterrupt: + print("\n\n测试已中断") + except Exception as e: + print(f"\n[FAIL] 测试过程中发生错误: {e}") + import traceback + traceback.print_exc() + finally: + # 清理测试文件 + if test_file_path: + cleanup_test_file(test_file_path) + +if __name__ == '__main__': + # 禁用SSL警告(如果使用自签名证书) + import urllib3 + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + main() + diff --git a/upload_templates_to_new_minio.py b/upload_templates_to_new_minio.py new file mode 100644 index 0000000..28205f8 --- /dev/null +++ b/upload_templates_to_new_minio.py @@ -0,0 +1,328 @@ +""" +批量上传本地模板文件到新MinIO服务器 +确保上传路径和文件名与数据库中的file_path字段值一致 +""" +import os +import pymysql +from minio import Minio +from minio.error import S3Error +from pathlib import Path +from typing import Dict, List, Optional +from dotenv import load_dotenv +import difflib + +# 加载环境变量 +load_dotenv() + +# 新MinIO配置 +NEW_MINIO_CONFIG = { + 'endpoint': '10.100.31.21:9000', + 'access_key': 'minio_PC8dcY', + 'secret_key': 'minio_7k7RNJ', + 'secure': False # 注意:根据测试结果应该是false,但用户要求true,如果失败会自动尝试false +} +BUCKET_NAME = 'finyx' +TENANT_ID = 615873064429507639 + +# 数据库配置 +DB_CONFIG = { + 'host': os.getenv('DB_HOST', '152.136.177.240'), + 'port': int(os.getenv('DB_PORT', 5012)), + 'user': os.getenv('DB_USER', 'finyx'), + 'password': os.getenv('DB_PASSWORD', '6QsGK6MpePZDE57Z'), + 'database': os.getenv('DB_NAME', 'finyx'), + 'charset': 'utf8mb4' +} + +# 本地模板目录 +TEMPLATES_DIR = Path('template_finish') + +def print_section(title): + """打印章节标题""" + print("\n" + "="*70) + print(f" {title}") + print("="*70) + +def print_result(success, message): + """打印结果""" + status = "[OK]" if success else "[FAIL]" + print(f"{status} {message}") + +def get_minio_client(secure=None): + """获取MinIO客户端""" + if secure is None: + secure = NEW_MINIO_CONFIG['secure'] + + return Minio( + NEW_MINIO_CONFIG['endpoint'], + access_key=NEW_MINIO_CONFIG['access_key'], + secret_key=NEW_MINIO_CONFIG['secret_key'], + secure=secure + ) + +def test_minio_connection(): + """测试MinIO连接""" + print_section("1. 测试MinIO连接") + + # 先尝试用户指定的secure值 + for secure in [NEW_MINIO_CONFIG['secure'], not NEW_MINIO_CONFIG['secure']]: + try: + print(f"\n尝试连接(secure={secure})...") + client = get_minio_client(secure=secure) + buckets = client.list_buckets() + print_result(True, f"MinIO连接成功(secure={secure})") + + # 检查存储桶 + if client.bucket_exists(BUCKET_NAME): + print_result(True, f"存储桶 '{BUCKET_NAME}' 存在") + # 更新配置 + NEW_MINIO_CONFIG['secure'] = secure + return client + else: + print_result(False, f"存储桶 '{BUCKET_NAME}' 不存在") + return None + + except Exception as e: + if secure == NEW_MINIO_CONFIG['secure']: + print_result(False, f"使用secure={secure}连接失败: {str(e)}") + print(" 将尝试另一个secure值...") + continue + else: + print_result(False, f"MinIO连接失败: {str(e)}") + return None + + return None + +def get_db_templates(conn) -> Dict[str, Dict]: + """从数据库获取所有模板配置""" + cursor = conn.cursor(pymysql.cursors.DictCursor) + + try: + sql = """ + SELECT id, name, file_path + FROM f_polic_file_config + WHERE tenant_id = %s + AND state = 1 + AND file_path IS NOT NULL + AND file_path != '' + """ + cursor.execute(sql, (TENANT_ID,)) + templates = cursor.fetchall() + + # 构建字典:文件名 -> 配置信息 + result = {} + for template in templates: + # 从file_path中提取文件名 + file_path = template['file_path'] + if file_path: + # 提取文件名(去掉路径) + file_name = Path(file_path).name + result[file_name] = { + 'id': template['id'], + 'name': template['name'], + 'file_path': file_path + } + + return result + + finally: + cursor.close() + +def scan_local_templates(base_dir: Path) -> Dict[str, Path]: + """扫描本地模板文件""" + templates = {} + + if not base_dir.exists(): + print(f"[WARN] 模板目录不存在: {base_dir}") + return templates + + # 递归扫描所有.docx文件 + for docx_file in base_dir.rglob('*.docx'): + file_name = docx_file.name + templates[file_name] = docx_file + + return templates + +def find_best_match(target_name: str, candidates: List[str], threshold=0.8) -> Optional[str]: + """使用模糊匹配找到最佳匹配的文件名""" + if not candidates: + return None + + # 精确匹配 + if target_name in candidates: + return target_name + + # 模糊匹配 + matches = difflib.get_close_matches(target_name, candidates, n=1, cutoff=threshold) + if matches: + return matches[0] + + return None + +def upload_file_to_minio(client: Minio, local_file: Path, object_name: str) -> bool: + """上传文件到MinIO""" + try: + # 检查文件是否存在 + if not local_file.exists(): + print(f" [ERROR] 本地文件不存在: {local_file}") + return False + + file_size = local_file.stat().st_size + print(f" 上传: {local_file.name} ({file_size:,} 字节)") + print(f" 目标路径: {object_name}") + + # 上传文件 + client.fput_object( + BUCKET_NAME, + object_name, + str(local_file), + content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document' + ) + + # 验证上传 + stat = client.stat_object(BUCKET_NAME, object_name) + print(f" [OK] 上传成功(验证大小: {stat.size:,} 字节)") + return True + + except S3Error as e: + print(f" [ERROR] MinIO错误: {str(e)}") + return False + except Exception as e: + print(f" [ERROR] 上传失败: {str(e)}") + import traceback + traceback.print_exc() + return False + +def main(): + """主函数""" + print("\n" + "="*70) + print(" 批量上传模板文件到新MinIO服务器") + print("="*70) + + # 1. 测试MinIO连接 + client = test_minio_connection() + if not client: + print("\n[ERROR] 无法连接到MinIO服务器,请检查配置") + return + + # 2. 连接数据库 + print_section("2. 连接数据库") + try: + conn = pymysql.connect(**DB_CONFIG) + print_result(True, "数据库连接成功") + except Exception as e: + print_result(False, f"数据库连接失败: {str(e)}") + return + + try: + # 3. 获取数据库中的模板配置 + print_section("3. 获取数据库模板配置") + db_templates = get_db_templates(conn) + print(f"\n数据库中找到 {len(db_templates)} 个模板配置") + + # 4. 扫描本地模板文件 + print_section("4. 扫描本地模板文件") + local_templates = scan_local_templates(TEMPLATES_DIR) + print(f"\n本地找到 {len(local_templates)} 个模板文件") + + if not local_templates: + print("[ERROR] 本地没有找到模板文件") + return + + # 5. 匹配并上传 + print_section("5. 匹配并上传文件") + + matched_count = 0 + uploaded_count = 0 + failed_count = 0 + unmatched_db = [] + unmatched_local = [] + + # 匹配数据库中的模板 + for db_file_name, db_config in db_templates.items(): + file_path = db_config['file_path'] + object_name = file_path.lstrip('/') # 去掉开头的/ + + # 查找匹配的本地文件 + local_file = None + + # 精确匹配 + if db_file_name in local_templates: + local_file = local_templates[db_file_name] + matched_count += 1 + else: + # 模糊匹配 + best_match = find_best_match(db_file_name, list(local_templates.keys())) + if best_match: + local_file = local_templates[best_match] + matched_count += 1 + print(f"\n[INFO] 使用模糊匹配: '{db_file_name}' -> '{best_match}'") + else: + unmatched_db.append((db_file_name, db_config)) + print(f"\n[WARN] 未找到匹配的本地文件: {db_file_name}") + continue + + # 上传文件 + print(f"\n处理: {db_config['name']}") + print(f" 数据库路径: {file_path}") + if upload_file_to_minio(client, local_file, object_name): + uploaded_count += 1 + else: + failed_count += 1 + + # 检查未匹配的本地文件 + matched_local_names = set() + for db_file_name in db_templates.keys(): + if db_file_name in local_templates: + matched_local_names.add(db_file_name) + else: + best_match = find_best_match(db_file_name, list(local_templates.keys())) + if best_match: + matched_local_names.add(best_match) + + for local_name, local_path in local_templates.items(): + if local_name not in matched_local_names: + unmatched_local.append((local_name, local_path)) + + # 6. 总结 + print_section("6. 上传总结") + + print(f"\n匹配统计:") + print(f" 数据库模板数: {len(db_templates)}") + print(f" 本地文件数: {len(local_templates)}") + print(f" 成功匹配: {matched_count}") + print(f" 成功上传: {uploaded_count}") + print(f" 上传失败: {failed_count}") + + if unmatched_db: + print(f"\n[WARN] 数据库中有 {len(unmatched_db)} 个模板未找到本地文件:") + for file_name, config in unmatched_db[:10]: # 只显示前10个 + print(f" - {file_name} (ID: {config['id']}, 名称: {config['name']})") + if len(unmatched_db) > 10: + print(f" ... 还有 {len(unmatched_db) - 10} 个") + + if unmatched_local: + print(f"\n[INFO] 本地有 {len(unmatched_local)} 个文件未在数据库中找到:") + for file_name, file_path in unmatched_local[:10]: # 只显示前10个 + print(f" - {file_name} ({file_path})") + if len(unmatched_local) > 10: + print(f" ... 还有 {len(unmatched_local) - 10} 个") + + if uploaded_count == matched_count and matched_count > 0: + print_result(True, f"所有匹配的文件都已成功上传!") + elif uploaded_count > 0: + print_result(True, f"成功上传 {uploaded_count} 个文件") + else: + print_result(False, "没有文件被上传") + + print(f"\n使用的MinIO配置:") + print(f" 端点: {NEW_MINIO_CONFIG['endpoint']}") + print(f" 存储桶: {BUCKET_NAME}") + print(f" 使用HTTPS: {NEW_MINIO_CONFIG['secure']}") + + finally: + conn.close() + +if __name__ == '__main__': + main() +