wucan преди 9 месеца
ревизия
20ae43c56a
променени са 57 файла, в които са добавени 2962 реда и са изтрити 0 реда
  1. 12 0
      .env
  2. 8 0
      .idea/.gitignore
  3. 9 0
      .idea/chunhao_admin.iml
  4. 8 0
      .idea/modules.xml
  5. 6 0
      .idea/vcs.xml
  6. 18 0
      app/common/enum/msg.go
  7. 10 0
      app/common/enum/status.go
  8. 60 0
      app/common/log/log.go
  9. 14 0
      app/common/request/auth.go
  10. 28 0
      app/common/request/user.go
  11. 26 0
      app/common/request/validate.go
  12. 30 0
      app/common/request/validator.go
  13. 123 0
      app/common/response/response.go
  14. 21 0
      bootstrap/config.go
  15. 41 0
      bootstrap/ipFile.go
  16. 114 0
      bootstrap/log.go
  17. 112 0
      bootstrap/redis.go
  18. 118 0
      bootstrap/router.go
  19. 325 0
      common/common.go
  20. 42 0
      common/ip.go
  21. 88 0
      common/redis.go
  22. 44 0
      config/app.go
  23. 160 0
      config/common.go
  24. 15 0
      config/config.go
  25. 9 0
      config/download.go
  26. BIN
      config/excel/city.xlsx
  27. BIN
      config/excel/game.xlsx
  28. 142 0
      config/json/city.json
  29. 10 0
      config/json/game.json
  30. 9 0
      config/jwt.go
  31. 11 0
      config/log.go
  32. 10 0
      config/redis.go
  33. 10 0
      config/temp.go
  34. 128 0
      controller/v1/blackList.go
  35. 57 0
      controller/v1/gameConfig.go
  36. 52 0
      controller/v1/poster.go
  37. 118 0
      controller/v1/user.go
  38. 64 0
      global/app.go
  39. 18 0
      global/error.go
  40. 68 0
      global/lock.go
  41. 49 0
      go.mod
  42. 132 0
      go.sum
  43. BIN
      ip2region.xdb
  44. 67 0
      main.go
  45. 148 0
      middleware/auth.go
  46. 48 0
      middleware/middlelogger.go
  47. 9 0
      model/user.go
  48. 123 0
      response/response.go
  49. 33 0
      route/api.go
  50. 61 0
      utils/array.go
  51. 23 0
      utils/bcrypt.go
  52. 46 0
      utils/directory.go
  53. 8 0
      utils/json.go
  54. 17 0
      utils/map.go
  55. 12 0
      utils/md5.go
  56. 32 0
      utils/str.go
  57. 16 0
      utils/validator.go

+ 12 - 0
.env

@@ -0,0 +1,12 @@
+APP_ENV=local
+APP_PORT=8000
+APP_NAME=default
+APP_URL=
+
+#redis
+REDIS_HOST=127.0.0.1
+REDIS_PORT=6379
+REDIS_PASSWORD=
+
+#path
+DOWNLOAD_DIR=

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 9 - 0
.idea/chunhao_admin.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/chunhao_admin.iml" filepath="$PROJECT_DIR$/.idea/chunhao_admin.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 18 - 0
app/common/enum/msg.go

@@ -0,0 +1,18 @@
+package enum
+
+import "designs/utils"
+
+var (
+	// MSG_DATABASE_ERROR mysql database error
+	MSG_DATABASE_ERROR = "database error"
+	// MSG_JSON_PARSE_ERROR json parse error
+	MSG_JSON_PARSE_ERROR = "json parse error"
+	// MSG_HTTP_REQUEST_ERROR http request error
+	MSG_HTTP_REQUEST_ERROR = "http request error"
+)
+
+var SystemErrorList = []string{MSG_DATABASE_ERROR, MSG_JSON_PARSE_ERROR, MSG_HTTP_REQUEST_ERROR}
+
+func IsSystemError(err error) bool {
+	return utils.InArray(err.Error(), SystemErrorList)
+}

+ 10 - 0
app/common/enum/status.go

@@ -0,0 +1,10 @@
+package enum
+
+const (
+	NormalError = 1   //普通报错
+	NoAuthError = 401 //没有权限
+
+	//错误类型
+	DatabaseError = "database error"
+	RedisError    = "redis error"
+)

+ 60 - 0
app/common/log/log.go

@@ -0,0 +1,60 @@
+package log
+
+import (
+	"designs/global"
+	"fmt"
+
+	"github.com/pkg/errors"
+	"go.uber.org/zap"
+)
+
+type causer interface {
+	Format(s fmt.State, verb rune)
+}
+
+// 会打印error时的堆栈
+func ErrorWithStack(err error, msg ...string) error {
+	titleMsg := ""
+	if len(msg) > 0 {
+		titleMsg = msg[0]
+	}
+	if _, ok := err.(causer); ok {
+		global.App.Log.Errorf("%s%+v", titleMsg, errors.WithStack(err))
+	} else {
+		global.App.Log.Error(titleMsg, err.Error(), zap.StackSkip("", 1))
+	}
+
+	return err
+}
+
+func Debug(args ...interface{}) {
+	global.App.Log.Debug(args...)
+}
+
+func Info(args ...interface{}) {
+	global.App.Log.Info(args...)
+}
+
+func Warn(args ...interface{}) {
+	global.App.Log.Warn(args...)
+}
+
+func Error(args ...interface{}) {
+	global.App.Log.Error(args...)
+}
+
+func Debugf(format string, args ...interface{}) {
+	global.App.Log.Debugf(format, args...)
+}
+
+func Infof(format string, args ...interface{}) {
+	global.App.Log.Infof(format, args...)
+}
+
+func Warnf(format string, args ...interface{}) {
+	global.App.Log.Warnf(format, args...)
+}
+
+func Errorf(format string, args ...interface{}) {
+	global.App.Log.Errorf(format, args...)
+}

+ 14 - 0
app/common/request/auth.go

@@ -0,0 +1,14 @@
+package request
+
+import (
+	"github.com/gin-gonic/gin"
+)
+
+type AuthReq struct {
+}
+
+var Auth = new(AuthReq)
+
+func (a *AuthReq) User(c *gin.Context) {
+
+}

+ 28 - 0
app/common/request/user.go

@@ -0,0 +1,28 @@
+package request
+
+type Register struct {
+	Name     string `form:"name" json:"name" binding:"required"`
+	Mobile   string `form:"mobile" json:"mobile" binding:"required,mobile"`
+	Password string `form:"password" json:"password" binding:"required"`
+}
+
+func (register Register) GetMessages() ValidatorMessages {
+	return ValidatorMessages{
+		"name.required":     "用户名称不能为空",
+		"mobile.required":   "手机号码不能为空",
+		"mobile.mobile":     "手机号码格式不正确",
+		"password.required": "用户密码不能为空",
+	}
+}
+
+type Login struct {
+	Email    string `form:"email" json:"email" binding:"required"`
+	Password string `form:"password" json:"password" binding:"required"`
+}
+
+func (login Login) GetMessages() ValidatorMessages {
+	return ValidatorMessages{
+		"email.required":    "账号不能为空",
+		"password.required": "用户密码不能为空",
+	}
+}

+ 26 - 0
app/common/request/validate.go

@@ -0,0 +1,26 @@
+package request
+
+import (
+	"github.com/gin-gonic/gin"
+)
+
+type ValidateError struct {
+	Err error
+}
+
+func NewValidateError(err error) ValidateError {
+	return ValidateError{Err: err}
+}
+
+func (e ValidateError) Error() string {
+	return e.Err.Error()
+}
+
+func Check[T any](c *gin.Context, obj *T) *T {
+
+	if err := c.ShouldBindJSON(obj); err != nil {
+		panic(NewValidateError(err))
+	}
+
+	return obj
+}

+ 30 - 0
app/common/request/validator.go

@@ -0,0 +1,30 @@
+package request
+
+import (
+	"github.com/go-playground/validator/v10"
+)
+
+type Validator interface {
+	GetMessages() ValidatorMessages
+}
+
+type ValidatorMessages map[string]string
+
+// GetErrorMsg 获取错误信息
+func GetErrorMsg(request interface{}, err error) string {
+	if _, isValidatorErrors := err.(validator.ValidationErrors); isValidatorErrors {
+		_, isValidator := request.(Validator)
+
+		for _, v := range err.(validator.ValidationErrors) {
+			// 若 request 结构体实现 Validator 接口即可实现自定义错误信息
+			if isValidator {
+				if message, exist := request.(Validator).GetMessages()[v.Field()+"."+v.Tag()]; exist {
+					return message
+				}
+			}
+			return v.Error()
+		}
+	}
+
+	return "数据格式错误"
+}

+ 123 - 0
app/common/response/response.go

@@ -0,0 +1,123 @@
+package response
+
+import (
+	"designs/config"
+	"designs/global"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/gin-gonic/gin"
+	"github.com/pkg/errors"
+	"go.uber.org/zap"
+)
+
+// 500 程序奔溃
+func ServerError(c *gin.Context, msg string) {
+	errMsg := "服务程序错误"
+	if !config.IsProduction() {
+		stack := strings.Split(strings.Replace(msg, "\t", "--", -1), "\n")
+
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"code":  http.StatusInternalServerError,
+			"msg":   errMsg,
+			"stack": stack,
+		})
+	} else {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"code": http.StatusInternalServerError,
+			"msg":  errMsg,
+		})
+	}
+
+	c.Abort()
+}
+
+func Success(c *gin.Context, data gin.H) {
+	//for k, v := range data {
+	//
+	//}
+	data["code"] = 0
+
+	//fmt.Println(data)
+
+	c.JSON(http.StatusOK, data)
+}
+
+type causer interface {
+	Format(s fmt.State, verb rune)
+}
+
+func Next(c *gin.Context, resp interface{}) {
+	c.JSON(http.StatusOK, resp)
+}
+
+func Fail(c *gin.Context, errorCode int, e interface{}) {
+	var msg string
+	var stack []string
+	if _, ok := e.(string); ok {
+		msg = e.(string)
+	} else if err, ok := e.(error); ok {
+		if _, ok := e.(causer); ok {
+			msg = strings.Split(fmt.Sprintf("%v", errors.WithStack(err)), ":")[0]
+			if !config.IsProduction() {
+				tmp := fmt.Sprintf("%+v", errors.WithStack(err))
+				stack = strings.Split(strings.Replace(tmp, "\t", "--", -1), "\n")
+			}
+		} else {
+			msg = fmt.Sprintf(err.Error())
+			if !config.IsProduction() {
+				tmp := fmt.Sprintf("%v", zap.StackSkip("", 1))
+				stack = strings.Split(strings.Replace(tmp, "\t", "--", -1), "\n")
+			}
+		}
+	}
+
+	if len(stack) > 0 {
+		c.JSON(http.StatusOK, gin.H{
+			"code":  errorCode,
+			"msg":   msg,
+			"stack": stack,
+		})
+	} else {
+		c.JSON(http.StatusOK, gin.H{
+			"code": errorCode,
+			"msg":  msg,
+		})
+	}
+
+	c.Abort()
+}
+
+func ValidateFail(c *gin.Context, msg string) {
+	Fail(c, global.Errors.ValidateError.ErrorCode, msg)
+}
+
+// 422 参数错误使用
+func ParameterError(c *gin.Context, msg interface{}) {
+	finalMsg := "参数错误"
+	if err, ok := msg.(error); ok {
+		// release 版本屏蔽掉报错
+		if !config.IsProduction() {
+			finalMsg = err.Error()
+		}
+	} else if str, ok := msg.(string); ok {
+		finalMsg = str
+	}
+
+	c.JSON(http.StatusUnprocessableEntity, gin.H{
+		"code": http.StatusUnprocessableEntity,
+		"msg":  finalMsg,
+	})
+	c.Abort()
+}
+
+// 401 权限错误
+func UnauthorizedRequestsFail(c *gin.Context, msg string) {
+	//c.JSON(http.StatusUnauthorized, gin.H{
+	c.JSON(http.StatusOK, gin.H{
+		"code": http.StatusUnauthorized,
+		"msg":  msg,
+	})
+	c.Abort()
+}

