快速开始
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 tidyv1.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.yaml与buf.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 文件:
# 调试模式
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),业务侧无需也不能手写这些节点的初始化:
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"
)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 来源 | 额外变量 |
|---|---|---|---|---|
| PostgreSQL | postgres | _ "…/plugins/database/postgres" | PostgresFromEnv() | DATABASE_TIMEZONE |
| MySQL | mysql | _ "…/plugins/database/mysql" | MySQLFromEnv() | DATABASE_CHARSET |
| SQLite | sqlite | _ "…/plugins/database/sqlite" | SQLiteFromEnv() | DATABASE_PATH |
| Oracle | oracle | _ "…/plugins/database/oracle" | OracleFromEnv() | DATABASE_SERVICE_NAME / DATABASE_LIB_DIR |
| SQL Server | sqlserver | _ "…/plugins/database/sqlserver" | SQLServerFromEnv() | — |
若设置了
DATABASE_DSN,则直接使用该完整连接串,跳过分项拼装。DATABASE_PREFIX会自动写入 GORMNamingStrategy.TablePrefix。
自动迁移与数据初始化
通过二级选项声明迁移表与建表后的数据初始化回调,框架在 Register 阶段建连成功后自动执行:
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 | 自动拼装内容 |
|---|---|---|
| Redis | redis | NOSQL_HOST / NOSQL_PORT / NOSQL_USER / NOSQL_PASS / NOSQL_DATABASE / NOSQL_POOL_SIZE |
| Memory | memory | NOSQL_MEMORY_DEFAULT_TTL / NOSQL_MEMORY_MAX_ENTRIES / NOSQL_MEMORY_SHARD_COUNT |
业务侧通过
ctx.Value(xCtx.RedisClientKey)获取*redis.Client,或通过ctx.Value(xCtx.CacheManagerKey)获取*xCache.Manager使用泛型缓存接口。详见 缓存系统。
实体定义
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
}路由注册
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 编写
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 层编写
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 结构定义
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"}'