### Configure and Start Gin HTTP Server with Middleware Source: https://context7.com/virzz/mulan/llms.txt This Go code snippet configures and starts an HTTP server using the Gin framework. It includes options for API prefix, endpoint, host, port, debugging, profiling, request IDs, and metrics. Middleware for API key authentication and custom authorization is applied to different route groups. ```go package main import ( "github.com/gin-gonic/gin" "github.com/virzz/mulan/auth/apikey" "github.com/virzz/mulan/rsp" "github.com/virzz/mulan/web" ) func setupWebServer() error { // Configure HTTP server settings httpCfg := &web.Config{ Prefix: "/api", // API route prefix Endpoint: "https://api.example.com", // Public endpoint URL Host: "0.0.0.0", // Listen address Port: 8080, // Listen port Debug: false, // Gin debug mode Pprof: true, // Enable Go profiling at /pprof RequestID: true, // Generate unique request IDs Metrics: true, // Enable Prometheus metrics at /metrics } // Define API routes and middleware applyFunc := func(api *gin.RouterGroup) { // API key protection for all routes api.Use(apikey.Mw("X-API-Key", "secret-key-1", "secret-key-2")) // Public routes public := api.Group("/public") public.GET("/info", func(c *gin.Context) { c.JSON(200, rsp.S(gin.H{"status": "operational"})) }) // Protected routes protected := api.Group("/protected") protected.GET("/data", func(c *gin.Context) { c.JSON(200, rsp.S(gin.H{"data": "sensitive information"})) }) // Admin routes with additional middleware admin := api.Group("/admin") admin.Use(func(c *gin.Context) { // Custom admin verification logic c.Next() }) admin.GET("/stats", getStats) } // Create HTTP server server, err := web.New(httpCfg, applyFunc) if err != nil { return err } // Start server return server.ListenAndServe() } func getStats(c *gin.Context) { c.JSON(200, rsp.S(gin.H{"requests": 12345, "uptime": "48h"})) } // Built-in endpoints automatically available: // GET /health - Health check // GET /version - Version information // GET /api/health - Prefixed health check // GET /api/version - Prefixed version information // GET /metrics - Prometheus metrics (if enabled) // GET /pprof/* - Go profiling (if enabled) ``` -------------------------------- ### Go 语言:Mulan Web 框架基本应用示例 Source: https://github.com/virzz/mulan/blob/main/README.md 演示如何使用 Mulan 框架创建一个基本的 Web 应用。代码展示了如何初始化应用、配置 Web 路由(包括一个简单的 GET 请求)以及运行应用。需要引入 Mulan 的 app 和 web 包。 ```go package main import ( "context" "github.com/virzz/mulan/app" "github.com/virzz/mulan/web" ) func main() { // 创建应用 meta := &app.Meta{ ID: "myapp", Name: "MyApplication", Description: "My Awesome Application", Version: "1.0.0", } application := app.New(meta) // 创建配置 config := app.NewDefaultConfig() // 配置Web路由 routers := web.NewRouters() routers.Register(func(r *web.Router) { r.GET("/hello", func(c *web.Context) { c.String(200, "Hello, Mulan!") }) }) application.SetRouters(routers) // 运行应用 application.Run(context.Background(), config) } ``` -------------------------------- ### Go: Standardized API Response Examples Source: https://context7.com/virzz/mulan/llms.txt Demonstrates various ways to create standardized API responses using the Mulan library in Go. This includes success responses with data, custom messages, list responses with total counts, and error responses with predefined or custom codes. It also shows chainable response builders and specific responses for empty lists or un-implemented features. ```go package main import ( "errors" "github.com/gin-gonic/gin" "github.com/virzz/mulan/rsp" "github.com/virzz/mulan/rsp/code" ) func responseExamples(c *gin.Context) { // Success with data c.JSON(200, rsp.S(gin.H{ "user_id": 123, "name": "John Doe", })) // Output: {"code": 0, "msg": "成功", "data": {"user_id": 123, "name": "John Doe"}} // Success with custom message c.JSON(200, rsp.M("User created successfully")) // Output: {"code": 0, "msg": "User created successfully", "data": null} // Success with data and message c.JSON(200, rsp.SM(gin.H{"id": 456}, "User updated")) // Output: {"code": 0, "msg": "User updated", "data": {"id": 456}} // Simple success c.JSON(200, rsp.OK()) // Output: {"code": 0, "msg": "成功", "data": null} // List response with total count users := []gin.H{{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}} c.JSON(200, rsp.Item(int64(2), users)) // Output: {"code": 0, "msg": "成功", "data": {"items": [...], "total": 2}} // List response with extension data (pagination) c.JSON(200, rsp.ItemExt(int64(100), users, gin.H{ "page": 1, "page_size": 20, "total_pages": 5, })) // Output: {"code": 0, "msg": "成功", "data": {"items": [...], "total": 100, "ext": {...}}} // Error with predefined code c.JSON(401, rsp.C(code.Unauthorized)) // Output: {"code": 11001, "msg": "未授权", "data": null} // Error with custom message c.JSON(400, rsp.E(code.ParamInvalid, "Username is required")) // Output: {"code": 10002, "msg": "Username is required", "data": null} // Error with error object err := errors.New("database connection failed") c.JSON(500, rsp.E(code.DatabaseExists, err)) // Output: {"code": 12101, "msg": "database connection failed", "data": null} // Chainable response builders response := rsp.C(code.Success). WithData(gin.H{"count": 42}). WithMsg("Query completed") c.JSON(200, response) // Empty list response c.JSON(200, rsp.ItemNone()) // Output: {"code": 20002, "msg": "资源未找到", "data": {"items": [], "total": 0}} // Not implemented c.JSON(501, rsp.UnImplemented()) // Output: {"code": 20001, "msg": "未实现", "data": null} } // Predefined error codes: // code.Success (0) - Success // code.BadRequest (10000) - Bad request // code.ParamInvalid (10002) - Invalid parameters // code.RequestLimit (10001) - Rate limit exceeded // code.Unauthorized (11001) - Not authenticated // code.Forbidden (11002) - Insufficient permissions // code.TokenInvalid (11003) - Invalid token // code.TokenExpired (11004) - Token expired // code.PasswordInvalid (11005) - Incorrect password // code.UnImplemented (20001) - Not implemented // code.NotFound (20002) - Resource not found // code.DatabaseExists (12101) - Already exists ``` -------------------------------- ### API Key Authentication Middleware (Go) Source: https://context7.com/virzz/mulan/llms.txt Provides a simple API key-based authentication middleware for Gin. It allows specifying the header, query parameter, or cookie name for the API key, along with a list of valid keys. This middleware can be applied to individual routes or entire router groups. It also demonstrates how to bypass authentication for specific public routes and offers an example using the Authorization header with a bearer token. ```go package main import ( "github.com/gin-gonic/gin" "github.com/virzz/mulan/auth/apikey" "github.com/virzz/mulan/rsp" ) func setupAPIKeyAuth() { applyFunc := func(api *gin.RouterGroup) { // Protect all routes with API key authentication // Keys can be in header, query, or cookie api.Use(apikey.Mw("X-API-Key", "key-alpha", "key-beta", "key-gamma")) api.GET("/data", func(c *gin.Context) { c.JSON(200, rsp.S(gin.H{"data": "protected content"})) }) // Using Authorization header instead v2 := api.Group("/v2") v2.Use(apikey.Mw("Authorization", "secret-bearer-token")) v2.GET("/resources", func(c *gin.Context) { c.JSON(200, rsp.S([]string{"resource1", "resource2"})) }) // Bypass API key for specific route api.GET("/public", func(c *gin.Context) { c.JSON(200, gin.H{"message": "no auth required"}) }) } } // Valid requests: // curl -H "X-API-Key: key-alpha" http://localhost:8080/api/data // curl http://localhost:8080/api/data?X-API-Key=key-beta // curl -b "X-API-Key=key-gamma" http://localhost:8080/api/data // curl -H "Authorization: Bearer secret-bearer-token" http://localhost:8080/api/v2/resources ``` -------------------------------- ### Standardized Response Format Source: https://context7.com/virzz/mulan/llms.txt This section provides examples of how to construct standardized API responses for various scenarios, including success with data, custom messages, and different types of errors. It demonstrates the usage of the `rsp` package for building consistent JSON outputs. ```APIDOC ## Standardized Response Format This section provides examples of how to construct standardized API responses for various scenarios, including success with data, custom messages, and different types of errors. It demonstrates the usage of the `rsp` package for building consistent JSON outputs. ### Success Responses - **Success with data** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Request Body**: (Not specified in example, assumes standard request) - **Response Example**: ```json { "code": 0, "msg": "成功", "data": { "user_id": 123, "name": "John Doe" } } ``` - **Success with custom message** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 0, "msg": "User created successfully", "data": null } ``` - **Success with data and message** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 0, "msg": "User updated", "data": { "id": 456 } } ``` - **Simple success** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 0, "msg": "成功", "data": null } ``` - **List response with total count** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 0, "msg": "成功", "data": { "items": [ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"} ], "total": 2 } } ``` - **List response with extension data (pagination)** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 0, "msg": "成功", "data": { "items": [ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"} ], "total": 100, "ext": { "page": 1, "page_size": 20, "total_pages": 5 } } } ``` - **Empty list response** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 20002, "msg": "资源未找到", "data": { "items": [], "total": 0 } } ``` ### Error Responses - **Error with predefined code** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 11001, "msg": "未授权", "data": null } ``` - **Error with custom message** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 10002, "msg": "Username is required", "data": null } ``` - **Error with error object** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 12101, "msg": "database connection failed", "data": null } ``` - **Not implemented** - **Method**: `POST` (example) - **Endpoint**: `/example` - **Response Example**: ```json { "code": 20001, "msg": "未实现", "data": null } ``` ### Chainable Response Builders - **Method**: `POST` (example) - **Endpoint**: `/example` - **Description**: Responses can be built using a chain of builder methods. - **Response Example**: ```json { "code": 0, "msg": "Query completed", "data": { "count": 42 } } ``` ### Predefined Error Codes Overview - `code.Success` (0) - Success - `code.BadRequest` (10000) - Bad request - `code.ParamInvalid` (10002) - Invalid parameters - `code.RequestLimit` (10001) - Rate limit exceeded - `code.Unauthorized` (11001) - Not authenticated - `code.Forbidden` (11002) - Insufficient permissions - `code.TokenInvalid` (11003) - Invalid token - `code.TokenExpired` (11004) - Token expired - `code.PasswordInvalid` (11005) - Incorrect password - `code.UnImplemented` (20001) - Not implemented - `code.NotFound` (20002) - Resource not found - `code.DatabaseExists` (12101) - Already exists ``` -------------------------------- ### Go 语言:安装 Mulan Web 框架 Source: https://github.com/virzz/mulan/blob/main/README.md 通过 go get 命令安装 Mulan Web 框架。此命令会下载并安装 Mulan 框架及其依赖项到您的 Go 工作区。请确保您的 Go 环境已正确配置。 ```bash go get https://github.com/virzz/mulan.git ``` -------------------------------- ### Go: Custom Error Code Definitions and Usage Source: https://context7.com/virzz/mulan/llms.txt Illustrates how to define and use custom error codes within an application using the Mulan library in Go. This includes creating system and business codes, defining specific error instances, and integrating them into request handlers. The example shows how to structure custom codes hierarchically and use them for specific error conditions like insufficient funds. ```go package main import ( "github.com/virzz/mulan/rsp/code" ) // Define custom system and business codes const ( // System codes (multiply by 100,000) SystemPayment code.SystemCode = 4 // Business codes (multiply by 1,000) BizPayment code.BizCode = 2000 BizShipping code.BizCode = 2001 ) // Create custom error codes var ( // Payment errors (400,000 + 2,000,000 + local) PaymentInsufficientFunds = code.New(SystemPayment, BizPayment, 1, "余额不足") PaymentGatewayError = code.New(SystemPayment, BizPayment, 2, "支付网关错误") PaymentCardDeclined = code.New(SystemPayment, BizPayment, 3, "银行卡被拒绝") // Shipping errors ShippingAddressInvalid = code.New(code.Client, BizShipping, 1, "配送地址无效") ShippingNotAvailable = code.New(code.Client, BizShipping, 2, "该地区不支持配送") // Simple custom codes (not using hierarchical system) CustomCode1 = code.NewCode(90001, "Custom error message") CustomCode2 = code.NewCode(90002, "Another custom error") ) // Use custom codes in handlers func paymentHandler(c *gin.Context) { balance := checkBalance() if balance < 100 { c.JSON(400, rsp.E(PaymentInsufficientFunds, "需要至少100元")) return } // Process payment } func checkBalance() float64 { return 50 } // Error code structure: // Code = SystemCode*100*1000 + BizCode*1000 + LocalCode // Example: 2,001,003 = Internal(2) * 100,000 + Auth(1) * 1,000 + LocalCode(3) ``` -------------------------------- ### Bootstrap and Manage Mulan Application Lifecycle in Go Source: https://context7.com/virzz/mulan/llms.txt This Go code demonstrates how to bootstrap and manage the lifecycle of a Mulan application. It includes defining application configuration, setting up pre-initialization hooks for database and Redis, configuring validation, defining API routes, and implementing graceful HTTP server shutdown. This snippet requires the Mulan framework and its dependencies like Gin, GORM, and Cobra. ```go package main import ( "context" "errors" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" "github.com/spf13/cobra" "github.com/virzz/mulan/app" "github.com/virzz/mulan/db" "github.com/virzz/mulan/rdb" "github.com/virzz/mulan/web" "go.uber.org/zap" "gorm.io/driver/mysql" ) // Define application configuration structure type Config struct { HTTP web.Config `json:"http" yaml:"http"` Token web.Token `json:"token" yaml:"token"` DB db.Config `json:"db" yaml:"db"` RDB rdb.Config `json:"rdb" yaml:"rdb"` } var Conf *Config func main() { // Create application metadata meta := &app.Meta{ ID: "com.example.myapp", Name: "myapp", CNName: "我的应用", Description: "My Application Service", Version: "1.0.0", Commit: "abc123", BuildAt: "2024-01-01T00:00:00Z", } // Initialize application with metadata and config application := app.New(meta, &Config{}) // Set pre-initialization hook for database/redis setup application.SetPreInit(func(ctx context.Context) error { // Initialize database connection dialector := mysql.Open(Conf.DB.DSN) gormDB, err := db.New(dialector, Conf.DB.Conn) if err != nil { return err } // Initialize Redis connection redisClient, err := rdb.New(&Conf.RDB) if err != nil { return err } zap.L().Info("Database and Redis initialized") return nil }) // Set configuration validation hook application.SetValidate(func() error { if Conf.HTTP.Port < 1024 || Conf.HTTP.Port > 65535 { return errors.New("invalid HTTP port") } return nil }) // Set version handler for /version endpoint web.SetVersionHandler(meta.Name, meta.Version, meta.Commit) // Configure main application action application.SetAction(func(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() // Define API routes applyFunc := func(api *gin.RouterGroup) { api.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{"message": "Hello, Mulan!"}) }) api.GET("/users", getUsers) api.POST("/users", createUser) } // Create HTTP server with configuration httpCfg := &Conf.HTTP httpSrv, err := web.New(httpCfg, applyFunc) if err != nil { return err } // Set up graceful shutdown sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGTERM) // Start HTTP server in goroutine go func() { err := httpSrv.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) { zap.L().Error("Failed to run http server", zap.Error(err)) sig <- os.Interrupt } }() // Wait for shutdown signal switch <-sig { case os.Interrupt: httpSrv.Close() case syscall.SIGTERM: ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() httpSrv.Shutdown(ctx) } return nil }) // Execute application (loads config, parses flags, runs action) if err := application.Execute(context.Background(), Conf); err != nil { panic(err) } } func getUsers(c *gin.Context) { /* implementation */ } func createUser(c *gin.Context) { /* implementation */ } // Run with: ./myapp // CLI flags: -v (verbose), --instance prod, --config /path/to/config.yaml // Built-in commands: config json, config yaml, validate, version ``` -------------------------------- ### Database Configuration and Models in Go Source: https://context7.com/virzz/mulan/llms.txt Sets up GORM database connections for MySQL and PostgreSQL, defining User and Product models with custom fields and GORM configurations. It includes connection pooling settings and demonstrates basic CRUD operations. Dependencies include GORM drivers for MySQL and PostgreSQL, and the `github.com/google/uuid` package. ```go package main import ( "github.com/google/uuid" "github.com/virzz/mulan/db" "gorm.io/driver/mysql" "gorm.io/driver/postgres" gorm.io/gorm" ) // User model with standard timestamp fields type User struct { db.Model // ID, CreatedAt, UpdatedAt Username string `gorm:"unique;not null"` Email string `gorm:"unique;not null"` Password string `gorm:"not null"` Roles db.StringSlice } func (User) TableName() string { return "users" } // Product model with UUID type Product struct { db.ModelUUID // Model + UUID field Name string `gorm:"not null"` Price float64 Stock int } func (Product) TableName() string { return "products" } func setupDatabase() (*gorm.DB, error) { // MySQL configuration mysqlCfg := &db.Config{ Debug: false, DSN: "mysql://user:password@localhost:3306/mydb?charset=utf8mb4&parseTime=True", User: "root", Pass: "secret", Name: "mydb", Conn: &db.ConnConfig{ Idle: 20, // Max idle connections Open: 250, // Max open connections Lifetime: 3600, // Connection max lifetime (seconds) }, } // Create MySQL connection mysqlDialector := mysql.Open(mysqlCfg.DSN) mysqlDB, err := db.New(mysqlDialector, mysqlCfg.Conn, &gorm.Config{ Logger: db.NewLogger(mysqlCfg.Debug), }) if err != nil { return nil, err } // PostgreSQL configuration pgCfg := &db.Config{ DSN: "postgresql://user:pass@localhost:5432/dbname?sslmode=disable", Conn: &db.ConnConfig{ Idle: 10, Open: 100, Lifetime: 1800, }, } // Create PostgreSQL connection pgDialector := postgres.Open(pgCfg.DSN) pgDB, err := db.New(pgDialector, pgCfg.Conn) if err != nil { return nil, err } // Auto-migrate models if err := mysqlDB.AutoMigrate(&User{}, &Product{}); err != nil { return nil, err } // CRUD operations // Create user := &User{ Username: "john_doe", Email: "john@example.com", Password: "hashed_password", Roles: []string{"user", "editor"}, } mysqlDB.Create(user) // Read var foundUser User mysqlDB.Where("username = ?", "john_doe").First(&foundUser) // Update mysqlDB.Model(&foundUser).Update("email", "newemail@example.com") // Delete mysqlDB.Delete(&foundUser) return mysqlDB, nil } ``` -------------------------------- ### Manage Application Configuration (Go) Source: https://context7.com/virzz/mulan/llms.txt Illustrates how to define, load, and validate application configuration using a structured approach. It supports loading from default JSON/YAML files, instance-specific files, custom paths, and environment variables. A validation function can be set to enforce configuration rules. ```go package main import ( "errors" "github.com/virzz/mulan/app" "github.com/virzz/mulan/db" "github.com/virzz/mulan/rdb" "github.com/virzz/mulan/web" ) // Application configuration structure type AppConfig struct { HTTP web.Config `json:"http" yaml:"http"` Token web.Token `json:"token" yaml:"token"` Database db.Config `json:"db" yaml:"db"` Redis rdb.Config `json:"rdb" yaml:"rdb"` Custom struct { UploadDir string `json:"upload_dir" yaml:"upload_dir"` MaxFileSize int64 `json:"max_file_size" yaml:"max_file_size"` } `json:"custom" yaml:"custom"` } // Save as config_default.json: // { // "http": { // "prefix": "/api", // "endpoint": "https://api.example.com", // "host": "0.0.0.0", // "port": 8080, // "debug": false, // "pprof": false, // "requestid": true, // "metrics": true // }, // "token": { // "disable": false, // "keyname": "X-API-Key", // "apikey": "your-secret-key" // }, // "db": { // "debug": false, // "dsn": "mysql://root:password@localhost:3306/myapp?charset=utf8mb4", // "conn": { // "idle": 20, // "open": 250, // "lifetime": 3600 // } // }, // "rdb": { // "debug": false, // "host": "127.0.0.1", // "port": 6379, // "db": 0, // "pass": "" // }, // "custom": { // "upload_dir": "/var/uploads", // "max_file_size": 10485760 // } // } func configExample() { meta := &app.Meta{ Name: "myapp", Version: "1.0.0", } cfg := &AppConfig{} application := app.New(meta, cfg) // Validate configuration application.SetValidate(func() error { if cfg.HTTP.Port == 0 { return errors.New("HTTP port is required") } if cfg.Custom.MaxFileSize <= 0 { cfg.Custom.MaxFileSize = 5 * 1024 * 1024 // 5MB default } return nil }) // Configuration loaded automatically from: // 1. config_default.json / config_default.yaml // 2. config_{instance}.json (if --instance flag set) // 3. --config flag path // 4. Environment variables (MYAPP_HTTP_PORT, MYAPP_DB_DSN, etc.) application.Execute(context.Background(), cfg) } // CLI usage: // ./myapp # Uses config_default.json // ./myapp --instance prod # Uses config_prod.json // ./myapp --config /etc/myapp/config.yaml # Uses custom path // MYAPP_HTTP_PORT=9000 ./myapp # Override via env var // ./myapp config json # Display JSON config template // ./myapp config yaml # Display YAML config template // ./myapp validate # Validate current config // ./myapp version # Show version info // ./myapp -v # Verbose logging // ./myapp -vv # More verbose ``` -------------------------------- ### User Registration API Source: https://context7.com/virzz/mulan/llms.txt Allows new users to register an account. ```APIDOC ## POST /api/register ### Description Register a new user account. ### Method POST ### Endpoint /api/register ### Parameters #### Request Body - **username** (string) - Required - The desired username. - **password** (string) - Required - The user's password. ### Request Example ```json { "username": "newuser", "password": "securepassword" } ``` ### Response #### Success Response (200) - **message** (string) - A success message indicating registration. #### Response Example ```json { "message": "User registered successfully" } ``` ``` -------------------------------- ### Configure Redis Client with Debug Logging (Go) Source: https://context7.com/virzz/mulan/llms.txt Demonstrates how to set up a Redis client with debug logging enabled. It covers basic connection configuration and common Redis operations like string, hash, list, set, and sorted set manipulations. The debug mode logs all executed commands. ```go package main import ( "context" "time" "github.com/virzz/mulan/rdb" ) func setupRedis() error { // Configure Redis connection cfg := &rdb.Config{ Debug: true, // Enable command logging Host: "127.0.0.1", Port: 6379, DB: 0, Pass: "", // Empty for no password } // Create Redis client client, err := rdb.New(cfg) if err != nil { return err } ctx := context.Background() // String operations client.Set(ctx, "user:1:name", "John Doe", 24*time.Hour) name, _ := client.Get(ctx, "user:1:name").Result() // Hash operations client.HSet(ctx, "user:1", "email", "john@example.com") client.HSet(ctx, "user:1", "age", 30) email, _ := client.HGet(ctx, "user:1", "email").Result() // List operations client.LPush(ctx, "queue:tasks", "task1", "task2", "task3") task, _ := client.RPop(ctx, "queue:tasks").Result() // Set operations client.SAdd(ctx, "user:1:roles", "admin", "editor") roles, _ := client.SMembers(ctx, "user:1:roles").Result() // Sorted set operations client.ZAdd(ctx, "leaderboard", rdb.Z{Score: 100, Member: "player1"}) client.ZAdd(ctx, "leaderboard", rdb.Z{Score: 200, Member: "player2"}) top, _ := client.ZRevRange(ctx, "leaderboard", 0, 9).Result() // Key expiration client.Expire(ctx, "session:token", 7*24*time.Hour) // Delete keys client.Del(ctx, "old:key") // Check nil (key not found) val, err := client.Get(ctx, "nonexistent").Result() if err == rdb.Nil { // Key does not exist } return nil } // Redis debug mode logs all commands to Zap logger: // INFO redis.hook: cmd: SET user:1:name "John Doe" EX 86400 // INFO redis.hook: cmd: HSET user:1 email "john@example.com" ``` -------------------------------- ### Session-Based Authentication with Redis and Role-Based Access Control (Go) Source: https://context7.com/virzz/mulan/llms.txt Implements session-based authentication using Redis for storage and provides role-based access control. It initializes a Redis client, sets up Gin middleware for authentication and authorization, and defines public, protected, and admin-only routes. Sessions store user ID, account, roles, and state, with tokens generated for client use. The implementation handles login, profile retrieval, logout, and password changes, with placeholder functions for other operations. It supports token retrieval via query, form, cookie, or Authorization header. ```go package main import ( "github.com/gin-gonic/gin" "github.com/virzz/mulan/auth" "github.com/virzz/mulan/rdb" "github.com/virzz/mulan/rsp" "github.com/virzz/mulan/rsp/code" ) func setupAuthentication() error { // Initialize Redis client redisClient, err := rdb.New(&rdb.Config{ Host: "127.0.0.1", Port: 6379, DB: 0, Pass: "", }) if err != nil { return err } applyFunc := func(api *gin.RouterGroup) { // Initialize session middleware for all routes api.Use(auth.Init(redisClient)) // Public authentication endpoints api.POST("/login", loginHandler) api.POST("/register", registerHandler) // Protected routes - require authentication protected := api.Group("/user") protected.Use(auth.AuthMW()) protected.GET("/profile", getProfile) protected.POST("/logout", logoutHandler) protected.PUT("/password", changePassword) // Admin-only routes - require "admin" role admin := api.Group("/admin") admin.Use(auth.AuthMW()) admin.Use(auth.RoleMW("admin")) admin.GET("/users", listAllUsers) admin.DELETE("/users/:id", deleteUser) // Moderator routes - require "admin" OR "moderator" role moderation := api.Group("/moderate") moderation.Use(auth.AuthMW()) moderation.Use(func(c *gin.Context) { if !auth.HasRole(c, "admin") && !auth.HasRole(c, "moderator") { c.AbortWithStatusJSON(403, rsp.C(code.Forbidden)) return } c.Next() }) moderation.POST("/content/:id/flag", flagContent) } return nil } func loginHandler(c *gin.Context) { var req struct { Account string `json:"account" binding:"required"` Password string `json:"password" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, rsp.E(code.ParamInvalid, err)) return } // Verify credentials (pseudo-code) userID := authenticateUser(req.Account, req.Password) if userID == 0 { c.JSON(401, rsp.C(code.PasswordInvalid)) return } // Get session from context sess := auth.Default(c) sess.SetID(userID) sess.SetAccount(req.Account) sess.SetRoles([]string{"user"}) sess.SetState(1) // active state // Save session to Redis (expires in 7 days by default) if err := sess.Save(); err != nil { c.JSON(500, rsp.E(code.DatabaseExists, err)) return } // Return token to client c.JSON(200, rsp.S(gin.H{ "token": sess.Token(), "account": sess.Account(), "roles": sess.Roles(), })) } func getProfile(c *gin.Context) { sess := auth.Default(c) c.JSON(200, rsp.S(gin.H{ "id": sess.ID(), "account": sess.Account(), "roles": sess.Roles(), })) } func logoutHandler(c *gin.Context) { sess := auth.Default(c) sess.Clear() // Remove session from Redis c.JSON(200, rsp.OK()) } func changePassword(c *gin.Context) { /* implementation */ } func listAllUsers(c *gin.Context) { /* implementation */ } func deleteUser(c *gin.Context) { /* implementation */ } func flagContent(c *gin.Context) { /* implementation */ } func authenticateUser(account, password string) uint64 { return 1 } // Token can be sent via: // - Query: ?token=abc123 // - Form: token=abc123 // - Cookie: token=abc123 // - Header: Authorization: Bearer abc123 ``` -------------------------------- ### Go REST API for User Management Source: https://context7.com/virzz/mulan/llms.txt This Go code implements a RESTful API for user management. It includes features for user registration, login, profile retrieval and updates, and administrative actions like listing and deleting users. The API uses Gin for routing, GORM for database interactions (MySQL), and Redis for session handling. It defines User model, sets up routes with authentication and role-based access control middleware, and handles request binding and response formatting. ```go package main import ( "context" "github.com/gin-gonic/gin" "github.com/virzz/mulan/app" "github.com/virzz/mulan/auth" "github.com/virzz/mulan/db" "github.com/virzz/mulan/req" "github.com/virzz/mulan/rsp" "github.com/virzz/mulan/rsp/code" "github.com/virzz/mulan/web" "gorm.io/driver/mysql" "gorm.io/gorm" ) type User struct { db.Model Username string `gorm:"unique;not null" Email string `gorm:"unique;not null" Password string `gorm:"not null" Roles db.StringSlice `gorm:"type:json" } func (User) TableName() string { return "users" } var ( database *gorm.DB redisClient *redis.Client ) func main() { meta := &app.Meta{ Name: "user-api", Version: "1.0.0", } application := app.New(meta, &Config{}) application.SetPreInit(func(ctx context.Context) error { // Initialize database dialector := mysql.Open("mysql://root:pass@localhost:3306/users") var err error database, err = db.New(dialector, &db.ConnConfig{Idle: 20, Open: 250, Lifetime: 3600}) if err != nil { return err } database.AutoMigrate(&User{}) // Initialize Redis redisClient, err = rdb.New(&rdb.Config{Host: "127.0.0.1", Port: 6379}) return err }) application.SetAction(func(cmd *cobra.Command, args []string) error { httpCfg := &web.Config{Prefix: "/api", Port: 8080, RequestID: true} httpSrv, _ := web.New(httpCfg, setupRoutes) return httpSrv.ListenAndServe() }) application.Execute(context.Background(), &Config{}) } func setupRoutes(api *gin.RouterGroup) { // Initialize session middleware api.Use(auth.Init(redisClient)) // Public routes api.POST("/register", registerUser) api.POST("/login", loginUser) // Protected routes protected := api.Group("") protected.Use(auth.AuthMW()) { protected.GET("/profile", getProfile) protected.PUT("/profile", updateProfile) protected.POST("/logout", logout) } // Admin routes admin := api.Group("/admin") admin.Use(auth.AuthMW(), auth.RoleMW("admin")) { admin.GET("/users", listUsers) admin.GET("/users/:id", getUser) admin.DELETE("/users/:id", deleteUser) } } func registerUser(c *gin.Context) { var req struct { Username string `json:"username" binding:"required,min=3,max=20" Email string `json:"email" binding:"required,email" Password string `json:"password" binding:"required,min=8" } if err := req.Bind(c, &req); err != nil { c.JSON(400, rsp.E(code.ParamInvalid, err)) return } user := &User{ Username: req.Username, Email: req.Email, Password: hashPassword(req.Password), Roles: []string{"user"}, } if err := database.Create(user).Error; err != nil { c.JSON(409, rsp.E(code.DatabaseExists, "User already exists")) return } c.JSON(200, rsp.S(gin.H{"id": user.ID, "username": user.Username})) } func loginUser(c *gin.Context) { var req struct { Username string `json:"username" binding:"required" Password string `json:"password" binding:"required" } if err := req.Bind(c, &req); err != nil { c.JSON(400, rsp.E(code.ParamInvalid, err)) return } var user User if err := database.Where("username = ?", req.Username).First(&user).Error; err != nil { c.JSON(401, rsp.C(code.PasswordInvalid)) return } if !verifyPassword(user.Password, req.Password) { c.JSON(401, rsp.C(code.PasswordInvalid)) return } sess := auth.Default(c) sess.SetID(user.ID) sess.SetAccount(user.Username) sess.SetRoles(user.Roles) sess.Save() c.JSON(200, rsp.S(gin.H{"token": sess.Token()})) } func getProfile(c *gin.Context) { sess := auth.Default(c) var user User database.First(&user, sess.ID()) c.JSON(200, rsp.S(gin.H{ "id": user.ID, "username": user.Username, "email": user.Email, "roles": user.Roles, })) } func updateProfile(c *gin.Context) { sess := auth.Default(c) var req struct { Email string `json:"email" binding:"omitempty,email" } req.Bind(c, &req) database.Model(&User{}).Where("id = ?", sess.ID()).Update("email", req.Email) c.JSON(200, rsp.OK()) } func logout(c *gin.Context) { auth.Default(c).Clear() c.JSON(200, rsp.OK()) } func listUsers(c *gin.Context) { var req req.PaginationReq req.ShouldBind(c, &req) offset, limit := req.FindPage(50) var users []User var total int64 database.Model(&User{}).Count(&total) database.Offset(offset).Limit(limit).Find(&users) c.JSON(200, rsp.Item(total, users)) } func getUser(c *gin.Context) { var req struct { ID uint64 `uri:"id" binding:"required" } req.Bind(c, &req) var user User if err := database.First(&user, req.ID).Error; err != nil { c.JSON(404, rsp.C(code.NotFound)) return } c.JSON(200, rsp.S(user)) } func deleteUser(c *gin.Context) { var req struct { ID uint64 `uri:"id" binding:"required" } req.Bind(c, &req) database.Delete(&User{}, req.ID) c.JSON(200, rsp.OK()) } ```