shanji 1 year ago
parent
commit
3a35c5fe20
  1. 21
      QYMessageCenter.Business/BaseBusiness.cs
  2. 45
      QYMessageCenter.Business/MessageBusiness.cs
  3. 21
      QYMessageCenter.Business/QYMessageCenter.Business.csproj
  4. 12
      QYMessageCenter.Business/TaskSchedulerManager.cs
  5. 29
      QYMessageCenter.Common/Extensions/ConverterExtensions.cs
  6. 12
      QYMessageCenter.Common/Extensions/CopyExtensions.cs
  7. 102
      QYMessageCenter.Common/Extensions/DateTimeExtension.cs
  8. 82
      QYMessageCenter.Common/Extensions/EncryptionExtension.cs
  9. 59
      QYMessageCenter.Common/Extensions/MapperExtension.cs
  10. 34
      QYMessageCenter.Common/Extensions/StartupExtension.cs
  11. 456
      QYMessageCenter.Common/Http/HttpDownloader.cs
  12. 117
      QYMessageCenter.Common/Http/RestAPIService.cs
  13. 34
      QYMessageCenter.Common/Models/ApiResponse.cs
  14. 22
      QYMessageCenter.Common/Models/BusinessException.cs
  15. 10
      QYMessageCenter.Common/Models/IDenpendency.cs
  16. 18
      QYMessageCenter.Common/QYMessageCenter.Common.csproj
  17. 70
      QYMessageCenter.Common/TaskSchedulers/DelayTrigger.cs
  18. 153
      QYMessageCenter.Common/TaskSchedulers/LimitedConcurrencyLevelTaskScheduler.cs
  19. 78
      QYMessageCenter.Model/DB/QYNotification.cs
  20. 62
      QYMessageCenter.Model/DTO/Message/SendMessageRequest.cs
  21. 12
      QYMessageCenter.Model/MappingProfiles.cs
  22. 15
      QYMessageCenter.Model/QYMessageCenter.Model.csproj
  23. 2
      QYMessageCenter.Model/__重新生成.bat
  24. 20
      QYMessageCenter.sln
  25. 36
      QYMessageCenter/Controllers/BaseApiController.cs
  26. 28
      QYMessageCenter/Controllers/MessageController.cs
  27. 33
      QYMessageCenter/Controllers/WeatherForecastController.cs
  28. 30
      QYMessageCenter/Filters/ResultFilter.cs
  29. 55
      QYMessageCenter/Middlewares/ClientVersionValidationMiddleWare.cs
  30. 85
      QYMessageCenter/Middlewares/CustomExceptionMiddleWare.cs
  31. 119
      QYMessageCenter/Program.cs
  32. 13
      QYMessageCenter/QYMessageCenter.csproj
  33. 13
      QYMessageCenter/WeatherForecast.cs
  34. 6
      QYMessageCenter/appsettings.json

21
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;
}
}
}

45
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)
{
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="request"></param>
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
}
}
}

21
QYMessageCenter.Business/QYMessageCenter.Business.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FreeSql" Version="3.2.812" />
<PackageReference Include="FreeSql.Provider.MySql" Version="3.2.812" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\QYMessageCenter.Common\QYMessageCenter.Common.csproj" />
<ProjectReference Include="..\QYMessageCenter.Model\QYMessageCenter.Model.csproj" />
</ItemGroup>
</Project>

12
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
{
}
}

29
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;
}
}
}
}

12
QYMessageCenter.Common/Extensions/CopyExtensions.cs

@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace QYMessageCenter.Common.Extensions
{
public static class CopyExtensions
{
public static T Copy<T>(this T p)
{
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(p));
}
}
}

102
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);
/// <summary>
/// 时间戳转时间
/// </summary>
/// <param name="timeStamp">时间</param>
/// <param name="len13">true:13, false:10</param>
/// <returns></returns>
[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);
}
/// <summary>
/// 时间转时间戳
/// </summary>
/// <param name="time">时间</param>
/// <param name="len13">true:13, false:10</param>
/// <returns></returns>
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);
}
/// <summary>
/// 将秒数转换为时分秒形式
/// </summary>
/// <param name="second"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 修改系统时间(需要管理员权限)
/// </summary>
/// <param name="date"></param>
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
}
}

82
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));
}
}
}

59
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;
/// <summary>
/// 注册对象映射器
/// </summary>
/// <param name="services"></param>
/// <param name="profile"></param>
/// <returns></returns>
public static IServiceCollection AddMapper(this IServiceCollection services, Profile profile)
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile(profile);
});
_mapper = config.CreateMapper();
return services;
}
/// <summary>
/// 设置对象映射执行者
/// </summary>
/// <param name="mapper">映射执行者</param>
public static void SetMapper(IMapper mapper)
{
_mapper = mapper;
}
/// <summary>
/// 将对象映射为指定类型
/// </summary>
/// <typeparam name="TTarget">要映射的目标类型</typeparam>
/// <param name="source">源对象</param>
/// <returns>目标类型的对象</returns>
public static TTarget Map<TTarget>(this object source)
{
return _mapper.Map<TTarget>(source);
}
/// <summary>
/// 使用源类型的对象更新目标类型的对象
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TTarget">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <param name="target">待更新的目标对象</param>
/// <returns>更新后的目标类型对象</returns>
public static TTarget Map<TSource, TTarget>(this TSource source, TTarget target)
{
return _mapper.Map(source, target);
}
}
}

34
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<Type> typeList = new List<Type>(); //所有符合注册条件的类集合
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));
}
}
}
}

456
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
{
/// <summary>
/// Http下载器
/// </summary>
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
/// <summary>
/// 下载进度变化参数
/// </summary>
private DownloadProgressChangedEventArgs changeArgs;
/// <summary>
/// 下载完成参数
/// </summary>
private DownloadCompletedEventArgs complateArgs;
/// <summary>
/// 下载参数配置类
/// </summary>
private DownloadSetting downloadSetting;
/// <summary>
/// 下载缓冲区
/// </summary>
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
/// <summary>
/// 下载进度变化事件
/// </summary>
public event DownloadProgressChangedEventHandler OnDownloadProgressChanged;
/// <summary>
/// 下载完成事件
/// </summary>
public event DownloadCompletedEventHandler OnDownloadComplated;
/// <summary>
/// 自动重下事件
/// </summary>
public event ReDownloadEventHandler OnReDownload;
#endregion
#region Method
/// <summary>
/// 设置下载参数
/// </summary>
/// <param name="dwSetting"></param>
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;
}
/// <summary>
/// 获取文件总大小
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 取消/暂停下载
/// </summary>
public void CancelDownload()
{
this.changeArgs.Cancel = true;
this.complateArgs.Cancelled = true;
}
#endregion
}
/// <summary>
/// 下载进度变化参数
/// </summary>
public class DownloadProgressChangedEventArgs
{
public DownloadProgressChangedEventArgs()
{
ProgressPercentage = 0.0;
Cancel = false;
}
/// <summary>
/// 下载进度百分比
/// </summary>
public double ProgressPercentage;
private long currentSize;
/// <summary>
/// 当前下载总大小
/// </summary>
public long CurrentSize
{
get { return currentSize; }
set
{
currentSize = value;
this.ProgressPercentage = Math.Round((CurrentSize * 1.0 / TotalSize) * 100, 2);
}
}
/// <summary>
/// 文件总大小
/// </summary>
public long TotalSize;
/// <summary>
/// 取消状态
/// </summary>
public bool Cancel;
}
/// <summary>
/// 下载完成参数
/// </summary>
public class DownloadCompletedEventArgs
{
public DownloadCompletedEventArgs()
{
Cancelled = false;
Error = null;
}
/// <summary>
/// 下载异常
/// </summary>
public Exception Error;
/// <summary>
/// 重试次数
/// </summary>
public int TryCount;
/// <summary>
/// 下载操作是否被取消 【取消则为true;默认false】
/// </summary>
public bool Cancelled;
}
public class DownloadSetting
{
public DownloadSetting()
{
this.BufferSize = 1024 * 1024 * 1;
this.TryCount = 5;
this.SleepTime = 5000;
}
/// <summary>
///
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="tryCount"></param>
/// <param name="sleepTime">毫秒</param>
public DownloadSetting(int bufferSize, int tryCount, int sleepTime)
{
this.BufferSize = bufferSize;
this.TryCount = tryCount;
this.SleepTime = sleepTime;
}
/// <summary>
/// 下载缓冲区大小
/// </summary>
public int BufferSize;
/// <summary>
/// 重试次数
/// </summary>
public int TryCount;
/// <summary>
/// 自动重下休眠时间, 毫秒
/// </summary>
public int SleepTime;
}
}

117
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;
}
/// <summary>
/// 发送请求
/// </summary>
/// <param name="apiHost"></param>
/// <param name="apiPath"></param>
/// <param name="param"></param>
/// <param name="requestHeaders"></param>
/// <param name="httpMethod"></param>
/// <param name="contentType"></param>
/// <param name="paramPosition"></param>
/// <param name="enableRandomTimeStamp"></param>
/// <param name="getResponseHeader"></param>
/// <param name="httpCompletionOption"></param>
/// <param name="httpClientName"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public RestApiResult SendRequest(string apiHost,
string apiPath,
object param,
IDictionary<string, string> 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; }
}
/// <summary>
/// 参数传递位置
/// </summary>
public enum ParamPosition
{
Query,
Body
}
}

34
QYMessageCenter.Common/Models/ApiResponse.cs

@ -0,0 +1,34 @@
using System;
namespace QYMessageCenter.Common.Models
{
public class ApiResponse<T>
{
public bool Success { get { return Code == 200; } }
/// <summary>
/// 接口调用状态
/// </summary>
public int Code { get; set; } = 200;
/// <summary>
/// 返回消息
/// </summary>
public string Msg { get; set; }
/// <summary>
/// 数据内容
/// </summary>
public T Data { get; set; }
public static ApiResponse<T> Error(int code, string msg)
{
return new ApiResponse<T>() { Code = code, Msg = msg };
}
}
public class ApiResponse : ApiResponse<object>
{
}
}

22
QYMessageCenter.Common/Models/BusinessException.cs

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace QYMessageCenter.Common.Models
{
/// <summary>
/// 业务异常
/// </summary>
public class BusinessException : Exception
{
public BusinessException(string message) : base(message)
{
}
/// <summary>
/// 错误代码
/// </summary>
public int Code { get; set; }
}
}

10
QYMessageCenter.Common/Models/IDenpendency.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace QYMessageCenter.Common.Models
{
public interface IDenpendency
{
}
}

18
QYMessageCenter.Common/QYMessageCenter.Common.csproj

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.8" />
</ItemGroup>
</Project>

70
QYMessageCenter.Common/TaskSchedulers/DelayTrigger.cs

@ -0,0 +1,70 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace QYMessageCenter.Common.Trigger
{
/// <summary>
/// 延迟触发组件
/// </summary>
public class DelayTrigger
{
public DelayTrigger(int delayTime = 1000)
{
if (delayTime < 1000)
delayTime = 1000;
this.delayTime = delayTime;
}
/// <summary>
/// 延迟执行时间(ms)
/// </summary>
private int delayTime;
/// <summary>
/// 关键字
/// </summary>
private string currentKey;
/// <summary>
/// 是否可以执行
/// </summary>
private volatile bool canExecute;
/// <summary>
/// 是否正在延迟响应中
/// </summary>
private volatile bool isDelaying;
public Action<string> OnExecute;
public void SetKey(string key)
{
currentKey = key;
if (isDelaying)
{
canExecute = false;
return;
}
Task.Factory.StartNew(delegate
{
isDelaying = true;
while (true)
{
canExecute = true;
Thread.Sleep(delayTime);
if (canExecute)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"DelayTrigger {currentKey} Execute at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ffff")}");
Console.ResetColor();
OnExecute?.Invoke(currentKey);
isDelaying = false;
break;
}
}
});
}
}
}

153
QYMessageCenter.Common/TaskSchedulers/LimitedConcurrencyLevelTaskScheduler.cs

