parent
b3c82d7d6d
commit
fa896b66b6
@ -0,0 +1 @@ |
||||
package main |
@ -1,140 +0,0 @@ |
||||
package middleware |
||||
|
||||
import ( |
||||
"github.com/golang-jwt/jwt/v5" |
||||
"github.com/labstack/echo-jwt/v4" |
||||
"github.com/labstack/echo/v4" |
||||
) |
||||
|
||||
// JWTConfig defines the config for JWT middleware.
|
||||
type JWTConfig struct { |
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper |
||||
|
||||
// BeforeFunc defines a function which is executed just before the middleware.
|
||||
BeforeFunc BeforeFunc |
||||
|
||||
// SuccessHandler defines a function which is executed for a valid token.
|
||||
SuccessHandler func(c echo.Context) |
||||
|
||||
// ErrorHandler defines a function which is executed when all lookups have been done and none of them passed Validator
|
||||
// function. ErrorHandler is executed with last missing (ErrExtractionValueMissing) or an invalid key.
|
||||
// It may be used to define a custom JWT error.
|
||||
//
|
||||
// Note: when error handler swallows the error (returns nil) middleware continues handler chain execution towards handler.
|
||||
// This is useful in cases when portion of your site/api is publicly accessible and has extra features for authorized users
|
||||
// In that case you can use ErrorHandler to set default public JWT token value to request and continue with handler chain.
|
||||
ErrorHandler func(c echo.Context, err error) error |
||||
|
||||
// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to
|
||||
// ignore the error (by returning `nil`).
|
||||
// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
|
||||
// In that case you can use ErrorHandler to set a default public JWT token value in the request context
|
||||
// and continue. Some logic down the remaining execution chain needs to check that (public) token value then.
|
||||
ContinueOnIgnoredError bool |
||||
|
||||
// Context key to store user information from the token into context.
|
||||
// Optional. Default value "user".
|
||||
ContextKey string |
||||
|
||||
// Signing key to validate token.
|
||||
// This is one of the three options to provide a token validation key.
|
||||
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
|
||||
// Required if neither user-defined KeyFunc nor SigningKeys is provided.
|
||||
SigningKey any |
||||
|
||||
// Map of signing keys to validate token with kid field usage.
|
||||
// This is one of the three options to provide a token validation key.
|
||||
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
|
||||
// Required if neither user-defined KeyFunc nor SigningKey is provided.
|
||||
SigningKeys map[string]any |
||||
|
||||
// Signing method used to check the token's signing algorithm.
|
||||
// Optional. Default value HS256.
|
||||
SigningMethod string |
||||
|
||||
// KeyFunc defines a user-defined function that supplies the public key for a token validation.
|
||||
// The function shall take care of verifying the signing algorithm and selecting the proper key.
|
||||
// A user-defined KeyFunc can be useful if tokens are issued by an external party.
|
||||
// Used by default ParseTokenFunc implementation.
|
||||
//
|
||||
// When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored.
|
||||
// This is one of the three options to provide a token validation key.
|
||||
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
|
||||
// Required if neither SigningKeys nor SigningKey is provided.
|
||||
// Not used if custom ParseTokenFunc is set.
|
||||
// Default to an internal implementation verifying the signing algorithm and selecting the proper key.
|
||||
KeyFunc jwt.Keyfunc |
||||
|
||||
// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
|
||||
// to extract token from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
// Possible values:
|
||||
// - "header:<name>" or "header:<name>:<cut-prefix>"
|
||||
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
|
||||
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
|
||||
// want to cut is `<auth-scheme> ` note the space at the end.
|
||||
// In case of JWT tokens `Authorization: Bearer <token>` prefix we cut is `Bearer `.
|
||||
// If prefix is left empty the whole value is returned.
|
||||
// - "query:<name>"
|
||||
// - "param:<name>"
|
||||
// - "cookie:<name>"
|
||||
// - "form:<name>"
|
||||
// Multiple sources example:
|
||||
// - "header:Authorization:Bearer ,cookie:myowncookie"
|
||||
TokenLookup string |
||||
|
||||
// TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context.
|
||||
// This is one of the two options to provide a token extractor.
|
||||
// The order of precedence is user-defined TokenLookupFuncs, and TokenLookup.
|
||||
// You can also provide both if you want.
|
||||
TokenLookupFuncs []ValuesExtractor |
||||
|
||||
// ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token
|
||||
// parsing fails or parsed token is invalid.
|
||||
// Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library
|
||||
ParseTokenFunc func(c echo.Context, auth string) (any, error) |
||||
|
||||
// Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation.
|
||||
// Not used if custom ParseTokenFunc is set.
|
||||
// Optional. Defaults to function returning jwt.MapClaims
|
||||
NewClaimsFunc func(c echo.Context) jwt.Claims |
||||
} |
||||
|
||||
// Errors
|
||||
var ( |
||||
ErrJWTMissing = echojwt.ErrJWTMissing |
||||
ErrJWTInvalid = echojwt.ErrJWTInvalid |
||||
) |
||||
|
||||
func (config *JWTConfig) ToMiddleware() echo.MiddlewareFunc { |
||||
return echojwt.WithConfig(echojwt.Config{ |
||||
Skipper: config.Skipper, |
||||
BeforeFunc: config.BeforeFunc, |
||||
SuccessHandler: config.SuccessHandler, |
||||
ErrorHandler: config.ErrorHandler, |
||||
ContinueOnIgnoredError: config.ContinueOnIgnoredError, |
||||
ContextKey: config.ContextKey, |
||||
SigningKey: config.SigningKey, |
||||
SigningKeys: config.SigningKeys, |
||||
SigningMethod: config.SigningMethod, |
||||
KeyFunc: config.KeyFunc, |
||||
TokenLookup: config.TokenLookup, |
||||
TokenLookupFuncs: config.TokenLookupFuncs, |
||||
ParseTokenFunc: config.ParseTokenFunc, |
||||
NewClaimsFunc: nil, |
||||
}) |
||||
} |
||||
|
||||
// JWT returns a JSON Web Token (JWT) auth middleware.
|
||||
//
|
||||
// For valid token, it sets the user in context and calls next handler.
|
||||
// For invalid token, it returns "401 - Unauthorized" error.
|
||||
// For missing token, it returns "400 - Bad Request" error.
|
||||
//
|
||||
// See: https://jwt.io/introduction
|
||||
// See `JWTConfig.TokenLookup`
|
||||
// See https://github.com/labstack/echo-jwt
|
||||
func JWT(signingKey any) echo.MiddlewareFunc { |
||||
return echojwt.JWT(signingKey) |
||||
} |
@ -0,0 +1,104 @@ |
||||
package middleware |
||||
|
||||
import ( |
||||
"crypto/rsa" |
||||
"github.com/golang-jwt/jwt/v5" |
||||
"github.com/labstack/echo/v4" |
||||
"slices" |
||||
"sorbet/pkg/env" |
||||
"sorbet/pkg/ticket" |
||||
) |
||||
|
||||
var ticketPublicKey *rsa.PublicKey |
||||
|
||||
type TicketConfig struct { |
||||
Skipper Skipper |
||||
Anonymously bool |
||||
Audiences []string |
||||
PublicKey *rsa.PublicKey |
||||
TicketFinder ticket.Finder |
||||
ClaimsLooker func(c echo.Context, claims *ticket.Claims) error |
||||
ErrorHandler func(c echo.Context, err error) error |
||||
SuccessHandler func(c echo.Context) |
||||
} |
||||
|
||||
func Ticket(anonymously bool, roles ...string) echo.MiddlewareFunc { |
||||
return TicketWithConfig(TicketConfig{ |
||||
Anonymously: anonymously, |
||||
TicketFinder: ticket.DefaultFinder, |
||||
ClaimsLooker: func(c echo.Context, claims *ticket.Claims) error { |
||||
if len(roles) > 0 && slices.Contains(roles, claims.Role) { |
||||
return nil |
||||
} |
||||
return ticket.ErrUnauthorized |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
func TicketWithConfig(config TicketConfig) echo.MiddlewareFunc { |
||||
return config.ToMiddleware() |
||||
} |
||||
|
||||
func (t *TicketConfig) ToMiddleware() echo.MiddlewareFunc { |
||||
if t.Skipper == nil { |
||||
t.Skipper = DefaultSkipper |
||||
} |
||||
if len(t.Audiences) == 0 { |
||||
t.Audiences = append(t.Audiences, "*") |
||||
} |
||||
if t.TicketFinder == nil { |
||||
t.TicketFinder = ticket.DefaultFinder |
||||
} |
||||
if t.ErrorHandler == nil { |
||||
t.ErrorHandler = func(c echo.Context, err error) error { |
||||
return err |
||||
} |
||||
} |
||||
if t.ClaimsLooker == nil { |
||||
t.ClaimsLooker = func(c echo.Context, claims *ticket.Claims) error { |
||||
return nil |
||||
} |
||||
} |
||||
return func(next echo.HandlerFunc) echo.HandlerFunc { |
||||
return func(c echo.Context) error { |
||||
if t.Skipper(c) { |
||||
return next(c) |
||||
} |
||||
ticketString := t.TicketFinder(c.Request()) |
||||
if ticketString == "" { |
||||
if t.Anonymously { |
||||
return next(c) |
||||
} |
||||
return t.ErrorHandler(c, ticket.ErrNoTicketFound) |
||||
} |
||||
publicKey := t.PublicKey |
||||
if publicKey == nil { |
||||
key, err := getTicketPublicKey() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
publicKey = key |
||||
} |
||||
claims, err := ticket.Verify(ticketString, publicKey, t.Audiences...) |
||||
if err != nil { |
||||
return t.ErrorHandler(c, err) |
||||
} |
||||
if err = t.ClaimsLooker(c, claims); err != nil { |
||||
return t.ErrorHandler(c, err) |
||||
} |
||||
c.Set("ticket", ticketString) |
||||
c.Set("ticket_claims", claims) |
||||
return next(c) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func getTicketPublicKey() (*rsa.PublicKey, error) { |
||||
if ticketPublicKey != nil { |
||||
return ticketPublicKey, nil |
||||
} |
||||
var err error |
||||
source := []byte(env.String("TICKET_PUBLIC_KEY")) |
||||
ticketPublicKey, err = jwt.ParseRSAPublicKeyFromPEM(source) |
||||
return ticketPublicKey, err |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/company/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type CompanyController struct { |
||||
util.Controller[entities.Company, request.CompanyUpsertRequest] |
||||
} |
||||
|
||||
func (ctr *CompanyController) InitRoutes(r *echo.Group) { |
||||
ctr.RegisterRoutes("/companies", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/company/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type CompanyDepartmentController struct { |
||||
util.Controller[entities.CompanyDepartment, request.CompanyDepartmentUpsertRequest] |
||||
} |
||||
|
||||
func (c *CompanyDepartmentController) InitRoutes(r *echo.Group) { |
||||
c.RegisterRoutes("/company/departments", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/company/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type CompanyStaffController struct { |
||||
util.Controller[entities.CompanyStaff, request.CompanyStaffUpsertRequest] |
||||
} |
||||
|
||||
func (c *CompanyStaffController) InitRoutes(r *echo.Group) { |
||||
c.RegisterRoutes("/company/staffs", r) |
||||
} |
@ -0,0 +1,42 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
"sorbet/pkg/db" |
||||
) |
||||
|
||||
type CompanyDepartmentUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
PID uint `json:"pid" xml:"pid" form:"pid"` |
||||
CompanyID uint `json:"company_id" xml:"company_id" form:"company_id"` |
||||
PrincipalID uint `json:"principal_id" xml:"principal_id" form:"principal_id"` |
||||
Name string `json:"name" xml:"name" form:"name"` |
||||
Sort int32 `json:"sort" xml:"sort" form:"sort"` |
||||
} |
||||
|
||||
func (c *CompanyDepartmentUpsertRequest) GetID() any { |
||||
return c.ID |
||||
} |
||||
|
||||
func (c *CompanyDepartmentUpsertRequest) ToEntity() any { |
||||
return &entities.CompanyDepartment{ |
||||
ID: c.ID, |
||||
PID: &c.PID, |
||||
CompanyID: c.CompanyID, |
||||
PrincipalID: &c.PrincipalID, |
||||
Name: c.Name, |
||||
Sort: c.Sort, |
||||
Version: db.Version{Int64: 1, Valid: true}, |
||||
} |
||||
} |
||||
|
||||
func (c *CompanyDepartmentUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": c.ID, |
||||
"pid": c.PID, |
||||
"company_id": c.CompanyID, |
||||
"principal_id": c.PrincipalID, |
||||
"name": c.Name, |
||||
"sort": c.Sort, |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type CompanyStaffUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
CompanyID uint `json:"company_id" xml:"company_id" form:"company_id"` |
||||
Name string `json:"name" xml:"name" form:"name"` |
||||
Gender string `json:"gender" xml:"gender" form:"gender"` |
||||
Position string `json:"position" xml:"position" form:"position"` |
||||
PhoneNumber string `json:"phone_number" xml:"phone_number" form:"phone_number"` |
||||
WechatOpenid string `json:"wechat_openid" xml:"wechat_openid" form:"wechat_openid"` |
||||
WithoutStudy bool `json:"without_study" xml:"without_study" form:"without_study"` |
||||
IsAdmin bool `json:"is_admin" xml:"is_admin" form:"is_admin"` |
||||
} |
||||
|
||||
func (c *CompanyStaffUpsertRequest) GetID() any { |
||||
return c.ID |
||||
} |
||||
|
||||
func (c *CompanyStaffUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": c.ID, |
||||
"company_id": c.CompanyID, |
||||
"name": c.Name, |
||||
"gender": c.Gender, |
||||
"position": c.Position, |
||||
"phone_number": c.PhoneNumber, |
||||
"wechat_openid": c.WechatOpenid, |
||||
"without_study": c.WithoutStudy, |
||||
"is_admin": c.IsAdmin, |
||||
} |
||||
} |
||||
|
||||
func (c *CompanyStaffUpsertRequest) ToEntity() any { |
||||
return &entities.CompanyStaff{ |
||||
ID: c.ID, |
||||
CompanyID: c.CompanyID, |
||||
Name: c.Name, |
||||
Gender: c.Gender, |
||||
Position: c.Position, |
||||
PhoneNumber: c.PhoneNumber, |
||||
WechatOpenid: c.WechatOpenid, |
||||
WithoutStudy: c.WithoutStudy, |
||||
IsAdmin: c.IsAdmin, |
||||
} |
||||
} |
@ -0,0 +1,39 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
"sorbet/pkg/db" |
||||
) |
||||
|
||||
type CompanyUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
PrincipalID uint `json:"principal_id" xml:"principal_id" form:"principal_id"` |
||||
Name string `json:"name" xml:"name" form:"name"` |
||||
Logo string `json:"logo" xml:"logo" form:"logo"` |
||||
Status bool `json:"status" xml:"status" form:"status"` |
||||
} |
||||
|
||||
func (r *CompanyUpsertRequest) GetID() any { |
||||
return r.ID |
||||
} |
||||
|
||||
func (r *CompanyUpsertRequest) ToEntity() *entities.Company { |
||||
return &entities.Company{ |
||||
ID: r.ID, |
||||
PrincipalID: &r.PrincipalID, |
||||
Name: r.Name, |
||||
Logo: r.Logo, |
||||
Status: r.Status, |
||||
Version: db.Version{Int64: 1, Valid: true}, |
||||
} |
||||
} |
||||
|
||||
func (r *CompanyUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": r.ID, |
||||
"principal_id": r.PrincipalID, |
||||
"name": r.Name, |
||||
"logo": r.Logo, |
||||
"status": r.Status, |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package company |
||||
|
||||
import ( |
||||
"sorbet/internal/services/company/controller" |
||||
"sorbet/pkg/app" |
||||
) |
||||
|
||||
type Service struct{} |
||||
|
||||
func (s *Service) Init(ctx *app.Context) error { |
||||
ctx.Routes(&controller.CompanyController{}) |
||||
ctx.Routes(&controller.CompanyDepartmentController{}) |
||||
ctx.Routes(&controller.CompanyStaffController{}) |
||||
return nil |
||||
} |
||||
|
||||
func (s *Service) Start() error { |
||||
return nil |
||||
} |
||||
|
||||
func (s *Service) Stop() error { |
||||
return nil |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/config/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type ConfigController struct { |
||||
util.Controller[entities.Config, request.ConfigUpsertRequest] |
||||
} |
||||
|
||||
func (ctr *ConfigController) InitRoutes(r *echo.Group) { |
||||
ctr.RegisterRoutes("/config", r) |
||||
} |
@ -0,0 +1,50 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
"sorbet/pkg/db" |
||||
) |
||||
|
||||
type ConfigUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
GroupID uint `json:"group_id" xml:"group_id" form:"group_id"` |
||||
Name string `json:"name" xml:"name" form:"name"` |
||||
Title string `json:"title" xml:"title" form:"title"` |
||||
Description string `json:"description" xml:"description" form:"description"` |
||||
DataType string `json:"data_type" xml:"data_type" form:"data_type"` |
||||
Attributes map[string]any `json:"attributes" xml:"attributes" form:"attributes"` |
||||
Value any `json:"value" xml:"value" form:"value"` |
||||
Sort int32 `json:"sort" xml:"sort" form:"sort"` |
||||
} |
||||
|
||||
func (c *ConfigUpsertRequest) GetID() any { |
||||
return c.ID |
||||
} |
||||
|
||||
func (c *ConfigUpsertRequest) ToEntity() *entities.Config { |
||||
return &entities.Config{ |
||||
ID: c.ID, |
||||
GroupID: c.GroupID, |
||||
Name: c.Name, |
||||
Title: c.Title, |
||||
Description: c.Description, |
||||
DataType: c.DataType, |
||||
Attributes: c.Attributes, |
||||
Value: c.Value, |
||||
Sort: c.Sort, |
||||
Version: db.Version{Int64: 1, Valid: true}, |
||||
} |
||||
} |
||||
|
||||
func (c *ConfigUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"group_id": c.GroupID, |
||||
"name": c.Name, |
||||
"title": c.Title, |
||||
"description": c.Description, |
||||
"data_type": c.DataType, |
||||
"attributes": c.Attributes, |
||||
"value": c.Value, |
||||
"sort": c.Sort, |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/feature/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type FeatureCategoryController struct { |
||||
util.Controller[entities.FeatureCategory, request.FeatureCategoryUpsertRequest] |
||||
} |
||||
|
||||
func (f *FeatureCategoryController) InitRoutes(r *echo.Group) { |
||||
f.RegisterRoutes("/feature/categories", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/feature/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type FeatureConfigController struct { |
||||
util.Controller[entities.FeatureConfig, request.FeatureConfigUpsertRequest] |
||||
} |
||||
|
||||
func (f *FeatureConfigController) InitRoutes(r *echo.Group) { |
||||
f.RegisterRoutes("/feature/configs", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/feature/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type FeatureContentChapterController struct { |
||||
util.Controller[entities.FeatureContentChapter, request.FeatureContentChapterUpsertRequest] |
||||
} |
||||
|
||||
func (f *FeatureContentChapterController) InitRoutes(r *echo.Group) { |
||||
f.RegisterRoutes("/feature/content/chapters", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/feature/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type FeatureContentController struct { |
||||
util.Controller[entities.FeatureContent, request.FeatureContentUpsertRequest] |
||||
} |
||||
|
||||
func (f *FeatureContentController) InitRoutes(r *echo.Group) { |
||||
f.RegisterRoutes("/feature/contents", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/feature/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type FeatureContentDetailController struct { |
||||
util.Controller[entities.FeatureContentDetail, request.FeatureContentDetailUpsertRequest] |
||||
} |
||||
|
||||
func (f *FeatureContentDetailController) InitRoutes(r *echo.Group) { |
||||
f.RegisterRoutes("/feature/content/detail", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/feature/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type FeatureController struct { |
||||
util.Controller[entities.Feature, request.FeatureUpsertRequest] |
||||
} |
||||
|
||||
func (f *FeatureController) InitRoutes(r *echo.Group) { |
||||
f.RegisterRoutes("/features", r) |
||||
} |
@ -0,0 +1,43 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type FeatureCategoryUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
PID uint `json:"pid" xml:"pid" form:"pid"` |
||||
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"` |
||||
Title string `json:"title" xml:"title" form:"title"` |
||||
Description string `json:"description" xml:"description" form:"description"` |
||||
Sort int32 `json:"sort" xml:"sort" form:"sort"` |
||||
Status bool `json:"status" xml:"status" form:"status"` |
||||
} |
||||
|
||||
func (f *FeatureCategoryUpsertRequest) GetID() any { |
||||
return f.ID |
||||
} |
||||
|
||||
func (f *FeatureCategoryUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": f.ID, |
||||
"pid": f.PID, |
||||
"feature_id": f.FeatureID, |
||||
"title": f.Title, |
||||
"description": f.Description, |
||||
"sort": f.Sort, |
||||
"status": f.Status, |
||||
} |
||||
} |
||||
|
||||
func (f *FeatureCategoryUpsertRequest) ToEntity() any { |
||||
return &entities.FeatureCategory{ |
||||
ID: f.ID, |
||||
PID: &f.PID, |
||||
FeatureID: f.FeatureID, |
||||
Title: f.Title, |
||||
Description: f.Description, |
||||
Sort: f.Sort, |
||||
Status: f.Status, |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type FeatureConfigUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"` |
||||
Status bool `json:"status" xml:"status" form:"status"` |
||||
Categorizable bool `json:"categorizable" xml:"categorizable" form:"categorizable"` |
||||
CategoryDepth int64 `json:"category_depth" xml:"category_depth" form:"category_depth"` |
||||
ContentTypes []string `json:"content_types" xml:"content_types" form:"content_types"` |
||||
} |
||||
|
||||
func (f *FeatureConfigUpsertRequest) GetID() any { |
||||
return f.ID |
||||
} |
||||
|
||||
func (f *FeatureConfigUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": f.ID, |
||||
"feature_id": f.FeatureID, |
||||
"status": f.Status, |
||||
"categorizable": f.Categorizable, |
||||
"category_depth": f.CategoryDepth, |
||||
"content_types": f.ContentTypes, |
||||
} |
||||
} |
||||
|
||||
func (f *FeatureConfigUpsertRequest) ToEntity() any { |
||||
return &entities.FeatureConfig{ |
||||
ID: f.ID, |
||||
FeatureID: f.FeatureID, |
||||
Status: f.Status, |
||||
Categorizable: f.Categorizable, |
||||
CategoryDepth: f.CategoryDepth, |
||||
ContentTypes: f.ContentTypes, |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type FeatureContentChapterUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
PID uint `json:"pid" xml:"pid" form:"pid"` |
||||
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"` |
||||
ContentID uint `json:"content_id" xml:"content_id" form:"content_id"` |
||||
Title string `json:"title" xml:"title" form:"title"` |
||||
Intro string `json:"intro" xml:"intro" form:"intro"` |
||||
Sort int32 `json:"sort" xml:"sort" form:"sort"` |
||||
} |
||||
|
||||
func (f *FeatureContentChapterUpsertRequest) GetID() any { |
||||
return f.ID |
||||
} |
||||
|
||||
func (f *FeatureContentChapterUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": f.ID, |
||||
"pid": f.PID, |
||||
"feature_id": f.FeatureID, |
||||
"content_id": f.ContentID, |
||||
"title": f.Title, |
||||
"intro": f.Intro, |
||||
"sort": f.Sort, |
||||
} |
||||
} |
||||
|
||||
func (f *FeatureContentChapterUpsertRequest) ToEntity() any { |
||||
return &entities.FeatureContentChapter{ |
||||
ID: f.ID, |
||||
PID: &f.PID, |
||||
FeatureID: f.FeatureID, |
||||
ContentID: f.ContentID, |
||||
Title: f.Title, |
||||
Intro: f.Intro, |
||||
Sort: f.Sort, |
||||
} |
||||
} |
@ -0,0 +1,55 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type FeatureContentDetailUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"` |
||||
ChapterID uint `json:"chapter_id" xml:"chapter_id" form:"chapter_id"` |
||||
ContentID uint `json:"content_id" xml:"content_id" form:"content_id"` |
||||
Type string `json:"type" xml:"type" form:"type"` |
||||
Title string `json:"title" xml:"title" form:"title"` |
||||
Intro string `json:"intro" xml:"intro" form:"intro"` |
||||
PosterUrl string `json:"poster_url" xml:"poster_url" form:"poster_url"` |
||||
VideoUrl string `json:"video_url" xml:"video_url" form:"video_url"` |
||||
Text string `json:"text" xml:"text" form:"text"` |
||||
Attributes map[string]any `json:"attributes" xml:"attributes" form:"attributes"` |
||||
} |
||||
|
||||
func (f *FeatureContentDetailUpsertRequest) GetID() any { |
||||
return f.ID |
||||
} |
||||
|
||||
func (f *FeatureContentDetailUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": f.ID, |
||||
"feature_id": f.FeatureID, |
||||
"chapter_id": f.ChapterID, |
||||
"content_id": f.ContentID, |
||||
"type": f.Type, |
||||
"title": f.Title, |
||||
"intro": f.Intro, |
||||
"poster_url": f.PosterUrl, |
||||
"video_url": f.VideoUrl, |
||||
"text": f.Text, |
||||
"attributes": f.Attributes, |
||||
} |
||||
} |
||||
|
||||
func (f *FeatureContentDetailUpsertRequest) ToEntity() any { |
||||
return &entities.FeatureContentDetail{ |
||||
ID: f.ID, |
||||
FeatureID: f.FeatureID, |
||||
ChapterID: &f.ChapterID, |
||||
ContentID: f.ContentID, |
||||
Type: f.Type, |
||||
Title: f.Title, |
||||
Intro: f.Intro, |
||||
PosterUrl: f.PosterUrl, |
||||
VideoUrl: f.VideoUrl, |
||||
Text: f.Text, |
||||
Attributes: f.Attributes, |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type FeatureContentUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"` |
||||
CategoryID *uint `json:"category_id" xml:"category_id" form:"category_id"` |
||||
Type string `json:"type" xml:"type" form:"type"` |
||||
Title string `json:"title" xml:"title" form:"title"` |
||||
Intro string `json:"intro" xml:"intro" form:"intro"` |
||||
} |
||||
|
||||
func (f *FeatureContentUpsertRequest) GetID() any { |
||||
return f.ID |
||||
} |
||||
|
||||
func (f *FeatureContentUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": f.ID, |
||||
"feature_id": f.FeatureID, |
||||
"category_id": f.CategoryID, |
||||
"type": f.Type, |
||||
"title": f.Title, |
||||
"intro": f.Intro, |
||||
} |
||||
} |
||||
|
||||
func (f *FeatureContentUpsertRequest) ToEntity() any { |
||||
return &entities.FeatureContent{ |
||||
ID: f.ID, |
||||
FeatureID: f.FeatureID, |
||||
CategoryID: f.CategoryID, |
||||
Type: f.Type, |
||||
Title: f.Title, |
||||
Intro: f.Intro, |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type FeatureUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
Title string `json:"title" xml:"title" form:"title"` |
||||
Intro string `json:"intro" xml:"intro" form:"intro"` |
||||
Icon string `json:"icon" xml:"icon" form:"icon"` |
||||
Sort int32 `json:"sort" xml:"sort" form:"sort"` |
||||
} |
||||
|
||||
func (f *FeatureUpsertRequest) GetID() any { |
||||
return f.ID |
||||
} |
||||
|
||||
func (f *FeatureUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": f.ID, |
||||
"title": f.Title, |
||||
"intro": f.Intro, |
||||
"icon": f.Icon, |
||||
"sort": f.Sort, |
||||
} |
||||
} |
||||
|
||||
func (f *FeatureUpsertRequest) ToEntity() any { |
||||
return &entities.Feature{ |
||||
ID: f.ID, |
||||
Title: f.Title, |
||||
Intro: f.Intro, |
||||
Icon: f.Icon, |
||||
Sort: f.Sort, |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
package feature |
||||
|
||||
import ( |
||||
"sorbet/internal/services/feature/controller" |
||||
"sorbet/pkg/app" |
||||
) |
||||
|
||||
type Service struct{} |
||||
|
||||
func (s *Service) Init(ctx *app.Context) error { |
||||
ctx.Routes(&controller.FeatureCategoryController{}) |
||||
ctx.Routes(&controller.FeatureConfigController{}) |
||||
ctx.Routes(&controller.FeatureContentChapterController{}) |
||||
ctx.Routes(&controller.FeatureContentDetailController{}) |
||||
ctx.Routes(&controller.FeatureController{}) |
||||
return nil |
||||
} |
||||
|
||||
func (s *Service) Start() error { |
||||
return nil |
||||
} |
||||
|
||||
func (s *Service) Stop() error { |
||||
return nil |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/resource/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type ResourceCategoryController struct { |
||||
util.Controller[entities.Resource, request.ResourceCategoryUpsertRequest] |
||||
} |
||||
|
||||
func (ctr *ResourceCategoryController) InitRoutes(r *echo.Group) { |
||||
ctr.RegisterRoutes("/resource/categories", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/resource/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type ResourceController struct { |
||||
util.Controller[entities.Resource, request.ResourceUpsertRequest] |
||||
} |
||||
|
||||
func (ctr *ResourceController) InitRoutes(r *echo.Group) { |
||||
ctr.RegisterRoutes("/resources", r) |
||||
} |
@ -0,0 +1,37 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type ResourceCategoryUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
PID uint `json:"pid" xml:"pid" form:"pid"` |
||||
Title string `json:"title" xml:"title" form:"title"` |
||||
Sort int32 `json:"sort" xml:"sort" form:"sort"` |
||||
Status bool `json:"status" xml:"status" form:"status"` |
||||
} |
||||
|
||||
func (r *ResourceCategoryUpsertRequest) GetID() any { |
||||
return r.ID |
||||
} |
||||
|
||||
func (r *ResourceCategoryUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": r.ID, |
||||
"pid": r.PID, |
||||
"title": r.Title, |
||||
"sort": r.Sort, |
||||
"status": r.Status, |
||||
} |
||||
} |
||||
|
||||
func (r *ResourceCategoryUpsertRequest) ToEntity() any { |
||||
return &entities.ResourceCategory{ |
||||
ID: r.ID, |
||||
PID: &r.PID, |
||||
Title: r.Title, |
||||
Sort: r.Sort, |
||||
Status: r.Status, |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type ResourceUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
CategoryID uint `json:"category_id" xml:"category_id" form:"category_id"` |
||||
Title string `json:"title" xml:"title" form:"title"` |
||||
Path string `json:"path" xml:"path" form:"path"` |
||||
Width int32 `json:"width" xml:"width" form:"width"` |
||||
Height int32 `json:"height" xml:"height" form:"height"` |
||||
Duration int32 `json:"duration" xml:"duration" form:"duration"` |
||||
MimeType string `json:"mime_type" xml:"mime_type" form:"mime_type"` |
||||
Extension string `json:"extension" xml:"extension" form:"extension"` |
||||
Size int64 `json:"size" xml:"size" form:"size"` |
||||
} |
||||
|
||||
func (r *ResourceUpsertRequest) GetID() any { |
||||
return r.ID |
||||
} |
||||
|
||||
func (r *ResourceUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": r.ID, |
||||
"category_id": r.CategoryID, |
||||
"title": r.Title, |
||||
"path": r.Path, |
||||
"width": r.Width, |
||||
"height": r.Height, |
||||
"duration": r.Duration, |
||||
"mime_type": r.MimeType, |
||||
"extension": r.Extension, |
||||
"size": r.Size, |
||||
} |
||||
} |
||||
|
||||
func (r *ResourceUpsertRequest) ToEntity() any { |
||||
return &entities.Resource{ |
||||
ID: r.ID, |
||||
CategoryID: r.CategoryID, |
||||
Title: r.Title, |
||||
Path: r.Path, |
||||
Width: r.Width, |
||||
Height: r.Height, |
||||
Duration: r.Duration, |
||||
MimeType: r.MimeType, |
||||
Extension: r.Extension, |
||||
Size: r.Size, |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
package resource |
||||
|
||||
import ( |
||||
"sorbet/internal/services/resource/controller" |
||||
"sorbet/pkg/app" |
||||
) |
||||
|
||||
type Service struct{} |
||||
|
||||
func (s *Service) Init(ctx *app.Context) error { |
||||
ctx.Routes(&controller.ResourceController{}) |
||||
ctx.Routes(&controller.ResourceCategoryController{}) |
||||
return nil |
||||
} |
||||
|
||||
func (s *Service) Start() error { |
||||
return nil |
||||
} |
||||
|
||||
func (s *Service) Stop() error { |
||||
return nil |
||||
} |
@ -0,0 +1,24 @@ |
||||
package services |
||||
|
||||
import ( |
||||
"sorbet/pkg/app" |
||||
) |
||||
|
||||
type Service struct { |
||||
inners []Service |
||||
} |
||||
|
||||
func (s Service) Init(ctx *app.Context) error { |
||||
//TODO implement me
|
||||
panic("implement me") |
||||
} |
||||
|
||||
func (s Service) Start() error { |
||||
//TODO implement me
|
||||
panic("implement me") |
||||
} |
||||
|
||||
func (s Service) Stop() error { |
||||
//TODO implement me
|
||||
panic("implement me") |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/system/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type SystemLogController struct { |
||||
util.Controller[entities.SystemLog, request.SystemLogUpsertRequest] |
||||
} |
||||
|
||||
func (s *SystemLogController) InitRoutes(r *echo.Group) { |
||||
s.RegisterRoutes("/system/logs", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/system/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type SystemMenuController struct { |
||||
util.Controller[entities.SystemMenu, request.SystemMenuUpsertRequest] |
||||
} |
||||
|
||||
func (s *SystemMenuController) InitRoutes(r *echo.Group) { |
||||
s.RegisterRoutes("/system/menus", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/system/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type SystemPermissionController struct { |
||||
util.Controller[entities.SystemPermission, request.SystemPermissionUpsertRequest] |
||||
} |
||||
|
||||
func (s *SystemPermissionController) InitRoutes(r *echo.Group) { |
||||
s.RegisterRoutes("/system/permissions", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/system/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type SystemRoleController struct { |
||||
util.Controller[entities.SystemRole, request.SystemRoleUpsertRequest] |
||||
} |
||||
|
||||
func (s *SystemRoleController) InitRoutes(r *echo.Group) { |
||||
s.RegisterRoutes("/system/roles", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/system/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type SystemRolePowerController struct { |
||||
util.Controller[entities.SystemRolePower, request.SystemRolePowerUpsertRequest] |
||||
} |
||||
|
||||
func (s *SystemRolePowerController) InitRoutes(r *echo.Group) { |
||||
s.RegisterRoutes("/system/role/powers", r) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/services/system/request" |
||||
"sorbet/internal/util" |
||||
) |
||||
|
||||
type SystemUserController struct { |
||||
util.Controller[entities.SystemUser, request.SystemUserUpsertRequest] |
||||
} |
||||
|
||||
func (s *SystemUserController) InitRoutes(r *echo.Group) { |
||||
s.RegisterRoutes("/system/users", r) |
||||
} |
@ -0,0 +1,55 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type SystemLogUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
Table string `json:"table" xml:"table" form:"table"` |
||||
RowID uint `json:"row_id" xml:"row_id" form:"row_id"` |
||||
Operation string `json:"operation" xml:"operation" form:"operation"` |
||||
IP string `json:"ip" xml:"ip" form:"ip"` |
||||
Comment string `json:"comment" xml:"comment" form:"comment"` |
||||
RequestID string `json:"request_id" xml:"request_id" form:"request_id"` |
||||
RequestInfo string `json:"request_info" xml:"request_info" form:"request_info"` |
||||
ColumnInfo string `json:"column_info" xml:"column_info" form:"column_info"` |
||||
UserID int64 `json:"user_id" xml:"user_id" form:"user_id"` |
||||
UserType int64 `json:"user_type" xml:"user_type" form:"user_type"` |
||||
} |
||||
|
||||
func (s *SystemLogUpsertRequest) GetID() any { |
||||
return s.ID |
||||
} |
||||
|
||||
func (s *SystemLogUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": s.ID, |
||||
"table": s.Table, |
||||
"row_id": s.RowID, |
||||
"operation": s.Operation, |
||||
"ip": s.IP, |
||||
"comment": s.Comment, |
||||
"request_id": s.RequestID, |
||||
"request_info": s.RequestInfo, |
||||
"column_info": s.ColumnInfo, |
||||
"user_id": s.UserID, |
||||
"user_type": s.UserType, |
||||
} |
||||
} |
||||
|
||||
func (s *SystemLogUpsertRequest) ToEntity() any { |
||||
return &entities.SystemLog{ |
||||
ID: s.ID, |
||||
Table: s.Table, |
||||
RowID: s.RowID, |
||||
Operation: s.Operation, |
||||
IP: s.IP, |
||||
Comment: s.Comment, |
||||
RequestID: s.RequestID, |
||||
RequestInfo: s.RequestInfo, |
||||
ColumnInfo: s.ColumnInfo, |
||||
UserID: s.UserID, |
||||
UserType: s.UserType, |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type SystemMenuUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
PID uint `json:"pid" xml:"pid" form:"pid"` |
||||
Title string `json:"title" xml:"title" form:"title"` |
||||
Icon string `json:"icon" xml:"icon" form:"icon"` |
||||
Sort int32 `json:"sort" xml:"sort" form:"sort"` |
||||
Path string `json:"path" xml:"path" form:"path"` |
||||
} |
||||
|
||||
func (s *SystemMenuUpsertRequest) GetID() any { |
||||
return s.ID |
||||
} |
||||
|
||||
func (s *SystemMenuUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": s.ID, |
||||
"pid": s.PID, |
||||
"title": s.Title, |
||||
"icon": s.Icon, |
||||
"sort": s.Sort, |
||||
"path": s.Path, |
||||
} |
||||
} |
||||
|
||||
func (s *SystemMenuUpsertRequest) ToEntity() any { |
||||
return &entities.SystemMenu{ |
||||
ID: s.ID, |
||||
PID: &s.PID, |
||||
Title: s.Title, |
||||
Icon: s.Icon, |
||||
Sort: s.Sort, |
||||
Path: s.Path, |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type SystemPermissionUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
PID uint `json:"pid" xml:"pid" form:"pid"` |
||||
Name string `json:"name" xml:"name" form:"name"` |
||||
Type string `json:"type" xml:"type" form:"type"` |
||||
Identifier string `json:"identifier" xml:"identifier" form:"identifier"` |
||||
} |
||||
|
||||
func (s *SystemPermissionUpsertRequest) GetID() any { |
||||
return s.ID |
||||
} |
||||
|
||||
func (s *SystemPermissionUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": s.ID, |
||||
"pid": s.PID, |
||||
"name": s.Name, |
||||
"type": s.Type, |
||||
"identifier": s.Identifier, |
||||
} |
||||
} |
||||
|
||||
func (s *SystemPermissionUpsertRequest) ToEntity() any { |
||||
return &entities.SystemPermission{ |
||||
ID: s.ID, |
||||
PID: &s.PID, |
||||
Name: s.Name, |
||||
Type: s.Type, |
||||
Identifier: s.Identifier, |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type SystemRolePowerUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
RoleID uint `json:"role_id" xml:"role_id" form:"role_id"` |
||||
WithType string `json:"with_type" xml:"with_type" form:"with_type"` |
||||
WithID uint `json:"with_id" xml:"with_id" form:"with_id"` |
||||
} |
||||
|
||||
func (s *SystemRolePowerUpsertRequest) GetID() any { |
||||
return s.ID |
||||
} |
||||
|
||||
func (s *SystemRolePowerUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": s.ID, |
||||
"role_id": s.RoleID, |
||||
"with_type": s.WithType, |
||||
"with_id": s.WithID, |
||||
} |
||||
} |
||||
|
||||
func (s *SystemRolePowerUpsertRequest) ToEntity() any { |
||||
return &entities.SystemRolePower{ |
||||
ID: s.ID, |
||||
RoleID: s.RoleID, |
||||
WithType: s.WithType, |
||||
WithID: s.WithID, |
||||
} |
||||
} |
@ -0,0 +1,28 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type SystemRoleUpsertRequest struct { |
||||
ID uint `json:"id" xml:"id" form:"id" path:"id"` |
||||
Name string `json:"name" xml:"name" form:"name"` |
||||
} |
||||
|
||||
func (s *SystemRoleUpsertRequest) GetID() any { |
||||
return s.ID |
||||
} |
||||
|
||||
func (s *SystemRoleUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": s.ID, |
||||
"name": s.Name, |
||||
} |
||||
} |
||||
|
||||
func (s *SystemRoleUpsertRequest) ToEntity() any { |
||||
return &entities.SystemRole{ |
||||
ID: s.ID, |
||||
Name: s.Name, |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"sorbet/internal/entities" |
||||
) |
||||
|
||||
type SystemUserUpsertRequest struct { |
||||
ID int64 `json:"id" xml:"id" form:"id" path:"id"` |
||||
Username string `json:"username" xml:"username" form:"username"` |
||||
Password string `json:"password" xml:"password" form:"password"` |
||||
Status bool `json:"status" xml:"status" form:"status"` |
||||
} |
||||
|
||||
func (s *SystemUserUpsertRequest) GetID() any { |
||||
return s.ID |
||||
} |
||||
|
||||
func (s *SystemUserUpsertRequest) ToMap() map[string]any { |
||||
return map[string]any{ |
||||
"id": s.ID, |
||||
"username": s.Username, |
||||
"password": s.Password, |
||||
"status": s.Status, |
||||
} |
||||
} |
||||
|
||||
func (s *SystemUserUpsertRequest) ToEntity() any { |
||||
return &entities.SystemUser{ |
||||
ID: s.ID, |
||||
Username: s.Username, |
||||
Password: s.Password, |
||||
Status: s.Status, |
||||
} |
||||
} |
@ -0,0 +1,26 @@ |
||||
package system |
||||
|
||||
import ( |
||||
"sorbet/internal/services/system/controller" |
||||
"sorbet/pkg/app" |
||||
) |
||||
|
||||
type Service struct{} |
||||
|
||||
func (s *Service) Init(ctx *app.Context) error { |
||||
ctx.Routes(&controller.SystemLogController{}) |
||||
ctx.Routes(&controller.SystemMenuController{}) |
||||
ctx.Routes(&controller.SystemPermissionController{}) |
||||
ctx.Routes(&controller.SystemRoleController{}) |
||||
ctx.Routes(&controller.SystemRolePowerController{}) |
||||
ctx.Routes(&controller.SystemUserController{}) |
||||
return nil |
||||
} |
||||
|
||||
func (s *Service) Start() error { |
||||
return nil |
||||
} |
||||
|
||||
func (s *Service) Stop() error { |
||||
return nil |
||||
} |
@ -0,0 +1,312 @@ |
||||
package util |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"gorm.io/gorm" |
||||
"net/url" |
||||
"reflect" |
||||
"sorbet/pkg/db" |
||||
"sorbet/pkg/ioc" |
||||
"sorbet/pkg/rsp" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
type GetID interface { |
||||
GetID() any |
||||
} |
||||
|
||||
type ToMap interface { |
||||
ToMap() map[string]any |
||||
} |
||||
|
||||
type ToEntity interface { |
||||
ToEntity() any |
||||
} |
||||
|
||||
type ControllerRequest interface { |
||||
GetID |
||||
ToMap |
||||
ToEntity |
||||
} |
||||
|
||||
func ParseQuery[T any](query url.Values, qb *db.QueryBuilder[T]) (page, limit int, err error) { |
||||
var paginating bool |
||||
for key, values := range query { |
||||
switch key { |
||||
case "sortby": |
||||
for _, s := range values { |
||||
if s[0] == '+' { |
||||
qb.AscentBy(s[1:]) |
||||
} else if s[0] == '-' { |
||||
qb.DescentBy(s[1:]) |
||||
} else { |
||||
qb.AscentBy(s) |
||||
} |
||||
} |
||||
case "limit", "page": |
||||
var v int |
||||
if values[0] != "" { |
||||
v, err = strconv.Atoi(values[0]) |
||||
if err != nil { |
||||
return |
||||
} |
||||
} |
||||
if v <= 0 { |
||||
return 0, 0, rsp.ErrInternal |
||||
} |
||||
if key == "limit" { |
||||
qb.Limit(v) |
||||
limit = v |
||||
} else { |
||||
paginating = true |
||||
page = max(v, 1) |
||||
} |
||||
default: |
||||
v := values[0] |
||||
i := strings.IndexByte(key, '#') |
||||
if i == -1 { |
||||
qb.Eq(key, v) |
||||
continue |
||||
} |
||||
switch k, op := key[:i], key[i+1:]; op { |
||||
case "=": |
||||
qb.Eq(k, v) |
||||
case "!=": |
||||
qb.Neq(k, v) |
||||
case "<": |
||||
qb.Lt(k, v) |
||||
case "<=": |
||||
qb.Lte(k, v) |
||||
case ">": |
||||
qb.Gt(k, v) |
||||
case ">=": |
||||
qb.Gte(k, v) |
||||
case "<>", "><": |
||||
var less, more any |
||||
switch len(values) { |
||||
case 2: |
||||
less, more = values[0], values[1] |
||||
case 1: |
||||
vs := strings.Split(v, ",") |
||||
if len(vs) != 2 || vs[0] == "" || vs[1] == "" { |
||||
return 0, 0, rsp.ErrBadParams |
||||
} |
||||
less, more = vs[0], vs[1] |
||||
} |
||||
if op == "<>" { |
||||
qb.Between(k, less, more) |
||||
} else { |
||||
qb.NotBetween(k, key, more) |
||||
} |
||||
case "nil": |
||||
qb.IsNull(k) |
||||
case "!nil": |
||||
qb.NotNull(k) |
||||
case "~": |
||||
qb.Like(k, v) |
||||
case "!~": |
||||
qb.NotLike(k, v) |
||||
case "in", "!in": |
||||
if len(values) == 1 { |
||||
values = strings.Split(v, ",") |
||||
} |
||||
vs := make([]any, len(values)) |
||||
for i, value := range values { |
||||
vs[i] = value |
||||
} |
||||
if op == "in" { |
||||
qb.In(k, vs...) |
||||
} else { |
||||
qb.NotIn(k, vs...) |
||||
} |
||||
default: |
||||
qb.Eq(key, v) |
||||
} |
||||
} |
||||
} |
||||
if paginating { |
||||
return |
||||
} |
||||
if limit == 0 { |
||||
limit = 30 |
||||
qb.Limit(limit) |
||||
} |
||||
qb.Offset((page - 1) * limit) |
||||
return |
||||
} |
||||
|
||||
func getID(req any) (val any) { |
||||
defer func() { |
||||
if recover() != nil { |
||||
val = nil |
||||
} |
||||
}() |
||||
val = req.(GetID).GetID() |
||||
rv := reflect.ValueOf(val) |
||||
if rv.IsZero() || rv.IsNil() { |
||||
val = nil |
||||
} |
||||
return |
||||
} |
||||
|
||||
func getValues(req any) map[string]any { |
||||
if v, ok := req.(ToMap); ok { |
||||
return v.ToMap() |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func getEntity[T any](request any) *T { |
||||
v, ok := request.(ToEntity) |
||||
if !ok { |
||||
return nil |
||||
} |
||||
ent, ok := v.ToEntity().(*T) |
||||
if ok { |
||||
return ent |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Controller 控制器基类
|
||||
//
|
||||
// 泛型 [Entity] 表示操作的具体数据;
|
||||
// 泛型 [Upsert] 表示创建或更新时需要的数据。
|
||||
type Controller[Entity any, Upsert any] struct{} |
||||
|
||||
func (ctr *Controller[Entity, Upsert]) RegisterRoutes(path string, r *echo.Group) { |
||||
r.PUT(path, ctr.Create) |
||||
r.DELETE(path+"/:id", ctr.Delete) |
||||
r.POST(path+"/:id", ctr.Update) |
||||
r.GET(path+"/:id", ctr.Get) |
||||
r.GET(path, ctr.List) |
||||
} |
||||
|
||||
func (ctr *Controller[Entity, Upsert]) ORM(c echo.Context) (*gorm.DB, error) { |
||||
orm, err := ioc.Get[gorm.DB]() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return orm.WithContext(c.Request().Context()), nil |
||||
} |
||||
|
||||
func (ctr *Controller[Entity, Upsert]) Repository() (*db.Repository[Entity], error) { |
||||
orm, err := ioc.Get[gorm.DB]() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return db.NewRepository[Entity](orm), nil |
||||
} |
||||
|
||||
// Create 创建数据
|
||||
func (ctr *Controller[Entity, Upsert]) Create(c echo.Context) error { |
||||
return ctr.upsert(c, true) |
||||
} |
||||
|
||||
// Update 更新数据
|
||||
func (ctr *Controller[Entity, Upsert]) Update(c echo.Context) error { |
||||
return ctr.upsert(c, false) |
||||
} |
||||
|
||||
func (ctr *Controller[Entity, Upsert]) upsert(c echo.Context, isCreate bool) error { |
||||
request, err := Bind[Upsert](c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
repo, err := ctr.Repository() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
id := getID(request) |
||||
if isCreate != reflect.ValueOf(id).IsZero() { |
||||
return rsp.BadParams(c, "参数错误") |
||||
} |
||||
// 更新数据
|
||||
if !isCreate { |
||||
values := getValues(request) |
||||
if values == nil { |
||||
return rsp.ErrInternal |
||||
} |
||||
err = repo.UpdateByID(c.Request().Context(), id, values) |
||||
if err == nil { |
||||
// TODO(hupeh): 返回更新后的实体数据
|
||||
return rsp.Ok(c, nil) |
||||
} |
||||
return err |
||||
} |
||||
// 创建数据
|
||||
group := getEntity[Entity](request) |
||||
if group == nil { |
||||
return rsp.ErrInternal |
||||
} |
||||
err = repo.Create(c.Request().Context(), group) |
||||
if err != nil { |
||||
return rsp.Created(c, group) |
||||
} |
||||
return err |
||||
} |
||||
|
||||
// Delete 通过ID删除数据
|
||||
func (ctr *Controller[Entity, Upsert]) Delete(c echo.Context) error { |
||||
id, err := BindId(c, true) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
repo, err := ctr.Repository() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
err = repo.DeleteByID(c.Request().Context(), id) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return rsp.Ok(c, nil) |
||||
} |
||||
|
||||
// Get 通过ID获取数据
|
||||
func (ctr *Controller[Entity, Upsert]) Get(c echo.Context) error { |
||||
id, err := BindId(c, true) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
repo, err := ctr.Repository() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
entity, err := repo.GetByID(c.Request().Context(), id) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return rsp.Ok(c, entity) |
||||
} |
||||
|
||||
// List 获取数据列表
|
||||
func (ctr *Controller[Entity, Upsert]) List(c echo.Context) error { |
||||
repo, err := ctr.Repository() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
qb := repo.NewQueryBuilder(c.Request().Context()) |
||||
_, _, err = ParseQuery[Entity](c.QueryParams(), qb) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// 不是分页查询
|
||||
if !c.QueryParams().Has("page") { |
||||
var result []*Entity |
||||
err = qb.Find(&result) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return rsp.Ok(c, result) |
||||
} |
||||
|
||||
// 分页查询
|
||||
pager, err := qb.Paginate() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return rsp.Ok(c, pager) |
||||
} |
@ -0,0 +1,43 @@ |
||||
package util |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"sorbet/pkg/rsp" |
||||
) |
||||
|
||||
// RequestGuarder 参数守卫函数签名
|
||||
type RequestGuarder[T any] func(c echo.Context, req *T) error |
||||
|
||||
// Bind 将提交的参数绑定到泛型 T 的实例上
|
||||
func Bind[T any](c echo.Context, guards ...RequestGuarder[T]) (*T, error) { |
||||
var req T |
||||
if err := c.Bind(&req); err != nil { |
||||
return nil, err |
||||
} |
||||
if err := c.Validate(&req); err != nil { |
||||
return nil, err |
||||
} |
||||
for _, guard := range guards { |
||||
if err := guard(c, &req); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
return &req, nil |
||||
} |
||||
|
||||
func BindId(c echo.Context, must bool) (uint, error) { |
||||
request, err := Bind[struct { |
||||
ID uint `json:"id" xml:"id" path:"id"` |
||||
}](c) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
if must { |
||||
if request.ID <= 0 { |
||||
return 0, rsp.ErrBadParams |
||||
} |
||||
} else if request.ID < 0 { |
||||
return 0, rsp.ErrBadParams |
||||
} |
||||
return request.ID, err |
||||
} |
@ -1,7 +0,0 @@ |
||||
package util |
||||
|
||||
import "github.com/labstack/echo/v4" |
||||
|
||||
type EchoContext struct { |
||||
echo.Context |
||||
} |
@ -1,88 +1,19 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"github.com/swaggo/echo-swagger" |
||||
"gorm.io/gorm" |
||||
"net/http" |
||||
_ "sorbet/docs" // 开发文档
|
||||
"log" |
||||
"sorbet/internal" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/middleware" |
||||
"sorbet/internal/repositories" |
||||
"sorbet/internal/util" |
||||
"sorbet/pkg/env" |
||||
"sorbet/pkg/ioc" |
||||
"sorbet/pkg/rsp" |
||||
) |
||||
|
||||
// @title 博客系统
|
||||
// @version 1.0
|
||||
// @description 基于 Echo 框架的基本库
|
||||
//
|
||||
// @contact.name API Support
|
||||
// @contact.url http://www.swagger.io/support
|
||||
// @contact.email support@swagger.io
|
||||
//
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
// @accept json
|
||||
func main() { |
||||
if err := env.Init(); err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
if err := internal.Init(); err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
repositories.Init() |
||||
|
||||
e := echo.New() |
||||
e.HideBanner = true |
||||
e.HidePort = true |
||||
e.HTTPErrorHandler = func(err error, c echo.Context) { |
||||
if !c.Response().Committed { |
||||
http.Error(c.Response(), err.Error(), 500) |
||||
} |
||||
} |
||||
e.Logger = util.NewLogger() |
||||
e.Use(middleware.Recover()) |
||||
e.Use(middleware.CORS()) |
||||
e.Use(middleware.Logger) |
||||
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { |
||||
return func(c echo.Context) error { |
||||
db := ioc.MustGet[gorm.DB]().WithContext(c.Request().Context()) |
||||
ci := ioc.Fork() |
||||
ci.Bind(db) |
||||
c.Set("db", db) |
||||
c.Set("ioc", ci) |
||||
return next(c) |
||||
} |
||||
}) |
||||
e.GET("/swagger/*", echoSwagger.WrapHandler) |
||||
e.GET("/", func(c echo.Context) error { |
||||
repo := repositories.NewCompanyRepository(c.Get("db").(*gorm.DB)) |
||||
//err := c.Get("ioc").(*ioc.Container).Resolve(&repo)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//db := ioc.MustGet[gorm.DB]().WithContext(c.Request().Context())
|
||||
//ioc.Fork().Bind(db)
|
||||
//repo := ioc.MustGet[repositories.CompanyRepository]()
|
||||
repo.Create(c.Request().Context(), &entities.Company{Name: "海苔一诺"}) |
||||
pager, err := repo.Paginate(c.Request().Context()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return rsp.Ok(c, pager) |
||||
}) |
||||
e.Logger.Fatal(e.Start(":1323")) |
||||
} |
||||
|
||||
func panicIf(e error) { |
||||
if e != nil { |
||||
panic(e) |
||||
if err := internal.Start(); err != nil { |
||||
log.Panicln(err) |
||||
} |
||||
} |
||||
|
@ -1,67 +0,0 @@ |
||||
package logs |
||||
|
||||
import ( |
||||
"log/slog" |
||||
"time" |
||||
) |
||||
|
||||
type Attr = slog.Attr |
||||
|
||||
// String returns an Attr for a string value.
|
||||
func String(key, value string) Attr { |
||||
return slog.String(key, value) |
||||
} |
||||
|
||||
// Int64 returns an Attr for an int64.
|
||||
func Int64(key string, value int64) Attr { |
||||
return slog.Int64(key, value) |
||||
} |
||||
|
||||
// Int converts an int to an int64 and returns
|
||||
// an Attr with that value.
|
||||
func Int(key string, value int) Attr { |
||||
return slog.Int(key, value) |
||||
} |
||||
|
||||
// Uint64 returns an Attr for a uint64.
|
||||
func Uint64(key string, v uint64) Attr { |
||||
return slog.Uint64(key, v) |
||||
} |
||||
|
||||
// Float64 returns an Attr for a floating-point number.
|
||||
func Float64(key string, v float64) Attr { |
||||
return slog.Float64(key, v) |
||||
} |
||||
|
||||
// Bool returns an Attr for a bool.
|
||||
func Bool(key string, v bool) Attr { |
||||
return slog.Bool(key, v) |
||||
} |
||||
|
||||
// Time returns an Attr for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func Time(key string, v time.Time) Attr { |
||||
return slog.Time(key, v) |
||||
} |
||||
|
||||
// Duration returns an Attr for a time.Duration.
|
||||
func Duration(key string, v time.Duration) Attr { |
||||
return slog.Duration(key, v) |
||||
} |
||||
|
||||
// Group returns an Attr for a Group Instance.
|
||||
// The first argument is the key; the remaining arguments
|
||||
// are converted to Attrs as in [Logger.Log].
|
||||
//
|
||||
// Use Group to collect several key-value pairs under a single
|
||||
// key on a log line, or as the result of LogValue
|
||||
// in order to log a single value as multiple Attrs.
|
||||
func Group(key string, args ...any) Attr { |
||||
return slog.Group(key, args...) |
||||
} |
||||
|
||||
// Any returns an Attr for the supplied value.
|
||||
// See [AnyValue] for how values are treated.
|
||||
func Any(key string, value any) Attr { |
||||
return slog.Any(key, value) |
||||
} |
@ -1,44 +0,0 @@ |
||||
package logs |
||||
|
||||
import ( |
||||
"github.com/mattn/go-isatty" |
||||
"os" |
||||
) |
||||
|
||||
type Color string |
||||
|
||||
var ( |
||||
//fgBlack Color = "\x1b[30m"
|
||||
//fgWhiteItalic Color = "\x1b[37;3m"
|
||||
|
||||
FgRed Color = "\x1b[31m" |
||||
FgGreen Color = "\x1b[32m" |
||||
FgYellow Color = "\x1b[33m" |
||||
FgBlue Color = "\x1b[34m" |
||||
FgMagenta Color = "\x1b[35m" |
||||
FgCyan Color = "\x1b[36m" |
||||
FgWhite Color = "\x1b[37m" |
||||
FgHiBlack Color = "\x1b[90m" |
||||
fgGreenItalic Color = "\x1b[32;3m" |
||||
|
||||
// NoColor defines if the output is colorized or not. It's dynamically set to
|
||||
// false or true based on the stdout's file descriptor referring to a terminal
|
||||
// or not. It's also set to true if the NO_COLOR environment variable is
|
||||
// set (regardless of its value). This is a global option and affects all
|
||||
// colors. For more control over each Color block use the methods
|
||||
// DisableColor() individually.
|
||||
noColor = noColorIsSet() || os.Getenv("TERM") == "dumb" || |
||||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) |
||||
) |
||||
|
||||
// noColorIsSet returns true if the environment variable NO_COLOR is set to a non-empty string.
|
||||
func noColorIsSet() bool { |
||||
return os.Getenv("NO_COLOR") != "" |
||||
} |
||||
|
||||
func (c Color) Wrap(msg string) string { |
||||
if noColorIsSet() || noColor { |
||||
return msg |
||||
} |
||||
return string(c) + msg + "\x1b[0m" |
||||
} |
@ -1 +0,0 @@ |
||||
package logs |
@ -1,59 +0,0 @@ |
||||
package logs |
||||
|
||||
import ( |
||||
"log/slog" |
||||
) |
||||
|
||||
type Level int |
||||
|
||||
const ( |
||||
LevelTrace Level = iota |
||||
LevelDebug // 用于程序调试
|
||||
LevelInfo // 用于程序运行
|
||||
LevelWarn // 潜在错误或非预期结果
|
||||
LevelError // 发生错误,但不影响系统的继续运行
|
||||
LevelFatal |
||||
LevelSilent |
||||
) |
||||
|
||||
// 越界取近值
|
||||
func (l Level) real() Level { |
||||
return min(LevelSilent, max(l, LevelTrace)) |
||||
} |
||||
|
||||
// Level 实现 slog.Leveler 接口
|
||||
func (l Level) Level() slog.Level { |
||||
return slog.Level(16 - int(LevelSilent-l.real())*4) |
||||
} |
||||
|
||||
func (l Level) slog() slog.Leveler { |
||||
return l.Level() |
||||
} |
||||
|
||||
func (l Level) String() string { |
||||
switch l { |
||||
case LevelTrace: |
||||
return "TRACE" |
||||
case LevelDebug: |
||||
return "DEBUG" |
||||
case LevelInfo: |
||||
return "INFO" |
||||
case LevelWarn: |
||||
return "WARN" |
||||
case LevelError: |
||||
return "ERROR" |
||||
case LevelFatal: |
||||
return "FATAL" |
||||
case LevelSilent: |
||||
return "OFF" |
||||
} |
||||
if l < LevelTrace { |
||||
return "TRACE" |
||||
} else { |
||||
return "OFF" |
||||
} |
||||
} |
||||
|
||||
func parseSlogLevel(level slog.Level) Level { |
||||
return Level(level/4 + 2).real() |
||||
} |
@ -1,38 +0,0 @@ |
||||
package logs |
||||
|
||||
import ( |
||||
"io" |
||||
"time" |
||||
) |
||||
|
||||
var std = New(&Options{Level: LevelInfo}) |
||||
|
||||
func Default() Logger { return std } |
||||
|
||||
func SetFlags(flags int) { Default().SetFlags(flags) } |
||||
func Flags() int { return Default().Flags() } |
||||
func SetTimezone(loc *time.Location) { Default().SetTimezone(loc) } |
||||
func Timezone() *time.Location { return Default().Timezone() } |
||||
func SetLevel(level Level) { Default().SetLevel(level) } |
||||
func GetLevel() Level { return Default().Level() } |
||||
func SetPersistWriter(w io.Writer) { Default().SetPersistWriter(w) } |
||||
func SetWriter(w io.Writer) { Default().SetWriter(w) } |
||||
func With(args ...Attr) Logger { return Default().With(args...) } |
||||
func WithGroup(name string) Logger { return Default().WithGroup(name) } |
||||
func Enabled(level Level) bool { return Default().Enabled(level) } |
||||
func Log(level Level, msg string, args ...any) { Default().Log(level, msg, args...) } |
||||
func ForkLevel(level Level, msg string, args ...any) ChildLogger { |
||||
return Default().ForkLevel(level, msg, args...) |
||||
} |
||||
func Trace(msg string, args ...any) { Default().Trace(msg, args...) } |
||||
func ForkTrace(msg string, args ...any) ChildLogger { return Default().ForkTrace(msg, args...) } |
||||
func Debug(msg string, args ...any) { Default().Debug(msg, args...) } |
||||
func ForkDebug(msg string, args ...any) ChildLogger { return Default().ForkDebug(msg, args...) } |
||||
func Info(msg string, args ...any) { Default().Info(msg) } |
||||
func ForkInfo(msg string, args ...any) ChildLogger { return Default().ForkInfo(msg, args...) } |
||||
func Warn(msg string, args ...any) { Default().Warn(msg, args...) } |
||||
func ForkWarn(msg string, args ...any) ChildLogger { return Default().ForkWarn(msg, args...) } |
||||
func Error(msg string, args ...any) { Default().Error(msg, args...) } |
||||
func ForkError(msg string, args ...any) ChildLogger { return Default().ForkError(msg, args...) } |
||||
func Fatal(msg string, args ...any) { Default().Fatal(msg, args...) } |
||||
func ForkFatal(msg string, args ...any) ChildLogger { return Default().ForkFatal(msg, args...) } |
@ -1,512 +0,0 @@ |
||||
package logs |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"log" |
||||
"log/slog" |
||||
"os" |
||||
"runtime" |
||||
"strings" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
"unsafe" |
||||
) |
||||
|
||||
var ( |
||||
// 使用东八区时间
|
||||
// https://cloud.tencent.com/developer/article/1805859
|
||||
cstZone = time.FixedZone("CST", 8*3600) |
||||
childLoggerKey = "sorbet/log:ChildLogger" |
||||
) |
||||
|
||||
const ( |
||||
Ldate = 1 << iota |
||||
Ltime |
||||
Lmicroseconds |
||||
Llongfile |
||||
Lshortfile |
||||
Lfields |
||||
Lcolor |
||||
LstdFlags = Ltime | Lmicroseconds | Lfields | Lcolor |
||||
) |
||||
|
||||
type Logger interface { |
||||
SetFlags(flags int) |
||||
Flags() int |
||||
SetTimezone(loc *time.Location) |
||||
Timezone() *time.Location |
||||
SetLevel(level Level) |
||||
Level() Level |
||||
SetPersistWriter(w io.Writer) |
||||
SetWriter(w io.Writer) |
||||
With(args ...Attr) Logger |
||||
WithGroup(name string) Logger |
||||
Enabled(level Level) bool |
||||
Log(level Level, msg string, args ...any) |
||||
ForkLevel(level Level, msg string, args ...any) ChildLogger |
||||
Trace(msg string, args ...any) |
||||
ForkTrace(msg string, args ...any) ChildLogger |
||||
Debug(msg string, args ...any) |
||||
ForkDebug(msg string, args ...any) ChildLogger |
||||
Info(msg string, args ...any) |
||||
ForkInfo(msg string, args ...any) ChildLogger |
||||
Warn(msg string, args ...any) |
||||
ForkWarn(msg string, args ...any) ChildLogger |
||||
Error(msg string, args ...any) |
||||
ForkError(msg string, args ...any) ChildLogger |
||||
Fatal(msg string, args ...any) |
||||
ForkFatal(msg string, args ...any) ChildLogger |
||||
} |
||||
|
||||
type ChildLogger interface { |
||||
Print(msg string, args ...any) |
||||
Finish() |
||||
} |
||||
|
||||
type Options struct { |
||||
Flags int |
||||
Level Level |
||||
Timezone *time.Location |
||||
PersistWriter io.Writer |
||||
Writer io.Writer |
||||
} |
||||
|
||||
type logger struct { |
||||
parent *logger |
||||
isChild int32 |
||||
indent int32 |
||||
|
||||
level int32 |
||||
flags int32 |
||||
timezone unsafe.Pointer |
||||
|
||||
outMu *sync.Mutex |
||||
isPersistDiscard int32 |
||||
isDiscard int32 |
||||
persistWriter io.Writer |
||||
writer io.Writer |
||||
|
||||
handler slog.Handler |
||||
l *log.Logger |
||||
} |
||||
|
||||
func New(opts *Options) Logger { |
||||
if opts.Flags == 0 { |
||||
opts.Flags = LstdFlags |
||||
} |
||||
if opts.Timezone == nil { |
||||
opts.Timezone = cstZone |
||||
} |
||||
if opts.PersistWriter == nil { |
||||
opts.PersistWriter = io.Discard |
||||
} |
||||
if opts.Writer == nil { |
||||
opts.Writer = os.Stderr |
||||
} |
||||
|
||||
var l *logger |
||||
l = &logger{ |
||||
outMu: &sync.Mutex{}, |
||||
persistWriter: opts.PersistWriter, |
||||
writer: opts.Writer, |
||||
l: log.New(opts.Writer, "", 0), |
||||
} |
||||
|
||||
l.SetLevel(opts.Level) |
||||
l.SetFlags(opts.Flags) |
||||
l.SetTimezone(opts.Timezone) |
||||
l.SetPersistWriter(opts.PersistWriter) |
||||
l.SetWriter(opts.Writer) |
||||
|
||||
l.handler = slog.NewJSONHandler(opts.PersistWriter, &slog.HandlerOptions{ |
||||
AddSource: true, |
||||
Level: opts.Level, |
||||
ReplaceAttr: l.onAttr, |
||||
}) |
||||
|
||||
return l |
||||
} |
||||
|
||||
func (l *logger) SetFlags(flags int) { |
||||
atomic.StoreInt32(&l.flags, int32(flags)) |
||||
} |
||||
|
||||
func (l *logger) Flags() int { |
||||
return int(atomic.LoadInt32(&l.flags)) |
||||
} |
||||
|
||||
func (l *logger) SetTimezone(loc *time.Location) { |
||||
// FIXME(hupeh): 如何原子化储存结构体实例
|
||||
atomic.StorePointer(&l.timezone, unsafe.Pointer(loc)) |
||||
} |
||||
|
||||
func (l *logger) Timezone() *time.Location { |
||||
return (*time.Location)(atomic.LoadPointer(&l.timezone)) |
||||
} |
||||
|
||||
func (l *logger) SetLevel(level Level) { |
||||
atomic.StoreInt32(&l.level, int32(level)) |
||||
} |
||||
|
||||
func (l *logger) Level() Level { |
||||
return Level(int(atomic.LoadInt32(&l.level))) |
||||
} |
||||
|
||||
func (l *logger) SetPersistWriter(w io.Writer) { |
||||
l.outMu.Lock() |
||||
defer l.outMu.Unlock() |
||||
l.persistWriter = w |
||||
atomic.StoreInt32(&l.isPersistDiscard, discard(w)) |
||||
} |
||||
|
||||
func (l *logger) SetWriter(w io.Writer) { |
||||
l.outMu.Lock() |
||||
defer l.outMu.Unlock() |
||||
l.writer = w |
||||
atomic.StoreInt32(&l.isDiscard, discard(w)) |
||||
} |
||||
|
||||
func discard(w io.Writer) int32 { |
||||
if w == io.Discard { |
||||
return 1 |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
func (l *logger) onAttr(_ []string, a slog.Attr) slog.Attr { |
||||
switch a.Key { |
||||
case slog.LevelKey: |
||||
level := a.Value.Any().(slog.Level) |
||||
levelLabel := parseSlogLevel(level).String() |
||||
a.Value = slog.StringValue(levelLabel) |
||||
case slog.TimeKey: |
||||
t := a.Value.Any().(time.Time) |
||||
a.Value = slog.TimeValue(t.In(l.Timezone())) |
||||
case slog.SourceKey: |
||||
s := a.Value.Any().(*slog.Source) |
||||
var as []Attr |
||||
if s.Function != "" { |
||||
as = append(as, String("func", s.Function)) |
||||
} |
||||
if s.File != "" { |
||||
as = append(as, String("file", s.File)) |
||||
} |
||||
if s.Line != 0 { |
||||
as = append(as, Int("line", s.Line)) |
||||
} |
||||
a.Value = slog.GroupValue(as...) |
||||
} |
||||
return a |
||||
} |
||||
|
||||
func (l *logger) Handle(ctx context.Context, r slog.Record) error { |
||||
if atomic.LoadInt32(&l.isDiscard) == 0 { |
||||
child, ok := ctx.Value(childLoggerKey).(*childLogger) |
||||
indent, err := l.println(child, r) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if ok && indent > 0 { |
||||
atomic.StoreInt32(&child.indent, indent) |
||||
} |
||||
} |
||||
if atomic.LoadInt32(&l.isPersistDiscard) == 0 { |
||||
return l.handler.Handle(ctx, r) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (l *logger) println(child *childLogger, r slog.Record) (int32, error) { |
||||
var output string |
||||
var sep string |
||||
var indent int32 |
||||
|
||||
write := func(s string) { |
||||
if sep == "" { |
||||
sep = " " |
||||
} else { |
||||
output += sep |
||||
} |
||||
output += s |
||||
} |
||||
|
||||
flags := l.Flags() |
||||
colorful := flags&Lcolor != 0 |
||||
msg := r.Message |
||||
level := parseSlogLevel(r.Level) |
||||
levelStr := level.String() |
||||
withChild := child != nil |
||||
|
||||
if withChild { |
||||
indent = atomic.LoadInt32(&child.indent) |
||||
withChild = indent > 0 |
||||
} |
||||
|
||||
if withChild { |
||||
write(strings.Repeat(" ", int(indent))) |
||||
indent = 0 |
||||
} else { |
||||
if flags&(Ldate|Ltime|Lmicroseconds) != 0 { |
||||
t := r.Time.In(l.Timezone()) |
||||
if flags&Ldate != 0 { |
||||
write(t.Format("2006/01/02")) |
||||
} |
||||
if flags&(Ltime|Lmicroseconds) != 0 { |
||||
if flags&Lmicroseconds != 0 { |
||||
write(t.Format("15:04:05.000")) |
||||
} else { |
||||
write(t.Format("15:04:05")) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 保存缩进
|
||||
indent += int32(len(output) + len(levelStr) + 3) |
||||
|
||||
if colorful { |
||||
switch level { |
||||
case LevelDebug: |
||||
levelStr = FgCyan.Wrap(levelStr) |
||||
msg = FgCyan.Wrap(msg) |
||||
case LevelInfo: |
||||
levelStr = FgBlue.Wrap(levelStr) + " " |
||||
msg = FgBlue.Wrap(msg) |
||||
indent += 1 |
||||
case LevelWarn: |
||||
levelStr = FgYellow.Wrap(levelStr) + " " |
||||
msg = FgYellow.Wrap(msg) |
||||
indent += 1 |
||||
case LevelError: |
||||
levelStr = FgRed.Wrap(levelStr) |
||||
msg = FgRed.Wrap(msg) |
||||
case LevelFatal: |
||||
levelStr = FgMagenta.Wrap(levelStr) |
||||
msg = FgMagenta.Wrap(msg) |
||||
} |
||||
levelStr = FgHiBlack.Wrap("[") + levelStr + FgHiBlack.Wrap("]") |
||||
} else { |
||||
levelStr = "[" + r.Level.String() + "]" |
||||
} |
||||
|
||||
write(levelStr) |
||||
} |
||||
|
||||
write(msg) |
||||
|
||||
if flags&(Lshortfile|Llongfile) != 0 && r.PC > 0 { |
||||
var fileStr string |
||||
fs := runtime.CallersFrames([]uintptr{r.PC}) |
||||
f, _ := fs.Next() |
||||
file := f.File |
||||
if flags&Lshortfile != 0 { |
||||
i := strings.LastIndexAny(file, "\\/") |
||||
if i > -1 { |
||||
file = file[i+1:] |
||||
} |
||||
} |
||||
fileStr = fmt.Sprintf("%s:%s:%d", f.Function, file, f.Line) |
||||
if colorful { |
||||
fileStr = fgGreenItalic.Wrap(fileStr) |
||||
} |
||||
write(fileStr) |
||||
} |
||||
|
||||
if numAttrs := r.NumAttrs(); flags&Lfields != 0 && numAttrs > 0 { |
||||
fields := make(map[string]any, numAttrs) |
||||
r.Attrs(func(a Attr) bool { |
||||
fields[a.Key] = a.Value.Any() |
||||
return true |
||||
}) |
||||
b, err := json.Marshal(fields) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
fieldsStr := string(bytes.TrimSpace(b)) |
||||
if fieldsStr != "" { |
||||
if colorful { |
||||
fieldsStr = FgHiBlack.Wrap(fieldsStr) |
||||
} |
||||
write(fieldsStr) |
||||
} |
||||
} |
||||
|
||||
l.l.Println(output) |
||||
|
||||
return indent, nil |
||||
} |
||||
|
||||
func (l *logger) clone() *logger { |
||||
c := *l |
||||
return &c |
||||
} |
||||
|
||||
func (l *logger) With(args ...Attr) Logger { |
||||
if len(args) == 0 { |
||||
return l |
||||
} |
||||
c := l.clone() |
||||
c.handler = c.handler.WithAttrs(args) |
||||
return c |
||||
} |
||||
|
||||
func (l *logger) WithGroup(name string) Logger { |
||||
if name == "" { |
||||
return l |
||||
} |
||||
c := l.clone() |
||||
c.handler = c.handler.WithGroup(name) |
||||
return c |
||||
} |
||||
|
||||
func (l *logger) Enabled(level Level) bool { |
||||
return l.handler.Enabled(nil, level.slog().Level()) |
||||
} |
||||
|
||||
// Log logs at level.
|
||||
func (l *logger) Log(level Level, msg string, args ...any) { |
||||
l.log(nil, level, msg, args...) |
||||
} |
||||
|
||||
func (l *logger) ForkLevel(level Level, msg string, args ...any) ChildLogger { |
||||
c := &childLogger{ |
||||
parent: l, |
||||
level: level, |
||||
indent: 0, |
||||
records: make([]slog.Record, 0), |
||||
closed: make(chan struct{}), |
||||
} |
||||
c.Print(msg, args...) |
||||
return c |
||||
} |
||||
|
||||
// Trace logs at LevelTrace.
|
||||
func (l *logger) Trace(msg string, args ...any) { |
||||
l.log(nil, LevelTrace, msg, args...) |
||||
} |
||||
|
||||
func (l *logger) ForkTrace(msg string, args ...any) ChildLogger { |
||||
return l.ForkLevel(LevelTrace, msg, args...) |
||||
} |
||||
|
||||
// Debug logs at LevelDebug.
|
||||
func (l *logger) Debug(msg string, args ...any) { |
||||
l.log(nil, LevelDebug, msg, args...) |
||||
} |
||||
|
||||
func (l *logger) ForkDebug(msg string, args ...any) ChildLogger { |
||||
return l.ForkLevel(LevelDebug, msg, args...) |
||||
} |
||||
|
||||
// Info logs at LevelInfo.
|
||||
func (l *logger) Info(msg string, args ...any) { |
||||
l.log(nil, LevelInfo, msg, args...) |
||||
} |
||||
|
||||
func (l *logger) ForkInfo(msg string, args ...any) ChildLogger { |
||||
return l.ForkLevel(LevelInfo, msg, args...) |
||||
} |
||||
|
||||
// Warn logs at LevelWarn.
|
||||
func (l *logger) Warn(msg string, args ...any) { |
||||
l.log(nil, LevelWarn, msg, args...) |
||||
} |
||||
|
||||
func (l *logger) ForkWarn(msg string, args ...any) ChildLogger { |
||||
return l.ForkLevel(LevelWarn, msg, args...) |
||||
} |
||||
|
||||
// Error logs at LevelError.
|
||||
func (l *logger) Error(msg string, args ...any) { |
||||
l.log(nil, LevelError, msg, args...) |
||||
} |
||||
|
||||
func (l *logger) ForkError(msg string, args ...any) ChildLogger { |
||||
return l.ForkLevel(LevelError, msg, args...) |
||||
} |
||||
|
||||
// Fatal logs at LevelFatal.
|
||||
func (l *logger) Fatal(msg string, args ...any) { |
||||
l.log(nil, LevelFatal, msg, args...) |
||||
} |
||||
|
||||
func (l *logger) ForkFatal(msg string, args ...any) ChildLogger { |
||||
return l.ForkLevel(LevelFatal, msg, args...) |
||||
} |
||||
|
||||
func (l *logger) log(ctx context.Context, level Level, msg string, args ...any) { |
||||
if !l.Enabled(level) { |
||||
return |
||||
} |
||||
if ctx == nil { |
||||
ctx = context.Background() |
||||
} |
||||
_ = l.Handle(ctx, newRecord(level, msg, args)) |
||||
} |
||||
|
||||
func newRecord(level Level, msg string, args []any) slog.Record { |
||||
//var pc uintptr
|
||||
//if !internal.IgnorePC {
|
||||
// var pcs [1]uintptr
|
||||
// // skip [runtime.Callers, this function, this function's caller]
|
||||
// runtime.Callers(3, pcs[:])
|
||||
// pc = pcs[0]
|
||||
//}
|
||||
//r := slog.NewRecord(time.Now(), level.slog().Level(), msg, pc)
|
||||
r := slog.NewRecord(time.Now(), level.slog().Level(), msg, 0) |
||||
if len(args) > 0 { |
||||
var sprintfArgs []any |
||||
for _, arg := range args { |
||||
switch v := arg.(type) { |
||||
case Attr: |
||||
r.AddAttrs(v) |
||||
default: |
||||
sprintfArgs = append(sprintfArgs, arg) |
||||
} |
||||
} |
||||
if len(sprintfArgs) > 0 { |
||||
r.Message = fmt.Sprintf(msg, sprintfArgs...) |
||||
} |
||||
} |
||||
return r |
||||
} |
||||
|
||||
type childLogger struct { |
||||
parent *logger |
||||
level Level |
||||
indent int32 |
||||
begin slog.Record |
||||
finish slog.Record |
||||
records []slog.Record |
||||
closed chan struct{} |
||||
} |
||||
|
||||
func (c *childLogger) Print(msg string, args ...any) { |
||||
select { |
||||
case <-c.closed: |
||||
default: |
||||
c.records = append( |
||||
c.records, |
||||
newRecord(c.level, msg, args), |
||||
) |
||||
} |
||||
} |
||||
|
||||
func (c *childLogger) Finish() { |
||||
select { |
||||
case <-c.closed: |
||||
return |
||||
default: |
||||
close(c.closed) |
||||
} |
||||
|
||||
ctx := context.Background() |
||||
ctx = context.WithValue(ctx, childLoggerKey, c) |
||||
for _, record := range c.records { |
||||
_ = c.parent.Handle(ctx, record) |
||||
} |
||||
} |
@ -0,0 +1,83 @@ |
||||
package rsp |
||||
|
||||
import ( |
||||
"github.com/labstack/echo/v4" |
||||
"net/http" |
||||
) |
||||
|
||||
// GuardFunc 参数守卫函数前面
|
||||
type GuardFunc[T any] func(c echo.Context, req *T) error |
||||
|
||||
// ServeFunc 参数处理函数签名
|
||||
type ServeFunc[T any, R any] func(c echo.Context, req *T) (*R, error) |
||||
|
||||
// ServeWithDataFunc 参数处理函数签名,支持自定义数据
|
||||
type ServeWithDataFunc[T any, R any, O any] func(c echo.Context, req *T, opt O) (*R, error) |
||||
|
||||
// RespondFunc 数据响应函数前面
|
||||
type RespondFunc[R any] func(c echo.Context, res *R) error |
||||
|
||||
// Handle 通用 CRUD 函数构造器,具体参数与函数 HandleWithData 保持一致
|
||||
func Handle[T any, R any](guard GuardFunc[T], serve ServeFunc[T, R], respond ...RespondFunc[R]) echo.HandlerFunc { |
||||
return HandleWithData[T, R, any](guard, func(c echo.Context, req *T, opt any) (*R, error) { |
||||
return serve(c, req) |
||||
}, nil, respond...) |
||||
} |
||||
|
||||
// HandleWithData 通用 CRUD 函数构造器,可以预置数据
|
||||
//
|
||||
// 参数 guard 可以为空值,表示无参数守卫;
|
||||
// 参数 serve 必传;
|
||||
// 参数 data 为自定义数据,该值最好不可被修改;
|
||||
// 参数 respond 为自定义响应函数,若未指定,内部将使用 Ok 或 Created 来响应结果。
|
||||
func HandleWithData[T any, R any, D any](guard GuardFunc[T], serve ServeWithDataFunc[T, R, D], data D, respond ...RespondFunc[R]) echo.HandlerFunc { |
||||
if serve == nil { |
||||
panic("miss ServeFunc") |
||||
} |
||||
return func(c echo.Context) error { |
||||
var req T |
||||
if err := c.Bind(&req); err != nil { |
||||
return err |
||||
} |
||||
if err := c.Validate(&req); err != nil { |
||||
return err |
||||
} |
||||
if guard != nil { |
||||
err := guard(c, &req) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
res, err := serve(c, &req, data) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
for _, send := range respond { |
||||
if send != nil { |
||||
return send(c, res) |
||||
} |
||||
} |
||||
// 我们认为凡是 PUT 请求,都是创建数据
|
||||
// 所以这里使用 Created 函数来响应数据。
|
||||
if c.Request().Method == http.MethodPut { |
||||
return Created(c, res) |
||||
} |
||||
return Ok(c, res) |
||||
} |
||||
} |
||||
|
||||
func Bind[T any](c echo.Context, guards ...GuardFunc[T]) (*T, error) { |
||||
var req T |
||||
if err := c.Bind(&req); err != nil { |
||||
return nil, err |
||||
} |
||||
if err := c.Validate(&req); err != nil { |
||||
return nil, err |
||||
} |
||||
for _, guard := range guards { |
||||
if err := guard(c, &req); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
return &req, nil |
||||
} |
Loading…
Reference in new issue