修改监控为证书监控支持文件导入和smtp监控

监控支持多渠道通知
将静态文件打包到二进制文件
This commit is contained in:
v-me-50
2025-07-08 16:45:28 +08:00
parent b347ade900
commit e4917d7caf
396 changed files with 2263 additions and 584 deletions

View File

@@ -0,0 +1,212 @@
package monitor
import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
"net"
"net/http"
"net/smtp"
"strings"
"time"
)
// buildCertChainRecursive 构建证书链树结构
func buildCertChainRecursive(certs []*x509.Certificate, index int) *CertNode {
if index >= len(certs) {
return nil
}
node := &CertNode{
CommonName: certs[index].Subject.CommonName,
Subject: certs[index].Subject.String(),
Issuer: certs[index].Issuer.String(),
}
if index+1 < len(certs) {
child := buildCertChainRecursive(certs, index+1)
if child != nil {
node.Children = append(node.Children, child)
}
}
return node
}
// checkChainRecursive 递归检查链条完整性
func checkChainRecursive(node *CertNode, level int) error {
if node == nil || len(node.Children) == 0 {
return nil
}
for _, child := range node.Children {
if node.Issuer != child.Subject {
return fmt.Errorf("证书链第 %d 层 [CN=%s] 的 Issuer 与第 %d 层 [CN=%s] 的 Subject 不匹配,链条断裂",
level, node.CommonName,
level+1, child.CommonName,
)
}
if err := checkChainRecursive(child, level+1); err != nil {
return err
}
}
return nil
}
// Check 检查证书链的完整性和有效性
func Check(certs []*x509.Certificate, host string, advanceDay int) (result *CertInfo, err error) {
result = &CertInfo{}
if len(certs) == 0 {
return nil, fmt.Errorf("未获取到服务端证书")
}
leafCert := certs[0]
// 提取 CA 名称Issuer 的组织名)
result.CA = "UNKNOWN"
if len(leafCert.Issuer.Organization) > 0 {
result.CA = leafCert.Issuer.Organization[0]
} else if leafCert.Issuer.CommonName != "" {
result.CA = leafCert.Issuer.CommonName
}
result.CommonName = leafCert.Subject.CommonName
result.NotBefore = leafCert.NotBefore.Format("2006-01-02 15:04:05")
result.NotAfter = leafCert.NotAfter.Format("2006-01-02 15:04:05")
result.DaysLeft = int(leafCert.NotAfter.Sub(time.Now()).Hours() / 24)
result.SANs = strings.Join(leafCert.DNSNames, ",")
result.SignatureAlgo = leafCert.SignatureAlgorithm.String()
sha256Sum := sha256.Sum256(leafCert.Raw)
result.Sha256 = hex.EncodeToString(sha256Sum[:])
// 构建证书链树结构
result.CertChain = buildCertChainRecursive(certs, 0)
// 用系统根证书池校验是否受信任
roots, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("加载系统根证书失败:%v", err)
}
if roots == nil {
roots = x509.NewCertPool()
}
intermediates := x509.NewCertPool()
for _, intermediate := range certs[1:] {
intermediates.AddCert(intermediate)
}
opts := x509.VerifyOptions{
DNSName: host,
Roots: roots,
Intermediates: intermediates,
}
if _, err := leafCert.Verify(opts); err != nil {
result.Valid = false
result.VerifyError = fmt.Sprintf("证书验证失败:%v", err)
return result, nil
} else if result.DaysLeft <= advanceDay {
result.VerifyError = fmt.Sprintf("证书即将过期,剩余 %d 天,请及时更新", result.DaysLeft)
} else if err := checkChainRecursive(result.CertChain, 1); err != nil {
result.VerifyError = fmt.Sprintf("证书验证成功,但在检查真实获取到的证书链时出现问题:\n%v\n可忽略仅作提醒如遇到ssl证书问题可作为排查方向", err)
}
result.Valid = true
return result, nil
}
// CheckHttps 严格检查 HTTPS 证书链,基于 CertChain 结构检查链完整性
func CheckHttps(target string, advanceDay int) (result *CertInfo, err error) {
// 解析 host 和 port
host, port, err := net.SplitHostPort(target)
if err != nil {
host = target
port = "443"
}
// 校验 host
if net.ParseIP(host) == nil {
if _, err := net.LookupHost(host); err != nil {
return nil, fmt.Errorf("无效的域名或 IP%v", err)
}
}
// 拼接 HTTPS URL
url := fmt.Sprintf("https://%s", net.JoinHostPort(host, port))
// 构建 HTTP 客户端
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
Timeout: 5 * time.Second,
}
// 发送请求
resp, err := client.Get(url)
if err != nil {
return nil, fmt.Errorf("无法建立 HTTPS 连接:%v", err)
}
defer resp.Body.Close()
// 获取证书链
certs := resp.TLS.PeerCertificates
return Check(certs, host, advanceDay)
}
// CheckSmtp 检查smtp
func CheckSmtp(target string, advanceDay int) (result *CertInfo, err error) {
// 解析 host 和 port
host, port, err := net.SplitHostPort(target)
if err != nil {
host = target
port = "465" // 默认端口
}
// 校验 host
if net.ParseIP(host) == nil {
if _, err := net.LookupHost(host); err != nil {
return nil, fmt.Errorf("无效的域名或 IP%v", err)
}
}
// 如果端口是 465使用 TCP
if port == "465" {
return CheckTCP(host, port, advanceDay)
}
// 建立smtp连接
conn, err := smtp.Dial(host + ":" + port)
if err != nil {
return nil, fmt.Errorf("无法建立 SMTP 连接:%v", err)
}
defer conn.Close()
// 升级到 TLS
if err := conn.StartTLS(&tls.Config{
InsecureSkipVerify: true,
ServerName: host,
}); err != nil {
return nil, fmt.Errorf("无法升级到 TLS%v", err)
}
// 获取证书
state, ok := conn.TLSConnectionState()
if !ok {
return nil, fmt.Errorf("无法获取 TLS 连接状态")
}
certs := state.PeerCertificates
return Check(certs, host, advanceDay)
}
// CheckTCP 通过 tcp 连接检查证书
func CheckTCP(host, port string, advanceDay int) (result *CertInfo, err error) {
// 建立 tcp 连接
url := fmt.Sprintf("%s:%s", host, port)
conn, err := tls.Dial("tcp", url, &tls.Config{
InsecureSkipVerify: true,
ServerName: host,
})
if err != nil {
return nil, fmt.Errorf("无法建立tcp连接%v", err)
}
defer conn.Close()
// 获取证书链
certs := conn.ConnectionState().PeerCertificates
return Check(certs, host, advanceDay)
}

