竹简文档

快速开始

5 分钟快速上手 Bamboo Base Go

快速开始

本指南将帮助你快速搭建一个基于 Bamboo Base Go 的 Web 服务。

安装

# 按需安装对应层级模块
go get github.com/bamboo-services/bamboo-base-go/defined
go get github.com/bamboo-services/bamboo-base-go/common
go get github.com/bamboo-services/bamboo-base-go/major
go get github.com/bamboo-services/bamboo-base-go/plugins/grpc  # 可选

项目结构

推荐的项目结构:

your-project/
├── main.go                     # 入口文件
├── .env                        # 环境变量配置
├── api/                        # API 请求/响应结构定义
│   └── user/
│       └── user.go
├── internal/
│   ├── app/
│   │   ├── middleware/         # 中间件
│   │   ├── route/              # 路由注册
│   │   └── startup/            # 启动初始化
│   ├── entity/                 # 数据库实体
│   ├── handler/                # HTTP 处理器
│   └── logic/                  # 业务逻辑层
└── go.mod

环境配置

创建 .env 文件:

.env
# 调试模式
XLF_DEBUG=true

# 服务配置
XLF_HOST=localhost
XLF_PORT=8080

# gRPC 配置(可选)
GRPC_PORT=1119
GRPC_REFLECTION=false

# 数据库配置
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USER=postgres
DATABASE_PASS=your_password
DATABASE_NAME=your_database
DATABASE_PREFIX=app_
DATABASE_TIMEZONE=Asia/Shanghai

# Redis 配置
NOSQL_HOST=localhost
NOSQL_PORT=6379
NOSQL_PASS=
NOSQL_DATABASE=0
NOSQL_POOL_SIZE=10

# 雪花算法配置(可选,默认自动生成)
SNOWFLAKE_DATACENTER_ID=1
SNOWFLAKE_NODE_ID=1

入口文件

main.go
package main

import (
    "context"

    xCtx "github.com/bamboo-services/bamboo-base-go/defined/context"
    xLog "github.com/bamboo-services/bamboo-base-go/common/log"
    xMain "github.com/bamboo-services/bamboo-base-go/major/main"
    xReg "github.com/bamboo-services/bamboo-base-go/major/register"
    xRegNode "github.com/bamboo-services/bamboo-base-go/major/register/node"
    "your-project/internal/app/startup"
    "your-project/internal/app/route"
)

func main() {
    // 1. 初始化 bamboo-base-go 核心组件(传入自定义节点)
    reg := xReg.Register(context.Background(), []xRegNode.RegNodeList{
        {Key: xCtx.DatabaseKey, Node: startup.InitDatabase},
        {Key: xCtx.RedisClientKey, Node: startup.InitRedis},
    })

    log := xLog.WithName(xLog.NamedMAIN)

    // 2. 交由 Runner 托管服务生命周期(路由注册 + 启动 + 优雅关闭)
    xMain.Runner(reg, log, route.NewRoute)
}

可选:HTTP + gRPC 一体化启动

当服务同时暴露 HTTP 与 gRPC 接口时,可将 gRPC 任务函数直接挂入 xMain.Runner

需要额外引入 gRPC 运行时相关包:

import (
    xGrpcInterface "github.com/bamboo-services/bamboo-base-go/plugins/grpc/interceptor"
    xGrpcRunner "github.com/bamboo-services/bamboo-base-go/plugins/grpc/runner"
    "google.golang.org/grpc"
)
main.go
grpcTask := xGrpcRunner.New(
    xGrpcRunner.WithLogger(xLog.WithName(xLog.NamedGRPC)),
    xGrpcRunner.WithRegisterService(func(ctx context.Context, server grpc.ServiceRegistrar) {
        // 注册你的 gRPC Service
    }),
    xGrpcRunner.WithUnaryInterceptors(
        xGrpcInterface.Recover(),
        xGrpcInterface.InitContext(reg.Init.Ctx),
        xGrpcInterface.ResponseBuilder(),
    ),
)

