message和notification的逻辑(未测试)

This commit is contained in:
Xu Chang 2023-02-15 15:57:48 +08:00
parent 0f8d42573d
commit 73a818b7df
41 changed files with 710 additions and 281 deletions

View File

@ -1,18 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var filter_1 = require("oak-domain/lib/store/filter");
var tslib_1 = require("tslib");
var assert_1 = tslib_1.__importDefault(require("assert"));
var validator_1 = require("oak-domain/lib/utils/validator");
var checkers = [
{
type: 'logical',
action: 'select',
type: 'data',
action: 'create',
entity: 'messageTypeTemplateId',
checker: function (operation, context) {
var applicationId = context.getApplicationId();
var filter = {
applicationId: applicationId,
};
operation.filter = operation.filter ? (0, filter_1.combineFilters)([operation.filter, filter]) : filter;
},
checker: function (data, context) {
(0, assert_1.default)(!(data instanceof Array));
(0, validator_1.checkAttributesNotNull)('messageTypeTemplateId', data, ['type', 'templateId', 'applicationId']);
}
}
];
exports.default = checkers;

View File

@ -15,7 +15,7 @@ exports.default = OakComponent({
id: 1,
name: 1,
},
params: 1,
router: 1,
},
formData: function (_a) {
var message = _a.data, features = _a.features, props = _a.props;

View File

@ -4,12 +4,12 @@ import { EntityDict } from '../../../general-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'message', false, {
onItemClicked: (item: {
id: string;
params: EntityDict['message']['Schema']['params'];
router: EntityDict['message']['Schema']['router'];
}) => void;
$$createAt$$: number;
type: string;
title: string;
params: EntityDict['message']['Schema']['params'];
router: EntityDict['message']['Schema']['router'];
visitState: EntityDict['message']['Schema']['visitState'];
id: string;
}, {}>): JSX.Element;

View File

@ -16,13 +16,13 @@ var MessageTypeToColor = {
};
function Render(props) {
var data = props.data, methods = props.methods;
var id = data.id, params = data.params, title = data.title, type = data.type, $$createAt$$ = data.$$createAt$$, visitState = data.visitState, _a = data.oakLegalActions, oakLegalActions = _a === void 0 ? [] : _a, onItemClicked = data.onItemClicked;
var id = data.id, router = data.router, title = data.title, type = data.type, $$createAt$$ = data.$$createAt$$, visitState = data.visitState, _a = data.oakLegalActions, oakLegalActions = _a === void 0 ? [] : _a, onItemClicked = data.onItemClicked;
var navigateTo = methods.navigateTo, execute = methods.execute;
return ((0, jsx_runtime_1.jsxs)("div", tslib_1.__assign({ className: web_module_less_1.default.list, onClick: onItemClicked
? function () {
onItemClicked({
id: id,
params: params,
params: router,
});
}
: undefined }, { children: [(0, jsx_runtime_1.jsxs)("div", tslib_1.__assign({ className: web_module_less_1.default.list__notify }, { children: [visitState === 'unvisited' && ((0, jsx_runtime_1.jsx)(antd_1.Badge, { style: { marginRight: 5 }, status: "processing" })), (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: web_module_less_1.default.notify_deadline }, { children: title })), oakLegalActions.includes('visit') && ((0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: web_module_less_1.default.notify_mask, onClick: function (event) {

View File

@ -2,7 +2,7 @@ import { String, Text } from 'oak-domain/lib/types/DataType';
import { Schema as User } from './User';
import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Channel, Weight } from '../types/Message';
declare type MessageParams = {
declare type Router = {
pathname: string;
props?: Record<string, any>;
state?: Record<string, any>;
@ -21,6 +21,6 @@ export interface Schema extends EntityShape {
title: String<256>;
content: Text;
data?: Object;
params?: MessageParams;
router?: Router;
}
export {};

View File

@ -26,7 +26,7 @@ var locale = {
weight: '优先级',
iState: '发送状态',
visitState: '访问状态',
params: '渠道定制参数',
router: '目标路由',
data: '透传数据',
},
action: {

View File

@ -7,7 +7,7 @@ import { Action, ParticularAction, IState, VisitState } from "./Action";
import { Channel, Weight } from "../../types/Message";
import * as User from "../User/Schema";
import * as MessageSystem from "../MessageSystem/Schema";
declare type MessageParams = {
declare type Router = {
pathname: string;
props?: Record<string, any>;
state?: Record<string, any>;
@ -26,7 +26,7 @@ export declare type OpSchema = EntityShape & {
title: String<256>;
content: Text;
data?: Object | null;
params?: MessageParams | null;
router?: Router | null;
iState?: IState | null;
visitState?: VisitState | null;
};
@ -41,7 +41,7 @@ export declare type Schema = EntityShape & {
title: String<256>;
content: Text;
data?: Object | null;
params?: MessageParams | null;
router?: Router | null;
iState?: IState | null;
visitState?: VisitState | null;
user: User.Schema;
@ -65,7 +65,7 @@ declare type AttrFilter = {
title: Q_StringValue;
content: Q_StringValue;
data: Object;
params: Q_EnumValue<MessageParams>;
router: Q_EnumValue<Router>;
iState: Q_EnumValue<IState>;
visitState: Q_EnumValue<VisitState>;
};
@ -87,7 +87,7 @@ export declare type Projection = {
title?: number;
content?: number;
data?: number;
params?: number;
router?: number;
iState?: number;
visitState?: number;
messageSystem$message?: MessageSystem.Selection & {
@ -130,7 +130,7 @@ export declare type SortAttr = {
} | {
content: number;
} | {
params: number;
router: number;
} | {
iState: number;
} | {

View File

@ -44,7 +44,7 @@ exports.desc = {
data: {
type: "object"
},
params: {
router: {
type: "object"
},
iState: {

View File

@ -1 +1 @@
{ "attr": { "entity": "关联对象", "entityId": "关联对象ID", "restriction": "限制", "title": "标题", "content": "内容", "user": "关联用户", "type": "消息类型", "weight": "优先级", "iState": "发送状态", "visitState": "访问状态", "params": "渠道定制参数", "data": "透传数据" }, "action": { "succeed": "成功", "fail": "失败", "visit": "阅读" }, "v": { "iState": { "sending": "发送中", "success": "发送成功", "failure": "发送失败" }, "visitState": { "unvisited": "未读", "visited": "已读" }, "weight": { "high": "高", "medium": "中", "low": "低" } } }
{ "attr": { "entity": "关联对象", "entityId": "关联对象ID", "restriction": "限制", "title": "标题", "content": "内容", "user": "关联用户", "type": "消息类型", "weight": "优先级", "iState": "发送状态", "visitState": "访问状态", "router": "目标路由", "data": "透传数据" }, "action": { "succeed": "成功", "fail": "失败", "visit": "阅读" }, "v": { "iState": { "sending": "发送中", "success": "发送成功", "failure": "发送失败" }, "visitState": { "unvisited": "未读", "visited": "已读" }, "weight": { "high": "高", "medium": "中", "low": "低" } } }

View File

@ -77,6 +77,7 @@ function createComponent(option, features) {
});
} }, methods), lifetimes: tslib_1.__assign({ ready: function () {
if (relatedMessageTypes) {
var applicationId = this.features.application.getApplicationId();
this.features.cache.refresh('messageTypeTemplateId', {
data: {
id: 1,
@ -87,6 +88,7 @@ function createComponent(option, features) {
type: {
$in: relatedMessageTypes,
},
applicationId: applicationId,
},
});
}

View File

@ -5,6 +5,7 @@ var jsx_runtime_1 = require("react/jsx-runtime");
var antd_1 = require("antd");
var pageHeader_1 = tslib_1.__importDefault(require("../../../components/common/pageHeader"));
var web_module_less_1 = tslib_1.__importDefault(require("./web.module.less"));
var list_1 = tslib_1.__importDefault(require("../../../components/messageTypeTemplateId/list"));
function Render(props) {
var _a = props.data, oakId = _a.oakId, tabValue = _a.tabValue, config = _a.config, name = _a.name, description = _a.description, type = _a.type, system = _a.system;
var _b = props.methods, t = _b.t, navigateBack = _b.navigateBack, onTabClick = _b.onTabClick, goWechatPublicTagList = _b.goWechatPublicTagList;
@ -12,13 +13,21 @@ function Render(props) {
if (type === 'wechatPublic') {
Actions.push((0, jsx_runtime_1.jsx)(antd_1.Button, tslib_1.__assign({ onClick: function () { return goWechatPublicTagList(); } }, { children: "\u516C\u4F17\u53F7Tag\u7BA1\u7406" })));
}
return ((0, jsx_runtime_1.jsx)(pageHeader_1.default, tslib_1.__assign({ showBack: true, title: "\u5E94\u7528\u6982\u89C8" }, { children: (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: web_module_less_1.default.container }, { children: (0, jsx_runtime_1.jsx)(antd_1.Card, tslib_1.__assign({ title: name, bordered: false, actions: Actions }, { children: (0, jsx_runtime_1.jsx)(antd_1.Tabs, { items: [
{
label: '应用概览',
key: 'detail',
children: ((0, jsx_runtime_1.jsxs)(antd_1.Descriptions, tslib_1.__assign({ column: 1, bordered: true }, { children: [(0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: "id" }, { children: (0, jsx_runtime_1.jsx)(antd_1.Typography.Paragraph, tslib_1.__assign({ copyable: true }, { children: oakId })) })), (0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: t('application:attr.name') }, { children: name })), (0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: t('application:attr.description') }, { children: description })), (0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: t('application:attr.type') }, { children: t("application:v.type.".concat(type)) })), (0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: t('application:attr.system') +
t('system:attr.name') }, { children: system === null || system === void 0 ? void 0 : system.name }))] }))),
},
] }) })) })) })));
var items = [
{
label: '应用概览',
key: 'detail',
children: ((0, jsx_runtime_1.jsxs)(antd_1.Descriptions, tslib_1.__assign({ column: 1, bordered: true }, { children: [(0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: "id" }, { children: (0, jsx_runtime_1.jsx)(antd_1.Typography.Paragraph, tslib_1.__assign({ copyable: true }, { children: oakId })) })), (0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: t('application:attr.name') }, { children: name })), (0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: t('application:attr.description') }, { children: description })), (0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: t('application:attr.type') }, { children: t("application:v.type.".concat(type)) })), (0, jsx_runtime_1.jsx)(antd_1.Descriptions.Item, tslib_1.__assign({ label: t('application:attr.system') +
t('system:attr.name') }, { children: system === null || system === void 0 ? void 0 : system.name }))] }))),
},
];
if (['wechatPublic', 'wechatMp'].includes(type)) {
items.push({
label: '模板消息管理',
key: 'mttId',
children: (0, jsx_runtime_1.jsx)(list_1.default, { applicationId: oakId, oakPath: "$application-detail-mttId" })
});
}
return ((0, jsx_runtime_1.jsx)(pageHeader_1.default, tslib_1.__assign({ showBack: true, title: "\u5E94\u7528\u6982\u89C8" }, { children: (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: web_module_less_1.default.container }, { children: (0, jsx_runtime_1.jsx)(antd_1.Card, tslib_1.__assign({ title: name, bordered: false, actions: Actions }, { children: (0, jsx_runtime_1.jsx)(antd_1.Tabs, { items: items }) })) })) })));
}
exports.default = Render;

View File

@ -15,7 +15,7 @@ exports.default = OakComponent({
id: 1,
name: 1,
},
params: 1,
router: 1,
},
isList: false,
formData: function (_a) {
@ -34,10 +34,10 @@ exports.default = OakComponent({
},
methods: {
goPage: function () {
var params = this.state.params;
var pathname = params === null || params === void 0 ? void 0 : params.pathname;
var props = (params === null || params === void 0 ? void 0 : params.props) || {};
var state = params === null || params === void 0 ? void 0 : params.state;
var router = this.state.router;
var pathname = router === null || router === void 0 ? void 0 : router.pathname;
var props = (router === null || router === void 0 ? void 0 : router.props) || {};
var state = router === null || router === void 0 ? void 0 : router.state;
if (!pathname) {
return;
}

View File

@ -8,7 +8,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'message', f
$$createAt$$: number;
type: string;
visitState: EntityDict['message']['Schema']['visitState'];
params: EntityDict['message']['Schema']['params'];
router: EntityDict['message']['Schema']['router'];
}, {
goPage: () => void;
}>): JSX.Element;

View File

@ -6,9 +6,9 @@ var antd_1 = require("antd");
var mobile_module_less_1 = tslib_1.__importDefault(require("./mobile.module.less"));
function Render(props) {
var data = props.data, methods = props.methods;
var title = data.title, content = data.content, params = data.params;
var title = data.title, content = data.content, router = data.router;
var t = methods.t, goPage = methods.goPage;
var pathname = params === null || params === void 0 ? void 0 : params.pathname;
var pathname = router === null || router === void 0 ? void 0 : router.pathname;
return ((0, jsx_runtime_1.jsxs)("div", tslib_1.__assign({ className: mobile_module_less_1.default.container }, { children: [(0, jsx_runtime_1.jsx)("h1", tslib_1.__assign({ className: mobile_module_less_1.default.title }, { children: title })), (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: mobile_module_less_1.default.content }, { children: content })), pathname && ((0, jsx_runtime_1.jsx)(antd_1.Button, tslib_1.__assign({ className: mobile_module_less_1.default.btn, block: true, type: "primary", onClick: function () {
goPage();
} }, { children: "\u524D\u5F80" })))] })));

View File

@ -8,7 +8,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'message', f
$$createAt$$: number;
type: string;
visitState: EntityDict['message']['Schema']['visitState'];
params: EntityDict['message']['Schema']['params'];
router: EntityDict['message']['Schema']['router'];
}, {
goPage: () => void;
}>): JSX.Element;

