extraFile-gallery在创建filter的时候有bug

This commit is contained in:
Xu Chang 2023-01-30 17:10:47 +08:00
parent bb911c759a
commit d9a73291ac
30 changed files with 1145 additions and 174 deletions

View File

@ -21,7 +21,7 @@ function createWechatQrCode(options, context) {
return tslib_1.__generator(this, function (_f) {
switch (_f.label) {
case 0:
entity = options.entity, entityId = options.entityId, tag = options.tag, _a = options.lifetimeLength, lifetimeLength = _a === void 0 ? 300 * 10000 : _a, _b = options.permanent, permanent = _b === void 0 ? false : _b, props = options.props;
entity = options.entity, entityId = options.entityId, tag = options.tag, _a = options.lifetimeLength, lifetimeLength = _a === void 0 ? 2592000 * 1000 : _a, _b = options.permanent, permanent = _b === void 0 ? false : _b, props = options.props;
applicationId = context.getApplicationId();
(0, assert_1.assert)(applicationId);
return [4 /*yield*/, context.select('system', {
@ -109,6 +109,10 @@ function createWechatQrCode(options, context) {
if (!appId || !appType) {
throw new Error('无法生成二维码找不到此system下的服务号或者小程序信息');
}
if (lifetimeLength > 2592000 * 1000 && !permanent) {
// 二维码的过期应当由上层对象来自主控制以保持一致性
throw new Error('二维码过期时间太长不能长于30天');
}
data = {
id: id,
type: appType,
@ -127,7 +131,6 @@ function createWechatQrCode(options, context) {
application = applications.find(function (ele) { return ele.id === data.applicationId; });
(0, assert_1.assert)(application);
applicationType = application.type, config = application.config;
console.log(process.env.OAK_PLATFORM, process.env.NODE_ENV);
_e = type;
switch (_e) {
case 'wechatMpWxaCode': return [3 /*break*/, 2];

View File

@ -45,6 +45,22 @@ exports.default = OakComponent({
wechatMp: {
externalClasses: ['oak-class', 'oak-item-class'],
},
filters: [
{
filter: function (_a) {
var props = _a.props;
var tag1 = props.tag1, tag2 = props.tag2;
var filter1 = {};
if (tag1) {
Object.assign(filter1, { tag1: tag1 });
}
if (tag2) {
Object.assign(filter1, { tag2: tag2 });
}
return filter1;
}
}
],
properties: {
removeLater: Boolean,
autoUpload: {

View File

@ -12,6 +12,7 @@ export declare class BackendRuntimeContext<ED extends EntityDict> extends AsyncC
protected amIReallyRoot?: boolean;
protected rootMode?: boolean;
setTokenValue(tokenValue: string): Promise<void>;
setApplication(appId: string): Promise<void>;
protected initialize(data?: SerializedData): Promise<void>;
getApplicationId(): ED["application"]["Schema"]["id"] | undefined;
getSystemId(): ED["application"]["Schema"]["systemId"] | undefined;

View File

@ -87,78 +87,92 @@ var BackendRuntimeContext = /** @class */ (function (_super) {
});
});
};
BackendRuntimeContext.prototype.setApplication = function (appId) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var result;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.select('application', {
data: {
id: 1,
name: 1,
config: 1,
type: 1,
systemId: 1,
style: 1,
system: {
id: 1,
name: 1,
config: 1,
platformId: 1,
style: 1,
folder: 1,
super: 1,
platform: {
id: 1,
config: 1,
style: 1,
},
},
},
filter: {
id: appId,
},
}, {
dontCollect: true,
blockTrigger: true,
})];
case 1:
result = _a.sent();
(0, assert_1.default)(result.length > 0, "\u6784\u5EFABackendRuntimeContext\u5BF9\u5E94appId\u300C".concat(appId, "\u300D\u627E\u4E0D\u5230application"));
this.application = result[0];
return [2 /*return*/];
}
});
});
};
BackendRuntimeContext.prototype.initialize = function (data) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var appId, tokenValue, result, err_1;
var appId, tokenValue, promises, err_1;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!data) return [3 /*break*/, 11];
if (!data) return [3 /*break*/, 9];
return [4 /*yield*/, this.begin()];
case 1:
_a.sent();
_a.label = 2;
case 2:
_a.trys.push([2, 8, , 10]);
_a.trys.push([2, 6, , 8]);
appId = data.a, tokenValue = data.t;
if (!appId) return [3 /*break*/, 4];
return [4 /*yield*/, this.select('application', {
data: {
id: 1,
name: 1,
config: 1,
type: 1,
systemId: 1,
style: 1,
system: {
id: 1,
name: 1,
config: 1,
platformId: 1,
style: 1,
folder: 1,
super: 1,
platform: {
id: 1,
config: 1,
style: 1,
},
},
},
filter: {
id: appId,
},
}, {
dontCollect: true,
blockTrigger: true,
})];
promises = [];
if (appId) {
promises.push(this.setApplication(appId));
}
if (tokenValue) {
promises.push(this.setTokenValue(tokenValue));
}
if (!(promises.length > 0)) return [3 /*break*/, 4];
return [4 /*yield*/, Promise.all(promises)];
case 3:
result = _a.sent();
(0, assert_1.default)(result.length > 0, "\u6784\u5EFABackendRuntimeContext\u5BF9\u5E94appId\u300C".concat(appId, "\u300D\u627E\u4E0D\u5230application"));
this.application = result[0];
_a.sent();
_a.label = 4;
case 4:
if (!tokenValue) return [3 /*break*/, 6];
return [4 /*yield*/, this.setTokenValue(tokenValue)];
case 4: return [4 /*yield*/, this.commit()];
case 5:
_a.sent();
_a.label = 6;
case 6: return [4 /*yield*/, this.commit()];
case 7:
_a.sent();
return [3 /*break*/, 10];
case 8:
return [3 /*break*/, 8];
case 6:
err_1 = _a.sent();
return [4 /*yield*/, this.rollback()];
case 9:
case 7:
_a.sent();
throw err_1;
case 10: return [3 /*break*/, 12];
case 11:
case 8: return [3 /*break*/, 10];
case 9:
// 否则是后台模式默认用root
this.rootMode = true;
_a.label = 12;
case 12: return [2 /*return*/];
_a.label = 10;
case 10: return [2 /*return*/];
}
});
});

