From 9f50af2041513edc5fc3c248a8d4592fb1001622 Mon Sep 17 00:00:00 2001 From: shanji <18996038927@163.com> Date: Wed, 8 Mar 2023 17:30:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BBWYB.Client/App.xaml | 9 + BBWYB.Client/App.xaml.cs | 17 + BBWYB.Client/AssemblyInfo.cs | 10 + BBWYB.Client/BBWYB.Client.csproj | 10 + BBWYB.Client/MainWindow.xaml | 12 + BBWYB.Client/MainWindow.xaml.cs | 28 ++ BBWYB.Common/BBWYB.Common.csproj | 16 + BBWYB.Common/Extensions/DateTimeExtension.cs | 102 ++++ BBWYB.Common/Extensions/MapperExtension.cs | 59 +++ BBWYB.Common/Extensions/StartupExtension.cs | 37 ++ BBWYB.Common/Http/HttpDownloader.cs | 456 ++++++++++++++++++ BBWYB.Common/Http/RestAPIService.cs | 117 +++++ BBWYB.Common/Models/ApiResponse.cs | 34 ++ BBWYB.Common/Models/BusinessException.cs | 22 + BBWYB.Common/Models/IDenpendency.cs | 10 + BBWYB.Common/TaskSchedulers/DelayTrigger.cs | 70 +++ .../LimitedConcurrencyLevelTaskScheduler.cs | 153 ++++++ BBWYB.Server.API/BBWYB.Server.API.csproj | 17 + .../Controllers/WeatherForecastController.cs | 33 ++ BBWYB.Server.API/Program.cs | 25 + .../Properties/launchSettings.json | 31 ++ BBWYB.Server.API/WeatherForecast.cs | 13 + BBWYB.Server.API/appsettings.Development.json | 8 + BBWYB.Server.API/appsettings.json | 9 + .../BBWYB.Server.Business.csproj | 14 + BBWYB.Server.Model/BBWYB.Server.Model.csproj | 9 + bbwyb.sln | 62 +++ 27 files changed, 1383 insertions(+) create mode 100644 BBWYB.Client/App.xaml create mode 100644 BBWYB.Client/App.xaml.cs create mode 100644 BBWYB.Client/AssemblyInfo.cs create mode 100644 BBWYB.Client/BBWYB.Client.csproj create mode 100644 BBWYB.Client/MainWindow.xaml create mode 100644 BBWYB.Client/MainWindow.xaml.cs create mode 100644 BBWYB.Common/BBWYB.Common.csproj create mode 100644 BBWYB.Common/Extensions/DateTimeExtension.cs create mode 100644 BBWYB.Common/Extensions/MapperExtension.cs create mode 100644 BBWYB.Common/Extensions/StartupExtension.cs create mode 100644 BBWYB.Common/Http/HttpDownloader.cs create mode 100644 BBWYB.Common/Http/RestAPIService.cs create mode 100644 BBWYB.Common/Models/ApiResponse.cs create mode 100644 BBWYB.Common/Models/BusinessException.cs create mode 100644 BBWYB.Common/Models/IDenpendency.cs create mode 100644 BBWYB.Common/TaskSchedulers/DelayTrigger.cs create mode 100644 BBWYB.Common/TaskSchedulers/LimitedConcurrencyLevelTaskScheduler.cs create mode 100644 BBWYB.Server.API/BBWYB.Server.API.csproj create mode 100644 BBWYB.Server.API/Controllers/WeatherForecastController.cs create mode 100644 BBWYB.Server.API/Program.cs create mode 100644 BBWYB.Server.API/Properties/launchSettings.json create mode 100644 BBWYB.Server.API/WeatherForecast.cs create mode 100644 BBWYB.Server.API/appsettings.Development.json create mode 100644 BBWYB.Server.API/appsettings.json create mode 100644 BBWYB.Server.Business/BBWYB.Server.Business.csproj create mode 100644 BBWYB.Server.Model/BBWYB.Server.Model.csproj create mode 100644 bbwyb.sln diff --git a/BBWYB.Client/App.xaml b/BBWYB.Client/App.xaml new file mode 100644 index 0000000..692fc99 --- /dev/null +++ b/BBWYB.Client/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/BBWYB.Client/App.xaml.cs b/BBWYB.Client/App.xaml.cs new file mode 100644 index 0000000..a689af0 --- /dev/null +++ b/BBWYB.Client/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace BBWYB.Client +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/BBWYB.Client/AssemblyInfo.cs b/BBWYB.Client/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/BBWYB.Client/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/BBWYB.Client/BBWYB.Client.csproj b/BBWYB.Client/BBWYB.Client.csproj new file mode 100644 index 0000000..4106cb0 --- /dev/null +++ b/BBWYB.Client/BBWYB.Client.csproj @@ -0,0 +1,10 @@ + + + + WinExe + net6.0-windows + enable + true + + + diff --git a/BBWYB.Client/MainWindow.xaml b/BBWYB.Client/MainWindow.xaml new file mode 100644 index 0000000..06898df --- /dev/null +++ b/BBWYB.Client/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/BBWYB.Client/MainWindow.xaml.cs b/BBWYB.Client/MainWindow.xaml.cs new file mode 100644 index 0000000..f80b15d --- /dev/null +++ b/BBWYB.Client/MainWindow.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace BBWYB.Client +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/BBWYB.Common/BBWYB.Common.csproj b/BBWYB.Common/BBWYB.Common.csproj new file mode 100644 index 0000000..7c02750 --- /dev/null +++ b/BBWYB.Common/BBWYB.Common.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/BBWYB.Common/Extensions/DateTimeExtension.cs b/BBWYB.Common/Extensions/DateTimeExtension.cs new file mode 100644 index 0000000..c35ca35 --- /dev/null +++ b/BBWYB.Common/Extensions/DateTimeExtension.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace BBWY.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/BBWYB.Common/Extensions/MapperExtension.cs b/BBWYB.Common/Extensions/MapperExtension.cs new file mode 100644 index 0000000..de97c59 --- /dev/null +++ b/BBWYB.Common/Extensions/MapperExtension.cs @@ -0,0 +1,59 @@ +using AutoMapper; +using Microsoft.Extensions.DependencyInjection; + +namespace BBWY.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/BBWYB.Common/Extensions/StartupExtension.cs b/BBWYB.Common/Extensions/StartupExtension.cs new file mode 100644 index 0000000..7dd0426 --- /dev/null +++ b/BBWYB.Common/Extensions/StartupExtension.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace BBWY.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/BBWYB.Common/Http/HttpDownloader.cs b/BBWYB.Common/Http/HttpDownloader.cs new file mode 100644 index 0000000..665bdaf --- /dev/null +++ b/BBWYB.Common/Http/HttpDownloader.cs @@ -0,0 +1,456 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading; + +namespace BBWY.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/BBWYB.Common/Http/RestAPIService.cs b/BBWYB.Common/Http/RestAPIService.cs new file mode 100644 index 0000000..21545b4 --- /dev/null +++ b/BBWYB.Common/Http/RestAPIService.cs @@ -0,0 +1,117 @@ +using BBWY.Common.Extensions; +using Swifter.Json; +using System.Net; +using System.Net.Http.Headers; +using System.Text; + +namespace BBWY.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 ? JsonFormatter.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/BBWYB.Common/Models/ApiResponse.cs b/BBWYB.Common/Models/ApiResponse.cs new file mode 100644 index 0000000..e475241 --- /dev/null +++ b/BBWYB.Common/Models/ApiResponse.cs @@ -0,0 +1,34 @@ +using System; + +namespace BBWY.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 + { + + } +} diff --git a/BBWYB.Common/Models/BusinessException.cs b/BBWYB.Common/Models/BusinessException.cs new file mode 100644 index 0000000..d26b041 --- /dev/null +++ b/BBWYB.Common/Models/BusinessException.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BBWY.Common.Models +{ + /// + /// 业务异常 + /// + public class BusinessException : Exception + { + public BusinessException(string message) : base(message) + { + + } + + /// + /// 错误代码 + /// + public int Code { get; set; } + } +} diff --git a/BBWYB.Common/Models/IDenpendency.cs b/BBWYB.Common/Models/IDenpendency.cs new file mode 100644 index 0000000..91b78ec --- /dev/null +++ b/BBWYB.Common/Models/IDenpendency.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BBWY.Common.Models +{ + public interface IDenpendency + { + } +} diff --git a/BBWYB.Common/TaskSchedulers/DelayTrigger.cs b/BBWYB.Common/TaskSchedulers/DelayTrigger.cs new file mode 100644 index 0000000..53e7f31 --- /dev/null +++ b/BBWYB.Common/TaskSchedulers/DelayTrigger.cs @@ -0,0 +1,70 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace BBWY.Common.Trigger +{ + /// + /// 延迟触发组件 + /// + public class DelayTrigger + { + public DelayTrigger(int delayTime = 1000) + { + if (delayTime < 1000) + delayTime = 1000; + this.delayTime = delayTime; + } + /// + /// 延迟执行时间(ms) + /// + private int delayTime; + + /// + /// 关键字 + /// + private string currentKey; + + /// + /// 是否可以执行 + /// + private volatile bool canExecute; + + /// + /// 是否正在延迟响应中 + /// + private volatile bool isDelaying; + + + public Action 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; + } + } + }); + } + } +} diff --git a/BBWYB.Common/TaskSchedulers/LimitedConcurrencyLevelTaskScheduler.cs b/BBWYB.Common/TaskSchedulers/LimitedConcurrencyLevelTaskScheduler.cs new file mode 100644 index 0000000..05a2dcc --- /dev/null +++ b/BBWYB.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 +{ + /// + /// Provides a task scheduler that ensures a maximum concurrency level while + /// running on top of the ThreadPool. + /// + public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler + { + /// Whether the current thread is processing work items. + [ThreadStatic] + private static bool _currentThreadIsProcessingItems; + /// The list of tasks to be executed. + private readonly LinkedList _tasks = new LinkedList(); // protected by lock(_tasks) + /// The maximum concurrency level allowed by this scheduler. + private readonly int _maxDegreeOfParallelism; + /// Whether the scheduler is currently processing work items. + private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks) + + /// + /// 最大并发数 + /// + private readonly int maxConcurrencyCountOfSystem = Environment.ProcessorCount * 2; + + /// + /// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the + /// specified degree of parallelism. + /// + /// The maximum degree of parallelism provided by this scheduler. + public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism) + { + if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism"); + if (maxDegreeOfParallelism > maxConcurrencyCountOfSystem) + maxDegreeOfParallelism = maxConcurrencyCountOfSystem; + _maxDegreeOfParallelism = maxDegreeOfParallelism; + } + + /// Queues a task to the scheduler. + /// The task to be queued. + 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(); + } + } + } + + /// + /// Informs the ThreadPool that there's work to be executed for this scheduler. + /// + 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); + } + + /// Attempts to execute the specified task on the current thread. + /// The task to be executed. + /// + /// Whether the task could be executed on the current thread. + 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); + } + + /// Attempts to remove a previously scheduled task from the scheduler. + /// The task to be removed. + /// Whether the task could be found and removed. + protected sealed override bool TryDequeue(Task task) + { + lock (_tasks) return _tasks.Remove(task); + } + + /// Gets the maximum concurrency level supported by this scheduler. + public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } } + + /// Gets an enumerable of the tasks currently scheduled on this scheduler. + /// An enumerable of the tasks currently scheduled. + protected sealed override IEnumerable 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); + } + } + } +} \ No newline at end of file diff --git a/BBWYB.Server.API/BBWYB.Server.API.csproj b/BBWYB.Server.API/BBWYB.Server.API.csproj new file mode 100644 index 0000000..1133df6 --- /dev/null +++ b/BBWYB.Server.API/BBWYB.Server.API.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/BBWYB.Server.API/Controllers/WeatherForecastController.cs b/BBWYB.Server.API/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..13bc48c --- /dev/null +++ b/BBWYB.Server.API/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace BBWYB.Server.API.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 _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable 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(); + } + } +} \ No newline at end of file diff --git a/BBWYB.Server.API/Program.cs b/BBWYB.Server.API/Program.cs new file mode 100644 index 0000000..48863a6 --- /dev/null +++ b/BBWYB.Server.API/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +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.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/BBWYB.Server.API/Properties/launchSettings.json b/BBWYB.Server.API/Properties/launchSettings.json new file mode 100644 index 0000000..38e19b3 --- /dev/null +++ b/BBWYB.Server.API/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56103", + "sslPort": 44384 + } + }, + "profiles": { + "BBWYB.Server.API": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7256;http://localhost:5256", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/BBWYB.Server.API/WeatherForecast.cs b/BBWYB.Server.API/WeatherForecast.cs new file mode 100644 index 0000000..7d91879 --- /dev/null +++ b/BBWYB.Server.API/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace BBWYB.Server.API +{ + 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; } + } +} \ No newline at end of file diff --git a/BBWYB.Server.API/appsettings.Development.json b/BBWYB.Server.API/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/BBWYB.Server.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/BBWYB.Server.API/appsettings.json b/BBWYB.Server.API/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/BBWYB.Server.API/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/BBWYB.Server.Business/BBWYB.Server.Business.csproj b/BBWYB.Server.Business/BBWYB.Server.Business.csproj new file mode 100644 index 0000000..8532fd1 --- /dev/null +++ b/BBWYB.Server.Business/BBWYB.Server.Business.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/BBWYB.Server.Model/BBWYB.Server.Model.csproj b/BBWYB.Server.Model/BBWYB.Server.Model.csproj new file mode 100644 index 0000000..132c02c --- /dev/null +++ b/BBWYB.Server.Model/BBWYB.Server.Model.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/bbwyb.sln b/bbwyb.sln new file mode 100644 index 0000000..3be7a6d --- /dev/null +++ b/bbwyb.sln @@ -0,0 +1,62 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BBWYB.Server.API", "BBWYB.Server.API\BBWYB.Server.API.csproj", "{B6039632-07B7-4C74-9F74-F16B6782EF56}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{99D234A0-6830-4C0C-91E8-C626DA939D59}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{1E7D36DB-A817-4208-8FC6-36A66FAB17E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BBWYB.Client", "BBWYB.Client\BBWYB.Client.csproj", "{D52D0167-EF94-4FC8-91BF-FCE5B3ED9C6A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BBWYB.Server.Business", "BBWYB.Server.Business\BBWYB.Server.Business.csproj", "{52DF7178-3C36-4CA6-A2E9-D9E2BB41C0B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BBWYB.Server.Model", "BBWYB.Server.Model\BBWYB.Server.Model.csproj", "{5707BF58-3A98-4283-A6D0-3B78EF7ED2F1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{1191C1AE-7275-4643-AF24-BEC852717299}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BBWYB.Common", "BBWYB.Common\BBWYB.Common.csproj", "{DD328472-01CE-4CA8-AF29-C098FC499483}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B6039632-07B7-4C74-9F74-F16B6782EF56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6039632-07B7-4C74-9F74-F16B6782EF56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6039632-07B7-4C74-9F74-F16B6782EF56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6039632-07B7-4C74-9F74-F16B6782EF56}.Release|Any CPU.Build.0 = Release|Any CPU + {D52D0167-EF94-4FC8-91BF-FCE5B3ED9C6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D52D0167-EF94-4FC8-91BF-FCE5B3ED9C6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D52D0167-EF94-4FC8-91BF-FCE5B3ED9C6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D52D0167-EF94-4FC8-91BF-FCE5B3ED9C6A}.Release|Any CPU.Build.0 = Release|Any CPU + {52DF7178-3C36-4CA6-A2E9-D9E2BB41C0B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52DF7178-3C36-4CA6-A2E9-D9E2BB41C0B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52DF7178-3C36-4CA6-A2E9-D9E2BB41C0B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52DF7178-3C36-4CA6-A2E9-D9E2BB41C0B8}.Release|Any CPU.Build.0 = Release|Any CPU + {5707BF58-3A98-4283-A6D0-3B78EF7ED2F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5707BF58-3A98-4283-A6D0-3B78EF7ED2F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5707BF58-3A98-4283-A6D0-3B78EF7ED2F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5707BF58-3A98-4283-A6D0-3B78EF7ED2F1}.Release|Any CPU.Build.0 = Release|Any CPU + {DD328472-01CE-4CA8-AF29-C098FC499483}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD328472-01CE-4CA8-AF29-C098FC499483}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD328472-01CE-4CA8-AF29-C098FC499483}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD328472-01CE-4CA8-AF29-C098FC499483}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B6039632-07B7-4C74-9F74-F16B6782EF56} = {99D234A0-6830-4C0C-91E8-C626DA939D59} + {D52D0167-EF94-4FC8-91BF-FCE5B3ED9C6A} = {1E7D36DB-A817-4208-8FC6-36A66FAB17E5} + {52DF7178-3C36-4CA6-A2E9-D9E2BB41C0B8} = {99D234A0-6830-4C0C-91E8-C626DA939D59} + {5707BF58-3A98-4283-A6D0-3B78EF7ED2F1} = {99D234A0-6830-4C0C-91E8-C626DA939D59} + {DD328472-01CE-4CA8-AF29-C098FC499483} = {1191C1AE-7275-4643-AF24-BEC852717299} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0D069898-04B7-4D24-A6A4-D7C703B8BFFC} + EndGlobalSection +EndGlobal