View File

@ -7,9 +7,9 @@ var pageHeader_1 = tslib_1.__importDefault(require("../../../components/common/p
var web_module_less_1 = tslib_1.__importDefault(require("./web.module.less"));
function Render(props) {
var data = props.data, methods = props.methods;
var title = data.title, content = data.content, params = data.params;
var title = data.title, content = data.content, router = data.router;
var t = methods.t, goPage = methods.goPage;
var pathname = params === null || params === void 0 ? void 0 : params.pathname;
var pathname = router === null || router === void 0 ? void 0 : router.pathname;
return ((0, jsx_runtime_1.jsx)(pageHeader_1.default, tslib_1.__assign({ title: "\u6D88\u606F\u8BE6\u60C5", showBack: true }, { children: (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: web_module_less_1.default.container }, { children: (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: web_module_less_1.default.warp }, { children: (0, jsx_runtime_1.jsxs)("div", tslib_1.__assign({ className: web_module_less_1.default.inner }, { children: [(0, jsx_runtime_1.jsx)("h1", tslib_1.__assign({ className: web_module_less_1.default.title }, { children: title })), (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: web_module_less_1.default.content }, { children: content })), pathname && ((0, jsx_runtime_1.jsx)(antd_1.Button, tslib_1.__assign({ className: web_module_less_1.default.btn, block: true, type: "primary", onClick: function () {
goPage();
} }, { children: "\u524D\u5F80" })))] })) })) })) })));

View File

@ -15,7 +15,7 @@ exports.default = OakComponent({
id: 1,
name: 1,
},
params: 1,
router: 1,
},
filters: [
{

View File

@ -18,7 +18,7 @@ function Render(props) {
}, destroyOnClose: true }, { children: (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: web_module_less_1.default.container }, { children: (messages === null || messages === void 0 ? void 0 : messages.length) > 0 ? ((0, jsx_runtime_1.jsx)("div", { children: messages === null || messages === void 0 ? void 0 : messages.map(function (message, index) { return ((0, jsx_runtime_1.jsx)(cell_1.default, { oakId: message.id, oakPath: oakFullpath
? "".concat(oakFullpath, ".").concat(message.id)
: '', onItemClicked: function (item) {
var params = item.params, id = item.id;
var id = item.id;
onClose && onClose();
goDetailById(id);
} }, message.id)); }) })) : ((0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: web_module_less_1.default.noData }, { children: (0, jsx_runtime_1.jsx)(empty_1.default, { description: "\u6682\u65E0\u6D88\u606F", image: empty_1.default.PRESENTED_IMAGE_SIMPLE }) }))) })) })));

View File