View File

@ -1,4 +1,6 @@
import { registerWeChatPublicEventCallback } from './wechat';
declare const _default: {
[x: string]: import("oak-domain/lib/types").Endpoint<import("../general-app-domain").EntityDict, import("../types/RuntimeCxt").BRC>;
};
export default _default;
export { registerWeChatPublicEventCallback, };

View File

@ -1,5 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerWeChatPublicEventCallback = void 0;
var tslib_1 = require("tslib");
var wechat_1 = tslib_1.__importDefault(require("./wechat"));
var wechat_1 = tslib_1.__importStar(require("./wechat"));
Object.defineProperty(exports, "registerWeChatPublicEventCallback", { enumerable: true, get: function () { return wechat_1.registerWeChatPublicEventCallback; } });
exports.default = tslib_1.__assign({}, wechat_1.default);

View File

@ -1,5 +1,7 @@
import { Endpoint } from 'oak-domain/lib/types/Endpoint';
import { EntityDict } from '../general-app-domain';
import { BRC } from '../types/RuntimeCxt';
import { WechatPublicEventData } from 'oak-external-sdk';
export declare function registerWeChatPublicEventCallback(appId: string, callback: (data: WechatPublicEventData, context: BRC) => void): void;
declare const endpoints: Record<string, Endpoint<EntityDict, BRC>>;
export default endpoints;

View File

