当前位置: 首页 > ops >正文

go项目实战

结合之前的基础,我们这里给出自己的实践环节。先给出完整的步骤

  1. 创建项目目录结构。
  2. 初始化 Go 模块。
  3. 添加必要的依赖。
  4. 编写 config.yaml 配置文件。
  5. 定义 Proto 文件并编译。
  6. 定义 Model。
  7. 实现 Repository 层。
  8. 实现 Service 层。
  9. 实现 Handler 层。
  10. 实现 Server 层。
  11. 实现数据库初始化逻辑。
  12. 启动服务

创建项目目录结构

mkdir -p scaling-group-service/{api/proto/open/v1,cmd,config,internal/{model,repository,service,handler,server,utils},go.mod}

项目目录如下

scaling-group-service % tree
.
├── api                                     # 存放 gRPC 接口定义及相关生成代码
│   └── proto                               # Protocol Buffers 定义文件
│       └── open                            # 开放 API 命名空间(可理解为模块或分类)
│           └── v1                          # API 版本 v1
│               ├── scaling_group.pb.go     # 由 proto 编译生成的 Go 数据结构(pb)
│               ├── scaling_group.pb.gw.go  # gRPC-Gateway 生成的 HTTP 路由代理代码
│               ├── scaling_group.proto     # 原始的 gRPC 接口定义文件(proto3 格式)
│               └── scaling_group_grpc.pb.go# gRPC 服务接口和客户端的 Go 实现代码
├── cmd                                     # 命令行入口目录
│   └── main.go                             # 程序主入口,用于启动 gRPC 和 HTTP 服务
├── config                                  # 配置相关目录
│   ├── config.go                           # 配置加载逻辑(如读取 YAML 文件)
│   └── config.yml                          # YAML 格式的配置文件,包含数据库、端口等配置
├── go.mod                                  # Go 模块描述文件,定义模块路径和依赖
├── go.sum                                  # Go 模块依赖校验文件,记录依赖的哈希值
└── internal                                # 项目核心代码目录(Go 推荐使用 internal)├── handler                             # HTTP 请求处理器(适配 gRPC-Gateway)│   └── scaling_group_handler.go        # ScalingGroup 的 HTTP 请求处理逻辑├── model                               # 数据模型目录│   └── scaling_group.go                # ScalingGroup 的结构体定义,对应数据库表├── repository                          # 数据访问层(DAO),负责与数据库交互│   └── scaling_group_repository.go     # ScalingGroup 的数据库操作逻辑(如 CRUD)├── server                              # 服务启动逻辑目录│   ├── grpc_server.go                  # 启动 gRPC 服务,注册服务实现│   └── http_server.go                  # 启动 HTTP 服务(gRPC-Gateway),注册路由├── service                             # 业务逻辑层目录│   └── scaling_group_service.go        # ScalingGroup 的业务逻辑实现(gRPC 接口实现)└── utils                               # 工具类函数目录└── db.go                           # 数据库连接工具函数(如 PostgreSQL 初始化)

初始化go模块

cd scaling-group-service
go mod init scaling-group-service

生成一个go.mod文件,如下所示:

module scaling-group-servicego 1.23.4

添加依赖

我们需要添加一些必要的依赖,包括Viper, GORM和gRPC等,分别用于读取配置(数据库信息)文件、GORM数据库操作,gRPC等

go get github.com/spf13/viper
go get gorm.io/gorm
go get gorm.io/driver/postgres
go get google.golang.org/grpc
go get github.com/grpc-ecosystem/grpc-gateway/v2
go get google.golang.org/protobuf/cmd/protoc-gen-go
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc

此时go.mod中新增如下内容
在这里插入图片描述

编写配置文件

config/config.yml文件

database:host: localhost # 我是本地启的dockerport: 5432user: postgrespassword: yourpassworddbname: scaling_group_dbsslmode: disabletimezone: Asia/Shanghaiserver:http_port: 8080grpc_port: 50051

其中config\config.go

