fix
Some checks failed
部署后端服务 / 🧪 测试后端 (push) Failing after 5m8s
部署后端服务 / 🚀 构建并部署 (push) Has been skipped
部署后端服务 / 🔄 回滚部署 (push) Has been skipped

This commit is contained in:
xujiang
2025-07-10 18:09:11 +08:00
parent 35004f224e
commit 010fe2a8c7
96 changed files with 23709 additions and 19 deletions

View File

@ -0,0 +1,172 @@
package utils
import (
"crypto/md5"
"crypto/sha256"
"fmt"
"io"
"mime"
"os"
"path/filepath"
"strings"
"time"
)
// GetFileExtension 获取文件扩展名
func GetFileExtension(filename string) string {
return strings.ToLower(filepath.Ext(filename))
}
// GetMimeType 根据文件扩展名获取MIME类型
func GetMimeType(filename string) string {
ext := GetFileExtension(filename)
mimeType := mime.TypeByExtension(ext)
if mimeType == "" {
return "application/octet-stream"
}
return mimeType
}
// IsImageFile 检查是否为图片文件
func IsImageFile(filename string) bool {
imageExtensions := []string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff"}
ext := GetFileExtension(filename)
for _, imageExt := range imageExtensions {
if ext == imageExt {
return true
}
}
return false
}
// GenerateUniqueFilename 生成唯一的文件名
func GenerateUniqueFilename(originalFilename string) string {
ext := GetFileExtension(originalFilename)
timestamp := time.Now().Unix()
randomStr := GenerateRandomString(8)
return fmt.Sprintf("%d_%s%s", timestamp, randomStr, ext)
}
// GenerateFilePath 生成文件路径
func GenerateFilePath(baseDir, subDir, filename string) string {
// 按日期组织文件夹
now := time.Now()
dateDir := now.Format("2006/01/02")
if subDir != "" {
return filepath.Join(baseDir, subDir, dateDir, filename)
}
return filepath.Join(baseDir, dateDir, filename)
}
// EnsureDir 确保目录存在
func EnsureDir(dirPath string) error {
return os.MkdirAll(dirPath, 0755)
}
// FileExists 检查文件是否存在
func FileExists(filepath string) bool {
_, err := os.Stat(filepath)
return !os.IsNotExist(err)
}
// GetFileSize 获取文件大小
func GetFileSize(filepath string) (int64, error) {
info, err := os.Stat(filepath)
if err != nil {
return 0, err
}
return info.Size(), nil
}
// CalculateFileMD5 计算文件MD5哈希
func CalculateFileMD5(filepath string) (string, error) {
file, err := os.Open(filepath)
if err != nil {
return "", err
}
defer file.Close()
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
// CalculateFileSHA256 计算文件SHA256哈希
func CalculateFileSHA256(filepath string) (string, error) {
file, err := os.Open(filepath)
if err != nil {
return "", err
}
defer file.Close()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
// CopyFile 复制文件
func CopyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
// 确保目标目录存在
if err := EnsureDir(filepath.Dir(dst)); err != nil {
return err
}
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, sourceFile)
return err
}
// DeleteFile 删除文件
func DeleteFile(filepath string) error {
if !FileExists(filepath) {
return nil // 文件不存在,认为删除成功
}
return os.Remove(filepath)
}
// FormatFileSize 格式化文件大小为人类可读格式
func FormatFileSize(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
units := []string{"KB", "MB", "GB", "TB", "PB"}
return fmt.Sprintf("%.1f %s", float64(bytes)/float64(div), units[exp])
}
// GetImageDimensions 获取图片尺寸(需要额外的图片处理库)
// 这里只是占位符,实际实现需要使用如 github.com/disintegration/imaging 等库
func GetImageDimensions(filepath string) (width, height int, err error) {
// TODO: 实现图片尺寸获取
// 需要添加图片处理依赖
return 0, 0, fmt.Errorf("not implemented")
}

View File

@ -0,0 +1,77 @@
package utils
import (
"crypto/rand"
"encoding/base64"
"math/big"
"time"
)
const (
// 字符集
alphanumeric = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
numbers = "0123456789"
)
// GenerateRandomString 生成指定长度的随机字符串
func GenerateRandomString(length int) string {
return generateRandomFromCharset(length, alphanumeric)
}
// GenerateRandomLetters 生成指定长度的随机字母字符串
func GenerateRandomLetters(length int) string {
return generateRandomFromCharset(length, letters)
}
// GenerateRandomNumbers 生成指定长度的随机数字字符串
func GenerateRandomNumbers(length int) string {
return generateRandomFromCharset(length, numbers)
}
// generateRandomFromCharset 从指定字符集生成随机字符串
func generateRandomFromCharset(length int, charset string) string {
result := make([]byte, length)
charsetLen := big.NewInt(int64(len(charset)))
for i := 0; i < length; i++ {
randomIndex, err := rand.Int(rand.Reader, charsetLen)
if err != nil {
// 如果加密随机数生成失败,回退到时间种子
return generateRandomFallback(length, charset)
}
result[i] = charset[randomIndex.Int64()]
}
return string(result)
}
// generateRandomFallback 回退的随机生成方法
func generateRandomFallback(length int, charset string) string {
// 使用时间作为种子的简单随机生成
seed := time.Now().UnixNano()
result := make([]byte, length)
for i := 0; i < length; i++ {
seed = seed*1103515245 + 12345
result[i] = charset[(seed/65536)%int64(len(charset))]
}
return string(result)
}
// GenerateSecureToken 生成安全令牌
func GenerateSecureToken(length int) (string, error) {
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(bytes), nil
}
// GenerateID 生成唯一ID
func GenerateID() string {
timestamp := time.Now().UnixNano()
random := GenerateRandomString(8)
return base64.URLEncoding.EncodeToString([]byte(string(timestamp) + random))[:16]
}

