* +---------------------------------------------------------------------- */ namespace app\services\system; use app\dao\system\SystemCrudDao; use app\services\BaseServices; use crmeb\services\crud\Controller; use crmeb\services\crud\Dao; use crmeb\services\crud\Make; use crmeb\services\crud\Model; use crmeb\services\crud\Route; use crmeb\services\crud\Service; use crmeb\services\crud\Validate; use crmeb\services\crud\ViewApi; use crmeb\services\crud\ViewPages; use crmeb\services\crud\ViewRouter; use think\exception\ValidateException; use think\facade\Db; use think\helper\Str; use think\migration\Migrator; use Phinx\Db\Adapter\MysqlAdapter; /** * Class SystemCrudServices * @author 等风来 * @email 136327134@qq.com * @date 2023/4/6 * @package app\services\system */ class SystemCrudServices extends BaseServices { //不能生成的系统自带表 const NOT_CRUD_TABANAME = [ 'system_config', 'system_attachment', 'system_attachment_category', 'system_config_tab', 'system_admin', 'eb_system_city', 'system_log', 'system_menus', 'system_notice', 'system_notice_admin', 'system_notification', 'system_role', 'system_route', 'system_route_cate', 'system_storage', 'system_timer', 'system_user_level', 'system_crud', 'wechat_key', 'user_label_relation', 'user_brokerage_frozen', 'user_brokerage', 'store_product_cate', 'store_bargain_user_help', 'shipping_templates_region', 'shipping_templates_no_delivery', 'shipping_templates_free', 'other_order_status', 'lang_code', 'lang_country', 'app_version', ]; /** * SystemCrudServices constructor. * @param SystemCrudDao $dao */ public function __construct(SystemCrudDao $dao) { $this->dao = $dao; } /** * @return array * @author 等风来 * @email 136327134@qq.com * @date 2023/4/11 */ public function getList() { [$page, $limit] = $this->getPageValue(); $list = $this->dao->selectList([], 'add_time,id,name,table_name', $page, $limit, 'id desc'); $count = $this->dao->count(); return compact('list', 'count'); } /** * 数据库字段类型 * @return \string[][] * @author 等风来 * @email 136327134@qq.com * @date 2023/4/11 */ public function getTabelRule() { $rule = [ 'varchat' => 'string', 'int' => 'integer', 'biginteger' => 'bigint', ]; return [ 'types' => [ 'varchar', 'char', 'text', 'longtext', 'tinytext', 'enum', 'blob', 'binary', 'varbinary', 'datetime', 'timestamp', 'time', 'date', 'year', 'boolean', 'tinyint', 'int', 'decimal', 'float', 'json', 'addTimestamps', 'addSoftDelete', ], 'rule' => $rule ]; } /** * 改变数据库类型 * @param string $type * @return string * @author 等风来 * @email 136327134@qq.com * @date 2023/4/13 */ public function changeTabelRule(string $type) { if (!in_array($type, $this->getTabelRule()['type'])) { throw new ValidateException('类型不在支持范围'); } return $this->getTabelRule()['rule'][$type] ?? $type; } /** * 获取表字段 * @param string $tableName * @return mixed * @author 等风来 * @email 136327134@qq.com * @date 2023/4/7 */ public function getColumnNamesList(string $tableName) { $sql = 'SELECT * FROM `information_schema`.`columns` WHERE TABLE_SCHEMA = ? AND table_name = ? ORDER BY ORDINAL_POSITION'; $column = Db::query($sql, [config('database.connections.mysql.database'), $this->getTableName($tableName)]); $columns = []; foreach ($column as $item) { $column = [ 'name' => $item['COLUMN_NAME'], 'type' => $item['DATA_TYPE'], 'dataType' => stripos($item['COLUMN_TYPE'], '(') !== false ? substr_replace($item['COLUMN_TYPE'], '', stripos($item['COLUMN_TYPE'], ')') + 1) : $item['COLUMN_TYPE'], 'default' => $item['COLUMN_DEFAULT'], 'null' => $item['IS_NULLABLE'] == 'YES', 'primaryKey' => $item['COLUMN_KEY'] == 'PRI', 'unsigned' => (bool)stripos($item['COLUMN_TYPE'], 'unsigned'), 'autoIncrement' => stripos($item['EXTRA'], 'auto_increment') !== false, 'comment' => $item['COLUMN_COMMENT'], 'limit' => $item['CHARACTER_MAXIMUM_LENGTH'] ?: $item['NUMERIC_PRECISION'], ]; $columns[$item['COLUMN_NAME']] = $column; } return $columns; } /** * @param array $data * @return array * @author 等风来 * @email 136327134@qq.com * @date 2023/4/12 */ public function valueReplace(array $data) { $replace = ['phar://']; $newData = []; foreach ($data as $key => $item) { if (is_array($item)) { $item = $this->valueReplace($item); } else { $item = str_replace($replace, '', $item); } $newData[str_replace($replace, '', $key)] = $item; } return $newData; } /** * 创建 * @param array $data * @return mixed * @author 等风来 * @email 136327134@qq.com * @date 2023/4/11 */ public function createCrud(array $data) { $tableName = $data['tableName']; $tableComment = $data['tableComment']; $tableField = $this->valueReplace($data['tableField']); $filePath = $this->valueReplace($data['filePath']); //创建数据库 if ($tableField && !$data['isTable']) { $this->makeDatebase($tableName, $tableComment, $tableField); } if (in_array($tableName, self::NOT_CRUD_TABANAME)) { throw new ValidateException('不能生成系统自带数据表'); } //读取表结构 $column = $this->getColumnNamesList($tableName); if (!$column) { throw new ValidateException('请先创建' . $tableName . '表'); } //获取主键 foreach ($column as $value) { if ($value['primaryKey']) { $data['key'] = $value['name']; break; } } $routeName = 'crud/' . Str::snake($tableName); $uniqueAuth = Str::snake($tableName) . '-crud-list-index'; //增加保存的绝对路径 foreach ($filePath as $k => $i) { if (in_array($k, ['pages', 'router', 'api'])) { $filePath[$k] = Make::adminTemplatePath() . $i; } else { $filePath[$k] = app()->getRootPath() . $i; } } //创建菜单 if (!$data['menuName']) { $data['menuName'] = $tableName; } $dataMenu = [ 'pid' => $data['pid'], 'menu_name' => $data['menuName'], 'menu_path' => '', 'auth_type' => 1, 'is_show' => 1, 'is_del' => 0, 'unique_auth' => $uniqueAuth, 'is_header' => $data['pid'] ? 0 : 1, ]; $res = $this->transaction(function () use ($filePath, $tableName, $routeName, $data, $dataMenu) { $menuInfo = app()->make(SystemMenusServices::class)->save($dataMenu); //写入路由权限 $cateId = app()->make(SystemRouteServices::class)->topCateId('adminapi'); $ruleData = [ [ 'path' => $routeName, 'method' => 'GET', 'name' => $data['menuName'] . '列表接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '/create', 'method' => 'GET', 'name' => $data['menuName'] . '获取创建表单接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName, 'method' => 'POST', 'name' => $data['menuName'] . '保存数据接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '//edit', 'method' => 'GET', 'name' => $data['menuName'] . '获取修改表单接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '/', 'method' => 'PUT', 'name' => $data['menuName'] . '修改数据接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '/', 'method' => 'DELETE', 'name' => $data['menuName'] . '删除数据接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'add_time' => date('Y-m-d H:i:s') ], ]; app()->make(SystemRouteServices::class)->saveAll($ruleData); //记录权限加入菜单表 $menuData = []; foreach ($ruleData as $item) { $menuData[] = [ 'pid' => $menuInfo->id, 'methods' => $item['method'], 'api_url' => $item['path'], 'name' => $item['name'], 'is_del' => 0, ]; } app()->make(SystemMenusServices::class)->saveAll($menuData); //生成文件 $make = $this->makeFile($tableName, $routeName, true, $data, $filePath); $makePath = []; foreach ($make as $key => $item) { $makePath[$key] = $item['path']; } //记录crud生成 $res = $this->dao->save([ 'pid' => $data['pid'], 'name' => $data['menuName'], 'table_name' => $tableName, 'field' => json_encode($data), 'make_path' => json_encode($makePath), 'add_time' => time() ]); return $res; }); return $res->toArray(); } /** * 创建数据库 * @param string $tableName * @param string $tableComment * @param array $tableField * @author 等风来 * @email 136327134@qq.com * @date 2023/4/7 */ public function makeDatebase(string $tableName, string $tableComment, array $tableField = []) { $migrator = app()->make(Migrator::class, [date('YmdHis')]); //创建表 $table = $migrator->table($tableName, $tableComment); //创建字段 foreach ($tableField as $item) { $option = []; if (!isset($item['limit'])) { $option['limit'] = (int)$item['limit']; } if (!isset($item['default'])) { $option['default'] = $item['default']; } //创建伪删除 if ($item['type'] === 'addSoftDelete') { $table->addSoftDelete(); } else if ($item['type'] === 'addTimestamps') { //创建修改和增加时间 $table->addTimestamps(); } else { $option['comment'] = $item['comment']; $table->addColumn($item['field'], $this->changeTabelRule($item['type']), $option); } } //创建索引 if (!empty($data['tableIndex'])) { foreach ($data['tableIndex'] as $item) { $table->addIndex($item); } } //执行创建 $table->create(); } /** * 创建文件返回文件路径和内容 * @param string $tableName * @param string $routeName * @param bool $isMake * @param array $options * @param array $filePath * @return array[] * @author 等风来 * @email 136327134@qq.com * @date 2023/4/7 */ public function makeFile(string $tableName, string $routeName, bool $isMake = false, array $options = [], array $filePath = []) { $options['fromField'] = is_array($options['fromField']) ? $options['fromField'] : []; $options['columnField'] = is_array($options['columnField']) ? $options['columnField'] : []; //生成模型 $model = app()->make(Model::class); [$modelContent, $modelPath, $usePath] = $model->setFilePathName($filePath['model'] ?? '')->isMake($isMake)->handle($tableName, $options); //生成dao $dao = app()->make(Dao::class); [$daoContent, $daoPath, $usePath] = $dao->setFilePathName($filePath['dao'] ?? '')->isMake($isMake)->handle($tableName, [ 'usePath' => $usePath, ]); //生成service $service = app()->make(Service::class); [$serviceContent, $servicePath, $usePath] = $service->setFilePathName($filePath['service'] ?? '')->isMake($isMake)->handle($tableName, [ 'field' => $options['fromField'], 'usePath' => $usePath, ]); //生成验证器 $validate = app()->make(Validate::class); [$validateContent, $validatePath] = $validate->setFilePathName($filePath['validate'] ?? '')->isMake($isMake)->handle($tableName); //生成控制器 $controller = app()->make(Controller::class); [$controllerContent, $controllerPath] = $controller->setFilePathName($filePath['controller'] ?? '')->isMake($isMake)->handle($tableName, [ 'usePath' => $usePath ]); //生成路由 $route = app()->make(Route::class); [$routeContent, $routePath] = $route->setFilePathName($filePath['route'] ?? '')->isMake($isMake)->handle($tableName, [ 'menus' => $options['menuName'], 'route' => $routeName ]); //生成前台路由 $viewRouter = app()->make(ViewRouter::class); [$routerContent, $routerPath] = $viewRouter->setFilePathName($filePath['router'] ?? '')->isMake($isMake)->handle($tableName, [ 'route' => $routeName, ]); //生成前台接口 $viewApi = app()->make(ViewApi::class); [$apiContent, $apiPath] = $viewApi->setFilePathName($filePath['api'] ?? '')->isMake($isMake)->handle($tableName, [ 'route' => $routeName, ]); //生成前台页面 $viewPages = app()->make(ViewPages::class); [$pagesContent, $pagesPath] = $viewPages->setFilePathName($filePath['pages'] ?? '')->isMake($isMake)->handle($tableName, [ 'field' => $options['columnField'], 'pathApiJs' => '@/' . str_replace('\\', '/', str_replace([Make::adminTemplatePath(), '.js'], '', $apiPath)), ]); return [ 'controller' => [ 'path' => $this->replace($controllerPath), 'content' => $controllerContent ], 'model' => [ 'path' => $this->replace($modelPath), 'content' => $modelContent ], 'dao' => [ 'path' => $this->replace($daoPath), 'content' => $daoContent ], 'route' => [ 'path' => $this->replace($routePath), 'content' => $routeContent ], 'service' => [ 'path' => $this->replace($servicePath), 'content' => $serviceContent ], 'validate' => [ 'path' => $this->replace($validatePath), 'content' => $validateContent ], 'router' => [ 'path' => $this->replace($routerPath), 'content' => $routerContent ], 'api' => [ 'path' => $this->replace($apiPath), 'content' => $apiContent ], 'pages' => [ 'path' => $this->replace($pagesPath), 'content' => $pagesContent ], ]; } protected function replace(string $path) { return str_replace([app()->getRootPath(), Make::adminTemplatePath()], '', $path); } /** * @param string $tableName * @param bool $fullName * @return string * @author 等风来 * @email 136327134@qq.com * @date 2023/4/7 */ public function getTableName(string $tableName, bool $fullName = true) { $tablePrefix = config('database.connections.mysql.prefix'); $pattern = '/^' . $tablePrefix . '/i'; return ($fullName ? $tablePrefix : '') . (preg_replace($pattern, '', $tableName)); } }