package configimport ("fmt""github.com/spf13/viper"
)//type Config struct {
//	Database struct {
//		Host     string `yaml:"host"`
//		Port     int    `yaml:"port"`
//		User     string `yaml:"user"`
//		Password string `yaml:"password"`
//		DBName   string `yaml:"dbname"`
//		SSLMode  string `yaml:"sslmode"`
//		Timezone string `yaml:"timezone"`
//	} `yaml:"database"`
//
//	Server struct {
//		HTTPPort int `yaml:"http_port"`
//		GRPCPort int `yaml:"grpc_port"`
//	} `yaml:"server"`
//}type Config struct {Database struct {Host     string `yaml:"host"     mapstructure:"host"`Port     int    `yaml:"port"     mapstructure:"port"`User     string `yaml:"user"     mapstructure:"user"`Password string `yaml:"password" mapstructure:"password"`DBName   string `yaml:"dbname"   mapstructure:"dbname"`SSLMode  string `yaml:"sslmode"  mapstructure:"sslmode"`Timezone string `yaml:"timezone" mapstructure:"timezone"`} `yaml:"database" mapstructure:"database"`Server struct {HTTPPort int `yaml:"http_port" mapstructure:"http_port"`GRPCPort int `yaml:"grpc_port" mapstructure:"grpc_port"`} `yaml:"server" mapstructure:"server"`
}func LoadConfig(path string) (Config, error) {var cfg Configviper.AddConfigPath(path)viper.SetConfigName("config")viper.SetConfigType("yaml")viper.AutomaticEnv()if err := viper.ReadInConfig(); err != nil {return cfg, fmt.Errorf("failed to read config file: %w", err)}fmt.Printf("Raw config: %+v\n", viper.AllSettings())if err := viper.Unmarshal(&cfg); err != nil {return cfg, fmt.Errorf("unable to decode into struct: %w", err)}fmt.Printf("Loaded config: %+v\n", cfg)return cfg, nil
}

定义Proto文件

api/proto/open/v1/scaling_group.proto 中定义服务接口:

syntax = "proto3";package open.v1;option go_package = "scaling-group-service/api/proto/open/v1;v1";
import "google/api/annotations.proto";message ScalingGroup {string id = 1;string name = 2;int32 min_instance_count = 3;int32 max_instance_count = 4;string region = 5;string zone = 6;string status = 7;int64 created_at = 8;
}message CreateScalingGroupRequest {string name = 1;int32 min_instance_count = 2;int32 max_instance_count = 3;string region = 4;string zone = 5;
}message CreateScalingGroupResponse {string id = 1;
}message DeleteScalingGroupRequest {string id = 1;
}message DeleteScalingGroupResponse {}message DescribeScalingGroupRequest {string id = 1;
}message DescribeScalingGroupResponse {ScalingGroup group = 1;
}message ModifyScalingGroupRequest {string id = 1;optional int32 min_instance_count = 2;optional int32 max_instance_count = 3;optional string name = 4;
}message ModifyScalingGroupResponse {}service ScalingGroupService {rpc CreateScalingGroup(CreateScalingGroupRequest) returns (CreateScalingGroupResponse) {option (google.api.http) = {post: "/v1/scaling-groups"body: "*"};}rpc DeleteScalingGroup(DeleteScalingGroupRequest) returns (DeleteScalingGroupResponse) {option (google.api.http) = {delete: "/v1/scaling-groups/{id}"};}rpc DescribeScalingGroup(DescribeScalingGroupRequest) returns (DescribeScalingGroupResponse) {option (google.api.http) = {get: "/v1/scaling-groups/{id}"};}rpc ModifyScalingGroup(ModifyScalingGroupRequest) returns (ModifyScalingGroupResponse) {option (google.api.http) = {put: "/v1/scaling-groups/{id}"body: "*"};}
}    

为了生成 Go 代码,您需要使用 protoc 编译器以及相关的插件。首先,确保您已经安装了protoc和必要的插件。如果尚未安装,请参考以下命令进行安装:

ggo install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest

然后,在项目根目录下运行以下命令来编译 .proto 文件:

