优化结构

This commit is contained in:
Yvan
2026-08-27 22:33:43 +08:00
parent 5052835fcb
commit e407d3ac10
86 changed files with 3151 additions and 726 deletions
+7 -6
View File
@@ -19,16 +19,17 @@ func announcementDTO(item *system.Announcement) *dto.AnnouncementResponse {
return &dto.AnnouncementResponse{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, DeletedAt: nil, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: json.RawMessage(item.Attachments)}
}
func announcementDO(in *dto.AnnouncementRequest) *system.Announcement {
return &system.Announcement{ID: in.ID, Title: in.Title, Content: in.Content, UserID: in.UserID, Attachments: in.Attachments}
func announcementDO(in *dto.AnnouncementRequest, userID uint) *system.Announcement {
authorID := int(userID)
return &system.Announcement{ID: in.ID, Title: in.Title, Content: in.Content, UserID: &authorID, Attachments: in.Attachments}
}
func (s *AnnouncementService) Create(ctx context.Context, in *dto.AnnouncementRequest) error {
return s.uc.Create(ctx, announcementDO(in))
func (s *AnnouncementService) Create(ctx context.Context, userID uint, in *dto.AnnouncementRequest) error {
return s.uc.Create(ctx, announcementDO(in, userID))
}
func (s *AnnouncementService) Update(ctx context.Context, in *dto.AnnouncementRequest) error {
return s.uc.Update(ctx, announcementDO(in))
func (s *AnnouncementService) Update(ctx context.Context, userID uint, in *dto.AnnouncementRequest) error {
return s.uc.Update(ctx, announcementDO(in, userID))
}
func (s *AnnouncementService) Delete(ctx context.Context, id string) error {
@@ -0,0 +1,14 @@
package system
import (
"testing"
"kra/internal/service/dto"
)
func TestAnnouncementDomainUsesAuthenticatedUser(t *testing.T) {
value := announcementDO(&dto.AnnouncementRequest{Title: "notice"}, 42)
if value.UserID == nil || *value.UserID != 42 {
t.Fatalf("announcement user = %#v, want 42", value.UserID)
}
}
+3 -3
View File
@@ -56,8 +56,8 @@ func (s *AuditService) DeleteOperations(ctx context.Context, ids []int) error {
func (s *AuditRecorder) RecordLogin(ctx context.Context, v *system.LoginLog) error {
return s.uc.RecordLogin(ctx, v)
}
func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status bool) ([]*dto.LoginLogResponse, int64, error) {
return s.Logins(ctx, page, size, &system.LoginLog{Username: username, Status: status, FilterByStatus: status})
func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status *bool) ([]*dto.LoginLogResponse, int64, error) {
return s.Logins(ctx, page, size, &system.LoginLogFilter{Username: username, Status: status})
}
func loginDTO(v *system.LoginLog) *dto.LoginLogResponse {
var user any = map[string]any{}
@@ -66,7 +66,7 @@ func loginDTO(v *system.LoginLog) *dto.LoginLogResponse {
}
return &dto.LoginLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, 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 *system.LoginLog) ([]*dto.LoginLogResponse, int64, error) {
func (s *AuditService) Logins(ctx context.Context, page, size int, q *system.LoginLogFilter) ([]*dto.LoginLogResponse, int64, error) {
items, total, err := s.uc.ListLogins(ctx, page, size, q)
if err != nil {
return nil, 0, err
+4 -2
View File
@@ -24,13 +24,15 @@ func recordedErrorDomain(value *dto.ErrorRecordRequest) *system.ErrorRecord {
return result
}
func mutatedErrorDomain(value *dto.ErrorRecordMutationRequest) *system.ErrorRecord {
return &system.ErrorRecord{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, Form: value.Form, Info: value.Info, Level: value.Level, RequestID: value.RequestID, TraceID: value.TraceID, Solution: value.Solution, Status: value.Status}
return &system.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 *AuditRecorder) CreateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error {
return s.CreateError(ctx, recordedErrorDomain(req))
}
func (s *AuditRecorder) CreateErrorMutationRequest(ctx context.Context, req *dto.ErrorRecordMutationRequest) error {
return s.CreateError(ctx, mutatedErrorDomain(req))
value := mutatedErrorDomain(req)
value.ID = 0
return s.CreateError(ctx, value)
}
func (s *AuditService) UpdateErrorRequest(ctx context.Context, req *dto.ErrorRecordMutationRequest) error {
return s.UpdateError(ctx, mutatedErrorDomain(req))
+2 -2
View File
@@ -70,10 +70,10 @@ func (s *ExportService) ConsumeToken(ctx context.Context, token string, expectBl
func exportTemplateDomain(value *dto.ExportTemplateRequest) *system.ExportTemplate {
out := &system.ExportTemplate{ID: value.ID, DBName: value.DBName, Name: value.Name, TableName: value.TableName, TemplateID: value.TemplateID, TemplateInfo: value.TemplateInfo, SQL: value.SQL, ImportSQL: value.ImportSQL, Limit: value.Limit, Order: value.Order}
for _, item := range value.Conditions {
out.Conditions = append(out.Conditions, system.ExportCondition{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, TemplateID: item.TemplateID, From: item.From, Column: item.Column, Operator: item.Operator})
out.Conditions = append(out.Conditions, system.ExportCondition{ID: item.ID, TemplateID: item.TemplateID, From: item.From, Column: item.Column, Operator: item.Operator})
}
for _, item := range value.Joins {
out.Joins = append(out.Joins, system.ExportJoin{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, TemplateID: item.TemplateID, Join: item.Join, Table: item.Table, On: item.On})
out.Joins = append(out.Joins, system.ExportJoin{ID: item.ID, TemplateID: item.TemplateID, Join: item.Join, Table: item.Table, On: item.On})
}
return out
}
+14 -11
View File
@@ -4,9 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"strings"
"time"
@@ -69,15 +67,7 @@ func (s *ExportService) Export(ctx context.Context, tid string, params map[strin
lookup = parts[1]
}
}
value := fmt.Sprintf("%v", row[lookup])
if timestamp, ok := row[lookup].(time.Time); ok {
value = timestamp.Format("2006-01-02 15:04:05")
}
if number, parseErr := strconv.ParseFloat(value, 64); parseErr == nil {
_ = file.SetCellValue(sheet, cell, number)
} else {
_ = file.SetCellValue(sheet, cell, value)
}
_ = file.SetCellValue(sheet, cell, exportCellValue(row[lookup]))
}
}
buffer, err := file.WriteToBuffer()
@@ -87,6 +77,19 @@ func (s *ExportService) Export(ctx context.Context, tid string, params map[strin
}
return buffer.Bytes(), template.Name + ".xlsx", nil
}
func exportCellValue(value any) any {
switch typed := value.(type) {
case time.Time:
return typed.Format("2006-01-02 15:04:05")
case []byte:
return string(typed)
case string:
return typed
default:
return value
}
}
func (s *ExportService) ExportBlankTemplate(ctx context.Context, tid string) ([]byte, string, error) {
template, err := s.uc.FindExportTemplate(ctx, 0, tid)
if err != nil {
@@ -0,0 +1,10 @@
package system
import "testing"
func TestExportCellValuePreservesNumericText(t *testing.T) {
value, ok := exportCellValue("007").(string)
if !ok || value != "007" {
t.Fatalf("exportCellValue() = %#v, want string 007", exportCellValue("007"))
}
}
+2 -2
View File
@@ -72,10 +72,10 @@ func (s *MediaService) Rename(ctx context.Context, id uint, name string) error {
func (s *MediaService) ImportURLs(ctx context.Context, items []*system.MediaFile) error {
return s.uc.CreateMediaBatch(ctx, items)
}
func (s *MediaService) ImportURLRequests(ctx context.Context, values []dto.ImportMediaRequest) error {
func (s *MediaService) ImportURLRequests(ctx context.Context, userID uint, values []dto.ImportMediaRequest) error {
items := make([]*system.MediaFile, 0, len(values))
for _, value := range values {
items = append(items, &system.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})
items = append(items, &system.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})
}
return s.ImportURLs(ctx, items)
}
@@ -0,0 +1,30 @@
package system
import (
"context"
"testing"
bizsystem "kra/internal/biz/system"
"kra/internal/service/dto"
)
type mediaImportRepo struct {
bizsystem.MediaRepo
items []*bizsystem.MediaFile
}
func (r *mediaImportRepo) CreateMediaBatch(_ context.Context, items []*bizsystem.MediaFile) error {
r.items = items
return nil
}
func TestImportURLRequestsUsesAuthenticatedUser(t *testing.T) {
repo := &mediaImportRepo{}
service := NewMediaService(bizsystem.NewMediaUsecase(repo, nil, nil), nil)
if err := service.ImportURLRequests(context.Background(), 42, []dto.ImportMediaRequest{{Name: "image", URL: "https://example.test/image.png"}}); err != nil {
t.Fatal(err)
}
if len(repo.items) != 1 || repo.items[0].UserID != 42 {
t.Fatalf("imported media = %#v, want authenticated user 42", repo.items)
}
}
+3 -3
View File
@@ -75,14 +75,14 @@ func (s *MenuService) SetRoles(ctx context.Context, req *dto.SetMenuRolesRequest
}
func menuDomain(v *dto.MenuRequest) *system.Menu {
menu := &system.Menu{CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, 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 := &system.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([]*system.MenuButton, 0, len(v.MenuButtons))
for _, button := range v.MenuButtons {
menu.Buttons = append(menu.Buttons, &system.MenuButton{CreatedAt: button.CreatedAt, UpdatedAt: button.UpdatedAt, ID: button.ID, Name: button.Name, Description: button.Description, MenuID: v.ID})
menu.Buttons = append(menu.Buttons, &system.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: v.ID})
}
menu.Parameters = make([]*system.MenuParameter, 0, len(v.Parameters))
for _, parameter := range v.Parameters {
menu.Parameters = append(menu.Parameters, &system.MenuParameter{CreatedAt: parameter.CreatedAt, UpdatedAt: parameter.UpdatedAt, ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value})
menu.Parameters = append(menu.Parameters, &system.MenuParameter{ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value})
}
return menu
}
+7 -12
View File
@@ -4,32 +4,27 @@ import (
"encoding/json"
"kra/internal/biz/system"
"testing"
"time"
"kra/internal/service/dto"
)
func TestMenuDomainPreservesRelationIdentity(t *testing.T) {
createdAt := time.Date(2025, time.March, 4, 5, 6, 7, 0, time.UTC)
updatedAt := time.Date(2025, time.April, 5, 6, 7, 8, 0, time.UTC)
menu := menuDomain(&dto.MenuRequest{
CreatedAt: createdAt,
UpdatedAt: updatedAt,
ID: 10,
ID: 10,
MenuButtons: []dto.MenuButtonRequest{{
CreatedAt: createdAt, UpdatedAt: updatedAt, ID: 31, Name: "edit", Description: "编辑",
ID: 31, Name: "edit", Description: "编辑",
}},
Parameters: []dto.MenuParameterRequest{{
CreatedAt: createdAt, UpdatedAt: updatedAt, ID: 21, Type: "query", Key: "id", Value: "1",
ID: 21, Type: "query", Key: "id", Value: "1",
}},
})
if !menu.CreatedAt.Equal(createdAt) || !menu.UpdatedAt.Equal(updatedAt) {
t.Fatalf("menu timestamps = (%v, %v), want (%v, %v)", menu.CreatedAt, menu.UpdatedAt, createdAt, updatedAt)
if !menu.CreatedAt.IsZero() || !menu.UpdatedAt.IsZero() {
t.Fatalf("menu timestamps = (%v, %v), want zero before persistence", menu.CreatedAt, menu.UpdatedAt)
}
if len(menu.Buttons) != 1 || menu.Buttons[0].ID != 31 || menu.Buttons[0].MenuID != 10 || !menu.Buttons[0].CreatedAt.Equal(createdAt) || !menu.Buttons[0].UpdatedAt.Equal(updatedAt) {
if len(menu.Buttons) != 1 || menu.Buttons[0].ID != 31 || menu.Buttons[0].MenuID != 10 || !menu.Buttons[0].CreatedAt.IsZero() || !menu.Buttons[0].UpdatedAt.IsZero() {
t.Fatalf("button conversion = %+v", menu.Buttons)
}
if len(menu.Parameters) != 1 || menu.Parameters[0].ID != 21 || menu.Parameters[0].MenuID != 10 || !menu.Parameters[0].CreatedAt.Equal(createdAt) || !menu.Parameters[0].UpdatedAt.Equal(updatedAt) {
if len(menu.Parameters) != 1 || menu.Parameters[0].ID != 21 || menu.Parameters[0].MenuID != 10 || !menu.Parameters[0].CreatedAt.IsZero() || !menu.Parameters[0].UpdatedAt.IsZero() {
t.Fatalf("parameter conversion = %+v", menu.Parameters)
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ func NewParameterService(uc *system.ParameterUsecase) *ParameterService {
}
func parameterDomain(value *dto.SystemParameterRequest) *system.SystemParameter {
return &system.SystemParameter{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, Name: value.Name, Key: value.Key, Value: value.Value, Desc: value.Description}
return &system.SystemParameter{ID: value.ID, Name: value.Name, Key: value.Key, Value: value.Value, Desc: value.Description}
}
func (s *ParameterService) CreateParameterRequest(ctx context.Context, req *dto.SystemParameterRequest) error {
return s.CreateParameter(ctx, parameterDomain(req))
+7
View File
@@ -55,6 +55,13 @@ func (s *SecurityService) ValidatePassword(ctx context.Context, password string)
if err != nil {
return err
}
return s.ValidatePasswordWithConfig(cfg, password)
}
func (s *SecurityService) ValidatePasswordWithConfig(cfg *system.SecurityConfig, password string) error {
if cfg == nil {
return errors.New("安全配置不可用")
}
if err := s.uc.ValidatePassword(cfg, password); err != nil {
return &PasswordPolicyError{Err: err}
}
+5 -2
View File
@@ -29,10 +29,13 @@ func (s *UserService) ListUsersRequest(ctx context.Context, value *dto.UserListR
}
func (s *UserService) CreateUserRequest(ctx context.Context, value *dto.UserRequest) (*dto.UserResponse, error) {
if err := s.settings.ValidatePassword(ctx, value.Password); err != nil {
security, err := s.settings.CurrentSecurity(ctx)
if err != nil {
return nil, err
}
if err := s.settings.ValidatePasswordWithConfig(security, value.Password); err != nil {
return nil, err
}
security, _ := s.settings.CurrentSecurity(ctx)
mustChange := security != nil && security.ForceNewUserChangePassword
user, err := s.uc.CreateUser(ctx, &system.User{Username: value.Username, Password: value.Password, NickName: value.NickName, HeaderImg: value.HeaderImg, AuthorityID: value.AuthorityID, Phone: value.Phone, Email: value.Email, Enable: value.Enable, MustChangePassword: mustChange}, value.AuthorityIDs)
if err != nil {
+8 -3
View File
@@ -256,12 +256,17 @@ func (s *VersionService) ImportRequest(ctx context.Context, req *dto.ImportVersi
if err := s.Import(ctx, bundle); err != nil {
return err
}
raw, _ := json.Marshal(req)
_ = s.uc.CreateVersion(ctx, &system.Version{
raw, err := json.Marshal(req)
if err != nil {
return &system.VersionStageError{Stage: system.VersionStageJSON, Err: err}
}
if err = s.uc.CreateVersion(ctx, &system.Version{
Name: versionPointer(req.Version.Name),
Code: versionPointer(req.Version.Code + "_imported_" + time.Now().Format("20060102150405")),
Description: versionPointer("导入版本: " + req.Version.Description),
Data: versionPointer(string(raw)),
})
}); err != nil {
return &system.VersionStageError{Stage: system.VersionStageSave, Err: err}
}
return nil
}
@@ -0,0 +1,36 @@
package system
import (
"context"
"errors"
"testing"
bizsystem "kra/internal/biz/system"
"kra/internal/service/dto"
)
type versionImportRepo struct {
bizsystem.VersionRepo
saveErr error
}
func (r *versionImportRepo) ImportVersionBundle(context.Context, *bizsystem.VersionBundle) error {
return nil
}
func (r *versionImportRepo) CreateVersion(context.Context, *bizsystem.Version) error {
return r.saveErr
}
func TestImportRequestReturnsVersionAuditSaveFailure(t *testing.T) {
repo := &versionImportRepo{saveErr: errors.New("database unavailable")}
service := NewVersionService(bizsystem.NewVersionUsecase(repo))
request := &dto.ImportVersionRequest{}
request.Version.Name = "release"
request.Version.Code = "v1"
err := service.ImportRequest(context.Background(), request)
var stageErr *bizsystem.VersionStageError
if !errors.As(err, &stageErr) || stageErr.Stage != bizsystem.VersionStageSave {
t.Fatalf("ImportRequest() error = %v, want save stage error", err)
}
}