四、Abp Vnext中使用Minio打造文件管理模块(上)

在Abp商业版本中已经提供了文件管理模块的,免费版本是没有的,本文将介绍如何使用Minio打造一个自己的文件管理模块。

在项目开始之前,需要先安装一个Minio服务,可以在本地pc或云主机中安装,具体可见Minio中文文档,这里只介绍docker-compose方式安装

  • windows中安装docker desktop并切换到linux容器
  • linux中安装docker和docker-compose

任意选择一个目录作为minio安装目录,创建docker-compose.yml文件,输入如下内容:

version: '3.7'
services:
  minio:
    image: minio/minio
    hostname: "minio"
    ports:
      - 50010:9000 # api 端口
      - 50011:9001 # 控制台端口
    environment:
      MINIO_ROOT_USER: admin    #管理后台用户名
      MINIO_ROOT_PASSWORD: 123qwe!@#  #管理后台密码,最小8个字符
    volumes:
      - /docker/minio/data:/data               #映射当前目录下的data目录至容器内/data目录
      - /docker/minio/config:/root/.minio/     #映射配置目录
    command: server --console-address ':9001' /data  #指定容器中的目录 /data
    privileged: true
    restart: always

 执行命令创建并运行容器

docker-compose up -d

成功后浏览器打开http://localhost:50011/,出现MinIO登录页面,输入docker-compose.yml中定义的用户名和密码登录

登录后选择Buckets目录,添加一个files库作为文件管理的库,如图

 选择Users菜单,添加一个test用户,并赋予读写权限

接着开始编写后台代码,步骤如下:

1、生成框架代码

可以使用Abp官方的代码模板或者前一章节中自定义的项目模板生成框架代码

自定义代码框架模板,见上一章

abp new MyCompanyName.TestModuleProject -u angular --mobile none -d ef -csf -cs "server=192.168.100.175;port=3306;database=abp_test3;uid=test;pwd=Test123$;SslMode=none" -ts "F:\BlogSamples\templates\app" -v 5.0.0-rc.1

或者使用官方默认的代码框架模板(上面命令去掉-ts参数即可)

abp new MyCompanyName.TestModuleProject -u angular --mobile none -d ef -csf -cs "server=192.168.100.175;port=3306;database=abp_test3;uid=test;pwd=Test123$;SslMode=none"

如果使用官方模板,需要在Api.Host项目的模块文件中添加文件上传下载的配置,具体操作参考上一章内容

修改angular目录名为filemanagement.angular,这么做是避免在添加模块时生成angular的代码(自动生成的前端有点乱),接着进入aspnet-core目录执行以下命令在后台添加文件模块

abp add-module MyCompany.FileManagement --new --add-to-solution-file

添加完模块后启动模块的项目也自动添加项目引用和模块依赖,打开MyCompanyName.TestModuleProject.sln解决方案查看src目录下每个项目的Module文件,比如Application项目的TestProjectApplicationModule.cs文件

2、在文件模块中添加Minio支持

首先在MyCompany.TestProject.HttpApi.Host项目的appsettings.json配置文件中添加minio的连接参数配置,如下:

{
  ...
  ... 
  "Minio": {
    "EndPoint": "localhost:50010",
    "User": "test",
    "Password": "test123$%^",
    "BucketName": "files"
  }
}

双击MyCompany.FileManagement.Domain项目,添加如下引用:

  <ItemGroup>
    <PackageReference Include="Volo.Abp.BlobStoring" Version="5.0.0-rc.1" />
    <PackageReference Include="Volo.Abp.BlobStoring.Minio" Version="5.0.0-rc.1" />
    ...
    ...
  </ItemGroup>

打开FileManagementDomainModule.cs文件,修改为以下内容:

using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BlobStoring;
using Volo.Abp.BlobStoring.Minio;
using Volo.Abp.Domain;
using Volo.Abp.Modularity;

namespace MyCompany.FileManagement
{
    [DependsOn(
        typeof(AbpDddDomainModule),
        typeof(FileManagementDomainSharedModule),
        typeof(AbpBlobStoringMinioModule)
    )]
    public class FileManagementDomainModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            // 获取appsettings配置
            var configuration = context.Services.GetConfiguration();
            // 配置使用Minio作为Blob存储的容器
            Configure<AbpBlobStoringOptions>(options =>
            {
                options.Containers.ConfigureDefault(container =>
                {
                    container.UseMinio(minio =>
                    {
                        minio.EndPoint = configuration["Minio:EndPoint"]; // your minio endPoint
                        minio.AccessKey = configuration["Minio:User"]; // your minio accessKey
                        minio.SecretKey = configuration["Minio:Password"]; // your minio secretKey
                        minio.BucketName = configuration["Minio:FilesBucket"]; // your minio bucketName
                    });
                });
            });
        }
    }
}

3、添加数据库存储

Mino作为文件存储的容器,我们还需要一个表来存储文件的基本信息和目录关系,步骤如下:

(1)在FileManagement.Domain项目中添加Entities目录,在其中添加BlobFile.cs文件,内容如下:

using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;

namespace MyCompany.FileManagement.Entities
{
    public class BlobFile : AuditedAggregateRoot<Guid>, IMultiTenant
    {
        public long FileSize { get; set; }
        public string MimeType { get; set; }
        public Guid? TenantId { get; set; }
        public string Path { get; set; }
        public bool IsDirectory { get; set; }
        public string Name { get; set; }
        // 用户id,不为空的为个人文件,否则为公共文件
        public Guid? OwnerId { get; set; }
        protected BlobFile() { }
        public BlobFile(
            Guid id,
            string name,
            long fileSize,
            string mimeType,
            string path,
            Guid? tenantId = null,
            Guid? ownerId = null,
            bool isDir = false
        ) : base(id)
        {
            Name = name;
            FileSize = fileSize;
            MimeType = mimeType;
            TenantId = tenantId;
            Path = path;
            OwnerId = ownerId;
            IsDirectory = isDir;
        }
    }
}

继续添加文件管理数据仓库接口IBlobFileRepository.cs,代码如下:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;

namespace MyCompany.FileManagement.Entities
{
    /// <summary>
    /// 文件管理数据仓库接口
    /// </summary>
    public interface IBlobFileRepository : IBasicRepository<BlobFile, Guid>
    {
        // 根据条件获取分页记录
        Task<List<BlobFile>> GetListAsync(string sorting = null, int maxResultCount = int.MaxValue, int skipCount = 0,
            string path = null, Guid? userId = null, string filter = null, bool includeDetails = false,
            CancellationToken cancellationToken = default);
        // 根据条件获取记录总数
        Task<long> GetCountAsync(string path = null, Guid? userId = null, string filter = null, CancellationToken cancellationToken = default);
        // 获取子目录中文件记录
        Task<List<BlobFile>> GetChildByPathAsync(string path, Guid? userId = null, CancellationToken cancellationToken = default);
        // 获取已用存储空间大小
        Task<long> GetStorageSizeAsync(Guid? userId, CancellationToken cancellationToken = default);
    }
}

最后添加一个领域服务类,领域服务提供个人文件和公共文件的存储和访问方法,FileManagementManager.cs

using MyCompany.FileManagement.Entities;
using Shktiot.FileManagement;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.BlobStoring;
using Volo.Abp.Domain.Services;

namespace MyCompany.FileManagement
{
    public class FileManagementManager: DomainService
    {
        // 注入类型化Blob容器接口
        private readonly IBlobContainer<FilesContainer> _blobContainer;
        // 注入文件管理数据仓库接口
        private readonly IBlobFileRepository _fileRepository;

        public FileManagementManager(IBlobContainer<FilesContainer> blobContainer, IBlobFileRepository fileRepository)
        {
            _blobContainer = blobContainer;
            _fileRepository = fileRepository;
        }

        /// <summary>
        /// 上传文件,userId为空时是公共文件
        /// </summary>
        /// <param name="path"></param>
        /// <param name="fileName"></param>
        /// <param name="bytes"></param>
        /// <param name="contentType"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        /// <exception cref="UserFriendlyException"></exception>
        public async Task<string> CreateAsync(string path, string fileName, byte[] bytes, string contentType,  Guid? userId = null)
        {
            // 获取当前已用用户空间大小
            var userUsedSize = await _fileRepository.GetStorageSizeAsync(userId);
            var userTotalSize = bytes.Length + userUsedSize;
            if (userTotalSize > 100000000)
            {
                throw new UserFriendlyException("剩余空间不足!");
            }

            var filePath = FormatPathName(path);
            var newFile = new BlobFile(GuidGenerator.Create(), fileName, bytes.Length, contentType, filePath, CurrentTenant.Id, userId);
            var created = await _fileRepository.InsertAsync(newFile);

            await _blobContainer.SaveAsync(newFile.Id.ToString(), bytes).ConfigureAwait(false);

            return filePath;
        }

        public async Task<string> CreateDirectoryAsync(string path, string dirName, Guid? userId = null)
        {
            var dirPath = FormatPathName(path);
            var newDir = new BlobFile(GuidGenerator.Create(), dirName, 0, null, dirPath, CurrentTenant.Id, userId, true);

            var created = await _fileRepository.InsertAsync(newDir);

            return created.Path;
        }

        public async Task DeleteDirAsync(Guid id, Guid? userId = null)
        {
            var dir = await _fileRepository.GetAsync(id);

            if (!dir.IsDirectory)
            {
                throw new UserFriendlyException("目录不存在!");
            }

            var dirPath = ConbinePathName(dir.Path, dir.Name);
            var chidren = await _fileRepository.GetChildByPathAsync(dirPath, userId);

            await _fileRepository.DeleteManyAsync(chidren);

            foreach (var item in chidren)
            {
                await _blobContainer.DeleteAsync(item.Id.ToString());
            }

            await _fileRepository.DeleteAsync(id);
        }

        public async Task DeleteFileAsync(Guid id)
        {
            var file = await _fileRepository.GetAsync(id);

            if (file.IsDirectory)
            {
                throw new UserFriendlyException("文件不存在!");
            }

            await _blobContainer.DeleteAsync(id.ToString());

            await _fileRepository.DeleteAsync(id);
        }

        public async Task<byte[]> GetBytesAsync(Guid id)
        {
            var file = await _blobContainer.GetAllBytesAsync(id.ToString());

            return file;
        }

        public async Task<BlobFile> GetFileInfoAsync(Guid id)
        {
            var fileInfo = await _fileRepository.GetAsync(id);
            return fileInfo;
        }

        public Task<long> GetCountAsync(string path, string filter, Guid? userId = null)
        {
            var dir = FormatPathName(path);
            return _fileRepository.GetCountAsync(dir, userId, filter);
        }

        public Task<List<BlobFile>> GetListAsync(string sorting, int maxCount, int skipCount, string path, string filter, Guid? userId = null)
        {
            var dir = FormatPathName(path);
            return _fileRepository.GetListAsync(sorting, maxCount, skipCount, dir, userId, filter);
        }

        public async Task RenameAsync(Guid id, string newName)
        {
            var file = await _fileRepository.GetAsync(id);
            file.Name = newName;

            await _fileRepository.UpdateAsync(file);
        }

        protected string FormatPathName(string path)
        {
            path = $"/{path}/".Trim("./".ToArray());
            if (path.Length == 0) return "/";
            return $"/{path}/";
        }

        private string ConbinePathName(string path, string name)
        {
            return $"{FormatPathName(path).TrimEnd('/')}/{name}";
        }
    }
}

(2)添加EntityFrameworkCore配置

首先在IFileManagementDbContext.cs中添加BlobFile属性

    public interface IFileManagementDbContext : IEfCoreDbContext
    {
        DbSet<BlobFile> BlobFiles { get; }
    }

在FileManagementDbContext.cs中添加BlobFile属性实现

    public class FileManagementDbContext : AbpDbContext<FileManagementDbContext>, IFileManagementDbContext
    {
        public DbSet<BlobFile> BlobFiles { get; set; }
        ...
    }

在FileManagementDbContextModelCreatingExtensions.cs的ConfigureFileManagement方法中添加BlobFile实体的数据库配置

using Microsoft.EntityFrameworkCore;
using MyCompany.FileManagement.Entities;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;

namespace MyCompany.FileManagement.EntityFrameworkCore
{
    public static class FileManagementDbContextModelCreatingExtensions
    {
        public static void ConfigureFileManagement(
            this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));

            builder.Entity<BlobFile>(b =>
            {
                // 表名
                b.ToTable(FileManagementDbProperties.DbTablePrefix + "BlobFiles", FileManagementDbProperties.DbSchema);
                // 一些基础实体字段的自动配置,比如ExreaProperties扩展字段等
                b.ConfigureByConvention();

                // 配置字段属性,长度、类型、是否为空等
                b.Property(x => x.Name).HasMaxLength(256);
                b.Property(x => x.Path).HasMaxLength(256);
                b.Property(x => x.MimeType).HasMaxLength(128);

                // 定义索引
                b.HasIndex(f => f.Name);
                b.HasIndex(f => f.Path);
                b.HasIndex(f => f.OwnerId);
                b.HasIndex(f => f.LastModificationTime);

            });
        }
    }
}

在EntityFrameworkCore目录中添加IBlobFileRepository数据仓库接口的实现类BlobFileRepository.cs

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using System.Linq.Dynamic.Core;
using MyCompany.FileManagement.Entities;

namespace MyCompany.FileManagement.EntityFrameworkCore
{
    public class BlobFileRepository :
        EfCoreRepository<IFileManagementDbContext, BlobFile, Guid>,
        IBlobFileRepository
    {

        public BlobFileRepository(
            IDbContextProvider<IFileManagementDbContext> dbContextProvider)
         : base(dbContextProvider)
        {
        }

        public async Task<List<BlobFile>> GetChildByPathAsync(string path, Guid? userId = null, CancellationToken cancellationToken = default)
        {
            return await (await GetDbSetAsync())
               .WhereIf(!path.IsNullOrWhiteSpace(), u => u.Path.StartsWith(path))
               .Where(t => t.OwnerId == userId)
               .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<long> GetCountAsync(string path = null, Guid? userId = null, string filter = null, CancellationToken cancellationToken = default)
        {
            return await (await GetDbSetAsync())
               .WhereIf(!path.IsNullOrWhiteSpace(), u => u.Path == path)
               .WhereIf(!filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter))
               .Where(t => t.OwnerId == userId)
               .LongCountAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<BlobFile>> GetListAsync(string sorting = null, int maxResultCount = int.MaxValue, int skipCount = 0, string path = null, Guid? userId = null, string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            return await (await GetDbSetAsync())
                .WhereIf(!path.IsNullOrWhiteSpace(), u => u.Path == path)
                .WhereIf(!filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter))
                .Where(t => t.OwnerId == userId)
                .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(BlobFile.Name) : sorting)
                .PageBy(skipCount, maxResultCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<long> GetStorageSizeAsync(Guid? userId, CancellationToken cancellationToken = default)
        {
            return await (await GetDbSetAsync())
              .WhereIf(userId.HasValue, u => u.OwnerId == userId)
              .SumAsync(t => t.FileSize, GetCancellationToken(cancellationToken));
        }
    }
}

(3) 生成数据库迁移

打开启动模块MyCompany.TestProject.EntityFrameworkCore项目中的TestProjectDbContext.cs文件,可以看到在执行add-module命令时已自动添加了文件模块的数据库配置方法:

    protected override void OnModelCreating(ModelBuilder builder)
    { 
        ...
        ...
        builder.ConfigureFileManagement();
    }

右键该项目->在终端打开,或者在cmd命令行中进入项目所在目录,执行数据生成迁移命令:

 dotnet ef migrations add  -o Migrations InitDb

可以看到Migrations\XXXXXXX_InitDb.cs中有了创建FileManagementBlobFiles表的脚本

执行MyCompany.TestProject.DbMigrator项目,生成数据库

4、添加文件管理数据服务

数据服务提供前端代码访问的接口方法,需要添加以下内容:

(1)权限控制

        首先在MyCompany.FileManagement.Application.Contracts项目的FileManagementPermissions.cs文件中添加两组权限定义的常量如下

    public class FileManagementPermissions
    {
        ...
        ...
        // 个人文件
        public static class MyFiles
        {
            public const string Default = GroupName + ".MyFiles";
            public const string CreateDir = Default + ".CreateDir";
            public const string UploadFile = Default + ".UploadFile";
            public const string Rename = Default + ".Rename";
            public const string Delete = Default + ".Delete";
            public const string Download = Default + ".Download";
        }
        // 公共文件
        public static class PublicFiles
        {
            public const string Default = GroupName + ".PublicFiles";
            public const string CreateDir = Default + ".CreateDir";
            public const string UploadFile = Default + ".UploadFile";
            public const string Rename = Default + ".Rename";
            public const string Delete = Default + ".Delete";
            public const string Download = Default + ".Download";
        }
    }

        然后在FileManagementPermissionDefinitionProvider中添加权限定义:

        public override void Define(IPermissionDefinitionContext context)
        {
            var fileManageGroup = context.AddGroup(FileManagementPermissions.GroupName, L("Menu:FileManagement"));

            var publicFiles = fileManageGroup.AddPermission(FileManagementPermissions.PublicFiles.Default, L("Menu:PublicFiles"));
            publicFiles.AddChild(FileManagementPermissions.PublicFiles.CreateDir, L("Files:CreateDir"));
            publicFiles.AddChild(FileManagementPermissions.PublicFiles.Rename, L("Files:ReName"));
            publicFiles.AddChild(FileManagementPermissions.PublicFiles.UploadFile, L("Files:UploadFile"));
            publicFiles.AddChild(FileManagementPermissions.PublicFiles.Delete, L("Files:Delete"));
            publicFiles.AddChild(FileManagementPermissions.PublicFiles.Download, L("Files:Download"));

            var myFiles = fileManageGroup.AddPermission(FileManagementPermissions.MyFiles.Default, L("Menu:MyFiles"));
            myFiles.AddChild(FileManagementPermissions.MyFiles.CreateDir, L("Files:CreateDir"));
            myFiles.AddChild(FileManagementPermissions.MyFiles.UploadFile, L("Files:UploadFile"));
            myFiles.AddChild(FileManagementPermissions.MyFiles.Rename, L("Files:ReName"));
            myFiles.AddChild(FileManagementPermissions.MyFiles.Delete, L("Files:Delete"));
            myFiles.AddChild(FileManagementPermissions.MyFiles.Download, L("Files:Download"));
        }

(2)数据服务接口

移除MyCompany.FileManagement.Application.Contracts项目中的Sample目录,添加FileBlob目录,在其中添加文件管理数据服务接口IBlobFileManageAppService.cs:

using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;

namespace MyCompany.FileManagement.FileBlob
{
    public interface IBlobFileManageAppService : IApplicationService
    {
        /// <summary>
        /// 获取文件
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        Task<RawFileDto> GetAsync(Guid id);
        /// <summary>
        /// 创建目录
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task<string> CreateDirectoryAsync(CreateDirInputDto input);
        /// <summary>
        /// 重命名
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task RenameAsync(RenameInputDto input);
        /// <summary>
        /// 上传创建文件
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input);
        /// <summary>
        /// 获取文件列表
        /// </summary>
        /// <param name="path"></param>
        /// <param name="input"></param>
        /// <returns></returns>
        Task<PagedResultDto<FileInfoDto>> GetListAsync(string path, GetDirectoryListInput input);
        /// <summary>
        /// 删除目录
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        Task DeleteDirAsync(Guid id);
        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        Task DeleteFileAsync(Guid id);
    }
}

继续添加个人文件服务接口IMyFileManageAppService.cs

namespace MyCompany.FileManagement.FileBlob
{
    public interface IMyFileManageAppService : IBlobFileManageAppService
    {
    }
}

继续添加公共文件服务接口IPublicFileManageAppService.cs

namespace MyCompany.FileManagement.FileBlob
{
    // 继承自文件服务,存储时用户Id为空时认为是公共文件
    public interface IPublicFileManageAppService : IBlobFileManageAppService
    {
    }
}

个人文件和公共文件的区分是存储时用户Id为空时认为是公共文件

(3)DTO数据传输对象

在MyCompany.FileManagementApplication.Contracts项目的FileBlob目录下添加以下Dto类:

CreateDirInputDto.cs

using System.ComponentModel.DataAnnotations;

namespace MyCompany.FileManagement.FileBlob
{
    public class CreateDirInputDto
    {

        [Required]
        public string Name { get; set; }

        public string Path { get; set; }
    }
}

FileInfoDto.cs

using System;
using Volo.Abp.Application.Dtos;

namespace MyCompany.FileManagement.FileBlob
{
    public class FileInfoDto : EntityDto<Guid>
    {
        public DateTime CreationTime { get; set; }
        public DateTime LastModificationTime { get; set; }
        public long FileSize { get; set; }
        public string MimeType { get; set; }
        public string Path { get; set; }
        public bool IsDirectory { get; set; }
        public string Name { get; set; }
    }
}

FileUploadInputDto.cs

using System.ComponentModel.DataAnnotations;

namespace MyCompany.FileManagement.FileBlob
{
    public class FileUploadInputDto
    {
        [Required]
        public byte[] Bytes { get; set; }
        [Required]
        public string Name { get; set; }
        public string ContentType { get; set; }
        public string Path { get; set; }
    }
}

FileUploadOutputDto.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace MyCompany.FileManagement.FileBlob
{
    public class FileUploadOutputDto
    {
        public string Name { get; set; }
        public string WebUrl { get; set; }
    }
}

GetDirectoryListInput.cs

using Volo.Abp.Application.Dtos;

namespace MyCompany.FileManagement.FileBlob
{
    public class GetDirectoryListInput : PagedAndSortedResultRequestDto
    {
        public string Filter { get; set; }
    }
}

RawFileDto.cs

namespace MyCompany.FileManagement.FileBlob
{
    public class RawFileDto
    {
        public byte[] Bytes { get; set; }
        public string Name { get; set; }
        public string MimeType { get; set; }
        public bool IsFileEmpty => Bytes == null || Bytes.Length == 0;
        public RawFileDto() { }

        public static RawFileDto EmptyResult()
        {
            return new RawFileDto() { Bytes = new byte[0] };
        }
    }
}

RenameInputDto.cs

using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;
namespace MyCompany.FileManagement.FileBlob
{
    public class RenameInputDto : EntityDto<Guid>
    {
        [Required]
        public string Name { get; set; }
        [Required]
        public string NewName { get; set; }
        public bool IsFile { get; set; }
        public string Path { get; set; }
    }
}

(4)数据服务实现

这里需要使用类型化容器,类型化BLOB容器可以在程序中创建和管理多个容器,关于类型化容器可以参考官方文档,在Domain项目中添加BlobContainerName 属性装饰的类FilesContainer.cs

using Volo.Abp.BlobStoring;

namespace Shktiot.FileManagement
{
    [BlobContainerName("files")]
    public class FilesContainer
    {
    }
}

在FileManagementApplicationAutoMapperProfile.cs文件中添加Automapper映射配置:

        public FileManagementApplicationAutoMapperProfile()
        {
            CreateMap<BlobFile, FileInfoDto>();
        }

删除MyCompany.FileManagement.Application项目下Samples目录,添加Services目录,在其中添加个人文件服务类MyFileManageAppService.cs,内容如下:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using MyCompany.FileManagement.Entities;
using MyCompany.FileManagement.FileBlob;
using MyCompany.FileManagement.Permissions;
using Volo.Abp.Application.Dtos;

namespace MyCompany.FileManagement.Services
{
    public class MyFileManageAppService : FileManagementAppService, IMyFileManageAppService
    {
        // 注入领域服务类
        private readonly FileManagementManager _fileManagementManager;

        public MyFileManageAppService(FileManagementManager fileManagementManager)
        {
            _fileManagementManager = fileManagementManager;
        }

        [Authorize(FileManagementPermissions.MyFiles.UploadFile)]
        public async Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input)
        {
            var filePath = await _fileManagementManager.CreateAsync(input.Path, input.Name, input.Bytes, input.ContentType, CurrentUser.Id);

            return new FileUploadOutputDto { WebUrl = filePath, Name = input.Name };
        }
        [Authorize(FileManagementPermissions.MyFiles.CreateDir)]
        public async Task<string> CreateDirectoryAsync(CreateDirInputDto input)
        {
            var created = await _fileManagementManager.CreateDirectoryAsync(input.Path, input.Name, CurrentUser.Id);

            return created;
        }
        [Authorize(FileManagementPermissions.MyFiles.Delete)]
        public  Task DeleteDirAsync(Guid id)
        {
            return _fileManagementManager.DeleteDirAsync(id);
        }
        [Authorize(FileManagementPermissions.MyFiles.Delete)]
        public Task DeleteFileAsync(Guid id)
        {
            return _fileManagementManager.DeleteFileAsync(id);
        }
        [Authorize(FileManagementPermissions.MyFiles.Default)]
        public async Task<RawFileDto> GetAsync(Guid id)
        {
            var file = await _fileManagementManager.GetBytesAsync(id);

            var fileInfo = await _fileManagementManager.GetFileInfoAsync(id);

            return new RawFileDto { Bytes = file, MimeType = fileInfo.MimeType, Name = fileInfo.Name };
        }
        [Authorize(FileManagementPermissions.MyFiles.Default)]
        public async  Task<PagedResultDto<FileInfoDto>> GetListAsync(string path, GetDirectoryListInput input)
        {
            var count = await _fileManagementManager.GetCountAsync(path, input.Filter, CurrentUser.Id);
            var list = await _fileManagementManager.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount, path, input.Filter, CurrentUser.Id);