scaling-group-service % protoc \--go_out=. \--go_opt=paths=source_relative \--go-grpc_out=. \--go-grpc_opt=paths=source_relative \--grpc-gateway_out=. \--grpc-gateway_opt=paths=source_relative \--openapiv2_out=./swagger \--openapiv2_opt=logtostderr=true \api/proto/open/v1/scaling_group.proto

此时会报错如下:

google/api/annotations.proto: File not found.
launch_template.proto:5:1: Import "google/api/annotations.proto" was not found or had errors.

注意:我们使用了 google/api/annotations.proto,需要下载这个文件到本地或通过 proto import path解析
如果你遇到找不到 google/api/annotations.proto 的问题,可以克隆官方仓库:

mkdir -p third_party/google/api
curl -o third_party/google/api/annotations.proto https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto

然后编译时加上 -Ithird_party 参数。

或者使用

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@latest

之所以要下载这个,因为github.com\grpc-ecosystem\grpc-gateway@latest\third_party\googleapis\google\api目录下就有我们需要的annotations.proto文件。
执行上述下载命令之后,就会将protoc-gen-grpc-gateway下载到电脑的GOPATH下,自己电脑的GOPATH可以通过命令go env查看.

echo $GOPATH
/Users/zhiyu/go
dance@MacBook-Pro v2@v2.27.1 % cd /Users/zhiyu/go/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.14.5/third_party/googleapis 
dance@MacBook-Pro googleapis % ls
LICENSE                 README.grpc-gateway     google

我们引用本地的路劲,现在再次执行protoc

protoc \-I . \-I $(go env GOPATH)/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.16.0/third_party/googleapis \--go_out=. \--go_opt=paths=source_relative \--go-grpc_out=. \--go-grpc_opt=paths=source_relative \--grpc-gateway_out=. \--grpc-gateway_opt=paths=source_relative \api/proto/open/v1/scaling_group.proto

此时会在v1目录下生成两个文件

  1. api/proto/open/v1/scaling_group.pb.go: 包含消息类型和序列化逻辑。
  2. api/proto/open/v1/scaling_group_grpc.pb.go: 包含 gRPC 服务的客户端和服务器端接口。
  3. api/proto/open/v1/scaling_group.pb.gw.go: HTTP 路由绑定代码。

定义Model

internal/model/scaling_group.go 中定义模型:

package model// import "gorm.io/gorm"type ScalingGroup struct {ID               string `gorm:"primaryKey"`Name             stringMinInstanceCount int32MaxInstanceCount int32Region           stringZone             stringStatus           stringCreatedAt        int64
}

实现Repository层

internal/repository/scaling_group_repository.go 中实现数据访问层:

package repositoryimport ("context""errors""gorm.io/gorm""scaling-group-service/internal/model"
)type ScalingGroupRepository struct {db *gorm.DB
}func NewScalingGroupRepository(db *gorm.DB) *ScalingGroupRepository {return &ScalingGroupRepository{db: db}
}func (r *ScalingGroupRepository) Create(ctx context.Context, group *model.ScalingGroup) error {return r.db.WithContext(ctx).Create(group).Error
}func (r *ScalingGroupRepository) GetByID(ctx context.Context, id string) (*model.ScalingGroup, error) {var group model.ScalingGroupif err := r.db.WithContext(ctx).Where("id = ?", id).First(&group).Error; err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {return nil, nil}return nil, err}return &group, nil
}func (r *ScalingGroupRepository) Update(ctx context.Context, group *model.ScalingGroup) error {return r.db.WithContext(ctx).Model(group).Where("id = ?", group.ID).Save(group).Error
}func (r *ScalingGroupRepository) Delete(ctx context.Context, id string) error {return r.db.WithContext(ctx).Where("id = ?", id).Delete(&model.ScalingGroup{}).Error
}

实现Service层

internal/service/scaling_group_service.go 中实现业务逻辑层:

