Skip to content

Repository files navigation

English | 中文

Project Introduction

  • A lightweight framework developed based on the Golang language framework Go Gin, out of the box, inspired by mainstream PHP frameworks such as Laravel and ThinkPHP. The project architecture directory has a clear hierarchy, which is a blessing for beginners. The framework integrates facede, provider, jwt, log, middleware, cache, validator, event, routing, queue(kafka、rabbitmq)redisCommandElasticsearch and other technologies. support multiple languages, simple to develop and easy to use, convenient for extension.
  • The command line correctly creates CURD complete code that can generate runnable swagger documents in the order of model, request validation, service, controller, and routing.
  • The directory structure of the grpc service uses model, proto, request, and service to support one-click command-line generation of model, request, proto, and service code, with grpc:gen automatically generating the gRPC code
  • AI assistant support (openai、deepseek...)
    • Data query
      • Operation log statistics: Query PV, UV, request method distribution, status code statistics, etc. for today/specified date
      • System configuration query: Query system configuration items such as site name and logo
      • User search: Search system users by name or username
      • Department query: Query department tree structure, specify superior sub departments, and number of department members
      • Dict query: Query gender, status, and other enumerated dictionary entries
  • CLI command execution
    • Code generation:
      • Controller creation (make:controller)
      • Model creation (make:model)
      • Service creation (make:service)
      • Request creation (make:request)
      • Middleware creation (make:middleware)
      • Router creation (make:router)
      • Errcode creation (make:errcode)
      • Generate Swagger document (make:docs)
  • Permission Management:
    • Synchronize user permissions to Redis (permission:sync)
  • View information:
    • View routing list (route:list)
    • View Job List (job:list)

Project Address

Web Address

View Screenshots

image image image image image image image image image image image image image image image image image image image image image

Introduction to the Gin Framework

Gin is a web framework written in Go language. It has the characteristics of simplicity, speed, and efficiency, and is widely used in Go language web development.

Features of Gin Framework

  • Fast: The Gin framework is based on the standard library net/http, using goroutines and channels to implement asynchronous processing and improve performance.
  • Simple: The Gin framework provides a range of APIs and middleware, enabling developers to quickly build web applications.
  • Efficient: The Gin framework uses sync. Pool to cache objects, reducing memory allocation and release, and improving performance.

Golang Gin is a lightweight and efficient Golang web framework. It has the characteristics of high performance, ease of use, and flexibility, and is widely used in the development of various web applications.

License

  • 📘 Open source version: Following AGPL-3.0, for learning, research, and non-commercial use only.
  • 💼 Commercial version: If closed source or commercial use is required, please contact the author 📧 [ 25076778@qq.com ]Obtain commercial authorization.

Version History

Installation Instructions

  • Project update Golang version to 1.27.0, low version is not compatible, adjust the support for generic methods, the installation version must be >= 1.27.0.
  • On June 24, 2026, the Golang project was updated to version 1.26.4. There may be version differences in lower versions, and it is recommended to have a version>=1.26.4.
  • The project is developed based on Golang version 1.25.2, and there may be version differences in lower versions. It is recommended that the version be greater than or equal to 1.25.2.

Clone Project

$ git clone https://github.com/dsxwk/gin-admin.git
$ cd gin-admin
$ copy dev.config.yaml.example dev.config.yaml

Initialize Go Environment And Dependencies

Method One

$ go env -w GOPROXY=https://goproxy.cn,direct
$ go generate ./...

Method Two

$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
# $ go get -u
$ go mod tidy
# $ go mod download
$ go mod vendor

Initialize Database

$ go run ./cmd/cli.go db:seed --init=true

Permission Sync

To synchronize permission data to Redis database, Redis service must be installed and started

$ go run ./cmd/cli.go permission:sync

Start

$ go run main.go

Use Air Hot Update

$ go install github.com/air-verse/air@latest
$ air

Compile

Compile Project

$ go build main.go
$ ./main

Compile Command

$ go build ./cmd/cli.go
$ ./cli demo:command --args=11

 SUCCESS  Excute Command: demo:command, Argument: 11

Directory Structure

├── app                         # Application
│   ├── command                 # Command
│   ├── controller              # Controller
│   ├── enum                    # Enum
│   ├── errcode                 # Errcode
│   ├── es                      # ES
│   ├── event                   # Event
│   ├── facade                  # Facade
│   ├── job                     # Job
│   ├── listener                # Listener
│   ├── mcp                     # MCP Tool
│   ├── middleware              # Middleware
│   ├── model                   # Model
│   ├── provider                # Provider
│   ├── queue                   # Queue(Kafka/RabbitMQ/Redis)
│   │   ├── consumer            # Consumer
│   │   └── producer            # Producer
│   ├── request                 # Validator
│   └── service                 # Service
├── bootstrap                   # Bootstrap
├── cmd                         # Command Script Tool
│   └── cli.go                  # Entry File
├── common                      # Common Module
│   ├── base                    # Base
│   ├── ctxkey                  # Context Key
│   ├── flag                    # Flag
│   └── template                # Template
├── config                      # Config File
├── database                    # Database Test File
├── docs                        # Swagger Doc
├── grpc                        # gRPC
│   ├── model                   # gRPC Model
│   ├── proto                   # Proto Definition
│   ├── request                 # gRPC Request
│   └── service                 # gRPC Service
├── pkg                         # Package
│   ├── cli                     # Command
│   │   ├── grpcgen             # gRPC Code Generator
│   │   └── make                # Make Command
│   ├── container               # Container
│   ├── errcode                 # Errcode
│   ├── serviceprovider         # Service Providers Package
│   │   ├── agent               # Agent Provider
│   │   ├── cache               # Cache
│   │   ├── debugger            # Debugger
│   │   ├── es                  # ES
│   │   ├── eventbus            # Event Bus
│   │   ├── grpcclient          # gRPC Client
│   │   ├── http                # Http Request
│   │   ├── job                 # Task Schedule
│   │   ├── lang                # Language
│   │   ├── logger              # Logger
│   │   ├── mcp                 # MCP Tool
│   │   ├── orm                 # Orm Tool
│   │   ├── queue               # Queue
│   │   ├── ratelimit           # Rate Limit
│   │   └── request             # Request
│   └── time                    # Time Processing
├── public                      # Static Resources
├── router                      # Router
├── storage                     # Storage
│   ├── cache                   # Disk Cache
│   ├── logs                    # Logs
│   └── locales                 # Translation
│       ├── en                  # English Translation
│       └── zh                  # Chinese Translation
├── tests                       # Test Case
├── vendor                      # Vendor
├── .air.linux.toml             # Air Configuration File
├── .air.toml                   # Air Configuration File
├── .gitignore                  # Gitignore
├── config.yaml                 # Default Configuration File
├── dev.config.yaml             # Local Environment Configuration File
├── go.mod                      # go mod
├── LICENSE                     # LICENSE
├── main.go                     # Entry File
├── readme.md                   # English Document
├── readme_zh.md                # Chinese Document
├── version_history.md          # Version History English Document
└── version_history_zh.md       # Version History Chinese Document

Start Service

$ go run main.go

Air Hot Update

$ go install github.com/air-verse/air@latest
$ air

  __    _   ___
 / /\  | | | |_)
/_/--\ |_| |_| \_ v1.62.0, built with Go go1.24.2

watching .
watching app
watching app\command
watching app\controller
...
...
[GIN-debug] GET    /api/v1/user/:id          --> gin/app/controller/v1.(*UserController).Detail-fm (6 handlers)
App:                   gin
Env:                   dev
Port:                  8080
Database:              gin
🌐 Local Address:      http://127.0.0.1:8080
🌐 Network Address:    http://192.168.8.54:8080
👉 Local Swagger:      http://127.0.0.1:8080/swagger/index.html
👉 Network Swagger:    http://192.168.8.54:8080/swagger/index.html
👉 Local Test API:     http://127.0.0.1:8080/ping
👉 Network Test API:   http://192.168.8.54:8080/ping
 SUCCESS  Gin server started successfully!

Configuration File

Project Configuration

config.yaml is the default configuration file and can be modified by oneself. dev.config.yaml corresponds to the local environment configuration, and environment variables can be configured through the following app.exe file to switch environments

app:
  env: dev # dev|testing|production dev=local-environment testing=test-environment production=production-environment

Hot Update Configuration

.air.toml is the default configuration file in Windows environment, and .air.Linux.toml is the default configuration file in Linux environment. You can modify it according to the overall needs of the project.

Command

Get Version

$ go run ./cmd/cli.go --version # -v
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Command Help

$ go run ./cmd/cli.go -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Available commands:
consumer:
  consumer:list    Consumer List
db:
  db:migrate       Database Migration
  db:rollback      Database Rollback
  db:seed          Database Seed
demo:
  demo:command     test-demo
event:
  event:list       Event List
listener:
  listener:list    Listener List
make:
  make:command     Command Creation
  make:controller  Controller Creation
  make:errcode     Errcode Creation
  make:es          ES Search Creation
  make:event       Event Creation
  make:facade      Facade Creation
  make:listener    Listener Creation
  make:middleware  Middleware Creation
  make:migration   Generate database migration template
  make:model       Model Creation
  make:model-old   Model Creation old
  make:provider    Producer Creation
  make:queue       Queue Creation(Kafka/RabbitMQ/Redis)
  make:request     Request Creation
  make:router      Router Creation
  make:seed        Generate database seeder template
  make:service     Service Creation
producer:
  producer:list    Producer List
route:
  route:list       Route List

Options:
  -f, --format     The output format (txt, json) [default: txt]
  -h, --help       Display help for the given command
  -v, --version    Display CLI version

Command List

$ go run ./cmd/cli.go --format=json # -f=json
{
  "commands": [
    {
      "description": "Consumer List",
      "name": "consumer:list"
    },
    {
      "description": "Database migrate",
      "name": "db:migrate"
    },
    {
      "description": "Database rollback",
      "name": "db:rollback"
    },
    {
      "description": "Database seed",
      "name": "db:seed"
    },
    {
      "description": "Demo test",
      "name": "demo:command"
    },
    {
      "description": "Event List",
      "name": "event:list"
    },
    {
      "description": "Listener List",
      "name": "listener:list"
    },
    {
      "description": "Command Creation",
      "name": "make:command"
    },
    {
      "description": "Controller Creation",
      "name": "make:controller"
    },
    {
      "description": "Errcode Creation",
      "name": "make:errcode"
    },
    {
      "description": "ES Search Creation",
      "name": "make:es"
    },
    {
      "description": "Event Creation",
      "name": "make:event"
    },
    {
      "description": "Facade Creation",
      "name": "make:facade"
    },
    {
      "description": "Listener Creation",
      "name": "make:listener"
    },
    {
      "description": "Middleware Creation",
      "name": "make:middleware"
    },
    {
      "description": "Generate database migration template",
      "name": "make:migration"
    },
    {
      "description": "Model Creation",
      "name": "make:model"
    },
    {
      "description": "Model Creation old",
      "name": "make:model-old"
    },
    {
      "description": "Provider Creation",
      "name": "make:provider"
    },
    {
      "description": "Queue Creation(Kafka/RabbitMQ/Redis)",
      "name": "make:queue"
    },
    {
      "description": "Request Creation",
      "name": "make:request"
    },
    {
      "description": "Route Creation",
      "name": "make:router"
    },
    {
      "description": "Generate database seeder template",
      "name": "make:seed"
    },
    {
      "description": "Service Creation",
      "name": "make:service"
    },
    {
      "description": "Producer List",
      "name": "producer:list"
    },
    {
      "description": "Route List",
      "name": "route:list"
    }
  ],
  "version": "Gin Cli v2.0.0"
}

