Browse Source

feat(notifications): add dynamic notifications support.

pull/894/head
colin 2 years ago
parent
commit
1b8fa76d37
  1. 40
      apps/vue/src/api/realtime/notifications/definitions/groups/index.ts
  2. 23
      apps/vue/src/api/realtime/notifications/definitions/groups/model/index.ts
  3. 40
      apps/vue/src/api/realtime/notifications/definitions/notifications/index.ts
  4. 43
      apps/vue/src/api/realtime/notifications/definitions/notifications/model/index.ts
  5. 51
      apps/vue/src/api/realtime/notifications/index.ts
  6. 19
      apps/vue/src/api/realtime/notifications/model/index.ts
  7. 23
      apps/vue/src/api/realtime/notifications/subscribes/index.ts
  8. 14
      apps/vue/src/api/realtime/notifications/subscribes/model/index.ts
  9. 60
      apps/vue/src/api/realtime/notifications/types/index.ts
  10. 215
      apps/vue/src/views/realtime/notifications/definitions/groups/components/GroupDefinitionModal.vue
  11. 149
      apps/vue/src/views/realtime/notifications/definitions/groups/components/GroupDefinitionTable.vue
  12. 15
      apps/vue/src/views/realtime/notifications/definitions/groups/datas/ModalData.ts
  13. 43
      apps/vue/src/views/realtime/notifications/definitions/groups/datas/TableData.ts
  14. 16
      apps/vue/src/views/realtime/notifications/definitions/groups/index.vue
  15. 338
      apps/vue/src/views/realtime/notifications/definitions/notifications/components/NotificationDefinitionModal.vue
  16. 288
      apps/vue/src/views/realtime/notifications/definitions/notifications/components/NotificationDefinitionTable.vue
  17. 307
      apps/vue/src/views/realtime/notifications/definitions/notifications/components/NotificationSendModal.vue
  18. 50
      apps/vue/src/views/realtime/notifications/definitions/notifications/datas/ModalData.ts
  19. 91
      apps/vue/src/views/realtime/notifications/definitions/notifications/datas/TableData.ts
  20. 115
      apps/vue/src/views/realtime/notifications/definitions/notifications/hooks/useNotificationDefinition.ts
  21. 16
      apps/vue/src/views/realtime/notifications/definitions/notifications/index.vue
  22. 5314
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/Migrations/20231026015443_Add-Field-With-Notification-Definition.Designer.cs
  23. 29
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/Migrations/20231026015443_Add-Field-With-Notification-Definition.cs
  24. 3
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/Migrations/SingleMigrationsDbContextModelSnapshot.cs
  25. 11
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionCreateDto.cs
  26. 19
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionCreateOrUpdateDto.cs
  27. 12
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionDto.cs
  28. 5
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionGetListInput.cs
  29. 4
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionUpdateDto.cs
  30. 18
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/INotificationGroupDefinitionAppService.cs
  31. 15
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionCreateDto.cs
  32. 31
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionCreateOrUpdateDto.cs
  33. 29
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionDto.cs
  34. 11
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionGetListInput.cs
  35. 4
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionUpdateDto.cs
  36. 13
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/INotificationDefinitionAppService.cs
  37. 42
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Dto/NotificationSendDto.cs
  38. 4
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Dto/NotificationTemplateSendDto.cs
  39. 2
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/INotificationAppService.cs
  40. 20
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Permissions/NotificationsPermissions.cs
  41. 35
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Permissions/NotificationsPermissionsDefinitionProvider.cs
  42. 5
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/AbpNotificationsApplicationModule.cs
  43. 13
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/AbpNotificationsApplicationServiceBase.cs
  44. 219
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/Definitions/Groups/NotificationGroupDefinitionAppService.cs
  45. 301
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/Definitions/Notifications/NotificationDefinitionAppService.cs
  46. 3
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/MyNotificationAppService.cs
  47. 3
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/MySubscriptionAppService.cs
  48. 68
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/NotificationAppService.cs
  49. 13
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationTemplateDefinitionProvider.cs
  50. 2
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionStore.cs
  51. 2
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/INotificationDefinitionManager.cs
  52. 2
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/IStaticNotificationDefinitionStore.cs
  53. 7
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinition.cs
  54. 10
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionManager.cs
  55. 10
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationGroupDefinition.cs
  56. 6
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NullDynamicNotificationDefinitionStore.cs
  57. 9
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/StaticNotificationDefinitionStore.cs
  58. 45
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain.Shared/LINGYUN/Abp/Notifications/Localization/DomainShared/en.json
  59. 45
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain.Shared/LINGYUN/Abp/Notifications/Localization/DomainShared/zh-Hans.json
  60. 1
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain.Shared/LINGYUN/Abp/Notifications/NotificationDefinitionRecordConsts.cs
  61. 58
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain.Shared/LINGYUN/Abp/Notifications/NotificationsErrorCodes.cs
  62. 36
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionInMemoryCache.cs
  63. 14
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionStore.cs
  64. 66
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionStoreCacheInvalidator.cs
  65. 2
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionStoreCache.cs
  66. 3
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/INotificationDefinitionGroupRecordRepository.cs
  67. 3
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/INotificationDefinitionRecordRepository.cs
  68. 20
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/NotificationDefinitionGroupRecord.cs
  69. 66
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/NotificationDefinitionRecord.cs
  70. 1
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/NotificationDefinitionSerializer.cs
  71. 14
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.EntityFrameworkCore/LINGYUN/Abp/Notifications/EntityFrameworkCore/EfCoreNotificationDefinitionGroupRecordRepository.cs
  72. 13
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.EntityFrameworkCore/LINGYUN/Abp/Notifications/EntityFrameworkCore/EfCoreNotificationDefinitionRecordRepository.cs
  73. 59
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.HttpApi/LINGYUN/Abp/Notifications/Definitions/Groups/NotificationGroupDefinitionController.cs
  74. 59
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.HttpApi/LINGYUN/Abp/Notifications/Definitions/Notifications/NotificationDefinitionController.cs
  75. 20
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.HttpApi/LINGYUN/Abp/Notifications/NotificationController.cs
  76. 13
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Localization/Resources/en.json
  77. 13
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Localization/Resources/zh-Hans.json
  78. 44
      aspnet-core/services/LY.MicroService.Applications.Single/BackgroundJobs/NotificationPublishJob.cs
  79. 22
      aspnet-core/services/LY.MicroService.Applications.Single/BackgroundJobs/NotificationPublishJobArgs.cs
  80. 59
      aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/ChatMessageEventHandler.cs
  81. 467
      aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/NotificationEventHandler.cs
  82. 53
      aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/TenantSynchronizer.cs
  83. 30
      aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/UserCreateEventHandler.cs
  84. 58
      aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Local/UserCreateJoinIMEventHandler.cs
  85. 69
      aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Local/UserCreateSendWelcomeEventHandler.cs
  86. 12
      aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs
  87. 1
      aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs
  88. 10
      aspnet-core/services/LY.MicroService.Applications.Single/MultiTenancy/ITenantConfigurationCache.cs
  89. 59
      aspnet-core/services/LY.MicroService.Applications.Single/MultiTenancy/TenantConfigurationCache.cs
  90. 19
      aspnet-core/services/LY.MicroService.Applications.Single/MultiTenancy/TenantConfigurationCacheItem.cs
  91. 2
      aspnet-core/services/LY.MicroService.Applications.Single/Properties/PublishProfiles/FolderProfile1.pubxml.user

40
apps/vue/src/api/realtime/notifications/definitions/groups/index.ts

@ -0,0 +1,40 @@
import { defHttp } from '/@/utils/http/axios';
import {
NotificationGroupDefinitionDto,
NotificationGroupDefinitionCreateDto,
NotificationGroupDefinitionUpdateDto,
NotificationGroupDefinitionGetListInput,
} from './model';
export const CreateAsyncByInput = (input: NotificationGroupDefinitionCreateDto) => {
return defHttp.post<NotificationGroupDefinitionDto>({
url: '/api/notifications/definitions/groups',
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/notifications/definitions/groups/${name}`,
});
};
export const GetAsyncByName = (name: string) => {
return defHttp.get<NotificationGroupDefinitionDto>({
url: `/api/notifications/definitions/groups/${name}`,
});
};
export const GetListAsyncByInput = (input: NotificationGroupDefinitionGetListInput) => {
return defHttp.get<ListResultDto<NotificationGroupDefinitionDto>>({
url: '/api/notifications/definitions/groups',
params: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: NotificationGroupDefinitionUpdateDto) => {
return defHttp.put<NotificationGroupDefinitionDto>({
url: `/api/notifications/definitions/groups/${name}`,
data: input,
});
};

23
apps/vue/src/api/realtime/notifications/definitions/groups/model/index.ts

@ -0,0 +1,23 @@
interface NotificationGroupDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
description?: string;
allowSubscriptionToClients: boolean;
}
export interface NotificationGroupDefinitionCreateDto extends NotificationGroupDefinitionCreateOrUpdateDto {
name: string;
}
export interface NotificationGroupDefinitionDto extends ExtensibleObject {
name: string;
displayName: string;
description?: string;
isStatic: boolean;
allowSubscriptionToClients: boolean;
}
export interface NotificationGroupDefinitionGetListInput {
filter?: string;
}
export type NotificationGroupDefinitionUpdateDto = NotificationGroupDefinitionCreateOrUpdateDto;

40
apps/vue/src/api/realtime/notifications/definitions/notifications/index.ts

@ -0,0 +1,40 @@
import { defHttp } from '/@/utils/http/axios';
import {
NotificationDefinitionDto,
NotificationDefinitionCreateDto,
NotificationDefinitionUpdateDto,
NotificationDefinitionGetListInput,
} from './model';
export const CreateAsyncByInput = (input: NotificationDefinitionCreateDto) => {
return defHttp.post<NotificationDefinitionDto>({
url: '/api/notifications/definitions/notifications',
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/notifications/definitions/notifications/${name}`,
});
};
export const GetAsyncByName = (name: string) => {
return defHttp.get<NotificationDefinitionDto>({
url: `/api/notifications/definitions/notifications/${name}`,
});
};
export const GetListAsyncByInput = (input: NotificationDefinitionGetListInput) => {
return defHttp.get<ListResultDto<NotificationDefinitionDto>>({
url: '/api/notifications/definitions/notifications',
params: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: NotificationDefinitionUpdateDto) => {
return defHttp.put<NotificationDefinitionDto>({
url: `/api/notifications/definitions/notifications/${name}`,
data: input,
});
};

43
apps/vue/src/api/realtime/notifications/definitions/notifications/model/index.ts

@ -0,0 +1,43 @@
import { NotificationContentType, NotificationLifetime, NotificationType } from "../../../types";
interface NotificationDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
description?: string;
template?: string;
allowSubscriptionToClients: boolean;
notificationType: NotificationType;
contentType: NotificationContentType;
notificationLifetime: NotificationLifetime;
providers: string[];
}
export interface NotificationDefinitionCreateDto extends NotificationDefinitionCreateOrUpdateDto {
name: string;
groupName: string;
}
export interface NotificationDefinitionDto extends ExtensibleObject {
name: string;
groupName: string;
isStatic: boolean;
displayName: string;
description?: string;
template?: string;
allowSubscriptionToClients: boolean;
notificationType: NotificationType;
contentType: NotificationContentType;
notificationLifetime: NotificationLifetime;
providers: string[];
}
export interface NotificationDefinitionGetListInput {
filter?: string;
groupName?: string;
template?: string;
allowSubscriptionToClients?: boolean;
notificationType?: NotificationType;
contentType?: NotificationContentType;
notificationLifetime?: NotificationLifetime;
}
export type NotificationDefinitionUpdateDto = NotificationDefinitionCreateOrUpdateDto;

51
apps/vue/src/api/realtime/notifications/index.ts

@ -0,0 +1,51 @@
import { defHttp } from '/@/utils/http/axios';
import {
GetNotificationPagedRequest, NotificationSendDto,
} from './model';
import { NotificationGroup, NotificationInfo, NotificationReadState } from './types';
export const markReadState = (
ids: string[],
state: NotificationReadState = NotificationReadState.Read,
) => {
return defHttp.put<void>({
url: '/api/notifications/my-notifilers/mark-read-state',
data: {
idList: ids,
state: state,
},
});
};
export const deleteById = (id: string) => {
return defHttp.delete<void>({
url: `/api/notifications/my-notifilers/${id}`,
});
};
export const getList = (input: GetNotificationPagedRequest) => {
return defHttp.get<PagedResultDto<NotificationInfo>>({
url: '/api/notifications/my-notifilers',
params: input,
});
};
export const getAssignableNotifiers = () => {
return defHttp.get<ListResultDto<NotificationGroup>>({
url: '/api/notifications/assignables',
});
};
export const send = (input: NotificationSendDto) => {
return defHttp.post<void>({
url: '/api/notifications/send',
data: input,
});
}
export const sendTemplate = (input: NotificationSendDto) => {
return defHttp.post<void>({
url: '/api/notifications/send/template',
data: input,
});
}

19
apps/vue/src/api/realtime/notifications/model/index.ts

@ -0,0 +1,19 @@
import { NotificationReadState, NotificationSeverity } from "../types";
export interface GetNotificationPagedRequest extends PagedAndSortedResultRequestDto {
reverse?: boolean;
readState?: NotificationReadState;
}
export interface UserIdentifier {
userId: string;
userName?: string;
}
export interface NotificationSendDto {
name: string;
culture?: string;
toUsers?: UserIdentifier[];
severity?: NotificationSeverity;
data: Dictionary<string, any>;
}

23
apps/vue/src/api/realtime/notifications/subscribes/index.ts

@ -0,0 +1,23 @@
import { defHttp } from '/@/utils/http/axios';
import { UserSubscreNotificationListResult } from './model';
export const getAll = () => {
return defHttp.get<UserSubscreNotificationListResult>({
url: '/api/notifications/my-subscribes/all',
});
};
export const subscribe = (name: string) => {
return defHttp.post<void>({
url: '/api/notifications/my-subscribes',
data: {
name: name,
},
});
};
export const unSubscribe = (name: string) => {
return defHttp.delete<void>({
url: `/api/notifications/my-subscribes?name=${name}`,
});
};

14
apps/vue/src/api/realtime/notifications/subscribes/model/index.ts

@ -0,0 +1,14 @@
export interface UserSubscreNotification {
name: string;
}
export interface UserSubscriptionsResult {
isSubscribed: boolean;
}
export interface GetSubscriptionsPagedRequest extends PagedAndSortedResultRequestDto {}
export interface UserSubscreNotificationPagedResult
extends PagedResultDto<UserSubscreNotification> {}
export interface UserSubscreNotificationListResult extends ListResultDto<UserSubscreNotification> {}

60
apps/vue/src/api/realtime/notifications/types/index.ts

@ -0,0 +1,60 @@
export enum NotificationLifetime {
Persistent = 0,
OnlyOne = 1,
}
export enum NotificationType {
Application = 0,
System = 10,
User = 20,
ServiceCallback = 30,
}
export enum NotificationContentType {
Text = 0,
Html = 1,
Markdown = 2,
Json = 3,
}
export enum NotificationSeverity {
Success = 0,
Info = 10,
Warn = 20,
Error = 30,
Fatal = 40,
}
export enum NotificationReadState {
Read = 0,
UnRead = 1,
}
export interface NotificationData {
type: string;
extraProperties: { [key: string]: any };
}
export interface NotificationInfo {
// tenantId?: string;
name: string;
id: string;
data: NotificationData;
creationTime: Date;
lifetime: NotificationLifetime;
type: NotificationType;
severity: NotificationSeverity;
contentType: NotificationContentType;
}
export interface NotificationGroup {
name: string;
displayName: string;
notifications: {
name: string;
displayName: string;
description: string;
type: NotificationType;
lifetime: NotificationLifetime;
}[];
}

215
apps/vue/src/views/realtime/notifications/definitions/groups/components/GroupDefinitionModal.vue

@ -0,0 +1,215 @@
<template>
<BasicModal
@register="registerModal"
:title="L('GroupDefinitions')"
:can-fullscreen="false"
:width="800"
:min-height="400"
:close-func="handleBeforeClose"
@ok="handleSubmit"
>
<Form
ref="formRef"
:model="state.entity"
:rules="state.entityRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<Tabs v-model:active-key="state.activeTab">
<TabPane key="basic" :tab="L('BasicInfo')">
<FormItem name="name" :label="L('DisplayName:Name')">
<Input :disabled="state.entityEditFlag && !state.allowedChange" :allow-clear="true" v-model:value="state.entity.name" />
</FormItem>
<FormItem name="displayName" :label="L('DisplayName:DisplayName')">
<LocalizableInput :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.displayName" />
</FormItem>
<FormItem name="description" :label="L('DisplayName:Description')">
<LocalizableInput :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.description" />
</FormItem>
<FormItem
name="allowSubscriptionToClients"
:label="L('DisplayName:AllowSubscriptionToClients')"
:extra="L('Description:AllowSubscriptionToClients')"
>
<Checkbox
:disabled="!state.allowedChange"
v-model:checked="state.entity.allowSubscriptionToClients"
>{{ L('DisplayName:AllowSubscriptionToClients') }}
</Checkbox>
</FormItem>
</TabPane>
<TabPane key="propertites" :tab="L('Properties')">
<FormItem name="extraProperties" label="" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
<ExtraPropertyDictionary :disabled="!state.allowedChange" :allow-delete="true" :allow-edit="true" v-model:value="state.entity.extraProperties" />
</FormItem>
</TabPane>
</Tabs>
</Form>
</BasicModal>
</template>
<script setup lang="ts">
import type { Rule } from 'ant-design-vue/lib/form';
import { cloneDeep } from 'lodash-es';
import { ref, reactive, unref, nextTick, watch } from 'vue';
import { Checkbox, Form, Input, Tabs } from 'ant-design-vue';
import { ExtraPropertyDictionary } from '/@/components/Abp';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { LocalizableInput } from '/@/components/Abp';
import { useMessage } from '/@/hooks/web/useMessage';
import { ValidationEnum, useValidation } from '/@/hooks/abp/useValidation';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import {
GetAsyncByName,
CreateAsyncByInput,
UpdateAsyncByNameAndInput
} from '/@/api/realtime/notifications/definitions/groups';
import {
NotificationGroupDefinitionUpdateDto,
NotificationGroupDefinitionCreateDto
} from '/@/api/realtime/notifications/definitions/groups/model';
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
interface State {
activeTab: string,
allowedChange: boolean,
entity: Recordable,
entityRules?: Dictionary<string, Rule>,
entityChanged: boolean,
entityEditFlag: boolean,
}
const emits = defineEmits(['register', 'change']);
const { ruleCreator } = useValidation();
const { validate } = useLocalizationSerializer();
const { createConfirm, createMessage } = useMessage();
const { L } = useLocalization(['Notifications', 'AbpValidation', 'AbpUi']);
const formRef = ref<any>();
const state = reactive<State>({
activeTab: 'basic',
entity: {},
allowedChange: false,
entityChanged: false,
entityEditFlag: false,
entityRules: {
name: ruleCreator.fieldRequired({
name: 'Name',
prefix: 'DisplayName',
resourceName: 'Notifications',
trigger: 'blur',
}),
displayName: ruleCreator.defineValidator({
required: true,
trigger: 'blur',
validator(_rule, value) {
if (!validate(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:DisplayName')]));
}
return Promise.resolve();
},
}),
description: ruleCreator.defineValidator({
trigger: 'blur',
validator(_rule, value) {
if (!validate(value, { required: false })) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:Description')]));
}
return Promise.resolve();
},
}),
},
});
watch(
() => state.entity,
() => {
state.entityChanged = true;
},
{
deep: true,
},
);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner((record) => {
nextTick(() => fetch(record.name));
});
function fetch(name?: string) {
state.activeTab = 'basic';
state.entityEditFlag = false;
if (!name) {
state.entity = {
allowSubscriptionToClients: false,
};
state.allowedChange = true;
nextTick(() => state.entityChanged = false);
return;
}
changeLoading(true);
changeOkLoading(true);
GetAsyncByName(name).then((record) => {
state.entity = record;
state.entityEditFlag = true;
state.allowedChange = !record.isStatic;
}).finally(() => {
changeLoading(false);
changeOkLoading(false);
nextTick(() => state.entityChanged = false);
});
}
function handleBeforeClose(): Promise<boolean> {
return new Promise((resolve) => {
if (!state.entityChanged) {
const form = unref(formRef);
form?.resetFields();
return resolve(true);
}
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('AreYouSureYouWantToCancelEditingWarningMessage'),
onOk: () => {
const form = unref(formRef);
form?.resetFields();
resolve(true);
},
onCancel: () => {
resolve(false);
},
afterClose: () => {
state.allowedChange = false;
},
});
});
}
function handleSubmit() {
if (!state.allowedChange) {
closeModal();
return;
}
const form = unref(formRef);
form?.validate().then(() => {
changeOkLoading(true);
const api = state.entityEditFlag
? UpdateAsyncByNameAndInput(state.entity.name, cloneDeep(state.entity) as NotificationGroupDefinitionUpdateDto)
: CreateAsyncByInput(cloneDeep(state.entity) as NotificationGroupDefinitionCreateDto);
api.then((res) => {
createMessage.success(L('Successful'));
emits('change', res);
form.resetFields();
closeModal();
}).finally(() => {
changeOkLoading(false);
})
});
}
</script>
<style scoped>
</style>

149
apps/vue/src/views/realtime/notifications/definitions/groups/components/GroupDefinitionTable.vue

