修复文档生成逻辑中的冲突,更新文件路径和下载链接的返回逻辑,增强用户体验。同时,优化前端文件列表加载功能,支持加载所有可用模板,并添加清空文件列表的功能。

This commit is contained in:
python 2025-12-11 14:50:07 +08:00
parent 28bf100ca4
commit d8d2817aed
25 changed files with 3340 additions and 58 deletions

6
app.py
View File

@ -706,7 +706,6 @@ def generate_document():
return error_response(3001, f"文档生成失败: {str(e)}")
<<<<<<< HEAD
@app.route('/fPolicTask/getDocument', methods=['POST'])
def get_document_by_task():
"""
@ -810,7 +809,8 @@ def get_document_by_task():
result_file_list.append({
'fileId': file_id,
'fileName': generated_file_name, # 使用生成的文档名
'filePath': result['filePath']
'filePath': result['filePath'], # MinIO相对路径
'downloadUrl': result.get('downloadUrl') # MinIO预签名下载URL完整链接
})
except Exception as e:
@ -835,8 +835,6 @@ def get_document_by_task():
return error_response(3001, f"文档生成失败: {str(e)}")
=======
>>>>>>> parent of 4897c96 (添加通过taskId获取文档的接口支持文件列表查询和参数验证增强错误处理能力同时优化文档生成逻辑确保生成的文档名称和路径的准确性)
if __name__ == '__main__':
# 确保static目录存在
os.makedirs('static', exist_ok=True)

117
check_and_fix_duplicates.py Normal file
View File

@ -0,0 +1,117 @@
"""
检查并修复重复记录
"""
import pymysql
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
UPDATED_BY = 655162080928945152
conn = pymysql.connect(**DB_CONFIG)
cursor = conn.cursor(pymysql.cursors.DictCursor)
try:
# 检查"1.初核请示"下的所有记录
cursor.execute("""
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND parent_id = %s
ORDER BY id
""", (TENANT_ID, 1765431558933731)) # 1.初核请示
results = cursor.fetchall()
print(f"'1.初核请示'下有 {len(results)} 条记录:\n")
for r in results:
print(f"ID: {r['id']}, name: {r['name']}, file_path: {r['file_path']}")
# 检查"1请示报告卡"的记录
request_cards = [r for r in results if r['name'] == '1请示报告卡']
if len(request_cards) > 1:
print(f"\n发现 {len(request_cards)} 个重复的'1请示报告卡'记录")
# 保留file_path正确的那个
correct_one = None
for r in request_cards:
if r['file_path'] and '1.请示报告卡XXX' in r['file_path']:
correct_one = r
break
if correct_one:
# 删除其他的
for r in request_cards:
if r['id'] != correct_one['id']:
# 删除关联关系
cursor.execute("""
DELETE FROM f_polic_file_field
WHERE tenant_id = %s AND file_id = %s
""", (TENANT_ID, r['id']))
# 删除模板记录
cursor.execute("""
DELETE FROM f_polic_file_config
WHERE tenant_id = %s AND id = %s
""", (TENANT_ID, r['id']))
print(f"[DELETE] 删除重复记录: ID {r['id']}, file_path: {r['file_path']}")
# 检查"走读式谈话审批"下是否有"1请示报告卡"
cursor.execute("""
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND parent_id = %s AND name = %s
""", (TENANT_ID, 1765273962700431, '1请示报告卡')) # 走读式谈话审批
result = cursor.fetchone()
if not result:
print("\n[WARN] '走读式谈话审批'下缺少'1请示报告卡'记录")
# 创建记录
import time
import random
timestamp = int(time.time() * 1000)
random_part = random.randint(100000, 999999)
new_id = timestamp * 1000 + random_part
insert_sql = """
INSERT INTO f_polic_file_config
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
"""
cursor.execute(insert_sql, (
new_id,
TENANT_ID,
1765273962700431, # 走读式谈话审批
'1请示报告卡',
None,
'/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx',
655162080928945152,
655162080928945152,
1
))
print(f"[CREATE] 在'走读式谈话审批'下创建'1请示报告卡'记录 (ID: {new_id})")
else:
# 检查file_path是否正确
if result['file_path'] and '1.请示报告卡(初核谈话)' not in result['file_path']:
cursor.execute("""
UPDATE f_polic_file_config
SET file_path = %s, updated_time = NOW(), updated_by = %s
WHERE tenant_id = %s AND id = %s
""", ('/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx', UPDATED_BY, TENANT_ID, result['id']))
print(f"[UPDATE] 修复'走读式谈话审批''1请示报告卡'的file_path")
conn.commit()
print("\n[OK] 修复完成")
except Exception as e:
conn.rollback()
print(f"[ERROR] 修复失败: {e}")
import traceback
traceback.print_exc()
finally:
cursor.close()
conn.close()

View File

@ -0,0 +1,36 @@
"""查询保密承诺书相关的模板记录"""
import pymysql
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
conn = pymysql.connect(**DB_CONFIG)
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("""
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND name LIKE %s
ORDER BY name
""", (TENANT_ID, '%保密承诺书%'))
results = cursor.fetchall()
print(f"找到 {len(results)} 条记录:\n")
for r in results:
print(f"ID: {r['id']}")
print(f"名称: {r['name']}")
print(f"文件路径: {r['file_path']}")
print(f"父节点ID: {r['parent_id']}")
print()
cursor.close()
conn.close()

View File

@ -0,0 +1,361 @@
"""
清理 f_polic_file_config 表中的重复和无效数据
确保文档模板结构和 template_finish/ 文件夹对应
"""
import os
import re
import json
import pymysql
from pathlib import Path
from typing import Dict, List, Set, Optional
from collections import defaultdict
# 数据库连接配置
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
UPDATED_BY = 655162080928945152
TEMPLATE_BASE_DIR = 'template_finish'
def normalize_template_name(name: str) -> str:
"""
标准化模板名称去掉扩展名括号内容数字前缀等
Args:
name: 文件名或模板名称
Returns:
标准化后的名称
"""
# 去掉扩展名
name = Path(name).stem if '.' in name else name
# 去掉括号内容
name = re.sub(r'[(].*?[)]', '', name)
name = name.strip()
# 去掉数字前缀和点号
name = re.sub(r'^\d+[\.\-]?\s*', '', name)
name = name.strip()
return name
def scan_template_files(base_dir: str) -> Dict[str, Dict]:
"""
扫描模板文件夹获取所有有效的模板文件
Returns:
字典key为标准化名称value为模板信息列表可能有多个同名文件
"""
base_path = Path(base_dir)
if not base_path.exists():
print(f"错误: 目录不存在 - {base_dir}")
return {}
templates = defaultdict(list)
print("=" * 80)
print("扫描模板文件...")
print("=" * 80)
for docx_file in sorted(base_path.rglob("*.docx")):
# 跳过临时文件
if docx_file.name.startswith("~$"):
continue
relative_path = docx_file.relative_to(base_path)
file_name = docx_file.name
normalized_name = normalize_template_name(file_name)
templates[normalized_name].append({
'file_path': str(docx_file),
'relative_path': str(relative_path),
'file_name': file_name,
'normalized_name': normalized_name
})
print(f"总共扫描到 {sum(len(v) for v in templates.values())} 个模板文件")
print(f"唯一模板名称: {len(templates)}")
return dict(templates)
def get_all_templates_from_db(conn) -> Dict[str, List[Dict]]:
"""
从数据库获取所有模板按标准化名称分组
Returns:
字典key为标准化名称value为模板记录列表
"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = """
SELECT id, name, file_path, parent_id, state, input_data, created_time, updated_time
FROM f_polic_file_config
WHERE tenant_id = %s
ORDER BY created_time DESC
"""
cursor.execute(sql, (TENANT_ID,))
templates = cursor.fetchall()
result = defaultdict(list)
for template in templates:
normalized_name = normalize_template_name(template['name'])
result[normalized_name].append({
'id': template['id'],
'name': template['name'],
'normalized_name': normalized_name,
'file_path': template['file_path'],
'parent_id': template['parent_id'],
'state': template['state'],
'input_data': template['input_data'],
'created_time': template['created_time'],
'updated_time': template['updated_time']
})
cursor.close()
return dict(result)
def find_duplicates(db_templates: Dict[str, List[Dict]]) -> Dict[str, List[Dict]]:
"""
找出重复的模板同一标准化名称有多个记录
Returns:
字典key为标准化名称value为重复的模板记录列表
"""
duplicates = {}
for normalized_name, templates in db_templates.items():
if len(templates) > 1:
duplicates[normalized_name] = templates
return duplicates
def select_best_template(templates: List[Dict], valid_template_files: List[Dict]) -> Optional[Dict]:
"""
从多个重复的模板中选择最好的一个保留最新的有效的
Args:
templates: 数据库中的模板记录列表
valid_template_files: 有效的模板文件列表
Returns:
应该保留的模板记录或None
"""
if not templates:
return None
# 优先选择state=1 且 file_path 有效的
enabled_templates = [t for t in templates if t.get('state') == 1]
if enabled_templates:
# 如果有多个启用的,选择最新的
enabled_templates.sort(key=lambda x: x.get('updated_time') or x.get('created_time'), reverse=True)
return enabled_templates[0]
# 如果没有启用的,选择最新的
templates.sort(key=lambda x: x.get('updated_time') or x.get('created_time'), reverse=True)
return templates[0]
def delete_template_and_relations(conn, template_id: int):
"""
删除模板及其关联关系
Args:
conn: 数据库连接
template_id: 模板ID
"""
cursor = conn.cursor()
try:
# 删除字段关联
delete_relations_sql = """
DELETE FROM f_polic_file_field
WHERE tenant_id = %s AND file_id = %s
"""
cursor.execute(delete_relations_sql, (TENANT_ID, template_id))
relations_deleted = cursor.rowcount
# 删除模板配置
delete_template_sql = """
DELETE FROM f_polic_file_config
WHERE tenant_id = %s AND id = %s
"""
cursor.execute(delete_template_sql, (TENANT_ID, template_id))
template_deleted = cursor.rowcount
conn.commit()
return relations_deleted, template_deleted
except Exception as e:
conn.rollback()
raise Exception(f"删除模板失败: {str(e)}")
finally:
cursor.close()
def mark_invalid_templates(conn, valid_template_names: Set[str]):
"""
标记无效的模板不在template_finish文件夹中的模板
Args:
conn: 数据库连接
valid_template_names: 有效的模板名称集合标准化后的
"""
cursor = conn.cursor()
try:
# 获取所有模板
sql = """
SELECT id, name FROM f_polic_file_config
WHERE tenant_id = %s
"""
cursor.execute(sql, (TENANT_ID,))
all_templates = cursor.fetchall()
invalid_count = 0
for template in all_templates:
template_id = template[0]
template_name = template[1]
normalized_name = normalize_template_name(template_name)
# 检查是否在有效模板列表中
if normalized_name not in valid_template_names:
# 标记为未启用
update_sql = """
UPDATE f_polic_file_config
SET state = 0, updated_time = NOW(), updated_by = %s
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (UPDATED_BY, template_id, TENANT_ID))
invalid_count += 1
print(f" [WARN] 标记无效模板: {template_name} (ID: {template_id})")
conn.commit()
print(f"\n总共标记 {invalid_count} 个无效模板")
except Exception as e:
conn.rollback()
raise Exception(f"标记无效模板失败: {str(e)}")
finally:
cursor.close()
def main():
"""主函数"""
print("=" * 80)
print("清理重复和无效的模板数据")
print("=" * 80)
print()
try:
# 连接数据库
print("1. 连接数据库...")
conn = pymysql.connect(**DB_CONFIG)
print("[OK] 数据库连接成功\n")
# 扫描模板文件
print("2. 扫描模板文件...")
valid_templates = scan_template_files(TEMPLATE_BASE_DIR)
valid_template_names = set(valid_templates.keys())
print(f"[OK] 找到 {len(valid_template_names)} 个有效模板名称\n")
# 获取数据库中的模板
print("3. 获取数据库中的模板...")
db_templates = get_all_templates_from_db(conn)
print(f"[OK] 数据库中有 {sum(len(v) for v in db_templates.values())} 个模板记录")
print(f"[OK] 唯一模板名称: {len(db_templates)}\n")
# 找出重复的模板
print("4. 查找重复的模板...")
duplicates = find_duplicates(db_templates)
print(f"[OK] 找到 {len(duplicates)} 个重复的模板名称\n")
# 处理重复模板
print("5. 处理重复模板...")
print("=" * 80)
total_deleted = 0
total_relations_deleted = 0
for normalized_name, templates in duplicates.items():
print(f"\n处理重复模板: {normalized_name}")
print(f" 重复记录数: {len(templates)}")
# 获取对应的有效模板文件
valid_files = valid_templates.get(normalized_name, [])
# 选择要保留的模板
keep_template = select_best_template(templates, valid_files)
if keep_template:
print(f" [KEEP] 保留模板: {keep_template['name']} (ID: {keep_template['id']})")
# 删除其他重复的模板
for template in templates:
if template['id'] != keep_template['id']:
print(f" [DELETE] 删除重复模板: {template['name']} (ID: {template['id']})")
relations_deleted, template_deleted = delete_template_and_relations(conn, template['id'])
total_relations_deleted += relations_deleted
total_deleted += template_deleted
else:
print(f" [WARN] 无法确定要保留的模板,跳过")
print(f"\n[OK] 删除重复模板: {total_deleted}")
print(f"[OK] 删除关联关系: {total_relations_deleted}\n")
# 标记无效模板
print("6. 标记无效模板...")
mark_invalid_templates(conn, valid_template_names)
# 统计最终结果
print("\n7. 统计最终结果...")
final_templates = get_all_templates_from_db(conn)
enabled_count = sum(1 for templates in final_templates.values()
for t in templates if t.get('state') == 1)
disabled_count = sum(1 for templates in final_templates.values()
for t in templates if t.get('state') != 1)
print(f"[OK] 最终模板总数: {sum(len(v) for v in final_templates.values())}")
print(f"[OK] 启用模板数: {enabled_count}")
print(f"[OK] 禁用模板数: {disabled_count}")
print(f"[OK] 唯一模板名称: {len(final_templates)}")
# 打印最终模板列表
print("\n8. 最终模板列表(启用的):")
print("=" * 80)
for normalized_name, templates in sorted(final_templates.items()):
enabled = [t for t in templates if t.get('state') == 1]
if enabled:
for template in enabled:
print(f" - {template['name']} (ID: {template['id']})")
print("\n" + "=" * 80)
print("清理完成!")
print("=" * 80)
except Exception as e:
print(f"\n[ERROR] 发生错误: {e}")
import traceback
traceback.print_exc()
if 'conn' in locals():
conn.rollback()
finally:
if 'conn' in locals():
conn.close()
print("\n数据库连接已关闭")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,158 @@
"""
最终完善模板层级结构
修复文件路径错误和重复问题
"""
import pymysql
import time
import random
from pathlib import Path
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
CREATED_BY = 655162080928945152
UPDATED_BY = 655162080928945152
def generate_id():
timestamp = int(time.time() * 1000)
random_part = random.randint(100000, 999999)
return timestamp * 1000 + random_part
conn = pymysql.connect(**DB_CONFIG)
cursor = conn.cursor(pymysql.cursors.DictCursor)
try:
# 检查"1请示报告卡"的记录
# 根据目录结构,应该有两个不同的文件:
# 1. "1.初核请示"下的"1.请示报告卡XXX.docx"
# 2. "走读式谈话审批"下的"1.请示报告卡(初核谈话).docx"
cursor.execute("""
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND name = %s
ORDER BY id
""", (TENANT_ID, '1请示报告卡'))
results = cursor.fetchall()
# 检查是否在"1.初核请示"下有记录
in_initial_request = any(r['parent_id'] == 1765431558933731 for r in results)
# 检查是否在"走读式谈话审批"下有记录
in_interview_approval = any(r['parent_id'] == 1765273962700431 for r in results)
if not in_initial_request:
# 需要在"1.初核请示"下创建记录
new_id = generate_id()
insert_sql = """
INSERT INTO f_polic_file_config
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
"""
cursor.execute(insert_sql, (
new_id,
TENANT_ID,
1765431558933731, # 1.初核请示
'1请示报告卡',
None,
'/615873064429507639/TEMPLATE/2025/12/1.请示报告卡XXX.docx',
CREATED_BY,
CREATED_BY,
1
))
print(f"[CREATE] 在'1.初核请示'下创建'1请示报告卡'记录 (ID: {new_id})")
if not in_interview_approval:
# 需要在"走读式谈话审批"下创建记录
new_id = generate_id()
insert_sql = """
INSERT INTO f_polic_file_config
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
"""
cursor.execute(insert_sql, (
new_id,
TENANT_ID,
1765273962700431, # 走读式谈话审批
'1请示报告卡',
None,
'/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx',
CREATED_BY,
CREATED_BY,
1
))
print(f"[CREATE] 在'走读式谈话审批'下创建'1请示报告卡'记录 (ID: {new_id})")
# 更新现有记录的文件路径
for result in results:
if result['parent_id'] == 1765431558933731: # 1.初核请示
correct_path = '/615873064429507639/TEMPLATE/2025/12/1.请示报告卡XXX.docx'
elif result['parent_id'] == 1765273962700431: # 走读式谈话审批
correct_path = '/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx'
else:
continue
if result['file_path'] != correct_path:
cursor.execute("""
UPDATE f_polic_file_config
SET file_path = %s, updated_time = NOW(), updated_by = %s
WHERE tenant_id = %s AND id = %s
""", (correct_path, UPDATED_BY, TENANT_ID, result['id']))
print(f"[UPDATE] 修复'1请示报告卡'的文件路径 (ID: {result['id']}): {result['file_path']} -> {correct_path}")
# 检查重复的"XXX初核情况报告"
cursor.execute("""
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND name LIKE %s
ORDER BY id
""", (TENANT_ID, '%XXX初核情况报告%'))
results = cursor.fetchall()
if len(results) > 1:
# 保留最新的,删除旧的
# 或者根据file_path判断哪个是正确的
# 根据目录结构,应该是"8.XXX初核情况报告.docx"
correct_name = 'XXX初核情况报告'
correct_path = '/615873064429507639/TEMPLATE/2025/12/8.XXX初核情况报告.docx'
for r in results:
if r['name'] == '8.XXX初核情况报告':
# 这个应该删除(名称带数字前缀)
cursor.execute("""
DELETE FROM f_polic_file_field
WHERE tenant_id = %s AND file_id = %s
""", (TENANT_ID, r['id']))
cursor.execute("""
DELETE FROM f_polic_file_config
WHERE tenant_id = %s AND id = %s
""", (TENANT_ID, r['id']))
print(f"[DELETE] 删除重复记录: {r['name']} (ID: {r['id']})")
elif r['name'] == 'XXX初核情况报告':
# 更新这个记录的文件路径
if r['file_path'] != correct_path:
cursor.execute("""
UPDATE f_polic_file_config
SET file_path = %s, updated_time = NOW(), updated_by = %s
WHERE tenant_id = %s AND id = %s
""", (correct_path, UPDATED_BY, TENANT_ID, r['id']))
print(f"[UPDATE] 更新'XXX初核情况报告'的文件路径: {r['file_path']} -> {correct_path}")
conn.commit()
print("\n[OK] 修复完成")
except Exception as e:
conn.rollback()
print(f"[ERROR] 修复失败: {e}")
import traceback
traceback.print_exc()
finally:
cursor.close()
conn.close()

View File

@ -0,0 +1,131 @@
"""
修复重复的"1请示报告卡"记录
确保每个文件在正确的位置只有一个记录
"""
import pymysql
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
UPDATED_BY = 655162080928945152
conn = pymysql.connect(**DB_CONFIG)
cursor = conn.cursor(pymysql.cursors.DictCursor)
try:
# 查找所有"1请示报告卡"记录
cursor.execute("""
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND name = %s
ORDER BY id
""", (TENANT_ID, '1请示报告卡'))
results = cursor.fetchall()
print(f"找到 {len(results)}'1请示报告卡'记录:\n")
# 根据file_path和parent_id判断哪些是正确的
correct_records = []
for r in results:
print(f"ID: {r['id']}, file_path: {r['file_path']}, parent_id: {r['parent_id']}")
# 判断是否正确
if r['parent_id'] == 1765431558933731: # 1.初核请示
if '1.请示报告卡XXX' in (r['file_path'] or ''):
correct_records.append(r)
elif r['parent_id'] == 1765273962700431: # 走读式谈话审批
if '1.请示报告卡(初核谈话)' in (r['file_path'] or ''):
correct_records.append(r)
print(f"\n正确的记录数: {len(correct_records)}")
# 删除不正确的记录
for r in results:
if r not in correct_records:
# 先删除关联关系
cursor.execute("""
DELETE FROM f_polic_file_field
WHERE tenant_id = %s AND file_id = %s
""", (TENANT_ID, r['id']))
# 删除模板记录
cursor.execute("""
DELETE FROM f_polic_file_config
WHERE tenant_id = %s AND id = %s
""", (TENANT_ID, r['id']))
print(f"[DELETE] 删除不正确的记录: ID {r['id']}, file_path: {r['file_path']}, parent_id: {r['parent_id']}")
# 确保两个位置都有正确的记录
has_initial_request = any(r['parent_id'] == 1765431558933731 for r in correct_records)
has_interview_approval = any(r['parent_id'] == 1765273962700431 for r in correct_records)
if not has_initial_request:
# 创建"1.初核请示"下的记录
import time
import random
timestamp = int(time.time() * 1000)
random_part = random.randint(100000, 999999)
new_id = timestamp * 1000 + random_part
insert_sql = """
INSERT INTO f_polic_file_config
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
"""
cursor.execute(insert_sql, (
new_id,
TENANT_ID,
1765431558933731, # 1.初核请示
'1请示报告卡',
None,
'/615873064429507639/TEMPLATE/2025/12/1.请示报告卡XXX.docx',
655162080928945152,
655162080928945152,
1
))
print(f"[CREATE] 在'1.初核请示'下创建'1请示报告卡'记录 (ID: {new_id})")
if not has_interview_approval:
# 创建"走读式谈话审批"下的记录
import time
import random
timestamp = int(time.time() * 1000)
random_part = random.randint(100000, 999999)
new_id = timestamp * 1000 + random_part
insert_sql = """
INSERT INTO f_polic_file_config
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
"""
cursor.execute(insert_sql, (
new_id,
TENANT_ID,
1765273962700431, # 走读式谈话审批
'1请示报告卡',
None,
'/615873064429507639/TEMPLATE/2025/12/1.请示报告卡(初核谈话).docx',
655162080928945152,
655162080928945152,
1
))
print(f"[CREATE] 在'走读式谈话审批'下创建'1请示报告卡'记录 (ID: {new_id})")
conn.commit()
print("\n[OK] 修复完成")
except Exception as e:
conn.rollback()
print(f"[ERROR] 修复失败: {e}")
import traceback
traceback.print_exc()
finally:
cursor.close()
conn.close()

View File

@ -0,0 +1,61 @@
"""
修复剩余的层级结构问题
"""
import pymysql
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
UPDATED_BY = 655162080928945152
conn = pymysql.connect(**DB_CONFIG)
cursor = conn.cursor()
try:
# 1. 修复"2保密承诺书"的parent_id应该在"走读式谈话流程"下)
# "走读式谈话流程"的ID是 1765273962716807
cursor.execute("""
UPDATE f_polic_file_config
SET parent_id = %s, updated_time = NOW(), updated_by = %s
WHERE tenant_id = %s AND id = %s
""", (1765273962716807, UPDATED_BY, TENANT_ID, 1765425919729046))
print(f"[UPDATE] 更新'2保密承诺书'的parent_id: {cursor.rowcount}")
# 2. 检查"8.XXX初核情况报告"的位置(应该在"3.初核结论"下,而不是"走读式谈话流程"下)
# "3.初核结论"的ID是 1765431559135346
# 先查找"8.XXX初核情况报告"的ID
cursor.execute("""
SELECT id, name, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND name LIKE %s
""", (TENANT_ID, '%XXX初核情况报告%'))
result = cursor.fetchone()
if result:
file_id, file_name, current_parent = result
if current_parent != 1765431559135346:
cursor.execute("""
UPDATE f_polic_file_config
SET parent_id = %s, updated_time = NOW(), updated_by = %s
WHERE tenant_id = %s AND id = %s
""", (1765431559135346, UPDATED_BY, TENANT_ID, file_id))
print(f"[UPDATE] 更新'{file_name}'的parent_id: {cursor.rowcount}")
conn.commit()
print("\n[OK] 修复完成")
except Exception as e:
conn.rollback()
print(f"[ERROR] 修复失败: {e}")
import traceback
traceback.print_exc()
finally:
cursor.close()
conn.close()

