From 27da6939f4c67468e547d0be4ab6d01b1d3875c2 Mon Sep 17 00:00:00 2001 From: fish Date: Sat, 28 Mar 2026 18:11:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=BE=AE=E6=9C=8D=E5=8A=A1=20gRPC=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=EF=BC=8C=E5=8C=85=E5=90=AB=E6=95=B0=E6=8D=AE=E5=BA=93=E4=B8=8E?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=90=AF=E5=8A=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/docker-compose.yml | 8 ++ backend/services/user-svc/Dockerfile | 23 ++++ backend/services/user-svc/generate.sh | 9 ++ backend/services/user-svc/go.mod | 40 +++++++ backend/services/user-svc/main.go | 156 ++++++++++++++++++++++++++ backend/services/user-svc/user.proto | 64 +++++++++++ 6 files changed, 300 insertions(+) create mode 100644 backend/services/user-svc/Dockerfile create mode 100644 backend/services/user-svc/generate.sh create mode 100644 backend/services/user-svc/go.mod create mode 100644 backend/services/user-svc/main.go create mode 100644 backend/services/user-svc/user.proto diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 7b48a21..82a0d86 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -17,3 +17,11 @@ services: - "8080:8080" environment: - GO_ENV=production + + user-svc: + build: + context: ./services/user-svc + ports: + - "50051:50051" + environment: + - GO_ENV=production diff --git a/backend/services/user-svc/Dockerfile b/backend/services/user-svc/Dockerfile new file mode 100644 index 0000000..74cb808 --- /dev/null +++ b/backend/services/user-svc/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.26.1-alpine3.23 AS builder + +WORKDIR /app + +COPY go.mod ./ +COPY main.go ./ +COPY user.proto ./ + +RUN go mod download +RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest +RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +RUN protoc --go_out=. --go-grpc_out=. user.proto +RUN go build -o user-svc main.go + +FROM alpine:latest + +WORKDIR /app + +COPY --from=builder /app/user-svc . + +EXPOSE 50051 + +CMD ["./user-svc"] \ No newline at end of file diff --git a/backend/services/user-svc/generate.sh b/backend/services/user-svc/generate.sh new file mode 100644 index 0000000..57255be --- /dev/null +++ b/backend/services/user-svc/generate.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# ็”Ÿๆˆ gRPC ไปฃ็  +go install google.golang.org/protobuf/cmd/protoc-gen-go@latest +go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + +protoc --go_out=. --go-grpc_out=. user.proto + +echo "gRPC code generated successfully!" diff --git a/backend/services/user-svc/go.mod b/backend/services/user-svc/go.mod new file mode 100644 index 0000000..becd004 --- /dev/null +++ b/backend/services/user-svc/go.mod @@ -0,0 +1,40 @@ +module user-svc + +go 1.26 + +require ( + google.golang.org/grpc v1.58.0 + gorm.io/driver/sqlite v1.5.4 + gorm.io/gorm v1.25.5 + google.golang.org/protobuf v1.31.0 +) + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.12.0 // indirect + yaml.v3 v3.0.1 // indirect +) \ No newline at end of file diff --git a/backend/services/user-svc/main.go b/backend/services/user-svc/main.go new file mode 100644 index 0000000..df7d738 --- /dev/null +++ b/backend/services/user-svc/main.go @@ -0,0 +1,156 @@ +package main + +import ( + "context" + "fmt" + "log" + "net" + "user-svc/user" + + "google.golang.org/grpc" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +type User struct { + ID string `json:"id" gorm:"primaryKey"` + Name string `json:"name"` + Email string `json:"email"` + Password string `json:"password"` +} + +type UserServiceServer struct { + user.UnimplementedUserServiceServer + db *gorm.DB +} + +func (s *UserServiceServer) CreateUser(ctx context.Context, req *user.CreateUserRequest) (*user.CreateUserResponse, error) { + newUser := User{ + ID: fmt.Sprintf("%d", len(s.getAllUsers())+1), + Name: req.Name, + Email: req.Email, + Password: req.Password, + } + + result := s.db.Create(&newUser) + if result.Error != nil { + return nil, result.Error + } + + return &user.CreateUserResponse{ + User: &user.User{ + Id: newUser.ID, + Name: newUser.Name, + Email: newUser.Email, + Password: newUser.Password, + }, + }, nil +} + +func (s *UserServiceServer) GetUser(ctx context.Context, req *user.GetUserRequest) (*user.GetUserResponse, error) { + var user User + result := s.db.First(&user, "id = ?", req.Id) + if result.Error != nil { + return nil, result.Error + } + + return &user.GetUserResponse{ + User: &user.User{ + Id: user.ID, + Name: user.Name, + Email: user.Email, + Password: user.Password, + }, + }, nil +} + +func (s *UserServiceServer) ListUsers(ctx context.Context, req *user.ListUsersRequest) (*user.ListUsersResponse, error) { + users := s.getAllUsers() + var protoUsers []*user.User + + for _, u := range users { + protoUsers = append(protoUsers, &user.User{ + Id: u.ID, + Name: u.Name, + Email: u.Email, + Password: u.Password, + }) + } + + return &user.ListUsersResponse{ + Users: protoUsers, + }, nil +} + +func (s *UserServiceServer) UpdateUser(ctx context.Context, req *user.UpdateUserRequest) (*user.UpdateUserResponse, error) { + var user User + result := s.db.First(&user, "id = ?", req.Id) + if result.Error != nil { + return nil, result.Error + } + + if req.Name != "" { + user.Name = req.Name + } + if req.Email != "" { + user.Email = req.Email + } + if req.Password != "" { + user.Password = req.Password + } + + s.db.Save(&user) + + return &user.UpdateUserResponse{ + User: &user.User{ + Id: user.ID, + Name: user.Name, + Email: user.Email, + Password: user.Password, + }, + }, nil +} + +func (s *UserServiceServer) DeleteUser(ctx context.Context, req *user.DeleteUserRequest) (*user.DeleteUserResponse, error) { + result := s.db.Delete(&User{}, "id = ?", req.Id) + if result.Error != nil { + return nil, result.Error + } + + return &user.DeleteUserResponse{ + Success: true, + }, nil +} + +func (s *UserServiceServer) getAllUsers() []User { + var users []User + s.db.Find(&users) + return users +} + +func initDB() *gorm.DB { + db, err := gorm.Open(sqlite.Open("users.db"), &gorm.Config{}) + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + + db.AutoMigrate(&User{}) + return db +} + +func main() { + db := initDB() + + server := grpc.NewServer() + user.RegisterUserServiceServer(server, &UserServiceServer{db: db}) + + listener, err := net.Listen("tcp", ":50051") + if err != nil { + log.Fatalf("Failed to listen: %v", err) + } + + log.Println("User service starting on port 50051...") + if err := server.Serve(listener); err != nil { + log.Fatalf("Failed to serve: %v", err) + } +} \ No newline at end of file diff --git a/backend/services/user-svc/user.proto b/backend/services/user-svc/user.proto new file mode 100644 index 0000000..85a3765 --- /dev/null +++ b/backend/services/user-svc/user.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +package user; + +option go_package = "./user"; + +message User { + string id = 1; + string name = 2; + string email = 3; + string password = 4; +} + +message CreateUserRequest { + string name = 1; + string email = 2; + string password = 3; +} + +message CreateUserResponse { + User user = 1; +} + +message GetUserRequest { + string id = 1; +} + +message GetUserResponse { + User user = 1; +} + +message ListUsersRequest { +} + +message ListUsersResponse { + repeated User users = 1; +} + +message UpdateUserRequest { + string id = 1; + string name = 2; + string email = 3; + string password = 4; +} + +message UpdateUserResponse { + User user = 1; +} + +message DeleteUserRequest { + string id = 1; +} + +message DeleteUserResponse { + bool success = 1; +} + +service UserService { + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); + rpc GetUser(GetUserRequest) returns (GetUserResponse); + rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); + rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse); + rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse); +} \ No newline at end of file