Browse Source

上传代码

wucan 5 months ago
commit
e64c42db29
60 changed files with 5163 additions and 0 deletions
  1. 18 0
      app/common/enum/msg.go
  2. 10 0
      app/common/enum/status.go
  3. 60 0
      app/common/log/log.go
  4. 14 0
      app/common/request/auth.go
  5. 28 0
      app/common/request/user.go
  6. 26 0
      app/common/request/validate.go
  7. 30 0
      app/common/request/validator.go
  8. 130 0
      app/common/response/response.go
  9. 21 0
      bootstrap/config.go
  10. 130 0
      bootstrap/db.go
  11. 114 0
      bootstrap/log.go
  12. 33 0
      bootstrap/mongodb.go
  13. 33 0
      bootstrap/redis.go
  14. 118 0
      bootstrap/router.go
  15. 5 0
      bootstrap/tencent.go
  16. 328 0
      common/common.go
  17. 42 0
      common/ip.go
  18. 88 0
      common/redis.go
  19. 46 0
      config/app.go
  20. 160 0
      config/common.go
  21. 16 0
      config/config.go
  22. 17 0
      config/database.go
  23. 9 0
      config/download.go
  24. 142 0
      config/json/city.json
  25. 10 0
      config/json/game.json
  26. 9 0
      config/jwt.go
  27. 11 0
      config/log.go
  28. 10 0
      config/redis.go
  29. 10 0
      config/temp.go
  30. 958 0
      controller/asset.go
  31. 52 0
      controller/game.go
  32. 422 0
      controller/identity.go
  33. 74 0
      controller/log.go
  34. 242 0
      controller/user.go
  35. 65 0
      global/app.go
  36. 18 0
      global/error.go
  37. 68 0
      global/lock.go
  38. 76 0
      go.mod
  39. 208 0
      go.sum
  40. 74 0
      main.go
  41. 152 0
      middleware/auth.go
  42. 48 0
      middleware/middlelogger.go
  43. 61 0
      model/identity.go
  44. 56 0
      model/property.go
  45. 134 0
      model/user.go
  46. 1 0
      model/video.go
  47. 124 0
      response/response.go
  48. 70 0
      route/api.go
  49. 27 0
      service/actionLog.go
  50. 95 0
      service/imgSlice.go
  51. 65 0
      utils/array.go
  52. 23 0
      utils/bcrypt.go
  53. 128 0
      utils/db.go
  54. 46 0
      utils/directory.go
  55. 8 0
      utils/json.go
  56. 17 0
      utils/map.go
  57. 12 0
      utils/md5.go
  58. 40 0
      utils/str.go
  59. 115 0
      utils/time.go
  60. 16 0
      utils/validator.go

+ 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 "数据格式错误"
+}

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

@@ -0,0 +1,130 @@
+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
+	if data["data"] == nil {
+		data["data"] = "success"
+	}
+	if data["msg"] == nil {
+		data["msg"] = "success"
+	}
+	//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,
+			"data":  "",
+			"stack": stack,
+		})
+	} else {
+		c.JSON(http.StatusOK, gin.H{
+			"data": "",
+			"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)
+	}
+
+}

+ 130 - 0
bootstrap/db.go

@@ -0,0 +1,130 @@
+package bootstrap
+
+import (
+	"designs/config"
+	"designs/global"
+	"io"
+	"log"
+	"os"
+	"time"
+
+	"go.uber.org/zap"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
+)
+
+func InitializeDB() *gorm.DB {
+	// 根据驱动配置进行初始化
+	switch config.Get("database.driver") {
+	case "mysql":
+		return initMySqlGorm()
+	default:
+		return initMySqlGorm()
+	}
+}
+
+func UnInitializeDB(db *gorm.DB) {
+	if db != nil {
+		if sqldb, err := db.DB(); err == nil {
+			sqldb.Close()
+		} else {
+			global.App.Log.Errorf("close database failed: %v", err)
+		}
+	}
+}
+
+func initMySqlGorm() *gorm.DB {
+
+	UserName := config.Get("database.userName")
+	Password := config.Get("database.password")
+	Host := config.Get("database.host")
+	Port := config.Get("database.port")
+	Database := config.Get("database.database")
+	Charset := config.Get("database.charset")
+
+	dsn := UserName + ":" + Password + "@tcp(" + Host + ":" + Port + ")/" +
+		Database + "?charset=" + Charset + "&parseTime=True&loc=Local"
+	mysqlConfig := mysql.Config{
+		DSN:                       dsn,   // DSN data source name
+		DefaultStringSize:         191,   // string 类型字段的默认长度
+		DisableDatetimePrecision:  true,  // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
+		DontSupportRenameIndex:    true,  // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
+		DontSupportRenameColumn:   true,  // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
+		SkipInitializeWithVersion: false, // 根据版本自动配置
+	}
+	if db, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{
+		DisableForeignKeyConstraintWhenMigrating: true,            // 禁用自动创建外键约束
+		Logger:                                   getGormLogger(), // 使用自定义 Logger
+	}); err != nil {
+		global.App.Log.Error("mysql connect failed, err:", zap.Any("err", err))
+		return nil
+	} else {
+		sqlDB, _ := db.DB()
+		sqlDB.SetMaxIdleConns(config.GetInt("database.maxIdleConns"))
+		sqlDB.SetMaxOpenConns(config.GetInt("database.maxOpenConns"))
+		initMySqlTables(db)
+		return db
+	}
+}
+
+func getGormLogger() logger.Interface {
+	var logMode logger.LogLevel
+
+	switch config.Get("database.logMode") {
+	case "silent":
+		logMode = logger.Silent
+	case "error":
+		logMode = logger.Error
+	case "warn":
+		logMode = logger.Warn
+	case "info":
+		logMode = logger.Info
+	default:
+		logMode = logger.Info
+	}
+
+	return logger.New(getGormLogWriter(), logger.Config{
+		SlowThreshold:             2 * time.Second,                                 // 慢 SQL 阈值
+		LogLevel:                  logMode,                                         // 日志级别
+		IgnoreRecordNotFoundError: true,                                            // 忽略ErrRecordNotFound(记录未找到)错误
+		Colorful:                  !config.GetBool("database.enableFileLogWriter"), // 禁用彩色打印
+	})
+}
+
+// 自定义 gorm Writer
+func getGormLogWriter() logger.Writer {
+	var writer io.Writer
+
+	// 是否启用日志文件
+	if config.GetBool("database.enableFileLogWriter") {
+		// 自定义 Writer
+		// writer = &lumberjack.Logger{
+		// 	Filename:   global.App.Config.Log.RootDir + "/" + global.App.Config.Database.LogFilename,
+		// 	MaxSize:    global.App.Config.Log.MaxSize,
+		// 	MaxBackups: global.App.Config.Log.MaxBackups,
+		// 	MaxAge:     global.App.Config.Log.MaxAge,
+		// 	Compress:   global.App.Config.Log.Compress,
+		// }
+		if global.App.LogWriter == nil {
+			panic("log writer is nil")
+		}
+		writer = global.App.LogWriter
+	} else {
+		// 默认 Writer
+		writer = os.Stdout
+	}
+	return log.New(writer, "\r\n", log.LstdFlags)
+}
+
+// 数据库表初始化
+func initMySqlTables(db *gorm.DB) {
+	err := db.AutoMigrate(
+	// models.User{},
+	// models.Media{},
+	)
+	if err != nil {
+		global.App.Log.Error("migrate table failed", zap.Any("err", err))
+		os.Exit(0)
+	}
+}

+ 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
+}

+ 33 - 0
bootstrap/mongodb.go

@@ -0,0 +1,33 @@
+package bootstrap
+
+import (
+	"context"
+	"designs/config"
+	"designs/global"
+	"go.mongodb.org/mongo-driver/v2/mongo"
+	"go.mongodb.org/mongo-driver/v2/mongo/readpref"
+	"go.uber.org/zap"
+)
+import mongoOption "go.mongodb.org/mongo-driver/v2/mongo/options"
+
+func InitializeMongo() *mongo.Client {
+
+	var url string
+	if config.Get("app.local") == "local" {
+		url = "mongodb://localhost:27017"
+	} else {
+		url = "mongodb://admin:admin@localhost:27017"
+	}
+
+	//url = "mongodb://admin:admin@124.223.73.12:27017"
+
+	client, _ := mongo.Connect(mongoOption.Client().ApplyURI(url))
+	//client, _ := mongo.Connect(mongoOption.Client().ApplyURI("mongodb://localhost:27017"))
+	err := client.Ping(context.Background(), readpref.Primary())
+	if err != nil {
+		global.App.Log.Error("mongoDB connect ping failed, err:", zap.Any("err", err))
+		return nil
+	}
+
+	return client
+}

+ 33 - 0
bootstrap/redis.go

@@ -0,0 +1,33 @@
+package bootstrap
+
+import (
+	"context"
+	"designs/config"
+	"designs/global"
+	"github.com/go-redis/redis/v8"
+	"go.uber.org/zap"
+)
+
+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()
+	}
+}

+ 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")
+}

+ 5 - 0
bootstrap/tencent.go

@@ -0,0 +1,5 @@
+package bootstrap
+
+func InitTencentApi() {
+
+}

+ 328 - 0
common/common.go

@@ -0,0 +1,328 @@
+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"`
+	UserId    int    `json:"userId"`
+	TokenType int    `json:"tokenType"` //0 token 登录  1:刷新token
+	jwt.RegisteredClaims
+}
+
+// 生成jwt token
+func GetJwtToken(UserName string, UserId int, 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,
+		UserId,
+		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, 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, 0
+	} else if claims, ok := token.Claims.(*MyCustomClaims); ok {
+		// fmt.Println(claims.OpenId, claims.RegisteredClaims.Issuer)
+		return claims.UserName, claims.UserId, claims.TokenType
+	} else {
+		// fmt.Println("unknown claims type, cannot proceed")
+		return "", 0, 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 ""
+}

+ 46 - 0
config/app.go

@@ -0,0 +1,46 @@
+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"),
+		"local":   env("LOCAL", ""),
+
+		"app_secret":       env("APP_SECRET", "6YJSuc50uJ18zj45"),
+		"app_check_secret": env("APP_CHECK_SECRET", "6YJSuc50uJ18zj45"), //检测数据篡改密钥
+		"api_expiry":       env("API_EXPIRY", "12000000"),               //
+		"max_content":      env("MAX_CONTENT", "50000000"),              //最大请求内容长度
+		"api_exp":          env("API_EXP", "600000000"),                 //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"),   //默认管理密码
+		"user_total":   env("USER_TOTAL", "user_total"), //所有用户的集合
+	}
+}
+
+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"
+}

+ 16 - 0
config/config.go

@@ -0,0 +1,16 @@
+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,
+	"database": Database,
+}

+ 17 - 0
config/database.go

@@ -0,0 +1,17 @@
+package config
+
+func Database() *ConfigNode {
+	return &ConfigNode{
+		"driver":              "mysql",
+		"host":                env("MYSQL_HOST", "localhost"),
+		"port":                env("MYSQL_PORT", "3306"),
+		"database":            env("MYSQL_DATABASE", "chunhao"),
+		"userName":            env("MYSQL_USERNAME", "root"),
+		"password":            env("MYSQL_PASSWORD", "root"),
+		"charset":             "utf8mb4",
+		"maxIdleConns":        "10",
+		"maxOpenConns":        "50",
+		"logMode":             env("MYSQL_LOG_MODE", "info"),
+		"enableFileLogWriter": env("MYSQL_ENABLE_LOG_WRITE", "true"),
+	}
+}

+ 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"),
+	}
+}

+ 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/"),
+	}
+}

+ 958 - 0
controller/asset.go

