前言:
1、由于项目是.NET Framework 4.7 MVC LayUI,所以需要找一个资源站点存放项目中静态资源文件;
2、需要支持服务端和客户端都支持上传文件方式;
3、调用简单,涉及库越少越好。
结果:
调用 AWSSDK.S3 和 AWSSDK.Core 实现文件上传到 MinIO ;调用MimeMapping获取文件ContentType
MinIO
Minio是Apache License v2.0下发布的对象存储服务器。它与Amazon S3云存储服务兼容。它最适合存储非结构化数据,如照片,视频,日志文件,备份和容器/ VM映像。对象的大小可以从几KB到最大5TB Minio服务器足够轻,可以与应用程序堆栈捆绑在一起,类似于NodeJS,Redis和MySQL。
AWS S3
全称:(Amazon Simple Storage Service), 是一种面向网络的存储服务,可以支持用户随时在Web的任何位置,存储和检索任意大小的数据本身也提供了简单而直观的管理控制台来处理这些任务,但我这里使用的是,面向C# 方向的S3网络存储服务。
具体调研实现方法如下
1、服务器的配置
推荐版本:minio.RELEASE.2022-05-26T05-48-41Z
当前版本文件可来取自如。
2、服务端上传实现代码
需要NuGit调用有三项
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AWSSDK.Core" version="3.7.304.16" targetFramework="net47" />
<package id="AWSSDK.S3" version="3.7.309.4" targetFramework="net47" />
<package id="MimeMapping" version="3.0.1" targetFramework="net47" />
</packages>
具体实现代码
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace DealMvc.AmazonS3 {
/// <summary>
/// 实现MinIO文件的上传
/// 调用 AWSSDK.S3 和 AWSSDK.Core
/// 文件的 ContentType 调用 MimeMapping 实现
/// </summary>
public class AmazonHelper {
/// <summary>
/// Bucket访问AccessKey
/// </summary>
readonly string AccessKey = "admin";
/// <summary>
/// Bucket访问SecretKey
/// </summary>
readonly string SecretKey = "123456+++";
/// <summary>
/// 存储桶名称
/// </summary>
readonly string BucketName = "test";
/// <summary>
/// 服务地址
/// </summary>
readonly string ServiceURL = "http://192.168.20.66:9000";
/// <summary>
/// 预览地址
/// </summary>
readonly string WebURL = "https://res.test.cn";
/// <summary>
///
/// </summary>
//readonly string awsRegion = "us-east-1";
readonly AmazonS3Client client;
/// <summary>
/// 构造函数
/// </summary>
public AmazonHelper() {
//提供awsAccessKeyId和awsSecretAccessKey构造凭证
var credentials = new BasicAWSCredentials(AccessKey, SecretKey);
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config {
// 必须在设置ServiceURL前进行设置,并且需要和`MINIO_REGION`环境变量一致。
//RegionEndpoint = RegionEndpoint.GetBySystemName(awsRegion),
RegionEndpoint = RegionEndpoint.USEast1,
// 替换成你自己的MinIO Server的URL
ServiceURL = ServiceURL,
// 必须设为true
ForcePathStyle = true,
};
client = new AmazonS3Client(credentials, clientConfig);
}
#region 上传文件
/// <summary>
/// 上传文件[本地]
/// </summary>
/// <param name="key"></param>
/// <param name="filePath">路径</param>
/// <returns>网址</returns>
public async Task<string> UploadFilePath(string saveKey, string filePath) {
var request = new PutObjectRequest {
BucketName = BucketName,
Key = saveKey,
ContentType = GetContentType(saveKey),
FilePath = filePath,
};
var response = await client.PutObjectAsync(request);
//
return WebUrlKey(response.HttpStatusCode, saveKey);
}
/// <summary>
/// 上传文件[流]
/// </summary>
public async Task<string> UploadInputStream(string saveKey, Stream inputStream) {
var request = new PutObjectRequest {
BucketName = BucketName,
Key = saveKey,
ContentType = GetContentType(saveKey),
InputStream = inputStream //File.OpenRead
};
var response = await client.PutObjectAsync(request);
//
return WebUrlKey(response.HttpStatusCode, saveKey);
}
#endregion
/// <summary>
/// 客户端上传文件
/// </summary>
public string GetPreSignedUrl(string saveKey) {
var request = new GetPreSignedUrlRequest {
BucketName = BucketName,
Key = saveKey,
Expires = DateTime.UtcNow.AddMinutes(15),
Verb = HttpVerb.PUT,
ContentType = GetContentType(saveKey),
Protocol = Protocol.HTTP // 指定使用HTTPS协议
};
var url= client.GetPreSignedURL(request);
return url;
}
#region 私有方法
/// <summary>
/// 根据文件Key返回预览地址
/// </summary>
private string WebUrlKey(HttpStatusCode statusCode, string saveKey) {
if (statusCode != System.Net.HttpStatusCode.OK)
throw new Exception("上传文件失败");
return WebURL + "/" + BucketName + "/" + saveKey;
}
/// <summary>
/// 根据文件获取ContentType
/// </summary>
private string GetContentType(string saveKey) {
return MimeMapping.MimeUtility.GetMimeMapping(Path.GetFileName(saveKey));
}
#endregion
}
}
控制器中实现代码
// 用于服务端本地文件
[HttpGet]
public async Task<ActionResult> UpFilePathAmazon() {
var filePath = @"D:\a\KAT\01.jpg";
var key = "kat/" + 0L.NewLongId() + System.IO.Path.GetExtension(filePath);
var url = await amazon.UploadFilePath(key, filePath);
return Content(url);
}
// 用于服务端上传文件
[HttpGet]
public async Task<ActionResult> UpFileStreamAmazon() {
///pdfFile[0].OpenReadStream()
var filePath = @"D:\a\KAT\02.jpg";
var key = "kat/" + 0L.NewLongId() + System.IO.Path.GetExtension(filePath);
var stream = System.IO.File.OpenRead(filePath);
var url = await amazon.UploadInputStream(key, stream);
return Content(url);
}
// 用于客户端上传文件
[HttpPost]
public ActionResult UploadImgUrl(string filename) {
string fileType = "images";
string fileExt = System.IO.Path.GetExtension(filename).ToLower();
string saveKey = fileType+"/" + 0L.NewLongId() + fileExt;
var url = new DealMvc.AmazonS3.AmazonHelper().GetPreSignedUrl(saveKey);
return JsonMessageData(new{
url,
});
}
前端页面实现方式(代码中涉及到)
代码中涉及到LayUI前端框架如下:
HTML代码
<form class="layui-form" lay-filter="subform">
<div class="layui-form-item">
<label class="layui-form-label"><em>*</em>广告图片</label>
<div class="layui-input-inline" style="width: 660px;">
<div class="flex align-center margin-bottom-sm">
<button type="button" class="layui-btn layui-btn-primary layui-border-green layui-btn-sm margin-right-sm" id="ID-PicUrl">
<i class="layui-icon layui-icon-upload"></i> 上传图片
</button>
<div class="">宽高比例按4:3,格式png、jpg</div>
</div>
<input name="PicUrl" lay-verify="required" lay-reqtext="请上传 广告图片" autocomplete="off" class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="layui-btn" onclick="uploadFile()">Upload</button>
</div>
</div>
</form>
JavaScript代码
layui.use(function () {
var upload = layui.upload;
var layer = layui.layer;
//执行upload组件实例
upload.render({
auto: false,//选择文件后不自动上传
elem: '#ID-PicUrl', //绑定元素
acceptMime: 'image/jpeg, image/png, image/gif',
choose: function (obj) {
//start
var files = obj.pushFile(), thisStart = 0, keysArray = Object.keys(files);
if (files && keysArray.length > 0) {
keysArray.forEach(function (key) {
if (thisStart === keysArray.length - 1) {
let index = key
, file = files[key]
, resultObj = files[key]
;
let requestData = {};
if (file && file.name) {
thisFilename = file.name;
requestData = { filename: thisFilename };
}
UploadToken(file, requestData);
} else {
layer.msg("上传时发生异常,请重试 ~ ", { icon: 2, time: 1500 });
}
thisStart++;
});
}
}
, error: function () {
layer.alert("网络异常,请重试 ~ ", { title: "上传失败提示", icon: 2, })
}
});
// 通过文件名称获取到服务器存放路径
var UploadToken = (file, requestData) => {
$.ajax({
url: "/File/UploadImgUrl",
type: "POST",
data: requestData,
dataType: "json",
success: function (result) {
//console.log("token", result)
if (result.status) {
let thisData = result.data;
UploadLoadeer(file, thisData)
} else {
layer.alert("网络异常,请重试 ~ ", { title: "上传失败提示", icon: 2, })
}
},
error: function () {
layer.alert("网络异常,请重试 ~ ", { title: "上传失败提示", icon: 2, })
}
});
}
// 通过XMLHttpRequest 方式将文件上传到服务器
var UploadLoadeer = (file, thisData) => {
let { url } = thisData;
console.log(url);
// 使用预签名URL上传文件
const xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('Content-Type', file.type);
xhr.onload = function () {
if (xhr.status === 200) {
alert('File uploaded successfully.');
} else {
alert('File upload failed.');
}
};
xhr.send(file);
}
});