first commit

This commit is contained in:
何昌清
2026-03-26 22:13:03 +08:00
parent bbe1faa363
commit a2685f7f1e
51 changed files with 11244 additions and 0 deletions

126
utils/generator_util.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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,
})
}