@@ -0,0 +1,958 @@
+package controller
+
+import (
+	"crypto/md5"
+	"designs/app/common/request"
+	localConfig "designs/config"
+	"designs/global"
+	"designs/model"
+	"designs/response"
+	"designs/service"
+	"designs/utils"
+	"encoding/hex"
+	"fmt"
+	"github.com/antihax/optional"
+	"github.com/gin-gonic/gin"
+	"github.com/tencentad/marketing-api-go-sdk/pkg/ads/v3"
+	"github.com/tencentad/marketing-api-go-sdk/pkg/api/v3"
+	"github.com/tencentad/marketing-api-go-sdk/pkg/config/v3"
+	"io"
+	"mime/multipart"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+)
+
+func GetAccessToken(gid string, pid string) string {
+	var org struct {
+		AccessToken string `json:"access_token" gorm:"column:access_token"`
+	}
+
+	var AccessToken string
+	global.App.DB.Table("organizations").Where("oid", 46379136).Select("access_token").First(&org)
+	if org.AccessToken != "" {
+		AccessToken = org.AccessToken
+	} else {
+		AccessToken = "da5a0590d1653fd1e6c9b72cabcb14f1"
+	}
+
+	return AccessToken
+}
+
+func generateFileMD5(fileHeader *multipart.FileHeader) (string, error) {
+	file, err := fileHeader.Open()
+	if err != nil {
+		return "", err
+	}
+	defer file.Close()
+
+	hash := md5.New()
+	if _, err := io.Copy(hash, file); err != nil {
+		return "", err
+	}
+	return hex.EncodeToString(hash.Sum(nil)), nil
+}
+
+// 上传图片到服务器
+func Upload(c *gin.Context) {
+	// 获取文件
+	file, err := c.FormFile("file")
+	if err != nil {
+		response.Fail(c, 400, "未选择文件")
+		return
+	}
+
+	// 类型检查
+	ext := strings.ToLower(filepath.Ext(file.Filename))
+	allowed := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".avi": true, ".mp4": true}
+	if !allowed[ext] {
+
+		response.Fail(c, 1001, "图片仅支持JPG/PNG,视频支持avi/mp4")
+		return
+	}
+	md5Value, err := generateFileMD5(file)
+
+	// 存储路径生成
+	saveDir := fmt.Sprintf("uploads/%s", time.Now().Format("2006-01-02"))
+
+	os.MkdirAll(saveDir, 0755)
+
+	savePath := filepath.Join(saveDir, md5Value+ext)
+
+	// 保存文件
+	if err := c.SaveUploadedFile(file, savePath); err != nil {
+		response.Fail(c, 500, "文件保存失败")
+
+		return
+	}
+
+	response.Success(c, gin.H{
+		"path": savePath,
+		"md5":  md5Value,
+	})
+}
+
+func ImageSet(c *gin.Context) {
+	form := request.Check(c, &struct {
+		ImgPath    string `json:"imgPath" binding:"required"`
+		Md5        string `json:"md5" binding:"required"`
+		Name       string `json:"name" binding:"required"`
+		Gid        string `json:"gid" binding:"required"`
+		Pid        string `json:"pid" binding:""`
+		Tags       []int  `json:"tags" binding:""`
+		IsImg9     int    `json:"isImg9" binding:""`
+		Resolution struct {
+			Width  int `json:"width" binding:""`
+			Height int `json:"height" binding:""`
+		} `json:"resolution" binding:""`
+	}{})
+
+	if !CheckTags(form.Tags) {
+		response.Fail(c, 500, "tag中有不存在或错误的ID")
+		return
+	}
+	//检查重复
+	var image model.PropertyImage
+	global.App.DB.Table(model.TablePropertyImage).Where("name", form.Name).Or("md5", form.Md5).First(&image)
+	if image.ID != 0 {
+		response.Fail(c, 1001, "图片名称或素材重复")
+		return
+	}
+
+	//处理9图
+	if form.IsImg9 != 0 {
+		err := service.SliceImg(form.ImgPath, form.Name)
+		if err != nil {
+			response.Fail(c, 500, err.Error())
+			return
+		}
+	}
+	//上传微信
+	files, err := os.Open(form.ImgPath)
+	if err != nil {
+		response.Fail(c, 500, "文件读取失败"+err.Error())
+		return
+	}
+	defer files.Close()
+
+	//请求微信接口
+	va := api.ImagesAddOpts{File: optional.NewInterface(files), AccountId: optional.NewInt64(57213719)}
+	tads := ads.Init(&config.SDKConfig{
+		AccessToken: GetAccessToken("", ""),
+	})
+	ctx := *tads.Ctx
+	//res, _, err := tads.Advertiser().Get(ctx, nil)
+	res, _, err := tads.Images().Add(ctx, "UPLOAD_TYPE_FILE", form.Md5, &va)
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	//归档到纯皓系统中
+	now := model.XTime{Time: time.Now()}
+	newImage := model.PropertyImage{
+		Name:           form.Name,
+		LocalPath:      form.ImgPath,
+		Gid:            form.Gid,
+		Pid:            form.Pid,
+		WxId:           *res.ImageId,
+		ImageWidth:     int(*res.ImageWidth),
+		ImageHeight:    int(*res.ImageHeight),
+		ImageFileSize:  int(*res.ImageFileSize),
+		ImageSignature: *res.ImageSignature,
+		IsImg9:         form.IsImg9,
+		CreatedAt:      now,
+		UpdatedAt:      now,
+		Resolution:     strconv.Itoa(form.Resolution.Width) + "x" + strconv.Itoa(form.Resolution.Height),
+	}
+
+	err = global.App.DB.Transaction(func(tx *utils.WtDB) error {
+		err = global.App.DB.Table(model.TablePropertyImage).Create(&newImage).Error
+		if err != nil {
+			return err
+		}
+
+		//增加新tag
+		var tags []model.PropertyImageTag
+		for _, v := range form.Tags {
+			tags = append(tags, model.PropertyImageTag{
+				TagId:   v,
+				ImageId: newImage.ID,
+			})
+		}
+		err = global.App.DB.Table(model.TablePropertyImageTag).CreateInBatches(&tags, 100).Error
+		if err != nil {
+			return err
+		}
+
+		userId := c.GetInt("userId")
+		err = service.SetActionLog("新增", userId, "图片", newImage)
+		if err != nil {
+			global.App.Log.Error("")
+			return err
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		response.Fail(c, 1001, "存入数据库失败"+err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": res,
+	})
+}
+
+func ImageList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Limit      int     `form:"limit" json:"limit" binding:"required"`
+		Offset     int     `form:"offset" json:"offset" binding:""`
+		Search     string  `form:"search" json:"search" binding:""`
+		Gid        string  `form:"gid" json:"gid" binding:""`
+		Pid        *string `form:"pid" json:"pid" binding:""`
+		Order      string  `form:"order" json:"order" binding:""`
+		Prop       string  `form:"prop" json:"prop" binding:""`
+		IsImg9     string  `form:"isImg9" json:"isImg9" binding:""`
+		Resolution string  `form:"resolution" json:"resolution" binding:""`
+		Tag        []int   `form:"tag" json:"tag" binding:""`
+	}{})
+
+	query := global.App.DB.Table(model.TablePropertyImage)
+
+	if form.Search != "" {
+		query = query.Where("name", "like", "%"+form.Search+"%")
+	}
+
+	if form.Gid != "" {
+		query = query.Where("gid", "=", form.Gid)
+	}
+	if form.Pid != nil && form.Gid == "" {
+		var gid []string
+		global.App.DB.Table(model.TableGame).Where("pid", form.Pid).Pluck("gid", &gid)
+
+		query = query.WhereIn("gid", gid)
+	}
+	if form.IsImg9 != "" {
+		query = query.Where("is_img9", "=", form.IsImg9)
+	}
+	if form.Resolution != "" {
+		query = query.Where("resolution", "=", form.Resolution)
+	}
+
+	if form.Order != "" && form.Prop != "" {
+		query = query.Order(form.Prop + " " + form.Order)
+	}
+
+	if form.Tag != nil {
+		var imageIds []int
+		query1 := global.App.DB.Table(model.TablePropertyImageTag)
+		for _, tag := range form.Tag {
+			query1 = query1.Where("tag_id", tag)
+		}
+		query1.Pluck("image_id", &imageIds)
+
+		query = query.WhereIn("id", imageIds)
+	}
+
+	var count int64
+	err := query.Count(&count).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	type TagData struct {
+		model.PropertyImageTag
+		Name string `json:"name" gorm:"not null;"`
+	}
+	var imgData []model.PropertyImage
+
+	err = query.Offset(form.Offset).Limit(form.Limit).Scan(&imgData).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+	//查询出tag
+	var ids []int
+	for _, v := range imgData {
+		ids = append(ids, v.ID)
+	}
+	var tagData []TagData
+	err = global.App.DB.Table(model.TablePropertyImageTag).
+		LeftJoin(model.TablePropertyTag, "property_image_tag.tag_id = property_tag.id").
+		WhereIn("property_image_tag.image_id", ids).
+		Scan(&tagData).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	list := make([]struct {
+		model.PropertyImage
+		Tag      []TagData `json:"tag"`
+		Download string    `json:"download"`
+	}, len(imgData))
+
+	for k, v := range imgData {
+		list[k].ID = v.ID
+		list[k].Gid = v.Gid
+		list[k].Pid = v.Pid
+		list[k].WxId = v.WxId
+		list[k].Name = v.Name
+		list[k].CreatedAt = v.CreatedAt
+		list[k].UpdatedAt = v.UpdatedAt
+		list[k].LocalPath = v.LocalPath
+		list[k].ImageWidth = v.ImageWidth
+		list[k].ImageHeight = v.ImageHeight
+		list[k].ImageFileSize = v.ImageFileSize
+		list[k].ImageSignature = v.ImageSignature
+		list[k].ImageType = v.ImageType
+		list[k].Resolution = v.Resolution
+		list[k].Download = GetDownload(v.LocalPath)
+
+		for _, tag := range tagData {
+			if v.ID == tag.ImageId {
+				list[k].Tag = append(list[k].Tag, tag)
+			}
+		}
+	}
+
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"list":  list,
+			"count": count,
+		},
+	})
+}
+
+func GetImageListHead(c *gin.Context) {
+	//获取分辨率的数据
+	var resolution []string
+	err := global.App.DB.Table(model.TablePropertyImage).Group("resolution").Pluck("resolution", &resolution).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"resolution": resolution,
+		},
+	})
+}
+
+func GetVideoListHead(c *gin.Context) {
+	//获取分辨率的数据
+	var resolution []string
+	err := global.App.DB.Table(model.TablePropertyVideo).Group("resolution").Pluck("resolution", &resolution).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"resolution": resolution,
+		},
+	})
+}
+func ImageDelete(c *gin.Context) {
+	form := request.Check(c, &struct {
+		IdList []int `form:"idList" json:"idList" binding:"required"`
+	}{})
+
+	var image []model.PropertyImage
+	global.App.DB.Table(model.TablePropertyImage).WhereIn("id", form.IdList).Scan(&image)
+	if len(image) != len(form.IdList) {
+		response.Fail(c, 500, "图片id错误")
+		return
+	}
+
+	////删除云端数据
+	//for _, v := range image {
+	//	var AccountId = int64(57213719)
+	//	va := modelV3.ImagesDeleteRequest{
+	//		AccountId: &AccountId,
+	//		ImageId:   &v.WxId,
+	//	}
+	//	tads := ads.Init(&config.SDKConfig{
+	//		AccessToken: GetAccessToken("", ""),
+	//	})
+	//	ctx := *tads.Ctx
+	//	//res, _, err := tads.Advertiser().Get(ctx, nil)
+	//	_, _, err := tads.Images().Delete(ctx, va)
+	//	if err != nil {
+	//		response.Fail(c, 1001, err.Error())
+	//		return
+	//	}
+	//}
+
+	var d interface{}
+	err := global.App.DB.Transaction(func(tx *utils.WtDB) error {
+		//删除image
+		err := global.App.DB.Table(model.TablePropertyImage).WhereIn("id", form.IdList).Delete(d).Error
+		if err != nil {
+			return err
+		}
+		//删除tag
+		err = global.App.DB.Table(model.TablePropertyImageTag).WhereIn("image_id", form.IdList).Delete(d).Error
+		if err != nil {
+			return err
+		}
+
+		userId := c.GetInt("userId")
+		err = service.SetActionLog("删除", userId, "图片", image)
+		if err != nil {
+			global.App.Log.Error("")
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		fmt.Println(err.Error())
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	//记录日志
+
+	response.Success(c, gin.H{})
+}
+
+func UpdateImageTags(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Id   int   `form:"id" json:"id" binding:"required"`
+		Tags []int `form:"tags" json:"tags" binding:""`
+	}{})
+
+	if !CheckTags(form.Tags) {
+		response.Fail(c, 500, "tag中有不存在或错误的ID")
+		return
+	}
+
+	var image model.PropertyImage
+	global.App.DB.Table(model.TablePropertyImage).Where("id", form.Id).Select("id", "wx_id").First(&image)
+	if image.ID == 0 {
+		response.Fail(c, 500, "图片id错误")
+		return
+	}
+
+	var d interface{}
+	err := global.App.DB.Transaction(func(tx *utils.WtDB) error {
+		//删除tag
+		err := global.App.DB.Table(model.TablePropertyImageTag).Where("image_id", image.ID).Delete(d).Error
+		if err != nil {
+			return err
+		}
+		//增加新tag
+		var tags []model.PropertyImageTag
+		for _, v := range form.Tags {
+			tags = append(tags, model.PropertyImageTag{
+				TagId:   v,
+				ImageId: image.ID,
+			})
+		}
+		err = global.App.DB.Table(model.TablePropertyImageTag).CreateInBatches(&tags, 100).Error
+		if err != nil {
+			return err
+		}
+
+		//更新image表更新时间
+		err = global.App.DB.Table(model.TablePropertyImage).Where("id", image.ID).Updates(map[string]interface{}{
+			"updatedAt": time.Now(),
+		}).Error
+		if err != nil {
+			return err
+		}
+
+		userId := c.GetInt("userId")
+		err = service.SetActionLog("修改tag", userId, "图片", form.Tags)
+		if err != nil {
+			global.App.Log.Error("")
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	//记录日志
+
+	response.Success(c, gin.H{})
+}
+
+func CheckTags(Tags []int) bool {
+	var count int64
+	global.App.DB.Table(model.TablePropertyTag).WhereIn("id", Tags).Count(&count)
+
+	return int(count) == len(Tags)
+}
+
+func SetTag(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Name string `form:"name" json:"name" binding:"required"`
+	}{})
+
+	var tag model.PropertyTag
+	global.App.DB.Table(model.TablePropertyTag).Where("name", form.Name).First(&tag)
+	if tag.ID != 0 {
+		response.Fail(c, 1001, "该tag已建立")
+		return
+	}
+
+	now := model.XTime{
+		Time: time.Now(),
+	}
+	tag = model.PropertyTag{
+		Name:      form.Name,
+		CreatedAt: now,
+	}
+	err := global.App.DB.Table(model.TablePropertyTag).Create(&tag).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+	//记录日志
+	userId := c.GetInt("userId")
+	err = service.SetActionLog("新增", userId, "tag", tag)
+	if err != nil {
+		global.App.Log.Error("")
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"id": tag.ID,
+		},
+	})
+}
+
+func DeleteTag(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Id int `form:"id" json:"id" binding:"required"`
+	}{})
+
+	var tag model.PropertyTag
+	global.App.DB.Table(model.TablePropertyTag).Where("id", form.Id).First(&tag)
+	if tag.ID == 0 {
+		response.Fail(c, 1001, "该tag不存在")
+		return
+	}
+
+	var d interface{}
+	err := global.App.DB.Table(model.TablePropertyTag).Where("id", form.Id).Delete(d).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	//记录日志
+	userId := c.GetInt("userId")
+	err = service.SetActionLog("删除", userId, "tag", tag)
+	if err != nil {
+		global.App.Log.Error("")
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}
+
+func VideoSet(c *gin.Context) {
+	form := request.Check(c, &struct {
+		VideoPath   string `json:"videoPath" binding:"required"`
+		HeadImgPath string `json:"headImgPath" binding:"required"`
+		Md5         string `json:"md5" binding:"required"`
+		Name        string `json:"name" binding:"required"`
+		Gid         string `json:"gid" binding:"required"`
+		Pid         string `json:"pid" binding:""`
+		Tags        []int  `json:"tags" binding:""`
+		Resolution  struct {
+			Width  int `json:"width" binding:""`
+			Height int `json:"height" binding:""`
+		} `json:"resolution" binding:""`
+		VideoType int `json:"videoType" binding:"required"`
+	}{})
+
+	if !CheckTags(form.Tags) {
+		response.Fail(c, 500, "tag中有不存在或错误的ID")
+		return
+	}
+	//检查重复
+	var video model.PropertyVideo
+	global.App.DB.Table(model.TablePropertyVideo).Where("name", form.Name).Or("md5", form.Md5).First(&video)
+	if video.ID != 0 {
+		response.Fail(c, 1001, "视频名称或素材重复")
+		return
+	}
+
+	//上传微信
+	files, err := os.Open(form.VideoPath)
+	if err != nil {
+		response.Fail(c, 500, "文件读取失败"+err.Error())
+		return
+	}
+	defer files.Close()
+
+	//请求微信接口
+	va := api.VideosAddOpts{AccountId: optional.NewInt64(57213719)}
+	tads := ads.Init(&config.SDKConfig{
+		AccessToken: GetAccessToken("", ""),
+	})
+	ctx := *tads.Ctx
+	//res, _, err := tads.Advertiser().Get(ctx, nil)
+	res, _, err := tads.Videos().Add(ctx, files, form.Md5, &va)
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	//入库
+	//归档到纯皓系统中
+	now := model.XTime{Time: time.Now()}
+
+	newVideo := model.PropertyVideo{
+		Name:           form.Name,
+		LocalPath:      form.VideoPath,
+		Gid:            form.Gid,
+		Pid:            form.Pid,
+		WxId:           int(*res.VideoId),
+		VideoSignature: form.Md5,
+		CreatedAt:      now,
+		UpdatedAt:      now,
+		VideoType:      form.VideoType,
+		Resolution:     strconv.Itoa(form.Resolution.Width) + "x" + strconv.Itoa(form.Resolution.Height),
+		HeadImgPath:    form.HeadImgPath,
+	}
+
+	err = global.App.DB.Transaction(func(tx *utils.WtDB) error {
+		err = global.App.DB.Table(model.TablePropertyVideo).Create(&newVideo).Error
+		if err != nil {
+			return err
+		}
+
+		//增加新tag
+		var tags []model.PropertyVideoTag
+		for _, v := range form.Tags {
+			tags = append(tags, model.PropertyVideoTag{
+				TagId:   v,
+				VideoId: newVideo.ID,
+			})
+		}
+		err = global.App.DB.Table(model.TablePropertyVideoTag).CreateInBatches(&tags, 100).Error
+		if err != nil {
+			return err
+		}
+
+		//记录日志
+		userId := c.GetInt("userId")
+		err = service.SetActionLog("新增", userId, "视频", newVideo)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		response.Fail(c, 1001, "存入数据库失败"+err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"files": res,
+	})
+}
+
+func VideoDelete(c *gin.Context) {
+	form := request.Check(c, &struct {
+		IdList []int `form:"idList" json:"idList" binding:"required"`
+	}{})
+
+	var video []model.PropertyVideo
+	global.App.DB.Table(model.TablePropertyVideo).WhereIn("id", form.IdList).Scan(&video)
+	if len(video) != len(form.IdList) {
+		response.Fail(c, 500, "图片id错误")
+		return
+	}
+
+	////删除云端数据
+	//videoId := int64(video.WxId)
+	//var AccountId = int64(57213719)
+	//va := modelV3.VideosDeleteRequest{
+	//	AccountId: &AccountId,
+	//	VideoId:   &videoId,
+	//}
+	//tads := ads.Init(&config.SDKConfig{
+	//	AccessToken: GetAccessToken("", ""),
+	//})
+	//ctx := *tads.Ctx
+	////res, _, err := tads.Advertiser().Get(ctx, nil)
+	//_, _, err := tads.Videos().Delete(ctx, va)
+	//if err != nil {
+	//	response.Fail(c, 1001, err.Error())
+	//	return
+	//}
+
+	var d interface{}
+	err := global.App.DB.Transaction(func(tx *utils.WtDB) error {
+		//删除video
+		err := global.App.DB.Table(model.TablePropertyVideo).WhereIn("id", form.IdList).Delete(d).Error
+		if err != nil {
+			return err
+		}
+		//删除tag
+		err = global.App.DB.Table(model.TablePropertyVideoTag).Where("video_id", form.IdList).Delete(d).Error
+		if err != nil {
+			return err
+		}
+
+		//记录日志
+		userId := c.GetInt("userId")
+		err = service.SetActionLog("删除", userId, "视频", video)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		response.Fail(c, 501, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}
+
+func UpdateVideoTags(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Id   int   `form:"id" json:"id" binding:"required"`
+		Tags []int `form:"tags" json:"tags" binding:""`
+	}{})
+
+	if !CheckTags(form.Tags) {
+		response.Fail(c, 500, "tag中有不存在或错误的ID")
+		return
+	}
+
+	var video model.PropertyVideo
+	global.App.DB.Table(model.TablePropertyVideo).Where("id", form.Id).Select("id", "wx_id").First(&video)
+	if video.ID == 0 {
+		response.Fail(c, 500, "图片id错误")
+		return
+	}
+
+	var d interface{}
+	err := global.App.DB.Transaction(func(tx *utils.WtDB) error {
+		//删除tag
+		err := global.App.DB.Table(model.TablePropertyVideoTag).Where("video_id", video.ID).Delete(d).Error
+		if err != nil {
+			return err
+		}
+		//增加新tag
+		var tags []model.PropertyVideoTag
+		for _, v := range form.Tags {
+			tags = append(tags, model.PropertyVideoTag{
+				TagId:   v,
+				VideoId: video.ID,
+			})
+		}
+		err = global.App.DB.Table(model.TablePropertyVideoTag).CreateInBatches(&tags, 100).Error
+		if err != nil {
+			return err
+		}
+
+		//更新image表更新时间
+		err = global.App.DB.Table(model.TablePropertyVideo).Where("id", video.ID).Updates(map[string]interface{}{
+			"updatedAt": time.Now(),
+		}).Error
+		if err != nil {
+			return err
+		}
+
+		//记录日志
+		userId := c.GetInt("userId")
+		err = service.SetActionLog("修改", userId, "视频", form.Tags)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		response.Fail(c, 500, "数据库错误"+err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}
+
+func GetTags(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Offset int    `form:"offset" json:"offset" binding:""`
+		Limit  int    `form:"limit" json:"limit" binding:""`
+		Search string `form:"search" json:"search" binding:""`
+	}{})
+
+	query := global.App.DB.Table(model.TablePropertyTag)
+
+	if form.Search != "" {
+		query = query.Where("name", "like", "%"+form.Search+"%")
+	}
+	var count int64
+	err := query.Count(&count).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	if form.Offset != 0 {
+		query = query.Offset(form.Offset)
+	}
+	if form.Limit != 0 {
+		query = query.Limit(form.Limit)
+	}
+
+	var tags []model.PropertyTag
+	err = query.Scan(&tags).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data":  tags,
+		"count": count,
+	})
+}
+
+func VideoList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Offset     int    `form:"offset" json:"offset" binding:""`
+		Limit      int    `form:"limit" json:"limit" binding:"required"`
+		Search     string `form:"search" json:"search" binding:""`
+		Order      string `form:"order" json:"order" binding:""`
+		Prop       string `form:"prop" json:"prop" binding:""`
+		Gid        string `form:"gid" json:"gid" binding:""`
+		Pid        string `form:"pid" json:"pid" binding:""`
+		Resolution string `form:"resolution" json:"resolution" binding:""`
+		VideoType  string `form:"video_type" json:"video_type" binding:""`
+		Tag        []int  `form:"tag" json:"tag" binding:""`
+	}{})
+
+	query := global.App.DB.Table(model.TablePropertyVideo)
+	if form.Search != "" {
+		query = query.Where("name", "like", "%"+form.Search+"%")
+	}
+	if form.Gid != "" {
+		query = query.Where("gid", "=", form.Gid)
+	}
+	if form.Pid != "" {
+		query = query.Where("pid", "=", form.Pid)
+	}
+
+	if form.Order != "" && form.Prop != "" {
+		query = query.Order(form.Prop + " " + form.Order)
+	}
+	if form.Resolution != "" {
+		query = query.Where("resolution", "=", form.Resolution)
+	}
+	if form.VideoType != "" {
+		query = query.Where("video_type", "=", form.VideoType)
+	}
+	if form.Tag != nil {
+		var videoIds []int
+		query1 := global.App.DB.Table(model.TablePropertyVideoTag)
+		for _, tag := range form.Tag {
+			query1 = query1.Where("tag_id", tag)
+		}
+		query1.Pluck("video_id", &videoIds)
+
+		query = query.WhereIn("id", videoIds)
+	}
+
+	var count int64
+	err := query.Count(&count).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+	type TagData struct {
+		model.PropertyVideoTag
+		Name string `json:"name" gorm:"not null;"`
+	}
+	var videos []model.PropertyVideo
+	err = query.Offset(form.Offset).Limit(form.Limit).Scan(&videos).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	//查询出tag
+	var ids []int
+	for _, v := range videos {
+		ids = append(ids, v.ID)
+	}
+	var tagData []TagData
+	err = global.App.DB.Table(model.TablePropertyImageTag).
+		LeftJoin(model.TablePropertyTag, "property_image_tag.tag_id = property_tag.id").
+		WhereIn("property_image_tag.image_id", ids).
+		Scan(&tagData).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	list := make([]struct {
+		model.PropertyVideo
+		Tag             []TagData `json:"tag"`
+		Download        string    `json:"download"`
+		HeadImgDownload string    `json:"headImgDownload"`
+	}, len(videos))
+
+	for k, v := range videos {
+		list[k].ID = v.ID
+		list[k].Gid = v.Gid
+		list[k].Pid = v.Pid
+		list[k].Name = v.Name
+		list[k].WxId = v.WxId
+		list[k].CreatedAt = v.CreatedAt
+		list[k].UpdatedAt = v.UpdatedAt
+		list[k].LocalPath = v.LocalPath
+		list[k].Resolution = v.Resolution
+		list[k].VideoType = v.VideoType
+		list[k].Download = GetDownload(v.LocalPath)
+
+		list[k].HeadImgPath = v.HeadImgPath
+
+		list[k].HeadImgDownload = GetDownload(v.HeadImgPath)
+
+		for _, tag := range tagData {
+			if v.ID == tag.VideoId {
+				list[k].Tag = append(list[k].Tag, tag)
+			}
+		}
+	}
+
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"list":  list,
+			"count": count,
+		},
+	})
+
+}
+
+func GetDownload(filPath string) string {
+
+	return localConfig.Get("app.appUrl") + "/download?filename=" + filPath
+}

