* +---------------------------------------------------------------------- */ namespace app\services\system; use app\dao\system\SystemCrudDao; use app\services\BaseServices; use crmeb\exceptions\AdminException; use crmeb\services\crud\Controller; use crmeb\services\crud\Dao; use crmeb\services\crud\enum\FormTypeEnum; use crmeb\services\crud\enum\SearchEnum; 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 crmeb\services\FileService; use Phinx\Db\Adapter\AdapterFactory; use think\facade\Db; use think\helper\Str; use think\migration\db\Column; use think\migration\db\Table; /** * 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', 'user', 'wechat_user', 'template_message', 'store_order', 'other_order', 'store_order_cart_info', 'store_order_economize', 'store_order_invoice', 'store_order_refund', 'store_order_status', 'store_pink', 'agent_level', 'agent_level_task', 'agent_level_task_record', 'agreement', 'app_version', 'article', 'article_category', 'article_content', 'auxiliary', 'cache', 'capital_flow', 'category', 'diy', 'express', 'lang_type', 'live_anchor', 'live_goods', 'live_room', 'live_room_goods', 'luck_lottery', 'luck_lottery_record', 'luck_prize', 'member_card', 'member_card_batch', 'member_right', 'member_ship', 'message_system', 'other_order', 'other_order_status', 'out_account', 'out_interface', 'page_categroy', 'page_link', 'qrcode', 'shipping_templates', 'shipping_templates_free', 'shipping_templates_no_delivery', 'shipping_templates_region', 'sms_record', 'store_advance', 'store_bargain', 'store_bargain_user', 'store_bargain_user_help', 'store_cart', 'store_category', 'store_combination', 'store_coupon_issue', 'store_coupon_issue_user', 'store_coupon_product', 'store_coupon_user', 'store_integral', 'store_integral_order', 'store_integral_order_status', 'store_order', 'store_order_cart_info', 'store_order_economize', 'store_order_invoice', 'store_order_refund', 'store_order_status', 'store_pink', 'store_product', 'store_product_attr', 'store_product_attr_result', 'store_product_attr_value', 'store_product_cate', 'store_product_coupon', 'store_product_description', 'store_product_log', 'store_product_relation', 'store_service', 'store_service_feedback', 'store_product_reply', 'store_product_rule', 'store_product_virtual', 'store_seckill', 'store_seckill_time', 'store_service_log', 'store_service_record', 'store_service_speechcraft', 'store_visit', 'system_attachment', 'system_attachment_category', 'system_city', 'system_config', 'system_config_tab', 'system_file', 'system_file_info', 'system_group', 'system_group_data', 'system_log', 'system_notice', 'system_notice_admin', 'system_notification', 'system_role', 'system_route', 'system_route_cate', 'system_storage', 'system_store', 'system_store_staff', 'system_timer', 'system_user_level', 'template_message', 'upgrade_log', 'user', 'user_address', 'user_bill', 'user_brokerage', 'user_brokerage_frozen', 'user_cancel', 'user_enter', 'user_extract', 'user_friends', 'user_group', 'user_invoice', 'user_label', 'user_label_relation', 'user_level', 'user_money', 'user_notice', 'user_notice_see', 'user_recharge', 'user_search', 'user_sign', 'user_spread', 'user_visit', 'wechat_key', 'wechat_media', 'wechat_message', 'wechat_news_category', 'wechat_qrcode', 'wechat_qrcode_cate', 'wechat_qrcode_record', 'wechat_reply', 'wechat_user', 'system_crud_data', 'admins', ]; //表字符集 const TABLR_COLLATION = 'utf8mb4_general_ci'; /** * 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,table_comment,table_collation', $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 = [ 'varchar' => 'string', 'int' => 'integer', 'biginteger' => 'bigint', 'tinyint' => 'boolean', ]; return [ 'types' => [ 'varchar', 'char', 'text', 'longtext', 'tinytext', 'enum', 'blob', 'binary', 'varbinary', 'datetime', 'timestamp', 'time', 'date', 'year', 'boolean', 'tinyint', 'int', 'decimal', 'float', 'json', ], 'form' => [ [ 'value' => FormTypeEnum::INPUT, 'label' => '输入框', 'field_type' => 'varchar', 'limit' => 255 ], [ 'value' => FormTypeEnum::NUMBER, 'label' => '数字输入框', 'field_type' => 'int', 'limit' => 11 ], [ 'value' => FormTypeEnum::TEXTAREA, 'label' => '多行文本框', 'field_type' => 'text', 'limit' => null ], [ 'value' => FormTypeEnum::DATE_TIME, 'label' => '单选日期时间', 'field_type' => 'varchar', 'limit' => 200 ], [ 'value' => FormTypeEnum::DATE_TIME_RANGE, 'label' => '日期时间区间选择', 'field_type' => 'varchar', 'limit' => 200 ], [ 'value' => FormTypeEnum::CHECKBOX, 'label' => '多选框', 'field_type' => 'varchar', 'limit' => 200 ], [ 'value' => FormTypeEnum::RADIO, 'label' => '单选框', 'field_type' => 'int', 'limit' => 11 ], [ 'value' => FormTypeEnum::SWITCH, 'label' => '开关', 'field_type' => 'int', 'limit' => 11 ], [ 'value' => FormTypeEnum::SELECT, 'label' => '下拉框', 'field_type' => 'int', 'limit' => 11 ], [ 'value' => FormTypeEnum::FRAME_IMAGE_ONE, 'label' => '单图选择', 'field_type' => 'varchar', 'limit' => 200 ], [ 'value' => FormTypeEnum::FRAME_IMAGES, 'label' => '多图选择', 'field_type' => 'varchar', 'limit' => 200 ], ], 'search_type' => [ [ 'value' => SearchEnum::SEARCH_TYPE_EQ, 'label' => '等于搜索', ], [ 'value' => SearchEnum::SEARCH_TYPE_LTEQ, 'label' => '小于等于搜索', ], [ 'value' => SearchEnum::SEARCH_TYPE_GTEQ, 'label' => '大于等于搜索', ], [ 'value' => SearchEnum::SEARCH_TYPE_NEQ, 'label' => '不等于搜索', ], [ 'value' => SearchEnum::SEARCH_TYPE_LIKE, 'label' => '模糊搜索', ], [ 'value' => SearchEnum::SEARCH_TYPE_BETWEEN, 'label' => '用来时间区间搜索', ], ], 'default_type' => [ [ 'value' => '-1', 'label' => '无', 'disabled' => false, ], [ 'value' => '1', 'label' => '自定义', 'disabled' => true, ], [ 'value' => '2', 'label' => 'NULL', 'disabled' => false, ], [ 'value' => '3', 'label' => 'CURRENT_TIMESTAMP', 'disabled' => false, ], ], '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()['types'])) { throw new AdminException(500044); } return $this->getTabelRule()['rule'][$type] ?? $type; } /** * @param string $tableName * @return mixed * @author 等风来 * @email 136327134@qq.com * @date 2023/4/14 */ public function getTableInfo(string $tableName) { $sql = 'SELECT * FROM `information_schema`.`TABLES` WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?'; $tableInfo = Db::query($sql, [config('database.connections.mysql.database'), $this->getTableName($tableName)]); return $tableInfo[0] ?? []; } /** * 获取表字段 * @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; } /** * 获取当前数据库所有表名 * @return mixed * @author 等风来 * @email 136327134@qq.com * @date 2023/8/2 */ public function getTableAll() { $sql = "SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?"; $tableAll = Db::query($sql, [config('database.connections.mysql.database')]); $data = []; foreach ($tableAll as $item) { $item['TABLE_NAME'] = str_replace(config('database.connections.mysql.prefix'), '', $item['TABLE_NAME']); // if (!in_array($item['TABLE_NAME'], self::NOT_CRUD_TABANAME)) { $data[] = [ 'value' => $item['TABLE_NAME'], 'label' => $item['TABLE_COMMENT'] ?: $item['TABLE_NAME'], ]; // } } return $data; } /** * @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 string $tableName * @param string $field * @param string $changeFiled * @param string $type * @param string $limit * @param string $default * @param string $comment * @param array $options * @return mixed * @author 等风来 * @email 136327134@qq.com * @date 2023/4/24 */ protected function updateAlter(string $tableName, string $field, string $changeFiled, string $prevFiled, string $type, $limit = '', string $default = '', string $comment = '', array $options = []) { $tableName = $this->getTableName($tableName); $comment = addslashes($comment); $field = addslashes($field); $changeFiled = addslashes($changeFiled); $prevFiled = addslashes($prevFiled); $type = addslashes($type); $default = addslashes($default); if ($prevFiled) { $after = "AFTER `$prevFiled`"; } else { $after = ""; } if (isset($options['default_type'])) { switch ($options['default_type']) { case '-1': $default = 'NULL'; break; case '1'://自定义 $default = "NOT NULL DEFAULT '$default'"; break; case '2'://为null $default = 'NULL DEFAULT NULL'; break; case '3'://时间 $default = 'NULL DEFAULT CURRENT_TIMESTAMP'; break; } } if (in_array(strtolower($type), ['text', 'longtext', 'tinytext'])) { $sql = "ALTER TABLE `$tableName` CHANGE `$field` `$changeFiled` $type CHARACTER SET utf8mb4 COLLATE " . self::TABLR_COLLATION . " NULL COMMENT '$comment' $after;"; } else if (strtolower($type) == 'enum') { $enum = []; foreach ($options['options'] as $option) { $enum[] = "'$option'"; } $enumStr = implode(',', $enum); $sql = "ALTER TABLE `$tableName` CHANGE `$field` `$changeFiled` $type($enumStr) $default COMMENT '$comment' $after;"; } else { $sql = "ALTER TABLE `$tableName` CHANGE `$field` `$changeFiled` $type($limit) $default COMMENT '$comment' $after;"; } return Db::execute($sql); } /** * 添加字段 * @param string $tableName * @param string $field * @param string $prevFiled * @param string $type * @param string $limit * @param string $default * @param string $comment * @param array $options * @return mixed * @author 等风来 * @email 136327134@qq.com * @date 2023/4/24 */ public function addAlter(string $tableName, string $field, string $prevFiled, string $type, $limit = '', string $default = '', string $comment = '', array $options = []) { $tableName = $this->getTableName($tableName); $comment = addslashes($comment); $field = addslashes($field); $prevFiled = addslashes($prevFiled); $type = addslashes($type); $default = addslashes($default); if ($prevFiled) { $after = "AFTER `$prevFiled`"; } else { $after = ""; } if (isset($options['default_type'])) { switch ($options['default_type']) { case '-1': $default = 'NULL'; break; case '1'://自定义 $default = "NOT NULL DEFAULT '$default'"; break; case '2'://为null $default = 'NULL DEFAULT NULL'; break; case '3'://时间 $default = 'NULL DEFAULT CURRENT_TIMESTAMP'; break; } } if (in_array(strtolower($type), ['text', 'longtext', 'tinytext'])) { $sql = "ALTER TABLE `$tableName` ADD `$field` $type NULL COMMENT '$comment' $after;"; } else { $defaultSql = $default; //处理时间字段默认值 if (in_array(strtolower($type), ['datetime', 'timestamp', 'time', 'date', 'year'])) { switch ($field) { case 'delete_time': $defaultSql = 'NULL DEFAULT NULL'; break; case 'create_time': case 'update_time': $defaultSql = 'NOT NULL DEFAULT CURRENT_TIMESTAMP'; break; } } //兼容枚举字段 if (strtolower($type) == 'enum') { $enum = []; foreach ($options['options'] as $option) { $enum[] = "'$option'"; } $enumStr = implode(',', $enum); $limitSql = $enumStr ? '(' . $enumStr . ')' : ''; } else { $limitSql = $limit ? '(' . $limit . ')' : ''; } $sql = "ALTER TABLE `$tableName` ADD `$field` $type$limitSql $defaultSql COMMENT '$comment' $after;"; } return Db::execute($sql); } /** * 删除表字段 * @param string $tableName * @param string $field * @return mixed * @author 等风来 * @email 136327134@qq.com * @date 2023/4/24 */ protected function deleteAlter(string $tableName, string $field) { $tableName = $this->getTableName($tableName); $field = addslashes($field); $sql = "ALTER TABLE `$tableName` DROP `$field`"; return Db::execute($sql); } /** * 修改表备注 * @param string $tableName * @param string $common * @return mixed * @author 等风来 * @email 136327134@qq.com * @date 2023/4/24 */ protected function updateFromCommon(string $tableName, string $common) { $tableName = $this->getTableName($tableName); $common = addslashes($common); $sql = "ALTER TABLE `$tableName` COMMENT = '$common';"; return Db::execute($sql); } /** * 对比字段变动了更改 * @param string $tableName * @param array $deleteField * @param array $tableField * @author 等风来 * @email 136327134@qq.com * @date 2023/4/24 */ protected function diffAlter(string $tableName, array $deleteField, array $tableField) { $updateAlter = []; $addAlter = []; $columns = $this->getColumnNamesList($tableName); $fieldAll = array_column($columns, 'name'); //对比数据库字段 foreach ($tableField as $i => $item) { if ($item['primaryKey'] || $item['field'] == 'delete_time') { continue; } $prevFiled = $i ? ($tableField[$i - 1]['field'] ?? 'id') : 'id'; //前台新增的字段进行添加 if (!(isset($item['default_field']) && isset($item['default_field_type']) && isset($item['default_limit']) && isset($item['default_comment']) && isset($item['default_default']) && isset($item['default_default_type'])) ) { if (!in_array($item['field'], $fieldAll)) { $addAlter[] = [ 'prev_filed' => $prevFiled, 'field' => $item['field'], 'limit' => $item['limit'], 'type' => $item['field_type'], 'comment' => $item['comment'], 'default' => $item['default'], 'default_type' => $item['default_type'], 'values' => $item['field_type'] == 'enum' ? $item['limit'] : [], ]; } continue; } else { //从数据库中新增的字段,并没有记录在表中做兼容处理; //默认字段没有在数据库中,需要添加字段; if (!in_array($item['default_field'], $fieldAll)) { $addAlter[] = [ 'prev_filed' => $prevFiled, 'field' => $item['field'], 'limit' => $item['limit'], 'type' => $item['field_type'], 'comment' => $item['comment'], 'default' => $item['default'], 'default_type' => $item['default_type'], 'values' => $item['field_type'] == 'enum' ? $item['limit'] : [], ]; continue; } } if ($item['default_field'] != $item['field'] && in_array($item['field_type'], ['addTimestamps', 'addSoftDelete'])) { throw new AdminException($item['field'] . '字段不允许被更改'); } //数据库表存在的,字段,并且被修改 if (!in_array($item['field'], ['id', 'create_time', 'update_time'])) { $updateAlter[] = [ 'default_field' => $item['default_field'], 'prev_filed' => $prevFiled, 'field' => $item['field'], 'limit' => $item['limit'], 'type' => $item['field_type'], 'comment' => $item['comment'], 'default' => $item['default'], 'default_type' => $item['default_type'], 'values' => $item['field_type'] == 'enum' ? $item['limit'] : [], ]; } } //添加字段 foreach ($addAlter as $item) { $this->addAlter($tableName, $item['field'], $item['prev_filed'], $item['type'], $item['limit'], $item['default'], $item['comment'], [ 'options' => $item['values'], 'default_type' => $item['default_type'], ]); } //删除多余字段 foreach ($deleteField as $item) { $this->deleteAlter($tableName, $item); } //更新数据库字段 foreach ($updateAlter as $item) { $this->updateAlter($tableName, $item['default_field'], $item['field'], $item['prev_filed'], $item['type'], $item['limit'], $item['default'], $item['comment'], [ 'options' => $item['values'], 'default_type' => $item['default_type'], ]); } } /** * 创建 * @param array $data * @return mixed * @author 等风来 * @email 136327134@qq.com * @date 2023/4/11 */ public function createCrud(int $id, array $data) { $tableName = $data['tableName']; $tableField = $this->valueReplace($data['tableField']); $filePath = $this->valueReplace($data['filePath']); $modelName = !empty($data['modelName']) ? $data['modelName'] : $tableName; $tableComment = !empty($data['tableComment']) ? $data['tableComment'] : $modelName; //检测是否为系统表 if (in_array($tableName, self::NOT_CRUD_TABANAME)) { throw new AdminException(500041); } $data['softDelete'] = false; $tableInfo = null; //先检查表存在则 if ($id) { $this->updateFromCommon($tableName, $tableComment); //读取数据库表 $tableInfo = $this->getTableInfo($tableName); if ($tableInfo) { //对比字段进行更新/删除字段 $this->diffAlter($tableName, $data['deleteField'], $tableField); } } else { if ($this->dao->count(['table_name' => $tableName])) { throw new AdminException('表已经被生成过,请在列表中进行修改'); } } //创建数据库 $tableCreateInfo = null; if ($tableField && (!$data['isTable'] || !$tableInfo)) { $tableCreateInfo = $this->makeDatebase($tableName, $tableComment, $tableField); if ($tableCreateInfo['softDelete']) { $data['softDelete'] = true; } } //获取主键 foreach ($tableField as $value) { if ($value['primaryKey']) { $data['key'] = $value['field']; 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' => '/' . $routeName, 'auth_type' => 1, 'is_show' => 1, 'is_show_path' => 1, 'is_del' => 0, 'unique_auth' => $uniqueAuth, 'is_header' => $data['pid'] ? 0 : 1, ]; $crudInfo = null; if ($id) { $crudInfo = $this->dao->get($id); } $res = $this->transaction(function () use ($tableComment, $tableCreateInfo, $crudInfo, $modelName, $filePath, $tableName, $routeName, $data, $dataMenu) { $routeService = app()->make(SystemRouteServices::class); $meunService = app()->make(SystemMenusServices::class); //修改菜单名称 if ($crudInfo) { //菜单存在的时候进行修改 if ($crudInfo->menu_id && $meunService->value(['id' => [$crudInfo->menu_id]], 'id')) { $meunService->update($crudInfo->menu_id, $dataMenu); $menuInfo = (object)['id' => $crudInfo->menu_id]; } else { $menuInfo = $meunService->save($dataMenu); } //删除掉添加的路由权限 if ($crudInfo->route_ids) { $routeService->deleteRoutes($crudInfo->route_ids); } //删除掉权限路由 if ($crudInfo->menu_ids) { app()->make(SystemMenusServices::class)->deleteMenus($crudInfo->menu_ids); } } else { $menuInfo = $meunService->save($dataMenu); } //写入路由权限 $cateId = app()->make(SystemRouteServices::class)->topCateId('adminapi', 'CRUD'); $ruleData = [ [ 'path' => $routeName, 'method' => 'GET', 'name' => $modelName . '列表接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'unique_auth' => '', 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '/create', 'method' => 'GET', 'name' => $modelName . '获取创建表单接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'unique_auth' => Str::snake($tableName) . '-add', 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName, 'method' => 'POST', 'name' => $modelName . '保存接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'unique_auth' => '', 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '//edit', 'method' => 'GET', 'name' => $modelName . '获取修改表单接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'unique_auth' => '', 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '/', 'method' => 'GET', 'name' => $modelName . '查看数据接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'unique_auth' => '', 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '/', 'method' => 'PUT', 'name' => $modelName . '修改接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'unique_auth' => '', 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '/status/', 'method' => 'PUT', 'name' => $modelName . '修改状态接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'unique_auth' => '', 'add_time' => date('Y-m-d H:i:s') ], [ 'path' => $routeName . '/', 'method' => 'DELETE', 'name' => $modelName . '删除接口', 'app_name' => 'adminapi', 'cate_id' => $cateId, 'unique_auth' => '', 'add_time' => date('Y-m-d H:i:s') ], ]; $routeList = $routeService->saveAll($ruleData); $routeIds = array_column($routeList->toArray(), 'id'); //记录权限加入菜单表 $menuData = []; foreach ($ruleData as $item) { $menuData[] = [ 'pid' => $menuInfo->id ?: 0, 'methods' => $item['method'], 'api_url' => $item['path'], 'unique_auth' => $item['unique_auth'], 'menu_name' => $item['name'], 'is_del' => 0, 'auth_type' => 2, ]; } $menus = app()->make(SystemMenusServices::class)->saveAll($menuData); $menuIds = array_column($menus->toArray(), 'id'); //生成文件 $make = $this->makeFile($tableName, $routeName, true, $data, $filePath); $makePath = []; foreach ($make as $key => $item) { $makePath[$key] = $item['path']; } if ($tableCreateInfo && isset($tableCreateInfo['table']) && $tableCreateInfo['table'] instanceof Table) { //创建数据库 $tableCreateInfo['table']->create(); } $crudDate = [ 'pid' => $data['pid'], 'name' => $data['menuName'], 'model_name' => $data['modelName'], 'table_name' => $tableName, 'table_comment' => $tableComment, 'table_collation' => self::TABLR_COLLATION, 'field' => json_encode($data),//提交的数据 'menu_ids' => json_encode($menuIds),//生成的菜单id 'menu_id' => $menuInfo->id,//生成的菜单id 'make_path' => json_encode($makePath), 'route_ids' => json_encode($routeIds), ]; if ($crudInfo) { $res = $this->dao->update($crudInfo->id, $crudDate); } else { $crudDate['add_time'] = time(); //记录crud生成 $res = $this->dao->save($crudDate); } return $res; }); return $res->toArray(); } /** * 获取数据库配置 * @return array */ protected function getDbConfig(): array { $default = app()->config->get('database.default'); $config = app()->config->get("database.connections.{$default}"); if (0 == $config['deploy']) { $dbConfig = [ 'adapter' => $config['type'], 'host' => $config['hostname'], 'name' => $config['database'], 'user' => $config['username'], 'pass' => $config['password'], 'port' => $config['hostport'], 'charset' => $config['charset'], 'table_prefix' => $config['prefix'], ]; } else { $dbConfig = [ 'adapter' => explode(',', $config['type'])[0], 'host' => explode(',', $config['hostname'])[0], 'name' => explode(',', $config['database'])[0], 'user' => explode(',', $config['username'])[0], 'pass' => explode(',', $config['password'])[0], 'port' => explode(',', $config['hostport'])[0], 'charset' => explode(',', $config['charset'])[0], 'table_prefix' => explode(',', $config['prefix'])[0], ]; } $table = app()->config->get('database.migration_table', 'migrations'); $dbConfig['default_migration_table'] = $dbConfig['table_prefix'] . $table; return $dbConfig; } public function getAdapter() { $options = $this->getDbConfig(); $adapter = AdapterFactory::instance()->getAdapter($options['adapter'], $options); if ($adapter->hasOption('table_prefix') || $adapter->hasOption('table_suffix')) { $adapter = AdapterFactory::instance()->getWrapper('prefix', $adapter); } return $adapter; } /** * 创建数据库 * @param string $tableName * @param string $tableComment * @param array $tableField * @return array * @author 等风来 * @email 136327134@qq.com * @date 2023/4/7 */ public function makeDatebase(string $tableName, string $tableComment, array $tableField = [], string $collation = self::TABLR_COLLATION) { $timestampsField = []; $softDelete = false; $timestamps = false; $indexField = []; //创建表 $table = new Table($tableName, ['comment' => $tableComment, 'collation' => $collation], $this->getAdapter()); //创建字段 foreach ($tableField as $item) { if (isset($item['primaryKey']) && $item['primaryKey']) { continue; } $option = []; if (isset($item['limit'])) { $option['limit'] = (int)$item['limit']; } if (isset($item['default']) && isset($item['default_type'])) { switch ($item['default_type']) { case '1'://自定义 $option['default'] = $item['default']; break; case '2'://为null $option['null'] = true; break; case '3'://时间 $option['default'] = Db::raw('CURRENT_TIMESTAMP'); break; } } //创建伪删除 if ($item['field_type'] === 'addSoftDelete') { $table->addSoftDelete(); $softDelete = true; } else if ($item['field_type'] == 'timestamp' && ($item['field'] === 'create_time' || $item['field'] === 'update_time')) { $timestampsField[] = $item; } else { $option['comment'] = $item['comment']; $fieldType = $this->changeTabelRule($item['field_type']); if (in_array($fieldType, ['text', 'longtext', 'tinytext'])) { unset($option['limit']); } //判断字段类型 if ($fieldType == 'boolean' && isset($option['default']) && $option['default'] === '') { unset($option['default']); } //兼容枚举字段 if ($fieldType == 'enum') { unset($option['limit']); $option['values'] = $item['limit']; } $table->addColumn($item['field'], $this->changeTabelRule($item['field_type']), $option); } } //创建索引 if (!empty($data['tableIndex'])) { $indexField = $data['tableIndex']; foreach ($data['tableIndex'] as $item) { $table->addIndex($item); } } //如果是成对出现的create_time和update_time就直接增加修改和添加时间 if (count($timestampsField) == 2) { //创建修改和增加时间 $table->addTimestamps(); $timestamps = true; } else { //如果是一个数组,增加一列 foreach ($timestampsField as $item) { $option['comment'] = $item['comment']; $table->addColumn($item['field'], $this->changeTabelRule($item['field_type']), $option); } } return compact('indexField', 'softDelete', 'timestamps', 'table'); } /** * 创建文件返回文件路径和内容 * @param string $tableName * @param string $routeName * @param bool $isMake * @param array $options * @param array $filePath * @param string $basePath * @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 = [], string $basePath = '') { $options['fromField'] = is_array($options['fromField']) ? $options['fromField'] : []; $options['columnField'] = is_array($options['columnField']) ? $options['columnField'] : []; //生成模型 $model = app()->make(Model::class); $model->setFilePathName($filePath['model'] ?? '')->setbasePath($basePath)->handle($tableName, $options); //生成dao $dao = app()->make(Dao::class); $dao->setFilePathName($filePath['dao'] ?? '')->setbasePath($basePath)->handle($tableName, [ 'usePath' => $model->getUsePath(), 'modelName' => $options['modelName'] ?? '', 'searchField' => $options['searchField'] ?? [], ]); //生成service $service = app()->make(Service::class); $service->setFilePathName($filePath['service'] ?? '')->setbasePath($basePath)->handle($tableName, [ 'field' => $options['fromField'], 'columnField' => $options['columnField'], 'key' => $options['key'], 'usePath' => $dao->getUsePath(), 'modelName' => $options['modelName'] ?? '', 'hasOneField' => $options['hasOneField'] ?? [], ]); //生成验证器 $validate = app()->make(Validate::class); $validate->setFilePathName($filePath['validate'] ?? '')->setbasePath($basePath)->handle($tableName, [ 'field' => $options['fromField'], 'modelName' => $options['modelName'] ?? '', ]); //生成控制器 $controller = app()->make(Controller::class); $controller->setFilePathName($filePath['controller'] ?? '')->setbasePath($basePath)->handle($tableName, [ 'usePath' => $service->getUsePath(), 'modelName' => $options['modelName'] ?? '', 'searchField' => $options['searchField'] ?? [], 'columnField' => $options['columnField'] ?? [], 'validateName' => '\\' . str_replace('/', '\\', $validate->getUsePath()) . 'Validate::class', 'field' => array_column($options['fromField'], 'field'), ]); //生成路由 $route = app()->make(Route::class); $route->setFilePathName($filePath['route'] ?? '')->setbasePath($basePath)->handle($tableName, [ 'menus' => $options['modelName'] ?? $options['menuName'], 'route' => $routeName ]); //生成前台路由 $viewRouter = app()->make(ViewRouter::class); $viewRouter->setFilePathName($filePath['router'] ?? '')->setbasePath($basePath)->handle($tableName, [ 'route' => $routeName, 'menuName' => $options['menuName'], 'modelName' => $options['modelName'] ?? $options['menuName'], ]); //生成前台接口 $viewApi = app()->make(ViewApi::class); $viewApi->setFilePathName($filePath['api'] ?? '')->setbasePath($basePath)->handle($tableName, [ 'route' => $routeName, ]); //生成前台页面 $viewPages = app()->make(ViewPages::class); $viewPages->setFilePathName($filePath['pages'] ?? '')->setbasePath($basePath)->handle($tableName, [ 'field' => $options['columnField'], 'tableFields' => $options['tableField'] ?? [], 'searchField' => $options['searchField'] ?? [], 'route' => $routeName, 'key' => $options['key'], 'pathApiJs' => '@/' . str_replace('\\', '/', str_replace([Make::adminTemplatePath(), '.js'], '', $viewApi->getPath())), ]); //创建文件 if ($isMake) { FileService::batchMakeFiles([$model, $validate, $dao, $service, $controller, $route, $viewApi, $viewPages, $viewRouter]); } return [ 'controller' => [ 'path' => $this->replace($controller->getPath()), 'content' => $controller->getContent() ], 'model' => [ 'path' => $this->replace($model->getPath()), 'content' => $model->getContent() ], 'dao' => [ 'path' => $this->replace($dao->getPath()), 'content' => $dao->getContent() ], 'route' => [ 'path' => $this->replace($route->getPath()), 'content' => $route->getContent() ], 'service' => [ 'path' => $this->replace($service->getPath()), 'content' => $service->getContent() ], 'validate' => [ 'path' => $this->replace($validate->getPath()), 'content' => $validate->getContent() ], 'router' => [ 'path' => $this->replace($viewRouter->getPath()), 'content' => $viewRouter->getContent() ], 'api' => [ 'path' => $this->replace($viewApi->getPath()), 'content' => $viewApi->getContent() ], 'pages' => [ 'path' => $this->replace($viewPages->getPath()), 'content' => $viewPages->getContent() ], ]; } 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)); } }