├── internal ├── biz │ ├── README.md │ ├── reason.go │ ├── biz.go │ └── game.go ├── data │ ├── README.md │ ├── model │ │ └── game.gen.go │ ├── client.go │ ├── query │ │ └── gen.go │ ├── tracer.go │ ├── game.go │ ├── data.go │ └── cache.go ├── service │ ├── README.md │ ├── health.go │ ├── service.go │ └── game.go ├── db │ ├── db.go │ └── migrations │ │ └── 2022081510-game.sql ├── server │ ├── middleware │ │ ├── locales │ │ │ ├── zh.yml │ │ │ └── en.yml │ │ ├── header.go │ │ └── idempotent.go │ ├── server.go │ ├── health.go │ ├── grpc.go │ └── http.go ├── tests │ ├── service │ │ └── game_test.go │ └── mock │ │ └── mock.go ├── pkg │ └── task │ │ └── task.go └── conf │ └── conf.proto ├── third_party ├── README.md ├── validate │ └── README.md ├── cinch │ └── params │ │ └── params.proto ├── errors │ └── errors.proto ├── google │ ├── api │ │ ├── annotations.proto │ │ ├── httpbody.proto │ │ ├── field_behavior.proto │ │ ├── client.proto │ │ └── http.proto │ └── protobuf │ │ ├── source_context.proto │ │ ├── empty.proto │ │ ├── struct.proto │ │ ├── wrappers.proto │ │ ├── duration.proto │ │ ├── any.proto │ │ ├── type.proto │ │ ├── timestamp.proto │ │ ├── api.proto │ │ ├── field_mask.proto │ │ └── compiler │ │ └── plugin.proto └── openapiv3 │ └── annotations.proto ├── configs ├── log.yml ├── client.yml ├── task.yml ├── gen.yml ├── tracer.yml └── config.yml ├── .gitmodules ├── Dockerfile ├── api ├── reason │ ├── reason.pb.validate.go │ ├── reason_errors.pb.go │ └── reason.pb.go └── game │ ├── game_http.pb.go │ └── game_grpc.pb.go ├── .gitignore ├── cmd └── server │ ├── wire.go │ ├── wire_gen.go │ └── main.go ├── LICENSE ├── .golangci.yml ├── Makefile ├── go.mod └── README.md /internal/biz/README.md: -------------------------------------------------------------------------------- 1 | # Biz 2 | -------------------------------------------------------------------------------- /internal/data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | -------------------------------------------------------------------------------- /internal/service/README.md: -------------------------------------------------------------------------------- 1 | # Service 2 | -------------------------------------------------------------------------------- /third_party/README.md: -------------------------------------------------------------------------------- 1 | # third_party 2 | -------------------------------------------------------------------------------- /configs/log.yml: -------------------------------------------------------------------------------- 1 | log: 2 | level: 'info' 3 | # text or json 4 | JSON: true 5 | # show sql or not 6 | showSQL: true -------------------------------------------------------------------------------- /third_party/validate/README.md: -------------------------------------------------------------------------------- 1 | # protoc-gen-validate (PGV) 2 | 3 | * https://github.com/envoyproxy/protoc-gen-validate 4 | -------------------------------------------------------------------------------- /configs/client.yml: -------------------------------------------------------------------------------- 1 | client: 2 | health: false 3 | timeout: 20s 4 | # service: grpc uri 5 | # xxx: ip:port 6 | auth: 'auth:6160' -------------------------------------------------------------------------------- /internal/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "embed" 4 | 5 | const SQLRoot = "migrations" 6 | 7 | //go:embed migrations/*.sql 8 | var SQLFiles embed.FS 9 | -------------------------------------------------------------------------------- /configs/task.yml: -------------------------------------------------------------------------------- 1 | task: 2 | task1: 3 | name: 'every10s' 4 | expr: '0/10 * * * * * *' 5 | task2: 6 | name: 'every3min' 7 | expr: '0 0/3 * * * * *' 8 | timeout: 60 9 | retry: 10 10 | -------------------------------------------------------------------------------- /internal/server/middleware/locales/zh.yml: -------------------------------------------------------------------------------- 1 | idempotent.token.invalid: '幂等性签名无效' 2 | 3 | too.many.requests: '请求频繁, 请稍候重试' 4 | data.not.change: '数据没有发生变化' 5 | duplicate.field: '字段重复' 6 | record.not.found: '记录不存在' 7 | illegal.parameter: '参数错误' -------------------------------------------------------------------------------- /internal/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/google/wire" 7 | ) 8 | 9 | // ProviderSet is server providers. 10 | var ProviderSet = wire.NewSet(NewGRPCServer, NewHTTPServer) 11 | 12 | //go:embed middleware/locales 13 | var locales embed.FS 14 | -------------------------------------------------------------------------------- /internal/server/health.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/go-cinch/layout/internal/service" 7 | ) 8 | 9 | func HealthHandler(svc *service.GameService) *http.ServeMux { 10 | mux := http.NewServeMux() 11 | mux.HandleFunc("/pub/healthcheck", svc.HealthCheck) 12 | return mux 13 | } 14 | -------------------------------------------------------------------------------- /internal/server/middleware/locales/en.yml: -------------------------------------------------------------------------------- 1 | idempotent.token.invalid: 'idempotent token is invalid' 2 | 3 | too.many.requests: 'too many requests, please try again later' 4 | data.not.change: 'data has not changed' 5 | duplicate.field: 'duplicate field' 6 | record.not.found: 'record not found' 7 | illegal.parameter: 'illegal parameter' -------------------------------------------------------------------------------- /configs/gen.yml: -------------------------------------------------------------------------------- 1 | # go install github.com/go-cinch/cinch/cmd/cinch@latest 2 | # cinch gen gorm 3 | gen: 4 | dsn: 'root:passwd@tcp(127.0.0.1:3306)/game?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 5 | tables: 6 | - game 7 | exclude: 8 | - schema_migrations 9 | association: 10 | field-with-string-tag: 11 | -------------------------------------------------------------------------------- /internal/service/health.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/go-cinch/common/log" 7 | ) 8 | 9 | func (*GameService) HealthCheck(writer http.ResponseWriter, _ *http.Request) { 10 | log.Debug("healthcheck") 11 | writer.Header().Set("Content-Type", "application/json") 12 | writer.WriteHeader(http.StatusOK) 13 | _, _ = writer.Write([]byte("{}")) 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "api/game-proto"] 2 | path = api/game-proto 3 | url = https://github.com/go-cinch/game-proto.git 4 | branch = main 5 | [submodule "api/reason-proto"] 6 | path = api/reason-proto 7 | url = https://github.com/go-cinch/reason-proto.git 8 | branch = main 9 | [submodule "api/auth-proto"] 10 | path = api/auth-proto 11 | url = https://github.com/go-cinch/auth-proto.git 12 | branch = main 13 | -------------------------------------------------------------------------------- /configs/tracer.yml: -------------------------------------------------------------------------------- 1 | tracer: 2 | # enable tracer or not, if gcp=false, otlp endpoint='', will use stdout 3 | enable: true 4 | # samples ratio 5 | ratio: 1 6 | otlp: 7 | # how to run a otel-collector? see this: https://github.com/SigNoz/signoz/blob/develop/deploy/README.md#using-docker-compose 8 | # endpoint: '127.0.0.1:4317' 9 | endpoint: '' 10 | insecure: true 11 | stdout: 12 | prettyPrint: false 13 | -------------------------------------------------------------------------------- /internal/db/migrations/2022081510-game.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | CREATE TABLE `game` 3 | ( 4 | `id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT 'auto increment id' PRIMARY KEY, 5 | `created_at` DATETIME(3) NULL COMMENT 'create time', 6 | `updated_at` DATETIME(3) NULL COMMENT 'update time', 7 | `name` VARCHAR(50) COMMENT 'name' 8 | ) ENGINE = InnoDB 9 | DEFAULT CHARSET = utf8mb4 10 | COLLATE = utf8mb4_general_ci; 11 | 12 | -- +migrate Down 13 | DROP TABLE `game`; -------------------------------------------------------------------------------- /third_party/cinch/params/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package params; 4 | 5 | option go_package = "github.com/go-cinch/common/proto/params;params"; 6 | option java_multiple_files = true; 7 | option java_package = "com.github.go-cinch.params"; 8 | option objc_class_prefix = "CinchParams"; 9 | 10 | message Page { 11 | uint64 num = 1; 12 | uint64 size = 2; 13 | int64 total = 3; 14 | bool disable = 4; 15 | } 16 | 17 | message IdsRequest { 18 | string ids = 1; 19 | } 20 | -------------------------------------------------------------------------------- /third_party/errors/errors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package errors; 4 | 5 | option go_package = "github.com/go-kratos/kratos/v2/errors;errors"; 6 | option java_multiple_files = true; 7 | option java_package = "com.github.kratos.errors"; 8 | option objc_class_prefix = "KratosErrors"; 9 | 10 | import "google/protobuf/descriptor.proto"; 11 | 12 | extend google.protobuf.EnumOptions { 13 | int32 default_code = 1108; 14 | } 15 | 16 | extend google.protobuf.EnumValueOptions { 17 | int32 code = 1109; 18 | } -------------------------------------------------------------------------------- /internal/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/go-cinch/common/worker" 5 | "github.com/go-cinch/layout/api/game" 6 | "github.com/go-cinch/layout/internal/biz" 7 | "github.com/google/wire" 8 | ) 9 | 10 | // ProviderSet is service providers. 11 | var ProviderSet = wire.NewSet(NewGameService) 12 | 13 | // GameService is a game service. 14 | type GameService struct { 15 | game.UnimplementedGameServer 16 | 17 | task *worker.Worker 18 | game *biz.GameUseCase 19 | } 20 | 21 | // NewGameService new a service. 22 | func NewGameService(task *worker.Worker, game *biz.GameUseCase) *GameService { 23 | return &GameService{task: task, game: game} 24 | } 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine3.18 AS builder 2 | 3 | #ENV GOPROXY=https://goproxy.cn 4 | # CGO_ENABLED=1, need check `ldd --version` is same as builder 5 | ENV CGO_ENABLED=0 6 | 7 | RUN apk update && apk add --no-cache git make 8 | 9 | COPY . /src 10 | WORKDIR /src 11 | # download first can use docker build cache if go.mod not change 12 | COPY go.mod go.sum ./ 13 | RUN go mod download 14 | RUN go mod verify 15 | 16 | COPY . . 17 | RUN make build 18 | 19 | FROM alpine:3.18 20 | 21 | RUN apk update && apk add --no-cache bash 22 | 23 | COPY --from=builder /src/bin /app 24 | 25 | WORKDIR /app 26 | 27 | COPY configs /data/conf 28 | 29 | CMD ["sh", "-c", "./server -c /data/conf"] 30 | -------------------------------------------------------------------------------- /api/reason/reason.pb.validate.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-validate. DO NOT EDIT. 2 | // source: reason-proto/reason.proto 3 | 4 | package reason 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "net/mail" 12 | "net/url" 13 | "regexp" 14 | "sort" 15 | "strings" 16 | "time" 17 | "unicode/utf8" 18 | 19 | "google.golang.org/protobuf/types/known/anypb" 20 | ) 21 | 22 | // ensure the imports are used 23 | var ( 24 | _ = bytes.MinRead 25 | _ = errors.New("") 26 | _ = fmt.Print 27 | _ = utf8.UTFMax 28 | _ = (*regexp.Regexp)(nil) 29 | _ = (*strings.Reader)(nil) 30 | _ = net.IPv4len 31 | _ = time.Duration(0) 32 | _ = (*url.URL)(nil) 33 | _ = (*mail.Address)(nil) 34 | _ = anypb.Any{} 35 | _ = sort.Sort 36 | ) 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Reference https://github.com/github/gitignore/blob/master/Go.gitignore 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | vendor/ 17 | 18 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 19 | *.o 20 | *.a 21 | *.so 22 | 23 | # OS General 24 | Thumbs.db 25 | .DS_Store 26 | 27 | # project 28 | *.cert 29 | *.key 30 | *.log 31 | bin/ 32 | 33 | # Develop tools 34 | .vscode/ 35 | .idea/ 36 | *.swp 37 | 38 | docs/*/* 39 | !docs/game-proto/game.openapi.yaml 40 | -------------------------------------------------------------------------------- /cmd/server/wire.go: -------------------------------------------------------------------------------- 1 | //go:build wireinject 2 | // +build wireinject 3 | 4 | // The build tag makes sure the stub is not built in the final build. 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/go-cinch/layout/internal/biz" 10 | "github.com/go-cinch/layout/internal/conf" 11 | "github.com/go-cinch/layout/internal/data" 12 | "github.com/go-cinch/layout/internal/pkg/task" 13 | "github.com/go-cinch/layout/internal/server" 14 | "github.com/go-cinch/layout/internal/service" 15 | "github.com/go-kratos/kratos/v2" 16 | "github.com/google/wire" 17 | ) 18 | 19 | // wireApp init kratos application. 20 | func wireApp(c *conf.Bootstrap) (*kratos.App, func(), error) { 21 | panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, task.ProviderSet, service.ProviderSet, newApp)) 22 | } 23 | -------------------------------------------------------------------------------- /internal/server/middleware/header.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kratos/kratos/v2/middleware" 7 | "github.com/go-kratos/kratos/v2/transport" 8 | ) 9 | 10 | func Header() middleware.Middleware { 11 | return func(handler middleware.Handler) middleware.Handler { 12 | return func(ctx context.Context, req interface{}) (rp interface{}, err error) { 13 | if tr, ok := transport.FromServerContext(ctx); ok { 14 | switch tr.Kind() { 15 | case transport.KindHTTP: 16 | if tr.ReplyHeader() != nil { 17 | tr.ReplyHeader().Set("x-content-type-options", "nosniff") 18 | tr.ReplyHeader().Set("x-xss-protection", "1; mode=block") 19 | tr.ReplyHeader().Set("x-frame-options", "deny") 20 | tr.ReplyHeader().Set("Cache-Control", "private") 21 | } 22 | } 23 | } 24 | return handler(ctx, req) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/data/model/game.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package model 6 | 7 | import ( 8 | "github.com/golang-module/carbon/v2" 9 | ) 10 | 11 | const TableNameGame = "game" 12 | 13 | // Game mapped from table 14 | type Game struct { 15 | ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:auto increment id" json:"id,string"` // auto increment id 16 | CreatedAt carbon.DateTime `gorm:"column:created_at;comment:create time" json:"createdAt"` // create time 17 | UpdatedAt carbon.DateTime `gorm:"column:updated_at;comment:update time" json:"updatedAt"` // update time 18 | Name string `gorm:"column:name;comment:name" json:"name"` // name 19 | } 20 | 21 | // TableName Game's table name 22 | func (*Game) TableName() string { 23 | return TableNameGame 24 | } 25 | -------------------------------------------------------------------------------- /internal/server/middleware/idempotent.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-cinch/common/idempotent" 7 | "github.com/go-cinch/layout/internal/biz" 8 | "github.com/go-kratos/kratos/v2/middleware" 9 | "github.com/go-kratos/kratos/v2/transport" 10 | "github.com/redis/go-redis/v9" 11 | ) 12 | 13 | func Idempotent(rds redis.UniversalClient) middleware.Middleware { 14 | idt := idempotent.New( 15 | idempotent.WithPrefix("idempotent"), 16 | idempotent.WithRedis(rds), 17 | ) 18 | return func(handler middleware.Handler) middleware.Handler { 19 | return func(ctx context.Context, req interface{}) (rp interface{}, err error) { 20 | tr, _ := transport.FromServerContext(ctx) 21 | // check idempotent token if it has header 22 | token := tr.RequestHeader().Get("x-idempotent") 23 | if token == "" { 24 | return handler(ctx, req) 25 | } 26 | pass := idt.Check(ctx, token) 27 | if !pass { 28 | err = biz.ErrIdempotentTokenExpired(ctx) 29 | return 30 | } 31 | return handler(ctx, req) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Go Cinch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third_party/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | go: '1.22' 3 | timeout: '5m' 4 | skip-files: [ ] 5 | 6 | linters: 7 | enable: 8 | - gci 9 | - revive 10 | disable-all: true 11 | 12 | linters-settings: 13 | revive: 14 | rules: 15 | - name: var-naming 16 | arguments: [ [ "ID", "IDS" ] ] 17 | - name: line-length-limit 18 | arguments: [ 200 ] 19 | - name: argument-limit 20 | arguments: [ 10 ] 21 | - name: function-result-limit 22 | arguments: [ 10 ] 23 | - name: blank-imports 24 | - name: duplicated-imports 25 | - name: bool-literal-in-expr 26 | - name: constant-logical-expr 27 | - name: context-as-argument 28 | - name: error-return 29 | - name: deep-exit 30 | - name: defer 31 | - name: early-return 32 | - name: indent-error-flow 33 | - name: if-return 34 | - name: superfluous-else 35 | - name: empty-block 36 | - name: get-return 37 | - name: increment-decrement 38 | - name: modifies-value-receiver 39 | - name: range 40 | - name: range-val-in-closure 41 | - name: receiver-naming 42 | - name: string-of-int 43 | - name: struct-tag 44 | - name: unexported-naming 45 | - name: unexported-return 46 | - name: unreachable-code 47 | - name: unused-parameter 48 | - name: unused-receiver 49 | - name: waitgroup-by-value 50 | -------------------------------------------------------------------------------- /internal/biz/reason.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-cinch/common/constant" 7 | "github.com/go-cinch/common/middleware/i18n" 8 | "github.com/go-cinch/layout/api/reason" 9 | ) 10 | 11 | var ( 12 | ErrIdempotentTokenExpired = func(ctx context.Context) error { 13 | return i18n.NewError(ctx, constant.IdempotentTokenExpired, reason.ErrorIllegalParameter) 14 | } 15 | 16 | ErrTooManyRequests = func(ctx context.Context) error { 17 | return reason.ErrorTooManyRequests(i18n.FromContext(ctx).T(constant.TooManyRequests)) 18 | } 19 | ErrDataNotChange = func(ctx context.Context, args ...string) error { 20 | return i18n.NewError(ctx, constant.DataNotChange, reason.ErrorIllegalParameter, args...) 21 | } 22 | ErrDuplicateField = func(ctx context.Context, args ...string) error { 23 | return i18n.NewError(ctx, constant.DuplicateField, reason.ErrorIllegalParameter, args...) 24 | } 25 | ErrRecordNotFound = func(ctx context.Context, args ...string) error { 26 | return i18n.NewError(ctx, constant.RecordNotFound, reason.ErrorNotFound, args...) 27 | } 28 | ErrInternal = func(ctx context.Context, args ...string) error { 29 | return i18n.NewError(ctx, constant.InternalError, reason.ErrorInternal, args...) 30 | } 31 | ErrIllegalParameter = func(ctx context.Context, args ...string) error { 32 | return i18n.NewError(ctx, constant.IllegalParameter, reason.ErrorIllegalParameter, args...) 33 | } 34 | ) 35 | -------------------------------------------------------------------------------- /internal/biz/biz.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/google/wire" 7 | "github.com/redis/go-redis/v9" 8 | ) 9 | 10 | // ProviderSet is biz providers. 11 | var ProviderSet = wire.NewSet( 12 | NewGameUseCase, 13 | ) 14 | 15 | type Transaction interface { 16 | Tx(ctx context.Context, handler func(context.Context) error) error 17 | } 18 | 19 | type Cache interface { 20 | // Cache is get redis instance 21 | Cache() redis.UniversalClient 22 | // WithPrefix will add cache key prefix 23 | WithPrefix(prefix string) Cache 24 | // WithRefresh get data from db skip cache and refresh cache 25 | WithRefresh() Cache 26 | // Get is get cache data by key from redis, do write handler if cache is empty 27 | Get(ctx context.Context, action string, write func(context.Context) (string, error)) (string, error) 28 | // Set is set data to redis 29 | Set(ctx context.Context, action, data string, short bool) 30 | // Del delete key 31 | Del(ctx context.Context, action string) 32 | // SetWithExpiration is set data to redis with custom expiration 33 | SetWithExpiration(ctx context.Context, action, data string, seconds int64) 34 | // Flush is clean association cache if handler err=nil 35 | Flush(ctx context.Context, handler func(context.Context) error) error 36 | // FlushByPrefix clean cache by prefix, without prefix equals flush all by default cache prefix 37 | FlushByPrefix(ctx context.Context, prefix ...string) (err error) 38 | } 39 | -------------------------------------------------------------------------------- /configs/config.yml: -------------------------------------------------------------------------------- 1 | server: 2 | prod: false 3 | machineId: '' 4 | http: 5 | addr: 0.0.0.0:8080 6 | timeout: 20s 7 | grpc: 8 | addr: 0.0.0.0:8180 9 | timeout: 20s 10 | language: 'en' 11 | idempotent: true 12 | validate: true 13 | # disable biz.Cache or not 14 | nocache: false 15 | data: 16 | database: 17 | driver: mysql 18 | # dsn: 'root:root@tcp(127.0.0.1:3306)/layout?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 19 | dsn: '' 20 | endpoint: '127.0.0.1:3306' 21 | username: 'root' 22 | password: 'passwd' 23 | schema: 'layout' 24 | query: 'charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 25 | # tenants: 26 | # layout.go-cinch.top: 'root:root@tcp(127.0.0.1:3306)/layout?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms}' 27 | # layout2.go-cinch.top: 'root:root@tcp(127.0.0.1:3306)/layout2?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 28 | # layout3.go-cinch.top: 'root:root@tcp(127.0.0.1:3306)/layout3?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 29 | # layout4.go-cinch.top: 'root:root@tcp(127.0.0.1:3306)/layout4?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 30 | redis: 31 | # redis dsn like this: 32 | # redis://[:password@]host[:port][/dbnumber] 33 | # redis://[:password@]host1[:port][,host2:[:port]][,hostN:[:port]][?master=masterName&sentinel=true] 34 | dsn: 'redis://127.0.0.1:6379/0' 35 | -------------------------------------------------------------------------------- /internal/tests/service/game_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/go-cinch/common/proto/params" 10 | "github.com/go-cinch/layout/api/game" 11 | "github.com/go-cinch/layout/internal/tests/mock" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func TestGameService_CreateGame(t *testing.T) { 16 | s := mock.GameService() 17 | ctx := context.Background() 18 | userID := uuid.NewString() 19 | ctx = mock.NewContextWithUserId(ctx, userID) 20 | 21 | _, err := s.CreateGame(ctx, &game.CreateGameRequest{ 22 | Name: "game1", 23 | }) 24 | if err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | _, err = s.CreateGame(ctx, &game.CreateGameRequest{ 29 | Name: "game2", 30 | }) 31 | if err != nil { 32 | t.Error(err) 33 | return 34 | } 35 | res1, _ := s.FindGame(ctx, &game.FindGameRequest{ 36 | Page: ¶ms.Page{ 37 | Disable: true, 38 | }, 39 | }) 40 | if res1 == nil || len(res1.List) != 2 { 41 | t.Error("res1 len must be 2") 42 | return 43 | } 44 | res2, err := s.GetGame(ctx, &game.GetGameRequest{ 45 | Id: res1.List[0].Id, 46 | }) 47 | if err != nil { 48 | t.Error(err) 49 | return 50 | } 51 | if res2.Name != res1.List[0].Name { 52 | t.Errorf("res2.Name must be %s", res1.List[0].Name) 53 | return 54 | } 55 | _, err = s.DeleteGame(ctx, ¶ms.IdsRequest{ 56 | Ids: strings.Join([]string{ 57 | strconv.FormatUint(res1.List[0].Id, 10), 58 | strconv.FormatUint(res1.List[1].Id, 10), 59 | }, ","), 60 | }) 61 | if err != nil { 62 | t.Error(err) 63 | return 64 | } 65 | res3, _ := s.FindGame(ctx, &game.FindGameRequest{ 66 | Page: ¶ms.Page{ 67 | Disable: true, 68 | }, 69 | }) 70 | if res3 == nil || len(res3.List) != 0 { 71 | t.Error("res3 len must be 0") 72 | return 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /internal/pkg/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-cinch/common/log" 7 | "github.com/go-cinch/common/worker" 8 | "github.com/go-cinch/layout/internal/conf" 9 | "github.com/google/wire" 10 | "github.com/pkg/errors" 11 | "go.opentelemetry.io/otel" 12 | ) 13 | 14 | // ProviderSet is task providers. 15 | var ProviderSet = wire.NewSet(New) 16 | 17 | // New is initialize task worker from config 18 | func New(c *conf.Bootstrap) (w *worker.Worker, err error) { 19 | w = worker.New( 20 | worker.WithRedisURI(c.Data.Redis.Dsn), 21 | worker.WithGroup(c.Name), 22 | worker.WithHandler(func(ctx context.Context, p worker.Payload) error { 23 | return process(task{ 24 | ctx: ctx, 25 | payload: p, 26 | }) 27 | }), 28 | ) 29 | if w.Error != nil { 30 | log.Error(w.Error) 31 | err = errors.New("initialize worker failed") 32 | return 33 | } 34 | 35 | for id, item := range c.Task { 36 | err = w.Cron( 37 | context.Background(), 38 | worker.WithRunUUID(id), 39 | worker.WithRunGroup(item.Name), 40 | worker.WithRunExpr(item.Expr), 41 | worker.WithRunTimeout(int(item.Timeout)), 42 | worker.WithRunMaxRetry(int(item.Retry)), 43 | ) 44 | if err != nil { 45 | log.Error(err) 46 | err = errors.New("initialize worker failed") 47 | return 48 | } 49 | } 50 | 51 | log.Info("initialize worker success") 52 | return 53 | } 54 | 55 | type task struct { 56 | ctx context.Context 57 | payload worker.Payload 58 | } 59 | 60 | func process(t task) (err error) { 61 | tr := otel.Tracer("task") 62 | ctx, span := tr.Start(t.ctx, "Task") 63 | defer span.End() 64 | switch t.payload.UID { 65 | case "task1": 66 | log.WithContext(ctx).Info("task1: %s", t.payload.Payload) 67 | case "task2": 68 | log.WithContext(ctx).Info("task2: %s", t.payload.Payload) 69 | } 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /internal/conf/conf.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cinch.conf; 3 | 4 | option go_package = "github.com/go-cinch/layout/internal/conf;conf"; 5 | 6 | import "google/protobuf/duration.proto"; 7 | 8 | message Bootstrap { 9 | string name = 1; 10 | string version = 2; 11 | Server server = 3; 12 | Data data = 4; 13 | Tracer tracer = 5; 14 | map task = 6; 15 | Client client = 7; 16 | Log log = 8; 17 | } 18 | 19 | message Server { 20 | message HTTP { 21 | string network = 1; 22 | string addr = 2; 23 | google.protobuf.Duration timeout = 3; 24 | } 25 | message GRPC { 26 | string network = 1; 27 | string addr = 2; 28 | google.protobuf.Duration timeout = 3; 29 | } 30 | bool prod = 1; 31 | string machineId = 2; 32 | HTTP http = 3; 33 | GRPC grpc = 4; 34 | string language = 5; 35 | bool idempotent = 6; 36 | bool validate = 7; 37 | bool nocache = 8; 38 | } 39 | 40 | message Data { 41 | message Database { 42 | string dsn = 1; 43 | string driver = 2; 44 | string endpoint = 3; 45 | string username = 4; 46 | string password = 5; 47 | string schema = 6; 48 | string query = 7; 49 | map tenants = 8; 50 | } 51 | message Redis { 52 | string dsn = 1; 53 | } 54 | Database database = 1; 55 | Redis redis = 2; 56 | } 57 | 58 | message Log { 59 | string level = 1; 60 | bool JSON = 2; 61 | bool showSQL = 3; 62 | } 63 | 64 | message Tracer { 65 | message Otlp { 66 | string endpoint = 1; 67 | bool insecure = 2; 68 | } 69 | message Stdout { 70 | bool prettyPrint = 1; 71 | } 72 | bool enable = 1; 73 | float ratio = 2; 74 | Otlp otlp = 3; 75 | Stdout stdout = 4; 76 | } 77 | 78 | message Client { 79 | bool health = 1; 80 | google.protobuf.Duration timeout = 2; 81 | string auth = 3; 82 | } 83 | 84 | message Task { 85 | string name = 1; 86 | string expr = 2; 87 | int64 timeout = 3; 88 | int64 retry = 4; 89 | } 90 | -------------------------------------------------------------------------------- /internal/data/client.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "github.com/go-cinch/common/log" 9 | "github.com/go-cinch/layout/api/auth" 10 | "github.com/go-cinch/layout/internal/conf" 11 | "github.com/go-kratos/kratos/v2/middleware/circuitbreaker" 12 | "github.com/go-kratos/kratos/v2/middleware/metadata" 13 | "github.com/go-kratos/kratos/v2/middleware/recovery" 14 | "github.com/go-kratos/kratos/v2/middleware/tracing" 15 | "github.com/go-kratos/kratos/v2/transport/grpc" 16 | "github.com/pkg/errors" 17 | g "google.golang.org/grpc" 18 | healthpb "google.golang.org/grpc/health/grpc_health_v1" 19 | ) 20 | 21 | func NewAuthClient(c *conf.Bootstrap) (auth.AuthClient, error) { 22 | return NewClient[auth.AuthClient]( 23 | "auth", 24 | c.Client.Auth, 25 | c.Client.Health, 26 | c.Client.Timeout.AsDuration(), 27 | auth.NewAuthClient, 28 | ) 29 | } 30 | 31 | func NewClient[T any](name, endpoint string, health bool, timeout time.Duration, newClient func(cc g.ClientConnInterface) T) (client T, err error) { 32 | ops := []grpc.ClientOption{ 33 | grpc.WithEndpoint(endpoint), 34 | grpc.WithMiddleware( 35 | tracing.Client(), 36 | metadata.Client(), 37 | circuitbreaker.Client(), 38 | recovery.Recovery(), 39 | ), 40 | grpc.WithOptions(g.WithDisableHealthCheck()), 41 | grpc.WithTimeout(timeout), 42 | } 43 | conn, err := grpc.DialInsecure(context.Background(), ops...) 44 | if err != nil { 45 | err = errors.WithMessage(err, strings.Join([]string{"initialize", name, "client failed"}, " ")) 46 | return 47 | } 48 | if health { 49 | healthClient := healthpb.NewHealthClient(conn) 50 | _, err = healthClient.Check(context.Background(), &healthpb.HealthCheckRequest{}) 51 | if err != nil { 52 | err = errors.WithMessage(err, strings.Join([]string{name, "healthcheck failed"}, " ")) 53 | return 54 | } 55 | } 56 | client = newClient(conn) 57 | log. 58 | WithField("endpoint", endpoint). 59 | Info(strings.Join([]string{"initialize", name, "client success"}, " ")) 60 | return 61 | } 62 | -------------------------------------------------------------------------------- /cmd/server/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate go run -mod=mod github.com/google/wire/cmd/wire 4 | //go:build !wireinject 5 | // +build !wireinject 6 | 7 | package main 8 | 9 | import ( 10 | "github.com/go-cinch/layout/internal/biz" 11 | "github.com/go-cinch/layout/internal/conf" 12 | "github.com/go-cinch/layout/internal/data" 13 | "github.com/go-cinch/layout/internal/pkg/task" 14 | "github.com/go-cinch/layout/internal/server" 15 | "github.com/go-cinch/layout/internal/service" 16 | "github.com/go-kratos/kratos/v2" 17 | ) 18 | 19 | import ( 20 | _ "github.com/go-cinch/common/plugins/gorm/filter" 21 | _ "github.com/go-cinch/common/plugins/kratos/encoding/yml" 22 | ) 23 | 24 | // Injectors from wire.go: 25 | 26 | // wireApp init kratos application. 27 | func wireApp(c *conf.Bootstrap) (*kratos.App, func(), error) { 28 | worker, err := task.New(c) 29 | if err != nil { 30 | return nil, nil, err 31 | } 32 | universalClient, err := data.NewRedis(c) 33 | if err != nil { 34 | return nil, nil, err 35 | } 36 | tenant, err := data.NewDB(c) 37 | if err != nil { 38 | return nil, nil, err 39 | } 40 | sonyflake, err := data.NewSonyflake(c) 41 | if err != nil { 42 | return nil, nil, err 43 | } 44 | tracerProvider, err := data.NewTracer(c) 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | authClient, err := data.NewAuthClient(c) 49 | if err != nil { 50 | return nil, nil, err 51 | } 52 | dataData, cleanup := data.NewData(universalClient, tenant, sonyflake, tracerProvider, authClient) 53 | gameRepo := data.NewGameRepo(dataData) 54 | transaction := data.NewTransaction(dataData) 55 | cache := data.NewCache(c, universalClient) 56 | gameUseCase := biz.NewGameUseCase(c, gameRepo, transaction, cache) 57 | gameService := service.NewGameService(worker, gameUseCase) 58 | grpcServer := server.NewGRPCServer(c, gameService, universalClient) 59 | httpServer := server.NewHTTPServer(c, gameService, universalClient) 60 | app := newApp(grpcServer, httpServer) 61 | return app, func() { 62 | cleanup() 63 | }, nil 64 | } 65 | -------------------------------------------------------------------------------- /internal/data/query/gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package query 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | 11 | "gorm.io/gorm" 12 | 13 | "gorm.io/gen" 14 | 15 | "gorm.io/plugin/dbresolver" 16 | ) 17 | 18 | func Use(db *gorm.DB, opts ...gen.DOOption) *Query { 19 | return &Query{ 20 | db: db, 21 | Game: newGame(db, opts...), 22 | } 23 | } 24 | 25 | type Query struct { 26 | db *gorm.DB 27 | 28 | Game game 29 | } 30 | 31 | func (q *Query) Available() bool { return q.db != nil } 32 | 33 | func (q *Query) clone(db *gorm.DB) *Query { 34 | return &Query{ 35 | db: db, 36 | Game: q.Game.clone(db), 37 | } 38 | } 39 | 40 | func (q *Query) ReadDB() *Query { 41 | return q.ReplaceDB(q.db.Clauses(dbresolver.Read)) 42 | } 43 | 44 | func (q *Query) WriteDB() *Query { 45 | return q.ReplaceDB(q.db.Clauses(dbresolver.Write)) 46 | } 47 | 48 | func (q *Query) ReplaceDB(db *gorm.DB) *Query { 49 | return &Query{ 50 | db: db, 51 | Game: q.Game.replaceDB(db), 52 | } 53 | } 54 | 55 | type queryCtx struct { 56 | Game *gameDo 57 | } 58 | 59 | func (q *Query) WithContext(ctx context.Context) *queryCtx { 60 | return &queryCtx{ 61 | Game: q.Game.WithContext(ctx), 62 | } 63 | } 64 | 65 | func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error { 66 | return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...) 67 | } 68 | 69 | func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx { 70 | tx := q.db.Begin(opts...) 71 | return &QueryTx{Query: q.clone(tx), Error: tx.Error} 72 | } 73 | 74 | type QueryTx struct { 75 | *Query 76 | Error error 77 | } 78 | 79 | func (q *QueryTx) Commit() error { 80 | return q.db.Commit().Error 81 | } 82 | 83 | func (q *QueryTx) Rollback() error { 84 | return q.db.Rollback().Error 85 | } 86 | 87 | func (q *QueryTx) SavePoint(name string) error { 88 | return q.db.SavePoint(name).Error 89 | } 90 | 91 | func (q *QueryTx) RollbackTo(name string) error { 92 | return q.db.RollbackTo(name).Error 93 | } 94 | -------------------------------------------------------------------------------- /internal/data/tracer.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/go-cinch/common/log" 8 | "github.com/go-cinch/layout/internal/conf" 9 | "github.com/pkg/errors" 10 | "go.opentelemetry.io/otel" 11 | "go.opentelemetry.io/otel/attribute" 12 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace" 13 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" 14 | "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 15 | "go.opentelemetry.io/otel/sdk/resource" 16 | "go.opentelemetry.io/otel/sdk/trace" 17 | semconv "go.opentelemetry.io/otel/semconv/v1.26.0" 18 | "google.golang.org/grpc" 19 | ) 20 | 21 | func NewTracer(c *conf.Bootstrap) (tp *trace.TracerProvider, err error) { 22 | if !c.Tracer.Enable { 23 | log.Info("skip initialize tracer") 24 | return 25 | } 26 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 27 | defer cancel() 28 | var exporter trace.SpanExporter 29 | var resourcer *resource.Resource 30 | attrs := []attribute.KeyValue{semconv.ServiceNameKey.String(c.Name)} 31 | if c.Tracer.Otlp.Endpoint != "" { 32 | // rpc driver 33 | driverOpts := []otlptracegrpc.Option{ 34 | otlptracegrpc.WithEndpoint(c.Tracer.Otlp.Endpoint), 35 | otlptracegrpc.WithDialOption(grpc.WithBlock()), 36 | } 37 | if c.Tracer.Otlp.Insecure { 38 | driverOpts = append(driverOpts, otlptracegrpc.WithInsecure()) 39 | } 40 | driver := otlptracegrpc.NewClient(driverOpts...) 41 | exporter, err = otlptrace.New(ctx, driver) 42 | resourcer = resource.NewSchemaless(attrs...) 43 | } else { 44 | // stdout driver 45 | if c.Tracer.Stdout.PrettyPrint { 46 | exporter, err = stdouttrace.New(stdouttrace.WithPrettyPrint()) 47 | } else { 48 | exporter, err = stdouttrace.New() 49 | } 50 | resourcer = resource.NewSchemaless(attrs...) 51 | } 52 | 53 | if err != nil { 54 | log.Error(err) 55 | err = errors.New("initialize tracer failed") 56 | return 57 | } 58 | providerOpts := []trace.TracerProviderOption{ 59 | trace.WithBatcher(exporter), 60 | trace.WithResource(resourcer), 61 | trace.WithSampler(trace.TraceIDRatioBased(float64(c.Tracer.Ratio))), 62 | } 63 | tp = trace.NewTracerProvider(providerOpts...) 64 | otel.SetTracerProvider(tp) 65 | log.Info("initialize tracer success, ratio: %v", c.Tracer.Ratio) 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /internal/server/grpc.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/go-cinch/common/i18n" 5 | i18nMiddleware "github.com/go-cinch/common/middleware/i18n" 6 | "github.com/go-cinch/common/middleware/logging" 7 | tenantMiddleware "github.com/go-cinch/common/middleware/tenant" 8 | traceMiddleware "github.com/go-cinch/common/middleware/trace" 9 | "github.com/go-cinch/layout/api/game" 10 | "github.com/go-cinch/layout/internal/conf" 11 | localMiddleware "github.com/go-cinch/layout/internal/server/middleware" 12 | "github.com/go-cinch/layout/internal/service" 13 | "github.com/go-kratos/kratos/v2/middleware" 14 | "github.com/go-kratos/kratos/v2/middleware/metadata" 15 | "github.com/go-kratos/kratos/v2/middleware/ratelimit" 16 | "github.com/go-kratos/kratos/v2/middleware/recovery" 17 | "github.com/go-kratos/kratos/v2/middleware/tracing" 18 | "github.com/go-kratos/kratos/v2/middleware/validate" 19 | "github.com/go-kratos/kratos/v2/transport/grpc" 20 | "github.com/redis/go-redis/v9" 21 | "golang.org/x/text/language" 22 | ) 23 | 24 | // NewGRPCServer new a gRPC server. 25 | func NewGRPCServer( 26 | c *conf.Bootstrap, 27 | svc *service.GameService, 28 | rds redis.UniversalClient, 29 | ) *grpc.Server { 30 | middlewares := []middleware.Middleware{ 31 | recovery.Recovery(), 32 | tenantMiddleware.Tenant(), 33 | ratelimit.Server(), 34 | } 35 | if c.Tracer.Enable { 36 | middlewares = append(middlewares, tracing.Server(), traceMiddleware.ID()) 37 | } 38 | 39 | middlewares = append( 40 | middlewares, 41 | logging.Server(), 42 | i18nMiddleware.Translator(i18n.WithLanguage(language.Make(c.Server.Language)), i18n.WithFs(locales)), 43 | metadata.Server(), 44 | ) 45 | if c.Server.Idempotent { 46 | middlewares = append(middlewares, localMiddleware.Idempotent(rds)) 47 | } 48 | if c.Server.Validate { 49 | middlewares = append(middlewares, validate.Validator()) 50 | } 51 | var opts = []grpc.ServerOption{grpc.Middleware(middlewares...)} 52 | if c.Server.Grpc.Network != "" { 53 | opts = append(opts, grpc.Network(c.Server.Grpc.Network)) 54 | } 55 | if c.Server.Grpc.Addr != "" { 56 | opts = append(opts, grpc.Address(c.Server.Grpc.Addr)) 57 | } 58 | if c.Server.Grpc.Timeout != nil { 59 | opts = append(opts, grpc.Timeout(c.Server.Grpc.Timeout.AsDuration())) 60 | } 61 | srv := grpc.NewServer(opts...) 62 | game.RegisterGameServer(srv, svc) 63 | return srv 64 | } 65 | -------------------------------------------------------------------------------- /internal/service/game.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-cinch/common/copierx" 7 | "github.com/go-cinch/common/page" 8 | "github.com/go-cinch/common/proto/params" 9 | "github.com/go-cinch/common/utils" 10 | "github.com/go-cinch/layout/api/game" 11 | "github.com/go-cinch/layout/internal/biz" 12 | "go.opentelemetry.io/otel" 13 | "google.golang.org/protobuf/types/known/emptypb" 14 | ) 15 | 16 | func (s *GameService) CreateGame(ctx context.Context, req *game.CreateGameRequest) (rp *emptypb.Empty, err error) { 17 | tr := otel.Tracer("api") 18 | ctx, span := tr.Start(ctx, "CreateGame") 19 | defer span.End() 20 | rp = &emptypb.Empty{} 21 | r := &biz.CreateGame{} 22 | copierx.Copy(&r, req) 23 | err = s.game.Create(ctx, r) 24 | return 25 | } 26 | 27 | func (s *GameService) GetGame(ctx context.Context, req *game.GetGameRequest) (rp *game.GetGameReply, err error) { 28 | tr := otel.Tracer("api") 29 | ctx, span := tr.Start(ctx, "GetGame") 30 | defer span.End() 31 | rp = &game.GetGameReply{} 32 | res, err := s.game.Get(ctx, req.Id) 33 | if err != nil { 34 | return 35 | } 36 | copierx.Copy(&rp, res) 37 | return 38 | } 39 | 40 | func (s *GameService) FindGame(ctx context.Context, req *game.FindGameRequest) (rp *game.FindGameReply, err error) { 41 | tr := otel.Tracer("api") 42 | ctx, span := tr.Start(ctx, "FindGame") 43 | defer span.End() 44 | rp = &game.FindGameReply{} 45 | rp.Page = ¶ms.Page{} 46 | r := &biz.FindGame{} 47 | r.Page = page.Page{} 48 | copierx.Copy(&r, req) 49 | copierx.Copy(&r.Page, req.Page) 50 | res, err := s.game.Find(ctx, r) 51 | if err != nil { 52 | return 53 | } 54 | copierx.Copy(&rp.Page, r.Page) 55 | copierx.Copy(&rp.List, res) 56 | return 57 | } 58 | 59 | func (s *GameService) UpdateGame(ctx context.Context, req *game.UpdateGameRequest) (rp *emptypb.Empty, err error) { 60 | tr := otel.Tracer("api") 61 | ctx, span := tr.Start(ctx, "UpdateGame") 62 | defer span.End() 63 | rp = &emptypb.Empty{} 64 | r := &biz.UpdateGame{} 65 | copierx.Copy(&r, req) 66 | err = s.game.Update(ctx, r) 67 | return 68 | } 69 | 70 | func (s *GameService) DeleteGame(ctx context.Context, req *params.IdsRequest) (rp *emptypb.Empty, err error) { 71 | tr := otel.Tracer("api") 72 | ctx, span := tr.Start(ctx, "DeleteGame") 73 | defer span.End() 74 | rp = &emptypb.Empty{} 75 | err = s.game.Delete(ctx, utils.Str2Uint64Arr(req.Ids)...) 76 | return 77 | } 78 | -------------------------------------------------------------------------------- /third_party/openapiv3/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package openapi.v3; 18 | 19 | import "openapiv3/OpenAPIv3.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | // This option lets the proto compiler generate Java code inside the package 23 | // name (see below) instead of inside an outer class. It creates a simpler 24 | // developer experience by reducing one-level of name nesting and be 25 | // consistent with most programming languages that don't support outer classes. 26 | option java_multiple_files = true; 27 | 28 | // The Java outer classname should be the filename in UpperCamelCase. This 29 | // class is only used to hold proto descriptor, so developers don't need to 30 | // work with it directly. 31 | option java_outer_classname = "AnnotationsProto"; 32 | 33 | // The Java package name must be proto package name with proper prefix. 34 | option java_package = "org.openapi_v3"; 35 | 36 | // A reasonable prefix for the Objective-C symbols generated from the package. 37 | // It should at a minimum be 3 characters long, all uppercase, and convention 38 | // is to use an abbreviation of the package name. Something short, but 39 | // hopefully unique enough to not conflict with things that may come along in 40 | // the future. 'GPB' is reserved for the protocol buffer implementation itself. 41 | option objc_class_prefix = "OAS"; 42 | 43 | // The Go package name. 44 | option go_package = "github.com/google/gnostic/openapiv3;openapi_v3"; 45 | 46 | extend google.protobuf.FileOptions { 47 | Document document = 1143; 48 | } 49 | 50 | extend google.protobuf.MethodOptions { 51 | Operation operation = 1143; 52 | } 53 | 54 | extend google.protobuf.MessageOptions { 55 | Schema schema = 1143; 56 | } 57 | 58 | extend google.protobuf.FieldOptions { 59 | Schema property = 1143; 60 | } -------------------------------------------------------------------------------- /third_party/google/protobuf/source_context.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "SourceContextProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; 41 | 42 | // `SourceContext` represents information about the source of a 43 | // protobuf element, like the file in which it is defined. 44 | message SourceContext { 45 | // The path-qualified name of the .proto file that contained the associated 46 | // protobuf element. For example: `"google/protobuf/source_context.proto"`. 47 | string file_name = 1; 48 | } 49 | -------------------------------------------------------------------------------- /internal/server/http.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/go-cinch/common/i18n" 5 | i18nMiddleware "github.com/go-cinch/common/middleware/i18n" 6 | "github.com/go-cinch/common/middleware/logging" 7 | tenantMiddleware "github.com/go-cinch/common/middleware/tenant" 8 | traceMiddleware "github.com/go-cinch/common/middleware/trace" 9 | "github.com/go-cinch/layout/api/game" 10 | "github.com/go-cinch/layout/internal/conf" 11 | localMiddleware "github.com/go-cinch/layout/internal/server/middleware" 12 | "github.com/go-cinch/layout/internal/service" 13 | "github.com/go-kratos/kratos/v2/middleware" 14 | "github.com/go-kratos/kratos/v2/middleware/metadata" 15 | "github.com/go-kratos/kratos/v2/middleware/ratelimit" 16 | "github.com/go-kratos/kratos/v2/middleware/recovery" 17 | "github.com/go-kratos/kratos/v2/middleware/tracing" 18 | "github.com/go-kratos/kratos/v2/middleware/validate" 19 | "github.com/go-kratos/kratos/v2/transport/http" 20 | "github.com/go-kratos/kratos/v2/transport/http/pprof" 21 | "github.com/redis/go-redis/v9" 22 | "golang.org/x/text/language" 23 | ) 24 | 25 | // NewHTTPServer new a HTTP server. 26 | func NewHTTPServer( 27 | c *conf.Bootstrap, 28 | svc *service.GameService, 29 | rds redis.UniversalClient, 30 | ) *http.Server { 31 | middlewares := []middleware.Middleware{ 32 | recovery.Recovery(), 33 | tenantMiddleware.Tenant(), 34 | ratelimit.Server(), 35 | localMiddleware.Header(), 36 | } 37 | if c.Tracer.Enable { 38 | middlewares = append(middlewares, tracing.Server(), traceMiddleware.ID()) 39 | } 40 | middlewares = append( 41 | middlewares, 42 | logging.Server(), 43 | i18nMiddleware.Translator(i18n.WithLanguage(language.Make(c.Server.Language)), i18n.WithFs(locales)), 44 | metadata.Server(), 45 | ) 46 | if c.Server.Idempotent { 47 | middlewares = append(middlewares, localMiddleware.Idempotent(rds)) 48 | } 49 | if c.Server.Validate { 50 | middlewares = append(middlewares, validate.Validator()) 51 | } 52 | var opts = []http.ServerOption{http.Middleware(middlewares...)} 53 | if c.Server.Http.Network != "" { 54 | opts = append(opts, http.Network(c.Server.Http.Network)) 55 | } 56 | if c.Server.Http.Addr != "" { 57 | opts = append(opts, http.Address(c.Server.Http.Addr)) 58 | } 59 | if c.Server.Http.Timeout != nil { 60 | opts = append(opts, http.Timeout(c.Server.Http.Timeout.AsDuration())) 61 | } 62 | srv := http.NewServer(opts...) 63 | game.RegisterGameHTTPServer(srv, svc) 64 | srv.HandlePrefix("/debug/pprof", pprof.NewHandler()) 65 | srv.HandlePrefix("/pub/healthcheck", HealthHandler(svc)) 66 | return srv 67 | } 68 | -------------------------------------------------------------------------------- /third_party/google/protobuf/empty.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "google.golang.org/protobuf/types/known/emptypb"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "EmptyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | option cc_enable_arenas = true; 42 | 43 | // A generic empty message that you can re-use to avoid defining duplicated 44 | // empty messages in your APIs. A typical example is to use it as the request 45 | // or the response type of an API method. For instance: 46 | // 47 | // service Foo { 48 | // rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); 49 | // } 50 | // 51 | // The JSON representation for `Empty` is empty JSON object `{}`. 52 | message Empty {} 53 | -------------------------------------------------------------------------------- /api/reason/reason_errors.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-errors. DO NOT EDIT. 2 | 3 | package reason 4 | 5 | import ( 6 | fmt "fmt" 7 | errors "github.com/go-kratos/kratos/v2/errors" 8 | ) 9 | 10 | // This is a compile-time assertion to ensure that this generated file 11 | // is compatible with the kratos package it is being compiled against. 12 | const _ = errors.SupportPackageIsVersion1 13 | 14 | func IsInternal(err error) bool { 15 | if err == nil { 16 | return false 17 | } 18 | e := errors.FromError(err) 19 | return e.Reason == ErrorReason_INTERNAL.String() && e.Code == 500 20 | } 21 | 22 | func ErrorInternal(format string, args ...interface{}) *errors.Error { 23 | return errors.New(500, ErrorReason_INTERNAL.String(), fmt.Sprintf(format, args...)) 24 | } 25 | 26 | func IsTooManyRequests(err error) bool { 27 | if err == nil { 28 | return false 29 | } 30 | e := errors.FromError(err) 31 | return e.Reason == ErrorReason_TOO_MANY_REQUESTS.String() && e.Code == 429 32 | } 33 | 34 | func ErrorTooManyRequests(format string, args ...interface{}) *errors.Error { 35 | return errors.New(429, ErrorReason_TOO_MANY_REQUESTS.String(), fmt.Sprintf(format, args...)) 36 | } 37 | 38 | func IsIllegalParameter(err error) bool { 39 | if err == nil { 40 | return false 41 | } 42 | e := errors.FromError(err) 43 | return e.Reason == ErrorReason_ILLEGAL_PARAMETER.String() && e.Code == 400 44 | } 45 | 46 | func ErrorIllegalParameter(format string, args ...interface{}) *errors.Error { 47 | return errors.New(400, ErrorReason_ILLEGAL_PARAMETER.String(), fmt.Sprintf(format, args...)) 48 | } 49 | 50 | func IsNotFound(err error) bool { 51 | if err == nil { 52 | return false 53 | } 54 | e := errors.FromError(err) 55 | return e.Reason == ErrorReason_NOT_FOUND.String() && e.Code == 400 56 | } 57 | 58 | func ErrorNotFound(format string, args ...interface{}) *errors.Error { 59 | return errors.New(400, ErrorReason_NOT_FOUND.String(), fmt.Sprintf(format, args...)) 60 | } 61 | 62 | func IsUnauthorized(err error) bool { 63 | if err == nil { 64 | return false 65 | } 66 | e := errors.FromError(err) 67 | return e.Reason == ErrorReason_UNAUTHORIZED.String() && e.Code == 401 68 | } 69 | 70 | func ErrorUnauthorized(format string, args ...interface{}) *errors.Error { 71 | return errors.New(401, ErrorReason_UNAUTHORIZED.String(), fmt.Sprintf(format, args...)) 72 | } 73 | 74 | func IsForbidden(err error) bool { 75 | if err == nil { 76 | return false 77 | } 78 | e := errors.FromError(err) 79 | return e.Reason == ErrorReason_FORBIDDEN.String() && e.Code == 403 80 | } 81 | 82 | func ErrorForbidden(format string, args ...interface{}) *errors.Error { 83 | return errors.New(403, ErrorReason_FORBIDDEN.String(), fmt.Sprintf(format, args...)) 84 | } 85 | -------------------------------------------------------------------------------- /third_party/google/api/httpbody.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/protobuf/any.proto"; 20 | 21 | option cc_enable_arenas = true; 22 | option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "HttpBodyProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | // Message that represents an arbitrary HTTP body. It should only be used for 29 | // payload formats that can't be represented as JSON, such as raw binary or 30 | // an HTML page. 31 | // 32 | // 33 | // This message can be used both in streaming and non-streaming API methods in 34 | // the request as well as the response. 35 | // 36 | // It can be used as a top-level request field, which is convenient if one 37 | // wants to extract parameters from either the URL or HTTP template into the 38 | // request fields and also want access to the raw HTTP body. 39 | // 40 | // Example: 41 | // 42 | // message GetResourceRequest { 43 | // // A unique request id. 44 | // string request_id = 1; 45 | // 46 | // // The raw HTTP body is bound to this field. 47 | // google.api.HttpBody http_body = 2; 48 | // } 49 | // 50 | // service ResourceService { 51 | // rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); 52 | // rpc UpdateResource(google.api.HttpBody) returns 53 | // (google.protobuf.Empty); 54 | // } 55 | // 56 | // Example with streaming methods: 57 | // 58 | // service CaldavService { 59 | // rpc GetCalendar(stream google.api.HttpBody) 60 | // returns (stream google.api.HttpBody); 61 | // rpc UpdateCalendar(stream google.api.HttpBody) 62 | // returns (stream google.api.HttpBody); 63 | // } 64 | // 65 | // Use of this type only changes how the request and response bodies are 66 | // handled, all other features will continue to work unchanged. 67 | message HttpBody { 68 | // The HTTP Content-Type header value specifying the content type of the body. 69 | string content_type = 1; 70 | 71 | // The HTTP request/response body as raw binary. 72 | bytes data = 2; 73 | 74 | // Application specific response metadata. Must be set in the first response 75 | // for streaming APIs. 76 | repeated google.protobuf.Any extensions = 3; 77 | } 78 | -------------------------------------------------------------------------------- /third_party/google/api/field_behavior.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/protobuf/descriptor.proto"; 20 | 21 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 22 | option java_multiple_files = true; 23 | option java_outer_classname = "FieldBehaviorProto"; 24 | option java_package = "com.google.api"; 25 | option objc_class_prefix = "GAPI"; 26 | 27 | 28 | // An indicator of the behavior of a given field (for example, that a field 29 | // is required in requests, or given as output but ignored as input). 30 | // This **does not** change the behavior in protocol buffers itself; it only 31 | // denotes the behavior and may affect how API tooling handles the field. 32 | // 33 | // Note: This enum **may** receive new values in the future. 34 | enum FieldBehavior { 35 | // Conventional default for enums. Do not use this. 36 | FIELD_BEHAVIOR_UNSPECIFIED = 0; 37 | 38 | // Specifically denotes a field as optional. 39 | // While all fields in protocol buffers are optional, this may be specified 40 | // for emphasis if appropriate. 41 | OPTIONAL = 1; 42 | 43 | // Denotes a field as required. 44 | // This indicates that the field **must** be provided as part of the request, 45 | // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). 46 | REQUIRED = 2; 47 | 48 | // Denotes a field as output only. 49 | // This indicates that the field is provided in responses, but including the 50 | // field in a request does nothing (the server *must* ignore it and 51 | // *must not* throw an error as a result of the field's presence). 52 | OUTPUT_ONLY = 3; 53 | 54 | // Denotes a field as input only. 55 | // This indicates that the field is provided in requests, and the 56 | // corresponding field is not included in output. 57 | INPUT_ONLY = 4; 58 | 59 | // Denotes a field as immutable. 60 | // This indicates that the field may be set once in a request to create a 61 | // resource, but may not be changed thereafter. 62 | IMMUTABLE = 5; 63 | } 64 | 65 | 66 | extend google.protobuf.FieldOptions { 67 | // A designation of a specific field behavior (required, output only, etc.) 68 | // in protobuf messages. 69 | // 70 | // Examples: 71 | // 72 | // string name = 1 [(google.api.field_behavior) = REQUIRED]; 73 | // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; 74 | // google.protobuf.Duration ttl = 1 75 | // [(google.api.field_behavior) = INPUT_ONLY]; 76 | // google.protobuf.Timestamp expire_time = 1 77 | // [(google.api.field_behavior) = OUTPUT_ONLY, 78 | // (google.api.field_behavior) = IMMUTABLE]; 79 | repeated FieldBehavior field_behavior = 1052; 80 | } -------------------------------------------------------------------------------- /internal/data/game.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/go-cinch/common/constant" 8 | "github.com/go-cinch/common/copierx" 9 | "github.com/go-cinch/common/log" 10 | "github.com/go-cinch/common/utils" 11 | "github.com/go-cinch/layout/internal/biz" 12 | "github.com/go-cinch/layout/internal/data/model" 13 | "github.com/go-cinch/layout/internal/data/query" 14 | "gorm.io/gen" 15 | ) 16 | 17 | type gameRepo struct { 18 | data *Data 19 | } 20 | 21 | func NewGameRepo(data *Data) biz.GameRepo { 22 | return &gameRepo{ 23 | data: data, 24 | } 25 | } 26 | 27 | func (ro gameRepo) Create(ctx context.Context, item *biz.CreateGame) (err error) { 28 | err = ro.NameExists(ctx, item.Name) 29 | if err == nil { 30 | err = biz.ErrDuplicateField(ctx, "name", item.Name) 31 | return 32 | } 33 | var m model.Game 34 | copierx.Copy(&m, item) 35 | p := query.Use(ro.data.DB(ctx)).Game 36 | db := p.WithContext(ctx) 37 | m.ID = ro.data.ID(ctx) 38 | err = db.Create(&m) 39 | return 40 | } 41 | 42 | func (ro gameRepo) Get(ctx context.Context, id uint64) (item *biz.Game, err error) { 43 | item = &biz.Game{} 44 | p := query.Use(ro.data.DB(ctx)).Game 45 | db := p.WithContext(ctx) 46 | m := db.GetByID(id) 47 | if m.ID == constant.UI0 { 48 | err = biz.ErrRecordNotFound(ctx) 49 | return 50 | } 51 | copierx.Copy(&item, m) 52 | return 53 | } 54 | 55 | func (ro gameRepo) Find(ctx context.Context, condition *biz.FindGame) (rp []biz.Game) { 56 | p := query.Use(ro.data.DB(ctx)).Game 57 | db := p.WithContext(ctx) 58 | rp = make([]biz.Game, 0) 59 | list := make([]model.Game, 0) 60 | conditions := make([]gen.Condition, 0, 2) 61 | if condition.Name != nil { 62 | conditions = append(conditions, p.Name.Like(strings.Join([]string{"%", *condition.Name, "%"}, ""))) 63 | } 64 | condition.Page.Primary = "id" 65 | condition.Page. 66 | WithContext(ctx). 67 | Query( 68 | db. 69 | Order(p.ID.Desc()). 70 | Where(conditions...). 71 | UnderlyingDB(), 72 | ). 73 | Find(&list) 74 | copierx.Copy(&rp, list) 75 | return 76 | } 77 | 78 | func (ro gameRepo) Update(ctx context.Context, item *biz.UpdateGame) (err error) { 79 | p := query.Use(ro.data.DB(ctx)).Game 80 | db := p.WithContext(ctx) 81 | m := db.GetByID(item.ID) 82 | if m.ID == constant.UI0 { 83 | err = biz.ErrRecordNotFound(ctx) 84 | return 85 | } 86 | change := make(map[string]interface{}) 87 | utils.CompareDiff(m, item, &change) 88 | if len(change) == 0 { 89 | err = biz.ErrDataNotChange(ctx) 90 | return 91 | } 92 | if item.Name != nil && *item.Name != m.Name { 93 | err = ro.NameExists(ctx, *item.Name) 94 | if err == nil { 95 | err = biz.ErrDuplicateField(ctx, "name", *item.Name) 96 | return 97 | } 98 | } 99 | _, err = db. 100 | Where(p.ID.Eq(item.ID)). 101 | Updates(&change) 102 | return 103 | } 104 | 105 | func (ro gameRepo) Delete(ctx context.Context, ids ...uint64) (err error) { 106 | p := query.Use(ro.data.DB(ctx)).Game 107 | db := p.WithContext(ctx) 108 | _, err = db. 109 | Where(p.ID.In(ids...)). 110 | Delete() 111 | return 112 | } 113 | 114 | func (ro gameRepo) NameExists(ctx context.Context, name string) (err error) { 115 | p := query.Use(ro.data.DB(ctx)).Game 116 | db := p.WithContext(ctx) 117 | arr := strings.Split(name, ",") 118 | for _, item := range arr { 119 | res := db.GetByCol("name", item) 120 | if res.ID == constant.UI0 { 121 | err = biz.ErrRecordNotFound(ctx) 122 | log. 123 | WithError(err). 124 | Warn("invalid `name`: %s", name) 125 | return 126 | } 127 | } 128 | return 129 | } 130 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOHOSTOS:=$(shell go env GOHOSTOS) 2 | GOPATH:=$(shell go env GOPATH) 3 | VERSION=$(shell git describe --tags --always) 4 | 5 | ifeq ($(GOHOSTOS), windows) 6 | #the `find.exe` is different from `find` in bash/shell. 7 | #to see https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/find. 8 | #changed to use git-bash.exe to run find cli or other cli friendly, caused of every developer has a Git. 9 | Git_Bash= $(subst cmd\,bin\bash.exe,$(dir $(shell where git))) 10 | INTERNAL_PROTO_FILES=$(shell $(Git_Bash) -c "find internal -name *.proto") 11 | API_PROTO_FILES=$(shell $(Git_Bash) -c "find api -name *.proto") 12 | else 13 | INTERNAL_PROTO_FILES=$(shell find internal -name *.proto) 14 | API_PROTO_FILES=$(shell find api -maxdepth 2 -name *.proto) 15 | endif 16 | 17 | .PHONY: init 18 | # init env 19 | init: 20 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.32.0 21 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 22 | go install github.com/go-kratos/kratos/cmd/kratos/v2@latest 23 | go install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest 24 | go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest 25 | go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 26 | go install github.com/envoyproxy/protoc-gen-validate@v1.0.4 27 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.55.2 28 | go install github.com/go-cinch/cinch/cmd/cinch@latest 29 | 30 | .PHONY: config 31 | # generate internal proto 32 | config: 33 | protoc --proto_path=./internal \ 34 | --proto_path=./third_party \ 35 | --go_out=paths=source_relative:./internal \ 36 | $(INTERNAL_PROTO_FILES) 37 | 38 | .PHONY: sub 39 | # update submodule 40 | sub: 41 | git submodule update --force --recursive --init --remote 42 | 43 | .PHONY: api 44 | # generate api proto 45 | api: 46 | @mkdir -p docs 47 | for NAME in $(API_PROTO_FILES); do \ 48 | ROOT=$(shell pwd); \ 49 | DIR=`echo $$NAME | awk -F '-proto/[^/]*$$' '{print $$1}'`; \ 50 | echo $$NAME; \ 51 | protoc --proto_path=./api \ 52 | --proto_path=./third_party \ 53 | --go_out=. \ 54 | --go-errors_out=. \ 55 | --go-http_out=. \ 56 | --go-grpc_out=. \ 57 | --validate_out=lang=go:. \ 58 | --openapi_out=fq_schema_naming=true,default_response=false,output_mode=source_relative:docs \ 59 | $$NAME; \ 60 | done 61 | @echo 'You can import *.json into https://editor.swagger.io/' 62 | 63 | .PHONY: build 64 | # build 65 | build: 66 | mkdir -p bin/ && go build -ldflags "-X main.Version=$(VERSION)" -o ./bin/ ./... 67 | 68 | .PHONY: gen 69 | # generate wire 70 | gen: 71 | go mod tidy 72 | go get github.com/google/wire/cmd/wire@latest 73 | go generate ./... 74 | go mod tidy 75 | 76 | .PHONY: lint 77 | # golangci-lint 78 | lint: 79 | golangci-lint run --fix 80 | 81 | .PHONY: all 82 | # generate all 83 | all: 84 | make api; 85 | make config; 86 | make gen; 87 | make lint; 88 | 89 | local: 90 | cinch run 91 | 92 | # show help 93 | help: 94 | @echo '' 95 | @echo 'Usage:' 96 | @echo ' make [target]' 97 | @echo '' 98 | @echo 'Targets:' 99 | @awk '/^[a-zA-Z\-\_0-9]+:/ { \ 100 | helpMessage = match(lastLine, /^# (.*)/); \ 101 | if (helpMessage) { \ 102 | helpCommand = substr($$1, 0, index($$1, ":")-1); \ 103 | helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \ 104 | printf "\033[36m%-22s\033[0m %s\n", helpCommand,helpMessage; \ 105 | } \ 106 | } \ 107 | { lastLine = $$0 }' $(MAKEFILE_LIST) 108 | 109 | .DEFAULT_GOAL := help 110 | -------------------------------------------------------------------------------- /third_party/google/api/client.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/protobuf/descriptor.proto"; 20 | 21 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 22 | option java_multiple_files = true; 23 | option java_outer_classname = "ClientProto"; 24 | option java_package = "com.google.api"; 25 | option objc_class_prefix = "GAPI"; 26 | 27 | 28 | extend google.protobuf.ServiceOptions { 29 | // The hostname for this service. 30 | // This should be specified with no prefix or protocol. 31 | // 32 | // Example: 33 | // 34 | // service Foo { 35 | // option (google.api.default_host) = "foo.googleapi.com"; 36 | // ... 37 | // } 38 | string default_host = 1049; 39 | 40 | // OAuth scopes needed for the client. 41 | // 42 | // Example: 43 | // 44 | // service Foo { 45 | // option (google.api.oauth_scopes) = \ 46 | // "https://www.googleapis.com/auth/cloud-platform"; 47 | // ... 48 | // } 49 | // 50 | // If there is more than one scope, use a comma-separated string: 51 | // 52 | // Example: 53 | // 54 | // service Foo { 55 | // option (google.api.oauth_scopes) = \ 56 | // "https://www.googleapis.com/auth/cloud-platform," 57 | // "https://www.googleapis.com/auth/monitoring"; 58 | // ... 59 | // } 60 | string oauth_scopes = 1050; 61 | } 62 | 63 | 64 | extend google.protobuf.MethodOptions { 65 | // A definition of a client library method signature. 66 | // 67 | // In client libraries, each proto RPC corresponds to one or more methods 68 | // which the end user is able to call, and calls the underlying RPC. 69 | // Normally, this method receives a single argument (a struct or instance 70 | // corresponding to the RPC request object). Defining this field will 71 | // add one or more overloads providing flattened or simpler method signatures 72 | // in some languages. 73 | // 74 | // The fields on the method signature are provided as a comma-separated 75 | // string. 76 | // 77 | // For example, the proto RPC and annotation: 78 | // 79 | // rpc CreateSubscription(CreateSubscriptionRequest) 80 | // returns (Subscription) { 81 | // option (google.api.method_signature) = "name,topic"; 82 | // } 83 | // 84 | // Would add the following Java overload (in addition to the method accepting 85 | // the request object): 86 | // 87 | // public final Subscription createSubscription(String name, String topic) 88 | // 89 | // The following backwards-compatibility guidelines apply: 90 | // 91 | // * Adding this annotation to an unannotated method is backwards 92 | // compatible. 93 | // * Adding this annotation to a method which already has existing 94 | // method signature annotations is backwards compatible if and only if 95 | // the new method signature annotation is last in the sequence. 96 | // * Modifying or removing an existing method signature annotation is 97 | // a breaking change. 98 | // * Re-ordering existing method signature annotations is a breaking 99 | // change. 100 | repeated string method_signature = 1051; 101 | } -------------------------------------------------------------------------------- /third_party/google/protobuf/struct.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/structpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "StructProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // `Struct` represents a structured data value, consisting of fields 44 | // which map to dynamically typed values. In some languages, `Struct` 45 | // might be supported by a native representation. For example, in 46 | // scripting languages like JS a struct is represented as an 47 | // object. The details of that representation are described together 48 | // with the proto support for the language. 49 | // 50 | // The JSON representation for `Struct` is JSON object. 51 | message Struct { 52 | // Unordered map of dynamically typed values. 53 | map fields = 1; 54 | } 55 | 56 | // `Value` represents a dynamically typed value which can be either 57 | // null, a number, a string, a boolean, a recursive struct value, or a 58 | // list of values. A producer of value is expected to set one of these 59 | // variants. Absence of any variant indicates an error. 60 | // 61 | // The JSON representation for `Value` is JSON value. 62 | message Value { 63 | // The kind of value. 64 | oneof kind { 65 | // Represents a null value. 66 | NullValue null_value = 1; 67 | // Represents a double value. 68 | double number_value = 2; 69 | // Represents a string value. 70 | string string_value = 3; 71 | // Represents a boolean value. 72 | bool bool_value = 4; 73 | // Represents a structured value. 74 | Struct struct_value = 5; 75 | // Represents a repeated `Value`. 76 | ListValue list_value = 6; 77 | } 78 | } 79 | 80 | // `NullValue` is a singleton enumeration to represent the null value for the 81 | // `Value` type union. 82 | // 83 | // The JSON representation for `NullValue` is JSON `null`. 84 | enum NullValue { 85 | // Null value. 86 | NULL_VALUE = 0; 87 | } 88 | 89 | // `ListValue` is a wrapper around a repeated field of values. 90 | // 91 | // The JSON representation for `ListValue` is JSON array. 92 | message ListValue { 93 | // Repeated field of dynamically typed values. 94 | repeated Value values = 1; 95 | } 96 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "strconv" 7 | 8 | "github.com/go-cinch/common/log" 9 | "github.com/go-cinch/common/log/caller" 10 | _ "github.com/go-cinch/common/plugins/gorm/filter" 11 | "github.com/go-cinch/common/plugins/k8s/pod" 12 | "github.com/go-cinch/common/plugins/kratos/config/env" 13 | _ "github.com/go-cinch/common/plugins/kratos/encoding/yml" 14 | "github.com/go-cinch/common/utils" 15 | "github.com/go-cinch/layout/internal/conf" 16 | "github.com/go-kratos/kratos/v2" 17 | "github.com/go-kratos/kratos/v2/config" 18 | "github.com/go-kratos/kratos/v2/config/file" 19 | "github.com/go-kratos/kratos/v2/middleware/tracing" 20 | "github.com/go-kratos/kratos/v2/transport/grpc" 21 | "github.com/go-kratos/kratos/v2/transport/http" 22 | ) 23 | 24 | // go build -ldflags "-X main.Version=x.y.z" 25 | var ( 26 | // Name is the name of the compiled software. 27 | Name = "layout" 28 | // EnvPrefix is the prefix of the env params 29 | EnvPrefix = "SERVICE" 30 | // Version is the version of the compiled software. 31 | Version string 32 | // flagConf is the config flag. 33 | flagConf string 34 | // beforeReadConfigLogLevel is log level before read config. 35 | beforeReadConfigLogLevel = log.InfoLevel 36 | 37 | id, _ = os.Hostname() 38 | ) 39 | 40 | func init() { 41 | flag.StringVar(&flagConf, "c", "../../configs", "config path, eg: -c config.yml") 42 | } 43 | 44 | func newApp(gs *grpc.Server, hs *http.Server) *kratos.App { 45 | return kratos.New( 46 | kratos.ID(id), 47 | kratos.Name(Name), 48 | kratos.Version(Version), 49 | kratos.Metadata(map[string]string{}), 50 | kratos.Logger(log.DefaultWrapper.Options().Logger()), 51 | kratos.Server( 52 | gs, 53 | hs, 54 | ), 55 | ) 56 | } 57 | 58 | func main() { 59 | flag.Parse() 60 | // set default log before read config 61 | logOps := []func(*log.Options){ 62 | log.WithJSON(true), 63 | log.WithLevel(beforeReadConfigLogLevel), 64 | log.WithValuer("service.id", id), 65 | log.WithValuer("service.name", Name), 66 | log.WithValuer("service.version", Version), 67 | log.WithValuer("trace.id", tracing.TraceID()), 68 | log.WithValuer("span.id", tracing.SpanID()), 69 | log.WithCallerOptions( 70 | caller.WithSource(false), 71 | caller.WithLevel(2), 72 | caller.WithVersion(true), 73 | ), 74 | } 75 | log.DefaultWrapper = log.NewWrapper(logOps...) 76 | c := config.New( 77 | config.WithSource(file.NewSource(flagConf)), 78 | config.WithResolver( 79 | env.NewRevolver( 80 | env.WithPrefix(EnvPrefix), 81 | env.WithLoaded(func(k string, v interface{}) { 82 | log.Info("env loaded: %s=%v", k, v) 83 | }), 84 | ), 85 | ), 86 | ) 87 | defer c.Close() 88 | 89 | fields := log.Fields{ 90 | "conf": flagConf, 91 | } 92 | if err := c.Load(); err != nil { 93 | log. 94 | WithError(err). 95 | WithFields(fields). 96 | Fatal("load conf failed") 97 | } 98 | 99 | var bc conf.Bootstrap 100 | if err := c.Scan(&bc); err != nil { 101 | log. 102 | WithError(err). 103 | WithFields(fields). 104 | Fatal("scan conf failed") 105 | } 106 | bc.Name = Name 107 | bc.Version = Version 108 | // override log level after read config 109 | logOps = append(logOps, 110 | []func(*log.Options){ 111 | log.WithLevel(log.NewLevel(bc.Log.Level)), 112 | log.WithJSON(bc.Log.JSON), 113 | }..., 114 | ) 115 | log.DefaultWrapper = log.NewWrapper(logOps...) 116 | if bc.Server.MachineId == "" { 117 | // if machine id not set, gen from pod ip 118 | machineId, err := pod.MachineID() 119 | if err == nil { 120 | bc.Server.MachineId = strconv.FormatUint(uint64(machineId), 10) 121 | } else { 122 | bc.Server.MachineId = "0" 123 | } 124 | } 125 | // os.Setenv("COPIERX_UTC", "true") 126 | 127 | app, cleanup, err := wireApp(&bc) 128 | if err != nil { 129 | str := utils.Struct2JSON(&bc) 130 | log. 131 | WithError(err). 132 | Error("wire app failed") 133 | // env str maybe very long, log with another line 134 | log. 135 | WithFields(fields). 136 | Fatal(str) 137 | } 138 | defer cleanup() 139 | 140 | // start and wait for stop signal 141 | if err = app.Run(); err != nil { 142 | log. 143 | WithError(err). 144 | WithFields(fields). 145 | Fatal("run app failed") 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /internal/biz/game.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/go-cinch/common/constant" 9 | "github.com/go-cinch/common/copierx" 10 | "github.com/go-cinch/common/page" 11 | "github.com/go-cinch/common/utils" 12 | "github.com/go-cinch/layout/internal/conf" 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | type CreateGame struct { 17 | ID uint64 `json:"id,string"` 18 | Name string `json:"name"` 19 | } 20 | 21 | type Game struct { 22 | ID uint64 `json:"id,string"` 23 | Name string `json:"name"` 24 | } 25 | 26 | type FindGame struct { 27 | Page page.Page `json:"page"` 28 | Name *string `json:"name"` 29 | } 30 | 31 | type FindGameCache struct { 32 | Page page.Page `json:"page"` 33 | List []Game `json:"list"` 34 | } 35 | 36 | type UpdateGame struct { 37 | ID uint64 `json:"id,string"` 38 | Name *string `json:"name,omitempty"` 39 | } 40 | 41 | type GameRepo interface { 42 | Create(ctx context.Context, item *CreateGame) error 43 | Get(ctx context.Context, id uint64) (*Game, error) 44 | Find(ctx context.Context, condition *FindGame) []Game 45 | Update(ctx context.Context, item *UpdateGame) error 46 | Delete(ctx context.Context, ids ...uint64) error 47 | } 48 | 49 | type GameUseCase struct { 50 | c *conf.Bootstrap 51 | repo GameRepo 52 | tx Transaction 53 | cache Cache 54 | } 55 | 56 | func NewGameUseCase(c *conf.Bootstrap, repo GameRepo, tx Transaction, cache Cache) *GameUseCase { 57 | return &GameUseCase{ 58 | c: c, 59 | repo: repo, 60 | tx: tx, 61 | cache: cache.WithPrefix(strings.Join([]string{ 62 | c.Name, "game", 63 | }, "_")), 64 | } 65 | } 66 | 67 | func (uc *GameUseCase) Create(ctx context.Context, item *CreateGame) error { 68 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 69 | return uc.cache.Flush(ctx, func(ctx context.Context) error { 70 | return uc.repo.Create(ctx, item) 71 | }) 72 | }) 73 | } 74 | 75 | func (uc *GameUseCase) Get(ctx context.Context, id uint64) (rp *Game, err error) { 76 | rp = &Game{} 77 | action := strings.Join([]string{"get", strconv.FormatUint(id, 10)}, "_") 78 | str, err := uc.cache.Get(ctx, action, func(ctx context.Context) (string, error) { 79 | return uc.get(ctx, action, id) 80 | }) 81 | if err != nil { 82 | return 83 | } 84 | utils.JSON2Struct(&rp, str) 85 | if rp.ID == constant.UI0 { 86 | err = ErrRecordNotFound(ctx) 87 | return 88 | } 89 | return 90 | } 91 | 92 | func (uc *GameUseCase) get(ctx context.Context, action string, id uint64) (res string, err error) { 93 | // read data from db and write to cache 94 | rp := &Game{} 95 | item, err := uc.repo.Get(ctx, id) 96 | notFound := errors.Is(err, ErrRecordNotFound(ctx)) 97 | if err != nil && !notFound { 98 | return 99 | } 100 | copierx.Copy(&rp, item) 101 | res = utils.Struct2JSON(rp) 102 | uc.cache.Set(ctx, action, res, notFound) 103 | return 104 | } 105 | 106 | func (uc *GameUseCase) Find(ctx context.Context, condition *FindGame) (rp []Game, err error) { 107 | // use md5 string as cache replay json str, key is short 108 | action := strings.Join([]string{"find", utils.StructMd5(condition)}, "_") 109 | str, err := uc.cache.Get(ctx, action, func(ctx context.Context) (string, error) { 110 | return uc.find(ctx, action, condition) 111 | }) 112 | if err != nil { 113 | return 114 | } 115 | var cache FindGameCache 116 | utils.JSON2Struct(&cache, str) 117 | condition.Page = cache.Page 118 | rp = cache.List 119 | return 120 | } 121 | 122 | func (uc *GameUseCase) find(ctx context.Context, action string, condition *FindGame) (res string, err error) { 123 | // read data from db and write to cache 124 | list := uc.repo.Find(ctx, condition) 125 | var cache FindGameCache 126 | cache.List = list 127 | cache.Page = condition.Page 128 | res = utils.Struct2JSON(cache) 129 | uc.cache.Set(ctx, action, res, len(list) == 0) 130 | return 131 | } 132 | 133 | func (uc *GameUseCase) Update(ctx context.Context, item *UpdateGame) error { 134 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 135 | return uc.cache.Flush(ctx, func(ctx context.Context) (err error) { 136 | err = uc.repo.Update(ctx, item) 137 | return 138 | }) 139 | }) 140 | } 141 | 142 | func (uc *GameUseCase) Delete(ctx context.Context, ids ...uint64) error { 143 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 144 | return uc.cache.Flush(ctx, func(ctx context.Context) (err error) { 145 | err = uc.repo.Delete(ctx, ids...) 146 | return 147 | }) 148 | }) 149 | } 150 | -------------------------------------------------------------------------------- /third_party/google/protobuf/wrappers.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Wrappers for primitive (non-message) types. These types are useful 32 | // for embedding primitives in the `google.protobuf.Any` type and for places 33 | // where we need to distinguish between the absence of a primitive 34 | // typed field and its default value. 35 | // 36 | // These wrappers have no meaningful use within repeated fields as they lack 37 | // the ability to detect presence on individual elements. 38 | // These wrappers have no meaningful use within a map or a oneof since 39 | // individual entries of a map or fields of a oneof can already detect presence. 40 | 41 | syntax = "proto3"; 42 | 43 | package google.protobuf; 44 | 45 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 46 | option cc_enable_arenas = true; 47 | option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; 48 | option java_package = "com.google.protobuf"; 49 | option java_outer_classname = "WrappersProto"; 50 | option java_multiple_files = true; 51 | option objc_class_prefix = "GPB"; 52 | 53 | // Wrapper message for `double`. 54 | // 55 | // The JSON representation for `DoubleValue` is JSON number. 56 | message DoubleValue { 57 | // The double value. 58 | double value = 1; 59 | } 60 | 61 | // Wrapper message for `float`. 62 | // 63 | // The JSON representation for `FloatValue` is JSON number. 64 | message FloatValue { 65 | // The float value. 66 | float value = 1; 67 | } 68 | 69 | // Wrapper message for `int64`. 70 | // 71 | // The JSON representation for `Int64Value` is JSON string. 72 | message Int64Value { 73 | // The int64 value. 74 | int64 value = 1; 75 | } 76 | 77 | // Wrapper message for `uint64`. 78 | // 79 | // The JSON representation for `UInt64Value` is JSON string. 80 | message UInt64Value { 81 | // The uint64 value. 82 | uint64 value = 1; 83 | } 84 | 85 | // Wrapper message for `int32`. 86 | // 87 | // The JSON representation for `Int32Value` is JSON number. 88 | message Int32Value { 89 | // The int32 value. 90 | int32 value = 1; 91 | } 92 | 93 | // Wrapper message for `uint32`. 94 | // 95 | // The JSON representation for `UInt32Value` is JSON number. 96 | message UInt32Value { 97 | // The uint32 value. 98 | uint32 value = 1; 99 | } 100 | 101 | // Wrapper message for `bool`. 102 | // 103 | // The JSON representation for `BoolValue` is JSON `true` and `false`. 104 | message BoolValue { 105 | // The bool value. 106 | bool value = 1; 107 | } 108 | 109 | // Wrapper message for `string`. 110 | // 111 | // The JSON representation for `StringValue` is JSON string. 112 | message StringValue { 113 | // The string value. 114 | string value = 1; 115 | } 116 | 117 | // Wrapper message for `bytes`. 118 | // 119 | // The JSON representation for `BytesValue` is JSON string. 120 | message BytesValue { 121 | // The bytes value. 122 | bytes value = 1; 123 | } 124 | -------------------------------------------------------------------------------- /internal/tests/mock/mock.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "runtime/debug" 8 | "sync" 9 | 10 | "github.com/go-cinch/common/mock" 11 | "github.com/go-cinch/layout/internal/biz" 12 | "github.com/go-cinch/layout/internal/conf" 13 | "github.com/go-cinch/layout/internal/data" 14 | "github.com/go-cinch/layout/internal/service" 15 | "github.com/go-kratos/kratos/v2/transport" 16 | ) 17 | 18 | func GameService() (gameService *service.GameService) { 19 | gameUseCase := GameUseCase() 20 | gameService = service.NewGameService(nil, gameUseCase) 21 | return 22 | } 23 | 24 | func GameUseCase() (gameUseCase *biz.GameUseCase) { 25 | c, dataData, cache := Data() 26 | gameRepo := GameRepo() 27 | transaction := data.NewTransaction(dataData) 28 | gameUseCase = biz.NewGameUseCase(c, gameRepo, transaction, cache) 29 | return 30 | } 31 | 32 | func GameRepo() biz.GameRepo { 33 | _, d, _ := Data() 34 | return data.NewGameRepo(d) 35 | } 36 | 37 | type headerCarrier http.Header 38 | 39 | func (hc headerCarrier) Get(key string) string { return http.Header(hc).Get(key) } 40 | 41 | func (hc headerCarrier) Set(key string, value string) { http.Header(hc).Set(key, value) } 42 | 43 | func (hc headerCarrier) Add(key string, value string) { http.Header(hc).Add(key, value) } 44 | 45 | // Keys lists the keys stored in this carrier. 46 | func (hc headerCarrier) Keys() []string { 47 | keys := make([]string, 0, len(hc)) 48 | for k := range http.Header(hc) { 49 | keys = append(keys, k) 50 | } 51 | return keys 52 | } 53 | 54 | // Values returns a slice value associated with the passed key. 55 | func (hc headerCarrier) Values(key string) []string { 56 | return http.Header(hc).Values(key) 57 | } 58 | 59 | func newUserHeader(k, v string) *headerCarrier { 60 | header := &headerCarrier{} 61 | header.Set(k, v) 62 | return header 63 | } 64 | 65 | type Transport struct { 66 | kind transport.Kind 67 | endpoint string 68 | operation string 69 | reqHeader transport.Header 70 | } 71 | 72 | func (tr *Transport) Kind() transport.Kind { 73 | return tr.kind 74 | } 75 | 76 | func (tr *Transport) Endpoint() string { 77 | return tr.endpoint 78 | } 79 | 80 | func (tr *Transport) Operation() string { 81 | return tr.operation 82 | } 83 | 84 | func (tr *Transport) RequestHeader() transport.Header { 85 | return tr.reqHeader 86 | } 87 | 88 | func (*Transport) ReplyHeader() transport.Header { 89 | return nil 90 | } 91 | 92 | func NewContextWithUserId(ctx context.Context, u string) context.Context { 93 | tr := &Transport{ 94 | reqHeader: newUserHeader("X-Md-Global-Code", u), 95 | } 96 | return transport.NewServerContext(ctx, tr) 97 | } 98 | 99 | var ( 100 | onceC *conf.Bootstrap 101 | once sync.Once 102 | ) 103 | 104 | func Data() (c *conf.Bootstrap, dataData *data.Data, cache biz.Cache) { 105 | debug.SetGCPercent(-1) 106 | once.Do(func() { 107 | onceC = MySQLAndRedis() 108 | }) 109 | c = onceC 110 | // os.Setenv("COPIERX_UTC", "true") 111 | 112 | universalClient, err := data.NewRedis(c) 113 | if err != nil { 114 | panic(err) 115 | } 116 | tenant, err := data.NewDB(c) 117 | if err != nil { 118 | panic(err) 119 | } 120 | sonyflake, err := data.NewSonyflake(c) 121 | if err != nil { 122 | panic(err) 123 | } 124 | tracerProvider, err := data.NewTracer(c) 125 | if err != nil { 126 | panic(err) 127 | } 128 | dataData, _ = data.NewData(universalClient, tenant, sonyflake, tracerProvider, nil) 129 | cache = data.NewCache(c, universalClient) 130 | return c, dataData, cache 131 | } 132 | 133 | func MySQLAndRedis() *conf.Bootstrap { 134 | host1, port1, err := mock.NewMySQL() 135 | if err != nil { 136 | panic(err) 137 | } 138 | host2, port2, err := mock.NewRedis() 139 | if err != nil { 140 | panic(err) 141 | } 142 | // host1 = "127.0.0.1" 143 | // port1 = 3306 144 | return &conf.Bootstrap{ 145 | Server: &conf.Server{ 146 | MachineId: "123", 147 | }, 148 | Log: &conf.Log{ 149 | Level: "debug", 150 | JSON: false, 151 | ShowSQL: true, 152 | }, 153 | Data: &conf.Data{ 154 | Database: &conf.Data_Database{ 155 | Endpoint: fmt.Sprintf("%s:%d", host1, port1), 156 | Username: "root", 157 | Password: "passwd", 158 | Schema: "game", 159 | Query: "charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms", 160 | }, 161 | Redis: &conf.Data_Redis{ 162 | Dsn: fmt.Sprintf("redis://%s:%d", host2, port2), 163 | }, 164 | }, 165 | Tracer: &conf.Tracer{ 166 | Enable: true, 167 | Otlp: &conf.Tracer_Otlp{}, 168 | Stdout: &conf.Tracer_Stdout{}, 169 | }, 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /third_party/google/protobuf/duration.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/durationpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "DurationProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Duration represents a signed, fixed-length span of time represented 44 | // as a count of seconds and fractions of seconds at nanosecond 45 | // resolution. It is independent of any calendar and concepts like "day" 46 | // or "month". It is related to Timestamp in that the difference between 47 | // two Timestamp values is a Duration and it can be added or subtracted 48 | // from a Timestamp. Range is approximately +-10,000 years. 49 | // 50 | // # Examples 51 | // 52 | // Example 1: Compute Duration from two Timestamps in pseudo code. 53 | // 54 | // Timestamp start = ...; 55 | // Timestamp end = ...; 56 | // Duration duration = ...; 57 | // 58 | // duration.seconds = end.seconds - start.seconds; 59 | // duration.nanos = end.nanos - start.nanos; 60 | // 61 | // if (duration.seconds < 0 && duration.nanos > 0) { 62 | // duration.seconds += 1; 63 | // duration.nanos -= 1000000000; 64 | // } else if (duration.seconds > 0 && duration.nanos < 0) { 65 | // duration.seconds -= 1; 66 | // duration.nanos += 1000000000; 67 | // } 68 | // 69 | // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. 70 | // 71 | // Timestamp start = ...; 72 | // Duration duration = ...; 73 | // Timestamp end = ...; 74 | // 75 | // end.seconds = start.seconds + duration.seconds; 76 | // end.nanos = start.nanos + duration.nanos; 77 | // 78 | // if (end.nanos < 0) { 79 | // end.seconds -= 1; 80 | // end.nanos += 1000000000; 81 | // } else if (end.nanos >= 1000000000) { 82 | // end.seconds += 1; 83 | // end.nanos -= 1000000000; 84 | // } 85 | // 86 | // Example 3: Compute Duration from datetime.timedelta in Python. 87 | // 88 | // td = datetime.timedelta(days=3, minutes=10) 89 | // duration = Duration() 90 | // duration.FromTimedelta(td) 91 | // 92 | // # JSON Mapping 93 | // 94 | // In JSON format, the Duration type is encoded as a string rather than an 95 | // object, where the string ends in the suffix "s" (indicating seconds) and 96 | // is preceded by the number of seconds, with nanoseconds expressed as 97 | // fractional seconds. For example, 3 seconds with 0 nanoseconds should be 98 | // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should 99 | // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 100 | // microsecond should be expressed in JSON format as "3.000001s". 101 | // 102 | // 103 | message Duration { 104 | // Signed seconds of the span of time. Must be from -315,576,000,000 105 | // to +315,576,000,000 inclusive. Note: these bounds are computed from: 106 | // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years 107 | int64 seconds = 1; 108 | 109 | // Signed fractions of a second at nanosecond resolution of the span 110 | // of time. Durations less than one second are represented with a 0 111 | // `seconds` field and a positive or negative `nanos` field. For durations 112 | // of one second or more, a non-zero value for the `nanos` field must be 113 | // of the same sign as the `seconds` field. Must be from -999,999,999 114 | // to +999,999,999 inclusive. 115 | int32 nanos = 2; 116 | } 117 | -------------------------------------------------------------------------------- /internal/data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/url" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/go-cinch/common/id" 11 | "github.com/go-cinch/common/log" 12 | glog "github.com/go-cinch/common/plugins/gorm/log" 13 | "github.com/go-cinch/common/plugins/gorm/tenant" 14 | "github.com/go-cinch/common/utils" 15 | "github.com/go-cinch/layout/api/auth" 16 | "github.com/go-cinch/layout/internal/biz" 17 | "github.com/go-cinch/layout/internal/conf" 18 | "github.com/go-cinch/layout/internal/db" 19 | "github.com/google/wire" 20 | "github.com/pkg/errors" 21 | "github.com/redis/go-redis/v9" 22 | "go.opentelemetry.io/otel/sdk/trace" 23 | "gorm.io/gorm" 24 | "gorm.io/gorm/schema" 25 | ) 26 | 27 | // ProviderSet is data providers. 28 | var ProviderSet = wire.NewSet( 29 | NewRedis, NewDB, NewSonyflake, NewTracer, NewData, NewTransaction, NewCache, 30 | NewAuthClient, 31 | NewGameRepo, 32 | ) 33 | 34 | // Data . 35 | type Data struct { 36 | tenant *tenant.Tenant 37 | redis redis.UniversalClient 38 | sonyflake *id.Sonyflake 39 | 40 | auth auth.AuthClient 41 | } 42 | 43 | // NewData . 44 | func NewData( 45 | redis redis.UniversalClient, 46 | gormTenant *tenant.Tenant, 47 | sonyflake *id.Sonyflake, 48 | tp *trace.TracerProvider, 49 | auth auth.AuthClient, 50 | ) (d *Data, cleanup func()) { 51 | d = &Data{ 52 | redis: redis, 53 | tenant: gormTenant, 54 | sonyflake: sonyflake, 55 | auth: auth, 56 | } 57 | cleanup = func() { 58 | if tp != nil { 59 | tp.Shutdown(context.Background()) 60 | } 61 | log.Info("clean data") 62 | } 63 | return 64 | } 65 | 66 | type contextTxKey struct{} 67 | 68 | // Tx is transaction wrapper 69 | func (d *Data) Tx(ctx context.Context, handler func(ctx context.Context) error) error { 70 | return d.tenant.DB(ctx).Transaction(func(tx *gorm.DB) error { 71 | ctx = context.WithValue(ctx, contextTxKey{}, tx) 72 | return handler(ctx) 73 | }) 74 | } 75 | 76 | // DB can get tx from ctx, if not exist return db 77 | func (d *Data) DB(ctx context.Context) *gorm.DB { 78 | tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB) 79 | if ok { 80 | return tx 81 | } 82 | return d.tenant.DB(ctx) 83 | } 84 | 85 | // HiddenSQL return a hidden sql ctx 86 | func (*Data) HiddenSQL(ctx context.Context) context.Context { 87 | ctx = glog.NewHiddenSQLContext(ctx) 88 | return ctx 89 | } 90 | 91 | // Cache can get cache instance 92 | func (d *Data) Cache() redis.UniversalClient { 93 | return d.redis 94 | } 95 | 96 | // ID can get unique id 97 | func (d *Data) ID(ctx context.Context) uint64 { 98 | return d.sonyflake.ID(ctx) 99 | } 100 | 101 | // NewTransaction . 102 | func NewTransaction(d *Data) biz.Transaction { 103 | return d 104 | } 105 | 106 | // NewRedis is initialize redis connection from config 107 | func NewRedis(c *conf.Bootstrap) (client redis.UniversalClient, err error) { 108 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 109 | defer cancel() 110 | var u *url.URL 111 | u, err = url.Parse(c.Data.Redis.Dsn) 112 | if err != nil { 113 | log.Error(err) 114 | err = errors.New("initialize redis failed") 115 | return 116 | } 117 | u.User = url.UserPassword(u.User.Username(), "***") 118 | showDsn, _ := url.PathUnescape(u.String()) 119 | client, err = utils.ParseRedisURI(c.Data.Redis.Dsn) 120 | if err != nil { 121 | log.Error(err) 122 | err = errors.New("initialize redis failed") 123 | return 124 | } 125 | err = client.Ping(ctx).Err() 126 | if err != nil { 127 | log.Error(err) 128 | err = errors.New("initialize redis failed") 129 | return 130 | } 131 | log. 132 | WithField("redis.dsn", showDsn). 133 | Info("initialize redis success") 134 | return 135 | } 136 | 137 | // NewDB is initialize db connection from config 138 | func NewDB(c *conf.Bootstrap) (gormTenant *tenant.Tenant, err error) { 139 | ops := make([]func(*tenant.Options), 0, len(c.Data.Database.Tenants)+3) 140 | if len(c.Data.Database.Tenants) > 0 { 141 | for k, v := range c.Data.Database.Tenants { 142 | ops = append(ops, tenant.WithDSN(k, v)) 143 | } 144 | } else { 145 | dsn := c.Data.Database.Dsn 146 | if dsn == "" { 147 | dsn = fmt.Sprintf( 148 | "%s:%s@tcp(%s)/%s?%s", 149 | c.Data.Database.Username, 150 | c.Data.Database.Password, 151 | c.Data.Database.Endpoint, 152 | c.Data.Database.Schema, 153 | c.Data.Database.Query, 154 | ) 155 | } 156 | ops = append(ops, tenant.WithDSN("", dsn)) 157 | } 158 | ops = append(ops, tenant.WithSQLFile(db.SQLFiles)) 159 | ops = append(ops, tenant.WithSQLRoot(db.SQLRoot)) 160 | 161 | level := log.NewLevel(c.Log.Level) 162 | // force to warn level when show sql is false 163 | if level > log.WarnLevel && !c.Log.ShowSQL { 164 | level = log.WarnLevel 165 | } 166 | ops = append(ops, tenant.WithConfig(&gorm.Config{ 167 | NamingStrategy: schema.NamingStrategy{ 168 | SingularTable: true, 169 | }, 170 | QueryFields: true, 171 | Logger: glog.New( 172 | glog.WithColorful(false), 173 | glog.WithSlow(200), 174 | glog.WithLevel(level), 175 | ), 176 | })) 177 | 178 | gormTenant, err = tenant.New(ops...) 179 | if err != nil { 180 | log.Error(err) 181 | err = errors.New("initialize db failed") 182 | return 183 | } 184 | err = gormTenant.Migrate() 185 | if err != nil { 186 | log.Error(err) 187 | err = errors.New("initialize db failed") 188 | return 189 | } 190 | log.Info("initialize db success") 191 | return 192 | } 193 | 194 | // NewSonyflake is initialize sonyflake id generator 195 | func NewSonyflake(c *conf.Bootstrap) (sf *id.Sonyflake, err error) { 196 | machineID, _ := strconv.ParseUint(c.Server.MachineId, 10, 16) 197 | sf = id.NewSonyflake( 198 | id.WithSonyflakeMachineID(uint16(machineID)), 199 | id.WithSonyflakeStartTime(time.Date(100, 10, 10, 0, 0, 0, 0, time.UTC)), 200 | ) 201 | if sf.Error != nil { 202 | log.Error(sf.Error) 203 | err = errors.New("initialize sonyflake failed") 204 | return 205 | } 206 | log. 207 | WithField("machine.id", machineID). 208 | Info("initialize sonyflake success") 209 | return 210 | } 211 | -------------------------------------------------------------------------------- /api/reason/reason.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.32.0 4 | // protoc v4.25.0 5 | // source: reason-proto/reason.proto 6 | 7 | package reason 8 | 9 | import ( 10 | _ "github.com/go-kratos/kratos/v2/errors" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type ErrorReason int32 25 | 26 | const ( 27 | ErrorReason_INTERNAL ErrorReason = 0 28 | ErrorReason_TOO_MANY_REQUESTS ErrorReason = 1 29 | ErrorReason_ILLEGAL_PARAMETER ErrorReason = 2 30 | ErrorReason_NOT_FOUND ErrorReason = 3 31 | ErrorReason_UNAUTHORIZED ErrorReason = 4 32 | ErrorReason_FORBIDDEN ErrorReason = 5 33 | ) 34 | 35 | // Enum value maps for ErrorReason. 36 | var ( 37 | ErrorReason_name = map[int32]string{ 38 | 0: "INTERNAL", 39 | 1: "TOO_MANY_REQUESTS", 40 | 2: "ILLEGAL_PARAMETER", 41 | 3: "NOT_FOUND", 42 | 4: "UNAUTHORIZED", 43 | 5: "FORBIDDEN", 44 | } 45 | ErrorReason_value = map[string]int32{ 46 | "INTERNAL": 0, 47 | "TOO_MANY_REQUESTS": 1, 48 | "ILLEGAL_PARAMETER": 2, 49 | "NOT_FOUND": 3, 50 | "UNAUTHORIZED": 4, 51 | "FORBIDDEN": 5, 52 | } 53 | ) 54 | 55 | func (x ErrorReason) Enum() *ErrorReason { 56 | p := new(ErrorReason) 57 | *p = x 58 | return p 59 | } 60 | 61 | func (x ErrorReason) String() string { 62 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 63 | } 64 | 65 | func (ErrorReason) Descriptor() protoreflect.EnumDescriptor { 66 | return file_reason_proto_reason_proto_enumTypes[0].Descriptor() 67 | } 68 | 69 | func (ErrorReason) Type() protoreflect.EnumType { 70 | return &file_reason_proto_reason_proto_enumTypes[0] 71 | } 72 | 73 | func (x ErrorReason) Number() protoreflect.EnumNumber { 74 | return protoreflect.EnumNumber(x) 75 | } 76 | 77 | // Deprecated: Use ErrorReason.Descriptor instead. 78 | func (ErrorReason) EnumDescriptor() ([]byte, []int) { 79 | return file_reason_proto_reason_proto_rawDescGZIP(), []int{0} 80 | } 81 | 82 | var File_reason_proto_reason_proto protoreflect.FileDescriptor 83 | 84 | var file_reason_proto_reason_proto_rawDesc = []byte{ 85 | 0x0a, 0x19, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 86 | 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x72, 0x65, 0x61, 87 | 0x73, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x13, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2f, 0x65, 88 | 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2a, 0x9d, 0x01, 0x0a, 0x0b, 89 | 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x08, 0x49, 90 | 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x00, 0x1a, 0x04, 0xa8, 0x45, 0xf4, 0x03, 0x12, 91 | 0x1b, 0x0a, 0x11, 0x54, 0x4f, 0x4f, 0x5f, 0x4d, 0x41, 0x4e, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 92 | 0x45, 0x53, 0x54, 0x53, 0x10, 0x01, 0x1a, 0x04, 0xa8, 0x45, 0xad, 0x03, 0x12, 0x1b, 0x0a, 0x11, 93 | 0x49, 0x4c, 0x4c, 0x45, 0x47, 0x41, 0x4c, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 94 | 0x52, 0x10, 0x02, 0x1a, 0x04, 0xa8, 0x45, 0x90, 0x03, 0x12, 0x13, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 95 | 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x03, 0x1a, 0x04, 0xa8, 0x45, 0x90, 0x03, 0x12, 0x16, 96 | 0x0a, 0x0c, 0x55, 0x4e, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x04, 97 | 0x1a, 0x04, 0xa8, 0x45, 0x91, 0x03, 0x12, 0x13, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 98 | 0x44, 0x45, 0x4e, 0x10, 0x05, 0x1a, 0x04, 0xa8, 0x45, 0x93, 0x03, 0x42, 0x30, 0x0a, 0x09, 0x72, 99 | 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x50, 0x01, 0x5a, 0x13, 0x2e, 0x2f, 0x61, 0x70, 100 | 0x69, 0x2f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x3b, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0xa2, 101 | 0x02, 0x0b, 0x41, 0x50, 0x49, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x56, 0x31, 0x62, 0x06, 0x70, 102 | 0x72, 0x6f, 0x74, 0x6f, 0x33, 103 | } 104 | 105 | var ( 106 | file_reason_proto_reason_proto_rawDescOnce sync.Once 107 | file_reason_proto_reason_proto_rawDescData = file_reason_proto_reason_proto_rawDesc 108 | ) 109 | 110 | func file_reason_proto_reason_proto_rawDescGZIP() []byte { 111 | file_reason_proto_reason_proto_rawDescOnce.Do(func() { 112 | file_reason_proto_reason_proto_rawDescData = protoimpl.X.CompressGZIP(file_reason_proto_reason_proto_rawDescData) 113 | }) 114 | return file_reason_proto_reason_proto_rawDescData 115 | } 116 | 117 | var file_reason_proto_reason_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 118 | var file_reason_proto_reason_proto_goTypes = []interface{}{ 119 | (ErrorReason)(0), // 0: reason.v1.ErrorReason 120 | } 121 | var file_reason_proto_reason_proto_depIdxs = []int32{ 122 | 0, // [0:0] is the sub-list for method output_type 123 | 0, // [0:0] is the sub-list for method input_type 124 | 0, // [0:0] is the sub-list for extension type_name 125 | 0, // [0:0] is the sub-list for extension extendee 126 | 0, // [0:0] is the sub-list for field type_name 127 | } 128 | 129 | func init() { file_reason_proto_reason_proto_init() } 130 | func file_reason_proto_reason_proto_init() { 131 | if File_reason_proto_reason_proto != nil { 132 | return 133 | } 134 | type x struct{} 135 | out := protoimpl.TypeBuilder{ 136 | File: protoimpl.DescBuilder{ 137 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 138 | RawDescriptor: file_reason_proto_reason_proto_rawDesc, 139 | NumEnums: 1, 140 | NumMessages: 0, 141 | NumExtensions: 0, 142 | NumServices: 0, 143 | }, 144 | GoTypes: file_reason_proto_reason_proto_goTypes, 145 | DependencyIndexes: file_reason_proto_reason_proto_depIdxs, 146 | EnumInfos: file_reason_proto_reason_proto_enumTypes, 147 | }.Build() 148 | File_reason_proto_reason_proto = out.File 149 | file_reason_proto_reason_proto_rawDesc = nil 150 | file_reason_proto_reason_proto_goTypes = nil 151 | file_reason_proto_reason_proto_depIdxs = nil 152 | } 153 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-cinch/layout 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/bsm/redislock v0.9.4 7 | github.com/envoyproxy/protoc-gen-validate v1.1.0 8 | github.com/go-cinch/common/constant v1.0.5 9 | github.com/go-cinch/common/copierx v1.0.4 10 | github.com/go-cinch/common/i18n v1.0.7 11 | github.com/go-cinch/common/id v1.0.6 12 | github.com/go-cinch/common/idempotent v1.1.0 13 | github.com/go-cinch/common/log v1.2.0 14 | github.com/go-cinch/common/middleware/i18n v1.0.6 15 | github.com/go-cinch/common/middleware/logging v1.0.1 16 | github.com/go-cinch/common/middleware/tenant v1.0.2 17 | github.com/go-cinch/common/middleware/trace v1.0.3 18 | github.com/go-cinch/common/mock v1.0.1 19 | github.com/go-cinch/common/page v1.0.5 20 | github.com/go-cinch/common/plugins/gorm/filter v1.0.3 21 | github.com/go-cinch/common/plugins/gorm/log v1.0.5 22 | github.com/go-cinch/common/plugins/gorm/tenant v1.0.3 23 | github.com/go-cinch/common/plugins/k8s/pod v1.0.1 24 | github.com/go-cinch/common/plugins/kratos/config/env v1.0.3 25 | github.com/go-cinch/common/plugins/kratos/encoding/yml v1.0.2 26 | github.com/go-cinch/common/proto/params v1.0.1 27 | github.com/go-cinch/common/utils v1.0.5 28 | github.com/go-cinch/common/worker v1.1.0 29 | github.com/go-kratos/kratos/v2 v2.8.3 30 | github.com/golang-module/carbon/v2 v2.3.12 31 | github.com/google/gnostic v0.7.0 32 | github.com/google/uuid v1.6.0 33 | github.com/google/wire v0.6.0 34 | github.com/patrickmn/go-cache v2.1.0+incompatible 35 | github.com/pkg/errors v0.9.1 36 | github.com/redis/go-redis/v9 v9.7.0 37 | go.opentelemetry.io/otel v1.34.0 38 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 39 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 40 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 41 | go.opentelemetry.io/otel/sdk v1.34.0 42 | golang.org/x/text v0.21.0 43 | google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f 44 | google.golang.org/grpc v1.69.4 45 | google.golang.org/protobuf v1.36.5 46 | gorm.io/gen v0.3.26 47 | gorm.io/gorm v1.25.9 48 | gorm.io/plugin/dbresolver v1.5.0 49 | ) 50 | 51 | require ( 52 | dario.cat/mergo v1.0.0 // indirect 53 | github.com/BurntSushi/toml v1.3.2 // indirect 54 | github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect 55 | github.com/alicebob/miniredis/v2 v2.31.1 // indirect 56 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 57 | github.com/cespare/xxhash v1.1.0 // indirect 58 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 59 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 60 | github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect 61 | github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e // indirect 62 | github.com/dolthub/go-mysql-server v0.17.0 // indirect 63 | github.com/dolthub/jsonpath v0.0.2-0.20230525180605-8dc13778fd72 // indirect 64 | github.com/dolthub/vitess v0.0.0-20230823204737-4a21a94e90c3 // indirect 65 | github.com/fsnotify/fsnotify v1.6.0 // indirect 66 | github.com/go-cinch/common/migrate v1.0.5 // indirect 67 | github.com/go-cinch/common/queue/stream v1.0.0 // indirect 68 | github.com/go-gorp/gorp/v3 v3.1.0 // indirect 69 | github.com/go-kit/kit v0.10.0 // indirect 70 | github.com/go-kratos/aegis v0.2.0 // indirect 71 | github.com/go-logr/logr v1.4.2 // indirect 72 | github.com/go-logr/stdr v1.2.2 // indirect 73 | github.com/go-ole/go-ole v1.2.6 // indirect 74 | github.com/go-playground/form/v4 v4.2.1 // indirect 75 | github.com/go-sql-driver/mysql v1.7.1 // indirect 76 | github.com/gocraft/dbr/v2 v2.7.2 // indirect 77 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect 78 | github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 // indirect 79 | github.com/gorilla/mux v1.8.1 // indirect 80 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect 81 | github.com/hashicorp/golang-lru v0.5.4 // indirect 82 | github.com/hibiken/asynq v0.25.1 // indirect 83 | github.com/jinzhu/copier v0.4.0 // indirect 84 | github.com/jinzhu/inflection v1.0.0 // indirect 85 | github.com/jinzhu/now v1.1.5 // indirect 86 | github.com/lestrrat-go/strftime v1.0.4 // indirect 87 | github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect 88 | github.com/mitchellh/hashstructure v1.1.0 // indirect 89 | github.com/nicksnyder/go-i18n/v2 v2.2.1 // indirect 90 | github.com/paulbellamy/ratecounter v0.2.0 // indirect 91 | github.com/pmezard/go-difflib v1.0.0 // indirect 92 | github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect 93 | github.com/r3labs/diff/v3 v3.0.1 // indirect 94 | github.com/robfig/cron/v3 v3.0.1 // indirect 95 | github.com/rubenv/sql-migrate v1.5.1 // indirect 96 | github.com/samber/lo v1.49.1 // indirect 97 | github.com/shirou/gopsutil/v3 v3.23.6 // indirect 98 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 99 | github.com/shopspring/decimal v1.3.1 // indirect 100 | github.com/sirupsen/logrus v1.9.3 // indirect 101 | github.com/sony/sonyflake v1.1.0 // indirect 102 | github.com/spf13/cast v1.7.0 // indirect 103 | github.com/tetratelabs/wazero v1.1.0 // indirect 104 | github.com/tidwall/gjson v1.14.4 // indirect 105 | github.com/tidwall/match v1.1.1 // indirect 106 | github.com/tidwall/pretty v1.2.1 // indirect 107 | github.com/tidwall/sjson v1.2.5 // indirect 108 | github.com/tklauser/go-sysconf v0.3.11 // indirect 109 | github.com/tklauser/numcpus v0.6.1 // indirect 110 | github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect 111 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 112 | github.com/yuin/gopher-lua v1.1.0 // indirect 113 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 114 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 115 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 116 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 117 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 118 | golang.org/x/mod v0.17.0 // indirect 119 | golang.org/x/net v0.34.0 // indirect 120 | golang.org/x/sync v0.10.0 // indirect 121 | golang.org/x/sys v0.29.0 // indirect 122 | golang.org/x/time v0.8.0 // indirect 123 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 124 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect 125 | gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect 126 | gopkg.in/yaml.v3 v3.0.1 // indirect 127 | gorm.io/datatypes v1.2.0 // indirect 128 | gorm.io/driver/mysql v1.5.1 // indirect 129 | gorm.io/hints v1.1.2 // indirect 130 | ) 131 | -------------------------------------------------------------------------------- /third_party/google/protobuf/any.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "google.golang.org/protobuf/types/known/anypb"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "AnyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | 42 | // `Any` contains an arbitrary serialized protocol buffer message along with a 43 | // URL that describes the type of the serialized message. 44 | // 45 | // Protobuf library provides support to pack/unpack Any values in the form 46 | // of utility functions or additional generated methods of the Any type. 47 | // 48 | // Example 1: Pack and unpack a message in C++. 49 | // 50 | // Foo foo = ...; 51 | // Any any; 52 | // any.PackFrom(foo); 53 | // ... 54 | // if (any.UnpackTo(&foo)) { 55 | // ... 56 | // } 57 | // 58 | // Example 2: Pack and unpack a message in Java. 59 | // 60 | // Foo foo = ...; 61 | // Any any = Any.pack(foo); 62 | // ... 63 | // if (any.is(Foo.class)) { 64 | // foo = any.unpack(Foo.class); 65 | // } 66 | // 67 | // Example 3: Pack and unpack a message in Python. 68 | // 69 | // foo = Foo(...) 70 | // any = Any() 71 | // any.Pack(foo) 72 | // ... 73 | // if any.Is(Foo.DESCRIPTOR): 74 | // any.Unpack(foo) 75 | // ... 76 | // 77 | // Example 4: Pack and unpack a message in Go 78 | // 79 | // foo := &pb.Foo{...} 80 | // any, err := anypb.New(foo) 81 | // if err != nil { 82 | // ... 83 | // } 84 | // ... 85 | // foo := &pb.Foo{} 86 | // if err := any.UnmarshalTo(foo); err != nil { 87 | // ... 88 | // } 89 | // 90 | // The pack methods provided by protobuf library will by default use 91 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack 92 | // methods only use the fully qualified type name after the last '/' 93 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type 94 | // name "y.z". 95 | // 96 | // 97 | // JSON 98 | // 99 | // The JSON representation of an `Any` value uses the regular 100 | // representation of the deserialized, embedded message, with an 101 | // additional field `@type` which contains the type URL. Example: 102 | // 103 | // package google.profile; 104 | // message Person { 105 | // string first_name = 1; 106 | // string last_name = 2; 107 | // } 108 | // 109 | // { 110 | // "@type": "type.googleapis.com/google.profile.Person", 111 | // "firstName": , 112 | // "lastName": 113 | // } 114 | // 115 | // If the embedded message type is well-known and has a custom JSON 116 | // representation, that representation will be embedded adding a field 117 | // `value` which holds the custom JSON in addition to the `@type` 118 | // field. Example (for message [google.protobuf.Duration][]): 119 | // 120 | // { 121 | // "@type": "type.googleapis.com/google.protobuf.Duration", 122 | // "value": "1.212s" 123 | // } 124 | // 125 | message Any { 126 | // A URL/resource name that uniquely identifies the type of the serialized 127 | // protocol buffer message. This string must contain at least 128 | // one "/" character. The last segment of the URL's path must represent 129 | // the fully qualified name of the type (as in 130 | // `path/google.protobuf.Duration`). The name should be in a canonical form 131 | // (e.g., leading "." is not accepted). 132 | // 133 | // In practice, teams usually precompile into the binary all types that they 134 | // expect it to use in the context of Any. However, for URLs which use the 135 | // scheme `http`, `https`, or no scheme, one can optionally set up a type 136 | // server that maps type URLs to message definitions as follows: 137 | // 138 | // * If no scheme is provided, `https` is assumed. 139 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][] 140 | // value in binary format, or produce an error. 141 | // * Applications are allowed to cache lookup results based on the 142 | // URL, or have them precompiled into a binary to avoid any 143 | // lookup. Therefore, binary compatibility needs to be preserved 144 | // on changes to types. (Use versioned type names to manage 145 | // breaking changes.) 146 | // 147 | // Note: this functionality is not currently available in the official 148 | // protobuf release, and it is not used for type URLs beginning with 149 | // type.googleapis.com. 150 | // 151 | // Schemes other than `http`, `https` (or the empty scheme) might be 152 | // used with implementation specific semantics. 153 | // 154 | string type_url = 1; 155 | 156 | // Must be a valid serialized protocol buffer of the above specified type. 157 | bytes value = 2; 158 | } 159 | -------------------------------------------------------------------------------- /third_party/google/protobuf/type.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | import "google/protobuf/any.proto"; 36 | import "google/protobuf/source_context.proto"; 37 | 38 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 39 | option cc_enable_arenas = true; 40 | option java_package = "com.google.protobuf"; 41 | option java_outer_classname = "TypeProto"; 42 | option java_multiple_files = true; 43 | option objc_class_prefix = "GPB"; 44 | option go_package = "google.golang.org/protobuf/types/known/typepb"; 45 | 46 | // A protocol buffer message type. 47 | message Type { 48 | // The fully qualified message name. 49 | string name = 1; 50 | // The list of fields. 51 | repeated Field fields = 2; 52 | // The list of types appearing in `oneof` definitions in this type. 53 | repeated string oneofs = 3; 54 | // The protocol buffer options. 55 | repeated Option options = 4; 56 | // The source context. 57 | SourceContext source_context = 5; 58 | // The source syntax. 59 | Syntax syntax = 6; 60 | } 61 | 62 | // A single field of a message type. 63 | message Field { 64 | // Basic field types. 65 | enum Kind { 66 | // Field type unknown. 67 | TYPE_UNKNOWN = 0; 68 | // Field type double. 69 | TYPE_DOUBLE = 1; 70 | // Field type float. 71 | TYPE_FLOAT = 2; 72 | // Field type int64. 73 | TYPE_INT64 = 3; 74 | // Field type uint64. 75 | TYPE_UINT64 = 4; 76 | // Field type int32. 77 | TYPE_INT32 = 5; 78 | // Field type fixed64. 79 | TYPE_FIXED64 = 6; 80 | // Field type fixed32. 81 | TYPE_FIXED32 = 7; 82 | // Field type bool. 83 | TYPE_BOOL = 8; 84 | // Field type string. 85 | TYPE_STRING = 9; 86 | // Field type group. Proto2 syntax only, and deprecated. 87 | TYPE_GROUP = 10; 88 | // Field type message. 89 | TYPE_MESSAGE = 11; 90 | // Field type bytes. 91 | TYPE_BYTES = 12; 92 | // Field type uint32. 93 | TYPE_UINT32 = 13; 94 | // Field type enum. 95 | TYPE_ENUM = 14; 96 | // Field type sfixed32. 97 | TYPE_SFIXED32 = 15; 98 | // Field type sfixed64. 99 | TYPE_SFIXED64 = 16; 100 | // Field type sint32. 101 | TYPE_SINT32 = 17; 102 | // Field type sint64. 103 | TYPE_SINT64 = 18; 104 | } 105 | 106 | // Whether a field is optional, required, or repeated. 107 | enum Cardinality { 108 | // For fields with unknown cardinality. 109 | CARDINALITY_UNKNOWN = 0; 110 | // For optional fields. 111 | CARDINALITY_OPTIONAL = 1; 112 | // For required fields. Proto2 syntax only. 113 | CARDINALITY_REQUIRED = 2; 114 | // For repeated fields. 115 | CARDINALITY_REPEATED = 3; 116 | } 117 | 118 | // The field type. 119 | Kind kind = 1; 120 | // The field cardinality. 121 | Cardinality cardinality = 2; 122 | // The field number. 123 | int32 number = 3; 124 | // The field name. 125 | string name = 4; 126 | // The field type URL, without the scheme, for message or enumeration 127 | // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. 128 | string type_url = 6; 129 | // The index of the field type in `Type.oneofs`, for message or enumeration 130 | // types. The first type has index 1; zero means the type is not in the list. 131 | int32 oneof_index = 7; 132 | // Whether to use alternative packed wire representation. 133 | bool packed = 8; 134 | // The protocol buffer options. 135 | repeated Option options = 9; 136 | // The field JSON name. 137 | string json_name = 10; 138 | // The string value of the default value of this field. Proto2 syntax only. 139 | string default_value = 11; 140 | } 141 | 142 | // Enum type definition. 143 | message Enum { 144 | // Enum type name. 145 | string name = 1; 146 | // Enum value definitions. 147 | repeated EnumValue enumvalue = 2; 148 | // Protocol buffer options. 149 | repeated Option options = 3; 150 | // The source context. 151 | SourceContext source_context = 4; 152 | // The source syntax. 153 | Syntax syntax = 5; 154 | } 155 | 156 | // Enum value definition. 157 | message EnumValue { 158 | // Enum value name. 159 | string name = 1; 160 | // Enum value number. 161 | int32 number = 2; 162 | // Protocol buffer options. 163 | repeated Option options = 3; 164 | } 165 | 166 | // A protocol buffer option, which can be attached to a message, field, 167 | // enumeration, etc. 168 | message Option { 169 | // The option's name. For protobuf built-in options (options defined in 170 | // descriptor.proto), this is the short name. For example, `"map_entry"`. 171 | // For custom options, it should be the fully-qualified name. For example, 172 | // `"google.api.http"`. 173 | string name = 1; 174 | // The option's value packed in an Any message. If the value is a primitive, 175 | // the corresponding wrapper type defined in google/protobuf/wrappers.proto 176 | // should be used. If the value is an enum, it should be stored as an int32 177 | // value using the google.protobuf.Int32Value type. 178 | Any value = 2; 179 | } 180 | 181 | // The syntax in which a protocol buffer element is defined. 182 | enum Syntax { 183 | // Syntax `proto2`. 184 | SYNTAX_PROTO2 = 0; 185 | // Syntax `proto3`. 186 | SYNTAX_PROTO3 = 1; 187 | } 188 | -------------------------------------------------------------------------------- /third_party/google/protobuf/timestamp.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/timestamppb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "TimestampProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Timestamp represents a point in time independent of any time zone or local 44 | // calendar, encoded as a count of seconds and fractions of seconds at 45 | // nanosecond resolution. The count is relative to an epoch at UTC midnight on 46 | // January 1, 1970, in the proleptic Gregorian calendar which extends the 47 | // Gregorian calendar backwards to year one. 48 | // 49 | // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap 50 | // second table is needed for interpretation, using a [24-hour linear 51 | // smear](https://developers.google.com/time/smear). 52 | // 53 | // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By 54 | // restricting to that range, we ensure that we can convert to and from [RFC 55 | // 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. 56 | // 57 | // # Examples 58 | // 59 | // Example 1: Compute Timestamp from POSIX `time()`. 60 | // 61 | // Timestamp timestamp; 62 | // timestamp.set_seconds(time(NULL)); 63 | // timestamp.set_nanos(0); 64 | // 65 | // Example 2: Compute Timestamp from POSIX `gettimeofday()`. 66 | // 67 | // struct timeval tv; 68 | // gettimeofday(&tv, NULL); 69 | // 70 | // Timestamp timestamp; 71 | // timestamp.set_seconds(tv.tv_sec); 72 | // timestamp.set_nanos(tv.tv_usec * 1000); 73 | // 74 | // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. 75 | // 76 | // FILETIME ft; 77 | // GetSystemTimeAsFileTime(&ft); 78 | // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 79 | // 80 | // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z 81 | // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. 82 | // Timestamp timestamp; 83 | // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); 84 | // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); 85 | // 86 | // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. 87 | // 88 | // long millis = System.currentTimeMillis(); 89 | // 90 | // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) 91 | // .setNanos((int) ((millis % 1000) * 1000000)).build(); 92 | // 93 | // 94 | // Example 5: Compute Timestamp from Java `Instant.now()`. 95 | // 96 | // Instant now = Instant.now(); 97 | // 98 | // Timestamp timestamp = 99 | // Timestamp.newBuilder().setSeconds(now.getEpochSecond()) 100 | // .setNanos(now.getNano()).build(); 101 | // 102 | // 103 | // Example 6: Compute Timestamp from current time in Python. 104 | // 105 | // timestamp = Timestamp() 106 | // timestamp.GetCurrentTime() 107 | // 108 | // # JSON Mapping 109 | // 110 | // In JSON format, the Timestamp type is encoded as a string in the 111 | // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the 112 | // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" 113 | // where {year} is always expressed using four digits while {month}, {day}, 114 | // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional 115 | // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), 116 | // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone 117 | // is required. A proto3 JSON serializer should always use UTC (as indicated by 118 | // "Z") when printing the Timestamp type and a proto3 JSON parser should be 119 | // able to accept both UTC and other timezones (as indicated by an offset). 120 | // 121 | // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 122 | // 01:30 UTC on January 15, 2017. 123 | // 124 | // In JavaScript, one can convert a Date object to this format using the 125 | // standard 126 | // [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) 127 | // method. In Python, a standard `datetime.datetime` object can be converted 128 | // to this format using 129 | // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with 130 | // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use 131 | // the Joda Time's [`ISODateTimeFormat.dateTime()`]( 132 | // http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D 133 | // ) to obtain a formatter capable of generating timestamps in this format. 134 | // 135 | // 136 | message Timestamp { 137 | // Represents seconds of UTC time since Unix epoch 138 | // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 139 | // 9999-12-31T23:59:59Z inclusive. 140 | int64 seconds = 1; 141 | 142 | // Non-negative fractions of a second at nanosecond resolution. Negative 143 | // second values with fractions must still have non-negative nanos values 144 | // that count forward in time. Must be from 0 to 999,999,999 145 | // inclusive. 146 | int32 nanos = 2; 147 | } 148 | -------------------------------------------------------------------------------- /internal/data/cache.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "math/rand" 7 | "strings" 8 | "time" 9 | 10 | "github.com/bsm/redislock" 11 | "github.com/go-cinch/common/log" 12 | "github.com/go-cinch/common/plugins/gorm/tenant" 13 | "github.com/go-cinch/layout/internal/biz" 14 | "github.com/go-cinch/layout/internal/conf" 15 | "github.com/patrickmn/go-cache" 16 | "github.com/redis/go-redis/v9" 17 | "go.opentelemetry.io/otel" 18 | "go.opentelemetry.io/otel/codes" 19 | ) 20 | 21 | // Cache . 22 | type Cache struct { 23 | redis redis.UniversalClient 24 | locker *redislock.Client 25 | disable bool 26 | prefix string 27 | lock string 28 | val string 29 | refresh bool 30 | } 31 | 32 | var local = cache.New(30*time.Minute, 60*time.Minute) 33 | 34 | // NewCache . 35 | func NewCache(c *conf.Bootstrap, client redis.UniversalClient) biz.Cache { 36 | return &Cache{ 37 | redis: client, 38 | locker: redislock.New(client), 39 | disable: c.Server.Nocache, 40 | lock: "lock", 41 | val: "val", 42 | } 43 | } 44 | 45 | func (c *Cache) Cache() redis.UniversalClient { 46 | return c.redis 47 | } 48 | 49 | func (c *Cache) WithPrefix(prefix string) biz.Cache { 50 | return &Cache{ 51 | redis: c.redis, 52 | locker: c.locker, 53 | disable: c.disable, 54 | prefix: prefix, 55 | lock: c.lock, 56 | val: c.val, 57 | } 58 | } 59 | 60 | func (c *Cache) WithRefresh() biz.Cache { 61 | return &Cache{ 62 | redis: c.redis, 63 | locker: c.locker, 64 | disable: c.disable, 65 | prefix: c.prefix, 66 | lock: c.lock, 67 | val: c.val, 68 | refresh: true, 69 | } 70 | } 71 | 72 | func (c *Cache) Get( 73 | ctx context.Context, 74 | action string, 75 | write func(context.Context) (string, error), 76 | ) (res string, err error) { 77 | tr := otel.Tracer("cache") 78 | ctx, span := tr.Start(ctx, "Get") 79 | defer span.End() 80 | if c.disable { 81 | return write(ctx) 82 | } 83 | key := c.getValKey(ctx, action) 84 | if !c.refresh { 85 | // 1. first get cache 86 | // 1.1. get from local 87 | res, err = GetFromLocal(key) 88 | if err == nil { 89 | // cache exists 90 | return 91 | } 92 | // 1.2. get from redis 93 | res, err = c.redis.Get(ctx, key).Result() 94 | if err == nil { 95 | // cache exists 96 | return 97 | } 98 | } 99 | // 2. get lock before read db 100 | lock, err := c.Lock(ctx, action) 101 | if err != nil { 102 | err = biz.ErrTooManyRequests(ctx) 103 | return 104 | } 105 | defer func() { 106 | _ = lock.Release(ctx) 107 | }() 108 | if !c.refresh { 109 | // 3. double check cache exists(avoid concurrency step 1 ok=false) 110 | res, err = GetFromLocal(key) 111 | if err == nil { 112 | // cache exists 113 | return 114 | } 115 | res, err = c.redis.Get(ctx, key).Result() 116 | if err == nil { 117 | // cache exists 118 | return 119 | } 120 | } 121 | // 4. load data from db and write to cache 122 | if write != nil { 123 | res, err = write(ctx) 124 | } 125 | return 126 | } 127 | 128 | func (c *Cache) Set(ctx context.Context, action, data string, short bool) { 129 | // set random expiration avoid a large number of keys expire at the same time 130 | seconds := rand.New(rand.NewSource(time.Now().Unix())).Int63n(300) + 300 131 | if short { 132 | // if record not found, set a short expiration 133 | seconds = 60 134 | } 135 | c.SetWithExpiration(ctx, action, data, seconds) 136 | } 137 | 138 | func (c *Cache) SetWithExpiration(ctx context.Context, action, data string, seconds int64) { 139 | if c.disable { 140 | return 141 | } 142 | key := c.getValKey(ctx, action) 143 | // set to local cache 144 | Set2Local(key, data, int(seconds)) 145 | // set to redis 146 | err := c.redis.Set(ctx, key, data, time.Duration(seconds)*time.Second).Err() 147 | if err != nil { 148 | log. 149 | WithContext(ctx). 150 | WithError(err). 151 | WithFields(log.Fields{ 152 | "action": action, 153 | "seconds": seconds, 154 | }). 155 | Warn("set cache failed") 156 | return 157 | } 158 | } 159 | 160 | func (c *Cache) Del(ctx context.Context, action string) { 161 | if c.disable { 162 | return 163 | } 164 | key := c.getValKey(ctx, action) 165 | DelFromLocal(key) 166 | err := c.redis.Del(ctx, key).Err() 167 | if err != nil { 168 | log. 169 | WithContext(ctx). 170 | WithError(err). 171 | WithFields(log.Fields{ 172 | "action": action, 173 | "key": key, 174 | }). 175 | Warn("del cache failed") 176 | } 177 | } 178 | 179 | func (c *Cache) Flush(ctx context.Context, handler func(ctx context.Context) error) (err error) { 180 | err = handler(ctx) 181 | if err != nil { 182 | return 183 | } 184 | if c.disable { 185 | return 186 | } 187 | action := c.getPrefixKey(ctx) 188 | arr := c.redis.Keys(ctx, action).Val() 189 | p := c.redis.Pipeline() 190 | for _, item := range arr { 191 | if item == c.lock { 192 | continue 193 | } 194 | DelFromLocal(item) 195 | p.Del(ctx, item) 196 | } 197 | _, pErr := p.Exec(ctx) 198 | if pErr != nil { 199 | log. 200 | WithContext(ctx). 201 | WithError(pErr). 202 | WithFields(log.Fields{ 203 | "action": action, 204 | }). 205 | Warn("flush cache failed") 206 | } 207 | return 208 | } 209 | 210 | func (c *Cache) FlushByPrefix(ctx context.Context, prefix ...string) (err error) { 211 | action := c.getPrefixKey(ctx, prefix...) 212 | arr := c.redis.Keys(ctx, action).Val() 213 | p := c.redis.Pipeline() 214 | for _, item := range arr { 215 | if item == c.lock { 216 | continue 217 | } 218 | DelFromLocal(item) 219 | p.Del(ctx, item) 220 | } 221 | _, pErr := p.Exec(ctx) 222 | if pErr != nil { 223 | log. 224 | WithContext(ctx). 225 | WithError(pErr). 226 | WithFields(log.Fields{ 227 | "action": action, 228 | }). 229 | Warn("flush cache by prefix failed") 230 | } 231 | return 232 | } 233 | 234 | func (c *Cache) Lock(ctx context.Context, action string) (*redislock.Lock, error) { 235 | tr := otel.Tracer("cache") 236 | ctx, span := tr.Start(ctx, "Lock") 237 | defer span.End() 238 | lock, err := c.locker.Obtain( 239 | ctx, 240 | c.getLockKey(ctx, action), 241 | 20*time.Second, 242 | &redislock.Options{ 243 | RetryStrategy: redislock.LimitRetry(redislock.LinearBackoff(5*time.Millisecond), 400), 244 | }, 245 | ) 246 | if err != nil { 247 | span.SetStatus(codes.Error, err.Error()) 248 | } 249 | return lock, err 250 | } 251 | 252 | func Set2Local(key, val string, expire int) { 253 | local.Set(key, val, time.Duration(expire)*time.Second) 254 | } 255 | 256 | func GetFromLocal(key string) (string, error) { 257 | val, ok := local.Get(key) 258 | if !ok { 259 | return "", errors.New("key not found") 260 | } 261 | return val.(string), nil 262 | } 263 | 264 | func DelFromLocal(key string) { 265 | local.Delete(key) 266 | } 267 | 268 | func (c *Cache) getPrefixKey(ctx context.Context, arr ...string) string { 269 | id := tenant.FromContext(ctx) 270 | prefix := c.prefix 271 | if len(arr) > 0 { 272 | // append params prefix need add val 273 | prefix = strings.Join(append([]string{prefix, c.val}, arr...), "_") 274 | } 275 | if strings.TrimSpace(prefix) == "" { 276 | // avoid flush all key 277 | log. 278 | WithContext(ctx). 279 | Warn("invalid prefix") 280 | prefix = "prefix" 281 | } 282 | if id == "" { 283 | return strings.Join([]string{prefix, "*"}, "") 284 | } 285 | return strings.Join([]string{id, "_", prefix, "*"}, "") 286 | } 287 | 288 | func (c *Cache) getValKey(ctx context.Context, action string) string { 289 | id := tenant.FromContext(ctx) 290 | if id == "" { 291 | return strings.Join([]string{c.prefix, c.val, action}, "_") 292 | } 293 | return strings.Join([]string{id, c.prefix, c.val, action}, "_") 294 | } 295 | 296 | func (c *Cache) getLockKey(ctx context.Context, action string) string { 297 | id := tenant.FromContext(ctx) 298 | if id == "" { 299 | return strings.Join([]string{c.prefix, c.lock, action}, "_") 300 | } 301 | return strings.Join([]string{id, c.prefix, c.lock, action}, "_") 302 | } 303 | -------------------------------------------------------------------------------- /third_party/google/protobuf/api.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | import "google/protobuf/source_context.proto"; 36 | import "google/protobuf/type.proto"; 37 | 38 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 39 | option java_package = "com.google.protobuf"; 40 | option java_outer_classname = "ApiProto"; 41 | option java_multiple_files = true; 42 | option objc_class_prefix = "GPB"; 43 | option go_package = "google.golang.org/protobuf/types/known/apipb"; 44 | 45 | // Api is a light-weight descriptor for an API Interface. 46 | // 47 | // Interfaces are also described as "protocol buffer services" in some contexts, 48 | // such as by the "service" keyword in a .proto file, but they are different 49 | // from API Services, which represent a concrete implementation of an interface 50 | // as opposed to simply a description of methods and bindings. They are also 51 | // sometimes simply referred to as "APIs" in other contexts, such as the name of 52 | // this message itself. See https://cloud.google.com/apis/design/glossary for 53 | // detailed terminology. 54 | message Api { 55 | // The fully qualified name of this interface, including package name 56 | // followed by the interface's simple name. 57 | string name = 1; 58 | 59 | // The methods of this interface, in unspecified order. 60 | repeated Method methods = 2; 61 | 62 | // Any metadata attached to the interface. 63 | repeated Option options = 3; 64 | 65 | // A version string for this interface. If specified, must have the form 66 | // `major-version.minor-version`, as in `1.10`. If the minor version is 67 | // omitted, it defaults to zero. If the entire version field is empty, the 68 | // major version is derived from the package name, as outlined below. If the 69 | // field is not empty, the version in the package name will be verified to be 70 | // consistent with what is provided here. 71 | // 72 | // The versioning schema uses [semantic 73 | // versioning](http://semver.org) where the major version number 74 | // indicates a breaking change and the minor version an additive, 75 | // non-breaking change. Both version numbers are signals to users 76 | // what to expect from different versions, and should be carefully 77 | // chosen based on the product plan. 78 | // 79 | // The major version is also reflected in the package name of the 80 | // interface, which must end in `v`, as in 81 | // `google.feature.v1`. For major versions 0 and 1, the suffix can 82 | // be omitted. Zero major versions must only be used for 83 | // experimental, non-GA interfaces. 84 | // 85 | // 86 | string version = 4; 87 | 88 | // Source context for the protocol buffer service represented by this 89 | // message. 90 | SourceContext source_context = 5; 91 | 92 | // Included interfaces. See [Mixin][]. 93 | repeated Mixin mixins = 6; 94 | 95 | // The source syntax of the service. 96 | Syntax syntax = 7; 97 | } 98 | 99 | // Method represents a method of an API interface. 100 | message Method { 101 | // The simple name of this method. 102 | string name = 1; 103 | 104 | // A URL of the input message type. 105 | string request_type_url = 2; 106 | 107 | // If true, the request is streamed. 108 | bool request_streaming = 3; 109 | 110 | // The URL of the output message type. 111 | string response_type_url = 4; 112 | 113 | // If true, the response is streamed. 114 | bool response_streaming = 5; 115 | 116 | // Any metadata attached to the method. 117 | repeated Option options = 6; 118 | 119 | // The source syntax of this method. 120 | Syntax syntax = 7; 121 | } 122 | 123 | // Declares an API Interface to be included in this interface. The including 124 | // interface must redeclare all the methods from the included interface, but 125 | // documentation and options are inherited as follows: 126 | // 127 | // - If after comment and whitespace stripping, the documentation 128 | // string of the redeclared method is empty, it will be inherited 129 | // from the original method. 130 | // 131 | // - Each annotation belonging to the service config (http, 132 | // visibility) which is not set in the redeclared method will be 133 | // inherited. 134 | // 135 | // - If an http annotation is inherited, the path pattern will be 136 | // modified as follows. Any version prefix will be replaced by the 137 | // version of the including interface plus the [root][] path if 138 | // specified. 139 | // 140 | // Example of a simple mixin: 141 | // 142 | // package google.acl.v1; 143 | // service AccessControl { 144 | // // Get the underlying ACL object. 145 | // rpc GetAcl(GetAclRequest) returns (Acl) { 146 | // option (google.api.http).get = "/v1/{resource=**}:getAcl"; 147 | // } 148 | // } 149 | // 150 | // package google.storage.v2; 151 | // service Storage { 152 | // rpc GetAcl(GetAclRequest) returns (Acl); 153 | // 154 | // // Get a data record. 155 | // rpc GetData(GetDataRequest) returns (Data) { 156 | // option (google.api.http).get = "/v2/{resource=**}"; 157 | // } 158 | // } 159 | // 160 | // Example of a mixin configuration: 161 | // 162 | // apis: 163 | // - name: google.storage.v2.Storage 164 | // mixins: 165 | // - name: google.acl.v1.AccessControl 166 | // 167 | // The mixin construct implies that all methods in `AccessControl` are 168 | // also declared with same name and request/response types in 169 | // `Storage`. A documentation generator or annotation processor will 170 | // see the effective `Storage.GetAcl` method after inheriting 171 | // documentation and annotations as follows: 172 | // 173 | // service Storage { 174 | // // Get the underlying ACL object. 175 | // rpc GetAcl(GetAclRequest) returns (Acl) { 176 | // option (google.api.http).get = "/v2/{resource=**}:getAcl"; 177 | // } 178 | // ... 179 | // } 180 | // 181 | // Note how the version in the path pattern changed from `v1` to `v2`. 182 | // 183 | // If the `root` field in the mixin is specified, it should be a 184 | // relative path under which inherited HTTP paths are placed. Example: 185 | // 186 | // apis: 187 | // - name: google.storage.v2.Storage 188 | // mixins: 189 | // - name: google.acl.v1.AccessControl 190 | // root: acls 191 | // 192 | // This implies the following inherited HTTP annotation: 193 | // 194 | // service Storage { 195 | // // Get the underlying ACL object. 196 | // rpc GetAcl(GetAclRequest) returns (Acl) { 197 | // option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; 198 | // } 199 | // ... 200 | // } 201 | message Mixin { 202 | // The fully qualified name of the interface which is included. 203 | string name = 1; 204 | 205 | // If non-empty specifies a path under which inherited HTTP paths 206 | // are rooted. 207 | string root = 2; 208 | } 209 | -------------------------------------------------------------------------------- /third_party/google/protobuf/field_mask.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "FieldMaskProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; 41 | option cc_enable_arenas = true; 42 | 43 | // `FieldMask` represents a set of symbolic field paths, for example: 44 | // 45 | // paths: "f.a" 46 | // paths: "f.b.d" 47 | // 48 | // Here `f` represents a field in some root message, `a` and `b` 49 | // fields in the message found in `f`, and `d` a field found in the 50 | // message in `f.b`. 51 | // 52 | // Field masks are used to specify a subset of fields that should be 53 | // returned by a get operation or modified by an update operation. 54 | // Field masks also have a custom JSON encoding (see below). 55 | // 56 | // # Field Masks in Projections 57 | // 58 | // When used in the context of a projection, a response message or 59 | // sub-message is filtered by the API to only contain those fields as 60 | // specified in the mask. For example, if the mask in the previous 61 | // example is applied to a response message as follows: 62 | // 63 | // f { 64 | // a : 22 65 | // b { 66 | // d : 1 67 | // x : 2 68 | // } 69 | // y : 13 70 | // } 71 | // z: 8 72 | // 73 | // The result will not contain specific values for fields x,y and z 74 | // (their value will be set to the default, and omitted in proto text 75 | // output): 76 | // 77 | // 78 | // f { 79 | // a : 22 80 | // b { 81 | // d : 1 82 | // } 83 | // } 84 | // 85 | // A repeated field is not allowed except at the last position of a 86 | // paths string. 87 | // 88 | // If a FieldMask object is not present in a get operation, the 89 | // operation applies to all fields (as if a FieldMask of all fields 90 | // had been specified). 91 | // 92 | // Note that a field mask does not necessarily apply to the 93 | // top-level response message. In case of a REST get operation, the 94 | // field mask applies directly to the response, but in case of a REST 95 | // list operation, the mask instead applies to each individual message 96 | // in the returned resource list. In case of a REST custom method, 97 | // other definitions may be used. Where the mask applies will be 98 | // clearly documented together with its declaration in the API. In 99 | // any case, the effect on the returned resource/resources is required 100 | // behavior for APIs. 101 | // 102 | // # Field Masks in Update Operations 103 | // 104 | // A field mask in update operations specifies which fields of the 105 | // targeted resource are going to be updated. The API is required 106 | // to only change the values of the fields as specified in the mask 107 | // and leave the others untouched. If a resource is passed in to 108 | // describe the updated values, the API ignores the values of all 109 | // fields not covered by the mask. 110 | // 111 | // If a repeated field is specified for an update operation, new values will 112 | // be appended to the existing repeated field in the target resource. Note that 113 | // a repeated field is only allowed in the last position of a `paths` string. 114 | // 115 | // If a sub-message is specified in the last position of the field mask for an 116 | // update operation, then new value will be merged into the existing sub-message 117 | // in the target resource. 118 | // 119 | // For example, given the target message: 120 | // 121 | // f { 122 | // b { 123 | // d: 1 124 | // x: 2 125 | // } 126 | // c: [1] 127 | // } 128 | // 129 | // And an update message: 130 | // 131 | // f { 132 | // b { 133 | // d: 10 134 | // } 135 | // c: [2] 136 | // } 137 | // 138 | // then if the field mask is: 139 | // 140 | // paths: ["f.b", "f.c"] 141 | // 142 | // then the result will be: 143 | // 144 | // f { 145 | // b { 146 | // d: 10 147 | // x: 2 148 | // } 149 | // c: [1, 2] 150 | // } 151 | // 152 | // An implementation may provide options to override this default behavior for 153 | // repeated and message fields. 154 | // 155 | // In order to reset a field's value to the default, the field must 156 | // be in the mask and set to the default value in the provided resource. 157 | // Hence, in order to reset all fields of a resource, provide a default 158 | // instance of the resource and set all fields in the mask, or do 159 | // not provide a mask as described below. 160 | // 161 | // If a field mask is not present on update, the operation applies to 162 | // all fields (as if a field mask of all fields has been specified). 163 | // Note that in the presence of schema evolution, this may mean that 164 | // fields the client does not know and has therefore not filled into 165 | // the request will be reset to their default. If this is unwanted 166 | // behavior, a specific service may require a client to always specify 167 | // a field mask, producing an error if not. 168 | // 169 | // As with get operations, the location of the resource which 170 | // describes the updated values in the request message depends on the 171 | // operation kind. In any case, the effect of the field mask is 172 | // required to be honored by the API. 173 | // 174 | // ## Considerations for HTTP REST 175 | // 176 | // The HTTP kind of an update operation which uses a field mask must 177 | // be set to PATCH instead of PUT in order to satisfy HTTP semantics 178 | // (PUT must only be used for full updates). 179 | // 180 | // # JSON Encoding of Field Masks 181 | // 182 | // In JSON, a field mask is encoded as a single string where paths are 183 | // separated by a comma. Fields name in each path are converted 184 | // to/from lower-camel naming conventions. 185 | // 186 | // As an example, consider the following message declarations: 187 | // 188 | // message Profile { 189 | // User user = 1; 190 | // Photo photo = 2; 191 | // } 192 | // message User { 193 | // string display_name = 1; 194 | // string address = 2; 195 | // } 196 | // 197 | // In proto a field mask for `Profile` may look as such: 198 | // 199 | // mask { 200 | // paths: "user.display_name" 201 | // paths: "photo" 202 | // } 203 | // 204 | // In JSON, the same mask is represented as below: 205 | // 206 | // { 207 | // mask: "user.displayName,photo" 208 | // } 209 | // 210 | // # Field Masks and Oneof Fields 211 | // 212 | // Field masks treat fields in oneofs just as regular fields. Consider the 213 | // following message: 214 | // 215 | // message SampleMessage { 216 | // oneof test_oneof { 217 | // string name = 4; 218 | // SubMessage sub_message = 9; 219 | // } 220 | // } 221 | // 222 | // The field mask can be: 223 | // 224 | // mask { 225 | // paths: "name" 226 | // } 227 | // 228 | // Or: 229 | // 230 | // mask { 231 | // paths: "sub_message" 232 | // } 233 | // 234 | // Note that oneof type names ("test_oneof" in this case) cannot be used in 235 | // paths. 236 | // 237 | // ## Field Mask Verification 238 | // 239 | // The implementation of any API method which has a FieldMask type field in the 240 | // request should verify the included field paths, and return an 241 | // `INVALID_ARGUMENT` error if any path is unmappable. 242 | message FieldMask { 243 | // The set of field mask paths. 244 | repeated string paths = 1; 245 | } 246 | -------------------------------------------------------------------------------- /third_party/google/protobuf/compiler/plugin.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Author: kenton@google.com (Kenton Varda) 32 | // 33 | // WARNING: The plugin interface is currently EXPERIMENTAL and is subject to 34 | // change. 35 | // 36 | // protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is 37 | // just a program that reads a CodeGeneratorRequest from stdin and writes a 38 | // CodeGeneratorResponse to stdout. 39 | // 40 | // Plugins written using C++ can use google/protobuf/compiler/plugin.h instead 41 | // of dealing with the raw protocol defined here. 42 | // 43 | // A plugin executable needs only to be placed somewhere in the path. The 44 | // plugin should be named "protoc-gen-$NAME", and will then be used when the 45 | // flag "--${NAME}_out" is passed to protoc. 46 | 47 | syntax = "proto2"; 48 | 49 | package google.protobuf.compiler; 50 | option java_package = "com.google.protobuf.compiler"; 51 | option java_outer_classname = "PluginProtos"; 52 | 53 | option go_package = "google.golang.org/protobuf/types/pluginpb"; 54 | 55 | import "google/protobuf/descriptor.proto"; 56 | 57 | // The version number of protocol compiler. 58 | message Version { 59 | optional int32 major = 1; 60 | optional int32 minor = 2; 61 | optional int32 patch = 3; 62 | // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should 63 | // be empty for mainline stable releases. 64 | optional string suffix = 4; 65 | } 66 | 67 | // An encoded CodeGeneratorRequest is written to the plugin's stdin. 68 | message CodeGeneratorRequest { 69 | // The .proto files that were explicitly listed on the command-line. The 70 | // code generator should generate code only for these files. Each file's 71 | // descriptor will be included in proto_file, below. 72 | repeated string file_to_generate = 1; 73 | 74 | // The generator parameter passed on the command-line. 75 | optional string parameter = 2; 76 | 77 | // FileDescriptorProtos for all files in files_to_generate and everything 78 | // they import. The files will appear in topological order, so each file 79 | // appears before any file that imports it. 80 | // 81 | // protoc guarantees that all proto_files will be written after 82 | // the fields above, even though this is not technically guaranteed by the 83 | // protobuf wire format. This theoretically could allow a plugin to stream 84 | // in the FileDescriptorProtos and handle them one by one rather than read 85 | // the entire set into memory at once. However, as of this writing, this 86 | // is not similarly optimized on protoc's end -- it will store all fields in 87 | // memory at once before sending them to the plugin. 88 | // 89 | // Type names of fields and extensions in the FileDescriptorProto are always 90 | // fully qualified. 91 | repeated FileDescriptorProto proto_file = 15; 92 | 93 | // The version number of protocol compiler. 94 | optional Version compiler_version = 3; 95 | 96 | } 97 | 98 | // The plugin writes an encoded CodeGeneratorResponse to stdout. 99 | message CodeGeneratorResponse { 100 | // Error message. If non-empty, code generation failed. The plugin process 101 | // should exit with status code zero even if it reports an error in this way. 102 | // 103 | // This should be used to indicate errors in .proto files which prevent the 104 | // code generator from generating correct code. Errors which indicate a 105 | // problem in protoc itself -- such as the input CodeGeneratorRequest being 106 | // unparseable -- should be reported by writing a message to stderr and 107 | // exiting with a non-zero status code. 108 | optional string error = 1; 109 | 110 | // A bitmask of supported features that the code generator supports. 111 | // This is a bitwise "or" of values from the Feature enum. 112 | optional uint64 supported_features = 2; 113 | 114 | // Sync with code_generator.h. 115 | enum Feature { 116 | FEATURE_NONE = 0; 117 | FEATURE_PROTO3_OPTIONAL = 1; 118 | } 119 | 120 | // Represents a single generated file. 121 | message File { 122 | // The file name, relative to the output directory. The name must not 123 | // contain "." or ".." components and must be relative, not be absolute (so, 124 | // the file cannot lie outside the output directory). "/" must be used as 125 | // the path separator, not "\". 126 | // 127 | // If the name is omitted, the content will be appended to the previous 128 | // file. This allows the generator to break large files into small chunks, 129 | // and allows the generated text to be streamed back to protoc so that large 130 | // files need not reside completely in memory at one time. Note that as of 131 | // this writing protoc does not optimize for this -- it will read the entire 132 | // CodeGeneratorResponse before writing files to disk. 133 | optional string name = 1; 134 | 135 | // If non-empty, indicates that the named file should already exist, and the 136 | // content here is to be inserted into that file at a defined insertion 137 | // point. This feature allows a code generator to extend the output 138 | // produced by another code generator. The original generator may provide 139 | // insertion points by placing special annotations in the file that look 140 | // like: 141 | // @@protoc_insertion_point(NAME) 142 | // The annotation can have arbitrary text before and after it on the line, 143 | // which allows it to be placed in a comment. NAME should be replaced with 144 | // an identifier naming the point -- this is what other generators will use 145 | // as the insertion_point. Code inserted at this point will be placed 146 | // immediately above the line containing the insertion point (thus multiple 147 | // insertions to the same point will come out in the order they were added). 148 | // The double-@ is intended to make it unlikely that the generated code 149 | // could contain things that look like insertion points by accident. 150 | // 151 | // For example, the C++ code generator places the following line in the 152 | // .pb.h files that it generates: 153 | // // @@protoc_insertion_point(namespace_scope) 154 | // This line appears within the scope of the file's package namespace, but 155 | // outside of any particular class. Another plugin can then specify the 156 | // insertion_point "namespace_scope" to generate additional classes or 157 | // other declarations that should be placed in this scope. 158 | // 159 | // Note that if the line containing the insertion point begins with 160 | // whitespace, the same whitespace will be added to every line of the 161 | // inserted text. This is useful for languages like Python, where 162 | // indentation matters. In these languages, the insertion point comment 163 | // should be indented the same amount as any inserted code will need to be 164 | // in order to work correctly in that context. 165 | // 166 | // The code generator that generates the initial file and the one which 167 | // inserts into it must both run as part of a single invocation of protoc. 168 | // Code generators are executed in the order in which they appear on the 169 | // command line. 170 | // 171 | // If |insertion_point| is present, |name| must also be present. 172 | optional string insertion_point = 2; 173 | 174 | // The file contents. 175 | optional string content = 15; 176 | 177 | // Information describing the file content being inserted. If an insertion 178 | // point is used, this information will be appropriately offset and inserted 179 | // into the code generation metadata for the generated files. 180 | optional GeneratedCodeInfo generated_code_info = 16; 181 | } 182 | repeated File file = 15; 183 | } 184 | -------------------------------------------------------------------------------- /api/game/game_http.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-http. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-http v2.8.0 4 | // - protoc v4.25.0 5 | // source: game-proto/game.proto 6 | 7 | package game 8 | 9 | import ( 10 | context "context" 11 | params "github.com/go-cinch/common/proto/params" 12 | http "github.com/go-kratos/kratos/v2/transport/http" 13 | binding "github.com/go-kratos/kratos/v2/transport/http/binding" 14 | emptypb "google.golang.org/protobuf/types/known/emptypb" 15 | ) 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the kratos package it is being compiled against. 19 | var _ = new(context.Context) 20 | var _ = binding.EncodeURL 21 | 22 | const _ = http.SupportPackageIsVersion1 23 | 24 | const OperationGameCreateGame = "/game.v1.Game/CreateGame" 25 | const OperationGameDeleteGame = "/game.v1.Game/DeleteGame" 26 | const OperationGameFindGame = "/game.v1.Game/FindGame" 27 | const OperationGameGetGame = "/game.v1.Game/GetGame" 28 | const OperationGameUpdateGame = "/game.v1.Game/UpdateGame" 29 | 30 | type GameHTTPServer interface { 31 | // CreateGame create one Game record 32 | CreateGame(context.Context, *CreateGameRequest) (*emptypb.Empty, error) 33 | // DeleteGame delete one or more Game record by id 34 | DeleteGame(context.Context, *params.IdsRequest) (*emptypb.Empty, error) 35 | // FindGame query Game list by page 36 | FindGame(context.Context, *FindGameRequest) (*FindGameReply, error) 37 | // GetGame query one Game record 38 | GetGame(context.Context, *GetGameRequest) (*GetGameReply, error) 39 | // UpdateGame update one Game record by id 40 | UpdateGame(context.Context, *UpdateGameRequest) (*emptypb.Empty, error) 41 | } 42 | 43 | func RegisterGameHTTPServer(s *http.Server, srv GameHTTPServer) { 44 | r := s.Route("/") 45 | r.POST("/game/create", _Game_CreateGame0_HTTP_Handler(srv)) 46 | r.GET("/game/get", _Game_GetGame0_HTTP_Handler(srv)) 47 | r.GET("/game/list", _Game_FindGame0_HTTP_Handler(srv)) 48 | r.PATCH("/game/update", _Game_UpdateGame0_HTTP_Handler(srv)) 49 | r.PUT("/game/update", _Game_UpdateGame1_HTTP_Handler(srv)) 50 | r.DELETE("/game/delete", _Game_DeleteGame0_HTTP_Handler(srv)) 51 | } 52 | 53 | func _Game_CreateGame0_HTTP_Handler(srv GameHTTPServer) func(ctx http.Context) error { 54 | return func(ctx http.Context) error { 55 | var in CreateGameRequest 56 | if err := ctx.Bind(&in); err != nil { 57 | return err 58 | } 59 | if err := ctx.BindQuery(&in); err != nil { 60 | return err 61 | } 62 | http.SetOperation(ctx, OperationGameCreateGame) 63 | h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { 64 | return srv.CreateGame(ctx, req.(*CreateGameRequest)) 65 | }) 66 | out, err := h(ctx, &in) 67 | if err != nil { 68 | return err 69 | } 70 | reply := out.(*emptypb.Empty) 71 | return ctx.Result(200, reply) 72 | } 73 | } 74 | 75 | func _Game_GetGame0_HTTP_Handler(srv GameHTTPServer) func(ctx http.Context) error { 76 | return func(ctx http.Context) error { 77 | var in GetGameRequest 78 | if err := ctx.BindQuery(&in); err != nil { 79 | return err 80 | } 81 | http.SetOperation(ctx, OperationGameGetGame) 82 | h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { 83 | return srv.GetGame(ctx, req.(*GetGameRequest)) 84 | }) 85 | out, err := h(ctx, &in) 86 | if err != nil { 87 | return err 88 | } 89 | reply := out.(*GetGameReply) 90 | return ctx.Result(200, reply) 91 | } 92 | } 93 | 94 | func _Game_FindGame0_HTTP_Handler(srv GameHTTPServer) func(ctx http.Context) error { 95 | return func(ctx http.Context) error { 96 | var in FindGameRequest 97 | if err := ctx.BindQuery(&in); err != nil { 98 | return err 99 | } 100 | http.SetOperation(ctx, OperationGameFindGame) 101 | h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { 102 | return srv.FindGame(ctx, req.(*FindGameRequest)) 103 | }) 104 | out, err := h(ctx, &in) 105 | if err != nil { 106 | return err 107 | } 108 | reply := out.(*FindGameReply) 109 | return ctx.Result(200, reply) 110 | } 111 | } 112 | 113 | func _Game_UpdateGame0_HTTP_Handler(srv GameHTTPServer) func(ctx http.Context) error { 114 | return func(ctx http.Context) error { 115 | var in UpdateGameRequest 116 | if err := ctx.Bind(&in); err != nil { 117 | return err 118 | } 119 | if err := ctx.BindQuery(&in); err != nil { 120 | return err 121 | } 122 | http.SetOperation(ctx, OperationGameUpdateGame) 123 | h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { 124 | return srv.UpdateGame(ctx, req.(*UpdateGameRequest)) 125 | }) 126 | out, err := h(ctx, &in) 127 | if err != nil { 128 | return err 129 | } 130 | reply := out.(*emptypb.Empty) 131 | return ctx.Result(200, reply) 132 | } 133 | } 134 | 135 | func _Game_UpdateGame1_HTTP_Handler(srv GameHTTPServer) func(ctx http.Context) error { 136 | return func(ctx http.Context) error { 137 | var in UpdateGameRequest 138 | if err := ctx.Bind(&in); err != nil { 139 | return err 140 | } 141 | if err := ctx.BindQuery(&in); err != nil { 142 | return err 143 | } 144 | http.SetOperation(ctx, OperationGameUpdateGame) 145 | h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { 146 | return srv.UpdateGame(ctx, req.(*UpdateGameRequest)) 147 | }) 148 | out, err := h(ctx, &in) 149 | if err != nil { 150 | return err 151 | } 152 | reply := out.(*emptypb.Empty) 153 | return ctx.Result(200, reply) 154 | } 155 | } 156 | 157 | func _Game_DeleteGame0_HTTP_Handler(srv GameHTTPServer) func(ctx http.Context) error { 158 | return func(ctx http.Context) error { 159 | var in params.IdsRequest 160 | if err := ctx.BindQuery(&in); err != nil { 161 | return err 162 | } 163 | http.SetOperation(ctx, OperationGameDeleteGame) 164 | h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { 165 | return srv.DeleteGame(ctx, req.(*params.IdsRequest)) 166 | }) 167 | out, err := h(ctx, &in) 168 | if err != nil { 169 | return err 170 | } 171 | reply := out.(*emptypb.Empty) 172 | return ctx.Result(200, reply) 173 | } 174 | } 175 | 176 | type GameHTTPClient interface { 177 | CreateGame(ctx context.Context, req *CreateGameRequest, opts ...http.CallOption) (rsp *emptypb.Empty, err error) 178 | DeleteGame(ctx context.Context, req *params.IdsRequest, opts ...http.CallOption) (rsp *emptypb.Empty, err error) 179 | FindGame(ctx context.Context, req *FindGameRequest, opts ...http.CallOption) (rsp *FindGameReply, err error) 180 | GetGame(ctx context.Context, req *GetGameRequest, opts ...http.CallOption) (rsp *GetGameReply, err error) 181 | UpdateGame(ctx context.Context, req *UpdateGameRequest, opts ...http.CallOption) (rsp *emptypb.Empty, err error) 182 | } 183 | 184 | type GameHTTPClientImpl struct { 185 | cc *http.Client 186 | } 187 | 188 | func NewGameHTTPClient(client *http.Client) GameHTTPClient { 189 | return &GameHTTPClientImpl{client} 190 | } 191 | 192 | func (c *GameHTTPClientImpl) CreateGame(ctx context.Context, in *CreateGameRequest, opts ...http.CallOption) (*emptypb.Empty, error) { 193 | var out emptypb.Empty 194 | pattern := "/game/create" 195 | path := binding.EncodeURL(pattern, in, false) 196 | opts = append(opts, http.Operation(OperationGameCreateGame)) 197 | opts = append(opts, http.PathTemplate(pattern)) 198 | err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) 199 | if err != nil { 200 | return nil, err 201 | } 202 | return &out, nil 203 | } 204 | 205 | func (c *GameHTTPClientImpl) DeleteGame(ctx context.Context, in *params.IdsRequest, opts ...http.CallOption) (*emptypb.Empty, error) { 206 | var out emptypb.Empty 207 | pattern := "/game/delete" 208 | path := binding.EncodeURL(pattern, in, true) 209 | opts = append(opts, http.Operation(OperationGameDeleteGame)) 210 | opts = append(opts, http.PathTemplate(pattern)) 211 | err := c.cc.Invoke(ctx, "DELETE", path, nil, &out, opts...) 212 | if err != nil { 213 | return nil, err 214 | } 215 | return &out, nil 216 | } 217 | 218 | func (c *GameHTTPClientImpl) FindGame(ctx context.Context, in *FindGameRequest, opts ...http.CallOption) (*FindGameReply, error) { 219 | var out FindGameReply 220 | pattern := "/game/list" 221 | path := binding.EncodeURL(pattern, in, true) 222 | opts = append(opts, http.Operation(OperationGameFindGame)) 223 | opts = append(opts, http.PathTemplate(pattern)) 224 | err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) 225 | if err != nil { 226 | return nil, err 227 | } 228 | return &out, nil 229 | } 230 | 231 | func (c *GameHTTPClientImpl) GetGame(ctx context.Context, in *GetGameRequest, opts ...http.CallOption) (*GetGameReply, error) { 232 | var out GetGameReply 233 | pattern := "/game/get" 234 | path := binding.EncodeURL(pattern, in, true) 235 | opts = append(opts, http.Operation(OperationGameGetGame)) 236 | opts = append(opts, http.PathTemplate(pattern)) 237 | err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) 238 | if err != nil { 239 | return nil, err 240 | } 241 | return &out, nil 242 | } 243 | 244 | func (c *GameHTTPClientImpl) UpdateGame(ctx context.Context, in *UpdateGameRequest, opts ...http.CallOption) (*emptypb.Empty, error) { 245 | var out emptypb.Empty 246 | pattern := "/game/update" 247 | path := binding.EncodeURL(pattern, in, false) 248 | opts = append(opts, http.Operation(OperationGameUpdateGame)) 249 | opts = append(opts, http.PathTemplate(pattern)) 250 | err := c.cc.Invoke(ctx, "PUT", path, in, &out, opts...) 251 | if err != nil { 252 | return nil, err 253 | } 254 | return &out, nil 255 | } 256 | -------------------------------------------------------------------------------- /api/game/game_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc v4.25.0 5 | // source: game-proto/game.proto 6 | 7 | package game 8 | 9 | import ( 10 | context "context" 11 | params "github.com/go-cinch/common/proto/params" 12 | grpc "google.golang.org/grpc" 13 | codes "google.golang.org/grpc/codes" 14 | status "google.golang.org/grpc/status" 15 | emptypb "google.golang.org/protobuf/types/known/emptypb" 16 | ) 17 | 18 | // This is a compile-time assertion to ensure that this generated file 19 | // is compatible with the grpc package it is being compiled against. 20 | // Requires gRPC-Go v1.32.0 or later. 21 | const _ = grpc.SupportPackageIsVersion7 22 | 23 | const ( 24 | Game_CreateGame_FullMethodName = "/game.v1.Game/CreateGame" 25 | Game_GetGame_FullMethodName = "/game.v1.Game/GetGame" 26 | Game_FindGame_FullMethodName = "/game.v1.Game/FindGame" 27 | Game_UpdateGame_FullMethodName = "/game.v1.Game/UpdateGame" 28 | Game_DeleteGame_FullMethodName = "/game.v1.Game/DeleteGame" 29 | ) 30 | 31 | // GameClient is the client API for Game service. 32 | // 33 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 34 | type GameClient interface { 35 | // create one Game record 36 | CreateGame(ctx context.Context, in *CreateGameRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) 37 | // query one Game record 38 | GetGame(ctx context.Context, in *GetGameRequest, opts ...grpc.CallOption) (*GetGameReply, error) 39 | // query Game list by page 40 | FindGame(ctx context.Context, in *FindGameRequest, opts ...grpc.CallOption) (*FindGameReply, error) 41 | // update one Game record by id 42 | UpdateGame(ctx context.Context, in *UpdateGameRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) 43 | // delete one or more Game record by id 44 | DeleteGame(ctx context.Context, in *params.IdsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) 45 | } 46 | 47 | type gameClient struct { 48 | cc grpc.ClientConnInterface 49 | } 50 | 51 | func NewGameClient(cc grpc.ClientConnInterface) GameClient { 52 | return &gameClient{cc} 53 | } 54 | 55 | func (c *gameClient) CreateGame(ctx context.Context, in *CreateGameRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { 56 | out := new(emptypb.Empty) 57 | err := c.cc.Invoke(ctx, Game_CreateGame_FullMethodName, in, out, opts...) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return out, nil 62 | } 63 | 64 | func (c *gameClient) GetGame(ctx context.Context, in *GetGameRequest, opts ...grpc.CallOption) (*GetGameReply, error) { 65 | out := new(GetGameReply) 66 | err := c.cc.Invoke(ctx, Game_GetGame_FullMethodName, in, out, opts...) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return out, nil 71 | } 72 | 73 | func (c *gameClient) FindGame(ctx context.Context, in *FindGameRequest, opts ...grpc.CallOption) (*FindGameReply, error) { 74 | out := new(FindGameReply) 75 | err := c.cc.Invoke(ctx, Game_FindGame_FullMethodName, in, out, opts...) 76 | if err != nil { 77 | return nil, err 78 | } 79 | return out, nil 80 | } 81 | 82 | func (c *gameClient) UpdateGame(ctx context.Context, in *UpdateGameRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { 83 | out := new(emptypb.Empty) 84 | err := c.cc.Invoke(ctx, Game_UpdateGame_FullMethodName, in, out, opts...) 85 | if err != nil { 86 | return nil, err 87 | } 88 | return out, nil 89 | } 90 | 91 | func (c *gameClient) DeleteGame(ctx context.Context, in *params.IdsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { 92 | out := new(emptypb.Empty) 93 | err := c.cc.Invoke(ctx, Game_DeleteGame_FullMethodName, in, out, opts...) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return out, nil 98 | } 99 | 100 | // GameServer is the server API for Game service. 101 | // All implementations must embed UnimplementedGameServer 102 | // for forward compatibility 103 | type GameServer interface { 104 | // create one Game record 105 | CreateGame(context.Context, *CreateGameRequest) (*emptypb.Empty, error) 106 | // query one Game record 107 | GetGame(context.Context, *GetGameRequest) (*GetGameReply, error) 108 | // query Game list by page 109 | FindGame(context.Context, *FindGameRequest) (*FindGameReply, error) 110 | // update one Game record by id 111 | UpdateGame(context.Context, *UpdateGameRequest) (*emptypb.Empty, error) 112 | // delete one or more Game record by id 113 | DeleteGame(context.Context, *params.IdsRequest) (*emptypb.Empty, error) 114 | mustEmbedUnimplementedGameServer() 115 | } 116 | 117 | // UnimplementedGameServer must be embedded to have forward compatible implementations. 118 | type UnimplementedGameServer struct { 119 | } 120 | 121 | func (UnimplementedGameServer) CreateGame(context.Context, *CreateGameRequest) (*emptypb.Empty, error) { 122 | return nil, status.Errorf(codes.Unimplemented, "method CreateGame not implemented") 123 | } 124 | func (UnimplementedGameServer) GetGame(context.Context, *GetGameRequest) (*GetGameReply, error) { 125 | return nil, status.Errorf(codes.Unimplemented, "method GetGame not implemented") 126 | } 127 | func (UnimplementedGameServer) FindGame(context.Context, *FindGameRequest) (*FindGameReply, error) { 128 | return nil, status.Errorf(codes.Unimplemented, "method FindGame not implemented") 129 | } 130 | func (UnimplementedGameServer) UpdateGame(context.Context, *UpdateGameRequest) (*emptypb.Empty, error) { 131 | return nil, status.Errorf(codes.Unimplemented, "method UpdateGame not implemented") 132 | } 133 | func (UnimplementedGameServer) DeleteGame(context.Context, *params.IdsRequest) (*emptypb.Empty, error) { 134 | return nil, status.Errorf(codes.Unimplemented, "method DeleteGame not implemented") 135 | } 136 | func (UnimplementedGameServer) mustEmbedUnimplementedGameServer() {} 137 | 138 | // UnsafeGameServer may be embedded to opt out of forward compatibility for this service. 139 | // Use of this interface is not recommended, as added methods to GameServer will 140 | // result in compilation errors. 141 | type UnsafeGameServer interface { 142 | mustEmbedUnimplementedGameServer() 143 | } 144 | 145 | func RegisterGameServer(s grpc.ServiceRegistrar, srv GameServer) { 146 | s.RegisterService(&Game_ServiceDesc, srv) 147 | } 148 | 149 | func _Game_CreateGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 150 | in := new(CreateGameRequest) 151 | if err := dec(in); err != nil { 152 | return nil, err 153 | } 154 | if interceptor == nil { 155 | return srv.(GameServer).CreateGame(ctx, in) 156 | } 157 | info := &grpc.UnaryServerInfo{ 158 | Server: srv, 159 | FullMethod: Game_CreateGame_FullMethodName, 160 | } 161 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 162 | return srv.(GameServer).CreateGame(ctx, req.(*CreateGameRequest)) 163 | } 164 | return interceptor(ctx, in, info, handler) 165 | } 166 | 167 | func _Game_GetGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 168 | in := new(GetGameRequest) 169 | if err := dec(in); err != nil { 170 | return nil, err 171 | } 172 | if interceptor == nil { 173 | return srv.(GameServer).GetGame(ctx, in) 174 | } 175 | info := &grpc.UnaryServerInfo{ 176 | Server: srv, 177 | FullMethod: Game_GetGame_FullMethodName, 178 | } 179 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 180 | return srv.(GameServer).GetGame(ctx, req.(*GetGameRequest)) 181 | } 182 | return interceptor(ctx, in, info, handler) 183 | } 184 | 185 | func _Game_FindGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 186 | in := new(FindGameRequest) 187 | if err := dec(in); err != nil { 188 | return nil, err 189 | } 190 | if interceptor == nil { 191 | return srv.(GameServer).FindGame(ctx, in) 192 | } 193 | info := &grpc.UnaryServerInfo{ 194 | Server: srv, 195 | FullMethod: Game_FindGame_FullMethodName, 196 | } 197 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 198 | return srv.(GameServer).FindGame(ctx, req.(*FindGameRequest)) 199 | } 200 | return interceptor(ctx, in, info, handler) 201 | } 202 | 203 | func _Game_UpdateGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 204 | in := new(UpdateGameRequest) 205 | if err := dec(in); err != nil { 206 | return nil, err 207 | } 208 | if interceptor == nil { 209 | return srv.(GameServer).UpdateGame(ctx, in) 210 | } 211 | info := &grpc.UnaryServerInfo{ 212 | Server: srv, 213 | FullMethod: Game_UpdateGame_FullMethodName, 214 | } 215 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 216 | return srv.(GameServer).UpdateGame(ctx, req.(*UpdateGameRequest)) 217 | } 218 | return interceptor(ctx, in, info, handler) 219 | } 220 | 221 | func _Game_DeleteGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 222 | in := new(params.IdsRequest) 223 | if err := dec(in); err != nil { 224 | return nil, err 225 | } 226 | if interceptor == nil { 227 | return srv.(GameServer).DeleteGame(ctx, in) 228 | } 229 | info := &grpc.UnaryServerInfo{ 230 | Server: srv, 231 | FullMethod: Game_DeleteGame_FullMethodName, 232 | } 233 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 234 | return srv.(GameServer).DeleteGame(ctx, req.(*params.IdsRequest)) 235 | } 236 | return interceptor(ctx, in, info, handler) 237 | } 238 | 239 | // Game_ServiceDesc is the grpc.ServiceDesc for Game service. 240 | // It's only intended for direct use with grpc.RegisterService, 241 | // and not to be introspected or modified (even as a copy) 242 | var Game_ServiceDesc = grpc.ServiceDesc{ 243 | ServiceName: "game.v1.Game", 244 | HandlerType: (*GameServer)(nil), 245 | Methods: []grpc.MethodDesc{ 246 | { 247 | MethodName: "CreateGame", 248 | Handler: _Game_CreateGame_Handler, 249 | }, 250 | { 251 | MethodName: "GetGame", 252 | Handler: _Game_GetGame_Handler, 253 | }, 254 | { 255 | MethodName: "FindGame", 256 | Handler: _Game_FindGame_Handler, 257 | }, 258 | { 259 | MethodName: "UpdateGame", 260 | Handler: _Game_UpdateGame_Handler, 261 | }, 262 | { 263 | MethodName: "DeleteGame", 264 | Handler: _Game_DeleteGame_Handler, 265 | }, 266 | }, 267 | Streams: []grpc.StreamDesc{}, 268 | Metadata: "game-proto/game.proto", 269 | } 270 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Go Cinch Layout

