parent
6996112349
commit
6b48932683
@ -0,0 +1,116 @@ |
||||
# interceptor-通用grpcInterceptor |
||||
|
||||
## 说明 |
||||
|
||||
> grpc请求和响应拦截器 |
||||
> - server |
||||
> - server原样返回业务错误,正式服其它错误统一返回InternalServerError |
||||
> - client |
||||
> - client默认转换server的所有error为kratos的error,方便bff原样返回 |
||||
|
||||
## 使用示例 |
||||
|
||||
### client |
||||
|
||||
```go |
||||
package data |
||||
|
||||
import ( |
||||
"context" |
||||
grpc2 "gitea.drugeyes.vip/pharnexbase/utils/interceptor/v1/grpc" |
||||
"github.com/go-kratos/kratos/v2/middleware/circuitbreaker" |
||||
"github.com/go-kratos/kratos/v2/middleware/tracing" |
||||
"github.com/go-kratos/kratos/v2/transport/grpc" |
||||
"github.com/google/wire" |
||||
user "user-center-front/api/user/v2" |
||||
"user-center-front/internal/conf" |
||||
) |
||||
|
||||
// ProviderSet is data providers. |
||||
var ProviderSet = wire.NewSet( |
||||
NewData, |
||||
NewUserService, |
||||
) |
||||
|
||||
// Data . |
||||
type Data struct { |
||||
User user.UserCenterClient |
||||
} |
||||
|
||||
// NewData . |
||||
func NewData(userServer user.UserCenterClient) *Data { |
||||
return &Data{ |
||||
User: userServer, |
||||
} |
||||
} |
||||
|
||||
func NewUserService(c *conf.Bootstrap) user.UserCenterClient { |
||||
grpcInterceptor := grpc2.NewInterceptor(c.Env, nil) |
||||
conn, err := grpc.DialInsecure( |
||||
context.Background(), |
||||
grpc.WithEndpoint(c.Service.GetUser()), |
||||
grpc.WithTimeout(0), |
||||
grpc.WithMiddleware( |
||||
tracing.Client(), // 链路追踪 |
||||
circuitbreaker.Client(), // 熔断器 |
||||
), |
||||
// 拦截器 |
||||
grpc.WithUnaryInterceptor(grpcInterceptor.UnaryClientInterceptor()), |
||||
) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
return user.NewUserCenterClient(conn) |
||||
} |
||||
|
||||
``` |
||||
|
||||
### server |
||||
|
||||
```go |
||||
package server |
||||
|
||||
import ( |
||||
"github.com/go-kratos/kratos/v2/log" |
||||
"github.com/go-kratos/kratos/v2/middleware/logging" |
||||
"github.com/go-kratos/kratos/v2/middleware/ratelimit" |
||||
"github.com/go-kratos/kratos/v2/middleware/recovery" |
||||
"github.com/go-kratos/kratos/v2/middleware/validate" |
||||
"github.com/go-kratos/kratos/v2/transport/grpc" |
||||
grpc2 "gitea.drugeyes.vip/pharnexbase/utils/interceptor/v1/grpc" |
||||
v2 "user-center-v2/api/user/v2" |
||||
"user-center-v2/internal/conf" |
||||
pkgLog "user-center-v2/internal/pkg/log" |
||||
"user-center-v2/internal/service" |
||||
) |
||||
|
||||
// NewGRPCServer new a gRPC server. |
||||
func NewGRPCServer(c *conf.Bootstrap, userService *service.UserCenterService, logger log.Logger) *grpc.Server { |
||||
grpcInterceptor := grpc2.NewInterceptor(c.Env, v2.ErrorReason_value) |
||||
var opts = []grpc.ServerOption{ |
||||
grpc.Middleware( |
||||
recovery.Recovery(), |
||||
pkgLog.Trace(), |
||||
ratelimit.Server(), |
||||
logging.Server(logger), |
||||
validate.Validator(), |
||||
), |
||||
// 拦截器 |
||||
grpc.UnaryInterceptor(grpcInterceptor.UnaryServerInterceptor()), |
||||
} |
||||
if c.Server.Grpc.Network != "" { |
||||
opts = append(opts, grpc.Network(c.Server.Grpc.Network)) |
||||
} |
||||
if c.Server.Grpc.Addr != "" { |
||||
opts = append(opts, grpc.Address(c.Server.Grpc.Addr)) |
||||
} |
||||
if c.Server.Grpc.Timeout != nil { |
||||
opts = append(opts, grpc.Timeout(c.Server.Grpc.Timeout.AsDuration())) |
||||
} |
||||
|
||||
srv := grpc.NewServer(opts...) |
||||
v2.RegisterUserCenterServer(srv, userService) |
||||
return srv |
||||
} |
||||
``` |
@ -0,0 +1,55 @@ |
||||
package grpc |
||||
|
||||
import ( |
||||
"context" |
||||
"gitea.drugeyes.vip/pharnexbase/utils/enum" |
||||
"github.com/go-kratos/kratos/v2/errors" |
||||
"google.golang.org/grpc" |
||||
) |
||||
|
||||
var ErrInternalServer = errors.InternalServer("InternalServerError", "服务错误") |
||||
|
||||
type Interceptor struct { |
||||
env enum.Env // 环境
|
||||
reason map[string]int32 // 业务错误枚举
|
||||
} |
||||
|
||||
func NewInterceptor(env enum.Env, reason map[string]int32) *Interceptor { |
||||
return &Interceptor{ |
||||
env: env, |
||||
reason: reason, |
||||
} |
||||
} |
||||
|
||||
func (i *Interceptor) UnaryClientInterceptor() grpc.UnaryClientInterceptor { |
||||
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { |
||||
h := func(ctx context.Context, req interface{}) (interface{}, error) { |
||||
return reply, invoker(ctx, method, req, reply, cc, opts...) |
||||
} |
||||
_, err := h(ctx, req) |
||||
if err != nil { |
||||
err = errors.FromError(err) |
||||
} |
||||
return err |
||||
} |
||||
} |
||||
|
||||
func (i *Interceptor) UnaryServerInterceptor() grpc.UnaryServerInterceptor { |
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { |
||||
resp, err = handler(ctx, req) |
||||
if err == nil { |
||||
return |
||||
} |
||||
|
||||
if i.env == enum.Env_Production { |
||||
se := errors.FromError(err) |
||||
if _, ok := i.reason[se.Reason]; ok { |
||||
// 业务错误,原样返回
|
||||
return |
||||
} |
||||
// 正式服,统一返回“服务错误”
|
||||
err = ErrInternalServer |
||||
} |
||||
return |
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
# interceptor-通用grpcInterceptor |
||||
|
||||
## 说明 |
||||
|
||||
> http请求和响应封装 |
||||
> - request |
||||
> - 验证前端参数签名 |
||||
> - response |
||||
> - ResponseEncoderWithEncrypt 正式服加密后端响应参数 |
||||
> - bff原样返回server错误及自身业务错误,正式服其它错误统一返回InternalServerError |
||||
|
||||
## 使用示例 |
||||
|
||||
### http |
||||
|
||||
```go |
||||
package server |
||||
|
||||
import ( |
||||
userV1 "gitea.drugeyes.vip/ebm/ebm-bff/api/v1/user" |
||||
"gitea.drugeyes.vip/ebm/ebm-bff/internal/conf" |
||||
"gitea.drugeyes.vip/ebm/ebm-bff/internal/service" |
||||
trace "gitea.drugeyes.vip/ebm/ebm-bff/middleware/trace" |
||||
http1 "gitea.drugeyes.vip/pharnexbase/utils/transport/v1/http" |
||||
"github.com/go-kratos/kratos/v2/log" |
||||
"github.com/go-kratos/kratos/v2/middleware/logging" |
||||
"github.com/go-kratos/kratos/v2/middleware/recovery" |
||||
"github.com/go-kratos/kratos/v2/middleware/tracing" |
||||
"github.com/go-kratos/kratos/v2/middleware/validate" |
||||
"github.com/go-kratos/kratos/v2/transport/http" |
||||
"github.com/gorilla/handlers" |
||||
) |
||||
|
||||
// NewHTTPServer new HTTP server. |
||||
func NewHTTPServer( |
||||
c *conf.Bootstrap, |
||||
user *service.UserService, |
||||
logger log.Logger) *http.Server { |
||||
headersOk := handlers.AllowedHeaders([]string{ |
||||
"Content-Type", "X-Requested-With", |
||||
"Authorization", "User-Agent", "Accept", "Referer", |
||||
"Client-Version"}) |
||||
originsOk := handlers.AllowedOrigins([]string{"*"}) |
||||
methodsOk := handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"}) |
||||
|
||||
httpTrans := http1.NewTransport(c.Env, "sm4Key", "apiKey") |
||||
var opts = []http.ServerOption{ |
||||
http.Filter(handlers.CORS(headersOk, originsOk, methodsOk)), |
||||
http.RequestDecoder(httpTrans.RequestDecoder()), // body解码 |
||||
http.RequestQueryDecoder(httpTrans.RequestQueryDecoder()), // query解码 |
||||
http.ResponseEncoder(httpTrans.ResponseEncoder()), // 正常响应 |
||||
http.ErrorEncoder(httpTrans.ErrorEncoder()), // 错误响应 |
||||
http.Middleware( |
||||
recovery.Recovery(), |
||||
tracing.Server(), |
||||
logging.Server(logger), |
||||
validate.Validator(), |
||||
trace.SetTraceIdToHeader, |
||||
), |
||||
} |
||||
if c.Server.Http.Network != "" { |
||||
opts = append(opts, http.Network(c.Server.Http.Network)) |
||||
} |
||||
if c.Server.Http.Addr != "" { |
||||
opts = append(opts, http.Address(c.Server.Http.Addr)) |
||||
} |
||||
if c.Server.Http.Timeout != nil { |
||||
opts = append(opts, http.Timeout(c.Server.Http.Timeout.AsDuration())) |
||||
} |
||||
|
||||
srv := http.NewServer(opts...) |
||||
userV1.RegisterUserCenterHTTPServer(srv, user) |
||||
return srv |
||||
} |
||||
|
||||
``` |
@ -0,0 +1,89 @@ |
||||
package http |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"gitea.drugeyes.vip/pharnexbase/tools/request" |
||||
"github.com/go-kratos/kratos/v2/errors" |
||||
"github.com/go-kratos/kratos/v2/transport/http" |
||||
"github.com/go-kratos/kratos/v2/transport/http/binding" |
||||
"github.com/tidwall/gjson" |
||||
"io" |
||||
"net/url" |
||||
"strings" |
||||
) |
||||
|
||||
var SignErr = errors.Forbidden("SignError", "签名错误") |
||||
|
||||
func (t *Transport) RequestQueryDecoder() http.DecodeRequestFunc { |
||||
return func(r *http.Request, v interface{}) error { |
||||
// 将post和query参数合并
|
||||
params := make(map[string]string) |
||||
for _, val := range strings.Split(r.URL.RawQuery, "&") { |
||||
vals := strings.Split(val, "=") |
||||
if len(vals) < 2 { |
||||
continue |
||||
} |
||||
params[vals[0]], _ = url.PathUnescape(vals[1]) |
||||
} |
||||
signature := request.NewSignature(t.apiKey, request.NewSHA1HashAlg()) |
||||
signStr := signature.GenSignature(params) |
||||
if signStr != r.Header.Get("sm5") { |
||||
return SignErr |
||||
} |
||||
if err := binding.BindQuery(r.URL.Query(), v); err != nil { |
||||
return errors.BadRequest("CODEC", err.Error()) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
} |
||||
|
||||
// RequestDecoder 请求拦截
|
||||
func (t *Transport) RequestDecoder() http.DecodeRequestFunc { |
||||
return func(r *http.Request, v interface{}) error { |
||||
// 从Request Header的Content-Type中提取出对应的解码器
|
||||
codec, ok := http.CodecForRequest(r, "Content-Type") |
||||
// 如果找不到对应的解码器此时会报错
|
||||
if !ok { |
||||
return errors.BadRequest("CODEC", r.Header.Get("Content-Type")) |
||||
} |
||||
data, err := io.ReadAll(r.Body) |
||||
if err != nil { |
||||
return errors.BadRequest("CODEC", err.Error()) |
||||
} |
||||
|
||||
// 将post和query参数合并
|
||||
params := make(map[string]string) |
||||
gjson.ParseBytes(data).ForEach(func(key, value gjson.Result) bool { |
||||
switch value.Type { |
||||
case gjson.JSON: |
||||
var buf bytes.Buffer |
||||
json.Compact(&buf, []byte(value.String())) |
||||
params[key.String()] = buf.String() |
||||
default: |
||||
params[key.String()] = value.String() |
||||
} |
||||
|
||||
return true |
||||
}) |
||||
for _, val := range strings.Split(r.URL.RawQuery, "&") { |
||||
vals := strings.Split(val, "=") |
||||
if len(vals) < 2 { |
||||
continue |
||||
} |
||||
params[vals[0]], _ = url.PathUnescape(vals[1]) |
||||
} |
||||
signature := request.NewSignature(t.apiKey, request.NewSHA1HashAlg()) |
||||
signStr := signature.GenSignature(params) |
||||
if signStr != r.Header.Get("sm5") { |
||||
return SignErr |
||||
} |
||||
|
||||
if err = codec.Unmarshal(data, v); err != nil { |
||||
return errors.BadRequest("CODEC", err.Error()) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
package http |
||||
|
||||
import ( |
||||
"gitea.drugeyes.vip/pharnexbase/utils/enum" |
||||
) |
||||
|
||||
type Transport struct { |
||||
env enum.Env // 环境
|
||||
sm4Key string // sm4Key
|
||||
apiKey string // apiKey
|
||||
} |
||||
|
||||
func NewTransport(env enum.Env, sm4Key, apiKey string) *Transport { |
||||
return &Transport{ |
||||
env: env, |
||||
sm4Key: sm4Key, |
||||
apiKey: apiKey, |
||||
} |
||||
} |
Loading…
Reference in new issue