迁移
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
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) })
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type captureWriter struct {
|
||||
gin.ResponseWriter
|
||||
body bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *captureWriter) Write(data []byte) (int, error) {
|
||||
if w.body.Len() < 32768 {
|
||||
remaining := 32768 - w.body.Len()
|
||||
if len(data) > remaining {
|
||||
_, _ = w.body.Write(data[:remaining])
|
||||
} else {
|
||||
_, _ = w.body.Write(data)
|
||||
}
|
||||
}
|
||||
return w.ResponseWriter.Write(data)
|
||||
}
|
||||
func redactJSON(raw []byte) string {
|
||||
if len(raw) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(raw) > 32768 {
|
||||
raw = raw[:32768]
|
||||
}
|
||||
var value any
|
||||
if json.Unmarshal(raw, &value) != nil {
|
||||
return string(raw)
|
||||
}
|
||||
var clean func(any)
|
||||
clean = func(current any) {
|
||||
switch v := current.(type) {
|
||||
case map[string]any:
|
||||
for key, item := range v {
|
||||
lower := strings.ToLower(key)
|
||||
if strings.Contains(lower, "password") || strings.Contains(lower, "token") || strings.Contains(lower, "secret") {
|
||||
v[key] = "******"
|
||||
} else {
|
||||
clean(item)
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
for _, item := range v {
|
||||
clean(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
clean(value)
|
||||
encoded, _ := json.Marshal(value)
|
||||
return string(encoded)
|
||||
}
|
||||
func operationAudit(svc *service.AuditService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.Request.Method == "GET" || c.Request.Method == "HEAD" || c.Request.Method == "OPTIONS" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
path := c.Request.URL.Path
|
||||
if strings.Contains(path, "sysOperationRecord") || strings.Contains(path, "sysLoginLog") || strings.Contains(path, "dataAccessLog") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
var requestBody []byte
|
||||
if c.Request.Body != nil {
|
||||
requestBody, _ = io.ReadAll(io.LimitReader(c.Request.Body, 32769))
|
||||
c.Request.Body = io.NopCloser(bytes.NewReader(requestBody))
|
||||
}
|
||||
writer := &captureWriter{ResponseWriter: c.Writer}
|
||||
c.Writer = writer
|
||||
started := time.Now()
|
||||
c.Next()
|
||||
userID := uint(0)
|
||||
if claims := currentClaims(c); claims != nil {
|
||||
userID = claims.ID
|
||||
}
|
||||
requestID, _ := c.Get("request_id")
|
||||
status := c.Writer.Status()
|
||||
errorMessage := ""
|
||||
if status >= 400 {
|
||||
errorMessage = writer.body.String()
|
||||
}
|
||||
_ = svc.RecordOperation(c.Request.Context(), &biz.OperationRecord{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: redactJSON(requestBody), Response: redactJSON(writer.body.Bytes()), UserID: userID, RequestID: toString(requestID), TraceID: c.GetHeader("traceparent"), DeviceID: c.GetHeader("X-Device-Id")})
|
||||
}
|
||||
}
|
||||
func toString(value any) string {
|
||||
if text, ok := value.(string); ok {
|
||||
return text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func idsFromQuery(c *gin.Context) []uint {
|
||||
values := c.QueryArray("IDs[]")
|
||||
if len(values) == 0 {
|
||||
values = c.QueryArray("ids[]")
|
||||
}
|
||||
ids := make([]uint, 0, len(values))
|
||||
for _, v := range values {
|
||||
id, _ := strconv.ParseUint(v, 10, 64)
|
||||
if id > 0 {
|
||||
ids = append(ids, uint(id))
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
func registerAuditRoutes(group, public *gin.RouterGroup, svc *service.AuditService) {
|
||||
operations := group.Group("/sysOperationRecord")
|
||||
operations.GET("/getSysOperationRecordList", func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
status, _ := strconv.Atoi(c.Query("status"))
|
||||
userID, _ := strconv.ParseUint(c.Query("user_id"), 10, 64)
|
||||
items, total, err := svc.Operations(c.Request.Context(), page, size, &biz.OperationRecord{Path: c.Query("path"), Method: c.Query("method"), Status: status, IP: c.Query("ip"), UserID: uint(userID)})
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
|
||||
})
|
||||
operations.GET("/findSysOperationRecord", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.Operation(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"resysOperationRecord": item}, "查询成功")
|
||||
})
|
||||
operations.DELETE("/deleteSysOperationRecord", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
if err := svc.DeleteOperations(c.Request.Context(), []uint{uint(id)}); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
operations.DELETE("/deleteSysOperationRecordByIds", func(c *gin.Context) {
|
||||
if err := svc.DeleteOperations(c.Request.Context(), idsFromQuery(c)); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
logins := group.Group("/sysLoginLog")
|
||||
logins.GET("/getLoginLogList", func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
items, total, err := svc.Logins(c.Request.Context(), page, size, &biz.LoginLog{Username: c.Query("username"), IP: c.Query("ip")})
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
|
||||
})
|
||||
logins.GET("/findLoginLog", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.Login(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, item, "查询成功")
|
||||
})
|
||||
logins.DELETE("/deleteLoginLog", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
if err := svc.DeleteLogins(c.Request.Context(), []uint{uint(id)}); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
logins.DELETE("/deleteLoginLogByIds", func(c *gin.Context) {
|
||||
if err := svc.DeleteLogins(c.Request.Context(), idsFromQuery(c)); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
access := group.Group("/dataAccessLog")
|
||||
access.POST("/getDataAccessLogList", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
EventType string `json:"eventType"`
|
||||
TargetTable string `json:"targetTable"`
|
||||
UserID uint `json:"userId"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
items, total, err := svc.DataAccess(c.Request.Context(), req.Page, req.PageSize, &biz.DataAccessLog{EventType: req.EventType, TargetTable: req.TargetTable, UserID: req.UserID})
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
|
||||
})
|
||||
access.DELETE("/deleteDataAccessLogByIds", func(c *gin.Context) {
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.DeleteDataAccess(c.Request.Context(), req.IDs); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
viewer := group.Group("/logViewer")
|
||||
viewer.GET("/dates", func(c *gin.Context) {
|
||||
data, err := svc.LogDates(c.Request.Context(), c.Query("month"))
|
||||
if err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, data, "获取成功")
|
||||
})
|
||||
viewer.GET("/files", func(c *gin.Context) {
|
||||
data, err := svc.LogFiles(c.Request.Context(), c.Query("date"))
|
||||
if err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, data, "获取成功")
|
||||
})
|
||||
viewer.GET("/content", func(c *gin.Context) {
|
||||
var cursor *int64
|
||||
if raw := c.Query("cursor"); raw != "" {
|
||||
value, err := strconv.ParseInt(raw, 10, 64)
|
||||
if err != nil || value < 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
cursor = &value
|
||||
}
|
||||
data, err := svc.LogContent(c.Request.Context(), c.Query("date"), c.Query("path"), cursor)
|
||||
if err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, data, "获取成功")
|
||||
})
|
||||
errorsRoute := group.Group("/sysError")
|
||||
errorsRoute.DELETE("/deleteSysError", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
if err := svc.DeleteErrors(c.Request.Context(), []uint{uint(id)}); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
errorsRoute.DELETE("/deleteSysErrorByIds", func(c *gin.Context) {
|
||||
if err := svc.DeleteErrors(c.Request.Context(), idsFromQuery(c)); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
errorsRoute.PUT("/updateSysError", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
Form string `json:"form"`
|
||||
Info string `json:"info"`
|
||||
Level string `json:"level"`
|
||||
Solution string `json:"solution"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.UpdateError(c.Request.Context(), &biz.ErrorRecord{ID: req.ID, Form: req.Form, Info: req.Info, Level: req.Level, Solution: req.Solution, Status: req.Status}); err != nil {
|
||||
fail(c, "更新失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
errorsRoute.GET("/findSysError", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.Error(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"resysError": item}, "查询成功")
|
||||
})
|
||||
errorsRoute.GET("/getSysErrorList", func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
items, total, err := svc.Errors(c.Request.Context(), page, size, &biz.ErrorRecord{Form: c.Query("form"), Level: c.Query("level"), Status: c.Query("status")})
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
|
||||
})
|
||||
errorsRoute.GET("/getSysErrorSolution", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.Error(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"solution": item["solution"]}, "获取成功")
|
||||
})
|
||||
public.Group("/sysError").POST("/createSysError", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Form string `json:"form"`
|
||||
Info string `json:"info"`
|
||||
Level string `json:"level"`
|
||||
RequestID string `json:"request_id"`
|
||||
TraceID string `json:"trace_id"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil || req.Form == "" {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.CreateError(c.Request.Context(), &biz.ErrorRecord{Form: req.Form, Info: req.Info, Level: req.Level, RequestID: req.RequestID, TraceID: req.TraceID}); err != nil {
|
||||
fail(c, "创建失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
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, "获取成功")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type exportTemplateRequest struct {
|
||||
ID uint `json:"ID"`
|
||||
DBName string `json:"dbName"`
|
||||
Name string `json:"name"`
|
||||
TableName string `json:"tableName"`
|
||||
TemplateID string `json:"templateID"`
|
||||
TemplateInfo string `json:"templateInfo"`
|
||||
SQL string `json:"sql"`
|
||||
ImportSQL string `json:"importSql"`
|
||||
Limit *int `json:"limit"`
|
||||
Order string `json:"order"`
|
||||
Conditions []struct {
|
||||
From string `json:"from"`
|
||||
Column string `json:"column"`
|
||||
Operator string `json:"operator"`
|
||||
} `json:"conditions"`
|
||||
Joins []struct {
|
||||
Join string `json:"joins"`
|
||||
Table string `json:"table"`
|
||||
On string `json:"on"`
|
||||
} `json:"joinTemplate"`
|
||||
}
|
||||
|
||||
func exportTemplateDomain(v exportTemplateRequest) *biz.ExportTemplate {
|
||||
out := &biz.ExportTemplate{ID: v.ID, DBName: v.DBName, Name: v.Name, TableName: v.TableName, TemplateID: v.TemplateID, TemplateInfo: v.TemplateInfo, SQL: v.SQL, ImportSQL: v.ImportSQL, Limit: v.Limit, Order: v.Order}
|
||||
for _, x := range v.Conditions {
|
||||
out.Conditions = append(out.Conditions, biz.ExportCondition{From: x.From, Column: x.Column, Operator: x.Operator})
|
||||
}
|
||||
for _, x := range v.Joins {
|
||||
out.Joins = append(out.Joins, biz.ExportJoin{Join: x.Join, Table: x.Table, On: x.On})
|
||||
}
|
||||
return out
|
||||
}
|
||||
func exportParams(values url.Values) map[string]string {
|
||||
out := map[string]string{}
|
||||
nested, _ := url.ParseQuery(values.Get("params"))
|
||||
for key, items := range nested {
|
||||
if len(items) > 0 {
|
||||
out[key] = items[0]
|
||||
}
|
||||
}
|
||||
for key, items := range values {
|
||||
if key != "params" && len(items) > 0 {
|
||||
out[key] = items[0]
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type exportToken struct {
|
||||
TemplateID string `json:"templateID"`
|
||||
Params map[string]string `json:"params"`
|
||||
Blank bool `json:"blank"`
|
||||
}
|
||||
|
||||
func registerExportRoutes(group, public *gin.RouterGroup, system *service.SystemService, svc *service.ExportService) {
|
||||
router := group.Group("/sysExportTemplate")
|
||||
router.POST("/createSysExportTemplate", func(c *gin.Context) {
|
||||
var req exportTemplateRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.Name == "" || req.TemplateID == "" {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.Create(c.Request.Context(), exportTemplateDomain(req)); err != nil {
|
||||
fail(c, "创建失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.PUT("/updateSysExportTemplate", func(c *gin.Context) {
|
||||
var req exportTemplateRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.Update(c.Request.Context(), exportTemplateDomain(req)); err != nil {
|
||||
fail(c, "更新失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.DELETE("/deleteSysExportTemplate", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.Delete(c.Request.Context(), []uint{req.ID}); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.DELETE("/deleteSysExportTemplateByIds", func(c *gin.Context) {
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.Delete(c.Request.Context(), req.IDs); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.GET("/findSysExportTemplate", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.Template(c.Request.Context(), uint(id), "")
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"resysExportTemplate": item}, "查询成功")
|
||||
})
|
||||
router.GET("/getSysExportTemplateList", func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
items, total, err := svc.Templates(c.Request.Context(), page, size, &biz.ExportTemplate{Name: c.Query("name"), TableName: c.Query("tableName"), TemplateID: c.Query("templateID")})
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
|
||||
})
|
||||
router.GET("/previewSQL", func(c *gin.Context) {
|
||||
sql, err := svc.Preview(c.Request.Context(), c.Query("templateID"), exportParams(c.Request.URL.Query()))
|
||||
if err != nil {
|
||||
fail(c, "预览失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"sql": sql}, "获取成功")
|
||||
})
|
||||
issue := func(blank bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
tid := c.Query("templateID")
|
||||
if tid == "" {
|
||||
fail(c, "模板ID不能为空")
|
||||
return
|
||||
}
|
||||
token := strings.ReplaceAll(uuid.NewString(), "-", "")
|
||||
raw, _ := json.Marshal(exportToken{TemplateID: tid, Params: exportParams(c.Request.URL.Query()), Blank: blank})
|
||||
if err := system.CacheSet(c.Request.Context(), "export:"+token, string(raw), 5*time.Minute); err != nil {
|
||||
fail(c, "导出令牌创建失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"token": token}, "获取成功")
|
||||
}
|
||||
}
|
||||
router.GET("/exportExcel", issue(false))
|
||||
router.GET("/exportTemplate", issue(true))
|
||||
router.POST("/importExcel", func(c *gin.Context) {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
fail(c, "请选择导入文件")
|
||||
return
|
||||
}
|
||||
opened, err := file.Open()
|
||||
if err != nil {
|
||||
fail(c, "读取文件失败")
|
||||
return
|
||||
}
|
||||
defer opened.Close()
|
||||
tid := c.PostForm("templateID")
|
||||
if tid == "" {
|
||||
tid = c.Query("templateID")
|
||||
}
|
||||
if err = svc.Import(c.Request.Context(), tid, opened); err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
pub := public.Group("/sysExportTemplate")
|
||||
download := func(expectBlank bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.Query("token")
|
||||
raw, okValue, err := system.CacheGet(c.Request.Context(), "export:"+token)
|
||||
if err != nil || !okValue {
|
||||
fail(c, "导出令牌无效或已过期")
|
||||
return
|
||||
}
|
||||
_ = system.CacheDelete(c.Request.Context(), "export:"+token)
|
||||
var value exportToken
|
||||
if json.Unmarshal([]byte(raw), &value) != nil || value.Blank != expectBlank {
|
||||
fail(c, "导出令牌无效")
|
||||
return
|
||||
}
|
||||
var data []byte
|
||||
var name string
|
||||
if expectBlank {
|
||||
data, name, err = svc.ExportBlankTemplate(c.Request.Context(), value.TemplateID)
|
||||
} else {
|
||||
data, name, err = svc.Export(c.Request.Context(), value.TemplateID, value.Params)
|
||||
}
|
||||
if err != nil {
|
||||
fail(c, "导出失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
c.Header("Content-Disposition", "attachment; filename*=UTF-8''"+url.QueryEscape(name))
|
||||
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", data)
|
||||
}
|
||||
}
|
||||
pub.GET("/exportExcelByToken", download(false))
|
||||
pub.GET("/exportTemplateByToken", download(true))
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/service"
|
||||
"kra/pkg/adminauth"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
// 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, svc *service.SystemService, access *service.AccessService, settings *service.SettingsService, versions *service.VersionService, exports *service.ExportService, audit *service.AuditService, tasks *service.TaskService, media *service.MediaService, scheduler *TaskScheduler, logger *slog.Logger) *GinServer {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
engine := gin.New()
|
||||
engine.Use(ginRequestMeta(), gin.Recovery(), securityRateLimit(svc, settings), operationAudit(audit))
|
||||
if config != nil && config.Local != nil && config.Local.StorePath != "" {
|
||||
pathPrefix := "/" + strings.Trim(config.Local.PathPrefix, "/")
|
||||
if pathPrefix != "/" {
|
||||
engine.StaticFS(pathPrefix, filesOnly{FileSystem: http.Dir(config.Local.StorePath)})
|
||||
}
|
||||
}
|
||||
|
||||
prefix := ""
|
||||
if config != nil {
|
||||
prefix = strings.TrimSuffix(config.RouterPrefix, "/")
|
||||
}
|
||||
public := engine.Group(prefix)
|
||||
public.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, "ok")
|
||||
})
|
||||
registerPublicRoutes(public, engine, config, svc, settings, access, audit)
|
||||
private := engine.Group(prefix)
|
||||
private.Use(jwtAuth(config, settings), accessControl(access, audit))
|
||||
registerPrivateRoutes(private, svc)
|
||||
registerAccessRoutes(private, engine, access)
|
||||
registerSettingsRoutes(private, public, settings)
|
||||
registerSystemConfigRoutes(private, config, settings)
|
||||
registerVersionRoutes(private, versions)
|
||||
registerExportRoutes(private, public, svc, exports)
|
||||
registerAuditRoutes(private, public, audit)
|
||||
registerTaskRoutes(private, tasks, scheduler)
|
||||
registerMediaRoutes(private, media)
|
||||
|
||||
engine.NoRoute(func(c *gin.Context) {
|
||||
fail(c, "请求的接口不存在")
|
||||
})
|
||||
|
||||
network := "tcp"
|
||||
address := ":8000"
|
||||
if c != nil && c.Http != nil {
|
||||
if c.Http.Network != "" {
|
||||
network = c.Http.Network
|
||||
}
|
||||
if c.Http.Addr != "" {
|
||||
address = c.Http.Addr
|
||||
}
|
||||
}
|
||||
|
||||
return &GinServer{
|
||||
network: network,
|
||||
address: address,
|
||||
server: &http.Server{Addr: address, Handler: engine},
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.Set("admin_claims", claims)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func currentClaims(c *gin.Context) *adminauth.Claims {
|
||||
claims, _ := c.Get("admin_claims")
|
||||
result, _ := claims.(*adminauth.Claims)
|
||||
return result
|
||||
}
|
||||
|
||||
func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *conf.AdminBackend, svc *service.SystemService, settings *service.SettingsService, access *service.AccessService, audit *service.AuditService) {
|
||||
expiration := 3 * time.Minute
|
||||
keyLong, width, height := 6, 240, 80
|
||||
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)
|
||||
}
|
||||
if config.Captcha.ImgWidth > 0 {
|
||||
width = int(config.Captcha.ImgWidth)
|
||||
}
|
||||
if config.Captcha.ImgHeight > 0 {
|
||||
height = int(config.Captcha.ImgHeight)
|
||||
}
|
||||
}
|
||||
store := &captchaStore{service: svc, expiration: expiration}
|
||||
base := group.Group("/base")
|
||||
base.POST("/captcha", func(c *gin.Context) {
|
||||
security, _ := 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(), svc, "login:fail:"+c.ClientIP())
|
||||
openCaptcha = failures >= security.CaptchaOpen
|
||||
}
|
||||
}
|
||||
if !openCaptcha {
|
||||
writeResult(c, codeSuccess, gin.H{"captchaId": "", "picPath": "", "captchaLength": keyLong, "openCaptcha": false}, "验证码获取成功")
|
||||
return
|
||||
}
|
||||
driver := base64Captcha.NewDriverDigit(height, width, keyLong, 0.7, 80)
|
||||
id, picture, _, err := base64Captcha.NewCaptcha(driver, store).Generate()
|
||||
if err != nil {
|
||||
fail(c, "验证码获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"captchaId": id, "picPath": picture, "captchaLength": keyLong, "openCaptcha": openCaptcha}, "验证码获取成功")
|
||||
})
|
||||
base.POST("/login", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Captcha string `json:"captcha"`
|
||||
CaptchaID string `json:"captchaId"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.Username == "" || req.Password == "" {
|
||||
fail(c, "请输入用户名和密码")
|
||||
return
|
||||
}
|
||||
security, _ := settings.CurrentSecurity(c.Request.Context())
|
||||
if security != nil && security.LockEnable {
|
||||
if _, locked, _ := svc.CacheGet(c.Request.Context(), "login:lock:"+req.Username); locked {
|
||||
fail(c, "账户已锁定,请稍后重试")
|
||||
return
|
||||
}
|
||||
}
|
||||
requireCaptcha := security == nil || security.CaptchaOpen == 0
|
||||
if security != nil && security.CaptchaOpen > 0 {
|
||||
failures, _ := cachedInt(c.Request.Context(), svc, "login:fail:"+c.ClientIP())
|
||||
requireCaptcha = failures >= security.CaptchaOpen
|
||||
}
|
||||
if requireCaptcha && (req.CaptchaID == "" || req.Captcha == "" || !store.Verify(req.CaptchaID, req.Captcha, true)) {
|
||||
fail(c, "验证码错误")
|
||||
return
|
||||
}
|
||||
result, err := svc.Login(c.Request.Context(), req.Username, req.Password)
|
||||
if err != nil {
|
||||
_ = audit.RecordLogin(c.Request.Context(), &biz.LoginLog{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
|
||||
}
|
||||
failures, _ := incrementCached(c.Request.Context(), svc, "login:fail:"+c.ClientIP(), ttl)
|
||||
if security != nil && security.LockEnable && failures >= security.LockThreshold {
|
||||
_ = svc.CacheSet(c.Request.Context(), "login:lock:"+req.Username, "1", time.Duration(security.LockDuration)*time.Minute)
|
||||
}
|
||||
fail(c, "用户名不存在或者密码错误")
|
||||
return
|
||||
}
|
||||
userID, _ := result.User["ID"].(uint)
|
||||
_ = audit.RecordLogin(c.Request.Context(), &biz.LoginLog{Username: req.Username, IP: c.ClientIP(), Status: true, Agent: c.Request.UserAgent(), UserID: userID})
|
||||
_ = svc.CacheDelete(c.Request.Context(), "login:fail:"+c.ClientIP())
|
||||
_ = svc.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})
|
||||
writeResult(c, codeSuccess, result, "登录成功")
|
||||
})
|
||||
initGroup := group.Group("/init")
|
||||
initGroup.POST("/checkdb", func(c *gin.Context) {
|
||||
initialized, err := svc.IsInitialized(c.Request.Context())
|
||||
if err != nil {
|
||||
fail(c, "数据库状态检查失败")
|
||||
return
|
||||
}
|
||||
message := "数据库无需初始化"
|
||||
if !initialized {
|
||||
message = "前往初始化数据库"
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"needInit": !initialized}, message)
|
||||
})
|
||||
initGroup.POST("/initdb", func(c *gin.Context) {
|
||||
if err := svc.Initialize(c.Request.Context()); err != nil {
|
||||
fail(c, "自动创建数据库失败")
|
||||
return
|
||||
}
|
||||
routes := engine.Routes()
|
||||
apis := make([]*biz.API, 0, len(routes))
|
||||
for _, route := range routes {
|
||||
apis = append(apis, &biz.API{Path: route.Path, Method: route.Method, APIGroup: routeGroup(route.Path)})
|
||||
}
|
||||
if err := access.ApplyAPISync(c.Request.Context(), apis, nil); err != nil {
|
||||
fail(c, "数据库已初始化,但 API 权限数据写入失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{}, "自动创建数据库成功")
|
||||
})
|
||||
}
|
||||
|
||||
func routeGroup(path string) string {
|
||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||
if len(parts) > 0 && parts[0] != "" {
|
||||
return parts[0]
|
||||
}
|
||||
return "base"
|
||||
}
|
||||
|
||||
func cachedInt(ctx context.Context, svc *service.SystemService, key string) (int, error) {
|
||||
value, ok, err := svc.CacheGet(ctx, key)
|
||||
if err != nil || !ok {
|
||||
return 0, err
|
||||
}
|
||||
result, err := strconv.Atoi(value)
|
||||
return result, err
|
||||
}
|
||||
func incrementCached(ctx context.Context, svc *service.SystemService, key string, ttl time.Duration) (int, error) {
|
||||
value, _ := cachedInt(ctx, svc, key)
|
||||
value++
|
||||
return value, svc.CacheSet(ctx, key, strconv.Itoa(value), ttl)
|
||||
}
|
||||
func securityRateLimit(svc *service.SystemService, settings *service.SettingsService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
cfg, err := settings.CurrentSecurity(c.Request.Context())
|
||||
if err != nil || cfg == nil || !cfg.LimitEnable {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
window := cfg.LimitWindow
|
||||
if window < 1 {
|
||||
window = 60
|
||||
}
|
||||
bucket := time.Now().Unix() / int64(window)
|
||||
key := "rate:" + c.ClientIP() + ":" + strconv.FormatInt(bucket, 10)
|
||||
count, cacheErr := incrementCached(c.Request.Context(), svc, key, time.Duration(window+1)*time.Second)
|
||||
if cacheErr == nil && count > cfg.LimitCount {
|
||||
c.AbortWithStatusJSON(http.StatusTooManyRequests, response{Code: codeError, Data: nil, Msg: "请求过于频繁"})
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
type captchaStore struct {
|
||||
service *service.SystemService
|
||||
expiration time.Duration
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
func (s *captchaStore) Get(id string, clear bool) string {
|
||||
value, ok, err := s.service.CacheGet(context.Background(), s.key(id))
|
||||
if err != nil || !ok {
|
||||
return ""
|
||||
}
|
||||
if clear {
|
||||
_ = s.service.CacheDelete(context.Background(), s.key(id))
|
||||
}
|
||||
return value
|
||||
}
|
||||
func (s *captchaStore) Verify(id, answer string, clear bool) bool {
|
||||
if id == "" || answer == "" {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(s.Get(id, clear), answer)
|
||||
}
|
||||
|
||||
type filesOnly struct{ http.FileSystem }
|
||||
|
||||
func (f filesOnly) Open(name string) (http.File, error) {
|
||||
file, err := f.FileSystem.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, statErr := file.Stat()
|
||||
if statErr != nil {
|
||||
_ = file.Close()
|
||||
return nil, statErr
|
||||
}
|
||||
if info.IsDir() {
|
||||
_ = file.Close()
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func registerPrivateRoutes(group *gin.RouterGroup, svc *service.SystemService) {
|
||||
user := group.Group("/user")
|
||||
user.POST("/getUserList", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Username string `json:"username"`
|
||||
NickName string `json:"nickName"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
OrderKey string `json:"orderKey"`
|
||||
Desc bool `json:"desc"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
list, total, err := svc.ListUsers(c.Request.Context(), req.Page, req.PageSize, &biz.UserListFilter{Username: req.Username, NickName: req.NickName, Phone: req.Phone, Email: req.Email, OrderKey: req.OrderKey, Desc: req.Desc})
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: list, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
|
||||
})
|
||||
user.POST("/admin_register", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"userName"`
|
||||
Password string `json:"passWord"`
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
AuthorityID uint `json:"authorityId"`
|
||||
AuthorityIDs []uint `json:"authorityIds"`
|
||||
Enable int `json:"enable"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil || req.Username == "" || req.Password == "" {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
result, err := svc.CreateUser(c.Request.Context(), service.UserInput{Username: req.Username, Password: req.Password, NickName: req.NickName, HeaderImg: req.HeaderImg, AuthorityID: req.AuthorityID, AuthorityIDs: req.AuthorityIDs, Enable: req.Enable, Phone: req.Phone, Email: req.Email})
|
||||
if err != nil {
|
||||
fail(c, "注册失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"user": result}, "注册成功")
|
||||
})
|
||||
user.PUT("/setUserInfo", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
AuthorityID uint `json:"authorityId"`
|
||||
AuthorityIDs []uint `json:"authorityIds"`
|
||||
Enable int `json:"enable"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.UpdateUser(c.Request.Context(), service.UserInput{ID: req.ID, NickName: req.NickName, HeaderImg: req.HeaderImg, AuthorityID: req.AuthorityID, AuthorityIDs: req.AuthorityIDs, Enable: req.Enable, Phone: req.Phone, Email: req.Email}); err != nil {
|
||||
fail(c, "修改失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
user.PUT("/setSelfInfo", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
if claims == nil {
|
||||
noAuth(c, "未登录或非法访问")
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.UpdateUser(c.Request.Context(), service.UserInput{ID: claims.ID, NickName: req.NickName, HeaderImg: req.HeaderImg, Phone: req.Phone, Email: req.Email, Enable: 1}); err != nil {
|
||||
fail(c, "修改失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
user.DELETE("/deleteUser", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `form:"id" json:"id"`
|
||||
}
|
||||
_ = c.ShouldBind(&req)
|
||||
if req.ID == 0 {
|
||||
var body struct {
|
||||
ID uint `json:"ID"`
|
||||
}
|
||||
_ = c.ShouldBindJSON(&body)
|
||||
req.ID = body.ID
|
||||
}
|
||||
if err := svc.DeleteUser(c.Request.Context(), req.ID); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
user.POST("/resetPassword", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.ResetPassword(c.Request.Context(), req.ID, req.Password); err != nil {
|
||||
fail(c, "重置失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
user.POST("/changePassword", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
var req struct {
|
||||
Password string `json:"password"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
if claims == nil || c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.ChangePassword(c.Request.Context(), claims.ID, req.Password, req.NewPassword); err != nil {
|
||||
fail(c, "修改失败,原密码与当前账户不符")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
user.PUT("/setSelfSetting", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
var setting map[string]any
|
||||
if claims == nil || c.ShouldBindJSON(&setting) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.SetUserSetting(c.Request.Context(), claims.ID, setting); err != nil {
|
||||
fail(c, "设置失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
user.POST("/setUserAuthorities", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
AuthorityIDs []uint `json:"authorityIds"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil || req.ID == 0 || len(req.AuthorityIDs) == 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.SetUserAuthorities(c.Request.Context(), req.ID, req.AuthorityIDs); err != nil {
|
||||
fail(c, "修改失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
user.POST("/setUserAuthority", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
var req struct {
|
||||
AuthorityID uint `json:"authorityId"`
|
||||
}
|
||||
if claims == nil || c.ShouldBindJSON(&req) != nil || req.AuthorityID == 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
login, err := svc.SwitchAuthority(c.Request.Context(), claims.ID, req.AuthorityID)
|
||||
if err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
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})
|
||||
ok(c)
|
||||
})
|
||||
user.GET("/getUserInfo", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
if claims == nil {
|
||||
noAuth(c, "未登录或非法访问")
|
||||
return
|
||||
}
|
||||
result, err := svc.User(c.Request.Context(), claims.ID)
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"userInfo": result}, "获取成功")
|
||||
})
|
||||
menu := group.Group("/menu")
|
||||
menu.POST("/getMenu", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
if claims == nil {
|
||||
noAuth(c, "未登录或非法访问")
|
||||
return
|
||||
}
|
||||
menus, err := svc.Menus(c.Request.Context(), claims.AuthorityID)
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"menus": menus}, "获取成功")
|
||||
})
|
||||
jwt := group.Group("/jwt")
|
||||
jwt.POST("/jsonInBlacklist", func(c *gin.Context) {
|
||||
http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: "", Path: "/", MaxAge: -1, HttpOnly: true})
|
||||
ok(c)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func registerMediaRoutes(group *gin.RouterGroup, svc *service.MediaService) {
|
||||
files := group.Group("/fileUploadAndDownload")
|
||||
files.POST("/upload", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
header, err := c.FormFile("file")
|
||||
if claims == nil || err != nil {
|
||||
fail(c, "接收文件失败")
|
||||
return
|
||||
}
|
||||
opened, err := header.Open()
|
||||
if err != nil {
|
||||
fail(c, "读取文件失败")
|
||||
return
|
||||
}
|
||||
defer opened.Close()
|
||||
category, _ := strconv.Atoi(c.DefaultPostForm("classId", "0"))
|
||||
save := c.DefaultQuery("noSave", "0") == "0"
|
||||
item, err := svc.Upload(c.Request.Context(), claims.ID, header.Filename, header.Header.Get("Content-Type"), category, opened, save)
|
||||
if err != nil {
|
||||
fail(c, "上传失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"file": item}, "上传成功")
|
||||
})
|
||||
files.POST("/getFileList", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Keyword string `json:"keyword"`
|
||||
ClassID int `json:"classId"`
|
||||
Tag string `json:"tag"`
|
||||
UserID uint `json:"userId"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
items, total, err := svc.MediaList(c.Request.Context(), req.Page, req.PageSize, req.Keyword, req.ClassID, req.Tag, req.UserID)
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
|
||||
})
|
||||
files.POST("/deleteFile", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.Delete(c.Request.Context(), req.ID); err != nil {
|
||||
fail(c, "删除失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
files.POST("/deleteFiles", func(c *gin.Context) {
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
failed := []uint{}
|
||||
for _, id := range req.IDs {
|
||||
if svc.Delete(c.Request.Context(), id) != nil {
|
||||
failed = append(failed, id)
|
||||
}
|
||||
}
|
||||
if len(failed) > 0 {
|
||||
writeResult(c, codeSuccess, gin.H{"failedIds": failed}, "部分文件删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
files.GET("/findFile", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
|
||||
item, err := svc.Media(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, item, "获取成功")
|
||||
})
|
||||
files.POST("/editFileName", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.Rename(c.Request.Context(), req.ID, req.Name); err != nil {
|
||||
fail(c, "编辑失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
files.POST("/importURL", func(c *gin.Context) {
|
||||
var req []struct {
|
||||
Name string `json:"name"`
|
||||
ClassID int `json:"classId"`
|
||||
URL string `json:"url"`
|
||||
Tag string `json:"tag"`
|
||||
Key string `json:"key"`
|
||||
Size int64 `json:"size"`
|
||||
Mime string `json:"mime"`
|
||||
MD5 string `json:"md5"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
claims := currentClaims(c)
|
||||
items := make([]*biz.MediaFile, 0, len(req))
|
||||
for _, v := range req {
|
||||
items = append(items, &biz.MediaFile{Name: v.Name, CategoryID: v.ClassID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: claims.ID})
|
||||
}
|
||||
if err := svc.ImportURLs(c.Request.Context(), items); err != nil {
|
||||
fail(c, "导入失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
files.POST("/listOssFiles", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Prefix string `json:"prefix"`
|
||||
Cursor string `json:"cursor"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
items, next, more, err := svc.Storage(c.Request.Context(), req.Prefix, req.Cursor, req.Limit)
|
||||
if err != nil {
|
||||
fail(c, "列举失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"list": items, "nextCursor": next, "hasMore": more}, "获取成功")
|
||||
})
|
||||
categories := group.Group("/attachmentCategory")
|
||||
categories.GET("/getCategoryList", func(c *gin.Context) {
|
||||
items, err := svc.Categories(c.Request.Context())
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, items, "获取成功")
|
||||
})
|
||||
categories.POST("/addCategory", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
Name string `json:"name"`
|
||||
PID uint `json:"pid"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil || req.Name == "" {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.SaveCategory(c.Request.Context(), &biz.AttachmentCategory{ID: req.ID, Name: req.Name, ParentID: req.PID}); err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
categories.POST("/deleteCategory", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.DeleteCategory(c.Request.Context(), req.ID); err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
upload := group.Group("/mediaUpload")
|
||||
upload.POST("/init", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
var req struct {
|
||||
FileName string `json:"fileName"`
|
||||
FileHash string `json:"fileHash"`
|
||||
FileSize int64 `json:"fileSize"`
|
||||
ChunkSize int64 `json:"chunkSize"`
|
||||
ChunkTotal int `json:"chunkTotal"`
|
||||
}
|
||||
if claims == nil || c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
result, err := svc.InitUpload(c.Request.Context(), claims.ID, req.FileName, req.FileHash, req.FileSize, req.ChunkSize, req.ChunkTotal)
|
||||
if err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, result, "获取成功")
|
||||
})
|
||||
upload.POST("/chunk", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
uploadID, _ := strconv.ParseUint(c.PostForm("uploadId"), 10, 64)
|
||||
index, _ := strconv.Atoi(c.PostForm("chunkIndex"))
|
||||
header, err := c.FormFile("chunk")
|
||||
if claims == nil || err != nil {
|
||||
fail(c, "接收分片失败")
|
||||
return
|
||||
}
|
||||
opened, err := header.Open()
|
||||
if err != nil {
|
||||
fail(c, "读取分片失败")
|
||||
return
|
||||
}
|
||||
defer opened.Close()
|
||||
if err = svc.SaveChunk(c.Request.Context(), claims.ID, uint(uploadID), index, c.PostForm("chunkHash"), opened); err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
upload.POST("/complete", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
var req struct {
|
||||
UploadID uint `json:"uploadId"`
|
||||
}
|
||||
if claims == nil || c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
item, err := svc.CompleteUpload(c.Request.Context(), claims.ID, req.UploadID)
|
||||
if err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"media": item}, "上传成功")
|
||||
})
|
||||
upload.DELETE("/:uploadId", func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
id, _ := strconv.ParseUint(c.Param("uploadId"), 10, 64)
|
||||
if claims == nil {
|
||||
fail(c, "未登录")
|
||||
return
|
||||
}
|
||||
if err := svc.CancelUpload(c.Request.Context(), claims.ID, uint(id)); err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
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"`
|
||||
}
|
||||
|
||||
func menuDomain(v menuRequest) *biz.Menu {
|
||||
return &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}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if err := svc.ReplaceMenuButtons(c.Request.Context(), menu.ID, menuButtons(req, menu.ID)); 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
|
||||
}
|
||||
if err := svc.ReplaceMenuButtons(c.Request.Context(), req.ID, menuButtons(req, req.ID)); 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)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package server
|
||||
|
||||
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 writeResult(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) {
|
||||
writeResult(c, codeSuccess, gin.H{}, "操作成功")
|
||||
}
|
||||
|
||||
func okWithData(c *gin.Context, data any) {
|
||||
writeResult(c, codeSuccess, data, "成功")
|
||||
}
|
||||
|
||||
func fail(c *gin.Context, message string) {
|
||||
writeResult(c, codeError, gin.H{}, message)
|
||||
}
|
||||
|
||||
func noAuth(c *gin.Context, message string) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, response{
|
||||
Code: codeError,
|
||||
Data: nil,
|
||||
Msg: message,
|
||||
})
|
||||
}
|
||||
@@ -5,4 +5,4 @@ import (
|
||||
)
|
||||
|
||||
// ProviderSet is server providers.
|
||||
var ProviderSet = wire.NewSet(NewGRPCServer, NewHTTPServer)
|
||||
var ProviderSet = wire.NewSet(NewGRPCServer, NewGinServer, NewTaskScheduler)
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type dictionaryRequest struct {
|
||||
ID uint `json:"ID" form:"ID"`
|
||||
Name string `json:"name" form:"name"`
|
||||
Type string `json:"type" form:"type"`
|
||||
Status *bool `json:"status" form:"status"`
|
||||
Desc string `json:"desc" form:"desc"`
|
||||
ParentID *uint `json:"parentID" form:"parentID"`
|
||||
}
|
||||
|
||||
func dictionaryDomain(v dictionaryRequest) *biz.Dictionary {
|
||||
status := true
|
||||
if v.Status != nil {
|
||||
status = *v.Status
|
||||
}
|
||||
return &biz.Dictionary{ID: v.ID, Name: v.Name, Type: v.Type, Status: status, Desc: v.Desc, ParentID: v.ParentID}
|
||||
}
|
||||
|
||||
type detailRequest struct {
|
||||
ID uint `json:"ID" form:"ID"`
|
||||
Label string `json:"label" form:"label"`
|
||||
Value string `json:"value" form:"value"`
|
||||
Extend string `json:"extend" form:"extend"`
|
||||
Status *bool `json:"status" form:"status"`
|
||||
Sort int `json:"sort" form:"sort"`
|
||||
DictionaryID uint `json:"sysDictionaryID" form:"sysDictionaryID"`
|
||||
ParentID *uint `json:"parentID" form:"parentID"`
|
||||
Level int `json:"level"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func detailDomain(v detailRequest) *biz.DictionaryDetail {
|
||||
status := true
|
||||
if v.Status != nil {
|
||||
status = *v.Status
|
||||
}
|
||||
return &biz.DictionaryDetail{ID: v.ID, Label: v.Label, Value: v.Value, Extend: v.Extend, Status: status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID, Level: v.Level, Path: v.Path}
|
||||
}
|
||||
|
||||
func registerSettingsRoutes(group *gin.RouterGroup, public *gin.RouterGroup, svc *service.SettingsService) {
|
||||
registerDictionaryRoutes(group, svc)
|
||||
registerParameterRoutes(group, svc)
|
||||
registerAPITokenRoutes(group, svc)
|
||||
}
|
||||
|
||||
func registerDictionaryRoutes(group *gin.RouterGroup, svc *service.SettingsService) {
|
||||
dictionaries := group.Group("/sysDictionary")
|
||||
dictionaries.POST("/createSysDictionary", func(c *gin.Context) {
|
||||
var req dictionaryRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.Name == "" || req.Type == "" {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.CreateDictionary(c.Request.Context(), dictionaryDomain(req)); err != nil {
|
||||
fail(c, "创建失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
dictionaries.PUT("/updateSysDictionary", func(c *gin.Context) {
|
||||
var req dictionaryRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.UpdateDictionary(c.Request.Context(), dictionaryDomain(req)); err != nil {
|
||||
fail(c, "更新失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
dictionaries.DELETE("/deleteSysDictionary", func(c *gin.Context) {
|
||||
var req dictionaryRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.DeleteDictionary(c.Request.Context(), req.ID); err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
dictionaries.GET("/findSysDictionary", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.Dictionary(c.Request.Context(), uint(id), c.Query("type"), true)
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"reSysDictionary": item}, "查询成功")
|
||||
})
|
||||
listHandler := func(details bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
items, total, err := svc.Dictionaries(c.Request.Context(), page, size, c.Query("name"), c.Query("type"), details)
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
|
||||
}
|
||||
}
|
||||
dictionaries.GET("/getSysDictionaryList", listHandler(false))
|
||||
dictionaries.GET("/getSysDictionaryListWithDetails", listHandler(true))
|
||||
dictionaries.GET("/exportSysDictionary", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.Dictionary(c.Request.Context(), uint(id), "", true)
|
||||
if err != nil {
|
||||
fail(c, "导出失败")
|
||||
return
|
||||
}
|
||||
delete(item, "ID")
|
||||
delete(item, "CreatedAt")
|
||||
delete(item, "UpdatedAt")
|
||||
delete(item, "DeletedAt")
|
||||
writeResult(c, codeSuccess, item, "导出成功")
|
||||
})
|
||||
dictionaries.POST("/importSysDictionary", func(c *gin.Context) {
|
||||
var req struct {
|
||||
JSON string `json:"json"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
var payload struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status *bool `json:"status"`
|
||||
Desc string `json:"desc"`
|
||||
Details []detailRequest `json:"sysDictionaryDetails"`
|
||||
}
|
||||
if json.Unmarshal([]byte(req.JSON), &payload) != nil || payload.Name == "" || payload.Type == "" {
|
||||
fail(c, "JSON 格式错误")
|
||||
return
|
||||
}
|
||||
dictionary := dictionaryDomain(dictionaryRequest{Name: payload.Name, Type: payload.Type, Status: payload.Status, Desc: payload.Desc})
|
||||
if err := svc.CreateDictionary(c.Request.Context(), dictionary); err != nil {
|
||||
fail(c, "导入失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
mapping := map[uint]uint{}
|
||||
pending := append([]detailRequest(nil), payload.Details...)
|
||||
for len(pending) > 0 {
|
||||
progress := false
|
||||
next := pending[:0]
|
||||
for _, item := range pending {
|
||||
oldID := item.ID
|
||||
if item.ParentID != nil && *item.ParentID != 0 {
|
||||
parent, exists := mapping[*item.ParentID]
|
||||
if !exists {
|
||||
next = append(next, item)
|
||||
continue
|
||||
}
|
||||
item.ParentID = &parent
|
||||
}
|
||||
value := detailDomain(item)
|
||||
value.ID = 0
|
||||
value.DictionaryID = dictionary.ID
|
||||
if err := svc.CreateDictionaryDetail(c.Request.Context(), value); err != nil {
|
||||
fail(c, "导入详情失败")
|
||||
return
|
||||
}
|
||||
if oldID != 0 {
|
||||
mapping[oldID] = value.ID
|
||||
}
|
||||
progress = true
|
||||
}
|
||||
if !progress {
|
||||
fail(c, "字典层级关系无效")
|
||||
return
|
||||
}
|
||||
pending = next
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
|
||||
details := group.Group("/sysDictionaryDetail")
|
||||
details.POST("/createSysDictionaryDetail", func(c *gin.Context) {
|
||||
var req detailRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.DictionaryID == 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.CreateDictionaryDetail(c.Request.Context(), detailDomain(req)); err != nil {
|
||||
fail(c, "创建失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
details.PUT("/updateSysDictionaryDetail", func(c *gin.Context) {
|
||||
var req detailRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.UpdateDictionaryDetail(c.Request.Context(), detailDomain(req)); err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
details.DELETE("/deleteSysDictionaryDetail", func(c *gin.Context) {
|
||||
var req detailRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.DeleteDictionaryDetail(c.Request.Context(), req.ID); err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
details.GET("/findSysDictionaryDetail", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.DictionaryDetail(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"reSysDictionaryDetail": item}, "查询成功")
|
||||
})
|
||||
details.GET("/getSysDictionaryDetailList", func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
did, _ := strconv.ParseUint(c.Query("sysDictionaryID"), 10, 64)
|
||||
items, total, err := svc.DictionaryDetails(c.Request.Context(), page, size, uint(did), c.Query("label"), c.Query("value"))
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
|
||||
})
|
||||
treeHandler := func(byType bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("sysDictionaryID"), 10, 64)
|
||||
typ := ""
|
||||
if byType {
|
||||
typ = c.Query("type")
|
||||
}
|
||||
items, err := svc.DictionaryTree(c.Request.Context(), uint(id), typ)
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, items, "获取成功")
|
||||
}
|
||||
}
|
||||
details.GET("/getDictionaryTreeList", treeHandler(false))
|
||||
details.GET("/getDictionaryTreeListByType", treeHandler(true))
|
||||
details.GET("/getDictionaryDetailsByParent", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("parentID"), 10, 64)
|
||||
did, _ := strconv.ParseUint(c.Query("sysDictionaryID"), 10, 64)
|
||||
items, _, err := svc.DictionaryDetails(c.Request.Context(), 1, 10000, uint(did), "", "")
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
out := []map[string]any{}
|
||||
for _, item := range items {
|
||||
parent, _ := item["parentID"].(*uint)
|
||||
if (id == 0 && parent == nil) || (parent != nil && uint64(*parent) == id) {
|
||||
out = append(out, item)
|
||||
}
|
||||
}
|
||||
writeResult(c, codeSuccess, out, "获取成功")
|
||||
})
|
||||
details.GET("/getDictionaryPath", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.DictionaryDetail(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"path": item["path"]}, "获取成功")
|
||||
})
|
||||
}
|
||||
|
||||
type parameterRequest struct {
|
||||
ID uint `json:"ID" form:"ID"`
|
||||
Name string `json:"name" form:"name"`
|
||||
Key string `json:"key" form:"key"`
|
||||
Value string `json:"value" form:"value"`
|
||||
Desc string `json:"desc" form:"desc"`
|
||||
}
|
||||
|
||||
func parameterDomain(v parameterRequest) *biz.SystemParameter {
|
||||
return &biz.SystemParameter{ID: v.ID, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc}
|
||||
}
|
||||
func registerParameterRoutes(group *gin.RouterGroup, svc *service.SettingsService) {
|
||||
router := group.Group("/sysParams")
|
||||
router.POST("/createSysParams", func(c *gin.Context) {
|
||||
var req parameterRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.Key == "" {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.CreateParameter(c.Request.Context(), parameterDomain(req)); err != nil {
|
||||
fail(c, "创建失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.PUT("/updateSysParams", func(c *gin.Context) {
|
||||
var req parameterRequest
|
||||
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.UpdateParameter(c.Request.Context(), parameterDomain(req)); err != nil {
|
||||
fail(c, "更新失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.DELETE("/deleteSysParams", func(c *gin.Context) {
|
||||
var req parameterRequest
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.DeleteParameters(c.Request.Context(), []uint{req.ID}); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.DELETE("/deleteSysParamsByIds", func(c *gin.Context) {
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.DeleteParameters(c.Request.Context(), req.IDs); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.GET("/findSysParams", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.Parameter(c.Request.Context(), uint(id), "")
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"resysParams": item}, "查询成功")
|
||||
})
|
||||
router.GET("/getSysParam", func(c *gin.Context) {
|
||||
item, err := svc.Parameter(c.Request.Context(), 0, c.Query("key"))
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"data": item}, "查询成功")
|
||||
})
|
||||
router.GET("/getSysParamsList", func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
items, total, err := svc.Parameters(c.Request.Context(), page, size, &biz.SystemParameter{Name: c.Query("name"), Key: c.Query("key")})
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
|
||||
})
|
||||
}
|
||||
|
||||
func registerAPITokenRoutes(group *gin.RouterGroup, svc *service.SettingsService) {
|
||||
router := group.Group("/sysApiToken")
|
||||
router.POST("/createApiToken", func(c *gin.Context) {
|
||||
var req struct {
|
||||
UserID uint `json:"userId"`
|
||||
AuthorityID uint `json:"authorityId"`
|
||||
Days int `json:"days"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
token, err := svc.CreateAPIToken(c.Request.Context(), req.UserID, req.AuthorityID, req.Days, req.Remark)
|
||||
if err != nil {
|
||||
fail(c, "签发失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"token": token}, "签发成功")
|
||||
})
|
||||
router.POST("/getApiTokenList", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
UserID uint `json:"userId"`
|
||||
Status *bool `json:"status"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
items, total, err := svc.APITokens(c.Request.Context(), req.Page, req.PageSize, req.UserID, req.Status)
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
|
||||
})
|
||||
router.POST("/deleteApiToken", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.DisableAPIToken(c.Request.Context(), req.ID); err != nil {
|
||||
fail(c, "作废失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, settings *service.SettingsService) {
|
||||
security := group.Group("/securityConfig")
|
||||
security.GET("/getSecurityConfig", func(c *gin.Context) {
|
||||
value, err := settings.Security(c.Request.Context())
|
||||
if err != nil {
|
||||
fail(c, "获取安全配置失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, value, "获取成功")
|
||||
})
|
||||
security.POST("/setSecurityConfig", func(c *gin.Context) {
|
||||
var value biz.SecurityConfig
|
||||
if c.ShouldBindJSON(&value) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
result, err := settings.SaveSecurity(c.Request.Context(), &value)
|
||||
if err != nil {
|
||||
fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, result, "设置成功")
|
||||
})
|
||||
|
||||
system := group.Group("/system")
|
||||
system.POST("/getSystemConfig", func(c *gin.Context) {
|
||||
admin := gin.H{"routerPrefix": ""}
|
||||
if config != nil {
|
||||
admin["routerPrefix"] = config.RouterPrefix
|
||||
if config.Jwt != nil {
|
||||
admin["jwt"] = gin.H{"signingKey": "******", "expiresTime": config.Jwt.ExpiresTime.AsDuration().String(), "bufferTime": config.Jwt.BufferTime.AsDuration().String(), "issuer": config.Jwt.Issuer}
|
||||
}
|
||||
if config.Captcha != nil {
|
||||
admin["captcha"] = gin.H{"keyLong": config.Captcha.KeyLong, "imgWidth": config.Captcha.ImgWidth, "imgHeight": config.Captcha.ImgHeight, "storeExpiration": config.Captcha.StoreExpiration.AsDuration().String()}
|
||||
}
|
||||
if config.Local != nil {
|
||||
admin["local"] = gin.H{"storePath": config.Local.StorePath, "pathPrefix": config.Local.PathPrefix}
|
||||
}
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"config": gin.H{"admin": admin}}, "获取成功")
|
||||
})
|
||||
system.POST("/setSystemConfig", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Config struct {
|
||||
Admin struct {
|
||||
RouterPrefix string `json:"routerPrefix"`
|
||||
JWT struct {
|
||||
SigningKey string `json:"signingKey"`
|
||||
Issuer string `json:"issuer"`
|
||||
} `json:"jwt"`
|
||||
Local struct {
|
||||
StorePath string `json:"storePath"`
|
||||
PathPrefix string `json:"pathPrefix"`
|
||||
} `json:"local"`
|
||||
} `json:"admin"`
|
||||
} `json:"config"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if config != nil {
|
||||
config.RouterPrefix = req.Config.Admin.RouterPrefix
|
||||
if config.Jwt != nil {
|
||||
if req.Config.Admin.JWT.SigningKey != "" && req.Config.Admin.JWT.SigningKey != "******" {
|
||||
config.Jwt.SigningKey = req.Config.Admin.JWT.SigningKey
|
||||
}
|
||||
if req.Config.Admin.JWT.Issuer != "" {
|
||||
config.Jwt.Issuer = req.Config.Admin.JWT.Issuer
|
||||
}
|
||||
}
|
||||
if config.Local != nil {
|
||||
if req.Config.Admin.Local.StorePath != "" {
|
||||
config.Local.StorePath = req.Config.Admin.Local.StorePath
|
||||
}
|
||||
if req.Config.Admin.Local.PathPrefix != "" {
|
||||
config.Local.PathPrefix = req.Config.Admin.Local.PathPrefix
|
||||
}
|
||||
}
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
system.POST("/reloadSystem", func(c *gin.Context) { writeResult(c, codeSuccess, gin.H{"reload": true}, "配置已热更新") })
|
||||
system.POST("/getServerInfo", func(c *gin.Context) {
|
||||
var mem runtime.MemStats
|
||||
runtime.ReadMemStats(&mem)
|
||||
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)
|
||||
}
|
||||
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})
|
||||
}
|
||||
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": []float64{}, "cores": runtime.NumCPU()}, "ram": gin.H{"usedMb": mem.Alloc / 1024 / 1024, "totalMb": mem.Sys / 1024 / 1024, "usedPercent": 0}, "disk": disk}
|
||||
writeResult(c, codeSuccess, gin.H{"server": server}, "获取成功")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
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) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
scheduler.Remove(uint(id))
|
||||
if err := svc.Delete(c.Request.Context(), uint(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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
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) Schedule(task *biz.TimedTask) error {
|
||||
s.Remove(task.ID)
|
||||
if !task.Enabled {
|
||||
return nil
|
||||
}
|
||||
copy := *task
|
||||
run := func() {
|
||||
log := s.service.Run(context.Background(), ©, "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:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
)
|
||||
|
||||
type versionMenuRequest struct {
|
||||
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 {
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc"`
|
||||
} `json:"menuBtn"`
|
||||
Children []versionMenuRequest `json:"children"`
|
||||
}
|
||||
|
||||
func versionMenuDomain(v versionMenuRequest) *biz.Menu {
|
||||
m := &biz.Menu{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}
|
||||
for _, b := range v.MenuButtons {
|
||||
m.Buttons = append(m.Buttons, &biz.MenuButton{Name: b.Name, Description: b.Desc})
|
||||
}
|
||||
for _, child := range v.Children {
|
||||
m.Children = append(m.Children, versionMenuDomain(child))
|
||||
}
|
||||
return m
|
||||
}
|
||||
func registerVersionRoutes(group *gin.RouterGroup, svc *service.VersionService) {
|
||||
router := group.Group("/sysVersion")
|
||||
router.DELETE("/deleteSysVersion", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
if err := svc.DeleteVersions(c.Request.Context(), []uint{uint(id)}); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.DELETE("/deleteSysVersionByIds", func(c *gin.Context) {
|
||||
raw := c.QueryArray("IDs[]")
|
||||
ids := make([]uint, 0, len(raw))
|
||||
for _, v := range raw {
|
||||
id, _ := strconv.ParseUint(v, 10, 64)
|
||||
ids = append(ids, uint(id))
|
||||
}
|
||||
if err := svc.DeleteVersions(c.Request.Context(), ids); err != nil {
|
||||
fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.GET("/findSysVersion", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := svc.Version(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, item, "查询成功")
|
||||
})
|
||||
router.GET("/getSysVersionList", func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
items, total, err := svc.Versions(c.Request.Context(), page, size, c.Query("versionName"), c.Query("versionCode"))
|
||||
if err != nil {
|
||||
fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功")
|
||||
})
|
||||
router.POST("/exportVersion", func(c *gin.Context) {
|
||||
var req struct {
|
||||
VersionName string `json:"versionName"`
|
||||
VersionCode string `json:"versionCode"`
|
||||
Description string `json:"description"`
|
||||
MenuIDs []uint `json:"menuIds"`
|
||||
APIIDs []uint `json:"apiIds"`
|
||||
DictIDs []uint `json:"dictIds"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil || req.VersionName == "" || req.VersionCode == "" {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := svc.Export(c.Request.Context(), req.VersionName, req.VersionCode, req.Description, req.MenuIDs, req.APIIDs, req.DictIDs); err != nil {
|
||||
fail(c, "创建发版失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
router.GET("/downloadVersionJson", func(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
raw, code, err := svc.VersionData(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
fail(c, "下载失败")
|
||||
return
|
||||
}
|
||||
name := fmt.Sprintf("version_%s_%s.json", code, time.Now().Format("20060102150405"))
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name))
|
||||
c.Data(http.StatusOK, "application/json; charset=utf-8", []byte(raw))
|
||||
})
|
||||
router.POST("/importVersion", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Version struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description"`
|
||||
ExportTime string `json:"exportTime"`
|
||||
} `json:"version"`
|
||||
Menus []versionMenuRequest `json:"menus"`
|
||||
APIs []struct {
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
APIGroup string `json:"apiGroup"`
|
||||
Method string `json:"method"`
|
||||
} `json:"apis"`
|
||||
Dictionaries []struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status bool `json:"status"`
|
||||
Desc string `json:"desc"`
|
||||
Details []detailRequest `json:"sysDictionaryDetails"`
|
||||
} `json:"dictionaries"`
|
||||
}
|
||||
if c.ShouldBindJSON(&req) != nil {
|
||||
fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
bundle := &biz.VersionBundle{Name: req.Version.Name, Code: req.Version.Code, Description: req.Version.Description, ExportTime: req.Version.ExportTime}
|
||||
for _, m := range req.Menus {
|
||||
bundle.Menus = append(bundle.Menus, versionMenuDomain(m))
|
||||
}
|
||||
for _, a := range req.APIs {
|
||||
bundle.APIs = append(bundle.APIs, &biz.API{Path: a.Path, Description: a.Description, APIGroup: a.APIGroup, Method: a.Method})
|
||||
}
|
||||
for _, d := range req.Dictionaries {
|
||||
dictionary := &biz.Dictionary{Name: d.Name, Type: d.Type, Status: d.Status, Desc: d.Desc}
|
||||
for _, detail := range d.Details {
|
||||
dictionary.Details = append(dictionary.Details, detailDomain(detail))
|
||||
}
|
||||
bundle.Dictionaries = append(bundle.Dictionaries, dictionary)
|
||||
}
|
||||
if err := svc.Import(c.Request.Context(), bundle); err != nil {
|
||||
fail(c, "导入失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(c)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user