feat: 提供notification发送和发送失败处理的注入点

This commit is contained in:
Pan Qiancheng 2025-12-03 21:51:32 +08:00
parent 5ff8f1ed53
commit 78eb039a04
73 changed files with 1672 additions and 1142 deletions

View File

@ -1,11 +1,10 @@
import { String } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Channel } from '../types/Message';
import { Schema as Application } from './Application';
import { Schema as MessageSystem } from './MessageSystem';
import { EntityDesc } from 'oak-domain/lib/types/EntityDesc';
export interface Schema extends EntityShape {
channel: Channel;
channel: String<32>;
application?: Application;
data?: Object;
messageSystem: MessageSystem;
@ -18,6 +17,5 @@ export type IState = 'sending' | 'success' | 'failure';
type Action = IAction;
export declare const entityDesc: EntityDesc<Schema, Action, '', {
iState: IState;
channel: Schema['channel'];
}>;
export {};

View File

@ -30,14 +30,14 @@ export const entityDesc = {
success: '发送成功',
failure: '发送失败',
},
channel: {
wechatPublic: '公众号',
jPush: '极光推送',
jim: '极光消息',
wechatMp: '小程序',
sms: '短信',
email: '邮箱',
}
// channel: {
// wechatPublic: '公众号',
// jPush: '极光推送',
// jim: '极光消息',
// wechatMp: '小程序',
// sms: '短信',
// email: '邮箱',
// }
}
},
},
@ -52,14 +52,14 @@ export const entityDesc = {
success: '#008000',
failure: '#9A9A9A',
},
channel: {
wechatMp: '#008000',
jPush: '#0000FF',
jim: '#0000FF',
wechatPublic: '#008000',
sms: '#000000',
email: '#000000',
},
// channel: {
// wechatMp: '#008000',
// jPush: '#0000FF',
// jim: '#0000FF',
// wechatPublic: '#008000',
// sms: '#000000',
// email: '#000000',
// },
}
}
};

View File

@ -3,8 +3,10 @@ export const desc = {
attributes: {
channel: {
notNull: true,
type: "enum",
enumeration: ["wechatPublic", "jPush", "jim", "wechatMp", "sms", "email"]
type: "varchar",
params: {
length: 32
}
},
applicationId: {
type: "ref",

View File

@ -9,13 +9,13 @@ export const style = {
success: '#008000',
failure: '#9A9A9A',
},
channel: {
wechatMp: '#008000',
jPush: '#0000FF',
jim: '#0000FF',
wechatPublic: '#008000',
sms: '#000000',
email: '#000000',
},
// channel: {
// wechatMp: '#008000',
// jPush: '#0000FF',
// jim: '#0000FF',
// wechatPublic: '#008000',
// sms: '#000000',
// email: '#000000',
// },
}
};

View File

@ -2,10 +2,9 @@ import { ForeignKey } from "oak-domain/lib/types/DataType";
import { Q_DateValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, ExprOp, ExpressionKey } from "oak-domain/lib/types/Demand";
import { MakeAction as OakMakeAction, EntityShape } from "oak-domain/lib/types/Entity";
import { Action, ParticularAction, IState } from "./Action";
import { Channel } from "../../types/Message";
import { String } from "oak-domain/lib/types/DataType";
export type OpSchema = EntityShape & {
channel: Channel;
channel: String<32>;
applicationId?: ForeignKey<"application"> | null;
data?: Object | null;
messageSystemId: ForeignKey<"messageSystem">;
@ -22,7 +21,7 @@ export type OpFilter = {
$$createAt$$: Q_DateValue;
$$seq$$: Q_NumberValue;
$$updateAt$$: Q_DateValue;
channel: Q_EnumValue<Channel>;
channel: Q_StringValue;
applicationId: Q_StringValue;
data: Object;
messageSystemId: Q_StringValue;

View File

@ -19,14 +19,6 @@
"sending": "发送中",
"success": "发送成功",
"failure": "发送失败"
},
"channel": {
"wechatPublic": "公众号",
"jPush": "极光推送",
"jim": "极光消息",
"wechatMp": "小程序",
"sms": "短信",
"email": "邮箱"
}
}
}

View File

