refactor: split backend transport and domain modules

This commit is contained in:
2026-08-14 01:04:21 +08:00
parent acba9624d7
commit e6775f0966
59 changed files with 2733 additions and 1963 deletions
-44
View File
@@ -1,44 +0,0 @@
package server
import (
"net/http"
"kra/internal/biz"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
func registerAccessRoutes(group *gin.RouterGroup, engine *gin.Engine, svc *service.AccessService) {
registerAuthorityRoutes(group, svc)
registerMenuRoutes(group, svc)
registerAPIRoutes(group, engine, svc)
registerPolicyRoutes(group, svc)
registerDepartmentRoutes(group, svc)
registerPositionRoutes(group, svc)
}
func accessControl(svc *service.AccessService, audit *service.AuditService) gin.HandlerFunc {
return func(c *gin.Context) {
claims := currentClaims(c)
if claims == nil {
noAuth(c, "未登录或非法访问")
return
}
path := c.Request.URL.Path
allowed, err := svc.Authorize(c.Request.Context(), claims.AuthorityID, path, c.Request.Method)
if err != nil || !allowed {
requestID, _ := c.Get("request_id")
_ = audit.RecordDataAccess(c.Request.Context(), &biz.DataAccessLog{EventType: "blocked_access", Operation: c.Request.Method, UserID: claims.ID, AuthorityID: claims.AuthorityID, RequestID: toString(requestID), Method: c.Request.Method, Path: c.Request.URL.Path, Detail: "Casbin policy denied the request"})
c.AbortWithStatusJSON(http.StatusForbidden, response{Code: codeError, Data: nil, Msg: "权限不足"})
return
}
scope, err := svc.ResolveDataScope(c.Request.Context(), claims.AuthorityID, claims.ID)
if err != nil {
fail(c, "数据权限加载失败")
return
}
c.Request = c.Request.WithContext(biz.NewDataScopeContext(c.Request.Context(), scope))
c.Next()
}
}
-127
View File
@@ -1,127 +0,0 @@
package server
import (
"encoding/json"
"strconv"
"time"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
type announcementRequest struct {
ID uint `json:"ID"`
Title string `json:"title"`
Content string `json:"content"`
UserID *uint `json:"userID"`
Attachments json.RawMessage `json:"attachments"`
}
func announcementInput(req announcementRequest) service.AnnouncementInput {
return service.AnnouncementInput{ID: req.ID, Title: req.Title, Content: req.Content, UserID: req.UserID, Attachments: req.Attachments}
}
func registerAnnouncementRoutes(private, public *gin.RouterGroup, svc *service.AnnouncementService) {
privateInfo := private.Group("/info")
privateInfo.POST("/createInfo", func(c *gin.Context) {
var req announcementRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.Create(c.Request.Context(), announcementInput(req)); err != nil {
fail(c, "创建失败:"+err.Error())
return
}
writeResult(c, codeSuccess, gin.H{}, "创建成功")
})
privateInfo.DELETE("/deleteInfo", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
if err := svc.Delete(c.Request.Context(), uint(id)); err != nil {
fail(c, "删除失败:"+err.Error())
return
}
writeResult(c, codeSuccess, gin.H{}, "删除成功")
})
privateInfo.DELETE("/deleteInfoByIds", func(c *gin.Context) {
values := c.QueryArray("IDs[]")
if len(values) == 0 {
values = c.QueryArray("IDs")
}
ids := make([]uint, 0, len(values))
for _, value := range values {
id, err := strconv.ParseUint(value, 10, 64)
if err != nil || id == 0 {
fail(c, "参数错误")
return
}
ids = append(ids, uint(id))
}
if err := svc.DeleteByIDs(c.Request.Context(), ids); err != nil {
fail(c, "批量删除失败:"+err.Error())
return
}
writeResult(c, codeSuccess, gin.H{}, "批量删除成功")
})
privateInfo.PUT("/updateInfo", func(c *gin.Context) {
var req announcementRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.Update(c.Request.Context(), announcementInput(req)); err != nil {
fail(c, "更新失败:"+err.Error())
return
}
writeResult(c, codeSuccess, gin.H{}, "更新成功")
})
privateInfo.GET("/findInfo", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := svc.Find(c.Request.Context(), uint(id))
if err != nil {
fail(c, "查询失败:"+err.Error())
return
}
okWithData(c, item)
})
privateInfo.GET("/getInfoList", func(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
start, startErr := parseAnnouncementTime(c.Query("startCreatedAt"))
end, endErr := parseAnnouncementTime(c.Query("endCreatedAt"))
if startErr != nil || endErr != nil || (start == nil) != (end == nil) {
fail(c, "创建日期范围不合法")
return
}
items, total, err := svc.List(c.Request.Context(), page, pageSize, start, end)
if err != nil {
fail(c, "获取失败:"+err.Error())
return
}
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功")
})
publicInfo := public.Group("/info")
publicInfo.GET("/getInfoDataSource", func(c *gin.Context) {
users, err := svc.UserOptions(c.Request.Context())
if err != nil {
fail(c, "查询失败:"+err.Error())
return
}
okWithData(c, gin.H{"userID": users})
})
publicInfo.GET("/getInfoPublic", func(c *gin.Context) {
writeResult(c, codeSuccess, gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功")
})
}
func parseAnnouncementTime(value string) (*time.Time, error) {
if value == "" {
return nil, nil
}
parsed, err := time.Parse(time.RFC3339, value)
if err != nil {
return nil, err
}
return &parsed, nil
}
-209
View File
@@ -1,209 +0,0 @@
package server
import (
"strconv"
"kra/internal/biz"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
func registerAPIRoutes(group *gin.RouterGroup, engine *gin.Engine, svc *service.AccessService) {
router := group.Group("/api")
router.POST("/getApiList", func(c *gin.Context) {
var req struct {
Page int `json:"page"`
PageSize int `json:"pageSize"`
Path string `json:"path"`
Description string `json:"description"`
APIGroup string `json:"apiGroup"`
Method string `json:"method"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
items, total, err := svc.APIs(c.Request.Context(), req.Page, req.PageSize, &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method})
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
})
router.POST("/getAllApis", func(c *gin.Context) {
items, _, err := svc.APIs(c.Request.Context(), 1, 10000, nil)
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, gin.H{"apis": items}, "获取成功")
})
type apiRequest struct {
ID uint `json:"ID"`
Path string `json:"path"`
Description string `json:"description"`
APIGroup string `json:"apiGroup"`
Method string `json:"method"`
}
router.POST("/createApi", func(c *gin.Context) {
var req apiRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.CreateAPI(c.Request.Context(), &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method}); err != nil {
fail(c, "创建失败")
return
}
ok(c)
})
router.POST("/updateApi", func(c *gin.Context) {
var req apiRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.UpdateAPI(c.Request.Context(), &biz.API{ID: req.ID, Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method}); err != nil {
fail(c, "更新失败")
return
}
ok(c)
})
router.POST("/deleteApi", func(c *gin.Context) {
var req struct {
ID uint `json:"ID"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.DeleteAPIs(c.Request.Context(), []uint{req.ID}); err != nil {
fail(c, "删除失败")
return
}
ok(c)
})
router.DELETE("/deleteApisByIds", func(c *gin.Context) {
var req struct {
IDs []uint `json:"ids"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.DeleteAPIs(c.Request.Context(), req.IDs); err != nil {
fail(c, "删除失败")
return
}
ok(c)
})
router.POST("/getApiById", func(c *gin.Context) {
var req struct {
ID uint `json:"id"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
item, err := svc.API(c.Request.Context(), req.ID)
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, gin.H{"api": item}, "获取成功")
})
router.GET("/getApiGroups", func(c *gin.Context) {
items, _, err := svc.APIs(c.Request.Context(), 1, 10000, nil)
if err != nil {
fail(c, "获取失败")
return
}
seen := map[string]bool{}
groups := []string{}
for _, item := range items {
value, _ := item["apiGroup"].(string)
if value != "" && !seen[value] {
seen[value] = true
groups = append(groups, value)
}
}
writeResult(c, codeSuccess, gin.H{"groups": groups}, "获取成功")
})
router.GET("/getApiRoles", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("apiId"), 10, 64)
ids, err := svc.APIRoleIDs(c.Request.Context(), uint(id))
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, ids, "获取成功")
})
router.POST("/setApiRoles", func(c *gin.Context) {
var req struct {
APIID uint `json:"apiId"`
AuthorityIDs []uint `json:"authorityIds"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.SetAPIRoles(c.Request.Context(), req.APIID, req.AuthorityIDs); err != nil {
fail(c, "设置失败")
return
}
ok(c)
})
router.GET("/syncApi", func(c *gin.Context) {
routes := engine.Routes()
items := make([]*biz.API, 0, len(routes))
for _, route := range routes {
items = append(items, &biz.API{Path: route.Path, Method: route.Method})
}
result, err := svc.SyncAPIs(c.Request.Context(), items)
if err != nil {
fail(c, "同步检查失败")
return
}
writeResult(c, codeSuccess, result, "获取成功")
})
router.POST("/ignoreApi", func(c *gin.Context) {
var req struct {
Path string `json:"path"`
Method string `json:"method"`
Flag bool `json:"flag"`
}
if c.ShouldBindJSON(&req) != nil || req.Path == "" || req.Method == "" {
fail(c, "参数错误")
return
}
if err := svc.SetAPIIgnored(c.Request.Context(), req.Path, req.Method, req.Flag); err != nil {
fail(c, "忽略设置失败")
return
}
ok(c)
})
router.POST("/enterSyncApi", func(c *gin.Context) {
var req struct {
NewAPIs []apiRequest `json:"newApis"`
DeleteAPIs []apiRequest `json:"deleteApis"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
toDomain := func(values []apiRequest) []*biz.API {
items := make([]*biz.API, 0, len(values))
for _, value := range values {
items = append(items, &biz.API{ID: value.ID, Path: value.Path, Description: value.Description, APIGroup: value.APIGroup, Method: value.Method})
}
return items
}
if err := svc.ApplyAPISync(c.Request.Context(), toDomain(req.NewAPIs), toDomain(req.DeleteAPIs)); err != nil {
fail(c, "同步失败")
return
}
ok(c)
})
router.GET("/freshCasbin", func(c *gin.Context) { ok(c) })
}
-134
View File
@@ -1,134 +0,0 @@
package server
import (
"strconv"
"kra/internal/biz"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
func registerAuthorityRoutes(group *gin.RouterGroup, svc *service.AccessService) {
router := group.Group("/authority")
router.POST("/getAuthorityList", func(c *gin.Context) {
items, err := svc.Authorities(c.Request.Context())
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, items, "获取成功")
})
type authorityRequest struct {
AuthorityID uint `json:"authorityId"`
AuthorityName string `json:"authorityName"`
ParentID *uint `json:"parentId"`
DataScope int `json:"dataScope"`
DefaultRouter string `json:"defaultRouter"`
}
router.POST("/createAuthority", func(c *gin.Context) {
var req authorityRequest
if c.ShouldBindJSON(&req) != nil || req.AuthorityID == 0 {
fail(c, "参数错误")
return
}
value := &biz.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter}
if err := svc.CreateAuthority(c.Request.Context(), value); err != nil {
fail(c, "创建失败:"+err.Error())
return
}
writeResult(c, codeSuccess, gin.H{"authority": gin.H{"authorityId": value.AuthorityID, "authorityName": value.AuthorityName, "parentId": value.ParentID, "dataScope": value.DataScope, "defaultRouter": value.DefaultRouter}}, "创建成功")
})
router.POST("/copyAuthority", func(c *gin.Context) {
var req struct {
Authority authorityRequest `json:"authority"`
OldAuthorityID uint `json:"oldAuthorityId"`
}
if c.ShouldBindJSON(&req) != nil || req.Authority.AuthorityID == 0 || req.OldAuthorityID == 0 {
fail(c, "参数错误")
return
}
value := &biz.Authority{AuthorityID: req.Authority.AuthorityID, AuthorityName: req.Authority.AuthorityName, ParentID: req.Authority.ParentID, DataScope: req.Authority.DataScope, DefaultRouter: req.Authority.DefaultRouter}
if err := svc.CopyAuthority(c.Request.Context(), req.OldAuthorityID, value); err != nil {
fail(c, "拷贝失败:"+err.Error())
return
}
writeResult(c, codeSuccess, gin.H{"authority": gin.H{"authorityId": value.AuthorityID, "authorityName": value.AuthorityName, "parentId": value.ParentID, "dataScope": value.DataScope, "defaultRouter": value.DefaultRouter}}, "拷贝成功")
})
router.PUT("/updateAuthority", func(c *gin.Context) {
var req authorityRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
value := &biz.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter}
if err := svc.UpdateAuthority(c.Request.Context(), value); err != nil {
fail(c, "更新失败")
return
}
writeResult(c, codeSuccess, gin.H{"authority": gin.H{"authorityId": value.AuthorityID, "authorityName": value.AuthorityName, "parentId": value.ParentID, "dataScope": value.DataScope, "defaultRouter": value.DefaultRouter}}, "更新成功")
})
router.POST("/deleteAuthority", func(c *gin.Context) {
var req struct {
AuthorityID uint `json:"authorityId"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.DeleteAuthority(c.Request.Context(), req.AuthorityID); err != nil {
fail(c, err.Error())
return
}
ok(c)
})
router.POST("/setRoleUsers", func(c *gin.Context) {
var req struct {
AuthorityID uint `json:"authorityId"`
UserIDs []uint `json:"userIds"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.SetAuthorityUsers(c.Request.Context(), req.AuthorityID, req.UserIDs); err != nil {
fail(c, "设置失败")
return
}
ok(c)
})
router.GET("/getUsersByAuthority", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64)
ids, err := svc.AuthorityUserIDs(c.Request.Context(), uint(id))
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, ids, "获取成功")
})
router.POST("/setDataScope", func(c *gin.Context) {
var req struct {
AuthorityID uint `json:"authorityId"`
DataScope int `json:"dataScope"`
DepartmentIDs []uint `json:"deptIds"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.SetDataScope(c.Request.Context(), req.AuthorityID, req.DataScope, req.DepartmentIDs); err != nil {
fail(c, "设置失败")
return
}
ok(c)
})
router.GET("/getDataScopeDepts", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64)
ids, err := svc.DataScopeDepartmentIDs(c.Request.Context(), uint(id))
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, ids, "获取成功")
})
}
-27
View File
@@ -13,33 +13,6 @@ import (
"github.com/gin-gonic/gin"
)
func registerEmailRoutes(group *gin.RouterGroup, svc *service.EmailService) {
email := group.Group("/email")
email.POST("/emailTest", func(c *gin.Context) {
if err := svc.Test(c.Request.Context()); err != nil {
fail(c, "发送失败:"+err.Error())
return
}
writeResult(c, codeSuccess, gin.H{}, "发送成功")
})
email.POST("/sendEmail", func(c *gin.Context) {
var req struct {
To string `json:"to"`
Subject string `json:"subject"`
Body string `json:"body"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.Send(c.Request.Context(), req.To, req.Subject, req.Body); err != nil {
fail(c, "发送失败:"+err.Error())
return
}
writeResult(c, codeSuccess, gin.H{}, "发送成功")
})
}
func emailErrorAlert(svc *service.EmailService, logger *slog.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
if strings.Contains(c.Request.URL.Path, "/email/") {
+17 -87
View File
@@ -13,11 +13,14 @@ import (
"kra/internal/biz"
"kra/internal/conf"
"kra/internal/server/handler"
servermiddleware "kra/internal/server/middleware"
serverrouter "kra/internal/server/router"
"kra/internal/service"
"kra/internal/worker"
"kra/pkg/adminauth"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/mojocn/base64Captcha"
)
@@ -30,10 +33,10 @@ type GinServer struct {
logger *slog.Logger
}
func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.SystemService, access *service.AccessService, settings *service.SettingsService, versions *service.VersionService, exports *service.ExportService, audit *service.AuditService, tasks *service.TaskService, media *service.MediaService, announcements *service.AnnouncementService, emails *service.EmailService, scheduler *TaskScheduler, logger *slog.Logger) *GinServer {
func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *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, settings *service.SettingsService, versions *service.VersionService, exports *service.ExportService, audit *service.AuditService, media *service.MediaService, emails *service.EmailService, scheduler *worker.TaskScheduler, logger *slog.Logger) *GinServer {
gin.SetMode(gin.ReleaseMode)
engine := gin.New()
engine.Use(ginRequestMeta(), emailErrorAlert(emails, logger), gin.Recovery(), securityRateLimit(svc, settings), operationAudit(audit))
engine.Use(servermiddleware.RequestMeta(), emailErrorAlert(emails, logger), gin.Recovery(), securityRateLimit(svc, settings), operationAudit(audit))
prefix := ""
if config != nil {
prefix = strings.TrimSuffix(config.RouterPrefix, "/")
@@ -44,18 +47,22 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System
})
registerPublicRoutes(public, engine, config, svc, settings, access, audit, scheduler)
private := engine.Group(prefix)
private.Use(jwtAuth(config, settings), mustChangePasswordGuard(), accessControl(access, audit))
private.Use(servermiddleware.Auth(config, settings), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(access, audit))
registerPrivateRoutes(private, svc, settings)
registerAccessRoutes(private, engine, access)
serverrouter.RegisterAuthority(private, authority)
serverrouter.RegisterMenu(private, menu)
serverrouter.RegisterAPI(private, engine, api)
serverrouter.RegisterPermission(private, permission)
serverrouter.RegisterOrganization(private, organization)
registerSettingsRoutes(private, public, settings)
registerSystemConfigRoutes(private, config, svc, settings, scheduler)
registerVersionRoutes(private, versions)
registerExportRoutes(private, public, svc, exports)
registerAuditRoutes(private, public, audit)
registerTaskRoutes(private, tasks, scheduler)
serverrouter.RegisterTask(private, task)
registerMediaRoutes(private, media)
registerAnnouncementRoutes(private, public, announcements)
registerEmailRoutes(private, emails)
serverrouter.RegisterAnnouncement(private, public, announcement)
serverrouter.RegisterEmail(private, email)
engine.NoRoute(func(c *gin.Context) {
if serveLocalStorage(c, config) {
@@ -100,74 +107,9 @@ func serveLocalStorage(c *gin.Context, config *conf.AdminBackend) bool {
return true
}
func jwtAuth(config *conf.AdminBackend, settings *service.SettingsService) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("x-token")
if token == "" {
token, _ = c.Cookie("x-token")
}
secret := ""
if config != nil && config.Jwt != nil {
secret = config.Jwt.SigningKey
}
claims, err := adminauth.Parse(token, secret)
if err != nil {
noAuth(c, "未登录或非法访问")
return
}
if disabled, checkErr := settings.IsTokenDisabled(c.Request.Context(), token); checkErr != nil || disabled {
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
if config != nil && config.Jwt != nil {
if config.Jwt.ExpiresTime != nil {
expires = config.Jwt.ExpiresTime.AsDuration()
}
if config.Jwt.BufferTime != nil {
buffer = config.Jwt.BufferTime.AsDuration()
}
if config.Jwt.Issuer != "" {
issuer = config.Jwt.Issuer
}
}
newToken, newClaims, refreshErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd)
if refreshErr == 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})
}
}
c.Set("admin_claims", claims)
c.Next()
}
}
func currentClaims(c *gin.Context) *adminauth.Claims { return servermiddleware.Claims(c) }
func currentClaims(c *gin.Context) *adminauth.Claims {
claims, _ := c.Get("admin_claims")
result, _ := claims.(*adminauth.Claims)
return result
}
func mustChangePasswordGuard() gin.HandlerFunc {
return func(c *gin.Context) {
claims := currentClaims(c)
if claims == nil || !claims.MustChangePwd {
c.Next()
return
}
path := strings.TrimSuffix(c.Request.URL.Path, "/")
if strings.HasSuffix(path, "/user/changePassword") || strings.HasSuffix(path, "/user/getUserInfo") || strings.HasSuffix(path, "/jwt/jsonInBlacklist") {
c.Next()
return
}
c.AbortWithStatusJSON(http.StatusConflict, response{Code: codePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "请先修改初始密码"})
}
}
func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *conf.AdminBackend, svc *service.SystemService, settings *service.SettingsService, access *service.AccessService, audit *service.AuditService, scheduler *TaskScheduler) {
func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *conf.AdminBackend, svc *service.SystemService, settings *service.SettingsService, access *service.AccessService, audit *service.AuditService, scheduler *worker.TaskScheduler) {
expiration := 3 * time.Minute
keyLong, width, height := 6, 240, 80
if config != nil && config.Captcha != nil {
@@ -644,15 +586,3 @@ func (s *GinServer) Stop(ctx context.Context) error {
s.logger.InfoContext(ctx, "Gin HTTP server stopping")
return s.server.Shutdown(ctx)
}
func ginRequestMeta() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := c.GetHeader("X-Request-Id")
if requestID == "" {
requestID = uuid.NewString()
}
c.Header("X-Request-Id", requestID)
c.Set("request_id", requestID)
c.Next()
}
}
+129
View File
@@ -0,0 +1,129 @@
package handler
import (
"strconv"
"time"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Announcement struct{ service *service.AnnouncementService }
func NewAnnouncement(service *service.AnnouncementService) *Announcement {
return &Announcement{service: service}
}
func announcementInput(req dto.AnnouncementRequest) service.AnnouncementInput {
return service.AnnouncementInput{ID: req.ID, Title: req.Title, Content: req.Content, UserID: req.UserID, Attachments: req.Attachments}
}
func (h *Announcement) Create(c *gin.Context) {
var req dto.AnnouncementRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Create(c.Request.Context(), announcementInput(req)); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
}
func (h *Announcement) Delete(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
if err := h.service.Delete(c.Request.Context(), uint(id)); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
}
func (h *Announcement) DeleteByIDs(c *gin.Context) {
values := c.QueryArray("IDs[]")
if len(values) == 0 {
values = c.QueryArray("IDs")
}
ids := make([]uint, 0, len(values))
for _, value := range values {
id, err := strconv.ParseUint(value, 10, 64)
if err != nil || id == 0 {
httpx.Fail(c, "参数错误")
return
}
ids = append(ids, uint(id))
}
if err := h.service.DeleteByIDs(c.Request.Context(), ids); err != nil {
httpx.Fail(c, "批量删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Announcement) Update(c *gin.Context) {
var req dto.AnnouncementRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Update(c.Request.Context(), announcementInput(req)); err != nil {
httpx.Fail(c, "更新失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
}
func (h *Announcement) Find(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Find(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败:"+err.Error())
return
}
httpx.OKWithData(c, item)
}
func (h *Announcement) List(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
start, startErr := parseTime(c.Query("startCreatedAt"))
end, endErr := parseTime(c.Query("endCreatedAt"))
if startErr != nil || endErr != nil || (start == nil) != (end == nil) {
httpx.Fail(c, "创建日期范围不合法")
return
}
items, total, err := h.service.List(c.Request.Context(), page, pageSize, start, end)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功")
}
func (h *Announcement) DataSource(c *gin.Context) {
users, err := h.service.UserOptions(c.Request.Context())
if err != nil {
httpx.Fail(c, "查询失败:"+err.Error())
return
}
httpx.OKWithData(c, gin.H{"userID": users})
}
func (h *Announcement) Public(c *gin.Context) {
httpx.Write(c, httpx.CodeSuccess, gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功")
}
func parseTime(value string) (*time.Time, error) {
if value == "" {
return nil, nil
}
parsed, err := time.Parse(time.RFC3339, value)
if err != nil {
return nil, err
}
return &parsed, nil
}
+201
View File
@@ -0,0 +1,201 @@
package handler
import (
"strconv"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type API struct{ service *service.AccessService }
func NewAPI(service *service.AccessService) *API { return &API{service: service} }
func (h *API) List(c *gin.Context) {
var req dto.APIListRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
items, total, err := h.service.ListAPI(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
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())
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"apis": items}, "获取成功")
}
func (h *API) Create(c *gin.Context) {
var req dto.APIRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.CreateAPIRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败")
return
}
httpx.OK(c)
}
func (h *API) Update(c *gin.Context) {
var req dto.APIRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdateAPIRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
return
}
httpx.OK(c)
}
func (h *API) Delete(c *gin.Context) {
var req dto.DeleteAPIRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteAPIs(c.Request.Context(), []uint{req.ID}); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *API) DeleteByIDs(c *gin.Context) {
var req dto.DeleteAPIsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteAPIs(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *API) Find(c *gin.Context) {
var req dto.GetAPIRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
item, err := h.service.FindAPIResponse(c.Request.Context(), req.ID)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"api": item}, "获取成功")
}
func (h *API) Groups(c *gin.Context) {
items, err := h.service.AllAPI(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取失败")
return
}
seen := map[string]bool{}
groups := []string{}
for _, item := range items {
if item.APIGroup != "" && !seen[item.APIGroup] {
seen[item.APIGroup] = true
groups = append(groups, item.APIGroup)
}
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"groups": groups}, "获取成功")
}
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))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
}
func (h *API) SetRoles(c *gin.Context) {
var req dto.SetAPIRolesRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetAPIRoles(c.Request.Context(), req.APIID, req.AuthorityIDs); err != nil {
httpx.Fail(c, "设置失败")
return
}
httpx.OK(c)
}
func (h *API) Sync(engine *gin.Engine) gin.HandlerFunc {
return func(c *gin.Context) {
routes := engine.Routes()
values := make([]dto.APIRequest, 0, len(routes))
for _, route := range routes {
values = append(values, dto.APIRequest{Path: route.Path, Method: route.Method})
}
result, err := h.service.SyncAPIResponses(c.Request.Context(), values)
if err != nil {
httpx.Fail(c, "同步检查失败")
return
}
httpx.Write(c, httpx.CodeSuccess, result, "获取成功")
}
}
func (h *API) Ignore(c *gin.Context) {
var req dto.IgnoreAPIRequest
if c.ShouldBindJSON(&req) != nil || req.Path == "" || req.Method == "" {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetAPIIgnored(c.Request.Context(), req.Path, req.Method, req.Flag); err != nil {
httpx.Fail(c, "忽略设置失败")
return
}
httpx.OK(c)
}
func (h *API) ApplySync(c *gin.Context) {
var req dto.ApplyAPISyncRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.ApplyAPISyncRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "同步失败")
return
}
httpx.OK(c)
}
func (h *API) FreshCasbin(c *gin.Context) { httpx.OK(c) }
func (h *API) SetPolicyPaths(c *gin.Context) {
var req dto.SetPolicyPathsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetPolicyPathsRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
return
}
httpx.OK(c)
}
func (h *API) PolicyPaths(c *gin.Context) {
var req dto.GetPolicyPathsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
paths, err := h.service.PolicyPathResponses(c.Request.Context(), req.AuthorityID)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"paths": paths}, "获取成功")
}
+117
View File
@@ -0,0 +1,117 @@
package handler
import (
"strconv"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Authority struct{ service *service.AccessService }
func NewAuthority(service *service.AccessService) *Authority { return &Authority{service: service} }
func (h *Authority) List(c *gin.Context) {
items, err := h.service.Authorities(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
}
func (h *Authority) Create(c *gin.Context) {
var req dto.AuthorityRequest
if c.ShouldBindJSON(&req) != nil || req.AuthorityID == 0 {
httpx.Fail(c, "参数错误")
return
}
value, err := h.service.CreateAuthorityRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "创建成功")
}
func (h *Authority) Copy(c *gin.Context) {
var req dto.CopyAuthorityRequest
if c.ShouldBindJSON(&req) != nil || req.Authority.AuthorityID == 0 || req.OldAuthorityID == 0 {
httpx.Fail(c, "参数错误")
return
}
value, err := h.service.CopyAuthorityRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "拷贝失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "拷贝成功")
}
func (h *Authority) Update(c *gin.Context) {
var req dto.AuthorityRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
value, err := h.service.UpdateAuthorityRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "更新失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "更新成功")
}
func (h *Authority) Delete(c *gin.Context) {
var req dto.DeleteAuthorityRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteAuthority(c.Request.Context(), req.AuthorityID); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Authority) SetUsers(c *gin.Context) {
var req dto.SetRoleUsersRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetAuthorityUsers(c.Request.Context(), req.AuthorityID, req.UserIDs); err != nil {
httpx.Fail(c, "设置失败")
return
}
httpx.OK(c)
}
func (h *Authority) Users(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64)
ids, err := h.service.AuthorityUserIDs(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
}
func (h *Authority) SetDataScope(c *gin.Context) {
var req dto.SetDataScopeRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetDataScope(c.Request.Context(), req.AuthorityID, req.DataScope, req.DepartmentIDs); err != nil {
httpx.Fail(c, "设置失败")
return
}
httpx.OK(c)
}
func (h *Authority) DataScopeDepartments(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64)
ids, err := h.service.DataScopeDepartmentIDs(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
}
+34
View File
@@ -0,0 +1,34 @@
package handler
import (
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Email struct{ service *service.EmailService }
func NewEmail(service *service.EmailService) *Email { return &Email{service: service} }
func (h *Email) Test(c *gin.Context) {
if err := h.service.Test(c.Request.Context()); err != nil {
httpx.Fail(c, "发送失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功")
}
func (h *Email) Send(c *gin.Context) {
var req dto.SendEmailRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Send(c.Request.Context(), req.To, req.Subject, req.Body); err != nil {
httpx.Fail(c, "发送失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功")
}
+136
View File
@@ -0,0 +1,136 @@
package handler
import (
"strconv"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
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())
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: int64(len(items)), Page: 1, PageSize: 999}, "获取成功")
}
func (h *Menu) Tree(c *gin.Context) {
items, err := h.service.Tree(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": items}, "获取成功")
}
func (h *Menu) Create(c *gin.Context) {
var req dto.MenuRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Create(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "新增失败")
return
}
httpx.OK(c)
}
func (h *Menu) Update(c *gin.Context) {
var req dto.MenuRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Update(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
return
}
httpx.OK(c)
}
func (h *Menu) Delete(c *gin.Context) {
var req dto.DeleteMenuRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Delete(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Menu) Find(c *gin.Context) {
var req dto.GetMenuRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
item, err := h.service.Find(c.Request.Context(), req.ID)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"menu": item}, "获取成功")
}
func (h *Menu) SetAuthorityMenus(c *gin.Context) {
var req dto.SetAuthorityMenusRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetAuthorityMenus(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "添加失败")
return
}
httpx.OK(c)
}
func (h *Menu) AuthorityMenus(c *gin.Context) {
var req dto.GetAuthorityMenusRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
menus, err := h.service.AuthorityMenus(c.Request.Context(), req.AuthorityID)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": menus}, "获取成功")
}
func (h *Menu) RoleIDs(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("menuId"), 10, 64)
ids, err := h.service.RoleIDs(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
}
func (h *Menu) SetRoles(c *gin.Context) {
var req dto.SetMenuRolesRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetRoles(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败")
return
}
httpx.OK(c)
}
+198
View File
@@ -0,0 +1,198 @@
package handler
import (
"strconv"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Organization struct{ service *service.OrganizationService }
func NewOrganization(service *service.OrganizationService) *Organization {
return &Organization{service: service}
}
func (h *Organization) ListDepartments(c *gin.Context) {
var req dto.DepartmentListRequest
_ = c.ShouldBindJSON(&req)
items, err := h.service.Departments(c.Request.Context(), req.Name)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
}
func (h *Organization) CreateDepartment(c *gin.Context) {
var req dto.DepartmentRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.CreateDepartment(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败")
return
}
httpx.OK(c)
}
func (h *Organization) UpdateDepartment(c *gin.Context) {
var req dto.DepartmentRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdateDepartment(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
return
}
httpx.OK(c)
}
func (h *Organization) DeleteDepartment(c *gin.Context) {
var req dto.DeleteDepartmentRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteDepartment(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Organization) FindDepartment(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
item, err := h.service.Department(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
}
func (h *Organization) DepartmentUsers(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("deptId"), 10, 64)
ids, err := h.service.DepartmentUserIDs(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
}
func (h *Organization) SetDepartmentUsers(c *gin.Context) {
var req dto.SetDepartmentUsersRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetDepartmentUsers(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败")
return
}
httpx.OK(c)
}
func (h *Organization) SetUserDepartments(c *gin.Context) {
var req dto.SetUserDepartmentsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetUserDepartments(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败")
return
}
httpx.OK(c)
}
func (h *Organization) ListPositions(c *gin.Context) {
var req dto.PositionListRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
items, total, err := h.service.Positions(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Organization) CreatePosition(c *gin.Context) {
var req dto.PositionRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.CreatePosition(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败")
return
}
httpx.OK(c)
}
func (h *Organization) UpdatePosition(c *gin.Context) {
var req dto.PositionRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdatePosition(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
return
}
httpx.OK(c)
}
func (h *Organization) DeletePosition(c *gin.Context) {
var req dto.DeletePositionRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeletePosition(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Organization) FindPosition(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
item, err := h.service.Position(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
}
func (h *Organization) PositionUsers(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("positionId"), 10, 64)
ids, err := h.service.PositionUserIDs(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
}
func (h *Organization) SetPositionUsers(c *gin.Context) {
var req dto.SetPositionUsersRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetPositionUsers(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败")
return
}
httpx.OK(c)
}
func (h *Organization) SetUserPositions(c *gin.Context) {
var req dto.SetUserPositionsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetUserPositions(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败")
return
}
httpx.OK(c)
}
+56
View File
@@ -0,0 +1,56 @@
package handler
import (
"strconv"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Permission struct{ service *service.AccessService }
func NewPermission(service *service.AccessService) *Permission { return &Permission{service: service} }
func (h *Permission) Buttons(c *gin.Context) {
var req dto.GetAuthorityButtonsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
selected, err := h.service.SelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID)
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"selected": selected}, "查询成功")
}
func (h *Permission) SetButtons(c *gin.Context) {
var req dto.SetAuthorityButtonsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetSelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID, req.Selected); err != nil {
httpx.Fail(c, "分配失败")
return
}
httpx.OK(c)
}
func (h *Permission) CanRemoveButton(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
allowed, err := h.service.CanRemoveButton(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "检查失败")
return
}
if !allowed {
httpx.Fail(c, "此按钮正在被使用无法删除")
return
}
httpx.OK(c)
}
+168
View File
@@ -0,0 +1,168 @@
package handler
import (
"fmt"
"net/http"
"strconv"
"time"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"kra/internal/worker"
"github.com/gin-gonic/gin"
)
type Task struct {
service *service.TaskService
scheduler *worker.TaskScheduler
}
func NewTask(service *service.TaskService, scheduler *worker.TaskScheduler) *Task {
return &Task{service: service, scheduler: scheduler}
}
func (h *Task) Create(c *gin.Context) {
var req dto.TaskRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
id, err := h.service.CreateRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
return
}
if err = h.scheduler.ScheduleID(c.Request.Context(), id); err != nil {
httpx.Fail(c, "调度失败:"+err.Error())
return
}
httpx.OK(c)
}
func (h *Task) Update(c *gin.Context) {
var req dto.TaskRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败:"+err.Error())
return
}
if err := h.scheduler.ScheduleID(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "调度失败:"+err.Error())
return
}
httpx.OK(c)
}
func (h *Task) Delete(c *gin.Context) {
var req dto.DeleteTaskRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
h.scheduler.Remove(req.ID)
if err := h.service.Delete(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Task) Toggle(c *gin.Context) {
var req dto.ToggleTaskRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Toggle(c.Request.Context(), req.ID, req.Enabled); err != nil {
httpx.Fail(c, "设置失败")
return
}
if err := h.scheduler.ScheduleID(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "调度失败")
return
}
httpx.OK(c)
}
func (h *Task) Trigger(c *gin.Context) {
var req dto.DeleteTaskRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.scheduler.TriggerID(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "任务不存在")
return
}
httpx.OK(c)
}
func (h *Task) List(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
var enabled *bool
if raw := c.Query("enabled"); raw != "" {
value, _ := strconv.ParseBool(raw)
enabled = &value
}
items, total, err := h.service.ListRequest(c.Request.Context(), page, size, c.Query("name"), c.Query("executorType"), enabled, h.scheduler.NextRuns())
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
}
func (h *Task) Logs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
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 {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
}
func (h *Task) Methods(c *gin.Context) {
httpx.Write(c, httpx.CodeSuccess, gin.H{"methods": h.service.RegisteredMethods()}, "获取成功")
}
func (h *Task) AlertStream(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("X-Accel-Buffering", "no")
flusher, ok := c.Writer.(http.Flusher)
if !ok {
httpx.Fail(c, "SSE 不受支持")
return
}
events := h.scheduler.Subscribe()
defer h.scheduler.Unsubscribe(events)
ticker := time.NewTicker(20 * time.Second)
defer ticker.Stop()
_, _ = fmt.Fprint(c.Writer, "event: connected\ndata: {}\n\n")
flusher.Flush()
for {
select {
case event, open := <-events:
if !open {
return
}
_, _ = fmt.Fprintf(c.Writer, "event: alert\ndata: %s\n\n", event)
flusher.Flush()
case <-ticker.C:
_, _ = fmt.Fprint(c.Writer, ": keepalive\n\n")
flusher.Flush()
case <-c.Request.Context().Done():
return
}
}
}
+40
View File
@@ -0,0 +1,40 @@
package httpx
import (
"net/http"
"github.com/gin-gonic/gin"
)
const (
CodeSuccess = 0
CodeError = 7
CodePasswordChangeRequired = 10001
)
type Response struct {
Code int `json:"code"`
Data any `json:"data"`
Msg string `json:"msg"`
}
type PageResult struct {
List any `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
func Write(c *gin.Context, code int, data any, message string) {
c.JSON(http.StatusOK, Response{Code: code, Data: data, Msg: message})
}
func OK(c *gin.Context) { Write(c, CodeSuccess, gin.H{}, "操作成功") }
func OKWithData(c *gin.Context, data any) { Write(c, CodeSuccess, data, "成功") }
func Fail(c *gin.Context, message string) { Write(c, CodeError, gin.H{}, message) }
func NoAuth(c *gin.Context, message string) {
c.AbortWithStatusJSON(http.StatusUnauthorized, Response{Code: CodeError, Data: nil, Msg: message})
}
-192
View File
@@ -1,192 +0,0 @@
package server
import (
"strconv"
"kra/internal/biz"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
type menuRequest struct {
ID uint `json:"ID"`
ParentID uint `json:"parentId"`
Path string `json:"path"`
Name string `json:"name"`
Hidden bool `json:"hidden"`
Component string `json:"component"`
Sort int `json:"sort"`
Meta struct {
ActiveName string `json:"activeName"`
KeepAlive bool `json:"keepAlive"`
DefaultMenu bool `json:"defaultMenu"`
Title string `json:"title"`
Icon string `json:"icon"`
CloseTab bool `json:"closeTab"`
TransitionType string `json:"transitionType"`
} `json:"meta"`
MenuButtons []struct {
ID uint `json:"ID"`
Name string `json:"name"`
Description string `json:"desc"`
} `json:"menuBtn"`
Parameters []struct {
ID uint `json:"ID"`
Type string `json:"type"`
Key string `json:"key"`
Value string `json:"value"`
} `json:"parameters"`
}
func menuDomain(v menuRequest) *biz.Menu {
menu := &biz.Menu{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.Meta.ActiveName, KeepAlive: v.Meta.KeepAlive, DefaultMenu: v.Meta.DefaultMenu, Title: v.Meta.Title, Icon: v.Meta.Icon, CloseTab: v.Meta.CloseTab, TransitionType: v.Meta.TransitionType}
menu.Buttons = menuButtons(v, v.ID)
menu.Parameters = make([]*biz.MenuParameter, 0, len(v.Parameters))
for _, parameter := range v.Parameters {
menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value})
}
return menu
}
func menuButtons(v menuRequest, menuID uint) []*biz.MenuButton {
buttons := make([]*biz.MenuButton, 0, len(v.MenuButtons))
for _, button := range v.MenuButtons {
buttons = append(buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: menuID})
}
return buttons
}
func registerMenuRoutes(group *gin.RouterGroup, svc *service.AccessService) {
router := group.Group("/menu")
router.POST("/getMenuList", func(c *gin.Context) {
items, err := svc.Menus(c.Request.Context())
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, pageResult{List: items, Total: int64(len(items)), Page: 1, PageSize: 999}, "获取成功")
})
router.POST("/getBaseMenuTree", func(c *gin.Context) {
items, err := svc.MenuTree(c.Request.Context())
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, gin.H{"menus": items}, "获取成功")
})
router.POST("/addBaseMenu", func(c *gin.Context) {
var req menuRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
menu := menuDomain(req)
if err := svc.CreateMenu(c.Request.Context(), menu); err != nil {
fail(c, "新增失败")
return
}
ok(c)
})
router.POST("/updateBaseMenu", func(c *gin.Context) {
var req menuRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.UpdateMenu(c.Request.Context(), menuDomain(req)); err != nil {
fail(c, "更新失败")
return
}
ok(c)
})
router.POST("/deleteBaseMenu", func(c *gin.Context) {
var req struct {
ID uint `json:"ID"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.DeleteMenu(c.Request.Context(), req.ID); err != nil {
fail(c, err.Error())
return
}
ok(c)
})
router.POST("/getBaseMenuById", func(c *gin.Context) {
var req struct {
ID uint `json:"id"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
item, err := svc.Menu(c.Request.Context(), req.ID)
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, gin.H{"menu": item}, "获取成功")
})
router.POST("/addMenuAuthority", func(c *gin.Context) {
var req struct {
AuthorityID uint `json:"authorityId"`
Menus []struct {
ID uint `json:"ID"`
} `json:"menus"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
ids := make([]uint, 0, len(req.Menus))
for _, m := range req.Menus {
ids = append(ids, m.ID)
}
if err := svc.SetAuthorityMenus(c.Request.Context(), req.AuthorityID, ids); err != nil {
fail(c, "添加失败")
return
}
ok(c)
})
router.POST("/getMenuAuthority", func(c *gin.Context) {
var req struct {
AuthorityID uint `json:"authorityId"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
menus, err := svc.AuthorityMenus(c.Request.Context(), req.AuthorityID)
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, gin.H{"menus": menus}, "获取成功")
})
router.GET("/getMenuRoles", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("menuId"), 10, 64)
ids, err := svc.MenuRoleIDs(c.Request.Context(), uint(id))
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, ids, "获取成功")
})
router.POST("/setMenuRoles", func(c *gin.Context) {
var req struct {
MenuID uint `json:"menuId"`
AuthorityIDs []uint `json:"authorityIds"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.SetMenuRoles(c.Request.Context(), req.MenuID, req.AuthorityIDs); err != nil {
fail(c, "设置失败")
return
}
ok(c)
})
}
+37
View File
@@ -0,0 +1,37 @@
package middleware
import (
"net/http"
"kra/internal/biz"
"kra/internal/server/httpx"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
func AccessControl(access *service.AccessService, audit *service.AuditService) gin.HandlerFunc {
return func(c *gin.Context) {
claims := Claims(c)
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
return
}
path := c.Request.URL.Path
allowed, err := access.Authorize(c.Request.Context(), claims.AuthorityID, path, c.Request.Method)
if err != nil || !allowed {
requestID, _ := c.Get("request_id")
requestIDText, _ := requestID.(string)
_ = audit.RecordDataAccess(c.Request.Context(), &biz.DataAccessLog{EventType: "blocked_access", Operation: c.Request.Method, UserID: claims.ID, AuthorityID: claims.AuthorityID, RequestID: requestIDText, Method: c.Request.Method, Path: path, Detail: "Casbin policy denied the request"})
c.AbortWithStatusJSON(http.StatusForbidden, httpx.Response{Code: httpx.CodeError, Data: nil, Msg: "权限不足"})
return
}
scope, err := access.ResolveDataScope(c.Request.Context(), claims.AuthorityID, claims.ID)
if err != nil {
httpx.Fail(c, "数据权限加载失败")
return
}
c.Request = c.Request.WithContext(biz.NewDataScopeContext(c.Request.Context(), scope))
c.Next()
}
}
+84
View File
@@ -0,0 +1,84 @@
package middleware
import (
"net/http"
"strconv"
"strings"
"time"
"kra/internal/conf"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/pkg/adminauth"
"github.com/gin-gonic/gin"
)
const claimsKey = "admin_claims"
func Auth(config *conf.AdminBackend, settings *service.SettingsService) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("x-token")
if token == "" {
token, _ = c.Cookie("x-token")
}
secret := ""
if config != nil && config.Jwt != nil {
secret = config.Jwt.SigningKey
}
claims, err := adminauth.Parse(token, secret)
if err != nil {
httpx.NoAuth(c, "未登录或非法访问")
return
}
if disabled, checkErr := settings.IsTokenDisabled(c.Request.Context(), token); checkErr != nil || disabled {
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
if config != nil && config.Jwt != nil {
if config.Jwt.ExpiresTime != nil {
expires = config.Jwt.ExpiresTime.AsDuration()
}
if config.Jwt.BufferTime != nil {
buffer = config.Jwt.BufferTime.AsDuration()
}
if config.Jwt.Issuer != "" {
issuer = config.Jwt.Issuer
}
}
newToken, newClaims, refreshErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd)
if refreshErr == 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})
}
}
c.Set(claimsKey, claims)
c.Next()
}
}
func Claims(c *gin.Context) *adminauth.Claims {
value, _ := c.Get(claimsKey)
claims, _ := value.(*adminauth.Claims)
return claims
}
func MustChangePassword() gin.HandlerFunc {
return func(c *gin.Context) {
claims := Claims(c)
if claims == nil || !claims.MustChangePwd {
c.Next()
return
}
path := strings.TrimSuffix(c.Request.URL.Path, "/")
if strings.HasSuffix(path, "/user/changePassword") || strings.HasSuffix(path, "/user/getUserInfo") || strings.HasSuffix(path, "/jwt/jsonInBlacklist") {
c.Next()
return
}
c.AbortWithStatusJSON(http.StatusConflict, httpx.Response{Code: httpx.CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "请先修改初始密码"})
}
}
+18
View File
@@ -0,0 +1,18 @@
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func RequestMeta() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := c.GetHeader("X-Request-Id")
if requestID == "" {
requestID = uuid.NewString()
}
c.Header("X-Request-Id", requestID)
c.Set("request_id", requestID)
c.Next()
}
}
-237
View File
@@ -1,237 +0,0 @@
package server
import (
"strconv"
"kra/internal/biz"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
func registerDepartmentRoutes(group *gin.RouterGroup, svc *service.AccessService) {
router := group.Group("/department")
router.POST("/getDepartmentList", func(c *gin.Context) {
var req struct {
Name string `json:"name"`
}
_ = c.ShouldBindJSON(&req)
items, err := svc.Departments(c.Request.Context(), req.Name)
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, items, "获取成功")
})
type departmentRequest struct {
ID uint `json:"ID"`
Name string `json:"name"`
ParentID uint `json:"parentId"`
Sort int `json:"sort"`
LeaderID uint `json:"leaderId"`
Status bool `json:"status"`
}
router.POST("/createDepartment", func(c *gin.Context) {
var req departmentRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.CreateDepartment(c.Request.Context(), &biz.Department{Name: req.Name, ParentID: req.ParentID, Sort: req.Sort, LeaderID: req.LeaderID, Status: req.Status}); err != nil {
fail(c, "创建失败")
return
}
ok(c)
})
router.PUT("/updateDepartment", func(c *gin.Context) {
var req departmentRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.UpdateDepartment(c.Request.Context(), &biz.Department{ID: req.ID, Name: req.Name, ParentID: req.ParentID, Sort: req.Sort, LeaderID: req.LeaderID, Status: req.Status}); err != nil {
fail(c, "更新失败")
return
}
ok(c)
})
router.DELETE("/deleteDepartment", func(c *gin.Context) {
var req struct {
ID uint `json:"ID"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.DeleteDepartment(c.Request.Context(), req.ID); err != nil {
fail(c, err.Error())
return
}
ok(c)
})
router.GET("/findDepartment", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
item, err := svc.Department(c.Request.Context(), uint(id))
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, item, "获取成功")
})
router.GET("/getDepartmentUsers", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("deptId"), 10, 64)
ids, err := svc.DepartmentUserIDs(c.Request.Context(), uint(id))
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, ids, "获取成功")
})
router.POST("/setDepartmentUsers", func(c *gin.Context) {
var req struct {
DepartmentID uint `json:"deptId"`
UserIDs []uint `json:"userIds"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.SetDepartmentUsers(c.Request.Context(), req.DepartmentID, req.UserIDs); err != nil {
fail(c, "设置失败")
return
}
ok(c)
})
group.POST("/user/setUserDepartments", func(c *gin.Context) {
var req struct {
ID uint `json:"ID"`
DepartmentIDs []uint `json:"deptIds"`
Primary uint `json:"primaryDeptId"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.SetUserDepartments(c.Request.Context(), req.ID, req.DepartmentIDs, req.Primary); err != nil {
fail(c, "设置失败")
return
}
ok(c)
})
}
func registerPositionRoutes(group *gin.RouterGroup, svc *service.AccessService) {
router := group.Group("/position")
router.POST("/getPositionList", func(c *gin.Context) {
var req struct {
Page int `json:"page"`
PageSize int `json:"pageSize"`
Name string `json:"name"`
Code string `json:"code"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
items, total, err := svc.Positions(c.Request.Context(), req.Page, req.PageSize, &biz.Position{Name: req.Name, Code: req.Code})
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
})
type positionRequest struct {
ID uint `json:"ID"`
Name string `json:"name"`
Code string `json:"code"`
Sort int `json:"sort"`
Status bool `json:"status"`
Remark string `json:"remark"`
}
router.POST("/createPosition", func(c *gin.Context) {
var req positionRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.CreatePosition(c.Request.Context(), &biz.Position{Name: req.Name, Code: req.Code, Sort: req.Sort, Status: req.Status, Remark: req.Remark}); err != nil {
fail(c, "创建失败")
return
}
ok(c)
})
router.PUT("/updatePosition", func(c *gin.Context) {
var req positionRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.UpdatePosition(c.Request.Context(), &biz.Position{ID: req.ID, Name: req.Name, Code: req.Code, Sort: req.Sort, Status: req.Status, Remark: req.Remark}); err != nil {
fail(c, "更新失败")
return
}
ok(c)
})
router.DELETE("/deletePosition", func(c *gin.Context) {
var req struct {
ID uint `json:"ID"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.DeletePosition(c.Request.Context(), req.ID); err != nil {
fail(c, "删除失败")
return
}
ok(c)
})
router.GET("/findPosition", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
item, err := svc.Position(c.Request.Context(), uint(id))
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, item, "获取成功")
})
router.GET("/getPositionUsers", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("positionId"), 10, 64)
ids, err := svc.PositionUserIDs(c.Request.Context(), uint(id))
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, ids, "获取成功")
})
router.POST("/setPositionUsers", func(c *gin.Context) {
var req struct {
PositionID uint `json:"positionId"`
UserIDs []uint `json:"userIds"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.SetPositionUsers(c.Request.Context(), req.PositionID, req.UserIDs); err != nil {
fail(c, "设置失败")
return
}
ok(c)
})
group.POST("/user/setUserPositions", func(c *gin.Context) {
var req struct {
ID uint `json:"ID"`
PositionIDs []uint `json:"positionIds"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.SetUserPositions(c.Request.Context(), req.ID, req.PositionIDs); err != nil {
fail(c, "设置失败")
return
}
ok(c)
})
}
-97
View File
@@ -1,97 +0,0 @@
package server
import (
"strconv"
"kra/internal/biz"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
func registerPolicyRoutes(group *gin.RouterGroup, svc *service.AccessService) {
casbin := group.Group("/casbin")
casbin.POST("/updateCasbin", func(c *gin.Context) {
var req struct {
AuthorityID uint `json:"authorityId"`
Infos []struct {
Path string `json:"path"`
Method string `json:"method"`
} `json:"casbinInfos"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
paths := make([]*biz.API, 0, len(req.Infos))
for _, v := range req.Infos {
paths = append(paths, &biz.API{Path: v.Path, Method: v.Method})
}
if err := svc.SetPolicyPaths(c.Request.Context(), req.AuthorityID, paths); err != nil {
fail(c, "更新失败")
return
}
ok(c)
})
casbin.POST("/getPolicyPathByAuthorityId", func(c *gin.Context) {
var req struct {
AuthorityID uint `json:"authorityId"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
paths, err := svc.PolicyPaths(c.Request.Context(), req.AuthorityID)
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, gin.H{"paths": paths}, "获取成功")
})
buttons := group.Group("/authorityBtn")
buttons.POST("/getAuthorityBtn", func(c *gin.Context) {
var req struct {
MenuID uint `json:"menuID"`
AuthorityID uint `json:"authorityId"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
selected, err := svc.SelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID)
if err != nil {
fail(c, "查询失败")
return
}
writeResult(c, codeSuccess, gin.H{"selected": selected}, "查询成功")
})
buttons.POST("/setAuthorityBtn", func(c *gin.Context) {
var req struct {
MenuID uint `json:"menuID"`
AuthorityID uint `json:"authorityId"`
Selected []uint `json:"selected"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.SetSelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID, req.Selected); err != nil {
fail(c, "分配失败")
return
}
ok(c)
})
buttons.POST("/canRemoveAuthorityBtn", func(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
allowed, err := svc.CanRemoveButton(c.Request.Context(), uint(id))
if err != nil {
fail(c, "检查失败")
return
}
if !allowed {
fail(c, "此按钮正在被使用无法删除")
return
}
ok(c)
})
}
+11 -32
View File
@@ -1,50 +1,29 @@
package server
import (
"net/http"
"kra/internal/server/httpx"
"github.com/gin-gonic/gin"
)
const (
codeSuccess = 0
codeError = 7
codePasswordChangeRequired = 10001
codeSuccess = httpx.CodeSuccess
codeError = httpx.CodeError
codePasswordChangeRequired = httpx.CodePasswordChangeRequired
)
type response struct {
Code int `json:"code"`
Data any `json:"data"`
Msg string `json:"msg"`
}
type response = httpx.Response
type pageResult struct {
List any `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
type pageResult = httpx.PageResult
func writeResult(c *gin.Context, code int, data any, message string) {
c.JSON(http.StatusOK, response{Code: code, Data: data, Msg: message})
httpx.Write(c, code, data, message)
}
func ok(c *gin.Context) {
writeResult(c, codeSuccess, gin.H{}, "操作成功")
}
func ok(c *gin.Context) { httpx.OK(c) }
func okWithData(c *gin.Context, data any) {
writeResult(c, codeSuccess, data, "成功")
}
func okWithData(c *gin.Context, data any) { httpx.OKWithData(c, data) }
func fail(c *gin.Context, message string) {
writeResult(c, codeError, gin.H{}, message)
}
func fail(c *gin.Context, message string) { httpx.Fail(c, message) }
func noAuth(c *gin.Context, message string) {
c.AbortWithStatusJSON(http.StatusUnauthorized, response{
Code: codeError,
Data: nil,
Msg: message,
})
}
func noAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) }
+20
View File
@@ -0,0 +1,20 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterAnnouncement(private, public *gin.RouterGroup, h *handler.Announcement) {
privateInfo := private.Group("/info")
privateInfo.POST("/createInfo", h.Create)
privateInfo.DELETE("/deleteInfo", h.Delete)
privateInfo.DELETE("/deleteInfoByIds", h.DeleteByIDs)
privateInfo.PUT("/updateInfo", h.Update)
privateInfo.GET("/findInfo", h.Find)
privateInfo.GET("/getInfoList", h.List)
publicInfo := public.Group("/info")
publicInfo.GET("/getInfoDataSource", h.DataSource)
publicInfo.GET("/getInfoPublic", h.Public)
}
+28
View File
@@ -0,0 +1,28 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterAPI(group *gin.RouterGroup, engine *gin.Engine, h *handler.API) {
api := group.Group("/api")
api.POST("/getApiList", h.List)
api.POST("/getAllApis", h.All)
api.POST("/createApi", h.Create)
api.POST("/updateApi", h.Update)
api.POST("/deleteApi", h.Delete)
api.DELETE("/deleteApisByIds", h.DeleteByIDs)
api.POST("/getApiById", h.Find)
api.GET("/getApiGroups", h.Groups)
api.GET("/getApiRoles", h.Roles)
api.POST("/setApiRoles", h.SetRoles)
api.GET("/syncApi", h.Sync(engine))
api.POST("/ignoreApi", h.Ignore)
api.POST("/enterSyncApi", h.ApplySync)
api.GET("/freshCasbin", h.FreshCasbin)
casbin := group.Group("/casbin")
casbin.POST("/updateCasbin", h.SetPolicyPaths)
casbin.POST("/getPolicyPathByAuthorityId", h.PolicyPaths)
}
+20
View File
@@ -0,0 +1,20 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterAuthority(group *gin.RouterGroup, h *handler.Authority) {
router := group.Group("/authority")
router.POST("/getAuthorityList", h.List)
router.POST("/createAuthority", h.Create)
router.POST("/copyAuthority", h.Copy)
router.PUT("/updateAuthority", h.Update)
router.POST("/deleteAuthority", h.Delete)
router.POST("/setRoleUsers", h.SetUsers)
router.GET("/getUsersByAuthority", h.Users)
router.POST("/setDataScope", h.SetDataScope)
router.GET("/getDataScopeDepts", h.DataScopeDepartments)
}
+13
View File
@@ -0,0 +1,13 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterEmail(group *gin.RouterGroup, h *handler.Email) {
email := group.Group("/email")
email.POST("/emailTest", h.Test)
email.POST("/sendEmail", h.Send)
}
+21
View File
@@ -0,0 +1,21 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterMenu(group *gin.RouterGroup, h *handler.Menu) {
router := group.Group("/menu")
router.POST("/getMenuList", h.List)
router.POST("/getBaseMenuTree", h.Tree)
router.POST("/addBaseMenu", h.Create)
router.POST("/updateBaseMenu", h.Update)
router.POST("/deleteBaseMenu", h.Delete)
router.POST("/getBaseMenuById", h.Find)
router.POST("/addMenuAuthority", h.SetAuthorityMenus)
router.POST("/getMenuAuthority", h.AuthorityMenus)
router.GET("/getMenuRoles", h.RoleIDs)
router.POST("/setMenuRoles", h.SetRoles)
}
+29
View File
@@ -0,0 +1,29 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterOrganization(group *gin.RouterGroup, h *handler.Organization) {
departments := group.Group("/department")
departments.POST("/getDepartmentList", h.ListDepartments)
departments.POST("/createDepartment", h.CreateDepartment)
departments.PUT("/updateDepartment", h.UpdateDepartment)
departments.DELETE("/deleteDepartment", h.DeleteDepartment)
departments.GET("/findDepartment", h.FindDepartment)
departments.GET("/getDepartmentUsers", h.DepartmentUsers)
departments.POST("/setDepartmentUsers", h.SetDepartmentUsers)
group.POST("/user/setUserDepartments", h.SetUserDepartments)
positions := group.Group("/position")
positions.POST("/getPositionList", h.ListPositions)
positions.POST("/createPosition", h.CreatePosition)
positions.PUT("/updatePosition", h.UpdatePosition)
positions.DELETE("/deletePosition", h.DeletePosition)
positions.GET("/findPosition", h.FindPosition)
positions.GET("/getPositionUsers", h.PositionUsers)
positions.POST("/setPositionUsers", h.SetPositionUsers)
group.POST("/user/setUserPositions", h.SetUserPositions)
}
+14
View File
@@ -0,0 +1,14 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterPermission(group *gin.RouterGroup, h *handler.Permission) {
buttons := group.Group("/authorityBtn")
buttons.POST("/getAuthorityBtn", h.Buttons)
buttons.POST("/setAuthorityBtn", h.SetButtons)
buttons.POST("/canRemoveAuthorityBtn", h.CanRemoveButton)
}
+20
View File
@@ -0,0 +1,20 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterTask(group *gin.RouterGroup, h *handler.Task) {
router := group.Group("/timedTask")
router.POST("/createTimedTask", h.Create)
router.PUT("/updateTimedTask", h.Update)
router.DELETE("/deleteTimedTask", h.Delete)
router.POST("/toggleTimedTask", h.Toggle)
router.POST("/triggerTimedTask", h.Trigger)
router.GET("/getTimedTaskList", h.List)
router.GET("/getTimedTaskLogList", h.Logs)
router.GET("/getRegisteredMethods", h.Methods)
router.GET("/alertStream", h.AlertStream)
}
+4 -1
View File
@@ -1,8 +1,11 @@
package server
import (
"kra/internal/server/handler"
"kra/internal/worker"
"github.com/google/wire"
)
// ProviderSet is server providers.
var ProviderSet = wire.NewSet(NewGinServer, NewTaskScheduler)
var ProviderSet = wire.NewSet(NewGinServer, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewTask, worker.NewTaskScheduler)
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"kra/internal/biz"
"kra/internal/conf"
"kra/internal/service"
"kra/internal/worker"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v4/cpu"
@@ -15,7 +16,7 @@ import (
"google.golang.org/protobuf/proto"
)
func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, systemService *service.SystemService, settings *service.SettingsService, scheduler *TaskScheduler) {
func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, systemService *service.SystemService, settings *service.SettingsService, scheduler *worker.TaskScheduler) {
security := group.Group("/securityConfig")
security.GET("/getSecurityConfig", func(c *gin.Context) {
value, err := settings.Security(c.Request.Context())
-187
View File
@@ -1,187 +0,0 @@
package server
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"kra/internal/biz"
"kra/internal/service"
)
type taskRequest struct {
ID uint `json:"ID"`
Name string `json:"name"`
Description string `json:"description"`
Spec string `json:"spec"`
WithSeconds bool `json:"withSeconds"`
ExecutorType string `json:"executorType"`
MethodName string `json:"methodName"`
Params json.RawMessage `json:"params"`
HTTPURL string `json:"httpUrl"`
HTTPMethod string `json:"httpMethod"`
HTTPHeader json.RawMessage `json:"httpHeader"`
HTTPBody string `json:"httpBody"`
HTTPAllowPrivate bool `json:"httpAllowPrivate"`
Enabled bool `json:"enabled"`
}
func taskDomain(v taskRequest) *biz.TimedTask {
return &biz.TimedTask{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
}
func registerTaskRoutes(group *gin.RouterGroup, svc *service.TaskService, scheduler *TaskScheduler) {
router := group.Group("/timedTask")
router.POST("/createTimedTask", func(c *gin.Context) {
var req taskRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
task := taskDomain(req)
if err := svc.Create(c.Request.Context(), task); err != nil {
fail(c, "创建失败:"+err.Error())
return
}
if err := scheduler.Schedule(task); err != nil {
fail(c, "调度失败:"+err.Error())
return
}
ok(c)
})
router.PUT("/updateTimedTask", func(c *gin.Context) {
var req taskRequest
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
task := taskDomain(req)
if err := svc.Update(c.Request.Context(), task); err != nil {
fail(c, "更新失败:"+err.Error())
return
}
if err := scheduler.Schedule(task); err != nil {
fail(c, "调度失败:"+err.Error())
return
}
ok(c)
})
router.DELETE("/deleteTimedTask", func(c *gin.Context) {
var req struct {
ID uint `json:"ID"`
}
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
fail(c, "参数错误")
return
}
scheduler.Remove(req.ID)
if err := svc.Delete(c.Request.Context(), req.ID); err != nil {
fail(c, "删除失败")
return
}
ok(c)
})
router.POST("/toggleTimedTask", func(c *gin.Context) {
var req struct {
ID uint `json:"ID"`
Enabled bool `json:"enabled"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if err := svc.Toggle(c.Request.Context(), req.ID, req.Enabled); err != nil {
fail(c, "设置失败")
return
}
task, err := svc.Task(c.Request.Context(), req.ID)
if err != nil {
fail(c, "任务不存在")
return
}
if err = scheduler.Schedule(task); err != nil {
fail(c, "调度失败")
return
}
ok(c)
})
router.POST("/triggerTimedTask", func(c *gin.Context) {
var req struct {
ID uint `json:"ID"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
task, err := svc.Task(c.Request.Context(), req.ID)
if err != nil {
fail(c, "任务不存在")
return
}
scheduler.Trigger(task)
ok(c)
})
router.GET("/getTimedTaskList", func(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
var enabled *bool
if raw := c.Query("enabled"); raw != "" {
value, _ := strconv.ParseBool(raw)
enabled = &value
}
items, total, err := svc.Tasks(c.Request.Context(), page, size, &biz.TimedTask{Name: c.Query("name"), ExecutorType: c.Query("executorType"), EnabledFilter: enabled}, scheduler.NextRuns())
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
})
router.GET("/getTimedTaskLogList", func(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
taskID, _ := strconv.ParseUint(c.Query("taskId"), 10, 64)
items, total, err := svc.Logs(c.Request.Context(), page, size, uint(taskID), c.Query("status"))
if err != nil {
fail(c, "获取失败")
return
}
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
})
router.GET("/getRegisteredMethods", func(c *gin.Context) {
writeResult(c, codeSuccess, gin.H{"methods": svc.RegisteredMethods()}, "获取成功")
})
router.GET("/alertStream", func(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("X-Accel-Buffering", "no")
flusher, okFlush := c.Writer.(http.Flusher)
if !okFlush {
fail(c, "SSE 不受支持")
return
}
events := scheduler.Subscribe()
defer scheduler.Unsubscribe(events)
ticker := time.NewTicker(20 * time.Second)
defer ticker.Stop()
_, _ = fmt.Fprint(c.Writer, "event: connected\ndata: {}\n\n")
flusher.Flush()
for {
select {
case event, open := <-events:
if !open {
return
}
_, _ = fmt.Fprintf(c.Writer, "event: alert\ndata: %s\n\n", event)
flusher.Flush()
case <-ticker.C:
_, _ = fmt.Fprint(c.Writer, ": keepalive\n\n")
flusher.Flush()
case <-c.Request.Context().Done():
return
}
}
})
}
-184
View File
@@ -1,184 +0,0 @@
package server
import (
"context"
"encoding/json"
"fmt"
"github.com/robfig/cron/v3"
"kra/internal/biz"
"kra/internal/service"
"log/slog"
"sync"
"time"
)
type TaskScheduler struct {
service *service.TaskService
logger *slog.Logger
standard *cron.Cron
seconds *cron.Cron
mu sync.Mutex
entries map[uint]scheduledEntry
subMu sync.RWMutex
subscribers map[chan []byte]struct{}
}
type scheduledEntry struct {
seconds bool
entry cron.EntryID
}
func NewTaskScheduler(service *service.TaskService, logger *slog.Logger) *TaskScheduler {
return &TaskScheduler{service: service, logger: logger, standard: cron.New(), seconds: cron.New(cron.WithSeconds()), entries: map[uint]scheduledEntry{}, subscribers: map[chan []byte]struct{}{}}
}
func (s *TaskScheduler) Start(ctx context.Context) error {
s.standard.Start()
s.seconds.Start()
items, _, err := s.service.Tasks(ctx, 1, 100000, nil, nil)
if err == nil {
for _, raw := range items {
id, _ := raw["ID"].(uint)
if task, findErr := s.service.Task(ctx, id); findErr == nil && task.Enabled {
if scheduleErr := s.Schedule(task); scheduleErr != nil {
s.logger.ErrorContext(ctx, "restore timed task failed", "id", id, "error", scheduleErr)
}
}
}
} else {
s.logger.WarnContext(ctx, "timed task table is not ready", "error", err)
}
<-ctx.Done()
return nil
}
func (s *TaskScheduler) Stop(ctx context.Context) error {
standardDone := s.standard.Stop().Done()
secondsDone := s.seconds.Stop().Done()
select {
case <-standardDone:
case <-ctx.Done():
return ctx.Err()
}
select {
case <-secondsDone:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (s *TaskScheduler) Remove(id uint) {
s.mu.Lock()
defer s.mu.Unlock()
if old, ok := s.entries[id]; ok {
if old.seconds {
s.seconds.Remove(old.entry)
} else {
s.standard.Remove(old.entry)
}
delete(s.entries, id)
}
}
func (s *TaskScheduler) Reload(ctx context.Context) error {
s.mu.Lock()
for id, old := range s.entries {
if old.seconds {
s.seconds.Remove(old.entry)
} else {
s.standard.Remove(old.entry)
}
delete(s.entries, id)
}
s.mu.Unlock()
items, _, err := s.service.Tasks(ctx, 1, 100000, nil, nil)
if err != nil {
return err
}
for _, raw := range items {
id, _ := raw["ID"].(uint)
task, findErr := s.service.Task(ctx, id)
if findErr != nil {
return findErr
}
if task.Enabled {
if err = s.Schedule(task); err != nil {
return err
}
}
}
return nil
}
func (s *TaskScheduler) Schedule(task *biz.TimedTask) error {
s.Remove(task.ID)
if !task.Enabled {
return nil
}
copy := *task
run := func() {
log := s.service.Run(context.Background(), &copy, "auto")
if log.Status != "success" {
s.Broadcast(map[string]any{"taskId": copy.ID, "taskName": copy.Name, "status": log.Status, "errorMsg": log.ErrorMsg, "time": time.Now()})
}
}
var id cron.EntryID
var err error
if task.WithSeconds {
id, err = s.seconds.AddFunc(task.Spec, run)
} else {
id, err = s.standard.AddFunc(task.Spec, run)
}
if err != nil {
return err
}
s.mu.Lock()
s.entries[task.ID] = scheduledEntry{seconds: task.WithSeconds, entry: id}
s.mu.Unlock()
return nil
}
func (s *TaskScheduler) NextRuns() map[uint]time.Time {
s.mu.Lock()
defer s.mu.Unlock()
out := map[uint]time.Time{}
for id, item := range s.entries {
if item.seconds {
out[id] = s.seconds.Entry(item.entry).Next
} else {
out[id] = s.standard.Entry(item.entry).Next
}
}
return out
}
func (s *TaskScheduler) Trigger(task *biz.TimedTask) {
go func() {
log := s.service.Run(context.Background(), task, "manual")
if log.Status != "success" {
s.Broadcast(map[string]any{"taskId": task.ID, "taskName": task.Name, "status": log.Status, "errorMsg": log.ErrorMsg, "time": time.Now()})
}
}()
}
func (s *TaskScheduler) Subscribe() chan []byte {
ch := make(chan []byte, 16)
s.subMu.Lock()
s.subscribers[ch] = struct{}{}
s.subMu.Unlock()
return ch
}
func (s *TaskScheduler) Unsubscribe(ch chan []byte) {
s.subMu.Lock()
if _, ok := s.subscribers[ch]; ok {
delete(s.subscribers, ch)
close(ch)
}
s.subMu.Unlock()
}
func (s *TaskScheduler) Broadcast(value any) {
raw, err := json.Marshal(value)
if err != nil {
raw = []byte(fmt.Sprint(value))
}
s.subMu.RLock()
defer s.subMu.RUnlock()
for ch := range s.subscribers {
select {
case ch <- raw:
default:
}
}
}