@ -0,0 +1,149 @@
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<Button
v-auth="['Notifications.GroupDefinitions.Create']"
type="primary"
@click="handleAddNew"
>
{{ L('GroupDefinitions:AddNew') }}
</Button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'displayName'">
<span>{{ getDisplayName(record.displayName) }}</span>
</template>
<template v-else-if="column.key === 'description'">
<span>{{ getDisplayName(record.description) }}</span>
</template>
<template v-else-if="column.key === 'action'">
<TableAction
:stop-button-propagation="true"
:actions="[
{
auth: 'Notifications.GroupDefinitions.Update',
label: L('Edit'),
icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record),
},
{
auth: 'Notifications.GroupDefinitions.Delete',
label: L('Delete'),
color: 'error',
icon: 'ant-design:delete-outlined',
ifShow: !record.isStatic,
onClick: handleDelete.bind(null, record),
},
]"
:dropDownActions="[
{
ifShow: !record.isStatic,
auth: 'Notifications.Definitions.Create',
label: L('NotificationDefinitions:AddNew'),
icon: 'ant-design:edit-outlined',
onClick: handleAddNotification.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
<GroupDefinitionModal @register="registerModal" />
<NotificationDefinitionModal @register="registerNotificationModal" />
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { Button } from 'ant-design-vue';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { getDataColumns } from '../datas/TableData';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { GetListAsyncByInput, DeleteAsyncByName } from '/@/api/realtime/notifications/definitions/groups';
import { getSearchFormSchemas } from '../datas/ModalData';
import GroupDefinitionModal from './GroupDefinitionModal.vue';
import NotificationDefinitionModal from '../../notifications/components/NotificationDefinitionModal.vue';
const { deserialize } = useLocalizationSerializer();
const { L, Lr } = useLocalization(['Notifications', 'AbpUi']);
const { createConfirm, createMessage } = useMessage();
const [registerModal, { openModal }] = useModal();
const [registerNotificationModal, { openModal: openNotificationModal }] = useModal();
const [registerTable, { setLoading, getForm, setTableData }] = useTable({
rowKey: 'name',
title: L('GroupDefinitions'),
columns: getDataColumns(),
pagination: true,
striped: false,
useSearchForm: true,
showIndexColumn: false,
showTableSetting: true,
tableSetting: {
redo: false,
},
formConfig: {
labelWidth: 100,
submitFunc: fetch,
schemas: getSearchFormSchemas(),
},
bordered: true,
canResize: true,
immediate: false,
actionColumn: {
width: 120,
title: L('Actions'),
dataIndex: 'action',
},
});
const getDisplayName = (displayName?: string) => {
if (!displayName) return displayName;
const info = deserialize(displayName);
return Lr(info.resourceName, info.name);
};
onMounted(fetch);
function fetch() {
const form = getForm();
return form.validate().then(() => {
setLoading(true);
setTableData([]);
var input = form.getFieldsValue();
GetListAsyncByInput(input).then((res) => {
setTableData(res.items);
}).finally(() => {
setLoading(false);
});
});
}
function handleAddNew() {
openModal(true, {});
}
function handleEdit(record) {
openModal(true, record);
}
function handleAddNotification(record) {
openNotificationModal(true, { groupName: record.name });
}
function handleDelete(record) {
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('ItemWillBeDeleteOrRestoreMessage'),
onOk: () => {
return DeleteAsyncByName(record.name).then(() => {
createMessage.success(L('Successful'));
fetch();
});
},
});
}
</script>

15
apps/vue/src/views/realtime/notifications/definitions/groups/datas/ModalData.ts

@ -0,0 +1,15 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { FormSchema } from '/@/components/Form';
const { L } = useLocalization(['AbpUi']);
export function getSearchFormSchemas():FormSchema[] {
return [
{
field: 'filter',
component: 'Input',
label: L('Search'),
colProps: { span: 24 },
},
];
}

43
apps/vue/src/views/realtime/notifications/definitions/groups/datas/TableData.ts

@ -0,0 +1,43 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicColumn } from '/@/components/Table';
import { sorter } from '/@/utils/table';
const { L } = useLocalization(['Notifications']);
export function getDataColumns(): BasicColumn[] {
return [
{
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 350,
resizable: true,
sorter: (a, b) => sorter(a, b, 'name'),
},
{
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},
{
title: L('DisplayName:Description'),
dataIndex: 'description',
align: 'left',
width: 280,
resizable: true,
sorter: (a, b) => sorter(a, b, 'description'),
},
{
title: L('DisplayName:IsStatic'),
dataIndex: 'isStatic',
align: 'center',
width: 150,
resizable: true,
defaultHidden: true,
sorter: (a, b) => sorter(a, b, 'isStatic'),
},
];
}

16
apps/vue/src/views/realtime/notifications/definitions/groups/index.vue

@ -0,0 +1,16 @@
<template>
<GroupDefinitionTable />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import GroupDefinitionTable from './components/GroupDefinitionTable.vue';
export default defineComponent({
name: 'NotificationsGroupDefinitions',
components: {
GroupDefinitionTable,
},
setup() {},
});
</script>

338
apps/vue/src/views/realtime/notifications/definitions/notifications/components/NotificationDefinitionModal.vue

@ -0,0 +1,338 @@
<template>
<BasicModal
@register="registerModal"
:title="L('NotificationDefinitions')"
:can-fullscreen="false"
:width="800"
:height="500"
:close-func="handleBeforeClose"
@ok="handleSubmit"
>
<Form
ref="formRef"
:model="state.entity"
:rules="state.entityRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<Tabs v-model:active-key="state.activeTab">
<TabPane key="basic" :tab="L('BasicInfo')">
<FormItem name="groupName" :label="L('DisplayName:GroupName')">
<Select
:disabled="!state.allowedChange"
:allow-clear="true"
v-model:value="state.entity.groupName"
:options="getGroupOptions"
/>
</FormItem>
<FormItem name="name" :label="L('DisplayName:Name')">
<Input :disabled="state.entityEditFlag && !state.allowedChange" :allow-clear="true" v-model:value="state.entity.name" />
</FormItem>
<FormItem name="displayName" :label="L('DisplayName:DisplayName')">
<LocalizableInput :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.displayName" />
</FormItem>
<FormItem name="description" :label="L('DisplayName:Description')">
<LocalizableInput :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.description" />
</FormItem>
<FormItem
name="allowSubscriptionToClients"
:label="L('DisplayName:AllowSubscriptionToClients')"
:extra="L('Description:AllowSubscriptionToClients')"
>
<Checkbox
:disabled="!state.allowedChange"
v-model:checked="state.entity.allowSubscriptionToClients"
>{{ L('DisplayName:AllowSubscriptionToClients') }}
</Checkbox>
</FormItem>
<FormItem
name="notificationType"
:label="L('DisplayName:NotificationType')"
:extra="L('Description:NotificationType')"
>
<Select
:disabled="!state.allowedChange"
:allow-clear="true"
v-model:value="state.entity.notificationType"
:options="notificationTypeOptions"
/>
</FormItem>
<FormItem
name="notificationLifetime"
:label="L('DisplayName:NotificationLifetime')"
:extra="L('Description:NotificationLifetime')"
>
<Select
:disabled="!state.allowedChange"
:allow-clear="true"
v-model:value="state.entity.notificationLifetime"
:options="notificationLifetimeOptions"
/>
</FormItem>
<FormItem
name="contentType"
:label="L('DisplayName:ContentType')"
:extra="L('Description:ContentType')"
>
<Select
:disabled="!state.allowedChange"
:allow-clear="true"
v-model:value="state.entity.contentType"
:options="notificationContentTypeOptions"
/>
</FormItem>
<FormItem
name="providers"
:label="L('DisplayName:Providers')"
:extra="L('Description:Providers')"
>
<Select
:disabled="!state.allowedChange"
:allow-clear="true"
mode="tags"
v-model:value="state.entity.providers"
:options="notificationPushProviderOptions"
/>
</FormItem>
<FormItem
name="template"
:label="L('DisplayName:Template')"
:extra="L('Description:Template')"
>
<Select
:disabled="!state.allowedChange"
:allow-clear="true"
v-model:value="state.entity.template"
:options="getAvailableTemplateOptions"
/>
</FormItem>
</TabPane>
<TabPane key="propertites" :tab="L('Properties')">
<FormItem name="extraProperties" label="" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
<ExtraPropertyDictionary :disabled="!state.allowedChange" :allow-delete="true" :allow-edit="true" v-model:value="state.entity.extraProperties" />
</FormItem>
</TabPane>
</Tabs>
</Form>
</BasicModal>
</template>
<script setup lang="ts">
import type { Rule } from 'ant-design-vue/lib/form';
import { cloneDeep } from 'lodash-es';
import { computed, ref, reactive, unref, nextTick, watch } from 'vue';
import { Checkbox, Form, Input, Select, Tabs } from 'ant-design-vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { LocalizableInput, ExtraPropertyDictionary } from '/@/components/Abp';
import { useMessage } from '/@/hooks/web/useMessage';
import { ValidationEnum, useValidation } from '/@/hooks/abp/useValidation';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { useNotificationDefinition } from '../hooks/useNotificationDefinition';
import {
GetAsyncByName,
CreateAsyncByInput,
UpdateAsyncByNameAndInput
} from '/@/api/realtime/notifications/definitions/notifications';
import {
NotificationDefinitionUpdateDto,
NotificationDefinitionCreateDto
} from '/@/api/realtime/notifications/definitions/notifications/model';
import { NotificationGroupDefinitionDto } from '/@/api/realtime/notifications/definitions/groups/model';
import { GetListAsyncByInput as getGroupDefinitions } from '/@/api/realtime/notifications/definitions/groups';
import { NotificationContentType, NotificationLifetime, NotificationType } from '/@/api/realtime/notifications/types';
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
interface State {
activeTab: string,
allowedChange: boolean,
entity: Recordable,
entityRules?: Dictionary<string, Rule>,
entityChanged: boolean,
entityEditFlag: boolean,
defaultGroup?: string,
availableGroups: NotificationGroupDefinitionDto[],
}
const emits = defineEmits(['register', 'change']);
const { ruleCreator } = useValidation();
const { validate } = useLocalizationSerializer();
const { createConfirm, createMessage } = useMessage();
const { L } = useLocalization(['Notifications', 'AbpValidation', 'AbpUi']);
const {
formatDisplayName,
notificationTypeOptions,
notificationLifetimeOptions,
notificationContentTypeOptions,
notificationPushProviderOptions,
getAvailableTemplateOptions,
} = useNotificationDefinition();
const formRef = ref<any>();
const state = reactive<State>({
activeTab: 'basic',
entity: {},
allowedChange: false,
entityChanged: false,
entityEditFlag: false,
availableGroups: [],
entityRules: {
groupName: ruleCreator.fieldRequired({
name: 'GroupName',
prefix: 'DisplayName',
resourceName: 'Notifications',
trigger: 'change',
}),
name: ruleCreator.fieldRequired({
name: 'Name',
prefix: 'DisplayName',
resourceName: 'Notifications',
trigger: 'blur',
}),
displayName: ruleCreator.defineValidator({
required: true,
trigger: 'blur',
validator(_rule, value) {
if (!validate(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:DisplayName')]));
}
return Promise.resolve();
},
}),
description: ruleCreator.defineValidator({
trigger: 'blur',
validator(_rule, value) {
if (!validate(value, { required: false })) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:Description')]));
}
return Promise.resolve();
},
}),
},
});
const getGroupOptions = computed(() => {
return state.availableGroups
.filter((group) => !group.isStatic)
.map((group) => {
return {
label: group.displayName,
value: group.name,
};
});
});
watch(
() => state.entity,
() => {
state.entityChanged = true;
},
{
deep: true,
},
);
watch(
() => state.defaultGroup,
fetchGroups,
{
deep: true,
},
);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner((record) => {
state.defaultGroup = record.groupName;
nextTick(() => {
fetch(record.name);
});
});
function fetch(name?: string) {
state.activeTab = 'basic';
state.entityEditFlag = false;
if (!name) {
state.entity = {
groupName: state.defaultGroup,
allowSubscriptionToClients: false,
notificationLifetime: NotificationLifetime.OnlyOne,
notificationType: NotificationType.User,
contentType: NotificationContentType.Text,
};
state.allowedChange = true;
nextTick(() => state.entityChanged = false);
return;
}
changeLoading(true);
changeOkLoading(true);
GetAsyncByName(name).then((record) => {
state.entity = record;
state.entityEditFlag = true;
state.allowedChange = !record.isStatic;
}).finally(() => {
changeLoading(false);
changeOkLoading(false);
nextTick(() => state.entityChanged = false);
});
}
function fetchGroups() {
getGroupDefinitions({
filter: state.defaultGroup,
}).then((res) => {
formatDisplayName(res.items);
state.availableGroups = res.items;
});
}
function handleBeforeClose(): Promise<boolean> {
return new Promise((resolve) => {
if (!state.entityChanged) {
const form = unref(formRef);
form?.resetFields();
return resolve(true);
}
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('AreYouSureYouWantToCancelEditingWarningMessage'),
onOk: () => {
const form = unref(formRef);
form?.resetFields();
resolve(true);
},
onCancel: () => {
resolve(false);
},
afterClose: () => {
state.allowedChange = false;
state.defaultGroup = undefined;
},
});
});
}
function handleSubmit() {
if (!state.allowedChange) {
closeModal();
return;
}
const form = unref(formRef);
form?.validate().then(() => {
changeOkLoading(true);
const api = state.entityEditFlag
? UpdateAsyncByNameAndInput(state.entity.name, cloneDeep(state.entity) as NotificationDefinitionUpdateDto)
: CreateAsyncByInput(cloneDeep(state.entity) as NotificationDefinitionCreateDto);
api.then((res) => {
createMessage.success(L('Successful'));
emits('change', res);
form.resetFields();
closeModal();
}).finally(() => {
changeOkLoading(false);
})
});
}
</script>
<style scoped>
</style>

288
apps/vue/src/views/realtime/notifications/definitions/notifications/components/NotificationDefinitionTable.vue

@ -0,0 +1,288 @@
<template>
<div>
<BasicTable @register="registerTable">
<template #form-groupName="{ model, field }">
<FormItem name="groupName">
<Select v-model:value="model[field]" :options="getGroupOptions" />
</FormItem>
</template>
<template #form-notificationType="{ model, field }">
<FormItem name="notificationType">
<Select v-model:value="model[field]" :options="notificationTypeOptions" />
</FormItem>
</template>
<template #form-notificationLifetime="{ model, field }">
<FormItem name="notificationLifetime">
<Select v-model:value="model[field]" :options="notificationLifetimeOptions" />
</FormItem>
</template>
<template #form-contentType="{ model, field }">
<FormItem name="contentType">
<Select v-model:value="model[field]" :options="notificationContentTypeOptions" />
</FormItem>
</template>
<template #form-template="{ model, field }">
<FormItem name="template">
<Select v-model:value="model[field]" :options="getAvailableTemplateOptions" />
</FormItem>
</template>
<template #toolbar>
<Button
v-auth="['Notifications.Definitions.Create']"
type="primary"
@click="handleAddNew"
>
{{ L('NotificationDefinitions:AddNew') }}
</Button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'displayName'">
<span>{{ getGroupDisplayName(record.displayName) }}</span>
</template>
</template>
<template #expandedRowRender="{ record }">
<BasicTable @register="registerSubTable" :data-source="record.notifications">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'displayName'">
<span>{{ getDisplayName(record.displayName) }}</span>
</template>
<template v-else-if="column.key === 'description'">
<span>{{ getDisplayName(record.description) }}</span>
</template>
<template v-else-if="column.key === 'allowSubscriptionToClients'">
<CheckOutlined v-if="record.allowSubscriptionToClients" class="enable" />
<CloseOutlined v-else class="disable" />
</template>
<template v-else-if="column.key === 'notificationLifetime'">
<span>{{ notificationLifetimeMap[record.notificationLifetime] }}</span>
</template>
<template v-else-if="column.key === 'notificationType'">
<span>{{ notificationTypeMap[record.notificationType] }}</span>
</template>
<template v-else-if="column.key === 'contentType'">
<span>{{ notificationContentTypeMap[record.contentType] }}</span>
</template>
<template v-else-if="column.key === 'providers'">
<Tag v-for="provider in record.providers" color="blue" style="margin-right: 5px;">{{ provider }}</Tag>
</template>
<template v-else-if="column.key === 'isStatic'">
<CheckOutlined v-if="record.isStatic" class="enable" />
<CloseOutlined v-else class="disable" />
</template>
<template v-else-if="column.key === 'action'">
<TableAction
:stop-button-propagation="true"
:actions="[
{
auth: 'Notifications.Definitions.Update',
label: L('Edit'),
icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record),
},
{
auth: 'Notifications.Definitions.Delete',
label: L('Delete'),
color: 'error',
icon: 'ant-design:delete-outlined',
ifShow: !record.isStatic,
onClick: handleDelete.bind(null, record),
},
]"
:dropDownActions="[
{
label: L('Notifications:Send'),
icon: 'ant-design:edit-outlined',
onClick: handleSendNotification.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
</template>
</BasicTable>
<NotificationDefinitionModal @register="registerModal" @change="fetch" />
<NotificationSendModal @register="registerSendModal" />
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, onMounted } from 'vue';
import { Button, Form, Select, Tag } from 'ant-design-vue';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { getDataColumns } from '../datas/TableData';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { useNotificationDefinition } from '../hooks/useNotificationDefinition';
import { GetListAsyncByInput as getGroupDefinitions } from '/@/api/realtime/notifications/definitions/groups';
import { NotificationGroupDefinitionDto } from '/@/api/realtime/notifications/definitions/groups/model';
import { GetListAsyncByInput, DeleteAsyncByName } from '/@/api/realtime/notifications/definitions/notifications';
import { NotificationDefinitionDto } from '/@/api/realtime/notifications/definitions/notifications/model';
import { getSearchFormSchemas } from '../datas/ModalData';
import { groupBy } from '/@/utils/array';
import { sorter } from '/@/utils/table';
import NotificationDefinitionModal from './NotificationDefinitionModal.vue';
import NotificationSendModal from './NotificationSendModal.vue';
const FormItem = Form.Item;
interface NotificationGroup {
name: string;
displayName: string;
notifications: NotificationDefinitionDto[];
}
interface State {
groups: NotificationGroupDefinitionDto[],
}
const { deserialize } = useLocalizationSerializer();
const { L, Lr } = useLocalization(['Notifications', 'AbpUi']);
const {
notificationTypeMap,
notificationTypeOptions,
notificationLifetimeMap,
notificationLifetimeOptions,
notificationContentTypeMap,
notificationContentTypeOptions,
getAvailableTemplateOptions,
} = useNotificationDefinition();
const { createConfirm, createMessage } = useMessage();
const [registerModal, { openModal }] = useModal();
const [registerSendModal, { openModal: openSendModal }] = useModal();
const [registerTable, { setLoading, getForm, setTableData }] = useTable({
rowKey: 'name',
title: L('NotificationDefinitions'),
columns: [
{
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},
],
pagination: true,
striped: false,
useSearchForm: true,
showIndexColumn: false,
showTableSetting: true,
tableSetting: {
redo: false,
},
formConfig: {
labelWidth: 100,
submitFunc: fetch,
schemas: getSearchFormSchemas(),
},
bordered: true,
canResize: true,
immediate: false,
});
const [registerSubTable] = useTable({
rowKey: 'name',
columns: getDataColumns(),
pagination: false,
striped: false,
useSearchForm: false,
showIndexColumn: true,
immediate: false,
scroll: { x: 2000, y: 900 },
actionColumn: {
width: 180,
title: L('Actions'),
dataIndex: 'action',
},
});
const state = reactive<State>({
groups: [],
});
const getGroupOptions = computed(() => {
return state.groups.map((group) => {
const info = deserialize(group.displayName);
return {
label: Lr(info.resourceName, info.name),
value: group.name,
};
});
});
const getGroupDisplayName = computed(() => {
return (groupName: string) => {
const group = state.groups.find(x => x.name === groupName);
if (!group) return groupName;
const info = deserialize(group.displayName);
return Lr(info.resourceName, info.name);
};
});
const getDisplayName = computed(() => {
return (displayName?: string) => {
if (!displayName) return displayName;
const info = deserialize(displayName);
return Lr(info.resourceName, info.name);
};
});
onMounted(() => {
fetch();
fetchGroups();
});
function fetch() {
const form = getForm();
return form.validate().then(() => {
setLoading(true);
setTableData([]);
var input = form.getFieldsValue();
GetListAsyncByInput(input).then((res) => {
const definitionGroup = groupBy(res.items, 'groupName');
const definitionGroupData: NotificationGroup[] = [];
Object.keys(definitionGroup).forEach((gk) => {
const groupData: NotificationGroup = {
name: gk,
displayName: gk,
notifications: [],
};
groupData.notifications.push(...definitionGroup[gk]);
definitionGroupData.push(groupData);
});
setTableData(definitionGroupData);
}).finally(() => {
setLoading(false);
});
});
}
function fetchGroups() {
getGroupDefinitions({}).then((res) => {
state.groups = res.items;
});
}
function handleAddNew() {
openModal(true, {});
}
function handleEdit(record: NotificationDefinitionDto) {
openModal(true, record);
}
function handleDelete(record: NotificationDefinitionDto) {
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('ItemWillBeDeleteOrRestoreMessage'),
onOk: () => {
return DeleteAsyncByName(record.name).then(() => {
createMessage.success(L('Successful'));
fetch();
});
},
});
}
function handleSendNotification(record: NotificationDefinitionDto) {
openSendModal(true, record);
}
</script>

307
apps/vue/src/views/realtime/notifications/definitions/notifications/components/NotificationSendModal.vue

