当前位置: 首页 > news >正文

gRPC之gRPC转换HTTP

1、gRPC转换HTTP

我们通常把RPC用作内部通信,而使用Restful Api进行外部通信。为了避免写两套应用,我们使用grpc-

gateway gRPC转成HTTP。服务接收到HTTP请求后,grpc-gateway把它转成gRPC进行处理,然后以JSON

形式返回数据。本篇代码最终转成的Restful Api支持bearer token验证、数据验证,并添加swagger文档。

1.1 编写proto

// simple.proto
// 协议为proto3
syntax = "proto3";
package proto;
option go_package = "./simple;proto";import "validator.proto";
import "google/api/annotations.proto";message InnerMessage {// some_integer can only be in range (1, 100).int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];// some_float can only be in range (0;1).double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
}message OuterMessage {// important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];// proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
}service Simple{rpc Route (InnerMessage) returns (OuterMessage){option (google.api.http) ={post:"/v1/example/route"body:"*"};}
}

可以看到,proto变化不大,只是添加了API的路由路径:

option (google.api.http) ={post:"/v1/example/route"body:"*"
};

annotations.proto文件的内容:

// ./proto/google/api/annotations.proto
syntax = "proto3";package google.api;option go_package = "google/api;google_api";import "google/api/http.proto";
import "google/protobuf/descriptor.proto";option java_multiple_files = true;
option java_outer_classname = "AnnotationsProto";
option java_package = "com.google.api";extend google.protobuf.MethodOptions {HttpRule http = 72295728;}

http.proto文件的内容:

// ./proto/google/api/http.proto
syntax = "proto3";package google.api;option go_package = "google/api;google_api";option cc_enable_arenas = true;
option java_multiple_files = true;
option java_outer_classname = "HttpProto";
option java_package = "com.google.api";message Http {repeated HttpRule rules = 1;
}message HttpRule {string selector = 1;oneof pattern {string get = 2;string put = 3;string post = 4;string delete = 5;string patch = 6;CustomHttpPattern custom = 8;}string body = 7;repeated HttpRule additional_bindings = 11;
}message CustomHttpPattern {string kind = 1;string path = 2;
}

1.2 编译proto

