793 lines
21 KiB
Markdown
793 lines
21 KiB
Markdown
# 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. **配置化**: 支持配置化使用
|
||
|
||
本模块提供了应用的基础设施和工具支持,确保代码的可复用性和一致性。 |