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

go项目实战二

还是参考阿里云创建伸缩组的接口。挑选几个接口用来继续练习如何开发go项目,作为学习笔记。

项目目录如下所示:

autoscaling-app % tree
.
├── cmd
│ └── main.go
├── go.mod
├── go.sum
├── openapi
│ └── pkg
│ └── proto
│ └── autoscaling.swagger.json
└── pkg
├── config
│ └── config.go
├── model
│ └── scaling_group.go
├── proto
│ ├── autoscaling.pb.go
│ ├── autoscaling.pb.gw.go
│ ├── autoscaling.proto
│ └── autoscaling_grpc.pb.go
├── repository
│ └── scaling_group_repo.go
├── server
│ └── grpc_server
│ └── grpc_server.go
└── service
└── scaling_group_service.go

初始化项目

mkdir -p autoscaling-app/{cmd,pkg/{config,model,repository,service,proto,openapi}}
cd autoscaling-app
go mod init autoscaling-app

标题准备 .proto 文件

创建proto/autoscaling.proto文件

syntax = "proto3";package autoscaling;import "google/api/annotations.proto";option go_package = "pkg/proto;proto";service AutoScalingService {rpc CreateScalingGroup (CreateScalingGroupRequest) returns (CreateScalingGroupResponse) {option (google.api.http) = {post: "/v1/scalingGroups"body: "*"};}rpc GetScalingGroup (GetScalingGroupRequest) returns (GetScalingGroupResponse) {option (google.api.http) = {get: "/v1/scalingGroups/{id}"};}rpc ListScalingGroups (ListScalingGroupsRequest) returns (ListScalingGroupsResponse) {option (google.api.http) = {get: "/v1/scalingGroups"};}rpc UpdateScalingGroup (UpdateScalingGroupRequest) returns (UpdateScalingGroupResponse) {option (google.api.http) = {put: "/v1/scalingGroups/{id}"body: "*"};}rpc DeleteScalingGroup (DeleteScalingGroupRequest) returns (DeleteScalingGroupResponse) {option (google.api.http) = {delete: "/v1/scalingGroups/{id}"};}
}message CreateScalingGroupRequest {string name = 1;int32 min_instance_num = 2;int32 max_instance_num = 3;repeated string subnet_ids = 4;string health_check_type = 5;
}message CreateScalingGroupResponse {string id = 1;int64 created_at = 2;
}message GetScalingGroupRequest {string id = 1;
}message GetScalingGroupResponse {string id = 1;string name = 2;int32 min_instance_num = 3;int32 max_instance_num = 4;repeated string subnet_ids = 5;string health_check_type = 6;int64 created_at = 7;
}message ListScalingGroupsRequest {}message ListScalingGroupsResponse {repeated GetScalingGroupResponse scaling_groups = 1;
}message UpdateScalingGroupRequest {string id = 1;string name = 2;int32 min_instance_num = 3;int32 max_instance_num = 4;repeated string subnet_ids = 5;string health_check_type = 6;
}message UpdateScalingGroupResponse {}message DeleteScalingGroupRequest {string id = 1;
}message DeleteScalingGroupResponse {}

这里为什么不使用post: "/v1/CreateScalingGroups"而是使用post: “/v1/scalingGroups”

因为这违背了 RESTful API 的核心设计原则:

RESTful API 应该“通过标准 HTTP 方法 + 资源路径”来表达操作,而不是在路径中用动词描述操作。

正确做法:使用统一资源路径 + HTTP 方法区分操作
在这里插入图片描述

发现最后是一个:action的方式,这个是GoogleAPI的风格,表示对一个资源的动作,而不是/v1/scalingGroups/{id}/start,如果是/start是RESTful,表示访问资源下的资资源。

安装必要工具和依赖

安装 Protobuf 编译器插件

go 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# PostgreSQL 驱动
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres# 加载 .env 文件
go get -u github.com/joho/godotenv

获取 google/api/annotations.proto

git clone https://github.com/googleapis/googleapis.git $GOPATH/src/github.com/googleapis/googleapis

确保你的 protoc 命令能找到它(通过 -I 参数指定路径)。