View File

@ -0,0 +1,68 @@
package utils
import (
"regexp"
"strconv"
"strings"
"unicode"
)
// GenerateSlug 生成URL友好的slug
func GenerateSlug(text string) string {
// 转换为小写
text = strings.ToLower(text)
// 移除重音字符
text = removeAccents(text)
// 替换空格和特殊字符为连字符
reg := regexp.MustCompile(`[^\p{L}\p{N}]+`)
text = reg.ReplaceAllString(text, "-")
// 移除首尾的连字符
text = strings.Trim(text, "-")
// 移除连续的连字符
reg = regexp.MustCompile(`-+`)
text = reg.ReplaceAllString(text, "-")
return text
}
// removeAccents 移除重音字符的转换函数
func removeAccents(text string) string {
var result strings.Builder
for _, r := range text {
if !unicode.Is(unicode.Mn, r) {
result.WriteRune(r)
}
}
return result.String()
}
// TruncateString 截断字符串到指定长度
func TruncateString(s string, length int) string {
if len(s) <= length {
return s
}
return s[:length]
}
// GenerateUniqueSlug 生成唯一的slug
func GenerateUniqueSlug(base string, existingCheck func(string) bool) string {
slug := GenerateSlug(base)
if !existingCheck(slug) {
return slug
}
// 如果存在重复,添加数字后缀
for i := 1; i <= 1000; i++ {
candidateSlug := slug + "-" + strconv.Itoa(i)
if !existingCheck(candidateSlug) {
return candidateSlug
}
}
// 如果还是重复,使用时间戳
return slug + "-" + GenerateRandomString(6)
}

View File

@ -0,0 +1,153 @@
package utils
import (
"fmt"
"time"
)
// FormatTime 格式化时间为字符串
func FormatTime(t time.Time, layout string) string {
if layout == "" {
layout = "2006-01-02 15:04:05"
}
return t.Format(layout)
}
// ParseTime 解析时间字符串
func ParseTime(timeStr, layout string) (time.Time, error) {
if layout == "" {
layout = "2006-01-02 15:04:05"
}
return time.Parse(layout, timeStr)
}
// GetTimeAgo 获取相对时间描述
func GetTimeAgo(t time.Time) string {
now := time.Now()
diff := now.Sub(t)
if diff < time.Minute {
return "刚刚"
}
if diff < time.Hour {
minutes := int(diff.Minutes())
return fmt.Sprintf("%d分钟前", minutes)
}
if diff < 24*time.Hour {
hours := int(diff.Hours())
return fmt.Sprintf("%d小时前", hours)
}
if diff < 30*24*time.Hour {
days := int(diff.Hours() / 24)
return fmt.Sprintf("%d天前", days)
}
if diff < 365*24*time.Hour {
months := int(diff.Hours() / (24 * 30))
return fmt.Sprintf("%d个月前", months)
}
years := int(diff.Hours() / (24 * 365))
return fmt.Sprintf("%d年前", years)
}
// IsToday 检查时间是否为今天
func IsToday(t time.Time) bool {
now := time.Now()
return t.Year() == now.Year() && t.YearDay() == now.YearDay()
}
// IsThisWeek 检查时间是否为本周
func IsThisWeek(t time.Time) bool {
now := time.Now()
year, week := now.ISOWeek()
tYear, tWeek := t.ISOWeek()
return year == tYear && week == tWeek
}
// IsThisMonth 检查时间是否为本月
func IsThisMonth(t time.Time) bool {
now := time.Now()
return t.Year() == now.Year() && t.Month() == now.Month()
}
// IsThisYear 检查时间是否为今年
func IsThisYear(t time.Time) bool {
now := time.Now()
return t.Year() == now.Year()
}
// GetWeekRange 获取本周的开始和结束时间
func GetWeekRange(t time.Time) (start, end time.Time) {
// 获取周一作为周开始
weekday := int(t.Weekday())
if weekday == 0 {
weekday = 7 // 周日为7
}
start = t.AddDate(0, 0, -(weekday-1))
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, start.Location())
end = start.AddDate(0, 0, 6)
end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 999999999, end.Location())
return start, end
}
// GetMonthRange 获取本月的开始和结束时间
func GetMonthRange(t time.Time) (start, end time.Time) {
start = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
end = start.AddDate(0, 1, -1)
end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 999999999, end.Location())
return start, end
}
// GetYearRange 获取本年的开始和结束时间
func GetYearRange(t time.Time) (start, end time.Time) {
start = time.Date(t.Year(), 1, 1, 0, 0, 0, 0, t.Location())
end = time.Date(t.Year(), 12, 31, 23, 59, 59, 999999999, t.Location())
return start, end
}
// Timestamp 获取当前时间戳(秒)
func Timestamp() int64 {
return time.Now().Unix()
}
// TimestampMilli 获取当前时间戳(毫秒)
func TimestampMilli() int64 {
return time.Now().UnixNano() / 1e6
}
// FromTimestamp 从时间戳创建时间对象
func FromTimestamp(timestamp int64) time.Time {
return time.Unix(timestamp, 0)
}
// FromTimestampMilli 从毫秒时间戳创建时间对象
func FromTimestampMilli(timestamp int64) time.Time {
return time.Unix(0, timestamp*1e6)
}
// FormatDuration 格式化持续时间
func FormatDuration(d time.Duration) string {
if d < time.Minute {
return fmt.Sprintf("%.0f秒", d.Seconds())
}
if d < time.Hour {
return fmt.Sprintf("%.0f分钟", d.Minutes())
}
if d < 24*time.Hour {
return fmt.Sprintf("%.1f小时", d.Hours())
}
days := d.Hours() / 24
return fmt.Sprintf("%.1f天", days)
}

View File

@ -0,0 +1,128 @@
package utils
import (
"regexp"
"strings"
"unicode"
)
// IsValidEmail 验证邮箱格式
func IsValidEmail(email string) bool {
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
return emailRegex.MatchString(email)
}
// IsValidUsername 验证用户名格式
func IsValidUsername(username string) bool {
// 用户名长度3-20只能包含字母、数字、下划线
if len(username) < 3 || len(username) > 20 {
return false
}
usernameRegex := regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
return usernameRegex.MatchString(username)
}
// IsValidPassword 验证密码强度
func IsValidPassword(password string) bool {
// 密码长度至少6位
if len(password) < 6 {
return false
}
// 检查是否包含字母和数字
hasLetter := false
hasNumber := false
for _, char := range password {
if unicode.IsLetter(char) {
hasLetter = true
}
if unicode.IsNumber(char) {
hasNumber = true
}
}
return hasLetter && hasNumber
}
// IsValidSlug 验证slug格式
func IsValidSlug(slug string) bool {
// slug只能包含小写字母、数字和连字符
if len(slug) == 0 || len(slug) > 100 {
return false
}
slugRegex := regexp.MustCompile(`^[a-z0-9-]+$`)
return slugRegex.MatchString(slug) && !strings.HasPrefix(slug, "-") && !strings.HasSuffix(slug, "-")
}
// IsValidHexColor 验证十六进制颜色代码
func IsValidHexColor(color string) bool {
colorRegex := regexp.MustCompile(`^#[a-fA-F0-9]{6}$`)
return colorRegex.MatchString(color)
}
// IsValidURL 验证URL格式
func IsValidURL(url string) bool {
urlRegex := regexp.MustCompile(`^https?://[^\s/$.?#].[^\s]*$`)
return urlRegex.MatchString(url)
}
// SanitizeString 清理字符串移除HTML标签和特殊字符
func SanitizeString(input string) string {
// 移除HTML标签
htmlRegex := regexp.MustCompile(`<[^>]*>`)
cleaned := htmlRegex.ReplaceAllString(input, "")
// 移除多余的空白字符
whitespaceRegex := regexp.MustCompile(`\s+`)
cleaned = whitespaceRegex.ReplaceAllString(cleaned, " ")
return strings.TrimSpace(cleaned)
}
// ValidateImageFormat 验证图片格式
func ValidateImageFormat(filename string) bool {
allowedExtensions := []string{".jpg", ".jpeg", ".png", ".gif", ".webp"}
lowerFilename := strings.ToLower(filename)
for _, ext := range allowedExtensions {
if strings.HasSuffix(lowerFilename, ext) {
return true
}
}
return false
}
// ValidateFileSize 验证文件大小(字节)
func ValidateFileSize(size int64, maxSizeMB int64) bool {
maxSizeBytes := maxSizeMB * 1024 * 1024
return size <= maxSizeBytes && size > 0
}
// NormalizeString 标准化字符串(去空格、转小写)
func NormalizeString(s string) string {
return strings.ToLower(strings.TrimSpace(s))
}
// ContainsOnlyASCII 检查字符串是否只包含ASCII字符
func ContainsOnlyASCII(s string) bool {
for _, char := range s {
if char > 127 {
return false
}
}
return true
}
// Contains 检查切片是否包含指定元素
func Contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}