oak-general-business/src/triggers/notification.ts

361 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Trigger, CreateTrigger, UpdateTrigger, UpdateTriggerInTxn } from 'oak-domain/lib/types/Trigger';
import { EntityDict } from '../general-app-domain/EntityDict';
import { CreateOperationData as CreateNotificationData } from '../general-app-domain/Notification/Schema';
import { assert } from 'oak-domain/lib/utils/assert';
import { RuntimeCxt } from '../types/RuntimeCxt';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
import { WechatMpConfig, WechatPublicConfig, WebConfig } from '../general-app-domain/Application/Schema';
import { WechatMpInstance, WechatPublicInstance, WechatSDK } from 'oak-external-sdk';
import { composeUrl } from 'oak-domain/lib/utils/url';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { sendSms } from '../utils/sms';
import { tryMakeSmsNotification } from './message';
import { composeDomainUrl } from '../utils/domain';
async function sendMessage(notification: CreateNotificationData, context: BackendRuntimeContext<EntityDict>) {
const { data, templateId, channel, messageSystemId, data1, id } = notification;
const [messageSystem] = await context.select('messageSystem', {
data: {
id: 1,
messageId: 1,
message: {
id: 1,
userId: 1,
router: 1,
type: 1,
},
system: {
id: 1,
application$system: {
$entity: 'application',
data: {
id: 1,
type: 1,
config: 1,
},
},
}
},
filter: {
id: messageSystemId,
}
}, { dontCollect: true });
const { system, message } = messageSystem!;
const { router, userId, type } = message!;
const { application$system: applications, config } = system!;
switch (channel) {
case 'wechatMp': {
const app = applications!.find(
ele => ele.type === 'wechatMp'
);
const { config } = app!;
const { appId, appSecret } = config as WechatMpConfig;
const instance = WechatSDK.getInstance(appId!, 'wechatMp', appSecret) as WechatMpInstance;
const page = `/pages/${composeUrl(router!.pathname!, Object.assign({}, router!.props!, router!.state!))}`;
// 根据当前环境决定消息推哪个版本
const StateDict = {
'development': 'developer',
'staging': 'trial',
'production': 'former',
}
try {
await instance.sendSubscribedMessage({
templateId: templateId!,
data: data!,
openId: (data1 as { openId: string }).openId, // 在notification创建时就赋值了
page,
state: StateDict[process.env.NODE_ENV as 'development'] as 'developer',
});
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
catch (err) {
console.warn('发微信小程序消息失败', err);
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
}
case 'wechatPublic': {
const app = applications!.find(
ele => ele.type === 'wechatPublic'
);
const { config, id: applicationId } = app!;
const { appId, appSecret } = config as WechatPublicConfig;
const [domain] = await context.select(
'domain',
{
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
systemId: {
$in: {
entity: 'application',
data: {
systemId: 1,
},
filter: {
id: applicationId,
},
},
},
},
},
{ dontCollect: true }
);
const instance = WechatSDK.getInstance(appId!, 'wechatPublic', appSecret) as WechatPublicInstance;
const { openId, wechatMpAppId } = data1 as {
openId: string,
wechatMpAppId?: string,
};
const url = wechatMpAppId ? router!.pathname! : composeDomainUrl(domain as EntityDict['domain']['Schema'], router!.pathname!);
const page = composeUrl(
url,
Object.assign({}, router!.props!, router!.state!)
);
try {
await instance.sendTemplateMessage({
openId,
templateId: templateId!,
url: !wechatMpAppId ? page : undefined,
data: data!,
miniProgram: wechatMpAppId ? {
appid: wechatMpAppId,
pagepath: `/pages/${page}`,
} : undefined,
clientMsgId: id,
});
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
catch (err) {
console.warn('发微信公众号消息失败', err);
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
}
default: {
assert(channel === 'sms');
try {
await sendSms({
origin: 'ali',
templateName: type,
templateParamSet: (data as { params: any }).params!,
mobile: (data1 as { mobile: string }).mobile,
}, context);
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
catch (err) {
console.warn('发tencent sms消息失败', err);
try {
await sendSms({
origin: 'tencent',
templateName: type,
templateParamSet: (data as { paramsArray: any }).paramsArray!,
mobile: (data1 as { mobile: string }).mobile,
}, context);
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
catch (err2) {
console.warn('发aliyun sms消息失败', err2);
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
}
}
}
}
async function tryCreateSmsNotification(message: EntityDict['message']['Schema'], context: BackendRuntimeContext<EntityDict>) {
const smsNotification = await tryMakeSmsNotification(message, context);
if (smsNotification) {
const { messageSystem$message } = message;
for (const ms of messageSystem$message!) {
const { id } = ms;
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'create',
data: Object.assign(smsNotification, {
messageSystemId: id,
}),
}, { dontCollect: true });
}
return messageSystem$message!.length;
}
return 0;
}
const triggers: Trigger<EntityDict, 'notification', BackendRuntimeContext<EntityDict>>[] = [
{
name: '当创建notification后业务提交后再进行推送',
entity: 'notification',
action: 'create',
when: 'commit',
strict: 'takeEasy',
fn: async ({ operation }, context) => {
const { data } = operation;
if (data instanceof Array) {
for (const d of data) {
await sendMessage(d, context);
}
}
else {
await sendMessage(data, context);
}
return 0;
}
} as CreateTrigger<EntityDict, 'notification', BackendRuntimeContext<EntityDict>>,
{
name: '当notification完成时根据情况去更新message',
entity: 'notification',
when: 'after',
action: ['fail', 'succeed'],
fn: async ({ operation }, context) => {
const { filter } = operation;
assert(filter!.id);
const [message] = await context.select('message', {
data: {
id: 1,
weight: 1,
iState: 1,
type: 1,
entity: 1,
entityId: 1,
userId: 1,
messageSystem$message: {
$entity: 'messageSystem',
data: {
id: 1,
notification$messageSystem: {
$entity: 'notification',
data: {
id: 1,
iState: 1,
channel: 1,
},
},
},
},
}
}, { dontCollect: true });
assert(message);
if (message.iState === 'success') {
return 0;
}
// 查看所有的notification状态只要有一个完成就已经完成了
let success = false;
let allFailed = true;
let smsTried = false;
for (const ms of message.messageSystem$message!) {
for (const n of ms.notification$messageSystem!) {
if (n.iState === 'success') {
success = true;
break;
}
if (n.iState !== 'failure') {
allFailed = false;
}
if (n.channel === 'sms') {
smsTried = true;
}
}
if (success === true) {
break;
}
}
if (success) {
// 有一个完成就算完成
await context.operate('message', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id: message.id,
},
}, { dontCollect: true });
return 1;
}
if (message.weight === 'medium' && !smsTried && allFailed) {
// 中级的消息,在其它途径都失败的情况下再发短信
const result = await tryCreateSmsNotification(message as EntityDict['message']['Schema'], context);
return result;
}
// 标识消息发送失败
await context.operate('message', {
id: await generateNewIdAsync(),
action: 'fail',
data: {},
filter: {
id: message.id,
},
}, { dontCollect: true });
return 1;
}
} as UpdateTriggerInTxn<EntityDict, 'notification', BackendRuntimeContext<EntityDict>>
];
export default triggers;