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:
793
backend/pkg/CLAUDE.md
Normal file
793
backend/pkg/CLAUDE.md
Normal file
@ -0,0 +1,793 @@
|
||||
# Shared Package Layer - CLAUDE.md
|
||||
|
||||
本文件为 Claude Code 在共享包模块中工作时提供指导。
|
||||
|
||||
## 🎯 模块概览
|
||||
|
||||
pkg 包提供可复用的工具和组件,供整个应用使用,遵循 Go 语言的包设计哲学。
|
||||
|
||||
### 主要职责
|
||||
- 📦 提供通用工具和实用函数
|
||||
- 📝 提供日志记录组件
|
||||
- 🔄 提供统一响应格式
|
||||
- ✅ 提供数据验证工具
|
||||
- 🛠️ 提供辅助功能函数
|
||||
|
||||
## 📁 模块结构
|
||||
|
||||
```
|
||||
pkg/
|
||||
├── CLAUDE.md # 📋 当前文件 - 公共工具包指导
|
||||
├── logger/ # 📝 日志工具
|
||||
│ ├── logger.go # 日志接口和实现
|
||||
│ ├── zap_logger.go # Zap 日志实现
|
||||
│ ├── config.go # 日志配置
|
||||
│ └── logger_test.go # 日志测试
|
||||
├── response/ # 🔄 响应格式
|
||||
│ ├── response.go # 统一响应结构
|
||||
│ ├── error.go # 错误响应
|
||||
│ ├── success.go # 成功响应
|
||||
│ └── pagination.go # 分页响应
|
||||
├── validator/ # ✅ 数据验证
|
||||
│ ├── validator.go # 验证器接口
|
||||
│ ├── gin_validator.go # Gin 验证器实现
|
||||
│ ├── custom_validators.go # 自定义验证规则
|
||||
│ └── validator_test.go # 验证器测试
|
||||
├── utils/ # 🛠️ 通用工具
|
||||
│ ├── string.go # 字符串工具
|
||||
│ ├── time.go # 时间工具
|
||||
│ ├── crypto.go # 加密工具
|
||||
│ ├── file.go # 文件工具
|
||||
│ ├── uuid.go # UUID 生成
|
||||
│ └── utils_test.go # 工具测试
|
||||
├── middleware/ # 🔗 中间件
|
||||
│ ├── cors.go # CORS 中间件
|
||||
│ ├── rate_limit.go # 限流中间件
|
||||
│ ├── request_id.go # 请求ID中间件
|
||||
│ └── recovery.go # 错误恢复中间件
|
||||
├── database/ # 🗄️ 数据库工具
|
||||
│ ├── connection.go # 数据库连接
|
||||
│ ├── migrate.go # 数据库迁移
|
||||
│ └── health.go # 健康检查
|
||||
└── config/ # ⚙️ 配置工具
|
||||
├── config.go # 配置管理
|
||||
├── env.go # 环境变量处理
|
||||
└── validation.go # 配置验证
|
||||
```
|
||||
|
||||
## 📝 日志组件
|
||||
|
||||
### 日志接口设计
|
||||
```go
|
||||
// logger/logger.go - 日志接口
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Logger 日志接口
|
||||
type Logger interface {
|
||||
// 基础日志方法
|
||||
Debug(msg string, fields ...Field)
|
||||
Info(msg string, fields ...Field)
|
||||
Warn(msg string, fields ...Field)
|
||||
Error(msg string, fields ...Field)
|
||||
Fatal(msg string, fields ...Field)
|
||||
|
||||
// 上下文日志方法
|
||||
DebugContext(ctx context.Context, msg string, fields ...Field)
|
||||
InfoContext(ctx context.Context, msg string, fields ...Field)
|
||||
WarnContext(ctx context.Context, msg string, fields ...Field)
|
||||
ErrorContext(ctx context.Context, msg string, fields ...Field)
|
||||
|
||||
// 结构化字段方法
|
||||
With(fields ...Field) Logger
|
||||
WithError(err error) Logger
|
||||
WithContext(ctx context.Context) Logger
|
||||
|
||||
// 日志级别控制
|
||||
SetLevel(level Level)
|
||||
GetLevel() Level
|
||||
}
|
||||
|
||||
// Field 日志字段
|
||||
type Field struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Level 日志级别
|
||||
type Level int
|
||||
|
||||
const (
|
||||
DebugLevel Level = iota
|
||||
InfoLevel
|
||||
WarnLevel
|
||||
ErrorLevel
|
||||
FatalLevel
|
||||
)
|
||||
|
||||
// 便捷字段构造函数
|
||||
func String(key, value string) Field {
|
||||
return Field{Key: key, Value: value}
|
||||
}
|
||||
|
||||
func Int(key string, value int) Field {
|
||||
return Field{Key: key, Value: value}
|
||||
}
|
||||
|
||||
func Uint(key string, value uint) Field {
|
||||
return Field{Key: key, Value: value}
|
||||
}
|
||||
|
||||
func Error(err error) Field {
|
||||
return Field{Key: "error", Value: err}
|
||||
}
|
||||
|
||||
func Any(key string, value interface{}) Field {
|
||||
return Field{Key: key, Value: value}
|
||||
}
|
||||
```
|
||||
|
||||
### Zap 日志实现
|
||||
```go
|
||||
// logger/zap_logger.go - Zap 日志实现
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// ZapLogger Zap 日志实现
|
||||
type ZapLogger struct {
|
||||
logger *zap.Logger
|
||||
config *Config
|
||||
}
|
||||
|
||||
// Config 日志配置
|
||||
type Config struct {
|
||||
Level string `mapstructure:"level" json:"level"`
|
||||
Format string `mapstructure:"format" json:"format"` // json, console
|
||||
OutputPath string `mapstructure:"output_path" json:"output_path"`
|
||||
ErrorPath string `mapstructure:"error_path" json:"error_path"`
|
||||
MaxSize int `mapstructure:"max_size" json:"max_size"`
|
||||
MaxAge int `mapstructure:"max_age" json:"max_age"`
|
||||
MaxBackups int `mapstructure:"max_backups" json:"max_backups"`
|
||||
Compress bool `mapstructure:"compress" json:"compress"`
|
||||
}
|
||||
|
||||
// NewZapLogger 创建 Zap 日志实例
|
||||
func NewZapLogger(config *Config) (Logger, error) {
|
||||
zapConfig := zap.NewProductionConfig()
|
||||
|
||||
// 设置日志级别
|
||||
level, err := zapcore.ParseLevel(config.Level)
|
||||
if err != nil {
|
||||
level = zapcore.InfoLevel
|
||||
}
|
||||
zapConfig.Level.SetLevel(level)
|
||||
|
||||
// 设置输出格式
|
||||
if config.Format == "console" {
|
||||
zapConfig.Encoding = "console"
|
||||
zapConfig.EncoderConfig = zap.NewDevelopmentEncoderConfig()
|
||||
} else {
|
||||
zapConfig.Encoding = "json"
|
||||
zapConfig.EncoderConfig = zap.NewProductionEncoderConfig()
|
||||
}
|
||||
|
||||
// 设置输出路径
|
||||
if config.OutputPath != "" {
|
||||
zapConfig.OutputPaths = []string{config.OutputPath}
|
||||
}
|
||||
if config.ErrorPath != "" {
|
||||
zapConfig.ErrorOutputPaths = []string{config.ErrorPath}
|
||||
}
|
||||
|
||||
logger, err := zapConfig.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ZapLogger{
|
||||
logger: logger,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Debug 调试日志
|
||||
func (l *ZapLogger) Debug(msg string, fields ...Field) {
|
||||
l.logger.Debug(msg, l.convertFields(fields...)...)
|
||||
}
|
||||
|
||||
// Info 信息日志
|
||||
func (l *ZapLogger) Info(msg string, fields ...Field) {
|
||||
l.logger.Info(msg, l.convertFields(fields...)...)
|
||||
}
|
||||
|
||||
// Warn 警告日志
|
||||
func (l *ZapLogger) Warn(msg string, fields ...Field) {
|
||||
l.logger.Warn(msg, l.convertFields(fields...)...)
|
||||
}
|
||||
|
||||
// Error 错误日志
|
||||
func (l *ZapLogger) Error(msg string, fields ...Field) {
|
||||
l.logger.Error(msg, l.convertFields(fields...)...)
|
||||
}
|
||||
|
||||
// Fatal 致命错误日志
|
||||
func (l *ZapLogger) Fatal(msg string, fields ...Field) {
|
||||
l.logger.Fatal(msg, l.convertFields(fields...)...)
|
||||
}
|
||||
|
||||
// With 添加字段
|
||||
func (l *ZapLogger) With(fields ...Field) Logger {
|
||||
return &ZapLogger{
|
||||
logger: l.logger.With(l.convertFields(fields...)...),
|
||||
config: l.config,
|
||||
}
|
||||
}
|
||||
|
||||
// WithError 添加错误字段
|
||||
func (l *ZapLogger) WithError(err error) Logger {
|
||||
return l.With(Error(err))
|
||||
}
|
||||
|
||||
// convertFields 转换字段格式
|
||||
func (l *ZapLogger) convertFields(fields ...Field) []zap.Field {
|
||||
zapFields := make([]zap.Field, len(fields))
|
||||
for i, field := range fields {
|
||||
zapFields[i] = zap.Any(field.Key, field.Value)
|
||||
}
|
||||
return zapFields
|
||||
}
|
||||
|
||||
// NewNoop 创建空日志实例(用于测试)
|
||||
func NewNoop() Logger {
|
||||
return &ZapLogger{
|
||||
logger: zap.NewNop(),
|
||||
config: &Config{},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 响应格式
|
||||
|
||||
### 统一响应结构
|
||||
```go
|
||||
// response/response.go - 统一响应结构
|
||||
package response
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Response 基础响应结构
|
||||
type Response struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Error *Error `json:"error,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
}
|
||||
|
||||
// Error 错误信息结构
|
||||
type Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// PaginatedResponse 分页响应结构
|
||||
type PaginatedResponse struct {
|
||||
Response
|
||||
Pagination *Pagination `json:"pagination,omitempty"`
|
||||
}
|
||||
|
||||
// Pagination 分页信息
|
||||
type Pagination struct {
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
Total int64 `json:"total"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
HasNext bool `json:"has_next"`
|
||||
HasPrev bool `json:"has_prev"`
|
||||
}
|
||||
|
||||
// Success 成功响应
|
||||
func Success(c *gin.Context, data interface{}, message ...string) {
|
||||
msg := "Success"
|
||||
if len(message) > 0 {
|
||||
msg = message[0]
|
||||
}
|
||||
|
||||
response := Response{
|
||||
Success: true,
|
||||
Data: data,
|
||||
Message: msg,
|
||||
Timestamp: time.Now().Unix(),
|
||||
RequestID: getRequestID(c),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Error 错误响应
|
||||
func Error(c *gin.Context, code string, message string, details ...string) {
|
||||
detail := ""
|
||||
if len(details) > 0 {
|
||||
detail = details[0]
|
||||
}
|
||||
|
||||
response := Response{
|
||||
Success: false,
|
||||
Error: &Error{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Details: detail,
|
||||
},
|
||||
Timestamp: time.Now().Unix(),
|
||||
RequestID: getRequestID(c),
|
||||
}
|
||||
|
||||
c.JSON(getHTTPStatusFromCode(code), response)
|
||||
}
|
||||
|
||||
// BadRequest 400 错误
|
||||
func BadRequest(c *gin.Context, message string, details ...string) {
|
||||
Error(c, "BAD_REQUEST", message, details...)
|
||||
}
|
||||
|
||||
// Unauthorized 401 错误
|
||||
func Unauthorized(c *gin.Context, message string, details ...string) {
|
||||
Error(c, "UNAUTHORIZED", message, details...)
|
||||
}
|
||||
|
||||
// Forbidden 403 错误
|
||||
func Forbidden(c *gin.Context, message string, details ...string) {
|
||||
Error(c, "FORBIDDEN", message, details...)
|
||||
}
|
||||
|
||||
// NotFound 404 错误
|
||||
func NotFound(c *gin.Context, message string, details ...string) {
|
||||
Error(c, "NOT_FOUND", message, details...)
|
||||
}
|
||||
|
||||
// InternalError 500 错误
|
||||
func InternalError(c *gin.Context, message string, details ...string) {
|
||||
Error(c, "INTERNAL_ERROR", message, details...)
|
||||
}
|
||||
|
||||
// Paginated 分页响应
|
||||
func Paginated(c *gin.Context, data interface{}, page, limit int, total int64, message ...string) {
|
||||
msg := "Success"
|
||||
if len(message) > 0 {
|
||||
msg = message[0]
|
||||
}
|
||||
|
||||
totalPages := int((total + int64(limit) - 1) / int64(limit))
|
||||
|
||||
pagination := &Pagination{
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Total: total,
|
||||
TotalPages: totalPages,
|
||||
HasNext: page < totalPages,
|
||||
HasPrev: page > 1,
|
||||
}
|
||||
|
||||
response := PaginatedResponse{
|
||||
Response: Response{
|
||||
Success: true,
|
||||
Data: data,
|
||||
Message: msg,
|
||||
Timestamp: time.Now().Unix(),
|
||||
RequestID: getRequestID(c),
|
||||
},
|
||||
Pagination: pagination,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// getRequestID 获取请求ID
|
||||
func getRequestID(c *gin.Context) string {
|
||||
if requestID, exists := c.Get("X-Request-ID"); exists {
|
||||
if id, ok := requestID.(string); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getHTTPStatusFromCode 根据错误码获取HTTP状态码
|
||||
func getHTTPStatusFromCode(code string) int {
|
||||
switch code {
|
||||
case "BAD_REQUEST", "INVALID_PARAMETER", "VALIDATION_ERROR":
|
||||
return http.StatusBadRequest
|
||||
case "UNAUTHORIZED", "INVALID_TOKEN", "TOKEN_EXPIRED":
|
||||
return http.StatusUnauthorized
|
||||
case "FORBIDDEN", "PERMISSION_DENIED":
|
||||
return http.StatusForbidden
|
||||
case "NOT_FOUND", "USER_NOT_FOUND", "PHOTO_NOT_FOUND":
|
||||
return http.StatusNotFound
|
||||
case "CONFLICT", "USER_EXISTS", "DUPLICATE_KEY":
|
||||
return http.StatusConflict
|
||||
case "TOO_MANY_REQUESTS":
|
||||
return http.StatusTooManyRequests
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ✅ 数据验证
|
||||
|
||||
### 自定义验证器
|
||||
```go
|
||||
// validator/custom_validators.go - 自定义验证规则
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// RegisterCustomValidators 注册自定义验证器
|
||||
func RegisterCustomValidators(v *validator.Validate) {
|
||||
v.RegisterValidation("username", validateUsername)
|
||||
v.RegisterValidation("password", validatePassword)
|
||||
v.RegisterValidation("phone", validatePhone)
|
||||
v.RegisterValidation("slug", validateSlug)
|
||||
v.RegisterValidation("file_ext", validateFileExtension)
|
||||
}
|
||||
|
||||
// validateUsername 验证用户名
|
||||
func validateUsername(fl validator.FieldLevel) bool {
|
||||
username := fl.Field().String()
|
||||
|
||||
// 3-50个字符,只允许字母、数字、下划线、短横线
|
||||
matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]{3,50}$`, username)
|
||||
return matched
|
||||
}
|
||||
|
||||
// validatePassword 验证密码强度
|
||||
func validatePassword(fl validator.FieldLevel) bool {
|
||||
password := fl.Field().String()
|
||||
|
||||
// 至少8个字符,包含字母、数字和特殊字符
|
||||
if len(password) < 8 {
|
||||
return false
|
||||
}
|
||||
|
||||
hasLetter := regexp.MustCompile(`[a-zA-Z]`).MatchString(password)
|
||||
hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password)
|
||||
hasSpecial := regexp.MustCompile(`[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]`).MatchString(password)
|
||||
|
||||
return hasLetter && hasNumber && hasSpecial
|
||||
}
|
||||
|
||||
// validatePhone 验证手机号
|
||||
func validatePhone(fl validator.FieldLevel) bool {
|
||||
phone := fl.Field().String()
|
||||
|
||||
// 支持多种手机号格式
|
||||
patterns := []string{
|
||||
`^1[3-9]\d{9}$`, // 中国大陆
|
||||
`^\+1[2-9]\d{9}$`, // 美国
|
||||
`^\+44[1-9]\d{8,9}$`, // 英国
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
if matched, _ := regexp.MatchString(pattern, phone); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// validateSlug 验证 URL 友好字符串
|
||||
func validateSlug(fl validator.FieldLevel) bool {
|
||||
slug := fl.Field().String()
|
||||
matched, _ := regexp.MatchString(`^[a-z0-9]+(?:-[a-z0-9]+)*$`, slug)
|
||||
return matched
|
||||
}
|
||||
|
||||
// validateFileExtension 验证文件扩展名
|
||||
func validateFileExtension(fl validator.FieldLevel) bool {
|
||||
filename := fl.Field().String()
|
||||
ext := strings.ToLower(getFileExtension(filename))
|
||||
|
||||
// 允许的图片文件扩展名
|
||||
allowedExts := []string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".svg"}
|
||||
|
||||
for _, allowedExt := range allowedExts {
|
||||
if ext == allowedExt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getFileExtension 获取文件扩展名
|
||||
func getFileExtension(filename string) string {
|
||||
lastDot := strings.LastIndex(filename, ".")
|
||||
if lastDot == -1 {
|
||||
return ""
|
||||
}
|
||||
return filename[lastDot:]
|
||||
}
|
||||
|
||||
// ValidationError 验证错误结构
|
||||
type ValidationError struct {
|
||||
Field string `json:"field"`
|
||||
Value string `json:"value"`
|
||||
Tag string `json:"tag"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// FormatValidationErrors 格式化验证错误
|
||||
func FormatValidationErrors(err error) []ValidationError {
|
||||
var errors []ValidationError
|
||||
|
||||
if validationErrors, ok := err.(validator.ValidationErrors); ok {
|
||||
for _, e := range validationErrors {
|
||||
errors = append(errors, ValidationError{
|
||||
Field: e.Field(),
|
||||
Value: e.Value().(string),
|
||||
Tag: e.Tag(),
|
||||
Message: getErrorMessage(e),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
// getErrorMessage 获取错误消息
|
||||
func getErrorMessage(e validator.FieldError) string {
|
||||
switch e.Tag() {
|
||||
case "required":
|
||||
return e.Field() + " is required"
|
||||
case "email":
|
||||
return e.Field() + " must be a valid email"
|
||||
case "min":
|
||||
return e.Field() + " must be at least " + e.Param() + " characters"
|
||||
case "max":
|
||||
return e.Field() + " must be at most " + e.Param() + " characters"
|
||||
case "username":
|
||||
return e.Field() + " must be 3-50 characters and contain only letters, numbers, underscores, and hyphens"
|
||||
case "password":
|
||||
return e.Field() + " must be at least 8 characters and contain letters, numbers, and special characters"
|
||||
case "phone":
|
||||
return e.Field() + " must be a valid phone number"
|
||||
case "slug":
|
||||
return e.Field() + " must be a valid URL slug"
|
||||
case "file_ext":
|
||||
return e.Field() + " must have a valid image file extension"
|
||||
default:
|
||||
return e.Field() + " is invalid"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ 通用工具
|
||||
|
||||
### 字符串工具
|
||||
```go
|
||||
// utils/string.go - 字符串工具
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// GenerateRandomString 生成随机字符串
|
||||
func GenerateRandomString(length int) (string, error) {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
result := make([]byte, length)
|
||||
|
||||
for i := range result {
|
||||
index, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result[i] = charset[index.Int64()]
|
||||
}
|
||||
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
// ToSnakeCase 转换为蛇形命名
|
||||
func ToSnakeCase(str string) string {
|
||||
var result []rune
|
||||
|
||||
for i, r := range str {
|
||||
if unicode.IsUpper(r) {
|
||||
if i > 0 {
|
||||
result = append(result, '_')
|
||||
}
|
||||
result = append(result, unicode.ToLower(r))
|
||||
} else {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// ToCamelCase 转换为驼峰命名
|
||||
func ToCamelCase(str string) string {
|
||||
parts := strings.Split(str, "_")
|
||||
for i := 1; i < len(parts); i++ {
|
||||
if len(parts[i]) > 0 {
|
||||
parts[i] = strings.ToUpper(parts[i][:1]) + parts[i][1:]
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, "")
|
||||
}
|
||||
|
||||
// Truncate 截断字符串
|
||||
func Truncate(str string, length int) string {
|
||||
if len(str) <= length {
|
||||
return str
|
||||
}
|
||||
return str[:length] + "..."
|
||||
}
|
||||
|
||||
// IsValidEmail 验证邮箱格式
|
||||
func IsValidEmail(email string) bool {
|
||||
pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
|
||||
matched, _ := regexp.MatchString(pattern, email)
|
||||
return matched
|
||||
}
|
||||
|
||||
// SanitizeFilename 清理文件名
|
||||
func SanitizeFilename(filename string) string {
|
||||
// 移除或替换不安全的字符
|
||||
reg := regexp.MustCompile(`[<>:"/\\|?*]`)
|
||||
return reg.ReplaceAllString(filename, "_")
|
||||
}
|
||||
|
||||
// Contains 检查字符串切片是否包含指定字符串
|
||||
func Contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
### 加密工具
|
||||
```go
|
||||
// utils/crypto.go - 加密工具
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// HashPassword 加密密码
|
||||
func HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
// CheckPassword 验证密码
|
||||
func CheckPassword(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// MD5Hash 计算MD5哈希
|
||||
func MD5Hash(data string) string {
|
||||
hash := md5.Sum([]byte(data))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// SHA256Hash 计算SHA256哈希
|
||||
func SHA256Hash(data string) string {
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
```
|
||||
|
||||
## 🔗 中间件
|
||||
|
||||
### CORS 中间件
|
||||
```go
|
||||
// middleware/cors.go - CORS 中间件
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// CORS 创建CORS中间件
|
||||
func CORS() gin.HandlerFunc {
|
||||
config := cors.DefaultConfig()
|
||||
config.AllowOrigins = []string{"*"}
|
||||
config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
|
||||
config.AllowHeaders = []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Request-ID"}
|
||||
config.ExposeHeaders = []string{"X-Request-ID"}
|
||||
config.AllowCredentials = true
|
||||
|
||||
return cors.New(config)
|
||||
}
|
||||
```
|
||||
|
||||
### 请求ID中间件
|
||||
```go
|
||||
// middleware/request_id.go - 请求ID中间件
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// RequestID 请求ID中间件
|
||||
func RequestID() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
if requestID == "" {
|
||||
requestID = uuid.New().String()
|
||||
}
|
||||
|
||||
c.Set("X-Request-ID", requestID)
|
||||
c.Header("X-Request-ID", requestID)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 包设计原则
|
||||
1. **单一职责**: 每个包只负责一个明确的功能
|
||||
2. **接口导向**: 优先定义接口,便于测试和替换
|
||||
3. **零依赖**: 包应尽量减少外部依赖
|
||||
4. **文档完善**: 提供清晰的使用文档和示例
|
||||
5. **向后兼容**: 保持API的向后兼容性
|
||||
|
||||
### 代码质量
|
||||
1. **测试覆盖**: 为所有公共函数编写测试
|
||||
2. **错误处理**: 合理处理和传播错误
|
||||
3. **性能考虑**: 关注内存分配和性能影响
|
||||
4. **线程安全**: 确保并发使用的安全性
|
||||
5. **资源管理**: 及时释放资源
|
||||
|
||||
### 使用建议
|
||||
1. **导入路径**: 使用清晰的导入路径
|
||||
2. **命名规范**: 遵循 Go 语言命名约定
|
||||
3. **版本管理**: 使用语义化版本管理
|
||||
4. **依赖管理**: 合理管理第三方依赖
|
||||
5. **配置化**: 支持配置化使用
|
||||
|
||||
本模块提供了应用的基础设施和工具支持,确保代码的可复用性和一致性。
|
||||
Reference in New Issue
Block a user