English | 中文
- Project Introduction
- License
- Version History
- Installation Instructions
- Directory Structure
- Start Service
- Configuration File
- Command
- gRPC
- Model
- Form Validation
- Service
- Controller
- Route
- Middleware
- Cache
- Event
- Listener
- Queue
- Job
- Es
- Publish Event
- Event List
- Response
- Log
- Language Support
- Service Provider
- Facade
- Enum
- Errcode
- Database
- Swagger Documents
- A lightweight framework developed based on the Golang language framework
Go Gin, out of the box, inspired by mainstream PHP frameworks such asLaravelandThinkPHP. The project architecture directory has a clear hierarchy, which is a blessing for beginners. The framework integratesfacede,provider,jwt,log,middleware,cache,validator,event,routing,queue(kafka、rabbitmq)、redis、Command、Elasticsearchand 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 serviceusesmodel,proto,request, andserviceto support one-click command-line generation of model, request, proto, and service code, withgrpc:genautomatically 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)
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.
- 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.
- 📘 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.
- Latest Version v3.3.2
- Historical Version Records
- 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.
$ git clone https://github.com/dsxwk/gin-admin.git
$ cd gin-admin
$ copy dev.config.yaml.example dev.config.yaml$ go env -w GOPROXY=https://goproxy.cn,direct
$ go generate ./...$ 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$ go run ./cmd/cli.go db:seed --init=trueTo synchronize permission data to Redis database, Redis service must be installed and started
$ go run ./cmd/cli.go permission:sync$ go run main.go$ go install github.com/air-verse/air@latest
$ air$ go build main.go
$ ./main$ go build ./cmd/cli.go
$ ./cli demo:command --args=11
SUCCESS Excute Command: demo:command, Argument: 11├── 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
$ go run main.go$ 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!
config.yamlis the default configuration file and can be modified by oneself.dev.config.yamlcorresponds to the local environment configuration, and environment variables can be configured through the following app.exe file to switch environmentsapp: env: dev # dev|testing|production dev=local-environment testing=test-environment production=production-environment
.air.tomlis the default configuration file in Windows environment, and.air.Linux.tomlis the default configuration file in Linux environment. You can modify it according to the overall needs of the project.
$ go run ./cmd/cli.go --version # -v
██████ ██████ ██ ██
██ ██ ██ ██ ██
██ ██ ██████ ███
██ ██ ██ ██ ██
██████ ██████ ██ ██
Gin Cli v2.0.0, built with Go go1.25.2$ 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$ 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"
}$ 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$ go run ./cmd/cli.go make:command --file=cronjob/demo --name=demo-test --desc=command-descAfter generating the command, appropriate values should be defined for the
Name()andDescript()functions. These properties will be used when displaying the command list. TheName()function also allows you to define the expected input value for the command. It will call theExecute()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{})
}
cli.goregisters all commands in thecommandpackage under thegin/app/commanddirectory by default. If the command you registered is notcommandpackage, 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()
}Command option parameters are defined using the
base. CommandOptionstructure. Thebase. CommandOptionstruct contains two attributes:FlagandDescription. TheFlagattribute 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). TheDescriptionattribute is used to define the description of command options. Thebase. CommandOptionstruct also contains aRequiredattribute that specifies whether a command option is required. At the same time, this method supports the console--helpparameter 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$ go run ./cmd/cli.go demo:command --args=arg1
SUCCESS Excute Command: demo:command --args=arg1$ go build ./cmd/cli.go
$ ./cli demo:command --args=arg1Generate protobuf message and gRPC service code from grpc/proto/*.proto:
$ go run ./cmd/cli.go grpc:genOptions:
--type=allGenerate message and service code (default)--type=pbGenerate message code only (user.pb.go)--type=grpcGenerate service code only (user_grpc.pb.go)--file=grpc/proto/user.protoGenerate the specified proto file--tool-dir=.tools/binPlugin directory (installed automatically)
Generate a Go model from a database table:
$ go run ./cmd/cli.go grpc-make:model --table=userOptions:
--path=grpc/modelOutput directory (default)--connection=mysqlDatabase 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 a gRPC proto file from a database table:
$ go run ./cmd/cli.go grpc-make:proto --table=userIt 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/protoOutput directory (default)--connection=mysqlDatabase connection
Generate a gRPC request from a database table:
$ go run ./cmd/cli.go grpc-make:request --table=userOptions:
--path=grpc/requestOutput directory (default)--connection=mysqlDatabase connection
Generate a gRPC service from a database table:
$ go run ./cmd/cli.go grpc-make:service --table=userOptions:
--path=grpc/serviceOutput directory (default)--connection=mysqlDatabase connection--auth=trueRequire authentication (default, use--auth=falseto 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.
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})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.
$ 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:falseSupport 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"
}By passing the
query|bodyparameter__searchthroughpostorget, dynamically specify the query criteria based on the list fields. The__searchtype ismap[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
GET /api/v1/user?__search={"or":[{"username":"test"},{"age":18}]} // {"or":[{"username":["=", "test"]},{"age":["=", 18]}]}SELECT *
FROM `user`
WHERE (username = 'test' OR age = 18)GET /api/v1/user?__search={"and":[{"username":"test"},{"age":18}]} // {"and":[{"username":["=", "test"]},{"age":["=", 18]}]}SELECT *
FROM `user`
WHERE (username = 'test' AND age = 18)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'))))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')))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
}$ 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$ go run ./cmd/cli.go make:request --file=roles --table=roles --desc=role-request-validationpackage 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",
}
}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
}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)
}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",
}
}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",
}
}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",
}
}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
})
}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
}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
}package request
type User struct {
Age int `json:"gender" validate:"required|is_even" label:"age"`
}
BindValidatebinds query/body and validates in one step.Validateonly validates. Both accept the request context and automatically inject it intoBaseRequest, 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))
}$ 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$ go run ./cmd/cli.go make:service -f=user --table=user -c=mysqlControllers obtain
ctx := c.Request.Context()and pass it to every service method. Do not callservice.WithContext(ctx)anymore. Request validation viafacade.Request().BindValidateorfacade.Request().Validateautomatically injects the request context into the request struct for translation.
$ 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$ go run ./cmd/cli.go make:controller --file=v1/user --desc=userpackage 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())
}The
router/root.gofile defines global routing rules, androuter/registry.goexplicitly lists all route modules. Themake:routercommand automatically appends new routes torouter/registry.go.
$ 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$ go run ./cmd/cli.go make:router --file=user --desc=User-Routingpackage 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
}$ 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目录下为中间件目录, 可自行添加中间件, 并在router/root.go文件中注册中间件。
$ 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$ go run ./cmd/cli.go make:middleware --file=auth --desc=Authorization-MiddlewareThe
middleware/rate_imit.gofile 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"))
})
}
}With
memoryas the default cache driver and support for custom extensions. By default, it supports three modes:Memory cache,Redis cache, andDisk cache. It can use global cache or any cache separately. The global cache only integrates the common methods ofSet,Get,Delete, andExpireby default. If you need to use more, you can use them separately, or you can integrate them yourself.
The configuration of global cache can be switched through the
cache.driverconfiguration in theyamlconfiguration 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
}
}Use
facade.Cache("redis")for common cache operations, orfacade.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
}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
}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
} $ 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$ go run ./cmd/cli.go make:event -f=user_login -n='user.login' -d=user-login-eventpackage 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"
}$ 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$ go run ./cmd/cli.go make:listener -f=user_login -e=UserLoginEventpackage 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, andPublishedEventpublishing.
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{})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
Handlemethod to process your business logic, with automatic error retries and delayed queue support. Generated consumers and producers are also appended to the registration lists inapp/queue/consumers.goandapp/queue/producers.go.
$ 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,routingare auto-generated fromname.retrydefaults to 3,delayMsdefaults to 0.
$ go run ./cmd/cli.go make:queue --connection=kafka --name=kafka_demoGenerated files: app/queue/consumer/kafka_demo.go, app/queue/producer/kafka_demo.go
$ go run ./cmd/cli.go make:queue --connection=rabbitmq --name=rabbitmq_demo$ go run ./cmd/cli.go make:queue --connection=redis --name=redis_demo$ go run ./cmd/cli.go make:queue --connection=kafka --name=order_delay --delay=truepackage 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)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)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")$ 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$ 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 producersThe Job system provides Laravel-style asynchronous task processing with support for
sync,redis,kafka, andrabbitmqdrivers.
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) |
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{},
}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)| 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 |
$ 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$ go run ./cmd/cli.go job:clear
All unconsumed jobs have been clearedOnly supports Redis driver.
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 uses HTTP JSON-RPC. The service address is controlled by
mcp.path, and the default address ishttp://127.0.0.1:8080/mcp. It does not use/mcp/sseor/mcp/message; every JSON-RPC request is sent with POST to the configured address. Ifmcp.pathis changed to/debug/mcp, the client address must also be changed tohttp://127.0.0.1:8080/debug/mcp.
mcp:
enabled: true # Enable the MCP service
path: /mcp # MCP service address
auth: # Authentication configuration
enabled: false # Enable authentication
token: "" # Bearer tokenWhen authentication is enabled, requests must include the
Authorization: Bearer <token>ortoken: <token>header.
$ go run ./cmd/cli.go mcp:list
$ go run ./cmd/cli.go make:mcp --file=user_search --name=user_search --desc="Search users"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"
}
}
}'Generate Es based on the database table:
$ go run ./cmd/cli.go make:es --table=usercommand options:
--table=userTable Name--path=grpc/modelOutput directory (default)--connection=mysqlDatabase connection--exclude=password,tokenExclude field
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,
}),
)
}$ 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$ go run ./cmd/cli.go event:list
┌────────────────────────────────────────────────────────────┐
│ Event Name Description │
├────────────────────────────────────────────────────────────┤
│ user.login User Login Event │
└────────────────────────────────────────────────────────────┘
Total 1 event$ go run ./cmd/cli.go listener:list
┌────────────────────────────────────────────────────────────┐
│ Event Name Description │
├────────────────────────────────────────────────────────────┤
│ user.login User Login Event │
│ ├─ *listener.TestListener │
│ └─ *listener.UserLoginListener │
└────────────────────────────────────────────────────────────┘
Total 1 event 2 listenerspackage 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())
}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"))
}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"}))
}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())
}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))
}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"))
}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"}))
}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"}))
}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())
}Use the
zappackage to implement logging. The storage path for log files isstorage/logs, and the default log level isdebug. 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. Doeslog.accessin the configuration fileyamlsupport 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": []
}
}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")
}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"
}Multilingualism has been integrated into the facade and provider, supporting both
zhandenlanguages, and supporting custom extensions. Language transmission defaults to transmitting theAccept-Languageparameter in theheader, such aszhoren, which is not case sensitive and does not pass the default language aszh.
The storage path for translation files is
storage/scales, the default language iszh, and multiple languages are separated by commas. Languages are stored in the corresponding language directory without distinguishing between subdirectories. For example, Chinese is stored instorage/scales/zhand can supportjsonandyamlformat files in any directory.
# Translation Configuration
i18n:
dir: "storage/locales" # Translation file storage path
lang: "zh,en" # Default language, multiple languages separated by commaspackage 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 is supported in the translation file, such as
{{. name}}, usingmap[string]anyto 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 the corresponding language directory, such as
en, in thestorage/scalesdirectory, and then add a translation file in the directory. The translation file supportsjsonandyamlformats, withidas the unique identifier andtranslationas the translation content. Any number of translation contents can be added to the translation file. The configuration language support requires adjusting thei18n.langparameter in the configuration file.
# Translation Configuration
i18n:
dir: "storage/locales" # Translation file storage path
lang: "zh,en" # Default language, multiple languages separated by commasThe service provider will automatically load the registration upon startup and release it upon shutdown.
Create models, controllers, etc. using the command line, refer to the previous documentation for details.
Create models, controllers, etc. using the command line, refer to the previous documentation for details.
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 ...
}Create models, controllers, etc. using the command line, refer to the previous documentation for details.
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"},
)
}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,
}))
}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=200The generated file is
app/errcode/user_errcode.go, containing theUserErrCodePrefixprefix constant and theUserErrCodeerror code struct. Add error code methods according to the actual business after generation. If--prefixis not passed, the next available error code prefix will be used automatically.
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()
}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
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)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 ...
}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
}$ 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