refactor: 重构后端架构,采用 Go 风格四层设计模式
Some checks failed
部署后端服务 / 🧪 测试后端 (push) Failing after 1m37s
部署后端服务 / 🚀 构建并部署 (push) Has been skipped
部署后端服务 / 🔄 回滚部署 (push) Has been skipped

## 主要变更

### 🏗️ 架构重构
- 采用简洁的四层架构: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:
xujiang
2025-07-10 11:20:59 +08:00
parent 540593f1dc
commit a2f2f66f88
40 changed files with 9682 additions and 1798 deletions

View File

@ -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
}