+ 52 - 0
controller/game.go

@@ -0,0 +1,52 @@
+package controller
+
+import (
+	"designs/app/common/request"
+	"designs/app/common/response"
+	"designs/global"
+	"designs/model"
+	"github.com/gin-gonic/gin"
+)
+
+func GameList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Gid string `form:"gid" binding:""`
+		Pid string `form:"pid" binding:""`
+	}{})
+
+	query := global.App.DB.Table(model.TableGame)
+
+	if form.Gid != "" {
+		query = query.Where("gid", form.Gid)
+	}
+
+	if form.Pid != "" {
+		query = query.Where("pid", form.Pid)
+	}
+
+	var data []model.Game
+	err := query.Select("gid", "pid", "gameName").Scan(&data).Error
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	type game struct {
+		Pid      string `json:"pid" gorm:"column:pid; "`
+		Gid      string `json:"gid" gorm:"column:gid; "`
+		GameName string `json:"gameName" gorm:"column:gameName; "`
+	}
+
+	res := make(map[string][]game)
+	for _, v := range data {
+		res[v.Pid] = append(res[v.Pid], game{
+			Pid:      v.Pid,
+			Gid:      v.Gid,
+			GameName: v.GameName,
+		})
+	}
+
+	response.Success(c, gin.H{
+		"data": res,
+	})
+}