@ -59,7 +59,7 @@ export const desc = {
qrCodeType: {
notNull: true,
type: "enum",
enumeration: ["wechatPublic", "wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublicForMp", "webForWechatPublic"]
enumeration: ["wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublic", "wechatPublicForMp", "webForWechatPublic"]
},
expiresAt: {
type: "datetime"

View File

@ -20,7 +20,7 @@ export const desc = {
qrCodeType: {
notNull: true,
type: "enum",
enumeration: ["wechatPublic", "wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublicForMp", "webForWechatPublic"]
enumeration: ["wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublic", "wechatPublicForMp", "webForWechatPublic"]
},
expiresAt: {
type: "datetime"

View File

@ -19,7 +19,7 @@ export const desc = {
type: {
notNull: true,
type: "enum",
enumeration: ["wechatPublic", "wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublicForMp", "webForWechatPublic"]
enumeration: ["wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublic", "wechatPublicForMp", "webForWechatPublic"]
},
allowShare: {
notNull: true,

View File

@ -8,6 +8,32 @@ export {
*/
registerMessageType, } from './aspects/template';
export { registerWeChatPublicEventCallback, } from './endpoints/wechat';
export { registerMessageNotificationConverters, registerMessageHandler, } from './utils/message';
export {
/**
*
*
* 例如: 将message转换为微信小程序
*/
registerMessageNotificationConverters,
/**
*
*
* 例如: 处理微信小程序
*/
registerMessageHandler, } from './utils/message';
export {
/**
*
*
* 例如: 实际发送微信小程序
*/
registerNotificationHandler,
/**
*
*
* ,
* 例如: 在其他渠道失败后自动发送短信通知
*/
registerNotificationFailureHandler, } from './utils/notification';
export { registSms, } from './utils/sms';
export { registerCosBackend, } from './utils/cos/index.backend';

View File

@ -11,8 +11,32 @@ export {
// 注册微信事件回调处理器endpoint
registerWeChatPublicEventCallback, } from './endpoints/wechat';
export {
// 注册消息通知转换器trigger
registerMessageNotificationConverters, registerMessageHandler, } from './utils/message';
/**
* 注册消息通知转换器
* 用于将消息数据转换为特定渠道所需的格式
* 例如: 将message转换为微信小程序公众号短信邮件所需的数据格式
*/
registerMessageNotificationConverters,
/**
* 注册消息渠道处理器
* 用于处理特定渠道的消息创建逻辑
* 例如: 处理微信小程序公众号短信邮件等渠道的消息生成
*/
registerMessageHandler, } from './utils/message';
export {
/**
* 注册通知渠道处理器
* 用于处理特定渠道的通知发送逻辑
* 例如: 实际发送微信小程序公众号短信邮件等渠道的通知
*/
registerNotificationHandler,
/**
* 注册通知失败处理器
* 用于在所有通知渠道都失败后执行自定义的补救逻辑
* 可以注册多个处理器,它们会依次执行
* 例如: 在其他渠道失败后自动发送短信通知
*/
registerNotificationFailureHandler, } from './utils/notification';
export {
// 注册短信服务商实现
registSms, } from './utils/sms';

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -1,25 +1,5 @@
import { Trigger } from 'oak-domain/lib/types/Trigger';
import { EntityDict } from '../oak-app-domain/EntityDict';
import { BRC } from '../types/RuntimeCxt';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
import { Router } from '../entities/Message';
export declare function tryMakeSmsNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>): Promise<any>;
export declare function tryMakeEmailNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>): Promise<{
id: string;
data: import("../types/Email").EmailOptions;
channel: string;
} | undefined>;
declare const triggers: Trigger<EntityDict, 'message', BRC<EntityDict>>[];
export default triggers;

View File

@ -1,55 +1,12 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { assert } from 'oak-domain/lib/utils/assert';
import { uniqBy } from 'oak-domain/lib/utils/lodash';
import { ConverterDict, getMessageHandler } from '../utils/message';
import { getMessageHandler } from '../utils/message';
const InitialChannelByWeightMatrix = {
high: ['wechatMp', 'wechatPublic', 'sms', 'email'],
medium: ['wechatMp', 'wechatPublic', 'email'],
low: ['wechatMp', 'wechatPublic', 'email'],
};
export async function tryMakeSmsNotification(message, context) {
const { userId, type, entity, entityId, router } = message;
assert(userId);
const [mobile] = await context.select('mobile', {
data: {
id: 1,
mobile: 1,
},
filter: {
userId,
},
indexFrom: 0,
count: 1,
}, { dontCollect: true });
if (mobile) {
const converter = ConverterDict[type] && ConverterDict[type].toSms;
if (converter) {
const dispersedData = await converter(message, context);
if (dispersedData) {
return {
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'sms',
data1: mobile,
};
}
}
}
}
export async function tryMakeEmailNotification(message, context) {
const { userId, type, entity, entityId, router } = message;
const converter = ConverterDict[type] && ConverterDict[type].toEmail;
if (converter) {
const dispersedData = await converter(message, context);
if (dispersedData) {
return {
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'email',
};
}
}
}
async function createNotification(message, context) {
const { restriction, userId, weight, type, entity, entityId, platformId, channels } = message;
assert(userId);

View File

@ -1,264 +1,12 @@
import { assert } from 'oak-domain/lib/utils/assert';
import WechatSDK from 'oak-external-sdk/lib/WechatSDK';
import { composeUrl } from 'oak-domain/lib/utils/domain';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { sendSms } from '../utils/sms';
import { sendEmail } from '../utils/email';
import { tryMakeSmsNotification } from './message';
import { composeDomainUrl } from '../utils/domain';
import { tryMakeSmsNotification } from '../utils/message/sms';
import { getNotificationHandler, getNotificationFailureHandlers } from '../utils/notification';
async function sendNotification(notification, context) {
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;
const instance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
let page;
if (router) {
const pathname = router.pathname;
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = composeUrl(url, Object.assign({}, router.props, router.state));
}
// 根据当前环境决定消息推哪个版本
const StateDict = {
'development': 'developer',
'staging': 'trial',
'production': 'former',
};
try {
await instance.sendSubscribedMessage({
templateId: templateId,
data: data,
openId: data1.openId, // 在notification创建时就赋值了
page,
state: StateDict[process.env.NODE_ENV],
});
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;
const [domain] = await context.select('domain', {
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
system: {
application$system: {
id: applicationId,
},
},
},
}, { dontCollect: true });
const instance = WechatSDK.getInstance(appId, 'wechatPublic', appSecret);
const { openId, wechatMpAppId } = data1;
let page;
// message 用户不需要跳转页面
if (router) {
const pathname = router.pathname;
if (wechatMpAppId) {
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = composeUrl(url, Object.assign({}, router.props, router.state));
}
else {
const url = composeDomainUrl(domain, pathname);
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: 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;
}
}
case 'email': {
try {
const result = await sendEmail(data, context);
if (result?.success) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
},
}, { dontCollect: true });
}
else {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: result?.error,
},
},
filter: {
id,
},
}, { dontCollect: true });
}
return 1;
}
catch (err) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: err?.message,
},
},
filter: {
id,
},
}, { dontCollect: true });
console.warn('发邮件消息失败', err);
return 1;
}
}
default: {
assert(channel === 'sms');
try {
const result = await sendSms({
messageType: type,
templateParam: data.params,
mobile: data1.mobile,
}, context);
if (result?.success === true) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
else {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
}
catch (err) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
console.warn('发短信消息失败', err);
return 1;
}
}
}
const { channel } = notification;
const handler = getNotificationHandler(channel);
await handler(notification, context);
return 1;
}
async function tryCreateSmsNotification(message, context) {
const smsNotification = await tryMakeSmsNotification(message, context);
@ -415,8 +163,27 @@ const triggers = [
closeRootMode();
return 1;
}
if (message.weight === 'medium' && !smsTried && allFailed) {
// 中级的消息,在其它途径都失败的情况下再发短信
// 获取所有注册的失败处理器
const failureHandlers = getNotificationFailureHandlers();
if (failureHandlers.length > 0) {
// 如果有注册的失败处理器,执行所有处理器
let totalResult = 0;
for (const handler of failureHandlers) {
try {
const result = await handler(message, context);
totalResult += result;
}
catch (err) {
console.error('执行notification失败处理器时出错:', err);
}
}
if (totalResult > 0) {
closeRootMode();
return totalResult;
}
}
else if (message.weight === 'medium' && !smsTried && allFailed) {
// 如果没有注册处理器,走原有逻辑:中级的消息,在其它途径都失败的情况下再发短信
const result = await tryCreateSmsNotification(message, context);
closeRootMode();
return result;

View File

@ -1,2 +1,16 @@
import BackendRuntimeContext from '../../context/BackendRuntimeContext';
import { Router } from '../../entities/Message';
import { EntityDict } from '../../oak-app-domain';
import { MessageHandler } from './index';
export declare const emailHandler: MessageHandler;
export declare function tryMakeEmailNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>): Promise<{
id: string;
data: import("../../types/Email").EmailOptions;
channel: string;
} | undefined>;

View File

@ -1,4 +1,5 @@
import { tryMakeEmailNotification } from '../../triggers/message';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { ConverterDict } from './index';
export const emailHandler = async ({ message, applications, system, messageTypeTemplates, context }) => {
const notificationDatas = [];
const emailNotification = await tryMakeEmailNotification(message, context);
@ -7,3 +8,17 @@ export const emailHandler = async ({ message, applications, system, messageTypeT
}
return notificationDatas;
};
export async function tryMakeEmailNotification(message, context) {
const { userId, type, entity, entityId, router } = message;
const converter = ConverterDict[type] && ConverterDict[type].toEmail;
if (converter) {
const dispersedData = await converter(message, context);
if (dispersedData) {
return {
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'email',
};
}
}
}

View File

@ -1,4 +1,8 @@
import { assert } from "oak-domain/lib/utils/assert";
import { wechatMpHandler } from './wechatMp';
import { wechatPublicHandler } from './wechatPublic';
import { smsHandler } from './sms';
import { emailHandler } from './email';
export const ConverterDict = {};
export function registerMessageNotificationConverters(converter) {
Object.keys(converter).forEach(key => {
@ -14,3 +18,8 @@ export function getMessageHandler(channel) {
assert(handler, `消息渠道 ${channel} 的处理器未注册`);
return handler;
}
// 默认注册所有处理器
registerMessageHandler('wechatMp', wechatMpHandler);
registerMessageHandler('wechatPublic', wechatPublicHandler);
registerMessageHandler('sms', smsHandler);
registerMessageHandler('email', emailHandler);

View File

@ -1,2 +1,12 @@
import BackendRuntimeContext from '../../context/BackendRuntimeContext';
import { Router } from '../../entities/Message';
import { EntityDict } from '../../oak-app-domain';
import { MessageHandler } from './index';
export declare const smsHandler: MessageHandler;
export declare function tryMakeSmsNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>): Promise<any>;

View File

@ -1,4 +1,6 @@
import { tryMakeSmsNotification } from '../../triggers/message';
import assert from 'assert';
import { ConverterDict } from './index';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
export const smsHandler = async ({ message, applications, system, messageTypeTemplates, context }) => {
const notificationDatas = [];
const smsNotification = await tryMakeSmsNotification(message, context);
@ -7,3 +9,32 @@ export const smsHandler = async ({ message, applications, system, messageTypeTem
}
return notificationDatas;
};
export async function tryMakeSmsNotification(message, context) {
const { userId, type, entity, entityId, router } = message;
assert(userId);
const [mobile] = await context.select('mobile', {
data: {
id: 1,
mobile: 1,
},
filter: {
userId,
},
indexFrom: 0,
count: 1,
}, { dontCollect: true });
if (mobile) {
const converter = ConverterDict[type] && ConverterDict[type].toSms;
if (converter) {
const dispersedData = await converter(message, context);
if (dispersedData) {
return {
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'sms',
data1: mobile,
};
}
}
}
}

2
es/utils/notification/email.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import { NotificationHandler } from './index';
export declare const emailHandler: NotificationHandler;

View File

@ -0,0 +1,47 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { sendEmail } from '../email';
export const emailHandler = async (notification, context) => {
const { data, id } = notification;
try {
const result = await sendEmail(data, context);
if (result?.success) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
},
}, { dontCollect: true });
}
else {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: result?.error,
},
},
filter: {
id,
},
}, { dontCollect: true });
}
}
catch (err) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: err?.message,
},
},
filter: {
id,
},
}, { dontCollect: true });
console.warn('发邮件消息失败', err);
}
};

9
es/utils/notification/index.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { EntityDict } from "../../oak-app-domain";
import { BRC } from "../../types/RuntimeCxt";
import { Channel } from "../../types/Message";
export type NotificationHandler = (notification: EntityDict['notification']['OpSchema'], context: BRC<EntityDict>) => Promise<void>;
export type NotificationFailureHandler = (message: EntityDict['message']['Schema'], context: BRC<EntityDict>) => Promise<number>;
export declare function registerNotificationHandler(channel: Channel, handler: NotificationHandler): void;
export declare function getNotificationHandler(channel: Channel): NotificationHandler;
export declare function registerNotificationFailureHandler(handler: NotificationFailureHandler): void;
export declare function getNotificationFailureHandlers(): NotificationFailureHandler[];

View File

@ -0,0 +1,26 @@
import { assert } from "oak-domain/lib/utils/assert";
import { wechatMpHandler } from './wechatMp';
import { wechatPublicHandler } from './wechatPublic';
import { smsHandler } from './sms';
import { emailHandler } from './email';
const notificationHandlers = {};
const notificationFailureHandlers = [];
export function registerNotificationHandler(channel, handler) {
notificationHandlers[channel] = handler;
}
export function getNotificationHandler(channel) {
const handler = notificationHandlers[channel];
assert(handler, `通知渠道 ${channel} 的处理器未注册`);
return handler;
}
export function registerNotificationFailureHandler(handler) {
notificationFailureHandlers.push(handler);
}
export function getNotificationFailureHandlers() {
return notificationFailureHandlers;
}
// 默认注册所有处理器
registerNotificationHandler('wechatMp', wechatMpHandler);
registerNotificationHandler('wechatPublic', wechatPublicHandler);
registerNotificationHandler('sms', smsHandler);
registerNotificationHandler('email', emailHandler);

2
es/utils/notification/sms.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import { NotificationHandler } from './index';
export declare const smsHandler: NotificationHandler;

View File

@ -0,0 +1,65 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { sendSms } from '../sms';
export const smsHandler = async (notification, context) => {
const { data, data1, id } = notification;
const [messageSystem] = await context.select('messageSystem', {
data: {
id: 1,
message: {
id: 1,
type: 1,
},
},
filter: {
id: notification.messageSystemId,
}
}, { dontCollect: true });
const { message } = messageSystem;
const { type } = message;
try {
const result = await sendSms({
messageType: type,
templateParam: data.params,
mobile: data1.mobile,
}, context);
if (result?.success === true) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
else {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
}
catch (err) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
console.warn('发短信消息失败', err);
}
};

2
es/utils/notification/wechatMp.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import { NotificationHandler } from './index';
export declare const wechatMpHandler: NotificationHandler;

View File

@ -0,0 +1,81 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import WechatSDK from 'oak-external-sdk/lib/WechatSDK';
import { composeUrl } from 'oak-domain/lib/utils/domain';
export const wechatMpHandler = async (notification, context) => {
const { data, templateId, 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 } = message;
const { application$system: applications } = system;
const app = applications.find(ele => ele.type === 'wechatMp');
const { config } = app;
const { appId, appSecret } = config;
const instance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
let page;
if (router) {
const pathname = router.pathname;
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = composeUrl(url, Object.assign({}, router.props, router.state));
}
// 根据当前环境决定消息推哪个版本
const StateDict = {
'development': 'developer',
'staging': 'trial',
'production': 'former',
};
try {
await instance.sendSubscribedMessage({
templateId: templateId,
data: data,
openId: data1.openId,
page,
state: StateDict[process.env.NODE_ENV],
});
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
catch (err) {
console.warn('发微信小程序消息失败', err);
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
};

View File

@ -0,0 +1,2 @@
import { NotificationHandler } from './index';
export declare const wechatPublicHandler: NotificationHandler;

View File

@ -0,0 +1,106 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import WechatSDK from 'oak-external-sdk/lib/WechatSDK';
import { composeUrl } from 'oak-domain/lib/utils/domain';
import { composeDomainUrl } from '../domain';
export const wechatPublicHandler = async (notification, context) => {
const { data, templateId, 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 } = message;
const { application$system: applications } = system;
const app = applications.find(ele => ele.type === 'wechatPublic');
const { config, id: applicationId } = app;
const { appId, appSecret } = config;
const [domain] = await context.select('domain', {
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
system: {
application$system: {
id: applicationId,
},
},
},
}, { dontCollect: true });
const instance = WechatSDK.getInstance(appId, 'wechatPublic', appSecret);
const { openId, wechatMpAppId } = data1;
let page;
// message 用户不需要跳转页面
if (router) {
const pathname = router.pathname;
if (wechatMpAppId) {
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = composeUrl(url, Object.assign({}, router.props, router.state));
}
else {
const url = composeDomainUrl(domain, pathname);
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: page,
}
: undefined,
clientMsgId: id,
});
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
catch (err) {
console.warn('发微信公众号消息失败', err);
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
};

View File

@ -1,11 +1,10 @@
import { String } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Channel } from '../types/Message';
import { Schema as Application } from './Application';
import { Schema as MessageSystem } from './MessageSystem';
import { EntityDesc } from 'oak-domain/lib/types/EntityDesc';
export interface Schema extends EntityShape {
channel: Channel;
channel: String<32>;
application?: Application;
data?: Object;
messageSystem: MessageSystem;
@ -18,6 +17,5 @@ export type IState = 'sending' | 'success' | 'failure';
type Action = IAction;
export declare const entityDesc: EntityDesc<Schema, Action, '', {
iState: IState;
channel: Schema['channel'];
}>;
export {};

View File

@ -33,14 +33,14 @@ exports.entityDesc = {
success: '发送成功',
failure: '发送失败',
},
channel: {
wechatPublic: '公众号',
jPush: '极光推送',
jim: '极光消息',
wechatMp: '小程序',
sms: '短信',
email: '邮箱',
}
// channel: {
// wechatPublic: '公众号',
// jPush: '极光推送',
// jim: '极光消息',
// wechatMp: '小程序',
// sms: '短信',
// email: '邮箱',
// }
}
},
},
@ -55,14 +55,14 @@ exports.entityDesc = {
success: '#008000',
failure: '#9A9A9A',
},
channel: {
wechatMp: '#008000',
jPush: '#0000FF',
jim: '#0000FF',
wechatPublic: '#008000',
sms: '#000000',
email: '#000000',
},
// channel: {
// wechatMp: '#008000',
// jPush: '#0000FF',
// jim: '#0000FF',
// wechatPublic: '#008000',
// sms: '#000000',
// email: '#000000',
// },
}
}
};

View File

@ -6,8 +6,10 @@ exports.desc = {
attributes: {
channel: {
notNull: true,
type: "enum",
enumeration: ["wechatPublic", "jPush", "jim", "wechatMp", "sms", "email"]
type: "varchar",
params: {
length: 32
}
},
applicationId: {
type: "ref",

View File

@ -12,13 +12,13 @@ exports.style = {
success: '#008000',
failure: '#9A9A9A',
},
channel: {
wechatMp: '#008000',
jPush: '#0000FF',
jim: '#0000FF',
wechatPublic: '#008000',
sms: '#000000',
email: '#000000',
},
// channel: {
// wechatMp: '#008000',
// jPush: '#0000FF',
// jim: '#0000FF',
// wechatPublic: '#008000',
// sms: '#000000',
// email: '#000000',
// },
}
};

View File

@ -2,10 +2,9 @@ import { ForeignKey } from "oak-domain/lib/types/DataType";
import { Q_DateValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, ExprOp, ExpressionKey } from "oak-domain/lib/types/Demand";
import { MakeAction as OakMakeAction, EntityShape } from "oak-domain/lib/types/Entity";
import { Action, ParticularAction, IState } from "./Action";
import { Channel } from "../../types/Message";
import { String } from "oak-domain/lib/types/DataType";
export type OpSchema = EntityShape & {
channel: Channel;
channel: String<32>;
applicationId?: ForeignKey<"application"> | null;
data?: Object | null;
messageSystemId: ForeignKey<"messageSystem">;
@ -22,7 +21,7 @@ export type OpFilter = {
$$createAt$$: Q_DateValue;
$$seq$$: Q_NumberValue;
$$updateAt$$: Q_DateValue;
channel: Q_EnumValue<Channel>;
channel: Q_StringValue;
applicationId: Q_StringValue;
data: Object;
messageSystemId: Q_StringValue;

View File

@ -19,14 +19,6 @@
"sending": "发送中",
"success": "发送成功",
"failure": "发送失败"
},
"channel": {
"wechatPublic": "公众号",
"jPush": "极光推送",
"jim": "极光消息",
"wechatMp": "小程序",
"sms": "短信",
"email": "邮箱"
}
}
}