package serviceimport ("context""fmt""scaling-group-service/api/proto/open/v1""scaling-group-service/internal/model""scaling-group-service/internal/repository""time"
)type ScalingGroupService struct {v1.UnimplementedScalingGroupServiceServerrepo *repository.ScalingGroupRepository
}func NewScalingGroupService(repo *repository.ScalingGroupRepository) *ScalingGroupService {return &ScalingGroupService{repo: repo,}
}func (s *ScalingGroupService) CreateScalingGroup(ctx context.Context, req *v1.CreateScalingGroupRequest) (*v1.CreateScalingGroupResponse, error) {// 修改这里:生成 asg- 开头的 IDid := fmt.Sprintf("asg-%d", time.Now().UnixNano())group := &model.ScalingGroup{ID:               id,Name:             req.Name,MinInstanceCount: req.MinInstanceCount,MaxInstanceCount: req.MaxInstanceCount,Region:           req.Region,Zone:             req.Zone,Status:           "active",CreatedAt:        time.Now().Unix(),}if err := s.repo.Create(ctx, group); err != nil {return nil, err}return &v1.CreateScalingGroupResponse{Id: group.ID}, nil
}func (s *ScalingGroupService) DeleteScalingGroup(ctx context.Context, req *v1.DeleteScalingGroupRequest) (*v1.DeleteScalingGroupResponse, error) {if err := s.repo.Delete(ctx, req.Id); err != nil {return nil, err}return &v1.DeleteScalingGroupResponse{}, nil
}func (s *ScalingGroupService) DescribeScalingGroup(ctx context.Context, req *v1.DescribeScalingGroupRequest) (*v1.DescribeScalingGroupResponse, error) {group, err := s.repo.GetByID(ctx, req.Id)if err != nil {return nil, err}if group == nil {return &v1.DescribeScalingGroupResponse{}, nil}return &v1.DescribeScalingGroupResponse{Group: &v1.ScalingGroup{Id:               group.ID,Name:             group.Name,MinInstanceCount: group.MinInstanceCount,MaxInstanceCount: group.MaxInstanceCount,Region:           group.Region,Zone:             group.Zone,Status:           group.Status,CreatedAt:        group.CreatedAt,},}, nil
}func (s *ScalingGroupService) ModifyScalingGroup(ctx context.Context, req *v1.ModifyScalingGroupRequest) (*v1.ModifyScalingGroupResponse, error) {existing, err := s.repo.GetByID(ctx, req.Id)if err != nil || existing == nil {return nil, fmt.Errorf("group not found")}if req.Name != nil {existing.Name = *req.Name}if req.MinInstanceCount != nil {existing.MinInstanceCount = *req.MinInstanceCount}if req.MaxInstanceCount != nil {existing.MaxInstanceCount = *req.MaxInstanceCount}if err := s.repo.Update(ctx, existing); err != nil {return nil, err}return &v1.ModifyScalingGroupResponse{}, nil
}

实现Server层

server/grpc_server.go

package serverimport ("fmt""google.golang.org/grpc""net""scaling-group-service/api/proto/open/v1""scaling-group-service/internal/service"
)func StartGRPCServer(grpcPort int, scalingGroupService *service.ScalingGroupService) error {lis, err := net.Listen("tcp", fmt.Sprintf(":%d", grpcPort))if err != nil {return err}grpcServer := grpc.NewServer()v1.RegisterScalingGroupServiceServer(grpcServer, scalingGroupService)fmt.Printf("gRPC server listening on port %d\n", grpcPort)return grpcServer.Serve(lis)
}

另一个是http_server.go

package serverimport ("context""fmt""google.golang.org/grpc""log""net/http""os""os/signal""time""github.com/grpc-ecosystem/grpc-gateway/v2/runtime""scaling-group-service/internal/handler"
)func StartHTTPServer(ctx context.Context, httpPort int, grpcAddr string) error {mux := runtime.NewServeMux()if err := handler.RegisterHandlers(ctx, mux, grpcAddr, []grpc.DialOption{grpc.WithInsecure()}); err != nil {return err}srv := &http.Server{Addr:    fmt.Sprintf(":%d", httpPort),Handler: mux,}go func() {if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {log.Fatalf("HTTP server ListenAndServe: %v", err)}}()fmt.Printf("HTTP server listening on port %d\n", httpPort)// Graceful shutdownstop := make(chan os.Signal, 1)signal.Notify(stop, os.Interrupt)<-stopctxShutDown, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer func() {cancel()}()if err := srv.Shutdown(ctxShutDown); err != nil {log.Fatalf("HTTP Server Shutdown failed: %v", err)}return nil
}

实现utils

db.go文件

package utilsimport ("fmt""gorm.io/driver/postgres""gorm.io/gorm""scaling-group-service/config""scaling-group-service/internal/model"
)func ConnectDB(cfg config.Config) (*gorm.DB, error) {dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s TimeZone=%s",cfg.Database.Host,cfg.Database.Port,cfg.Database.User,cfg.Database.Password,cfg.Database.DBName,cfg.Database.SSLMode,cfg.Database.Timezone,)db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})if err != nil {return nil, err}err = db.AutoMigrate(&model.ScalingGroup{})if err != nil {return nil, err}return db, nil
}

handler

实现handler/scaling_group_handler.go

package handlerimport ("context""github.com/grpc-ecosystem/grpc-gateway/v2/runtime""google.golang.org/grpc""scaling-group-service/api/proto/open/v1"
)func RegisterHandlers(ctx context.Context, mux *runtime.ServeMux, grpcAddr string, opts []grpc.DialOption) error {conn, err := grpc.DialContext(ctx, grpcAddr, opts...)if err != nil {return err}//defer conn.Close()client := v1.NewScalingGroupServiceClient(conn)return v1.RegisterScalingGroupServiceHandlerClient(ctx, mux, client)
}

入口函数cmd/main

package mainimport ("context""fmt""log""sync""scaling-group-service/config""scaling-group-service/internal/repository""scaling-group-service/internal/server""scaling-group-service/internal/service""scaling-group-service/internal/utils"
)func main() {cfg, err := config.LoadConfig("./config")if err != nil {log.Fatalf("Load config error: %v", err)}fmt.Printf("Http port: %v\n", cfg.Server.HTTPPort)fmt.Printf("Http port: %v\n", cfg.Server.GRPCPort)db, err := utils.ConnectDB(cfg)if err != nil {log.Fatalf("Connect DB error: %v", err)}repo := repository.NewScalingGroupRepository(db)svc := service.NewScalingGroupService(repo)grpcPort := cfg.Server.GRPCPorthttpPort := cfg.Server.HTTPPortgrpcAddr := fmt.Sprintf("localhost:%d", grpcPort)var wg sync.WaitGroupwg.Add(2)go func() {defer wg.Done()if err := server.StartGRPCServer(grpcPort, svc); err != nil {log.Fatalf("Start GRPC server error: %v", err)}}()go func() {defer wg.Done()if err := server.StartHTTPServer(context.Background(), httpPort, grpcAddr); err != nil {log.Fatalf("Start HTTP server error: %v", err)}}()wg.Wait()
}

准备好之后,创建db
使用docker run 创建PostgreSQL

  1. 启动postgreslq

运行 PostgreSQL 容器

docker run --name scaling-db \-e POSTGRES_PASSWORD=yourpassword \-p 5432:5432 \-d postgres:14
  1. 创建库和表

创建数据库

docker exec scaling-db psql -U postgres -c "CREATE DATABASE scaling_group_db;"

注意这里的model结构如下

package model// import "gorm.io/gorm"type ScalingGroup struct {ID               string `gorm:"primaryKey"`Name             stringMinInstanceCount int32MaxInstanceCount int32Region           stringZone             stringStatus           stringCreatedAt        int64
}

所以创建表结构

# 创建表结构
docker exec scaling-db psql -U postgres -d scaling_group_db -c "
docker exec scaling-db psql -U postgres -d scaling_group_db -c "
CREATE TABLE IF NOT EXISTS scaling_groups (id VARCHAR(255) PRIMARY KEY,name VARCHAR(255),min_instance_count INT,max_instance_count INT,region VARCHAR(255),zone VARCHAR(255),status VARCHAR(255),created_at BIGINT
);
"
  1. 查看数据库表
# 列出所有数据库
docker exec scaling-db psql -U postgres -c "\l"
# 查看表结构
docker exec scaling-db psql -U postgres -d scaling_group_db -c "\d scaling_groups"

