فهرست منبع

添加过期和错误状态通知功能

在`notify/action.go`中增加了对证书过期和请求错误的通知处理,并修改了`watcher/watcher.go`以支持新的通知类型。新增了重试机制来提高获取证书的稳定性。
SongZihuan 1 ماه پیش
والد
کامیت
461c49670a
2فایلهای تغییر یافته به همراه150 افزوده شده و 32 حذف شده
  1. 95 5
      src/notify/action.go
  2. 55 27
      src/watcher/watcher.go

+ 95 - 5
src/notify/action.go

@@ -11,14 +11,22 @@ import (
 	"time"
 )
 
+const (
+	StatusError     = 1
+	StatusOutOfDate = 2
+)
+
 type urlRecord struct {
 	Name     string
 	URL      string
+	Status   int
 	Deadline time.Duration
+	ErrorMsg string
 }
 
 var startTime time.Time
-var records sync.Map
+var outOfDateRecords sync.Map
+var errorRecords sync.Map
 
 func InitNotify() error {
 	if !config.IsReady() {
@@ -35,31 +43,49 @@ func InitNotify() error {
 	return nil
 }
 
-func NewRecord(name string, url string, deadline time.Duration) {
+func NewOutOfDateRecord(name string, url string, deadline time.Duration) {
 	if name == "" {
 		name = url
 	}
 
-	records.Store(name, &urlRecord{
+	outOfDateRecords.Store(name, &urlRecord{
 		Name:     name,
 		URL:      url,
+		Status:   StatusOutOfDate,
 		Deadline: deadline,
 	})
 }
 
-func SendNotify() {
+func NewErrorRecord(name string, url string, err string) {
+	if name == "" {
+		name = url
+	}
+
+	errorRecords.Store(name, &urlRecord{
+		Name:     name,
+		URL:      url,
+		Status:   StatusError,
+		ErrorMsg: err,
+	})
+}
+
+func SendOutOfDateNotify() {
 	var res strings.Builder
 	var expiredCount uint64 = 0
 	var expiringSoonCount uint64 = 0
 
 	res.WriteString(fmt.Sprintf("日期:%s %s\n", startTime.Format("2006-01-02 15:04:05"), startTime.Location().String()))
 
-	records.Range(func(key, value any) bool {
+	outOfDateRecords.Range(func(key, value any) bool {
 		record, ok := value.(*urlRecord)
 		if !ok {
 			return true
 		}
 
+		if record.Status != StatusOutOfDate {
+			return true
+		}
+
 		if record.Deadline <= 0 {
 			expiredCount += 1
 			res.WriteString(fmt.Sprintf("- %s 已过期\n", record.Name))
@@ -95,3 +121,67 @@ func SendNotify() {
 
 	wg.Wait()
 }
+
+func SendErrorNotify() {
+	var res strings.Builder
+	var count uint64 = 0
+
+	res.WriteString(fmt.Sprintf("日期:%s %s\n", startTime.Format("2006-01-02 15:04:05"), startTime.Location().String()))
+
+	errorRecords.Range(func(key, value any) bool {
+		record, ok := value.(*urlRecord)
+		if !ok {
+			return true
+		}
+
+		if record.Status != StatusError {
+			return true
+		}
+
+		count += 1
+		res.WriteString(fmt.Sprintf("- 检查 %s 出错: %s\n", record.Name, record.ErrorMsg))
+
+		return true
+	})
+
+	if count <= 0 {
+		// 无任何记录
+		return
+	}
+
+	res.WriteString(fmt.Sprintf("共计:出粗 %d 条。\n", count))
+	res.WriteString("完毕\n")
+	msg := res.String()
+
+	var wg sync.WaitGroup
+	wg.Add(2)
+
+	go func() {
+		defer wg.Done()
+		wxrobot.SendNotify(msg)
+	}()
+
+	go func() {
+		defer wg.Done()
+		smtpserver.SendNotify(msg)
+	}()
+
+	wg.Wait()
+}
+
+func SendNotify() {
+	var wg sync.WaitGroup
+	wg.Add(2)
+
+	go func() {
+		defer wg.Done()
+		SendOutOfDateNotify()
+	}()
+
+	go func() {
+		defer wg.Done()
+		SendErrorNotify()
+	}()
+
+	wg.Wait()
+}

+ 55 - 27
src/watcher/watcher.go

@@ -2,12 +2,12 @@ package watcher
 
 import (
 	"crypto/tls"
-	"errors"
 	"fmt"
 	"github.com/SongZihuan/https-watcher/src/config"
 	"github.com/SongZihuan/https-watcher/src/logger"
 	"github.com/SongZihuan/https-watcher/src/notify"
 	"net/http"
+	"strings"
 	"time"
 )
 
@@ -20,38 +20,30 @@ func Run() error {
 
 	now := time.Now()
 
-MainCycle:
 	for _, url := range config.GetConfig().Watcher.URLs {
 		logger.Infof("开始请求 %s", url.Name)
 
-		tlsState, err := getCertificate(url.URL)
+		tlsState, err := getCertificateRetry(url.URL, url.Name)
 		if err != nil {
-			if errors.Is(err, errNotTLS) {
-				logger.Errorf("请求 %s 出现异常:未返回TLS证书", url.Name)
-				continue MainCycle
-			}
-
 			logger.Errorf("请求 %s 出现异常:%s", url.Name, err.Error())
-			continue MainCycle
-		}
-
-		if len(tlsState.PeerCertificates) == 0 {
+			notify.NewErrorRecord(url.Name, url.URL, err.Error())
+		} else if len(tlsState.PeerCertificates) == 0 {
 			logger.Errorf("请求 %s 出现异常:证书链为空", url.Name)
-			continue MainCycle
-		}
-
-		logger.Infof("开始处理 %s", url.Name)
-
-		if now.After(tlsState.PeerCertificates[0].NotAfter) {
-			// 证书已过期
-			logger.Infof("%s 已过期", url.Name)
-			notify.NewRecord(url.Name, url.URL, 0)
-		} else if deadline := tlsState.PeerCertificates[0].NotAfter.Sub(now); deadline <= url.DeadlineDuration {
-			// 证书即将过期
-			logger.Infof("%s 即将过期", url.Name)
-			notify.NewRecord(url.Name, url.URL, deadline)
+			notify.NewErrorRecord(url.Name, url.URL, "证书链为空")
 		} else {
-			logger.Infof("%s 正常", url.Name)
+			logger.Infof("开始处理 %s 证书", url.Name)
+
+			if now.After(tlsState.PeerCertificates[0].NotAfter) {
+				// 证书已过期
+				logger.Infof("%s 已过期", url.Name)
+				notify.NewOutOfDateRecord(url.Name, url.URL, 0)
+			} else if deadline := tlsState.PeerCertificates[0].NotAfter.Sub(now); deadline <= url.DeadlineDuration {
+				// 证书即将过期
+				logger.Infof("%s 即将过期", url.Name)
+				notify.NewOutOfDateRecord(url.Name, url.URL, deadline)
+			} else {
+				logger.Infof("%s 正常", url.Name)
+			}
 		}
 
 		logger.Infof("处理 %s 完成", url.Name)
@@ -62,10 +54,46 @@ MainCycle:
 	return nil
 }
 
+func getCertificateRetry(url string, name string) (*tls.ConnectionState, error) {
+	var err1, err2, err3 error
+	var tlsStats *tls.ConnectionState
+
+	tlsStats, err1 = getCertificate(url)
+	if err1 == nil {
+		return tlsStats, nil
+	}
+
+	tlsStats, err2 = getCertificate(url)
+	if err2 == nil {
+		return tlsStats, nil
+	}
+
+	tlsStats, err3 = getCertificate(url)
+	if err3 == nil {
+		return tlsStats, nil
+	}
+
+	// 去除重复
+	var errMap = make(map[string]bool, 3)
+	errMap[err1.Error()] = true
+	errMap[err2.Error()] = true
+	errMap[err3.Error()] = true
+
+	var errStrBuilder strings.Builder
+	var n = 0
+	for err, _ := range errMap {
+		n += 1
+		errStrBuilder.WriteString(fmt.Sprintf("检查 %s 错误[%d]: %s; ", name, n, err))
+	}
+
+	err := fmt.Errorf("%s", strings.TrimSpace(errStrBuilder.String()))
+	return nil, err
+}
+
 func getCertificate(url string) (*tls.ConnectionState, error) {
 	// 创建一个自定义的Transport,这样我们可以访问TLS连接状态
 	tr := &http.Transport{
-		TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // 忽略服务器证书验证
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // 忽略服务器证书验证(因为我的目的只是检查获取到的证书是否到期)
 	}
 
 	// 使用自定义的Transport创建一个HTTP客户端