@ -0,0 +1,307 @@
<template>
<BasicModal
@register="registerModal"
:title="L('Notifications:Send')"
:can-fullscreen="false"
:width="800"
:height="500"
@ok="handleSubmit"
>
<Form
v-if="state.notification"
ref="formRef"
:model="state.entity"
:rules="state.entityRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<Tabs v-model:active-key="state.activeTab">
<TabPane key="basic" :tab="L('BasicInfo')">
<FormItem name="name" :label="L('DisplayName:Name')">
<Input disabled :value="getDisplayName(state.notification.displayName)" />
</FormItem>
<!-- <FormItem v-if="!getIsTemplate" name="useLocalization" :label="L('Notifications:UseLocalization')">
<Checkbox v-model:checked="state.entity.useLocalization" @change="(e) => handleUseLocalization(e.target.checked)">
{{ L('Notifications:UseLocalization') }}
</Checkbox>
</FormItem> -->
<FormItem v-if="!getIsTemplate" name="title" :label="L('Notifications:Title')">
<TextArea v-model:value="state.entity.title" />
</FormItem>
<FormItem v-if="!getIsTemplate" name="message" :label="L('Notifications:Message')">
<TextArea v-model:value="state.entity.message" />
</FormItem>
<FormItem v-if="!getIsTemplate" name="description" :label="L('Notifications:Description')">
<TextArea v-model:value="state.entity.description" />
</FormItem>
<FormItem name="culture" :label="L('Notifications:Culture')">
<Select allow-clear v-model:value="state.entity.culture" :options="getLanguageOptions" @change="handleCultureChange" />
</FormItem>
<FormItem name="toUsers" :label="L('Notifications:ToUsers')">
<Select
mode="tags"
show-search
allow-clear
:default-active-first-option="false"
:filter-option="false"
:not-found-content="null"
v-model:value="state.entity.toUsers"
:options="getUserOptions"
/>
</FormItem>
<FormItem name="severity" :label="L('Notifications:Severity')">
<Select allow-clear v-model:value="state.entity.severity" :options="notificationSeverityOptions" />
</FormItem>
</TabPane>
<TabPane key="propertites" :tab="L('Properties')">
<FormItem name="data" label="" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
<ExtraPropertyDictionary :allow-delete="true" :allow-edit="true" v-model:value="state.entity.data" />
</FormItem>
</TabPane>
<TabPane v-if="getIsTemplate && state.templateContent" key="content" :tab="L('TemplateContent')">
<TextArea readonly :auto-size="{ minRows: 15, maxRows: 50 }" :value="state.templateContent.content" />
</TabPane>
</Tabs>
</Form>
</BasicModal>
</template>
<script setup lang="ts">
import type { Rule } from 'ant-design-vue/lib/form';
import { computed, reactive, ref, unref, onMounted } from 'vue';
import { Form, Input, Select, Tabs } from 'ant-design-vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ExtraPropertyDictionary } from '/@/components/Abp';
import { useAbpStoreWithOut } from '/@/store/modules/abp';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { ValidationEnum, useValidation } from '/@/hooks/abp/useValidation';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { getList as getLanguages } from '/@/api/localization/languages';
import { Language } from '/@/api/localization/model/languagesModel';
import { getList as getUsers } from '/@/api/identity/user';
import { User } from '/@/api/identity/model/userModel';
import { send, sendTemplate } from '/@/api/realtime/notifications';
import { NotificationSendDto } from '/@/api/realtime/notifications/model';
import { NotificationDefinitionDto } from '/@/api/realtime/notifications/definitions/notifications/model';
import { GetAsyncByInput as getTemplateContent } from '/@/api/text-templating/contents';
import { TextTemplateContentDto } from '/@/api/text-templating/contents/model';
import { isNullOrWhiteSpace } from '/@/utils/strings';
import { useNotificationDefinition } from '../hooks/useNotificationDefinition';
import { formatToDateTime } from '/@/utils/dateUtil';
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
const TextArea = Input.TextArea;
interface State {
activeTab: string,
entity: Recordable;
entityRules?: Dictionary<string, Rule>;
notification?: NotificationDefinitionDto;
templateContent?: TextTemplateContentDto;
languages: Language[];
users: User[];
}
const formRef = ref<any>();
const { getApplication } = useAbpStoreWithOut();
const { ruleCreator } = useValidation();
const { deserialize, validate } = useLocalizationSerializer();
const { createMessage } = useMessage();
const { L, Lr } = useLocalization(['Notifications', 'AbpValidation', 'AbpUi']);
const { notificationSeverityOptions } = useNotificationDefinition();
const state = reactive<State>({
activeTab: 'basic',
entity: {},
entityRules: {
title: ruleCreator.defineValidator({
required: true,
trigger: 'blur',
validator(_rule, value) {
if (state.entity.useLocalization) {
if (!validate(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('Notifications:Title')]));
}
} else {
if (isNullOrWhiteSpace(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('Notifications:Title')]));
}
}
return Promise.resolve();
},
}),
message: ruleCreator.defineValidator({
trigger: 'blur',
required: true,
validator(_rule, value) {
if (state.entity.useLocalization) {
if (!validate(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('Notifications:Message')]));
}
} else {
if (isNullOrWhiteSpace(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('Notifications:Message')]));
}
}
return Promise.resolve();
},
}),
description: ruleCreator.defineValidator({
trigger: 'blur',
validator(_rule, value) {
if (state.entity.useLocalization) {
if (!validate(value, { required: false })) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('Notifications:Description')]));
}
}
return Promise.resolve();
},
}),
},
notification: undefined,
languages: [],
users: [],
});
const [registerModal, { changeOkLoading, closeModal }] = useModalInner((data: NotificationDefinitionDto) => {
state.activeTab = 'basic';
state.entity = {
data: {} as Recordable,
};
state.notification = data;
state.templateContent = undefined;
if (!isNullOrWhiteSpace(data.template)) {
fetchTemplateContent(data.template!);
}
});
const getIsTemplate = computed(() => {
if (!state.notification) return false;
return !isNullOrWhiteSpace(state.notification.template);
});
const getUserOptions = computed(() => {
return state.users.map((item) => {
return {
label: item.userName,
value: item.id,
};
});
});
const getLanguageOptions = computed(() => {
return state.languages.map((item) => {
return {
label: item.displayName,
value: item.cultureName,
};
});
});
onMounted(() => {
fetchUsers();
fetchLanguages();
});
const getDisplayName = (displayName?: string) => {
if (!displayName) return displayName;
const info = deserialize(displayName);
return Lr(info.resourceName, info.name);
};
function fetchLanguages() {
getLanguages({}).then((res) => {
state.languages = res.items;
});
}
function fetchUsers(filter?: string) {
getUsers({
filter: filter,
}).then((res) => {
state.users = res.items;
})
}
function fetchTemplateContent(template: string) {
getTemplateContent({
name: template,
culture: state.entity.culture,
}).then((content) => {
state.templateContent = content;
});
}
function handleCultureChange() {
if (!isNullOrWhiteSpace(state.notification?.template)) {
fetchTemplateContent(state.notification!.template!);
}
}
function handleSubmit() {
if (!state.notification) return;
const formEl = unref(formRef);
formEl?.validate().then(() => {
let input: NotificationSendDto;
if (getIsTemplate.value) {
input = {
name: state.notification!.template!,
severity: state.entity.severity,
culture: state.entity.culture,
data: state.entity.data,
};
changeOkLoading(true);
sendTemplate(input).then(() => {
createMessage.success(L('Successful'));
closeModal();
}).finally(() => {
changeOkLoading(false);
});
} else {
let title: any = state.entity.title;
let message: any = state.entity.message;
let description: any = state.entity.description;
// if (state.entity.useLocalization) {
// const titleInfo = deserialize(title);
// title = {
// resourceName: titleInfo.resourceName,
// name: titleInfo.name,
// };
// }
// if (state.entity.useLocalization) {
// const messageInfo = deserialize(message);
// message = {
// resourceName: messageInfo.resourceName,
// name: messageInfo.name,
// };
// }
// if (state.entity.useLocalization && !isNullOrUnDef(description)) {
// const descriptionInfo = deserialize(description);
// description = {
// resourceName: descriptionInfo.resourceName,
// name: descriptionInfo.name,
// };
// }
input = {
name: state.notification!.name,
severity: state.entity.severity,
culture: state.entity.culture,
data: {
...state.entity.data,
title: title,
message: message,
description: description,
formUser: getApplication.currentUser.userName,
createTime: formatToDateTime(new Date()),
},
};
changeOkLoading(true);
send(input).then(() => {
createMessage.success(L('Successful'));
closeModal();
}).finally(() => {
changeOkLoading(false);
});
}
});
}
</script>
<style scoped>
</style>

50
apps/vue/src/views/realtime/notifications/definitions/notifications/datas/ModalData.ts

@ -0,0 +1,50 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { FormSchema } from '/@/components/Form';
const { L } = useLocalization(['Notifications', 'AbpUi']);
export function getSearchFormSchemas():FormSchema[] {
return [
{
field: 'groupName',
component: 'Select',
label: L('DisplayName:GroupName'),
colProps: { span: 6 },
slot: 'groupName',
},
{
field: 'notificationType',
component: 'Select',
label: L('DisplayName:NotificationType'),
colProps: { span: 6 },
slot: 'notificationType',
},
{
field: 'template',
component: 'Select',
label: L('DisplayName:Template'),
colProps: { span: 12 },
slot: 'template',
},
{
field: 'notificationLifetime',
component: 'Select',
label: L('DisplayName:NotificationLifetime'),
colProps: { span: 6 },
slot: 'notificationLifetime',
},
{
field: 'contentType',
component: 'Select',
label: L('DisplayName:ContentType'),
colProps: { span: 6 },
slot: 'contentType',
},
{
field: 'filter',
component: 'Input',
label: L('Search'),
colProps: { span: 12 },
},
];
}

91
apps/vue/src/views/realtime/notifications/definitions/notifications/datas/TableData.ts

@ -0,0 +1,91 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicColumn } from '/@/components/Table';
import { sorter } from '/@/utils/table';
const { L } = useLocalization(['Notifications']);
export function getDataColumns(): BasicColumn[] {
return [
{
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 350,
resizable: true,
sorter: (a, b) => sorter(a, b, 'name'),
},
{
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},
{
title: L('DisplayName:Description'),
dataIndex: 'description',
align: 'left',
width: 250,
resizable: true,
sorter: (a, b) => sorter(a, b, 'description'),
},
{
title: L('DisplayName:AllowSubscriptionToClients'),
dataIndex: 'allowSubscriptionToClients',
align: 'center',
width: 150,
resizable: true,
sorter: (a, b) => sorter(a, b, 'allowSubscriptionToClients'),
},
{
title: L('DisplayName:Template'),
dataIndex: 'template',
align: 'center',
width: 150,
resizable: true,
sorter: (a, b) => sorter(a, b, 'template'),
},
{
title: L('DisplayName:NotificationLifetime'),
dataIndex: 'notificationLifetime',
align: 'center',
width: 150,
resizable: true,
sorter: (a, b) => sorter(a, b, 'notificationLifetime'),
},
{
title: L('DisplayName:NotificationType'),
dataIndex: 'notificationType',
align: 'center',
width: 150,
resizable: true,
sorter: (a, b) => sorter(a, b, 'notificationType'),
},
{
title: L('DisplayName:ContentType'),
dataIndex: 'contentType',
align: 'center',
width: 150,
resizable: true,
sorter: (a, b) => sorter(a, b, 'contentType'),
},
{
title: L('DisplayName:Providers'),
dataIndex: 'providers',
align: 'center',
width: 150,
resizable: true,
sorter: (a, b) => sorter(a, b, 'providers'),
},
{
title: L('DisplayName:IsStatic'),
dataIndex: 'isStatic',
align: 'center',
width: 150,
resizable: true,
defaultHidden: true,
sorter: (a, b) => sorter(a, b, 'isStatic'),
},
];
}

115
apps/vue/src/views/realtime/notifications/definitions/notifications/hooks/useNotificationDefinition.ts

@ -0,0 +1,115 @@
import { computed, ref, unref, onMounted } from 'vue';
import {
NotificationLifetime,
NotificationType,
NotificationContentType,
NotificationSeverity,
} from '/@/api/realtime/notifications/types';
import { GetListAsyncByInput as getAvailableTemplates } from '/@/api/text-templating/definitions';
import { TextTemplateDefinitionDto } from '/@/api/text-templating/definitions/model';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { useLocalization } from '/@/hooks/abp/useLocalization';
export function useNotificationDefinition() {
const availableTemplates = ref<TextTemplateDefinitionDto[]>([]);
const { L, Lr } = useLocalization(['Notifications']);
const { deserialize } = useLocalizationSerializer();
const getAvailableTemplateOptions = computed(() => {
const templates = unref(availableTemplates);
return templates.map((item) => {
return {
label: item.displayName,
value: item.name,
};
});
});
onMounted(fetchTemplates);
function fetchTemplates() {
getAvailableTemplates({}).then((res) => {
formatDisplayName(res.items);
availableTemplates.value = res.items;
});
}
function formatDisplayName(list: any[]) {
if (list && Array.isArray(list)) {
list.forEach((item) => {
if (Reflect.has(item, 'displayName')) {
const info = deserialize(item.displayName);
item.displayName = Lr(info.resourceName, info.name);
}
if (Reflect.has(item, 'children')) {
formatDisplayName(item.children);
}
});
}
}
const notificationLifetimeMap = {
[NotificationLifetime.OnlyOne]: L('NotificationLifetime:OnlyOne'),
[NotificationLifetime.Persistent]: L('NotificationLifetime:Persistent'),
};
const notificationLifetimeOptions = [
{ label: L('NotificationLifetime:OnlyOne'), value: NotificationLifetime.OnlyOne },
{ label: L('NotificationLifetime:Persistent'), value: NotificationLifetime.Persistent },
];
const notificationTypeMap = {
[NotificationType.User]: L('NotificationType:User'),
[NotificationType.System]: L('NotificationType:System'),
[NotificationType.Application]: L('NotificationType:Application'),
[NotificationType.ServiceCallback]: L('NotificationType:ServiceCallback'),
};
const notificationTypeOptions = [
{ label: L('NotificationType:User'), value: NotificationType.User },
{ label: L('NotificationType:System'), value: NotificationType.System },
{ label: L('NotificationType:Application'), value: NotificationType.Application },
{ label: L('NotificationType:ServiceCallback'), value: NotificationType.ServiceCallback },
];
const notificationContentTypeMap = {
[NotificationContentType.Text]: L('NotificationContentType:Text'),
[NotificationContentType.Json]: L('NotificationContentType:Json'),
[NotificationContentType.Html]: L('NotificationContentType:Html'),
[NotificationContentType.Markdown]: L('NotificationContentType:Markdown'),
};
const notificationContentTypeOptions = [
{ label: L('NotificationContentType:Text'), value: NotificationContentType.Text },
{ label: L('NotificationContentType:Json'), value: NotificationContentType.Json },
{ label: L('NotificationContentType:Html'), value: NotificationContentType.Html },
{ label: L('NotificationContentType:Markdown'), value: NotificationContentType.Markdown },
];
const notificationSeverityOptions = [
{ label: L('NotificationSeverity:Success'), value: NotificationSeverity.Success },
{ label: L('NotificationSeverity:Info'), value: NotificationSeverity.Info },
{ label: L('NotificationSeverity:Warn'), value: NotificationSeverity.Warn },
{ label: L('NotificationSeverity:Fatal'), value: NotificationSeverity.Fatal },
{ label: L('NotificationSeverity:Error'), value: NotificationSeverity.Error },
];
const notificationPushProviderOptions = [
{ label: L('Providers:Emailing'), value: 'Emailing' },
{ label: L('Providers:SignalR'), value: 'SignalR' },
{ label: L('Providers:WeChat.MiniProgram'), value: 'WeChat.MiniProgram' },
{ label: L('Providers:WeChat.Work'), value: 'WeChat.Work' },
{ label: L('Providers:Sms'), value: 'Sms' },
{ label: L('Providers:WxPusher'), value: 'WxPusher' },
];
return {
formatDisplayName,
getAvailableTemplateOptions,
notificationTypeMap,
notificationLifetimeOptions,
notificationLifetimeMap,
notificationTypeOptions,
notificationContentTypeMap,
notificationContentTypeOptions,
notificationPushProviderOptions,
notificationSeverityOptions,
}
}

16
apps/vue/src/views/realtime/notifications/definitions/notifications/index.vue

@ -0,0 +1,16 @@
<template>
<NotificationDefinitionTable />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import NotificationDefinitionTable from './components/NotificationDefinitionTable.vue';
export default defineComponent({
name: 'NotificationsDefinitions',
components: {
NotificationDefinitionTable,
},
setup() {},
});
</script>

5314
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/Migrations/20231026015443_Add-Field-With-Notification-Definition.Designer.cs

File diff suppressed because it is too large

29
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/Migrations/20231026015443_Add-Field-With-Notification-Definition.cs

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LY.MicroService.Applications.Single.EntityFrameworkCore.Migrations
{
/// <inheritdoc />
public partial class AddFieldWithNotificationDefinition : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Template",
table: "AppNotificationDefinitions",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Template",
table: "AppNotificationDefinitions");
}
}
}

3
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/Migrations/SingleMigrationsDbContextModelSnapshot.cs

@ -797,6 +797,9 @@ namespace LY.MicroService.Applications.Single.EntityFrameworkCore.Migrations
.HasMaxLength(200) .HasMaxLength(200)
.HasColumnType("varchar(200)"); .HasColumnType("varchar(200)");
b.Property<string>("Template")
.HasColumnType("longtext");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("AppNotificationDefinitions", (string)null); b.ToTable("AppNotificationDefinitions", (string)null);

11
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionCreateDto.cs

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.Notifications.Definitions.Groups;
public class NotificationGroupDefinitionCreateDto : NotificationGroupDefinitionCreateOrUpdateDto
{
[Required]
[DynamicStringLength(typeof(NotificationDefinitionGroupRecordConsts), nameof(NotificationDefinitionGroupRecordConsts.MaxNameLength))]
public string Name { get; set; }
}

19
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionCreateOrUpdateDto.cs

@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Data;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.Notifications.Definitions.Groups;
public abstract class NotificationGroupDefinitionCreateOrUpdateDto : IHasExtraProperties
{
[Required]
[DynamicStringLength(typeof(NotificationDefinitionGroupRecordConsts), nameof(NotificationDefinitionGroupRecordConsts.MaxDisplayNameLength))]
public string DisplayName { get; set; }
[DynamicStringLength(typeof(NotificationDefinitionGroupRecordConsts), nameof(NotificationDefinitionGroupRecordConsts.MaxDescriptionLength))]
public string Description { get; set; }
public bool AllowSubscriptionToClients { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; set; } = new ExtraPropertyDictionary();
}

12
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionDto.cs