@ -0,0 +1,153 @@
//--------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: LimitedConcurrencyTaskScheduler.cs
//
//--------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
namespace System.Threading.Tasks.Schedulers
{
/// <summary>
/// Provides a task scheduler that ensures a maximum concurrency level while
/// running on top of the ThreadPool.
/// </summary>
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
/// <summary>Whether the current thread is processing work items.</summary>
[ThreadStatic]
private static bool _currentThreadIsProcessingItems;
/// <summary>The list of tasks to be executed.</summary>
private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
/// <summary>The maximum concurrency level allowed by this scheduler.</summary>
private readonly int _maxDegreeOfParallelism;
/// <summary>Whether the scheduler is currently processing work items.</summary>
private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks)
/// <summary>
/// 最大并发数
/// </summary>
private readonly int maxConcurrencyCountOfSystem = Environment.ProcessorCount * 2;
/// <summary>
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the
/// specified degree of parallelism.
/// </summary>
/// <param name="maxDegreeOfParallelism">The maximum degree of parallelism provided by this scheduler.</param>
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
if (maxDegreeOfParallelism > maxConcurrencyCountOfSystem)
maxDegreeOfParallelism = maxConcurrencyCountOfSystem;
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}
/// <summary>Queues a task to the scheduler.</summary>
/// <param name="task">The task to be queued.</param>
protected sealed override void QueueTask(Task task)
{
// Add the task to the list of tasks to be processed. If there aren't enough
// delegates currently queued or running to process tasks, schedule another.
lock (_tasks)
{
_tasks.AddLast(task);
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
{
++_delegatesQueuedOrRunning;
NotifyThreadPoolOfPendingWork();
}
}
}
/// <summary>
/// Informs the ThreadPool that there's work to be executed for this scheduler.
/// </summary>
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
// Note that the current thread is now processing work items.
// This is necessary to enable inlining of tasks into this thread.
//new Thread(() =>
//{
_currentThreadIsProcessingItems = true;
try
{
// Process all available items in the queue.
while (true)
{
Task item;
lock (_tasks)
{
// When there are no more items to be processed,
// note that we're done processing, and get out.
if (_tasks.Count == 0)
{
--_delegatesQueuedOrRunning;
break;
}
// Get the next item from the queue
item = _tasks.First.Value;
_tasks.RemoveFirst();
}
// Execute the task we pulled out of the queue
base.TryExecuteTask(item);
}
}
// We're done processing items on the current thread
finally { _currentThreadIsProcessingItems = false; }
//})
//{ IsBackground = true }.Start();
}, null);
}
/// <summary>Attempts to execute the specified task on the current thread.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued"></param>
/// <returns>Whether the task could be executed on the current thread.</returns>
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// If this thread isn't already processing a task, we don't support inlining
if (!_currentThreadIsProcessingItems) return false;
// If the task was previously queued, remove it from the queue
if (taskWasPreviouslyQueued) TryDequeue(task);
// Try to run the task.
return base.TryExecuteTask(task);
}
/// <summary>Attempts to remove a previously scheduled task from the scheduler.</summary>
/// <param name="task">The task to be removed.</param>
/// <returns>Whether the task could be found and removed.</returns>
protected sealed override bool TryDequeue(Task task)
{
lock (_tasks) return _tasks.Remove(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }
/// <summary>Gets an enumerable of the tasks currently scheduled on this scheduler.</summary>
/// <returns>An enumerable of the tasks currently scheduled.</returns>
protected sealed override IEnumerable<Task> GetScheduledTasks()
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_tasks, ref lockTaken);
if (lockTaken) return _tasks.ToArray();
else throw new NotSupportedException();
}
finally
{
if (lockTaken) Monitor.Exit(_tasks);
}
}
}
}

78
QYMessageCenter.Model/DB/QYNotification.cs

