首次提交:初始化项目代码
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"cloudnest/internal/domain/auth/entity"
|
||||
fileentity "cloudnest/internal/domain/file/entity"
|
||||
"cloudnest/internal/pkg/logger"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
gormlogger "gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// MySQLClient provides MySQL database operations.
|
||||
// MySQLClient 提供 MySQL 数据库操作。
|
||||
//
|
||||
// This struct wraps the GORM database connection and provides methods
|
||||
// for database initialization and migration.
|
||||
//
|
||||
// 此结构体封装了 GORM 数据库连接,并提供了数据库初始化和迁移的方法。
|
||||
type MySQLClient struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
// NewMySQLClient creates a new MySQL client.
|
||||
// NewMySQLClient 创建一个新的 MySQL 客户端。
|
||||
//
|
||||
// Parameters:
|
||||
// - dsn: The MySQL data source name in format "user:password@tcp(host:port)/database".
|
||||
//
|
||||
// Returns:
|
||||
// - *MySQLClient: A pointer to the new MySQLClient instance.
|
||||
// - error: An error if the connection fails.
|
||||
//
|
||||
// Note:
|
||||
// - The connection is established immediately.
|
||||
// - GORM logging is enabled at Info level.
|
||||
//
|
||||
// 参数:
|
||||
// - dsn: MySQL 数据源名称,格式为 "user:password@tcp(host:port)/database"。
|
||||
//
|
||||
// 返回值:
|
||||
// - *MySQLClient: 新创建的 MySQLClient 实例指针。
|
||||
// - error: 如果连接失败则返回错误。
|
||||
//
|
||||
// 注意:
|
||||
// - 连接会立即建立。
|
||||
// - GORM 日志级别为 Info。
|
||||
func NewMySQLClient(dsn string) (*MySQLClient, error) {
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: gormlogger.Default.LogMode(gormlogger.Info),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("mysql connected")
|
||||
return &MySQLClient{DB: db}, nil
|
||||
}
|
||||
|
||||
// Migrate runs database migrations for all registered models.
|
||||
// Migrate 对所有注册的模型运行数据库迁移。
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if migration fails.
|
||||
//
|
||||
// Note:
|
||||
// - This method should be called once during application startup.
|
||||
// - It automatically creates or updates database tables based on model definitions.
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果迁移失败则返回错误。
|
||||
//
|
||||
// 注意:
|
||||
// - 此方法应在应用程序启动时调用一次。
|
||||
// - 它会根据模型定义自动创建或更新数据库表。
|
||||
func (c *MySQLClient) Migrate() error {
|
||||
if err := c.DB.AutoMigrate(&entity.User{}, &fileentity.Favorite{}, &fileentity.DeletedFile{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.setTableComment("users", "用户表")
|
||||
c.setTableComment("favorites", "用户收藏表")
|
||||
c.setTableComment("deleted_files", "回收站文件表")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLClient) setTableComment(tableName, comment string) {
|
||||
sql := "ALTER TABLE `" + tableName + "` COMMENT = '" + comment + "'"
|
||||
if err := c.DB.Exec(sql).Error; err != nil {
|
||||
logger.Error("failed to set table comment", "table", tableName, "error", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"cloudnest/internal/pkg/logger"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
// RedisClient provides Redis cache operations.
|
||||
// RedisClient 提供 Redis 缓存操作。
|
||||
//
|
||||
// This struct wraps the go-redis client and provides methods for cache operations.
|
||||
//
|
||||
// 此结构体封装了 go-redis 客户端,并提供了缓存操作的方法。
|
||||
type RedisClient struct {
|
||||
Client *redis.Client
|
||||
}
|
||||
|
||||
// NewRedisClient creates a new Redis client.
|
||||
// NewRedisClient 创建一个新的 Redis 客户端。
|
||||
//
|
||||
// Parameters:
|
||||
// - addr: The Redis server address (host:port).
|
||||
// - password: The Redis password (empty string for no password).
|
||||
// - db: The Redis database number (0-15).
|
||||
//
|
||||
// Returns:
|
||||
// - *RedisClient: A pointer to the new RedisClient instance.
|
||||
// - error: An error if the connection fails.
|
||||
//
|
||||
// Note:
|
||||
// - A PING command is sent to verify the connection.
|
||||
//
|
||||
// 参数:
|
||||
// - addr: Redis 服务器地址(host:port)。
|
||||
// - password: Redis 密码(无密码时为空字符串)。
|
||||
// - db: Redis 数据库编号(0-15)。
|
||||
//
|
||||
// 返回值:
|
||||
// - *RedisClient: 新创建的 RedisClient 实例指针。
|
||||
// - error: 如果连接失败则返回错误。
|
||||
//
|
||||
// 注意:
|
||||
// - 会发送 PING 命令来验证连接。
|
||||
func NewRedisClient(addr, password string, db int) (*RedisClient, error) {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: password,
|
||||
DB: db,
|
||||
})
|
||||
|
||||
_, err := client.Ping(client.Context()).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("redis connected")
|
||||
return &RedisClient{Client: client}, nil
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"cloudnest/internal/domain/file/entity"
|
||||
"cloudnest/internal/infrastructure/database"
|
||||
"cloudnest/internal/pkg/errors"
|
||||
)
|
||||
|
||||
// FileMetaRepository provides database operations for file metadata.
|
||||
// FileMetaRepository 提供文件元数据的数据库操作。
|
||||
//
|
||||
// This repository handles operations related to file favorites and recycle bin,
|
||||
// storing data in MySQL database.
|
||||
//
|
||||
// 此仓储处理文件收藏和回收站相关操作,数据存储在 MySQL 数据库中。
|
||||
type FileMetaRepository struct {
|
||||
db *database.MySQLClient
|
||||
}
|
||||
|
||||
// NewFileMetaRepository creates a new FileMetaRepository.
|
||||
// NewFileMetaRepository 创建一个新的 FileMetaRepository。
|
||||
//
|
||||
// Parameters:
|
||||
// - db: The MySQL client for database operations.
|
||||
//
|
||||
// Returns:
|
||||
// - *FileMetaRepository: A pointer to the new FileMetaRepository instance.
|
||||
//
|
||||
// 参数:
|
||||
// - db: 用于数据库操作的 MySQL 客户端。
|
||||
//
|
||||
// 返回值:
|
||||
// - *FileMetaRepository: 新创建的 FileMetaRepository 实例指针。
|
||||
func NewFileMetaRepository(db *database.MySQLClient) *FileMetaRepository {
|
||||
return &FileMetaRepository{db: db}
|
||||
}
|
||||
|
||||
// AddFavorite adds a file to user's favorites.
|
||||
// AddFavorite 将文件添加到用户的收藏夹。
|
||||
//
|
||||
// Parameters:
|
||||
// - favorite: The Favorite entity to save.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - favorite: 要保存的 Favorite 实体。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *FileMetaRepository) AddFavorite(favorite *entity.Favorite) error {
|
||||
var exists entity.Favorite
|
||||
if err := r.db.DB.Where("user_code = ? AND file_key = ?", favorite.UserCode, favorite.FileKey).First(&exists).Error; err == nil {
|
||||
return errors.BadRequest("文件已在收藏夹中")
|
||||
}
|
||||
if err := r.db.DB.Create(favorite).Error; err != nil {
|
||||
return errors.InternalError("添加收藏失败", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveFavorite removes a file from user's favorites.
|
||||
// RemoveFavorite 从用户的收藏夹中移除文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - userCode: The user's userCode.
|
||||
// - fileKey: The file storage key.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - userCode: 用户的用户代码。
|
||||
// - fileKey: 文件的存储键。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *FileMetaRepository) RemoveFavorite(userCode, fileKey string) error {
|
||||
result := r.db.DB.Where("user_code = ? AND file_key = ?", userCode, fileKey).Delete(&entity.Favorite{})
|
||||
if result.Error != nil {
|
||||
return errors.InternalError("取消收藏失败", result.Error)
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.NotFound("收藏记录不存在")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFavorites retrieves all favorites for a user.
|
||||
// GetFavorites 获取用户的所有收藏。
|
||||
//
|
||||
// Parameters:
|
||||
// - userCode: The user's userCode.
|
||||
//
|
||||
// Returns:
|
||||
// - []entity.Favorite: A list of Favorite entities.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - userCode: 用户的用户代码。
|
||||
//
|
||||
// 返回值:
|
||||
// - []entity.Favorite: Favorite 实体列表。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *FileMetaRepository) GetFavorites(userCode string) ([]entity.Favorite, error) {
|
||||
var favorites []entity.Favorite
|
||||
if err := r.db.DB.Where("user_code = ?", userCode).Order("created_at DESC").Find(&favorites).Error; err != nil {
|
||||
return nil, errors.InternalError("获取收藏列表失败", err)
|
||||
}
|
||||
return favorites, nil
|
||||
}
|
||||
|
||||
// AddToRecycleBin adds a file to the recycle bin.
|
||||
// AddToRecycleBin 将文件添加到回收站。
|
||||
//
|
||||
// Parameters:
|
||||
// - deletedFile: The DeletedFile entity to save.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - deletedFile: 要保存的 DeletedFile 实体。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *FileMetaRepository) AddToRecycleBin(deletedFile *entity.DeletedFile) error {
|
||||
if err := r.db.DB.Create(deletedFile).Error; err != nil {
|
||||
return errors.InternalError("添加到回收站失败", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreFromRecycleBin restores a file from the recycle bin.
|
||||
// RestoreFromRecycleBin 从回收站恢复文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - userCode: The user's userCode.
|
||||
// - fileKey: The file storage key.
|
||||
//
|
||||
// Returns:
|
||||
// - *entity.DeletedFile: The restored DeletedFile entity.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - userCode: 用户的用户代码。
|
||||
// - fileKey: 文件的存储键。
|
||||
//
|
||||
// 返回值:
|
||||
// - *entity.DeletedFile: 恢复的 DeletedFile 实体。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *FileMetaRepository) RestoreFromRecycleBin(userCode, fileKey string) (*entity.DeletedFile, error) {
|
||||
var deletedFile entity.DeletedFile
|
||||
if err := r.db.DB.Where("user_code = ? AND file_key = ?", userCode, fileKey).First(&deletedFile).Error; err != nil {
|
||||
return nil, errors.NotFound("回收站中不存在该文件")
|
||||
}
|
||||
if err := r.db.DB.Delete(&deletedFile).Error; err != nil {
|
||||
return nil, errors.InternalError("恢复文件失败", err)
|
||||
}
|
||||
return &deletedFile, nil
|
||||
}
|
||||
|
||||
// DeleteFromRecycleBin permanently deletes a file from the recycle bin.
|
||||
// DeleteFromRecycleBin 从回收站永久删除文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - userCode: The user's userCode.
|
||||
// - fileKey: The file storage key.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - userCode: 用户的用户代码。
|
||||
// - fileKey: 文件的存储键。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *FileMetaRepository) DeleteFromRecycleBin(userCode, fileKey string) error {
|
||||
result := r.db.DB.Where("user_code = ? AND file_key = ?", userCode, fileKey).Delete(&entity.DeletedFile{})
|
||||
if result.Error != nil {
|
||||
return errors.InternalError("永久删除失败", result.Error)
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.NotFound("回收站中不存在该文件")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRecycleBin retrieves all deleted files for a user.
|
||||
// GetRecycleBin 获取用户的所有已删除文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - userCode: The user's userCode.
|
||||
//
|
||||
// Returns:
|
||||
// - []entity.DeletedFile: A list of DeletedFile entities.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - userCode: 用户的用户代码。
|
||||
//
|
||||
// 返回值:
|
||||
// - []entity.DeletedFile: DeletedFile 实体列表。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *FileMetaRepository) GetRecycleBin(userCode string) ([]entity.DeletedFile, error) {
|
||||
var deletedFiles []entity.DeletedFile
|
||||
if err := r.db.DB.Where("user_code = ?", userCode).Order("deleted_at DESC").Find(&deletedFiles).Error; err != nil {
|
||||
return nil, errors.InternalError("获取回收站列表失败", err)
|
||||
}
|
||||
return deletedFiles, nil
|
||||
}
|
||||
|
||||
func (r *FileMetaRepository) GetDeletedFileKeys(userCode string) ([]string, error) {
|
||||
var keys []string
|
||||
if err := r.db.DB.Model(&entity.DeletedFile{}).Where("user_code = ?", userCode).Pluck("file_key", &keys).Error; err != nil {
|
||||
return nil, errors.InternalError("获取已删除文件键失败", err)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (r *FileMetaRepository) CleanExpiredRecycleBin(expireDays int) ([]entity.DeletedFile, error) {
|
||||
var expiredFiles []entity.DeletedFile
|
||||
if err := r.db.DB.Where("deleted_at < ?", time.Now().AddDate(0, 0, -expireDays)).Find(&expiredFiles).Error; err != nil {
|
||||
return nil, errors.InternalError("查询过期文件失败", err)
|
||||
}
|
||||
|
||||
if len(expiredFiles) == 0 {
|
||||
return []entity.DeletedFile{}, nil
|
||||
}
|
||||
|
||||
result := r.db.DB.Where("deleted_at < ?", time.Now().AddDate(0, 0, -expireDays)).Delete(&entity.DeletedFile{})
|
||||
if result.Error != nil {
|
||||
return nil, errors.InternalError("清理过期文件失败", result.Error)
|
||||
}
|
||||
|
||||
return expiredFiles, nil
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"cloudnest/internal/domain/file/repository"
|
||||
"cloudnest/internal/infrastructure/storage"
|
||||
"cloudnest/internal/pkg/errors"
|
||||
"github.com/minio/minio-go/v7"
|
||||
)
|
||||
|
||||
// fileRepository is the MinIO implementation of FileRepository.
|
||||
// fileRepository 是 FileRepository 的 MinIO 实现。
|
||||
//
|
||||
// This struct implements the FileRepository interface using MinIO for object storage operations.
|
||||
//
|
||||
// 此结构体使用 MinIO 实现了 FileRepository 接口,用于对象存储操作。
|
||||
type fileRepository struct {
|
||||
minio *storage.MinioClient
|
||||
}
|
||||
|
||||
// NewFileRepository creates a new file repository.
|
||||
// NewFileRepository 创建一个新的文件仓储。
|
||||
//
|
||||
// Parameters:
|
||||
// - minio: The MinIO client for storage operations.
|
||||
//
|
||||
// Returns:
|
||||
// - repository.FileRepository: A FileRepository implementation.
|
||||
//
|
||||
// 参数:
|
||||
// - minio: 用于存储操作的 MinIO 客户端。
|
||||
//
|
||||
// 返回值:
|
||||
// - repository.FileRepository: FileRepository 的实现。
|
||||
func NewFileRepository(minio *storage.MinioClient) repository.FileRepository {
|
||||
return &fileRepository{minio: minio}
|
||||
}
|
||||
|
||||
// Upload uploads a file to the object storage.
|
||||
// Upload 将文件上传到对象存储。
|
||||
//
|
||||
// Parameters:
|
||||
// - bucket: The target bucket name.
|
||||
// - objectName: The unique name/path for the object.
|
||||
// - reader: The file content reader.
|
||||
// - size: The size of the file in bytes.
|
||||
// - contentType: The MIME type of the file.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - bucket: 目标桶名称。
|
||||
// - objectName: 对象的唯一名称/路径。
|
||||
// - reader: 文件内容读取器。
|
||||
// - size: 文件大小(字节)。
|
||||
// - contentType: 文件的 MIME 类型。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *fileRepository) Upload(bucket, objectName string, reader io.Reader, size int64, contentType string) error {
|
||||
_, err := r.minio.Client.PutObject(context.Background(), bucket, objectName, reader, size, minio.PutObjectOptions{ContentType: contentType})
|
||||
if err != nil {
|
||||
return errors.InternalError("上传文件失败", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List retrieves a list of files under the specified prefix with metadata.
|
||||
// List 获取指定前缀下的文件列表及其元数据。
|
||||
//
|
||||
// Parameters:
|
||||
// - bucket: The bucket name.
|
||||
// - prefix: The prefix/path to filter files.
|
||||
//
|
||||
// Returns:
|
||||
// - []repository.FileInfo: A list of file information.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - bucket: 桶名称。
|
||||
// - prefix: 用于过滤文件的前缀/路径。
|
||||
//
|
||||
// 返回值:
|
||||
// - []repository.FileInfo: 文件信息列表。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *fileRepository) List(bucket, prefix string) ([]repository.FileInfo, error) {
|
||||
var files []repository.FileInfo
|
||||
for obj := range r.minio.Client.ListObjects(context.Background(), bucket, minio.ListObjectsOptions{Prefix: prefix}) {
|
||||
if obj.Err != nil {
|
||||
return nil, errors.InternalError("获取文件列表失败", obj.Err)
|
||||
}
|
||||
files = append(files, repository.FileInfo{
|
||||
Key: obj.Key,
|
||||
Size: obj.Size,
|
||||
LastModified: obj.LastModified.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// PresignDownload generates a pre-signed URL for downloading a file.
|
||||
// PresignDownload 生成用于下载文件的预签名 URL。
|
||||
//
|
||||
// Parameters:
|
||||
// - bucket: The bucket name.
|
||||
// - objectName: The name/path of the object.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The pre-signed download URL.
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// Note:
|
||||
// - The URL expires after storage.PresignDuration (default 24 hours).
|
||||
// - Returns errors.NotFoundError if the file doesn't exist.
|
||||
//
|
||||
// 参数:
|
||||
// - bucket: 桶名称。
|
||||
// - objectName: 对象的名称/路径。
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 预签名下载 URL。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
//
|
||||
// 注意:
|
||||
// - URL 在 storage.PresignDuration(默认 24 小时)后过期。
|
||||
// - 如果文件不存在,返回 errors.NotFoundError。
|
||||
func (r *fileRepository) PresignDownload(bucket, objectName string) (string, error) {
|
||||
url, err := r.minio.Client.PresignedGetObject(context.Background(), bucket, objectName, storage.PresignDuration, nil)
|
||||
if err != nil {
|
||||
return "", errors.NotFound("文件不存在")
|
||||
}
|
||||
return url.String(), nil
|
||||
}
|
||||
|
||||
// Delete removes a file from the object storage.
|
||||
// Delete 从对象存储中删除文件。
|
||||
//
|
||||
// Parameters:
|
||||
// - bucket: The bucket name.
|
||||
// - objectName: The name/path of the object to delete.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// 参数:
|
||||
// - bucket: 桶名称。
|
||||
// - objectName: 要删除的对象的名称/路径。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *fileRepository) Delete(bucket, objectName string) error {
|
||||
err := r.minio.Client.RemoveObject(context.Background(), bucket, objectName, minio.RemoveObjectOptions{})
|
||||
if err != nil {
|
||||
return errors.InternalError("删除文件失败", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetObject retrieves a file as a readable stream from the object storage.
|
||||
// GetObject 从对象存储中获取文件的可读流。
|
||||
//
|
||||
// Parameters:
|
||||
// - bucket: The bucket name.
|
||||
// - objectName: The name/path of the object.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 参数:
|
||||
// - bucket: 桶名称。
|
||||
// - objectName: 对象的名称/路径。
|
||||
//
|
||||
// 返回值:
|
||||
// - io.ReadCloser: 文件内容的可读流。调用方必须关闭它。
|
||||
// - int64: 文件大小(字节)。
|
||||
// - string: 文件的 Content-Type。
|
||||
// - error: 如果操作失败则返回错误。
|
||||
func (r *fileRepository) GetObject(bucket, objectName string) (io.ReadCloser, int64, string, error) {
|
||||
obj, err := r.minio.Client.GetObject(context.Background(), bucket, objectName, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
return nil, 0, "", errors.NotFound("文件不存在")
|
||||
}
|
||||
|
||||
info, err := obj.Stat()
|
||||
if err != nil {
|
||||
obj.Close()
|
||||
return nil, 0, "", errors.NotFound("文件不存在")
|
||||
}
|
||||
|
||||
contentType := info.ContentType
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
return obj, info.Size, contentType, nil
|
||||
}
|
||||
|
||||
func (r *fileRepository) GetObjectInfo(bucket, objectName string) (int64, string, error) {
|
||||
info, err := r.minio.Client.StatObject(context.Background(), bucket, objectName, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
return 0, "", errors.NotFound("文件不存在")
|
||||
}
|
||||
|
||||
contentType := info.ContentType
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
return info.Size, contentType, nil
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"cloudnest/internal/domain/auth/entity"
|
||||
"cloudnest/internal/domain/auth/repository"
|
||||
"cloudnest/internal/infrastructure/database"
|
||||
"cloudnest/internal/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type userRepository struct {
|
||||
db *database.MySQLClient
|
||||
}
|
||||
|
||||
func NewUserRepository(db *database.MySQLClient) repository.UserRepository {
|
||||
return &userRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByUsername(username string) (*entity.User, error) {
|
||||
var user entity.User
|
||||
err := r.db.DB.Where("username = ?", username).First(&user).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.NotFound("用户不存在")
|
||||
}
|
||||
return nil, errors.InternalError("查询用户失败", err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByUserCode(userCode string) (*entity.User, error) {
|
||||
var user entity.User
|
||||
err := r.db.DB.Where("user_code = ?", userCode).First(&user).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.NotFound("用户不存在")
|
||||
}
|
||||
return nil, errors.InternalError("查询用户失败", err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByEmail(email string) (*entity.User, error) {
|
||||
var user entity.User
|
||||
err := r.db.DB.Where("email = ?", email).First(&user).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.NotFound("用户不存在")
|
||||
}
|
||||
return nil, errors.InternalError("查询用户失败", err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) Create(user *entity.User) error {
|
||||
err := r.db.DB.Create(user).Error
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "Duplicate entry") || strings.Contains(err.Error(), "unique constraint") {
|
||||
return errors.Conflict("用户名或用户码已存在")
|
||||
}
|
||||
return errors.InternalError("创建用户失败", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByID(id uint) (*entity.User, error) {
|
||||
var user entity.User
|
||||
err := r.db.DB.First(&user, id).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.NotFound("用户不存在")
|
||||
}
|
||||
return nil, errors.InternalError("查询用户失败", err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) UpdateStorageUsed(userCode string, size int64) error {
|
||||
err := r.db.DB.Model(&entity.User{}).Where("user_code = ?", userCode).Update("storage_used", gorm.Expr("storage_used + ?", size)).Error
|
||||
if err != nil {
|
||||
return errors.InternalError("更新存储空间失败", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) UpdateProfile(userCode, email, phone, nickname string) error {
|
||||
err := r.db.DB.Model(&entity.User{}).Where("user_code = ?", userCode).Updates(map[string]interface{}{
|
||||
"email": email,
|
||||
"phone": phone,
|
||||
"nickname": nickname,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return errors.InternalError("更新个人信息失败", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) UpdatePassword(userCode, newPassword string) error {
|
||||
err := r.db.DB.Model(&entity.User{}).Where("user_code = ?", userCode).Update("password", newPassword).Error
|
||||
if err != nil {
|
||||
return errors.InternalError("更新密码失败", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"cloudnest/internal/pkg/logger"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
// PresignDuration is the default duration for pre-signed URLs.
|
||||
// PresignDuration 是预签名 URL 的默认有效期。
|
||||
var PresignDuration = 24 * time.Hour
|
||||
|
||||
// MinioClient provides MinIO object storage operations.
|
||||
// MinioClient 提供 MinIO 对象存储操作。
|
||||
//
|
||||
// This struct wraps the MinIO client and provides methods for storage operations.
|
||||
//
|
||||
// 此结构体封装了 MinIO 客户端,并提供了存储操作的方法。
|
||||
type MinioClient struct {
|
||||
Client *minio.Client
|
||||
}
|
||||
|
||||
// NewMinioClient creates a new MinIO client.
|
||||
// NewMinioClient 创建一个新的 MinIO 客户端。
|
||||
//
|
||||
// Parameters:
|
||||
// - endpoint: The MinIO server endpoint (host:port).
|
||||
// - accessKey: The MinIO access key.
|
||||
// - secretKey: The MinIO secret key.
|
||||
// - useSSL: Whether to use SSL/TLS for connections.
|
||||
//
|
||||
// Returns:
|
||||
// - *MinioClient: A pointer to the new MinioClient instance.
|
||||
// - error: An error if the connection fails.
|
||||
//
|
||||
// 参数:
|
||||
// - endpoint: MinIO 服务器端点(host:port)。
|
||||
// - accessKey: MinIO 访问密钥。
|
||||
// - secretKey: MinIO 秘密密钥。
|
||||
// - useSSL: 是否使用 SSL/TLS 连接。
|
||||
//
|
||||
// 返回值:
|
||||
// - *MinioClient: 新创建的 MinioClient 实例指针。
|
||||
// - error: 如果连接失败则返回错误。
|
||||
func NewMinioClient(endpoint, accessKey, secretKey string, useSSL bool) (*MinioClient, error) {
|
||||
client, err := minio.New(endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||
Secure: useSSL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("minio connected")
|
||||
return &MinioClient{Client: client}, nil
|
||||
}
|
||||
|
||||
// EnsureBucket creates the bucket if it doesn't exist.
|
||||
// EnsureBucket 如果桶不存在则创建桶。
|
||||
//
|
||||
// Parameters:
|
||||
// - bucketName: The name of the bucket to ensure.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
//
|
||||
// Note:
|
||||
// - This method is idempotent - it can be called safely even if the bucket exists.
|
||||
//
|
||||
// 参数:
|
||||
// - bucketName: 要确保存在的桶名称。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 如果操作失败则返回错误。
|
||||
//
|
||||
// 注意:
|
||||
// - 此方法是幂等的 - 即使桶已存在也可以安全调用。
|
||||
func (c *MinioClient) EnsureBucket(bucketName string) error {
|
||||
exists, err := c.Client.BucketExists(context.Background(), bucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if err := c.Client.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("bucket created", "bucket", bucketName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user