View File

@@ -0,0 +1,124 @@
package monitor
import (
"encoding/csv"
"encoding/json"
"fmt"
"github.com/tealeg/xlsx"
"io"
"mime/multipart"
"path/filepath"
"strings"
)
func ParseMonitorFile(fileHeader *multipart.FileHeader) ([]*Monitor, error) {
ext := strings.ToLower(filepath.Ext(fileHeader.Filename))
file, err := fileHeader.Open()
if err != nil {
return nil, fmt.Errorf("无法打开文件: %v", err)
}
defer file.Close()
switch ext {
case ".csv", ".txt":
return parseCSV(file)
case ".json":
return parseJSON(file)
case ".xlsx":
return parseXLSX(file)
default:
return nil, fmt.Errorf("不支持的文件类型: %s", ext)
}
}
func parseCSV(reader io.Reader) ([]*Monitor, error) {
csvReader := csv.NewReader(reader)
var monitors []*Monitor
isHeader := true
for {
record, err := csvReader.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("CSV 解析失败: %v", err)
}
if isHeader {
isHeader = false
continue
}
if len(record) < 8 {
continue
}
monitor := &Monitor{
Name: record[0],
Target: record[1],
MonitorType: record[2],
ReportTypes: record[3],
Cycle: record[4],
RepeatSendGap: record[5],
Active: record[6],
AdvanceDay: record[7],
}
monitors = append(monitors, monitor)
}
return monitors, nil
}
func parseJSON(reader io.Reader) ([]*Monitor, error) {
var monitors []*Monitor
err := json.NewDecoder(reader).Decode(&monitors)
if err != nil {
return nil, fmt.Errorf("JSON 解析失败: %v", err)
}
return monitors, nil
}
func parseXLSX(file multipart.File) ([]*Monitor, error) {
var monitors []*Monitor
// 读取文件内容到内存
data, err := io.ReadAll(file)
if err != nil {
return nil, err
}
// 解析 XLSX 内容
xlFile, err := xlsx.OpenBinary(data)
if err != nil {
return nil, fmt.Errorf("解析 xlsx 失败: %v", err)
}
if len(xlFile.Sheets) == 0 {
return nil, fmt.Errorf("未找到工作表")
}
sheet := xlFile.Sheets[0]
for i, row := range sheet.Rows {
if i == 0 {
continue // 跳过表头
}
if len(row.Cells) < 8 {
continue
}
monitor := &Monitor{
Name: row.Cells[0].String(),
Target: row.Cells[1].String(),
MonitorType: row.Cells[2].String(),
ReportTypes: row.Cells[3].String(),
Cycle: row.Cells[4].String(),
RepeatSendGap: row.Cells[5].String(),
Active: row.Cells[6].String(),
AdvanceDay: row.Cells[7].String(),
}
monitors = append(monitors, monitor)
}
return monitors, nil
}

