91 changed files with 9580 additions and 126 deletions
@ -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, |
||||
|
}); |
||||
|
}; |
||||
@ -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; |
||||
@ -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, |
||||
|
}); |
||||
|
}; |
||||
@ -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; |
||||
@ -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, |
||||
|
}); |
||||
|
} |
||||
@ -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>; |
||||
|
} |
||||
@ -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}`, |
||||
|
}); |
||||
|
}; |
||||
@ -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> {} |
||||
@ -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; |
||||
|
}[]; |
||||
|
} |
||||
@ -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> |
||||
@ -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> |
||||
@ -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 }, |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -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'), |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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 }, |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -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'), |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -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, |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
File diff suppressed because it is too large
@ -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"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications.Definitions.Groups; |
||||
|
public class NotificationGroupDefinitionGetListInput |
||||
|
{ |
||||
|
public string Filter { get; set; } |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications.Definitions.Groups; |
||||
|
public class NotificationGroupDefinitionUpdateDto : NotificationGroupDefinitionCreateOrUpdateDto |
||||
|
{ |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications.Definitions.Notifications; |
||||
|
public class NotificationDefinitionUpdateDto : NotificationDefinitionCreateOrUpdateDto |
||||
|
{ |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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; |
||||
} |
|
||||
} |
} |
||||
|
|||||
@ -0,0 +1,4 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications; |
||||
|
public class NotificationTemplateSendDto : NotificationSendDto |
||||
|
{ |
||||
|
} |
||||
@ -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); |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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"; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
} |
} |
||||
|
|||||
@ -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); |
||||
} |
} |
||||
|
|||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LY.MicroService.Applications.Single.MultiTenancy; |
||||
|
|
||||
|
public interface ITenantConfigurationCache |
||||
|
{ |
||||
|
Task RefreshAsync(); |
||||
|
|
||||
|
Task<List<TenantConfiguration>> GetTenantsAsync(); |
||||
|
} |
||||
@ -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"; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue