竹简文档

快速开始

5 分钟快速上手 Bamboo Base Go

快速开始

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

安装

go get github.com/bamboo-services/bamboo-base-go/major
go get github.com/bamboo-services/bamboo-base-go/common
go get github.com/bamboo-services/bamboo-base-go/defined
go get github.com/bamboo-services/bamboo-base-go/plugins/grpc   # 可选
go get github.com/bamboo-services/bamboo-base-go/plugins/async  # 可选
go get github.com/bamboo-services/bamboo-base-go/plugins/cron   # 可选
go get github.com/bamboo-services/bamboo-base-go/plugins/email  # 可选
go get github.com/bamboo-services/bamboo-base-go/plugins/database/postgres   # v1.2.0 起数据库驱动插件化,按需安装
go get github.com/bamboo-services/bamboo-base-go/plugins/database/mysql      # 按需
go get github.com/bamboo-services/bamboo-base-go/plugins/database/sqlite     # 按需
go get github.com/bamboo-services/bamboo-base-go/plugins/database/oracle     # 按需(需 CGO + Oracle Instant Client)
go get github.com/bamboo-services/bamboo-base-go/plugins/database/sqlserver  # 按需
go mod tidy

v1.2.0 数据库驱动插件化:框架不再内置 GORM 驱动,数据库驱动拆分为独立插件 plugins/database/*。使用哪个数据库就安装并空白导入哪个插件(如 _ "github.com/bamboo-services/bamboo-base-go/plugins/database/postgres"),否则启动时会在 DatabaseInit 阶段报「不支持的数据库驱动」。

快速开始默认使用声明式配置xOption)装配数据库与缓存,无需手写初始化节点。如需自定义初始化逻辑(第三方 SDK 等),参见 节点初始化声明式配置

工具链与命令(接入方 vs 维护方)

bamboo-base-go 同时面向 业务项目接入基础库仓库维护,两类场景的命令不同:

场景推荐命令说明
业务项目接入go get ... + go mod tidy只安装你实际 import 的模块
本地启动业务服务go run .启动当前业务项目
业务项目测试go test ./...运行当前业务项目测试
维护 bamboo-base-go 仓库make tidy / make test / make vet统一执行依赖整理、测试和静态检查
生成 gRPC 代码make proto封装 Buf 生成流程

plugins/grpc 目录包含 buf.yamlbuf.gen.yaml。若在仓库根目录执行 make proto 提示找不到 Buf 配置,请切到 plugins/grpc 后执行 buf generate

项目结构

推荐的项目结构:

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_DRIVER 决定声明式装配的驱动:mysql / postgres / sqlite / oracle / sqlserver / none
DATABASE_DRIVER=postgres
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_DRIVER 决定声明式装配的缓存后端:redis / memory / none
NOSQL_DRIVER=redis
NOSQL_HOST=localhost
NOSQL_PORT=6379
NOSQL_USER=
NOSQL_PASS=
NOSQL_DATABASE=0
NOSQL_POOL_SIZE=10
NOSQL_PREFIX=

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

# 邮件服务配置(可选)
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USER=your_email@example.com
EMAIL_PASS=your_password
EMAIL_FROM=noreply@example.com
EMAIL_FROM_NAME=Bamboo Service
EMAIL_TLS=starttls

入口文件

通过 声明式配置xOption)装配数据库、缓存与路由,框架根据 opts 自动注册保留键(DatabaseKey / CacheManagerKey / RedisClientKey / SnowflakeNodeKey),业务侧无需也不能手写这些节点的初始化:

main.go
package main

import (
    "context"

    xLog "github.com/bamboo-services/bamboo-base-go/common/log"
    xMain "github.com/bamboo-services/bamboo-base-go/major/main"
    xOption "github.com/bamboo-services/bamboo-base-go/major/option"
    xOptCache "github.com/bamboo-services/bamboo-base-go/major/option/cache"
    xOptDatabase "github.com/bamboo-services/bamboo-base-go/major/option/database"
    xReg "github.com/bamboo-services/bamboo-base-go/major/register"

    // v1.2.0 起数据库驱动插件化:FromEnv 按 DATABASE_DRIVER=postgres 需空白导入对应插件
    _ "github.com/bamboo-services/bamboo-base-go/plugins/database/postgres"

    "your-project/internal/app/route"
    "your-project/internal/entity"
)

func main() {
    // v1.0.5 起:opts 直接传给 Register,声明式装配内置组件
    // DatabaseKey / CacheManagerKey / RedisClientKey / SnowflakeNodeKey
    // 由框架根据 opts 自动注册,业务侧不能在 nodeList 中重复注册
    reg := xReg.Register(context.Background(), nil,
        // 数据库:从环境变量装配(DATABASE_DRIVER=postgres,需导入 postgres 驱动插件)
        xOption.WithDatabase(
            xOptDatabase.FromEnv(),
            xOptDatabase.WithAutoMigrate(&entity.User{}),
        ),
        // 缓存:从环境变量装配(NOSQL_DRIVER=redis)
        xOption.WithCache(xOptCache.FromEnv()),
        // 路由
        xOption.WithRoute(route.NewRoute),
    )

    log := xLog.WithName(xLog.NamedMAIN)

    // Runner 启动 HTTP 服务并优雅关闭
    xMain.Runner(reg, log, nil)
}

也可将 opts 传给 xMain.Runner(reg, log, opts) 而非 Register,两种方式等价。区别是传给 Register 时业务自定义节点能立即从 ctx 取到 DB / 缓存;传给 Runner 时仅在 Runner 阶段装配。详见 服务运行时

可选: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(),
    ),
)

// opts 已通过 Register 装配(方式 A),Runner 传 nil 并附加 gRPC 任务
xMain.Runner(reg, log, nil, grpcTask)

完整说明参见:gRPC 运行时

声明式装配:数据库

入口文件中 xOption.WithDatabase(xOptDatabase.FromEnv(), ...) 会从环境变量自动装配数据库。框架根据 DATABASE_DRIVER 选择对应驱动的 DSN 拼装函数,建连后将 *gorm.DB 注入 ctx.DatabaseKey,业务侧通过 xCtxUtil.MustGetDB(ctx) 获取。

v1.2.0 起必须导入驱动插件FromEnv 仅装配驱动枚举与 DSN 字符串,打开连接时仍需从 common 层注册表解析 Dialector 工厂,因此必须空白导入对应驱动插件。下表「驱动插件」列为对应 import 路径(前缀均为 github.com/bamboo-services/bamboo-base-go/plugins/database/)。

FromEnv 读取的环境变量

驱动DATABASE_DRIVER驱动插件(空白导入)自动拼装的 DSN 来源额外变量
PostgreSQLpostgres_ "…/plugins/database/postgres"PostgresFromEnv()DATABASE_TIMEZONE
MySQLmysql_ "…/plugins/database/mysql"MySQLFromEnv()DATABASE_CHARSET
SQLitesqlite_ "…/plugins/database/sqlite"SQLiteFromEnv()DATABASE_PATH
Oracleoracle_ "…/plugins/database/oracle"OracleFromEnv()DATABASE_SERVICE_NAME / DATABASE_LIB_DIR
SQL Serversqlserver_ "…/plugins/database/sqlserver"SQLServerFromEnv()

若设置了 DATABASE_DSN,则直接使用该完整连接串,跳过分项拼装。DATABASE_PREFIX 会自动写入 GORM NamingStrategy.TablePrefix

自动迁移与数据初始化

通过二级选项声明迁移表与建表后的数据初始化回调,框架在 Register 阶段建连成功后自动执行:

main.go(片段)
xOption.WithDatabase(
    xOptDatabase.FromEnv(),
    // AutoMigrate 目标表,可多次叠加
    xOptDatabase.WithAutoMigrate(&entity.User{}, &entity.Role{}),
    // 建表后数据初始化回调,按注册顺序执行
    xOptDatabase.WithPrepare(seedRoles),
)

// seedRoles 示例:建表后插入初始角色数据
func seedRoles(db *gorm.DB) error {
    return db.Create([]*entity.Role{
        {Name: "admin"},
        {Name: "user"},
    }).Error
}

如需完全手写数据库初始化逻辑(自定义 GORM 配置、多数据源等),可不用 WithDatabase,改为通过 xRegNode.RegNodeList 注册自定义键(非 DatabaseKey)的节点。详见 节点初始化

声明式装配:缓存

入口文件中 xOption.WithCache(xOptCache.FromEnv()) 会从环境变量自动装配缓存后端。框架根据 NOSQL_DRIVER 选择 Redis 或 Memory,建连后将 *xCache.Manager 注入 ctx.CacheManagerKey,Redis 后端额外将 *redis.Client 注入 ctx.RedisClientKey

FromEnv 读取的环境变量

后端NOSQL_DRIVER自动拼装内容
RedisredisNOSQL_HOST / NOSQL_PORT / NOSQL_USER / NOSQL_PASS / NOSQL_DATABASE / NOSQL_POOL_SIZE
MemorymemoryNOSQL_MEMORY_DEFAULT_TTL / NOSQL_MEMORY_MAX_ENTRIES / NOSQL_MEMORY_SHARD_COUNT

业务侧通过 ctx.Value(xCtx.RedisClientKey) 获取 *redis.Client,或通过 ctx.Value(xCtx.CacheManagerKey) 获取 *xCache.Manager 使用泛型缓存接口。详见 缓存系统

实体定义

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 (
    "context"

    "github.com/gin-gonic/gin"

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

func NewRoute(ctx context.Context, serve *gin.Engine) {
    // 全局异常处理
    serve.NoMethod(xRoute.NoMethod)
    serve.NoRoute(xRoute.NoRoute)

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

    // API 路由组
    api := serve.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.Bind(c, &apiUser.RegisterRequest{}).Data()
    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.Bind(c, &apiUser.LoginRequest{}).Data()
    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.Password().EncryptString(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.Password().IsValid(password, hashedPassword)
}

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 .

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

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