fix: align admin behavior and Kratos integrations

This commit is contained in:
2026-08-14 14:14:51 +08:00
parent 8c4c6ba661
commit e0b0e1d8c8
123 changed files with 4334 additions and 1877 deletions
+2
View File
@@ -1 +1,3 @@
# Service
本目录负责管理端 DTO 与领域对象之间的转换和输入校验,不直接访问数据库。固定 JSON 契约放在 `dto/`,只有用户设置、导出行等真正动态的数据使用 `map[string]any`。
+24 -96
View File
@@ -2,7 +2,6 @@ package service
import (
"context"
"strings"
"kra/internal/biz"
"kra/internal/service/dto"
@@ -15,54 +14,37 @@ func (s *AccessService) Authorize(ctx context.Context, aid uint, path, method st
return s.uc.Authorize(ctx, aid, path, method)
}
func authorityDTO(v *biz.Authority) map[string]any { return convertAuthority(*v) }
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 departmentDTO(v *biz.Department) map[string]any {
func authorityDTO(v *biz.Authority) map[string]any {
out := convertAuthority(*v)
children := make([]map[string]any, 0, len(v.Children))
for _, c := range v.Children {
children = append(children, departmentDTO(c))
for _, child := range v.Children {
children = append(children, authorityDTO(child))
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "parentId": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leaderId": v.LeaderID, "leader": nil, "status": v.Status, "children": children, "namePath": ""}
out["children"] = children
return out
}
func positionDTO(v *biz.Position) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark}
}
func (s *AccessService) Authorities(ctx context.Context) ([]map[string]any, error) {
items, err := s.uc.Repo().ListAuthorities(ctx)
items, err := s.uc.AuthorityTree(ctx)
if err != nil {
return nil, err
}
byID := make(map[uint]map[string]any, len(items))
roots := make([]map[string]any, 0, len(items))
for _, v := range items {
byID[v.AuthorityID] = authorityDTO(v)
}
roots := make([]map[string]any, 0)
for _, v := range items {
item := byID[v.AuthorityID]
if v.ParentID != nil && *v.ParentID != 0 && byID[*v.ParentID] != nil {
parent := byID[*v.ParentID]
children, _ := parent["children"].([]map[string]any)
parent["children"] = append(children, item)
} else {
roots = append(roots, item)
}
roots = append(roots, authorityDTO(v))
}
return roots, nil
}
func (s *AccessService) CreateAuthority(ctx context.Context, v *biz.Authority) error {
return s.uc.Repo().CreateAuthority(ctx, v)
return s.uc.CreateAuthority(ctx, v)
}
func (s *AccessService) CopyAuthority(ctx context.Context, sourceID uint, v *biz.Authority) error {
return s.uc.Repo().CopyAuthority(ctx, sourceID, v)
return s.uc.CopyAuthority(ctx, sourceID, v)
}
func (s *AccessService) UpdateAuthority(ctx context.Context, v *biz.Authority) error {
return s.uc.Repo().UpdateAuthority(ctx, v)
return s.uc.UpdateAuthority(ctx, v)
}
func (s *AccessService) DeleteAuthority(ctx context.Context, id uint) error {
return s.uc.Repo().DeleteAuthority(ctx, id)
return s.uc.DeleteAuthority(ctx, id)
}
func authorityDomain(req *dto.AuthorityRequest) *biz.Authority {
return &biz.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter}
@@ -92,19 +74,19 @@ func (s *AccessService) UpdateAuthorityRequest(ctx context.Context, req *dto.Aut
return authorityResponse(value), nil
}
func (s *AccessService) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error {
return s.uc.Repo().SetAuthorityUsers(ctx, id, ids)
return s.uc.SetAuthorityUsers(ctx, id, ids)
}
func (s *AccessService) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().AuthorityUserIDs(ctx, id)
return s.uc.AuthorityUserIDs(ctx, id)
}
func (s *AccessService) SetDataScope(ctx context.Context, id uint, scope int, ids []uint) error {
return s.uc.Repo().SetDataScope(ctx, id, scope, ids)
return s.uc.SetDataScope(ctx, id, scope, ids)
}
func (s *AccessService) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().DataScopeDepartmentIDs(ctx, id)
return s.uc.DataScopeDepartmentIDs(ctx, id)
}
func (s *AccessService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) {
return s.uc.Repo().ResolveDataScope(ctx, authorityID, userID)
return s.uc.ResolveDataScope(ctx, authorityID, userID)
}
func (s *AccessService) ContextWithDataScope(ctx context.Context, authorityID, userID uint) (context.Context, error) {
scope, err := s.ResolveDataScope(ctx, authorityID, userID)
@@ -114,62 +96,8 @@ func (s *AccessService) ContextWithDataScope(ctx context.Context, authorityID, u
return biz.NewDataScopeContext(ctx, scope), nil
}
func (s *AccessService) DeleteAPIs(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteAPIs(ctx, ids)
}
func (s *AccessService) APIRoleIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().APIRoleIDs(ctx, id)
}
func (s *AccessService) SetAPIRoles(ctx context.Context, id uint, ids []uint) error {
return s.uc.Repo().SetAPIRoles(ctx, id, ids)
}
func (s *AccessService) SyncAPIs(ctx context.Context, routes []*biz.API) (*dto.APISyncResponse, error) {
stored, _, err := s.uc.Repo().ListAPIs(ctx, 1, 100000, nil)
if err != nil {
return nil, err
}
ignored, err := s.uc.Repo().IgnoredAPIs(ctx)
if err != nil {
return nil, err
}
key := func(v *biz.API) string { return strings.ToUpper(v.Method) + " " + v.Path }
ignoreSet := make(map[string]bool, len(ignored))
for _, item := range ignored {
ignoreSet[key(item)] = true
}
routeSet := make(map[string]*biz.API, len(routes))
for _, item := range routes {
if !ignoreSet[key(item)] {
routeSet[key(item)] = item
}
}
storedSet := make(map[string]*biz.API, len(stored))
for _, item := range stored {
storedSet[key(item)] = item
}
added := make([]*dto.APIResponse, 0)
deleted := make([]*dto.APIResponse, 0)
ignoredDTO := make([]*dto.APIResponse, 0, len(ignored))
for k, item := range routeSet {
if storedSet[k] == nil {
added = append(added, apiResponse(item))
}
}
for k, item := range storedSet {
if routeSet[k] == nil && !ignoreSet[k] {
deleted = append(deleted, apiResponse(item))
}
}
for _, item := range ignored {
ignoredDTO = append(ignoredDTO, apiResponse(item))
}
return &dto.APISyncResponse{NewAPIs: added, DeleteAPIs: deleted, IgnoreAPIs: ignoredDTO}, nil
}
func (s *AccessService) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error {
return s.uc.Repo().SetAPIIgnored(ctx, path, method, ignored)
}
func (s *AccessService) Buttons(ctx context.Context, id uint) ([]map[string]any, error) {
items, err := s.uc.Repo().Buttons(ctx, id)
items, err := s.uc.Buttons(ctx, id)
if err != nil {
return nil, err
}
@@ -180,17 +108,17 @@ 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 {
return s.uc.Repo().SetAuthorityButtons(ctx, id, v)
return s.uc.SetAuthorityButtons(ctx, id, v)
}
func (s *AccessService) AuthorityButtonIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().AuthorityButtonIDs(ctx, id)
return s.uc.AuthorityButtonIDs(ctx, id)
}
func (s *AccessService) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
return s.uc.Repo().SelectedButtons(ctx, aid, menuID)
return s.uc.SelectedButtons(ctx, aid, menuID)
}
func (s *AccessService) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error {
return s.uc.Repo().SetSelectedButtons(ctx, aid, menuID, ids)
return s.uc.SetSelectedButtons(ctx, aid, menuID, ids)
}
func (s *AccessService) CanRemoveButton(ctx context.Context, id uint) (bool, error) {
return s.uc.Repo().CanRemoveButton(ctx, id)
return s.uc.CanRemoveButton(ctx, id)
}
+37 -10
View File
@@ -24,29 +24,33 @@ func apiResponses(items []*biz.API) []*dto.APIResponse {
}
func (s *AccessService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*dto.APIResponse, int64, error) {
items, total, err := s.uc.Repo().ListAPIs(ctx, req.Page, req.PageSize, &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method})
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
}
return apiResponses(items), total, nil
}
func (s *AccessService) AllAPI(ctx context.Context) ([]*dto.APIResponse, error) {
items, _, err := s.uc.Repo().ListAPIs(ctx, 1, 10000, nil)
func (s *AccessService) 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
}
return apiResponses(items), nil
}
func (s *AccessService) CreateAPIRequest(ctx context.Context, req *dto.APIRequest) error {
return s.uc.Repo().CreateAPI(ctx, apiDomain(req))
func (s *AccessService) 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 {
return s.uc.Repo().UpdateAPI(ctx, apiDomain(req))
return s.uc.UpdateAPI(ctx, apiDomain(req))
}
func (s *AccessService) FindAPIResponse(ctx context.Context, id uint) (*dto.APIResponse, error) {
value, err := s.uc.Repo().FindAPI(ctx, id)
value, err := s.uc.FindAPI(ctx, id)
if err != nil {
return nil, err
}
@@ -61,17 +65,17 @@ func (s *AccessService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyA
for i := range req.DeleteAPIs {
deleted = append(deleted, apiDomain(&req.DeleteAPIs[i]))
}
return s.uc.Repo().ApplyAPISync(ctx, added, deleted)
return s.uc.ApplyAPISync(ctx, added, deleted)
}
func (s *AccessService) 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.Repo().SetPolicyPaths(ctx, req.AuthorityID, paths)
return s.uc.SetPolicyPaths(ctx, req.AuthorityID, paths)
}
func (s *AccessService) PolicyPathResponses(ctx context.Context, authorityID uint) ([]dto.PolicyPathRequest, error) {
items, err := s.uc.Repo().PolicyPaths(ctx, authorityID)
items, err := s.uc.PolicyPaths(ctx, authorityID)
if err != nil {
return nil, err
}
@@ -88,3 +92,26 @@ func (s *AccessService) SyncAPIResponses(ctx context.Context, routes []dto.APIRe
}
return s.SyncAPIs(ctx, items)
}
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 {
return s.uc.DeleteAPIs(ctx, ids)
}
func (s *AccessService) 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 {
return s.uc.SetAPIRoles(ctx, path, method, ids)
}
func (s *AccessService) 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 {
return s.uc.SetAPIIgnored(ctx, path, method, ignored)
}
+13 -27
View File
@@ -2,7 +2,6 @@ package service
import (
"context"
"errors"
"time"
"kra/internal/biz"
@@ -18,29 +17,20 @@ func tokenDTO(v *biz.APIToken) map[string]any {
}
func (s *SettingsService) CreateAPIToken(ctx context.Context, userID, authorityID uint, days int, remark string) (string, error) {
user, allowed, err := s.Repo().UserHasAuthority(ctx, userID, authorityID)
user, duration, err := s.uc.PrepareAPIToken(ctx, userID, authorityID, days)
if err != nil {
return "", err
}
if !allowed {
return "", errors.New("用户不具备该角色权限")
}
duration := time.Duration(days) * 24 * time.Hour
if days == -1 {
duration = 100 * 365 * 24 * time.Hour
}
if duration <= 0 {
return "", errors.New("有效天数必须大于0或为-1")
}
secret, issuer := "", "kra"
buffer := 24 * time.Hour
if s.config != nil && s.config.Jwt != nil {
secret = s.config.Jwt.SigningKey
if s.config.Jwt.Issuer != "" {
issuer = s.config.Jwt.Issuer
config := s.runtime.Admin()
if config != nil && config.Jwt != nil {
secret = config.Jwt.SigningKey
if config.Jwt.Issuer != "" {
issuer = config.Jwt.Issuer
}
if s.config.Jwt.BufferTime != nil {
buffer = s.config.Jwt.BufferTime.AsDuration()
if config.Jwt.BufferTime != nil {
buffer = config.Jwt.BufferTime.AsDuration()
}
}
token, _, err := adminauth.Generate(secret, issuer, duration, buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, false)
@@ -48,13 +38,13 @@ func (s *SettingsService) CreateAPIToken(ctx context.Context, userID, authorityI
return "", err
}
record := &biz.APIToken{UserID: userID, AuthorityID: authorityID, Token: token, Status: true, ExpiresAt: time.Now().Add(duration), Remark: remark}
if err = s.Repo().CreateAPIToken(ctx, record); err != nil {
if err = s.uc.CreateAPIToken(ctx, record); err != nil {
return "", err
}
return token, nil
}
func (s *SettingsService) APITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]map[string]any, int64, error) {
items, total, err := s.Repo().ListAPITokens(ctx, page, size, userID, status)
items, total, err := s.uc.ListAPITokens(ctx, page, size, userID, status)
if err != nil {
return nil, 0, err
}
@@ -65,15 +55,11 @@ func (s *SettingsService) APITokens(ctx context.Context, page, size int, userID
return out, total, nil
}
func (s *SettingsService) DisableAPIToken(ctx context.Context, id uint) error {
token, err := s.Repo().DisableAPIToken(ctx, id)
if err != nil {
return err
}
return s.Repo().BlacklistToken(ctx, token)
return s.uc.DisableToken(ctx, id)
}
func (s *SettingsService) BlacklistToken(ctx context.Context, token string) error {
return s.Repo().BlacklistToken(ctx, token)
return s.uc.BlacklistToken(ctx, token)
}
func (s *SettingsService) IsTokenDisabled(ctx context.Context, token string) (bool, error) {
return s.Repo().IsTokenDisabled(ctx, token)
return s.uc.IsTokenDisabled(ctx, token)
}
+48 -107
View File
@@ -2,6 +2,7 @@ package service
import (
"context"
"kra/internal/biz"
"kra/internal/service/dto"
)
@@ -9,44 +10,25 @@ import (
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 {
return s.uc.Repo().RecordOperation(ctx, v)
return s.uc.RecordOperation(ctx, v)
}
func (s *AuditService) 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, ip string, userID uint) ([]map[string]any, int64, error) {
return s.Operations(ctx, page, size, &biz.OperationRecord{Path: path, Method: method, Status: status, IP: ip, UserID: userID})
}
func (s *AuditService) RecordLogin(ctx context.Context, v *biz.LoginLog) error {
return s.uc.Repo().RecordLogin(ctx, v)
}
func (s *AuditService) 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, ip string) ([]map[string]any, int64, error) {
return s.Logins(ctx, page, size, &biz.LoginLog{Username: username, IP: ip})
}
func (s *AuditService) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error {
return s.uc.Repo().RecordDataAccess(ctx, v)
}
func (s *AuditService) 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) {
return s.DataAccess(ctx, req.Page, req.PageSize, &biz.DataAccessLog{EventType: req.EventType, TargetTable: req.TargetTable, UserID: req.UserID})
func (s *AuditService) OperationsFilter(ctx context.Context, page, size int, path, method string, status int) ([]map[string]any, int64, error) {
return s.Operations(ctx, page, size, &biz.OperationRecord{Path: path, Method: method, Status: status})
}
func opDTO(v *biz.OperationRecord) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "ip": v.IP, "method": v.Method, "path": v.Path, "status": v.Status, "latency_ms": v.LatencyMS, "agent": v.Agent, "error_message": v.ErrorMessage, "body": v.Body, "resp": v.Response, "user_id": v.UserID, "request_id": v.RequestID, "trace_id": v.TraceID, "device_id": v.DeviceID, "user": nil}
}
func loginDTO(v *biz.LoginLog) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "username": v.Username, "ip": v.IP, "status": v.Status, "errorMessage": v.ErrorMessage, "agent": v.Agent, "userId": v.UserID, "user": nil}
}
func dataAccessDTO(v *biz.DataAccessLog) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "eventType": v.EventType, "targetTable": v.TargetTable, "operation": v.Operation, "userId": v.UserID, "authorityId": v.AuthorityID, "scope": v.Scope, "requestId": v.RequestID, "method": v.Method, "path": v.Path, "detail": v.Detail}
user := map[string]any{}
if v.User != nil {
user = convertUser(v.User)
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "ip": v.IP, "method": v.Method, "path": v.Path, "status": v.Status, "latency_ms": v.LatencyMS, "agent": v.Agent, "error_message": v.ErrorMessage, "body": v.Body, "resp": v.Response, "user_id": v.UserID, "request_id": v.RequestID, "trace_id": v.TraceID, "device_id": v.DeviceID, "user": user}
}
func (s *AuditService) Operations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListOperations(ctx, page, size, q)
items, total, err := s.uc.ListOperations(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
@@ -57,17 +39,34 @@ func (s *AuditService) Operations(ctx context.Context, page, size int, q *biz.Op
return out, total, nil
}
func (s *AuditService) Operation(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindOperation(ctx, id)
v, err := s.uc.FindOperation(ctx, id)
if err != nil {
return nil, err
}
return opDTO(v), nil
}
func (s *AuditService) DeleteOperations(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteOperations(ctx, ids)
return s.uc.DeleteOperations(ctx, ids)
}
func (s *AuditService) 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 {
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) {
return s.Logins(ctx, page, size, &biz.LoginLog{Username: username, Status: status, FilterByStatus: status})
}
func loginDTO(v *biz.LoginLog) map[string]any {
user := map[string]any{}
if v.User != nil {
user = convertUser(v.User)
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "username": v.Username, "ip": v.IP, "status": v.Status, "errorMessage": v.ErrorMessage, "agent": v.Agent, "userId": v.UserID, "user": user}
}
func (s *AuditService) Logins(ctx context.Context, page, size int, q *biz.LoginLog) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListLogins(ctx, page, size, q)
items, total, err := s.uc.ListLogins(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
@@ -78,17 +77,30 @@ func (s *AuditService) Logins(ctx context.Context, page, size int, q *biz.LoginL
return out, total, nil
}
func (s *AuditService) Login(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindLogin(ctx, id)
v, err := s.uc.FindLogin(ctx, id)
if err != nil {
return nil, err
}
return loginDTO(v), nil
}
func (s *AuditService) DeleteLogins(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteLogins(ctx, ids)
return s.uc.DeleteLogins(ctx, ids)
}
func (s *AuditService) 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 {
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) {
return s.DataAccess(ctx, req.Page, req.PageSize, &biz.DataAccessLog{EventType: req.EventType, TargetTable: req.TargetTable, UserID: req.UserID})
}
func dataAccessDTO(v *biz.DataAccessLog) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "eventType": v.EventType, "targetTable": v.TargetTable, "operation": v.Operation, "userId": v.UserID, "authorityId": v.AuthorityID, "scope": v.Scope, "requestId": v.RequestID, "method": v.Method, "path": v.Path, "detail": v.Detail}
}
func (s *AuditService) DataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListDataAccess(ctx, page, size, q)
items, total, err := s.uc.ListDataAccess(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
@@ -99,76 +111,5 @@ func (s *AuditService) DataAccess(ctx context.Context, page, size int, q *biz.Da
return out, total, nil
}
func (s *AuditService) DeleteDataAccess(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteDataAccess(ctx, ids)
}
func (s *AuditService) LogDates(ctx context.Context, month string) (map[string]any, error) {
items, err := s.uc.Repo().LogDates(ctx, month)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"date": v.Date, "fileCount": v.FileCount})
}
return map[string]any{"month": month, "dates": out}, nil
}
func (s *AuditService) LogFiles(ctx context.Context, date string) (map[string]any, error) {
items, err := s.uc.Repo().LogFiles(ctx, date)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"path": v.Path, "name": v.Name, "size": v.Size, "modifiedAt": v.ModifiedAt})
}
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) {
v, err := s.uc.Repo().LogContent(ctx, date, path, cursor)
if err != nil {
return nil, err
}
return map[string]any{"date": v.Date, "path": v.Path, "content": v.Content, "lineCount": v.LineCount, "nextCursor": v.NextCursor, "hasMore": v.HasMore, "limitedByBytes": v.LimitedByBytes, "size": v.Size, "modifiedAt": v.ModifiedAt}, nil
}
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 {
return s.uc.Repo().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 {
return s.CreateError(ctx, errorDomain(req))
}
func (s *AuditService) UpdateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error {
return s.UpdateError(ctx, errorDomain(req))
}
func (s *AuditService) ErrorsFilter(ctx context.Context, page, size int, form, level, status string) ([]map[string]any, int64, error) {
return s.Errors(ctx, page, size, &biz.ErrorRecord{Form: form, Level: level, Status: status})
}
func (s *AuditService) UpdateError(ctx context.Context, v *biz.ErrorRecord) error {
return s.uc.Repo().UpdateError(ctx, v)
}
func (s *AuditService) DeleteErrors(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteErrors(ctx, ids)
}
func (s *AuditService) Error(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindError(ctx, id)
if err != nil {
return nil, err
}
return errorDTO(v), nil
}
func (s *AuditService) Errors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListErrors(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, errorDTO(v))
}
return out, total, nil
return s.uc.DeleteDataAccess(ctx, ids)
}
+52
View File
@@ -0,0 +1,52 @@
package service
import (
"context"
"time"
"kra/internal/biz"
"kra/internal/service/dto"
)
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 {
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 {
return s.CreateError(ctx, errorDomain(req))
}
func (s *AuditService) UpdateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error {
return s.UpdateError(ctx, errorDomain(req))
}
func (s *AuditService) ErrorsFilter(ctx context.Context, page, size int, form, info string, createdAtRange []time.Time) ([]map[string]any, int64, error) {
return s.Errors(ctx, page, size, &biz.ErrorRecord{Form: form, Info: info, CreatedAtRange: createdAtRange})
}
func (s *AuditService) UpdateError(ctx context.Context, v *biz.ErrorRecord) error {
return s.uc.UpdateError(ctx, v)
}
func (s *AuditService) DeleteErrors(ctx context.Context, ids []uint) error {
return s.uc.DeleteErrors(ctx, ids)
}
func (s *AuditService) Error(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.FindError(ctx, id)
if err != nil {
return nil, err
}
return errorDTO(v), nil
}
func (s *AuditService) Errors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]map[string]any, int64, error) {
items, total, err := s.uc.ListErrors(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, errorDTO(v))
}
return out, total, nil
}
+35
View File
@@ -0,0 +1,35 @@
package service
import (
"context"
)
func (s *AuditService) LogDates(ctx context.Context, month string) (map[string]any, error) {
items, err := s.uc.LogDates(ctx, month)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"date": v.Date, "fileCount": v.FileCount})
}
return map[string]any{"month": month, "dates": out}, nil
}
func (s *AuditService) LogFiles(ctx context.Context, date string) (map[string]any, error) {
items, err := s.uc.LogFiles(ctx, date)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"path": v.Path, "name": v.Name, "size": v.Size, "modifiedAt": v.ModifiedAt})
}
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) {
v, err := s.uc.LogContent(ctx, date, path, cursor)
if err != nil {
return nil, err
}
return map[string]any{"date": v.Date, "path": v.Path, "content": v.Content, "lineCount": v.LineCount, "nextCursor": v.NextCursor, "hasMore": v.HasMore, "limitedByBytes": v.LimitedByBytes, "size": v.Size, "modifiedAt": v.ModifiedAt}, nil
}
+19 -9
View File
@@ -34,29 +34,39 @@ func (s *SystemService) Login(ctx context.Context, username, password string) (*
return nil, err
}
if u.Enable != 1 {
return nil, biz.ErrInvalidCredentials
return nil, biz.ErrUserDisabled
}
if security, securityErr := s.settings.CurrentSecurity(ctx); securityErr == nil && security.PwdExpireEnable && u.PasswordUpdatedAt != nil && time.Since(*u.PasswordUpdatedAt) > time.Duration(security.PwdExpireDays)*24*time.Hour {
u.MustChangePassword = true
}
return s.issueLogin(u, u.AuthorityID)
return s.issueLogin(ctx, u, u.AuthorityID)
}
func (s *SystemService) issueLogin(user *biz.User, authorityID uint) (*LoginResult, error) {
func (s *SystemService) issueLogin(ctx context.Context, user *biz.User, authorityID uint) (*LoginResult, error) {
expires, buffer := 7*24*time.Hour, 24*time.Hour
secret, issuer := "", "kra"
if s.config != nil && s.config.Jwt != nil {
secret, issuer = s.config.Jwt.SigningKey, s.config.Jwt.Issuer
if s.config.Jwt.ExpiresTime != nil {
expires = s.config.Jwt.ExpiresTime.AsDuration()
config := s.runtime.Admin()
if config != nil && config.Jwt != nil {
secret, issuer = config.Jwt.SigningKey, config.Jwt.Issuer
if config.Jwt.ExpiresTime != nil {
expires = config.Jwt.ExpiresTime.AsDuration()
}
if s.config.Jwt.BufferTime != nil {
buffer = s.config.Jwt.BufferTime.AsDuration()
if config.Jwt.BufferTime != nil {
buffer = config.Jwt.BufferTime.AsDuration()
}
}
token, claims, err := adminauth.Generate(secret, issuer, expires, buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, user.MustChangePassword)
if err != nil {
return nil, err
}
if s.settings.UseMultipoint() {
oldToken, _, cacheErr := s.settings.cache.Get(ctx, activeTokenKey(user.Username))
if cacheErr != nil {
return nil, cacheErr
}
if cacheErr = s.settings.RotateActiveToken(ctx, user.Username, oldToken, token, expires); cacheErr != nil {
return nil, cacheErr
}
}
return &LoginResult{User: convertUser(user), Token: token, ExpiresAt: claims.ExpiresAt.UnixMilli(), NeedChangePassword: user.MustChangePassword}, nil
}
+41 -97
View File
@@ -2,8 +2,6 @@ package service
import (
"context"
"encoding/json"
"errors"
"kra/internal/biz"
"kra/internal/service/dto"
@@ -16,25 +14,12 @@ func dictionaryDomain(value *dto.DictionaryRequest) *biz.Dictionary {
}
return &biz.Dictionary{ID: value.ID, Name: value.Name, Type: value.Type, Status: status, Desc: value.Description, ParentID: value.ParentID}
}
func detailDomain(value *dto.DictionaryDetailRequest) *biz.DictionaryDetail {
status := true
if value.Status != nil {
status = *value.Status
}
return &biz.DictionaryDetail{ID: value.ID, Label: value.Label, Value: value.Value, Extend: value.Extend, Status: status, Sort: value.Sort, DictionaryID: value.DictionaryID, ParentID: value.ParentID, Level: value.Level, Path: value.Path}
}
func (s *SettingsService) CreateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) error {
return s.CreateDictionary(ctx, dictionaryDomain(req))
}
func (s *SettingsService) UpdateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) error {
return s.UpdateDictionary(ctx, dictionaryDomain(req))
}
func (s *SettingsService) CreateDictionaryDetailRequest(ctx context.Context, req *dto.DictionaryDetailRequest) error {
return s.CreateDictionaryDetail(ctx, detailDomain(req))
}
func (s *SettingsService) UpdateDictionaryDetailRequest(ctx context.Context, req *dto.DictionaryDetailRequest) error {
return s.UpdateDictionaryDetail(ctx, detailDomain(req))
}
func dictionaryDTO(v *biz.Dictionary) map[string]any {
children := make([]map[string]any, 0, len(v.Children))
for _, x := range v.Children {
@@ -46,15 +31,8 @@ func dictionaryDTO(v *biz.Dictionary) map[string]any {
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parentID": v.ParentID, "children": children, "sysDictionaryDetails": details}
}
func detailDTO(v *biz.DictionaryDetail) map[string]any {
children := make([]map[string]any, 0, len(v.Children))
for _, x := range v.Children {
children = append(children, detailDTO(x))
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "label": v.Label, "value": v.Value, "extend": v.Extend, "status": v.Status, "sort": v.Sort, "sysDictionaryID": v.DictionaryID, "parentID": v.ParentID, "level": v.Level, "path": v.Path, "disabled": !v.Status, "children": children}
}
func (s *SettingsService) Dictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]map[string]any, int64, error) {
items, total, err := s.Repo().ListDictionaries(ctx, page, size, name, typ, details)
items, total, err := s.uc.ListDictionaries(ctx, page, size, name, typ, details)
if err != nil {
return nil, 0, err
}
@@ -64,24 +42,52 @@ func (s *SettingsService) Dictionaries(ctx context.Context, page, size int, name
}
return out, total, nil
}
func (s *SettingsService) Dictionary(ctx context.Context, id uint, typ string, details bool) (map[string]any, error) {
v, err := s.Repo().FindDictionary(ctx, id, typ, details)
func (s *SettingsService) Dictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (map[string]any, error) {
v, err := s.uc.FindDictionary(ctx, id, typ, status, details)
if err != nil {
return nil, err
}
return dictionaryDTO(v), nil
}
func (s *SettingsService) ExportDictionary(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.ExportDictionary(ctx, id)
if err != nil {
return nil, err
}
return dictionaryDTO(v), nil
}
func (s *SettingsService) CreateDictionary(ctx context.Context, v *biz.Dictionary) error {
return s.Repo().CreateDictionary(ctx, v)
return s.uc.CreateDictionary(ctx, v)
}
func (s *SettingsService) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error {
return s.Repo().UpdateDictionary(ctx, v)
return s.uc.UpdateDictionary(ctx, v)
}
func (s *SettingsService) DeleteDictionary(ctx context.Context, id uint) error {
return s.Repo().DeleteDictionary(ctx, id)
return s.uc.DeleteDictionary(ctx, id)
}
func (s *SettingsService) DictionaryDetails(ctx context.Context, page, size int, dictionaryID uint, label, value string) ([]map[string]any, int64, error) {
items, total, err := s.Repo().ListDictionaryDetails(ctx, page, size, dictionaryID, label, value)
func detailDomain(value *dto.DictionaryDetailRequest) *biz.DictionaryDetail {
status := true
if value.Status != nil {
status = *value.Status
}
return &biz.DictionaryDetail{ID: value.ID, Label: value.Label, Value: value.Value, Extend: value.Extend, Status: status, Sort: value.Sort, DictionaryID: value.DictionaryID, ParentID: value.ParentID, Level: value.Level, Path: value.Path}
}
func (s *SettingsService) CreateDictionaryDetailRequest(ctx context.Context, req *dto.DictionaryDetailRequest) error {
return s.CreateDictionaryDetail(ctx, detailDomain(req))
}
func (s *SettingsService) UpdateDictionaryDetailRequest(ctx context.Context, req *dto.DictionaryDetailRequest) error {
return s.UpdateDictionaryDetail(ctx, detailDomain(req))
}
func detailDTO(v *biz.DictionaryDetail) map[string]any {
children := make([]map[string]any, 0, len(v.Children))
for _, x := range v.Children {
children = append(children, detailDTO(x))
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "label": v.Label, "value": v.Value, "extend": v.Extend, "status": v.Status, "sort": v.Sort, "sysDictionaryID": v.DictionaryID, "parentID": v.ParentID, "level": v.Level, "path": v.Path, "disabled": !v.Status, "children": children}
}
func (s *SettingsService) DictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]map[string]any, int64, error) {
items, total, err := s.uc.ListDictionaryDetails(ctx, page, size, filter)
if err != nil {
return nil, 0, err
}
@@ -92,14 +98,14 @@ func (s *SettingsService) DictionaryDetails(ctx context.Context, page, size int,
return out, total, nil
}
func (s *SettingsService) DictionaryDetail(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.Repo().FindDictionaryDetail(ctx, id)
v, err := s.uc.FindDictionaryDetail(ctx, id)
if err != nil {
return nil, err
}
return detailDTO(v), nil
}
func (s *SettingsService) DictionaryTree(ctx context.Context, id uint, typ string) ([]map[string]any, error) {
items, err := s.Repo().DictionaryDetailTree(ctx, id, typ)
items, err := s.uc.DictionaryDetailTree(ctx, id, typ)
if err != nil {
return nil, err
}
@@ -110,73 +116,11 @@ func (s *SettingsService) DictionaryTree(ctx context.Context, id uint, typ strin
return out, nil
}
func (s *SettingsService) CreateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
return s.Repo().CreateDictionaryDetail(ctx, v)
return s.uc.CreateDictionaryDetail(ctx, v)
}
func (s *SettingsService) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
return s.Repo().UpdateDictionaryDetail(ctx, v)
return s.uc.UpdateDictionaryDetail(ctx, v)
}
func (s *SettingsService) DeleteDictionaryDetail(ctx context.Context, id uint) error {
return s.Repo().DeleteDictionaryDetail(ctx, id)
}
func (s *SettingsService) ImportDictionaryJSON(ctx context.Context, raw string) error {
var payload struct {
Name string `json:"name"`
Type string `json:"type"`
Status *bool `json:"status"`
Description string `json:"desc"`
Details []dto.DictionaryDetailRequest `json:"sysDictionaryDetails"`
}
if json.Unmarshal([]byte(raw), &payload) != nil || payload.Name == "" || payload.Type == "" {
return errors.New("JSON 格式错误")
}
dictionary := dictionaryDomain(&dto.DictionaryRequest{Name: payload.Name, Type: payload.Type, Status: payload.Status, Description: payload.Description})
if err := s.CreateDictionary(ctx, dictionary); err != nil {
return err
}
mapping := map[uint]uint{}
pending := append([]dto.DictionaryDetailRequest(nil), payload.Details...)
for len(pending) > 0 {
progress := false
next := pending[:0]
for _, item := range pending {
oldID := item.ID
if item.ParentID != nil && *item.ParentID != 0 {
parent, exists := mapping[*item.ParentID]
if !exists {
next = append(next, item)
continue
}
item.ParentID = &parent
}
value := detailDomain(&item)
value.ID = 0
value.DictionaryID = dictionary.ID
if err := s.CreateDictionaryDetail(ctx, value); err != nil {
return errors.New("导入详情失败")
}
if oldID != 0 {
mapping[oldID] = value.ID
}
progress = true
}
if !progress {
return errors.New("字典层级关系无效")
}
pending = next
}
return nil
}
func (s *SettingsService) DictionaryDetailsByParent(ctx context.Context, dictionaryID, parentID uint) ([]map[string]any, error) {
items, _, err := s.DictionaryDetails(ctx, 1, 10000, dictionaryID, "", "")
if err != nil {
return nil, err
}
out := []map[string]any{}
for _, item := range items {
parent, _ := item["parentID"].(*uint)
if (parentID == 0 && parent == nil) || (parent != nil && *parent == parentID) {
out = append(out, item)
}
}
return out, nil
return s.uc.DeleteDictionaryDetail(ctx, id)
}
+40
View File
@@ -0,0 +1,40 @@
package service
import (
"context"
"encoding/json"
"errors"
"kra/internal/biz"
"kra/internal/service/dto"
)
func (s *SettingsService) ImportDictionaryJSON(ctx context.Context, raw string) error {
var payload struct {
Name string `json:"name"`
Type string `json:"type"`
Status *bool `json:"status"`
Description string `json:"desc"`
Details []dto.DictionaryDetailRequest `json:"sysDictionaryDetails"`
}
if json.Unmarshal([]byte(raw), &payload) != nil || payload.Name == "" || payload.Type == "" {
return errors.New("JSON 格式错误")
}
dictionary := dictionaryDomain(&dto.DictionaryRequest{Name: payload.Name, Type: payload.Type, Status: payload.Status, Description: payload.Description})
details := make([]*biz.DictionaryDetail, 0, len(payload.Details))
for i := range payload.Details {
details = append(details, detailDomain(&payload.Details[i]))
}
return s.uc.ImportDictionary(ctx, dictionary, details)
}
func (s *SettingsService) DictionaryDetailsByParent(ctx context.Context, dictionaryID, parentID uint) ([]map[string]any, error) {
items, err := s.uc.DictionaryDetailsByParent(ctx, dictionaryID, parentID)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, item := range items {
out = append(out, detailDTO(item))
}
return out, nil
}
+4 -1
View File
@@ -17,6 +17,8 @@ type APIListRequest struct {
Description string `json:"description"`
APIGroup string `json:"apiGroup"`
Method string `json:"method"`
OrderKey string `json:"orderKey"`
Desc bool `json:"desc"`
}
type APIResponse struct {
@@ -40,7 +42,8 @@ type GetAPIRequest struct {
ID uint `json:"id"`
}
type SetAPIRolesRequest struct {
APIID uint `json:"apiId"`
Path string `json:"path"`
Method string `json:"method"`
AuthorityIDs []uint `json:"authorityIds"`
}
type IgnoreAPIRequest struct {
+13 -6
View File
@@ -1,12 +1,18 @@
package dto
import "time"
type MediaListRequest struct {
Page int `json:"page"`
PageSize int `json:"pageSize"`
Keyword string `json:"keyword"`
ClassID int `json:"classId"`
Tag string `json:"tag"`
UserID uint `json:"userId"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
Keyword string `json:"keyword"`
ClassID int `json:"classId"`
Tag string `json:"tag"`
UserID uint `json:"userId"`
StartCreatedAt time.Time `json:"startCreatedAt"`
EndCreatedAt time.Time `json:"endCreatedAt"`
OrderKey string `json:"orderKey"`
Desc bool `json:"desc"`
}
type IDRequest struct {
ID uint `json:"ID" form:"id"`
@@ -27,6 +33,7 @@ type ImportMediaRequest struct {
Size int64 `json:"size"`
Mime string `json:"mime"`
MD5 string `json:"md5"`
UserID uint `json:"userId"`
}
type StorageListRequest struct {
Prefix string `json:"prefix"`
+1
View File
@@ -57,6 +57,7 @@ type PositionListRequest struct {
PageSize int `json:"pageSize"`
Name string `json:"name"`
Code string `json:"code"`
Status *bool `json:"status"`
}
type DeletePositionRequest struct {
ID uint `json:"ID"`
+1
View File
@@ -41,6 +41,7 @@ type SelfUserRequest struct {
HeaderImg string `json:"headerImg"`
Phone string `json:"phone"`
Email string `json:"email"`
Enable int `json:"enable"`
}
type ResetPasswordRequest struct {
+36 -3
View File
@@ -27,12 +27,45 @@ type SecurityConfigRequest struct {
type SetSystemConfigRequest struct {
Config struct {
Data *struct {
Database *struct {
Driver string `json:"driver"`
Source string `json:"source"`
Host string `json:"host"`
Port string `json:"port"`
User string `json:"user"`
Password string `json:"password"`
Name string `json:"name"`
Config string `json:"config"`
Path string `json:"path"`
} `json:"database"`
Redis *struct {
Network string `json:"network"`
Addr string `json:"addr"`
ReadTimeout string `json:"read_timeout"`
WriteTimeout string `json:"write_timeout"`
} `json:"redis"`
} `json:"data"`
Admin struct {
RouterPrefix string `json:"routerPrefix"`
JWT struct {
SigningKey string `json:"signingKey"`
Issuer string `json:"issuer"`
System struct {
UseRedis bool `json:"useRedis"`
UseMultipoint bool `json:"useMultipoint"`
UseStrictAuth bool `json:"useStrictAuth"`
DisableAutoMigrate bool `json:"disableAutoMigrate"`
} `json:"system"`
JWT struct {
SigningKey string `json:"signingKey"`
ExpiresTime string `json:"expiresTime"`
BufferTime string `json:"bufferTime"`
Issuer string `json:"issuer"`
} `json:"jwt"`
Captcha struct {
KeyLong int32 `json:"keyLong"`
ImgWidth int32 `json:"imgWidth"`
ImgHeight int32 `json:"imgHeight"`
StoreExpiration string `json:"storeExpiration"`
} `json:"captcha"`
Local struct {
StorePath string `json:"storePath"`
PathPrefix string `json:"pathPrefix"`
+14 -8
View File
@@ -13,15 +13,21 @@ type VersionMenuButtonRequest struct {
Name string `json:"name"`
Description string `json:"desc"`
}
type VersionMenuParameterRequest struct {
Type string `json:"type"`
Key string `json:"key"`
Value string `json:"value"`
}
type VersionMenuRequest struct {
Path string `json:"path"`
Name string `json:"name"`
Hidden bool `json:"hidden"`
Component string `json:"component"`
Sort int `json:"sort"`
Meta VersionMenuMetaRequest `json:"meta"`
MenuButtons []VersionMenuButtonRequest `json:"menuBtn"`
Children []VersionMenuRequest `json:"children"`
Path string `json:"path"`
Name string `json:"name"`
Hidden bool `json:"hidden"`
Component string `json:"component"`
Sort int `json:"sort"`
Meta VersionMenuMetaRequest `json:"meta"`
MenuButtons []VersionMenuButtonRequest `json:"menuBtn"`
Parameters []VersionMenuParameterRequest `json:"parameters"`
Children []VersionMenuRequest `json:"children"`
}
type VersionAPIRequest struct {
Path string `json:"path"`
+9 -143
View File
@@ -2,14 +2,10 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/xuri/excelize/v2"
"io"
"time"
"kra/internal/biz"
"kra/internal/service/dto"
"sort"
)
type ExportService struct{ uc *biz.ExportUsecase }
@@ -31,8 +27,8 @@ func (s *ExportService) CreateRequest(ctx context.Context, req *dto.ExportTempla
func (s *ExportService) UpdateRequest(ctx context.Context, req *dto.ExportTemplateRequest) error {
return s.Update(ctx, exportTemplateDomain(req))
}
func (s *ExportService) TemplatesFilter(ctx context.Context, page, size int, name, tableName, templateID string) ([]map[string]any, int64, error) {
return s.Templates(ctx, page, size, &biz.ExportTemplate{Name: name, TableName: tableName, TemplateID: templateID})
func (s *ExportService) TemplatesFilter(ctx context.Context, page, size int, name, tableName, templateID string, start, end *time.Time) ([]map[string]any, int64, error) {
return s.Templates(ctx, page, size, &biz.ExportTemplate{Name: name, TableName: tableName, TemplateID: templateID, StartCreatedAt: start, EndCreatedAt: end})
}
func exportDTO(v *biz.ExportTemplate) map[string]any {
conditions := make([]map[string]any, 0, len(v.Conditions))
@@ -46,23 +42,23 @@ func exportDTO(v *biz.ExportTemplate) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "dbName": v.DBName, "name": v.Name, "tableName": v.TableName, "templateID": v.TemplateID, "templateInfo": v.TemplateInfo, "sql": v.SQL, "importSql": v.ImportSQL, "limit": v.Limit, "order": v.Order, "conditions": conditions, "joinTemplate": joins}
}
func (s *ExportService) Create(ctx context.Context, v *biz.ExportTemplate) error {
return s.uc.Repo().CreateExportTemplate(ctx, v)
return s.uc.CreateExportTemplate(ctx, v)
}
func (s *ExportService) Update(ctx context.Context, v *biz.ExportTemplate) error {
return s.uc.Repo().UpdateExportTemplate(ctx, v)
return s.uc.UpdateExportTemplate(ctx, v)
}
func (s *ExportService) Delete(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteExportTemplates(ctx, ids)
return s.uc.DeleteExportTemplates(ctx, ids)
}
func (s *ExportService) Template(ctx context.Context, id uint, tid string) (map[string]any, error) {
v, err := s.uc.Repo().FindExportTemplate(ctx, id, tid)
v, err := s.uc.FindExportTemplate(ctx, id, tid)
if err != nil {
return nil, err
}
return exportDTO(v), nil
}
func (s *ExportService) Templates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListExportTemplates(ctx, page, size, q)
items, total, err := s.uc.ListExportTemplates(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
@@ -72,133 +68,3 @@ func (s *ExportService) Templates(ctx context.Context, page, size int, q *biz.Ex
}
return out, total, nil
}
func templateColumns(raw string) ([]string, map[string]string, error) {
labels := map[string]string{}
if err := json.Unmarshal([]byte(raw), &labels); err != nil {
return nil, nil, err
}
keys := make([]string, 0, len(labels))
for key := range labels {
keys = append(keys, key)
}
sort.Strings(keys)
return keys, labels, nil
}
func (s *ExportService) Export(ctx context.Context, tid string, params map[string]string) ([]byte, string, error) {
template, err := s.uc.Repo().FindExportTemplate(ctx, 0, tid)
if err != nil {
return nil, "", err
}
rows, _, err := s.uc.Repo().QueryExport(ctx, template, params)
if err != nil {
return nil, "", err
}
keys, labels, err := templateColumns(template.TemplateInfo)
if err != nil {
return nil, "", err
}
file := excelize.NewFile()
sheet := file.GetSheetName(0)
for column, key := range keys {
cell, _ := excelize.CoordinatesToCellName(column+1, 1)
_ = file.SetCellValue(sheet, cell, labels[key])
}
for rowIndex, row := range rows {
for column, key := range keys {
cell, _ := excelize.CoordinatesToCellName(column+1, rowIndex+2)
_ = file.SetCellValue(sheet, cell, row[key])
}
}
buffer, err := file.WriteToBuffer()
_ = file.Close()
if err != nil {
return nil, "", err
}
return buffer.Bytes(), template.Name + ".xlsx", nil
}
func (s *ExportService) ExportBlankTemplate(ctx context.Context, tid string) ([]byte, string, error) {
template, err := s.uc.Repo().FindExportTemplate(ctx, 0, tid)
if err != nil {
return nil, "", err
}
keys, labels, err := templateColumns(template.TemplateInfo)
if err != nil {
return nil, "", err
}
file := excelize.NewFile()
sheet := file.GetSheetName(0)
for column, key := range keys {
cell, _ := excelize.CoordinatesToCellName(column+1, 1)
_ = file.SetCellValue(sheet, cell, labels[key])
}
buffer, err := file.WriteToBuffer()
_ = file.Close()
if err != nil {
return nil, "", err
}
return buffer.Bytes(), template.Name + "_template.xlsx", nil
}
func (s *ExportService) Preview(ctx context.Context, tid string, params map[string]string) (string, error) {
template, err := s.uc.Repo().FindExportTemplate(ctx, 0, tid)
if err != nil {
return "", err
}
_, sql, err := s.uc.Repo().QueryExport(ctx, template, params)
return sql, err
}
func (s *ExportService) Import(ctx context.Context, tid string, reader io.Reader) error {
template, err := s.uc.Repo().FindExportTemplate(ctx, 0, tid)
if err != nil {
return err
}
file, err := excelize.OpenReader(reader)
if err != nil {
return err
}
defer file.Close()
sheets := file.GetSheetList()
if len(sheets) == 0 {
return errors.New("工作簿没有工作表")
}
rows, err := file.GetRows(sheets[0])
if err != nil {
return err
}
if len(rows) < 2 {
return nil
}
keys, labels, err := templateColumns(template.TemplateInfo)
if err != nil {
return err
}
reverse := map[string]string{}
for _, key := range keys {
reverse[labels[key]] = key
}
headers := rows[0]
items := make([]map[string]any, 0, len(rows)-1)
for rowIndex, row := range rows[1:] {
item := map[string]any{}
empty := true
for column, value := range row {
if column >= len(headers) {
continue
}
key := reverse[headers[column]]
if key != "" {
item[key] = value
if value != "" {
empty = false
}
}
}
if !empty {
items = append(items, item)
}
_ = rowIndex
}
if err = s.uc.Repo().ImportExportRows(ctx, template, items); err != nil {
return fmt.Errorf("导入失败: %w", err)
}
return nil
}
+149
View File
@@ -0,0 +1,149 @@
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"github.com/xuri/excelize/v2"
)
func templateColumns(raw string) ([]string, map[string]string, error) {
labels := map[string]string{}
decoder := json.NewDecoder(strings.NewReader(raw))
token, err := decoder.Token()
if err != nil || token != json.Delim('{') {
return nil, nil, err
}
keys := make([]string, 0)
for decoder.More() {
keyToken, tokenErr := decoder.Token()
if tokenErr != nil {
return nil, nil, tokenErr
}
key := keyToken.(string)
var label string
if tokenErr = decoder.Decode(&label); tokenErr != nil {
return nil, nil, tokenErr
}
keys = append(keys, key)
labels[key] = label
}
if _, err = decoder.Token(); err != nil {
return nil, nil, err
}
return keys, labels, nil
}
func (s *ExportService) Export(ctx context.Context, tid string, params map[string]string) ([]byte, string, error) {
template, err := s.uc.FindExportTemplate(ctx, 0, tid)
if err != nil {
return nil, "", err
}
rows, _, err := s.uc.QueryExport(ctx, template, params)
if err != nil {
return nil, "", err
}
keys, labels, err := templateColumns(template.TemplateInfo)
if err != nil {
return nil, "", err
}
file := excelize.NewFile()
sheet := file.GetSheetName(0)
for column, key := range keys {
cell, _ := excelize.CoordinatesToCellName(column+1, 1)
_ = file.SetCellValue(sheet, cell, labels[key])
}
for rowIndex, row := range rows {
for column, key := range keys {
cell, _ := excelize.CoordinatesToCellName(column+1, rowIndex+2)
_ = file.SetCellValue(sheet, cell, row[key])
}
}
buffer, err := file.WriteToBuffer()
_ = file.Close()
if err != nil {
return nil, "", err
}
return buffer.Bytes(), template.Name + ".xlsx", nil
}
func (s *ExportService) ExportBlankTemplate(ctx context.Context, tid string) ([]byte, string, error) {
template, err := s.uc.FindExportTemplate(ctx, 0, tid)
if err != nil {
return nil, "", err
}
keys, labels, err := templateColumns(template.TemplateInfo)
if err != nil {
return nil, "", err
}
file := excelize.NewFile()
sheet := file.GetSheetName(0)
for column, key := range keys {
cell, _ := excelize.CoordinatesToCellName(column+1, 1)
_ = file.SetCellValue(sheet, cell, labels[key])
}
buffer, err := file.WriteToBuffer()
_ = file.Close()
if err != nil {
return nil, "", err
}
return buffer.Bytes(), template.Name + "_template.xlsx", nil
}
func (s *ExportService) Preview(ctx context.Context, tid string, params map[string]string) (string, error) {
template, err := s.uc.FindExportTemplate(ctx, 0, tid)
if err != nil {
return "", err
}
return s.uc.PreviewExport(ctx, template, params)
}
func (s *ExportService) Import(ctx context.Context, tid string, reader io.Reader) error {
template, err := s.uc.FindExportTemplate(ctx, 0, tid)
if err != nil {
return err
}
file, err := excelize.OpenReader(reader)
if err != nil {
return err
}
defer file.Close()
sheets := file.GetSheetList()
if len(sheets) == 0 {
return errors.New("工作簿没有工作表")
}
rows, err := file.GetRows(sheets[0])
if err != nil {
return err
}
if len(rows) < 2 {
return errors.New("Excel data is not enough.\nIt should contain title row and data")
}
keys, labels, err := templateColumns(template.TemplateInfo)
if err != nil {
return err
}
reverse := map[string]string{}
for _, key := range keys {
reverse[labels[key]] = key
}
headers := rows[0]
items := make([]map[string]any, 0, len(rows)-1)
for _, row := range rows[1:] {
item := map[string]any{}
for column, value := range row {
if column >= len(headers) {
continue
}
key := reverse[headers[column]]
if key != "" {
item[key] = value
}
}
items = append(items, item)
}
if err = s.uc.ImportExportRows(ctx, template, items); err != nil {
return fmt.Errorf("导入失败: %w", err)
}
return nil
}
+10 -10
View File
@@ -29,14 +29,14 @@ func (s *MediaService) Upload(ctx context.Context, userID uint, name, mime strin
return mediaDTO(v), nil
}
func (s *MediaService) Media(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindMedia(ctx, id)
v, err := s.uc.FindMedia(ctx, id)
if err != nil {
return nil, err
}
return mediaDTO(v), nil
}
func (s *MediaService) MediaList(ctx context.Context, page, size int, keyword string, categoryID int, tag string, userID uint) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListMedia(ctx, page, size, keyword, categoryID, tag, userID)
func (s *MediaService) MediaList(ctx context.Context, filter biz.MediaFilter) ([]map[string]any, int64, error) {
items, total, err := s.uc.ListMedia(ctx, filter)
if err != nil {
return nil, 0, err
}
@@ -48,15 +48,15 @@ func (s *MediaService) MediaList(ctx context.Context, page, size int, keyword st
}
func (s *MediaService) Delete(ctx context.Context, id uint) error { return s.uc.Delete(ctx, id) }
func (s *MediaService) Rename(ctx context.Context, id uint, name string) error {
return s.uc.Repo().UpdateMediaName(ctx, id, name)
return s.uc.UpdateMediaName(ctx, id, name)
}
func (s *MediaService) ImportURLs(ctx context.Context, items []*biz.MediaFile) error {
return s.uc.Repo().CreateMediaBatch(ctx, items)
return s.uc.CreateMediaBatch(ctx, items)
}
func (s *MediaService) ImportURLRequests(ctx context.Context, userID uint, values []dto.ImportMediaRequest) error {
func (s *MediaService) ImportURLRequests(ctx context.Context, values []dto.ImportMediaRequest) error {
items := make([]*biz.MediaFile, 0, len(values))
for _, value := range values {
items = append(items, &biz.MediaFile{Name: value.Name, CategoryID: value.ClassID, URL: value.URL, Tag: value.Tag, Key: value.Key, Size: value.Size, Mime: value.Mime, MD5: value.MD5, UserID: userID})
items = append(items, &biz.MediaFile{Name: value.Name, CategoryID: value.ClassID, URL: value.URL, Tag: value.Tag, Key: value.Key, Size: value.Size, Mime: value.Mime, MD5: value.MD5, UserID: value.UserID})
}
return s.ImportURLs(ctx, items)
}
@@ -72,7 +72,7 @@ func (s *MediaService) Storage(ctx context.Context, prefix, cursor string, limit
return out, next, more, nil
}
func (s *MediaService) Categories(ctx context.Context) ([]map[string]any, error) {
items, err := s.uc.Repo().ListCategories(ctx)
items, err := s.uc.ListCategories(ctx)
if err != nil {
return nil, err
}
@@ -83,11 +83,11 @@ func (s *MediaService) Categories(ctx context.Context) ([]map[string]any, error)
return out, nil
}
func (s *MediaService) SaveCategory(ctx context.Context, v *biz.AttachmentCategory) error {
return s.uc.Repo().SaveCategory(ctx, v)
return s.uc.SaveCategory(ctx, v)
}
func (s *MediaService) SaveCategoryRequest(ctx context.Context, value *dto.CategoryRequest) error {
return s.SaveCategory(ctx, &biz.AttachmentCategory{ID: value.ID, Name: value.Name, ParentID: value.PID})
}
func (s *MediaService) DeleteCategory(ctx context.Context, id uint) error {
return s.uc.Repo().DeleteCategory(ctx, id)
return s.uc.DeleteCategory(ctx, id)
}
+44 -41
View File
@@ -11,47 +11,6 @@ type MenuService struct{ uc *biz.MenuUsecase }
func NewMenuService(uc *biz.MenuUsecase) *MenuService { return &MenuService{uc: uc} }
func menuDomain(v *dto.MenuRequest) *biz.Menu {
menu := &biz.Menu{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.Meta.ActiveName, KeepAlive: v.Meta.KeepAlive, DefaultMenu: v.Meta.DefaultMenu, Title: v.Meta.Title, Icon: v.Meta.Icon, CloseTab: v.Meta.CloseTab, TransitionType: v.Meta.TransitionType}
menu.Buttons = make([]*biz.MenuButton, 0, len(v.MenuButtons))
for _, button := range v.MenuButtons {
menu.Buttons = append(menu.Buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: v.ID})
}
menu.Parameters = make([]*biz.MenuParameter, 0, len(v.Parameters))
for _, parameter := range v.Parameters {
menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value})
}
return menu
}
func menuResponse(menu *biz.Menu) *dto.MenuResponse {
children := make([]*dto.MenuResponse, 0, len(menu.Children))
for _, child := range menu.Children {
children = append(children, menuResponse(child))
}
buttons := make([]dto.MenuButtonResponse, 0, len(menu.Buttons))
buttonAuthorities := make(map[string]uint, len(menu.Buttons))
for _, button := range menu.Buttons {
buttons = append(buttons, dto.MenuButtonResponse{ID: button.ID, Name: button.Name, Description: button.Description, SysBaseMenuID: button.MenuID})
if button.AuthorityID != 0 {
buttonAuthorities[button.Name] = button.AuthorityID
}
}
parameters := make([]dto.MenuParameterResponse, 0, len(menu.Parameters))
for _, parameter := range menu.Parameters {
parameters = append(parameters, dto.MenuParameterResponse{ID: parameter.ID, SysBaseMenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value})
}
return &dto.MenuResponse{ID: menu.ID, ParentID: menu.ParentID, Path: menu.Path, Name: menu.Name, Hidden: menu.Hidden, Component: menu.Component, Sort: menu.Sort, Meta: dto.MenuMetaResponse{ActiveName: menu.ActiveName, KeepAlive: menu.KeepAlive, DefaultMenu: menu.DefaultMenu, Title: menu.Title, Icon: menu.Icon, CloseTab: menu.CloseTab, TransitionType: menu.TransitionType}, Children: children, Parameters: parameters, MenuButtons: buttons, Buttons: buttonAuthorities}
}
func menuResponses(items []*biz.Menu) []*dto.MenuResponse {
out := make([]*dto.MenuResponse, 0, len(items))
for _, item := range items {
out = append(out, menuResponse(item))
}
return out
}
func (s *MenuService) List(ctx context.Context) ([]*dto.MenuResponse, error) {
items, err := s.uc.List(ctx)
if err != nil {
@@ -107,6 +66,50 @@ func (s *MenuService) AuthorityMenus(ctx context.Context, authorityID uint) ([]*
func (s *MenuService) RoleIDs(ctx context.Context, menuID uint) ([]uint, error) {
return s.uc.RoleIDs(ctx, menuID)
}
func (s *MenuService) DefaultRouterRoleIDs(ctx context.Context, menuID uint) ([]uint, error) {
return s.uc.DefaultRouterRoleIDs(ctx, menuID)
}
func (s *MenuService) SetRoles(ctx context.Context, req *dto.SetMenuRolesRequest) error {
return s.uc.SetRoles(ctx, req.MenuID, req.AuthorityIDs)
}
func menuDomain(v *dto.MenuRequest) *biz.Menu {
menu := &biz.Menu{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.Meta.ActiveName, KeepAlive: v.Meta.KeepAlive, DefaultMenu: v.Meta.DefaultMenu, Title: v.Meta.Title, Icon: v.Meta.Icon, CloseTab: v.Meta.CloseTab, TransitionType: v.Meta.TransitionType}
menu.Buttons = make([]*biz.MenuButton, 0, len(v.MenuButtons))
for _, button := range v.MenuButtons {
menu.Buttons = append(menu.Buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: v.ID})
}
menu.Parameters = make([]*biz.MenuParameter, 0, len(v.Parameters))
for _, parameter := range v.Parameters {
menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value})
}
return menu
}
func menuResponse(menu *biz.Menu) *dto.MenuResponse {
children := make([]*dto.MenuResponse, 0, len(menu.Children))
for _, child := range menu.Children {
children = append(children, menuResponse(child))
}
buttons := make([]dto.MenuButtonResponse, 0, len(menu.Buttons))
buttonAuthorities := make(map[string]uint, len(menu.Buttons))
for _, button := range menu.Buttons {
buttons = append(buttons, dto.MenuButtonResponse{ID: button.ID, Name: button.Name, Description: button.Description, SysBaseMenuID: button.MenuID})
if button.AuthorityID != 0 {
buttonAuthorities[button.Name] = button.AuthorityID
}
}
parameters := make([]dto.MenuParameterResponse, 0, len(menu.Parameters))
for _, parameter := range menu.Parameters {
parameters = append(parameters, dto.MenuParameterResponse{ID: parameter.ID, SysBaseMenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value})
}
return &dto.MenuResponse{ID: menu.ID, ParentID: menu.ParentID, Path: menu.Path, Name: menu.Name, Hidden: menu.Hidden, Component: menu.Component, Sort: menu.Sort, Meta: dto.MenuMetaResponse{ActiveName: menu.ActiveName, KeepAlive: menu.KeepAlive, DefaultMenu: menu.DefaultMenu, Title: menu.Title, Icon: menu.Icon, CloseTab: menu.CloseTab, TransitionType: menu.TransitionType}, Children: children, Parameters: parameters, MenuButtons: buttons, Buttons: buttonAuthorities}
}
func menuResponses(items []*biz.Menu) []*dto.MenuResponse {
out := make([]*dto.MenuResponse, 0, len(items))
for _, item := range items {
out = append(out, menuResponse(item))
}
return out
}
+6 -2
View File
@@ -18,7 +18,11 @@ func departmentResponse(value *biz.Department) *dto.DepartmentResponse {
for _, child := range value.Children {
children = append(children, departmentResponse(child))
}
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: nil, Status: value.Status, Children: children, NamePath: ""}
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 {
@@ -69,7 +73,7 @@ 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.Position{Name: req.Name, Code: req.Code})
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
}
+8 -7
View File
@@ -2,6 +2,7 @@ package service
import (
"context"
"time"
"kra/internal/biz"
"kra/internal/service/dto"
@@ -16,14 +17,14 @@ func (s *SettingsService) CreateParameterRequest(ctx context.Context, req *dto.S
func (s *SettingsService) UpdateParameterRequest(ctx context.Context, req *dto.SystemParameterRequest) error {
return s.UpdateParameter(ctx, parameterDomain(req))
}
func (s *SettingsService) ParametersFilter(ctx context.Context, page, size int, name, key string) ([]map[string]any, int64, error) {
return s.Parameters(ctx, page, size, &biz.SystemParameter{Name: name, Key: key})
func (s *SettingsService) ParametersFilter(ctx context.Context, page, size int, name, key string, start, end *time.Time) ([]map[string]any, int64, error) {
return s.Parameters(ctx, page, size, &biz.SystemParameter{Name: name, Key: key, StartCreatedAt: start, EndCreatedAt: end})
}
func parameterDTO(v *biz.SystemParameter) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "key": v.Key, "value": v.Value, "desc": v.Desc}
}
func (s *SettingsService) Parameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]map[string]any, int64, error) {
items, total, err := s.Repo().ListParameters(ctx, page, size, q)
items, total, err := s.uc.ListParameters(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
@@ -34,18 +35,18 @@ func (s *SettingsService) Parameters(ctx context.Context, page, size int, q *biz
return out, total, nil
}
func (s *SettingsService) Parameter(ctx context.Context, id uint, key string) (map[string]any, error) {
v, err := s.Repo().FindParameter(ctx, id, key)
v, err := s.uc.FindParameter(ctx, id, key)
if err != nil {
return nil, err
}
return parameterDTO(v), nil
}
func (s *SettingsService) CreateParameter(ctx context.Context, v *biz.SystemParameter) error {
return s.Repo().CreateParameter(ctx, v)
return s.uc.CreateParameter(ctx, v)
}
func (s *SettingsService) UpdateParameter(ctx context.Context, v *biz.SystemParameter) error {
return s.Repo().UpdateParameter(ctx, v)
return s.uc.UpdateParameter(ctx, v)
}
func (s *SettingsService) DeleteParameters(ctx context.Context, ids []uint) error {
return s.Repo().DeleteParameters(ctx, ids)
return s.uc.DeleteParameters(ctx, ids)
}
+3 -35
View File
@@ -2,7 +2,6 @@ package service
import (
"context"
"errors"
"kra/internal/biz"
"kra/internal/service/dto"
@@ -19,7 +18,7 @@ func (s *SettingsService) CurrentSecurity(ctx context.Context) (*biz.SecurityCon
return &copy, nil
}
s.securityMu.RUnlock()
value, err := s.Repo().SecurityConfig(ctx)
value, err := s.uc.SecurityConfig(ctx)
if err != nil {
return nil, err
}
@@ -37,10 +36,7 @@ func (s *SettingsService) Security(ctx context.Context) (map[string]any, error)
return securityDTO(value), nil
}
func (s *SettingsService) SaveSecurity(ctx context.Context, value *biz.SecurityConfig) (map[string]any, error) {
if value.KeyLong < 1 || value.ImgWidth < 1 || value.ImgHeight < 1 || value.PwdMinLength < 1 || value.LimitWindow < 1 || value.LimitCount < 1 || value.LockThreshold < 1 || value.LockDuration < 1 || value.PwdExpireDays < 1 {
return nil, errors.New("安全配置数值必须大于0")
}
if err := s.Repo().SaveSecurityConfig(ctx, value); err != nil {
if err := s.uc.UpdateSecurity(ctx, value); err != nil {
return nil, err
}
s.securityMu.Lock()
@@ -66,33 +62,5 @@ func (s *SettingsService) ValidatePassword(ctx context.Context, password string)
if err != nil {
return err
}
if len([]rune(password)) < cfg.PwdMinLength {
return errors.New("密码长度不足")
}
hasUpper, hasLower, hasDigit, hasSpecial := false, false, false, false
for _, ch := range password {
switch {
case ch >= 'A' && ch <= 'Z':
hasUpper = true
case ch >= 'a' && ch <= 'z':
hasLower = true
case ch >= '0' && ch <= '9':
hasDigit = true
default:
hasSpecial = true
}
}
if cfg.PwdRequireUpper && !hasUpper {
return errors.New("密码必须包含大写字母")
}
if cfg.PwdRequireLower && !hasLower {
return errors.New("密码必须包含小写字母")
}
if cfg.PwdRequireDigit && !hasDigit {
return errors.New("密码必须包含数字")
}
if cfg.PwdRequireSpecial && !hasSpecial {
return errors.New("密码必须包含特殊字符")
}
return nil
return s.uc.ValidatePassword(cfg, password)
}
+33 -4
View File
@@ -1,7 +1,9 @@
package service
import (
"context"
"sync"
"time"
"kra/internal/biz"
"kra/internal/conf"
@@ -9,12 +11,39 @@ import (
type SettingsService struct {
uc *biz.SettingsUsecase
config *conf.AdminBackend
runtime *conf.Runtime
securityMu sync.RWMutex
securityCache *biz.SecurityConfig
cache biz.Cache
}
func NewSettingsService(uc *biz.SettingsUsecase, config *conf.AdminBackend) *SettingsService {
return &SettingsService{uc: uc, config: config}
func NewSettingsService(uc *biz.SettingsUsecase, runtime *conf.Runtime, cache biz.Cache) *SettingsService {
return &SettingsService{uc: uc, runtime: runtime, cache: cache}
}
func (s *SettingsService) UseMultipoint() bool {
config := s.runtime.Admin()
return config != nil && config.System != nil && config.System.UseMultipoint
}
func activeTokenKey(username string) string { return "jwt:active:" + username }
func (s *SettingsService) ActiveTokenMatches(ctx context.Context, username, token string) (bool, error) {
if !s.UseMultipoint() {
return true, nil
}
active, ok, err := s.cache.Get(ctx, activeTokenKey(username))
return ok && active == token, err
}
func (s *SettingsService) RotateActiveToken(ctx context.Context, username, oldToken, newToken string, expiration time.Duration) error {
if !s.UseMultipoint() {
return nil
}
if oldToken != "" && oldToken != newToken {
if err := s.BlacklistToken(ctx, oldToken); err != nil {
return err
}
}
return s.cache.Set(ctx, activeTokenKey(username), newToken, expiration)
}
func (s *SettingsService) Repo() biz.SettingsRepo { return s.uc.Repo() }
+3 -3
View File
@@ -9,12 +9,12 @@ import (
type SystemService struct {
uc *biz.SystemUsecase
config *conf.AdminBackend
runtime *conf.Runtime
settings *SettingsService
}
func NewSystemService(uc *biz.SystemUsecase, config *conf.AdminBackend, settings *SettingsService) *SystemService {
return &SystemService{uc: uc, config: config, settings: settings}
func NewSystemService(uc *biz.SystemUsecase, runtime *conf.Runtime, settings *SettingsService) *SystemService {
return &SystemService{uc: uc, runtime: runtime, settings: settings}
}
func (s *SystemService) IsInitialized(ctx context.Context) (bool, error) {
+114 -19
View File
@@ -2,12 +2,15 @@ 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 *SystemService) PersistConfig(ctx context.Context) error { return s.uc.PersistConfig(ctx) }
@@ -22,42 +25,84 @@ func (s *SystemService) PersistAdminConfig(ctx context.Context, value *conf.Admi
func (s *SystemService) ReloadConfig(ctx context.Context) error { return s.uc.ReloadConfig(ctx) }
func (s *SystemService) DiskMountPoints() []string {
config := s.runtime.Admin()
if config == nil {
return nil
}
points := make([]string, 0, len(config.DiskList))
for _, item := range config.DiskList {
if item != nil && item.MountPoint != "" {
points = append(points, item.MountPoint)
}
}
return points
}
func (s *SystemService) SystemConfig() map[string]any {
admin := map[string]any{"routerPrefix": ""}
email := map[string]any{}
if s.config == nil {
return map[string]any{"config": map[string]any{"admin": admin, "email": email}}
config := s.runtime.Admin()
if config == nil {
return map[string]any{"config": map[string]any{"admin": admin, "email": email, "data": map[string]any{}}}
}
admin["routerPrefix"] = s.config.RouterPrefix
if s.config.Jwt != nil {
admin["jwt"] = map[string]any{"signingKey": "******", "expiresTime": s.config.Jwt.ExpiresTime.AsDuration().String(), "bufferTime": s.config.Jwt.BufferTime.AsDuration().String(), "issuer": s.config.Jwt.Issuer}
admin["routerPrefix"] = config.RouterPrefix
if config.System != nil {
admin["system"] = map[string]any{"useRedis": config.System.UseRedis, "useMultipoint": config.System.UseMultipoint, "useStrictAuth": config.System.UseStrictAuth, "disableAutoMigrate": config.System.DisableAutoMigrate}
}
if s.config.Captcha != nil {
admin["captcha"] = map[string]any{"keyLong": s.config.Captcha.KeyLong, "imgWidth": s.config.Captcha.ImgWidth, "imgHeight": s.config.Captcha.ImgHeight, "storeExpiration": s.config.Captcha.StoreExpiration.AsDuration().String()}
if config.Jwt != nil {
admin["jwt"] = map[string]any{"signingKey": "******", "expiresTime": config.Jwt.ExpiresTime.AsDuration().String(), "bufferTime": config.Jwt.BufferTime.AsDuration().String(), "issuer": config.Jwt.Issuer}
}
if s.config.Local != nil {
admin["local"] = map[string]any{"storePath": s.config.Local.StorePath, "pathPrefix": s.config.Local.PathPrefix}
if config.Captcha != nil {
admin["captcha"] = map[string]any{"keyLong": config.Captcha.KeyLong, "imgWidth": config.Captcha.ImgWidth, "imgHeight": config.Captcha.ImgHeight, "storeExpiration": config.Captcha.StoreExpiration.AsDuration().String()}
}
if s.config.Media != nil {
admin["media"] = map[string]any{"sessionTtl": s.config.Media.SessionTtl}
if config.Local != nil {
admin["local"] = map[string]any{"storePath": config.Local.StorePath, "pathPrefix": config.Local.PathPrefix}
}
if s.config.Email != nil {
email = map[string]any{"to": s.config.Email.To, "from": s.config.Email.From, "host": s.config.Email.Host, "secret": "******", "nickname": s.config.Email.Nickname, "port": s.config.Email.Port, "is-ssl": s.config.Email.IsSsl, "is-loginauth": s.config.Email.IsLoginAuth}
if config.Media != nil {
admin["media"] = map[string]any{"sessionTtl": config.Media.SessionTtl}
}
if s.config.Storage != nil {
storage := proto.Clone(s.config.Storage).(*conf.AdminBackend_Storage)
if config.Email != nil {
email = map[string]any{"to": config.Email.To, "from": config.Email.From, "host": config.Email.Host, "secret": "******", "nickname": config.Email.Nickname, "port": config.Email.Port, "is-ssl": config.Email.IsSsl, "is-loginauth": config.Email.IsLoginAuth}
}
if config.Storage != nil {
storage := proto.Clone(config.Storage).(*conf.AdminBackend_Storage)
maskStorageSecrets(storage)
admin["storage"] = storage
}
return map[string]any{"config": map[string]any{"admin": admin, "email": email}}
data := s.runtime.Data()
if data != nil {
if data.Database != nil {
data.Database.Password = "******"
}
for _, database := range data.DatabaseList {
if database != nil {
database.Password = "******"
}
}
}
dataMap := map[string]any{}
if raw, err := (protojson.MarshalOptions{UseProtoNames: true}).Marshal(data); err == nil {
_ = json.Unmarshal(raw, &dataMap)
}
return map[string]any{"config": map[string]any{"admin": admin, "email": email, "data": dataMap}}
}
func (s *SystemService) SaveSystemConfig(ctx context.Context, req *dto.SetSystemConfigRequest) error {
if s.config == nil {
config := s.runtime.Admin()
if config == nil {
return nil
}
next := proto.Clone(s.config).(*conf.AdminBackend)
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
if next.Jwt != nil {
if req.Config.Admin.JWT.SigningKey != "" && req.Config.Admin.JWT.SigningKey != "******" {
next.Jwt.SigningKey = req.Config.Admin.JWT.SigningKey
@@ -65,6 +110,26 @@ func (s *SystemService) SaveSystemConfig(ctx context.Context, req *dto.SetSystem
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 != "" {
@@ -89,7 +154,37 @@ func (s *SystemService) SaveSystemConfig(ctx context.Context, req *dto.SetSystem
next.Email.Secret = req.Config.Email.Secret
}
}
return s.PersistAdminConfig(ctx, next)
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}
}
if value := req.Config.Data.Redis; value != nil {
redis := &conf.Data_Redis{Network: value.Network, Addr: value.Addr}
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
}
}
dataRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(data)
if err != nil {
return err
}
adminRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(next)
if err != nil {
return err
}
return s.uc.PersistRuntimeConfig(ctx, dataRaw, adminRaw)
}
func objectStores(storage *conf.AdminBackend_Storage) []*conf.AdminBackend_ObjectStore {
+21 -164
View File
@@ -1,31 +1,23 @@
package service
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/robfig/cron/v3"
"io"
"time"
"kra/internal/biz"
"kra/internal/conf"
"kra/internal/service/dto"
"net"
"net/http"
"net/url"
"strings"
"time"
)
type TaskService struct {
uc *biz.TaskUsecase
media *biz.MediaUsecase
config *conf.AdminBackend
uc *biz.TaskUsecase
media *biz.MediaUsecase
runtime *conf.Runtime
}
func NewTaskService(uc *biz.TaskUsecase, media *biz.MediaUsecase, config *conf.AdminBackend) *TaskService {
return &TaskService{uc: uc, media: media, config: config}
func NewTaskService(uc *biz.TaskUsecase, media *biz.MediaUsecase, runtime *conf.Runtime) *TaskService {
return &TaskService{uc: uc, media: media, runtime: runtime}
}
func taskDomain(v *dto.TaskRequest) *biz.TimedTask {
return &biz.TimedTask{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
@@ -43,86 +35,26 @@ func (s *TaskService) UpdateRequest(ctx context.Context, req *dto.TaskRequest) e
func (s *TaskService) ListRequest(ctx context.Context, page, size int, name, executorType string, enabled *bool, next map[uint]time.Time) ([]map[string]any, int64, error) {
return s.Tasks(ctx, page, size, &biz.TimedTask{Name: name, ExecutorType: executorType, EnabledFilter: enabled}, next)
}
func (s *TaskService) Validate(v *biz.TimedTask) error {
if v.Name == "" {
return errors.New("任务名不能为空")
}
var err error
if v.WithSeconds {
_, err = cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor).Parse(v.Spec)
} else {
_, err = cron.ParseStandard(v.Spec)
}
if err != nil {
return fmt.Errorf("cron 表达式非法: %w", err)
}
switch v.ExecutorType {
case "method":
if v.MethodName != "ClearDB" && v.MethodName != "CleanStaleUploads" {
return errors.New("方法未注册")
}
if len(v.Params) > 0 && !json.Valid(v.Params) {
return errors.New("params 必须是合法 JSON")
}
case "http":
parsed, parseErr := url.Parse(v.HTTPURL)
if parseErr != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Hostname() == "" || parsed.User != nil {
return errors.New("httpUrl 必须是合法的 http/https 地址")
}
if len(v.HTTPHeader) > 0 {
headers := map[string]string{}
if json.Unmarshal(v.HTTPHeader, &headers) != nil {
return errors.New("httpHeader 必须是 JSON 对象")
}
}
default:
return errors.New("executorType 必须为 method 或 http")
}
return nil
}
func (s *TaskService) Create(ctx context.Context, v *biz.TimedTask) error {
if err := s.Validate(v); err != nil {
return err
}
exists, err := s.uc.Repo().TaskNameExists(ctx, v.Name, 0)
if err != nil {
return err
}
if exists {
return fmt.Errorf("任务名 %s 已存在", v.Name)
}
return s.uc.Repo().CreateTask(ctx, v)
return s.uc.Create(ctx, v)
}
func (s *TaskService) Update(ctx context.Context, v *biz.TimedTask) error {
if v.ID == 0 {
return errors.New("缺少任务 ID")
}
if err := s.Validate(v); err != nil {
return err
}
exists, err := s.uc.Repo().TaskNameExists(ctx, v.Name, v.ID)
if err != nil {
return err
}
if exists {
return fmt.Errorf("任务名 %s 已存在", v.Name)
}
return s.uc.Repo().UpdateTask(ctx, v)
return s.uc.Update(ctx, v)
}
func (s *TaskService) Delete(ctx context.Context, id uint) error {
return s.uc.Repo().DeleteTask(ctx, id)
return s.uc.DeleteTask(ctx, id)
}
func (s *TaskService) Toggle(ctx context.Context, id uint, enabled bool) error {
return s.uc.Repo().ToggleTask(ctx, id, enabled)
return s.uc.ToggleTask(ctx, id, enabled)
}
func (s *TaskService) Task(ctx context.Context, id uint) (*biz.TimedTask, error) {
return s.uc.Repo().FindTask(ctx, id)
return s.uc.FindTask(ctx, id)
}
func taskDTO(v *biz.TimedTask, next *time.Time) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "description": v.Description, "spec": v.Spec, "withSeconds": v.WithSeconds, "executorType": v.ExecutorType, "methodName": v.MethodName, "params": json.RawMessage(v.Params), "httpUrl": v.HTTPURL, "httpMethod": v.HTTPMethod, "httpHeader": json.RawMessage(v.HTTPHeader), "httpBody": v.HTTPBody, "httpAllowPrivate": v.HTTPAllowPrivate, "enabled": v.Enabled, "nextRunAt": next}
}
func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTask, next map[uint]time.Time) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListTasks(ctx, page, size, q)
items, total, err := s.uc.ListTasks(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
@@ -137,8 +69,15 @@ func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTas
}
return out, total, nil
}
// ScheduledTasks is the typed internal contract used by the scheduler and
// deliberately bypasses transport response maps.
func (s *TaskService) ScheduledTasks(ctx context.Context) ([]*biz.TimedTask, error) {
items, _, err := s.uc.ListTasks(ctx, 0, 0, nil)
return items, err
}
func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, status string) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListTaskLogs(ctx, page, size, taskID, status)
items, total, err := s.uc.ListTaskLogs(ctx, page, size, taskID, status)
if err != nil {
return nil, 0, err
}
@@ -148,85 +87,3 @@ func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, sta
}
return out, total, nil
}
func privateIP(ip net.IP) bool {
return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsUnspecified()
}
func (s *TaskService) runHTTP(ctx context.Context, v *biz.TimedTask) (string, error) {
parsed, _ := url.Parse(v.HTTPURL)
if !v.HTTPAllowPrivate {
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", parsed.Hostname())
if err != nil {
return "", err
}
for _, ip := range ips {
if privateIP(ip) {
return "", errors.New("禁止访问内网或环回地址")
}
}
}
method := strings.ToUpper(v.HTTPMethod)
if method == "" {
method = http.MethodGet
}
request, err := http.NewRequestWithContext(ctx, method, v.HTTPURL, bytes.NewBufferString(v.HTTPBody))
if err != nil {
return "", err
}
headers := map[string]string{}
_ = json.Unmarshal(v.HTTPHeader, &headers)
for key, value := range headers {
request.Header.Set(key, value)
}
response, err := (&http.Client{Timeout: 30 * time.Second}).Do(request)
if err != nil {
return "", err
}
defer response.Body.Close()
body, _ := io.ReadAll(io.LimitReader(response.Body, 4096))
output := fmt.Sprintf("HTTP %d: %s", response.StatusCode, string(body))
if response.StatusCode < 200 || response.StatusCode >= 300 {
return output, fmt.Errorf("HTTP 状态码 %d", response.StatusCode)
}
return output, nil
}
func (s *TaskService) Run(ctx context.Context, v *biz.TimedTask, trigger string) (log *biz.TimedTaskLog) {
started := time.Now()
log = &biz.TimedTaskLog{TaskID: v.ID, TaskName: v.Name, TriggerType: trigger, StartedAt: started, Status: "success"}
defer func() {
if recovered := recover(); recovered != nil {
log.Status = "fail"
log.ErrorMsg = fmt.Sprint(recovered)
}
log.FinishedAt = time.Now()
log.DurationMS = log.FinishedAt.Sub(started).Milliseconds()
_ = s.uc.Repo().RecordTaskLog(context.Background(), log)
}()
runCtx, cancel := context.WithTimeout(ctx, 35*time.Second)
defer cancel()
var err error
switch v.ExecutorType {
case "method":
switch v.MethodName {
case "ClearDB":
err = s.uc.Repo().CleanupLogs(runCtx)
log.Output = "过期日志清理完成"
case "CleanStaleUploads":
ttl := 24
if s.config != nil && s.config.Media != nil && s.config.Media.SessionTtl > 0 {
ttl = int(s.config.Media.SessionTtl)
}
err = s.media.CleanupStale(runCtx, ttl)
log.Output = "过期大文件上传会话清理完成"
}
case "http":
log.Output, err = s.runHTTP(runCtx, v)
}
if err != nil {
log.Status = "fail"
log.ErrorMsg = err.Error()
}
return log
}
func (s *TaskService) RegisteredMethods() []map[string]any {
return []map[string]any{{"name": "ClearDB", "description": "清理数据库过期日志(操作记录、JWT黑名单、定时任务日志)"}, {"name": "CleanStaleUploads", "description": "清理过期大文件上传会话"}}
}
+167
View File
@@ -0,0 +1,167 @@
package service
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"syscall"
"time"
"kra/internal/biz"
)
func privateIP(ip net.IP) bool {
return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsUnspecified()
}
func taskHTTPClient(allowPrivate bool) *http.Client {
dialer := &net.Dialer{Timeout: 10 * time.Second, Control: func(_ string, address string, _ syscall.RawConn) error {
if allowPrivate {
return nil
}
host, _, err := net.SplitHostPort(address)
if err != nil {
return fmt.Errorf("解析拨号地址失败: %w", err)
}
ip := net.ParseIP(host)
if ip == nil {
return fmt.Errorf("非法拨号 IP: %s", host)
}
if privateIP(ip) {
return fmt.Errorf("目标解析为内网/环回/链路本地地址, 已被 SSRF 防护拒绝(可在任务上开启\"允许内网\"豁免): %s", ip)
}
return nil
}}
return &http.Client{Timeout: 30 * time.Second, Transport: &http.Transport{Proxy: nil, DialContext: dialer.DialContext}}
}
func (s *TaskService) runHTTP(ctx context.Context, v *biz.TimedTask) (string, error) {
parsed, err := url.Parse(v.HTTPURL)
if err != nil {
return "", err
}
if parsed.Scheme != "http" && parsed.Scheme != "https" {
return "", fmt.Errorf("仅允许 http/https, 实际为 %q", parsed.Scheme)
}
method := strings.ToUpper(v.HTTPMethod)
if method == "" {
method = http.MethodGet
}
request, err := http.NewRequestWithContext(ctx, method, v.HTTPURL, bytes.NewBufferString(v.HTTPBody))
if err != nil {
return "", err
}
headers := map[string]string{}
_ = json.Unmarshal(v.HTTPHeader, &headers)
for key, value := range headers {
request.Header.Set(key, value)
}
response, err := taskHTTPClient(v.HTTPAllowPrivate).Do(request)
if err != nil {
return "", err
}
defer response.Body.Close()
body, _ := io.ReadAll(io.LimitReader(response.Body, 1<<20))
output := fmt.Sprintf("HTTP %d: %s", response.StatusCode, string(body))
if response.StatusCode < 200 || response.StatusCode >= 300 {
return output, fmt.Errorf("非 2xx 响应: %d", response.StatusCode)
}
return output, nil
}
var errTaskTimeout = errors.New("任务执行超时")
func truncateTaskText(value string) string {
const limit = 4000
if len(value) <= limit {
return value
}
return value[:limit] + "...(截断)"
}
func (s *TaskService) runMethod(v *biz.TimedTask) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
done := make(chan error, 1)
go func() {
defer func() {
if recovered := recover(); recovered != nil {
done <- fmt.Errorf("panic: %v", recovered)
}
}()
var err error
switch v.MethodName {
case biz.TaskMethodClearDB:
err = s.uc.CleanupLogs(ctx)
case biz.TaskMethodUploads:
ttl := 24
config := s.runtime.Admin()
if config != nil && config.Media != nil && config.Media.SessionTtl > 0 {
ttl = int(config.Media.SessionTtl)
}
err = s.media.CleanupStale(ctx, ttl)
}
done <- err
}()
select {
case err := <-done:
if errors.Is(err, context.DeadlineExceeded) {
return errTaskTimeout
}
return err
case <-ctx.Done():
return errTaskTimeout
}
}
func (s *TaskService) Run(ctx context.Context, v *biz.TimedTask, trigger string) (log *biz.TimedTaskLog) {
started := time.Now()
log = &biz.TimedTaskLog{TaskID: v.ID, TaskName: v.Name, TriggerType: trigger, StartedAt: started, Status: "success"}
defer func() {
if recovered := recover(); recovered != nil {
log.Status = "fail"
log.ErrorMsg = truncateTaskText(fmt.Sprint(recovered))
}
log.Output = truncateTaskText(log.Output)
log.FinishedAt = time.Now()
log.DurationMS = log.FinishedAt.Sub(started).Milliseconds()
logCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 3*time.Second)
defer cancel()
_ = s.uc.RecordTaskLog(logCtx, log)
}()
var err error
switch v.ExecutorType {
case biz.TaskExecutorMethod:
err = s.runMethod(v)
case biz.TaskExecutorHTTP:
runCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
log.Output, err = s.runHTTP(runCtx, v)
cancel()
default:
err = fmt.Errorf("未知执行器类型: %s", v.ExecutorType)
}
if err != nil {
if errors.Is(err, errTaskTimeout) || errors.Is(err, context.DeadlineExceeded) {
log.Status = "timeout"
} else {
log.Status = "fail"
}
log.ErrorMsg = truncateTaskText(err.Error())
}
return log
}
func (s *TaskService) RegisteredMethods() []map[string]any {
methods := biz.RegisteredTaskMethods()
out := make([]map[string]any, 0, len(methods))
for _, method := range methods {
out = append(out, map[string]any{"name": method.Name, "description": method.Description})
}
return out
}
+17
View File
@@ -0,0 +1,17 @@
package service
import (
"net"
"testing"
)
func TestPrivateIP(t *testing.T) {
for _, value := range []string{"127.0.0.1", "10.0.0.1", "172.16.0.1", "192.168.1.1", "169.254.1.1", "::1"} {
if !privateIP(net.ParseIP(value)) {
t.Fatalf("expected %s to be private", value)
}
}
if privateIP(net.ParseIP("8.8.8.8")) {
t.Fatal("public IP was classified as private")
}
}
+39 -106
View File
@@ -7,26 +7,6 @@ import (
"kra/internal/service/dto"
)
func (s *SystemService) User(ctx context.Context, id uint) (map[string]any, error) {
value, err := s.uc.User(ctx, id)
if err != nil {
return nil, err
}
return convertUser(value), nil
}
func (s *SystemService) Menus(ctx context.Context, authorityID uint) ([]map[string]any, error) {
menus, err := s.uc.Menus(ctx, authorityID)
if err != nil {
return nil, err
}
result := make([]map[string]any, 0, len(menus))
for _, menu := range menus {
result = append(result, convertMenu(menu))
}
return result, nil
}
type UserInput struct {
ID uint
Username, Password, NickName, HeaderImg, Phone, Email string
@@ -48,6 +28,26 @@ func (s *SystemService) UpdateUserRequest(ctx context.Context, value *dto.UserRe
return s.UpdateUser(ctx, userInput(value))
}
func (s *SystemService) User(ctx context.Context, id uint) (map[string]any, error) {
value, err := s.uc.User(ctx, id)
if err != nil {
return nil, err
}
return convertUser(value), nil
}
func (s *SystemService) Menus(ctx context.Context, authorityID uint) ([]map[string]any, error) {
menus, err := s.uc.Menus(ctx, authorityID)
if err != nil {
return nil, err
}
result := make([]map[string]any, 0, len(menus))
for _, menu := range menus {
result = append(result, convertMenu(menu))
}
return result, nil
}
func (s *SystemService) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]map[string]any, int64, error) {
users, total, err := s.uc.ListUsers(ctx, page, pageSize, filter)
if err != nil {
@@ -59,51 +59,36 @@ func (s *SystemService) ListUsers(ctx context.Context, page, pageSize int, filte
}
return result, total, nil
}
func (s *SystemService) Authorities(ctx context.Context) ([]map[string]any, error) {
values, err := s.uc.Authorities(ctx)
if err != nil {
return nil, err
}
result := make([]map[string]any, 0, len(values))
for _, value := range values {
result = append(result, convertAuthority(*value))
}
return result, nil
}
func (s *SystemService) CreateUser(ctx context.Context, input UserInput) (map[string]any, error) {
if err := s.settings.ValidatePassword(ctx, input.Password); err != nil {
return nil, err
}
security, _ := s.settings.CurrentSecurity(ctx)
mustChange := security != nil && security.ForceNewUserChangePassword
if input.AuthorityID == 0 && len(input.AuthorityIDs) > 0 {
input.AuthorityID = input.AuthorityIDs[0]
}
input.AuthorityIDs = includeAuthority(input.AuthorityIDs, input.AuthorityID)
user, err := s.uc.CreateUser(ctx, &biz.User{Username: input.Username, Password: input.Password, NickName: input.NickName, HeaderImg: input.HeaderImg, AuthorityID: input.AuthorityID, Phone: input.Phone, Email: input.Email, Enable: input.Enable, MustChangePassword: mustChange})
user, err := s.uc.CreateUser(ctx, &biz.User{Username: input.Username, Password: input.Password, NickName: input.NickName, HeaderImg: input.HeaderImg, AuthorityID: input.AuthorityID, Phone: input.Phone, Email: input.Email, Enable: input.Enable, MustChangePassword: mustChange}, input.AuthorityIDs)
if err != nil {
return nil, err
}
if len(input.AuthorityIDs) > 0 {
if err = s.uc.SetUserAuthorities(ctx, user.ID, input.AuthorityIDs); err != nil {
return nil, err
}
user, err = s.uc.User(ctx, user.ID)
if err != nil {
return nil, err
}
}
return convertUser(user), nil
}
func (s *SystemService) UpdateUser(ctx context.Context, input UserInput) error {
input.AuthorityIDs = includeAuthority(input.AuthorityIDs, input.AuthorityID)
if err := s.uc.UpdateUser(ctx, &biz.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, AuthorityID: input.AuthorityID, Phone: input.Phone, Email: input.Email, Enable: input.Enable}); err != nil {
return err
}
if len(input.AuthorityIDs) > 0 {
return s.uc.SetUserAuthorities(ctx, input.ID, input.AuthorityIDs)
}
return nil
return s.uc.UpdateUser(ctx, &biz.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, AuthorityID: input.AuthorityID, Phone: input.Phone, Email: input.Email, Enable: input.Enable}, input.AuthorityIDs)
}
func includeAuthority(ids []uint, primary uint) []uint {
if primary == 0 {
return ids
}
for _, id := range ids {
if id == primary {
return ids
}
}
return append(ids, primary)
func (s *SystemService) UpdateSelfUser(ctx context.Context, input UserInput) error {
return s.uc.UpdateSelfUser(ctx, &biz.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, Phone: input.Phone, Email: input.Email, Enable: input.Enable})
}
func (s *SystemService) DeleteUser(ctx context.Context, id uint) error {
return s.uc.DeleteUser(ctx, id)
@@ -120,17 +105,7 @@ func (s *SystemService) ChangePassword(ctx context.Context, id uint, oldPassword
}
return s.uc.ChangePassword(ctx, id, oldPassword, newPassword)
}
func (s *SystemService) Authorities(ctx context.Context) ([]map[string]any, error) {
values, err := s.uc.Authorities(ctx)
if err != nil {
return nil, err
}
result := make([]map[string]any, 0, len(values))
for _, value := range values {
result = append(result, convertAuthority(*value))
}
return result, nil
}
func (s *SystemService) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error {
return s.uc.SetUserAuthorities(ctx, id, authorityIDs)
}
@@ -142,50 +117,8 @@ func (s *SystemService) SwitchAuthority(ctx context.Context, id, authorityID uin
if err != nil {
return nil, err
}
return s.issueLogin(user, authorityID)
return s.issueLogin(ctx, user, authorityID)
}
func (s *SystemService) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error {
return s.uc.SetUserSetting(ctx, id, setting)
}
func convertAuthority(value biz.Authority) map[string]any {
return map[string]any{"authorityId": value.AuthorityID, "authorityName": value.AuthorityName, "parentId": value.ParentID, "children": []map[string]any{}, "menus": []any{}, "dataScope": value.DataScope, "defaultRouter": value.DefaultRouter}
}
func convertUser(user *biz.User) map[string]any {
authorities := make([]map[string]any, 0, len(user.Authorities))
for _, value := range user.Authorities {
authorities = append(authorities, convertAuthority(value))
}
departments := make([]map[string]any, 0, len(user.Departments))
for i := range user.Departments {
departments = append(departments, departmentDTO(&user.Departments[i]))
}
positions := make([]map[string]any, 0, len(user.Positions))
for i := range user.Positions {
positions = append(positions, positionDTO(&user.Positions[i]))
}
department := map[string]any{}
if user.Department != nil {
department = departmentDTO(user.Department)
}
return map[string]any{"ID": user.ID, "CreatedAt": user.CreatedAt, "UpdatedAt": user.UpdatedAt, "DeletedAt": nil, "uuid": user.UUID, "userName": user.Username, "nickName": user.NickName, "headerImg": user.HeaderImg, "authorityId": user.AuthorityID, "authority": convertAuthority(user.Authority), "authorities": authorities, "deptId": user.DeptID, "dept": department, "departments": departments, "positions": positions, "phone": user.Phone, "email": user.Email, "enable": user.Enable, "originSetting": user.OriginSetting}
}
func convertMenu(menu *biz.Menu) map[string]any {
children := make([]map[string]any, 0, len(menu.Children))
for _, child := range menu.Children {
children = append(children, convertMenu(child))
}
buttons := make([]map[string]any, 0, len(menu.Buttons))
buttonAuthorities := make(map[string]uint, len(menu.Buttons))
for _, button := range menu.Buttons {
buttons = append(buttons, map[string]any{"ID": button.ID, "name": button.Name, "desc": button.Description, "sysBaseMenuID": button.MenuID})
if button.AuthorityID != 0 {
buttonAuthorities[button.Name] = button.AuthorityID
}
}
parameters := make([]map[string]any, 0, len(menu.Parameters))
for _, parameter := range menu.Parameters {
parameters = append(parameters, map[string]any{"ID": parameter.ID, "SysBaseMenuID": parameter.MenuID, "type": parameter.Type, "key": parameter.Key, "value": parameter.Value})
}
return map[string]any{"ID": menu.ID, "parentId": menu.ParentID, "path": menu.Path, "name": menu.Name, "hidden": menu.Hidden, "component": menu.Component, "sort": menu.Sort, "meta": map[string]any{"activeName": menu.ActiveName, "keepAlive": menu.KeepAlive, "defaultMenu": menu.DefaultMenu, "title": menu.Title, "icon": menu.Icon, "closeTab": menu.CloseTab, "transitionType": menu.TransitionType}, "children": children, "parameters": parameters, "menuBtn": buttons, "btns": buttonAuthorities}
}
+56
View File
@@ -0,0 +1,56 @@
package service
import "kra/internal/biz"
func departmentDTO(v *biz.Department) map[string]any {
children := make([]map[string]any, 0, len(v.Children))
for _, c := range v.Children {
children = append(children, departmentDTO(c))
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "parentId": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leaderId": v.LeaderID, "leader": nil, "status": v.Status, "children": children, "namePath": v.NamePath}
}
func positionDTO(v *biz.Position) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark}
}
func convertAuthority(value biz.Authority) map[string]any {
return map[string]any{"authorityId": value.AuthorityID, "authorityName": value.AuthorityName, "parentId": value.ParentID, "children": []map[string]any{}, "menus": []any{}, "dataScope": value.DataScope, "defaultRouter": value.DefaultRouter}
}
func convertUser(user *biz.User) map[string]any {
authorities := make([]map[string]any, 0, len(user.Authorities))
for _, value := range user.Authorities {
authorities = append(authorities, convertAuthority(value))
}
departments := make([]map[string]any, 0, len(user.Departments))
for i := range user.Departments {
departments = append(departments, departmentDTO(&user.Departments[i]))
}
positions := make([]map[string]any, 0, len(user.Positions))
for i := range user.Positions {
positions = append(positions, positionDTO(&user.Positions[i]))
}
department := map[string]any{}
if user.Department != nil {
department = departmentDTO(user.Department)
}
return map[string]any{"ID": user.ID, "CreatedAt": user.CreatedAt, "UpdatedAt": user.UpdatedAt, "DeletedAt": nil, "uuid": user.UUID, "userName": user.Username, "nickName": user.NickName, "headerImg": user.HeaderImg, "authorityId": user.AuthorityID, "authority": convertAuthority(user.Authority), "authorities": authorities, "deptId": user.DeptID, "dept": department, "departments": departments, "positions": positions, "phone": user.Phone, "email": user.Email, "enable": user.Enable, "originSetting": user.OriginSetting}
}
func convertMenu(menu *biz.Menu) map[string]any {
children := make([]map[string]any, 0, len(menu.Children))
for _, child := range menu.Children {
children = append(children, convertMenu(child))
}
buttons := make([]map[string]any, 0, len(menu.Buttons))
buttonAuthorities := make(map[string]uint, len(menu.Buttons))
for _, button := range menu.Buttons {
buttons = append(buttons, map[string]any{"ID": button.ID, "name": button.Name, "desc": button.Description, "sysBaseMenuID": button.MenuID})
if button.AuthorityID != 0 {
buttonAuthorities[button.Name] = button.AuthorityID
}
}
parameters := make([]map[string]any, 0, len(menu.Parameters))
for _, parameter := range menu.Parameters {
parameters = append(parameters, map[string]any{"ID": parameter.ID, "SysBaseMenuID": parameter.MenuID, "type": parameter.Type, "key": parameter.Key, "value": parameter.Value})
}
return map[string]any{"ID": menu.ID, "parentId": menu.ParentID, "path": menu.Path, "name": menu.Name, "hidden": menu.Hidden, "component": menu.Component, "sort": menu.Sort, "meta": map[string]any{"activeName": menu.ActiveName, "keepAlive": menu.KeepAlive, "defaultMenu": menu.DefaultMenu, "title": menu.Title, "icon": menu.Icon, "closeTab": menu.CloseTab, "transitionType": menu.TransitionType}, "children": children, "parameters": parameters, "menuBtn": buttons, "btns": buttonAuthorities}
}
+26 -9
View File
@@ -15,8 +15,8 @@ func NewVersionService(uc *biz.VersionUsecase) *VersionService { return &Version
func versionDTO(v *biz.Version) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "versionName": v.Name, "versionCode": v.Code, "description": v.Description, "versionData": v.Data}
}
func (s *VersionService) Versions(ctx context.Context, page, size int, name, code string) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListVersions(ctx, page, size, name, code)
func (s *VersionService) Versions(ctx context.Context, page, size int, name, code string, createdAtRange []*time.Time) ([]map[string]any, int64, error) {
items, total, err := s.uc.ListVersions(ctx, page, size, name, code, createdAtRange)
if err != nil {
return nil, 0, err
}
@@ -27,24 +27,25 @@ func (s *VersionService) Versions(ctx context.Context, page, size int, name, cod
return out, total, nil
}
func (s *VersionService) Version(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindVersion(ctx, id)
v, err := s.uc.FindVersion(ctx, id)
if err != nil {
return nil, err
}
return versionDTO(v), nil
}
func (s *VersionService) VersionData(ctx context.Context, id uint) (string, string, error) {
v, err := s.uc.Repo().FindVersion(ctx, id)
v, err := s.uc.FindVersion(ctx, id)
if err != nil {
return "", "", err
}
return v.Data, v.Code, nil
}
func (s *VersionService) DeleteVersions(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteVersions(ctx, ids)
return s.uc.DeleteVersions(ctx, ids)
}
func (s *VersionService) Export(ctx context.Context, name, code, description string, menuIDs, apiIDs, dictIDs []uint) error {
bundle, err := s.uc.Repo().BuildVersionBundle(ctx, menuIDs, apiIDs, dictIDs)
bundle, err := s.uc.BuildVersionBundle(ctx, menuIDs, apiIDs, dictIDs)
if err != nil {
return err
}
@@ -69,22 +70,28 @@ func (s *VersionService) Export(ctx context.Context, name, code, description str
if err != nil {
return err
}
return s.uc.Repo().CreateVersion(ctx, &biz.Version{Name: name, Code: code, Description: description, Data: string(raw)})
return s.uc.CreateVersion(ctx, &biz.Version{Name: name, Code: code, Description: description, Data: string(raw)})
}
func (s *VersionService) Import(ctx context.Context, bundle *biz.VersionBundle) error {
return s.uc.Repo().ImportVersionBundle(ctx, bundle)
return s.uc.ImportVersionBundle(ctx, bundle)
}
func versionMenuDomain(value dto.VersionMenuRequest) *biz.Menu {
menu := &biz.Menu{Path: value.Path, Name: value.Name, Hidden: value.Hidden, Component: value.Component, Sort: value.Sort, ActiveName: value.Meta.ActiveName, KeepAlive: value.Meta.KeepAlive, DefaultMenu: value.Meta.DefaultMenu, Title: value.Meta.Title, Icon: value.Meta.Icon, CloseTab: value.Meta.CloseTab, TransitionType: value.Meta.TransitionType}
for _, button := range value.MenuButtons {
menu.Buttons = append(menu.Buttons, &biz.MenuButton{Name: button.Name, Description: button.Description})
}
for _, parameter := range value.Parameters {
menu.Parameters = append(menu.Parameters, &biz.MenuParameter{Type: parameter.Type, Key: parameter.Key, Value: parameter.Value})
}
for _, child := range value.Children {
menu.Children = append(menu.Children, versionMenuDomain(child))
}
return menu
}
func (s *VersionService) ImportRequest(ctx context.Context, req *dto.ImportVersionRequest) error {
if req.Version.Name == "" || req.Version.Code == "" {
return biz.ErrInvalidVersion
}
bundle := &biz.VersionBundle{Name: req.Version.Name, Code: req.Version.Code, Description: req.Version.Description, ExportTime: req.Version.ExportTime}
for _, menu := range req.Menus {
bundle.Menus = append(bundle.Menus, versionMenuDomain(menu))
@@ -103,5 +110,15 @@ func (s *VersionService) ImportRequest(ctx context.Context, req *dto.ImportVersi
}
bundle.Dictionaries = append(bundle.Dictionaries, item)
}
return s.Import(ctx, bundle)
if err := s.Import(ctx, bundle); err != nil {
return err
}
raw, _ := json.Marshal(req)
_ = s.uc.CreateVersion(ctx, &biz.Version{
Name: req.Version.Name,
Code: req.Version.Code + "_imported_" + time.Now().Format("20060102150405"),
Description: "导入版本: " + req.Version.Description,
Data: string(raw),
})
return nil
}