diff --git a/cmd/kratos-admin/main.go b/cmd/kratos-admin/main.go index d9eb32a..b7114c6 100644 --- a/cmd/kratos-admin/main.go +++ b/cmd/kratos-admin/main.go @@ -49,6 +49,20 @@ func newApp(logger *slog.Logger, hs *kratoshttp.Server, scheduler *worker.TaskSc ) } +func zapSettings(admin *conf.AdminBackend) (string, logging.Options) { + options := logging.Options{Level: "info", Format: "json", EncodeLevel: "LowercaseLevelEncoder", LogInConsole: true, ShowLine: true, RetentionDay: 7} + root := "logs" + if admin == nil || admin.Zap == nil { + return root, options + } + zapConfig := admin.Zap + options = logging.Options{Level: zapConfig.Level, Format: zapConfig.Format, EncodeLevel: zapConfig.EncodeLevel, Prefix: zapConfig.Prefix, StacktraceKey: zapConfig.StacktraceKey, LogInConsole: zapConfig.LogInConsole, ShowLine: zapConfig.ShowLine, RetentionDay: int(zapConfig.RetentionDay), FileOnlyModules: zapConfig.FileOnlyModules} + if zapConfig.Director != "" { + root = zapConfig.Director + } + return root, options +} + func main() { flag.Parse() c := config.New( @@ -66,21 +80,13 @@ func main() { if err := c.Scan(&bc); err != nil { panic(err) } - logOptions := logging.Options{Level: "info", Format: "json", EncodeLevel: "LowercaseLevelEncoder", LogInConsole: true, ShowLine: true, RetentionDay: 7} - logRoot := "logs" - if bc.Admin != nil && bc.Admin.Zap != nil { - zapConfig := bc.Admin.Zap - logOptions = logging.Options{Level: zapConfig.Level, Format: zapConfig.Format, EncodeLevel: zapConfig.EncodeLevel, Prefix: zapConfig.Prefix, StacktraceKey: zapConfig.StacktraceKey, LogInConsole: zapConfig.LogInConsole, ShowLine: zapConfig.ShowLine, RetentionDay: int(zapConfig.RetentionDay), FileOnlyModules: zapConfig.FileOnlyModules} - if zapConfig.Director != "" { - logRoot = zapConfig.Director - } - } + logRoot, logOptions := zapSettings(bc.Admin) loggerAttrs := []any{slog.String("service.id", id), slog.String("service.name", Name), slog.String("service.version", Version)} if bc.Admin != nil && bc.Admin.App != nil { loggerAttrs = append(loggerAttrs, slog.String("node", bc.Admin.App.Node), slog.String("app_id", bc.Admin.App.AppId), slog.String("env", bc.Admin.App.Env)) } - logger, closeLogger := logging.NewZapLogger(logRoot, "application.log", logOptions, loggerAttrs...) - defer closeLogger() + logger, loggerControl := logging.NewReloadableZapLogger(logRoot, "application.log", logOptions, loggerAttrs...) + defer loggerControl.Close() log.SetDefault(logger) if bc.Admin != nil { bc.Admin.ConfigPath = flagconf @@ -89,7 +95,14 @@ func main() { } } - app, cleanup, err := wireApp(bc.Server, bc.Data, bc.Admin, logger) + runtime := conf.NewRuntime(bc.Data, bc.Admin) + unsubscribeLogger := runtime.Subscribe(func(_ *conf.Data, admin *conf.AdminBackend) { + root, options := zapSettings(admin) + loggerControl.Reload(root, options) + }) + defer unsubscribeLogger() + + app, cleanup, err := wireApp(bc.Server, runtime, logger) if err != nil { panic(err) } diff --git a/cmd/kratos-admin/wire.go b/cmd/kratos-admin/wire.go index d5d7e47..37b9903 100644 --- a/cmd/kratos-admin/wire.go +++ b/cmd/kratos-admin/wire.go @@ -19,6 +19,6 @@ import ( ) // wireApp init kratos application. -func wireApp(*conf.Server, *conf.Data, *conf.AdminBackend, *slog.Logger) (*kratos.App, func(), error) { - panic(wire.Build(conf.NewRuntime, server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp)) +func wireApp(*conf.Server, *conf.Runtime, *slog.Logger) (*kratos.App, func(), error) { + panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp)) } diff --git a/cmd/kratos-admin/wire_gen.go b/cmd/kratos-admin/wire_gen.go index 1b86afd..d4a5aad 100644 --- a/cmd/kratos-admin/wire_gen.go +++ b/cmd/kratos-admin/wire_gen.go @@ -25,8 +25,7 @@ import ( // Injectors from wire.go: // wireApp init kratos application. -func wireApp(confServer *conf.Server, confData *conf.Data, adminBackend *conf.AdminBackend, logger *slog.Logger) (*kratos.App, func(), error) { - runtime := conf.NewRuntime(confData, adminBackend) +func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger) (*kratos.App, func(), error) { dataData, cleanup, err := data.NewData(runtime) if err != nil { return nil, nil, err @@ -72,7 +71,7 @@ func wireApp(confServer *conf.Server, confData *conf.Data, adminBackend *conf.Ad taskService := service.NewTaskService(taskUsecase, mediaUsecase, runtime) taskScheduler := worker.NewTaskScheduler(taskService, logger) task := handler.NewTask(taskService, taskScheduler) - mediaService := service.NewMediaService(mediaUsecase) + mediaService := service.NewMediaService(mediaUsecase, runtime) media := handler.NewMedia(mediaService) auditRepo := data.NewAuditRepo(dataData) auditUsecase := biz.NewAuditUsecase(auditRepo) @@ -94,7 +93,8 @@ func wireApp(confServer *conf.Server, confData *conf.Data, adminBackend *conf.Ad user := handler.NewUser(systemService) navigation := handler.NewNavigation(systemService) session := handler.NewSession(settingsService) - httpServer := server.NewGinServer(confServer, runtime, systemService, accessService, authority, menu, api, permission, organization, announcement, email, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, settingsService, auditService, logger) + engine := server.NewGinEngine(runtime, systemService, accessService, authority, menu, api, permission, organization, announcement, email, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, settingsService, auditService, logger) + httpServer := server.NewGinServer(confServer, engine) app := newApp(logger, httpServer, taskScheduler) return app, func() { cleanup() diff --git a/configs/config.yaml b/configs/config.yaml index b2e81f7..cc5c231 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -47,6 +47,7 @@ admin: path_prefix: uploads/file media: session_ttl: 24 + max_file_size: 0 system: use_redis: false use_multipoint: false diff --git a/internal/biz/announcement.go b/internal/biz/announcement.go index 6b6f540..3b5dbe0 100644 --- a/internal/biz/announcement.go +++ b/internal/biz/announcement.go @@ -3,7 +3,6 @@ package biz import ( "context" "encoding/json" - "errors" "time" ) @@ -43,51 +42,23 @@ func NewAnnouncementUsecase(repo AnnouncementRepo) *AnnouncementUsecase { return &AnnouncementUsecase{repo: repo} } -func validateAnnouncement(item *Announcement) error { - if item == nil || item.Title == "" { - return errors.New("公告标题不能为空") - } - if len(item.Attachments) == 0 || !json.Valid(item.Attachments) { - item.Attachments = json.RawMessage("[]") - } - return nil -} - func (uc *AnnouncementUsecase) Create(ctx context.Context, item *Announcement) error { - if err := validateAnnouncement(item); err != nil { - return err - } return uc.repo.Create(ctx, item) } func (uc *AnnouncementUsecase) Update(ctx context.Context, item *Announcement) error { - if item == nil || item.ID == 0 { - return errors.New("公告ID不能为空") - } - if err := validateAnnouncement(item); err != nil { - return err - } return uc.repo.Update(ctx, item) } func (uc *AnnouncementUsecase) Delete(ctx context.Context, id uint) error { - if id == 0 { - return errors.New("公告ID不能为空") - } return uc.repo.Delete(ctx, id) } func (uc *AnnouncementUsecase) DeleteByIDs(ctx context.Context, ids []uint) error { - if len(ids) == 0 { - return errors.New("公告ID不能为空") - } return uc.repo.DeleteByIDs(ctx, ids) } func (uc *AnnouncementUsecase) Find(ctx context.Context, id uint) (*Announcement, error) { - if id == 0 { - return nil, errors.New("公告ID不能为空") - } return uc.repo.Find(ctx, id) } diff --git a/internal/biz/audit.go b/internal/biz/audit.go index 6b45473..2d032b5 100644 --- a/internal/biz/audit.go +++ b/internal/biz/audit.go @@ -2,9 +2,19 @@ package biz import ( "context" + "errors" "time" ) +var ( + ErrInvalidLogMonth = errors.New("日志月份格式不正确") + ErrInvalidLogDate = errors.New("日志日期格式不正确") + ErrInvalidLogPath = errors.New("日志文件路径不合法") + ErrLogFileNotFound = errors.New("日志文件不存在") + ErrLogFileUnreadable = errors.New("日志文件不可读取") + ErrLogRootUnavailable = errors.New("日志目录不可读取") +) + type OperationRecord struct { ID uint CreatedAt time.Time diff --git a/internal/biz/dictionary.go b/internal/biz/dictionary.go index a860d41..92ac4e9 100644 --- a/internal/biz/dictionary.go +++ b/internal/biz/dictionary.go @@ -71,16 +71,39 @@ func (uc *SettingsUsecase) ImportDictionary(ctx context.Context, dictionary *Dic return uc.SettingsRepo.ImportDictionary(ctx, dictionary, details) } -func (uc *SettingsUsecase) DictionaryDetailsByParent(ctx context.Context, dictionaryID, parentID uint) ([]*DictionaryDetail, error) { +func (uc *SettingsUsecase) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]*DictionaryDetail, error) { items, _, err := uc.ListDictionaryDetails(ctx, 0, 0, DictionaryDetailFilter{DictionaryID: dictionaryID}) if err != nil { return nil, err } - out := make([]*DictionaryDetail, 0) + direct := make([]*DictionaryDetail, 0) for _, item := range items { - if (parentID == 0 && item.ParentID == nil) || (item.ParentID != nil && *item.ParentID == parentID) { - out = append(out, item) + if (parentID == nil && item.ParentID == nil) || (parentID != nil && item.ParentID != nil && *item.ParentID == *parentID) { + direct = append(direct, item) } } - return out, nil + if !includeChildren { + return direct, nil + } + children := make(map[uint][]*DictionaryDetail) + for _, item := range items { + item.Children = []*DictionaryDetail{} + if item.ParentID != nil { + children[*item.ParentID] = append(children[*item.ParentID], item) + } + } + var attach func(*DictionaryDetail) + attach = func(item *DictionaryDetail) { + item.Children = children[item.ID] + if item.Children == nil { + item.Children = []*DictionaryDetail{} + } + for _, child := range item.Children { + attach(child) + } + } + for _, item := range direct { + attach(item) + } + return direct, nil } diff --git a/internal/biz/media_upload.go b/internal/biz/media_upload.go index 958e2f4..4c7862a 100644 --- a/internal/biz/media_upload.go +++ b/internal/biz/media_upload.go @@ -22,12 +22,12 @@ func (uc *MediaUsecase) InitUpload(ctx context.Context, userID uint, name, hash if size <= 0 || chunkSize <= 0 || total <= 0 { return nil, nil, nil, errors.New("上传参数不合法") } - if media, err := uc.FindMediaByHash(ctx, userID, hash); err == nil { - copy := *media - copy.ID = 0 - copy.Name = name - if err = uc.CreateMedia(ctx, ©); err == nil { - return nil, ©, nil, nil + if completed, err := uc.FindCompletedSession(ctx, userID, hash); err == nil && completed.MediaID != 0 { + if media, findErr := uc.FindMedia(ctx, completed.MediaID); findErr == nil { + copy := &MediaFile{Name: name, URL: media.URL, Tag: media.Tag, Key: media.Key} + if createErr := uc.CreateMedia(ctx, copy); createErr == nil { + return nil, copy, nil, nil + } } } session, err := uc.FindUploadingSession(ctx, userID, hash) @@ -53,8 +53,8 @@ func (uc *MediaUsecase) SaveChunk(ctx context.Context, userID, uploadID uint, in if err != nil || session.UserID != userID { return errors.New("上传会话不存在或无权操作") } - if session.Status != "uploading" || index < 0 || index >= session.ChunkTotal { - return errors.New("上传会话状态或分片序号不合法") + if session.Status != "uploading" { + return errors.New("上传会话状态不允许收片") } hash := md5.New() key := fmt.Sprintf(".chunks/%d/%08d", uploadID, index) @@ -63,9 +63,9 @@ func (uc *MediaUsecase) SaveChunk(ctx context.Context, userID, uploadID uint, in return err } actual := hex.EncodeToString(hash.Sum(nil)) - if !strings.EqualFold(actual, expected) { + if actual != expected { _ = uc.files.Delete(ctx, key) - return errors.New("分片校验失败") + return fmt.Errorf("分片 %d 校验失败", index) } return uc.UpsertChunk(ctx, uploadID, &UploadChunk{Index: index, Hash: actual, Size: stored.Size}) } @@ -74,15 +74,26 @@ func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uin if err != nil || session.UserID != userID { return nil, errors.New("上传会话不存在或无权操作") } + claimed, err := uc.ClaimUploadSession(ctx, uploadID) + if err != nil { + return nil, err + } + if !claimed { + return nil, errors.New("上传不在可合并状态(可能已在合并或已完成)") + } + fail := func(value error) (*MediaFile, error) { + _ = uc.FailUploadSession(ctx, uploadID) + return nil, value + } chunks, err := uc.ListChunks(ctx, uploadID) if err != nil || len(chunks) != session.ChunkTotal { - return nil, errors.New("分片不完整") + return fail(fmt.Errorf("分片不全: %d/%d", len(chunks), session.ChunkTotal)) } sort.Slice(chunks, func(i, j int) bool { return chunks[i].Index < chunks[j].Index }) names := make([]string, 0, len(chunks)) for index, chunk := range chunks { if chunk.Index != index { - return nil, errors.New("分片序号不连续") + return fail(errors.New("分片序号不连续")) } names = append(names, fmt.Sprintf(".chunks/%d/%08d", uploadID, index)) } @@ -90,16 +101,16 @@ func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uin key := time.Now().Format("20060102") + "/" + uuid.NewString() + ext stored, hash, err := uc.files.Compose(ctx, names, key) if err != nil { - return nil, err + return fail(err) } - if !strings.EqualFold(hash, session.FileHash) { + if hash != session.FileHash { _ = uc.files.Delete(ctx, key) - return nil, errors.New("整文件校验失败") + return fail(errors.New("整文件校验失败")) } media := &MediaFile{Name: session.FileName, URL: stored.URL, Tag: strings.TrimPrefix(ext, "."), Key: key, Size: stored.Size, Mime: mime, MD5: hash, UserID: userID} if err = uc.CreateMedia(ctx, media); err != nil { _ = uc.files.Delete(ctx, key) - return nil, err + return fail(err) } _ = uc.CompleteUploadSession(ctx, uploadID, key, media.ID) _ = uc.DeleteChunks(ctx, uploadID) diff --git a/internal/biz/upload_session.go b/internal/biz/upload_session.go index bae39aa..ed966cf 100644 --- a/internal/biz/upload_session.go +++ b/internal/biz/upload_session.go @@ -22,9 +22,12 @@ type UploadChunk struct { } type UploadRepo interface { + FindCompletedSession(context.Context, uint, string) (*UploadSession, error) FindUploadingSession(context.Context, uint, string) (*UploadSession, error) CreateUploadSession(context.Context, *UploadSession) error FindUploadSession(context.Context, uint) (*UploadSession, error) + ClaimUploadSession(context.Context, uint) (bool, error) + FailUploadSession(context.Context, uint) error CompleteUploadSession(context.Context, uint, string, uint) error DeleteUploadSession(context.Context, uint) error UpsertChunk(context.Context, uint, *UploadChunk) error diff --git a/internal/conf/conf.pb.go b/internal/conf/conf.pb.go index 259a486..38bec0e 100644 --- a/internal/conf/conf.pb.go +++ b/internal/conf/conf.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 -// protoc (unknown) +// protoc-gen-go v1.36.12 +// protoc v7.35.1 // source: conf/conf.proto package conf @@ -1167,6 +1167,7 @@ func (x *AdminBackend_Email) GetIsLoginAuth() bool { type AdminBackend_Media struct { state protoimpl.MessageState `protogen:"open.v1"` SessionTtl int32 `protobuf:"varint,1,opt,name=session_ttl,json=sessionTtl,proto3" json:"session_ttl,omitempty"` + MaxFileSize int64 `protobuf:"varint,2,opt,name=max_file_size,json=maxFileSize,proto3" json:"max_file_size,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1208,6 +1209,13 @@ func (x *AdminBackend_Media) GetSessionTtl() int32 { return 0 } +func (x *AdminBackend_Media) GetMaxFileSize() int64 { + if x != nil { + return x.MaxFileSize + } + return 0 +} + type AdminBackend_Disk struct { state protoimpl.MessageState `protogen:"open.v1"` MountPoint string `protobuf:"bytes,1,opt,name=mount_point,json=mountPoint,proto3" json:"mount_point,omitempty"` @@ -2046,7 +2054,7 @@ const file_conf_conf_proto_rawDesc = "" + "\x12connect_timeout_ms\x18\n" + " \x01(\x03R\x10connectTimeoutMs\x12\x15\n" + "\x06is_zap\x18\v \x01(\bR\x05isZap\x120\n" + - "\x05hosts\x18\f \x03(\v2\x1a.kratos.api.Data.MongoHostR\x05hosts\"\x90\x1b\n" + + "\x05hosts\x18\f \x03(\v2\x1a.kratos.api.Data.MongoHostR\x05hosts\"\xb4\x1b\n" + "\fAdminBackend\x12#\n" + "\rrouter_prefix\x18\x01 \x01(\tR\frouterPrefix\x12.\n" + "\x03jwt\x18\x02 \x01(\v2\x1c.kratos.api.AdminBackend.JWTR\x03jwt\x12:\n" + @@ -2089,10 +2097,11 @@ const file_conf_conf_proto_rawDesc = "" + "\bnickname\x18\x05 \x01(\tR\bnickname\x12\x12\n" + "\x04port\x18\x06 \x01(\x05R\x04port\x12\x15\n" + "\x06is_ssl\x18\a \x01(\bR\x05isSsl\x12\"\n" + - "\ris_login_auth\x18\b \x01(\bR\visLoginAuth\x1a(\n" + + "\ris_login_auth\x18\b \x01(\bR\visLoginAuth\x1aL\n" + "\x05Media\x12\x1f\n" + "\vsession_ttl\x18\x01 \x01(\x05R\n" + - "sessionTtl\x1a'\n" + + "sessionTtl\x12\"\n" + + "\rmax_file_size\x18\x02 \x01(\x03R\vmaxFileSize\x1a'\n" + "\x04Disk\x12\x1f\n" + "\vmount_point\x18\x01 \x01(\tR\n" + "mountPoint\x1a\xc3\x01\n" + diff --git a/internal/conf/conf.proto b/internal/conf/conf.proto index 0ea2a98..c93be73 100644 --- a/internal/conf/conf.proto +++ b/internal/conf/conf.proto @@ -126,6 +126,7 @@ message AdminBackend { message Media { int32 session_ttl = 1; + int64 max_file_size = 2; } message Disk { diff --git a/internal/conf/runtime.go b/internal/conf/runtime.go index 5c26a76..e8bf6f6 100644 --- a/internal/conf/runtime.go +++ b/internal/conf/runtime.go @@ -1,6 +1,7 @@ package conf import ( + "sync" "sync/atomic" "google.golang.org/protobuf/proto" @@ -9,7 +10,10 @@ import ( // Runtime owns the active immutable configuration snapshot. Callers always // receive clones so a config file reload cannot race with request handling. type Runtime struct { - snapshot atomic.Pointer[runtimeSnapshot] + snapshot atomic.Pointer[runtimeSnapshot] + mu sync.RWMutex + nextID uint64 + listeners map[uint64]func(*Data, *AdminBackend) } type runtimeSnapshot struct { @@ -18,7 +22,7 @@ type runtimeSnapshot struct { } func NewRuntime(data *Data, admin *AdminBackend) *Runtime { - runtime := &Runtime{} + runtime := &Runtime{listeners: make(map[uint64]func(*Data, *AdminBackend))} runtime.Replace(data, admin) return runtime } @@ -38,7 +42,37 @@ func cloneAdmin(value *AdminBackend) *AdminBackend { } func (r *Runtime) Replace(data *Data, admin *AdminBackend) { - r.snapshot.Store(&runtimeSnapshot{data: cloneData(data), admin: cloneAdmin(admin)}) + data = cloneData(data) + admin = cloneAdmin(admin) + r.snapshot.Store(&runtimeSnapshot{data: data, admin: admin}) + r.mu.RLock() + listeners := make([]func(*Data, *AdminBackend), 0, len(r.listeners)) + for _, listener := range r.listeners { + listeners = append(listeners, listener) + } + r.mu.RUnlock() + for _, listener := range listeners { + listener(cloneData(data), cloneAdmin(admin)) + } +} + +// Subscribe registers a callback invoked after every successful runtime +// configuration replacement. It is used by long-lived clients such as the +// logger and other long-lived clients that must follow runtime config changes. +func (r *Runtime) Subscribe(listener func(*Data, *AdminBackend)) func() { + if listener == nil { + return func() {} + } + r.mu.Lock() + r.nextID++ + id := r.nextID + r.listeners[id] = listener + r.mu.Unlock() + return func() { + r.mu.Lock() + delete(r.listeners, id) + r.mu.Unlock() + } } func (r *Runtime) Data() *Data { diff --git a/internal/data/announcement.go b/internal/data/announcement.go index 452d5b4..12f506e 100644 --- a/internal/data/announcement.go +++ b/internal/data/announcement.go @@ -29,19 +29,11 @@ type announcementRepo struct{ data *Data } func NewAnnouncementRepo(data *Data) biz.AnnouncementRepo { return &announcementRepo{data: data} } func newAnnouncement(item *biz.Announcement) announcementPO { - attachments := []byte(item.Attachments) - if len(attachments) == 0 || !json.Valid(attachments) { - attachments = []byte("[]") - } - return announcementPO{ID: item.ID, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: jsonPO(attachments)} + return announcementPO{ID: item.ID, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: jsonPO(item.Attachments)} } func announcementToBiz(item announcementPO) *biz.Announcement { - attachments := json.RawMessage(item.Attachments) - if len(attachments) == 0 || !json.Valid(attachments) { - attachments = json.RawMessage("[]") - } - return &biz.Announcement{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: attachments} + return &biz.Announcement{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: json.RawMessage(item.Attachments)} } func (r *announcementRepo) Create(ctx context.Context, item *biz.Announcement) error { @@ -63,16 +55,7 @@ func (r *announcementRepo) DeleteByIDs(ctx context.Context, ids []uint) error { func (r *announcementRepo) Update(ctx context.Context, item *biz.Announcement) error { po := newAnnouncement(item) - result := r.data.gormDB.WithContext(ctx).Model(&announcementPO{}).Where("id = ?", item.ID).Updates(map[string]any{ - "title": po.Title, "content": po.Content, "user_id": po.UserID, "attachments": po.Attachments, - }) - if result.Error != nil { - return result.Error - } - if result.RowsAffected == 0 { - return gorm.ErrRecordNotFound - } - return nil + return r.data.gormDB.WithContext(ctx).Model(&announcementPO{}).Where("id = ?", item.ID).Updates(&po).Error } func (r *announcementRepo) Find(ctx context.Context, id uint) (*biz.Announcement, error) { diff --git a/internal/data/api.go b/internal/data/api.go index b76ba2e..20b644a 100644 --- a/internal/data/api.go +++ b/internal/data/api.go @@ -46,6 +46,13 @@ func apiFromPO(po apiPO) *biz.API { } func (r *accessRepo) CreateAPI(ctx context.Context, v *biz.API) error { po := apiPO{Path: v.Path, Description: v.Description, APIGroup: v.APIGroup, Method: strings.ToUpper(v.Method)} + var count int64 + if err := r.data.gormDB.WithContext(ctx).Model(&apiPO{}).Where("path = ? AND method = ?", po.Path, po.Method).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("存在相同api") + } if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil { return err } @@ -53,7 +60,22 @@ func (r *accessRepo) CreateAPI(ctx context.Context, v *biz.API) error { return nil } func (r *accessRepo) UpdateAPI(ctx context.Context, v *biz.API) error { - return r.data.gormDB.WithContext(ctx).Model(&apiPO{}).Where("id = ?", v.ID).Updates(map[string]any{"path": v.Path, "description": v.Description, "api_group": v.APIGroup, "method": strings.ToUpper(v.Method)}).Error + db := r.data.gormDB.WithContext(ctx) + var old apiPO + if err := db.First(&old, v.ID).Error; err != nil { + return err + } + method := strings.ToUpper(v.Method) + if old.Path != v.Path || old.Method != method { + var count int64 + if err := db.Model(&apiPO{}).Where("id <> ? AND path = ? AND method = ?", v.ID, v.Path, method).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("存在相同api路径") + } + } + return db.Model(&old).Updates(map[string]any{"path": v.Path, "description": v.Description, "api_group": v.APIGroup, "method": method}).Error } func (r *accessRepo) DeleteAPIs(ctx context.Context, ids []uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { diff --git a/internal/data/log_file.go b/internal/data/log_file.go index 1a6cfb4..0d28839 100644 --- a/internal/data/log_file.go +++ b/internal/data/log_file.go @@ -14,28 +14,32 @@ import ( "kra/internal/biz" ) -func logRoot() (string, error) { - root, err := filepath.Abs("logs") +func (r *auditRepo) logRoot() (string, error) { + admin := r.data.runtime.Admin() + if admin == nil || admin.Zap == nil || strings.TrimSpace(admin.Zap.Director) == "" { + return "", biz.ErrLogRootUnavailable + } + root, err := filepath.Abs(admin.Zap.Director) if err != nil { - return "", err + return "", errors.Join(biz.ErrLogRootUnavailable, err) } info, err := os.Stat(root) if errors.Is(err, fs.ErrNotExist) { return root, nil } if err != nil { - return "", err + return "", errors.Join(biz.ErrLogRootUnavailable, err) } if !info.IsDir() { - return "", errors.New("日志路径不是目录") + return "", biz.ErrLogRootUnavailable } return root, nil } func (r *auditRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) { if parsed, err := time.Parse("2006-01", month); err != nil || parsed.Format("2006-01") != month { - return nil, errors.New("日志月份格式不正确") + return nil, biz.ErrInvalidLogMonth } - root, err := logRoot() + root, err := r.logRoot() if err != nil { return nil, err } @@ -73,9 +77,9 @@ func (r *auditRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, } func (r *auditRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, error) { if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date { - return nil, errors.New("日志日期格式不正确") + return nil, biz.ErrInvalidLogDate } - root, err := logRoot() + root, err := r.logRoot() if err != nil { return nil, err } @@ -112,24 +116,27 @@ func (r *auditRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, e return out, err } func (r *auditRepo) LogContent(ctx context.Context, date, path string, cursor *int64) (*biz.LogContent, error) { - if _, err := time.Parse("2006-01-02", date); err != nil { - return nil, errors.New("日志日期格式不正确") + if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date { + return nil, biz.ErrInvalidLogDate } - root, err := logRoot() + root, err := r.logRoot() if err != nil { return nil, err } dateRoot := filepath.Join(root, date) target := filepath.Clean(filepath.Join(dateRoot, filepath.FromSlash(path))) if target == dateRoot || !strings.HasPrefix(target, dateRoot+string(os.PathSeparator)) || !strings.EqualFold(filepath.Ext(target), ".log") { - return nil, errors.New("日志文件路径不合法") + return nil, biz.ErrInvalidLogPath } info, err := os.Lstat(target) + if errors.Is(err, fs.ErrNotExist) { + return nil, biz.ErrLogFileNotFound + } if err != nil { - return nil, err + return nil, errors.Join(biz.ErrLogFileUnreadable, err) } if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() { - return nil, errors.New("日志文件路径不合法") + return nil, biz.ErrInvalidLogPath } end := info.Size() if cursor != nil && *cursor >= 0 && *cursor < end { @@ -142,13 +149,13 @@ func (r *auditRepo) LogContent(ctx context.Context, date, path string, cursor *i } file, err := os.Open(target) if err != nil { - return nil, err + return nil, errors.Join(biz.ErrLogFileUnreadable, err) } defer file.Close() data := make([]byte, end-start) n, err := file.ReadAt(data, start) if err != nil && n == 0 { - return nil, err + return nil, errors.Join(biz.ErrLogFileUnreadable, err) } data = data[:n] lines := bytes.Split(data, []byte("\n")) @@ -162,5 +169,9 @@ func (r *auditRepo) LogContent(ctx context.Context, date, path string, cursor *i data = data[offset:] limited = true } - return &biz.LogContent{Date: date, Path: path, Content: string(data), LineCount: bytes.Count(data, []byte("\n")), NextCursor: start, HasMore: start > 0, LimitedByBytes: limited, Size: info.Size(), ModifiedAt: info.ModTime()}, nil + lineCount := bytes.Count(data, []byte("\n")) + if len(data) > 0 && data[len(data)-1] != '\n' { + lineCount++ + } + return &biz.LogContent{Date: date, Path: path, Content: string(data), LineCount: lineCount, NextCursor: start, HasMore: start > 0, LimitedByBytes: limited, Size: info.Size(), ModifiedAt: info.ModTime()}, nil } diff --git a/internal/data/media_upload.go b/internal/data/media_upload.go index 6717e85..3bac82f 100644 --- a/internal/data/media_upload.go +++ b/internal/data/media_upload.go @@ -44,6 +44,13 @@ func (uploadChunkPO) TableName() string { return "media_upload_chunks" } func uploadFromPO(v uploadSessionPO) *biz.UploadSession { return &biz.UploadSession{ID: v.ID, UserID: v.UserID, FileName: v.FileName, FileHash: v.FileHash, FileSize: v.FileSize, ChunkSize: v.ChunkSize, ChunkTotal: v.ChunkTotal, Status: v.Status, StorageKey: v.StorageKey, MediaID: v.MediaID} } +func (r *mediaRepo) FindCompletedSession(ctx context.Context, userID uint, hash string) (*biz.UploadSession, error) { + var po uploadSessionPO + if err := r.data.gormDB.WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "completed").First(&po).Error; err != nil { + return nil, err + } + return uploadFromPO(po), nil +} func (r *mediaRepo) FindUploadingSession(ctx context.Context, userID uint, hash string) (*biz.UploadSession, error) { var po uploadSessionPO if err := r.data.gormDB.WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "uploading").First(&po).Error; err != nil { @@ -66,6 +73,13 @@ func (r *mediaRepo) FindUploadSession(ctx context.Context, id uint) (*biz.Upload } return uploadFromPO(po), nil } +func (r *mediaRepo) ClaimUploadSession(ctx context.Context, id uint) (bool, error) { + result := r.data.gormDB.WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ? AND status = ?", id, "uploading").Update("status", "merging") + return result.RowsAffected == 1, result.Error +} +func (r *mediaRepo) FailUploadSession(ctx context.Context, id uint) error { + return r.data.gormDB.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.gormDB.WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ?", id).Updates(map[string]any{"status": "completed", "storage_key": key, "media_id": mediaID}).Error } @@ -74,7 +88,7 @@ func (r *mediaRepo) DeleteUploadSession(ctx context.Context, id uint) error { } func (r *mediaRepo) UpsertChunk(ctx context.Context, uploadID uint, v *biz.UploadChunk) error { po := uploadChunkPO{UploadID: uploadID, ChunkIndex: v.Index, ChunkHash: v.Hash, Size: v.Size} - return r.data.gormDB.WithContext(ctx).Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "upload_id"}, {Name: "chunk_index"}}, DoUpdates: clause.AssignmentColumns([]string{"chunk_hash", "size", "updated_at"})}).Create(&po).Error + return r.data.gormDB.WithContext(ctx).Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "upload_id"}, {Name: "chunk_index"}}, DoNothing: true}).Create(&po).Error } func (r *mediaRepo) ListChunks(ctx context.Context, uploadID uint) ([]*biz.UploadChunk, error) { var pos []uploadChunkPO diff --git a/internal/server/gin.go b/internal/server/gin.go index 58a024e..e202d43 100644 --- a/internal/server/gin.go +++ b/internal/server/gin.go @@ -20,7 +20,7 @@ import ( kratoshttp "github.com/go-kratos/kratos/v3/transport/http" ) -func NewGinServer(c *conf.Server, runtime *conf.Runtime, system *service.SystemService, access *service.AccessService, authority *handler.Authority, menu *handler.Menu, api *handler.API, permission *handler.Permission, organization *handler.Organization, announcement *handler.Announcement, email *handler.Email, task *handler.Task, media *handler.Media, auditHandler *handler.Audit, export *handler.Export, version *handler.Version, dictionary *handler.Dictionary, parameter *handler.Parameter, apiToken *handler.APIToken, systemConfig *handler.SystemConfig, publicHandler *handler.Public, user *handler.User, navigation *handler.Navigation, session *handler.Session, settings *service.SettingsService, audit *service.AuditService, logger *slog.Logger) *kratoshttp.Server { +func NewGinEngine(runtime *conf.Runtime, system *service.SystemService, access *service.AccessService, authority *handler.Authority, menu *handler.Menu, api *handler.API, permission *handler.Permission, organization *handler.Organization, announcement *handler.Announcement, email *handler.Email, task *handler.Task, media *handler.Media, auditHandler *handler.Audit, export *handler.Export, version *handler.Version, dictionary *handler.Dictionary, parameter *handler.Parameter, apiToken *handler.APIToken, systemConfig *handler.SystemConfig, publicHandler *handler.Public, user *handler.User, navigation *handler.Navigation, session *handler.Session, settings *service.SettingsService, audit *service.AuditService, logger *slog.Logger) *gin.Engine { gin.SetMode(gin.ReleaseMode) engine := gin.New() engine.Use(servermiddleware.RequestMeta(), servermiddleware.Recovery(audit, logger), servermiddleware.AccessLog(runtime, logger), servermiddleware.ErrorAudit(audit), servermiddleware.SecurityRateLimit(system, settings), servermiddleware.OperationAudit(runtime, audit)) @@ -62,7 +62,10 @@ func NewGinServer(c *conf.Server, runtime *conf.Runtime, system *service.SystemS } httpx.Fail(c, "请求的接口不存在") }) + return engine +} +func NewGinServer(c *conf.Server, engine *gin.Engine) *kratoshttp.Server { network, address := "tcp", ":8000" if c != nil && c.Http != nil { if c.Http.Network != "" { diff --git a/internal/server/handler/announcement.go b/internal/server/handler/announcement.go index 50f9271..57595b0 100644 --- a/internal/server/handler/announcement.go +++ b/internal/server/handler/announcement.go @@ -23,12 +23,12 @@ func announcementInput(req dto.AnnouncementRequest) service.AnnouncementInput { func (h *Announcement) Create(c *gin.Context) { var req dto.AnnouncementRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.Create(c.Request.Context(), announcementInput(req)); err != nil { - httpx.Fail(c, "创建失败:"+err.Error()) + httpx.Fail(c, "创建失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") @@ -37,7 +37,7 @@ func (h *Announcement) Create(c *gin.Context) { func (h *Announcement) Delete(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) if err := h.service.Delete(c.Request.Context(), uint(id)); err != nil { - httpx.Fail(c, "删除失败:"+err.Error()) + httpx.Fail(c, "删除失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") @@ -58,7 +58,7 @@ func (h *Announcement) DeleteByIDs(c *gin.Context) { ids = append(ids, uint(id)) } if err := h.service.DeleteByIDs(c.Request.Context(), ids); err != nil { - httpx.Fail(c, "批量删除失败:"+err.Error()) + httpx.Fail(c, "批量删除失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") @@ -66,12 +66,12 @@ func (h *Announcement) DeleteByIDs(c *gin.Context) { func (h *Announcement) Update(c *gin.Context) { var req dto.AnnouncementRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.Update(c.Request.Context(), announcementInput(req)); err != nil { - httpx.Fail(c, "更新失败:"+err.Error()) + httpx.Fail(c, "更新失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") @@ -81,7 +81,7 @@ func (h *Announcement) Find(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) item, err := h.service.Find(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "查询失败:"+err.Error()) + httpx.Fail(c, "查询失败") return } httpx.OKWithData(c, item) @@ -98,7 +98,7 @@ func (h *Announcement) List(c *gin.Context) { } items, total, err := h.service.List(c.Request.Context(), page, pageSize, start, end) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + httpx.Fail(c, "获取失败") return } httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功") @@ -107,7 +107,7 @@ func (h *Announcement) List(c *gin.Context) { func (h *Announcement) DataSource(c *gin.Context) { users, err := h.service.UserOptions(c.Request.Context()) if err != nil { - httpx.Fail(c, "查询失败:"+err.Error()) + httpx.Fail(c, "查询失败") return } httpx.OKWithData(c, gin.H{"userID": users}) diff --git a/internal/server/handler/api.go b/internal/server/handler/api.go index 5629c4f..28ecc46 100644 --- a/internal/server/handler/api.go +++ b/internal/server/handler/api.go @@ -16,8 +16,8 @@ func NewAPI(service *service.AccessService) *API { return &API{service: service} func (h *API) List(c *gin.Context) { var req dto.APIListRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } items, total, err := h.service.ListAPI(c.Request.Context(), &req) @@ -37,8 +37,8 @@ func (h *API) All(c *gin.Context) { } func (h *API) Create(c *gin.Context) { var req dto.APIRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } created, err := h.service.CreateAPIRequest(c.Request.Context(), &req) @@ -50,44 +50,44 @@ func (h *API) Create(c *gin.Context) { } func (h *API) Update(c *gin.Context) { var req dto.APIRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.UpdateAPIRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败") + httpx.Fail(c, "修改失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功") } func (h *API) Delete(c *gin.Context) { var req dto.DeleteAPIRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.DeleteAPIs(c.Request.Context(), []uint{req.ID}); err != nil { httpx.Fail(c, "删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *API) DeleteByIDs(c *gin.Context) { var req dto.DeleteAPIsRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.DeleteAPIs(c.Request.Context(), req.IDs); err != nil { httpx.Fail(c, "删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *API) Find(c *gin.Context) { var req dto.GetAPIRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } item, err := h.service.FindAPIResponse(c.Request.Context(), req.ID) @@ -116,7 +116,7 @@ func (h *API) Groups(c *gin.Context) { groupAPIMap[parts[1]] = item.APIGroup } } - httpx.Write(c, httpx.CodeSuccess, gin.H{"groups": groups, "apiGroupMap": groupAPIMap}, "获取成功") + httpx.OKWithData(c, gin.H{"groups": groups, "apiGroupMap": groupAPIMap}) } func (h *API) Roles(c *gin.Context) { path, method := c.Query("path"), c.Query("method") @@ -126,7 +126,7 @@ func (h *API) Roles(c *gin.Context) { } ids, err := h.service.APIRoleIDs(c.Request.Context(), path, method) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败"+err.Error()) return } if ids == nil { @@ -136,8 +136,8 @@ func (h *API) Roles(c *gin.Context) { } func (h *API) SetRoles(c *gin.Context) { var req dto.SetAPIRolesRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if req.Path == "" || req.Method == "" { @@ -145,10 +145,10 @@ func (h *API) SetRoles(c *gin.Context) { return } if err := h.service.SetAPIRoles(c.Request.Context(), req.Path, req.Method, req.AuthorityIDs); err != nil { - httpx.Fail(c, "设置失败") + httpx.Fail(c, "设置失败"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } func (h *API) Sync(engine *gin.Engine) gin.HandlerFunc { return func(c *gin.Context) { @@ -159,28 +159,32 @@ func (h *API) Sync(engine *gin.Engine) gin.HandlerFunc { } result, err := h.service.SyncAPIResponses(c.Request.Context(), values) if err != nil { - httpx.Fail(c, "同步检查失败") + httpx.Fail(c, "同步失败") return } - httpx.Write(c, httpx.CodeSuccess, result, "获取成功") + httpx.OKWithData(c, result) } } func (h *API) Ignore(c *gin.Context) { var req dto.IgnoreAPIRequest - if c.ShouldBindJSON(&req) != nil || req.Path == "" || req.Method == "" { + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) + return + } + if req.Path == "" || req.Method == "" { httpx.Fail(c, "参数错误") return } if err := h.service.SetAPIIgnored(c.Request.Context(), req.Path, req.Method, req.Flag); err != nil { - httpx.Fail(c, "忽略设置失败") + httpx.Fail(c, "忽略失败") return } httpx.OK(c) } func (h *API) ApplySync(c *gin.Context) { var req dto.ApplyAPISyncRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.ApplyAPISyncRequest(c.Request.Context(), &req); err != nil { @@ -189,24 +193,26 @@ func (h *API) ApplySync(c *gin.Context) { } httpx.OK(c) } -func (h *API) FreshCasbin(c *gin.Context) { httpx.OK(c) } +func (h *API) FreshCasbin(c *gin.Context) { + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "刷新成功") +} func (h *API) SetPolicyPaths(c *gin.Context) { var req dto.SetPolicyPathsRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.SetPolicyPathsRequest(c.Request.Context(), &req); err != nil { httpx.Fail(c, "更新失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") } func (h *API) PolicyPaths(c *gin.Context) { var req dto.GetPolicyPathsRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } paths, err := h.service.PolicyPathResponses(c.Request.Context(), req.AuthorityID) diff --git a/internal/server/handler/api_token.go b/internal/server/handler/api_token.go index daac263..7bad7cb 100644 --- a/internal/server/handler/api_token.go +++ b/internal/server/handler/api_token.go @@ -18,13 +18,13 @@ func NewAPIToken(service *service.SettingsService) *APIToken { func (h *APIToken) Create(c *gin.Context) { var req dto.CreateAPITokenRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } token, err := h.service.CreateAPIToken(c.Request.Context(), req.UserID, req.AuthorityID, req.Days, req.Remark) if err != nil { - httpx.Fail(c, "签发失败:"+err.Error()) + httpx.Fail(c, "签发失败: "+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, gin.H{"token": token}, "签发成功") @@ -32,8 +32,8 @@ func (h *APIToken) Create(c *gin.Context) { func (h *APIToken) List(c *gin.Context) { var req dto.APITokenListRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } items, total, err := h.service.APITokens(c.Request.Context(), req.Page, req.PageSize, req.UserID, req.Status) @@ -46,13 +46,13 @@ func (h *APIToken) List(c *gin.Context) { func (h *APIToken) Delete(c *gin.Context) { var req dto.IDRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.DisableAPIToken(c.Request.Context(), req.ID); err != nil { httpx.Fail(c, "作废失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "作废成功") } diff --git a/internal/server/handler/audit.go b/internal/server/handler/audit.go index 6b70d48..ccd416a 100644 --- a/internal/server/handler/audit.go +++ b/internal/server/handler/audit.go @@ -1,9 +1,11 @@ package handler import ( + "errors" "strconv" "time" + "kra/internal/biz" "kra/internal/server/httpx" "kra/internal/service" "kra/internal/service/dto" @@ -56,27 +58,27 @@ func (h *Audit) Operation(c *gin.Context) { } func (h *Audit) DeleteOperation(c *gin.Context) { var req dto.IDRequest - if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.DeleteOperations(c.Request.Context(), []uint{req.ID}); err != nil { httpx.Fail(c, "删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) DeleteOperations(c *gin.Context) { var req dto.IDsRequest - if c.ShouldBindJSON(&req) != nil || len(req.IDs) == 0 { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.DeleteOperations(c.Request.Context(), req.IDs); err != nil { - httpx.Fail(c, "删除失败") + httpx.Fail(c, "批量删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") } func (h *Audit) Logins(c *gin.Context) { @@ -100,27 +102,27 @@ func (h *Audit) Login(c *gin.Context) { } func (h *Audit) DeleteLogin(c *gin.Context) { var req dto.IDRequest - if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.DeleteLogins(c.Request.Context(), []uint{req.ID}); err != nil { httpx.Fail(c, "删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) DeleteLogins(c *gin.Context) { var req dto.IDsRequest - if c.ShouldBindJSON(&req) != nil || len(req.IDs) == 0 { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.DeleteLogins(c.Request.Context(), req.IDs); err != nil { - httpx.Fail(c, "删除失败") + httpx.Fail(c, "批量删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") } func (h *Audit) DataAccess(c *gin.Context) { @@ -131,7 +133,7 @@ func (h *Audit) DataAccess(c *gin.Context) { } items, total, err := h.service.DataAccessRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") @@ -143,16 +145,16 @@ func (h *Audit) DeleteDataAccess(c *gin.Context) { return } if err := h.service.DeleteDataAccess(c.Request.Context(), req.IDs); err != nil { - httpx.Fail(c, "删除失败") + httpx.Fail(c, "删除失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) LogDates(c *gin.Context) { data, err := h.service.LogDates(c.Request.Context(), c.Query("month")) if err != nil { - httpx.Fail(c, err.Error()) + failLogViewer(c, err) return } httpx.Write(c, httpx.CodeSuccess, data, "获取成功") @@ -160,7 +162,7 @@ func (h *Audit) LogDates(c *gin.Context) { func (h *Audit) LogFiles(c *gin.Context) { data, err := h.service.LogFiles(c.Request.Context(), c.Query("date")) if err != nil { - httpx.Fail(c, err.Error()) + failLogViewer(c, err) return } httpx.Write(c, httpx.CodeSuccess, data, "获取成功") @@ -177,26 +179,45 @@ func (h *Audit) LogContent(c *gin.Context) { } data, err := h.service.LogContent(c.Request.Context(), c.Query("date"), c.Query("path"), cursor) if err != nil { - httpx.Fail(c, err.Error()) + failLogViewer(c, err) return } httpx.Write(c, httpx.CodeSuccess, data, "获取成功") } +func failLogViewer(c *gin.Context, err error) { + message := "读取日志失败" + switch { + case errors.Is(err, biz.ErrInvalidLogMonth): + message = "日志月份格式不正确" + case errors.Is(err, biz.ErrInvalidLogDate): + message = "日志日期格式不正确" + case errors.Is(err, biz.ErrInvalidLogPath): + message = "日志文件路径不合法" + case errors.Is(err, biz.ErrLogFileNotFound): + message = "日志文件不存在" + case errors.Is(err, biz.ErrLogFileUnreadable): + message = "日志文件不可读取" + case errors.Is(err, biz.ErrLogRootUnavailable): + message = "日志目录不可读取" + } + httpx.Fail(c, message) +} + func (h *Audit) DeleteError(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) if err := h.service.DeleteErrors(c.Request.Context(), []uint{uint(id)}); err != nil { - httpx.Fail(c, "删除失败") + httpx.Fail(c, "删除失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) DeleteErrors(c *gin.Context) { if err := h.service.DeleteErrors(c.Request.Context(), IDsFromQuery(c)); err != nil { - httpx.Fail(c, "删除失败") + httpx.Fail(c, "批量删除失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") } func (h *Audit) UpdateError(c *gin.Context) { var req dto.ErrorRecordRequest @@ -205,19 +226,19 @@ func (h *Audit) UpdateError(c *gin.Context) { return } if err := h.service.UpdateErrorRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败") + httpx.Fail(c, "更新失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") } func (h *Audit) Error(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) item, err := h.service.Error(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "查询失败") + httpx.Fail(c, "查询失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, item, "查询成功") + httpx.OKWithData(c, item) } func (h *Audit) Errors(c *gin.Context) { p, size := page(c) @@ -232,20 +253,20 @@ func (h *Audit) Errors(c *gin.Context) { } items, total, err := h.service.ErrorsFilter(c.Request.Context(), p, size, c.Query("form"), c.Query("info"), createdAtRange) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") } func (h *Audit) CreateError(c *gin.Context) { var req dto.ErrorRecordRequest - if c.ShouldBindJSON(&req) != nil || req.Form == "" { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.CreateErrorRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败") + httpx.Fail(c, "创建失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") } diff --git a/internal/server/handler/authority.go b/internal/server/handler/authority.go index 9104d4d..d6e9d27 100644 --- a/internal/server/handler/authority.go +++ b/internal/server/handler/authority.go @@ -30,7 +30,7 @@ func (h *Authority) Create(c *gin.Context) { } value, err := h.service.CreateAuthorityRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "创建失败:"+err.Error()) + httpx.Fail(c, "创建失败"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "创建成功") @@ -43,7 +43,7 @@ func (h *Authority) Copy(c *gin.Context) { } value, err := h.service.CopyAuthorityRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "拷贝失败:"+err.Error()) + httpx.Fail(c, "拷贝失败"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "拷贝成功") @@ -56,7 +56,7 @@ func (h *Authority) Update(c *gin.Context) { } value, err := h.service.UpdateAuthorityRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "更新失败") + httpx.Fail(c, "更新失败"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "更新成功") @@ -68,10 +68,10 @@ func (h *Authority) Delete(c *gin.Context) { return } if err := h.service.DeleteAuthority(c.Request.Context(), req.AuthorityID); err != nil { - httpx.Fail(c, err.Error()) + httpx.Fail(c, "删除失败"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Authority) SetUsers(c *gin.Context) { var req dto.SetRoleUsersRequest @@ -80,16 +80,16 @@ func (h *Authority) SetUsers(c *gin.Context) { return } if err := h.service.SetAuthorityUsers(c.Request.Context(), req.AuthorityID, req.UserIDs); err != nil { - httpx.Fail(c, "设置失败") + httpx.Fail(c, "设置失败"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } func (h *Authority) Users(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64) ids, err := h.service.AuthorityUserIDs(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败"+err.Error()) return } if ids == nil { @@ -104,16 +104,16 @@ func (h *Authority) SetDataScope(c *gin.Context) { return } if err := h.service.SetDataScope(c.Request.Context(), req.AuthorityID, req.DataScope, req.DepartmentIDs); err != nil { - httpx.Fail(c, "设置失败") + httpx.Fail(c, "设置失败"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } func (h *Authority) DataScopeDepartments(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64) ids, err := h.service.DataScopeDepartmentIDs(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败"+err.Error()) return } if ids == nil { diff --git a/internal/server/handler/dictionary.go b/internal/server/handler/dictionary.go index b072cda..b3c9fef 100644 --- a/internal/server/handler/dictionary.go +++ b/internal/server/handler/dictionary.go @@ -19,54 +19,50 @@ func NewDictionary(service *service.SettingsService) *Dictionary { func (h *Dictionary) Create(c *gin.Context) { var req dto.DictionaryRequest - if c.ShouldBindJSON(&req) != nil || req.Name == "" || req.Type == "" { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } - if err := h.service.CreateDictionaryRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败:"+err.Error()) + created, err := h.service.CreateDictionaryRequest(c.Request.Context(), &req) + if err != nil { + httpx.Fail(c, "创建失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, created, "创建成功") } func (h *Dictionary) Update(c *gin.Context) { var req dto.DictionaryRequest - if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.UpdateDictionaryRequest(c.Request.Context(), &req); err != nil { httpx.Fail(c, "更新失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") } func (h *Dictionary) Delete(c *gin.Context) { var req dto.DictionaryRequest - if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - httpx.Fail(c, "参数错误") - return - } - if err := h.service.DeleteDictionary(c.Request.Context(), req.ID); err != nil { + if err := c.ShouldBindJSON(&req); err != nil { httpx.Fail(c, err.Error()) return } - httpx.OK(c) + if err := h.service.DeleteDictionary(c.Request.Context(), req.ID); err != nil { + httpx.Fail(c, "删除失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Dictionary) Find(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) - var status *bool - if raw, exists := c.GetQuery("status"); exists { - value, parseErr := strconv.ParseBool(raw) - if parseErr != nil { - httpx.Fail(c, parseErr.Error()) - return - } - status = &value + var req dto.DictionaryRequest + if err := c.ShouldBindQuery(&req); err != nil { + httpx.Fail(c, err.Error()) + return } - item, err := h.service.Dictionary(c.Request.Context(), uint(id), c.Query("type"), status, true) + item, err := h.service.Dictionary(c.Request.Context(), req.ID, req.Type, req.Status, true) if err != nil { - httpx.Fail(c, "查询失败") + httpx.Fail(c, "字典未创建或未开启") return } httpx.Write(c, httpx.CodeSuccess, gin.H{"resysDictionary": item}, "查询成功") @@ -82,8 +78,16 @@ func (h *Dictionary) List(details bool) gin.HandlerFunc { } } func (h *Dictionary) Export(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) - item, err := h.service.ExportDictionary(c.Request.Context(), uint(id)) + var req dto.DictionaryRequest + if err := c.ShouldBindQuery(&req); err != nil { + httpx.Fail(c, err.Error()) + return + } + if req.ID == 0 { + httpx.Fail(c, "字典ID不能为空") + return + } + item, err := h.service.ExportDictionary(c.Request.Context(), req.ID) if err != nil { httpx.Fail(c, "导出失败") return @@ -103,56 +107,60 @@ func (h *Dictionary) Export(c *gin.Context) { } func (h *Dictionary) Import(c *gin.Context) { var req dto.ImportDictionaryRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") - return - } - if err := h.service.ImportDictionaryJSON(c.Request.Context(), req.JSON); err != nil { + if err := c.ShouldBindJSON(&req); err != nil { httpx.Fail(c, err.Error()) return } - httpx.OK(c) + if err := h.service.ImportDictionaryJSON(c.Request.Context(), req.JSON); err != nil { + httpx.Fail(c, "导入失败: "+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入成功") } func (h *Dictionary) CreateDetail(c *gin.Context) { var req dto.DictionaryDetailRequest - if c.ShouldBindJSON(&req) != nil || req.DictionaryID == 0 { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.CreateDictionaryDetailRequest(c.Request.Context(), &req); err != nil { httpx.Fail(c, "创建失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") } func (h *Dictionary) UpdateDetail(c *gin.Context) { var req dto.DictionaryDetailRequest - if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.UpdateDictionaryDetailRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, err.Error()) + httpx.Fail(c, "更新失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") } func (h *Dictionary) DeleteDetail(c *gin.Context) { var req dto.DictionaryDetailRequest - if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - httpx.Fail(c, "参数错误") - return - } - if err := h.service.DeleteDictionaryDetail(c.Request.Context(), req.ID); err != nil { + if err := c.ShouldBindJSON(&req); err != nil { httpx.Fail(c, err.Error()) return } - httpx.OK(c) + if err := h.service.DeleteDictionaryDetail(c.Request.Context(), req.ID); err != nil { + httpx.Fail(c, "删除失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Dictionary) FindDetail(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) - item, err := h.service.DictionaryDetail(c.Request.Context(), uint(id)) + var req dto.DictionaryDetailRequest + if err := c.ShouldBindQuery(&req); err != nil { + httpx.Fail(c, err.Error()) + return + } + item, err := h.service.DictionaryDetail(c.Request.Context(), req.ID) if err != nil { httpx.Fail(c, "查询失败") return @@ -197,31 +205,54 @@ func (h *Dictionary) Details(c *gin.Context) { } func (h *Dictionary) Tree(byType bool) gin.HandlerFunc { return func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("sysDictionaryID"), 10, 64) + rawID := c.Query("sysDictionaryID") + id, parseErr := strconv.ParseUint(rawID, 10, 64) typ := "" if byType { typ = c.Query("type") + if typ == "" { + httpx.Fail(c, "字典类型不能为空") + return + } + } else if rawID == "" { + httpx.Fail(c, "字典ID不能为空") + return + } else if parseErr != nil { + httpx.Fail(c, "字典ID格式错误") + return } items, err := h.service.DictionaryTree(c.Request.Context(), uint(id), typ) if err != nil { httpx.Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, items, "获取成功") + httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items}, "获取成功") } } func (h *Dictionary) DetailsByParent(c *gin.Context) { - parentID, _ := strconv.ParseUint(c.Query("parentID"), 10, 64) - dictionaryID, _ := strconv.ParseUint(c.Query("sysDictionaryID"), 10, 64) - items, err := h.service.DictionaryDetailsByParent(c.Request.Context(), uint(dictionaryID), uint(parentID)) + var req dto.DictionaryDetailsByParentRequest + if err := c.ShouldBindQuery(&req); err != nil { + httpx.Fail(c, err.Error()) + return + } + items, err := h.service.DictionaryDetailsByParent(c.Request.Context(), req.DictionaryID, req.ParentID, req.IncludeChildren) if err != nil { httpx.Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, items, "获取成功") + httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items}, "获取成功") } func (h *Dictionary) Path(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) + rawID := c.Query("id") + if rawID == "" { + httpx.Fail(c, "字典详情ID不能为空") + return + } + id, parseErr := strconv.ParseUint(rawID, 10, 64) + if parseErr != nil { + httpx.Fail(c, "字典详情ID格式错误") + return + } item, err := h.service.DictionaryDetail(c.Request.Context(), uint(id)) if err != nil { httpx.Fail(c, "获取失败") diff --git a/internal/server/handler/export.go b/internal/server/handler/export.go index e746368..f598abc 100644 --- a/internal/server/handler/export.go +++ b/internal/server/handler/export.go @@ -4,7 +4,6 @@ import ( "encoding/json" "net/http" "net/url" - "strconv" "strings" "time" @@ -49,60 +48,72 @@ func exportParams(values url.Values) map[string]string { func (h *Export) Create(c *gin.Context) { var req dto.ExportTemplateRequest - if c.ShouldBindJSON(&req) != nil || req.Name == "" || req.TemplateID == "" { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) + return + } + if req.Name == "" { + httpx.Fail(c, "Name值不能为空") return } if err := h.service.CreateRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败:"+err.Error()) + httpx.Fail(c, "创建失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") } func (h *Export) Update(c *gin.Context) { var req dto.ExportTemplateRequest - if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) + return + } + if req.Name == "" { + httpx.Fail(c, "Name值不能为空") return } if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败:"+err.Error()) + httpx.Fail(c, "更新失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") } func (h *Export) Delete(c *gin.Context) { var req dto.IDRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.Delete(c.Request.Context(), []uint{req.ID}); err != nil { httpx.Fail(c, "删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Export) DeleteMany(c *gin.Context) { var req dto.IDsRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.Delete(c.Request.Context(), req.IDs); err != nil { - httpx.Fail(c, "删除失败") + httpx.Fail(c, "批量删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") } func (h *Export) Find(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) - item, err := h.service.Template(c.Request.Context(), uint(id), "") + var req dto.ExportTemplateRequest + if err := c.ShouldBindQuery(&req); err != nil { + httpx.Fail(c, err.Error()) + return + } + item, err := h.service.Template(c.Request.Context(), req.ID, "") if err != nil { httpx.Fail(c, "查询失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"resysExportTemplate": item}, "查询成功") + httpx.OKWithData(c, gin.H{"resysExportTemplate": item}) } func (h *Export) List(c *gin.Context) { p, size := page(c) @@ -120,12 +131,17 @@ func (h *Export) List(c *gin.Context) { httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") } func (h *Export) Preview(c *gin.Context) { - sql, err := h.service.Preview(c.Request.Context(), c.Query("templateID"), exportParams(c.Request.URL.Query())) - if err != nil { - httpx.Fail(c, "预览失败:"+err.Error()) + templateID := c.Query("templateID") + if templateID == "" { + httpx.Fail(c, "模板ID不能为空") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"sql": sql}, "获取成功") + sql, err := h.service.Preview(c.Request.Context(), templateID, exportParams(c.Request.URL.Query())) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.OKWithData(c, gin.H{"sql": sql}) } func (h *Export) Issue(blank bool) gin.HandlerFunc { @@ -137,18 +153,27 @@ func (h *Export) Issue(blank bool) gin.HandlerFunc { } token := strings.ReplaceAll(uuid.NewString(), "-", "") raw, _ := json.Marshal(exportToken{TemplateID: templateID, Params: exportParams(c.Request.URL.Query()), Blank: blank}) - if err := h.system.CacheSet(c.Request.Context(), "export:"+token, string(raw), 5*time.Minute); err != nil { + if err := h.system.CacheSet(c.Request.Context(), "export:"+token, string(raw), 30*time.Minute); err != nil { httpx.Fail(c, "导出令牌创建失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"token": token}, "获取成功") + path := "/sysExportTemplate/exportExcelByToken?token=" + token + if blank { + path = "/sysExportTemplate/exportTemplateByToken?token=" + token + } + httpx.OKWithData(c, path) } } func (h *Export) Import(c *gin.Context) { + templateID := c.Query("templateID") + if templateID == "" { + httpx.Fail(c, "模板ID不能为空") + return + } file, err := c.FormFile("file") if err != nil { - httpx.Fail(c, "请选择导入文件") + httpx.Fail(c, "文件获取失败") return } opened, err := file.Open() @@ -157,31 +182,35 @@ func (h *Export) Import(c *gin.Context) { return } defer opened.Close() - templateID := c.PostForm("templateID") - if templateID == "" { - templateID = c.Query("templateID") - } if err = h.service.Import(c.Request.Context(), templateID, opened); err != nil { httpx.Fail(c, err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入成功") } func (h *Export) Download(expectBlank bool) gin.HandlerFunc { return func(c *gin.Context) { token := c.Query("token") + if token == "" { + httpx.Fail(c, "导出token不能为空") + return + } raw, ok, err := h.system.CacheGet(c.Request.Context(), "export:"+token) if err != nil || !ok { - httpx.Fail(c, "导出令牌无效或已过期") + httpx.Fail(c, "导出token无效或已过期") + return + } + var value exportToken + if json.Unmarshal([]byte(raw), &value) != nil { + httpx.Fail(c, "解析导出参数失败") + return + } + if value.Blank != expectBlank { + httpx.Fail(c, "token类型错误") return } _ = h.system.CacheDelete(c.Request.Context(), "export:"+token) - var value exportToken - if json.Unmarshal([]byte(raw), &value) != nil || value.Blank != expectBlank { - httpx.Fail(c, "导出令牌无效") - return - } var data []byte var name string if expectBlank { @@ -190,10 +219,16 @@ func (h *Export) Download(expectBlank bool) gin.HandlerFunc { data, name, err = h.service.Export(c.Request.Context(), value.TemplateID, value.Params) } if err != nil { - httpx.Fail(c, "导出失败:"+err.Error()) + httpx.Fail(c, "获取失败") return } - c.Header("Content-Disposition", "attachment; filename*=UTF-8''"+url.QueryEscape(name)) + if expectBlank { + name = strings.TrimSuffix(name, "_template.xlsx") + "模板.xlsx" + } else { + name = strings.TrimSuffix(name, ".xlsx") + strings.ReplaceAll(uuid.NewString(), "-", "")[:6] + ".xlsx" + } + c.Header("Content-Disposition", "attachment; filename="+name) + c.Header("success", "true") c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", data) } } diff --git a/internal/server/handler/media.go b/internal/server/handler/media.go index 4b09dd2..fd1bf5a 100644 --- a/internal/server/handler/media.go +++ b/internal/server/handler/media.go @@ -127,7 +127,7 @@ func (h *Media) Storage(c *gin.Context) { } items, next, more, err := h.service.Storage(c.Request.Context(), req.Prefix, req.Cursor, req.Limit) if err != nil { - httpx.Fail(c, "列举失败") + httpx.Fail(c, "列举存储桶文件失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items, "nextCursor": next, "hasMore": more}, "获取成功") @@ -136,14 +136,14 @@ func (h *Media) Storage(c *gin.Context) { func (h *Media) Categories(c *gin.Context) { items, err := h.service.Categories(c.Request.Context()) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取分类列表失败") return } - httpx.Write(c, httpx.CodeSuccess, items, "获取成功") + httpx.OKWithData(c, items) } func (h *Media) SaveCategory(c *gin.Context) { var req dto.CategoryRequest - if c.ShouldBindJSON(&req) != nil || req.Name == "" { + if c.ShouldBindJSON(&req) != nil { httpx.Fail(c, "参数错误") return } @@ -155,7 +155,7 @@ func (h *Media) SaveCategory(c *gin.Context) { } func (h *Media) DeleteCategory(c *gin.Context) { var req dto.IDRequest - if c.ShouldBindJSON(&req) != nil { + if c.ShouldBindJSON(&req) != nil || req.ID == 0 { httpx.Fail(c, "参数错误") return } @@ -173,12 +173,16 @@ func (h *Media) InitUpload(c *gin.Context) { httpx.Fail(c, "参数错误") return } + if config := h.service.MediaConfig(); config != nil && config.MaxFileSize > 0 && req.FileSize > config.MaxFileSize { + httpx.Fail(c, "文件超过大小上限") + return + } result, err := h.service.InitUpload(c.Request.Context(), claims.ID, req.FileName, req.FileHash, req.FileSize, req.ChunkSize, req.ChunkTotal) if err != nil { httpx.Fail(c, err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, result, "获取成功") + httpx.Write(c, httpx.CodeSuccess, result, "成功") } func (h *Media) SaveChunk(c *gin.Context) { claims := servermiddleware.Claims(c) @@ -199,7 +203,7 @@ func (h *Media) SaveChunk(c *gin.Context) { httpx.Fail(c, err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "ok") } func (h *Media) CompleteUpload(c *gin.Context) { claims := servermiddleware.Claims(c) @@ -213,7 +217,7 @@ func (h *Media) CompleteUpload(c *gin.Context) { httpx.Fail(c, err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"media": item}, "上传成功") + httpx.Write(c, httpx.CodeSuccess, gin.H{"media": item}, "成功") } func (h *Media) CancelUpload(c *gin.Context) { claims := servermiddleware.Claims(c) @@ -226,5 +230,5 @@ func (h *Media) CancelUpload(c *gin.Context) { httpx.Fail(c, err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "已取消") } diff --git a/internal/server/handler/menu.go b/internal/server/handler/menu.go index e41db48..3a6d851 100644 --- a/internal/server/handler/menu.go +++ b/internal/server/handler/menu.go @@ -39,10 +39,10 @@ func (h *Menu) Create(c *gin.Context) { return } if err := h.service.Create(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "新增失败") + httpx.Fail(c, "添加失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "添加成功") } func (h *Menu) Update(c *gin.Context) { @@ -55,7 +55,7 @@ func (h *Menu) Update(c *gin.Context) { httpx.Fail(c, "更新失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") } func (h *Menu) Delete(c *gin.Context) { @@ -65,10 +65,10 @@ func (h *Menu) Delete(c *gin.Context) { return } if err := h.service.Delete(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, err.Error()) + httpx.Fail(c, "删除失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Menu) Find(c *gin.Context) { @@ -95,7 +95,7 @@ func (h *Menu) SetAuthorityMenus(c *gin.Context) { httpx.Fail(c, "添加失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "添加成功") } func (h *Menu) AuthorityMenus(c *gin.Context) { @@ -143,5 +143,5 @@ func (h *Menu) SetRoles(c *gin.Context) { httpx.Fail(c, "设置失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } diff --git a/internal/server/handler/organization.go b/internal/server/handler/organization.go index 4052b4f..ec13b7d 100644 --- a/internal/server/handler/organization.go +++ b/internal/server/handler/organization.go @@ -74,7 +74,7 @@ func (h *Organization) FindDepartment(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("id"), 10, 64) item, err := h.service.Department(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, item, "获取成功") @@ -83,7 +83,7 @@ func (h *Organization) DepartmentUsers(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("deptId"), 10, 64) ids, err := h.service.DepartmentUserIDs(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败:"+err.Error()) return } if ids == nil { @@ -98,10 +98,10 @@ func (h *Organization) SetDepartmentUsers(c *gin.Context) { return } if err := h.service.SetDepartmentUsers(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "设置失败") + httpx.Fail(c, "设置失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } func (h *Organization) SetUserDepartments(c *gin.Context) { var req dto.SetUserDepartmentsRequest @@ -113,7 +113,7 @@ func (h *Organization) SetUserDepartments(c *gin.Context) { httpx.Fail(c, "设置失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } func (h *Organization) ListPositions(c *gin.Context) { @@ -177,7 +177,7 @@ func (h *Organization) FindPosition(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("id"), 10, 64) item, err := h.service.Position(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, item, "获取成功") @@ -186,7 +186,7 @@ func (h *Organization) PositionUsers(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("positionId"), 10, 64) ids, err := h.service.PositionUserIDs(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败:"+err.Error()) return } if ids == nil { @@ -201,10 +201,10 @@ func (h *Organization) SetPositionUsers(c *gin.Context) { return } if err := h.service.SetPositionUsers(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "设置失败") + httpx.Fail(c, "设置失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } func (h *Organization) SetUserPositions(c *gin.Context) { var req dto.SetUserPositionsRequest @@ -216,5 +216,5 @@ func (h *Organization) SetUserPositions(c *gin.Context) { httpx.Fail(c, "设置失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } diff --git a/internal/server/handler/parameter.go b/internal/server/handler/parameter.go index c08f52a..e0595f4 100644 --- a/internal/server/handler/parameter.go +++ b/internal/server/handler/parameter.go @@ -20,73 +20,65 @@ func NewParameter(service *service.SettingsService) *Parameter { func (h *Parameter) Create(c *gin.Context) { var req dto.SystemParameterRequest - if c.ShouldBindJSON(&req) != nil || req.Key == "" { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.CreateParameterRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败") + httpx.Fail(c, "创建失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") } func (h *Parameter) Update(c *gin.Context) { var req dto.SystemParameterRequest - if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.service.UpdateParameterRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败") + httpx.Fail(c, "更新失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") } func (h *Parameter) Delete(c *gin.Context) { - id, err := strconv.ParseUint(c.Query("ID"), 10, 64) - if err != nil || id == 0 { - httpx.Fail(c, "参数错误") - return - } + id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) if err := h.service.DeleteParameters(c.Request.Context(), []uint{uint(id)}); err != nil { - httpx.Fail(c, "删除失败") + httpx.Fail(c, "删除失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Parameter) DeleteMany(c *gin.Context) { ids := IDsFromQuery(c) - if len(ids) == 0 { - httpx.Fail(c, "参数错误") - return - } if err := h.service.DeleteParameters(c.Request.Context(), ids); err != nil { - httpx.Fail(c, "删除失败") + httpx.Fail(c, "批量删除失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") } func (h *Parameter) Find(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) item, err := h.service.Parameter(c.Request.Context(), uint(id), "") if err != nil { - httpx.Fail(c, "查询失败") + httpx.Fail(c, "查询失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, item, "查询成功") + httpx.OKWithData(c, item) } func (h *Parameter) Get(c *gin.Context) { item, err := h.service.Parameter(c.Request.Context(), 0, c.Query("key")) if err != nil { - httpx.Fail(c, "查询失败") + httpx.Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, item, "查询成功") + httpx.Write(c, httpx.CodeSuccess, item, "获取成功") } func (h *Parameter) List(c *gin.Context) { @@ -99,7 +91,7 @@ func (h *Parameter) List(c *gin.Context) { } items, total, err := h.service.ParametersFilter(c.Request.Context(), p, size, c.Query("name"), c.Query("key"), start, end) if err != nil { - httpx.Fail(c, "获取失败") + httpx.Fail(c, "获取失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") diff --git a/internal/server/handler/permission.go b/internal/server/handler/permission.go index c290408..2edbe29 100644 --- a/internal/server/handler/permission.go +++ b/internal/server/handler/permission.go @@ -38,7 +38,7 @@ func (h *Permission) SetButtons(c *gin.Context) { httpx.Fail(c, "分配失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "分配成功") } func (h *Permission) CanRemoveButton(c *gin.Context) { @@ -52,5 +52,5 @@ func (h *Permission) CanRemoveButton(c *gin.Context) { httpx.Fail(c, "此按钮正在被使用无法删除") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } diff --git a/internal/server/handler/session.go b/internal/server/handler/session.go index a89ca48..ef2ca34 100644 --- a/internal/server/handler/session.go +++ b/internal/server/handler/session.go @@ -16,9 +16,9 @@ func (h *Session) Logout(c *gin.Context) { token, _ = c.Cookie("x-token") } if err := h.settings.BlacklistToken(c.Request.Context(), token); err != nil { - httpx.Fail(c, "退出失败") + httpx.Fail(c, "jwt作废失败") return } httpx.SetTokenCookie(c, "", -1) - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "jwt作废成功") } diff --git a/internal/server/handler/system_config.go b/internal/server/handler/system_config.go index 1b45015..4649485 100644 --- a/internal/server/handler/system_config.go +++ b/internal/server/handler/system_config.go @@ -36,13 +36,13 @@ func (h *SystemConfig) GetSecurity(c *gin.Context) { func (h *SystemConfig) SetSecurity(c *gin.Context) { var req dto.SecurityConfigRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } result, err := h.settings.SaveSecurityRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, err.Error()) + httpx.Fail(c, "设置安全配置失败") return } httpx.Write(c, httpx.CodeSuccess, result, "设置成功") @@ -54,12 +54,12 @@ func (h *SystemConfig) Get(c *gin.Context) { func (h *SystemConfig) Set(c *gin.Context) { var req dto.SetSystemConfigRequest - if c.ShouldBindJSON(&req) != nil { - httpx.Fail(c, "参数错误") + if err := c.ShouldBindJSON(&req); err != nil { + httpx.Fail(c, err.Error()) return } if err := h.system.SaveSystemConfig(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "配置保存失败: "+err.Error()) + httpx.Fail(c, "设置失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") diff --git a/internal/server/handler/task.go b/internal/server/handler/task.go index a34fc76..391eeab 100644 --- a/internal/server/handler/task.go +++ b/internal/server/handler/task.go @@ -38,7 +38,7 @@ func (h *Task) Create(c *gin.Context) { httpx.Fail(c, "调度失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") } func (h *Task) Update(c *gin.Context) { @@ -55,7 +55,7 @@ func (h *Task) Update(c *gin.Context) { httpx.Fail(c, "调度失败:"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") } func (h *Task) Delete(c *gin.Context) { @@ -69,7 +69,7 @@ func (h *Task) Delete(c *gin.Context) { httpx.Fail(c, "删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Task) Toggle(c *gin.Context) { @@ -79,14 +79,14 @@ func (h *Task) Toggle(c *gin.Context) { return } if err := h.service.Toggle(c.Request.Context(), req.ID, req.Enabled); err != nil { - httpx.Fail(c, "设置失败") + httpx.Fail(c, "操作失败: "+err.Error()) return } if err := h.scheduler.ScheduleID(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "调度失败") + httpx.Fail(c, "操作失败: "+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "操作成功") } func (h *Task) Trigger(c *gin.Context) { @@ -96,10 +96,10 @@ func (h *Task) Trigger(c *gin.Context) { return } if err := h.scheduler.TriggerID(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "任务不存在") + httpx.Fail(c, "触发失败: "+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "已触发, 执行结果见执行日志") } func (h *Task) List(c *gin.Context) { diff --git a/internal/server/handler/user.go b/internal/server/handler/user.go index 420f5f2..7480aec 100644 --- a/internal/server/handler/user.go +++ b/internal/server/handler/user.go @@ -54,7 +54,7 @@ func (h *User) Update(c *gin.Context) { httpx.Fail(c, "修改失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } func (h *User) UpdateSelf(c *gin.Context) { @@ -73,7 +73,7 @@ func (h *User) UpdateSelf(c *gin.Context) { httpx.Fail(c, "修改失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } func (h *User) Delete(c *gin.Context) { @@ -91,7 +91,7 @@ func (h *User) Delete(c *gin.Context) { httpx.Fail(c, "删除失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *User) ResetPassword(c *gin.Context) { @@ -101,10 +101,10 @@ func (h *User) ResetPassword(c *gin.Context) { return } if err := h.service.ResetPassword(c.Request.Context(), req.ID, req.Password); err != nil { - httpx.Fail(c, "重置失败") + httpx.Fail(c, "重置失败"+err.Error()) return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "重置成功") } func (h *User) ChangePassword(c *gin.Context) { @@ -118,7 +118,7 @@ func (h *User) ChangePassword(c *gin.Context) { httpx.Fail(c, "修改失败,原密码与当前账户不符") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功") } func (h *User) SetSelfSetting(c *gin.Context) { @@ -132,7 +132,7 @@ func (h *User) SetSelfSetting(c *gin.Context) { httpx.Fail(c, "设置失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") } func (h *User) SetAuthorities(c *gin.Context) { @@ -145,7 +145,7 @@ func (h *User) SetAuthorities(c *gin.Context) { httpx.Fail(c, "修改失败") return } - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功") } func (h *User) SwitchAuthority(c *gin.Context) { @@ -164,7 +164,7 @@ func (h *User) SwitchAuthority(c *gin.Context) { c.Header("new-expires-at", strconv.FormatInt(login.ExpiresAt/1000, 10)) maxAge := int(time.Until(time.UnixMilli(login.ExpiresAt)).Seconds()) httpx.SetTokenCookie(c, login.Token, maxAge) - httpx.OK(c) + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功") } func (h *User) Get(c *gin.Context) { diff --git a/internal/server/handler/version.go b/internal/server/handler/version.go index e9ca1d8..70ee06c 100644 --- a/internal/server/handler/version.go +++ b/internal/server/handler/version.go @@ -45,7 +45,7 @@ func (h *Version) Find(c *gin.Context) { httpx.Fail(c, "查询失败") return } - httpx.Write(c, httpx.CodeSuccess, item, "查询成功") + httpx.OKWithData(c, item) } func (h *Version) List(c *gin.Context) { p, size := page(c) diff --git a/internal/server/server.go b/internal/server/server.go index ed7d9a5..aa38095 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -8,4 +8,4 @@ import ( ) // ProviderSet is server providers. -var ProviderSet = wire.NewSet(NewGinServer, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewTask, handler.NewMedia, handler.NewAudit, handler.NewExport, handler.NewVersion, handler.NewDictionary, handler.NewParameter, handler.NewAPIToken, handler.NewSystemConfig, handler.NewPublic, handler.NewUser, handler.NewNavigation, handler.NewSession, worker.NewTaskScheduler) +var ProviderSet = wire.NewSet(NewGinEngine, NewGinServer, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewTask, handler.NewMedia, handler.NewAudit, handler.NewExport, handler.NewVersion, handler.NewDictionary, handler.NewParameter, handler.NewAPIToken, handler.NewSystemConfig, handler.NewPublic, handler.NewUser, handler.NewNavigation, handler.NewSession, worker.NewTaskScheduler) diff --git a/internal/service/api_metadata.go b/internal/service/api_metadata.go index 682d617..2c9456c 100644 --- a/internal/service/api_metadata.go +++ b/internal/service/api_metadata.go @@ -6,7 +6,6 @@ type apiMetadataValue struct{ group, description string } var apiMetadata = map[string]apiMetadataValue{ "DELETE /api/deleteApisByIds": {group: "api", description: "批量删除api"}, - "DELETE /customer/customer": {group: "客户", description: "删除客户"}, "DELETE /dataAccessLog/deleteDataAccessLogByIds": {group: "数据权限审计", description: "批量删除数据权限审计日志"}, "DELETE /department/deleteDepartment": {group: "部门", description: "删除部门"}, "DELETE /info/deleteInfo": {group: "公告", description: "删除公告"}, @@ -35,8 +34,6 @@ var apiMetadata = map[string]apiMetadataValue{ "GET /attachmentCategory/getCategoryList": {group: "媒体库分类", description: "分类列表"}, "GET /authority/getDataScopeDepts": {group: "角色", description: "获取角色自定义部门集"}, "GET /authority/getUsersByAuthority": {group: "角色", description: "获取角色关联用户ID列表"}, - "GET /customer/customer": {group: "客户", description: "获取单一客户"}, - "GET /customer/customerList": {group: "客户", description: "获取客户列表"}, "GET /department/findDepartment": {group: "部门", description: "根据ID获取部门"}, "GET /department/getDepartmentUsers": {group: "部门", description: "获取部门成员ID列表"}, "GET /info/findInfo": {group: "公告", description: "根据ID获取公告"}, @@ -48,8 +45,6 @@ var apiMetadata = map[string]apiMetadataValue{ "GET /position/findPosition": {group: "岗位", description: "根据ID获取岗位"}, "GET /position/getPositionUsers": {group: "岗位", description: "获取岗位成员ID列表"}, "GET /securityConfig/getSecurityConfig": {group: "安全配置", description: "获取安全配置"}, - "GET /simpleUploader/checkFileMd5": {group: "断点续传(插件版)", description: "文件完整度验证"}, - "GET /simpleUploader/mergeFileMd5": {group: "断点续传(插件版)", description: "上传完成合并文件"}, "GET /sysDictionary/exportSysDictionary": {group: "系统字典", description: "导出字典JSON"}, "GET /sysDictionary/findSysDictionary": {group: "系统字典", description: "根据ID获取字典(建议选择)"}, "GET /sysDictionary/getSysDictionaryList": {group: "系统字典", description: "获取字典列表"}, @@ -62,7 +57,6 @@ var apiMetadata = map[string]apiMetadataValue{ "GET /sysDictionaryDetail/getSysDictionaryDetailList": {group: "系统字典详情", description: "获取字典内容列表"}, "GET /sysError/findSysError": {group: "错误日志", description: "根据ID获取错误日志"}, "GET /sysError/getSysErrorList": {group: "错误日志", description: "获取错误日志列表"}, - "GET /sysError/getSysErrorSolution": {group: "错误日志", description: "触发错误处理(异步)"}, "GET /sysExportTemplate/exportExcel": {group: "导出模板", description: "导出Excel"}, "GET /sysExportTemplate/exportTemplate": {group: "导出模板", description: "下载模板"}, "GET /sysExportTemplate/findSysExportTemplate": {group: "导出模板", description: "根据ID获取导出模板"}, @@ -103,12 +97,8 @@ var apiMetadata = map[string]apiMetadataValue{ "POST /authorityBtn/canRemoveAuthorityBtn": {group: "按钮权限", description: "删除按钮"}, "POST /authorityBtn/getAuthorityBtn": {group: "按钮权限", description: "获取已有按钮权限"}, "POST /authorityBtn/setAuthorityBtn": {group: "按钮权限", description: "设置按钮权限"}, - "POST /autoCode/initAPI": {group: "代码生成器", description: "生成插件 API 初始化文件"}, - "POST /autoCode/initDictionary": {group: "代码生成器", description: "生成插件字典初始化文件"}, - "POST /autoCode/initMenu": {group: "代码生成器", description: "生成插件菜单初始化文件"}, "POST /casbin/getPolicyPathByAuthorityId": {group: "casbin", description: "获取权限列表"}, "POST /casbin/updateCasbin": {group: "casbin", description: "更改角色api权限"}, - "POST /customer/customer": {group: "客户", description: "创建客户"}, "POST /dataAccessLog/getDataAccessLogList": {group: "数据权限审计", description: "获取数据权限审计日志"}, "POST /department/createDepartment": {group: "部门", description: "创建部门"}, "POST /department/getDepartmentList": {group: "部门", description: "获取部门树"}, @@ -139,7 +129,6 @@ var apiMetadata = map[string]apiMetadataValue{ "POST /position/getPositionList": {group: "岗位", description: "获取岗位列表"}, "POST /position/setPositionUsers": {group: "岗位", description: "设置岗位成员(反向分配)"}, "POST /securityConfig/setSecurityConfig": {group: "安全配置", description: "设置安全配置"}, - "POST /simpleUploader/upload": {group: "断点续传(插件版)", description: "插件版分片上传"}, "POST /sysApiToken/createApiToken": {group: "API Token", description: "签发API Token"}, "POST /sysApiToken/deleteApiToken": {group: "API Token", description: "作废API Token"}, "POST /sysApiToken/getApiTokenList": {group: "API Token", description: "获取API Token列表"}, @@ -168,7 +157,6 @@ var apiMetadata = map[string]apiMetadataValue{ "POST /user/setUserDepartments": {group: "系统用户", description: "设置用户归属部门"}, "POST /user/setUserPositions": {group: "系统用户", description: "设置用户岗位"}, "PUT /authority/updateAuthority": {group: "角色", description: "更新角色信息"}, - "PUT /customer/customer": {group: "客户", description: "更新客户"}, "PUT /department/updateDepartment": {group: "部门", description: "更新部门"}, "PUT /info/updateInfo": {group: "公告", description: "更新公告"}, "PUT /position/updatePosition": {group: "岗位", description: "更新岗位"}, diff --git a/internal/service/dictionary.go b/internal/service/dictionary.go index 6fb77d6..2329164 100644 --- a/internal/service/dictionary.go +++ b/internal/service/dictionary.go @@ -14,8 +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 (s *SettingsService) CreateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) error { - return s.CreateDictionary(ctx, dictionaryDomain(req)) +func (s *SettingsService) CreateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) (map[string]any, error) { + value := dictionaryDomain(req) + if err := s.CreateDictionary(ctx, value); err != nil { + return nil, err + } + return dictionaryDTO(value), nil } func (s *SettingsService) UpdateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) error { return s.UpdateDictionary(ctx, dictionaryDomain(req)) diff --git a/internal/service/dictionary_import.go b/internal/service/dictionary_import.go index bac157d..0bdbf3b 100644 --- a/internal/service/dictionary_import.go +++ b/internal/service/dictionary_import.go @@ -27,8 +27,8 @@ func (s *SettingsService) ImportDictionaryJSON(ctx context.Context, raw string) } 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) +func (s *SettingsService) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]map[string]any, error) { + items, err := s.uc.DictionaryDetailsByParent(ctx, dictionaryID, parentID, includeChildren) if err != nil { return nil, err } diff --git a/internal/service/dto/media.go b/internal/service/dto/media.go index 7dc7f6f..688f940 100644 --- a/internal/service/dto/media.go +++ b/internal/service/dto/media.go @@ -51,6 +51,7 @@ type InitUploadRequest struct { FileSize int64 `json:"fileSize"` ChunkSize int64 `json:"chunkSize"` ChunkTotal int `json:"chunkTotal"` + Mime string `json:"mime"` } type CompleteUploadRequest struct { UploadID uint `json:"uploadId"` diff --git a/internal/service/dto/settings.go b/internal/service/dto/settings.go index 1870e22..6e9a7bf 100644 --- a/internal/service/dto/settings.go +++ b/internal/service/dto/settings.go @@ -23,6 +23,11 @@ type DictionaryDetailRequest struct { type ImportDictionaryRequest struct { JSON string `json:"json"` } +type DictionaryDetailsByParentRequest struct { + DictionaryID uint `json:"sysDictionaryID" form:"sysDictionaryID" binding:"required"` + ParentID *uint `json:"parentID" form:"parentID"` + IncludeChildren bool `json:"includeChildren" form:"includeChildren"` +} type SystemParameterRequest struct { ID uint `json:"ID" form:"ID"` Name string `json:"name" form:"name"` diff --git a/internal/service/dto/system_config.go b/internal/service/dto/system_config.go index 4f50f77..f161206 100644 --- a/internal/service/dto/system_config.go +++ b/internal/service/dto/system_config.go @@ -87,7 +87,8 @@ type SetSystemConfigRequest struct { PathPrefix string `json:"pathPrefix"` } `json:"local"` Media struct { - SessionTTL int32 `json:"sessionTtl"` + SessionTTL int32 `json:"sessionTtl"` + MaxFileSize int64 `json:"maxFileSize"` } `json:"media"` Storage *conf.AdminBackend_Storage `json:"storage"` Zap *conf.AdminBackend_Zap `json:"zap"` diff --git a/internal/service/media.go b/internal/service/media.go index 0947441..cfcf93b 100644 --- a/internal/service/media.go +++ b/internal/service/media.go @@ -5,12 +5,24 @@ import ( "io" "kra/internal/biz" + "kra/internal/conf" "kra/internal/service/dto" ) -type MediaService struct{ uc *biz.MediaUsecase } +type MediaService struct { + uc *biz.MediaUsecase + runtime *conf.Runtime +} -func NewMediaService(uc *biz.MediaUsecase) *MediaService { return &MediaService{uc: uc} } +func NewMediaService(uc *biz.MediaUsecase, runtime *conf.Runtime) *MediaService { + return &MediaService{uc: uc, runtime: runtime} +} +func (s *MediaService) MediaConfig() *conf.AdminBackend_Media { + if config := s.runtime.Admin(); config != nil { + return config.Media + } + return nil +} func mediaDTO(v *biz.MediaFile) map[string]any { return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "classId": v.CategoryID, "url": v.URL, "tag": v.Tag, "key": v.Key, "size": v.Size, "mime": v.Mime, "md5": v.MD5, "userId": v.UserID} } diff --git a/internal/service/system_config.go b/internal/service/system_config.go index ccc5991..736d3ff 100644 --- a/internal/service/system_config.go +++ b/internal/service/system_config.go @@ -60,7 +60,7 @@ func (s *SystemService) SystemConfig() map[string]any { admin["local"] = map[string]any{"storePath": config.Local.StorePath, "pathPrefix": config.Local.PathPrefix} } if config.Media != nil { - admin["media"] = map[string]any{"sessionTtl": config.Media.SessionTtl} + admin["media"] = map[string]any{"sessionTtl": config.Media.SessionTtl, "maxFileSize": config.Media.MaxFileSize} } 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} @@ -169,8 +169,11 @@ func (s *SystemService) SaveSystemConfig(ctx context.Context, req *dto.SetSystem next.Local.PathPrefix = req.Config.Admin.Local.PathPrefix } } - if next.Media != nil && req.Config.Admin.Media.SessionTTL > 0 { - next.Media.SessionTtl = req.Config.Admin.Media.SessionTTL + if next.Media != nil { + if req.Config.Admin.Media.SessionTTL > 0 { + next.Media.SessionTtl = req.Config.Admin.Media.SessionTTL + } + next.Media.MaxFileSize = req.Config.Admin.Media.MaxFileSize } if req.Config.Admin.Storage != nil { preserveStorageSecrets(req.Config.Admin.Storage, next.Storage) diff --git a/pkg/logging/zap.go b/pkg/logging/zap.go index 6794808..b7d1aae 100644 --- a/pkg/logging/zap.go +++ b/pkg/logging/zap.go @@ -1,9 +1,11 @@ package logging import ( + "context" "log/slog" "os" "strings" + "sync" "time" "github.com/go-kratos/kratos/contrib/otel/v3/tracing" @@ -20,6 +22,85 @@ type Options struct { FileOnlyModules []string } +type handlerOperation struct { + attrs []slog.Attr + group string +} + +type reloadableHandlerState struct { + mu sync.RWMutex + handler slog.Handler + cleanup func() +} + +type reloadableHandler struct { + state *reloadableHandlerState + ops []handlerOperation +} + +func (h *reloadableHandler) resolved() slog.Handler { + current := h.state.handler + for _, operation := range h.ops { + if operation.group != "" { + current = current.WithGroup(operation.group) + } else { + current = current.WithAttrs(operation.attrs) + } + } + return current +} + +func (h *reloadableHandler) Enabled(ctx context.Context, level slog.Level) bool { + h.state.mu.RLock() + defer h.state.mu.RUnlock() + return h.resolved().Enabled(ctx, level) +} + +func (h *reloadableHandler) Handle(ctx context.Context, record slog.Record) error { + h.state.mu.RLock() + defer h.state.mu.RUnlock() + return h.resolved().Handle(ctx, record) +} + +func (h *reloadableHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + next := append([]handlerOperation(nil), h.ops...) + next = append(next, handlerOperation{attrs: append([]slog.Attr(nil), attrs...)}) + return &reloadableHandler{state: h.state, ops: next} +} + +func (h *reloadableHandler) WithGroup(name string) slog.Handler { + next := append([]handlerOperation(nil), h.ops...) + next = append(next, handlerOperation{group: name}) + return &reloadableHandler{state: h.state, ops: next} +} + +type ReloadableLogger struct { + state *reloadableHandlerState + filename string +} + +func (l *ReloadableLogger) Reload(root string, options Options) { + handler, cleanup := newZapHandler(root, l.filename, options) + l.state.mu.Lock() + previous := l.state.cleanup + l.state.handler = handler + l.state.cleanup = cleanup + l.state.mu.Unlock() + if previous != nil { + previous() + } +} + +func (l *ReloadableLogger) Close() { + l.state.mu.Lock() + cleanup := l.state.cleanup + l.state.cleanup = nil + l.state.mu.Unlock() + if cleanup != nil { + cleanup() + } +} + type moduleFilterCore struct { zapcore.Core fileOnly map[string]struct{} @@ -63,7 +144,7 @@ func moduleField(fields []zapcore.Field) string { // NewZapLogger adapts a Zap core to the slog logger used by Kratos v3. // The file layout remains compatible with the administration log viewer. -func NewZapLogger(root, filename string, options Options, attrs ...any) (*slog.Logger, func()) { +func newZapHandler(root, filename string, options Options) (slog.Handler, func()) { file := NewDailyWriter(root, filename, options.RetentionDay) encoder := zap.NewProductionEncoderConfig() encoder.EncodeTime = zapcore.RFC3339NanoTimeEncoder @@ -114,10 +195,24 @@ func NewZapLogger(root, filename string, options Options, attrs ...any) (*slog.L handlerOptions = append(handlerOptions, zapslog.WithCaller(true)) } handler := zapslog.NewHandler(zapLogger.Core(), handlerOptions...) - logger := kratoslog.NewLogger(handler, kratoslog.WithExtractor(tracing.TraceAttrs)).With(attrs...) cleanup := func() { _ = zapLogger.Sync() _ = file.Close() } - return logger, cleanup + return handler, cleanup +} + +// NewReloadableZapLogger keeps the slog/Kratos adapter stable while replacing +// the underlying Zap core when the runtime configuration changes. +func NewReloadableZapLogger(root, filename string, options Options, attrs ...any) (*slog.Logger, *ReloadableLogger) { + handler, cleanup := newZapHandler(root, filename, options) + state := &reloadableHandlerState{handler: handler, cleanup: cleanup} + control := &ReloadableLogger{state: state, filename: filename} + logger := kratoslog.NewLogger(&reloadableHandler{state: state}, kratoslog.WithExtractor(tracing.TraceAttrs)).With(attrs...) + return logger, control +} + +func NewZapLogger(root, filename string, options Options, attrs ...any) (*slog.Logger, func()) { + logger, control := NewReloadableZapLogger(root, filename, options, attrs...) + return logger, control.Close } diff --git a/web/src/view/systemTools/system/system.vue b/web/src/view/systemTools/system/system.vue index 8398790..9b02d45 100644 --- a/web/src/view/systemTools/system/system.vue +++ b/web/src/view/systemTools/system/system.vue @@ -50,6 +50,9 @@ + + +
@@ -226,7 +229,7 @@ routerPrefix: '', jwt: { signingKey: '******', expiresTime: '168h', bufferTime: '24h', issuer: 'kra' }, captcha: { keyLong: 6, imgWidth: 240, imgHeight: 80, storeExpiration: '3m' }, - local: { storePath: 'uploads/file', pathPrefix: 'uploads/file' }, media: { sessionTtl: 24 }, + local: { storePath: 'uploads/file', pathPrefix: 'uploads/file' }, media: { sessionTtl: 24, maxFileSize: 0 }, zap: { level: 'info', prefix: '[kra] ', format: 'json', director: 'logs', encode_level: 'LowercaseLevelEncoder', stacktrace_key: 'stacktrace', show_line: true, log_in_console: true, retention_day: 7, access_req_body: true, access_resp_data: true, access_req_headers: false, access_log_max_bytes: 32768, file_only_modules: [] }, cors: { mode: 'whitelist', whitelist: [] }, app: { node: '', app_id: 'kra', env: 'development' }, system: { useRedis: false, useMultipoint: false, useStrictAuth: false, disableAutoMigrate: false, useMongo: false },