@ -1,8 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerWeChatPublicEventCallback = void 0;
var tslib_1 = require("tslib");
var url_1 = tslib_1.__importDefault(require("url"));
var assert_1 = tslib_1.__importDefault(require("assert"));
var node_url_1 = require("node:url");
var node_path_1 = tslib_1.__importDefault(require("node:path"));
var sha1_1 = tslib_1.__importDefault(require("sha1"));
var xml2json_1 = tslib_1.__importDefault(require("xml2json"));
var oak_external_sdk_1 = require("oak-external-sdk");
var uuid_1 = require("oak-domain/lib/utils/uuid");
var domain_1 = require("../utils/domain");
function assertFromWeChat(query, config) {
var _a;
var signature = query.signature, nonce = query.nonce, timestamp = query.timestamp;
@ -15,58 +22,408 @@ function assertFromWeChat(query, config) {
var sha1Sign = (0, sha1_1.default)(sign);
return signature === sha1Sign;
}
var endpoints = {
wechatApi: {
name: '微信公众号回调接口',
method: 'post',
params: ['appId'],
fn: function (context, params, body, req, headers) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var appId;
return tslib_1.__generator(this, function (_a) {
appId = params.appId;
throw new Error('not implemented yet');
});
}); },
},
wechatVerify: {
name: '微信公众号验证接口',
method: 'get',
// params: ['applicationId'],
fn: function (context, params, body, req, headers) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var query, applicationId, _a, application, isWeChat;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
query = url_1.default.parse(req.url, true, false).query;
applicationId = query.applicationId;
if (!applicationId) {
throw new Error('applicationId参数不存在');
var CALLBACK = {};
function registerWeChatPublicEventCallback(appId, callback) {
(0, assert_1.default)(!CALLBACK.hasOwnProperty(appId));
CALLBACK[appId] = callback;
}
exports.registerWeChatPublicEventCallback = registerWeChatPublicEventCallback;
/**
* 用户取关事件注意要容wechatUser不存在的情况
* @param openId
* @param context
* @returns
*/
function setUserUnsubscribed(openId, context) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var list, weChatUser;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, context.select('wechatUser', {
data: {
id: 1,
subscribed: 1,
subscribedAt: 1,
},
filter: {
applicationId: context.getApplicationId(),
openId: openId,
},
indexFrom: 0,
count: 10,
}, { dontCollect: true })];
case 1:
list = _a.sent();
if (!(list && list.length > 0)) return [3 /*break*/, 4];
(0, assert_1.default)(list.length === 1);
weChatUser = list[0];
if (!weChatUser.subscribed) return [3 /*break*/, 3];
return [4 /*yield*/, context.operate('wechatUser', {
action: 'update',
data: {
subscribed: false,
unsubscribeAt: Date.now(),
},
id: weChatUser.id,
}, { dontCollect: true, dontCreateOper: true })];
case 2:
_a.sent();
_a.label = 3;
case 3: return [3 /*break*/, 6];
case 4: return [4 /*yield*/, context.operate('wechatUser', {
action: 'create',
data: {
subscribed: false,
applicationId: context.getApplicationId(),
openId: openId,
},
}, { dontCollect: true, dontCreateOper: true })];
case 5:
_a.sent();
_a.label = 6;
case 6: return [2 /*return*/];
}
});
});
}
function setUserSubscribed(openId, eventKey, context) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var applicationId, list, now, data, doUpdate, sceneStr, wcqId, _a, weChatQrCode, entity, entityId, _b, _c, userEntityGrant, _d, id, granter, expired, entity2, _e, domain, url, name_1, application, _f, type, config, _g, appId, appSecret, wechatInstance;
var _this = this;
return tslib_1.__generator(this, function (_h) {
switch (_h.label) {
case 0:
applicationId = context.getApplicationId();
return [4 /*yield*/, context.select('wechatUser', {
data: {
id: 1,
subscribed: 1,
subscribedAt: 1,
},
filter: {
applicationId: applicationId,
openId: openId,
},
indexFrom: 0,
count: 1,
}, { dontCollect: true })];
case 1:
list = _h.sent();
now = Date.now();
data = {
activeAt: now,
};
doUpdate = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var wechatUser;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(list && list.length > 0)) return [3 /*break*/, 2];
(0, assert_1.default)(list.length === 1);
wechatUser = list[0];
if (!wechatUser.subscribed) {
Object.assign(data, {
subscribed: true,
subscribeAt: now,
});
}
return [4 /*yield*/, context.operate('wechatUser', {
action: 'update',
data: data,
filter: {
id: wechatUser.id,
},
}, { dontCollect: true, dontCreateOper: true })];
case 1: return [2 /*return*/, _a.sent()];
case 2:
Object.assign(data, {
subscribed: true,
subscribeAt: now,
applicationId: applicationId,
openId: openId,
});
return [4 /*yield*/, context.operate('wechatUser', {
action: 'create',
data: data,
}, { dontCollect: true })];
case 3:
// 这里试着直接把user也创建出来by Xc 20190720
/**
* 这里不能创建user否则会出现一个weChatUser有openId和userId却没有unionId
* 当同一个user先从小程序登录再从公众号登录时就会生成两个user
*/
/* return warden.insertEntity(tables.user, {
state: UserState.normal,
activeAt: Date.now(),
}, txn).then(
(user) => {
assign(data, { userId: user.id });
return warden.insertEntity(tables.weChatUser, data, txn);
}
);*/
return [2 /*return*/, _a.sent()];
}
});
}); };
if (!eventKey) return [3 /*break*/, 9];
sceneStr = void 0;
if (eventKey.startsWith('qrscene_')) {
sceneStr = eventKey.slice(eventKey.indexOf('qrscene_') + 8);
}
else {
sceneStr = eventKey;
}
wcqId = (0, uuid_1.expandUuidTo36Bytes)(sceneStr);
return [4 /*yield*/, context.select('wechatQrCode', {
data: {
id: 1,
entity: 1,
entityId: 1,
},
filter: {
id: wcqId,
},
indexFrom: 0,
count: 10,
}, { dontCollect: true })];
case 2:
_a = tslib_1.__read.apply(void 0, [_h.sent(), 1]), weChatQrCode = _a[0];
if (!weChatQrCode) return [3 /*break*/, 8];
entity = weChatQrCode.entity, entityId = weChatQrCode.entityId;
_b = entity;
switch (_b) {
case 'user': return [3 /*break*/, 3];
case 'userEntityGrant': return [3 /*break*/, 4];
}
return [3 /*break*/, 7];
case 3:
{
// 裂变获得的用户
if (list[0] && !list[0].userId) {
Object.assign(data, { userId: entityId });
}
return [4 /*yield*/, context.select('application', {
data: {
id: 1,
config: 1,
},
filter: {
id: applicationId,
},
}, {})];
case 1:
_a = tslib_1.__read.apply(void 0, [_b.sent(), 1]), application = _a[0];
if (!application) {
throw new Error("\u672A\u627E\u5230".concat(applicationId, "\u5BF9\u5E94\u7684app"));
return [3 /*break*/, 7];
}
_h.label = 4;
case 4: return [4 /*yield*/, context.select('userEntityGrant', {
data: {
id: 1,
},
filter: {
id: entityId,
}
isWeChat = assertFromWeChat(query, application.config);
if (isWeChat) {
return [2 /*return*/, query.echostr];
}
else {
throw new Error('Verify Failed');
}
return [2 /*return*/];
}
});
}); },
}, { dontCollect: true })];
case 5:
_c = tslib_1.__read.apply(void 0, [_h.sent(), 1]), userEntityGrant = _c[0];
_d = userEntityGrant, id = _d.id, granter = _d.granter, expired = _d.expired, entity2 = _d.entity;
return [4 /*yield*/, context.select('domain', {
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
systemId: {
$in: {
entity: 'application',
data: {
systemId: 1,
},
filter: {
id: applicationId,
}
}
}
}
}, { dontCollect: true })];
case 6:
_e = tslib_1.__read.apply(void 0, [_h.sent(), 1]), domain = _e[0];
(0, assert_1.default)(domain, "\u5904\u7406userEntityGrant\u65F6\uFF0C\u627E\u4E0D\u5230\u5BF9\u5E94\u7684domain\uFF0CapplicationId\u662F\u300C".concat(applicationId, "\u300D"));
url = (0, domain_1.composeDomainUrl)(domain, 'wechatQrCode/scan', {
scene: sceneStr,
});
(0, assert_1.default)(!expired); // 如果生成的wechatQrCode没过期userEntityGrant就不可能过期。
name_1 = granter.name || granter.nickname;
application = context.getApplication();
_f = application, type = _f.type, config = _f.config;
(0, assert_1.default)(type === 'wechatPublic');
_g = config, appId = _g.appId, appSecret = _g.appSecret;
wechatInstance = oak_external_sdk_1.WechatSDK.getInstance(appId, appSecret, 'wechatPublic');
wechatInstance.sendServeMessage({
openId: openId,
type: 'news',
url: url,
title: "".concat(name_1, "\u7ED9\u60A8\u521B\u5EFA\u4E86\u4E00\u4E2A\u6388\u6743"),
description: '请接受',
picurl: 'http://img95.699pic.com/element/40018/2473.png_860.png',
});
_h.label = 7;
case 7: return [3 /*break*/, 9];
case 8:
console.warn("\u7EBF\u4E0A\u6709\u626B\u63CF\u4E8C\u7EF4\u7801\u573A\u666F\u503C\uFF0C\u4F46\u627E\u4E0D\u5230\u5BF9\u5E94\u7684qrCode\uFF0CeventKey\u662F".concat(eventKey));
_h.label = 9;
case 9: return [4 /*yield*/, doUpdate()];
case 10:
_h.sent();
return [2 /*return*/];
}
});
});
}
function onWeChatPublicEvent(data, context) {
var ToUserName = data.ToUserName, FromUserName = data.FromUserName, CreateTime = data.CreateTime, MsgType = data.MsgType, Event = data.Event, Content = data.Content, EventKey = data.EventKey;
var appId = context.getApplicationId();
var evt;
// 如果有应用注入的事件回调则处理之,不依赖其返回
if (CALLBACK[appId]) {
CALLBACK[appId](data, context);
}
if (Event) {
var event_1 = Event.toLowerCase();
switch (event_1) {
case 'subscribe':
setUserSubscribed(FromUserName, EventKey, context);
evt = "\u7528\u6237".concat(FromUserName, "\u5173\u6CE8\u516C\u4F17\u53F7");
break;
case 'scan':
setUserSubscribed(FromUserName, EventKey, context);
evt = "\u7528\u6237".concat(FromUserName, "\u518D\u6B21\u626B\u63CF\u5E26").concat(EventKey, "\u952E\u503C\u7684\u4E8C\u7EF4\u7801");
break;
case 'unsubscribe': {
setUserUnsubscribed(FromUserName, context);
evt = "\u7528\u6237".concat(FromUserName, "\u53D6\u5173");
break;
}
case 'location': {
evt = "\u7528\u6237".concat(FromUserName, "\u4E0A\u4F20\u4E86\u5730\u7406\u4F4D\u7F6E\u4FE1\u606F");
break;
}
case 'click': {
evt = "\u7528\u6237".concat(FromUserName, "\u70B9\u51FB\u83DC\u5355\u3010").concat(EventKey, "\u3011");
break;
}
case 'view': {
evt = "\u7528\u6237".concat(FromUserName, "\u70B9\u51FB\u83DC\u5355\u8DF3\u8F6C\u94FE\u63A5\u3010").concat(EventKey, "\u3011");
break;
}
case 'templatesendjobfinish': {
// 模板消息发送完成去更新对应的messageSent对象
// 这个在线上测试没法通过返回的msgId不符合不知道为什么
var msgId = data.MsgID, status_1 = data.Status, openId = data.FromUserName;
evt = "\u5E94\u7528".concat(appId, "\u7684\u7528\u6237").concat(FromUserName, "\u53D1\u6765\u4E86").concat(Event, "\u4E8B\u4EF6\uFF0C\u5185\u5BB9\u662F").concat(JSON.stringify(data));
break;
}
default: {
evt = "\u5E94\u7528".concat(appId, "\u7684\u7528\u6237").concat(FromUserName, "\u53D1\u6765\u4E86").concat(Event, "\u4E8B\u4EF6\uFF0C\u5185\u5BB9\u662F").concat(JSON.stringify(data));
break;
}
}
if (process.env.NODE_ENV === 'development') {
console.log(evt);
}
return {
content: '',
contentType: 'application/text',
};
}
(0, assert_1.default)(MsgType);
var content = '<xml>' +
"<ToUserName>".concat(FromUserName, "</ToUserName>") +
"<FromUserName>".concat(ToUserName, "</FromUserName>") +
"<CreateTime>".concat(CreateTime, "</CreateTime>") +
'<MsgType>transfer_customer_service</MsgType>' +
'</xml>';
switch (MsgType) {
case 'text':
case 'link': {
evt = "\u63A5\u6536\u5230\u6765\u81EA\u7528\u6237\u7684\u6587\u5B57\u6D88\u606F\uFF1A".concat(Content);
break;
}
case 'image': {
evt = "\u63A5\u6536\u5230\u6765\u81EA\u7528\u6237\u7684\u56FE\u7247\u6D88\u606F\uFF1A".concat(Content);
break;
}
default: {
evt = "\u63A5\u6536\u5230\u6765\u81EA\u7528\u6237\u7684".concat(MsgType, "\u578B\u6D88\u606F");
break;
}
}
if (process.env.NODE_ENV === 'development') {
console.log(evt);
}
return {
content: content,
contentType: 'application/xml',
};
}
var endpoints = {
wechatPublicEvent: [{
name: '微信公众号回调接口',
method: 'post',
params: ['appId'],
fn: function (context, params, headers, req, body) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var appId, data, _a, content, contentType;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
appId = params.appId;
if (!appId) {
throw new Error('applicationId参数不存在');
}
return [4 /*yield*/, context.setApplication(appId)];
case 1:
_b.sent();
data = xml2json_1.default.toJson(body, { object: true });
_a = onWeChatPublicEvent(data, context), content = _a.content, contentType = _a.contentType;
return [2 /*return*/, content];
}
});
}); },
}, {
name: '微信公众号验证接口',
method: 'get',
params: ['appId'],
fn: function (context, params, body, req, headers) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var searchParams, appId, _a, application, signature, timestamp, nonce, isWeChat, echostr;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
searchParams = new node_url_1.URL(node_path_1.default.join('http://', req.headers.host, req.url)).searchParams;
appId = params.appId;
if (!appId) {
throw new Error('applicationId参数不存在');
}
return [4 /*yield*/, context.select('application', {
data: {
id: 1,
config: 1,
},
filter: {
id: appId,
},
}, {})];
case 1:
_a = tslib_1.__read.apply(void 0, [_b.sent(), 1]), application = _a[0];
if (!application) {
throw new Error("\u672A\u627E\u5230".concat(appId, "\u5BF9\u5E94\u7684app"));
}
signature = searchParams.get('signature');
timestamp = searchParams.get('timestamp');
nonce = searchParams.get('nonce');
isWeChat = assertFromWeChat({ signature: signature, timestamp: timestamp, nonce: nonce }, application.config);
if (isWeChat) {
echostr = searchParams.get('echostr');
return [2 /*return*/, echostr];
}
else {
throw new Error('Verify Failed');
}
return [2 /*return*/];
}
});
}); },
}],
};
exports.default = endpoints;