View File

@@ -0,0 +1,35 @@
package monitor
// CertInfo 用于返回证书检查的详细信息
type CertInfo struct {
CommonName string `json:"common_name"` // 证书主体 CN
CA string `json:"ca"` // 颁发机构 CN
NotBefore string `json:"not_before"` // 生效时间
NotAfter string `json:"not_after"` // 失效时间
DaysLeft int `json:"days_left"` // 证书剩余天数
SANs string `json:"sans"` // 证书 SAN 列表
SignatureAlgo string `json:"signature_algo"` // 签名算法
Sha256 string `json:"sha256"` // 证书 SHA256 指纹
Valid bool `json:"valid"` // 是否校验通过
VerifyError string `json:"verify_error"` // 校验失败原因
CertChain *CertNode `json:"cert_chain"` // 证书链结构树
}
// CertNode 代表证书链中的节点
type CertNode struct {
CommonName string `json:"common_name"` // 当前节点证书 CN
Subject string `json:"subject"` // 证书 Subject 字符串
Issuer string `json:"issuer"` // 证书 Issuer 字符串
Children []*CertNode `json:"children"` // 下级节点
}
type Monitor struct {
Name string `json:"name"`
Target string `json:"target"`
MonitorType string `json:"monitor_type"` // 监控类型
ReportTypes string `json:"report_types"` // 报告类型
Cycle string `json:"cycle"` // 监控周期
RepeatSendGap string `json:"repeat_send_gap"` // 重复发送间隔
Active string `json:"active"` // 是否启用
AdvanceDay string `json:"advance_day"` // 提前多少天提醒
}

View File

