关于 golang-web
golang-web 是面向 Go 服务端开发者的 Web 架构技能,介绍项目结构、依赖装配、分层设计、配置和运维模式,并建议通过 LiteLLM 使用 LLM API。
主要功能
- 01cmd/internal/pkg 项目布局
- 02显式装配依赖
- 03基于接口的分层设计
- 04带上下文的错误封装
- 05Viper 配置模式
- 06通过 LiteLLM 代理访问 LLM API
适用场景
- 01规划新的 Go Web 项目结构
- 02拆分处理器、服务与仓储层
- 03使用 YAML 和环境变量配置应用
- 04通过 LiteLLM 连接 LLM API
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ majiayu000/ spellbook --skill golang-web安装后在 Claude Code 中输入/golang-web确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
reference/architecture.mdpatterns.mdtech-stack.mdtemplates/project/config.yamlgo.modMakefiletemplates/project/cmd/myapp/main.gotemplates/project/configs/config.gotemplates/project/internal/handlers/user.gotemplates/project/internal/models/user.gotemplates/project/internal/repositories/user.gotemplates/project/internal/router/router.gotemplates/project/internal/services/user.gotemplates/project/pkg/database/database.gotemplates/project/pkg/errors/errors.gotemplates/project/pkg/response/response.gotemplates/project/pkg/server/server.go详细介绍
Go Web 架构
核心原则
- 标准目录结构 — 遵循 cmd/internal/pkg 约定
- 显式依赖 — 在 main.go 中装配依赖,不使用全局变量
- 接口驱动 — 在使用接口的地方定义接口,而不是在实现接口的地方定义
- 错误包装 — 为错误添加上下文并使用错误代码
- 不考虑向后兼容 — 删除,不要弃用。直接修改
- 使用 LiteLLM 调用大模型 API — 所有大模型集成均使用 LiteLLM 代理
不考虑向后兼容
删除未使用的代码。直接修改。不使用兼容层。
// ❌ BAD: Deprecated function kept around
// Deprecated: Use NewUserService instead
func CreateUserService() *UserService { ... }
// ❌ BAD: Alias for renamed types
type OldName = NewName // "for backwards compatibility"
// ❌ BAD: Unused parameters
func Process(_ context.Context, data Data) { ... }
// ✅ GOOD: Just delete and update all usages
func NewUserService(repo UserRepository) *UserService { ... }
使用 LiteLLM 调用大模型 API
使用 LiteLLM 代理。不要直接调用服务提供商的 API。
// adapters/llm/client.go
package llm
import (
"github.com/sashabaranov/go-openai"
)
// Connect to LiteLLM proxy using OpenAI-compatible SDK
func NewClient(cfg Config) *openai.Client {
config := openai.DefaultConfig(cfg.APIKey)
config.BaseURL = cfg.BaseURL // LiteLLM proxy URL
return openai.NewClientWithConfig(config)
}
快速开始
1. 初始化项目
mkdir myapp && cd myapp
go mod init github.com/yourname/myapp
# Install core dependencies
go get github.com/gin-gonic/gin
go get github.com/spf13/viper
go get github.com/sirupsen/logrus
go get gorm.io/gorm
2. 应用技术栈
| 层 | 建议 |
|---|---|
| HTTP 框架 | Gin / Chi / Echo |
| 配置 | Viper |
| 日志记录 | Logrus / Zap / Slog |
| 数据库 ORM | GORM / sqlx / sqlc |
| 验证 | go-playground/validator |
| 测试 | testify / go test |
版本策略
始终获取最新版本。模板中不要固定版本。
# Always fetch latest
go get -u github.com/gin-gonic/gin
go get -u ./...
# go.mod handles version locking
# go.sum ensures reproducible builds
3. 使用标准结构
myapp/
├── cmd/
│ └── myapp/
│ └── main.go # Entry point, dependency wiring
├── configs/
│ └── config.go # Configuration struct + loader
├── internal/ # Private application code
│ ├── handlers/ # HTTP handlers
│ ├── services/ # Business logic
│ ├── repositories/ # Data access
│ ├── models/ # Domain models
│ ├── middleware/ # HTTP middleware
│ └── router/ # Route definitions
├── pkg/ # Public reusable packages
│ ├── errors/ # Error types
│ ├── logger/ # Logging setup
│ ├── response/ # Unified response format
│ └── database/ # Database connection
├── config.yaml # Configuration file
├── Makefile # Build automation
├── Dockerfile
└── go.mod
架构分层
cmd/ — 入口
在此处装配所有依赖。不包含业务逻辑。
// cmd/myapp/main.go
func main() {
// Load config
cfg := configs.Load()
// Initialize infrastructure
db := database.New(cfg.Database)
cache := cache.New(cfg.Redis)
logger := logger.New(cfg.Log)
// Initialize repositories
userRepo := repositories.NewUserRepository(db)
// Initialize services
userService := services.NewUserService(userRepo)
// Initialize handlers
userHandler := handlers.NewUserHandler(userService)
// Setup router
r := router.Setup(cfg, userHandler)
// Start server with graceful shutdown
server.Run(r, cfg.Server)
}
internal/ — 私有业务代码
handlers/ — HTTP 层
// internal/handlers/user.go
type UserHandler struct {
service services.UserService
}
func NewUserHandler(s services.UserService) *UserHandler {
return &UserHandler{service: s}
}
func (h *UserHandler) Create(c *gin.Context) {
var input CreateUserInput
if err := c.ShouldBindJSON(&input); err != nil {
response.Error(c, errors.ErrInvalidParams)
return
}
user, err := h.service.Create(c.Request.Context(), input)
if err != nil {
response.Error(c, err)
return
}
response.Success(c, user)
}
services/ — 业务逻辑
// internal/services/user.go
type UserService interface {
Create(ctx context.Context, input CreateUserInput) (*models.User, error)
GetByID(ctx context.Context, id string) (*models.User, error)
}
type userService struct {
repo repositories.UserRepository
}
func NewUserService(repo repositories.UserRepository) UserService {
return &userService{repo: repo}
}
func (s *userService) Create(ctx context.Context, input CreateUserInput) (*models.User, error) {
existing, _ := s.repo.FindByEmail(ctx, input.Email)
if existing != nil {
return nil, errors.ErrUserExists
}
user := &models.User{
ID: uuid.New().String(),
Email: input.Email,
Name: input.Name,
}
return s.repo.Save(ctx, user)
}
repositories/ — 数据访问
// internal/repositories/user.go
type UserRepository interface {
FindByID(ctx context.Context, id string) (*models.User, error)
FindByEmail(ctx context.Context, email string) (*models.User, error)
Save(ctx context.Context, user *models.User) (*models.User, error)
Delete(ctx context.Context, id string) error
}
type userRepository struct {
db *gorm.DB
}
func NewUserRepository(db *gorm.DB) UserRepository {
return &userRepository{db: db}
}
func (r *userRepository) FindByID(ctx context.Context, id string) (*models.User, error) {
var user models.User
if err := r.db.WithContext(ctx).First(&user, "id = ?", id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return &user, nil
}
pkg/ — 可复用包
errors/ — 错误处理
// pkg/errors/errors.go
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Cause error `json:"-"`
}
func (e *AppError) Error() string { return e.Message }
func (e *AppError) Unwrap() error { return e.Cause }
func New(code int, message string) *AppError {
return &AppError{Code: code, Message: message}
}
func Wrap(err error, code int, message string) *AppError {
return &AppError{Code: code, Message: message, Cause: err}
}
// Predefined errors
var (
ErrInternal = New(500, "internal server error")
ErrInvalidParams = New(400, "invalid parameters")
ErrNotFound = New(404, "resource not found")
ErrUnauthorized = New(401, "unauthorized")
ErrUserExists = New(409, "user already exists")
)
response/ — 统一响应
// pkg/response/response.go
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func Success(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Response{
Code: 0,
Message: "success",
Data: data,
})
}
func Error(c *gin.Context, err error) {
var appErr *errors.AppError
if errors.As(err, &appErr) {
c.JSON(appErr.Code/100, Response{
Code: appErr.Code,
Message: appErr.Message,
})
return
}
c.JSON(http.StatusInternalServerError, Response{
Code: 500,
Message: "internal server error",
})
}
配置
Viper + YAML + 环境变量
// configs/config.go
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
Log LogConfig `mapstructure:"log"`
LLM LLMConfig `mapstructure:"llm"`
}
type LLMConfig struct {
BaseURL string `mapstructure:"base_url"`
APIKey string `mapstructure:"api_key"`
DefaultModel string `mapstructure:"default_model"`
}
func Load() *Config {
viper.SetConfigFile("config.yaml")
viper.AutomaticEnv()
viper.SetEnvPrefix("APP")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// Defaults
viper.SetDefault("server.port", 8080)
viper.SetDefault("llm.base_url", "http://localhost:4000")
viper.SetDefault("llm.default_model", "gpt-4o")
viper.ReadInConfig()
var cfg Config
viper.Unmarshal(&cfg)
return &cfg
}
优雅关闭
// pkg/server/server.go
func Run(handler http.Handler, cfg ServerConfig) {
srv := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Port),
Handler: handler,
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)
}
Makefile
.PHONY: build run test lint clean
APP_NAME=myapp
build:
go build -o bin/$(APP_NAME) ./cmd/$(APP_NAME)
run:
go run ./cmd/$(APP_NAME)
dev:
air
test:
go test -v ./...
lint:
golangci-lint run
clean:
rm -rf bin/
tidy:
go mod tidy
upgrade:
go get -u ./...
go mod tidy
检查清单
## Project Setup
- [ ] Go 1.21+ installed
- [ ] Standard directory structure (cmd/internal/pkg)
- [ ] go.mod initialized
- [ ] Makefile created
## Architecture
- [ ] Dependencies wired in main.go
- [ ] Handlers → Services → Repositories layers
- [ ] Interfaces defined at usage site
- [ ] No circular dependencies
## Infrastructure
- [ ] Configuration with Viper
- [ ] Structured logging
- [ ] Custom error types
- [ ] Unified response format
- [ ] Graceful shutdown
## Quality
- [ ] Tests for services
- [ ] golangci-lint configured
- [ ] go vet passes
- [ ] Race detection tested
另请参阅
- reference/architecture.md — 详细的架构模式
- reference/tech-stack.md — 技术栈对比
- reference/patterns.md — Go 设计模式
评论
支持 Markdown