+ 21 - 0
bootstrap/config.go

@@ -0,0 +1,21 @@
+package bootstrap
+
+import (
+	"designs/config"
+
+	"github.com/go-ini/ini"
+)
+
+func InitializeConfig(configPath string) {
+	cfg, err := ini.Load(configPath)
+	if err != nil {
+		panic(err)
+	}
+
+	envMap := cfg.Section("").KeysHash()
+
+	if err := config.InitConfig(envMap, &config.RootConfig); err != nil {
+		panic(err)
+	}
+
+}

+ 41 - 0
bootstrap/ipFile.go

@@ -0,0 +1,41 @@
+package bootstrap
+
+import (
+	"fmt"
+	"github.com/lionsoul2014/ip2region/binding/golang/xdb"
+	"strings"
+)
+
+var cBuffData []byte
+
+func InitIpFile() {
+	// 1、从 dbPath 加载整个 xdb 到内存
+	dbPath := "./ip2region.xdb"
+	cBuff, err := xdb.LoadContentFromFile(dbPath)
+	if err != nil {
+		fmt.Printf("failed to load content from `%s`: %s\n", dbPath, err)
+		return
+	}
+	cBuffData = cBuff
+}
+
+/* 通过id地址获取省份 并发使用时候 每个goruiting 需要创建一个独立的search */
+func FindAddressByIp(ip string) (string, string) {
+	// 2、用全局的 cBuff 创建完全基于内存的查询对象。
+	searcher, err := xdb.NewWithBuffer(cBuffData)
+	if err != nil {
+		fmt.Printf("failed to create searcher with content: %s\n", err)
+		return "", ""
+	}
+
+	// do the search
+	// var tStart = time.Now()
+	region, err := searcher.SearchByStr(ip)
+	if err != nil {
+		fmt.Printf("failed to SearchIP(%s): %s\n", ip, err)
+		return "", ""
+	}
+	data := strings.Split(region, "|")
+	// fmt.Printf("{region: %s, took: %s}\n", data, time.Since(tStart))
+	return data[2], data[3]
+}

+ 114 - 0
bootstrap/log.go

@@ -0,0 +1,114 @@
+package bootstrap
+
+import (
+	"fmt"
+	"io"
+	"path/filepath"
+
+	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
+
+	"designs/config"
+	"designs/global"
+	"designs/utils"
+
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+
+	// "gopkg.in/natefinch/lumberjack.v2"
+	"os"
+	"time"
+)
+
+var (
+	level   zapcore.Level // zap 日志等级
+	options []zap.Option  // zap 配置项
+)
+
+func InitializeLog() (*zap.SugaredLogger, io.Writer) {
+	// 创建根目录
+	createRootDir()
+	// 设置日志等级
+	setLogLevel()
+
+	if config.GetBool("log.showLine") {
+		options = append(options, zap.AddCaller())
+	}
+
+	writer := getLogWriter() // file-rotatelogs
+
+	// 初始化 zap
+	logger := zap.New(getZapCore(writer), options...)
+	return logger.Sugar(), writer
+}
+
+func createRootDir() {
+	if ok, _ := utils.PathExists(config.Get("log.rootDir")); !ok {
+
+		if err := os.Mkdir(config.Get("log.rootDir"), os.ModePerm); err != nil {
+			panic(fmt.Errorf("mkdir log root dir failed: %s, %s \n", config.Get("log.rootDir"), err))
+		}
+	}
+}
+
+func setLogLevel() {
+	switch config.Get("log.level") {
+	case "debug":
+		level = zap.DebugLevel
+		options = append(options, zap.AddStacktrace(level))
+	case "info":
+		level = zap.InfoLevel
+	case "warn":
+		level = zap.WarnLevel
+	case "error":
+		level = zap.ErrorLevel
+		options = append(options, zap.AddStacktrace(level))
+	case "dpanic":
+		level = zap.DPanicLevel
+	case "panic":
+		level = zap.PanicLevel
+	case "fatal":
+		level = zap.FatalLevel
+	default:
+		level = zap.InfoLevel
+	}
+}
+
+// 扩展 Zap
+func getZapCore(writer io.Writer) zapcore.Core {
+	var encoder zapcore.Encoder
+
+	// 调整编码器默认配置
+	encoderConfig := zap.NewProductionEncoderConfig()
+	encoderConfig.EncodeTime = func(time time.Time, encoder zapcore.PrimitiveArrayEncoder) {
+		encoder.AppendString(time.Format("[" + "2006-01-02 15:04:05.000" + "]"))
+	}
+	encoderConfig.EncodeLevel = func(l zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) {
+		encoder.AppendString(config.Get("app.env") + "." + l.String())
+	}
+
+	// 设置编码器
+	if config.Get("log.format") == "json" {
+		encoder = zapcore.NewJSONEncoder(encoderConfig)
+	} else {
+		encoder = zapcore.NewConsoleEncoder(encoderConfig)
+	}
+
+	syncWriter := zapcore.AddSync(writer)
+	return zapcore.NewCore(encoder, syncWriter, level)
+}
+
+func getLogWriter() io.Writer {
+	logFile := config.Get("log.filename")
+
+	writer, err := rotatelogs.New(
+		filepath.Join(global.App.PwdPath, "storage", "logs", logFile),
+		rotatelogs.WithMaxAge(-1),
+		rotatelogs.WithRotationTime(24*time.Hour),
+	)
+
+	if err != nil {
+		panic(fmt.Errorf("failed to create rotatelogs: %s", err))
+	}
+
+	return writer
+}

+ 112 - 0
bootstrap/redis.go

@@ -0,0 +1,112 @@
+package bootstrap
+
+import (
+	"context"
+	"designs/config"
+	"designs/global"
+	"fmt"
+	"github.com/go-redis/redis/v8"
+	"github.com/goccy/go-json"
+	"go.uber.org/zap"
+	"os"
+	"strconv"
+	"strings"
+)
+
+func InitializeRedis() *redis.Client {
+	client := redis.NewClient(&redis.Options{
+		Addr:     config.Get("redis.host") + ":" + config.Get("redis.port"),
+		Password: config.Get("redis.password"), // no password set
+		DB:       config.GetInt("redis.db"),    // use default DB
+
+		//Addr:     "localhost:6379", // Redis 服务器地址和端口
+		//Password: "",               // Redis 访问密码,如果没有可以为空字符串
+		//DB:       0,                // 使用的 Redis 数据库编号,默认为 0
+	})
+	_, err := client.Ping(context.Background()).Result()
+	if err != nil {
+		global.App.Log.Error("Redis connect ping failed, err:", zap.Any("err", err))
+		return nil
+	}
+	return client
+}
+
+func UnInitializeRedis(client *redis.Client) {
+	if client != nil {
+		client.Close()
+	}
+}
+
+/* 初始游戏配置 */
+func InitGameCfg() {
+	//判读本地有没有数据
+	res, err := global.Redis().Exists(context.Background(), config.Get("app.province_table")).Result()
+	if err != nil {
+		global.App.Log.Error("InitGameCfg err")
+	}
+
+	if res != 1 {
+		//不存在数据
+		// 指定目录
+		filePath := "./config/json/city.json"
+		bytes, err := os.ReadFile(filePath)
+		if err != nil {
+			fmt.Println("os.ReadFile err=", err)
+		}
+		data := []interface{}{}
+		json.Unmarshal(bytes, &data)
+		// dat := data[0].(map[string]interface{})
+		// fmt.Println(dat["hid"])
+		cityData := make(map[string]interface{})
+		for _, val := range data {
+			res := val.(map[string]interface{})
+			// fmt.Println(res["hid"], res["hname"])
+			cityData[res["hname"].(string)] = res["hid"]
+		}
+		//设置数据
+		global.App.Redis.HMSet(context.Background(), config.Get("app.province_table"), cityData)
+	}
+
+}
+
+/* 通过ip地址获取地址code */
+func FindAddresscodeByIp(ip string) int {
+	pro, _ := FindAddressByIp(ip)
+	code := 999
+	res, err := global.App.Redis.HGetAll(context.Background(), config.Get("app.province_table")).Result()
+	if err != nil {
+		return code
+	}
+	for key, val := range res {
+		code, _ := strconv.Atoi(val)
+
+		if strings.Contains(pro, key) {
+			return code
+		}
+	}
+	return code
+}
+
+/* 通过hid获取地址code */
+func FindAddresscodeByHid(hid int) (int, error) {
+	res, _ := global.App.Redis.HGetAll(context.Background(), config.Get("app.province_table")).Result()
+	for _, val := range res {
+		code, _ := strconv.Atoi(val)
+		if code == hid {
+			return hid, nil
+		}
+	}
+	return 0, redis.Nil
+}
+
+/* 通过hid获取地址 */
+func FindAddressByHid(hid int) string {
+	res, _ := global.App.Redis.HGetAll(context.Background(), config.Get("app.province_table")).Result()
+	for key, val := range res {
+		code, _ := strconv.Atoi(val)
+		if code == hid {
+			return key
+		}
+	}
+	return ""
+}

+ 118 - 0
bootstrap/router.go

