feat: Adapt to Redis Cluster (#9588)

This commit is contained in:
CityFun 2025-07-21 17:56:42 +08:00 committed by GitHub
parent 81f97f6f88
commit 6427337728
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 59 additions and 42 deletions

View file

@ -421,12 +421,12 @@ func (b *BaseApi) Recover(c *gin.Context) {
} }
req.File = downloadPath req.File = downloadPath
switch req.Type { switch req.Type {
case "mysql", "mariadb": case "mysql", "mariadb", constant.AppMysqlCluster:
if err := backupService.MysqlRecover(req); err != nil { if err := backupService.MysqlRecover(req); err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
} }
case constant.AppPostgresql: case constant.AppPostgresql, constant.AppPostgresqlCluster:
if err := backupService.PostgresqlRecover(req); err != nil { if err := backupService.PostgresqlRecover(req); err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
@ -436,7 +436,7 @@ func (b *BaseApi) Recover(c *gin.Context) {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
} }
case "redis": case "redis", constant.AppRedisCluster:
if err := backupService.RedisRecover(req); err != nil { if err := backupService.RedisRecover(req); err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return

View file

@ -33,13 +33,13 @@ func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
// @Tags Database Redis // @Tags Database Redis
// @Summary Load redis conf // @Summary Load redis conf
// @Accept json // @Accept json
// @Param request body dto.OperationWithName true "request" // @Param request body dto.LoadRedisStatus true "request"
// @Success 200 {object} dto.RedisConf // @Success 200 {object} dto.RedisConf
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /databases/redis/conf [post] // @Router /databases/redis/conf [post]
func (b *BaseApi) LoadRedisConf(c *gin.Context) { func (b *BaseApi) LoadRedisConf(c *gin.Context) {
var req dto.OperationWithName var req dto.LoadRedisStatus
if err := helper.CheckBind(&req, c); err != nil { if err := helper.CheckBind(&req, c); err != nil {
return return
} }
@ -55,13 +55,13 @@ func (b *BaseApi) LoadRedisConf(c *gin.Context) {
// @Tags Database Redis // @Tags Database Redis
// @Summary Load redis persistence conf // @Summary Load redis persistence conf
// @Accept json // @Accept json
// @Param request body dto.OperationWithName true "request" // @Param request body dto.LoadRedisStatus true "request"
// @Success 200 {object} dto.RedisPersistence // @Success 200 {object} dto.RedisPersistence
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /databases/redis/persistence/conf [post] // @Router /databases/redis/persistence/conf [post]
func (b *BaseApi) LoadPersistenceConf(c *gin.Context) { func (b *BaseApi) LoadPersistenceConf(c *gin.Context) {
var req dto.OperationWithName var req dto.LoadRedisStatus
if err := helper.CheckBind(&req, c); err != nil { if err := helper.CheckBind(&req, c); err != nil {
return return
} }

View file

@ -174,6 +174,7 @@ type RedisConfUpdate struct {
Timeout string `json:"timeout"` Timeout string `json:"timeout"`
Maxclients string `json:"maxclients"` Maxclients string `json:"maxclients"`
Maxmemory string `json:"maxmemory"` Maxmemory string `json:"maxmemory"`
DBType string `json:"dbType" validate:"required,oneof=redis redis-cluster"`
} }
type RedisConfPersistenceUpdate struct { type RedisConfPersistenceUpdate struct {
Database string `json:"database" validate:"required"` Database string `json:"database" validate:"required"`
@ -181,6 +182,7 @@ type RedisConfPersistenceUpdate struct {
Appendonly string `json:"appendonly"` Appendonly string `json:"appendonly"`
Appendfsync string `json:"appendfsync"` Appendfsync string `json:"appendfsync"`
Save string `json:"save"` Save string `json:"save"`
DBType string `json:"dbType" validate:"required,oneof=redis redis-cluster"`
} }
type RedisConf struct { type RedisConf struct {

View file

@ -23,7 +23,7 @@ import (
) )
func (u *BackupService) RedisBackup(req dto.CommonBackup) error { func (u *BackupService) RedisBackup(req dto.CommonBackup) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name) redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil { if err != nil {
return err return err
} }
@ -64,7 +64,7 @@ func (u *BackupService) RedisBackup(req dto.CommonBackup) error {
} }
func (u *BackupService) RedisRecover(req dto.CommonRecover) error { func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name) redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil { if err != nil {
return err return err
} }
@ -76,6 +76,7 @@ func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
} }
func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDir, fileName, secret, taskID string) error { func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDir, fileName, secret, taskID string) error {
var ( var (
err error err error
itemTask *task.Task itemTask *task.Task
@ -102,7 +103,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDi
} }
if strings.HasSuffix(fileName, ".tar.gz") { if strings.HasSuffix(fileName, ".tar.gz") {
redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", global.Dir.AppInstallDir, "redis", redisInfo.Name) redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name)
if err := fileOp.TarGzCompressPro(true, redisDataDir, path.Join(backupDir, fileName), secret, ""); err != nil { if err := fileOp.TarGzCompressPro(true, redisDataDir, path.Join(backupDir, fileName), secret, ""); err != nil {
return err return err
} }
@ -111,14 +112,14 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDi
if strings.HasSuffix(fileName, ".aof") { if strings.HasSuffix(fileName, ".aof") {
stdout1, err := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/appendonly.aof %s/%s", redisInfo.ContainerName, backupDir, fileName) stdout1, err := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/appendonly.aof %s/%s", redisInfo.ContainerName, backupDir, fileName)
if err != nil { if err != nil {
return errors.New(string(stdout1)) return errors.New(stdout1)
} }
return nil return nil
} }
stdout1, err1 := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName) stdout1, err1 := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName)
if err1 != nil { if err1 != nil {
return errors.New(string(stdout1)) return errors.New(stdout1)
} }
return nil return nil
} }
@ -196,12 +197,12 @@ func handleRedisRecover(redisInfo *repo.RootInfo, parentTask *task.Task, recover
} }
}() }()
} }
composeDir := fmt.Sprintf("%s/redis/%s", global.Dir.AppInstallDir, redisInfo.Name) composeDir := fmt.Sprintf("%s/%s/%s", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name)
if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil { if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil {
return err return err
} }
if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "7.") { if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "7.") {
redisDataDir := fmt.Sprintf("%s/%s/%s/data", global.Dir.AppInstallDir, "redis", redisInfo.Name) redisDataDir := fmt.Sprintf("%s/%s/%s/data", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name)
if err := fileOp.TarGzExtractPro(recoverFile, redisDataDir, secret); err != nil { if err := fileOp.TarGzExtractPro(recoverFile, redisDataDir, secret); err != nil {
return err return err
} }

View file

@ -29,8 +29,8 @@ type IRedisService interface {
ChangePassword(info dto.ChangeRedisPass) error ChangePassword(info dto.ChangeRedisPass) error
LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error) LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error)
LoadConf(req dto.OperationWithName) (*dto.RedisConf, error) LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error)
LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error) LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
CheckHasCli() bool CheckHasCli() bool
InstallCli() error InstallCli() error
@ -41,7 +41,7 @@ func NewIRedisService() IRedisService {
} }
func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error { func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database) redisInfo, err := appInstallRepo.LoadBaseInfo(req.DBType, req.Database)
if err != nil { if err != nil {
return err return err
} }
@ -50,7 +50,7 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
confs = append(confs, redisConfig{key: "timeout", value: req.Timeout}) confs = append(confs, redisConfig{key: "timeout", value: req.Timeout})
confs = append(confs, redisConfig{key: "maxclients", value: req.Maxclients}) confs = append(confs, redisConfig{key: "maxclients", value: req.Maxclients})
confs = append(confs, redisConfig{key: "maxmemory", value: req.Maxmemory}) confs = append(confs, redisConfig{key: "maxmemory", value: req.Maxmemory})
if err := confSet(redisInfo.Name, "", confs); err != nil { if err := confSet(redisInfo.Name, req.DBType, "", confs); err != nil {
return err return err
} }
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", global.Dir.AppInstallDir, redisInfo.Name)); err != nil { if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", global.Dir.AppInstallDir, redisInfo.Name)); err != nil {
@ -107,7 +107,7 @@ func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
} }
func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error { func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database) redisInfo, err := appInstallRepo.LoadBaseInfo(req.DBType, req.Database)
if err != nil { if err != nil {
return err return err
} }
@ -119,10 +119,10 @@ func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate)
confs = append(confs, redisConfig{key: "appendonly", value: req.Appendonly}) confs = append(confs, redisConfig{key: "appendonly", value: req.Appendonly})
confs = append(confs, redisConfig{key: "appendfsync", value: req.Appendfsync}) confs = append(confs, redisConfig{key: "appendfsync", value: req.Appendfsync})
} }
if err := confSet(redisInfo.Name, req.Type, confs); err != nil { if err := confSet(redisInfo.Name, req.DBType, req.Type, confs); err != nil {
return err return err
} }
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", global.Dir.AppInstallDir, redisInfo.Name)); err != nil { if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, req.DBType, redisInfo.Name)); err != nil {
return err return err
} }
@ -157,8 +157,8 @@ func (u *RedisService) LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, er
return &info, nil return &info, nil
} }
func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, error) { func (u *RedisService) LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name) redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -174,8 +174,8 @@ func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, erro
return &item, nil return &item, nil
} }
func (u *RedisService) LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error) { func (u *RedisService) LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name) redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -214,8 +214,8 @@ type redisConfig struct {
value string value string
} }
func confSet(redisName string, updateType string, changeConf []redisConfig) error { func confSet(redisName string, redisType string, updateType string, changeConf []redisConfig) error {
path := fmt.Sprintf("%s/redis/%s/conf/redis.conf", global.Dir.AppInstallDir, redisName) path := fmt.Sprintf("%s/%s/%s/conf/redis.conf", global.Dir.AppInstallDir, redisType, redisName)
lineBytes, err := os.ReadFile(path) lineBytes, err := os.ReadFile(path)
if err != nil { if err != nil {
return err return err

View file

@ -232,6 +232,7 @@ export namespace Database {
appendonly: string; appendonly: string;
appendfsync: string; appendfsync: string;
save: string; save: string;
dbType: string;
} }
export interface RedisStatus { export interface RedisStatus {
tcp_port: string; tcp_port: string;

View file

@ -78,8 +78,9 @@ export const getAppInstalledByID = (installID: number, node?: string) => {
return http.get<App.AppInstalledInfo>(`apps/installed/info/${installID}${params}`); return http.get<App.AppInstalledInfo>(`apps/installed/info/${installID}${params}`);
}; };
export const installedOp = (op: App.AppInstalledOp) => { export const installedOp = (op: App.AppInstalledOp, node?: string) => {
return http.post<any>('apps/installed/op', op, TimeoutEnum.T_40S); const params = node ? `?operateNode=${node}` : '';
return http.post<any>(`apps/installed/op${params}`, op, TimeoutEnum.T_40S);
}; };
export const syncInstalledApp = () => { export const syncInstalledApp = () => {

View file

@ -111,11 +111,14 @@ export const loadRemoteAccess = (type: string, database: string) => {
export const loadRedisStatus = (type: string, database: string) => { export const loadRedisStatus = (type: string, database: string) => {
return http.post<Database.RedisStatus>(`/databases/redis/status`, { type: type, name: database }); return http.post<Database.RedisStatus>(`/databases/redis/status`, { type: type, name: database });
}; };
export const loadRedisConf = (database: string) => { export const loadRedisConf = (type: string, database: string) => {
return http.post<Database.RedisConf>(`/databases/redis/conf`, { name: database }); return http.post<Database.RedisConf>(`/databases/redis/conf`, { type: type, name: database });
}; };
export const redisPersistenceConf = (database: string) => { export const redisPersistenceConf = (type: string, database: string) => {
return http.post<Database.RedisPersistenceConf>(`/databases/redis/persistence/conf`, { name: database }); return http.post<Database.RedisPersistenceConf>(`/databases/redis/persistence/conf`, {
type: type,
name: database,
});
}; };
export const checkRedisCli = () => { export const checkRedisCli = () => {
return http.get<boolean>(`/databases/redis/check`); return http.get<boolean>(`/databases/redis/check`);

View file

@ -19,7 +19,7 @@ export const jumpToInstall = (type: string, key: string) => {
case 'redis-cluster': case 'redis-cluster':
jumpToPath(router, '/xpack/cluster/redis'); jumpToPath(router, '/xpack/cluster/redis');
return true; return true;
case 'postgres-cluster': case 'postgresql-cluster':
jumpToPath(router, '/xpack/cluster/postgres'); jumpToPath(router, '/xpack/cluster/postgres');
return true; return true;
} }

View file

@ -13,7 +13,7 @@
<LayoutContent title="Redis"> <LayoutContent title="Redis">
<template #app v-if="currentDB?.from === 'local'"> <template #app v-if="currentDB?.from === 'local'">
<AppStatus <AppStatus
:app-key="'redis'" :app-key="currentDB.type"
:app-name="appName" :app-name="appName"
v-model:loading="loading" v-model:loading="loading"
@before="onBefore" @before="onBefore"

View file

@ -174,7 +174,11 @@ const changeTab = (val: string) => {
loadConfFile(); loadConfFile();
break; break;
case 'persistence': case 'persistence':
persistenceRef.value!.acceptParams({ status: redisStatus.value, database: database.value }); persistenceRef.value!.acceptParams({
status: redisStatus.value,
database: database.value,
type: dbType.value,
});
break; break;
case 'tuning': case 'tuning':
case 'port': case 'port':
@ -228,7 +232,7 @@ const onChangePort = async (formEl: FormInstance | undefined) => {
return; return;
} }
let params = { let params = {
key: 'redis', key: dbType.value,
name: form.name, name: form.name,
port: form.port, port: form.port,
}; };
@ -298,7 +302,7 @@ const onSaveFile = async () => {
}; };
const submitFile = async () => { const submitFile = async () => {
let param = { let param = {
type: 'redis', type: dbType.value,
database: database.value, database: database.value,
file: redisConf.value, file: redisConf.value,
}; };
@ -315,7 +319,7 @@ const submitFile = async () => {
}; };
const loadForm = async () => { const loadForm = async () => {
const res = await loadRedisConf(database.value); const res = await loadRedisConf(dbType.value, database.value);
form.name = res.data?.name; form.name = res.data?.name;
form.timeout = Number(res.data?.timeout); form.timeout = Number(res.data?.timeout);
form.maxclients = Number(res.data?.maxclients); form.maxclients = Number(res.data?.maxclients);

View file

@ -145,13 +145,16 @@ const rules = reactive({
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const database = ref(); const database = ref();
const opRef = ref(); const opRef = ref();
const dbType = ref('redis');
interface DialogProps { interface DialogProps {
database: string; database: string;
status: string; status: string;
type: string;
} }
const persistenceShow = ref(false); const persistenceShow = ref(false);
const acceptParams = (prop: DialogProps): void => { const acceptParams = (prop: DialogProps): void => {
dbType.value = prop.type;
persistenceShow.value = true; persistenceShow.value = true;
database.value = prop.database; database.value = prop.database;
if (prop.status === 'Running') { if (prop.status === 'Running') {
@ -197,7 +200,7 @@ const search = async () => {
}; };
const onBackup = async () => { const onBackup = async () => {
emit('loading', true); emit('loading', true);
await handleBackup({ name: database.value, detailName: '', type: 'redis', secret: '', taskID: '' }) await handleBackup({ name: database.value, detailName: '', type: dbType.value, secret: '', taskID: '' })
.then(() => { .then(() => {
emit('loading', false); emit('loading', false);
search(); search();
@ -210,7 +213,7 @@ const onBackup = async () => {
const onRecover = async () => { const onRecover = async () => {
let param = { let param = {
downloadAccountID: currentRow.value.downloadAccountID, downloadAccountID: currentRow.value.downloadAccountID,
type: 'redis', type: dbType.value,
name: database.value, name: database.value,
detailName: '', detailName: '',
file: currentRow.value.fileDir + '/' + currentRow.value.fileName, file: currentRow.value.fileDir + '/' + currentRow.value.fileName,
@ -283,6 +286,7 @@ const onSave = async (formEl: FormInstance | undefined, type: string) => {
param.type = type; param.type = type;
param.appendfsync = form.appendfsync; param.appendfsync = form.appendfsync;
param.appendonly = form.appendonly; param.appendonly = form.appendonly;
param.dbType = dbType.value;
emit('loading', true); emit('loading', true);
await updateRedisPersistenceConf(param) await updateRedisPersistenceConf(param)
.then(() => { .then(() => {
@ -305,6 +309,7 @@ const onSave = async (formEl: FormInstance | undefined, type: string) => {
} }
param.type = type; param.type = type;
param.save = itemSaves.join(','); param.save = itemSaves.join(',');
param.dbType = dbType.value;
emit('loading', true); emit('loading', true);
await updateRedisPersistenceConf(param) await updateRedisPersistenceConf(param)
.then(() => { .then(() => {
@ -318,7 +323,7 @@ const onSave = async (formEl: FormInstance | undefined, type: string) => {
const loadform = async () => { const loadform = async () => {
form.saves = []; form.saves = [];
const res = await redisPersistenceConf(database.value); const res = await redisPersistenceConf(dbType.value, database.value);
form.appendonly = res.data?.appendonly; form.appendonly = res.data?.appendonly;
form.appendfsync = res.data?.appendfsync; form.appendfsync = res.data?.appendfsync;
let itemSaves = res.data?.save.split(' '); let itemSaves = res.data?.save.split(' ');