请求与测试

go run cmd/main.go

在这里插入图片描述

创建资源

curl -X POST http://localhost:8080/v1/scaling-groups \-H "Content-Type: application/json" \-d '{"name": "test-group","min_instance_count": 1,"max_instance_count": 3,"region": "cn-beijing","zone": "cn-beijing-1"}'

在这里插入图片描述

登录数据库查询

docker exec -it scaling-db psql -U postgres -d scaling_group_db

在这里插入图片描述

查询资源

curl http://localhost:8080/v1/scaling-groups/asg-1752661325196631000

修改资源

curl -X PUT http://localhost:8080/v1/scaling-groups/asg-1752661325196631000 \-H "Content-Type: application/json" \-d '{"min_instance_count": 2,"max_instance_count": 5,"name": "updated-name"}'

删除资源

curl -X DELETE http://localhost:8080/v1/scaling-groups/asg-1752661325196631000

Q&A

config/config.go中我们注释了如下的部分

type Config struct {Database struct {Host     string `yaml:"host"`Port     int    `yaml:"port"`User     string `yaml:"user"`Password string `yaml:"password"`DBName   string `yaml:"dbname"`SSLMode  string `yaml:"sslmode"`Timezone string `yaml:"timezone"`} `yaml:"database"`Server struct {HTTPPort int `yaml:"http_port"`GRPCPort int `yaml:"grpc_port"`} `yaml:"server"`
}

执行go run cmd/main.go的时候,发现服务的port是0
在这里插入图片描述
✅ viper 成功读取了 config.yaml 中server.http_portserver.grpc_port
❌ 但 viper.Unmarshal(&cfg) 没有把值映射到结构体中的 Server.HTTPPortServer.GRPCPort你正在使用 viper 的默认解码器(mapstructure),它不支持 yaml tag,只支持 mapstructure tag。也就是说:

HTTPPort int `yaml:"http_port"`

会被 YAML 正确解析,但 不会被 viper.Unmarshal 识别,因为 viper 默认使用 mapstructure 标签。

http://www.xdnf.cn/news/15479.html

相关文章:

  • 高频面试雷区:Java Object六大核心方法源码剖析
  • Linux Ubuntu apt包管理器安装K8s1.30.1+Ingress-Nginx
  • fastadmin中ajax弹窗修改文字为英文
  • AJAX 技术
  • MinIO介绍以及结合SpringBoot的应用场景详解
  • 数据降维方法:PCA
  • 微算法科技研究量子视觉计算,利用量子力学原理提升传统计算机视觉任务的性能
  • flink sql读hive catalog数据,将string类型的时间戳数据排序后写入kafka,如何保障写入kafka的数据是有序的
  • 动态规划题解_打家劫舍【LeetCode】
  • 解决容器dns问题
  • [时序数据库-iotdb]时序数据库iotdb的安装部署
  • Go从入门到精通(25) - 一个简单web项目-实现链路跟踪
  • audiorecord 之 抢占优先级
  • 数据库询问RAG框架Vanna的总体架构
  • CMake基础:覆盖项目开发的五大配套工具
  • 数据结构——顺序表的相关操作
  • 信息学奥赛一本通 1552:【例 1】点的距离
  • 【Keil】C/C++混合编程的简单方法
  • 内存的基础相关知识,什么是内存,内存管理
  • 学习C++、QT---26(QT中实现记事本项目实现文件路径的提示、C++类模板、记事本的行高亮的操作的讲解)
  • LVS(Linux Virtual Server)详细笔记(理论篇)
  • 202507中央城市工作会议
  • 【Java】JUC并发(线程的方法、多线程的同步并发)
  • UE5多人MOBA+GAS 23、制作一个地面轰炸的技能
  • SHAP 值的数值尺度
  • 梳理Bean的创建流程
  • burpsuite使用中遇到的一些问题(bp启动后浏览器无法连接)/如何导入证书
  • GPIO 输入/输出
  • 2025年睿抗机器人开发者大赛CAIP-编程技能赛-高职组(省赛)解题报告 | 珂学家
  • 在Autodl服务器中使用VNC建立图形界面