@@ -0,0 +1,118 @@
+package bootstrap
+
+import (
+	"context"
+	"designs/app/common/request"
+	"designs/app/common/response"
+	"designs/config"
+	"designs/global"
+	"designs/route"
+	"fmt"
+	"go.uber.org/zap"
+	"log"
+	"net/http"
+	"os"
+	"os/signal"
+	"runtime/debug"
+	"syscall"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+// 回收致命错误
+func CustomRecovery(c *gin.Context) {
+	defer func() {
+		if r := recover(); r != nil {
+
+			if v, ok := r.(request.ValidateError); ok {
+				response.ParameterError(c, v)
+				return
+			}
+
+			//打印错误堆栈信息
+			errTitle := fmt.Sprintf("panic: %v", r)
+			errMsg := fmt.Sprintf("stack: %v", string(debug.Stack()))
+
+			global.App.Log.Error("custom_recovery", zap.Any("info", errTitle), zap.Stack("stack"))
+
+			response.ServerError(c, errTitle+errMsg)
+		}
+	}()
+	//继续后续接口调用
+	c.Next()
+
+}
+
+func corsMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		c.Writer.Header().Set("Access-Control-Allow-Origin", "*") // 或者指定允许的源
+		c.Writer.Header().Set("Access-Control-Max-Age", "86400")  // 缓存请求信息
+		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
+		c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Length, Content-Type, Authorization, X-Requested-With")
+		c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type")
+		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
+		if c.Request.Method == "OPTIONS" {
+			c.AbortWithStatus(204)
+			return
+		}
+
+		c.Next()
+	}
+}
+
+func setupRouter() *gin.Engine {
+	if config.Get("app.env") == "production" {
+		gin.SetMode(gin.ReleaseMode)
+	}
+	router := gin.New()
+	router.Use(gin.Logger(), CustomRecovery)
+	router.Use(corsMiddleware())
+	// 跨域处理
+	//router.Use(middleware.Cors())
+
+	// 前端项目静态资源
+	// router.StaticFile("/", "./static/dist/index.html")
+	// router.Static("/assets", "./static/dist/assets")
+	// router.StaticFile("/favicon.ico", "./static/dist/favicon.ico")
+	// 其他静态资源
+	// router.Static("/public", "./static")
+	// router.Static("/storage", "./storage/app/public")
+
+	// 注册 api 分组路由
+	apiGroup := router.Group("")
+	route.SetApiGroupRoutes(apiGroup)
+
+	return router
+}
+
+func RunServer() {
+	r := setupRouter()
+
+	srv := &http.Server{
+		Addr:         ":" + config.Get("app.port"),
+		ReadTimeout:  60 * time.Second,
+		WriteTimeout: 60 * time.Second,
+		Handler:      r,
+	}
+	fmt.Println("Starting server at port " + config.Get("app.port") + " ...")
+
+	go func() {
+		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+			log.Fatalf("listen: %s\n", err)
+		}
+	}()
+
+	// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
+	quit := make(chan os.Signal)
+	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
+	<-quit
+	log.Println("Shutdown Server ...")
+
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	if err := srv.Shutdown(ctx); err != nil {
+		log.Fatal("Server Shutdown:", err)
+	}
+	log.Println("Server exiting")
+}

+ 325 - 0
common/common.go

@@ -0,0 +1,325 @@
+package common
+
+import (
+	"crypto/hmac"
+	"crypto/md5"
+	"crypto/sha256"
+	"designs/config"
+	"designs/global"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math/rand"
+	"net/http"
+	"net/url"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"github.com/golang-jwt/jwt/v5"
+)
+
+// 打印
+func Print(i interface{}) {
+	fmt.Println("---")
+	fmt.Println(i)
+	fmt.Println("---")
+}
+
+// 返回JSON
+func RetJson(code int, msg string, data interface{}, c *gin.Context) {
+	c.JSON(http.StatusOK, gin.H{
+		"code":      code,
+		"msg":       msg,
+		"data":      data,
+		"timestamp": GetTimeUnix(),
+	})
+	c.Abort()
+}
+
+// 获取当前时间戳
+func GetTimeUnix() int64 {
+	return time.Now().Unix()
+}
+
+// MD5 方法
+func MD5(str string) string {
+	s := md5.New()
+	s.Write([]byte(str))
+	return hex.EncodeToString(s.Sum(nil))
+}
+
+// 生成签名
+func CreateSign(params url.Values) string {
+	var key []string
+	var str = ""
+	for k := range params {
+		if k != "sn" {
+			key = append(key, k)
+		}
+	}
+	sort.Strings(key)
+	for i := 0; i < len(key); i++ {
+		if i == 0 {
+			str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
+		} else {
+			str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
+		}
+	}
+	// 自定义签名算法
+	sign := MD5(MD5(str) + MD5(config.Get("app_name")+config.Get("app_secret")))
+	return sign
+}
+
+// 验证签名
+func VerifySign(c *gin.Context) {
+	var method = c.Request.Method
+	var ts int64
+	var sn string
+	var req url.Values
+
+	if method == "GET" {
+		req = c.Request.URL.Query()
+		sn = c.Query("sn")
+		ts, _ = strconv.ParseInt(c.Query("ts"), 10, 64)
+
+	} else if method == "POST" {
+		c.Request.ParseForm()
+		req = c.Request.PostForm
+		sn = c.PostForm("sn")
+		ts, _ = strconv.ParseInt(c.PostForm("ts"), 10, 64)
+	} else {
+		RetJson(500, "Illegal requests", "", c)
+		return
+	}
+
+	exp, _ := strconv.ParseInt(config.Get("api_expire"), 10, 64)
+
+	// 验证过期时间
+	if ts > GetTimeUnix() || GetTimeUnix()-ts >= exp {
+		RetJson(500, "Ts Error", "", c)
+		return
+	}
+
+	// 验证签名
+	if sn == "" || sn != CreateSign(req) {
+		RetJson(500, "Sn Error", "", c)
+		return
+	}
+}
+
+func StructToArr(data interface{}) (string, error) {
+	res, err := json.Marshal(data)
+	if err != nil {
+		return "", err
+	}
+	//将json解析为map
+	var mapdata map[string]interface{}
+	err1 := json.Unmarshal([]byte(res), &mapdata)
+	if err1 != nil {
+		return "", err1
+	}
+	//获取排序map键值
+	keys := make([]string, 0, len(mapdata))
+	for key := range mapdata {
+		keys = append(keys, key)
+	}
+	//排序
+	sort.Strings(keys)
+	fmt.Println("我的数据:", string(res))
+	//排序后直接构建map
+	sortMap := make(map[string]interface{}, len(mapdata))
+	for _, key := range keys {
+		sortMap[key] = mapdata[key]
+	}
+	//序列化
+	sortJosn, err2 := json.Marshal(sortMap)
+	if err2 != nil {
+		return "", err2
+	}
+	return string(sortJosn), nil
+}
+
+/* 检测数据是否篡改 */
+func VerifySignData(data interface{}) bool {
+	var checkSign string
+	var sign string = "test"
+	checkKey := "secret"
+
+	res, err := json.Marshal(data)
+	if err != nil {
+		return false
+	}
+	//将json解析为map
+	var mapdata map[string]interface{}
+	err1 := json.Unmarshal([]byte(res), &mapdata)
+	if err1 != nil {
+		return false
+	}
+	//fmt.Println("解析后的数据:", mapdata)
+
+	val, exist := mapdata[checkKey].(string)
+	if exist {
+		checkSign = val
+	} else {
+		return false
+	}
+	fmt.Println("解析后的数据:", mapdata)
+	//获取排序map键值
+	keys := make([]string, 0, len(mapdata))
+	for key := range mapdata {
+		if key != checkKey {
+			keys = append(keys, key)
+		}
+	}
+	// fmt.Println("解析后的keys数据:", keys)
+	//排序
+	sort.Strings(keys)
+	//排序后直接构建map
+	sortMap := make(map[string]interface{}, len(mapdata))
+	for _, key := range keys {
+		newKey := strings.ToLower(string(key[0])) + key[1:]
+		sortMap[newKey] = mapdata[key]
+	}
+	//序列化数据
+	sortJosn, err2 := json.Marshal(sortMap)
+	if err2 != nil {
+		return false
+	}
+	// fmt.Println("我的排序数据:", string(sortJosn), sign)
+
+	// sum :=  ([]byte("hello world"))
+	// fmt.Printf("加密数据:%x\n", sum)
+	// 密钥
+	key := []byte(config.Get("app.app_check_secret"))
+	// 数据
+	// data := []byte(msg)
+	// 创建一个新的HMAC将SHA-256用作哈希函数
+	h := hmac.New(sha256.New, key)
+	// 写入数据
+	h.Write(sortJosn)
+	// 得到MAC
+	mac := h.Sum(nil)
+	sign = hex.EncodeToString(mac)
+	// 打印MAC
+	fmt.Println("生成的签名:", sign, checkSign)
+
+	if sign != checkSign {
+		global.App.Log.Error("mapdata:", mapdata)
+		global.App.Log.Error("sign:", sign)
+		global.App.Log.Error("checkSign:", checkSign)
+	}
+
+	return sign == checkSign
+}
+
+// 生成token
+func GetDefineToken(openid string) string {
+	timestamp := time.Now().UnixNano() / int64(time.Millisecond)
+	randNum := 10000 + rand.Intn(10000)
+	token := fmt.Sprintf("%s%d%d", openid, timestamp, randNum)
+	return MD5(token)
+}
+
+// 自定义结构体
+type MyCustomClaims struct {
+	UserName  string `json:"userName"`
+	TokenType int    `json:"tokenType"` //0 token 登录  1:刷新token
+	jwt.RegisteredClaims
+}
+
+// 生成jwt token
+func GetJwtToken(UserName string, tokenType int) (string, error) {
+	mySigningKey := []byte(config.Get("app.JwtSecret"))
+	timeData := time.Second
+	if tokenType == 0 {
+		timeData = time.Duration(config.GetInt("app.api_exp")) * time.Second
+	} else {
+		timeData = 24 * time.Hour
+	}
+	claims := MyCustomClaims{
+		UserName,
+		tokenType,
+		jwt.RegisteredClaims{
+			// A usual scenario is to set the expiration time relative to the current time
+			ExpiresAt: jwt.NewNumericDate(time.Now().Add(timeData)),
+			IssuedAt:  jwt.NewNumericDate(time.Now()),
+			NotBefore: jwt.NewNumericDate(time.Now()),
+			Issuer:    "test",
+			Subject:   "somebody",
+			ID:        "1",
+			Audience:  []string{"somebody_else"},
+		},
+	}
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	ss, err := token.SignedString(mySigningKey)
+	return ss, err
+}
+
+/* 解析jwt */
+func ParseJwtWithClaims(tokenString string) (string, int) {
+	token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
+		return []byte(config.Get("app.JwtSecret")), nil
+	})
+	if err != nil {
+		// fmt.Println(err)
+		return "", 0
+	} else if claims, ok := token.Claims.(*MyCustomClaims); ok {
+		// fmt.Println(claims.OpenId, claims.RegisteredClaims.Issuer)
+		return claims.UserName, claims.TokenType
+	} else {
+		// fmt.Println("unknown claims type, cannot proceed")
+		return "", 0
+	}
+}
+
+// 刷新Token
+func RefreshToken(oldToken string) (string, error) {
+	token, err := jwt.Parse(oldToken, func(*jwt.Token) (interface{}, error) {
+		return config.Get("app.JwtSecret"), nil
+	})
+	if err != nil {
+		return "", err
+	}
+	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
+		claims["exp"] = time.Now().Add(time.Duration(config.GetInt("app.api_exp")) * time.Second).Unix() // 更新过期时间
+		return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(config.Get("app.JwtSecret"))
+	}
+	return "", err
+}
+
+// 返回剩余时间
+func GetRemainderTime(timeType int) (time.Duration, error) {
+	var remaining time.Duration
+	err := errors.New("发生错误")
+	//获取当前时间
+	now := time.Now()
+	switch timeType {
+	case 2:
+		//计算本日剩余时间
+		endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())
+		remaining = endOfDay.Sub(now)
+	case 3:
+		//计算本周剩余时间
+		daysUntiEndOfWeek := 6 - now.Weekday()
+		endOfWeek := time.Date(now.Year(), now.Month(), now.Day()+int(daysUntiEndOfWeek), 23, 59, 59, 0, now.Location())
+		remaining = endOfWeek.Sub(now)
+	case 4:
+		//计算本月剩余时间
+		// endOfMonth := time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location()).Add(-time.Second)
+		// remaining = endOfMonth.Sub(now)
+		nextMonth := now.AddDate(0, 1, 0)
+		endOfMonth := time.Date(nextMonth.Year(), nextMonth.Month(), 1, 0, 0, 0, 0, nextMonth.Location()).Add(-time.Second)
+		remaining = endOfMonth.Sub(now)
+	default:
+		return remaining, err
+	}
+
+	// fmt.Printf("Remaning day data:%v\n", remaining)
+	// fmt.Printf("Remaning week data:%v\n", remaining)
+	// fmt.Printf("Remaning month data:%v\n", remaining)
+	return remaining, nil
+}

