2021-07-28 18:03:19 +08:00
|
|
|
|
package application
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2022-07-10 12:14:06 +08:00
|
|
|
|
"context"
|
2021-08-18 17:57:33 +08:00
|
|
|
|
"fmt"
|
2022-07-23 16:41:04 +08:00
|
|
|
|
"mayfly-go/internal/constant"
|
2022-06-02 17:41:11 +08:00
|
|
|
|
"mayfly-go/internal/devops/domain/entity"
|
|
|
|
|
|
"mayfly-go/internal/devops/domain/repository"
|
2022-07-20 23:25:52 +08:00
|
|
|
|
"mayfly-go/internal/devops/infrastructure/machine"
|
2022-06-02 17:41:11 +08:00
|
|
|
|
"mayfly-go/internal/devops/infrastructure/persistence"
|
|
|
|
|
|
"mayfly-go/pkg/biz"
|
|
|
|
|
|
"mayfly-go/pkg/cache"
|
|
|
|
|
|
"mayfly-go/pkg/global"
|
|
|
|
|
|
"mayfly-go/pkg/model"
|
2022-07-20 23:25:52 +08:00
|
|
|
|
"mayfly-go/pkg/utils"
|
|
|
|
|
|
"net"
|
2022-07-10 12:14:06 +08:00
|
|
|
|
"strings"
|
2021-08-18 17:57:33 +08:00
|
|
|
|
"time"
|
2021-07-28 18:03:19 +08:00
|
|
|
|
|
2022-07-10 12:14:06 +08:00
|
|
|
|
"github.com/go-redis/redis/v8"
|
2021-07-28 18:03:19 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type Redis interface {
|
|
|
|
|
|
// 分页获取机器脚本信息列表
|
|
|
|
|
|
GetPageList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
|
|
|
|
|
|
|
2021-09-11 14:04:09 +08:00
|
|
|
|
Count(condition *entity.Redis) int64
|
|
|
|
|
|
|
2021-07-28 18:03:19 +08:00
|
|
|
|
// 根据id获取
|
|
|
|
|
|
GetById(id uint64, cols ...string) *entity.Redis
|
|
|
|
|
|
|
|
|
|
|
|
// 根据条件获取
|
|
|
|
|
|
GetRedisBy(condition *entity.Redis, cols ...string) error
|
|
|
|
|
|
|
|
|
|
|
|
Save(entity *entity.Redis)
|
|
|
|
|
|
|
|
|
|
|
|
// 删除数据库信息
|
|
|
|
|
|
Delete(id uint64)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取数据库连接实例
|
|
|
|
|
|
GetRedisInstance(id uint64) *RedisInstance
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type redisAppImpl struct {
|
|
|
|
|
|
redisRepo repository.Redis
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var RedisApp Redis = &redisAppImpl{
|
|
|
|
|
|
redisRepo: persistence.RedisDao,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 分页获取机器脚本信息列表
|
|
|
|
|
|
func (r *redisAppImpl) GetPageList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
|
|
|
|
|
|
return r.redisRepo.GetRedisList(condition, pageParam, toEntity, orderBy...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-11 14:04:09 +08:00
|
|
|
|
func (r *redisAppImpl) Count(condition *entity.Redis) int64 {
|
|
|
|
|
|
return r.redisRepo.Count(condition)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-28 18:03:19 +08:00
|
|
|
|
// 根据id获取
|
|
|
|
|
|
func (r *redisAppImpl) GetById(id uint64, cols ...string) *entity.Redis {
|
|
|
|
|
|
return r.redisRepo.GetById(id, cols...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据条件获取
|
|
|
|
|
|
func (r *redisAppImpl) GetRedisBy(condition *entity.Redis, cols ...string) error {
|
|
|
|
|
|
return r.redisRepo.GetRedis(condition, cols...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *redisAppImpl) Save(re *entity.Redis) {
|
2022-07-18 20:36:31 +08:00
|
|
|
|
// ’修改信息且密码不为空‘ or ‘新增’需要测试是否可连接
|
|
|
|
|
|
if (re.Id != 0 && re.Password != "") || re.Id == 0 {
|
|
|
|
|
|
TestRedisConnection(re)
|
|
|
|
|
|
}
|
2021-07-28 18:03:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 查找是否存在该库
|
|
|
|
|
|
oldRedis := &entity.Redis{Host: re.Host, Db: re.Db}
|
|
|
|
|
|
err := r.GetRedisBy(oldRedis)
|
|
|
|
|
|
|
|
|
|
|
|
if re.Id == 0 {
|
|
|
|
|
|
biz.IsTrue(err != nil, "该库已存在")
|
2022-08-02 21:44:01 +08:00
|
|
|
|
re.PwdEncrypt()
|
2021-07-28 18:03:19 +08:00
|
|
|
|
r.redisRepo.Insert(re)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果存在该库,则校验修改的库是否为该库
|
|
|
|
|
|
if err == nil {
|
2022-04-01 11:23:22 +08:00
|
|
|
|
biz.IsTrue(oldRedis.Id == re.Id, "该库已存在")
|
2021-07-28 18:03:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 先关闭数据库连接
|
|
|
|
|
|
CloseRedis(re.Id)
|
2022-08-02 21:44:01 +08:00
|
|
|
|
re.PwdEncrypt()
|
2021-07-28 18:03:19 +08:00
|
|
|
|
r.redisRepo.Update(re)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除Redis信息
|
|
|
|
|
|
func (r *redisAppImpl) Delete(id uint64) {
|
|
|
|
|
|
CloseRedis(id)
|
|
|
|
|
|
r.redisRepo.Delete(id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取数据库连接实例
|
|
|
|
|
|
func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
|
|
|
|
|
|
// Id不为0,则为需要缓存
|
|
|
|
|
|
needCache := id != 0
|
|
|
|
|
|
if needCache {
|
2021-08-18 17:57:33 +08:00
|
|
|
|
load, ok := redisCache.Get(id)
|
2021-07-28 18:03:19 +08:00
|
|
|
|
if ok {
|
|
|
|
|
|
return load.(*RedisInstance)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 缓存不存在,则回调获取redis信息
|
|
|
|
|
|
re := r.GetById(id)
|
2022-08-02 21:44:01 +08:00
|
|
|
|
re.PwdDecrypt()
|
2021-07-28 18:03:19 +08:00
|
|
|
|
biz.NotNil(re, "redis信息不存在")
|
2021-08-18 17:57:33 +08:00
|
|
|
|
|
2022-07-10 12:14:06 +08:00
|
|
|
|
redisMode := re.Mode
|
2022-07-20 23:25:52 +08:00
|
|
|
|
var ri *RedisInstance
|
2022-07-10 12:14:06 +08:00
|
|
|
|
if redisMode == "" || redisMode == entity.RedisModeStandalone {
|
2022-07-20 23:25:52 +08:00
|
|
|
|
ri = getRedisCient(re)
|
2022-07-10 12:14:06 +08:00
|
|
|
|
// 测试连接
|
2022-07-20 23:25:52 +08:00
|
|
|
|
_, e := ri.Cli.Ping(context.Background()).Result()
|
2022-07-10 12:14:06 +08:00
|
|
|
|
if e != nil {
|
2022-07-20 23:25:52 +08:00
|
|
|
|
ri.Close()
|
2022-07-10 12:14:06 +08:00
|
|
|
|
panic(biz.NewBizErr(fmt.Sprintf("redis连接失败: %s", e.Error())))
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if redisMode == entity.RedisModeCluster {
|
2022-07-20 23:25:52 +08:00
|
|
|
|
ri = getRedisClusterClient(re)
|
2022-07-10 12:14:06 +08:00
|
|
|
|
// 测试连接
|
2022-07-20 23:25:52 +08:00
|
|
|
|
_, e := ri.ClusterCli.Ping(context.Background()).Result()
|
2022-07-10 12:14:06 +08:00
|
|
|
|
if e != nil {
|
2022-07-20 23:25:52 +08:00
|
|
|
|
ri.Close()
|
2022-07-10 12:14:06 +08:00
|
|
|
|
panic(biz.NewBizErr(fmt.Sprintf("redis集群连接失败: %s", e.Error())))
|
|
|
|
|
|
}
|
2022-08-23 18:50:07 +08:00
|
|
|
|
} else if redisMode == entity.RedisModeSentinel {
|
|
|
|
|
|
ri = getRedisSentinelCient(re)
|
|
|
|
|
|
// 测试连接
|
|
|
|
|
|
_, e := ri.Cli.Ping(context.Background()).Result()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
ri.Close()
|
|
|
|
|
|
panic(biz.NewBizErr(fmt.Sprintf("redis sentinel连接失败: %s", e.Error())))
|
|
|
|
|
|
}
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
2021-07-28 18:03:19 +08:00
|
|
|
|
|
2022-07-10 12:14:06 +08:00
|
|
|
|
global.Log.Infof("连接redis: %s", re.Host)
|
2021-07-28 18:03:19 +08:00
|
|
|
|
if needCache {
|
2021-08-18 17:57:33 +08:00
|
|
|
|
redisCache.Put(re.Id, ri)
|
2021-07-28 18:03:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
return ri
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-20 23:25:52 +08:00
|
|
|
|
func getRedisCient(re *entity.Redis) *RedisInstance {
|
|
|
|
|
|
ri := &RedisInstance{Id: re.Id, ProjectId: re.ProjectId, Mode: re.Mode}
|
|
|
|
|
|
|
|
|
|
|
|
redisOptions := &redis.Options{
|
|
|
|
|
|
Addr: re.Host,
|
|
|
|
|
|
Password: re.Password, // no password set
|
|
|
|
|
|
DB: re.Db, // use default DB
|
|
|
|
|
|
DialTimeout: 8 * time.Second,
|
|
|
|
|
|
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
|
|
|
|
|
|
WriteTimeout: -1,
|
|
|
|
|
|
}
|
|
|
|
|
|
if re.EnableSshTunnel == 1 {
|
2022-07-23 16:41:04 +08:00
|
|
|
|
ri.sshTunnelMachineId = re.SshTunnelMachineId
|
|
|
|
|
|
redisOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
|
2022-07-20 23:25:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
ri.Cli = redis.NewClient(redisOptions)
|
|
|
|
|
|
return ri
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-20 23:25:52 +08:00
|
|
|
|
func getRedisClusterClient(re *entity.Redis) *RedisInstance {
|
|
|
|
|
|
ri := &RedisInstance{Id: re.Id, ProjectId: re.ProjectId, Mode: re.Mode}
|
|
|
|
|
|
|
|
|
|
|
|
redisClusterOptions := &redis.ClusterOptions{
|
2022-07-10 12:14:06 +08:00
|
|
|
|
Addrs: strings.Split(re.Host, ","),
|
|
|
|
|
|
Password: re.Password,
|
|
|
|
|
|
DialTimeout: 8 * time.Second,
|
2022-07-20 23:25:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
if re.EnableSshTunnel == 1 {
|
2022-07-23 16:41:04 +08:00
|
|
|
|
ri.sshTunnelMachineId = re.SshTunnelMachineId
|
|
|
|
|
|
redisClusterOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
|
2022-07-20 23:25:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
ri.ClusterCli = redis.NewClusterClient(redisClusterOptions)
|
|
|
|
|
|
return ri
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-23 18:50:07 +08:00
|
|
|
|
func getRedisSentinelCient(re *entity.Redis) *RedisInstance {
|
|
|
|
|
|
ri := &RedisInstance{Id: re.Id, ProjectId: re.ProjectId, Mode: re.Mode}
|
|
|
|
|
|
// sentinel模式host为 masterName=host:port,host:port
|
|
|
|
|
|
masterNameAndHosts := strings.Split(re.Host, "=")
|
|
|
|
|
|
sentinelOptions := &redis.FailoverOptions{
|
|
|
|
|
|
MasterName: masterNameAndHosts[0],
|
|
|
|
|
|
SentinelAddrs: strings.Split(masterNameAndHosts[1], ","),
|
|
|
|
|
|
Password: re.Password, // no password set
|
|
|
|
|
|
DB: re.Db, // use default DB
|
|
|
|
|
|
DialTimeout: 8 * time.Second,
|
|
|
|
|
|
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
|
|
|
|
|
|
WriteTimeout: -1,
|
|
|
|
|
|
}
|
|
|
|
|
|
if re.EnableSshTunnel == 1 {
|
|
|
|
|
|
ri.sshTunnelMachineId = re.SshTunnelMachineId
|
|
|
|
|
|
sentinelOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
|
|
|
|
|
|
}
|
|
|
|
|
|
ri.Cli = redis.NewFailoverClient(sentinelOptions)
|
|
|
|
|
|
return ri
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-23 16:41:04 +08:00
|
|
|
|
func getRedisDialer(machineId uint64) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
|
|
|
|
sshTunnel := MachineApp.GetSshTunnelMachine(machineId)
|
|
|
|
|
|
return func(_ context.Context, network, addr string) (net.Conn, error) {
|
|
|
|
|
|
if sshConn, err := sshTunnel.GetDialConn(network, addr); err == nil {
|
2022-07-20 23:25:52 +08:00
|
|
|
|
// 将ssh conn包装,否则redis内部设置超时会报错,ssh conn不支持设置超时会返回错误: ssh: tcpChan: deadline not supported
|
|
|
|
|
|
return &utils.WrapSshConn{Conn: sshConn}, nil
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-28 18:03:19 +08:00
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
2022-07-23 16:41:04 +08:00
|
|
|
|
// redis客户端连接缓存,指定时间内没有访问则会被关闭
|
|
|
|
|
|
var redisCache = cache.NewTimedCache(constant.RedisConnExpireTime, 5*time.Second).
|
2021-08-18 17:57:33 +08:00
|
|
|
|
WithUpdateAccessTime(true).
|
|
|
|
|
|
OnEvicted(func(key interface{}, value interface{}) {
|
2022-05-12 10:34:16 +08:00
|
|
|
|
global.Log.Info(fmt.Sprintf("删除redis连接缓存 id = %d", key))
|
2022-07-10 12:14:06 +08:00
|
|
|
|
value.(*RedisInstance).Close()
|
2021-08-18 17:57:33 +08:00
|
|
|
|
})
|
2021-07-28 18:03:19 +08:00
|
|
|
|
|
2022-05-12 10:34:16 +08:00
|
|
|
|
// 移除redis连接缓存并关闭redis连接
|
2021-07-28 18:03:19 +08:00
|
|
|
|
func CloseRedis(id uint64) {
|
2022-05-12 10:34:16 +08:00
|
|
|
|
redisCache.Delete(id)
|
2021-07-28 18:03:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-23 16:41:04 +08:00
|
|
|
|
func init() {
|
|
|
|
|
|
machine.AddCheckSshTunnelMachineUseFunc(func(machineId uint64) bool {
|
|
|
|
|
|
// 遍历所有redis连接实例,若存在redis实例使用该ssh隧道机器,则返回true,表示还在使用中...
|
|
|
|
|
|
items := redisCache.Items()
|
|
|
|
|
|
for _, v := range items {
|
|
|
|
|
|
if v.Value.(*RedisInstance).sshTunnelMachineId == machineId {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-28 18:03:19 +08:00
|
|
|
|
func TestRedisConnection(re *entity.Redis) {
|
2022-07-10 12:14:06 +08:00
|
|
|
|
var cmd redis.Cmdable
|
|
|
|
|
|
if re.Mode == "" || re.Mode == entity.RedisModeStandalone {
|
|
|
|
|
|
rcli := getRedisCient(re)
|
|
|
|
|
|
defer rcli.Close()
|
2022-07-20 23:25:52 +08:00
|
|
|
|
cmd = rcli.Cli
|
2022-07-10 12:14:06 +08:00
|
|
|
|
} else if re.Mode == entity.RedisModeCluster {
|
|
|
|
|
|
ccli := getRedisClusterClient(re)
|
|
|
|
|
|
defer ccli.Close()
|
2022-07-20 23:25:52 +08:00
|
|
|
|
cmd = ccli.ClusterCli
|
2022-08-23 18:50:07 +08:00
|
|
|
|
} else if re.Mode == entity.RedisModeSentinel {
|
|
|
|
|
|
rcli := getRedisSentinelCient(re)
|
|
|
|
|
|
defer rcli.Close()
|
|
|
|
|
|
cmd = rcli.Cli
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-28 18:03:19 +08:00
|
|
|
|
// 测试连接
|
2022-07-10 12:14:06 +08:00
|
|
|
|
_, e := cmd.Ping(context.Background()).Result()
|
2021-07-28 18:03:19 +08:00
|
|
|
|
biz.ErrIsNilAppendErr(e, "Redis连接失败: %s")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-10 12:14:06 +08:00
|
|
|
|
// redis实例
|
|
|
|
|
|
type RedisInstance struct {
|
2022-07-23 16:41:04 +08:00
|
|
|
|
Id uint64
|
|
|
|
|
|
ProjectId uint64
|
|
|
|
|
|
Mode string
|
|
|
|
|
|
Cli *redis.Client
|
|
|
|
|
|
ClusterCli *redis.ClusterClient
|
|
|
|
|
|
sshTunnelMachineId uint64
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取命令执行接口的具体实现
|
|
|
|
|
|
func (r *RedisInstance) GetCmdable() redis.Cmdable {
|
|
|
|
|
|
redisMode := r.Mode
|
2022-08-23 18:50:07 +08:00
|
|
|
|
if redisMode == "" || redisMode == entity.RedisModeStandalone || r.Mode == entity.RedisModeSentinel {
|
2022-07-10 12:14:06 +08:00
|
|
|
|
return r.Cli
|
|
|
|
|
|
}
|
2022-08-23 18:50:07 +08:00
|
|
|
|
if redisMode == entity.RedisModeCluster {
|
2022-07-10 12:14:06 +08:00
|
|
|
|
return r.ClusterCli
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-28 18:03:19 +08:00
|
|
|
|
func (r *RedisInstance) Scan(cursor uint64, match string, count int64) ([]string, uint64) {
|
2022-07-10 12:14:06 +08:00
|
|
|
|
keys, newcursor, err := r.GetCmdable().Scan(context.Background(), cursor, match, count).Result()
|
2021-07-28 18:03:19 +08:00
|
|
|
|
biz.ErrIsNilAppendErr(err, "scan失败: %s")
|
|
|
|
|
|
return keys, newcursor
|
|
|
|
|
|
}
|
2022-07-10 12:14:06 +08:00
|
|
|
|
|
|
|
|
|
|
func (r *RedisInstance) Close() {
|
2022-08-23 18:50:07 +08:00
|
|
|
|
if r.Mode == entity.RedisModeStandalone || r.Mode == entity.RedisModeSentinel {
|
2022-07-20 23:25:52 +08:00
|
|
|
|
if err := r.Cli.Close(); err != nil {
|
|
|
|
|
|
global.Log.Errorf("关闭redis单机实例[%d]连接失败: %s", r.Id, err.Error())
|
|
|
|
|
|
}
|
2022-07-23 16:41:04 +08:00
|
|
|
|
r.Cli = nil
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
if r.Mode == entity.RedisModeCluster {
|
2022-07-20 23:25:52 +08:00
|
|
|
|
if err := r.ClusterCli.Close(); err != nil {
|
|
|
|
|
|
global.Log.Errorf("关闭redis集群实例[%d]连接失败: %s", r.Id, err.Error())
|
|
|
|
|
|
}
|
2022-07-23 16:41:04 +08:00
|
|
|
|
r.ClusterCli = nil
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|