$ cd proto# 编译google.api
$ protoc -I . --go_out=plugins=grpc:. google/api/*.proto# 编译simple.proto
$ protoc -I . --go_out=plugins=grpc:. simple/*.proto# 编译simple.proto gateway
$ protoc --grpc-gateway_out=logtostderr=true:. simple/simple.proto# 编译simple.proto validator
$ protoc --govalidators_out=. --go_out=plugins=grpc:. simple/simple.proto

以上完成proto编译,接着修改服务端代码。

1.3 项目结构

# 项目结构
$ tree demo/
demo/
├── cert
│   ├── ca.crt
│   ├── ca.csr
│   ├── ca.key
│   ├── ca.srl
│   ├── client
│   │   ├── client.csr
│   │   ├── client.key
│   │   └── client.pem
│   ├── openssl.cnf
│   └── server
│       ├── server.csr
│       ├── server.key
│       └── server.pem
├── client
│   ├── auth
│   │   └── auth.go
│   └── client.go
├── go.mod
├── go.sum
├── proto
│   ├── google
│   │   └── api
│   │       ├── annotations.pb.go
│   │       ├── annotations.proto
│   │       ├── http.pb.go
│   │       └── http.proto
│   ├── simple
│   │   ├── simple.pb.go
│   │   ├── simple.pb.gw.go
│   │   ├── simple.proto
│   │   └── simple.validator.pb.go
│   └── validator.proto
└── server├── gateway│   └── gateway.go├── log│   └── debug.log├── middleware│   ├── auth│   │   └── auth.go│   ├── cred│   │   └── cred.go│   ├── recovery│   │   └── recovery.go│   └── zap│       └── zap.go└── server.go17 directories, 31 files
1.3.1 gateway.go
package gatewayimport ("context""crypto/tls"pb "demo/proto/simple""github.com/grpc-ecosystem/grpc-gateway/runtime""golang.org/x/net/http2""golang.org/x/net/http2/h2c""google.golang.org/grpc""google.golang.org/grpc/credentials""io/ioutil""log""net/http""strings"
)// ProvideHTTP 把gRPC服务转成HTTP服务,让gRPC同时支持HTTP
func ProvideHTTP(endpoint string, grpcServer *grpc.Server) *http.Server {ctx := context.Background()//获取证书creds, err := credentials.NewClientTLSFromFile("../cert/server/server.pem", "test.example.com")if err != nil {log.Fatalf("Failed to create TLS credentials %v", err)}//添加证书dopts := []grpc.DialOption{grpc.WithTransportCredentials(creds)}//新建gwmux,它是grpc-gateway的请求复用器。它将http请求与模式匹配,并调用相应的处理程序。gwmux := runtime.NewServeMux()//将服务的http处理程序注册到gwmux。处理程序通过endpoint转发请求到grpc端点err = pb.RegisterSimpleHandlerFromEndpoint(ctx, gwmux, endpoint, dopts)if err != nil {log.Fatalf("Register Endpoint err: %v", err)}//新建mux,它是http的请求复用器mux := http.NewServeMux()//注册gwmuxmux.Handle("/", gwmux)log.Println(endpoint + " HTTP.Listing with TLS and token...")return &http.Server{Addr:      endpoint,Handler:   grpcHandlerFunc(grpcServer, mux),TLSConfig: getTLSConfig(),}
}// grpcHandlerFunc 根据不同的请求重定向到指定的Handler处理
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {grpcServer.ServeHTTP(w, r)} else {otherHandler.ServeHTTP(w, r)}}), &http2.Server{})
}// getTLSConfig获取TLS配置
func getTLSConfig() *tls.Config {cert, _ := ioutil.ReadFile("../cert/server/server.pem")key, _ := ioutil.ReadFile("../cert/server/server.key")var demoKeyPair *tls.Certificatepair, err := tls.X509KeyPair(cert, key)if err != nil {log.Fatalf("TLS KeyPair err: %v\n", err)}demoKeyPair = &pairreturn &tls.Config{Certificates: []tls.Certificate{*demoKeyPair},NextProtos:   []string{http2.NextProtoTLS}, // HTTP2 TLS支持}
}
1.3.2 server.go
package mainimport ("context""crypto/tls"pb "demo/proto/simple""demo/server/gateway""demo/server/middleware/auth""demo/server/middleware/cred""demo/server/middleware/recovery""demo/server/middleware/zap"grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator""google.golang.org/grpc""log""net"
)// SimpleService 定义我们的服务
type SimpleService struct{}// Route 实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.InnerMessage) (*pb.OuterMessage, error) {res := pb.OuterMessage{ImportantString: "hello grpc validator",Inner:           req,}return &res, nil
}const (// Address 监听地址Address string = "127.0.0.1:8000"// Network 网络通信协议Network string = "tcp"
)func main() {// 监听本地端口listener, err := net.Listen(Network, Address)if err != nil {log.Fatalf("net.Listen err: %v", err)}// 新建gRPC服务器实例grpcServer := grpc.NewServer(cred.TLSInterceptor(),grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(grpc_validator.StreamServerInterceptor(),grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),grpc_recovery.StreamServerInterceptor(recovery.RecoveryInterceptor()),)),grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(grpc_validator.UnaryServerInterceptor(),grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),grpc_recovery.UnaryServerInterceptor(recovery.RecoveryInterceptor()),)),)// 在gRPC服务器注册我们的服务pb.RegisterSimpleServer(grpcServer, &SimpleService{})log.Println(Address + " net.Listing with TLS and token...")//使用gateway把grpcServer转成httpServerhttpServer := gateway.ProvideHTTP(Address, grpcServer)//用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用if err = httpServer.Serve(tls.NewListener(listener, httpServer.TLSConfig)); err != nil {log.Fatal("ListenAndServe: ", err)}
}
1.3.3 auth.go
package authimport ("context""errors"grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth""google.golang.org/grpc""google.golang.org/grpc/codes"
)// TokenInfo 用户信息
type TokenInfo struct {ID    stringRoles []string
}// AuthInterceptor 认证拦截器,对以authorization为头部,形式为`bearer token`的Token进行验证
func AuthInterceptor(ctx context.Context) (context.Context, error) {token, err := grpc_auth.AuthFromMD(ctx, "bearer")if err != nil {return nil, err}tokenInfo, err := parseToken(token)if err != nil {return nil, grpc.Errorf(codes.Unauthenticated, " %v", err)}//使用context.WithValue添加了值后,可以用Value(key)方法获取值newCtx := context.WithValue(ctx, tokenInfo.ID, tokenInfo)//log.Println(newCtx.Value(tokenInfo.ID))return newCtx, nil
}//解析token,并进行验证
func parseToken(token string) (TokenInfo, error) {var tokenInfo TokenInfoif token == "grpc.auth.token" {tokenInfo.ID = "1"tokenInfo.Roles = []string{"admin"}return tokenInfo, nil}return tokenInfo, errors.New("Token无效: bearer " + token)
}//从token中获取用户唯一标识
func userClaimFromToken(tokenInfo TokenInfo) string {return tokenInfo.ID
}
1.3.4 cred.go
package credimport ("google.golang.org/grpc""google.golang.org/grpc/credentials""log"
)// TLSInterceptor TLS证书认证
func TLSInterceptor() grpc.ServerOption {// 从输入证书文件和密钥文件为服务端构造TLS凭证creds, err := credentials.NewServerTLSFromFile("../cert/server/server.pem", "../cert/server/server.key")if err != nil {log.Fatalf("Failed to generate credentials %v", err)}return grpc.Creds(creds)
}
1.3.5 recovery.go
package recoveryimport (grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery""google.golang.org/grpc""google.golang.org/grpc/codes"
)// RecoveryInterceptor panic时返回Unknown错误吗
func RecoveryInterceptor() grpc_recovery.Option {return grpc_recovery.WithRecoveryHandler(func(p interface{}) (err error) {return grpc.Errorf(codes.Unknown, "panic triggered: %v", p)})
}
1.3.6 zap.go
package zapimport (grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap""go.uber.org/zap""go.uber.org/zap/zapcore""gopkg.in/natefinch/lumberjack.v2"
)// ZapInterceptor返回zap.logger实例(把日志写到文件中)
func ZapInterceptor() *zap.Logger {w := zapcore.AddSync(&lumberjack.Logger{Filename:  "log/debug.log",MaxSize:   1024, //MBLocalTime: true,})config := zap.NewProductionEncoderConfig()config.EncodeTime = zapcore.ISO8601TimeEncodercore := zapcore.NewCore(zapcore.NewJSONEncoder(config),w,zap.NewAtomicLevel(),)logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))grpc_zap.ReplaceGrpcLogger(logger)return logger
}// ZapInterceptor 返回zap.logger实例(把日志输出到控制台)
// func ZapInterceptor() *zap.Logger {
// 	logger, err := zap.NewDevelopment()
// 	if err != nil {
// 		log.Fatalf("failed to initialize zap logger: %v", err)
// 	}
// 	grpc_zap.ReplaceGrpcLogger(logger)
// 	return logger
// }
1.3.7 client.go
package mainimport ("context""demo/client/auth"pb "demo/proto/simple""google.golang.org/grpc""google.golang.org/grpc/credentials""log"
)// Address 连接地址
const Address string = ":8000"var grpcClient pb.SimpleClientfunc main() {//从输入的证书文件中为客户端构造TLS凭证creds, err := credentials.NewClientTLSFromFile("../cert/server/server.pem", "test.example.com")if err != nil {log.Fatalf("Failed to create TLS credentials %v", err)}//构建Tokentoken := auth.Token{Value: "bearer grpc.auth.token",}// 连接服务器conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))if err != nil {log.Fatalf("net.Connect err: %v", err)}defer conn.Close()// 建立gRPC连接grpcClient = pb.NewSimpleClient(conn)route()
}// route 调用服务端Route方法
func route() {// 创建发送结构体req := pb.InnerMessage{SomeInteger: 99,SomeFloat:   1,}// 调用我们的服务(Route方法)// 同时传入了一个 context.Context ,在有需要时可以让我们改变RPC的行为,比如超时/取消一个正在运行的RPCres, err := grpcClient.Route(context.Background(), &req)if err != nil {log.Fatalf("Call Route err: %v", err)}// 打印返回值log.Println(res)
}
1.3.8 auth.go
package authimport ("context"
)// Token token认证
type Token struct {Value string
}const headerAuthorize string = "authorization"// GetRequestMetadata 获取当前请求认证所需的元数据
func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {return map[string]string{headerAuthorize: t.Value}, nil
}// RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
func (t *Token) RequireTransportSecurity() bool {return true
}

1.4 测试

[root@zsx server]# go run server.go
2023/02/12 13:31:42 127.0.0.1:8000 net.Listing with TLS and token...
2023/02/12 13:31:42 127.0.0.1:8000 HTTP.Listing with TLS and token...
[root@zsx client]# go run client.go
2023/02/12 13:31:50 important_string:"hello grpc validator" inner:{some_integer:99 some_float:1}

1.5 发送http请求

[root@zsx client]# curl -X POST -k https://127.0.0.1:8000/v1/example/route --header 'Authorization: Bearer grpc.auth.token' -d '{"some_integer": 10,"some_float": 1}'
{"important_string":"hello grpc validator","inner":{"some_integer":10,"some_float":1}}

会进行输入的校验:

[root@zsx client]# curl -X POST -k https://127.0.0.1:8000/v1/example/route --header 'Authorization: Bearer grpc.auth.token' -d '{"some_integer": 200,"some_float": 1}'
{"error":"invalid field SomeInteger: value '200' must be less than '100'","code":3,"message":"invalid field SomeInteger: value '200' must be less than '100'"}

会进行auth的验证:

[root@zsx client]# curl -X POST -k https://127.0.0.1:8000/v1/example/route -d '{"some_integer": 10,"some_float": 1}'
{"error":"Request unauthenticated with bearer","code":16,"message":"Request unauthenticated with bearer"}

在上面可以看到,我们的gRPC服务已经同时支持RPCHTTP请求了,而且API接口支持bearer token验证和数

据验证。为了方便对接,我们把API接口生成swagger文档。

1.6 生成swagger文档

1.6.1 生成swagger文档(simple.swagger.json)

1、安装protoc-gen-swagger

$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
$ go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

2、编译生成simple.swagger.json

$ cd proto
$ protoc --swagger_out=logtostderr=true:. simple/simple.proto
1.6.2 把swagger-ui转成Go代码,备用

1、下载swagger-ui

https://github.com/swagger-api/swagger-ui下载包,解压把dist目录下的所有文件拷贝我们项目的

server/swagger/swagger-ui/目录下。

$ tree ./server/swagger/swagger-ui/
./server/swagger/swagger-ui/
├── favicon-16x16.png
├── favicon-32x32.png
├── index.css
├── index.html
├── oauth2-redirect.html
├── swagger-initializer.js
├── swagger-ui-bundle.js
├── swagger-ui-bundle.js.map
├── swagger-ui.css
├── swagger-ui.css.map
├── swagger-ui-es-bundle-core.js
├── swagger-ui-es-bundle-core.js.map
├── swagger-ui-es-bundle.js
├── swagger-ui-es-bundle.js.map
├── swagger-ui.js
├── swagger-ui.js.map
├── swagger-ui-standalone-preset.js
└── swagger-ui-standalone-preset.js.map0 directories, 18 files

2、把Swagger UI转换为Go代码

安装go-bindata

$ go get -u github.com/jteeuwen/go-bindata/...
$ go install github.com/jteeuwen/go-bindata/...

回到server/所在目录,运行指令把Swagger UI转成Go代码:

$ go-bindata --nocompress -pkg swagger -o swagger/datafile.go swagger/swagger-ui/...

swagger目录下回生成datafile.go文件。

1.6.3 对外提供swagger-ui

1、在swagger/目录下新建swagger.go文件

package swaggerimport (assetfs "github.com/elazarl/go-bindata-assetfs""log""net/http""path""strings"
)// ServeSwaggerFile把proto文件夹中的swagger.json文件暴露出去
func ServeSwaggerFile(w http.ResponseWriter, r *http.Request) {if !strings.HasSuffix(r.URL.Path, "swagger.json") {log.Printf("Not Found: %s", r.URL.Path)http.NotFound(w, r)return}p := strings.TrimPrefix(r.URL.Path, "/swagger/")// "../proto/"为.swagger.json所在目录p = path.Join("../proto/simple/", p)log.Printf("Serving swagger-file: %s", p)http.ServeFile(w, r, p)
}// ServeSwaggerUI 对外提供swagger-ui
func ServeSwaggerUI(mux *http.ServeMux) {fileServer := http.FileServer(&assetfs.AssetFS{Asset:    Asset,AssetDir: AssetDir,Prefix:   "swagger/swagger-ui", //swagger-ui文件夹所在目录})prefix := "/swagger-ui/"mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
}

2、注册swagger

gateway.go中添加如下代码:

//新建mux,它是http的请求复用器
mux := http.NewServeMux()
//注册gwmux
mux.Handle("/", gwmux)
//注册swagger
mux.HandleFunc("/swagger/", swagger.ServeSwaggerFile)
swagger.ServeSwaggerUI(mux)

3、到这里我们已经完成了swagger文档的添加工作了,重新运行server.go

[root@zsx server]# go run server.go
2023/02/12 16:59:27 127.0.0.1:8000 net.Listing with TLS and token...
2023/02/12 16:59:27 127.0.0.1:8000 HTTP.Listing with TLS and token...

由于谷歌浏览器不能使用自己制作的TLS证书,所以我们用火狐浏览器进行测试。

用火狐浏览器打开:https://127.0.0.1:8000/swagger-ui/

在这里插入图片描述

在最上面地址栏输入:https://127.0.0.1:8000/swagger/simple.swagger.json

在这里插入图片描述

然后就可以看到swagger生成的API文档了:

在这里插入图片描述

还有个问题,我们使用了bearer token进行接口验证的,怎么把bearer token也添加到swagger中呢?

grpc-gatewayGitHub上的这个Issues找到解决办法

https://github.com/grpc-ecosystem/grpc-gateway/issues/1089

1.6.4 在swagger中配置bearer token

1、修改simple.proto文件

// 协议为proto3
syntax = "proto3";
package proto;
option go_package = "./simple;proto";import "validator.proto";
import "google/api/annotations.proto";
import "protoc-gen-swagger/options/annotations.proto";message InnerMessage {// some_integer can only be in range (1, 100).int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];// some_float can only be in range (0;1).double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
}message OuterMessage {// important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];// proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
}option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {security_definitions: {security: {key: "bearer"value: {type: TYPE_API_KEYin: IN_HEADERname: "Authorization"description: "Authentication token, prefixed by Bearer: Bearer <token>"}}}security: {security_requirement: {key: "bearer"}}info: {title: "grpc gateway sample";version: "1.0";license: {name: "MIT";};}schemes: HTTPS
};service Simple{rpc Route (InnerMessage) returns (OuterMessage){option (google.api.http) ={post:"/v1/example/route"body:"*"};// 禁用bearer token// option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {//   security: { } // Disable security key// };}
}

2、重新编译生成simple.swagger.json

$ cd proto
$ protoc --swagger_out=logtostderr=true:. simple/simple.proto
1.6.5 验证测试

1、添加bearer token

在这里插入图片描述

填写token:

在这里插入图片描述

2、调用接口,正确返回数据

填写数据:

在这里插入图片描述

进行测试:

在这里插入图片描述

3、传递不合规则的数据,返回违反数据验证逻辑错误

填写数据:

在这里插入图片描述

进行测试:

在这里插入图片描述

# 项目结构
$ tree demo/
demo/
├── cert
│   ├── ca.crt
│   ├── ca.csr
│   ├── ca.key
│   ├── ca.srl
│   ├── client
│   │   ├── client.csr
│   │   ├── client.key
│   │   └── client.pem
│   ├── openssl.cnf
│   └── server
│       ├── server.csr
│       ├── server.key
│       └── server.pem
├── client
│   ├── auth
│   │   └── auth.go
│   └── client.go
├── go.mod
├── go.sum
├── proto
│   ├── google
│   │   └── api
│   │       ├── annotations.pb.go
│   │       ├── annotations.proto
│   │       ├── http.pb.go
│   │       └── http.proto
│   ├── protoc-gen-swagger
│   │   └── options
│   │       ├── annotations.proto
│   │       └── openapiv2.proto
│   ├── simple
│   │   ├── simple.pb.go
│   │   ├── simple.pb.gw.go
│   │   ├── simple.proto
│   │   ├── simple.swagger.json
│   │   └── simple.validator.pb.go
│   └── validator.proto
└── server├── gateway│   └── gateway.go├── log│   └── debug.log├── middleware│   ├── auth│   │   └── auth.go│   ├── cred│   │   └── cred.go│   ├── recovery│   │   └── recovery.go│   └── zap│       └── zap.go├── server.go└── swagger├── datafile.go├── swagger.go└── swagger-ui├── favicon-16x16.png├── favicon-32x32.png├── index.css├── index.html├── oauth2-redirect.html├── swagger-initializer.js├── swagger-ui-bundle.js├── swagger-ui-bundle.js.map├── swagger-ui.css├── swagger-ui.css.map├── swagger-ui-es-bundle-core.js├── swagger-ui-es-bundle-core.js.map├── swagger-ui-es-bundle.js├── swagger-ui-es-bundle.js.map├── swagger-ui.js├── swagger-ui.js.map├── swagger-ui-standalone-preset.js└── swagger-ui-standalone-preset.js.map21 directories, 54 files

1.7 总结

本篇介绍了如何使用grpc-gatewaygRPC同时支持HTTP,最终转成的Restful Api支持bearer token

证、数据验证。同时生成swagger文档,方便API接口对接。

相关文章:

gRPC之gRPC转换HTTP

1、gRPC转换HTTP 我们通常把RPC用作内部通信&#xff0c;而使用Restful Api进行外部通信。为了避免写两套应用&#xff0c;我们使用grpc- gateway 把gRPC转成HTTP。服务接收到HTTP请求后&#xff0c;grpc-gateway把它转成gRPC进行处理&#xff0c;然后以JSON 形式返回数据。…...

【十四】记一次MySQL宕机恢复过程,MySQL INNODB 损坏恢复

记一次MySQL宕机恢复过程 简介&#xff1a;一个业务数据库疏于运维管理&#xff0c;突然在今天崩溃宕机了&#xff0c;真是让人抓狂&#xff0c;上面也不知道积累了多久的数据&#xff0c;平时也没有定期做好备份&#xff0c;这下岂不是瞎了啊&#xff0c;经过不断的收集信息和…...

从0开始在Vscode中搭建Vue2/3项目详细步骤

1.安装node.js:Node.js下载安装及环境配置教程【超详细】_nodejs下载_WHF__的博客-CSDN博客 node.js自带npm&#xff0c;无需单独安装。 验证&#xff1a; node -v npm -v 2.先简单创建一个空文件夹&#xff0c;vscode进入该文件夹&#xff0c;并打开终端。 3.安装cnpm&…...

JavaScript ES6类的定义与继承

文章目录 一、class方式定义类1.认识class定义类2.类和构造函数的异同3.类的构造函数4.类的实例方法5.类的访问器方法6.类的静态方法 二、继承1.extends实现继承2.super关键字3.继承内置类4.类的混入mixin 三、ES6转ES51.class转换2.extends转换 四、多态 一、class方式定义类 …...

中科芯与IAR共建生态合作,IAR集成开发环境全面支持CKS32系列MCU

中国上海–2023年10月18日–嵌入式开发软件和服务的全球领导者IAR今日宣布&#xff0c;与中科芯集成电路有限公司&#xff08;以下简称中科芯&#xff09;达成生态合作&#xff0c;IAR已全面支持CKS32系列MCU的应用开发。这一合作将进一步推动嵌入式系统的发展&#xff0c;并为…...

设计模式:外观模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

大家好&#xff01;本节主要介绍设计模式中的外观模式。 简介&#xff1a; 外观模式&#xff0c;它是一种设计模式&#xff0c;它为子系统中的一组接口提供一个统一的、简单的接口。这种模式主张按照描述和判断资料来评价课程&#xff0c;关键活动是在课程实施的全过程中进行…...

Leetcode—34.在排序数组中查找元素的第一个和最后一个位置【中等】

2023每日刷题&#xff08;六&#xff09; Leetcode—34.在排序数组中查找元素的第一个和最后一个位置 实现代码 /*** Note: The returned array must be malloced, assume caller calls free().*/ int lower_bound(int *arr, int numsSize, int target) {// 左闭右开区间[lef…...

Java 8 新特性 Ⅱ

方法引用 举例: Integer :: compare 理解: 可以看作是基于lambda表达式的进一步简化 当需要提供一个函数式接口的实例时, 可以使用lambda表达式提供实例 当满足一定条件下, 可以使用方法引用or构造器引用替换lambda表达式 实质: 方法引用作为函数式接口的实例 (注: 需要熟悉…...

C语言学习书籍推荐

C语言学习书籍推荐如下&#xff1a; 《C程序设计语言》&#xff08;The C Programming language&#xff09;&#xff1a;这本书由C语言创始人Brian W. Kernighan和Dennis M. Ritchie所写&#xff0c;是介绍标准C语言及其程序设计方法的权威性经典著作。《C陷阱与缺陷》&#…...

IntelliJ IDEA Maven加载超时问题

IDEA创建Maven项目遇到如下错误&#xff1a; Could not transfer artifact org.apache.maven.plugins:maven-compiler-plugin:pom:3.10.1 from/to central (Central Repository:): Connect to repo.maven.apache.org:443 [repo.maven.apache.org/146.75.112.215] failed: conn…...

Spring中事务失效的几种场景及解决办法

未抛出异常&#xff1a;如果在一个带有事务的方法中没有抛出异常&#xff0c;Spring无法检测到事务失败&#xff0c;从而无法回滚。解决方法是确保在事务中遇到错误时抛出异常。 异常被捕获&#xff1a;如果在一个带有事务的方法中抛出异常&#xff0c;但被捕获并处理了&#…...

第五届太原理工大学程序设计竞赛新生赛(初赛)题解

第五届太原理工大学程序设计竞赛新生赛&#xff08;初赛&#xff09;题解 时隔半年重做一次&#xff0c;还是有几道不会&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c; ⭐️A.饿饿饭饭 题目&#xff1a; &#x1f31f;题解&#xff1a; 很简单&#xff0c;签…...

微信小程序开发之后台数据交互及wxs应用

目录 一、后端准备 1. 应用配置 2. 数据源配置 二、数据库 1. 创建 2. 数据表 3. 数据测试 三、前端 1. 请求方法整合 2. 数据请求 3. WXS的使用 4. 样式美化 5. 页面 一、后端准备 通过SpringMVC及mybatis的技术学习&#xff0c;还有前后端分离的技术应用&…...

Java进阶篇--并发容器之ThreadLocal内存泄漏

目录 ThreadLocal内存泄漏的原因&#xff1f; 改进和优化 cleanSomeSlots方法 expungeStaleEntry方法 replaceStaleEntry方法 为什么使用弱引用&#xff1f; Thread.exit() ThreadLocal内存泄漏最佳解决方案 在使用完毕后立即清理ThreadLocal 使用InheritableThreadL…...

js实现红包雨功能(canvas,react,ts),包括图片不规则旋转、大小、转速、掉落速度控制、屏幕最大红包数量控制等功能

介绍 本文功能由canvas实现红包雨功能&#xff08;index.tsx&#xff09;本文为react的ts版。如有其他版本需求可评论区观赏地址&#xff0c;需过墙 import React, { Component } from react; // import ./index.css; import moneyx from /assets/images/RedEnvelopeRain/bal…...

【数字IC设计/FPGA】FIFO与流控机制

流控&#xff0c;简单来说就是控制数据流停止发送。常见的流控机制分为带内流控和带外流控。 FIFO的流水反压机制 一般来说&#xff0c;每一个fifo都有一个将满阈值afull_value&#xff08;almost full&#xff09;。当fifo内的数据量达到或超过afull_value时&#xff0c;将满…...

C++笔记之遍历vector的所有方式

C笔记之遍历vector的所有方式 —— 2023年4月15日 上海 code review 文章目录 C笔记之遍历vector的所有方式1.普通for循环2.迭代器版3.const迭代器4.C11引入的范围for循环5.使用auto关键字和迭代器6.使用std::for_each算法7.使用std::for_each和lambda表达式8.普通版vector::at…...

OpenCV 笔记(2):图像的属性以及像素相关的操作

Part11. 图像的属性 11.1 Mat 的主要属性 在前文中&#xff0c;我们大致了解了 Mat 的基本结构以及它的创建与赋值。接下来我们通过一个例子&#xff0c;来看看 Mat 所包含的常用属性。 先创建一个 3*4 的四通道的矩阵&#xff0c;并打印出其相关的属性&#xff0c;稍后会详细…...

基于指数分布优化的BP神经网络(分类应用) - 附代码

基于指数分布优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于指数分布优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.指数分布优化BP神经网络3.1 BP神经网络参数设置3.2 指数分布算法应用 4.测试结果…...

Python--练习:使用while循环求1~100之间,所有偶数的和(涉及if判断是不是偶数)

案例&#xff1a;求1~100之间&#xff0c;所有偶数的和 思考&#xff1a; 先套用原有基础模式&#xff0c;之后再思考其他的。 其实就是在之前文章 Python--练习&#xff1a;使用while循环求1..100的和-CSDN博客 的基础上&#xff0c;再判断如果获取到里面的全部偶数&#…...

互斥锁与消息队列的架构哲学

更多精彩内容请访问&#xff1a;通义灵码2.5——基于编程智能体开发Wiki多功能搜索引擎更多精彩内容请访问&#xff1a;更多精彩内容请访问&#xff1a;通义灵码2.5——基于编程智能体开发Wiki多功能搜索引擎 一、资源争用的现实镜像 当多个ATM机共用一个现金库时&#xff0c;…...

Spring Boot微服务架构(十一):独立部署是否抛弃了架构优势?

Spring Boot 的独立部署&#xff08;即打包为可执行 JAR/WAR 文件&#xff09;本身并不会直接丧失架构优势&#xff0c;但其是否体现架构价值取决于具体应用场景和设计选择。以下是关键分析&#xff1a; 一、独立部署与架构优势的关系 内嵌容器的优势保留 Spring Boot 独立部署…...

【Linux】文件赋权(指定文件所有者、所属组)、挂载光驱(图文教程)

文章目录 文件赋权创建文件 testChmod查看文件的当前权限使用 chmod 命令修改权限验证权限关键命令总结答案汇总 光驱挂载确认文件是否存在打包压缩压缩验证创建 work 目录将压缩文件复制到 work 目录新建挂载点 /MNT/CDROM 并挂载光驱答案汇总 更多相关内容可查看 此篇用以解决…...

CAD多面体密堆积3D插件

插件介绍 CAD多面体密堆积3D插件可在AutoCAD内建立三维随机多面体密堆积模型。 插件内置物理动力学模拟算法&#xff0c;通过模拟重力、碰撞等现象&#xff0c;使多面体在虚拟环境中发生自然堆积&#xff0c;进而实现真实的堆积效果。多面体堆积模拟中存在的局部穿模问题可通…...

【计算机网络】数据链路层-滑动窗口协议

数据链路层滑动窗口协议 1. 三种协议对比表 特性停止-等待协议GBN协议SR协议窗口大小发送 1&#xff0c;接收 1发送 W (1<W≤2ⁿ-1)&#xff0c;接收 1发送 C&#xff0c;接收 R确认方式单个确认累积确认选择性确认重传策略超时重传回退N帧重传选择性重传接收缓冲区…...

解析“与此站点的连接不安全”警告:成因与应对策略

一、技术本质&#xff1a;SSL/TLS协议的信任链断裂 现代浏览器通过SSL/TLS协议建立加密通信&#xff0c;其核心在于证书颁发机构&#xff08;CA&#xff09;构建的信任链。当用户访问网站时&#xff0c;浏览器会验证服务器证书的有效性&#xff0c;包括&#xff1a; 证书链完…...

F(x,y)= 0 隐函数 微分法

&#x1f7e6; 一、隐函数微分法简介 ▶ 什么是隐函数&#xff1f; 显函数&#xff1a;形如 y f ( x ) y f(x) yf(x)&#xff0c;变量之间是显式关系。 隐函数&#xff1a;形如 F ( x , y ) 0 F(x, y) 0 F(x,y)0&#xff0c;变量间不是直接表达的&#xff0c;需要通过…...

WEB3技术重要吗,还是可有可无?

我从几个角度给你一个全面、理性、技术导向的回答&#xff1a; ✅ 一、Web3 技术的重要性&#xff1a;“有意义&#xff0c;但不是万能” Web3 技术并不是可有可无的噱头&#xff0c;而是一种在特定场景下提供独特价值的技术体系。 它重要的原因包括&#xff1a; 1. 重构数字…...

《深入理解 Nacos 集群与 Raft 协议》系列四:日志复制机制:Raft 如何确保提交可靠且幂等

《深入理解 Nacos 集群与 Raft 协议》系列 大家好&#xff0c;我是G探险者&#xff01; 在前几篇中我们介绍了选主与日志对比机制&#xff0c;它们保证了“谁能成为 Leader”以及“Leader 的日志是否可靠”。 而当 Leader 已选定&#xff0c;系统需要把客户端的写请求写入所…...

华为OD机考-内存冷热标记-多条件排序

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextInt();int[] arr new int[a];for(int…...