From 37dee0dd8103704aa4f6336d9fe6589f61dd361c Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 31 Oct 2022 17:26:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=20redis=20=E5=A2=9E?= =?UTF-8?q?=E5=88=A0=E6=94=B9=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{databse_mysql.go => database_mysql.go} | 0 backend/app/api/v1/database_redis.go | 85 +++++++ backend/app/api/v1/entry.go | 1 + backend/app/dto/backup.go | 11 +- backend/app/dto/database.go | 26 +++ backend/app/model/backup.go | 1 + backend/app/repo/databse_mysql.go | 4 +- backend/app/service/database_mysql.go | 21 +- backend/app/service/database_redis.go | 130 +++++++++++ backend/app/service/database_test.go | 64 ++++-- backend/app/service/entry.go | 1 + backend/router/ro_database.go | 5 + frontend/src/api/interface/backup.ts | 1 + frontend/src/api/interface/database.ts | 23 ++ frontend/src/api/modules/database.ts | 14 ++ frontend/src/lang/modules/zh.ts | 10 + frontend/src/routers/modules/database.ts | 2 +- .../src/views/database/mysql/backup/index.vue | 4 +- frontend/src/views/database/redis/index.vue | 207 ++++++++++++++---- go.mod | 3 + go.sum | 14 ++ 21 files changed, 547 insertions(+), 80 deletions(-) rename backend/app/api/v1/{databse_mysql.go => database_mysql.go} (100%) create mode 100644 backend/app/api/v1/database_redis.go create mode 100644 backend/app/service/database_redis.go diff --git a/backend/app/api/v1/databse_mysql.go b/backend/app/api/v1/database_mysql.go similarity index 100% rename from backend/app/api/v1/databse_mysql.go rename to backend/app/api/v1/database_mysql.go diff --git a/backend/app/api/v1/database_redis.go b/backend/app/api/v1/database_redis.go new file mode 100644 index 000000000..255c4a55a --- /dev/null +++ b/backend/app/api/v1/database_redis.go @@ -0,0 +1,85 @@ +package v1 + +import ( + "errors" + "strconv" + + "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) SetRedis(c *gin.Context) { + var req dto.RedisDataSet + 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 := redisService.Set(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) SearchRedis(c *gin.Context) { + var req dto.SearchRedisWithPage + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + total, list, err := redisService.SearchWithPage(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +func (b *BaseApi) DeleteRedis(c *gin.Context) { + var req dto.RedisDelBatch + 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 := redisService.Delete(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) CleanRedis(c *gin.Context) { + db, ok := c.Params.Get("db") + if !ok { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error db in path")) + return + } + dbNum, err := strconv.Atoi(db) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error db in path")) + return + } + + if err := redisService.CleanAll(dbNum); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/api/v1/entry.go b/backend/app/api/v1/entry.go index 6564f8403..597dddace 100644 --- a/backend/app/api/v1/entry.go +++ b/backend/app/api/v1/entry.go @@ -19,6 +19,7 @@ var ( imageService = service.ServiceGroupApp.ImageService mysqlService = service.ServiceGroupApp.MysqlService + redisService = service.ServiceGroupApp.RedisService cronjobService = service.ServiceGroupApp.CronjobService diff --git a/backend/app/dto/backup.go b/backend/app/dto/backup.go index 6b377aa6d..1ecb8dfb0 100644 --- a/backend/app/dto/backup.go +++ b/backend/app/dto/backup.go @@ -25,11 +25,12 @@ type BackupSearch struct { } type BackupRecords struct { - ID uint `json:"id"` - CreatedAt time.Time `json:"createdAt"` - Source string `json:"source"` - FileDir string `json:"fileDir"` - FileName string `json:"fileName"` + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Source string `json:"source"` + BackupType string `json:"backupType"` + FileDir string `json:"fileDir"` + FileName string `json:"fileName"` } type DownloadRecord struct { diff --git a/backend/app/dto/database.go b/backend/app/dto/database.go index 0c29567c4..e28f15d01 100644 --- a/backend/app/dto/database.go +++ b/backend/app/dto/database.go @@ -135,3 +135,29 @@ type RecoverDB struct { DBName string `json:"dbName" validate:"required"` BackupName string `json:"backupName" validate:"required"` } + +// redis +type SearchRedisWithPage struct { + PageInfo + DB int `json:"db" validate:"required"` +} + +type RedisData struct { + Key string `json:"key"` + Value string `json:"value"` + Type string `json:"type"` + Length int64 `json:"length"` + Expiration int64 `json:"expiration"` +} + +type RedisDataSet struct { + DB int `json:"db"` + Key string `json:"key" validate:"required"` + Value string `json:"value" validate:"required"` + Expiration int64 `json:"expiration"` +} + +type RedisDelBatch struct { + DB int `json:"db" validate:"required"` + Names []string `json:"names" validate:"required"` +} diff --git a/backend/app/model/backup.go b/backend/app/model/backup.go index 54fc49d9f..a31c3e370 100644 --- a/backend/app/model/backup.go +++ b/backend/app/model/backup.go @@ -14,6 +14,7 @@ type BackupRecord struct { Name string `gorm:"type:varchar(64);not null" json:"name"` DetailName string `gorm:"type:varchar(256)" json:"detailName"` Source string `gorm:"type:varchar(256)" json:"source"` + BackupType string `gorm:"type:varchar(256)" json:"backupType"` FileDir string `gorm:"type:varchar(256)" json:"fileDir"` FileName string `gorm:"type:varchar(256)" json:"fileName"` } diff --git a/backend/app/repo/databse_mysql.go b/backend/app/repo/databse_mysql.go index 21f649747..51db39042 100644 --- a/backend/app/repo/databse_mysql.go +++ b/backend/app/repo/databse_mysql.go @@ -20,7 +20,7 @@ type IMysqlRepo interface { Delete(opts ...DBOption) error Update(id uint, vars map[string]interface{}) error LoadRunningVersion() ([]string, error) - LoadBaseInfoByVersion(key string) (*RootInfo, error) + LoadBaseInfoByKey(key string) (*RootInfo, error) UpdateMysqlConf(id uint, vars map[string]interface{}) error } @@ -90,7 +90,7 @@ type RootInfo struct { Env string `json:"env"` } -func (u *MysqlRepo) LoadBaseInfoByVersion(key string) (*RootInfo, error) { +func (u *MysqlRepo) LoadBaseInfoByKey(key string) (*RootInfo, error) { var ( app model.App appInstall model.AppInstall diff --git a/backend/app/service/database_mysql.go b/backend/app/service/database_mysql.go index 8c876831a..00be98f11 100644 --- a/backend/app/service/database_mysql.go +++ b/backend/app/service/database_mysql.go @@ -66,7 +66,7 @@ func (u *MysqlService) ListDBByVersion(version string) ([]string, error) { } func (u *MysqlService) SearchBackupsWithPage(search dto.SearchBackupsWithPage) (int64, interface{}, error) { - app, err := mysqlRepo.LoadBaseInfoByVersion(search.Version) + app, err := mysqlRepo.LoadBaseInfoByKey(search.Version) if err != nil { return 0, nil, err } @@ -96,7 +96,7 @@ func (u *MysqlService) Create(mysqlDto dto.MysqlDBCreate) error { return errors.WithMessage(constant.ErrStructTransform, err.Error()) } - app, err := mysqlRepo.LoadBaseInfoByVersion(mysqlDto.Version) + app, err := mysqlRepo.LoadBaseInfoByKey(mysqlDto.Version) if err != nil { return err } @@ -138,7 +138,7 @@ func (u *MysqlService) Backup(db dto.BackupDB) error { } func (u *MysqlService) Recover(db dto.RecoverDB) error { - app, err := mysqlRepo.LoadBaseInfoByVersion(db.Version) + app, err := mysqlRepo.LoadBaseInfoByKey(db.Version) if err != nil { return err } @@ -163,7 +163,7 @@ func (u *MysqlService) Recover(db dto.RecoverDB) error { } func (u *MysqlService) Delete(version string, ids []uint) error { - app, err := mysqlRepo.LoadBaseInfoByVersion(version) + app, err := mysqlRepo.LoadBaseInfoByKey(version) if err != nil { return err } @@ -198,7 +198,7 @@ func (u *MysqlService) ChangeInfo(info dto.ChangeDBInfo) error { return err } } - app, err := mysqlRepo.LoadBaseInfoByVersion(info.Version) + app, err := mysqlRepo.LoadBaseInfoByKey(info.Version) if err != nil { return err } @@ -266,7 +266,7 @@ func (u *MysqlService) ChangeInfo(info dto.ChangeDBInfo) error { } func (u *MysqlService) UpdateVariables(variables dto.MysqlVariablesUpdate) error { - app, err := mysqlRepo.LoadBaseInfoByVersion(variables.Version) + app, err := mysqlRepo.LoadBaseInfoByKey(variables.Version) if err != nil { return err } @@ -320,7 +320,7 @@ func (u *MysqlService) UpdateVariables(variables dto.MysqlVariablesUpdate) error func (u *MysqlService) LoadBaseInfo(version string) (*dto.DBBaseInfo, error) { var data dto.DBBaseInfo - app, err := mysqlRepo.LoadBaseInfoByVersion(version) + app, err := mysqlRepo.LoadBaseInfoByKey(version) if err != nil { return nil, err } @@ -342,7 +342,7 @@ func (u *MysqlService) LoadBaseInfo(version string) (*dto.DBBaseInfo, error) { } func (u *MysqlService) LoadVariables(version string) (*dto.MysqlVariables, error) { - app, err := mysqlRepo.LoadBaseInfoByVersion(version) + app, err := mysqlRepo.LoadBaseInfoByKey(version) if err != nil { return nil, err } @@ -360,7 +360,7 @@ func (u *MysqlService) LoadVariables(version string) (*dto.MysqlVariables, error } func (u *MysqlService) LoadStatus(version string) (*dto.MysqlStatus, error) { - app, err := mysqlRepo.LoadBaseInfoByVersion(version) + app, err := mysqlRepo.LoadBaseInfoByKey(version) if err != nil { return nil, err } @@ -444,7 +444,7 @@ func excuteSql(containerName, password, command string) error { } func backupMysql(backupType, baseDir, backupDir, version, dbName, fileName string) error { - app, err := mysqlRepo.LoadBaseInfoByVersion(version) + app, err := mysqlRepo.LoadBaseInfoByKey(version) if err != nil { return err } @@ -471,6 +471,7 @@ func backupMysql(backupType, baseDir, backupDir, version, dbName, fileName strin Name: app.Name, DetailName: dbName, Source: backupType, + BackupType: backupType, FileDir: backupDir, FileName: fileName, } diff --git a/backend/app/service/database_redis.go b/backend/app/service/database_redis.go new file mode 100644 index 000000000..3b562bd0a --- /dev/null +++ b/backend/app/service/database_redis.go @@ -0,0 +1,130 @@ +package service + +import ( + "fmt" + "time" + + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/go-redis/redis" + _ "github.com/go-sql-driver/mysql" +) + +type RedisService struct{} + +type IRedisService interface { + SearchWithPage(search dto.SearchRedisWithPage) (int64, interface{}, error) + Set(setData dto.RedisDataSet) error + Delete(info dto.RedisDelBatch) error + CleanAll(db int) error + + // Backup(db dto.BackupDB) error + // Recover(db dto.RecoverDB) error +} + +func NewIRedisService() IRedisService { + return &RedisService{} +} + +func (u *RedisService) SearchWithPage(search dto.SearchRedisWithPage) (int64, interface{}, error) { + redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis") + if err != nil { + return 0, nil, err + } + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("localhost:%v", redisInfo.Port), + Password: "eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81", + DB: search.DB, + }) + total, err := client.DbSize().Result() + if err != nil { + return 0, nil, err + } + keys, _, err := client.Scan(uint64((search.Page-1)*search.PageSize), "*", int64(search.PageSize)).Result() + if err != nil { + return 0, nil, err + } + var data []dto.RedisData + for _, key := range keys { + var dataItem dto.RedisData + dataItem.Key = key + value, err := client.Get(key).Result() + if err != nil { + return 0, nil, err + } + dataItem.Value = value + typeVal, err := client.Type(key).Result() + if err != nil { + return 0, nil, err + } + dataItem.Type = typeVal + length, err := client.StrLen(key).Result() + if err != nil { + return 0, nil, err + } + dataItem.Length = length + ttl, err := client.TTL(key).Result() + if err != nil { + return 0, nil, err + } + dataItem.Expiration = int64(ttl / 1000000000) + data = append(data, dataItem) + } + return total, data, nil +} + +func (u *RedisService) Set(setData dto.RedisDataSet) error { + redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis") + if err != nil { + return err + } + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("localhost:%v", redisInfo.Port), + Password: "eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81", + DB: setData.DB, + }) + value, _ := client.Get(setData.Key).Result() + if err != nil { + return err + } + if len(value) != 0 { + if _, err := client.Del(setData.Key).Result(); err != nil { + return err + } + } + if _, err := client.Set(setData.Key, setData.Value, time.Duration(setData.Expiration*int64(time.Second))).Result(); err != nil { + return err + } + return nil +} + +func (u *RedisService) Delete(req dto.RedisDelBatch) error { + redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis") + if err != nil { + return err + } + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("localhost:%v", redisInfo.Port), + Password: "eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81", + DB: req.DB, + }) + if _, err := client.Del(req.Names...).Result(); err != nil { + return err + } + return nil +} + +func (u *RedisService) CleanAll(db int) error { + redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis") + if err != nil { + return err + } + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("localhost:%v", redisInfo.Port), + Password: "eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81", + DB: db, + }) + if _, err := client.FlushAll().Result(); err != nil { + return err + } + return nil +} diff --git a/backend/app/service/database_test.go b/backend/app/service/database_test.go index fc9cabdbc..0dabb461d 100644 --- a/backend/app/service/database_test.go +++ b/backend/app/service/database_test.go @@ -1,30 +1,62 @@ package service import ( - "compress/gzip" "fmt" - "os" - "os/exec" "testing" + "time" - _ "github.com/go-sql-driver/mysql" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/go-redis/redis" ) func TestMysql(t *testing.T) { + client := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81", + DB: 0, + }) + // fmt.Println(rdb.Get("dqwas")) - gzipFile, err := os.Open("/tmp/ko.sql.gz") + client.Set("omg", "111", 10*time.Minute) + client.Set("omg1", "111", 10*time.Minute) + client.Set("omg2", "111", 10*time.Minute) + client.Set("omg3", "111", 10*time.Minute) + client.Set("omg4", "111", 10*time.Minute) + client.Set("omg5", "111", 10*time.Minute) + client.Set("omg6", "111", 10*time.Minute) + client.Set("omg7", "111", 10*time.Minute) + client.Set("omg8", "111", 10*time.Minute) + client.Set("omg9", "111", 10*time.Minute) + keys, _, err := client.Scan(0, "*", 5).Result() if err != nil { - fmt.Println(err) + panic(err) } - defer gzipFile.Close() - gzipReader, err := gzip.NewReader(gzipFile) - if err != nil { - fmt.Println(err) - } - defer gzipReader.Close() - cmd := exec.Command("docker", "exec", "-i", "365", "mysql", "-uroot", "-pCalong@2012", "kubeoperator") - cmd.Stdin = gzipReader - stdout, err := cmd.CombinedOutput() - fmt.Println(string(stdout), err) + var data []dto.RedisData + for _, key := range keys { + var dataItem dto.RedisData + dataItem.Key = key + value, err := client.Get(key).Result() + if err != nil { + fmt.Println(err) + } + dataItem.Value = value + typeVal, err := client.Type(key).Result() + if err != nil { + fmt.Println(err) + } + dataItem.Type = typeVal + length, err := client.StrLen(key).Result() + if err != nil { + fmt.Println(err) + } + dataItem.Length = length + ttl, err := client.TTL(key).Result() + if err != nil { + fmt.Println(err) + } + dataItem.Expiration = int64(ttl / 1000000000) + data = append(data, dataItem) + } + fmt.Println(data) } diff --git a/backend/app/service/entry.go b/backend/app/service/entry.go index 62b436c31..d82084bb9 100644 --- a/backend/app/service/entry.go +++ b/backend/app/service/entry.go @@ -13,6 +13,7 @@ type ServiceGroup struct { ComposeTemplateService MysqlService + RedisService CronjobService diff --git a/backend/router/ro_database.go b/backend/router/ro_database.go index 88f605e96..498f8cdb2 100644 --- a/backend/router/ro_database.go +++ b/backend/router/ro_database.go @@ -34,5 +34,10 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) { cmdRouter.GET("/baseinfo/:version", baseApi.LoadBaseinfo) cmdRouter.GET("/versions", baseApi.LoadVersions) cmdRouter.GET("/dbs/:version", baseApi.ListDBNameByVersion) + + cmdRouter.POST("/redis/search", baseApi.SearchRedis) + withRecordRouter.POST("/redis", baseApi.SetRedis) + withRecordRouter.POST("/redis/del", baseApi.DeleteRedis) + withRecordRouter.POST("/redis/clean/:db", baseApi.CleanRedis) } } diff --git a/frontend/src/api/interface/backup.ts b/frontend/src/api/interface/backup.ts index af21577e9..409e3b9ec 100644 --- a/frontend/src/api/interface/backup.ts +++ b/frontend/src/api/interface/backup.ts @@ -22,6 +22,7 @@ export namespace Backup { id: number; createdAt: Date; source: string; + backupType: string; fileDir: string; fileName: string; } diff --git a/frontend/src/api/interface/database.ts b/frontend/src/api/interface/database.ts index aaa15f399..87c5f22d5 100644 --- a/frontend/src/api/interface/database.ts +++ b/frontend/src/api/interface/database.ts @@ -102,4 +102,27 @@ export namespace Database { operation: string; value: string; } + + // redis + export interface SearchRedisWithPage extends ReqPage { + db: number; + } + export interface RedisData { + key: string; + value: string; + type: string; + length: number; + expiration: number; + } + export interface RedisDataSet { + db: number; + key: string; + value: string; + expiration: number; + } + + export interface RedisDelBatch { + db: number; + names: Array; + } } diff --git a/frontend/src/api/modules/database.ts b/frontend/src/api/modules/database.ts index 0e427535d..fa6e2e620 100644 --- a/frontend/src/api/modules/database.ts +++ b/frontend/src/api/modules/database.ts @@ -6,6 +6,9 @@ import { Database } from '../interface/database'; export const searchMysqlDBs = (params: Database.Search) => { return http.post>(`databases/search`, params); }; +export const searchRedisDBs = (params: Database.SearchRedisWithPage) => { + return http.post>(`databases/redis/search`, params); +}; export const listDBByVersion = (params: string) => { return http.get(`databases/dbs/${params}`); }; @@ -45,3 +48,14 @@ export const loadMysqlStatus = (param: string) => { export const loadVersions = () => { return http.get(`/databases/versions`); }; + +// redis +export const setRedis = (params: Database.RedisDataSet) => { + return http.post(`/databases/redis`, params); +}; +export const deleteRedisKey = (params: Database.RedisDelBatch) => { + return http.post(`/databases/redis/del`, params); +}; +export const cleanRedisKey = (db: number) => { + return http.post(`/databases/redis/clean/${db}`); +}; diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 093719a7f..1c277943c 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -218,6 +218,16 @@ export default { tableOpenCacheHelper: '表缓存', maxConnectionsHelper: '最大连接数', restart: '重启数据库', + + key: '键', + value: '值', + type: '数据类型', + length: '数据长度', + expiration: '有效期', + cleanAll: '清除所有', + expirationHelper: '有效期为 0 表示永久', + forever: '永久', + second: '秒', }, container: { operatorHelper: '将对选中容器进行 {0} 操作,是否继续?', diff --git a/frontend/src/routers/modules/database.ts b/frontend/src/routers/modules/database.ts index fffc84c0a..d0f2b49c5 100644 --- a/frontend/src/routers/modules/database.ts +++ b/frontend/src/routers/modules/database.ts @@ -20,7 +20,7 @@ const databaseRouter = { }, }, { - path: '/redis', + path: 'redis', name: 'Redis', component: () => import('@/views/database/redis/index.vue'), hidden: true, diff --git a/frontend/src/views/database/mysql/backup/index.vue b/frontend/src/views/database/mysql/backup/index.vue index 796b8b476..8e297ce1e 100644 --- a/frontend/src/views/database/mysql/backup/index.vue +++ b/frontend/src/views/database/mysql/backup/index.vue @@ -11,13 +11,13 @@ {{ $t('database.backup') }} - + {{ $t('commons.button.delete') }} - +
- - - - - - - - - - + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + {{ $t('database.expirationHelper') }} + + + + +