@ -15,7 +15,7 @@ exports.default = OakComponent({
id: 1,
name: 1,
},
params: 1,
router: 1,
},
filters: [
{

View File

@ -7,20 +7,21 @@ var types_1 = require("oak-domain/lib/types");
var assert_1 = tslib_1.__importDefault(require("assert"));
function rewriteFilter(schema, entity, filter) {
var e_1, _a, _b, _c, _d, _e;
var _f;
for (var attr in filter) {
if (attr === '#id' || attr === '$text' || attr.toLowerCase().startsWith(types_1.EXPRESSION_PREFIX)) {
}
else if (['$and', '$or'].includes(attr)) {
try {
for (var _f = (e_1 = void 0, tslib_1.__values(filter[attr])), _g = _f.next(); !_g.done; _g = _f.next()) {
var node = _g.value;
for (var _g = (e_1 = void 0, tslib_1.__values(filter[attr])), _h = _g.next(); !_h.done; _h = _g.next()) {
var node = _h.value;
rewriteFilter(schema, entity, node);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_g && !_g.done && (_a = _f.return)) _a.call(_f);
if (_h && !_h.done && (_a = _g.return)) _a.call(_g);
}
finally { if (e_1) throw e_1.error; }
}
@ -33,7 +34,7 @@ function rewriteFilter(schema, entity, filter) {
* 这里要处理的就是把userId受到的约束扩展到存在merge的case
* 大部分这类约束都来自relation类型的checkerauth来自auth的由系统创建的checker一定是{ userId: xxx }的形式但用户手写的有可能是{ user: { id: xxxx }}的形式
*/
if (attr.endsWith('Id') && attr !== 'entityId') {
if (attr.endsWith('Id') && attr !== 'entityId' && ((_f = schema[entity].attributes[attr]) === null || _f === void 0 ? void 0 : _f.type) === 'ref') {
// 只要是指向user的ref都要处理
var rel = (0, relation_1.judgeRelation)(schema, entity, attr.slice(0, attr.length - 2));
if (rel === 'user') {
@ -107,7 +108,7 @@ function rewriteFilter(schema, entity, filter) {
rewriteFilter(schema, rel, filter[attr]);
}
else if (rel instanceof Array) {
var _h = tslib_1.__read(rel, 1), e = _h[0];
var _j = tslib_1.__read(rel, 1), e = _j[0];
var f = filter[attr].filter;
if (f) {
rewriteFilter(schema, e, f);
@ -115,7 +116,7 @@ function rewriteFilter(schema, entity, filter) {
}
else if (typeof filter[attr] === 'object') {
// 还要处理子查询
var _j = filter[attr], $in = _j.$in, $nin = _j.$nin;
var _k = filter[attr], $in = _k.$in, $nin = _k.$nin;
if ($in && !($in instanceof Array)) {
var e = $in.entity, f = $in.filter;
rewriteFilter(schema, e, f);

View File

@ -1,5 +1,5 @@
import { Trigger } from 'oak-domain/lib/types/Trigger';
import { EntityDict } from '../general-app-domain/EntityDict';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const triggers: Trigger<EntityDict, 'notification', RuntimeCxt>[];
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
declare const triggers: Trigger<EntityDict, 'notification', BackendRuntimeContext<EntityDict>>[];
export default triggers;

View File

@ -1,11 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var assert_1 = require("oak-domain/lib/utils/assert");
function sendMessage(messageSentData, context) {
var oak_external_sdk_1 = require("oak-external-sdk");
function sendMessage(notification, context) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
return [2 /*return*/];
var data, templateId, channel, messageSystemId, _a, messageSystem, system, applications, _b, app, config, _c, appId, appSecret, instance;
return tslib_1.__generator(this, function (_d) {
switch (_d.label) {
case 0:
data = notification.data, templateId = notification.templateId, channel = notification.channel, messageSystemId = notification.messageSystemId;
return [4 /*yield*/, context.select('messageSystem', {
data: {
id: 1,
messageId: 1,
message: {
id: 1,
userId: 1,
},
system: {
id: 1,
application$system: {
$entity: 'application',
data: {
id: 1,
type: 1,
config: 1,
},
},
}
},
filter: {
id: messageSystemId,
}
}, { dontCollect: true })];
case 1:
_a = tslib_1.__read.apply(void 0, [_d.sent(), 1]), messageSystem = _a[0];
system = messageSystem.system;
applications = system.application$system;
_b = channel;
switch (_b) {
case 'mp': return [3 /*break*/, 2];
}
return [3 /*break*/, 4];
case 2:
app = applications.find(function (ele) { return ele.type === 'wechatMp'; });
config = app.config;
_c = config, appId = _c.appId, appSecret = _c.appSecret;
instance = oak_external_sdk_1.WechatSDK.getInstance(appId, 'wechatMp', appSecret);
return [4 /*yield*/, instance.sendSubscribedMessage({
templateId: templateId,
data: data,
})];
case 3:
_d.sent();
_d.label = 4;
case 4: return [2 /*return*/];
}
});
});
}
@ -19,29 +69,45 @@ var triggers = [
fn: function (_a, context, params) {
var operation = _a.operation;
return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var data, filter, fn;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
var data, data_1, data_1_1, d, e_1_1;
var e_1, _b;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0:
data = operation.data, filter = operation.filter;
fn = function (messageSentData) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, sendMessage(messageSentData, context)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
}); };
if (!(data instanceof Array)) return [3 /*break*/, 1];
(0, assert_1.assert)('不存在一对多的情况');
return [3 /*break*/, 3];
case 1: return [4 /*yield*/, fn(data)];
data = operation.data;
if (!(data instanceof Array)) return [3 /*break*/, 9];
_c.label = 1;
case 1:
_c.trys.push([1, 6, 7, 8]);
data_1 = tslib_1.__values(data), data_1_1 = data_1.next();
_c.label = 2;
case 2:
_b.sent();
_b.label = 3;
case 3: return [2 /*return*/, 0];
if (!!data_1_1.done) return [3 /*break*/, 5];
d = data_1_1.value;
return [4 /*yield*/, sendMessage(d, context)];
case 3:
_c.sent();
_c.label = 4;
case 4:
data_1_1 = data_1.next();
return [3 /*break*/, 2];
case 5: return [3 /*break*/, 8];
case 6:
e_1_1 = _c.sent();
e_1 = { error: e_1_1 };
return [3 /*break*/, 8];
case 7:
try {
if (data_1_1 && !data_1_1.done && (_b = data_1.return)) _b.call(data_1);
}
finally { if (e_1) throw e_1.error; }
return [7 /*endfinally*/];
case 8: return [3 /*break*/, 11];
case 9: return [4 /*yield*/, sendMessage(data, context)];
case 10:
_c.sent();
_c.label = 11;
case 11: return [2 /*return*/, 0];
}
});
});

View File

@ -988,7 +988,7 @@ export async function sendCaptcha<ED extends EntityDict, Cxt extends BackendRunt
});
if (captcha) {
const code = captcha.code!;
if (process.env.NODE_ENV === 'development' || mockSend) {
if (process.env.NODE_ENV !== 'production' || mockSend) {
return `验证码[${code}]已创建`;
}
else if (captcha.$$createAt$$! as number - now < 60000) {
@ -1001,22 +1001,10 @@ export async function sendCaptcha<ED extends EntityDict, Cxt extends BackendRunt
origin: 'tencent',
templateName: '登录',
mobile,
templateParamSet: {
templateParamSet: [
code,
duration: duration.toString(),
},
templateParamSetFn: (origin, templateParamSet) => {
if (!templateParamSet) {
return templateParamSet;
}
if (origin === 'tencent') {
return [
templateParamSet.code,
templateParamSet.duration,
];
}
return undefined;
},
duration.toString(),
],
},
context
);
@ -1064,22 +1052,10 @@ export async function sendCaptcha<ED extends EntityDict, Cxt extends BackendRunt
origin: 'tencent',
templateName: '登录',
mobile,
templateParamSet: {
templateParamSet: [
code,
duration: duration.toString(),
},
templateParamSetFn: (origin, templateParamSet) => {
if (!templateParamSet) {
return templateParamSet;
}
if (origin === 'tencent') {
return [
templateParamSet.code,
templateParamSet.duration,
];
}
return undefined;
},
duration.toString(),
],
},
context
);

View File

@ -2,19 +2,18 @@ import { Checker } from "oak-domain/lib/types";
import { EntityDict } from '../general-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
import { combineFilters } from 'oak-domain/lib/store/filter';
import assert from "assert";
import { checkAttributesNotNull } from "oak-domain/lib/utils/validator";
const checkers: Checker<EntityDict, 'messageTypeTemplateId', RuntimeCxt> [] = [
{
type: 'logical',
action: 'select',
type: 'data',
action: 'create',
entity: 'messageTypeTemplateId',
checker: (operation, context) => {
const applicationId = context.getApplicationId();
const filter: EntityDict['messageTypeTemplateId']['Selection']['filter'] = {
applicationId,
};
operation.filter = operation.filter ? combineFilters([operation.filter, filter]): filter;
},
checker: (data, context) => {
assert(!(data instanceof Array));
checkAttributesNotNull('messageTypeTemplateId', data, ['type', 'templateId', 'applicationId']);
}
}
];

View File

@ -13,7 +13,7 @@ export default OakComponent({
id: 1,
name: 1,
},
params: 1,
router: 1,
},
formData: function ({ data: message, features, props }) {
return message || {};

View File

@ -26,12 +26,12 @@ export default function Render(
{
onItemClicked: (item: {
id: string;
params: EntityDict['message']['Schema']['params'];
router: EntityDict['message']['Schema']['router'];
}) => void;
$$createAt$$: number;
type: string;
title: string;
params: EntityDict['message']['Schema']['params'];
router: EntityDict['message']['Schema']['router'];
visitState: EntityDict['message']['Schema']['visitState'];
id: string;
},
@ -41,7 +41,7 @@ export default function Render(
const { data, methods } = props;
const {
id,
params,
router,
title,
type,
$$createAt$$,
@ -59,7 +59,7 @@ export default function Render(
? () => {
onItemClicked({
id,
params,
router,
});
}
: undefined

View File

@ -6,7 +6,7 @@ import { LocaleDef } from 'oak-domain/lib/types/Locale';
import { Index, ActionDef } from 'oak-domain/lib/types';
import { Channel, Weight } from '../types/Message';
type MessageParams = {
type Router = {
pathname: string;
props?: Record<string, any>;
state?: Record<string, any>;
@ -27,7 +27,7 @@ export interface Schema extends EntityShape {
title: String<256>;
content: Text;
data?: Object; // 透传到前台的数据OpRecords
params?: MessageParams; // 通知前端需要到达的路由
router?: Router; // 通知前端需要到达的路由
};
type IAction = 'succeed' | 'fail';
@ -74,7 +74,7 @@ const locale: LocaleDef<
weight: '优先级',
iState: '发送状态',
visitState: '访问状态',
params: '渠道定制参数',
router: '目标路由',
data: '透传数据',
},
action: {

View File

@ -60,7 +60,7 @@ const locale: LocaleDef<Schema, Action, '', {
wechatPublic: '公众号',
jPush: '极光推送',
jim: '极光消息',
mp: '小程序',
wechatMp: '小程序',
sms: '短信',
}
}

View File

@ -126,6 +126,7 @@ export function createComponent<
lifetimes: {
ready() {
if (relatedMessageTypes) {
const applicationId = this.features.application.getApplicationId();
this.features.cache.refresh('messageTypeTemplateId', {
data: {
id: 1,
@ -136,6 +137,7 @@ export function createComponent<
type: {
$in: relatedMessageTypes,
},
applicationId,
},
});
}

View File

@ -1,5 +1,5 @@
import React, { ReactNode } from 'react';
import { Tabs, Card, Descriptions, Typography, Button } from 'antd';
import { Tabs, Card, Descriptions, Typography, Button, TabsProps } from 'antd';
import PageHeader from '../../../components/common/pageHeader';
import Style from './web.module.less';
@ -12,6 +12,7 @@ import {
import { EntityDict } from '../../../general-app-domain';
import { WebComponentProps } from 'oak-frontend-base';
import MessageTypeTemplateIdList from '../../../components/messageTypeTemplateId/list';
type Config = WebConfig | WechatPublicConfig | WechatMpConfig;
@ -50,53 +51,61 @@ export default function Render(
</Button>
);
}
const items: TabsProps['items'] = [
{
label: '应用概览',
key: 'detail',
children: (
<Descriptions column={1} bordered>
<Descriptions.Item label="id">
<Typography.Paragraph copyable>
{oakId}
</Typography.Paragraph>
</Descriptions.Item>
<Descriptions.Item
label={t('application:attr.name')}
>
{name}
</Descriptions.Item>
<Descriptions.Item
label={t(
'application:attr.description'
)}
>
{description}
</Descriptions.Item>
<Descriptions.Item
label={t('application:attr.type')}
>
{t(`application:v.type.${type}`)}
</Descriptions.Item>
<Descriptions.Item
label={
t('application:attr.system') +
t('system:attr.name')
}
>
{system?.name}
</Descriptions.Item>
</Descriptions>
),
},
];
if (['wechatPublic', 'wechatMp'].includes(type)) {
items.push({
label: '模板消息管理',
key: 'mttId',
children: <MessageTypeTemplateIdList applicationId={oakId} oakPath="$application-detail-mttId"/>
})
}
return (
<PageHeader showBack={true} title="应用概览">
<div className={Style.container}>
<Card title={name} bordered={false} actions={Actions}>
<Tabs
items={[
{
label: '应用概览',
key: 'detail',
children: (
<Descriptions column={1} bordered>
<Descriptions.Item label="id">
<Typography.Paragraph copyable>
{oakId}
</Typography.Paragraph>
</Descriptions.Item>
<Descriptions.Item
label={t('application:attr.name')}
>
{name}
</Descriptions.Item>
<Descriptions.Item
label={t(
'application:attr.description'
)}
>
{description}
</Descriptions.Item>
<Descriptions.Item
label={t('application:attr.type')}
>
{t(`application:v.type.${type}`)}
</Descriptions.Item>
<Descriptions.Item
label={
t('application:attr.system') +
t('system:attr.name')
}
>
{system?.name}
</Descriptions.Item>
</Descriptions>
),
},
]}
items={items}
/>
</Card>
</div>

View File

@ -13,7 +13,7 @@ export default OakComponent({
id: 1,
name: 1,
},
params: 1,
router: 1,
},
isList: false,
formData: ({ data: message }) => {
@ -31,10 +31,10 @@ export default OakComponent({
},
methods: {
goPage() {
const { params } = this.state;
const pathname = params?.pathname;
const props = params?.props || {};
const state = params?.state;
const { router } = this.state;
const pathname = router?.pathname;
const props = router?.props || {};
const state = router?.state;
if (!pathname) {
return;
}

View File

@ -20,7 +20,7 @@ export default function Render(
$$createAt$$: number;
type: string;
visitState: EntityDict['message']['Schema']['visitState'];
params: EntityDict['message']['Schema']['params'];
router: EntityDict['message']['Schema']['router'];
},
{
goPage: () => void;
@ -28,9 +28,9 @@ export default function Render(
>
) {
const { data, methods } = props;
const { title, content, params } = data;
const { title, content, router } = data;
const { t, goPage } = methods;
const pathname = params?.pathname;
const pathname = router?.pathname;
return (

View File

@ -20,7 +20,7 @@ export default function Render(
$$createAt$$: number;
type: string;
visitState: EntityDict['message']['Schema']['visitState'];
params: EntityDict['message']['Schema']['params'];
router: EntityDict['message']['Schema']['router'];
},
{
goPage: () => void;
@ -28,9 +28,9 @@ export default function Render(
>
) {
const { data, methods } = props;
const { title, content, params } = data;
const { title, content, router } = data;
const { t, goPage } = methods;
const pathname = params?.pathname;
const pathname = router?.pathname;
return (
<div className={Style.container}>

View File

@ -13,7 +13,7 @@ export default OakComponent({
id: 1,
name: 1,
},
params: 1,
router: 1,
},
filters: [
{

View File

@ -69,9 +69,8 @@ export default function Render(
}
onItemClicked={(item: {
id: string;
params: EntityDict['message']['Schema']['params'];
}) => {
const { params, id } = item;
const { id } = item;
onClose && onClose();
goDetailById(id);
}}

View File

@ -14,7 +14,7 @@ export default OakComponent({
id: 1,
name: 1,
},
params: 1,
router: 1,
},
filters: [
{

View File

@ -25,7 +25,7 @@ function rewriteFilter<ED extends EntityDict & BaseEntityDict, T extends keyof E
* userId受到的约束扩展到存在merge的case
* relation类型的checkerauthauth的由系统创建的checker一定是{ userId: xxx }{ user: { id: xxxx }}
*/
if (attr.endsWith('Id') && attr !== 'entityId') {
if (attr.endsWith('Id') && attr !== 'entityId' && schema[entity].attributes[attr as string]?.type === 'ref') {
// 只要是指向user的ref都要处理
const rel = judgeRelation(schema, entity, attr.slice(0, attr.length - 2));
if (rel === 'user') {

View File

@ -20,11 +20,42 @@ export function registerMessageNotificationConverters<ED extends EntityDict, Cxt
const InitialChannalByWeightMatrix: Record<Weight, Channel[]> = {
high: ['mp', 'wechatPublic', 'sms'],
medium: ['mp', 'wechatPublic'],
low: ['mp', 'wechatPublic'],
high: ['wechatMp', 'wechatPublic', 'sms'],
medium: ['wechatMp', 'wechatPublic'],
low: ['wechatMp', 'wechatPublic'],
};
async function tryCreateSmsNotification(message: EntityDict['message']['Schema'], context: BackendRuntimeContext<EntityDict>) {
const { userId, type, entity, entityId } = message;
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(entity!, entityId!, context);
if (dispersedData) {
return {
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'sms',
data1: {
mobile,
},
} as Omit<EntityDict['notification']['CreateSingle']['data'], 'messageSystemId'>;
}
}
}
}
async function createNotification(message: CreateMessageData, context: BRC) {
const { restriction, userId, weight, type, entity, entityId } = message;
assert(userId);
@ -100,7 +131,7 @@ async function createNotification(message: CreateMessageData, context: BRC) {
return true;
}
);
if (channels.length === 0) {
console.warn(`类型为${type}的消息在生成时尝试为之生成通知找不到可推送的channel`);
return 0;
@ -114,21 +145,39 @@ async function createNotification(message: CreateMessageData, context: BRC) {
async (system) => {
const { application$system: applications, config } = system;
const notificationDatas: Omit<EntityDict['notification']['CreateSingle']['data'], 'messageSystemId'>[] = [];
await Promise.all(
channels.map(
async (channel) => {
switch (channel) {
case 'mp': {
const app = applications?.find(
case 'wechatMp': {
const apps = applications!.filter(
ele => ele.type === 'wechatMp',
);
if (app) {
const wechatUsers = await context.select('wechatUser', {
data: {
id: 1,
applicationId: 1,
openId: 1,
},
filter: {
applicationId: {
$in: apps.map(ele => ele.id!),
},
userId,
}
}, { dontCollect: true });
for (const app of apps) {
// 如果是wechatMp或者wechat还要保证用户已经有openId
const wechatUser = wechatUsers.find(
ele => ele.applicationId === app.id
);
const messageTypeTemplateId = messageTypeTemplateIds.find(
ele => ele.applicationId === app.id && ele.type === type
);
if (messageTypeTemplateId) {
if (messageTypeTemplateId && wechatUser) {
const converter = ConverterDict[type!] && ConverterDict[type!]!.toWechatMp;
const dispersedData = converter && await converter(entity!, entityId!, context);
const dispersedData = converter && await converter(entity!, entityId!, apps, app, context);
if (dispersedData) {
notificationDatas.push({
id: await generateNewIdAsync(),
@ -136,6 +185,9 @@ async function createNotification(message: CreateMessageData, context: BRC) {
channel,
applicationId: app.id,
templateId: messageTypeTemplateId.templateId!,
data1: {
openId: wechatUser.openId!,
}
});
}
}
@ -143,23 +195,45 @@ async function createNotification(message: CreateMessageData, context: BRC) {
break;
}
case 'wechatPublic': {
const app = applications?.find(
const apps = applications!.filter(
ele => ele.type === 'wechatPublic',
);
if (app) {
const wechatUsers = await context.select('wechatUser', {
data: {
id: 1,
applicationId: 1,
openId: 1,
},
filter: {
applicationId: {
$in: apps.map(ele => ele.id!),
},
userId,
}
}, { dontCollect: true });
for (const app of apps) {
// 如果是wechatMp或者wechat还要保证用户已经有openId
const wechatUser = wechatUsers.find(
ele => ele.applicationId === app.id
);
const messageTypeTemplateId = messageTypeTemplateIds.find(
ele => ele.applicationId === app.id && ele.type === type
);
if (messageTypeTemplateId) {
if (messageTypeTemplateId && wechatUser) {
const converter = ConverterDict[type!] && ConverterDict[type!]!.toWechatPublic;
const dispersedData = converter && await converter(entity!, entityId!, context);
if (dispersedData) {
const disperseResult = converter && await converter(entity!, entityId!, apps, app, context);
if (disperseResult) {
const { data, wechatMpAppId } = disperseResult;
notificationDatas.push({
id: await generateNewIdAsync(),
data: dispersedData,
data,
channel,
applicationId: app.id,
templateId: messageTypeTemplateId.templateId!,
data1: {
openId: wechatUser.openId!,
wechatMpAppId,
}
});
}
}
@ -167,7 +241,7 @@ async function createNotification(message: CreateMessageData, context: BRC) {
break;
}
default: {
assert (channel === 'sms'); // 目前只支持三种
assert(channel === 'sms'); // 目前只支持三种
break;
}
}
@ -175,19 +249,12 @@ async function createNotification(message: CreateMessageData, context: BRC) {
)
);
if (channels.includes('sms')) {
const converter = ConverterDict[type!] && ConverterDict[type!].toSms;
if (converter) {
const dispersedData = await converter(entity!, entityId!, context);
if (dispersedData) {
notificationDatas.push({
id: await generateNewIdAsync(),
data: dispersedData,
channel: 'sms',
});
}
const smsNotification = await tryCreateSmsNotification(message as EntityDict['message']['Schema'], context);
if (smsNotification) {
notificationDatas.push(smsNotification);
}
}
const messageSystemData: EntityDict['messageSystem']['CreateSingle']['data'] = {
id: await generateNewIdAsync(),
messageId: message.id,

View File

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

View File

@ -2,35 +2,42 @@ import { BRC } from './RuntimeCxt';
import { EntityDict } from '../general-app-domain';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
type WechatPublicTemplateMsgKeyword = 'keyword1' | 'keyword2' | 'keyword3'| 'keyword4'| 'keyword5'| 'keyword6'| 'keyword7';
export type Channel = 'wechatPublic' | 'jPush' | 'jim' | 'mp' | 'sms';
type WechatPublicTemplateMsgKeyword = 'keyword1' | 'keyword2' | 'keyword3' | 'keyword4' | 'keyword5' | 'keyword6' | 'keyword7';
export type Channel = 'wechatPublic' | 'jPush' | 'jim' | 'wechatMp' | 'sms';
export type Weight = 'high' | 'medium' | 'low';
export interface MessageNotificationConverter<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>> {
type: string;
toWechatMp?: (entity: ED['message']['OpSchema']['entity'], entityId: string, context: Cxt) => Promise<{
[K : string]: {
value: string;
}
}>;
toWechatPublic?: (entity: ED['message']['OpSchema']['entity'], entityId: string, context: Cxt) => Promise<{
first?: {
value: string;
color?: string;
};
remark?: {
value: string;
color?: string;
};
} & {
[K in WechatPublicTemplateMsgKeyword]?: {
value: string;
color?: string;
};
}>;
toWechatMp?: (entity: ED['message']['OpSchema']['entity'], entityId: string,
applications: EntityDict['application']['Schema'][], application: EntityDict['application']['Schema'],
context: Cxt) => Promise<{
[K: string]: {
value: string;
}
} | undefined>;
toWechatPublic?: (entity: ED['message']['OpSchema']['entity'], entityId: string,
applications: EntityDict['application']['Schema'][], application: EntityDict['application']['Schema'],
context: Cxt) => Promise<{
data: {
first?: {
value: string;
color?: string;
};
remark?: {
value: string;
color?: string;
};
} & {
[K in WechatPublicTemplateMsgKeyword]?: {
value: string;
color?: string;
};
},
wechatMpAppId?: string,
} | undefined>;
toSms?: (entity: ED['message']['OpSchema']['entity'], entityId: string, context: Cxt) => Promise<{
signName?: string; // 可能的签名
params?: Record<string, string>; // 模板参数,需要替换的参数名和 value 的键值对
paramsArray?: Array<string>; // 数组形式的模板参数,按序传入服务商接口
}>;
} | undefined>;
}

View File

@ -9,6 +9,7 @@ import {
AliCloudConfig,
AliSmsConfig,
} from '../types/Config';
import { OakExternalException } from 'oak-domain/lib/types';
export async function sendSms<
ED extends EntityDict,
@ -18,11 +19,7 @@ export async function sendSms<
origin: 'ali' | 'tencent';
templateName: string;
mobile: string;
templateParamSet?: Record<string, string>;
templateParamSetFn?: (
origin: 'ali' | 'tencent',
templateParamSet?: Record<string, string>
) => string[] | Record<string, string> | undefined;
templateParamSet?: Record<string, string> | string[];
},
context: Cxt
) {
@ -31,7 +28,6 @@ export async function sendSms<
templateName,
mobile,
templateParamSet,
templateParamSetFn,
} = options;
const application = context.getApplication();
@ -50,33 +46,36 @@ export async function sendSms<
) {
assert(false, `${origin}短信未配置`);
}
const templateParamSet2 = templateParamSetFn
? templateParamSetFn(origin, templateParamSet)
: templateParamSet;
if (origin === 'tencent') {
const accountConfig = accountConfigs[0] as TencentCloudConfig;
const smsConfig = smsConfigs[0] as TencentSmsConfig;
const template = smsConfig.templates?.[templateName];
const SmsSdkInstance = SmsSdk.getInstance(
origin,
accountConfig.secretId,
accountConfig.secretKey,
accountConfig.region,
accountConfig.endpoint
) as TencentSmsInstance;
const data = await SmsSdkInstance.sendSms({
PhoneNumberSet: [mobile],
SmsSdkAppId: smsConfig.smsSdkAppId,
SignName: template.signName || smsConfig.defaultSignName,
TemplateId: template.code,
TemplateParamSet: templateParamSet2 as string[],
});
const sendStatus = data.SendStatusSet[0];
if (sendStatus.Code === 'Ok') {
return true;
for (const smsConfig of smsConfigs) {
const smsConfig = smsConfigs[0] as TencentSmsConfig;
const accountConfig = (accountConfigs as TencentCloudConfig[]).find(
ele => ele.secretId === smsConfig.secretId
)!;
const template = smsConfig.templates?.[templateName];
const SmsSdkInstance = SmsSdk.getInstance(
origin,
accountConfig.secretId,
accountConfig.secretKey,
accountConfig.region,
accountConfig.endpoint
) as TencentSmsInstance;
const data = await SmsSdkInstance.sendSms({
PhoneNumberSet: [mobile],
SmsSdkAppId: smsConfig.smsSdkAppId,
SignName: template.signName || smsConfig.defaultSignName,
TemplateId: template.code,
TemplateParamSet: templateParamSet as string[],
});
const sendStatus = data.SendStatusSet[0];
if (sendStatus.Code === 'Ok') {
return true;
}
console.warn(`通过微信云发送sms失败电话是${mobile}模板Id是${template.code}`, sendStatus);
}
return false;
} else {
throw new Error('未实现');
}
throw new OakExternalException('尝试发送sms短信失败');
}