commit d074e4e58b3b5bbeade97a3f9ffcff7253dde309 Author: NoahLan <6995syu@163.com> Date: Mon Aug 7 14:40:23 2023 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..d72aa7a --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +goctls-template +--- + +Custom golang template repo for [goctls](https://github.com/suyuan32/goctls). \ No newline at end of file diff --git a/api/authortymiddleware.tpl b/api/authortymiddleware.tpl new file mode 100644 index 0000000..cf0fbbb --- /dev/null +++ b/api/authortymiddleware.tpl @@ -0,0 +1,87 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/casbin/casbin/v2" + "github.com/zeromicro/go-zero/core/stores/redis" + + "git.noahlan.cn/noahlan/ntools-go/core/nlog" + "git.noahlan.cn/noahlan/ntools-go/zero/statusz" + "git.noahlan.cn/noahlan/ntool-biz/core/nstatus" + {{if .useTrans}} + "git.noahlan.cn/noahlan/ntools-go/core/i18n"{{end}} +) + +type AuthorityMiddleware struct { + Cbn *casbin.Enforcer + Rds *redis.Redis +} + +func NewAuthorityMiddleware(cbn *casbin.Enforcer, rds *redis.Redis) *AuthorityMiddleware { + return &AuthorityMiddleware{ + Cbn: cbn, + Rds: rds, + } +} + +func (m *AuthorityMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + {{if .useTrans}}const transErr = true{{else}}const transErr = false{{end}} + // get the path + obj := r.URL.Path + // get the method + act := r.Method + // get the role id + roleIds := r.Context().Value("roleIds").(string) + + // check jwt blacklist + jwtResult, err := m.Rds.Get("token_" + r.Header.Get("Authorization")) + if err != nil { + nlog.Errorw("redis error in jwt", nlog.Field("detail", err.Error())) + statusz.ResponseHandler(r, w, transErr, nil, nstatus.NewApiInternalErr(err.Error())) + return + } + if jwtResult == "1" { + nlog.Errorw("token in blacklist", nlog.Field("detail", r.Header.Get("Authorization"))) + statusz.ResponseHandler(r, w, transErr, nil, nstatus.NewApiErrWithCode(http.StatusUnauthorized)) + return + } + + result := batchCheck(m.Cbn, roleIds, act, obj) + + if result { + nlog.Infow("HTTP/HTTPS Request", nlog.Field("UUID", r.Context().Value("userId").(string)), + nlog.Field("path", obj), nlog.Field("method", act)) + next(w, r) + return + } else { + nlog.Errorw("the role is not permitted to access the API", nlog.Field("roleIds", roleIds), + nlog.Field("path", obj), nlog.Field("method", act)) + statusz.ResponseHandler(r, w, transErr, nil, nstatus.NewApiForbiddenErr("common.permissionDeny")) + return + } + } +} + +func batchCheck(cbn *casbin.Enforcer, roleIds, act, obj string) bool { + var checkReq [][]any + for _, v := range strings.Split(roleIds, ",") { + checkReq = append(checkReq, []any{v, obj, act}) + } + + result, err := cbn.BatchEnforce(checkReq) + if err != nil { + nlog.Errorw("Casbin enforce error", nlog.Field("detail", err.Error())) + return false + } + + for _, v := range result { + if v { + return true + } + } + + return false +} diff --git a/api/config.tpl b/api/config.tpl new file mode 100644 index 0000000..2ac6d18 --- /dev/null +++ b/api/config.tpl @@ -0,0 +1,25 @@ +package config + +import ( + nconfig "git.noahlan.cn/noahlan/ntool-biz/core/config" + {{if .useCasbin}} + "git.noahlan.cn/noahlan/ntool-biz/core/casbin" + dbconfig "git.noahlan.cn/noahlan/ntool-biz/core/orm/nent/config"{{else}} + {{if .useEnt}} + dbconfig "git.noahlan.cn/noahlan/ntool-biz/core/orm/nent/config" + {{end}}{{end}} + "github.com/zeromicro/go-zero/rest" +) + +type Config struct { + rest.RestConf + Auth nconfig.AuthConf + CROSConf nconfig.CORSConf + {{if .useCasbin}} + CasbinConf casbin.CasbinConf + DatabaseConf dbconfig.Database + RedisConf nconfig.RedisConf{{else}} + {{if .useEnt}}DatabaseConf dbconfig.Database{{end}} + {{end}} + {{.jwtTrans}} +} diff --git a/api/context.tpl b/api/context.tpl new file mode 100644 index 0000000..11f1694 --- /dev/null +++ b/api/context.tpl @@ -0,0 +1,61 @@ +package svc + +import ( + {{.configImport}} + "github.com/zeromicro/go-zero/core/logx" + "git.noahlan.cn/noahlan/ntool/nlog" + "git.noahlan.cn/noahlan/ntool-biz/zero/logz" + {{if .useI18n}} + "git.noahlan.cn/noahlan/ntool-biz/core/i18n"{{end}} + {{if .useEnt}} + "{{.projectPackage}}/ent"{{end}} + {{if .useCasbin}} + "github.com/zeromicro/go-zero/core/stores/redis" + "github.com/zeromicro/go-zero/rest" + "github.com/casbin/casbin/v2"{{end}} +) + +type ServiceContext struct { + Config {{.config}} + {{.middleware}} + {{if .useCasbin}}Casbin *casbin.Enforcer + Authority rest.Middleware{{end}} + {{if .useEnt}} + DB *ent.Client{{end}} +} + +func NewServiceContext(c {{.config}}) *ServiceContext { + // 配置nlog-logx适配器 + nlog.MustSetup(logz.WrapConf(c.Log)) + logx.SetWriter(logz.NewWriter()) +{{if .useI18n}} + // 配置多语言 + i18n.InitWithConfig(c.I18nConf) +{{end}} + +{{if .useCasbin}} + rds := redis.MustNewRedis(redis.RedisConf{ + Host: c.RedisConf.Addr, + Type: c.RedisConf.Type, + Pass: c.RedisConf.Password, + Tls: c.RedisConf.Tls, + }) + cbn := c.CasbinConf.MustNewCasbinWithRedisWatcher(c.DatabaseConf.Type, c.DatabaseConf.GetDSN(), c.RedisConf) +{{end}} + +{{if .useEnt}} + db := ent.NewClient( + ent.Log(nlog.Info), // logger + ent.Driver(c.DatabaseConf.NewNoCacheDriver()), + ent.Debug(), // debug mode + ) +{{end}} + return &ServiceContext{ + Config: c, + {{if .useCasbin}} + Authority: middleware.NewAuthorityMiddleware(cbn, rds).Handle, + Casbin: cbn, + {{end}} + {{if .useEnt}}DB: db,{{end}} + } +} diff --git a/api/etc.tpl b/api/etc.tpl new file mode 100644 index 0000000..f597c0b --- /dev/null +++ b/api/etc.tpl @@ -0,0 +1,78 @@ +Name: {{.serviceName}}.api +Host: {{.host}} +Port: {{.port}} +Timeout: 30000 + +Auth: + AccessSecret: # the same as core + AccessExpire: 259200 + +CORSConf: + Address: '*' + +Log: + ServiceName: {{.serviceName}}ApiLogger + Mode: console + Path: ./logs/{{.serviceName}}/api + Encoding: plain # json or plain + Level: debug + Stat: false + Compress: false + KeepDays: 7 + StackCoolDownMillis: 100 + +Prometheus: + Host: 0.0.0.0 + Port: 4000 + Path: /metrics + +{{if .useCasbin}} +RedisConf: + Host: 127.0.0.1:6379 + Type: node + +DatabaseConf: + Type: mysql + Host: 127.0.0.1 + Port: 3306 + DBName: test + Username: # set your username + Password: # set your password + MaxOpenConn: 1000 + SSLMode: disable + CacheTime: 5 + +CasbinConf: + ModelText: | + [request_definition] + r = sub, obj, act + [policy_definition] + p = sub, obj, act + [role_definition] + g = _, _ + [policy_effect] + e = some(where (p.eft == allow)) + [matchers] + m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act + +{{else}} +{{if .useEnt}} +DatabaseConf: + Type: mysql + Host: 127.0.0.1 + Port: 3306 + DBName: simple_admin + Username: # set your username + Password: # set your password + MaxOpenConn: 100 + SSLMode: disable + CacheTime: 5 +{{end}} +{{end}} + +{{if .useI18n}} +I18nConf: + Default: zh + RootPath: dal/i18n/locale + SortedParameterPrefix: p +{{end}} \ No newline at end of file diff --git a/api/handler.tpl b/api/handler.tpl new file mode 100644 index 0000000..772768b --- /dev/null +++ b/api/handler.tpl @@ -0,0 +1,32 @@ +package {{.PkgName}} + +import ( + "net/http" + + "git.noahlan.cn/noahlan/ntool-biz/zero/statusz" + {{if .HasRequest}}"github.com/zeromicro/go-zero/rest/httpx"{{end}} + + {{.ImportPackages}} +) + +{{.HandlerDoc}} + +func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + {{if .TransErr}}const transErr = true{{else}}const transErr = false{{end}} + {{if .HasRequest}}var req types.{{.RequestType}} + if err := httpx.Parse(r, &req, true); err != nil { + statusz.ResponseHandler(r, w, transErr, nil, err) + return + } + + {{end}} + l := {{.LogicName}}.New{{.LogicType}}(r, r.Context(), svcCtx) + {{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}}) + {{if .HasResp}} + statusz.ResponseHandler(r, w, transErr, resp, err) + {{else}} + statusz.ResponseHandler(r, w, transErr, nil, err) + {{end}} + } +} diff --git a/api/logic.tpl b/api/logic.tpl new file mode 100644 index 0000000..a1df9b8 --- /dev/null +++ b/api/logic.tpl @@ -0,0 +1,26 @@ +package {{.pkgName}} + +import ( + {{.imports}} + "net/http" +) + +type {{.logic}} struct { + ctx context.Context + svcCtx *svc.ServiceContext + r *http.Request +} + +func New{{.logic}}(r *http.Request, ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} { + return &{{.logic}}{ + r: r, + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} { + // todo: add your logic here and delete this line + + {{.returnString}} +} diff --git a/api/main.tpl b/api/main.tpl new file mode 100644 index 0000000..2d20fc8 --- /dev/null +++ b/api/main.tpl @@ -0,0 +1,49 @@ +// {{.serviceName}} +// +// Description: {{.serviceName}} service +// +// Schemes: http, https +// Host: localhost:{{.port}} +// BasePath: / +// Version: 0.0.1 +// SecurityDefinitions: +// Token: +// type: apiKey +// name: Authorization +// in: header +// Security: +// - Token: [] +// Consumes: +// - application/json +// +// Produces: +// - application/json +// +// swagger:meta +package main + +import ( + "flag" + "fmt" + + {{.importPackages}} +) + +var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file") + + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c, conf.UseEnv()) + + server := rest.MustNewServer(c.RestConf, rest.WithCors(c.CORSConf.Address)) + defer server.Stop() + + ctx := svc.NewServiceContext(c) + handler.RegisterHandlers(server, ctx) + + fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) + server.Start() +} diff --git a/api/middleware.tpl b/api/middleware.tpl new file mode 100644 index 0000000..3a9f8e9 --- /dev/null +++ b/api/middleware.tpl @@ -0,0 +1,19 @@ +package middleware + +import "net/http" + +type {{.name}} struct { +} + +func New{{.name}}() *{{.name}} { + return &{{.name}}{} +} + +func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // TODO generate middleware implement function, delete after code implementation + + // Passthrough to next handler if need + next(w, r) + } +} diff --git a/api/route-addition.tpl b/api/route-addition.tpl new file mode 100644 index 0000000..bb8a5df --- /dev/null +++ b/api/route-addition.tpl @@ -0,0 +1,4 @@ + + server.AddRoutes( + {{.routes}} {{.jwt}}{{.signature}} {{.prefix}} {{.timeout}} {{.maxBytes}} + ) diff --git a/api/routes.tpl b/api/routes.tpl new file mode 100644 index 0000000..f13fb11 --- /dev/null +++ b/api/routes.tpl @@ -0,0 +1,13 @@ +// Code generated by goctl. DO NOT EDIT. +package handler + +import ( + "net/http"{{if .hasTimeout}} + "time"{{end}} + + {{.importPackages}} +) + +func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { + {{.routesAdditions}} +} diff --git a/api/template.tpl b/api/template.tpl new file mode 100644 index 0000000..2176441 --- /dev/null +++ b/api/template.tpl @@ -0,0 +1,24 @@ +syntax = "v1" + +info ( + title: // TODO: add title + desc: // TODO: add description + author: "{{.gitUser}}" + email: "{{.gitEmail}}" +) + +type request { + // TODO: add members here and delete this comment +} + +type response { + // TODO: add members here and delete this comment +} + +service {{.serviceName}} { + @handler GetUser // TODO: set handler name and delete this comment + get /users/id/:userId(request) returns(response) + + @handler CreateUser // TODO: set handler name and delete this comment + post /users/create(request) +} diff --git a/api/types.tpl b/api/types.tpl new file mode 100644 index 0000000..735ec2d --- /dev/null +++ b/api/types.tpl @@ -0,0 +1,6 @@ +// Code generated by goctl. DO NOT EDIT. +package types{{if .containsTime}} +import ( + "time" +){{end}} +{{.types}} diff --git a/docker/docker.tpl b/docker/docker.tpl new file mode 100644 index 0000000..d3d7603 --- /dev/null +++ b/docker/docker.tpl @@ -0,0 +1,43 @@ +FROM {{.Image}} as builder + +# Define the project name | 定义项目名称 +ARG PROJECT={{.ServiceName}} + +WORKDIR /build +COPY . . +{{if .Chinese}} +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories +{{end}}{{if .HasTimezone}} +RUN apk update --no-cache && apk add --no-cache tzdata +{{end}} +RUN go env -w GO111MODULE=on \ +{{if .Chinese}} && go env -w GOPROXY=https://goproxy.cn,direct \ +{{end}} && go env -w CGO_ENABLED=0 \ + && go env \ + && go mod tidy \ + && go build -ldflags="-s -w" -o /build/${PROJECT}_{{.ServiceType}} ${PROJECT}.go + +FROM {{.BaseImage}} + +# Define the project name | 定义项目名称 +ARG PROJECT={{.ServiceName}} +# Define the config file name | 定义配置文件名 +ARG CONFIG_FILE={{.ServiceName}}.yaml +# Define the author | 定义作者 +ARG AUTHOR="{{.Author}}" + +LABEL org.opencontainers.image.authors=${AUTHOR} + +WORKDIR /app +ENV PROJECT=${PROJECT} +ENV CONFIG_FILE=${CONFIG_FILE} +{{if .HasTimezone}} +COPY --from=builder /usr/share/zoneinfo/{{.Timezone}} /usr/share/zoneinfo/{{.Timezone}} +ENV TZ={{.Timezone}} +{{end}} +COPY --from=builder /build/${PROJECT}_{{.ServiceType}} ./ +COPY --from=builder /build/etc/${CONFIG_FILE} ./etc/ +{{if .HasPort}} +EXPOSE {{.Port}} +{{end}} +ENTRYPOINT ./${PROJECT}_{{.ServiceType}} -f etc/${CONFIG_FILE} \ No newline at end of file diff --git a/gateway/etc.tpl b/gateway/etc.tpl new file mode 100644 index 0000000..0a70f1a --- /dev/null +++ b/gateway/etc.tpl @@ -0,0 +1,18 @@ +Name: gateway-example # gateway name +Host: localhost # gateway host +Port: 8888 # gateway port +Upstreams: # upstreams + - Grpc: # grpc upstream + Target: 0.0.0.0:8080 # grpc target,the direct grpc server address,for only one node +# Endpoints: [0.0.0.0:8080,192.168.120.1:8080] # grpc endpoints, the grpc server address list, for multiple nodes +# Etcd: # etcd config, if you want to use etcd to discover the grpc server address +# Hosts: [127.0.0.1:2378,127.0.0.1:2379] # etcd hosts +# Key: greet.grpc # the discovery key + # protoset mode + ProtoSets: + - hello.pb + # Mappings can also be written in proto options +# Mappings: # routes mapping +# - Method: get +# Path: /ping +# RpcPath: hello.Hello/Ping diff --git a/gateway/main.tpl b/gateway/main.tpl new file mode 100644 index 0000000..6273451 --- /dev/null +++ b/gateway/main.tpl @@ -0,0 +1,20 @@ +package main + +import ( + "flag" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/gateway" +) + +var configFile = flag.String("f", "etc/gateway.yaml", "config file") + +func main() { + flag.Parse() + + var c gateway.GatewayConf + conf.MustLoad(*configFile, &c) + gw := gateway.MustNewServer(c) + defer gw.Stop() + gw.Start() +} diff --git a/kube/deployment.tpl b/kube/deployment.tpl new file mode 100644 index 0000000..9484359 --- /dev/null +++ b/kube/deployment.tpl @@ -0,0 +1,132 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{.Name}} + namespace: {{.Namespace}} + labels: + app: {{.Name}} +spec: + replicas: {{.Replicas}} + revisionHistoryLimit: {{.Revisions}} + selector: + matchLabels: + app: {{.Name}} + template: + metadata: + labels: + app: {{.Name}} + spec:{{if .ServiceAccount}} + serviceAccountName: {{.ServiceAccount}}{{end}} + containers: + - name: {{.Name}} + image: {{.Image}} + {{if .ImagePullPolicy}}imagePullPolicy: {{.ImagePullPolicy}} + {{end}}ports: + - containerPort: {{.Port}} + readinessProbe: + tcpSocket: + port: {{.Port}} + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: {{.Port}} + initialDelaySeconds: 15 + periodSeconds: 20 + resources: + requests: + cpu: {{.RequestCpu}}m + memory: {{.RequestMem}}Mi + limits: + cpu: {{.LimitCpu}}m + memory: {{.LimitMem}}Mi + volumeMounts: + - name: timezone + mountPath: /etc/localtime + {{if .Secret}}imagePullSecrets: + - name: {{.Secret}} + {{end}}volumes: + - name: timezone + hostPath: + path: /usr/share/zoneinfo/Asia/Shanghai + +--- + +apiVersion: v1 +kind: Service +metadata: + name: {{.Name}}-svc + namespace: {{.Namespace}} +spec: + ports: + {{if .UseNodePort}}- nodePort: {{.NodePort}} + port: {{.Port}} + protocol: TCP + targetPort: {{.TargetPort}} + type: NodePort{{else}}- port: {{.Port}} + targetPort: {{.TargetPort}}{{end}} + selector: + app: {{.Name}} + +--- + +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{.Name}} + labels: + serviceMonitor: prometheus +spec: + selector: + matchLabels: + app: {{.Name}}-svc + endpoints: + - port: prometheus + +--- + +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{.Name}}-hpa-c + namespace: {{.Namespace}} + labels: + app: {{.Name}}-hpa-c +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{.Name}} + minReplicas: {{.MinReplicas}} + maxReplicas: {{.MaxReplicas}} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 + +--- + +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{.Name}}-hpa-m + namespace: {{.Namespace}} + labels: + app: {{.Name}}-hpa-m +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{.Name}} + minReplicas: {{.MinReplicas}} + maxReplicas: {{.MaxReplicas}} + metrics: + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/kube/job.tpl b/kube/job.tpl new file mode 100644 index 0000000..0da72ed --- /dev/null +++ b/kube/job.tpl @@ -0,0 +1,37 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{.Name}} + namespace: {{.Namespace}} +spec: + successfulJobsHistoryLimit: {{.SuccessfulJobsHistoryLimit}} + schedule: "{{.Schedule}}" + jobTemplate: + spec: + template: + spec:{{if .ServiceAccount}} + serviceAccountName: {{.ServiceAccount}}{{end}} + {{end}}containers: + - name: {{.Name}} + image: # todo image url + resources: + requests: + cpu: {{.RequestCpu}}m + memory: {{.RequestMem}}Mi + limits: + cpu: {{.LimitCpu}}m + memory: {{.LimitMem}}Mi + command: + - ./{{.ServiceName}} + - -f + - ./{{.Name}}.yaml + volumeMounts: + - name: timezone + mountPath: /etc/localtime + imagePullSecrets: + - name: # registry secret, if no, remove this + restartPolicy: OnFailure + volumes: + - name: timezone + hostPath: + path: /usr/share/zoneinfo/Asia/Shanghai diff --git a/newapi/base.tpl b/newapi/base.tpl new file mode 100644 index 0000000..c40e2d4 --- /dev/null +++ b/newapi/base.tpl @@ -0,0 +1,81 @@ +syntax = "v1" + +info( + title: "Base Api" + desc: "API basic" + author: "NorthLan" + email: "lan6995@gmail.com" + version: "v1.0" +) + +// Pagination | 分页模型 +// swagger:model Pagination +// swagger:allOf +type Pagination { + // Page Size | 每页大小 + // + // Minimum: 0 + // Maximum: 9999 + // Required: false + // Example: 10 + Size uint64 `json:"size,optional" form:"size,optional" validate:"number,max=9999"` + // Current Page | 当前页 + // + // Minimum: 1 + // Maximum: 100000 + // Required: false + // Example: 1 + Current uint64 `json:"current,optional" form:"current,optional" validate:"number,max=100000"` + // Total Number | 数据总数 + // + // Required: false + Total uint64 `json:"total,optional" form:"total,optional" validate:"number"` +} + +// TimeInfo | 时间结构 +// swagger:model TimeInfo +type TimeInfo { + // Create Time | 创建时间 + CreatedAt string `json:"createdAt,optional"` + // Update Time | 更新时间 + UpdatedAt string `json:"updatedAt,optional"` +} + +// Basic ID Request | 基础ID结构 +type IDReq { + // Id | 序号 + Id uint64 `json:"id,string,optional" form:"id,string,optional"` +} + +// Basic ID request (path) | 基础ID结构,地址参数 +type IDPathReq { + // ID | 序号 + // + // In: path + Id uint64 `json:"id,optional,string" path:"id,optional,string"` +} + +// Basic ID List request | 基础ID列表结构,用于Post +// swagger:model IDsReq +type IDsReq { + // 主键列表 + Ids []uint64 `json:"ids,optional" form:"ids,optional"` +} + +// The base ID response data | 基础ID信息 +type BaseIDInfo { + // Id | 序号 + Id uint64 `json:"id,string,optional"` + + TimeInfo +} + +@server( + group: base + prefix: /api/base +) +service {{.name}} { + // Initialize database | 初始化数据库 + @handler initDatabase + get /initDatabase +} diff --git a/rpc/call.tpl b/rpc/call.tpl new file mode 100644 index 0000000..27b4879 --- /dev/null +++ b/rpc/call.tpl @@ -0,0 +1,33 @@ +{{.head}} + +package {{.filePackage}} + +import ( + "context" + + {{.pbPackage}} + {{if ne .pbPackage .protoGoPackage}}{{.protoGoPackage}}{{end}} + + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" +) + +type ( + {{.alias}} + + {{.serviceName}} interface { + {{.interface}} + } + + default{{.serviceName}} struct { + cli zrpc.Client + } +) + +func New{{.serviceName}}(cli zrpc.Client) {{.serviceName}} { + return &default{{.serviceName}}{ + cli: cli, + } +} + +{{.functions}} diff --git a/rpc/config.tpl b/rpc/config.tpl new file mode 100644 index 0000000..1444787 --- /dev/null +++ b/rpc/config.tpl @@ -0,0 +1,19 @@ +package config + +import ( +{{if .isEnt}} + dbconfig "git.noahlan.cn/noahlan/ntool-biz/core/orm/nent/config" + nconfig "git.noahlan.cn/noahlan/ntool-biz/core/config" +{{end}} + "github.com/zeromicro/go-zero/zrpc" + +) + +type Config struct { + zrpc.RpcServerConf +{{if .isEnt}} + DatabaseConf dbconfig.Database + RedisConf nconfig.RedisConf +{{end}} +} + diff --git a/rpc/dberrorhandler.tpl b/rpc/dberrorhandler.tpl new file mode 100644 index 0000000..a545bd3 --- /dev/null +++ b/rpc/dberrorhandler.tpl @@ -0,0 +1,40 @@ +package dberrorhandler + +import ( + "git.noahlan.cn/noahlan/ntool/nlog" + "git.noahlan.cn/noahlan/ntool-biz/core/nstatus" + "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/msg" + // api + "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code" + + "{{.package}}/ent" +) + +// HandleEntErr returns errors dealing with default functions. +func HandleEntErr(logger logx.Logger, err error, detail any) error { + logger := nlog.WithCallerSkip(1) + if err != nil { + switch { + case ent.IsNotFound(err): + // 未找到数据实体 + logger.Errorw(err.Error(), nlog.Field("detail", detail)) + return nstatus.NewBizErr(code.StatusNotFound, msg.ObjectNotFound) + case ent.IsConstraintError(err): + // 创建/更新时,发生约束错误 + logger.Errorw(err.Error(), nlog.Field("detail", detail)) + return nstatus.NewBizErr(code.StatusConflict, msg.ConstraintError) + case ent.IsValidationError(err): + // 数据类型错误,验证错误 + logger.Errorw(err.Error(), nlog.Field("detail", detail)) + return nstatus.NewBizErr(code.StatusBadRequest, msg.ValidationError) + case ent.IsNotSingular(err): + // 查询时发生,非单一实体错误,通常是因为记录中存在多个实体,查询单个实体时未添加Limit + logger.Errorw(err.Error(), nlog.Field("detail", detail)) + return nstatus.NewBizErr(code.StatusBadRequest, msg.NotSingularError) + default: + logger.Errorw("database error", nlog.Field("err", err.Error()), nlog.Field("detail", detail)) + return nstatus.NewGrpcInternalErr(msg.DatabaseError) + } + } + return err +} diff --git a/rpc/enttx.tpl b/rpc/enttx.tpl new file mode 100644 index 0000000..afd896b --- /dev/null +++ b/rpc/enttx.tpl @@ -0,0 +1,38 @@ +package entx + +import ( + "context" + "fmt" + + "git.noahlan.cn/noahlan/ntool/nlog" + + "{{.package}}/ent" +) + + +// WithTx uses transaction in ent. +func WithTx(ctx context.Context, client *ent.Client, fn func(tx *ent.Tx) error) error { + tx, err := client.Tx(ctx) + if err != nil { + nlog.Errorw("failed to start transaction", nlog.Field("err", err.Error())) + return err + } + defer func() { + if v := recover(); v != nil { + tx.Rollback() + panic(v) + } + }() + if err := fn(tx); err != nil { + if rollBackErr := tx.Rollback(); rollBackErr != nil { + err = fmt.Errorf("%w: rolling back transaction: %v", err, rollBackErr) + } + nlog.Errorw("errors occur in transaction", nlog.Field("err", err.Error())) + return err + } + if err := tx.Commit(); err != nil { + nlog.Errorw("failed to commit transaction", nlog.Field("err", err.Error())) + return err + } + return nil +} diff --git a/rpc/etc.tpl b/rpc/etc.tpl new file mode 100644 index 0000000..e10330f --- /dev/null +++ b/rpc/etc.tpl @@ -0,0 +1,37 @@ +Name: {{.serviceName}}.rpc +ListenOn: 0.0.0.0:{{.port}} +Timeout: 0 +Mode: dev + +{{if .isEnt}} +DatabaseConf: + Type: mysql + Host: 127.0.0.1 + Port: 3306 + DBName: test + Username: # set your username + Password: # set your password + MaxOpenConn: 1000 + SSLMode: disable + CacheTime: 5 + +RedisConf: + Host: 127.0.0.1:6379 + Type: node +{{end}} + +Log: + ServiceName: {{.serviceName}}RpcLogger + Mode: console + Path: ./logs/{{.serviceName}}/rpc + Encoding: plain # json or plain + Level: debug + Stat: false + Compress: false + KeepDays: 7 + StackCoolDownMillis: 100 + +Prometheus: + Host: 0.0.0.0 + Port: 4001 + Path: /metrics diff --git a/rpc/logic-func.tpl b/rpc/logic-func.tpl new file mode 100644 index 0000000..e9410d4 --- /dev/null +++ b/rpc/logic-func.tpl @@ -0,0 +1,6 @@ +{{if .hasComment}}{{.comment}}{{end}} +func (l *{{.logicName}}) {{.method}} ({{if .hasReq}}in {{.request}}{{if .stream}},stream {{.streamBody}}{{end}}{{else}}stream {{.streamBody}}{{end}}) ({{if .hasReply}}{{.response}},{{end}} error) { + // todo: add your logic here and delete this line + + return {{if .hasReply}}&{{.responseType}}{},{{end}} nil +} diff --git a/rpc/logic.tpl b/rpc/logic.tpl new file mode 100644 index 0000000..7d0193c --- /dev/null +++ b/rpc/logic.tpl @@ -0,0 +1,20 @@ +package {{.packageName}} + +import ( + "context" + + {{.imports}} +) + +type {{.logicName}} struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logicName}} { + return &{{.logicName}}{ + ctx: ctx, + svcCtx: svcCtx, + } +} +{{.functions}} diff --git a/rpc/main.tpl b/rpc/main.tpl new file mode 100644 index 0000000..42e350a --- /dev/null +++ b/rpc/main.tpl @@ -0,0 +1,36 @@ +package main + +import ( + "flag" + "fmt" + + {{.imports}} + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/core/service" + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c, conf.UseEnv()) + ctx := svc.NewServiceContext(c) + + s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { +{{range .serviceNames}} {{.Pkg}}.Register{{.Service}}Server(grpcServer, {{.ServerPkg}}.New{{.Service}}Server(ctx)) +{{end}} + if c.Mode == service.DevMode || c.Mode == service.TestMode { + reflection.Register(grpcServer) + } + }) + defer s.Stop() + + fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) + s.Start() +} diff --git a/rpc/server-func.tpl b/rpc/server-func.tpl new file mode 100644 index 0000000..d771b43 --- /dev/null +++ b/rpc/server-func.tpl @@ -0,0 +1,6 @@ + +{{if .hasComment}}{{.comment}}{{end}} +func (s *{{.server}}Server) {{.method}} ({{if .notStream}}ctx context.Context,{{if .hasReq}} in {{.request}}{{end}}{{else}}{{if .hasReq}} in {{.request}},{{end}}stream {{.streamBody}}{{end}}) ({{if .notStream}}{{.response}},{{end}}error) { + l := {{.logicPkg}}.New{{.logicName}}({{if .notStream}}ctx,{{else}}stream.Context(),{{end}}s.svcCtx) + return l.{{.method}}({{if .hasReq}}in{{if .stream}} ,stream{{end}}{{else}}{{if .stream}}stream{{end}}{{end}}) +} diff --git a/rpc/server.tpl b/rpc/server.tpl new file mode 100644 index 0000000..84a2f9c --- /dev/null +++ b/rpc/server.tpl @@ -0,0 +1,22 @@ +{{.head}} + +package server + +import ( + {{if .notStream}}"context"{{end}} + + {{.imports}} +) + +type {{.server}}Server struct { + svcCtx *svc.ServiceContext + {{.unimplementedServer}} +} + +func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server { + return &{{.server}}Server{ + svcCtx: svcCtx, + } +} + +{{.funcs}} diff --git a/rpc/svc.tpl b/rpc/svc.tpl new file mode 100644 index 0000000..563777f --- /dev/null +++ b/rpc/svc.tpl @@ -0,0 +1,48 @@ +package svc + +import ( +{{.imports}} + "github.com/zeromicro/go-zero/core/logx" + "git.noahlan.cn/noahlan/ntool/nlog" + "git.noahlan.cn/noahlan/ntool-biz/zero/logz" + "git.noahlan.cn/noahlan/ntool-biz/core/i18n" +) + +type ServiceContext struct { + Config config.Config + {{if .isEnt}} + DB *ent.Client + Redis *redis.Redis + {{end}} +} + +func NewServiceContext(c config.Config) *ServiceContext { + nlog.MustSetup(logz.WrapConf(c.Log)) + logx.SetWriter(logz.NewWriter()) + + // 配置多语言 + i18n.InitWithConfig(c.I18nConf) + +{{if .isEnt}} + db := ent.NewClient( + ent.Log(nlog.Info), // logger + ent.Driver(c.DatabaseConf.NewNoCacheDriver()), + ent.Debug(), // debug mode + ) + {{end}} + + rds := redis.MustNewRedis(redis.RedisConf{ + Host: c.RedisConf.Addr, + Type: c.RedisConf.Type, + Pass: c.RedisConf.Password, + Tls: c.RedisConf.Tls, + }) + + return &ServiceContext{ + Config: c, + {{if .isEnt}} + DB: db, + Redis: rds, + {{end}} + } +} diff --git a/rpc/template.tpl b/rpc/template.tpl new file mode 100644 index 0000000..eb22132 --- /dev/null +++ b/rpc/template.tpl @@ -0,0 +1,58 @@ +syntax = "proto3"; + +package {{.package}}; +option go_package="./{{.package}}"; + +// Base message +message Empty {} + +message IDReq { + uint64 id = 1; +} + +message IDsReq { + repeated uint64 ids = 1; +} + +// 基础回执 +message BaseResp { + uint32 code = 1; + string msg = 2; +} + +// 基础回执带ID +message BaseIDResp { + uint64 id = 1; + uint32 code = 2; + string msg = 3; +} + +// 分页参数 +message Pagination { + uint64 size = 1; // 每页条目个数 + uint64 total = 2; // 数据条目总数 + uint64 current = 3; // 当前页码 +} + +// 操作类型 +enum Operator { + EQ = 0; // == + NEQ = 1; // != + IN = 2; // in () + NotIn = 3; // not in () + GT = 4; // > + GTE = 5; // >= + LT = 6; // < + LTE = 7; // <= + Contains = 8; // %{}% + Prefix = 9; // {}% + Suffix = 10; // %{} + EqualFold = 11; // == with toLower + ContainsFold = 12; // %{}% with toLower +} + + +service {{.serviceName}} { + // group: base + rpc initDatabase (Empty) returns (BaseResp); +}