diff --git a/kit/restfulx/log_handler.go b/kit/restfulx/log_handler.go new file mode 100644 index 0000000..fc39caf --- /dev/null +++ b/kit/restfulx/log_handler.go @@ -0,0 +1,15 @@ +package restfulx + +type LogInfo struct { + LogResp bool // 是否记录返回结果 + Description string // 请求描述 +} + +func NewLogInfo(description string) *LogInfo { + return &LogInfo{Description: description, LogResp: false} +} + +func (i *LogInfo) WithLogResp(logResp bool) *LogInfo { + i.LogResp = logResp + return i +} diff --git a/kit/restfulx/permission_handler.go b/kit/restfulx/permission_handler.go new file mode 100644 index 0000000..65db4bf --- /dev/null +++ b/kit/restfulx/permission_handler.go @@ -0,0 +1,16 @@ +package restfulx + +type Permission struct { + NeedToken bool // 是否需要token + NeedCasbin bool // 是否进行权限 api路径权限验证 +} + +func (p *Permission) WithNeedToken(needToken bool) *Permission { + p.NeedToken = needToken + return p +} + +func (p *Permission) WithNeedCasBin(needCasBin bool) *Permission { + p.NeedCasbin = needCasBin + return p +} diff --git a/kit/restfulx/req_ctx.go b/kit/restfulx/req_ctx.go new file mode 100644 index 0000000..8592668 --- /dev/null +++ b/kit/restfulx/req_ctx.go @@ -0,0 +1,135 @@ +package restfulx + +import ( + "pandax/kit/biz" + "pandax/kit/token" + "time" + + "github.com/emicklei/go-restful/v3" + "github.com/go-playground/validator/v10" +) + +// 处理函数 +type HandlerFunc func(*ReqCtx) + +type ReqCtx struct { + Request *restful.Request + Response *restful.Response + // NeedToken bool // 是否需要token + RequiredPermission *Permission // 需要的权限信息,默认为nil,需要校验token + LoginAccount *token.Claims // 登录账号信息,只有校验token后才会有值 + + LogInfo *LogInfo // 日志相关信息 + ReqParam any // 请求参数,主要用于记录日志 + ResData any // 响应结果 + Err any // 请求错误 + Validate *validator.Validate + Timed int64 // 执行时间 + NoRes bool // 无需返回结果,即文件下载等 +} + +func (rc *ReqCtx) Handle(handler HandlerFunc) { + request := rc.Response + defer func() { + var err any + err = recover() + if err != nil { + rc.Err = err + ErrorRes(request, err) + } + // 应用所有请求后置处理器 + ApplyHandlerInterceptor(afterHandlers, rc) + }() + biz.IsTrue(rc.Request != nil, "Request == nil") + + // 默认为不记录请求参数,可在handler回调函数中覆盖赋值 + rc.ReqParam = 0 + // 默认响应结果为nil,可在handler中赋值 + rc.ResData = nil + + // 调用请求前所有处理器 + err := ApplyHandlerInterceptor(beforeHandlers, rc) + if err != nil { + panic(err) + } + + begin := time.Now() + handler(rc) + rc.Timed = time.Now().Sub(begin).Milliseconds() + if !rc.NoRes { + SuccessRes(rc.Response, rc.ResData) + } +} + +func (rc *ReqCtx) Download(filename string) { + rc.NoRes = true + Download(rc, filename) +} + +func NewReqCtx(request *restful.Request, response *restful.Response) *ReqCtx { + return &ReqCtx{ + Request: request, + Response: response, + LogInfo: NewLogInfo("默认日志信息"), + Validate: validator.New(), + RequiredPermission: &Permission{NeedToken: true, NeedCasbin: true}, + } +} + +// 调用该方法设置请求描述,则默认记录日志,并不记录响应结果 +func (r *ReqCtx) WithLog(model string) *ReqCtx { + r.LogInfo.Description = model + return r +} + +func (r *ReqCtx) NoAppend() *ReqCtx { + r.NoRes = true + return r +} + +// 设置请求上下文需要的权限信息 +func (r *ReqCtx) WithRequiredPermission(permission *Permission) *ReqCtx { + r.RequiredPermission = permission + return r +} + +// 是否需要token +func (r *ReqCtx) WithNeedToken(needToken bool) *ReqCtx { + r.RequiredPermission.NeedToken = needToken + return r +} + +// 是否需要Casbin +func (r *ReqCtx) WithNeedCasbin(needCasbin bool) *ReqCtx { + r.RequiredPermission.NeedCasbin = needCasbin + return r +} + +// 处理器拦截器函数 +type HandlerInterceptorFunc func(*ReqCtx) error +type HandlerInterceptors []HandlerInterceptorFunc + +var ( + beforeHandlers HandlerInterceptors + afterHandlers HandlerInterceptors +) + +// 使用前置处理器函数 +func UseBeforeHandlerInterceptor(b HandlerInterceptorFunc) { + beforeHandlers = append(beforeHandlers, b) +} + +// 使用后置处理器函数 +func UseAfterHandlerInterceptor(b HandlerInterceptorFunc) { + afterHandlers = append(afterHandlers, b) +} + +// 应用指定处理器拦截器,如果有一个错误则直接返回错误 +func ApplyHandlerInterceptor(his HandlerInterceptors, rc *ReqCtx) any { + for _, handler := range his { + if err := handler(rc); err != nil { + return err + } + } + return nil +} diff --git a/kit/restfulx/restfulx.go b/kit/restfulx/restfulx.go new file mode 100644 index 0000000..91303c7 --- /dev/null +++ b/kit/restfulx/restfulx.go @@ -0,0 +1,116 @@ +package restfulx + +import ( + "encoding/json" + "net/http" + "pandax/kit/biz" + "pandax/kit/logger" + "pandax/kit/model" + "strconv" + + "github.com/emicklei/go-restful/v3" + "github.com/gorilla/schema" +) + +// 绑定并校验请求结构体参数 结构体添加 例如: validate:"required" validate:"required,gt=10" +func BindJsonAndValid(rc *ReqCtx, data any) { + if err := rc.Request.ReadEntity(data); err != nil { + panic(any(biz.NewBizErr(err.Error()))) + } + if err := rc.Validate.Struct(data); err != nil { + panic(any(biz.NewBizErr("传参格式错误:" + err.Error()))) + } +} + +// BindQuery 绑定查询字符串到 +func BindQuery(rc *ReqCtx, data any) { + if err := rc.Request.ReadEntity(data); err != nil { + panic(any(biz.NewBizErr(err.Error()))) + } +} + +// PathParamsToAny 获取虽有路径中的参数 +func PathParamsToAny(rc *ReqCtx, in any) { + vars := make(map[string]any) + for k, v := range rc.Request.PathParameters() { + vars[k] = v + } + marshal, _ := json.Marshal(vars) + err := json.Unmarshal(marshal, in) + biz.ErrIsNil(err, "error get path value encoding unmarshal") + return +} + +// QueryParamsToAny 获取所有Query的参数 +func QueryParamsToAny(rc *ReqCtx, in any) { + err := rc.Request.Request.ParseForm() + biz.ErrIsNil(err, "error get ParseForm value encoding unmarshal") + decoder := schema.NewDecoder() + err = decoder.Decode(in, rc.Request.Request.Form) + biz.ErrIsNil(err, "error get path value encoding unmarshal") + return +} + +// GetPageQueryParam 获取分页参数 +func GetPageQueryParam(rc *ReqCtx) *model.PageParam { + return &model.PageParam{PageNum: QueryInt(rc, "pageNum", 1), PageSize: QueryInt(rc, "pageSize", 10)} +} + +// QueryInt 获取查询参数中指定参数值,并转为int +func QueryInt(rc *ReqCtx, qm string, defaultInt int) int { + qv := rc.Request.QueryParameter(qm) + if qv == "" { + return defaultInt + } + qvi, err := strconv.Atoi(qv) + biz.ErrIsNil(err, "query param not int") + return qvi +} + +// QueryParam QueryParam +func QueryParam(rc *ReqCtx, key string) string { + return rc.Request.QueryParameter(key) +} + +// PathParamInt 获取路径参数 +func PathParamInt(rc *ReqCtx, pm string) int { + value, _ := strconv.Atoi(rc.Request.PathParameter(pm)) + return value +} +func PathParam(rc *ReqCtx, pm string) string { + return rc.Request.PathParameter(pm) +} + +// 文件下载 +func Download(rc *ReqCtx, filename string) { + rc.Response.Header().Add("success", "true") + rc.Response.Header().Set("Content-Length", "-1") + rc.Response.Header().Set("Content-Disposition", "attachment; filename="+filename) + http.ServeFile( + rc.Response.ResponseWriter, + rc.Request.Request, filename) +} + +// 返回统一成功结果 +func SuccessRes(response *restful.Response, data any) { + response.WriteEntity(model.Success(data)) +} + +// 返回失败结果集 +func ErrorRes(response *restful.Response, err any) { + switch t := err.(type) { + case *biz.BizError: + response.WriteEntity(model.Error(t)) + break + case error: + response.WriteEntity(model.ServerError()) + logger.Log.Error(t) + break + case string: + response.WriteEntity(model.ServerError()) + logger.Log.Error(t) + break + default: + logger.Log.Error(t) + } +}