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); |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
namespace LINGYUN.Abp.Notifications; |
|||
public class NotificationTemplateSendDto : NotificationSendDto |
|||
{ |
|||
} |
|||
@ -1,14 +1,13 @@ |
|||
using LINGYUN.Abp.Notifications.Localization; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.Notifications |
|||
namespace LINGYUN.Abp.Notifications; |
|||
|
|||
public abstract class AbpNotificationsApplicationServiceBase : ApplicationService |
|||
{ |
|||
public abstract class AbpMessageServiceApplicationServiceBase : ApplicationService |
|||
{ |
|||
protected AbpMessageServiceApplicationServiceBase() |
|||
protected AbpNotificationsApplicationServiceBase() |
|||
{ |
|||
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.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace LINGYUN.Abp.Notifications; |
|||
|
|||
public interface INotificationDefinitionGroupRecordRepository : IBasicRepository<NotificationDefinitionGroupRecord, Guid> |
|||
{ |
|||
Task<NotificationDefinitionGroupRecord> FindByNameAsync(string name, CancellationToken cancellationToken = default); |
|||
} |
|||
|
|||
@ -1,8 +1,11 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace LINGYUN.Abp.Notifications; |
|||
|
|||
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