bool, 'msg' => string] */ public static function generate(string $tableName, string $modelName, bool $force = false): array { // 1. 定义模型文件路径(兼容webman的app_path) $modelPath = app_path() . '/model/' . ucfirst($modelName) . '.php'; $modelClassName = ucfirst($modelName); // 确保类名首字母大写 // 2. 检查文件是否已存在,存在则返回不覆盖提示 if (file_exists($modelPath) && !$force) { return [ 'status' => false, 'msg' => "模型文件 {$modelClassName}.php 已存在,不执行覆盖操作" ]; } try { // 3. 读取数据表基本信息(包含表注释)- 修复参数绑定问题 $tableComment = self::getTableComment($tableName); // 4. 读取数据表结构(包含字段注释、类型等) $tableStruct = self::getTableStruct($tableName); if (empty($tableStruct)) { return [ 'status' => false, 'msg' => "数据表 {$tableName} 无字段信息,生成失败" ]; } // 5. 构建模型文件内容(传入表注释) $modelContent = self::buildModelContent($tableName, $modelClassName, $tableStruct, $tableComment); // 6. 确保model目录存在 if (!is_dir(dirname($modelPath))) { mkdir(dirname($modelPath), 0755, true); } // 7. 写入模型文件 $writeResult = file_put_contents($modelPath, $modelContent); if ($writeResult === false) { throw new RuntimeException("模型文件写入失败,检查目录权限"); } return [ 'status' => true, 'msg' => "模型 {$modelClassName}.php 已成功生成至 app/model 目录" ]; } catch (\Exception $e) { return [ 'status' => false, 'msg' => "生成失败:{$e->getMessage()}" ]; } } /** * 批量生成所有数据表的模型文件 * * @param bool $force 是否强制覆盖已存在的模型文件 * @param array $excludeTables 排除的数据表(如:['migrations', 'logs']) * @return array 批量生成结果 ['success' => 成功数量, 'fail' => 失败列表] */ public static function generate_all(bool $force = false, array $excludeTables = []): array { $result = [ 'success' => 0, 'fail' => [] ]; try { // 1. 获取数据库中所有数据表 $tables = Db::select("SHOW TABLES"); $tableNameKey = 'Tables_in_' . config('database.connections.mysql.database'); foreach ($tables as $table) { $tableName = $table->$tableNameKey; // 跳过排除的表 if (in_array($tableName, $excludeTables)) { continue; } // 跳过视图(以 v_ 开头的表,避免视图生成模型) if (str_starts_with($tableName, 'v_')) { continue; } // 2. 表名转模型名(下划线转驼峰,如 user_info → UserInfo) $modelName = self::tableNameToModelName($tableName); // 3. 调用单个生成方法 $generateResult = self::generate($tableName, $modelName, $force); if ($generateResult['status']) { $result['success']++; } else { $result['fail'][] = [ 'table' => $tableName, 'model' => $modelName, 'reason' => $generateResult['msg'] ]; } } return $result; } catch (\Exception $e) { $result['fail'][] = [ 'table' => 'all', 'model' => 'all', 'reason' => "批量生成异常:{$e->getMessage()}" ]; return $result; } } /** * 获取数据表注释(修复参数绑定问题) * * @param string $tableName 数据表名 * @return string 表注释(无注释则返回空字符串) */ private static function getTableComment(string $tableName): string { // 修复核心:不使用参数绑定,直接拼接表名(先过滤表名防止注入) $safeTableName = preg_replace('/[^a-zA-Z0-9_]/', '', $tableName); $sql = "SHOW TABLE STATUS LIKE '{$safeTableName}'"; try { $tableInfo = Db::select($sql); } catch (\Exception $e) { // 读取表注释失败时返回空字符串,不影响模型生成 return ''; } if (empty($tableInfo)) { return ''; } // 兼容不同数据库驱动的字段名(Comment/comment) $comment = $tableInfo[0]->Comment ?? $tableInfo[0]->comment ?? ''; return trim($comment); } /** * 获取数据表结构(修复关键字冲突+兼容字段注释读取) * * @param string $tableName 数据表名 * @return array 表结构数组 */ private static function getTableStruct(string $tableName): array { // 表名安全过滤 $safeTableName = preg_replace('/[^a-zA-Z0-9_]/', '', $tableName); // 修复关键字冲突:给别名加反引号,避免与MySQL保留字冲突 $sql = "SELECT COLUMN_NAME AS `Field`, DATA_TYPE AS `TypeSimple`, COLUMN_TYPE AS `Type`, IS_NULLABLE AS `IsNull`, COLUMN_KEY AS `ColumnKey`, COLUMN_DEFAULT AS `ColumnDefault`, COLUMN_COMMENT AS `Comment` FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION"; // 使用参数绑定读取字段信息(INFORMATION_SCHEMA 支持参数绑定) $tableStruct = Db::select($sql, [ config('database.connections.mysql.database'), $safeTableName ]); // 格式化字段信息,确保注释字段统一 return array_map(function ($field) { return (object)[ 'Field' => $field->Field ?? '', 'Type' => $field->Type ?? $field->TypeSimple ?? '', 'Key' => $field->ColumnKey ?? '', // 对应修改后的别名 // 优先读取 COMMENT,兼容大小写,确保去空格 'Comment' => trim($field->Comment ?? $field->comment ?? ''), 'Null' => $field->IsNull ?? '', // 对应修改后的别名 'Default' => $field->ColumnDefault ?? '' // 对应修改后的别名 ]; }, $tableStruct); } /** * 构建模型文件内容(确保字段注释正确展示) * * @param string $tableName 数据表名 * @param string $modelClassName 模型类名 * @param array $tableStruct 表结构信息 * @param string $tableComment 表注释 * @return string 模型文件内容 */ private static function buildModelContent(string $tableName, string $modelClassName, array $tableStruct, string $tableComment): string { // 提取主键 $primaryKey = 'id'; foreach ($tableStruct as $field) { if ($field->Key === 'PRI') { $primaryKey = $field->Field; break; } } // 构建字段注释(格式:字段名 数据库注释) $fieldComments = []; foreach ($tableStruct as $field) { $fieldName = $field->Field; // 原始字段名(如 dt) $dbComment = $field->Comment; // 数据库注释(如 基准时间) // 拼接注释:字段名 + (有数据库注释则加)数据库注释 $fullComment = $fieldName; if (!empty($dbComment)) { $fullComment .= " {$dbComment}"; } // 补充字段属性说明(非空、主键、默认值) $extComment = []; if ($field->Null === 'NO') { $extComment[] = '非空'; } if ($field->Key === 'PRI') { $extComment[] = '主键'; } if ($field->Default !== '') { $extComment[] = "默认值:{$field->Default}"; } // 如有属性说明,追加到注释末尾 if (!empty($extComment)) { $fullComment .= '(' . implode(',', $extComment) . ')'; } $fieldType = self::dbTypeToPhpType($field->Type); $fieldComments[] = " * @property {$fieldType} \${$fieldName} {$fullComment}"; } $fieldCommentsStr = implode("\n", $fieldComments); // 构建表注释(无注释则使用默认描述) $tableCommentStr = !empty($tableComment) ? $tableComment : "{$tableName} 数据表模型"; // 模型模板(适配webman的think-orm) return << 'int', 'float', 'double', 'decimal' => 'float', 'date', 'time', 'datetime', 'timestamp' => 'string', 'bool', 'boolean' => 'bool', default => 'string' }; } }