diff --git a/QYMessageCenter.Business/BaseBusiness.cs b/QYMessageCenter.Business/BaseBusiness.cs
new file mode 100644
index 0000000..0e38afb
--- /dev/null
+++ b/QYMessageCenter.Business/BaseBusiness.cs
@@ -0,0 +1,21 @@
+using QYMessageCenter.Common.Http;
+using QYMessageCenter.Common.Log;
+using Yitter.IdGenerator;
+namespace BBWYB.Server.Business
+{
+ public class BaseBusiness
+ {
+ protected IFreeSql fsql;
+ protected NLogManager nLogManager;
+ protected IIdGenerator idGenerator;
+ protected RestApiService restApiService;
+
+ public BaseBusiness(IFreeSql fsql, NLogManager nLogManager, IIdGenerator idGenerator, RestApiService restApiService)
+ {
+ this.fsql = fsql;
+ this.nLogManager = nLogManager;
+ this.idGenerator = idGenerator;
+ this.restApiService = restApiService;
+ }
+ }
+}
diff --git a/QYMessageCenter.Business/MessageBusiness.cs b/QYMessageCenter.Business/MessageBusiness.cs
new file mode 100644
index 0000000..9fcd4b4
--- /dev/null
+++ b/QYMessageCenter.Business/MessageBusiness.cs
@@ -0,0 +1,45 @@
+using BBWYB.Server.Business;
+using Microsoft.Extensions.Configuration;
+using QYMessageCenter.Common.Http;
+using QYMessageCenter.Common.Log;
+using QYMessageCenter.Common.Models;
+using QYMessageCenter.Model.DTO;
+using System.Configuration;
+using System.Threading.Channels;
+using Yitter.IdGenerator;
+
+namespace QYMessageCenter.Business
+{
+ public class MessageBusiness : BaseBusiness, IDenpendency
+ {
+ private readonly string Key = "BC-652c4236c6ba4083b026e8cfa2e199b1";
+
+ public MessageBusiness(IFreeSql fsql, NLogManager nLogManager, IIdGenerator idGenerator, RestApiService restApiService) : base(fsql, nLogManager, idGenerator, restApiService)
+ {
+
+ }
+
+ ///
+ /// 发送消息
+ ///
+ ///
+ public void Send(SendMessageRequest request)
+ {
+ #region 调用goeasy
+ var goeasy_httpResult = restApiService.SendRequest("https://rest-hz.goeasy.io/", "v2/pubsub/publish", new
+ {
+ appkey = Key,
+ channel = request.Channel,
+ content = request.Content
+ }, null, HttpMethod.Post);
+
+ if (goeasy_httpResult.StatusCode != System.Net.HttpStatusCode.OK)
+ throw new BusinessException($"消息发送失败 httpcode {goeasy_httpResult.StatusCode} conent {goeasy_httpResult.Content}");
+ #endregion
+
+ #region 保存入库
+
+ #endregion
+ }
+ }
+}
diff --git a/QYMessageCenter.Business/QYMessageCenter.Business.csproj b/QYMessageCenter.Business/QYMessageCenter.Business.csproj
new file mode 100644
index 0000000..98a83de
--- /dev/null
+++ b/QYMessageCenter.Business/QYMessageCenter.Business.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net6.0
+ enable
+ enable
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QYMessageCenter.Business/TaskSchedulerManager.cs b/QYMessageCenter.Business/TaskSchedulerManager.cs
new file mode 100644
index 0000000..6420a41
--- /dev/null
+++ b/QYMessageCenter.Business/TaskSchedulerManager.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYMessageCenter.Business
+{
+ public class TaskSchedulerManager
+ {
+ }
+}
diff --git a/QYMessageCenter.Common/Extensions/ConverterExtensions.cs b/QYMessageCenter.Common/Extensions/ConverterExtensions.cs
new file mode 100644
index 0000000..410bd42
--- /dev/null
+++ b/QYMessageCenter.Common/Extensions/ConverterExtensions.cs
@@ -0,0 +1,29 @@
+namespace QYMessageCenter.Common.Extensions
+{
+ public static class ConverterExtensions
+ {
+ public static long? ToInt64(this object? o)
+ {
+ try
+ {
+ return Convert.ToInt64(o);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ public static int? ToInt32(this object? o)
+ {
+ try
+ {
+ return Convert.ToInt32(o);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/QYMessageCenter.Common/Extensions/CopyExtensions.cs b/QYMessageCenter.Common/Extensions/CopyExtensions.cs
new file mode 100644
index 0000000..694c2f7
--- /dev/null
+++ b/QYMessageCenter.Common/Extensions/CopyExtensions.cs
@@ -0,0 +1,12 @@
+using Newtonsoft.Json;
+
+namespace QYMessageCenter.Common.Extensions
+{
+ public static class CopyExtensions
+ {
+ public static T Copy(this T p)
+ {
+ return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(p));
+ }
+ }
+}
diff --git a/QYMessageCenter.Common/Extensions/DateTimeExtension.cs b/QYMessageCenter.Common/Extensions/DateTimeExtension.cs
new file mode 100644
index 0000000..3322f15
--- /dev/null
+++ b/QYMessageCenter.Common/Extensions/DateTimeExtension.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace QYMessageCenter.Common.Extensions
+{
+ public static class DateTimeExtension
+ {
+ private static readonly DateTime beginTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
+
+ ///
+ /// 时间戳转时间
+ ///
+ /// 时间
+ /// true:13, false:10
+ ///
+ [Obsolete]
+ public static DateTime StampToDateTime(this long timeStamp)
+ {
+ DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(beginTime);
+ return timeStamp.ToString().Length == 13 ? dt.AddMilliseconds(timeStamp) : dt.AddSeconds(timeStamp);
+ }
+
+ ///
+ /// 时间转时间戳
+ ///
+ /// 时间
+ /// true:13, false:10
+ ///
+ public static long DateTimeToStamp(this DateTime time, bool len13 = true)
+ {
+ TimeSpan ts = time.ToUniversalTime() - beginTime;
+ if (len13)
+ return Convert.ToInt64(ts.TotalMilliseconds);
+ else
+ return Convert.ToInt64(ts.TotalSeconds);
+ }
+
+ ///
+ /// 将秒数转换为时分秒形式
+ ///
+ ///
+ ///
+ public static string FormatToHHmmss(long second)
+ {
+ if (second < 60)
+ return $"00:00:{(second >= 10 ? $"{second}" : $"0{second}")}";
+ if (second < 3600)
+ {
+ var minute = second / 60;
+ var s = second % 60;
+ return $"00:{(minute >= 10 ? $"{minute}" : $"0{minute}")}:{(s >= 10 ? $"{s}" : $"0{s}")}";
+ }
+ else
+ {
+ var hour = second / 3600;
+ var minute = (second - (hour * 3600)) / 60;
+ var s = (second - ((hour * 3600) + minute * 60)) % 60;
+ return $"{(hour >= 10 ? $"{hour}" : $"0{hour}")}:{(minute >= 10 ? $"{minute}" : $"0{minute}")}:{(s >= 10 ? $"{s}" : $"0{s}")}";
+ }
+ }
+
+ #region SetLocalTime
+ [DllImport("Kernel32.dll")]
+ private static extern bool SetLocalTime(ref SYSTEMTIME lpSystemTime);
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct SYSTEMTIME
+ {
+ public ushort wYear;
+ public ushort wMonth;
+ public ushort wDayOfWeek;
+ public ushort wDay;
+ public ushort wHour;
+ public ushort wMinute;
+ public ushort wSecond;
+ public ushort wMilliseconds;
+ }
+
+ ///
+ /// 修改系统时间(需要管理员权限)
+ ///
+ ///
+ public static void SetSystemTime(DateTime date)
+ {
+ SYSTEMTIME lpTime = new SYSTEMTIME();
+ lpTime.wYear = Convert.ToUInt16(date.Year);
+ lpTime.wMonth = Convert.ToUInt16(date.Month);
+ lpTime.wDayOfWeek = Convert.ToUInt16(date.DayOfWeek);
+ lpTime.wDay = Convert.ToUInt16(date.Day);
+ DateTime time = date;
+ lpTime.wHour = Convert.ToUInt16(time.Hour);
+ lpTime.wMinute = Convert.ToUInt16(time.Minute);
+ lpTime.wSecond = Convert.ToUInt16(time.Second);
+ lpTime.wMilliseconds = Convert.ToUInt16(time.Millisecond);
+ var r = SetLocalTime(ref lpTime);
+ Console.WriteLine($"修改系统时间 {r}");
+ }
+ #endregion
+ }
+}
diff --git a/QYMessageCenter.Common/Extensions/EncryptionExtension.cs b/QYMessageCenter.Common/Extensions/EncryptionExtension.cs
new file mode 100644
index 0000000..23cf96d
--- /dev/null
+++ b/QYMessageCenter.Common/Extensions/EncryptionExtension.cs
@@ -0,0 +1,82 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace QYMessageCenter.Common.Extensions
+{
+ public static class EncryptionExtension
+ {
+
+ public static string Md5Encrypt(this string originStr)
+ {
+ using (var md5 = MD5.Create())
+ {
+ return string.Join(string.Empty, md5.ComputeHash(Encoding.UTF8.GetBytes(originStr)).Select(x => x.ToString("x2")));
+ }
+ }
+
+ //AES加密 传入,要加密的串和, 解密key
+ public static string AESEncrypt(this string input)
+ {
+ var key = "dataplatform2019";
+ var ivStr = "1012132405963708";
+
+ var encryptKey = Encoding.UTF8.GetBytes(key);
+ var iv = Encoding.UTF8.GetBytes(ivStr); //偏移量,最小为16
+ using (var aesAlg = Aes.Create())
+ {
+ using (var encryptor = aesAlg.CreateEncryptor(encryptKey, iv))
+ {
+ using (var msEncrypt = new MemoryStream())
+ {
+ using (var csEncrypt = new CryptoStream(msEncrypt, encryptor,
+ CryptoStreamMode.Write))
+
+ using (var swEncrypt = new StreamWriter(csEncrypt))
+ {
+ swEncrypt.Write(input);
+ }
+ var decryptedContent = msEncrypt.ToArray();
+
+ return Convert.ToBase64String(decryptedContent);
+ }
+ }
+ }
+ }
+
+ public static string AESDecrypt(this string cipherText)
+ {
+ var fullCipher = Convert.FromBase64String(cipherText);
+
+ var ivStr = "1012132405963708";
+ var key = "dataplatform2019";
+
+ var iv = Encoding.UTF8.GetBytes(ivStr);
+ var decryptKey = Encoding.UTF8.GetBytes(key);
+
+ using (var aesAlg = Aes.Create())
+ {
+ using (var decryptor = aesAlg.CreateDecryptor(decryptKey, iv))
+ {
+ string result;
+ using (var msDecrypt = new MemoryStream(fullCipher))
+ {
+ using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
+ {
+ using (var srDecrypt = new StreamReader(csDecrypt))
+ {
+ result = srDecrypt.ReadToEnd();
+ }
+ }
+ }
+
+ return result;
+ }
+ }
+ }
+
+ public static string Base64Encrypt(this string originStr)
+ {
+ return Convert.ToBase64String(Encoding.UTF8.GetBytes(originStr));
+ }
+ }
+}
diff --git a/QYMessageCenter.Common/Extensions/MapperExtension.cs b/QYMessageCenter.Common/Extensions/MapperExtension.cs
new file mode 100644
index 0000000..747cae1
--- /dev/null
+++ b/QYMessageCenter.Common/Extensions/MapperExtension.cs
@@ -0,0 +1,59 @@
+using AutoMapper;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace QYMessageCenter.Common.Extensions
+{
+ public static class MapperExtension
+ {
+ private static IMapper _mapper;
+
+ ///
+ /// 注册对象映射器
+ ///
+ ///
+ ///
+ ///
+ public static IServiceCollection AddMapper(this IServiceCollection services, Profile profile)
+ {
+ var config = new MapperConfiguration(cfg =>
+ {
+ cfg.AddProfile(profile);
+ });
+ _mapper = config.CreateMapper();
+ return services;
+ }
+
+ ///
+ /// 设置对象映射执行者
+ ///
+ /// 映射执行者
+ public static void SetMapper(IMapper mapper)
+ {
+ _mapper = mapper;
+ }
+
+ ///
+ /// 将对象映射为指定类型
+ ///
+ /// 要映射的目标类型
+ /// 源对象
+ /// 目标类型的对象
+ public static TTarget Map(this object source)
+ {
+ return _mapper.Map(source);
+ }
+
+ ///
+ /// 使用源类型的对象更新目标类型的对象
+ ///
+ /// 源类型
+ /// 目标类型
+ /// 源对象
+ /// 待更新的目标对象
+ /// 更新后的目标类型对象
+ public static TTarget Map(this TSource source, TTarget target)
+ {
+ return _mapper.Map(source, target);
+ }
+ }
+}
diff --git a/QYMessageCenter.Common/Extensions/StartupExtension.cs b/QYMessageCenter.Common/Extensions/StartupExtension.cs
new file mode 100644
index 0000000..9c6bf69
--- /dev/null
+++ b/QYMessageCenter.Common/Extensions/StartupExtension.cs
@@ -0,0 +1,34 @@
+using Microsoft.Extensions.DependencyInjection;
+using System.Reflection;
+
+namespace QYMessageCenter.Common.Extensions
+{
+ public static class StartupExtension
+ {
+ public static void BatchRegisterServices(this IServiceCollection serviceCollection, Assembly[] assemblys, Type baseType, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton, bool registerSelf = true)
+ {
+ List typeList = new List(); //所有符合注册条件的类集合
+ foreach (var assembly in assemblys)
+ {
+ var types = assembly.GetTypes().Where(t => t.IsClass && !t.IsSealed && !t.IsAbstract && baseType.IsAssignableFrom(t));
+ typeList.AddRange(types);
+ }
+
+ if (typeList.Count() == 0)
+ return;
+
+ foreach (var instanceType in typeList)
+ {
+ if (registerSelf)
+ {
+ serviceCollection.Add(new ServiceDescriptor(instanceType, instanceType, serviceLifetime));
+ continue;
+ }
+ var interfaces = instanceType.GetInterfaces();
+ if (interfaces != null && interfaces.Length > 0)
+ foreach (var interfaceType in interfaces)
+ serviceCollection.Add(new ServiceDescriptor(interfaceType, instanceType, serviceLifetime));
+ }
+ }
+ }
+}
diff --git a/QYMessageCenter.Common/Http/HttpDownloader.cs b/QYMessageCenter.Common/Http/HttpDownloader.cs
new file mode 100644
index 0000000..f6c53cf
--- /dev/null
+++ b/QYMessageCenter.Common/Http/HttpDownloader.cs
@@ -0,0 +1,456 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+
+namespace QYMessageCenter.Common.Http
+{
+ ///
+ /// Http下载器
+ ///
+ public class HttpDownloader
+ {
+
+ private IHttpClientFactory httpClientFactory;
+
+ public HttpDownloader(IHttpClientFactory httpClientFactory, DownloadSetting dwSetting = null)
+ {
+ this.httpClientFactory = httpClientFactory;
+ complateArgs = new DownloadCompletedEventArgs();
+ changeArgs = new DownloadProgressChangedEventArgs();
+ if (dwSetting == null)
+ dwSetting = new DownloadSetting();
+ downloadSetting = dwSetting;
+ buffer = new byte[downloadSetting.BufferSize];
+ }
+
+ ~HttpDownloader()
+ {
+ this.buffer = null;
+ this.downloadSetting = null;
+ this.complateArgs.Error = null;
+ this.complateArgs = null;
+ this.changeArgs = null;
+ }
+
+ #region Property
+
+ ///
+ /// 下载进度变化参数
+ ///
+ private DownloadProgressChangedEventArgs changeArgs;
+
+ ///
+ /// 下载完成参数
+ ///
+ private DownloadCompletedEventArgs complateArgs;
+
+ ///
+ /// 下载参数配置类
+ ///
+ private DownloadSetting downloadSetting;
+
+ ///
+ /// 下载缓冲区
+ ///
+ private byte[] buffer;
+
+ #endregion
+
+ #region Delegate
+ public delegate void DownloadProgressChangedEventHandler(object sender, DownloadProgressChangedEventArgs e);
+
+ public delegate void DownloadCompletedEventHandler(object sender, DownloadCompletedEventArgs e);
+
+ public delegate void ReDownloadEventHandler(object sender, DownloadCompletedEventArgs e);
+ #endregion
+
+ #region Event
+ ///
+ /// 下载进度变化事件
+ ///
+ public event DownloadProgressChangedEventHandler OnDownloadProgressChanged;
+
+ ///
+ /// 下载完成事件
+ ///
+ public event DownloadCompletedEventHandler OnDownloadComplated;
+
+ ///
+ /// 自动重下事件
+ ///
+ public event ReDownloadEventHandler OnReDownload;
+ #endregion
+
+ #region Method
+ ///
+ /// 设置下载参数
+ ///
+ ///
+ public void SetDownloadSetting(DownloadSetting dwSetting)
+ {
+ if (dwSetting != null)
+ downloadSetting = dwSetting;
+ }
+
+ private void DoProcessChangedEvent(DownloadProgressChangedEventArgs args)
+ {
+ OnDownloadProgressChanged?.Invoke(this, args);
+ }
+
+ private void DoCompletedEvent(DownloadCompletedEventArgs args)
+ {
+ OnDownloadComplated?.Invoke(this, args);
+ }
+
+ private void DoReDownloadEvent(DownloadCompletedEventArgs args)
+ {
+ OnReDownload?.Invoke(this, args);
+ }
+
+ private void ArgsInit()
+ {
+ changeArgs.Cancel = false;
+ complateArgs.Cancelled = false;
+ complateArgs.Error = null;
+ }
+
+ ///
+ /// 获取文件总大小
+ ///
+ ///
+ ///
+ public long GetTotalLength(string url)
+ {
+ long length = 0;
+ try
+ {
+ using (var httpClient = httpClientFactory.CreateClient())
+ {
+ var requestMessage = new HttpRequestMessage(HttpMethod.Head, url);
+ var httpResponse = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).Result;
+ if (httpResponse.IsSuccessStatusCode)
+ length = httpResponse.Content.Headers.ContentLength ?? 0;
+ }
+
+ //var req = (HttpWebRequest)WebRequest.Create(new Uri(url));
+ //req.Method = "HEAD";
+ //req.Timeout = 5000;
+ //var res = (HttpWebResponse)req.GetResponse();
+ //if (res.StatusCode == HttpStatusCode.OK)
+ //{
+ // length = res.ContentLength;
+ //}
+ //res.Close();
+ return length;
+ }
+ catch (WebException wex)
+ {
+ throw wex;
+ }
+ }
+
+
+ private bool DownloadFile(string url, string savePath, long startPosition, long totalSize)
+ {
+ long currentReadSize = 0; //当前已经读取的字节数
+ if (startPosition != 0)
+ currentReadSize = startPosition;
+ using (var httpClient = httpClientFactory.CreateClient())
+ {
+ try
+ {
+ httpClient.Timeout = new TimeSpan(0, 0, 20);
+ if (currentReadSize != 0 && currentReadSize == totalSize)
+ {
+ changeArgs.CurrentSize = changeArgs.TotalSize = totalSize;
+ return true;
+ }
+ if (currentReadSize != 0)
+ {
+ try
+ {
+ httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(currentReadSize, totalSize);
+ }
+ catch (Exception ex)
+ {
+ //http 416
+ currentReadSize = 0;
+ }
+ }
+
+ using (var response = httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result)
+ {
+ using (var responseStream = response.Content.ReadAsStreamAsync().Result)
+ {
+ changeArgs.TotalSize = totalSize;
+ using (var fs = new FileStream(savePath, currentReadSize == 0 ? FileMode.Create : FileMode.Append, FileAccess.Write))
+ {
+ //判断服务器是否支持断点续下
+ if (currentReadSize != 0 &&
+ response.StatusCode == HttpStatusCode.PartialContent &&
+ response.Content.Headers.ContentLength != totalSize)
+ fs.Seek(startPosition, SeekOrigin.Begin); //支持,从本地文件流末尾进行写入
+ else
+ currentReadSize = 0; //不支持,从本地文件流开始进行写入
+
+ if (buffer.Length != downloadSetting.BufferSize)
+ buffer = new byte[downloadSetting.BufferSize];
+ int size = responseStream.Read(buffer, 0, buffer.Length);
+ while (size != 0)
+ {
+ fs.Write(buffer, 0, size);
+ if (buffer.Length != downloadSetting.BufferSize)
+ buffer = new byte[downloadSetting.BufferSize];
+ size = responseStream.Read(buffer, 0, buffer.Length);
+ currentReadSize += size; //累计下载大小
+ //执行下载进度变化事件
+ changeArgs.CurrentSize = currentReadSize;
+ DoProcessChangedEvent(changeArgs);
+ //判断挂起状态
+ if (changeArgs.Cancel)
+ return true;
+ }
+
+ //执行下载进度变化事件
+ changeArgs.CurrentSize = changeArgs.TotalSize;
+ DoProcessChangedEvent(changeArgs);
+ fs.Flush(true);
+ }
+
+ //验证远程服务器文件字节数和本地文件字节数是否一致
+ long localFileSize = 0;
+ try
+ {
+ localFileSize = new FileInfo(savePath).Length;
+ }
+ catch (Exception ex)
+ {
+ localFileSize = changeArgs.CurrentSize;
+ }
+
+ if (totalSize != localFileSize)
+ {
+ complateArgs.Error = new Exception(string.Format("远程文件字节:[{0}]与本地文件字节:[{1}]不一致", totalSize, localFileSize));
+ }
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ complateArgs.Error = ex;
+ }
+ catch (WebException ex)
+ {
+ complateArgs.Error = ex;
+ }
+ catch (Exception ex)
+ {
+ complateArgs.Error = ex;
+ }
+ }
+ return complateArgs.Error == null;
+ }
+
+
+ public bool DownloadFile(string url, string saveFolderPath, string saveFileName, long? totalSize)
+ {
+ ArgsInit();
+ var result = false;
+ //验证文件夹
+ try
+ {
+ if (!Directory.Exists(saveFolderPath))
+ Directory.CreateDirectory(saveFolderPath);
+ }
+ catch (Exception ex)
+ {
+ complateArgs.Error = ex;
+ DoCompletedEvent(complateArgs);
+ return result;
+ }
+
+ if (totalSize == null || totalSize.Value == 0)
+ {
+ try
+ {
+ totalSize = GetTotalLength(url);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("获取远程服务器字节数失败 {0}", ex.Message);
+ complateArgs.Error = ex;
+ DoCompletedEvent(complateArgs);
+ return result;
+ }
+ }
+
+ string saveFilePath = Path.Combine(saveFolderPath, saveFileName);
+ long startPosition = 0;
+ for (var i = 1; i <= downloadSetting.TryCount; i++)
+ {
+ if (File.Exists(saveFilePath))
+ {
+ try
+ {
+ startPosition = new FileInfo(saveFilePath).Length;
+ }
+ catch
+ {
+ startPosition = 0;
+ }
+ }
+
+ result = DownloadFile(url, saveFilePath, startPosition, totalSize.Value);
+ if (result)
+ {
+ break;
+ }
+ else
+ {
+ if (i != downloadSetting.TryCount)
+ {
+ complateArgs.TryCount = i + 1;
+ DoReDownloadEvent(complateArgs);
+ Thread.Sleep(downloadSetting.SleepTime);
+ if (complateArgs.Cancelled)
+ break;
+ ArgsInit();
+ }
+ }
+ if (complateArgs.Cancelled)
+ break;
+ }
+ DoCompletedEvent(complateArgs);
+ return result;
+ }
+
+ public byte[] DwonloadBytes(string url)
+ {
+ ArgsInit();
+ using (var httpClient = httpClientFactory.CreateClient())
+ {
+ return httpClient.GetByteArrayAsync(url).Result;
+ }
+
+ }
+
+ ///
+ /// 取消/暂停下载
+ ///
+ public void CancelDownload()
+ {
+ this.changeArgs.Cancel = true;
+ this.complateArgs.Cancelled = true;
+ }
+ #endregion
+ }
+
+
+ ///
+ /// 下载进度变化参数
+ ///
+ public class DownloadProgressChangedEventArgs
+ {
+ public DownloadProgressChangedEventArgs()
+ {
+ ProgressPercentage = 0.0;
+ Cancel = false;
+ }
+ ///
+ /// 下载进度百分比
+ ///
+ public double ProgressPercentage;
+
+
+ private long currentSize;
+
+ ///
+ /// 当前下载总大小
+ ///
+ public long CurrentSize
+ {
+ get { return currentSize; }
+ set
+ {
+ currentSize = value;
+ this.ProgressPercentage = Math.Round((CurrentSize * 1.0 / TotalSize) * 100, 2);
+ }
+ }
+
+ ///
+ /// 文件总大小
+ ///
+ public long TotalSize;
+
+ ///
+ /// 取消状态
+ ///
+ public bool Cancel;
+ }
+
+ ///
+ /// 下载完成参数
+ ///
+ public class DownloadCompletedEventArgs
+ {
+ public DownloadCompletedEventArgs()
+ {
+ Cancelled = false;
+ Error = null;
+ }
+ ///
+ /// 下载异常
+ ///
+ public Exception Error;
+
+ ///
+ /// 重试次数
+ ///
+ public int TryCount;
+
+ ///
+ /// 下载操作是否被取消 【取消则为true;默认false】
+ ///
+ public bool Cancelled;
+ }
+
+ public class DownloadSetting
+ {
+ public DownloadSetting()
+ {
+ this.BufferSize = 1024 * 1024 * 1;
+ this.TryCount = 5;
+ this.SleepTime = 5000;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// 毫秒
+ public DownloadSetting(int bufferSize, int tryCount, int sleepTime)
+ {
+ this.BufferSize = bufferSize;
+ this.TryCount = tryCount;
+ this.SleepTime = sleepTime;
+ }
+
+ ///
+ /// 下载缓冲区大小
+ ///
+ public int BufferSize;
+
+ ///
+ /// 重试次数
+ ///
+ public int TryCount;
+
+ ///
+ /// 自动重下休眠时间, 毫秒
+ ///
+ public int SleepTime;
+ }
+}
diff --git a/QYMessageCenter.Common/Http/RestAPIService.cs b/QYMessageCenter.Common/Http/RestAPIService.cs
new file mode 100644
index 0000000..a28b059
--- /dev/null
+++ b/QYMessageCenter.Common/Http/RestAPIService.cs
@@ -0,0 +1,117 @@
+using QYMessageCenter.Common.Extensions;
+using Newtonsoft.Json;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Text;
+
+namespace QYMessageCenter.Common.Http
+{
+ public class RestApiService
+ {
+ public const string ContentType_Json = "application/json";
+ public const string ContentType_Form = "application/x-www-form-urlencoded";
+ public TimeSpan TimeOut { get; set; } = new TimeSpan(0, 0, 40);
+
+ private IHttpClientFactory httpClientFactory;
+
+ public RestApiService(IHttpClientFactory httpClientFactory)
+ {
+ this.httpClientFactory = httpClientFactory;
+ }
+
+ ///
+ /// 发送请求
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public RestApiResult SendRequest(string apiHost,
+ string apiPath,
+ object param,
+ IDictionary requestHeaders,
+ HttpMethod httpMethod,
+ string contentType = ContentType_Json,
+ ParamPosition paramPosition = ParamPosition.Body,
+ bool enableRandomTimeStamp = false,
+ bool getResponseHeader = false,
+ HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead,
+ string httpClientName = "",
+ int timeOutSeconds = 0)
+ {
+ //Get和Delete强制使用QueryString形式传参
+ if (httpMethod == HttpMethod.Get)
+ paramPosition = ParamPosition.Query;
+
+ //拼接Url
+
+ var url = $"{apiHost}{(apiHost.EndsWith("/") ? string.Empty : (string.IsNullOrEmpty(apiPath) ? string.Empty : "/"))}{(apiPath.StartsWith("/") ? apiPath.Substring(1) : apiPath)}";
+
+ var isCombineParam = false;
+ if (paramPosition == ParamPosition.Query && param != null)
+ {
+ url = $"{url}{(param.ToString().StartsWith("?") ? string.Empty : "?")}{param}";
+ isCombineParam = true;
+ }
+
+ //使用时间戳绕过CDN
+ if (enableRandomTimeStamp)
+ url = $"{url}{(isCombineParam ? "&" : "?")}t={DateTime.Now.DateTimeToStamp()}";
+
+ using (var httpClient = string.IsNullOrEmpty(httpClientName) ? httpClientFactory.CreateClient() : httpClientFactory.CreateClient(httpClientName))
+ {
+ if (timeOutSeconds == 0)
+ httpClient.Timeout = TimeOut;
+ else
+ httpClient.Timeout = TimeSpan.FromSeconds(timeOutSeconds);
+ using (var request = new HttpRequestMessage(httpMethod, url))
+ {
+ if (requestHeaders != null && requestHeaders.Count > 0)
+ foreach (var key in requestHeaders.Keys)
+ request.Headers.Add(key, requestHeaders[key]);
+
+ if (paramPosition == ParamPosition.Body && param != null)
+ request.Content = new StringContent(contentType == ContentType_Json ? JsonConvert.SerializeObject(param) : param.ToString(), Encoding.UTF8, contentType);
+
+ using (var response = httpClient.SendAsync(request, httpCompletionOption).Result)
+ {
+ return new RestApiResult()
+ {
+ StatusCode = response.StatusCode,
+ Content = httpCompletionOption == HttpCompletionOption.ResponseContentRead ? response.Content.ReadAsStringAsync().Result :
+ string.Empty,
+ Headers = getResponseHeader ? response.Headers : null
+ };
+ }
+ }
+ }
+ }
+ }
+
+ public class RestApiResult
+ {
+ public HttpStatusCode StatusCode { get; set; }
+
+ public string Content { get; set; }
+
+ public HttpResponseHeaders Headers { get; set; }
+ }
+
+ ///
+ /// 参数传递位置
+ ///
+ public enum ParamPosition
+ {
+ Query,
+ Body
+ }
+}
diff --git a/QYMessageCenter.Common/Models/ApiResponse.cs b/QYMessageCenter.Common/Models/ApiResponse.cs
new file mode 100644
index 0000000..fe61a9e
--- /dev/null
+++ b/QYMessageCenter.Common/Models/ApiResponse.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace QYMessageCenter.Common.Models
+{
+ public class ApiResponse
+ {
+ public bool Success { get { return Code == 200; } }
+
+ ///
+ /// 接口调用状态
+ ///
+ public int Code { get; set; } = 200;
+
+ ///
+ /// 返回消息
+ ///
+ public string Msg { get; set; }
+
+ ///
+ /// 数据内容
+ ///
+ public T Data { get; set; }
+
+ public static ApiResponse Error(int code, string msg)
+ {
+ return new ApiResponse() { Code = code, Msg = msg };
+ }
+ }
+
+ public class ApiResponse : ApiResponse