View File

@ -1,5 +1,6 @@
import { String, Text, Image, Datetime } from 'oak-domain/lib/types/DataType';
import { Schema as ExtraFile } from './ExtraFile';
import { Schema as WechatQrCode } from './WechatQrCode';
import { EntityShape } from 'oak-domain/lib/types/Entity';
export interface Schema extends EntityShape {
name?: String<16>;
@ -13,4 +14,5 @@ export interface Schema extends EntityShape {
idNumber?: String<32>;
ref?: Schema;
files: Array<ExtraFile>;
codes: Array<WechatQrCode>;
}

View File

@ -60,6 +60,7 @@ var locale = {
files: '相关文件',
userState: '用户状态',
idState: '身份验证状态',
codes: '微信分享二维码',
},
action: {
activate: '激活',

View File

@ -12,7 +12,7 @@ export interface Schema extends EntityShape {
remark?: Text;
granter: User;
grantee?: User;
files: Array<WechatQrCode>;
codes: Array<WechatQrCode>;
expiresAt?: Datetime;
expired?: Boolean;
redirectTo?: Object;

View File

@ -36,7 +36,7 @@ var locale = {
remark: '备注',
grantee: '领取人',
granter: '授权人',
files: '微信码',
codes: '微信码',
expired: '是否过期',
expiresAt: '过期时间',
redirectTo: '重定向页面',

3
lib/index.d.ts vendored
View File

@ -5,7 +5,8 @@ import watchers from './watchers';
import data from "./data";
export * from './exceptionHandlers';
import { registerMessagePropsConverter } from './triggers/message';
export { checkers, triggers, watchers, data, aspectDict, registerMessagePropsConverter, };
import { registerWeChatPublicEventCallback } from './endpoints';
export { checkers, triggers, watchers, data, aspectDict, registerMessagePropsConverter, registerWeChatPublicEventCallback, };
export * from './types/Exception';
export { composeFileUrl, decomposeFileUrl } from './utils/extraFile';
export * from './data/DEV-CONFIG';

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decomposeFileUrl = exports.composeFileUrl = exports.registerMessagePropsConverter = exports.aspectDict = exports.data = exports.watchers = exports.triggers = exports.checkers = void 0;
exports.decomposeFileUrl = exports.composeFileUrl = exports.registerWeChatPublicEventCallback = exports.registerMessagePropsConverter = exports.aspectDict = exports.data = exports.watchers = exports.triggers = exports.checkers = void 0;
var tslib_1 = require("tslib");
var aspects_1 = require("./aspects");
Object.defineProperty(exports, "aspectDict", { enumerable: true, get: function () { return aspects_1.aspectDict; } });
@ -15,6 +15,8 @@ exports.data = data_1.default;
tslib_1.__exportStar(require("./exceptionHandlers"), exports);
var message_1 = require("./triggers/message");
Object.defineProperty(exports, "registerMessagePropsConverter", { enumerable: true, get: function () { return message_1.registerMessagePropsConverter; } });
var endpoints_1 = require("./endpoints");
Object.defineProperty(exports, "registerWeChatPublicEventCallback", { enumerable: true, get: function () { return endpoints_1.registerWeChatPublicEventCallback; } });
tslib_1.__exportStar(require("./types/Exception"), exports);
var extraFile_1 = require("./utils/extraFile");
Object.defineProperty(exports, "composeFileUrl", { enumerable: true, get: function () { return extraFile_1.composeFileUrl; } });

View File

@ -15,6 +15,9 @@ exports.default = OakComponent({
props: 1,
},
isList: true,
properties: {
scene: String,
},
filters: [
{
filter: function (_a) {

View File

@ -32,6 +32,11 @@ var triggers = [
granterId: userId,
expired: false,
});
if (!userEntityGrantData.expiresAt) {
Object.assign(userEntityGrantData, {
expiresAt: Date.now() + 300 * 1000,
});
}
// 为之创建微信体系下的一个weChatQrCode
return [4 /*yield*/, (0, wechatQrCode_1.createWechatQrCode)({
entity: 'userEntityGrant',
@ -42,6 +47,7 @@ var triggers = [
oakId: id,
},
},
lifetimeLength: userEntityGrantData.expiresAt - Date.now() + 300 * 1000, // wechatQrCode的过期由trigger负责可以放长一些
}, context)];
case 1:
// 为之创建微信体系下的一个weChatQrCode
@ -233,6 +239,55 @@ var triggers = [
});
});
}
},
{
name: '当userEntityGrant过期时使其相关的wechatQrCode也过期',
entity: 'userEntityGrant',
action: 'update',
check: function (operation) {
var data = operation.data;
return !!(data.expired);
},
when: 'after',
fn: function (_a, context, params) {
var operation = _a.operation;
return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var filter, wechatQrCodes, ids;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
filter = operation.filter;
return [4 /*yield*/, context.select('wechatQrCode', {
data: {
id: 1,
expired: 1,
},
filter: {
userEntityGrant: filter,
expired: false,
},
}, { dontCollect: true })];
case 1:
wechatQrCodes = _b.sent();
ids = wechatQrCodes.map(function (ele) { return ele.id; });
return [4 /*yield*/, context.operate('wechatQrCode', {
action: 'update',
data: {
expired: true,
},
filter: {
id: {
$in: ids,
},
},
}, { dontCollect: true })];
case 2:
_b.sent();
return [2 /*return*/, ids.length];
}
});
});
}
}
];
exports.default = triggers;

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

