feat: 实现TCP Server
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
|
||||
namespace app\utils;
|
||||
|
||||
use support\Db;
|
||||
use RuntimeException;
|
||||
|
||||
class ModelAutoGenerator
|
||||
{
|
||||
/**
|
||||
* 生成单个数据表对应的模型文件
|
||||
*
|
||||
* @param string $tableName 数据库表名(如:user)
|
||||
* @param string $modelName 模型类名(如:User)
|
||||
* @param bool $force 是否强制覆盖已存在的模型文件
|
||||
* @return array 生成结果 ['status' => 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 <<<PHP
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* {$modelClassName} 模型
|
||||
* 表说明:{$tableCommentStr}
|
||||
* 对应数据表:{$tableName}
|
||||
{$fieldCommentsStr}
|
||||
*/
|
||||
class {$modelClassName} extends Model
|
||||
{
|
||||
/**
|
||||
* 数据表名
|
||||
* @var string
|
||||
*/
|
||||
protected \$table = '{$tableName}';
|
||||
|
||||
/**
|
||||
* 主键字段名
|
||||
* @var string
|
||||
*/
|
||||
protected \$pk = '{$primaryKey}';
|
||||
|
||||
/**
|
||||
* 关闭自动时间戳(如需开启请改为 true,需确保表有 create_time/update_time 字段)
|
||||
* @var bool
|
||||
*/
|
||||
protected \$autoWriteTimestamp = false;
|
||||
|
||||
/**
|
||||
* 字段严格检查(false=允许操作未定义字段)
|
||||
* @var bool
|
||||
*/
|
||||
protected \$strict = false;
|
||||
}
|
||||
PHP;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据表名转模型名(下划线转驼峰)
|
||||
*
|
||||
* @param string $tableName 数据表名(如:user_info)
|
||||
* @return string 模型名(如:UserInfo)
|
||||
*/
|
||||
private static function tableNameToModelName(string $tableName): string
|
||||
{
|
||||
// 移除表前缀(如需处理前缀,可在此添加逻辑)
|
||||
// $prefix = config('database.connections.mysql.prefix');
|
||||
// $tableName = str_replace($prefix, '', $tableName);
|
||||
|
||||
// 下划线转驼峰并首字母大写
|
||||
return str_replace(' ', '', ucwords(str_replace('_', ' ', $tableName)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库字段类型转PHP类型注释
|
||||
*
|
||||
* @param string $dbType 数据库类型(如:int(11), varchar(255))
|
||||
* @return string PHP类型(如:int, string)
|
||||
*/
|
||||
private static function dbTypeToPhpType(string $dbType): string
|
||||
{
|
||||
$type = strtolower(explode('(', $dbType)[0]);
|
||||
return match ($type) {
|
||||
'int', 'tinyint', 'smallint', 'mediumint', 'bigint' => 'int',
|
||||
'float', 'double', 'decimal' => 'float',
|
||||
'date', 'time', 'datetime', 'timestamp' => 'string',
|
||||
'bool', 'boolean' => 'bool',
|
||||
default => 'string'
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user