From 53d8aede667e74c1f6ea9870cb027f0a59602849 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 24 Oct 2022 18:46:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E6=94=B9=E5=AF=86=E5=8F=8A=E6=9D=83=E9=99=90=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/databse_mysql.go | 19 ++- backend/app/dto/database.go | 26 ++- backend/app/model/database_mysql.go | 14 +- backend/app/repo/databse_mysql.go | 17 ++ backend/app/service/database_mysql.go | 180 ++++++++++++++++---- backend/router/ro_database.go | 1 + frontend/src/api/interface/database.ts | 10 +- frontend/src/api/modules/database.ts | 4 +- frontend/src/views/database/mysql/index.vue | 120 +++++++++++-- 9 files changed, 323 insertions(+), 68 deletions(-) diff --git a/backend/app/api/v1/databse_mysql.go b/backend/app/api/v1/databse_mysql.go index 8522e8c06..088812db7 100644 --- a/backend/app/api/v1/databse_mysql.go +++ b/backend/app/api/v1/databse_mysql.go @@ -25,6 +25,23 @@ func (b *BaseApi) CreateMysql(c *gin.Context) { helper.SuccessWithData(c, nil) } +func (b *BaseApi) UpdateMysql(c *gin.Context) { + var req dto.ChangeDBInfo + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := mysqlService.ChangeInfo(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + func (b *BaseApi) SearchMysql(c *gin.Context) { var req dto.SearchWithPage if err := c.ShouldBindJSON(&req); err != nil { @@ -73,7 +90,7 @@ func (b *BaseApi) LoadStatus(c *gin.Context) { } func (b *BaseApi) LoadConf(c *gin.Context) { - data, err := mysqlService.LoadConf("") + data, err := mysqlService.LoadVariables("") if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return diff --git a/backend/app/dto/database.go b/backend/app/dto/database.go index 2c3a30f36..26abc1abf 100644 --- a/backend/app/dto/database.go +++ b/backend/app/dto/database.go @@ -14,13 +14,13 @@ type MysqlDBInfo struct { } type MysqlDBCreate struct { - Name string `json:"name" validate:"required"` - Format string `json:"format" validate:"required,oneof=utf8mb4 utf-8 gbk big5"` - Username string `json:"username" validate:"required"` - Password string `json:"password" validate:"required"` - Permission string `json:"permission" validate:"required,oneof=local all ip"` - PermissionIPs string `json:"permissionIPs"` - Description string `json:"description"` + Name string `json:"name" validate:"required"` + Version string `json:"version" validate:"required,oneof=5.7.39 8.0.30"` + Format string `json:"format" validate:"required,oneof=utf8mb4 utf-8 gbk big5"` + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + Permission string `json:"permission" validate:"required"` + Description string `json:"description"` } type MysqlStatus struct { @@ -61,7 +61,7 @@ type MysqlStatus struct { Position string `json:"Position"` } -type MysqlConf struct { +type MysqlVariables struct { BinlogCachSize string `json:"binlog_cache_size"` InnodbBufferPoolSize string `json:"innodb_buffer_pool_size"` InnodbLogBufferSize string `json:"innodb_log_buffer_size"` @@ -79,3 +79,13 @@ type MysqlConf struct { ThreadStack string `json:"thread_stack"` Tmp_tableSize string `json:"tmp_table_size"` } + +type ChangeDBInfo struct { + ID uint `json:"id" validate:"required"` + Operation string `json:"operation" validate:"required,oneof=password privilege"` + Value string `json:"value" validate:"required"` +} + +type BatchDeleteByName struct { + Names []string `json:"names" validate:"required"` +} diff --git a/backend/app/model/database_mysql.go b/backend/app/model/database_mysql.go index 1ba25ba05..998cf6ad4 100644 --- a/backend/app/model/database_mysql.go +++ b/backend/app/model/database_mysql.go @@ -2,11 +2,11 @@ package model type DatabaseMysql struct { BaseModel - Name string `json:"name" gorm:"type:varchar(256);not null"` - Format string `json:"format" gorm:"type:varchar(64);not null"` - Username string `json:"username" gorm:"type:varchar(256);not null"` - Password string `json:"password" gorm:"type:varchar(256);not null"` - Permission string `json:"permission" gorm:"type:varchar(256);not null"` - PermissionIPs string `json:"permissionIPs" gorm:"type:varchar(256)"` - Description string `json:"description" gorm:"type:varchar(256);"` + Name string `json:"name" gorm:"type:varchar(256);not null"` + Version string `json:"version" gorm:"type:varchar(64);not null"` + Format string `json:"format" gorm:"type:varchar(64);not null"` + Username string `json:"username" gorm:"type:varchar(256);not null"` + Password string `json:"password" gorm:"type:varchar(256);not null"` + Permission string `json:"permission" gorm:"type:varchar(256);not null"` + Description string `json:"description" gorm:"type:varchar(256);"` } diff --git a/backend/app/repo/databse_mysql.go b/backend/app/repo/databse_mysql.go index cb09d3f62..6c9149b4e 100644 --- a/backend/app/repo/databse_mysql.go +++ b/backend/app/repo/databse_mysql.go @@ -8,9 +8,12 @@ import ( type MysqlRepo struct{} type IMysqlRepo interface { + Get(opts ...DBOption) (model.DatabaseMysql, error) + List(opts ...DBOption) ([]model.DatabaseMysql, error) Page(limit, offset int, opts ...DBOption) (int64, []model.DatabaseMysql, error) Create(mysql *model.DatabaseMysql) error Delete(opts ...DBOption) error + Update(id uint, vars map[string]interface{}) error } func NewIMysqlRepo() IMysqlRepo { @@ -27,6 +30,16 @@ func (u *MysqlRepo) Get(opts ...DBOption) (model.DatabaseMysql, error) { return mysql, err } +func (u *MysqlRepo) List(opts ...DBOption) ([]model.DatabaseMysql, error) { + var users []model.DatabaseMysql + db := global.DB.Model(&model.DatabaseMysql{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&users).Error + return users, err +} + func (u *MysqlRepo) Page(page, size int, opts ...DBOption) (int64, []model.DatabaseMysql, error) { var users []model.DatabaseMysql db := global.DB.Model(&model.DatabaseMysql{}) @@ -50,3 +63,7 @@ func (u *MysqlRepo) Delete(opts ...DBOption) error { } return db.Delete(&model.DatabaseMysql{}).Error } + +func (u *MysqlRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.DatabaseMysql{}).Where("id = ?", id).Updates(vars).Error +} diff --git a/backend/app/service/database_mysql.go b/backend/app/service/database_mysql.go index 1fc312c2f..6bb514a54 100644 --- a/backend/app/service/database_mysql.go +++ b/backend/app/service/database_mysql.go @@ -4,6 +4,8 @@ import ( "database/sql" "encoding/json" "fmt" + "strconv" + "time" "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/constant" @@ -17,15 +19,41 @@ type MysqlService struct{} type IMysqlService interface { SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) Create(mysqlDto dto.MysqlDBCreate) error + ChangeInfo(info dto.ChangeDBInfo) error Delete(ids []uint) error LoadStatus(version string) (*dto.MysqlStatus, error) - LoadConf(version string) (*dto.MysqlConf, error) + LoadVariables(version string) (*dto.MysqlVariables, error) } func NewIMysqlService() IMysqlService { return &MysqlService{} } +func newDatabaseClient() (*sql.DB, error) { + connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8", "root", "Calong@2015", "localhost", 2306) + db, err := sql.Open("mysql", connArgs) + if err != nil { + return nil, err + } + return db, nil +} +func handleSql(db *sql.DB, query string) (map[string]string, error) { + variableMap := make(map[string]string) + rows, err := db.Query(query) + if err != nil { + return variableMap, err + } + + for rows.Next() { + var variableName, variableValue string + if err := rows.Scan(&variableName, &variableValue); err != nil { + return variableMap, err + } + variableMap[variableName] = variableValue + } + return variableMap, err +} + func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) { total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info)) var dtoMysqls []dto.MysqlDBInfo @@ -40,6 +68,9 @@ func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interfa } func (u *MysqlService) Create(mysqlDto dto.MysqlDBCreate) error { + if mysqlDto.Username == "root" { + return errors.New("Cannot set root as user name") + } mysql, _ := mysqlRepo.Get(commonRepo.WithByName(mysqlDto.Name)) if mysql.ID != 0 { return constant.ErrRecordExist @@ -47,6 +78,25 @@ func (u *MysqlService) Create(mysqlDto dto.MysqlDBCreate) error { if err := copier.Copy(&mysql, &mysqlDto); err != nil { return errors.WithMessage(constant.ErrStructTransform, err.Error()) } + sql, err := newDatabaseClient() + if err != nil { + return err + } + defer sql.Close() + if _, err := sql.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s CHARACTER SET=%s", mysqlDto.Name, mysqlDto.Format)); err != nil { + return err + } + tmpPermission := mysqlDto.Permission + if _, err := sql.Exec(fmt.Sprintf("CREATE USER '%s'@'%s' IDENTIFIED BY '%s';", mysqlDto.Name, tmpPermission, mysqlDto.Password)); err != nil { + return err + } + grantStr := fmt.Sprintf("GRANT ALL PRIVILEGES ON %s.* TO '%s'@'%s'", mysqlDto.Name, mysqlDto.Username, tmpPermission) + if mysqlDto.Version == "5.7.39" { + grantStr = fmt.Sprintf("%s IDENTIFIED BY '%s' WITH GRANT OPTION;", grantStr, mysqlDto.Password) + } + if _, err := sql.Exec(grantStr); err != nil { + return err + } if err := mysqlRepo.Create(&mysql); err != nil { return err } @@ -54,38 +104,78 @@ func (u *MysqlService) Create(mysqlDto dto.MysqlDBCreate) error { } func (u *MysqlService) Delete(ids []uint) error { - if len(ids) == 1 { - mysql, _ := mysqlRepo.Get(commonRepo.WithByID(ids[0])) - if mysql.ID == 0 { - return constant.ErrRecordNotFound - } - return mysqlRepo.Delete(commonRepo.WithByID(ids[0])) + dbClient, err := newDatabaseClient() + if err != nil { + return err } - return mysqlRepo.Delete(commonRepo.WithIdsIn(ids)) + defer dbClient.Close() + dbs, err := mysqlRepo.List(commonRepo.WithIdsIn(ids)) + if err != nil { + return err + } + + for _, db := range dbs { + if len(db.Name) != 0 { + if _, err := dbClient.Exec(fmt.Sprintf("DROP USER IF EXISTS '%s'@'%s'", db.Name, db.Permission)); err != nil { + return err + } + if _, err := dbClient.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", db.Name)); err != nil { + return err + } + } + _ = mysqlRepo.Delete(commonRepo.WithByID(db.ID)) + } + return nil } -func (u *MysqlService) LoadConf(version string) (*dto.MysqlConf, error) { - connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8", "root", "Calong@2015", "localhost", 2306) - db, err := sql.Open("mysql", connArgs) +func (u *MysqlService) ChangeInfo(info dto.ChangeDBInfo) error { + mysql, err := mysqlRepo.Get(commonRepo.WithByID(info.ID)) + if err != nil { + return err + } + db, err := newDatabaseClient() + if err != nil { + return err + } + defer db.Close() + if info.Operation == "password" { + if _, err := db.Exec(fmt.Sprintf("SET PASSWORD FOR %s@%s = password('%s')", mysql.Username, mysql.Permission, info.Value)); err != nil { + return err + } + _ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"password": info.Value}) + return nil + } + + if _, err := db.Exec(fmt.Sprintf("DROP USER IF EXISTS '%s'@'%s'", mysql.Name, mysql.Permission)); err != nil { + return err + } + grantStr := fmt.Sprintf("GRANT ALL PRIVILEGES ON %s.* TO '%s'@'%s'", mysql.Name, mysql.Username, info.Value) + if mysql.Version == "5.7.39" { + grantStr = fmt.Sprintf("%s IDENTIFIED BY '%s' WITH GRANT OPTION;", grantStr, mysql.Password) + } + if _, err := db.Exec(grantStr); err != nil { + return err + } + if _, err := db.Exec("FLUSH PRIVILEGES"); err != nil { + return err + } + _ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"permission": info.Value}) + + return nil +} + +func (u *MysqlService) LoadVariables(version string) (*dto.MysqlVariables, error) { + db, err := newDatabaseClient() if err != nil { return nil, err } defer db.Close() - rows, err := db.Query("show variables") + variableMap, err := handleSql(db, "SHOW VARIABLES") if err != nil { return nil, err } - variableMap := make(map[string]string) - for rows.Next() { - var variableName, variableValue string - if err := rows.Scan(&variableName, &variableValue); err != nil { - continue - } - variableMap[variableName] = variableValue - } - - var info dto.MysqlConf + var info dto.MysqlVariables arr, err := json.Marshal(variableMap) if err != nil { return nil, err @@ -95,31 +185,51 @@ func (u *MysqlService) LoadConf(version string) (*dto.MysqlConf, error) { } func (u *MysqlService) LoadStatus(version string) (*dto.MysqlStatus, error) { - connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8", "root", "Calong@2015", "localhost", 2306) - db, err := sql.Open("mysql", connArgs) + db, err := newDatabaseClient() if err != nil { return nil, err } defer db.Close() - rows, err := db.Query("show status") + globalMap, err := handleSql(db, "SHOW GLOBAL STATUS") if err != nil { return nil, err } - variableMap := make(map[string]string) - for rows.Next() { - var variableName, variableValue string - if err := rows.Scan(&variableName, &variableValue); err != nil { - continue - } - variableMap[variableName] = variableValue - } - var info dto.MysqlStatus - arr, err := json.Marshal(variableMap) + arr, err := json.Marshal(globalMap) if err != nil { return nil, err } _ = json.Unmarshal(arr, &info) + + if value, ok := globalMap["Run"]; ok { + uptime, _ := strconv.Atoi(value) + info.Run = time.Unix(time.Now().Unix()-int64(uptime), 0).Format("2006-01-02 15:04:05") + } else { + if value, ok := globalMap["Uptime"]; ok { + uptime, _ := strconv.Atoi(value) + info.Run = time.Unix(time.Now().Unix()-int64(uptime), 0).Format("2006-01-02 15:04:05") + } + } + + rows, err := db.Query("SHOW MASTER STATUS") + if err != nil { + return &info, err + } + masterRows := make([]string, 5) + for rows.Next() { + if err := rows.Scan(&masterRows[0], &masterRows[1], &masterRows[2], &masterRows[3], &masterRows[4]); err != nil { + return &info, err + } + } + info.File = masterRows[0] + if len(masterRows[0]) == 0 { + info.File = "OFF" + } + info.Position = masterRows[1] + if len(masterRows[1]) == 0 { + info.Position = "OFF" + } + return &info, nil } diff --git a/backend/router/ro_database.go b/backend/router/ro_database.go index bce0a2f24..ba728206c 100644 --- a/backend/router/ro_database.go +++ b/backend/router/ro_database.go @@ -22,6 +22,7 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) { baseApi := v1.ApiGroupApp.BaseApi { withRecordRouter.POST("", baseApi.CreateMysql) + withRecordRouter.PUT("/:id", baseApi.UpdateMysql) withRecordRouter.POST("/del", baseApi.DeleteMysql) cmdRouter.POST("/search", baseApi.SearchMysql) cmdRouter.GET("/conf", baseApi.LoadConf) diff --git a/frontend/src/api/interface/database.ts b/frontend/src/api/interface/database.ts index 8b4965e75..8deed946e 100644 --- a/frontend/src/api/interface/database.ts +++ b/frontend/src/api/interface/database.ts @@ -7,16 +7,15 @@ export namespace Database { username: string; password: string; permission: string; - permissionIPs: string; description: string; } export interface MysqlDBCreate { name: string; + version: string; format: string; username: string; password: string; permission: string; - permissionIPs: string; description: string; } export interface MysqlVariables { @@ -26,9 +25,7 @@ export namespace Database { join_buffer_size: number; key_buffer_size: number; max_connections: number; - max_heap_table_size: number; query_cache_size: number; - query_cache_type: string; read_buffer_size: number; read_rnd_buffer_size: number; sort_buffer_size: number; @@ -74,4 +71,9 @@ export namespace Database { File: string; Position: number; } + export interface ChangeInfo { + id: number; + operation: string; + value: string; + } } diff --git a/frontend/src/api/modules/database.ts b/frontend/src/api/modules/database.ts index 4bf018b10..499d505c3 100644 --- a/frontend/src/api/modules/database.ts +++ b/frontend/src/api/modules/database.ts @@ -9,7 +9,9 @@ export const searchMysqlDBs = (params: ReqPage) => { export const addMysqlDB = (params: Database.MysqlDBCreate) => { return http.post(`/databases`, params); }; - +export const updateMysqlDBInfo = (params: Database.ChangeInfo) => { + return http.put(`/databases/${params.id}`, params); +}; export const deleteMysqlDB = (params: { ids: number[] }) => { return http.post(`/databases/del`, params); }; diff --git a/frontend/src/views/database/mysql/index.vue b/frontend/src/views/database/mysql/index.vue index 8d4eda614..837b49fab 100644 --- a/frontend/src/views/database/mysql/index.vue +++ b/frontend/src/views/database/mysql/index.vue @@ -24,12 +24,12 @@ style="margin-top: 20px; margin-left: 10px" size="default" icon="Back" - @click="isOnSetting = false" + @click="onBacklist" > {{ $t('commons.button.back') }}列表 - + @@ -61,6 +61,50 @@ + + + + +
+ + + + + + +
+
+ + + + + + + + + + +
+
+
+ +
+ @@ -72,10 +116,13 @@ import Setting from '@/views/database/mysql/setting/index.vue'; import Submenu from '@/views/database/index.vue'; import { dateFromat } from '@/utils/util'; import { onMounted, reactive, ref } from 'vue'; -import { deleteMysqlDB, searchMysqlDBs } from '@/api/modules/database'; +import { deleteMysqlDB, searchMysqlDBs, updateMysqlDBInfo } from '@/api/modules/database'; import i18n from '@/lang'; import { Cronjob } from '@/api/interface/cronjob'; import { useDeleteData } from '@/hooks/use-delete-data'; +import { ElForm, ElMessage } from 'element-plus'; +import { Database } from '@/api/interface/database'; +import { Rules } from '@/global/form-rules'; const selects = ref([]); const version = ref('5.7.39'); @@ -90,13 +137,49 @@ const paginationConfig = reactive({ const dialogRef = ref(); const onOpenDialog = async () => { - dialogRef.value!.acceptParams(); + let params = { + version: version.value, + }; + dialogRef.value!.acceptParams(params); }; const settingRef = ref(); const onSetting = async () => { isOnSetting.value = true; - console.log(settingRef.value); + let params = { + version: version.value, + }; + settingRef.value!.acceptParams(params); +}; +const onBacklist = async () => { + isOnSetting.value = false; + search(); + settingRef.value!.onClose(); +}; + +const changeVisiable = ref(false); +type FormInstance = InstanceType; +const changeFormRef = ref(); +const changeForm = reactive({ + id: 0, + userName: '', + password: '', + operation: '', + privilege: '', + privilegeIPs: '', + value: '', +}); +const submitChangeInfo = async (formEl: FormInstance | undefined) => { + console.log(formEl); + if (!formEl) return; + formEl.validate(async (valid) => { + if (!valid) return; + changeForm.value = changeForm.operation === 'password' ? changeForm.password : changeForm.privilege; + await updateMysqlDBInfo(changeForm); + ElMessage.success(i18n.global.t('commons.msg.operationSuccess')); + search(); + changeVisiable.value = false; + }); }; const search = async () => { @@ -123,29 +206,42 @@ const onBatchDelete = async (row: Cronjob.CronjobInfo | null) => { }; const buttons = [ { - label: i18n.global.t('commons.button.edit'), - icon: 'Delete', - click: (row: Cronjob.CronjobInfo) => { - onBatchDelete(row); + label: i18n.global.t('database.changePassword'), + click: (row: Database.MysqlDBInfo) => { + changeForm.id = row.id; + changeForm.operation = 'password'; + changeForm.userName = row.username; + changeForm.password = row.password; + changeVisiable.value = true; + }, + }, + { + label: i18n.global.t('database.permission'), + click: (row: Database.MysqlDBInfo) => { + changeForm.id = row.id; + changeForm.operation = 'privilege'; + if (row.permission === '%' || row.permission === 'localhost') { + changeForm.privilege = row.permission; + } else { + changeForm.privilege = 'ip'; + } + changeVisiable.value = true; }, }, { label: i18n.global.t('database.backupList') + '(1)', - icon: 'Delete', click: (row: Cronjob.CronjobInfo) => { onBatchDelete(row); }, }, { label: i18n.global.t('database.loadBackup'), - icon: 'Delete', click: (row: Cronjob.CronjobInfo) => { onBatchDelete(row); }, }, { label: i18n.global.t('commons.button.delete'), - icon: 'Delete', click: (row: Cronjob.CronjobInfo) => { onBatchDelete(row); },