欢迎光临
我们一直在努力

基于AspNetWebApi实现简单的文件服务

文章目录

    • 概要
    • 设计思路
    • 具体代码实现
      • 1、文件服务API接口
      • 2、文件服务API接口调用工具类
    • 服务调用示例
      • 1、后端API接口
      • 2、前端代码(Vue2+ElementUI实现)
      • 3、示例效果

概要

说到文件服务可能很多朋友会选择集成minio这些框架,但是对于一些大部分的系统来说,不会用到很复杂的场景。本文将分享基于AspNet WebApi(.NET4.5)实现一个简单文件服务。

设计思路

1、所有文件放在网站的根目录下Uploads文件夹中(其实也考虑过放其他盘,但是我预览文件要通过URL访问,所以只有放网站目录下); 2、文件名格式为“文件ID.扩展名”,后面访问文件也是通过文件Id查找; 3、设计文件上传、获取文件路径、删除文件三个接口; 4、封装一个工具类,用于调用文件服务。

具体代码实现

1、文件服务API接口

1、单个文件限制最大100M,这个可以自己调整(这里需要注意WebApi项目本身也是对请求的报文有限制,这个怎么弄搜一下就行); 2、对于允许哪些扩展名的文件,配置文件里面配AllowedExtensions;

BaseController代码

public abstract class BaseController : ApiController
{
protected IHttpActionResult Success(string message = "", object data = null)
{
return Json(new { Code = 0, Message = message, Data = data });
}
protected IHttpActionResult Fail(string message = "", object data = null)
{
return Json(new { Code = 1, Message = message, Data = data });
}
}

Web.config

<appSettings>
<add key="AllowedExtensions" value="jpg,jpeg,png,gif,pdf,txt,doc,docx,xls,xlsx,zip,rar" />
</appSettings>

FileController

public class FileController : BaseController
{
private const string folder = "~/Uploads";
private readonly string uploadFolder;

private readonly long maxFileSize = 100 * 1024 * 1024;
private readonly string[] allowedExtensions;

public FileController()
{
allowedExtensions = ConfigurationManager.AppSettings["AllowedExtensions"].Split(',').Select(s => "." + s).ToArray();

uploadFolder = HttpContext.Current.Server.MapPath(folder);
if (!Directory.Exists(uploadFolder))
Directory.CreateDirectory(uploadFolder);
}

[HttpPost]
public async Task<IHttpActionResult> UploadFile()
{
if (!Request.Content.IsMimeMultipartContent())
return Fail("请选择文件上传");

var provider = new MultipartFormDataStreamProvider(Path.GetTempPath());
await Request.Content.ReadAsMultipartAsync(provider);

if (!provider.FileData.Any())
return Fail("没有上传文件");

var resultFiles = new List<object>();
var savedFiles = new List<string>();

var baseUrl = $"{Request.RequestUri.Scheme}://{Request.RequestUri.Authority}";
var relativePath = folder.TrimStart('~');

try
{
foreach (var fileData in provider.FileData)
{
var tempPath = fileData.LocalFileName;
var originalName = fileData.Headers.ContentDisposition.FileName.Trim('\\"');
var extension = Path.GetExtension(originalName).ToLower();

if (!allowedExtensions.Contains(extension))
throw new Exception($"不支持的文件类型: {extension}");

var fileInfo = new FileInfo(tempPath);
if (fileInfo.Length > maxFileSize)
throw new Exception($"文件大小超过100MB: {originalName}");

var fileId = Guid.NewGuid().ToString();
var newFileName = fileId + extension;
var savePath = Path.Combine(uploadFolder, newFileName);

File.Move(tempPath, savePath);
savedFiles.Add(savePath);

var fileUrl = $"{baseUrl}{relativePath}/{newFileName}";
resultFiles.Add(new { FileId = fileId, FilePath = fileUrl });
}

return Success(data: resultFiles);
}
catch (Exception ex)
{
foreach (var savedFile in savedFiles)
{
try { File.Delete(savedFile); } catch { }
}
foreach (var fileData in provider.FileData)
{
try { File.Delete(fileData.LocalFileName); } catch { }
}
return Fail($"文件上传失败: {ex.Message}");
}
}

[HttpPost]
public IHttpActionResult GetFilePaths(string[] fileIds)
{
var baseUrl = $"{Request.RequestUri.Scheme}://{Request.RequestUri.Authority}";
var relativePath = folder.TrimStart('~');
var resultFiles = new List<object>();
foreach (string fileId in fileIds)
{
var filePath = Directory.GetFiles(uploadFolder, fileId + ".*").FirstOrDefault();
if (filePath == null)
return Fail($"文件不存在{fileId}");

var fileName = Path.GetFileName(filePath);
resultFiles.Add(new { FileId = fileId, FilePath = $"{baseUrl}{relativePath}/{fileName}" });
}

return Success(data: resultFiles);
}

[HttpPost]
public IHttpActionResult DeleteFiles(string[] fileIds)
{
if (fileIds == null || fileIds.Length == 0)
return Fail("请提供要删除的文件ID");

using (var scope = new TransactionScope())
{
foreach (var fileId in fileIds)
{
var filePath = Directory.GetFiles(uploadFolder, fileId + ".*").SingleOrDefault();
if (filePath == null)
return Fail($"文件不存在: {fileId}");

File.Delete(filePath);
}
scope.Complete();
}
return Success();
}
}

