diff --git a/AutoDispathingWork/ApplicationDbContext.cs b/AutoDispathingWork/ApplicationDbContext.cs index ebfd47f..5042db1 100644 --- a/AutoDispathingWork/ApplicationDbContext.cs +++ b/AutoDispathingWork/ApplicationDbContext.cs @@ -12,10 +12,7 @@ public class ApplicationDbContext : DbContext } private static readonly ILoggerFactory _loggerFactory = new LoggerFactory(); - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseLoggerFactory(_loggerFactory); - } + protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/AutoDispathingWork/AutoDispathingWork.csproj b/AutoDispathingWork/AutoDispathingWork.csproj index fab1160..340a4be 100644 --- a/AutoDispathingWork/AutoDispathingWork.csproj +++ b/AutoDispathingWork/AutoDispathingWork.csproj @@ -22,6 +22,7 @@ + diff --git a/AutoDispathingWork/CloseWorker.cs b/AutoDispathingWork/CloseWorker.cs index d264d65..21110bf 100644 --- a/AutoDispathingWork/CloseWorker.cs +++ b/AutoDispathingWork/CloseWorker.cs @@ -19,9 +19,9 @@ public class CloseWorker : BackgroundService { while (!stoppingToken.IsCancellationRequested) { - using var scope = StaticServiceProvider.Current.CreateScope(); + var scope = StaticServiceProvider.Current.CreateScope(); var settingServices = scope.ServiceProvider.GetRequiredService(); - var options = settingServices.GetClientOptions(); + var options = await settingServices.GetClientOptions(); if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); @@ -39,6 +39,7 @@ public class CloseWorker : BackgroundService finally { await Task.Delay(options.Delay, stoppingToken); + scope.Dispose(); } } } diff --git a/AutoDispathingWork/Controllers/UserController.cs b/AutoDispathingWork/Controllers/UserController.cs index 2abd3d0..47a03f2 100644 --- a/AutoDispathingWork/Controllers/UserController.cs +++ b/AutoDispathingWork/Controllers/UserController.cs @@ -217,20 +217,20 @@ public class UserController : ControllerBase } [HttpGet("/api/Setting")] - public SpiderResponse GetClientOptions([FromServices] SettingServices settingServices) + public async Task> GetClientOptions([FromServices] SettingServices settingServices) { return new SpiderResponse() { IsSuccess = true, Code = SpiderResponseCode.Success, Message = "获取配置成功", - Result = settingServices.GetClientOptions() + Result = await settingServices.GetClientOptions() }; } [HttpPost("/api/Setting")] - public SpiderResponse SettingClientOptions([FromBody] ClientOptionsReq options, + public async Task> SettingClientOptions([FromBody] ClientOptionsReq options, [FromServices] SettingServices settingServices) { - var setting = settingServices.GetClientOptions(); + var setting = await settingServices.GetClientOptions(); setting.Delay = options.Delay; setting.DispatchingRunning = options.DispatchingRunning; setting.CloseFileRunning = options.CloseFileRunning; diff --git a/AutoDispathingWork/Domains/LogInfo.cs b/AutoDispathingWork/Domains/LogInfo.cs index b512fe5..3fb62d7 100644 --- a/AutoDispathingWork/Domains/LogInfo.cs +++ b/AutoDispathingWork/Domains/LogInfo.cs @@ -13,7 +13,7 @@ public class LogInfo public LogInfo(string level, string message, [CallerMemberName] string from = "") { Id = Guid.NewGuid(); - CreateTime = DateTime.Now; + CreateTime = DateTime.UtcNow; Message = message; Level = level; From = from; diff --git a/AutoDispathingWork/Migrations/20240812130932_dbv3.Designer.cs b/AutoDispathingWork/Migrations/20240812130932_dbv3.Designer.cs new file mode 100644 index 0000000..ff83dd2 --- /dev/null +++ b/AutoDispathingWork/Migrations/20240812130932_dbv3.Designer.cs @@ -0,0 +1,185 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using WorkerService1; +using WorkerService1.Domains; + +#nullable disable + +namespace AutoDispathingWork.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240812130932_dbv3")] + partial class dbv3 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("WorkerService1.Domains.LogInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("From") + .IsRequired() + .HasColumnType("text"); + + b.Property("Level") + .IsRequired() + .HasColumnType("text"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CreateTime"); + + b.ToTable("LogInfos"); + }); + + modelBuilder.Entity("WorkerService1.Domains.Polygon", b => + { + b.Property("PolygonId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property>>("Points") + .HasColumnType("jsonb"); + + b.Property>("RangeCameras") + .HasColumnType("jsonb"); + + b.Property("UserId") + .HasColumnType("text"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("PolygonId"); + + b.ToTable("Polygons"); + }); + + modelBuilder.Entity("WorkerService1.Domains.SmsSendRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("ErrorMessage") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsSuccess") + .HasColumnType("boolean"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RefrenceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SendTime"); + + b.ToTable("SmsSendRecords"); + }); + + modelBuilder.Entity("WorkerService1.Dto.Configuration.ClientOptions", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiGateway") + .IsRequired() + .HasColumnType("text"); + + b.Property("CloseFileApi") + .IsRequired() + .HasColumnType("text"); + + b.Property("CloseFileRunning") + .HasColumnType("boolean"); + + b.Property("Delay") + .HasColumnType("integer"); + + b.Property("DiposeOrder") + .IsRequired() + .HasColumnType("text"); + + b.Property("DispatchingRunning") + .HasColumnType("boolean"); + + b.Property("GetCamerasApi") + .IsRequired() + .HasColumnType("text"); + + b.Property("GetTaskApi") + .IsRequired() + .HasColumnType("text"); + + b.Property("GetUserApi") + .IsRequired() + .HasColumnType("text"); + + b.Property("LoginApi") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "password"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "username"); + + b.HasKey("Id"); + + b.ToTable("ClientOptions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/AutoDispathingWork/Migrations/20240812130932_dbv3.cs b/AutoDispathingWork/Migrations/20240812130932_dbv3.cs new file mode 100644 index 0000000..a507f07 --- /dev/null +++ b/AutoDispathingWork/Migrations/20240812130932_dbv3.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AutoDispathingWork.Migrations +{ + /// + public partial class dbv3 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PhoneNumber", + table: "Polygons", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PhoneNumber", + table: "Polygons"); + } + } +} diff --git a/AutoDispathingWork/Migrations/ApplicationDbContextModelSnapshot.cs b/AutoDispathingWork/Migrations/ApplicationDbContextModelSnapshot.cs index e55bf80..fd6fbb6 100644 --- a/AutoDispathingWork/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/AutoDispathingWork/Migrations/ApplicationDbContextModelSnapshot.cs @@ -62,6 +62,9 @@ namespace AutoDispathingWork.Migrations .IsRequired() .HasColumnType("text"); + b.Property("PhoneNumber") + .HasColumnType("text"); + b.Property>>("Points") .HasColumnType("jsonb"); diff --git a/AutoDispathingWork/Program.cs b/AutoDispathingWork/Program.cs index 9702286..f5ee0a3 100644 --- a/AutoDispathingWork/Program.cs +++ b/AutoDispathingWork/Program.cs @@ -30,7 +30,7 @@ builder.Services.AddCors(x => }); }); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); -builder.Services.AddDbContext(x => +builder.Services.AddDbContextPool(x => { var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString); // 启用动态 JSON 序列化 @@ -40,7 +40,6 @@ builder.Services.AddDbContext(x => .UseLoggerFactory(new LoggerFactory()) .LogTo(Console.WriteLine, LogLevel.Information); x.ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning)); - } ); builder.Services.AddSwaggerGen(); @@ -53,7 +52,8 @@ builder.Services.AddMemoryCache(); builder.Services.AddLogging(); // builder.Services.AddMvcCore(); builder.Services.AddSingleton(); -builder.Services.AddScoped(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); // #if !DEBUG builder.Services.AddHostedService(); builder.Services.AddHostedService(); @@ -67,6 +67,9 @@ app.UseCors("AllowAllOrigin"); #if DEBUG app.UseSwagger(); app.UseSwaggerUI(); +var wechatRobot = app.Services.GetService(); +wechatRobot.SetKey("e68b3791-e040-4c9b-9ac0-6f424e662185"); + #endif app.UseDefaultFiles(); diff --git a/AutoDispathingWork/Services/SettingServices.cs b/AutoDispathingWork/Services/SettingServices.cs index 6eb67b4..00b09f5 100644 --- a/AutoDispathingWork/Services/SettingServices.cs +++ b/AutoDispathingWork/Services/SettingServices.cs @@ -1,5 +1,6 @@ using AutoDispathingWork.Utils; using LiteDB; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using WorkerService1.Dto.Configuration; @@ -9,7 +10,6 @@ public class SettingServices { private readonly IOptionsMonitor _optionsMonitor; - public SettingServices(IOptionsMonitor optionsMonitor) { @@ -21,23 +21,27 @@ public class SettingServices SettingClientOptions(_optionsMonitor.CurrentValue); } - public ClientOptions GetClientOptions() + public async Task GetClientOptions() { - using var scope = StaticServiceProvider.Current.CreateScope(); + var scope = StaticServiceProvider.Current.CreateScope(); var _db = scope.ServiceProvider.GetService(); var clientOptions = _db.ClientOptions; - var dbOptions = clientOptions.FirstOrDefault(); + var dbOptions = await clientOptions.FirstOrDefaultAsync(); if (dbOptions != null) { + await _db.DisposeAsync(); + scope.Dispose(); return dbOptions; } + await _db.DisposeAsync(); + scope.Dispose(); return _optionsMonitor.CurrentValue; } public ClientOptions SettingClientOptions(ClientOptions options) { - using var scope = StaticServiceProvider.Current.CreateScope(); + var scope = StaticServiceProvider.Current.CreateScope(); var _db = scope.ServiceProvider.GetService(); var clientOptions = _db.ClientOptions; var dbOptions = clientOptions.FirstOrDefault(); @@ -49,6 +53,9 @@ public class SettingServices clientOptions.Add(options); _db.SaveChanges(); + + _db.Dispose(); + scope.Dispose(); return options; } } \ No newline at end of file diff --git a/AutoDispathingWork/Services/SmsService.cs b/AutoDispathingWork/Services/SmsService.cs index 50c387b..4a8c3a2 100644 --- a/AutoDispathingWork/Services/SmsService.cs +++ b/AutoDispathingWork/Services/SmsService.cs @@ -1,12 +1,117 @@ -namespace WorkerService1.Services; +using TencentCloud.Common; +using TencentCloud.Common.Profile; +using TencentCloud.Cvm.V20170312; +using TencentCloud.Cvm.V20170312.Models; +using TencentCloud.Sms.V20210111; +using TencentCloud.Sms.V20210111.Models; + +namespace WorkerService1.Services; public class SmsService { - public Task SendSmsAsync(string phoneNumber, string message,string refrenceId) + public Task SendSmsAsync(string phoneNumber, string message, string refrenceId) { - //发送短信逻辑 - - Console.WriteLine($"Sending SMS to {phoneNumber} with message {message} and refrenceId {refrenceId}"); + try + { + // 必要步骤: + // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 SecretId,SecretKey。 + // 为了保护密钥安全,建议将密钥设置在环境变量中或者配置文件中。 + // 硬编码密钥到代码中有可能随代码泄露而暴露,有安全隐患,并不推荐。 + // 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。 + // SecretId、SecretKey 查询:https://console.cloud.tencent.com/cam/capi + Credential cred = new Credential + { + SecretId = Environment.GetEnvironmentVariable("TENCENTCLOUD_SECRET_ID"), + SecretKey = Environment.GetEnvironmentVariable("TENCENTCLOUD_SECRET_KEY") + }; + + + /* 非必要步骤: + * 实例化一个客户端配置对象,可以指定超时时间等配置 */ + ClientProfile clientProfile = new ClientProfile + { + /* SDK默认用TC3-HMAC-SHA256进行签名 + * 非必要请不要修改这个字段 */ + SignMethod = ClientProfile.SIGN_TC3SHA256 + }; + /* 非必要步骤 + * 实例化一个客户端配置对象,可以指定超时时间等配置 */ + var httpProfile = new HttpProfile + { + /* SDK默认使用POST方法。 + * 如果您一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */ + ReqMethod = "GET", + Timeout = 10, // 请求连接超时时间,单位为秒(默认60秒) + /* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */ + Endpoint = "sms.tencentcloudapi.com" + }; + // 代理服务器,当您的环境下有代理服务器时设定(无需要直接忽略) + // httpProfile.WebProxy = Environment.GetEnvironmentVariable("HTTPS_PROXY"); + + + clientProfile.HttpProfile = httpProfile; + /* 实例化要请求产品(以sms为例)的client对象 + * 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */ + var client = new SmsClient(cred, "ap-guangzhou", clientProfile); + + + /* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数 + * 您可以直接查询SDK源码确定SendSmsRequest有哪些属性可以设置 + * 属性可能是基本类型,也可能引用了另一个数据结构 + * 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */ + var req = new SendSmsRequest + { + /* 基本类型的设置: + * SDK采用的是指针风格指定参数,即使对于基本类型您也需要用指针来对参数赋值。 + * SDK提供对基本类型的指针引用封装函数 + * 帮助链接: + * 短信控制台: https://console.cloud.tencent.com/smsv2 + * 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */ + /* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */ + // 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看 + SmsSdkAppId = "1400787878", + /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */ + // 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看 + SignName = "腾讯云", + /* 模板 ID: 必须填写已审核通过的模板 ID */ + // 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看 + TemplateId = "449739", + /* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空 */ + TemplateParamSet = new String[] { "1234" }, + /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] + * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/ + PhoneNumberSet = new String[] { "+86" + phoneNumber }, + /* 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回 */ + SessionContext = "", + /* 短信码号扩展号(无需要可忽略): 默认未开通,如需开通请联系 [腾讯云短信小助手] */ + ExtendCode = "", + /* 国内短信无需填写该项;国际/港澳台短信已申请独立 SenderId 需要填写该字段,默认使用公共 SenderId,无需填写该字段。注:月度使用量达到指定量级可申请独立 SenderId 使用,详情请联系 [腾讯云短信小助手](https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81)。 */ + SenderId = "" + }; + + + SendSmsResponse resp = client.SendSmsSync(req); + + + // 输出json格式的字符串回包 + Console.WriteLine(AbstractModel.ToJsonString(resp)); + + + /* 当出现以下错误码时,快速解决方案参考 + * [FailedOperation.SignatureIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.signatureincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [FailedOperation.TemplateIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.templateincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [UnauthorizedOperation.SmsSdkAppIdVerifyFail](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunauthorizedoperation.smssdkappidverifyfail-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [UnsupportedOperation.ContainDomesticAndInternationalPhoneNumber](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunsupportedoperation.containdomesticandinternationalphonenumber-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * 更多错误,可咨询[腾讯云助手](https://tccc.qcloud.com/web/im/index.html#/chat?webAppId=8fa15978f85cb41f7e2ea36920cb3ae1&title=Sms) + */ + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + + Console.Read(); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/AutoDispathingWork/Services/SpiderServices.cs b/AutoDispathingWork/Services/SpiderServices.cs index 8a66359..b66e8d2 100644 --- a/AutoDispathingWork/Services/SpiderServices.cs +++ b/AutoDispathingWork/Services/SpiderServices.cs @@ -44,7 +44,7 @@ public class SpiderServices { using var scope = _serviceProvider.CreateScope(); using var client = _httpClientFactory.CreateClient(); - var getClientOptions = scope.ServiceProvider.GetService()? + var getClientOptions = await scope.ServiceProvider.GetService()? .GetClientOptions(); var loginApi = getClientOptions.LoginUrl; var json = $@" @@ -137,7 +137,7 @@ public class SpiderServices // {"state":[1],"pageNo":1,"pageSize":50,"sortType":[20,10],"createStartTime":1697290618034,"createEndTime":1699882618034} // {\"state\":[1],\"pageNo\":1,\"pageSize\":50,\"sortType\":[20,10],\"createStartTime\":1697204639551,\"createEndTime\":1697204639551} using var scope = _serviceProvider.CreateScope(); - var getClientOptions = scope.ServiceProvider.GetService()? + var getClientOptions = await scope.ServiceProvider.GetService()? .GetClientOptions(); var taskPath = getClientOptions.GetTaskUrl; var response = await client.PostAsync(taskPath, @@ -183,7 +183,7 @@ public class SpiderServices client.DefaultRequestHeaders.Add("module-source", "megcity-web"); var request = query ?? new UserQuery(); using var scope = _serviceProvider.CreateScope(); - var getClientOptions = scope.ServiceProvider.GetService()? + var getClientOptions = await scope.ServiceProvider.GetService()? .GetClientOptions(); var userPath = getClientOptions.GetUserUrl; var response = await client.PostAsync(userPath, @@ -228,7 +228,7 @@ public class SpiderServices client.DefaultRequestHeaders.Add("module-alias", "pending-forward"); client.DefaultRequestHeaders.Add("module-source", "megcity-web"); - var (handlerId,phone, userRealName) = await GetUserIdByCamera(cameraName); + var (handlerId, phone, userRealName) = await GetUserIdByCamera(cameraName); if (string.IsNullOrWhiteSpace(handlerId)) { return new SpiderResponse() @@ -248,7 +248,7 @@ public class SpiderServices TypeCode = typeCode }; using var scope = _serviceProvider.CreateScope(); - var getClientOptions = scope.ServiceProvider.GetService()? + var getClientOptions = await scope.ServiceProvider.GetService()? .GetClientOptions(); var dispatchPath = string.Format(getClientOptions.DiposeOrderUrl, caseNumber); @@ -263,8 +263,8 @@ public class SpiderServices if (spiderRes?.code == 0) { var msg = $"成功分发任务,任务编号:{caseNumber},任务地址:{cameraName},任务类型:{typeCode},处理人:{userRealName}"; - var smsService = scope.ServiceProvider.GetService(); - await smsService.SendSmsAsync(phoneNumber:phone, $"{userRealName}您好,您有一条新案件,请及时处置,谢谢。",caseNumber); + var smsService = scope.ServiceProvider.GetRequiredService(); + await smsService.SendText($"{userRealName},您好,您有一条新案件,请及时处置,谢谢。", false); _logger.LogInformation(msg); return new SpiderResponse() @@ -303,7 +303,7 @@ public class SpiderServices form.Add(new StringContent(caseNumber), "caseNumber"); form.Add(new StringContent(suggestion), "suggestion"); using var scope = _serviceProvider.CreateScope(); - var getClientOptions = scope.ServiceProvider.GetService()? + var getClientOptions = await scope.ServiceProvider.GetService()? .GetClientOptions(); var closeFile = getClientOptions.CloseFileUrl; @@ -352,7 +352,7 @@ public class SpiderServices {{""managementIds"":[""6e9232ef-7b84-11e8-86b1-6c92bf4e6960""],""name"":""{name}"" ,""action"":""all"",""pageNo"":1,""pageSize"":200}} "; using var scope = _serviceProvider.CreateScope(); - var getClientOptions = scope.ServiceProvider.GetService()? + var getClientOptions = await scope.ServiceProvider.GetService()? .GetClientOptions(); var getCamersPath = getClientOptions.GetCamerasUrl; var response = await client.PostAsync(getCamersPath, @@ -392,12 +392,12 @@ public class SpiderServices }; } - public async Task<(string userId,string phone, string userRealName )> GetUserIdByCamera(string cameraAddress) + public async Task<(string userId, string phone, string userRealName )> GetUserIdByCamera(string cameraAddress) { using var scope = _serviceProvider.CreateScope(); var camerasResp = await GetCameras(cameraAddress); var camera = camerasResp.Result?.Records.FirstOrDefault(); - if (camera == null) return (string.Empty,string.Empty, string.Empty); + if (camera == null) return (string.Empty, string.Empty, string.Empty); var location = new Points(camera.lon, camera.lat); var db = scope.ServiceProvider.GetService() .Polygons; @@ -411,6 +411,6 @@ public class SpiderServices } } - return (string.Empty, string.Empty,string.Empty); + return (string.Empty, string.Empty, string.Empty); } } \ No newline at end of file diff --git a/AutoDispathingWork/WechatRobot.cs b/AutoDispathingWork/WechatRobot.cs new file mode 100644 index 0000000..b818ff4 --- /dev/null +++ b/AutoDispathingWork/WechatRobot.cs @@ -0,0 +1,118 @@ +using System.Text; +using Microsoft.Net.Http.Headers; + +namespace WorkerService1; + +public class WechatRobot + { + private const string ApiUrl = "https://qyapi.weixin.qq.com"; + private const int TextUtf8MaxLength = 2000; + private const int MarkdownUtf8MaxLength = 4000; + private string _key; + + private static readonly MediaTypeHeaderValue ApplicationJson = + new MediaTypeHeaderValue("application/json") + { + Charset = "utf-8" + }; + + private readonly HttpClient _client; + private readonly ILogger _logger; + + public WechatRobot(ILogger logger, HttpClient client) + { + client.BaseAddress = new Uri(ApiUrl); + this._logger = logger; + _client = client; + } + + public WechatRobot SetKey(string key) + { + this._key = key; + return this; + } + + public async Task SendText(string content, bool isAtAll) + { + await this.Post($"/cgi-bin/webhook/send?key={_key}", BuildTextBody(content, isAtAll)); + } + + public async Task SendMarkdown(string content) + { + await this.Post($"/cgi-bin/webhook/send?key={_key}", BuildMarkdownBody(content)); + } + + private string BuildTextBody(string content, bool isAtAll) + { + var result = new StringBuilder("{\"msgtype\": \"text\","); + result.Append("\"text\": {"); + result.Append($"\"content\": \"{Substring(content, TextUtf8MaxLength)}\","); + if (isAtAll) + { + result.Append($"\"mentioned_list\":[\"@all\"]"); + } + + result.Append("}}"); + + return result.ToString(); + } + + private string BuildMarkdownBody(string content) + { + var result = new StringBuilder("{\"msgtype\": \"markdown\","); + result.Append("\"markdown\": {"); + result.Append($"\"content\": \"{Substring(FilterSpecialChar(content), MarkdownUtf8MaxLength)}\""); + result.Append("}}"); + + return result.ToString(); + } + + /// + /// 截取字符串 + /// + /// 字符串内容 + /// 字符串最大长度 + /// 如果长度超出则按照 maxLength 截取返回,若没超出则原样返回 + private string Substring(string content, int maxLength) + { + var bytes = Encoding.UTF8.GetBytes(content); + if (bytes.Length > maxLength) + { + var temporaryBytes = bytes.Take(maxLength).ToArray(); + return $"{Encoding.UTF8.GetString(temporaryBytes)}"; + } + + return content; + } + + /// + /// 过滤 markdown 特殊字符 + /// + /// + /// + private string FilterSpecialChar(string content) => content?.Replace("\"", "''").Replace("`", "'"); + + private async Task Post(string url, string parameters) + { + try + { + HttpContent content = null; + if (!String.IsNullOrEmpty(parameters)) + { + content = new StringContent(parameters, Encoding.UTF8); + if (content.Headers.ContentType != null) + { + content.Headers.ContentType.MediaType = "application/json"; + content.Headers.ContentType.CharSet = "utf-8"; + } + } + + var resp = await _client.PostAsync(url, content); + await resp.Content.ReadAsStringAsync(); + } + catch (Exception ex) + { + this._logger.LogError(ex, $"WechatRobot Post fail,Url:{url},Parameters:{parameters}"); + } + } + } \ No newline at end of file diff --git a/AutoDispathingWork/Worker.cs b/AutoDispathingWork/Worker.cs index 93ba8e8..ec675c1 100644 --- a/AutoDispathingWork/Worker.cs +++ b/AutoDispathingWork/Worker.cs @@ -17,9 +17,9 @@ public class Worker : BackgroundService { while (!stoppingToken.IsCancellationRequested) { - using var scope = StaticServiceProvider.Current.CreateScope(); + var scope = StaticServiceProvider.Current.CreateScope(); var settingServices = scope.ServiceProvider.GetRequiredService(); - var options = settingServices.GetClientOptions(); + var options = await settingServices.GetClientOptions(); if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now.LocalDateTime); @@ -37,6 +37,7 @@ public class Worker : BackgroundService finally { await Task.Delay(options.Delay, stoppingToken); + scope.Dispose(); } } }