@ -0,0 +1,78 @@
using FreeSql.DataAnnotations;
namespace QYMessageCenter.Model.DB
{
[Table(Name = "qynotification", DisableSyncStructure = true)]
public partial class Qynotification
{
[Column(DbType = "bigint", IsPrimary = true)]
public long Id { get; set; }
/// <summary>
/// 消息所属应用
/// </summary>
[Column(StringLength = 50)]
public string AppCode { get; set; }
/// <summary>
/// 消息频道
/// </summary>
[Column(StringLength = 50)]
public string Channel { get; set; }
/// <summary>
/// 消息所属团队Id
/// </summary>
[Column(StringLength = 50)]
public string TeamId { get; set; }
/// <summary>
/// 消息所属店铺Id
/// </summary>
[Column(StringLength = 50)]
public string ShopId { get; set; }
/// <summary>
/// 消息内容
/// </summary>
[Column(StringLength = 500)]
public string Content { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 自定义类型编码
/// </summary>
[Column(StringLength = 50)]
public string CustomTypeCode { get; set; }
/// <summary>
/// 已读人员
/// </summary>
[Column(StringLength = 1000)]
public string ReaderId { get; set; }
/// <summary>
/// 发送人
/// </summary>
[Column(StringLength = 50)]
public string SenderId { get; set; }
/// <summary>
/// 接收人(可空)
/// </summary>
[Column(StringLength = 1000)]
public string RecevierId { get; set; }
/// <summary>
/// 是否为Json消息, 解析规则参考CustomTypeCode的约定
/// </summary>
[Column(DbType = "bit")]
public bool IsJsonMsg { get; set; }
}
}

62
QYMessageCenter.Model/DTO/Message/SendMessageRequest.cs

@ -0,0 +1,62 @@
using System.ComponentModel.DataAnnotations;
namespace QYMessageCenter.Model.DTO
{
public class SendMessageRequest
{
/// <summary>
/// 消息所属应用
/// </summary>
[Required(ErrorMessage = "AppCode不能为空")]
public string AppCode { get; set; }
/// <summary>
/// 消息频道
/// </summary>
[Required(ErrorMessage = "Channel不能为空")]
public string Channel { get; set; }
/// <summary>
/// 消息所属团队Id
/// </summary>
[Required(ErrorMessage = "TeamId不能为空")]
public string TeamId { get; set; }
/// <summary>
/// 消息所属店铺Id
/// </summary>
public string ShopId { get; set; }
/// <summary>
/// 消息内容
/// </summary>
[Required(ErrorMessage = "消息内容不能为空")]
public string Content { get; set; }
/// <summary>
/// 自定义类型编码
/// </summary>
[Required(ErrorMessage = "自定义类型编码不能为空")]
public string CustomTypeCode { get; set; }
/// <summary>
/// 已读人员
/// </summary>
public string ReaderId { get; set; }
/// <summary>
/// 发送人
/// </summary>
public string SenderId { get; set; }
/// <summary>
/// 接收人(可空)
/// </summary>
public string RecevierId { get; set; }
/// <summary>
/// 是否为Json消息, 解析规则参考CustomTypeCode的约定
/// </summary>
public bool IsJsonMsg { get; set; }
}
}

12
QYMessageCenter.Model/MappingProfiles.cs

@ -0,0 +1,12 @@
namespace QYMessageCenter.Model
{
public class MappingProfiles : AutoMapper.Profile
{
// private IDictionary<string, string> storeDictionary;
public MappingProfiles()
{
}
}
}

15
QYMessageCenter.Model/QYMessageCenter.Model.csproj

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="FreeSql" Version="3.2.812" />
</ItemGroup>
</Project>

2
QYMessageCenter.Model/__重新生成.bat

@ -0,0 +1,2 @@
FreeSql.Generator -Razor 1 -NameOptions 1,0,0,0 -NameSpace QYMessageCenter.Model.DB -DB "MySql,data source=rm-bp1508okrh23710yfao.mysql.rds.aliyuncs.com;port=3306;user id=qyroot;password=kaicn1132+-;initial catalog=qymessagecenter;charset=utf8;sslmode=none;" -FileName "{name}.cs"

20
QYMessageCenter.sln

@ -3,7 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QYMessageCenter", "QYMessageCenter\QYMessageCenter.csproj", "{2EA5C5B7-A4AD-4841-9CBD-3D1039ED1355}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QYMessageCenter", "QYMessageCenter\QYMessageCenter.csproj", "{2EA5C5B7-A4AD-4841-9CBD-3D1039ED1355}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QYMessageCenter.Business", "QYMessageCenter.Business\QYMessageCenter.Business.csproj", "{17CB9C4E-4008-4E5A-98B2-40B666922CE1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QYMessageCenter.Model", "QYMessageCenter.Model\QYMessageCenter.Model.csproj", "{91F9B6F7-EB22-42AE-80A9-0FF427B055A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QYMessageCenter.Common", "QYMessageCenter.Common\QYMessageCenter.Common.csproj", "{3414DE6F-7D25-4A09-892E-3A359101EB44}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -15,6 +21,18 @@ Global
{2EA5C5B7-A4AD-4841-9CBD-3D1039ED1355}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2EA5C5B7-A4AD-4841-9CBD-3D1039ED1355}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2EA5C5B7-A4AD-4841-9CBD-3D1039ED1355}.Release|Any CPU.Build.0 = Release|Any CPU
{17CB9C4E-4008-4E5A-98B2-40B666922CE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17CB9C4E-4008-4E5A-98B2-40B666922CE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17CB9C4E-4008-4E5A-98B2-40B666922CE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17CB9C4E-4008-4E5A-98B2-40B666922CE1}.Release|Any CPU.Build.0 = Release|Any CPU
{91F9B6F7-EB22-42AE-80A9-0FF427B055A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91F9B6F7-EB22-42AE-80A9-0FF427B055A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91F9B6F7-EB22-42AE-80A9-0FF427B055A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91F9B6F7-EB22-42AE-80A9-0FF427B055A6}.Release|Any CPU.Build.0 = Release|Any CPU
{3414DE6F-7D25-4A09-892E-3A359101EB44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3414DE6F-7D25-4A09-892E-3A359101EB44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3414DE6F-7D25-4A09-892E-3A359101EB44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3414DE6F-7D25-4A09-892E-3A359101EB44}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

36
QYMessageCenter/Controllers/BaseApiController.cs

@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
namespace QYMessageCenter.Controllers
{
[Produces("application/json")]
[Route("Api/[Controller]/[Action]")]
[ApiController]
[EnableCors("cors")]
public class BaseApiController : ControllerBase
{
protected IHttpContextAccessor httpContextAccessor;
public BaseApiController(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
protected string GetUserId()
{
return httpContextAccessor?.HttpContext?.User.Claims.Where(x => x.Type == "userId")?.FirstOrDefault()?.Value;
}
protected string GetToken()
{
httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Authorization", out StringValues token);
return token;
}
//protected string GetClientCode()
//{
// httpContextAccessor.HttpContext.Request.Headers.TryGetValue("ClientCode", out StringValues clientCode);
// return clientCode;
//}
}
}

28
QYMessageCenter/Controllers/MessageController.cs

@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using QYMessageCenter.Business;
using QYMessageCenter.Model.DTO;
namespace QYMessageCenter.Controllers
{
public class MessageController : BaseApiController
{
private MessageBusiness messageBusiness;
public MessageController(IHttpContextAccessor httpContextAccessor, MessageBusiness messageBusiness) : base(httpContextAccessor)
{
this.messageBusiness = messageBusiness;
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="request"></param>
[HttpPost]
public void Send([FromBody] SendMessageRequest request)
{
messageBusiness.Send(request);
}
}
}

33
QYMessageCenter/Controllers/WeatherForecastController.cs

@ -1,33 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace QYMessageCenter.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

30
QYMessageCenter/Filters/ResultFilter.cs

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using QYMessageCenter.Common.Models;
namespace QYMessageCenter.Filters
{
public class ResultFilter : IResultFilter
{
public void OnResultExecuted(ResultExecutedContext context)
{
}
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ObjectResult)
{
var objectResult = context.Result as ObjectResult;
if (!(objectResult.Value is ApiResponse))
{
objectResult.Value = new ApiResponse() { Data = objectResult.Value };
}
}
else if (context.Result is EmptyResult)
{
context.Result = new ObjectResult(new ApiResponse());
}
}
}
}

55
QYMessageCenter/Middlewares/ClientVersionValidationMiddleWare.cs

@ -0,0 +1,55 @@
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using QYMessageCenter.Common.Models;
namespace QYMessageCenter.Middlewares
{
public class ClientVersionValidationMiddleWare
{
/// <summary>
/// 管道请求委托
/// </summary>
private RequestDelegate _next;
private IDictionary<string, int> apiVersionDictionary;
private IOptionsMonitor<List<ClientVersionValidationModel>> _monitor;
public ClientVersionValidationMiddleWare(RequestDelegate requestDelegate, IOptionsMonitor<List<ClientVersionValidationModel>> monitor)
{
_next = requestDelegate;
_monitor = monitor;
apiVersionDictionary = new Dictionary<string, int>();
}
public async Task Invoke(HttpContext context)
{
try
{
Console.WriteLine(context.Request.Path);
var apiRequirement = _monitor.CurrentValue.FirstOrDefault(x => x.Api.Equals(context.Request.Path, StringComparison.CurrentCultureIgnoreCase));
if (apiRequirement != null)
{
if (!context.Request.Headers.TryGetValue("ClientVersion", out StringValues clientVersionStr))
throw new BusinessException("缺少版本信息,请更新步步为盈");
if (!int.TryParse(clientVersionStr, out int clientVersion))
throw new BusinessException("版本信息不正确,请更新步步为盈");
if (clientVersion < apiRequirement.MinimumVersion)
throw new BusinessException("当前请求需更新步步为盈~!");
}
await _next(context); //调用管道执行下一个中间件
}
catch
{
throw;
}
}
}
public class ClientVersionValidationModel
{
public string Api { get; set; }
public int MinimumVersion { get; set; }
}
}

85
QYMessageCenter/Middlewares/CustomExceptionMiddleWare.cs

@ -0,0 +1,85 @@
using Newtonsoft.Json;
using QYMessageCenter.Common.Log;
using QYMessageCenter.Common.Models;
using System.Text;
namespace QYMessageCenter.Middlewares
{
public class CustomExceptionMiddleWare
{
/// <summary>
/// 管道请求委托
/// </summary>
private RequestDelegate _next;
/// <summary>
/// 需要处理的状态码字典
/// </summary>
private IDictionary<int, string> _exceptionStatusCodeDic;
private NLogManager nLogManager;
public CustomExceptionMiddleWare(RequestDelegate next, NLogManager nLogManager)
{
_next = next;
//this.logger = logger;
this.nLogManager = nLogManager;
_exceptionStatusCodeDic = new Dictionary<int, string>
{
{ 401, "未授权的请求" },
{ 404, "找不到该资源" },
{ 403, "访问被拒绝" },
{ 500, "服务器发生意外的错误" },
{ 503, "服务不可用" }
//其余状态自行扩展
};
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context); //调用管道执行下一个中间件
}
catch (Exception ex)
{
if (ex is BusinessException)
{
var busEx = ex as BusinessException;
context.Response.StatusCode = 200; //业务异常时将Http状态码改为200
await ErrorHandle(context, busEx.Code, busEx.Message);
}
else
{
context.Response.Clear();
context.Response.StatusCode = 500; //发生未捕获的异常,手动设置状态码
//logger.Error(ex); //记录错误
nLogManager.Default().Error(ex);
}
}
finally
{
if (_exceptionStatusCodeDic.TryGetValue(context.Response.StatusCode, out string exMsg))
{
await ErrorHandle(context, context.Response.StatusCode, exMsg);
}
}
}
/// <summary>
/// 处理方式:返回Json格式
/// </summary>
/// <param name="context"></param>
/// <param name="code"></param>
/// <param name="exMsg"></param>
/// <returns></returns>
private async Task ErrorHandle(HttpContext context, int code, string exMsg)
{
var apiResponse = ApiResponse.Error(code, exMsg);
var serialzeStr = JsonConvert.SerializeObject(apiResponse);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(serialzeStr, Encoding.UTF8);
}
}
}

