调整模板和测试页面

This commit is contained in:
python 2025-12-11 15:10:46 +08:00
parent d8d2817aed
commit d3afba9191
9 changed files with 774 additions and 39 deletions

148
analyze_duplicate_fields.py Normal file
View File

@ -0,0 +1,148 @@
"""分析 f_polic_field 表中的重复字段"""
import pymysql
import os
from dotenv import load_dotenv
from collections import defaultdict
load_dotenv()
TENANT_ID = 615873064429507639
conn = pymysql.connect(
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'
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
print("=" * 80)
print("1. 分析按 name 字段的重复情况")
print("=" * 80)
# 查询所有字段
cursor.execute("""
SELECT id, name, filed_code, field_type, state
FROM f_polic_field
WHERE tenant_id = %s
ORDER BY name, id
""", (TENANT_ID,))
all_fields = cursor.fetchall()
# 按 name 分组
name_groups = defaultdict(list)
for field in all_fields:
name_groups[field['name']].append(field)
# 找出重复的 name
duplicate_names = {name: fields for name, fields in name_groups.items() if len(fields) > 1}
print(f"\n发现 {len(duplicate_names)} 个重复的字段名称:\n")
for name, fields in sorted(duplicate_names.items()):
print(f"字段名称: {name}")
for field in fields:
print(f" ID: {field['id']}, filed_code: {field['filed_code']}, field_type: {field['field_type']}, state: {field['state']}")
print()
print("\n" + "=" * 80)
print("2. 分析按 filed_code 字段的重复情况")
print("=" * 80)
# 按 filed_code 分组
code_groups = defaultdict(list)
for field in all_fields:
code_groups[field['filed_code']].append(field)
# 找出重复的 filed_code
duplicate_codes = {code: fields for code, fields in code_groups.items() if len(fields) > 1}
print(f"\n发现 {len(duplicate_codes)} 个重复的字段编码:\n")
for code, fields in sorted(duplicate_codes.items()):
print(f"字段编码: {code}")
for field in fields:
print(f" ID: {field['id']}, name: {field['name']}, field_type: {field['field_type']}, state: {field['state']}")
print()
print("\n" + "=" * 80)
print("3. 分析重复字段的关联关系f_polic_file_field")
print("=" * 80)
# 获取所有重复字段的ID
all_duplicate_field_ids = set()
for fields in duplicate_names.values():
for field in fields:
all_duplicate_field_ids.add(field['id'])
for fields in duplicate_codes.values():
for field in fields:
all_duplicate_field_ids.add(field['id'])
if all_duplicate_field_ids:
placeholders = ','.join(['%s'] * len(all_duplicate_field_ids))
cursor.execute(f"""
SELECT ff.file_id, ff.filed_id, f.name, f.filed_code, fc.name as file_name, fc.state as file_state
FROM f_polic_file_field ff
INNER JOIN f_polic_field f ON ff.filed_id = f.id
INNER JOIN f_polic_file_config fc ON ff.file_id = fc.id
WHERE ff.filed_id IN ({placeholders})
AND f.tenant_id = %s
ORDER BY f.filed_code, ff.file_id
""", list(all_duplicate_field_ids) + [TENANT_ID])
associations = cursor.fetchall()
# 按 filed_code 分组关联关系
code_associations = defaultdict(list)
for assoc in associations:
code_associations[assoc['filed_code']].append(assoc)
print(f"\n重复字段的关联关系:\n")
for code, assocs in sorted(code_associations.items()):
print(f"字段编码: {code} ({assocs[0]['name']})")
for assoc in assocs:
print(f" 字段ID: {assoc['filed_id']}, 文件ID: {assoc['file_id']}, 文件名: {assoc['file_name']}, 文件状态: {assoc['file_state']}")
print()
else:
print("\n没有发现重复字段的关联关系")
print("\n" + "=" * 80)
print("4. 统计每个 filed_code 关联的模板数量")
print("=" * 80)
cursor.execute("""
SELECT f.filed_code, f.name, COUNT(DISTINCT ff.file_id) as template_count,
GROUP_CONCAT(DISTINCT ff.filed_id ORDER BY ff.filed_id) as field_ids,
GROUP_CONCAT(DISTINCT fc.name ORDER BY fc.name SEPARATOR ' | ') as template_names
FROM f_polic_field f
LEFT JOIN f_polic_file_field ff ON f.id = ff.filed_id
LEFT JOIN f_polic_file_config fc ON ff.file_id = fc.id AND fc.state = 1
WHERE f.tenant_id = %s
GROUP BY f.filed_code, f.name
HAVING COUNT(DISTINCT ff.filed_id) > 0 OR f.filed_code IN (
SELECT filed_code FROM (
SELECT filed_code, COUNT(*) as cnt
FROM f_polic_field
WHERE tenant_id = %s
GROUP BY filed_code
HAVING cnt > 1
) AS dup
)
ORDER BY template_count DESC, f.filed_code
""", (TENANT_ID, TENANT_ID))
stats = cursor.fetchall()
print(f"\n字段关联统计(包含重复字段):\n")
for stat in stats:
print(f"字段编码: {stat['filed_code']}")
print(f" 字段名称: {stat['name']}")
print(f" 关联模板数: {stat['template_count']}")
print(f" 字段ID列表: {stat['field_ids']}")
if stat['template_names']:
print(f" 关联模板: {stat['template_names']}")
print()
cursor.close()
conn.close()

176
fix_duplicate_fields.py Normal file
View File

@ -0,0 +1,176 @@
"""修复 f_polic_field 表中的重复字段"""
import pymysql
import os
from dotenv import load_dotenv
from collections import defaultdict
load_dotenv()
TENANT_ID = 615873064429507639
conn = pymysql.connect(
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'
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
print("=" * 80)
print("修复重复字段")
print("=" * 80)
# 1. 查找所有重复的 filed_code
cursor.execute("""
SELECT filed_code, COUNT(*) as cnt, GROUP_CONCAT(id ORDER BY id) as field_ids
FROM f_polic_field
WHERE tenant_id = %s
GROUP BY filed_code
HAVING cnt > 1
""", (TENANT_ID,))
duplicate_codes = cursor.fetchall()
print(f"\n发现 {len(duplicate_codes)} 个重复的字段编码:\n")
for dup in duplicate_codes:
code = dup['filed_code']
field_ids = [int(x) for x in dup['field_ids'].split(',')]
print(f"\n处理字段编码: {code}")
print(f" 字段ID列表: {field_ids}")
# 获取每个字段的详细信息
placeholders = ','.join(['%s'] * len(field_ids))
cursor.execute(f"""
SELECT id, name, field_type, state
FROM f_polic_field
WHERE id IN ({placeholders})
ORDER BY id
""", field_ids)
fields = cursor.fetchall()
# 获取每个字段的关联关系
field_associations = {}
for field_id in field_ids:
cursor.execute("""
SELECT COUNT(*) as cnt, GROUP_CONCAT(file_id) as file_ids
FROM f_polic_file_field
WHERE filed_id = %s
""", (field_id,))
result = cursor.fetchone()
field_associations[field_id] = {
'count': result['cnt'] if result else 0,
'file_ids': result['file_ids'].split(',') if result and result['file_ids'] else []
}
print(f"\n 字段详情和关联关系:")
for field in fields:
assoc = field_associations[field['id']]
print(f" ID: {field['id']}, name: {field['name']}, "
f"field_type: {field['field_type']}, state: {field['state']}, "
f"关联模板数: {assoc['count']}")
# 选择保留的字段优先选择关联模板数最多的如果相同则选择ID较小的
fields_with_assoc = [(f, field_associations[f['id']]) for f in fields]
fields_with_assoc.sort(key=lambda x: (-x[1]['count'], x[0]['id']))
keep_field = fields_with_assoc[0][0]
remove_fields = [f for f, _ in fields_with_assoc[1:]]
print(f"\n 保留字段: ID={keep_field['id']}, name={keep_field['name']}, "
f"关联模板数={field_associations[keep_field['id']]['count']}")
print(f" 删除字段: {[f['id'] for f in remove_fields]}")
# 迁移关联关系:将删除字段的关联关系迁移到保留字段
for remove_field in remove_fields:
remove_id = remove_field['id']
keep_id = keep_field['id']
# 获取删除字段的所有关联
cursor.execute("""
SELECT file_id
FROM f_polic_file_field
WHERE filed_id = %s
""", (remove_id,))
remove_assocs = cursor.fetchall()
migrated_count = 0
skipped_count = 0
for assoc in remove_assocs:
file_id = assoc['file_id']
# 检查保留字段是否已经关联了这个文件
cursor.execute("""
SELECT COUNT(*) as cnt
FROM f_polic_file_field
WHERE filed_id = %s AND file_id = %s
""", (keep_id, file_id))
exists = cursor.fetchone()['cnt'] > 0
if not exists:
# 迁移关联关系
cursor.execute("""
UPDATE f_polic_file_field
SET filed_id = %s
WHERE filed_id = %s AND file_id = %s
""", (keep_id, remove_id, file_id))
migrated_count += 1
else:
# 如果已存在,直接删除重复的关联
cursor.execute("""
DELETE FROM f_polic_file_field
WHERE filed_id = %s AND file_id = %s
""", (remove_id, file_id))
skipped_count += 1
print(f" 字段ID {remove_id} -> {keep_id}: 迁移 {migrated_count} 个关联, 跳过 {skipped_count} 个重复关联")
# 删除字段的所有关联关系(应该已经迁移或删除完毕)
cursor.execute("""
DELETE FROM f_polic_file_field
WHERE filed_id = %s
""", (remove_id,))
# 删除字段本身
cursor.execute("""
DELETE FROM f_polic_field
WHERE id = %s
""", (remove_id,))
print(f" 已删除字段 ID {remove_id} 及其关联关系")
print("\n" + "=" * 80)
print("验证修复结果")
print("=" * 80)
# 再次检查是否还有重复
cursor.execute("""
SELECT filed_code, COUNT(*) as cnt
FROM f_polic_field
WHERE tenant_id = %s
GROUP BY filed_code
HAVING cnt > 1
""", (TENANT_ID,))
remaining_duplicates = cursor.fetchall()
if remaining_duplicates:
print(f"\n警告:仍有 {len(remaining_duplicates)} 个重复的字段编码:")
for dup in remaining_duplicates:
print(f" {dup['filed_code']}: {dup['cnt']}")
else:
print("\n[OK] 所有重复字段已修复filed_code 现在唯一")
# 提交事务
conn.commit()
print("\n[OK] 所有更改已提交到数据库")
cursor.close()
conn.close()

View File

@ -132,14 +132,61 @@ class DocumentService:
""" """
try: try:
print(f"[DEBUG] 开始填充模板: {template_path}") print(f"[DEBUG] 开始填充模板: {template_path}")
print(f"[DEBUG] 字段数据: {field_data}") print(f"[DEBUG] 传入的字段数据: {field_data}")
# 打开模板文档 # 打开模板文档
doc = Document(template_path) doc = Document(template_path)
print(f"[DEBUG] 文档包含 {len(doc.paragraphs)} 个段落, {len(doc.tables)} 个表格") print(f"[DEBUG] 文档包含 {len(doc.paragraphs)} 个段落, {len(doc.tables)} 个表格")
# 第一步:扫描模板文档,找出所有占位符(格式:{{field_code}}
# 使用正则表达式匹配所有占位符
placeholder_pattern = re.compile(r'\{\{([^}]+)\}\}')
all_placeholders_in_template = set()
# 扫描段落中的占位符
for paragraph in doc.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)
# 扫描表格中的占位符
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)
print(f"[DEBUG] 模板中发现 {len(all_placeholders_in_template)} 个不同的占位符: {sorted(all_placeholders_in_template)}")
# 第二步对于模板中存在的占位符如果field_data中没有对应的值则使用空字符串
# 创建一个完整的字段数据字典,包含所有需要的字段
complete_field_data = {}
for field_code in all_placeholders_in_template:
# 如果传入的数据中有该字段,使用传入的值;否则使用空字符串
complete_field_data[field_code] = field_data.get(field_code, '')
# 同时保留传入的字段数据(可能包含模板中没有的字段,虽然不会使用,但保留以兼容)
for field_code, field_value in field_data.items():
if field_code not in complete_field_data:
complete_field_data[field_code] = field_value or ''
print(f"[DEBUG] 完整的字段数据(包含默认空值): {complete_field_data}")
print(f"[DEBUG] 补充的空值字段: {sorted(set(complete_field_data.keys()) - set(field_data.keys()))}")
# 使用完整的字段数据进行替换
field_data = complete_field_data
def replace_placeholder_in_paragraph(paragraph): def replace_placeholder_in_paragraph(paragraph):
"""在段落中替换占位符处理跨run的情况""" """在段落中替换占位符,保持原有格式处理跨run的情况"""
try: try:
# 获取段落完整文本 # 获取段落完整文本
full_text = paragraph.text full_text = paragraph.text
@ -148,38 +195,120 @@ class DocumentService:
# 检查是否有占位符需要替换 # 检查是否有占位符需要替换
has_placeholder = False has_placeholder = False
replaced_text = full_text for field_code in field_data.keys():
replacement_count = 0 placeholder = f"{{{{{field_code}}}}}"
if placeholder in full_text:
has_placeholder = True
break
# 遍历所有字段,替换所有匹配的占位符(包括重复的) if not has_placeholder:
return
# 收集所有runs及其位置和格式信息
runs_info = []
current_pos = 0
for run in paragraph.runs:
run_text = run.text
run_start = current_pos
run_end = current_pos + len(run_text)
# 保存run的格式信息
format_info = {}
try:
if run.font.name:
format_info['font_name'] = run.font.name
if run.font.size:
format_info['font_size'] = run.font.size
if run.bold is not None:
format_info['bold'] = run.bold
if run.italic is not None:
format_info['italic'] = run.italic
if run.underline is not None:
format_info['underline'] = run.underline
if run.font.color and run.font.color.rgb:
format_info['color'] = run.font.color.rgb
except:
pass
runs_info.append({
'run': run,
'text': run_text,
'start': run_start,
'end': run_end,
'format': format_info
})
current_pos = run_end
# 执行所有替换,构建最终文本
final_text = full_text
replacement_count = 0
for field_code, field_value in field_data.items(): for field_code, field_value in field_data.items():
placeholder = f"{{{{{field_code}}}}}" placeholder = f"{{{{{field_code}}}}}"
# 使用循环替换所有匹配项(不仅仅是第一个) replacement_value = str(field_value) if field_value else ''
while placeholder in replaced_text: # 替换所有出现的占位符
has_placeholder = True while placeholder in final_text:
final_text = final_text.replace(placeholder, replacement_value, 1)
replacement_count += 1 replacement_count += 1
# 替换占位符,如果值为空则替换为空字符串 print(f"[DEBUG] 替换占位符: {placeholder} -> '{replacement_value}'")
replaced_text = replaced_text.replace(placeholder, str(field_value) if field_value else '', 1)
print(f"[DEBUG] 替换占位符: {placeholder} -> '{field_value}' (在段落中)")
# 如果有替换,使用安全的方式更新段落文本 # 找到包含占位符的第一个run使用它的格式
if has_placeholder: placeholder_run_format = None
print(f"[DEBUG] 段落替换了 {replacement_count} 个占位符: '{full_text[:50]}...' -> '{replaced_text[:50]}...'") for run_info in runs_info:
try: run_text = run_info['text']
# 方法1直接设置text推荐会自动处理run # 检查这个run是否包含任何占位符
paragraph.text = replaced_text for field_code in field_data.keys():
except Exception as e1: placeholder = f"{{{{{field_code}}}}}"
# 如果方法1失败尝试方法2手动处理run if placeholder in run_text:
placeholder_run_format = run_info['format']
break
if placeholder_run_format:
break
# 如果没有找到包含占位符的run使用第一个run的格式
if not placeholder_run_format and runs_info:
placeholder_run_format = runs_info[0]['format']
# 如果只有一个run直接替换文本会自动保持格式
if len(runs_info) == 1:
runs_info[0]['run'].text = final_text
else:
# 多个run的情况合并为一个run保持格式
# 先清空所有runs
for run_info in runs_info:
run_info['run'].text = ''
# 在第一个run中添加替换后的文本
first_run = runs_info[0]['run']
first_run.text = final_text
# 应用格式使用包含占位符的run的格式或第一个run的格式
if placeholder_run_format:
try: try:
# 清空所有run if 'font_name' in placeholder_run_format:
paragraph.clear() first_run.font.name = placeholder_run_format['font_name']
# 添加新的run if 'font_size' in placeholder_run_format:
if replaced_text: first_run.font.size = placeholder_run_format['font_size']
paragraph.add_run(replaced_text) if 'bold' in placeholder_run_format:
except Exception as e2: first_run.bold = placeholder_run_format['bold']
# 如果两种方法都失败,记录错误但继续 if 'italic' in placeholder_run_format:
print(f"[WARN] 无法更新段落文本方法1错误: {str(e1)}, 方法2错误: {str(e2)}") first_run.italic = placeholder_run_format['italic']
if 'underline' in placeholder_run_format:
first_run.underline = placeholder_run_format['underline']
if 'color' in placeholder_run_format:
first_run.font.color.rgb = placeholder_run_format['color']
except Exception as fmt_error:
print(f"[WARN] 应用格式时出错: {str(fmt_error)}")
# 删除其他空的runs从后往前删除避免索引问题
for i in range(len(runs_info) - 1, 0, -1):
run_element = runs_info[i]['run']._element
try:
paragraph._element.remove(run_element)
except:
pass pass
print(f"[DEBUG] 段落替换了 {replacement_count} 个占位符(保持格式): '{full_text[:50]}...' -> '{final_text[:50]}...'")
except Exception as e: except Exception as e:
# 如果单个段落处理失败,记录错误但继续处理其他段落 # 如果单个段落处理失败,记录错误但继续处理其他段落
print(f"[WARN] 处理段落时出错: {str(e)}") print(f"[WARN] 处理段落时出错: {str(e)}")
@ -238,13 +367,14 @@ class DocumentService:
print(f"[WARN] 处理表格时出错: {str(e)}") print(f"[WARN] 处理表格时出错: {str(e)}")
pass pass
# 验证是否还有未替换的占位符 # 第三步:验证是否还有未替换的占位符(使用正则表达式匹配所有可能的占位符)
remaining_placeholders = set() remaining_placeholders = set()
for paragraph in doc.paragraphs: for paragraph in doc.paragraphs:
text = paragraph.text text = paragraph.text
for field_code in field_data.keys(): matches = placeholder_pattern.findall(text)
placeholder = f"{{{{{field_code}}}}}" for match in matches:
if placeholder in text: field_code = match.strip()
if field_code:
remaining_placeholders.add(field_code) remaining_placeholders.add(field_code)
# 检查表格中的占位符 # 检查表格中的占位符
@ -254,17 +384,20 @@ class DocumentService:
if hasattr(cell, 'paragraphs'): if hasattr(cell, 'paragraphs'):
for paragraph in cell.paragraphs: for paragraph in cell.paragraphs:
text = paragraph.text text = paragraph.text
for field_code in field_data.keys(): matches = placeholder_pattern.findall(text)
placeholder = f"{{{{{field_code}}}}}" for match in matches:
if placeholder in text: field_code = match.strip()
if field_code:
remaining_placeholders.add(field_code) remaining_placeholders.add(field_code)
# 输出统计信息 # 输出统计信息
print(f"[DEBUG] 占位符替换统计:") print(f"[DEBUG] 占位符替换统计:")
print(f" - 模板中的占位符总数: {len(all_placeholders_in_template)}")
print(f" - 已替换的占位符: {sorted(replaced_placeholders)}") print(f" - 已替换的占位符: {sorted(replaced_placeholders)}")
print(f" - 总替换次数: {total_replacements}") print(f" - 总替换次数: {total_replacements}")
if remaining_placeholders: if remaining_placeholders:
print(f" - ⚠️ 仍有未替换的占位符: {sorted(remaining_placeholders)}") print(f" - ⚠️ 仍有未替换的占位符: {sorted(remaining_placeholders)}")
print(f" - ⚠️ 警告:文档中仍存在占位符,可能格式不正确或替换逻辑有问题")
else: else:
print(f" - ✓ 所有占位符已成功替换") print(f" - ✓ 所有占位符已成功替换")