+ 42 - 0
common/ip.go

@@ -0,0 +1,42 @@
+package common
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/lionsoul2014/ip2region/binding/golang/xdb"
+)
+
+var cBuffData []byte
+
+func InitIpFile() {
+	// 1、从 dbPath 加载整个 xdb 到内存
+	dbPath := "./ip2region.xdb"
+	cBuff, err := xdb.LoadContentFromFile(dbPath)
+	if err != nil {
+		fmt.Printf("failed to load content from `%s`: %s\n", dbPath, err)
+		return
+	}
+	cBuffData = cBuff
+}
+
+/* 通过id地址获取省份 并发使用时候 每个goruiting 需要创建一个独立的search */
+func FindAddressByIp(ip string) (string, string) {
+	// 2、用全局的 cBuff 创建完全基于内存的查询对象。
+	searcher, err := xdb.NewWithBuffer(cBuffData)
+	if err != nil {
+		fmt.Printf("failed to create searcher with content: %s\n", err)
+		return "", ""
+	}
+
+	// do the search
+	// var tStart = time.Now()
+	region, err := searcher.SearchByStr(ip)
+	if err != nil {
+		fmt.Printf("failed to SearchIP(%s): %s\n", ip, err)
+		return "", ""
+	}
+	data := strings.Split(region, "|")
+	// fmt.Printf("{region: %s, took: %s}\n", data, time.Since(tStart))
+	return data[2], data[3]
+}

+ 88 - 0
common/redis.go

@@ -0,0 +1,88 @@
+package common
+
+import (
+	"context"
+	"designs/config"
+	"designs/global"
+	"encoding/json"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/go-redis/redis/v8"
+	"github.com/sirupsen/logrus"
+)
+
+/* 初始游戏配置 */
+func InitGameCfg() {
+	//判读本地有没有数据
+	res, err := global.App.Redis.Exists(context.Background(), config.Get("app.province_table")).Result()
+	if err != nil {
+		logrus.Error("InitGameCfg err")
+	}
+	if res != 1 {
+		//不存在数据
+		// 指定目录
+		filePath := "./config/json/city.json"
+		bytes, err := os.ReadFile(filePath)
+		if err != nil {
+			fmt.Println("os.ReadFile err=", err)
+		}
+		data := []interface{}{}
+		json.Unmarshal(bytes, &data)
+		// dat := data[0].(map[string]interface{})
+		// fmt.Println(dat["hid"])
+		cityData := make(map[string]interface{})
+		for _, val := range data {
+			res := val.(map[string]interface{})
+			// fmt.Println(res["hid"], res["hname"])
+			cityData[res["hname"].(string)] = res["hid"]
+		}
+		//设置数据
+		global.App.Redis.HMSet(context.Background(), config.Get("app.province_table"), cityData)
+	}
+
+}
+
+/* 通过ip地址获取地址code */
+func FindAddresscodeByIp(ip string) int {
+	pro, _ := FindAddressByIp(ip)
+	code := 999
+	res, err := global.App.Redis.HGetAll(context.Background(), config.Get("app.province_table")).Result()
+	if err != nil {
+		return code
+	}
+	for key, val := range res {
+		code, _ := strconv.Atoi(val)
+
+		if strings.Contains(pro, key) {
+			return code
+		}
+	}
+	return code
+}
+
+/* 通过hid获取地址code */
+func FindAddresscodeByHid(hid int) (int, error) {
+	res, _ := global.App.Redis.HGetAll(context.Background(), config.Get("app.province_table")).Result()
+	for _, val := range res {
+		code, _ := strconv.Atoi(val)
+		if code == hid {
+			return hid, nil
+		}
+	}
+	return 0, redis.Nil
+}
+
+/* 通过hid获取地址 */
+func FindAddressByHid(hid int) string {
+	res, _ := global.App.Redis.HGetAll(context.Background(), config.Get("app.province_table")).Result()
+	for key, val := range res {
+		code, _ := strconv.Atoi(val)
+		if code == hid {
+			return key
+		}
+	}
+	return ""
+}

+ 44 - 0
config/app.go

@@ -0,0 +1,44 @@
+package config
+
+func App() *ConfigNode {
+	return &ConfigNode{
+		"env":     env("APP_ENV", "local"),
+		"port":    env("APP_PORT", "8000"),
+		"appName": env("APP_NAME", "goProject"),
+		"appUrl":  env("APP_URL", "http://localhost"),
+
+		"app_secret":       env("APP_SECRET", "6YJSuc50uJ18zj45"),
+		"app_check_secret": env("APP_CHECK_SECRET", "6YJSuc50uJ18zj45"), //检测数据篡改密钥
+		"api_expiry":       env("API_EXPIRY", "12000"),                  //
+		"max_content":      env("MAX_CONTENT", "50000"),                 //最大请求内容长度
+		"api_exp":          env("API_EXP", "6000"),                      //api 过期时间
+		"api_limit_key":    env("API_LIMIT_KEY", "api_limit_key"),       //api限制key  api_limit_key:gid:openid:apipath
+		"api_limit_count":  env("API_LIMIT_COUNT", "50"),                //每分钟限制次数
+		"pf_wx":            env("PF_WX", "wx"),
+		"pf_tt":            env("PF_TT", "tt"),
+		"pf_web":           env("PF_WEB", "web"),
+		"province_table":   env("PROVINCE_TABLE", "province_table"), //省份配置映射表
+		"gid":              env("GID", "gid:"),                      //游戏配置key  gid:gid
+		"user_table_key":   env("USER_TABLE_KEY", "user:"),          //每个游戏的用户键值     gid:pf:user:openid
+		"user_table_id":    env("USER_TABLE_ID", "user_table_id"),   //每个游戏的用户表计数键值 自增id  gid:pf:user_tabel_id     id对应表   gid:pf:user_tabel_id:userid
+
+		"user_init_id":                env("USER_INIT_ID", "10000"),                                 //用户初始表id
+		"game_data_table_key":         env("GAME_DATA_TABLE_KEY", "gameData:"),                      //每个游戏游戏数据键值   gid:pf:gameData:openid
+		"rank_table_key":              env("RANK_TABLE_KEY", "rank:"),                               //每个游戏排行数据键值   gid:pf:rank:TypeId:排序字段
+		"rank_province_table_key":     env("RANK_PROVINCE_TABLE_KEY", "rank_province:"),             //每个游戏省排行数据键值  (省内) gid:pf:rank_province:hid:TypeId:排序字段   (省汇总)  gid:pf:rank_province:Sum:排序字段
+		"rank_table_extends":          env("RANK_TABLE_EXTENDS", "rank_extends:"),                   //每个游戏排行扩展数据键值  gid:pf:rank_extends:openid:TypeId:排序字段
+		"rank_province_table_extends": env("RANK_PROVINCE_TABLE_EXTENDS", "rank_province_extends:"), //每个游戏省排行扩展数据键值   gid:pf:rank_province_extends:hid:openid:TypeId:排序字段
+		"rank_max_count":              env("RANK_MAX_COUNT", "100"),                                 //最大拉取排行榜数量
+		"JwtSecret":                   env("JwtSecret", "ac11b12e5199457602e42cdab79ca770"),         //替换为你的JWT密钥
+
+		"black_list_table": env("BLACK_LIST_TABLE", "black_list_table:"), //黑名单表
+		"option_key":       env("OPTION_KEY", "option_key:"),             //用户配置表    option_key:openid
+
+		"admin_user":   env("ADMIN_USER", "chunhao"),  //默认管理员
+		"admin_secret": env("ADMIN_SECRET", "123456"), //默认管理密码
+	}
+}
+
+func IsProduction() bool {
+	return Get("app.env") == "production"
+}

+ 160 - 0
config/common.go