@@ -0,0 +1,263 @@
package monitor
import (
"ALLinSSL/backend/public"
"encoding/json"
"fmt"
"time"
)
func GetSqlite() (*public.Sqlite, error) {
s, err := public.NewSqlite("data/monitor.db", "")
if err != nil {
return nil, err
}
s.TableName = "monitor"
return s, nil
}
func GetList(search string, p, limit int64) ([]map[string]any, int, error) {
var data []map[string]any
var count int64
s, err := GetSqlite()
if err != nil {
return data, 0, err
}
defer s.Close()
var limits []int64
if p >= 0 && limit >= 0 {
limits = []int64{0, limit}
if p > 1 {
limits[0] = (p - 1) * limit
limits[1] = limit
}
}
if search != "" {
count, err = s.Where("name like ? or target like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Count()
data, err = s.Where("name like ? or target like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Order("update_time", "desc").Limit(limits).Select()
} else {
count, err = s.Count()
data, err = s.Order("update_time", "desc").Limit(limits).Select()
}
if err != nil {
return data, 0, err
}
for _, v := range data {
info, ok := v["info"].(string)
if !ok || info == "" {
continue
}
var certInfo CertInfo
err := json.Unmarshal([]byte(info), &certInfo)
if err != nil {
continue
}
v["common_name"] = certInfo.CommonName
v["ca"] = certInfo.CA
v["not_before"] = certInfo.NotBefore
v["not_after"] = certInfo.NotAfter
v["days_left"] = certInfo.DaysLeft
v["sans"] = certInfo.SANs
//v["valid"] = certInfo.Valid
delete(v, "info")
}
return data, int(count), nil
}
func GetInfo(id string) (map[string]any, error) {
s, err := GetSqlite()
if err != nil {
return nil, err
}
defer s.Close()
data, err := s.Where("id=?", []interface{}{id}).Select()
if err != nil {
return nil, err
}
if len(data) == 0 || data[0] == nil {
return nil, fmt.Errorf("未找到对应的监控记录")
}
dataMap := data[0]
monitorInfo := map[string]any{
"id": dataMap["id"],
"name": dataMap["name"],
"target": dataMap["target"],
"monitor_type": dataMap["monitor_type"],
"valid": dataMap["valid"],
}
info, ok := dataMap["info"].(string)
if !ok || info == "" {
return monitorInfo, nil
}
var certInfo CertInfo
err = json.Unmarshal([]byte(info), &certInfo)
if err != nil {
return monitorInfo, fmt.Errorf("解析证书信息失败: %v", err)
}
monitorInfo["common_name"] = certInfo.CommonName
monitorInfo["ca"] = certInfo.CA
monitorInfo["not_before"] = certInfo.NotBefore
monitorInfo["not_after"] = certInfo.NotAfter
monitorInfo["days_left"] = certInfo.DaysLeft
monitorInfo["sans"] = certInfo.SANs
//monitorInfo["valid"] = certInfo.Valid
monitorInfo["verify_error"] = certInfo.VerifyError
monitorInfo["cert_chain"] = certInfo.CertChain
// 查询异常次数
// 计算7天前的时间
sevenDaysAgo := time.Now().AddDate(0, 0, -7).Format("2006-01-02 15:04:05")
s.TableName = "err_record"
errCount, err := s.Where("monitor_id=? and create_time >= ?", []interface{}{id, sevenDaysAgo}).Count()
if err != nil {
errCount = 0
}
monitorInfo["err_count"] = errCount
return monitorInfo, nil
}
// AddMonitor 添加新的监控记录
func AddMonitor(name, target, monitorType, reportTypes, cycle, repeatSendGap, active, advanceDay string) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
data := map[string]any{
"name": name,
"target": target,
"monitor_type": monitorType,
"report_types": reportTypes,
"cycle": cycle,
"repeat_send_gap": repeatSendGap,
"active": active,
"advance_day": advanceDay,
"create_time": time.Now().Format("2006-01-02 15:04:05"),
"update_time": time.Now().Format("2006-01-02 15:04:05"),
}
if _, err := s.Insert(data); err != nil {
return fmt.Errorf("添加监控记录失败: %v", err)
}
return nil
}
// UpdMonitor 更新监控记录
func UpdMonitor(id, name, target, reportTypes, cycle, repeatSendGap, active, advanceDay string) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
data := map[string]any{
"name": name,
"target": target,
"report_types": reportTypes,
"cycle": cycle,
"repeat_send_gap": repeatSendGap,
"active": active,
"advance_day": advanceDay,
"update_time": time.Now().Format("2006-01-02 15:04:05"),
"info": "", // 清空 info 字段
}
_, err = s.Where("id=?", []interface{}{id}).Update(data)
if err != nil {
return fmt.Errorf("更新监控记录失败: %v", err)
}
return nil
}
func DelMonitor(id string) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
_, err = s.Where("id=?", []interface{}{id}).Delete()
if err != nil {
return err
}
s.TableName = "err_record"
_, err = s.Where("monitor_id=?", []interface{}{id}).Delete()
if err != nil {
return err
}
return nil
}
func SetMonitor(id string, active int) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
_, err = s.Where("id=?", []interface{}{id}).Update(map[string]any{
"active": active,
"update_time": time.Now().Format("2006-01-02 15:04:05"),
})
if err != nil {
return err
}
return nil
}
func GetErrRecord(id, p, limit int64) ([]map[string]any, int, error) {
var data []map[string]any
var count int64
s, err := public.NewSqlite("data/monitor.db", "")
if err != nil {
return data, 0, err
}
defer s.Close()
s.TableName = "err_record"
var limits []int64
if p >= 0 && limit >= 0 {
limits = []int64{0, limit}
if p > 1 {
limits[0] = (p - 1) * limit
limits[1] = limit
}
}
count, err = s.Where("monitor_id=?", []any{id}).Count()
if err != nil {
return data, 0, err
}
data, err = s.Where("monitor_id=?", []any{id}).Order("create_time", "desc").Limit(limits).Select()
if err != nil {
return data, 0, err
}
return data, int(count), nil
}
func MultiAddMonitor(monitors []*Monitor) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
for _, monitor := range monitors {
data := map[string]any{
"name": monitor.Name,
"target": monitor.Target,
"monitor_type": monitor.MonitorType,
"report_types": monitor.ReportTypes,
"cycle": monitor.Cycle,
"repeat_send_gap": monitor.RepeatSendGap,
"active": monitor.Active,
"advance_day": monitor.AdvanceDay,
"create_time": time.Now().Format("2006-01-02 15:04:05"),
"update_time": time.Now().Format("2006-01-02 15:04:05"),
}
if _, err := s.Insert(data); err != nil {
return fmt.Errorf("添加监控记录失败: %v", err)
}
}
return nil
}