+ 422 - 0
controller/identity.go

@@ -0,0 +1,422 @@
+package controller
+
+import (
+	"designs/app/common/request"
+	"designs/app/common/response"
+	"designs/global"
+	"designs/model"
+	"designs/service"
+	"designs/utils"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"strings"
+	"time"
+)
+
+var permissionToInterface = map[string][]string{
+	"1": {"/property/imageList"},
+	"2": {"/property/imageSet", "/property/imageDelete", "/property/upload", "/property/updateImageTags"},
+	"3": {"/property/videoSet", "/property/videoDelete", "/property/updateVideoTags"},
+	"4": {"/property/videoList"},
+}
+
+var permissions = map[int]string{
+	1:   "图片查看",
+	2:   "图片编辑",
+	3:   "视频编辑",
+	4:   "视频查看",
+	999: "超级权限",
+}
+
+func CreateAdminUser(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Account  string `json:"account" binding:"required"`
+		Name     string `json:"name" binding:"required"`
+		Identity int    `json:"identity" binding:"required"`
+	}{})
+
+	if form.Account == "admin" {
+		response.Fail(c, 1001, "admin名称不可用")
+		return
+	}
+
+	newPass := utils.RandomStringFast(10)
+
+	now := model.XTime{
+		Time: time.Now(),
+	}
+	newAdmin := model.AdminUser{
+		Account:    form.Account,
+		Name:       form.Name,
+		IdentityId: form.Identity,
+		Password:   newPass,
+		CreatedAt:  now,
+		UpdatedAt:  now,
+	}
+	err := global.App.DB.Table(model.TableAdminUser).Create(&newAdmin).Error
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	//记录日志
+	userId := c.GetInt("userId")
+	err = service.SetActionLog("新增", userId, "管理员", newAdmin)
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"password": newPass,
+			"account":  newAdmin.Account,
+		},
+	})
+}
+
+func AdminUserList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Limit    int    `form:"limit" json:"limit" binding:"required"`
+		Offset   int    `form:"offset" json:"offset" binding:""`
+		Search   string `form:"search" json:"search" binding:""`
+		Identity []int  `form:"identity" json:"identity" binding:""`
+		//Prop   string `form:"prop" json:"prop" binding:""`
+		//Order  string `form:"order" json:"order" binding:""`
+	}{})
+
+	var res []struct {
+		model.AdminUser
+		IdentityName string `json:"identityName" gorm:"column:identityName;"`
+	}
+	var count int64
+	query := global.App.DB.Table(model.TableAdminUser).LeftJoin(model.TableAdminIdentity, fmt.Sprintf("%s.id = %s.identityId", model.TableAdminIdentity, model.TableAdminUser))
+	if form.Search != "" {
+		query = query.Where(model.TableAdminUser+".name", "like", "%"+form.Search+"%")
+	}
+	if len(form.Identity) != 0 {
+		query = query.WhereIn(model.TableAdminUser+".identityId", form.Identity)
+	}
+
+	err := query.Count(&count).Error
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	err = query.
+		Order("id desc").
+		Offset(form.Offset).
+		Select("admin_user.*", "identityName").
+		Limit(form.Limit).Scan(&res).Error
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"list":  res,
+			"count": count,
+		},
+	})
+}
+
+func AdminList(c *gin.Context) {
+	var res []struct {
+		ID   int    `json:"id" gorm:"not null;"`
+		Name string `json:"name" gorm:"not null;"`
+	}
+
+	query := global.App.DB.Table(model.TableAdminUser)
+	err := query.
+		Order("id desc").
+		Select("id", "name").
+		Scan(&res).Error
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": res,
+	})
+}
+
+func DeleteAdminUser(c *gin.Context) {
+	form := request.Check(c, &struct {
+		UserId int `form:"userId" json:"userId" binding:"required"`
+	}{})
+
+	var d interface{}
+	err := global.App.DB.Table(model.TableAdminUser).Where("id", form.UserId).Delete(d).Error
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	//记录操作
+	//记录日志
+	userId := c.GetInt("userId")
+	err = service.SetActionLog("删除", userId, "管理员", map[string]interface{}{
+		"userId": userId,
+	})
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+
+}
+
+func UpdateUserPassword(c *gin.Context) {
+	form := request.Check(c, &struct {
+		UserId  int    `form:"userId" json:"userId" binding:"required"`
+		NewPass string `form:"newPass" json:"newPass" binding:"required|max=20,min=6"`
+	}{})
+
+	var res model.AdminUser
+	global.App.DB.Table(model.TableAdminUser).Where("id", form.UserId).Select("id").First(&res)
+	if res.ID == 0 {
+		response.Fail(c, 1001, "用户不存在")
+		return
+	}
+	now := time.Now()
+
+	err := global.App.DB.Table(model.TableAdminUser).Where("id", form.UserId).Updates(map[string]interface{}{
+		"password":  form.NewPass,
+		"updatedAt": now,
+	}).Error
+	if err != nil {
+		response.Fail(c, 1002, err.Error())
+		return
+	}
+
+	//记录日志
+	userId := c.GetInt("userId")
+	err = service.SetActionLog("修改", userId, "管理员", map[string]interface{}{
+		"userId": userId,
+	})
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}
+
+func SetAdminUserPermission(c *gin.Context) {
+	form := request.Check(c, &struct {
+		UserId   int `form:"userId" json:"userId" binding:"required"`
+		Identity int `json:"identity" binding:"required"`
+	}{})
+
+	var res model.AdminUser
+	global.App.DB.Table(model.TableAdminUser).Where("id", form.UserId).Select("id").First(&res)
+	if res.ID == 0 {
+		response.Fail(c, 1001, "用户不存在")
+		return
+	}
+	now := time.Now()
+
+	err := global.App.DB.Table(model.TableAdminUser).Where("id", form.UserId).Updates(map[string]interface{}{
+		"identity":  form.Identity,
+		"updatedAt": now,
+	}).Error
+	if err != nil {
+		response.Fail(c, 1002, err.Error())
+		return
+	}
+
+	//记录日志
+	userId := c.GetInt("userId")
+	err = service.SetActionLog("修改", userId, "管理员", map[string]interface{}{
+		"userId": userId,
+	})
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+
+}
+
+func SetIdentity(c *gin.Context) {
+	form := request.Check(c, &struct {
+		IdentityName string `form:"identityName" json:"identityName" binding:"required"`
+		Permissions  []int  `form:"permissions" json:"permissions" binding:""`
+	}{})
+
+	var data model.AdminIdentity
+	global.App.DB.Table(model.TableAdminIdentity).Where("identityName", form.IdentityName).Select("id").First(&data)
+
+	if data.ID != 0 {
+		response.Fail(c, 1001, "identityName 重复")
+		return
+	}
+
+	now := model.XTime{
+		Time: time.Now(),
+	}
+	data = model.AdminIdentity{
+		IdentityName: form.IdentityName,
+		Permissions:  utils.ArrayToString(form.Permissions),
+		CreatedAt:    now,
+		UpdatedAt:    now,
+	}
+
+	err := global.App.DB.Table(model.TableAdminIdentity).Create(&data).Error
+	if err != nil {
+		response.Fail(c, 1001, "AdminIdentity 写入数据失败"+err.Error())
+		return
+	}
+
+	//记录日志
+	userId := c.GetInt("userId")
+	err = service.SetActionLog("新增", userId, "身份", data)
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}
+
+func DeleteIdentity(c *gin.Context) {
+	form := request.Check(c, &struct {
+		IdentityId int `form:"identityId" json:"identityId" binding:"required"`
+	}{})
+
+	//如果这个身份已经有用户关联,无法删除
+	var res model.AdminUser
+	global.App.DB.Table(model.TableAdminUser).Where("identityId", form.IdentityId).Select("id").First(&res)
+	if res.ID != 0 {
+		response.Fail(c, 1001, "该身份已经有用户使用,无法删除")
+		return
+	}
+	var d interface{}
+	err := global.App.DB.Table(model.TableAdminIdentity).Where("id", form.IdentityId).Delete(d).Error
+	if err != nil {
+		response.Fail(c, 1001, "AdminIdentity 写入数据失败")
+		return
+	}
+
+	//记录日志
+	userId := c.GetInt("userId")
+	err = service.SetActionLog("删除", userId, "身份", map[string]interface{}{
+		"identityId": form.IdentityId,
+	})
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+	response.Success(c, gin.H{})
+}
+
+func GetIdentityList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Limit  int    `form:"limit" json:"limit" binding:"required"`
+		Offset int    `form:"offset" json:"offset" binding:""`
+		Search string `form:"search" json:"search" binding:""`
+	}{})
+
+	var count int64
+	query := global.App.DB.Table(model.TableAdminIdentity)
+
+	if form.Search != "" {
+		query = query.Where("identityName", "like", "%"+form.Search+"%")
+	}
+	err := query.Count(&count).Error
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	var adminIdentity []struct {
+		model.AdminIdentity
+		PermissionList *[]string `json:"permissions"`
+	}
+	err = query.Order("id desc").Offset(form.Offset).Limit(form.Limit).Scan(&adminIdentity).Error
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	//var res
+	for k, v := range adminIdentity {
+		per := strings.Split(v.Permissions, ",")
+
+		adminIdentity[k].PermissionList = &per
+	}
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"list":  adminIdentity,
+			"count": count,
+		},
+	})
+
+}
+
+func GetIdentity(c *gin.Context) {
+	query := global.App.DB.Table(model.TableAdminIdentity)
+
+	var adminIdentity []struct {
+		ID           int    `json:"id" gorm:"not null;"`
+		IdentityName string `json:"identityName" gorm:"column:identityName;"`
+	}
+	err := query.Order("id desc").Select("id", "identityName").Scan(&adminIdentity).Error
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": adminIdentity,
+	})
+
+}
+
+func PermissionList(c *gin.Context) {
+	response.Success(c, gin.H{
+		"data": permissions,
+	})
+}
+
+func UpdateIdentityPermission(c *gin.Context) {
+	form := request.Check(c, &struct {
+		IdentityId  int   `form:"identityId" json:"identityId" binding:"required"`
+		Permissions []int `form:"permissions" json:"permissions" binding:""`
+	}{})
+
+	var data model.AdminIdentity
+	global.App.DB.Table(model.TableAdminIdentity).Where("id", form.IdentityId).Select("id").First(&data)
+
+	if data.ID == 0 {
+		response.Fail(c, 1001, "identity不存在")
+		return
+	}
+
+	err := global.App.DB.Table(model.TableAdminIdentity).Where("id", form.IdentityId).Updates(map[string]interface{}{
+		"permissions": utils.ArrayToString(form.Permissions),
+		"updatedAt":   time.Now(),
+	}).Error
+	if err != nil {
+		response.Fail(c, 1002, err.Error())
+		return
+	}
+
+	//记录日志
+	userId := c.GetInt("userId")
+	err = service.SetActionLog("删除", userId, "权限", map[string]interface{}{
+		"permissions": utils.ArrayToString(form.Permissions),
+		"identityId":  form.IdentityId,
+	})
+	if err != nil {
+		response.Fail(c, 500, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}

+ 74 - 0
controller/log.go

@@ -0,0 +1,74 @@
+package controller
+
+import (
+	"designs/app/common/request"
+	"designs/app/common/response"
+	"designs/global"
+	"designs/model"
+	"github.com/gin-gonic/gin"
+)
+
+func GetActionLogHeader(c *gin.Context) {
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"action": []string{
+				"新增", "修改", "删除",
+			},
+			"object": []string{
+				"图片", "视频",
+			},
+		},
+	})
+}
+
+func ActionLogList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Offset int    `form:"offset" json:"offset" binding:""`
+		Limit  int    `form:"limit" json:"limit" binding:"required"`
+		Action string `form:"action" json:"action" binding:""`
+		Object string `form:"object" json:"object" binding:""`
+		Order  string `form:"order" json:"order" binding:""`
+		UserId int    `form:"user_id" json:"user_id" binding:""`
+	}{})
+
+	query := global.App.DB.Table(model.TableActionLog)
+
+	if form.Action != "" {
+		query = query.Where("action", form.Action)
+	}
+	if form.Object != "" {
+		query = query.Where("object", form.Object)
+	}
+	if form.UserId != 0 {
+		query = query.Where("user_id", form.UserId)
+	}
+
+	var count int64
+	err := query.Count(&count).Error
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	var data []struct {
+		model.ActionLog
+		Name string `json:"name" gorm:"column:name"`
+	}
+
+	err = query.Offset(form.Offset).Limit(form.Limit).
+		Join(model.TableAdminUser, "admin_user.id", "=", "action_log.user_id").
+		//Select("action_log.*", "admin_user.name as user_name").
+		Scan(&data).Error
+
+	if err != nil {
+		response.Fail(c, 1002, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"list":  data,
+			"total": count,
+		},
+	})
+}

