修复文档生成异常。修复模板输入字段关联异常。

This commit is contained in:
python 2025-12-11 16:10:48 +08:00
parent d3afba9191
commit 7cbe4b29b7
12 changed files with 2265 additions and 20 deletions

175
app.py
View File

@ -835,6 +835,180 @@ def get_document_by_task():
return error_response(3001, f"文档生成失败: {str(e)}")
@app.route('/template-field-manager')
def template_field_manager():
"""返回模板字段关联管理页面"""
return send_from_directory('static', 'template_field_manager.html')
@app.route('/api/template-field-relations', methods=['GET'])
def get_template_field_relations():
"""
获取所有模板和字段的关联关系
用于模板字段关联管理页面
"""
try:
conn = document_service.get_connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
try:
# 获取所有启用的模板
cursor.execute("""
SELECT id, name, template_code
FROM f_polic_file_config
WHERE tenant_id = %s AND state = 1
ORDER BY name
""", (document_service.tenant_id,))
templates = cursor.fetchall()
# 获取所有启用的输入字段
cursor.execute("""
SELECT id, name, filed_code, field_type
FROM f_polic_field
WHERE tenant_id = %s AND field_type = 1 AND state = 1
ORDER BY name
""", (document_service.tenant_id,))
input_fields = cursor.fetchall()
# 获取所有启用的输出字段
cursor.execute("""
SELECT id, name, filed_code, field_type
FROM f_polic_field
WHERE tenant_id = %s AND field_type = 2 AND state = 1
ORDER BY name
""", (document_service.tenant_id,))
output_fields = cursor.fetchall()
# 获取现有的关联关系
cursor.execute("""
SELECT file_id, filed_id
FROM f_polic_file_field
WHERE tenant_id = %s AND state = 1
""", (document_service.tenant_id,))
relations = cursor.fetchall()
# 构建关联关系映射 (file_id -> list of filed_id)
# 注意JSON不支持set所以转换为list
relation_map = {}
for rel in relations:
file_id = rel['file_id']
filed_id = rel['filed_id']
if file_id not in relation_map:
relation_map[file_id] = []
relation_map[file_id].append(filed_id)
return success_response({
'templates': templates,
'input_fields': input_fields,
'output_fields': output_fields,
'relations': relation_map
})
finally:
cursor.close()
conn.close()
except Exception as e:
return error_response(500, f"获取关联关系失败: {str(e)}")
@app.route('/api/template-field-relations', methods=['POST'])
def save_template_field_relations():
"""
保存模板和字段的关联关系
请求体格式: {
"template_id": 123,
"input_field_ids": [1, 2, 3],
"output_field_ids": [4, 5, 6]
}
"""
try:
data = request.get_json()
if not data:
return error_response(400, "请求参数不能为空")
template_id = data.get('template_id')
input_field_ids = data.get('input_field_ids', [])
output_field_ids = data.get('output_field_ids', [])
if not template_id:
return error_response(400, "template_id参数不能为空")
conn = document_service.get_connection()
cursor = conn.cursor()
try:
# 验证模板是否存在
cursor.execute("""
SELECT id FROM f_polic_file_config
WHERE id = %s AND tenant_id = %s AND state = 1
""", (template_id, document_service.tenant_id))
if not cursor.fetchone():
return error_response(400, f"模板ID {template_id} 不存在或未启用")
# 合并所有字段ID
all_field_ids = set(input_field_ids + output_field_ids)
# 验证字段是否存在
if all_field_ids:
placeholders = ','.join(['%s'] * len(all_field_ids))
cursor.execute(f"""
SELECT id FROM f_polic_field
WHERE id IN ({placeholders}) AND tenant_id = %s AND state = 1
""", list(all_field_ids) + [document_service.tenant_id])
existing_field_ids = {row[0] for row in cursor.fetchall()}
invalid_field_ids = all_field_ids - existing_field_ids
if invalid_field_ids:
return error_response(400, f"字段ID {list(invalid_field_ids)} 不存在或未启用")
# 删除该模板的所有现有关联关系
cursor.execute("""
DELETE FROM f_polic_file_field
WHERE file_id = %s AND tenant_id = %s
""", (template_id, document_service.tenant_id))
# 插入新的关联关系
current_time = datetime.now()
created_by = 655162080928945152 # 默认创建者ID
if all_field_ids:
insert_sql = """
INSERT INTO f_polic_file_field
(tenant_id, file_id, filed_id, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, %s, 1)
"""
for field_id in all_field_ids:
cursor.execute(insert_sql, (
document_service.tenant_id,
template_id,
field_id,
current_time,
created_by,
current_time,
created_by
))
conn.commit()
return success_response({
'template_id': template_id,
'input_field_count': len(input_field_ids),
'output_field_count': len(output_field_ids),
'total_field_count': len(all_field_ids)
}, "保存成功")
except Exception as e:
conn.rollback()
raise e
finally:
cursor.close()
conn.close()
except Exception as e:
return error_response(500, f"保存关联关系失败: {str(e)}")
if __name__ == '__main__':
# 确保static目录存在
os.makedirs('static', exist_ok=True)
@ -844,6 +1018,7 @@ if __name__ == '__main__':
print(f"服务启动在 http://localhost:{port}")
print(f"测试页面: http://localhost:{port}/")
print(f"模板字段管理页面: http://localhost:{port}/template-field-manager")
print(f"Swagger API文档: http://localhost:{port}/api-docs")
app.run(host='0.0.0.0', port=port, debug=debug)

View File