# 项目所在的根目录下
autoscaling-app % protoc \--go_out=. \--go-grpc_out=. \--grpc-gateway_out=. \--openapiv2_out=./openapi \-I . \-I $GOPATH/src/github.com/googleapis/googleapis \pkg/proto/autoscaling.proto
    • –go_out=. 会生成 .pb.go 文件,放在 pkg/proto/ 目录下
    • –go-grpc_out=. 会生成 _grpc.pb.go 文件。
    • –grpc-gateway_out=. 会生成 .pb.gw.go 文件。
    • –openapiv2_out=./openapi 会在 ./openapi 下生成 OpenAPI 文档。

配置文件.env

DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=yourpassword
DB_NAME=autoscaling
HTTP_PORT=:8080
GRPC_PORT=:50051

配置管理(pkg/config/config.go)

package configimport (//"github.com/spf13/viper""github.com/joho/godotenv""os"
)type Config struct {DBHost     stringDBPort     stringDBUser     stringDBPassword stringDBName     stringHTTPPort   stringGRPCPort   string
}// 加载配置文件
func LoadConfig() (*Config, error) {err := godotenv.Load()if err != nil {return nil, err}return &Config{DBHost:     os.Getenv("DB_HOST"),DBPort:     os.Getenv("DB_PORT"),DBUser:     os.Getenv("DB_USER"),DBPassword: os.Getenv("DB_PASSWORD"),DBName:     os.Getenv("DB_NAME"),HTTPPort:   os.Getenv("HTTP_PORT"),GRPCPort:   os.Getenv("GRPC_PORT"),}, nil
}

数据模型(pkg/model/scaling_group.go)

package modelimport "github.com/lib/pq"// 定义数据库模型
// omitempty 表示在该字段的值其类型的零值时,在序列化的时候JSON会忽略type ScalingGroup struct {ID             string `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"`Name           string `json:"name" gorm:"not null"`MinInstanceNum int    `json:"min_instance_num" gorm:"not null;check:min_instance_num >= 0"`MaxInstanceNum int    `json:"max_instance_num" gorm:"not null;check:max_instance_num >= min_instance_num"`//SubnetIDs       []string `json:"subnet_ids" gorm:"type:text[]"`SubnetIDs pq.StringArray `json:"subnet_ids" gorm:"type:text[]"` // 修改这里HealthCheckType string `json:"health_check_type" gorm:"not null"`CreatedAt       int64  `json:"created_at" gorm:"autoCreateTime:milli"`
}

数据库 Repository(pkg/repository/scaling_group_repo.go)

package repositoryimport ("autoscaling-app/pkg/model""gorm.io/driver/postgres""gorm.io/gorm""log"
)type ScalingGroupRepository struct {db *gorm.DB
}func NewScalingGroupRepository(dsn string) (*ScalingGroupRepository, error) {db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})if err != nil {return nil, err}log.Println("Connected to PostgreSQL")if err := db.AutoMigrate(&model.ScalingGroup{}); err != nil {return nil, err}return &ScalingGroupRepository{db: db}, nil
}func (r *ScalingGroupRepository) Create(group *model.ScalingGroup) error {return r.db.Create(group).Error
}func (r *ScalingGroupRepository) GetAll() ([]model.ScalingGroup, error) {var groups []model.ScalingGrouperr := r.db.Find(&groups).Errorreturn groups, err
}func (r *ScalingGroupRepository) GetByID(id string) (*model.ScalingGroup, error) {var group model.ScalingGrouperr := r.db.Where("id = ?", id).First(&group).Errorreturn &group, err
}func (r *ScalingGroupRepository) Update(id string, group *model.ScalingGroup) error {return r.db.Where("id = ?", id).Updates(group).Error
}func (r *ScalingGroupRepository) Delete(id string) error {return r.db.Where("id = ?", id).Delete(&model.ScalingGroup{}).Error
}

业务逻辑(pkg/service/scaling_group_service.go)

package service//负责业务逻辑的实现,它不关心请求是如何到达的(HTTP、gRPC 等),只关注如何处理数据和完成业务需求。import ("autoscaling-app/pkg/model""autoscaling-app/pkg/repository"
)type ScalingGroupService struct {repo *repository.ScalingGroupRepository
}func NewScalingGroupService(repo *repository.ScalingGroupRepository) *ScalingGroupService {return &ScalingGroupService{repo: repo}
}func (s *ScalingGroupService) Create(req *model.ScalingGroup) (*model.ScalingGroup, error) {err := s.repo.Create(req)return req, err
}func (s *ScalingGroupService) GetAll() ([]model.ScalingGroup, error) {return s.repo.GetAll()
}func (s *ScalingGroupService) GetByID(id string) (*model.ScalingGroup, error) {return s.repo.GetByID(id)
}func (s *ScalingGroupService) Update(id string, req *model.ScalingGroup) error {return s.repo.Update(id, req)
}func (s *ScalingGroupService) Delete(id string) error {return s.repo.Delete(id)
}