@ -0,0 +1,12 @@
using Volo.Abp.ObjectExtending;
namespace LINGYUN.Abp.Notifications.Definitions.Groups;
public class NotificationGroupDefinitionDto : ExtensibleObject
{
public string Name { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public bool IsStatic { get; set; }
public bool AllowSubscriptionToClients { get; set; }
}

5
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionGetListInput.cs

@ -0,0 +1,5 @@
namespace LINGYUN.Abp.Notifications.Definitions.Groups;
public class NotificationGroupDefinitionGetListInput
{
public string Filter { get; set; }
}

4
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/Dto/NotificationGroupDefinitionUpdateDto.cs

@ -0,0 +1,4 @@
namespace LINGYUN.Abp.Notifications.Definitions.Groups;
public class NotificationGroupDefinitionUpdateDto : NotificationGroupDefinitionCreateOrUpdateDto
{
}

18
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Groups/INotificationGroupDefinitionAppService.cs

@ -0,0 +1,18 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.Notifications.Definitions.Groups;
public interface INotificationGroupDefinitionAppService : IApplicationService
{
Task<NotificationGroupDefinitionDto> GetAsync(string name);
Task DeleteAsync(string name);
Task<ListResultDto<NotificationGroupDefinitionDto>> GetListAsync(NotificationGroupDefinitionGetListInput input);
Task<NotificationGroupDefinitionDto> CreateAsync(NotificationGroupDefinitionCreateDto input);
Task<NotificationGroupDefinitionDto> UpdateAsync(string name, NotificationGroupDefinitionUpdateDto input);
}

15
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionCreateDto.cs

@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.Notifications.Definitions.Notifications;
public class NotificationDefinitionCreateDto : NotificationDefinitionCreateOrUpdateDto
{
[Required]
[DynamicStringLength(typeof(NotificationDefinitionRecordConsts), nameof(NotificationDefinitionRecordConsts.MaxNameLength))]
public string Name { get; set; }
[Required]
[DynamicStringLength(typeof(NotificationDefinitionGroupRecordConsts), nameof(NotificationDefinitionGroupRecordConsts.MaxNameLength))]
public string GroupName { get; set; }
}

31
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionCreateOrUpdateDto.cs

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Data;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.Notifications.Definitions.Notifications;
public abstract class NotificationDefinitionCreateOrUpdateDto : IHasExtraProperties
{
[DynamicStringLength(typeof(NotificationDefinitionRecordConsts), nameof(NotificationDefinitionRecordConsts.MaxTemplateLength))]
public string Template { get; set; }
[Required]
[DynamicStringLength(typeof(NotificationDefinitionRecordConsts), nameof(NotificationDefinitionRecordConsts.MaxDisplayNameLength))]
public string DisplayName { get; set; }
[DynamicStringLength(typeof(NotificationDefinitionRecordConsts), nameof(NotificationDefinitionRecordConsts.MaxDescriptionLength))]
public string Description { get; set; }
public bool AllowSubscriptionToClients { get; set; }
public NotificationLifetime NotificationLifetime { get; set; } = NotificationLifetime.OnlyOne;
public NotificationType NotificationType { get; set; } = NotificationType.User;
public NotificationContentType ContentType { get; set; } = NotificationContentType.Text;
public List<string> Providers { get; set; } = new List<string>();
public ExtraPropertyDictionary ExtraProperties { get; set; } = new ExtraPropertyDictionary();
}

29
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionDto.cs

@ -0,0 +1,29 @@
using System.Collections.Generic;
using Volo.Abp.ObjectExtending;
namespace LINGYUN.Abp.Notifications.Definitions.Notifications;
public class NotificationDefinitionDto : ExtensibleObject
{
public string Name { get; set; }
public bool IsStatic { get; set; }
public string GroupName { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public bool AllowSubscriptionToClients { get; set; }
public NotificationLifetime NotificationLifetime { get; set; }
public NotificationType NotificationType { get; set; }
public NotificationContentType ContentType { get; set; }
public List<string> Providers { get; set; } = new List<string>();
public string Template { get; set; }
}

11
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionGetListInput.cs

@ -0,0 +1,11 @@
namespace LINGYUN.Abp.Notifications.Definitions.Notifications;
public class NotificationDefinitionGetListInput
{
public string Filter { get; set; }
public string GroupName { get; set; }
public string Template { get; set; }
public bool? AllowSubscriptionToClients { get; set; }
public NotificationLifetime? NotificationLifetime { get; set; }
public NotificationType? NotificationType { get; set; }
public NotificationContentType? ContentType { get; set; }
}

4
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/Dto/NotificationDefinitionUpdateDto.cs

@ -0,0 +1,4 @@
namespace LINGYUN.Abp.Notifications.Definitions.Notifications;
public class NotificationDefinitionUpdateDto : NotificationDefinitionCreateOrUpdateDto
{
}

13
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Definitions/Notifications/INotificationDefinitionAppService.cs

@ -0,0 +1,13 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.Notifications.Definitions.Notifications;
public interface INotificationDefinitionAppService : IApplicationService
{
Task<NotificationDefinitionDto> GetAsync(string name);
Task DeleteAsync(string name);
Task<NotificationDefinitionDto> CreateAsync(NotificationDefinitionCreateDto input);
Task<NotificationDefinitionDto> UpdateAsync(string name, NotificationDefinitionUpdateDto input);
Task<ListResultDto<NotificationDefinitionDto>> GetListAsync(NotificationDefinitionGetListInput input);
}

42
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Dto/NotificationSendDto.cs

@ -1,35 +1,25 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace LINGYUN.Abp.Notifications namespace LINGYUN.Abp.Notifications;
{
public class NotificationSendDto
{
[Required]
[StringLength(NotificationConsts.MaxNameLength)]
[DisplayName("Notifications:Name")]
public string Name { get; set; }
[StringLength(NotificationConsts.MaxNameLength)]
[DisplayName("Notifications:TemplateName")]
public string TemplateName { get; set; }
[DisplayName("Notifications:Data")] public class NotificationSendDto
public Dictionary<string, object> Data { get; set; } = new Dictionary<string, object>(); {
[Required]
[StringLength(NotificationConsts.MaxNameLength)]
[DisplayName("Notifications:Name")]
public string Name { get; set; }
[DisplayName("Notifications:Culture")] [DisplayName("Notifications:Data")]
public string Culture { get; set; } public Dictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
[DisplayName("Notifications:ToUserId")] [DisplayName("Notifications:Culture")]
public Guid? ToUserId { get; set; } public string Culture { get; set; }
[StringLength(128)] [DisplayName("Notifications:ToUserId")]
[DisplayName("Notifications:ToUserName")] public List<UserIdentifier> ToUsers { get; set; } = new List<UserIdentifier>();
public string ToUserName { get; set; }
[DisplayName("Notifications:Severity")] [DisplayName("Notifications:Severity")]
public NotificationSeverity Severity { get; set; } = NotificationSeverity.Info; public NotificationSeverity Severity { get; set; } = NotificationSeverity.Info;
}
} }

4
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Dto/NotificationTemplateSendDto.cs

@ -0,0 +1,4 @@
namespace LINGYUN.Abp.Notifications;
public class NotificationTemplateSendDto : NotificationSendDto
{
}

2
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/INotificationAppService.cs

@ -10,4 +10,6 @@ public interface INotificationAppService
Task<ListResultDto<NotificationTemplateDto>> GetAssignableTemplatesAsync(); Task<ListResultDto<NotificationTemplateDto>> GetAssignableTemplatesAsync();
Task SendAsync(NotificationSendDto input); Task SendAsync(NotificationSendDto input);
Task SendAsync(NotificationTemplateSendDto input);
} }

20
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Permissions/NotificationsPermissions.cs

@ -4,11 +4,29 @@
{ {
public const string GroupName = "Notifications"; public const string GroupName = "Notifications";
public class Notification public static class Notification
{ {
public const string Default = GroupName + ".Notification"; public const string Default = GroupName + ".Notification";
public const string Delete = Default + ".Delete"; public const string Delete = Default + ".Delete";
} }
public static class GroupDefinition
{
public const string Default = GroupName + ".GroupDefinitions";
public const string Create = Default + ".Create";
public const string Update = Default + ".Update";
public const string Delete = Default + ".Delete";
}
public static class Definition
{
public const string Default = GroupName + ".Definitions";
public const string Create = Default + ".Create";
public const string Update = Default + ".Update";
public const string Delete = Default + ".Delete";
}
} }
} }

35
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application.Contracts/LINGYUN/Abp/Notifications/Permissions/NotificationsPermissionsDefinitionProvider.cs

@ -1,6 +1,7 @@
using LINGYUN.Abp.Notifications.Localization; using LINGYUN.Abp.Notifications.Localization;
using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.Notifications.Permissions namespace LINGYUN.Abp.Notifications.Permissions
{ {
@ -10,6 +11,40 @@ namespace LINGYUN.Abp.Notifications.Permissions
{ {
var group = context.AddGroup(NotificationsPermissions.GroupName, L("Permission:Notifications")); var group = context.AddGroup(NotificationsPermissions.GroupName, L("Permission:Notifications"));
var groupDefinition = group.AddPermission(
NotificationsPermissions.GroupDefinition.Default,
L("Permission:GroupDefinitions"),
MultiTenancySides.Host);
groupDefinition.AddChild(
NotificationsPermissions.GroupDefinition.Create,
L("Permission:Create"),
MultiTenancySides.Host);
groupDefinition.AddChild(
NotificationsPermissions.GroupDefinition.Update,
L("Permission:Edit"),
MultiTenancySides.Host);
groupDefinition.AddChild(
NotificationsPermissions.GroupDefinition.Delete,
L("Permission:Delete"),
MultiTenancySides.Host);
var definition = group.AddPermission(
NotificationsPermissions.Definition.Default,
L("Permission:NotificationDefinitions"),
MultiTenancySides.Host);
definition.AddChild(
NotificationsPermissions.Definition.Create,
L("Permission:Create"),
MultiTenancySides.Host);
definition.AddChild(
NotificationsPermissions.Definition.Update,
L("Permission:Edit"),
MultiTenancySides.Host);
definition.AddChild(
NotificationsPermissions.Definition.Delete,
L("Permission:Delete"),
MultiTenancySides.Host);
var noticeGroup = group.AddPermission(NotificationsPermissions.Notification.Default, L("Permission:Notification")); var noticeGroup = group.AddPermission(NotificationsPermissions.Notification.Default, L("Permission:Notification"));
noticeGroup.AddChild(NotificationsPermissions.Notification.Delete, L("Permission:Delete")); noticeGroup.AddChild(NotificationsPermissions.Notification.Delete, L("Permission:Delete"));
} }

5
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/AbpNotificationsApplicationModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.Application; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Application;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -12,6 +13,8 @@ public class AbpNotificationsApplicationModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.AddAutoMapperObjectMapper<AbpNotificationsApplicationModule>();
Configure<AbpAutoMapperOptions>(options => Configure<AbpAutoMapperOptions>(options =>
{ {
options.AddProfile<AbpNotificationsApplicationAutoMapperProfile>(validate: true); options.AddProfile<AbpNotificationsApplicationAutoMapperProfile>(validate: true);

13
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/AbpNotificationsApplicationServiceBase.cs

@ -1,14 +1,13 @@
using LINGYUN.Abp.Notifications.Localization; using LINGYUN.Abp.Notifications.Localization;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.Notifications namespace LINGYUN.Abp.Notifications;
public abstract class AbpNotificationsApplicationServiceBase : ApplicationService
{ {
public abstract class AbpMessageServiceApplicationServiceBase : ApplicationService protected AbpNotificationsApplicationServiceBase()
{ {
protected AbpMessageServiceApplicationServiceBase() LocalizationResource = typeof(NotificationsResource);
{ ObjectMapperContext = typeof(AbpNotificationsApplicationModule);
LocalizationResource = typeof(NotificationsResource);
ObjectMapperContext = typeof(AbpNotificationsApplicationModule);
}
} }
} }

219
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/Definitions/Groups/NotificationGroupDefinitionAppService.cs

@ -0,0 +1,219 @@
using LINGYUN.Abp.Notifications.Permissions;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Data;
using Volo.Abp.Localization;
namespace LINGYUN.Abp.Notifications.Definitions.Groups;
[Authorize(NotificationsPermissions.GroupDefinition.Default)]
public class NotificationGroupDefinitionAppService : AbpNotificationsApplicationServiceBase, INotificationGroupDefinitionAppService
{
private readonly ILocalizableStringSerializer _localizableStringSerializer;
private readonly INotificationDefinitionManager _definitionManager;
private readonly IStaticNotificationDefinitionStore _staticDefinitionStore;
private readonly IDynamicNotificationDefinitionStore _dynamicDefinitionStore;
private readonly INotificationDefinitionGroupRecordRepository _definitionRecordRepository;
public NotificationGroupDefinitionAppService(
ILocalizableStringSerializer localizableStringSerializer,
INotificationDefinitionManager definitionManager,
IStaticNotificationDefinitionStore staticDefinitionStore,
IDynamicNotificationDefinitionStore dynamicDefinitionStore,
INotificationDefinitionGroupRecordRepository definitionRecordRepository)
{
_localizableStringSerializer = localizableStringSerializer;
_definitionManager = definitionManager;
_staticDefinitionStore = staticDefinitionStore;
_dynamicDefinitionStore = dynamicDefinitionStore;
_definitionRecordRepository = definitionRecordRepository;
}
[Authorize(NotificationsPermissions.GroupDefinition.Create)]
public async virtual Task<NotificationGroupDefinitionDto> CreateAsync(NotificationGroupDefinitionCreateDto input)
{
if (await _definitionManager.GetGroupOrNullAsync(input.Name) != null)
{
throw new BusinessException(NotificationsErrorCodes.GroupDefinition.AlreayNameExists)
.WithData(nameof(NotificationDefinitionGroupRecord.Name), input.Name);
}
var definitionRecord = await FindByNameAsync(input.Name);
if (definitionRecord != null)
{
throw new BusinessException(NotificationsErrorCodes.GroupDefinition.AlreayNameExists)
.WithData(nameof(NotificationDefinitionGroupRecord.Name), input.Name);
}
definitionRecord = new NotificationDefinitionGroupRecord(
GuidGenerator.Create(),
input.Name,
input.DisplayName);
UpdateByInput(definitionRecord, input);
await _definitionRecordRepository.InsertAsync(definitionRecord);
await CurrentUnitOfWork.SaveChangesAsync();
return DefinitionRecordToDto(definitionRecord);
}
[Authorize(NotificationsPermissions.GroupDefinition.Delete)]
public async virtual Task DeleteAsync(string name)
{
var definitionRecord = await FindByNameAsync(name);
if (definitionRecord != null)
{
await _definitionRecordRepository.DeleteAsync(definitionRecord);
await CurrentUnitOfWork.SaveChangesAsync();
}
}
public async virtual Task<NotificationGroupDefinitionDto> GetAsync(string name)
{
var definition = await _staticDefinitionStore.GetGroupOrNullAsync(name);
if (definition != null)
{
return DefinitionToDto(definition, true);
}
definition = await _dynamicDefinitionStore.GetGroupOrNullAsync(name);
if (definition == null)
{
throw new BusinessException(NotificationsErrorCodes.GroupDefinition.NameNotFount)
.WithData(nameof(NotificationDefinitionGroupRecord.Name), name);
}
return DefinitionToDto(definition);
}
public async virtual Task<ListResultDto<NotificationGroupDefinitionDto>> GetListAsync(NotificationGroupDefinitionGetListInput input)
{
var definitionDtoList = new List<NotificationGroupDefinitionDto>();
var staticGroups = await _staticDefinitionStore.GetGroupsAsync();
var staticGroupsNames = staticGroups
.Select(p => p.Name)
.ToImmutableHashSet();
definitionDtoList.AddRange(staticGroups.Select(d => DefinitionToDto(d, true)));
var dynamicGroups = await _dynamicDefinitionStore.GetGroupsAsync();
definitionDtoList.AddRange(dynamicGroups
.Where(d => !staticGroupsNames.Contains(d.Name))
.Select(d => DefinitionToDto(d)));
return new ListResultDto<NotificationGroupDefinitionDto>(
definitionDtoList
.WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(input.Filter))
.ToList());
}
[Authorize(NotificationsPermissions.GroupDefinition.Update)]
public async virtual Task<NotificationGroupDefinitionDto> UpdateAsync(string name, NotificationGroupDefinitionUpdateDto input)
{
var definition = await _staticDefinitionStore.GetGroupOrNullAsync(name);
if (definition != null)
{
throw new BusinessException(NotificationsErrorCodes.GroupDefinition.StaticGroupNotAllowedChanged)
.WithData(nameof(NotificationDefinitionGroupRecord.Name), name);
}
var definitionRecord = await FindByNameAsync(name);
if (definitionRecord == null)
{
definitionRecord = new NotificationDefinitionGroupRecord(
GuidGenerator.Create(),
name,
input.DisplayName);
UpdateByInput(definitionRecord, input);
definitionRecord = await _definitionRecordRepository.InsertAsync(definitionRecord);
}
else
{
UpdateByInput(definitionRecord, input);
definitionRecord = await _definitionRecordRepository.UpdateAsync(definitionRecord);
}
await CurrentUnitOfWork.SaveChangesAsync();
return DefinitionRecordToDto(definitionRecord);
}
protected virtual void UpdateByInput(NotificationDefinitionGroupRecord record, NotificationGroupDefinitionCreateOrUpdateDto input)
{
record.AllowSubscriptionToClients = input.AllowSubscriptionToClients;
record.ExtraProperties.Clear();
foreach (var property in input.ExtraProperties)
{
record.SetProperty(property.Key, property.Value);
}
if (!string.Equals(record.DisplayName, input.DisplayName, StringComparison.InvariantCultureIgnoreCase))
{
record.DisplayName = input.DisplayName;
}
if (!string.Equals(record.Description, input.Description, StringComparison.InvariantCultureIgnoreCase))
{
record.Description = input.Description;
}
}
protected async virtual Task<NotificationDefinitionGroupRecord> FindByNameAsync(string name)
{
var definitionRecord = await _definitionRecordRepository.FindByNameAsync(name);
return definitionRecord;
}
protected virtual NotificationGroupDefinitionDto DefinitionRecordToDto(NotificationDefinitionGroupRecord definitionRecord)
{
var dto = new NotificationGroupDefinitionDto
{
IsStatic = false,
Name = definitionRecord.Name,
DisplayName = definitionRecord.DisplayName,
Description = definitionRecord.Description,
AllowSubscriptionToClients = definitionRecord.AllowSubscriptionToClients,
};
foreach (var property in definitionRecord.ExtraProperties)
{
dto.SetProperty(property.Key, property.Value);
}
return dto;
}
protected virtual NotificationGroupDefinitionDto DefinitionToDto(NotificationGroupDefinition definition, bool isStatic = false)
{
var dto = new NotificationGroupDefinitionDto
{
IsStatic = isStatic,
Name = definition.Name,
AllowSubscriptionToClients = definition.AllowSubscriptionToClients,
DisplayName = _localizableStringSerializer.Serialize(definition.DisplayName),
};
if (definition.Description != null)
{
dto.Description = _localizableStringSerializer.Serialize(definition.Description);
}
foreach (var property in definition.Properties)
{
dto.SetProperty(property.Key, property.Value);
}
return dto;
}
}

301
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/Definitions/Notifications/NotificationDefinitionAppService.cs

@ -0,0 +1,301 @@
using LINGYUN.Abp.Notifications.Permissions;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Data;
using Volo.Abp.Localization;
namespace LINGYUN.Abp.Notifications.Definitions.Notifications;
[Authorize(NotificationsPermissions.Definition.Default)]
public class NotificationDefinitionAppService : AbpNotificationsApplicationServiceBase, INotificationDefinitionAppService
{
private readonly ILocalizableStringSerializer _localizableStringSerializer;
private readonly INotificationDefinitionManager _definitionManager;
private readonly IStaticNotificationDefinitionStore _staticDefinitionStore;
private readonly IDynamicNotificationDefinitionStore _dynamicDefinitionStore;
private readonly INotificationDefinitionRecordRepository _definitionRecordRepository;
public NotificationDefinitionAppService(
ILocalizableStringSerializer localizableStringSerializer,
INotificationDefinitionManager definitionManager,
IStaticNotificationDefinitionStore staticDefinitionStore,
IDynamicNotificationDefinitionStore dynamicDefinitionStore,
INotificationDefinitionRecordRepository definitionRecordRepository)
{
_localizableStringSerializer = localizableStringSerializer;
_definitionManager = definitionManager;
_staticDefinitionStore = staticDefinitionStore;
_dynamicDefinitionStore = dynamicDefinitionStore;
_definitionRecordRepository = definitionRecordRepository;
}
[Authorize(NotificationsPermissions.Definition.Create)]
public async virtual Task<NotificationDefinitionDto> CreateAsync(NotificationDefinitionCreateDto input)
{
if (await _staticDefinitionStore.GetGroupOrNullAsync(input.GroupName) != null)
{
throw new BusinessException(NotificationsErrorCodes.GroupDefinition.StaticGroupNotAllowedChanged)
.WithData(nameof(NotificationDefinitionGroupRecord.Name), input.GroupName);
}
if (await _staticDefinitionStore.GetOrNullAsync(input.Name) != null)
{
throw new BusinessException(NotificationsErrorCodes.Definition.AlreayNameExists)
.WithData(nameof(NotificationDefinitionRecord.Name), input.Name);
}
if (await _definitionRecordRepository.FindByNameAsync(input.Name) != null)
{
throw new BusinessException(NotificationsErrorCodes.Definition.AlreayNameExists)
.WithData(nameof(NotificationDefinitionRecord.Name), input.Name);
}
var groupDefinition = await _definitionManager.GetGroupOrNullAsync(input.GroupName);
if (groupDefinition == null)
{
throw new BusinessException(NotificationsErrorCodes.GroupDefinition.NameNotFount)
.WithData(nameof(NotificationDefinitionGroupRecord.Name), input.GroupName);
}
var definitionRecord = new NotificationDefinitionRecord(
GuidGenerator.Create(),
input.Name,
groupDefinition.Name,
input.DisplayName,
input.Description,
input.Template,
input.NotificationLifetime,
input.NotificationType,
input.ContentType);
UpdateByInput(definitionRecord, input);
await _definitionRecordRepository.InsertAsync(definitionRecord);
await CurrentUnitOfWork.SaveChangesAsync();
return DefinitionRecordToDto(definitionRecord);
}
[Authorize(NotificationsPermissions.Definition.Delete)]
public async virtual Task DeleteAsync(string name)
{
var definitionRecord = await FindRecordByNameAsync(name);
if (definitionRecord != null)
{
await _definitionRecordRepository.DeleteAsync(definitionRecord);
await CurrentUnitOfWork.SaveChangesAsync();
}
}
public async virtual Task<NotificationDefinitionDto> GetAsync(string name)
{
var definition = await _staticDefinitionStore.GetOrNullAsync(name);
if (definition != null)
{
return DefinitionToDto(await GetGroupDefinition(definition), definition, true);
}
definition = await _dynamicDefinitionStore.GetOrNullAsync(name);
return DefinitionToDto(await GetGroupDefinition(definition), definition);
}
public async virtual Task<ListResultDto<NotificationDefinitionDto>> GetListAsync(NotificationDefinitionGetListInput input)
{
var dtoList = new List<NotificationDefinitionDto>();
var staticDefinitions = new List<NotificationDefinition>();
var staticGroups = await _staticDefinitionStore.GetGroupsAsync();
var staticGroupNames = staticGroups
.Select(p => p.Name)
.ToImmutableHashSet();
foreach (var group in staticGroups.WhereIf(!input.GroupName.IsNullOrWhiteSpace(), x => x.Name == input.GroupName))
{
var definitions = group.Notifications;
staticDefinitions.AddRange(definitions);
dtoList.AddRange(definitions.Select(f => DefinitionToDto(group, f, true)));
}
var staticDefinitionNames = staticDefinitions
.Select(p => p.Name)
.ToImmutableHashSet();
var dynamicGroups = await _dynamicDefinitionStore.GetGroupsAsync();
foreach (var group in dynamicGroups
.Where(d => !staticGroupNames.Contains(d.Name))
.WhereIf(!input.GroupName.IsNullOrWhiteSpace(), x => x.Name == input.GroupName))
{
var definitions = group.Notifications;
dtoList.AddRange(definitions
.Where(d => !staticDefinitionNames.Contains(d.Name))
.Select(f => DefinitionToDto(group, f)));
}
return new ListResultDto<NotificationDefinitionDto>(dtoList
.WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(input.Filter) || x.DisplayName.Contains(input.Filter))
.WhereIf(!input.Template.IsNullOrWhiteSpace(), x => x.Template == input.Template)
.WhereIf(input.AllowSubscriptionToClients.HasValue, x => x.AllowSubscriptionToClients == input.AllowSubscriptionToClients)
.WhereIf(input.ContentType.HasValue, x => x.ContentType == input.ContentType)
.WhereIf(input.NotificationLifetime.HasValue, x => x.NotificationLifetime == input.NotificationLifetime)
.WhereIf(input.NotificationType.HasValue, x => x.NotificationType == input.NotificationType)
.ToList());
}
[Authorize(NotificationsPermissions.Definition.Update)]
public async virtual Task<NotificationDefinitionDto> UpdateAsync(string name, NotificationDefinitionUpdateDto input)
{
if (await _staticDefinitionStore.GetOrNullAsync(name) != null)
{
throw new BusinessException(NotificationsErrorCodes.Definition.StaticFeatureNotAllowedChanged)
.WithData(nameof(NotificationDefinitionRecord.Name), name);
}
var definition = await _definitionManager.GetAsync(name);
var definitionRecord = await FindRecordByNameAsync(name);
if (definitionRecord == null)
{
var groupDefinition = await GetGroupDefinition(definition);
definitionRecord = new NotificationDefinitionRecord(
GuidGenerator.Create(),
name,
groupDefinition.Name,
input.DisplayName,
input.Description,
input.Template,
input.NotificationLifetime,
input.NotificationType,
input.ContentType);
UpdateByInput(definitionRecord, input);
definitionRecord = await _definitionRecordRepository.InsertAsync(definitionRecord);
}
else
{
UpdateByInput(definitionRecord, input);
definitionRecord = await _definitionRecordRepository.UpdateAsync(definitionRecord);
}
await CurrentUnitOfWork.SaveChangesAsync();
return DefinitionRecordToDto(definitionRecord);
}
protected virtual void UpdateByInput(NotificationDefinitionRecord record, NotificationDefinitionCreateOrUpdateDto input)
{
record.AllowSubscriptionToClients = input.AllowSubscriptionToClients;
record.NotificationLifetime = input.NotificationLifetime;
record.NotificationType = input.NotificationType;
record.ContentType = input.ContentType;
if (!string.Equals(record.Template, input.Template, StringComparison.InvariantCultureIgnoreCase))
{
record.Template = input.Template;
}
if (!string.Equals(record.DisplayName, input.DisplayName, StringComparison.InvariantCultureIgnoreCase))
{
record.DisplayName = input.DisplayName;
}
if (!string.Equals(record.Description, input.Description, StringComparison.InvariantCultureIgnoreCase))
{
record.Description = input.Description;
}
string allowedProviders = null;
if (!input.Providers.IsNullOrEmpty())
{
allowedProviders = input.Providers.JoinAsString(",");
}
if (!string.Equals(record.Providers, allowedProviders, StringComparison.InvariantCultureIgnoreCase))
{
record.UseProviders(input.Providers.ToArray());
}
record.ExtraProperties.Clear();
foreach (var property in input.ExtraProperties)
{
record.SetProperty(property.Key, property.Value);
}
}
protected async virtual Task<NotificationDefinitionRecord> FindRecordByNameAsync(string name)
{
return await _definitionRecordRepository.FindByNameAsync(name);
}
protected async virtual Task<NotificationGroupDefinition> GetGroupDefinition(NotificationDefinition definition)
{
var groups = await _definitionManager.GetGroupsAsync();
foreach (var group in groups)
{
if (group.GetNotificationOrNull(definition.Name) != null)
{
return group;
}
}
throw new BusinessException(NotificationsErrorCodes.Definition.FailedGetGroup)
.WithData(nameof(NotificationDefinitionRecord.Name), definition.Name);
}
protected virtual NotificationDefinitionDto DefinitionRecordToDto(NotificationDefinitionRecord definitionRecord)
{
var dto = new NotificationDefinitionDto
{
IsStatic = false,
Name = definitionRecord.Name,
GroupName = definitionRecord.GroupName,
Description = definitionRecord.Description,
DisplayName = definitionRecord.DisplayName,
Template = definitionRecord.Template,
ContentType = definitionRecord.ContentType,
NotificationLifetime = definitionRecord.NotificationLifetime,
NotificationType = definitionRecord.NotificationType,
AllowSubscriptionToClients = definitionRecord.AllowSubscriptionToClients,
Providers = definitionRecord.Providers?.Split(",").ToList(),
};
foreach (var property in definitionRecord.ExtraProperties)
{
dto.SetProperty(property.Key, property.Value);
}
return dto;
}
protected virtual NotificationDefinitionDto DefinitionToDto(NotificationGroupDefinition groupDefinition, NotificationDefinition definition, bool isStatic = false)
{
var dto = new NotificationDefinitionDto
{
IsStatic = isStatic,
Name = definition.Name,
GroupName = groupDefinition.Name,
Template = definition.Template?.Name,
AllowSubscriptionToClients = definition.AllowSubscriptionToClients,
ContentType = definition.ContentType,
NotificationLifetime = definition.NotificationLifetime,
NotificationType = definition.NotificationType,
DisplayName = _localizableStringSerializer.Serialize(definition.DisplayName),
Providers = definition.Providers,
};
if (definition.Description != null)
{
dto.Description = _localizableStringSerializer.Serialize(definition.Description);
}
foreach (var property in definition.Properties)
{
dto.SetProperty(property.Key, property.Value);
}
return dto;
}
}

3
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/MyNotificationAppService.cs

@ -2,13 +2,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Users; using Volo.Abp.Users;
namespace LINGYUN.Abp.Notifications; namespace LINGYUN.Abp.Notifications;
[Authorize] [Authorize]
public class MyNotificationAppService : ApplicationService, IMyNotificationAppService public class MyNotificationAppService : AbpNotificationsApplicationServiceBase, IMyNotificationAppService
{ {
protected INotificationSender NotificationSender { get; } protected INotificationSender NotificationSender { get; }

3
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/MySubscriptionAppService.cs

@ -2,13 +2,12 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Users; using Volo.Abp.Users;
namespace LINGYUN.Abp.Notifications; namespace LINGYUN.Abp.Notifications;
[Authorize] [Authorize]
public class MySubscriptionAppService : ApplicationService, IMySubscriptionAppService public class MySubscriptionAppService : AbpNotificationsApplicationServiceBase, IMySubscriptionAppService
{ {
protected IUserSubscribeRepository UserSubscribeRepository { get; } protected IUserSubscribeRepository UserSubscribeRepository { get; }
protected INotificationSubscriptionManager NotificationSubscriptionManager { get; } protected INotificationSubscriptionManager NotificationSubscriptionManager { get; }

68
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/NotificationAppService.cs

@ -1,18 +1,15 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.TextTemplating; using Volo.Abp.TextTemplating;
namespace LINGYUN.Abp.Notifications; namespace LINGYUN.Abp.Notifications;
[Authorize] [Authorize]
public class NotificationAppService : ApplicationService, INotificationAppService public class NotificationAppService : AbpNotificationsApplicationServiceBase, INotificationAppService
{ {
protected ITemplateContentProvider TemplateContentProvider { get; } protected ITemplateContentProvider TemplateContentProvider { get; }
protected INotificationSender NotificationSender { get; } protected INotificationSender NotificationSender { get; }
@ -100,52 +97,33 @@ public class NotificationAppService : ApplicationService, INotificationAppServic
public async virtual Task SendAsync(NotificationSendDto input) public async virtual Task SendAsync(NotificationSendDto input)
{ {
var notification = await GetNotificationDefinition(input.Name); var notificationData = new NotificationData();
notificationData.ExtraProperties.AddIfNotContains(input.Data);
UserIdentifier user = null;
if (input.ToUserId.HasValue) await NotificationSender
{ .SendNofiterAsync(
user = new UserIdentifier(input.ToUserId.Value, input.ToUserName); name: input.Name,
} data: notificationData,
users: input.ToUsers,
tenantId: CurrentTenant.Id,
severity: input.Severity);
}
if (!input.TemplateName.IsNullOrWhiteSpace()) public async virtual Task SendAsync(NotificationTemplateSendDto input)
{ {
if (notification.Template == null) var notificationTemplate = new NotificationTemplate(
{ input.Name,
throw new BusinessException(
NotificationsErrorCodes.NotificationTemplateNotFound,
$"The notification template {input.TemplateName} does not exist!")
.WithData("Name", input.TemplateName);
}
var notificationTemplate = new NotificationTemplate(
notification.Name,
culture: input.Culture ?? CultureInfo.CurrentCulture.Name, culture: input.Culture ?? CultureInfo.CurrentCulture.Name,
formUser: CurrentUser.Name ?? CurrentUser.UserName, formUser: CurrentUser.Name ?? CurrentUser.UserName,
data: input.Data); data: input.Data);
await NotificationSender await NotificationSender
.SendNofiterAsync( .SendNofiterAsync(
name: input.Name, name: input.Name,
template: notificationTemplate, template: notificationTemplate,
user: user, users: input.ToUsers,
tenantId: CurrentTenant.Id, tenantId: CurrentTenant.Id,
severity: input.Severity); severity: input.Severity);
}
else
{
var notificationData = new NotificationData();
notificationData.ExtraProperties.AddIfNotContains(input.Data);
notificationData = NotificationData.ToStandardData(notificationData);
await NotificationSender
.SendNofiterAsync(
name: input.Name,
data: notificationData,
user: user,
tenantId: CurrentTenant.Id,
severity: input.Severity);
}
} }
protected async virtual Task<NotificationDefinition> GetNotificationDefinition(string name) protected async virtual Task<NotificationDefinition> GetNotificationDefinition(string name)

13
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationTemplateDefinitionProvider.cs

@ -4,22 +4,25 @@ using Volo.Abp.TextTemplating;
namespace LINGYUN.Abp.Notifications; namespace LINGYUN.Abp.Notifications;
public class AbpNotificationTemplateDefinitionProvider : TemplateDefinitionProvider public class AbpNotificationTemplateDefinitionProvider : TemplateDefinitionProvider
{ {
private readonly INotificationDefinitionManager _notificationDefinitionManager; private readonly IStaticNotificationDefinitionStore _staticNotificationDefinitionStore;
public AbpNotificationTemplateDefinitionProvider( public AbpNotificationTemplateDefinitionProvider(
INotificationDefinitionManager notificationDefinitionManager) IStaticNotificationDefinitionStore staticNotificationDefinitionStore)
{ {
_notificationDefinitionManager = notificationDefinitionManager; _staticNotificationDefinitionStore = staticNotificationDefinitionStore;
} }
public override void Define(ITemplateDefinitionContext context) public override void Define(ITemplateDefinitionContext context)
{ {
var notifications = _notificationDefinitionManager var notifications = _staticNotificationDefinitionStore
.GetNotificationsAsync().GetAwaiter().GetResult(); .GetNotificationsAsync().GetAwaiter().GetResult();
foreach (var notification in notifications.Where(n => n.Template != null)) foreach (var notification in notifications.Where(n => n.Template != null))
{ {
context.Add(notification.Template); if (context.GetOrNull(notification.Template.Name) == null)
{
context.Add(notification.Template);
}
} }
} }
} }

2
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionStore.cs

@ -9,5 +9,7 @@ public interface IDynamicNotificationDefinitionStore
Task<IReadOnlyList<NotificationDefinition>> GetNotificationsAsync(); Task<IReadOnlyList<NotificationDefinition>> GetNotificationsAsync();
Task<NotificationGroupDefinition> GetGroupOrNullAsync(string name);
Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync(); Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync();
} }

2
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/INotificationDefinitionManager.cs

@ -13,6 +13,8 @@ namespace LINGYUN.Abp.Notifications
Task<NotificationDefinition> GetOrNullAsync(string name); Task<NotificationDefinition> GetOrNullAsync(string name);
Task<NotificationGroupDefinition> GetGroupOrNullAsync(string name);
Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync(); Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync();
} }
} }

2
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/IStaticNotificationDefinitionStore.cs

@ -9,5 +9,7 @@ public interface IStaticNotificationDefinitionStore
Task<IReadOnlyList<NotificationDefinition>> GetNotificationsAsync(); Task<IReadOnlyList<NotificationDefinition>> GetNotificationsAsync();
Task<NotificationGroupDefinition> GetGroupOrNullAsync(string name);
Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync(); Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync();
} }

7
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinition.cs

@ -134,6 +134,13 @@ namespace LINGYUN.Abp.Notifications
return this; return this;
} }
public virtual NotificationDefinition WithTemplate(TemplateDefinition template)
{
Template = template;
return this;
}
public virtual NotificationDefinition WithProperty(string key, object value) public virtual NotificationDefinition WithProperty(string key, object value)
{ {
Properties[key] = value; Properties[key] = value;

10
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionManager.cs

@ -53,7 +53,15 @@ namespace LINGYUN.Abp.Notifications
.ToImmutableList(); .ToImmutableList();
} }
public async Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync() public async virtual Task<NotificationGroupDefinition> GetGroupOrNullAsync(string name)
{
Check.NotNull(name, nameof(name));
return await _staticStore.GetGroupOrNullAsync(name) ??
await _dynamicStore.GetGroupOrNullAsync(name);
}
public async virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync()
{ {
var staticGroups = await _staticStore.GetGroupsAsync(); var staticGroups = await _staticStore.GetGroupsAsync();
var staticGroupNames = staticGroups var staticGroupNames = staticGroups

10
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationGroupDefinition.cs

@ -1,6 +1,7 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Localization; using Volo.Abp.Localization;
@ -56,7 +57,7 @@ namespace LINGYUN.Abp.Notifications
{ {
Name = name; Name = name;
DisplayName = displayName ?? new FixedLocalizableString(Name); DisplayName = displayName ?? new FixedLocalizableString(Name);
Description = description ?? DisplayName; Description = description;
AllowSubscriptionToClients = allowSubscriptionToClients; AllowSubscriptionToClients = allowSubscriptionToClients;
_notifications = new List<NotificationDefinition>(); _notifications = new List<NotificationDefinition>();
@ -87,5 +88,12 @@ namespace LINGYUN.Abp.Notifications
return notification; return notification;
} }
public NotificationDefinition GetNotificationOrNull([NotNull] string name)
{
Check.NotNull(name, nameof(name));
return _notifications.FirstOrDefault(x => x.Name == name);
}
} }
} }

6
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NullDynamicNotificationDefinitionStore.cs

@ -10,6 +10,7 @@ namespace LINGYUN.Abp.Notifications;
public class NullDynamicNotificationDefinitionStore : IDynamicNotificationDefinitionStore, ISingletonDependency public class NullDynamicNotificationDefinitionStore : IDynamicNotificationDefinitionStore, ISingletonDependency
{ {
private readonly static Task<NotificationDefinition> CachedNotificationResult = Task.FromResult((NotificationDefinition)null); private readonly static Task<NotificationDefinition> CachedNotificationResult = Task.FromResult((NotificationDefinition)null);
private readonly static Task<NotificationGroupDefinition> CachedNotificationGroupResult = Task.FromResult((NotificationGroupDefinition)null);
private readonly static Task<IReadOnlyList<NotificationDefinition>> CachedNotificationsResult = private readonly static Task<IReadOnlyList<NotificationDefinition>> CachedNotificationsResult =
Task.FromResult((IReadOnlyList<NotificationDefinition>)Array.Empty<NotificationDefinition>().ToImmutableList()); Task.FromResult((IReadOnlyList<NotificationDefinition>)Array.Empty<NotificationDefinition>().ToImmutableList());
@ -27,6 +28,11 @@ public class NullDynamicNotificationDefinitionStore : IDynamicNotificationDefini
return CachedNotificationsResult; return CachedNotificationsResult;
} }
public Task<NotificationGroupDefinition> GetGroupOrNullAsync(string name)
{
return CachedNotificationGroupResult;
}
public Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync() public Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync()
{ {
return CachedGroupsResult; return CachedGroupsResult;

9
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/StaticNotificationDefinitionStore.cs

@ -80,7 +80,7 @@ public class StaticNotificationDefinitionStore : IStaticNotificationDefinitionSt
return context.Groups; return context.Groups;
} }
public Task<NotificationDefinition> GetOrNullAsync(string name) public virtual Task<NotificationDefinition> GetOrNullAsync(string name)
{ {
return Task.FromResult(NotificationDefinitions.GetOrDefault(name)); return Task.FromResult(NotificationDefinitions.GetOrDefault(name));
} }
@ -92,7 +92,12 @@ public class StaticNotificationDefinitionStore : IStaticNotificationDefinitionSt
); );
} }
public Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync() public virtual Task<NotificationGroupDefinition> GetGroupOrNullAsync(string name)
{
return Task.FromResult(NotificationGroupDefinitions.GetOrDefault(name));
}
public virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync()
{ {
return Task.FromResult<IReadOnlyList<NotificationGroupDefinition>>( return Task.FromResult<IReadOnlyList<NotificationGroupDefinition>>(
NotificationGroupDefinitions.Values.ToImmutableList() NotificationGroupDefinitions.Values.ToImmutableList()

45
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain.Shared/LINGYUN/Abp/Notifications/Localization/DomainShared/en.json

@ -2,7 +2,50 @@
"culture": "en", "culture": "en",
"texts": { "texts": {
"Permission:Notification": "Notification", "Permission:Notification": "Notification",
"Permission:GroupDefinitions": "Group Definitions",
"Permission:NotificationDefinitions": "Notification Definitions",
"Permission:Create": "Create",
"Permission:Edit": "Edit",
"Permission:Delete": "Delete", "Permission:Delete": "Delete",
"LINGYUN.Abp.Notifications:01404": "The notification template does not exist!" "Notifications:001404": "The notification template does not exist!",
"Notifications:002400": "The static notification group {Name} is not allowed to change!",
"Notifications:002403": "Notification that the {Name} group already exists!",
"Notifications:002404": "Notification group with the Name {Name} was not found!",
"Notifications:003400": "Static advice {Name} is not allowed to change!",
"Notifications:003401": "Failed to retrieve the notification {Name} group definition!",
"Notifications:003403": "Notification that {Name} already exists!",
"Notifications:003404": "No notification definition with the Name {Name} was found!",
"DisplayName:Name": "Name",
"DisplayName:GroupName": "Group Name",
"DisplayName:DisplayName": "Display Name",
"DisplayName:Description": "Description",
"DisplayName:NotificationType": "Notification Type",
"Description:NotificationType": "The Service Callback only supports the SignalR provider",
"DisplayName:NotificationLifetime": "Lifetime",
"Description:NotificationLifetime": "Only One notifications cancel the user's subscription after they are sent.",
"DisplayName:ContentType": "Content Type",
"Description:ContentType": "The receiver should process the notification content according to the content form.",
"DisplayName:AllowSubscriptionToClients": "Allow Client Subscriptions",
"Description:AllowSubscriptionToClients": "Whether notifications can be subscribed by the user on the client.",
"DisplayName:Providers": "Providers",
"Description:Providers": "By which providers the notification can be published to subscribers after it is sent, leaving it blank will be handled by all providers.",
"DisplayName:Template": "Template",
"Description:Template": "The notification template is required when sending template notifications.",
"DisplayName:IsStatic": "Static",
"Providers:Emailing": "Email",
"Providers:SignalR": "SignalR",
"Providers:Sms": "Sms",
"Providers:WxPusher": "WxPusher",
"Providers:WeChat.Work": "WeCom",
"Providers:WeChat.MiniProgram": "Wechat MiniProgram",
"GroupDefinitions": "Group Definitions",
"GroupDefinitions:AddNew": "Add Group",
"NotificationDefinitions": "Notification Definitions",
"NotificationDefinitions:AddNew": "Add Notification",
"BasicInfo": "Basic",
"Properties": "Properties",
"TemplateContent": "Template Content",
"ItemWillBeDeleteOrRestoreMessage": "If the notification has been changed, it will revert to the default notification. Otherwise, the custom notification is removed.",
"Notifications:Send": "Send Notification"
} }
} }

45
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain.Shared/LINGYUN/Abp/Notifications/Localization/DomainShared/zh-Hans.json

@ -2,7 +2,50 @@
"culture": "zh-Hans", "culture": "zh-Hans",
"texts": { "texts": {
"Permission:Notification": "通知管理", "Permission:Notification": "通知管理",
"Permission:GroupDefinitions": "分组定义",
"Permission:NotificationDefinitions": "通知定义",
"Permission:Create": "新增",
"Permission:Edit": "编辑",
"Permission:Delete": "删除", "Permission:Delete": "删除",
"LINGYUN.Abp.Notifications:01404": "通知模板不存在!" "Notifications:001404": "通知模板不存在!",
"Notifications:002400": "静态通知分组 {Name} 不允许变更!",
"Notifications:002403": "通知分组 {Name} 已经存在!",
"Notifications:002404": "没有找到名为 {Name} 的通知分组!",
"Notifications:003400": "静态通知 {Name} 不允许变更!",
"Notifications:003401": "未能检索到通知 {Name} 所在分组定义!",
"Notifications:003403": "通知 {Name} 已经存在!",
"Notifications:003404": "没有找到名为 {Name} 的通知定义!",
"DisplayName:Name": "名称",
"DisplayName:GroupName": "分组名称",
"DisplayName:DisplayName": "显示名称",
"DisplayName:Description": "描述",
"DisplayName:NotificationType": "通知类型",
"Description:NotificationType": "服务端回调只支持 SignalR 提供者.",
"DisplayName:NotificationLifetime": "生命周期",
"Description:NotificationLifetime": "一次性通知在发送之后会取消用户订阅.",
"DisplayName:ContentType": "内容形式",
"Description:ContentType": "接收端应根据内容形式处理通知内容.",
"DisplayName:AllowSubscriptionToClients": "允许客户端订阅",
"Description:AllowSubscriptionToClients": "通知是否能被用户在客户端上订阅.",
"DisplayName:Providers": "提供者",
"Description:Providers": "通知发送后能被哪些提供者发布到订阅者, 留空将被所有提供者处理.",
"DisplayName:Template": "模板",
"Description:Template": "发送模板通知时需要提供通知模板.",
"DisplayName:IsStatic": "静态",
"Providers:Emailing": "邮件",
"Providers:SignalR": "SignalR",
"Providers:Sms": "短信",
"Providers:WxPusher": "WxPusher",
"Providers:WeChat.Work": "企业微信",
"Providers:WeChat.MiniProgram": "微信小程序",
"GroupDefinitions": "分组定义",
"GroupDefinitions:AddNew": "添加分组",
"NotificationDefinitions": "通知定义",
"NotificationDefinitions:AddNew": "添加通知",
"BasicInfo": "基本信息",
"Properties": "属性",
"TemplateContent": "模板内容",
"ItemWillBeDeleteOrRestoreMessage": "如果已改变通知, 将还原到默认通知。否则会删除自定义通知.",
"Notifications:Send": "发送通知"
} }
} }

