first commit

This commit is contained in:
2026-02-16 00:13:37 +08:00
commit 74f15c282e
44 changed files with 8708 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
package utils
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"time"
)
// HTTPClient HTTP客户端工具
type HTTPClient struct {
client *http.Client
}
// NewHTTPClient 创建HTTP客户端
func NewHTTPClient(timeout time.Duration) *HTTPClient {
return &HTTPClient{
client: &http.Client{
Timeout: timeout,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return fmt.Errorf("too many redirects")
}
return nil
},
},
}
}
// CheckResult 检查结果
type CheckResult struct {
StatusCode int
Latency time.Duration
Title string
Favicon string
Error error
}
// CheckWebsite 检查网站
func (c *HTTPClient) CheckWebsite(targetURL string) CheckResult {
result := CheckResult{}
start := time.Now()
req, err := http.NewRequest("GET", targetURL, nil)
if err != nil {
result.Error = err
return result
}
req.Header.Set("User-Agent", "MengYaPing/1.0 (Website Monitor)")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
resp, err := c.client.Do(req)
if err != nil {
result.Error = err
result.Latency = time.Since(start)
return result
}
defer resp.Body.Close()
result.Latency = time.Since(start)
result.StatusCode = resp.StatusCode
// 读取响应体获取标题
body, err := io.ReadAll(io.LimitReader(resp.Body, 1024*100)) // 限制100KB
if err == nil {
result.Title = extractTitle(string(body))
result.Favicon = extractFavicon(string(body), targetURL)
}
return result
}
// extractTitle 提取网页标题
func extractTitle(html string) string {
re := regexp.MustCompile(`(?i)<title[^>]*>([^<]+)</title>`)
matches := re.FindStringSubmatch(html)
if len(matches) > 1 {
return strings.TrimSpace(matches[1])
}
return ""
}
// extractFavicon 提取Favicon
func extractFavicon(html string, baseURL string) string {
parsedURL, err := url.Parse(baseURL)
if err != nil {
return ""
}
// 尝试从HTML中提取favicon链接
patterns := []string{
`(?i)<link[^>]*rel=["'](?:shortcut )?icon["'][^>]*href=["']([^"']+)["']`,
`(?i)<link[^>]*href=["']([^"']+)["'][^>]*rel=["'](?:shortcut )?icon["']`,
`(?i)<link[^>]*rel=["']apple-touch-icon["'][^>]*href=["']([^"']+)["']`,
}
for _, pattern := range patterns {
re := regexp.MustCompile(pattern)
matches := re.FindStringSubmatch(html)
if len(matches) > 1 {
faviconURL := matches[1]
return resolveURL(parsedURL, faviconURL)
}
}
// 默认返回 /favicon.ico
return fmt.Sprintf("%s://%s/favicon.ico", parsedURL.Scheme, parsedURL.Host)
}
// resolveURL 解析相对URL
func resolveURL(base *url.URL, ref string) string {
refURL, err := url.Parse(ref)
if err != nil {
return ref
}
return base.ResolveReference(refURL).String()
}
// IsSuccessStatus 判断是否为成功状态码
func IsSuccessStatus(statusCode int) bool {
return statusCode >= 200 && statusCode < 400
}

View File

@@ -0,0 +1,31 @@
package utils
import (
"crypto/rand"
"encoding/hex"
"time"
)
// GenerateID 生成唯一ID
func GenerateID() string {
timestamp := time.Now().UnixNano()
randomBytes := make([]byte, 4)
rand.Read(randomBytes)
return hex.EncodeToString([]byte{
byte(timestamp >> 56),
byte(timestamp >> 48),
byte(timestamp >> 40),
byte(timestamp >> 32),
byte(timestamp >> 24),
byte(timestamp >> 16),
byte(timestamp >> 8),
byte(timestamp),
}) + hex.EncodeToString(randomBytes)
}
// GenerateShortID 生成短ID
func GenerateShortID() string {
randomBytes := make([]byte, 6)
rand.Read(randomBytes)
return hex.EncodeToString(randomBytes)
}