allinssl/backend/internal/siteMonitor/monitor.go

256 lines
6.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
}