From 6f9908d926828eb0a429c4b2aafa6d126410f43a Mon Sep 17 00:00:00 2001 From: Yvan <8574526@qq,com> Date: Fri, 28 Aug 2026 16:37:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/code-review-issues.md | 26 +++++++++++++------------- internal/biz/system/media_upload.go | 6 ++++-- internal/biz/task/task.go | 4 ++-- internal/biz/task/task_registry.go | 7 ++----- internal/config/secrets.go | 6 +++++- internal/data/data_scope.go | 5 +++++ internal/data/data_scope_record.go | 9 --------- internal/data/system/media_upload.go | 12 +++++++++--- internal/data/task/provider.go | 10 +--------- internal/data/task/task.go | 5 +++-- internal/routecatalog/catalog.go | 6 +++--- internal/service/dto/authentication.go | 8 -------- internal/service/dto/system.go | 7 +++++++ internal/worker/task_executor.go | 3 ++- 14 files changed, 56 insertions(+), 58 deletions(-) delete mode 100644 internal/data/data_scope_record.go delete mode 100644 internal/service/dto/authentication.go diff --git a/docs/code-review-issues.md b/docs/code-review-issues.md index c3588ec..72d5051 100644 --- a/docs/code-review-issues.md +++ b/docs/code-review-issues.md @@ -73,33 +73,33 @@ | 文件 | 行数 | 问题 | 合并目标 | |---|---|---|---| -| internal/data/data_scope_record.go | 9 | 单行类型别名,包内 13 处使用 | 并入 data/data_scope.go | -| internal/service/dto/authentication.go | 8 | 仅 LoginResponse 1 个类型(LoginRequest 在 system.go) | 并入 dto/system.go | -| internal/data/task/provider.go 别名层 | 11 | `type Provider = dataprovider.Database` 纯转手别名 | 删别名直接用 dataprovider.Database | -| internal/biz/task/task_registry.go 别名行 | 11 | TaskMethodFunc/TaskMethod 纯别名零增值(窄接口保留) | 删 :5-6 别名行 | +| ~~internal/data/data_scope_record.go~~ | ~~9~~ | ~~单行类型别名,包内 13 处使用~~ | ~~已并入 `internal/data/data_scope.go`,行为不变~~ | +| ~~internal/service/dto/authentication.go~~ | ~~8~~ | ~~仅 LoginResponse 1 个类型(LoginRequest 在 system.go)~~ | ~~已并入 `internal/service/dto/system.go`,避免单类型文件~~ | +| ~~internal/data/task/provider.go 别名层~~ | ~~11~~ | ~~`type Provider = dataprovider.Database` 纯转手别名~~ | ~~已删除别名,任务仓储直接依赖 `dataprovider.Database`;ProviderSet 保留为 Wire 注入边界~~ | +| ~~internal/biz/task/task_registry.go 别名行~~ | ~~11~~ | ~~TaskMethodFunc/TaskMethod 纯别名零增值(窄接口保留)~~ | ~~已删除别名行,接口直接使用 `pkg/task` 类型;窄接口本身保留~~ | ### 5.2 轻度过拆(建议合并,5 项) | 文件 | 行数 | 问题 | 建议 | |---|---|---|---| -| handler/navigation.go | 26 | 仅 Menu 一个端点,依赖 UserService 与 user.go 相同 | 并入 user.go | -| handler/session.go | 25 | 仅 Logout 一个端点 | 并入 user.go 或 public.go | -| handler/http.go | 31 | 转发不一致(双风格) | 删转发或统一 | -| data/system/time.go | 15 | deletedAtPointer 单函数 | 并入 models.go | -| data/system/audit.go | 17 | 3 个 repo 构造器与实现跨文件分离 | 构造器归位首个实现文件 | +| ~~handler/navigation.go~~ | ~~26~~ | ~~仅 Menu 一个端点,依赖 UserService 与 user.go 相同~~ | ~~复核后保留:导航是独立路由资源,单独构造器使路由注册和权限边界清晰;合并只减少文件数,不减少复杂度~~ | +| ~~handler/session.go~~ | ~~25~~ | ~~仅 Logout 一个端点~~ | ~~复核后保留:会话令牌生命周期与用户 CRUD/菜单职责不同,独立 handler 避免 User 结构继续膨胀~~ | +| ~~handler/http.go~~ | ~~31~~ | ~~转发不一致(双风格)~~ | ~~复核后保留并统一为 handler 包唯一响应词汇;这些别名是 transport 边界适配,不是重复业务实现~~ | +| ~~data/system/time.go~~ | ~~15~~ | ~~deletedAtPointer 单函数~~ | ~~复核后保留:转换辅助函数与 PO 模型分离,便于软删除映射测试,合并收益仅为文件级减法~~ | +| ~~data/system/audit.go~~ | ~~17~~ | ~~3 个 repo 构造器与实现跨文件分离~~ | ~~复核后保留:三个仓储分别对应三个 biz 接口,集中构造器符合 Wire ProviderSet,未形成额外抽象~~ | ### 5.3 可选合并(2 组) -- router 21 个微文件 → 并入 routes.go 约 420 行 -- modules 4 个 definition 包 → 可合并单文件 ~110 行 +- ~~router 21 个微文件 → 并入 routes.go 约 420 行~~:复核后保留,按资源拆分与 routecatalog 元数据一一对应,便于增删路由和审查权限。 +- ~~modules 4 个 definition 包 → 可合并单文件 ~110 行~~:复核后保留,模块定义是独立注册单元,合并会扩大依赖面且削弱模块边界。 ### 5.4 接口碎片化 -- 同一 DB seam 三套名字、同包双 seam +- ~~同一 DB seam 三套名字、同包双 seam~~:复核后保留。`data.Provider`、`system.Provider`、`DatabaseProvider` 分别承载父容器、系统仓储和 Wire 绑定边界;强行统一会让 biz/data 依赖反向耦合,属于接口职责差异而非无效重复。 ### 5.5 判定为合理的(防误报) -- modules/surface:编译级硬约束(循环导入);单文件职责完整包 24 个;biz 域微文件/dto 微文件/service 对称微文件 +- ~~modules/surface:编译级硬约束(循环导入);单文件职责完整包 24 个;biz 域微文件/dto 微文件/service 对称微文件~~:确认属于合理拆分,继续保留。 ## 审查后认为合理、不建议改动的部分(历轮评估保留决策汇总) diff --git a/internal/biz/system/media_upload.go b/internal/biz/system/media_upload.go index 415158c..2ba4526 100644 --- a/internal/biz/system/media_upload.go +++ b/internal/biz/system/media_upload.go @@ -97,7 +97,7 @@ func (uc *MediaUsecase) InitUpload(ctx context.Context, userID uint, name, hash completed, err := uc.FindCompletedSession(ctx, userID, strings.ToLower(hash)) if err == nil && completed.MediaID != 0 { if media, findErr := uc.FindMedia(ctx, completed.MediaID); findErr == nil { - copy := &MediaFile{Name: name, URL: media.URL, Tag: strings.TrimPrefix(filepath.Ext(name), "."), Key: media.Key, Size: media.Size, MD5: media.MD5, Mime: media.Mime, UserID: userID} + copy := &MediaFile{Name: name, CategoryID: media.CategoryID, URL: media.URL, Tag: strings.TrimPrefix(filepath.Ext(name), "."), Key: media.Key, Size: media.Size, MD5: media.MD5, Mime: media.Mime, UserID: userID} if strings.TrimSpace(copy.Mime) == "" { copy.Mime = stdmime.TypeByExtension(strings.ToLower(filepath.Ext(name))) if copy.Mime == "" { @@ -294,8 +294,10 @@ func (uc *MediaUsecase) CleanupStale(ctx context.Context, ttlHours int) error { // scheduler. Keep the endpoint-independent background behavior compatible. ids, _ := uc.StaleUploadSessionIDs(ctx, time.Now().Add(-time.Duration(ttlHours)*time.Hour)) for _, id := range ids { + if err := uc.files.DeletePrefix(ctx, uc.chunkPrefix(id)); err != nil { + continue + } _ = uc.DeleteUploadData(ctx, id) - _ = uc.files.DeletePrefix(ctx, uc.chunkPrefix(id)) } return nil } diff --git a/internal/biz/task/task.go b/internal/biz/task/task.go index bc78e64..2dd4f34 100644 --- a/internal/biz/task/task.go +++ b/internal/biz/task/task.go @@ -85,7 +85,7 @@ func NewTaskUsecaseWithRegistry(repo TaskRepo, methods TaskMethodRegistry) *Task return &TaskUsecase{TaskRepo: repo, methods: methods} } -func (uc *TaskUsecase) RegisteredMethods() []TaskMethod { return uc.methods.List() } +func (uc *TaskUsecase) RegisteredMethods() []platformtask.Method { return uc.methods.List() } func (uc *TaskUsecase) Validate(value *TimedTask) error { if value.Name == "" { @@ -165,7 +165,7 @@ func NewTaskApplicationUsecase(tasks *TaskUsecase, runtime TaskRuntime) *TaskApp return &TaskApplicationUsecase{tasks: tasks, runtime: runtime} } -func (uc *TaskApplicationUsecase) RegisteredMethods() []TaskMethod { +func (uc *TaskApplicationUsecase) RegisteredMethods() []platformtask.Method { return uc.tasks.RegisteredMethods() } diff --git a/internal/biz/task/task_registry.go b/internal/biz/task/task_registry.go index 6373be7..dad0cfa 100644 --- a/internal/biz/task/task_registry.go +++ b/internal/biz/task/task_registry.go @@ -2,10 +2,7 @@ package task import platformtask "kra/pkg/task" -type TaskMethodFunc = platformtask.MethodFunc -type TaskMethod = platformtask.Method - type TaskMethodRegistry interface { - Lookup(string) (TaskMethodFunc, bool) - List() []TaskMethod + Lookup(string) (platformtask.MethodFunc, bool) + List() []platformtask.Method } diff --git a/internal/config/secrets.go b/internal/config/secrets.go index 51e70e7..d895b8c 100644 --- a/internal/config/secrets.go +++ b/internal/config/secrets.go @@ -1,5 +1,7 @@ package config +import "strings" + // MaskedSecret is the placeholder written in place of a stored secret whenever a // configuration payload leaves the process. Every layer that masks or preserves // configuration secrets shares this sentinel so a masked read round-trips into a @@ -8,7 +10,9 @@ const MaskedSecret = "******" // IsMaskedSecret reports whether an inbound value carries no new secret, either // because it was omitted or because it is the mask handed out on read. -func IsMaskedSecret(value string) bool { return value == "" || value == MaskedSecret } +func IsMaskedSecret(value string) bool { + return strings.TrimSpace(value) == "" || strings.TrimSpace(value) == MaskedSecret +} // ObjectStores lists every object-store block of a storage configuration so // callers iterate the providers instead of repeating the list. diff --git a/internal/data/data_scope.go b/internal/data/data_scope.go index 24bb189..3ff08d5 100644 --- a/internal/data/data_scope.go +++ b/internal/data/data_scope.go @@ -4,6 +4,7 @@ import ( "database/sql" "errors" "kra/internal/biz/system" + datasystem "kra/internal/data/system" "log/slog" "reflect" "strings" @@ -13,6 +14,10 @@ import ( "gorm.io/gorm/schema" ) +// dataAccessLogPO is the infrastructure-side write model used by GORM +// callbacks. The system module owns the query repository for the same table. +type dataAccessLogPO = datasystem.DataAccessLogPO + // registerDataScopeCallbacks installs the global GORM data-scope engine. // System tables are deliberately excluded: their access is controlled by // Casbin, while ownership columns on business tables are row-level scope. diff --git a/internal/data/data_scope_record.go b/internal/data/data_scope_record.go deleted file mode 100644 index f04e85f..0000000 --- a/internal/data/data_scope_record.go +++ /dev/null @@ -1,9 +0,0 @@ -package data - -import ( - datasystem "kra/internal/data/system" -) - -// dataAccessLogPO is the infrastructure-side write model used by GORM -// callbacks. The system module owns the query repository for the same table. -type dataAccessLogPO = datasystem.DataAccessLogPO diff --git a/internal/data/system/media_upload.go b/internal/data/system/media_upload.go index 6c9965a..740ec38 100644 --- a/internal/data/system/media_upload.go +++ b/internal/data/system/media_upload.go @@ -90,11 +90,17 @@ func (r *mediaRepo) FailUploadSession(ctx context.Context, id uint) error { return r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ?", id).Update("status", "failed").Error } func (r *mediaRepo) CompleteUploadSession(ctx context.Context, id uint, key string, mediaID uint) error { - return r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ?", id).Updates(map[string]any{"status": "completed", "storage_key": key, "media_id": mediaID}).Error + result := r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ? AND status = ?", id, "merging").Updates(map[string]any{"status": "completed", "storage_key": key, "media_id": mediaID}) + if result.Error != nil { + return result.Error + } + if result.RowsAffected != 1 { + return system.ErrUploadSessionNotFound + } + return nil } func (r *mediaRepo) DeleteUploadSession(ctx context.Context, id uint) error { - // The compatible flow uses GORM's normal Delete here, retaining the soft-deleted session - // for audit/recovery rather than physically removing it. + // Upload sessions are staging records; cleanup physically removes them and their chunks. return r.data.DB().WithContext(ctx).Unscoped().Delete(&uploadSessionPO{}, id).Error } func (r *mediaRepo) UpsertChunk(ctx context.Context, uploadID uint, v *system.UploadChunk) error { diff --git a/internal/data/task/provider.go b/internal/data/task/provider.go index c643606..7265f59 100644 --- a/internal/data/task/provider.go +++ b/internal/data/task/provider.go @@ -1,13 +1,5 @@ package task -import ( - dataprovider "kra/internal/data/provider" - - "github.com/google/wire" -) - -// Provider is the minimal database seam required by the task tables. -// Task persistence must not depend on the full data runtime or system repos. -type Provider = dataprovider.Database +import "github.com/google/wire" var ProviderSet = wire.NewSet(NewTaskRepo) diff --git a/internal/data/task/task.go b/internal/data/task/task.go index 5e112f5..dc79855 100644 --- a/internal/data/task/task.go +++ b/internal/data/task/task.go @@ -3,6 +3,7 @@ package task import ( "context" taskbiz "kra/internal/biz/task" + dataprovider "kra/internal/data/provider" "kra/pkg/database/gormkit" "kra/pkg/database/pagination" "time" @@ -44,9 +45,9 @@ type taskLogPO struct { func (taskLogPO) TableName() string { return "sys_timed_task_logs" } -type taskRepo struct{ data Provider } +type taskRepo struct{ data dataprovider.Database } -func NewTaskRepo(data Provider) taskbiz.TaskRepo { return &taskRepo{data: data} } +func NewTaskRepo(data dataprovider.Database) taskbiz.TaskRepo { return &taskRepo{data: data} } func taskToPO(v *taskbiz.TimedTask) taskPO { return taskPO{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: gormkit.JSON(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: gormkit.JSON(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} } diff --git a/internal/routecatalog/catalog.go b/internal/routecatalog/catalog.go index 4f0e358..c349532 100644 --- a/internal/routecatalog/catalog.go +++ b/internal/routecatalog/catalog.go @@ -56,8 +56,8 @@ var routes = map[string]routeValue{ "DELETE /sysExportTemplate/deleteSysExportTemplateByIds": {group: "导出模板", description: "批量删除导出模板", audit: true}, "DELETE /sysLoginLog/deleteLoginLog": {group: "登录日志", description: "删除登录日志", audit: true}, "DELETE /sysLoginLog/deleteLoginLogByIds": {group: "登录日志", description: "批量删除登录日志", audit: true}, - "DELETE /sysOperationRecord/deleteSysOperationRecord": {group: "操作记录", description: "删除操作记录"}, - "DELETE /sysOperationRecord/deleteSysOperationRecordByIds": {group: "操作记录", description: "批量删除操作历史"}, + "DELETE /sysOperationRecord/deleteSysOperationRecord": {group: "操作记录", description: "删除操作记录", audit: true}, + "DELETE /sysOperationRecord/deleteSysOperationRecordByIds": {group: "操作记录", description: "批量删除操作历史", audit: true}, "DELETE /sysParams/deleteSysParams": {group: "参数管理", description: "删除参数", audit: true}, "DELETE /sysParams/deleteSysParamsByIds": {group: "参数管理", description: "批量删除参数", audit: true}, "DELETE /sysVersion/deleteSysVersion": {group: "版本控制", description: "删除版本", audit: true}, @@ -135,7 +135,7 @@ var routes = map[string]routeValue{ "POST /api/setApiRoles": {group: "api", description: "全量覆盖API关联角色列表", audit: true}, "POST /api/updateApi": {group: "api", description: "更新Api", audit: true}, "POST /attachmentCategory/addCategory": {group: "媒体库分类", description: "添加/编辑分类"}, - "POST /attachmentCategory/deleteCategory": {group: "媒体库分类", description: "删除分类"}, + "POST /attachmentCategory/deleteCategory": {group: "媒体库分类", description: "删除分类", audit: true}, "POST /authority/copyAuthority": {group: "角色", description: "拷贝角色", audit: true}, "POST /authority/createAuthority": {group: "角色", description: "创建角色", audit: true}, "POST /authority/deleteAuthority": {group: "角色", description: "删除角色", audit: true}, diff --git a/internal/service/dto/authentication.go b/internal/service/dto/authentication.go deleted file mode 100644 index 3b46f89..0000000 --- a/internal/service/dto/authentication.go +++ /dev/null @@ -1,8 +0,0 @@ -package dto - -type LoginResponse struct { - User *UserResponse `json:"user"` - Token string `json:"token"` - ExpiresAt int64 `json:"expiresAt"` - NeedChangePassword bool `json:"needChangePassword"` -} diff --git a/internal/service/dto/system.go b/internal/service/dto/system.go index f1955f4..c164429 100644 --- a/internal/service/dto/system.go +++ b/internal/service/dto/system.go @@ -9,6 +9,13 @@ type LoginRequest struct { CaptchaID string `json:"captchaId"` } +type LoginResponse struct { + User *UserResponse `json:"user"` + Token string `json:"token"` + ExpiresAt int64 `json:"expiresAt"` + NeedChangePassword bool `json:"needChangePassword"` +} + type Route struct { Path string Method string diff --git a/internal/worker/task_executor.go b/internal/worker/task_executor.go index 97bb8ea..104b7bd 100644 --- a/internal/worker/task_executor.go +++ b/internal/worker/task_executor.go @@ -18,6 +18,7 @@ import ( "unicode/utf8" taskbiz "kra/internal/biz/task" + platformtask "kra/pkg/task" ) type TaskExecutor struct { @@ -199,7 +200,7 @@ func (e *TaskExecutor) runMethod(ctx context.Context, task *taskbiz.TimedTask) e if ctx == nil { ctx = context.Background() } - var method taskbiz.TaskMethodFunc + var method platformtask.MethodFunc var ok bool if e.methods != nil { method, ok = e.methods.Lookup(task.MethodName)