diff --git a/docs/DATABASE_ARCHITECTURE.md b/docs/DATABASE_ARCHITECTURE.md new file mode 100644 index 0000000..86fb96f --- /dev/null +++ b/docs/DATABASE_ARCHITECTURE.md @@ -0,0 +1,1031 @@ +# 数据库架构文档 + +## 概述 + +本文档描述了 Playwrite 自动化项目的数据库架构,包括设计模式、组件关系、数据流和数据库表结构。 + +### 系统简介 + +本项目是一个 Python 自动化框架,使用 Playwright 与中国 ERP 系统(用友 YonBIP)交互。数据库层采用抽象工厂模式和 DAO(Data Access Object)模式,支持多种数据库类型。 + +### 支持的数据库类型 + +- **SQL Server** (Microsoft SQL Server) + - 驱动:ODBC Driver 18 for SQL Server + - 连接库:`pyodbc` + - 占位符:`?` + +- **MySQL** + - 连接库:`mysql-connector-python` + - 占位符:`%s` + +### 关键特性 + +- 多数据库支持:通过抽象基类实现统一的接口 +- 自动 SQL 转换:处理不同数据库的表名格式差异 +- 上下文管理器:支持 `with` 语句自动管理连接生命周期 +- 批处理优化:根据数据库参数限制自动分批处理 +- 事务管理:自动提交和回滚 + +--- + +## 架构设计 + +### 类层次结构 + +```mermaid +classDiagram + class BaseDatabaseConnection { + <> + +config: Dict + +connection: Connection + +connect() Connection + +disconnect() void + +execute_query(sql, params) List~Dict~ + +execute_update(sql, params) int + +get_placeholder() string + } + + class SQLServerConnection { + +connect() pyodbc.Connection + +disconnect() void + +execute_query(sql, params) List~Dict~ + +execute_update(sql, params) int + +get_placeholder() string + } + + class MySQLConnection { + +connect() MySQLConnection + +disconnect() void + +execute_query(sql, params) List~Dict~ + +execute_update(sql, params) int + +get_placeholder() string + } + + class BaseDAO { + <> + #db: BaseDatabaseConnection + #_db_type: DatabaseType + +__enter__() BaseDAO + +__exit__() void + +_convert_sql(sql) string + +_get_placeholder() string + } + + class BIPUsersDAO { + +authenticate(username, password) Dict + +get_all_users() List~Dict~ + +create_user(username, password, user_type) bool + +update_user_type(username, user_type) bool + +update_password(username, new_password) bool + +delete_user(username) bool + } + + class DiscreteMaterialPlanDAO { + +save_dataframe_with_replace(df) Dict + +query_by_plan_number(plan_number) List~Dict~ + +query_by_plan_numbers(plan_numbers) List~Dict~ + +query_by_source_numbers(source_numbers) List~Dict~ + +get_statistics() Dict + } + + class ProductionContractDataDAO { + +query_by_总排号(总排号_list) List~Dict~ + +get_source_numbers_by_总排号(总排号_list) List~string~ + } + + class MaterialsToBeDeletedDAO { + +insert_material(material_name, manager_name) bool + +get_all_materials() List~Dict~ + +get_materials_by_manager(manager_name) List~Dict~ + +update_manager(material_name, old_manager, new_manager) bool + +delete_material(material_name, manager_name) bool + +get_statistics() Dict + } + + class ConnectionFactory { + <> + +create_connection(db_type, config) BaseDatabaseConnection + +create_from_config(database_config) BaseDatabaseConnection + } + + class TableNameConverter { + <> + +to_mysql(table_name) string + +to_sqlserver(table_name) string + +convert_sql(sql, db_type) string + +extract_table_names(sql) List~string~ + } + + BaseDatabaseConnection <|-- SQLServerConnection + BaseDatabaseConnection <|-- MySQLConnection + BaseDAO <|-- BIPUsersDAO + BaseDAO <|-- DiscreteMaterialPlanDAO + BaseDAO <|-- ProductionContractDataDAO + BaseDAO <|-- MaterialsToBeDeletedDAO + ConnectionFactory ..> BaseDatabaseConnection : creates + BaseDAO ..> BaseDatabaseConnection : uses + BaseDAO ..> TableNameConverter : uses +``` + +### 设计模式 + +1. **抽象工厂模式 (Abstract Factory)** + - `ConnectionFactory` 根据配置创建相应的数据库连接实例 + - 客户端代码无需关心具体实现类型 + +2. **DAO 模式 (Data Access Object)** + - `BaseDAO` 提供统一的数据访问接口 + - 每个 DAO 类专注于特定表的数据操作 + - 封装 SQL 语句和数据库交互细节 + +3. **策略模式 (Strategy)** + - 不同的数据库连接类实现相同的接口 + - 运行时根据配置选择具体实现 + +4. **模板方法模式 (Template Method)** + - `BaseDAO` 定义通用的数据访问流程 + - 子类实现具体的业务逻辑 + +--- + +## 连接管理 + +### 连接工厂流程 + +```mermaid +flowchart TD + A[Application calls get_connection] --> B[ConfigLoader.load] + B --> C{Database Type?} + C -->|SQL Server| D[ConnectionFactory.create_from_config] + C -->|MySQL| D + D --> E{db_type value} + E -->|sqlserver| F[SQLServerConnection config] + E -->|mysql| G[MySQLConnection config] + F --> H[Connect to database] + G --> H + H --> I[Return connection instance] + I --> J[Execute query/update] + J --> K[Close connection] +``` + +### 配置加载流程 + +```mermaid +flowchart LR + A[.env file] --> B[env_loader.py load_env_file] + B --> C[ConfigLoader.load] + C --> D[AppConfig.from_env] + D --> E[DatabaseConfig.from_env] + E --> F[DatabaseType from env] + F --> G{DB_TYPE value} + G -->|sqlserver| H[SQLServerConfig.from_env] + G -->|mysql| I[MySQLConfig.from_env] + H --> J[ConnectionFactory.create_from_config] + I --> J +``` + +### 连接生命周期 + +```mermaid +sequenceDiagram + participant App as Application + participant CF as ConnectionFactory + participant DB as DatabaseConnection + participant SQL as SQL Database + + App->>CF: get_connection() + CF->>CF: load database config + CF->>DB: create_connection(db_type, config) + DB->>SQL: connect() + SQL-->>DB: connection object + DB-->>CF: connection instance + CF-->>App: return connection + + App->>DB: execute_query(sql, params) + DB->>SQL: execute query + SQL-->>DB: results + DB-->>App: return results + + App->>DB: disconnect() + DB->>SQL: close connection +``` + +--- + +## 数据访问层 + +### DAO 操作序列图 + +```mermaid +sequenceDiagram + participant App as Application + participant DAO as BaseDAO + participant CF as ConnectionFactory + participant DB as DatabaseConnection + participant SQL as SQL Database + + App->>DAO: with dao: + DAO->>DAO: __enter__() + DAO->>CF: get_connection() + CF->>DB: create_connection() + DB->>SQL: connect() + DB-->>DAO: return connection + + DAO->>DAO: _convert_sql(sql) + Note over DAO: TableNameConverter.convert_sql + + DAO->>DB: execute_query(sql, params) + DB->>SQL: execute + SQL-->>DB: results + DB-->>DAO: return data + + DAO-->>App: return results + + App->>DAO: end with block + DAO->>DAO: __exit__() + DAO->>DB: disconnect() + DB->>SQL: close connection +``` + +### 批处理流程 + +```mermaid +flowchart TD + A[Batch Data] --> B{Database Type?} + B -->|SQL Server| C[Calculate batch size] + B -->|MySQL| D[Calculate batch size] + + C --> E[Max 72 records/batch] + D --> F[Max 2000 records/batch] + + E --> G[Split into batches] + F --> G + + G --> H[Execute first batch] + H --> I{Has more data?} + I -->|Yes| J[Next batch] + J --> H + I -->|No| K[Complete] +``` + +**批处理限制说明**: +- **SQL Server**: 最多 2100 个参数/查询。以 28 个字段的表为例,每批最多 72 条记录(2100 ÷ 28 ≈ 72) +- **MySQL**: 默认无硬限制,但建议每批不超过 2000 条记录以优化性能 + +### 错误处理流程 + +```mermaid +flowchart TD + A[Execute Query/Update] --> B{Success?} + B -->|Yes| C[Return Results] + B -->|No| D[Catch Exception] + D --> E{Is Update Operation?} + E -->|Yes| F[Rollback Transaction] + E -->|No| G[Skip Rollback] + F --> H[Log Error] + G --> H + H --> I[Raise Exception to Caller] +``` + +--- + +## 数据库表结构 + +### 实体关系图 + +```mermaid +erDiagram + BIPUsers ||--o{ DiscreteMaterialPlanData : creates + BIPUsers ||--o{ MaterialsTypeToBeDeleted : manages + BIPUsers ||--o{ MaterialsToBeDeleted : tracks + ProductionContractData ||--o{ DiscreteMaterialPlanData : references + + BIPUsers { + int ID PK + string UserName + string Password + string UserType + datetime CreateTime + } + + DiscreteMaterialPlanData { + int ID PK + string Factory + string MaterialStatus + string PlanNumber + string SourceNumber + string MaterialType + string ProductCode + string ProductName + string ProductUnit + int ProductPlanQuantity + string UseDepartment + string Remark + string Creator + datetime CreateDate + string Approver + datetime ApproveDate + int SequenceNumber + string MaterialCode + string MaterialName + string Specification + string Model + string DrawingNumber + string MaterialQuality + int PlanQuantity + string Unit + datetime RequiredDate + string Warehouse + float UnitUsage + int CumulativeOutputQuantity + string BOMVersion + } + + ProductionContractData { + int ID PK + string 总排号 + string 生产订单号 + int 序号 + string 订单号 + string 客户名称 + string 产品型号 + } + + MaterialsTypeToBeDeleted { + int ID PK + string MaterialName + string ManagerName + } + + MaterialsToBeDeleted { + int ID PK + string MaterialName + string ManagerName + string Reason + datetime CreateDate + } +``` + +### 表结构说明 + +#### 1. BIPUsers (用户表) + +存储系统用户信息和认证凭据。 + +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| ID | int | 用户 ID | 主键 | +| UserName | varchar | 用户名 | 唯一 | +| Password | varchar | 密码 | - | +| UserType | varchar | 用户类型 | 'Admin', 'User', 'Guest' | +| CreateTime | datetime | 创建时间 | - | + +#### 2. DiscreteMaterialPlanData (离散备料计划数据表) + +存储生产备料计划详细信息。 + +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| ID | int | 记录 ID | 主键 | +| Factory | varchar | 工厂 | - | +| MaterialStatus | varchar | 备料状态 | - | +| PlanNumber | varchar | 备料计划单号 | - | +| SourceNumber | varchar | 来源单号(生产订单号) | 外键关联 | +| MaterialType | varchar | 备料类型 | - | +| ProductCode | varchar | 产品编码 | - | +| ProductName | varchar | 产品名称 | - | +| ProductUnit | varchar | 产品单位 | - | +| ProductPlanQuantity | int | 产品计划数量 | 默认 0 | +| UseDepartment | varchar | 用料部门 | - | +| Remark | varchar | 备注 | - | +| Creator | varchar | 制单人 | 关联 BIPUsers | +| CreateDate | datetime | 制单日期 | - | +| Approver | varchar | 审批人 | - | +| ApproveDate | datetime | 审批日期 | - | +| SequenceNumber | int | 序号 | 默认 0 | +| MaterialCode | varchar | 材料编码 | - | +| MaterialName | varchar | 材料名称 | - | +| Specification | varchar | 规格 | - | +| Model | varchar | 型号 | - | +| DrawingNumber | varchar | 图号 | - | +| MaterialQuality | varchar | 物料材质 | - | +| PlanQuantity | int | 计划数量 | 默认 0 | +| Unit | varchar | 单位 | - | +| RequiredDate | datetime | 需用日期 | - | +| Warehouse | varchar | 发料仓库 | - | +| UnitUsage | float | 单位用量 | 默认 0.0 | +| CumulativeOutputQuantity | int | 累计出库数量 | 默认 0 | +| BOMVersion | varchar | BOM 版本 | - | + +#### 3. ProductionContractData (生产合同数据表) + +存储生产合同信息(26年压力表合同数据)。 + +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| ID | int | 记录 ID | 主键 | +| 总排号 | varchar | 总排号 | - | +| 生产订单号 | varchar | 生产订单号 | - | +| 序号 | int | 序号 | - | +| 订单号 | varchar | 订单号 | - | +| 客户名称 | varchar | 客户名称 | - | +| 产品型号 | varchar | 产品型号 | - | + +#### 4. MaterialsTypeToBeDeleted (待删除物料类型表) + +存储需要删除的物料及其管理员信息。 + +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| ID | int | 记录 ID | 主键 | +| MaterialName | varchar | 物料名称 | - | +| ManagerName | varchar | 管理员名称 | 关联 BIPUsers | + +#### 5. MaterialsToBeDeleted (待删除物料记录表) + +存储待删除物料的详细记录。 + +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| ID | int | 记录 ID | 主键 | +| MaterialName | varchar | 物料名称 | - | +| ManagerName | varchar | 管理员名称 | 关联 BIPUsers | +| Reason | varchar | 删除原因 | - | +| CreateDate | datetime | 创建日期 | - | + +--- + +## SQL 方言处理 + +### SQL 转换流程 + +```mermaid +flowchart TD + A[SQL Server Query] --> B{Contains schema dot table format?} + B -->|Yes| C[TableNameConverter.convert_sql] + B -->|No| D[Skip conversion] + C --> E{Target Database Type?} + E -->|MySQL| F[Replace schema.table with schema_table] + E -->|SQL Server| G[Keep original format] + D --> G + F --> H{Placeholder conversion} + G --> H + H --> I{Database Type?} + I -->|MySQL| J[Replace question mark with percent s] + I -->|SQL Server| K[Keep question mark placeholders] + J --> L[Execute query] + K --> L +``` + +### 表名转换规则 + +| SQL Server 格式 | MySQL 格式 | 示例 | +|-----------------|------------|------| +| `[dbo].[TableName]` | `dbo_TableName` | `[dbo].[BIPUsers]` → `dbo_BIPUsers` | +| `[schema].[table name]` | `schema_table name` | `[productionContractData].[26年压力表合同数据]` → `productionContractData_26年压力表合同数据` | +| `TableName` | `dbo_TableName` | `BIPUsers` → `dbo_BIPUsers` | + +### 占位符差异 + +| 数据库 | 占位符 | 示例 | +|--------|--------|------| +| SQL Server | `?` | `WHERE UserName = ? AND Password = ?` | +| MySQL | `%s` | `WHERE UserName = %s AND Password = %s` | + +### 列名格式差异 + +| 数据库 | 列名格式 | 示例 | +|--------|----------|------| +| SQL Server | `[ColumnName]` | `SELECT [ID], [UserName] FROM [dbo].[BIPUsers]` | +| MySQL | `ColumnName` | `SELECT ID, UserName FROM dbo_BIPUsers` | + +--- + +## 数据流 + +### 查询执行流程 + +```mermaid +flowchart TD + A[User calls DAO method] --> B[DAO enters context with 'with'] + B --> C[DAO __enter__] + C --> D[Get connection from factory] + D --> E[Database connect] + E --> F[Convert SQL if needed] + F --> G[Build placeholders] + G --> H[Execute query] + H --> I{Success?} + I -->|Yes| J[Convert results to dicts] + I -->|No| K[Raise exception] + J --> L[Return data to user] + K --> M[DAO __exit__ cleanup] + L --> M + M --> N[Database disconnect] +``` + +### 保存数据流程 + +```mermaid +flowchart TD + A[User provides DataFrame] --> B[Check if empty] + B -->|Empty| C[Return zero counts] + B -->|Has data| D[Remove duplicates] + D --> E[Extract unique plan numbers] + E --> F[Delete existing records] + F --> G[Split into batches] + G --> H[Insert batch 1] + H --> I{More batches?} + I -->|Yes| J[Insert next batch] + I -->|No| K[Return statistics] + J --> H +``` + +### 权限过滤流程 + +```mermaid +flowchart TD + A[User queries materials] --> B{User authenticated?} + B -->|No| C[Return empty result] + B -->|Yes| D{User type?} + D -->|Admin| E[Return all materials] + D -->|User| F[Get user's materials] + D -->|Guest| G[Return limited materials] + F --> H[Filter by ManagerName] + H --> I[Return filtered result] + G --> I + E --> J[Return unfiltered result] +``` + +--- + +## 代码示例 + +### 连接使用示例 + +```python +from db.connection import get_connection + +# 使用上下文管理器自动管理连接 +with get_connection() as db: + results = db.execute_query( + "SELECT * FROM [dbo].[BIPUsers] WHERE UserType = ?", + ('Admin',) + ) + for user in results: + print(f"User: {user['UserName']}") + +# 连接自动关闭 +``` + +### DAO 使用示例 + +```python +from db.bip_users_dao import BIPUsersDAO + +# 用户认证 +dao = BIPUsersDAO() +user = dao.authenticate("admin", "password123") +if user: + print(f"Authenticated: {user['username']}") + +# 获取所有用户 +with dao: + users = dao.get_all_users() + for user in users: + print(f"{user['username']} - {user['user_type']}") + +# 创建新用户 +success = dao.create_user( + username="newuser", + password="pass123", + user_type="User" +) +``` + +### 配置示例 + +#### .env 文件配置 + +```bash +# 数据库类型 (sqlserver 或 mysql) +DB_TYPE=mysql + +# SQL Server 配置 +DB_SERVER=192.168.110.114 +DB_NAME=CompanyDB +DB_USERNAME=peng +DB_PASSWORD=your_password +DB_SQLSERVER_DRIVER=ODBC Driver 18 for SQL Server +DB_TRUST_SERVER_CERTIFICATE=yes + +# MySQL 配置 +DB_MYSQL_HOST=192.168.31.83 +DB_MYSQL_PORT=3306 +DB_MYSQL_CHARSET=utf8mb4 + +# ERP 配置 +ERP_URL=https://68.11.34.30:8082/ +ERP_USERNAME=your_username +ERP_PASSWORD=your_password +ERP_HEADLESS=false +``` + +#### 代码中加载配置 + +```python +from config.loader import ConfigLoader + +# 加载配置 +config = ConfigLoader.load() + +# 访问数据库配置 +db_config = config.database +print(f"Database type: {db_config.db_type}") +print(f"Database name: {db_config.database}") + +# 访问 ERP 配置 +erp_config = config.erp +print(f"ERP URL: {erp_config.url}") +``` + +--- + +## 批处理限制详解 + +### SQL Server 参数限制 + +SQL Server 对每个 SQL 查询的参数数量有硬性限制:**最多 2100 个参数**。 + +#### 计算示例 + +对于 `DiscreteMaterialPlanData` 表(28 个字段): + +``` +最大记录数 = floor(2100 / 28) = 72 条记录/批次 +``` + +#### 实现代码 + +```python +def _batch_insert(self, db, df: pd.DataFrame, batch_size: int = 72) -> int: + """SQL Server: 最多 72 条记录/批次""" + total_inserted = 0 + records = self._convert_df_to_records(df) + + for i in range(0, len(records), batch_size): + batch = records[i:i + batch_size] + for record in batch: + db.execute_update(sql, record) + total_inserted += 1 + + return total_inserted +``` + +### MySQL 批处理 + +MySQL 没有硬性参数限制,但建议每批不超过 2000 条记录。 + +#### 计算示例 + +``` +建议批次大小 = 2000 条记录/批次 +``` + +#### 实现代码 + +```python +def _batch_insert(self, db, df: pd.DataFrame, batch_size: int = 2000) -> int: + """MySQL: 最多 2000 条记录/批次""" + total_inserted = 0 + records = self._convert_df_to_records(df) + + for i in range(0, len(records), batch_size): + batch = records[i:i + batch_size] + for record in batch: + db.execute_update(sql, record) + total_inserted += 1 + + return total_inserted +``` + +### IN 子句限制 + +对于包含 `IN` 子句的查询,也需要批处理: + +```python +def _delete_by_plan_numbers(self, db, plan_numbers: List[str]) -> int: + """分批删除以避免参数限制""" + batch_size = 1000 # 安全限制 + total_deleted = 0 + + for i in range(0, len(plan_numbers), batch_size): + batch = plan_numbers[i:i + batch_size] + placeholders = ','.join(['?' for _ in batch]) + sql = f"DELETE FROM {table_name} WHERE PlanNumber IN ({placeholders})" + deleted = db.execute_update(sql, tuple(batch)) + total_deleted += deleted + + return total_deleted +``` + +--- + +## 事务管理 + +### 自动提交和回滚 + +```mermaid +flowchart TD + A[Start Transaction] --> B[Execute Query] + B --> C{Success?} + C -->|Yes| D[Commit Transaction] + C -->|No| E[Rollback Transaction] + D --> F[Return Results] + E --> G[Log Error] + G --> H[Raise Exception] +``` + +### 实现代码 + +#### SQL Server + +```python +def execute_update(self, sql: str, params: Optional[tuple] = None) -> int: + cursor = self.connection.cursor() + try: + if params: + cursor.execute(sql, params) + else: + cursor.execute(sql) + + self.connection.commit() # 自动提交 + return cursor.rowcount + except pyodbc.Error as e: + self.connection.rollback() # 自动回滚 + print(f"执行失败,已回滚: {e}") + raise + finally: + cursor.close() +``` + +#### MySQL + +```python +def execute_update(self, sql: str, params: Optional[tuple] = None) -> int: + cursor = self.connection.cursor() + try: + if params: + cursor.execute(sql, params) + else: + cursor.execute(sql) + + self.connection.commit() # 自动提交 + return cursor.rowcount + except Error as e: + self.connection.rollback() # 自动回滚 + print(f"执行失败,已回滚: {e}") + raise + finally: + cursor.close() +``` + +--- + +## 错误处理 + +### 常见错误类型 + +| 错误类型 | 原因 | 处理方式 | +|----------|------|----------| +| 连接失败 | 网络问题、凭据错误 | 记录日志,抛出异常 | +| 参数超限 | 超过 2100 参数限制 | 自动分批处理 | +| 表名格式错误 | SQL 方言不匹配 | 自动转换表名 | +| 约束违反 | 重复键、外键约束 | 回滚事务,返回错误 | +| 超时 | 查询执行时间过长 | 增加超时时间或优化查询 | + +### 错误处理最佳实践 + +```python +def safe_database_operation(): + try: + with get_connection() as db: + # 执行数据库操作 + results = db.execute_query(sql, params) + return results + except pyodbc.Error as e: + # 数据库特定错误 + print(f"Database error: {e}") + # 记录到日志文件 + log_error(e) + raise + except Exception as e: + # 通用错误 + print(f"Unexpected error: {e}") + log_error(e) + raise +``` + +--- + +## 性能优化建议 + +### 1. 使用批处理 + +对于大量数据插入/更新,始终使用批处理: + +```python +# 推荐:批处理 +for i in range(0, len(records), batch_size): + batch = records[i:i + batch_size] + db.execute_batch(sql, batch) + +# 避免:逐条插入 +for record in records: + db.execute_update(sql, record) +``` + +### 2. 使用索引 + +确保常用查询字段有索引: + +```sql +CREATE INDEX idx_plan_number ON DiscreteMaterialPlanData(PlanNumber) +CREATE INDEX idx_source_number ON DiscreteMaterialPlanData(SourceNumber) +CREATE INDEX idx_manager_name ON MaterialsTypeToBeDeleted(ManagerName) +``` + +### 3. 使用连接池 + +对于频繁的数据库操作,考虑使用连接池: + +```python +from db.connection_pool import ConnectionPool + +pool = ConnectionPool(max_connections=5) +with pool.get_connection() as db: + results = db.execute_query(sql, params) +``` + +### 4. 优化查询 + +- 只选择需要的列 +- 使用 `WHERE` 子句过滤数据 +- 避免使用 `SELECT *` + +```python +# 推荐 +sql = "SELECT ID, UserName FROM dbo_BIPUsers WHERE UserType = ?" + +# 避免 +sql = "SELECT * FROM dbo_BIPUsers" +``` + +--- + +## 安全考虑 + +### 1. 参数化查询 + +始终使用参数化查询防止 SQL 注入: + +```python +# 推荐:参数化查询 +sql = "SELECT * FROM dbo_BIPUsers WHERE UserName = ?" +results = db.execute_query(sql, (username,)) + +# 避免:字符串拼接 +sql = f"SELECT * FROM dbo_BIPUsers WHERE UserName = '{username}'" # 危险! +``` + +### 2. 密码管理 + +- 不要在代码中硬编码密码 +- 使用环境变量或配置文件 +- 考虑使用密钥管理服务 + +### 3. 最小权限原则 + +数据库用户应该只有必要的权限: + +```sql +-- 只授予必要的权限 +GRANT SELECT, INSERT, UPDATE ON DiscreteMaterialPlanData TO app_user; +GRANT SELECT ON BIPUsers TO app_user; +``` + +### 4. 连接字符串安全 + +- 不要在日志中记录连接字符串 +- 使用加密存储敏感信息 +- 定期轮换凭据 + +--- + +## 维护指南 + +### 添加新 DAO 类 + +1. 创建新的 DAO 类继承 `BaseDAO` +2. 实现业务方法 +3. 使用 `_convert_sql()` 处理表名 +4. 使用 `_get_placeholder()` 处理参数 + +```python +from db.base_dao import BaseDAO + +class NewTableDAO(BaseDAO): + def get_by_id(self, record_id: int) -> Dict: + table_name = self._convert_sql('[dbo].[NewTable]') + placeholder = self._get_placeholder() + sql = f"SELECT * FROM {table_name} WHERE ID = {placeholder}" + + with get_connection() as db: + results = db.execute_query(sql, (record_id,)) + return results[0] if results else None +``` + +### 添加新数据库支持 + +1. 创建新的连接类继承 `BaseDatabaseConnection` +2. 实现所有抽象方法 +3. 在 `ConnectionFactory` 中注册 +4. 更新 `DatabaseType` 枚举 + +```python +# 1. 创建连接类 +class PostgreSQLConnection(BaseDatabaseConnection): + def connect(self): + # 实现连接逻辑 + pass + + # 实现其他抽象方法... + +# 2. 更新枚举 +class DatabaseType(str, Enum): + SQLSERVER = "sqlserver" + MYSQL = "mysql" + POSTGRESQL = "postgresql" + +# 3. 更新工厂 +def create_connection(db_type, config): + if db_type == DatabaseType.POSTGRESQL: + return PostgreSQLConnection(config) + # 其他类型... +``` + +### 更新文档 + +当代码变更时,及时更新本文档: + +- 更新类图和关系图 +- 添加新的表结构说明 +- 更新代码示例 +- 记录新的限制和最佳实践 + +--- + +## 附录 + +### 相关文件 + +| 文件路径 | 说明 | +|----------|------| +| `db/base_connection.py` | 数据库连接抽象基类 | +| `db/sqlserver_connection.py` | SQL Server 连接实现 | +| `db/mysql_connection.py` | MySQL 连接实现 | +| `db/base_dao.py` | DAO 基类 | +| `db/connection_factory.py` | 连接工厂 | +| `db/table_name_converter.py` | 表名转换工具 | +| `db/bip_users_dao.py` | 用户 DAO | +| `db/discrete_material_plan_dao.py` | 备料计划 DAO | +| `db/production_contract_data_dao.py` | 合同数据 DAO | +| `db/materials_to_be_deleted_dao.py` | 待删除物料 DAO | +| `db/connection.py` | 连接获取辅助函数 | +| `config/schema.py` | 配置结构定义 | +| `config/env_loader.py` | 环境变量加载器 | +| `config/loader.py` | 配置加载器 | + +### 依赖项 + +``` +pyodbc>=4.0.0 # SQL Server 支持 +mysql-connector-python>=8.0.0 # MySQL 支持 +python-dotenv>=0.19.0 # 环境变量管理 +pandas>=1.3.0 # 数据处理 +``` + +### 参考资料 + +- [SQL Server Documentation](https://docs.microsoft.com/en-us/sql/) +- [MySQL Documentation](https://dev.mysql.com/doc/) +- [pyodbc Documentation](https://github.com/mkleehammer/pyodbc) +- [MySQL Connector/Python](https://dev.mysql.com/doc/connector-python/en/) +- [DAO Pattern](https://en.wikipedia.org/wiki/Data_access_object) +- [Abstract Factory Pattern](https://en.wikipedia.org/wiki/Abstract_factory_pattern) + +--- + +**文档版本**: 1.0 +**最后更新**: 2026-02-09 +**维护者**: Development Team