Command Creation Help

$ go run ./cmd/cli.go make:command -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:command  Command Creation

Options:
  -f, --file  File Path, Example: cronjob/demo     required:true
  -n, --name  Command Name, Example: demo-test     required:false
  -d, --desc  Description, Example: command-desc   required:false

Command Creation

$ go run ./cmd/cli.go make:command --file=cronjob/demo --name=demo-test --desc=command-desc

Command Structure

After generating the command, appropriate values should be defined for the Name() and Descript() functions. These properties will be used when displaying the command list. The Name() function also allows you to define the expected input value for the command. It will call the Execute() function when executing the command. You can put the command logic in this method. Let's take a look at an example command.

package cronjob

import (
	"gin/common/base"
	"gin/pkg/cli"
)

type DemoCommand struct {
	base.BaseCommand
}

func (m *DemoCommand) Name() string {
	return "demo-test"
}

func (m *DemoCommand) Description() string {
	return "command-desc"
}

func (m *DemoCommand) Help() []base.CommandOption {
	return []base.CommandOption{
		{
			base.Flag{
				Short: "a",
				Long:  "args",
			},
			"Example Argument, Example: arg1",
			true,
		},
	}
}

func (m *DemoCommand) Execute(values map[string]string) {
	// todo
}

func init() {
	cli.Register(&DemoCommand{})
}

Command Registration

cli.go registers all commands in the command package under the gin/app/command directory by default. If the command you registered is not command package, you can add the path to import the package in ./common/imports/import.go.

//go:build cli

package main

import (
	"gin/app/facade"
	_ "gin/common/imports"
	"gin/pkg/cli"
)

func main() {
	_ = facade.Config()

	cli.Execute()
}

Help Options

Command option parameters are defined using the base. CommandOption structure. The base. CommandOption struct contains two attributes: Flag and Description. The Flag attribute is used to define the flag of command options, which can be a short flag (such as - a ) or a long flag (such as --args). The Description attribute is used to define the description of command options. The base. CommandOption struct also contains a Required attribute that specifies whether a command option is required. At the same time, this method supports the console --help parameter and automatically generates help information.

func (m *DemoCommand) Help() []base.CommandOption {
	return []base.CommandOption{
        {
            base.Flag{
                Short: "a",
                Long:  "args",
            },
            "Example Argument, Example: arg1",
            true,
        },
    }
}
$ go run ./cmd/cli.go demo-test -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  demo-test  command-desc

Options:
  -a, --args  Example Argument, Example: arg1  required:true

Execute Command

$ go run ./cmd/cli.go demo:command --args=arg1
 SUCCESS  Excute Command: demo:command --args=arg1

Compile And Execute Commands

$ go build ./cmd/cli.go
$ ./cli demo:command --args=arg1

gRPC

Generate gRPC Code