View File

@ -62,7 +62,7 @@ exports.desc = {
qrCodeType: {
notNull: true,
type: "enum",
enumeration: ["wechatPublic", "wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublicForMp", "webForWechatPublic"]
enumeration: ["wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublic", "wechatPublicForMp", "webForWechatPublic"]
},
expiresAt: {
type: "datetime"

View File

@ -23,7 +23,7 @@ exports.desc = {
qrCodeType: {
notNull: true,
type: "enum",
enumeration: ["wechatPublic", "wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublicForMp", "webForWechatPublic"]
enumeration: ["wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublic", "wechatPublicForMp", "webForWechatPublic"]
},
expiresAt: {
type: "datetime"

View File

@ -22,7 +22,7 @@ exports.desc = {
type: {
notNull: true,
type: "enum",
enumeration: ["wechatPublic", "wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublicForMp", "webForWechatPublic"]
enumeration: ["wechatMpDomainUrl", "wechatMpWxaCode", "wechatPublic", "wechatPublicForMp", "webForWechatPublic"]
},
allowShare: {
notNull: true,

View File

@ -8,6 +8,32 @@ export {
*/
registerMessageType, } from './aspects/template';
export { registerWeChatPublicEventCallback, } from './endpoints/wechat';
export { registerMessageNotificationConverters, registerMessageHandler, } from './utils/message';
export {
/**
*
*
* 例如: 将message转换为微信小程序
*/
registerMessageNotificationConverters,
/**
*
*
* 例如: 处理微信小程序
*/
registerMessageHandler, } from './utils/message';
export {
/**
*
*
* 例如: 实际发送微信小程序
*/
registerNotificationHandler,
/**
*
*
* ,
* 例如: 在其他渠道失败后自动发送短信通知
*/
registerNotificationFailureHandler, } from './utils/notification';
export { registSms, } from './utils/sms';
export { registerCosBackend, } from './utils/cos/index.backend';

View File

@ -4,7 +4,7 @@
* 如需要注入请在routine中编写注册逻辑使用此处提供的注册方法进行注册
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerCosBackend = exports.registSms = exports.registerMessageHandler = exports.registerMessageNotificationConverters = exports.registerWeChatPublicEventCallback = exports.registerMessageType = void 0;
exports.registerCosBackend = exports.registSms = exports.registerNotificationFailureHandler = exports.registerNotificationHandler = exports.registerMessageHandler = exports.registerMessageNotificationConverters = exports.registerWeChatPublicEventCallback = exports.registerMessageType = void 0;
var template_1 = require("./aspects/template");
/**
* 注册消息类型
@ -14,9 +14,32 @@ var wechat_1 = require("./endpoints/wechat");
// 注册微信事件回调处理器endpoint
Object.defineProperty(exports, "registerWeChatPublicEventCallback", { enumerable: true, get: function () { return wechat_1.registerWeChatPublicEventCallback; } });
var message_1 = require("./utils/message");
// 注册消息通知转换器trigger
/**
* 注册消息通知转换器
* 用于将消息数据转换为特定渠道所需的格式
* 例如: 将message转换为微信小程序公众号短信邮件所需的数据格式
*/
Object.defineProperty(exports, "registerMessageNotificationConverters", { enumerable: true, get: function () { return message_1.registerMessageNotificationConverters; } });
/**
* 注册消息渠道处理器
* 用于处理特定渠道的消息创建逻辑
* 例如: 处理微信小程序公众号短信邮件等渠道的消息生成
*/
Object.defineProperty(exports, "registerMessageHandler", { enumerable: true, get: function () { return message_1.registerMessageHandler; } });
var notification_1 = require("./utils/notification");
/**
* 注册通知渠道处理器
* 用于处理特定渠道的通知发送逻辑
* 例如: 实际发送微信小程序公众号短信邮件等渠道的通知
*/
Object.defineProperty(exports, "registerNotificationHandler", { enumerable: true, get: function () { return notification_1.registerNotificationHandler; } });
/**
* 注册通知失败处理器
* 用于在所有通知渠道都失败后执行自定义的补救逻辑
* 可以注册多个处理器,它们会依次执行
* 例如: 在其他渠道失败后自动发送短信通知
*/
Object.defineProperty(exports, "registerNotificationFailureHandler", { enumerable: true, get: function () { return notification_1.registerNotificationFailureHandler; } });
var sms_1 = require("./utils/sms");
// 注册短信服务商实现
Object.defineProperty(exports, "registSms", { enumerable: true, get: function () { return sms_1.registSms; } });

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -1,25 +1,5 @@
import { Trigger } from 'oak-domain/lib/types/Trigger';
import { EntityDict } from '../oak-app-domain/EntityDict';
import { BRC } from '../types/RuntimeCxt';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
import { Router } from '../entities/Message';
export declare function tryMakeSmsNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>): Promise<any>;
export declare function tryMakeEmailNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>): Promise<{
id: string;
data: import("../types/Email").EmailOptions;
channel: string;
} | undefined>;
declare const triggers: Trigger<EntityDict, 'message', BRC<EntityDict>>[];
export default triggers;

View File

@ -1,7 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.tryMakeSmsNotification = tryMakeSmsNotification;
exports.tryMakeEmailNotification = tryMakeEmailNotification;
const uuid_1 = require("oak-domain/lib/utils/uuid");
const assert_1 = require("oak-domain/lib/utils/assert");
const lodash_1 = require("oak-domain/lib/utils/lodash");
@ -11,49 +9,6 @@ const InitialChannelByWeightMatrix = {
medium: ['wechatMp', 'wechatPublic', 'email'],
low: ['wechatMp', 'wechatPublic', 'email'],
};
async function tryMakeSmsNotification(message, context) {
const { userId, type, entity, entityId, router } = message;
(0, assert_1.assert)(userId);
const [mobile] = await context.select('mobile', {
data: {
id: 1,
mobile: 1,
},
filter: {
userId,
},
indexFrom: 0,
count: 1,
}, { dontCollect: true });
if (mobile) {
const converter = message_1.ConverterDict[type] && message_1.ConverterDict[type].toSms;
if (converter) {
const dispersedData = await converter(message, context);
if (dispersedData) {
return {
id: await (0, uuid_1.generateNewIdAsync)(),
data: dispersedData,
channel: 'sms',
data1: mobile,
};
}
}
}
}
async function tryMakeEmailNotification(message, context) {
const { userId, type, entity, entityId, router } = message;
const converter = message_1.ConverterDict[type] && message_1.ConverterDict[type].toEmail;
if (converter) {
const dispersedData = await converter(message, context);
if (dispersedData) {
return {
id: await (0, uuid_1.generateNewIdAsync)(),
data: dispersedData,
channel: 'email',
};
}
}
}
async function createNotification(message, context) {
const { restriction, userId, weight, type, entity, entityId, platformId, channels } = message;
(0, assert_1.assert)(userId);

View File

@ -1,270 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const assert_1 = require("oak-domain/lib/utils/assert");
const WechatSDK_1 = tslib_1.__importDefault(require("oak-external-sdk/lib/WechatSDK"));
const domain_1 = require("oak-domain/lib/utils/domain");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const sms_1 = require("../utils/sms");
const email_1 = require("../utils/email");
const message_1 = require("./message");
const domain_2 = require("../utils/domain");
const sms_1 = require("../utils/message/sms");
const notification_1 = require("../utils/notification");
async function sendNotification(notification, context) {
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;
const instance = WechatSDK_1.default.getInstance(appId, 'wechatMp', appSecret);
let page;
if (router) {
const pathname = router.pathname;
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = (0, domain_1.composeUrl)(url, Object.assign({}, router.props, router.state));
}
// 根据当前环境决定消息推哪个版本
const StateDict = {
'development': 'developer',
'staging': 'trial',
'production': 'former',
};
try {
await instance.sendSubscribedMessage({
templateId: templateId,
data: data,
openId: data1.openId, // 在notification创建时就赋值了
page,
state: StateDict[process.env.NODE_ENV],
});
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
catch (err) {
console.warn('发微信小程序消息失败', err);
await context.operate('notification', {
id: await (0, uuid_1.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;
const [domain] = await context.select('domain', {
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
system: {
application$system: {
id: applicationId,
},
},
},
}, { dontCollect: true });
const instance = WechatSDK_1.default.getInstance(appId, 'wechatPublic', appSecret);
const { openId, wechatMpAppId } = data1;
let page;
// message 用户不需要跳转页面
if (router) {
const pathname = router.pathname;
if (wechatMpAppId) {
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = (0, domain_1.composeUrl)(url, Object.assign({}, router.props, router.state));
}
else {
const url = (0, domain_2.composeDomainUrl)(domain, pathname);
page = (0, domain_1.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: page,
}
: undefined,
clientMsgId: id,
});
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
catch (err) {
console.warn('发微信公众号消息失败', err);
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
return 1;
}
}
case 'email': {
try {
const result = await (0, email_1.sendEmail)(data, context);
if (result?.success) {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeed',
data: {},
filter: {
id,
},
}, { dontCollect: true });
}
else {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {
data2: {
res: result?.error,
},
},
filter: {
id,
},
}, { dontCollect: true });
}
return 1;
}
catch (err) {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {
data2: {
res: err?.message,
},
},
filter: {
id,
},
}, { dontCollect: true });
console.warn('发邮件消息失败', err);
return 1;
}
}
default: {
(0, assert_1.assert)(channel === 'sms');
try {
const result = await (0, sms_1.sendSms)({
messageType: type,
templateParam: data.params,
mobile: data1.mobile,
}, context);
if (result?.success === true) {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeed',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
else {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
}
catch (err) {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
console.warn('发短信消息失败', err);
return 1;
}
}
}
const { channel } = notification;
const handler = (0, notification_1.getNotificationHandler)(channel);
await handler(notification, context);
return 1;
}
async function tryCreateSmsNotification(message, context) {
const smsNotification = await (0, message_1.tryMakeSmsNotification)(message, context);
const smsNotification = await (0, sms_1.tryMakeSmsNotification)(message, context);
if (smsNotification) {
const { messageSystem$message } = message;
for (const ms of messageSystem$message) {
@ -418,8 +165,27 @@ const triggers = [
closeRootMode();
return 1;
}
if (message.weight === 'medium' && !smsTried && allFailed) {
// 中级的消息,在其它途径都失败的情况下再发短信
// 获取所有注册的失败处理器
const failureHandlers = (0, notification_1.getNotificationFailureHandlers)();
if (failureHandlers.length > 0) {
// 如果有注册的失败处理器,执行所有处理器
let totalResult = 0;
for (const handler of failureHandlers) {
try {
const result = await handler(message, context);
totalResult += result;
}
catch (err) {
console.error('执行notification失败处理器时出错:', err);
}
}
if (totalResult > 0) {
closeRootMode();
return totalResult;
}
}
else if (message.weight === 'medium' && !smsTried && allFailed) {
// 如果没有注册处理器,走原有逻辑:中级的消息,在其它途径都失败的情况下再发短信
const result = await tryCreateSmsNotification(message, context);
closeRootMode();
return result;

View File

@ -14,7 +14,7 @@ export declare function createToDo<ED extends EntityDict & BaseEntityDict, T ext
redirectTo: EntityDict['toDo']['OpSchema']['redirectTo'];
entity: any;
entityId: string;
}, userIds?: string[]): Promise<1 | 0>;
}, userIds?: string[]): Promise<0 | 1>;
/**
* todo例程entity对象上进行action操作时filtertodo完成
* entity的action的后trigger中调用

View File

@ -1,2 +1,16 @@
import BackendRuntimeContext from '../../context/BackendRuntimeContext';
import { Router } from '../../entities/Message';
import { EntityDict } from '../../oak-app-domain';
import { MessageHandler } from './index';
export declare const emailHandler: MessageHandler;
export declare function tryMakeEmailNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>): Promise<{
id: string;
data: import("../../types/Email").EmailOptions;
channel: string;
} | undefined>;

View File

@ -1,13 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.emailHandler = void 0;
const message_1 = require("../../triggers/message");
exports.tryMakeEmailNotification = tryMakeEmailNotification;
const uuid_1 = require("oak-domain/lib/utils/uuid");
const index_1 = require("./index");
const emailHandler = async ({ message, applications, system, messageTypeTemplates, context }) => {
const notificationDatas = [];
const emailNotification = await (0, message_1.tryMakeEmailNotification)(message, context);
const emailNotification = await tryMakeEmailNotification(message, context);
if (emailNotification) {
notificationDatas.push(emailNotification);
}
return notificationDatas;
};
exports.emailHandler = emailHandler;
async function tryMakeEmailNotification(message, context) {
const { userId, type, entity, entityId, router } = message;
const converter = index_1.ConverterDict[type] && index_1.ConverterDict[type].toEmail;
if (converter) {
const dispersedData = await converter(message, context);
if (dispersedData) {
return {
id: await (0, uuid_1.generateNewIdAsync)(),
data: dispersedData,
channel: 'email',
};
}
}
}

View File

@ -5,6 +5,10 @@ exports.registerMessageNotificationConverters = registerMessageNotificationConve
exports.registerMessageHandler = registerMessageHandler;
exports.getMessageHandler = getMessageHandler;
const assert_1 = require("oak-domain/lib/utils/assert");
const wechatMp_1 = require("./wechatMp");
const wechatPublic_1 = require("./wechatPublic");
const sms_1 = require("./sms");
const email_1 = require("./email");
exports.ConverterDict = {};
function registerMessageNotificationConverters(converter) {
Object.keys(converter).forEach(key => {
@ -20,3 +24,8 @@ function getMessageHandler(channel) {
(0, assert_1.assert)(handler, `消息渠道 ${channel} 的处理器未注册`);
return handler;
}
// 默认注册所有处理器
registerMessageHandler('wechatMp', wechatMp_1.wechatMpHandler);
registerMessageHandler('wechatPublic', wechatPublic_1.wechatPublicHandler);
registerMessageHandler('sms', sms_1.smsHandler);
registerMessageHandler('email', email_1.emailHandler);

View File

@ -1,2 +1,12 @@
import BackendRuntimeContext from '../../context/BackendRuntimeContext';
import { Router } from '../../entities/Message';
import { EntityDict } from '../../oak-app-domain';
import { MessageHandler } from './index';
export declare const smsHandler: MessageHandler;
export declare function tryMakeSmsNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>): Promise<any>;

View File

@ -1,13 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.smsHandler = void 0;
const message_1 = require("../../triggers/message");
exports.tryMakeSmsNotification = tryMakeSmsNotification;
const tslib_1 = require("tslib");
const assert_1 = tslib_1.__importDefault(require("assert"));
const index_1 = require("./index");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const smsHandler = async ({ message, applications, system, messageTypeTemplates, context }) => {
const notificationDatas = [];
const smsNotification = await (0, message_1.tryMakeSmsNotification)(message, context);
const smsNotification = await tryMakeSmsNotification(message, context);
if (smsNotification) {
notificationDatas.push(smsNotification);
}
return notificationDatas;
};
exports.smsHandler = smsHandler;
async function tryMakeSmsNotification(message, context) {
const { userId, type, entity, entityId, router } = message;
(0, assert_1.default)(userId);
const [mobile] = await context.select('mobile', {
data: {
id: 1,
mobile: 1,
},
filter: {
userId,
},
indexFrom: 0,
count: 1,
}, { dontCollect: true });
if (mobile) {
const converter = index_1.ConverterDict[type] && index_1.ConverterDict[type].toSms;
if (converter) {
const dispersedData = await converter(message, context);
if (dispersedData) {
return {
id: await (0, uuid_1.generateNewIdAsync)(),
data: dispersedData,
channel: 'sms',
data1: mobile,
};
}
}
}
}

2
lib/utils/notification/email.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import { NotificationHandler } from './index';
export declare const emailHandler: NotificationHandler;

View File

@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.emailHandler = void 0;
const uuid_1 = require("oak-domain/lib/utils/uuid");
const email_1 = require("../email");
const emailHandler = async (notification, context) => {
const { data, id } = notification;
try {
const result = await (0, email_1.sendEmail)(data, context);
if (result?.success) {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeed',
data: {},
filter: {
id,
},
}, { dontCollect: true });
}
else {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {
data2: {
res: result?.error,
},
},
filter: {
id,
},
}, { dontCollect: true });
}
}
catch (err) {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {
data2: {
res: err?.message,
},
},
filter: {
id,
},
}, { dontCollect: true });
console.warn('发邮件消息失败', err);
}
};
exports.emailHandler = emailHandler;

9
lib/utils/notification/index.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { EntityDict } from "../../oak-app-domain";
import { BRC } from "../../types/RuntimeCxt";
import { Channel } from "../../types/Message";
export type NotificationHandler = (notification: EntityDict['notification']['OpSchema'], context: BRC<EntityDict>) => Promise<void>;
export type NotificationFailureHandler = (message: EntityDict['message']['Schema'], context: BRC<EntityDict>) => Promise<number>;
export declare function registerNotificationHandler(channel: Channel, handler: NotificationHandler): void;
export declare function getNotificationHandler(channel: Channel): NotificationHandler;
export declare function registerNotificationFailureHandler(handler: NotificationFailureHandler): void;
export declare function getNotificationFailureHandlers(): NotificationFailureHandler[];

View File

@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerNotificationHandler = registerNotificationHandler;
exports.getNotificationHandler = getNotificationHandler;
exports.registerNotificationFailureHandler = registerNotificationFailureHandler;
exports.getNotificationFailureHandlers = getNotificationFailureHandlers;
const assert_1 = require("oak-domain/lib/utils/assert");
const wechatMp_1 = require("./wechatMp");
const wechatPublic_1 = require("./wechatPublic");
const sms_1 = require("./sms");
const email_1 = require("./email");
const notificationHandlers = {};
const notificationFailureHandlers = [];
function registerNotificationHandler(channel, handler) {
notificationHandlers[channel] = handler;
}
function getNotificationHandler(channel) {
const handler = notificationHandlers[channel];
(0, assert_1.assert)(handler, `通知渠道 ${channel} 的处理器未注册`);
return handler;
}
function registerNotificationFailureHandler(handler) {
notificationFailureHandlers.push(handler);
}
function getNotificationFailureHandlers() {
return notificationFailureHandlers;
}
// 默认注册所有处理器
registerNotificationHandler('wechatMp', wechatMp_1.wechatMpHandler);
registerNotificationHandler('wechatPublic', wechatPublic_1.wechatPublicHandler);
registerNotificationHandler('sms', sms_1.smsHandler);
registerNotificationHandler('email', email_1.emailHandler);

2
lib/utils/notification/sms.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import { NotificationHandler } from './index';
export declare const smsHandler: NotificationHandler;

View File

@ -0,0 +1,69 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.smsHandler = void 0;
const uuid_1 = require("oak-domain/lib/utils/uuid");
const sms_1 = require("../sms");
const smsHandler = async (notification, context) => {
const { data, data1, id } = notification;
const [messageSystem] = await context.select('messageSystem', {
data: {
id: 1,
message: {
id: 1,
type: 1,
},
},
filter: {
id: notification.messageSystemId,
}
}, { dontCollect: true });
const { message } = messageSystem;
const { type } = message;
try {
const result = await (0, sms_1.sendSms)({
messageType: type,
templateParam: data.params,
mobile: data1.mobile,
}, context);
if (result?.success === true) {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeed',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
else {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
}
catch (err) {
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
console.warn('发短信消息失败', err);
}
};
exports.smsHandler = smsHandler;

2
lib/utils/notification/wechatMp.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import { NotificationHandler } from './index';
export declare const wechatMpHandler: NotificationHandler;

View File

@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.wechatMpHandler = void 0;
const tslib_1 = require("tslib");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const WechatSDK_1 = tslib_1.__importDefault(require("oak-external-sdk/lib/WechatSDK"));
const domain_1 = require("oak-domain/lib/utils/domain");
const wechatMpHandler = async (notification, context) => {
const { data, templateId, 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 } = message;
const { application$system: applications } = system;
const app = applications.find(ele => ele.type === 'wechatMp');
const { config } = app;
const { appId, appSecret } = config;
const instance = WechatSDK_1.default.getInstance(appId, 'wechatMp', appSecret);
let page;
if (router) {
const pathname = router.pathname;
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = (0, domain_1.composeUrl)(url, Object.assign({}, router.props, router.state));
}
// 根据当前环境决定消息推哪个版本
const StateDict = {
'development': 'developer',
'staging': 'trial',
'production': 'former',
};
try {
await instance.sendSubscribedMessage({
templateId: templateId,
data: data,
openId: data1.openId,
page,
state: StateDict[process.env.NODE_ENV],
});
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
catch (err) {
console.warn('发微信小程序消息失败', err);
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
};
exports.wechatMpHandler = wechatMpHandler;

View File

@ -0,0 +1,2 @@
import { NotificationHandler } from './index';
export declare const wechatPublicHandler: NotificationHandler;

View File

@ -0,0 +1,111 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.wechatPublicHandler = void 0;
const tslib_1 = require("tslib");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const WechatSDK_1 = tslib_1.__importDefault(require("oak-external-sdk/lib/WechatSDK"));
const domain_1 = require("oak-domain/lib/utils/domain");
const domain_2 = require("../domain");
const wechatPublicHandler = async (notification, context) => {
const { data, templateId, 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 } = message;
const { application$system: applications } = system;
const app = applications.find(ele => ele.type === 'wechatPublic');
const { config, id: applicationId } = app;
const { appId, appSecret } = config;
const [domain] = await context.select('domain', {
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
system: {
application$system: {
id: applicationId,
},
},
},
}, { dontCollect: true });
const instance = WechatSDK_1.default.getInstance(appId, 'wechatPublic', appSecret);
const { openId, wechatMpAppId } = data1;
let page;
// message 用户不需要跳转页面
if (router) {
const pathname = router.pathname;
if (wechatMpAppId) {
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = (0, domain_1.composeUrl)(url, Object.assign({}, router.props, router.state));
}
else {
const url = (0, domain_2.composeDomainUrl)(domain, pathname);
page = (0, domain_1.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: page,
}
: undefined,
clientMsgId: id,
});
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
catch (err) {
console.warn('发微信公众号消息失败', err);
await context.operate('notification', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
};
exports.wechatPublicHandler = wechatPublicHandler;

View File

@ -9,7 +9,7 @@ import { Schema as MessageSystem } from './MessageSystem';
import { EntityDesc } from 'oak-domain/lib/types/EntityDesc';
export interface Schema extends EntityShape {
channel: Channel,
channel: String<32>,
application?: Application,
data?: Object,
messageSystem: MessageSystem,
@ -33,7 +33,7 @@ const IActionDef: ActionDef<IAction, IState> = {
export const entityDesc: EntityDesc<Schema, Action, '', {
iState: IState;
channel: Schema['channel']
// channel: Schema['channel']
}> = {
locales: {
zh_CN: {
@ -58,14 +58,14 @@ export const entityDesc: EntityDesc<Schema, Action, '', {
success: '发送成功',
failure: '发送失败',
},
channel: {
wechatPublic: '公众号',
jPush: '极光推送',
jim: '极光消息',
wechatMp: '小程序',
sms: '短信',
email: '邮箱',
}
// channel: {
// wechatPublic: '公众号',
// jPush: '极光推送',
// jim: '极光消息',
// wechatMp: '小程序',
// sms: '短信',
// email: '邮箱',
// }
}
},
},
@ -80,14 +80,14 @@ export const entityDesc: EntityDesc<Schema, Action, '', {
success: '#008000',
failure: '#9A9A9A',
},
channel: {
wechatMp: '#008000',
jPush: '#0000FF',
jim: '#0000FF',
wechatPublic: '#008000',
sms: '#000000',
email: '#000000',
},
// channel: {
// wechatMp: '#008000',
// jPush: '#0000FF',
// jim: '#0000FF',
// wechatPublic: '#008000',
// sms: '#000000',
// email: '#000000',
// },
}
}
};

View File

@ -16,11 +16,36 @@ export {
} from './endpoints/wechat';
export {
// 注册消息通知转换器trigger
/**
*
*
* 例如: 将message转换为微信小程序
*/
registerMessageNotificationConverters,
/**
*
*
* 例如: 处理微信小程序
*/
registerMessageHandler,
} from './utils/message';
export {
/**
*
*
* 例如: 实际发送微信小程序
*/
registerNotificationHandler,
/**
*
*
* ,
* 例如: 在其他渠道失败后自动发送短信通知
*/
registerNotificationFailureHandler,
} from './utils/notification';
export {
// 注册短信服务商实现
registSms,

View File

@ -5,11 +5,8 @@ import { CreateOperationData as CreateMessageData } from '../oak-app-domain/Mess
import { assert } from 'oak-domain/lib/utils/assert';
import { BRC } from '../types/RuntimeCxt';
import { Channel, Weight } from '../types/Message';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
import { uniqBy } from 'oak-domain/lib/utils/lodash';
import { Router } from '../entities/Message';
import { ConverterDict, getMessageHandler } from '../utils/message';
import { getMessageHandler } from '../utils/message';
const InitialChannelByWeightMatrix: Record<Weight, Channel[]> = {
high: ['wechatMp', 'wechatPublic', 'sms', 'email'],
@ -17,69 +14,6 @@ const InitialChannelByWeightMatrix: Record<Weight, Channel[]> = {
low: ['wechatMp', 'wechatPublic', 'email'],
};
export async function tryMakeSmsNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>) {
const { userId, type, entity, entityId, router } = message;
assert(userId);
const [mobile] = await context.select('mobile', {
data: {
id: 1,
mobile: 1,
},
filter: {
userId,
},
indexFrom: 0,
count: 1,
}, { dontCollect: true });
if (mobile) {
const converter = ConverterDict[type!] && ConverterDict[type!].toSms;
if (converter) {
const dispersedData = await converter(message as EntityDict['message']['OpSchema'], context);
if (dispersedData) {
return {
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'sms',
data1: mobile,
} as any;
}
}
}
}
export async function tryMakeEmailNotification(
message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
},
context: BackendRuntimeContext<EntityDict>
) {
const { userId, type, entity, entityId, router } = message;
const converter = ConverterDict[type!] && ConverterDict[type!].toEmail;
if (converter) {
const dispersedData = await converter(
message as EntityDict['message']['OpSchema'],
context
);
if (dispersedData) {
return {
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'email',
};
}
}
}
async function createNotification(message: CreateMessageData, context: BRC<EntityDict>) {
const { restriction, userId, weight, type, entity, entityId, platformId, channels } = message;
assert(userId);

View File

@ -3,307 +3,15 @@ import { EntityDict } from '../oak-app-domain/EntityDict';
import { CreateOperationData as CreateNotificationData } from '../oak-app-domain/Notification/Schema';
import { assert } from 'oak-domain/lib/utils/assert';
import { BRC } from '../types/RuntimeCxt';
import { WechatMpConfig, WechatPublicConfig, WebConfig } from '../oak-app-domain/Application/Schema';
import WechatSDK, { WechatMpInstance, WechatPublicInstance } from 'oak-external-sdk/lib/WechatSDK';
import { composeUrl } from 'oak-domain/lib/utils/domain';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { sendSms } from '../utils/sms';
import { sendEmail } from '../utils/email';
import { tryMakeSmsNotification } from './message';
import { composeDomainUrl } from '../utils/domain';
import { tryMakeSmsNotification } from '../utils/message/sms';
import { getNotificationHandler, getNotificationFailureHandlers } from '../utils/notification';
async function sendNotification(notification: EntityDict['notification']['OpSchema'], context: BRC<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;
let page;
if (router) {
const pathname = router.pathname;
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = composeUrl(
url,
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: {
system: {
application$system: {
id: applicationId,
},
},
},
},
{ dontCollect: true }
);
const instance = WechatSDK.getInstance(appId!, 'wechatPublic', appSecret) as WechatPublicInstance;
const { openId, wechatMpAppId } = data1 as {
openId: string,
wechatMpAppId?: string,
};
let page;
// message 用户不需要跳转页面
if (router) {
const pathname = router.pathname;
if (wechatMpAppId) {
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = composeUrl(
url,
Object.assign({}, router!.props!, router!.state!)
);
} else {
const url = composeDomainUrl(
domain as EntityDict['domain']['Schema'],
pathname
);
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: page as string,
}
: 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;
}
}
case 'email': {
try {
const result = await sendEmail(data as any, context);
if (result?.success) {
await context.operate(
'notification',
{
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
},
},
{ dontCollect: true }
);
} else {
await context.operate(
'notification',
{
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: result?.error,
},
},
filter: {
id,
},
},
{ dontCollect: true }
);
}
return 1;
} catch (err: any) {
await context.operate(
'notification',
{
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: err?.message,
},
},
filter: {
id,
},
},
{ dontCollect: true }
);
console.warn('发邮件消息失败', err);
return 1;
}
}
default: {
assert(channel === 'sms');
try {
const result = await sendSms({
messageType: type!,
templateParam: (data as { params: any }).params!,
mobile: (data1 as { mobile: string }).mobile,
}, context)
if (result?.success === true) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
} else {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
} catch (err) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
},
filter: {
id,
}
}, { dontCollect: true });
console.warn('发短信消息失败', err);
return 1;
}
}
}
const { channel } = notification;
const handler = getNotificationHandler(channel!);
await handler(notification, context);
return 1;
}
async function tryCreateSmsNotification(message: EntityDict['message']['Schema'], context: BRC<EntityDict>) {
@ -473,8 +181,26 @@ const triggers: Trigger<EntityDict, 'notification', BRC<EntityDict>>[] = [
return 1;
}
if (message.weight === 'medium' && !smsTried && allFailed) {
// 中级的消息,在其它途径都失败的情况下再发短信
// 获取所有注册的失败处理器
const failureHandlers = getNotificationFailureHandlers();
if (failureHandlers.length > 0) {
// 如果有注册的失败处理器,执行所有处理器
let totalResult = 0;
for (const handler of failureHandlers) {
try {
const result = await handler(message as EntityDict['message']['Schema'], context);
totalResult += result;
} catch (err) {
console.error('执行notification失败处理器时出错:', err);
}
}
if (totalResult > 0) {
closeRootMode();
return totalResult;
}
} else if (message.weight === 'medium' && !smsTried && allFailed) {
// 如果没有注册处理器,走原有逻辑:中级的消息,在其它途径都失败的情况下再发短信
const result = await tryCreateSmsNotification(
message as EntityDict['message']['Schema'],
context
@ -482,6 +208,7 @@ const triggers: Trigger<EntityDict, 'notification', BRC<EntityDict>>[] = [
closeRootMode();
return result;
}
// 标识消息发送失败
if (allFailed) {
await context.operate(

View File

@ -1,6 +1,8 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import BackendRuntimeContext from '../../context/BackendRuntimeContext';
import { Router } from '../../entities/Message';
import { EntityDict } from '../../oak-app-domain';
import { MessageHandler } from './index';
import { tryMakeEmailNotification } from '../../triggers/message';
import { ConverterDict, MessageHandler } from './index';
export const emailHandler: MessageHandler = async ({ message, applications, system, messageTypeTemplates, context }) => {
const notificationDatas: Omit<EntityDict['notification']['CreateOperationData'], 'messageSystemId'>[] = [];
@ -14,3 +16,31 @@ export const emailHandler: MessageHandler = async ({ message, applications, syst
return notificationDatas;
};
export async function tryMakeEmailNotification(
message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
},
context: BackendRuntimeContext<EntityDict>
) {
const { userId, type, entity, entityId, router } = message;
const converter = ConverterDict[type!] && ConverterDict[type!].toEmail;
if (converter) {
const dispersedData = await converter(
message as EntityDict['message']['OpSchema'],
context
);
if (dispersedData) {
return {
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'email',
};
}
}
}

View File

@ -5,6 +5,10 @@ import { CreateOperationData as CreateMessageData } from '../../oak-app-domain/M
import { Channel, MessageNotificationConverter } from "../../types/Message";
import { assert } from "oak-domain/lib/utils/assert";
import BackendRuntimeContext from "../../context/BackendRuntimeContext";
import { wechatMpHandler } from './wechatMp';
import { wechatPublicHandler } from './wechatPublic';
import { smsHandler } from './sms';
import { emailHandler } from './email';
export type MessageHandler = (options: {
message: CreateMessageData,
@ -33,3 +37,9 @@ export function getMessageHandler(channel: Channel): MessageHandler {
assert(handler, `消息渠道 ${channel} 的处理器未注册`);
return handler;
}
// 默认注册所有处理器
registerMessageHandler('wechatMp', wechatMpHandler);
registerMessageHandler('wechatPublic', wechatPublicHandler);
registerMessageHandler('sms', smsHandler);
registerMessageHandler('email', emailHandler);

View File

@ -1,6 +1,9 @@
import assert from 'assert';
import BackendRuntimeContext from '../../context/BackendRuntimeContext';
import { Router } from '../../entities/Message';
import { EntityDict } from '../../oak-app-domain';
import { MessageHandler } from './index';
import { tryMakeSmsNotification } from '../../triggers/message';
import { ConverterDict, MessageHandler } from './index';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
export const smsHandler: MessageHandler = async ({ message, applications, system, messageTypeTemplates, context }) => {
const notificationDatas: Omit<EntityDict['notification']['CreateOperationData'], 'messageSystemId'>[] = [];
@ -12,3 +15,39 @@ export const smsHandler: MessageHandler = async ({ message, applications, system
return notificationDatas;
};
export async function tryMakeSmsNotification(message: {
userId?: string;
type?: string;
entity?: string;
router?: Router | null;
entityId?: string;
}, context: BackendRuntimeContext<EntityDict>) {
const { userId, type, entity, entityId, router } = message;
assert(userId);
const [mobile] = await context.select('mobile', {
data: {
id: 1,
mobile: 1,
},
filter: {
userId,
},
indexFrom: 0,
count: 1,
}, { dontCollect: true });
if (mobile) {
const converter = ConverterDict[type!] && ConverterDict[type!].toSms;
if (converter) {
const dispersedData = await converter(message as EntityDict['message']['OpSchema'], context);
if (dispersedData) {
return {
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'sms',
data1: mobile,
} as any;
}
}
}
}

View File

@ -0,0 +1,62 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { EntityDict } from '../../oak-app-domain';
import { BRC } from '../../types/RuntimeCxt';
import { sendEmail } from '../email';
import { NotificationHandler } from './index';
export const emailHandler: NotificationHandler = async (notification, context) => {
const { data, id } = notification;
try {
const result = await sendEmail(data as any, context);
if (result?.success) {
await context.operate(
'notification',
{
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
},
},
{ dontCollect: true }
);
} else {
await context.operate(
'notification',
{
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: result?.error,
},
},
filter: {
id,
},
},
{ dontCollect: true }
);
}
} catch (err: any) {
await context.operate(
'notification',
{
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: err?.message,
},
},
filter: {
id,
},
},
{ dontCollect: true }
);
console.warn('发邮件消息失败', err);
}
};

View File

@ -0,0 +1,45 @@
import { EntityDict } from "../../oak-app-domain";
import { BRC } from "../../types/RuntimeCxt";
import { Channel } from "../../types/Message";
import { assert } from "oak-domain/lib/utils/assert";
import { wechatMpHandler } from './wechatMp';
import { wechatPublicHandler } from './wechatPublic';
import { smsHandler } from './sms';
import { emailHandler } from './email';
export type NotificationHandler = (
notification: EntityDict['notification']['OpSchema'],
context: BRC<EntityDict>
) => Promise<void>;
export type NotificationFailureHandler = (
message: EntityDict['message']['Schema'],
context: BRC<EntityDict>
) => Promise<number>;
const notificationHandlers: Record<Channel, NotificationHandler> = {} as Record<Channel, NotificationHandler>;
const notificationFailureHandlers: NotificationFailureHandler[] = [];
export function registerNotificationHandler(channel: Channel, handler: NotificationHandler) {
notificationHandlers[channel] = handler;
}
export function getNotificationHandler(channel: Channel): NotificationHandler {
const handler = notificationHandlers[channel];
assert(handler, `通知渠道 ${channel} 的处理器未注册`);
return handler;
}
export function registerNotificationFailureHandler(handler: NotificationFailureHandler) {
notificationFailureHandlers.push(handler);
}
export function getNotificationFailureHandlers(): NotificationFailureHandler[] {
return notificationFailureHandlers;
}
// 默认注册所有处理器
registerNotificationHandler('wechatMp', wechatMpHandler);
registerNotificationHandler('wechatPublic', wechatPublicHandler);
registerNotificationHandler('sms', smsHandler);
registerNotificationHandler('email', emailHandler);

View File

@ -0,0 +1,71 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { EntityDict } from '../../oak-app-domain';
import { BRC } from '../../types/RuntimeCxt';
import { sendSms } from '../sms';
import { NotificationHandler } from './index';
export const smsHandler: NotificationHandler = async (notification, context) => {
const { data, data1, id } = notification;
const [messageSystem] = await context.select('messageSystem', {
data: {
id: 1,
message: {
id: 1,
type: 1,
},
},
filter: {
id: notification.messageSystemId,
}
}, { dontCollect: true });
const { message } = messageSystem!;
const { type } = message!;
try {
const result = await sendSms({
messageType: type!,
templateParam: (data as { params: any }).params!,
mobile: (data1 as { mobile: string }).mobile,
}, context);
if (result?.success === true) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
} else {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {
data2: {
res: result?.res || {}
}
},
filter: {
id,
}
}, { dontCollect: true });
}
} catch (err) {
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
console.warn('发短信消息失败', err);
}
};

View File

@ -0,0 +1,96 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { EntityDict } from '../../oak-app-domain';
import { BRC } from '../../types/RuntimeCxt';
import { WechatMpConfig } from '../../oak-app-domain/Application/Schema';
import WechatSDK, { WechatMpInstance } from 'oak-external-sdk/lib/WechatSDK';
import { composeUrl } from 'oak-domain/lib/utils/domain';
import { NotificationHandler } from './index';
export const wechatMpHandler: NotificationHandler = async (notification, context) => {
const { data, templateId, 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 } = message!;
const { application$system: applications } = system!;
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;
let page;
if (router) {
const pathname = router.pathname;
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = composeUrl(
url,
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,
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 });
} catch (err) {
console.warn('发微信小程序消息失败', err);
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
};

View File

@ -0,0 +1,136 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { EntityDict } from '../../oak-app-domain';
import { BRC } from '../../types/RuntimeCxt';
import { WechatPublicConfig } from '../../oak-app-domain/Application/Schema';
import WechatSDK, { WechatPublicInstance } from 'oak-external-sdk/lib/WechatSDK';
import { composeUrl } from 'oak-domain/lib/utils/domain';
import { composeDomainUrl } from '../domain';
import { NotificationHandler } from './index';
export const wechatPublicHandler: NotificationHandler = async (notification, context) => {
const { data, templateId, 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 } = message!;
const { application$system: applications } = system!;
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: {
system: {
application$system: {
id: applicationId,
},
},
},
},
{ dontCollect: true }
);
const instance = WechatSDK.getInstance(appId!, 'wechatPublic', appSecret) as WechatPublicInstance;
const { openId, wechatMpAppId } = data1 as {
openId: string,
wechatMpAppId?: string,
};
let page;
// message 用户不需要跳转页面
if (router) {
const pathname = router.pathname;
if (wechatMpAppId) {
const url = pathname.startsWith('/')
? `pages${pathname}/index`
: `pages/${pathname}/index`;
page = composeUrl(
url,
Object.assign({}, router!.props!, router!.state!)
);
} else {
const url = composeDomainUrl(
domain as EntityDict['domain']['Schema'],
pathname
);
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: page as string,
}
: undefined,
clientMsgId: id,
});
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {},
filter: {
id,
}
}, { dontCollect: true });
} catch (err) {
console.warn('发微信公众号消息失败', err);
await context.operate('notification', {
id: await generateNewIdAsync(),
action: 'fail',
data: {},
filter: {
id,
}
}, { dontCollect: true });
}
};