121 lines
2.9 KiB
Go
121 lines
2.9 KiB
Go
package util
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"path"
|
||
"strings"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
// GetImageExtension 根据文件头检测图片格式并返回对应的扩展名
|
||
func GetImageExtension(data []byte) string {
|
||
if len(data) < 8 {
|
||
return ".jpg" // 默认返回jpg
|
||
}
|
||
|
||
// 检查文件头(Magic Numbers)
|
||
switch {
|
||
case bytes.HasPrefix(data, []byte{0x89, 0x50, 0x4E, 0x47}):
|
||
return ".png"
|
||
case bytes.HasPrefix(data, []byte{0xFF, 0xD8, 0xFF}):
|
||
return ".jpg"
|
||
case bytes.HasPrefix(data, []byte{0x47, 0x49, 0x46}):
|
||
return ".gif"
|
||
case bytes.HasPrefix(data, []byte{0x42, 0x4D}):
|
||
return ".bmp"
|
||
case bytes.HasPrefix(data, []byte{0x52, 0x49, 0x46, 0x46}) && bytes.Contains(data[0:12], []byte("WEBP")):
|
||
return ".webp"
|
||
default:
|
||
return ".jpg" // 默认返回jpg
|
||
}
|
||
}
|
||
|
||
// isEquivalentURL 判断两个 URL 是否在规范化后相同
|
||
func IsEquivalentURL(url1, url2 string) bool {
|
||
norm1 := normalizeURL(url1)
|
||
norm2 := normalizeURL(url2)
|
||
return norm1 == norm2
|
||
}
|
||
|
||
// normalizeURL 规范化 URL:移除末尾斜杠、协议标准化、移除 index.html
|
||
func normalizeURL(rawURL string) string {
|
||
u, err := url.Parse(rawURL)
|
||
if err != nil {
|
||
return rawURL
|
||
}
|
||
|
||
// 将 http 和 https 视为同一种协议
|
||
u.Scheme = "https"
|
||
|
||
// 移除尾部的 /index.html 或 .html
|
||
u.Path = strings.TrimSuffix(u.Path, "/index.html")
|
||
u.Path = strings.TrimSuffix(u.Path, ".html")
|
||
|
||
// 移除末尾的 /
|
||
u.Path = strings.TrimRight(u.Path, "/")
|
||
|
||
return u.String()
|
||
}
|
||
|
||
// DownloadFile 下载文件到指定目录,返回带有正确扩展名的完整文件路径
|
||
func DownloadFile(url string, dirPath string) (filepath string, err error) {
|
||
// 发送 HTTP GET 请求
|
||
var resp *http.Response
|
||
var maxRetry = 100
|
||
var retry = 0
|
||
for resp, err = http.Get(url); err != nil && retry < maxRetry; resp, err = http.Get(url) {
|
||
fmt.Println("下载失败,正在重试...")
|
||
retry++
|
||
}
|
||
if err != nil {
|
||
return "", fmt.Errorf("下载失败: %v", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 检查 HTTP 响应状态码
|
||
if resp.StatusCode != http.StatusOK {
|
||
return "", fmt.Errorf("请求失败,状态码: %d", resp.StatusCode)
|
||
}
|
||
|
||
// 读取响应内容到内存中以检测文件类型
|
||
bodyData, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return "", fmt.Errorf("读取响应内容失败: %v", err)
|
||
}
|
||
|
||
// 获取正确的文件扩展名
|
||
ext := GetImageExtension(bodyData)
|
||
|
||
// 生成随机文件名
|
||
fileName := uuid.New().String() + ext
|
||
|
||
// 确保目录存在
|
||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||
return "", fmt.Errorf("创建目录失败: %v", err)
|
||
}
|
||
|
||
// 构建完整的文件路径
|
||
filepath = path.Join(dirPath, fileName)
|
||
|
||
// 创建文件
|
||
out, err := os.Create(filepath)
|
||
if err != nil {
|
||
return "", fmt.Errorf("创建文件失败: %v", err)
|
||
}
|
||
defer out.Close()
|
||
|
||
// 将内容写入文件
|
||
_, err = io.Copy(out, bytes.NewReader(bodyData))
|
||
if err != nil {
|
||
return "", fmt.Errorf("保存失败: %v", err)
|
||
}
|
||
|
||
return filepath, nil
|
||
}
|