GinSkeleton

repository·github·Indexed 21 days ago

https://github.com/qifengzhang007/ginskeleton

A high-performance Go web project skeleton built on the Gin framework, designed for decoupled front-end and back-end architectures. It provides a pre-configured foundation for user management, authentication, and database operations (MySQL, SQL Server, PostgreSQL via GORM v2), as well as integrated Redis client management and WebSocket support with heartbeat mechanisms. The project includes an API server, a CLI tool, and gRPC server capabilities.

Tokens
39.1K
Snippets
116
Records
159
Agent score
76%

What's inside ginskeleton

  1. Overview of GinSkeleton-Admin

    github

    GinSkeleton-Admin is an enterprise-level backend framework built using GinSkeleton (v1.5.10) and Iview (v4.5.0). It provides a complete administrative system skeleton.

    Note that the online demo version has restricted permissions for modifying or deleting data compared to a local installation.

  2. Overview of GinSkeleton

    github

    GinSkeleton is a web project skeleton based on the Go language gin framework, specifically designed for decoupled front-end and back-end business scenarios.

    Its primary goals are:

    • To clarify the main logic flow of web projects.
    • To provide a complete encapsulation of foundational features so developers can focus on their own business logic.
    • To provide a ready-to-use core centered around a tb_users table, which includes user-related interface parameter validation, registration, login (token acquisition), token refreshing, CRUD operations, and token authentication.

    Key Requirements & Recommendations:

    • Branch: Use the master branch, which is the latest stable version.
    • Go Version: Since version V1.4.00, you must use Go >= 1.15 to ensure stable usage of the GORM v2 read/write splitting solution.
    • Design Philosophy: The skeleton is designed to be concise and headless (no built-in UI), making it easy to extend for custom business needs.
  3. Understand the ginskeleton project directory structure

    github

    The ginskeleton project follows a structured layout designed to separate core logic, HTTP handling, database models, and entry points. Understanding this structure is essential for locating where to add new controllers, models, or services.

    Core Directory Mapping

    • app/: The heart of the application logic.
      • app/aop/: Aspect-Oriented Programming (AOP) demonstration code.
      • app/core/: Application container, form parameter registration, and configuration storage (includes container, destroy, and event_manage).
      • app/global/: Global variables, constants, and error definitions (consts, my_errors, variable).
      • app/http/: Web-related logic, including controller, middleware, and validator.
      • app/model/: Database table models (e.g., base_model.go).
      • app/service/: Business logic layer (e.g., sys_log_hook).
      • app/utils/: Wrappers for third-party packages (e.g., gorm_v2).
    • bootstrap/: Initialization code for starting the application (init.go).
    • cmd/: Application entry points for different modes:
      • cmd/api/: API backend entry.
      • cmd/cli/: Command Line Interface entry.
      • cmd/web/: Web portal/frontend entry.
    • command/: Logic for CLI mode commands.
    • config/: Configuration files for the project and database (e.g., config.yml, gorm_v2.yml).
    • routers/: Routing definitions for API and Web (api.go, web.go).
    • storage/: Directory for logs and resource storage.
    • test/: Unit testing directory.
    |-- app
    |   |-- aop
    |   |-- core
    |   |-- global
    |   |-- http
    |   |-- model
    |   |-- service
    |   |-- utils
    |-- bootstrap
    |-- cmd
    |-- command
    |-- config
    |-- routers
    |-- storage
    `-- test
  4. Implement AOP (Aspect-Oriented Programming) in Routes

    github

    If you need to perform actions before or after a controller logic (e.g., permission checks before deletion or data backup after deletion), you can use an AOP-style pattern by registering multiple handlers in the route.

    Warning: If your project requires extensive AOP (many before/after hooks), decoupling might make your route files and import blocks overly complex and heavy. Only use this decoupling pattern if your route hooks are minimal.

    // Example of AOP-style route with before/after hooks
    users.POST("delete", 
        validatorFactory.Create(consts.ValidatorPrefix+"UsersDestroy"), // 1. Validate
        (&Users.DestroyBefore{}).Before,                               // 2. Pre-logic (e.g. Permission check)
        (&web.Users{}).Destroy,                                        // 3. Core Logic
        (&Users.DestroyAfter{}).After,                                // 4. Post-logic (e.g. Data backup)
    )
  5. Database Connection Management and Multi-source Support

    github

    Initializing a model does not create a new database connection for every table.

    Connection Pooling: The relational database driver is initialized once based on config.yml. Each database type maintains a single global connection pool (managed in app/utils/sql_factory/client.go). When a model is initialized, it uses the existing driver pointer to retrieve a connection from the pool via ping(), which is then automatically released.

    Why initialize per model? This design allows individual tables to be easily switched to different database connections, facilitating multi-source database scenarios.

  6. Implement custom Log Hooks

    github

    You can process logs secondary to the main application logging by implementing a log hook.

    • Hook Location: The default implementation is in app/service/sys_log_hook/zap_log_hooks.go.
    • Configuration: You can modify the hook location in bootStrap/init.go.

    The hook receives a zapcore.Entry object, which contains:

    • Level: Log level
    • Time: Current timestamp
    • LoggerName: Logger name
    • Message: Log message
    • Caller: File path/line number
    • Stack: Call stack

    Best Practice: Run heavy processing (like database writes or external API calls) inside a goroutine within the hook to ensure the logging call does not block the main application performance.

    // Example hook implementation
    func ZapLogHandler(entry zapcore.Entry) error {
    	// entry contains Level, Time, LoggerName, Message, Caller, Stack
    	go func(paramEntry zapcore.Entry) {
    		// Perform secondary processing here (e.g., saving to DB)
    	}(entry)
    	return nil
    }
  7. How to start RabbitMQ consumers in ginskeleton

    github

    In ginskeleton, you can start RabbitMQ consumers using three different patterns depending on your deployment needs:

    1. Bundled Startup: Import the consumer package in your application's initialization entry point (e.g., BootStrap/Init.go). This binds the consumer lifecycle to the main application.
    2. Independent Entrypoint: Create a specific functional category and entry file in the cmd directory. Compile and run this as a standalone process.
    3. Cobra CLI: Use the integrated cobra package to create independent CLI commands for starting consumers.
  8. Avoid data pollution in Form Parameter Validators

    github

    When implementing form parameter validators, you must define the CheckParams method on a value receiver, not a pointer receiver.

    Why? The validator is automatically registered in the global container during program startup. If you use a pointer receiver (e.g., func (r *Register) CheckParams(...)), the first successful request will bind the request's data to the instance stored in the container. Subsequent requests will then access the data from the previous request, leading to data pollution and security issues.

    Correct Pattern:

    type Register struct {
    	Base
    	Pass  string `form:"pass" json:"pass" binding:"required,min=3,max=20"` 
    	Phone string `form:"phone" json:"phone"  binding:"required,len=11"`    
    }
    
    // Use a value receiver (r Register)
    func (r Register) CheckParams(context *gin.Context) {
        // ...
    }
    type Register struct {
    	Base
    	Pass  string `form:"pass" json:"pass" binding:"required,min=3,max=20"` 
    	Phone string `form:"phone" json:"phone"  binding:"required,len=11"`    
    }
    
    // Correct: Value receiver
    func (r Register) CheckParams(context *gin.Context) {
        // ...
    }
    
    // Incorrect: Pointer receiver (causes data pollution)
    func (r *Register) CheckParams(context *gin.Context) {
        // ...
    }
  9. How a request-to-response lifecycle works

    github

    The framework follows a structured pipeline for handling HTTP requests. The flow typically moves through these stages:

    1. Routing: The request is matched to a route in routers/web.go.
    2. Middleware (Optional): If the route requires authentication, a middleware (e.g., authorization.CheckAuth()) intercepts the request. If validation fails, the middleware calls c.Abort() to stop the chain.
    3. Form Parameter Validator: The request is passed to a validator (located in app/http/validator/...). The validator checks the request body/params against a struct using context.ShouldBind. If valid, it binds the data to the gin.Context using a specific key pattern and then calls the Controller.
    4. Controller: Acts as a dispatcher (located in app/http/controller/...). It retrieves the validated data from the context using context.GetString(consts.ValidatorPrefix + "key") and calls the appropriate Service layer.
    5. Service Layer: Handles business logic (e.g., password encryption) and calls the Model layer.
    6. Model Layer: Handles direct database interactions using GORM (located in app/models/...).
    7. Response: The controller uses the response utility to return a standardized JSON response to the client.
  10. Implement Aspect-Oriented Programming (AOP) for Controllers

    github

    To avoid polluting core controller logic with repetitive pre-processing (e.g., permission checks) or post-processing (e.g., data backups), you can implement an AOP-inspired pattern. Since true dynamic proxying in Go is complex and often unsuitable for production, this project uses a manual injection pattern via anonymous functions at the validator level.

    The AOP Pattern Workflow

    1. Define a Before callback: This function must return a bool. If it returns false, the core controller logic is aborted.
    2. Define an After callback: This function executes after the core logic. It can be run synchronously or asynchronously (using go func()).
    3. Inject via Validator: Instead of calling the controller directly, wrap the call in an anonymous function that executes the Before check, then the controller, and finally the After callback (using defer).
    // Pattern for injecting AOP callbacks in the validator layer
    func(before_callback_fn func(context *gin.Context) bool, after_callback_fn func(context *gin.Context)) {
        if before_callback_fn(extraAddBindDataContext) {
            defer after_callback_fn(extraAddBindDataContext)
            (&Web.Users{}).Destroy(extraAddBindDataContext)
        } else {
            // Handle failure of the 'Before' check (e.g., return error response)
        }
    }((&Users.destroy_before{}).Before, (&Users.destroy_after{}).After)
  11. Use GinSkeleton-Admin2 for rapid business development

    github
    GinSkeleton-Admin2 is an integrated interface system designed for rapid business development. It allows developers to enter a business development mode quickly without needing to modify the core skeleton code.