This commit is contained in:
2026-08-15 01:12:04 +08:00
parent f63acae353
commit 6698e59c3a
45 changed files with 725 additions and 415 deletions
+3 -20
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) {
+23 -1
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 {
+29 -18
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
}
+15 -1
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