+ 242 - 0
controller/user.go

@@ -0,0 +1,242 @@
+package controller
+
+import (
+	"context"
+	"designs/app/common/request"
+	"designs/app/common/response"
+	"designs/common"
+	"designs/config"
+	"designs/global"
+	"designs/model"
+	"designs/utils"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+type GameConfig struct {
+	Gid       string `form:"gid" json:"gid" binding:"required"`             //游戏id
+	Pid       string `form:"pid" json:"pid" binding:""`                     //游戏pid
+	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"` //游戏配置密钥
+	TtTplId   string `form:"ttTplId" json:"ttTplId" binding:""`
+	WxTplId   string `form:"wxTplId" json:"wxTplId" binding:""`
+}
+
+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"`
+	}{})
+
+	//验证
+	var admin model.AdminUser
+	global.App.DB.Table(model.TableAdminUser).Where("account", form.UserName).First(&admin)
+	if admin.Password != form.Password {
+		response.Fail(c, 1003, "账户密码错误")
+		return
+	}
+
+	//获取权限
+	var identity model.AdminIdentity
+	global.App.DB.Table(model.TableAdminIdentity).Where("id", admin.IdentityId).First(&identity)
+
+	var permission []string
+	per := strings.Split(identity.Permissions, ",")
+	for _, v := range per {
+		into, _ := strconv.Atoi(v)
+		permission = append(permission, permissions[into])
+	}
+
+	//获取token
+	token, err := common.GetJwtToken(form.UserName, admin.ID, 0)
+	//刷新refreshtoken
+	refresh, _ := common.GetJwtToken(form.UserName, admin.ID, 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(),
+		"name":         admin.Name,
+		"permission":   permission,
+	})
+}
+
+/* 刷新token */
+func RefreshToken(c *gin.Context) {
+	userName := c.GetString("userName")
+	userId := c.GetInt("userId")
+	//获取token
+	token, err0 := common.GetJwtToken(userName, userId, 0)
+	if err0 != nil {
+		global.App.Log.Info("RefreshToken err")
+		response.Fail(c, 1003, "RefreshToken err")
+		return
+	}
+
+	data := map[string]interface{}{
+		"token": token,
+	}
+	//返回数据
+	response.Success(c, gin.H{
+		"data": data,
+	})
+
+}
+
+/* 获取时间 */
+func GetSysTime(c *gin.Context) {
+	time := time.Now().Unix()
+	common.RetJson(0, "获取系统时间", time, c)
+}
+
+type User struct {
+	UserId   string `json:"userId"`
+	Gid      string `json:"gid"`
+	Pf       string `json:"pf"`
+	NickName string `json:"nickName"`
+	Head     string `json:"head"`
+	OpenId   string `json:"openId"`
+	InBlack  bool   `json:"inBlack"`
+	Option   string `json:"option"`
+}
+
+func UserList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Gid    string `form:"gid" json:"gid" binding:""`
+		Pf     string `form:"pf" json:"pf" binding:""`
+		Offset int    `form:"offset" json:"offset" binding:""`
+		Limit  int    `form:"limit" json:"limit" binding:"required"`
+	}{})
+
+	//读取表
+	userTotalKey := config.Get("app.user_total")
+	if form.Gid != "" && form.Pf != "" {
+		userTotalKey = config.Get("app.user_total") + ":" + form.Gid + ":" + form.Pf
+	}
+
+	data, err := global.App.Redis.ZRevRangeWithScores(context.Background(), userTotalKey, int64(form.Offset), int64(form.Offset+form.Limit)-1).Result()
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	count, err := global.App.Redis.ZCard(context.Background(), userTotalKey).Result()
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	//读取黑名单
+	blackListKey := config.Get("app.black_list_table")
+	black, err := global.App.Redis.ZRange(context.Background(), blackListKey, 0, -1).Result()
+	if err != nil {
+		response.Fail(c, 1003, "读取黑名单列表失败"+err.Error())
+		return
+	}
+
+	var res []User
+	for _, val := range data {
+		userKey := val.Member.(string)
+		userData, err := global.App.Redis.HGetAll(context.Background(), userKey).Result()
+		if err != nil {
+			global.App.Log.Error("GetUserData err")
+			response.Fail(c, 1003, "GetUserData err"+err.Error())
+			return
+		}
+
+		//查看是否在黑名单
+		var inBlack bool
+		if utils.InArray(userKey, black) {
+			inBlack = true
+		}
+
+		userMsg := strings.Split(userKey, ":")
+		openId := userMsg[len(userMsg)-1]
+		gid := userMsg[0]
+		pf := userMsg[1]
+
+		//查看是否加了权限
+		optionKey := config.Get("app.option_key") + gid + ":" + pf + ":" + openId
+		option, err := global.App.Redis.HGetAll(context.Background(), optionKey).Result()
+		if err != nil {
+			response.Fail(c, 1003, err.Error())
+			return
+		}
+
+		res = append(res, User{
+			UserId:   userData["userId"],
+			Gid:      userData["gid"],
+			Pf:       userData["pf"],
+			NickName: userData["nickName"],
+			Head:     userData["head"],
+			OpenId:   openId,
+			InBlack:  inBlack,
+			Option:   option["option"],
+		})
+	}
+
+	response.Success(c, gin.H{
+		"data":  res,
+		"count": count,
+	})
+}
+
+func DownloadFile(c *gin.Context) {
+	filename := c.Query("filename")
+
+	// 验证文件是否存在
+	if _, err := os.Stat(filename); os.IsNotExist(err) {
+		c.JSON(404, gin.H{"error": "文件不存在"})
+		return
+	}
+
+	// 设置响应头,触发浏览器下载行为
+	c.Header("Content-Disposition", "attachment; filename="+filename)
+	//c.Header("Content-Disposition", "inline; filename="+filename) // 或直接省略此行
+
+	c.Header("Content-Type", "application/octet-stream")
+
+	c.File(filename)
+}