@ -0,0 +1,2 @@
import { Schema as Domain } from '../general-app-domain/Domain/Schema';
export declare function composeDomainUrl(domain: Domain, url?: string, props?: Record<string, string>): string;

36
lib/utils/domain.js Normal file
View File

@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.composeDomainUrl = void 0;
var tslib_1 = require("tslib");
function composeDomainUrl(domain, url, props) {
var e_1, _a;
var port = domain.port, protocol = domain.protocol, apiPath = domain.apiPath, domainUrl = domain.url;
var Url = "".concat(protocol, "://").concat(domainUrl);
if (port) {
Url += ":".concat(port);
}
if (url) {
Url += "/".concat(url);
if (props) {
var k = Object.keys(props);
if (k.length > 0) {
Url += '?';
try {
for (var k_1 = tslib_1.__values(k), k_1_1 = k_1.next(); !k_1_1.done; k_1_1 = k_1.next()) {
var k2 = k_1_1.value;
Url += "".concat(k2, "=").concat(encodeURI(props[k2]));
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (k_1_1 && !k_1_1.done && (_a = k_1.return)) _a.call(k_1);
}
finally { if (e_1) throw e_1.error; }
}
}
}
}
return Url;
}
exports.composeDomainUrl = composeDomainUrl;

View File