1
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain.Shared/LINGYUN/Abp/Notifications/NotificationDefinitionRecordConsts.cs

@ -5,5 +5,6 @@ public static class NotificationDefinitionRecordConsts
public static int MaxNameLength { get; set; } = 64; public static int MaxNameLength { get; set; } = 64;
public static int MaxDisplayNameLength { get; set; } = 255; public static int MaxDisplayNameLength { get; set; } = 255;
public static int MaxDescriptionLength { get; set; } = 255; public static int MaxDescriptionLength { get; set; } = 255;
public static int MaxTemplateLength { get; set; } = 128;
public static int MaxProvidersLength { get; set; } = 200; public static int MaxProvidersLength { get; set; } = 200;
} }

58
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain.Shared/LINGYUN/Abp/Notifications/NotificationsErrorCodes.cs

@ -2,34 +2,58 @@
{ {
/// <summary> /// <summary>
/// 消息系统错误码设计 /// 消息系统错误码设计
/// 状态码分为两部分 前2位领域 后3位状态 /// 状态码分为两部分 前2位领域 后3位状态
/// /// <br />
/// <list type="table">
/// 领域部分: /// 领域部分:
/// 01 输入 /// <list type="table">
/// 02 群组 /// <item>000-019 输入</item>
/// 03 用户 /// <item>020-029 群组</item>
/// 04 应用 /// <item>030-039 用户</item>
/// 05 内部 /// <item>040-049 应用</item>
/// 10 输出 /// <item>050-059 内部</item>
/// <item>100-199 输出</item>
/// </list> /// </list>
/// ///
/// <list type="table">
/// 状态部分: /// 状态部分:
/// 200-299 成功 /// <list type="table">
/// 300-399 成功但有后续操作 /// <item>200-299 成功</item>
/// 400-499 业务异常 /// <item>300-399 成功但有后续操作</item>
/// 500-599 内部异常 /// <item>400-499 业务异常</item>
/// 900-999 输入输出异常 /// <item>500-599 内部异常</item>
/// <item>900-999 输入输出异常</item>
/// </list> /// </list>
/// ///
/// </summary> /// </summary>
public class NotificationsErrorCodes public class NotificationsErrorCodes
{ {
public const string Namespace = "LINGYUN.Abp.Notifications"; public const string Namespace = "Notifications";
/// <summary> /// <summary>
/// 通知模板不存在! /// 通知模板不存在!
/// </summary> /// </summary>
public const string NotificationTemplateNotFound = Namespace + ":01404"; public const string NotificationTemplateNotFound = Namespace + ":001404";
public static class GroupDefinition
{
private const string Prefix = Namespace + ":002";
public const string StaticGroupNotAllowedChanged = Prefix + "400";
public const string AlreayNameExists = Prefix + "403";
public const string NameNotFount = Prefix + "404";
}
public static class Definition
{
private const string Prefix = Namespace + ":003";
public const string StaticFeatureNotAllowedChanged = Prefix + "400";
public const string FailedGetGroup = Prefix + "401";
public const string AlreayNameExists = Prefix + "403";
public const string NameNotFount = Prefix + "404";
}
} }
} }

36
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionInMemoryCache.cs

@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.TextTemplating;
namespace LINGYUN.Abp.Notifications; namespace LINGYUN.Abp.Notifications;
@ -18,15 +19,18 @@ public class DynamicNotificationDefinitionInMemoryCache : IDynamicNotificationDe
protected IDictionary<string, NotificationGroupDefinition> NotificationGroupDefinitions { get; } protected IDictionary<string, NotificationGroupDefinition> NotificationGroupDefinitions { get; }
protected IDictionary<string, NotificationDefinition> NotificationDefinitions { get; } protected IDictionary<string, NotificationDefinition> NotificationDefinitions { get; }
protected ILocalizableStringSerializer LocalizableStringSerializer { get; } protected ILocalizableStringSerializer LocalizableStringSerializer { get; }
protected ITemplateDefinitionManager TemplateDefinitionManager { get; }
public SemaphoreSlim SyncSemaphore { get; } = new(1, 1); public SemaphoreSlim SyncSemaphore { get; } = new(1, 1);
public DateTime? LastCheckTime { get; set; } public DateTime? LastCheckTime { get; set; }
public DynamicNotificationDefinitionInMemoryCache( public DynamicNotificationDefinitionInMemoryCache(
ILocalizableStringSerializer localizableStringSerializer) ILocalizableStringSerializer localizableStringSerializer,
ITemplateDefinitionManager templateDefinitionManager)
{ {
LocalizableStringSerializer = localizableStringSerializer; LocalizableStringSerializer = localizableStringSerializer;
TemplateDefinitionManager = templateDefinitionManager;
NotificationGroupDefinitions = new Dictionary<string, NotificationGroupDefinition>(); NotificationGroupDefinitions = new Dictionary<string, NotificationGroupDefinition>();
NotificationDefinitions = new Dictionary<string, NotificationDefinition>(); NotificationDefinitions = new Dictionary<string, NotificationDefinition>();
@ -43,10 +47,15 @@ public class DynamicNotificationDefinitionInMemoryCache : IDynamicNotificationDe
foreach (var notificationGroupRecord in notificationGroupRecords) foreach (var notificationGroupRecord in notificationGroupRecords)
{ {
ILocalizableString description = null;
if (!notificationGroupRecord.Description.IsNullOrWhiteSpace())
{
description = LocalizableStringSerializer.Deserialize(notificationGroupRecord.Description);
}
var notificationGroup = context.AddGroup( var notificationGroup = context.AddGroup(
notificationGroupRecord.Name, notificationGroupRecord.Name,
LocalizableStringSerializer.Deserialize(notificationGroupRecord.DisplayName), LocalizableStringSerializer.Deserialize(notificationGroupRecord.DisplayName),
LocalizableStringSerializer.Deserialize(notificationGroupRecord.Description), description,
notificationGroupRecord.AllowSubscriptionToClients notificationGroupRecord.AllowSubscriptionToClients
); );
@ -79,19 +88,30 @@ public class DynamicNotificationDefinitionInMemoryCache : IDynamicNotificationDe
return NotificationDefinitions.Values.ToList(); return NotificationDefinitions.Values.ToList();
} }
public virtual NotificationGroupDefinition GetNotificationGroupOrNull(string name)
{
return NotificationGroupDefinitions.GetOrDefault(name);
}
public virtual IReadOnlyList<NotificationGroupDefinition> GetGroups() public virtual IReadOnlyList<NotificationGroupDefinition> GetGroups()
{ {
return NotificationGroupDefinitions.Values.ToList(); return NotificationGroupDefinitions.Values.ToList();
} }
private void AddNotification( private async void AddNotification(
NotificationGroupDefinition notificationGroup, NotificationGroupDefinition notificationGroup,
NotificationDefinitionRecord notificationRecord) NotificationDefinitionRecord notificationRecord)
{ {
ILocalizableString description = null;
if (!notificationRecord.Description.IsNullOrWhiteSpace())
{
description = LocalizableStringSerializer.Deserialize(notificationRecord.Description);
}
var notification = notificationGroup.AddNotification( var notification = notificationGroup.AddNotification(
notificationRecord.Name, notificationRecord.Name,
LocalizableStringSerializer.Deserialize(notificationRecord.DisplayName), LocalizableStringSerializer.Deserialize(notificationRecord.DisplayName),
LocalizableStringSerializer.Deserialize(notificationRecord.Description), description,
notificationRecord.NotificationType, notificationRecord.NotificationType,
notificationRecord.NotificationLifetime, notificationRecord.NotificationLifetime,
notificationRecord.ContentType, notificationRecord.ContentType,
@ -102,7 +122,13 @@ public class DynamicNotificationDefinitionInMemoryCache : IDynamicNotificationDe
if (!notificationRecord.Providers.IsNullOrWhiteSpace()) if (!notificationRecord.Providers.IsNullOrWhiteSpace())
{ {
notification.Providers.AddRange(notificationRecord.Providers.Split(',', ';')); notification.Providers.AddRange(notificationRecord.Providers.Split(','));
}
if (!notificationRecord.Template.IsNullOrWhiteSpace())
{
var templateDefinition = await TemplateDefinitionManager.GetOrNullAsync(notificationRecord.Template);
notification.WithTemplate(templateDefinition);
} }
foreach (var property in notificationRecord.ExtraProperties) foreach (var property in notificationRecord.ExtraProperties)

14
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionStore.cs

@ -72,6 +72,20 @@ public class DynamicNotificationDefinitionStore : IDynamicNotificationDefinition
} }
} }
public async virtual Task<NotificationGroupDefinition> GetGroupOrNullAsync(string name)
{
if (!NotificationManagementOptions.IsDynamicNotificationsStoreEnabled)
{
return null;
}
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetNotificationGroupOrNull(name);
}
}
public async virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync() public async virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync()
{ {
if (!NotificationManagementOptions.IsDynamicNotificationsStoreEnabled) if (!NotificationManagementOptions.IsDynamicNotificationsStoreEnabled)

66
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionStoreCacheInvalidator.cs

@ -0,0 +1,66 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.Notifications;
public class DynamicNotificationDefinitionStoreCacheInvalidator :
ILocalEventHandler<EntityChangedEventData<NotificationDefinitionGroupRecord>>,
ILocalEventHandler<EntityChangedEventData<NotificationDefinitionRecord>>,
ITransientDependency
{
private readonly IDynamicNotificationDefinitionStoreCache _storeCache;
private readonly IClock _clock;
private readonly IDistributedCache _distributedCache;
private readonly AbpDistributedCacheOptions _cacheOptions;
private readonly AbpNotificationsManagementOptions _managementOptions;
public DynamicNotificationDefinitionStoreCacheInvalidator(
IClock clock,
IDistributedCache distributedCache,
IDynamicNotificationDefinitionStoreCache storeCache,
IOptions<AbpDistributedCacheOptions> cacheOptions,
IOptions<AbpNotificationsManagementOptions> managementOptions)
{
_storeCache = storeCache;
_clock = clock;
_distributedCache = distributedCache;
_cacheOptions = cacheOptions.Value;
_managementOptions = managementOptions.Value;
}
public async virtual Task HandleEventAsync(EntityChangedEventData<NotificationDefinitionGroupRecord> eventData)
{
await RemoveStampInDistributedCacheAsync();
}
public async virtual Task HandleEventAsync(EntityChangedEventData<NotificationDefinitionRecord> eventData)
{
await RemoveStampInDistributedCacheAsync();
}
protected async virtual Task RemoveStampInDistributedCacheAsync()
{
using (await _storeCache.SyncSemaphore.LockAsync())
{
var cacheKey = GetCommonStampCacheKey();
await _distributedCache.RemoveAsync(cacheKey);
_storeCache.CacheStamp = Guid.NewGuid().ToString();
_storeCache.LastCheckTime = _clock.Now.AddMinutes(-_managementOptions.NotificationsCacheRefreshInterval.Minutes - 5);
}
}
protected virtual string GetCommonStampCacheKey()
{
return $"{_cacheOptions.KeyPrefix}_AbpInMemoryNotificationCacheStamp";
}
}

2
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionStoreCache.cs

@ -21,5 +21,7 @@ public interface IDynamicNotificationDefinitionStoreCache
IReadOnlyList<NotificationDefinition> GetNotifications(); IReadOnlyList<NotificationDefinition> GetNotifications();
NotificationGroupDefinition GetNotificationGroupOrNull(string name);
IReadOnlyList<NotificationGroupDefinition> GetGroups(); IReadOnlyList<NotificationGroupDefinition> GetGroups();
} }

3
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/INotificationDefinitionGroupRecordRepository.cs

@ -1,8 +1,11 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
namespace LINGYUN.Abp.Notifications; namespace LINGYUN.Abp.Notifications;
public interface INotificationDefinitionGroupRecordRepository : IBasicRepository<NotificationDefinitionGroupRecord, Guid> public interface INotificationDefinitionGroupRecordRepository : IBasicRepository<NotificationDefinitionGroupRecord, Guid>
{ {
Task<NotificationDefinitionGroupRecord> FindByNameAsync(string name, CancellationToken cancellationToken = default);
} }

3
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/INotificationDefinitionRecordRepository.cs

@ -1,8 +1,11 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
namespace LINGYUN.Abp.Notifications; namespace LINGYUN.Abp.Notifications;
public interface INotificationDefinitionRecordRepository : IBasicRepository<NotificationDefinitionRecord, Guid> public interface INotificationDefinitionRecordRepository : IBasicRepository<NotificationDefinitionRecord, Guid>
{ {
Task<NotificationDefinitionRecord> FindByNameAsync(string name, CancellationToken cancellationToken = default);
} }

20
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/NotificationDefinitionGroupRecord.cs

@ -67,6 +67,16 @@ public class NotificationDefinitionGroupRecord : BasicAggregateRoot<Guid>, IHasE
return false; return false;
} }
if (Description != otherRecord.Description)
{
return false;
}
if (AllowSubscriptionToClients != otherRecord.AllowSubscriptionToClients)
{
return false;
}
if (!this.HasSameExtraProperties(otherRecord)) if (!this.HasSameExtraProperties(otherRecord))
{ {
return false; return false;
@ -87,6 +97,16 @@ public class NotificationDefinitionGroupRecord : BasicAggregateRoot<Guid>, IHasE
DisplayName = otherRecord.DisplayName; DisplayName = otherRecord.DisplayName;
} }
if (Description != otherRecord.Description)
{
Description = otherRecord.Description;
}
if (AllowSubscriptionToClients != otherRecord.AllowSubscriptionToClients)
{
AllowSubscriptionToClients = otherRecord.AllowSubscriptionToClients;
}
if (!this.HasSameExtraProperties(otherRecord)) if (!this.HasSameExtraProperties(otherRecord))
{ {
ExtraProperties.Clear(); ExtraProperties.Clear();

66
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/NotificationDefinitionRecord.cs

@ -32,6 +32,10 @@ public class NotificationDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraP
/// </remarks> /// </remarks>
public virtual string Description { get; set; } public virtual string Description { get; set; }
/// <summary> /// <summary>
/// 通知模板
/// </summary>
public virtual string Template { get; set; }
/// <summary>
/// 存活类型 /// 存活类型
/// </summary> /// </summary>
public virtual NotificationLifetime NotificationLifetime { get; set; } public virtual NotificationLifetime NotificationLifetime { get; set; }
@ -68,6 +72,7 @@ public class NotificationDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraP
string groupName, string groupName,
string displayName = null, string displayName = null,
string description = null, string description = null,
string template = null,
NotificationLifetime lifetime = NotificationLifetime.Persistent, NotificationLifetime lifetime = NotificationLifetime.Persistent,
NotificationType notificationType = NotificationType.Application, NotificationType notificationType = NotificationType.Application,
NotificationContentType contentType = NotificationContentType.Text) NotificationContentType contentType = NotificationContentType.Text)
@ -77,6 +82,7 @@ public class NotificationDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraP
GroupName = Check.NotNullOrWhiteSpace(groupName, nameof(groupName), NotificationDefinitionGroupRecordConsts.MaxNameLength); GroupName = Check.NotNullOrWhiteSpace(groupName, nameof(groupName), NotificationDefinitionGroupRecordConsts.MaxNameLength);
DisplayName = Check.Length(displayName, nameof(displayName), NotificationDefinitionRecordConsts.MaxDisplayNameLength); DisplayName = Check.Length(displayName, nameof(displayName), NotificationDefinitionRecordConsts.MaxDisplayNameLength);
Description = Check.Length(description, nameof(description), NotificationDefinitionRecordConsts.MaxDescriptionLength); Description = Check.Length(description, nameof(description), NotificationDefinitionRecordConsts.MaxDescriptionLength);
Template = Check.Length(template, nameof(template), NotificationDefinitionRecordConsts.MaxTemplateLength);
NotificationLifetime = lifetime; NotificationLifetime = lifetime;
NotificationType = notificationType; NotificationType = notificationType;
ContentType = contentType; ContentType = contentType;
@ -121,11 +127,41 @@ public class NotificationDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraP
return false; return false;
} }
if (Description != otherRecord.Description)
{
return false;
}
if (Providers != otherRecord.Providers) if (Providers != otherRecord.Providers)
{ {
return false; return false;
} }
if (Template != otherRecord.Template)
{
return false;
}
if (ContentType != otherRecord.ContentType)
{
return false;
}
if (NotificationLifetime != otherRecord.NotificationLifetime)
{
return false;
}
if (NotificationType != otherRecord.NotificationType)
{
return false;
}
if (AllowSubscriptionToClients != otherRecord.AllowSubscriptionToClients)
{
return false;
}
if (!this.HasSameExtraProperties(otherRecord)) if (!this.HasSameExtraProperties(otherRecord))
{ {
return false; return false;
@ -151,11 +187,41 @@ public class NotificationDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraP
DisplayName = otherRecord.DisplayName; DisplayName = otherRecord.DisplayName;
} }
if (Description != otherRecord.Description)
{
Description = otherRecord.Description;
}
if (Providers != otherRecord.Providers) if (Providers != otherRecord.Providers)
{ {
Providers = otherRecord.Providers; Providers = otherRecord.Providers;
} }
if (Template != otherRecord.Template)
{
Template = otherRecord.Template;
}
if (ContentType != otherRecord.ContentType)
{
ContentType = otherRecord.ContentType;
}
if (NotificationLifetime != otherRecord.NotificationLifetime)
{
NotificationLifetime = otherRecord.NotificationLifetime;
}
if (NotificationType != otherRecord.NotificationType)
{
NotificationType = otherRecord.NotificationType;
}
if (AllowSubscriptionToClients != otherRecord.AllowSubscriptionToClients)
{
AllowSubscriptionToClients = otherRecord.AllowSubscriptionToClients;
}
if (!this.HasSameExtraProperties(otherRecord)) if (!this.HasSameExtraProperties(otherRecord))
{ {
ExtraProperties.Clear(); ExtraProperties.Clear();

1
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/NotificationDefinitionSerializer.cs

@ -76,6 +76,7 @@ public class NotificationDefinitionSerializer : INotificationDefinitionSerialize
notificationGroup?.Name, notificationGroup?.Name,
LocalizableStringSerializer.Serialize(notification.DisplayName), LocalizableStringSerializer.Serialize(notification.DisplayName),
LocalizableStringSerializer.Serialize(notification.Description), LocalizableStringSerializer.Serialize(notification.Description),
notification.Template?.Name,
notification.NotificationLifetime, notification.NotificationLifetime,
notification.NotificationType, notification.NotificationType,
notification.ContentType notification.ContentType

14
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.EntityFrameworkCore/LINGYUN/Abp/Notifications/EntityFrameworkCore/EfCoreNotificationDefinitionGroupRecordRepository.cs

@ -1,4 +1,9 @@
using System; using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
@ -15,4 +20,11 @@ public class EfCoreNotificationDefinitionGroupRecordRepository :
: base(dbContextProvider) : base(dbContextProvider)
{ {
} }
public async virtual Task<NotificationDefinitionGroupRecord> FindByNameAsync(string name, CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync())
.Where(x => x.Name == name)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
}
} }

13
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.EntityFrameworkCore/LINGYUN/Abp/Notifications/EntityFrameworkCore/EfCoreNotificationDefinitionRecordRepository.cs

@ -1,4 +1,8 @@
using System; using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
@ -15,4 +19,11 @@ public class EfCoreNotificationDefinitionRecordRepository :
: base(dbContextProvider) : base(dbContextProvider)
{ {
} }
public async virtual Task<NotificationDefinitionRecord> FindByNameAsync(string name, CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync())
.Where(x => x.Name == name)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
}
} }

59
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.HttpApi/LINGYUN/Abp/Notifications/Definitions/Groups/NotificationGroupDefinitionController.cs

