fix: align admin behavior and Kratos integrations
This commit is contained in:
+39
-37
@@ -1,13 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/conf"
|
||||
"kra/internal/server/handler"
|
||||
@@ -17,23 +17,16 @@ import (
|
||||
"kra/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
|
||||
)
|
||||
|
||||
// GinServer adapts Gin to the Kratos transport.Server lifecycle.
|
||||
type GinServer struct {
|
||||
network string
|
||||
address string
|
||||
server *http.Server
|
||||
listener net.Listener
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewGinServer(c *conf.Server, config *conf.AdminBackend, system *service.SystemService, access *service.AccessService, authority *handler.Authority, menu *handler.Menu, api *handler.API, permission *handler.Permission, organization *handler.Organization, announcement *handler.Announcement, email *handler.Email, task *handler.Task, media *handler.Media, auditHandler *handler.Audit, export *handler.Export, version *handler.Version, dictionary *handler.Dictionary, parameter *handler.Parameter, apiToken *handler.APIToken, systemConfig *handler.SystemConfig, publicHandler *handler.Public, user *handler.User, navigation *handler.Navigation, session *handler.Session, settings *service.SettingsService, audit *service.AuditService, emails *service.EmailService, logger *slog.Logger) *GinServer {
|
||||
func NewGinServer(c *conf.Server, runtime *conf.Runtime, system *service.SystemService, access *service.AccessService, authority *handler.Authority, menu *handler.Menu, api *handler.API, permission *handler.Permission, organization *handler.Organization, announcement *handler.Announcement, email *handler.Email, task *handler.Task, media *handler.Media, auditHandler *handler.Audit, export *handler.Export, version *handler.Version, dictionary *handler.Dictionary, parameter *handler.Parameter, apiToken *handler.APIToken, systemConfig *handler.SystemConfig, publicHandler *handler.Public, user *handler.User, navigation *handler.Navigation, session *handler.Session, settings *service.SettingsService, audit *service.AuditService, emails *service.EmailService, logger *slog.Logger) *kratoshttp.Server {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
engine := gin.New()
|
||||
engine.Use(servermiddleware.RequestMeta(), servermiddleware.EmailErrorAlert(emails, logger), gin.Recovery(), servermiddleware.SecurityRateLimit(system, settings), servermiddleware.OperationAudit(audit))
|
||||
|
||||
prefix := ""
|
||||
config := runtime.Admin()
|
||||
if config != nil {
|
||||
prefix = strings.TrimSuffix(config.RouterPrefix, "/")
|
||||
}
|
||||
@@ -42,13 +35,13 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, system *service.Sys
|
||||
serverrouter.RegisterPublic(public, engine, publicHandler)
|
||||
|
||||
private := engine.Group(prefix)
|
||||
private.Use(servermiddleware.Auth(config, settings), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(access, audit))
|
||||
private.Use(servermiddleware.Auth(runtime, settings), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(runtime, access, audit))
|
||||
serverrouter.RegisterUser(private, user)
|
||||
serverrouter.RegisterNavigation(private, navigation)
|
||||
serverrouter.RegisterSession(private, session)
|
||||
serverrouter.RegisterAuthority(private, authority)
|
||||
serverrouter.RegisterMenu(private, menu)
|
||||
serverrouter.RegisterAPI(private, engine, api)
|
||||
serverrouter.RegisterAPI(private, public, engine, api)
|
||||
serverrouter.RegisterPermission(private, permission)
|
||||
serverrouter.RegisterOrganization(private, organization)
|
||||
serverrouter.RegisterDictionary(private, dictionary)
|
||||
@@ -64,7 +57,7 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, system *service.Sys
|
||||
serverrouter.RegisterEmail(private, email)
|
||||
|
||||
engine.NoRoute(func(c *gin.Context) {
|
||||
if serveLocalStorage(c, config) {
|
||||
if serveLocalStorage(c, runtime) {
|
||||
return
|
||||
}
|
||||
httpx.Fail(c, "请求的接口不存在")
|
||||
@@ -79,12 +72,24 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, system *service.Sys
|
||||
address = c.Http.Addr
|
||||
}
|
||||
}
|
||||
return &GinServer{network: network, address: address, server: &http.Server{Addr: address, Handler: engine}, logger: logger}
|
||||
timeout := 10 * time.Minute
|
||||
if c != nil && c.Http != nil && c.Http.Timeout != nil && c.Http.Timeout.AsDuration() > 0 {
|
||||
timeout = c.Http.Timeout.AsDuration()
|
||||
}
|
||||
server := kratoshttp.NewServer(kratoshttp.Network(network), kratoshttp.Address(address), kratoshttp.Timeout(timeout))
|
||||
server.HandlePrefix("/", engine)
|
||||
server.Server.ReadHeaderTimeout = 10 * time.Second
|
||||
server.Server.ReadTimeout = timeout
|
||||
server.Server.WriteTimeout = timeout
|
||||
server.Server.IdleTimeout = 2 * time.Minute
|
||||
server.Server.MaxHeaderBytes = 1 << 20
|
||||
return server
|
||||
}
|
||||
|
||||
// serveLocalStorage resolves the local path for every request so a config
|
||||
// reload takes effect without rebuilding the Gin engine.
|
||||
func serveLocalStorage(c *gin.Context, config *conf.AdminBackend) bool {
|
||||
func serveLocalStorage(c *gin.Context, runtime *conf.Runtime) bool {
|
||||
config := runtime.Admin()
|
||||
if config == nil || config.Local == nil || config.Local.StorePath == "" {
|
||||
return false
|
||||
}
|
||||
@@ -95,10 +100,26 @@ func serveLocalStorage(c *gin.Context, config *conf.AdminBackend) bool {
|
||||
if prefix == "/" || (c.Request.URL.Path != prefix && !strings.HasPrefix(c.Request.URL.Path, prefix+"/")) {
|
||||
return false
|
||||
}
|
||||
c.Header("X-Content-Type-Options", "nosniff")
|
||||
filename := path.Base(c.Request.URL.Path)
|
||||
if !canServeUploadInline(filename) {
|
||||
c.Header("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename}))
|
||||
}
|
||||
http.StripPrefix(prefix, http.FileServer(filesOnly{FileSystem: http.Dir(config.Local.StorePath)})).ServeHTTP(c.Writer, c.Request)
|
||||
return true
|
||||
}
|
||||
|
||||
func canServeUploadInline(filename string) bool {
|
||||
switch strings.ToLower(path.Ext(filename)) {
|
||||
case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".ico", ".avif",
|
||||
".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac",
|
||||
".mp4", ".webm", ".mov", ".avi", ".mkv":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type filesOnly struct{ http.FileSystem }
|
||||
|
||||
func (f filesOnly) Open(name string) (http.File, error) {
|
||||
@@ -117,22 +138,3 @@ func (f filesOnly) Open(name string) (http.File, error) {
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (s *GinServer) Start(ctx context.Context) error {
|
||||
listener, err := net.Listen(s.network, s.address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.listener = listener
|
||||
s.server.BaseContext = func(net.Listener) context.Context { return ctx }
|
||||
s.logger.InfoContext(ctx, "Gin HTTP server listening", "addr", listener.Addr().String())
|
||||
if err := s.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GinServer) Stop(ctx context.Context) error {
|
||||
s.logger.InfoContext(ctx, "Gin HTTP server stopping")
|
||||
return s.server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ func (h *Announcement) Find(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Announcement) List(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
page, _ := strconv.Atoi(c.Query("page"))
|
||||
pageSize, _ := strconv.Atoi(c.Query("pageSize"))
|
||||
start, startErr := parseTime(c.Query("startCreatedAt"))
|
||||
end, endErr := parseTime(c.Query("endCreatedAt"))
|
||||
if startErr != nil || endErr != nil || (start == nil) != (end == nil) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/service"
|
||||
@@ -28,7 +28,7 @@ func (h *API) List(c *gin.Context) {
|
||||
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
|
||||
}
|
||||
func (h *API) All(c *gin.Context) {
|
||||
items, err := h.service.AllAPI(c.Request.Context())
|
||||
items, err := h.service.AllAPI(c.Request.Context(), true)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
@@ -41,11 +41,12 @@ func (h *API) Create(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.service.CreateAPIRequest(c.Request.Context(), &req); err != nil {
|
||||
created, err := h.service.CreateAPIRequest(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "创建失败")
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, created, "创建成功")
|
||||
}
|
||||
func (h *API) Update(c *gin.Context) {
|
||||
var req dto.APIRequest
|
||||
@@ -97,28 +98,40 @@ func (h *API) Find(c *gin.Context) {
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"api": item}, "获取成功")
|
||||
}
|
||||
func (h *API) Groups(c *gin.Context) {
|
||||
items, err := h.service.AllAPI(c.Request.Context())
|
||||
items, err := h.service.AllAPI(c.Request.Context(), false)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
groups := []string{}
|
||||
groupAPIMap := map[string]string{}
|
||||
for _, item := range items {
|
||||
if item.APIGroup != "" && !seen[item.APIGroup] {
|
||||
if !seen[item.APIGroup] {
|
||||
seen[item.APIGroup] = true
|
||||
groups = append(groups, item.APIGroup)
|
||||
}
|
||||
parts := strings.Split(item.Path, "/")
|
||||
if len(parts) > 1 {
|
||||
groupAPIMap[parts[1]] = item.APIGroup
|
||||
}
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"groups": groups}, "获取成功")
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"groups": groups, "apiGroupMap": groupAPIMap}, "获取成功")
|
||||
}
|
||||
func (h *API) Roles(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("apiId"), 10, 64)
|
||||
ids, err := h.service.APIRoleIDs(c.Request.Context(), uint(id))
|
||||
path, method := c.Query("path"), c.Query("method")
|
||||
if path == "" || method == "" {
|
||||
httpx.Fail(c, "API路径和请求方法不能为空")
|
||||
return
|
||||
}
|
||||
ids, err := h.service.APIRoleIDs(c.Request.Context(), path, method)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
if ids == nil {
|
||||
ids = []uint{}
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
|
||||
}
|
||||
func (h *API) SetRoles(c *gin.Context) {
|
||||
@@ -127,7 +140,11 @@ func (h *API) SetRoles(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.service.SetAPIRoles(c.Request.Context(), req.APIID, req.AuthorityIDs); err != nil {
|
||||
if req.Path == "" || req.Method == "" {
|
||||
httpx.Fail(c, "API路径和请求方法不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.service.SetAPIRoles(c.Request.Context(), req.Path, req.Method, req.AuthorityIDs); err != nil {
|
||||
httpx.Fail(c, "设置失败")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/service"
|
||||
@@ -15,8 +16,8 @@ type Audit struct{ service *service.AuditService }
|
||||
func NewAudit(service *service.AuditService) *Audit { return &Audit{service: service} }
|
||||
|
||||
func page(c *gin.Context) (int, int) {
|
||||
value, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
value, _ := strconv.Atoi(c.Query("page"))
|
||||
size, _ := strconv.Atoi(c.Query("pageSize"))
|
||||
return value, size
|
||||
}
|
||||
func IDsFromQuery(c *gin.Context) []uint {
|
||||
@@ -37,8 +38,7 @@ func IDsFromQuery(c *gin.Context) []uint {
|
||||
func (h *Audit) Operations(c *gin.Context) {
|
||||
p, size := page(c)
|
||||
status, _ := strconv.Atoi(c.Query("status"))
|
||||
userID, _ := strconv.ParseUint(c.Query("user_id"), 10, 64)
|
||||
items, total, err := h.service.OperationsFilter(c.Request.Context(), p, size, c.Query("path"), c.Query("method"), status, c.Query("ip"), uint(userID))
|
||||
items, total, err := h.service.OperationsFilter(c.Request.Context(), p, size, c.Query("path"), c.Query("method"), status)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
@@ -52,7 +52,7 @@ func (h *Audit) Operation(c *gin.Context) {
|
||||
httpx.Fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"resysOperationRecord": item}, "查询成功")
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"reSysOperationRecord": item}, "查询成功")
|
||||
}
|
||||
func (h *Audit) DeleteOperation(c *gin.Context) {
|
||||
var req dto.IDRequest
|
||||
@@ -81,7 +81,8 @@ func (h *Audit) DeleteOperations(c *gin.Context) {
|
||||
|
||||
func (h *Audit) Logins(c *gin.Context) {
|
||||
p, size := page(c)
|
||||
items, total, err := h.service.LoginsFilter(c.Request.Context(), p, size, c.Query("username"), c.Query("ip"))
|
||||
status, _ := strconv.ParseBool(c.Query("status"))
|
||||
items, total, err := h.service.LoginsFilter(c.Request.Context(), p, size, c.Query("username"), status)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
@@ -216,11 +217,20 @@ func (h *Audit) Error(c *gin.Context) {
|
||||
httpx.Fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"resysError": item}, "查询成功")
|
||||
httpx.Write(c, httpx.CodeSuccess, item, "查询成功")
|
||||
}
|
||||
func (h *Audit) Errors(c *gin.Context) {
|
||||
p, size := page(c)
|
||||
items, total, err := h.service.ErrorsFilter(c.Request.Context(), p, size, c.Query("form"), c.Query("level"), c.Query("status"))
|
||||
createdAtRange := make([]time.Time, 0, 2)
|
||||
for _, raw := range c.QueryArray("createdAtRange[]") {
|
||||
value, parseErr := time.Parse(time.RFC3339, raw)
|
||||
if parseErr != nil {
|
||||
httpx.Fail(c, parseErr.Error())
|
||||
return
|
||||
}
|
||||
createdAtRange = append(createdAtRange, value)
|
||||
}
|
||||
items, total, err := h.service.ErrorsFilter(c.Request.Context(), p, size, c.Query("form"), c.Query("info"), createdAtRange)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
|
||||
@@ -92,6 +92,9 @@ func (h *Authority) Users(c *gin.Context) {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
if ids == nil {
|
||||
ids = []uint{}
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
|
||||
}
|
||||
func (h *Authority) SetDataScope(c *gin.Context) {
|
||||
@@ -113,5 +116,8 @@ func (h *Authority) DataScopeDepartments(c *gin.Context) {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
if ids == nil {
|
||||
ids = []uint{}
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/service"
|
||||
"kra/internal/service/dto"
|
||||
@@ -54,27 +55,35 @@ func (h *Dictionary) Delete(c *gin.Context) {
|
||||
}
|
||||
func (h *Dictionary) Find(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := h.service.Dictionary(c.Request.Context(), uint(id), c.Query("type"), true)
|
||||
var status *bool
|
||||
if raw, exists := c.GetQuery("status"); exists {
|
||||
value, parseErr := strconv.ParseBool(raw)
|
||||
if parseErr != nil {
|
||||
httpx.Fail(c, parseErr.Error())
|
||||
return
|
||||
}
|
||||
status = &value
|
||||
}
|
||||
item, err := h.service.Dictionary(c.Request.Context(), uint(id), c.Query("type"), status, true)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"reSysDictionary": item}, "查询成功")
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"resysDictionary": item}, "查询成功")
|
||||
}
|
||||
func (h *Dictionary) List(details bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
p, size := page(c)
|
||||
items, total, err := h.service.Dictionaries(c.Request.Context(), p, size, c.Query("name"), c.Query("type"), details)
|
||||
items, _, err := h.service.Dictionaries(c.Request.Context(), 0, 0, c.Query("name"), "", details)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
|
||||
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
|
||||
}
|
||||
}
|
||||
func (h *Dictionary) Export(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := h.service.Dictionary(c.Request.Context(), uint(id), "", true)
|
||||
item, err := h.service.ExportDictionary(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
httpx.Fail(c, "导出失败")
|
||||
return
|
||||
@@ -83,6 +92,13 @@ func (h *Dictionary) Export(c *gin.Context) {
|
||||
delete(item, "CreatedAt")
|
||||
delete(item, "UpdatedAt")
|
||||
delete(item, "DeletedAt")
|
||||
if details, ok := item["sysDictionaryDetails"].([]map[string]any); ok {
|
||||
for _, detail := range details {
|
||||
for _, key := range []string{"ID", "CreatedAt", "UpdatedAt", "DeletedAt", "sysDictionaryID", "parentID", "disabled", "children"} {
|
||||
delete(detail, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, item, "导出成功")
|
||||
}
|
||||
func (h *Dictionary) Import(c *gin.Context) {
|
||||
@@ -146,7 +162,33 @@ func (h *Dictionary) FindDetail(c *gin.Context) {
|
||||
func (h *Dictionary) Details(c *gin.Context) {
|
||||
p, size := page(c)
|
||||
dictionaryID, _ := strconv.ParseUint(c.Query("sysDictionaryID"), 10, 64)
|
||||
items, total, err := h.service.DictionaryDetails(c.Request.Context(), p, size, uint(dictionaryID), c.Query("label"), c.Query("value"))
|
||||
filter := biz.DictionaryDetailFilter{DictionaryID: uint(dictionaryID), Label: c.Query("label"), Value: c.Query("value")}
|
||||
if raw, exists := c.GetQuery("status"); exists {
|
||||
value, parseErr := strconv.ParseBool(raw)
|
||||
if parseErr != nil {
|
||||
httpx.Fail(c, parseErr.Error())
|
||||
return
|
||||
}
|
||||
filter.Status = &value
|
||||
}
|
||||
if raw, exists := c.GetQuery("parentID"); exists {
|
||||
value, parseErr := strconv.ParseUint(raw, 10, 64)
|
||||
if parseErr != nil {
|
||||
httpx.Fail(c, parseErr.Error())
|
||||
return
|
||||
}
|
||||
parentID := uint(value)
|
||||
filter.ParentID = &parentID
|
||||
}
|
||||
if raw, exists := c.GetQuery("level"); exists {
|
||||
value, parseErr := strconv.Atoi(raw)
|
||||
if parseErr != nil {
|
||||
httpx.Fail(c, parseErr.Error())
|
||||
return
|
||||
}
|
||||
filter.Level = &value
|
||||
}
|
||||
items, total, err := h.service.DictionaryDetails(c.Request.Context(), p, size, filter)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
|
||||
@@ -14,7 +14,7 @@ func NewEmail(service *service.EmailService) *Email { return &Email{service: ser
|
||||
|
||||
func (h *Email) Test(c *gin.Context) {
|
||||
if err := h.service.Test(c.Request.Context()); err != nil {
|
||||
httpx.Fail(c, "发送失败:"+err.Error())
|
||||
httpx.Fail(c, "发送失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功")
|
||||
@@ -22,12 +22,12 @@ func (h *Email) Test(c *gin.Context) {
|
||||
|
||||
func (h *Email) Send(c *gin.Context) {
|
||||
var req dto.SendEmailRequest
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
httpx.Fail(c, "参数错误")
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httpx.Fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.service.Send(c.Request.Context(), req.To, req.Subject, req.Body); err != nil {
|
||||
httpx.Fail(c, "发送失败:"+err.Error())
|
||||
httpx.Fail(c, "发送失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功")
|
||||
|
||||
@@ -106,7 +106,13 @@ func (h *Export) Find(c *gin.Context) {
|
||||
}
|
||||
func (h *Export) List(c *gin.Context) {
|
||||
p, size := page(c)
|
||||
items, total, err := h.service.TemplatesFilter(c.Request.Context(), p, size, c.Query("name"), c.Query("tableName"), c.Query("templateID"))
|
||||
start, startErr := parseTime(c.Query("startCreatedAt"))
|
||||
end, endErr := parseTime(c.Query("endCreatedAt"))
|
||||
if startErr != nil || endErr != nil {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
items, total, err := h.service.TemplatesFilter(c.Request.Context(), p, size, c.Query("name"), c.Query("tableName"), c.Query("templateID"), start, end)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
|
||||
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/server/httpx"
|
||||
servermiddleware "kra/internal/server/middleware"
|
||||
"kra/internal/service"
|
||||
@@ -32,7 +33,7 @@ func (h *Media) Upload(c *gin.Context) {
|
||||
save := c.DefaultQuery("noSave", "0") == "0"
|
||||
item, err := h.service.Upload(c.Request.Context(), claims.ID, header.Filename, header.Header.Get("Content-Type"), category, opened, save)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "上传失败:"+err.Error())
|
||||
httpx.Fail(c, "上传文件失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"file": item}, "上传成功")
|
||||
@@ -44,7 +45,7 @@ func (h *Media) List(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
items, total, err := h.service.MediaList(c.Request.Context(), req.Page, req.PageSize, req.Keyword, req.ClassID, req.Tag, req.UserID)
|
||||
items, total, err := h.service.MediaList(c.Request.Context(), biz.MediaFilter{Page: req.Page, PageSize: req.PageSize, Keyword: req.Keyword, CategoryID: req.ClassID, Tag: req.Tag, UserID: req.UserID, StartCreatedAt: req.StartCreatedAt, EndCreatedAt: req.EndCreatedAt, OrderKey: req.OrderKey, Desc: req.Desc})
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
@@ -58,10 +59,10 @@ func (h *Media) Delete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.Delete(c.Request.Context(), req.ID); err != nil {
|
||||
httpx.Fail(c, "删除失败:"+err.Error())
|
||||
httpx.Fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
|
||||
}
|
||||
func (h *Media) DeleteMany(c *gin.Context) {
|
||||
var req dto.IDsRequest
|
||||
@@ -79,7 +80,7 @@ func (h *Media) DeleteMany(c *gin.Context) {
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"failedIds": failed}, "部分文件删除失败")
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
|
||||
}
|
||||
func (h *Media) Find(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
|
||||
@@ -88,7 +89,7 @@ func (h *Media) Find(c *gin.Context) {
|
||||
httpx.Fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
|
||||
httpx.OKWithData(c, item)
|
||||
}
|
||||
func (h *Media) Rename(c *gin.Context) {
|
||||
var req dto.RenameMediaRequest
|
||||
@@ -100,7 +101,7 @@ func (h *Media) Rename(c *gin.Context) {
|
||||
httpx.Fail(c, "编辑失败")
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "编辑成功")
|
||||
}
|
||||
func (h *Media) ImportURLs(c *gin.Context) {
|
||||
var req []dto.ImportMediaRequest
|
||||
@@ -108,16 +109,11 @@ func (h *Media) ImportURLs(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
claims := servermiddleware.Claims(c)
|
||||
if claims == nil {
|
||||
httpx.Fail(c, "未登录")
|
||||
if err := h.service.ImportURLRequests(c.Request.Context(), req); err != nil {
|
||||
httpx.Fail(c, "导入URL失败")
|
||||
return
|
||||
}
|
||||
if err := h.service.ImportURLRequests(c.Request.Context(), claims.ID, req); err != nil {
|
||||
httpx.Fail(c, "导入失败")
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入URL成功")
|
||||
}
|
||||
func (h *Media) Storage(c *gin.Context) {
|
||||
var req dto.StorageListRequest
|
||||
@@ -148,10 +144,10 @@ func (h *Media) SaveCategory(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.SaveCategoryRequest(c.Request.Context(), &req); err != nil {
|
||||
httpx.Fail(c, err.Error())
|
||||
httpx.Fail(c, "创建/更新失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建/更新成功")
|
||||
}
|
||||
func (h *Media) DeleteCategory(c *gin.Context) {
|
||||
var req dto.IDRequest
|
||||
@@ -160,10 +156,10 @@ func (h *Media) DeleteCategory(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteCategory(c.Request.Context(), req.ID); err != nil {
|
||||
httpx.Fail(c, err.Error())
|
||||
httpx.Fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
|
||||
}
|
||||
|
||||
func (h *Media) InitUpload(c *gin.Context) {
|
||||
|
||||
@@ -15,12 +15,12 @@ type Menu struct{ service *service.MenuService }
|
||||
func NewMenu(service *service.MenuService) *Menu { return &Menu{service: service} }
|
||||
|
||||
func (h *Menu) List(c *gin.Context) {
|
||||
items, err := h.service.List(c.Request.Context())
|
||||
items, err := h.service.Tree(c.Request.Context())
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: int64(len(items)), Page: 1, PageSize: 999}, "获取成功")
|
||||
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
|
||||
}
|
||||
|
||||
func (h *Menu) Tree(c *gin.Context) {
|
||||
@@ -119,7 +119,18 @@ func (h *Menu) RoleIDs(c *gin.Context) {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
|
||||
if ids == nil {
|
||||
ids = []uint{}
|
||||
}
|
||||
defaultIDs, err := h.service.DefaultRouterRoleIDs(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
if defaultIDs == nil {
|
||||
defaultIDs = []uint{}
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"authorityIds": ids, "defaultRouterAuthorityIds": defaultIDs}, "获取成功")
|
||||
}
|
||||
|
||||
func (h *Menu) SetRoles(c *gin.Context) {
|
||||
|
||||
@@ -28,27 +28,35 @@ func (h *Organization) ListDepartments(c *gin.Context) {
|
||||
}
|
||||
func (h *Organization) CreateDepartment(c *gin.Context) {
|
||||
var req dto.DepartmentRequest
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
httpx.Fail(c, "参数错误")
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httpx.Fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
if req.Name == "" {
|
||||
httpx.Fail(c, "部门名称不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.service.CreateDepartment(c.Request.Context(), &req); err != nil {
|
||||
httpx.Fail(c, "创建失败")
|
||||
httpx.Fail(c, "创建失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, nil, "创建成功")
|
||||
}
|
||||
func (h *Organization) UpdateDepartment(c *gin.Context) {
|
||||
var req dto.DepartmentRequest
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
httpx.Fail(c, "参数错误")
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httpx.Fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
if req.ID == 0 {
|
||||
httpx.Fail(c, "部门ID不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.service.UpdateDepartment(c.Request.Context(), &req); err != nil {
|
||||
httpx.Fail(c, "更新失败")
|
||||
httpx.Fail(c, "更新失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, nil, "更新成功")
|
||||
}
|
||||
func (h *Organization) DeleteDepartment(c *gin.Context) {
|
||||
var req dto.DeleteDepartmentRequest
|
||||
@@ -60,7 +68,7 @@ func (h *Organization) DeleteDepartment(c *gin.Context) {
|
||||
httpx.Fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, nil, "删除成功")
|
||||
}
|
||||
func (h *Organization) FindDepartment(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
|
||||
@@ -78,6 +86,9 @@ func (h *Organization) DepartmentUsers(c *gin.Context) {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
if ids == nil {
|
||||
ids = []uint{}
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
|
||||
}
|
||||
func (h *Organization) SetDepartmentUsers(c *gin.Context) {
|
||||
@@ -120,27 +131,35 @@ func (h *Organization) ListPositions(c *gin.Context) {
|
||||
}
|
||||
func (h *Organization) CreatePosition(c *gin.Context) {
|
||||
var req dto.PositionRequest
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
httpx.Fail(c, "参数错误")
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httpx.Fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
if req.Name == "" {
|
||||
httpx.Fail(c, "岗位名称不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.service.CreatePosition(c.Request.Context(), &req); err != nil {
|
||||
httpx.Fail(c, "创建失败")
|
||||
httpx.Fail(c, "创建失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, nil, "创建成功")
|
||||
}
|
||||
func (h *Organization) UpdatePosition(c *gin.Context) {
|
||||
var req dto.PositionRequest
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
httpx.Fail(c, "参数错误")
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httpx.Fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
if req.ID == 0 {
|
||||
httpx.Fail(c, "岗位ID不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.service.UpdatePosition(c.Request.Context(), &req); err != nil {
|
||||
httpx.Fail(c, "更新失败")
|
||||
httpx.Fail(c, "更新失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, nil, "更新成功")
|
||||
}
|
||||
func (h *Organization) DeletePosition(c *gin.Context) {
|
||||
var req dto.DeletePositionRequest
|
||||
@@ -149,10 +168,10 @@ func (h *Organization) DeletePosition(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.DeletePosition(c.Request.Context(), req.ID); err != nil {
|
||||
httpx.Fail(c, "删除失败")
|
||||
httpx.Fail(c, "删除失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, nil, "删除成功")
|
||||
}
|
||||
func (h *Organization) FindPosition(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
|
||||
@@ -170,6 +189,9 @@ func (h *Organization) PositionUsers(c *gin.Context) {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
if ids == nil {
|
||||
ids = []uint{}
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
|
||||
}
|
||||
func (h *Organization) SetPositionUsers(c *gin.Context) {
|
||||
|
||||
@@ -77,7 +77,7 @@ func (h *Parameter) Find(c *gin.Context) {
|
||||
httpx.Fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"resysParams": item}, "查询成功")
|
||||
httpx.Write(c, httpx.CodeSuccess, item, "查询成功")
|
||||
}
|
||||
|
||||
func (h *Parameter) Get(c *gin.Context) {
|
||||
@@ -91,7 +91,13 @@ func (h *Parameter) Get(c *gin.Context) {
|
||||
|
||||
func (h *Parameter) List(c *gin.Context) {
|
||||
p, size := page(c)
|
||||
items, total, err := h.service.ParametersFilter(c.Request.Context(), p, size, c.Query("name"), c.Query("key"))
|
||||
start, startErr := parseTime(c.Query("startCreatedAt"))
|
||||
end, endErr := parseTime(c.Query("endCreatedAt"))
|
||||
if startErr != nil || endErr != nil {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
items, total, err := h.service.ParametersFilter(c.Request.Context(), p, size, c.Query("name"), c.Query("key"), start, end)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
|
||||
@@ -2,11 +2,12 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/service"
|
||||
@@ -18,23 +19,22 @@ import (
|
||||
)
|
||||
|
||||
type Public struct {
|
||||
system *service.SystemService
|
||||
settings *service.SettingsService
|
||||
audit *service.AuditService
|
||||
scheduler *worker.TaskScheduler
|
||||
store *captchaStore
|
||||
keyLong int
|
||||
imageWidth int
|
||||
imageHeight int
|
||||
system *service.SystemService
|
||||
settings *service.SettingsService
|
||||
audit *service.AuditService
|
||||
scheduler *worker.TaskScheduler
|
||||
runtime *conf.Runtime
|
||||
store *captchaStore
|
||||
}
|
||||
|
||||
func NewPublic(config *conf.AdminBackend, system *service.SystemService, settings *service.SettingsService, audit *service.AuditService, scheduler *worker.TaskScheduler) *Public {
|
||||
expiration := 3 * time.Minute
|
||||
func NewPublic(runtime *conf.Runtime, system *service.SystemService, settings *service.SettingsService, audit *service.AuditService, scheduler *worker.TaskScheduler) *Public {
|
||||
return &Public{system: system, settings: settings, audit: audit, scheduler: scheduler, runtime: runtime, store: &captchaStore{service: system, runtime: runtime}}
|
||||
}
|
||||
|
||||
func (h *Public) captchaConfig() (int, int, int) {
|
||||
keyLong, width, height := 6, 240, 80
|
||||
config := h.runtime.Admin()
|
||||
if config != nil && config.Captcha != nil {
|
||||
if config.Captcha.StoreExpiration != nil {
|
||||
expiration = config.Captcha.StoreExpiration.AsDuration()
|
||||
}
|
||||
if config.Captcha.KeyLong > 0 {
|
||||
keyLong = int(config.Captcha.KeyLong)
|
||||
}
|
||||
@@ -45,23 +45,18 @@ func NewPublic(config *conf.AdminBackend, system *service.SystemService, setting
|
||||
height = int(config.Captcha.ImgHeight)
|
||||
}
|
||||
}
|
||||
return &Public{system: system, settings: settings, audit: audit, scheduler: scheduler, store: &captchaStore{service: system, expiration: expiration}, keyLong: keyLong, imageWidth: width, imageHeight: height}
|
||||
return keyLong, width, height
|
||||
}
|
||||
|
||||
func (h *Public) Captcha(c *gin.Context) {
|
||||
keyLong, width, height := h.keyLong, h.imageWidth, h.imageHeight
|
||||
keyLong, width, height := h.captchaConfig()
|
||||
security, _ := h.settings.CurrentSecurity(c.Request.Context())
|
||||
openCaptcha := true
|
||||
if security != nil {
|
||||
keyLong, width, height = security.KeyLong, security.ImgWidth, security.ImgHeight
|
||||
if security.CaptchaOpen > 0 {
|
||||
failures, _ := cachedInt(c.Request.Context(), h.system, "login:fail:"+c.ClientIP())
|
||||
openCaptcha = failures >= security.CaptchaOpen
|
||||
}
|
||||
}
|
||||
if !openCaptcha {
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"captchaId": "", "picPath": "", "captchaLength": keyLong, "openCaptcha": false}, "验证码获取成功")
|
||||
return
|
||||
ttl := time.Duration(security.CaptchaTimeout) * time.Second
|
||||
failures, _ := ensureLoginIPCounter(c.Request.Context(), h.system, c.ClientIP(), ttl)
|
||||
openCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen
|
||||
}
|
||||
driver := base64Captcha.NewDriverDigit(height, width, keyLong, 0.7, 80)
|
||||
id, picture, _, err := base64Captcha.NewCaptcha(driver, h.store).Generate()
|
||||
@@ -81,42 +76,71 @@ func (h *Public) Login(c *gin.Context) {
|
||||
security, _ := h.settings.CurrentSecurity(c.Request.Context())
|
||||
if security != nil && security.LockEnable {
|
||||
if _, locked, _ := h.system.CacheGet(c.Request.Context(), "login:lock:"+req.Username); locked {
|
||||
httpx.Fail(c, "账户已锁定,请稍后重试")
|
||||
httpx.Fail(c, "账号已锁定,请 "+strconv.Itoa(security.LockDuration)+" 分钟后再试")
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "账号已锁定", Agent: c.Request.UserAgent()})
|
||||
return
|
||||
}
|
||||
}
|
||||
requireCaptcha := security == nil || security.CaptchaOpen == 0
|
||||
if security != nil && security.CaptchaOpen > 0 {
|
||||
failures, _ := cachedInt(c.Request.Context(), h.system, "login:fail:"+c.ClientIP())
|
||||
requireCaptcha = failures >= security.CaptchaOpen
|
||||
ipTTL := time.Hour
|
||||
if security != nil {
|
||||
if security.CaptchaTimeout > 0 {
|
||||
ipTTL = time.Duration(security.CaptchaTimeout) * time.Second
|
||||
}
|
||||
failures, _ := ensureLoginIPCounter(c.Request.Context(), h.system, c.ClientIP(), ipTTL)
|
||||
requireCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen
|
||||
}
|
||||
if requireCaptcha && (req.CaptchaID == "" || req.Captcha == "" || !h.store.Verify(req.CaptchaID, req.Captcha, true)) {
|
||||
_, _ = h.system.CacheIncrement(c.Request.Context(), c.ClientIP(), ipTTL)
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "验证码错误", Agent: c.Request.UserAgent()})
|
||||
httpx.Fail(c, "验证码错误")
|
||||
return
|
||||
}
|
||||
result, err := h.system.Login(c.Request.Context(), req.Username, req.Password)
|
||||
if err != nil {
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "用户名不存在或者密码错误", Agent: c.Request.UserAgent()})
|
||||
ttl := time.Hour
|
||||
if security != nil && security.CaptchaTimeout > 0 {
|
||||
ttl = time.Duration(security.CaptchaTimeout) * time.Second
|
||||
_, _ = h.system.CacheIncrement(c.Request.Context(), c.ClientIP(), ipTTL)
|
||||
if errors.Is(err, biz.ErrUserDisabled) {
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "用户被禁止登录", Agent: c.Request.UserAgent()})
|
||||
httpx.Fail(c, "用户被禁止登录")
|
||||
return
|
||||
}
|
||||
failures, _ := incrementCached(c.Request.Context(), h.system, "login:fail:"+c.ClientIP(), ttl)
|
||||
if security != nil && security.LockEnable && failures >= security.LockThreshold {
|
||||
_ = h.system.CacheSet(c.Request.Context(), "login:lock:"+req.Username, "1", time.Duration(security.LockDuration)*time.Minute)
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "用户名不存在或者密码错误", Agent: c.Request.UserAgent()})
|
||||
if security != nil && security.LockEnable {
|
||||
lockTTL := time.Duration(security.LockDuration) * time.Minute
|
||||
failures, _ := h.system.CacheIncrement(c.Request.Context(), "login:fail:"+req.Username, lockTTL)
|
||||
if int(failures) >= security.LockThreshold {
|
||||
_ = h.system.CacheSet(c.Request.Context(), "login:lock:"+req.Username, "1", lockTTL)
|
||||
}
|
||||
}
|
||||
httpx.Fail(c, "用户名不存在或者密码错误")
|
||||
return
|
||||
}
|
||||
userID, _ := result.User["ID"].(uint)
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: true, Agent: c.Request.UserAgent(), UserID: userID})
|
||||
_ = h.system.CacheDelete(c.Request.Context(), "login:fail:"+c.ClientIP())
|
||||
_ = h.system.CacheDelete(c.Request.Context(), "login:fail:"+req.Username)
|
||||
_ = h.system.CacheDelete(c.Request.Context(), "login:lock:"+req.Username)
|
||||
maxAge := int(time.Until(time.UnixMilli(result.ExpiresAt)).Seconds())
|
||||
http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: result.Token, Path: "/", MaxAge: maxAge, HttpOnly: true, SameSite: http.SameSiteStrictMode})
|
||||
httpx.SetTokenCookie(c, result.Token, maxAge)
|
||||
httpx.Write(c, httpx.CodeSuccess, result, "登录成功")
|
||||
}
|
||||
|
||||
func ensureLoginIPCounter(ctx context.Context, system *service.SystemService, ip string, expiration time.Duration) (int, error) {
|
||||
value, exists, err := system.CacheGet(ctx, ip)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if exists {
|
||||
return strconv.Atoi(value)
|
||||
}
|
||||
if expiration <= 0 {
|
||||
expiration = time.Hour
|
||||
}
|
||||
if err = system.CacheSet(ctx, ip, "1", expiration); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (h *Public) CheckDatabase(c *gin.Context) {
|
||||
initialized, err := h.system.IsInitialized(c.Request.Context())
|
||||
if err != nil {
|
||||
@@ -163,27 +187,19 @@ func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func cachedInt(ctx context.Context, system *service.SystemService, key string) (int, error) {
|
||||
value, ok, err := system.CacheGet(ctx, key)
|
||||
if err != nil || !ok {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.Atoi(value)
|
||||
}
|
||||
|
||||
func incrementCached(ctx context.Context, system *service.SystemService, key string, ttl time.Duration) (int, error) {
|
||||
value, err := system.CacheIncrement(ctx, key, ttl)
|
||||
return int(value), err
|
||||
}
|
||||
|
||||
type captchaStore struct {
|
||||
service *service.SystemService
|
||||
expiration time.Duration
|
||||
service *service.SystemService
|
||||
runtime *conf.Runtime
|
||||
}
|
||||
|
||||
func (s *captchaStore) key(id string) string { return "captcha:" + id }
|
||||
func (s *captchaStore) Set(id, value string) error {
|
||||
return s.service.CacheSet(context.Background(), s.key(id), value, s.expiration)
|
||||
expiration := 3 * time.Minute
|
||||
config := s.runtime.Admin()
|
||||
if config != nil && config.Captcha != nil && config.Captcha.StoreExpiration != nil {
|
||||
expiration = config.Captcha.StoreExpiration.AsDuration()
|
||||
}
|
||||
return s.service.CacheSet(context.Background(), s.key(id), value, expiration)
|
||||
}
|
||||
func (s *captchaStore) Get(id string, clear bool) string {
|
||||
value, ok, err := s.service.CacheGet(context.Background(), s.key(id))
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/service"
|
||||
|
||||
@@ -21,6 +19,6 @@ func (h *Session) Logout(c *gin.Context) {
|
||||
httpx.Fail(c, "退出失败")
|
||||
return
|
||||
}
|
||||
http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: "", Path: "/", MaxAge: -1, HttpOnly: true})
|
||||
httpx.SetTokenCookie(c, "", -1)
|
||||
httpx.OK(c)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package handler
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"kra/internal/server/httpx"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
)
|
||||
|
||||
@@ -78,24 +78,31 @@ func (h *SystemConfig) Reload(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *SystemConfig) ServerInfo(c *gin.Context) {
|
||||
cpuPercent, _ := cpu.Percent(200*time.Millisecond, false)
|
||||
memory, _ := mem.VirtualMemory()
|
||||
disk := []gin.H{}
|
||||
var stat syscall.Statfs_t
|
||||
if syscall.Statfs(".", &stat) == nil {
|
||||
total := uint64(stat.Blocks) * uint64(stat.Bsize)
|
||||
free := uint64(stat.Bavail) * uint64(stat.Bsize)
|
||||
used := total - free
|
||||
percent := 0
|
||||
if total > 0 {
|
||||
percent = int(used * 100 / total)
|
||||
physicalCores, err := cpu.Counts(false)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
cpuPercent, err := cpu.Percent(200*time.Millisecond, true)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
memory, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
diskInfo := make([]gin.H, 0)
|
||||
for _, mountPoint := range h.system.DiskMountPoints() {
|
||||
usage, usageErr := disk.Usage(mountPoint)
|
||||
if usageErr != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
disk = append(disk, gin.H{"mountPoint": ".", "usedMb": used / 1024 / 1024, "usedGb": used / 1024 / 1024 / 1024, "totalMb": total / 1024 / 1024, "totalGb": total / 1024 / 1024 / 1024, "usedPercent": percent})
|
||||
diskInfo = append(diskInfo, gin.H{"mountPoint": mountPoint, "usedMb": usage.Used / 1024 / 1024, "usedGb": usage.Used / 1024 / 1024 / 1024, "totalMb": usage.Total / 1024 / 1024, "totalGb": usage.Total / 1024 / 1024 / 1024, "usedPercent": int(usage.UsedPercent)})
|
||||
}
|
||||
usedMB, totalMB, memoryPercent := uint64(0), uint64(0), float64(0)
|
||||
if memory != nil {
|
||||
usedMB, totalMB, memoryPercent = memory.Used/1024/1024, memory.Total/1024/1024, memory.UsedPercent
|
||||
}
|
||||
server := gin.H{"os": gin.H{"goos": runtime.GOOS, "numCpu": runtime.NumCPU(), "compiler": runtime.Compiler, "goVersion": runtime.Version(), "numGoroutine": runtime.NumGoroutine()}, "cpu": gin.H{"cpus": cpuPercent, "cores": runtime.NumCPU()}, "ram": gin.H{"usedMb": usedMB, "totalMb": totalMB, "usedPercent": memoryPercent}, "disk": disk}
|
||||
usedMB, totalMB, memoryPercent := memory.Used/1024/1024, memory.Total/1024/1024, int(memory.UsedPercent)
|
||||
server := gin.H{"os": gin.H{"goos": runtime.GOOS, "numCpu": runtime.NumCPU(), "compiler": runtime.Compiler, "goVersion": runtime.Version(), "numGoroutine": runtime.NumGoroutine()}, "cpu": gin.H{"cpus": cpuPercent, "cores": physicalCores}, "ram": gin.H{"usedMb": usedMB, "totalMb": totalMB, "usedPercent": memoryPercent}, "disk": diskInfo}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"server": server}, "获取成功")
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ func (h *Task) Trigger(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Task) List(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
page, _ := strconv.Atoi(c.Query("page"))
|
||||
size, _ := strconv.Atoi(c.Query("pageSize"))
|
||||
var enabled *bool
|
||||
if raw := c.Query("enabled"); raw != "" {
|
||||
value, _ := strconv.ParseBool(raw)
|
||||
@@ -119,8 +119,8 @@ func (h *Task) List(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Task) Logs(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
page, _ := strconv.Atoi(c.Query("page"))
|
||||
size, _ := strconv.Atoi(c.Query("pageSize"))
|
||||
taskID, _ := strconv.ParseUint(c.Query("taskId"), 10, 64)
|
||||
items, total, err := h.service.Logs(c.Request.Context(), page, size, uint(taskID), c.Query("status"))
|
||||
if err != nil {
|
||||
@@ -144,6 +144,8 @@ func (h *Task) AlertStream(c *gin.Context) {
|
||||
httpx.Fail(c, "SSE 不受支持")
|
||||
return
|
||||
}
|
||||
// Remove the normal server deadline for long-lived SSE connections.
|
||||
_ = http.NewResponseController(c.Writer).SetWriteDeadline(time.Time{})
|
||||
events := h.scheduler.Subscribe()
|
||||
defer h.scheduler.Unsubscribe(events)
|
||||
ticker := time.NewTicker(20 * time.Second)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -69,8 +68,8 @@ func (h *User) UpdateSelf(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
value := &dto.UserRequest{ID: claims.ID, NickName: req.NickName, HeaderImg: req.HeaderImg, Phone: req.Phone, Email: req.Email, Enable: 1}
|
||||
if err := h.service.UpdateUserRequest(c.Request.Context(), value); err != nil {
|
||||
value := service.UserInput{ID: claims.ID, NickName: req.NickName, HeaderImg: req.HeaderImg, Phone: req.Phone, Email: req.Email, Enable: req.Enable}
|
||||
if err := h.service.UpdateSelfUser(c.Request.Context(), value); err != nil {
|
||||
httpx.Fail(c, "修改失败")
|
||||
return
|
||||
}
|
||||
@@ -83,6 +82,11 @@ func (h *User) Delete(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
claims := middleware.Claims(c)
|
||||
if claims != nil && claims.ID == req.ID {
|
||||
httpx.Fail(c, "删除失败, 无法删除自己。")
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteUser(c.Request.Context(), req.ID); err != nil {
|
||||
httpx.Fail(c, "删除失败")
|
||||
return
|
||||
@@ -159,7 +163,7 @@ func (h *User) SwitchAuthority(c *gin.Context) {
|
||||
c.Header("new-token", login.Token)
|
||||
c.Header("new-expires-at", strconv.FormatInt(login.ExpiresAt/1000, 10))
|
||||
maxAge := int(time.Until(time.UnixMilli(login.ExpiresAt)).Seconds())
|
||||
http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: login.Token, Path: "/", MaxAge: maxAge, HttpOnly: true, SameSite: http.SameSiteStrictMode})
|
||||
httpx.SetTokenCookie(c, login.Token, maxAge)
|
||||
httpx.OK(c)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ func (h *Version) Delete(c *gin.Context) {
|
||||
httpx.Fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
|
||||
}
|
||||
func (h *Version) DeleteMany(c *gin.Context) {
|
||||
raw := c.QueryArray("IDs[]")
|
||||
@@ -36,7 +36,7 @@ func (h *Version) DeleteMany(c *gin.Context) {
|
||||
httpx.Fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
|
||||
}
|
||||
func (h *Version) Find(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
@@ -49,7 +49,16 @@ func (h *Version) Find(c *gin.Context) {
|
||||
}
|
||||
func (h *Version) List(c *gin.Context) {
|
||||
p, size := page(c)
|
||||
items, total, err := h.service.Versions(c.Request.Context(), p, size, c.Query("versionName"), c.Query("versionCode"))
|
||||
createdAtRange := make([]*time.Time, 0, 2)
|
||||
for _, raw := range c.QueryArray("createdAtRange[]") {
|
||||
value, parseErr := parseTime(raw)
|
||||
if parseErr != nil {
|
||||
httpx.Fail(c, parseErr.Error())
|
||||
return
|
||||
}
|
||||
createdAtRange = append(createdAtRange, value)
|
||||
}
|
||||
items, total, err := h.service.Versions(c.Request.Context(), p, size, c.Query("versionName"), c.Query("versionCode"), createdAtRange)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
@@ -66,7 +75,7 @@ func (h *Version) Export(c *gin.Context) {
|
||||
httpx.Fail(c, "创建发版失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建发版成功")
|
||||
}
|
||||
func (h *Version) Download(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
@@ -89,5 +98,5 @@ func (h *Version) Import(c *gin.Context) {
|
||||
httpx.Fail(c, "导入失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OK(c)
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入成功")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func requestUsesHTTPS(request *http.Request) bool {
|
||||
if request.TLS != nil {
|
||||
return true
|
||||
}
|
||||
forwarded := strings.SplitN(request.Header.Get("X-Forwarded-Proto"), ",", 2)[0]
|
||||
return strings.EqualFold(strings.TrimSpace(forwarded), "https")
|
||||
}
|
||||
|
||||
// SetTokenCookie also marks cookies Secure behind TLS or a TLS-terminating
|
||||
// reverse proxy.
|
||||
func SetTokenCookie(c *gin.Context, value string, maxAge int) {
|
||||
cookie := &http.Cookie{Name: "x-token", Value: value, Path: "/", MaxAge: maxAge, Secure: requestUsesHTTPS(c.Request), HttpOnly: true, SameSite: http.SameSiteStrictMode}
|
||||
if maxAge < 0 {
|
||||
cookie.Expires = time.Unix(1, 0)
|
||||
}
|
||||
http.SetCookie(c.Writer, cookie)
|
||||
}
|
||||
@@ -2,7 +2,10 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/service"
|
||||
"kra/internal/service/dto"
|
||||
@@ -10,7 +13,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AccessControl(access *service.AccessService, audit *service.AuditService) gin.HandlerFunc {
|
||||
func AccessControl(runtime *conf.Runtime, access *service.AccessService, audit *service.AuditService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
claims := Claims(c)
|
||||
if claims == nil {
|
||||
@@ -18,7 +21,14 @@ func AccessControl(access *service.AccessService, audit *service.AuditService) g
|
||||
return
|
||||
}
|
||||
path := c.Request.URL.Path
|
||||
allowed, err := access.Authorize(c.Request.Context(), claims.AuthorityID, path, c.Request.Method)
|
||||
policyPath := path
|
||||
if config := runtime.Admin(); config != nil && config.RouterPrefix != "" {
|
||||
policyPath = strings.TrimPrefix(policyPath, strings.TrimSuffix(config.RouterPrefix, "/"))
|
||||
if policyPath == "" {
|
||||
policyPath = "/"
|
||||
}
|
||||
}
|
||||
allowed, err := access.Authorize(c.Request.Context(), claims.AuthorityID, policyPath, c.Request.Method)
|
||||
if err != nil || !allowed {
|
||||
requestID, _ := c.Get("request_id")
|
||||
requestIDText, _ := requestID.(string)
|
||||
@@ -31,6 +41,7 @@ func AccessControl(access *service.AccessService, audit *service.AuditService) g
|
||||
httpx.Fail(c, "数据权限加载失败")
|
||||
return
|
||||
}
|
||||
requestContext = biz.NewActorContext(requestContext, biz.Actor{UserID: claims.ID, AuthorityID: claims.AuthorityID})
|
||||
c.Request = c.Request.WithContext(requestContext)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@ import (
|
||||
|
||||
const claimsKey = "admin_claims"
|
||||
|
||||
func Auth(config *conf.AdminBackend, settings *service.SettingsService) gin.HandlerFunc {
|
||||
func Auth(runtime *conf.Runtime, settings *service.SettingsService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.GetHeader("x-token")
|
||||
if token == "" {
|
||||
token, _ = c.Cookie("x-token")
|
||||
}
|
||||
secret := ""
|
||||
config := runtime.Admin()
|
||||
if config != nil && config.Jwt != nil {
|
||||
secret = config.Jwt.SigningKey
|
||||
}
|
||||
@@ -35,9 +36,14 @@ func Auth(config *conf.AdminBackend, settings *service.SettingsService) gin.Hand
|
||||
httpx.NoAuth(c, "登录状态已失效")
|
||||
return
|
||||
}
|
||||
if active, checkErr := settings.ActiveTokenMatches(c.Request.Context(), claims.Username, token); checkErr != nil || !active {
|
||||
httpx.NoAuth(c, "登录状态已失效")
|
||||
return
|
||||
}
|
||||
if claims.ExpiresAt != nil && claims.BufferTime > 0 && time.Until(claims.ExpiresAt.Time) < time.Duration(claims.BufferTime)*time.Second {
|
||||
expires, buffer := 7*24*time.Hour, time.Duration(claims.BufferTime)*time.Second
|
||||
issuer := claims.Issuer
|
||||
config = runtime.Admin()
|
||||
if config != nil && config.Jwt != nil {
|
||||
if config.Jwt.ExpiresTime != nil {
|
||||
expires = config.Jwt.ExpiresTime.AsDuration()
|
||||
@@ -50,10 +56,10 @@ func Auth(config *conf.AdminBackend, settings *service.SettingsService) gin.Hand
|
||||
}
|
||||
}
|
||||
newToken, newClaims, refreshErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd)
|
||||
if refreshErr == nil {
|
||||
if refreshErr == nil && settings.RotateActiveToken(c.Request.Context(), claims.Username, token, newToken, expires) == nil {
|
||||
c.Header("new-token", newToken)
|
||||
c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10))
|
||||
http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: newToken, Path: "/", MaxAge: int(expires.Seconds()), HttpOnly: true, SameSite: http.SameSiteStrictMode})
|
||||
httpx.SetTokenCookie(c, newToken, int(expires.Seconds()))
|
||||
}
|
||||
}
|
||||
c.Set(claimsKey, claims)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"kra/internal/server/httpx"
|
||||
@@ -26,11 +24,11 @@ func SecurityRateLimit(system *service.SystemService, settings *service.Settings
|
||||
if window < 1 {
|
||||
window = 60
|
||||
}
|
||||
bucket := time.Now().Unix() / int64(window)
|
||||
key := "rate:" + c.ClientIP() + ":" + strconv.FormatInt(bucket, 10)
|
||||
count, cacheErr := system.CacheIncrement(c.Request.Context(), key, time.Duration(window+1)*time.Second)
|
||||
key := "KRA_SecLimit" + c.ClientIP() + c.FullPath()
|
||||
count, cacheErr := system.CacheIncrement(c.Request.Context(), key, time.Duration(window)*time.Second)
|
||||
if cacheErr == nil && int(count) > config.LimitCount {
|
||||
c.AbortWithStatusJSON(http.StatusTooManyRequests, httpx.Response{Code: httpx.CodeError, Data: nil, Msg: "请求过于频繁"})
|
||||
httpx.Fail(c, "请求太过频繁,请稍后再试")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func RegisterAPI(group *gin.RouterGroup, engine *gin.Engine, h *handler.API) {
|
||||
func RegisterAPI(group, public *gin.RouterGroup, engine *gin.Engine, h *handler.API) {
|
||||
api := group.Group("/api")
|
||||
api.POST("/getApiList", h.List)
|
||||
api.POST("/getAllApis", h.All)
|
||||
@@ -21,7 +21,7 @@ func RegisterAPI(group *gin.RouterGroup, engine *gin.Engine, h *handler.API) {
|
||||
api.GET("/syncApi", h.Sync(engine))
|
||||
api.POST("/ignoreApi", h.Ignore)
|
||||
api.POST("/enterSyncApi", h.ApplySync)
|
||||
api.GET("/freshCasbin", h.FreshCasbin)
|
||||
public.Group("/api").GET("/freshCasbin", h.FreshCasbin)
|
||||
casbin := group.Group("/casbin")
|
||||
casbin.POST("/updateCasbin", h.SetPolicyPaths)
|
||||
casbin.POST("/getPolicyPathByAuthorityId", h.PolicyPaths)
|
||||
|
||||
Reference in New Issue
Block a user