234
fix_template_names.py Normal file
View File

@ -0,0 +1,234 @@
"""
检查并修复 f_polic_file_config 表中模板名称与文件名的对应关系
确保 name 字段与模板文档名称去掉扩展名完全一致
"""
import os
import sys
import pymysql
from pathlib import Path
from typing import Dict, List, Optional
# 设置输出编码为UTF-8Windows兼容
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# 数据库连接配置
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
UPDATED_BY = 655162080928945152
TEMPLATE_BASE_DIR = 'template_finish'
def scan_template_files(base_dir: str) -> Dict[str, str]:
"""
扫描模板文件夹获取所有模板文件信息
Returns:
字典key为MinIO路径用于匹配value为文件名不含扩展名
"""
base_path = Path(base_dir)
if not base_path.exists():
print(f"错误: 目录不存在 - {base_dir}")
return {}
templates = {}
print("=" * 80)
print("扫描模板文件...")
print("=" * 80)
for docx_file in sorted(base_path.rglob("*.docx")):
# 跳过临时文件
if docx_file.name.startswith("~$"):
continue
# 获取文件名(不含扩展名)
file_name_without_ext = docx_file.stem
# 构建MinIO路径用于匹配数据库中的file_path
from datetime import datetime
now = datetime.now()
minio_path = f'/615873064429507639/TEMPLATE/{now.year}/{now.month:02d}/{docx_file.name}'
templates[minio_path] = {
'file_name': docx_file.name,
'name_without_ext': file_name_without_ext,
'relative_path': str(docx_file.relative_to(base_path))
}
print(f"找到 {len(templates)} 个模板文件\n")
return templates
def get_db_templates(conn) -> Dict[str, Dict]:
"""
获取数据库中所有模板记录
Returns:
字典key为file_pathvalue为模板信息
"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = """
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND file_path IS NOT NULL
"""
cursor.execute(sql, (TENANT_ID,))
templates = cursor.fetchall()
result = {}
for template in templates:
if template['file_path']:
result[template['file_path']] = {
'id': template['id'],
'name': template['name'],
'file_path': template['file_path'],
'parent_id': template['parent_id']
}
cursor.close()
return result
def update_template_name(conn, template_id: int, new_name: str, old_name: str):
"""
更新模板名称
"""
cursor = conn.cursor()
try:
update_sql = """
UPDATE f_polic_file_config
SET name = %s, updated_time = NOW(), updated_by = %s
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (new_name, UPDATED_BY, template_id, TENANT_ID))
conn.commit()
print(f" [UPDATE] ID: {template_id}")
print(f" 旧名称: {old_name}")
print(f" 新名称: {new_name}")
return True
except Exception as e:
conn.rollback()
print(f" [ERROR] 更新失败: {str(e)}")
return False
finally:
cursor.close()
def match_file_path(file_path: str, db_paths: List[str]) -> Optional[str]:
"""
匹配文件路径可能日期不同
Args:
file_path: 当前构建的MinIO路径
db_paths: 数据库中的所有路径列表
Returns:
匹配的数据库路径如果找到的话
"""
# 提取文件名
file_name = Path(file_path).name
# 在数据库路径中查找相同文件名的路径
for db_path in db_paths:
if Path(db_path).name == file_name:
return db_path
return None
def main():
"""主函数"""
print("=" * 80)
print("检查并修复模板名称")
print("=" * 80)
print()
try:
# 连接数据库
print("1. 连接数据库...")
conn = pymysql.connect(**DB_CONFIG)
print("[OK] 数据库连接成功\n")
# 扫描模板文件
print("2. 扫描模板文件...")
file_templates = scan_template_files(TEMPLATE_BASE_DIR)
# 获取数据库模板
print("3. 获取数据库模板...")
db_templates = get_db_templates(conn)
print(f"[OK] 找到 {len(db_templates)} 个数据库模板\n")
# 检查并更新
print("4. 检查并更新模板名称...")
print("=" * 80)
updated_count = 0
not_found_count = 0
matched_count = 0
# 遍历文件模板
for file_path, file_info in file_templates.items():
file_name = file_info['file_name']
expected_name = file_info['name_without_ext']
# 尝试直接匹配
db_template = db_templates.get(file_path)
# 如果直接匹配失败,尝试通过文件名匹配
if not db_template:
matched_path = match_file_path(file_path, list(db_templates.keys()))
if matched_path:
db_template = db_templates[matched_path]
if db_template:
matched_count += 1
current_name = db_template['name']
# 检查名称是否一致
if current_name != expected_name:
print(f"\n文件: {file_name}")
if update_template_name(conn, db_template['id'], expected_name, current_name):
updated_count += 1
else:
print(f" [OK] {file_name} - 名称已正确")
else:
not_found_count += 1
print(f" [WARN] 未找到: {file_name}")
print("\n" + "=" * 80)
print("检查完成")
print("=" * 80)
print(f"总文件数: {len(file_templates)}")
print(f"匹配成功: {matched_count}")
print(f"更新数量: {updated_count}")
print(f"未找到: {not_found_count}")
print("=" * 80)
except Exception as e:
print(f"\n[ERROR] 发生错误: {e}")
import traceback
traceback.print_exc()
if 'conn' in locals():
conn.rollback()
finally:
if 'conn' in locals():
conn.close()
print("\n数据库连接已关闭")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,372 @@
"""
处理"6.1保密承诺书(谈话对象使用-非中共党员用).docx"
- 解析占位符
- 上传到MinIO
- 更新数据库
"""
import os
import sys
import re
import json
import pymysql
from minio import Minio
from minio.error import S3Error
from datetime import datetime
from pathlib import Path
from docx import Document
from typing import Dict, List, Optional, Tuple
# 设置输出编码为UTF-8Windows兼容
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# MinIO连接配置
MINIO_CONFIG = {
'endpoint': 'minio.datacubeworld.com:9000',
'access_key': 'JOLXFXny3avFSzB0uRA5',
'secret_key': 'G1BR8jStNfovkfH5ou39EmPl34E4l7dGrnd3Cz0I',
'secure': True
}
# 数据库连接配置
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
# 固定值
TENANT_ID = 615873064429507639
CREATED_BY = 655162080928945152
UPDATED_BY = 655162080928945152
BUCKET_NAME = 'finyx'
# 文件路径
TEMPLATE_FILE = 'template_finish/2-初核模版/2.谈话审批/走读式谈话流程/6.1保密承诺书(谈话对象使用-非中共党员用).docx'
PARENT_ID = 1765273962716807 # 走读式谈话流程的ID
TEMPLATE_NAME = '6.1保密承诺书(谈话对象使用-非中共党员用)'
def generate_id():
"""生成ID"""
import time
import random
timestamp = int(time.time() * 1000)
random_part = random.randint(100000, 999999)
return timestamp * 1000 + random_part
def extract_placeholders_from_docx(file_path: str) -> List[str]:
"""
从docx文件中提取所有占位符
Args:
file_path: docx文件路径
Returns:
占位符列表格式: ['field_code1', 'field_code2', ...]
"""
placeholders = set()
pattern = r'\{\{([^}]+)\}\}' # 匹配 {{field_code}} 格式
try:
doc = Document(file_path)
# 从段落中提取占位符
for paragraph in doc.paragraphs:
text = paragraph.text
matches = re.findall(pattern, text)
for match in matches:
placeholders.add(match.strip())
# 从表格中提取占位符
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
text = paragraph.text
matches = re.findall(pattern, text)
for match in matches:
placeholders.add(match.strip())
except Exception as e:
print(f" 错误: 读取文件失败 - {str(e)}")
return []
return sorted(list(placeholders))
def upload_to_minio(file_path: str, minio_client: Minio) -> str:
"""
上传文件到MinIO
Args:
file_path: 本地文件路径
minio_client: MinIO客户端实例
Returns:
MinIO中的相对路径
"""
try:
# 检查存储桶是否存在
found = minio_client.bucket_exists(BUCKET_NAME)
if not found:
raise Exception(f"存储桶 '{BUCKET_NAME}' 不存在,请先创建")
# 生成MinIO对象路径使用当前日期
now = datetime.now()
file_name = Path(file_path).name
object_name = f'{TENANT_ID}/TEMPLATE/{now.year}/{now.month:02d}/{file_name}'
# 上传文件
minio_client.fput_object(
BUCKET_NAME,
object_name,
file_path,
content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)
# 返回相对路径(以/开头)
return f"/{object_name}"
except S3Error as e:
raise Exception(f"MinIO错误: {e}")
except Exception as e:
raise Exception(f"上传文件时发生错误: {e}")
def get_db_fields(conn) -> Dict[str, Dict]:
"""
获取数据库中所有字段field_type=2的输出字段
Returns:
字典key为filed_codevalue为字段信息
"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = """
SELECT id, name, filed_code, field_type
FROM f_polic_field
WHERE tenant_id = %s AND field_type = 2
"""
cursor.execute(sql, (TENANT_ID,))
fields = cursor.fetchall()
result = {}
for field in fields:
result[field['filed_code']] = {
'id': field['id'],
'name': field['name'],
'filed_code': field['filed_code'],
'field_type': field['field_type']
}
cursor.close()
return result
def match_placeholders_to_fields(placeholders: List[str], fields: Dict[str, Dict]) -> Tuple[List[int], List[str]]:
"""
匹配占位符到数据库字段
Returns:
(匹配的字段ID列表, 未匹配的占位符列表)
"""
matched_field_ids = []
unmatched_placeholders = []
for placeholder in placeholders:
if placeholder in fields:
matched_field_ids.append(fields[placeholder]['id'])
else:
unmatched_placeholders.append(placeholder)
return matched_field_ids, unmatched_placeholders
def create_or_update_template(conn, template_name: str, file_path: str, minio_path: str, parent_id: Optional[int]) -> int:
"""
创建或更新模板记录
Returns:
模板ID
"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
try:
# 查找是否已存在通过名称和parent_id匹配
sql = """
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND name = %s AND parent_id = %s
"""
cursor.execute(sql, (TENANT_ID, template_name, parent_id))
existing = cursor.fetchone()
if existing:
# 更新现有记录
template_id = existing['id']
update_sql = """
UPDATE f_polic_file_config
SET file_path = %s, updated_time = NOW(), updated_by = %s, state = 1
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (minio_path, UPDATED_BY, template_id, TENANT_ID))
conn.commit()
print(f" [UPDATE] 更新模板记录 (ID: {template_id})")
return template_id
else:
# 创建新记录
template_id = generate_id()
insert_sql = """
INSERT INTO f_polic_file_config
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
"""
cursor.execute(insert_sql, (
template_id,
TENANT_ID,
parent_id,
template_name,
None, # input_data
minio_path,
CREATED_BY,
CREATED_BY,
1 # state: 1表示启用
))
conn.commit()
print(f" [CREATE] 创建模板记录 (ID: {template_id})")
return template_id
except Exception as e:
conn.rollback()
raise Exception(f"创建或更新模板失败: {str(e)}")
finally:
cursor.close()
def update_template_field_relations(conn, template_id: int, field_ids: List[int]):
"""
更新模板-字段关联关系
"""
cursor = conn.cursor()
try:
# 删除旧的关联关系
delete_sql = """
DELETE FROM f_polic_file_field
WHERE tenant_id = %s AND file_id = %s
"""
cursor.execute(delete_sql, (TENANT_ID, template_id))
# 插入新的关联关系
if field_ids:
insert_sql = """
INSERT INTO f_polic_file_field
(tenant_id, file_id, filed_id, created_time, created_by, updated_time, updated_by)
VALUES (%s, %s, %s, NOW(), %s, NOW(), %s)
"""
for field_id in field_ids:
cursor.execute(insert_sql, (TENANT_ID, template_id, field_id, CREATED_BY, CREATED_BY))
conn.commit()
print(f" [UPDATE] 更新字段关联关系: {len(field_ids)} 个字段")
except Exception as e:
conn.rollback()
raise Exception(f"更新字段关联关系失败: {str(e)}")
finally:
cursor.close()
def main():
"""主函数"""
print("=" * 80)
print("处理保密承诺书(非中共党员用)模板")
print("=" * 80)
print()
# 检查文件是否存在
if not os.path.exists(TEMPLATE_FILE):
print(f"错误: 文件不存在 - {TEMPLATE_FILE}")
return
print(f"文件路径: {TEMPLATE_FILE}")
print()
try:
# 1. 提取占位符
print("1. 提取占位符...")
placeholders = extract_placeholders_from_docx(TEMPLATE_FILE)
print(f" 找到 {len(placeholders)} 个占位符:")
for i, placeholder in enumerate(placeholders, 1):
print(f" {i}. {{{{ {placeholder} }}}}")
print()
# 2. 连接数据库和MinIO
print("2. 连接数据库和MinIO...")
conn = pymysql.connect(**DB_CONFIG)
minio_client = Minio(
MINIO_CONFIG['endpoint'],
access_key=MINIO_CONFIG['access_key'],
secret_key=MINIO_CONFIG['secret_key'],
secure=MINIO_CONFIG['secure']
)
print(" [OK] 连接成功\n")
# 3. 获取数据库字段
print("3. 获取数据库字段...")
db_fields = get_db_fields(conn)
print(f" [OK] 找到 {len(db_fields)} 个输出字段\n")
# 4. 匹配占位符到字段
print("4. 匹配占位符到字段...")
matched_field_ids, unmatched_placeholders = match_placeholders_to_fields(placeholders, db_fields)
print(f" 匹配成功: {len(matched_field_ids)}")
print(f" 未匹配: {len(unmatched_placeholders)}")
if unmatched_placeholders:
print(f" 未匹配的占位符: {', '.join(unmatched_placeholders)}")
print()
# 5. 上传到MinIO
print("5. 上传到MinIO...")
minio_path = upload_to_minio(TEMPLATE_FILE, minio_client)
print(f" [OK] MinIO路径: {minio_path}\n")
# 6. 创建或更新数据库记录
print("6. 创建或更新数据库记录...")
template_id = create_or_update_template(conn, TEMPLATE_NAME, TEMPLATE_FILE, minio_path, PARENT_ID)
print(f" [OK] 模板ID: {template_id}\n")
# 7. 更新字段关联关系
print("7. 更新字段关联关系...")
update_template_field_relations(conn, template_id, matched_field_ids)
print()
print("=" * 80)
print("处理完成!")
print("=" * 80)
print(f"模板ID: {template_id}")
print(f"MinIO路径: {minio_path}")
print(f"关联字段数: {len(matched_field_ids)}")
except Exception as e:
print(f"\n[ERROR] 发生错误: {e}")
import traceback
traceback.print_exc()
if 'conn' in locals():
conn.rollback()
finally:
if 'conn' in locals():
conn.close()
print("\n数据库连接已关闭")
if __name__ == '__main__':
main()

View File

@ -138,7 +138,6 @@ class DocumentService:
doc = Document(template_path)
print(f"[DEBUG] 文档包含 {len(doc.paragraphs)} 个段落, {len(doc.tables)} 个表格")
<<<<<<< HEAD
def replace_placeholder_in_paragraph(paragraph):
"""在段落中替换占位符处理跨run的情况"""
try:
@ -204,18 +203,6 @@ class DocumentService:
if placeholder in before_text and placeholder not in after_text:
replaced_placeholders.add(field_code)
total_replacements += before_text.count(placeholder)
=======
# 替换占位符 {{field_code}} 为实际值
for paragraph in doc.paragraphs:
# 替换段落文本中的占位符
for field_code, field_value in field_data.items():
placeholder = f"{{{{{field_code}}}}}"
if placeholder in paragraph.text:
# 替换占位符
for run in paragraph.runs:
if placeholder in run.text:
run.text = run.text.replace(placeholder, field_value or '')
>>>>>>> parent of 4897c96 (添加通过taskId获取文档的接口支持文件列表查询和参数验证增强错误处理能力同时优化文档生成逻辑确保生成的文档名称和路径的准确性)
# 替换表格中的占位符
try:
@ -264,7 +251,6 @@ class DocumentService:
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
<<<<<<< HEAD
if hasattr(cell, 'paragraphs'):
for paragraph in cell.paragraphs:
text = paragraph.text
@ -281,15 +267,6 @@ class DocumentService:
print(f" - ⚠️ 仍有未替换的占位符: {sorted(remaining_placeholders)}")
else:
print(f" - ✓ 所有占位符已成功替换")
=======
for paragraph in cell.paragraphs:
for field_code, field_value in field_data.items():
placeholder = f"{{{{{field_code}}}}}"
if placeholder in paragraph.text:
for run in paragraph.runs:
if placeholder in run.text:
run.text = run.text.replace(placeholder, field_value or '')
>>>>>>> parent of 4897c96 (添加通过taskId获取文档的接口支持文件列表查询和参数验证增强错误处理能力同时优化文档生成逻辑确保生成的文档名称和路径的准确性)
# 保存到临时文件
temp_dir = tempfile.gettempdir()
@ -493,9 +470,11 @@ class DocumentService:
if target_name and target_name.strip():
suffix = f"_{target_name.strip()}"
<<<<<<< HEAD
# 生成新文件名
return f"{base_name}{suffix}.docx"
# 生成新文件名(确保是.docx格式
generated_name = f"{base_name}{suffix}.docx"
print(f"[DEBUG] 文档名称生成: '{original_file_name}' -> '{generated_name}' (base_name='{base_name}', suffix='{suffix}')")
return generated_name
def generate_presigned_download_url(self, file_path: str, expires_days: int = 7) -> Optional[str]:
"""
@ -530,11 +509,4 @@ class DocumentService:
# 如果生成URL失败记录错误但不影响主流程
print(f"生成预签名URL失败: {str(e)}")
return None
=======
# 生成新文件名(确保是.docx格式
generated_name = f"{base_name}{suffix}.docx"
print(f"[DEBUG] 文档名称生成: '{original_file_name}' -> '{generated_name}' (base_name='{base_name}', suffix='{suffix}')")
return generated_name
>>>>>>> e3f4a394c1a4333db2fd3a9383be29fa9d9055e0

