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

  1. 尝试新的开发组合:Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS
  2. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之配置IdentityServer
  3. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之数据迁移
  4. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之添加实体
  5. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之显示登录视图
  6. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之验证码
  7. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之登录、权限、菜单和登出
  8. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之文章管理
  9. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之文件上传

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

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

    [AutoMapFrom(typeof(Media))]
    public class MediaDto: EntityDto<long>
    {
        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<long>
    {
        [MaxLength(Media.MaxDescriptionLength)]
        public string Description { get; set; }
    }

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

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

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

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

    public interface IMediaAppService :IAsyncCrudAppService<MediaDto, long>
    {

    }

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

    [AbpAuthorize(PermissionNames.Pages_Articles)]
    public class MediaAppService: AsyncCrudAppService<Media, MediaDto, long, GetAllMediaInputDto, CreateMediaDto, UpdateMediaInputDto, MediaDto>
    {
        public MediaAppService(IRepository<Media, long> repository) : base(repository)
        {
        }

        public override async Task<PagedResultDto<MediaDto>> 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<MediaDto>(
                totalCount,
                entities.Select(MapToEntityDto).ToList()
            );

        }

        public override async Task<MediaDto> 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<int>(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<Rgba32>($"{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<ComboBoxItemDto>> 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<ComboBoxItemDto> {new ComboBoxItemDto("all", "全部"), new ComboBoxItemDto("today", "今天")};
            var itemDtos = results.Union(list.ToList());
            return new ListResultDto<ComboBoxItemDto>(itemDtos.ToList());
        }


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

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

扫描二维码关注公众号,回复: 1704859 查看本文章
    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<SettingDefinition> 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请求配置methodPUT,将params修改为jsonData.。

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

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

猜你喜欢

转载自blog.csdn.net/tianxiaode/article/details/79048253
今日推荐