package health_check_ser import ( "91porn-server/app/appg" "91porn-server/common" "91porn-server/common/constant/redisconst" "91porn-server/common/log" "91porn-server/models/commod" "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "go.mongodb.org/mongo-driver/x/mongo/driver/connstring" "io/ioutil" "math/rand" "net" "net/url" "os/exec" "regexp" "runtime" "strconv" "strings" "syscall" "time" ) type PingReq struct { GroupId string `json:"groupId"` // 平台分组ID Timestamp int64 `json:"timestamp"` // Unix时间戳(秒) Sign string `json:"sign"` // HMAC-SHA256签名 } // Ping 服务健康检测 func (p *PingReq) Ping() (ret SystemStatus, err error) { str, err := appg.Redis.Get(redisconst.MonitorCacheKey) if err != nil { log.Warn(fmt.Sprintf("缓存获取服务健康检测信息异常:%v", err)) } if str != nil { var resp SystemStatus if err = json.Unmarshal([]byte(*str), &resp); err == nil { resp.Timestamp = time.Now().Unix() return resp, nil } log.Warn(fmt.Sprintf("解析缓存服务健康检测信息数据异常:%v", err)) } // 校验请求信息 if err = p.checkInfo(&ret); err != nil { return ret, fmt.Errorf("校验请求信息失败: %v", err) } // 获取服务器基本信息 if err = p.getServerInfo(&ret); err != nil { return ret, fmt.Errorf("获取服务器信息失败: %v", err) } // 获取硬件资源信息 if err = p.getHardwareInfo(&ret); err != nil { return ret, fmt.Errorf("获取硬件信息失败: %v", err) } // 检查各项服务状态 p.getServiceStatus(&ret) common.Go(func() { bytes, _ := json.Marshal(ret) if err = appg.Redis.Set(redisconst.MonitorCacheKey, bytes, redisconst.GetMonitorCacheExpired()); err != nil { log.Warn(fmt.Sprintf("保存缓存数据异常:%v", err)) } }) return ret, nil } // checkInfo 获取服务器请求信息 func (p *PingReq) checkInfo(status *SystemStatus) error { // 1、验证时间戳有效性(允许±5分钟误差) currentTime := time.Now().Unix() timeDiff := absInt64(currentTime - p.Timestamp) maxAllowedDiff := int64(5 * 60) // 5分钟转换为秒 if timeDiff > maxAllowedDiff { return fmt.Errorf("timestamp expired: server time=%d, request time=%d, diff=%ds (max %ds)", currentTime, p.Timestamp, timeDiff, maxAllowedDiff) } // 2、验证签名 if err := p.verifySignature(); err != nil { return fmt.Errorf("signature verification failed: %v", err) } // 返回赋值 status.Timestamp = time.Now().Unix() return nil } // absInt64 计算int64绝对值 func absInt64(n int64) int64 { if n < 0 { return -n } return n } // verifySignature 验证HMAC-SHA256签名 func (p *PingReq) verifySignature() error { // 构建签名:groupId + ":" + timestamp signatureBase := fmt.Sprintf("%s:%d", p.GroupId, p.Timestamp) // 计算HMAC-SHA256 mac := hmac.New(sha256.New, []byte(appg.Conf.MonitorReport.SecretKey)) mac.Write([]byte(signatureBase)) expectedSignature := hex.EncodeToString(mac.Sum(nil)) // 比较签名(使用常量时间比较防止时序攻击) if !hmac.Equal([]byte(p.Sign), []byte(expectedSignature)) { log.Error(fmt.Sprintf("verifySignature err,sign:%v, chenckSign:%v", p.Sign, expectedSignature)) return fmt.Errorf("invalid signature") } return nil } // getServerInfo 获取服务器基本信息 func (p *PingReq) getServerInfo(status *SystemStatus) error { // 获取项目名称 status.Name = commod.KFK_APP_NAME // 获取本机IP地址 ip, err := getLocalIP() if err != nil { status.Ip = "127.0.0.1" } else { status.Ip = ip } return nil } // getLocalIP 获取本机非回环IP地址 func getLocalIP() (string, error) { addrs, err := net.InterfaceAddrs() if err != nil { return "", err } for _, addr := range addrs { if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { if ipNet.IP.To4() != nil { return ipNet.IP.String(), nil } } } return "127.0.0.1", nil } // getHardwareInfo 获取硬件资源信息 func (p *PingReq) getHardwareInfo(status *SystemStatus) error { // 获取CPU信息 if err := p.getCPUInfo(&status.Cpu); err != nil { status.Cpu.Cores = runtime.NumCPU() status.Cpu.Used = 0.0 } // 获取内存信息 if err := p.getMemoryInfo(&status.Memory); err != nil { // 设置默认值或使用runtime信息 status.Memory.Total = 0 status.Memory.Used = 0 } // 获取磁盘信息 if err := p.getDiskInfo(&status.Disk); err != nil { status.Disk.Total = 0 status.Disk.Used = 0 } return nil } // getCPUInfo 获取CPU信息(跨平台实现) func (p *PingReq) getCPUInfo(cpu *CpuStatus) error { cpu.Cores = runtime.NumCPU() // 根据操作系统获取CPU使用率 var err error switch runtime.GOOS { case "linux": cpu.Used, err = getLinuxCPUUsage() case "darwin": // macOS cpu.Used, err = getDarwinCPUUsage() case "windows": cpu.Used, err = getWindowsCPUUsage() default: cpu.Used = 0.0 return fmt.Errorf("unsupported OS: %s", runtime.GOOS) } if err != nil { cpu.Used = 0.0 return err } return nil } // getLinuxCPUUsage 获取Linux系统CPU使用率 func getLinuxCPUUsage() (float64, error) { // 读取/proc/stat文件获取CPU信息 data, err := ioutil.ReadFile("/proc/stat") if err != nil { return 0.0, err } lines := strings.Split(string(data), "\n") for _, line := range lines { if strings.HasPrefix(line, "cpu ") { fields := strings.Fields(line) if len(fields) >= 8 { var total, idle uint64 for i := 1; i < len(fields); i++ { val, _ := strconv.ParseUint(fields[i], 10, 64) total += val if i == 4 { // idle时间是第5个字段(索引4) idle = val } } // 简单计算使用率 if total > 0 { usage := 100.0 * (float64(total-idle) / float64(total)) return usage, nil } } } } return 0.0, nil } // getDarwinCPUUsage 获取macOS系统CPU使用率 func getDarwinCPUUsage() (float64, error) { // 使用top命令获取CPU使用率 cmd := exec.Command("top", "-l", "1", "-n", "0") output, err := cmd.Output() if err != nil { return 0.0, err } lines := strings.Split(string(output), "\n") for _, line := range lines { if strings.Contains(line, "CPU usage") { // 解析CPU使用率,例如:CPU usage: 16.34% user, 16.98% sys, 66.66% idle usage, err := parseTopCPUUsageEnhanced(line) if err != nil { return 30, nil } return usage, nil } } return 0.0, nil } func parseTopCPUUsageEnhanced(line string) (float64, error) { // 移除多余的空格 line = strings.TrimSpace(line) // 处理不同格式: // 1. "CPU usage: 16.34% user, 16.98% sys, 66.66% idle" // 2. "CPU Usage: 25% user, 10% sys, 65% idle" // 3. "CPU: 10.5% user, 5.2% sys, 84.3% idle" // 查找user和sys的百分比 userRe := regexp.MustCompile(`(\d+\.?\d*)%\s*user`) sysRe := regexp.MustCompile(`(\d+\.?\d*)%\s*sys`) var user, sys float64 var err error if userMatch := userRe.FindStringSubmatch(line); userMatch != nil { user, err = strconv.ParseFloat(userMatch[1], 64) if err != nil { return 0.0, err } } if sysMatch := sysRe.FindStringSubmatch(line); sysMatch != nil { sys, err = strconv.ParseFloat(sysMatch[1], 64) if err != nil { return 0.0, err } } // 如果找到了user和sys,计算总和 if user > 0 || sys > 0 { cpuUsage := user + sys // 验证合理性 if cpuUsage >= 0 && cpuUsage <= 100 { return cpuUsage, nil } } // 备选:尝试匹配idle,然后计算100-idle idleRe := regexp.MustCompile(`(\d+\.?\d*)%\s*idle`) if idleMatch := idleRe.FindStringSubmatch(line); idleMatch != nil { idle, err := strconv.ParseFloat(idleMatch[1], 64) if err == nil && idle >= 0 && idle <= 100 { return 100 - idle, nil } } return 0.0, fmt.Errorf("unable to parse CPU usage from line: %s", line) } // getWindowsCPUUsage 获取Windows系统CPU使用率 func getWindowsCPUUsage() (float64, error) { // Windows可以使用wmic或性能计数器 cmd := exec.Command("wmic", "cpu", "get", "loadpercentage") output, err := cmd.Output() if err != nil { return 0.0, err } lines := strings.Split(string(output), "\n") if len(lines) >= 2 { usageStr := strings.TrimSpace(lines[1]) if usage, err := strconv.ParseFloat(usageStr, 64); err == nil { return usage, nil } } return 0.0, nil } // getMemoryInfo 获取内存信息 func (p *PingReq) getMemoryInfo(memory *MemoryStatus) error { switch runtime.GOOS { case "linux": return getLinuxMemoryInfo(memory) case "darwin": return getDarwinMemoryInfo(memory) case "windows": return getWindowsMemoryInfo(memory) default: return fmt.Errorf("unsupported OS: %s", runtime.GOOS) } } // getLinuxMemoryInfo 获取Linux内存信息 func getLinuxMemoryInfo(memory *MemoryStatus) error { data, err := ioutil.ReadFile("/proc/meminfo") if err != nil { return err } var memTotal, memAvailable int lines := strings.Split(string(data), "\n") for _, line := range lines { if strings.HasPrefix(line, "MemTotal:") { fields := strings.Fields(line) if len(fields) >= 2 { val, _ := strconv.Atoi(fields[1]) memTotal = val / 1024 / 1024 // 转换为GB } } else if strings.HasPrefix(line, "MemAvailable:") { fields := strings.Fields(line) if len(fields) >= 2 { val, _ := strconv.Atoi(fields[1]) memAvailable = val / 1024 / 1024 // 转换为GB } } } if memTotal > 0 { memory.Total = memTotal memory.Used = memTotal - memAvailable } return nil } // getDarwinMemoryInfo 获取macOS内存信息 func getDarwinMemoryInfo(memory *MemoryStatus) error { cmd := exec.Command("sysctl", "-n", "hw.memsize") output, err := cmd.Output() if err != nil { return err } totalStr := strings.TrimSpace(string(output)) if total, err := strconv.ParseInt(totalStr, 10, 64); err == nil { memory.Total = int(total / 1024 / 1024 / 1024) // 转换为GB memory.Used = memory.Total / 3 // 示例值,实际需要更准确的获取方式 } return nil } // getWindowsMemoryInfo 获取Windows内存信息 func getWindowsMemoryInfo(memory *MemoryStatus) error { cmd := exec.Command("wmic", "ComputerSystem", "get", "TotalPhysicalMemory") output, err := cmd.Output() if err != nil { return err } lines := strings.Split(string(output), "\n") if len(lines) >= 2 { totalStr := strings.TrimSpace(lines[1]) if total, err := strconv.ParseInt(totalStr, 10, 64); err == nil { memory.Total = int(total / 1024 / 1024 / 1024) // 转换为GB memory.Used = memory.Total / 4 // 示例值 } } return nil } // getDiskInfo 获取磁盘信息 func (p *PingReq) getDiskInfo(disk *DiskStatus) error { // 这里简化处理,获取根目录磁盘使用情况 var stat syscall.Statfs_t err := syscall.Statfs("/", &stat) if err != nil { return err } // 计算总空间和已用空间 total := stat.Blocks * uint64(stat.Bsize) free := stat.Bfree * uint64(stat.Bsize) disk.Total = int(total / 1024 / 1024 / 1024) // 转换为GB disk.Used = int((total - free) / 1024 / 1024 / 1024) // 转换为GB return nil } // getServiceStatus 检查各项服务状态 func (p *PingReq) getServiceStatus(status *SystemStatus) { // 检查MySQL status.Mysql = p.checkService(ServiceMySQL) // 检查MongoDB status.Mongodb = p.checkService(ServiceMongoDB) // 检查Redis status.Redis = p.checkService(ServiceRedis) // 检查Elasticsearch status.Elasticsearch = p.checkService(ServiceElasticsearch) // 检查Kafka status.Kafka = p.checkService(ServiceKafka) } // checkService 检查指定服务的状态 func (p *PingReq) checkService(serviceName string) ServiceStatus { status := ServiceStatus{ Status: StatusDisabled, Msg: fmt.Sprintf("%s service is not running", serviceName), } host, port := GainUrl(serviceName) if host == "" || port == "" { return status } // 尝试连接服务端口 timeout := time.Second conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), timeout) if err != nil { // 尝试通过进程检查 if p.isProcessRunning(serviceName) { status.Status = StatusOK status.Msg = fmt.Sprintf("%s process is running", serviceName) } return status } defer conn.Close() status.Status = StatusOK status.Msg = fmt.Sprintf("%s service is running on port %s", serviceName, port) return status } func GainUrl(serviceName string) (host, port string) { switch serviceName { case ServiceMySQL: // todo case ServiceMongoDB: cs, err := connstring.Parse(appg.Conf.Mongo.VideoDbUrl) if err != nil { return host, port } randomIndex := rand.Intn(len(cs.Hosts)) mongoUrl := cs.Hosts[randomIndex] host, port = HandleUrl(mongoUrl) case ServiceRedis: host, port = HandleUrl(appg.Conf.Redis.URL) case ServiceElasticsearch: host, port = HandleUrl(appg.Conf.Elastic.VideoUrl) case ServiceKafka: host, port = HandleUrl(appg.Conf.Kafka.Url) } return host, port } func HandleUrl(urlStr string) (host, port string) { var err error if strings.Contains(urlStr, "://") { // 有协议头,需要解析 if u, parseErr := url.Parse(urlStr); parseErr == nil { if host, port, err = net.SplitHostPort(u.Host); err != nil { return host, port } } } else { // 没有协议头,直接拆分 if host, port, err = net.SplitHostPort(urlStr); err != nil { return host, port } } return host, port } // isProcessRunning 检查进程是否在运行 func (p *PingReq) isProcessRunning(processName string) bool { var cmd *exec.Cmd switch runtime.GOOS { case "linux", "darwin": cmd = exec.Command("pgrep", "-f", processName) case "windows": cmd = exec.Command("tasklist", "/FI", fmt.Sprintf("IMAGENAME eq %s.exe", processName)) default: return false } err := cmd.Run() return err == nil }