评价系统(4)

编写中间件和错误处理。

中间件是一种位于请求处理链中的组件,用来在请求到达业务逻辑前、或响应返回客户端之前,进行一些通用的处理。常用的:比如参数校验,限流,日志记录等等。
在将kratos中间件之前,先回顾一下gin框架的中间件。
gin 采用了 洋葱模型(Onion Model)的方式执行中间件,使其能够以链式调用的方式拦截、修改或终止请求。

洋葱模型

请求 -> 中间件1 -> 中间件2 -> 业务逻辑 -> 中间件2 -> 中间件1 -> 响应

gin框架涉及中间件相关有4个常用的方法,它们分别是c.Next()、c.Abort()、c.Set()、c.Get()
gin.default来看中间件的使用

func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())  // 默认注册的两个中间件
    return engine
}

再来看看Use里的函数,可以看到他接收了一系列中间件函数

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    engine.RouterGroup.Use(middleware...)  // 实际上还是调用的RouterGroup的Use函数
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)  // 将处理请求的函数与中间件函数结合
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}

const abortIndex int8 = math.MaxInt8 / 2

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    finalSize := len(group.Handlers) + len(handlers)
    if finalSize >= int(abortIndex) {  // 这里有一个最大限制
        panic("too many handlers")
    }
    mergedHandlers := make(HandlersChain, finalSize)
    copy(mergedHandlers, group.Handlers)
    copy(mergedHandlers[len(group.Handlers):], handlers)
    return mergedHandlers
}

而中间件是通过c.Next()、c.Abort()来控制调用链的。
可以看到Next 通过判断hanler的长度,按顺序执行在handler里注册的函数。而Abort 通过将索引值设成最大,直接退出循环

func (c *Context) Next() {
    c.index++
    for c.index < int8(len(c.handlers)) {
        c.handlers[c.index](c)
        c.index++
    }
}

func (c *Context) Abort() {
    c.index = abortIndex  // 直接将索引置为最大限制值,从而退出循环
}

c.Set()c.Get()这两个方法多用于在多个函数之间通过c传递数据的,比如我们可以在认证中间件中获取当前请求的相关信息(userID等)通过c.Set()存入c,然后在后续处理业务逻辑的函数中通过c.Get()来获取当前请求的用户。c就像是一根绳子,将该次请求相关的所有的函数都串起来了。

Kratos中间件

kratos也同gin框架一般,采用洋葱状并且内置了很多中间件的方法,可以直接使用。
可以通过实现 Middleware 接口,开发自定义 middleware,进行通用的业务处理,比如用户登录鉴权等。
接着来对比一下两者在中间件上在路由上的应用。
gin

全局中间件
router := gin.Default()
router.Use(gin.Logger(), gin.Recovery())
组路由中间件
v1 := router.Group("/v1")
v1.Use(VersionMiddleware())
v1.GET("/users", ...)
特定路由中间件
router.GET("/endpoint", AuthMiddleware(), controllerHandler)

Kratos

全局中间件
// http
// 定义opts
var opts = []http.ServerOption{
    http.Middleware(
        recovery.Recovery(), // 把middleware按照需要的顺序加入
        tracing.Server(),
        logging.Server(),
    ),
}
特定路由,kratos不同于gin,他是通过路由匹配的方式来对进行中间件的使用
http.Middleware(
            selector.Server(recovery.Recovery(), tracing.Server(),testMiddleware).
                Path("/hello.Update/UpdateUser", "/hello.kratos/SayHello").
                Regex(`/test.hello/Get[0-9]+`).
                Prefix("/kratos.", "/go-kratos.", "/helloworld.Greeter/").
                Build(),
        )

匹配规则(多参数):

Path(path...): 路由匹配
Regex(regex...): 正则匹配
Prefix(prefix...): 前缀匹配
Match(fn): 函数匹配,函数格式为func(ctx context.Context,operation string) bool。 operation为 path,函数返回值为true,匹配成功,ctx可使用transport.FromServerContext(ctx)或者 transport.FromClientContext(ctx获取 Transporter)
kratos不同于gin,他通过来protobuf生成文件控制配置参数。
具体可查validate
最后在server包里添加中间件即可

httpSrv := http.NewServer(
    http.Address(":8000"),
    http.Middleware(
        validate.Validator(),
    ))
grpcSrv := grpc.NewServer(
    grpc.Address(":9000"),
    grpc.Middleware(
        validate.Validator(),
    ))

错误处理

当希望对不同的请求响应不同的错误就需要定义不同的错误响应,kratos的错误处理可以分为三步:

  1. 定义proto⽂件
  2. ⽣成代码
  3. 业务代码中使⽤⽣成的代码返回错误
import "errors/errors.proto";

// 多语言特定包名,用于源代码引用
option go_package = "review-service/api/review/v1;v1";
option java_multiple_files = true;
option java_package = "api.review.v1";

enum ErrorReason {
// 设置缺省错误码
option (errors.default_code) = 500;

// 为某个枚举单独设置错误码
NEED_LOGIN = 0 [(errors.code) = 401];
DB_FAILED = 1 [(errors.code) = 500];

ORDER_REVIEWED = 100 [(errors.code) = 400];
}

之后生成代码
diz层便可以使用生成的代码进行错误响应。

// CreateGreeter creates a Review, and returns the new Review.
func (uc *ReviewUsecase) CreateReview(ctx context.Context, review *model.ReviewInfo) (*model.ReviewInfo, error) {
    uc.log.WithContext(ctx).Debugf("[biz] CreateReview, req:%v", review)
    reviews, err := uc.repo.GetReviewByOrderID(ctx, review.OrderID)
    if err != nil {
        return nil, v1.ErrorDbFailed("查询数据库失败")
    }
    if len(reviews) > 0 {
        // 已经评价过
        fmt.Printf("订单已评价, len(reviews):%d\n", len(reviews))
        return nil, v1.ErrorOrderReviewed("订单已:%d 已评价", review.OrderID)
    }
    review.ReviewID = snowflake.GenID()
    return uc.repo.SaveReview(ctx, review)
}

这样便可以定义想返回的

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容