This commit is contained in:
yvan 2026-08-15 01:58:41 +08:00
parent e55fb57432
commit 1a5969897a
32 changed files with 687 additions and 475 deletions

View File

@ -32,18 +32,24 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
}
accessRepo := data.NewAccessRepo(dataData)
accessUsecase := biz.NewAccessUsecase(accessRepo)
accessService := service.NewAccessService(accessUsecase, runtime)
authority := handler.NewAuthority(accessService)
accessControlService := service.NewAccessControlService(accessUsecase)
authorityService := service.NewAuthorityService(accessUsecase)
authority := handler.NewAuthority(authorityService)
menuRepo := data.NewMenuRepo(dataData)
menuUsecase := biz.NewMenuUsecase(menuRepo)
menuService := service.NewMenuService(menuUsecase)
menu := handler.NewMenu(menuService)
api := handler.NewAPI(accessService)
permission := handler.NewPermission(accessService)
organizationRepo := data.NewOrganizationRepo(dataData)
organizationUsecase := biz.NewOrganizationUsecase(organizationRepo)
organizationService := service.NewOrganizationService(organizationUsecase)
organization := handler.NewOrganization(organizationService)
apiService := service.NewAPIService(accessUsecase, runtime)
api := handler.NewAPI(apiService)
permissionService := service.NewPermissionService(accessUsecase)
permission := handler.NewPermission(permissionService)
departmentRepo := data.NewDepartmentRepo(dataData)
departmentUsecase := biz.NewDepartmentUsecase(departmentRepo)
departmentService := service.NewDepartmentService(departmentUsecase)
positionRepo := data.NewPositionRepo(dataData)
positionUsecase := biz.NewPositionUsecase(positionRepo)
positionService := service.NewPositionService(positionUsecase)
organization := handler.NewOrganization(departmentService, positionService)
announcementRepo := data.NewAnnouncementRepo(dataData)
announcementUsecase := biz.NewAnnouncementUsecase(announcementRepo)
announcementService := service.NewAnnouncementService(announcementUsecase)
@ -70,18 +76,14 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
auditRepo := data.NewAuditRepo(dataData)
auditUsecase := biz.NewAuditUsecase(auditRepo)
auditService := service.NewAuditService(auditUsecase)
audit := handler.NewAudit(auditService)
securityRepo := data.NewSecurityRepo(dataData)
securityUsecase := biz.NewSecurityUsecase(securityRepo)
cache := data.NewCache(dataData)
apiTokenRepo := data.NewAPITokenRepo(dataData)
tokenUsecase := biz.NewTokenUsecase(apiTokenRepo)
tokenService := service.NewTokenService(tokenUsecase, runtime)
securityService := service.NewSecurityService(securityUsecase, runtime, cache, tokenService)
auditRecorder := service.NewAuditRecorder(auditUsecase)
logViewerService := service.NewLogViewerService(auditUsecase)
audit := handler.NewAudit(auditService, auditRecorder, logViewerService)
exportRepo := data.NewExportRepo(dataData)
exportUsecase := biz.NewExportUsecase(exportRepo)
exportService := service.NewExportService(exportUsecase)
export := handler.NewExport(securityService, exportService)
cache := data.NewCache(dataData)
exportService := service.NewExportService(exportUsecase, cache)
export := handler.NewExport(exportService)
versionRepo := data.NewVersionRepo(dataData)
versionUsecase := biz.NewVersionUsecase(versionRepo)
versionService := service.NewVersionService(versionUsecase)
@ -94,20 +96,26 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
parameterUsecase := biz.NewParameterUsecase(parameterRepo)
parameterService := service.NewParameterService(parameterUsecase)
parameter := handler.NewParameter(parameterService)
apiTokenRepo := data.NewAPITokenRepo(dataData)
tokenUsecase := biz.NewTokenUsecase(apiTokenRepo)
tokenService := service.NewTokenService(tokenUsecase, runtime)
apiToken := handler.NewAPIToken(tokenService)
initializationRepo := data.NewInitializationRepo(dataData)
systemConfigUsecase := biz.NewSystemConfigUsecase(initializationRepo)
systemConfigService := service.NewSystemConfigService(systemConfigUsecase, runtime)
securityRepo := data.NewSecurityRepo(dataData)
securityUsecase := biz.NewSecurityUsecase(securityRepo)
securityService := service.NewSecurityService(securityUsecase, runtime, cache, tokenService)
systemConfig := handler.NewSystemConfig(systemConfigService, securityService, taskScheduler)
userRepo := data.NewUserRepo(dataData)
userUsecase := biz.NewUserUsecase(userRepo)
authService := service.NewAuthService(userUsecase, runtime, securityService)
public := handler.NewPublic(runtime, authService, systemConfigService, securityService, auditService, taskScheduler)
public := handler.NewPublic(runtime, authService, systemConfigService, securityService, auditRecorder, taskScheduler)
userService := service.NewUserService(userUsecase, securityService)
user := handler.NewUser(userService, authService)
navigation := handler.NewNavigation(userService)
session := handler.NewSession(tokenService)
engine := server.NewGinEngine(runtime, accessService, authority, menu, api, permission, organization, announcement, email, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, securityService, tokenService, auditService, logger)
engine := server.NewGinEngine(runtime, accessControlService, authority, menu, api, permission, organization, announcement, email, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, securityService, tokenService, auditRecorder, logger)
httpServer := server.NewGinServer(confServer, engine)
app := newApp(logger, httpServer, taskScheduler)
return app, func() {

View File

@ -3,4 +3,4 @@ package biz
import "github.com/google/wire"
// ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewUserUsecase, NewSystemConfigUsecase, NewAccessUsecase, NewMenuUsecase, NewOrganizationUsecase, NewDictionaryUsecase, NewParameterUsecase, NewTokenUsecase, NewSecurityUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase)
var ProviderSet = wire.NewSet(NewUserUsecase, NewSystemConfigUsecase, NewAccessUsecase, NewMenuUsecase, NewDepartmentUsecase, NewPositionUsecase, NewDictionaryUsecase, NewParameterUsecase, NewTokenUsecase, NewSecurityUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase)

View File

@ -31,10 +31,16 @@ type DepartmentRepo interface {
SetUserDepartments(context.Context, uint, []uint, uint) error
}
func (uc *OrganizationUsecase) Departments(ctx context.Context, name string) ([]*Department, error) {
type DepartmentUsecase struct{ DepartmentRepo }
func NewDepartmentUsecase(repo DepartmentRepo) *DepartmentUsecase {
return &DepartmentUsecase{DepartmentRepo: repo}
}
func (uc *DepartmentUsecase) Departments(ctx context.Context, name string) ([]*Department, error) {
return uc.ListDepartments(ctx, name)
}
func (uc *OrganizationUsecase) Department(ctx context.Context, id uint) (*Department, error) {
func (uc *DepartmentUsecase) Department(ctx context.Context, id uint) (*Department, error) {
return uc.FindDepartment(ctx, id)
}
@ -66,20 +72,15 @@ type PositionRepo interface {
SetUserPositions(context.Context, uint, []uint) error
}
func (uc *OrganizationUsecase) Positions(ctx context.Context, page, size int, filter *PositionListFilter) ([]*Position, int64, error) {
type PositionUsecase struct{ PositionRepo }
func NewPositionUsecase(repo PositionRepo) *PositionUsecase {
return &PositionUsecase{PositionRepo: repo}
}
func (uc *PositionUsecase) Positions(ctx context.Context, page, size int, filter *PositionListFilter) ([]*Position, int64, error) {
return uc.ListPositions(ctx, page, size, filter)
}
func (uc *OrganizationUsecase) Position(ctx context.Context, id uint) (*Position, error) {
func (uc *PositionUsecase) Position(ctx context.Context, id uint) (*Position, error) {
return uc.FindPosition(ctx, id)
}
type OrganizationRepo interface {
DepartmentRepo
PositionRepo
}
type OrganizationUsecase struct{ OrganizationRepo }
func NewOrganizationUsecase(repo OrganizationRepo) *OrganizationUsecase {
return &OrganizationUsecase{OrganizationRepo: repo}
}

View File

@ -13,7 +13,7 @@ import (
"kra/internal/conf"
)
var ProviderSet = wire.NewSet(NewData, NewUserRepo, NewInitializationRepo, NewAccessRepo, NewMenuRepo, NewOrganizationRepo, NewDictionaryRepo, NewParameterRepo, NewAPITokenRepo, NewSecurityRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage)
var ProviderSet = wire.NewSet(NewData, NewUserRepo, NewInitializationRepo, NewAccessRepo, NewMenuRepo, NewDepartmentRepo, NewPositionRepo, NewDictionaryRepo, NewParameterRepo, NewAPITokenRepo, NewSecurityRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage)
type Data struct {
initMu sync.Mutex

View File

@ -4,4 +4,6 @@ import "kra/internal/biz"
type organizationRepo struct{ data *Data }
func NewOrganizationRepo(data *Data) biz.OrganizationRepo { return &organizationRepo{data: data} }
func NewDepartmentRepo(data *Data) biz.DepartmentRepo { return &organizationRepo{data: data} }
func NewPositionRepo(data *Data) biz.PositionRepo { return &organizationRepo{data: data} }

View File

@ -20,7 +20,7 @@ import (
kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
)
func NewGinEngine(runtime *conf.Runtime, 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, security *service.SecurityService, tokens *service.TokenService, audit *service.AuditService, logger *slog.Logger) *gin.Engine {
func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, 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, security *service.SecurityService, tokens *service.TokenService, audit *service.AuditRecorder, logger *slog.Logger) *gin.Engine {
gin.SetMode(gin.ReleaseMode)
engine := gin.New()
engine.Use(servermiddleware.RequestMeta(), servermiddleware.Recovery(audit, logger), servermiddleware.AccessLog(runtime, logger), servermiddleware.ErrorAudit(audit), servermiddleware.SecurityRateLimit(security), servermiddleware.OperationAudit(runtime, audit))

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

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
}

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())

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 {

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
}

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

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
}

View File

@ -12,7 +12,7 @@ import (
"github.com/gin-gonic/gin"
)
func AccessControl(runtime *conf.Runtime, access *service.AccessService, audit *service.AuditService) gin.HandlerFunc {
func AccessControl(runtime *conf.Runtime, access *service.AccessControlService, audit *service.AuditRecorder) gin.HandlerFunc {
return func(c *gin.Context) {
claims := Claims(c)
if claims == nil {

View File

@ -13,7 +13,7 @@ import (
"github.com/gin-gonic/gin"
)
func OperationAudit(runtime *conf.Runtime, service *service.AuditService) gin.HandlerFunc {
func OperationAudit(runtime *conf.Runtime, service *service.AuditRecorder) gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
if !recordsOperation(c.Request.Method, path) {

View File

@ -14,7 +14,7 @@ import (
// ErrorAudit supplies the database sink that the Error-level logging core
// provides. Expected authentication, permission and input failures are not
// system errors and therefore are not inserted into sys_error.
func ErrorAudit(audit *service.AuditService) gin.HandlerFunc {
func ErrorAudit(audit *service.AuditRecorder) gin.HandlerFunc {
return func(c *gin.Context) {
writer := &captureWriter{ResponseWriter: c.Writer, maxBytes: 1 << 20}
c.Writer = writer

View File

@ -27,7 +27,7 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc {
window = 60
}
key := "KRA_SecLimit" + c.ClientIP() + c.FullPath()
count, cacheErr := settings.CacheIncrement(c.Request.Context(), key, time.Duration(window)*time.Second)
count, cacheErr := settings.IncrementRateLimit(c.Request.Context(), key, time.Duration(window)*time.Second)
if cacheErr == nil && int(count) > config.LimitCount {
httpx.Fail(c, "请求太过频繁,请稍后再试")
c.Abort()

View File

@ -16,7 +16,7 @@ import (
"github.com/gin-gonic/gin"
)
func Recovery(audit *service.AuditService, logger *slog.Logger) gin.HandlerFunc {
func Recovery(audit *service.AuditRecorder, logger *slog.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
panicValue := recover()

View File

@ -2,35 +2,30 @@ package service
import (
"context"
"strings"
"kra/internal/biz"
"kra/internal/conf"
"kra/internal/service/dto"
)
type AccessService struct {
uc *biz.AccessUsecase
runtime *conf.Runtime
type AuthorityService struct{ uc *biz.AccessUsecase }
func NewAuthorityService(uc *biz.AccessUsecase) *AuthorityService {
return &AuthorityService{uc: uc}
}
func NewAccessService(uc *biz.AccessUsecase, runtime *conf.Runtime) *AccessService {
return &AccessService{uc: uc, runtime: runtime}
type PermissionService struct{ uc *biz.AccessUsecase }
func NewPermissionService(uc *biz.AccessUsecase) *PermissionService {
return &PermissionService{uc: uc}
}
func (s *AccessService) NormalizeRoutePath(path string) string {
config := s.runtime.Admin()
if config == nil || config.RouterPrefix == "" {
return path
}
prefix := strings.TrimSuffix(config.RouterPrefix, "/")
normalized := strings.TrimPrefix(path, prefix)
if normalized == "" {
return "/"
}
return normalized
type AccessControlService struct{ uc *biz.AccessUsecase }
func NewAccessControlService(uc *biz.AccessUsecase) *AccessControlService {
return &AccessControlService{uc: uc}
}
func (s *AccessService) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) {
func (s *AccessControlService) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) {
return s.uc.Authorize(ctx, aid, path, method)
}
@ -43,7 +38,7 @@ func authorityDTO(v *biz.Authority) map[string]any {
out["children"] = children
return out
}
func (s *AccessService) Authorities(ctx context.Context) ([]map[string]any, error) {
func (s *AuthorityService) Authorities(ctx context.Context) ([]map[string]any, error) {
items, err := s.uc.AuthorityTree(ctx)
if err != nil {
return nil, err
@ -54,16 +49,16 @@ func (s *AccessService) Authorities(ctx context.Context) ([]map[string]any, erro
}
return roots, nil
}
func (s *AccessService) CreateAuthority(ctx context.Context, v *biz.Authority) error {
func (s *AuthorityService) CreateAuthority(ctx context.Context, v *biz.Authority) error {
return s.uc.CreateAuthority(ctx, v)
}
func (s *AccessService) CopyAuthority(ctx context.Context, sourceID uint, v *biz.Authority) error {
func (s *AuthorityService) CopyAuthority(ctx context.Context, sourceID uint, v *biz.Authority) error {
return s.uc.CopyAuthority(ctx, sourceID, v)
}
func (s *AccessService) UpdateAuthority(ctx context.Context, v *biz.Authority) error {
func (s *AuthorityService) UpdateAuthority(ctx context.Context, v *biz.Authority) error {
return s.uc.UpdateAuthority(ctx, v)
}
func (s *AccessService) DeleteAuthority(ctx context.Context, id uint) error {
func (s *AuthorityService) DeleteAuthority(ctx context.Context, id uint) error {
return s.uc.DeleteAuthority(ctx, id)
}
func authorityDomain(req *dto.AuthorityRequest) *biz.Authority {
@ -72,43 +67,43 @@ func authorityDomain(req *dto.AuthorityRequest) *biz.Authority {
func authorityResponse(value *biz.Authority) *dto.AuthorityResponse {
return &dto.AuthorityResponse{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}
}
func (s *AccessService) CreateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) {
func (s *AuthorityService) CreateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) {
value := authorityDomain(req)
if err := s.CreateAuthority(ctx, value); err != nil {
return nil, err
}
return authorityResponse(value), nil
}
func (s *AccessService) CopyAuthorityRequest(ctx context.Context, req *dto.CopyAuthorityRequest) (*dto.AuthorityResponse, error) {
func (s *AuthorityService) CopyAuthorityRequest(ctx context.Context, req *dto.CopyAuthorityRequest) (*dto.AuthorityResponse, error) {
value := authorityDomain(&req.Authority)
if err := s.CopyAuthority(ctx, req.OldAuthorityID, value); err != nil {
return nil, err
}
return authorityResponse(value), nil
}
func (s *AccessService) UpdateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) {
func (s *AuthorityService) UpdateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) {
value := authorityDomain(req)
if err := s.UpdateAuthority(ctx, value); err != nil {
return nil, err
}
return authorityResponse(value), nil
}
func (s *AccessService) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error {
func (s *AuthorityService) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error {
return s.uc.SetAuthorityUsers(ctx, id, ids)
}
func (s *AccessService) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) {
func (s *AuthorityService) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.AuthorityUserIDs(ctx, id)
}
func (s *AccessService) SetDataScope(ctx context.Context, id uint, scope int, ids []uint) error {
func (s *AuthorityService) SetDataScope(ctx context.Context, id uint, scope int, ids []uint) error {
return s.uc.SetDataScope(ctx, id, scope, ids)
}
func (s *AccessService) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
func (s *AuthorityService) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.DataScopeDepartmentIDs(ctx, id)
}
func (s *AccessService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) {
func (s *AccessControlService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) {
return s.uc.ResolveDataScope(ctx, authorityID, userID)
}
func (s *AccessService) ContextWithDataScope(ctx context.Context, authorityID, userID uint) (context.Context, error) {
func (s *AccessControlService) ContextWithDataScope(ctx context.Context, authorityID, userID uint) (context.Context, error) {
scope, err := s.ResolveDataScope(ctx, authorityID, userID)
if err != nil {
return ctx, err
@ -116,7 +111,7 @@ func (s *AccessService) ContextWithDataScope(ctx context.Context, authorityID, u
return biz.NewDataScopeContext(ctx, scope), nil
}
func (s *AccessService) Buttons(ctx context.Context, id uint) ([]map[string]any, error) {
func (s *PermissionService) Buttons(ctx context.Context, id uint) ([]map[string]any, error) {
items, err := s.uc.Buttons(ctx, id)
if err != nil {
return nil, err
@ -127,18 +122,18 @@ func (s *AccessService) Buttons(ctx context.Context, id uint) ([]map[string]any,
}
return out, nil
}
func (s *AccessService) SetAuthorityButtons(ctx context.Context, id uint, v map[uint][]uint) error {
func (s *PermissionService) SetAuthorityButtons(ctx context.Context, id uint, v map[uint][]uint) error {
return s.uc.SetAuthorityButtons(ctx, id, v)
}
func (s *AccessService) AuthorityButtonIDs(ctx context.Context, id uint) ([]uint, error) {
func (s *PermissionService) AuthorityButtonIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.AuthorityButtonIDs(ctx, id)
}
func (s *AccessService) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
func (s *PermissionService) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
return s.uc.SelectedButtons(ctx, aid, menuID)
}
func (s *AccessService) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error {
func (s *PermissionService) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error {
return s.uc.SetSelectedButtons(ctx, aid, menuID, ids)
}
func (s *AccessService) CanRemoveButton(ctx context.Context, id uint) (bool, error) {
func (s *PermissionService) CanRemoveButton(ctx context.Context, id uint) (bool, error) {
return s.uc.CanRemoveButton(ctx, id)
}

View File

@ -2,11 +2,35 @@ package service
import (
"context"
"strings"
"kra/internal/biz"
"kra/internal/conf"
"kra/internal/service/dto"
)
type APIService struct {
uc *biz.AccessUsecase
runtime *conf.Runtime
}
func NewAPIService(uc *biz.AccessUsecase, runtime *conf.Runtime) *APIService {
return &APIService{uc: uc, runtime: runtime}
}
func (s *APIService) NormalizeRoutePath(path string) string {
config := s.runtime.Admin()
if config == nil || config.RouterPrefix == "" {
return path
}
prefix := strings.TrimSuffix(config.RouterPrefix, "/")
normalized := strings.TrimPrefix(path, prefix)
if normalized == "" {
return "/"
}
return normalized
}
func apiDomain(value *dto.APIRequest) *biz.API {
return &biz.API{ID: value.ID, Path: value.Path, Description: value.Description, APIGroup: value.APIGroup, Method: value.Method}
}
@ -23,7 +47,7 @@ func apiResponses(items []*biz.API) []*dto.APIResponse {
return out
}
func (s *AccessService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*dto.APIResponse, int64, error) {
func (s *APIService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*dto.APIResponse, int64, error) {
items, total, err := s.uc.ListAPIs(ctx, req.Page, req.PageSize, &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method, OrderKey: req.OrderKey, Desc: req.Desc})
if err != nil {
return nil, 0, err
@ -31,7 +55,7 @@ func (s *AccessService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([
return apiResponses(items), total, nil
}
func (s *AccessService) AllAPI(ctx context.Context, strict bool) ([]*dto.APIResponse, error) {
func (s *APIService) AllAPI(ctx context.Context, strict bool) ([]*dto.APIResponse, error) {
items, _, err := s.uc.ListAPIs(ctx, 0, 0, &biz.API{StrictAll: strict})
if err != nil {
return nil, err
@ -39,24 +63,24 @@ func (s *AccessService) AllAPI(ctx context.Context, strict bool) ([]*dto.APIResp
return apiResponses(items), nil
}
func (s *AccessService) CreateAPIRequest(ctx context.Context, req *dto.APIRequest) (*dto.APIResponse, error) {
func (s *APIService) CreateAPIRequest(ctx context.Context, req *dto.APIRequest) (*dto.APIResponse, error) {
value := apiDomain(req)
if err := s.uc.CreateAPI(ctx, value); err != nil {
return nil, err
}
return apiResponse(value), nil
}
func (s *AccessService) UpdateAPIRequest(ctx context.Context, req *dto.APIRequest) error {
func (s *APIService) UpdateAPIRequest(ctx context.Context, req *dto.APIRequest) error {
return s.uc.UpdateAPI(ctx, apiDomain(req))
}
func (s *AccessService) FindAPIResponse(ctx context.Context, id uint) (*dto.APIResponse, error) {
func (s *APIService) FindAPIResponse(ctx context.Context, id uint) (*dto.APIResponse, error) {
value, err := s.uc.FindAPI(ctx, id)
if err != nil {
return nil, err
}
return apiResponse(value), nil
}
func (s *AccessService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyAPISyncRequest) error {
func (s *APIService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyAPISyncRequest) error {
added := make([]*biz.API, 0, len(req.NewAPIs))
deleted := make([]*biz.API, 0, len(req.DeleteAPIs))
for i := range req.NewAPIs {
@ -67,14 +91,14 @@ func (s *AccessService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyA
}
return s.uc.ApplyAPISync(ctx, added, deleted)
}
func (s *AccessService) SetPolicyPathsRequest(ctx context.Context, req *dto.SetPolicyPathsRequest) error {
func (s *APIService) SetPolicyPathsRequest(ctx context.Context, req *dto.SetPolicyPathsRequest) error {
paths := make([]*biz.API, 0, len(req.Infos))
for _, value := range req.Infos {
paths = append(paths, &biz.API{Path: value.Path, Method: value.Method})
}
return s.uc.SetPolicyPaths(ctx, req.AuthorityID, paths)
}
func (s *AccessService) PolicyPathResponses(ctx context.Context, authorityID uint) ([]dto.PolicyPathRequest, error) {
func (s *APIService) PolicyPathResponses(ctx context.Context, authorityID uint) ([]dto.PolicyPathRequest, error) {
items, err := s.uc.PolicyPaths(ctx, authorityID)
if err != nil {
return nil, err
@ -85,7 +109,7 @@ func (s *AccessService) PolicyPathResponses(ctx context.Context, authorityID uin
}
return out, nil
}
func (s *AccessService) SyncAPIResponses(ctx context.Context, routes []dto.APIRequest) (*dto.APISyncResponse, error) {
func (s *APIService) SyncAPIResponses(ctx context.Context, routes []dto.APIRequest) (*dto.APISyncResponse, error) {
items := make([]*biz.API, 0, len(routes))
for i := range routes {
routes[i].Path = s.NormalizeRoutePath(routes[i].Path)
@ -106,22 +130,22 @@ func (s *AccessService) SyncAPIResponses(ctx context.Context, routes []dto.APIRe
func apiDTO(v *biz.API) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "path": v.Path, "description": v.Description, "apiGroup": v.APIGroup, "method": v.Method}
}
func (s *AccessService) DeleteAPIs(ctx context.Context, ids []uint) error {
func (s *APIService) DeleteAPIs(ctx context.Context, ids []uint) error {
return s.uc.DeleteAPIs(ctx, ids)
}
func (s *AccessService) APIRoleIDs(ctx context.Context, path, method string) ([]uint, error) {
func (s *APIService) APIRoleIDs(ctx context.Context, path, method string) ([]uint, error) {
return s.uc.APIRoleIDs(ctx, path, method)
}
func (s *AccessService) SetAPIRoles(ctx context.Context, path, method string, ids []uint) error {
func (s *APIService) SetAPIRoles(ctx context.Context, path, method string, ids []uint) error {
return s.uc.SetAPIRoles(ctx, path, method, ids)
}
func (s *AccessService) SyncAPIs(ctx context.Context, routes []*biz.API) (*dto.APISyncResponse, error) {
func (s *APIService) SyncAPIs(ctx context.Context, routes []*biz.API) (*dto.APISyncResponse, error) {
diff, err := s.uc.SyncAPIs(ctx, routes)
if err != nil {
return nil, err
}
return &dto.APISyncResponse{NewAPIs: apiResponses(diff.Added), DeleteAPIs: apiResponses(diff.Deleted), IgnoreAPIs: apiResponses(diff.Ignored)}, nil
}
func (s *AccessService) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error {
func (s *APIService) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error {
return s.uc.SetAPIIgnored(ctx, path, method, ignored)
}

View File

@ -11,10 +11,14 @@ type AuditService struct{ uc *biz.AuditUsecase }
func NewAuditService(uc *biz.AuditUsecase) *AuditService { return &AuditService{uc: uc} }
func (s *AuditService) RecordOperation(ctx context.Context, v *biz.OperationRecord) error {
type AuditRecorder struct{ uc *biz.AuditUsecase }
func NewAuditRecorder(uc *biz.AuditUsecase) *AuditRecorder { return &AuditRecorder{uc: uc} }
func (s *AuditRecorder) RecordOperation(ctx context.Context, v *biz.OperationRecord) error {
return s.uc.RecordOperation(ctx, v)
}
func (s *AuditService) RecordOperationRequest(ctx context.Context, value *dto.OperationRecordRequest) error {
func (s *AuditRecorder) RecordOperationRequest(ctx context.Context, value *dto.OperationRecordRequest) error {
return s.RecordOperation(ctx, &biz.OperationRecord{IP: value.IP, Method: value.Method, Path: value.Path, Status: value.Status, LatencyMS: value.LatencyMS, Agent: value.Agent, ErrorMessage: value.ErrorMessage, Body: value.Body, Response: value.Response, UserID: value.UserID, RequestID: value.RequestID, TraceID: value.TraceID, DeviceID: value.DeviceID})
}
func (s *AuditService) OperationsFilter(ctx context.Context, page, size int, path, method string, status int) ([]map[string]any, int64, error) {
@ -49,10 +53,10 @@ func (s *AuditService) DeleteOperations(ctx context.Context, ids []uint) error {
return s.uc.DeleteOperations(ctx, ids)
}
func (s *AuditService) RecordLogin(ctx context.Context, v *biz.LoginLog) error {
func (s *AuditRecorder) RecordLogin(ctx context.Context, v *biz.LoginLog) error {
return s.uc.RecordLogin(ctx, v)
}
func (s *AuditService) RecordLoginRequest(ctx context.Context, value *dto.LoginLogRequest) error {
func (s *AuditRecorder) RecordLoginRequest(ctx context.Context, value *dto.LoginLogRequest) error {
return s.RecordLogin(ctx, &biz.LoginLog{Username: value.Username, IP: value.IP, Status: value.Status, ErrorMessage: value.ErrorMessage, Agent: value.Agent, UserID: value.UserID})
}
func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status bool) ([]map[string]any, int64, error) {
@ -87,10 +91,10 @@ func (s *AuditService) DeleteLogins(ctx context.Context, ids []uint) error {
return s.uc.DeleteLogins(ctx, ids)
}
func (s *AuditService) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error {
func (s *AuditRecorder) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error {
return s.uc.RecordDataAccess(ctx, v)
}
func (s *AuditService) RecordDataAccessRequest(ctx context.Context, value *dto.DataAccessRecordRequest) error {
func (s *AuditRecorder) RecordDataAccessRequest(ctx context.Context, value *dto.DataAccessRecordRequest) error {
return s.RecordDataAccess(ctx, &biz.DataAccessLog{EventType: value.EventType, Operation: value.Operation, UserID: value.UserID, AuthorityID: value.AuthorityID, RequestID: value.RequestID, Method: value.Method, Path: value.Path, Detail: value.Detail})
}
func (s *AuditService) DataAccessRequest(ctx context.Context, req *dto.DataAccessListRequest) ([]map[string]any, int64, error) {

View File

@ -11,13 +11,13 @@ import (
func errorDTO(v *biz.ErrorRecord) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "form": v.Form, "info": v.Info, "level": v.Level, "request_id": v.RequestID, "trace_id": v.TraceID, "solution": v.Solution, "status": v.Status}
}
func (s *AuditService) CreateError(ctx context.Context, v *biz.ErrorRecord) error {
func (s *AuditRecorder) CreateError(ctx context.Context, v *biz.ErrorRecord) error {
return s.uc.CreateError(ctx, v)
}
func errorDomain(value *dto.ErrorRecordRequest) *biz.ErrorRecord {
return &biz.ErrorRecord{ID: value.ID, Form: value.Form, Info: value.Info, Level: value.Level, RequestID: value.RequestID, TraceID: value.TraceID, Solution: value.Solution, Status: value.Status}
}
func (s *AuditService) CreateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error {
func (s *AuditRecorder) CreateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error {
return s.CreateError(ctx, errorDomain(req))
}
func (s *AuditService) UpdateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error {

View File

@ -2,9 +2,17 @@ package service
import (
"context"
"kra/internal/biz"
)
func (s *AuditService) LogDates(ctx context.Context, month string) (map[string]any, error) {
type LogViewerService struct{ uc *biz.AuditUsecase }
func NewLogViewerService(uc *biz.AuditUsecase) *LogViewerService {
return &LogViewerService{uc: uc}
}
func (s *LogViewerService) LogDates(ctx context.Context, month string) (map[string]any, error) {
items, err := s.uc.LogDates(ctx, month)
if err != nil {
return nil, err
@ -15,7 +23,7 @@ func (s *AuditService) LogDates(ctx context.Context, month string) (map[string]a
}
return map[string]any{"month": month, "dates": out}, nil
}
func (s *AuditService) LogFiles(ctx context.Context, date string) (map[string]any, error) {
func (s *LogViewerService) LogFiles(ctx context.Context, date string) (map[string]any, error) {
items, err := s.uc.LogFiles(ctx, date)
if err != nil {
return nil, err
@ -26,7 +34,7 @@ func (s *AuditService) LogFiles(ctx context.Context, date string) (map[string]an
}
return map[string]any{"date": date, "files": out}, nil
}
func (s *AuditService) LogContent(ctx context.Context, date, path string, cursor *int64) (map[string]any, error) {
func (s *LogViewerService) LogContent(ctx context.Context, date, path string, cursor *int64) (map[string]any, error) {
v, err := s.uc.LogContent(ctx, date, path, cursor)
if err != nil {
return nil, err

View File

@ -63,7 +63,7 @@ func (s *AuthService) issueLogin(ctx context.Context, user *biz.User, authorityI
return nil, err
}
if s.settings.UseMultipoint() {
oldToken, _, cacheErr := s.settings.CacheGet(ctx, activeTokenKey(user.Username))
oldToken, cacheErr := s.settings.ActiveToken(ctx, user.Username)
if cacheErr != nil {
return nil, cacheErr
}

View File

@ -0,0 +1,74 @@
package service
import (
"context"
"kra/internal/biz"
"kra/internal/service/dto"
)
type DepartmentService struct{ uc *biz.DepartmentUsecase }
func NewDepartmentService(uc *biz.DepartmentUsecase) *DepartmentService {
return &DepartmentService{uc: uc}
}
func departmentResponse(value *biz.Department) *dto.DepartmentResponse {
children := make([]*dto.DepartmentResponse, 0, len(value.Children))
for _, child := range value.Children {
children = append(children, departmentResponse(child))
}
var leader any
if value.Leader != nil {
leader = convertUser(value.Leader)
}
return &dto.DepartmentResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, ParentID: value.ParentID, Ancestors: value.Ancestors, Sort: value.Sort, LeaderID: value.LeaderID, Leader: leader, Status: value.Status, Children: children, NamePath: value.NamePath}
}
func departmentDomain(value *dto.DepartmentRequest) *biz.Department {
return &biz.Department{ID: value.ID, Name: value.Name, ParentID: value.ParentID, Sort: value.Sort, LeaderID: value.LeaderID, Status: value.Status}
}
func (s *DepartmentService) Departments(ctx context.Context, name string) ([]*dto.DepartmentResponse, error) {
items, err := s.uc.Departments(ctx, name)
if err != nil {
return nil, err
}
out := make([]*dto.DepartmentResponse, 0, len(items))
for _, item := range items {
out = append(out, departmentResponse(item))
}
return out, nil
}
func (s *DepartmentService) Create(ctx context.Context, req *dto.DepartmentRequest) error {
return s.uc.CreateDepartment(ctx, departmentDomain(req))
}
func (s *DepartmentService) Update(ctx context.Context, req *dto.DepartmentRequest) error {
return s.uc.UpdateDepartment(ctx, departmentDomain(req))
}
func (s *DepartmentService) Delete(ctx context.Context, id uint) error {
return s.uc.DeleteDepartment(ctx, id)
}
func (s *DepartmentService) Department(ctx context.Context, id uint) (*dto.DepartmentResponse, error) {
value, err := s.uc.Department(ctx, id)
if err != nil {
return nil, err
}
return departmentResponse(value), nil
}
func (s *DepartmentService) UserIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.DepartmentUserIDs(ctx, id)
}
func (s *DepartmentService) SetUsers(ctx context.Context, req *dto.SetDepartmentUsersRequest) error {
return s.uc.SetDepartmentUsers(ctx, req.DepartmentID, req.UserIDs)
}
func (s *DepartmentService) SetUserDepartments(ctx context.Context, req *dto.SetUserDepartmentsRequest) error {
return s.uc.SetUserDepartments(ctx, req.ID, req.DepartmentIDs, req.Primary)
}

View File

@ -2,15 +2,71 @@ package service
import (
"context"
"encoding/json"
"errors"
"strings"
"time"
"kra/internal/biz"
"kra/internal/service/dto"
"github.com/google/uuid"
)
type ExportService struct{ uc *biz.ExportUsecase }
var (
ErrExportTokenInvalid = errors.New("export token invalid")
ErrExportTokenMalformed = errors.New("export token malformed")
ErrExportTokenType = errors.New("export token type mismatch")
)
func NewExportService(uc *biz.ExportUsecase) *ExportService { return &ExportService{uc: uc} }
type ExportService struct {
uc *biz.ExportUsecase
cache biz.Cache
}
type ExportToken struct {
TemplateID string
Params map[string]string
Blank bool
}
type exportTokenCache struct {
TemplateID string `json:"templateID"`
Params map[string]string `json:"params"`
Blank bool `json:"blank"`
}
func NewExportService(uc *biz.ExportUsecase, cache biz.Cache) *ExportService {
return &ExportService{uc: uc, cache: cache}
}
func (s *ExportService) IssueToken(ctx context.Context, templateID string, params map[string]string, blank bool) (string, error) {
token := strings.ReplaceAll(uuid.NewString(), "-", "")
raw, err := json.Marshal(exportTokenCache{TemplateID: templateID, Params: params, Blank: blank})
if err != nil {
return "", err
}
if err = s.cache.Set(ctx, "export:"+token, string(raw), 30*time.Minute); err != nil {
return "", err
}
return token, nil
}
func (s *ExportService) ConsumeToken(ctx context.Context, token string, expectBlank bool) (*ExportToken, error) {
raw, ok, err := s.cache.Get(ctx, "export:"+token)
if err != nil || !ok {
return nil, ErrExportTokenInvalid
}
var value exportTokenCache
if json.Unmarshal([]byte(raw), &value) != nil {
return nil, ErrExportTokenMalformed
}
if value.Blank != expectBlank {
return nil, ErrExportTokenType
}
_ = s.cache.Delete(ctx, "export:"+token)
return &ExportToken{TemplateID: value.TemplateID, Params: value.Params, Blank: value.Blank}, nil
}
func exportTemplateDomain(value *dto.ExportTemplateRequest) *biz.ExportTemplate {
out := &biz.ExportTemplate{ID: value.ID, DBName: value.DBName, Name: value.Name, TableName: value.TableName, TemplateID: value.TemplateID, TemplateInfo: value.TemplateInfo, SQL: value.SQL, ImportSQL: value.ImportSQL, Limit: value.Limit, Order: value.Order}
for _, item := range value.Conditions {

View File

@ -1,110 +0,0 @@
package service
import (
"context"
"kra/internal/biz"
"kra/internal/service/dto"
)
type OrganizationService struct{ uc *biz.OrganizationUsecase }
func NewOrganizationService(uc *biz.OrganizationUsecase) *OrganizationService {
return &OrganizationService{uc: uc}
}
func departmentResponse(value *biz.Department) *dto.DepartmentResponse {
children := make([]*dto.DepartmentResponse, 0, len(value.Children))
for _, child := range value.Children {
children = append(children, departmentResponse(child))
}
var leader any
if value.Leader != nil {
leader = convertUser(value.Leader)
}
return &dto.DepartmentResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, ParentID: value.ParentID, Ancestors: value.Ancestors, Sort: value.Sort, LeaderID: value.LeaderID, Leader: leader, Status: value.Status, Children: children, NamePath: value.NamePath}
}
func departmentDomain(value *dto.DepartmentRequest) *biz.Department {
return &biz.Department{ID: value.ID, Name: value.Name, ParentID: value.ParentID, Sort: value.Sort, LeaderID: value.LeaderID, Status: value.Status}
}
func (s *OrganizationService) Departments(ctx context.Context, name string) ([]*dto.DepartmentResponse, error) {
items, err := s.uc.Departments(ctx, name)
if err != nil {
return nil, err
}
out := make([]*dto.DepartmentResponse, 0, len(items))
for _, item := range items {
out = append(out, departmentResponse(item))
}
return out, nil
}
func (s *OrganizationService) CreateDepartment(ctx context.Context, req *dto.DepartmentRequest) error {
return s.uc.CreateDepartment(ctx, departmentDomain(req))
}
func (s *OrganizationService) UpdateDepartment(ctx context.Context, req *dto.DepartmentRequest) error {
return s.uc.UpdateDepartment(ctx, departmentDomain(req))
}
func (s *OrganizationService) DeleteDepartment(ctx context.Context, id uint) error {
return s.uc.DeleteDepartment(ctx, id)
}
func (s *OrganizationService) Department(ctx context.Context, id uint) (*dto.DepartmentResponse, error) {
value, err := s.uc.Department(ctx, id)
if err != nil {
return nil, err
}
return departmentResponse(value), nil
}
func (s *OrganizationService) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.DepartmentUserIDs(ctx, id)
}
func (s *OrganizationService) SetDepartmentUsers(ctx context.Context, req *dto.SetDepartmentUsersRequest) error {
return s.uc.SetDepartmentUsers(ctx, req.DepartmentID, req.UserIDs)
}
func (s *OrganizationService) SetUserDepartments(ctx context.Context, req *dto.SetUserDepartmentsRequest) error {
return s.uc.SetUserDepartments(ctx, req.ID, req.DepartmentIDs, req.Primary)
}
func positionDomain(value *dto.PositionRequest) *biz.Position {
return &biz.Position{ID: value.ID, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark}
}
func positionResponse(value *biz.Position) *dto.PositionResponse {
return &dto.PositionResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark}
}
func (s *OrganizationService) Positions(ctx context.Context, req *dto.PositionListRequest) ([]*dto.PositionResponse, int64, error) {
items, total, err := s.uc.Positions(ctx, req.Page, req.PageSize, &biz.PositionListFilter{Name: req.Name, Code: req.Code, Status: req.Status})
if err != nil {
return nil, 0, err
}
out := make([]*dto.PositionResponse, 0, len(items))
for _, item := range items {
out = append(out, positionResponse(item))
}
return out, total, nil
}
func (s *OrganizationService) CreatePosition(ctx context.Context, req *dto.PositionRequest) error {
return s.uc.CreatePosition(ctx, positionDomain(req))
}
func (s *OrganizationService) UpdatePosition(ctx context.Context, req *dto.PositionRequest) error {
return s.uc.UpdatePosition(ctx, positionDomain(req))
}
func (s *OrganizationService) DeletePosition(ctx context.Context, id uint) error {
return s.uc.DeletePosition(ctx, id)
}
func (s *OrganizationService) Position(ctx context.Context, id uint) (*dto.PositionResponse, error) {
value, err := s.uc.Position(ctx, id)
if err != nil {
return nil, err
}
return positionResponse(value), nil
}
func (s *OrganizationService) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.PositionUserIDs(ctx, id)
}
func (s *OrganizationService) SetPositionUsers(ctx context.Context, req *dto.SetPositionUsersRequest) error {
return s.uc.SetPositionUsers(ctx, req.PositionID, req.UserIDs)
}
func (s *OrganizationService) SetUserPositions(ctx context.Context, req *dto.SetUserPositionsRequest) error {
return s.uc.SetUserPositions(ctx, req.ID, req.PositionIDs)
}

View File

@ -0,0 +1,66 @@
package service
import (
"context"
"kra/internal/biz"
"kra/internal/service/dto"
)
type PositionService struct{ uc *biz.PositionUsecase }
func NewPositionService(uc *biz.PositionUsecase) *PositionService {
return &PositionService{uc: uc}
}
func positionDomain(value *dto.PositionRequest) *biz.Position {
return &biz.Position{ID: value.ID, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark}
}
func positionResponse(value *biz.Position) *dto.PositionResponse {
return &dto.PositionResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark}
}
func (s *PositionService) Positions(ctx context.Context, req *dto.PositionListRequest) ([]*dto.PositionResponse, int64, error) {
items, total, err := s.uc.Positions(ctx, req.Page, req.PageSize, &biz.PositionListFilter{Name: req.Name, Code: req.Code, Status: req.Status})
if err != nil {
return nil, 0, err
}
out := make([]*dto.PositionResponse, 0, len(items))
for _, item := range items {
out = append(out, positionResponse(item))
}
return out, total, nil
}
func (s *PositionService) Create(ctx context.Context, req *dto.PositionRequest) error {
return s.uc.CreatePosition(ctx, positionDomain(req))
}
func (s *PositionService) Update(ctx context.Context, req *dto.PositionRequest) error {
return s.uc.UpdatePosition(ctx, positionDomain(req))
}
func (s *PositionService) Delete(ctx context.Context, id uint) error {
return s.uc.DeletePosition(ctx, id)
}
func (s *PositionService) Position(ctx context.Context, id uint) (*dto.PositionResponse, error) {
value, err := s.uc.Position(ctx, id)
if err != nil {
return nil, err
}
return positionResponse(value), nil
}
func (s *PositionService) UserIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.PositionUserIDs(ctx, id)
}
func (s *PositionService) SetUsers(ctx context.Context, req *dto.SetPositionUsersRequest) error {
return s.uc.SetPositionUsers(ctx, req.PositionID, req.UserIDs)
}
func (s *PositionService) SetUserPositions(ctx context.Context, req *dto.SetUserPositionsRequest) error {
return s.uc.SetUserPositions(ctx, req.ID, req.PositionIDs)
}

View File

@ -2,6 +2,7 @@ package service
import (
"context"
"strconv"
"sync"
"time"
@ -22,22 +23,82 @@ func NewSecurityService(uc *biz.SecurityUsecase, runtime *conf.Runtime, cache bi
return &SecurityService{uc: uc, runtime: runtime, cache: cache, tokens: tokens}
}
func (s *SecurityService) CacheGet(ctx context.Context, key string) (string, bool, error) {
func (s *SecurityService) cacheGet(ctx context.Context, key string) (string, bool, error) {
return s.cache.Get(ctx, key)
}
func (s *SecurityService) CacheSet(ctx context.Context, key, value string, expiration time.Duration) error {
func (s *SecurityService) cacheSet(ctx context.Context, key, value string, expiration time.Duration) error {
return s.cache.Set(ctx, key, value, expiration)
}
func (s *SecurityService) CacheDelete(ctx context.Context, key string) error {
func (s *SecurityService) cacheDelete(ctx context.Context, key string) error {
return s.cache.Delete(ctx, key)
}
func (s *SecurityService) CacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) {
func (s *SecurityService) cacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) {
return s.cache.Increment(ctx, key, expiration)
}
func (s *SecurityService) ActiveToken(ctx context.Context, username string) (string, error) {
value, _, err := s.cacheGet(ctx, activeTokenKey(username))
return value, err
}
func (s *SecurityService) LoginLocked(ctx context.Context, username string) (bool, error) {
_, locked, err := s.cacheGet(ctx, "login:lock:"+username)
return locked, err
}
func (s *SecurityService) IncrementLoginFailure(ctx context.Context, username string, expiration time.Duration) (int64, error) {
return s.cacheIncrement(ctx, "login:fail:"+username, expiration)
}
func (s *SecurityService) LockLogin(ctx context.Context, username string, expiration time.Duration) error {
return s.cacheSet(ctx, "login:lock:"+username, "1", expiration)
}
func (s *SecurityService) ClearLoginState(ctx context.Context, username string) {
_ = s.cacheDelete(ctx, "login:fail:"+username)
_ = s.cacheDelete(ctx, "login:lock:"+username)
}
func (s *SecurityService) EnsureLoginIPCounter(ctx context.Context, ip string, expiration time.Duration) (int, error) {
value, exists, err := s.cacheGet(ctx, ip)
if err != nil {
return 0, err
}
if exists {
return strconv.Atoi(value)
}
if expiration <= 0 {
expiration = time.Hour
}
if err = s.cacheSet(ctx, ip, "1", expiration); err != nil {
return 0, err
}
return 1, nil
}
func (s *SecurityService) IncrementLoginIP(ctx context.Context, ip string, expiration time.Duration) (int64, error) {
return s.cacheIncrement(ctx, ip, expiration)
}
func (s *SecurityService) IncrementRateLimit(ctx context.Context, key string, expiration time.Duration) (int64, error) {
return s.cacheIncrement(ctx, key, expiration)
}
func (s *SecurityService) SetCaptcha(ctx context.Context, id, value string, expiration time.Duration) error {
return s.cacheSet(ctx, "captcha:"+id, value, expiration)
}
func (s *SecurityService) GetCaptcha(ctx context.Context, id string) (string, bool, error) {
return s.cacheGet(ctx, "captcha:"+id)
}
func (s *SecurityService) DeleteCaptcha(ctx context.Context, id string) error {
return s.cacheDelete(ctx, "captcha:"+id)
}
func (s *SecurityService) UseMultipoint() bool {
config := s.runtime.Admin()
return config != nil && config.System != nil && config.System.UseMultipoint
@ -49,7 +110,7 @@ func (s *SecurityService) ActiveTokenMatches(ctx context.Context, username, toke
if !s.UseMultipoint() {
return true, nil
}
active, ok, err := s.CacheGet(ctx, activeTokenKey(username))
active, ok, err := s.cacheGet(ctx, activeTokenKey(username))
return ok && active == token, err
}
@ -62,5 +123,5 @@ func (s *SecurityService) RotateActiveToken(ctx context.Context, username, oldTo
return err
}
}
return s.CacheSet(ctx, activeTokenKey(username), newToken, expiration)
return s.cacheSet(ctx, activeTokenKey(username), newToken, expiration)
}

View File

@ -3,4 +3,4 @@ package service
import "github.com/google/wire"
// ProviderSet is service providers.
var ProviderSet = wire.NewSet(NewAuthService, NewUserService, NewSystemConfigService, NewAccessService, NewMenuService, NewOrganizationService, NewDictionaryService, NewParameterService, NewTokenService, NewSecurityService, NewVersionService, NewExportService, NewAuditService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService)
var ProviderSet = wire.NewSet(NewAuthService, NewUserService, NewSystemConfigService, NewAuthorityService, NewPermissionService, NewAccessControlService, NewAPIService, NewMenuService, NewDepartmentService, NewPositionService, NewDictionaryService, NewParameterService, NewTokenService, NewSecurityService, NewVersionService, NewExportService, NewAuditService, NewAuditRecorder, NewLogViewerService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService)

View File

@ -3,14 +3,12 @@ package service
import (
"context"
"encoding/json"
"time"
"kra/internal/conf"
"kra/internal/service/dto"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
)
func (s *SystemConfigService) PersistConfig(ctx context.Context) error {
@ -116,127 +114,8 @@ func (s *SystemConfigService) SaveSystemConfig(ctx context.Context, req *dto.Set
return nil
}
next := proto.Clone(config).(*conf.AdminBackend)
data := s.runtime.Data()
next.RouterPrefix = req.Config.Admin.RouterPrefix
if next.System == nil {
next.System = &conf.AdminBackend_System{}
}
next.System.UseRedis = req.Config.Admin.System.UseRedis
next.System.UseMultipoint = req.Config.Admin.System.UseMultipoint
next.System.UseStrictAuth = req.Config.Admin.System.UseStrictAuth
next.System.DisableAutoMigrate = req.Config.Admin.System.DisableAutoMigrate
next.System.UseMongo = req.Config.Admin.System.UseMongo
if req.Config.Admin.Zap != nil {
next.Zap = req.Config.Admin.Zap
}
if req.Config.Admin.Cors != nil {
next.Cors = req.Config.Admin.Cors
}
if req.Config.Admin.App != nil {
next.App = req.Config.Admin.App
}
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 value, err := time.ParseDuration(req.Config.Admin.JWT.ExpiresTime); err == nil && value > 0 {
next.Jwt.ExpiresTime = durationpb.New(value)
}
if value, err := time.ParseDuration(req.Config.Admin.JWT.BufferTime); err == nil && value >= 0 {
next.Jwt.BufferTime = durationpb.New(value)
}
}
if next.Captcha != nil {
if req.Config.Admin.Captcha.KeyLong > 0 {
next.Captcha.KeyLong = req.Config.Admin.Captcha.KeyLong
}
if req.Config.Admin.Captcha.ImgWidth > 0 {
next.Captcha.ImgWidth = req.Config.Admin.Captcha.ImgWidth
}
if req.Config.Admin.Captcha.ImgHeight > 0 {
next.Captcha.ImgHeight = req.Config.Admin.Captcha.ImgHeight
}
if value, err := time.ParseDuration(req.Config.Admin.Captcha.StoreExpiration); err == nil && value > 0 {
next.Captcha.StoreExpiration = durationpb.New(value)
}
}
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 {
if req.Config.Admin.Media.SessionTTL > 0 {
next.Media.SessionTtl = req.Config.Admin.Media.SessionTTL
}
next.Media.MaxFileSize = req.Config.Admin.Media.MaxFileSize
}
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, next.Email.From, next.Email.Host = req.Config.Email.To, req.Config.Email.From, req.Config.Email.Host
next.Email.Nickname, next.Email.Port = req.Config.Email.Nickname, req.Config.Email.Port
next.Email.IsSsl, next.Email.IsLoginAuth = req.Config.Email.IsSSL, req.Config.Email.IsLoginAuth
if req.Config.Email.Secret != "" && req.Config.Email.Secret != "******" {
next.Email.Secret = req.Config.Email.Secret
}
}
if req.Config.Data != nil {
if data == nil {
data = &conf.Data{}
}
if value := req.Config.Data.Database; value != nil {
password := value.Password
if data.Database != nil && (password == "" || password == "******") {
password = data.Database.Password
}
data.Database = &conf.Data_Database{Driver: value.Driver, Source: value.Source, Host: value.Host, Port: value.Port, User: value.User, Password: password, Name: value.Name, Config: value.Config, Path: value.Path, Prefix: value.Prefix, Engine: value.Engine, LogMode: value.LogMode, MaxIdleConns: value.MaxIdleConns, MaxOpenConns: value.MaxOpenConns, ConnMaxLifetime: value.ConnMaxLifetime, Singular: value.Singular}
}
if value := req.Config.Data.Redis; value != nil {
password := value.Password
if data.Redis != nil && (password == "" || password == "******") {
password = data.Redis.Password
}
redis := &conf.Data_Redis{Network: value.Network, Addr: value.Addr, Name: value.Name, Password: password, Db: value.DB, UseCluster: value.UseCluster, ClusterAddrs: value.ClusterAddrs}
if duration, parseErr := time.ParseDuration(value.ReadTimeout); parseErr == nil && duration >= 0 {
redis.ReadTimeout = durationpb.New(duration)
}
if duration, parseErr := time.ParseDuration(value.WriteTimeout); parseErr == nil && duration >= 0 {
redis.WriteTimeout = durationpb.New(duration)
}
data.Redis = redis
}
if req.Config.Data.DatabaseList != nil {
for i, item := range req.Config.Data.DatabaseList {
if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.DatabaseList) && data.DatabaseList[i] != nil {
item.Password = data.DatabaseList[i].Password
}
}
data.DatabaseList = req.Config.Data.DatabaseList
}
if req.Config.Data.RedisList != nil {
for i, item := range req.Config.Data.RedisList {
if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.RedisList) && data.RedisList[i] != nil {
item.Password = data.RedisList[i].Password
}
}
data.RedisList = req.Config.Data.RedisList
}
if req.Config.Data.Mongo != nil {
if data.Mongo != nil && (req.Config.Data.Mongo.Password == "" || req.Config.Data.Mongo.Password == "******") {
req.Config.Data.Mongo.Password = data.Mongo.Password
}
data.Mongo = req.Config.Data.Mongo
}
}
applyAdminConfig(next, req)
data := applyDataConfig(s.runtime.Data(), req)
dataRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(data)
if err != nil {
return err
@ -247,36 +126,3 @@ func (s *SystemConfigService) SaveSystemConfig(ctx context.Context, req *dto.Set
}
return s.uc.PersistRuntimeConfig(ctx, dataRaw, adminRaw)
}
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
}
}
}

View File

@ -0,0 +1,197 @@
package service
import (
"time"
"kra/internal/conf"
"kra/internal/service/dto"
"google.golang.org/protobuf/types/known/durationpb"
)
func applyAdminConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) {
next.RouterPrefix = req.Config.Admin.RouterPrefix
if next.System == nil {
next.System = &conf.AdminBackend_System{}
}
next.System.UseRedis = req.Config.Admin.System.UseRedis
next.System.UseMultipoint = req.Config.Admin.System.UseMultipoint
next.System.UseStrictAuth = req.Config.Admin.System.UseStrictAuth
next.System.DisableAutoMigrate = req.Config.Admin.System.DisableAutoMigrate
next.System.UseMongo = req.Config.Admin.System.UseMongo
if req.Config.Admin.Zap != nil {
next.Zap = req.Config.Admin.Zap
}
if req.Config.Admin.Cors != nil {
next.Cors = req.Config.Admin.Cors
}
if req.Config.Admin.App != nil {
next.App = req.Config.Admin.App
}
applyJWTConfig(next, req)
applyCaptchaConfig(next, req)
applyMediaConfig(next, req)
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, next.Email.From, next.Email.Host = req.Config.Email.To, req.Config.Email.From, req.Config.Email.Host
next.Email.Nickname, next.Email.Port = req.Config.Email.Nickname, req.Config.Email.Port
next.Email.IsSsl, next.Email.IsLoginAuth = req.Config.Email.IsSSL, req.Config.Email.IsLoginAuth
if req.Config.Email.Secret != "" && req.Config.Email.Secret != "******" {
next.Email.Secret = req.Config.Email.Secret
}
}
}
func applyJWTConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) {
if next.Jwt == nil {
return
}
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 value, err := time.ParseDuration(req.Config.Admin.JWT.ExpiresTime); err == nil && value > 0 {
next.Jwt.ExpiresTime = durationpb.New(value)
}
if value, err := time.ParseDuration(req.Config.Admin.JWT.BufferTime); err == nil && value >= 0 {
next.Jwt.BufferTime = durationpb.New(value)
}
}
func applyCaptchaConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) {
if next.Captcha != nil {
if req.Config.Admin.Captcha.KeyLong > 0 {
next.Captcha.KeyLong = req.Config.Admin.Captcha.KeyLong
}
if req.Config.Admin.Captcha.ImgWidth > 0 {
next.Captcha.ImgWidth = req.Config.Admin.Captcha.ImgWidth
}
if req.Config.Admin.Captcha.ImgHeight > 0 {
next.Captcha.ImgHeight = req.Config.Admin.Captcha.ImgHeight
}
if value, err := time.ParseDuration(req.Config.Admin.Captcha.StoreExpiration); err == nil && value > 0 {
next.Captcha.StoreExpiration = durationpb.New(value)
}
}
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
}
}
}
func applyMediaConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) {
if next.Media == nil {
return
}
if req.Config.Admin.Media.SessionTTL > 0 {
next.Media.SessionTtl = req.Config.Admin.Media.SessionTTL
}
next.Media.MaxFileSize = req.Config.Admin.Media.MaxFileSize
}
func applyDataConfig(data *conf.Data, req *dto.SetSystemConfigRequest) *conf.Data {
if req.Config.Data == nil {
return data
}
if data == nil {
data = &conf.Data{}
}
applyDatabaseConfig(data, req)
applyRedisConfig(data, req)
if req.Config.Data.DatabaseList != nil {
for i, item := range req.Config.Data.DatabaseList {
if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.DatabaseList) && data.DatabaseList[i] != nil {
item.Password = data.DatabaseList[i].Password
}
}
data.DatabaseList = req.Config.Data.DatabaseList
}
if req.Config.Data.RedisList != nil {
for i, item := range req.Config.Data.RedisList {
if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.RedisList) && data.RedisList[i] != nil {
item.Password = data.RedisList[i].Password
}
}
data.RedisList = req.Config.Data.RedisList
}
if req.Config.Data.Mongo != nil {
if data.Mongo != nil && (req.Config.Data.Mongo.Password == "" || req.Config.Data.Mongo.Password == "******") {
req.Config.Data.Mongo.Password = data.Mongo.Password
}
data.Mongo = req.Config.Data.Mongo
}
return data
}
func applyDatabaseConfig(data *conf.Data, req *dto.SetSystemConfigRequest) {
value := req.Config.Data.Database
if value == nil {
return
}
password := value.Password
if data.Database != nil && (password == "" || password == "******") {
password = data.Database.Password
}
data.Database = &conf.Data_Database{Driver: value.Driver, Source: value.Source, Host: value.Host, Port: value.Port, User: value.User, Password: password, Name: value.Name, Config: value.Config, Path: value.Path, Prefix: value.Prefix, Engine: value.Engine, LogMode: value.LogMode, MaxIdleConns: value.MaxIdleConns, MaxOpenConns: value.MaxOpenConns, ConnMaxLifetime: value.ConnMaxLifetime, Singular: value.Singular}
}
func applyRedisConfig(data *conf.Data, req *dto.SetSystemConfigRequest) {
value := req.Config.Data.Redis
if value == nil {
return
}
password := value.Password
if data.Redis != nil && (password == "" || password == "******") {
password = data.Redis.Password
}
redis := &conf.Data_Redis{Network: value.Network, Addr: value.Addr, Name: value.Name, Password: password, Db: value.DB, UseCluster: value.UseCluster, ClusterAddrs: value.ClusterAddrs}
if duration, err := time.ParseDuration(value.ReadTimeout); err == nil && duration >= 0 {
redis.ReadTimeout = durationpb.New(duration)
}
if duration, err := time.ParseDuration(value.WriteTimeout); err == nil && duration >= 0 {
redis.WriteTimeout = durationpb.New(duration)
}
data.Redis = redis
}
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
}
}
}