配置 PostgreSQL 服务

启动PostgreSQL

如果你使用的是 Ubuntu 或 Debian:

# 启动 PostgreSQL 服务
sudo service postgresql start# 检查状态
sudo service postgresql status

如果你使用的是 macOS(brew 安装):

brew services start postgresql

创建数据库

sudo -u postgres psql

在 psql 中执行:

-- 创建数据库
CREATE DATABASE autoscaling;-- 创建用户(可选)
CREATE USER autoscaling_user WITH PASSWORD 'yourpassword';-- 授权(可选)
GRANT ALL PRIVILEGES ON DATABASE autoscaling TO autoscaling_user;-- 退出
\q

创建数据表

使用 GORM 自动迁移(推荐)
在我们之前写的 pkg/repository/scaling_group_repository.go 中已经包含了自动迁移:

if err := db.AutoMigrate(&model.ScalingGroup{}); err != nil {return nil, err
}

只要你启动服务,GORM 会自动帮你创建表结构。
✅ 你不需要手动建表,除非你想自定义索引、约束等.

✅ 或者手动建表(可选)

docker run数据库

当然上面的方法也可以使用docker run启动postgresql并创建数据库

# 运行 PostgreSQL 容器
docker run --name autoscaling-postgres \-e POSTGRES_DB=autoscaling \-e POSTGRES_USER=postgres \-e POSTGRES_PASSWORD=yourpassword \-p 5432:5432 \-d \postgres:14

启动之后

ec112f3c6378d028db28ea1d89236785030d2b00118cbf398e0a58ca5bb0b911

#测试连接.输入密码 yourpassword,如果进入 psql 命令行,说明数据库已成功创建。
Book-Pro ~ % psql -h localhost -U postgres -d autoscaling -W
口令:
psql (16.9 (Homebrew), 服务器 14.18 (Debian 14.18-1.pgdg120+1))
输入 “help” 来获取帮助信息.

autoscaling=#

server/grpc_server.go

  • 实现对外暴露的接口(如 gRPC 接口、HTTP 接口)
  • 接收请求、解析参数、调用 service 层处理业务逻辑
  • 返回响应(gRPC 响应、JSON 响应等)
  • 不包含核心业务逻辑
