[golang 微服务] 8.go-micro的负载均衡操作,go Web框(Gin,Beego)调用go-micro微服务

news2024/11/25 4:45:27

一.先创建go-micro服务端

  1. 启动consul

需要先启动consul, consol相关内容见
[golang 微服务] 5. 微服务服务发现介绍,安装以及consul的使用,Consul集群
[golang 微服务] 6. GRPC微服务集群+Consul集群+grpc-consul-resolver案例演示
启动consul命令,这里,使用dev模式: consul agent -dev,启动如下:

启动后UI界面:

  1. 启动微服务

这里使用 上一节的goodsinfo微服务,见上一节 [golang 微服务] 7. go-micro框架介绍,go-micro脚手架,go-micro结合consul搭建微服务案例
这里对 .proto进行优化: 统一商品模型,以及增加获取商品相关请求以及响应代码

goodsinfo.proto代码如下:

syntax = "proto3";

package goodsinfo;

option go_package = "./proto;goodsinfo";

//商品相关方法
service Goodsinfo {
    //AddGoods: 定义增加商品的微服务, 这里的写法和gRPC中的写法一致
    rpc AddGoods(AddGoodsRequest) returns (AddGoodsResponse) {}
    //获取商品列表信息
    rpc GetGoods(GetGoodsRequest) returns (GetGoodsResponse) {}
}

//定义GoodsModel结构体,以便增加商品,获取商品数据时使用
message GoodsModel{
    string title = 1;
    double price = 2;
    string content = 3;
}

//增加商品请求: 和gRPC中的写法一致
message AddGoodsRequest {
    GoodsModel parame = 1;
}

//增加商品响应:和gRPC中的写法一致
message AddGoodsResponse {
    string message = 1;
    bool success = 2;
}

//获取商品请求
message GetGoodsRequest {
}
//获取商品响应
message GetGoodsResponse {
    repeated GoodsModel GoodsList = 1;
}
在goodsinfo下执行命令:
protoc --proto_path=. --micro_out=. --go_out=:. proto/goodsinfo.proto
生成对应的.pb.go,.micro.go文件,然后在handler/goodsinfo.go中完善对应的方法:

handler/goodsinfo.go中的代码如下:

package handler

import (
    "context"
    "strconv"
    "go-micro.dev/v4/logger"
    pb "goodsinfo/proto"
)

type Goodsinfo struct{}

//增加商品
func (e *Goodsinfo) AddGoods(ctx context.Context, req *pb.AddGoodsRequest, rsp *pb.AddGoodsResponse) error {
    logger.Infof("request: %v", req)
    //书写返回的逻辑结果
    rsp.Message = "增加成功"
    rsp.Success = true
    return nil
}

//获取商品
func (e *Goodsinfo) GetGoods(ctx context.Context, req *pb.GetGoodsRequest, rsp *pb.GetGoodsResponse) error {
    var tempList []*pb.GoodsModel
    //模拟从数据库中获取商品数据
    for i := 0; i < 10; i++ {
        tempList = append(tempList, &pb.GoodsModel{
            Title:   "第" + strconv.Itoa(i) + "条数据",
            Price:   float64((i + 1) * 2),
            Content: "第" + strconv.Itoa(i) + "内容",
        })
    }
    rsp.GoodsList = tempList
    return nil
}

goodsinfo.pb.go代码如下:

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
//     protoc-gen-go v1.30.0
//     protoc        v3.15.5
// source: proto/goodsinfo.proto

package goodsinfo

import (
    protoreflect "google.golang.org/protobuf/reflect/protoreflect"
    protoimpl "google.golang.org/protobuf/runtime/protoimpl"
    reflect "reflect"
    sync "sync"
)

const (
    // Verify that this generated code is sufficiently up-to-date.
    _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
    // Verify that runtime/protoimpl is sufficiently up-to-date.
    _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

// 定义GoodsModel结构体,以便增加商品,获取商品数据时使用
type GoodsModel struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Title   string  `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
    Price   float64 `protobuf:"fixed64,2,opt,name=price,proto3" json:"price,omitempty"`
    Content string  `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"`
}

func (x *GoodsModel) Reset() {
    *x = GoodsModel{}
    if protoimpl.UnsafeEnabled {
        mi := &file_proto_goodsinfo_proto_msgTypes[0]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}

func (x *GoodsModel) String() string {
    return protoimpl.X.MessageStringOf(x)
}

func (*GoodsModel) ProtoMessage() {}

func (x *GoodsModel) ProtoReflect() protoreflect.Message {
    mi := &file_proto_goodsinfo_proto_msgTypes[0]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}

// Deprecated: Use GoodsModel.ProtoReflect.Descriptor instead.
func (*GoodsModel) Descriptor() ([]byte, []int) {
    return file_proto_goodsinfo_proto_rawDescGZIP(), []int{0}
}

func (x *GoodsModel) GetTitle() string {
    if x != nil {
        return x.Title
    }
    return ""
}

func (x *GoodsModel) GetPrice() float64 {
    if x != nil {
        return x.Price
    }
    return 0
}

func (x *GoodsModel) GetContent() string {
    if x != nil {
        return x.Content
    }
    return ""
}

// 增加商品请求: 和gRPC中的写法一致
type AddGoodsRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Parame *GoodsModel `protobuf:"bytes,1,opt,name=parame,proto3" json:"parame,omitempty"`
}

func (x *AddGoodsRequest) Reset() {
    *x = AddGoodsRequest{}
    if protoimpl.UnsafeEnabled {
        mi := &file_proto_goodsinfo_proto_msgTypes[1]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}

func (x *AddGoodsRequest) String() string {
    return protoimpl.X.MessageStringOf(x)
}

func (*AddGoodsRequest) ProtoMessage() {}

func (x *AddGoodsRequest) ProtoReflect() protoreflect.Message {
    mi := &file_proto_goodsinfo_proto_msgTypes[1]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}

// Deprecated: Use AddGoodsRequest.ProtoReflect.Descriptor instead.
func (*AddGoodsRequest) Descriptor() ([]byte, []int) {
    return file_proto_goodsinfo_proto_rawDescGZIP(), []int{1}
}

func (x *AddGoodsRequest) GetParame() *GoodsModel {
    if x != nil {
        return x.Parame
    }
    return nil
}

// 增加商品响应:和gRPC中的写法一致
type AddGoodsResponse struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
    Success bool   `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"`
}

func (x *AddGoodsResponse) Reset() {
    *x = AddGoodsResponse{}
    if protoimpl.UnsafeEnabled {
        mi := &file_proto_goodsinfo_proto_msgTypes[2]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}

func (x *AddGoodsResponse) String() string {
    return protoimpl.X.MessageStringOf(x)
}

func (*AddGoodsResponse) ProtoMessage() {}

func (x *AddGoodsResponse) ProtoReflect() protoreflect.Message {
    mi := &file_proto_goodsinfo_proto_msgTypes[2]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}

// Deprecated: Use AddGoodsResponse.ProtoReflect.Descriptor instead.
func (*AddGoodsResponse) Descriptor() ([]byte, []int) {
    return file_proto_goodsinfo_proto_rawDescGZIP(), []int{2}
}

func (x *AddGoodsResponse) GetMessage() string {
    if x != nil {
        return x.Message
    }
    return ""
}

func (x *AddGoodsResponse) GetSuccess() bool {
    if x != nil {
        return x.Success
    }
    return false
}

// 获取商品请求
type GetGoodsRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields
}

func (x *GetGoodsRequest) Reset() {
    *x = GetGoodsRequest{}
    if protoimpl.UnsafeEnabled {
        mi := &file_proto_goodsinfo_proto_msgTypes[3]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}

func (x *GetGoodsRequest) String() string {
    return protoimpl.X.MessageStringOf(x)
}

func (*GetGoodsRequest) ProtoMessage() {}

func (x *GetGoodsRequest) ProtoReflect() protoreflect.Message {
    mi := &file_proto_goodsinfo_proto_msgTypes[3]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}

// Deprecated: Use GetGoodsRequest.ProtoReflect.Descriptor instead.
func (*GetGoodsRequest) Descriptor() ([]byte, []int) {
    return file_proto_goodsinfo_proto_rawDescGZIP(), []int{3}
}

// 获取商品响应
type GetGoodsResponse struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    GoodsList []*GoodsModel `protobuf:"bytes,1,rep,name=GoodsList,proto3" json:"GoodsList,omitempty"`
}

func (x *GetGoodsResponse) Reset() {
    *x = GetGoodsResponse{}
    if protoimpl.UnsafeEnabled {
        mi := &file_proto_goodsinfo_proto_msgTypes[4]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}

func (x *GetGoodsResponse) String() string {
    return protoimpl.X.MessageStringOf(x)
}

func (*GetGoodsResponse) ProtoMessage() {}

func (x *GetGoodsResponse) ProtoReflect() protoreflect.Message {
    mi := &file_proto_goodsinfo_proto_msgTypes[4]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}

// Deprecated: Use GetGoodsResponse.ProtoReflect.Descriptor instead.
func (*GetGoodsResponse) Descriptor() ([]byte, []int) {
    return file_proto_goodsinfo_proto_rawDescGZIP(), []int{4}
}

func (x *GetGoodsResponse) GetGoodsList() []*GoodsModel {
    if x != nil {
        return x.GoodsList
    }
    return nil
}

var File_proto_goodsinfo_proto protoreflect.FileDescriptor

var file_proto_goodsinfo_proto_rawDesc = []byte{
    0x0a, 0x15, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x6f, 0x64, 0x73, 0x69, 0x6e, 0x66,
    0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x67, 0x6f, 0x6f, 0x64, 0x73, 0x69, 0x6e,
    0x66, 0x6f, 0x22, 0x52, 0x0a, 0x0a, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x4d, 0x6f, 0x64, 0x65, 0x6c,
    0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
    0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18,
    0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07,
    0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63,
    0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x40, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x47, 0x6f, 0x6f,
    0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x70, 0x61, 0x72,
    0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x6f, 0x6f, 0x64,
    0x73, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x4d, 0x6f, 0x64, 0x65, 0x6c,
    0x52, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x22, 0x46, 0x0a, 0x10, 0x41, 0x64, 0x64, 0x47,
    0x6f, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07,
    0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d,
    0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73,
    0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
    0x22, 0x11, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75,
    0x65, 0x73, 0x74, 0x22, 0x47, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x52,
    0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x47, 0x6f, 0x6f, 0x64, 0x73,
    0x4c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x6f, 0x6f,
    0x64, 0x73, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x4d, 0x6f, 0x64, 0x65,
    0x6c, 0x52, 0x09, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x32, 0x99, 0x01, 0x0a,
    0x09, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x45, 0x0a, 0x08, 0x41, 0x64,
    0x64, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x64, 0x73, 0x69, 0x6e,
    0x66, 0x6f, 0x2e, 0x41, 0x64, 0x64, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
    0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x64, 0x73, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x41,
    0x64, 0x64, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
    0x00, 0x12, 0x45, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x12, 0x1a, 0x2e,
    0x67, 0x6f, 0x6f, 0x64, 0x73, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6f,
    0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x64,
    0x73, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6f, 0x64, 0x73, 0x52, 0x65,
    0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x13, 0x5a, 0x11, 0x2e, 0x2f, 0x70, 0x72,
    0x6f, 0x74, 0x6f, 0x3b, 0x67, 0x6f, 0x6f, 0x64, 0x73, 0x69, 0x6e, 0x66, 0x6f, 0x62, 0x06, 0x70,
    0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
    file_proto_goodsinfo_proto_rawDescOnce sync.Once
    file_proto_goodsinfo_proto_rawDescData = file_proto_goodsinfo_proto_rawDesc
)

func file_proto_goodsinfo_proto_rawDescGZIP() []byte {
    file_proto_goodsinfo_proto_rawDescOnce.Do(func() {
        file_proto_goodsinfo_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_goodsinfo_proto_rawDescData)
    })
    return file_proto_goodsinfo_proto_rawDescData
}