@@ -0,0 +1,160 @@
+package config
+
+import (
+	"designs/app/common/log"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/pkg/errors"
+)
+
+type ConfigRootFunc func() *ConfigNode
+
+type ConfigEnv [2]string
+type ConfigNode map[string]interface{}
+type ConfigRoot map[string]ConfigRootFunc
+
+var EnvMap map[string]string
+var Root = make(map[string]*ConfigNode)
+
+func env(envField, defaultValue string) string {
+	if envValue, ok := EnvMap[envField]; ok {
+		return envValue
+	} else {
+		return defaultValue
+	}
+}
+
+// 初始化配置信息
+func InitConfig(envMap map[string]string, root *ConfigRoot) error {
+	EnvMap = envMap
+	for k, v := range *root {
+		Root[k] = v()
+	}
+
+	return nil
+}
+
+// 打印配置信息
+func PrintConfig() {
+	for k, v := range Root {
+		printConfigNode(k, v)
+	}
+}
+
+func printConfigNode(prefix string, node *ConfigNode) {
+	for k, v := range *node {
+		if strValue, ok := v.(string); ok {
+			fmt.Printf("%-50s%s\n", prefix+"."+k, strValue)
+		} else if _, ok := v.(*ConfigNode); ok {
+			printConfigNode(prefix+"."+k, v.(*ConfigNode))
+		} else {
+			fmt.Printf("%-50s%+v\n", prefix+"."+k, v)
+		}
+	}
+}
+
+// 获取配置信息
+func Get(k string) string {
+	kList := strings.Split(k, ".")
+	if len(kList) < 2 {
+		log.Errorf("get error config: %s", k)
+		return ""
+	}
+
+	//获取第一个节点[1,1]
+	node, ok := Root[kList[0]]
+	if !ok {
+		log.Errorf("get not exist config: %s", k)
+		return ""
+	}
+
+	//循环[2, n-1]
+	for _, v := range kList[1 : len(kList)-1] {
+		if n, ok := (*node)[v]; ok {
+			if nn, ok := n.(*ConfigNode); ok {
+				node = nn
+			} else {
+				log.Errorf("get not exist config: %s", k)
+				return ""
+			}
+		} else {
+			log.Errorf("get not exist config: %s", k)
+			return ""
+		}
+	}
+
+	//最后一个节点[n,n]
+	if n, ok := (*node)[kList[len(kList)-1]]; ok {
+		if sv, ok := n.(string); ok {
+			return sv
+		} else {
+			log.Errorf("get not exist config: %s", k)
+			return ""
+		}
+	}
+
+	log.Errorf("get not exist config: %s", k)
+	return ""
+}
+
+func GetNode(k string) (*ConfigNode, error) {
+	kList := strings.Split(k, ".")
+
+	//获取第一个节点[1,1]
+	node, ok := Root[kList[0]]
+	if !ok {
+		log.Errorf("get not exist config: %s", k)
+		return nil, errors.New("get not exist config")
+	}
+
+	//循环[2, n-1]
+	if len(kList) > 2 {
+		for _, v := range kList[1 : len(kList)-1] {
+			if n, ok := (*node)[v]; ok {
+				if nn, ok := n.(*ConfigNode); ok {
+					node = nn
+				} else {
+					log.Errorf("get not exist config: %s", k)
+					return nil, errors.New("get not exist config")
+				}
+			} else {
+				log.Errorf("get not exist config: %s", k)
+				return nil, errors.New("get not exist config")
+			}
+		}
+	}
+
+	return node, nil
+}
+
+// 获取配置信息,并转成int
+func GetInt(k string) int {
+	v := Get(k)
+	if v == "" {
+		return 0
+	}
+
+	i, err := strconv.Atoi(v)
+	if err != nil {
+		log.Warnf("can not convert %s, value %s to int", k, v)
+		return 0
+	}
+
+	return i
+}
+
+func GetInt64(k string) int64 {
+	return int64(GetInt(k))
+}
+
+// 获取配置信息,并转成bool
+func GetBool(k string) bool {
+	v := Get(k)
+	if v == "" {
+		return false
+	}
+
+	return v == "true"
+}

+ 15 - 0
config/config.go

@@ -0,0 +1,15 @@
+package config
+
+const (
+	Wxcode2sessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code" //微信openid获取地址
+	TTcode2sessionURL = "https://minigame.zijieapi.com/mgplatform/api/apps/jscode2session?appid=%s&secret=%s&code=%s"              //抖音openid获取地址
+)
+
+var RootConfig = ConfigRoot{
+	"app":      App,
+	"log":      Log,
+	"jwt":      Jwt,
+	"redis":    Redis,
+	"temp":     Temp,
+	"download": Download,
+}

+ 9 - 0
config/download.go

@@ -0,0 +1,9 @@
+package config
+
+func Download() *ConfigNode {
+	return &ConfigNode{
+		"dir":            env("DOWNLOAD_DIR", ""),
+		"sendFilePrefix": env("DOWNLOAD_SEND_FILE_PREFIX", "/fs/file"),
+		"mode":           env("DOWNLOAD_MODE", "sendfile"),
+	}
+}

BIN
config/excel/city.xlsx


BIN
config/excel/game.xlsx


+ 142 - 0
config/json/city.json

@@ -0,0 +1,142 @@
+[
+  {
+    "hid": 100,
+    "hname": "北京"
+  },
+  {
+    "hid": 110,
+    "hname": "天津"
+  },
+  {
+    "hid": 120,
+    "hname": "河北"
+  },
+  {
+    "hid": 130,
+    "hname": "山西"
+  },
+  {
+    "hid": 140,
+    "hname": "内蒙古"
+  },
+  {
+    "hid": 150,
+    "hname": "辽宁"
+  },
+  {
+    "hid": 160,
+    "hname": "吉林"
+  },
+  {
+    "hid": 170,
+    "hname": "黑龙江"
+  },
+  {
+    "hid": 180,
+    "hname": "上海"
+  },
+  {
+    "hid": 190,
+    "hname": "江苏"
+  },
+  {
+    "hid": 200,
+    "hname": "浙江"
+  },
+  {
+    "hid": 210,
+    "hname": "安徽"
+  },
+  {
+    "hid": 220,
+    "hname": "福建"
+  },
+  {
+    "hid": 230,
+    "hname": "江西"
+  },
+  {
+    "hid": 240,
+    "hname": "山东"
+  },
+  {
+    "hid": 250,
+    "hname": "河南"
+  },
+  {
+    "hid": 260,
+    "hname": "湖北"
+  },
+  {
+    "hid": 270,
+    "hname": "湖南"
+  },
+  {
+    "hid": 280,
+    "hname": "广东"
+  },
+  {
+    "hid": 290,
+    "hname": "广西"
+  },
+  {
+    "hid": 300,
+    "hname": "海南"
+  },
+  {
+    "hid": 310,
+    "hname": "重庆"
+  },
+  {
+    "hid": 320,
+    "hname": "四川"
+  },
+  {
+    "hid": 330,
+    "hname": "贵州"
+  },
+  {
+    "hid": 340,
+    "hname": "云南"
+  },
+  {
+    "hid": 350,
+    "hname": "西藏"
+  },
+  {
+    "hid": 360,
+    "hname": "陕西"
+  },
+  {
+    "hid": 370,
+    "hname": "甘肃"
+  },
+  {
+    "hid": 380,
+    "hname": "青海"
+  },
+  {
+    "hid": 390,
+    "hname": "宁夏"
+  },
+  {
+    "hid": 400,
+    "hname": "新疆"
+  },
+  {
+    "hid": 410,
+    "hname": "台湾"
+  },
+  {
+    "hid": 420,
+    "hname": "香港"
+  },
+  {
+    "hid": 430,
+    "hname": "澳门"
+  },
+  {
+    "hid": 999,
+    "hname": "其他"
+  }
+]

+ 10 - 0
config/json/game.json

@@ -0,0 +1,10 @@
+[
+  {
+    "gid": 1001,
+    "gameName": "测试游戏",
+    "wxAppid": "wxf332a8f693181efb",
+    "wxSecret": "610a1767db04d47b26073859b29d655a",
+    "ttAppid": "wxf332a8f693181efb",
+    "ttSecret": "610a1767db04d47b26073859b29d655a"
+  }
+]

+ 9 - 0
config/jwt.go

@@ -0,0 +1,9 @@
+package config
+
+func Jwt() *ConfigNode {
+	return &ConfigNode{
+		"secret":     "ac11b12e5199457602e42cdab79ca770",
+		"jwtTtl":     "43200", // token 有效期(秒)
+		"refreshTtl": "43200", // token 刷新期(秒)
+	}
+}

+ 11 - 0
config/log.go

@@ -0,0 +1,11 @@
+package config
+
+func Log() *ConfigNode {
+	return &ConfigNode{
+		"level":    "info",
+		"rootDir":  "logs",
+		"filename": "gin-server-%Y-%m-%d.log",
+		"format":   "",
+		"showLine": "true",
+	}
+}

+ 10 - 0
config/redis.go

@@ -0,0 +1,10 @@
+package config
+
+func Redis() *ConfigNode {
+	return &ConfigNode{
+		"host":     env("REDIS_HOST", "localhost"),
+		"port":     env("REDIS_PORT", "6379"),
+		"db":       "1",
+		"password": env("REDIS_PASSWORD", ""),
+	}
+}

+ 10 - 0
config/temp.go

@@ -0,0 +1,10 @@
+package config
+
+import "designs/global"
+
+func Temp() *ConfigNode {
+	return &ConfigNode{
+		"uploadTempDir": global.App.PwdPath + env("UPLOAD_TEMP_DIR", "/storage/tmp/"),
+		"uploadDir":     global.App.PwdPath + env("UPLOAD_DIR", "/storage/"),
+	}
+}

+ 128 - 0
controller/v1/blackList.go

@@ -0,0 +1,128 @@
+package v1
+
+import (
+	"context"
+	"designs/app/common/request"
+	"designs/common"
+	"designs/config"
+	"designs/global"
+	"github.com/gin-gonic/gin"
+	"github.com/go-redis/redis/v8"
+	"strconv"
+	"strings"
+)
+
+/* 排行类型 */
+const (
+	RankTypeByEver  = 1 //永久不过期
+	RankTypeByDay   = 2 //日榜
+	RankTypeByWeek  = 3 //周榜
+	RankTypeByMonth = 4 //月榜
+)
+
+// AddUserToBlackList 将玩家加入黑名单
+func AddUserToBlackList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Code string `json:"code" binding:"required"`
+		Gid  string `json:"gid" binding:"required"`
+		Pf   string `json:"pf" binding:"required"`
+	}{})
+
+	userKey := form.Gid + ":" + form.Pf + ":" + config.Get("app.user_table_key") + form.Code
+
+	//存在用户 读取数据
+	userdata, err := global.App.Redis.HGetAll(context.Background(), userKey).Result()
+	if err != nil {
+		common.RetJson(1003, "用户数据不存在", err.Error(), c)
+		return
+	}
+	curHid := userdata["hid"]
+
+	//清除省榜数据
+	rankDataKeys := form.Gid + ":" + form.Pf + ":" + config.Get("app.rank_province_table_key") + curHid + ":" + strconv.Itoa(RankTypeByEver) + ":*"
+	keys, _ := global.App.Redis.Keys(context.Background(), rankDataKeys).Result()
+	for _, val := range keys {
+		defScore, _ := global.App.Redis.ZScore(context.Background(), val, form.Code).Result()
+		lastindex := strings.LastIndex(val, ":")
+		if lastindex != -1 {
+			curField := val[lastindex+1:]
+			//删除出省排行数据
+			global.App.Redis.ZRem(context.Background(), val, form.Code)
+			//处理省排行汇总数据
+			rankSumDataKey := form.Gid + ":" + form.Pf + ":" + config.Get("app.rank_province_table_key") + "Sum" + ":" + curField
+			global.App.Redis.ZIncrBy(context.Background(), rankSumDataKey, -defScore, curHid).Result()
+		}
+	}
+
+	//清除排行榜数据
+	rankDataKeyTotal := form.Gid + ":" + form.Pf + ":" + config.Get("app.rank_table_key") + strconv.Itoa(RankTypeByEver) + ":*"
+	keysTotal, _ := global.App.Redis.Keys(context.Background(), rankDataKeyTotal).Result()
+	for _, val := range keysTotal {
+		lastindex := strings.LastIndex(val, ":")
+		if lastindex != -1 {
+			//清除排行榜数据
+			global.App.Redis.ZRem(context.Background(), val, form.Code)
+		}
+	}
+
+	//加入黑名单队列
+	blackListKey := config.Get("app.black_list_table")
+	err = global.App.Redis.ZAdd(context.Background(), blackListKey, &redis.Z{Member: userKey}).Err()
+	if err != nil {
+		common.RetJson(1003, "加入黑名单失败", "参数错误2"+err.Error(), c)
+		return
+	}
+
+	common.RetJson(0, "加入黑名单成功", "", c)
+}
+
+// DeleteUserFormBlackList 将玩家从从黑名单中去掉
+func DeleteUserFormBlackList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Code string `json:"code" binding:"required"`
+		Gid  string `json:"gid" binding:"required"`
+		Pf   string `json:"pf" binding:"required"`
+	}{})
+
+	userKey := form.Gid + ":" + form.Pf + ":" + config.Get("app.user_table_key") + form.Code
+
+	//存在用户 读取数据
+	_, err := global.App.Redis.HGetAll(context.Background(), userKey).Result()
+	if err != nil {
+		common.RetJson(1003, "用户数据不存在", err.Error(), c)
+		return
+	}
+
+	//从黑名单队列中删除
+	blackListKey := config.Get("app.black_list_table")
+	err = global.App.Redis.ZRem(context.Background(), blackListKey, userKey).Err()
+	if err != nil {
+		common.RetJson(1003, "从黑名单中移除失败", "参数错误2", c)
+		return
+	}
+
+	common.RetJson(0, "从黑名单中移除成功", "", c)
+}
+
+// ReadBlackList 查看目前黑名单有哪些人
+func ReadBlackList(c *gin.Context) {
+	blackListKey := config.Get("app.black_list_table")
+
+	data, err := global.App.Redis.ZRange(context.Background(), blackListKey, 0, -1).Result()
+	if err != nil {
+		common.RetJson(1003, "读取黑名单列表失败", "参数错误2", c)
+		return
+	}
+
+	var userData []interface{}
+	for _, value := range data {
+		user, err := global.App.Redis.HGetAll(context.Background(), value).Result()
+		if err != nil {
+			common.RetJson(1003, "GetUserData err", "", c)
+			return
+		}
+		userData = append(userData, user)
+	}
+
+	common.RetJson(0, "读取黑名单列表成功", userData, c)
+}

