77 changed files with 3464 additions and 2 deletions
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Sms" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\common\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.PushPlus\LINGYUN.Abp.PushPlus.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,36 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications; |
||||
|
public static class NotificationDefinitionExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 消息群发群组编码
|
||||
|
/// see: https://www.pushplus.plus/doc/guide/api.html#%E4%B8%80%E3%80%81%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3
|
||||
|
/// </summary>
|
||||
|
/// <param name="notification">群组编码</param>
|
||||
|
/// <param name="topic"></param>
|
||||
|
/// <returns>
|
||||
|
/// <see cref="NotificationDefinition"/>
|
||||
|
/// </returns>
|
||||
|
public static NotificationDefinition WithTopic( |
||||
|
this NotificationDefinition notification, |
||||
|
string topic) |
||||
|
{ |
||||
|
return notification.WithProperty("topic", topic); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 获取消息群发群组编码
|
||||
|
/// </summary>
|
||||
|
/// <param name="notification"></param>
|
||||
|
/// <returns>
|
||||
|
/// 通知定义的群组编码,未定义返回null
|
||||
|
/// </returns>
|
||||
|
public static string GetTopicOrNull( |
||||
|
this NotificationDefinition notification) |
||||
|
{ |
||||
|
if (notification.Properties.TryGetValue("topic", out var topicDefine) == true) |
||||
|
{ |
||||
|
return topicDefine.ToString(); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using LINGYUN.Abp.PushPlus; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.PushPlus; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpNotificationModule), |
||||
|
typeof(AbpPushPlusModule))] |
||||
|
public class AbpNotificationsPushPlusModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpNotificationOptions>(options => |
||||
|
{ |
||||
|
options.PublishProviders.Add<PushPlusNotificationPublishProvider>(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Message; |
||||
|
using LINGYUN.Abp.RealTime.Localization; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.PushPlus; |
||||
|
|
||||
|
public class PushPlusNotificationPublishProvider : NotificationPublishProvider |
||||
|
{ |
||||
|
public const string ProviderName = "PushPlus"; |
||||
|
|
||||
|
public override string Name => ProviderName; |
||||
|
|
||||
|
protected IPushPlusMessageSender PushPlusMessageSender { get; } |
||||
|
|
||||
|
protected IStringLocalizerFactory LocalizerFactory { get; } |
||||
|
|
||||
|
protected AbpLocalizationOptions LocalizationOptions { get; } |
||||
|
|
||||
|
protected INotificationDefinitionManager NotificationDefinitionManager { get; } |
||||
|
|
||||
|
public PushPlusNotificationPublishProvider( |
||||
|
IPushPlusMessageSender pushPlusMessageSender, |
||||
|
IStringLocalizerFactory localizerFactory, |
||||
|
IOptions<AbpLocalizationOptions> localizationOptions, |
||||
|
INotificationDefinitionManager notificationDefinitionManager) |
||||
|
{ |
||||
|
PushPlusMessageSender = pushPlusMessageSender; |
||||
|
LocalizerFactory = localizerFactory; |
||||
|
LocalizationOptions = localizationOptions.Value; |
||||
|
NotificationDefinitionManager = notificationDefinitionManager; |
||||
|
} |
||||
|
|
||||
|
protected async override Task PublishAsync( |
||||
|
NotificationInfo notification, |
||||
|
IEnumerable<UserIdentifier> identifiers, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var topic = ""; |
||||
|
|
||||
|
var notificationDefine = NotificationDefinitionManager.GetOrNull(notification.Name); |
||||
|
var topicDefine = notificationDefine?.GetTopicOrNull(); |
||||
|
if (!topicDefine.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
topic = topicDefine; |
||||
|
} |
||||
|
|
||||
|
if (!notification.Data.NeedLocalizer()) |
||||
|
{ |
||||
|
var title = notification.Data.TryGetData("title").ToString(); |
||||
|
var message = notification.Data.TryGetData("message").ToString(); |
||||
|
|
||||
|
await PushPlusMessageSender.SendAsync( |
||||
|
title, |
||||
|
message, |
||||
|
topic, |
||||
|
cancellationToken: cancellationToken); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var titleInfo = notification.Data.TryGetData("title").As<LocalizableStringInfo>(); |
||||
|
var titleResource = GetResource(titleInfo.ResourceName); |
||||
|
var title = LocalizerFactory.Create(titleResource.ResourceType)[titleInfo.Name, titleInfo.Values].Value; |
||||
|
|
||||
|
var messageInfo = notification.Data.TryGetData("message").As<LocalizableStringInfo>(); |
||||
|
var messageResource = GetResource(messageInfo.ResourceName); |
||||
|
var message = LocalizerFactory.Create(messageResource.ResourceType)[messageInfo.Name, messageInfo.Values].Value; |
||||
|
|
||||
|
await PushPlusMessageSender.SendAsync( |
||||
|
title, |
||||
|
message, |
||||
|
topic, |
||||
|
cancellationToken: cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private LocalizationResource GetResource(string resourceName) |
||||
|
{ |
||||
|
return LocalizationOptions.Resources.Values |
||||
|
.First(x => x.ResourceName.Equals(resourceName)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
# LINGYUN.Abp.Notifications.PushPlus |
||||
|
|
||||
|
通知模块的PushPlus实现 |
||||
|
|
||||
|
使应用可通过PushPlus发布实时通知 |
||||
|
|
||||
|
## 模块引用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpNotificationsPushPlusModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
|
``` |
||||
|
|
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\PushPlus\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\PushPlus\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Caching" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Json" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="$(MicrosoftPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\common\LINGYUN.Abp.Features.LimitValidation\LINGYUN.Abp.Features.LimitValidation.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,53 @@ |
|||||
|
using LINGYUN.Abp.Features.LimitValidation; |
||||
|
using LINGYUN.Abp.PushPlus.Channel.Webhook; |
||||
|
using LINGYUN.Abp.PushPlus.Message; |
||||
|
using LINGYUN.Abp.PushPlus.Setting; |
||||
|
using LINGYUN.Abp.PushPlus.Token; |
||||
|
using LINGYUN.Abp.PushPlus.Topic; |
||||
|
using LINGYUN.Abp.PushPlus.User; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Json.SystemTextJson; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpJsonModule), |
||||
|
typeof(AbpSettingsModule), |
||||
|
typeof(AbpCachingModule), |
||||
|
typeof(AbpFeaturesLimitValidationModule))] |
||||
|
public class AbpPushPlusModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddPushPlusClient(); |
||||
|
|
||||
|
Configure<AbpSystemTextJsonSerializerOptions>(options => |
||||
|
{ |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<int>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<string>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<object>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusWebhook>>(); |
||||
|
|
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusMessage>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<SendPushPlusMessageResult>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusPagedResponse<PushPlusMessage>>>(); |
||||
|
|
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusChannel>>(); |
||||
|
|
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusToken>>(); |
||||
|
|
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusTopicForMe>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusTopicProfile>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusTopicQrCode>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusPagedResponse<PushPlusTopicUser>>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusPagedResponse<PushPlusTopic>>>(); |
||||
|
|
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusUserLimitTime>>(); |
||||
|
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusUserProfile>>(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Channel; |
||||
|
/// <summary>
|
||||
|
/// 发送渠道(channel)枚举
|
||||
|
/// </summary>
|
||||
|
public enum PushPlusChannelType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 微信公众号
|
||||
|
/// </summary>
|
||||
|
WeChat = 0, |
||||
|
/// <summary>
|
||||
|
/// 第三方webhook
|
||||
|
/// </summary>
|
||||
|
Webhook = 1, |
||||
|
/// <summary>
|
||||
|
/// 企业微信应用
|
||||
|
/// </summary>
|
||||
|
WeWork = 2, |
||||
|
/// <summary>
|
||||
|
/// 邮箱
|
||||
|
/// </summary>
|
||||
|
Email = 3, |
||||
|
/// <summary>
|
||||
|
/// 短信
|
||||
|
/// </summary>
|
||||
|
Sms = 4 |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Channel; |
||||
|
public static class PushPlusChannelTypeExtensions |
||||
|
{ |
||||
|
public static string GetChannelName(this PushPlusChannelType channelType) |
||||
|
{ |
||||
|
return channelType switch |
||||
|
{ |
||||
|
PushPlusChannelType.WeChat => "wechat", |
||||
|
PushPlusChannelType.WeWork => "cp", |
||||
|
PushPlusChannelType.Webhook => "webhook", |
||||
|
PushPlusChannelType.Email => "mail", |
||||
|
PushPlusChannelType.Sms => "sms", |
||||
|
_ => "wechat", |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Channel.Webhook; |
||||
|
/// <summary>
|
||||
|
/// Webhook配置接口
|
||||
|
/// </summary>
|
||||
|
public interface IPushPlusWebhookProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 获取webhook列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="current">当前所在分页数</param>
|
||||
|
/// <param name="pageSize">每页大小,最大值为50</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusPagedResponse<PushPlusWebhook>> GetWebhookListAsync( |
||||
|
int current = 1, |
||||
|
int pageSize = 20, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// webhook详情
|
||||
|
/// </summary>
|
||||
|
/// <param name="webhookId">webhook编号</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusWebhook> GetWebhookAsync( |
||||
|
int webhookId, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 新增webhook
|
||||
|
/// </summary>
|
||||
|
/// <param name="webhookCode">webhook编码</param>
|
||||
|
/// <param name="webhookName">webhook名称</param>
|
||||
|
/// <param name="webhookType">webhook类型;1-企业微信,2-钉钉,3-飞书,4-server酱</param>
|
||||
|
/// <param name="webhookUrl">调用的url地址</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<int> CreateWebhookAsync( |
||||
|
string webhookCode, |
||||
|
string webhookName, |
||||
|
PushPlusWebhookType webhookType, |
||||
|
string webhookUrl, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 修改webhook配置
|
||||
|
/// </summary>
|
||||
|
/// <param name="id">webhook编号</param>
|
||||
|
/// <param name="webhookCode">webhook编码</param>
|
||||
|
/// <param name="webhookName">webhook名称</param>
|
||||
|
/// <param name="webhookType">webhook类型;1-企业微信,2-钉钉,3-飞书,4-server酱</param>
|
||||
|
/// <param name="webhookUrl">调用的url地址</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<string> UpdateWebhookAsync( |
||||
|
int id, |
||||
|
string webhookCode, |
||||
|
string webhookName, |
||||
|
PushPlusWebhookType webhookType, |
||||
|
string webhookUrl, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Channel.Webhook; |
||||
|
/// <summary>
|
||||
|
/// webhook
|
||||
|
/// </summary>
|
||||
|
public class PushPlusWebhook |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// webhook编号
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("id")] |
||||
|
public int Id { get; set; } |
||||
|
/// <summary>
|
||||
|
/// webhook编码
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("webhookCode")] |
||||
|
public string WebhookCode { get; set; } |
||||
|
/// <summary>
|
||||
|
/// webhook名称
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("webhookName")] |
||||
|
public string WebhookName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// webhook类型;
|
||||
|
/// 1-企业微信,
|
||||
|
/// 2-钉钉,
|
||||
|
/// 3-飞书,
|
||||
|
/// 4-server酱
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("webhookType")] |
||||
|
public PushPlusWebhookType WebhookType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 调用的url地址
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("webhookUrl")] |
||||
|
public string WebhookUrl { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 创建日期
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("createTime")] |
||||
|
public DateTime CreateTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Token; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Channel.Webhook; |
||||
|
|
||||
|
public class PushPlusWebhookProvider : IPushPlusWebhookProvider, ITransientDependency |
||||
|
{ |
||||
|
protected ILogger<PushPlusWebhookProvider> Logger { get; } |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected IHttpClientFactory HttpClientFactory { get; } |
||||
|
protected IPushPlusTokenProvider PushPlusTokenProvider { get; } |
||||
|
|
||||
|
public PushPlusWebhookProvider( |
||||
|
ILogger<PushPlusWebhookProvider> logger, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IHttpClientFactory httpClientFactory, |
||||
|
IPushPlusTokenProvider pushPlusTokenProvider) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
PushPlusTokenProvider = pushPlusTokenProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<int> CreateWebhookAsync( |
||||
|
string webhookCode, |
||||
|
string webhookName, |
||||
|
PushPlusWebhookType webhookType, |
||||
|
string webhookUrl, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(webhookCode, nameof(webhookCode)); |
||||
|
Check.NotNullOrWhiteSpace(webhookName, nameof(webhookName)); |
||||
|
Check.NotNullOrWhiteSpace(webhookUrl, nameof(webhookUrl)); |
||||
|
Check.NotNull(webhookType, nameof(webhookType)); |
||||
|
|
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetCreateWebhookContentAsync( |
||||
|
token.AccessKey, |
||||
|
webhookCode, |
||||
|
webhookName, |
||||
|
webhookType, |
||||
|
webhookUrl, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<int>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusWebhook> GetWebhookAsync( |
||||
|
int webhookId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetWebhookContentAsync( |
||||
|
token.AccessKey, |
||||
|
webhookId, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<PushPlusWebhook>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusPagedResponse<PushPlusWebhook>> GetWebhookListAsync( |
||||
|
int current = 1, |
||||
|
int pageSize = 20, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetWebhookListContentAsync( |
||||
|
token.AccessKey, |
||||
|
current, |
||||
|
pageSize, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusPagedResponse<PushPlusWebhook>>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<string> UpdateWebhookAsync( |
||||
|
int id, |
||||
|
string webhookCode, |
||||
|
string webhookName, |
||||
|
PushPlusWebhookType webhookType, |
||||
|
string webhookUrl, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(webhookCode, nameof(webhookCode)); |
||||
|
Check.NotNullOrWhiteSpace(webhookName, nameof(webhookName)); |
||||
|
Check.NotNullOrWhiteSpace(webhookUrl, nameof(webhookUrl)); |
||||
|
Check.NotNull(webhookType, nameof(webhookType)); |
||||
|
|
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetUpdateWebhookContentAsync( |
||||
|
token.AccessKey, |
||||
|
id, |
||||
|
webhookCode, |
||||
|
webhookName, |
||||
|
webhookType, |
||||
|
webhookUrl, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<string>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Channel.Webhook; |
||||
|
public enum PushPlusWebhookType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 企业微信
|
||||
|
/// </summary>
|
||||
|
WeWork = 1, |
||||
|
/// <summary>
|
||||
|
/// 钉钉
|
||||
|
/// </summary>
|
||||
|
DingTalk = 2, |
||||
|
/// <summary>
|
||||
|
/// 飞书
|
||||
|
/// </summary>
|
||||
|
FeiShu = 3, |
||||
|
/// <summary>
|
||||
|
/// Server酱
|
||||
|
/// </summary>
|
||||
|
SctFtqq = 4, |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Channel.Webhook; |
||||
|
|
||||
|
internal static class WebhookHttpClientExtensions |
||||
|
{ |
||||
|
private const string _getWebhookListTemplate = "{\"current\":$current,\"pageSize\":$pageSize}"; |
||||
|
public async static Task<string> GetWebhookListContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/open/webhook/list"); |
||||
|
|
||||
|
var requestBody = _getWebhookListTemplate |
||||
|
.Replace("$current", current.ToString()) |
||||
|
.Replace("$pageSize", pageSize.ToString()); |
||||
|
|
||||
|
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); |
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetWebhookContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int webhookId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/open/webhook/detail?webhookId={webhookId}"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
private const string _createWebhookTemplate = "{\"webhookCode\":\"$webhookCode\",\"webhookName\":\"$webhookName\",\"webhookType\":$webhookType,\"webhookUrl\":\"$webhookUrl\"}"; |
||||
|
public async static Task<string> GetCreateWebhookContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
string webhookCode, |
||||
|
string webhookName, |
||||
|
PushPlusWebhookType webhookType, |
||||
|
string webhookUrl, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/open/webhook/add"); |
||||
|
|
||||
|
var requestBody = _createWebhookTemplate |
||||
|
.Replace("$webhookCode", webhookCode) |
||||
|
.Replace("$webhookName", webhookName) |
||||
|
.Replace("$webhookType", ((int)webhookType).ToString()) |
||||
|
.Replace("$webhookUrl", webhookUrl); |
||||
|
|
||||
|
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); |
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
private const string _updateWebhookTemplate = "{\"id\":$id,\"webhookCode\":\"$webhookCode\",\"webhookName\":\"$webhookName\",\"webhookType\":$webhookType,\"webhookUrl\":\"$webhookUrl\"}"; |
||||
|
public async static Task<string> GetUpdateWebhookContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int id, |
||||
|
string webhookCode, |
||||
|
string webhookName, |
||||
|
PushPlusWebhookType webhookType, |
||||
|
string webhookUrl, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/open/topic/edit"); |
||||
|
|
||||
|
var requestBody = _updateWebhookTemplate |
||||
|
.Replace("$id", id.ToString()) |
||||
|
.Replace("$webhookCode", webhookCode) |
||||
|
.Replace("$webhookName", webhookName) |
||||
|
.Replace("$webhookType", ((int)webhookType).ToString()) |
||||
|
.Replace("$webhookUrl", webhookUrl); |
||||
|
|
||||
|
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); |
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,168 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Localization; |
||||
|
using Volo.Abp.Features; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Validation.StringValues; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Features; |
||||
|
public class PushPlusFeatureDefinitionProvider : FeatureDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(IFeatureDefinitionContext context) |
||||
|
{ |
||||
|
var group = context.AddGroup( |
||||
|
name: PushPlusFeatureNames.GroupName, |
||||
|
displayName: L("Features:PushPlus")); |
||||
|
|
||||
|
group.AddFeature( |
||||
|
name: PushPlusFeatureNames.Message.Enable, |
||||
|
defaultValue: "true", |
||||
|
displayName: L("Features:MessageEnable"), |
||||
|
description: L("Features:MessageEnableDesc"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
|
||||
|
CreateWeChatChannelFeature(group); |
||||
|
CreateWeWorkChannelFeature(group); |
||||
|
CreateWebhookChannelFeature(group); |
||||
|
CreateEmailChannelFeature(group); |
||||
|
CreateSmsChannelFeature(group); |
||||
|
} |
||||
|
|
||||
|
private static void CreateWeChatChannelFeature( |
||||
|
FeatureGroupDefinition group) |
||||
|
{ |
||||
|
var weChatChannel = group.AddFeature( |
||||
|
name: PushPlusFeatureNames.Channel.WeChat.GroupName, |
||||
|
displayName: L("Features:Channel.WeChat"), |
||||
|
description: L("Features:Channel.WeChat")); |
||||
|
weChatChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.WeChat.Enable, |
||||
|
defaultValue: "true", |
||||
|
displayName: L("Features:Channel.WeChat.Enable"), |
||||
|
description: L("Features:Channel.WeChat.EnableDesc"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
weChatChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.WeChat.SendLimit, |
||||
|
defaultValue: "200", |
||||
|
displayName: L("Features:Channel.WeChat.SendLimit"), |
||||
|
description: L("Features:Channel.WeChat.SendLimitDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000))); |
||||
|
weChatChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.WeChat.SendLimitInterval, |
||||
|
defaultValue: "1", |
||||
|
displayName: L("Features:Channel.WeChat.SendLimitInterval"), |
||||
|
description: L("Features:Channel.WeChat.SendLimitIntervalDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1))); |
||||
|
} |
||||
|
|
||||
|
private static void CreateWeWorkChannelFeature( |
||||
|
FeatureGroupDefinition group) |
||||
|
{ |
||||
|
var weWorkChannel = group.AddFeature( |
||||
|
name: PushPlusFeatureNames.Channel.WeWork.GroupName, |
||||
|
displayName: L("Features:Channel.WeWork"), |
||||
|
description: L("Features:Channel.WeWork")); |
||||
|
weWorkChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.WeWork.Enable, |
||||
|
defaultValue: "true", |
||||
|
displayName: L("Features:Channel.WeWork.Enable"), |
||||
|
description: L("Features:Channel.WeWork.EnableDesc"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
weWorkChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.WeWork.SendLimit, |
||||
|
defaultValue: "200", |
||||
|
displayName: L("Features:Channel.WeWork.SendLimit"), |
||||
|
description: L("Features:Channel.WeWork.SendLimitDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 10000))); |
||||
|
weWorkChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.WeWork.SendLimitInterval, |
||||
|
defaultValue: "1", |
||||
|
displayName: L("Features:Channel.WeWork.SendLimitInterval"), |
||||
|
description: L("Features:Channel.WeWork.SendLimitIntervalDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1))); |
||||
|
} |
||||
|
|
||||
|
private static void CreateWebhookChannelFeature( |
||||
|
FeatureGroupDefinition group) |
||||
|
{ |
||||
|
var webhookChannel = group.AddFeature( |
||||
|
name: PushPlusFeatureNames.Channel.Webhook.GroupName, |
||||
|
displayName: L("Features:Channel.Webhook"), |
||||
|
description: L("Features:Channel.Webhook")); |
||||
|
webhookChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.Webhook.Enable, |
||||
|
defaultValue: "true", |
||||
|
displayName: L("Features:Channel.Webhook.Enable"), |
||||
|
description: L("Features:Channel.Webhook.EnableDesc"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
webhookChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.Webhook.SendLimit, |
||||
|
defaultValue: "200", |
||||
|
displayName: L("Features:Channel.Webhook.SendLimit"), |
||||
|
description: L("Features:Channel.Webhook.SendLimitDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 10000))); |
||||
|
webhookChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.Webhook.SendLimitInterval, |
||||
|
defaultValue: "1", |
||||
|
displayName: L("Features:Channel.Webhook.SendLimitInterval"), |
||||
|
description: L("Features:Channel.Webhook.SendLimitIntervalDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1))); |
||||
|
} |
||||
|
|
||||
|
private static void CreateEmailChannelFeature( |
||||
|
FeatureGroupDefinition group) |
||||
|
{ |
||||
|
var emailChannel = group.AddFeature( |
||||
|
name: PushPlusFeatureNames.Channel.Email.GroupName, |
||||
|
displayName: L("Features:Channel.Email"), |
||||
|
description: L("Features:Channel.Email")); |
||||
|
emailChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.Email.Enable, |
||||
|
defaultValue: "true", |
||||
|
displayName: L("Features:Channel.Email.Enable"), |
||||
|
description: L("Features:Channel.Email.EnableDesc"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
emailChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.Email.SendLimit, |
||||
|
defaultValue: "200", |
||||
|
displayName: L("Features:Channel.Email.SendLimit"), |
||||
|
description: L("Features:Channel.Email.SendLimitDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000))); |
||||
|
emailChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.Email.SendLimitInterval, |
||||
|
defaultValue: "1", |
||||
|
displayName: L("Features:Channel.Email.SendLimitInterval"), |
||||
|
description: L("Features:Channel.Email.SendLimitIntervalDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1))); |
||||
|
} |
||||
|
|
||||
|
private static void CreateSmsChannelFeature( |
||||
|
FeatureGroupDefinition group) |
||||
|
{ |
||||
|
var smsChannel = group.AddFeature( |
||||
|
name: PushPlusFeatureNames.Channel.Sms.GroupName, |
||||
|
displayName: L("Features:Channel.Sms"), |
||||
|
description: L("Features:Channel.Sms")); |
||||
|
smsChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.Sms.Enable, |
||||
|
defaultValue: "false", |
||||
|
displayName: L("Features:Channel.Sms.Enable"), |
||||
|
description: L("Features:Channel.Sms.EnableDesc"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
smsChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.Sms.SendLimit, |
||||
|
defaultValue: "200", |
||||
|
displayName: L("Features:Channel.Sms.SendLimit"), |
||||
|
description: L("Features:Channel.Sms.SendLimitDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000))); |
||||
|
smsChannel.CreateChild( |
||||
|
name: PushPlusFeatureNames.Channel.Sms.SendLimitInterval, |
||||
|
defaultValue: "1", |
||||
|
displayName: L("Features:Channel.Sms.SendLimitInterval"), |
||||
|
description: L("Features:Channel.Sms.SendLimitIntervalDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1))); |
||||
|
} |
||||
|
|
||||
|
private static LocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<PushPlusResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Features; |
||||
|
|
||||
|
public static class PushPlusFeatureNames |
||||
|
{ |
||||
|
public const string GroupName = "PushPlus"; |
||||
|
|
||||
|
public static class Message |
||||
|
{ |
||||
|
public const string GroupName = PushPlusFeatureNames.GroupName + ".Message"; |
||||
|
|
||||
|
public const string Enable = GroupName + ".Enable"; |
||||
|
} |
||||
|
|
||||
|
public static class Channel |
||||
|
{ |
||||
|
public const string GroupName = PushPlusFeatureNames.GroupName + ".Channel"; |
||||
|
|
||||
|
public static class WeChat |
||||
|
{ |
||||
|
public const string GroupName = Channel.GroupName + ".WeChat"; |
||||
|
/// <summary>
|
||||
|
/// 启用微信通道
|
||||
|
/// </summary>
|
||||
|
public const string Enable = GroupName + ".Enable"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限
|
||||
|
/// </summary>
|
||||
|
public const string SendLimit = GroupName + ".SendLimit"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限时长
|
||||
|
/// </summary>
|
||||
|
public const string SendLimitInterval = GroupName + ".SendLimitInterval"; |
||||
|
} |
||||
|
|
||||
|
public static class WeWork |
||||
|
{ |
||||
|
public const string GroupName = Channel.GroupName + ".WeWork"; |
||||
|
/// <summary>
|
||||
|
/// 启用企业微信通道
|
||||
|
/// </summary>
|
||||
|
public const string Enable = GroupName + ".Enable"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限
|
||||
|
/// </summary>
|
||||
|
public const string SendLimit = GroupName + ".SendLimit"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限时长
|
||||
|
/// </summary>
|
||||
|
public const string SendLimitInterval = GroupName + ".SendLimitInterval"; |
||||
|
} |
||||
|
|
||||
|
public static class Webhook |
||||
|
{ |
||||
|
public const string GroupName = Channel.GroupName + ".Webhook"; |
||||
|
/// <summary>
|
||||
|
/// 启用Webhook通道
|
||||
|
/// </summary>
|
||||
|
public const string Enable = GroupName + ".Enable"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限
|
||||
|
/// </summary>
|
||||
|
public const string SendLimit = GroupName + ".SendLimit"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限时长
|
||||
|
/// </summary>
|
||||
|
public const string SendLimitInterval = GroupName + ".SendLimitInterval"; |
||||
|
} |
||||
|
|
||||
|
public static class Email |
||||
|
{ |
||||
|
public const string GroupName = Channel.GroupName + ".Email"; |
||||
|
/// <summary>
|
||||
|
/// 启用Email通道
|
||||
|
/// </summary>
|
||||
|
public const string Enable = GroupName + ".Enable"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限
|
||||
|
/// </summary>
|
||||
|
public const string SendLimit = GroupName + ".SendLimit"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限时长
|
||||
|
/// </summary>
|
||||
|
public const string SendLimitInterval = GroupName + ".SendLimitInterval"; |
||||
|
} |
||||
|
|
||||
|
public static class Sms |
||||
|
{ |
||||
|
public const string GroupName = Channel.GroupName + ".Sms"; |
||||
|
/// <summary>
|
||||
|
/// 启用Sms通道
|
||||
|
/// </summary>
|
||||
|
public const string Enable = GroupName + ".Enable"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限
|
||||
|
/// </summary>
|
||||
|
public const string SendLimit = GroupName + ".SendLimit"; |
||||
|
/// <summary>
|
||||
|
/// 发送次数上限时长
|
||||
|
/// </summary>
|
||||
|
public const string SendLimitInterval = GroupName + ".SendLimitInterval"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Localization; |
||||
|
|
||||
|
[LocalizationResourceName("PushPlus")] |
||||
|
public class PushPlusResource |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Settings:Security.Token": "User Token", |
||||
|
"Settings:Security.TokenDesc": "It can be obtained after logging in to the public account of PushPlus.", |
||||
|
"Settings:Security.SecretKey": "Secret Key", |
||||
|
"Settings:Security.SecretKeyDesc": "It can be obtained after logging in to the public account of PushPlus.", |
||||
|
"Features:PushPlus": "PushPlus", |
||||
|
"Features:MessageEnable": "Enable PushPlus", |
||||
|
"Features:MessageEnableDesc": "Enable so that the application will have the ability to push messages via PushPlus.", |
||||
|
"Features:Channel.WeChat": "Wechat Channel", |
||||
|
"Features:Channel.WeChat.Enable": "Enable Wechat Channel", |
||||
|
"Features:Channel.WeChat.EnableDesc": "Enable to allow PushPlus to push messages via the wechat official account.", |
||||
|
"Features:Channel.WeChat.SendLimit": "Wechat Channel Limit", |
||||
|
"Features:Channel.WeChat.SendLimitDesc": "Set to limit the amount of wechat channel push.", |
||||
|
"Features:Channel.WeChat.SendLimitInterval": "Wechat Channel Limit Interval", |
||||
|
"Features:Channel.WeChat.SendLimitIntervalDesc": "Set the limit period of wechat channel (time scale: days).", |
||||
|
"Features:Channel.WeWork": "WeWork Channel", |
||||
|
"Features:Channel.WeWork.Enable": "Enable WeWork Channel", |
||||
|
"Features:Channel.WeWork.EnableDesc": "Enable to allow PushPlus to push messages via the enterprise wechat.", |
||||
|
"Features:Channel.WeWork.SendLimit": "WeWork Channel Limit", |
||||
|
"Features:Channel.WeWork.SendLimitDesc": "Set to limit the amount of enterprise wechat channel push.", |
||||
|
"Features:Channel.WeWork.SendLimitInterval": "WeWork Channel Limit Interval", |
||||
|
"Features:Channel.WeWork.SendLimitIntervalDesc": "Set the limit period of enterprise wechat channel (time scale: days).", |
||||
|
"Features:Channel.Webhook": "Webhook Channel", |
||||
|
"Features:Channel.Webhook.Enable": "Enable Webhook Channel", |
||||
|
"Features:Channel.Webhook.EnableDesc": "Enable to allow PushPlus to push messages through third-party webhooks.", |
||||
|
"Features:Channel.Webhook.SendLimit": "Webhook Channel Limit", |
||||
|
"Features:Channel.Webhook.SendLimitDesc": "Set to limit the amount of Webhook channel push.", |
||||
|
"Features:Channel.Webhook.SendLimitInterval": "Webhook Channel Limit Interval", |
||||
|
"Features:Channel.Webhook.SendLimitIntervalDesc": "Set the Webhook channel limit period (time scale: days).", |
||||
|
"Features:Channel.Email": "Email Channel", |
||||
|
"Features:Channel.Email.Enable": "Enable Email Channel", |
||||
|
"Features:Channel.Email.EnableDesc": "Enable to allow PushPlus to push messages via Email.", |
||||
|
"Features:Channel.Email.SendLimit": "Email Channel Limit", |
||||
|
"Features:Channel.Email.SendLimitDesc": "Set to limit the amount of Email channel push.", |
||||
|
"Features:Channel.Email.SendLimitInterval": "Email Channel Limit Interval", |
||||
|
"Features:Channel.Email.SendLimitIntervalDesc": "Set the Email channel limit period (time scale: days).", |
||||
|
"Features:Channel.Sms": "Sms Channel", |
||||
|
"Features:Channel.Sms.Enable": "Enable Sms Channel", |
||||
|
"Features:Channel.Sms.EnableDesc": "Enable to allow PushPlus to push messages via sms.", |
||||
|
"Features:Channel.Sms.SendLimit": "Sms Channel Limit", |
||||
|
"Features:Channel.Sms.SendLimitDesc": "Set to limit the amount of sms channel push.", |
||||
|
"Features:Channel.Sms.SendLimitInterval": "Sms Channel Limit Interval", |
||||
|
"Features:Channel.Sms.SendLimitIntervalDesc": "Set the sms channel limit period (time scale: days)." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Settings:Security.Token": "用户令牌", |
||||
|
"Settings:Security.TokenDesc": "在PushPlus公众号登录后获取.", |
||||
|
"Settings:Security.SecretKey": "用户密钥", |
||||
|
"Settings:Security.SecretKeyDesc": "在PushPlus公众号登录后获取.", |
||||
|
"Features:PushPlus": "PushPlus(推送加)", |
||||
|
"Features:MessageEnable": "启用PushPlus消息推送", |
||||
|
"Features:MessageEnableDesc": "启用以使应用将拥有通过PushPlus推送消息的能力.", |
||||
|
"Features:Channel.WeChat": "微信消息通道", |
||||
|
"Features:Channel.WeChat.Enable": "启用微信消息通道", |
||||
|
"Features:Channel.WeChat.EnableDesc": "启用以允许PushPlus通过微信公众号推送消息.", |
||||
|
"Features:Channel.WeChat.SendLimit": "微信渠道推送量", |
||||
|
"Features:Channel.WeChat.SendLimitDesc": "设置以限制微信渠道推送量.", |
||||
|
"Features:Channel.WeChat.SendLimitInterval": "微信渠道限制周期", |
||||
|
"Features:Channel.WeChat.SendLimitIntervalDesc": "设置微信渠道限制周期(时间刻度: 天).", |
||||
|
"Features:Channel.WeWork": "企业微信消息通道", |
||||
|
"Features:Channel.WeWork.Enable": "启用企业微信消息通道", |
||||
|
"Features:Channel.WeWork.EnableDesc": "启用以允许PushPlus通过企业微信应用推送消息.", |
||||
|
"Features:Channel.WeWork.SendLimit": "企业微信渠道推送量", |
||||
|
"Features:Channel.WeWork.SendLimitDesc": "设置以限制企业微信渠道推送量.", |
||||
|
"Features:Channel.WeWork.SendLimitInterval": "企业微信渠道限制周期", |
||||
|
"Features:Channel.WeWork.SendLimitIntervalDesc": "设置企业微信渠道限制周期(时间刻度: 天).", |
||||
|
"Features:Channel.Webhook": "Webhook消息通道", |
||||
|
"Features:Channel.Webhook.Enable": "启用Webhook消息通道", |
||||
|
"Features:Channel.Webhook.EnableDesc": "启用以允许PushPlus通过第三方Webhook推送消息.", |
||||
|
"Features:Channel.Webhook.SendLimit": "Webhook渠道推送量", |
||||
|
"Features:Channel.Webhook.SendLimitDesc": "设置以限制Webhook渠道推送量.", |
||||
|
"Features:Channel.Webhook.SendLimitInterval": "Webhook渠道限制周期", |
||||
|
"Features:Channel.Webhook.SendLimitIntervalDesc": "设置Webhook渠道限制周期(时间刻度: 天).", |
||||
|
"Features:Channel.Email": "Email消息通道", |
||||
|
"Features:Channel.Email.Enable": "启用Email消息通道", |
||||
|
"Features:Channel.Email.EnableDesc": "启用以允许PushPlus通过Email推送消息.", |
||||
|
"Features:Channel.Email.SendLimit": "Email渠道推送量", |
||||
|
"Features:Channel.Email.SendLimitDesc": "设置以限制Email渠道推送量.", |
||||
|
"Features:Channel.Email.SendLimitInterval": "Email渠道限制周期", |
||||
|
"Features:Channel.Email.SendLimitIntervalDesc": "设置Email渠道限制周期(时间刻度: 天).", |
||||
|
"Features:Channel.Sms": "短信消息通道", |
||||
|
"Features:Channel.Sms.Enable": "启用短信消息通道", |
||||
|
"Features:Channel.Sms.EnableDesc": "启用以允许PushPlus通过短信推送消息.", |
||||
|
"Features:Channel.Sms.SendLimit": "短信渠道推送量", |
||||
|
"Features:Channel.Sms.SendLimitDesc": "设置以限制短信渠道推送量.", |
||||
|
"Features:Channel.Sms.SendLimitInterval": "短信渠道限制周期", |
||||
|
"Features:Channel.Sms.SendLimitIntervalDesc": "设置短信渠道限制周期(时间刻度: 天)." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
public interface IPushPlusMessageProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 查询消息发送结果
|
||||
|
/// </summary>
|
||||
|
/// <param name="shortCode">消息短链码</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<SendPushPlusMessageResult> GetSendResultAsync( |
||||
|
string shortCode, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 消息列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="current">当前所在分页数</param>
|
||||
|
/// <param name="pageSize">每页大小,最大值为50</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusPagedResponse<PushPlusMessage>> GetMessageListAsync( |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
public interface IPushPlusMessageSender |
||||
|
{ |
||||
|
Task<string> SendWeChatAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<string> SendEmailAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<string> SendWeWorkAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<string> SendWebhookAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<string> SendSmsAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
internal static class MessageHttpClientExtensions |
||||
|
{ |
||||
|
private const string _sendMessagTemplate = "{\"token\":\"$token\",\"title\":\"$title\",\"content\":\"$content\",\"topic\":\"$topic\",\"template\":\"$template\",\"channel\":\"$channel\",\"webhook\":\"$webhook\",\"callbackUrl\":\"$callbackUrl\",\"timestamp\":\"$timestamp\"}"; |
||||
|
|
||||
|
public async static Task<string> GetSendMessageContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string token, |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
string template = "html", |
||||
|
string channel = "wechat", |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
string timestamp = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/send"); |
||||
|
|
||||
|
var requestBody = _sendMessagTemplate |
||||
|
.Replace("$token", token) |
||||
|
.Replace("$title", title) |
||||
|
.Replace("$content", content) |
||||
|
.Replace("$topic", topic) |
||||
|
.Replace("$template", template) |
||||
|
.Replace("$channel", channel) |
||||
|
.Replace("$webhook", webhook) |
||||
|
.Replace("$callbackUrl", callbackUrl) |
||||
|
.Replace("$timestamp", timestamp); |
||||
|
|
||||
|
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public async static Task<string> GetSendResultContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
string shortCode, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/open/message/sendMessageResult?shortCode={shortCode}"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetMessageListContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/open/message/list"); |
||||
|
|
||||
|
var requestBodyTemplate = "{\"current\":\"$current\",\"pageSize\":\"$pageSize\"}"; |
||||
|
var requestBody = requestBodyTemplate |
||||
|
.Replace("$current", current.ToString()) |
||||
|
.Replace("$pageSize", pageSize.ToString()); |
||||
|
|
||||
|
requestMessage.Content = new StringContent(requestBody); |
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
public class PushPlusMessage |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 消息发送渠道;
|
||||
|
/// wechat-微信公众号,
|
||||
|
/// mail-邮件,
|
||||
|
/// cp-企业微信应用,
|
||||
|
/// webhook-第三方webhook
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("channel")] |
||||
|
public string Channel { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 消息类型;
|
||||
|
/// 1-一对一消息,
|
||||
|
/// 2-一对多消息
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("messageType")] |
||||
|
public PushPlusMessageType MessageType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 消息短链码;可用于查询消息发送结果
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("shortCode")] |
||||
|
public string ShortCode { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 消息标题
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("title")] |
||||
|
public string Title { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 群组名称,一对多消息才有值
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("topicName")] |
||||
|
public string TopicName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 更新日期
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("updateTime")] |
||||
|
public DateTime UpdateTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Token; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
public class PushPlusMessageProvider : IPushPlusMessageProvider, ITransientDependency |
||||
|
{ |
||||
|
protected ILogger<PushPlusMessageProvider> Logger { get; } |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected IHttpClientFactory HttpClientFactory { get; } |
||||
|
protected IPushPlusTokenProvider PushPlusTokenProvider { get; } |
||||
|
|
||||
|
public PushPlusMessageProvider( |
||||
|
ILogger<PushPlusMessageProvider> logger, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IHttpClientFactory httpClientFactory, |
||||
|
IPushPlusTokenProvider pushPlusTokenProvider) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
PushPlusTokenProvider = pushPlusTokenProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusPagedResponse<PushPlusMessage>> GetMessageListAsync( |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetMessageListContentAsync( |
||||
|
token.AccessKey, |
||||
|
current, |
||||
|
pageSize, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusPagedResponse<PushPlusMessage>>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<SendPushPlusMessageResult> GetSendResultAsync( |
||||
|
string shortCode, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetSendResultContentAsync( |
||||
|
token.AccessKey, |
||||
|
shortCode, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<SendPushPlusMessageResult>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,214 @@ |
|||||
|
using LINGYUN.Abp.Features.LimitValidation; |
||||
|
using LINGYUN.Abp.PushPlus.Channel; |
||||
|
using LINGYUN.Abp.PushPlus.Features; |
||||
|
using LINGYUN.Abp.PushPlus.Settings; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Features; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Settings; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
[RequiresFeature(PushPlusFeatureNames.Message.Enable)] |
||||
|
public class PushPlusMessageSender : IPushPlusMessageSender, ITransientDependency |
||||
|
{ |
||||
|
protected ILogger<PushPlusMessageSender> Logger { get; } |
||||
|
protected IClock Clock { get; } |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected ISettingProvider SettingProvider { get; } |
||||
|
protected IHttpClientFactory HttpClientFactory { get; } |
||||
|
|
||||
|
public PushPlusMessageSender( |
||||
|
ILogger<PushPlusMessageSender> logger, |
||||
|
IClock clock, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
ISettingProvider settingProvider, |
||||
|
IHttpClientFactory httpClientFactory) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
Clock = clock; |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
SettingProvider = settingProvider; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
} |
||||
|
|
||||
|
[RequiresFeature(PushPlusFeatureNames.Channel.WeChat.Enable)] |
||||
|
[RequiresLimitFeature( |
||||
|
PushPlusFeatureNames.Channel.WeChat.SendLimit, |
||||
|
PushPlusFeatureNames.Channel.WeChat.SendLimitInterval, |
||||
|
LimitPolicy.Days)] |
||||
|
public async virtual Task<string> SendWeChatAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await SendAsync( |
||||
|
title, |
||||
|
content, |
||||
|
topic, |
||||
|
template, |
||||
|
PushPlusChannelType.WeChat, |
||||
|
webhook, |
||||
|
callbackUrl, |
||||
|
cancellationToken); |
||||
|
} |
||||
|
|
||||
|
[RequiresFeature(PushPlusFeatureNames.Channel.WeWork.Enable)] |
||||
|
[RequiresLimitFeature( |
||||
|
PushPlusFeatureNames.Channel.WeWork.SendLimit, |
||||
|
PushPlusFeatureNames.Channel.WeWork.SendLimitInterval, |
||||
|
LimitPolicy.Days)] |
||||
|
public async virtual Task<string> SendWeWorkAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await SendAsync( |
||||
|
title, |
||||
|
content, |
||||
|
topic, |
||||
|
template, |
||||
|
PushPlusChannelType.WeWork, |
||||
|
webhook, |
||||
|
callbackUrl, |
||||
|
cancellationToken); |
||||
|
} |
||||
|
|
||||
|
[RequiresFeature(PushPlusFeatureNames.Channel.Email.Enable)] |
||||
|
[RequiresLimitFeature( |
||||
|
PushPlusFeatureNames.Channel.Email.SendLimit, |
||||
|
PushPlusFeatureNames.Channel.Email.SendLimitInterval, |
||||
|
LimitPolicy.Days)] |
||||
|
public async virtual Task<string> SendEmailAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await SendAsync( |
||||
|
title, |
||||
|
content, |
||||
|
topic, |
||||
|
template, |
||||
|
PushPlusChannelType.Email, |
||||
|
webhook, |
||||
|
callbackUrl, |
||||
|
cancellationToken); |
||||
|
} |
||||
|
|
||||
|
[RequiresFeature(PushPlusFeatureNames.Channel.Webhook.Enable)] |
||||
|
[RequiresLimitFeature( |
||||
|
PushPlusFeatureNames.Channel.Webhook.SendLimit, |
||||
|
PushPlusFeatureNames.Channel.Webhook.SendLimitInterval, |
||||
|
LimitPolicy.Days)] |
||||
|
public async virtual Task<string> SendWebhookAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await SendAsync( |
||||
|
title, |
||||
|
content, |
||||
|
topic, |
||||
|
template, |
||||
|
PushPlusChannelType.Webhook, |
||||
|
webhook, |
||||
|
callbackUrl, |
||||
|
cancellationToken); |
||||
|
} |
||||
|
|
||||
|
[RequiresFeature(PushPlusFeatureNames.Channel.Sms.Enable)] |
||||
|
[RequiresLimitFeature( |
||||
|
PushPlusFeatureNames.Channel.Sms.SendLimit, |
||||
|
PushPlusFeatureNames.Channel.Sms.SendLimitInterval, |
||||
|
LimitPolicy.Days)] |
||||
|
public async virtual Task<string> SendSmsAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await SendAsync( |
||||
|
title, |
||||
|
content, |
||||
|
topic, |
||||
|
template, |
||||
|
PushPlusChannelType.Sms, |
||||
|
webhook, |
||||
|
callbackUrl, |
||||
|
cancellationToken); |
||||
|
} |
||||
|
|
||||
|
protected async virtual Task<string> SendAsync( |
||||
|
string title, |
||||
|
string content, |
||||
|
string topic = "", |
||||
|
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, |
||||
|
PushPlusChannelType channel = PushPlusChannelType.WeChat, |
||||
|
string webhook = "", |
||||
|
string callbackUrl = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
Check.NotNullOrEmpty(title, nameof(title)); |
||||
|
Check.NotNullOrEmpty(content, nameof(content)); |
||||
|
|
||||
|
var token = await SettingProvider.GetOrNullAsync(PushPlusSettingNames.Security.Token); |
||||
|
Check.NotNullOrEmpty(token, PushPlusSettingNames.Security.Token); |
||||
|
|
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var sendContent = await client.GetSendMessageContentAsync( |
||||
|
token, |
||||
|
title, |
||||
|
content, |
||||
|
topic, |
||||
|
GetTemplate(template), |
||||
|
channel.GetChannelName(), |
||||
|
webhook, |
||||
|
callbackUrl, |
||||
|
cancellationToken: cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<string>>(sendContent); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
private static string GetTemplate(PushPlusMessageTemplate template) |
||||
|
{ |
||||
|
return template switch |
||||
|
{ |
||||
|
PushPlusMessageTemplate.Html => "html", |
||||
|
PushPlusMessageTemplate.Text => "txt", |
||||
|
PushPlusMessageTemplate.Json => "json", |
||||
|
PushPlusMessageTemplate.Markdown => "markdown", |
||||
|
PushPlusMessageTemplate.CloudMonitor => "cloudMonitor", |
||||
|
PushPlusMessageTemplate.Jenkins => "jenkins", |
||||
|
PushPlusMessageTemplate.Route => "route", |
||||
|
_ => "html", |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
public enum PushPlusMessageStatus |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 未发送
|
||||
|
/// </summary>
|
||||
|
NotSend = 0, |
||||
|
/// <summary>
|
||||
|
/// 发送中
|
||||
|
/// </summary>
|
||||
|
Sending = 1, |
||||
|
/// <summary>
|
||||
|
/// 已发送
|
||||
|
/// </summary>
|
||||
|
Successed = 2, |
||||
|
/// <summary>
|
||||
|
/// 发送失败
|
||||
|
/// </summary>
|
||||
|
Failed = 3, |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
/// <summary>
|
||||
|
/// 模板(template)枚举。默认使用html模板
|
||||
|
/// </summary>
|
||||
|
public enum PushPlusMessageTemplate |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 默认模板,支持html文本
|
||||
|
/// </summary>
|
||||
|
Html = 0, |
||||
|
/// <summary>
|
||||
|
/// 纯文本展示,不转义html
|
||||
|
/// </summary>
|
||||
|
Text = 1, |
||||
|
/// <summary>
|
||||
|
/// 内容基于json格式展示
|
||||
|
/// </summary>
|
||||
|
Json = 2, |
||||
|
/// <summary>
|
||||
|
/// 内容基于markdown格式展示
|
||||
|
/// </summary>
|
||||
|
Markdown = 3, |
||||
|
/// <summary>
|
||||
|
/// 阿里云监控报警定制模板
|
||||
|
/// </summary>
|
||||
|
CloudMonitor = 4, |
||||
|
/// <summary>
|
||||
|
/// jenkins插件定制模板
|
||||
|
/// </summary>
|
||||
|
Jenkins = 5, |
||||
|
/// <summary>
|
||||
|
/// 路由器插件定制模板
|
||||
|
/// </summary>
|
||||
|
Route = 6 |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
public enum PushPlusMessageType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 一对一消息
|
||||
|
/// </summary>
|
||||
|
Once, |
||||
|
/// <summary>
|
||||
|
/// 一对多消息
|
||||
|
/// </summary>
|
||||
|
Multiple |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
public class SendPushPlusMessageResult |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 消息投递状态
|
||||
|
/// 0-未投递,
|
||||
|
/// 1-发送中,
|
||||
|
/// 2-已发送,
|
||||
|
/// 3-发送失败
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("status")] |
||||
|
public PushPlusMessageStatus Status { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 发送失败原因
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("errorMessage")] |
||||
|
public string ErrorMessage { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 更新时间
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("updateTime")] |
||||
|
public DateTime UpdateTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus; |
||||
|
|
||||
|
public class PushPlusPagedResponse<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 当前页码
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("pageNum")] |
||||
|
public int PageNum { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 分页大小
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("pageSize")] |
||||
|
public int PageSize { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 总行数
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("total")] |
||||
|
public int Total { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 总页数
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("pages")] |
||||
|
public int Pages { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 消息列表
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("list")] |
||||
|
public List<T> Items { get; set; } |
||||
|
|
||||
|
public PushPlusPagedResponse() |
||||
|
{ |
||||
|
Items = new List<T>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.ExceptionHandling; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus; |
||||
|
public class PushPlusRequestException : AbpException, IHasErrorCode |
||||
|
{ |
||||
|
public string Code { get; } |
||||
|
|
||||
|
public PushPlusRequestException(string code, string message) |
||||
|
: base($"The PushPlush API returns an error: {code} - {message}") |
||||
|
{ |
||||
|
Code = code; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus; |
||||
|
|
||||
|
public class PushPlusResponse<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 状态码
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("code")] |
||||
|
public string Code { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 错误消息
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("msg")] |
||||
|
public string Message { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 返回数据
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("data")] |
||||
|
public T Data { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否调用成功
|
||||
|
/// </summary>
|
||||
|
public bool IsSuccessed => string.Equals("200", Code); |
||||
|
|
||||
|
public PushPlusResponse() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public PushPlusResponse(string code, string message) |
||||
|
{ |
||||
|
Code = code; |
||||
|
Message = message; |
||||
|
} |
||||
|
|
||||
|
public PushPlusResponse(string code, string message, T data) |
||||
|
{ |
||||
|
Code = code; |
||||
|
Message = message; |
||||
|
Data = data; |
||||
|
} |
||||
|
|
||||
|
public T GetData() |
||||
|
{ |
||||
|
ThrowOfFailed(); |
||||
|
|
||||
|
return Data; |
||||
|
} |
||||
|
|
||||
|
public void ThrowOfFailed() |
||||
|
{ |
||||
|
if (!IsSuccessed) |
||||
|
{ |
||||
|
throw new PushPlusRequestException(Code, Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Channel; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Setting; |
||||
|
/// <summary>
|
||||
|
/// 发送渠道接口
|
||||
|
/// </summary>
|
||||
|
public interface IPushPlusChannelProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 获取默认发送渠道
|
||||
|
/// </summary>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusChannel> GetDefaultChannelAsync( |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 修改默认发送渠道
|
||||
|
/// </summary>
|
||||
|
/// <param name="defaultChannel">默认渠道;wechat-微信公众号,mail-邮件,cp-企业微信应用,webhook-第三方webhook</param>
|
||||
|
/// <param name="defaultWebhook">渠道参数;webhook和cp渠道需要填写具体的webhook编号或自定义编码</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task UpdateDefaultChannelAsync( |
||||
|
PushPlusChannelType defaultChannel, |
||||
|
string defaultWebhook = "", |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 修改接收消息限制
|
||||
|
/// </summary>
|
||||
|
/// <param name="recevieLimit">接收消息限制;0-接收全部,1-不接收消息</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task UpdateRecevieLimitAsync( |
||||
|
PushPlusChannelRecevieLimit recevieLimit, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Setting; |
||||
|
/// <summary>
|
||||
|
/// 发送渠道
|
||||
|
/// </summary>
|
||||
|
public class PushPlusChannel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 默认渠道编码
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("defaultChannel")] |
||||
|
public string DefaultChannel { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 默认渠道名称
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("defaultChannelTxt")] |
||||
|
public string DefaultChannelName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 渠道参数
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("defaultWebhook")] |
||||
|
public string DefaultWebhook { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 发送限制;0-无限制,1-禁止所有渠道发送,2-限制微信渠道,3-限制邮件渠道
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("sendLimit")] |
||||
|
public PushPlusChannelRecevieLimit SendLimit { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 接收限制;0-接收全部,1-不接收消息
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("recevieLimit")] |
||||
|
public PushPlusChannelRecevieLimit RecevieLimit { get; set; } |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Channel; |
||||
|
using LINGYUN.Abp.PushPlus.Token; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Setting; |
||||
|
|
||||
|
public class PushPlusChannelProvider : IPushPlusChannelProvider, ITransientDependency |
||||
|
{ |
||||
|
protected ILogger<PushPlusChannelProvider> Logger { get; } |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected IHttpClientFactory HttpClientFactory { get; } |
||||
|
protected IPushPlusTokenProvider PushPlusTokenProvider { get; } |
||||
|
|
||||
|
public PushPlusChannelProvider( |
||||
|
ILogger<PushPlusChannelProvider> logger, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IHttpClientFactory httpClientFactory, |
||||
|
IPushPlusTokenProvider pushPlusTokenProvider) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
PushPlusTokenProvider = pushPlusTokenProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusChannel> GetDefaultChannelAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetDefaultChannelContentAsync( |
||||
|
token.AccessKey, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusChannel>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task UpdateDefaultChannelAsync( |
||||
|
PushPlusChannelType defaultChannel, |
||||
|
string defaultWebhook = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetUpdateDefaultChannelContentAsync( |
||||
|
token.AccessKey, |
||||
|
defaultChannel.GetChannelName(), |
||||
|
defaultWebhook, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<object>>(content); |
||||
|
|
||||
|
pushPlusResponse.ThrowOfFailed(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task UpdateRecevieLimitAsync( |
||||
|
PushPlusChannelRecevieLimit recevieLimit, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetUpdateRecevieLimitContentAsync( |
||||
|
token.AccessKey, |
||||
|
recevieLimit, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<object>>(content); |
||||
|
|
||||
|
pushPlusResponse.ThrowOfFailed(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Setting; |
||||
|
/// <summary>
|
||||
|
/// 接收限制;0-接收全部,1-不接收消息
|
||||
|
/// </summary>
|
||||
|
public enum PushPlusChannelRecevieLimit |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 接收全部
|
||||
|
/// </summary>
|
||||
|
RecevieAll = 0, |
||||
|
/// <summary>
|
||||
|
/// 不接收消息
|
||||
|
/// </summary>
|
||||
|
DontRecevied = 1 |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Setting; |
||||
|
/// <summary>
|
||||
|
/// 发送限制;
|
||||
|
/// 0-无限制,
|
||||
|
/// 1-禁止所有渠道发送,
|
||||
|
/// 2-限制微信渠道,
|
||||
|
/// 3-限制邮件渠道
|
||||
|
/// </summary>
|
||||
|
public enum PushPlusChannelSendLimit |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 无限制
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
/// <summary>
|
||||
|
/// 禁止所有渠道发送
|
||||
|
/// </summary>
|
||||
|
ForbidAllChannel = 1, |
||||
|
/// <summary>
|
||||
|
/// 限制微信渠道
|
||||
|
/// </summary>
|
||||
|
ForbidWeChatChannel = 2, |
||||
|
/// <summary>
|
||||
|
/// 限制邮件渠道
|
||||
|
/// </summary>
|
||||
|
ForbidEmailChannel = 3, |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Setting; |
||||
|
|
||||
|
internal static class SettingHttpClientExtensions |
||||
|
{ |
||||
|
public async static Task<string> GetDefaultChannelContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
"/api/open/setting/getUserSettings"); |
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
private const string _updateDefaultChannelTemplate = "{\"defaultChannel\":\"$defaultChannel\",\"defaultWebhook\":\"$defaultWebhook\"}"; |
||||
|
public async static Task<string> GetUpdateDefaultChannelContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
string defaultChannel, |
||||
|
string defaultWebhook = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/open/setting/changeDefaultChannel"); |
||||
|
|
||||
|
var requestBody = _updateDefaultChannelTemplate |
||||
|
.Replace("$defaultChannel", defaultChannel) |
||||
|
.Replace("$defaultWebhook", defaultWebhook); |
||||
|
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetUpdateRecevieLimitContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
PushPlusChannelRecevieLimit recevieLimit, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/open/setting/changeRecevieLimit?recevieLimit=${(int)recevieLimit}"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Settings; |
||||
|
|
||||
|
public class PushPlusSettingDefinitionProvider : SettingDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(ISettingDefinitionContext context) |
||||
|
{ |
||||
|
context.Add(new[] |
||||
|
{ |
||||
|
new SettingDefinition( |
||||
|
name: PushPlusSettingNames.Security.Token, |
||||
|
defaultValue: "qGE8NZ8rrQYj207kSv9vb5XzG1a+iK6z8yGPICjx3cY5p8bGcmMav5t0DRLjCprA", |
||||
|
displayName: L("Settings:Security.Token"), |
||||
|
description: L("Settings:Security.TokenDesc"), |
||||
|
isEncrypted: true) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
ConfigurationSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
new SettingDefinition( |
||||
|
name: PushPlusSettingNames.Security.SecretKey, |
||||
|
defaultValue: "HXGIfCpkUNonrOB8znJzNcDoKvZBKpNZ0tN38tktgrg=", |
||||
|
displayName: L("Settings:Security.SecretKey"), |
||||
|
description: L("Settings:Security.SecretKeyDesc"), |
||||
|
isEncrypted: true) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
ConfigurationSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static ILocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<PushPlusResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Settings; |
||||
|
|
||||
|
public static class PushPlusSettingNames |
||||
|
{ |
||||
|
public const string Prefix = "PushPlus"; |
||||
|
|
||||
|
public static class Security |
||||
|
{ |
||||
|
public const string Prefix = PushPlusSettingNames.Prefix + ".Security"; |
||||
|
|
||||
|
public const string Token = Prefix + ".Token"; |
||||
|
|
||||
|
public const string SecretKey = Prefix + ".SecretKey"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Token; |
||||
|
/// <summary>
|
||||
|
/// 获取AccessKey
|
||||
|
/// </summary>
|
||||
|
public interface IPushPlusTokenProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 获取全局可用Token
|
||||
|
/// </summary>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusToken> GetTokenAsync(CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Token; |
||||
|
|
||||
|
public class PushPlusToken |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 访问令牌,后续请求需加到header中
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("accessKey")] |
||||
|
public string AccessKey { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 过期时间,过期后需要重新获取
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("expiresIn")] |
||||
|
public int ExpiresIn { get; set; } |
||||
|
|
||||
|
public PushPlusToken() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public PushPlusToken(string accessKey, int expiresIn) |
||||
|
{ |
||||
|
AccessKey = accessKey; |
||||
|
ExpiresIn = expiresIn; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Token; |
||||
|
|
||||
|
public class PushPlusTokenCacheItem |
||||
|
{ |
||||
|
public const string KeyFormat = "t:{0};s:{1}"; |
||||
|
|
||||
|
public string AccessKey { get; set; } |
||||
|
|
||||
|
public int ExpiresIn { get; set; } |
||||
|
|
||||
|
public PushPlusTokenCacheItem() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public PushPlusTokenCacheItem(string accessKey, int expiresIn) |
||||
|
{ |
||||
|
AccessKey = accessKey; |
||||
|
ExpiresIn = expiresIn; |
||||
|
} |
||||
|
|
||||
|
public static string CalculateCacheKey(string token, string secretKey) |
||||
|
{ |
||||
|
return string.Format(KeyFormat, token, secretKey); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Settings; |
||||
|
using Microsoft.Extensions.Caching.Distributed; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Token; |
||||
|
|
||||
|
public class PushPlusTokenProvider : IPushPlusTokenProvider, ITransientDependency |
||||
|
{ |
||||
|
public ILogger<PushPlusTokenProvider> Logger { protected get; set; } |
||||
|
|
||||
|
protected IDistributedCache<PushPlusTokenCacheItem> Cache { get; } |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected ISettingProvider SettingProvider { get; } |
||||
|
protected IHttpClientFactory HttpClientFactory { get; } |
||||
|
|
||||
|
public PushPlusTokenProvider( |
||||
|
IDistributedCache<PushPlusTokenCacheItem> cache, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
ISettingProvider settingProvider, |
||||
|
IHttpClientFactory httpClientFactory) |
||||
|
{ |
||||
|
Cache = cache; |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
SettingProvider = settingProvider; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
|
||||
|
Logger = NullLogger<PushPlusTokenProvider>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusToken> GetTokenAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await SettingProvider.GetOrNullAsync(PushPlusSettingNames.Security.Token); |
||||
|
var secretKey = await SettingProvider.GetOrNullAsync(PushPlusSettingNames.Security.SecretKey); |
||||
|
|
||||
|
Check.NotNullOrEmpty(token, PushPlusSettingNames.Security.Token); |
||||
|
Check.NotNullOrEmpty(secretKey, PushPlusSettingNames.Security.SecretKey); |
||||
|
|
||||
|
return await GetTokenAsync(token, secretKey, cancellationToken); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusToken> GetTokenAsync( |
||||
|
string token, |
||||
|
string secretKey, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var cacheItem = await GetCacheItemAsync(token, secretKey, cancellationToken); |
||||
|
|
||||
|
return new PushPlusToken(cacheItem.AccessKey, cacheItem.ExpiresIn); |
||||
|
} |
||||
|
|
||||
|
protected async virtual Task<PushPlusTokenCacheItem> GetCacheItemAsync( |
||||
|
string token, |
||||
|
string secretKey, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var cacheKey = PushPlusTokenCacheItem.CalculateCacheKey(token, secretKey); |
||||
|
|
||||
|
Logger.LogDebug($"PushPlusTokenProvider.GetCacheItemAsync: {cacheKey}"); |
||||
|
|
||||
|
var cacheItem = await Cache.GetAsync(cacheKey, token: cancellationToken); |
||||
|
|
||||
|
if (cacheItem != null) |
||||
|
{ |
||||
|
Logger.LogDebug($"Found in the cache: {cacheKey}"); |
||||
|
return cacheItem; |
||||
|
} |
||||
|
|
||||
|
Logger.LogDebug($"Not found in the cache, getting from the httpClient: {cacheKey}"); |
||||
|
|
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetTokenContentAsync(token, secretKey, cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<PushPlusToken>>(content); |
||||
|
var pushPlusToken = pushPlusResponse.GetData(); |
||||
|
|
||||
|
cacheItem = new PushPlusTokenCacheItem( |
||||
|
pushPlusToken.AccessKey, |
||||
|
pushPlusToken.ExpiresIn); |
||||
|
|
||||
|
await Cache.SetAsync( |
||||
|
cacheKey, |
||||
|
cacheItem, |
||||
|
new DistributedCacheEntryOptions |
||||
|
{ |
||||
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(pushPlusToken.ExpiresIn - 120) |
||||
|
}, |
||||
|
token: cancellationToken); |
||||
|
|
||||
|
Logger.LogDebug($"Finished setting the cache item: {cacheKey}"); |
||||
|
|
||||
|
return cacheItem; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Token; |
||||
|
|
||||
|
internal static class TokenHttpClientExtensions |
||||
|
{ |
||||
|
public async static Task<string> GetTokenContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string token, |
||||
|
string secretKey, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/common/openApi/getAccessKey"); |
||||
|
var requestBodyTemplate = "{\"token\":\"$token\",\"secretKey\":\"$secretKey\"}"; |
||||
|
var requestBody = requestBodyTemplate |
||||
|
.Replace("$token", token) |
||||
|
.Replace("$secretKey", secretKey); |
||||
|
|
||||
|
requestMessage.Content = new StringContent(requestBody); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
/// <summary>
|
||||
|
/// 群组接口
|
||||
|
/// </summary>
|
||||
|
public interface IPushPlusTopicProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 获取群组列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="current">当前所在分页数</param>
|
||||
|
/// <param name="pageSize">每页大小,最大值为50</param>
|
||||
|
/// <param name="topicType">群组类型;0-我创建的,1-我加入的</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusPagedResponse<PushPlusTopic>> GetTopicListAsync( |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
PushPlusTopicType topicType = PushPlusTopicType.Create, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取我创建的群组详情
|
||||
|
/// </summary>
|
||||
|
/// <param name="topicId">群组编号</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusTopicProfile> GetTopicProfileAsync( |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取我加入的群详情
|
||||
|
/// </summary>
|
||||
|
/// <param name="topicId">群组编号</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusTopicForMe> GetTopicForMeProfileAsync( |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 新增群组
|
||||
|
/// </summary>
|
||||
|
/// <param name="topicCode">群组编码</param>
|
||||
|
/// <param name="topicName">群组名称</param>
|
||||
|
/// <param name="contact">联系方式</param>
|
||||
|
/// <param name="introduction">群组简介</param>
|
||||
|
/// <param name="receiptMessage">加入后回复内容</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<int> CreateTopicAsync( |
||||
|
string topicCode, |
||||
|
string topicName, |
||||
|
string contact, |
||||
|
string introduction, |
||||
|
string receiptMessage = "", |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取群组二维码
|
||||
|
/// </summary>
|
||||
|
/// <param name="topicId">群组编号</param>
|
||||
|
/// <param name="forever">二维码类型;0-临时二维码,1-永久二维码</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusTopicQrCode> GetTopicQrCodeAsync( |
||||
|
int topicId, |
||||
|
PushPlusTopicQrCodeType forever = PushPlusTopicQrCodeType.Temporary, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 退出群组
|
||||
|
/// </summary>
|
||||
|
/// <param name="topicId">群组编号</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<string> QuitTopicAsync( |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取群组内用户
|
||||
|
/// </summary>
|
||||
|
/// <param name="current">当前所在分页数</param>
|
||||
|
/// <param name="pageSize">每页大小,最大值为50</param>
|
||||
|
/// <param name="topicId">群组编号</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusPagedResponse<PushPlusTopicUser>> GetSubscriberListAsync( |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 删除群组内用户
|
||||
|
/// </summary>
|
||||
|
/// <param name="topicRelationId">用户编号</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<string> UnSubscriberAsync( |
||||
|
int topicRelationId, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
/// <summary>
|
||||
|
/// 群组
|
||||
|
/// </summary>
|
||||
|
public class PushPlusTopic |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 群组编号
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("topicId")] |
||||
|
public int TopicId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 群组编码
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("topicCode")] |
||||
|
public string TopicCode { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 群组名称
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("topicName")] |
||||
|
public string TopicName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 创建时间
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("createTime")] |
||||
|
public DateTime CreateTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
/// <summary>
|
||||
|
/// 我加入的群详情
|
||||
|
/// </summary>
|
||||
|
public class PushPlusTopicForMe : PushPlusTopic |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 联系方式
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("contact")] |
||||
|
public string Contact { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 群组简介
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("introduction")] |
||||
|
public string Introduction { get; set; } |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
/// <summary>
|
||||
|
/// 群组详情
|
||||
|
/// </summary>
|
||||
|
public class PushPlusTopicProfile : PushPlusTopic |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 永久二维码图片地址
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("qrCodeImgUrl")] |
||||
|
public string QrCodeImgUrl { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 联系方式
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("contact")] |
||||
|
public string Contact { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 群组简介
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("introduction")] |
||||
|
public string Introduction { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 加入后回复内容
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("receiptMessage")] |
||||
|
public string ReceiptMessage { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 群组订阅人总数
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("topicUserCount")] |
||||
|
public string TopicUserCount { get; set; } |
||||
|
} |
||||
@ -0,0 +1,196 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Token; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
|
||||
|
public class PushPlusTopicProvider : IPushPlusTopicProvider, ITransientDependency |
||||
|
{ |
||||
|
protected ILogger<PushPlusTopicProvider> Logger { get; } |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected IHttpClientFactory HttpClientFactory { get; } |
||||
|
protected IPushPlusTokenProvider PushPlusTokenProvider { get; } |
||||
|
|
||||
|
public PushPlusTopicProvider( |
||||
|
ILogger<PushPlusTopicProvider> logger, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IHttpClientFactory httpClientFactory, |
||||
|
IPushPlusTokenProvider pushPlusTokenProvider) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
PushPlusTokenProvider = pushPlusTokenProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<int> CreateTopicAsync( |
||||
|
string topicCode, |
||||
|
string topicName, |
||||
|
string contact, |
||||
|
string introduction, |
||||
|
string receiptMessage = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(topicCode, nameof(topicCode)); |
||||
|
Check.NotNullOrWhiteSpace(topicName, nameof(topicName)); |
||||
|
Check.NotNullOrWhiteSpace(contact, nameof(contact)); |
||||
|
Check.NotNullOrWhiteSpace(introduction, nameof(introduction)); |
||||
|
|
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetCreateTopicContentAsync( |
||||
|
token.AccessKey, |
||||
|
topicCode, |
||||
|
topicName, |
||||
|
contact, |
||||
|
introduction, |
||||
|
receiptMessage, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<int>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusTopicForMe> GetTopicForMeProfileAsync( |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetTopicForMeProfileContentAsync( |
||||
|
token.AccessKey, |
||||
|
topicId, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusTopicForMe>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusPagedResponse<PushPlusTopic>> GetTopicListAsync( |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
PushPlusTopicType topicType = PushPlusTopicType.Create, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetTopicListContentAsync( |
||||
|
token.AccessKey, |
||||
|
current, |
||||
|
pageSize, |
||||
|
topicType, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusPagedResponse<PushPlusTopic>>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusTopicProfile> GetTopicProfileAsync( |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetTopicProfileContentAsync( |
||||
|
token.AccessKey, |
||||
|
topicId, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusTopicProfile>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusTopicQrCode> GetTopicQrCodeAsync( |
||||
|
int topicId, |
||||
|
PushPlusTopicQrCodeType forever = PushPlusTopicQrCodeType.Temporary, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetTopicQrCodeContentAsync( |
||||
|
token.AccessKey, |
||||
|
topicId, |
||||
|
forever, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusTopicQrCode>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<string> QuitTopicAsync( |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetQuitTopicContentAsync( |
||||
|
token.AccessKey, |
||||
|
topicId, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<string>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusPagedResponse<PushPlusTopicUser>> GetSubscriberListAsync( |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetSubscriberListContentAsync( |
||||
|
token.AccessKey, |
||||
|
current, |
||||
|
pageSize, |
||||
|
topicId, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusPagedResponse<PushPlusTopicUser>>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<string> UnSubscriberAsync( |
||||
|
int topicRelationId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetUnSubscriberContentAsync( |
||||
|
token.AccessKey, |
||||
|
topicRelationId, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<string>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
|
||||
|
public class PushPlusTopicQrCode |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 群组二维码图片路径
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("qrCodeImgUrl")] |
||||
|
public string QrCodeImgUrl { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 二维码类型;
|
||||
|
/// 0-临时二维码,
|
||||
|
/// 1-永久二维码
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("forever")] |
||||
|
public PushPlusTopicQrCodeType Forever { get; set; } |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
public enum PushPlusTopicQrCodeType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 临时
|
||||
|
/// </summary>
|
||||
|
Temporary = 0, |
||||
|
/// <summary>
|
||||
|
/// 永久
|
||||
|
/// </summary>
|
||||
|
Permanent = 1, |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
public enum PushPlusTopicType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 创建的
|
||||
|
/// </summary>
|
||||
|
Create = 0, |
||||
|
/// <summary>
|
||||
|
/// 加入的
|
||||
|
/// </summary>
|
||||
|
Join = 1 |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.User; |
||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
|
||||
|
public class PushPlusTopicUser |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 用户编号;可用于删除用户
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("id")] |
||||
|
public int Id { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 昵称
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("nickName")] |
||||
|
public string NickName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 用户微信openId
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("openId")] |
||||
|
public string OpenId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 头像url地址
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("headImgUrl")] |
||||
|
public string HeadImgUrl { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 头像url地址
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("userSex")] |
||||
|
public PushPlusUserSex? UserSex { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否绑定手机;0-未绑定,1-已绑定
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("havePhone")] |
||||
|
public PushPlusUserPhoneBindStatus? HavePhone { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否关注微信公众号;0-未关注,1-已关注
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("isFollow")] |
||||
|
public PushPlusUserFollowStatus? IsFollow { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 邮箱验证状态;0-未验证,1-待验证,2-已验证
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("emailStatus")] |
||||
|
public PushPlusUserEmailStatus? EmailStatus { get; set; } |
||||
|
} |
||||
@ -0,0 +1,177 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Topic; |
||||
|
|
||||
|
internal static class TopicHttpClientExtensions |
||||
|
{ |
||||
|
private const string _getTopicListTemplate = "{\"current\":$current,\"pageSize\":$pageSize,\"params\":{\"topicType\":$topicType}}"; |
||||
|
public async static Task<string> GetTopicListContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
PushPlusTopicType topicType = PushPlusTopicType.Create, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/open/topic/list"); |
||||
|
|
||||
|
var requestBody = _getTopicListTemplate |
||||
|
.Replace("$current", current.ToString()) |
||||
|
.Replace("$pageSize", pageSize.ToString()) |
||||
|
.Replace("$topicType", ((int)topicType).ToString()); |
||||
|
|
||||
|
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); |
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetTopicProfileContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/open/topic/detail?topicId={topicId}"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetTopicForMeProfileContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/open/topic/joinTopicDetail?topicId={topicId}"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
private const string _createTopicTemplate = "{\"topicCode\":\"$topicCode\",\"topicName\":\"$topicName\",\"contact\":\"$contact\",\"introduction\":\"$introduction\",\"receiptMessage\":\"$receiptMessage\"}"; |
||||
|
public async static Task<string> GetCreateTopicContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
string topicCode, |
||||
|
string topicName, |
||||
|
string contact, |
||||
|
string introduction, |
||||
|
string receiptMessage = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/open/topic/add"); |
||||
|
|
||||
|
var requestBody = _createTopicTemplate |
||||
|
.Replace("$topicCode", topicCode) |
||||
|
.Replace("$topicName", topicName) |
||||
|
.Replace("$contact", contact) |
||||
|
.Replace("$introduction", introduction) |
||||
|
.Replace("$receiptMessage", receiptMessage); |
||||
|
|
||||
|
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); |
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetTopicQrCodeContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int topicId, |
||||
|
PushPlusTopicQrCodeType forever = PushPlusTopicQrCodeType.Temporary, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/open/topic/qrCode?topicId={topicId}&forever={(int)forever}"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetQuitTopicContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/open/topic/exitTopic?topicId={topicId}"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
private const string _getSubscriberListTemplate = "{\"current\":$current,\"pageSize\":$pageSize,\"params\":{\"topicId\":$topicId}}"; |
||||
|
public async static Task<string> GetSubscriberListContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int current, |
||||
|
int pageSize, |
||||
|
int topicId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/open/topicUser/subscriberList"); |
||||
|
|
||||
|
var requestBody = _getSubscriberListTemplate |
||||
|
.Replace("$current", current.ToString()) |
||||
|
.Replace("$pageSize", pageSize.ToString()) |
||||
|
.Replace("$topicId", topicId.ToString()); |
||||
|
|
||||
|
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); |
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetUnSubscriberContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
int topicRelationId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/open/topicUser/deleteTopicUser?topicRelationId={topicRelationId}"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
/// <summary>
|
||||
|
/// 用户接口
|
||||
|
/// </summary>
|
||||
|
public interface IPushPlusUserProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 获取token
|
||||
|
/// </summary>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<string> GetMyTokenAsync(CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 个人资料详情
|
||||
|
/// </summary>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusUserProfile> GetMyProfileAsync(CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取解封剩余时间
|
||||
|
/// </summary>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<PushPlusUserLimitTime> GetMyLimitTimeAsync(CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
|
||||
|
public enum PushPlusUserEmailStatus |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 未验证
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
/// <summary>
|
||||
|
/// 待验证
|
||||
|
/// </summary>
|
||||
|
UnConfirmed = 1, |
||||
|
/// <summary>
|
||||
|
/// 已验证
|
||||
|
/// </summary>
|
||||
|
Confirmed = 2, |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
public enum PushPlusUserFollowStatus |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 已关注
|
||||
|
/// </summary>
|
||||
|
Follow = 0, |
||||
|
/// <summary>
|
||||
|
/// 未关注
|
||||
|
/// </summary>
|
||||
|
NoFollow = 1, |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
|
||||
|
public class PushPlusUserLimitTime |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 发送限制状态;
|
||||
|
/// 1-无限制,
|
||||
|
/// 2-短期限制,
|
||||
|
/// 3-永久限制
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("sendLimit")] |
||||
|
public PushPlusUserSendLimit SendLimit { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 解封时间
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("userLimitTime")] |
||||
|
public string UserLimitTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
public enum PushPlusUserPhoneBindStatus |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 绑定
|
||||
|
/// </summary>
|
||||
|
Bind = 0, |
||||
|
/// <summary>
|
||||
|
/// 未绑定
|
||||
|
/// </summary>
|
||||
|
NoBind = 1, |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
|
||||
|
public class PushPlusUserProfile |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 用户微信的openId
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("openId")] |
||||
|
public string OpenId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 用户微信的unionId
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("unionId")] |
||||
|
public string UnionId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 昵称
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("nickName")] |
||||
|
public string NickName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 头像
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("headImgUrl")] |
||||
|
public string HeadImgUrl { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 性别;
|
||||
|
/// 0-未设置,
|
||||
|
/// 1-男,
|
||||
|
/// 2-女
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("userSex")] |
||||
|
public PushPlusUserSex? Sex { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 用户令牌
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("token")] |
||||
|
public string Token { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 手机号
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("phoneNumber")] |
||||
|
public string PhoneNumber { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 邮箱
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("email")] |
||||
|
public string Email { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 邮箱验证状态;
|
||||
|
/// 0-未验证,
|
||||
|
/// 1-待验证,
|
||||
|
/// 2-已验证
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("emailStatus")] |
||||
|
public PushPlusUserEmailStatus? EmailStatus { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 生日
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("birthday")] |
||||
|
public DateTime? Birthday { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 用户积分
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("points")] |
||||
|
public int? Points { get; set; } |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
using LINGYUN.Abp.PushPlus.Token; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
|
||||
|
public class PushPlusUserProvider : IPushPlusUserProvider, ITransientDependency |
||||
|
{ |
||||
|
protected ILogger<PushPlusUserProvider> Logger { get; } |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected IHttpClientFactory HttpClientFactory { get; } |
||||
|
protected IPushPlusTokenProvider PushPlusTokenProvider { get; } |
||||
|
|
||||
|
public PushPlusUserProvider( |
||||
|
ILogger<PushPlusUserProvider> logger, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IHttpClientFactory httpClientFactory, |
||||
|
IPushPlusTokenProvider pushPlusTokenProvider) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
PushPlusTokenProvider = pushPlusTokenProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusUserLimitTime> GetMyLimitTimeAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetLimitTimeContentAsync( |
||||
|
token.AccessKey, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusUserLimitTime>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PushPlusUserProfile> GetMyProfileAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetProfileContentAsync( |
||||
|
token.AccessKey, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<PushPlusUserProfile>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<string> GetMyTokenAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await PushPlusTokenProvider.GetTokenAsync(); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetTokenContentAsync( |
||||
|
token.AccessKey, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var pushPlusResponse = JsonSerializer |
||||
|
.Deserialize<PushPlusResponse<string>>(content); |
||||
|
|
||||
|
return pushPlusResponse.GetData(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
|
||||
|
public enum PushPlusUserSendLimit |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 未限制
|
||||
|
/// </summary>
|
||||
|
None = 1, |
||||
|
/// <summary>
|
||||
|
/// 短期限制
|
||||
|
/// </summary>
|
||||
|
Short = 2, |
||||
|
/// <summary>
|
||||
|
/// 永久限制
|
||||
|
/// </summary>
|
||||
|
Permanent = 3, |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
public enum PushPlusUserSex |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 未设置
|
||||
|
/// </summary>
|
||||
|
NoSet = 0, |
||||
|
/// <summary>
|
||||
|
/// 男
|
||||
|
/// </summary>
|
||||
|
Man =1, |
||||
|
/// <summary>
|
||||
|
/// 女
|
||||
|
/// </summary>
|
||||
|
Woman = 2, |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.User; |
||||
|
|
||||
|
internal static class UserHttpClientExtensions |
||||
|
{ |
||||
|
public async static Task<string> GetTokenContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
"/api/open/user/token"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetProfileContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
"api/open/user/myInfo"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> GetLimitTimeContentAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string accessKey, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
"api/open/user/userLimitTime"); |
||||
|
|
||||
|
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection; |
||||
|
|
||||
|
internal static class IServiceConnectionExtensions |
||||
|
{ |
||||
|
public static IServiceCollection AddPushPlusClient( |
||||
|
this IServiceCollection services) |
||||
|
{ |
||||
|
services.AddHttpClient( |
||||
|
"_Abp_PushPlus_Client", |
||||
|
(httpClient) => |
||||
|
{ |
||||
|
httpClient.BaseAddress = new Uri("https://www.pushplus.plus"); |
||||
|
}); |
||||
|
|
||||
|
return services; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
# LINGYUN.Abp.PushPlus |
||||
|
|
||||
|
集成PushPlus |
||||
|
|
||||
|
实现PushPlus相关Api文档,拥有PushPlus开放能力 |
||||
|
|
||||
|
详情见PushPlus文档: https://www.pushplus.plus/doc/guide/openApi.html#%E6%96%87%E6%A1%A3%E8%AF%B4%E6%98%8E |
||||
|
|
||||
|
## 模块引用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpPushPlusModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
* PushPlus PushPlus特性分组 |
||||
|
* PushPlus.Message.Enable 全局启用PushPlus消息通道 |
||||
|
* PushPlus.Channel.WeChat 微信公众号消息通道 |
||||
|
* PushPlus.Channel.WeChat.Enable 启用微信公众号消息通道 |
||||
|
* PushPlus.Channel.WeChat.SendLimit 微信公众号消息通道限制次数 |
||||
|
* PushPlus.Channel.WeChat.SendLimitInterval 微信公众号消息通道限制周期(天) |
||||
|
* PushPlus.Channel.WeWork 企业微信消息通道 |
||||
|
* PushPlus.Channel.WeWork.Enable 启用企业微信消息通道 |
||||
|
* PushPlus.Channel.WeWork.SendLimit 企业微信消息通道限制次数 |
||||
|
* PushPlus.Channel.WeWork.SendLimitInterval 企业微信消息通道限制周期(天) |
||||
|
* PushPlus.Channel.Webhook Webhook消息通道 |
||||
|
* PushPlus.Channel.Webhook.Enable 启用Webhook消息通道 |
||||
|
* PushPlus.Channel.Webhook.SendLimit Webhook消息通道限制次数 |
||||
|
* PushPlus.Channel.Webhook.SendLimitInterval Webhook消息通道限制周期(天) |
||||
|
* PushPlus.Channel.Email Email消息通道 |
||||
|
* PushPlus.Channel.Email.Enable 启用Email消息通道 |
||||
|
* PushPlus.Channel.Email.SendLimit Email消息通道限制次数 |
||||
|
* PushPlus.Channel.Email.SendLimitInterval Email消息通道限制周期(天) |
||||
|
* PushPlus.Channel.Sms 短信消息通道 |
||||
|
* PushPlus.Channel.Sms.Enable 启用短信消息通道 |
||||
|
* PushPlus.Channel.Sms.SendLimit 短信消息通道限制次数 |
||||
|
* PushPlus.Channel.Sms.SendLimitInterval 短信消息通道限制周期(天) |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace System.Net.Http; |
||||
|
|
||||
|
internal static class IHttpClientFactoryExtensions |
||||
|
{ |
||||
|
public static HttpClient GetPushPlusClient( |
||||
|
this IHttpClientFactory httpClientFactory) |
||||
|
{ |
||||
|
return httpClientFactory.CreateClient("_Abp_PushPlus_Client"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\pushplus\LINGYUN.Abp.PushPlus\LINGYUN.Abp.PushPlus.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,7 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus; |
||||
|
|
||||
|
public class AbpPushPlusTestBase : AbpTestsBase<AbpPushPlusTestModule> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpPushPlusModule), |
||||
|
typeof(AbpTestsBaseModule))] |
||||
|
public class AbpPushPlusTestModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configurationOptions = new AbpConfigurationBuilderOptions |
||||
|
{ |
||||
|
BasePath = @"D:\Projects\Development\Abp\PushPlus", |
||||
|
EnvironmentName = "Test" |
||||
|
}; |
||||
|
var configuration = ConfigurationHelper.BuildConfiguration(configurationOptions); |
||||
|
|
||||
|
context.Services.ReplaceConfiguration(configuration); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
using Shouldly; |
||||
|
|
||||
|
namespace LINGYUN.Abp.PushPlus.Message; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// TODO: 接入webhook测试
|
||||
|
/// TODO: 接入企业微信测试
|
||||
|
/// </summary>
|
||||
|
public class PushPlusMessageSenderTests : AbpPushPlusTestBase |
||||
|
{ |
||||
|
protected IPushPlusMessageSender PushPlusMessageSender { get; } |
||||
|
public PushPlusMessageSenderTests() |
||||
|
{ |
||||
|
PushPlusMessageSender = GetRequiredService<IPushPlusMessageSender>(); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("Title from the Xunit unit test", "Content from the Xunit unit test.")] |
||||
|
public async virtual Task Send_To_We_Chat_Test( |
||||
|
string title, |
||||
|
string content) |
||||
|
{ |
||||
|
var result = await PushPlusMessageSender.SendWeChatAsync(title, content); |
||||
|
|
||||
|
result.ShouldNotBeNullOrWhiteSpace(); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("Title from the Xunit unit test", "Content from the Xunit unit test.")] |
||||
|
public async virtual Task Send_To_Email_Test( |
||||
|
string title, |
||||
|
string content) |
||||
|
{ |
||||
|
var result = await PushPlusMessageSender.SendEmailAsync(title, content); |
||||
|
|
||||
|
result.ShouldNotBeNullOrWhiteSpace(); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue