首次提交:初始化项目代码
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
package file
|
||||
|
||||
// UploadResponse represents the response body for successful file upload.
|
||||
// UploadResponse 表示成功上传文件的响应体。
|
||||
//
|
||||
// Fields:
|
||||
// - Message: A success message.
|
||||
// - File: The storage path of the uploaded file.
|
||||
// - Size: The file size in bytes.
|
||||
//
|
||||
// 字段:
|
||||
// - Message: 成功消息。
|
||||
// - File: 上传文件的存储路径。
|
||||
// - Size: 文件大小(字节)。
|
||||
type UploadResponse struct {
|
||||
Message string `json:"message"`
|
||||
File string `json:"file"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// FileItem represents a single file in the listing.
|
||||
// FileItem 表示列表中的单个文件。
|
||||
//
|
||||
// Fields:
|
||||
// - Key: The storage key/path of the file.
|
||||
// - Size: The file size in bytes.
|
||||
// - LastModified: The timestamp when the file was last modified.
|
||||
//
|
||||
// 字段:
|
||||
// - Key: 文件的存储键/路径。
|
||||
// - Size: 文件大小(字节)。
|
||||
// - LastModified: 文件最后修改的时间戳。
|
||||
type FileItem struct {
|
||||
Key string `json:"key"`
|
||||
Size int64 `json:"size"`
|
||||
LastModified string `json:"last_modified"`
|
||||
}
|
||||
|
||||
// ListResponse represents the response body for file listing.
|
||||
// ListResponse 表示文件列表的响应体。
|
||||
//
|
||||
// Fields:
|
||||
// - Files: A list of file items with metadata.
|
||||
//
|
||||
// 字段:
|
||||
// - Files: 包含元数据的文件项列表。
|
||||
type ListResponse struct {
|
||||
Files []FileItem `json:"files"`
|
||||
}
|
||||
|
||||
// DeleteResponse represents the response body for successful file deletion.
|
||||
// DeleteResponse 表示成功删除文件的响应体。
|
||||
//
|
||||
// Fields:
|
||||
// - Message: A success message.
|
||||
//
|
||||
// 字段:
|
||||
// - Message: 成功消息。
|
||||
type DeleteResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// FavoriteResponse represents the response body for favorite operations.
|
||||
// FavoriteResponse 表示收藏操作的响应体。
|
||||
//
|
||||
// Fields:
|
||||
// - Message: A success message.
|
||||
// - FileKey: The file key that was favorited/unfavorited.
|
||||
//
|
||||
// 字段:
|
||||
// - Message: 成功消息。
|
||||
// - FileKey: 被收藏/取消收藏的文件键。
|
||||
type FavoriteResponse struct {
|
||||
Message string `json:"message"`
|
||||
FileKey string `json:"file_key"`
|
||||
}
|
||||
|
||||
// FavoriteListResponse represents the response body for favorite list.
|
||||
// FavoriteListResponse 表示收藏列表的响应体。
|
||||
//
|
||||
// Fields:
|
||||
// - Favorites: A list of favorite file information.
|
||||
//
|
||||
// 字段:
|
||||
// - Favorites: 收藏文件信息列表。
|
||||
type FavoriteListResponse struct {
|
||||
Favorites []FavoriteItem `json:"favorites"`
|
||||
}
|
||||
|
||||
// FavoriteItem represents a single favorite file.
|
||||
// FavoriteItem 表示单个收藏文件。
|
||||
//
|
||||
// Fields:
|
||||
// - FileKey: The storage key of the file.
|
||||
// - FileName: The original filename.
|
||||
// - CreatedAt: Timestamp when the file was favorited.
|
||||
//
|
||||
// 字段:
|
||||
// - FileKey: 文件的存储键。
|
||||
// - FileName: 原始文件名。
|
||||
// - CreatedAt: 文件被收藏的时间戳。
|
||||
type FavoriteItem struct {
|
||||
FileKey string `json:"file_key"`
|
||||
FileName string `json:"file_name"`
|
||||
Size int64 `json:"size"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// RecycleBinResponse represents the response body for recycle bin list.
|
||||
// RecycleBinResponse 表示回收站列表的响应体。
|
||||
//
|
||||
// Fields:
|
||||
// - Files: A list of deleted file information.
|
||||
//
|
||||
// 字段:
|
||||
// - Files: 已删除文件信息列表。
|
||||
type RecycleBinResponse struct {
|
||||
Files []DeletedFileItem `json:"files"`
|
||||
}
|
||||
|
||||
// DeletedFileItem represents a single deleted file.
|
||||
// DeletedFileItem 表示单个已删除文件。
|
||||
//
|
||||
// Fields:
|
||||
// - FileKey: The storage key of the file.
|
||||
// - FileName: The original filename.
|
||||
// - Size: The file size in bytes.
|
||||
// - DeletedAt: Timestamp when the file was deleted.
|
||||
//
|
||||
// 字段:
|
||||
// - FileKey: 文件的存储键。
|
||||
// - FileName: 原始文件名。
|
||||
// - Size: 文件大小(字节)。
|
||||
// - DeletedAt: 文件被删除的时间戳。
|
||||
type DeletedFileItem struct {
|
||||
FileKey string `json:"file_key"`
|
||||
FileName string `json:"file_name"`
|
||||
Size int64 `json:"size"`
|
||||
DeletedAt string `json:"deleted_at"`
|
||||
}
|
||||
|
||||
// PreviewResponse represents the response body for file preview info.
|
||||
// PreviewResponse 表示文件预览信息的响应体。
|
||||
//
|
||||
// Fields:
|
||||
// - FileType: The type of file (image, text, pdf, other).
|
||||
//
|
||||
// 字段:
|
||||
// - FileType: 文件类型(image, text, pdf, other)。
|
||||
type PreviewResponse struct {
|
||||
FileType string `json:"file_type"`
|
||||
}
|
||||
@@ -0,0 +1,627 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"cloudnest/internal/pkg/errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cloudnest/internal/domain/file/entity"
|
||||
"cloudnest/internal/domain/file/repository"
|
||||
infrastructureRepo "cloudnest/internal/infrastructure/repository"
|
||||
)
|
||||
|
||||
// FileService provides file management operations.
|
||||
// FileService 提供文件管理操作。
|
||||
//
|
||||
// This service implements the application use cases for file upload, download, list, and delete.
|
||||
// It organizes files by username, creating user-specific directories in the object storage.
|
||||
//
|
||||
// 此服务实现了文件上传、下载、列表和删除的应用用例。
|
||||
// 它按用户名组织文件,在对象存储中创建用户特定的目录。
|
||||
type FileService struct {
|
||||
fileRepo repository.FileRepository
|
||||
fileMetaRepo *infrastructureRepo.FileMetaRepository
|
||||
bucket string
|
||||
}
|
||||
|
||||
// NewFileService creates a new FileService instance.
|
||||
// NewFileService 创建一个新的 FileService 实例。
|
||||
//
|
||||
// Parameters:
|
||||
// - fileRepo: The file repository for storage operations.
|
||||
// - bucket: The default bucket name for file storage.
|
||||
//
|
||||
// Returns:
|
||||
// - *FileService: A pointer to the new FileService instance.
|
||||
//
|
||||
// 参数:
|
||||
// - fileRepo: 文件存储操作的仓储。
|
||||
// - bucket: 文件存储的默认桶名称。
|
||||
//
|
||||
// 返回值:
|
||||
// - *FileService: 新创建的 FileService 实例指针。
|
||||
func NewFileService(fileRepo repository.FileRepository, fileMetaRepo *infrastructureRepo.FileMetaRepository, bucket string) *FileService {
|
||||
return &FileService{
|
||||
fileRepo: fileRepo,
|
||||
fileMetaRepo: fileMetaRepo,
|
||||
bucket: bucket,
|
||||
}
|
||||
}
|
||||
|
||||
// userObject generates the storage key for a user's file.
|
||||
// userObject 为用户文件生成存储键。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The user's username.
|
||||
// - name: The filename.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The storage key in format "username/filename".
|
||||
//
|
||||
// 参数:
|
||||
// - username: 用户的用户名。
|
||||
// - name: 文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 存储键,格式为 "username/filename"。
|
||||
func userObject(userCode, name string) string {
|
||||
return fmt.Sprintf("%s/%s", userCode, name)
|
||||
}
|
||||
|
||||
// Upload uploads a file to the user's directory.
|
||||
// Upload 将文件上传到用户的目录。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The owner of the file.
|
||||
// - filename: The original filename.
|
||||
// - reader: The file content reader.
|
||||
// - size: The file size in bytes.
|
||||
// - contentType: The MIME type of the file.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the upload fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 文件的所有者。
|
||||
// - filename: 原始文件名。
|
||||
// - reader: 文件内容读取器。
|
||||
// - size: 文件大小(字节)。
|
||||
// - contentType: 文件的 MIME 类型。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果上传失败则返回错误。
|
||||
func (s *FileService) Upload(userCode, filename string, reader io.Reader, size int64, contentType string) error {
|
||||
return s.fileRepo.Upload(s.bucket, userObject(userCode, filename), reader, size, contentType)
|
||||
}
|
||||
|
||||
// List retrieves all files for a user.
|
||||
// List 获取用户的所有文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The user's username.
|
||||
//
|
||||
// Returns:
|
||||
// - []string: A list of file keys belonging to the user.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 用户的用户名。
|
||||
//
|
||||
// 返回值:
|
||||
// - []string: 用户所属的文件键列表。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) List(userCode string) ([]repository.FileInfo, error) {
|
||||
fileInfos, err := s.fileRepo.List(s.bucket, userCode+"/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deletedKeys, err := s.fileMetaRepo.GetDeletedFileKeys(userCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deletedSet := make(map[string]bool)
|
||||
for _, key := range deletedKeys {
|
||||
deletedSet[key] = true
|
||||
}
|
||||
|
||||
var filtered []repository.FileInfo
|
||||
for _, info := range fileInfos {
|
||||
if !deletedSet[info.Key] {
|
||||
filtered = append(filtered, info)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// PresignDownload generates a pre-signed URL for file download.
|
||||
// PresignDownload 生成用于文件下载的预签名 URL。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The file owner's username.
|
||||
// - filename: The name of the file to download.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The pre-signed download URL.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 文件所有者的用户名。
|
||||
// - filename: 要下载的文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 预签名下载 URL。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) PresignDownload(userCode, filename string) (string, error) {
|
||||
return s.fileRepo.PresignDownload(s.bucket, userObject(userCode, filename))
|
||||
}
|
||||
|
||||
// GetFileStream retrieves a file as a readable stream for proxying to the client.
|
||||
// GetFileStream 获取文件的可读流,用于代理给客户端。
|
||||
//
|
||||
// This method allows the Go backend to stream file content directly to the HTTP response,
|
||||
// avoiding CORS issues with MinIO and keeping MinIO internal to the server.
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The file owner's username.
|
||||
// - filename: The name of the file to stream.
|
||||
//
|
||||
// Returns:
|
||||
// - io.ReadCloser: A readable stream of the file content. Caller must close it.
|
||||
// - int64: The file size in bytes.
|
||||
// - string: The Content-Type of the file.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 此方法允许 Go 后端将文件内容直接流式传输到 HTTP 响应,
|
||||
// 避免 MinIO 的 CORS 问题,并保持 MinIO 对服务器内部可见。
|
||||
//
|
||||
// 参数:
|
||||
// - username: 文件所有者的用户名。
|
||||
// - filename: 要流式传输的文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - io.ReadCloser: 文件内容的可读流。调用方必须关闭它。
|
||||
// - int64: 文件大小(字节)。
|
||||
// - string: 文件的 Content-Type。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) GetFileStream(userCode, filename string) (io.ReadCloser, int64, string, error) {
|
||||
return s.fileRepo.GetObject(s.bucket, userObject(userCode, filename))
|
||||
}
|
||||
|
||||
// Delete removes a file from the user's directory.
|
||||
// Delete 从用户的目录中删除文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The file owner's username.
|
||||
// - filename: The name of the file to delete.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the deletion fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 文件所有者的用户名。
|
||||
// - filename: 要删除的文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果删除失败则返回错误。
|
||||
func (s *FileService) Delete(userCode, filename string) error {
|
||||
return s.fileRepo.Delete(s.bucket, userObject(userCode, filename))
|
||||
}
|
||||
|
||||
// SoftDelete moves a file to the recycle bin instead of permanent deletion.
|
||||
// SoftDelete 将文件移动到回收站而不是永久删除。
|
||||
//
|
||||
// Parameters:
|
||||
// - userID: The user's ID.
|
||||
// - username: The user's username.
|
||||
// - filename: The name of the file to delete.
|
||||
// - size: The file size in bytes.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - userID: 用户的 ID。
|
||||
// - username: 用户的用户名。
|
||||
// - filename: 要删除的文件名。
|
||||
// - size: 文件大小(字节)。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) SoftDelete(userID uint, userCode, filename string, _ int64) error {
|
||||
fileKey := userObject(userCode, filename)
|
||||
|
||||
size, _, err := s.fileRepo.GetObjectInfo(s.bucket, fileKey)
|
||||
if err != nil {
|
||||
return errors.InternalError("获取文件信息失败", err)
|
||||
}
|
||||
|
||||
s.fileMetaRepo.RemoveFavorite(userCode, fileKey)
|
||||
|
||||
deletedFile := entity.NewDeletedFile(userID, userCode, fileKey, filename, size)
|
||||
return s.fileMetaRepo.AddToRecycleBin(deletedFile)
|
||||
}
|
||||
|
||||
// Favorite adds a file to user's favorites.
|
||||
// Favorite 将文件添加到用户的收藏夹。
|
||||
//
|
||||
// Parameters:
|
||||
// - userID: The user's ID.
|
||||
// - username: The user's username.
|
||||
// - filename: The name of the file to favorite.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - userID: 用户的 ID。
|
||||
// - username: 用户的用户名。
|
||||
// - filename: 要收藏的文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) Favorite(userID uint, userCode, filename string) error {
|
||||
fileKey := userObject(userCode, filename)
|
||||
|
||||
size, _, err := s.fileRepo.GetObjectInfo(s.bucket, fileKey)
|
||||
if err != nil {
|
||||
return errors.InternalError("获取文件信息失败", err)
|
||||
}
|
||||
|
||||
favorite := entity.NewFavorite(userID, userCode, fileKey, size)
|
||||
return s.fileMetaRepo.AddFavorite(favorite)
|
||||
}
|
||||
|
||||
// Unfavorite removes a file from user's favorites.
|
||||
// Unfavorite 从用户的收藏夹中移除文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The user's username.
|
||||
// - filename: The name of the file to unfavorite.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 用户的用户名。
|
||||
// - filename: 要取消收藏的文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) Unfavorite(userCode, filename string) error {
|
||||
fileKey := userObject(userCode, filename)
|
||||
return s.fileMetaRepo.RemoveFavorite(userCode, fileKey)
|
||||
}
|
||||
|
||||
// GetFavorites retrieves all favorites for a user.
|
||||
// GetFavorites 获取用户的所有收藏。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The user's username.
|
||||
//
|
||||
// Returns:
|
||||
// - []entity.Favorite: A list of Favorite entities.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 用户的用户名。
|
||||
//
|
||||
// 返回值:
|
||||
// - []entity.Favorite: Favorite 实体列表。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) GetFavorites(userCode string) ([]entity.Favorite, error) {
|
||||
favorites, err := s.fileMetaRepo.GetFavorites(userCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deletedKeys, err := s.fileMetaRepo.GetDeletedFileKeys(userCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deletedSet := make(map[string]bool)
|
||||
for _, key := range deletedKeys {
|
||||
deletedSet[key] = true
|
||||
}
|
||||
|
||||
var filtered []entity.Favorite
|
||||
for _, f := range favorites {
|
||||
if !deletedSet[f.FileKey] {
|
||||
filtered = append(filtered, f)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// GetRecycleBin retrieves all deleted files for a user.
|
||||
// GetRecycleBin 获取用户的所有已删除文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The user's username.
|
||||
//
|
||||
// Returns:
|
||||
// - []entity.DeletedFile: A list of DeletedFile entities.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 用户的用户名。
|
||||
//
|
||||
// 返回值:
|
||||
// - []entity.DeletedFile: DeletedFile 实体列表。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) GetRecycleBin(userCode string) ([]entity.DeletedFile, error) {
|
||||
deletedFiles, err := s.fileMetaRepo.GetRecycleBin(userCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range deletedFiles {
|
||||
if deletedFiles[i].Size == 0 {
|
||||
size, _, err := s.fileRepo.GetObjectInfo(s.bucket, deletedFiles[i].FileKey)
|
||||
if err == nil {
|
||||
deletedFiles[i].Size = size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deletedFiles, nil
|
||||
}
|
||||
|
||||
// RestoreFromRecycleBin restores a file from the recycle bin.
|
||||
// RestoreFromRecycleBin 从回收站恢复文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The user's username.
|
||||
// - filename: The name of the file to restore.
|
||||
//
|
||||
// Returns:
|
||||
// - *entity.DeletedFile: The restored DeletedFile entity.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 用户的用户名。
|
||||
// - filename: 要恢复的文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - *entity.DeletedFile: 恢复的 DeletedFile 实体。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) RestoreFromRecycleBin(userCode, filename string) (*entity.DeletedFile, error) {
|
||||
fileKey := userObject(userCode, filename)
|
||||
return s.fileMetaRepo.RestoreFromRecycleBin(userCode, fileKey)
|
||||
}
|
||||
|
||||
// DeleteFromRecycleBin permanently deletes a file from the recycle bin.
|
||||
// DeleteFromRecycleBin 从回收站永久删除文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The user's username.
|
||||
// - filename: The name of the file to permanently delete.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 用户的用户名。
|
||||
// - filename: 要永久删除的文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) DeleteFromRecycleBin(userCode, filename string) error {
|
||||
fileKey := userObject(userCode, filename)
|
||||
if err := s.fileMetaRepo.DeleteFromRecycleBin(userCode, fileKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.fileRepo.Delete(s.bucket, fileKey)
|
||||
}
|
||||
|
||||
// PresignPreview generates a pre-signed URL for file preview.
|
||||
// PresignPreview 生成用于文件预览的预签名 URL。
|
||||
//
|
||||
// Parameters:
|
||||
// - username: The file owner's username.
|
||||
// - filename: The name of the file to preview.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The pre-signed preview URL.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - username: 文件所有者的用户名。
|
||||
// - filename: 要预览的文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 预签名预览 URL。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (s *FileService) PresignPreview(userCode, filename string) (string, error) {
|
||||
return s.fileRepo.PresignDownload(s.bucket, userObject(userCode, filename))
|
||||
}
|
||||
|
||||
// GetFileExtension returns the file extension without the dot.
|
||||
// GetFileExtension 返回文件扩展名(不含点)。
|
||||
//
|
||||
// Parameters:
|
||||
// - filename: The filename.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The file extension in lowercase.
|
||||
//
|
||||
// 参数:
|
||||
// - filename: 文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 小写的文件扩展名。
|
||||
func (s *FileService) GetFileExtension(filename string) string {
|
||||
idx := strings.LastIndex(filename, ".")
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
return strings.ToLower(filename[idx+1:])
|
||||
}
|
||||
|
||||
// IsImageFile checks if the file is an image based on its extension.
|
||||
// IsImageFile 根据文件扩展名检查是否为图片文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - filename: The filename.
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if the file is an image.
|
||||
//
|
||||
// 参数:
|
||||
// - filename: 文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: 如果是图片文件则返回 true。
|
||||
func (s *FileService) IsImageFile(filename string) bool {
|
||||
ext := s.GetFileExtension(filename)
|
||||
imageExts := map[string]bool{
|
||||
"jpg": true,
|
||||
"jpeg": true,
|
||||
"png": true,
|
||||
"gif": true,
|
||||
"bmp": true,
|
||||
"webp": true,
|
||||
"svg": true,
|
||||
}
|
||||
return imageExts[ext]
|
||||
}
|
||||
|
||||
// IsTextFile checks if the file is a text file based on its extension.
|
||||
// IsTextFile 根据文件扩展名检查是否为文本文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - filename: The filename.
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if the file is a text file.
|
||||
//
|
||||
// 参数:
|
||||
// - filename: 文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: 如果是文本文件则返回 true。
|
||||
func (s *FileService) IsTextFile(filename string) bool {
|
||||
ext := s.GetFileExtension(filename)
|
||||
textExts := map[string]bool{
|
||||
"txt": true,
|
||||
"md": true,
|
||||
"json": true,
|
||||
"xml": true,
|
||||
"html": true,
|
||||
"css": true,
|
||||
"js": true,
|
||||
"go": true,
|
||||
"py": true,
|
||||
"java": true,
|
||||
"cpp": true,
|
||||
"c": true,
|
||||
}
|
||||
return textExts[ext]
|
||||
}
|
||||
|
||||
// IsPDFFile checks if the file is a PDF file based on its extension.
|
||||
// IsPDFFile 根据文件扩展名检查是否为 PDF 文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - filename: The filename.
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if the file is a PDF.
|
||||
//
|
||||
// 参数:
|
||||
// - filename: 文件名。
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: 如果是 PDF 文件则返回 true。
|
||||
func (s *FileService) IsPDFFile(filename string) bool {
|
||||
ext := s.GetFileExtension(filename)
|
||||
return ext == "pdf"
|
||||
}
|
||||
|
||||
func (s *FileService) GetChunkDir() string {
|
||||
return filepath.Join(os.TempDir(), "cloudnest_chunks")
|
||||
}
|
||||
|
||||
func (s *FileService) SaveChunk(username, hash string, chunkIndex int, data []byte) error {
|
||||
chunkDir := filepath.Join(s.GetChunkDir(), username, hash)
|
||||
if err := os.MkdirAll(chunkDir, 0755); err != nil {
|
||||
return errors.InternalError("创建分片目录失败", err)
|
||||
}
|
||||
|
||||
chunkPath := filepath.Join(chunkDir, strconv.Itoa(chunkIndex))
|
||||
if err := os.WriteFile(chunkPath, data, 0644); err != nil {
|
||||
return errors.InternalError("保存分片失败", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileService) GetUploadedChunks(username, hash string) ([]int, error) {
|
||||
chunkDir := filepath.Join(s.GetChunkDir(), username, hash)
|
||||
entries, err := os.ReadDir(chunkDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return []int{}, nil
|
||||
}
|
||||
return nil, errors.InternalError("读取分片目录失败", err)
|
||||
}
|
||||
|
||||
var chunks []int
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
idx, err := strconv.Atoi(entry.Name())
|
||||
if err == nil {
|
||||
chunks = append(chunks, idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chunks, nil
|
||||
}
|
||||
|
||||
func (s *FileService) MergeChunks(username, hash, filename string, chunkCount int, fileSize int64) error {
|
||||
chunkDir := filepath.Join(s.GetChunkDir(), username, hash)
|
||||
|
||||
destPath := filepath.Join(chunkDir, "merged_"+filename)
|
||||
destFile, err := os.Create(destPath)
|
||||
if err != nil {
|
||||
return errors.InternalError("创建合并文件失败", err)
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
for i := 0; i < chunkCount; i++ {
|
||||
chunkPath := filepath.Join(chunkDir, strconv.Itoa(i))
|
||||
chunkData, err := os.ReadFile(chunkPath)
|
||||
if err != nil {
|
||||
return errors.InternalError("读取分片失败", err)
|
||||
}
|
||||
|
||||
if _, err := destFile.Write(chunkData); err != nil {
|
||||
return errors.InternalError("写入合并文件失败", err)
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(destPath)
|
||||
if err != nil {
|
||||
return errors.InternalError("打开合并文件失败", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
contentType := "application/octet-stream"
|
||||
|
||||
if err := s.Upload(username, filename, file, fileSize, contentType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
os.RemoveAll(chunkDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user