Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+546
View File
@@ -0,0 +1,546 @@
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
}