前言
- 最近小胖接到一个任务,就是公司要求把一些大文件从linux上,上传到阿里云上,但是又不想使用阿里云的工具,想要写个程序在后台跑,定时将文件夹中的没上传过的文件上传到阿里云上。好家伙,听到这里我在想为啥不用工具,它提供的工具不好吗,上传就一个命令而已,没必要吧,但是任务就是任务没办法
需求
- 将文件上传
- 定时任务
- 自动判断文件夹中是否有未上传的文件
- 可自动配置是否删除已上传的文件
第一次尝试
- 因为第一次做这种程序类的任务,还是有点懵逼的,但是还是顺着需求一步步来,首先先把阿里云上传的公共类调通先。
- 对于阿里云上传的上传方式都有四种,分别是简单上传,追加上传,断点续传和分片上传,前两个方法都是小文件上传的,后面两个是大文件上传的,一般来说都会选择后面两种,以为一般上传到上面的都不会是小文件。
- 而在分片上传和断点续传两种,我选择了后者,因为为了避免程序因为一些意外而终止之后,还需要重新上传文件,所以我选择了断点续传。
- 下面附上阿里云工具类的代码,至于如何使用,看网址阿里云断点续传
using Aliyun.OSS;
using Aliyun.OSS.Common;
var endpoint = "yourEndpoint";
var accessKeyId = "yourAccessKeyId";
var accessKeySecret = "yourAccessKeySecret";
var bucketName = "examplebucket";
var objectName = "exampledir/exampleobject.txt";
var localFilename = "D:\\localpath\\examplefile.txt";
string checkpointDir = "yourCheckpointDir";
var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
try
{
UploadObjectRequest request = new UploadObjectRequest(bucketName, objectName, localFilename)
{
PartSize = 8 * 1024 * 1024,
ParallelThreadCount = 3,
CheckpointDir = checkpointDir,
};
client.ResumableUploadObject(request);
Console.WriteLine("Resumable upload object:{0} succeeded", objectName);
}
catch (OssException ex)
{
Console.WriteLine("Failed with error code: {0}; Error info: {1}. \nRequestID:{2}\tHostID:{3}",
ex.ErrorCode, ex.Message, ex.RequestId, ex.HostId);
}
catch (Exception ex)
{
Console.WriteLine("Failed with error info: {0}", ex.Message);
}
- 好了,阿里云的上传工具类调通了,剩下就剩两个重点,一个是配置文件的添加,另一个则是定时任务,还是先看配置文件的添加,java的配置类我还是会配置,但是c#程序后台获取配置类我还是第一次写,哪能咋办的只能去网上找了,一开始我找的是config配置类的获取,但是领导说这是很老的技术了,现在用的都是json的配置类获取,好吧,我也觉得是,所以下面是我找的配置文件获取其中字段的方法
private static Dictionary<string, string> GetSettings(string jsonFile)
{
var map = new Dictionary<string, string>();
using (System.IO.StreamReader file = System.IO.File.OpenText(@jsonFile))
{
using (JsonTextReader reader = new JsonTextReader(file))
{
JObject setting = (JObject)JToken.ReadFrom(reader);
foreach(var item in setting)
{
map.Add(item.Key, item.Value.ToString());
}
}
}
return map;
}
- 上面是我获取c#的配置文件的方法了,如果有更好的方法,请评论区告诉我,谢谢大家了
- 好了解决掉获取配置文件的方法,那我们就可以先来着手来写文件上传的流程了,因为我们不仅是一次上传还是多次上传,所以我的第一个想法就是,将阿里云的文件列表拿下来,再把我的本地文件列表获取,然后将两者的文件名相互比对,这样就知道那些文件没有上传,将这些文件路径保存,然后开始循环上传。流程如下:
- 获取配置类的文件目录
- 根据文件夹名称去阿里云得到文件夹
- 根据文件目录等到本地文件名称
- 文件相互比对,找出未上传的文件
- 将未上传的文件上传
var settings = GetSettings();
var logs = new List<string>();
foreach (var key in settings.Keys)
{
if (key.Contains("Log"))
{
logs.Add(settings[key]);
}
}
var updateFiles = new List<string[]>();
foreach (var log in logs)
{
var item = log.Split("\\");
var objectNames = GetObjectNames(endpoint, accessKeyId, accessKeySecret, bucketName, item[item.Length - 1]);
var files = GetFileNames(log);
var fileNames = files.Select(d =>
{
var strs = d.Split(log);
var str = strs[1].Replace("\\", "/");
return new string[]{
item[item.Length - 1] + str,
d
};
}).ToList();
foreach (var fileName in fileNames)
{
if (!objectNames.Contains(fileName[0]))
{
updateFiles.Add(fileName);
}
}
}
foreach (var updateFile in updateFiles)
{
var checkpointDir = "D:\\test\\checkpoint_" + DateTime.Now.ToString("yyyy_MM_dd");
if (!Directory.Exists(checkpointDir))
{
Directory.CreateDirectory(checkpointDir);
}
var objectName = updateFile[0];
var localFilename = updateFile[1];
PutObject(endpoint, accessKeyId, accessKeySecret, bucketName, objectName, localFilename, checkpointDir);
}
Console.WriteLine("----------------------");
- 本来我以为这样就大功告成了,并且佩服我自己的聪明才智,后来领导的评价是,十分粗糙,并且流程有问题,给我提了一个问题,怎么判断这个文件到底是上传成功还是上传失败,并且假设上传到一半终止了,你这个又怎么判断呢,你这样太繁琐了,有没有简便一点的方法来判断是否已经上传呢?
- 这几个问题一出来,我就知道,我写的真是个垃圾,这种解决方案确实太过繁琐了,但是如何判断文件的上传状态呢?
- 以我的聪明的脑袋瓜只能想到两种方法
- 第一种:通过配置文件来存储已经上传和上传中的文件名称,但是这种方法一样繁琐,给否决了
- 第二种:通过更改文件名称来判断,其实我觉得也挺繁琐的,但是领导思考了一下还是让我想想
- 最后看到我实在想不出了,领导给了解决方案,创建两个文件夹,其中一个用来存放上传中的文件,叫原文件夹_uploadig,另一个用来存放已经上传成功的文件,叫原文件夹_uploaded。这样就不需要上面这么复杂,并且删除文件的时候直接删除最后一个文件夹里的文件就好了,这想法简直了,我给五星好评。
- 在没有加上定时任务的时候,因为有了断点续传,所以我觉得中间那个过度的文件夹就没必要了,流程应该是下面这样的:
- 获取配置信息
- 将要上传的文件夹中的文件上传
- 上传完毕
- 查看是否有uploaded文件夹
- 如果没有,则创建
- 如果有,则将上传完毕的文件转移到uploaded文件
- 看配置信息中是否有需要删除
foreach (var project in projects)
{
var file = project.Value.ToString();
var uploading = file + "_uploading";
var uploaded = file + "_uploaded";
var uploadFiles = new List<string>();
if (!Directory.Exists(file))
{
Directory.CreateDirectory(file);
}
var files = GetFileNames(file);
uploadFiles.AddRange(files);
foreach (var uploadFile in uploadFiles)
{
string objectName = project.Key + "/" + GetFileName(uploadFile);
string localFilename = uploadFile;
string checkpointDir = Path.Combine(System.Environment.CurrentDirectory, "checkpointDir");
if (!Directory.Exists(@checkpointDir))
{
Directory.CreateDirectory(@checkpointDir);
}
var flag = PutObject(endpoint, accessKeyId, accessKeySecret, bucketName, objectName, localFilename, checkpointDir);
if (flag)
{
if (!Directory.Exists(uploaded))
{
Directory.CreateDirectory(uploaded);
}
var uploadedFile = Path.Combine(uploaded, GetFileName(uploadFile));
MoveFile(uploadFile, uploadedFile);
}
}
if (isDelete == "true")
{
var uploadedFiles = GetFileNames(uploaded);
foreach (var uploadedFile in uploadedFiles)
{
File.Delete(uploadedFile);
}
}
}
- 好了,解决完大部分流程了之后,我就要开始思考定时任务怎么办,定时任务就是在经过一定时间后自动执行想要执行的任务,这个倒是不难,但是又有个问题,假设上一个任务还没有完成,拿下一个任务又开始了怎么办?
- 这个时候就需要用到锁了,但时间刚好开始定时任务,我们就把这个线程锁住,并且关闭定时任务,等到任务执行完,就打开锁,并且开启任务,这样就可以解决上面的问题了,给出代码
public static void TimeTask(int time)
{
Console.WriteLine("定时任务开始" + DateTime.Now.ToString() + ":");
timer = new System.Timers.Timer(time);
timer.Enabled = true;
timer.Interval = time;
timer.Start();
timer.Elapsed += new System.Timers.ElapsedEventHandler(TaskLocal);
timer.AutoReset = true;
}
public static void TaskLocal(object source, ElapsedEventArgs e)
{
{
timer.Enabled = false;
lock (task)
{
task = Convert.ToInt32(task) + 1;
Thread.CurrentThread.Name = task.ToString();
}
Run();
timer.Enabled = true;
}
}
- 但是我感觉我这代码有点问题,如果大佬可以给我点改进意见
- 以上就是我这个任务最初的模型了,我跑了一天,发现他没有报错依旧坚挺,但是这个工具远远不会这么简单,后面领导又提了几个需求,所有我们还需要做第二版。