@ -0,0 +1,59 @@
using LINGYUN.Abp.Notifications.Permissions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc;
namespace LINGYUN.Abp.Notifications.Definitions.Groups;
[Controller]
[Authorize(NotificationsPermissions.GroupDefinition.Default)]
[RemoteService(Name = AbpNotificationsRemoteServiceConsts.RemoteServiceName)]
[Area(AbpNotificationsRemoteServiceConsts.ModuleName)]
[Route("api/notifications/definitions/groups")]
public class NotificationGroupDefinitionController : AbpControllerBase, INotificationGroupDefinitionAppService
{
private readonly INotificationGroupDefinitionAppService _service;
public NotificationGroupDefinitionController(INotificationGroupDefinitionAppService service)
{
_service = service;
}
[HttpPost]
[Authorize(NotificationsPermissions.GroupDefinition.Create)]
public virtual Task<NotificationGroupDefinitionDto> CreateAsync(NotificationGroupDefinitionCreateDto input)
{
return _service.CreateAsync(input);
}
[HttpDelete]
[Route("{name}")]
[Authorize(NotificationsPermissions.GroupDefinition.Delete)]
public virtual Task DeleteAsync(string name)
{
return _service.DeleteAsync(name);
}
[HttpGet]
[Route("{name}")]
public virtual Task<NotificationGroupDefinitionDto> GetAsync(string name)
{
return _service.GetAsync(name);
}
[HttpGet]
public virtual Task<ListResultDto<NotificationGroupDefinitionDto>> GetListAsync(NotificationGroupDefinitionGetListInput input)
{
return _service.GetListAsync(input);
}
[HttpPut]
[Route("{name}")]
[Authorize(NotificationsPermissions.GroupDefinition.Update)]
public virtual Task<NotificationGroupDefinitionDto> UpdateAsync(string name, NotificationGroupDefinitionUpdateDto input)
{
return _service.UpdateAsync(name, input);
}
}

59
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.HttpApi/LINGYUN/Abp/Notifications/Definitions/Notifications/NotificationDefinitionController.cs

@ -0,0 +1,59 @@
using LINGYUN.Abp.Notifications.Permissions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc;
namespace LINGYUN.Abp.Notifications.Definitions.Notifications;
[Controller]
[Authorize(NotificationsPermissions.Definition.Default)]
[RemoteService(Name = AbpNotificationsRemoteServiceConsts.RemoteServiceName)]
[Area(AbpNotificationsRemoteServiceConsts.ModuleName)]
[Route("api/notifications/definitions/notifications")]
public class NotificationDefinitionController : AbpControllerBase, INotificationDefinitionAppService
{
private readonly INotificationDefinitionAppService _service;
public NotificationDefinitionController(INotificationDefinitionAppService service)
{
_service = service;
}
[HttpPost]
[Authorize(NotificationsPermissions.Definition.Create)]
public virtual Task<NotificationDefinitionDto> CreateAsync(NotificationDefinitionCreateDto input)
{
return _service.CreateAsync(input);
}
[HttpDelete]
[Route("{name}")]
[Authorize(NotificationsPermissions.Definition.Delete)]
public virtual Task DeleteAsync(string name)
{
return _service.DeleteAsync(name);
}
[HttpGet]
[Route("{name}")]
public virtual Task<NotificationDefinitionDto> GetAsync(string name)
{
return _service.GetAsync(name);
}
[HttpGet]
public virtual Task<ListResultDto<NotificationDefinitionDto>> GetListAsync(NotificationDefinitionGetListInput input)
{
return _service.GetListAsync(input);
}
[HttpPut]
[Route("{name}")]
[Authorize(NotificationsPermissions.Definition.Update)]
public virtual Task<NotificationDefinitionDto> UpdateAsync(string name, NotificationDefinitionUpdateDto input)
{
return _service.UpdateAsync(name, input);
}
}

20
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.HttpApi/LINGYUN/Abp/Notifications/NotificationController.cs

@ -23,22 +23,30 @@ public class NotificationController : AbpController, INotificationAppService
} }
[HttpPost] [HttpPost]
public async virtual Task SendAsync(NotificationSendDto input) [Route("send")]
public virtual Task SendAsync(NotificationSendDto input)
{ {
await NotificationAppService.SendAsync(input); return NotificationAppService.SendAsync(input);
}
[HttpPost]
[Route("send/template")]
public virtual Task SendAsync(NotificationTemplateSendDto input)
{
return NotificationAppService.SendAsync(input);
} }
[HttpGet] [HttpGet]
[Route("assignables")] [Route("assignables")]
public async virtual Task<ListResultDto<NotificationGroupDto>> GetAssignableNotifiersAsync() public virtual Task<ListResultDto<NotificationGroupDto>> GetAssignableNotifiersAsync()
{ {
return await NotificationAppService.GetAssignableNotifiersAsync(); return NotificationAppService.GetAssignableNotifiersAsync();
} }
[HttpGet] [HttpGet]
[Route("assignable-templates")] [Route("assignable-templates")]
public async virtual Task<ListResultDto<NotificationTemplateDto>> GetAssignableTemplatesAsync() public virtual Task<ListResultDto<NotificationTemplateDto>> GetAssignableTemplatesAsync()
{ {
return await NotificationAppService.GetAssignableTemplatesAsync(); return NotificationAppService.GetAssignableTemplatesAsync();
} }
} }

13
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Localization/Resources/en.json

@ -3,6 +3,7 @@
"texts": { "texts": {
"Notifications": "Notifications", "Notifications": "Notifications",
"Notifications:Title": "Title", "Notifications:Title": "Title",
"Notifications:Message": "Message",
"Notifications:Content": "Content", "Notifications:Content": "Content",
"Notifications:Type": "Type", "Notifications:Type": "Type",
"Notifications:SendTime": "SendTime", "Notifications:SendTime": "SendTime",
@ -27,11 +28,23 @@
"Notifications:ActivityNoticeDesc": "The administrator delivers an event notification push", "Notifications:ActivityNoticeDesc": "The administrator delivers an event notification push",
"Notifications:SystemNotice": "System", "Notifications:SystemNotice": "System",
"Notifications:SystemNoticeDesc": "System global notification push", "Notifications:SystemNoticeDesc": "System global notification push",
"Notifications:ToUsers": "To Users",
"MarkSelectedAsRead": "Mark selected as read", "MarkSelectedAsRead": "Mark selected as read",
"NotificationType:Application": "Platform", "NotificationType:Application": "Platform",
"NotificationType:System": "System", "NotificationType:System": "System",
"NotificationType:User": "User", "NotificationType:User": "User",
"NotificationType:ServiceCallback": "Service Callback", "NotificationType:ServiceCallback": "Service Callback",
"NotificationLifetime:OnlyOne": "Only One",
"NotificationLifetime:Persistent": "Persistent",
"NotificationContentType:Text": "Text",
"NotificationContentType:Json": "Json",
"NotificationContentType:Html": "Html",
"NotificationContentType:Markdown": "Markdown",
"NotificationSeverity:Success": "Success",
"NotificationSeverity:Info": "Info",
"NotificationSeverity:Warn": "Warn",
"NotificationSeverity:Fatal": "Fatal",
"NotificationSeverity:Error": "Error",
"MarkAs": "Mark as", "MarkAs": "Mark as",
"Read": "Read", "Read": "Read",
"UnRead": "Un Read" "UnRead": "Un Read"

13
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Localization/Resources/zh-Hans.json

@ -3,6 +3,7 @@
"texts": { "texts": {
"Notifications": "通知列表", "Notifications": "通知列表",
"Notifications:Title": "标题", "Notifications:Title": "标题",
"Notifications:Message": "消息",
"Notifications:Content": "内容", "Notifications:Content": "内容",
"Notifications:Type": "类型", "Notifications:Type": "类型",
"Notifications:SendTime": "发送时间", "Notifications:SendTime": "发送时间",
@ -27,11 +28,23 @@
"Notifications:ActivityNoticeDesc": "管理员下发活动通知推送", "Notifications:ActivityNoticeDesc": "管理员下发活动通知推送",
"Notifications:SystemNotice": "系统通知", "Notifications:SystemNotice": "系统通知",
"Notifications:SystemNoticeDesc": "系统全局消息推送", "Notifications:SystemNoticeDesc": "系统全局消息推送",
"Notifications:ToUsers": "接收用户",
"MarkSelectedAsRead": "标记选中已读", "MarkSelectedAsRead": "标记选中已读",
"NotificationType:Application": "平台", "NotificationType:Application": "平台",
"NotificationType:System": "系统", "NotificationType:System": "系统",
"NotificationType:User": "用户", "NotificationType:User": "用户",
"NotificationType:ServiceCallback": "服务回调", "NotificationType:ServiceCallback": "服务回调",
"NotificationLifetime:OnlyOne": "一次性",
"NotificationLifetime:Persistent": "持久化",
"NotificationContentType:Text": "Text",
"NotificationContentType:Json": "Json",
"NotificationContentType:Html": "Html",
"NotificationContentType:Markdown": "Markdown",
"NotificationSeverity:Success": "成功",
"NotificationSeverity:Info": "信息",
"NotificationSeverity:Warn": "警告",
"NotificationSeverity:Fatal": "致命",
"NotificationSeverity:Error": "错误",
"MarkAs": "标记为", "MarkAs": "标记为",
"Read": "已读", "Read": "已读",
"UnRead": "未读" "UnRead": "未读"

44
aspnet-core/services/LY.MicroService.Applications.Single/BackgroundJobs/NotificationPublishJob.cs

@ -0,0 +1,44 @@
using LINGYUN.Abp.Notifications;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
namespace LY.MicroService.Applications.Single.BackgroundJobs;
public class NotificationPublishJob : AsyncBackgroundJob<NotificationPublishJobArgs>, ITransientDependency
{
protected AbpNotificationsPublishOptions Options { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected INotificationDataSerializer NotificationDataSerializer { get; }
public NotificationPublishJob(
IOptions<AbpNotificationsPublishOptions> options,
IServiceScopeFactory serviceScopeFactory,
INotificationDataSerializer notificationDataSerializer)
{
Options = options.Value;
ServiceScopeFactory = serviceScopeFactory;
NotificationDataSerializer = notificationDataSerializer;
}
public override async Task ExecuteAsync(NotificationPublishJobArgs args)
{
var providerType = Type.GetType(args.ProviderType);
using (var scope = ServiceScopeFactory.CreateScope())
{
if (scope.ServiceProvider.GetRequiredService(providerType) is INotificationPublishProvider publishProvider)
{
var store = scope.ServiceProvider.GetRequiredService<INotificationStore>();
var notification = await store.GetNotificationOrNullAsync(args.TenantId, args.NotificationId);
notification.Data = NotificationDataSerializer.Serialize(notification.Data);
var notifacationDataMapping = Options.NotificationDataMappings
.GetMapItemOrDefault(notification.Name, publishProvider.Name);
if (notifacationDataMapping != null)
{
notification.Data = notifacationDataMapping.MappingFunc(notification.Data);
}
await publishProvider.PublishAsync(notification, args.UserIdentifiers);
}
}
}
}

22
aspnet-core/services/LY.MicroService.Applications.Single/BackgroundJobs/NotificationPublishJobArgs.cs

@ -0,0 +1,22 @@
using LINGYUN.Abp.Notifications;
namespace LY.MicroService.Applications.Single.BackgroundJobs;
public class NotificationPublishJobArgs
{
public Guid? TenantId { get; set; }
public long NotificationId { get; set; }
public string ProviderType { get; set; }
public List<UserIdentifier> UserIdentifiers { get; set; }
public NotificationPublishJobArgs()
{
UserIdentifiers = new List<UserIdentifier>();
}
public NotificationPublishJobArgs(long id, string providerType, List<UserIdentifier> userIdentifiers, Guid? tenantId = null)
{
NotificationId = id;
ProviderType = providerType;
UserIdentifiers = userIdentifiers;
TenantId = tenantId;
}
}

59
aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/ChatMessageEventHandler.cs

@ -0,0 +1,59 @@
using LINGYUN.Abp.IM;
using LINGYUN.Abp.IM.Messages;
using LINGYUN.Abp.RealTime;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
namespace LY.MicroService.Applications.Single.EventBus.Distributed
{
public class ChatMessageEventHandler : IDistributedEventHandler<RealTimeEto<ChatMessage>>, ITransientDependency
{
/// <summary>
/// Reference to <see cref="ILogger<DefaultNotificationDispatcher>"/>.
/// </summary>
public ILogger<ChatMessageEventHandler> Logger { get; set; }
/// <summary>
/// Reference to <see cref="AbpIMOptions"/>.
/// </summary>
protected AbpIMOptions Options { get; }
protected IMessageStore MessageStore { get; }
protected IMessageBlocker MessageBlocker { get; }
protected IMessageSenderProviderManager MessageSenderProviderManager { get; }
public ChatMessageEventHandler(
IOptions<AbpIMOptions> options,
IMessageStore messageStore,
IMessageBlocker messageBlocker,
IMessageSenderProviderManager messageSenderProviderManager)
{
Options = options.Value;
MessageStore = messageStore;
MessageBlocker = messageBlocker;
MessageSenderProviderManager = messageSenderProviderManager;
Logger = NullLogger<ChatMessageEventHandler>.Instance;
}
public async virtual Task HandleEventAsync(RealTimeEto<ChatMessage> eventData)
{
Logger.LogDebug($"Persistent chat message.");
var message = eventData.Data;
// 消息拦截
// 扩展敏感词汇过滤
await MessageBlocker.InterceptAsync(message);
await MessageStore.StoreMessageAsync(message);
// 发送消息
foreach (var provider in MessageSenderProviderManager.Providers)
{
Logger.LogDebug($"Sending message with provider {provider.Name}");
await provider.SendMessageAsync(message);
}
}
}
}

467
aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/NotificationEventHandler.cs

@ -0,0 +1,467 @@
using LINGYUN.Abp.Notifications;
using LY.MicroService.Applications.Single.BackgroundJobs;
using LY.MicroService.Applications.Single.MultiTenancy;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System.Globalization;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Json;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.TextTemplating;
using Volo.Abp.Uow;
namespace LY.MicroService.Applications.Single.EventBus.Distributed
{
/// <summary>
/// 订阅通知发布事件,统一发布消息
/// </summary>
/// <remarks>
/// 作用在于SignalR客户端只会与一台服务器建立连接,
/// 只有启用了SignlR服务端的才能真正将消息发布到客户端
/// </remarks>
public class NotificationEventHandler :
IDistributedEventHandler<NotificationEto<NotificationData>>,
IDistributedEventHandler<NotificationEto<NotificationTemplate>>,
ITransientDependency
{
/// <summary>
/// Reference to <see cref="ILogger<DefaultNotificationDispatcher>"/>.
/// </summary>
public ILogger<NotificationEventHandler> Logger { get; set; }
/// <summary>
/// Reference to <see cref="AbpNotificationsPublishOptions"/>.
/// </summary>
protected AbpNotificationsPublishOptions Options { get; }
/// <summary>
/// Reference to <see cref="ICurrentTenant"/>.
/// </summary>
protected ICurrentTenant CurrentTenant { get; }
/// <summary>
/// Reference to <see cref="ITenantConfigurationCache"/>.
/// </summary>
protected ITenantConfigurationCache TenantConfigurationCache { get; }
/// <summary>
/// Reference to <see cref="IJsonSerializer"/>.
/// </summary>
protected IJsonSerializer JsonSerializer { get; }
/// <summary>
/// Reference to <see cref="IBackgroundJobManager"/>.
/// </summary>
protected IBackgroundJobManager BackgroundJobManager { get; }
/// <summary>
/// Reference to <see cref="ITemplateRenderer"/>.
/// </summary>
protected ITemplateRenderer TemplateRenderer { get; }
/// <summary>
/// Reference to <see cref="INotificationStore"/>.
/// </summary>
protected INotificationStore NotificationStore { get; }
/// <summary>
/// Reference to <see cref="IStringLocalizerFactory"/>.
/// </summary>
protected IStringLocalizerFactory StringLocalizerFactory { get; }
/// <summary>
/// Reference to <see cref="INotificationDataSerializer"/>.
/// </summary>
protected INotificationDataSerializer NotificationDataSerializer { get; }
/// <summary>
/// Reference to <see cref="INotificationDefinitionManager"/>.
/// </summary>
protected INotificationDefinitionManager NotificationDefinitionManager { get; }
/// <summary>
/// Reference to <see cref="INotificationSubscriptionManager"/>.
/// </summary>
protected INotificationSubscriptionManager NotificationSubscriptionManager { get; }
/// <summary>
/// Reference to <see cref="INotificationPublishProviderManager"/>.
/// </summary>
protected INotificationPublishProviderManager NotificationPublishProviderManager { get; }
/// <summary>
/// Initializes a new instance of the <see cref="NotificationEventHandler"/> class.
/// </summary>
public NotificationEventHandler(
ICurrentTenant currentTenant,
ITenantConfigurationCache tenantConfigurationCache,
IJsonSerializer jsonSerializer,
ITemplateRenderer templateRenderer,
IBackgroundJobManager backgroundJobManager,
IStringLocalizerFactory stringLocalizerFactory,
IOptions<AbpNotificationsPublishOptions> options,
INotificationStore notificationStore,
INotificationDataSerializer notificationDataSerializer,
INotificationDefinitionManager notificationDefinitionManager,
INotificationSubscriptionManager notificationSubscriptionManager,
INotificationPublishProviderManager notificationPublishProviderManager)
{
Options = options.Value;
TenantConfigurationCache = tenantConfigurationCache;
CurrentTenant = currentTenant;
JsonSerializer = jsonSerializer;
TemplateRenderer = templateRenderer;
BackgroundJobManager = backgroundJobManager;
StringLocalizerFactory = stringLocalizerFactory;
NotificationStore = notificationStore;
NotificationDataSerializer = notificationDataSerializer;
NotificationDefinitionManager = notificationDefinitionManager;
NotificationSubscriptionManager = notificationSubscriptionManager;
NotificationPublishProviderManager = notificationPublishProviderManager;
Logger = NullLogger<NotificationEventHandler>.Instance;
}
[UnitOfWork]
public async virtual Task HandleEventAsync(NotificationEto<NotificationTemplate> eventData)
{
var notification = await NotificationDefinitionManager.GetOrNullAsync(eventData.Name);
if (notification == null)
{
return;
}
var culture = eventData.Data.Culture;
if (culture.IsNullOrWhiteSpace())
{
culture = CultureInfo.CurrentCulture.Name;
}
using (CultureHelper.Use(culture, culture))
{
if (notification.NotificationType == NotificationType.System)
{
using (CurrentTenant.Change(null))
{
await SendToTenantAsync(null, notification, eventData);
var allActiveTenants = await TenantConfigurationCache.GetTenantsAsync();
foreach (var activeTenant in allActiveTenants)
{
await SendToTenantAsync(activeTenant.Id, notification, eventData);
}
}
}
else
{
await SendToTenantAsync(eventData.TenantId, notification, eventData);
}
}
}
[UnitOfWork]
public async virtual Task HandleEventAsync(NotificationEto<NotificationData> eventData)
{
var notification = await NotificationDefinitionManager.GetOrNullAsync(eventData.Name);
if (notification == null)
{
return;
}
if (notification.NotificationType == NotificationType.System)
{
using (CurrentTenant.Change(null))
{
await SendToTenantAsync(null, notification, eventData);
var allActiveTenants = await TenantConfigurationCache.GetTenantsAsync();
foreach (var activeTenant in allActiveTenants)
{
await SendToTenantAsync(activeTenant.Id, notification, eventData);
}
}
}
else
{
await SendToTenantAsync(eventData.TenantId, notification, eventData);
}
}
protected async virtual Task SendToTenantAsync(
Guid? tenantId,
NotificationDefinition notification,
NotificationEto<NotificationTemplate> eventData)
{
using (CurrentTenant.Change(tenantId))
{
var providers = Enumerable.Reverse(NotificationPublishProviderManager.Providers);
// 过滤用户指定提供者
if (eventData.UseProviders.Any())
{
providers = providers.Where(p => eventData.UseProviders.Contains(p.Name));
}
else if (notification.Providers.Any())
{
providers = providers.Where(p => notification.Providers.Contains(p.Name));
}
var notificationInfo = new NotificationInfo
{
Name = notification.Name,
TenantId = tenantId,
Severity = eventData.Severity,
Type = notification.NotificationType,
ContentType = notification.ContentType,
CreationTime = eventData.CreationTime,
Lifetime = notification.NotificationLifetime,
};
notificationInfo.SetId(eventData.Id);
var title = notification.DisplayName.Localize(StringLocalizerFactory);
var message = "";
try
{
// 由于模板通知受租户影响, 格式化失败的消息将被丢弃.
message = await TemplateRenderer.RenderAsync(
templateName: eventData.Data.Name,
model: eventData.Data.ExtraProperties,
cultureName: eventData.Data.Culture,
globalContext: new Dictionary<string, object>
{
// 模板不支持 $ 字符, 改为普通关键字
{ NotificationKeywords.Name, notification.Name },
{ NotificationKeywords.FormUser, eventData.Data.FormUser },
{ NotificationKeywords.Id, eventData.Id },
{ NotificationKeywords.Title, title.ToString() },
{ NotificationKeywords.CreationTime, eventData.CreationTime.ToString(Options.DateTimeFormat) },
});
}
catch(Exception ex)
{
Logger.LogWarning("Formatting template notification failed, message will be discarded, cause :{message}", ex.Message);
return;
}
var notificationData = new NotificationData();
notificationData.WriteStandardData(
title: title.ToString(),
message: message,
createTime: eventData.CreationTime,
formUser: eventData.Data.FormUser);
notificationData.ExtraProperties.AddIfNotContains(eventData.Data.ExtraProperties);
notificationInfo.Data = notificationData;
var subscriptionUsers = await GerSubscriptionUsersAsync(
notificationInfo.Name,
eventData.Users,
tenantId);
await PersistentNotificationAsync(
notificationInfo,
subscriptionUsers,
providers);
if (subscriptionUsers.Any())
{
// 发布通知
foreach (var provider in providers)
{
await PublishToSubscriberAsync(provider, notificationInfo, subscriptionUsers);
}
}
}
}
protected async virtual Task SendToTenantAsync(
Guid? tenantId,
NotificationDefinition notification,
NotificationEto<NotificationData> eventData)
{
using (CurrentTenant.Change(tenantId))
{
var providers = Enumerable.Reverse(NotificationPublishProviderManager.Providers);
// 过滤用户指定提供者
if (eventData.UseProviders.Any())
{
providers = providers.Where(p => eventData.UseProviders.Contains(p.Name));
}
else if (notification.Providers.Any())
{
providers = providers.Where(p => notification.Providers.Contains(p.Name));
}
var notificationInfo = new NotificationInfo
{
Name = notification.Name,
CreationTime = eventData.CreationTime,
Data = eventData.Data,
Severity = eventData.Severity,
Lifetime = notification.NotificationLifetime,
TenantId = tenantId,
Type = notification.NotificationType,
ContentType = notification.ContentType,
};
notificationInfo.SetId(eventData.Id);
notificationInfo.Data = NotificationDataSerializer.Serialize(notificationInfo.Data);
// 获取用户订阅
var subscriptionUsers = await GerSubscriptionUsersAsync(
notificationInfo.Name,
eventData.Users,
tenantId);
// 持久化通知
await PersistentNotificationAsync(
notificationInfo,
subscriptionUsers,
providers);
if (subscriptionUsers.Any())
{
// 发布订阅通知
foreach (var provider in providers)
{
await PublishToSubscriberAsync(provider, notificationInfo, subscriptionUsers);
}
}
}
}
/// <summary>
/// 获取用户订阅列表
/// </summary>
/// <param name="notificationName">通知名称</param>
/// <param name="sendToUsers">接收用户列表</param>
/// <param name="tenantId">租户标识</param>
/// <returns>用户订阅列表</returns>
protected async Task<IEnumerable<UserIdentifier>> GerSubscriptionUsersAsync(
string notificationName,
IEnumerable<UserIdentifier> sendToUsers,
Guid? tenantId = null)
{
try
{
// 获取用户订阅列表
var userSubscriptions = await NotificationSubscriptionManager.GetUsersSubscriptionsAsync(
tenantId,
notificationName,
sendToUsers);
return userSubscriptions.Select(us => new UserIdentifier(us.UserId, us.UserName));
}
catch(Exception ex)
{
Logger.LogWarning("Failed to get user subscription, message will not be received by the user, reason: {message}", ex.Message);
}
return new List<UserIdentifier>();
}
/// <summary>
/// 持久化通知并返回订阅用户列表
/// </summary>
/// <param name="notificationInfo">通知实体</param>
/// <param name="subscriptionUsers">订阅用户列表</param>
/// <param name="sendToProviders">通知发送提供者</param>
/// <returns>返回订阅者列表</returns>
protected async Task PersistentNotificationAsync(
NotificationInfo notificationInfo,
IEnumerable<UserIdentifier> subscriptionUsers,
IEnumerable<INotificationPublishProvider> sendToProviders)
{
try
{
// 持久化通知
await NotificationStore.InsertNotificationAsync(notificationInfo);
if (!subscriptionUsers.Any())
{
return;
}
// 持久化用户通知
await NotificationStore.InsertUserNotificationsAsync(notificationInfo, subscriptionUsers.Select(u => u.UserId));
if (notificationInfo.Lifetime == NotificationLifetime.OnlyOne)
{
// 一次性通知取消用户订阅
await NotificationStore.DeleteUserSubscriptionAsync(
notificationInfo.TenantId,
subscriptionUsers,
notificationInfo.Name);
}
}
catch (Exception ex)
{
Logger.LogWarning("Failed to persistent notification failed, reason: {message}", ex.Message);
foreach (var provider in sendToProviders)
{
// 处理持久化失败进入后台队列
await ProcessingFailedToQueueAsync(provider, notificationInfo, subscriptionUsers);
}
}
}
/// <summary>
/// 发布订阅者通知
/// </summary>
/// <param name="provider">通知发布者</param>
/// <param name="notificationInfo">通知信息</param>
/// <param name="subscriptionUserIdentifiers">订阅用户列表</param>
/// <returns></returns>
protected async Task PublishToSubscriberAsync(
INotificationPublishProvider provider,
NotificationInfo notificationInfo,
IEnumerable<UserIdentifier> subscriptionUsers)
{
try
{
Logger.LogDebug($"Sending notification with provider {provider.Name}");
var notifacationDataMapping = Options.NotificationDataMappings
.GetMapItemOrDefault(provider.Name, notificationInfo.Name);
if (notifacationDataMapping != null)
{
notificationInfo.Data = notifacationDataMapping.MappingFunc(notificationInfo.Data);
}
// 发布
await provider.PublishAsync(notificationInfo, subscriptionUsers);
Logger.LogDebug($"Send notification {notificationInfo.Name} with provider {provider.Name} was successful");
}
catch (Exception ex)
{
Logger.LogWarning($"Send notification error with provider {provider.Name}");
Logger.LogWarning($"Error message:{ex.Message}");
Logger.LogDebug($"Failed to send notification {notificationInfo.Name}. Try to push notification to background job");
// 发送失败的消息进入后台队列
await ProcessingFailedToQueueAsync(provider, notificationInfo, subscriptionUsers);
}
}
/// <summary>
/// 处理失败的消息进入后台队列
/// </summary>
/// <remarks>
/// 注: 如果入队失败,消息将被丢弃.
/// </remarks>
/// <param name="provider"></param>
/// <param name="notificationInfo"></param>
/// <param name="subscriptionUsers"></param>
/// <returns></returns>
protected async Task ProcessingFailedToQueueAsync(
INotificationPublishProvider provider,
NotificationInfo notificationInfo,
IEnumerable<UserIdentifier> subscriptionUsers)
{
try
{
// 发送失败的消息进入后台队列
await BackgroundJobManager.EnqueueAsync(
new NotificationPublishJobArgs(
notificationInfo.GetId(),
provider.GetType().AssemblyQualifiedName,
subscriptionUsers.ToList(),
notificationInfo.TenantId));
}
catch(Exception ex)
{
Logger.LogWarning("Failed to push to background job, notification will be discarded, error cause: {message}", ex.Message);
}
}
}
}

53
aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/TenantSynchronizer.cs

@ -0,0 +1,53 @@
using LINGYUN.Abp.Saas.Tenants;
using LY.MicroService.Applications.Single.MultiTenancy;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace LY.MicroService.Applications.Single.EventBus.Distributed
{
public class TenantSynchronizer :
IDistributedEventHandler<EntityCreatedEto<TenantEto>>,
IDistributedEventHandler<EntityUpdatedEto<TenantEto>>,
IDistributedEventHandler<EntityDeletedEto<TenantEto>>,
IDistributedEventHandler<TenantConnectionStringUpdatedEto>,
ITransientDependency
{
protected IDataSeeder DataSeeder { get; }
protected ITenantConfigurationCache TenantConfigurationCache { get; }
public TenantSynchronizer(
IDataSeeder dataSeeder,
ITenantConfigurationCache tenantConfigurationCache)
{
DataSeeder = dataSeeder;
TenantConfigurationCache = tenantConfigurationCache;
}
[UnitOfWork]
public async virtual Task HandleEventAsync(EntityCreatedEto<TenantEto> eventData)
{
await TenantConfigurationCache.RefreshAsync();
await DataSeeder.SeedAsync(eventData.Entity.Id);
}
public async virtual Task HandleEventAsync(EntityUpdatedEto<TenantEto> eventData)
{
await TenantConfigurationCache.RefreshAsync();
}
public async virtual Task HandleEventAsync(EntityDeletedEto<TenantEto> eventData)
{
await TenantConfigurationCache.RefreshAsync();
}
public async virtual Task HandleEventAsync(TenantConnectionStringUpdatedEto eventData)
{
await TenantConfigurationCache.RefreshAsync();
}
}
}

30
aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/UserCreateEventHandler.cs

@ -0,0 +1,30 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Users;
namespace LY.MicroService.Applications.Single.EventBus.Distributed
{
public class UserCreateEventHandler : IDistributedEventHandler<EntityCreatedEto<UserEto>>, ITransientDependency
{
private readonly ILocalEventBus _localEventBus;
public UserCreateEventHandler(
ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}
/// <summary>
/// 接收添加用户事件,发布本地事件
/// </summary>
/// <param name="eventData"></param>
/// <returns></returns>
public async Task HandleEventAsync(EntityCreatedEto<UserEto> eventData)
{
var localUserCreateEventData = new EntityCreatedEventData<UserEto>(eventData.Entity);
await _localEventBus.PublishAsync(localUserCreateEventData);
}
}
}

58
aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Local/UserCreateJoinIMEventHandler.cs

@ -0,0 +1,58 @@
using LINGYUN.Abp.MessageService.Chat;
using LINGYUN.Abp.MessageService.Notifications;
using LINGYUN.Abp.Notifications;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.Uow;
using Volo.Abp.Users;
namespace LY.MicroService.Applications.Single.EventBus.Distributed
{
public class UserCreateJoinIMEventHandler : ILocalEventHandler<EntityCreatedEventData<UserEto>>, ITransientDependency
{
private readonly IChatDataSeeder _chatDataSeeder;
private readonly INotificationSubscriptionManager _notificationSubscriptionManager;
public UserCreateJoinIMEventHandler(
IChatDataSeeder chatDataSeeder,
INotificationSubscriptionManager notificationSubscriptionManager)
{
_chatDataSeeder = chatDataSeeder;
_notificationSubscriptionManager = notificationSubscriptionManager;
}
/// <summary>
/// 接收添加用户事件,初始化IM用户种子
/// </summary>
/// <param name="eventData"></param>
/// <returns></returns>
[UnitOfWork]
public async virtual Task HandleEventAsync(EntityCreatedEventData<UserEto> eventData)
{
await SeedChatDataAsync(eventData.Entity);
await SeedUserSubscriptionNotifiersAsync(eventData.Entity);
}
protected async virtual Task SeedChatDataAsync(IUserData user)
{
await _chatDataSeeder.SeedAsync(user);
}
protected async virtual Task SeedUserSubscriptionNotifiersAsync(IUserData user)
{
var userIdentifier = new UserIdentifier(user.Id, user.UserName);
await _notificationSubscriptionManager
.SubscribeAsync(
user.TenantId,
userIdentifier,
MessageServiceNotificationNames.IM.FriendValidation);
await _notificationSubscriptionManager
.SubscribeAsync(
user.TenantId,
userIdentifier,
MessageServiceNotificationNames.IM.NewFriend);
}
}
}

69
aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Local/UserCreateSendWelcomeEventHandler.cs

@ -0,0 +1,69 @@
using LINGYUN.Abp.Notifications;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.Users;
namespace LY.MicroService.Applications.Single.EventBus
{
public class UserCreateSendWelcomeEventHandler : ILocalEventHandler<EntityCreatedEventData<UserEto>>, ITransientDependency
{
private readonly INotificationSender _notificationSender;
private readonly INotificationSubscriptionManager _notificationSubscriptionManager;
public UserCreateSendWelcomeEventHandler(
INotificationSender notificationSender,
INotificationSubscriptionManager notificationSubscriptionManager
)
{
_notificationSender = notificationSender;
_notificationSubscriptionManager = notificationSubscriptionManager;
}
public async Task HandleEventAsync(EntityCreatedEventData<UserEto> eventData)
{
var userIdentifer = new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName);
// 订阅用户欢迎消息
await SubscribeInternalNotifers(userIdentifer, eventData.Entity.TenantId);
await _notificationSender.SendNofiterAsync(
UserNotificationNames.WelcomeToApplication,
new NotificationTemplate(
UserNotificationNames.WelcomeToApplication,
formUser: eventData.Entity.UserName,
data: new Dictionary<string, object>
{
{ "name", eventData.Entity.UserName },
}),
userIdentifer,
eventData.Entity.TenantId,
NotificationSeverity.Info);
}
private async Task SubscribeInternalNotifers(UserIdentifier userIdentifer, Guid? tenantId = null)
{
// 订阅内置通知
await _notificationSubscriptionManager
.SubscribeAsync(
tenantId,
userIdentifer,
DefaultNotifications.SystemNotice);
await _notificationSubscriptionManager
.SubscribeAsync(
tenantId,
userIdentifer,
DefaultNotifications.OnsideNotice);
await _notificationSubscriptionManager
.SubscribeAsync(
tenantId,
userIdentifer,
DefaultNotifications.ActivityNotice);
// 订阅用户欢迎消息
await _notificationSubscriptionManager
.SubscribeAsync(
tenantId,
userIdentifer,
UserNotificationNames.WelcomeToApplication);
}
}
}

12
aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs

@ -8,6 +8,7 @@ using LINGYUN.Abp.Idempotent;
using LINGYUN.Abp.IdentityServer; using LINGYUN.Abp.IdentityServer;
using LINGYUN.Abp.IdentityServer.IdentityResources; using LINGYUN.Abp.IdentityServer.IdentityResources;
using LINGYUN.Abp.Localization.CultureMap; using LINGYUN.Abp.Localization.CultureMap;
using LINGYUN.Abp.Notifications;
using LINGYUN.Abp.OpenIddict.Permissions; using LINGYUN.Abp.OpenIddict.Permissions;
using LINGYUN.Abp.Saas; using LINGYUN.Abp.Saas;
using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.Application;
@ -416,6 +417,17 @@ public partial class MicroServiceApplicationsSingleModule
}); });
} }
private void ConfigureNotificationManagement(IConfiguration configuration)
{
if (configuration.GetValue<bool>("NotificationsManagement:IsDynamicStoreEnabled"))
{
Configure<AbpNotificationsManagementOptions>(options =>
{
options.IsDynamicNotificationsStoreEnabled = true;
});
}
}
private void ConfigureDistributedLock(IServiceCollection services, IConfiguration configuration) private void ConfigureDistributedLock(IServiceCollection services, IConfiguration configuration)
{ {
var distributedLockEnabled = configuration["DistributedLock:IsEnabled"]; var distributedLockEnabled = configuration["DistributedLock:IsEnabled"];

1
aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs

@ -314,6 +314,7 @@ public partial class MicroServiceApplicationsSingleModule : AbpModule
ConfigureSettingManagement(configuration); ConfigureSettingManagement(configuration);
ConfigureWebhooksManagement(configuration); ConfigureWebhooksManagement(configuration);
ConfigurePermissionManagement(configuration); ConfigurePermissionManagement(configuration);
ConfigureNotificationManagement(configuration);
ConfigureCors(context.Services, configuration); ConfigureCors(context.Services, configuration);
ConfigureDistributedLock(context.Services, configuration); ConfigureDistributedLock(context.Services, configuration);
ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment());

10
aspnet-core/services/LY.MicroService.Applications.Single/MultiTenancy/ITenantConfigurationCache.cs

@ -0,0 +1,10 @@
using Volo.Abp.MultiTenancy;
namespace LY.MicroService.Applications.Single.MultiTenancy;
public interface ITenantConfigurationCache
{
Task RefreshAsync();
Task<List<TenantConfiguration>> GetTenantsAsync();
}

59
aspnet-core/services/LY.MicroService.Applications.Single/MultiTenancy/TenantConfigurationCache.cs

@ -0,0 +1,59 @@
using LINGYUN.Abp.Saas.Tenants;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace LY.MicroService.Applications.Single.MultiTenancy;
public class TenantConfigurationCache : ITenantConfigurationCache, ITransientDependency
{
protected ITenantRepository TenantRepository { get; }
protected IDistributedCache<TenantConfigurationCacheItem> TenantCache { get; }
public TenantConfigurationCache(
ITenantRepository tenantRepository,
IDistributedCache<TenantConfigurationCacheItem> tenantCache)
{
TenantRepository = tenantRepository;
TenantCache = tenantCache;
}
public async virtual Task RefreshAsync()
{
var cacheKey = GetCacheKey();
await TenantCache.RemoveAsync(cacheKey);
}
public async virtual Task<List<TenantConfiguration>> GetTenantsAsync()
{
return (await GetForCacheItemAsync()).Tenants;
}
protected async virtual Task<TenantConfigurationCacheItem> GetForCacheItemAsync()
{
var cacheKey = GetCacheKey();
var cacheItem = await TenantCache.GetAsync(cacheKey);
if (cacheItem == null)
{
var allActiveTenants = await TenantRepository.GetListAsync();
cacheItem = new TenantConfigurationCacheItem(
allActiveTenants
.Where(t => t.IsActive)
.Select(t => new TenantConfiguration(t.Id, t.Name)
{
IsActive = t.IsActive,
}).ToList());
await TenantCache.SetAsync(cacheKey, cacheItem);
}
return cacheItem;
}
protected virtual string GetCacheKey()
{
return "_Abp_Tenant_Configuration";
}
}

19
aspnet-core/services/LY.MicroService.Applications.Single/MultiTenancy/TenantConfigurationCacheItem.cs

@ -0,0 +1,19 @@
using Volo.Abp.MultiTenancy;
namespace LY.MicroService.Applications.Single.MultiTenancy;
[IgnoreMultiTenancy]
public class TenantConfigurationCacheItem
{
public List<TenantConfiguration> Tenants { get; set; }
public TenantConfigurationCacheItem()
{
Tenants = new List<TenantConfiguration>();
}
public TenantConfigurationCacheItem(List<TenantConfiguration> tenants)
{
Tenants = tenants;
}
}

2
aspnet-core/services/LY.MicroService.Applications.Single/Properties/PublishProfiles/FolderProfile1.pubxml.user

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<_PublishTargetUrl>D:\C Sharp\Open-Sources\abp-next-admin\aspnet-core\services\LY.MicroService.Applications.Single\bin\Release\net7.0\publish\</_PublishTargetUrl> <_PublishTargetUrl>D:\C Sharp\Open-Sources\abp-next-admin\aspnet-core\services\LY.MicroService.Applications.Single\bin\Release\net7.0\publish\</_PublishTargetUrl>
<History>True|2023-10-24T01:18:20.5030667Z;True|2023-10-23T17:44:35.6749808+08:00;True|2023-10-18T17:35:46.8173884+08:00;True|2023-10-18T08:20:25.4724000+08:00;True|2023-10-17T20:41:39.8166265+08:00;True|2023-10-17T20:32:05.6551486+08:00;True|2023-10-17T20:29:19.7472269+08:00;True|2023-10-17T18:19:43.6344087+08:00;True|2023-10-17T17:20:20.0797294+08:00;True|2023-10-17T09:00:40.0972772+08:00;True|2023-10-17T08:55:45.9955777+08:00;True|2023-10-17T08:24:44.7012068+08:00;True|2023-10-16T18:10:03.1232748+08:00;True|2023-10-16T17:52:29.6719862+08:00;True|2023-10-16T16:19:59.2730598+08:00;True|2023-10-16T15:43:02.1945039+08:00;True|2023-10-16T08:02:18.1704809+08:00;True|2023-10-14T16:45:12.9838816+08:00;False|2023-10-14T16:43:45.3839438+08:00;True|2023-10-14T15:48:46.0226669+08:00;True|2023-10-14T09:19:00.9482903+08:00;True|2023-10-13T16:17:49.1884589+08:00;False|2023-10-13T16:14:57.9204224+08:00;False|2023-10-13T16:12:31.6792705+08:00;False|2023-10-13T16:09:52.4764354+08:00;True|2023-10-12T11:06:51.5823669+08:00;</History> <History>True|2023-10-27T03:11:20.2834666Z;True|2023-10-27T11:06:21.3079746+08:00;True|2023-10-27T10:48:34.6995829+08:00;True|2023-10-26T10:08:24.4497429+08:00;True|2023-10-26T10:02:10.6238014+08:00;True|2023-10-26T09:48:20.1017009+08:00;True|2023-10-26T09:33:37.3991393+08:00;True|2023-10-25T16:14:51.9658198+08:00;True|2023-10-24T09:18:20.5030667+08:00;True|2023-10-23T17:44:35.6749808+08:00;True|2023-10-18T17:35:46.8173884+08:00;True|2023-10-18T08:20:25.4724000+08:00;True|2023-10-17T20:41:39.8166265+08:00;True|2023-10-17T20:32:05.6551486+08:00;True|2023-10-17T20:29:19.7472269+08:00;True|2023-10-17T18:19:43.6344087+08:00;True|2023-10-17T17:20:20.0797294+08:00;True|2023-10-17T09:00:40.0972772+08:00;True|2023-10-17T08:55:45.9955777+08:00;True|2023-10-17T08:24:44.7012068+08:00;True|2023-10-16T18:10:03.1232748+08:00;True|2023-10-16T17:52:29.6719862+08:00;True|2023-10-16T16:19:59.2730598+08:00;True|2023-10-16T15:43:02.1945039+08:00;True|2023-10-16T08:02:18.1704809+08:00;True|2023-10-14T16:45:12.9838816+08:00;False|2023-10-14T16:43:45.3839438+08:00;True|2023-10-14T15:48:46.0226669+08:00;True|2023-10-14T09:19:00.9482903+08:00;True|2023-10-13T16:17:49.1884589+08:00;False|2023-10-13T16:14:57.9204224+08:00;False|2023-10-13T16:12:31.6792705+08:00;False|2023-10-13T16:09:52.4764354+08:00;True|2023-10-12T11:06:51.5823669+08:00;</History>
<LastFailureDetails /> <LastFailureDetails />
</PropertyGroup> </PropertyGroup>
</Project> </Project>
Loading…
Cancel
Save