├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── api └── lottery │ └── v1 │ ├── lottery.pb.go │ ├── lottery.proto │ ├── lottery_grpc.pb.go │ └── lottery_http.pb.go ├── cmd └── lotterysvr │ ├── main.go │ ├── wire.go │ └── wire_gen.go ├── configs └── config.yaml ├── go.mod ├── go.sum ├── img ├── image-20240810160430856.png ├── image-20240810160756296.png └── image-20240810161401268.png ├── internal ├── biz │ ├── README.md │ ├── admin.go │ ├── biz.go │ ├── blackip.go │ ├── blackuser.go │ ├── coupon.go │ ├── entity.go │ ├── limit.go │ ├── lottery.go │ ├── lotterytimes.go │ ├── prize.go │ └── result.go ├── conf │ ├── conf.pb.go │ └── conf.proto ├── constant │ ├── const.go │ ├── error.go │ └── prize.go ├── data │ ├── blackip.go │ ├── blackuser.go │ ├── coupon.go │ ├── data.go │ ├── lotterytimes.go │ ├── prize.go │ └── result.go ├── interfaces │ ├── admin.go │ ├── admin_test.go │ ├── entity.go │ ├── interfaces.go │ ├── lotteryv1.go │ ├── lotteryv2.go │ ├── lotteryv3.go │ └── routes.go ├── server │ ├── grpc.go │ ├── http.go │ ├── middleware.go │ └── server.go ├── service │ ├── README.md │ ├── admin.go │ ├── entity.go │ ├── error_info.go │ ├── lotteryv1.go │ ├── lotteryv2.go │ ├── lotteryv3.go │ └── service.go ├── task │ ├── task.go │ ├── type.go │ └── user.go └── utils │ ├── utils.go │ └── utils_test.go ├── openapi.yaml ├── scripts ├── lottery.lua ├── test.lua ├── wrkv1.sh ├── wrkv2.sh └── wrkv3.sh ├── sql └── lottery.sql └── third_party ├── README.md ├── errors └── errors.proto ├── google ├── api │ ├── annotations.proto │ ├── client.proto │ ├── field_behavior.proto │ ├── http.proto │ └── httpbody.proto └── protobuf │ ├── any.proto │ ├── api.proto │ ├── compiler │ └── plugin.proto │ ├── descriptor.proto │ ├── duration.proto │ ├── empty.proto │ ├── field_mask.proto │ ├── source_context.proto │ ├── struct.proto │ ├── timestamp.proto │ ├── type.proto │ └── wrappers.proto ├── openapi └── v3 │ ├── annotations.proto │ └── openapi.proto └── validate ├── README.md └── validate.proto /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.log 3 | .DS_Store -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18 AS builder 2 | 3 | COPY . /src 4 | WORKDIR /src 5 | 6 | RUN GOPROXY=https://goproxy.cn make build 7 | 8 | FROM debian:stable-slim 9 | 10 | RUN apt-get update && apt-get install -y --no-install-recommends \ 11 | ca-certificates \ 12 | netbase \ 13 | && rm -rf /var/lib/apt/lists/ \ 14 | && apt-get autoremove -y && apt-get autoclean -y 15 | 16 | COPY --from=builder /src/bin /app 17 | 18 | WORKDIR /app 19 | 20 | EXPOSE 8000 21 | EXPOSE 9000 22 | VOLUME /data/conf 23 | 24 | CMD ["./server", "-conf", "/data/conf"] 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 go-kratos 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 | -------------------------------------------------------------------------------- /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 | #Git_Bash=$(subst \,/,$(subst cmd\,bin\bash.exe,$(dir $(shell where git)))) 11 | Git_Bash=$(subst \,/,$(subst \cmd,\\bin,$(dir $(shell where git)))bash.exe) 12 | INTERNAL_PROTO_FILES=$(shell $(Git_Bash) -c "find internal -name *.proto") 13 | API_PROTO_FILES=$(shell $(Git_Bash) -c "find api -name *.proto") 14 | else 15 | INTERNAL_PROTO_FILES=$(shell find internal -name *.proto) 16 | API_PROTO_FILES=$(shell find api -name *.proto) 17 | endif 18 | 19 | .PHONY: init 20 | # init env 21 | init: 22 | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 23 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 24 | go install github.com/go-kratos/kratos/cmd/kratos/v2@latest 25 | go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest 26 | go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest 27 | go install github.com/google/wire/cmd/wire@latest 28 | 29 | .PHONY: config 30 | # generate internal proto 31 | config: 32 | protoc --proto_path=./internal \ 33 | --proto_path=./third_party \ 34 | --go_out=paths=source_relative:./internal \ 35 | $(INTERNAL_PROTO_FILES) 36 | #protoc --proto_path=./internal/conf --go_out=./internal/conf/ conf.proto 37 | 38 | 39 | .PHONY: api 40 | # generate api proto 41 | api: 42 | protoc --proto_path=./api \ 43 | --proto_path=./third_party \ 44 | --go_out=paths=source_relative:./api \ 45 | --go-errors_out=paths=source_relative:./api \ 46 | --go-http_out=paths=source_relative:./api \ 47 | --go-grpc_out=paths=source_relative:./api \ 48 | --openapi_out=fq_schema_naming=true,default_response=false:. \ 49 | $(API_PROTO_FILES) 50 | 51 | .PHONY: build 52 | # build 53 | build: 54 | mkdir -p bin/ && go build -ldflags "-X main.Version=$(VERSION)" -o ./bin/ ./... 55 | 56 | .PHONY: generate 57 | # generate 58 | generate: 59 | go mod tidy 60 | go get github.com/google/wire/cmd/wire@latest 61 | go generate ./... 62 | 63 | .PHONY: all 64 | # generate all 65 | all: 66 | make api; 67 | make config; 68 | make generate; 69 | 70 | # show help 71 | help: 72 | @echo '' 73 | @echo 'Usage:' 74 | @echo ' make [target]' 75 | @echo '' 76 | @echo 'Targets:' 77 | @awk '/^[a-zA-Z\-\_0-9]+:/ { \ 78 | helpMessage = match(lastLine, /^# (.*)/); \ 79 | if (helpMessage) { \ 80 | helpCommand = substr($$1, 0, index($$1, ":")); \ 81 | helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \ 82 | printf "\033[36m%-22s\033[0m %s\n", helpCommand,helpMessage; \ 83 | } \ 84 | } \ 85 | { lastLine = $$0 }' $(MAKEFILE_LIST) 86 | 87 | .DEFAULT_GOAL := help 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ODSD 2 | The OPPO Store Lucky Draw Platform is an integrated feature within the OPPO online store, offering daily users an exciting opportunity to participate in online lucky draw events. This platform enhances user engagement and rewards customer loyalty through regular prize-winning chances. 3 | 4 | ## Personal Responsibilities 5 | 6 | **System Architecture and Design** 7 | 8 | - Spearheaded the comprehensive requirements analysis, technology selection, and architectural design of the lucky draw system. 9 | - Implemented a microservices architecture, dividing the system into key modules: gateway, usersvr, and lotterysvr. 10 | 11 | **Data Storage Optimization** 12 | 13 | - Designed and implemented a two-tier storage structure using MySQL and Redis. 14 | - This approach effectively addressed high-precision data requirements and managed high-load scenarios, ensuring smooth operation of the lucky draw logic and result storage. 15 | 16 | **Implementation and Development** 17 | 18 | - Utilized the Kratos microservices framework to build the lucky draw service. 19 | - Developed a well-structured codebase with clear module responsibilities and distinct architectural layers. 20 | 21 | ## Technical Challenges and Solutions: 22 | 23 | **Probability Management and Fairness** 24 | 25 | - Implemented a sophisticated system for setting prize probability intervals. 26 | - Utilized random number matching to ensure varied winning probabilities for different prizes while maintaining fairness across users. 27 | 28 | **Performance Optimization** 29 | 30 | - Leveraged various Redis data structures to optimize prize management, blacklist handling, and draw attempt tracking. 31 | - Enhanced performance of eligibility verification and prize matching/distribution logic. 32 | - Improved system throughput from 1,000 QPS to 4,000 QPS. 33 | 34 | **Even Distribution of Prizes** 35 | 36 | - Designed and implemented a Redis-based prize pool system. 37 | - Integrated with prize distribution plans and automated pool replenishment tasks. 38 | - Ensured uniform distribution of winning probabilities throughout the entire campaign period. 39 | - Further optimized performance to achieve 7,000 QPS. 40 | 41 | **Concurrency and Idempotency** 42 | 43 | - Implemented a Redis-based distributed locking mechanism. 44 | - Ensured idempotency of the lucky draw API, preventing multiple simultaneous draw attempts by the same user. 45 | 46 | This project showcases advanced system design, high-performance optimization, and robust implementation of a complex, high-concurrency platform, demonstrating strong skills in microservices architecture, data management, and scalable system development. 47 | 48 | ## Overall Architecture 49 | 50 | The backend architecture is structured in a top-down approach, comprising four distinct layers: 51 | 52 | 1. Access Layer 53 | 2. Interface Layer 54 | 3. Service Layer 55 | 4. Storage Layer 56 | 57 | The Storage Layer primarily utilizes MySQL for relational data storage and Redis for high-performance caching and in-memory data structures. This combination offers a robust and scalable foundation for data management. 58 | 59 | The architectural diagram illustrating this layered structure is as follows: 60 | 61 | ![image-20240810161401268](img/image-20240810161401268.png) -------------------------------------------------------------------------------- /api/lottery/v1/lottery.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.lottery.v1; 4 | 5 | import "google/api/annotations.proto"; 6 | 7 | option go_package = "github.com/bitstormhub/bitstorm/lotterysvr/api/lottery/v1;v1"; 8 | option java_multiple_files = true; 9 | option java_package = "api.lottery.v1"; 10 | 11 | service Lottery { 12 | rpc LotteryV1 (LotteryReq) returns (LotteryRsp){ 13 | option (google.api.http) = { 14 | post: "/lottery", 15 | body: "*" 16 | }; 17 | } 18 | } 19 | 20 | message CommonRspInfo { 21 | int32 code = 1; 22 | string msg = 2; 23 | } 24 | 25 | message LotteryReq { 26 | uint32 user_id = 1; 27 | string user_name = 2; 28 | string ip = 3; 29 | } 30 | 31 | message LotteryPrizeInfo { 32 | uint32 id = 1; 33 | string title = 2; 34 | int32 prize_num = 3; 35 | int32 left_num = 4; 36 | int32 prize_code_low = 5; 37 | int32 prize_code_high = 6; 38 | string img = 7; 39 | uint32 display_order = 8; 40 | uint32 prize_type = 9; 41 | string prize_profile = 10; 42 | string coupon_code = 11; // 如果中奖奖品是优惠券,这个字段位优惠券编码,否则为空 43 | } 44 | 45 | message LotteryRsp { 46 | CommonRspInfo common_rsp = 1; 47 | LotteryPrizeInfo prize_info = 2; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /api/lottery/v1/lottery_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 v3.20.1 5 | // source: lottery/v1/lottery.proto 6 | 7 | package v1 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Lottery_LotteryV1_FullMethodName = "/api.lottery.v1.Lottery/LotteryV1" 23 | ) 24 | 25 | // LotteryClient is the client API for Lottery service. 26 | // 27 | // 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. 28 | type LotteryClient interface { 29 | LotteryV1(ctx context.Context, in *LotteryReq, opts ...grpc.CallOption) (*LotteryRsp, error) 30 | } 31 | 32 | type lotteryClient struct { 33 | cc grpc.ClientConnInterface 34 | } 35 | 36 | func NewLotteryClient(cc grpc.ClientConnInterface) LotteryClient { 37 | return &lotteryClient{cc} 38 | } 39 | 40 | func (c *lotteryClient) LotteryV1(ctx context.Context, in *LotteryReq, opts ...grpc.CallOption) (*LotteryRsp, error) { 41 | out := new(LotteryRsp) 42 | err := c.cc.Invoke(ctx, Lottery_LotteryV1_FullMethodName, in, out, opts...) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return out, nil 47 | } 48 | 49 | // LotteryServer is the server API for Lottery service. 50 | // All implementations must embed UnimplementedLotteryServer 51 | // for forward compatibility 52 | type LotteryServer interface { 53 | LotteryV1(context.Context, *LotteryReq) (*LotteryRsp, error) 54 | mustEmbedUnimplementedLotteryServer() 55 | } 56 | 57 | // UnimplementedLotteryServer must be embedded to have forward compatible implementations. 58 | type UnimplementedLotteryServer struct { 59 | } 60 | 61 | func (UnimplementedLotteryServer) LotteryV1(context.Context, *LotteryReq) (*LotteryRsp, error) { 62 | return nil, status.Errorf(codes.Unimplemented, "method LotteryV1 not implemented") 63 | } 64 | func (UnimplementedLotteryServer) mustEmbedUnimplementedLotteryServer() {} 65 | 66 | // UnsafeLotteryServer may be embedded to opt out of forward compatibility for this service. 67 | // Use of this interface is not recommended, as added methods to LotteryServer will 68 | // result in compilation errors. 69 | type UnsafeLotteryServer interface { 70 | mustEmbedUnimplementedLotteryServer() 71 | } 72 | 73 | func RegisterLotteryServer(s grpc.ServiceRegistrar, srv LotteryServer) { 74 | s.RegisterService(&Lottery_ServiceDesc, srv) 75 | } 76 | 77 | func _Lottery_LotteryV1_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 78 | in := new(LotteryReq) 79 | if err := dec(in); err != nil { 80 | return nil, err 81 | } 82 | if interceptor == nil { 83 | return srv.(LotteryServer).LotteryV1(ctx, in) 84 | } 85 | info := &grpc.UnaryServerInfo{ 86 | Server: srv, 87 | FullMethod: Lottery_LotteryV1_FullMethodName, 88 | } 89 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 90 | return srv.(LotteryServer).LotteryV1(ctx, req.(*LotteryReq)) 91 | } 92 | return interceptor(ctx, in, info, handler) 93 | } 94 | 95 | // Lottery_ServiceDesc is the grpc.ServiceDesc for Lottery service. 96 | // It's only intended for direct use with grpc.RegisterService, 97 | // and not to be introspected or modified (even as a copy) 98 | var Lottery_ServiceDesc = grpc.ServiceDesc{ 99 | ServiceName: "api.lottery.v1.Lottery", 100 | HandlerType: (*LotteryServer)(nil), 101 | Methods: []grpc.MethodDesc{ 102 | { 103 | MethodName: "LotteryV1", 104 | Handler: _Lottery_LotteryV1_Handler, 105 | }, 106 | }, 107 | Streams: []grpc.StreamDesc{}, 108 | Metadata: "lottery/v1/lottery.proto", 109 | } 110 | -------------------------------------------------------------------------------- /api/lottery/v1/lottery_http.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-http. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-http v2.7.1 4 | // - protoc v3.20.1 5 | // source: lottery/v1/lottery.proto 6 | 7 | package v1 8 | 9 | import ( 10 | context "context" 11 | http "github.com/go-kratos/kratos/v2/transport/http" 12 | binding "github.com/go-kratos/kratos/v2/transport/http/binding" 13 | ) 14 | 15 | // This is a compile-time assertion to ensure that this generated file 16 | // is compatible with the kratos package it is being compiled against. 17 | var _ = new(context.Context) 18 | var _ = binding.EncodeURL 19 | 20 | const _ = http.SupportPackageIsVersion1 21 | 22 | const OperationLotteryLotteryV1 = "/api.lottery.v1.Lottery/LotteryV1" 23 | 24 | type LotteryHTTPServer interface { 25 | LotteryV1(context.Context, *LotteryReq) (*LotteryRsp, error) 26 | } 27 | 28 | func RegisterLotteryHTTPServer(s *http.Server, srv LotteryHTTPServer) { 29 | r := s.Route("/") 30 | r.POST("/lottery", _Lottery_LotteryV10_HTTP_Handler(srv)) 31 | } 32 | 33 | func _Lottery_LotteryV10_HTTP_Handler(srv LotteryHTTPServer) func(ctx http.Context) error { 34 | return func(ctx http.Context) error { 35 | var in LotteryReq 36 | if err := ctx.Bind(&in); err != nil { 37 | return err 38 | } 39 | if err := ctx.BindQuery(&in); err != nil { 40 | return err 41 | } 42 | http.SetOperation(ctx, OperationLotteryLotteryV1) 43 | h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { 44 | return srv.LotteryV1(ctx, req.(*LotteryReq)) 45 | }) 46 | out, err := h(ctx, &in) 47 | if err != nil { 48 | return err 49 | } 50 | reply := out.(*LotteryRsp) 51 | return ctx.Result(200, reply) 52 | } 53 | } 54 | 55 | type LotteryHTTPClient interface { 56 | LotteryV1(ctx context.Context, req *LotteryReq, opts ...http.CallOption) (rsp *LotteryRsp, err error) 57 | } 58 | 59 | type LotteryHTTPClientImpl struct { 60 | cc *http.Client 61 | } 62 | 63 | func NewLotteryHTTPClient(client *http.Client) LotteryHTTPClient { 64 | return &LotteryHTTPClientImpl{client} 65 | } 66 | 67 | func (c *LotteryHTTPClientImpl) LotteryV1(ctx context.Context, in *LotteryReq, opts ...http.CallOption) (*LotteryRsp, error) { 68 | var out LotteryRsp 69 | pattern := "/lottery" 70 | path := binding.EncodeURL(pattern, in, false) 71 | opts = append(opts, http.Operation(OperationLotteryLotteryV1)) 72 | opts = append(opts, http.PathTemplate(pattern)) 73 | err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return &out, err 78 | } 79 | -------------------------------------------------------------------------------- /cmd/lotterysvr/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/BitofferHub/lotterysvr/internal/conf" 6 | "github.com/BitofferHub/lotterysvr/internal/task" 7 | "github.com/BitofferHub/pkg/middlewares/discovery" 8 | "github.com/BitofferHub/pkg/middlewares/log" 9 | "github.com/go-kratos/kratos/v2" 10 | "os" 11 | 12 | "github.com/go-kratos/kratos/v2/config" 13 | "github.com/go-kratos/kratos/v2/config/file" 14 | "github.com/go-kratos/kratos/v2/transport/grpc" 15 | "github.com/go-kratos/kratos/v2/transport/http" 16 | 17 | _ "go.uber.org/automaxprocs" 18 | ) 19 | 20 | // go build -ldflags "-X main.Version=x.y.z" 21 | var ( 22 | // Name is the name of the compiled software. 23 | Name string = "lottery-svr" 24 | // Version is the version of the compiled software. 25 | Version string 26 | // flagconf is the config flag. 27 | flagconf string 28 | 29 | id, _ = os.Hostname() 30 | ) 31 | 32 | func init() { 33 | flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml") 34 | 35 | } 36 | 37 | func newApp(gs *grpc.Server, hs *http.Server, ts *task.TaskServer) *kratos.App { 38 | // new reg with etcd client 39 | reg := discovery.GetRegistrar() 40 | return kratos.New( 41 | kratos.ID(id), 42 | kratos.Name(Name), 43 | kratos.Version(Version), 44 | kratos.Metadata(map[string]string{}), 45 | kratos.Server( 46 | gs, 47 | hs, 48 | ts, 49 | ), 50 | kratos.Registrar(reg.Reg), 51 | ) 52 | } 53 | 54 | func main() { 55 | flag.Parse() 56 | c := config.New( 57 | config.WithSource( 58 | file.NewSource(flagconf), 59 | ), 60 | ) 61 | defer c.Close() 62 | 63 | if err := c.Load(); err != nil { 64 | panic(err) 65 | } 66 | 67 | var bc conf.Bootstrap 68 | if err := c.Scan(&bc); err != nil { 69 | panic(err) 70 | } 71 | 72 | InitSource(&bc) 73 | app, cleanup, err := wireApp(bc.GetServer(), bc.GetData()) 74 | if err != nil { 75 | panic(err) 76 | } 77 | defer cleanup() 78 | 79 | // start and wait for stop signal 80 | if err := app.Run(); err != nil { 81 | panic(err) 82 | } 83 | } 84 | 85 | func InitSource(c *conf.Bootstrap) { 86 | l := c.GetLog() 87 | // 初始化日志 88 | log.Init(log.WithLogLevel(l.GetLevel()), 89 | log.WithFileName(l.GetFilename()), 90 | log.WithMaxSize(l.GetMaxSize()), 91 | log.WithMaxBackups(l.GetMaxBackups()), 92 | log.WithLogPath(l.GetLogPath()), 93 | log.WithConsole(l.GetConsole())) 94 | // // 注册服务 95 | discovery.NewRegistrar(c.GetMicro().GetLb().GetAddr()) 96 | } 97 | -------------------------------------------------------------------------------- /cmd/lotterysvr/wire.go: -------------------------------------------------------------------------------- 1 | //go:build wireinject 2 | 3 | // The build tag makes sure the stub is not built in the final build. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/BitofferHub/lotterysvr/internal/biz" 9 | "github.com/BitofferHub/lotterysvr/internal/conf" 10 | "github.com/BitofferHub/lotterysvr/internal/data" 11 | "github.com/BitofferHub/lotterysvr/internal/interfaces" 12 | "github.com/BitofferHub/lotterysvr/internal/server" 13 | "github.com/BitofferHub/lotterysvr/internal/service" 14 | "github.com/BitofferHub/lotterysvr/internal/task" 15 | "github.com/google/wire" 16 | ) 17 | 18 | // wireApp 19 | // 20 | // @Author 狂飙训练营 21 | // @Description: wireApp init kratos application. 22 | // @param *conf.Server 23 | // @param *conf.Data 24 | // @return *kratos.App 25 | // @return func() 26 | // @return error 27 | func wireApp(*conf.Server, *conf.Data) (*kratos.App, func(), error) { 28 | panic(wire.Build( 29 | server.ProviderSet, 30 | data.ProviderSet, 31 | biz.ProviderSet, 32 | service.ProviderSet, 33 | interfaces.ProviderSet, 34 | task.ProviderSet, 35 | newApp)) 36 | } 37 | -------------------------------------------------------------------------------- /cmd/lotterysvr/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate go run github.com/google/wire/cmd/wire 4 | //go:build !wireinject 5 | // +build !wireinject 6 | 7 | package main 8 | 9 | import ( 10 | "github.com/BitofferHub/lotterysvr/internal/biz" 11 | "github.com/BitofferHub/lotterysvr/internal/conf" 12 | "github.com/BitofferHub/lotterysvr/internal/data" 13 | "github.com/BitofferHub/lotterysvr/internal/interfaces" 14 | "github.com/BitofferHub/lotterysvr/internal/server" 15 | "github.com/BitofferHub/lotterysvr/internal/service" 16 | "github.com/BitofferHub/lotterysvr/internal/task" 17 | "github.com/go-kratos/kratos/v2" 18 | ) 19 | 20 | import ( 21 | _ "go.uber.org/automaxprocs" 22 | ) 23 | 24 | // Injectors from wire.go: 25 | 26 | // wireApp 27 | // 28 | // @Author 狂飙训练营 29 | // @Description: wireApp init kratos application. 30 | // @param *conf.Server 31 | // @param *conf.Data 32 | // @return *kratos.App 33 | // @return func() 34 | // @return error 35 | func wireApp(confServer *conf.Server, confData *conf.Data) (*kratos.App, func(), error) { 36 | db := data.NewDatabase(confData) 37 | client := data.NewCache(confData) 38 | dataData := data.NewData(db, client) 39 | prizeRepo := data.NewPrizeRepo(dataData) 40 | couponRepo := data.NewCouponRepo(dataData) 41 | blackUserRepo := data.NewBlackUserRepo(dataData) 42 | blackIpRepo := data.NewBlackIpRepo(dataData) 43 | resultRepo := data.NewResultRepo(dataData) 44 | transaction := data.NewTransaction(dataData) 45 | lotteryCase := biz.NewLotteryCase(prizeRepo, couponRepo, blackUserRepo, blackIpRepo, resultRepo, transaction) 46 | lotteryTimesRepo := data.NewLotteryTimesRepo(dataData) 47 | limitCase := biz.NewLimitCase(blackUserRepo, blackIpRepo, lotteryTimesRepo, transaction) 48 | adminCase := biz.NewAdminCase(prizeRepo, couponRepo, lotteryTimesRepo, resultRepo) 49 | lotteryService := service.NewLotteryService(lotteryCase, limitCase, adminCase) 50 | grpcServer := server.NewGRPCServer(confServer, lotteryService) 51 | adminService := service.NewAdminService(adminCase) 52 | handler := interfaces.NewHandler(lotteryService, adminService) 53 | httpServer := server.NewHTTPServer(confServer, handler) 54 | taskServer := task.NewTaskServer(lotteryService, confServer) 55 | app := newApp(grpcServer, httpServer, taskServer) 56 | return app, func() { 57 | }, nil 58 | } 59 | -------------------------------------------------------------------------------- /configs/config.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | http: 3 | addr: 0.0.0.0:10080 4 | timeout: 1s 5 | grpc: 6 | addr: 0.0.0.0:10081 7 | timeout: 1s 8 | task: 9 | addr: 10 | tasks: 11 | - name: job1 12 | type: "once" 13 | #schedule: "5s" # "5s" "5m" "5h" "5h5m5s" 14 | - name: job2 15 | type: "once" 16 | - name: job3 17 | type: "once" 18 | - name: job4 19 | type: "once" 20 | # - name: job2 21 | # type: "cron" 22 | # schedule: "@every 5s" 23 | # schedule: "*/5 * * * *" 24 | 25 | data: 26 | database: 27 | addr: 192.168.5.51:3306 28 | user: root 29 | password: "123456" 30 | database: lottery_system 31 | max_idle_conn : 2000 32 | max_open_conn: 20000 33 | max_idle_time: 30 34 | slow_threshold_millisecond: 10 # SQL执行超过10ms,就算慢sql 35 | 36 | redis: 37 | addr: 192.168.5.52:6379 38 | password: "123456" 39 | db: 8 40 | pool_size: 20 41 | read_timeout: 2s 42 | write_timeout: 2s 43 | 44 | micro: 45 | lb: 46 | addr: 47 | - "192.168.5.51:20000" 48 | - "192.168.5.51:20002" 49 | - "192.168.5.51:20004" 50 | 51 | log: 52 | level: info 53 | log_path: ./log 54 | max_size: 100 # 日志保留大小,以 M 为单位 55 | max_backups: 3 # 保留文件个数 56 | console: false # false为不打印到终端 57 | filename: lotterysvr.log 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/BitofferHub/lotterysvr 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/BitofferHub/pkg v1.0.2 7 | github.com/BitofferHub/proto_center v1.0.6 8 | github.com/gin-gonic/gin v1.9.1 9 | github.com/go-kratos/kratos/v2 v2.7.2 10 | github.com/go-sql-driver/mysql v1.7.1 11 | github.com/golang-jwt/jwt v3.2.2+incompatible 12 | github.com/google/uuid v1.5.0 13 | github.com/google/wire v0.5.0 14 | github.com/robfig/cron/v3 v3.0.1 15 | go.uber.org/automaxprocs v1.5.3 16 | google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 17 | google.golang.org/grpc v1.60.1 18 | google.golang.org/protobuf v1.32.0 19 | gorm.io/gorm v1.25.5 20 | ) 21 | 22 | require ( 23 | github.com/bytedance/sonic v1.9.1 // indirect 24 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 25 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 26 | github.com/coreos/go-semver v0.3.0 // indirect 27 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect 28 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 29 | github.com/fsnotify/fsnotify v1.6.0 // indirect 30 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 31 | github.com/gin-contrib/sse v0.1.0 // indirect 32 | github.com/go-kratos/aegis v0.2.0 // indirect 33 | github.com/go-kratos/kratos/contrib/registry/etcd/v2 v2.0.0-20240105030612-34d9666e0e1b // indirect 34 | github.com/go-playground/form/v4 v4.2.0 // indirect 35 | github.com/go-playground/locales v0.14.1 // indirect 36 | github.com/go-playground/universal-translator v0.18.1 // indirect 37 | github.com/go-playground/validator/v10 v10.14.0 // indirect 38 | github.com/goccy/go-json v0.10.2 // indirect 39 | github.com/gogo/protobuf v1.3.2 // indirect 40 | github.com/golang/protobuf v1.5.3 // indirect 41 | github.com/gorilla/mux v1.8.0 // indirect 42 | github.com/imdario/mergo v0.3.16 // indirect 43 | github.com/jinzhu/inflection v1.0.0 // indirect 44 | github.com/jinzhu/now v1.1.5 // indirect 45 | github.com/jonboulle/clockwork v0.4.0 // indirect 46 | github.com/json-iterator/go v1.1.12 // indirect 47 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 48 | github.com/leodido/go-urn v1.2.4 // indirect 49 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect 50 | github.com/lestrrat-go/strftime v1.0.6 // indirect 51 | github.com/mattn/go-isatty v0.0.19 // indirect 52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 53 | github.com/modern-go/reflect2 v1.0.2 // indirect 54 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 55 | github.com/pkg/errors v0.9.1 // indirect 56 | github.com/redis/go-redis/v9 v9.4.0 // indirect 57 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 58 | github.com/ugorji/go/codec v1.2.11 // indirect 59 | go.etcd.io/etcd/api/v3 v3.5.11 // indirect 60 | go.etcd.io/etcd/client/pkg/v3 v3.5.11 // indirect 61 | go.etcd.io/etcd/client/v3 v3.5.11 // indirect 62 | go.uber.org/multierr v1.10.0 // indirect 63 | go.uber.org/zap v1.26.0 // indirect 64 | golang.org/x/arch v0.3.0 // indirect 65 | golang.org/x/crypto v0.18.0 // indirect 66 | golang.org/x/net v0.20.0 // indirect 67 | golang.org/x/sync v0.6.0 // indirect 68 | golang.org/x/sys v0.16.0 // indirect 69 | golang.org/x/text v0.14.0 // indirect 70 | google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect 71 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect 72 | gopkg.in/yaml.v3 v3.0.1 // indirect 73 | gorm.io/driver/mysql v1.5.2 // indirect 74 | gorm.io/plugin/dbresolver v1.5.0 // indirect 75 | ) 76 | -------------------------------------------------------------------------------- /img/image-20240810160430856.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShawnWhite666/ODSD/d40fec664fb0b34e233681bdcd1c153679ff6f31/img/image-20240810160430856.png -------------------------------------------------------------------------------- /img/image-20240810160756296.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShawnWhite666/ODSD/d40fec664fb0b34e233681bdcd1c153679ff6f31/img/image-20240810160756296.png -------------------------------------------------------------------------------- /img/image-20240810161401268.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShawnWhite666/ODSD/d40fec664fb0b34e233681bdcd1c153679ff6f31/img/image-20240810161401268.png -------------------------------------------------------------------------------- /internal/biz/README.md: -------------------------------------------------------------------------------- 1 | # Biz 2 | -------------------------------------------------------------------------------- /internal/biz/biz.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | "github.com/google/wire" 6 | ) 7 | 8 | var ProviderSet = wire.NewSet(NewLotteryCase, NewLimitCase, NewAdminCase) 9 | 10 | // Transaction 解耦biz与data层,biz层只调用接口的方法 11 | type Transaction interface { 12 | InTx(context.Context, func(ctx context.Context) error) error 13 | } 14 | -------------------------------------------------------------------------------- /internal/biz/blackip.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import "time" 4 | 5 | // BlackIp ip黑明单表 6 | type BlackIp struct { 7 | Id uint `gorm:"column:id;type:int(10) unsigned;primary_key;AUTO_INCREMENT" json:"id"` 8 | Ip string `gorm:"column:ip;type:varchar(50);comment:IP地址;NOT NULL" json:"ip"` 9 | BlackTime time.Time `gorm:"column:black_time;type:datetime;default:1000-01-01 00:00:00;comment:黑名单限制到期时间;NOT NULL" json:"black_time"` 10 | SysCreated *time.Time `gorm:"autoCreateTime;column:sys_created;type:datetime;default null;comment:创建时间;NOT NULL" json:"sys_created"` 11 | SysUpdated *time.Time `gorm:"autoUpdateTime;column:sys_updated;type:datetime;default null;comment:修改时间;NOT NULL" json:"sys_updated"` 12 | } 13 | 14 | func (m *BlackIp) TableName() string { 15 | return "t_black_ip" 16 | } 17 | 18 | type BlackIpRepo interface { 19 | Get(id uint) (*BlackIp, error) 20 | GetByIP(ip string) (*BlackIp, error) 21 | GetByIPWithCache(ip string) (*BlackIp, error) 22 | GetAll() ([]*BlackIp, error) 23 | CountAll() (int64, error) 24 | Create(blackIp *BlackIp) error 25 | Delete(id uint) error 26 | Update(ip string, blackIp *BlackIp, cols ...string) error 27 | UpdateWithCache(ip string, blackIp *BlackIp, cols ...string) error 28 | GetFromCache(id uint) (*BlackIp, error) 29 | SetByCache(blackIp *BlackIp) error 30 | GetByCache(ip string) (*BlackIp, error) 31 | UpdateByCache(blackIp *BlackIp) error 32 | } 33 | -------------------------------------------------------------------------------- /internal/biz/blackuser.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import "time" 4 | 5 | // BlackUser 用户黑明单表 6 | type BlackUser struct { 7 | Id uint `gorm:"column:id;type:int(10) unsigned;primary_key;AUTO_INCREMENT" json:"id"` 8 | UserId uint `gorm:"column:user_id;type:int(10) unsigned;default:0;comment:用户ID;NOT NULL" json:"user_id"` 9 | UserName string `gorm:"column:user_name;type:varchar(50);comment:用户名;NOT NULL" json:"user_name"` 10 | BlackTime time.Time `gorm:"column:black_time;type:datetime;default:1000-01-01 00:00:00;comment:黑名单限制到期时间;NOT NULL" json:"black_time"` 11 | RealName string `gorm:"column:real_name;type:varchar(50);comment:真是姓名;NOT NULL" json:"real_name"` 12 | Mobile string `gorm:"column:mobile;type:varchar(50);comment:手机号;NOT NULL" json:"mobile"` 13 | Address string `gorm:"column:address;type:varchar(255);comment:联系地址;NOT NULL" json:"address"` 14 | SysCreated *time.Time `gorm:"autoCreateTime;column:sys_created;type:datetime;default null;comment:创建时间;NOT NULL" json:"sys_created"` 15 | SysUpdated *time.Time `gorm:"autoUpdateTime;column:sys_updated;type:datetime;default null;comment:修改时间;NOT NULL" json:"sys_updated"` 16 | SysIp string `gorm:"column:sys_ip;type:varchar(50);comment:IP地址;NOT NULL" json:"sys_ip"` 17 | } 18 | 19 | func (m *BlackUser) TableName() string { 20 | return "t_black_user" 21 | } 22 | 23 | type BlackUserRepo interface { 24 | GetByUserID(uid uint) (*BlackUser, error) 25 | GetByUserIDWithCache(uid uint) (*BlackUser, error) 26 | GetAll() ([]*BlackUser, error) 27 | CountAll() (int64, error) 28 | Create(blackUser *BlackUser) error 29 | Delete(id uint) error 30 | DeleteWithCache(uid uint) error 31 | Update(userID uint, blackUser *BlackUser, cols ...string) error 32 | UpdateWithCache(userID uint, blackUser *BlackUser, cols ...string) error 33 | GetFromCache(id uint) (*BlackUser, error) 34 | GetByCache(uid uint) (*BlackUser, error) 35 | SetByCache(blackUser *BlackUser) error 36 | UpdateByCache(blackUser *BlackUser) error 37 | } 38 | -------------------------------------------------------------------------------- /internal/biz/coupon.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import "time" 4 | 5 | type Coupon struct { 6 | Id uint `gorm:"column:id;type:int(10) unsigned;primary_key;AUTO_INCREMENT" json:"id"` 7 | PrizeId uint `gorm:"column:prize_id;type:int(10) unsigned;default:0;comment:奖品ID,关联lt_prize表;NOT NULL" json:"prize_id"` 8 | Code string `gorm:"column:code;type:varchar(255);comment:虚拟券编码;NOT NULL" json:"code"` 9 | SysCreated *time.Time `gorm:"autoCreateTime;column:sys_created;type:datetime;default null;comment:创建时间;NOT NULL" json:"sys_created"` 10 | SysUpdated *time.Time `gorm:"autoUpdateTime;column:sys_updated;type:datetime;default null;comment:更新时间;NOT NULL" json:"sys_updated"` 11 | SysStatus uint `gorm:"column:sys_status;type:smallint(5) unsigned;default:0;comment:状态,1正常,2作废,2已发放;NOT NULL" json:"sys_status"` 12 | } 13 | 14 | func (c *Coupon) TableName() string { 15 | return "t_coupon" 16 | } 17 | 18 | type CouponRepo interface { 19 | Get(id uint) (*Coupon, error) 20 | GetAll() ([]*Coupon, error) 21 | GetCouponListByPrizeID(prizeID uint) ([]*Coupon, error) 22 | CountAll() (int64, error) 23 | Create(coupon *Coupon) error 24 | Delete(id uint) error 25 | DeleteAllWithCache() error 26 | Update(coupon *Coupon, cols ...string) error 27 | UpdateByCode(code string, coupon *Coupon, cols ...string) error 28 | GetFromCache(id uint) (*Coupon, error) 29 | GetGetNextUsefulCoupon(prizeID, couponID int) (*Coupon, error) 30 | ImportCacheCoupon(prizeID uint, code string) (bool, error) 31 | ReSetCacheCoupon(prizeID uint) (int64, int64, error) 32 | GetCacheCouponNum(prizeID uint) (int64, int64, error) 33 | GetNextUsefulCouponFromCache(prizeID int) (string, error) 34 | } 35 | -------------------------------------------------------------------------------- /internal/biz/entity.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import "time" 4 | 5 | // DayPrizeWeights 定义一天中24小时内,每个小时的发奖比例权重,100的数组,0-23出现的次数为权重大小 6 | var DayPrizeWeights = [100]int{ 7 | 0, 0, 0, 8 | 1, 1, 1, 9 | 2, 2, 2, 10 | 3, 3, 3, 11 | 4, 4, 4, 12 | 5, 5, 5, 13 | 6, 6, 6, 14 | 7, 7, 7, 15 | 8, 8, 8, 8, 8, 8, 8, 16 | 9, 9, 9, 17 | 10, 10, 10, 18 | 11, 11, 11, 19 | 12, 12, 12, 20 | 13, 13, 13, 21 | 14, 14, 14, 14, 14, 14, 14, 22 | 15, 15, 15, 15, 15, 15, 15, 23 | 16, 16, 16, 16, 16, 16, 16, 24 | 17, 17, 17, 17, 17, 17, 17, 25 | 18, 18, 18, 26 | 19, 19, 19, 27 | 20, 20, 20, 20, 20, 20, 20, 28 | 21, 21, 21, 21, 21, 21, 21, 29 | 22, 22, 22, 30 | 23, 23, 23, 31 | } 32 | 33 | // ViewPrize 对外返回的数据(区别于存储层的数据) 34 | type ViewPrize struct { 35 | Id uint `json:"id"` 36 | Title string `json:"title"` 37 | Img string `json:"img"` 38 | PrizeNum int `json:"prize_num"` 39 | PrizeCode string `json:"prize_code"` 40 | PrizeTime uint `json:"prize_time"` 41 | LeftNum int `json:"left_num"` 42 | PrizeType uint `json:"prize_type"` 43 | PrizePlan string `json:"prize_plan"` 44 | BeginTime time.Time `json:"begin_time"` 45 | EndTime time.Time `json:"end_time"` 46 | DisplayOrder uint `json:"display_order"` 47 | SysStatus uint `json:"sys_status"` 48 | } 49 | 50 | type LoginRsp struct { 51 | UserID uint `json:"user_id"` 52 | Token string `json:"token"` 53 | } 54 | 55 | // LotteryPrize 中奖奖品信息 56 | type LotteryPrize struct { 57 | Id uint `json:"id"` 58 | Title string `json:"title"` 59 | PrizeNum int `json:"-"` 60 | LeftNum int `json:"-"` 61 | PrizeCodeLow int `json:"-"` 62 | PrizeCodeHigh int `json:"-"` 63 | Img string `json:"img"` 64 | DisplayOrder uint `json:"display_order"` 65 | PrizeType uint `json:"prize_type"` 66 | PrizeProfile string `json:"prize_profile"` 67 | CouponCode string `json:"coupon_code"` // 如果中奖奖品是优惠券,这个字段位优惠券编码,否则为空 68 | } 69 | 70 | type LotteryUserInfo struct { 71 | UserID uint `json:"user_id"` 72 | UserName string `json:"user_name"` 73 | IP string `json:"ip"` 74 | } 75 | 76 | type ViewCouponInfo struct { 77 | Id uint `json:"id"` 78 | PrizeId uint `json:"prize_id"` 79 | Code string `json:"code"` 80 | SysCreated time.Time `json:"sys_created"` 81 | SysUpdated time.Time `json:"sys_updated"` 82 | SysStatus uint `json:"sys_status"` 83 | } 84 | 85 | type TimePrizeInfo struct { 86 | Time string `json:"time"` 87 | Num int `json:"num"` 88 | } 89 | -------------------------------------------------------------------------------- /internal/biz/limit.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/BitofferHub/lotterysvr/internal/constant" 7 | "github.com/BitofferHub/lotterysvr/internal/utils" 8 | "github.com/BitofferHub/pkg/middlewares/cache" 9 | "github.com/BitofferHub/pkg/middlewares/log" 10 | "math" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | type LimitCase struct { 16 | lotteryTimesRepo LotteryTimesRepo 17 | blackIpRepo BlackIpRepo 18 | blackUserRepo BlackUserRepo 19 | tm Transaction 20 | } 21 | 22 | func NewLimitCase(bur BlackUserRepo, bir BlackIpRepo, ltr LotteryTimesRepo, tm Transaction) *LimitCase { 23 | return &LimitCase{ 24 | blackUserRepo: bur, 25 | blackIpRepo: bir, 26 | lotteryTimesRepo: ltr, 27 | tm: tm, 28 | } 29 | } 30 | 31 | // GetUserCurrentLotteryTimes 获取当天该用户的抽奖次数 32 | func (l *LimitCase) GetUserCurrentLotteryTimes(ctx context.Context, uid uint) (*LotteryTimes, error) { 33 | y, m, d := time.Now().Date() 34 | strDay := fmt.Sprintf("%d%02d%02d", y, m, d) 35 | day, _ := strconv.Atoi(strDay) 36 | lotteryTimes, err := l.lotteryTimesRepo.GetByUserIDAndDay(uid, uint(day)) 37 | if err != nil { 38 | log.ErrorContextf(ctx, "lotteryTimesCase|GetUserCurrentLotteryTimes:%v", err) 39 | return nil, err 40 | } 41 | return lotteryTimes, nil 42 | } 43 | 44 | // CheckUserDayLotteryTimes 判断当天是否还可以进行抽奖 45 | func (l *LimitCase) CheckUserDayLotteryTimes(ctx context.Context, uid uint) (bool, error) { 46 | userLotteryTimes, err := l.GetUserCurrentLotteryTimes(ctx, uid) 47 | if err != nil { 48 | return false, fmt.Errorf("checkUserDayLotteryTimes|err:%v", err) 49 | } 50 | if userLotteryTimes != nil { 51 | // 今天的抽奖记录已经达到了抽奖次数限制 52 | if userLotteryTimes.Num >= constant.UserPrizeMax { 53 | return false, nil 54 | } else { 55 | userLotteryTimes.Num++ 56 | if err := l.lotteryTimesRepo.Update(userLotteryTimes, "num"); err != nil { 57 | return false, fmt.Errorf("updateLotteryTimes|update:%v", err) 58 | } 59 | } 60 | return true, nil 61 | } 62 | y, m, d := time.Now().Date() 63 | strDay := fmt.Sprintf("%d%02d%02d", y, m, d) 64 | day, _ := strconv.Atoi(strDay) 65 | lotteryTimesInfo := &LotteryTimes{ 66 | UserId: uid, 67 | Day: uint(day), 68 | Num: 1, 69 | } 70 | if err := l.lotteryTimesRepo.Create(lotteryTimesInfo); err != nil { 71 | return false, fmt.Errorf("updateLotteryTimes|create:%v", err) 72 | } 73 | return true, nil 74 | } 75 | 76 | func (l *LimitCase) CheckUserDayLotteryTimesWithCache(ctx context.Context, uid uint) (bool, error) { 77 | // 通过缓存验证 78 | userLotteryNum := l.lotteryTimesRepo.IncrUserDayLotteryNum(uid) 79 | //log.InfoContextf(ctx, "CheckUserDayLotteryTimesWithCache|userLotteryNum = %d", userLotteryNum) 80 | // 缓存验证没通过,直接返回 81 | log.Infof("checkUserDayLotteryTimes|uid=%d|userLotteryNum=%d", uid, userLotteryNum) 82 | if userLotteryNum > constant.UserPrizeMax { 83 | return false, nil 84 | } 85 | // 通过数据库验证,还要在数据库中做一次验证 86 | userLotteryTimes, err := l.GetUserCurrentLotteryTimes(ctx, uid) 87 | if err != nil { 88 | return false, fmt.Errorf("checkUserDayLotteryTimes|err:%v", err) 89 | } 90 | if userLotteryTimes != nil { 91 | // 数据库验证今天的抽奖记录已经达到了抽奖次数限制,不能在抽奖 92 | if userLotteryTimes.Num >= constant.UserPrizeMax { 93 | // 缓存数据不可靠,不对,需要更新 94 | if int64(userLotteryTimes.Num) > userLotteryNum { 95 | if err = l.lotteryTimesRepo.InitUserLuckyNum(uid, int64(userLotteryTimes.Num)); err != nil { 96 | return false, fmt.Errorf("LimitCase|CheckUserDayLotteryTimesWithCache:%v", err) 97 | } 98 | } 99 | return false, nil 100 | } else { // 数据库验证通过,今天还可以抽奖 101 | userLotteryTimes.Num++ 102 | // 此时次数抽奖次数增加了,需要更新缓存 103 | if int64(userLotteryTimes.Num) > userLotteryNum { 104 | if err = l.lotteryTimesRepo.InitUserLuckyNum(uid, int64(userLotteryTimes.Num)); err != nil { 105 | return false, fmt.Errorf("LimitCase|CheckUserDayLotteryTimesWithCache:%v", err) 106 | } 107 | } 108 | // 更新数据库 109 | if err = l.lotteryTimesRepo.Update(userLotteryTimes); err != nil { 110 | return false, fmt.Errorf("updateLotteryTimes|update:%v", err) 111 | } 112 | } 113 | return true, nil 114 | } 115 | y, m, d := time.Now().Date() 116 | strDay := fmt.Sprintf("%d%02d%02d", y, m, d) 117 | day, _ := strconv.Atoi(strDay) 118 | lotteryTimesInfo := &LotteryTimes{ 119 | UserId: uid, 120 | Day: uint(day), 121 | Num: 1, 122 | } 123 | if err = l.lotteryTimesRepo.Create(lotteryTimesInfo); err != nil { 124 | return false, fmt.Errorf("updateLotteryTimes|create:%v", err) 125 | } 126 | if err = l.lotteryTimesRepo.InitUserLuckyNum(uid, 1); err != nil { 127 | return false, fmt.Errorf("LimitCase|CheckUserDayLotteryTimesWithCache:%v", err) 128 | } 129 | return true, nil 130 | } 131 | 132 | // CheckIPLimit 验证ip抽奖是否受限制 133 | func (l *LimitCase) CheckIPLimit(ctx context.Context, strIp string) int64 { 134 | ip := utils.Ip4toInt(strIp) 135 | i := ip % constant.IpFrameSize 136 | key := fmt.Sprintf("day_ip_num_%d", i) 137 | ret, err := cache.GetRedisCli().HIncrBy(ctx, key, strIp, 1) 138 | if err != nil { 139 | log.ErrorContextf(ctx, "CheckIPLimit|Incr:%v", err) 140 | return math.MaxInt32 141 | } 142 | return ret 143 | } 144 | 145 | func (l *LimitCase) CheckBlackIP(ctx context.Context, ip string) (bool, *BlackIp, error) { 146 | info, err := l.blackIpRepo.GetByIP(ip) 147 | if err != nil { 148 | log.ErrorContextf(ctx, "CheckBlackIP|GetByIP:%v", err) 149 | return false, nil, fmt.Errorf("CheckBlackIP|GetByIP:%v", err) 150 | } 151 | if info == nil || info.Ip == "" { 152 | return true, nil, nil 153 | } 154 | if time.Now().Before(info.BlackTime) { 155 | // IP黑名单存在,而且还在黑名单有效期内 156 | return false, info, nil 157 | } 158 | return true, info, nil 159 | } 160 | 161 | func (l *LimitCase) CheckBlackIPWithCache(ctx context.Context, ip string) (bool, *BlackIp, error) { 162 | info, err := l.blackIpRepo.GetByIPWithCache(ip) 163 | if err != nil { 164 | log.ErrorContextf(ctx, "CheckBlackIP|GetByIP:%v", err) 165 | return false, nil, fmt.Errorf("CheckBlackIP|GetByIP:%v", err) 166 | } 167 | if info == nil || info.Ip == "" { 168 | return true, nil, nil 169 | } 170 | if time.Now().Before(info.BlackTime) { 171 | // IP黑名单存在,而且还在黑名单有效期内 172 | return false, info, nil 173 | } 174 | return true, info, nil 175 | } 176 | 177 | func (l *LimitCase) CheckBlackUser(ctx context.Context, uid uint) (bool, *BlackUser, error) { 178 | info, err := l.blackUserRepo.GetByUserID(uid) 179 | if err != nil { 180 | log.ErrorContextf(ctx, "CheckBlackUser|Get:%v", err) 181 | return false, nil, fmt.Errorf("CheckBlackUser|Get:%v", err) 182 | } 183 | // 黑名单存在并且有效,不能通过 184 | if info != nil && time.Now().Before(info.BlackTime) { 185 | return false, info, nil 186 | } 187 | return true, info, nil 188 | } 189 | 190 | func (l *LimitCase) CheckBlackUserWithCache(ctx context.Context, uid uint) (bool, *BlackUser, error) { 191 | info, err := l.blackUserRepo.GetByUserIDWithCache(uid) 192 | if err != nil { 193 | log.ErrorContextf(ctx, "CheckBlackUser|Get:%v", err) 194 | return false, nil, fmt.Errorf("CheckBlackUser|Get:%v", err) 195 | } 196 | if info != nil && info.BlackTime.Unix() > time.Now().Unix() { 197 | // 黑名单存在并且有效 198 | return false, info, nil 199 | } 200 | return true, info, nil 201 | } 202 | 203 | func (l *LimitCase) CronJobResetIPLotteryNums() { 204 | l.lotteryTimesRepo.ResetIPLotteryNums() 205 | } 206 | 207 | func (l *LimitCase) CronJobResetUserLotteryNums() { 208 | l.lotteryTimesRepo.ResetUserLotteryNums() 209 | } 210 | -------------------------------------------------------------------------------- /internal/biz/lotterytimes.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import "time" 4 | 5 | // LotteryTimes 用户每日抽奖次数表 6 | type LotteryTimes struct { 7 | Id uint `gorm:"column:id;type:int(10) unsigned;primary_key;AUTO_INCREMENT" json:"id"` 8 | UserId uint `gorm:"column:user_id;type:int(10) unsigned;default:0;comment:用户ID;NOT NULL" json:"user_id"` 9 | Day uint `gorm:"column:day;type:int(10) unsigned;default:0;comment:日期,如:20220625;NOT NULL" json:"day"` 10 | Num uint `gorm:"column:num;type:int(10) unsigned;default:0;comment:次数;NOT NULL" json:"num"` 11 | SysCreated *time.Time `gorm:"autoCreateTime;column:sys_created;type:datetime;default null;comment:创建时间;NOT NULL" json:"sys_created"` 12 | SysUpdated *time.Time `gorm:"autoUpdateTime;column:sys_updated;type:datetime;default null;comment:修改时间;NOT NULL" json:"sys_updated"` 13 | } 14 | 15 | func (l *LotteryTimes) TableName() string { 16 | return "t_lottery_times" 17 | } 18 | 19 | type LotteryTimesRepo interface { 20 | Get(id uint) (*LotteryTimes, error) 21 | GetByUserIDAndDay(uid uint, day uint) (*LotteryTimes, error) 22 | GetAll() ([]*LotteryTimes, error) 23 | CountAll() (int64, error) 24 | Create(lotteryTimes *LotteryTimes) error 25 | Delete(id uint) error 26 | DeleteAll() error 27 | Update(lotteryTimes *LotteryTimes, cols ...string) error 28 | IncrUserDayLotteryNum(uid uint) int64 29 | InitUserLuckyNum(uid uint, num int64) error 30 | ResetIPLotteryNums() 31 | ResetUserLotteryNums() 32 | } 33 | -------------------------------------------------------------------------------- /internal/biz/prize.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import "time" 4 | 5 | // Prize 奖品表 6 | type Prize struct { 7 | Id uint `gorm:"column:id;type:int(10) unsigned;primary_key;AUTO_INCREMENT" json:"id"` 8 | Title string `gorm:"column:title;type:varchar(255);comment:奖品名称;NOT NULL" json:"title"` 9 | PrizeNum int `gorm:"column:prize_num;type:int(11);default:-1;comment:奖品数量,0 无限量,>0限量,<0无奖品;NOT NULL" json:"prize_num"` 10 | LeftNum int `gorm:"column:left_num;type:int(11);default:0;comment:剩余数量;NOT NULL" json:"left_num"` 11 | PrizeCode string `gorm:"column:prize_code;type:varchar(50);comment:0-9999表示100%,0-0表示万分之一的中奖概率;NOT NULL" json:"prize_code"` 12 | PrizeTime uint `gorm:"column:prize_time;type:int(10) unsigned;default:0;comment:发奖周期,多少天,以天为单位;NOT NULL" json:"prize_time"` 13 | Img string `gorm:"column:img;type:varchar(255);comment:奖品图片;NOT NULL" json:"img"` 14 | DisplayOrder uint `gorm:"column:display_order;type:int(10) unsigned;default:0;comment:位置序号,小的排在前面;NOT NULL" json:"display_order"` 15 | PrizeType uint `gorm:"column:prize_type;type:int(10) unsigned;default:0;comment:奖品类型,0 虚拟币,1 虚拟券,2 实物-小奖,3 实物-大奖;NOT NULL" json:"prize_type"` 16 | PrizeProfile string `gorm:"column:prize_profile;type:varchar(255);comment:奖品扩展数据,如:虚拟币数量;NOT NULL" json:"prize_profile"` 17 | BeginTime time.Time `gorm:"column:begin_time;type:datetime;default:1000-01-01 00:00:00;comment:奖品有效周期:开始时间;NOT NULL" json:"begin_time"` 18 | EndTime time.Time `gorm:"column:end_time;type:datetime;default:1000-01-01 00:00:00;comment:奖品有效周期:结束时间;NOT NULL" json:"end_time"` 19 | PrizePlan string `gorm:"column:prize_plan;type:mediumtext;comment:发奖计划,[[时间1,数量1],[时间2,数量2]]" json:"prize_plan"` 20 | PrizeBegin time.Time `gorm:"column:prize_begin;type:int(11);default:1000-01-01 00:00:00;comment:发奖计划周期的开始;NOT NULL" json:"prize_begin"` 21 | PrizeEnd time.Time `gorm:"column:prize_end;type:int(11);default:1000-01-01 00:00:00;comment:发奖计划周期的结束;NOT NULL" json:"prize_end"` 22 | SysStatus uint `gorm:"column:sys_status;type:smallint(5) unsigned;default:0;comment:状态,0 正常,1 删除;NOT NULL" json:"sys_status"` 23 | SysCreated *time.Time `gorm:"autoCreateTime:datetime;column:sys_created;type:datetime;default null;comment:创建时间;NOT NULL" json:"sys_created"` 24 | SysUpdated *time.Time `gorm:"autoUpdateTime:datetime;column:sys_updated;type:datetime;default null;comment:修改时间;NOT NULL" json:"sys_updated"` 25 | SysIp string `gorm:"column:sys_ip;type:varchar(50);comment:操作人IP;NOT NULL" json:"sys_ip"` 26 | } 27 | 28 | func (p *Prize) TableName() string { 29 | return "t_prize" 30 | } 31 | 32 | type PrizeRepo interface { 33 | Get(id uint) (*Prize, error) 34 | GetWithCache(id uint) (*Prize, error) 35 | GetAll() ([]*Prize, error) 36 | GetAllWithCache() ([]*Prize, error) 37 | CountAll() (int64, error) 38 | CountAllWithCache() (int64, error) 39 | Create(prize *Prize) error 40 | CreateInBatches(prizeList []Prize) error 41 | CreateWithCache(prize *Prize) error 42 | Delete(id uint) error 43 | DeleteAll() error 44 | DeleteWithCache(id uint) error 45 | Update(prize *Prize, cols ...string) error 46 | UpdateWithCache(prize *Prize, cols ...string) error 47 | GetFromCache(id uint) (*Prize, error) 48 | GetAllUsefulPrizeList() ([]*Prize, error) 49 | GetAllUsefulPrizeListWithCache() ([]*Prize, error) 50 | DecrLeftNum(id int, num int) (bool, error) 51 | DecrLeftNumByPool(prizeID int) (int64, error) 52 | IncrLeftNum(id int, column string, num int) error 53 | SetAllByCache(prizeList []*Prize) error 54 | GetAllByCache() ([]*Prize, error) 55 | UpdateByCache(prize *Prize) error 56 | GetPrizePoolNum(prizeID uint) (int, error) 57 | SetPrizePoolNum(key string, prizeID uint, num int) error 58 | IncrPrizePoolNum(key string, prizeID uint, num int) (int, error) 59 | } 60 | -------------------------------------------------------------------------------- /internal/biz/result.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import "time" 4 | 5 | // Result 抽奖记录表 6 | type Result struct { 7 | Id uint `gorm:"column:id;type:int(10) unsigned;primary_key;AUTO_INCREMENT" json:"id"` 8 | PrizeId uint `gorm:"column:prize_id;type:int(10) unsigned;default:0;comment:奖品ID,关联lt_prize表;NOT NULL" json:"prize_id"` 9 | PrizeName string `gorm:"column:prize_name;type:varchar(255);comment:奖品名称;NOT NULL" json:"prize_name"` 10 | PrizeType uint `gorm:"column:prize_type;type:int(10) unsigned;default:0;comment:奖品类型,同lt_prize. gtype;NOT NULL" json:"prize_type"` 11 | UserId uint `gorm:"column:user_id;type:int(10) unsigned;default:0;comment:用户ID;NOT NULL" json:"user_id"` 12 | UserName string `gorm:"column:user_name;type:varchar(50);comment:用户名;NOT NULL" json:"user_name"` 13 | PrizeCode uint `gorm:"column:prize_code;type:int(10) unsigned;default:0;comment:抽奖编号(4位的随机数);NOT NULL" json:"prize_code"` 14 | PrizeData string `gorm:"column:prize_data;type:varchar(255);comment:获奖信息;NOT NULL" json:"prize_data"` 15 | SysCreated *time.Time `gorm:"autoCreateTime;column:sys_created;type:datetime;default null;comment:创建时间;NOT NULL" json:"sys_created"` 16 | SysIp string `gorm:"column:sys_ip;type:varchar(50);comment:用户抽奖的IP;NOT NULL" json:"sys_ip"` 17 | SysStatus uint `gorm:"column:sys_status;type:smallint(5) unsigned;default:0;comment:状态,0 正常,1删除,2作弊;NOT NULL" json:"sys_status"` 18 | } 19 | 20 | func (r *Result) TableName() string { 21 | return "t_result" 22 | } 23 | 24 | type ResultRepo interface { 25 | Get(id uint) (*Result, error) 26 | GetAll() ([]*Result, error) 27 | CountAll() (int64, error) 28 | Create(result *Result) error 29 | Delete(id uint) error 30 | DeleteAll() error 31 | Update(result *Result, cols ...string) error 32 | GetFromCache(id uint) (*Result, error) 33 | } 34 | -------------------------------------------------------------------------------- /internal/conf/conf.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package kratos.api; 3 | 4 | option go_package = "./;conf"; 5 | 6 | import "google/protobuf/duration.proto"; 7 | 8 | message Bootstrap { 9 | Server server = 1; 10 | Data data = 2; 11 | Micro micro = 3; 12 | Log log = 4; 13 | } 14 | 15 | message Server { 16 | message HTTP { 17 | string network = 1; 18 | string addr = 2; 19 | google.protobuf.Duration timeout = 3; 20 | } 21 | message GRPC { 22 | string network = 1; 23 | string addr = 2; 24 | google.protobuf.Duration timeout = 3; 25 | } 26 | message TASK { 27 | string addr = 1; 28 | repeated Task tasks = 2; 29 | } 30 | HTTP http = 1; 31 | GRPC grpc = 2; 32 | TASK task = 3; 33 | } 34 | 35 | message Data { 36 | message Database { 37 | string addr = 1; 38 | string user = 2; 39 | string password = 3; 40 | string database = 4; 41 | int32 max_idle_conn = 5; 42 | int32 max_open_conn = 6; 43 | int32 max_idle_time = 7; 44 | int64 slow_threshold_millisecond = 8; 45 | } 46 | message Redis { 47 | string addr = 1; 48 | string password = 2; 49 | int32 db = 3; 50 | int32 pool_size = 4; 51 | } 52 | Database database = 1; 53 | Redis redis = 2; 54 | } 55 | 56 | message Micro { 57 | message LB { 58 | repeated string addr = 1; 59 | } 60 | message RPC { 61 | } 62 | LB lb = 1; 63 | RPC rpc = 2; 64 | } 65 | 66 | message Log { 67 | bool console = 1; 68 | int64 max_size = 2; 69 | int64 max_backups = 3; 70 | string level = 4; 71 | string log_path = 5; 72 | string filename = 6; 73 | } 74 | 75 | 76 | message Task { 77 | string name = 1; 78 | string type = 2; 79 | string schedule = 3; 80 | } -------------------------------------------------------------------------------- /internal/constant/const.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import "time" 4 | 5 | const ReqID = "req_id" 6 | 7 | // 时间标准化 8 | const ( 9 | SysTimeFormat = "2006-01-02 15:04:05" 10 | SysTimeFormatShort = "2006-01-02" 11 | ) 12 | 13 | const ( 14 | TraceID = "Trace-ID" 15 | UserID = "User-ID" 16 | ) 17 | 18 | // 奖品状态 19 | const ( 20 | PrizeStatusNormal = 1 // 正常 21 | PrizeStatusDelete = 2 // 删除 22 | ) 23 | 24 | const ( 25 | Issuer = "lottery" 26 | Expires = 3600 27 | SecretKey = "lottery-single" 28 | TokenExpireDuration = time.Hour * 2 29 | ) 30 | 31 | const ( 32 | LotteryLockKeyPrefix = "lucky_lock_" 33 | ) 34 | -------------------------------------------------------------------------------- /internal/constant/error.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | ERR_HANDLE_INPUT = errors.New("handle input error") 10 | ) 11 | 12 | type ErrCode int // 错误码 13 | 14 | const ( 15 | Success ErrCode = 0 16 | ErrInternalServer ErrCode = 500 17 | ErrInputInvalid ErrCode = 8020 18 | ErrShouldBind ErrCode = 8021 19 | ErrJsonMarshal ErrCode = 8022 20 | ErrJwtParse ErrCode = 8023 21 | 22 | ErrLogin ErrCode = 10000 23 | ErrIPLimitInvalid ErrCode = 10001 24 | ErrUserLimitInvalid ErrCode = 10002 25 | ErrBlackedIP ErrCode = 10003 26 | ErrBlackedUser ErrCode = 10004 27 | ErrPrizeNotEnough ErrCode = 10005 28 | ErrNotWon ErrCode = 100010 29 | ) 30 | 31 | var errMsgDic = map[ErrCode]string{ 32 | Success: "ok", 33 | ErrInternalServer: "internal server error", 34 | ErrInputInvalid: "input invalid", 35 | ErrShouldBind: "should bind failed", 36 | ErrJwtParse: "json marshal failed", 37 | ErrLogin: "login fail", 38 | ErrIPLimitInvalid: "ip day num limited", 39 | ErrUserLimitInvalid: "user day num limited", 40 | ErrBlackedIP: "blacked ip", 41 | ErrBlackedUser: "blacked user", 42 | ErrPrizeNotEnough: "prize not enough", 43 | ErrNotWon: "not won,please try again!", 44 | } 45 | 46 | // GetErrMsg 获取错误描述 47 | func GetErrMsg(code ErrCode) string { 48 | if msg, ok := errMsgDic[code]; ok { 49 | return msg 50 | } 51 | return fmt.Sprintf("unknown error code %d", code) 52 | } 53 | -------------------------------------------------------------------------------- /internal/constant/prize.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | UserPrizeMax = 20 // 用户每天最多抽奖次数 5 | IpPrizeMax = 30000 // 同一个IP每天最多抽奖次数 6 | IpLimitMax = 3000 // 同一个IP每天最多抽奖次数 7 | ) 8 | 9 | const ( 10 | IpFrameSize = 2 11 | UserFrameSize = 2 12 | ) 13 | 14 | const ( 15 | PrizeCodeMax = 10000 16 | ) 17 | 18 | const ( 19 | PrizeTypeVirtualCoin = 0 // 虚拟币 20 | PrizeTypeCouponSame = 1 // 虚拟券,相同的码 21 | PrizeTypeCouponDiff = 2 // 虚拟券,不同的码 22 | PrizeTypeEntitySmall = 3 // 实物小奖 23 | PrizeTypeEntityMiddle = 4 // 实物中等将 24 | PrizeTypeEntityLarge = 5 // 实物大奖 25 | ) 26 | 27 | const ( 28 | DefaultBlackTime = 7 * 86400 // 默认1周 29 | AllPrizeCacheTime = 30 * 86400 // 默认1周 30 | CouponDiffLockLimit = 10000000 31 | ) 32 | 33 | const ( 34 | AllPrizeCacheKey = "all_prize" 35 | UserCacheKeyPrefix = "black_user_info_" 36 | IpCacheKeyPrefix = "black_ip_info_" 37 | UserLotteryDayNumPrefix = "user_lottery_day_num_" 38 | PrizePoolCacheKey = "prize_pool" 39 | PrizeCouponCacheKey = "prize_coupon_" 40 | ) 41 | -------------------------------------------------------------------------------- /internal/data/blackip.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/BitofferHub/lotterysvr/internal/biz" 8 | "github.com/BitofferHub/lotterysvr/internal/constant" 9 | "github.com/BitofferHub/lotterysvr/internal/utils" 10 | "github.com/BitofferHub/pkg/middlewares/log" 11 | "gorm.io/gorm" 12 | "strconv" 13 | ) 14 | 15 | type blackIpRepo struct { 16 | data *Data 17 | } 18 | 19 | func NewBlackIpRepo(data *Data) biz.BlackIpRepo { 20 | return &blackIpRepo{ 21 | data: data, 22 | } 23 | } 24 | 25 | func (r *blackIpRepo) Get(id uint) (*biz.BlackIp, error) { 26 | // 优先从缓存获取 27 | db := r.data.db 28 | blackIp, err := r.GetFromCache(id) 29 | if err == nil && blackIp != nil { 30 | return blackIp, nil 31 | } 32 | blackIp = &biz.BlackIp{ 33 | Id: id, 34 | } 35 | err = db.Model(&biz.BlackIp{}).First(blackIp).Error 36 | if err != nil { 37 | if err.Error() == gorm.ErrRecordNotFound.Error() { 38 | return nil, nil 39 | } 40 | return nil, fmt.Errorf("blackIpRepo|Get:%v", err) 41 | } 42 | return blackIp, nil 43 | } 44 | 45 | func (r *blackIpRepo) GetByIP(ip string) (*biz.BlackIp, error) { 46 | db := r.data.db 47 | blackIP := &biz.BlackIp{ 48 | Ip: ip, 49 | } 50 | err := db.Model(&biz.BlackIp{}).Where("ip = ?", ip).First(blackIP).Error 51 | if err != nil { 52 | if err.Error() == gorm.ErrRecordNotFound.Error() { 53 | return nil, nil 54 | } 55 | return nil, fmt.Errorf("blackIpRepo|GetByIp:%v", err) 56 | } 57 | return blackIP, nil 58 | } 59 | 60 | func (r *blackIpRepo) GetByIPWithCache(ip string) (*biz.BlackIp, error) { 61 | db := r.data.db 62 | // 优先从缓存获取 63 | blackIp, err := r.GetByCache(ip) 64 | // 从缓存获取到IP 65 | if err == nil && blackIp != nil { 66 | return blackIp, nil 67 | } 68 | // 缓存中没有获取到ip 69 | blackIP := &biz.BlackIp{ 70 | Ip: ip, 71 | } 72 | err = db.Model(&biz.BlackIp{}).Where("ip = ?", ip).First(blackIP).Error 73 | if err != nil { 74 | if err.Error() == gorm.ErrRecordNotFound.Error() { 75 | return nil, nil 76 | } 77 | return nil, fmt.Errorf("blackIpRepo|GetByIp:%v", err) 78 | } 79 | // 数据库中正确读到数据,设置到缓存中 80 | if err = r.SetByCache(blackIP); err != nil { 81 | return nil, fmt.Errorf("blackIpRepo|SetByCache:%v", err) 82 | } 83 | return blackIP, nil 84 | } 85 | 86 | func (r *blackIpRepo) GetAll() ([]*biz.BlackIp, error) { 87 | db := r.data.db 88 | var BlackIps []*biz.BlackIp 89 | err := db.Model(&biz.BlackIp{}).Where("").Order("sys_updated desc").Find(&BlackIps).Error 90 | if err != nil { 91 | return nil, fmt.Errorf("blackIpRepo|GetAll:%v", err) 92 | } 93 | return BlackIps, nil 94 | } 95 | 96 | func (r *blackIpRepo) CountAll() (int64, error) { 97 | db := r.data.db 98 | var num int64 99 | err := db.Model(&biz.BlackIp{}).Count(&num).Error 100 | if err != nil { 101 | return 0, fmt.Errorf("blackIpRepo|CountAll:%v", err) 102 | } 103 | return num, nil 104 | } 105 | 106 | func (r *blackIpRepo) Create(blackIp *biz.BlackIp) error { 107 | db := r.data.db 108 | err := db.Model(blackIp).Create(blackIp).Error 109 | if err != nil { 110 | return fmt.Errorf("blackIpRepo|Create:%v", err) 111 | } 112 | return nil 113 | } 114 | 115 | func (r *blackIpRepo) Delete(id uint) error { 116 | db := r.data.db 117 | blackIp := &biz.BlackIp{Id: id} 118 | if err := db.Model(blackIp).Delete(blackIp).Error; err != nil { 119 | return fmt.Errorf("blackIpRepo|Delete:%v") 120 | } 121 | return nil 122 | } 123 | 124 | func (r *blackIpRepo) Update(ip string, blackIp *biz.BlackIp, cols ...string) error { 125 | db := r.data.db 126 | if err := r.UpdateByCache(&biz.BlackIp{Ip: ip}); err != nil { 127 | return fmt.Errorf("blackIpRepo|UpdateWithCache:%v", err) 128 | } 129 | var err error 130 | if len(cols) == 0 { 131 | err = db.Model(blackIp).Where("ip=?", ip).Updates(blackIp).Error 132 | } else { 133 | err = db.Model(blackIp).Where("ip=?", ip).Select(cols).Updates(blackIp).Error 134 | } 135 | if err != nil { 136 | return fmt.Errorf("blackIpRepo|Update:%v", err) 137 | } 138 | return nil 139 | } 140 | 141 | func (r *blackIpRepo) UpdateWithCache(ip string, blackIp *biz.BlackIp, cols ...string) error { 142 | db := r.data.db 143 | if err := r.UpdateByCache(&biz.BlackIp{Ip: ip}); err != nil { 144 | return fmt.Errorf("blackIpRepo|UpdateWithCache:%v", err) 145 | } 146 | var err error 147 | if len(cols) == 0 { 148 | err = db.Model(blackIp).Where("ip=?", ip).Updates(blackIp).Error 149 | } else { 150 | err = db.Model(blackIp).Where("ip=?", ip).Select(cols).Updates(blackIp).Error 151 | } 152 | if err != nil { 153 | return fmt.Errorf("blackIpRepo|Update:%v", err) 154 | } 155 | return nil 156 | } 157 | 158 | // GetFromCache 根据id从缓存获取奖品 159 | func (r *blackIpRepo) GetFromCache(id uint) (*biz.BlackIp, error) { 160 | redisCli := r.data.cache 161 | idStr := strconv.FormatUint(uint64(id), 10) 162 | ret, exist, err := redisCli.Get(context.Background(), idStr) 163 | if err != nil { 164 | log.Errorf("blackIpRepo|GetFromCache:" + err.Error()) 165 | return nil, err 166 | } 167 | 168 | if !exist { 169 | return nil, nil 170 | } 171 | 172 | blackIp := biz.BlackIp{} 173 | if err = json.Unmarshal([]byte(ret), &blackIp); err != nil { 174 | return nil, fmt.Errorf("blackIpRepo|GetFromCache|json.Unmarshal:%v", err) 175 | } 176 | 177 | return &blackIp, nil 178 | } 179 | 180 | func (s *blackIpRepo) SetByCache(blackIp *biz.BlackIp) error { 181 | if blackIp == nil || blackIp.Ip == "" { 182 | return fmt.Errorf("blackIpRepo|SetByCache invalid user") 183 | } 184 | redisCli := s.data.cache 185 | key := fmt.Sprintf(constant.IpCacheKeyPrefix+"%s", blackIp.Ip) 186 | valueMap := make(map[string]string) 187 | valueMap["Id"] = strconv.Itoa(int(blackIp.Id)) 188 | valueMap["BlackTime"] = utils.FormatFromUnixTime(blackIp.BlackTime.Unix()) 189 | valueMap["SysCreated"] = utils.FormatFromUnixTime(blackIp.SysCreated.Unix()) 190 | valueMap["SysUpdated"] = utils.FormatFromUnixTime(blackIp.SysUpdated.Unix()) 191 | valueMap["Ip"] = blackIp.Ip 192 | _, err := redisCli.HMSet(context.Background(), key, valueMap) 193 | if err != nil { 194 | log.Errorf("blackUserRepo|SetByCache invalid user") 195 | } 196 | return nil 197 | } 198 | 199 | func (s *blackIpRepo) GetByCache(ip string) (*biz.BlackIp, error) { 200 | redisCli := s.data.cache 201 | key := fmt.Sprintf(constant.IpCacheKeyPrefix+"%s", ip) 202 | valueMap, err := redisCli.HGetAll(context.Background(), key) 203 | if err != nil { 204 | return nil, fmt.Errorf("blackIpRepo|GetByCache:%v", err) 205 | } 206 | idStr := valueMap["Id"] 207 | id, _ := strconv.Atoi(idStr) 208 | blackIp := &biz.BlackIp{ 209 | Id: uint(id), 210 | Ip: ip, 211 | } 212 | blackTime, err := utils.ParseTime(valueMap["BlackTime"]) 213 | if err != nil { 214 | return nil, fmt.Errorf("blackIpRepo|GetByCache:%v", err) 215 | } 216 | blackIp.BlackTime = blackTime 217 | sysCreated, err := utils.ParseTime(valueMap["SysCreated"]) 218 | if err != nil { 219 | return nil, fmt.Errorf("blackIpRepo|GetByCache:%v", err) 220 | } 221 | blackIp.SysCreated = &sysCreated 222 | sysUpdated, err := utils.ParseTime(valueMap["SysUpdated"]) 223 | if err != nil { 224 | return nil, fmt.Errorf("blackIpRepo|GetByCache:%v", err) 225 | } 226 | blackIp.SysUpdated = &sysUpdated 227 | return blackIp, nil 228 | } 229 | 230 | func (r *blackIpRepo) UpdateByCache(blackIp *biz.BlackIp) error { 231 | redisCli := r.data.cache 232 | if blackIp == nil || blackIp.Ip == "" { 233 | return fmt.Errorf("blackIpRepo|UpdateByCache invalid blackUser") 234 | } 235 | key := fmt.Sprintf(constant.UserCacheKeyPrefix+"%s", blackIp.Ip) 236 | if err := redisCli.Delete(context.Background(), key); err != nil { 237 | return fmt.Errorf("blackIpRepo|UpdateByCache:%v", err) 238 | } 239 | return nil 240 | } 241 | -------------------------------------------------------------------------------- /internal/data/blackuser.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/BitofferHub/lotterysvr/internal/biz" 8 | "github.com/BitofferHub/lotterysvr/internal/constant" 9 | "github.com/BitofferHub/lotterysvr/internal/utils" 10 | "github.com/BitofferHub/pkg/middlewares/log" 11 | "gorm.io/gorm" 12 | "strconv" 13 | ) 14 | 15 | type blackUserRepo struct { 16 | data *Data 17 | } 18 | 19 | func NewBlackUserRepo(data *Data) biz.BlackUserRepo { 20 | return &blackUserRepo{ 21 | data: data, 22 | } 23 | } 24 | 25 | func (r *blackUserRepo) GetByUserID(uid uint) (*biz.BlackUser, error) { 26 | db := r.data.db 27 | blackUser := &biz.BlackUser{ 28 | UserId: uid, 29 | } 30 | err := db.Model(&biz.BlackUser{}).First(blackUser).Error 31 | if err != nil { 32 | if err.Error() == gorm.ErrRecordNotFound.Error() { 33 | return nil, nil 34 | } 35 | return nil, fmt.Errorf("blackUserRepo|Get:%v", err) 36 | } 37 | return blackUser, nil 38 | } 39 | 40 | func (r *blackUserRepo) GetByUserIDWithCache(uid uint) (*biz.BlackUser, error) { 41 | // 优先从缓存获取 42 | blackUser, err := r.GetByCache(uid) 43 | // 从缓存获取到用户 44 | if err == nil && blackUser != nil { 45 | return blackUser, nil 46 | } 47 | // 缓存没有获取到黑明单用户 48 | blackUser = &biz.BlackUser{ 49 | UserId: uid, 50 | } 51 | db := r.data.db 52 | err = db.Model(&biz.BlackUser{}).First(blackUser).Error 53 | if err != nil { 54 | if err.Error() == gorm.ErrRecordNotFound.Error() { 55 | return nil, nil 56 | } 57 | return nil, fmt.Errorf("blackUserRepo|Get:%v", err) 58 | } 59 | // db获取到了黑明单用户,同步到缓存中 60 | if err = r.SetByCache(blackUser); err != nil { 61 | return nil, fmt.Errorf("blackUserRepo|SetByCache:%v", err) 62 | } 63 | return blackUser, nil 64 | } 65 | 66 | func (r *blackUserRepo) GetAll() ([]*biz.BlackUser, error) { 67 | db := r.data.db 68 | var BlackUsers []*biz.BlackUser 69 | err := db.Model(&biz.BlackUser{}).Where("").Order("sys_updated desc").Find(&BlackUsers).Error 70 | if err != nil { 71 | return nil, fmt.Errorf("blackUserRepo|GetAll:%v", err) 72 | } 73 | return BlackUsers, nil 74 | } 75 | 76 | func (r *blackUserRepo) CountAll() (int64, error) { 77 | db := r.data.db 78 | var num int64 79 | err := db.Model(&biz.BlackUser{}).Count(&num).Error 80 | if err != nil { 81 | return 0, fmt.Errorf("blackUserRepo|CountAll:%v", err) 82 | } 83 | return num, nil 84 | } 85 | 86 | func (r *blackUserRepo) Create(blackUser *biz.BlackUser) error { 87 | db := r.data.db 88 | err := db.Model(blackUser).Create(blackUser).Error 89 | if err != nil { 90 | return fmt.Errorf("blackUserRepo|Create:%v", err) 91 | } 92 | return nil 93 | } 94 | 95 | func (r *blackUserRepo) Delete(id uint) error { 96 | db := r.data.db 97 | blackUser := &biz.BlackUser{Id: id} 98 | if err := db.Model(blackUser).Delete(blackUser).Error; err != nil { 99 | return fmt.Errorf("blackUserRepo|Delete:%v", err) 100 | } 101 | return nil 102 | } 103 | 104 | func (r *blackUserRepo) DeleteWithCache(uid uint) error { 105 | db := r.data.db 106 | blackUser := &biz.BlackUser{UserId: uid} 107 | if err := r.UpdateByCache(blackUser); err != nil { 108 | return fmt.Errorf("blackUserRepo|DeleteWithCache:%v", err) 109 | } 110 | if err := db.Model(&biz.BlackUser{}).Delete(blackUser).Error; err != nil { 111 | return fmt.Errorf("blackUserRepo|Delete:%v", err) 112 | } 113 | return nil 114 | } 115 | 116 | func (r *blackUserRepo) Update(userID uint, blackUser *biz.BlackUser, cols ...string) error { 117 | db := r.data.db 118 | var err error 119 | if len(cols) == 0 { 120 | err = db.Model(blackUser).Where("user_id=?", userID).Updates(blackUser).Error 121 | } else { 122 | err = db.Model(blackUser).Where("user_id=?", userID).Select(cols).Updates(blackUser).Error 123 | } 124 | if err != nil { 125 | return fmt.Errorf("blackUserRepo|Update:%v", err) 126 | } 127 | return nil 128 | } 129 | 130 | func (r *blackUserRepo) UpdateWithCache(userID uint, blackUser *biz.BlackUser, cols ...string) error { 131 | db := r.data.db 132 | if err := r.UpdateByCache(&biz.BlackUser{UserId: userID}); err != nil { 133 | return fmt.Errorf("blackUserRepo|DeleteWithCache:%v", err) 134 | } 135 | var err error 136 | if len(cols) == 0 { 137 | err = db.Model(blackUser).Where("user_id=?", userID).Updates(blackUser).Error 138 | } else { 139 | err = db.Model(blackUser).Where("user_id=?", userID).Select(cols).Updates(blackUser).Error 140 | } 141 | if err != nil { 142 | return fmt.Errorf("blackUserRepo|Update:%v", err) 143 | } 144 | return nil 145 | } 146 | 147 | // GetFromCache 根据id从缓存获取奖品 148 | func (r *blackUserRepo) GetFromCache(id uint) (*biz.BlackUser, error) { 149 | redisCli := r.data.cache 150 | idStr := strconv.FormatUint(uint64(id), 10) 151 | ret, exist, err := redisCli.Get(context.Background(), idStr) 152 | if err != nil { 153 | log.Errorf("blackUserRepo|GetFromCache:" + err.Error()) 154 | return nil, err 155 | } 156 | 157 | if !exist { 158 | return nil, nil 159 | } 160 | 161 | blackUser := biz.BlackUser{} 162 | if err = json.Unmarshal([]byte(ret), &biz.BlackUser{}); err != nil { 163 | return nil, fmt.Errorf("blackUserRepo|GetFromCache|json.Unmarshal:%v", err) 164 | } 165 | return &blackUser, nil 166 | } 167 | 168 | func (r *blackUserRepo) GetByCache(uid uint) (*biz.BlackUser, error) { 169 | redisCli := r.data.cache 170 | key := fmt.Sprintf(constant.UserCacheKeyPrefix+"%d", uid) 171 | valueMap, err := redisCli.HGetAll(context.Background(), key) 172 | if err != nil { 173 | return nil, fmt.Errorf("blackUserRepo|GetByCache:%v", err) 174 | } 175 | userIdStr := valueMap["UserId"] 176 | num, _ := strconv.Atoi(userIdStr) 177 | userID := uint(num) 178 | if userID <= 0 { 179 | return nil, nil 180 | } 181 | idStr := valueMap["Id"] 182 | id, _ := strconv.Atoi(idStr) 183 | blackUser := &biz.BlackUser{ 184 | Id: uint(id), 185 | UserId: userID, 186 | UserName: valueMap["UserName"], 187 | RealName: valueMap["RealName"], 188 | Mobile: valueMap["Mobile"], 189 | Address: valueMap["Address"], 190 | SysIp: valueMap["SysIp"], 191 | } 192 | blackTime, err := utils.ParseTime(valueMap["BlackTime"]) 193 | if err != nil { 194 | return nil, fmt.Errorf("blackUserRepo|GetByCache:%v", err) 195 | } 196 | blackUser.BlackTime = blackTime 197 | sysCreated, err := utils.ParseTime(valueMap["SysCreated"]) 198 | if err != nil { 199 | return nil, fmt.Errorf("blackUserRepo|GetByCache:%v", err) 200 | } 201 | blackUser.SysCreated = &sysCreated 202 | sysUpdated, err := utils.ParseTime(valueMap["SysUpdated"]) 203 | if err != nil { 204 | return nil, fmt.Errorf("blackUserRepo|GetByCache:%v", err) 205 | } 206 | blackUser.SysUpdated = &sysUpdated 207 | return blackUser, nil 208 | } 209 | 210 | func (r *blackUserRepo) SetByCache(blackUser *biz.BlackUser) error { 211 | redisCli := r.data.cache 212 | if blackUser == nil || blackUser.UserId <= 0 { 213 | return fmt.Errorf("blackUserRepo|SetByCache invalid user") 214 | } 215 | key := fmt.Sprintf(constant.UserCacheKeyPrefix+"%d", blackUser.UserId) 216 | valueMap := make(map[string]string) 217 | valueMap["Id"] = strconv.Itoa(int(blackUser.Id)) 218 | valueMap["UserId"] = strconv.Itoa(int(blackUser.UserId)) 219 | valueMap["UserName"] = blackUser.UserName 220 | valueMap["BlackTime"] = utils.FormatFromUnixTime(blackUser.BlackTime.Unix()) 221 | valueMap["RealName"] = blackUser.RealName 222 | valueMap["Mobile"] = blackUser.Mobile 223 | valueMap["Address"] = blackUser.Address 224 | valueMap["SysCreated"] = utils.FormatFromUnixTime(blackUser.SysCreated.Unix()) 225 | valueMap["SysUpdated"] = utils.FormatFromUnixTime(blackUser.SysUpdated.Unix()) 226 | valueMap["SysIp"] = blackUser.SysIp 227 | _, err := redisCli.HMSet(context.Background(), key, valueMap) 228 | if err != nil { 229 | log.Errorf("blackUserRepo|SetByCache invalid user") 230 | } 231 | return nil 232 | } 233 | 234 | func (r *blackUserRepo) UpdateByCache(blackUser *biz.BlackUser) error { 235 | redisCli := r.data.cache 236 | if blackUser == nil || blackUser.UserId <= 0 { 237 | return fmt.Errorf("blackUserRepo|UpdateByCache invalid blackUser") 238 | } 239 | key := fmt.Sprintf(constant.UserCacheKeyPrefix+"%d", blackUser.UserId) 240 | if err := redisCli.Delete(context.Background(), key); err != nil { 241 | return fmt.Errorf("blackUserRepo|UpdateByCache:%v", err) 242 | } 243 | return nil 244 | } 245 | -------------------------------------------------------------------------------- /internal/data/coupon.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/BitofferHub/lotterysvr/internal/biz" 8 | "github.com/BitofferHub/lotterysvr/internal/constant" 9 | "github.com/BitofferHub/pkg/middlewares/log" 10 | "gorm.io/gorm" 11 | "strconv" 12 | ) 13 | 14 | // biz.Coupon 优惠券表 15 | 16 | type couponRepo struct { 17 | data *Data 18 | } 19 | 20 | func NewCouponRepo(data *Data) biz.CouponRepo { 21 | return &couponRepo{ 22 | data: data, 23 | } 24 | } 25 | 26 | func (r *couponRepo) Get(id uint) (*biz.Coupon, error) { 27 | db := r.data.db 28 | // 优先从缓存获取 29 | coupon, err := r.GetFromCache(id) 30 | if err == nil && coupon != nil { 31 | return coupon, nil 32 | } 33 | coupon = &biz.Coupon{ 34 | Id: id, 35 | } 36 | err = db.Model(&biz.Coupon{}).First(coupon).Error 37 | if err != nil { 38 | if err.Error() == gorm.ErrRecordNotFound.Error() { 39 | return nil, nil 40 | } 41 | return nil, fmt.Errorf("couponRepo|Get:%v", err) 42 | } 43 | return coupon, nil 44 | } 45 | 46 | func (r *couponRepo) GetAll() ([]*biz.Coupon, error) { 47 | db := r.data.db 48 | var coupons []*biz.Coupon 49 | err := db.Model(&biz.Coupon{}).Order("sys_updated desc").Find(&coupons).Error 50 | if err != nil { 51 | return nil, fmt.Errorf("couponRepo|GetAll:%v", err) 52 | } 53 | return coupons, nil 54 | } 55 | 56 | func (r *couponRepo) GetCouponListByPrizeID(prizeID uint) ([]*biz.Coupon, error) { 57 | db := r.data.db 58 | var coupons []*biz.Coupon 59 | err := db.Model(&biz.Coupon{}).Where("prize_id=?", prizeID).Order("id desc").Find(&coupons).Error 60 | if err != nil { 61 | return nil, fmt.Errorf("couponRepo|GetAll:%v", err) 62 | } 63 | return coupons, nil 64 | } 65 | 66 | func (r *couponRepo) CountAll() (int64, error) { 67 | db := r.data.db 68 | var num int64 69 | err := db.Model(&biz.Coupon{}).Count(&num).Error 70 | if err != nil { 71 | return 0, fmt.Errorf("couponRepo|CountAll:%v", err) 72 | } 73 | return num, nil 74 | } 75 | 76 | func (r *couponRepo) Create(coupon *biz.Coupon) error { 77 | db := r.data.db 78 | err := db.Model(&biz.Coupon{}).Create(coupon).Error 79 | if err != nil { 80 | return fmt.Errorf("couponRepo|Create:%v", err) 81 | } 82 | return nil 83 | } 84 | 85 | func (r *couponRepo) Delete(id uint) error { 86 | db := r.data.db 87 | coupon := &biz.Coupon{Id: id} 88 | if err := db.Model(&biz.Coupon{}).Delete(coupon).Error; err != nil { 89 | return fmt.Errorf("couponRepo|Delete:%v", err) 90 | } 91 | return nil 92 | } 93 | 94 | func (r *couponRepo) DeleteAllWithCache() error { 95 | db := r.data.db 96 | couponList := make([]biz.Coupon, 0) 97 | if err := db.Model(&biz.Coupon{}).Select("prize_id").Distinct().Find(&couponList).Error; err != nil { 98 | log.Errorf("couponRepo|DeleteAllWithCache:%v", err) 99 | return fmt.Errorf("couponRepo|DeleteAllWithCache:%v", err) 100 | } 101 | if err := db.Exec("DELETE FROM t_coupon").Error; err != nil { 102 | log.Errorf("couponRepo|DeleteAllWithCache:%v", err) 103 | return fmt.Errorf("couponRepo|DeleteAllWithCache:%v", err) 104 | } 105 | log.Infof("couponRepo|DeleteAllWithCache|couponList=%v", couponList) 106 | for _, coupon := range couponList { 107 | key := fmt.Sprintf(constant.PrizeCouponCacheKey+"%d", coupon.PrizeId) 108 | if err := r.data.cache.Delete(context.Background(), key); err != nil { 109 | log.Errorf("couponRepo|DeleteAllWithCache|redis delete:%v", err) 110 | return fmt.Errorf("couponRepo|DeleteAllWithCache|redis delete:%v", err) 111 | } 112 | } 113 | return nil 114 | } 115 | 116 | func (r *couponRepo) Update(coupon *biz.Coupon, cols ...string) error { 117 | db := r.data.db 118 | var err error 119 | if len(cols) == 0 { 120 | err = db.Model(coupon).Updates(coupon).Error 121 | } else { 122 | err = db.Model(coupon).Select(cols).Updates(coupon).Error 123 | } 124 | if err != nil { 125 | return fmt.Errorf("couponRepo|Update:%v", err) 126 | } 127 | return nil 128 | } 129 | 130 | func (r *couponRepo) UpdateByCode(code string, coupon *biz.Coupon, cols ...string) error { 131 | db := r.data.db 132 | var err error 133 | if len(cols) == 0 { 134 | err = db.Model(coupon).Where("code = ?", code).Updates(coupon).Error 135 | } else { 136 | err = db.Model(coupon).Where("code = ?", code).Select(cols).Updates(coupon).Error 137 | } 138 | if err != nil { 139 | return fmt.Errorf("couponRepo|Update:%v", err) 140 | } 141 | return nil 142 | } 143 | 144 | // GetFromCache 根据id从缓存获取奖品 145 | func (r *couponRepo) GetFromCache(id uint) (*biz.Coupon, error) { 146 | redisCli := r.data.cache 147 | idStr := strconv.FormatUint(uint64(id), 10) 148 | ret, exist, err := redisCli.Get(context.Background(), idStr) 149 | if err != nil { 150 | log.Errorf("couponRepo|GetFromCache:" + err.Error()) 151 | return nil, err 152 | } 153 | 154 | if !exist { 155 | return nil, nil 156 | } 157 | 158 | coupon := biz.Coupon{} 159 | json.Unmarshal([]byte(ret), &biz.Coupon{}) 160 | 161 | return &coupon, nil 162 | } 163 | 164 | // GetGetNextUsefulCoupon 获取下一个可用编码的优惠券 165 | func (r *couponRepo) GetGetNextUsefulCoupon(prizeID, couponID int) (*biz.Coupon, error) { 166 | db := r.data.db 167 | coupon := &biz.Coupon{} 168 | err := db.Model(coupon).Where("prize_id=?", prizeID).Where("id > ?", couponID). 169 | Where("sys_status = ?", 1).First(coupon).Error 170 | if err != nil { 171 | if err.Error() == gorm.ErrRecordNotFound.Error() { 172 | return nil, nil 173 | } 174 | return nil, fmt.Errorf("couponRepo|GetGetNextUsefulCoupon err:%v", err) 175 | } 176 | return coupon, nil 177 | } 178 | 179 | // ImportCacheCoupon 往缓存导入优惠券 180 | func (r *couponRepo) ImportCacheCoupon(prizeID uint, code string) (bool, error) { 181 | redisCli := r.data.cache 182 | key := fmt.Sprintf(constant.PrizeCouponCacheKey+"%d", prizeID) 183 | cnt, err := redisCli.SAdd(context.Background(), key, code) 184 | if err != nil { 185 | return false, fmt.Errorf("couponRepo|ImportCacheCoupon:%v", err) 186 | } 187 | if cnt == 0 { 188 | return false, nil 189 | } 190 | return true, nil 191 | } 192 | 193 | // ReSetCacheCoupon 根据库存优惠券重置优惠券缓存 194 | func (r *couponRepo) ReSetCacheCoupon(prizeID uint) (int64, int64, error) { 195 | redisCli := r.data.cache 196 | var successNum, failureNum int64 = 0, 0 197 | couponList, err := r.GetCouponListByPrizeID(prizeID) 198 | if err != nil { 199 | return 0, 0, fmt.Errorf("couponRepo") 200 | } 201 | if couponList == nil || len(couponList) == 0 { 202 | return 0, 0, nil 203 | } 204 | key := fmt.Sprintf(constant.PrizeCouponCacheKey+"%d", prizeID) 205 | // 这里先用临时keu统计,在原key上统计的话,因为db里的数量可能变化,没有同步到缓存中,比如db里面减少了10条数据,如果在原key上增加,那么缓存就会多处10条数据,所以根据db全部统计完了之后,在覆盖 206 | tmpKey := "tmp_" + key 207 | for _, coupon := range couponList { 208 | code := coupon.Code 209 | if coupon.SysStatus == 1 { 210 | cnt, err := redisCli.SAdd(context.Background(), tmpKey, code) 211 | if err != nil { 212 | return 0, 0, fmt.Errorf("couponRepo|ReSetCacheCoupon:%v", err) 213 | } 214 | if cnt <= 0 { 215 | failureNum++ 216 | } else { 217 | successNum++ 218 | } 219 | } 220 | } 221 | _, err = redisCli.Rename(context.Background(), tmpKey, key) 222 | if err != nil { 223 | return 0, 0, fmt.Errorf("couponRepo|ReSetCacheCoupon:%v", err) 224 | } 225 | return successNum, failureNum, nil 226 | } 227 | 228 | // GetCacheCouponNum 获取缓存中的剩余优惠券数量以及数据库中的剩余优惠券数量 229 | func (r *couponRepo) GetCacheCouponNum(prizeID uint) (int64, int64, error) { 230 | redisCli := r.data.cache 231 | var dbNum, cacheNum int64 = 0, 0 232 | couponList, err := r.GetCouponListByPrizeID(prizeID) 233 | if err != nil { 234 | return 0, 0, fmt.Errorf("couponRepo|GetCacheCouponNum:%v", err) 235 | } 236 | if couponList == nil { 237 | return 0, 0, nil 238 | } 239 | for _, coupon := range couponList { 240 | if coupon.SysStatus == 1 { 241 | dbNum++ 242 | } 243 | } 244 | key := fmt.Sprintf(constant.PrizeCouponCacheKey+"%d", prizeID) 245 | cacheNum, err = redisCli.SCard(context.Background(), key) 246 | if err != nil { 247 | return 0, 0, fmt.Errorf("couponRepo|GetCacheCouponNum:%v", err) 248 | } 249 | return dbNum, cacheNum, nil 250 | } 251 | 252 | // GetNextUsefulCouponFromCache 从缓存中拿出一个可用优惠券 253 | func (r *couponRepo) GetNextUsefulCouponFromCache(prizeID int) (string, error) { 254 | redisCli := r.data.cache 255 | key := fmt.Sprintf(constant.PrizeCouponCacheKey+"%d", prizeID) 256 | code, err := redisCli.SPop(context.Background(), key) 257 | if err != nil { 258 | if err.Error() == "redis: nil" { 259 | //log.Infof("coupon not left") 260 | return "", nil 261 | } 262 | return "", fmt.Errorf("lotteryService|PrizeCouponDiffByCache:%v", err) 263 | } 264 | if code == "" { 265 | //log.Infof("lotteryService|PrizeCouponDiffByCache code is nil with prize_id=%d", prizeID) 266 | return "", nil 267 | } 268 | return code, nil 269 | } 270 | -------------------------------------------------------------------------------- /internal/data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "github.com/BitofferHub/lotterysvr/internal/biz" 6 | "github.com/BitofferHub/lotterysvr/internal/conf" 7 | "github.com/BitofferHub/pkg/middlewares/cache" 8 | "github.com/BitofferHub/pkg/middlewares/gormcli" 9 | _ "github.com/go-sql-driver/mysql" 10 | "github.com/google/wire" 11 | "gorm.io/gorm" 12 | ) 13 | 14 | // ProviderSet is data providers. 15 | var ProviderSet = wire.NewSet(NewData, NewDatabase, NewCache, NewCouponRepo, NewPrizeRepo, 16 | NewResultRepo, NewBlackIpRepo, NewBlackUserRepo, NewLotteryTimesRepo, NewTransaction) 17 | 18 | type Data struct { 19 | db *gorm.DB 20 | cache *cache.Client 21 | } 22 | 23 | type contextTxKey struct{} 24 | 25 | func (d *Data) InTx(ctx context.Context, fn func(ctx context.Context) error) error { 26 | return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { 27 | ctx = context.WithValue(ctx, contextTxKey{}, tx) 28 | return fn(ctx) 29 | }) 30 | } 31 | 32 | func (d *Data) DB(ctx context.Context) *gorm.DB { 33 | tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB) 34 | if ok { 35 | return tx 36 | } 37 | return d.db 38 | } 39 | 40 | func NewTransaction(d *Data) biz.Transaction { 41 | return d 42 | } 43 | 44 | func NewData(db *gorm.DB, cache *cache.Client) *Data { 45 | dt := &Data{db: db, cache: cache} 46 | return dt 47 | } 48 | 49 | func NewDatabase(conf *conf.Data) *gorm.DB { 50 | dt := conf.GetDatabase() 51 | gormcli.Init( 52 | gormcli.WithAddr(dt.GetAddr()), 53 | gormcli.WithUser(dt.GetUser()), 54 | gormcli.WithPassword(dt.GetPassword()), 55 | gormcli.WithDataBase(dt.GetDatabase()), 56 | gormcli.WithMaxIdleConn(int(dt.GetMaxIdleConn())), 57 | gormcli.WithMaxOpenConn(int(dt.GetMaxOpenConn())), 58 | gormcli.WithMaxIdleTime(int64(dt.GetMaxIdleTime())), 59 | // 如果设置了慢查询阈值,就打印日志 60 | gormcli.WithSlowThresholdMillisecond(dt.GetSlowThresholdMillisecond()), 61 | ) 62 | 63 | return gormcli.GetDB() 64 | } 65 | 66 | func NewCache(conf *conf.Data) *cache.Client { 67 | dt := conf.GetRedis() 68 | cache.Init( 69 | cache.WithAddr(dt.GetAddr()), 70 | cache.WithPassWord(dt.GetPassword()), 71 | cache.WithDB(int(dt.GetDb())), 72 | cache.WithPoolSize(int(dt.GetPoolSize()))) 73 | 74 | return cache.GetRedisCli() 75 | } 76 | -------------------------------------------------------------------------------- /internal/data/lotterytimes.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/BitofferHub/lotterysvr/internal/biz" 7 | "github.com/BitofferHub/lotterysvr/internal/constant" 8 | "github.com/BitofferHub/pkg/middlewares/cache" 9 | "github.com/BitofferHub/pkg/middlewares/log" 10 | "gorm.io/gorm" 11 | "math" 12 | ) 13 | 14 | type lotteryTimesRepo struct { 15 | data *Data 16 | } 17 | 18 | func NewLotteryTimesRepo(data *Data) biz.LotteryTimesRepo { 19 | return &lotteryTimesRepo{ 20 | data: data, 21 | } 22 | } 23 | 24 | func (r *lotteryTimesRepo) Get(id uint) (*biz.LotteryTimes, error) { 25 | db := r.data.db 26 | lotteryTimes := &biz.LotteryTimes{ 27 | Id: id, 28 | } 29 | err := db.Model(&biz.LotteryTimes{}).First(lotteryTimes).Error 30 | if err != nil { 31 | if err.Error() == gorm.ErrRecordNotFound.Error() { 32 | return nil, nil 33 | } 34 | return nil, fmt.Errorf("lotteryTimesRepo|Get:%v", err) 35 | } 36 | return lotteryTimes, nil 37 | } 38 | 39 | func (r *lotteryTimesRepo) GetByUserIDAndDay(uid uint, day uint) (*biz.LotteryTimes, error) { 40 | db := r.data.db 41 | lotteryTimes := &biz.LotteryTimes{} 42 | err := db.Model(&biz.LotteryTimes{}).Where("user_id=? and day=?", uid, day).First(lotteryTimes).Error 43 | if err != nil { 44 | if err.Error() == gorm.ErrRecordNotFound.Error() { 45 | return nil, nil 46 | } 47 | return nil, fmt.Errorf("lotteryTimesRepo|GetByUserID:%v", err) 48 | } 49 | return lotteryTimes, nil 50 | } 51 | 52 | func (r *lotteryTimesRepo) GetAll() ([]*biz.LotteryTimes, error) { 53 | db := r.data.db 54 | var lotteryTimesList []*biz.LotteryTimes 55 | err := db.Model(&biz.LotteryTimes{}).Where("").Order("sys_updated desc").Find(&lotteryTimesList).Error 56 | if err != nil { 57 | return nil, fmt.Errorf("lotteryTimesRepo|GetAll:%v", err) 58 | } 59 | return lotteryTimesList, nil 60 | } 61 | 62 | func (r *lotteryTimesRepo) CountAll() (int64, error) { 63 | db := r.data.db 64 | var num int64 65 | err := db.Model(&biz.LotteryTimes{}).Count(&num).Error 66 | if err != nil { 67 | return 0, fmt.Errorf("lotteryTimesRepo|CountAll:%v", err) 68 | } 69 | return num, nil 70 | } 71 | 72 | func (r *lotteryTimesRepo) Create(lotteryTimes *biz.LotteryTimes) error { 73 | db := r.data.db 74 | err := db.Model(&biz.LotteryTimes{}).Create(lotteryTimes).Error 75 | if err != nil { 76 | return fmt.Errorf("lotteryTimesRepo|Create:%v", err) 77 | } 78 | return nil 79 | } 80 | 81 | func (r *lotteryTimesRepo) Delete(id uint) error { 82 | db := r.data.db 83 | lotteryTimes := &biz.LotteryTimes{Id: id} 84 | if err := db.Model(&biz.LotteryTimes{}).Delete(lotteryTimes).Error; err != nil { 85 | return fmt.Errorf("lotteryTimesRepo|Delete:%v", err) 86 | } 87 | return nil 88 | } 89 | 90 | func (r *lotteryTimesRepo) DeleteAll() error { 91 | db := r.data.db 92 | if err := db.Exec("DELETE FROM t_lottery_times").Error; err != nil { 93 | log.Errorf("lotteryTimesRepo|DeleteAll:%v", err) 94 | return fmt.Errorf("lotteryTimesRepo|DeleteAll:%v", err) 95 | } 96 | return nil 97 | } 98 | 99 | func (r *lotteryTimesRepo) Update(lotteryTimes *biz.LotteryTimes, cols ...string) error { 100 | var err error 101 | db := r.data.db 102 | if len(cols) == 0 { 103 | err = db.Model(lotteryTimes).Updates(lotteryTimes).Error 104 | } else { 105 | err = db.Model(lotteryTimes).Select(cols).Updates(lotteryTimes).Error 106 | } 107 | if err != nil { 108 | return fmt.Errorf("lotteryTimesRepo|Update:%v", err) 109 | } 110 | return nil 111 | } 112 | 113 | // IncrUserDayLotteryNum 每天缓存的用户抽奖次数递增,返回递增后的数值 114 | func (r *lotteryTimesRepo) IncrUserDayLotteryNum(uid uint) int64 { 115 | redisCli := r.data.cache 116 | i := uid % constant.UserFrameSize 117 | // 集群的redis统计数递增 118 | key := fmt.Sprintf(constant.UserLotteryDayNumPrefix+"%d", i) 119 | ret, err := redisCli.HIncrBy(context.Background(), key, fmt.Sprint(uid), 1) 120 | if err != nil { 121 | log.Errorf("lotteryTimesRepo|IncrUserDayLotteryNum:%v", err) 122 | return math.MaxInt32 123 | } 124 | return ret 125 | } 126 | 127 | // InitUserLuckyNum 从给定的数据直接初始化用户的参与抽奖次数 128 | func (r *lotteryTimesRepo) InitUserLuckyNum(uid uint, num int64) error { 129 | redisCli := r.data.cache 130 | if num <= 1 { 131 | return nil 132 | } 133 | i := uid % constant.UserFrameSize 134 | key := fmt.Sprintf(constant.UserLotteryDayNumPrefix+"%d", i) 135 | _, err := redisCli.HSet(context.Background(), key, fmt.Sprint(uid), num) 136 | if err != nil { 137 | log.Errorf("lotteryTimesRepo|InitUserLuckyNum:%v", err) 138 | return fmt.Errorf("lotteryTimesRepo|InitUserLuckyNum:%v", err) 139 | } 140 | return nil 141 | } 142 | 143 | func (r *lotteryTimesRepo) ResetIPLotteryNums() { 144 | //log.Infof("重置所有的IP抽奖次数") 145 | for i := 0; i < constant.IpFrameSize; i++ { 146 | key := fmt.Sprintf("day_ip_num_%d", i) 147 | if err := cache.GetRedisCli().Delete(context.Background(), key); err != nil { 148 | log.Errorf("ResetIPLotteryNums err:%v", err) 149 | } 150 | } 151 | //log.Infof("重置所有的IP抽奖次数完成!!!") 152 | } 153 | 154 | func (r *lotteryTimesRepo) ResetUserLotteryNums() { 155 | //log.Infof("重置今日用户抽奖次数") 156 | for i := 0; i < constant.UserFrameSize; i++ { 157 | key := fmt.Sprintf(constant.UserLotteryDayNumPrefix+"%d", i) 158 | if err := cache.GetRedisCli().Delete(context.Background(), key); err != nil { 159 | log.Errorf("ResetIPLotteryNums err:%v", err) 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /internal/data/result.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/BitofferHub/lotterysvr/internal/biz" 8 | "github.com/BitofferHub/pkg/middlewares/log" 9 | "gorm.io/gorm" 10 | "strconv" 11 | ) 12 | 13 | type resultRepo struct { 14 | data *Data 15 | } 16 | 17 | func NewResultRepo(data *Data) biz.ResultRepo { 18 | return &resultRepo{ 19 | data: data, 20 | } 21 | } 22 | 23 | func (r *resultRepo) Get(id uint) (*biz.Result, error) { 24 | db := r.data.db 25 | // 优先从缓存获取 26 | result, err := r.GetFromCache(id) 27 | if err == nil && result != nil { 28 | return result, nil 29 | } 30 | result = &biz.Result{ 31 | Id: id, 32 | } 33 | err = db.Model(&biz.Result{}).First(result).Error 34 | if err != nil { 35 | if err.Error() == gorm.ErrRecordNotFound.Error() { 36 | return nil, nil 37 | } 38 | return nil, fmt.Errorf("resultRepo|Get:%v", err) 39 | } 40 | return result, nil 41 | } 42 | 43 | func (r *resultRepo) GetAll() ([]*biz.Result, error) { 44 | db := r.data.db 45 | var results []*biz.Result 46 | err := db.Model(&biz.Result{}).Where("").Order("sys_updated desc").Find(&results).Error 47 | if err != nil { 48 | return nil, fmt.Errorf("resultRepo|GetAll:%v", err) 49 | } 50 | return results, nil 51 | } 52 | 53 | func (r *resultRepo) CountAll() (int64, error) { 54 | db := r.data.db 55 | var num int64 56 | err := db.Model(&biz.Result{}).Count(&num).Error 57 | if err != nil { 58 | return 0, fmt.Errorf("resultRepo|CountAll:%v", err) 59 | } 60 | return num, nil 61 | } 62 | 63 | func (r *resultRepo) Create(result *biz.Result) error { 64 | db := r.data.db 65 | err := db.Model(&biz.Result{}).Create(result).Error 66 | if err != nil { 67 | return fmt.Errorf("resultRepo|Create:%v", err) 68 | } 69 | return nil 70 | } 71 | 72 | func (r *resultRepo) Delete(id uint) error { 73 | db := r.data.db 74 | result := &biz.Result{Id: id} 75 | if err := db.Model(&biz.Result{}).Delete(result).Error; err != nil { 76 | return fmt.Errorf("resultRepo|Delete:%v") 77 | } 78 | return nil 79 | } 80 | 81 | func (r *resultRepo) DeleteAll() error { 82 | db := r.data.db 83 | if err := db.Exec("DELETE FROM t_result").Error; err != nil { 84 | log.Errorf("resultRepo|DeleteAll:%v", err) 85 | return fmt.Errorf("resultRepo|DeleteAll:%v", err) 86 | } 87 | return nil 88 | } 89 | 90 | func (r *resultRepo) Update(result *biz.Result, cols ...string) error { 91 | db := r.data.db 92 | var err error 93 | if len(cols) == 0 { 94 | err = db.Model(result).Updates(result).Error 95 | } else { 96 | err = db.Model(result).Select(cols).Updates(result).Error 97 | } 98 | if err != nil { 99 | return fmt.Errorf("resultRepo|Update:%v", err) 100 | } 101 | return nil 102 | } 103 | 104 | // GetFromCache 根据id从缓存获取奖品 105 | func (r *resultRepo) GetFromCache(id uint) (*biz.Result, error) { 106 | redisCli := r.data.cache 107 | idStr := strconv.FormatUint(uint64(id), 10) 108 | ret, exist, err := redisCli.Get(context.Background(), idStr) 109 | if err != nil { 110 | log.Errorf("resultRepo|GetFromCache:" + err.Error()) 111 | return nil, err 112 | } 113 | 114 | if !exist { 115 | return nil, nil 116 | } 117 | 118 | result := biz.Result{} 119 | json.Unmarshal([]byte(ret), &biz.Result{}) 120 | 121 | return &result, nil 122 | } 123 | -------------------------------------------------------------------------------- /internal/interfaces/entity.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "github.com/BitofferHub/lotterysvr/internal/biz" 5 | "github.com/BitofferHub/lotterysvr/internal/constant" 6 | ) 7 | 8 | // HttpResponse http独立请求返回结构体,这个通用的,不需要修改 9 | type HttpResponse struct { 10 | Code constant.ErrCode `json:"code"` 11 | Msg string `json:"msg"` 12 | Data interface{} `json:"data"` 13 | UserID uint32 `json:"user_id"` 14 | } 15 | 16 | type LotteryReq struct { 17 | UserID uint `json:"user_id"` 18 | UserName string `json:"user_name"` 19 | IP string `json:"ip"` 20 | } 21 | 22 | type AddPrizeReq struct { 23 | UserID uint `json:"user_id"` 24 | Prize *biz.ViewPrize `json:"prize"` 25 | } 26 | 27 | type AddPrizeListReq struct { 28 | UserID uint `json:"user_id"` 29 | PrizeList []*biz.ViewPrize `json:"prize_list"` 30 | } 31 | 32 | type ClearPrizeReq struct { 33 | UserID uint `json:"user_id"` 34 | } 35 | 36 | type ImportCouponReq struct { 37 | UserID uint `json:"user_id"` 38 | CouponInfo *biz.ViewCouponInfo `json:"coupon"` 39 | } 40 | 41 | type ClearCouponReq struct { 42 | UserID uint `json:"user_id"` 43 | } 44 | 45 | type ClearLotteryTimesReq struct { 46 | UserID uint `json:"user_id"` 47 | } 48 | 49 | type ClearResultReq struct { 50 | UserID uint `json:"user_id"` 51 | } 52 | -------------------------------------------------------------------------------- /internal/interfaces/interfaces.go: -------------------------------------------------------------------------------- 1 | // interfaces.go 2 | package interfaces 3 | 4 | import ( 5 | "github.com/google/wire" 6 | ) 7 | 8 | // ProviderSet is interfaces providers. 9 | var ProviderSet = wire.NewSet(NewHandler) 10 | -------------------------------------------------------------------------------- /internal/interfaces/lotteryv1.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "context" 5 | "github.com/BitofferHub/lotterysvr/internal/constant" 6 | "github.com/BitofferHub/lotterysvr/internal/utils" 7 | "github.com/BitofferHub/pkg/middlewares/log" 8 | pb "github.com/BitofferHub/proto_center/api/lotterysvr/v1" 9 | "github.com/gin-gonic/gin" 10 | "net/http" 11 | "strconv" 12 | ) 13 | 14 | func (h *Handler) LotteryV1(c *gin.Context) { 15 | rsp := HttpResponse{} 16 | // HTTP响应 17 | req := LotteryReq{} 18 | if err := c.ShouldBind(&req); err != nil { 19 | log.Errorf("LotteryV1|Error binding:%v", err) 20 | rsp.Code = constant.ErrShouldBind 21 | rsp.Msg = constant.GetErrMsg(rsp.Code) 22 | c.JSON(http.StatusOK, rsp) 23 | return 24 | } 25 | userIDStr := c.Request.Header.Get(constant.UserID) 26 | userID, err := strconv.Atoi(userIDStr) 27 | if err == nil { 28 | req.UserID = uint(userID) 29 | } 30 | //log.Infof("LotteryV1|Handler|req=====%+v", req) 31 | h.lotteryV1(&req, &rsp) 32 | c.JSON(http.StatusOK, rsp) 33 | } 34 | 35 | func (h *Handler) lotteryV1(lotteryReq *LotteryReq, lotteryRsp *HttpResponse) { 36 | ctx := context.WithValue(context.Background(), constant.ReqID, utils.NewUuid()) 37 | req := &pb.LotteryReq{ 38 | UserId: uint32(lotteryReq.UserID), 39 | UserName: lotteryReq.UserName, 40 | Ip: lotteryReq.IP, 41 | } 42 | // 2. 验证用户今日抽奖次数 43 | rsp, err := h.lotteryService.LotteryV1(ctx, req) 44 | if err != nil { 45 | log.ErrorContextf(ctx, "http lotteryv1|err:%v", err) 46 | return 47 | } 48 | lotteryRsp.Code = constant.ErrCode(rsp.CommonRsp.Code) 49 | lotteryRsp.Msg = rsp.CommonRsp.Msg 50 | lotteryRsp.Data = rsp.PrizeInfo 51 | lotteryRsp.UserID = rsp.CommonRsp.UserId 52 | } 53 | -------------------------------------------------------------------------------- /internal/interfaces/lotteryv2.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "context" 5 | "github.com/BitofferHub/lotterysvr/internal/constant" 6 | "github.com/BitofferHub/lotterysvr/internal/utils" 7 | "github.com/BitofferHub/pkg/middlewares/log" 8 | pb "github.com/BitofferHub/proto_center/api/lotterysvr/v1" 9 | "github.com/gin-gonic/gin" 10 | "net/http" 11 | "strconv" 12 | ) 13 | 14 | func (h *Handler) LotteryV2(c *gin.Context) { 15 | rsp := HttpResponse{} 16 | // HTTP响应 17 | req := LotteryReq{} 18 | if err := c.ShouldBind(&req); err != nil { 19 | log.Errorf("LotteryV2|Error binding:%v", err) 20 | rsp.Code = constant.ErrShouldBind 21 | rsp.Msg = constant.GetErrMsg(rsp.Code) 22 | c.JSON(http.StatusOK, rsp) 23 | return 24 | } 25 | userIDStr := c.Request.Header.Get(constant.UserID) 26 | userID, err := strconv.Atoi(userIDStr) 27 | if err != nil { 28 | req.UserID = uint(userID) 29 | } 30 | h.lotteryV2(&req, &rsp) 31 | c.JSON(http.StatusOK, rsp) 32 | } 33 | 34 | func (h *Handler) lotteryV2(lotteryReq *LotteryReq, lotteryRsp *HttpResponse) { 35 | ctx := context.WithValue(context.Background(), constant.ReqID, utils.NewUuid()) 36 | req := &pb.LotteryReq{ 37 | UserId: uint32(lotteryReq.UserID), 38 | UserName: lotteryReq.UserName, 39 | Ip: lotteryReq.IP, 40 | } 41 | // 2. 验证用户今日抽奖次数 42 | rsp, err := h.lotteryService.LotteryV2(ctx, req) 43 | if err != nil { 44 | log.ErrorContextf(ctx, "http lotteryv2|err:%v", err) 45 | return 46 | } 47 | lotteryRsp.Code = constant.ErrCode(rsp.CommonRsp.Code) 48 | lotteryRsp.Msg = rsp.CommonRsp.Msg 49 | lotteryRsp.Data = rsp.PrizeInfo 50 | } 51 | -------------------------------------------------------------------------------- /internal/interfaces/lotteryv3.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "context" 5 | "github.com/BitofferHub/lotterysvr/internal/constant" 6 | "github.com/BitofferHub/lotterysvr/internal/utils" 7 | "github.com/BitofferHub/pkg/middlewares/log" 8 | pb "github.com/BitofferHub/proto_center/api/lotterysvr/v1" 9 | "github.com/gin-gonic/gin" 10 | "net/http" 11 | "strconv" 12 | ) 13 | 14 | func (h *Handler) LotteryV3(c *gin.Context) { 15 | rsp := HttpResponse{} 16 | // HTTP响应 17 | req := LotteryReq{} 18 | if err := c.ShouldBind(&req); err != nil { 19 | log.Errorf("LotteryV3|Error binding:%v", err) 20 | rsp.Code = constant.ErrShouldBind 21 | rsp.Msg = constant.GetErrMsg(rsp.Code) 22 | c.JSON(http.StatusOK, rsp) 23 | return 24 | } 25 | userIDStr := c.Request.Header.Get(constant.UserID) 26 | userID, err := strconv.Atoi(userIDStr) 27 | if err != nil { 28 | req.UserID = uint(userID) 29 | } 30 | h.lotteryV3(&req, &rsp) 31 | c.JSON(http.StatusOK, rsp) 32 | } 33 | 34 | func (h *Handler) lotteryV3(lotteryReq *LotteryReq, lotteryRsp *HttpResponse) { 35 | ctx := context.WithValue(context.Background(), constant.ReqID, utils.NewUuid()) 36 | req := &pb.LotteryReq{ 37 | UserId: uint32(lotteryReq.UserID), 38 | UserName: lotteryReq.UserName, 39 | Ip: lotteryReq.IP, 40 | } 41 | // 2. 验证用户今日抽奖次数 42 | rsp, err := h.lotteryService.LotteryV3(ctx, req) 43 | if err != nil { 44 | log.ErrorContextf(ctx, "http lotteryv3|err:%v", err) 45 | return 46 | } 47 | lotteryRsp.Code = constant.ErrCode(rsp.CommonRsp.Code) 48 | lotteryRsp.Msg = rsp.CommonRsp.Msg 49 | lotteryRsp.Data = rsp.PrizeInfo 50 | } 51 | -------------------------------------------------------------------------------- /internal/interfaces/routes.go: -------------------------------------------------------------------------------- 1 | // routes.go 2 | 3 | package interfaces 4 | 5 | import ( 6 | "github.com/BitofferHub/lotterysvr/internal/service" 7 | engine "github.com/BitofferHub/pkg/middlewares/gin" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | type Handler struct { 12 | lotteryService *service.LotteryService 13 | adminService *service.AdminService 14 | } 15 | 16 | func NewHandler(s *service.LotteryService, a *service.AdminService) *Handler { 17 | return &Handler{ 18 | lotteryService: s, 19 | adminService: a, 20 | } 21 | } 22 | 23 | func NewRouter(h *Handler) *gin.Engine { 24 | r := engine.NewEngine(engine.WithLogger(false)) 25 | 26 | r.GET("/ping", func(c *gin.Context) { 27 | c.JSON(200, gin.H{ 28 | "message": "pong", 29 | }) 30 | }) 31 | 32 | adminGroup := r.Group("admin") 33 | // 获取奖品列表 34 | //adminGroup.GET("/get_prize_list", handlers.GetPrizeList) 35 | // 添加奖品 36 | adminGroup.POST("/add_prize", h.AddPrize) 37 | // 添加奖品列表 38 | adminGroup.POST("/add_prize_list", h.AddPrizeList) 39 | // 清空奖品 40 | adminGroup.POST("/clear_prize", h.ClearPrize) 41 | // 导入优惠券 42 | adminGroup.POST("/import_coupon", h.ImportCoupon) 43 | // 导入优惠券,同时导入缓存 44 | adminGroup.POST("/import_coupon_cache", h.ImportCouponWithCache) 45 | // 清空优惠券 46 | adminGroup.POST("/clear_coupon", h.ClearCoupon) 47 | // 清空用户抽奖次数 48 | adminGroup.POST("/clear_lottery_times", h.ClearLotteryTimes) 49 | // 清空获奖结果 50 | adminGroup.POST("/clear_result", h.ClearResult) 51 | 52 | lotteryGroup := r.Group("lottery") 53 | // V1基础版获取中奖 54 | lotteryGroup.POST("/v1/get_lucky", h.LotteryV1) 55 | // 优化V2版中奖逻辑 56 | lotteryGroup.POST("/v2/get_lucky", h.LotteryV2) 57 | // 优化V3版中奖逻辑 58 | lotteryGroup.POST("/v3/get_lucky", h.LotteryV3) 59 | return r 60 | } 61 | -------------------------------------------------------------------------------- /internal/server/grpc.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/BitofferHub/lotterysvr/internal/conf" 5 | "github.com/BitofferHub/lotterysvr/internal/service" 6 | v1 "github.com/BitofferHub/proto_center/api/lotterysvr/v1" 7 | mmd "github.com/go-kratos/kratos/v2/middleware/metadata" 8 | "github.com/go-kratos/kratos/v2/middleware/recovery" 9 | "github.com/go-kratos/kratos/v2/transport/grpc" 10 | ) 11 | 12 | // NewGRPCServer 13 | // 14 | // @Author 狂飙训练营 15 | // @Description: NewGRPCServer new a gRPC server. 16 | // @param c 17 | // @param greeter 18 | // @return *grpc.Server 19 | func NewGRPCServer(c *conf.Server, greeter *service.LotteryService) *grpc.Server { 20 | var opts = []grpc.ServerOption{ 21 | grpc.Middleware( 22 | recovery.Recovery(), 23 | mmd.Server(), 24 | MiddlewareTraceID(), 25 | MiddlewareLog(), 26 | ), 27 | } 28 | if c.Grpc.Network != "" { 29 | opts = append(opts, grpc.Network(c.Grpc.Network)) 30 | } 31 | if c.Grpc.Addr != "" { 32 | opts = append(opts, grpc.Address(c.Grpc.Addr)) 33 | } 34 | if c.Grpc.Timeout != nil { 35 | opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration())) 36 | } 37 | srv := grpc.NewServer(opts...) 38 | v1.RegisterLotteryServer(srv, greeter) 39 | return srv 40 | } 41 | -------------------------------------------------------------------------------- /internal/server/http.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/BitofferHub/lotterysvr/internal/conf" 5 | "github.com/BitofferHub/lotterysvr/internal/interfaces" 6 | "github.com/go-kratos/kratos/v2/middleware/recovery" 7 | "github.com/go-kratos/kratos/v2/transport/http" 8 | ) 9 | 10 | // NewHTTPServer new an HTTP server. 11 | func NewHTTPServer(c *conf.Server, h *interfaces.Handler) *http.Server { 12 | var opts = []http.ServerOption{ 13 | http.Middleware( 14 | recovery.Recovery(), 15 | MiddlewareTraceID(), 16 | MiddlewareLog(), 17 | ), 18 | } 19 | if c.Http.Network != "" { 20 | opts = append(opts, http.Network(c.Http.Network)) 21 | } 22 | if c.Http.Addr != "" { 23 | opts = append(opts, http.Address(c.Http.Addr)) 24 | } 25 | if c.Http.Timeout != nil { 26 | opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration())) 27 | } 28 | srv := http.NewServer(opts...) 29 | srv.HandlePrefix("/", interfaces.NewRouter(h)) 30 | return srv 31 | } 32 | -------------------------------------------------------------------------------- /internal/server/middleware.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/BitofferHub/pkg/constant" 7 | "github.com/BitofferHub/pkg/middlewares/log" 8 | "github.com/go-kratos/kratos/v2/errors" 9 | "github.com/go-kratos/kratos/v2/metadata" 10 | "github.com/go-kratos/kratos/v2/middleware" 11 | "github.com/go-kratos/kratos/v2/transport" 12 | "time" 13 | ) 14 | 15 | // MiddlewareTraceID 16 | // 17 | // @Author 狂飙训练营 18 | // @Description: kratos middleware for traceID 19 | // @return middleware.Middleware 20 | func MiddlewareTraceID() middleware.Middleware { 21 | return func(handler middleware.Handler) middleware.Handler { 22 | return func(ctx context.Context, req interface{}) (reply interface{}, err error) { 23 | fmt.Printf("ctx %v\n", ctx) 24 | if md, ok := metadata.FromServerContext(ctx); ok { 25 | traceID := md.Get(fmt.Sprintf("x-md-global-%s", constant.TraceID)) 26 | ctx = context.WithValue(ctx, constant.TraceID, traceID) 27 | //log.InfoContextf(ctx, "traceID %v", traceID) 28 | } 29 | return handler(ctx, req) 30 | } 31 | } 32 | } 33 | 34 | // MiddlewareLog 35 | // 36 | // @Author 狂飙训练营 37 | // @Description: server logging middleware. 38 | // @return middleware.Middleware 39 | func MiddlewareLog() middleware.Middleware { 40 | return func(handler middleware.Handler) middleware.Handler { 41 | return func(ctx context.Context, req interface{}) (reply interface{}, err error) { 42 | var ( 43 | code int32 44 | reason string 45 | kind string 46 | operation string 47 | ) 48 | if info, ok := transport.FromServerContext(ctx); ok { 49 | kind = info.Kind().String() 50 | operation = info.Operation() 51 | } 52 | log.InfoContextf(ctx, 53 | "component:%s,operation:%s,args:%s,code:%d,reason:%s", kind, 54 | operation, 55 | req, 56 | code, 57 | reason, 58 | ) 59 | begin := time.Now() 60 | reply, err = handler(ctx, req) 61 | if se := errors.FromError(err); se != nil { 62 | code = se.Code 63 | reason = se.Reason 64 | } 65 | log.InfoContextf(ctx, "cost %v", time.Since(begin)) 66 | log.InfoContextf(ctx, "reply %v", reply) 67 | 68 | return 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/google/wire" 5 | ) 6 | 7 | // ProviderSet is server providers. 8 | var ProviderSet = wire.NewSet(NewGRPCServer, NewHTTPServer) 9 | -------------------------------------------------------------------------------- /internal/service/README.md: -------------------------------------------------------------------------------- 1 | # Service 2 | -------------------------------------------------------------------------------- /internal/service/admin.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/BitofferHub/lotterysvr/internal/biz" 7 | "github.com/BitofferHub/pkg/middlewares/log" 8 | ) 9 | 10 | // AddPrize 添加奖品 11 | func (a *AdminService) AddPrize(ctx context.Context, viewPrize *biz.ViewPrize) error { 12 | if err := a.adminCase.AddPrize(ctx, viewPrize); err != nil { 13 | log.ErrorContextf(ctx, "adminService|AddPrize err:%v", err) 14 | return fmt.Errorf("adminService|AddPrize:%v", err) 15 | } 16 | return nil 17 | } 18 | 19 | // AddPrizeList 添加奖品列表 20 | func (a *AdminService) AddPrizeList(ctx context.Context, viewPrizeList []*biz.ViewPrize) error { 21 | if err := a.adminCase.AddPrizeList(ctx, viewPrizeList); err != nil { 22 | log.ErrorContextf(ctx, "adminService|AddPrizeList err:%v", err) 23 | return fmt.Errorf("adminService|AddPrizeList:%v", err) 24 | } 25 | return nil 26 | } 27 | 28 | // ClearPrize 清空奖品 29 | func (a *AdminService) ClearPrize(ctx context.Context) error { 30 | if err := a.adminCase.ClearPrize(ctx); err != nil { 31 | log.ErrorContextf(ctx, "adminService|ClearPrize err:%v", err) 32 | return fmt.Errorf("adminService|ClearPrize:%v", err) 33 | } 34 | return nil 35 | } 36 | 37 | func (a *AdminService) ImportCoupon(ctx context.Context, prizeID uint, codes string) error { 38 | successNum, failNum, err := a.adminCase.ImportCoupon(ctx, prizeID, codes) 39 | if err != nil { 40 | return fmt.Errorf("AdminService|ImportCoupon|%v", err) 41 | } 42 | log.Infof("ImportCoupon|successNum=%d|failNum=%d\n", successNum, failNum) 43 | return nil 44 | } 45 | 46 | func (a *AdminService) ClearCoupon(ctx context.Context) error { 47 | if err := a.adminCase.ClearCoupon(ctx); err != nil { 48 | log.ErrorContextf(ctx, "adminService|ClearCoupon err:%v", err) 49 | return fmt.Errorf("adminService|ClearCoupon:%v", err) 50 | } 51 | return nil 52 | } 53 | 54 | func (a *AdminService) ImportCouponWithCache(ctx context.Context, prizeID uint, codes string) error { 55 | successNum, failNum, err := a.adminCase.ImportCouponWithCache(ctx, prizeID, codes) 56 | if err != nil { 57 | return fmt.Errorf("AdminService|ImportCouponWithCache|%v", err) 58 | } 59 | log.Infof("ImportCouponWithCache|successNum=%d|failNum=%d\n", successNum, failNum) 60 | return nil 61 | } 62 | 63 | func (a *AdminService) ClearLotteryTimes(ctx context.Context) error { 64 | if err := a.adminCase.ClearLotteryTimes(ctx); err != nil { 65 | log.ErrorContextf(ctx, "adminService|ClearCoupon err:%v", err) 66 | return fmt.Errorf("adminService|ClearCoupon:%v", err) 67 | } 68 | return nil 69 | } 70 | 71 | func (a *AdminService) ClearResult(ctx context.Context) error { 72 | if err := a.adminCase.ClearResult(ctx); err != nil { 73 | log.ErrorContextf(ctx, "adminService|ClearCoupon err:%v", err) 74 | return fmt.Errorf("adminService|ClearCoupon:%v", err) 75 | } 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /internal/service/entity.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "time" 4 | 5 | // ViewPrize 对外返回的数据(区别于存储层的数据) 6 | type ViewPrize struct { 7 | Id uint `json:"id"` 8 | Title string `json:"title"` 9 | Img string `json:"img"` 10 | PrizeNum int `json:"prize_num"` 11 | PrizeCode string `json:"prize_code"` 12 | PrizeTime uint `json:"prize_time"` 13 | LeftNum int `json:"left_num"` 14 | PrizeType uint `json:"prize_type"` 15 | PrizePlan string `json:"prize_plan"` 16 | BeginTime time.Time `json:"begin_time"` 17 | EndTime time.Time `json:"end_time"` 18 | DisplayOrder uint `json:"display_order"` 19 | SysStatus uint `json:"sys_status"` 20 | } 21 | -------------------------------------------------------------------------------- /internal/service/error_info.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "fmt" 4 | 5 | type ErrCode int // 错误码 6 | 7 | const ( 8 | Success ErrCode = 0 9 | ErrInternalServer ErrCode = 500 10 | ErrInputInvalid ErrCode = 8020 11 | ErrShouldBind ErrCode = 8021 12 | ErrJsonMarshal ErrCode = 8022 13 | ErrJwtParse ErrCode = 8023 14 | 15 | ErrLogin ErrCode = 10000 16 | ErrIPLimitInvalid ErrCode = 10001 17 | ErrUserLimitInvalid ErrCode = 10002 18 | ErrBlackedIP ErrCode = 10003 19 | ErrBlackedUser ErrCode = 10004 20 | ErrPrizeNotEnough ErrCode = 10005 21 | ErrNotWon ErrCode = 100010 22 | ) 23 | 24 | var errMsgDic = map[ErrCode]string{ 25 | Success: "success", 26 | ErrInternalServer: "internal server error", 27 | ErrInputInvalid: "input invalid", 28 | ErrShouldBind: "should bind failed", 29 | ErrJsonMarshal: "json marsh failed", 30 | ErrJwtParse: "jwt marshal failed", 31 | ErrLogin: "login fail", 32 | ErrIPLimitInvalid: "ip day num limited", 33 | ErrUserLimitInvalid: "user day num limited", 34 | ErrBlackedIP: "blacked ip", 35 | ErrBlackedUser: "blacked user", 36 | ErrPrizeNotEnough: "prize not enough", 37 | ErrNotWon: "not won,please try again!", 38 | } 39 | 40 | // GetErrMsg 获取错误描述 41 | func GetErrMsg(code ErrCode) string { 42 | if msg, ok := errMsgDic[code]; ok { 43 | return msg 44 | } 45 | return fmt.Sprintf("unknown error code %d", code) 46 | } 47 | -------------------------------------------------------------------------------- /internal/service/lotteryv1.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/BitofferHub/lotterysvr/internal/biz" 7 | "github.com/BitofferHub/lotterysvr/internal/constant" 8 | "github.com/BitofferHub/lotterysvr/internal/utils" 9 | "github.com/BitofferHub/pkg/middlewares/lock" 10 | "github.com/BitofferHub/pkg/middlewares/log" 11 | pb "github.com/BitofferHub/proto_center/api/lotterysvr/v1" 12 | ) 13 | 14 | func (l *LotteryService) LotteryV1(ctx context.Context, req *pb.LotteryReq) (*pb.LotteryRsp, error) { 15 | rsp := &pb.LotteryRsp{ 16 | CommonRsp: &pb.CommonRspInfo{ 17 | Code: int32(Success), 18 | Msg: GetErrMsg(Success), 19 | UserId: req.UserId, 20 | }, 21 | } 22 | defer func() { 23 | // 通过对应的Code,获取Msg 24 | rsp.CommonRsp.Msg = GetErrMsg(ErrCode(rsp.CommonRsp.Code)) 25 | }() 26 | var ( 27 | ok bool 28 | err error 29 | ) 30 | // 1. 根据token解析出用户信息 31 | //jwtClaims, err := utils.ParseJwtToken(req.Token, constant.SecretKey) 32 | //if err != nil || jwtClaims == nil { 33 | // rsp.CommonRsp.Code = int32(ErrJwtParse) 34 | // log.Errorf("jwt parse err, token=%s,user_id=%s\n", req.Token, req.UserId) 35 | // return nil, fmt.Errorf("LotteryV1|jwt parse err") 36 | //} 37 | //log.Infof("LotteryV1|req====%+v", req) 38 | userID := uint(req.UserId) 39 | log.Infof("LotteryV1|user_id=%d", userID) 40 | lockKey := fmt.Sprintf(constant.LotteryLockKeyPrefix+"%d", userID) 41 | lock1 := lock.NewRedisLock(lockKey, lock.WithExpireSeconds(5), lock.WithWatchDogMode()) 42 | 43 | // 1. 用户抽奖分布式锁定,防止同一个用户同一时间抽奖抽奖多次 44 | if err := lock1.Lock(ctx); err != nil { 45 | rsp.CommonRsp.Code = int32(ErrInternalServer) 46 | log.ErrorContextf(ctx, "LotteryHandler|Process:%v", err) 47 | return nil, fmt.Errorf("LotteryV1|lock err") 48 | } 49 | defer lock1.Unlock(ctx) 50 | // 2. 验证用户今日抽奖次数 51 | ok, err = l.limitCase.CheckUserDayLotteryTimes(ctx, userID) 52 | if err != nil { 53 | rsp.CommonRsp.Code = int32(ErrInternalServer) 54 | log.ErrorContextf(ctx, "LotteryHandler|CheckUserDayLotteryTimes:%v", err) 55 | return nil, fmt.Errorf("LotteryV1|CheckUserDayLotteryTimes err") 56 | } 57 | if !ok { 58 | rsp.CommonRsp.Code = int32(ErrUserLimitInvalid) 59 | //log.InfoContextf(ctx, "LotteryHandler|CheckUserDayLotteryTimes:%v", err) 60 | return rsp, nil 61 | } 62 | 63 | // 3. 验证当天IP参与的抽奖次数 64 | ipDayLotteryTimes := l.limitCase.CheckIPLimit(ctx, req.Ip) 65 | if ipDayLotteryTimes > constant.IpLimitMax { 66 | rsp.CommonRsp.Code = int32(ErrIPLimitInvalid) 67 | //log.InfoContextf(ctx, "LotteryHandler|CheckUserDayLotteryTimes:%v", err) 68 | return rsp, nil 69 | } 70 | 71 | // 4. 验证IP是否在ip黑名单 72 | ok, blackIpInfo, err := l.limitCase.CheckBlackIP(ctx, req.Ip) 73 | if err != nil { 74 | rsp.CommonRsp.Code = int32(ErrInternalServer) 75 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackIP:%v", err) 76 | return nil, fmt.Errorf("LotteryV1|CheckBlackIP err") 77 | } 78 | // ip黑明单生效 79 | if !ok { 80 | rsp.CommonRsp.Code = int32(ErrBlackedIP) 81 | //log.InfoContextf(ctx, "LotteryHandler|CheckBlackIP blackIpInfo is %+v\n", blackIpInfo) 82 | return rsp, nil 83 | } 84 | 85 | // 5. 验证用户是否在黑明单中 86 | ok, blackUserInfo, err := l.limitCase.CheckBlackUser(ctx, userID) 87 | if err != nil { 88 | rsp.CommonRsp.Code = int32(ErrInternalServer) 89 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackUser:%v", err) 90 | return nil, fmt.Errorf("LotteryV1|CheckBlackIP err") 91 | } 92 | // 用户黑明单生效 93 | if !ok { 94 | rsp.CommonRsp.Code = int32(ErrBlackedUser) 95 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackUser blackUserInfo is %v\n", blackUserInfo) 96 | return rsp, nil 97 | } 98 | 99 | // 6. 中奖逻辑实现 100 | prizeCode := utils.Random(constant.PrizeCodeMax) 101 | log.InfoContextf(ctx, "LotteryHandlerV1|prizeCode=%d\n", prizeCode) 102 | prize, err := l.lotteryCase.GetPrize(ctx, prizeCode) 103 | if err != nil { 104 | rsp.CommonRsp.Code = int32(ErrInternalServer) 105 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackUser:%v", err) 106 | return nil, fmt.Errorf("LotteryV1|GetPrize err") 107 | } 108 | if prize == nil || prize.PrizeNum < 0 || (prize.PrizeNum > 0 && prize.LeftNum <= 0) { 109 | rsp.CommonRsp.Code = int32(ErrNotWon) 110 | return rsp, nil 111 | } 112 | 113 | // 7. 有剩余奖品发放 114 | if prize.PrizeNum > 0 { 115 | ok, err = l.lotteryCase.GiveOutPrize(ctx, int(prize.Id)) 116 | if err != nil { 117 | rsp.CommonRsp.Code = int32(ErrInternalServer) 118 | log.ErrorContextf(ctx, "LotteryHandler|GiveOutPrize:%v", err) 119 | return nil, fmt.Errorf("LotteryV1|GiveOutPrize err") 120 | } 121 | // 奖品不足,发放失败 122 | if !ok { 123 | rsp.CommonRsp.Code = int32(ErrPrizeNotEnough) 124 | //log.InfoContextf(ctx, "LotteryHandler|GiveOutPrize:%v", err) 125 | return rsp, nil 126 | } 127 | } 128 | 129 | /***如果中奖记录重要的的话,可以考虑用事务将下面逻辑包裹*****/ 130 | // 8. 发优惠券 131 | if prize.PrizeType == constant.PrizeTypeCouponDiff { 132 | code, err := l.lotteryCase.PrizeCouponDiff(ctx, int(prize.Id)) 133 | if err != nil { 134 | rsp.CommonRsp.Code = int32(ErrInternalServer) 135 | //log.InfoContextf(ctx, "LotteryHandler|PrizeCouponDiff:%v", err) 136 | return nil, fmt.Errorf("LotteryV1|PrizeCouponDiff err") 137 | } 138 | if code == "" { 139 | rsp.CommonRsp.Code = int32(ErrNotWon) 140 | //log.InfoContextf(ctx, "LotteryHandler|PrizeCouponDiff coupon left is nil") 141 | return rsp, nil 142 | } 143 | prize.CouponCode = code 144 | } 145 | rsp.PrizeInfo = &pb.LotteryPrizeInfo{ 146 | Id: uint32(prize.Id), 147 | Title: prize.Title, 148 | PrizeNum: int32(prize.PrizeNum), 149 | LeftNum: int32(prize.LeftNum), 150 | PrizeCodeLow: int32(prize.PrizeCodeLow), 151 | PrizeCodeHigh: int32(prize.PrizeCodeHigh), 152 | Img: prize.Img, 153 | DisplayOrder: uint32(prize.DisplayOrder), 154 | PrizeType: uint32(prize.PrizeType), 155 | PrizeProfile: prize.PrizeProfile, 156 | CouponCode: prize.CouponCode, 157 | } 158 | 159 | // 9 记录中奖纪录 160 | if err := l.lotteryCase.LotteryResult(ctx, prize, userID, req.UserName, req.Ip, prizeCode); err != nil { 161 | rsp.CommonRsp.Code = int32(ErrInternalServer) 162 | log.ErrorContextf(ctx, "LotteryHandler|PrizeCouponDiff:%v", err) 163 | return nil, fmt.Errorf("LotteryV1|LotteryResult err") 164 | } 165 | 166 | // 10. 如果中了实物大奖,需要把ip和用户置于黑明单中一段时间,防止同一个用户频繁中大奖 167 | if prize.PrizeType == constant.PrizeTypeEntityLarge { 168 | lotteryUserInfo := biz.LotteryUserInfo{ 169 | UserID: userID, 170 | UserName: req.UserName, 171 | IP: req.Ip, 172 | } 173 | log.InfoContextf(ctx, "LotteryV1|user_id=%d", userID) 174 | if err := l.lotteryCase.PrizeLargeBlackLimit(ctx, blackUserInfo, blackIpInfo, &lotteryUserInfo); err != nil { 175 | rsp.CommonRsp.Code = int32(ErrInternalServer) 176 | log.ErrorContextf(ctx, "LotteryHandler|PrizeLargeBlackLimit:%v", err) 177 | return nil, fmt.Errorf("LotteryV1|PrizeLargeBlackLimit err") 178 | } 179 | } 180 | 181 | return rsp, nil 182 | } 183 | -------------------------------------------------------------------------------- /internal/service/lotteryv2.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/BitofferHub/lotterysvr/internal/biz" 7 | "github.com/BitofferHub/lotterysvr/internal/constant" 8 | "github.com/BitofferHub/lotterysvr/internal/utils" 9 | "github.com/BitofferHub/pkg/middlewares/lock" 10 | "github.com/BitofferHub/pkg/middlewares/log" 11 | pb "github.com/BitofferHub/proto_center/api/lotterysvr/v1" 12 | ) 13 | 14 | func (l *LotteryService) LotteryV2(ctx context.Context, req *pb.LotteryReq) (*pb.LotteryRsp, error) { 15 | rsp := &pb.LotteryRsp{ 16 | CommonRsp: &pb.CommonRspInfo{ 17 | Code: int32(Success), 18 | Msg: GetErrMsg(Success), 19 | UserId: req.UserId, 20 | }, 21 | } 22 | defer func() { 23 | // 通过对应的Code,获取Msg 24 | rsp.CommonRsp.Msg = GetErrMsg(ErrCode(rsp.CommonRsp.Code)) 25 | }() 26 | var ( 27 | ok bool 28 | err error 29 | ) 30 | // 1. 根据token解析出用户信息 31 | //jwtClaims, err := utils.ParseJwtToken(req.Token, constant.SecretKey) 32 | //if err != nil || jwtClaims == nil { 33 | // rsp.CommonRsp.Code = int32(ErrJwtParse) 34 | // log.Errorf("jwt parse err, token=%s,user_id=%s\n", req.Token, req.UserId) 35 | // return nil, fmt.Errorf("LotteryV1|jwt parse err") 36 | //} 37 | userID := uint(req.UserId) 38 | log.Infof("LotteryV2|user_id=%d", userID) 39 | lockKey := fmt.Sprintf(constant.LotteryLockKeyPrefix+"%d", userID) 40 | lock1 := lock.NewRedisLock(lockKey, lock.WithExpireSeconds(5), lock.WithWatchDogMode()) 41 | 42 | // 1. 用户抽奖分布式锁定,防重入 43 | if err := lock1.Lock(ctx); err != nil { 44 | rsp.CommonRsp.Code = int32(ErrInternalServer) 45 | log.ErrorContextf(ctx, "LotteryHandler|Process:%v", err) 46 | return nil, fmt.Errorf("LotteryV1|lock err") 47 | } 48 | defer lock1.Unlock(ctx) 49 | 50 | // 2. 验证用户今日抽奖次数 51 | ok, err = l.limitCase.CheckUserDayLotteryTimesWithCache(ctx, userID) 52 | if err != nil { 53 | rsp.CommonRsp.Code = int32(ErrInternalServer) 54 | log.ErrorContextf(ctx, "LotteryHandler|CheckUserDayLotteryTimes:%v", err) 55 | return nil, fmt.Errorf("LotteryV1|CheckUserDayLotteryTimes err") 56 | } 57 | if !ok { 58 | rsp.CommonRsp.Code = int32(ErrUserLimitInvalid) 59 | log.InfoContextf(ctx, "LotteryHandler|CheckUserDayLotteryTimes:%v", err) 60 | return rsp, nil 61 | } 62 | 63 | // 3. 验证当天IP参与的抽奖次数 64 | ipDayLotteryTimes := l.limitCase.CheckIPLimit(ctx, req.Ip) 65 | if ipDayLotteryTimes > constant.IpLimitMax { 66 | rsp.CommonRsp.Code = int32(ErrIPLimitInvalid) 67 | log.InfoContextf(ctx, "LotteryHandler|CheckUserDayLotteryTimes:%v", err) 68 | return rsp, nil 69 | } 70 | 71 | // 4. 验证IP是否在ip黑名单 72 | ok, blackIpInfo, err := l.limitCase.CheckBlackIPWithCache(ctx, req.Ip) 73 | if err != nil { 74 | rsp.CommonRsp.Code = int32(ErrInternalServer) 75 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackIP:%v", err) 76 | return nil, fmt.Errorf("LotteryV1|CheckBlackIP err") 77 | } 78 | // ip黑明单生效 79 | if !ok { 80 | rsp.CommonRsp.Code = int32(ErrBlackedIP) 81 | log.InfoContextf(ctx, "LotteryHandler|CheckBlackIP blackIpInfo is %+v\n", blackIpInfo) 82 | return rsp, nil 83 | } 84 | 85 | // 5. 验证用户是否在黑明单中 86 | ok, blackUserInfo, err := l.limitCase.CheckBlackUserWithCache(ctx, userID) 87 | if err != nil { 88 | rsp.CommonRsp.Code = int32(ErrInternalServer) 89 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackUser:%v", err) 90 | return nil, fmt.Errorf("LotteryV1|CheckBlackIP err") 91 | } 92 | // 用户黑明单生效 93 | if !ok { 94 | rsp.CommonRsp.Code = int32(ErrBlackedUser) 95 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackUser blackUserInfo is %v\n", blackUserInfo) 96 | return rsp, nil 97 | } 98 | 99 | // 6. 中奖逻辑实现 100 | prizeCode := utils.Random(constant.PrizeCodeMax) 101 | log.InfoContextf(ctx, "LotteryHandlerV1|prizeCode=%d\n", prizeCode) 102 | prize, err := l.lotteryCase.GetPrizeWithCache(ctx, prizeCode) 103 | if err != nil { 104 | rsp.CommonRsp.Code = int32(ErrInternalServer) 105 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackUser:%v", err) 106 | return nil, fmt.Errorf("LotteryV1|GetPrize err") 107 | } 108 | if prize == nil || prize.PrizeNum < 0 || (prize.PrizeNum > 0 && prize.LeftNum <= 0) { 109 | rsp.CommonRsp.Code = int32(ErrNotWon) 110 | return rsp, nil 111 | } 112 | 113 | // 7. 有剩余奖品发放 114 | if prize.PrizeNum > 0 { 115 | ok, err = l.lotteryCase.GiveOutPrizeWithCache(ctx, int(prize.Id)) 116 | if err != nil { 117 | rsp.CommonRsp.Code = int32(ErrInternalServer) 118 | log.ErrorContextf(ctx, "LotteryHandler|GiveOutPrize:%v", err) 119 | return nil, fmt.Errorf("LotteryV1|GiveOutPrize err") 120 | } 121 | // 奖品不足,发放失败 122 | if !ok { 123 | rsp.CommonRsp.Code = int32(ErrPrizeNotEnough) 124 | log.InfoContextf(ctx, "LotteryHandler|GiveOutPrize:%v", err) 125 | return rsp, nil 126 | } 127 | } 128 | 129 | /***如果中奖记录重要的的话,可以考虑用事务将下面逻辑包裹*****/ 130 | // 8. 发优惠券 131 | if prize.PrizeType == constant.PrizeTypeCouponDiff { 132 | code, err := l.lotteryCase.PrizeCouponDiffWithCache(ctx, int(prize.Id)) 133 | if err != nil { 134 | rsp.CommonRsp.Code = int32(ErrInternalServer) 135 | log.InfoContextf(ctx, "LotteryHandler|PrizeCouponDiff:%v", err) 136 | return nil, fmt.Errorf("LotteryV1|PrizeCouponDiff err") 137 | } 138 | if code == "" { 139 | rsp.CommonRsp.Code = int32(ErrNotWon) 140 | log.InfoContextf(ctx, "LotteryHandler|PrizeCouponDiff coupon left is nil") 141 | return rsp, nil 142 | } 143 | prize.CouponCode = code 144 | } 145 | rsp.PrizeInfo = &pb.LotteryPrizeInfo{ 146 | Id: uint32(prize.Id), 147 | Title: prize.Title, 148 | PrizeNum: int32(prize.PrizeNum), 149 | LeftNum: int32(prize.LeftNum), 150 | PrizeCodeLow: int32(prize.PrizeCodeLow), 151 | PrizeCodeHigh: int32(prize.PrizeCodeHigh), 152 | Img: prize.Img, 153 | DisplayOrder: uint32(prize.DisplayOrder), 154 | PrizeType: uint32(prize.PrizeType), 155 | PrizeProfile: prize.PrizeProfile, 156 | CouponCode: prize.CouponCode, 157 | } 158 | 159 | // 9 记录中奖纪录 160 | if err := l.lotteryCase.LotteryResult(ctx, prize, userID, req.UserName, req.Ip, prizeCode); err != nil { 161 | rsp.CommonRsp.Code = int32(ErrInternalServer) 162 | log.InfoContextf(ctx, "LotteryHandler|PrizeCouponDiff:%v", err) 163 | return nil, fmt.Errorf("LotteryV1|LotteryResult err") 164 | } 165 | 166 | // 10. 如果中了实物大奖,需要把ip和用户置于黑明单中一段时间,防止同一个用户频繁中大奖 167 | if prize.PrizeType == constant.PrizeTypeEntityLarge { 168 | lotteryUserInfo := biz.LotteryUserInfo{ 169 | UserID: userID, 170 | UserName: req.UserName, 171 | IP: req.Ip, 172 | } 173 | if err := l.lotteryCase.PrizeLargeBlackLimit(ctx, blackUserInfo, blackIpInfo, &lotteryUserInfo); err != nil { 174 | rsp.CommonRsp.Code = int32(ErrInternalServer) 175 | log.InfoContextf(ctx, "LotteryHandler|PrizeLargeBlackLimit:%v", err) 176 | return nil, fmt.Errorf("LotteryV1|PrizeLargeBlackLimit err") 177 | } 178 | } 179 | return rsp, nil 180 | } 181 | -------------------------------------------------------------------------------- /internal/service/lotteryv3.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/BitofferHub/lotterysvr/internal/biz" 7 | "github.com/BitofferHub/lotterysvr/internal/constant" 8 | "github.com/BitofferHub/lotterysvr/internal/utils" 9 | "github.com/BitofferHub/pkg/middlewares/lock" 10 | "github.com/BitofferHub/pkg/middlewares/log" 11 | pb "github.com/BitofferHub/proto_center/api/lotterysvr/v1" 12 | ) 13 | 14 | func (l *LotteryService) LotteryV3(ctx context.Context, req *pb.LotteryReq) (*pb.LotteryRsp, error) { 15 | rsp := &pb.LotteryRsp{ 16 | CommonRsp: &pb.CommonRspInfo{ 17 | Code: int32(Success), 18 | Msg: GetErrMsg(Success), 19 | UserId: req.UserId, 20 | }, 21 | } 22 | defer func() { 23 | // 通过对应的Code,获取Msg 24 | rsp.CommonRsp.Msg = GetErrMsg(ErrCode(rsp.CommonRsp.Code)) 25 | }() 26 | var ( 27 | ok bool 28 | err error 29 | ) 30 | // 1. 根据token解析出用户信息 31 | //jwtClaims, err := utils.ParseJwtToken(req.Token, constant.SecretKey) 32 | //if err != nil || jwtClaims == nil { 33 | // rsp.CommonRsp.Code = int32(ErrJwtParse) 34 | // log.Errorf("jwt parse err, token=%s,user_id=%s\n", req.Token, req.UserId) 35 | // return nil, fmt.Errorf("LotteryV3|jwt parse err") 36 | //} 37 | userID := uint(req.UserId) 38 | log.Infof("LotteryV3|user_id=%d", userID) 39 | lockKey := fmt.Sprintf(constant.LotteryLockKeyPrefix+"%d", userID) 40 | lock1 := lock.NewRedisLock(lockKey, lock.WithExpireSeconds(5), lock.WithWatchDogMode()) 41 | 42 | // 1. 用户抽奖分布式锁定,防重入 43 | if err := lock1.Lock(ctx); err != nil { 44 | rsp.CommonRsp.Code = int32(ErrInternalServer) 45 | log.ErrorContextf(ctx, "LotteryHandler|Process:%v", err) 46 | return nil, fmt.Errorf("LotteryV3|lock err") 47 | } 48 | defer lock1.Unlock(ctx) 49 | 50 | // 2. 验证用户今日抽奖次数 51 | ok, err = l.limitCase.CheckUserDayLotteryTimesWithCache(ctx, userID) 52 | if err != nil { 53 | rsp.CommonRsp.Code = int32(ErrInternalServer) 54 | log.ErrorContextf(ctx, "LotteryHandler|CheckUserDayLotteryTimes:%v", err) 55 | return nil, fmt.Errorf("LotteryV3|CheckUserDayLotteryTimes err") 56 | } 57 | if !ok { 58 | rsp.CommonRsp.Code = int32(ErrUserLimitInvalid) 59 | //log.InfoContextf(ctx, "LotteryHandler|CheckUserDayLotteryTimes:%v", err) 60 | return rsp, nil 61 | } 62 | 63 | // 3. 验证当天IP参与的抽奖次数 64 | ipDayLotteryTimes := l.limitCase.CheckIPLimit(ctx, req.Ip) 65 | if ipDayLotteryTimes > constant.IpLimitMax { 66 | rsp.CommonRsp.Code = int32(ErrIPLimitInvalid) 67 | //log.InfoContextf(ctx, "LotteryHandler|CheckUserDayLotteryTimes:%v", err) 68 | return rsp, nil 69 | } 70 | 71 | // 4. 验证IP是否在ip黑名单 72 | ok, blackIpInfo, err := l.limitCase.CheckBlackIPWithCache(ctx, req.Ip) 73 | if err != nil { 74 | rsp.CommonRsp.Code = int32(ErrInternalServer) 75 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackIP:%v", err) 76 | return nil, fmt.Errorf("LotteryV3|CheckBlackIP err") 77 | } 78 | // ip黑明单生效 79 | if !ok { 80 | rsp.CommonRsp.Code = int32(ErrBlackedIP) 81 | //log.InfoContextf(ctx, "LotteryHandler|CheckBlackIP blackIpInfo is %+v\n", blackIpInfo) 82 | return rsp, nil 83 | } 84 | 85 | // 5. 验证用户是否在黑明单中 86 | ok, blackUserInfo, err := l.limitCase.CheckBlackUserWithCache(ctx, userID) 87 | if err != nil { 88 | rsp.CommonRsp.Code = int32(ErrInternalServer) 89 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackUser:%v", err) 90 | return nil, fmt.Errorf("LotteryV3|CheckBlackIP err") 91 | } 92 | // 用户黑明单生效 93 | if !ok { 94 | rsp.CommonRsp.Code = int32(ErrBlackedUser) 95 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackUser blackUserInfo is %v\n", blackUserInfo) 96 | return rsp, nil 97 | } 98 | 99 | // 6. 中奖逻辑实现 100 | prizeCode := utils.Random(constant.PrizeCodeMax) 101 | log.InfoContextf(ctx, "LotteryHandlerV1|prizeCode=%d\n", prizeCode) 102 | prize, err := l.lotteryCase.GetPrizeWithCache(ctx, prizeCode) 103 | if err != nil { 104 | rsp.CommonRsp.Code = int32(ErrInternalServer) 105 | log.ErrorContextf(ctx, "LotteryHandler|CheckBlackUser:%v", err) 106 | return nil, fmt.Errorf("LotteryV3|GetPrize err") 107 | } 108 | if prize == nil || prize.PrizeNum < 0 || (prize.PrizeNum > 0 && prize.LeftNum <= 0) { 109 | rsp.CommonRsp.Code = int32(ErrNotWon) 110 | return rsp, nil 111 | } 112 | 113 | // 7. 有剩余奖品发放 114 | if prize.PrizeNum > 0 { 115 | num, err := l.lotteryCase.GetPrizeNumWithPool(ctx, prize.Id) 116 | if err != nil { 117 | rsp.CommonRsp.Code = int32(ErrInternalServer) 118 | log.ErrorContextf(ctx, "LotteryHandler|GiveOutPrize:%v", err) 119 | return nil, fmt.Errorf("LotteryV3|GetPrizeNumWithPool err") 120 | } 121 | // 奖品池奖品不够,不能发奖 122 | if num <= 0 { 123 | rsp.CommonRsp.Code = int32(ErrNotWon) 124 | //log.InfoContextf(ctx, "LotteryHandler|GiveOutPrize|prize num not enough") 125 | return rsp, nil 126 | } 127 | ok, err = l.lotteryCase.GiveOutPrizeWithPool(ctx, int(prize.Id)) 128 | if err != nil { 129 | rsp.CommonRsp.Code = int32(ErrInternalServer) 130 | log.ErrorContextf(ctx, "LotteryHandler|GiveOutPrize:%v", err) 131 | return nil, fmt.Errorf("LotteryV3|GiveOutPrize err") 132 | } 133 | // 奖品不足,发放失败 134 | if !ok { 135 | rsp.CommonRsp.Code = int32(ErrPrizeNotEnough) 136 | //log.InfoContextf(ctx, "LotteryHandler|GiveOutPrize:%v", err) 137 | return rsp, nil 138 | } 139 | } 140 | 141 | /***如果中奖记录重要的的话,可以考虑用事务将下面逻辑包裹*****/ 142 | // 8. 发优惠券 143 | if prize.PrizeType == constant.PrizeTypeCouponDiff { 144 | code, err := l.lotteryCase.PrizeCouponDiffWithCache(ctx, int(prize.Id)) 145 | if err != nil { 146 | rsp.CommonRsp.Code = int32(ErrInternalServer) 147 | //log.InfoContextf(ctx, "LotteryHandler|PrizeCouponDiff:%v", err) 148 | return nil, fmt.Errorf("LotteryV3|PrizeCouponDiff err") 149 | } 150 | if code == "" { 151 | rsp.CommonRsp.Code = int32(ErrNotWon) 152 | //log.InfoContextf(ctx, "LotteryHandler|PrizeCouponDiff coupon left is nil") 153 | return rsp, nil 154 | } 155 | prize.CouponCode = code 156 | } 157 | rsp.PrizeInfo = &pb.LotteryPrizeInfo{ 158 | Id: uint32(prize.Id), 159 | Title: prize.Title, 160 | PrizeNum: int32(prize.PrizeNum), 161 | LeftNum: int32(prize.LeftNum), 162 | PrizeCodeLow: int32(prize.PrizeCodeLow), 163 | PrizeCodeHigh: int32(prize.PrizeCodeHigh), 164 | Img: prize.Img, 165 | DisplayOrder: uint32(prize.DisplayOrder), 166 | PrizeType: uint32(prize.PrizeType), 167 | PrizeProfile: prize.PrizeProfile, 168 | CouponCode: prize.CouponCode, 169 | } 170 | 171 | // 9 记录中奖纪录 172 | if err := l.lotteryCase.LotteryResult(ctx, prize, userID, req.UserName, req.Ip, prizeCode); err != nil { 173 | rsp.CommonRsp.Code = int32(ErrInternalServer) 174 | //log.InfoContextf(ctx, "LotteryHandler|PrizeCouponDiff:%v", err) 175 | return nil, fmt.Errorf("LotteryV3|LotteryResult err") 176 | } 177 | 178 | // 10. 如果中了实物大奖,需要把ip和用户置于黑明单中一段时间,防止同一个用户频繁中大奖 179 | if prize.PrizeType == constant.PrizeTypeEntityLarge { 180 | lotteryUserInfo := biz.LotteryUserInfo{ 181 | UserID: userID, 182 | UserName: req.UserName, 183 | IP: req.Ip, 184 | } 185 | if err := l.lotteryCase.PrizeLargeBlackLimit(ctx, blackUserInfo, blackIpInfo, &lotteryUserInfo); err != nil { 186 | rsp.CommonRsp.Code = int32(ErrInternalServer) 187 | //log.InfoContextf(ctx, "LotteryHandler|PrizeLargeBlackLimit:%v", err) 188 | return nil, fmt.Errorf("LotteryV3|PrizeLargeBlackLimit err") 189 | } 190 | } 191 | 192 | return rsp, nil 193 | } 194 | 195 | // CronJobResetIPLotteryNumsTask 定时任务方法, 重置所有的IP抽奖次数 196 | func (l *LotteryService) CronJobResetIPLotteryNumsTask() { 197 | l.limitCase.CronJobResetIPLotteryNums() 198 | } 199 | 200 | // CronJobResetUserLotteryNumsTask 定时任务方法, 重置今日用户抽奖次数 201 | func (l *LotteryService) CronJobResetUserLotteryNumsTask() { 202 | l.limitCase.CronJobResetUserLotteryNums() 203 | } 204 | 205 | // CronJobResetAllPrizePlanTask 定时任务方法, 重置所有的奖品发奖计划 206 | func (l *LotteryService) CronJobResetAllPrizePlanTask() { 207 | l.adminCase.ResetAllPrizePlan() 208 | } 209 | 210 | // CronJobFillAllPrizePoolTask 定时任务方法, 填充奖品池 211 | func (l *LotteryService) CronJobFillAllPrizePoolTask() { 212 | l.adminCase.FillAllPrizePool() 213 | } 214 | -------------------------------------------------------------------------------- /internal/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/BitofferHub/lotterysvr/internal/biz" 5 | pb "github.com/BitofferHub/proto_center/api/lotterysvr/v1" 6 | "github.com/google/wire" 7 | ) 8 | 9 | // ProviderSet is service providers. 10 | var ProviderSet = wire.NewSet(NewLotteryService, NewAdminService) 11 | 12 | type LotteryService struct { 13 | pb.UnimplementedLotteryServer 14 | lotteryCase *biz.LotteryCase 15 | limitCase *biz.LimitCase 16 | adminCase *biz.AdminCase 17 | } 18 | 19 | func NewLotteryService(loc *biz.LotteryCase, lic *biz.LimitCase, ac *biz.AdminCase) *LotteryService { 20 | return &LotteryService{ 21 | lotteryCase: loc, 22 | limitCase: lic, 23 | adminCase: ac, 24 | } 25 | } 26 | 27 | // AdminService 奖品管理后台 28 | type AdminService struct { 29 | adminCase *biz.AdminCase 30 | } 31 | 32 | func NewAdminService(ac *biz.AdminCase) *AdminService { 33 | return &AdminService{ 34 | adminCase: ac, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "github.com/BitofferHub/lotterysvr/internal/conf" 6 | "github.com/BitofferHub/lotterysvr/internal/service" 7 | "github.com/BitofferHub/lotterysvr/internal/utils" 8 | "github.com/google/wire" 9 | "time" 10 | ) 11 | 12 | // ProviderSet is service providers. 13 | var ProviderSet = wire.NewSet(NewTaskServer) 14 | 15 | type TaskServer struct { 16 | // 需要什么service, 就修改成自己的service 17 | service *service.LotteryService 18 | scheduler *TaskScheduler 19 | } 20 | 21 | func (t *TaskServer) Stop(ctx context.Context) error { 22 | t.scheduler.Stop() 23 | return nil 24 | } 25 | 26 | // NewJobs 添加Job方法 27 | func (t *TaskServer) NewJobs() []Job { 28 | return []Job{t.job1, t.job2, t.job3, t.job4} 29 | } 30 | 31 | // NewTaskServer 注入对应service 32 | func NewTaskServer(s *service.LotteryService, c *conf.Server) *TaskServer { 33 | t := &TaskServer{ 34 | service: s, 35 | } 36 | conf := c.GetTask() 37 | t.scheduler = NewScheduler(conf.GetAddr(), NewTasks(conf, t.NewJobs())) 38 | 39 | return t 40 | } 41 | func NewTasks(c *conf.Server_TASK, jobs []Job) []*Task { 42 | var tasks []*Task 43 | for i, job := range jobs { 44 | tasks = append(tasks, &Task{ 45 | Name: c.Tasks[i].Name, 46 | Type: c.Tasks[i].Type, 47 | Schedule: c.Tasks[i].Schedule, 48 | Handler: job, 49 | }) 50 | } 51 | 52 | return tasks 53 | } 54 | 55 | func (t *TaskServer) job1() { 56 | t.service.CronJobResetIPLotteryNumsTask() 57 | next := utils.NextDayTime() 58 | t.scheduler.AddTask(Task{ 59 | Name: "job1", 60 | Type: "once", 61 | NextTime: next, 62 | Handler: t.job1, 63 | }) 64 | } 65 | 66 | func (t *TaskServer) job2() { 67 | t.service.CronJobResetUserLotteryNumsTask() 68 | next := utils.NextDayTime() 69 | t.scheduler.AddTask(Task{ 70 | Name: "job2", 71 | Type: "once", 72 | NextTime: next, 73 | Handler: t.job2, 74 | }) 75 | } 76 | 77 | func (t *TaskServer) job3() { 78 | t.service.CronJobResetAllPrizePlanTask() 79 | next := time.Now().Add(5 * time.Minute) 80 | t.scheduler.AddTask(Task{ 81 | Name: "job3", 82 | Type: "once", 83 | NextTime: next, 84 | Handler: t.job3, 85 | }) 86 | } 87 | 88 | func (t *TaskServer) job4() { 89 | t.service.CronJobFillAllPrizePoolTask() 90 | next := time.Now().Add(1 * time.Minute) 91 | t.scheduler.AddTask(Task{ 92 | Name: "job4", 93 | Type: "once", 94 | NextTime: next, 95 | Handler: t.job4, 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /internal/task/type.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/BitofferHub/pkg/middlewares/lock" 7 | cronpkg "github.com/robfig/cron/v3" 8 | "time" 9 | ) 10 | 11 | // 此文件是task定时任务设计内容, 无须进行修改 12 | var locolAddr = "localhost:6379" 13 | 14 | var ctx, cancel = context.WithCancel(context.Background()) 15 | 16 | func (t *TaskServer) Start(ctx context.Context) error { 17 | t.scheduler.Start() 18 | return nil 19 | } 20 | 21 | // TaskType represents the type of task (case1, case2, once) 22 | 23 | const ( 24 | Cron = "cron" 25 | Once = "once" 26 | ) 27 | 28 | // Task represents a scheduled task 29 | type Task struct { 30 | Name string 31 | Type string 32 | // 有两种写法 33 | // 第一种: "@every 1h2m30s" 只能写到h, m, s 34 | // 第二种: cron表达式: "* 1 * * *" 35 | // 第三种: 只用于单次执行任务, 例如"4s", 代表任务添加后4s执行 只能写h, m, s 36 | Schedule string 37 | NextTime time.Time 38 | Handler Job 39 | } 40 | 41 | type Job func() 42 | 43 | func (t *Task) Run() { 44 | // 判断 45 | switch t.Type { 46 | case Once: 47 | // 单次执行 48 | t.once() 49 | case Cron: 50 | // 定时执行 51 | t.cron() 52 | default: 53 | panic("任务类型有误") 54 | } 55 | 56 | } 57 | 58 | func (t *Task) once() { 59 | // 单次执行 60 | go func() { 61 | var waitTime time.Duration 62 | var err error 63 | if t.Schedule != "" { 64 | waitTime, err = time.ParseDuration(t.Schedule) 65 | if err != nil { 66 | panic("once 任务类型表达式有误") 67 | } 68 | } 69 | if !t.NextTime.IsZero() { 70 | // 计算等待时间 71 | waitTime = t.NextTime.Sub(time.Now()) 72 | } 73 | t.run(waitTime) 74 | }() 75 | } 76 | 77 | func (t *Task) run(waitTime time.Duration) error { 78 | // 使用time.After等待指定时间 79 | select { 80 | case <-time.After(waitTime): 81 | // 先抢锁 82 | locker := lock.NewRedisLock(t.Name, lock.WithExpireSeconds(int64(waitTime.Seconds()))) 83 | err := locker.Lock(context.Background()) 84 | if err != nil { 85 | // 抢锁失败, 直接跳过执行, 下一轮 86 | return nil 87 | } 88 | if t.Handler == nil { 89 | panic(fmt.Sprintf("请检查%s任务%s的Handler是否为空", t.Type, t.Name)) 90 | } 91 | t.Handler() 92 | if t.Type == Once { 93 | locker.Unlock(context.Background()) 94 | } 95 | case <-ctx.Done(): 96 | // 终止 97 | return ctx.Err() 98 | } 99 | return nil 100 | } 101 | 102 | func (t *Task) cron() { 103 | // 定时执行 104 | go func() { 105 | // 解析 cron 表达式 106 | schedule, err := cronpkg.ParseStandard(t.Schedule) 107 | if err != nil { 108 | panic(fmt.Sprintf("请检查定时任务%s的cron表达式是否正确", t.Name)) 109 | } 110 | for { 111 | // 获取 cron 表达式下一次执行的时间 112 | nextTime := schedule.Next(time.Now()) 113 | fmt.Println(nextTime) 114 | // 计算等待时间 115 | waitTime := nextTime.Sub(time.Now()) 116 | if err := t.run(waitTime); err != nil { 117 | return 118 | } 119 | } 120 | }() 121 | } 122 | 123 | // TaskScheduler represents the task scheduler 124 | type TaskScheduler struct { 125 | // 定时控制 126 | tasks []*Task // 要执行的任务 127 | } 128 | 129 | // NewScheduler creates a new taskScheduler instance 130 | func NewScheduler(addr string, tasks []*Task) *TaskScheduler { 131 | if addr != "" { 132 | locolAddr = addr 133 | } 134 | return &TaskScheduler{ 135 | tasks: tasks, 136 | } 137 | } 138 | 139 | // AddTask adds a new task to the scheduler 140 | func (s *TaskScheduler) AddTask(task Task) { 141 | task.Run() 142 | } 143 | 144 | // Start starts the scheduler 145 | func (s *TaskScheduler) Start() { 146 | // 遍历所有任务 147 | for _, task := range s.tasks { 148 | task.Run() 149 | } 150 | } 151 | 152 | // Stop stops the scheduler 153 | func (s *TaskScheduler) Stop() { 154 | cancel() 155 | } 156 | -------------------------------------------------------------------------------- /internal/task/user.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/BitofferHub/lotterysvr/internal/constant" 7 | "github.com/BitofferHub/lotterysvr/internal/utils" 8 | "github.com/BitofferHub/pkg/middlewares/cache" 9 | "github.com/BitofferHub/pkg/middlewares/log" 10 | "time" 11 | ) 12 | 13 | func DoResetUserLotteryNumsTask() { 14 | go ResetUserLotteryNums() 15 | } 16 | 17 | func ResetUserLotteryNums() { 18 | //log.Infof("重置今日用户抽奖次数") 19 | for i := 0; i < constant.IpFrameSize; i++ { 20 | key := fmt.Sprintf(constant.UserLotteryDayNumPrefix+"%d", i) 21 | if err := cache.GetRedisCli().Delete(context.Background(), key); err != nil { 22 | log.Errorf("ResetIPLotteryNums err:%v", err) 23 | } 24 | } 25 | 26 | // IP当天的统计数,整点归零,设置定时器 27 | duration := utils.NextDayDuration() 28 | time.AfterFunc(duration, ResetUserLotteryNums) //等待时间段d过去,然后调用func 29 | } 30 | -------------------------------------------------------------------------------- /internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "encoding/base64" 7 | "encoding/binary" 8 | "errors" 9 | "fmt" 10 | "github.com/BitofferHub/lotterysvr/internal/constant" 11 | "github.com/golang-jwt/jwt" 12 | uuid2 "github.com/google/uuid" 13 | "math/rand" 14 | "strconv" 15 | "strings" 16 | "time" 17 | "unsafe" 18 | ) 19 | 20 | func NewUuid() string { 21 | uuid := uuid2.New() 22 | return uuid.String() 23 | } 24 | 25 | // NowUnix 当前时间戳 26 | func NowUnix() int { 27 | var sysTimeLocation, _ = time.LoadLocation("Asia/Shanghai") 28 | return int(time.Now().In(sysTimeLocation).Unix()) 29 | } 30 | 31 | // FormatFromUnixTime 将时间戳转为 yyyy-mm-dd H:i:s 格式 32 | func FormatFromUnixTime(t int64) string { 33 | cstSh, _ := time.LoadLocation("Asia/Shanghai") //上海 34 | if t > 0 { 35 | return time.Unix(t, 0).In(cstSh).Format(constant.SysTimeFormat) 36 | } else { 37 | return time.Now().In(cstSh).Format(constant.SysTimeFormat) 38 | } 39 | } 40 | 41 | // FormatFromUnixTimeShort 将时间戳转为 yyyy-mm-dd 格式 42 | func FormatFromUnixTimeShort(t int64) string { 43 | cstSh, _ := time.LoadLocation("Asia/Shanghai") //上海 44 | if t > 0 { 45 | return time.Unix(t, 0).In(cstSh).Format(constant.SysTimeFormatShort) 46 | } else { 47 | return time.Now().In(cstSh).Format(constant.SysTimeFormatShort) 48 | } 49 | } 50 | 51 | // ParseTime 将字符串转成时间 52 | func ParseTime(str string) (time.Time, error) { 53 | var sysTimeLocation, _ = time.LoadLocation("Asia/Shanghai") 54 | return time.ParseInLocation(constant.SysTimeFormat, str, sysTimeLocation) 55 | } 56 | 57 | // Random 得到一个随机数 58 | func Random(max int) int { 59 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 60 | if max < 1 { 61 | return r.Int() 62 | } else { 63 | return r.Intn(max) 64 | } 65 | } 66 | 67 | // encrypt 对一个字符串进行加密 68 | func encrypt(key, text []byte) ([]byte, error) { 69 | block, err := aes.NewCipher(key) 70 | if err != nil { 71 | return nil, err 72 | } 73 | b := base64.StdEncoding.EncodeToString(text) 74 | ciphertext := make([]byte, aes.BlockSize+len(b)) 75 | iv := ciphertext[:aes.BlockSize] 76 | cfb := cipher.NewCFBEncrypter(block, iv) 77 | cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b)) 78 | return ciphertext, nil 79 | } 80 | 81 | // decrypt 对一个字符串进行解密 82 | func decrypt(key, text []byte) ([]byte, error) { 83 | block, err := aes.NewCipher(key) 84 | if err != nil { 85 | return nil, err 86 | } 87 | if len(text) < aes.BlockSize { 88 | return nil, errors.New("ciphertext is too short") 89 | } 90 | iv := text[:aes.BlockSize] 91 | text = text[aes.BlockSize:] 92 | cfb := cipher.NewCFBDecrypter(block, iv) 93 | cfb.XORKeyStream(text, text) 94 | data, err := base64.StdEncoding.DecodeString(string(text)) 95 | if err != nil { 96 | return nil, err 97 | } 98 | return data, nil 99 | } 100 | 101 | // 在预定义字符前添加 \ 102 | // ' " \ 103 | // http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html 104 | func AddSlashes(str string) string { 105 | tmpRune := []rune{} 106 | strRune := []rune(str) 107 | for _, ch := range strRune { 108 | switch ch { 109 | case []rune{'\\'}[0], []rune{'"'}[0], []rune{'\''}[0]: 110 | tmpRune = append(tmpRune, []rune{'\\'}[0]) 111 | tmpRune = append(tmpRune, ch) 112 | default: 113 | tmpRune = append(tmpRune, ch) 114 | } 115 | } 116 | return string(tmpRune) 117 | } 118 | 119 | // StripsSlashes 删除 120 | func StripsSlashes(str string) string { 121 | dstRune := []rune{} 122 | strRune := []rune(str) 123 | for i := 0; i < len(strRune); i++ { 124 | if strRune[i] == []rune{'\\'}[0] { 125 | i++ 126 | } 127 | dstRune = append(dstRune, strRune[i]) 128 | } 129 | return string(dstRune) 130 | } 131 | 132 | // Ip4toInt 将字符串的 IP 转化为数字 133 | func Ip4toInt(ip string) int64 { 134 | bits := strings.Split(ip, ".") 135 | var sum int64 = 0 136 | if len(bits) == 4 { 137 | b0, _ := strconv.Atoi(bits[0]) 138 | b1, _ := strconv.Atoi(bits[1]) 139 | b2, _ := strconv.Atoi(bits[2]) 140 | b3, _ := strconv.Atoi(bits[3]) 141 | sum += int64(b0) << 24 142 | sum += int64(b1) << 16 143 | sum += int64(b2) << 8 144 | sum += int64(b3) 145 | } 146 | return sum 147 | } 148 | 149 | // NextDayDuration 得到当前时间到下一天零点的延时 150 | func NextDayDuration() time.Duration { 151 | year, month, day := time.Now().Add(time.Hour * 24).Date() 152 | var sysTimeLocation, _ = time.LoadLocation("Asia/Shanghai") 153 | next := time.Date(year, month, day, 0, 0, 0, 0, sysTimeLocation) 154 | return next.Sub(time.Now()) 155 | } 156 | 157 | func NextDayTime() time.Time { 158 | year, month, day := time.Now().Add(time.Hour * 24).Date() 159 | var sysTimeLocation, _ = time.LoadLocation("Asia/Shanghai") 160 | next := time.Date(year, month, day, 0, 0, 0, 0, sysTimeLocation) 161 | return next 162 | } 163 | 164 | // isLittleEndian 判断当前系统中的字节序类型是否是小端字节序 165 | func isLittleEndian() bool { 166 | var i int = 0x1 167 | bs := (*[int(unsafe.Sizeof(0))]byte)(unsafe.Pointer(&i)) 168 | return bs[0] == 0 169 | } 170 | 171 | // GetInt64 从接口类型安全获取到int64,d 是默认值 172 | func GetInt64(i interface{}, d int64) int64 { 173 | if i == nil { 174 | return d 175 | } 176 | switch i.(type) { 177 | case string: 178 | num, err := strconv.Atoi(i.(string)) 179 | if err != nil { 180 | return d 181 | } else { 182 | return int64(num) 183 | } 184 | case []byte: 185 | bits := i.([]byte) 186 | if len(bits) == 8 { 187 | if isLittleEndian() { 188 | return int64(binary.LittleEndian.Uint64(bits)) 189 | } else { 190 | return int64(binary.BigEndian.Uint64(bits)) 191 | } 192 | } else if len(bits) <= 4 { 193 | num, err := strconv.Atoi(string(bits)) 194 | if err != nil { 195 | return d 196 | } else { 197 | return int64(num) 198 | } 199 | } 200 | case uint: 201 | return int64(i.(uint)) 202 | case uint8: 203 | return int64(i.(uint8)) 204 | case uint16: 205 | return int64(i.(uint16)) 206 | case uint32: 207 | return int64(i.(uint32)) 208 | case uint64: 209 | return int64(i.(uint64)) 210 | case int: 211 | return int64(i.(int)) 212 | case int8: 213 | return int64(i.(int8)) 214 | case int16: 215 | return int64(i.(int16)) 216 | case int32: 217 | return int64(i.(int32)) 218 | case int64: 219 | return i.(int64) 220 | case float32: 221 | return int64(i.(float32)) 222 | case float64: 223 | return int64(i.(float64)) 224 | } 225 | return d 226 | } 227 | 228 | // GetString 从接口安全获取到字符串类型 229 | func GetString(str interface{}, d string) string { 230 | if str == nil { 231 | return d 232 | } 233 | switch str.(type) { 234 | case string: 235 | return str.(string) 236 | case []byte: 237 | return string(str.([]byte)) 238 | } 239 | return fmt.Sprintf("%s", str) 240 | } 241 | 242 | // GetInt64FromMap 从map中得到指定的key 243 | func GetInt64FromMap(dm map[string]interface{}, key string, d int64) int64 { 244 | data, ok := dm[key] 245 | if !ok { 246 | return d 247 | } 248 | return GetInt64(data, d) 249 | } 250 | 251 | func GetStringFromMap(dm map[string]interface{}, key string, d string) string { 252 | data, ok := dm[key] 253 | if !ok { 254 | return d 255 | } 256 | return GetString(data, d) 257 | } 258 | 259 | func GetTodayIntDay() int { 260 | y, m, d := time.Now().Date() 261 | strDay := fmt.Sprintf("%d%02d%02d", y, m, d) 262 | day, _ := strconv.Atoi(strDay) 263 | return day 264 | } 265 | 266 | // JWTClaims 自定义格式内容 267 | type JWTClaims struct { 268 | UserID uint `json:"user_id"` 269 | UserName string `json:"user_name"` 270 | StandardClaims jwt.StandardClaims 271 | } 272 | 273 | func (j JWTClaims) Valid() error { 274 | return nil 275 | } 276 | 277 | // GenerateJwtToken 生成token 278 | func GenerateJwtToken(secret string, issuer string, userId uint, userName string) (string, error) { 279 | hmacSampleSecret := []byte(secret) //密钥,不能泄露 280 | token := jwt.New(jwt.SigningMethodHS256) 281 | nowTime := time.Now().Unix() 282 | token.Claims = JWTClaims{ 283 | UserID: userId, 284 | UserName: userName, 285 | StandardClaims: jwt.StandardClaims{ 286 | NotBefore: nowTime, // 签名生效时间 287 | ExpiresAt: time.Now().Add(constant.TokenExpireDuration).Unix(), // 签名过期时间 288 | Issuer: issuer, // 签名颁发者 289 | }, 290 | } 291 | tokenString, err := token.SignedString(hmacSampleSecret) 292 | return tokenString, err 293 | } 294 | 295 | // ParseJwtToken 解析token 296 | func ParseJwtToken(tokenString string, secret string) (*JWTClaims, error) { 297 | var hmacSampleSecret = []byte(secret) 298 | //前面例子生成的token 299 | token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(t *jwt.Token) (interface{}, error) { 300 | return hmacSampleSecret, nil 301 | }) 302 | 303 | if err != nil { 304 | fmt.Println(err) 305 | return nil, err 306 | } 307 | claims := token.Claims.(*JWTClaims) 308 | return claims, nil 309 | } 310 | -------------------------------------------------------------------------------- /internal/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "testing" 4 | 5 | func TestIp4toInt(t *testing.T) { 6 | str := "192.168.32.33" 7 | t.Log(Ip4toInt(str)) 8 | } 9 | -------------------------------------------------------------------------------- /openapi.yaml: -------------------------------------------------------------------------------- 1 | # Generated with protoc-gen-openapi 2 | # https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi 3 | 4 | openapi: 3.0.3 5 | info: 6 | title: Lottery API 7 | version: 0.0.1 8 | paths: 9 | /lottery: 10 | post: 11 | tags: 12 | - Lottery 13 | operationId: Lottery_LotteryV1 14 | requestBody: 15 | content: 16 | application/json: 17 | schema: 18 | $ref: '#/components/schemas/api.lottery.v1.LotteryReq' 19 | required: true 20 | responses: 21 | "200": 22 | description: OK 23 | content: 24 | application/json: 25 | schema: 26 | $ref: '#/components/schemas/api.lottery.v1.LotteryRsp' 27 | components: 28 | schemas: 29 | api.lottery.v1.CommonRspInfo: 30 | type: object 31 | properties: 32 | code: 33 | type: integer 34 | format: int32 35 | msg: 36 | type: string 37 | api.lottery.v1.LotteryPrizeInfo: 38 | type: object 39 | properties: 40 | id: 41 | type: integer 42 | format: uint32 43 | title: 44 | type: string 45 | prizeNum: 46 | type: integer 47 | format: int32 48 | leftNum: 49 | type: integer 50 | format: int32 51 | prizeCodeLow: 52 | type: integer 53 | format: int32 54 | prizeCodeHigh: 55 | type: integer 56 | format: int32 57 | img: 58 | type: string 59 | displayOrder: 60 | type: integer 61 | format: uint32 62 | prizeType: 63 | type: integer 64 | format: uint32 65 | prizeProfile: 66 | type: string 67 | couponCode: 68 | type: string 69 | api.lottery.v1.LotteryReq: 70 | type: object 71 | properties: 72 | userId: 73 | type: integer 74 | format: uint32 75 | userName: 76 | type: string 77 | ip: 78 | type: string 79 | api.lottery.v1.LotteryRsp: 80 | type: object 81 | properties: 82 | commonRsp: 83 | $ref: '#/components/schemas/api.lottery.v1.CommonRspInfo' 84 | prizeInfo: 85 | $ref: '#/components/schemas/api.lottery.v1.LotteryPrizeInfo' 86 | tags: 87 | - name: Lottery 88 | -------------------------------------------------------------------------------- /scripts/lottery.lua: -------------------------------------------------------------------------------- 1 | -- local counter = 1 2 | -- local threads = {} 3 | -- -- 设置thread 4 | -- function setup(thread) 5 | -- thread:set("id",counter) 6 | -- thread:set("num",(counter-1)*100000000) -- num在后续的操作中可以直接使用,因为已经设置了 7 | -- table.insert(threads,thread) 8 | -- counter = counter + 1 9 | -- end 10 | 11 | 12 | function request() 13 | -- num = num + 1 14 | local body1='{"user_id": ' 15 | local body2=', "user_name": "zhangsan", "ip":"192.168.9.9"}' 16 | local user_id=tostring(math.random(1000000)) 17 | local req_body=body1..user_id..body2 18 | wrk.body = req_body 19 | wrk.headers["Content-Type"] = "application/json" 20 | wrk.method = "POST" 21 | return wrk.format('POST', nil, headers, body) 22 | end -------------------------------------------------------------------------------- /scripts/test.lua: -------------------------------------------------------------------------------- 1 | -- body1="{\"user_id\": " 2 | -- body2=", \"user_name\": \"zhangsan\", \"ip\":\"192.168.9.9\"}" 3 | -- user_id=tostring(math.random(1000000)) 4 | -- body=body1..user_id..body2 5 | -- print(body) 6 | 7 | body1='{"user_id": ' 8 | body2=', "user_name": "zhangsan", "ip":"192.168.9.9"}' 9 | user_id=tostring(math.random(1000000)) 10 | body=body1..user_id..body2 11 | print(body) 12 | -------------------------------------------------------------------------------- /scripts/wrkv1.sh: -------------------------------------------------------------------------------- 1 | # /bin/bash 2 | # wrk -t10 -c10 -d5 -s lottery.lua http://localhost:10080/lottery/v1/get_lucky 3 | wrk -t8 -c100 -d10s -T1s --script=lottery.lua --latency "http://localhost:10080/lottery/v1/get_lucky" -------------------------------------------------------------------------------- /scripts/wrkv2.sh: -------------------------------------------------------------------------------- 1 | # /bin/bash 2 | # wrk -t10 -c10 -d5 -s lottery.lua http://localhost:10080/lottery/v1/get_lucky 3 | wrk -t10 -c1000 -d30s -T1s --script=lottery.lua --latency "http://localhost:10080/lottery/v2/get_lucky" -------------------------------------------------------------------------------- /scripts/wrkv3.sh: -------------------------------------------------------------------------------- 1 | # /bin/bash 2 | # wrk -t10 -c10 -d5 -s lottery.lua http://localhost:10080/lottery/v1/get_lucky 3 | wrk -t10 -c1000 -d30s -T1s --script=lottery.lua --latency "http://localhost:10080/lottery/v3/get_lucky" -------------------------------------------------------------------------------- /sql/lottery.sql: -------------------------------------------------------------------------------- 1 | use lottery_system; 2 | 3 | DROP TABLE IF EXISTS `t_prize`; 4 | CREATE TABLE `t_prize` 5 | ( 6 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 7 | `title` varchar(255) NOT NULL DEFAULT '' COMMENT '奖品名称', 8 | `prize_num` int(11) NOT NULL DEFAULT '-1' COMMENT '奖品数量,0 无限量,>0限量,<0无奖品', 9 | `left_num` int(11) NOT NULL DEFAULT '0' COMMENT '剩余数量', 10 | `prize_code` varchar(50) NOT NULL DEFAULT '' COMMENT '0-9999表示100%,0-0表示万分之一的中奖概率', 11 | `prize_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '发奖周期,多少天,以天为单位', 12 | `img` varchar(255) NOT NULL DEFAULT '' COMMENT '奖品图片', 13 | `display_order` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '位置序号,小的排在前面', 14 | `prize_type` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '奖品类型,1-虚拟币,2-虚拟券,3-实物小奖,4-实物大奖', 15 | `prize_profile` varchar(255) NOT NULL DEFAULT '' COMMENT '奖品扩展数据,如:虚拟币数量', 16 | `begin_time` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '奖品有效周期:开始时间', 17 | `end_time` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '奖品有效周期:结束时间', 18 | `prize_plan` mediumtext COMMENT '发奖计划,[[时间1,数量1],[时间2,数量2]]', 19 | `prize_begin` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '发奖计划周期的开始', 20 | `prize_end` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '发奖计划周期的结束', 21 | `sys_status` smallint(5) unsigned NOT NULL DEFAULT '1' COMMENT '状态,1-正常,2-删除', 22 | `sys_created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '创建时间', 23 | `sys_updated` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT'修改时间', 24 | `sys_ip` varchar(50) NOT NULL DEFAULT '' COMMENT '操作人IP', 25 | PRIMARY KEY (`id`) 26 | )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='奖品表'; 27 | 28 | 29 | DROP TABLE IF EXISTS `t_coupon`; 30 | CREATE TABLE `t_coupon` ( 31 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 32 | `prize_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '奖品ID,关联lt_prize表', 33 | `code` varchar(255) NOT NULL DEFAULT '' COMMENT '虚拟券编码', 34 | `sys_created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '创建时间', 35 | `sys_updated` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '更新时间', 36 | `sys_status` smallint(5) unsigned NOT NULL DEFAULT '1' COMMENT '状态,1-正常,2-作废,3-已发放', 37 | PRIMARY KEY (`id`), 38 | UNIQUE KEY `uk_code` (`code`), 39 | KEY `idx_prize_id` (`prize_id`) 40 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='优惠券表'; 41 | 42 | 43 | DROP TABLE IF EXISTS `t_result`; 44 | CREATE TABLE `t_result` ( 45 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 46 | `prize_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '奖品ID,关联lt_prize表', 47 | `prize_name` varchar(255) NOT NULL DEFAULT '' COMMENT '奖品名称', 48 | `prize_type` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '奖品类型,同lt_prize. gtype', 49 | `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', 50 | `user_name` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名', 51 | `prize_code` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '抽奖编号(4位的随机数)', 52 | `prize_data` varchar(255) NOT NULL DEFAULT '' COMMENT '获奖信息', 53 | `sys_created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '创建时间', 54 | `sys_ip` varchar(50) NOT NULL DEFAULT '' COMMENT '用户抽奖的IP', 55 | `sys_status` smallint(5) unsigned NOT NULL DEFAULT '1' COMMENT '状态,1-正常,2-删除,3-作弊', 56 | PRIMARY KEY (`id`), 57 | KEY `idx_user_id` (`user_id`), 58 | KEY `idx_prize_id` (`prize_id`) 59 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='抽奖记录表'; 60 | 61 | 62 | DROP TABLE IF EXISTS `t_black_user`; 63 | CREATE TABLE `t_black_user` ( 64 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 65 | `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', 66 | `user_name` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名', 67 | `black_time` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '黑名单限制到期时间', 68 | `real_name` varchar(50) NOT NULL DEFAULT '' COMMENT '真是姓名', 69 | `mobile` varchar(50) NOT NULL DEFAULT '' COMMENT '手机号', 70 | `address` varchar(255) NOT NULL DEFAULT '' COMMENT '联系地址', 71 | `sys_created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '创建时间', 72 | `sys_updated` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '修改时间', 73 | `sys_ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'IP地址', 74 | PRIMARY KEY (`id`), 75 | KEY `idx_user_name` (`user_name`) 76 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户黑明单表'; 77 | 78 | 79 | DROP TABLE IF EXISTS `t_black_ip`; 80 | CREATE TABLE `t_black_ip` ( 81 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 82 | `ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'IP地址', 83 | `black_time` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '黑名单限制到期时间', 84 | `sys_created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '创建时间', 85 | `sys_updated` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '修改时间', 86 | PRIMARY KEY (`id`), 87 | UNIQUE KEY `uk_ip` (`ip`) 88 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 comment='ip黑明单表'; 89 | 90 | 91 | DROP TABLE IF EXISTS `t_lottery_times`; 92 | CREATE TABLE `t_lottery_times` ( 93 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 94 | `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', 95 | `day` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '日期,如:20220625', 96 | `num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '次数', 97 | `sys_created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '创建时间', 98 | `sys_updated` datetime NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT '修改时间', 99 | PRIMARY KEY (`id`), 100 | UNIQUE KEY `idx_user_id_day` (`user_id`,`day`) 101 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 comment='用户每日抽奖次数表'; 102 | 103 | 104 | DROP TABLE IF EXISTS `t_user`; 105 | CREATE TABLE `t_user` ( 106 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 107 | `user_name` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名', 108 | `pass_word` varchar(255) NOT NULL DEFAULT '' COMMENT '用户密码', 109 | `signature` varchar(255) NOT NULL DEFAULT '' COMMENT '登录用户签名', 110 | PRIMARY KEY (`id`) 111 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 comment='用户表'; -------------------------------------------------------------------------------- /third_party/README.md: -------------------------------------------------------------------------------- 1 | # third_party 2 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /third_party/openapi/v3/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 "openapi/v3/openapi.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/validate/README.md: -------------------------------------------------------------------------------- 1 | # protoc-gen-validate (PGV) 2 | 3 | * https://github.com/envoyproxy/protoc-gen-validate 4 | --------------------------------------------------------------------------------