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

View file

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

View file

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

View file

@ -23,7 +23,7 @@ import (
)
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 {
return err
}
@ -64,7 +64,7 @@ func (u *BackupService) RedisBackup(req dto.CommonBackup) 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 {
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 {
var (
err error
itemTask *task.Task
@ -102,7 +103,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDi
}
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 {
return err
}
@ -111,14 +112,14 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDi
if strings.HasSuffix(fileName, ".aof") {
stdout1, err := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/appendonly.aof %s/%s", redisInfo.ContainerName, backupDir, fileName)
if err != nil {
return errors.New(string(stdout1))
return errors.New(stdout1)
}
return nil
}
stdout1, err1 := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName)
if err1 != nil {
return errors.New(string(stdout1))
return errors.New(stdout1)
}
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 {
return err
}
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 {
return err
}

View file

@ -29,8 +29,8 @@ type IRedisService interface {
ChangePassword(info dto.ChangeRedisPass) error
LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error)
LoadConf(req dto.OperationWithName) (*dto.RedisConf, error)
LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error)
LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error)
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
CheckHasCli() bool
InstallCli() error
@ -41,7 +41,7 @@ func NewIRedisService() IRedisService {
}
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 {
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: "maxclients", value: req.Maxclients})
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
}
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 {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database)
redisInfo, err := appInstallRepo.LoadBaseInfo(req.DBType, req.Database)
if err != nil {
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: "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
}
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
}
@ -157,8 +157,8 @@ func (u *RedisService) LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, er
return &info, nil
}
func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
func (u *RedisService) LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@ -174,8 +174,8 @@ func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, erro
return &item, nil
}
func (u *RedisService) LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
func (u *RedisService) LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@ -214,8 +214,8 @@ type redisConfig struct {
value string
}
func confSet(redisName string, updateType string, changeConf []redisConfig) error {
path := fmt.Sprintf("%s/redis/%s/conf/redis.conf", global.Dir.AppInstallDir, redisName)
func confSet(redisName string, redisType string, updateType string, changeConf []redisConfig) error {
path := fmt.Sprintf("%s/%s/%s/conf/redis.conf", global.Dir.AppInstallDir, redisType, redisName)
lineBytes, err := os.ReadFile(path)
if err != nil {
return err

View file

@ -232,6 +232,7 @@ export namespace Database {
appendonly: string;
appendfsync: string;
save: string;
dbType: string;
}
export interface RedisStatus {
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}`);
};
export const installedOp = (op: App.AppInstalledOp) => {
return http.post<any>('apps/installed/op', op, TimeoutEnum.T_40S);
export const installedOp = (op: App.AppInstalledOp, node?: string) => {
const params = node ? `?operateNode=${node}` : '';
return http.post<any>(`apps/installed/op${params}`, op, TimeoutEnum.T_40S);
};
export const syncInstalledApp = () => {

View file

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

View file

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

View file

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

View file

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

View file

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