优化结构

This commit is contained in:
Yvan
2026-08-28 10:51:19 +08:00
parent fec3820464
commit 5b17f47141
12 changed files with 63 additions and 31 deletions
+7 -4
View File
@@ -13,9 +13,12 @@ type API struct {
Description string
APIGroup string
Method string
OrderKey string
Desc bool
StrictAll bool
}
type APIFilter struct {
Path, Description, APIGroup, Method string
OrderKey string
Desc, StrictAll bool
}
type APIRepo interface {
@@ -23,7 +26,7 @@ type APIRepo interface {
UpdateAPI(context.Context, *API) error
DeleteAPIs(context.Context, []uint) error
FindAPI(context.Context, uint) (*API, error)
ListAPIs(context.Context, int, int, *API) ([]*API, int64, error)
ListAPIs(context.Context, int, int, *APIFilter) ([]*API, int64, error)
APIRoleIDs(context.Context, string, string) ([]uint, error)
SetAPIRoles(context.Context, string, string, []uint) error
CheckPolicyStore(context.Context) error
+6 -1
View File
@@ -152,7 +152,12 @@ func (uc *AuthenticationUsecase) Login(ctx context.Context, attempt *LoginAttemp
return nil, fmt.Errorf("%w: %v", ErrLoginState, cacheErr)
}
}
return &AuthenticationResult{User: user, Token: issued.Value, ExpiresAt: issued.ExpiresAt, NeedChangePassword: user.MustChangePassword}, nil
// AuthenticationResult crosses the biz/service boundary; never retain the
// credential hash in the user object returned to transport code. Keep the
// repository-owned value untouched in case another caller shares it.
resultUser := *user
resultUser.Password = ""
return &AuthenticationResult{User: &resultUser, Token: issued.Value, ExpiresAt: issued.ExpiresAt, NeedChangePassword: resultUser.MustChangePassword}, nil
}
func userHasAuthority(user *User, authorityID uint) bool {
@@ -140,6 +140,23 @@ func TestLoginRecordsSuccessBeforeMultipointCacheFailure(t *testing.T) {
}
}
func TestLoginResultDoesNotExposePasswordHash(t *testing.T) {
hash, err := bcrypt.GenerateFromPassword([]byte("secret"), bcrypt.MinCost)
if err != nil {
t.Fatal(err)
}
user := &User{ID: 1, Username: "admin", Password: string(hash), AuthorityID: 888, Enable: 1}
security := NewSecurityUsecase(&authenticationSecurityRepo{}, &authenticationCache{}, &authenticationSettings{}, nil)
uc := NewAuthenticationUsecase(NewUserUsecase(&authenticationUserRepo{user: user}), security, &authenticationIssuer{}, nil)
result, err := uc.Login(context.Background(), &LoginAttempt{Username: "admin", Password: "secret", IP: "127.0.0.1"})
if err != nil {
t.Fatal(err)
}
if result.User.Password != "" {
t.Fatalf("authentication result retained password hash")
}
}
func TestRecordLoginAllowsMissingAuditRecorder(t *testing.T) {
uc := &AuthenticationUsecase{}
uc.recordLogin(context.Background(), &LoginAttempt{Username: "admin"}, false, "failed", 0)
+5 -3
View File
@@ -39,15 +39,17 @@ type ExportTemplate struct {
Order string
Conditions []ExportCondition
Joins []ExportJoin
StartCreatedAt *time.Time
EndCreatedAt *time.Time
}
type ExportTemplateFilter struct {
Name, TableName, TemplateID string
StartCreatedAt, EndCreatedAt *time.Time
}
type ExportRepo interface {
CreateExportTemplate(context.Context, *ExportTemplate) error
UpdateExportTemplate(context.Context, *ExportTemplate) error
DeleteExportTemplates(context.Context, []uint) error
FindExportTemplate(context.Context, uint, string) (*ExportTemplate, error)
ListExportTemplates(context.Context, int, int, *ExportTemplate) ([]*ExportTemplate, int64, error)
ListExportTemplates(context.Context, int, int, *ExportTemplateFilter) ([]*ExportTemplate, int64, error)
QueryExport(context.Context, *ExportTemplate, map[string]string) ([]map[string]any, string, error)
PreviewExport(context.Context, *ExportTemplate, map[string]string) (string, error)
ImportExportRows(context.Context, *ExportTemplate, []map[string]any) error
+3 -1
View File
@@ -19,7 +19,9 @@ import (
var ErrConfigPathRequired = errors.New("configuration path is required")
// Store owns an immutable configuration snapshot and its Viper-backed file
// watcher.
// watcher. It intentionally remains separate from integration/runtimeconfig.Store:
// this store replaces the whole file-backed application snapshot, while the
// integration store publishes database-backed provider changes by key.
// Every accessor returns a deep copy, so readers can safely use it without
// coordinating with a reload in another goroutine.
type Store struct {
+1 -1
View File
@@ -116,7 +116,7 @@ func (r *apiRepo) FindAPI(ctx context.Context, id uint) (*system.API, error) {
}
return apiFromPO(po), nil
}
func (r *apiRepo) ListAPIs(ctx context.Context, page, size int, q *system.API) ([]*system.API, int64, error) {
func (r *apiRepo) ListAPIs(ctx context.Context, page, size int, q *system.APIFilter) ([]*system.API, int64, error) {
db := r.data.DB().WithContext(ctx).Model(&apiPO{})
if q != nil && q.StrictAll {
config := r.data.Runtime().Admin()
+1 -1
View File
@@ -152,7 +152,7 @@ func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string
}
return exportFromPO(po, conditions, joins), nil
}
func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q *system.ExportTemplate) ([]*system.ExportTemplate, int64, error) {
func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q *system.ExportTemplateFilter) ([]*system.ExportTemplate, int64, error) {
db := r.data.DB().WithContext(ctx).Model(&exportTemplatePO{})
if q != nil {
if q.StartCreatedAt != nil && q.EndCreatedAt != nil {
@@ -23,6 +23,9 @@ type listener struct {
}
type Store struct {
// This store is intentionally not merged with config.Store. Integration
// settings are database-backed and listeners subscribe by provider key,
// whereas config.Store owns file snapshots and fsnotify lifecycle.
mu sync.RWMutex
values map[string]Config
listeners map[uint64]listener
+1 -1
View File
@@ -31,7 +31,7 @@ type SetDataScopeRequest struct {
type AuthorityResponse struct {
CreatedAt time.Time `json:"CreatedAt"`
UpdatedAt time.Time `json:"UpdatedAt"`
DeletedAt *time.Time `json:"DeletedAt"`
DeletedAt *time.Time `json:"-"`
AuthorityID uint `json:"authorityId"`
AuthorityName string `json:"authorityName"`
ParentID *uint `json:"parentId"`
+3 -3
View File
@@ -42,7 +42,7 @@ func apiResponses(items []*system.API) []*dto.APIResponse {
}
func (s *APIService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*dto.APIResponse, int64, error) {
items, total, err := s.uc.ListAPIs(ctx, req.Page, req.PageSize, &system.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method, OrderKey: req.OrderKey, Desc: req.Desc})
items, total, err := s.uc.ListAPIs(ctx, req.Page, req.PageSize, &system.APIFilter{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
}
@@ -50,7 +50,7 @@ func (s *APIService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*d
}
func (s *APIService) AllAPI(ctx context.Context, strict bool) ([]*dto.APIResponse, error) {
items, _, err := s.uc.ListAPIs(ctx, 0, 0, &system.API{StrictAll: strict})
items, _, err := s.uc.ListAPIs(ctx, 0, 0, &system.APIFilter{StrictAll: strict})
if err != nil {
return nil, err
}
@@ -61,7 +61,7 @@ func (s *APIService) Groups(ctx context.Context) ([]string, map[string]string, e
// The compatible group endpoint uses an unqualified Find, whose natural order is
// the primary-key ascending order. GetAllApis intentionally uses id DESC,
// so do not reuse it here or the group/dropdown order changes.
items, _, err := s.uc.ListAPIs(ctx, 0, 0, &system.API{OrderKey: "id"})
items, _, err := s.uc.ListAPIs(ctx, 0, 0, &system.APIFilter{OrderKey: "id"})
if err != nil {
return nil, nil, err
}
+2 -2
View File
@@ -84,7 +84,7 @@ func (s *ExportService) UpdateRequest(ctx context.Context, req *dto.ExportTempla
return s.Update(ctx, exportTemplateDomain(req))
}
func (s *ExportService) TemplatesFilter(ctx context.Context, page, size int, name, tableName, templateID string, start, end *time.Time) ([]*dto.ExportTemplateResponse, int64, error) {
return s.Templates(ctx, page, size, &system.ExportTemplate{Name: name, TableName: tableName, TemplateID: templateID, StartCreatedAt: start, EndCreatedAt: end})
return s.Templates(ctx, page, size, &system.ExportTemplateFilter{Name: name, TableName: tableName, TemplateID: templateID, StartCreatedAt: start, EndCreatedAt: end})
}
func exportDTO(v *system.ExportTemplate) *dto.ExportTemplateResponse {
var conditions []dto.ExportConditionResponse
@@ -119,7 +119,7 @@ func (s *ExportService) Template(ctx context.Context, id uint, tid string) (*dto
}
return exportDTO(v), nil
}
func (s *ExportService) Templates(ctx context.Context, page, size int, q *system.ExportTemplate) ([]*dto.ExportTemplateResponse, int64, error) {
func (s *ExportService) Templates(ctx context.Context, page, size int, q *system.ExportTemplateFilter) ([]*dto.ExportTemplateResponse, int64, error) {
items, total, err := s.uc.ListExportTemplates(ctx, page, size, q)
if err != nil {
return nil, 0, err