View File

@ -326,10 +326,14 @@
</div>
<div class="form-group">
<label>文件列表</label>
<label>文件列表(文档模板类型)</label>
<div style="margin-bottom: 10px;">
<button class="btn btn-secondary" onclick="loadAvailableFiles()" style="margin-right: 10px;">📋 加载可用文件列表</button>
<button class="btn btn-secondary" onclick="addFileItem()">+ 手动添加文件</button>
<button class="btn btn-secondary" onclick="loadAvailableFiles()" style="margin-right: 10px;">📋 加载全部可用模板</button>
<button class="btn btn-secondary" onclick="addFileItem()" style="margin-right: 10px;">+ 手动添加文件</button>
<button class="btn btn-danger" onclick="clearAllFiles()">🗑️ 清空列表</button>
</div>
<div style="margin-bottom: 10px; padding: 10px; background: #f0f0f0; border-radius: 4px; font-size: 13px; color: #666;">
💡 提示:点击"加载全部可用模板"可以加载所有可用的文档模板类型,方便测试不同模板的生成效果
</div>
<div id="fileListContainer">
<!-- 动态生成的文件列表 -->
@ -568,16 +572,12 @@
return;
}
// 添加前5个文件作为示例
filesWithPath.slice(0, 5).forEach(file => {
// 加载所有可用文件
filesWithPath.forEach(file => {
addFileItem(file.fileId, file.fileName);
});
if (filesWithPath.length > 5) {
alert(`已加载前5个文件共找到 ${filesWithPath.length} 个可用文件`);
} else {
alert(`已加载 ${filesWithPath.length} 个可用文件`);
}
alert(`已加载全部 ${filesWithPath.length} 个可用文件模板`);
} else {
alert('获取文件列表失败: ' + (result.errorMsg || '未知错误'));
}
@ -603,7 +603,7 @@
addGenerateField('department_opinion', '');
addGenerateField('filler_name', '');
// 自动加载可用的文件列表只加载前2个作为示例
// 自动加载所有可用的文件列表
try {
const response = await fetch('/api/file-configs');
const result = await response.json();
@ -612,19 +612,19 @@
// 只添加有filePath的文件有模板文件的
const filesWithPath = result.data.fileConfigs.filter(f => f.filePath);
// 添加前2个文件作为示例
filesWithPath.slice(0, 2).forEach(file => {
// 加载所有可用文件
filesWithPath.forEach(file => {
addFileItem(file.fileId, file.fileName);
});
if (filesWithPath.length > 0) {
console.log(`已自动加载 ${filesWithPath.length} 个可用文件模板`);
}
} else {
// 如果加载失败使用默认的fileId
addFileItem(1765273961883544, '初步核实审批表.doc'); // 2.初步核实审批表XXX
addFileItem(1765273961563507, '请示报告卡.doc'); // 1.请示报告卡XXX
console.warn('未找到可用的文件配置');
}
} catch (error) {
// 如果加载失败使用默认的fileId
addFileItem(1765273961883544, '初步核实审批表.doc');
addFileItem(1765273961563507, '请示报告卡.doc');
console.warn('自动加载文件列表失败:', error);
}
}
@ -769,8 +769,15 @@
result.data.fpolicFieldParamFileList.forEach(file => {
html += `<div class="result-item">
<strong>${file.fileName}:</strong><br>
文件路径: ${file.filePath || '(无路径)'}
</div>`;
文件路径: ${file.filePath || '(无路径)'}<br>`;
// 如果有下载链接,显示可点击的链接
if (file.downloadUrl) {
html += `下载链接: <a href="${file.downloadUrl}" target="_blank" style="color: #667eea; text-decoration: underline; word-break: break-all;">${file.downloadUrl}</a><br>`;
html += `<button class="btn btn-secondary" onclick="window.open('${file.downloadUrl}', '_blank')" style="margin-top: 5px; padding: 6px 15px; font-size: 14px;">📥 下载文档</button>`;
}
html += `</div>`;
});
}
}
@ -796,6 +803,12 @@
btn.closest('.field-row').remove();
}
function clearAllFiles() {
if (confirm('确定要清空所有文件列表吗?')) {
document.getElementById('fileListContainer').innerHTML = '';
}
}
function displayError(tabType, errorMsg) {
const resultSection = document.getElementById(tabType + 'ResultSection');
const resultBox = document.getElementById(tabType + 'ResultBox');

View File

@ -0,0 +1,472 @@
"""
根据 template_finish/ 目录结构更新 f_polic_file_config 表中的层级结构
"""
import os
import sys
import json
import pymysql
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from collections import defaultdict
# 设置输出编码为UTF-8Windows兼容
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# 数据库连接配置
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
CREATED_BY = 655162080928945152
UPDATED_BY = 655162080928945152
TEMPLATE_BASE_DIR = 'template_finish'
def generate_id():
"""生成ID"""
import time
import random
timestamp = int(time.time() * 1000)
random_part = random.randint(100000, 999999)
return timestamp * 1000 + random_part
def normalize_name(name: str) -> str:
"""
标准化名称去掉扩展名括号内容数字前缀等
用于匹配数据库中的记录
"""
# 去掉扩展名
name = Path(name).stem if '.' in name else name
# 去掉括号内容
import re
name = re.sub(r'[(].*?[)]', '', name)
name = name.strip()
# 去掉数字前缀和点号
name = re.sub(r'^\d+[\.\-]?\s*', '', name)
name = name.strip()
return name
def scan_directory_structure(base_dir: str) -> Dict:
"""
扫描目录结构构建层级关系
Returns:
字典包含目录和文件的层级信息
"""
base_path = Path(base_dir)
if not base_path.exists():
print(f"错误: 目录不存在 - {base_dir}")
return {}
structure = {
'directories': [], # 目录节点列表
'files': [], # 文件节点列表
'name_to_id': {} # 名称到ID的映射用于查找parent_id
}
print("=" * 80)
print("扫描目录结构...")
print("=" * 80)
# 遍历所有目录和文件
for item in base_path.rglob("*"):
relative_path = item.relative_to(base_path)
parts = relative_path.parts
if item.is_dir():
# 目录节点
level = len(parts) - 1 # 层级从0开始
dir_name = parts[-1]
parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None
structure['directories'].append({
'name': dir_name,
'path': str(relative_path),
'level': level,
'parent_path': parent_path,
'parent_id': None # 稍后设置
})
elif item.is_file() and item.suffix == '.docx' and not item.name.startswith("~$"):
# 文件节点
level = len(parts) - 1
file_name = item.name
parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None
structure['files'].append({
'name': file_name,
'path': str(relative_path),
'level': level,
'parent_path': parent_path,
'parent_id': None, # 稍后设置
'file_path': str(item)
})
# 按层级排序
structure['directories'].sort(key=lambda x: (x['level'], x['path']))
structure['files'].sort(key=lambda x: (x['level'], x['path']))
print(f"找到 {len(structure['directories'])} 个目录节点")
print(f"找到 {len(structure['files'])} 个文件节点")
return structure
def get_existing_templates(conn) -> Dict:
"""
获取数据库中现有的模板记录
Returns:
字典key为标准化名称value为模板信息
"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = """
SELECT id, name, parent_id, file_path, state
FROM f_polic_file_config
WHERE tenant_id = %s
"""
cursor.execute(sql, (TENANT_ID,))
templates = cursor.fetchall()
result = {}
for template in templates:
normalized_name = normalize_name(template['name'])
result[normalized_name] = {
'id': template['id'],
'name': template['name'],
'normalized_name': normalized_name,
'parent_id': template['parent_id'],
'file_path': template['file_path'],
'state': template['state']
}
cursor.close()
return result
def find_template_by_name(existing_templates: Dict, name: str, prefer_directory: bool = False) -> Optional[Dict]:
"""
根据名称查找模板支持标准化匹配
Args:
name: 模板名称
prefer_directory: 是否优先匹配目录节点
"""
normalized = normalize_name(name)
# 精确匹配标准化名称
if normalized in existing_templates:
template = existing_templates[normalized]
# 如果prefer_directory为True且找到的是文件继续查找目录
if prefer_directory and template.get('file_path') is not None:
pass # 继续查找
else:
return template
# 模糊匹配(包含关系)
candidates = []
for key, template in existing_templates.items():
if key.startswith("DIR:"):
# 目录节点
if normalized in template.get('normalized_name', '') or template.get('normalized_name', '') in normalized:
candidates.append((template, True))
else:
# 文件节点
if normalized in template.get('normalized_name', '') or template.get('normalized_name', '') in normalized:
candidates.append((template, False))
# 如果prefer_directory优先返回目录节点
if prefer_directory:
for template, is_dir in candidates:
if is_dir:
return template
# 返回第一个匹配的
if candidates:
return candidates[0][0]
return None
def create_or_update_directory(conn, dir_info: Dict, parent_id: Optional[int], existing_templates: Dict) -> int:
"""
创建或更新目录节点
Returns:
目录节点的ID
"""
cursor = conn.cursor()
try:
# 先通过路径查找(最准确)
path_key = f"DIR:{dir_info['path']}"
existing = existing_templates.get(path_key)
# 如果没找到,再通过名称查找(优先目录节点)
if not existing:
existing = find_template_by_name(existing_templates, dir_info['name'], prefer_directory=True)
# 确保找到的是目录节点file_path为None
if existing and existing.get('file_path') is not None:
existing = None
if existing:
# 更新现有记录
template_id = existing['id']
if existing['parent_id'] != parent_id:
update_sql = """
UPDATE f_polic_file_config
SET parent_id = %s, updated_time = NOW(), updated_by = %s, state = 1
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID))
conn.commit()
print(f" [UPDATE] 更新目录: {dir_info['name']} (ID: {template_id}, parent_id: {parent_id})")
else:
print(f" [KEEP] 保持目录: {dir_info['name']} (ID: {template_id})")
return template_id
else:
# 创建新记录
template_id = generate_id()
insert_sql = """
INSERT INTO f_polic_file_config
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
"""
cursor.execute(insert_sql, (
template_id,
TENANT_ID,
parent_id,
dir_info['name'],
None, # input_data
None, # file_path目录节点没有文件路径
CREATED_BY,
CREATED_BY,
1 # state: 1表示启用
))
conn.commit()
print(f" [CREATE] 创建目录: {dir_info['name']} (ID: {template_id}, parent_id: {parent_id})")
return template_id
except Exception as e:
conn.rollback()
raise Exception(f"创建或更新目录失败: {str(e)}")
finally:
cursor.close()
def update_file_parent(conn, file_info: Dict, parent_id: Optional[int], existing_templates: Dict) -> Optional[int]:
"""
更新文件节点的parent_id
Returns:
文件节点的ID如果未找到则返回None
"""
cursor = conn.cursor()
try:
# 查找文件(使用文件名匹配)
existing = find_template_by_name(existing_templates, file_info['name'])
if existing:
template_id = existing['id']
if existing['parent_id'] != parent_id:
update_sql = """
UPDATE f_polic_file_config
SET parent_id = %s, updated_time = NOW(), updated_by = %s
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID))
conn.commit()
print(f" [UPDATE] 更新文件: {file_info['name']} (ID: {template_id}, parent_id: {parent_id})")
else:
print(f" [KEEP] 保持文件: {file_info['name']} (ID: {template_id})")
return template_id
else:
print(f" [WARN] 未找到文件: {file_info['name']}")
return None
except Exception as e:
conn.rollback()
raise Exception(f"更新文件parent_id失败: {str(e)}")
finally:
cursor.close()
def build_path_to_id_map(structure: Dict, existing_templates: Dict, conn) -> Dict[str, int]:
"""
构建路径到ID的映射
Returns:
字典key为路径value为ID
"""
path_to_id = {}
# 处理目录节点(按层级顺序,确保父节点先处理)
# 按层级和路径排序
sorted_dirs = sorted(structure['directories'], key=lambda x: (x['level'], x['path']))
for dir_info in sorted_dirs:
parent_id = None
if dir_info['parent_path']:
parent_id = path_to_id.get(dir_info['parent_path'])
if parent_id is None:
print(f" [WARN] 未找到父目录: {dir_info['parent_path']}")
dir_id = create_or_update_directory(conn, dir_info, parent_id, existing_templates)
path_to_id[dir_info['path']] = dir_id
# 更新existing_templates以便后续查找使用完整路径作为key避免冲突
key = f"DIR:{dir_info['path']}"
existing_templates[key] = {
'id': dir_id,
'name': dir_info['name'],
'normalized_name': normalize_name(dir_info['name']),
'parent_id': parent_id,
'file_path': None,
'state': 1,
'path': dir_info['path']
}
# 同时用标准化名称存储(用于文件查找父目录)
normalized_key = normalize_name(dir_info['name'])
if normalized_key not in existing_templates or existing_templates[normalized_key].get('file_path') is not None:
# 只有当不存在或存在的是文件时才更新
existing_templates[normalized_key] = {
'id': dir_id,
'name': dir_info['name'],
'normalized_name': normalized_key,
'parent_id': parent_id,
'file_path': None,
'state': 1,
'path': dir_info['path']
}
return path_to_id
def update_file_hierarchy(structure: Dict, path_to_id: Dict[str, int], existing_templates: Dict, conn):
"""
更新文件节点的parent_id
"""
for file_info in structure['files']:
parent_id = None
if file_info['parent_path']:
parent_id = path_to_id.get(file_info['parent_path'])
update_file_parent(conn, file_info, parent_id, existing_templates)
def main():
"""主函数"""
print("=" * 80)
print("更新模板层级结构")
print("=" * 80)
print()
try:
# 连接数据库
print("1. 连接数据库...")
conn = pymysql.connect(**DB_CONFIG)
print("[OK] 数据库连接成功\n")
# 扫描目录结构
print("2. 扫描目录结构...")
structure = scan_directory_structure(TEMPLATE_BASE_DIR)
if not structure:
print("错误: 未找到任何目录或文件")
return
# 获取现有模板
print("\n3. 获取现有模板...")
existing_templates = get_existing_templates(conn)
print(f"[OK] 找到 {len(existing_templates)} 个现有模板\n")
# 构建路径到ID的映射处理目录节点
print("4. 创建/更新目录节点...")
print("=" * 80)
path_to_id = build_path_to_id_map(structure, existing_templates, conn)
print(f"\n[OK] 处理了 {len(path_to_id)} 个目录节点\n")
# 更新文件节点的parent_id
print("5. 更新文件节点的parent_id...")
print("=" * 80)
update_file_hierarchy(structure, path_to_id, existing_templates, conn)
print(f"\n[OK] 处理了 {len(structure['files'])} 个文件节点\n")
# 打印层级结构
print("6. 最终层级结构:")
print("=" * 80)
print_hierarchy(conn)
print("\n" + "=" * 80)
print("更新完成!")
print("=" * 80)
except Exception as e:
print(f"\n[ERROR] 发生错误: {e}")
import traceback
traceback.print_exc()
if 'conn' in locals():
conn.rollback()
finally:
if 'conn' in locals():
conn.close()
print("\n数据库连接已关闭")
def print_hierarchy(conn, parent_id=None, level=0, prefix=""):
"""打印层级结构"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
try:
if parent_id is None:
sql = """
SELECT id, name, parent_id, file_path
FROM f_polic_file_config
WHERE tenant_id = %s AND parent_id IS NULL
ORDER BY name
"""
cursor.execute(sql, (TENANT_ID,))
else:
sql = """
SELECT id, name, parent_id, file_path
FROM f_polic_file_config
WHERE tenant_id = %s AND parent_id = %s
ORDER BY name
"""
cursor.execute(sql, (TENANT_ID, parent_id))
items = cursor.fetchall()
for i, item in enumerate(items):
is_last = i == len(items) - 1
current_prefix = prefix + ("└── " if is_last else "├── ")
next_prefix = prefix + (" " if is_last else "")
node_type = "📁" if item['file_path'] is None else "📄"
print(f"{current_prefix}{node_type} {item['name']} (ID: {item['id']})")
# 递归打印子节点
print_hierarchy(conn, item['id'], level + 1, next_prefix)
finally:
cursor.close()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,405 @@
"""
根据 template_finish/ 目录结构更新 f_polic_file_config 表中的层级结构
使用file_path作为唯一标识确保正确建立层级关系
"""
import os
import sys
import json
import pymysql
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from collections import defaultdict
from datetime import datetime
# 设置输出编码为UTF-8Windows兼容
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# 数据库连接配置
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
CREATED_BY = 655162080928945152
UPDATED_BY = 655162080928945152
TEMPLATE_BASE_DIR = 'template_finish'
def generate_id():
"""生成ID"""
import time
import random
timestamp = int(time.time() * 1000)
random_part = random.randint(100000, 999999)
return timestamp * 1000 + random_part
def scan_directory_structure(base_dir: str) -> Dict:
"""
扫描目录结构构建层级关系
Returns:
字典包含目录和文件的层级信息
"""
base_path = Path(base_dir)
if not base_path.exists():
print(f"错误: 目录不存在 - {base_dir}")
return {}
structure = {
'directories': [], # 目录节点列表
'files': [] # 文件节点列表
}
print("=" * 80)
print("扫描目录结构...")
print("=" * 80)
# 遍历所有目录和文件
for item in base_path.rglob("*"):
relative_path = item.relative_to(base_path)
parts = relative_path.parts
if item.is_dir():
# 目录节点
level = len(parts) - 1 # 层级从0开始
dir_name = parts[-1]
parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None
structure['directories'].append({
'name': dir_name,
'path': str(relative_path),
'level': level,
'parent_path': parent_path
})
elif item.is_file() and item.suffix == '.docx' and not item.name.startswith("~$"):
# 文件节点
level = len(parts) - 1
file_name = item.name
parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None
# 构建MinIO路径
now = datetime.now()
minio_path = f'/615873064429507639/TEMPLATE/{now.year}/{now.month:02d}/{file_name}'
structure['files'].append({
'name': file_name,
'path': str(relative_path),
'level': level,
'parent_path': parent_path,
'file_path': str(item),
'minio_path': minio_path
})
# 按层级排序
structure['directories'].sort(key=lambda x: (x['level'], x['path']))
structure['files'].sort(key=lambda x: (x['level'], x['path']))
print(f"找到 {len(structure['directories'])} 个目录节点")
print(f"找到 {len(structure['files'])} 个文件节点")
return structure
def get_existing_templates(conn) -> Dict:
"""
获取数据库中现有的模板记录
Returns:
字典key为file_pathvalue为模板信息
"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = """
SELECT id, name, parent_id, file_path, state
FROM f_polic_file_config
WHERE tenant_id = %s
"""
cursor.execute(sql, (TENANT_ID,))
templates = cursor.fetchall()
# 使用file_path作为key如果存在
result_by_path = {}
# 使用name作为key用于目录节点
result_by_name = {}
for template in templates:
if template['file_path']:
result_by_path[template['file_path']] = {
'id': template['id'],
'name': template['name'],
'parent_id': template['parent_id'],
'file_path': template['file_path'],
'state': template['state']
}
else:
# 目录节点
name = template['name']
if name not in result_by_name:
result_by_name[name] = []
result_by_name[name].append({
'id': template['id'],
'name': template['name'],
'parent_id': template['parent_id'],
'file_path': None,
'state': template['state']
})
cursor.close()
return {
'by_path': result_by_path,
'by_name': result_by_name
}
def create_or_update_directory(conn, dir_name: str, parent_id: Optional[int], existing_templates: Dict) -> int:
"""
创建或更新目录节点
Returns:
目录节点的ID
"""
cursor = conn.cursor()
try:
# 查找是否已存在通过名称精确匹配且file_path为None
candidates = existing_templates['by_name'].get(dir_name, [])
existing = None
for candidate in candidates:
if candidate.get('file_path') is None: # 目录节点
# 如果parent_id匹配优先选择
if candidate['parent_id'] == parent_id:
existing = candidate
break
elif existing is None:
existing = candidate
if existing:
# 更新现有目录记录
template_id = existing['id']
if existing['parent_id'] != parent_id:
update_sql = """
UPDATE f_polic_file_config
SET parent_id = %s, updated_time = NOW(), updated_by = %s, state = 1
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID))
conn.commit()
print(f" [UPDATE] 更新目录: {dir_name} (ID: {template_id}, parent_id: {parent_id})")
else:
print(f" [KEEP] 保持目录: {dir_name} (ID: {template_id})")
return template_id
else:
# 创建新目录记录
template_id = generate_id()
insert_sql = """
INSERT INTO f_polic_file_config
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
"""
cursor.execute(insert_sql, (
template_id,
TENANT_ID,
parent_id,
dir_name,
None, # input_data
None, # file_path目录节点没有文件路径
CREATED_BY,
CREATED_BY,
1 # state: 1表示启用
))
conn.commit()
print(f" [CREATE] 创建目录: {dir_name} (ID: {template_id}, parent_id: {parent_id})")
# 更新existing_templates
if dir_name not in existing_templates['by_name']:
existing_templates['by_name'][dir_name] = []
existing_templates['by_name'][dir_name].append({
'id': template_id,
'name': dir_name,
'parent_id': parent_id,
'file_path': None,
'state': 1
})
return template_id
except Exception as e:
conn.rollback()
raise Exception(f"创建或更新目录失败: {str(e)}")
finally:
cursor.close()
def update_file_parent(conn, file_info: Dict, parent_id: Optional[int], existing_templates: Dict) -> Optional[int]:
"""
更新文件节点的parent_id
Args:
file_info: 文件信息包含nameminio_path等
parent_id: 父节点ID
Returns:
文件节点的ID如果未找到则返回None
"""
cursor = conn.cursor()
try:
file_name = file_info['name']
minio_path = file_info.get('minio_path')
# 优先通过file_pathminio_path匹配最准确
existing = None
if minio_path and minio_path in existing_templates['by_path']:
existing = existing_templates['by_path'][minio_path]
if not existing:
print(f" [WARN] 未找到文件: {file_name} (MinIO路径: {minio_path})")
return None
template_id = existing['id']
if existing['parent_id'] != parent_id:
update_sql = """
UPDATE f_polic_file_config
SET parent_id = %s, updated_time = NOW(), updated_by = %s
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID))
conn.commit()
print(f" [UPDATE] 更新文件: {file_name} (ID: {template_id}, parent_id: {parent_id})")
else:
print(f" [KEEP] 保持文件: {file_name} (ID: {template_id})")
return template_id
except Exception as e:
conn.rollback()
raise Exception(f"更新文件parent_id失败: {str(e)}")
finally:
cursor.close()
def main():
"""主函数"""
print("=" * 80)
print("更新模板层级结构")
print("=" * 80)
print()
try:
# 连接数据库
print("1. 连接数据库...")
conn = pymysql.connect(**DB_CONFIG)
print("[OK] 数据库连接成功\n")
# 扫描目录结构
print("2. 扫描目录结构...")
structure = scan_directory_structure(TEMPLATE_BASE_DIR)
if not structure:
print("错误: 未找到任何目录或文件")
return
# 获取现有模板
print("\n3. 获取现有模板...")
existing_templates = get_existing_templates(conn)
print(f"[OK] 找到 {len(existing_templates['by_path'])} 个文件模板")
print(f"[OK] 找到 {sum(len(v) for v in existing_templates['by_name'].values())} 个目录模板\n")
# 构建路径到ID的映射处理目录节点
print("4. 创建/更新目录节点...")
print("=" * 80)
path_to_id = {}
# 按层级顺序处理目录
for dir_info in structure['directories']:
parent_id = None
if dir_info['parent_path']:
parent_id = path_to_id.get(dir_info['parent_path'])
dir_id = create_or_update_directory(conn, dir_info['name'], parent_id, existing_templates)
path_to_id[dir_info['path']] = dir_id
print(f"\n[OK] 处理了 {len(path_to_id)} 个目录节点\n")
# 更新文件节点的parent_id
print("5. 更新文件节点的parent_id...")
print("=" * 80)
for file_info in structure['files']:
parent_id = None
if file_info['parent_path']:
parent_id = path_to_id.get(file_info['parent_path'])
update_file_parent(conn, file_info, parent_id, existing_templates)
print(f"\n[OK] 处理了 {len(structure['files'])} 个文件节点\n")
# 打印层级结构
print("6. 最终层级结构:")
print("=" * 80)
print_hierarchy(conn)
print("\n" + "=" * 80)
print("更新完成!")
print("=" * 80)
except Exception as e:
print(f"\n[ERROR] 发生错误: {e}")
import traceback
traceback.print_exc()
if 'conn' in locals():
conn.rollback()
finally:
if 'conn' in locals():
conn.close()
print("\n数据库连接已关闭")
def print_hierarchy(conn, parent_id=None, level=0, prefix=""):
"""打印层级结构"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
try:
if parent_id is None:
sql = """
SELECT id, name, parent_id, file_path
FROM f_polic_file_config
WHERE tenant_id = %s AND parent_id IS NULL
ORDER BY name
"""
cursor.execute(sql, (TENANT_ID,))
else:
sql = """
SELECT id, name, parent_id, file_path
FROM f_polic_file_config
WHERE tenant_id = %s AND parent_id = %s
ORDER BY name
"""
cursor.execute(sql, (TENANT_ID, parent_id))
items = cursor.fetchall()
for i, item in enumerate(items):
is_last = i == len(items) - 1
current_prefix = prefix + ("└── " if is_last else "├── ")
next_prefix = prefix + (" " if is_last else "")
node_type = "📁" if item['file_path'] is None else "📄"
print(f"{current_prefix}{node_type} {item['name']} (ID: {item['id']})")
# 递归打印子节点
print_hierarchy(conn, item['id'], level + 1, next_prefix)
finally:
cursor.close()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,445 @@
"""
根据 template_finish/ 目录结构更新 f_polic_file_config 表中的层级结构
使用路径作为唯一标识确保正确建立层级关系
"""
import os
import sys
import json
import pymysql
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from collections import defaultdict
# 设置输出编码为UTF-8Windows兼容
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# 数据库连接配置
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
CREATED_BY = 655162080928945152
UPDATED_BY = 655162080928945152
TEMPLATE_BASE_DIR = 'template_finish'
def generate_id():
"""生成ID"""
import time
import random
timestamp = int(time.time() * 1000)
random_part = random.randint(100000, 999999)
return timestamp * 1000 + random_part
def scan_directory_structure(base_dir: str) -> Dict:
"""
扫描目录结构构建层级关系
Returns:
字典包含目录和文件的层级信息
"""
base_path = Path(base_dir)
if not base_path.exists():
print(f"错误: 目录不存在 - {base_dir}")
return {}
structure = {
'directories': [], # 目录节点列表
'files': [] # 文件节点列表
}
print("=" * 80)
print("扫描目录结构...")
print("=" * 80)
# 遍历所有目录和文件
for item in base_path.rglob("*"):
relative_path = item.relative_to(base_path)
parts = relative_path.parts
if item.is_dir():
# 目录节点
level = len(parts) - 1 # 层级从0开始
dir_name = parts[-1]
parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None
structure['directories'].append({
'name': dir_name,
'path': str(relative_path),
'level': level,
'parent_path': parent_path
})
elif item.is_file() and item.suffix == '.docx' and not item.name.startswith("~$"):
# 文件节点
level = len(parts) - 1
file_name = item.name
parent_path = str(Path(*parts[:-1])) if len(parts) > 1 else None
structure['files'].append({
'name': file_name,
'path': str(relative_path),
'level': level,
'parent_path': parent_path,
'file_path': str(item)
})
# 按层级排序
structure['directories'].sort(key=lambda x: (x['level'], x['path']))
structure['files'].sort(key=lambda x: (x['level'], x['path']))
print(f"找到 {len(structure['directories'])} 个目录节点")
print(f"找到 {len(structure['files'])} 个文件节点")
return structure
def get_existing_templates(conn) -> Dict:
"""
获取数据库中现有的模板记录
Returns:
字典key为file_path如果存在或名称value为模板信息列表
"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = """
SELECT id, name, parent_id, file_path, state
FROM f_polic_file_config
WHERE tenant_id = %s
"""
cursor.execute(sql, (TENANT_ID,))
templates = cursor.fetchall()
result = {}
for template in templates:
# 优先使用file_path作为key更准确
if template['file_path']:
key = template['file_path']
if key not in result:
result[key] = []
result[key].append({
'id': template['id'],
'name': template['name'],
'parent_id': template['parent_id'],
'file_path': template['file_path'],
'state': template['state']
})
# 同时使用名称作为key用于目录节点和没有file_path的记录
name_key = template['name']
if name_key not in result:
result[name_key] = []
result[name_key].append({
'id': template['id'],
'name': template['name'],
'parent_id': template['parent_id'],
'file_path': template['file_path'],
'state': template['state']
})
cursor.close()
return result
def create_or_update_directory(conn, dir_name: str, parent_id: Optional[int], existing_templates: Dict) -> int:
"""
创建或更新目录节点
Returns:
目录节点的ID
"""
cursor = conn.cursor()
try:
# 查找是否已存在通过名称精确匹配且file_path为None
candidates = existing_templates.get(dir_name, [])
existing = None
for candidate in candidates:
if candidate.get('file_path') is None: # 目录节点
existing = candidate
break
if existing:
# 更新现有目录记录
template_id = existing['id']
if existing['parent_id'] != parent_id:
update_sql = """
UPDATE f_polic_file_config
SET parent_id = %s, updated_time = NOW(), updated_by = %s, state = 1
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID))
conn.commit()
print(f" [UPDATE] 更新目录: {dir_name} (ID: {template_id}, parent_id: {parent_id})")
else:
print(f" [KEEP] 保持目录: {dir_name} (ID: {template_id})")
return template_id
else:
# 创建新目录记录
template_id = generate_id()
insert_sql = """
INSERT INTO f_polic_file_config
(id, tenant_id, parent_id, name, input_data, file_path, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s)
"""
cursor.execute(insert_sql, (
template_id,
TENANT_ID,
parent_id,
dir_name,
None, # input_data
None, # file_path目录节点没有文件路径
CREATED_BY,
CREATED_BY,
1 # state: 1表示启用
))
conn.commit()
print(f" [CREATE] 创建目录: {dir_name} (ID: {template_id}, parent_id: {parent_id})")
# 更新existing_templates
if dir_name not in existing_templates:
existing_templates[dir_name] = []
existing_templates[dir_name].append({
'id': template_id,
'name': dir_name,
'parent_id': parent_id,
'file_path': None,
'state': 1
})
return template_id
except Exception as e:
conn.rollback()
raise Exception(f"创建或更新目录失败: {str(e)}")
finally:
cursor.close()
def normalize_name(name: str) -> str:
"""
标准化名称去掉扩展名括号内容数字前缀等
"""
import re
# 去掉扩展名
name = Path(name).stem if '.' in name else name
# 去掉括号内容
name = re.sub(r'[(].*?[)]', '', name)
name = name.strip()
# 去掉数字前缀和点号
name = re.sub(r'^\d+[\.\-]?\s*', '', name)
name = name.strip()
return name
def update_file_parent(conn, file_info: Dict, parent_id: Optional[int], existing_templates: Dict) -> Optional[int]:
"""
更新文件节点的parent_id
Args:
file_info: 文件信息包含name和file_path
parent_id: 父节点ID
Returns:
文件节点的ID如果未找到则返回None
"""
cursor = conn.cursor()
try:
file_name = file_info['name']
# 根据文件路径构建预期的MinIO路径
# 文件路径格式: template_finish/2-初核模版/1.初核请示/1.请示报告卡XXX.docx
# MinIO路径格式: /615873064429507639/TEMPLATE/2025/12/1.请示报告卡XXX.docx
expected_path = None
if 'file_path' in file_info:
# 从相对路径构建MinIO路径
from datetime import datetime
now = datetime.now()
relative_path = file_info.get('path', '')
file_name_only = Path(file_name).name
expected_path = f'/615873064429507639/TEMPLATE/{now.year}/{now.month:02d}/{file_name_only}'
# 优先通过file_path匹配最准确
existing = None
if expected_path and expected_path in existing_templates:
candidates = existing_templates[expected_path]
if candidates:
existing = candidates[0]
# 如果没找到,通过标准化名称匹配
if not existing:
normalized_file_name = normalize_name(file_name)
candidates = []
for key, templates in existing_templates.items():
for template in templates:
if template.get('file_path'): # 只匹配有file_path的文件节点
normalized_template_name = normalize_name(template['name'])
if normalized_template_name == normalized_file_name:
# 检查file_path是否匹配
if expected_path and template['file_path'] == expected_path:
existing = template
break
candidates.append(template)
if existing:
break
if not existing and candidates:
# 如果有多个候选选择parent_id匹配的
for candidate in candidates:
if candidate['parent_id'] == parent_id:
existing = candidate
break
# 如果还是没找到,选择第一个
if not existing:
existing = candidates[0]
if not existing:
print(f" [WARN] 未找到文件: {file_name} (标准化: {normalize_name(file_name)})")
return None
template_id = existing['id']
if existing['parent_id'] != parent_id:
update_sql = """
UPDATE f_polic_file_config
SET parent_id = %s, updated_time = NOW(), updated_by = %s
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (parent_id, UPDATED_BY, template_id, TENANT_ID))
conn.commit()
print(f" [UPDATE] 更新文件: {file_name} -> {existing['name']} (ID: {template_id}, parent_id: {parent_id})")
else:
print(f" [KEEP] 保持文件: {file_name} -> {existing['name']} (ID: {template_id})")
return template_id
except Exception as e:
conn.rollback()
raise Exception(f"更新文件parent_id失败: {str(e)}")
finally:
cursor.close()
def main():
"""主函数"""
print("=" * 80)
print("更新模板层级结构")
print("=" * 80)
print()
try:
# 连接数据库
print("1. 连接数据库...")
conn = pymysql.connect(**DB_CONFIG)
print("[OK] 数据库连接成功\n")
# 扫描目录结构
print("2. 扫描目录结构...")
structure = scan_directory_structure(TEMPLATE_BASE_DIR)
if not structure:
print("错误: 未找到任何目录或文件")
return
# 获取现有模板
print("\n3. 获取现有模板...")
existing_templates = get_existing_templates(conn)
print(f"[OK] 找到 {len(existing_templates)} 个现有模板\n")
# 构建路径到ID的映射处理目录节点
print("4. 创建/更新目录节点...")
print("=" * 80)
path_to_id = {}
# 按层级顺序处理目录
for dir_info in structure['directories']:
parent_id = None
if dir_info['parent_path']:
parent_id = path_to_id.get(dir_info['parent_path'])
dir_id = create_or_update_directory(conn, dir_info['name'], parent_id, existing_templates)
path_to_id[dir_info['path']] = dir_id
print(f"\n[OK] 处理了 {len(path_to_id)} 个目录节点\n")
# 更新文件节点的parent_id
print("5. 更新文件节点的parent_id...")
print("=" * 80)
for file_info in structure['files']:
parent_id = None
if file_info['parent_path']:
parent_id = path_to_id.get(file_info['parent_path'])
update_file_parent(conn, file_info, parent_id, existing_templates)
print(f"\n[OK] 处理了 {len(structure['files'])} 个文件节点\n")
# 打印层级结构
print("6. 最终层级结构:")
print("=" * 80)
print_hierarchy(conn)
print("\n" + "=" * 80)
print("更新完成!")
print("=" * 80)
except Exception as e:
print(f"\n[ERROR] 发生错误: {e}")
import traceback
traceback.print_exc()
if 'conn' in locals():
conn.rollback()
finally:
if 'conn' in locals():
conn.close()
print("\n数据库连接已关闭")
def print_hierarchy(conn, parent_id=None, level=0, prefix=""):
"""打印层级结构"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
try:
if parent_id is None:
sql = """
SELECT id, name, parent_id, file_path
FROM f_polic_file_config
WHERE tenant_id = %s AND parent_id IS NULL
ORDER BY name
"""
cursor.execute(sql, (TENANT_ID,))
else:
sql = """
SELECT id, name, parent_id, file_path
FROM f_polic_file_config
WHERE tenant_id = %s AND parent_id = %s
ORDER BY name
"""
cursor.execute(sql, (TENANT_ID, parent_id))
items = cursor.fetchall()
for i, item in enumerate(items):
is_last = i == len(items) - 1
current_prefix = prefix + ("└── " if is_last else "├── ")
next_prefix = prefix + (" " if is_last else "")
node_type = "📁" if item['file_path'] is None else "📄"
print(f"{current_prefix}{node_type} {item['name']} (ID: {item['id']})")
# 递归打印子节点
print_hierarchy(conn, item['id'], level + 1, next_prefix)
finally:
cursor.close()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,56 @@
"""
验证层级结构是否正确
"""
import pymysql
DB_CONFIG = {
'host': '152.136.177.240',
'port': 5012,
'user': 'finyx',
'password': '6QsGK6MpePZDE57Z',
'database': 'finyx',
'charset': 'utf8mb4'
}
TENANT_ID = 615873064429507639
conn = pymysql.connect(**DB_CONFIG)
cursor = conn.cursor(pymysql.cursors.DictCursor)
# 检查"1请示报告卡"的记录
cursor.execute("""
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND name LIKE %s
ORDER BY id
""", (TENANT_ID, '%请示报告卡%'))
results = cursor.fetchall()
print(f"找到 {len(results)}'请示报告卡'相关记录:\n")
for r in results:
print(f"ID: {r['id']}")
print(f"名称: {r['name']}")
print(f"文件路径: {r['file_path']}")
print(f"父节点ID: {r['parent_id']}")
print()
# 检查"XXX初核情况报告"的记录
cursor.execute("""
SELECT id, name, file_path, parent_id
FROM f_polic_file_config
WHERE tenant_id = %s AND name LIKE %s
ORDER BY id
""", (TENANT_ID, '%初核情况报告%'))
results = cursor.fetchall()
print(f"\n找到 {len(results)}'初核情况报告'相关记录:\n")
for r in results:
print(f"ID: {r['id']}")
print(f"名称: {r['name']}")
print(f"文件路径: {r['file_path']}")
print(f"父节点ID: {r['parent_id']}")
print()
cursor.close()
conn.close()

113
修复模板名称总结.md Normal file
View File

@ -0,0 +1,113 @@
# 修复模板名称总结
## 任务完成情况
✅ **已完成所有任务**
根据 template_finish/ 目录下的模板文件,检查并修复了数据库中 f_polic_file_config 表的 name 字段,确保与模板文档名称(去掉扩展名)完全一致。
## 修复结果
### 统计信息
- **总文件数**: 21
- **匹配成功**: 21
- **更新数量**: 16
- **未找到**: 0
### 更新的模板名称
1. **1.请示报告卡XXX.docx**
- 旧名称: `1请示报告卡`
- 新名称: `1.请示报告卡XXX`
2. **2.初步核实审批表XXX.docx**
- 旧名称: `初步核实审批表`
- 新名称: `2.初步核实审批表XXX`
3. **3.附件初核方案(XXX).docx**
- 旧名称: `附件初核方案`
- 新名称: `3.附件初核方案(XXX)`
4. **1.请示报告卡(初核谈话).docx**
- 旧名称: `1请示报告卡`
- 新名称: `1.请示报告卡(初核谈话)`
5. **2谈话审批表.docx**
- 旧名称: `谈话审批表`
- 新名称: `2谈话审批表`
6. **3.谈话前安全风险评估表.docx**
- 旧名称: `谈话前安全风险评估表`
- 新名称: `3.谈话前安全风险评估表`
7. **4.谈话方案.docx**
- 旧名称: `谈话方案`
- 新名称: `4.谈话方案`
8. **5.谈话后安全风险评估表.docx**
- 旧名称: `谈话后安全风险评估表`
- 新名称: `5.谈话后安全风险评估表`
9. **1.谈话笔录.docx**
- 旧名称: `谈话笔录`
- 新名称: `1.谈话笔录`
10. **2.谈话询问对象情况摸底调查30问.docx**
- 旧名称: `谈话询问对象情况摸底调查30问`
- 新名称: `2.谈话询问对象情况摸底调查30问`
11. **3.被谈话人权利义务告知书.docx**
- 旧名称: `被谈话人权利义务告知书`
- 新名称: `3.被谈话人权利义务告知书`
12. **4.点对点交接单.docx**
- 旧名称: `点对点交接单`
- 新名称: `4.点对点交接单`
13. **5.陪送交接单(新).docx**
- 旧名称: `陪送交接单`
- 新名称: `5.陪送交接单(新)`
14. **6.2保密承诺书(谈话对象使用-中共党员用).docx**
- 旧名称: `2保密承诺书`
- 新名称: `6.2保密承诺书(谈话对象使用-中共党员用)`
15. **7.办案人员-办案安全保密承诺书.docx**
- 旧名称: `办案人员-办案安全保密承诺书`
- 新名称: `7.办案人员-办案安全保密承诺书`
16. **8.XXX初核情况报告.docx**
- 旧名称: `XXX初核情况报告`
- 新名称: `8.XXX初核情况报告`
### 名称已正确的模板(无需更新)
1. 谈话通知书第一联.docx
2. 谈话通知书第三联.docx
3. 谈话通知书第二联.docx
4. 6.1保密承诺书(谈话对象使用-非中共党员用).docx
5. 8-1请示报告卡初核报告结论 .docx
## 修复规则
- **文件名**: `1.请示报告卡XXX.docx`
- **数据库name字段**: `1.请示报告卡XXX`(去掉扩展名,保留所有其他内容)
确保:
- 保留数字前缀(如 "1."、"2."
- 保留括号内容(如 "XXX"、"(初核谈话)"
- 保留所有标点符号和特殊字符
- 只去掉文件扩展名(.docx
## 脚本文件
**fix_template_names.py** - 修复模板名称的脚本
- 扫描 template_finish 目录下的所有 .docx 文件
- 获取每个文件的完整名称(去掉扩展名)
- 检查数据库中对应的记录
- 如果 name 字段不匹配,更新为正确的名称
## 完成时间
2025年12月11日

View File

@ -0,0 +1,75 @@
# 处理保密承诺书(非中共党员用)模板总结
## 任务完成情况
✅ **已完成所有任务**
1. ✅ 解析文档中的占位符
2. ✅ 上传文档到MinIO
3. ✅ 更新数据库记录
## 处理结果
### 文件信息
- **文件路径**: `template_finish/2-初核模版/2.谈话审批/走读式谈话流程/6.1保密承诺书(谈话对象使用-非中共党员用).docx`
- **模板名称**: `6.1保密承诺书(谈话对象使用-非中共党员用)`
- **父节点ID**: `1765273962716807` (走读式谈话流程)
### 占位符提取结果
找到 **5个占位符**,全部成功匹配到数据库字段:
1. `{{ target_contact }}` - 被核查人联系方式
2. `{{ target_id_number }}` - 被核查人身份证号
3. `{{ target_name }}` - 被核查人姓名
4. `{{ target_organization_and_position }}` - 被核查人单位及职务
5. `{{ target_political_status }}` - 被核查人政治面貌
### MinIO上传结果
- **MinIO路径**: `/615873064429507639/TEMPLATE/2025/12/6.1保密承诺书(谈话对象使用-非中共党员用).docx`
- **上传状态**: ✅ 成功
### 数据库更新结果
- **模板ID**: `1765434655750341`
- **创建状态**: ✅ 新创建
- **字段关联**: ✅ 5个字段全部关联成功
## 数据库记录详情
### f_polic_file_config 表
- **id**: 1765434655750341
- **tenant_id**: 615873064429507639
- **parent_id**: 1765273962716807 (走读式谈话流程)
- **name**: 6.1保密承诺书(谈话对象使用-非中共党员用)
- **file_path**: /615873064429507639/TEMPLATE/2025/12/6.1保密承诺书(谈话对象使用-非中共党员用).docx
- **state**: 1 (启用)
### f_polic_file_field 表
已创建5条关联记录关联的字段ID
- target_contact
- target_id_number
- target_name
- target_organization_and_position
- target_political_status
## 层级结构
该模板现在位于:
```
📁 2-初核模版
└── 📁 2.谈话审批
└── 📁 走读式谈话流程
└── 📄 6.1保密承诺书(谈话对象使用-非中共党员用) (ID: 1765434655750341)
```
## 脚本文件
**process_confidentiality_commitment_non_party.py** - 处理该模板的专用脚本
- 提取占位符
- 上传到MinIO
- 创建/更新数据库记录
- 建立字段关联关系
## 完成时间
2025年12月11日

View File

@ -0,0 +1,124 @@
# 更新模板层级结构总结
## 任务完成情况
✅ **已完成主要任务**
1. ✅ 根据 template_finish/ 目录结构更新 f_polic_file_config 表的层级结构
2. ✅ 创建目录节点(一级、二级、三级)
3. ✅ 更新文件的 parent_id建立正确的层级关系
## 目录结构
根据 `template_finish/2-初核模版/` 目录,层级结构如下:
### 一级节点
- **2-初核模版** (ID: 1765273961277310)
### 二级节点
- **1.初核请示** (ID: 1765431558933731) - parent: 2-初核模版
- **2.谈话审批** (ID: 1765431558825578) - parent: 2-初核模版
- **3.初核结论** (ID: 1765431559135346) - parent: 2-初核模版
### 三级节点
- **谈话通知书** (ID: 1765273962774249) - parent: 2.谈话审批
- **走读式谈话审批** (ID: 1765273962700431) - parent: 2.谈话审批
- **走读式谈话流程** (ID: 1765273962716807) - parent: 2.谈话审批
## 文件节点分布
### 1.初核请示 下的文件
- 初步核实审批表 (ID: 1765425918287774)
- 附件初核方案 (ID: 1765425918483987)
- 1请示报告卡 (ID: 1765425930118581) - 注意:此文件在多个位置出现
### 2.谈话审批/谈话通知书 下的文件
- 谈话通知书第一联 (ID: 1765273963625524)
- 谈话通知书第二联 (ID: 1765273963825806)
- 谈话通知书第三联 (ID: 1765273963038891)
### 2.谈话审批/走读式谈话审批 下的文件
- 1请示报告卡 (ID: 1765425930118581) - 注意:与"1.初核请示"下的文件重复
- 谈话审批表 (ID: 1765425918217504)
- 谈话前安全风险评估表 (ID: 1765425918902422)
- 谈话方案 (ID: 1765425918745496)
- 谈话后安全风险评估表 (ID: 1765425919242549)
### 2.谈话审批/走读式谈话流程 下的文件
- 谈话笔录 (ID: 1765425918749247)
- 谈话询问对象情况摸底调查30问 (ID: 1765425918921697)
- 被谈话人权利义务告知书 (ID: 1765425930059797)
- 点对点交接单 (ID: 1765425919512780)
- 陪送交接单 (ID: 1765425919389484)
- 办案人员-办案安全保密承诺书 (ID: 1765425919629084)
- 8.XXX初核情况报告 (ID: 1765273963158289) - 注意:此文件位置可能不正确
### 3.初核结论 下的文件
- 8-1请示报告卡初核报告结论 (ID: 1765273962631542)
- XXX初核情况报告 (ID: 1765425930460962)
## 已知问题
1. **缺失文件**: 以下文件在数据库中未找到:
- 6.1保密承诺书(谈话对象使用-非中共党员用).docx
这个文件可能在数据库中不存在,或者名称不同。数据库中只有"2保密承诺书"ID: 1765425919729046可能是"6.2保密承诺书(谈话对象使用-中共党员用)"的简化名称。
2. **重复文件已解决**: "1请示报告卡"现在正确地在两个位置:
- 1.初核请示 下 (ID: 1765425930118581) - 对应文件"1.请示报告卡XXX.docx"
- 走读式谈话审批 下 (ID: 1765432134276990) - 对应文件"1.请示报告卡(初核谈话).docx"
这两个是不同的文件,现在已正确区分。
## 执行结果
- **创建的目录节点**: 3个1.初核请示、2.谈话审批、3.初核结论)
- **更新的目录节点**: 4个更新了parent_id
- **更新的文件节点**: 20个更新了parent_id
- **未找到的文件**: 1个6.1保密承诺书(谈话对象使用-非中共党员用).docx
## 最终层级结构
```
📁 2-初核模版
├── 📁 1.初核请示
│ ├── 📄 1请示报告卡 (ID: 1765425930118581)
│ ├── 📄 初步核实审批表 (ID: 1765425918287774)
│ └── 📄 附件初核方案 (ID: 1765425918483987)
├── 📁 2.谈话审批
│ ├── 📁 谈话通知书
│ │ ├── 📄 谈话通知书第一联 (ID: 1765273963625524)
│ │ ├── 📄 谈话通知书第二联 (ID: 1765273963825806)
│ │ └── 📄 谈话通知书第三联 (ID: 1765273963038891)
│ ├── 📁 走读式谈话审批
│ │ ├── 📄 1请示报告卡 (ID: 1765432134276990)
│ │ ├── 📄 谈话审批表 (ID: 1765425918217504)
│ │ ├── 📄 谈话前安全风险评估表 (ID: 1765425918902422)
│ │ ├── 📄 谈话方案 (ID: 1765425918745496)
│ │ └── 📄 谈话后安全风险评估表 (ID: 1765425919242549)
│ └── 📁 走读式谈话流程
│ ├── 📄 2保密承诺书 (ID: 1765425919729046)
│ ├── 📄 办案人员-办案安全保密承诺书 (ID: 1765425919629084)
│ ├── 📄 点对点交接单 (ID: 1765425919512780)
│ ├── 📄 被谈话人权利义务告知书 (ID: 1765425930059797)
│ ├── 📄 谈话笔录 (ID: 1765425918749247)
│ ├── 📄 谈话询问对象情况摸底调查30问 (ID: 1765425918921697)
│ └── 📄 陪送交接单 (ID: 1765425919389484)
└── 📁 3.初核结论
├── 📄 8-1请示报告卡初核报告结论 (ID: 1765273962631542)
└── 📄 XXX初核情况报告 (ID: 1765425930460962)
```
## 脚本文件
**update_template_hierarchy_final.py** - 更新层级结构的最终版本脚本
- 扫描 template_finish 目录结构
- 使用 file_pathMinIO路径作为唯一标识匹配文件
- 创建/更新目录节点
- 更新文件的 parent_id
- 正确处理同名但不同路径的文件(如"1请示报告卡"在不同目录下)
## 完成时间
2025年12月11日

139
清理重复模板总结.md Normal file
View File

@ -0,0 +1,139 @@
# 清理重复和无效模板总结
## 任务完成情况
✅ **已完成所有任务**
1. ✅ 删除 f_polic_file_config 表中的重复数据
2. ✅ 删除无效的模板数据
3. ✅ 确保文档模板结构和 template_finish/ 文件夹对应
4. ✅ 同步更新测试页面
## 执行结果
### 1. 清理前状态
- **数据库中的模板总数**: 53个
- **唯一模板名称**: 24个
- **重复模板**: 4组共29个重复记录
### 2. 清理操作
- **删除重复模板**: 29个
- **删除关联关系**: 136条
- **标记无效模板**: 6个
### 3. 清理后状态
- **数据库中的模板总数**: 24个
- **唯一模板名称**: 24个
- **启用的模板**: 18个有filePath的
- **禁用的模板**: 6个无效模板
## 清理的重复模板详情
### 1. 请示报告卡
- **保留**: ID 1765425930118581 (1请示报告卡)
- **删除**: ID 1765425917608094 (请示报告卡)
### 2. 保密承诺书
- **保留**: ID 1765425919729046 (2保密承诺书)
- **删除**: ID 1765425919731077 (1保密承诺书)
### 3. 谈话审批表
- **保留**: ID 1765425918217504 (谈话审批表)
- **删除**:
- ID 1765273961492987 (2谈话审批表)
- ID 1765273964441714 (2谈话审批表)
### 4. 其他重复模板(标准化后名称相同)
- **删除**: 26个重复模板包括
- 多个版本的"初步核实审批表"
- 多个版本的"请示报告卡"
- 多个版本的"谈话通知书"
- 多个版本的"谈话笔录"
- 多个版本的"保密承诺书"
- 等等
## 标记的无效模板
以下模板不在 template_finish 文件夹中已标记为无效state=0
1. 2-初核模版 (ID: 1765273961277310)
2. 走读式谈话审批 (ID: 1765273962700431)
3. 走读式谈话流程 (ID: 1765273962716807)
4. 谈话通知书 (ID: 1765273962774249)
5. 8.XXX初核情况报告 (ID: 1765273963158289)
6. 2保密承诺书 (ID: 1765425919729046)
## 最终启用的模板列表18个
1. ✅ 1请示报告卡 (ID: 1765425930118581)
2. ✅ 8-1请示报告卡初核报告结论 (ID: 1765273962631542)
3. ✅ XXX初核情况报告 (ID: 1765425930460962)
4. ✅ 初步核实审批表 (ID: 1765425918287774)
5. ✅ 办案人员-办案安全保密承诺书 (ID: 1765425919629084)
6. ✅ 点对点交接单 (ID: 1765425919512780)
7. ✅ 被谈话人权利义务告知书 (ID: 1765425930059797)
8. ✅ 谈话前安全风险评估表 (ID: 1765425918902422)
9. ✅ 谈话后安全风险评估表 (ID: 1765425919242549)
10. ✅ 谈话审批表 (ID: 1765425918217504)
11. ✅ 谈话方案 (ID: 1765425918745496)
12. ✅ 谈话笔录 (ID: 1765425918749247)
13. ✅ 谈话询问对象情况摸底调查30问 (ID: 1765425918921697)
14. ✅ 谈话通知书第一联 (ID: 1765273963625524)
15. ✅ 谈话通知书第三联 (ID: 1765273963038891)
16. ✅ 谈话通知书第二联 (ID: 1765273963825806)
17. ✅ 附件初核方案 (ID: 1765425918483987)
18. ✅ 陪送交接单 (ID: 1765425919389484)
## 测试页面更新
### 更新内容
1. ✅ 移除了硬编码的默认fileId因为旧的ID已被删除
2. ✅ 改进了文件列表加载逻辑
3. ✅ 自动加载所有可用的模板文件
### 功能说明
- **自动加载**: 页面加载时自动从 `/api/file-configs` 接口获取所有可用的模板
- **手动加载**: 点击"加载全部可用模板"按钮可以重新加载模板列表
- **过滤**: 只显示有filePath的模板确保模板文件已上传到MinIO
## 数据库表更新情况
### f_polic_file_config 表
- **删除**: 29条重复记录
- **更新**: 6条记录标记为无效state=0
- **最终状态**: 24条记录18个启用6个禁用
### f_polic_file_field 表
- **删除**: 136条关联关系随重复模板一起删除
## 脚本文件
### 主要脚本
1. **cleanup_duplicate_templates.py** - 清理脚本
- 扫描 template_finish 文件夹
- 识别重复模板
- 删除重复记录
- 标记无效模板
2. **validate_and_update_templates.py** - 校验和更新脚本
- 重新校验模板和字段关联
- 上传模板到MinIO
- 更新数据库配置
## 注意事项
1. **重复判断标准**: 使用标准化后的模板名称进行判断(去掉括号、数字前缀等)
2. **保留策略**: 保留最新的、启用的模板记录
3. **无效模板**: 不在 template_finish 文件夹中的模板会被标记为无效,但不会删除(保留历史记录)
4. **关联关系**: 删除模板时会自动删除相关的字段关联关系
## 后续建议
1. **定期清理**: 建议定期运行 `cleanup_duplicate_templates.py` 脚本,确保没有重复数据
2. **新增模板**: 新增模板时,确保模板文件放在 `template_finish` 文件夹中,然后运行 `validate_and_update_templates.py`
3. **测试验证**: 每次清理后,建议测试文档生成功能,确保所有模板都能正常工作
## 完成时间
2025年12月11日