go-Rrpc使用nacos作为服务发现
前端vue3随便写个展示界面,API
目录结构
www.go-nacos-rpc
|---goods_service
|---proto
|---goods.proto
|---main.go
|---inventory_service
|---proto
|---inventory.proto
|---main.go
|---tools
|---core.go
|---getip.go
|---nacos.go
goods.proto
// goods.proto
syntax = "proto3";
option go_package = "./;goods";
service GoodService {
rpc GetGoodsMsg (GetGoodsMsgRequest) returns (GetGoodsMsgResponse);
rpc GetGoodsMsgList (Empty) returns (GetGoodsMsgListResponse);
}
message GetGoodsMsgRequest {
int64 id = 1;
}
message GetGoodsMsgResponse {
int64 id = 1;
string name = 2;
int64 price = 3;
string inventory_id = 4;
int64 inventory_count = 5;
}
message Empty{}
message GetGoodsMsgListResponse {
repeated GetGoodsMsgResponse items = 1;
}
使用protoc生成rpc
PS D:\All_Go_test> protoc -I =. --go_out=. --go-grpc_out=. .\goods.proto
inventory.proto
// goods.proto
syntax = "proto3";
option go_package = "./;inventory";
service InventoryService {
rpc GetInventoryMsg (GetInventoryRequest) returns (GetInventoryResponse);
rpc GetAllInventoryMsg
}
message GetInventoryRequest {
string inventory_id = 1;
}
message GetInventoryResponse {
string inventory_id = 1;
int32 count = 2;
}
使用protoc生成rpc
PS D:\All_Go_test> protoc -I =. --go_out=. --go-grpc_out=. .\inventory.proto
tools工具
解决跨域问题core.go
package tools
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 解决前后端跨域
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
c.Next()
}
}
动态获取服务IP地址getip.go
package tools
import (
"net"
)
// 获取IP
func GetLocalIP() (string, error) {
Addresses, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
var LocalIP string
for _, addr := range Addresses {
if IpNet, ok := addr.(*net.IPNet); ok && !IpNet.IP.IsLoopback() {
if IpNet.IP.To4() != nil {
if IpNet.IP.To4()[0] == 192 && IpNet.IP.To4()[1] == 168 {
LocalIP = IpNet.IP.String()
}
}
}
}
return LocalIP, nil
}
涉及nacos的操作nacos.go
package tools
import (
"errors"
"fmt"
"github.com/nacos-group/nacos-sdk-go/clients"
"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/vo"
)
// 获取nacos客户端
func initConfigClient() (iClient naming_client.INamingClient, err error) {
sv := []constant.ServerConfig{
{
IpAddr: "192.168.40.180",
ContextPath: "/nacos",
Port: 8848,
Scheme: "http",
},
}
cc := constant.ClientConfig{
NamespaceId: "c0c576b7-1d60-48e9-9b2f-8d440d68c19b", // 如果需要支持多namespace,我们可以创建多个client,它们有不同的NamespaceId。当namespace是public时,此处填空字符串。
TimeoutMs: 30000,
NotLoadCacheAtStart: true,
LogLevel: "debug",
Username: "nacos",
Password: "nacos",
}
iClient, err = clients.NewNamingClient(
vo.NacosClientParam{
ClientConfig: &cc,
ServerConfigs: sv,
},
)
if err != nil {
return nil, err
}
return iClient, nil
}
// 初始化nacos并注册服务到nacos
func InitNacosSVC(ServiceName, Ip, ClusterName, GroupName string, Port int, Weight float64, Metadata map[string]string) error {
iClient, err := initConfigClient()
if err != nil {
fmt.Println("初始化Nacos客户端失败了")
return err
}
if iClient == nil {
fmt.Println("iClient是空的")
panic("iClient空指针")
}
// 注册服务到nacos
success, err := iClient.RegisterInstance(vo.RegisterInstanceParam{
Ip: Ip,
Port: uint64(Port),
ServiceName: ServiceName,
Weight: Weight,
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: Metadata,
ClusterName: ClusterName, // 默认值DEFAULT
GroupName: GroupName, // 默认值DEFAULT_GROUP
})
if err != nil {
fmt.Println(err)
}
if success {
fmt.Println("register nacos success")
return nil
}
return errors.New("注册inventory服务到nacos失败")
}
// 获取服务信息
func GetNacosSVC(svcName string) (addr string, err error) {
iClient, err := initConfigClient()
if err != nil {
return "", err
}
if iClient == nil {
fmt.Println("iClient是空的")
panic("iClient空指针")
}
// SelectInstances 只返回满足这些条件的实例列表:healthy=${HealthyOnly},enable=true 和weight>0
instances, err := iClient.SelectInstances(vo.SelectInstancesParam{
ServiceName: svcName,
GroupName: "myGroup", // 默认值DEFAULT_GROUP
Clusters: []string{"myCluster"}, // 默认值DEFAULT
HealthyOnly: true,
})
if err != nil {
fmt.Println("获取实例失败:", err)
}
var Addr string
// 解析实例列表
for _, instance := range instances {
Addr = fmt.Sprintf("%s:%d", instance.Ip, instance.Port)
}
return Addr, nil
}
RPC服务端 inventory_service/main.go
package main
import (
"context"
"database/sql"
"errors"
"fmt"
_ "github.com/go-sql-driver/mysql"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net"
pb "testgo.ww.www/inventory_service/proto"
"testgo.ww.www/tools"
)
var db *sql.DB
// inventory server
type server struct {
pb.UnimplementedInventoryServiceServer
}
func initDB() error {
var err error
dsn := "dbuser1:NSD2021@tedu.cn@tcp(192.168.40.199:13306)/inventory"
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
return db.Ping()
}
func (s *server) GetInventoryMsg(ctx context.Context, req *pb.GetInventoryRequest) (*pb.GetInventoryResponse, error) {
// 查询数据库
var inventoryMsg int32
err := db.QueryRow("SELECT count FROM `inventory` WHERE id = ?", req.GetInventoryId()).Scan(&inventoryMsg)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Errorf(codes.NotFound, "Inventory with ID %v not found", req.GetInventoryId())
}
return nil, status.Errorf(codes.Internal, "Failed to query inventory: %v", err)
}
return &pb.GetInventoryResponse{
InventoryId: req.InventoryId,
Count: inventoryMsg,
}, nil
}
var (
inventoryPort = 9090
inventorySVCName = "inventory"
inventoryClusterName = "myCluster"
inventoryGroupName = "myGroup"
inventoryWeight = 10
inventoryMetadata = map[string]string{"app": "inventory"}
)
func main() {
// 初始化数据库
err := initDB()
if err != nil {
panic(fmt.Sprintf("Failed to initialize database: %v", err))
}
inventorySVCIP, err := tools.GetLocalIP()
if err != nil {
panic(fmt.Sprintf("获取IP地址失败: %v", err))
}
// 创建nacos服务实例
err = tools.InitNacosSVC(inventorySVCName, inventorySVCIP, inventoryClusterName, inventoryGroupName, inventoryPort, float64(inventoryWeight), inventoryMetadata)
if err != nil {
panic(err)
}
// 开启端口 fmt.Sprintf(":%d", inventoryPort)
listen, err := net.Listen("tcp", fmt.Sprintf(":%d", inventoryPort))
if err != nil {
panic(fmt.Sprintf("Failed to listen on port %d: %v", inventoryPort, err))
}
// 创建grpc服务
grpcServer := grpc.NewServer()
// 注册服务
pb.RegisterInventoryServiceServer(grpcServer, &server{})
// 启动服务
fmt.Println("Grpc service is start running... ", inventoryPort)
err = grpcServer.Serve(listen)
if err != nil {
fmt.Println(err)
return
}
}
RPC客户端 goods_service/main.go
package main
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"net/http"
"strconv"
pb "testgo.ww.www/goods_service/proto"
pb2 "testgo.ww.www/inventory_service/proto"
"testgo.ww.www/tools"
)
var (
db *sql.DB
goodsPort = 9091
goodsSVCName = "goods"
goodsClusterName = "myCluster"
goodsGroupName = "myGroup"
goodsWeight = 10
goodsMetadata = map[string]string{"app": "goods"}
)
type Server struct {
pb.UnimplementedGoodServiceServer
}
// 初始化数据库连接
func initDB() error {
var err error
dsn := "dbuser1:NSD2021@tedu.cn@tcp(192.168.40.199:13306)/goods"
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
return db.Ping()
}
// 创建 gRPC 客户端
func CreateGRPCClient(serviceName string) (pb2.InventoryServiceClient, *grpc.ClientConn, error) {
clientAddr, err := tools.GetNacosSVC(serviceName)
if err != nil || clientAddr == "" {
return nil, nil, fmt.Errorf("获取nacos的服务失败: %v", err)
}
fmt.Println("获取的nacos服务为:", clientAddr)
conn, err := grpc.NewClient(clientAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, nil, fmt.Errorf("创建gRPC连接失败: %v", err)
}
return pb2.NewInventoryServiceClient(conn), conn, nil
}
// 获取单个商品信息
func (s *Server) GetGoodsMsg(ctx context.Context, req *pb.GetGoodsMsgRequest) (res *pb.GetGoodsMsgResponse, err error) {
var (
id int
name string
price int
inventoryId string
)
// 查询数据库并保存结果
err = db.QueryRow("SELECT id,name,price,inventory_id FROM `goods` WHERE id = ?", req.GetId()).Scan(&id, &name, &price, &inventoryId)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Errorf(codes.NotFound, "Inventory with ID %d not found", req.GetId())
}
return nil, status.Errorf(codes.Internal, "Failed to query inventory: %v", err)
}
res = &pb.GetGoodsMsgResponse{
Id: int64(id),
Name: name,
Price: int64(price),
InventoryId: inventoryId,
}
return res, nil
}
// 获取所有商品信息
func (s *Server) GetGoodsMsgList(context.Context, *pb.Empty) (res *pb.GetGoodsMsgListResponse, err error) {
rows, err := db.Query("SELECT id, name, price, inventory_id FROM `goods`")
if err != nil {
return nil, status.Errorf(codes.Internal, "Failed to query goods: %v", err)
}
defer func(rows *sql.Rows) {
err := rows.Close()
if err != nil {
panic(err)
}
}(rows)
goodsList := &pb.GetGoodsMsgListResponse{
Items: []*pb.GetGoodsMsgResponse{},
}
for rows.Next() {
var item pb.GetGoodsMsgResponse
if err := rows.Scan(&item.Id, &item.Name, &item.Price, &item.InventoryId); err != nil {
return nil, status.Errorf(codes.Internal, "Failed to scan goods row: %v", err)
}
goodsList.Items = append(goodsList.Items, &item)
}
return goodsList, nil
}
// 调用inventory服务获取单个商品的库存数量方法
func GetGoodsCount(inventoryId string) int {
inventoryClient, conn, err := CreateGRPCClient("inventory")
if err != nil {
fmt.Println(err)
}
// 关闭连接
defer func(conn *grpc.ClientConn) {
err := conn.Close()
if err != nil {
fmt.Println(err)
}
}(conn)
res, err := inventoryClient.GetInventoryMsg(context.Background(), &pb2.GetInventoryRequest{
InventoryId: inventoryId,
})
if err != nil {
panic(err)
}
return int(res.Count)
}
// 获取单个商品信息和库存数量的处理函数
func GetGoodsAndInventory(c *gin.Context) {
goodsIdString := c.Query("goods_id")
if goodsIdString == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "goods_id is required"})
return
}
goodsID, err := strconv.ParseInt(goodsIdString, 10, 64)
// 获得商品信息
req := &pb.GetGoodsMsgRequest{Id: goodsID}
resp, err := (&Server{}).GetGoodsMsg(context.Background(), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 获取库存数量
resp.InventoryCount = int64(GetGoodsCount(resp.InventoryId))
c.JSON(http.StatusOK, gin.H{
"goods_id": resp.Id,
"name": resp.Name,
"price": resp.Price,
"inventory_id": resp.InventoryId,
"inventory_count": resp.InventoryCount,
})
}
// 获取所有商品信息和库存数量的处理函数
func GetAllGoodsAndInventory(c *gin.Context) {
// 获得所有商品信息
req := &pb.Empty{}
resp, err := (&Server{}).GetGoodsMsgList(context.Background(), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 为每个商品获取库存数量
for _, item := range resp.Items {
inventoryCount := GetGoodsCount(item.InventoryId)
item.InventoryCount = int64(inventoryCount)
}
c.JSON(http.StatusOK, resp)
}
func main() {
// 创建数据库连接
err := initDB()
if err != nil {
panic(fmt.Sprintf("连接数据库失败%v", err))
}
// 获取本地IP
goodsSVCIP, err := tools.GetLocalIP()
if err != nil {
panic(fmt.Sprintf("获取IP失败%v", err))
}
// 注册goods服务到nacos
err = tools.InitNacosSVC(goodsSVCName, goodsSVCIP, goodsClusterName, goodsGroupName, goodsPort, float64(goodsWeight), goodsMetadata)
if err != nil {
panic(fmt.Sprintf("注册NACOS失败%v", err))
}
// 创建路由
r := gin.Default()
r.Use(tools.Cors())
r.GET("/goods", GetGoodsAndInventory)
r.GET("/goods_list", GetAllGoodsAndInventory)
if err := r.Run(":8088"); err != nil {
panic(fmt.Sprintf("启动服务失败: %v", err))
}
}
前端vue3
<template>
<div class="fix">
<el-table :data="tableData" :border="true" style="width: 100%">
<el-table-column prop="id" label="序号" width="180" />
<el-table-column prop="name" label="名称" width="180" />
<el-table-column prop="price" label="价格" />
<el-table-column prop="inventory_id" label="库存序号" />
<el-table-column prop="inventory_count" label="库存" />
</el-table>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import axios from 'axios';
const tableData = ref([])
const getMsg = async () => {
try {
const res = await axios({
method: 'get',
url: 'http://127.0.0.1:8088/goods_list',
});
tableData.value = res.data.items
} catch (error) {
console.error('Request failed:', error);
}
}
onMounted(()=>{
getMsg()
})
</script>
<style lang='scss' scoped>
.fix {
position: relative;
padding-bottom: 60px; /* 预留按钮的高度 */
}
.el-button {
position: fixed;
bottom: 50px;
right: 473px;
}
</style>