var file_proto_goodsinfo_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_proto_goodsinfo_proto_goTypes = []interface{}{
    (*GoodsModel)(nil),       // 0: goodsinfo.GoodsModel
    (*AddGoodsRequest)(nil),  // 1: goodsinfo.AddGoodsRequest
    (*AddGoodsResponse)(nil), // 2: goodsinfo.AddGoodsResponse
    (*GetGoodsRequest)(nil),  // 3: goodsinfo.GetGoodsRequest
    (*GetGoodsResponse)(nil), // 4: goodsinfo.GetGoodsResponse
}
var file_proto_goodsinfo_proto_depIdxs = []int32{
    0, // 0: goodsinfo.AddGoodsRequest.parame:type_name -> goodsinfo.GoodsModel
    0, // 1: goodsinfo.GetGoodsResponse.GoodsList:type_name -> goodsinfo.GoodsModel
    1, // 2: goodsinfo.Goodsinfo.AddGoods:input_type -> goodsinfo.AddGoodsRequest
    3, // 3: goodsinfo.Goodsinfo.GetGoods:input_type -> goodsinfo.GetGoodsRequest
    2, // 4: goodsinfo.Goodsinfo.AddGoods:output_type -> goodsinfo.AddGoodsResponse
    4, // 5: goodsinfo.Goodsinfo.GetGoods:output_type -> goodsinfo.GetGoodsResponse
    4, // [4:6] is the sub-list for method output_type
    2, // [2:4] is the sub-list for method input_type
    2, // [2:2] is the sub-list for extension type_name
    2, // [2:2] is the sub-list for extension extendee
    0, // [0:2] is the sub-list for field type_name
}

func init() { file_proto_goodsinfo_proto_init() }
func file_proto_goodsinfo_proto_init() {
    if File_proto_goodsinfo_proto != nil {
        return
    }
    if !protoimpl.UnsafeEnabled {
        file_proto_goodsinfo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
            switch v := v.(*GoodsModel); i {
            case 0:
                return &v.state
            case 1:
                return &v.sizeCache
            case 2:
                return &v.unknownFields
            default:
                return nil
            }
        }
        file_proto_goodsinfo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
            switch v := v.(*AddGoodsRequest); i {
            case 0:
                return &v.state
            case 1:
                return &v.sizeCache
            case 2:
                return &v.unknownFields
            default:
                return nil
            }
        }
        file_proto_goodsinfo_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
            switch v := v.(*AddGoodsResponse); i {
            case 0:
                return &v.state
            case 1:
                return &v.sizeCache
            case 2:
                return &v.unknownFields
            default:
                return nil
            }
        }
        file_proto_goodsinfo_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
            switch v := v.(*GetGoodsRequest); i {
            case 0:
                return &v.state
            case 1:
                return &v.sizeCache
            case 2:
                return &v.unknownFields
            default:
                return nil
            }
        }
        file_proto_goodsinfo_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
            switch v := v.(*GetGoodsResponse); i {
            case 0:
                return &v.state
            case 1:
                return &v.sizeCache
            case 2:
                return &v.unknownFields
            default:
                return nil
            }
        }
    }
    type x struct{}
    out := protoimpl.TypeBuilder{
        File: protoimpl.DescBuilder{
            GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
            RawDescriptor: file_proto_goodsinfo_proto_rawDesc,
            NumEnums:      0,
            NumMessages:   5,
            NumExtensions: 0,
            NumServices:   1,
        },
        GoTypes:           file_proto_goodsinfo_proto_goTypes,
        DependencyIndexes: file_proto_goodsinfo_proto_depIdxs,
        MessageInfos:      file_proto_goodsinfo_proto_msgTypes,
    }.Build()
    File_proto_goodsinfo_proto = out.File
    file_proto_goodsinfo_proto_rawDesc = nil
    file_proto_goodsinfo_proto_goTypes = nil
    file_proto_goodsinfo_proto_depIdxs = nil
}

goodsinfo.pb.micro.go代码如下:

// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: proto/goodsinfo.proto

package goodsinfo

import (
    fmt "fmt"
    proto "google.golang.org/protobuf/proto"
    math "math"
)

import (
    context "context"
    api "go-micro.dev/v4/api"
    client "go-micro.dev/v4/client"
    server "go-micro.dev/v4/server"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// Reference imports to suppress errors if they are not otherwise used.
var _ api.Endpoint
var _ context.Context
var _ client.Option
var _ server.Option

// Api Endpoints for Goodsinfo service

func NewGoodsinfoEndpoints() []*api.Endpoint {
    return []*api.Endpoint{}
}

// Client API for Goodsinfo service

type GoodsinfoService interface {
    // AddGoods: 定义增加商品的微服务, 这里的写法和gRPC中的写法一致
    AddGoods(ctx context.Context, in *AddGoodsRequest, opts ...client.CallOption) (*AddGoodsResponse, error)
    GetGoods(ctx context.Context, in *GetGoodsRequest, opts ...client.CallOption) (*GetGoodsResponse, error)
}

type goodsinfoService struct {
    c    client.Client
    name string
}

func NewGoodsinfoService(name string, c client.Client) GoodsinfoService {
    return &goodsinfoService{
        c:    c,
        name: name,
    }
}

func (c *goodsinfoService) AddGoods(ctx context.Context, in *AddGoodsRequest, opts ...client.CallOption) (*AddGoodsResponse, error) {
    req := c.c.NewRequest(c.name, "Goodsinfo.AddGoods", in)
    out := new(AddGoodsResponse)
    err := c.c.Call(ctx, req, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

func (c *goodsinfoService) GetGoods(ctx context.Context, in *GetGoodsRequest, opts ...client.CallOption) (*GetGoodsResponse, error) {
    req := c.c.NewRequest(c.name, "Goodsinfo.GetGoods", in)
    out := new(GetGoodsResponse)
    err := c.c.Call(ctx, req, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// Server API for Goodsinfo service

type GoodsinfoHandler interface {
    // AddGoods: 定义增加商品的微服务, 这里的写法和gRPC中的写法一致
    AddGoods(context.Context, *AddGoodsRequest, *AddGoodsResponse) error
    GetGoods(context.Context, *GetGoodsRequest, *GetGoodsResponse) error
}

func RegisterGoodsinfoHandler(s server.Server, hdlr GoodsinfoHandler, opts ...server.HandlerOption) error {
    type goodsinfo interface {
        AddGoods(ctx context.Context, in *AddGoodsRequest, out *AddGoodsResponse) error
        GetGoods(ctx context.Context, in *GetGoodsRequest, out *GetGoodsResponse) error
    }
    type Goodsinfo struct {
        goodsinfo
    }
    h := &goodsinfoHandler{hdlr}
    return s.Handle(s.NewHandler(&Goodsinfo{h}, opts...))
}

type goodsinfoHandler struct {
    GoodsinfoHandler
}

func (h *goodsinfoHandler) AddGoods(ctx context.Context, in *AddGoodsRequest, out *AddGoodsResponse) error {
    return h.GoodsinfoHandler.AddGoods(ctx, in, out)
}

func (h *goodsinfoHandler) GetGoods(ctx context.Context, in *GetGoodsRequest, out *GetGoodsResponse) error {
    return h.GoodsinfoHandler.GetGoods(ctx, in, out)
}

启动goodsinfo服务端:

启动后consul UI展示如下:

看到 注册了goodsinfo服务

goodsinfo详情界面:

二.使用Go Web框架Gin创建微服务客户端

  1. 搭建Gin框架

上一节的微服务是直接在 main.go中调用的,这里使用使用 Go Web框架Gin创建微服务客户端,
Gin框架需要自己搭建,这里参考 [golang gin框架] 1.Gin环境搭建,程序的热加载,路由GET,POST,PUT,DELETE 里面的内容来进行开发

搭建好了后,引入相关包:

测试Gin框架是否ok:

  1. 在Gin框架中集成微服务客户端-proto相关

把上面微服务服务端的proto文件夹赋值到项目根目录,如下:
然后查看相关包,看看是否引入,没有引入的话,在项目main.go所在的目录运行: go mod tidy 即可
  1. 在Gin框架中集成微服务客户端

还是以上面goodsinfo微服务为案例,集成goodsinfo微服务客户端

(1).封装创建consul的服务

参考上一节 goodsinfo客户端代码

上一节客户端代码如下:

package main

import (
    "context"
    "go-micro.dev/v4/registry"
    pb "goodsinfo-client/proto"
    "go-micro.dev/v4"
    "go-micro.dev/v4/logger"
    "github.com/go-micro/plugins/v4/registry/consul"
)

var (
    service = "goodsinfo"
    version = "latest"
)

func main() {
    //集成consul
    consulReg := consul.NewRegistry(
        //指定微服务的ip:  选择注册服务器地址,默认为本机,也可以选择consul集群中的client
        registry.Addrs("127.0.0.1:8500"),
        )
    // Create service
    srv := micro.NewService(
        //注册consul
        micro.Registry(consulReg),
    )

    srv.Init()

    // 创建客户端服务
    c := pb.NewGoodsinfoService(service, srv.Client())

    // Call service
    rsp, err := c.AddGoods(context.Background(), &pb.AddRequest{
        Title: "我是一个商品",
        Price: "20.22",
        Content: "内容展示",
    })
    if err != nil {
        logger.Fatal(err)
    }

    logger.Info(rsp)
}
上述代码分为两部分:第一部分是 集成consul, 第二部分是 创建客户端服务, 当一个项目中存在 多个微服务 时, 都要 使用到第一部分,所以第一部分是 公共的 ,可以把它 封装 一下,然后在各个微服务客户端中调用
在项目的models下创建initGoodsMicro.go,封装集成consul相关逻辑,代码如下:
package models

//微服务客户端配置: 初始化consul配置,当一个项目中多个微服务时,就很方便了
//建议:一个微服务对应一个客户端,这样好管理

import (
    "github.com/go-micro/plugins/v4/registry/consul"
    "go-micro.dev/v4"
    "go-micro.dev/v4/client"
    "go-micro.dev/v4/registry"
)

//MicroClient: 全局变量 在外部的包中可以调用
var MicroClient client.Client

//init 方法: 当程序运行时就会自动执行
func init() {
    consulRegistry := consul.NewRegistry(
        //指定微服务的ip:  选择注册服务器地址,默认为本机,也可以选择consul集群中的client,建议一个微服务对应一个consul集群的client
        registry.Addrs("127.0.0.1:8500"),
    )
    // Create service
    srv := micro.NewService(
        micro.Registry(consulRegistry),
    )
    srv.Init()

    MicroClient = srv.Client()
}

(2).创建Goods相关方法

在controllers/frontend下创建GoodsController,里面有两个方法:Index(获取商品列表),Add(添加商品),代码如下:
package frontend

import (
    "context"
    "gindemo/models"
    pb "gindemo/proto"
    "github.com/gin-gonic/gin"
    log "go-micro.dev/v4/logger"
)

type GoodsController struct{}

//获取商品列表
func (con GoodsController) Index(c *gin.Context) {
    // Create client: 这里的服务名称需要和服务端注册的名称一致
    microClient := pb.NewGoodsinfoService("goodsinfo", models.MicroClient)
    // Call service
    rsp, err := microClient.GetGoods(context.Background(), &pb.GetGoodsRequest{})
    //判断是否获取商品成功: 这里会调用服务端handler/goodsinfo.go中的方法GetGoods()
    if err != nil {
        log.Fatal(err)
    }
    //记录log
    log.Info(rsp)
    //返回
    c.JSON(200, gin.H{
        "result": rsp.GoodsList,
    })
}

//添加商品
func (con GoodsController) Add(c *gin.Context) {
    // Create client
    microClient := pb.NewGoodsinfoService("goodsinfo", models.MicroClient)
    // Call service
    rsp, err := microClient.AddGoods(context.Background(), &pb.AddGoodsRequest{
        Parame: &pb.GoodsModel{
            Title:   "我是一个商品",  //这里的商品数据是模拟数据, 一般项目中是从前端获取
            Price:   12.0,
            Content: "我是一个内容",
        },
    })
    //判断是否增加成功
    if err != nil {
        log.Fatal(err)
    }
    //记录log
    log.Info(rsp)
    //返回
    c.JSON(200, gin.H{
        "message": rsp.Message,
        "success": rsp.Success,
    })
}

(3).在routers/frontendRouters.go中增加路由

package routers

import (
    "gindemo/controllers/frontend"
    "github.com/gin-gonic/gin"
)

func DefaultRoutersInit(r *gin.Engine) {
    defaultRouters := r.Group("/")
    {
        defaultRouters.GET("/", frontend.DefaultController{}.Index)
        //获取商品列表
        defaultRouters.GET("/goods", frontend.GoodsController{}.Index)
        //添加商品
        defaultRouters.GET("/goods/add", frontend.GoodsController{}.Add)
    }
}

(4).查看微服务客户端是否成功

浏览器中访问 http://localhost:8080/goods,http://localhost:8080/goods/add看看是否返回数据
返回数据成功,说明微服务客户端搭建成功

三.go-micro的负载均衡在项目中的使用

  1. 把goodsinfo微服务放置到另一台服务器上

复制server/goodsinfo微服务服务端代码,把代码放置到 另一台服务器上,然后运行go run main.go把goodsinfo微服务注册到 consul集群中对应的 服务发现里面就可以了,为了区分是来自不同服务器的同一个微服务,这里在handler/goodsinfo.go修改一下,返回的Title进行一个标识,表示调用的是不同服务器上的微服务,达到 负载均衡的效果,代码如下:
package handler

import (
    "context"
    "strconv"
    "go-micro.dev/v4/logger"
    pb "goodsinfo/proto"
)

type Goodsinfo struct{}

//增加商品
func (e *Goodsinfo) AddGoods(ctx context.Context, req *pb.AddGoodsRequest, rsp *pb.AddGoodsResponse) error {
    logger.Infof("request: %v", req)
    //书写返回的逻辑结果
    rsp.Message = "增加成功"
    rsp.Success = true
    return nil
}

//获取商品
func (e *Goodsinfo) GetGoods(ctx context.Context, req *pb.GetGoodsRequest, rsp *pb.GetGoodsResponse) error {
    var tempList []*pb.GoodsModel
    //模拟从数据库中获取商品数据
    for i := 0; i < 10; i++ {
        tempList = append(tempList, &pb.GoodsModel{
            Title:   "goodsinfo1第" + strconv.Itoa(i) + "条数据",  // goodsinfo1或者goodsinfo2代表不同服务器的同一个微服务
            Price:   float64((i + 1) * 2),
            Content: "goodsinfo1第" + strconv.Itoa(i) + "内容",
        })
    }
    rsp.GoodsList = tempList
    return nil
}

main.go中代码:

修改 micro.Address("127.0.0.1:8080"), 这里面的ip表示不同consul集群服务器ip,在这里模拟不同服务器负载均衡,把 micro.Address("127.0.0.1:8080")修改为 micro.Address("127.0.0.1:8081"),表示在不同服务器上的同一个微服务
package main

import (
    "goodsinfo/handler"
    pb "goodsinfo/proto"
    "go-micro.dev/v4"
    "go-micro.dev/v4/logger"
    "github.com/go-micro/plugins/v4/registry/consul"
)

var (
    service = "goodsinfo"
    version = "latest"
)

func main() {
    //集成consul
    consulReg := consul.NewRegistry()
    // Create service
    srv := micro.NewService(
        micro.Address("127.0.0.1:8081"),  //指定微服务的ip:  选择注册服务器地址,也可以不配置,默认为本机,也可以选择consul集群中的client
        micro.Name(service),
        micro.Version(version),
        //注册consul
        micro.Registry(consulReg),
    )
    srv.Init(
        micro.Name(service),
        micro.Version(version),
    )

    // Register handler
    if err := pb.RegisterGoodsinfoHandler(srv.Server(), new(handler.Goodsinfo)); err != nil {
        logger.Fatal(err)
    }
    // Run service
    if err := srv.Run(); err != nil {
        logger.Fatal(err)
    }
}
  1. 启动goodsinfo微服务

启动不同服务器上的goodsinfo微服务
  1. consul UI上查看效果

说明微服务启动成功了
  1. 运行客户端代码

运行项目代码: go run main.go,访问http://localhost:8080/goods,这时客户端就会通过轮询的方式查询consul服务器的goodsinfo服务端微服务,可以通过刷新的方式来检测,看看是否轮询查询
通过上面的方式检测,发现客户端获取数据是通过轮询的方式从consul的不同微服务服务器上获取数据的,负载均衡操作就完成了

四.Go Web框架(Beego)调用go-micro微服务

  1. 新建beego项目

在新建beego目录之前,需要按照go以及配置其环境变量,见 [go学习笔记.第二章] 2.go语言的开发工具以及安装和配置SDK, 以及bee 工具,bee工具的安装见 Beego之Bee安装(Windows)以及创建,运行项目
在client目录下创建beego项目,使用命令 bee new beegodemo创建一个beego项目,如下图:

生成的项目目录:

  1. 初始化项目

先删除go.mod文件,然后通过 go mod init beegodemo, go mod tidy 引入包,重新初始化项目
  1. 测试项目是否正常

(1).在controllers下新建goods.go

代码如下:
package controllers

//商品相关

import (
    beego "github.com/beego/beego/v2/server/web"
)

type GoodsController struct {
    beego.Controller
}

func (c *GoodsController) Get() {
    c.Data["json"] =map[string]interface{} {
        "message": "get",
    }

    c.ServeJSON()
}

func (c *GoodsController) Add() {
    c.Data["json"] =map[string]interface{} {
        "message": "add",
    }

    c.ServeJSON()
}

(2).设置路由

在routers/routers.go中完善控制器goods.go中的路由方法,代码如下:
package routers

import (
    "beegodemo/controllers"
    beego "github.com/beego/beego/v2/server/web"
)

func init() {
    beego.Router("/", &controllers.MainController{})

    //goods相关路由
    beego.Router("/goods", &controllers.GoodsController{})
    beego.Router("/goods/add", &controllers.GoodsController{}, "get:Add") //这里测试使用get方式,正式项目使用restful模式操作
}

(3).运行

在项目目录下,运行 bee run,然后浏览器中访问:http://127.0.0.1:8080/goods,看看是否正常显示相关数据
项目运行正常
  1. 搭建goodsinfo商品微服务beego客户端

(1).引入proto文件

把上面gin项目中的 proto文件复制到beegodemo项目下

(2).引入initGoodsMicro.go

把上面gin项目中的models/ initGoodsMicro.go文件复制到beegodemo项目models下

(3).编写controllers/goods.go代码

完善goods.go下的方法:商品列表方法Get(), 增加商品方法Add(),代码如下:
package controllers

//商品相关

import (
    "context"
    "beegodemo/models"
    pb "beegodemo/proto"
    log "go-micro.dev/v4/logger"
    beego "github.com/beego/beego/v2/server/web"

)

type GoodsController struct {
    beego.Controller
}

//商品列表
func (c *GoodsController) Get() {
    // Create client: 这里的服务名称需要和服务端注册的名称一致
    microClient := pb.NewGoodsinfoService("goodsinfo", models.MicroClient)
    // Call service
    rsp, err := microClient.GetGoods(context.Background(), &pb.GetGoodsRequest{})
    //判断是否获取商品成功: 这里会调用服务端handler/goodsinfo.go中的方法GetGoods()
    if err != nil {
        log.Fatal(err)
    }
    //记录log
    log.Info(rsp)
    //返回
    c.Data["json"] =map[string]interface{} {
        "result": rsp.GoodsList,
    }

    c.ServeJSON()
}

//添加商品
func (c *GoodsController) Add() {
    // Create client
    microClient := pb.NewGoodsinfoService("goodsinfo", models.MicroClient)
    // Call service
    rsp, err := microClient.AddGoods(context.Background(), &pb.AddGoodsRequest{
        Parame: &pb.GoodsModel{
            Title:   "增加一个商品beego",  //这里的商品数据是模拟数据, 一般项目中是从前端获取
            Price:   12.0,
            Content: "我是一个内容beego",
        },
    })
    //判断是否增加成功
    if err != nil {
        log.Fatal(err)
    }
    //记录log
    log.Info(rsp)
    //返回
    c.Data["json"] =map[string]interface{} {
        "message": rsp.Message,
        "success": rsp.Success,
    }
    c.ServeJSON()
}

(4).引入项目所需的包

再次执行命令 go mod tidy引入项目需要的包,在这里可以会出现不能引入包的情况,则需要 手动执行,命令为 go get xxx,也可以参考 https://blog.csdn.net/zhoupenghui168/article/details/131252892#t9

(5).启动微服务服务端

参考第三步骤: 三.go-micro的 负载均衡在项目中的使用
查看consul UI,看看goodsinfo微服务是否启动成功

(6).查看是否能够访问,是否可以负载均衡操作

1).先运行bee run,启动项目

2).访问

访问http://127.0.0.1:8080/goods,以及http://127.0.0.1:8080/goods/add,看看是否成功

刷新页面,查看:

好了,Beego框架调用go-micro微服务功能完成
  1. 总结

Beego框架调用go-micro微服务其实和Gin框架调用go-micro微服务差不多,主要的区别就是需要下载bee工具,以及要引入beego框架所需要的包,然后就是框架的路由操作,返回操作略有不同

[上一节][golang 微服务] 7. go-micro框架介绍,go-micro脚手架,go-micro结合consul搭建微服务案例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/702858.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手机上怎么压缩视频?教你几招手机压缩视频小技巧

压缩视频是一种有益的技术&#xff0c;可以帮助人们在存储、传输和观看视频时更有效率和便捷。尤其是在现今数字化信息时代&#xff0c;视频已经成为人们日常生活中不可或缺的一部分&#xff0c;因此更需要使用视频压缩技术来更好地管理和使用这些视频文件。下面给大家分享几种…

Yolov8优化: 多分支卷积模块RFB,扩大感受野提升小目标检测精度

1.RFB-Net介绍 论文&#xff1a;https://arxiv.org/pdf/1711.07767.pdf 代码&#xff1a;GitHub - GOATmessi7/RFBNet: Receptive Field Block Net for Accurate and Fast Object Detection, ECCV 2018 受启发于人类视觉的Receptive Fields结构&#xff0c;本文提出RFB&#xf…

3.1 C++纯虚函数

C 纯虚函数 C的纯虚函数是一种特殊的虚函数&#xff0c;没有函数体&#xff0c;只有函数原型。 纯虚函数语法格式为&#xff1a; 等号后面的 0 表示该函数为纯虚函数。 纯虚函数在抽象类中定义&#xff0c;抽象类是指包含至少一个纯虚函数的类&#xff0c;不能被实例化。 …

bim在建筑工程中的应用有哪些?

BIM以其在协同设计、冲突检测、可视化沟通、项目控制和可持续性设计等方面的应用&#xff0c;提高了建筑工程的效率、质量和可持续性&#xff0c;为各利益相关者带来了诸多益处。它已逐渐成为现代建筑工程不可或缺的重要工具和方法。BIM在建筑工程的作用越来越明显。 ​  BIM…

HOT29-删除链表的倒数第 N 个结点

leetcode原题链接&#xff1a;删除链表的倒数第 N 个结点 题目描述 删除链表的倒数第 N 个结点。 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,…

伦敦金k线图基础知识有多重要?

正所谓万丈高楼平地起&#xff0c;不积跬步无以至千里&#xff0c;在投资市场上也才如此&#xff0c;从来没有投资者能够在伦敦金市场上一蹴而就地取得成功&#xff0c;很多成功的交易者都是一步一个脚印&#xff0c;从最基础知识开始学起&#xff0c;逐渐成为专业的投资者。 看…

新星计划2023【Java基础及数据库Mysql】学习方向报名入口!

新星计划2023【Java基础及数据库Mysql】学习方向报名入口&#xff01; 一、关于本学习方向导师二、关于本学习方向官方微信群三、关于活动时间&奖品&要求四、学习计划五、TOP5评选规则六、活动要求七、注意事项 本赛道是针对那些希望从事Java开发并且想要学习如何与数据…

NVIDIA-Linux-x86_64-535.54.03.run cuda_12.2.0_535.54.03_linux.run下载地址

Official Drivers | NVIDIA Linux x64 (AMD64/EM64T) Display Driver | 535.54.03 | Linux 64-bit | NVIDIA 下载连接 Download NVIDIA, GeForce, Quadro, and Tesla DriversDownload drivers for NVIDIA graphics cards, video cards, GPU accelerators, and for other GeFor…

CAD转换PDF怎么转换?教你几种简单转换方法

CAD&#xff08;计算机辅助设计&#xff09;是一种广泛应用于工程设计和制造领域的软件。虽然CAD可以生成高质量的设计图纸&#xff0c;但是在与其他人共享这些图纸时可能会出现问题。因此&#xff0c;将CAD文件转换成PDF可以在各种设备上打开和查看。此外PDF还可以在不改变文件…

Postman设置断言

目录 前言&#xff1a; 一、断言的定义 二、Postman断言的语法 三、Postman中chai.js断言常用语法 前言&#xff1a; 在进行API测试时&#xff0c;断言是一项重要的功能。它能帮助我们验证接口的响应是否符合预期结果&#xff0c;从而确保API的正确性和可靠性。在Postman中…

Hudi 文件布局(File Layouts)

文章目录 Hudi File Layouts1 核心概念1.1 Base File1.2 Base File1.3 File Slice1.4 File Group 2. File Layouts写过程2.1 COW表2.2 MOR表 Hudi File Layouts 1 核心概念 File Layouts&#xff08;文件布局&#xff09;是指Hudi的数据文件在存储介质上的分布&#xff0c;Hu…

剑指 Offer ! ! 36. 二叉搜索树与双向链表

剑指 Offer 36. 二叉搜索树与双向链表 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点&#xff0c;只能调整树中节点指针的指向。 为了让您更好地理解问题&#xff0c;以下面的二叉搜索树为例&#xff1a; 思路&…

Spring Boot 中的 @MessageMapping 注解:原理、用法与示例

Spring Boot 中的 MessageMapping 注解&#xff1a;原理、用法与示例 前言 随着 Web 技术的发展&#xff0c;越来越多的应用程序开始使用 WebSocket 协议来实现实时通信。Spring Boot 提供了对 WebSocket 的支持&#xff0c;其中 MessageMapping 注解是一个常用的注解&#x…

【Keepalived】keepalived部署

1.keepalived二进制安装【Ubuntu20.04】 (1).官网下载二进制源码包 官网&#xff1a; https://keepalived.org/download.html 下载二进制包&#xff1a; wget https://keepalived.org/software/keepalived-2.2.7.tar.gz亦可通过window本地下载并上传 (2).解压文件 [rootu…

[ISO26262]汽车功能安全第二部分:功能安全管理

Road vehicles—Functionalsafety— Part 2: Management offunctionalsafety (ISO 26262-2:2011, MOD) GB/T34590《 道路车辆 功能安全》分为以下部分: TOPS:当前所浏览位置: 随着技术日益复杂、 软件和机电一体化应用不断增加, 来自系统性失效和随机硬件失效的风险逐渐…

蓝桥杯专题-试题版含答案-【两点距离】【字符串替换】【盗梦空间】【素数】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

无向图讲解

讲完了有向图,咱再来讲一下无向图。 直观来说若一个图中每条边都是无方向的,则称为无向图,无向图中的边均是顶点的无序对,无序对通常用圆括号表示。 定义 无向图G=<V,E>,其中: 1.V是非空集合,称为顶点集。 2.E是V中元素构成的无序二元组的集合,称为边集。 解释 直…

如何选择适合的项目管理软件?你需要考虑这些问题

在市场上有很多项目管理软件可供选择&#xff0c;但是如何确定哪种工具最易使用&#xff0c;哪些功能是项目管理必备的呢&#xff1f;在选择项目管理软件之前&#xff0c;你需要考虑以下问题&#xff1a; 项目性质 不同的项目需要不同的管理方法&#xff0c;敏捷管理适用于某…

chatgpt赋能python:Python量化数据来源-介绍

Python量化数据来源 - 介绍 Python在金融量化分析领域中得到了广泛的应用&#xff0c;这部分应用通常被称为Python量化金融。Python量化数据来源是Python量化金融分析的基础&#xff0c;只有良好的数据来源才能保证分析的准确性和有效性。 Python具有以其代码简洁易懂、强大的…

2023年富媒体行业研究报告

第一章 行业概况 富媒体行业是一个快速发展的领域&#xff0c;它涵盖了从视频、音频、动画、互动内容到虚拟现实和增强现实等多种媒体形式。这些媒体形式的共同特点是&#xff0c;它们都能提供比传统文本和静态图像更丰富、更引人入胜的用户体验。 在中国&#xff0c;富媒体行…