@ -0,0 +1,496 @@
"""
全面检查 f_polic_file_field 表的关联关系
重点检查输入字段field_type=1和输出字段field_type=2与模板的关联情况
"""
import pymysql
import os
import sys
from typing import Dict, List, Tuple
from collections import defaultdict
# 设置输出编码为UTF-8
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
# 数据库连接配置
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'
}
TENANT_ID = 615873064429507639
def check_all_templates_field_relations(conn) -> Dict:
"""检查所有模板的字段关联情况"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
print("\n" + "="*80)
print("1. 检查所有模板的字段关联情况")
print("="*80)
# 获取所有启用的模板
cursor.execute("""
SELECT id, name, template_code
FROM f_polic_file_config
WHERE tenant_id = %s AND state = 1
ORDER BY name
""", (TENANT_ID,))
templates = cursor.fetchall()
# 获取每个模板关联的字段(按类型分组)
cursor.execute("""
SELECT
fc.id AS template_id,
fc.name AS template_name,
f.id AS field_id,
f.name AS field_name,
f.filed_code AS field_code,
f.field_type
FROM f_polic_file_config fc
INNER JOIN f_polic_file_field fff ON fc.id = fff.file_id AND fc.tenant_id = fff.tenant_id
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
WHERE fc.tenant_id = %s
AND fc.state = 1
AND fff.state = 1
AND f.state = 1
ORDER BY fc.name, f.field_type, f.name
""", (TENANT_ID,))
relations = cursor.fetchall()
# 按模板分组统计
template_stats = {}
for template in templates:
template_stats[template['id']] = {
'template_id': template['id'],
'template_name': template['name'],
'template_code': template.get('template_code'),
'input_fields': [],
'output_fields': [],
'input_count': 0,
'output_count': 0
}
# 填充字段信息
for rel in relations:
template_id = rel['template_id']
if template_id in template_stats:
field_info = {
'field_id': rel['field_id'],
'field_name': rel['field_name'],
'field_code': rel['field_code'],
'field_type': rel['field_type']
}
if rel['field_type'] == 1:
template_stats[template_id]['input_fields'].append(field_info)
template_stats[template_id]['input_count'] += 1
elif rel['field_type'] == 2:
template_stats[template_id]['output_fields'].append(field_info)
template_stats[template_id]['output_count'] += 1
# 打印统计信息
print(f"\n共找到 {len(templates)} 个启用的模板\n")
templates_without_input = []
templates_without_output = []
templates_without_any = []
for template_id, stats in template_stats.items():
status = []
if stats['input_count'] == 0:
status.append("缺少输入字段")
templates_without_input.append(stats)
if stats['output_count'] == 0:
status.append("缺少输出字段")
templates_without_output.append(stats)
if stats['input_count'] == 0 and stats['output_count'] == 0:
templates_without_any.append(stats)
status_str = " | ".join(status) if status else "[OK] 正常"
print(f" {stats['template_name']} (ID: {stats['template_id']})")
print(f" 输入字段: {stats['input_count']} 个 | 输出字段: {stats['output_count']} 个 | {status_str}")
return {
'template_stats': template_stats,
'templates_without_input': templates_without_input,
'templates_without_output': templates_without_output,
'templates_without_any': templates_without_any
}
def check_input_field_relations_detail(conn) -> Dict:
"""详细检查输入字段的关联情况"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
print("\n" + "="*80)
print("2. 详细检查输入字段的关联情况")
print("="*80)
# 获取所有启用的输入字段
cursor.execute("""
SELECT id, name, filed_code
FROM f_polic_field
WHERE tenant_id = %s AND field_type = 1 AND state = 1
ORDER BY name
""", (TENANT_ID,))
input_fields = cursor.fetchall()
# 获取每个输入字段关联的模板
cursor.execute("""
SELECT
f.id AS field_id,
f.name AS field_name,
f.filed_code AS field_code,
fc.id AS template_id,
fc.name AS template_name
FROM f_polic_field f
INNER JOIN f_polic_file_field fff ON f.id = fff.filed_id AND f.tenant_id = fff.tenant_id
INNER JOIN f_polic_file_config fc ON fff.file_id = fc.id AND fff.tenant_id = fc.tenant_id
WHERE f.tenant_id = %s
AND f.field_type = 1
AND f.state = 1
AND fff.state = 1
AND fc.state = 1
ORDER BY f.name, fc.name
""", (TENANT_ID,))
input_field_relations = cursor.fetchall()
# 按字段分组
field_template_map = defaultdict(list)
for rel in input_field_relations:
field_template_map[rel['field_id']].append({
'template_id': rel['template_id'],
'template_name': rel['template_name']
})
print(f"\n共找到 {len(input_fields)} 个启用的输入字段\n")
fields_without_relations = []
fields_with_relations = []
for field in input_fields:
field_id = field['id']
templates = field_template_map.get(field_id, [])
if not templates:
fields_without_relations.append(field)
print(f" [WARN] {field['name']} ({field['filed_code']}) - 未关联任何模板")
else:
fields_with_relations.append({
'field': field,
'templates': templates
})
print(f" [OK] {field['name']} ({field['filed_code']}) - 关联了 {len(templates)} 个模板:")
for template in templates:
print(f" - {template['template_name']} (ID: {template['template_id']})")
return {
'input_fields': input_fields,
'fields_without_relations': fields_without_relations,
'fields_with_relations': fields_with_relations
}
def check_output_field_relations_detail(conn) -> Dict:
"""详细检查输出字段的关联情况"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
print("\n" + "="*80)
print("3. 详细检查输出字段的关联情况")
print("="*80)
# 获取所有启用的输出字段
cursor.execute("""
SELECT id, name, filed_code
FROM f_polic_field
WHERE tenant_id = %s AND field_type = 2 AND state = 1
ORDER BY name
""", (TENANT_ID,))
output_fields = cursor.fetchall()
# 获取每个输出字段关联的模板
cursor.execute("""
SELECT
f.id AS field_id,
f.name AS field_name,
f.filed_code AS field_code,
fc.id AS template_id,
fc.name AS template_name
FROM f_polic_field f
INNER JOIN f_polic_file_field fff ON f.id = fff.filed_id AND f.tenant_id = fff.tenant_id
INNER JOIN f_polic_file_config fc ON fff.file_id = fc.id AND fff.tenant_id = fc.tenant_id
WHERE f.tenant_id = %s
AND f.field_type = 2
AND f.state = 1
AND fff.state = 1
AND fc.state = 1
ORDER BY f.name, fc.name
""", (TENANT_ID,))
output_field_relations = cursor.fetchall()
# 按字段分组
field_template_map = defaultdict(list)
for rel in output_field_relations:
field_template_map[rel['field_id']].append({
'template_id': rel['template_id'],
'template_name': rel['template_name']
})
print(f"\n共找到 {len(output_fields)} 个启用的输出字段\n")
fields_without_relations = []
fields_with_relations = []
for field in output_fields:
field_id = field['id']
templates = field_template_map.get(field_id, [])
if not templates:
fields_without_relations.append(field)
print(f" [WARN] {field['name']} ({field['filed_code']}) - 未关联任何模板")
else:
fields_with_relations.append({
'field': field,
'templates': templates
})
if len(templates) <= 5:
print(f" [OK] {field['name']} ({field['filed_code']}) - 关联了 {len(templates)} 个模板:")
for template in templates:
print(f" - {template['template_name']} (ID: {template['template_id']})")
else:
print(f" [OK] {field['name']} ({field['filed_code']}) - 关联了 {len(templates)} 个模板")
for template in templates[:3]:
print(f" - {template['template_name']} (ID: {template['template_id']})")
print(f" ... 还有 {len(templates) - 3} 个模板")
return {
'output_fields': output_fields,
'fields_without_relations': fields_without_relations,
'fields_with_relations': fields_with_relations
}
def check_invalid_relations(conn) -> Dict:
"""检查无效的关联关系"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
print("\n" + "="*80)
print("4. 检查无效的关联关系")
print("="*80)
# 检查关联到不存在的 file_id
cursor.execute("""
SELECT fff.id, fff.file_id, fff.filed_id, fff.tenant_id
FROM f_polic_file_field fff
LEFT JOIN f_polic_file_config fc ON fff.file_id = fc.id AND fff.tenant_id = fc.tenant_id
WHERE fff.tenant_id = %s AND fc.id IS NULL
""", (TENANT_ID,))
invalid_file_relations = cursor.fetchall()
# 检查关联到不存在的 filed_id
cursor.execute("""
SELECT fff.id, fff.file_id, fff.filed_id, fff.tenant_id
FROM f_polic_file_field fff
LEFT JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
WHERE fff.tenant_id = %s AND f.id IS NULL
""", (TENANT_ID,))
invalid_field_relations = cursor.fetchall()
print(f"\n关联到不存在的 file_id: {len(invalid_file_relations)}")
if invalid_file_relations:
print(" 详情:")
for rel in invalid_file_relations[:10]:
print(f" - 关联ID: {rel['id']}, file_id: {rel['file_id']}, filed_id: {rel['filed_id']}")
if len(invalid_file_relations) > 10:
print(f" ... 还有 {len(invalid_file_relations) - 10}")
else:
print(" [OK] 没有无效的 file_id 关联")
print(f"\n关联到不存在的 filed_id: {len(invalid_field_relations)}")
if invalid_field_relations:
print(" 详情:")
for rel in invalid_field_relations[:10]:
print(f" - 关联ID: {rel['id']}, file_id: {rel['file_id']}, filed_id: {rel['filed_id']}")
if len(invalid_field_relations) > 10:
print(f" ... 还有 {len(invalid_field_relations) - 10}")
else:
print(" [OK] 没有无效的 filed_id 关联")
return {
'invalid_file_relations': invalid_file_relations,
'invalid_field_relations': invalid_field_relations
}
def get_summary_statistics(conn) -> Dict:
"""获取汇总统计信息"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
print("\n" + "="*80)
print("5. 汇总统计信息")
print("="*80)
# 总关联数
cursor.execute("""
SELECT COUNT(*) as total
FROM f_polic_file_field
WHERE tenant_id = %s AND state = 1
""", (TENANT_ID,))
total_relations = cursor.fetchone()['total']
# 输入字段关联数
cursor.execute("""
SELECT COUNT(*) as total
FROM f_polic_file_field fff
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
WHERE fff.tenant_id = %s
AND fff.state = 1
AND f.state = 1
AND f.field_type = 1
""", (TENANT_ID,))
input_relations = cursor.fetchone()['total']
# 输出字段关联数
cursor.execute("""
SELECT COUNT(*) as total
FROM f_polic_file_field fff
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
WHERE fff.tenant_id = %s
AND fff.state = 1
AND f.state = 1
AND f.field_type = 2
""", (TENANT_ID,))
output_relations = cursor.fetchone()['total']
# 关联的模板数
cursor.execute("""
SELECT COUNT(DISTINCT file_id) as total
FROM f_polic_file_field
WHERE tenant_id = %s AND state = 1
""", (TENANT_ID,))
related_templates = cursor.fetchone()['total']
# 关联的输入字段数
cursor.execute("""
SELECT COUNT(DISTINCT fff.filed_id) as total
FROM f_polic_file_field fff
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
WHERE fff.tenant_id = %s
AND fff.state = 1
AND f.state = 1
AND f.field_type = 1
""", (TENANT_ID,))
related_input_fields = cursor.fetchone()['total']
# 关联的输出字段数
cursor.execute("""
SELECT COUNT(DISTINCT fff.filed_id) as total
FROM f_polic_file_field fff
INNER JOIN f_polic_field f ON fff.filed_id = f.id AND fff.tenant_id = f.tenant_id
WHERE fff.tenant_id = %s
AND fff.state = 1
AND f.state = 1
AND f.field_type = 2
""", (TENANT_ID,))
related_output_fields = cursor.fetchone()['total']
print(f"\n总关联数: {total_relations}")
print(f" 输入字段关联数: {input_relations}")
print(f" 输出字段关联数: {output_relations}")
print(f"\n关联的模板数: {related_templates}")
print(f"关联的输入字段数: {related_input_fields}")
print(f"关联的输出字段数: {related_output_fields}")
return {
'total_relations': total_relations,
'input_relations': input_relations,
'output_relations': output_relations,
'related_templates': related_templates,
'related_input_fields': related_input_fields,
'related_output_fields': related_output_fields
}
def main():
"""主函数"""
print("="*80)
print("全面检查 f_polic_file_field 表的关联关系")
print("="*80)
try:
conn = pymysql.connect(**DB_CONFIG)
print("[OK] 数据库连接成功\n")
except Exception as e:
print(f"[ERROR] 数据库连接失败: {e}")
return
try:
# 1. 检查所有模板的字段关联情况
template_result = check_all_templates_field_relations(conn)
# 2. 详细检查输入字段的关联情况
input_result = check_input_field_relations_detail(conn)
# 3. 详细检查输出字段的关联情况
output_result = check_output_field_relations_detail(conn)
# 4. 检查无效的关联关系
invalid_result = check_invalid_relations(conn)
# 5. 获取汇总统计信息
stats = get_summary_statistics(conn)
# 总结
print("\n" + "="*80)
print("检查总结")
print("="*80)
issues = []
if len(template_result['templates_without_input']) > 0:
issues.append(f"[WARN] {len(template_result['templates_without_input'])} 个模板缺少输入字段关联")
if len(template_result['templates_without_output']) > 0:
issues.append(f"[WARN] {len(template_result['templates_without_output'])} 个模板缺少输出字段关联")
if len(template_result['templates_without_any']) > 0:
issues.append(f"[WARN] {len(template_result['templates_without_any'])} 个模板没有任何字段关联")
if len(input_result['fields_without_relations']) > 0:
issues.append(f"[WARN] {len(input_result['fields_without_relations'])} 个输入字段未关联任何模板")
if len(output_result['fields_without_relations']) > 0:
issues.append(f"[WARN] {len(output_result['fields_without_relations'])} 个输出字段未关联任何模板")
if len(invalid_result['invalid_file_relations']) > 0:
issues.append(f"[ERROR] {len(invalid_result['invalid_file_relations'])} 条无效的 file_id 关联")
if len(invalid_result['invalid_field_relations']) > 0:
issues.append(f"[ERROR] {len(invalid_result['invalid_field_relations'])} 条无效的 filed_id 关联")
if issues:
print("\n发现以下问题:\n")
for issue in issues:
print(f" {issue}")
else:
print("\n[OK] 未发现明显问题")
print("\n" + "="*80)
except Exception as e:
print(f"\n[ERROR] 检查过程中发生错误: {e}")
import traceback
traceback.print_exc()
finally:
conn.close()
print("\n数据库连接已关闭")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,234 @@
"""
将所有模板与两个输入字段关联
- 线索信息 (clue_info)
- 被核查人员工作基本情况线索 (target_basic_info_clue)
"""
import pymysql
import os
import sys
import time
import random
from datetime import datetime
# 设置输出编码为UTF-8
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
# 数据库连接配置
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'
}
TENANT_ID = 615873064429507639
CREATED_BY = 655162080928945152
UPDATED_BY = 655162080928945152
def generate_id():
"""生成ID"""
timestamp = int(time.time() * 1000)
random_part = random.randint(100000, 999999)
return timestamp * 1000 + random_part
def get_input_field_ids(conn):
"""获取两个输入字段的ID"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
field_codes = ['clue_info', 'target_basic_info_clue']
cursor.execute("""
SELECT id, name, filed_code
FROM f_polic_field
WHERE tenant_id = %s
AND filed_code IN (%s, %s)
AND field_type = 1
AND state = 1
""", (TENANT_ID, field_codes[0], field_codes[1]))
fields = cursor.fetchall()
field_map = {field['filed_code']: field for field in fields}
result = {}
for code in field_codes:
if code in field_map:
result[code] = field_map[code]
else:
print(f"[WARN] 未找到字段: {code}")
return result
def get_all_templates(conn):
"""获取所有启用的模板"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("""
SELECT id, name
FROM f_polic_file_config
WHERE tenant_id = %s AND state = 1
ORDER BY name
""", (TENANT_ID,))
return cursor.fetchall()
def get_existing_relations(conn, template_id, field_ids):
"""获取模板与字段的现有关联关系"""
cursor = conn.cursor()
if not field_ids:
return set()
placeholders = ','.join(['%s'] * len(field_ids))
cursor.execute(f"""
SELECT filed_id
FROM f_polic_file_field
WHERE tenant_id = %s
AND file_id = %s
AND filed_id IN ({placeholders})
AND state = 1
""", [TENANT_ID, template_id] + list(field_ids))
return {row[0] for row in cursor.fetchall()}
def create_relation(conn, template_id, field_id):
"""创建模板与字段的关联关系"""
cursor = conn.cursor()
current_time = datetime.now()
# 检查是否已存在
cursor.execute("""
SELECT id FROM f_polic_file_field
WHERE tenant_id = %s
AND file_id = %s
AND filed_id = %s
""", (TENANT_ID, template_id, field_id))
if cursor.fetchone():
return False # 已存在,不需要创建
# 创建新关联
relation_id = generate_id()
cursor.execute("""
INSERT INTO f_polic_file_field
(id, tenant_id, file_id, filed_id, created_time, created_by, updated_time, updated_by, state)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 1)
""", (
relation_id,
TENANT_ID,
template_id,
field_id,
current_time,
CREATED_BY,
current_time,
UPDATED_BY
))
return True # 创建成功
def main():
"""主函数"""
print("="*80)
print("将所有模板与输入字段关联")
print("="*80)
try:
conn = pymysql.connect(**DB_CONFIG)
print("[OK] 数据库连接成功\n")
except Exception as e:
print(f"[ERROR] 数据库连接失败: {e}")
return
try:
# 1. 获取输入字段ID
print("1. 获取输入字段ID...")
input_fields = get_input_field_ids(conn)
if len(input_fields) != 2:
print(f"[ERROR] 未找到所有输入字段,只找到: {list(input_fields.keys())}")
return
field_ids = [field['id'] for field in input_fields.values()]
print(f" 找到字段:")
for code, field in input_fields.items():
print(f" - {field['name']} ({code}): ID={field['id']}")
print()
# 2. 获取所有模板
print("2. 获取所有启用的模板...")
templates = get_all_templates(conn)
print(f" 找到 {len(templates)} 个模板\n")
# 3. 为每个模板创建关联关系
print("3. 创建关联关系...")
created_count = 0
existing_count = 0
error_count = 0
for template in templates:
template_id = template['id']
template_name = template['name']
# 获取现有关联
existing_relations = get_existing_relations(conn, template_id, field_ids)
# 为每个字段创建关联(如果不存在)
for field_code, field_info in input_fields.items():
field_id = field_info['id']
if field_id in existing_relations:
existing_count += 1
continue
try:
if create_relation(conn, template_id, field_id):
created_count += 1
print(f" [OK] {template_name} <- {field_info['name']} ({field_code})")
else:
existing_count += 1
except Exception as e:
error_count += 1
print(f" [ERROR] {template_name} <- {field_info['name']}: {e}")
# 提交事务
conn.commit()
# 4. 统计结果
print("\n" + "="*80)
print("执行结果")
print("="*80)
print(f"模板总数: {len(templates)}")
print(f"字段总数: {len(input_fields)}")
print(f"预期关联数: {len(templates) * len(input_fields)}")
print(f"新创建关联: {created_count}")
print(f"已存在关联: {existing_count}")
print(f"错误数量: {error_count}")
print(f"实际关联数: {created_count + existing_count}")
if error_count == 0:
print("\n[OK] 所有关联关系已成功创建或已存在")
else:
print(f"\n[WARN] 有 {error_count} 个关联关系创建失败")
except Exception as e:
conn.rollback()
print(f"\n[ERROR] 执行过程中发生错误: {e}")
import traceback
traceback.print_exc()
finally:
conn.close()
print("\n数据库连接已关闭")
if __name__ == '__main__':
main()

View File

@ -154,16 +154,40 @@ class DocumentService:
# 扫描表格中的占位符
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
if hasattr(cell, 'paragraphs'):
for paragraph in cell.paragraphs:
text = paragraph.text
matches = placeholder_pattern.findall(text)
for match in matches:
field_code = match.strip()
if field_code:
all_placeholders_in_template.add(field_code)
try:
if not table.rows:
continue
for row in table.rows:
try:
# 安全地访问 row.cells避免 docx 库在处理异常表格结构时的 bug
if not hasattr(row, 'cells'):
continue
# 使用 try-except 包裹,防止 IndexError
try:
cells = row.cells
except (IndexError, AttributeError) as e:
print(f"[WARN] 无法访问表格行的单元格,跳过该行: {str(e)}")
continue
for cell in cells:
try:
if hasattr(cell, 'paragraphs'):
for paragraph in cell.paragraphs:
text = paragraph.text
matches = placeholder_pattern.findall(text)
for match in matches:
field_code = match.strip()
if field_code:
all_placeholders_in_template.add(field_code)
except Exception as e:
print(f"[WARN] 处理表格单元格时出错,跳过: {str(e)}")
continue
except Exception as e:
print(f"[WARN] 处理表格行时出错,跳过: {str(e)}")
continue
except Exception as e:
print(f"[WARN] 处理表格时出错,跳过该表格: {str(e)}")
continue
print(f"[DEBUG] 模板中发现 {len(all_placeholders_in_template)} 个不同的占位符: {sorted(all_placeholders_in_template)}")
@ -379,16 +403,40 @@ class DocumentService:
# 检查表格中的占位符
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
if hasattr(cell, 'paragraphs'):
for paragraph in cell.paragraphs:
text = paragraph.text
matches = placeholder_pattern.findall(text)
for match in matches:
field_code = match.strip()
if field_code:
remaining_placeholders.add(field_code)
try:
if not table.rows:
continue
for row in table.rows:
try:
# 安全地访问 row.cells避免 docx 库在处理异常表格结构时的 bug
if not hasattr(row, 'cells'):
continue
# 使用 try-except 包裹,防止 IndexError
try:
cells = row.cells
except (IndexError, AttributeError) as e:
print(f"[WARN] 无法访问表格行的单元格,跳过该行: {str(e)}")
continue
for cell in cells:
try:
if hasattr(cell, 'paragraphs'):
for paragraph in cell.paragraphs:
text = paragraph.text
matches = placeholder_pattern.findall(text)
for match in matches:
field_code = match.strip()
if field_code:
remaining_placeholders.add(field_code)
except Exception as e:
print(f"[WARN] 处理表格单元格时出错,跳过: {str(e)}")
continue
except Exception as e:
print(f"[WARN] 处理表格行时出错,跳过: {str(e)}")
continue
except Exception as e:
print(f"[WARN] 处理表格时出错,跳过该表格: {str(e)}")
continue
# 输出统计信息
print(f"[DEBUG] 占位符替换统计:")

View File

@ -0,0 +1,569 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模板字段关联管理</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 30px;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 24px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 14px;
}
.template-selector {
margin-bottom: 30px;
}
.template-selector label {
display: block;
margin-bottom: 10px;
font-weight: 500;
color: #333;
}
.template-selector select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background: white;
}
.fields-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.field-section {
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 20px;
background: #fafafa;
}
.field-section h2 {
font-size: 18px;
margin-bottom: 15px;
color: #333;
padding-bottom: 10px;
border-bottom: 2px solid #4CAF50;
}
.field-section.output h2 {
border-bottom-color: #2196F3;
}
.field-count {
font-size: 12px;
color: #999;
font-weight: normal;
margin-left: 10px;
}
.field-list {
max-height: 500px;
overflow-y: auto;
padding: 10px 0;
}
.field-item {
display: flex;
align-items: center;
padding: 10px;
margin-bottom: 8px;
background: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.field-item:hover {
background: #f0f0f0;
border-color: #4CAF50;
}
.field-item.checked {
background: #e8f5e9;
border-color: #4CAF50;
}
.field-item input[type="checkbox"] {
margin-right: 10px;
width: 18px;
height: 18px;
cursor: pointer;
}
.field-info {
flex: 1;
}
.field-name {
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.field-code {
font-size: 12px;
color: #999;
font-family: 'Courier New', monospace;
}
.actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 20px;
border-top: 1px solid #e0e0e0;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #4CAF50;
color: white;
}
.btn-primary:hover {
background: #45a049;
}
.btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-secondary {
background: #f5f5f5;
color: #333;
border: 1px solid #ddd;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.message {
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
display: none;
}
.message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.message.show {
display: block;
}
.loading {
text-align: center;
padding: 40px;
color: #999;
}
.search-box {
margin-bottom: 15px;
}
.search-box input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.empty-state {
text-align: center;
padding: 40px;
color: #999;
}
.stats {
display: flex;
gap: 20px;
margin-bottom: 20px;
padding: 15px;
background: #f0f7ff;
border-radius: 4px;
}
.stat-item {
flex: 1;
text-align: center;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #2196F3;
}
.stat-label {
font-size: 12px;
color: #666;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>模板字段关联管理</h1>
<p class="subtitle">维护模板与输入字段、输出字段的关联关系</p>
<div id="message" class="message"></div>
<div class="template-selector">
<label for="templateSelect">选择模板:</label>
<select id="templateSelect">
<option value="">请选择模板...</option>
</select>
</div>
<div id="stats" class="stats" style="display: none;">
<div class="stat-item">
<div class="stat-value" id="inputCount">0</div>
<div class="stat-label">已选输入字段</div>
</div>
<div class="stat-item">
<div class="stat-value" id="outputCount">0</div>
<div class="stat-label">已选输出字段</div>
</div>
<div class="stat-item">
<div class="stat-value" id="totalCount">0</div>
<div class="stat-label">总字段数</div>
</div>
</div>
<div class="fields-container" id="fieldsContainer" style="display: none;">
<div class="field-section input">
<h2>
输入字段
<span class="field-count" id="inputFieldCount">(0)</span>
</h2>
<div class="search-box">
<input type="text" id="inputSearch" placeholder="搜索输入字段...">
</div>
<div class="field-list" id="inputFieldsList"></div>
</div>
<div class="field-section output">
<h2>
输出字段
<span class="field-count" id="outputFieldCount">(0)</span>
</h2>
<div class="search-box">
<input type="text" id="outputSearch" placeholder="搜索输出字段...">
</div>
<div class="field-list" id="outputFieldsList"></div>
</div>
</div>
<div class="actions" id="actions" style="display: none;">
<button class="btn btn-secondary" onclick="resetSelection()">重置</button>
<button class="btn btn-primary" onclick="saveRelations()">保存关联关系</button>
</div>
<div id="loading" class="loading">加载中...</div>
</div>
<script>
let templates = [];
let inputFields = [];
let outputFields = [];
let relations = {};
let currentTemplateId = null;
let selectedInputFields = new Set();
let selectedOutputFields = new Set();
// 页面加载时初始化
window.onload = function() {
loadData();
setupSearch();
};
// 加载数据
async function loadData() {
try {
const response = await fetch('/api/template-field-relations');
const result = await response.json();
if (result.isSuccess) {
templates = result.data.templates || [];
inputFields = result.data.input_fields || [];
outputFields = result.data.output_fields || [];
relations = result.data.relations || {};
populateTemplateSelect();
document.getElementById('loading').style.display = 'none';
} else {
showMessage('加载数据失败: ' + result.errorMsg, 'error');
document.getElementById('loading').style.display = 'none';
}
} catch (error) {
showMessage('加载数据失败: ' + error.message, 'error');
document.getElementById('loading').style.display = 'none';
}
}
// 填充模板选择框
function populateTemplateSelect() {
const select = document.getElementById('templateSelect');
select.innerHTML = '<option value="">请选择模板...</option>';
templates.forEach(template => {
const option = document.createElement('option');
option.value = template.id;
option.textContent = template.name;
select.appendChild(option);
});
select.onchange = function() {
const templateId = parseInt(this.value);
if (templateId) {
loadTemplateFields(templateId);
} else {
hideFields();
}
};
}
// 加载模板字段
function loadTemplateFields(templateId) {
currentTemplateId = templateId;
const template = templates.find(t => t.id === templateId);
// 获取该模板的关联字段
const relatedFieldIds = new Set(relations[templateId] || []);
selectedInputFields = new Set();
selectedOutputFields = new Set();
inputFields.forEach(field => {
if (relatedFieldIds.has(field.id)) {
selectedInputFields.add(field.id);
}
});
outputFields.forEach(field => {
if (relatedFieldIds.has(field.id)) {
selectedOutputFields.add(field.id);
}
});
renderFields();
updateStats();
document.getElementById('fieldsContainer').style.display = 'grid';
document.getElementById('actions').style.display = 'flex';
document.getElementById('stats').style.display = 'flex';
}
// 渲染字段列表
function renderFields() {
renderFieldList('inputFieldsList', inputFields, selectedInputFields, 'input');
renderFieldList('outputFieldsList', outputFields, selectedOutputFields, 'output');
document.getElementById('inputFieldCount').textContent = `(${inputFields.length})`;
document.getElementById('outputFieldCount').textContent = `(${outputFields.length})`;
}
// 渲染单个字段列表
function renderFieldList(containerId, fields, selectedSet, type) {
const container = document.getElementById(containerId);
const searchTerm = type === 'input'
? document.getElementById('inputSearch').value.toLowerCase()
: document.getElementById('outputSearch').value.toLowerCase();
const filteredFields = fields.filter(field => {
return field.name.toLowerCase().includes(searchTerm) ||
field.filed_code.toLowerCase().includes(searchTerm);
});
if (filteredFields.length === 0) {
container.innerHTML = '<div class="empty-state">没有找到匹配的字段</div>';
return;
}
container.innerHTML = filteredFields.map(field => {
const isChecked = selectedSet.has(field.id);
return `
<div class="field-item ${isChecked ? 'checked' : ''}" onclick="toggleField(${field.id}, '${type}')">
<input type="checkbox" ${isChecked ? 'checked' : ''}
onclick="event.stopPropagation(); toggleField(${field.id}, '${type}')">
<div class="field-info">
<div class="field-name">${escapeHtml(field.name)}</div>
<div class="field-code">${escapeHtml(field.filed_code)}</div>
</div>
</div>
`;
}).join('');
}
// 切换字段选择
function toggleField(fieldId, type) {
if (type === 'input') {
if (selectedInputFields.has(fieldId)) {
selectedInputFields.delete(fieldId);
} else {
selectedInputFields.add(fieldId);
}
renderFieldList('inputFieldsList', inputFields, selectedInputFields, 'input');
} else {
if (selectedOutputFields.has(fieldId)) {
selectedOutputFields.delete(fieldId);
} else {
selectedOutputFields.add(fieldId);
}
renderFieldList('outputFieldsList', outputFields, selectedOutputFields, 'output');
}
updateStats();
}
// 更新统计信息
function updateStats() {
document.getElementById('inputCount').textContent = selectedInputFields.size;
document.getElementById('outputCount').textContent = selectedOutputFields.size;
document.getElementById('totalCount').textContent = selectedInputFields.size + selectedOutputFields.size;
}
// 设置搜索功能
function setupSearch() {
document.getElementById('inputSearch').oninput = function() {
renderFieldList('inputFieldsList', inputFields, selectedInputFields, 'input');
};
document.getElementById('outputSearch').oninput = function() {
renderFieldList('outputFieldsList', outputFields, selectedOutputFields, 'output');
};
}
// 重置选择
function resetSelection() {
if (confirm('确定要重置当前选择吗?')) {
selectedInputFields.clear();
selectedOutputFields.clear();
renderFields();
updateStats();
}
}
// 保存关联关系
async function saveRelations() {
if (!currentTemplateId) {
showMessage('请先选择模板', 'error');
return;
}
const btn = event.target;
btn.disabled = true;
btn.textContent = '保存中...';
try {
const response = await fetch('/api/template-field-relations', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: currentTemplateId,
input_field_ids: Array.from(selectedInputFields),
output_field_ids: Array.from(selectedOutputFields)
})
});
const result = await response.json();
if (result.isSuccess) {
showMessage('保存成功!', 'success');
// 更新本地关联关系
relations[currentTemplateId] = [
...selectedInputFields,
...selectedOutputFields
];
} else {
showMessage('保存失败: ' + result.errorMsg, 'error');
}
} catch (error) {
showMessage('保存失败: ' + error.message, 'error');
} finally {
btn.disabled = false;
btn.textContent = '保存关联关系';
}
}
// 显示消息
function showMessage(message, type) {
const msgDiv = document.getElementById('message');
msgDiv.textContent = message;
msgDiv.className = 'message ' + type + ' show';
setTimeout(() => {
msgDiv.classList.remove('show');
}, 3000);
}
// 隐藏字段区域
function hideFields() {
document.getElementById('fieldsContainer').style.display = 'none';
document.getElementById('actions').style.display = 'none';
document.getElementById('stats').style.display = 'none';
}
// HTML转义
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
</body>
</html>

403
update_two_templates.py Normal file
View File

@ -0,0 +1,403 @@
"""
更新两个模板文件的信息并上传到MinIO
- 8.XXX初核情况报告.docx
- 8-1请示报告卡初核报告结论 .docx
"""
import os
import re
import json
import sys
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
# 设置输出编码为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_FILES = [
'template_finish/2-初核模版/3.初核结论/8.XXX初核情况报告.docx',
'template_finish/2-初核模版/3.初核结论/8-1请示报告卡初核报告结论 .docx'
]
# 模板名称映射(用于查找数据库中的记录)
TEMPLATE_NAME_MAP = {
'8.XXX初核情况报告.docx': ['8.XXX初核情况报告', 'XXX初核情况报告'],
'8-1请示报告卡初核报告结论 .docx': ['8-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:
cleaned = match.strip()
# 过滤掉不完整的占位符(包含 { 或 } 的)
if cleaned and '{' not in cleaned and '}' not in cleaned:
placeholders.add(cleaned)
# 从表格中提取占位符
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:
cleaned = match.strip()
# 过滤掉不完整的占位符(包含 { 或 } 的)
if cleaned and '{' not in cleaned and '}' not in cleaned:
placeholders.add(cleaned)
except Exception as e:
print(f" 错误: 读取文件失败 - {str(e)}")
return []
return sorted(list(placeholders))
def normalize_template_name(file_name: str) -> str:
"""
标准化模板名称去掉扩展名括号内容数字前缀等
Args:
file_name: 文件名 "8.XXX初核情况报告.docx"
Returns:
标准化后的名称 "XXX初核情况报告"
"""
# 去掉扩展名
name = Path(file_name).stem
# 去掉括号内容
name = re.sub(r'[(].*?[)]', '', name)
name = name.strip()
# 去掉数字前缀和点号
name = re.sub(r'^\d+[\.\-]?\s*', '', name)
name = name.strip()
return name
def upload_to_minio(client: Minio, file_path: str, template_name: str) -> str:
"""上传文件到MinIO"""
try:
now = datetime.now()
object_name = f'{TENANT_ID}/TEMPLATE/{now.year}/{now.month:02d}/{template_name}'
client.fput_object(
BUCKET_NAME,
object_name,
file_path,
content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)
return f"/{object_name}"
except Exception as e:
raise Exception(f"上传到MinIO失败: {str(e)}")
def find_template_by_names(conn, possible_names: List[str]) -> Optional[Dict]:
"""根据可能的模板名称查找数据库中的模板"""
cursor = conn.cursor(pymysql.cursors.DictCursor)
try:
# 尝试每个可能的名称
for name in possible_names:
sql = """
SELECT id, name, file_path, parent_id, input_data
FROM f_polic_file_config
WHERE tenant_id = %s AND name = %s
"""
cursor.execute(sql, (TENANT_ID, name))
result = cursor.fetchone()
if result:
return result
return None
finally:
cursor.close()
def get_template_code_from_input_data(input_data: Optional[str]) -> str:
"""从input_data中提取template_code如果没有则生成一个"""
if input_data:
try:
data = json.loads(input_data)
return data.get('template_code', '')
except:
pass
return ''
def update_template(conn, template_file_path: str, template_info: Dict, minio_path: str):
"""
更新模板配置
Args:
conn: 数据库连接
template_file_path: 模板文件路径
template_info: 模板信息包含占位符等
minio_path: MinIO中的文件路径
"""
cursor = conn.cursor()
try:
file_name = Path(template_file_path).name
possible_names = TEMPLATE_NAME_MAP.get(file_name, [normalize_template_name(file_name)])
# 查找现有记录
existing_template = find_template_by_names(conn, possible_names)
if not existing_template:
print(f" [WARN] 未找到数据库记录,将创建新记录")
template_id = generate_id()
template_name = possible_names[0] # 使用第一个名称
# 生成template_code
template_code = get_template_code_from_input_data(None)
if not template_code:
# 根据文件名生成template_code
if 'XXX初核情况报告' in file_name:
template_code = 'INVESTIGATION_REPORT'
elif '请示报告卡' in file_name and '初核报告结论' in file_name:
template_code = 'REPORT_CARD_CONCLUSION'
else:
template_code = f'TEMPLATE_{template_id % 1000000}'
# 准备input_data
input_data = json.dumps({
'template_code': template_code,
'business_type': 'INVESTIGATION',
'placeholders': template_info['placeholders']
}, ensure_ascii=False)
# 创建新记录
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,
None, # parent_id 需要根据实际情况设置
template_name,
input_data,
minio_path,
CREATED_BY,
UPDATED_BY,
1 # state: 1表示启用
))
print(f" [OK] 创建模板配置: {template_name}, ID: {template_id}")
conn.commit()
return template_id
else:
# 更新现有记录
template_id = existing_template['id']
template_name = existing_template['name']
# 获取现有的template_code
existing_input_data = existing_template.get('input_data')
template_code = get_template_code_from_input_data(existing_input_data)
if not template_code:
# 根据文件名生成template_code
if 'XXX初核情况报告' in file_name:
template_code = 'INVESTIGATION_REPORT'
elif '请示报告卡' in file_name and '初核报告结论' in file_name:
template_code = 'REPORT_CARD_CONCLUSION'
else:
template_code = f'TEMPLATE_{template_id % 1000000}'
# 准备input_data
input_data = json.dumps({
'template_code': template_code,
'business_type': 'INVESTIGATION',
'placeholders': template_info['placeholders']
}, ensure_ascii=False)
update_sql = """
UPDATE f_polic_file_config
SET file_path = %s, input_data = %s, updated_time = NOW(), updated_by = %s, state = 1
WHERE id = %s AND tenant_id = %s
"""
cursor.execute(update_sql, (
minio_path,
input_data,
UPDATED_BY,
template_id,
TENANT_ID
))
print(f" [OK] 更新模板配置: {template_name}, ID: {template_id}")
print(f" 占位符数量: {len(template_info['placeholders'])}")
if template_info['placeholders']:
print(f" 占位符: {', '.join(template_info['placeholders'][:10])}{'...' if len(template_info['placeholders']) > 10 else ''}")
conn.commit()
return template_id
except Exception as e:
conn.rollback()
raise Exception(f"更新模板配置失败: {str(e)}")
finally:
cursor.close()
def main():
"""主函数"""
print("=" * 80)
print("更新模板文件信息并上传到MinIO")
print("=" * 80)
# 连接数据库
try:
conn = pymysql.connect(**DB_CONFIG)
print("✓ 数据库连接成功")
except Exception as e:
print(f"✗ 数据库连接失败: {str(e)}")
return
# 创建MinIO客户端
try:
minio_client = Minio(
MINIO_CONFIG['endpoint'],
access_key=MINIO_CONFIG['access_key'],
secret_key=MINIO_CONFIG['secret_key'],
secure=MINIO_CONFIG['secure']
)
# 检查存储桶是否存在
found = minio_client.bucket_exists(BUCKET_NAME)
if not found:
print(f"✗ 存储桶 '{BUCKET_NAME}' 不存在")
conn.close()
return
print("✓ MinIO连接成功")
except Exception as e:
print(f"✗ MinIO连接失败: {str(e)}")
conn.close()
return
# 处理每个模板文件
success_count = 0
failed_files = []
for template_file in TEMPLATE_FILES:
print(f"\n{'=' * 80}")
print(f"处理模板: {template_file}")
print(f"{'=' * 80}")
# 检查文件是否存在
if not os.path.exists(template_file):
print(f" [ERROR] 文件不存在: {template_file}")
failed_files.append(template_file)
continue
try:
# 提取占位符
print(f" 正在提取占位符...")
placeholders = extract_placeholders_from_docx(template_file)
print(f" ✓ 提取到 {len(placeholders)} 个占位符")
if placeholders:
print(f" 占位符: {', '.join(placeholders[:10])}{'...' if len(placeholders) > 10 else ''}")
# 准备模板信息
file_name = Path(template_file).name
template_info = {
'file_path': template_file,
'file_name': file_name,
'placeholders': placeholders
}
# 上传到MinIO
print(f" 正在上传到MinIO...")
minio_path = upload_to_minio(minio_client, template_file, file_name)
print(f" ✓ 上传成功: {minio_path}")
# 更新数据库
print(f" 正在更新数据库...")
template_id = update_template(conn, template_file, template_info, minio_path)
print(f" ✓ 更新成功模板ID: {template_id}")
success_count += 1
except Exception as e:
print(f" [ERROR] 处理失败: {str(e)}")
import traceback
traceback.print_exc()
failed_files.append(template_file)
# 总结
print(f"\n{'=' * 80}")
print("处理完成")
print(f"{'=' * 80}")
print(f"成功: {success_count}/{len(TEMPLATE_FILES)}")
if failed_files:
print(f"失败的文件:")
for file in failed_files:
print(f" - {file}")
conn.close()
if __name__ == '__main__':
main()

147
检查结果报告.md Normal file
View File

@ -0,0 +1,147 @@
# f_polic_file_field 表关联关系检查报告
## 检查时间
2024年12月
## 检查结果摘要
### 总体统计
- **总关联数**: 114条
- 输入字段关联数: 4条
- 输出字段关联数: 110条
- **关联的模板数**: 19个共27个启用模板
- **关联的输入字段数**: 2个共2个启用输入字段
- **关联的输出字段数**: 34个共72个启用输出字段
### 发现的问题
#### 1. 输入字段关联缺失(严重问题)
- **23个模板缺少输入字段关联**
- 只有4个模板有输入字段关联
- `8-1请示报告卡初核报告结论` - 关联了1个输入字段
- `谈话通知书` - 关联了1个输入字段
- `走读式谈话审批` - 关联了1个输入字段
- `走读式谈话流程` - 关联了1个输入字段
#### 2. 输出字段关联缺失
- **9个模板缺少输出字段关联**
- **38个输出字段未关联任何模板**
#### 3. 完全无关联的模板
- **9个模板没有任何字段关联**(既没有输入字段,也没有输出字段)
## 详细问题列表
### 缺少输入字段关联的模板23个
1. 1.初核请示
2. 1.请示报告卡XXX
3. 1.请示报告卡(初核谈话)
4. 1.谈话笔录
5. 2-初核模版
6. 2.初步核实审批表XXX
7. 2.谈话审批
8. 2.谈话询问对象情况摸底调查30问
9. 2谈话审批表
10. 3.初核结论
11. 3.被谈话人权利义务告知书
12. 3.谈话前安全风险评估表
13. 3.附件初核方案(XXX)
14. 4.点对点交接单
15. 4.谈话方案
16. 5.谈话后安全风险评估表
17. 5.陪送交接单(新)
18. 6.1保密承诺书(谈话对象使用-非中共党员用)
19. 7.办案人员-办案安全保密承诺书
20. 8.XXX初核情况报告
21. 谈话通知书第一联
22. 谈话通知书第三联
23. 谈话通知书第二联
### 缺少输出字段关联的模板9个
1. 1.初核请示
2. 1.请示报告卡XXX
3. 1.请示报告卡(初核谈话)
4. 2-初核模版
5. 2.谈话审批
6. 3.初核结论
7. 3.被谈话人权利义务告知书
8. 6.1保密承诺书(谈话对象使用-非中共党员用)
9. 8.XXX初核情况报告
### 完全无关联的模板9个
与"缺少输出字段关联的模板"相同
## 输入字段关联情况
### 已关联的输入字段2个
1. **线索信息 (clue_info)**
- 关联模板: `8-1请示报告卡初核报告结论`
2. **被核查人员工作基本情况线索 (target_basic_info_clue)**
- 关联模板:
- `谈话通知书`
- `走读式谈话审批`
- `走读式谈话流程`
## 输出字段关联情况
### 未关联任何模板的输出字段38个
1. 初步核实审批表填表人 (filler_name)
2. 拟谈话地点 (proposed_interview_location)
3. 拟谈话时间 (proposed_interview_time)
4. 纪委名称 (commission_name)
5. 补空人员 (backup_personnel)
6. 被核查人员交代问题程度 (target_confession_level)
7. 被核查人员住址 (target_address)
8. 被核查人员健康状况 (target_health_status)
9. 被核查人员其他情况 (target_other_situation)
10. 被核查人员减压后的表现 (target_behavior_after_relief)
11. 被核查人员出生年月日 (target_date_of_birth_full)
12. 被核查人员学历 (target_education)
13. 被核查人员工作履历 (target_work_history)
14. 被核查人员思想负担程度 (target_mental_burden_level)
15. 被核查人员性格特征 (target_personality)
16. 被核查人员承受能力 (target_tolerance)
17. 被核查人员本人认识和态度 (target_attitude)
18. 被核查人员此前被审查情况 (target_previous_investigation)
19. 被核查人员涉及其他问题的可能性 (target_other_issues_possibility)
20. 被核查人员涉及问题严重程度 (target_issue_severity)
21. 被核查人员社会负面事件 (target_negative_events)
22. 被核查人员职业 (target_occupation)
23. 被核查人员谈话中的表现 (target_behavior_during_interview)
24. 被核查人员问题严重程度 (target_issue_severity_level)
25. 被核查人员风险等级 (target_risk_level)
26. 被核查人基本情况 (target_basic_info)
27. 被核查人问题描述 (target_problem_description)
28. 记录人 (recorder)
29. 评估意见 (assessment_opinion)
30. 谈话事由 (interview_reason)
31. 谈话人 (interviewer)
32. 谈话人员-安全员 (interview_personnel_safety_officer)
33. 谈话人员-组长 (interview_personnel_leader)
34. 谈话人员-谈话人员 (interview_personnel)
35. 谈话前安全风险评估结果 (pre_interview_risk_assessment_result)
36. 谈话地点 (interview_location)
37. 谈话次数 (interview_count)
38. 风险等级 (risk_level)
## 建议
### 紧急修复
1. **恢复输入字段关联关系**需要根据业务逻辑为23个缺少输入字段关联的模板建立正确的关联关系
2. **检查历史数据**:查看是否有备份或历史记录可以帮助恢复关联关系
### 长期改进
1. **建立关联关系管理机制**:确保在更新数据时不会丢失关联关系
2. **定期检查**:定期运行检查脚本,及时发现关联关系问题
3. **文档化**:记录每个模板应该关联哪些输入和输出字段
## 检查脚本
使用 `check_file_field_relations_comprehensive.py` 脚本进行检查。

View File

@ -0,0 +1,173 @@
# 模板字段关联管理使用说明
## 功能概述
模板字段关联管理页面提供了一个可视化的界面,用于维护模板与输入字段、输出字段的关联关系。通过这个页面,您可以:
1. 查看所有启用的模板
2. 查看所有输入字段和输出字段
3. 为每个模板选择关联的输入字段和输出字段
4. 保存关联关系到数据库
## 访问方式
启动Flask服务后访问以下URL
```
http://localhost:7500/template-field-manager
```
## 使用步骤
### 1. 选择模板
在页面顶部的下拉框中选择要管理的模板。选择后,页面会显示该模板当前关联的字段。
### 2. 查看字段
页面分为两个区域:
- **左侧**输入字段列表field_type=1
- **右侧**输出字段列表field_type=2
每个字段显示:
- 字段名称(中文)
- 字段编码field_code
### 3. 选择关联字段
- 点击字段项或复选框来勾选/取消勾选字段
- 已选中的字段会高亮显示(绿色背景)
- 可以使用搜索框快速查找字段
### 4. 查看统计信息
页面顶部显示当前选择的统计信息:
- 已选输入字段数量
- 已选输出字段数量
- 总字段数
### 5. 保存关联关系
点击"保存关联关系"按钮,系统会:
1. 删除该模板的所有现有关联关系
2. 创建新的关联关系(基于当前选择)
3. 显示保存结果
## 功能特性
### 搜索功能
- 在输入字段或输出字段区域,可以使用搜索框过滤字段
- 支持按字段名称或字段编码搜索
- 实时过滤,无需点击按钮
### 重置功能
- 点击"重置"按钮可以清空当前模板的所有选择
- 需要确认操作
### 数据验证
- 系统会自动验证模板和字段是否存在
- 保存时会检查数据完整性
- 错误信息会显示在页面顶部
## API接口
### 获取关联关系
**GET** `/api/template-field-relations`
返回所有模板、字段和关联关系数据。
**响应格式:**
```json
{
"code": 0,
"data": {
"templates": [...],
"input_fields": [...],
"output_fields": [...],
"relations": {
"template_id": [field_id1, field_id2, ...]
}
},
"isSuccess": true
}
```
### 保存关联关系
**POST** `/api/template-field-relations`
保存指定模板的字段关联关系。
**请求体:**
```json
{
"template_id": 123,
"input_field_ids": [1, 2, 3],
"output_field_ids": [4, 5, 6]
}
```
**响应格式:**
```json
{
"code": 0,
"data": {
"template_id": 123,
"input_field_count": 3,
"output_field_count": 3,
"total_field_count": 6
},
"msg": "保存成功",
"isSuccess": true
}
```
## 注意事项
1. **数据安全**:保存操作会删除该模板的所有现有关联关系,然后创建新的关联。请确保选择正确后再保存。
2. **字段状态**只有状态为启用state=1的模板和字段才会显示在页面上。
3. **关联关系**:关联关系存储在 `f_polic_file_field` 表中,包含以下字段:
- `file_id`模板ID
- `filed_id`字段ID
- `tenant_id`租户ID
- `state`状态1=启用)
4. **批量操作**:目前不支持批量操作多个模板,需要逐个模板进行管理。
## 故障排除
### 页面无法加载
- 检查Flask服务是否正常运行
- 检查端口号是否正确默认7500
- 查看浏览器控制台是否有错误信息
### 保存失败
- 检查网络连接
- 查看页面显示的错误信息
- 检查数据库连接是否正常
- 确认模板和字段ID是否有效
### 字段不显示
- 确认字段状态为启用state=1
- 检查字段类型是否正确1=输入字段2=输出字段)
- 刷新页面重新加载数据
## 技术实现
- **前端**纯HTML + CSS + JavaScript无依赖
- **后端**Flask + PyMySQL
- **数据库**MySQL
- **表结构**
- `f_polic_file_config`:模板配置表
- `f_polic_field`:字段定义表
- `f_polic_file_field`:关联关系表