# devcloud-mini **Repository Path**: wshadm/devcloud-mini ## Basic Information - **Project Name**: devcloud-mini - **Description**: 简易版的devcloud-mini - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-01-24 - **Last Updated**: 2024-10-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 研发管理平台裁剪版 1. 创建项目骨架(同vblog一样) 2. 编写mcenter服务业务模块: + 认证 + 权限 3. 编写一个cmdb服务, 接入用户中心(mcenter) 4. 审计服务maudit, 记录用户操作日志, 核心使用消息独立(Kafka) 社区的一些微服务框架: + go-kit https://github.com/go-kit/kit/ + go-zero https://go-zero.dev/cn/docs/introduction + go-micro https://go-micro.dev/ + Kratos https://github.com/cloudwego/kitex ## Web框架: go-restful ![](https://s2.51cto.com/images/blog/202112/31140311_61ce9d1fddcfb92964.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/format,webp) 1. 初始化container ```go //import "github.com/emicklei/go-restful/v3" r := restful.DefaultContainer // 设置默认ContentType restful.DefaultResponseContentType(restful.MIME_JSON) restful.DefaultRequestContentType(restful.MIME_JSON) // CORS中间件 cors := restful.CrossOriginResourceSharing{ AllowedHeaders: []string{"*"}, AllowedDomains: []string{"*"}, AllowedMethods: []string{"HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE"}, CookiesAllowed: false, Container: r, } // 使用Filter加载 GoRestful中间件(等同于Gin Use) r.Filter(cors.Filter) ``` 2. 模块注册路由到Container: ioc: LoadGoRestfulApi ```go // LoadHttpApp 装载所有的http app func LoadGoRestfulApi(pathPrefix string, root *restful.Container) { objects := store.Namespace(ApiNamespace) for _, obj := range objects.Items { api, ok := obj.(GoRestfulApiObject) if !ok { continue } // 为模块创建一个WebService pathPrefix = strings.TrimSuffix(pathPrefix, "/") ws := new(restful.WebService) ws. Path(fmt.Sprintf("%s/%s/%s", pathPrefix, api.Version(), api.Name())). Consumes(restful.MIME_JSON, form.MIME_POST_FORM, form.MIME_MULTIPART_FORM, yaml.MIME_YAML, yamlk8s.MIME_YAML). Produces(restful.MIME_JSON, yaml.MIME_YAML, yamlk8s.MIME_YAML) //把模块里面的路由 通通注册到这个WebService api.Registry(ws) // webservice 添加到container root.Add(ws) } } ``` ## GRPC 从Protobuf的角度看,gRPC只不过是一个针对service接口生成代码的生成器。因此我们需要提前安装grpc的代码生成插件 # go install google.golang.org/protobuf/cmd/protoc-gen-go@latest # 安装protoc-gen-go-grpc插件 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 生成tag插件:go get https://github.com/favadi/protoc-go-inject-tag 项目的根路径: devcloud-mini(作为一个微服务工程) 需要进入mcenter项目内进行代码生成 ```sh $ protoc -I=. --go_out=.. --go-grpc_out=.. --go_opt=module="gitee.com/wshadm/go11/devcloud-mini" --go-grpc_opt=module="gitee.com/wshadm/go11/devcloud-mini" mcenter/apps/token/*/*.proto xxx.pb.go生成在什么位置取决于: 1、xxx.proto中的option go_package="gitee.com/wshadm/go11/devcloud-mini/mcenter/apps/token"; 2、--go_out和--go-grpc_out的位置 示例:在/d/gocode/dev-golang/devcloud/devcloud-mini目录中执行protocl命令 $ protoc -I=. --go_out=. --go-grpc_out=. --go_opt=module="gitee.com/wshadm/go11/devcloud-mini" --go-grpc_opt=module="gitee.com/wshadm/go11/devcloud-mini" cmdb/apps/*/pb/*.proto pb.go文件中的go_package"gitee.com/wshadm/go11/devcloud-mini/mcenter/apps/token"; 由于--go_out=.. --go-grpc_out=..都是用的"..",那么输出路径会往上一层输出,导致在上一层新建了目录: mcenter/apps/token/xxx.pb.go 提问chatgpt:是不是可以简单理解为.proto文件中的go_package路径在哪,最终pb.go就会生成到哪里 是的,你的理解基本正确。在 Protocol Buffers 的 .proto 文件中,通过 go_package 选项指定 Go 代码生成的路径。这个路径包括了包名和生成的 .pb.go 文件的相对位置。 例如,如果你的 .proto 文件中有如下声明: option go_package = "gitee.com/wshadm/go11/devcloud-mini/mcenter/apps/token"; 这将指示 Protocol Buffers 编译器生成的 Go 代码的包名为 token,而且生成的 .pb.go 文件将被放置在 gitee.com/wshadm/go11/devcloud-mini/mcenter/apps/token 目录下。 这样的设计有助于将生成的代码与其他 Go 项目或模块进行隔离,同时也使得生成的代码的导入路径具有意义。在使用 protoc 编译命令时,通过 --go_out 选项指定的输出目录与 go_package 中指定的路径相关联。 ``` 在过程中经常遇到rpc.proto文件中不能正常import(例如meta.proto),最后修改为与学习项目结构一致D:/go11/devcloud-mini恢复了,要多练 再执行: protoc-go-inject-tag -input="cmdb/apps/*/*.pb.go" ## protobuf自定义标签 1. 安装自定义proto 插件 ```sh go install google.golang.org/protobuf/cmdb/protoc-gen-go@latest go install google.golang.org/grpc/cmdb/protoc-gen-go-grpc@latest go install github.com/favadi/protoc-go-inject-tag@latest ``` ## 补充Swagger文档接口 ```go // API Doc h.r.Add(apidoc.APIDocs(h.apiDocPath, swagger.Docs)) logger.L().Debug().Msgf("Swagger API Doc访问地址: http://%s%s", conf.C().Http.Address(), h.apiDocPath) ``` [配置对象](./mcenter/conf/config.go) 1、单独的对象 mysql对象 http对象 Grpc对象 。。 2、对象的集合,封装到一个统一的配置对象当中 type Config struct { m *mysql h *http g *Grpc } 3、配置的初始化给一些默认配置,func NewConfig() *Config {} [读取配置](./mcenter/conf/load.go) 动态?还是? 不给路径就读默认,给了就赋值 修改配置时要有sycn.Mutex, 可以封装一个函数暴露全局供调用var config *Config , func C() *Config,其它不暴露。 日志库:定义日志的级别 protocol协议配置: [http配置]:创建httpserver对象,是用gin的,还是go-Restful的?[协议配置](./mcenter/protocol/http.go), 当中定义httpserver的属性和配置(例如 是否需要跨域),读取config中http相关配置,如ip,port,超时时间等等 配置[start]和[stop]部分,当中可以加[swagger文档]的配置 [Grpc配置](./mcenter/protocol/grpc.go):创建grpc.server对象和start、stop 同样也要读取config中的配置信息,ip port等 [初始化](./mcenter/apps/registry.go) [鉴权流程](./mcenter/docs/%20mcenter_auth.drawio) [权限最小化原则,流程](./mcenter/docs/rpc.drawio) 在 Go 语言中,接口权限最小化原则通常指的是在定义接口时,只包含必要的方法,以减少接口的复杂性和依赖性。这个原则与软件设计中的最小权限原则相似,目的是限制接口的暴露,使其只暴露出必要的行为,而不是暴露过多的细节。 内部服务实现[interface接口](./mcenter/apps/token/interface.go)就好,grpc或rpc则只暴露client端需要的接口方法就好,不必暴露过多 因此,确保内部服务实现的接口仅用于服务内部调用,并且只暴露给客户端需要的接口方法,是一个很好的设计原则,有助于提高系统的可维护性和安全性。 [rpc、grpc](./mcenter/apps/token/pb/model.proto) [消息对象](./mcenter/apps/token/pb/model.proto) 使用 .proto 文件定义了要暴露给客户端的接口以及数据对象格式。这个文件定义了消息的结构和服务接口的方法。然后,使用 Protocol Buffers 的编译器(如 protoc)根据这个 .proto 文件生成相应语言的代码,例如 Go 语言的 .pb.go 文件。 生成的 .pb.go 文件中包含了客户端和服务端都可以使用的代码: 服务端代码:.pb.go 文件中包含了服务端需要实现的接口,这些接口定义了客户端可以调用的方法。服务端需要实现这些接口来提供具体的功能。 客户端代码:同样的 .pb.go 文件中也包含了客户端需要使用的代码,这些代码提供了对服务端定义的方法的调用接口。客户端可以使用这些方法来与服务端通信。 因此,.pb.go 文件是根据 .proto 文件生成的,其中包含了客户端和服务端所需的代码。这种方式使得定义接口和消息格式更加方便,并且通过自动生成代码,简化了客户端和服务端之间进行 RPC 或 gRPC 通信的过程。 [rpc_grpc.pb.go](./mcenter/apps/token/rpc_grpc.pb.go)) 文件通常包含 gRPC 协议相关的代码,其中包括根据 .proto 文件生成的 gRPC 服务接口的客户端和服务器端的代码,以及消息格式和序列化相关的代码。 [rpc.pb.go](./mcenter/apps/token/rpc.pb.go) 可能包含一些额外的业务逻辑代码,用于实现 .proto 文件中定义的 gRPC 服务接口中的方法。它也是根据 .proto 文件生成的,但可能包含了一些额外的业务逻辑,这取决于您是否在 .proto 文件中定义了自定义的方法或业务逻辑 [impl](./mcenter/apps/token/impl/impl.go)控制器 init初始化时,用ioc注册业务的实现 显式的约束对象实现接口,以及实现Grpcserver和Ioc的接口,编写ioc的相关方法 注册Grpcserver和impl业务实现,给客户端用的, 这个注册过程意味着将 impl 中定义的远程过程调用(RPC)服务方法暴露给客户端, 使得客户端能够通过 gRPC 连接调用这些方法。 [业务实现](./mcenter/apps/token/impl/token.go),token的颁发和删除 api当中都是在使用Grpc、rpc协议时使用的。做了权限最小化。只暴露必要的 [api-http](./mcenter/apps/token/api/http.go) 当中同样有IOC注册,来获取业务的实现方法 [api-token](./mcenter/apps/token/api/token.go) 通过绑定Handler和业务方法,来使用。当前使用的是GoRestful,需要符合框架签名。以及信息的自动脱敏。 [cmd](./mcenter/cmd/start/start.go) cobra命令行代码,启动http服务和Grpc服务,以及初始化业务部分的init方法 消息队列:kafka topic主题: 同一类消息在同一个topic中 partion分区: 一个topic可以分多个partion每个partition在存储层面是append log文件。任何发布到此partition的消息都会被直接追加到log文件的尾部 消费者组: [img.png](img.png)消费者组订阅一个主题,意味着主题下的所有分区都会被组中的消费者消费到, 并且主题下的每个分区只从属于组中的一个消费者,不可能出现组中的两个消费者负责同一个分区。 消费者从partition中消费数据,consumer有group的概念,每个group可以消费完整的一份topic中的数据。 如果分区数大于或者等于组中的消费者实例数,那么一个消费者会负责多个分区;如果消费者实例的数量大于分区数,有一些消费者是多余的,一直接不到消息而处于空闲状态。 即: 若consumer数量大于partition数量,会造成限制的consumer,产生浪费。 若consumer数量小于partition数量,会导致均衡失效,其中的某个或某些consumer会消费更多的任务。 消费分区是有策略的:https://cloud.tencent.com/developer/article/1953243 生产者: offset偏移量:每条消息在文件中的位置称为offset(偏移量),offset为一个long型数字,它是唯一标记一条消息。它唯一的标记一条消息。 kafka并没有提供其他额外的索引机制来存储offset,因为在kafka中几乎不允许对消息进行“随机读写” v1: #!/bin/bash stop_application() { #process_name=`ps -ef | grep start` app_pid=$(lsof -ti tcp:8080) if [ -n "$app_pid" ]; then #kill -15 $app_pid lsof -ti tcp:8080 | xargs kill -15 wait $app_pid } handle_sigterm() { stop_application exit 0 } trap 'handle_sigterm' SIGTERM #!/bin/bash stop_application() { process_name=`ps -ef | grep start` app_pid=$(ps -ef | grep $process_name | grep -v grep | awk '{print $2}') if [ -n "$app_pid" ]; then kill -15 $app_pid wait $app_pid else echo "Application process not found." fi } handle_sigterm() { stop_application exit 0 } trap 'handle_sigterm' SIGTERM wait $app_pid 16:35