package serverimport ("context""autoscaling-app/pkg/model""autoscaling-app/pkg/proto""autoscaling-app/pkg/service"
)// AutoScalingServer 实现 gRPC 接口
type AutoScalingServer struct {proto.UnimplementedAutoScalingServiceServerService *service.ScalingGroupService // 导出字段,供 main.go 初始化
}func (s *AutoScalingServer) CreateScalingGroup(ctx context.Context, req *proto.CreateScalingGroupRequest) (*proto.CreateScalingGroupResponse, error) {scalingGroup := &model.ScalingGroup{Name:            req.Name,MinInstanceNum:  int(req.MinInstanceNum),MaxInstanceNum:  int(req.MaxInstanceNum),SubnetIDs:       req.SubnetIds,HealthCheckType: req.HealthCheckType,}created, err := s.Service.Create(scalingGroup)if err != nil {return nil, err}return &proto.CreateScalingGroupResponse{Id:        created.ID,CreatedAt: created.CreatedAt,}, nil
}func (s *AutoScalingServer) GetScalingGroup(ctx context.Context, req *proto.GetScalingGroupRequest) (*proto.GetScalingGroupResponse, error) {group, err := s.Service.GetByID(req.Id)if err != nil {return nil, err}return &proto.GetScalingGroupResponse{Id:              group.ID,Name:            group.Name,MinInstanceNum:  int32(group.MinInstanceNum),MaxInstanceNum:  int32(group.MaxInstanceNum),SubnetIds:       group.SubnetIDs,HealthCheckType: group.HealthCheckType,CreatedAt:       group.CreatedAt,}, nil
}func (s *AutoScalingServer) ListScalingGroups(ctx context.Context, req *proto.ListScalingGroupsRequest) (*proto.ListScalingGroupsResponse, error) {groups, err := s.Service.GetAll()if err != nil {return nil, err}var responses []*proto.GetScalingGroupResponsefor _, g := range groups {responses = append(responses, &proto.GetScalingGroupResponse{Id:              g.ID,Name:            g.Name,MinInstanceNum:  int32(g.MinInstanceNum),MaxInstanceNum:  int32(g.MaxInstanceNum),SubnetIds:       g.SubnetIDs,HealthCheckType: g.HealthCheckType,CreatedAt:       g.CreatedAt,})}return &proto.ListScalingGroupsResponse{ScalingGroups: responses}, nil
}func (s *AutoScalingServer) UpdateScalingGroup(ctx context.Context, req *proto.UpdateScalingGroupRequest) (*proto.UpdateScalingGroupResponse, error) {scalingGroup := &model.ScalingGroup{Name:            req.Name,MinInstanceNum:  int(req.MinInstanceNum),MaxInstanceNum:  int(req.MaxInstanceNum),SubnetIDs:       req.SubnetIds,HealthCheckType: req.HealthCheckType,}err := s.Service.Update(req.Id, scalingGroup)return &proto.UpdateScalingGroupResponse{}, err
}func (s *AutoScalingServer) DeleteScalingGroup(ctx context.Context, req *proto.DeleteScalingGroupRequest) (*proto.DeleteScalingGroupResponse, error) {err := s.Service.Delete(req.Id)return &proto.DeleteScalingGroupResponse{}, err
}

创建mian.go并启动

package mainimport ("context""fmt""log""net""net/http""github.com/grpc-ecosystem/grpc-gateway/v2/runtime""google.golang.org/grpc""google.golang.org/grpc/reflection""autoscaling-app/pkg/config"pb "autoscaling-app/pkg/proto""autoscaling-app/pkg/repository""autoscaling-app/pkg/server/grpc_server""autoscaling-app/pkg/service"
)func main() {// 1. 加载配置cfg, err := config.LoadConfig()if err != nil {log.Fatalf("Load config error: %v", err)}// 2. 构建数据库连接dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",cfg.DBHost, cfg.DBPort, cfg.DBUser, cfg.DBPassword, cfg.DBName)// 3. 初始化数据库仓库repo, err := repository.NewScalingGroupRepository(dsn)if err != nil {log.Fatalf("Failed to initialize repository: %v", err)}// 4. 初始化业务服务svc := service.NewScalingGroupService(repo)// 5. 初始化 gRPC 服务grpcServer := grpc.NewServer()reflection.Register(grpcServer)// 6. 初始化 gRPC 接口实现(从 server 包中引用)autoScalingServer := &server.AutoScalingServer{Service: svc}pb.RegisterAutoScalingServiceServer(grpcServer, autoScalingServer)// 7. 启动 gRPC 服务go func() {lis, err := net.Listen("tcp", cfg.GRPCPort)if err != nil {log.Fatalf("Failed to listen on gRPC port: %v", err)}log.Printf("gRPC server is running at %s", cfg.GRPCPort)if err := grpcServer.Serve(lis); err != nil {log.Fatalf("Failed to serve gRPC: %v", err)}}()// 8. 启动 HTTP 网关ctx := context.Background()ctx, cancel := context.WithCancel(ctx)defer cancel()mux := runtime.NewServeMux()opts := []grpc.DialOption{grpc.WithInsecure()}err = pb.RegisterAutoScalingServiceHandlerFromEndpoint(ctx, mux, cfg.GRPCPort, opts)if err != nil {log.Fatalf("Failed to register HTTP gateway: %v", err)}log.Printf("HTTP server is running at %s", cfg.HTTPPort)if err := http.ListenAndServe(cfg.HTTPPort, mux); err != nil {log.Fatalf("Failed to serve HTTP: %v", err)}
}

main.go 只负责启动和初始化,业务逻辑应按模块拆分到 handler、service、repository 等目录中,这样代码结构清晰、易于维护、便于测试和扩展。

运行与测试

启动

go run cmd/main.go