2、文件服务API接口调用工具类

public class FileUploader
{
private string fileServiceUrl;
private HttpClient http;

public FileUploader(string fileServiceUrl)
{
this.fileServiceUrl = fileServiceUrl;
http = new HttpClient();
}

public async Task<dynamic> Upload(MultipartFileData[] files)
{
using (var form = new MultipartFormDataContent())
{
foreach (var file in files)
{
var fileBytes = File.ReadAllBytes(file.LocalFileName);
var content = new ByteArrayContent(fileBytes);
form.Add(content, "file", file.Headers.ContentDisposition.FileName.Trim('\\"'));
}

var url = fileServiceUrl + "/api/File/UploadFile";
var response = await http.PostAsync(url, form);
var json = await response.Content.ReadAsStringAsync();

return Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
}
}

public async Task<dynamic> GetFilePaths(string[] fileIds)
{
var url = fileServiceUrl + "/api/File/GetFilePaths";
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(fileIds),
Encoding.UTF8, "application/json");

var response = await http.PostAsync(url, content);
var json = await response.Content.ReadAsStringAsync();

return Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
}

public async Task<dynamic> DeleteFiles(string[] fileIds)
{
var url = fileServiceUrl + "/api/File/DeleteFiles";
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(fileIds),
Encoding.UTF8, "application/json");

var response = await http.PostAsync(url, content);
var json = await response.Content.ReadAsStringAsync();

return Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
}
}

服务调用示例

1、我设计的都是通过后端调文件服务接口,前端不直接调用文件服务 2、文件上传都是先保存在系统临时目录,所以一定要记得清除啊。。。 3、前端示例代码Vue2写的 4、有些细枝末节的都没有粘了啊,应该可以看懂,EF自己配一下

1、后端API接口