View File

@ -587,21 +587,97 @@
} }
async function initGenerateTab() { async function initGenerateTab() {
// 初始化默认字段(完整的虚拟测试数据) // 初始化所有字段(完整的虚拟测试数据)
// 基本信息字段
addGenerateField('target_name', '张三'); addGenerateField('target_name', '张三');
addGenerateField('target_gender', '男'); addGenerateField('target_gender', '男');
addGenerateField('target_age', '34'); addGenerateField('target_age', '34');
addGenerateField('target_date_of_birth', '199009'); addGenerateField('target_date_of_birth', '199009');
addGenerateField('target_date_of_birth_full', '1990年9月');
addGenerateField('target_id_number', '530123199009123456');
addGenerateField('target_ethnicity', '汉族');
addGenerateField('target_place_of_origin', '云南普洱');
addGenerateField('target_address', '云南省昆明市五华区某某街道某某小区1栋1单元101室');
addGenerateField('target_registered_address', '云南省昆明市五华区某某街道某某小区1栋1单元101室');
addGenerateField('target_contact', '13800138000');
// 组织和工作信息
addGenerateField('target_organization_and_position', '云南省农业机械公司党支部书记、经理'); addGenerateField('target_organization_and_position', '云南省农业机械公司党支部书记、经理');
addGenerateField('target_organization', '云南省农业机械公司'); addGenerateField('target_organization', '云南省农业机械公司');
addGenerateField('target_position', '党支部书记、经理'); addGenerateField('target_position', '党支部书记、经理');
addGenerateField('target_education_level', '研究生'); addGenerateField('target_education_level', '研究生');
addGenerateField('target_education', '研究生');
addGenerateField('target_political_status', '中共党员'); addGenerateField('target_political_status', '中共党员');
addGenerateField('target_professional_rank', ''); addGenerateField('target_professional_rank', '高级工程师');
addGenerateField('clue_source', ''); addGenerateField('target_occupation', '企业管理人员');
addGenerateField('target_work_basic_info', '2005年8月参加工作现任云南省农业机械公司党支部书记、经理');
addGenerateField('target_work_history', '2004年8月至2005年2月在云南省农业机械公司工作2005年2月至2012年2月历任云南省农业机械公司办公室副主任、主任、团委书记2012年2月至2018年3月任云南省农业机械公司支部书记、厂长2018年3月至2020年3月任云南省农业机械公司总经理助理、销售部部长2020年3月至2022年3月任云南省农业机械公司总经理助理2022年3月至2022年7月任云南省农业机械公司大理分公司副经理2022年7月至2023年12月任云南省农业机械公司西双版纳分公司经理2023年12月至今任云南省农业机械公司党支部书记、经理。');
addGenerateField('target_basic_info', '张三汉族1990年9月出生云南普洱人研究生学历中共党员现任云南省农业机械公司党支部书记、经理。');
// 线索和问题信息
addGenerateField('clue_info', '张三多次在私下聚会、网络群组中发表抹黑党中央决策部署的言论传播歪曲党的理论和路线方针政策的错误观点频繁接受管理服务对象安排的高档宴请、私人会所聚餐以及高尔夫球、高端足浴等娱乐活动相关费用均由对方全额承担在干部选拔任用、岗位调整工作中利用职务便利收受他人财物利用职权为其亲属经营的公司谋取不正当利益帮助该公司违规承接本单位及关联单位工程项目3个合同总额超200万元从中收受亲属给予的"感谢费"15万元其本人沉迷赌博活动每周至少参与1次大额赌资赌博单次赌资超1万元累计赌资达数十万元。');
addGenerateField('target_basic_info_clue', '张三汉族1990年9月出生云南普洱人研究生学历2005年8月参加工作2006年10月加入中国共产党。2004年8月至2005年2月在云南省农业机械公司工作2005年2月至2012年2月历任云南省农业机械公司办公室副主任、主任、团委书记2012年2月至2018年3月任云南省农业机械公司支部书记、厂长2018年3月至2020年3月任云南省农业机械公司总经理助理、销售部部长2020年3月至2022年3月任云南省农业机械公司总经理助理2022年3月至2022年7月任云南省农业机械公司大理分公司副经理2022年7月至2023年12月任云南省农业机械公司西双版纳分公司经理2023年12月至今任云南省农业机械公司党支部书记、经理。');
addGenerateField('clue_source', '群众举报');
addGenerateField('target_issue_description', '张三多次在私下聚会、网络群组中发表抹黑党中央决策部署的言论传播歪曲党的理论和路线方针政策的错误观点频繁接受管理服务对象安排的高档宴请、私人会所聚餐以及高尔夫球、高端足浴等娱乐活动相关费用均由对方全额承担在干部选拔任用、岗位调整工作中利用职务便利收受他人财物利用职权为其亲属经营的公司谋取不正当利益帮助该公司违规承接本单位及关联单位工程项目3个合同总额超200万元从中收受亲属给予的"感谢费"15万元其本人沉迷赌博活动每周至少参与1次大额赌资赌博单次赌资超1万元累计赌资达数十万元。'); addGenerateField('target_issue_description', '张三多次在私下聚会、网络群组中发表抹黑党中央决策部署的言论传播歪曲党的理论和路线方针政策的错误观点频繁接受管理服务对象安排的高档宴请、私人会所聚餐以及高尔夫球、高端足浴等娱乐活动相关费用均由对方全额承担在干部选拔任用、岗位调整工作中利用职务便利收受他人财物利用职权为其亲属经营的公司谋取不正当利益帮助该公司违规承接本单位及关联单位工程项目3个合同总额超200万元从中收受亲属给予的"感谢费"15万元其本人沉迷赌博活动每周至少参与1次大额赌资赌博单次赌资超1万元累计赌资达数十万元。');
addGenerateField('department_opinion', ''); addGenerateField('target_problem_description', '违反政治纪律、组织纪律、廉洁纪律,涉嫌违纪违法');
addGenerateField('filler_name', ''); addGenerateField('target_issue_severity', '严重');
addGenerateField('target_issue_severity_level', '严重');
addGenerateField('target_other_issues_possibility', '较大');
// 个人情况评估
addGenerateField('target_family_situation', '家庭关系和谐稳定');
addGenerateField('target_social_relations', '社会交往较多,人际关系基本正常');
addGenerateField('target_health_status', '良好');
addGenerateField('target_personality', '开朗');
addGenerateField('target_tolerance', '较强');
addGenerateField('target_previous_investigation', '无');
addGenerateField('target_negative_events', '无');
addGenerateField('target_other_situation', '无');
// 谈话和调查相关
addGenerateField('target_attitude', '配合调查');
addGenerateField('target_confession_level', '部分承认');
addGenerateField('target_behavior_during_interview', '情绪稳定,配合调查');
addGenerateField('target_behavior_after_relief', '情绪有所缓解');
addGenerateField('target_mental_burden_level', '中等');
addGenerateField('target_risk_level', '中');
addGenerateField('risk_level', '中');
addGenerateField('pre_interview_risk_assessment_result', '风险等级:中,已制定安全预案');
// 调查组织和人员
addGenerateField('investigation_unit_name', '纪检监察室');
addGenerateField('investigation_team_code', 'JC2024001');
addGenerateField('investigation_team_leader_name', '赵六');
addGenerateField('investigation_team_member_names', '赵六、钱七、孙八');
addGenerateField('investigation_location', '纪检监察室谈话室');
addGenerateField('handler_name', '王五');
addGenerateField('handling_department', '纪检监察室');
addGenerateField('commission_name', '中共某某市纪律检查委员会');
// 谈话相关
addGenerateField('interview_location', '纪检监察室谈话室');
addGenerateField('proposed_interview_location', '纪检监察室谈话室');
addGenerateField('notification_location', '纪检监察室');
addGenerateField('appointment_location', '纪检监察室谈话室');
addGenerateField('interview_time', '2024年12月10日14:00');
addGenerateField('proposed_interview_time', '2024年12月10日14:00');
addGenerateField('notification_time', '2024年12月9日');
addGenerateField('appointment_time', '2024年12月10日14:00');
addGenerateField('interview_reason', '就相关问题进行核实了解');
addGenerateField('interview_count', '1');
addGenerateField('interviewer', '赵六');
addGenerateField('recorder', '钱七');
addGenerateField('interview_personnel', '赵六、钱七');
addGenerateField('interview_personnel_leader', '赵六');
addGenerateField('interview_personnel_safety_officer', '孙八');
addGenerateField('backup_personnel', '周九');
// 审批和意见
addGenerateField('approval_time', '2024年12月8日');
addGenerateField('report_card_request_time', '2024年12月8日');
addGenerateField('department_opinion', '经初步核实,建议立案调查');
addGenerateField('assessment_opinion', '建议进行谈话核实');
addGenerateField('filler_name', '李四');
// 自动加载所有可用的文件列表 // 自动加载所有可用的文件列表
try { try {

103
verify_field_uniqueness.py Normal file
View File

@ -0,0 +1,103 @@
"""验证字段唯一性"""
import pymysql
import os
from dotenv import load_dotenv
load_dotenv()
TENANT_ID = 615873064429507639
conn = pymysql.connect(
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'
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
print("=" * 80)
print("验证字段唯一性")
print("=" * 80)
# 检查 filed_code 重复
cursor.execute("""
SELECT filed_code, COUNT(*) as cnt, GROUP_CONCAT(id ORDER BY id) as field_ids
FROM f_polic_field
WHERE tenant_id = %s
GROUP BY filed_code
HAVING cnt > 1
""", (TENANT_ID,))
duplicates = cursor.fetchall()
if duplicates:
print(f"\n[ERROR] 发现 {len(duplicates)} 个重复的字段编码:")
for dup in duplicates:
print(f" {dup['filed_code']}: {dup['cnt']} 个字段 (IDs: {dup['field_ids']})")
else:
print("\n[OK] 所有 filed_code 都是唯一的")
# 检查 name 重复
cursor.execute("""
SELECT name, COUNT(*) as cnt, GROUP_CONCAT(id ORDER BY id) as field_ids,
GROUP_CONCAT(filed_code ORDER BY id) as field_codes
FROM f_polic_field
WHERE tenant_id = %s
GROUP BY name
HAVING cnt > 1
""", (TENANT_ID,))
name_duplicates = cursor.fetchall()
if name_duplicates:
print(f"\n[WARN] 发现 {len(name_duplicates)} 个重复的字段名称:")
for dup in name_duplicates:
print(f" {dup['name']}: {dup['cnt']} 个字段")
print(f" 字段编码: {dup['field_codes']}")
print(f" 字段ID: {dup['field_ids']}")
else:
print("\n[OK] 所有字段名称都是唯一的")
# 统计字段总数
cursor.execute("""
SELECT COUNT(*) as total,
COUNT(DISTINCT filed_code) as unique_codes,
COUNT(DISTINCT name) as unique_names
FROM f_polic_field
WHERE tenant_id = %s
""", (TENANT_ID,))
stats = cursor.fetchone()
print(f"\n字段统计:")
print(f" 总字段数: {stats['total']}")
print(f" 唯一字段编码数: {stats['unique_codes']}")
print(f" 唯一字段名称数: {stats['unique_names']}")
# 检查孤立字段(没有关联任何模板的字段)
cursor.execute("""
SELECT f.id, f.name, f.filed_code, f.field_type
FROM f_polic_field f
LEFT JOIN f_polic_file_field ff ON f.id = ff.filed_id
WHERE f.tenant_id = %s
AND ff.filed_id IS NULL
AND f.field_type = 2
ORDER BY f.filed_code
""", (TENANT_ID,))
orphaned_fields = cursor.fetchall()
if orphaned_fields:
print(f"\n[INFO] 发现 {len(orphaned_fields)} 个未关联任何模板的输出字段:")
for field in orphaned_fields[:10]: # 只显示前10个
print(f" {field['filed_code']}: {field['name']}")
if len(orphaned_fields) > 10:
print(f" ... 还有 {len(orphaned_fields) - 10}")
else:
print("\n[OK] 所有输出字段都至少关联了一个模板")
cursor.close()
conn.close()

View File

@ -0,0 +1,99 @@
# 修复重复字段总结
## 问题分析
通过分析 `f_polic_field` 表,发现存在以下重复问题:
### 1. 重复的字段编码 (filed_code)
发现 **2个重复的字段编码**
#### 问题1: `target_id_number` 重复
- **字段1**: ID=1764836032902356, name="被核查人员身份证号码(带括号)", 关联模板数=0
- **字段2**: ID=1764836032913357, name="被核查人员身份证号", 关联模板数=9
**处理方案**
- 保留字段2ID=1764836032913357因为它关联了9个模板
- 删除字段1ID=1764836032902356因为它没有关联任何模板
#### 问题2: `target_organization_and_position` 重复
- **字段1**: ID=1764656917367205, name="被核查人员单位及职务", 关联模板数=4
- **字段2**: ID=1764836032734251, name="被核查人单位及职务", 关联模板数=9
**处理方案**
- 保留字段2ID=1764836032734251因为它关联了9个模板
- 删除字段1ID=1764656917367205并将它的4个关联关系迁移到保留字段
## 修复操作
### 1. 迁移关联关系
- 将删除字段的模板关联关系迁移到保留字段
- 如果保留字段已经关联了某个模板,则跳过重复关联
### 2. 删除无效字段
- 删除重复字段在 `f_polic_file_field` 表中的所有关联关系
- 删除重复字段本身
### 3. 验证结果
- ✅ 所有 `filed_code` 现在都是唯一的74个字段74个唯一编码
- ✅ 所有字段名称现在都是唯一的74个字段74个唯一名称
- ✅ 所有更改已提交到数据库
## 修复后的状态
### 字段统计
- **总字段数**: 74
- **唯一字段编码数**: 74
- **唯一字段名称数**: 74
### 未关联模板的字段
发现 38 个未关联任何模板的输出字段,这些字段可能是:
- 预留字段,供将来使用
- 已废弃但未删除的字段
- 特殊用途字段
这些字段不影响系统功能,可以保留或根据业务需求决定是否删除。
## 相关表结构
### f_polic_field字段定义表
- `id`: 字段ID主键
- `tenant_id`: 租户ID
- `name`: 字段名称
- `filed_code`: 字段编码(**现在唯一**
- `field_type`: 字段类型1=输入字段2=输出字段)
- `state`: 状态0=未启用1=启用)
### f_polic_file_field文件和字段关联表
- `file_id`: 文件配置ID关联 f_polic_file_config.id
- `filed_id`: 字段ID关联 f_polic_field.id
### f_polic_file_config文件模板配置表
- `id`: 文件配置ID主键
- `name`: 文件名称
- `file_path`: MinIO文件路径
- `state`: 状态0=未启用1=启用)
## 修复脚本
使用的修复脚本:
- `analyze_duplicate_fields.py`: 分析重复字段
- `fix_duplicate_fields.py`: 修复重复字段
- `verify_field_uniqueness.py`: 验证修复结果
## 注意事项
1. **数据完整性**:修复过程中已确保所有模板关联关系都迁移到保留字段,不会丢失数据
2. **唯一性约束**:虽然数据库表结构中没有对 `filed_code` 设置唯一约束,但通过修复已确保数据唯一性
3. **建议**:可以考虑在数据库层面为 `filed_code` 添加唯一索引,防止将来再次出现重复
## 建议的数据库优化
```sql
-- 为 filed_code 添加唯一索引(建议执行)
ALTER TABLE f_polic_field
ADD UNIQUE INDEX idx_tenant_filed_code (tenant_id, filed_code);
```
这样可以确保在数据库层面防止重复的 `filed_code`