参考之前的文章,不多叙述
传送门:Windows Server搭建MinIO快速指南
安装如下几个库
Install-Package Chloe
Install-Package Chloe.Extension
Install-Package Chloe.SQLite
Chloe是ORM框架,这里用SQLite数据库来记录文件上传记录及文件相关信息
Install-Package Microsoft.Data.Sqlite.Core
Install-Package Minio
操作MinIO对象存储的SDK
Install-Package Newtonsoft.Json
Json序列化和反序列化库
Install-Package NLog
日志记录
Install-Package Quartz
定时任务框架,用来定时清理临时文件
Install-Package SharpYaml
用来配置系统各项参数,使用yml作为系统配置文件
Install-Package SixLabors.ImageSharp
Image处理框架
Install-Package Swashbuckle.AspNetCore.Annotations
Swagger注解库
Install-Package System.Data.SQLite.Core
SQLite官方驱动
Databases:
SQLite:
Path: daka.data.db
MinIO:
EndPoint: 127.0.0.1:9001
APIEndPoint: 127.0.0.1:9000
UseSsl: false
FileBucket: file
ImageBucket: image
ReportBucket: report
AccessKey: dakapath
SecertKey: 123456
System:
Title: 杭州大伽文件服务接口系统
Company: 杭州大伽信息科技有限公司
TempPath: Temp
TempCleanJobCron: 0 0/1 * * * ?
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="info" xsi:type="ColoredConsole"
layout="${longdate} | ${uppercase:${level}} | ${message}" />
<target name="debug" xsi:type="Debugger"
layout="${longdate} | ${uppercase:${level}} | ${message}" />
<target name="error" xsi:type="File"
fileName="${basedir}/Logs/${shortdate}-Error.txt"
maxArchiveFiles="100"
layout="${longdate} | ${uppercase:${level}} | ${message}" />
<target name="trace" xsi:type="File"
fileName="${basedir}/Logs/${shortdate}-Trace.txt"
maxArchiveFiles="100"
layout="${longdate} | ${uppercase:${level}} | ${message}" />
<target name="fatal" xsi:type="File"
fileName="${basedir}/Logs/${shortdate}-Fatal.txt"
maxArchiveFiles="100"
layout="${longdate} | ${uppercase:${level}} | ${message}" />
targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="debug" />
<logger name="*" minlevel="Error" writeTo="error" />
<logger name="*" minlevel="Info" writeTo="info" />
<logger name="*" minlevel="Trace" writeTo="trace" />
<logger name="*" minlevel="Fatal" writeTo="fatal" />
rules>
nlog>
using Chloe;
using Chloe.SQLite;
using DakaPathAppFileService.ConfigModels;
using DakaPathAppFileService.DataHelper;
using DakaPathAppFileService.ExtendMethod;
using NLog;
using SharpYaml.Serialization;
using System;
using System.IO;
namespace DakaPathAppFileService
{
public class App
{
static readonly Logger logger = LogManager.GetCurrentClassLogger();
private static string SQLiteConnectionString;
public static string MinIOEndPoint;
public static string MinIOAPIEndPoint;
public static bool? MinIOUseSsl;
public static string MinIOFileBucket;
public static string MinIOImageBucket;
public static string MinIOReportBucket;
public static string MinIOAccessKey;
public static string MinIOSecertKey;
public static string SystemTitle;
public static string SystemCompany;
public static string SystemTempPath;
public static string SystemTempCleanJobCron;
public static int YearTime => DateTime.Now.Year;
public static DbContext sqlite
{
get
{
return SQLiteConnectionString.IsBlank() ? null : new SQLiteContext(new SQLiteConnectionFactory(SQLiteConnectionString));
}
}
public static void ReadSystemConfig()
{
try
{
var serializer = new Serializer();
string configConfigPath = Path.Combine(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "config.yml");
if (configConfigPath.IsBlank())
{
return;
}
if (!File.Exists(configConfigPath))
{
return;
}
using (Stream f = new FileStream(configConfigPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
DakaPathAppFileServiceYamlConfigModel Config = serializer.Deserialize<DakaPathAppFileServiceYamlConfigModel>(f);
SQLiteConnectionString = Config.Databases.SQLite.GetConnectionString();
MinIOEndPoint = Config.MinIO.EndPoint;
MinIOAPIEndPoint = Config.MinIO.APIEndPoint;
MinIOUseSsl = Config.MinIO.UseSsl;
MinIOFileBucket = Config.MinIO.FileBucket;
MinIOImageBucket = Config.MinIO.ImageBucket;
MinIOReportBucket = Config.MinIO.ReportBucket;
MinIOAccessKey = Config.MinIO.AccessKey;
MinIOSecertKey = Config.MinIO.SecertKey;
SystemTitle = Config.System.Title;
SystemCompany = Config.System.Company;
SystemTempPath = Config.System.TempPath;
SystemTempCleanJobCron = Config.System.TempCleanJobCron;
}
logger.Trace($"配置载入完毕!");
}
catch (Exception e)
{
logger.Error($"配置载入失败: {e}");
}
}
public App()
{
var logoDirPath = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
NLog.GlobalDiagnosticsContext.Set("LogPath", logoDirPath);
}
}
}
在Program
中添加配置载入入口和定时任务的触发
using DakaPathAppFileService.Jobs;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Quartz;
using Quartz.Impl;
namespace DakaPathAppFileService
{
public class Program
{
public static void Main(string[] args)
{
App.ReadSystemConfig();
IScheduler scheduler;
ISchedulerFactory factory;
factory = new StdSchedulerFactory();
scheduler = factory.GetScheduler().Result;
scheduler.Start().Wait();
IJobDetail job = JobBuilder.Create<TempCleanJob>().WithIdentity("TempCleanJob", "Group").Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("TempCleanJob", "Group")
.WithCronSchedule(App.SystemTempCleanJobCron)
.Build();
scheduler.ScheduleJob(job, trigger).Wait();
scheduler.Start().Wait();
CreateHostBuilder(args).Build().Run();
scheduler.Shutdown();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
用Put、Delete、Get
三种Http请求分别对应接口的增删查
。
Produces
注解是声明返回的Content-Type
类型SwaggerParameter
注解是解释参数的意义Sign
和TimeStamp
用来验证接口请求是否合法,简单加强接口安全性文件上传接口代码如下:
[HttpPut, Route("upload"), Produces("application/json")]
public async Task<object> Upload([SwaggerParameter("文件")] IFormFile file
, [SwaggerParameter("Sign")] string sign
, [SwaggerParameter("TimeStamp")] long? t)
{
dynamic json = new ExpandoObject();
json.data = null;
if (sign.IsBlank() || !t.HasValue)
{
json.code = 1;
json.msg = $"参数异常";
return json;
}
if (t.Value <= 0)
{
json.code = 1;
json.msg = $"接口请求超时";
return json;
}
var dis = Math.Abs(DateTime.Now.Subtract(t.Value.TimeStampToDateTime().Value).TotalSeconds);
if (dis > 5)
{
json.code = 1;
json.msg = $"接口请求超时";
return json;
}
var _sign = $"LONG.{t}AT.STRING".MD5Encrypt32().ToUpper();
if (sign != _sign)
{
json.code = 1;
json.msg = $"签名验证失败";
return json;
}
try
{
MinioClient mc = new MinioClient(App.MinIOAPIEndPoint, App.MinIOAccessKey, App.MinIOSecertKey);
var fileSaveName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
var filePath = $"http{(App.MinIOUseSsl == true ? "s" : "")}://{App.MinIOEndPoint}/{App.MinIOImageBucket}/{fileSaveName}";
await mc.PutObjectAsync(App.MinIOImageBucket, fileSaveName, file.OpenReadStream(), file.Length);
DakaFile dakaFile = new DakaFile
{
BucketName = App.MinIOImageBucket,
SaveName = fileSaveName,
FileName = file.FileName,
FilePath = filePath,
FileSize = file.Length,
FileExtension = Path.GetExtension(file.FileName),
CteateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
using (DbContext my = App.sqlite)
{
my.Insert(dakaFile);
}
dynamic _file = new ExpandoObject();
_file.Id = dakaFile.Id;
_file.FilePath = filePath;
json.code = 0;
json.msg = $"上传成功";
json.data = _file;
return json;
}
catch (Exception e)
{
logger.Error($"{e}");
json.code = 1;
json.msg = $"{e}";
json.data = null;
return json;
}
}
图片浏览和下载使用同一个接口实现,通过参数preview判断是下载还是浏览,代码如下:
[SwaggerOperation(
Summary = "图片下载",
Description = "图片下载接口,可以证据特定参数,实现图片的缩放、旋转。缩放时如果同时传了比例和高宽度参数,优先根据比例参数。",
Tags = new[] { "图片相关接口" }
)]
[HttpGet, Route("download")]
public async Task<IActionResult> Download([SwaggerParameter("图片ID")] int? id
, [SwaggerParameter("是否预览")] bool? preview
, [SwaggerParameter("是否缩放")] bool? resize
, [SwaggerParameter("是否旋转")] bool? rotate
, [SwaggerParameter("旋转方向:0(或非1值).逆时针;1.顺时针")] int? rotate_d
, [SwaggerParameter("按比例缩放")] double? resize_p
, [SwaggerParameter("按高度缩放")] int? resize_h
, [SwaggerParameter("按宽度缩放")] int? resize_w
, [SwaggerParameter("旋转度数")] int? rotate_degree
, [SwaggerParameter("Sign")] string sign
, [SwaggerParameter("TimeStamp")] long? t)
{
dynamic json = new ExpandoObject();
json.data = null;
if (sign.IsBlank() || !t.HasValue)
{
json.code = 1;
json.msg = $"参数异常";
return Content(JsonConvert.SerializeObject(json), "application/json", Encoding.UTF8);
}
if (t.Value <= 0)
{
json.code = 1;
json.msg = $"接口请求超时";
return Content(JsonConvert.SerializeObject(json), "application/json", Encoding.UTF8);
}
var dis = Math.Abs(DateTime.Now.Subtract(t.Value.TimeStampToDateTime().Value).TotalSeconds);
if (dis > 5)
{
json.code = 1;
json.msg = $"接口请求超时";
return Content(JsonConvert.SerializeObject(json), "application/json", Encoding.UTF8);
}
var _sign = $"LONG.{t}AT.STRING".MD5Encrypt32().ToUpper();
if (sign != _sign)
{
json.code = 1;
json.msg = $"签名验证失败";
return Content(JsonConvert.SerializeObject(json), "application/json", Encoding.UTF8);
}
DakaImage dakaImage = null;
using (DbContext my = App.sqlite)
{
dakaImage = my.QueryByKey<DakaImage>(id);
}
if (dakaImage == null)
{
json.code = 1;
json.msg = $"未找到相关图片信息";
return Content(JsonConvert.SerializeObject(json), "application/json", Encoding.UTF8);
}
if (dakaImage.IsDelete == true)
{
json.code = 1;
json.msg = $"图片已被删除";
return Content(JsonConvert.SerializeObject(json), "application/json", Encoding.UTF8);
}
try
{
MinioClient mc = new MinioClient(App.MinIOAPIEndPoint, App.MinIOAccessKey, App.MinIOSecertKey);
string filepath = Path.Combine(App.SystemTempPath, dakaImage.SaveName);
string new_iamge_filepath = Path.Combine(App.SystemTempPath, $"{Guid.NewGuid()}{dakaImage.FileExtension}");
string dirpath = Path.GetDirectoryName(filepath);
if (!Directory.Exists(dirpath))
{
Directory.CreateDirectory(dirpath);
}
await mc.GetObjectAsync(App.MinIOImageBucket, dakaImage.SaveName, (x =>
{
using (FileStream fs = new FileStream(filepath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
{
x.CopyTo(fs);
fs.Flush();
fs.Dispose();
}
x.Dispose();
}));
using (Image image = Image.Load(filepath))
{
if (image != null)
{
bool changed = false;
if (resize == true)
{
if (resize_p != null)
{
if (resize_p.Value > 2)
{
resize_p = 2d;
}
image.Mutate(x =>
{
x.Resize((int)(image.Width * resize_p.Value), (int)(image.Height * resize_p.Value));
});
changed = true;
}
else
{
if (resize_h.HasValue)
{
image.Mutate(x =>
{
x.Resize(image.Width, resize_h.Value);
});
changed = true;
}
if (resize_w.HasValue)
{
image.Mutate(x =>
{
x.Resize(resize_w.Value, image.Height);
});
changed = true;
}
}
}
if (rotate == true)
{
if (rotate_d == 1)
{
image.Mutate(x =>
{
x.Rotate(rotate_degree.Value);
});
changed = true;
}
else
{
image.Mutate(x =>
{
x.Rotate(360f - rotate_degree.Value);
});
changed = true;
}
}
if (changed)
{
image.Save(new_iamge_filepath);
filepath = new_iamge_filepath;
}
}
}
if (preview == true)
{
return File(System.IO.File.ReadAllBytes(filepath), "image/jpeg");
}
return File(System.IO.File.ReadAllBytes(filepath), "application/octet-stream", dakaImage.SaveName);
}
catch (Exception e)
{
logger.Error($"{e}");
json.code = 1;
json.msg = $"{e}";
return Content(JsonConvert.SerializeObject(json), "application/json", Encoding.UTF8);
}
}
文件删除接口代码如下:
[HttpDelete, Route("delete"), Produces("application/json")]
public async Task<object> Delete([SwaggerParameter("图片ID")] int? id
, [SwaggerParameter("Sign")] string sign
, [SwaggerParameter("TimeStamp")] long? t)
{
dynamic json = new ExpandoObject();
json.data = null;
if (sign.IsBlank() || !t.HasValue)
{
json.code = 1;
json.msg = $"参数异常";
return json;
}
if (t.Value <= 0)
{
json.code = 1;
json.msg = $"接口请求超时";
return json;
}
var dis = Math.Abs(DateTime.Now.Subtract(t.Value.TimeStampToDateTime().Value).TotalSeconds);
if (dis > 5)
{
json.code = 1;
json.msg = $"接口请求超时";
return json;
}
var _sign = $"LONG.{t}AT.STRING".MD5Encrypt32().ToUpper();
if (sign != _sign)
{
json.code = 1;
json.msg = $"签名验证失败";
return json;
}
DakaImage dakaImage = null;
using (DbContext my = App.sqlite)
{
dakaImage = my.QueryByKey<DakaImage>(id);
}
if (dakaImage == null)
{
json.code = 1;
json.msg = $"未找到相关图片信息";
return json;
}
if (dakaImage.IsDelete == true)
{
json.code = 1;
json.msg = $"图片已被删除";
return json;
}
try
{
MinioClient mc = new MinioClient(App.MinIOAPIEndPoint, App.MinIOAccessKey, App.MinIOSecertKey);
await mc.RemoveObjectAsync(App.MinIOImageBucket, dakaImage.SaveName);
using (DbContext my = App.sqlite)
{
dakaImage.IsDelete = true;
my.UpdateOnly<DakaImage>(dakaImage, a => new { a.IsDelete });
}
json.code = 0;
json.msg = $"删除成功";
return json;
}
catch (Exception e)
{
logger.Error($"{e}");
json.code = 1;
json.msg = $"{e}";
return json;
}
}
上传成功后会返回MinIO的直接访问地址,但是走接口的话,无法使用,可以屏蔽data
里的链接
可以根据参数对图像进行简单的处理
MinIO
做分布式对象存储系统,并使用Asp.Core封装使用接口对外开放上传、删除、下载等权限Swagger
作为接口测试工具,仅在Debug模式下可访问Swagger界面并调试接口,Release发布模式下无法访问Swagger和任何界面ImageSharp
做为图像处理工具,可以脱离System.Drawing,方便快捷的对图像进行简易处理SQLite
进行记录存储,方便接口系统迁移Chloe
作为数据库ORM框架Yaml
格式Quartz
作为系统定时任务执行框架,根据配置文件可以自行修改Cron表达式
实现定时清理缓存文件代码中Sign加密格式已做更改
杭州大伽文件服务接口系统:DakaPathAppFileService