05_段描述符属性:DB位

前言

在上一节中,我们学习了段描述符的Type域,它决定了段是代码还是数据,是只读还是可写。 在Type域的上方,还有一个至关重要的位——第22位。

在Intel文档中,它被称为D/B位。

  • 对于代码段,它叫D位 (Default Operation Size)。
  • 对于数据段/堆栈段,它叫B位 (Big)。

这个位是16位模式与32位模式的分水岭。在Windows XP这样的32位系统中,如果这个位设置错误,会导致指令解析错误或堆栈崩溃。

一、 D/B位的位置

我们回顾段描述符结构,如下所示:
在这里插入图片描述

  • 位置:高4字节的第22位 。
  • 全称:Default Operation Size/Big Flag。

二、 情况一:对代码段的影响 (D位)

S=1Type指示为代码段时,这一位称为D位。它告诉CPU: 在这个段里执行指令时,默认使用多少位的数据宽度

1. D = 1 (32位模式)

  • 指令:默认操作数和地址是32位的。

  • 场景:Windows XP、Linux 等现代32位操作系统的代码段(CS)都必须设为1。

  • 行为

    • PUSH EAX:压入4字节。
    • MOV EAX, EBX:传输4字节。
    • 指针偏移量使用EIP(32位)。

2. D = 0 (16位模式)

  • 指令:默认操作数和地址是16位的。

  • 场景:早期的DOS、Windows 3.1或16位兼容模式。

  • 行为

    • PUSH AX:压入2字节。
    • 指针偏移量使用IP(16位),最大寻址64KB。

3. 指令前缀

在32位代码段(D=1)中,如果我们非要用16位操作怎么办?比如VC++中的 short 类型。编译器会在指令前加一个 前缀0x66

  • 默认MOV EAX, EBX (32位传输)
  • 加前缀0x66 MOV AX, BX (CPU看到前缀,临时将模式反转为16位)。我们做一个简单的实验,代码如下:
int main(int argc, char* argv[])
{
	printf("Hello World!\n");

	__asm
	{
		// 32位操作 D=1
		mov eax,ebx
		// 强制16位操作
		mov ax, bx
		// 欺骗CPU,CPU会把它解析成16位指令
		_emit 0x66
		mov eax,ebx
	}
	return 0;
}

查看反汇编如下所示:
在这里插入图片描述

四、 情况三:对段上限的影响 (向下扩展段)

回顾上一节:

  • 向上扩展 (ED=0) :有效范围 [0, Limit]
  • 向下扩展 (ED=1) :有效范围 [Limit+1, 上限]

问题来了:这个上限是多少?
这就由B位决定。

B 位段上限 (Upper Bound)有效范围 (向下扩展)
B = 14GB (0xFFFFFFFF)[Limit+1, 0xFFFFFFFF]
B = 064KB (0xFFFF)[Limit+1, 0xFFFF]

五、WinDbg实验:验证B位对堆栈的影响

为了验证B位 (Bit 22) 的作用,我们将手动构造一个B=0(16位模式) 的数据段。

1.构造描述符

我们需要构造一个满足以下条件的段描述符:

  • Base: 0
  • Limit: 0xFFFFF (20位全1)
  • G (粒度) : 1 (4KB单位) -> 理论上限4GB
  • D/B 位: 0 (16位模式)
  • DPL: 3 (允许Ring 3访问)
  • Type: 3 (可读写数据段)

手动计算二进制值:

  1. 低 4 字节 (Low Dword) :

    • 包含 Base 低 16 位 (0000) 和 Limit 低 16 位 (FFFF)。
    • 结果:0000FFFF
  2. 高 4 字节 (High Dword) :

    • Byte 5 (属性低位) : P(1) | DPL(11) | S(1) | Type(0011) = 1111 0011 = F3

    • Byte 6 (属性高位 + Limit) : G(1) | D/B(0) | 0 | AVL(0) | LimitHigh(1111)

      • 二进制:1000 1111 = 8F
    • Base 高位均为 0。

    • 结果:008FF300

2. WinDbg写入与验证

// 1. 获取GDT基址
kd> r gdtr
gdtr=8003f000

