mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-08 07:41:10 +08:00
修改监控为证书监控支持文件导入和smtp监控
监控支持多渠道通知 将静态文件打包到二进制文件
This commit is contained in:
212
backend/internal/monitor/check.go
Normal file
212
backend/internal/monitor/check.go
Normal 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)
|
||||
}
|
||||
124
backend/internal/monitor/file_handle.go
Normal file
124
backend/internal/monitor/file_handle.go
Normal 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
|
||||
}
|
||||
35
backend/internal/monitor/model.go
Normal file
35
backend/internal/monitor/model.go
Normal 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"` // 提前多少天提醒
|
||||
}
|
||||
263
backend/internal/monitor/monitor.go
Normal file
263
backend/internal/monitor/monitor.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user