+ 57 - 0
controller/v1/gameConfig.go

@@ -0,0 +1,57 @@
+package v1
+
+import (
+	"context"
+	"designs/app/common/request"
+	"designs/common"
+	"designs/config"
+	"designs/global"
+	"github.com/gin-gonic/gin"
+)
+
+/* 添加游戏配置 */
+//http://127.0.0.1:8787/v1/user/AddGidConfig
+func AddGidConfig(c *gin.Context) {
+	var data GameConfig
+	form := request.Check(c, &data)
+
+	// 在这种情况下,将自动选择合适的绑定
+	if data.AppSecret != config.Get("app.app_secret") {
+		common.RetJson(1003, "fail", "密钥参数错误", c)
+		return
+	}
+
+	gameConfigData := make(map[string]interface{})
+	gameConfigData["gid"] = form.Gid
+	gameConfigData["gameName"] = form.GameName
+	gameConfigData["wxAppid"] = form.WxAppid
+	gameConfigData["wxSecret"] = form.WxSecret
+	gameConfigData["ttAppid"] = form.TtAppid
+	gameConfigData["ttSecret"] = form.TtSecret
+	gidKey := config.Get("app.gid") + form.Gid
+	err := global.App.Redis.HMSet(context.Background(), gidKey, gameConfigData).Err()
+	if err != nil {
+		common.RetJson(1003, "fail", "配置错误", c)
+		return
+	}
+
+	common.RetJson(0, "success", gameConfigData, c)
+}
+
+/* 获取配置 */
+func GetGidConfig(c *gin.Context) {
+	var data GetGameCfg
+	form := request.Check(c, &data)
+	if form.AppSecret != config.Get("app.app_secret") {
+		common.RetJson(1003, "fail", "密钥不对", c)
+	}
+
+	gidKey := config.Get("app.gid") + "*"
+	keys, _ := global.App.Redis.Keys(context.Background(), gidKey).Result()
+	var gameData = []interface{}{}
+	for _, val := range keys {
+		res, _ := global.App.Redis.HGetAll(context.Background(), val).Result()
+		gameData = append(gameData, res)
+	}
+	common.RetJson(0, "success", gameData, c)
+}

+ 52 - 0
controller/v1/poster.go

@@ -0,0 +1,52 @@
+package v1
+
+import (
+	"context"
+	"designs/app/common/request"
+	"designs/config"
+	"designs/global"
+	"designs/response"
+	"github.com/gin-gonic/gin"
+)
+
+func AddUserOption(c *gin.Context) {
+	//验证
+	form := request.Check(c, &struct {
+		OpenId string `json:"openid" binding:"required"`
+		Gid    string `json:"gid" binding:"required"`
+		Pf     string `json:"pf" binding:"required"`
+		Option string `json:"option" binding:"required"`
+	}{})
+
+	optionKey := config.Get("app.option_key") + form.Gid + ":" + form.OpenId
+
+	data := map[string]interface{}{
+		"option": form.Option,
+	}
+
+	err := global.App.Redis.HMSet(context.Background(), optionKey, data).Err()
+	if err != nil {
+		response.Fail(c, 1003, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}
+
+func GetUserOption(c *gin.Context) {
+	form := request.Check(c, &struct {
+		OpenId string `json:"openid" binding:"required"`
+		Gid    string `json:"gid" binding:"required"`
+	}{})
+
+	optionKey := config.Get("app.option_key") + form.Gid + ":" + form.OpenId
+	data, err := global.App.Redis.HGetAll(context.Background(), optionKey).Result()
+	if err != nil {
+		response.Fail(c, 1003, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"option": data["option"],
+	})
+}

+ 118 - 0
controller/v1/user.go

@@ -0,0 +1,118 @@
+package v1
+
+import (
+	"designs/app/common/request"
+	"designs/app/common/response"
+	"designs/common"
+	"designs/config"
+	"designs/global"
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+type GameConfig struct {
+	Gid       string `form:"gid" json:"gid" binding:"required"`             //游戏id
+	GameName  string `form:"gameName" json:"gameName" binding:"required"`   //游戏名称
+	WxAppid   string `form:"wxAppid" json:"wxAppid" binding:"required"`     //微信appid
+	WxSecret  string `form:"wxSecret" json:"wxSecret" binding:"required"`   //微信secret
+	TtAppid   string `form:"ttAppid" json:"ttAppid" binding:"required"`     //抖音appid
+	TtSecret  string `form:"ttSecret" json:"ttSecret" binding:"required"`   //抖音secret
+	AppSecret string `form:"appSecret" json:"appSecret" binding:"required"` //游戏配置密钥
+}
+
+type GetGameCfg struct {
+	AppSecret string `form:"appSecret" json:"appSecret" binding:"required"` //游戏配置密钥
+}
+
+/* code 结构体 */
+type CodeData struct {
+	Code string `form:"code" json:"code" binding:"required"`
+	Gid  string `form:"gid" json:"gid" binding:"required"`
+	Pf   string `form:"pf" json:"pf" binding:"required"`
+}
+
+/* 保存数据结构体 */
+type SaveData struct {
+	Extend string `form:"extend" json:"extend" binding:"required"`
+	Data   string `form:"data" json:"data" binding:"required"`
+	Secret string `form:"secret" json:"secret" binding:""`
+}
+
+/* 获取结构体 */
+type GetSaveData struct {
+	Extend string `form:"extend" json:"extend" binding:"required"`
+}
+
+/*
+*登陆
+ */
+func Login(c *gin.Context) {
+	//http://127.0.0.1:8787/v1/user/login
+	// 使用 ShouldBind 方法自动绑定:
+	form := request.Check(c, &struct {
+		UserName string `form:"userName" json:"userName" binding:"required"`
+		Password string `form:"password" json:"password" binding:"required"`
+	}{})
+
+	//验证
+	adminUser := config.Get("app.admin_user")
+	adminSecret := config.Get("app.admin_secret")
+
+	if form.UserName != adminUser || form.Password != adminSecret {
+		response.Fail(c, 1000, errors.New("账号密码错误"))
+	}
+
+	//获取token
+	token, err := common.GetJwtToken(form.UserName, 0)
+	//刷新refreshtoken
+	refresh, _ := common.GetJwtToken(form.UserName, 1)
+	if err != nil {
+		response.Fail(c, 1003, err.Error())
+		return
+	}
+
+	//回传
+	response.Success(c, gin.H{
+		"token":        token,
+		"refreshToken": refresh,
+		"loginTime":    time.Now().Unix(),
+		"ip":           c.ClientIP(),
+	})
+}
+
+/* 刷新token */
+func RefreshToken(c *gin.Context) {
+	userName := c.GetString("userName")
+
+	//获取token
+	token, err0 := common.GetJwtToken(userName, 0)
+	if err0 != nil {
+		global.App.Log.Info("RefreshToken err")
+		common.RetJson(1003, "RefreshToken err", "", c)
+		return
+	}
+
+	data := map[string]interface{}{
+		"token": token,
+	}
+	//返回数据
+	common.RetJson(0, "success", data, c)
+}
+
+/* 获取时间 */
+func GetSysTime(c *gin.Context) {
+	time := time.Now().Unix()
+	common.RetJson(0, "获取系统时间", time, c)
+}
+
+func UserList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Offset int `form:"offset" json:"offset" binding:""`
+		Limit  int `form:"limit" json:"limit" binding:"required"`
+	}{})
+
+	fmt.Print(form)
+}

+ 64 - 0
global/app.go

@@ -0,0 +1,64 @@
+package global
+
+import (
+	"go.uber.org/zap"
+	"io"
+
+	"github.com/go-redis/redis/v8"
+)
+
+type InitConfig struct {
+	//InitDBFunc    func() *gorm.DB
+	InitRedisFunc func() *redis.Client
+}
+
+var Init = new(InitConfig)
+
+type Application struct {
+	//配置
+
+	//数据库
+	//DB *utils.WtDB
+
+	Redis *redis.Client
+	//Cron      *cron.Cron
+	//Machinery *machinery.Server
+
+	//日志
+	Log       *zap.SugaredLogger
+	LogWriter io.Writer
+
+	PwdPath string
+}
+
+var (
+	// DB     *utils.WtDB
+	// Redis  *redis.Client
+	Log *zap.SugaredLogger
+)
+
+var App = new(Application)
+
+func InitFacade() {
+	// DB = App.DB
+	// Redis = App.Redis
+	Log = App.Log
+}
+
+//func DB() *utils.WtDB {
+//	if App.DB != nil {
+//		return App.DB
+//	}
+//
+//	App.DB = &utils.WtDB{DB: Init.InitDBFunc()}
+//	return App.DB
+//}
+
+func Redis() *redis.Client {
+	if App.Redis != nil {
+		return App.Redis
+	}
+
+	App.Redis = Init.InitRedisFunc()
+	return App.Redis
+}

+ 18 - 0
global/error.go