public class MyTestController : ApiController
{
private FileUploader fileUploader;

public MyTestController()
{
fileUploader = new FileUploader(ConfigurationManager.AppSettings["FileServiceUrl"]);
}

[HttpPost]
public async Task<IHttpActionResult> UploadTest()
{
var provider = new MultipartFormDataStreamProvider(Path.GetTempPath());
await Request.Content.ReadAsMultipartAsync(provider);

if (!provider.FileData.Any())
return BadRequest("没有上传文件");

try
{
var result = await fileUploader.Upload(provider.FileData.ToArray());
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
finally
{
foreach (var file in provider.FileData)
{
if (!File.Exists(file.LocalFileName))
continue;
File.Delete(file.LocalFileName);
}
}
}

[HttpGet]
public async Task<IHttpActionResult> QueryUserDatas()
{
user[] users;
using (var dbContext = new jonas_testEntities())
users = dbContext.user.ToArray();

var result = await fileUploader.GetFilePaths(users.Select(u => u.IconImageId).ToArray());
if (result.Code != 0)
return Json(result);
var fileInfos = ((IEnumerable<dynamic>)result.Data).Select(d => new { d.FileId, d.FilePath }).ToArray();

List<QueryUserDatasResultItem> list = new List<QueryUserDatasResultItem>();
foreach (var user in users)
{
QueryUserDatasResultItem model = new QueryUserDatasResultItem
{
Account = user.Account,
RealName = user.RealName
};
model.IconImageUrl = fileInfos.Single(s => s.FileId == user.IconImageId).FilePath;
list.Add(model);
}
return Json(list);
}

[HttpPost]
public async Task<IHttpActionResult> AddUser()
{
if (!Request.Content.IsMimeMultipartContent())
return Json(new { Code = 1, Message = "请求格式错误" });

var provider = new MultipartFormDataStreamProvider(Path.GetTempPath());
await Request.Content.ReadAsMultipartAsync(provider);

try
{
var formData = provider.FormData;
var user = new user
{
Account = formData["Account"],
RealName = formData["RealName"]
};

if (provider.FileData.Count > 0)
{
var fileData = provider.FileData[0];
var result = await fileUploader.Upload(new[] { fileData });
if (result.Code != 0)
return Json(result);
user.IconImageId = result.Data[0].FileId;
}

using (var dbContext = new jonas_testEntities())
{
if (dbContext.user.Any(u => u.Account == user.Account))
return Json(new { Code=1,Message="账号已经存在"});
dbContext.user.Add(user);
await dbContext.SaveChangesAsync();
}

return Json(new { Code = 0 });
}
catch (Exception ex)
{
return Json(new { Code = 1, Message = ex.Message });
}
finally
{
foreach (var fileData in provider.FileData)
{
try
{
if (File.Exists(fileData.LocalFileName))
{
File.Delete(fileData.LocalFileName);
}
}
catch
{
}
}
}
}

[HttpPost]
public async Task<IHttpActionResult> DeleteUser(string account)
{
user user = null;
using (var dbContext = new jonas_testEntities())
{
user = dbContext.user.SingleOrDefault(u => u.Account == account);
if (user == null)
return Json(new { Code = 1, Message = "用户不存在" });
dbContext.user.Remove(user);
await dbContext.SaveChangesAsync();
}

await fileUploader.DeleteFiles(new string[] { user.IconImageId });

return Json(new { Code = 0 });
}
}

2、前端代码(Vue2+ElementUI实现)

<template>
<div id="app">
<el–table :data="tableDatas" style="width: 800px;" stripe border>
<el–table–column label="账号" prop="Account" width="150">
</el–table–column>
<el–table–column label="真实姓名" prop="RealName" width="150">
</el–table–column>
<el–table–column label="头像" width="100">
<template slot–scope="scope">
<span style="color: #409EFF; cursor: pointer;" @click="previewIconHandler(scope.row.IconImageUrl)">预览</span>
</template>
</el–table–column>
<el–table–column width="150">
<template #header>
<el–button type="primary" size="medium" @click="dialogAdd.show = true">新增</el–button>
</template>
<template slot–scope="scope">
<el–button type="danger" size="small" @click="removeUserHandler(scope.row.Account)">删除</el–button>
</template>
</el–table–column>
</el–table>

<el–dialog title="预览头像" width="500px" :visible.sync="dialogPreviewIcon.show" :append–to–body="true"
:modal–append–to–body="false" :lock–scroll="false">
<div style="width: 100%;height: 500px;">
<img :src="dialogPreviewIcon.src" style="width: 100%;height: 100%;">
</div>
</el–dialog>

<el–dialog title="新增用户" width="800px" :visible.sync="dialogAdd.show" :append–to–body="true"
:modal–append–to–body="false" :lock–scroll="false">
<el–form :model="dialogAdd.userInfo" label–width="150px" class="demo-ruleForm">
<el–form–item label="账号">
<el–input v–model="dialogAdd.userInfo.Account"></el–input>
</el–form–item>
<el–form–item label="真实姓名">
<el–input v–model="dialogAdd.userInfo.RealName"></el–input>
</el–form–item>
<el–form–item label="头像">
<el–upload class="upload-demo" ref="upload" action="" :on–remove="removeOrChangeHandler"
:file–list="dialogAdd.files" :auto–upload="false" :on–change="removeOrChangeHandler">
<el–button slot="trigger" size="small" type="primary">选取文件</el–button>
</el–upload>
</el–form–item>
<el–form–item>
<el–button type="primary" @click="submitFormHandler">提交</el–button>
</el–form–item>
</el–form>
</el–dialog>
</div>
</template>
<script>
import axios from 'axios';

const baseUrl = 'http://localhost:4623'
export default {
data() {
return {
tableDatas: [],
dialogPreviewIcon: {
show: false,
src: ''
},
dialogAdd: {
show: false,
userInfo: {
Account: '',
RealName: ''
},
files: []
}
}
},
methods: {
async queryDatas() {
const { data: res } = await axios.get(baseUrl + '/api/MyTest/QueryUserDatas')
this.tableDatas = res
},
previewIconHandler(IconImageUrl) {
this.dialogPreviewIcon.src = IconImageUrl
this.dialogPreviewIcon.show = true
},
async submitFormHandler() {
console.log(this.dialogAdd)
if (this.dialogAdd.userInfo.Account === '' || this.dialogAdd.userInfo.RealName === '' || this.dialogAdd.files.length === 0)
return alert('请填写完整信息')

let formdata = new FormData()
formdata.append('file', this.dialogAdd.files[0].raw)
formdata.append('Account', this.dialogAdd.userInfo.Account)
formdata.append('RealName', this.dialogAdd.userInfo.RealName)

const { data: res } = await axios.post(baseUrl + '/api/MyTest/AddUser', formdata, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (res.Code != 0)
return alert(res.Message)
this.dialogAdd.show = false
this.queryDatas()
},
async removeUserHandler(account) {
const { data: res } = await axios.post(baseUrl + '/api/MyTest/DeleteUser?account=' + account)
if (res.Code != 0)
return alert(res.Message)
this.queryDatas()
},

removeOrChangeHandler(file, files) {
this.dialogAdd.files = files
},

},
created() {
this.queryDatas()
}
}
</script>
<style lang="less"></style>

3、示例效果

在这里插入图片描述 在这里插入图片描述

赞(0)
未经允许不得转载:171主机测评 » 基于AspNetWebApi实现简单的文件服务
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址