+ 65 - 0
global/app.go

@@ -0,0 +1,65 @@
+package global
+
+import (
+	"designs/utils"
+	"github.com/go-redis/redis/v8"
+	"go.mongodb.org/mongo-driver/v2/mongo"
+	"go.uber.org/zap"
+	"io"
+)
+
+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
+	MongoDB   *mongo.Client
+	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()
+}

+ 76 - 0
go.mod

@@ -0,0 +1,76 @@
+module designs
+
+go 1.23.0
+
+toolchain go1.23.3
+
+require (
+	github.com/antihax/optional v1.0.0
+	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/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
+	github.com/tencentad/marketing-api-go-sdk v1.7.65
+	go.mongodb.org/mongo-driver/v2 v2.0.0-beta2
+	go.uber.org/zap v1.27.0
+	golang.org/x/crypto v0.27.0
+	gorm.io/driver/mysql v1.5.7
+	gorm.io/gorm v1.25.11
+)
+
+require (
+	github.com/goccy/go-json v0.10.3 // indirect
+	github.com/golang/protobuf v1.5.0 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
+	google.golang.org/appengine v1.4.0 // indirect
+)
+
+require (
+	filippo.io/edwards25519 v1.1.0 // indirect
+	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/go-sql-driver/mysql v1.8.1 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/jonboulle/clockwork v0.4.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/compress v1.13.6 // 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/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // 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
+	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
+	github.com/xdg-go/scram v1.1.2 // indirect
+	github.com/xdg-go/stringprep v1.0.4 // indirect
+	github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // 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/sync v0.8.0 // indirect
+	golang.org/x/sys v0.25.0 // indirect
+	golang.org/x/text v0.18.0 // indirect
+	golang.org/x/time v0.11.0 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 208 - 0
go.sum

@@ -0,0 +1,208 @@
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+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/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
+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/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+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/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+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/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+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/tencentad/marketing-api-go-sdk v1.7.65 h1:LmBcmHL65zDZIe6ZRLPqSn0SQSNU7GjXKiTFnj2Znv4=
+github.com/tencentad/marketing-api-go-sdk v1.7.65/go.mod h1:DMWvwzHv/noUtVL6szGXO6O/3OPSMzrkOPyHDgbgg3w=
+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=
+github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
+github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
+github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
+github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.mongodb.org/mongo-driver/v2 v2.0.0-beta2 h1:PRtbRKwblE8ZfI8qOhofcjn9y8CmKZI7trS5vDMeJX0=
+go.mongodb.org/mongo-driver/v2 v2.0.0-beta2/go.mod h1:UGLb3ZgEzaY0cCbJpH9UFt9B6gEXiTPzsnJS38nBeoU=
+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.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
+golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+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/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
+golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+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=
+gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
+gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
+gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
+gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 74 - 0
main.go

@@ -0,0 +1,74 @@
+package main
+
+import (
+	"designs/bootstrap"
+	"designs/global"
+	"designs/middleware"
+	"designs/utils"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"net/http"
+	"path/filepath"
+)
+
+func init() {
+	fmt.Printf("初始化")
+}
+
+func main() {
+	//创建一个服务
+	gin.SetMode(gin.DebugMode)
+
+	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())
+
+	//跨域
+	ginServer.Use(Cors())
+
+	// 初始化Redis
+	global.Init.InitRedisFunc = bootstrap.InitializeRedis
+	global.App.Redis = bootstrap.InitializeRedis()
+
+	// 初始化facade
+	global.InitFacade()
+	//服务器端口
+	//ginServer.Run(":" + config.Get("app.port")) /*默认是8080*/
+
+	// 初始化数据库
+	global.App.DB = &utils.WtDB{DB: bootstrap.InitializeDB()}
+	// 程序关闭前,释放数据库连接
+	defer func() {
+		if global.App.DB != nil {
+			db, _ := global.App.DB.DB.DB()
+			db.Close()
+		}
+	}()
+
+	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)
+		}
+	}
+}

+ 152 - 0
middleware/auth.go

@@ -0,0 +1,152 @@
+package middleware
+
+import (
+	"context"
+	"designs/common"
+	"designs/config"
+	"designs/global"
+	"designs/response"
+	"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)
+			response.Fail(c, 1003, "ruquest too max")
+			c.Abort()
+			return
+		}
+		token := c.GetHeader("Authorization")
+		//校验token
+		if token == "" {
+			response.Fail(c, -2, "Unauthorized")
+			c.Abort()
+			return
+		}
+		ok, userName, userId := isValidToken(token)
+		if !ok {
+			response.Fail(c, -1, "authorized invalid!")
+			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)
+		c.Set("userId", userId)
+		//如果校验通过
+
+	}
+}
+
+/* token是否有效 */
+func isValidToken(token string) (bool, string, int) {
+	userName, userId, tokenType := common.ParseJwtWithClaims(token)
+
+	//fmt.Printf("openid:%v,gid:%v,pf:%v,tokenType:%v", openid, gid, pf, tokenType)
+
+	if userName == "" {
+		return false, userName, userId
+	} else {
+		//登录tonken 类型
+		if tokenType == 0 {
+			return true, userName, userId
+		}
+		return false, userName, userId
+	}
+}
+
+/* 刷新token中间件 */
+func RefreshTokenAuthMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		token := c.GetHeader("Authorization")
+		//校验token
+		if token == "" {
+			response.Fail(c, -2, "Unauthorized")
+
+			c.Abort()
+			return
+		}
+		ok, userName, userId := isValidRefreshToken(token)
+		if !ok {
+			response.Fail(c, -1, "authorized invalid!")
+			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 {
+			response.Fail(c, 1001, "server error!")
+			c.Abort()
+			return
+		}
+		if count == 1 {
+			global.App.Redis.Expire(context.Background(), key, time.Minute).Result()
+		}
+		if count > config.GetInt64("app.api_limit_count") {
+			response.Fail(c, 1002, "too many requests!")
+			c.Abort()
+			return
+		}
+		//设置上下文数据
+		c.Set("userName", userName)
+		c.Set("userId", userId)
+
+		//如果校验通过
+		c.Next()
+	}
+}
+
+/* token是否有效 */
+func isValidRefreshToken(token string) (bool, string, int) {
+	userName, userId, tokenType := common.ParseJwtWithClaims(token)
+	//fmt.Printf("openid:%v,gid:%v", openid, gid)
+	if userName == "" {
+		return false, userName, userId
+	} else {
+		//登录刷新tonken 类型
+		if tokenType == 1 {
+			return true, userName, userId
+		}
+		return false, userName, userId
+	}
+}

+ 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)
+	}
+}

+ 61 - 0
model/identity.go

@@ -0,0 +1,61 @@
+package model
+
+const (
+	TableAdminUser       = "admin_user"
+	TableAdminIdentity   = "admin_identity"
+	TableAdminPermission = "admin_permission"
+	TableDataPermission  = "data_permission"
+
+	TablePropertyImage    = "property_image"
+	TablePropertyVideo    = "property_video"
+	TablePropertyImageTag = "property_image_tag"
+	TablePropertyVideoTag = "property_video_tag"
+	TablePropertyTag      = "property_tag"
+
+	TableActionLog = "action_log"
+	TableGame      = "game"
+)
+
+type AdminUser struct {
+	ID         int    `json:"id" gorm:"not null;"`
+	Name       string `json:"name" gorm:"not null;"`
+	Account    string `json:"account" gorm:"not null;"`
+	Password   string `json:"password" gorm:"not null;"`
+	IdentityId int    `json:"identityId" gorm:"column:identityId;"`
+	CreatedAt  XTime  `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt  XTime  `json:"updatedAt" gorm:"column:updatedAt;"`
+}
+
+type AdminIdentity struct {
+	ID           int    `json:"id" gorm:"not null;"`
+	IdentityName string `json:"identityName" gorm:"column:identityName;"`
+	Permissions  string `json:"permissions" gorm:"column:permissions"`
+
+	CreatedAt XTime `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt XTime `json:"updatedAt" gorm:"column:updatedAt;"`
+}
+
+type AdminPermission struct {
+	ID             int    `json:"id" gorm:"not null;"`
+	PermissionName string `json:"permissionName" gorm:"column:permissionName;"`
+	CreatedAt      XTime  `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt      XTime  `json:"updatedAt" gorm:"column:updatedAt;"`
+}
+
+type DataPermission struct {
+	ID                int    `json:"id" gorm:"not null;"`
+	AdminPermissionId int    `json:"adminPermissionId" gorm:"column:adminPermissionId;"`
+	AdminIdentityId   int    `json:"adminIdentityId" gorm:"column:adminIdentityId;"`
+	DataList          string `json:"dataList" gorm:"column:dataList"`
+	CreatedAt         XTime  `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt         XTime  `json:"updatedAt" gorm:"column:updatedAt;"`
+}
+
+type ActionLog struct {
+	ID        int    `json:"id" gorm:"not null;"`
+	Action    string `json:"action" gorm:"column:action"`
+	UserId    int    `json:"user_id" gorm:"column:user_id"`
+	Object    string `json:"object" gorm:"column:object"`
+	Data      string `json:"data" gorm:"column:data"`
+	CreatedAt XTime  `json:"createdAt" gorm:"column:createdAt;"`
+}

+ 56 - 0
model/property.go

@@ -0,0 +1,56 @@
+package model
+
+type PropertyImage struct {
+	ID             int    `json:"id" gorm:"not null;"`
+	Name           string `json:"name" gorm:"not null;"`
+	LocalPath      string `json:"local_path" gorm:"not null;"`
+	Gid            string `json:"gid" gorm:"not null; column:gid;"`
+	Pid            string `json:"pid" gorm:"not null; column:pid;"`
+	WxId           string `json:"wx_id" gorm:"not null; column:wx_id;"`
+	ImageWidth     int    `json:"image_width"`
+	ImageHeight    int    `json:"image_height"`
+	ImageFileSize  int    `json:"image_file_size"`
+	ImageType      string `json:"image_type"`
+	ImageSignature string `json:"image_signature"`
+	IsImg9         int    `json:"isImg9" gorm:"not null; column:is_img9;"`
+	Resolution     string `json:"resolution" gorm:"not null; column:resolution;"`
+
+	CreatedAt XTime `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt XTime `json:"updatedAt" gorm:"column:updatedAt;"`
+}
+
+type PropertyTag struct {
+	ID        int    `json:"id" gorm:"not null;"`
+	Name      string `json:"name" gorm:"not null;"`
+	CreatedAt XTime  `json:"createdAt" gorm:"column:createdAt;"`
+}
+
+func (*PropertyTag) TableName() string {
+	return "property_tag"
+}
+
+type PropertyImageTag struct {
+	ImageId int `json:"image_id" gorm:"not null;"`
+	TagId   int `json:"tag_id" gorm:"not null;"`
+}
+
+type PropertyVideo struct {
+	ID             int    `json:"id" gorm:"not null;"`
+	Name           string `json:"name" gorm:"not null;"`
+	Gid            string `json:"gid" gorm:"not null; column:gid;"`
+	Pid            string `json:"pid" gorm:"not null; column:pid;"`
+	WxId           int    `json:"wx_id" gorm:"not null; column:wx_id;"`
+	VideoSignature string `json:"video_signature" gorm:"not null; column:video_signature;"`
+	LocalPath      string `json:"local_path" gorm:"not null;"`
+	HeadImgPath    string `json:"head_img_path" gorm:"not null;"`
+	VideoType      int    `json:"video_type" gorm:"not null; column:video_type;"`
+	Resolution     string `json:"resolution" gorm:"not null; column:resolution;"`
+
+	CreatedAt XTime `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt XTime `json:"updatedAt" gorm:"column:updatedAt;"`
+}
+
+type PropertyVideoTag struct {
+	VideoId int `json:"video_id" gorm:"not null;"`
+	TagId   int `json:"tag_id" gorm:"not null;"`
+}

+ 134 - 0
model/user.go

@@ -0,0 +1,134 @@
+package model
+
+import (
+	"database/sql/driver"
+	"fmt"
+	"time"
+)
+
+type Users struct {
+	ID     int    `json:"id" gorm:"not null;"`
+	OpenId string `json:"openId" gorm:"not null;column:openId;"`
+	Pf     string `json:"pf" gorm:"not null;"`
+	UserId int    `json:"userId" gorm:"not null;column:userId;"`
+
+	CreatedAt time.Time `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt time.Time `json:"updatedAt" gorm:"column:updatedAt;"`
+}
+
+type User struct {
+	ID        int       `json:"id" gorm:"not null;"`
+	Pf        string    `json:"pf" gorm:"not null;"`
+	Gid       string    `json:"gid" gorm:"not null;"`
+	UserId    int       `json:"userId" gorm:"not null;column:userId;"`
+	OpenId    string    `json:"openId" gorm:"not null;column:openId;"`
+	CreatedAt time.Time `json:"createdAt" gorm:"column:createdAt;"`
+}
+
+type UserLogin struct {
+	ID        int       `json:"id" gorm:"not null;"`
+	Pf        string    `json:"pf" gorm:"not null;"`
+	Gid       string    `json:"gid" gorm:"not null;"`
+	UserId    int       `json:"userId" gorm:"not null;column:userId;"`
+	LoginTime time.Time `json:"loginTime" gorm:"column:loginTime;"`
+}
+
+type Admin struct {
+	ID        int       `json:"id" gorm:"not null;"`
+	Account   string    `json:"account" gorm:"not null;"`
+	Password  string    `json:"password" gorm:"not null;"`
+	Name      string    `json:"name" gorm:"not null;"`
+	CreatedAt time.Time `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt time.Time `json:"updatedAt" gorm:"column:updatedAt;"`
+}
+
+type UserSeeAds struct {
+	ID        int    `json:"id" gorm:"not null;"`
+	Pf        string `json:"pf" gorm:"not null;"`
+	Gid       string `json:"gid" gorm:"not null;"`
+	UserId    int    `json:"userId" gorm:"not null;column:userId;"`
+	Date      string `json:"date" gorm:"not null;"`
+	CreatedAt XTime  `json:"createdAt" gorm:"column:createdAt;"`
+	StartTime XTime  `json:"startTime" gorm:"column:startTime;"`
+	AdsId     string `json:"adsId" gorm:"not null;column:adsId;"`
+	AdsType   string `json:"adsType" gorm:"not null;column:adsType;"`
+	AdsScene  string `json:"adsScene" gorm:"not null;column:adsScene;"`
+	AdsState  int    `json:"adsState" gorm:"not null;column:adsState;"`
+}
+
+// 1. 创建 time.Time 类型的副本 XTime;
+type XTime struct {
+	time.Time
+}
+
+// 2. 为 Xtime 重写 MarshaJSON 方法,在此方法中实现自定义格式的转换;
+func (t XTime) MarshalJSON() ([]byte, error) {
+	output := fmt.Sprintf("\"%s\"", t.Format("2006-01-02 15:04:05"))
+	return []byte(output), nil
+}
+
+// 3. 为 Xtime 实现 Value 方法,写入数据库时会调用该方法将自定义时间类型转换并写入数据库;
+func (t XTime) Value() (driver.Value, error) {
+	var zeroTime time.Time
+	if t.Time.UnixNano() == zeroTime.UnixNano() {
+		return nil, nil
+	}
+	return t.Time, nil
+}
+
+// 4. 为 Xtime 实现 Scan 方法,读取数据库时会调用该方法将时间数据转换成自定义时间类型;
+func (t *XTime) Scan(v interface{}) error {
+	value, ok := v.(time.Time)
+	if ok {
+		*t = XTime{Time: value}
+		return nil
+	}
+	return fmt.Errorf("can not convert %v to timestamp", v)
+}
+
+type GameAction struct {
+	ID         int    `json:"id" gorm:"not null;"`
+	Gid        string `json:"gid" gorm:"not null;"`
+	ActionId   string `json:"actionId" gorm:"not null;column:actionId;"`
+	ActionName string `json:"actionName" gorm:"not null;column:actionName;"`
+	Status     int    `json:"status" gorm:"not null;column:status;"`
+	Remark     string `json:"remark" gorm:"not null;column:remark;"`
+	CreatedAt  XTime  `json:"createdAt" gorm:"column:createdAt;type:date;"`
+	UpdatedAt  XTime  `json:"updatedAt" gorm:"column:updatedAt;type:date;"`
+}
+
+type GameActionOption struct {
+	ID         int    `json:"id" gorm:"not null;"`
+	ActionId   int    `json:"actionId" gorm:"not null;column:actionId;"`
+	OptionId   string `json:"optionId" gorm:"not null;column:optionId;"`
+	OptionName string `json:"optionName" gorm:"not null;column:optionName;"`
+	OptionType string `json:"optionType" gorm:"not null;column:optionType;"`
+	Status     int    `json:"status" gorm:"not null;column:status;"`
+	CreatedAt  XTime  `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt  XTime  `json:"updatedAt" gorm:"column:updatedAt;"`
+}
+
+type UserAction struct {
+	ID        int    `json:"id" gorm:"not null;"`
+	Pf        string `json:"pf" gorm:"not null;"`
+	Gid       string `json:"gid" gorm:"not null;"`
+	ActionId  string `json:"actionId" gorm:"not null;column:actionId;"`
+	UserId    int    `json:"userId" gorm:"not null;column:userId;"`
+	CreatedAt XTime  `json:"createdAt" gorm:"column:createdAt;type:date;"`
+	Data      string `json:"data" gorm:"not null;column:data;"`
+}
+
+type Game struct {
+	Id        int    `json:"id" gorm:"primary_key"`
+	Pid       string `json:"pid" gorm:"column:pid; "`
+	Gid       string `json:"gid" gorm:"column:gid; "`
+	GameName  string `json:"gameName" gorm:"column:gameName; "`
+	WxAppid   string `json:"wxAppid" gorm:"column:wxAppid; "`
+	WxSecret  string `json:"wxSecret" gorm:"column:wxSecret; "`
+	TtAppid   string `json:"ttAppid" gorm:"column:ttAppid; "`
+	TtSecret  string `json:"ttSecret" gorm:"column:ttSecret; "`
+	TtTplId   string `json:"ttTplId" gorm:"column:ttTplId; "`
+	WxTplId   string `json:"wxTplId" gorm:"column:wxTplId; "`
+	CreatedAt XTime  `json:"createdAt" gorm:"column:createdAt; "`
+	UpdatedAt XTime  `json:"updatedAt" gorm:"column:updatedAt; "`
+}

+ 1 - 0
model/video.go

@@ -0,0 +1 @@
+package model

+ 124 - 0
response/response.go

@@ -0,0 +1,124 @@
+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
+	data["msg"] = ""
+
+	//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()
+}

+ 70 - 0
route/api.go

@@ -0,0 +1,70 @@
+package route
+
+import (
+	"designs/controller"
+	"designs/middleware"
+	"github.com/gin-gonic/gin"
+)
+
+/* 跨域 */
+
+func SetApiGroupRoutes(router *gin.RouterGroup) {
+	// v1 版本
+	router.POST("/user/login", controller.Login)                                                        //游戏登录
+	router.POST("/user/refreshToken", middleware.RefreshTokenAuthMiddleware(), controller.RefreshToken) //token刷新
+
+	router.GET("/download", controller.DownloadFile) //文件下载
+
+	GroupAdmin := router.Group("")
+	GroupAdmin.Use(middleware.TokenAuthMiddleware()).Use()
+	{
+		//权限管理模块
+		GroupAdmin.POST("/admin/createAdminUser", controller.CreateAdminUser)
+		GroupAdmin.POST("/admin/adminUserList", controller.AdminUserList)
+		GroupAdmin.POST("/admin/adminList", controller.AdminList)
+		GroupAdmin.POST("/admin/deleteAdminUser", controller.DeleteAdminUser)
+		GroupAdmin.POST("/admin/setAdminUserPermission", controller.SetAdminUserPermission)
+
+		GroupAdmin.POST("/admin/setIdentity", controller.SetIdentity)
+		GroupAdmin.POST("/admin/permissionList", controller.PermissionList)
+		GroupAdmin.POST("/admin/deleteIdentity", controller.DeleteIdentity)
+		GroupAdmin.POST("/admin/getIdentityList", controller.GetIdentityList)
+		GroupAdmin.POST("/admin/getIdentity", controller.GetIdentity)
+		GroupAdmin.POST("/admin/updateIdentityPermission", controller.UpdateIdentityPermission)
+		GroupAdmin.POST("/admin/updateUserPassword", controller.UpdateUserPassword)
+
+		//日志管理
+		GroupAdmin.POST("/admin/actionLogList", controller.ActionLogList)
+		GroupAdmin.POST("/admin/getActionLogHeader", controller.GetActionLogHeader)
+
+	}
+
+	GroupProperty := router.Group("")
+	GroupProperty.Use(middleware.TokenAuthMiddleware()).Use()
+	{
+		//资产管理模块
+
+		//图片管理
+		GroupProperty.POST("/property/imageSet", controller.ImageSet)
+		GroupProperty.POST("/property/imageList", controller.ImageList)
+		GroupProperty.POST("/property/getImageListHead", controller.GetImageListHead)
+		GroupProperty.POST("/property/imageDelete", controller.ImageDelete)
+		GroupProperty.POST("/property/updateImageTags", controller.UpdateImageTags)
+
+		//tag 管理
+		GroupProperty.POST("/property/setTag", controller.SetTag)
+		GroupProperty.POST("/property/deleteTag", controller.DeleteTag)
+		GroupProperty.POST("/property/getTags", controller.GetTags)
+
+		//视频管理
+		GroupProperty.POST("/property/videoSet", controller.VideoSet)
+		GroupProperty.POST("/property/videoDelete", controller.VideoDelete)
+		GroupProperty.POST("/property/updateVideoTags", controller.UpdateVideoTags)
+		GroupProperty.POST("/property/getVideoListHead", controller.GetVideoListHead)
+		GroupProperty.POST("/property/videoList", controller.VideoList)
+
+		GroupProperty.POST("/property/gameList", controller.GameList)
+		GroupProperty.POST("/property/upload", controller.Upload)
+	}
+
+}

+ 27 - 0
service/actionLog.go

@@ -0,0 +1,27 @@
+package service
+
+import (
+	"designs/global"
+	"designs/model"
+	"encoding/json"
+	"time"
+)
+
+func SetActionLog(action string, userId int, object string, data interface{}) error {
+
+	dataString, _ := json.Marshal(data)
+
+	err := global.App.DB.Table(model.TableActionLog).Create(&model.ActionLog{
+		Action:    action,
+		UserId:    userId,
+		Object:    object,
+		Data:      string(dataString),
+		CreatedAt: model.XTime{Time: time.Now()},
+	}).Error
+	if err != nil {
+		global.App.Log.Error(err.Error())
+		return err
+	}
+
+	return nil
+}

+ 95 - 0
service/imgSlice.go

@@ -0,0 +1,95 @@
+package service
+
+import (
+	"fmt"
+	"github.com/nfnt/resize"
+	"github.com/pkg/errors"
+	"image"
+	"image/jpeg"
+	"image/png"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+func saveCroppedImage(img image.Image, imgName string, imgType string, row, col int) error {
+	// 生成文件名(例如 part_0_0.jpg)
+
+	saveDir := fmt.Sprintf("uploads/%s", time.Now().Format("2006-01-02"))
+
+	os.MkdirAll(saveDir, 0755)
+	filename := filepath.Join(saveDir, fmt.Sprintf("%s_%d_%d.%s", imgName, row, col, imgType))
+	outFile, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer outFile.Close()
+
+	// 可选:调整子图尺寸(使用 resize 库)
+	resizedImg := resize.Resize(300, 0, img, resize.Lanczos3)
+
+	// 编码为 JPEG 并保存[1](@ref)
+	if imgType == "jpeg" {
+		if err := jpeg.Encode(outFile, resizedImg, &jpeg.Options{Quality: 100}); err != nil {
+			return err
+		}
+	} else if imgType == "png" {
+		if err := png.Encode(outFile, resizedImg); err != nil {
+			return err
+		}
+	} else {
+		return errors.New("格式暂不支持")
+	}
+
+	return nil
+}
+
+func SliceImg(imagePath string, imgName string) error {
+	// 打开图片
+	file, err := os.Open(imagePath)
+	if err != nil {
+		return errors.New("打开图片失败")
+	}
+	defer file.Close()
+
+	// 解码图片
+	img, imgType, err := image.Decode(file)
+	if err != nil {
+		fmt.Println(err)
+		return errors.New("解码图片失败")
+	}
+
+	// 获取原始图片尺寸
+	bounds := img.Bounds()
+	width, height := bounds.Max.X, bounds.Max.Y
+
+	// 计算每个小块的尺寸(假设原图可被3整除)
+	blockWidth := width / 3
+	blockHeight := height / 3
+
+	// 循环切割九宫格
+	for row := 0; row < 3; row++ {
+		for col := 0; col < 3; col++ {
+			// 定义裁剪区域
+			rect := image.Rect(
+				col*blockWidth,      // 起始X
+				row*blockHeight,     // 起始Y
+				(col+1)*blockWidth,  // 结束X
+				(row+1)*blockHeight, // 结束Y
+			)
+
+			// 使用 SubImage 裁剪[3](@ref)
+			croppedImg := img.(interface {
+				SubImage(r image.Rectangle) image.Image
+			}).SubImage(rect)
+
+			// 保存文件(可选缩放)
+			err = saveCroppedImage(croppedImg, imgName, imgType, row, col)
+			if err != nil {
+				return errors.New("保存切片图片失败")
+			}
+		}
+	}
+
+	return nil
+}

+ 65 - 0
utils/array.go

@@ -0,0 +1,65 @@
+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 IntersectionRate(a, b []int) float64 {
+	setA := make(map[int]bool)
+	setB := make(map[int]bool)
+	intersection := make(map[int]bool)
+
+	// 将 a 切片中的元素添加到 setA
+	for _, value := range a {
+		setA[value] = true
+	}
+
+	// 将 b 切片中的元素添加到 setB,并检查是否也在 setA 中
+	for _, value := range b {
+		setB[value] = true
+		if setA[value] {
+			intersection[value] = true
+		}
+	}
+
+	// 计算交集大小
+	intersectionSize := len(intersection)
+
+	maxSize := len(a)
+	if maxSize == 0 {
+		return 0.0 // 避免除以零的情况
+	}
+	rate := float64(intersectionSize) / float64(maxSize)
+
+	return rate
+}

+ 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
+}

+ 128 - 0
utils/db.go

@@ -0,0 +1,128 @@
+package utils
+
+import (
+	"database/sql"
+	"fmt"
+
+	"gorm.io/gorm"
+)
+
+type WtDB struct {
+	*gorm.DB
+}
+
+func (db *WtDB) Table(name string, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Table(name, args...)}
+}
+
+func (db *WtDB) Distinct(args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Distinct(args...)}
+}
+
+func (db *WtDB) Select(query interface{}, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Select(query, args...)}
+}
+
+func (db *WtDB) WhereRaw(query interface{}, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Where(query, args...)}
+}
+
+func (db *WtDB) Where(field string, args ...interface{}) (tx *WtDB) {
+	n := len(args)
+	if n == 0 {
+		return &WtDB{db.DB.Where(field)}
+	}
+
+	if n == 1 {
+		return &WtDB{db.DB.Where(field+" = ?", args...)}
+	}
+
+	if n == 2 {
+		var opList = []string{"=", ">", "<", ">=", "<=", "!=", "like"}
+		if v, ok := args[0].(string); ok {
+			if InArray(v, opList) {
+				return &WtDB{db.DB.Where(fmt.Sprintf("%s %s ?", field, v), args[1:]...)}
+			}
+		}
+	}
+
+	return &WtDB{db.DB.Where(field, args...)}
+}
+
+func (db *WtDB) WhereIn(field string, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Where(fmt.Sprintf("%s in ?", field), args...)}
+}
+
+func (db *WtDB) WhereNotIn(field string, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Where(fmt.Sprintf("%s not in ?", field), args...)}
+}
+
+func (db *WtDB) Not(query interface{}, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Not(query, args...)}
+}
+
+func (db *WtDB) Or(query interface{}, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Or(query, args...)}
+}
+
+func (db *WtDB) Join(table string, query string, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Joins(fmt.Sprintf("inner join %s on %s", table, query), args...)}
+}
+
+func (db *WtDB) LeftJoin(table string, query string, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Joins(fmt.Sprintf("left join %s on %s", table, query), args...)}
+}
+
+func (db *WtDB) RightJoin(table string, query string, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Joins(fmt.Sprintf("right join %s on %s", table, query), args...)}
+}
+
+func (db *WtDB) JoinRaw(query string, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Joins(query, args...)}
+}
+
+func (db *WtDB) Group(name string) (tx *WtDB) {
+	return &WtDB{db.DB.Group(name)}
+}
+
+func (db *WtDB) Having(query interface{}, args ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Having(query, args...)}
+}
+
+func (db *WtDB) Order(value interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Order(value)}
+}
+
+func (db *WtDB) Limit(limit int) (tx *WtDB) {
+	return &WtDB{db.DB.Limit(limit)}
+}
+
+func (db *WtDB) Offset(offset int) (tx *WtDB) {
+	return &WtDB{db.DB.Offset(offset)}
+}
+
+func (db *WtDB) Raw(sql string, values ...interface{}) (tx *WtDB) {
+	return &WtDB{db.DB.Raw(sql, values...)}
+}
+
+func (db *WtDB) SubQuery() *gorm.DB {
+	return db.DB
+}
+
+func (db *WtDB) Begin(opts ...*sql.TxOptions) (tx *WtDB) {
+	return &WtDB{DB: db.DB.Begin(opts...)}
+}
+
+func (db *WtDB) Commit() (tx *WtDB) {
+	return &WtDB{DB: db.DB.Commit()}
+}
+
+func (db *WtDB) Rollback() (tx *WtDB) {
+	return &WtDB{DB: db.DB.Rollback()}
+}
+
+func (db *WtDB) Transaction(fc func(tx *WtDB) error) (err error) {
+	return db.DB.Transaction(func(t *gorm.DB) error {
+		return fc(&WtDB{DB: t})
+	})
+}

+ 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))
+}

+ 40 - 0
utils/str.go

@@ -0,0 +1,40 @@
+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 ArrayToString(array []int) string {
+	var str string
+	for k, v := range array {
+		if k == 0 {
+			str = strconv.Itoa(v)
+		} else {
+			str = str + "," + strconv.Itoa(v)
+		}
+	}
+
+	return str
+}
+
+func RandomStringFast(length int) string {
+	const letters = "abcdefghijklmnopqrstuvwxyz0123456789"
+	bytes := make([]byte, length)
+	for i := range bytes {
+		// 生成随机字节(0-255)
+		bytes[i] = letters[rand.Int63()%int64(len(letters))]
+	}
+	return string(bytes)
+}

+ 115 - 0
utils/time.go

@@ -0,0 +1,115 @@
+package utils
+
+import (
+	"fmt"
+	"strconv"
+	"time"
+)
+
+// 给出日期  获取其中每小时的开始时间戳
+func GetDayHour(date time.Time) []int64 {
+
+	date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.Local)
+
+	var hours []int64
+	// 遍历这一天中的每一小时
+	for hour := 0; hour < 24; hour++ {
+		// 设置小时并保留分钟和秒为零
+		t := date.Add(time.Duration(hour) * time.Hour)
+		//fmt.Println(t.Format("2006-01-02 15:04:05"))
+
+		hours = append(hours, t.Unix())
+	}
+
+	return hours
+}
+
+// 给出开始和结束日期  获取其中每一天的开始时间戳
+func GetTimeDay(startDate string, endDate string) []int64 {
+	var days []int64
+	startTime, _ := time.Parse("2006-01-02", startDate)
+
+	var endTime time.Time
+	if len(endDate) > 10 {
+		endTime, _ = time.Parse("2006-01-02  15:04:05", endDate)
+	} else {
+		endTime, _ = time.Parse("2006-01-02", endDate)
+	}
+
+	for currTime := startTime; !currTime.After(endTime); currTime = currTime.AddDate(0, 0, 1) {
+		// 设置时间为当天的开始时间
+		localLocation, _ := time.LoadLocation("Local") //设置时区
+
+		midnight := time.Date(currTime.Year(), currTime.Month(), currTime.Day(), 0, 0, 0, 0, localLocation).Unix()
+		days = append(days, midnight)
+	}
+
+	return days
+}
+
+// 给出开始和结束日期  获取中间每一天的date
+func GetTimeDayDate(startDate string, endDate string) map[string][]int {
+	days := make(map[string][]int)
+	startTime, _ := time.Parse("2006-01-02", startDate)
+	endTime, _ := time.Parse("2006-01-02", endDate)
+
+	for currTime := startTime; !currTime.After(endTime); currTime = currTime.AddDate(0, 0, 1) {
+		// 设置时间为当天的开始时间
+		midnight := time.Date(currTime.Year(), currTime.Month(), currTime.Day(), 0, 0, 0, 0, currTime.Location()).Format("2006-01-02")
+		days[midnight] = []int{}
+	}
+
+	return days
+}
+
+func GetTimeDayDateFormat(startDate string, endDate string) []string {
+	days := make([]string, 0)
+	startTime, _ := time.Parse("2006-01-02", startDate)
+	var endTime time.Time
+	if len(endDate) > 10 {
+		endTime, _ = time.Parse("2006-01-02  15:04:05", endDate)
+	} else {
+		endTime, _ = time.Parse("2006-01-02", endDate)
+	}
+
+	for currTime := startTime; !currTime.After(endTime); currTime = currTime.AddDate(0, 0, 1) {
+		// 设置时间为当天的开始时间
+		midnight := time.Date(currTime.Year(), currTime.Month(), currTime.Day(), 0, 0, 0, 0, currTime.Location()).Format("2006-01-02")
+		days = append(days, midnight)
+	}
+
+	return days
+}
+
+// 时间戳转化为 00:02:37 格式
+func TimeStampToMDS(timestamp int) string {
+
+	hours := timestamp / (60 * 60)
+
+	remainingMinutes := timestamp % (60 * 60)
+
+	minutes := remainingMinutes / 60
+	remainingSeconds := timestamp % 60
+
+	return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, remainingSeconds)
+}
+
+// 比较日期 ,
+func CompareDates(dateA, dateB string) (bool, string) {
+	tA, errA := time.Parse("2006-01-02", dateA)
+	tB, errB := time.Parse("2006-01-02", dateB)
+
+	if errA != nil || errB != nil {
+		return false, ""
+	}
+
+	diff := int(tA.Sub(tB).Hours() / 24)
+
+	valuableDiff := []int{1, 2, 3, 4, 5, 6, 7, 14, 30}
+
+	if InArray(diff, valuableDiff) {
+		return true, "+" + strconv.Itoa(diff) + "day"
+	} else {
+		return false, ""
+	}
+}

+ 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
+}