@@ -0,0 +1,18 @@
+package global
+
+type CustomError struct {
+	ErrorCode int
+	ErrorMsg  string
+}
+
+type CustomErrors struct {
+	BusinessError CustomError
+	ValidateError CustomError
+	TokenError    CustomError
+}
+
+var Errors = CustomErrors{
+	BusinessError: CustomError{400, "业务错误"},
+	ValidateError: CustomError{422, "请求参数错误"},
+	TokenError:    CustomError{401, "登录授权失效"},
+}

+ 68 - 0
global/lock.go

@@ -0,0 +1,68 @@
+package global
+
+import (
+	"context"
+	"designs/utils"
+	"github.com/go-redis/redis/v8"
+	"time"
+)
+
+type Interface interface {
+	Get() bool
+	Block(seconds int64) bool
+	Release() bool
+	ForceRelease()
+}
+
+type lock struct {
+	context context.Context
+	name    string
+	owner   string
+	seconds int64
+}
+
+const releaseLockLuaScript = `
+if redis.call("get",KEYS[1]) == ARGV[1] then
+    return redis.call("del",KEYS[1])
+else
+    return 0
+end
+`
+
+func Lock(name string, seconds int64) Interface {
+	return &lock{
+		context.Background(),
+		name,
+		utils.RandString(16),
+		seconds,
+	}
+}
+
+func (l *lock) Get() bool {
+	return App.Redis.SetNX(l.context, l.name, l.owner, time.Duration(l.seconds)*time.Second).Val()
+}
+
+func (l *lock) Block(seconds int64) bool {
+	timer := time.After(time.Duration(seconds) * time.Second)
+	for {
+		select {
+		case <-timer:
+			return false
+		default:
+			if l.Get() {
+				return true
+			}
+			time.Sleep(time.Duration(1) * time.Second)
+		}
+	}
+}
+
+func (l *lock) Release() bool {
+	luaScript := redis.NewScript(releaseLockLuaScript)
+	result := luaScript.Run(l.context, App.Redis, []string{l.name}, l.owner).Val().(int64)
+	return result != 0
+}
+
+func (l *lock) ForceRelease() {
+	App.Redis.Del(l.context, l.name).Val()
+}

+ 49 - 0
go.mod

@@ -0,0 +1,49 @@
+module designs
+
+go 1.22.5
+
+require (
+	github.com/gin-gonic/gin v1.10.0
+	github.com/go-ini/ini v1.67.0
+	github.com/go-playground/validator/v10 v10.22.0
+	github.com/go-redis/redis/v8 v8.11.5
+	github.com/goccy/go-json v0.10.3
+	github.com/golang-jwt/jwt/v5 v5.2.1
+	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
+	github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6
+	github.com/pkg/errors v0.9.1
+	github.com/sirupsen/logrus v1.9.3
+	go.uber.org/zap v1.27.0
+	golang.org/x/crypto v0.25.0
+)
+
+require (
+	github.com/bytedance/sonic v1.11.9 // indirect
+	github.com/bytedance/sonic/loader v0.1.1 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
+	github.com/cloudwego/base64x v0.1.4 // indirect
+	github.com/cloudwego/iasm v0.2.0 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/gabriel-vasile/mimetype v1.4.4 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/jonboulle/clockwork v0.4.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.8 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
+	github.com/lestrrat-go/strftime v1.0.6 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.12 // indirect
+	go.uber.org/multierr v1.10.0 // indirect
+	golang.org/x/arch v0.8.0 // indirect
+	golang.org/x/net v0.27.0 // indirect
+	golang.org/x/sys v0.22.0 // indirect
+	golang.org/x/text v0.16.0 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 132 - 0
go.sum

@@ -0,0 +1,132 @@
+github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
+github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
+github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
+github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
+github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
+github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
+github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
+github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
+github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
+github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
+github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
+github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
+github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
+github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
+github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
+github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6 h1:YeIGErDiB/fhmNsJy0cfjoT8XnRNT9hb19xZ4MvWQDU=
+github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6/go.mod h1:C5LA5UO2ZXJrLaPLYtE1wUJMiyd/nwWaCO5cw/2pSHs=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
+golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
+golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

BIN
ip2region.xdb


+ 67 - 0
main.go

@@ -0,0 +1,67 @@
+package main
+
+import (
+	"designs/bootstrap"
+	"designs/global"
+	"designs/middleware"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"net/http"
+	"path/filepath"
+)
+
+func init() {
+	fmt.Printf("初始化")
+}
+
+func main() {
+	//创建一个服务
+	gin.SetMode(gin.ReleaseMode)
+
+	ginServer := gin.Default()
+
+	// 初始化配置
+	bootstrap.InitializeConfig(filepath.Join(global.App.PwdPath, ".env"))
+
+	//config.PrintConfig()
+
+	// 初始化日志
+	global.App.Log, global.App.LogWriter = bootstrap.InitializeLog()
+
+	ginServer.Use(middleware.Logmiddleware())
+	//ip库
+	//common.InitIpFile()
+	bootstrap.InitIpFile()
+
+	//跨域
+	ginServer.Use(Cors())
+
+	// 初始化Redis
+	global.Init.InitRedisFunc = bootstrap.InitializeRedis
+	global.App.Redis = bootstrap.InitializeRedis()
+	bootstrap.InitGameCfg()
+
+	// 初始化facade
+	global.InitFacade()
+	//服务器端口
+	//ginServer.Run(":" + config.Get("app.port")) /*默认是8080*/
+
+	bootstrap.RunServer()
+	// ginServer.RunTLS(":443", "your_certificate.crt", "your_private_key.key")
+}
+
+/* 跨域 */
+func Cors() gin.HandlerFunc {
+	return func(context *gin.Context) {
+		method := context.Request.Method
+		context.Header("Access-Control-Allow-Origin", "*")
+		context.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token")
+		context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT")
+		context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
+		context.Header("Access-Control-Allow-Credentials", "true")
+
+		if method == "OPTIONS" {
+			context.AbortWithStatus(http.StatusNoContent)
+		}
+	}
+}

+ 148 - 0
middleware/auth.go

@@ -0,0 +1,148 @@
+package middleware
+
+import (
+	"context"
+	"designs/common"
+	"designs/config"
+	"designs/global"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+/* 包体大小 */
+func LimitRequestBodySize(maxSize int64) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxSize)
+		if err := c.Request.ParseForm(); err != nil {
+			c.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": "Request body too large"})
+			c.Abort()
+			return
+		}
+		c.Next()
+	}
+}
+
+/* token中间件 */
+func TokenAuthMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		contentLength := c.Request.ContentLength
+		if contentLength > config.GetInt64("app.max_content") {
+			// 输出请求体的大小
+			// fmt.Printf("Request body size: %d bytes\n", contentLength)
+			common.RetJson(1003, "ruquest too max", "", c)
+			c.Abort()
+			return
+		}
+		token := c.GetHeader("Authorization")
+		//校验token
+		if token == "" {
+			common.RetJson(-2, "Unauthorized", "", c)
+			c.Abort()
+			return
+		}
+		ok, userName := isValidToken(token)
+		if !ok {
+			common.RetJson(-1, "authorized invalid!", "", c)
+			c.Abort()
+			return
+		}
+
+		//校验请求次数
+		//apiPath := c.FullPath()
+		//key := fmt.Sprintf("%s:%s:%s:%s:%s", gid, config.Get("app.api_limit_key"), pf, openid, apiPath)
+		//count, err := global.App.Redis.Incr(context.Background(), key).Result()
+		//if err != nil {
+		//	common.RetJson(1001, "server error!", "", c)
+		//	c.Abort()
+		//	return
+		//}
+		//if count == 1 {
+		//	global.App.Redis.Expire(context.Background(), key, time.Minute).Result()
+		//}
+		//if count > config.GetInt64("app.api_limit_count") {
+		//	common.RetJson(1002, "too many requests!", "", c)
+		//	c.Abort()
+		//	return
+		//}
+		//设置上下文数据
+		c.Set("userName", userName)
+		//如果校验通过
+
+	}
+}
+
+/* token是否有效 */
+func isValidToken(token string) (bool, string) {
+	userName, tokenType := common.ParseJwtWithClaims(token)
+
+	//fmt.Printf("openid:%v,gid:%v,pf:%v,tokenType:%v", openid, gid, pf, tokenType)
+
+	if userName == "" {
+		return false, userName
+	} else {
+		//登录tonken 类型
+		if tokenType == 0 {
+			return true, userName
+		}
+		return false, userName
+	}
+}
+
+/* 刷新token中间件 */
+func RefreshTokenAuthMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		token := c.GetHeader("Authorization")
+		//校验token
+		if token == "" {
+			common.RetJson(-2, "Unauthorized", "", c)
+			c.Abort()
+			return
+		}
+		ok, userName := isValidRefreshToken(token)
+		if !ok {
+			common.RetJson(-1, "authorized invalid!", "", c)
+			c.Abort()
+			return
+		}
+		//校验请求次数
+		apiPath := c.FullPath()
+		key := fmt.Sprintf("%s:%s", config.Get("app.api_limit_key"), apiPath)
+		count, err := global.App.Redis.Incr(context.Background(), key).Result()
+		if err != nil {
+			common.RetJson(1001, "server error!", "", c)
+			c.Abort()
+			return
+		}
+		if count == 1 {
+			global.App.Redis.Expire(context.Background(), key, time.Minute).Result()
+		}
+		if count > config.GetInt64("app.api_limit_count") {
+			common.RetJson(1002, "too many requests!", "", c)
+			c.Abort()
+			return
+		}
+		//设置上下文数据
+		c.Set("userName", userName)
+
+		//如果校验通过
+		c.Next()
+	}
+}
+
+/* token是否有效 */
+func isValidRefreshToken(token string) (bool, string) {
+	userName, tokenType := common.ParseJwtWithClaims(token)
+	//fmt.Printf("openid:%v,gid:%v", openid, gid)
+	if userName == "" {
+		return false, userName
+	} else {
+		//登录刷新tonken 类型
+		if tokenType == 1 {
+			return true, userName
+		}
+		return false, userName
+	}
+}

+ 48 - 0
middleware/middlelogger.go

@@ -0,0 +1,48 @@
+package middleware
+
+import (
+	"designs/global"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+func Logmiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		start := time.Now()
+		path := c.Request.URL.Path
+		raw := c.Request.URL.RawQuery
+		if raw != "" {
+			path = path + "?" + raw
+		}
+
+		c.Next() //执行其他中间件
+
+		//end := time.Now()
+		//timesub := end.Sub(start)  //响应所需时间
+		ClientIp := c.ClientIP() //客户端ip
+		statuscode := c.Writer.Status()
+		//var statusColor string
+		//switch c.Writer.Status() {
+		//case 200:
+		//	statusColor = fmt.Sprintf("\033[%dm%d\033[0m", status200, statuscode)
+		//case 404:
+		//	statusColor = fmt.Sprintf("\033[%dm%d\033[0m", status404, statuscode)
+		//default:
+		//	statusColor = fmt.Sprintf("\033[%dm%d\033[0m", status500, statuscode)
+		//}
+		//
+		//var methodColor string
+		//switch c.Request.Method {
+		//case "GET":
+		//	methodColor = fmt.Sprintf("\033[%dm%s\033[0m", methodGET, c.Request.Method)
+		//}
+
+		global.Log.Infof("[GIN] %s |%s |%d  |%s  |%s",
+			start.Format("2006-01-02 15:04:06"),
+			ClientIp,
+			statuscode,
+			c.Request.Method,
+			path)
+	}
+}