Generate protobuf message and gRPC service code from grpc/proto/*.proto:

$ go run ./cmd/cli.go grpc:gen

Options:

  • --type=all Generate message and service code (default)
  • --type=pb Generate message code only (user.pb.go)
  • --type=grpc Generate service code only (user_grpc.pb.go)
  • --file=grpc/proto/user.proto Generate the specified proto file
  • --tool-dir=.tools/bin Plugin directory (installed automatically)

Generate gRPC Model

Generate a Go model from a database table:

$ go run ./cmd/cli.go grpc-make:model --table=user

Options:

  • --path=grpc/model Output directory (default)
  • --connection=mysql Database connection

Integer columns are generated as int32, so Postman displays numbers instead of strings. The gRPC service layer uses the models under grpc/model.

Generate gRPC Proto

Generate a gRPC proto file from a database table:

$ go run ./cmd/cli.go grpc-make:proto --table=user

It generates the Detail, List, Create, Update, and Delete rpc methods by default, with integer fields as int32. The shared EmptyResponse is defined in grpc/proto/base.proto and imported by generated protos.

Options:

  • --path=grpc/proto Output directory (default)
  • --connection=mysql Database connection

Generate gRPC Request

Generate a gRPC request from a database table:

$ go run ./cmd/cli.go grpc-make:request --table=user

Options:

  • --path=grpc/request Output directory (default)
  • --connection=mysql Database connection

Generate gRPC Service

Generate a gRPC service from a database table:

$ go run ./cmd/cli.go grpc-make:service --table=user

Options:

  • --path=grpc/service Output directory (default)
  • --connection=mysql Database connection
  • --auth=true Require authentication (default, use --auth=false to disable)

The service layer uses the requests under grpc/request and the models under grpc/model. Define the matching UserService in grpc/proto/user.proto and run grpc:gen. The generated service implements Name(), Register(), and AuthMethods(), and is automatically appended to grpc/service/registry.go.

Call From Go

gRPC services implement AuthMethods() map[string]bool to control each RPC method individually; methods not listed do not require authentication. For methods that require auth, the server validates the JWT and puts the user ID into the context. Use facade.Grpc().WithToken(ctx, token) for internal calls:

ctx := facade.Grpc().WithToken(ctx, token)
userGrpc, err := facade.Grpc().Service(proto.NewUserServiceClient)
if err != nil {
    return err
}
resp, err := userGrpc.Detail(ctx, &proto.UserRequest{Id: 1})

Call From Postman

The server uses the standard protobuf codec and enables gRPC reflection.

  • Server URL: grpc://127.0.0.1:50051
  • Method: grpc.UserService/Detail
  • Metadata: authorization: Bearer <token> (only for services that require auth)
  • Message: {"id": 1}

Update example:

  • Method: grpc.UserService/Update
  • Message: {"id": 1, "data": {"username": "张三", "gender": 0}}

You can import grpc/proto/user.proto in Postman or use server reflection to load the service definition.

Model

Model Creation Help

$ go run ./cmd/cli.go make:model -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:model  Model Creation

Options:
  -t, --table       Table Name, Example: user or user,menu     required:true
  -p, --path        Output Directory, Example: api/user        required:false
  -c, --camel       Is it a camel hump field, Example: true    required:false
  -C, --connection  Database Connection                        required:false

Model Creation

Support the creation of multiple model files simultaneously. If multiple model files need to be created, please separate the table name parameters of the descendants with commas, such as: user, menu

$ go run ./cmd/cli.go make:model --table='user,menu' --path=api/user --camel=true --connection=mysql
# go run ./cmd/cli.go make:model --table=user --path=api/user --camel=true --connection=sqlsrv
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.

package user

import "gin/app/model"

const TableNameUser = "user"

// User User-Table
type User struct {
	ID        int64            `gorm:"column:id;type:int(10) unsigned;primaryKey;autoIncrement:true;comment:ID" json:"id"`                       // ID
	Avatar    string           `gorm:"column:avatar;type:varchar(255);not null;comment:avatar" json:"avatar"`                                    // avatar
	Username  string           `gorm:"column:username;type:varchar(10);not null;comment:username" json:"username"`                               // username
	FullName  string           `gorm:"column:full_name;type:varchar(20);not null;comment:fullname" json:"fullName"`                              // fullName
	Email     string           `gorm:"column:email;type:varchar(50);not null;comment:email" json:"email"`                                        // email
	Password  string           `gorm:"column:password;type:varchar(255);not null;comment:password" json:"password"`                              // password
	Nickname  string           `gorm:"column:nickname;type:varchar(50);not null;comment:nickname" json:"nickname"`                               // nickname
	Gender    int64            `gorm:"column:gender;type:tinyint(1) unsigned;not null;comment:gender 1=male 2=female" json:"gender"`             // gender 1=male 2=female
	Age       int64            `gorm:"column:age;type:int(10) unsigned;not null;comment:age" json:"age"`                                         // age
	Status    int64            `gorm:"column:status;type:tinyint(3) unsigned;not null;default:1;comment:state 1=enable 2=disable" json:"status"` // state 1=enable 2=disable
	CreatedAt *model.DateTime  `gorm:"column:created_at;type:datetime;comment:Creation Time" json:"createdAt"`                                   // Creation Time
	UpdatedAt *model.DateTime  `gorm:"column:updated_at;type:datetime;comment:Update Time" json:"updatedAt"`                                     // Update Time
	DeletedAt *model.DeletedAt `gorm:"column:deleted_at;type:datetime;comment:Delete Time" json:"deletedAt" swaggerignore:"true"`                // Delete Time
}

// TableName User's table name
func (*User) TableName() string {
	return TableNameUser
}

// Connection Database connection name
func (*User) Connection() string {
	return "mysql"
}

ORM Dynamic Filtering

By passing the query | body parameter __search through post or get, dynamically specify the query criteria based on the list fields. The __search type is map[string]any, for example:__search={"and":[{"username":"test"},{"age":18}]}, __search={"or":[{"username":"test"},{"age":18}]}. support or、and、in、not in、between、not between、like、left like、right like、is not null、is null、gt、gte、lt、lte、exist、not exist、json_contains、json_extract Wait for conditions, case insensitive The parameter supports two modes: {'username': 'admin'} or {'username': ['like', 'admin']}. When the field name is a keyword of the 'mysql where' condition, SQL statements will be automatically constructed based on the condition

OR Condition Query

GET /api/v1/user?__search={"or":[{"username":"test"},{"age":18}]} // {"or":[{"username":["=", "test"]},{"age":["=", 18]}]}
SELECT *
FROM `user`
WHERE (username = 'test' OR age = 18)

AND Condition Query

GET /api/v1/user?__search={"and":[{"username":"test"},{"age":18}]} // {"and":[{"username":["=", "test"]},{"age":["=", 18]}]}
SELECT *
FROM `user`
WHERE (username = 'test' AND age = 18)

JSON Field Query

GET /api/v1/menu?__search={"or":[{"and":[{"createdAt":[">","2025-01-01"]},{"createdAt":["<","2026-01-01"]},{"name":""},{"$.meta.icon":["=","ele-Collection"]}]}]}
 SELECT *
 FROM `menu`
 WHERE ((((menu.created_at > '2025-01-01') AND (menu.created_at < '2026-01-01') AND (menu.name = '') AND
          (JSON_EXTRACT(meta, '$.icon') = 'ele-Collection'))))

Complex Condition Query

GET /api/v1/user?__search={"or":[{"and":[{"createdAt":[">","2025-01-01"]},{"createdAt":["<","2026-01-01"]},{"not exist":{"userRoles.name":"admin"}}]},{"username":"admin"}]}
 SELECT *
 FROM `user`
 WHERE ((((user.created_at > '2025-01-01') AND (user.created_at < '2026-01-01') AND
          (NOT EXISTS (SELECT 1 FROM user_roles WHERE user_roles.user_id = user.id AND user_roles.name = 'admin'))) OR
         (user.username = 'admin')))

Query Example

package service

import (
	"context"
	"gin/app/model"
	"gin/app/request"
	"gin/common/base"
)

type UserService struct {
	base.BaseService
}

// List user-list
func (s *UserService) List(ctx context.Context, req request.User) (pageData request.PageData, err error) {
	var (
		m  []model.User
		db = s.DB(ctx, &model.User{})
	)

	// Search
	db = s.Search(db, req.Search)

	err = db.Count(&pageData.Total).Error
	if err != nil {
		return pageData, err
	}

	if req.NotPage {
		err = db.Preload("UserRoles").Order("id DESC").Find(&m).Error
		if err != nil {
			return pageData, err
		}
		pageData.List = m
	} else {
		pageData.Page = req.Page
		pageData.PageSize = req.PageSize
		offset, limit := request.Pagination(req.Page, req.PageSize)

		err = db.Offset(offset).Limit(limit).Order("id DESC").Find(&m).Error
		if err != nil {
			return pageData, err
		}
		pageData.List = m
	}

	return pageData, nil
}

Form Validation

Validator Creation Help

$ go run ./cmd/cli.go make:request -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:request  Validator Creation

Options:
  -f, --file  File Path, Example: role                         required:true
  -d, --desc  Description, Example: role-request-validation    required:false
  -t, --table       Table, Example: roles                      required:false
  -c, --camel       Column is use camel                        required:false
  -C, --connection  Database connection                        required:false

Validator Creation

$ go run ./cmd/cli.go make:request --file=roles --table=roles --desc=role-request-validation
package request

import (
	"gin/app/errcode"
	"gin/common/base"

	"github.com/gookit/validate"
)

// Roles role-request-validation
type Roles struct {
	base.BaseRequest
	ID     int64  `json:"id" form:"id" validate:"required|int|gt:0" label:"ID"`
	Name   string `json:"name" form:"name" validate:"required|max:255" label:"Role Name"`
	Desc   string `json:"desc" form:"desc" validate:"required|max:255" label:"Role Description"`
	Status int64  `json:"status" form:"status" validate:"required|int" label:"Status 1=Enable 2=Disable"`
	PageListValidate
}

func (s Roles) Validate(data Roles, scene string) error {
	v := validate.Struct(data, scene)
	if !v.Validate(scene) {
		return errcode.ArgsError().WithMsg(v.Errors.One())
	}
	return nil
}

// ConfigValidation Configuration-Validation
// - Define validation scenes
// - You can also add verification settings
func (s Roles) ConfigValidation(v *validate.Validation) {
	scenes := validate.SValues{
		"List":   []string{"PageListValidate.Page", "PageListValidate.PageSize"},
		"Create": []string{"Name", "Desc", "Status"},
		"Update": []string{"ID", "Name", "Desc", "Status"},
		"Detail": []string{"ID"},
		"Delete": []string{"ID"},
	}
	v.WithScenes(scenes)
}

// Messages messages
func (s Roles) Messages() map[string]string {
	return validate.MS{
		"required":    "Field {field} Required",
		"int":         "Field {field} Must be an integer",
		"Page.gt":     "Field {field} Must be greater than 0",
		"PageSize.gt": "Field {field} Must be greater than 0",
	}
}

// Translates translate
func (s Roles) Translates() map[string]string {
	return validate.MS{
		"ID":       "ID",
		"Name":     "Role Name",
		"Desc":     "Role Description",
		"Status":   "Status 1=Enable 2=Disable",
		"Page":     "Page",
		"PageSize": "Page Size",
	}
}

Validator Rules

For more rules, please refer to gookit/validate

package request

// Roles role-request-validation
type Roles struct {
	base.BaseRequest
	ID     int64  `json:"id" form:"id" validate:"required|int|gt:0" label:"ID"`
	Name   string `json:"name" form:"name" validate:"required|maxLen:255" label:"Role Name"`
	Desc   string `json:"desc" form:"desc" validate:"required|maxLen:255" label:"Role Description"`
	Status int64  `json:"status" form:"status" validate:"required|int" label:"Status 1=Enable 2=Disable"`
	PageListValidate
}

Validator Scenes

package request

// ConfigValidation Configuration-Validation
// - Define validation scenes
// - You can also add verification settings
func (s Roles) ConfigValidation(v *validate.Validation) {
	scenes := validate.SValues{
		"List":   []string{"PageListValidate.Page", "PageListValidate.PageSize"},
		"Create": []string{"Name", "Desc", "Status"},
		"Update": []string{"ID", "Name", "Desc", "Status"},
		"Detail": []string{"ID"},
		"Delete": []string{"ID"},
	}
	v.WithScenes(scenes)
}

Prompt Message

package request

// Messages Validator-Error-Message
func (s Roles) Messages() map[string]string {
	return validate.MS{
		"required":                     "Field {field} Required",
		"int":                          "Field {field} Must be an integer",
		"PageListValidate.Page.gt":     "Field {field} Must be greater than 0",
		"PageListValidate.PageSize.gt": "Field {field} Must be greater than 0",
	}
}

Field Translation

package request

// Translates Field-Translation
func (s Roles) Translates() map[string]string {
	return validate.MS{
		"ID":       "ID",
		"Name":     "Role Name",
		"Desc":     "Role Description",
		"Status":   "Status 1=Enable 2=Disable",
		"Page":     "Page",
		"PageSize": "Page Size",
	}
}

Batch Validation

Method One

package request

import (
	"gin/app/errcode"
	"gin/pkg"

	"github.com/gookit/validate"
)

// UserImport User Import
type UserImport struct {
	Data []UserImportItem `json:"data" validate:"required|minLen:1" label:"Import Data"`
}

// UserImportItem User Import Item
type UserImportItem struct {
	Username string `json:"username" validate:"required|minLen:3|maxLen:20|regex:^[a-zA-Z0-9_]+$" label:"Username"`
	Password string `json:"password" validate:"required" label:"Password"`
	FullName string `json:"fullName" validate:"required" label:"FullName"`
	Nickname string `json:"nickname" validate:"required" label:"Nickname"`
	Email    string `json:"email" validate:"required|email" label:"Email"`
	Gender   int64  `json:"gender" validate:"required|int" label:"Gender"`
	Age      int64  `json:"age" validate:"int" label:"Age"`
	Status   int64  `json:"status" validate:"int" label:"Status"`
}

// Validate User Import Validate
func (s UserImport) Validate(data UserImport, scene string) error {
	v := validate.Struct(data, scene)
	if !v.Validate(scene) {
		return errcode.ArgsError().WithMsg(v.Errors.One())
	}
	return nil
}

// ConfigValidation Config Validation
func (s UserImport) ConfigValidation(v *validate.Validation) {
	scenes := validate.SValues{
		"Import": []string{"Data"},
	}
	v.WithScenes(scenes)
}

// Messages Validation Messages
func (s UserImport) Messages() map[string]string {
	return validate.MS{
		"required": "{field} Required",
		"minLen":   "{field} the length cannot be less than {min} characters",
		"maxLen":   "{field} the length cannot exceed {max} characters",
		"int":      "{field} Must be an integer",
		"regex":    "{field} format error",
		"email":    "{field} email format error",
	}
}

// Translates Translates
func (s UserImport) Translates() map[string]string {
	ms := validate.MS{
		"Data": "Import Data",
	}
	for i := range s.Data {
		prefix := pkg.Sprintf("Data.%d.", i)
		rowLabel := pkg.Sprintf("Line %d ", i+1)
		ms[prefix+"Username"] = rowLabel + "Username"
		ms[prefix+"Password"] = rowLabel + "Password"
		ms[prefix+"FullName"] = rowLabel + "FullName"
		ms[prefix+"Nickname"] = rowLabel + "Nickname"
		ms[prefix+"Email"] = rowLabel + "Email"
		ms[prefix+"Gender"] = rowLabel + "Gender"
		ms[prefix+"Age"] = rowLabel + "Age"
		ms[prefix+"Status"] = rowLabel + "Status"
	}
	return ms
}

Method Two

package request

import (
	"fmt"
	"gin/app/errcode"

	"github.com/gookit/validate"
)

// SystemConfigValueUpdate Batch update of system configuration
type SystemConfigValueUpdate struct {
	ID           int64  `json:"id" validate:"required|int|gt:0"`
	Key          string `json:"key" validate:""`
	DefaultValue string `json:"defaultValue" validate:""`
}

// SystemConfigUpdates Batch update verification of system configuration
type SystemConfigUpdates struct {
	List []SystemConfigValueUpdate `json:"list" validate:"required" label:"Config List"`
}

// Validate System configuration batch update request verification
func (s SystemConfigUpdates) Validate() error {
	if len(s.List) == 0 {
		return errcode.ArgsError().WithMsg("The configuration list cannot be empty")
	}

	for i, item := range s.List {
		v := validate.Struct(item)
		if !v.Validate() {
			return errcode.ArgsError().WithMsg(fmt.Sprintf("list[%d]item %s", i, v.Errors.One()))
		}
	}

	return nil
}

// Translates Field translation
func (s SystemConfigValueUpdate) Translates() map[string]string {
	return validate.MS{
		"ID":               "ID",
		"Key":              "Identification",
		"Name":             "Name",
		"DefaultValue":     "DefaultValue",
		"OptionValue":      "OptionValue",
		"Type":             "Type 1=input 2=radio 3=checkbox 4=select 5=textarea 6=file",
		"ConfigCategoryId": "ConfigCategoryId",
	}
}

// Messages Validator error message
func (s SystemConfigValueUpdate) Messages() map[string]string {
	return validate.MS{
		"required": "Field {field} is required",
		"int":      "Field {field} must be an integer",
		"gt":       "Field {field} must be greater than 0",
	}
}

Custom Validation

Global Rules

Global rules only need to be defined in the entry file main.go,applicable to all validators, without the need for repeated definitions.

package main

import (
	"github.com/gookit/validate"
)

// Register during initialization
func init() {
	validate.AddValidator("is_even", func(val any, rule string) bool {
		num, ok := val.(int)
		if !ok {
			return false
		}
		return num%2 == 0
	})
}

Local Rules

package request

// ValidateIsEven Define local rule methods (naming convention: Validate<rule name>)
func (s User) ValidateIsEven(val any) bool {
	num := val.(int)
	return num%2 == 0
}

Temporary Rules

package request

import (
	"gin/app/errcode"
)

// Validate Request-Validation
func (s User) Validate(data User, scene string) error {
	v := validate.Struct(data, scene)
	v.AddValidator("is_even", func(val any, rule string) bool {
		num, ok := val.(int)
		if !ok {
			return false
		}
		return num%2 == 0
	})
	if !v.Validate(scene) {
		return errcode.ArgsError().WithMsg(v.Errors.One())
	}

	return nil
}

Validator Usage

package request

type User struct {
	Age int `json:"gender" validate:"required|is_even" label:"age"`
}

Used In The Controller

BindValidate binds query/body and validates in one step. Validate only validates. Both accept the request context and automatically inject it into BaseRequest, so translation and validation messages can use the request context.

package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/app/model"
	"gin/app/request"
	"gin/app/service"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type UserController struct {
	base.BaseController
	service service.UserService
}

// List User-List
// @Tags User
// @Summary List
// @Description User-List
// @Param token header string true "Authentication Token"
// @Param page query string true "Page"
// @Param pageSize query string true "Page Size"
// @Success 200 {object} errcode.SuccessResponse{data=request.PageData{list=[]model.User}} "Login Successful"
// @Failure 400 {object} errcode.ArgsErrorResponse "Argument Error"
// @Failure 500 {object} errcode.SystemErrorResponse "System Error"
// @Router /api/v1/user [get]
func (s *UserController) List(c *gin.Context) {
	var (
		ctx = c.Request.Context()
		req request.User
	)

	// Method One
	/*err := c.ShouldBind(&req)
	  if err != nil {
	      s.Response.Error(c, err)
	      return
	  }

	  // Validator
	  err = facade.Request().Validate(c, &req, "List")
	  if err != nil {
	      s.Response.Error(c, err)
	      return
	  }*/
	// Method Two
	// Bind And Validate
	err := facade.Request().BindValidate(c, &req, "List")
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	res, err := s.service.List(ctx, req)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	s.Response.Success(c, errcode.Success().WithData(res))
}