2 | 3 |
4 | Layout是一套轻量级微服务项目模版. 5 |

6 | Go version 7 | Kratos version 8 | MySQL version 9 | Go redis version 10 | Gorm version 11 | Wire version 12 | License 13 |

14 |
15 | 16 | # 简介 17 | 18 | ## 起源 19 | 20 | 你的单体服务架构是否遇到一些问题, 不能满足业务需求? 那么微服务会是好的解决方案. 21 | Cinch是一套轻量级微服务脚手架, 基于[Kratos], 节省基础服务搭建时间, 快速投入业务开发. 22 | 我们参考了Go的许多微服务架构, 结合实际需求, 最终选择简洁的[Kratos]作为基石(B站架构), 从架构的设计思路以及代码的书写格式和我们非常匹配. 23 | > cinch意为简单的事, 小菜. 希望把复杂的事变得简单, 提升开发效率. 24 | 25 | > 若你想深入学习微服务每个组件, 建议直接看Kratos官方文档. 本项目整合一些业务常用组件, 开箱即用, 并不会把每个组件都介绍那么详细. 26 | 27 | ## 特性 28 | 29 | - `Go` - 当前Go版本直接上`v1.20` 30 | - `Proto` - proto协议同时开启gRPC & HTTP支持, 只需开发一次接口, 不用写两套 31 | - `Jwt` - 认证, 用户登入登出一键搞定 32 | - `Action` - 权限, 基于行为的权限校验 33 | - `Redis` - 缓存, 内置防缓存穿透/缓存击穿/缓存雪崩示例 34 | - `Gorm` - 数据库ORM管理框架, 可自行扩展多种数据库类型, 目前使用MySQL, 其他自行扩展 35 | - `Gorm Gen` - Gorm实体自动生成, 无需再写数据库实体映射 36 | - `Tenant` - 基于Gorm的schema层面的多租户(一个租户一个数据库schema) 37 | - `SqlMigrate` - 数据库迁移工具, 每次更新平滑迁移 38 | - `Asynq` - 分布式定时任务(异步任务) 39 | - `Log` - 日志, 在Kratos基础上增加一层包装, 无需每个方法传入 40 | - `Embed` - go 1.16文件嵌入属性, 轻松将静态文件打包到编译后的二进制应用中 41 | - `Opentelemetry` - 链路追踪, 跨服务调用可快速追踪完整链路 42 | - `Idempotent` - 接口幂等性(解决重复点击或提交) 43 | - `Pprof` - 内置性能分析开关, 对并发/性能测试友好 44 | - `Wire` - 依赖注入, 编译时完成依赖注入 45 | - `Swagger` - Api文档一键生成, 无需在代码里写注解 46 | - `I18n` - 国际化支持, 简单切换多语言 47 | 48 | # 当前版本 Current version 49 | 50 | 使用cinch工具快速[创建Game服务](https://github.com/go-cinch/layout#%E5%88%9B%E5%BB%BAgame%E6%9C%8D%E5%8A%A1) 51 | ```bash 52 | go install github.com/go-cinch/cinch/cmd/cinch@latest 53 | 54 | cinch new game 55 | ``` 56 | 57 | ## 在线演示 58 | 59 | [Vue3入口](https://vue3.go-cinch.top/) 60 | [React入口](https://react.go-cinch.top/) 61 | 62 | # 项目结构 63 | 64 | ``` 65 | ├── api // 各个微服务的proto/go文件, proto文件通过submodule管理包含proto后缀 66 | │ ├── auth // auth服务所需的go文件(通过命令生成) 67 | │ ├── auth-proto // auth服务proto文件 68 | │ ├── reason 69 | │ ├── reason-proto // 公共reason(错误码建议统一管理, 不要不同服务搞不同的) 70 | │ ├── xxx // xxx服务所需的go文件(通过命令生成) 71 | │ ├── xxx-proto // xxx服务proto文件 72 | │ └── ... 73 | ├── cmd 74 | │ └── game // 项目名 75 | │ ├── main.go // 程序主入口 76 | │ ├── wire.go // wire依赖注入 77 | │ └── wire_gen.go 78 | ├── configs // 配置文件目录 79 | │ ├── config.yml // 主配置文件 80 | │ ├── client.yml // 配置grpc服务client, 如auth 81 | │ ├── gen.yml // gen gorm或migrate会用到的配置文件 82 | │ └── ... // 其他自定义配置文件以yml/yaml结尾均可 83 | ├── internal // 内部逻辑代码 84 | │ ├── biz // 业务逻辑的组装层, 类似 DDD 的 domain 层, data 类似 DDD 的 repo, 而 repo 接口在这里定义, 使用依赖倒置的原则. 85 | │ │ ├── biz.go 86 | │ │ ├── reason.go // 定义错误描述 87 | │ │ ├── game.go // game业务 88 | │ │ └── xxx.go // 其他业务 89 | │ ├── conf 90 | │ │ ├── conf.pb.go 91 | │ │ └── conf.proto // 内部使用的config的结构定义, 使用proto格式生成 92 | │ ├── data // 业务数据访问, 包含 cache、db 等封装, 实现了 biz 的 repo 接口. 我们可能会把 data 与 dao 混淆在一起, data 偏重业务的含义, 它所要做的是将领域对象重新拿出来, 我们去掉了 DDD 的 infra层. 93 | │ │ ├── model // gorm gen生成model目录 94 | │ │ ├── query // gorm gen生成query目录 95 | │ │ ├── cache.go // cache层, 防缓存击穿/缓存穿透/缓存雪崩 96 | │ │ ├── client.go // 各个微服务client初始化 97 | │ │ ├── ctx.go 98 | │ │ ├── data.go // 数据初始化, 如DB/Redis 99 | │ │ ├── game.go 100 | │ │ └── tracer.go // 链路追踪tracer初始化 101 | │ ├── db 102 | │ │ ├── migrations // sql迁移文件目录, 每一次数据库变更都放在这里, 参考https://github.com/rubenv/sql-migrate 103 | │ │ │ ├── xxx.sql // sql文件 104 | │ │ │ └── ... 105 | │ │ └── migrate.go // embed sql文件 106 | │ ├── pkg // 自定义扩展包 107 | │ │ ├── idempotent // 接口幂等性 108 | │ │ ├── task // 异步任务, 内部调用asynq 109 | │ │ └── xxx // 其他扩展 110 | │ ├── server // http和grpc实例的创建和配置 111 | │ │ ├── middleware // 自定义中间件 112 | │ │ │ ├── locales // i18n多语言map配置文件 113 | │ │ │ └── xxx.go // 一些中间件 114 | │ │ ├── grpc.go 115 | │ │ ├── http.go 116 | │ │ └── server.go 117 | │ └── service // 实现了 api 定义的服务层, 类似 DDD 的 application 层, 处理 DTO 到 biz 领域实体的转换(DTO -> DO), 同时协同各类 biz 交互, 但是不应处理复杂逻辑 118 | │ ├── service.go 119 | │ ├── game.go // game接口入口 120 | │ └── xxx.go // 其他接口入口 121 | ├── third_party // api依赖的第三方proto, 编译proto文件需要用到 122 | │ ├── cinch // cinch公共依赖 123 | │ ├── errors 124 | │ ├── google 125 | │ ├── openapi 126 | │ │── validate 127 | │ └── ... // 其他自定义依赖 128 | ├─ .gitignore 129 | ├─ .gitmodules // submodule配置文件 130 | ├─ .golangci.yml // golangci-lint 131 | ├─ Dockerfile 132 | ├─ go.mod 133 | ├─ go.sum 134 | ├─ LICENSE 135 | ├─ Makefile 136 | └─ README.md 137 | ``` 138 | 139 | # 环境准备 140 | 141 | 启动项目前, 我默认你已准备好(部分软件按建议方式安装即可): 142 | 143 | - [go](https://golang.org/dl)1.18+(建议使用[g](https://github.com/voidint/g)) 144 | ```bash 145 | # sudo apt update 146 | # sudo apt install -y curl 147 | curl -sSL https://raw.githubusercontent.com/voidint/g/master/install.sh | bash 148 | source "$HOME/.g/env" 149 | # g --version 150 | # g version 1.5.0 151 | 152 | g install 1.20 153 | # go version 154 | # go version go1.20 linux/amd64 155 | 156 | echo "export GOPATH=/home/ubuntu/go" >> ~/.bashrc 157 | # 设置go/bin目录到PATH, 若不设置, go安装的一些文件无法识别 158 | echo "export PATH=$PATH:/home/ubuntu/.g/go/bin:/home/ubuntu/go/bin" >> ~/.bashrc 159 | source ~/.bashrc 160 | ``` 161 | - 开启go modules 162 | - [mysql](https://www.mysql.com)(本地测试建议使用docker-compose搭建) 163 | - [redis](https://redis.io)(本地测试建议使用docker-compose搭建) 164 | ```bash 165 | # 安装docker 166 | sudo apt-get update 167 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 168 | sudo apt-key fingerprint 0EBFCD88 169 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 170 | sudo apt-get update 171 | sudo apt-get install -y docker-ce 172 | # sudo docker -v 173 | # Docker version 23.0.1, build a5ee5b1 174 | 175 | # 国内加速安装 176 | sudo apt-get update 177 | sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common 178 | curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add - 179 | sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" 180 | sudo apt-get update 181 | sudo apt-get install -y docker-ce 182 | # sudo docker -v 183 | # Docker version 23.0.1, build a5ee5b1 184 | 185 | # 去除docker sudo 186 | sudo groupadd docker 187 | sudo gpasswd -a ${USER} docker 188 | sudo systemctl restart docker 189 | sudo chmod a+rw /var/run/docker.sock 190 | # docker -v 191 | # Docker version 23.0.1, build a5ee5b1 192 | 193 | # docker-compose 194 | sudo curl -L https://github.com/docker/compose/releases/download/v2.10.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 195 | sudo chmod +x /usr/local/bin/docker-compose 196 | # docker-compose -v 197 | # Docker Compose version v2.10.2 198 | 199 | 200 | # 简单启动一个单机版mysql和redis 201 | git clone https://github.com/go-cinch/compose 202 | cd compose/single 203 | # 修改默认密码 204 | source myenv 205 | docker-compose -f docker-compose.db.yml up -d redis mysql 206 | # docker ps 207 | # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 208 | # 918328d0aae1 mysql:8.0.19 "docker-entrypoint.s…" About an hour ago Up 59 minutes 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql 209 | # 918b2cfcd72e redis:7.0 "docker-entrypoint.s…" About an hour ago Up 59 minutes 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp redis 210 | ``` 211 | - [protoc](https://github.com/protocolbuffers/protobuf) 212 | ```bash 213 | curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v3.20.3/protoc-3.20.3-`uname -s`-`uname -m`.zip 214 | # apt install -y unzip 215 | sudo unzip protoc-3.20.3-`uname -s`-`uname -m`.zip -d /usr 216 | # protoc --version 217 | # libprotoc 3.20.3 218 | ``` 219 | - [protoc-gen-go](https://github.com/protocolbuffers/protobuf-go) 220 | ```bash 221 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.30.0 222 | # protoc-gen-go --version 223 | # protoc-gen-go v1.30.0 224 | ``` 225 | - [git](https://git-scm.com) 226 | ```bash 227 | sudo apt update 228 | sudo apt install -y git 229 | ``` 230 | - cinch cli工具 231 | ```bash 232 | go install github.com/go-cinch/cinch/cmd/cinch@latest 233 | # cinch -v 234 | cinch version v1.0.0 235 | ``` 236 | 237 | # [Auth服务](https://github.com/go-cinch/auth) 238 | 239 | 权限认证服务无需再开发, 下载开箱即用 240 | 241 | ```bash 242 | git clone https://github.com/go-cinch/auth 243 | # 可以指定tag 244 | # git clone -b v1.0.3 https://github.com/go-cinch/auth 245 | ``` 246 | 247 | # 创建Game服务 248 | 249 | ```bash 250 | # 1.通过模板创建项目 251 | cinch new game 252 | # -r 指定仓库 -b 指定分支 253 | # cinch new game -r https://github.com/go-cinch/layout.git -b dev 254 | 255 | # 2. 进入项目 256 | cd game 257 | # 创建个人开发分支 258 | git init -b mark/dev 259 | # 如果你的git版本较低 260 | # git init 261 | # git checkout -b mark/dev 262 | 263 | # 3. 初始化submodule(不想使用可忽略此步骤) 264 | # -b指定分支 --name指定submodule名称 265 | git submodule add -b master --name api/auth-proto https://github.com/go-cinch/auth-proto.git ./api/auth-proto 266 | 267 | # -b指定分支 --name指定submodule名称 268 | git submodule add -b master --name api/reason-proto https://github.com/go-cinch/reason-proto.git ./api/reason-proto 269 | 270 | # 这里用game作为示例, 按需修改 271 | # -b指定分支 --name指定submodule名称 272 | git submodule add -b master --name api/game-proto https://github.com/go-cinch/game-proto.git ./api/game-proto 273 | 274 | # 删除一个已经存在的submodule 275 | # git submodule deinit api/game-proto 276 | # git rm --cached api/game-proto 277 | # rm -rf .git/modules/api/game-proto 278 | # rm -rf api/game-proto 279 | 280 | # 4. 初始化依赖项(需确保已经安装make) 281 | # sudo apt install -y make 282 | make init 283 | 284 | # 5. 编译项目 285 | make all 286 | ``` 287 | 288 | # 启动 289 | 290 | ## 配置文件 291 | 292 | ```bash 293 | # 修改auth项目配置 294 | cd auth 295 | # 将mysql/redis的配置修改成你本地配置 296 | vim configs/config.yml 297 | 298 | # 修改game项目配置 299 | cd game 300 | # 将mysql/redis的配置修改成你本地配置 301 | vim game/configs/config.yml 302 | # 将auth服务host和端口修改成你本地配置 303 | vim game/configs/client.yml 304 | 305 | # 启动auth 306 | cd auth 307 | cinch run 308 | 309 | # 启动game 310 | cd game 311 | cinch run 312 | ``` 313 | 314 | ## 环境变量 315 | 316 | ```bash 317 | # 启动auth 318 | # 如果你用的是compose/single 319 | export AUTH_DATA_DATABASE_DSN='root:mysqlrootpwd@tcp(127.0.0.1:3306)/auth?parseTime=True' 320 | export AUTH_DATA_REDIS_DSN='redis://:redispwd@127.0.0.1:6379/0' 321 | # 其他按需修改 322 | export AUTH_DATA_DATABASE_DSN='root:root@tcp(127.0.0.1:3306)/auth?parseTime=True' 323 | export AUTH_DATA_REDIS_DSN='redis://127.0.0.1:6379/0' 324 | cd auth 325 | cinch run 326 | 327 | # 启动game 328 | # 如果你用的是compose/single 329 | export GAME_DATA_DATABASE_DSN='root:mysqlrootpwd@tcp(127.0.0.1:3306)/game?parseTime=True' 330 | export GAME_DATA_REDIS_DSN='redis://:redispwd@127.0.0.1:6379/0' 331 | # 其他按需修改 332 | export GAME_DATA_DATABASE_DSN='root:root@tcp(127.0.0.1:3306)/game?parseTime=True' 333 | export GAME_DATA_REDIS_DSN='redis://127.0.0.1:6379/0' 334 | # 设置auth服务 335 | export GAME_CLIENT_AUTH='127.0.0.1:6160' 336 | cd game 337 | cinch run 338 | ``` 339 | 340 | > Tip: 环境变量前缀可在cmd/xxx/main.go中修改, 341 | 参见[环境变量前缀](/base/0.config?id=%e7%8e%af%e5%a2%83%e5%8f%98%e9%87%8f%e5%89%8d%e7%bc%80) 342 | 343 | ## 测试访问 344 | 345 | auth服务: 346 | 347 | ```bash 348 | curl http://127.0.0.1:6060/idempotent 349 | # 输出如下说明服务通了只是没有权限, 出现其他说明配置有误 350 | # {"code":401, "reason":"UNAUTHORIZED", "message":"token is missing", "metadata":{}} 351 | ``` 352 | 353 | game服务: 354 | 355 | ```bash 356 | curl http://127.0.0.1:8080/hello/world 357 | # 输出如下说明服务通了只是没有权限, 出现其他说明配置有误 358 | # {"code":401, "reason":"UNAUTHORIZED", "message":"token is missing", "metadata":{}} 359 | ``` 360 | 361 | > 至此, 微服务已启动完毕, auth以及game, 接下来可以自定义你的game啦~ 362 | 363 | # 文档 364 | 365 | 以下几个较为常用, 当然你也可以按顺序查看[Docs](https://go-cinch.github.io/docs) 366 | 367 | - [第一个CRUD](https://go-cinch.github.io/docs/#/started/1.first-api) 368 | - [配置](https://go-cinch.github.io/docs/#/base/0.config) 369 | - [数据库](https://go-cinch.github.io/docs/#/base/2.db) 370 | - [Make命令](https://go-cinch.github.io/docs/#/base/4.make) 371 | - [错误管理](https://go-cinch.github.io/docs/#/base/5.reason) 372 | 373 | [Kratos]: (https://go-kratos.dev/docs/) 374 | 375 | -------------------------------------------------------------------------------- /third_party/google/api/http.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | option cc_enable_arenas = true; 20 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 21 | option java_multiple_files = true; 22 | option java_outer_classname = "HttpProto"; 23 | option java_package = "com.google.api"; 24 | option objc_class_prefix = "GAPI"; 25 | 26 | // Defines the HTTP configuration for an API service. It contains a list of 27 | // [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method 28 | // to one or more HTTP REST API methods. 29 | message Http { 30 | // A list of HTTP configuration rules that apply to individual API methods. 31 | // 32 | // **NOTE:** All service configuration rules follow "last one wins" order. 33 | repeated HttpRule rules = 1; 34 | 35 | // When set to true, URL path parameters will be fully URI-decoded except in 36 | // cases of single segment matches in reserved expansion, where "%2F" will be 37 | // left encoded. 38 | // 39 | // The default behavior is to not decode RFC 6570 reserved characters in multi 40 | // segment matches. 41 | bool fully_decode_reserved_expansion = 2; 42 | } 43 | 44 | // # gRPC Transcoding 45 | // 46 | // gRPC Transcoding is a feature for mapping between a gRPC method and one or 47 | // more HTTP REST endpoints. It allows developers to build a single API service 48 | // that supports both gRPC APIs and REST APIs. Many systems, including [Google 49 | // APIs](https://github.com/googleapis/googleapis), 50 | // [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC 51 | // Gateway](https://github.com/grpc-ecosystem/grpc-gateway), 52 | // and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature 53 | // and use it for large scale production services. 54 | // 55 | // `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies 56 | // how different portions of the gRPC request message are mapped to the URL 57 | // path, URL query parameters, and HTTP request body. It also controls how the 58 | // gRPC response message is mapped to the HTTP response body. `HttpRule` is 59 | // typically specified as an `google.api.http` annotation on the gRPC method. 60 | // 61 | // Each mapping specifies a URL path template and an HTTP method. The path 62 | // template may refer to one or more fields in the gRPC request message, as long 63 | // as each field is a non-repeated field with a primitive (non-message) type. 64 | // The path template controls how fields of the request message are mapped to 65 | // the URL path. 66 | // 67 | // Example: 68 | // 69 | // service Messaging { 70 | // rpc GetMessage(GetMessageRequest) returns (Message) { 71 | // option (google.api.http) = { 72 | // get: "/v1/{name=messages/*}" 73 | // }; 74 | // } 75 | // } 76 | // message GetMessageRequest { 77 | // string name = 1; // Mapped to URL path. 78 | // } 79 | // message Message { 80 | // string text = 1; // The resource content. 81 | // } 82 | // 83 | // This enables an HTTP REST to gRPC mapping as below: 84 | // 85 | // HTTP | gRPC 86 | // -----|----- 87 | // `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` 88 | // 89 | // Any fields in the request message which are not bound by the path template 90 | // automatically become HTTP query parameters if there is no HTTP request body. 91 | // For example: 92 | // 93 | // service Messaging { 94 | // rpc GetMessage(GetMessageRequest) returns (Message) { 95 | // option (google.api.http) = { 96 | // get:"/v1/messages/{message_id}" 97 | // }; 98 | // } 99 | // } 100 | // message GetMessageRequest { 101 | // message SubMessage { 102 | // string subfield = 1; 103 | // } 104 | // string message_id = 1; // Mapped to URL path. 105 | // int64 revision = 2; // Mapped to URL query parameter `revision`. 106 | // SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. 107 | // } 108 | // 109 | // This enables a HTTP JSON to RPC mapping as below: 110 | // 111 | // HTTP | gRPC 112 | // -----|----- 113 | // `GET /v1/messages/123456?revision=2&sub.subfield=foo` | 114 | // `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: 115 | // "foo"))` 116 | // 117 | // Note that fields which are mapped to URL query parameters must have a 118 | // primitive type or a repeated primitive type or a non-repeated message type. 119 | // In the case of a repeated type, the parameter can be repeated in the URL 120 | // as `...?param=A¶m=B`. In the case of a message type, each field of the 121 | // message is mapped to a separate parameter, such as 122 | // `...?foo.a=A&foo.b=B&foo.c=C`. 123 | // 124 | // For HTTP methods that allow a request body, the `body` field 125 | // specifies the mapping. Consider a REST update method on the 126 | // message resource collection: 127 | // 128 | // service Messaging { 129 | // rpc UpdateMessage(UpdateMessageRequest) returns (Message) { 130 | // option (google.api.http) = { 131 | // patch: "/v1/messages/{message_id}" 132 | // body: "message" 133 | // }; 134 | // } 135 | // } 136 | // message UpdateMessageRequest { 137 | // string message_id = 1; // mapped to the URL 138 | // Message message = 2; // mapped to the body 139 | // } 140 | // 141 | // The following HTTP JSON to RPC mapping is enabled, where the 142 | // representation of the JSON in the request body is determined by 143 | // protos JSON encoding: 144 | // 145 | // HTTP | gRPC 146 | // -----|----- 147 | // `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: 148 | // "123456" message { text: "Hi!" })` 149 | // 150 | // The special name `*` can be used in the body mapping to define that 151 | // every field not bound by the path template should be mapped to the 152 | // request body. This enables the following alternative definition of 153 | // the update method: 154 | // 155 | // service Messaging { 156 | // rpc UpdateMessage(Message) returns (Message) { 157 | // option (google.api.http) = { 158 | // patch: "/v1/messages/{message_id}" 159 | // body: "*" 160 | // }; 161 | // } 162 | // } 163 | // message Message { 164 | // string message_id = 1; 165 | // string text = 2; 166 | // } 167 | // 168 | // 169 | // The following HTTP JSON to RPC mapping is enabled: 170 | // 171 | // HTTP | gRPC 172 | // -----|----- 173 | // `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: 174 | // "123456" text: "Hi!")` 175 | // 176 | // Note that when using `*` in the body mapping, it is not possible to 177 | // have HTTP parameters, as all fields not bound by the path end in 178 | // the body. This makes this option more rarely used in practice when 179 | // defining REST APIs. The common usage of `*` is in custom methods 180 | // which don't use the URL at all for transferring data. 181 | // 182 | // It is possible to define multiple HTTP methods for one RPC by using 183 | // the `additional_bindings` option. Example: 184 | // 185 | // service Messaging { 186 | // rpc GetMessage(GetMessageRequest) returns (Message) { 187 | // option (google.api.http) = { 188 | // get: "/v1/messages/{message_id}" 189 | // additional_bindings { 190 | // get: "/v1/users/{user_id}/messages/{message_id}" 191 | // } 192 | // }; 193 | // } 194 | // } 195 | // message GetMessageRequest { 196 | // string message_id = 1; 197 | // string user_id = 2; 198 | // } 199 | // 200 | // This enables the following two alternative HTTP JSON to RPC mappings: 201 | // 202 | // HTTP | gRPC 203 | // -----|----- 204 | // `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` 205 | // `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: 206 | // "123456")` 207 | // 208 | // ## Rules for HTTP mapping 209 | // 210 | // 1. Leaf request fields (recursive expansion nested messages in the request 211 | // message) are classified into three categories: 212 | // - Fields referred by the path template. They are passed via the URL path. 213 | // - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP 214 | // request body. 215 | // - All other fields are passed via the URL query parameters, and the 216 | // parameter name is the field path in the request message. A repeated 217 | // field can be represented as multiple query parameters under the same 218 | // name. 219 | // 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields 220 | // are passed via URL path and HTTP request body. 221 | // 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all 222 | // fields are passed via URL path and URL query parameters. 223 | // 224 | // ### Path template syntax 225 | // 226 | // Template = "/" Segments [ Verb ] ; 227 | // Segments = Segment { "/" Segment } ; 228 | // Segment = "*" | "**" | LITERAL | Variable ; 229 | // Variable = "{" FieldPath [ "=" Segments ] "}" ; 230 | // FieldPath = IDENT { "." IDENT } ; 231 | // Verb = ":" LITERAL ; 232 | // 233 | // The syntax `*` matches a single URL path segment. The syntax `**` matches 234 | // zero or more URL path segments, which must be the last part of the URL path 235 | // except the `Verb`. 236 | // 237 | // The syntax `Variable` matches part of the URL path as specified by its 238 | // template. A variable template must not contain other variables. If a variable 239 | // matches a single path segment, its template may be omitted, e.g. `{var}` 240 | // is equivalent to `{var=*}`. 241 | // 242 | // The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` 243 | // contains any reserved character, such characters should be percent-encoded 244 | // before the matching. 245 | // 246 | // If a variable contains exactly one path segment, such as `"{var}"` or 247 | // `"{var=*}"`, when such a variable is expanded into a URL path on the client 248 | // side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The 249 | // server side does the reverse decoding. Such variables show up in the 250 | // [Discovery 251 | // Document](https://developers.google.com/discovery/v1/reference/apis) as 252 | // `{var}`. 253 | // 254 | // If a variable contains multiple path segments, such as `"{var=foo/*}"` 255 | // or `"{var=**}"`, when such a variable is expanded into a URL path on the 256 | // client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. 257 | // The server side does the reverse decoding, except "%2F" and "%2f" are left 258 | // unchanged. Such variables show up in the 259 | // [Discovery 260 | // Document](https://developers.google.com/discovery/v1/reference/apis) as 261 | // `{+var}`. 262 | // 263 | // ## Using gRPC API Service Configuration 264 | // 265 | // gRPC API Service Configuration (service config) is a configuration language 266 | // for configuring a gRPC service to become a user-facing product. The 267 | // service config is simply the YAML representation of the `google.api.Service` 268 | // proto message. 269 | // 270 | // As an alternative to annotating your proto file, you can configure gRPC 271 | // transcoding in your service config YAML files. You do this by specifying a 272 | // `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same 273 | // effect as the proto annotation. This can be particularly useful if you 274 | // have a proto that is reused in multiple services. Note that any transcoding 275 | // specified in the service config will override any matching transcoding 276 | // configuration in the proto. 277 | // 278 | // Example: 279 | // 280 | // http: 281 | // rules: 282 | // # Selects a gRPC method and applies HttpRule to it. 283 | // - selector: example.v1.Messaging.GetMessage 284 | // get: /v1/messages/{message_id}/{sub.subfield} 285 | // 286 | // ## Special notes 287 | // 288 | // When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the 289 | // proto to JSON conversion must follow the [proto3 290 | // specification](https://developers.google.com/protocol-buffers/docs/proto3#json). 291 | // 292 | // While the single segment variable follows the semantics of 293 | // [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String 294 | // Expansion, the multi segment variable **does not** follow RFC 6570 Section 295 | // 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion 296 | // does not expand special characters like `?` and `#`, which would lead 297 | // to invalid URLs. As the result, gRPC Transcoding uses a custom encoding 298 | // for multi segment variables. 299 | // 300 | // The path variables **must not** refer to any repeated or mapped field, 301 | // because client libraries are not capable of handling such variable expansion. 302 | // 303 | // The path variables **must not** capture the leading "/" character. The reason 304 | // is that the most common use case "{var}" does not capture the leading "/" 305 | // character. For consistency, all path variables must share the same behavior. 306 | // 307 | // Repeated message fields must not be mapped to URL query parameters, because 308 | // no client library can support such complicated mapping. 309 | // 310 | // If an API needs to use a JSON array for request or response body, it can map 311 | // the request or response body to a repeated field. However, some gRPC 312 | // Transcoding implementations may not support this feature. 313 | message HttpRule { 314 | // Selects a method to which this rule applies. 315 | // 316 | // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. 317 | string selector = 1; 318 | 319 | // Determines the URL pattern is matched by this rules. This pattern can be 320 | // used with any of the {get|put|post|delete|patch} methods. A custom method 321 | // can be defined using the 'custom' field. 322 | oneof pattern { 323 | // Maps to HTTP GET. Used for listing and getting information about 324 | // resources. 325 | string get = 2; 326 | 327 | // Maps to HTTP PUT. Used for replacing a resource. 328 | string put = 3; 329 | 330 | // Maps to HTTP POST. Used for creating a resource or performing an action. 331 | string post = 4; 332 | 333 | // Maps to HTTP DELETE. Used for deleting a resource. 334 | string delete = 5; 335 | 336 | // Maps to HTTP PATCH. Used for updating a resource. 337 | string patch = 6; 338 | 339 | // The custom pattern is used for specifying an HTTP method that is not 340 | // included in the `pattern` field, such as HEAD, or "*" to leave the 341 | // HTTP method unspecified for this rule. The wild-card rule is useful 342 | // for services that provide content to Web (HTML) clients. 343 | CustomHttpPattern custom = 8; 344 | } 345 | 346 | // The name of the request field whose value is mapped to the HTTP request 347 | // body, or `*` for mapping all request fields not captured by the path 348 | // pattern to the HTTP body, or omitted for not having any HTTP request body. 349 | // 350 | // NOTE: the referred field must be present at the top-level of the request 351 | // message type. 352 | string body = 7; 353 | 354 | // Optional. The name of the response field whose value is mapped to the HTTP 355 | // response body. When omitted, the entire response message will be used 356 | // as the HTTP response body. 357 | // 358 | // NOTE: The referred field must be present at the top-level of the response 359 | // message type. 360 | string response_body = 12; 361 | 362 | // Additional HTTP bindings for the selector. Nested bindings must 363 | // not contain an `additional_bindings` field themselves (that is, 364 | // the nesting may only be one level deep). 365 | repeated HttpRule additional_bindings = 11; 366 | } 367 | 368 | // A custom pattern is used for defining custom HTTP verb. 369 | message CustomHttpPattern { 370 | // The name of this custom HTTP verb. 371 | string kind = 1; 372 | 373 | // The path matched by this custom verb. 374 | string path = 2; 375 | } 376 | --------------------------------------------------------------------------------