View File

@@ -65,15 +65,15 @@ func GetCertCount() (map[string]int, error) {
return result, nil
}
func GetSiteMonitorCount() (map[string]any, error) {
s, err := public.NewSqlite("data/site_monitor.db", "")
func GetMonitorCount() (map[string]any, error) {
s, err := public.NewSqlite("data/monitor.db", "")
if err != nil {
return nil, err
}
defer s.Close()
cert, err := s.Query(`select count(*) as count,
count(case when state='异常' then 1 end ) as exception
from site_monitor`)
count(case when valid=-1 then 1 end ) as exception
from monitor`)
if err != nil {
return nil, err
}
@@ -144,18 +144,18 @@ func GetOverviewData() (map[string]any, error) {
if err != nil {
return nil, err
}
siteMonitorCount, err := GetSiteMonitorCount()
workflowHistory, err := GetWorkflowHistory()
if err != nil {
return nil, err
}
workflowHistory, err := GetWorkflowHistory()
monitorCount, err := GetMonitorCount()
if err != nil {
return nil, err
}
result := make(map[string]any)
result["workflow"] = workflowCount
result["cert"] = certCount
result["site_monitor"] = siteMonitorCount
result["monitor"] = monitorCount
result["workflow_history"] = workflowHistory
return result, nil
}

View File

@@ -1,255 +0,0 @@
package siteMonitor
import (
"ALLinSSL/backend/public"
"crypto/tls"
"fmt"
"net"
"net/http"
"strings"
"time"
)
// SSLInfo 定义结果结构体
type SSLInfo struct {
Target string
HTTPStatus int
HTTPStatusText string
Domains []string
Issuer string
NotBefore string
NotAfter string
DaysRemaining int
CertificateOK bool
CertificateNote string
}
func GetSqlite() (*public.Sqlite, error) {
s, err := public.NewSqlite("data/site_monitor.db", "")
if err != nil {
return nil, err
}
s.TableName = "site_monitor"
return s, nil
}
func GetList(search string, p, limit int64) ([]map[string]any, int, error) {
var data []map[string]any
var count int64
s, err := GetSqlite()
if err != nil {
return data, 0, err
}
defer s.Close()
var limits []int64
if p >= 0 && limit >= 0 {
limits = []int64{0, limit}
if p > 1 {
limits[0] = (p - 1) * limit
limits[1] = limit
}
}
if search != "" {
count, err = s.Where("name like ? or site_domain like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Count()
data, err = s.Where("name like ? or site_domain like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Order("update_time", "desc").Limit(limits).Select()
} else {
count, err = s.Count()
data, err = s.Order("update_time", "desc").Limit(limits).Select()
}
if err != nil {
return data, 0, err
}
for _, v := range data {
v["domain"] = v["site_domain"]
}
return data, int(count), nil
}
func AddMonitor(name, domain, reportType string, cycle int) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
info, err := CheckWebsite(domain)
if err != nil {
return err
}
_, err = s.Insert(map[string]any{
"name": name,
"site_domain": domain,
"report_type": reportType,
"cycle": cycle,
"state": info.HTTPStatusText,
"ca": info.Issuer,
"cert_domain": strings.Join(info.Domains, ","),
"end_time": info.NotAfter,
"end_day": info.DaysRemaining,
"create_time": time.Now().Format("2006-01-02 15:04:05"),
"update_time": time.Now().Format("2006-01-02 15:04:05"),
"last_time": time.Now().Format("2006-01-02 15:04:05"),
"active": 1,
})
if err != nil {
return err
}
return nil
}
func UpdMonitor(id, name, domain, reportType string, cycle int) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
info, err := CheckWebsite(domain)
if err != nil {
return err
}
_, err = s.Where("id=?", []interface{}{id}).Update(map[string]any{
"name": name,
"site_domain": domain,
"report_type": reportType,
"cycle": cycle,
"state": info.HTTPStatusText,
"ca": info.Issuer,
"cert_domain": strings.Join(info.Domains, ","),
"end_time": info.NotAfter,
"end_day": info.DaysRemaining,
"update_time": time.Now().Format("2006-01-02 15:04:05"),
"last_time": time.Now().Format("2006-01-02 15:04:05"),
"active": 1,
})
if err != nil {
return err
}
return nil
}
func DelMonitor(id string) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
_, err = s.Where("id=?", []interface{}{id}).Delete()
if err != nil {
return err
}
return nil
}
func SetMonitor(id string, active int) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
_, err = s.Where("id=?", []interface{}{id}).Update(map[string]any{
"active": active,
"update_time": time.Now().Format("2006-01-02 15:04:05"),
})
if err != nil {
return err
}
return nil
}
func UpdInfo(id, domain string, s *public.Sqlite, reportType string) error {
info, errCheck := CheckWebsite(domain)
now := time.Now()
updateData := map[string]any{
"state": info.HTTPStatusText,
"ca": info.Issuer,
"cert_domain": strings.Join(info.Domains, ","),
"end_time": info.NotAfter,
"end_day": info.DaysRemaining,
"last_time": now.Format("2006-01-02 15:04:05"),
"except_end_time": now.Format("2006-01-02 15:04:05"),
}
if errCheck != nil {
updateData["state"] = "异常"
// return err
} else {
if info.HTTPStatus != 0 && info.CertificateOK != false {
delete(updateData, "except_end_time")
} else {
errCheck = fmt.Errorf("证书异常")
}
}
_, err := s.Where("id=?", []interface{}{id}).Update(updateData)
if err != nil {
return err
}
return errCheck
}
// CheckWebsite 实际检测函数
func CheckWebsite(target string) (*SSLInfo, error) {
result := &SSLInfo{Target: target}
// 拆分 host 和 port
host, port, err := net.SplitHostPort(target)
if err != nil {
// 没有显式端口,默认 443
host = target
port = "443"
}
// 验证格式是否是 IP 或域名
if net.ParseIP(host) == nil {
if _, err := net.LookupHost(host); err != nil {
return result, fmt.Errorf("无效域名或 IP%v", err)
}
}
// TLS 连接(支持所有 TLS 版本)
conn, err := tls.Dial("tcp", net.JoinHostPort(host, port), &tls.Config{
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS10, // 显式支持所有版本
MaxVersion: tls.VersionTLS13,
})
if err != nil {
return result, fmt.Errorf("目标不支持 HTTPS%v", err)
}
defer conn.Close()
// HTTP 状态检测(构造 URL保留端口
url := fmt.Sprintf("https://%s", net.JoinHostPort(host, port))
resp, err := http.Get(url)
if err != nil {
result.HTTPStatus = 0
result.HTTPStatusText = "异常"
} else {
result.HTTPStatus = resp.StatusCode
result.HTTPStatusText = "正常"
resp.Body.Close()
}
// 获取证书
cert := conn.ConnectionState().PeerCertificates[0]
result.Domains = cert.DNSNames
result.Issuer = cert.Issuer.CommonName
result.NotBefore = cert.NotBefore.Format("2006-01-02 15:04:05")
result.NotAfter = cert.NotAfter.Format("2006-01-02 15:04:05")
result.DaysRemaining = int(cert.NotAfter.Sub(time.Now()).Hours() / 24)
now := time.Now()
switch {
case now.Before(cert.NotBefore):
result.CertificateOK = false
result.CertificateNote = "尚未生效"
case now.After(cert.NotAfter):
result.CertificateOK = false
result.CertificateNote = "已过期"
default:
result.CertificateOK = true
result.CertificateNote = "有效"
}
return result, nil
}

View File

@@ -1,21 +0,0 @@
package siteMonitor
import (
"fmt"
"testing"
)
func Test(t *testing.T) {
site := "bt.cn" // 只传域名或 IP不要 http://
result, err := CheckWebsite(site)
if err != nil {
fmt.Printf("❌ 检测失败: %v\n", err)
return
}
fmt.Println(result.HTTPStatusText)
fmt.Println(result.Domains)
fmt.Println(result.Issuer)
fmt.Println(result.NotAfter)
// fmt.Println(result.Domains)
// fmt.Println(result.Domains)
}