+ 9 - 0
model/user.go

@@ -0,0 +1,9 @@
+package model
+
+/* code 结构体 */
+type CodeData struct {
+	Code   string `form:"code" binding:"required"`
+	Gid    string `form:"gid" binding:"required"`
+	Pf     string `form:"pf" binding:"required"`
+	Secret string `form:"secret"`
+}

+ 123 - 0
response/response.go

@@ -0,0 +1,123 @@
+package response
+
+import (
+	"designs/config"
+	"designs/global"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/gin-gonic/gin"
+	"github.com/pkg/errors"
+	"go.uber.org/zap"
+)
+
+// 500 程序奔溃
+func ServerError(c *gin.Context, msg string) {
+	errMsg := "服务程序错误"
+	if !config.IsProduction() {
+		stack := strings.Split(strings.Replace(msg, "\t", "--", -1), "\n")
+
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"code":  http.StatusInternalServerError,
+			"msg":   errMsg,
+			"stack": stack,
+		})
+	} else {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"code": http.StatusInternalServerError,
+			"msg":  errMsg,
+		})
+	}
+
+	c.Abort()
+}
+
+func Success(c *gin.Context, data gin.H) {
+	//for k, v := range data {
+	//
+	//}
+	data["code"] = 0
+
+	//fmt.Println(data)
+
+	c.JSON(http.StatusOK, data)
+}
+
+type causer interface {
+	Format(s fmt.State, verb rune)
+}
+
+func Next(c *gin.Context, resp interface{}) {
+	c.JSON(http.StatusOK, resp)
+}
+
+func Fail(c *gin.Context, errorCode int, e interface{}) {
+	var msg string
+	var stack []string
+	if _, ok := e.(string); ok {
+		msg = e.(string)
+	} else if err, ok := e.(error); ok {
+		if _, ok := e.(causer); ok {
+			msg = strings.Split(fmt.Sprintf("%v", errors.WithStack(err)), ":")[0]
+			if !config.IsProduction() {
+				tmp := fmt.Sprintf("%+v", errors.WithStack(err))
+				stack = strings.Split(strings.Replace(tmp, "\t", "--", -1), "\n")
+			}
+		} else {
+			msg = fmt.Sprintf(err.Error())
+			if !config.IsProduction() {
+				tmp := fmt.Sprintf("%v", zap.StackSkip("", 1))
+				stack = strings.Split(strings.Replace(tmp, "\t", "--", -1), "\n")
+			}
+		}
+	}
+
+	if len(stack) > 0 {
+		c.JSON(http.StatusOK, gin.H{
+			"code":  errorCode,
+			"msg":   msg,
+			"stack": stack,
+		})
+	} else {
+		c.JSON(http.StatusOK, gin.H{
+			"code": errorCode,
+			"msg":  msg,
+		})
+	}
+
+	c.Abort()
+}
+
+func ValidateFail(c *gin.Context, msg string) {
+	Fail(c, global.Errors.ValidateError.ErrorCode, msg)
+}
+
+// 422 参数错误使用
+func ParameterError(c *gin.Context, msg interface{}) {
+	finalMsg := "参数错误"
+	if err, ok := msg.(error); ok {
+		// release 版本屏蔽掉报错
+		if !config.IsProduction() {
+			finalMsg = err.Error()
+		}
+	} else if str, ok := msg.(string); ok {
+		finalMsg = str
+	}
+
+	c.JSON(http.StatusUnprocessableEntity, gin.H{
+		"code": http.StatusUnprocessableEntity,
+		"msg":  finalMsg,
+	})
+	c.Abort()
+}
+
+// 401 权限错误
+func UnauthorizedRequestsFail(c *gin.Context, msg string) {
+	//c.JSON(http.StatusUnauthorized, gin.H{
+	c.JSON(http.StatusOK, gin.H{
+		"code": http.StatusUnauthorized,
+		"msg":  msg,
+	})
+	c.Abort()
+}

+ 33 - 0
route/api.go

@@ -0,0 +1,33 @@
+package route
+
+import (
+	v1 "designs/controller/v1"
+	"designs/middleware"
+	"github.com/gin-gonic/gin"
+)
+
+/* 跨域 */
+
+func SetApiGroupRoutes(router *gin.RouterGroup) {
+	// v1 版本
+	router.POST("/user/login", v1.Login)                                                        //游戏登录
+	router.POST("/user/refreshToken", middleware.RefreshTokenAuthMiddleware(), v1.RefreshToken) //token刷新
+	router.POST("/user/getSysTime", v1.GetSysTime)
+
+	GroupV1 := router.Group("")
+	GroupV1.Use(middleware.TokenAuthMiddleware()).Use()
+	{
+		GroupV1.POST("/user/userList", v1.UserList)
+
+		GroupV1.POST("/user/addGidConfig", v1.AddGidConfig)
+		GroupV1.POST("/user/getGidConfig", v1.GetGidConfig)
+
+		GroupV1.POST("/user/addUserToBlackList", v1.AddUserToBlackList)
+		GroupV1.POST("/user/deleteUserFormBlackList", v1.DeleteUserFormBlackList)
+		GroupV1.POST("/user/readBlackList", v1.ReadBlackList)
+
+		GroupV1.POST("/user/addUserOption", v1.AddUserOption)
+		GroupV1.POST("/user/getUserOption", v1.GetUserOption)
+	}
+
+}

+ 61 - 0
utils/array.go

@@ -0,0 +1,61 @@
+package utils
+
+type BasicType interface {
+	int | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | string
+}
+
+func InArray[T BasicType](needle T, hystack []T) bool {
+	for _, item := range hystack {
+		if needle == item {
+			return true
+		}
+	}
+
+	return false
+}
+
+func SliceArray[T any](arr []T, offset int, limit int) []T {
+	size := len(arr)
+	if size <= offset {
+		return []T{}
+	} else if size > offset && size < offset+limit {
+		return arr[offset:size]
+	} else {
+		return arr[offset : offset+limit]
+	}
+}
+
+func ToMap[K comparable, T any](arr []T, getKey func(*T) K) map[K]T {
+	r := make(map[K]T)
+	for _, v := range arr {
+		k := getKey(&v)
+		r[k] = v
+	}
+	return r
+}
+
+// func InArray(needle interface{}, hystack interface{}) bool {
+// 	switch key := needle.(type) {
+// 	case string:
+// 		for _, item := range hystack.([]string) {
+// 			if key == item {
+// 				return true
+// 			}
+// 		}
+// 	case int:
+// 		for _, item := range hystack.([]int) {
+// 			if key == item {
+// 				return true
+// 			}
+// 		}
+// 	case int64:
+// 		for _, item := range hystack.([]int64) {
+// 			if key == item {
+// 				return true
+// 			}
+// 		}
+// 	default:
+// 		return false
+// 	}
+// 	return false
+// }

+ 23 - 0
utils/bcrypt.go

@@ -0,0 +1,23 @@
+package utils
+
+import (
+	"golang.org/x/crypto/bcrypt"
+	"log"
+)
+
+func BcryptMake(pwd []byte) string {
+	hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost)
+	if err != nil {
+		log.Println(err)
+	}
+	return string(hash)
+}
+
+func BcryptMakeCheck(pwd []byte, hashedPwd string) bool {
+	byteHash := []byte(hashedPwd)
+	err := bcrypt.CompareHashAndPassword(byteHash, pwd)
+	if err != nil {
+		return false
+	}
+	return true
+}

+ 46 - 0
utils/directory.go

@@ -0,0 +1,46 @@
+package utils
+
+import (
+	"os"
+)
+
+func PathExists(path string) (bool, error) {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
+
+//判断是否问有效文件夹
+func IsDir(filename string) (bool, error) {
+	fd, err := os.Stat(filename)
+	if err != nil {
+		return false, err
+	}
+	fm := fd.Mode()
+	return fm.IsDir(), nil
+}
+
+//创建文件夹
+func CreateDir(dir string) error {
+	if ifdir, _ := IsDir(dir); !ifdir {
+		err := os.Mkdir(dir, 0777)
+		if err != nil {
+			//global.App.Log.Error("新建文件夹失败,path=" + dir)
+
+			return err
+		}
+		// 再修改权限
+		err = os.Chmod(dir, 0777)
+		if err != nil {
+			//global.App.Log.Error("修改文件夹权限失败,path=" + dir)
+			return err
+		}
+	}
+
+	return nil
+}

+ 8 - 0
utils/json.go

@@ -0,0 +1,8 @@
+package utils
+
+import "encoding/json"
+
+func JsonEncode(data interface{}) (string, error) {
+	jsons, err := json.Marshal(data)
+	return string(jsons), err
+}

+ 17 - 0
utils/map.go

@@ -0,0 +1,17 @@
+package utils
+
+func MergeMaps(destMap, sourceMap map[string]interface{}) map[string]interface{} {
+	newMap := make(map[string]interface{})
+
+	// 将目标 map 的元素复制到新 map 中
+	for key, value := range destMap {
+		newMap[key] = value
+	}
+
+	// 将源 map 中的元素合并到新 map 中
+	for key, value := range sourceMap {
+		newMap[key] = value
+	}
+
+	return newMap
+}

+ 12 - 0
utils/md5.go

@@ -0,0 +1,12 @@
+package utils
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+)
+
+func MD5(str []byte, b ...byte) string {
+	h := md5.New()
+	h.Write(str)
+	return hex.EncodeToString(h.Sum(b))
+}

+ 32 - 0
utils/str.go

@@ -0,0 +1,32 @@
+package utils
+
+import (
+	"math/rand"
+	"strconv"
+	"time"
+)
+
+func RandString(len int) string {
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	bytes := make([]byte, len)
+	for i := 0; i < len; i++ {
+		b := r.Intn(26) + 65
+		bytes[i] = byte(b)
+	}
+	return string(bytes)
+}
+
+func GetSampleAlleleNum(sampleId int) string {
+	sampleIdString := strconv.Itoa(sampleId)
+	var num string
+	if len(sampleIdString) > 2 {
+		num = sampleIdString[len(sampleIdString)-2:]
+	} else {
+		num = sampleIdString
+	}
+	if num[0:1] == "0" && len(num) == 2 {
+		num = num[1:]
+	}
+
+	return num
+}

+ 16 - 0
utils/validator.go

@@ -0,0 +1,16 @@
+package utils
+
+import (
+	"github.com/go-playground/validator/v10"
+	"regexp"
+)
+
+// ValidateMobile 校验手机号
+func ValidateMobile(fl validator.FieldLevel) bool {
+	mobile := fl.Field().String()
+	ok, _ := regexp.MatchString(`^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$`, mobile)
+	if !ok {
+		return false
+	}
+	return true
+}