xMain.Runner(reg, log, route.NewRoute, grpcTask)

完整说明参见:gRPC 运行时

业务初始化

数据库和 Redis 通过节点化系统注入,通常放在 startup 包中实现:

internal/app/startup/init_database.go
package startup

import (
    "context"
    "log/slog"
    "strings"

    xEnv "github.com/bamboo-services/bamboo-base-go/defined/env"
    xLog "github.com/bamboo-services/bamboo-base-go/common/log"
    "your-project/internal/entity"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/schema"
)

// 需要自动迁移的数据表
var migrateTables = []interface{}{
    &entity.User{},
}

// InitDatabase 数据库初始化节点
func InitDatabase(ctx context.Context) (any, error) {
    log := xLog.WithName(xLog.NamedINIT)
    log.Debug(ctx, "正在连接数据库...")

    // 构建 DSN
    dsn := strings.Builder{}
    dsn.WriteString("host=")
    dsn.WriteString(xEnv.GetEnvString(xEnv.DatabaseHost, "localhost"))
    dsn.WriteString(" user=")
    dsn.WriteString(xEnv.GetEnvString(xEnv.DatabaseUser, "postgres"))
    dsn.WriteString(" password=")
    dsn.WriteString(xEnv.GetEnvString(xEnv.DatabasePass, ""))
    dsn.WriteString(" dbname=")
    dsn.WriteString(xEnv.GetEnvString(xEnv.DatabaseName, "postgres"))
    dsn.WriteString(" port=")
    dsn.WriteString(xEnv.GetEnvString(xEnv.DatabasePort, "5432"))
    dsn.WriteString(" sslmode=disable")

    db, err := gorm.Open(postgres.Open(dsn.String()), &gorm.Config{
        NamingStrategy: schema.NamingStrategy{
            TablePrefix:   xEnv.GetEnvString(xEnv.DatabasePrefix, "app_"),
            SingularTable: true,
        },
        Logger: xLog.NewSlogLogger(slog.Default().WithGroup(xLog.NamedREPO), xLog.GormLoggerConfig{
            SlowThreshold:             200,
            LogLevel:                  xLog.LevelInfo,
            IgnoreRecordNotFoundError: true,
        }),
    })
    if err != nil {
        return nil, err
    }

    // 自动迁移
    _ = db.AutoMigrate(migrateTables...)

    log.Info(ctx, "数据库连接成功")

    // 返回值将被存入上下文
    return db, nil
}

Redis 初始化

internal/app/startup/init_redis.go
package startup

import (
    "context"

    xEnv "github.com/bamboo-services/bamboo-base-go/defined/env"
    xLog "github.com/bamboo-services/bamboo-base-go/common/log"
    "github.com/redis/go-redis/v9"
)

// InitRedis Redis 初始化节点
func InitRedis(ctx context.Context) (any, error) {
    log := xLog.WithName(xLog.NamedINIT)
    log.Debug(ctx, "正在连接 Redis...")

    rdb := redis.NewClient(&redis.Options{
        Addr:     xEnv.GetEnvString(xEnv.NoSqlHost, "localhost") + ":" + xEnv.GetEnvString(xEnv.NoSqlPort, "6379"),
        Password: xEnv.GetEnvString(xEnv.NoSqlPass, ""),
        DB:       xEnv.GetEnvInt(xEnv.NoSqlDatabase, 0),
        PoolSize: xEnv.GetEnvInt(xEnv.NoSqlPoolSize, 10),
    })

    log.Info(ctx, "Redis 连接成功")

    // 返回值将被存入上下文
    return rdb, nil
}

实体定义

internal/entity/user.go
package entity

import (
    xModels "github.com/bamboo-services/bamboo-base-go/major/models"
    xSnowflake "github.com/bamboo-services/bamboo-base-go/common/snowflake"
)

// User 用户实体
type User struct {
    xModels.BaseEntity                    // 继承基础实体(ID、CreatedAt、UpdatedAt)
    Username string `gorm:"type:varchar(64);uniqueIndex" json:"username"`
    Email    string `gorm:"type:varchar(128);uniqueIndex" json:"email"`
    Password string `gorm:"type:varchar(256)" json:"-"`
}

// GetGene 返回用户基因,用于雪花 ID 生成
func (u *User) GetGene() xSnowflake.Gene {
    return xSnowflake.GeneUser
}

路由注册

internal/app/route/route.go
package route

import (
    xMiddle "github.com/bamboo-services/bamboo-base-go/major/middleware"
    xReg "github.com/bamboo-services/bamboo-base-go/major/register"
    xRoute "github.com/bamboo-services/bamboo-base-go/major/route"
    "your-project/internal/handler"
)

func NewRoute(xReg *xReg.Reg) {
    engine := xReg.Serve

    // 全局异常处理
    engine.NoMethod(xRoute.NoMethod)
    engine.NoRoute(xRoute.NoRoute)

    // 全局中间件
    engine.Use(xMiddle.ResponseMiddleware)
    engine.Use(xMiddle.ReleaseAllCors)
    engine.Use(xMiddle.AllowOption)

    // API 路由组
    api := engine.Group("/api/v1")
    {
        userHandler := handler.NewUserHandler()

        // 用户路由
        user := api.Group("/user")
        user.POST("/register", userHandler.Register)
        user.POST("/login", userHandler.Login)
    }
}

Handler 编写

internal/handler/user.go
package handler

import (
    xError "github.com/bamboo-services/bamboo-base-go/common/error"
    xLog "github.com/bamboo-services/bamboo-base-go/common/log"
    xResult "github.com/bamboo-services/bamboo-base-go/major/result"
    xUtil "github.com/bamboo-services/bamboo-base-go/common/utility"
    "github.com/gin-gonic/gin"
    apiUser "your-project/api/user"
    "your-project/internal/logic"
)

type UserHandler struct {
    log     *xLog.LogNamedLogger
}

func NewUserHandler() *UserHandler {
    return &UserHandler{
        log:     xLog.WithName(xLog.NamedCONT),
    }
}

// Register 用户注册
func (h *UserHandler) Register(c *gin.Context) {
    h.log.Info(c, "开始处理用户注册请求")

    // 1. 验证并绑定数据
    req := xUtil.BindData(c, &apiUser.RegisterRequest{})
    if req == nil {
        return
    }

    // 2. 调用业务逻辑(通过上下文获取资源)
    user, xErr := logic.NewUserLogic().CreateUser(c, req.Username, req.Email, req.Password)
    if xErr != nil {
        _ = c.Error(xErr)  // 传递给错误中间件处理
        return
    }

    // 3. 返回成功响应
    xResult.SuccessHasData(c, "注册成功", user)
}

// Login 用户登录
func (h *UserHandler) Login(c *gin.Context) {
    h.log.Info(c, "开始处理用户登录请求")

    req := xUtil.BindData(c, &apiUser.LoginRequest{})
    if req == nil {
        return
    }

    // 查找用户
    user, xErr := logic.NewUserLogic().GetUserByUsername(c, req.Username)
    if xErr != nil {
        _ = c.Error(xErr)
        return
    }

    // 验证密码
    if !logic.NewUserLogic().VerifyPassword(user.Password, req.Password) {
        // 创建错误并传递给中间件
        _ = c.Error(xError.NewError(c.Request.Context(), xError.Unauthorized, "用户名或密码错误", false))
        return
    }

    xResult.SuccessHasData(c, "登录成功", user)
}

Logic 层编写

internal/logic/user.go
package logic

import (
    xError "github.com/bamboo-services/bamboo-base-go/common/error"
    xLog "github.com/bamboo-services/bamboo-base-go/common/log"
    xUtil "github.com/bamboo-services/bamboo-base-go/common/utility"
    xCtxUtil "github.com/bamboo-services/bamboo-base-go/common/utility/context"
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
    "your-project/internal/entity"
)

type UserLogic struct {
    log *xLog.LogNamedLogger
}

func NewUserLogic() *UserLogic {
    return &UserLogic{
        log: xLog.WithName(xLog.NamedLOGC),
    }
}

// CreateUser 创建用户
func (l *UserLogic) CreateUser(c *gin.Context, username, email, password string) (*entity.User, *xError.Error) {
    l.log.Info(c, "开始创建用户")

    // 从上下文获取数据库连接
    ctx := c.Request.Context()
    db := xCtxUtil.MustGetDB(ctx)

    // 检查用户名是否存在
    var count int64
    db.Model(&entity.User{}).Where("username = ?", username).Count(&count)
    if count > 0 {
        return nil, xError.NewError(c.Request.Context(), xError.Existed, "用户名已存在", false)
    }

    // 加密密码
    hashedPassword, err := xUtil.HashPassword(password)
    if err != nil {
        return nil, xError.NewInternalServerError(c.Request.Context(), "密码加密失败", err)
    }

    // 生成雪花 ID(从上下文获取节点)
    userID := xCtxUtil.MustGenerateGeneSnowflakeID(ctx, entity.User{}.GetGene())

    user := &entity.User{
        Username: username,
        Email:    email,
        Password: hashedPassword,
    }
    user.ID = userID

    if err := db.Create(user).Error; err != nil {
        return nil, xError.NewInternalServerError(c.Request.Context(), "创建用户失败", err)
    }

    return user, nil
}

// GetUserByUsername 根据用户名获取用户
func (l *UserLogic) GetUserByUsername(c *gin.Context, username string) (*entity.User, *xError.Error) {
    // 从上下文获取数据库连接
    db := xCtxUtil.MustGetDB(c.Request.Context())

    var user entity.User
    if err := db.Where("username = ?", username).First(&user).Error; err != nil {
        if err == gorm.ErrRecordNotFound {
            return nil, xError.NewError(c.Request.Context(), xError.UserNotFound, "用户不存在", false, err)
        }
        return nil, xError.NewInternalServerError(c.Request.Context(), "查询用户失败", err)
    }
    return &user, nil
}

// VerifyPassword 验证密码
func (l *UserLogic) VerifyPassword(hashedPassword, password string) bool {
    return xUtil.VerifyPassword(password, hashedPassword) == nil
}

API 结构定义

api/user/user.go
package user

import "your-project/internal/entity"

// RegisterRequest 注册请求
type RegisterRequest struct {
    Username string `json:"username" binding:"required,min=3,max=32"`
    Email    string `json:"email" binding:"required,email"`
    Password string `json:"password" binding:"required,min=6"`
}

// LoginRequest 登录请求
type LoginRequest struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

// UserResponse 用户响应
type UserResponse struct {
    *entity.User
    Token string `json:"token,omitempty"`
}

运行项目

# 启动服务
go run main.go

启动成功后,你将看到类似输出:

2024-01-15 10:00:00 [INFO] [INIT] 初始化系统上下文
2024-01-15 10:00:00 [INFO] [INIT] 数据库连接成功
2024-01-15 10:00:00 [INFO] [INIT] Redis 连接成功
2024-01-15 10:00:00 [INFO] [MAIN] 服务器启动成功 addr=http://localhost:8080

测试接口

# 注册用户
curl -X POST http://localhost:8080/api/v1/user/register \
  -H "Content-Type: application/json" \
  -d '{"username":"test","email":"test@example.com","password":"123456"}'

# 用户登录
curl -X POST http://localhost:8080/api/v1/user/login \
  -H "Content-Type: application/json" \
  -d '{"username":"test","password":"123456"}'

下一步

On this page