测试RPC服务

  1. 使用evans工具
    # 安装evans go install github.com/ktr0731/evans@latest

启动evans cli,确保服务已经在监听50051,-r参数表示交互模式。键入evans后,输入

package autoscaling
service AutoScalingService

输入命令如下:
在这里插入图片描述
当然也可以使用grpcurl

测试RESTful接口

curl -X POST http://localhost:8080/v1/scalingGroups \-H "Content-Type: application/json" \-d '{"name": "group1","min_instance_num": 1,"max_instance_num": 5,"subnet_ids": ["subnet-a", "subnet-b"],"health_check_type": "ECS"}'
{"id":"8a02bfd4-c1d3-4f9d-931d-3b24755996f2", "createdAt":"1753345069134"}%  
curl http://localhost:8080/v1/scalingGroups 
{"scalingGroups":[{"id":"8a02bfd4-c1d3-4f9d-931d-3b24755996f2", "name":"group1", "minInstanceNum":1, "maxInstanceNum":5, "subnetIds":["subnet-a", "subnet-b"], "healthCheckType":"ECS", "createdAt":"1753345069134"}]}%
curl --location 'http://localhost:8080/v1/scalingGroups/8a02bfd4-c1d3-4f9d-931d-3b24755996f2'
curl --location --request PUT 'http://localhost:8080/v1/scalingGroups/8a02bfd4-c1d3-4f9d-931d-3b24755996f2' \
--header 'Content-Type: application/json' \
--data '{"name": "updated-group-name","minInstanceNum": 2,"maxInstanceNum": 10,"subnetIds": ["subnet-c", "subnet-d"],"healthCheckType": "TCP"
}'
curl --location --request DELETE 'http://localhost:8080/v1/scalingGroups/8a02bfd4-c1d3-4f9d-931d-3b24755996f2' \
--data ''

总结

✅使用 .proto 文件定义接口 + google.api.http 注解
✅ 使用 grpc-gateway 自动生成 RESTful 网关
✅ 模型、仓储、服务、接口模块清晰分离
✅ CreatedAt 动态生成(基于 GORM 的 autoCreateTime)
✅ 同时支持 gRPC 和 HTTP/JSON

http://www.dtcms.com/a/295411.html

相关文章:

  • 支持OCR和AI解释的Web PDF阅读器:解决大文档阅读难题
  • 飞腾D3000麒麟信安系统下配置intel I210 MAC
  • 最新免费使用Claude Code指南(Windows macOS/Linux)
  • 使用ffmpeg转码h265后mac默认播放器不支持问题
  • 快速启用 JMeter(macOS Automator 创建 JMeter 脚本)
  • 【MAC电脑系统变量管理】
  • Mac电脑开发Python(基于vs code)
  • 闲庭信步使用图像验证平台加速FPGA的开发:第三十三课——车牌识别的FPGA实现(5)车牌字符的识别
  • 只对非空元素执行循环操作,怎么办?
  • Qt自定义图像显示控件(支持平移、缩放、横纵比自适应)
  • 图像认知与OpenCV——图像预处理2
  • 记一次electron开发插件市场遇到的问题
  • Linux 简单介绍及基础命令
  • 云原生MySQL Operator开发实战(一):Operator基础与CRD设计
  • 基于Odoo的微信小程序全栈开发探索分析
  • 开源中国:以国产开源生态筑基,赋能智能研发全栈升级
  • 【王树森推荐系统】推荐系统涨指标的方法05:特殊用户人群
  • [数据结构]#7 哈希表
  • 国产化PDF处理控件Spire.PDF教程:Python 将 PDF 转换为 Markdown (含批量转换示例)
  • spring boot 整合 Spring Cloud、Kafka 和 MyBatis菜鸟教程
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(9):ようなN
  • C++ 中值传参和引用传参
  • rust-数据结构
  • 聚观早报 | 猿编程推动中美青少年AI实践;华为Pura 80数字版售价公布;iPhone 17 Air电池曝光
  • Redis数据类型与内部编码
  • 国产数据库拐点已至:电科金仓用“融合+AI”重新定义下一代数据底座
  • rustfs/rustfs基于 Rust 的高性能分布式存储系统
  • 进程通信----匿名管道
  • 进阶向:基于Python的本地文件内容搜索工具
  • 加入淘宝联盟内容库,以便在B站等平台被推广