@ -25,7 +25,8 @@
"react-scripts": "^5.0.1",
"sha1": "^1.1.1",
"tslib": "^2.4.0",
"uuid": "^8.3.2"
"uuid": "^8.3.2",
"xml2json": "^0.12.0"
},
"peerDependencies": {
"@ant-design/icons": "^4.7.0",
@ -54,6 +55,7 @@
"@types/react-dom": "^18.0.5",
"@types/sha1": "^1.1.3",
"@types/uuid": "^8.3.0",
"@types/xml2json": "^0.11.4",
"antd": "^5.0.0",
"antd-mobile": "^5.24.1",
"antd-mobile-icons": "^0.3.0",

View File

@ -29,7 +29,7 @@ export async function createWechatQrCode<ED extends EntityDict, T extends keyof
permanent?: boolean;
props: WechatQrCodeProps;
}, context: Cxt) {
const { entity, entityId, tag, lifetimeLength = 300 * 10000, permanent = false, props } = options;
const { entity, entityId, tag, lifetimeLength = 2592000 * 1000, permanent = false, props } = options;
const applicationId = context.getApplicationId();
assert(applicationId);
const [system] = await context.select(
@ -134,6 +134,10 @@ export async function createWechatQrCode<ED extends EntityDict, T extends keyof
if (!appId || !appType) {
throw new Error('无法生成二维码找不到此system下的服务号或者小程序信息');
}
if (lifetimeLength > 2592000 * 1000 && !permanent) {
// 二维码的过期应当由上层对象来自主控制以保持一致性
throw new Error('二维码过期时间太长不能长于30天');
}
const data: CreateWechatQrcodeData = {
id,
@ -161,7 +165,7 @@ export async function createWechatQrCode<ED extends EntityDict, T extends keyof
const { type: applicationType, config } = application;
console.log(process.env.OAK_PLATFORM, process.env.NODE_ENV);
// console.log(process.env.OAK_PLATFORM, process.env.NODE_ENV);
switch (type) {
case 'wechatMpWxaCode': {
assert(

View File

@ -45,6 +45,22 @@ export default OakComponent({
wechatMp: {
externalClasses: ['oak-class', 'oak-item-class'],
},
filters: [
{
filter({ props }) {
const { tag1, tag2 } = props;
const filter1 = {};
if (tag1) {
Object.assign(filter1, { tag1 });
}
if (tag2) {
Object.assign(filter1, { tag2 });
}
return filter1;
}
}
],
properties: {
removeLater: Boolean,
autoUpload: {

View File

@ -85,51 +85,59 @@ export class BackendRuntimeContext<ED extends EntityDict> extends AsyncContext<E
this.token = token;
}
async setApplication(appId: string) {
const result = await this.select(
'application',
{
data: {
id: 1,
name: 1,
config: 1,
type: 1,
systemId: 1,
style: 1,
system: {
id: 1,
name: 1,
config: 1,
platformId: 1,
style: 1,
folder: 1,
super: 1,
platform: {
id: 1,
config: 1,
style: 1,
},
},
},
filter: {
id: appId,
},
},
{
dontCollect: true,
blockTrigger: true,
}
);
assert(result.length > 0, `构建BackendRuntimeContext对应appId「${appId}」找不到application`);
this.application = result[0];
}
protected async initialize(data?: SerializedData) {
if (data) {
await this.begin();
try {
const { a: appId, t: tokenValue } = data;
const promises: Promise<void>[] = [];
if (appId) {
const result = await this.select(
'application',
{
data: {
id: 1,
name: 1,
config: 1,
type: 1,
systemId: 1,
style: 1,
system: {
id: 1,
name: 1,
config: 1,
platformId: 1,
style: 1,
folder: 1,
super: 1,
platform: {
id: 1,
config: 1,
style: 1,
},
},
},
filter: {
id: appId,
},
},
{
dontCollect: true,
blockTrigger: true,
}
);
assert(result.length > 0, `构建BackendRuntimeContext对应appId「${appId}」找不到application`);
this.application = result[0];
promises.push(this.setApplication(appId));
}
if (tokenValue) {
await this.setTokenValue(tokenValue);
promises.push(this.setTokenValue(tokenValue));
}
if (promises.length > 0) {
await Promise.all(promises);
}
await this.commit();
}

View File

@ -1,6 +1,10 @@
import wechat from './wechat';
import wechat, { registerWeChatPublicEventCallback } from './wechat';
export default {
...wechat,
};
};
export {
registerWeChatPublicEventCallback,
};

View File

@ -1,17 +1,25 @@
import assert from 'assert';
import { URL } from 'node:url';
import Path from 'node:path';
import sha1 from 'sha1';
import parser from 'xml2json';
import { Endpoint } from 'oak-domain/lib/types/Endpoint';
import {
WechatSDK,
WechatMpInstance,
WechatPublicInstance,
} from 'oak-external-sdk';
import { EntityDict } from '../general-app-domain';
import { BRC } from '../types/RuntimeCxt';
import URL from 'url';
import { WechatPublicConfig } from '../entities/Application';
import sha1 from 'sha1';
import { AccountBookOutlined } from '@ant-design/icons';
import { WechatPublicEventData } from 'oak-external-sdk';
import { expandUuidTo36Bytes } from 'oak-domain/lib/utils/uuid';
import { composeDomainUrl } from '../utils/domain';
type VerifyQuery = {
applicationId: string,
signature: string,
nonce: string,
timestamp: string,
echostr: string,
}
function assertFromWeChat(query: VerifyQuery, config: WechatPublicConfig) {
@ -26,24 +34,378 @@ function assertFromWeChat(query: VerifyQuery, config: WechatPublicConfig) {
return signature === sha1Sign;
}
const CALLBACK: Record<string, (data: WechatPublicEventData, context: BRC) => void> = {};
export function registerWeChatPublicEventCallback(appId: string, callback: (data: WechatPublicEventData, context: BRC) => void) {
assert(!CALLBACK.hasOwnProperty(appId));
CALLBACK[appId] = callback;
}
/**
* wechatUser不存在的情况
* @param openId
* @param context
* @returns
*/
async function setUserUnsubscribed(openId: string, context: BRC) {
const list = await context.select(
'wechatUser',
{
data: {
id: 1,
subscribed: 1,
subscribedAt: 1,
},
filter: {
applicationId: context.getApplicationId(),
openId,
},
indexFrom: 0,
count: 10,
},
{ dontCollect: true },
);
if (list && list.length > 0) {
assert(list.length === 1);
const weChatUser = list[0];
if (weChatUser.subscribed) {
await context.operate(
'wechatUser',
{
action: 'update',
data: {
subscribed: false,
unsubscribeAt: Date.now(),
},
id: weChatUser.id,
},
{ dontCollect: true, dontCreateOper: true },
);
}
} else {
await context.operate(
'wechatUser',
{
action: 'create',
data: {
subscribed: false,
applicationId: context.getApplicationId(),
openId,
},
},
{ dontCollect: true, dontCreateOper: true },
);
}
return;
}
async function setUserSubscribed(openId: string, eventKey: string, context: BRC) {
const applicationId = context.getApplicationId();
const list = await context.select(
'wechatUser',
{
data: {
id: 1,
subscribed: 1,
subscribedAt: 1,
},
filter: {
applicationId,
openId,
},
indexFrom: 0,
count: 1,
},
{ dontCollect: true },
);
const now = Date.now();
const data = {
activeAt: now,
};
const doUpdate = async () => {
if (list && list.length > 0) {
assert(list.length === 1);
const wechatUser = list[0];
if (!wechatUser.subscribed) {
Object.assign(data, {
subscribed: true,
subscribeAt: now,
});
}
return await context.operate('wechatUser', {
action: 'update',
data,
filter: {
id: wechatUser.id,
},
}, { dontCollect: true, dontCreateOper: true });
}
Object.assign(data, {
subscribed: true,
subscribeAt: now,
applicationId,
openId,
});
// 这里试着直接把user也创建出来by Xc 20190720
/**
* userweChatUser有openId和userIdunionId
* user先从小程序登录user
*/
/* return warden.insertEntity(tables.user, {
state: UserState.normal,
activeAt: Date.now(),
}, txn).then(
(user) => {
assign(data, { userId: user.id });
return warden.insertEntity(tables.weChatUser, data, txn);
}
);*/
return await context.operate('wechatUser', {
action: 'create',
data,
}, { dontCollect: true });
};
if (eventKey) {
// 如果带着场景值,需要查找对应的二维码,如果有公共逻辑在这里处理
let sceneStr;
if (eventKey.startsWith('qrscene_')) {
sceneStr = eventKey.slice(eventKey.indexOf('qrscene_') + 8);
}
else {
sceneStr = eventKey;
}
// sceneStr是id压缩后的字符串
const wcqId = expandUuidTo36Bytes(sceneStr);
const [weChatQrCode] = await context.select(
'wechatQrCode',
{
data: {
id: 1,
entity: 1,
entityId: 1,
},
filter: {
id: wcqId,
},
indexFrom: 0,
count: 10,
},
{ dontCollect: true },
);
if (weChatQrCode) {
const { entity, entityId } = weChatQrCode;
switch (entity) {
case 'user': {
// 裂变获得的用户
if (list[0] && !list[0].userId) {
Object.assign(data, { userId: entityId });
}
break;
}
case 'userEntityGrant': {
// 授权过来的用户,推送接受分享的客服消息给他
const [userEntityGrant] = await context.select(
'userEntityGrant',
{
data: {
id: 1,
},
filter: {
id: entityId,
}
},
{ dontCollect: true },
);
const { id, granter, expired, entity: entity2 } = userEntityGrant!;
const [domain] = await context.select('domain', {
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
systemId: {
$in: {
entity: 'application',
data: {
systemId: 1,
},
filter: {
id: applicationId,
}
}
}
}
}, { dontCollect: true });
assert(domain, `处理userEntityGrant时找不到对应的domainapplicationId是「${applicationId}`);
const url = composeDomainUrl(domain as EntityDict['domain']['Schema'], 'wechatQrCode/scan', {
scene: sceneStr,
});
assert(!expired); // 如果生成的wechatQrCode没过期userEntityGrant就不可能过期。
const name = granter!.name || granter!.nickname;
const application = context.getApplication();
const { type, config } = application!;
assert(type === 'wechatPublic');
const { appId, appSecret } = config as WechatPublicConfig;
const wechatInstance = WechatSDK.getInstance(
appId,
appSecret,
'wechatPublic'
) as WechatPublicInstance;
wechatInstance.sendServeMessage({
openId,
type: 'news',
url,
title: `${name}给您创建了一个授权`,
description: '请接受',
picurl: 'http://img95.699pic.com/element/40018/2473.png_860.png',
});
}
}
}
else {
console.warn(`线上有扫描二维码场景值但找不到对应的qrCodeeventKey是${eventKey}`);
}
}
await doUpdate();
return;
}
function onWeChatPublicEvent(data: WechatPublicEventData, context: BRC) {
const { ToUserName, FromUserName, CreateTime, MsgType, Event, Content, EventKey } = data;
const appId = context.getApplicationId()!;
let evt: string;
// 如果有应用注入的事件回调则处理之,不依赖其返回
if (CALLBACK[appId]) {
CALLBACK[appId](data, context);
}
if (Event) {
const event = Event.toLowerCase();
switch (event) {
case 'subscribe':
setUserSubscribed(FromUserName, EventKey, context);
evt = `用户${FromUserName}关注公众号`;
break;
case 'scan':
setUserSubscribed(FromUserName, EventKey, context);
evt = `用户${FromUserName}再次扫描带${EventKey}键值的二维码`;
break;
case 'unsubscribe': {
setUserUnsubscribed(FromUserName, context);
evt = `用户${FromUserName}取关`;
break;
}
case 'location': {
evt = `用户${FromUserName}上传了地理位置信息`;
break;
}
case 'click': {
evt = `用户${FromUserName}点击菜单【${EventKey}`;
break;
}
case 'view': {
evt = `用户${FromUserName}点击菜单跳转链接【${EventKey}`;
break;
}
case 'templatesendjobfinish': {
// 模板消息发送完成去更新对应的messageSent对象
// 这个在线上测试没法通过返回的msgId不符合不知道为什么
const { MsgID: msgId, Status: status, FromUserName: openId } = data;
evt = `应用${appId}的用户${FromUserName}发来了${Event}事件,内容是${JSON.stringify(data)}`;
break;
}
default: {
evt = `应用${appId}的用户${FromUserName}发来了${Event}事件,内容是${JSON.stringify(data)}`;
break;
}
}
if (process.env.NODE_ENV === 'development') {
console.log(evt);
}
return {
content: '',
contentType: 'application/text',
};
}
assert(MsgType);
const content = '<xml>' +
`<ToUserName>${FromUserName}</ToUserName>` +
`<FromUserName>${ToUserName}</FromUserName>` +
`<CreateTime>${CreateTime}</CreateTime>` +
'<MsgType>transfer_customer_service</MsgType>' +
'</xml>';
switch (MsgType) {
case 'text':
case 'link': {
evt = `接收到来自用户的文字消息:${Content}`;
break;
}
case 'image': {
evt = `接收到来自用户的图片消息:${Content}`;
break;
}
default: {
evt = `接收到来自用户的${MsgType}型消息`;
break;
}
}
if (process.env.NODE_ENV === 'development') {
console.log(evt);
}
return {
content,
contentType: 'application/xml',
};
}
const endpoints: Record<string, Endpoint<EntityDict, BRC>> = {
wechatApi: {
wechatPublicEvent: [{
name: '微信公众号回调接口',
method: 'post',
params: ['appId'],
fn: async (context, params, body, req, headers) => {
const { appId } = params as { appId: string };
throw new Error('not implemented yet');
fn: async (context, params, headers, req, body) => {
const { appId } = params;
if (!appId) {
throw new Error('applicationId参数不存在');
}
await context.setApplication(appId);
const data = parser.toJson(body, { object: true });
const { content, contentType } = onWeChatPublicEvent(data as any, context);
return content;
},
},
wechatVerify: {
}, {
name: '微信公众号验证接口',
method: 'get',
// params: ['applicationId'],
params: ['appId'],
fn: async (context, params, body, req, headers) => {
const query = URL.parse(req.url as string, true, false).query as VerifyQuery;
const { applicationId } = query;
if (!applicationId) {
const { searchParams } = new URL(Path.join('http://', req.headers.host!, req.url!));
const { appId } = params;
if (!appId) {
throw new Error('applicationId参数不存在');
}
const [application] = await context.select(
@ -54,23 +416,27 @@ const endpoints: Record<string, Endpoint<EntityDict, BRC>> = {
config: 1,
},
filter: {
id: applicationId,
id: appId,
},
},
{}
);
if (!application) {
throw new Error(`未找到${applicationId}对应的app`);
throw new Error(`未找到${appId}对应的app`);
}
const isWeChat = assertFromWeChat(query, application.config as WechatPublicConfig);
const signature = searchParams.get('signature')!;
const timestamp = searchParams.get('timestamp')!;
const nonce = searchParams.get('nonce')!;
const isWeChat = assertFromWeChat({ signature, timestamp, nonce }, application.config as WechatPublicConfig);
if (isWeChat) {
return query.echostr;
const echostr = searchParams.get('echostr')!;
return echostr;
}
else {
throw new Error('Verify Failed');
}
},
}
}],
};

View File

@ -3,7 +3,7 @@ import { ActionDef } from 'oak-domain/lib/types/Action';
import { LocaleDef } from 'oak-domain/lib/types/Locale';
import { Index } from 'oak-domain/lib/types/Storage';
import { Schema as ExtraFile } from './ExtraFile';
import { Schema as System } from './System';
import { Schema as WechatQrCode } from './WechatQrCode';
import { EntityShape } from 'oak-domain/lib/types/Entity';
export interface Schema extends EntityShape {
@ -18,6 +18,7 @@ export interface Schema extends EntityShape {
idNumber?: String<32>;
ref?: Schema;
files: Array<ExtraFile>;
codes: Array<WechatQrCode>;
};
type IdAction = 'verify' | 'accept' | 'reject';
@ -93,6 +94,7 @@ const locale: LocaleDef<Schema, Action, '', {
files: '相关文件',
userState: '用户状态',
idState: '身份验证状态',
codes: '微信分享二维码',
},
action: {
activate: '激活',

View File

@ -15,7 +15,7 @@ export interface Schema extends EntityShape {
remark?: Text;
granter: User;
grantee?: User;
files: Array<WechatQrCode>;
codes: Array<WechatQrCode>;
expiresAt?: Datetime;
expired?: Boolean;
redirectTo?: Object;
@ -67,7 +67,7 @@ const locale: LocaleDef<
remark: '备注',
grantee: '领取人',
granter: '授权人',
files: '微信码',
codes: '微信码',
expired: '是否过期',
expiresAt: '过期时间',
redirectTo: '重定向页面',

View File

@ -5,6 +5,7 @@ import watchers from './watchers';
import data from "./data";
export * from './exceptionHandlers';
import { registerMessagePropsConverter } from './triggers/message';
import { registerWeChatPublicEventCallback } from './endpoints';
export {
checkers,
@ -13,6 +14,7 @@ export {
data,
aspectDict,
registerMessagePropsConverter,
registerWeChatPublicEventCallback,
};
export * from './types/Exception';

View File

@ -16,6 +16,9 @@ export default OakComponent(
props: 1,
},
isList: true,
properties: {
scene: String,
},
filters: [
{
filter: ({ props }) => {

View File

@ -27,6 +27,11 @@ const triggers: Trigger<EntityDict, 'userEntityGrant', RuntimeCxt>[] = [
granterId: userId,
expired: false,
});
if (!userEntityGrantData.expiresAt) {
Object.assign(userEntityGrantData, {
expiresAt: Date.now() + 300 * 1000,
});
}
// 为之创建微信体系下的一个weChatQrCode
await createWechatQrCode(
{
@ -38,6 +43,7 @@ const triggers: Trigger<EntityDict, 'userEntityGrant', RuntimeCxt>[] = [
oakId: id,
},
},
lifetimeLength: (<number>userEntityGrantData.expiresAt)! - Date.now() + 300 * 1000, // wechatQrCode的过期由trigger负责可以放长一些
},
context as BackendRuntimeContext<EntityDict>
);
@ -216,6 +222,42 @@ const triggers: Trigger<EntityDict, 'userEntityGrant', RuntimeCxt>[] = [
return 1;
}
}
} as UpdateTrigger<EntityDict, 'userEntityGrant', RuntimeCxt>,
{
name: '当userEntityGrant过期时使其相关的wechatQrCode也过期',
entity: 'userEntityGrant',
action: 'update',
check: (operation) => {
const { data } = operation;
return !!(data.expired);
},
when: 'after',
fn: async ({ operation }, context, params) => {
const { filter } = operation;
const wechatQrCodes = await context.select('wechatQrCode', {
data: {
id: 1,
expired: 1,
},
filter: {
userEntityGrant: filter,
expired: false,
},
}, { dontCollect: true });
const ids = wechatQrCodes.map(ele => ele.id);
await context.operate('wechatQrCode', {
action: 'update',
data: {
expired: true,
},
filter: {
id: {
$in: ids,
},
},
}, { dontCollect: true });
return ids.length;
}
} as UpdateTrigger<EntityDict, 'userEntityGrant', RuntimeCxt>
];
export default triggers;

23
src/utils/domain.ts Normal file
View File

@ -0,0 +1,23 @@
import { Schema as Domain } from '../general-app-domain/Domain/Schema';
export function composeDomainUrl(domain: Domain, url?: string, props?: Record<string, string>) {
const { port, protocol, apiPath, url: domainUrl } = domain;
let Url = `${protocol}://${domainUrl}`;
if (port) {
Url += `:${port}`;
}
if (url) {
Url += `/${url}`;
if (props) {
const k = Object.keys(props);
if (k.length > 0) {
Url += '?';
for (const k2 of k) {
Url += `${k2}=${encodeURI(props[k2])}`;
}
}
}
}
return Url;
}