Service

Service Creation Help

$ go run ./cmd/cli.go make:service -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:service  Service Creation

Options:
  -f, --file        File Path, Example: v1/user                 required:true
  -t, --table       Table Name, Used to generate model fields   required:false
  -c, --connection  Database Connection                         required:false

Service Creation

$ go run ./cmd/cli.go make:service -f=user --table=user -c=mysql

Controller

Controllers obtain ctx := c.Request.Context() and pass it to every service method. Do not call service.WithContext(ctx) anymore. Request validation via facade.Request().BindValidate or facade.Request().Validate automatically injects the request context into the request struct for translation.

Controller Creation Help

$ go run ./cmd/cli.go make:controller -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:controller  Controller Creation

Options:
  -f, --file      File Path,   Example: v1/user       required:true
  -d, --desc      Description, Example: user          required:false

Controller Creation

$ go run ./cmd/cli.go make:controller --file=v1/user --desc=user
package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/app/request"
	"gin/app/service"
	"gin/common/base"
	"gin/pkg/serviceprovider/lang"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-viper/mapstructure/v2"
)

type UserController struct {
	base.BaseController
	service service.UserService
}

// List User List
// @Tags User
// @Summary List
// @Description User List
// @Param token header string true "Authentication Token"
// @Param page query string true "Page"
// @Param pageSize query string true "Page Size"
// @Success 200 {object} errcode.SuccessResponse{data=request.PageData{list=[]model.User}} "Success"
// @Failure 400 {object} errcode.ArgsErrorResponse "Arguments Error"
// @Failure 500 {object} errcode.SystemErrorResponse "System Error"
// @Router /api/v1/user [get]
func (s *UserController) List(c *gin.Context) {
	var (
		ctx = c.Request.Context()
		req request.User
	)

	// Bind parameters and validate
	err := facade.Request().BindValidate(c, &req, "List")
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	res, err := s.service.List(ctx, req)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	s.Response.Success(c, errcode.Success().WithData(res))
}

// Create User Create
// @Tags User
// @Summary User
// @Description User Create
// @Param token header string true "Authentication Token"
// @Param data body request.UserCreate true "Create Params"
// @Success 200 {object} errcode.SuccessResponse{data=model.User} "Success"
// @Failure 400 {object} errcode.ArgsErrorResponse "Arguments Error"
// @Failure 500 {object} errcode.SystemErrorResponse "Syetem Error"
// @Router /api/v1/user [post]
func (s *UserController) Create(c *gin.Context) {
	var (
		ctx = c.Request.Context()
		req request.User
	)

	// Bind parameters and validate
	err := facade.Request().BindValidate(c, &req, "Create")
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	user, err := s.service.Create(ctx, req)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	s.Response.Success(c, errcode.Success().WithData(user))
}

// Update User Update
// @Tags User
// @Summary Update
// @Description User Update
// @Param token header string true "Authentication Token"
// @Param id path int true "User ID"
// @Param data body request.UserUpdate true "Update Params"
// @Success 200 {object} errcode.SuccessResponse "Success"
// @Failure 400 {object} errcode.ArgsErrorResponse "Arguments Error"
// @Failure 500 {object} errcode.SystemErrorResponse "System Error"
// @Router /api/v1/user/{id} [put]
func (s *UserController) Update(c *gin.Context) {
	var (
		ctx  = c.Request.Context()
		data map[string]any
		req  request.User
	)

	err := c.ShouldBindBodyWith(&data, binding.JSON)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	err = mapstructure.Decode(data, &req)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	req.ID = facade.Request().Path[int64](c, "id", 0)
	err = facade.Request().Validate(c, &req, "Update")
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	err = s.service.Update(ctx, req.ID, data)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	s.Response.Success(c, errcode.Success().WithData(data))
}

// Detail User Detail
// @Tags User
// @Summary Detail
// @Description User Detail
// @Param token header string true "Authentication Token"
// @Param id path int true "User ID"
// @Success 200 {object} errcode.SuccessResponse{data=model.User} "Success"
// @Failure 400 {object} errcode.ArgsErrorResponse "Arguments Error"
// @Failure 500 {object} errcode.SystemErrorResponse "System Error"
// @Router /api/v1/user/{id} [get]
func (s *UserController) Detail(c *gin.Context) {
	var (
		ctx = c.Request.Context()
		req request.User
	)

	req.ID = facade.Request().Path[int64](c, "id", 0)

	// Bind parameters and validate
	err := facade.Request().BindValidate(c, &req, "Detail")
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	m, err := s.service.Detail(ctx, req.ID)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	s.Response.Success(c, errcode.Success().WithData(m))
}

// Delete User Delete
// @Tags User
// @Summary Delete
// @Description User Delete
// @Param token header string true "Authentication Token"
// @Param id path int true "User ID"
// @Success 200 {object} errcode.SuccessResponse "Success"
// @Failure 400 {object} errcode.ArgsErrorResponse "Arguments Error"
// @Failure 500 {object} errcode.SystemErrorResponse "System Error"
// @Router /api/v1/user/{id} [delete]
func (s *UserController) Delete(c *gin.Context) {
	var (
		ctx = c.Request.Context()
		req request.User
	)

	req.ID = facade.Request().Path[int64](c, "id", 0)

	// Bind parameters and validate
	err := facade.Request().BindValidate(c, &req, "Delete")
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	err = s.service.Delete(ctx, req.ID)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	s.Response.Success(c, errcode.Success())
}

Route

The router/root.go file defines global routing rules, and router/registry.go explicitly lists all route modules. The make:router command automatically appends new routes to router/registry.go.

Route Creation Help

$ go run ./cmd/cli.go make:router -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:router  Route Creation

Options:
  -f, --file  File Path, Expample: user                   required:true
  -d, --desc  Route Description, Expample: User-Routing   required:false

Route Creation

$ go run ./cmd/cli.go make:router --file=user --desc=User-Routing
package router

import (
	"gin/app/controller/v1"

	"github.com/gin-gonic/gin"
)

// UserRouter User Router
type UserRouter struct{}

// Register Routes
func (r *UserRouter) Register(routerGroup *gin.RouterGroup) {
	var (
		user v1.UserController
	)

	router := routerGroup.Group("api/v1/user")
	{
		// List
		router.GET("", user.List)
		// Create
		router.POST("", user.Create)
		// Update
		router.PUT("/:id", user.Update)
		// Delete
		router.DELETE("/:id", user.Delete)
		// Detail
		router.GET("/:id", user.Detail)
	}
}

// IsAuth Is need auth
func (r *UserRouter) IsAuth() bool {
	return true
}

Route List

$ go run ./cmd/cli.go route:list

