This commit is contained in:
2026-08-15 01:58:41 +08:00
parent e55fb57432
commit 1a5969897a
32 changed files with 687 additions and 475 deletions
+2 -2
View File
@@ -10,9 +10,9 @@ import (
"github.com/gin-gonic/gin"
)
type API struct{ service *service.AccessService }
type API struct{ service *service.APIService }
func NewAPI(service *service.AccessService) *API { return &API{service: service} }
func NewAPI(service *service.APIService) *API { return &API{service: service} }
func (h *API) List(c *gin.Context) {
var req dto.APIListRequest
+12 -6
View File
@@ -13,9 +13,15 @@ import (
"github.com/gin-gonic/gin"
)
type Audit struct{ service *service.AuditService }
type Audit struct {
service *service.AuditService
recorder *service.AuditRecorder
logs *service.LogViewerService
}
func NewAudit(service *service.AuditService) *Audit { return &Audit{service: service} }
func NewAudit(service *service.AuditService, recorder *service.AuditRecorder, logs *service.LogViewerService) *Audit {
return &Audit{service: service, recorder: recorder, logs: logs}
}
func page(c *gin.Context) (int, int) {
value, _ := strconv.Atoi(c.Query("page"))
@@ -152,7 +158,7 @@ func (h *Audit) DeleteDataAccess(c *gin.Context) {
}
func (h *Audit) LogDates(c *gin.Context) {
data, err := h.service.LogDates(c.Request.Context(), c.Query("month"))
data, err := h.logs.LogDates(c.Request.Context(), c.Query("month"))
if err != nil {
failLogViewer(c, err)
return
@@ -160,7 +166,7 @@ func (h *Audit) LogDates(c *gin.Context) {
httpx.Write(c, httpx.CodeSuccess, data, "获取成功")
}
func (h *Audit) LogFiles(c *gin.Context) {
data, err := h.service.LogFiles(c.Request.Context(), c.Query("date"))
data, err := h.logs.LogFiles(c.Request.Context(), c.Query("date"))
if err != nil {
failLogViewer(c, err)
return
@@ -177,7 +183,7 @@ func (h *Audit) LogContent(c *gin.Context) {
}
cursor = &value
}
data, err := h.service.LogContent(c.Request.Context(), c.Query("date"), c.Query("path"), cursor)
data, err := h.logs.LogContent(c.Request.Context(), c.Query("date"), c.Query("path"), cursor)
if err != nil {
failLogViewer(c, err)
return
@@ -264,7 +270,7 @@ func (h *Audit) CreateError(c *gin.Context) {
httpx.Fail(c, err.Error())
return
}
if err := h.service.CreateErrorRequest(c.Request.Context(), &req); err != nil {
if err := h.recorder.CreateErrorRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
return
}
+2 -2
View File
@@ -10,9 +10,9 @@ import (
"github.com/gin-gonic/gin"
)
type Authority struct{ service *service.AccessService }
type Authority struct{ service *service.AuthorityService }
func NewAuthority(service *service.AccessService) *Authority { return &Authority{service: service} }
func NewAuthority(service *service.AuthorityService) *Authority { return &Authority{service: service} }
func (h *Authority) List(c *gin.Context) {
items, err := h.service.Authorities(c.Request.Context())
+16 -28
View File
@@ -1,11 +1,10 @@
package handler
import (
"encoding/json"
"errors"
"net/http"
"net/url"
"strings"
"time"
"kra/internal/server/httpx"
"kra/internal/service"
@@ -16,18 +15,11 @@ import (
)
type Export struct {
settings *service.SecurityService
service *service.ExportService
service *service.ExportService
}
func NewExport(settings *service.SecurityService, export *service.ExportService) *Export {
return &Export{settings: settings, service: export}
}
type exportToken struct {
TemplateID string `json:"templateID"`
Params map[string]string `json:"params"`
Blank bool `json:"blank"`
func NewExport(service *service.ExportService) *Export {
return &Export{service: service}
}
func exportParams(values url.Values) map[string]string {
@@ -151,9 +143,8 @@ func (h *Export) Issue(blank bool) gin.HandlerFunc {
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.settings.CacheSet(c.Request.Context(), "export:"+token, string(raw), 30*time.Minute); err != nil {
token, err := h.service.IssueToken(c.Request.Context(), templateID, exportParams(c.Request.URL.Query()), blank)
if err != nil {
httpx.Fail(c, "导出令牌创建失败")
return
}
@@ -196,21 +187,18 @@ func (h *Export) Download(expectBlank bool) gin.HandlerFunc {
httpx.Fail(c, "导出token不能为空")
return
}
raw, ok, err := h.settings.CacheGet(c.Request.Context(), "export:"+token)
if err != nil || !ok {
httpx.Fail(c, "导出token无效或已过期")
value, err := h.service.ConsumeToken(c.Request.Context(), token, expectBlank)
if err != nil {
switch {
case errors.Is(err, service.ErrExportTokenMalformed):
httpx.Fail(c, "解析导出参数失败")
case errors.Is(err, service.ErrExportTokenType):
httpx.Fail(c, "token类型错误")
default:
httpx.Fail(c, "导出token无效或已过期")
}
return
}
var value exportToken
if json.Unmarshal([]byte(raw), &value) != nil {
httpx.Fail(c, "解析导出参数失败")
return
}
if value.Blank != expectBlank {
httpx.Fail(c, "token类型错误")
return
}
_ = h.settings.CacheDelete(c.Request.Context(), "export:"+token)
var data []byte
var name string
if expectBlank {
+22 -19
View File
@@ -10,16 +10,19 @@ import (
"github.com/gin-gonic/gin"
)
type Organization struct{ service *service.OrganizationService }
type Organization struct {
departments *service.DepartmentService
positions *service.PositionService
}
func NewOrganization(service *service.OrganizationService) *Organization {
return &Organization{service: service}
func NewOrganization(departments *service.DepartmentService, positions *service.PositionService) *Organization {
return &Organization{departments: departments, positions: positions}
}
func (h *Organization) ListDepartments(c *gin.Context) {
var req dto.DepartmentListRequest
_ = c.ShouldBindJSON(&req)
items, err := h.service.Departments(c.Request.Context(), req.Name)
items, err := h.departments.Departments(c.Request.Context(), req.Name)
if err != nil {
httpx.Fail(c, "获取失败")
return
@@ -36,7 +39,7 @@ func (h *Organization) CreateDepartment(c *gin.Context) {
httpx.Fail(c, "部门名称不能为空")
return
}
if err := h.service.CreateDepartment(c.Request.Context(), &req); err != nil {
if err := h.departments.Create(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
return
}
@@ -52,7 +55,7 @@ func (h *Organization) UpdateDepartment(c *gin.Context) {
httpx.Fail(c, "部门ID不能为空")
return
}
if err := h.service.UpdateDepartment(c.Request.Context(), &req); err != nil {
if err := h.departments.Update(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败:"+err.Error())
return
}
@@ -64,7 +67,7 @@ func (h *Organization) DeleteDepartment(c *gin.Context) {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeleteDepartment(c.Request.Context(), req.ID); err != nil {
if err := h.departments.Delete(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, err.Error())
return
}
@@ -72,7 +75,7 @@ func (h *Organization) DeleteDepartment(c *gin.Context) {
}
func (h *Organization) FindDepartment(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
item, err := h.service.Department(c.Request.Context(), uint(id))
item, err := h.departments.Department(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
return
@@ -81,7 +84,7 @@ func (h *Organization) FindDepartment(c *gin.Context) {
}
func (h *Organization) DepartmentUsers(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("deptId"), 10, 64)
ids, err := h.service.DepartmentUserIDs(c.Request.Context(), uint(id))
ids, err := h.departments.UserIDs(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
return
@@ -97,7 +100,7 @@ func (h *Organization) SetDepartmentUsers(c *gin.Context) {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetDepartmentUsers(c.Request.Context(), &req); err != nil {
if err := h.departments.SetUsers(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败:"+err.Error())
return
}
@@ -109,7 +112,7 @@ func (h *Organization) SetUserDepartments(c *gin.Context) {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetUserDepartments(c.Request.Context(), &req); err != nil {
if err := h.departments.SetUserDepartments(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败")
return
}
@@ -122,7 +125,7 @@ func (h *Organization) ListPositions(c *gin.Context) {
httpx.Fail(c, "参数错误")
return
}
items, total, err := h.service.Positions(c.Request.Context(), &req)
items, total, err := h.positions.Positions(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败")
return
@@ -139,7 +142,7 @@ func (h *Organization) CreatePosition(c *gin.Context) {
httpx.Fail(c, "岗位名称不能为空")
return
}
if err := h.service.CreatePosition(c.Request.Context(), &req); err != nil {
if err := h.positions.Create(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
return
}
@@ -155,7 +158,7 @@ func (h *Organization) UpdatePosition(c *gin.Context) {
httpx.Fail(c, "岗位ID不能为空")
return
}
if err := h.service.UpdatePosition(c.Request.Context(), &req); err != nil {
if err := h.positions.Update(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败:"+err.Error())
return
}
@@ -167,7 +170,7 @@ func (h *Organization) DeletePosition(c *gin.Context) {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.DeletePosition(c.Request.Context(), req.ID); err != nil {
if err := h.positions.Delete(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
return
}
@@ -175,7 +178,7 @@ func (h *Organization) DeletePosition(c *gin.Context) {
}
func (h *Organization) FindPosition(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
item, err := h.service.Position(c.Request.Context(), uint(id))
item, err := h.positions.Position(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
return
@@ -184,7 +187,7 @@ func (h *Organization) FindPosition(c *gin.Context) {
}
func (h *Organization) PositionUsers(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("positionId"), 10, 64)
ids, err := h.service.PositionUserIDs(c.Request.Context(), uint(id))
ids, err := h.positions.UserIDs(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
return
@@ -200,7 +203,7 @@ func (h *Organization) SetPositionUsers(c *gin.Context) {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetPositionUsers(c.Request.Context(), &req); err != nil {
if err := h.positions.SetUsers(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败:"+err.Error())
return
}
@@ -212,7 +215,7 @@ func (h *Organization) SetUserPositions(c *gin.Context) {
httpx.Fail(c, "参数错误")
return
}
if err := h.service.SetUserPositions(c.Request.Context(), &req); err != nil {
if err := h.positions.SetUserPositions(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败")
return
}
+4 -2
View File
@@ -10,9 +10,11 @@ import (
"github.com/gin-gonic/gin"
)
type Permission struct{ service *service.AccessService }
type Permission struct{ service *service.PermissionService }
func NewPermission(service *service.AccessService) *Permission { return &Permission{service: service} }
func NewPermission(service *service.PermissionService) *Permission {
return &Permission{service: service}
}
func (h *Permission) Buttons(c *gin.Context) {
var req dto.GetAuthorityButtonsRequest
+13 -32
View File
@@ -22,13 +22,13 @@ type Public struct {
auth *service.AuthService
system *service.SystemConfigService
settings *service.SecurityService
audit *service.AuditService
audit *service.AuditRecorder
scheduler *worker.TaskScheduler
runtime *conf.Runtime
store *captchaStore
}
func NewPublic(runtime *conf.Runtime, auth *service.AuthService, system *service.SystemConfigService, settings *service.SecurityService, audit *service.AuditService, scheduler *worker.TaskScheduler) *Public {
func NewPublic(runtime *conf.Runtime, auth *service.AuthService, system *service.SystemConfigService, settings *service.SecurityService, audit *service.AuditRecorder, scheduler *worker.TaskScheduler) *Public {
return &Public{auth: auth, system: system, settings: settings, audit: audit, scheduler: scheduler, runtime: runtime, store: &captchaStore{service: settings, runtime: runtime}}
}
@@ -56,7 +56,7 @@ func (h *Public) Captcha(c *gin.Context) {
if security != nil {
keyLong, width, height = security.KeyLong, security.ImgWidth, security.ImgHeight
ttl := time.Duration(security.CaptchaTimeout) * time.Second
failures, _ := ensureLoginIPCounter(c.Request.Context(), h.settings, c.ClientIP(), ttl)
failures, _ := h.settings.EnsureLoginIPCounter(c.Request.Context(), c.ClientIP(), ttl)
openCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen
}
driver := base64Captcha.NewDriverDigit(height, width, keyLong, 0.7, 80)
@@ -76,7 +76,7 @@ func (h *Public) Login(c *gin.Context) {
}
security, _ := h.settings.CurrentSecurity(c.Request.Context())
if security != nil && security.LockEnable {
if _, locked, _ := h.settings.CacheGet(c.Request.Context(), "login:lock:"+req.Username); locked {
if locked, _ := h.settings.LoginLocked(c.Request.Context(), req.Username); locked {
httpx.Fail(c, "账号已锁定,请 "+strconv.Itoa(security.LockDuration)+" 分钟后再试")
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "账号已锁定", Agent: c.Request.UserAgent()})
return
@@ -88,18 +88,18 @@ func (h *Public) Login(c *gin.Context) {
if security.CaptchaTimeout > 0 {
ipTTL = time.Duration(security.CaptchaTimeout) * time.Second
}
failures, _ := ensureLoginIPCounter(c.Request.Context(), h.settings, c.ClientIP(), ipTTL)
failures, _ := h.settings.EnsureLoginIPCounter(c.Request.Context(), c.ClientIP(), ipTTL)
requireCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen
}
if requireCaptcha && (req.CaptchaID == "" || req.Captcha == "" || !h.store.Verify(req.CaptchaID, req.Captcha, true)) {
_, _ = h.settings.CacheIncrement(c.Request.Context(), c.ClientIP(), ipTTL)
_, _ = h.settings.IncrementLoginIP(c.Request.Context(), c.ClientIP(), ipTTL)
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "验证码错误", Agent: c.Request.UserAgent()})
httpx.Fail(c, "验证码错误")
return
}
result, err := h.auth.Login(c.Request.Context(), req.Username, req.Password)
if err != nil {
_, _ = h.settings.CacheIncrement(c.Request.Context(), c.ClientIP(), ipTTL)
_, _ = h.settings.IncrementLoginIP(c.Request.Context(), c.ClientIP(), ipTTL)
if errors.Is(err, biz.ErrUserDisabled) {
var disabled *service.UserDisabledError
errors.As(err, &disabled)
@@ -114,9 +114,9 @@ func (h *Public) Login(c *gin.Context) {
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "用户名不存在或者密码错误", Agent: c.Request.UserAgent()})
if security != nil && security.LockEnable {
lockTTL := time.Duration(security.LockDuration) * time.Minute
failures, _ := h.settings.CacheIncrement(c.Request.Context(), "login:fail:"+req.Username, lockTTL)
failures, _ := h.settings.IncrementLoginFailure(c.Request.Context(), req.Username, lockTTL)
if int(failures) >= security.LockThreshold {
_ = h.settings.CacheSet(c.Request.Context(), "login:lock:"+req.Username, "1", lockTTL)
_ = h.settings.LockLogin(c.Request.Context(), req.Username, lockTTL)
}
}
httpx.Fail(c, "用户名不存在或者密码错误")
@@ -124,30 +124,12 @@ func (h *Public) Login(c *gin.Context) {
}
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.settings.CacheDelete(c.Request.Context(), "login:fail:"+req.Username)
_ = h.settings.CacheDelete(c.Request.Context(), "login:lock:"+req.Username)
h.settings.ClearLoginState(c.Request.Context(), req.Username)
maxAge := int(time.Until(time.UnixMilli(result.ExpiresAt)).Seconds())
httpx.SetTokenCookie(c, result.Token, maxAge)
httpx.Write(c, httpx.CodeSuccess, result, "登录成功")
}
func ensureLoginIPCounter(ctx context.Context, settings *service.SecurityService, ip string, expiration time.Duration) (int, error) {
value, exists, err := settings.CacheGet(ctx, ip)
if err != nil {
return 0, err
}
if exists {
return strconv.Atoi(value)
}
if expiration <= 0 {
expiration = time.Hour
}
if err = settings.CacheSet(ctx, ip, "1", expiration); err != nil {
return 0, err
}
return 1, nil
}
func (h *Public) CheckDatabase(c *gin.Context) {
initialized, err := h.system.IsInitialized(c.Request.Context())
if err != nil {
@@ -199,22 +181,21 @@ type captchaStore struct {
runtime *conf.Runtime
}
func (s *captchaStore) key(id string) string { return "captcha:" + id }
func (s *captchaStore) Set(id, value string) error {
expiration := 3 * time.Minute
config := s.runtime.Admin()
if config != nil && config.Captcha != nil && config.Captcha.StoreExpiration != nil {
expiration = config.Captcha.StoreExpiration.AsDuration()
}
return s.service.CacheSet(context.Background(), s.key(id), value, expiration)
return s.service.SetCaptcha(context.Background(), id, value, expiration)
}
func (s *captchaStore) Get(id string, clear bool) string {
value, ok, err := s.service.CacheGet(context.Background(), s.key(id))
value, ok, err := s.service.GetCaptcha(context.Background(), id)
if err != nil || !ok {
return ""
}
if clear {
_ = s.service.CacheDelete(context.Background(), s.key(id))
_ = s.service.DeleteCaptcha(context.Background(), id)
}
return value
}