refactor: 重构后端架构,采用 Go 风格四层设计模式
## 主要变更 ### 🏗️ 架构重构 - 采用简洁的四层架构:API → Service → Repository → Model - 遵循 Go 语言最佳实践和命名规范 - 实现依赖注入和接口导向设计 - 统一错误处理和响应格式 ### 📁 目录结构优化 - 删除重复模块 (application/, domain/, infrastructure/ 等) - 规范化命名 (使用 Go 风格的 snake_case) - 清理无关文件 (package.json, node_modules/ 等) - 新增规范化的测试目录结构 ### 📚 文档系统 - 为每个模块创建详细的 CLAUDE.md 指导文件 - 包含开发规范、最佳实践和使用示例 - 支持模块化开发,缩短上下文长度 ### 🔧 开发规范 - 统一接口命名规范 (UserServicer, PhotoRepositoryr) - 标准化错误处理机制 - 完善的测试策略 (单元测试、集成测试、性能测试) - 规范化的配置管理 ### 🗂️ 新增文件 - cmd/server/ - 服务启动入口和配置 - internal/model/ - 数据模型层 (entity, dto, request) - pkg/ - 共享工具包 (logger, response, validator) - tests/ - 完整测试结构 - docs/ - API 文档和架构设计 - .gitignore - Git 忽略文件配置 ### 🗑️ 清理内容 - 删除 Node.js 相关文件 (package.json, node_modules/) - 移除重复的架构目录 - 清理临时文件和构建产物 - 删除重复的文档文件 ## 影响 - 提高代码可维护性和可扩展性 - 统一开发规范,提升团队协作效率 - 优化项目结构,符合 Go 语言生态标准 - 完善文档体系,降低上手难度
This commit is contained in:
@ -1,243 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/text/runes"
|
||||
"golang.org/x/text/transform"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
// Contains 检查字符串切片是否包含指定字符串
|
||||
func Contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsUint 检查 uint 切片是否包含指定值
|
||||
func ContainsUint(slice []uint, item uint) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateUniqueFilename 生成唯一文件名
|
||||
func GenerateUniqueFilename(originalFilename string) string {
|
||||
ext := filepath.Ext(originalFilename)
|
||||
name := strings.TrimSuffix(originalFilename, ext)
|
||||
|
||||
// 生成时间戳和哈希
|
||||
timestamp := time.Now().Unix()
|
||||
hash := md5.Sum([]byte(fmt.Sprintf("%s%d", name, timestamp)))
|
||||
|
||||
return fmt.Sprintf("%d_%x%s", timestamp, hash[:8], ext)
|
||||
}
|
||||
|
||||
// GenerateSlug 生成 URL 友好的 slug
|
||||
func GenerateSlug(text string) string {
|
||||
// 转换为小写
|
||||
slug := strings.ToLower(text)
|
||||
|
||||
// 移除重音符号
|
||||
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
||||
slug, _, _ = transform.String(t, slug)
|
||||
|
||||
// 只保留字母、数字、连字符和下划线
|
||||
reg := regexp.MustCompile(`[^a-z0-9\-_\s]`)
|
||||
slug = reg.ReplaceAllString(slug, "")
|
||||
|
||||
// 将空格替换为连字符
|
||||
slug = regexp.MustCompile(`\s+`).ReplaceAllString(slug, "-")
|
||||
|
||||
// 移除多余的连字符
|
||||
slug = regexp.MustCompile(`-+`).ReplaceAllString(slug, "-")
|
||||
|
||||
// 移除开头和结尾的连字符
|
||||
slug = strings.Trim(slug, "-")
|
||||
|
||||
return slug
|
||||
}
|
||||
|
||||
// ValidateEmail 验证邮箱格式
|
||||
func ValidateEmail(email string) bool {
|
||||
emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`)
|
||||
return emailRegex.MatchString(strings.ToLower(email))
|
||||
}
|
||||
|
||||
// ValidatePassword 验证密码强度
|
||||
func ValidatePassword(password string) bool {
|
||||
if len(password) < 8 {
|
||||
return false
|
||||
}
|
||||
|
||||
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
|
||||
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
|
||||
hasDigit := regexp.MustCompile(`\d`).MatchString(password)
|
||||
|
||||
return hasLower && hasUpper && hasDigit
|
||||
}
|
||||
|
||||
// Paginate 计算分页参数
|
||||
func Paginate(page, limit int) (offset int) {
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
}
|
||||
if limit > 100 {
|
||||
limit = 100
|
||||
}
|
||||
|
||||
offset = (page - 1) * limit
|
||||
return offset
|
||||
}
|
||||
|
||||
// CalculatePages 计算总页数
|
||||
func CalculatePages(total int64, limit int) int {
|
||||
if limit <= 0 {
|
||||
return 0
|
||||
}
|
||||
return int((total + int64(limit) - 1) / int64(limit))
|
||||
}
|
||||
|
||||
// TruncateString 截断字符串
|
||||
func TruncateString(s string, maxLength int) string {
|
||||
if len(s) <= maxLength {
|
||||
return s
|
||||
}
|
||||
return s[:maxLength] + "..."
|
||||
}
|
||||
|
||||
// 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++
|
||||
}
|
||||
|
||||
sizes := []string{"KB", "MB", "GB", "TB", "PB"}
|
||||
return fmt.Sprintf("%.1f %s", float64(bytes)/float64(div), sizes[exp])
|
||||
}
|
||||
|
||||
// ParseSortOrder 解析排序方向
|
||||
func ParseSortOrder(order string) string {
|
||||
order = strings.ToLower(strings.TrimSpace(order))
|
||||
if order == "asc" || order == "desc" {
|
||||
return order
|
||||
}
|
||||
return "desc" // 默认降序
|
||||
}
|
||||
|
||||
// SanitizeSearchQuery 清理搜索查询
|
||||
func SanitizeSearchQuery(query string) string {
|
||||
// 移除特殊字符,只保留字母、数字、空格和常用标点
|
||||
reg := regexp.MustCompile(`[^\w\s\-\.\_\@]`)
|
||||
query = reg.ReplaceAllString(query, "")
|
||||
|
||||
// 移除多余的空格
|
||||
query = regexp.MustCompile(`\s+`).ReplaceAllString(query, " ")
|
||||
|
||||
return strings.TrimSpace(query)
|
||||
}
|
||||
|
||||
// GenerateRandomString 生成随机字符串
|
||||
func GenerateRandomString(length int) string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
// 使用当前时间作为种子
|
||||
timestamp := time.Now().UnixNano()
|
||||
|
||||
result := make([]byte, length)
|
||||
for i := range result {
|
||||
result[i] = charset[(timestamp+int64(i))%int64(len(charset))]
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// IsValidImageExtension 检查是否为有效的图片扩展名
|
||||
func IsValidImageExtension(filename string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
validExts := []string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff"}
|
||||
return Contains(validExts, ext)
|
||||
}
|
||||
|
||||
// GetImageMimeType 根据文件扩展名获取 MIME 类型
|
||||
func GetImageMimeType(filename string) string {
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
mimeTypes := map[string]string{
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".png": "image/png",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".bmp": "image/bmp",
|
||||
".tiff": "image/tiff",
|
||||
}
|
||||
|
||||
if mimeType, exists := mimeTypes[ext]; exists {
|
||||
return mimeType
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
|
||||
// RemoveEmptyStrings 移除字符串切片中的空字符串
|
||||
func RemoveEmptyStrings(slice []string) []string {
|
||||
var result []string
|
||||
for _, s := range slice {
|
||||
if strings.TrimSpace(s) != "" {
|
||||
result = append(result, strings.TrimSpace(s))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// UniqueStrings 去重字符串切片
|
||||
func UniqueStrings(slice []string) []string {
|
||||
keys := make(map[string]bool)
|
||||
var result []string
|
||||
|
||||
for _, item := range slice {
|
||||
if !keys[item] {
|
||||
keys[item] = true
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// UniqueUints 去重 uint 切片
|
||||
func UniqueUints(slice []uint) []uint {
|
||||
keys := make(map[uint]bool)
|
||||
var result []uint
|
||||
|
||||
for _, item := range slice {
|
||||
if !keys[item] {
|
||||
keys[item] = true
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user