Method   Path                                Handler
POST     /api/v1/login                       gin/app/controller/v1.(*LoginController).Login
GET      /api/v1/user                        gin/app/controller/v1.(*UserController).List
POST     /api/v1/user                        gin/app/controller/v1.(*UserController).Create
GET      /api/v1/user/:id                    gin/app/controller/v1.(*UserController).Detail
PUT      /api/v1/user/:id                    gin/app/controller/v1.(*UserController).Update
DELETE   /api/v1/user/:id                    gin/app/controller/v1.(*UserController).Delete
GET      /ping                               gin/router.NewRouters
GET      /public/*filepath                   github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler
HEAD     /public/*filepath                   github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler
GET      /swagger/*any                       github.com/swaggo/gin-swagger.CustomWrapHandler
A total of 10 routes

Middleware

middleware目录下为中间件目录, 可自行添加中间件, 并在router/root.go文件中注册中间件。

Middleware Creation Help

$ go run ./cmd/cli.go make:middleware -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:middleware  Middleware Creation

Options:
  -f, --file  File Path, Expample: auth                        required:true
  -d, --desc  Description, Expample: Authorization-Middleware  required:false

Middleware Creation

$ go run ./cmd/cli.go make:middleware --file=auth --desc=Authorization-Middleware

Rate Limit Middleware

The middleware/rate_imit.go file defines a global flow limiting middleware that supports global user interface flow limiting, IP interface flow limiting, and global flow limiting.

package router

import (
	"gin/app/facade"
	"gin/app/middleware"
	"gin/pkg/errcode"

	"github.com/gin-gonic/gin"
)

var rateLimitMiddleware middleware.RateLimit

// NewRouters Load Routers
func NewRouters(router *gin.Engine) {
	// Global Rate Limit
	group := router.Group("", rateLimitMiddleware.Handle())
	r := group.Group("")
	{
		r.GET("/global-test1", func(c *gin.Context) {
			facade.Response().Success(c, errcode.NewError(0, "global test1"))
		})

		r.GET("/global-test2", func(c *gin.Context) {
			facade.Response().Success(c, errcode.NewError(0, "global test2"))
		})
	}

	// Specify interface current limit
	// User Rate Limit
	// r How many tokens are generated per second
	// burst Bucket capacity
	userGroup := router.Group("", rateLimitMiddleware.UserRateLimit(1, 1))
	r1 := userGroup.Group("")
	{
		r1.GET("/test1", func(c *gin.Context) {
			facade.Response().Success(c, errcode.NewError(0, "user test1"))
		})

		r1.GET("/test2", func(c *gin.Context) {
			facade.Response().Success(c, errcode.NewError(0, "user test2"))
		})
	}

	// Specify interface current limit
	// Ip Rate Limit
	// r How many tokens are generated per second
	// burst Bucket capacity
	ipGroup := router.Group("", rateLimitMiddleware.IpRateLimit(1, 1))
	r2 := ipGroup.Group("")
	{
		r2.GET("/test1", func(c *gin.Context) {
			facade.Response().Success(c, errcode.NewError(0, "ip test1"))
		})

		r2.GET("/test2", func(c *gin.Context) {
			facade.Response().Success(c, errcode.NewError(0, "ip test2"))
		})
	}
}

Cache

With memory as the default cache driver and support for custom extensions. By default, it supports three modes: Memory cache, Redis cache, and Disk cache. It can use global cache or any cache separately. The global cache only integrates the common methods of Set, Get, Delete, and Expire by default. If you need to use more, you can use them separately, or you can integrate them yourself.

Global Cache

The configuration of global cache can be switched through the cache.driver configuration in the yaml configuration file, or dynamically switched.

package controller

import (
	"fmt"
	"gin/app/facade"
	"gin/common/base"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test() {
	// Set Set-Cache	
	key := "test_key"
	value := "test_value"
	cache := facade.Cache()
	cache = facade.Cache("redis")
	err := cache.Set(key, value, time.Second*10)
	if err != nil {
		// Handle error	
	}

	// Get Get-Cache
	key = "test_key"
	value = "test_value"
	result, ok := cache.Get(key)
	if ok {
		println(result) // test_value	
	}

	// Delete Delete-Cache
	key = "test_key"
	err = cache.Delete(key)
	if err != nil {
		// Handle error	
	}

	// Expire Get-Cache-Expire
	key = "test_key"
	val, expireAt, ok, err := cache.Expire(key)
	if err != nil {
		// Handle error
	}
	if ok {
		fmt.Println(val)                       // test_value
		fmt.Printf("ExpireAt: %v\n", expireAt) // ExpireAt: 2025-10-28 11:23:38.7416956 +0800 CST
	}
}

Redis Cache

Use facade.Cache("redis") for common cache operations, or facade.Redis() for Redis-specific capabilities.

package controller

import (
	"fmt"
	"gin/app/facade"
	"gin/common/base"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test() {
	// Set Set-Cache	
	key := "test_key"
	value := "test_value"
	redisCache := facade.Redis()
	err := redisCache.Set(key, value, time.Second*10)
	if err != nil {
		// Handle error	
	}

	// Get Get-Cache
	key = "test_key"
	value = "test_value"
	result, ok := redisCache.Get(key)
	if ok {
		println(result) // test_value	
	}

	// Delete Delete-Cache
	key = "test_key"
	err = redisCache.Delete(key)
	if err != nil {
		// Handle error	
	}

	// Expire Get-Cache-Expire
	key = "test_key"
	val, expireAt, ok, err := redisCache.Expire(key)
	if err != nil {
		// Handle error
	}
	if ok {
		fmt.Println(val)                       // test_value
		fmt.Printf("ExpireAt: %v\n", expireAt) // ExpireAt: 2025-10-28 11:23:38.7416956 +0800 CST
	}

	// ... Other
}

Memory Cache

package controller

import (
	"fmt"
	"gin/app/facade"
	"gin/common/base"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test() {
	// Set Set-Cache	
	key := "test_key"
	value := "test_value"
	memoryCache := facade.Cache("memory")
	err := memoryCache.Set(key, value, time.Second*10)
	if err != nil {
		// Handle error	
	}

	// Get Get-Cache
	key = "test_key"
	value = "test_value"
	result, ok := memoryCache.Get(key)
	if ok {
		println(result) // test_value	
	}

	// Delete Delete-Cache
	key = "test_key"
	err = memoryCache.Delete(key)
	if err != nil {
		// Handle error	
	}

	// Expire Get-Cache-Expire
	key = "test_key"
	val, expireAt, ok, err := memoryCache.Expire(key)
	if err != nil {
		// Handle error
	}
	if ok {
		fmt.Println(val)                       // test_value
		fmt.Printf("ExpireAt: %v\n", expireAt) // ExpireAt: 2025-10-28 11:23:38.7416956 +0800 CST
	}

	// ... Other
}

Disk Cache

package controller

import (
	"fmt"
	"gin/app/facade"
	"gin/common/base"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test() {
	// Set Set-Cache	
	key := "test_key"
	value := "test_value"
	diskCache := facade.Cache("disk")
	err := diskCache.Set(key, value, time.Second*10)
	if err != nil {
		// Handle error	
	}

	// Get Get-Cache
	key = "test_key"
	value = "test_value"
	result, ok := diskCache.Get(key)
	if ok {
		println(result) // test_value	
	}

	// Delete Delete-Cache
	key = "test_key"
	err = diskCache.Delete(key)
	if err != nil {
		// Handle error	
	}

	// Expire Get-Cache-Expire
	key = "test_key"
	val, expireAt, ok, err := diskCache.Expire(key)
	if err != nil {
		// Handle error
	}
	if ok {
		fmt.Println(val)                       // test_value
		fmt.Printf("ExpireAt: %v\n", expireAt) // ExpireAt: 2025-10-28 11:23:38.7416956 +0800 CST
	}

	// ... Other
}    

Event

Event Creation Help

$ go run ./cmd/cli.go make:event -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:event  Event Creation

Options:
  -f, --file  File Path, Example: login/test             required:true
  -n, --name  Event Name, Example: test-event            required:false
  -d, --desc  Event Description, Example: test-event     required:false

Event Creation

$ go run ./cmd/cli.go make:event -f=user_login -n='user.login' -d=user-login-event
package event

// UserLoginEvent event-data
type UserLoginEvent struct {
	UserId   int64
	Username string
}

// Name event-name
func (u UserLoginEvent) Name() string {
	return "user.login"
}

// Description event-description
func (u UserLoginEvent) Description() string {
	return "User Login Event"
}

Listener

Listener Creation Help

$ go run ./cmd/cli.go make:listener -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:listener  Listener Creation

Options:
  -f, --file   File Path, Example: login/test   required:true
  -e, --event  Event Data, Example: UserLogin   required:true

Listener Creation

$ go run ./cmd/cli.go make:listener -f=user_login -e=UserLoginEvent
package listener

import (
	"fmt"
	"gin/app/event"
	"time"
)

type UserLoginListener struct{}

func (l *UserLoginListener) Handle(e event.UserLoginEvent) {
	fmt.Printf(
		"Recieved Event: %s Event Description: %s Event Data: %T, Time: %s\n",
		e.Name(),
		e.Description(),
		e,
		time.Now().Format("2006-01-02 15:04:05"),
	)
}

The event system is split into two layers:

  • eventbus.Bus: generic topic publish/subscribe, with no business or debugger knowledge.
  • eventbus.Registry: business event registration, listener dispatch, and PublishedEvent publishing.

The debugger is a consumer of the bus. It collects debug events and business events without becoming a dependency of eventbus. Business listeners are registered through app/listener/registry.go, and EventProvider runs listener.Register(registry) during startup.

make:listener automatically appends the generated listener to app/listener/registry.go:

listenerRegister(&UserLoginListener{}, event.UserLoginEvent{})

Queue

Executing the queue creation command will create both consumers and producers based on the connection type (kafka/rabbitmq/redis). You only need to implement the Handle method to process your business logic, with automatic error retries and delayed queue support. Generated consumers and producers are also appended to the registration lists in app/queue/consumers.go and app/queue/producers.go.

Queue Creation Help

$ go run ./cmd/cli.go make:queue -h # --help
  ██████  ██████ ██   ██
  ██   ██ ██      ██ ██
  ██   ██ ██████   ███
  ██   ██     ██  ██ ██
  ██████  ██████ ██   ██

Gin Cli v2.0.0, built with Go go1.25.2

Usage:
  cli [command] [options]

Command:
  make:queue  Queue Creation(Kafka/RabbitMQ/Redis)

Options:
  -n, --name        Queue Name, Example: order       required:true
  -c, --connection  Connection Type: kafka, rabbitmq, redis(Default from config)
  -d, --delay       Is Delay Queue: true/false
  -D, --desc        Queue Description

topic, key, group, queue, exchange, routing are auto-generated from name. retry defaults to 3, delayMs defaults to 0.

Queue Creation

Kafka

$ go run ./cmd/cli.go make:queue --connection=kafka --name=kafka_demo

Generated files: app/queue/consumer/kafka_demo.go, app/queue/producer/kafka_demo.go

RabbitMQ

$ go run ./cmd/cli.go make:queue --connection=rabbitmq --name=rabbitmq_demo

Redis

$ go run ./cmd/cli.go make:queue --connection=redis --name=redis_demo

Delay Queue

$ go run ./cmd/cli.go make:queue --connection=kafka --name=order_delay --delay=true

Generated Consumer Example (Kafka)

package consumer

import (
	"gin/app/facade"
	"gin/common/flag"
	"gin/config"
	"gin/pkg"
	"gin/pkg/serviceprovider/queue"
	"time"

	"github.com/segmentio/kafka-go"
)

// KafkaDemoConsumer Kafka consumer
type KafkaDemoConsumer struct {
	*queue.KafkaConsumer
}

// KafkaDemoPayload message payload
type KafkaDemoPayload struct {
	Name string `json:"name"`
}

func NewKafkaDemoConsumer() *KafkaDemoConsumer {
	cfg := facade.Config()
	kfk := queue.NewKafka(cfg, facade.Log(), facade.Event().Bus())
	kfk.Reader = kafka.NewReader(kafka.ReaderConfig{
		Brokers:        cfg.Queue.Kafka.Brokers,
		Topic:          "kafka_demo",
		GroupID:        "kafka_demo_group",
		MinBytes:       1,
		MaxBytes:       10e6,
		StartOffset:    kafka.LastOffset,
		CommitInterval: 0,
		MaxWait:        5 * time.Second,
	})
	return &KafkaDemoConsumer{
		KafkaConsumer: &queue.KafkaConsumer{
			Kafka: kfk,
			Topic: "kafka_demo",
			Group: "kafka_demo_group",
		},
	}
}

func (c *KafkaDemoConsumer) Name() string {
	return "kafka_demo"
}

func (c *KafkaDemoConsumer) Description() string {
	return "kafka demo"
}

func (c *KafkaDemoConsumer) Connection() string {
	return "kafka"
}

func (c *KafkaDemoConsumer) Retry() int {
	return 3
}

func (c *KafkaDemoConsumer) IsDelay() bool {
	return false
}

func (c *KafkaDemoConsumer) Start() error {
	c.KafkaConsumer.Start(c)
	flag.Infof("Kafka consumer started: %s", c.Name())
	return nil
}

func (c *KafkaDemoConsumer) Stop() error {
	return c.KafkaConsumer.Stop()
}

func (c *KafkaDemoConsumer) Enabled(cfg *config.Config) bool {
	return cfg.Queue.Kafka.Enabled
}

func (c *KafkaDemoConsumer) NewPayload() any {
	return &KafkaDemoPayload{}
}

func (c *KafkaDemoConsumer) Handle(payload any) error {
	data := payload.(*KafkaDemoPayload)
	facade.Log().Info(pkg.Sprintf("Kafka Received Msg: name=%s", data.Name))
	// todo business logic
	return nil
}

make:queue automatically appends the generated consumer to app/queue/consumers.go:

ConsumerFactory("kafka", appconsumer.NewKafkaDemoConsumer)

Generated Producer Example (Kafka)

package producer

import (
	"context"
	"gin/app/facade"
	"gin/pkg/serviceprovider/queue"

	"github.com/segmentio/kafka-go"
)

type KafkaDemoProducer struct {
	*queue.KafkaProducer
}

func NewKafkaDemoProducer() *KafkaDemoProducer {
	cfg := facade.Config()
	kfk := queue.NewKafka(cfg, facade.Log(), facade.Event().Bus())
	kfk.Writer = &kafka.Writer{
		Addr:         kafka.TCP(cfg.Queue.Kafka.Brokers...),
		Topic:        "kafka_demo",
		Balancer:     &kafka.LeastBytes{},
		RequiredAcks: kafka.RequireAll,
	}
	p := &KafkaDemoProducer{
		KafkaProducer: &queue.KafkaProducer{
			Kafka: kfk,
			Topic: "kafka_demo",
			Key:   "kafka_demo_key",
		},
	}
	p.KafkaProducer.Owner = p
	return p
}

func (p *KafkaDemoProducer) Name() string {
	return "kafka_demo"
}

func (p *KafkaDemoProducer) Description() string {
	return "kafka demo"
}

func (p *KafkaDemoProducer) Connection() string {
	return "kafka"
}

func (p *KafkaDemoProducer) IsDelay() bool {
	return false
}

func (p *KafkaDemoProducer) DelayMs() int64 {
	return 0
}

func (p *KafkaDemoProducer) Publish(ctx context.Context, msg any) error {
	return p.KafkaProducer.Publish(ctx, msg)
}

func (p *KafkaDemoProducer) Close() error {
	return p.KafkaProducer.Close()
}

make:queue automatically appends the generated producer to app/queue/producers.go:

ProducerFactory("kafka", appproducer.NewKafkaDemoProducer)

Queue Usage

Consumers are auto-registered at startup. Producers can be used directly via the facade with typed payload structs.

package controller

import (
	"gin/app/facade"
	"gin/app/queue/consumer"
	"gin/common/base"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(ctx context.Context) {
	// Kafka
	_ = facade.Queue().Producer("kafka_demo").Publish(ctx, consumer.KafkaDemoPayload{Name: "kafka_test111"})
	_ = facade.Queue().Producer("kafka_delay_demo").Publish(ctx, consumer.KafkaDelayDemoPayload{Name: "kafka_test222"})

	// RabbitMQ
	_ = facade.Queue().Producer("rabbitmq_demo").Publish(ctx, consumer.RabbitmqDemoPayload{Name: "test111"})
	_ = facade.Queue().Producer("rabbitmq_delay_demo").Publish(ctx, consumer.RabbitmqDelayDemoPayload{Name: "test222"})

	// Redis
	_ = facade.Queue().Producer("redis_demo").Publish(ctx, consumer.RedisDemoPayload{Name: "redis_test111"})
	_ = facade.Queue().Producer("redis_delay_demo").Publish(ctx, consumer.RedisDelayDemoPayload{Name: "redis_test222"})
}

Queue consumers and producers can be queried through the current facade methods:

consumers := facade.Queue().Consumers()
producers := facade.Queue().Producers()
consumerNames := facade.Queue().ConsumerNames()
runningConsumers := facade.Queue().RunningConsumers()
stoppedConsumers := facade.Queue().StoppedConsumers()
consumerStatuses := facade.Queue().ConsumerStatus()
producerStatuses := facade.Queue().ProducerStatus()

consumer := facade.Queue().Consumer("kafka_demo")
producer := facade.Queue().Producer("kafka_demo")

Consumer List

$ go run ./cmd/cli.go consumer:list

┌────────────────────────────────────────────────────────────────────────┐
│ Consumer Name        Conn     Delay    Description                     │
├────────────────────────────────────────────────────────────────────────┤
│ kafka_delay_demo     kafka    true     kafka delay queue consumer      │
│ kafka_demo           kafka    false    kafka demo                      │
│ rabbitmq_delay_demo  rabbitmq true     rabbitmq delay queue consumer   │
│ rabbitmq_demo        rabbitmq false    rabbitmq demo                   │
│ redis_delay_demo     redis    true     redis delay queue consumer      │
│ redis_demo           redis    false    redis demo                      │
└────────────────────────────────────────────────────────────────────────┘
Total 6 consumers

Producer List

$ go run ./cmd/cli.go producer:list

┌───────────────────────────────────────────────────────────────────────────────┐
│ Producer Name        Conn     Delay    DelayMs  Description                   │
├───────────────────────────────────────────────────────────────────────────────┤
│ kafka_delay_demo     kafka    true     0ms      kafka delay queue producer    │
│ kafka_demo           kafka    false    0ms      kafka demo                    │
│ rabbitmq_delay_demo  rabbitmq true     0ms      rabbitmq delay queue producer │
│ rabbitmq_demo        rabbitmq false    0ms      rabbitmq demo                 │
│ redis_delay_demo     redis    true     0ms      redis delay queue producer    │
│ redis_demo           redis    false    0ms      redis demo                    │
└───────────────────────────────────────────────────────────────────────────────┘
Total 6 producers

Job

The Job system provides Laravel-style asynchronous task processing with support for sync, redis, kafka, and rabbitmq drivers.

Job Creation

Create models, controllers, etc. using the command line, refer to the previous documentation for details. Generated Jobs are automatically appended to the registration list in app/job/registry.go.

Argument Short Required Default Description
--name -n Yes - Job name, e.g. send_email
--connection -c No queue.connection config Driver: sync, redis, kafka, rabbitmq
--desc -D No Same as --name Job description
--retry -R No 0 Retry count
--delay -d No 0 Retry delay (ms)

Job Structure

package job

import (
	"gin/app/facade"
	"gin/pkg"
)

type SendEmailJob struct{}

type SendEmail struct {
	To      string `json:"to"`
	Subject string `json:"subject"`
	Content string `json:"content"`
}

func (j *SendEmailJob) Name() string        { return "send_email" }
func (j *SendEmailJob) Description() string { return "Send email task" }
func (j *SendEmailJob) Connection() string  { return "redis" }
func (j *SendEmailJob) Retry() int          { return 3 }
func (j *SendEmailJob) Delay() int64        { return 3000 }
func (j *SendEmailJob) NewPayload() any     { return &SendEmail{} }

func (j *SendEmailJob) Handle(payload any) error {
	data := payload.(*SendEmail)
	// todo: implement business logic
	facade.Log().Info(pkg.Sprintf("Sending email to: %s, subject: %s", data.To, data.Subject))
	return nil
}

make:job automatically appends the generated Job to app/job/registry.go:

return []servicejob.Job{
	&SendEmailJob{},
}

Job Dispatch

package v1

import (
	"gin/app/facade"
	"gin/app/job"
	"github.com/gin-gonic/gin"
)

func (s *TestController) Test(c *gin.Context) {
	ctx := c.Request.Context()

	// Async dispatch (redis/kafka/rabbitmq)
	_ = facade.Job().Dispatch(ctx, "send_email", job.SendEmail{
		To:      "user@example.com",
		Subject: "Hello",
		Content: "Test email content",
	})

	// Sync dispatch (sync driver)
	_ = facade.Job().Dispatch(ctx, "sync_user", job.SyncUser{
		UserID: 1,
		Action: "update",
	})
}

Registered Jobs, Job statistics, and pending Redis Jobs can be queried through the facade:

jobs := facade.Job().Jobs()
stats := facade.Job().GetAllJobs()
count, err := facade.Job().Count(ctx)

Job Interface

Method Description
Name() string Unique job name
Description() string Job description
Connection() string Driver: "sync", "redis", "kafka", "rabbitmq" (empty = redis)
Retry() int Retry count (0 = execute once)
Delay() int64 Retry delay in milliseconds
NewPayload() any Returns pointer to payload struct for unmarshaling
Handle(payload any) error Business logic, payload is already deserialized

Job List

$ go run ./cmd/cli.go job:list
┌──────────────────────────────────────────────────────────────────────┐
│ Job Name         Connection       Retry     Delay(ms)   Description  │
├──────────────────────────────────────────────────────────────────────┤
│ export_report   rabbitmq          0          10000ms    Export report│
│ send_email      redis             3          3000ms     Send email   │
│ sync_user       sync              1          0ms        Sync user    │
└──────────────────────────────────────────────────────────────────────┘
Total 3 jobs

Job Clear

$ go run ./cmd/cli.go job:clear
All unconsumed jobs have been cleared

Only supports Redis driver.

Debugger

Job dispatch events are automatically recorded in the debugger:

{
  "job": [
    {
      "traceId": "xxx",
      "name": "send_email",
      "connection": "redis",
      "payload": "{\"to\":\"user@example.com\",\"subject\":\"Hello\"}",
      "ms": 14.08
    }
  ]
}

MCP

MCP uses HTTP JSON-RPC. The service address is controlled by mcp.path, and the default address is http://127.0.0.1:8080/mcp. It does not use /mcp/sse or /mcp/message; every JSON-RPC request is sent with POST to the configured address. If mcp.path is changed to /debug/mcp, the client address must also be changed to http://127.0.0.1:8080/debug/mcp.

MCP Configuration

mcp:
  enabled: true # Enable the MCP service
  path: /mcp # MCP service address
  auth: # Authentication configuration
    enabled: false # Enable authentication
    token: "" # Bearer token

When authentication is enabled, requests must include the Authorization: Bearer <token> or token: <token> header.

MCP Tools

$ go run ./cmd/cli.go mcp:list
$ go run ./cmd/cli.go make:mcp --file=user_search --name=user_search --desc="Search users"

MCP Calls

Initialize:

curl -X POST "http://127.0.0.1:8080/mcp" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {}
  }'

Get the tool list:

curl -X POST "http://127.0.0.1:8080/mcp" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list",
    "params": {}
  }'

Call a tool:

curl -X POST "http://127.0.0.1:8080/mcp" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "user_search",
      "arguments": {
        "keyword": "admin"
      }
    }
  }'

Es

Es Creation

Generate Es based on the database table:

$ go run ./cmd/cli.go make:es --table=user

command options:

  • --table=user Table Name
  • --path=grpc/model Output directory (default)
  • --connection=mysql Database connection
  • --exclude=password,token Exclude field

Publish Event

package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/app/model"
	"gin/app/request"
	"gin/app/service"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type LoginController struct {
	base.BaseController
	service service.LoginService
}

// Token token-info
type Token struct {
	AccessToken        string `json:"accessToken"`
	RefreshToken       string `json:"refreshToken"`
	TokenExpire        int64  `json:"tokenExpire" example:"7200"`
	RefreshTokenExpire int64  `json:"refreshTokenExpire" example:"172800"`
}

type LoginResponse struct {
	Token Token `json:"token"`
	User  model.User
}

// Login login
// @Tags login
// @Summary login
// @Description User login
// @Accept json
// @Produce json
// @Param data body request.UserLogin true "Login Argument"
// @Success 200 {object} errcode.SuccessResponse{data=LoginResponse} "Success"
// @Failure 400 {object} errcode.ArgsErrorResponse "Argument Error"
// @Failure 500 {object} errcode.SystemErrorResponse "System Error"
// @Router /api/v1/login [post]
func (s *LoginController) Login(c *gin.Context) {
	var (
		ctx = c.Request.Context()
		req request.Login
	)

	// Bind And Validate
	err := facade.Request().BindValidate(c, &req, "Login")
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	err, userModel, accessToken, refreshToken, tokenExpire, refreshTokenExpire := s.service.Login(ctx, req.Username, req.Password)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	// Publish Event
	facade.Event().Publish[event.UserLoginEvent](ctx, event.UserLoginEvent{
		UserId:   userModel.ID,
		Username: userModel.Username,
	})

	s.Response.Success(
		c, errcode.Success().WithMsg(
			facade.Lang().Trans(ctx, "login.success", map[string]any{
				"name": userModel.Username,
			}),
		).WithData(LoginResponse{
			Token{
				AccessToken:        accessToken,
				RefreshToken:       refreshToken,
				TokenExpire:        tokenExpire,
				RefreshTokenExpire: refreshTokenExpire,
			},
			userModel,
		}),
	)
}

Event Test

$ POST /api/v1/login HTTP/1.1
Host: 127.0.0.1:8080
Accept-Language: en-Us
Content-Type: application/json
Content-Length: 56

{
    "username": "admin",
    "password": "123456"
}

收到事件: user.login Event Description: User Login Event Event Data: {"UserId":1,"Username":"admin"}, Time: 2025-11-04 15:32:12

Event List

$ go run ./cmd/cli.go event:list

┌────────────────────────────────────────────────────────────┐
│ Event Name            Description                          │
├────────────────────────────────────────────────────────────┤
│ user.login            User Login Event                     │
└────────────────────────────────────────────────────────────┘
Total 1 event

Event Listener List

$ go run ./cmd/cli.go listener:list

┌────────────────────────────────────────────────────────────┐
│ Event Name             Description                         │
├────────────────────────────────────────────────────────────┤
│ user.login             User Login Event                    │
│                      ├─ *listener.TestListener             │
│                      └─ *listener.UserLoginListener        │
└────────────────────────────────────────────────────────────┘
Total 1 event 2 listeners

Response

Response Success

package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	return s.Response.Success(c, errcode.Success())
}

func (s *TestController) Test1(c *gin.Context) {
	return facade.Response().Success(c, errcode.Success())
}

Response Success With Message

package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	return s.Response.Success(c, errcode.Success().WithMsg("Success"))
}

func (s *TestController) Test1(c *gin.Context) {
	return facade.Response().Success(c, errcode.Success().WithMsg("Success"))
}

Response Success With Data

package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	return s.Response.Success(c, errcode.Success().WithData([]string{"test data"}))
}

func (s *TestController) Test1(c *gin.Context) {
	return facade.Response().Success(c, errcode.Success().WithData([]string{"test data"}))
}

Response Error

package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	return s.Response.Error(c, errcode.SystemError())
}

func (s *TestController) Test1(c *gin.Context) {
	return facade.Response().Error(c, errcode.SystemError())
}

Response Error With Code

package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	return s.Response.Error(c, errcode.SystemError().WithCode(500))
}

func (s *TestController) Test1(c *gin.Context) {
	return facade.Response().Error(c, errcode.SystemError().WithCode(500))
}

Response Error With Message

package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	return s.Response.Error(c, errcode.SystemError().WithMsg("System Error"))
}

func (s *TestController) Test1(c *gin.Context) {
	return facade.Response().Error(c, errcode.SystemError().WithMsg("System Error"))
}

Response Error With Data

package v1

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	return s.Response.Error(c, errcode.SystemError().WithData([]string{"test data"}))
}

func (s *TestController) Test1(c *gin.Context) {
	return facade.Response().Error(c, errcode.SystemError().WithData([]string{"test data"}))
}

Response Error With HTTP Code

package v1

import (
	"gin/app/errcode"
	"gin/common/base"
	"net/http"

	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	return s.Response.Error(c, errcode.ArgsError().WithHttpCode(http.StatusBadRequest).WithData([]string{"test data"}))
}

Response With Header

package v1

import (
	"gin/app/errcode"
    "gin/app/facade"
    "gin/common/base"
	"net/http"
    
    "github.com/gin-gonic/gin"
)

type TestController struct {
    base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
    return s.Response.WithHeader("X-ID", "XXX").Error(c, errcode.ArgsError())
}

func (s *TestController) Test(c *gin.Context) {
	return facade.Response().WithHeader("X-ID", "XXX").Error(c, errcode.ArgsError())
}

Log

Use the zap package to implement logging. The storage path for log files is storage/logs, and the default log level is debug. When the error code returned is not 0, it automatically records log TraceID, stack, SQL, HTTP, Redis, GRPC, and other call information. Logging can also be directly called to automatically record debugging information. Does log.access in the configuration file yaml support automatic recording of request logs? If enabled, it will automatically record request logs.

{
  "level": "info",
  "timestamp": "2025-11-17 16:35:09.402",
  "caller": "middleware/logger.go:83",
  "msg": "Access Log",
  "traceId": "fa505122-d31e-4d4f-a05c-13c1641d6c6c",
  "ip": "127.0.0.1",
  "path": "/api/v1/login",
  "method": "POST",
  "params": {
    "password": "1234561",
    "username": "admin"
  },
  "ms": 59,
  "debugger": {
    "sql": [
      {
        "traceId": "fa505122-d31e-4d4f-a05c-13c1641d6c6c",
        "ms": 2.5008,
        "rows": 1,
        "sql": "SELECT * FROM `user` WHERE username = 'admin' AND `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1"
      }
    ],
    "cache": [],
    "http": [],
    "mq": [],
    "grpc": [],
    "listener": [],
    "job": [],
    "es": []
  }
}

Write Log

Encapsulated in the facade, the log level supports debug, info, warn, error, dpanic, panic, and fatal, with the default being debug.

package v1

import (
	"gin/app/facade"
	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	facade.Log().Error("System Error")
}

Error Debug

When using public return errors and calling the Withdebugger () method, it will automatically record log TraceID, stack, SQL, HTTP, Redis, GRPC, and other call information. Debugging can be done based on debug and trace stack information. The log file storage path is' storage/logs'.

package v1

import (
	"gin/app/facade"
	"gin/common/base"
	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	ctx := c.Request.Context()
	facade.Log().WithDebugger(ctx).Error("System Error")
}
{
  "level": "error",
  "timestamp": "2025-11-17 16:35:09.401",
  "caller": "response/response.go:60",
  "msg": "Login Password Error",
  "traceId": "fa505122-d31e-4d4f-a05c-13c1641d6c6c",
  "ip": "127.0.0.1",
  "path": "/api/v1/login",
  "method": "POST",
  "params": {
    "password": "1234561",
    "username": "admin"
  },
  "ms": 58,
  "debugger": {
    "sql": [
      {
        "traceId": "fa505122-d31e-4d4f-a05c-13c1641d6c6c",
        "ms": 2.5008,
        "rows": 1,
        "sql": "SELECT * FROM `user` WHERE username = 'admin' AND `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1"
      }
    ],
    "cache": [],
    "http": [],
    "mq": [],
    "grpc": [],
    "listener": [],
    "job": [],
    "es": []
  },
  "stackTrace": "gin/app/errcode.Error\n\tE:/www/dsx/www-go/gin/app/errcode/response.go:60\ngin/common/base.(*BaseController).Error\n\tE:/www/dsx/www-go/gin/common/base/base_controller.go:25\ngin/app/controller/v1.(*LoginController).Login\n\tE:/www/dsx/www-go/gin/app/controller/v1/login.go:67\ngithub.com/gin-gonic/gin.(*Context).Next\n\tE:/www/dsx/www-go/gin/vendor/github.com/gin-gonic/gin/context.go:192\ngin/router.init.Cors.Handle.func2\n\tE:/www/dsx/www-go/gin/app/middleware/cors.go:30\ngithub.com/gin-gonic/gin.(*Context).Next\n\tE:/www/dsx/www-go/gin/vendor/github.com/gin-gonic/gin/context.go:192\ngin/router.init.Logger.Handle.func1\n\tE:/www/dsx/www-go/gin/app/middleware/logger.go:76\ngithub.com/gin-gonic/gin.(*Context).Next\n\tE:/www/dsx/www-go/gin/vendor/github.com/gin-gonic/gin/context.go:192\ngithub.com/gin-gonic/gin.CustomRecoveryWithWriter.func1\n\tE:/www/dsx/www-go/gin/vendor/github.com/gin-gonic/gin/recovery.go:92\ngithub.com/gin-gonic/gin.(*Context).Next\n\tE:/www/dsx/www-go/gin/vendor/github.com/gin-gonic/gin/context.go:192\ngithub.com/gin-gonic/gin.LoggerWithConfig.func1\n\tE:/www/dsx/www-go/gin/vendor/github.com/gin-gonic/gin/logger.go:249\ngithub.com/gin-gonic/gin.(*Context).Next\n\tE:/www/dsx/www-go/gin/vendor/github.com/gin-gonic/gin/context.go:192\ngithub.com/gin-gonic/gin.(*Engine).handleHTTPRequest\n\tE:/www/dsx/www-go/gin/vendor/github.com/gin-gonic/gin/gin.go:689\ngithub.com/gin-gonic/gin.(*Engine).ServeHTTP\n\tE:/www/dsx/www-go/gin/vendor/github.com/gin-gonic/gin/gin.go:643\nnet/http.serverHandler.ServeHTTP\n\tE:/go-sdk/go1.25.2/src/net/http/server.go:3340\nnet/http.(*conn).serve\n\tE:/go-sdk/go1.25.2/src/net/http/server.go:2109"
}

Language Support

Multilingualism has been integrated into the facade and provider, supporting both zh and en languages, and supporting custom extensions. Language transmission defaults to transmitting the Accept-Language parameter in the header, such as zh or en, which is not case sensitive and does not pass the default language as zh.

Directory Configuration

The storage path for translation files is storage/scales, the default language is zh, and multiple languages are separated by commas. Languages are stored in the corresponding language directory without distinguishing between subdirectories. For example, Chinese is stored in storage/scales/zh and can support json and yaml format files in any directory.

# Translation Configuration
i18n:
  dir: "storage/locales" # Translation file storage path
  lang: "zh,en" # Default language, multiple languages separated by commas

Ordinary Translation

package controller

import (
	"fmt"
	"gin/app/facade"
	"gin/common/base"
	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	ctx := c.Request.Context()
	trans := facade.Lang().Trans(ctx, "login.username", nil)
	fmt.Println(trans) // Output: 用户名, English Output: Username
}

Template Translation

Template translation is supported in the translation file, such as {{. name}}, using map[string]any to pass parameters.

[
  {
    "id": "login.success",
    "translation": "{{.name}},Login Success"
  }
]
package controller

import (
	"fmt"
	"gin/app/facade"
	"gin/common/base"
	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	ctx := c.Request.Context()
	trans := facade.Lang().Trans(ctx, "login.success", map[string]any{
		"name": "admin",
	}),
		fmt.Println(trans) // Output: admin,登录成功 English Output: admin,Login Success
}

Add Language Support

Add the corresponding language directory, such as en, in the storage/scales directory, and then add a translation file in the directory. The translation file supports json and yaml formats, with id as the unique identifier and translation as the translation content. Any number of translation contents can be added to the translation file. The configuration language support requires adjusting the i18n.lang parameter in the configuration file.

# Translation Configuration
i18n:
  dir: "storage/locales" # Translation file storage path
  lang: "zh,en" # Default language, multiple languages separated by commas

Service Provider

The service provider will automatically load the registration upon startup and release it upon shutdown.

Service Provider Creation

Create models, controllers, etc. using the command line, refer to the previous documentation for details.

Facade

Facade Creation

Create models, controllers, etc. using the command line, refer to the previous documentation for details.

Facade Usage

The project integrates features such as logs, databases, validator, caches, and throttling by Facade. Currently, cache is used as an example. The binding of context to databases, caches, HTTP requests, and queues will be recorded in the debugging log.

package controller

import (
	"gin/app/facade"
	"gin/common/base"
	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	ctx := c.Request.Context()
	cache := facade.Cache()
	redisCache := facade.Cache("redis")
	// Bind context to cache
	redisCache = redisCache.WithContext(ctx)
	memoryCache := facade.Cache("memory")
	diskCache := facade.Cache("disk")
	// Other facade usage ...
}

Enum

Enum Creation

Create models, controllers, etc. using the command line, refer to the previous documentation for details.

Enum Example

package enum

import (
	"gin/common/base"
)

const (
	UserGenderSecret = 0 // secrecy
	UserGenderMale   = 1 // male
	UserGenderFemale = 2 // female
)

const (
	UserStatusEnabled  = "enable"  // enable
	UserStatusDisabled = "disable" // disable
)

// UserEnum user-enum
type UserEnum struct{}

// Gender gender
func (s *UserEnum) Gender() *base.Enum[int] {
	return base.NewEnum(
		base.Item[int]{Value: UserGenderSecret, Desc: "secrecy"},
		base.Item[int]{Value: UserGenderMale, Desc: "male"},
		base.Item[int]{Value: UserGenderFemale, Desc: "female"},
	)
}

// Status status
func (s *UserEnum) Status() *base.Enum[string] {
	return base.NewEnum(
		base.Item[string]{Value: UserStatusEnabled, Desc: "enable"},
		base.Item[string]{Value: UserStatusDisabled, Desc: "disable"},
	)
}

Enum Usage

package v1

import (
	"gin/app/enum"
	"gin/app/errcode"
	"gin/common/base"

	"github.com/gin-gonic/gin"
)

type LoginController struct {
	base.BaseController
	service service.LoginService
}

// Test 
// @Tags Login
// @Summary test
// @Description test
// @Accept json
// @Produce json
// @Success 200 {object} errcode.SuccessResponse{data=map[string]any{}} "success"
// @Router /api/v1/test [post]
func (s *LoginController) Test(c *gin.Context) {
	var (
		userEnum enum.UserEnum
	)

	status := userEnum.Status().Get()
	desc1 := userEnum.Status().Desc(enum.UserStatusEnabled)
	value1 := userEnum.Status().Value("enable")
	_map := userEnum.Status().Map()
	containsValue := userEnum.Status().ContainsValue(enum.UserStatusEnabled)
	containsDesc := userEnum.Status().ContainsDesc("disable")
	length := userEnum.Status().Len()

	gender := userEnum.Gender().Get()
	desc2 := userEnum.Gender().Desc(enum.UserGenderMale)
	value2 := userEnum.Gender().Value("male")
	_map2 := userEnum.Gender().Map()
	containsValue2 := userEnum.Gender().ContainsValue(enum.UserGenderMale)
	containsDesc2 := userEnum.Gender().ContainsDesc("male")
	length2 := userEnum.Gender().Len()

	s.Response.Success(c, errcode.Success().WithData(map[string]any{
		"status":         status,
		"desc1":          desc1,
		"value1":         value1,
		"map":            _map,
		"containsValue":  containsValue,
		"containsDesc":   containsDesc,
		"length":         length,
		"gender":         gender,
		"desc2":          desc2,
		"value2":         value2,
		"map2":           _map2,
		"containsValue2": containsValue2,
		"containsDesc2":  containsDesc2,
		"length2":        length2,
	}))
}

Errcode

Errcode Creation

Create models, controllers, etc. using the command line, refer to the previous documentation for details.

$ go run ./cmd/cli.go make:errcode --file=user --prefix=200

The generated file is app/errcode/user_errcode.go, containing the UserErrCodePrefix prefix constant and the UserErrCode error code struct. Add error code methods according to the actual business after generation. If --prefix is not passed, the next available error code prefix will be used automatically.

Errcode Usage

package service

import (
	"context"
	"gin/app/errcode"
)

type UserService struct{}

func (s *UserService) Detail(ctx context.Context, id int64) error {
	var (
		userErr errcode.UserErrCode
	)
	// todo query user
	return userErr.ExampleError()
}

Database

The database is initialized through a container and bound to the context through middleware, so that database instances can be obtained wherever there is context. You can also obtain database instances separately. By default, MySQL, pgSQL, SQLite, and SQLSRV are integrated, and the default database can be configured and the database connection can be specified through the Connection method.

Database Configuration

# Database
databases:
  driver: mysql # Default database connection
  # Slow query time (ms) exceeding this time will be recorded in the log
  slow-query-duration: 3000ms # 3 Second(time.Duration)

# Mysql Database
mysql:
  driver: mysql
  # host: "username:password@tcp(127.0.0.1:3306)/databaseName?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai"
  host: 127.0.0.1
  port: 3306
  username: root
  password: root
  database: gin
  # Slow query time (ms) exceeding this time will be recorded in the log
  slow-query-duration: 3000ms # 3 Second(time.Duration)

# Postgresql Database
pgsql:
  driver: pgsql
  host: 127.0.0.1
  port: 5432
  username: testuser
  password: 123456
  database: testdb
  # Slow query time (ms) exceeding this time will be recorded in the log
  slow-query-duration: 3000ms # 3 Second(time.Duration)

# sqlite Database
sqlite:
  driver: sqlite
  path: storage/data/gin.db
  # Slow query time (ms) exceeding this time will be recorded in the log
  slow-query-duration: 3000ms # 3 Second(time.Duration)

# sqlsrv Database
sqlsrv:
  driver: sqlsrv
  host: 127.0.0.1
  port: 1433
  username: root
  password: root
  database: gin
  # Slow query time (ms) exceeding this time will be recorded in the log
  slow-query-duration: 3000ms # 3 Second(time.Duration)

Database Connection

The use of context is not mandatory. If the context is not bound, SQL records will not be recorded in the log.

package controller

import (
	"gin/app/facade"
	"gin/common/base"
	
	"github.com/gin-gonic/gin"
)

type TestController struct {
	base.BaseController
}

func (s *TestController) Test(c *gin.Context) {
	ctx := c.Request.Context()
	// Default Connection
	db := facade.DB()
	// Using context
	db1 := facade.DB().WithContext(ctx)
	// Connection pgsql
	db2 := facade.DB("pgsql").WithContext(ctx)
	// Connection sqlsrv
	db3 := facade.DB("sqlsrv").WithContext(ctx)
	// todo ...
}

Database Search

Use in conjunction with the ORM dynamic filtering example in the document.

package controller

import (
	"gin/app/errcode"
	"gin/app/facade"
	"gin/app/model"
	"gin/app/request"
	"gin/app/service"
	
	"github.com/gin-gonic/gin"
)

type UserController struct {
	base.BaseController
	service service.UserService
}

func (s *UserController) Test(c *gin.Context) {
	var (
		ctx = c.Request.Context()
		req request.User
	)

	// Bind and validate parameters
	err := facade.Request().BindValidate(c, &req, "List")
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	res, err := s.service.List(ctx, req)
	if err != nil {
		s.Response.Error(c, err)
		return
	}

	s.Response.Success(c, errcode.Success().WithData(res))
}
package service

import (
	"context"
	"gin/app/model"
	"gin/app/request"
	"gin/common/base"
)

type UserService struct {
	base.BaseService
}

// List
func (s *UserService) List(ctx context.Context, req request.User) (pageData request.PageData, err error) {
	var (
		m  []model.User
		db = s.DB(ctx, &model.User{})
	)

	// Search 
	db = (s.Search(db, m, req.Search)).
		Model(&m).
		Preload("UserRoles")

	err = db.Count(&pageData.Total).Error
	if err != nil {
		return pageData, err
	}

	if req.NotPage {
		err = db.Order("id DESC").Find(&m).Error
		if err != nil {
			return pageData, err
		}
		pageData.List = m
	} else {
		pageData.Page = req.Page
		pageData.PageSize = req.PageSize
		offset, limit := request.Pagination(req.Page, req.PageSize)

		err = db.Offset(offset).Limit(limit).Order("id DESC").Find(&m).Error
		if err != nil {
			return pageData, err
		}
		pageData.List = m
	}

	return pageData, nil
}

Swagger Documents

$ go install github.com/swaggo/swag/cmd/swag@latest
# Use
$ swag init -g main.go --exclude grpc # --exclude cli,app/service
# Or Use
$ go run ./cmd/cli.go make:docs
2025/10/23 16:26:42 Generate swagger docs....
2025/10/23 16:26:42 Generate general API Info, search dir:./
2025/10/23 16:26:43 Generating request.UserLogin
2025/10/23 16:26:43 Generating errcode.SuccessResponse
2025/10/23 16:26:43 Generating v1.LoginResponse
2025/10/23 16:26:43 Generating v1.Token
2025/10/23 16:26:43 Generating model.User
2025/10/23 16:26:43 Generating model.DateTime
2025/10/23 16:26:43 Generating errcode.ArgsErrorResponse
2025/10/23 16:26:43 Generating errcode.SystemErrorResponse
2025/10/23 16:26:43 Generating request.PageData
2025/10/23 16:26:43 Generating request.UserCreate
2025/10/23 16:26:43 Generating request.UserUpdate
2025/10/23 16:26:43 Generating request.UserDetail
2025/10/23 16:26:43 create docs.go at docs/docs.go
2025/10/23 16:26:43 create swagger.json at docs/swagger.json
2025/10/23 16:26:43 create swagger.yaml at docs/swagger.yaml

About

Gin Admin 基于Golang语言框架Go Gin开发的轻量级框架, 开箱即用, 设计灵感基于Laravel、ThinkPHP等主流PHP框架, 项目架构目录层次分明, 初学者的福音, 框架默认集成了门面、容器、跨域、jwt、日志、中间件、缓存、验证器、事件、路由、队列、Job、redis、命令行、权限、AI智能助手、grpc、mq、kafka、Es等,支持多语言,开发简单易于上手, 方便扩展。

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages