Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之文件上传

媒体管理的主要难度就在于媒体文件的上传,由于使用了 plupload
作为上传工具,因而整个实现流程还是比较简单。

由于媒体实体没有大文本数据,因而 Get
GetAll
方法使用相同的数据传输对象就可以,具体代码如下:

[AutoMapFrom(typeof(Media))]
    public class MediaDto: EntityDto
    {
        public string Filename { get; set; }

        public string Description { get; set; }

        public string Path { get; set; }

        public MediaType Type { get; set; }

        public int Size { get; set; }

        public DateTime CreationTime { get; set; }
    }

对于 GetAll
方法,需要提供自定义排序和查询等功能,因而需自定义输入对象,代码如下:

public class GetAllMediaInputDto : PagedAndSortedResultRequestDto, IShouldNormalize
    {
        private readonly JObject _allowSorts = new JObject()
        {
            { "creationTime", "CreationTime" },
            { "size", "Size" },
            { "description", "Description" }
        };

        public string Query { get; set; }
        public int? Year { get; set; }
        public int? Month { get; set; }
        public int? Day { get; set; }
        [Required]
        public int[] Type { get; set; }
        public string Sort { get; set; }

        public void Normalize()
        {
            if (!string.IsNullOrEmpty(Sort))
            {
                Sorting = Helper.ExtJs.OrderBy(Sort, _allowSorts);
            }
        }
    }

plupload
是一个个文件上传的,因而上传所用的数据传输对象,只接收一个文件就行了,代码如下:

public class CreateMediaDto
    {
        [Required]
        public IFormFile File { get; set; }
    }

要注意的是Asp.Net Core是接收文件是是否 IFormFile
对象,不是使用 HttpPostedFileBase

媒体的描述字段( Description
)允许更新,因而要写一个更新对象,代码如下:

[AutoMapTo(typeof(Media))]
    public class UpdateMediaInputDto : EntityDto
    {
        [MaxLength(Media.MaxDescriptionLength)]
        public string Description { get; set; }
    }

删除还是采取一次可删除多个记录的方式,需要重定义输入接口,代码如下:

public class DeleteMediaInputDto
    {
        public long[] Id { get; set; }
    }

感觉这个可以做成一个通用类,就不用写n个那么麻烦了。

数据传输对象完成后,先定义一个服务接口,代码如下:

public interface IMediaAppService :IAsyncCrudAppService
    {

    }

最后是完成服务类,代码如下:

[AbpAuthorize(PermissionNames.Pages_Articles)]
    public class MediaAppService: AsyncCrudAppService
    {
        public MediaAppService(IRepository repository) : base(repository)
        {
        }

        public override async Task<PagedResultDto> GetAll(GetAllMediaInputDto input)
        {
            CheckGetAllPermission();
            var query = Repository.GetAll().Where(m=>input.Type.Contains((int)m.Type));

            if (!string.IsNullOrEmpty(input.Query)) query = query.Where(m => m.Description.Contains(input.Query));
            if (input.Year != null && input.Month != null)
            {
                query = query.Where(m => m.CreationTime.Year == input.Year && m.CreationTime.Month == input.Month);
            }
            if (input.Day != null)
            {
                query = query.Where(m => m.CreationTime.Day == input.Day);
            }

            var totalCount = await AsyncQueryableExecuter.CountAsync(query);

            query = ApplySorting(query, input);
            query = ApplyPaging(query, input);

            var entities = await AsyncQueryableExecuter.ToListAsync(query);
            return new PagedResultDto(
                totalCount,
                entities.Select(MapToEntityDto).ToList()
            );

        }

        public override async Task Create([FromForm]CreateMediaDto input)
        {
            CheckCreatePermission();
            var stream = input.File.OpenReadStream();
            var allowImageFileType = await SettingManager.GetSettingValueAsync(AppSettingNames.AllowImageFileType);
            var allowAudioFileType = await SettingManager.GetSettingValueAsync(AppSettingNames.AllowAudioFileType);
            var allowVideoFileType = await SettingManager.GetSettingValueAsync(AppSettingNames.AllowVideoFileType);
            var allowUploadSize = await SettingManager.GetSettingValueAsync(AppSettingNames.AllowUploadSize);
            var fileType = stream.GetFileType();
            var ext = fileType?.Extension;
            MediaType? type = null;
            if (allowImageFileType.IndexOf($",{ext},", StringComparison.OrdinalIgnoreCase) >= 0)
            {
                type = MediaType.Image;

            }
            else if (allowAudioFileType.IndexOf($",{ext},", StringComparison.OrdinalIgnoreCase) >= 0)
            {
                type = MediaType.Audio;
            }
            else if (allowVideoFileType.IndexOf($",{ext},", StringComparison.OrdinalIgnoreCase) >= 0)
            {
                type = MediaType.Video;
            }
            if (type == null) throw new UserFriendlyException("fileTypeNotAllow");
            if(stream.Length ==0 || stream.Length>allowUploadSize ) throw new UserFriendlyException("fileSizeNotAllow");
            var filename = ShortGuid.ToShortGuid(Guid.NewGuid());
            var path = $"{filename.Substring(0, 2)}/{filename.Substring(2, 2)}";
            var dir = $"{Environment.CurrentDirectory}\wwwroot\upload\{path}";
            if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
            using (var fileStream = new FileStream($"{dir}\{filename}.{ext}", FileMode.Create))
            {
                await input.File.CopyToAsync(fileStream);
            }
            if (type == MediaType.Image)
            {
                using (var image = Image.Load($"{dir}\{filename}.{ext}"))
                {
                    image.Mutate(x => x
                        .Resize(160, 160));
                    image.Save($"{dir}\thumbnail_{filename}.{ext}");
                }
            }
            var entity = new Media()
            {
                Filename = $"{filename}.{ext}",
                Description = input.File.FileName,
                Size = (int)stream.Length,
                Path = path,
                Type = type ?? MediaType.Image,
                CreatorUserId = AbpSession.UserId,
                TenantId = AbpSession.TenantId ?? 1
            };
            await Repository.InsertAsync(entity);
            await CurrentUnitOfWork.SaveChangesAsync();
            return MapToEntityDto(entity);

        }


        public async Task Delete(DeleteMediaInputDto input)
        {
            CheckDeletePermission();
            foreach (var inputId in input.Id)
            {
                await Repository.DeleteAsync(inputId);
            }

        }

        public async Task<ListResultDto> GetDateList()
        {
            CheckGetAllPermission();
            var query = await Repository.GetAllListAsync();
            var list = from media in query
                let year = media.CreationTime.Year
                let month = media.CreationTime.Month
                group media by new {year, month}
                into g
                orderby g.Key.year descending, g.Key.month descending
                select new ComboBoxItemDto
                {
                    Id = g.Key.year.ToString() + "," + g.Key.month.ToString(),
                    Text = g.Key.year.ToString() + "年" + g.Key.month.ToString() + "月"
                };
            var results =
                new List {new ComboBoxItemDto("all", "全部"), new ComboBoxItemDto("today", "今天")};
            var itemDtos = results.Union(list.ToList());
            return new ListResultDto(itemDtos.ToList());
        }


        [NonAction]
        public override Task Delete(EntityDto input)
        {
            return base.Delete(input);
        }
    }

Create
方法内,使用到了ABP框架的设置管理( SettingManager
),这个,我们需要在Core项目的 Configuration
文件夹下定义,首先要在 AppSettingNames
类中定义一些常量,代码如下:

public static class AppSettingNames
    {
        public const string UiTheme = "App.UiTheme";
        public const string AllowImageFileType = "App.AllowImageFileType";
        public const string AllowAudioFileType = "App.AllowAudioFileType";
        public const string AllowVideoFileType = "App.AllowVideoFileType";
        public const string AllowUploadSize = "App.AllowUploadSize";
    }

这里的定义参考常量 UiTheme
的定义来进行就行了。接下来是在 AppSettingProvider
中添加设置,具体代码如下:

public class AppSettingProvider : SettingProvider
    {
        public override IEnumerable GetSettingDefinitions(SettingDefinitionProviderContext context)
        {
            return new[]
            {
                new SettingDefinition(AppSettingNames.UiTheme, "red", scopes: SettingScopes.Application | SettingScopes.Tenant | SettingScopes.User, isVisibleToClients: true),
                new SettingDefinition(AppSettingNames.AllowImageFileType,",jpg,gif,png,jpeg,",scopes:SettingScopes.All,isVisibleToClients:true),
                new SettingDefinition(AppSettingNames.AllowAudioFileType,",mp3,flac,wav,",scopes:SettingScopes.All,isVisibleToClients:true),
                new SettingDefinition(AppSettingNames.AllowVideoFileType,",mp4,m4v,flv,mov,",scopes:SettingScopes.All,isVisibleToClients:true),
                new SettingDefinition(AppSettingNames.AllowUploadSize,"10485760",scopes:SettingScopes.All,isVisibleToClients:true),
            };
        }
    }

有关 SettingDefinition
的具体信息可参考文档《 设置管理
》。

如果希望将设置定义在 appsettings.json
中,可以在Web.Core项目中添加一个从 SettingProvider
派生的类,然后在 GetSettingDefinitions
方法内从 appsettings.json
读取所需的配置再。最后调用 Configuration.Settings.Providers.Add
方法添加配置。

在获取到允许的文件类型和文件大小后,就可通过 Mime-Detective
包提供的扩展方法 GetFileType
来获取文件的实际类型了。如果实际的扩展名在允许的文件类型内,则设置媒体文件的类型。如果文件的大小也在允许范围内,则调用 ShortGuid.ToShortGuid
方法来生成一个文件名,并将文件名的前两个字符作为一级路径,第3、4位字符作为一个路径。在应用服务内,使用 Environment.CurrentDirectory
来获取当前工作目录比较简单,也不需要做转换,可抛弃 MapPath
方法。对于Asp.Net Core项目,直接访问路径是在 wwwroot
文件夹,不能直接方在工作目录的根文件夹,这个一定要注意。创建存放文件夹后,就可调用 CopyToAsync
方法将文件保存了。

由于 ImageResizer
包不支持Asp.Net Core,而后续项目 imageflow
虽然有测试版,但完全没文档说明如何去使用,只有暂时放下这东西,乖乖的创建一个缩略图了。在寻找可支持Asp.Net Core的图像处理库的时候,找到了《 .NET Core Image Processing
.NET Core Image Processing这篇文章,介绍了很多处理图片的库,最终选择了 SixLabors.ImageSharp
这个库。选择这个库的主要原因是不需要其他库的支持也可以支持Window和Linux环境,比较方便。

在保存缩略图后,就可创建实体并添加到数据库了,最后返回实体。

在媒体管理视图中,有个一个查询是根据日期来查询媒体的,而这需要服务器端返回根据年和月分组后的日期,因而需要创建一个 GetDateList
方法。在方法内,使用了根据前端需要定义的一个下拉列表类,具体代码如下:

[Serializable]
    public class ComboBoxItemDto
    {
        public string Text { get; set; }

        public string Id { get; set; }


        public ComboBoxItemDto()
        {

        }

        public ComboBoxItemDto(string id, string text)
        {
            Text = text;
            Id = id;
        }

    }

这个类是根据框架提供的下拉列表类修改属性后创建的。区域其实不大,但方便Ext JS使用。

GetDateList
方法内先通过分组创建一个 ComboBoxItemDto
列表,然后与额外的两个选项合并后返回客户端。

至此,后台代码就完成了,下面来完成客户端代码。

在客户端先把模型等与字段相关的地方修改好。接下来要修改的是视图模型内 datelists
存储的访问地址,需要将地址 datelist
修改为 getdatelist

最关键的修改是要在视图控制器的 onBeforeUpload
方法内,为 plupload
的请求添加认证头,代码如下:

onBeforeUpload: function (cmp, uploader, file) {
        var me = this,
            tb = me.lookupReference('progressToolBar'),
            progress = tb.down('progressbar');
        uploader.setOption('headers',Ext.apply( {}, HEADERS.getHeaders()));
        progress.setValue(0);
        progress.updateText(Ext.String.format(I18N.Uploading, file.name, 0));
        tb.show();
    },

plupload
提供了 setOption
方法来修改选择,把 headers
选项加进去就行了,难度不大。如果不使用 plupload
,而是使用Ajax提交,可以参考这个说明: https://stackoverflow.com/questions/37258612/upload-document-to-web-api

余下要修改的是 onDescriptionEditComplete
方法,为Ajax请求配置 method
PUT
,将 params
修改为 jsonData
.。

至此,文件上传功能就已经完成了。

项目源代码: https://gitee.com/tianxiaode

稿源:黄灯桥的专栏 (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 后端存储 » Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之文件上传

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录