119
QYMessageCenter/Program.cs

@ -1,23 +1,118 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Serialization;
using QYMessageCenter.Business;
using QYMessageCenter.Common.Extensions;
using QYMessageCenter.Common.Http;
using QYMessageCenter.Common.Log;
using QYMessageCenter.Common.Models;
using QYMessageCenter.Filters;
using QYMessageCenter.Middlewares;
using QYMessageCenter.Model;
using System.Reflection;
using Yitter.IdGenerator;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;
services.AddMemoryCache();
var idOption = new IdGeneratorOptions(1);
var idGenerator = new DefaultIdGenerator(idOption);
services.AddSingleton(typeof(IIdGenerator), idGenerator);
var fsql = new FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.MySql, configuration.GetConnectionString("DB")).Build();
services.AddSingleton(typeof(IFreeSql), fsql);
services.AddSingleton<NLogManager>();
services.AddSingleton<RestApiService>();
services.AddSingleton<TaskSchedulerManager>();
services.BatchRegisterServices(new Assembly[] { Assembly.Load("QYMessageCenter.Business") }, typeof(IDenpendency));
services.AddMapper(new MappingProfiles());
services.AddHttpContextAccessor();
services.AddHttpClient();
services.AddControllers(c =>
{
c.Filters.Add<ResultFilter>();
c.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
}).AddNewtonsoftJson(setupAction =>
{
setupAction.SerializerSettings.ContractResolver = new DefaultContractResolver();
setupAction.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
//setupAction.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
//setupAction.SerializerSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Include;
});
services.AddCors(options =>
{
options.AddPolicy("cors", p =>
{
p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
});
});
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1.0.0",
Title = "齐越消息中心API",
Description = "注意事项\r\n1.返回参数名称采用大驼峰命名\r\n2.ApiResponse为基础返回对象(Code,Data,Message),接口中所有的返回值均属于Data属性\r\n3.正常返回Code=200"
});
//JWT认证
c.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme
{
Scheme = JwtBearerDefaults.AuthenticationScheme,
BearerFormat = "JWT",
Type = SecuritySchemeType.ApiKey,
Name = "Authorization",
In = ParameterLocation.Header,
Description = "Authorization:Bearer {your JWT token}<br/>",
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme{Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = JwtBearerDefaults.AuthenticationScheme
}
},
new string[] { }
}
});
// Add services to the container.
var executingAssembly = Assembly.GetExecutingAssembly();
var assemblyNames = executingAssembly.GetReferencedAssemblies().Union(new AssemblyName[] { executingAssembly.GetName() }).ToArray();
Array.ForEach(assemblyNames, (assemblyName) =>
{
//var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlFile = $"{assemblyName.Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
if (!File.Exists(xmlPath))
return;
c.IncludeXmlComments(xmlPath, true);
});
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.UseSwagger(c => c.SerializeAsV2 = true)
.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "QYMessageCenter API");
c.RoutePrefix = string.Empty;
});
app.UseMiddleware<CustomExceptionMiddleWare>();
app.UseRouting();
app.UseCors("cors");
app.UseAuthorization();
app.MapControllers();
app.Run();

13
QYMessageCenter/QYMessageCenter.csproj

@ -4,10 +4,23 @@
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FreeSql" Version="3.2.812" />
<PackageReference Include="FreeSql.Provider.MySql" Version="3.2.812" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.27" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.27" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\QYMessageCenter.Business\QYMessageCenter.Business.csproj" />
<ProjectReference Include="..\QYMessageCenter.Common\QYMessageCenter.Common.csproj" />
<ProjectReference Include="..\QYMessageCenter.Model\QYMessageCenter.Model.csproj" />
</ItemGroup>
</Project>

13
QYMessageCenter/WeatherForecast.cs

@ -1,13 +0,0 @@
namespace QYMessageCenter
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

6
QYMessageCenter/appsettings.json

@ -5,5 +5,9 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"GoEasyKey": "",
"ConnectionStrings": {
"DB": "data source=rm-bp1508okrh23710yfao.mysql.rds.aliyuncs.com;port=3306;user id=qyroot;password=kaicn1132+-;initial catalog=qymessagecenter;charset=utf8;sslmode=none;"
}
}

Loading…
Cancel
Save