diff --git a/VERSION b/VERSION index 6678432..03fca44 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.8 +0.3.9-beta.1 diff --git a/template/service/api/template/service/v1/demo.proto.tmpl b/template/service/api/template/service/v1/demo.proto.tmpl index 7c42f14..2288f0e 100644 --- a/template/service/api/template/service/v1/demo.proto.tmpl +++ b/template/service/api/template/service/v1/demo.proto.tmpl @@ -11,6 +11,9 @@ import "google/protobuf/empty.proto"; // 引入第三方依赖的 proto 文件 import "github.com/grpc-kit/api/known/example/v1/example.proto"; +// 同服务内的 proto 文件使用相对路径引用,既不包含代码仓库前缀 +import "api/{{ .Global.ProductCode }}/{{ .Global.ShortName }}/{{ .Template.Service.APIVersion }}/microservice.common.proto"; + // DemoRequest Demo 方法请求可使用的接口参数 message DemoRequest { // UUID 资源编号 @@ -22,15 +25,6 @@ message DemoRequest { // DemoResponse Demo方法响应的具体内容 message DemoResponse { - - message Pong { - // UUID 资源编号 - string uuid = 1; - - // Pong 单个资源响应内容 - grpc_kit.api.known.example.v1.ExampleResponse pong = 2; - } - // Pong 返回创建的资源 Pong pong = 1; diff --git a/template/service/api/template/service/v1/microservice.common.proto.tmpl b/template/service/api/template/service/v1/microservice.common.proto.tmpl new file mode 100644 index 0000000..244916f --- /dev/null +++ b/template/service/api/template/service/v1/microservice.common.proto.tmpl @@ -0,0 +1,23 @@ +syntax = "proto3"; + +// 根据具体的微服务名称做更改 +package {{ .Global.ProtoPackage }}; + +option go_package = "{{ .Global.Repository }}/api/{{ .Global.ProductCode }}/{{ .Global.ShortName }}/{{ .Template.Service.APIVersion }};{{ .Global.ShortName }}{{ .Template.Service.APIVersion }}"; + +// 引入 google 公共类型 +import "google/protobuf/timestamp.proto"; + +// 引入第三方依赖的 proto 文件 +import "github.com/grpc-kit/api/known/example/v1/example.proto"; + +message Pong { + // UUID 资源编号 + string uuid = 1; + + // Pong 单个资源响应内容 + grpc_kit.api.known.example.v1.ExampleResponse pong = 2; + + google.protobuf.Timestamp created_at = 8; + google.protobuf.Timestamp updated_at = 9; +} \ No newline at end of file diff --git a/template/service/api/template/service/v1/microservice.gateway.yaml.tmpl b/template/service/api/template/service/v1/microservice.gateway.yaml.tmpl index d4c842f..8e13165 100644 --- a/template/service/api/template/service/v1/microservice.gateway.yaml.tmpl +++ b/template/service/api/template/service/v1/microservice.gateway.yaml.tmpl @@ -3,9 +3,35 @@ config_version: 3 http: rules: + # 内置服务 + ## 健康检查 - selector: {{ .Global.ProtoPackage }}.{{ .Global.ServiceTitle }}.HealthCheck get: "/api/healthz" + # 参考示例,书籍管理,实现相同一组任务的方法整合在一起,前后均需空行隔开 + ## 获取书籍详情 + #- selector: {{ .Global.ProtoPackage }}.{{ .Global.ServiceTitle }}.GetBook + # get: "/api/publishers/{publisher_id}/books/{book_id}" + ## 获取书籍列表 + #- selector: {{ .Global.ProtoPackage }}.{{ .Global.ServiceTitle }}.ListBooks + # get: "/api/publishers/{publisher_id}/books" + ## 创建书籍实体 + #- selector: {{ .Global.ProtoPackage }}.{{ .Global.ServiceTitle }}.CreateBook + # post: "/api/publishers/{publisher_id}/books" + # body: "book" + ## 更新书籍实体 + #- selector: {{ .Global.ProtoPackage }}.{{ .Global.ServiceTitle }}.UpdateBook + # patch: "/api/publishers/{publisher_id}/books/{book_id}" + # body: "book" + ## 删除书籍实体 + #- selector: {{ .Global.ProtoPackage }}.{{ .Global.ServiceTitle }}.DeleteBook + # delete: "/api/publishers/{publisher_id}/books/{book_id}" + ## 归档书籍实体 + #- selector: {{ .Global.ProtoPackage }}.{{ .Global.ServiceTitle }}.ArchiveBook + # post: "/api/publishers/{publisher_id}/books/{book_id}:archive" + # body: "*" + + # 示例实现 - selector: {{ .Global.ProtoPackage }}.{{ .Global.ServiceTitle }}.Demo post: "/api/demo" body: "*" diff --git a/template/service/api/template/service/v1/microservice.proto.tmpl b/template/service/api/template/service/v1/microservice.proto.tmpl index be8b49d..c59a898 100644 --- a/template/service/api/template/service/v1/microservice.proto.tmpl +++ b/template/service/api/template/service/v1/microservice.proto.tmpl @@ -5,6 +5,7 @@ package {{ .Global.ProtoPackage }}; option go_package = "{{ .Global.Repository }}/api/{{ .Global.ProductCode }}/{{ .Global.ShortName }}/{{ .Template.Service.APIVersion }};{{ .Global.ShortName }}{{ .Template.Service.APIVersion }}"; // 同服务内的 proto 文件使用相对路径引用,既不包含代码仓库前缀 +//import "api/{{ .Global.ProductCode }}/{{ .Global.ShortName }}/{{ .Template.Service.APIVersion }}/microservice.common.proto"; import "api/{{ .Global.ProductCode }}/{{ .Global.ShortName }}/{{ .Template.Service.APIVersion }}/demo.proto"; // 引入依赖的外部 proto 文件 @@ -12,6 +13,20 @@ import "github.com/grpc-kit/api/known/status/v1/response.proto"; // 该微服务支持的 RPC 方法定义 service {{ title .Global.ServiceTitle }} { + // 内置服务,健康检查 rpc HealthCheck(grpc_kit.api.known.status.v1.HealthCheckRequest) returns (grpc_kit.api.known.status.v1.HealthCheckResponse) {} + + // 参考示例,书籍管理,实现相同一组任务的方法整合在一起,前后均需空行隔开 + // rpc GetBook(GetBookRequest) returns (Book) {} + // rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {} + // rpc CreateBook(CreateBookRequest) returns (Book) {} + // rpc UpdateBook(CreateBookRequest) returns (Book) {} + // rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) {} + // rpc ArchiveBook(ArchiveBookRequest) returns (ArchiveBookResponse) {} + + // 示例实现,可以删除 rpc Demo(DemoRequest) returns (DemoResponse) {} + + // xx 管理,实现相同一组任务的方法整合在一起 + // ... } diff --git a/template/service/go.mod.tmpl b/template/service/go.mod.tmpl index bfc49fb..ee24121 100644 --- a/template/service/go.mod.tmpl +++ b/template/service/go.mod.tmpl @@ -1,6 +1,6 @@ module {{ .Global.Repository }} -go 1.22.7 +go 1.24 require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 diff --git a/template/service/handler/rpc_demo.go.tmpl b/template/service/handler/rpc_demo.go.tmpl index a148cc0..97ff1d4 100644 --- a/template/service/handler/rpc_demo.go.tmpl +++ b/template/service/handler/rpc_demo.go.tmpl @@ -25,7 +25,7 @@ func (m *Microservice) Demo(ctx context.Context, req *pb.DemoRequest) (*pb.DemoR Ping: &examplev1.ExampleResponse{}, // POST /api/demo // GET /api/demo/{uuid} - Pong: &pb.DemoResponse_Pong{ + Pong: &pb.Pong{ Uuid: "99feafb5-bed6-4daf-927a-69a2ab80c485", Pong: &examplev1.ExampleResponse{}, }, diff --git a/template/service/modeler/flow/README.md b/template/service/modeler/flow/README.md new file mode 100644 index 0000000..781d8f4 --- /dev/null +++ b/template/service/modeler/flow/README.md @@ -0,0 +1,27 @@ +## 工作流 + +规范: + +```text +# 用于定义 argo workflow 流程编排 +template/ + +# 用于实现工作流脚本编码 +script/ +``` + +每个工作流根据类型分组在各自文件夹下,如: + +```text +template/example/ +script/example/ +``` + +当创建新目录时,需要在 `embed.go` 中添加对应目录的文件引用,如: + +```text +//go:embed example/*.sh +//go:embed example/*.py +``` + +所有脚本大小尽量避免超过 100kb 否则有可能无法运行成功。 diff --git a/template/service/modeler/flow/flow.go.tmpl b/template/service/modeler/flow/flow.go.tmpl index 7062aad..c72172f 100644 --- a/template/service/modeler/flow/flow.go.tmpl +++ b/template/service/modeler/flow/flow.go.tmpl @@ -31,5 +31,48 @@ func NewClient(logger *logrus.Entry, fcc *cfg.FlowClientConfig) (*Client, error) // Create xx func (w *Client) Create(ctx context.Context) error { + /* + if err := w.sync(ctx, + example.NewExampleShell(w.config).Template(), + example.NewExamplePython(w.config).Template(), + ); err != nil { + return err + } + */ + return nil -} \ No newline at end of file +} + +/* +func (w *Client) sync(ctx context.Context, templates ...*wfv1.WorkflowTemplate) error { + for _, tmpl := range templates { + un := &unstructured.Unstructured{} + + rawBody, err := json.Marshal(tmpl) + if err != nil { + return err + } + + if err = un.UnmarshalJSON(rawBody); err != nil { + return err + } + + gvk := un.GroupVersionKind() + res := schema.GroupVersionResource{ + Group: gvk.Group, + Version: gvk.Version, + Resource: fmt.Sprintf("%v%v", strings.ToLower(gvk.Kind), "s"), + } + + _, err = w.dynamicset. + Resource(res). + Namespace(un.GetNamespace()). + Apply(ctx, un.GetName(), un, metav1.ApplyOptions{Force: true, FieldManager: w.config.Appname}) + if err != nil { + return err + } + } + + return nil +} +*/ diff --git a/template/service/modeler/flow/script/demo.py b/template/service/modeler/flow/script/demo.py deleted file mode 100644 index e75154b..0000000 --- a/template/service/modeler/flow/script/demo.py +++ /dev/null @@ -1 +0,0 @@ -print("hello world") \ No newline at end of file diff --git a/template/service/modeler/flow/script/embed.go.tmpl b/template/service/modeler/flow/script/embed.go.tmpl index 19731e6..e0d99dc 100644 --- a/template/service/modeler/flow/script/embed.go.tmpl +++ b/template/service/modeler/flow/script/embed.go.tmpl @@ -4,6 +4,6 @@ import ( "embed" ) -//go:embed *.sh -//go:embed *.py +//go:embed example/*.sh +//go:embed example/*.py var Assets embed.FS \ No newline at end of file diff --git a/template/service/modeler/flow/script/example/demo.py b/template/service/modeler/flow/script/example/demo.py new file mode 100644 index 0000000..81726d5 --- /dev/null +++ b/template/service/modeler/flow/script/example/demo.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +脚本名称: demo.py +功能描述: 简要描述脚本功能 +作者: Your Name +日期: YYYY-MM-DD +版本: 1.0 +""" + +import sys +import logging +from datetime import datetime, timezone +from typing import List + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + stream=sys.stdout +) + +# 全局变量 +update_time = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") + +def setup() -> None: + """初始化环境设置""" + logging.info("初始化环境") + logging.info("更新时间: %s", update_time) + +def process_data(data: List[int]) -> List[int]: + """ + 处理数据的示例函数 + + :param data: 输入的整数列表 + :return: 处理后的整数列表 + """ + + return [x * 2 for x in data] + +def main() -> None: + """主函数""" + setup() + + # 局部变量 + data = [1, 2, 3, 4, 5] + result = process_data(data) + + logging.info("处理结果: %s", result) + +if __name__ == "__main__": + # 全局变量定义,使用大写 + API_ENDPOINT = "" + API_TOKEN = "" + + try: + main() + except Exception as e: + logging.exception("出现异常") + sys.exit(1) diff --git a/template/service/modeler/flow/script/demo.sh b/template/service/modeler/flow/script/example/demo.sh similarity index 100% rename from template/service/modeler/flow/script/demo.sh rename to template/service/modeler/flow/script/example/demo.sh diff --git a/template/service/modeler/flow/template/demo.go.tmpl b/template/service/modeler/flow/template/demo.go.tmpl deleted file mode 100644 index 38cdfe4..0000000 --- a/template/service/modeler/flow/template/demo.go.tmpl +++ /dev/null @@ -1 +0,0 @@ -package template diff --git a/template/service/modeler/flow/template/example/python.go.tmpl b/template/service/modeler/flow/template/example/python.go.tmpl new file mode 100644 index 0000000..8aab1e5 --- /dev/null +++ b/template/service/modeler/flow/template/example/python.go.tmpl @@ -0,0 +1,97 @@ +package example + +/* +import ( + "fmt" + + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/grpc-kit/pkg/cfg" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + "{{ .Global.Repository }}/modeler/flow/script" +) + +// ExamplePython xx +type ExamplePython struct { + config *cfg.FlowClientConfig + template *wfv1.WorkflowTemplate +} + +// NewExampleShell xx +func NewExamplePython(config *cfg.FlowClientConfig) *ExamplePython { + t := &wfv1.WorkflowTemplate{ + TypeMeta: metav1.TypeMeta{ + Kind: "WorkflowTemplate", + APIVersion: "argoproj.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%v-example-python", config.Appname), + Namespace: config.Namespace, + }, + Spec: wfv1.WorkflowSpec{ + Entrypoint: "main", + ServiceAccountName: "executor", + Volumes: []corev1.Volume{ + { + Name: "global-tmp", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + Templates: []wfv1.Template{}, + ActiveDeadlineSeconds: ptr.To[int64](3600), + ArchiveLogs: ptr.To[bool](false), + }, + } + + d := &ExamplePython{config: config, template: t} + d.initTemplates() + + return d +} + +// Template xx +func (d *ExamplePython) Template() *wfv1.WorkflowTemplate { + return d.template +} + +func (d *ExamplePython) initTemplates() { + d.template.Spec.Templates = append(d.template.Spec.Templates, d.mainTemplate()) + d.template.Spec.Templates = append(d.template.Spec.Templates, d.scriptDemo()) +} + +func (d *ExamplePython) mainTemplate() wfv1.Template { + return wfv1.Template{ + Name: "main", + DAG: &wfv1.DAGTemplate{ + Tasks: []wfv1.DAGTask{ + { + Name: "demo", + Template: "demo", + }, + }, + }, + } +} + +func (d *ExamplePython) scriptDemo() wfv1.Template { + sourceRaw, err := script.Assets.ReadFile("example/demo.py") + if err != nil { + panic(err) + } + + return wfv1.Template{ + Name: "demo", + Script: &wfv1.ScriptTemplate{ + Container: corev1.Container{ + Image: "ccr.ccs.tencentyun.com/opsaid/python:alpine3.6", + Command: []string{"python"}, + }, + Source: string(sourceRaw), + }, + } +} +*/ diff --git a/template/service/modeler/flow/template/example/shell.go.tmpl b/template/service/modeler/flow/template/example/shell.go.tmpl new file mode 100644 index 0000000..e91af4b --- /dev/null +++ b/template/service/modeler/flow/template/example/shell.go.tmpl @@ -0,0 +1,97 @@ +package example + +/* +import ( + "fmt" + + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/grpc-kit/pkg/cfg" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + {{ .Global.Repository }}/modeler/flow/script +) + +// ExampleShell xx +type ExampleShell struct { + config *cfg.FlowClientConfig + template *wfv1.WorkflowTemplate +} + +// NewExampleShell xx +func NewExampleShell(config *cfg.FlowClientConfig) *ExampleShell { + t := &wfv1.WorkflowTemplate{ + TypeMeta: metav1.TypeMeta{ + Kind: "WorkflowTemplate", + APIVersion: "argoproj.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%v-example-shell", config.Appname), + Namespace: config.Namespace, + }, + Spec: wfv1.WorkflowSpec{ + Entrypoint: "main", + ServiceAccountName: "executor", + Volumes: []corev1.Volume{ + { + Name: "global-tmp", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + Templates: []wfv1.Template{}, + ActiveDeadlineSeconds: ptr.To[int64](3600), + ArchiveLogs: ptr.To[bool](false), + }, + } + + d := &ExampleShell{config: config, template: t} + d.initTemplates() + + return d +} + +// Template xx +func (d *ExampleShell) Template() *wfv1.WorkflowTemplate { + return d.template +} + +func (d *ExampleShell) initTemplates() { + d.template.Spec.Templates = append(d.template.Spec.Templates, d.mainTemplate()) + d.template.Spec.Templates = append(d.template.Spec.Templates, d.scriptDemo()) +} + +func (d *ExampleShell) mainTemplate() wfv1.Template { + return wfv1.Template{ + Name: "main", + DAG: &wfv1.DAGTemplate{ + Tasks: []wfv1.DAGTask{ + { + Name: "demo", + Template: "demo", + }, + }, + }, + } +} + +func (d *ExampleShell) scriptDemo() wfv1.Template { + sourceRaw, err := script.Assets.ReadFile("example/demo.sh") + if err != nil { + panic(err) + } + + return wfv1.Template{ + Name: "demo", + Script: &wfv1.ScriptTemplate{ + Container: corev1.Container{ + Image: "ccr.ccs.tencentyun.com/opsaid/minideb:bullseye", + Command: []string{"sh"}, + }, + Source: string(sourceRaw), + }, + } +} +*/ diff --git a/template/service/modeler/independent_cfg.go.tmpl b/template/service/modeler/independent_cfg.go.tmpl index 871eb66..7e7c90f 100644 --- a/template/service/modeler/independent_cfg.go.tmpl +++ b/template/service/modeler/independent_cfg.go.tmpl @@ -4,8 +4,6 @@ import ( "context" "fmt" - entsql "entgo.io/ent/dialect/sql" - "github.com/grpc-kit/pkg/cfg" "github.com/sirupsen/logrus" "{{ .Global.Repository }}/modeler/ent" @@ -21,38 +19,6 @@ type IndependentCfg struct { Name string `mapstructure:"name"` } -// ClientIndependentOption 用户自定义初始化资源选项 -type ClientIndependentOption func(cfg *IndependentCfg) - -// WithLogger 传递日志实例 -func WithLogger(logger *logrus.Entry) ClientIndependentOption { - return func(cfg *IndependentCfg) { - cfg.logger = logger - } -} - -// WithDatabaseEntDriver 传递 ent 数据库实例 -func WithDatabaseEntDriver(driver *entsql.Driver) ClientIndependentOption { - return func(cfg *IndependentCfg) { - client := ent.NewClient(ent.Driver(driver)) - cfg.db = client - } -} - -// WithWorkflow 传递流水线配置 -func WithWorkflow(logger *logrus.Entry, fcc *cfg.FlowClientConfig) ClientIndependentOption { - return func(cfg *IndependentCfg) { - if fcc != nil { - client, err := flow.NewClient(logger, fcc) - if err != nil { - panic(err) - } - - cfg.flow = client - } - } -} - // Init 用于初始化实例 func (i *IndependentCfg) Init(opts ...ClientIndependentOption) error { ctx := context.Background() diff --git a/template/service/modeler/independent_option.go.tmpl b/template/service/modeler/independent_option.go.tmpl new file mode 100644 index 0000000..e195271 --- /dev/null +++ b/template/service/modeler/independent_option.go.tmpl @@ -0,0 +1,44 @@ +// Code generated by "grpc-kit-cli/{{ .Global.ReleaseVersion }}". DO NOT EDIT. + +package modeler + +import ( + entsql "entgo.io/ent/dialect/sql" + "github.com/grpc-kit/pkg/cfg" + "github.com/sirupsen/logrus" + + "{{ .Global.Repository }}/modeler/ent" + "{{ .Global.Repository }}/modeler/flow" +) + +// ClientIndependentOption 用户自定义初始化资源选项 +type ClientIndependentOption func(cfg *IndependentCfg) + +// WithLogger 传递日志实例 +func WithLogger(logger *logrus.Entry) ClientIndependentOption { + return func(cfg *IndependentCfg) { + cfg.logger = logger + } +} + +// WithDatabaseEntDriver 传递 ent 数据库实例 +func WithDatabaseEntDriver(driver *entsql.Driver) ClientIndependentOption { + return func(cfg *IndependentCfg) { + client := ent.NewClient(ent.Driver(driver)) + cfg.db = client + } +} + +// WithWorkflow 传递流水线配置 +func WithWorkflow(logger *logrus.Entry, fcc *cfg.FlowClientConfig) ClientIndependentOption { + return func(cfg *IndependentCfg) { + if fcc != nil { + client, err := flow.NewClient(logger, fcc) + if err != nil { + panic(err) + } + + cfg.flow = client + } + } +}