import json import logging from util.mysql_utils import mysql_client # 配置日志 logger = logging.getLogger(__name__) class GraphStyleService: @staticmethod def _get_or_create_group(group_name: str) -> int: """内部辅助方法:获取或创建方案组 ID""" if not group_name or group_name.strip() == "": group_name = "默认方案" group_name = group_name.strip() # 1. 查询是否存在 check_sql = "SELECT id FROM graph_style_groups WHERE group_name = %s LIMIT 1" existing = mysql_client.execute_query(check_sql, (group_name,)) if existing: return int(existing[0]['id']) # 2. 不存在则插入 insert_sql = "INSERT INTO graph_style_groups (group_name, is_active, is_default) VALUES (%s, %s, %s)" mysql_client.execute_update(insert_sql, (group_name, False, False)) # 3. 获取新生成的 ID final_check = mysql_client.execute_query(check_sql, (group_name,)) return int(final_check[0]['id']) if final_check else 1 @staticmethod def create_config(canvas_name: str, current_label: str, styles_dict: dict, group_name: str = None) -> bool: """【纯新增】用于另存为或初始保存""" config_json = json.dumps(styles_dict, ensure_ascii=False) target_group_id = GraphStyleService._get_or_create_group(group_name) sql = """ INSERT INTO graph_configs (canvas_name, current_label, config_json, group_id) VALUES (%s, %s, %s, %s) """ affected_rows = mysql_client.execute_update(sql, (canvas_name, current_label, config_json, target_group_id)) return affected_rows > 0 @staticmethod def update_config(config_id: int, canvas_name: str, current_label: str, styles_dict: dict, group_name: str = None, is_auto_save: bool = False, target_group_id: int = None) -> bool: """ 核心更新逻辑:支持精准 ID 移动,优化了逻辑优先级判断 """ if not config_id: logger.error("更新失败:缺少 config_id") return False config_json_str = json.dumps(styles_dict, ensure_ascii=False) try: # --- 步骤 1:查询当前数据库状态 --- curr_sql = "SELECT group_id, canvas_name, current_label, config_json FROM graph_configs WHERE id = %s" current_data = mysql_client.execute_query(curr_sql, (config_id,)) if not current_data: logger.warning(f"更新失败:找不到 ID 为 {config_id} 的配置") return False curr_row = current_data[0] old_group_id = int(curr_row['group_id']) # --- 步骤 2:确定目标组 ID (调整优先级) --- # 优先级 1: 只要传了 target_group_id,就说明是移动操作,优先级最高 if target_group_id is not None: final_group_id = int(target_group_id) logger.info(f"【移动模式】配置 {config_id}: 强制设定目标组 ID 为 {final_group_id}") # 优先级 2: 自动保存模式下,锁定 group_id 不允许变动 elif is_auto_save: final_group_id = old_group_id logger.debug(f"【自保模式】配置 {config_id}: 锁定原组 ID {final_group_id}") # 优先级 3: 传了 group_name 但没传 target_group_id (旧版移动逻辑) elif group_name: final_group_id = GraphStyleService._get_or_create_group(group_name) logger.info(f"【名称模式】配置 {config_id}: 根据名称 [{group_name}] 获得 ID {final_group_id}") # 兜底:保持不变 else: final_group_id = old_group_id # --- 步骤 3:差异比对 --- # 增加对数据一致性的判定 has_changed = ( int(final_group_id) != old_group_id or canvas_name != curr_row['canvas_name'] or current_label != curr_row['current_label'] or config_json_str != curr_row['config_json'] ) if not has_changed: logger.info( f"配置 {config_id} 内容无变化 (最终目标ID:{final_group_id}, 原ID:{old_group_id}),跳过数据库更新") return True # --- 步骤 4:执行更新 --- sql = """ UPDATE graph_configs SET group_id = %s, canvas_name = %s, current_label = %s, config_json = %s WHERE id = %s """ params = (final_group_id, canvas_name, current_label, config_json_str, config_id) affected_rows = mysql_client.execute_update(sql, params) if affected_rows > 0: logger.info(f"更新成功,ID: {config_id}, 归属组已变更为: {final_group_id}") return True else: logger.error(f"数据库更新执行成功但受影响行数为 0,ID: {config_id}") return False except Exception as e: logger.error(f"Service 层更新异常: {str(e)}", exc_info=True) return False @staticmethod def get_grouped_configs() -> list: """获取嵌套结构的方案列表""" groups_sql = "SELECT id, group_name, is_active, is_default FROM graph_style_groups ORDER BY is_default DESC, id ASC" groups = mysql_client.execute_query(groups_sql) or [] configs_sql = "SELECT id, group_id, canvas_name, current_label, config_json, create_time FROM graph_configs" configs = mysql_client.execute_query(configs_sql) or [] # 格式化配置数据 for conf in configs: conf['styles'] = json.loads(conf['config_json']) if conf.get('config_json') else {} # 保持 key 简洁 if 'config_json' in conf: del conf['config_json'] if conf.get('create_time') and not isinstance(conf['create_time'], str): conf['create_time'] = conf['create_time'].strftime('%Y-%m-%d %H:%M:%S') # 组装嵌套结构 for g in groups: g['is_active'] = bool(g['is_active']) g['is_default'] = bool(g['is_default']) g['configs'] = [c for c in configs if c['group_id'] == g['id']] g['expanded'] = g['is_active'] return groups @staticmethod def get_active_configs() -> list: """ 获取 is_active = 1 的方案组及其配置项 返回长度为 0 或 1 的列表(因为通常只有一个激活组) """ # 1. 查询 is_active = 1 的组(按 id 排序,取第一个以防有多个) group_sql = """ SELECT id, group_name, is_active, is_default FROM graph_style_groups WHERE is_active = 1 ORDER BY id ASC LIMIT 1 """ groups = mysql_client.execute_query(group_sql) or [] if not groups: return [] # 没有激活的组 group = groups[0] group_id = group['id'] # 2. 查询该组下的所有配置 configs_sql = """ SELECT id, group_id, canvas_name, current_label, config_json, create_time FROM graph_configs WHERE group_id = %s """ configs = mysql_client.execute_query(configs_sql, (group_id,)) or [] # 3. 处理配置项 for conf in configs: if conf.get('config_json'): try: conf['styles'] = json.loads(conf['config_json']) except (TypeError, ValueError): conf['styles'] = {} del conf['config_json'] if conf.get('create_time') and not isinstance(conf['create_time'], str): conf['create_time'] = conf['create_time'].strftime('%Y-%m-%d %H:%M:%S') # 4. 组装结果 group['configs'] = configs return [group] # 返回单元素列表,保持接口兼容性 @staticmethod def apply_group_all(group_id: int) -> bool: """切换当前激活的方案组""" try: # 重置所有组的激活状态 mysql_client.execute_update("UPDATE graph_style_groups SET is_active = %s", (False,)) # 激活目标组 affected_rows = mysql_client.execute_update( "UPDATE graph_style_groups SET is_active = %s WHERE id = %s", (True, group_id) ) return affected_rows > 0 except Exception as e: logger.error(f"应用全案异常: {str(e)}") return False @staticmethod def set_default_group(group_id: int) -> bool: """设为默认方案组""" try: mysql_client.execute_update("UPDATE graph_style_groups SET is_default = %s", (False,)) affected_rows = mysql_client.execute_update( "UPDATE graph_style_groups SET is_default = %s WHERE id = %s", (True, group_id) ) return affected_rows > 0 except Exception as e: logger.error(f"设置默认方案异常: {str(e)}") return False @staticmethod def delete_group(group_id: int) -> bool: """级联删除组及其下的所有配置""" try: # 先删配置,再删组(如果没设外键级联) mysql_client.execute_update("DELETE FROM graph_configs WHERE group_id = %s", (group_id,)) affected_rows = mysql_client.execute_update("DELETE FROM graph_style_groups WHERE id = %s", (group_id,)) return affected_rows > 0 except Exception as e: logger.error(f"删除方案组异常: {str(e)}") return False @staticmethod def delete_config(config_id: int) -> bool: """删除单个配置""" try: affected_rows = mysql_client.execute_update("DELETE FROM graph_configs WHERE id = %s", (config_id,)) return affected_rows > 0 except Exception as e: logger.error(f"删除配置异常: {str(e)}") return False @staticmethod def batch_delete_configs(config_ids: list) -> int: """批量删除""" if not config_ids: return 0 try: placeholders = ', '.join(['%s'] * len(config_ids)) sql = f"DELETE FROM graph_configs WHERE id IN ({placeholders})" return mysql_client.execute_update(sql, tuple(config_ids)) except Exception as e: logger.error(f"批量删除异常: {str(e)}") return 0 @staticmethod def get_group_list() -> list: """简单的方案名称列表""" sql = "SELECT id, group_name, is_active, is_default FROM graph_style_groups ORDER BY is_default DESC, id DESC" return mysql_client.execute_query(sql) or []