版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tianxiaode/article/details/79048253
————————————————
版权声明:本文为CSDN博主「上将军」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tianxiaode/article/details/79048253
- 尝试新的开发组合:Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS
- Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之配置IdentityServer
- Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之数据迁移
- Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之添加实体
- Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之显示登录视图
- Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之验证码
- Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之登录、权限、菜单和登出
- Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之文章管理
- Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之文件上传
媒体管理的主要难度就在于媒体文件的上传,由于使用了plupload作为上传工具,因而整个实现流程还是比较简单。
由于媒体实体没有大文本数据,因而Get
和GetAll
方法使用相同的数据传输对象就可以,具体代码如下:
[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
类中定义一些常量,代码如下:
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请求配置method
为PUT
,将params
修改为jsonData
.。
至此,文件上传功能就已经完成了。
项目源代码:https://gitee.com/tianxiaode
<li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true"> <use xlink:href="#csdnc-thumbsup"></use> </svg><span class="name">点赞</span> <span class="count"></span> </a></li> <li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{"mod":"popu_824"}"><svg class="icon" aria-hidden="true"> <use xlink:href="#icon-csdnc-Collection-G"></use> </svg><span class="name">收藏</span></a></li> <li class="tool-item tool-active is-share"><a href="javascript:;"><svg class="icon" aria-hidden="true"> <use xlink:href="#icon-csdnc-fenxiang"></use> </svg>分享</a></li> <!--打赏开始--> <!--打赏结束--> <li class="tool-item tool-more"> <a> <svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg> </a> <ul class="more-box"> <li class="item"><a class="article-report">文章举报</a></li> </ul> </li> </ul> </div> </div> <div class="person-messagebox"> <div class="left-message"><a href="https://blog.csdn.net/tianxiaode"> <img src="https://profile.csdnimg.cn/7/E/6/3_tianxiaode" class="avatar_pic" username="tianxiaode"> <img src="https://g.csdnimg.cn/static/user-reg-year/1x/19.png" class="user-years"> </a></div> <div class="middle-message"> <div class="title"><span class="tit"><a href="https://blog.csdn.net/tianxiaode" data-report-click="{"mod":"popu_379"}" target="_blank">上将军</a></span> <span class="flag expert"> <a href="https://blog.csdn.net/home/help.html#classicfication" target="_blank"> <svg class="icon" aria-hidden="true"> <use xlink:href="#csdnc-blogexpert"></use> </svg> 博客专家 </a> </span> </div> <div class="text"><span>发布了179 篇原创文章</span> · <span>获赞 153</span> · <span>访问量 176万+</span></div> </div> <div class="right-message"> <a href="https://bbs.csdn.net/topics/395531463" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-messageboard">他的留言板 </a> <a class="btn btn-sm bt-button personal-watch" data-report-click="{"mod":"popu_379"}">关注</a> </div> </div> </div>