first commit
This commit is contained in:
126
utils/generator_util.go
Normal file
126
utils/generator_util.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// Package utils generator_util.go
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateUserCode 生成指定前缀 + 指定长度随机数字的编码
|
||||
// prefix: 字符串前缀,如 "S", "U" 等
|
||||
// length: 数字部分长度,默认为 9 位
|
||||
// 返回带前缀的 usercode,例如 S123456789
|
||||
func GenerateUserCode(prefix string, length int) string {
|
||||
// 设置随机种子
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// 计算数字范围
|
||||
mini := 1
|
||||
maxi := 9
|
||||
for i := 1; i < length; i++ {
|
||||
mini *= 10
|
||||
maxi = maxi*10 + 9
|
||||
}
|
||||
|
||||
// 生成随机数
|
||||
randomNum := rand.Intn(maxi-mini+1) + mini
|
||||
return fmt.Sprintf("%s%d", prefix, randomNum)
|
||||
}
|
||||
|
||||
// GenerateEmail 生成随机电子邮箱地址
|
||||
// domain: 邮箱域名,默认为 "example.com"
|
||||
// usernameLength: 用户名部分长度,默认为 8 位
|
||||
// 返回随机邮箱地址,如 "a1b2c3d4@example.com"
|
||||
func GenerateEmail(domain string, usernameLength int) (string, error) {
|
||||
// 设置随机种子
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// 如果域名为空,使用默认域名
|
||||
if domain == "" {
|
||||
domain = "example.com"
|
||||
}
|
||||
|
||||
// 如果用户名长度小于1,使用默认长度
|
||||
if usernameLength < 1 {
|
||||
usernameLength = 8
|
||||
}
|
||||
|
||||
// 定义字符集(小写字母和数字)
|
||||
charset := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
// 最多重试 5 次
|
||||
for i := 0; i < 5; i++ {
|
||||
// 生成指定长度的用户名
|
||||
username := make([]byte, usernameLength)
|
||||
for j := range username {
|
||||
username[j] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
|
||||
email := fmt.Sprintf("%s@%s", string(username), domain)
|
||||
|
||||
// 检查邮箱有效性
|
||||
if isValidEmail(email) {
|
||||
return email, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("无法生成有效邮箱地址,请调整参数")
|
||||
}
|
||||
|
||||
// isValidEmail 简单验证邮箱地址有效性
|
||||
func isValidEmail(email string) bool {
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
username := parts[0]
|
||||
domain := parts[1]
|
||||
|
||||
// 检查用户名和域名是否非空
|
||||
if username == "" || domain == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查域名是否包含点
|
||||
if !strings.Contains(domain, ".") {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查域名的点是否在正确位置
|
||||
domainParts := strings.Split(domain, ".")
|
||||
for _, part := range domainParts {
|
||||
if part == "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GenerateUsername 生成全英文随机用户名
|
||||
// length: 用户名长度,默认为 8
|
||||
// 返回全小写英文用户名,例如 "abcdefgh"
|
||||
// 如果长度参数无效会返回错误
|
||||
func GenerateUsername(length int) (string, error) {
|
||||
// 设置随机种子
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// 验证长度参数
|
||||
if length < 4 || length > 20 {
|
||||
return "", fmt.Errorf("用户名长度需在4-20个字符之间")
|
||||
}
|
||||
|
||||
// 定义字符集(仅小写字母)
|
||||
charset := "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
// 生成指定长度的用户名
|
||||
username := make([]byte, length)
|
||||
for i := range username {
|
||||
username[i] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
|
||||
return string(username), nil
|
||||
}
|
||||
37
utils/geoip_util.go
Normal file
37
utils/geoip_util.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Package utils/geoip_util.go
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
var (
|
||||
geoDB *geoip2.Reader
|
||||
geoOnce sync.Once
|
||||
)
|
||||
|
||||
// InitGeoIP 初始化 GeoIP 数据库
|
||||
func InitGeoIP(geoDBPath string) error {
|
||||
var err error
|
||||
geoOnce.Do(func() {
|
||||
if geoDBPath != "" {
|
||||
geoDB, err = geoip2.Open(geoDBPath)
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetGeoIP 获取 GeoIP 实例
|
||||
func GetGeoIP() *geoip2.Reader {
|
||||
return geoDB
|
||||
}
|
||||
|
||||
// CloseGeoIP 关闭 GeoIP 数据库
|
||||
func CloseGeoIP() error {
|
||||
if geoDB != nil {
|
||||
return geoDB.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
77
utils/jwt_util.go
Normal file
77
utils/jwt_util.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"Quincy_admin/config"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// Claims JWT 声明结构
|
||||
type Claims struct {
|
||||
UserID int `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
SessionCode string `json:"sessioncode"`
|
||||
RoleCode int `json:"rolecode"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// GenerateToken 生成 JWT token
|
||||
func GenerateToken(userID int, username, sessionCode string, roleCode int) (string, error) {
|
||||
// 从配置文件或其他地方获取密钥,这里先使用硬编码的密钥
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
secretKey := []byte(cfg.Jwt.Secret)
|
||||
|
||||
expireHours, _ := strconv.Atoi(cfg.Jwt.Expire)
|
||||
if expireHours <= 1 {
|
||||
expireHours = 1
|
||||
}
|
||||
|
||||
expirationTime := time.Now().Add(time.Duration(expireHours) * time.Hour)
|
||||
|
||||
// 创建 JWT 声明
|
||||
claims := &Claims{
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
SessionCode: sessionCode,
|
||||
RoleCode: roleCode,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(expirationTime),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: "Quincy_admin",
|
||||
},
|
||||
}
|
||||
|
||||
// 创建 token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// 签名并获取完整的 token 字符串
|
||||
return token.SignedString(secretKey)
|
||||
}
|
||||
|
||||
// ParseToken 解析 JWT token
|
||||
func ParseToken(tokenString string) (*Claims, error) {
|
||||
secretKey := []byte("hcq")
|
||||
|
||||
// 解析 token
|
||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("unexpected signing method")
|
||||
}
|
||||
return secretKey, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 验证并返回 claims
|
||||
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
48
utils/mysql_util.go
Normal file
48
utils/mysql_util.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"Quincy_admin/config"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/go-sql-driver/mysql" // MySQL驱动
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var db *sqlx.DB // 全局数据库连接实例,使用sqlx.DB替代sql.DB
|
||||
var once sync.Once // 确保初始化只执行一次
|
||||
|
||||
// InitDB 初始化数据库连接
|
||||
func InitDB(cfg *config.Config) (*sqlx.DB, error) {
|
||||
var err error
|
||||
once.Do(func() {
|
||||
// 构建数据库连接字符串 - 添加时区参数
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&loc=Asia%%2FShanghai",
|
||||
cfg.DB.User, cfg.DB.Password, cfg.DB.Host, cfg.DB.Port, cfg.DB.Name)
|
||||
|
||||
// 使用sqlx.Open替代sql.Open
|
||||
db, err = sqlx.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening database: %v", err)
|
||||
}
|
||||
|
||||
// 测试数据库连接
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
log.Fatalf("Error connecting to database: %v", err)
|
||||
}
|
||||
|
||||
// 设置连接池参数
|
||||
db.SetMaxOpenConns(20) // 最大打开连接数
|
||||
db.SetMaxIdleConns(10) // 最大空闲连接数
|
||||
})
|
||||
|
||||
return db, err
|
||||
}
|
||||
|
||||
// GetDB 从上下文中获取数据库连接
|
||||
func GetDB(c *gin.Context) *sqlx.DB {
|
||||
return db
|
||||
}
|
||||
55
utils/redis_util.go
Normal file
55
utils/redis_util.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"Quincy_admin/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
// Redis客户端全局变量
|
||||
var redisClient *redis.Client
|
||||
var redisOnce sync.Once
|
||||
|
||||
// InitRedis 初始化Redis连接
|
||||
func InitRedis(cfg *config.Config) *redis.Client {
|
||||
redisOnce.Do(func() {
|
||||
// 将字符串类型的DB转换为整数
|
||||
redisDB, err := strconv.Atoi(cfg.Redis.DB)
|
||||
if err != nil {
|
||||
log.Printf("Invalid Redis DB value '%s', using default 0: %v", cfg.Redis.DB, err)
|
||||
redisDB = 0
|
||||
}
|
||||
|
||||
// 创建Redis客户端
|
||||
redisClient = redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%s", cfg.Redis.Host, cfg.Redis.Port),
|
||||
Password: cfg.Redis.Password, // 没有密码则留空
|
||||
DB: redisDB, // 使用转换后的整数DB
|
||||
})
|
||||
|
||||
// 测试Redis连接
|
||||
ctx := context.Background()
|
||||
_, err = redisClient.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
log.Printf("Error connecting to Redis: %v", err)
|
||||
} else {
|
||||
}
|
||||
})
|
||||
|
||||
return redisClient
|
||||
}
|
||||
|
||||
// GetRedis 获取Redis客户端
|
||||
func GetRedis() *redis.Client {
|
||||
return redisClient
|
||||
}
|
||||
|
||||
// GetContext 获取上下文
|
||||
func GetContext() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
33
utils/response.go
Normal file
33
utils/response.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package utils
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type Response struct {
|
||||
Status int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func Success(c *gin.Context, data interface{}) {
|
||||
c.JSON(200, Response{
|
||||
Status: 1,
|
||||
Message: "success",
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func Fail(c *gin.Context, code int, message string) {
|
||||
c.JSON(200, Response{
|
||||
Status: code,
|
||||
Message: message,
|
||||
Data: nil,
|
||||
})
|
||||
}
|
||||
|
||||
func Error(c *gin.Context, code int, message string) {
|
||||
c.JSON(code, Response{
|
||||
Status: code,
|
||||
Message: message,
|
||||
Data: nil,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user