This commit is contained in:
yvan 2026-08-15 01:12:04 +08:00
parent f63acae353
commit 6698e59c3a
45 changed files with 725 additions and 415 deletions

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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()

View File

@ -47,6 +47,7 @@ admin:
path_prefix: uploads/file
media:
session_ttl: 24
max_file_size: 0
system:
use_redis: false
use_multipoint: false

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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, &copy); err == nil {
return nil, &copy, 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)

View File

@ -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

View File

@ -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" +

View File

@ -126,6 +126,7 @@ message AdminBackend {
message Media {
int32 session_ttl = 1;
int64 max_file_size = 2;
}
message Disk {

View File

@ -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 {

View File

@ -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) {

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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 != "" {

View File

@ -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})

View File

@ -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)

View File

@ -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{}, "作废成功")
}

View File

@ -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{}, "创建成功")
}

View File

@ -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 {

View File

@ -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, "获取失败")

View File

@ -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)
}
}

View File

@ -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{}, "已取消")
}

View File

@ -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{}, "设置成功")
}

View File

@ -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{}, "设置成功")
}

View File

@ -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}, "获取成功")

View File

@ -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{}, "删除成功")
}

View File

@ -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作废成功")
}

View File

@ -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{}, "设置成功")

View File

@ -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) {

View File

@ -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) {

View File

@ -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)

View File

@ -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)

View File

@ -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: "更新岗位"},

View File

@ -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))

View File

@ -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
}

View File

@ -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"`

View File

@ -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"`

View File

@ -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"`

View File

@ -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}
}

View File

@ -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)

View File

@ -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
}

View File

@ -50,6 +50,9 @@
<el-form-item label="大文件上传会话有效期(小时)">
<el-input-number v-model="config.admin.media.sessionTtl" :min="1" :max="720" class="!w-full" />
</el-form-item>
<el-form-item label="单文件最大字节数0 为不限)">
<el-input-number v-model="config.admin.media.maxFileSize" :min="0" class="!w-full" />
</el-form-item>
<el-form-item label="运行开关" class="md:col-span-2">
<div class="flex flex-wrap gap-5">
<el-switch v-model="config.admin.system.useRedis" active-text="启用 Redis" />
@ -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 },