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