58 changed files with 1768 additions and 7 deletions
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.1</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Identity.Domain" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WxPusher\LINGYUN.Abp.WxPusher.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,12 @@ |
|||||
|
using LINGYUN.Abp.WxPusher; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Identity.WxPusher; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpWxPusherModule), |
||||
|
typeof(AbpIdentityDomainModule))] |
||||
|
public class AbpIdentityWxPusherModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using LINGYUN.Abp.WxPusher.Security.Claims; |
||||
|
using LINGYUN.Abp.WxPusher.User; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Identity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Identity.WxPusher.User; |
||||
|
|
||||
|
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] |
||||
|
[ExposeServices(typeof(IWxPusherUserStore))] |
||||
|
public class IdentityWxPusherUserStore : IWxPusherUserStore |
||||
|
{ |
||||
|
protected IdentityUserManager UserManager { get; } |
||||
|
|
||||
|
public IdentityWxPusherUserStore(IdentityUserManager userManager) |
||||
|
{ |
||||
|
UserManager = userManager; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<List<int>> GetSubscribeTopicsAsync(IEnumerable<Guid> userIds, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var topics = new List<int>(); |
||||
|
|
||||
|
foreach (var userId in userIds) |
||||
|
{ |
||||
|
var user = await UserManager.FindByIdAsync(userId.ToString()); |
||||
|
|
||||
|
var userUidClaim = user?.Claims |
||||
|
.Where(c => c.ClaimType.Equals(AbpWxPusherClaimTypes.Uid)) |
||||
|
.FirstOrDefault(); |
||||
|
|
||||
|
if (userUidClaim != null && |
||||
|
int.TryParse(userUidClaim.ClaimValue, out var topic)) |
||||
|
{ |
||||
|
topics.Add(topic); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return topics.Distinct().ToList(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
# LINGYUN.Abp.Identity.WxPusher |
||||
|
|
||||
|
IWxPusherUserStore 接口的Identity模块实现, 通过用户Claims来获取关注的topic列表 |
||||
|
|
||||
|
## 模块引用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpIdentityWxPusherModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
|
``` |
||||
@ -0,0 +1,16 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\common\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WxPusher\LINGYUN.Abp.WxPusher.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,74 @@ |
|||||
|
using LINGYUN.Abp.WxPusher.Messages; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications; |
||||
|
|
||||
|
public static class NotificationDefinitionExtensions |
||||
|
{ |
||||
|
private const string Prefix = "wx-pusher:"; |
||||
|
private const string ContentTypeKey = Prefix + "contentType"; |
||||
|
private const string TopicKey = Prefix + "contentType"; |
||||
|
/// <summary>
|
||||
|
/// 设定消息内容类型
|
||||
|
/// </summary>
|
||||
|
/// <param name="notification"></param>
|
||||
|
/// <param name="contentType"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public static NotificationDefinition WithContentType( |
||||
|
this NotificationDefinition notification, |
||||
|
MessageContentType contentType) |
||||
|
{ |
||||
|
return notification.WithProperty(ContentTypeKey, contentType); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 获取消息发送通道
|
||||
|
/// </summary>
|
||||
|
/// <param name="notification"></param>
|
||||
|
/// <param name="defaultChannelType"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public static MessageContentType GetContentTypeOrDefault( |
||||
|
this NotificationDefinition notification, |
||||
|
MessageContentType defaultContentType = MessageContentType.Text) |
||||
|
{ |
||||
|
if (notification.Properties.TryGetValue(ContentTypeKey, out var defineContentType) == true && |
||||
|
defineContentType is MessageContentType contentType) |
||||
|
{ |
||||
|
return contentType; |
||||
|
} |
||||
|
|
||||
|
return defaultContentType; |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 消息主题(Topic)
|
||||
|
/// see: https://wxpusher.dingliqc.com/docs/#/?id=%e5%90%8d%e8%af%8d%e8%a7%a3%e9%87%8a
|
||||
|
/// </summary>
|
||||
|
/// <param name="notification">群组编码</param>
|
||||
|
/// <param name="topics"></param>
|
||||
|
/// <returns>
|
||||
|
/// <see cref="NotificationDefinition"/>
|
||||
|
/// </returns>
|
||||
|
public static NotificationDefinition WithTopics( |
||||
|
this NotificationDefinition notification, |
||||
|
List<int> topics) |
||||
|
{ |
||||
|
return notification.WithProperty(TopicKey, topics); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 获取消息群发群组编码
|
||||
|
/// </summary>
|
||||
|
/// <param name="notification"></param>
|
||||
|
/// <returns>
|
||||
|
/// 通知定义的群组编码列表
|
||||
|
/// </returns>
|
||||
|
public static List<int> GetTopics( |
||||
|
this NotificationDefinition notification) |
||||
|
{ |
||||
|
if (notification.Properties.TryGetValue(TopicKey, out var topicsDefine) == true && |
||||
|
topicsDefine is List<int> topics) |
||||
|
{ |
||||
|
return topics; |
||||
|
} |
||||
|
|
||||
|
return new List<int>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using LINGYUN.Abp.WxPusher; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WxPusher; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpNotificationModule), |
||||
|
typeof(AbpWxPusherModule))] |
||||
|
public class AbpNotificationsPushPlusModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpNotificationOptions>(options => |
||||
|
{ |
||||
|
options.PublishProviders.Add<WxPusherNotificationPublishProvider>(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
using LINGYUN.Abp.RealTime.Localization; |
||||
|
using LINGYUN.Abp.WxPusher.Messages; |
||||
|
using LINGYUN.Abp.WxPusher.User; |
||||
|
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.WxPusher; |
||||
|
|
||||
|
public class WxPusherNotificationPublishProvider : NotificationPublishProvider |
||||
|
{ |
||||
|
public const string ProviderName = "WxPusher"; |
||||
|
|
||||
|
public override string Name => ProviderName; |
||||
|
|
||||
|
protected IWxPusherUserStore WxPusherUserStore { get; } |
||||
|
|
||||
|
protected IWxPusherMessageSender WxPusherMessageSender { get; } |
||||
|
|
||||
|
protected IStringLocalizerFactory LocalizerFactory { get; } |
||||
|
|
||||
|
protected AbpLocalizationOptions LocalizationOptions { get; } |
||||
|
|
||||
|
protected INotificationDefinitionManager NotificationDefinitionManager { get; } |
||||
|
|
||||
|
public WxPusherNotificationPublishProvider( |
||||
|
IWxPusherUserStore wxPusherUserStore, |
||||
|
IWxPusherMessageSender wxPusherMessageSender, |
||||
|
IStringLocalizerFactory localizerFactory, |
||||
|
IOptions<AbpLocalizationOptions> localizationOptions, |
||||
|
INotificationDefinitionManager notificationDefinitionManager) |
||||
|
{ |
||||
|
WxPusherUserStore = wxPusherUserStore; |
||||
|
WxPusherMessageSender = wxPusherMessageSender; |
||||
|
LocalizerFactory = localizerFactory; |
||||
|
LocalizationOptions = localizationOptions.Value; |
||||
|
NotificationDefinitionManager = notificationDefinitionManager; |
||||
|
} |
||||
|
|
||||
|
protected async override Task PublishAsync( |
||||
|
NotificationInfo notification, |
||||
|
IEnumerable<UserIdentifier> identifiers, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var topics = await WxPusherUserStore |
||||
|
.GetSubscribeTopicsAsync( |
||||
|
identifiers.Select(x => x.UserId), |
||||
|
cancellationToken); |
||||
|
|
||||
|
var notificationDefine = NotificationDefinitionManager.GetOrNull(notification.Name); |
||||
|
var topicDefine = notificationDefine?.GetTopics(); |
||||
|
if (topicDefine.Any()) |
||||
|
{ |
||||
|
topics = topicDefine; |
||||
|
} |
||||
|
var contentType = notificationDefine?.GetContentTypeOrDefault(MessageContentType.Text) |
||||
|
?? MessageContentType.Text; |
||||
|
|
||||
|
if (!notification.Data.NeedLocalizer()) |
||||
|
{ |
||||
|
var title = notification.Data.TryGetData("title").ToString(); |
||||
|
var message = notification.Data.TryGetData("message").ToString(); |
||||
|
|
||||
|
await WxPusherMessageSender.SendAsync( |
||||
|
content: message, |
||||
|
summary: title, |
||||
|
contentType: contentType, |
||||
|
topicIds: topics, |
||||
|
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 WxPusherMessageSender.SendAsync( |
||||
|
content: message, |
||||
|
summary: title, |
||||
|
contentType: contentType, |
||||
|
topicIds: topics, |
||||
|
cancellationToken: cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private LocalizationResource GetResource(string resourceName) |
||||
|
{ |
||||
|
return LocalizationOptions.Resources.Values |
||||
|
.First(x => x.ResourceName.Equals(resourceName)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
# LINGYUN.Abp.Notifications.WxPusher |
||||
|
|
||||
|
通知模块的WxPusher实现 |
||||
|
|
||||
|
使应用可通过WxPusher发布实时通知 |
||||
|
|
||||
|
## 模块引用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpNotificationsWxPusherModule))] |
||||
|
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,31 @@ |
|||||
|
<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\WxPusher\Localization\Resources\en.json" /> |
||||
|
<None Remove="LINGYUN\Abp\WxPusher\Localization\Resources\zh-Hans.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\WxPusher\Localization\Resources\en.json" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\WxPusher\Localization\Resources\zh-Hans.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.WxPusher.Localization; |
||||
|
using LINGYUN.Abp.WxPusher.Messages; |
||||
|
using LINGYUN.Abp.WxPusher.QrCode; |
||||
|
using LINGYUN.Abp.WxPusher.User; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Json.SystemTextJson; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Settings; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpJsonModule), |
||||
|
typeof(AbpSettingsModule), |
||||
|
typeof(AbpCachingModule), |
||||
|
typeof(AbpFeaturesLimitValidationModule))] |
||||
|
public class AbpWxPusherModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddWxPusherClient(); |
||||
|
context.Services.TryAddSingleton<IWxPusherUserStore>(NullWxPusherUserStore.Instance); |
||||
|
|
||||
|
Configure<AbpSystemTextJsonSerializerOptions>(options => |
||||
|
{ |
||||
|
options.UnsupportedTypes.TryAdd<WxPusherResult<int>>(); |
||||
|
options.UnsupportedTypes.TryAdd<WxPusherResult<string>>(); |
||||
|
options.UnsupportedTypes.TryAdd<WxPusherResult<CreateQrcodeResult>>(); |
||||
|
options.UnsupportedTypes.TryAdd<WxPusherResult<GetScanQrCodeResult>>(); |
||||
|
options.UnsupportedTypes.TryAdd<WxPusherResult<List<SendMessageResult>>>(); |
||||
|
options.UnsupportedTypes.TryAdd<WxPusherResult<WxPusherPagedResult<UserProfile>>>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpWxPusherModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<WxPusherResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/WxPusher/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using LINGYUN.Abp.WxPusher.Localization; |
||||
|
using Volo.Abp.Features; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Validation.StringValues; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Features; |
||||
|
|
||||
|
public class WxPusherFeatureDefinitionProvider : FeatureDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(IFeatureDefinitionContext context) |
||||
|
{ |
||||
|
var group = context.AddGroup( |
||||
|
name: WxPusherFeatureNames.GroupName, |
||||
|
displayName: L("Features:WxPusher")); |
||||
|
group.AddFeature( |
||||
|
name: WxPusherFeatureNames.Enable, |
||||
|
defaultValue: "true", |
||||
|
displayName: L("Features:WxPusherEnable"), |
||||
|
description: L("Features:WxPusherEnableDesc"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
|
||||
|
var message = group.AddFeature( |
||||
|
name: WxPusherFeatureNames.Message.GroupName, |
||||
|
displayName: L("Features:Message"), |
||||
|
description: L("Features:Message")); |
||||
|
|
||||
|
message.CreateChild( |
||||
|
name: WxPusherFeatureNames.Message.Enable, |
||||
|
defaultValue: "true", |
||||
|
displayName: L("Features:MessageEnable"), |
||||
|
description: L("Features:MessageEnableDesc"), |
||||
|
valueType: new ToggleStringValueType(new BooleanValueValidator())); |
||||
|
message.CreateChild( |
||||
|
name: WxPusherFeatureNames.Message.SendLimit, |
||||
|
defaultValue: "500", |
||||
|
displayName: L("Features:Message.SendLimit"), |
||||
|
description: L("Features:Message.SendLimitDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 500))); |
||||
|
message.CreateChild( |
||||
|
name: WxPusherFeatureNames.Message.SendLimitInterval, |
||||
|
defaultValue: "1", |
||||
|
displayName: L("Features:Message.SendLimitInterval"), |
||||
|
description: L("Features:Message.SendLimitIntervalDesc"), |
||||
|
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1))); |
||||
|
} |
||||
|
|
||||
|
private static LocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<WxPusherResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
namespace LINGYUN.Abp.WxPusher.Features; |
||||
|
|
||||
|
public static class WxPusherFeatureNames |
||||
|
{ |
||||
|
public const string GroupName = "WxPusher"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 启用WxPusher
|
||||
|
/// </summary>
|
||||
|
public const string Enable = GroupName + ".Enable"; |
||||
|
|
||||
|
public static class Message |
||||
|
{ |
||||
|
public const string GroupName = WxPusherFeatureNames.GroupName + ".Message"; |
||||
|
/// <summary>
|
||||
|
/// 启用消息推送
|
||||
|
/// </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,17 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Settings:Security.AppToken": "App Token", |
||||
|
"Settings:Security.AppTokenDesc": "If you have APP_TOKEN, you can send messages to users of the corresponding application. Keep it confidential.", |
||||
|
"Features:WxPusher": "WxPusher Wechat push service", |
||||
|
"Features:WxPusherEnable": "Enable WxPusher", |
||||
|
"Features:WxPusherEnableDesc": "Enable to enable the application to have WxPusher capabilities.", |
||||
|
"Features:Message": "WxPusher Wechat message push", |
||||
|
"Features:MessageEnable": "Enable WxPusher Wechat Message Push", |
||||
|
"Features:MessageEnableDesc": "Enable so that apps will have the ability to be pushed to wechat via WxPusher.", |
||||
|
"Features:Message.SendLimit": "Amount of wechat message push", |
||||
|
"Features:Message.SendLimitDesc": "Set to limit the amount of wechat message push.", |
||||
|
"Features:Message.SendLimitInterval": "Wechat message limit interval", |
||||
|
"Features:Message.SendLimitIntervalDesc": "Set the wechat message limit period (time scale: days). A single wechat user (UID) can receive a maximum of 500 messages per day. Please arrange the sending frequency reasonably." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Settings:Security.AppToken": "应用的身份标志", |
||||
|
"Settings:Security.AppTokenDesc": "拥有APP_TOKEN,就可以给对应的应用的用户发送消息, 请严格保密.", |
||||
|
"Features:WxPusher": "WxPusher微信推送服务", |
||||
|
"Features:WxPusherEnable": "启用WxPusher", |
||||
|
"Features:WxPusherEnableDesc": "启用以使应用拥有WxPusher的能力.", |
||||
|
"Features:Message": "WxPusher微信消息推送", |
||||
|
"Features:MessageEnable": "启用WxPusher微信消息推送", |
||||
|
"Features:MessageEnableDesc": "启用以使应用将拥有通过WxPusher推送到微信的能力.", |
||||
|
"Features:Message.SendLimit": "微信消息推送量", |
||||
|
"Features:Message.SendLimitDesc": "设置以限制微信消息推送量.", |
||||
|
"Features:Message.SendLimitInterval": "微信消息限制周期", |
||||
|
"Features:Message.SendLimitIntervalDesc": "设置微信消息限制周期(时间刻度: 天).单个微信用户(uid),每天最多接收500条消息,请合理安排发送频率." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Localization; |
||||
|
|
||||
|
[LocalizationResourceName("WxPusher")] |
||||
|
public class WxPusherResource |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Messages; |
||||
|
|
||||
|
public interface IWxPusherMessageProvider |
||||
|
{ |
||||
|
Task<WxPusherResult<int>> QueryMessageAsync( |
||||
|
int messageId, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Messages; |
||||
|
|
||||
|
public interface IWxPusherMessageSender |
||||
|
{ |
||||
|
Task<List<SendMessageResult>> SendAsync( |
||||
|
string content, |
||||
|
string summary = "", |
||||
|
MessageContentType contentType = MessageContentType.Text, |
||||
|
List<int> topicIds = null, |
||||
|
List<string> uids = null, |
||||
|
string url = "", |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
namespace LINGYUN.Abp.WxPusher.Messages; |
||||
|
public enum MessageContentType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 文字
|
||||
|
/// </summary>
|
||||
|
Text = 1, |
||||
|
/// <summary>
|
||||
|
/// html(只发送body标签内部的数据即可,不包括body标签
|
||||
|
/// </summary>
|
||||
|
Html = 2, |
||||
|
/// <summary>
|
||||
|
/// markdown
|
||||
|
/// </summary>
|
||||
|
Markdown = 3 |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Messages; |
||||
|
|
||||
|
internal static class MessageHttpClientExtensions |
||||
|
{ |
||||
|
public async static Task<string> SendMessageAsync( |
||||
|
this HttpClient httpClient, |
||||
|
SendMessage sendMessage, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/send/message"); |
||||
|
|
||||
|
var requestBody = JsonConvert.SerializeObject(sendMessage); |
||||
|
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> QueryMessageAsync( |
||||
|
this HttpClient httpClient, |
||||
|
int messageId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/send/query/{messageId}"); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Messages; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class SendMessage |
||||
|
{ |
||||
|
[JsonProperty("appToken")] |
||||
|
public string AppToken { get; } |
||||
|
|
||||
|
[JsonProperty("content")] |
||||
|
public string Content { get; } |
||||
|
|
||||
|
[JsonProperty("summary")] |
||||
|
public string Summary { get; set; } |
||||
|
|
||||
|
[JsonProperty("contentType")] |
||||
|
public MessageContentType ContentType { get; } |
||||
|
|
||||
|
[JsonProperty("topicIds")] |
||||
|
public List<int> TopicIds { get; } |
||||
|
|
||||
|
[JsonProperty("uids")] |
||||
|
public List<string> Uids { get; } |
||||
|
|
||||
|
[JsonProperty("url")] |
||||
|
public string Url { get; } |
||||
|
public SendMessage( |
||||
|
[NotNull] string appToken, |
||||
|
[NotNull] string content, |
||||
|
string summary = "", |
||||
|
MessageContentType contentType = MessageContentType.Text, |
||||
|
string url = "") |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(appToken, nameof(appToken)); |
||||
|
Check.NotNullOrWhiteSpace(content, nameof(content)); |
||||
|
Check.Length(summary, nameof(summary), 100); |
||||
|
|
||||
|
AppToken = appToken; |
||||
|
Content = content; |
||||
|
Summary = summary; |
||||
|
ContentType = contentType; |
||||
|
Url = url; |
||||
|
|
||||
|
TopicIds = new List<int>(); |
||||
|
Uids = new List<string>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Messages; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class SendMessageResult |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 状态码
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("code")] |
||||
|
public int Code { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 消息标识
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("messageId")] |
||||
|
public long MessageId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 状态
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("status")] |
||||
|
public string Status { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 用户标识
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("uid")] |
||||
|
public string Uid { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 群组标识
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("topicId")] |
||||
|
public string TopicId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否调用成功
|
||||
|
/// </summary>
|
||||
|
public bool IsSuccessed => Code == 1000; |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using LINGYUN.Abp.WxPusher.Features; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Messages; |
||||
|
|
||||
|
[RequiresFeature(WxPusherFeatureNames.Enable)] |
||||
|
public class WxPusherMessageProvider : WxPusherRequestProvider, IWxPusherMessageProvider |
||||
|
{ |
||||
|
public async virtual Task<WxPusherResult<int>> QueryMessageAsync( |
||||
|
int messageId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.QueryMessageAsync( |
||||
|
messageId, |
||||
|
cancellationToken); |
||||
|
|
||||
|
return JsonSerializer.Deserialize<WxPusherResult<int>>(content); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
using LINGYUN.Abp.Features.LimitValidation; |
||||
|
using LINGYUN.Abp.WxPusher.Features; |
||||
|
using LINGYUN.Abp.WxPusher.Token; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Messages; |
||||
|
|
||||
|
[RequiresFeature(WxPusherFeatureNames.Enable)] |
||||
|
public class WxPusherMessageSender : WxPusherRequestProvider, IWxPusherMessageSender |
||||
|
{ |
||||
|
protected IWxPusherTokenProvider WxPusherTokenProvider { get; } |
||||
|
|
||||
|
public WxPusherMessageSender(IWxPusherTokenProvider wxPusherTokenProvider) |
||||
|
{ |
||||
|
WxPusherTokenProvider = wxPusherTokenProvider; |
||||
|
} |
||||
|
|
||||
|
[RequiresFeature(WxPusherFeatureNames.Message.Enable)] |
||||
|
[RequiresLimitFeature( |
||||
|
WxPusherFeatureNames.Message.SendLimit, |
||||
|
WxPusherFeatureNames.Message.SendLimitInterval, |
||||
|
LimitPolicy.Days)] |
||||
|
public async virtual Task<List<SendMessageResult>> SendAsync( |
||||
|
string content, |
||||
|
string summary = "", |
||||
|
MessageContentType contentType = MessageContentType.Text, |
||||
|
List<int> topicIds = null, |
||||
|
List<string> uids = null, |
||||
|
string url = "", |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await WxPusherTokenProvider.GetTokenAsync(cancellationToken); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
var sendMessage = new SendMessage( |
||||
|
token, |
||||
|
content, |
||||
|
summary, |
||||
|
contentType, |
||||
|
url); |
||||
|
if (topicIds != null) |
||||
|
{ |
||||
|
sendMessage.TopicIds.AddIfNotContains(topicIds); |
||||
|
} |
||||
|
if (uids != null) |
||||
|
{ |
||||
|
sendMessage.Uids.AddIfNotContains(uids); |
||||
|
} |
||||
|
|
||||
|
var resultContent = await client.SendMessageAsync( |
||||
|
sendMessage, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var response = JsonSerializer |
||||
|
.Deserialize<WxPusherResult<List<SendMessageResult>>>(resultContent); |
||||
|
|
||||
|
return response.GetData(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.QrCode; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class CreateQrcodeRequest |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 应用的标志
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("appToken")] |
||||
|
public string AppToken { get; } |
||||
|
/// <summary>
|
||||
|
/// 二维码携带的参数,最长64位
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("extra")] |
||||
|
public string Extra { get; } |
||||
|
/// <summary>
|
||||
|
/// 二维码有效时间,s为单位,最大30天。
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("validTime")] |
||||
|
public int ValidTime { get; } |
||||
|
|
||||
|
public CreateQrcodeRequest( |
||||
|
[NotNull] string appToken, |
||||
|
[NotNull] string extra, |
||||
|
int validTime = 1800) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(appToken, nameof(appToken)); |
||||
|
Check.NotNullOrWhiteSpace(extra, nameof(extra), 64); |
||||
|
|
||||
|
AppToken = appToken; |
||||
|
Extra = extra; |
||||
|
ValidTime = validTime; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.QrCode; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class CreateQrcodeResult |
||||
|
{ |
||||
|
[JsonProperty("expires")] |
||||
|
public long Expires { get; set; } |
||||
|
|
||||
|
[JsonProperty("code")] |
||||
|
public string Code { get; set; } |
||||
|
|
||||
|
[JsonProperty("shortUrl")] |
||||
|
public string ShortUrl { get; set; } |
||||
|
|
||||
|
[JsonProperty("url")] |
||||
|
public string Url { get; set; } |
||||
|
|
||||
|
[JsonProperty("extra")] |
||||
|
public string Extra { get; set; } |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.QrCode; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class GetScanQrCodeResult |
||||
|
{ |
||||
|
[JsonProperty("appId")] |
||||
|
public int AppId { get; set; } |
||||
|
|
||||
|
[JsonProperty("appKey")] |
||||
|
public string AppKey { get; set; } |
||||
|
|
||||
|
[JsonProperty("appName")] |
||||
|
public string AppName { get; set; } |
||||
|
|
||||
|
[JsonProperty("extra")] |
||||
|
public string Extra { get; set; } |
||||
|
|
||||
|
[JsonProperty("source")] |
||||
|
public string Source { get; set; } |
||||
|
|
||||
|
[JsonProperty("time")] |
||||
|
public long Time { get; set; } |
||||
|
|
||||
|
[JsonProperty("uid")] |
||||
|
public string Uid { get; set; } |
||||
|
|
||||
|
[JsonProperty("userHeadImg")] |
||||
|
public string UserHeadImg { get; set; } |
||||
|
|
||||
|
[JsonProperty("userName")] |
||||
|
public string UserName { get; set; } |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.QrCode; |
||||
|
|
||||
|
public interface IWxPusherQrCodeProvider |
||||
|
{ |
||||
|
Task<CreateQrcodeResult> CreateQrcodeAsync( |
||||
|
[NotNull] string extra, |
||||
|
int validTime = 1800, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<GetScanQrCodeResult> GetScanQrCodeAsync( |
||||
|
[NotNull] string code, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.QrCode; |
||||
|
|
||||
|
internal static class QrCodeHttpClientExtensions |
||||
|
{ |
||||
|
public async static Task<string> CreateQrcodeAsync( |
||||
|
this HttpClient httpClient, |
||||
|
CreateQrcodeRequest qrcodeRequest, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Post, |
||||
|
"/api/fun/create/qrcode"); |
||||
|
|
||||
|
var requestBody = JsonConvert.SerializeObject(qrcodeRequest); |
||||
|
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> GetScanQrCodeUidAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string code, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
$"/api/fun/scan-qrcode-uid?code={code}"); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using LINGYUN.Abp.WxPusher.Features; |
||||
|
using LINGYUN.Abp.WxPusher.Token; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.QrCode; |
||||
|
|
||||
|
[RequiresFeature(WxPusherFeatureNames.Enable)] |
||||
|
public class WxPusherQrCodeProvider : WxPusherRequestProvider, IWxPusherQrCodeProvider |
||||
|
{ |
||||
|
protected IWxPusherTokenProvider WxPusherTokenProvider { get; } |
||||
|
|
||||
|
public WxPusherQrCodeProvider(IWxPusherTokenProvider wxPusherTokenProvider) |
||||
|
{ |
||||
|
WxPusherTokenProvider = wxPusherTokenProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<CreateQrcodeResult> CreateQrcodeAsync( |
||||
|
[NotNull] string extra, |
||||
|
int validTime = 1800, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await WxPusherTokenProvider.GetTokenAsync(cancellationToken); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
var request = new CreateQrcodeRequest(token, extra, validTime); |
||||
|
|
||||
|
var content = await client.CreateQrcodeAsync( |
||||
|
request, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var response = JsonSerializer.Deserialize<WxPusherResult<CreateQrcodeResult>>(content); |
||||
|
|
||||
|
return response.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<GetScanQrCodeResult> GetScanQrCodeAsync( |
||||
|
[NotNull] string code, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(code, nameof(code)); |
||||
|
|
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetScanQrCodeUidAsync( |
||||
|
code, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var response = JsonSerializer.Deserialize<WxPusherResult<GetScanQrCodeResult>>(content); |
||||
|
|
||||
|
return response.GetData(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace LINGYUN.Abp.WxPusher.Security.Claims; |
||||
|
|
||||
|
public static class AbpWxPusherClaimTypes |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 用户的唯一标识
|
||||
|
/// </summary>
|
||||
|
public static string Uid { get; set; } = "wx-pusher-uid"; |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using LINGYUN.Abp.WxPusher.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Settings; |
||||
|
|
||||
|
public class WxPusherSettingDefinitionProvider : SettingDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(ISettingDefinitionContext context) |
||||
|
{ |
||||
|
context.Add(new[] |
||||
|
{ |
||||
|
new SettingDefinition( |
||||
|
name: WxPusherSettingNames.Security.AppToken, |
||||
|
displayName: L("Settings:Security.AppToken"), |
||||
|
description: L("Settings:Security.AppTokenDesc"), |
||||
|
isEncrypted: true) |
||||
|
.WithProviders( |
||||
|
DefaultValueSettingValueProvider.ProviderName, |
||||
|
ConfigurationSettingValueProvider.ProviderName, |
||||
|
GlobalSettingValueProvider.ProviderName, |
||||
|
TenantSettingValueProvider.ProviderName), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static ILocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<WxPusherResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
namespace LINGYUN.Abp.WxPusher.Settings; |
||||
|
|
||||
|
public static class WxPusherSettingNames |
||||
|
{ |
||||
|
public const string Prefix = "WxPusher"; |
||||
|
|
||||
|
public static class Security |
||||
|
{ |
||||
|
public const string Prefix = WxPusherSettingNames.Prefix + ".Security"; |
||||
|
|
||||
|
public const string AppToken = Prefix + ".AppToken"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Token; |
||||
|
|
||||
|
public interface IWxPusherTokenProvider |
||||
|
{ |
||||
|
Task<string> GetTokenAsync(CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using LINGYUN.Abp.WxPusher.Settings; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Token; |
||||
|
|
||||
|
public class WxPusherTokenProvider : IWxPusherTokenProvider, ITransientDependency |
||||
|
{ |
||||
|
protected ISettingProvider SettingProvider { get; } |
||||
|
|
||||
|
public WxPusherTokenProvider(ISettingProvider settingProvider) |
||||
|
{ |
||||
|
SettingProvider = settingProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<string> GetTokenAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await SettingProvider.GetOrNullAsync( |
||||
|
WxPusherSettingNames.Security.AppToken); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace LINGYUN.Abp.WxPusher.User; |
||||
|
public enum FlowType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 关注应用
|
||||
|
/// </summary>
|
||||
|
App = 0, |
||||
|
/// <summary>
|
||||
|
/// 关注topic
|
||||
|
/// </summary>
|
||||
|
Topic = 1, |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.User; |
||||
|
/// <summary>
|
||||
|
/// 用户接口
|
||||
|
/// </summary>
|
||||
|
public interface IWxPusherUserProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 查询App的关注用户V2
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 获取到所有关注应用/主题的微信用户用户信息。需要注意,一个微信用户,如果同时关注应用,主题,甚至关注多个主题,会返回多条记录。
|
||||
|
/// </remarks>
|
||||
|
/// <param name="page">请求数据的页码</param>
|
||||
|
/// <param name="pageSize">分页大小,不能超过100</param>
|
||||
|
/// <param name="uid">用户的uid,可选,如果不传就是查询所有用户,传uid就是查某个用户的信息。</param>
|
||||
|
/// <param name="isBlock">查询拉黑用户,可选,不传查询所有用户,true查询拉黑用户,false查询没有拉黑的用户</param>
|
||||
|
/// <param name="type">关注的类型,可选,不传查询所有用户,0是应用,1是主题</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<WxPusherPagedResult<UserProfile>> GetUserListAsync( |
||||
|
int page = 1, |
||||
|
int pageSize = 10, |
||||
|
string uid = null, |
||||
|
bool? isBlock = null, |
||||
|
FlowType? type = null, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 删除用户
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 你可以删除用户对应用、主题的关注,删除以后,用户可以重新关注,如想让用户再次关注,可以调用拉黑接口,对用户拉黑。
|
||||
|
/// </remarks>
|
||||
|
/// <param name="id">用户id,通过用户查询接口可以获取</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> DeleteUserAsync( |
||||
|
int id, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 拉黑用户
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 拉黑以后不能再发送消息,用户也不能再次关注,除非你取消对他的拉黑。调用拉黑接口,不用再调用删除接口。
|
||||
|
/// </remarks>
|
||||
|
/// <param name="id">用户id,通过用户查询接口可以获取</param>
|
||||
|
/// <param name="reject">是否拉黑,true表示拉黑,false表示取消拉黑</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> RejectUserAsync( |
||||
|
int id, |
||||
|
bool reject, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.User; |
||||
|
|
||||
|
public interface IWxPusherUserStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 获取用户订阅的topic列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="userIds">用户标识列表</param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<List<int>> GetSubscribeTopicsAsync( |
||||
|
IEnumerable<Guid> userIds, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.User; |
||||
|
|
||||
|
public sealed class NullWxPusherUserStore : IWxPusherUserStore |
||||
|
{ |
||||
|
public readonly static IWxPusherUserStore Instance = new NullWxPusherUserStore(); |
||||
|
|
||||
|
private NullWxPusherUserStore() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public Task<List<int>> GetSubscribeTopicsAsync( |
||||
|
IEnumerable<Guid> userIds, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return Task.FromResult(new List<int>()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.User; |
||||
|
|
||||
|
internal static class UserHttpClientExtensions |
||||
|
{ |
||||
|
public async static Task<string> GetUserListAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string appToken, |
||||
|
int page = 1, |
||||
|
int pageSize = 10, |
||||
|
string uid = null, |
||||
|
bool? isBlock = null, |
||||
|
FlowType? type = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestUrl = "/api/fun/wxuser/v2?appToken=$appToken&page=$page&pageSize=$pageSize&uid=$uid&isBlock=$isBlock&type=$type"; |
||||
|
|
||||
|
requestUrl = requestUrl |
||||
|
.Replace("$appToken", appToken) |
||||
|
.Replace("$page", page.ToString()) |
||||
|
.Replace("$pageSize", pageSize.ToString()) |
||||
|
.Replace("$uid", uid ?? "") |
||||
|
.Replace("$isBlock", isBlock?.ToString() ?? "") |
||||
|
.Replace("$type", type.HasValue ? ((int)type).ToString() : ""); |
||||
|
|
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Get, |
||||
|
requestUrl); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> DeleteUserAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string appToken, |
||||
|
int id, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Delete, |
||||
|
$"/api/fun/remove?appToken={appToken}&id={id}"); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
|
||||
|
public async static Task<string> RejectUserAsync( |
||||
|
this HttpClient httpClient, |
||||
|
string appToken, |
||||
|
int id, |
||||
|
bool reject, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var requestMessage = new HttpRequestMessage( |
||||
|
HttpMethod.Put, |
||||
|
$"/api/fun/reject?appToken={appToken}&id={id}&reject={reject}"); |
||||
|
|
||||
|
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken); |
||||
|
|
||||
|
return await httpResponse.Content.ReadAsStringAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.User; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class UserProfile |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 用户uid
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("uid")] |
||||
|
public string Uid { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 新用户微信不再返回 ,强制返回空
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("headImg")] |
||||
|
public string HeadImg { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 创建时间
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("createTime")] |
||||
|
public long CreateTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 新用户微信不再返回 ,强制返回空
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("nickName")] |
||||
|
public string NickName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否拉黑
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("reject")] |
||||
|
public bool Reject { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 如果调用删除或者拉黑接口,需要这个id
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("id")] |
||||
|
public int Id { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 关注类型,
|
||||
|
/// 0:关注应用,
|
||||
|
/// 1:关注topic
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("type")] |
||||
|
public FlowType Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 关注的应用或者主题名字
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("target")] |
||||
|
public string Target { get; set; } |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
using LINGYUN.Abp.WxPusher.Features; |
||||
|
using LINGYUN.Abp.WxPusher.Token; |
||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.User; |
||||
|
|
||||
|
[RequiresFeature(WxPusherFeatureNames.Enable)] |
||||
|
public class WxPusherUserProvider : WxPusherRequestProvider, IWxPusherUserProvider |
||||
|
{ |
||||
|
protected IWxPusherTokenProvider WxPusherTokenProvider { get; } |
||||
|
|
||||
|
public WxPusherUserProvider(IWxPusherTokenProvider wxPusherTokenProvider) |
||||
|
{ |
||||
|
WxPusherTokenProvider = wxPusherTokenProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<bool> DeleteUserAsync( |
||||
|
int id, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await WxPusherTokenProvider.GetTokenAsync(cancellationToken); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.DeleteUserAsync( |
||||
|
token, |
||||
|
id, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var response = JsonSerializer.Deserialize<WxPusherResult<string>>(content); |
||||
|
|
||||
|
return response.Success; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<WxPusherPagedResult<UserProfile>> GetUserListAsync( |
||||
|
int page = 1, |
||||
|
int pageSize = 10, |
||||
|
string uid = null, |
||||
|
bool? isBlock = null, |
||||
|
FlowType? type = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (pageSize > 100) |
||||
|
{ |
||||
|
throw new ArgumentException("pageSize must be equal to or lower than 100!", nameof(pageSize)); |
||||
|
} |
||||
|
|
||||
|
var token = await WxPusherTokenProvider.GetTokenAsync(cancellationToken); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.GetUserListAsync( |
||||
|
token, |
||||
|
page, |
||||
|
pageSize, |
||||
|
uid, |
||||
|
isBlock, |
||||
|
type, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var response = JsonSerializer |
||||
|
.Deserialize<WxPusherResult<WxPusherPagedResult<UserProfile>>>(content); |
||||
|
|
||||
|
return response.GetData(); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<bool> RejectUserAsync( |
||||
|
int id, |
||||
|
bool reject, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var token = await WxPusherTokenProvider.GetTokenAsync(cancellationToken); |
||||
|
var client = HttpClientFactory.GetPushPlusClient(); |
||||
|
|
||||
|
var content = await client.RejectUserAsync( |
||||
|
token, |
||||
|
id, |
||||
|
reject, |
||||
|
cancellationToken); |
||||
|
|
||||
|
var response = JsonSerializer.Deserialize<WxPusherResult<string>>(content); |
||||
|
|
||||
|
return response.Success; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class WxPusherPagedResult<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 总数
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("total")] |
||||
|
public int Total { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 当前页码
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("page")] |
||||
|
public int Page { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 页码大小
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("pageSize")] |
||||
|
public int PageSize { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 记录列表
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("records")] |
||||
|
public List<T> Records { get; set; } = new List<T>(); |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.ExceptionHandling; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher; |
||||
|
|
||||
|
public class WxPusherRemoteCallException : AbpException, IHasErrorCode |
||||
|
{ |
||||
|
public string Code { get; } |
||||
|
|
||||
|
public WxPusherRemoteCallException(string code, string message) |
||||
|
: base($"The WxPusher api returns an error: {code} - {message}") |
||||
|
{ |
||||
|
Code = code; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System.Net.Http; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher; |
||||
|
|
||||
|
public abstract class WxPusherRequestProvider : ITransientDependency |
||||
|
{ |
||||
|
public IAbpLazyServiceProvider LazyServiceProvider { get; set; } |
||||
|
|
||||
|
protected ILoggerFactory LoggerFactory => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>(); |
||||
|
protected ILogger Logger => LazyServiceProvider.LazyGetService<ILogger>(provider => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance); |
||||
|
protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService<IJsonSerializer>(); |
||||
|
protected IHttpClientFactory HttpClientFactory => LazyServiceProvider.LazyGetRequiredService<IHttpClientFactory>(); |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class WxPusherResult<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 状态码
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("code")] |
||||
|
public int Code { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 错误消息
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("msg")] |
||||
|
public string Message { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 返回数据
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("data")] |
||||
|
public T Data { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否调用成功
|
||||
|
/// </summary>
|
||||
|
[JsonProperty("success")] |
||||
|
public bool Success { get; set; } |
||||
|
|
||||
|
public WxPusherResult() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public WxPusherResult(int code, string message) |
||||
|
{ |
||||
|
Code = code; |
||||
|
Message = message; |
||||
|
} |
||||
|
|
||||
|
public WxPusherResult(int code, string message, T data) |
||||
|
{ |
||||
|
Code = code; |
||||
|
Message = message; |
||||
|
Data = data; |
||||
|
} |
||||
|
|
||||
|
public T GetData() |
||||
|
{ |
||||
|
ThrowOfFailed(); |
||||
|
|
||||
|
return Data; |
||||
|
} |
||||
|
|
||||
|
public void ThrowOfFailed() |
||||
|
{ |
||||
|
if (!Success) |
||||
|
{ |
||||
|
throw new WxPusherRemoteCallException(Code.ToString(), Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection; |
||||
|
|
||||
|
internal static class IServiceCollectionExtensions |
||||
|
{ |
||||
|
public static IServiceCollection AddWxPusherClient( |
||||
|
this IServiceCollection services) |
||||
|
{ |
||||
|
services.AddHttpClient( |
||||
|
"_Abp_WxPusher_Client", |
||||
|
(httpClient) => |
||||
|
{ |
||||
|
httpClient.BaseAddress = new Uri("https://wxpusher.zjiecode.com"); |
||||
|
}); |
||||
|
|
||||
|
return services; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
# LINGYUN.Abp.WxPusher |
||||
|
|
||||
|
集成WxPusher |
||||
|
|
||||
|
实现WxPusher相关Api文档,拥有WxPusher开放能力 |
||||
|
|
||||
|
详情见WxPusher文档: https://wxpusher.dingliqc.com/docs/#/ |
||||
|
|
||||
|
## 模块引用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpWxPusherModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 用户订阅 |
||||
|
|
||||
|
实现 [IWxPusherUserStore](./LINGYUN/Abp/WxPusher/User/IWxPusherUserStore) 接口获取用户订阅列表 |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
* WxPusher WxPusher特性分组 |
||||
|
* WxPusher.Enable 全局启用WxPusher |
||||
|
* WxPusher.Message.Enable 全局启用WxPusher消息通道 |
||||
|
* WxPusher.Message WxPusher消息推送 |
||||
|
* WxPusher.Message.Enable 启用WxPusher消息推送 |
||||
|
* WxPusher.Message.SendLimit WxPusher消息推送限制次数 |
||||
|
* WxPusher.Message.SendLimitInterval WxPusher消息推送限制周期(天) |
||||
|
|
||||
|
## Settings |
||||
|
|
||||
|
* WxPusher.Security.AppToken 应用的身份标志,拥有APP_TOKEN,就可以给对应的应用的用户发送消息, 请严格保密. |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace System.Net.Http; |
||||
|
|
||||
|
internal static class IHttpClientFactoryExtensions |
||||
|
{ |
||||
|
public static HttpClient GetPushPlusClient( |
||||
|
this IHttpClientFactory httpClientFactory) |
||||
|
{ |
||||
|
return httpClientFactory.CreateClient("_Abp_WxPusher_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\wx-pusher\LINGYUN.Abp.WxPusher\LINGYUN.Abp.WxPusher.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,7 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher; |
||||
|
|
||||
|
public class AbpWxPusherTestBase : AbpTestsBase<AbpWxPusherTestModule> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpWxPusherModule), |
||||
|
typeof(AbpTestsBaseModule))] |
||||
|
public class AbpWxPusherTestModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configurationOptions = new AbpConfigurationBuilderOptions |
||||
|
{ |
||||
|
BasePath = @"D:\Projects\Development\Abp\WxPusher", |
||||
|
EnvironmentName = "Test" |
||||
|
}; |
||||
|
var configuration = ConfigurationHelper.BuildConfiguration(configurationOptions); |
||||
|
|
||||
|
context.Services.ReplaceConfiguration(configuration); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
using Shouldly; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WxPusher.Messages; |
||||
|
|
||||
|
public class WxPusherMessageSenderTests : AbpWxPusherTestBase |
||||
|
{ |
||||
|
protected IWxPusherMessageSender WxPusherMessageSender { get; } |
||||
|
public WxPusherMessageSenderTests() |
||||
|
{ |
||||
|
WxPusherMessageSender = GetRequiredService<IWxPusherMessageSender>(); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("Content from the Xunit unit test. \r\n Click the link at the top to redirect baidu site.")] |
||||
|
public async virtual Task Send_Text_Test(string content) |
||||
|
{ |
||||
|
var result = await WxPusherMessageSender |
||||
|
.SendAsync( |
||||
|
content, |
||||
|
contentType: MessageContentType.Text, |
||||
|
topicIds: new List<int> { 7182 }, |
||||
|
url: "https://www.baidu.com/"); |
||||
|
|
||||
|
result.ShouldNotBeNull(); |
||||
|
result.Count.ShouldBeGreaterThanOrEqualTo(1); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("<h>Content from the Xunit unit test.</h> <br /> <a href=\"https://www.baidu.com/\">Click to redirect baidu site.</a>")] |
||||
|
public async virtual Task Send_Html_Test(string content) |
||||
|
{ |
||||
|
var result = await WxPusherMessageSender |
||||
|
.SendAsync( |
||||
|
content, |
||||
|
contentType: MessageContentType.Html, |
||||
|
topicIds: new List<int> { 7182 }, |
||||
|
url: "https://www.baidu.com/"); |
||||
|
|
||||
|
result.ShouldNotBeNull(); |
||||
|
result.Count.ShouldBeGreaterThanOrEqualTo(1); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("**Content from the Xunit unit test.** <br /> <a href=\"https://www.baidu.com/\">Click to redirect baidu site.</a>")] |
||||
|
public async virtual Task Send_Markdown_Test(string content) |
||||
|
{ |
||||
|
var result = await WxPusherMessageSender |
||||
|
.SendAsync( |
||||
|
content, |
||||
|
contentType: MessageContentType.Markdown, |
||||
|
topicIds: new List<int> { 7182 }, |
||||
|
url: "https://www.baidu.com/"); |
||||
|
|
||||
|
result.ShouldNotBeNull(); |
||||
|
result.Count.ShouldBeGreaterThanOrEqualTo(1); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue