feat(server): implement chat file TTL cleanup and enhance JPush vendor channel support
Add FileCleanupWorker for automatic expiration of chat files with configurable retention period and batch processing. Files uploaded via `/api/v1/uploads/files` are tracked in `uploaded_files` table with expiration timestamps, then deleted from S3 and database upon expiry. Expired file URLs are injected as `"expired": true` in message responses. Extend JPush push notification configuration with vendor-specific channel_id support (xiaomi, huawei, oppo, vivo, meizu, honor, fcm) for differentiating system vs chat notifications. Add iOS APNs thread-id grouping configuration for notification categorization.
This commit is contained in:
91
internal/repository/uploaded_file_repo.go
Normal file
91
internal/repository/uploaded_file_repo.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"with_you/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UploadedFileRepository 文件上传记录仓储接口
|
||||
type UploadedFileRepository interface {
|
||||
// Create 写入一条上传记录
|
||||
Create(ctx context.Context, file *model.UploadedFile) error
|
||||
// GetByURL 按 URL 查询记录(DTO 失效标记用)
|
||||
GetByURL(ctx context.Context, url string) (*model.UploadedFile, error)
|
||||
// ListPendingCleanup 取 created_at < before 且 expired=false 的记录,分批
|
||||
ListPendingCleanup(ctx context.Context, before time.Time, limit int) ([]model.UploadedFile, error)
|
||||
// MarkExpired 标记某条记录已清理
|
||||
MarkExpired(ctx context.Context, id uint, at time.Time) error
|
||||
// GetExpiredURLSet 批量查询已标记过期的 URL 集合(DTO 注入用)
|
||||
// 注意:这是全量扫描 expired=true 的记录,适用于上传量不大的场景;
|
||||
// 若 uploaded_files 表增长过大,可改为按时间窗口分页。
|
||||
GetExpiredURLSet(ctx context.Context) (map[string]struct{}, error)
|
||||
}
|
||||
|
||||
// uploadedFileRepository 文件上传记录仓储实现
|
||||
type uploadedFileRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUploadedFileRepository 创建文件上传记录仓储
|
||||
func NewUploadedFileRepository(db *gorm.DB) UploadedFileRepository {
|
||||
return &uploadedFileRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 写入一条上传记录
|
||||
func (r *uploadedFileRepository) Create(ctx context.Context, file *model.UploadedFile) error {
|
||||
return r.db.WithContext(ctx).Create(file).Error
|
||||
}
|
||||
|
||||
// GetByURL 按 URL 查询记录
|
||||
func (r *uploadedFileRepository) GetByURL(ctx context.Context, url string) (*model.UploadedFile, error) {
|
||||
var file model.UploadedFile
|
||||
if err := r.db.WithContext(ctx).Where("url = ?", url).First(&file).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
// ListPendingCleanup 取待清理记录
|
||||
func (r *uploadedFileRepository) ListPendingCleanup(ctx context.Context, before time.Time, limit int) ([]model.UploadedFile, error) {
|
||||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
var files []model.UploadedFile
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("created_at < ? AND expired = ?", before, false).
|
||||
Order("created_at ASC").
|
||||
Limit(limit).
|
||||
Find(&files).Error
|
||||
return files, err
|
||||
}
|
||||
|
||||
// MarkExpired 标记已清理
|
||||
func (r *uploadedFileRepository) MarkExpired(ctx context.Context, id uint, at time.Time) error {
|
||||
return r.db.WithContext(ctx).Model(&model.UploadedFile{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"expired": true,
|
||||
"expired_at": at,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// GetExpiredURLSet 批量查询已过期 URL 集合
|
||||
func (r *uploadedFileRepository) GetExpiredURLSet(ctx context.Context) (map[string]struct{}, error) {
|
||||
var urls []string
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&model.UploadedFile{}).
|
||||
Where("expired = ?", true).
|
||||
Pluck("url", &urls).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set := make(map[string]struct{}, len(urls))
|
||||
for _, u := range urls {
|
||||
set[u] = struct{}{}
|
||||
}
|
||||
return set, nil
|
||||
}
|
||||
Reference in New Issue
Block a user