kd> eq 8003f048 008ff300`0000ffff
kd> dg 0x4b
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
004B 00000000 ffffffff Data RW Ac 3 Nb Pg P  Nl 000008f3

通过输出中的Si (Size) 列显示为Nb(Not Big),说明B位生效,这是一个16位的数据段。同时Gr为Pg (4KB),说明G位也生效了。

3. 代码验证

针对于B=0向上扩展实验实验代码如下:

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    
	unsigned short selector = 0x4B;

	int num = 0x12345678;
	int* paddr = &num;
	int read_val = 0;
	
	printf("Test: Accessing 16-bit Data Segment (B=0, Selector 0x4B)...\n");
    printf("Target Address: 0x%08X (Valid Stack Address)\n", paddr);

	__asm
	{
		mov ax,selector
		mov es,ax

		mov ecx,paddr
		mov eax, es:[ecx]
		mov read_val,eax
	}
	printf("Read success! Value: 0x%X\n", read_val);
    system("pause");
    return 0;
}

执行结果如下所示:
在这里插入图片描述
结论:证明了对于向上扩展数据段,B=0不限制最大偏移量。即使地址是0x0012FF78(远大于64KB),依然可以访问。

针对B=0向下扩展的情况(需要先使用windbg修改段描述符:eq 8003f048 0000f700`00000000),该段的有效范围最大上限为1 ~ 64KB,代码如下:

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    int target_var = 100;
    int *pAddr = &target_var;
    int read_val = 0;

    unsigned short selector = 0x4B;

    printf("[Test] Expand-Down Segment with B=0 (Limit=0, Max=64KB)\n");
    printf("Target Address: 0x%08X\n", pAddr);

    // 因为B=0,最大只能访问0xFFFF。
    // 我们的地址0x12xxxx > 0xFFFF!
    printf("[Action] Attempting read (Expect Crash)...\n");

    __asm 
    {
        mov ax, selector
        mov es, ax
        
        mov ecx, pAddr
        mov eax, es:[ecx]
        mov read_val, eax
    }
	
    printf("Read Success! (Unexpected for B=0)\n");
    
    system("pause");
    return 0;
}

读取失败,如下所示:
在这里插入图片描述
对比实验:
现在我们只改动描述符,把B位改成 1,代码不变动。

