mcpField
G
golang-web

golang-web

github.com/majiayu000开发28899 次安装今天更新
代码仓库

关于 golang-web

golang-web 是面向 Go 服务端开发者的 Web 架构技能,介绍项目结构、依赖装配、分层设计、配置和运维模式,并建议通过 LiteLLM 使用 LLM API。

主要功能

  1. 01cmd/internal/pkg 项目布局
  2. 02显式装配依赖
  3. 03基于接口的分层设计
  4. 04带上下文的错误封装
  5. 05Viper 配置模式
  6. 06通过 LiteLLM 代理访问 LLM API

适用场景

  1. 01规划新的 Go Web 项目结构
  2. 02拆分处理器、服务与仓储层
  3. 03使用 YAML 和环境变量配置应用
  4. 04通过 LiteLLM 连接 LLM API

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/majiayu000/spellbook --skill golang-web

安装后在 Claude Code 中输入/golang-web确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

reference/
architecture.md
patterns.md
tech-stack.md
templates/project/
config.yaml
go.mod
Makefile
templates/project/cmd/myapp/
main.go
templates/project/configs/
config.go
templates/project/internal/handlers/
user.go
templates/project/internal/models/
user.go
templates/project/internal/repositories/
user.go
templates/project/internal/router/
router.go
templates/project/internal/services/
user.go
templates/project/pkg/database/
database.go
templates/project/pkg/errors/
errors.go
templates/project/pkg/response/
response.go
templates/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
数据库 ORMGORM / 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

另请参阅

评论

支持 Markdown