同步数据库和minio模板数据至智慧监督服务器

This commit is contained in:
python 2025-12-12 15:20:43 +08:00
parent 640f7834b6
commit 2563e7fc74
8 changed files with 1790 additions and 63 deletions

84
.env
View File

@ -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

136
MinIO迁移完成总结.md Normal file
View File

@ -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/6100%
- ✅ 环境变量配置正确
- ✅ 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. ✅ 所有功能测试通过
**现在可以重启应用服务并测试文档生成功能了!**

View File

@ -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以确保安全性

View File

@ -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等

View File

@ -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()

209
fix_minio_config.py Normal file
View File

@ -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()

398
test_minio_remote_server.py Normal file
View File

@ -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:
# 生成预签名URL7天有效期
expires = timedelta(days=7)
url = client.presigned_get_object(
BUCKET_NAME,
object_name,
expires=expires
)
print_result(True, "URL生成成功")
print(f"\n 预签名URL7天有效:")
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()

View File

@ -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()