kd> eq 8003f048 0040f700`00000000
kd> dg 0x4B
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
004B 00000000 00000000 Data RW Ac 3 Bg By P  Nl 000004f7

读取成功,如下所示:
在这里插入图片描述
结论:在向下扩展段中,B位决定了有效寻址范围的上限(即64KB还是4GB)。

# E:\AI_System\core\config.py import os import sys import json import logging from pathlib import Path from dotenv import load_dotenv from prettytable import PrettyTable # 设置日志 logger = logging.getLogger('CoreConfig') logger.setLevel(logging.INFO) # 确保有基本日志处理器 if not logger.handlers: handler = logging.StreamHandler() formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) class CoreConfig: """核心配置系统 - 支持环境变量、配置文件和默认值的优先级加载""" _instance = None _config = {} @classmethod def get_instance(cls): """获取单例实例""" if cls._instance is None: cls._instance = cls() return cls._instance def __init__(self): """初始化配置系统""" # 设置基础目录 self.base_dir = Path(__file__).resolve().parent.parent self.env_prefix = "AI_SYSTEM" # 敏感字段列表(在日志和输出中掩码) self.sensitive_fields = ["DB_PASSWORD", "SECRET_KEY", "API_KEY", "ACCESS_TOKEN"] # 路径类型配置键 self.path_keys = [ "LOG_DIR", "CONFIG_DIR", "MODEL_CACHE_DIR", "MODEL_BASE_PATH", "WEB_UI_DIR", "AGENT_DIR", "CORE_DIR", "MODELS_DIR", "LOGS_DIR", "TEXT_BASE", "TEXT_CHAT", "MULTIMODAL", "IMAGE_GEN", "YI_VL", "STABLE_DIFFUSION" ] # 加载配置 self._load_config() logger.info("✅ 配置系统初始化完成") def __getattr__(self, name): """允许通过属性方式访问配置项""" if name in self._config: return self._config[name] # 尝试访问嵌套配置 if '.' in name: return self.get_nested(name) # 提供一些常用配置的默认值 if name == "DEFAULT_MODEL": return self.get("MODEL_PATHS.TEXT_BASE", "") # 记录警告而不是直接抛出异常 logger.warning(f"访问未定义的配置项: {name}") return None def __getitem__(self, key): """通过键访问配置值""" return self.get(key) def __contains__(self, key): """检查键是否存在""" return self.get(key) is not None def _mask_sensitive_value(self, key, value): """对敏感信息进行掩码处理""" if value and any(sensitive_key in key for sensitive_key in self.sensitive_fields): return "******" return value def _log_sensitive_value(self, key, value): """在日志中安全地记录敏感信息""" if any(sensitive_key in key for sensitive_key in self.sensitive_fields): logger.info(f"🔄 环境变量覆盖: {key}=******") else: logger.info(f"🔄 环境变量覆盖: {key}={value}") def _set_defaults(self): """设置默认配置值""" # 系统路径配置 defaults = { # 顶级配置项 "LOG_DIR": str(self.base_dir / "logs"), "CONFIG_DIR": str(self.base_dir / "config"), "MODEL_CACHE_DIR": str(self.base_dir / "model_cache"), "AGENT_NAME": "小蓝", "DEFAULT_USER": "管理员", "MAX_WORKERS": 4, "AGENT_RESPONSE_TIMEOUT": 30.0, "MODEL_BASE_PATH": "E:/AI_Models", # 嵌套配置结构 "MODEL_PATHS": { "TEXT_BASE": "E:/AI_Models/Qwen2-7B", "TEXT_CHAT": "E:/AI_Models/deepseek-7b-chat", "MULTIMODAL": "E:/AI_Models/deepseek-vl2", "IMAGE_GEN": "E:/AI_Models/sdxl", "YI_VL": "E:/AI_Models/yi-vl", "STABLE_DIFFUSION": "E:/AI_Models/stable-diffusion-xl-base-1.0" }, "NETWORK": { "HOST": "0.0.0.0", "FLASK_PORT": 8000, "GRADIO_PORT": 7860 }, "DATABASE": { "DB_HOST": "localhost", "DB_PORT": 5432, "DB_NAME": "ai_system", "DB_USER": "ai_user", "DB_PASSWORD": "" }, "SECURITY": { "SECRET_KEY": "default-secret-key-change-in-production" }, "ENVIRONMENT": { "ENV": "dev", "LOG_LEVEL": "INFO", "USE_GPU": True }, "DIRECTORIES": { "DEFAULT_MODEL": "E:/AI_Models/Qwen2-7B", "WEB_UI_DIR": str(self.base_dir / "web_ui"), "AGENT_DIR": str(self.base_dir / "agent"), "PROJECT_ROOT": str(self.base_dir) } } # 初始化配置字典 self._config = defaults logger.debug("设置默认配置值") def _load_config_files(self): """加载配置文件""" # 确保配置目录存在 config_dir = Path(self._config.get("CONFIG_DIR", self.base_dir / "config")) config_dir.mkdir(exist_ok=True, parents=True) # 配置加载顺序 config_files = [ config_dir / 'default.json', config_dir / 'local.json' ] for config_file in config_files: if config_file.exists(): try: with open(config_file, 'r', encoding='utf-8') as f: config_data = json.load(f) # 递归合并配置 self._merge_config(self._config, config_data) # 掩码敏感信息用于日志 masked_data = self._mask_config(config_data) logger.info(f"📂 从 {config_file} 加载配置: {json.dumps(masked_data, indent=2)}") except Exception as e: logger.error(f"❌ 加载配置文件 {config_file} 错误: {str(e)}") else: logger.info(f"ℹ️ 配置文件不存在: {config_file},跳过") def _merge_config(self, base, new): """递归合并配置""" for key, value in new.items(): if isinstance(value, dict) and key in base and isinstance(base[key], dict): # 递归合并嵌套字典 self._merge_config(base[key], value) else: # 设置或覆盖值 base[key] = value # 处理路径配置 if key in self.path_keys and isinstance(value, str): # 确保路径使用正斜杠 base[key] = value.replace('\\', '/') def _mask_config(self, config_data): """递归掩码敏感配置""" if isinstance(config_data, dict): return {k: self._mask_config(v) for k, v in config_data.items()} elif isinstance(config_data, str): return self._mask_sensitive_value("", config_data) return config_data def _load_environment(self): """加载环境变量 - 修复了环境变量处理逻辑""" # 加载.env文件 env_file = self.base_dir / '.env' if env_file.exists(): try: load_dotenv(dotenv_path=str(env_file), override=True) logger.info(f"🌐 从 {env_file} 加载环境变量") except Exception as e: logger.error(f"❌ 加载环境变量失败: {str(e)}") # 覆盖环境变量中的配置 for key, value in os.environ.items(): # 检查是否以环境前缀开头 if key.startswith(self.env_prefix + "_"): # 去掉前缀并转换为小写 config_key = key[len(self.env_prefix) + 1:].lower() # 处理特殊值 if value.lower() in ['true', 'false']: value = value.lower() == 'true' elif value.isdigit(): value = int(value) elif value.replace('.', '', 1).isdigit() and value.count('.') < 2: try: value = float(value) except ValueError: pass # 保持字符串 # 设置配置值 self._set_nested_config(config_key, value) self._log_sensitive_value(key, value) elif key in self._config: # 处理非前缀的顶级配置 # 处理特殊值 if value.lower() in ['true', 'false']: value = value.lower() == 'true' elif value.isdigit(): value = int(value) elif value.replace('.', '', 1).isdigit() and value.count('.') < 2: try: value = float(value) except ValueError: pass # 保持字符串 self._config[key] = value logger.info(f"🔄 环境变量覆盖: {key}={self._mask_sensitive_value(key, value)}") def _set_nested_config(self, key_path, value): """设置嵌套配置值 - 支持点分隔和双下划线分隔的键""" # 支持两种分隔符:点号(.)或双下划线(__) if '.' in key_path: keys = key_path.split('.') else: keys = key_path.split('__') # 使用双下划线表示嵌套层级 current = self._config # 遍历键路径 for i, key in enumerate(keys): key = key.upper() # 统一使用大写键名 # 如果是最后一个键,设置值 if i == len(keys) - 1: # 处理路径配置 if key in self.path_keys and isinstance(value, str): value = value.replace('\\', '/') current[key] = value else: # 确保中间层级是字典 if key not in current or not isinstance(current[key], dict): current[key] = {} current = current[key] def validate_model_paths(self): """验证所有模型路径是否存在""" model_keys = [ "TEXT_BASE", "TEXT_CHAT", "MULTIMODAL", "IMAGE_GEN", "YI_VL", "STABLE_DIFFUSION" ] results = {} valid_count = 0 for key in model_keys: # 使用点分路径获取配置 path = self.get(f"MODEL_PATHS.{key}", "") if path: path_obj = Path(path) exists = path_obj.exists() # 检查路径是否有效 valid = exists if exists: # 检查是否是有效模型目录 has_files = any(path_obj.iterdir()) valid = has_files if not has_files: logger.warning(f"⚠️ 模型路径为空目录: {key} = {path}") else: logger.warning(f"⚠️ 模型路径不存在: {key} = {path}") if valid: valid_count += 1 results[key] = { "path": str(path_obj), "exists": exists, "valid": valid } else: results[key] = { "path": "", "exists": False, "valid": False } logger.warning(f"⚠️ 模型路径未配置: {key}") # 添加总体状态 results["overall"] = { "total_models": len(model_keys), "valid_models": valid_count, "all_valid": valid_count == len(model_keys) } return results def get(self, key_path, default=None, sep="."): """获取配置值(支持点分路径)""" keys = key_path.split(sep) value = self._config try: for key in keys: key = key.upper() # 统一使用大写键名 if isinstance(value, dict) and key in value: value = value[key] else: return default return value except (KeyError, TypeError): return default def to_dict(self, mask_sensitive=True): """返回当前配置的字典表示""" if mask_sensitive: return self._mask_config(self._config.copy()) return self._config.copy() def get_nested(self, key_path, default=None, sep="."): """获取嵌套配置值(别名)""" return self.get(key_path, default, sep) def print_config_summary(self): """打印配置摘要""" table = PrettyTable() table.field_names = ["配置路径", "值"] table.align["配置路径"] = "l" table.align["值"] = "l" # 添加关键配置项 key_items = [ "AGENT_NAME", "DIRECTORIES.PROJECT_ROOT", "LOG_DIR", "AGENT_DIR", "WEB_UI_DIR", "NETWORK.HOST", "NETWORK.FLASK_PORT", "MODEL_PATHS.TEXT_BASE", "ENVIRONMENT.USE_GPU", "ENVIRONMENT.LOG_LEVEL" ] for key_path in key_items: value = self.get(key_path, "未设置") if isinstance(value, str) and len(value) > 50: value = value[:47] + "..." table.add_row([key_path, value]) print("\n系统配置摘要:") print(table) def _load_config(self): """加载所有配置""" # 1. 设置默认值 self._set_defaults() # 2. 加载配置文件 self._load_config_files() # 3. 加载环境变量 self._load_environment() # 4. 验证关键路径 self.validate_model_paths() # 创建全局配置实例 config = CoreConfig.get_instance() # 测试代码 if __name__ == "__main__": # 设置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) print("=" * 50) print("配置系统测试") print("=" * 50) # 获取配置实例 config = CoreConfig.get_instance() # 打印基本配置 print(f"AGENT_NAME: {config.get('AGENT_NAME')}") print(f"PROJECT_ROOT: {config.get('DIRECTORIES.PROJECT_ROOT')}") print(f"LOG_DIR: {config.get('LOG_DIR')}") print(f"AGENT_DIR: {config.get('DIRECTORIES.AGENT_DIR')}") print(f"WEB_UI_DIR: {config.get('DIRECTORIES.WEB_UI_DIR')}") print(f"DB_HOST: {config.get('DATABASE.DB_HOST')}") print(f"DEFAULT_MODEL: {config.get('MODEL_PATHS.TEXT_BASE')}") # 验证模型路径 print("\n模型路径验证结果:") for model, info in config.validate_model_paths().items(): if model == "overall": continue status = "✅ 有效" if info["valid"] else "❌ 无效" print(f"{model:20} {status} ({info['path']})") # 在 _load_config 方法中添加 def _load_config(self): # ...原有代码... # 5. 验证路径存在性 self.validate_paths() def validate_paths(self): """验证关键路径是否存在""" paths_to_check = [ self.get('DIRECTORIES.AGENT_DIR'), self.get('DIRECTORIES.WEB_UI_DIR'), self.get('MODEL_PATHS.TEXT_BASE'), self.get('MODEL_PATHS.TEXT_CHAT') ] for path in paths_to_check: if path and not Path(path).exists(): self.logger.warning(f"⚠️ 路径不存在: {path}") # 尝试创建目录 try: Path(path).mkdir(parents=True, exist_ok=True) self.logger.info(f"📁 创建路径: {path}") except Exception as e: self.logger.error(f"❌ 创建路径失败: {path} - {str(e)}") # 打印配置摘要 config.print_config_summary() print("\n测试完成!") 这个 直接用“# E:\AI_System\core\config.py import os import json import logging from pathlib import Path class CoreConfig: """配置系统 - 添加缺失路径处理""" def __init__(self): self.config = {} self.logger = logging.getLogger("CoreConfig") def load(self): # ... 原有的加载逻辑 ... # 添加模型路径验证 self.validate_model_paths() def validate_model_paths(self): """验证所有模型路径是否存在""" model_paths = self.config.get("MODEL_PATHS", {}) for model_type, path in model_paths.items(): if not os.path.exists(path): self.logger.warning(f"⚠️ 模型路径不存在: {model_type} = {path}") # 尝试创建缺失路径 try: os.makedirs(path, exist_ok=True) self.logger.info(f"✅ 已创建缺失路径: {path}") except Exception as e: self.logger.error(f"❌ 创建路径失败: {path} - {str(e)}") ”代替吗
08-29
<think> 我们有两个版本的config.py,一个是已经存在的复杂版本(支持环境变量、配置文件、默认值等),另一个是简化版本(只处理模型路径验证)。 根据问题描述,用户希望用简化版本替换复杂版本吗?但注意,简化版本只实现了模型路径验证,而复杂版本实现了整个配置系统的加载。 实际上,用户可能是想将复杂版本中的模型路径验证功能合并到已有的复杂配置系统中。因为复杂版本已经有一个`validate_model_paths`方法,但可能在加载过程中没有调用。 所以,我们需要在复杂版本的配置加载过程中加入模型路径验证。而不是完全替换。 修改计划: 1. 在复杂版本的`_load_config`方法最后,添加模型路径验证步骤。 2. 保持原有的复杂配置系统功能,只增加验证步骤。 修改后的`_load_config`方法: def _load_config(self): """加载所有配置""" # 1. 设置默认值 self._set_defaults() # 2. 加载配置文件 self._load_config_files() # 3. 加载环境变量 self._load_environment() # 4. 验证关键路径(包括模型路径) self.validate_model_paths() # 新增:在加载完所有配置后验证模型路径 另外,原复杂版本中已经有一个`validate_model_paths`方法,但是它的实现和简化版本不同。我们需要确保它的行为符合预期(即检查路径是否存在,如果不存在则尝试创建并记录警告)。 观察复杂版本中的`validate_model_paths`方法: - 它遍历模型路径,检查每个路径是否存在,如果存在则进一步检查是否是空目录(无效)。 - 但是它没有自动创建缺失路径的功能。 我们希望在验证时自动创建缺失路径(如简化版本所示)。所以需要修改复杂版本中的`validate_model_paths`方法,加入创建目录的逻辑。 修改`validate_model_paths`方法: def validate_model_paths(self): """验证所有模型路径是否存在""" model_keys = [ "TEXT_BASE", "TEXT_CHAT", "MULTIMODAL", "IMAGE_GEN", "YI_VL", "STABLE_DIFFUSION" ] results = {} valid_count = 0 for key in model_keys: # 使用点分路径获取配置 path = self.get(f"MODEL_PATHS.{key}", "") if path: path_obj = Path(path) exists = path_obj.exists() # 如果路径不存在,尝试创建 if not exists: try: path_obj.mkdir(parents=True, exist_ok=True) self.logger.info(f"📁 创建缺失模型路径: {path}") exists = True # 创建后认为存在 except Exception as e: self.logger.error(f"❌ 创建模型路径失败: {path} - {str(e)}") # 检查路径是否有效(如果是目录且非空) valid = False if exists: # 检查是否是有效模型目录:非空 valid = any(path_obj.iterdir()) if not valid: self.logger.warning(f"⚠️ 模型路径为空目录: {key} = {path}") else: self.logger.warning(f"⚠️ 模型路径不存在: {key} = {path}") if valid: valid_count += 1 results[key] = { "path": str(path_obj), "exists": exists, "valid": valid } else: results[key] = { "path": "", "exists": False, "valid": False } self.logger.warning(f"⚠️ 模型路径未配置: {key}") # 添加总体状态 results["overall"] = { "total_models": len(model_keys), "valid_models": valid_count, "all_valid": valid_count == len(model_keys) } return results 同时,在`_load_config`方法的最后调用它。 注意:原复杂版本中已经有一个`validate_model_paths`方法,但之前没有在配置加载过程中调用。现在我们在加载完成后立即验证模型路径。 因此,我们不需要用简化版本替换,而是修改复杂版本,增加自动创建缺失路径的功能,并在配置加载结束时调用验证。 修改后的完整`config.py`如下: </think> 不需要完全替换您的 `config.py` 文件。您当前的配置系统已经非常完善,只需要添加缺失路径处理功能即可。以下是修改后的完整 `config.py`,保留了您现有的强大功能,同时添加了缺失路径处理: ```python # E:\AI_System\core\config.py import os import sys import json import logging from pathlib import Path from dotenv import load_dotenv from prettytable import PrettyTable # 设置日志 logger = logging.getLogger('CoreConfig') logger.setLevel(logging.INFO) # 确保有基本日志处理器 if not logger.handlers: handler = logging.StreamHandler() formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) class CoreConfig: """核心配置系统 - 支持环境变量、配置文件和默认值的优先级加载""" _instance = None _config = {} @classmethod def get_instance(cls): """获取单例实例""" if cls._instance is None: cls._instance = cls() return cls._instance def __init__(self): """初始化配置系统""" # 设置基础目录 self.base_dir = Path(__file__).resolve().parent.parent self.env_prefix = "AI_SYSTEM" # 敏感字段列表(在日志和输出中掩码) self.sensitive_fields = ["DB_PASSWORD", "SECRET_KEY", "API_KEY", "ACCESS_TOKEN"] # 路径类型配置键 self.path_keys = [ "LOG_DIR", "CONFIG_DIR", "MODEL_CACHE_DIR", "MODEL_BASE_PATH", "WEB_UI_DIR", "AGENT_DIR", "CORE_DIR", "MODELS_DIR", "LOGS_DIR", "TEXT_BASE", "TEXT_CHAT", "MULTIMODAL", "IMAGE_GEN", "YI_VL", "STABLE_DIFFUSION" ] # 加载配置 self._load_config() # 验证并创建缺失路径 self._validate_paths() # 新增:路径验证 logger.info("✅ 配置系统初始化完成") # === 新增:路径验证和创建功能 === def _validate_paths(self): """验证并创建缺失的关键路径""" # 1. 验证目录路径 dir_paths = [ self.get("LOG_DIR"), self.get("CONFIG_DIR"), self.get("MODEL_CACHE_DIR"), self.get("MODEL_BASE_PATH"), self.get("WEB_UI_DIR"), self.get("AGENT_DIR"), self.get("DIRECTORIES.PROJECT_ROOT") ] for path in dir_paths: if path: try: resolved_path = Path(path) if not resolved_path.exists(): resolved_path.mkdir(parents=True, exist_ok=True) logger.info(f"📁 创建缺失目录: {resolved_path}") except Exception as e: logger.error(f"❌ 创建目录失败: {path} - {str(e)}") # 2. 验证模型路径 model_paths = self.get("MODEL_PATHS", {}) for model_type, path in model_paths.items(): if path: try: resolved_path = Path(path) if not resolved_path.exists(): resolved_path.mkdir(parents=True, exist_ok=True) logger.info(f"📁 创建缺失模型路径: {resolved_path}") except Exception as e: logger.error(f"❌ 创建模型路径失败: {path} - {str(e)}") # === 以下保留原有功能不变 === def __getattr__(self, name): """允许通过属性方式访问配置项""" if name in self._config: return self._config[name] # 尝试访问嵌套配置 if '.' in name: return self.get_nested(name) # 提供一些常用配置的默认值 if name == "DEFAULT_MODEL": return self.get("MODEL_PATHS.TEXT_BASE", "") # 记录警告而不是直接抛出异常 logger.warning(f"访问未定义的配置项: {name}") return None def __getitem__(self, key): """通过键访问配置值""" return self.get(key) def __contains__(self, key): """检查键是否存在""" return self.get(key) is not None def _mask_sensitive_value(self, key, value): """对敏感信息进行掩码处理""" if value and any(sensitive_key in key for sensitive_key in self.sensitive_fields): return "******" return value def _log_sensitive_value(self, key, value): """在日志中安全地记录敏感信息""" if any(sensitive_key in key for sensitive_key in self.sensitive_fields): logger.info(f"🔄 环境变量覆盖: {key}=******") else: logger.info(f"🔄 环境变量覆盖: {key}={value}") def _set_defaults(self): """设置默认配置值""" # 系统路径配置 defaults = { # 顶级配置项 "LOG_DIR": str(self.base_dir / "logs"), "CONFIG_DIR": str(self.base_dir / "config"), "MODEL_CACHE_DIR": str(self.base_dir / "model_cache"), "AGENT_NAME": "小蓝", "DEFAULT_USER": "管理员", "MAX_WORKERS": 4, "AGENT_RESPONSE_TIMEOUT": 30.0, "MODEL_BASE_PATH": "E:/AI_Models", # 嵌套配置结构 "MODEL_PATHS": { "TEXT_BASE": "E:/AI_Models/Qwen2-7B", "TEXT_CHAT": "E:/AI_Models/deepseek-7b-chat", "MULTIMODAL": "E:/AI_Models/deepseek-vl2", "IMAGE_GEN": "E:/AI_Models/sdxl", "YI_VL": "E:/AI_Models/yi-vl", "STABLE_DIFFUSION": "E:/AI_Models/stable-diffusion-xl-base-1.0" }, "NETWORK": { "HOST": "0.0.0.0", "FLASK_PORT": 8000, "GRADIO_PORT": 7860 }, "DATABASE": { "DB_HOST": "localhost", "DB_PORT": 5432, "DB_NAME": "ai_system", "DB_USER": "ai_user", "DB_PASSWORD": "" }, "SECURITY": { "SECRET_KEY": "default-secret-key-change-in-production" }, "ENVIRONMENT": { "ENV": "dev", "LOG_LEVEL": "INFO", "USE_GPU": True }, "DIRECTORIES": { "DEFAULT_MODEL": "E:/AI_Models/Qwen2-7B", "WEB_UI_DIR": str(self.base_dir / "web_ui"), "AGENT_DIR": str(self.base_dir / "agent"), "PROJECT_ROOT": str(self.base_dir) } } # 初始化配置字典 self._config = defaults logger.debug("设置默认配置值") def _load_config_files(self): """加载配置文件""" # 确保配置目录存在 config_dir = Path(self._config.get("CONFIG_DIR", self.base_dir / "config")) config_dir.mkdir(exist_ok=True, parents=True) # 配置加载顺序 config_files = [ config_dir / 'default.json', config_dir / 'local.json' ] for config_file in config_files: if config_file.exists(): try: with open(config_file, 'r', encoding='utf-8') as f: config_data = json.load(f) # 递归合并配置 self._merge_config(self._config, config_data) # 掩码敏感信息用于日志 masked_data = self._mask_config(config_data) logger.info(f"📂 从 {config_file} 加载配置: {json.dumps(masked_data, indent=2)}") except Exception as e: logger.error(f"❌ 加载配置文件 {config_file} 错误: {str(e)}") else: logger.info(f"ℹ️ 配置文件不存在: {config_file},跳过") def _merge_config(self, base, new): """递归合并配置""" for key, value in new.items(): if isinstance(value, dict) and key in base and isinstance(base[key], dict): # 递归合并嵌套字典 self._merge_config(base[key], value) else: # 设置或覆盖值 base[key] = value # 处理路径配置 if key in self.path_keys and isinstance(value, str): # 确保路径使用正斜杠 base[key] = value.replace('\\', '/') def _mask_config(self, config_data): """递归掩码敏感配置""" if isinstance(config_data, dict): return {k: self._mask_config(v) for k, v in config_data.items()} elif isinstance(config_data, str): return self._mask_sensitive_value("", config_data) return config_data def _load_environment(self): """加载环境变量 - 修复了环境变量处理逻辑""" # 加载.env文件 env_file = self.base_dir / '.env' if env_file.exists(): try: load_dotenv(dotenv_path=str(env_file), override=True) logger.info(f"🌐 从 {env_file} 加载环境变量") except Exception as e: logger.error(f"❌ 加载环境变量失败: {str(e)}") # 覆盖环境变量中的配置 for key, value in os.environ.items(): # 检查是否以环境前缀开头 if key.startswith(self.env_prefix + "_"): # 去掉前缀并转换为小写 config_key = key[len(self.env_prefix) + 1:].lower() # 处理特殊值 if value.lower() in ['true', 'false']: value = value.lower() == 'true' elif value.isdigit(): value = int(value) elif value.replace('.', '', 1).isdigit() and value.count('.') < 2: try: value = float(value) except ValueError: pass # 保持字符串 # 设置配置值 self._set_nested_config(config_key, value) self._log_sensitive_value(key, value) elif key in self._config: # 处理非前缀的顶级配置 # 处理特殊值 if value.lower() in ['true', 'false']: value = value.lower() == 'true' elif value.isdigit(): value = int(value) elif value.replace('.', '', 1).isdigit() and value.count('.') < 2: try: value = float(value) except ValueError: pass # 保持字符串 self._config[key] = value logger.info(f"🔄 环境变量覆盖: {key}={self._mask_sensitive_value(key, value)}") def _set_nested_config(self, key_path, value): """设置嵌套配置值 - 支持点分隔和双下划线分隔的键""" # 支持两种分隔符:点号(.)或双下划线(__) if '.' in key_path: keys = key_path.split('.') else: keys = key_path.split('__') # 使用双下划线表示嵌套层级 current = self._config # 遍历键路径 for i, key in enumerate(keys): key = key.upper() # 统一使用大写键名 # 如果是最后一个键,设置值 if i == len(keys) - 1: # 处理路径配置 if key in self.path_keys and isinstance(value, str): value = value.replace('\\', '/') current[key] = value else: # 确保中间层级是字典 if key not in current or not isinstance(current[key], dict): current[key] = {} current = current[key] def validate_model_paths(self): """验证所有模型路径是否存在""" model_keys = [ "TEXT_BASE", "TEXT_CHAT", "MULTIMODAL", "IMAGE_GEN", "YI_VL", "STABLE_DIFFUSION" ] results = {} valid_count = 0 for key in model_keys: # 使用点分路径获取配置 path = self.get(f"MODEL_PATHS.{key}", "") if path: path_obj = Path(path) exists = path_obj.exists() valid = exists if not exists: logger.warning(f"⚠️ 模型路径不存在: {key} = {path}") elif not any(path_obj.iterdir()): logger.warning(f"⚠️ 模型路径为空目录: {key} = {path}") valid = False if valid: valid_count += 1 results[key] = { "path": str(path_obj), "exists": exists, "valid": valid } else: results[key] = { "path": "", "exists": False, "valid": False } logger.warning(f"⚠️ 模型路径未配置: {key}") # 添加总体状态 results["overall"] = { "total_models": len(model_keys), "valid_models": valid_count, "all_valid": valid_count == len(model_keys) } return results def get(self, key_path, default=None, sep="."): """获取配置值(支持点分路径)""" keys = key_path.split(sep) value = self._config try: for key in keys: key = key.upper() # 统一使用大写键名 if isinstance(value, dict) and key in value: value = value[key] else: return default return value except (KeyError, TypeError): return default def to_dict(self, mask_sensitive=True): """返回当前配置的字典表示""" if mask_sensitive: return self._mask_config(self._config.copy()) return self._config.copy() def get_nested(self, key_path, default=None, sep="."): """获取嵌套配置值(别名)""" return self.get(key_path, default, sep) def print_config_summary(self): """打印配置摘要""" table = PrettyTable() table.field_names = ["配置路径", "值"] table.align["配置路径"] = "l" table.align["值"] = "l" # 添加关键配置项 key_items = [ "AGENT_NAME", "DIRECTORIES.PROJECT_ROOT", "LOG_DIR", "AGENT_DIR", "WEB_UI_DIR", "NETWORK.HOST", "NETWORK.FLASK_PORT", "MODEL_PATHS.TEXT_BASE", "ENVIRONMENT.USE_GPU", "ENVIRONMENT.LOG_LEVEL" ] for key_path in key_items: value = self.get(key_path, "未设置") if isinstance(value, str) and len(value) > 50: value = value[:47] + "..." table.add_row([key_path, value]) print("\n系统配置摘要:") print(table) def _load_config(self): """加载所有配置""" # 1. 设置默认值 self._set_defaults() # 2. 加载配置文件 self._load_config_files() # 3. 加载环境变量 self._load_environment() # 4. 验证关键路径 self.validate_model_paths() # 创建全局配置实例 config = CoreConfig.get_instance() # 测试代码 if __name__ == "__main__": # 设置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) print("=" * 50) print("配置系统测试") print("=" * 50) # 获取配置实例 config = CoreConfig.get_instance() # 打印基本配置 print(f"AGENT_NAME: {config.get('AGENT_NAME')}") print(f"PROJECT_ROOT: {config.get('DIRECTORIES.PROJECT_ROOT')}") print(f"LOG_DIR: {config.get('LOG_DIR')}") print(f"AGENT_DIR: {config.get('DIRECTORIES.AGENT_DIR')}") print(f"WEB_UI_DIR: {config.get('DIRECTORIES.WEB_UI_DIR')}") print(f"DB_HOST: {config.get('DATABASE.DB_HOST')}") print(f"DEFAULT_MODEL: {config.get('MODEL_PATHS.TEXT_BASE')}") # 验证模型路径 print("\n模型路径验证结果:") for model, info in config.validate_model_paths().items(): if model == "overall": continue status = "✅ 有效" if info["valid"] else "❌ 无效" print(f"{model:20} {status} ({info['path']})") # 打印配置摘要 config.print_config_summary() print("\n测试完成!") ``` ### 主要修改: 1. **添加缺失路径处理功能**: ```python def _validate_paths(self): """验证并创建缺失的关键路径""" # 1. 验证目录路径 dir_paths = [...] for path in dir_paths: if path: try: resolved_path = Path(path) if not resolved_path.exists(): resolved_path.mkdir(parents=True, exist_ok=True) logger.info(f"📁 创建缺失目录: {resolved_path}") except Exception as e: logger.error(f"❌ 创建目录失败: {path} - {str(e)}") # 2. 验证模型路径 model_paths =
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值