refactor: separate backend domains and storage adapters

This commit is contained in:
2026-08-14 01:37:56 +08:00
parent e6775f0966
commit 8c4c6ba661
102 changed files with 6644 additions and 5735 deletions
-101
View File
@@ -1,101 +0,0 @@
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 isBootstrapPath(c.Request.URL.Path) || 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 ""
}
-263
View File
@@ -1,263 +0,0 @@
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) {
var req struct {
ID uint `json:"ID"`
}
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
fail(c, "参数错误")
return
}
if err := svc.DeleteOperations(c.Request.Context(), []uint{req.ID}); err != nil {
fail(c, "删除失败")
return
}
ok(c)
})
operations.DELETE("/deleteSysOperationRecordByIds", func(c *gin.Context) {
var req struct {
IDs []uint `json:"ids"`
}
if c.ShouldBindJSON(&req) != nil || len(req.IDs) == 0 {
fail(c, "参数错误")
return
}
if err := svc.DeleteOperations(c.Request.Context(), req.IDs); 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) {
var req struct {
ID uint `json:"ID"`
}
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
fail(c, "参数错误")
return
}
if err := svc.DeleteLogins(c.Request.Context(), []uint{req.ID}); err != nil {
fail(c, "删除失败")
return
}
ok(c)
})
logins.DELETE("/deleteLoginLogByIds", func(c *gin.Context) {
var req struct {
IDs []uint `json:"ids"`
}
if c.ShouldBindJSON(&req) != nil || len(req.IDs) == 0 {
fail(c, "参数错误")
return
}
if err := svc.DeleteLogins(c.Request.Context(), req.IDs); 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}, "获取成功")
})
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)
})
}
-224
View File
@@ -1,224 +0,0 @@
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))
}
+21 -471
View File
@@ -7,21 +7,16 @@ import (
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"kra/internal/biz"
"kra/internal/conf"
"kra/internal/server/handler"
"kra/internal/server/httpx"
servermiddleware "kra/internal/server/middleware"
serverrouter "kra/internal/server/router"
"kra/internal/service"
"kra/internal/worker"
"kra/pkg/adminauth"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
)
// GinServer adapts Gin to the Kratos transport.Server lifecycle.
@@ -33,34 +28,38 @@ type GinServer struct {
logger *slog.Logger
}
func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.SystemService, access *service.AccessService, authority *handler.Authority, menu *handler.Menu, api *handler.API, permission *handler.Permission, organization *handler.Organization, announcement *handler.Announcement, email *handler.Email, task *handler.Task, settings *service.SettingsService, versions *service.VersionService, exports *service.ExportService, audit *service.AuditService, media *service.MediaService, emails *service.EmailService, scheduler *worker.TaskScheduler, logger *slog.Logger) *GinServer {
func NewGinServer(c *conf.Server, config *conf.AdminBackend, system *service.SystemService, access *service.AccessService, authority *handler.Authority, menu *handler.Menu, api *handler.API, permission *handler.Permission, organization *handler.Organization, announcement *handler.Announcement, email *handler.Email, task *handler.Task, media *handler.Media, auditHandler *handler.Audit, export *handler.Export, version *handler.Version, dictionary *handler.Dictionary, parameter *handler.Parameter, apiToken *handler.APIToken, systemConfig *handler.SystemConfig, publicHandler *handler.Public, user *handler.User, navigation *handler.Navigation, session *handler.Session, settings *service.SettingsService, audit *service.AuditService, emails *service.EmailService, logger *slog.Logger) *GinServer {
gin.SetMode(gin.ReleaseMode)
engine := gin.New()
engine.Use(servermiddleware.RequestMeta(), emailErrorAlert(emails, logger), gin.Recovery(), securityRateLimit(svc, settings), operationAudit(audit))
engine.Use(servermiddleware.RequestMeta(), servermiddleware.EmailErrorAlert(emails, logger), gin.Recovery(), servermiddleware.SecurityRateLimit(system, settings), servermiddleware.OperationAudit(audit))
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, scheduler)
public.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, "ok") })
serverrouter.RegisterPublic(public, engine, publicHandler)
private := engine.Group(prefix)
private.Use(servermiddleware.Auth(config, settings), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(access, audit))
registerPrivateRoutes(private, svc, settings)
serverrouter.RegisterUser(private, user)
serverrouter.RegisterNavigation(private, navigation)
serverrouter.RegisterSession(private, session)
serverrouter.RegisterAuthority(private, authority)
serverrouter.RegisterMenu(private, menu)
serverrouter.RegisterAPI(private, engine, api)
serverrouter.RegisterPermission(private, permission)
serverrouter.RegisterOrganization(private, organization)
registerSettingsRoutes(private, public, settings)
registerSystemConfigRoutes(private, config, svc, settings, scheduler)
registerVersionRoutes(private, versions)
registerExportRoutes(private, public, svc, exports)
registerAuditRoutes(private, public, audit)
serverrouter.RegisterDictionary(private, dictionary)
serverrouter.RegisterParameter(private, parameter)
serverrouter.RegisterAPIToken(private, apiToken)
serverrouter.RegisterSystemConfig(private, systemConfig)
serverrouter.RegisterVersion(private, version)
serverrouter.RegisterExport(private, public, export)
serverrouter.RegisterAudit(private, public, auditHandler)
serverrouter.RegisterTask(private, task)
registerMediaRoutes(private, media)
serverrouter.RegisterMedia(private, media)
serverrouter.RegisterAnnouncement(private, public, announcement)
serverrouter.RegisterEmail(private, email)
@@ -68,11 +67,10 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System
if serveLocalStorage(c, config) {
return
}
fail(c, "请求的接口不存在")
httpx.Fail(c, "请求的接口不存在")
})
network := "tcp"
address := ":8000"
network, address := "tcp", ":8000"
if c != nil && c.Http != nil {
if c.Http.Network != "" {
network = c.Http.Network
@@ -81,13 +79,7 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System
address = c.Http.Addr
}
}
return &GinServer{
network: network,
address: address,
server: &http.Server{Addr: address, Handler: engine},
logger: logger,
}
return &GinServer{network: network, address: address, server: &http.Server{Addr: address, Handler: engine}, logger: logger}
}
// serveLocalStorage resolves the local path for every request so a config
@@ -107,219 +99,6 @@ func serveLocalStorage(c *gin.Context, config *conf.AdminBackend) bool {
return true
}
func currentClaims(c *gin.Context) *adminauth.Claims { return servermiddleware.Claims(c) }
func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *conf.AdminBackend, svc *service.SystemService, settings *service.SettingsService, access *service.AccessService, audit *service.AuditService, scheduler *worker.TaskScheduler) {
expiration := 3 * time.Minute
keyLong, width, height := 6, 240, 80
if config != nil && config.Captcha != nil {
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) {
initialized, err := svc.IsInitialized(c.Request.Context())
if err != nil {
fail(c, "数据库状态检查失败")
return
}
if initialized {
fail(c, "数据库已初始化,无需重复初始化")
return
}
var input service.DatabaseInit
if err := c.ShouldBindJSON(&input); 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 := svc.Initialize(c.Request.Context(), &input, apis); err != nil {
fail(c, "自动创建数据库失败: "+err.Error())
return
}
if err := scheduler.Reload(c.Request.Context()); err != nil {
fail(c, "数据库已初始化,但定时任务加载失败:"+err.Error())
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, err := svc.CacheIncrement(ctx, key, ttl)
return int(value), err
}
func securityRateLimit(svc *service.SystemService, settings *service.SettingsService) gin.HandlerFunc {
return func(c *gin.Context) {
if isBootstrapPath(c.Request.URL.Path) {
c.Next()
return
}
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()
}
}
func isBootstrapPath(path string) bool {
return strings.HasSuffix(path, "/health") || strings.Contains(path, "/init/")
}
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) {
@@ -339,235 +118,6 @@ func (f filesOnly) Open(name string) (http.File, error) {
return file, nil
}
func registerPrivateRoutes(group *gin.RouterGroup, svc *service.SystemService, settings *service.SettingsService) {
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) {
token := c.GetHeader("x-token")
if token == "" {
token, _ = c.Cookie("x-token")
}
if err := settings.BlacklistToken(c.Request.Context(), token); err != nil {
fail(c, "退出失败")
return
}
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 {
+58
View File
@@ -0,0 +1,58 @@
package handler
import (
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type APIToken struct {
service *service.SettingsService
}
func NewAPIToken(service *service.SettingsService) *APIToken {
return &APIToken{service: service}
}
func (h *APIToken) Create(c *gin.Context) {
var req dto.CreateAPITokenRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
token, err := h.service.CreateAPIToken(c.Request.Context(), req.UserID, req.AuthorityID, req.Days, req.Remark)
if err != nil {
httpx.Fail(c, "签发失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"token": token}, "签发成功")
}
func (h *APIToken) List(c *gin.Context) {
var req dto.APITokenListRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
items, total, err := h.service.APITokens(c.Request.Context(), req.Page, req.PageSize, req.UserID, req.Status)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *APIToken) Delete(c *gin.Context) {
var req dto.IDRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DisableAPIToken(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "作废失败")
return
}
httpx.OK(c)
}
+241
View File
@@ -0,0 +1,241 @@
package handler
import (
"strconv"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Audit struct{ service *service.AuditService }
func NewAudit(service *service.AuditService) *Audit { return &Audit{service: service} }
func page(c *gin.Context) (int, int) {
value, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
return value, size
}
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 _, value := range values {
id, _ := strconv.ParseUint(value, 10, 64)
if id > 0 {
ids = append(ids, uint(id))
}
}
return ids
}
func (h *Audit) Operations(c *gin.Context) {
p, size := page(c)
status, _ := strconv.Atoi(c.Query("status"))
userID, _ := strconv.ParseUint(c.Query("user_id"), 10, 64)
items, total, err := h.service.OperationsFilter(c.Request.Context(), p, size, c.Query("path"), c.Query("method"), status, c.Query("ip"), uint(userID))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
func (h *Audit) Operation(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Operation(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"resysOperationRecord": item}, "查询成功")
}
func (h *Audit) DeleteOperation(c *gin.Context) {
var req dto.IDRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteOperations(c.Request.Context(), []uint{req.ID}); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Audit) DeleteOperations(c *gin.Context) {
var req dto.IDsRequest
if c.ShouldBindJSON(&req) != nil || len(req.IDs) == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteOperations(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Audit) Logins(c *gin.Context) {
p, size := page(c)
items, total, err := h.service.LoginsFilter(c.Request.Context(), p, size, c.Query("username"), c.Query("ip"))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
func (h *Audit) Login(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Login(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, item, "查询成功")
}
func (h *Audit) DeleteLogin(c *gin.Context) {
var req dto.IDRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteLogins(c.Request.Context(), []uint{req.ID}); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Audit) DeleteLogins(c *gin.Context) {
var req dto.IDsRequest
if c.ShouldBindJSON(&req) != nil || len(req.IDs) == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteLogins(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Audit) DataAccess(c *gin.Context) {
var req dto.DataAccessListRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
items, total, err := h.service.DataAccessRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Audit) DeleteDataAccess(c *gin.Context) {
var req dto.IDsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteDataAccess(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Audit) LogDates(c *gin.Context) {
data, err := h.service.LogDates(c.Request.Context(), c.Query("month"))
if err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, data, "获取成功")
}
func (h *Audit) LogFiles(c *gin.Context) {
data, err := h.service.LogFiles(c.Request.Context(), c.Query("date"))
if err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, data, "获取成功")
}
func (h *Audit) LogContent(c *gin.Context) {
var cursor *int64
if raw := c.Query("cursor"); raw != "" {
value, err := strconv.ParseInt(raw, 10, 64)
if err != nil || value < 0 {
httpx.Fail(c, "参数错误")
return
}
cursor = &value
}
data, err := h.service.LogContent(c.Request.Context(), c.Query("date"), c.Query("path"), cursor)
if err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, data, "获取成功")
}
func (h *Audit) DeleteError(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
if err := h.service.DeleteErrors(c.Request.Context(), []uint{uint(id)}); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Audit) DeleteErrors(c *gin.Context) {
if err := h.service.DeleteErrors(c.Request.Context(), IDsFromQuery(c)); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Audit) UpdateError(c *gin.Context) {
var req dto.ErrorRecordRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdateErrorRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
return
}
httpx.OK(c)
}
func (h *Audit) Error(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Error(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"resysError": item}, "查询成功")
}
func (h *Audit) Errors(c *gin.Context) {
p, size := page(c)
items, total, err := h.service.ErrorsFilter(c.Request.Context(), p, size, c.Query("form"), c.Query("level"), c.Query("status"))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
func (h *Audit) CreateError(c *gin.Context) {
var req dto.ErrorRecordRequest
if c.ShouldBindJSON(&req) != nil || req.Form == "" {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.CreateErrorRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败")
return
}
httpx.OK(c)
}
+189
View File
@@ -0,0 +1,189 @@
package handler
import (
"strconv"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Dictionary struct{ service *service.SettingsService }
func NewDictionary(service *service.SettingsService) *Dictionary {
return &Dictionary{service: service}
}
func (h *Dictionary) Create(c *gin.Context) {
var req dto.DictionaryRequest
if c.ShouldBindJSON(&req) != nil || req.Name == "" || req.Type == "" {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.CreateDictionaryRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
return
}
httpx.OK(c)
}
func (h *Dictionary) Update(c *gin.Context) {
var req dto.DictionaryRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdateDictionaryRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
return
}
httpx.OK(c)
}
func (h *Dictionary) Delete(c *gin.Context) {
var req dto.DictionaryRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteDictionary(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Dictionary) Find(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Dictionary(c.Request.Context(), uint(id), c.Query("type"), true)
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"reSysDictionary": item}, "查询成功")
}
func (h *Dictionary) List(details bool) gin.HandlerFunc {
return func(c *gin.Context) {
p, size := page(c)
items, total, err := h.service.Dictionaries(c.Request.Context(), p, size, c.Query("name"), c.Query("type"), details)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
}
func (h *Dictionary) Export(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Dictionary(c.Request.Context(), uint(id), "", true)
if err != nil {
httpx.Fail(c, "导出失败")
return
}
delete(item, "ID")
delete(item, "CreatedAt")
delete(item, "UpdatedAt")
delete(item, "DeletedAt")
httpx.Write(c, httpx.CodeSuccess, item, "导出成功")
}
func (h *Dictionary) Import(c *gin.Context) {
var req dto.ImportDictionaryRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.ImportDictionaryJSON(c.Request.Context(), req.JSON); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Dictionary) CreateDetail(c *gin.Context) {
var req dto.DictionaryDetailRequest
if c.ShouldBindJSON(&req) != nil || req.DictionaryID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.CreateDictionaryDetailRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败")
return
}
httpx.OK(c)
}
func (h *Dictionary) UpdateDetail(c *gin.Context) {
var req dto.DictionaryDetailRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdateDictionaryDetailRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Dictionary) DeleteDetail(c *gin.Context) {
var req dto.DictionaryDetailRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteDictionaryDetail(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Dictionary) FindDetail(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.DictionaryDetail(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"reSysDictionaryDetail": item}, "查询成功")
}
func (h *Dictionary) Details(c *gin.Context) {
p, size := page(c)
dictionaryID, _ := strconv.ParseUint(c.Query("sysDictionaryID"), 10, 64)
items, total, err := h.service.DictionaryDetails(c.Request.Context(), p, size, uint(dictionaryID), c.Query("label"), c.Query("value"))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
func (h *Dictionary) Tree(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 := h.service.DictionaryTree(c.Request.Context(), uint(id), typ)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
}
}
func (h *Dictionary) DetailsByParent(c *gin.Context) {
parentID, _ := strconv.ParseUint(c.Query("parentID"), 10, 64)
dictionaryID, _ := strconv.ParseUint(c.Query("sysDictionaryID"), 10, 64)
items, err := h.service.DictionaryDetailsByParent(c.Request.Context(), uint(dictionaryID), uint(parentID))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
}
func (h *Dictionary) Path(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.DictionaryDetail(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"path": item["path"]}, "获取成功")
}
+193
View File
@@ -0,0 +1,193 @@
package handler
import (
"encoding/json"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type Export struct {
system *service.SystemService
service *service.ExportService
}
func NewExport(system *service.SystemService, service *service.ExportService) *Export {
return &Export{system: system, service: service}
}
type exportToken struct {
TemplateID string `json:"templateID"`
Params map[string]string `json:"params"`
Blank bool `json:"blank"`
}
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
}
func (h *Export) Create(c *gin.Context) {
var req dto.ExportTemplateRequest
if c.ShouldBindJSON(&req) != nil || req.Name == "" || req.TemplateID == "" {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.CreateRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
return
}
httpx.OK(c)
}
func (h *Export) Update(c *gin.Context) {
var req dto.ExportTemplateRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败:"+err.Error())
return
}
httpx.OK(c)
}
func (h *Export) Delete(c *gin.Context) {
var req dto.IDRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Delete(c.Request.Context(), []uint{req.ID}); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Export) DeleteMany(c *gin.Context) {
var req dto.IDsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Delete(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Export) Find(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Template(c.Request.Context(), uint(id), "")
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"resysExportTemplate": item}, "查询成功")
}
func (h *Export) List(c *gin.Context) {
p, size := page(c)
items, total, err := h.service.TemplatesFilter(c.Request.Context(), p, size, c.Query("name"), c.Query("tableName"), c.Query("templateID"))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
func (h *Export) Preview(c *gin.Context) {
sql, err := h.service.Preview(c.Request.Context(), c.Query("templateID"), exportParams(c.Request.URL.Query()))
if err != nil {
httpx.Fail(c, "预览失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"sql": sql}, "获取成功")
}
func (h *Export) Issue(blank bool) gin.HandlerFunc {
return func(c *gin.Context) {
templateID := c.Query("templateID")
if templateID == "" {
httpx.Fail(c, "模板ID不能为空")
return
}
token := strings.ReplaceAll(uuid.NewString(), "-", "")
raw, _ := json.Marshal(exportToken{TemplateID: templateID, Params: exportParams(c.Request.URL.Query()), Blank: blank})
if err := h.system.CacheSet(c.Request.Context(), "export:"+token, string(raw), 5*time.Minute); err != nil {
httpx.Fail(c, "导出令牌创建失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"token": token}, "获取成功")
}
}
func (h *Export) Import(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
httpx.Fail(c, "请选择导入文件")
return
}
opened, err := file.Open()
if err != nil {
httpx.Fail(c, "读取文件失败")
return
}
defer opened.Close()
templateID := c.PostForm("templateID")
if templateID == "" {
templateID = c.Query("templateID")
}
if err = h.service.Import(c.Request.Context(), templateID, opened); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Export) Download(expectBlank bool) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.Query("token")
raw, ok, err := h.system.CacheGet(c.Request.Context(), "export:"+token)
if err != nil || !ok {
httpx.Fail(c, "导出令牌无效或已过期")
return
}
_ = h.system.CacheDelete(c.Request.Context(), "export:"+token)
var value exportToken
if json.Unmarshal([]byte(raw), &value) != nil || value.Blank != expectBlank {
httpx.Fail(c, "导出令牌无效")
return
}
var data []byte
var name string
if expectBlank {
data, name, err = h.service.ExportBlankTemplate(c.Request.Context(), value.TemplateID)
} else {
data, name, err = h.service.Export(c.Request.Context(), value.TemplateID, value.Params)
}
if err != nil {
httpx.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)
}
}
+230
View File
@@ -0,0 +1,230 @@
package handler
import (
"strconv"
"kra/internal/server/httpx"
servermiddleware "kra/internal/server/middleware"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Media struct{ service *service.MediaService }
func NewMedia(service *service.MediaService) *Media { return &Media{service: service} }
func (h *Media) Upload(c *gin.Context) {
claims := servermiddleware.Claims(c)
header, err := c.FormFile("file")
if claims == nil || err != nil {
httpx.Fail(c, "接收文件失败")
return
}
opened, err := header.Open()
if err != nil {
httpx.Fail(c, "读取文件失败")
return
}
defer opened.Close()
category, _ := strconv.Atoi(c.DefaultPostForm("classId", "0"))
save := c.DefaultQuery("noSave", "0") == "0"
item, err := h.service.Upload(c.Request.Context(), claims.ID, header.Filename, header.Header.Get("Content-Type"), category, opened, save)
if err != nil {
httpx.Fail(c, "上传失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"file": item}, "上传成功")
}
func (h *Media) List(c *gin.Context) {
var req dto.MediaListRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
items, total, err := h.service.MediaList(c.Request.Context(), req.Page, req.PageSize, req.Keyword, req.ClassID, req.Tag, req.UserID)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Media) Delete(c *gin.Context) {
var req dto.IDRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Delete(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
return
}
httpx.OK(c)
}
func (h *Media) DeleteMany(c *gin.Context) {
var req dto.IDsRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
failed := []uint{}
for _, id := range req.IDs {
if h.service.Delete(c.Request.Context(), id) != nil {
failed = append(failed, id)
}
}
if len(failed) > 0 {
httpx.Write(c, httpx.CodeSuccess, gin.H{"failedIds": failed}, "部分文件删除失败")
return
}
httpx.OK(c)
}
func (h *Media) Find(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
item, err := h.service.Media(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
}
func (h *Media) Rename(c *gin.Context) {
var req dto.RenameMediaRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Rename(c.Request.Context(), req.ID, req.Name); err != nil {
httpx.Fail(c, "编辑失败")
return
}
httpx.OK(c)
}
func (h *Media) ImportURLs(c *gin.Context) {
var req []dto.ImportMediaRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
claims := servermiddleware.Claims(c)
if claims == nil {
httpx.Fail(c, "未登录")
return
}
if err := h.service.ImportURLRequests(c.Request.Context(), claims.ID, req); err != nil {
httpx.Fail(c, "导入失败")
return
}
httpx.OK(c)
}
func (h *Media) Storage(c *gin.Context) {
var req dto.StorageListRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
items, next, more, err := h.service.Storage(c.Request.Context(), req.Prefix, req.Cursor, req.Limit)
if err != nil {
httpx.Fail(c, "列举失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items, "nextCursor": next, "hasMore": more}, "获取成功")
}
func (h *Media) Categories(c *gin.Context) {
items, err := h.service.Categories(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
}
func (h *Media) SaveCategory(c *gin.Context) {
var req dto.CategoryRequest
if c.ShouldBindJSON(&req) != nil || req.Name == "" {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SaveCategoryRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Media) DeleteCategory(c *gin.Context) {
var req dto.IDRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteCategory(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Media) InitUpload(c *gin.Context) {
claims := servermiddleware.Claims(c)
var req dto.InitUploadRequest
if claims == nil || c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
result, err := h.service.InitUpload(c.Request.Context(), claims.ID, req.FileName, req.FileHash, req.FileSize, req.ChunkSize, req.ChunkTotal)
if err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, result, "获取成功")
}
func (h *Media) SaveChunk(c *gin.Context) {
claims := servermiddleware.Claims(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 {
httpx.Fail(c, "接收分片失败")
return
}
opened, err := header.Open()
if err != nil {
httpx.Fail(c, "读取分片失败")
return
}
defer opened.Close()
if err = h.service.SaveChunk(c.Request.Context(), claims.ID, uint(uploadID), index, c.PostForm("chunkHash"), opened); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
func (h *Media) CompleteUpload(c *gin.Context) {
claims := servermiddleware.Claims(c)
var req dto.CompleteUploadRequest
if claims == nil || c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
item, err := h.service.CompleteUpload(c.Request.Context(), claims.ID, req.UploadID)
if err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"media": item}, "上传成功")
}
func (h *Media) CancelUpload(c *gin.Context) {
claims := servermiddleware.Claims(c)
id, _ := strconv.ParseUint(c.Param("uploadId"), 10, 64)
if claims == nil {
httpx.Fail(c, "未登录")
return
}
if err := h.service.CancelUpload(c.Request.Context(), claims.ID, uint(id)); err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.OK(c)
}
+26
View File
@@ -0,0 +1,26 @@
package handler
import (
"kra/internal/server/httpx"
"kra/internal/server/middleware"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
type Navigation struct{ service *service.SystemService }
func NewNavigation(service *service.SystemService) *Navigation { return &Navigation{service: service} }
func (h *Navigation) Menu(c *gin.Context) {
claims := middleware.Claims(c)
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
return
}
menus, err := h.service.Menus(c.Request.Context(), claims.AuthorityID)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": menus}, "获取成功")
}
+100
View File
@@ -0,0 +1,100 @@
package handler
import (
"strconv"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Parameter struct {
service *service.SettingsService
}
func NewParameter(service *service.SettingsService) *Parameter {
return &Parameter{service: service}
}
func (h *Parameter) Create(c *gin.Context) {
var req dto.SystemParameterRequest
if c.ShouldBindJSON(&req) != nil || req.Key == "" {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.CreateParameterRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败")
return
}
httpx.OK(c)
}
func (h *Parameter) Update(c *gin.Context) {
var req dto.SystemParameterRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdateParameterRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
return
}
httpx.OK(c)
}
func (h *Parameter) Delete(c *gin.Context) {
id, err := strconv.ParseUint(c.Query("ID"), 10, 64)
if err != nil || id == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteParameters(c.Request.Context(), []uint{uint(id)}); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Parameter) DeleteMany(c *gin.Context) {
ids := IDsFromQuery(c)
if len(ids) == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteParameters(c.Request.Context(), ids); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Parameter) Find(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Parameter(c.Request.Context(), uint(id), "")
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"resysParams": item}, "查询成功")
}
func (h *Parameter) Get(c *gin.Context) {
item, err := h.service.Parameter(c.Request.Context(), 0, c.Query("key"))
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, item, "查询成功")
}
func (h *Parameter) List(c *gin.Context) {
p, size := page(c)
items, total, err := h.service.ParametersFilter(c.Request.Context(), p, size, c.Query("name"), c.Query("key"))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
+200
View File
@@ -0,0 +1,200 @@
package handler
import (
"context"
"net/http"
"strconv"
"strings"
"time"
"kra/internal/conf"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"kra/internal/worker"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
)
type Public struct {
system *service.SystemService
settings *service.SettingsService
audit *service.AuditService
scheduler *worker.TaskScheduler
store *captchaStore
keyLong int
imageWidth int
imageHeight int
}
func NewPublic(config *conf.AdminBackend, system *service.SystemService, settings *service.SettingsService, audit *service.AuditService, scheduler *worker.TaskScheduler) *Public {
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)
}
}
return &Public{system: system, settings: settings, audit: audit, scheduler: scheduler, store: &captchaStore{service: system, expiration: expiration}, keyLong: keyLong, imageWidth: width, imageHeight: height}
}
func (h *Public) Captcha(c *gin.Context) {
keyLong, width, height := h.keyLong, h.imageWidth, h.imageHeight
security, _ := h.settings.CurrentSecurity(c.Request.Context())
openCaptcha := true
if security != nil {
keyLong, width, height = security.KeyLong, security.ImgWidth, security.ImgHeight
if security.CaptchaOpen > 0 {
failures, _ := cachedInt(c.Request.Context(), h.system, "login:fail:"+c.ClientIP())
openCaptcha = failures >= security.CaptchaOpen
}
}
if !openCaptcha {
httpx.Write(c, httpx.CodeSuccess, gin.H{"captchaId": "", "picPath": "", "captchaLength": keyLong, "openCaptcha": false}, "验证码获取成功")
return
}
driver := base64Captcha.NewDriverDigit(height, width, keyLong, 0.7, 80)
id, picture, _, err := base64Captcha.NewCaptcha(driver, h.store).Generate()
if err != nil {
httpx.Fail(c, "验证码获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"captchaId": id, "picPath": picture, "captchaLength": keyLong, "openCaptcha": openCaptcha}, "验证码获取成功")
}
func (h *Public) Login(c *gin.Context) {
var req dto.LoginRequest
if c.ShouldBindJSON(&req) != nil || req.Username == "" || req.Password == "" {
httpx.Fail(c, "请输入用户名和密码")
return
}
security, _ := h.settings.CurrentSecurity(c.Request.Context())
if security != nil && security.LockEnable {
if _, locked, _ := h.system.CacheGet(c.Request.Context(), "login:lock:"+req.Username); locked {
httpx.Fail(c, "账户已锁定,请稍后重试")
return
}
}
requireCaptcha := security == nil || security.CaptchaOpen == 0
if security != nil && security.CaptchaOpen > 0 {
failures, _ := cachedInt(c.Request.Context(), h.system, "login:fail:"+c.ClientIP())
requireCaptcha = failures >= security.CaptchaOpen
}
if requireCaptcha && (req.CaptchaID == "" || req.Captcha == "" || !h.store.Verify(req.CaptchaID, req.Captcha, true)) {
httpx.Fail(c, "验证码错误")
return
}
result, err := h.system.Login(c.Request.Context(), req.Username, req.Password)
if err != nil {
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "用户名不存在或者密码错误", Agent: c.Request.UserAgent()})
ttl := time.Hour
if security != nil && security.CaptchaTimeout > 0 {
ttl = time.Duration(security.CaptchaTimeout) * time.Second
}
failures, _ := incrementCached(c.Request.Context(), h.system, "login:fail:"+c.ClientIP(), ttl)
if security != nil && security.LockEnable && failures >= security.LockThreshold {
_ = h.system.CacheSet(c.Request.Context(), "login:lock:"+req.Username, "1", time.Duration(security.LockDuration)*time.Minute)
}
httpx.Fail(c, "用户名不存在或者密码错误")
return
}
userID, _ := result.User["ID"].(uint)
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: true, Agent: c.Request.UserAgent(), UserID: userID})
_ = h.system.CacheDelete(c.Request.Context(), "login:fail:"+c.ClientIP())
_ = h.system.CacheDelete(c.Request.Context(), "login:lock:"+req.Username)
maxAge := int(time.Until(time.UnixMilli(result.ExpiresAt)).Seconds())
http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: result.Token, Path: "/", MaxAge: maxAge, HttpOnly: true, SameSite: http.SameSiteStrictMode})
httpx.Write(c, httpx.CodeSuccess, result, "登录成功")
}
func (h *Public) CheckDatabase(c *gin.Context) {
initialized, err := h.system.IsInitialized(c.Request.Context())
if err != nil {
httpx.Fail(c, "数据库状态检查失败")
return
}
message := "数据库无需初始化"
if !initialized {
message = "前往初始化数据库"
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"needInit": !initialized}, message)
}
func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc {
return func(c *gin.Context) {
initialized, err := h.system.IsInitialized(c.Request.Context())
if err != nil {
httpx.Fail(c, "数据库状态检查失败")
return
}
if initialized {
httpx.Fail(c, "数据库已初始化,无需重复初始化")
return
}
var input service.DatabaseInit
if c.ShouldBindJSON(&input) != nil {
httpx.Fail(c, "数据库初始化参数无效")
return
}
routes := engine.Routes()
values := make([]dto.Route, 0, len(routes))
for _, route := range routes {
values = append(values, dto.Route{Path: route.Path, Method: route.Method})
}
if err := h.system.InitializeRoutes(c.Request.Context(), &input, values); err != nil {
httpx.Fail(c, "自动创建数据库失败: "+err.Error())
return
}
if err := h.scheduler.Reload(c.Request.Context()); err != nil {
httpx.Fail(c, "数据库已初始化,但定时任务加载失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "自动创建数据库成功")
}
}
func cachedInt(ctx context.Context, system *service.SystemService, key string) (int, error) {
value, ok, err := system.CacheGet(ctx, key)
if err != nil || !ok {
return 0, err
}
return strconv.Atoi(value)
}
func incrementCached(ctx context.Context, system *service.SystemService, key string, ttl time.Duration) (int, error) {
value, err := system.CacheIncrement(ctx, key, ttl)
return int(value), err
}
type captchaStore struct {
service *service.SystemService
expiration time.Duration
}
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 {
return id != "" && answer != "" && strings.EqualFold(s.Get(id, clear), answer)
}
+26
View File
@@ -0,0 +1,26 @@
package handler
import (
"net/http"
"kra/internal/server/httpx"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
type Session struct{ settings *service.SettingsService }
func NewSession(settings *service.SettingsService) *Session { return &Session{settings: settings} }
func (h *Session) Logout(c *gin.Context) {
token := c.GetHeader("x-token")
if token == "" {
token, _ = c.Cookie("x-token")
}
if err := h.settings.BlacklistToken(c.Request.Context(), token); err != nil {
httpx.Fail(c, "退出失败")
return
}
http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: "", Path: "/", MaxAge: -1, HttpOnly: true})
httpx.OK(c)
}
+101
View File
@@ -0,0 +1,101 @@
package handler
import (
"runtime"
"syscall"
"time"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"kra/internal/worker"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/mem"
)
type SystemConfig struct {
system *service.SystemService
settings *service.SettingsService
scheduler *worker.TaskScheduler
}
func NewSystemConfig(system *service.SystemService, settings *service.SettingsService, scheduler *worker.TaskScheduler) *SystemConfig {
return &SystemConfig{system: system, settings: settings, scheduler: scheduler}
}
func (h *SystemConfig) GetSecurity(c *gin.Context) {
value, err := h.settings.Security(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取安全配置失败")
return
}
httpx.Write(c, httpx.CodeSuccess, value, "获取成功")
}
func (h *SystemConfig) SetSecurity(c *gin.Context) {
var req dto.SecurityConfigRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
result, err := h.settings.SaveSecurityRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, result, "设置成功")
}
func (h *SystemConfig) Get(c *gin.Context) {
httpx.Write(c, httpx.CodeSuccess, h.system.SystemConfig(), "获取成功")
}
func (h *SystemConfig) Set(c *gin.Context) {
var req dto.SetSystemConfigRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.system.SaveSystemConfig(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "配置保存失败: "+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
}
func (h *SystemConfig) Reload(c *gin.Context) {
if err := h.system.ReloadConfig(c.Request.Context()); err != nil {
httpx.Fail(c, "重载系统失败:"+err.Error())
return
}
if err := h.scheduler.Reload(c.Request.Context()); err != nil {
httpx.Fail(c, "系统配置已重载,但定时任务重载失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "重载系统成功")
}
func (h *SystemConfig) ServerInfo(c *gin.Context) {
cpuPercent, _ := cpu.Percent(200*time.Millisecond, false)
memory, _ := mem.VirtualMemory()
disk := []gin.H{}
var stat syscall.Statfs_t
if syscall.Statfs(".", &stat) == nil {
total := uint64(stat.Blocks) * uint64(stat.Bsize)
free := uint64(stat.Bavail) * uint64(stat.Bsize)
used := total - free
percent := 0
if total > 0 {
percent = int(used * 100 / total)
}
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})
}
usedMB, totalMB, memoryPercent := uint64(0), uint64(0), float64(0)
if memory != nil {
usedMB, totalMB, memoryPercent = memory.Used/1024/1024, memory.Total/1024/1024, memory.UsedPercent
}
server := gin.H{"os": gin.H{"goos": runtime.GOOS, "numCpu": runtime.NumCPU(), "compiler": runtime.Compiler, "goVersion": runtime.Version(), "numGoroutine": runtime.NumGoroutine()}, "cpu": gin.H{"cpus": cpuPercent, "cores": runtime.NumCPU()}, "ram": gin.H{"usedMb": usedMB, "totalMb": totalMB, "usedPercent": memoryPercent}, "disk": disk}
httpx.Write(c, httpx.CodeSuccess, gin.H{"server": server}, "获取成功")
}
+178
View File
@@ -0,0 +1,178 @@
package handler
import (
"net/http"
"strconv"
"time"
"kra/internal/server/httpx"
"kra/internal/server/middleware"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type User struct{ service *service.SystemService }
func NewUser(service *service.SystemService) *User { return &User{service: service} }
func (h *User) List(c *gin.Context) {
var req dto.UserListRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
items, total, err := h.service.ListUsersRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *User) Create(c *gin.Context) {
var req dto.UserRequest
if c.ShouldBindJSON(&req) != nil || req.Username == "" || req.Password == "" {
httpx.Fail(c, "参数错误")
return
}
result, err := h.service.CreateUserRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "注册失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"user": result}, "注册成功")
}
func (h *User) Update(c *gin.Context) {
var req dto.UserRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.UpdateUserRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "修改失败")
return
}
httpx.OK(c)
}
func (h *User) UpdateSelf(c *gin.Context) {
claims := middleware.Claims(c)
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
return
}
var req dto.SelfUserRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
value := &dto.UserRequest{ID: claims.ID, NickName: req.NickName, HeaderImg: req.HeaderImg, Phone: req.Phone, Email: req.Email, Enable: 1}
if err := h.service.UpdateUserRequest(c.Request.Context(), value); err != nil {
httpx.Fail(c, "修改失败")
return
}
httpx.OK(c)
}
func (h *User) Delete(c *gin.Context) {
var req dto.IDRequest
if c.ShouldBind(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteUser(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *User) ResetPassword(c *gin.Context) {
var req dto.ResetPasswordRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.ResetPassword(c.Request.Context(), req.ID, req.Password); err != nil {
httpx.Fail(c, "重置失败")
return
}
httpx.OK(c)
}
func (h *User) ChangePassword(c *gin.Context) {
claims := middleware.Claims(c)
var req dto.ChangePasswordRequest
if claims == nil || c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.ChangePassword(c.Request.Context(), claims.ID, req.Password, req.NewPassword); err != nil {
httpx.Fail(c, "修改失败,原密码与当前账户不符")
return
}
httpx.OK(c)
}
func (h *User) SetSelfSetting(c *gin.Context) {
claims := middleware.Claims(c)
var setting map[string]any
if claims == nil || c.ShouldBindJSON(&setting) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetUserSetting(c.Request.Context(), claims.ID, setting); err != nil {
httpx.Fail(c, "设置失败")
return
}
httpx.OK(c)
}
func (h *User) SetAuthorities(c *gin.Context) {
var req dto.UserAuthoritiesRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 || len(req.AuthorityIDs) == 0 {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetUserAuthorities(c.Request.Context(), req.ID, req.AuthorityIDs); err != nil {
httpx.Fail(c, "修改失败")
return
}
httpx.OK(c)
}
func (h *User) SwitchAuthority(c *gin.Context) {
claims := middleware.Claims(c)
var req dto.SwitchAuthorityRequest
if claims == nil || c.ShouldBindJSON(&req) != nil || req.AuthorityID == 0 {
httpx.Fail(c, "参数错误")
return
}
login, err := h.service.SwitchAuthority(c.Request.Context(), claims.ID, req.AuthorityID)
if err != nil {
httpx.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})
httpx.OK(c)
}
func (h *User) Get(c *gin.Context) {
claims := middleware.Claims(c)
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
return
}
result, err := h.service.User(c.Request.Context(), claims.ID)
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"userInfo": result}, "获取成功")
}
+93
View File
@@ -0,0 +1,93 @@
package handler
import (
"fmt"
"net/http"
"strconv"
"time"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
type Version struct{ service *service.VersionService }
func NewVersion(service *service.VersionService) *Version { return &Version{service: service} }
func (h *Version) Delete(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
if err := h.service.DeleteVersions(c.Request.Context(), []uint{uint(id)}); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Version) DeleteMany(c *gin.Context) {
raw := c.QueryArray("IDs[]")
ids := make([]uint, 0, len(raw))
for _, value := range raw {
id, _ := strconv.ParseUint(value, 10, 64)
ids = append(ids, uint(id))
}
if err := h.service.DeleteVersions(c.Request.Context(), ids); err != nil {
httpx.Fail(c, "删除失败")
return
}
httpx.OK(c)
}
func (h *Version) Find(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Version(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, item, "查询成功")
}
func (h *Version) List(c *gin.Context) {
p, size := page(c)
items, total, err := h.service.Versions(c.Request.Context(), p, size, c.Query("versionName"), c.Query("versionCode"))
if err != nil {
httpx.Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
func (h *Version) Export(c *gin.Context) {
var req dto.ExportVersionRequest
if c.ShouldBindJSON(&req) != nil || req.VersionName == "" || req.VersionCode == "" {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.Export(c.Request.Context(), req.VersionName, req.VersionCode, req.Description, req.MenuIDs, req.APIIDs, req.DictIDs); err != nil {
httpx.Fail(c, "创建发版失败:"+err.Error())
return
}
httpx.OK(c)
}
func (h *Version) Download(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
raw, code, err := h.service.VersionData(c.Request.Context(), uint(id))
if err != nil {
httpx.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))
}
func (h *Version) Import(c *gin.Context) {
var req dto.ImportVersionRequest
if c.ShouldBindJSON(&req) != nil {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.ImportRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "导入失败:"+err.Error())
return
}
httpx.OK(c)
}
-265
View File
@@ -1,265 +0,0 @@
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)
})
}
+4 -4
View File
@@ -3,9 +3,9 @@ package middleware
import (
"net/http"
"kra/internal/biz"
"kra/internal/server/httpx"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
@@ -22,16 +22,16 @@ func AccessControl(access *service.AccessService, audit *service.AuditService) g
if err != nil || !allowed {
requestID, _ := c.Get("request_id")
requestIDText, _ := requestID.(string)
_ = audit.RecordDataAccess(c.Request.Context(), &biz.DataAccessLog{EventType: "blocked_access", Operation: c.Request.Method, UserID: claims.ID, AuthorityID: claims.AuthorityID, RequestID: requestIDText, Method: c.Request.Method, Path: path, Detail: "Casbin policy denied the request"})
_ = audit.RecordDataAccessRequest(c.Request.Context(), &dto.DataAccessRecordRequest{EventType: "blocked_access", Operation: c.Request.Method, UserID: claims.ID, AuthorityID: claims.AuthorityID, RequestID: requestIDText, Method: c.Request.Method, Path: path, Detail: "Casbin policy denied the request"})
c.AbortWithStatusJSON(http.StatusForbidden, httpx.Response{Code: httpx.CodeError, Data: nil, Msg: "权限不足"})
return
}
scope, err := access.ResolveDataScope(c.Request.Context(), claims.AuthorityID, claims.ID)
requestContext, err := access.ContextWithDataScope(c.Request.Context(), claims.AuthorityID, claims.ID)
if err != nil {
httpx.Fail(c, "数据权限加载失败")
return
}
c.Request = c.Request.WithContext(biz.NewDataScopeContext(c.Request.Context(), scope))
c.Request = c.Request.WithContext(requestContext)
c.Next()
}
}
+46
View File
@@ -0,0 +1,46 @@
package middleware
import (
"bytes"
"io"
"strings"
"time"
"kra/internal/service"
"kra/internal/service/dto"
"github.com/gin-gonic/gin"
)
func OperationAudit(service *service.AuditService) gin.HandlerFunc {
return func(c *gin.Context) {
if isBootstrapPath(c.Request.URL.Path) || 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 := Claims(c); claims != nil {
userID = claims.ID
}
requestID, _ := c.Get("request_id")
status, errorMessage := c.Writer.Status(), ""
if status >= 400 {
errorMessage = writer.body.String()
}
_ = service.RecordOperationRequest(c.Request.Context(), &dto.OperationRecordRequest{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: stringValue(requestID), TraceID: c.GetHeader("traceparent"), DeviceID: c.GetHeader("X-Device-Id")})
}
}
+66
View File
@@ -0,0 +1,66 @@
package middleware
import (
"bytes"
"encoding/json"
"strings"
"github.com/gin-gonic/gin"
)
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 stringValue(value any) string { text, _ := value.(string); return text }
func isBootstrapPath(path string) bool {
return strings.HasSuffix(path, "/health") || strings.Contains(path, "/init/")
}
@@ -1,4 +1,4 @@
package server
package middleware
import (
"bytes"
@@ -8,12 +8,13 @@ import (
"strings"
"time"
"kra/internal/server/httpx"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
func emailErrorAlert(svc *service.EmailService, logger *slog.Logger) gin.HandlerFunc {
func EmailErrorAlert(service *service.EmailService, logger *slog.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
if strings.Contains(c.Request.URL.Path, "/email/") {
c.Next()
@@ -28,19 +29,19 @@ func emailErrorAlert(svc *service.EmailService, logger *slog.Logger) gin.Handler
var result struct {
Code int `json:"code"`
}
failed = json.Unmarshal(writer.body.Bytes(), &result) == nil && result.Code != codeSuccess
failed = json.Unmarshal(writer.body.Bytes(), &result) == nil && result.Code != httpx.CodeSuccess
}
if !failed {
return
}
username := "Unknown"
if claims := currentClaims(c); claims != nil && claims.Username != "" {
if claims := Claims(c); claims != nil && claims.Username != "" {
username = claims.Username
}
subject := fmt.Sprintf("%s %s 调用 %s 报错", username, c.ClientIP(), c.Request.URL.Path)
body := bytes.NewBuffer(nil)
fmt.Fprintf(body, "请求方式:%s<br>请求路径:%s<br>状态码:%d<br>耗时:%s<br>错误响应:<pre>%s</pre>", c.Request.Method, c.Request.URL.Path, c.Writer.Status(), time.Since(started), redactJSON(writer.body.Bytes()))
if err := svc.Alert(c.Request.Context(), subject, body.String()); err != nil && logger != nil {
if err := service.Alert(c.Request.Context(), subject, body.String()); err != nil && logger != nil {
logger.Error("send HTTP error email", "error", err)
}
}
+38
View File
@@ -0,0 +1,38 @@
package middleware
import (
"net/http"
"strconv"
"time"
"kra/internal/server/httpx"
"kra/internal/service"
"github.com/gin-gonic/gin"
)
func SecurityRateLimit(system *service.SystemService, settings *service.SettingsService) gin.HandlerFunc {
return func(c *gin.Context) {
if isBootstrapPath(c.Request.URL.Path) {
c.Next()
return
}
config, err := settings.CurrentSecurity(c.Request.Context())
if err != nil || config == nil || !config.LimitEnable {
c.Next()
return
}
window := config.LimitWindow
if window < 1 {
window = 60
}
bucket := time.Now().Unix() / int64(window)
key := "rate:" + c.ClientIP() + ":" + strconv.FormatInt(bucket, 10)
count, cacheErr := system.CacheIncrement(c.Request.Context(), key, time.Duration(window+1)*time.Second)
if cacheErr == nil && int(count) > config.LimitCount {
c.AbortWithStatusJSON(http.StatusTooManyRequests, httpx.Response{Code: httpx.CodeError, Data: nil, Msg: "请求过于频繁"})
return
}
c.Next()
}
}
-29
View File
@@ -1,29 +0,0 @@
package server
import (
"kra/internal/server/httpx"
"github.com/gin-gonic/gin"
)
const (
codeSuccess = httpx.CodeSuccess
codeError = httpx.CodeError
codePasswordChangeRequired = httpx.CodePasswordChangeRequired
)
type response = httpx.Response
type pageResult = httpx.PageResult
func writeResult(c *gin.Context, code int, data any, message string) {
httpx.Write(c, code, data, message)
}
func ok(c *gin.Context) { httpx.OK(c) }
func okWithData(c *gin.Context, data any) { httpx.OKWithData(c, data) }
func fail(c *gin.Context, message string) { httpx.Fail(c, message) }
func noAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) }
+14
View File
@@ -0,0 +1,14 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterAPIToken(group *gin.RouterGroup, handler *handler.APIToken) {
router := group.Group("/sysApiToken")
router.POST("/createApiToken", handler.Create)
router.POST("/getApiTokenList", handler.List)
router.POST("/deleteApiToken", handler.Delete)
}
+34
View File
@@ -0,0 +1,34 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterAudit(group, public *gin.RouterGroup, h *handler.Audit) {
operations := group.Group("/sysOperationRecord")
operations.GET("/getSysOperationRecordList", h.Operations)
operations.GET("/findSysOperationRecord", h.Operation)
operations.DELETE("/deleteSysOperationRecord", h.DeleteOperation)
operations.DELETE("/deleteSysOperationRecordByIds", h.DeleteOperations)
logins := group.Group("/sysLoginLog")
logins.GET("/getLoginLogList", h.Logins)
logins.GET("/findLoginLog", h.Login)
logins.DELETE("/deleteLoginLog", h.DeleteLogin)
logins.DELETE("/deleteLoginLogByIds", h.DeleteLogins)
access := group.Group("/dataAccessLog")
access.POST("/getDataAccessLogList", h.DataAccess)
access.DELETE("/deleteDataAccessLogByIds", h.DeleteDataAccess)
viewer := group.Group("/logViewer")
viewer.GET("/dates", h.LogDates)
viewer.GET("/files", h.LogFiles)
viewer.GET("/content", h.LogContent)
errors := group.Group("/sysError")
errors.DELETE("/deleteSysError", h.DeleteError)
errors.DELETE("/deleteSysErrorByIds", h.DeleteErrors)
errors.PUT("/updateSysError", h.UpdateError)
errors.GET("/findSysError", h.Error)
errors.GET("/getSysErrorList", h.Errors)
public.Group("/sysError").POST("/createSysError", h.CreateError)
}
+29
View File
@@ -0,0 +1,29 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterDictionary(group *gin.RouterGroup, h *handler.Dictionary) {
dictionaries := group.Group("/sysDictionary")
dictionaries.POST("/createSysDictionary", h.Create)
dictionaries.PUT("/updateSysDictionary", h.Update)
dictionaries.DELETE("/deleteSysDictionary", h.Delete)
dictionaries.GET("/findSysDictionary", h.Find)
dictionaries.GET("/getSysDictionaryList", h.List(false))
dictionaries.GET("/getSysDictionaryListWithDetails", h.List(true))
dictionaries.GET("/exportSysDictionary", h.Export)
dictionaries.POST("/importSysDictionary", h.Import)
details := group.Group("/sysDictionaryDetail")
details.POST("/createSysDictionaryDetail", h.CreateDetail)
details.PUT("/updateSysDictionaryDetail", h.UpdateDetail)
details.DELETE("/deleteSysDictionaryDetail", h.DeleteDetail)
details.GET("/findSysDictionaryDetail", h.FindDetail)
details.GET("/getSysDictionaryDetailList", h.Details)
details.GET("/getDictionaryTreeList", h.Tree(false))
details.GET("/getDictionaryTreeListByType", h.Tree(true))
details.GET("/getDictionaryDetailsByParent", h.DetailsByParent)
details.GET("/getDictionaryPath", h.Path)
}
+24
View File
@@ -0,0 +1,24 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterExport(group, public *gin.RouterGroup, h *handler.Export) {
router := group.Group("/sysExportTemplate")
router.POST("/createSysExportTemplate", h.Create)
router.PUT("/updateSysExportTemplate", h.Update)
router.DELETE("/deleteSysExportTemplate", h.Delete)
router.DELETE("/deleteSysExportTemplateByIds", h.DeleteMany)
router.GET("/findSysExportTemplate", h.Find)
router.GET("/getSysExportTemplateList", h.List)
router.GET("/previewSQL", h.Preview)
router.GET("/exportExcel", h.Issue(false))
router.GET("/exportTemplate", h.Issue(true))
router.POST("/importExcel", h.Import)
pub := public.Group("/sysExportTemplate")
pub.GET("/exportExcelByToken", h.Download(false))
pub.GET("/exportTemplateByToken", h.Download(true))
}
+28
View File
@@ -0,0 +1,28 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterMedia(group *gin.RouterGroup, h *handler.Media) {
files := group.Group("/fileUploadAndDownload")
files.POST("/upload", h.Upload)
files.POST("/getFileList", h.List)
files.POST("/deleteFile", h.Delete)
files.POST("/deleteFiles", h.DeleteMany)
files.GET("/findFile", h.Find)
files.POST("/editFileName", h.Rename)
files.POST("/importURL", h.ImportURLs)
files.POST("/listOssFiles", h.Storage)
categories := group.Group("/attachmentCategory")
categories.GET("/getCategoryList", h.Categories)
categories.POST("/addCategory", h.SaveCategory)
categories.POST("/deleteCategory", h.DeleteCategory)
upload := group.Group("/mediaUpload")
upload.POST("/init", h.InitUpload)
upload.POST("/chunk", h.SaveChunk)
upload.POST("/complete", h.CompleteUpload)
upload.DELETE("/:uploadId", h.CancelUpload)
}
+18
View File
@@ -0,0 +1,18 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterParameter(group *gin.RouterGroup, handler *handler.Parameter) {
router := group.Group("/sysParams")
router.POST("/createSysParams", handler.Create)
router.PUT("/updateSysParams", handler.Update)
router.DELETE("/deleteSysParams", handler.Delete)
router.DELETE("/deleteSysParamsByIds", handler.DeleteMany)
router.GET("/findSysParams", handler.Find)
router.GET("/getSysParam", handler.Get)
router.GET("/getSysParamsList", handler.List)
}
+16
View File
@@ -0,0 +1,16 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterPublic(group *gin.RouterGroup, engine *gin.Engine, handler *handler.Public) {
base := group.Group("/base")
base.POST("/captcha", handler.Captcha)
base.POST("/login", handler.Login)
init := group.Group("/init")
init.POST("/checkdb", handler.CheckDatabase)
init.POST("/initdb", handler.InitializeDatabase(engine))
}
+19
View File
@@ -0,0 +1,19 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterSystemConfig(group *gin.RouterGroup, handler *handler.SystemConfig) {
security := group.Group("/securityConfig")
security.GET("/getSecurityConfig", handler.GetSecurity)
security.POST("/setSecurityConfig", handler.SetSecurity)
system := group.Group("/system")
system.POST("/getSystemConfig", handler.Get)
system.POST("/setSystemConfig", handler.Set)
system.POST("/reloadSystem", handler.Reload)
system.POST("/getServerInfo", handler.ServerInfo)
}
+30
View File
@@ -0,0 +1,30 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterUser(group *gin.RouterGroup, handler *handler.User) {
user := group.Group("/user")
user.POST("/getUserList", handler.List)
user.POST("/admin_register", handler.Create)
user.PUT("/setUserInfo", handler.Update)
user.PUT("/setSelfInfo", handler.UpdateSelf)
user.DELETE("/deleteUser", handler.Delete)
user.POST("/resetPassword", handler.ResetPassword)
user.POST("/changePassword", handler.ChangePassword)
user.PUT("/setSelfSetting", handler.SetSelfSetting)
user.POST("/setUserAuthorities", handler.SetAuthorities)
user.POST("/setUserAuthority", handler.SwitchAuthority)
user.GET("/getUserInfo", handler.Get)
}
func RegisterNavigation(group *gin.RouterGroup, handler *handler.Navigation) {
group.Group("/menu").POST("/getMenu", handler.Menu)
}
func RegisterSession(group *gin.RouterGroup, handler *handler.Session) {
group.Group("/jwt").POST("/jsonInBlacklist", handler.Logout)
}
+18
View File
@@ -0,0 +1,18 @@
package router
import (
"kra/internal/server/handler"
"github.com/gin-gonic/gin"
)
func RegisterVersion(group *gin.RouterGroup, h *handler.Version) {
router := group.Group("/sysVersion")
router.DELETE("/deleteSysVersion", h.Delete)
router.DELETE("/deleteSysVersionByIds", h.DeleteMany)
router.GET("/findSysVersion", h.Find)
router.GET("/getSysVersionList", h.List)
router.POST("/exportVersion", h.Export)
router.GET("/downloadVersionJson", h.Download)
router.POST("/importVersion", h.Import)
}
+1 -1
View File
@@ -8,4 +8,4 @@ import (
)
// ProviderSet is server providers.
var ProviderSet = wire.NewSet(NewGinServer, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewTask, worker.NewTaskScheduler)
var ProviderSet = wire.NewSet(NewGinServer, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewTask, handler.NewMedia, handler.NewAudit, handler.NewExport, handler.NewVersion, handler.NewDictionary, handler.NewParameter, handler.NewAPIToken, handler.NewSystemConfig, handler.NewPublic, handler.NewUser, handler.NewNavigation, handler.NewSession, worker.NewTaskScheduler)
-435
View File
@@ -1,435 +0,0 @@
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) {
id, err := strconv.ParseUint(c.Query("ID"), 10, 64)
if err != nil || id == 0 {
fail(c, "参数错误")
return
}
if err := svc.DeleteParameters(c.Request.Context(), []uint{uint(id)}); err != nil {
fail(c, "删除失败")
return
}
ok(c)
})
router.DELETE("/deleteSysParamsByIds", func(c *gin.Context) {
ids := idsFromQuery(c)
if len(ids) == 0 {
fail(c, "参数错误")
return
}
if err := svc.DeleteParameters(c.Request.Context(), 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, 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)
})
}
-215
View File
@@ -1,215 +0,0 @@
package server
import (
"runtime"
"syscall"
"time"
"kra/internal/biz"
"kra/internal/conf"
"kra/internal/service"
"kra/internal/worker"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/mem"
"google.golang.org/protobuf/proto"
)
func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, systemService *service.SystemService, settings *service.SettingsService, scheduler *worker.TaskScheduler) {
security := group.Group("/securityConfig")
security.GET("/getSecurityConfig", func(c *gin.Context) {
value, err := settings.Security(c.Request.Context())
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": ""}
email := gin.H{}
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}
}
if config.Media != nil {
admin["media"] = gin.H{"sessionTtl": config.Media.SessionTtl}
}
if config.Email != nil {
email = gin.H{"to": config.Email.To, "from": config.Email.From, "host": config.Email.Host, "secret": "******", "nickname": config.Email.Nickname, "port": config.Email.Port, "is-ssl": config.Email.IsSsl, "is-loginauth": config.Email.IsLoginAuth}
}
if config.Storage != nil {
storage := proto.Clone(config.Storage).(*conf.AdminBackend_Storage)
maskStorageSecrets(storage)
admin["storage"] = storage
}
}
writeResult(c, codeSuccess, gin.H{"config": gin.H{"admin": admin, "email": email}}, "获取成功")
})
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"`
Media struct {
SessionTTL int32 `json:"sessionTtl"`
} `json:"media"`
Storage *conf.AdminBackend_Storage `json:"storage"`
} `json:"admin"`
Email *struct {
To string `json:"to"`
From string `json:"from"`
Host string `json:"host"`
Secret string `json:"secret"`
Nickname string `json:"nickname"`
Port int32 `json:"port"`
IsSSL bool `json:"is-ssl"`
IsLoginAuth bool `json:"is-loginauth"`
} `json:"email"`
} `json:"config"`
}
if c.ShouldBindJSON(&req) != nil {
fail(c, "参数错误")
return
}
if config != nil {
next := proto.Clone(config).(*conf.AdminBackend)
next.RouterPrefix = req.Config.Admin.RouterPrefix
if next.Jwt != nil {
if req.Config.Admin.JWT.SigningKey != "" && req.Config.Admin.JWT.SigningKey != "******" {
next.Jwt.SigningKey = req.Config.Admin.JWT.SigningKey
}
if req.Config.Admin.JWT.Issuer != "" {
next.Jwt.Issuer = req.Config.Admin.JWT.Issuer
}
}
if next.Local != nil {
if req.Config.Admin.Local.StorePath != "" {
next.Local.StorePath = req.Config.Admin.Local.StorePath
}
if req.Config.Admin.Local.PathPrefix != "" {
next.Local.PathPrefix = req.Config.Admin.Local.PathPrefix
}
}
if next.Media != nil && req.Config.Admin.Media.SessionTTL > 0 {
next.Media.SessionTtl = req.Config.Admin.Media.SessionTTL
}
if req.Config.Admin.Storage != nil {
preserveStorageSecrets(req.Config.Admin.Storage, next.Storage)
next.Storage = req.Config.Admin.Storage
}
if next.Email != nil && req.Config.Email != nil {
next.Email.To = req.Config.Email.To
next.Email.From = req.Config.Email.From
next.Email.Host = req.Config.Email.Host
next.Email.Nickname = req.Config.Email.Nickname
next.Email.Port = req.Config.Email.Port
next.Email.IsSsl = req.Config.Email.IsSSL
next.Email.IsLoginAuth = req.Config.Email.IsLoginAuth
if req.Config.Email.Secret != "" && req.Config.Email.Secret != "******" {
next.Email.Secret = req.Config.Email.Secret
}
}
if err := systemService.PersistAdminConfig(c.Request.Context(), next); err != nil {
fail(c, "配置保存失败: "+err.Error())
return
}
}
writeResult(c, codeSuccess, gin.H{}, "设置成功")
})
system.POST("/reloadSystem", func(c *gin.Context) {
if err := systemService.ReloadConfig(c.Request.Context()); err != nil {
fail(c, "重载系统失败:"+err.Error())
return
}
if err := scheduler.Reload(c.Request.Context()); err != nil {
fail(c, "系统配置已重载,但定时任务重载失败:"+err.Error())
return
}
writeResult(c, codeSuccess, gin.H{}, "重载系统成功")
})
system.POST("/getServerInfo", func(c *gin.Context) {
cpuPercent, _ := cpu.Percent(200*time.Millisecond, false)
memory, _ := mem.VirtualMemory()
disk := []gin.H{}
var stat syscall.Statfs_t
if syscall.Statfs(".", &stat) == nil {
total := uint64(stat.Blocks) * uint64(stat.Bsize)
free := uint64(stat.Bavail) * uint64(stat.Bsize)
used := total - free
percent := 0
if total > 0 {
percent = int(used * 100 / total)
}
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})
}
usedMB, totalMB, memoryPercent := uint64(0), uint64(0), float64(0)
if memory != nil {
usedMB, totalMB, memoryPercent = memory.Used/1024/1024, memory.Total/1024/1024, memory.UsedPercent
}
server := gin.H{"os": gin.H{"goos": runtime.GOOS, "numCpu": runtime.NumCPU(), "compiler": runtime.Compiler, "goVersion": runtime.Version(), "numGoroutine": runtime.NumGoroutine()}, "cpu": gin.H{"cpus": cpuPercent, "cores": runtime.NumCPU()}, "ram": gin.H{"usedMb": usedMB, "totalMb": totalMB, "usedPercent": memoryPercent}, "disk": disk}
writeResult(c, codeSuccess, gin.H{"server": server}, "获取成功")
})
}
func objectStores(storage *conf.AdminBackend_Storage) []*conf.AdminBackend_ObjectStore {
if storage == nil {
return nil
}
return []*conf.AdminBackend_ObjectStore{storage.AliyunOss, storage.HuaweiObs, storage.TencentCos, storage.AwsS3, storage.CloudflareR2, storage.Minio}
}
func maskStorageSecrets(storage *conf.AdminBackend_Storage) {
if storage.Qiniu != nil {
storage.Qiniu.SecretKey = "******"
}
for _, item := range objectStores(storage) {
if item != nil {
item.SecretKey = "******"
}
}
}
func preserveStorageSecrets(next, current *conf.AdminBackend_Storage) {
if next == nil || current == nil {
return
}
if next.Qiniu != nil && current.Qiniu != nil && (next.Qiniu.SecretKey == "" || next.Qiniu.SecretKey == "******") {
next.Qiniu.SecretKey = current.Qiniu.SecretKey
}
nextItems, currentItems := objectStores(next), objectStores(current)
for i := range nextItems {
if nextItems[i] != nil && currentItems[i] != nil && (nextItems[i].SecretKey == "" || nextItems[i].SecretKey == "******") {
nextItems[i].SecretKey = currentItems[i].SecretKey
}
}
}
-165
View File
@@ -1,165 +0,0 @@
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)
})
}