refactor: separate backend domains and storage adapters
This commit is contained in:
@@ -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 ""
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"]}, "获取成功")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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}, "获取成功")
|
||||
}
|
||||
@@ -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}, "获取成功")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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}, "获取成功")
|
||||
}
|
||||
@@ -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}, "获取成功")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user