feat():learning后台管理项目初始化
This commit is contained in:
133
pkg/utils/README.md
Normal file
133
pkg/utils/README.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# 工具包
|
||||
|
||||
本模块提供各种实用工具函数和类。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 加密工具(MD5、SHA256、密码哈希)
|
||||
- JWT token管理
|
||||
- 统一响应处理
|
||||
- 数据验证
|
||||
- 日志管理
|
||||
|
||||
## 模块结构
|
||||
|
||||
```
|
||||
utils/
|
||||
├── crypto.go # 加密工具
|
||||
├── jwt.go # JWT管理
|
||||
├── response.go # 响应处理
|
||||
├── validator.go # 数据验证
|
||||
├── logger.go # 日志管理
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 加密工具
|
||||
|
||||
```go
|
||||
import "goalfymax-admin/pkg/utils"
|
||||
|
||||
// MD5哈希
|
||||
hash := utils.MD5Hash("password")
|
||||
|
||||
// SHA256哈希
|
||||
hash := utils.SHA256Hash("password")
|
||||
|
||||
// 生成盐值
|
||||
salt, err := utils.GenerateSalt()
|
||||
|
||||
// 哈希密码
|
||||
hashedPassword := utils.HashPassword("password", salt)
|
||||
|
||||
// 验证密码
|
||||
isValid := utils.VerifyPassword("password", salt, hashedPassword)
|
||||
```
|
||||
|
||||
### JWT管理
|
||||
|
||||
```go
|
||||
// 创建JWT管理器
|
||||
jwtManager := utils.NewJWTManager("your-secret-key")
|
||||
|
||||
// 生成token
|
||||
token, err := jwtManager.GenerateToken(1, "admin", "admin")
|
||||
|
||||
// 解析token
|
||||
claims, err := jwtManager.ParseToken(token)
|
||||
|
||||
// 刷新token
|
||||
newToken, err := jwtManager.RefreshToken(token)
|
||||
```
|
||||
|
||||
### 响应处理
|
||||
|
||||
```go
|
||||
// 创建响应实例
|
||||
resp := utils.NewResponse()
|
||||
|
||||
// 成功响应
|
||||
resp.Success(c, data)
|
||||
|
||||
// 错误响应
|
||||
resp.Error(c, 400, "参数错误")
|
||||
resp.BadRequest(c, "请求参数错误")
|
||||
resp.Unauthorized(c, "未授权")
|
||||
resp.Forbidden(c, "禁止访问")
|
||||
resp.NotFound(c, "资源不存在")
|
||||
resp.InternalServerError(c, "服务器内部错误")
|
||||
|
||||
// 分页响应
|
||||
resp.Page(c, data, total, page, size)
|
||||
```
|
||||
|
||||
### 数据验证
|
||||
|
||||
```go
|
||||
// 创建验证器
|
||||
validator := utils.NewValidator()
|
||||
|
||||
// 验证邮箱
|
||||
isValid := validator.IsEmail("user@example.com")
|
||||
|
||||
// 验证手机号
|
||||
isValid := validator.IsPhone("13800138000")
|
||||
|
||||
// 验证用户名
|
||||
isValid := validator.IsUsername("admin")
|
||||
|
||||
// 验证密码强度
|
||||
isValid := validator.IsPassword("password123")
|
||||
|
||||
// 验证URL
|
||||
isValid := validator.IsURL("https://example.com")
|
||||
|
||||
// 检查是否为空
|
||||
isEmpty := validator.IsEmpty("")
|
||||
|
||||
// 验证角色
|
||||
isValid := validator.IsValidRole("admin")
|
||||
```
|
||||
|
||||
### 日志管理
|
||||
|
||||
```go
|
||||
// 创建日志实例
|
||||
logger, err := utils.NewLogger("info", "json", "stdout")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 记录日志
|
||||
logger.Info("用户登录", zap.String("username", "admin"))
|
||||
logger.Error("登录失败", zap.String("error", "密码错误"))
|
||||
|
||||
// 添加字段
|
||||
logger.WithField("user_id", 1).Info("用户操作")
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"user_id": 1,
|
||||
"action": "login",
|
||||
}).Info("用户登录")
|
||||
```
|
||||
|
||||
55
pkg/utils/crypto.go
Normal file
55
pkg/utils/crypto.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// MD5Hash 计算MD5哈希
|
||||
func MD5Hash(text string) string {
|
||||
hash := md5.Sum([]byte(text))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// SHA256Hash 计算SHA256哈希
|
||||
func SHA256Hash(text string) string {
|
||||
hash := sha256.Sum256([]byte(text))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// GenerateRandomString 生成指定长度的随机字符串
|
||||
func GenerateRandomString(length int) (string, error) {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b[i] = charset[num.Int64()]
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// GenerateSalt 生成盐值
|
||||
func GenerateSalt() (string, error) {
|
||||
return GenerateRandomString(16)
|
||||
}
|
||||
|
||||
// HashPassword 使用盐值哈希密码
|
||||
func HashPassword(password, salt string) string {
|
||||
return SHA256Hash(password + salt)
|
||||
}
|
||||
|
||||
// VerifyPassword 验证密码
|
||||
func VerifyPassword(password, salt, hash string) bool {
|
||||
return HashPassword(password, salt) == hash
|
||||
}
|
||||
|
||||
// GenerateToken 生成随机token
|
||||
func GenerateToken() (string, error) {
|
||||
return GenerateRandomString(32)
|
||||
}
|
||||
80
pkg/utils/jwt.go
Normal file
80
pkg/utils/jwt.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// JWTClaims JWT声明
|
||||
type JWTClaims struct {
|
||||
UserID uint `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// JWTManager JWT管理器
|
||||
type JWTManager struct {
|
||||
secretKey string
|
||||
}
|
||||
|
||||
// NewJWTManager 创建JWT管理器
|
||||
func NewJWTManager(secretKey string) *JWTManager {
|
||||
return &JWTManager{
|
||||
secretKey: secretKey,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateToken 生成JWT token
|
||||
func (j *JWTManager) GenerateToken(userID uint, username string) (string, error) {
|
||||
claims := JWTClaims{
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
Role: "", // 不再使用role字段
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(j.secretKey))
|
||||
}
|
||||
|
||||
// ParseToken 解析JWT token
|
||||
func (j *JWTManager) ParseToken(tokenString string) (*JWTClaims, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("unexpected signing method")
|
||||
}
|
||||
return []byte(j.secretKey), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
// RefreshToken 刷新token
|
||||
func (j *JWTManager) RefreshToken(tokenString string) (string, error) {
|
||||
claims, err := j.ParseToken(tokenString)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 检查token是否即将过期(剩余时间少于1小时)
|
||||
if time.Until(claims.ExpiresAt.Time) < time.Hour {
|
||||
return j.GenerateToken(claims.UserID, claims.Username)
|
||||
}
|
||||
|
||||
return tokenString, nil
|
||||
}
|
||||
98
pkg/utils/logger.go
Normal file
98
pkg/utils/logger.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// Logger 日志管理器
|
||||
type Logger struct {
|
||||
*zap.Logger
|
||||
}
|
||||
|
||||
// NewLogger 创建日志实例
|
||||
func NewLogger(level, format, output string) (*Logger, error) {
|
||||
// 设置日志级别
|
||||
var zapLevel zapcore.Level
|
||||
switch level {
|
||||
case "debug":
|
||||
zapLevel = zapcore.DebugLevel
|
||||
case "info":
|
||||
zapLevel = zapcore.InfoLevel
|
||||
case "warn":
|
||||
zapLevel = zapcore.WarnLevel
|
||||
case "error":
|
||||
zapLevel = zapcore.ErrorLevel
|
||||
default:
|
||||
zapLevel = zapcore.InfoLevel
|
||||
}
|
||||
|
||||
// 设置编码格式
|
||||
var encoderConfig zapcore.EncoderConfig
|
||||
if format == "json" {
|
||||
encoderConfig = zap.NewProductionEncoderConfig()
|
||||
} else {
|
||||
encoderConfig = zap.NewDevelopmentEncoderConfig()
|
||||
}
|
||||
|
||||
// 设置时间格式
|
||||
encoderConfig.TimeKey = "timestamp"
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
|
||||
// 创建配置
|
||||
config := zap.Config{
|
||||
Level: zap.NewAtomicLevelAt(zapLevel),
|
||||
Development: format != "json",
|
||||
Encoding: format,
|
||||
EncoderConfig: encoderConfig,
|
||||
OutputPaths: []string{output},
|
||||
ErrorOutputPaths: []string{output},
|
||||
}
|
||||
|
||||
// 创建logger
|
||||
logger, err := config.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Logger{Logger: logger}, nil
|
||||
}
|
||||
|
||||
// Info 记录信息日志
|
||||
func (l *Logger) Info(msg string, fields ...zap.Field) {
|
||||
l.Logger.Info(msg, fields...)
|
||||
}
|
||||
|
||||
// Debug 记录调试日志
|
||||
func (l *Logger) Debug(msg string, fields ...zap.Field) {
|
||||
l.Logger.Debug(msg, fields...)
|
||||
}
|
||||
|
||||
// Warn 记录警告日志
|
||||
func (l *Logger) Warn(msg string, fields ...zap.Field) {
|
||||
l.Logger.Warn(msg, fields...)
|
||||
}
|
||||
|
||||
// Error 记录错误日志
|
||||
func (l *Logger) Error(msg string, fields ...zap.Field) {
|
||||
l.Logger.Error(msg, fields...)
|
||||
}
|
||||
|
||||
// Fatal 记录致命错误日志
|
||||
func (l *Logger) Fatal(msg string, fields ...zap.Field) {
|
||||
l.Logger.Fatal(msg, fields...)
|
||||
}
|
||||
|
||||
// WithField 添加字段
|
||||
func (l *Logger) WithField(key string, value interface{}) *Logger {
|
||||
return &Logger{Logger: l.Logger.With(zap.Any(key, value))}
|
||||
}
|
||||
|
||||
// WithFields 添加多个字段
|
||||
func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
|
||||
zapFields := make([]zap.Field, 0, len(fields))
|
||||
for k, v := range fields {
|
||||
zapFields = append(zapFields, zap.Any(k, v))
|
||||
}
|
||||
return &Logger{Logger: l.Logger.With(zapFields...)}
|
||||
}
|
||||
61
pkg/utils/response.go
Normal file
61
pkg/utils/response.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"goalfymax-admin/internal/models"
|
||||
)
|
||||
|
||||
// Response 统一响应处理
|
||||
type Response struct{}
|
||||
|
||||
// Success 成功响应
|
||||
func (r *Response) Success(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, models.NewSuccessResponse(data))
|
||||
}
|
||||
|
||||
// Error 错误响应
|
||||
func (r *Response) Error(c *gin.Context, code int, message string) {
|
||||
c.JSON(code, models.NewResponse(code, message, nil))
|
||||
}
|
||||
|
||||
// BadRequest 400错误
|
||||
func (r *Response) BadRequest(c *gin.Context, message string) {
|
||||
r.Error(c, http.StatusBadRequest, message)
|
||||
}
|
||||
|
||||
// Unauthorized 401错误
|
||||
func (r *Response) Unauthorized(c *gin.Context, message string) {
|
||||
r.Error(c, http.StatusUnauthorized, message)
|
||||
}
|
||||
|
||||
// Forbidden 403错误
|
||||
func (r *Response) Forbidden(c *gin.Context, message string) {
|
||||
r.Error(c, http.StatusForbidden, message)
|
||||
}
|
||||
|
||||
// NotFound 404错误
|
||||
func (r *Response) NotFound(c *gin.Context, message string) {
|
||||
r.Error(c, http.StatusNotFound, message)
|
||||
}
|
||||
|
||||
// InternalServerError 500错误
|
||||
func (r *Response) InternalServerError(c *gin.Context, message string) {
|
||||
r.Error(c, http.StatusInternalServerError, message)
|
||||
}
|
||||
|
||||
// Page 分页响应
|
||||
func (r *Response) Page(c *gin.Context, data interface{}, total int64, page, size int) {
|
||||
c.JSON(http.StatusOK, models.NewPageResponse(data, total, page, size))
|
||||
}
|
||||
|
||||
// ValidateError 验证错误响应
|
||||
func (r *Response) ValidateError(c *gin.Context, err error) {
|
||||
r.BadRequest(c, err.Error())
|
||||
}
|
||||
|
||||
// NewResponse 创建响应实例
|
||||
func NewResponse() *Response {
|
||||
return &Response{}
|
||||
}
|
||||
72
pkg/utils/validator.go
Normal file
72
pkg/utils/validator.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Validator 验证器
|
||||
type Validator struct{}
|
||||
|
||||
// IsEmail 验证邮箱格式
|
||||
func (v *Validator) IsEmail(email string) bool {
|
||||
pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
|
||||
matched, _ := regexp.MatchString(pattern, email)
|
||||
return matched
|
||||
}
|
||||
|
||||
// IsPhone 验证手机号格式
|
||||
func (v *Validator) IsPhone(phone string) bool {
|
||||
pattern := `^1[3-9]\d{9}$`
|
||||
matched, _ := regexp.MatchString(pattern, phone)
|
||||
return matched
|
||||
}
|
||||
|
||||
// IsUsername 验证用户名格式
|
||||
func (v *Validator) IsUsername(username string) bool {
|
||||
// 用户名只能包含字母、数字、下划线,长度3-20
|
||||
pattern := `^[a-zA-Z0-9_]{3,20}$`
|
||||
matched, _ := regexp.MatchString(pattern, username)
|
||||
return matched
|
||||
}
|
||||
|
||||
// IsPassword 验证密码强度
|
||||
func (v *Validator) IsPassword(password string) bool {
|
||||
// 密码至少6位,包含字母和数字
|
||||
if len(password) < 6 {
|
||||
return false
|
||||
}
|
||||
|
||||
hasLetter := regexp.MustCompile(`[a-zA-Z]`).MatchString(password)
|
||||
hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password)
|
||||
|
||||
return hasLetter && hasNumber
|
||||
}
|
||||
|
||||
// IsURL 验证URL格式
|
||||
func (v *Validator) IsURL(url string) bool {
|
||||
pattern := `^https?://[^\s/$.?#].[^\s]*$`
|
||||
matched, _ := regexp.MatchString(pattern, url)
|
||||
return matched
|
||||
}
|
||||
|
||||
// IsEmpty 检查字符串是否为空
|
||||
func (v *Validator) IsEmpty(str string) bool {
|
||||
return strings.TrimSpace(str) == ""
|
||||
}
|
||||
|
||||
// IsValidRole 验证角色名称
|
||||
func (v *Validator) IsValidRole(role string) bool {
|
||||
validRoles := []string{"admin", "user", "guest"}
|
||||
for _, validRole := range validRoles {
|
||||
if role == validRole {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewValidator 创建验证器实例
|
||||
func NewValidator() *Validator {
|
||||
return &Validator{}
|
||||
}
|
||||
Reference in New Issue
Block a user