            return new PagedResultDto<FileInfoDto>(
                count,
                ObjectMapper.Map<List<BlobFile>, List<FileInfoDto>>(list)
            );
        }
        [Authorize(FileManagementPermissions.MyFiles.Rename)]
        public async  Task RenameAsync(RenameInputDto input)
        {
            await _fileManagementManager.RenameAsync(input.Id, input.NewName);
        }
    }
}

继续添加公共文件服务类PublicFileManageAppService.cs,区别仅是领域方法调用时不传入当前用户的Id

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using MyCompany.FileManagement.Entities;
using MyCompany.FileManagement.FileBlob;
using MyCompany.FileManagement.Permissions;
using Volo.Abp.Application.Dtos;

namespace MyCompany.FileManagement.Services
{
    /// <summary>
    /// 与个人文件服务区别仅是领域方法调用时不传入当前用户的Id
    /// </summary>
    public class PublicFileManageAppService : FileManagementAppService, IPublicFileManageAppService
    {
        // 注入领域服务类
        private readonly FileManagementManager _fileManagementManager;

        public PublicFileManageAppService(FileManagementManager fileManagementManager)
        {
            _fileManagementManager = fileManagementManager;
        }

        [Authorize(FileManagementPermissions.PublicFiles.UploadFile)]
        public async Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input)
        {
            var filePath = await _fileManagementManager.CreateAsync(input.Path, input.Name, input.Bytes, input.ContentType);

            return new FileUploadOutputDto { WebUrl = filePath, Name = input.Name };
        }
        [Authorize(FileManagementPermissions.PublicFiles.CreateDir)]
        public async Task<string> CreateDirectoryAsync(CreateDirInputDto input)
        {
            var created = await _fileManagementManager.CreateDirectoryAsync(input.Path, input.Name);

            return created;
        }
        [Authorize(FileManagementPermissions.PublicFiles.Delete)]
        public  Task DeleteDirAsync(Guid id)
        {
            return _fileManagementManager.DeleteDirAsync(id);
        }
        [Authorize(FileManagementPermissions.PublicFiles.Delete)]
        public Task DeleteFileAsync(Guid id)
        {
            return _fileManagementManager.DeleteFileAsync(id);
        }
        [Authorize(FileManagementPermissions.PublicFiles.Default)]
        public async Task<RawFileDto> GetAsync(Guid id)
        {
            var file = await _fileManagementManager.GetBytesAsync(id);

            var fileInfo = await _fileManagementManager.GetFileInfoAsync(id);

            return new RawFileDto { Bytes = file, MimeType = fileInfo.MimeType, Name = fileInfo.Name };
        }
        [Authorize(FileManagementPermissions.PublicFiles.Default)]
        public async  Task<PagedResultDto<FileInfoDto>> GetListAsync(string path, GetDirectoryListInput input)
        {
            var count = await _fileManagementManager.GetCountAsync(path, input.Filter);
            var list = await _fileManagementManager.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount, path, input.Filter);

            return new PagedResultDto<FileInfoDto>(
                count,
                ObjectMapper.Map<List<BlobFile>, List<FileInfoDto>>(list)
            );
        }
        [Authorize(FileManagementPermissions.PublicFiles.Rename)]
        public async  Task RenameAsync(RenameInputDto input)
        {
            await _fileManagementManager.RenameAsync(input.Id, input.NewName);
        }
    }
}

(5)添加Webapi 控制器

双击MyCompany.FileManagement.HttpApi项目,添加以下引用:

 <ItemGroup>
	<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.1.4" />
    ...
</ItemGroup>

删除MyCompany.FileManagement.HttpApi项目中Sample目录,添加文件管理基类控制器BlobFileBaseController.cs,内容如下:

using MyCompany.FileManagement.FileBlob;
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using Volo.Abp;
using Volo.Abp.Application.Dtos;

namespace MyCompany.FileManagement
{
    public class BlobFileBaseController<TAppService>: FileManagementController where TAppService : IBlobFileManageAppService
    {
        protected readonly TAppService _fileAppService;

        public BlobFileBaseController(TAppService fileAppService)
        {
            _fileAppService = fileAppService;
        }

        [HttpGet]
        [Route("{id}")]
        public Task<RawFileDto> GetAsync(Guid id)
        {
            return _fileAppService.GetAsync(id);
        }

        [RemoteService(false)]
        [SwaggerResponse(200, type: typeof(FileContentResult))]
        [ProducesResponseType(typeof(FileContentResult), 200)]
        [HttpGet]
        [Route("www/{id}")]
        public async Task<FileResult> GetForWebAsync(Guid id)
        {
            var file = await _fileAppService.GetAsync(id);
            return File(file.Bytes, file.MimeType, file.Name);
        }

        [HttpGet]
        public Task<PagedResultDto<FileInfoDto>> GetListAsync(string path, GetDirectoryListInput input)
        {
            return _fileAppService.GetListAsync(path, input);
        }

        [RemoteService(false)]
        [HttpPost]
        [Route("upload")]
        public async Task<JsonResult> CreateAsync(string path, IFormFile file)
        {
            if (file == null)
            {
                throw new UserFriendlyException("No file found!");
            }

            var bytes = await file.GetAllBytesAsync();
            var result = await _fileAppService.CreateAsync(new FileUploadInputDto()
            {
                Bytes = bytes,
                Name = file.FileName,
                Path = path?.TrimEnd('/'),
                ContentType = file.ContentType
            });
            return new JsonResult(result);
        }

        [HttpPost]
        [Route("dir")]
        public Task<string> CreateDirectoryAsync(CreateDirInputDto input)
        {
            return _fileAppService.CreateDirectoryAsync(input);
        }

        [HttpPut]
        [Route("rename")]
        public Task RenameAsync(RenameInputDto input)
        {
            return _fileAppService.RenameAsync(input);
        }
        [HttpDelete]
        [Route("dir/{id}")]
        public Task DeleteDirAsync(Guid id)
        {
            return _fileAppService.DeleteDirAsync(id);
        }
        [HttpDelete]
        [Route("file/{id}")]
        public Task DeleteFileAsync(Guid id)
        {
            return _fileAppService.DeleteFileAsync(id);
        }
    }
}

添加个人文件管理控制器类MyFilesController.cs

using MyCompany.FileManagement.FileBlob;
using Microsoft.AspNetCore.Mvc;

namespace MyCompany.FileManagement
{
    [Route("api/file-management/my-files")]
    public class MyFilesController : BlobFileBaseController<IMyFileManageAppService>
    {
        public MyFilesController(IMyFileManageAppService fileAppService) : base(fileAppService)
        {
        }
    }
}

继续添加公共文件管理控制器类PublicFilesController.cs

using MyCompany.FileManagement.FileBlob;
using Microsoft.AspNetCore.Mvc;

namespace MyCompany.FileManagement
{
    [Route("api/file-management/public-files")]
    public class PublicFilesController : BlobFileBaseController<IPublicFileManageAppService>
    {
        public PublicFilesController(IPublicFileManageAppService fileAppService): base(fileAppService)
        {
        }
    }
}

5、测试接口

用vscode打开filemanagement.angular目录,执行终端命令npm install 和npm start启动项目,在浏览器打开http://localhost:4200/,使用admin登录,在角色页面修改admin角色的权限,添加文件管理的所有权限(这里没有添加中文资源)

 浏览器打开Swagger Api 界面https://localhost:44358/swagger/index.html,点击Authorize按钮,使用admin用户进行认证

然后找到上传文件的接口方法:

 点击Try it out按钮,在file栏选择一个文件上传,然后点击Execute按钮,执行成功后结果如下:

 浏览器登录Minio控制台,可以看到文件已写入minio的库中了

 这一章主要介绍后端代码的实现,下一节将介绍angular前端的实现

本文源码:Abp Vnext中使用Minio打造文件管理模块

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沝林

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值