文件同步传输工具比较多,传输的方式也比较多,比如:FTP、共享、HTTP等,我这里要讲的就是基于HTTP协议的WEB API实现批量文件由一个服务器同步快速传输到其它多个服务器这样的一个工具(简称:一端到多端的文件同步工具)
一、设计原理:
1.使用的技术:WinForm、WebApi
1.1 WinForm:为程序主界面,作为一端(一个源文件服务器)同步传输到多端(多个目的文件服务器)文件的业务处理中介;程序内部主要通过System.Timers.Timer+HttpClient来实现定时执行文件同步传输业务;
1.2 WebApi:实现通过HTTP协议批量下载或批量上传多个文件(文件同步传输的核心业务逻辑);MultipartContent作为批量下载或批量上传的唯一媒介。
2.实现思路:
2.1客户端(WinForm程序主界面)通过HttpClient向源文件服务器目录URL发送GET请求;
2.2源文件服务器服务端(WebApi)的GetFiles方法接收到GET请求后,按照web.config中配置的源文件路径递归获取所有文件的字节信息并转换成MultipartFormDataContent对象后返回;(即:实现了批量下载)
2.3客户端(WinForm程序主界面)将响应的结果显式转换并生成对应的目的文件服务器数量的多文件流内容(MultipartContent)对象列表,以便后续用于批量上传;
2.4客户端(WinForm程序主界面)启用并行循环(Parallel.ForEach)来遍历目的文件服务器目录URL(采用并行循环是为了达到同时向多个目的文件服务器批量上传文件的效果,从而提高运行效率),在循环遍历中,每次将2.3中获得的多文件流内容(MultipartContent)通过HttpClient向目的文件服务器目录URL发送POST请求;
2.5目的文件服务器服务端(WebApi)的SaveFiles方法接收到POST请求后,解析2.4中POST过来的多文件流内容(MultipartContent),并循环遍历文件流,在循环遍历中,按照web.config中配置的上传文件路径,将文件流输出保存到指定的文件路径下,同时生成文件保存成功与失败的日志信息字典,最后返回该字典。(即:实现了批量上传保存)
2.6客户端(WinForm程序主界面)将响应的结果显式转换成保存成功与失败的日志信息字典,并添加到线程安全的无序集合对象中;(采用线程安全的无序集合对象是因为存在多线程并发更新的风险)
2.7客户端(WinForm程序主界面)等待所有并行循环同步上传执行完毕后,根据最后得到的保存成功与失败的日志信息无序集合对象,获得所有目的文件服务器全部保存成功文件名列表及保存成功与失败的日志信息列表(判断是否全部上传成功:若某个文件应上传到5个目的文件服务器,实际成功上传5个,则视为成功,否则有一个未上传成功则视为失败),然后通过HttpClient向源文件服务器目录URL发送PUT请求删除源文件服务器中的同名文件,向源文件服务器LOG URL发送POST请求将此次文件同步传输的日志保存到源文件服务器目录中
2.8源文件服务器服务端(WebApi)RemoveFiles方法接收到PUT请求后,循环遍历PUT过来的保存成功文件名列表,依次删除同名文件(含子目录),WriteLog方法接收到POST请求后,直接将POST过来的日志信息列表输出保存至源文件服务器web.config中配置的LOG文件路径;为了避免日志无限增大及考虑日志的使用价值,日志文件每天重新覆盖生成新的文件;
3.业务流程图:
序列图:
二、使用说明:
1.将KyFileSyncTransfer.Api项目分别发布到源文件服务器、目的文件服务器;
2.修改源文件服务器服务端(WebApi)web.config中的AppSettings节点,配置FileSyncDirectory(源文件存放目录)、LogDirectory(日志文件保存路径,仅限源文件服务器服务端配置),这两个路径支持当前项目的子目录(即:配置时使用~/)或其它路径(其它路径则需直接配置完整的物理路径);修改目的文件服务器服务端(WebApi)web.config中的AppSettings节点,配置FileSyncDirectory(上传文件存放目录),这个路径支持当前项目的子目录(即:配置时使用~/)或其它路径(其它路径则需直接配置完整的物理路径)
注:为了能够支持大文件批量上传,同时需修改请求内容长度限制,如下设置成最大批量上传1.9G:
3.将客户端(WinForm程序主界面)部署到某台服务器上(只要能够访问源文件服务器、目的文件服务器即可,也可以他们服务器上的任意一台),然后开启客户端(WinForm程序主界面),将上述的源文件服务器服务端URL(http://源文件服务器Host/Api/Files)及多个目的文件服务器服务端URL(http://目的文件服务器Host/Api/Files)录入到程序预设的地方,设置好时间间隔,最后点击开启即可(需保持该程序一直处于运行中,可以最小化到任务栏,双击图标可以显示界面);若需停止文件同步,点击停止按钮即可;若需查看运行日志,可以切换到运行日志页签浏览。
4.以上3步是完成了文件自动定时同步传输的所有工作,后续只需要将需要同步的文件放到源文件服务器服务端web.config中的AppSettings节点设置的FileSyncDirectory(源文件存放目录)即可。
运行效果如下:
三、贴出主要的源代码
服务端代码(WEB API代码,需要进行文件传输的每个服务器均需要部署该WEB API站点)
FilesController:(实现:批量下载文件、批量上传文件、批量删除文件、批量写日志信息)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using KyFileSyncTransfer.Api.Common;
using System.Net.Http.Headers;
using System.Net.Http.Formatting;
using System.Threading.Tasks; namespace KyFileSyncTransfer.Api.Controllers
{
public class FilesController : ApiController
{
private static string fileSyncDirectory = null;
private static string logDirectory = null; static FilesController()
{
fileSyncDirectory = BaseUtility.GetDirectoryFromConfig("FileSyncDirectory");
logDirectory = BaseUtility.GetDirectoryFromConfig("LogDirectory");
} /// <summary>
/// 从源文件服务器获取所有文件信息(采用JSON方式)
/// </summary>
/// <returns></returns>
[HttpGet, Route("~/api/downfiles")]
public IHttpActionResult GetFilesForJson()
{
if (!Directory.Exists(fileSyncDirectory))
{
return BadRequest("同步文件目录不存在或未配置。");
} Dictionary<string, byte[]> files = new Dictionary<string, byte[]>();
BaseUtility.LoadFileDatas(files, fileSyncDirectory);
files = files.ToDictionary(kv => kv.Key.Replace(fileSyncDirectory, ""), kv => kv.Value); return Json(files);
} /// <summary>
/// 将所有文件同步保存到目的文件服务器(采用JSON方式)
/// </summary>
/// <param name="files"></param>
/// <returns></returns>
[HttpPost, Route("~/api/upfiles")]
public IHttpActionResult SaveFilesForJson([FromBody]IDictionary<string, byte[]> files)
{
string requestUrl = HttpContext.Current.Request.Url.ToString();
var savedErrors = new Dictionary<string, string>(); if (files == null || !Directory.Exists(fileSyncDirectory))
{
return BadRequest();
}
foreach (var item in files)
{
string file = item.Key; string filePath = Path.GetDirectoryName(fileSyncDirectory + file); try
{
if (!Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
string saveFilePath = Path.Combine(filePath, Path.GetFileName(file));
File.WriteAllBytes(saveFilePath, item.Value);
}
catch (Exception ex)
{
savedErrors[file] = string.Format("[{0:yyyy-MM-dd HH:mm:ss}] -请求:{1}同步文件:{2}失败,原因:{3}",
DateTime.Now, requestUrl, file, ex.Message);
}
} return Json(savedErrors);
} /// <summary>
/// 从源文件服务器获取所有文件信息
/// </summary>
/// <returns></returns>
[HttpGet]
public HttpResponseMessage GetFiles()
{
if (!Directory.Exists(fileSyncDirectory))
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "同步文件目录不存在或未配置。");
} var response = new HttpResponseMessage(HttpStatusCode.OK);
var content = new MultipartFormDataContent();
BaseUtility.CreateMultipartFormDataContent(content, fileSyncDirectory, fileSyncDirectory);
response.Content = content;
return response;
} /// <summary>
/// 将所有文件同步保存到目的文件服务器
/// </summary>
/// <returns></returns>
[HttpPost]
public HttpResponseMessage SaveFiles()
{
if (!Request.Content.IsMimeMultipartContent())
{
return Request.CreateErrorResponse(HttpStatusCode.UnsupportedMediaType, "未上传任何文件");
} if (!Directory.Exists(fileSyncDirectory))
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "未找到文件同步上传目录:" + fileSyncDirectory);
} string requestUrl = HttpContext.Current.Request.Url.ToString();
Dictionary<string, Dictionary<string, string>> savedResult = new Dictionary<string, Dictionary<string, string>>();
var provider = new MultipartMemoryStreamProvider();
const string success = "success";
const string failure = "failure"; try
{ savedResult[success] = new Dictionary<string, string>();
savedResult[failure] = new Dictionary<string, string>(); //Request.Content.ReadAsMultipartAsync(provider).Wait(); Task.Run(async () => await Request.Content.ReadAsMultipartAsync(provider)).Wait(); foreach (var item in provider.Contents)
{
string fileName = item.Headers.ContentDisposition.FileName;
if (string.IsNullOrEmpty(fileName))
{
continue;
} var fileData = item.ReadAsByteArrayAsync().Result; fileName = BaseUtility.ReviseFileName(fileName);
string saveFilePath = fileSyncDirectory + fileName;
string fileBasePath = Path.GetDirectoryName(saveFilePath); try
{
if (!Directory.Exists(fileBasePath))
{
Directory.CreateDirectory(fileBasePath);
}
File.WriteAllBytes(saveFilePath, fileData);
savedResult[success][fileName] = string.Format("[{0:yyyy-MM-dd HH:mm:ss}] - V 请求:{1}同步文件:<{2}>成功。", DateTime.Now, requestUrl, fileName);
}
catch (Exception ex)
{
while(ex.InnerException!=null)
{
ex = ex.InnerException;
} savedResult[failure][fileName] = string.Format("[{0:yyyy-MM-dd HH:mm:ss}] - X 请求:{1}同步文件:<{2}>失败,原因:{3}",
DateTime.Now, requestUrl, fileName, ex.Message);
}
}
}
catch (Exception ex)
{
while (ex.InnerException != null)
{
ex = ex.InnerException;
} return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.Message);
}
return Request.CreateResponse(HttpStatusCode.OK, savedResult);
} /// <summary>
/// 移除源文件服务器指定的文件
/// </summary>
/// <param name="files"></param>
/// <returns></returns>
[HttpPut]
public IHttpActionResult RemoveFiles([FromBody]IEnumerable<string> files)
{
if (files == null || !Directory.Exists(fileSyncDirectory))
{
return BadRequest();
} foreach (string file in files)
{
string filePath = Path.Combine(fileSyncDirectory, file);
if (File.Exists(filePath))
{
File.Delete(filePath);
}
} return Ok();
} /// <summary>
/// 将同步的日志信息写入到源文件服务器LOG文件中
/// </summary>
/// <param name="savedErrors"></param>
/// <returns></returns>
[HttpPost]
[Route("~/Api/Files/Log")]
public IHttpActionResult WriteLog([FromBody] IEnumerable<string> savedResult)
{
if (!Directory.Exists(logDirectory))
{
return BadRequest("同步日志目录不存在或未配置。");
} BaseUtility.WriteLogToFile(logDirectory, savedResult.ToArray());
return Ok();
} }
}
WebApiConfig:(增加全局token验证及全部采用json数据返回)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using KyFileSyncTransfer.Api.Models;
using System.Net.Http.Formatting; namespace FileSyncTransfer.Api
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 配置和服务 // Web API 路由
config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
); config.Filters.Add(new TokenAuthentificationAttribute()); var jsonFormatter = new JsonMediaTypeFormatter();
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
}
}
}
JsonContentNegotiator:
public class JsonContentNegotiator : IContentNegotiator
{
private readonly JsonMediaTypeFormatter _jsonFormatter; public JsonContentNegotiator(JsonMediaTypeFormatter formatter)
{
_jsonFormatter = formatter;
} public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
var result = new ContentNegotiationResult(_jsonFormatter, new MediaTypeHeaderValue("application/json"));
return result;
}
}
TokenAuthentificationAttribute:
public class TokenAuthentificationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > )
{
base.OnAuthorization(actionContext);
return;
} //HttpContextBase context = (HttpContextBase)actionContext.Request.Properties["MS_HttpContext"];//获取传统context
//HttpRequestBase request = context.Request;//定义传统request对象 IEnumerable<string> requestToken = null;
if (actionContext.Request.Headers.TryGetValues("token", out requestToken) && BaseUtility.ValidateToken(requestToken.ElementAt()))
{
base.OnAuthorization(actionContext);
}
else
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "token验证未通过。");
return;
} }
}
BaseUtility:(通用方法类)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration;
using System.Net.Http;
using System.IO;
using System.Net.Http.Headers; namespace KyFileSyncTransfer.Api.Common
{
public static class BaseUtility
{
public static string GetDirectoryFromConfig(string cfgName)
{
string dir = ConfigurationManager.AppSettings[cfgName];
if (string.IsNullOrEmpty(dir))
{
return null;
} if (dir.Contains('~'))
{
dir = HttpContext.Current.Server.MapPath(dir);
} if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
} return dir;
} public static MultipartFormDataContent CreateMultipartFormDataContent(MultipartFormDataContent content, string removeRootDir, string dir)
{
foreach (string file in Directory.GetFileSystemEntries(dir))
{
if (File.Exists(file))
{
byte[] fileBytes = File.ReadAllBytes(file);
var fileContent = new ByteArrayContent(fileBytes);
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "files",
FileName = file.Replace(removeRootDir, "")
};
fileContent.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(file));
fileContent.Headers.ContentLength = fileBytes.LongLength;
content.Add(fileContent);
}
else
{
CreateMultipartFormDataContent(content, removeRootDir, file);
}
}
return content;
} public static void LoadFileDatas(Dictionary<string, byte[]> files, string path)
{
foreach (string file in Directory.GetFileSystemEntries(path))
{
if (File.Exists(file))
{
files[file] = File.ReadAllBytes(file);
}
else
{
LoadFileDatas(files, file);
}
}
} public static void WriteLogToFile(string logDir, params string[] contents)
{
string logFilePath = Path.Combine(logDir, "KyFileSyncTransfer.log");
if (File.Exists(logFilePath) && !File.GetLastWriteTime(logFilePath).Date.Equals(DateTime.Today))
{
File.Delete(logFilePath);
} File.AppendAllLines(logFilePath, contents, System.Text.Encoding.UTF8); } public static bool ValidateToken(string token)
{
try
{
token = EncryptUtility.Decrypt(token);
var tokenParts = token.Split(new[] { "-", string.Empty }, StringSplitOptions.RemoveEmptyEntries);
if (tokenParts.Length != )
{
return false;
}
if (tokenParts[] == string.Join(string.Empty, "KyFileSyncTransfer.Api".OrderBy(c => c))) //对固定KEY进行排序然后比对
{
long tokenTstamp = -;
long svrTokenTimeStamp = GetTimeStamp();
if (long.TryParse(tokenParts[], out tokenTstamp) && svrTokenTimeStamp - tokenTstamp <= ) //时间戳<=10则视为有效
{
return true;
}
}
}
catch
{ } return false;
} public static string ReviseFileName(string fileName)
{
var regex = new System.Text.RegularExpressions.Regex("^\"+(?<name>.*)\"+$");
var matchResult = regex.Match(fileName);
if (matchResult != null && matchResult.Length > )
{
return matchResult.Groups["name"].Value;
}
return fileName;
} /// <summary>
/// 获取时间戳
/// </summary>
/// <returns></returns>
private static long GetTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(, , , , , , );
return Convert.ToInt64(ts.TotalSeconds);
} }
}
客户端代码:(这里面有一个需要注意的地方就是:GetNeedSyncTransferFilesDatas方法,这个是将从源文件服务器下载有流转换成多个副本的多文件流对象,之前是用的GetNeedSyncTransferFilesData方法,但MultipartContent是一种字节流对象,一旦被用于请求后将会被关闭,再次使用时就会报错)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using KyFileSyncTransfer.Business;
using System.Diagnostics;
using System.Net.Http;
using System.Collections.Concurrent;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net.Http.Headers; namespace KyFileSyncTransfer
{
public partial class FrmMain : Form
{
private const string appVersion = "16.1215.1";
private FormWindowState thisFormWindowState;
private System.Timers.Timer appTimer = null; private static int syncFlag = ;
private static object syncObj = new object();
private static DateTime lastRunTime = DateTime.MinValue; private int runInterval = ; private string srcFileApiUrl = null;
private List<WebApiUrlInfo> destFileApiUrlList = null; private const string success = "success";
private const string failure = "failure";
private const string RunInterval = "RunInterval";
private const string SrcFileApiUrl = "SrcFileApiUrl";
private const string DestFileApiUrls = "DestFileApiUrls"; public FrmMain()
{
InitializeComponent();
} #region 自定义方法区域 private void ExecuteFileTransfer()
{
List<string> srcFileNameList = new List<string>();
var needSyncTransferFilesDatas = GetNeedSyncTransferFilesDatas(srcFileNameList, destFileApiUrlList.Count).Result; WriteLog(string.Format("从源服务器目录Http Url:{0},获取到{1}个需要同步的文件.", srcFileApiUrl, srcFileNameList.Count)); if (needSyncTransferFilesDatas == null || srcFileNameList.Count <= ) return; ShowFileInfoLogs("需要同步的文件列表如下:", srcFileNameList); var fileTransferResultBag = new ConcurrentBag<Dictionary<string, Dictionary<string, string>>>(); Parallel.ForEach(destFileApiUrlList, (destFileApiUrl) =>
{
MultipartContent needSyncTransferFilesData = null;
if (needSyncTransferFilesDatas.TryTake(out needSyncTransferFilesData))
{
var savedResult = new Dictionary<string, Dictionary<string, string>>();
try
{
savedResult = SyncTransferFiles(destFileApiUrl.Url, needSyncTransferFilesData).Result;
}
catch (Exception ex)
{
while (ex.InnerException != null)
{
ex = ex.InnerException;
} savedResult[success] = new Dictionary<string, string>();
savedResult[failure] = new Dictionary<string, string>();
savedResult[failure][destFileApiUrl.Url] = string.Format("[{0:yyyy-MM-dd HH:mm:ss}] - X 请求:{1} 响应失败,原因:{3}", DateTime.Now, destFileApiUrl, ex.Message);
}
fileTransferResultBag.Add(savedResult);
ShowSyncTransferFileLogs(savedResult);
}
}); #region 同步循环
//foreach (var destFileApiUrl in destFileApiUrlList)
//{
// MultipartContent needSyncTransferFilesData = null;
// if (needSyncTransferFilesDatas.TryTake(out needSyncTransferFilesData))
// {
// var savedResult = new Dictionary<string, Dictionary<string, string>>();
// try
// {
// savedResult = SyncTransferFiles(destFileApiUrl.Url, needSyncTransferFilesData).Result;
// }
// catch (Exception ex)
// {
// while (ex.InnerException != null)
// {
// ex = ex.InnerException;
// }
// savedResult[success] = new Dictionary<string, string>();
// savedResult[failure] = new Dictionary<string, string>();
// savedResult[failure][destFileApiUrl.Url] = string.Format("[{0:yyyy-MM-dd HH:mm:ss}] - X 请求:{1} 响应失败,原因:{2}", DateTime.Now, destFileApiUrl, ex.Message);
// }
// fileTransferResultBag.Add(savedResult);
// ShowSyncTransferFileLogs(savedResult);
// }
//}
#endregion List<string> needRemoveFileNameList = GetNeedRemoveFileNameList(srcFileNameList, fileTransferResultBag.Select(b => b[success])); RemoveSourceFiles(needRemoveFileNameList); WriteSyncTransferFileLog(GetSyncTransferFileLogList(fileTransferResultBag)); ShowFileInfoLogs("以下文件已成功同步保存到预设的所有目的服务器目录Http Url中,且已移除在源服务器目录Http Url中的相同文件:", needRemoveFileNameList); } private void ShowFileInfoLogs(string logTitle, List<string> fileList)
{
WriteLog(logTitle);
foreach (string file in fileList)
{
WriteLog(file);
}
WriteLog("-".PadRight(, '-'));
} private void ShowSyncTransferFileLogs(Dictionary<string, Dictionary<string, string>> savedResult)
{
foreach (var kv in savedResult)
{
bool isError = (kv.Key == failure);
foreach (var kv2 in kv.Value)
{
WriteLog(kv2.Value, isError);
}
}
} private void ReviseFileNames(List<string> fileNameList)
{
var regex = new System.Text.RegularExpressions.Regex("^\"+(?<name>.*)\"+$");
for (int i = ; i < fileNameList.Count; i++)
{
string fileName = fileNameList[i];
var matchResult = regex.Match(fileName);
if (matchResult != null && matchResult.Length > )
{
fileNameList[i] = matchResult.Groups["name"].Value;
}
}
} private string ReviseFileName(string fileName)
{
var regex = new System.Text.RegularExpressions.Regex("^\"+(?<name>.*)\"+$");
var matchResult = regex.Match(fileName);
if (matchResult != null && matchResult.Length > )
{
return matchResult.Groups["name"].Value;
}
return fileName;
} private List<string> GetSyncTransferFileLogList(IEnumerable<Dictionary<string, Dictionary<string, string>>> savedResult)
{
List<string> syncTransferFileLogList = new List<string>();
foreach (var dic in savedResult)
{
foreach (var kv in dic)
{
syncTransferFileLogList.AddRange(kv.Value.Values);
}
} return syncTransferFileLogList;
} private List<string> GetNeedRemoveFileNameList(List<string> srcFileNameList, IEnumerable<Dictionary<string, string>> successResult)
{
List<string> successFileNameList = new List<string>(); int needTransferFileCount = destFileApiUrlList.Count; foreach (var dic in successResult)
{
successFileNameList.AddRange(dic.Keys);
} var successFileNames = successFileNameList.GroupBy(f => f).Where(gp => gp.Count() >= needTransferFileCount).Select(gp => gp.Key); return srcFileNameList.Where(f => successFileNames.Contains(f, StringComparer.OrdinalIgnoreCase)).ToList();
} private void WriteSyncTransferFileLog(IEnumerable<string> savedResult)
{
try
{
string apiUrl = srcFileApiUrl + (srcFileApiUrl.EndsWith("/") ? "" : "/") + "/Log" + (srcFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("token", GetToken());
var result = client.PostAsJsonAsync(apiUrl, savedResult).Result;
result.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
WriteLog("WriteSyncTransferFileLog错误:" + ex.Message, true);
}
} private void RemoveSourceFiles(IEnumerable<string> needRemoveFileNames)
{
try
{
string apiUrl = srcFileApiUrl + (srcFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("token", GetToken());
var result = client.PutAsJsonAsync(apiUrl, needRemoveFileNames).Result;
result.EnsureSuccessStatusCode();
}
}
catch (Exception ex)
{
WriteLog("RemoveSourceFiles错误:" + ex.Message, true);
}
} private async Task<Dictionary<string, Dictionary<string, string>>> SyncTransferFiles(string destFileApiUrl, HttpContent filesData)
{
string apiUrl = destFileApiUrl + (destFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("token", GetToken());
var result = await client.PostAsync(apiUrl, filesData);
result.EnsureSuccessStatusCode();
var savedResult = await result.Content.ReadAsAsync<Dictionary<string, Dictionary<string, string>>>();
return savedResult;
}
} private async Task<MultipartContent> GetNeedSyncTransferFilesData(List<string> srcFileNameList)
{
string apiUrl = srcFileApiUrl + (srcFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
var filesCont = new MultipartContent();
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("token", GetToken()); var result = await client.GetAsync(apiUrl);
result.EnsureSuccessStatusCode();
var provider = await result.Content.ReadAsMultipartAsync();
foreach (var item in provider.Contents)
{
string fileName = item.Headers.ContentDisposition.FileName;
if (!string.IsNullOrEmpty(fileName))
{
filesCont.Add(item);
srcFileNameList.Add(ReviseFileName(fileName));
}
}
} return filesCont;
} private async Task<ConcurrentBag<MultipartContent>> GetNeedSyncTransferFilesDatas(List<string> srcFileNameList, int destFileServerCount)
{
var multipartContents = new ConcurrentBag<MultipartContent>(); string apiUrl = srcFileApiUrl + (srcFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("token", GetToken()); var result = await client.GetAsync(apiUrl);
result.EnsureSuccessStatusCode(); for (int i = ; i < destFileServerCount; i++)
{
multipartContents.Add(new MultipartContent());
} var provider = await result.Content.ReadAsMultipartAsync();
foreach (var item in provider.Contents)
{
string fileName = item.Headers.ContentDisposition.FileName;
if (!string.IsNullOrEmpty(fileName))
{
var bytes = await item.ReadAsByteArrayAsync(); foreach (var cont in multipartContents)
{
cont.Add(CreateByteArrayContent(bytes, fileName, item.Headers.ContentType.MediaType));
} srcFileNameList.Add(ReviseFileName(fileName));
}
}
} return multipartContents;
} private HttpContent CreateByteArrayContent(byte[] fileBytes, string fileName, string mediaTypeValue)
{
var fileContent = new ByteArrayContent(fileBytes);
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "files",
FileName = fileName
};
fileContent.Headers.ContentType = new MediaTypeHeaderValue(mediaTypeValue);
fileContent.Headers.ContentLength = fileBytes.LongLength; return fileContent;
} private string GetToken()
{
string timeStamp = GetTimeStamp();
string key = string.Join(string.Empty, "KyFileSyncTransfer.Api".OrderBy(c => c));
return EncryptUtility.Encrypt(string.Format("{0}-{1}", key, timeStamp));
} /// <summary>
/// 获取时间戳
/// </summary>
/// <returns></returns>
private string GetTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(, , , , , , );
return Convert.ToInt64(ts.TotalSeconds).ToString();
} private void WriteLog(string msg, bool isError = false)
{
if (this.IsHandleCreated && this.InvokeRequired)
{
this.Invoke(new Action<string, bool>(WriteLog), msg, isError);
}
else
{
if (isError && !msg.Contains("Error:"))
{
msg = "Error:" + msg;
} msg = string.Format("{0:yyyy-MM-dd HH:mm:ss} - {1}", DateTime.Now, msg); lstLog.Items.Add(msg);
lstLog.SelectedIndex = lstLog.Items.Count - ;
}
} private void SaveAppSettingsToConfig()
{
ConfigUtility.RemoveAppSettings(null);
Dictionary<string, string> settings = new Dictionary<string, string>();
settings[RunInterval] = txtInterval.Text.Trim();
settings[SrcFileApiUrl] = txtSrcHttpUrl.Text.Trim();
settings[DestFileApiUrls] = string.Join(",", destFileApiUrlList.Select(u => u.ToString()));
ConfigUtility.SetAppSettingValues(settings);
} private void LoadAppSettingsFromConfig()
{
var settings = ConfigUtility.GetAppSettingValues();
foreach (var kv in settings)
{
if (kv.Key.Equals(RunInterval, StringComparison.OrdinalIgnoreCase))
{
txtInterval.Text = settings[RunInterval];
}
else if (kv.Key.Equals(SrcFileApiUrl, StringComparison.OrdinalIgnoreCase))
{
txtSrcHttpUrl.Text = settings[SrcFileApiUrl];
}
else if (kv.Key.Equals(DestFileApiUrls, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(kv.Value))
{
var destFileApiUrls = settings[DestFileApiUrls].Split(new[] { ",", "" }, StringSplitOptions.RemoveEmptyEntries);
destFileApiUrlList = destFileApiUrls.Select(u => new WebApiUrlInfo(u) { }).ToList();
}
}
} #endregion private void FrmMain_Load(object sender, EventArgs e)
{
this.Text = string.Format("{0} V{1}", this.Text, appVersion);
this.notifyIconApp.Text = this.Text; destFileApiUrlList = new List<WebApiUrlInfo>(); LoadAppSettingsFromConfig(); dgvDestHttpUrls.AutoGenerateColumns = false;
dgvDestHttpUrls.DataSource = new BindingList<WebApiUrlInfo>(destFileApiUrlList); appTimer = new System.Timers.Timer();
appTimer.Elapsed += appTimer_Elapsed; btnStop.Enabled = false; } private void appTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (Interlocked.Increment(ref syncFlag) == ) //若不忙则执行(0+1=1表示不忙)
{
Stopwatch watch = new Stopwatch();
do
{
watch.Restart();
try
{
ExecuteFileTransfer();
}
catch (Exception ex)
{
WriteLog("执行文件同步传输时发生错误:" + ex.Message, true);
}
watch.Stop(); } while (watch.ElapsedMilliseconds >= runInterval); //如果执行的时间超过同步频率间隔,则直接再执行一次 Interlocked.Exchange(ref syncFlag, ); //解除忙
}
} private void lstLog_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index < ) return;
e.DrawBackground();
string itemValue = lstLog.Items[e.Index].ToString();
if (itemValue.Contains("Error:"))//如果Error,则红字显示
{
e.Graphics.DrawString(itemValue, e.Font, new SolidBrush(Color.Red), e.Bounds);
}
else
{
e.Graphics.DrawString(itemValue, e.Font, new SolidBrush(e.ForeColor), e.Bounds);
}
e.DrawFocusRectangle();
} private void notifyIconApp_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (WindowState == FormWindowState.Minimized)
{
WindowState = thisFormWindowState;
this.ShowInTaskbar = true;
}
} private void FrmMain_SizeChanged(object sender, EventArgs e)
{
//判断是否选择的是最小化按钮
if (WindowState == FormWindowState.Minimized)
{
//隐藏任务栏区图标
this.ShowInTaskbar = false;
//图标显示在托盘区
this.notifyIconApp.Visible = true;
}
else
{
thisFormWindowState = WindowState;
}
} private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
{
if (MessageBox.Show("您确定要退出程序吗?", "退出确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
{
e.Cancel = true;
return;
} this.notifyIconApp.Visible = false;
SaveAppSettingsToConfig();
} private void btnStart_Click(object sender, EventArgs e)
{
try
{
srcFileApiUrl = txtSrcHttpUrl.Text.Trim();
if (string.IsNullOrEmpty(srcFileApiUrl))
{
throw new Exception("源服务器目录Http Url不能为空!");
} if (destFileApiUrlList.Count <= )
{
throw new Exception("目的服务器目录Http Url列表不能为空!");
} if (!int.TryParse(txtInterval.Text, out runInterval) || runInterval < )
{
throw new Exception("时间间隔不正确,必需是整数且>=10!");
} runInterval = runInterval * ;
appTimer.Interval = runInterval;
appTimer.Start(); txtSrcHttpUrl.Enabled = false;
dgvDestHttpUrls.Enabled = false;
txtInterval.Enabled = false;
btnStart.Enabled = false;
btnStop.Enabled = true; }
catch (Exception ex)
{
MessageBox.Show("发生错误:" + ex.Message, "错误提示");
}
} private void btnStop_Click(object sender, EventArgs e)
{
appTimer.Stop();
Interlocked.Exchange(ref syncFlag, ); txtSrcHttpUrl.Enabled = true;
dgvDestHttpUrls.Enabled = true;
txtInterval.Enabled = true; btnStart.Enabled = true;
btnStop.Enabled = false;
} }
}
设计器生成的代码:
namespace KyFileSyncTransfer
{
partial class FrmMain
{
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.IContainer components = null; /// <summary>
/// 清理所有正在使用的资源。
/// </summary>
/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
} #region Windows 窗体设计器生成的代码 /// <summary>
/// 设计器支持所需的方法 - 不要
/// 使用代码编辑器修改此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmMain));
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.label1 = new System.Windows.Forms.Label();
this.txtSrcHttpUrl = new System.Windows.Forms.TextBox();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.dgvDestHttpUrls = new System.Windows.Forms.DataGridView();
this.DestHttpUrl = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.notifyIconApp = new System.Windows.Forms.NotifyIcon(this.components);
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.panel1 = new System.Windows.Forms.Panel();
this.btnStop = new System.Windows.Forms.Button();
this.btnStart = new System.Windows.Forms.Button();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.label4 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.txtInterval = new System.Windows.Forms.TextBox();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.groupBox4 = new System.Windows.Forms.GroupBox();
this.lstLog = new System.Windows.Forms.ListBox();
this.groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvDestHttpUrls)).BeginInit();
this.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout();
this.panel1.SuspendLayout();
this.groupBox3.SuspendLayout();
this.tabPage2.SuspendLayout();
this.groupBox4.SuspendLayout();
this.SuspendLayout();
//
// groupBox1
//
this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.groupBox1.Controls.Add(this.label1);
this.groupBox1.Controls.Add(this.txtSrcHttpUrl);
this.groupBox1.Location = new System.Drawing.Point(, );
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(, );
this.groupBox1.TabIndex = ;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "源服务器配置:";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(, );
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(, );
this.label1.TabIndex = ;
this.label1.Text = "源服务器目录Http Url:";
//
// txtSrcHttpUrl
//
this.txtSrcHttpUrl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtSrcHttpUrl.Location = new System.Drawing.Point(, );
this.txtSrcHttpUrl.Name = "txtSrcHttpUrl";
this.txtSrcHttpUrl.Size = new System.Drawing.Size(, );
this.txtSrcHttpUrl.TabIndex = ;
//
// groupBox2
//
this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.groupBox2.Controls.Add(this.dgvDestHttpUrls);
this.groupBox2.Location = new System.Drawing.Point(, );
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(, );
this.groupBox2.TabIndex = ;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "目的服务器配置:";
//
// dgvDestHttpUrls
//
this.dgvDestHttpUrls.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvDestHttpUrls.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.DestHttpUrl});
this.dgvDestHttpUrls.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgvDestHttpUrls.Location = new System.Drawing.Point(, );
this.dgvDestHttpUrls.Name = "dgvDestHttpUrls";
this.dgvDestHttpUrls.RowTemplate.Height = ;
this.dgvDestHttpUrls.Size = new System.Drawing.Size(, );
this.dgvDestHttpUrls.TabIndex = ;
//
// DestHttpUrl
//
this.DestHttpUrl.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.DestHttpUrl.DataPropertyName = "Url";
this.DestHttpUrl.HeaderText = "目的服务器目录Http Url";
this.DestHttpUrl.Name = "DestHttpUrl";
//
// notifyIconApp
//
this.notifyIconApp.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIconApp.Icon")));
this.notifyIconApp.Text = "notifyIcon1";
this.notifyIconApp.Visible = true;
this.notifyIconApp.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.notifyIconApp_MouseDoubleClick);
//
// tabControl1
//
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl1.Location = new System.Drawing.Point(, );
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = ;
this.tabControl1.Size = new System.Drawing.Size(, );
this.tabControl1.TabIndex = ;
//
// tabPage1
//
this.tabPage1.Controls.Add(this.groupBox1);
this.tabPage1.Controls.Add(this.panel1);
this.tabPage1.Controls.Add(this.groupBox2);
this.tabPage1.Location = new System.Drawing.Point(, );
this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding();
this.tabPage1.Size = new System.Drawing.Size(, );
this.tabPage1.TabIndex = ;
this.tabPage1.Text = "控制台";
this.tabPage1.UseVisualStyleBackColor = true;
//
// panel1
//
this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panel1.Controls.Add(this.btnStop);
this.panel1.Controls.Add(this.btnStart);
this.panel1.Controls.Add(this.groupBox3);
this.panel1.Location = new System.Drawing.Point(, );
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(, );
this.panel1.TabIndex = ;
//
// btnStop
//
this.btnStop.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.btnStop.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)()))), ((int)(((byte)()))), ((int)(((byte)()))));
this.btnStop.Location = new System.Drawing.Point(, );
this.btnStop.Name = "btnStop";
this.btnStop.Size = new System.Drawing.Size(, );
this.btnStop.TabIndex = ;
this.btnStop.Text = "停 止";
this.btnStop.UseVisualStyleBackColor = false;
this.btnStop.Click += new System.EventHandler(this.btnStop_Click);
//
// btnStart
//
this.btnStart.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.btnStart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)()))), ((int)(((byte)()))), ((int)(((byte)()))));
this.btnStart.Location = new System.Drawing.Point(, );
this.btnStart.Name = "btnStart";
this.btnStart.Size = new System.Drawing.Size(, );
this.btnStart.TabIndex = ;
this.btnStart.Text = "开 启";
this.btnStart.UseVisualStyleBackColor = false;
this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
//
// groupBox3
//
this.groupBox3.Controls.Add(this.label4);
this.groupBox3.Controls.Add(this.label3);
this.groupBox3.Controls.Add(this.txtInterval);
this.groupBox3.Location = new System.Drawing.Point(, );
this.groupBox3.Name = "groupBox3";
this.groupBox3.Size = new System.Drawing.Size(, );
this.groupBox3.TabIndex = ;
this.groupBox3.TabStop = false;
this.groupBox3.Text = "同步频率:";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(, );
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(, );
this.label4.TabIndex = ;
this.label4.Text = "秒";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(, );
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(, );
this.label3.TabIndex = ;
this.label3.Text = "时间间隔:";
//
// txtInterval
//
this.txtInterval.Location = new System.Drawing.Point(, );
this.txtInterval.Name = "txtInterval";
this.txtInterval.Size = new System.Drawing.Size(, );
this.txtInterval.TabIndex = ;
this.txtInterval.Text = "";
//
// tabPage2
//
this.tabPage2.Controls.Add(this.groupBox4);
this.tabPage2.Location = new System.Drawing.Point(, );
this.tabPage2.Name = "tabPage2";
this.tabPage2.Padding = new System.Windows.Forms.Padding();
this.tabPage2.Size = new System.Drawing.Size(, );
this.tabPage2.TabIndex = ;
this.tabPage2.Text = "运行日志";
this.tabPage2.UseVisualStyleBackColor = true;
//
// groupBox4
//
this.groupBox4.Controls.Add(this.lstLog);
this.groupBox4.Dock = System.Windows.Forms.DockStyle.Fill;
this.groupBox4.Location = new System.Drawing.Point(, );
this.groupBox4.Name = "groupBox4";
this.groupBox4.Size = new System.Drawing.Size(, );
this.groupBox4.TabIndex = ;
this.groupBox4.TabStop = false;
//
// lstLog
//
this.lstLog.Dock = System.Windows.Forms.DockStyle.Fill;
this.lstLog.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
this.lstLog.FormattingEnabled = true;
this.lstLog.ItemHeight = ;
this.lstLog.Location = new System.Drawing.Point(, );
this.lstLog.Name = "lstLog";
this.lstLog.Size = new System.Drawing.Size(, );
this.lstLog.TabIndex = ;
this.lstLog.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.lstLog_DrawItem);
//
// FrmMain
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(, );
this.Controls.Add(this.tabControl1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MinimumSize = new System.Drawing.Size(, );
this.Name = "FrmMain";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "开源文件同步传输工具";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FrmMain_FormClosing);
this.Load += new System.EventHandler(this.FrmMain_Load);
this.SizeChanged += new System.EventHandler(this.FrmMain_SizeChanged);
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.groupBox2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.dgvDestHttpUrls)).EndInit();
this.tabControl1.ResumeLayout(false);
this.tabPage1.ResumeLayout(false);
this.panel1.ResumeLayout(false);
this.groupBox3.ResumeLayout(false);
this.groupBox3.PerformLayout();
this.tabPage2.ResumeLayout(false);
this.groupBox4.ResumeLayout(false);
this.ResumeLayout(false); } #endregion private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox txtSrcHttpUrl;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.DataGridView dgvDestHttpUrls;
private System.Windows.Forms.NotifyIcon notifyIconApp;
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Button btnStop;
private System.Windows.Forms.Button btnStart;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.TextBox txtInterval;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.GroupBox groupBox4;
private System.Windows.Forms.ListBox lstLog;
private System.Windows.Forms.DataGridViewTextBoxColumn DestHttpUrl;
}
}
特别说明:若客户端需要直接使用ReadAsAsync<T>将响应的结果反序列化成对象,则Newtonsoft.Json应采用6.0.0.0版本,这个是WebApi框架默认使用的版本,若单独通过NuGet引用可能是最新的9.0版本,会报错,这点注意;
另外文中为何采用Winform作为客户端,目的是可以实时查看运行结果,若没有这个需求,那么就可以采用Windows服务程序即可,看个人意愿了。
还有人可能认为,批量上传与下载采取打包上传与打包下载更快,这个我认为看各人的实际需求。
看到大家都想要源代码,其实源代码上面都有复制到项目中即可,但为了便于大家测试,故我也将GIT仓库地址告诉给大家:git@git.oschina.net:zuowj/KyFileSyncTransfer.git,大家如有需要可以下载或查看。
详解:基于WEB API实现批量文件由一个服务器同步快速传输到其它多个服务器功能的更多相关文章
-
css 14-CSS3属性详解:Web字体
14-CSS3属性详解:Web字体 #前言 开发人员可以为自已的网页指定特殊的字体(将指定字体提前下载到站点中),无需考虑用户电脑上是否安装了此特殊字体.从此,把特殊字体处理成图片的方式便成为了过去. ...
-
配置文件详解和核心api讲解
一.配置文件详解 1.映射文件详解 1.映射配置文件的位置和名称没有限制. -建议:位置:和实体类放在统一目录下. 名称:实体类名称.hbm.xml. 2.在映射配置文件中,标签内的name属 ...
-
演示如何通过 web api 上传文件MVC40
演示如何通过 web api 上传文件WebApiWebFormHost/UploadFileController.cs /* * 通过 web api 上传文件 */ using System; u ...
-
返璞归真 asp.net mvc (11) - asp.net mvc 4.0 新特性之自宿主 Web API, 在 WebForm 中提供 Web API, 通过 Web API 上传文件, .net 4.5 带来的更方便的异步操作
原文:返璞归真 asp.net mvc (11) - asp.net mvc 4.0 新特性之自宿主 Web API, 在 WebForm 中提供 Web API, 通过 Web API 上传文件, ...
-
HashMap实现详解 基于JDK1.8
HashMap实现详解 基于JDK1.8 1.数据结构 散列表:是一种根据关键码值(Key value)而直接进行访问的数据结构.采用链地址法处理冲突. HashMap采用Node<K,V> ...
-
ASP.NET Core WEB API 使用element-ui文件上传组件el-upload执行手动文件文件,并在文件上传后清空文件
前言: 从开始学习Vue到使用element-ui-admin已经有将近快两年的时间了,在之前的开发中使用element-ui上传组件el-upload都是直接使用文件选取后立即选择上传,今天刚好做了 ...
-
Solr系列三:solr索引详解(Schema介绍、字段定义详解、Schema API 介绍)
一.Schema介绍 1. Schema 是什么? Schema:模式,是集合/内核中字段的定义,让solr知道集合/内核包含哪些字段.字段的数据类型.字段该索引存储. 2. Schema 的定义方式 ...
-
MapReduce过程详解(基于hadoop2.x架构)
本文基于hadoop2.x架构详细描述了mapreduce的执行过程,包括partition,combiner,shuffle等组件以及yarn平台与mapreduce编程模型的关系. mapredu ...
-
详解基于linux环境MySQL搭建与卸载
本篇文章将从实际操作的层面,讲解基于linux环境的mysql的搭建和卸载. 1 搭建mysql 1.1 官网下载mysql压缩包 下载压缩包时,可以先把安装包下载到本地,再上传到服务器,也可以在 ...
随机推荐
-
MySQL使用Union创建视图报错
mysql> select * from test_main; +----+-------+ | id | value | +----+-------+ | 1 | ONE | | 2 ...
-
Unity3D中的第三人称镜头的脚本控制
原地址:http://blog.csdn.net/mobanchengshuang/article/details/27591271 好久没有敲Blog了,谢谢大家的留言.关注.私信等支持,但是我好像 ...
-
hdu2660 Accepted Necklace (DFS)
Problem Description I have N precious stones, and plan to use K of them to make a necklace for my mo ...
-
VBA开发中的前绑定与后绑定
凡是能用createobject创建的对象,都可以在引用相对应的运行库(library)文件之后在对象浏览器中得到它的方法.属性.枚举和事件列表,比如Shell.Application对象在Shell ...
-
Android应用程序组件Content Provider应用实例
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6950440 文简要介绍了Android应用程序 ...
-
ZeroMQ注意事项
Request-Reply状态 供client它必须是严肃的格在根据第一呼叫zmq_send() 函数,然后调用zmq_recv()函数的顺序来运行 对于server来说,运行时序相反 假设不依照这个 ...
-
linux 定时关机命令
一. 关机流程 Linux 运作时, 不能够直接将电源关闭, 否则, 可能会损毁档案系统. 因此, 必须依照正常的程序关机: 观察系统使用情形(或许当时, 正有使用者做着重要的工作呢!) 通知线上使用 ...
-
JavaScript 的 作用域
在看了几本书之后的一些理解和自己的想法. 作用域,变量的作用范围 在ES6之前 变量的声明 只有var可以声明变量属于某个作用域,并且,也只有全局作用域和函数作用域. (没有var声明的变 ...
-
HTML5 开发APP( 支付宝支付)
我们搞app有一个重要的功能那就是支付功能.无论是微信支付,支付宝,还是银联支付总要有一样支付手段来支持我们网上付款.实现完整的功能.我们公司app的支付方式经过大家开会讨论选择了支付宝支付(其实是当 ...
-
JavaScript 运行机制 (Event Loop)
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务.如果前一个任务耗时很长,后一个任务就不得不一直等着. 所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步 ...