├── .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 | 
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------