465 lines
18 KiB
JavaScript
465 lines
18 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.BackendRuntimeContext = void 0;
|
||
const assert_1 = require("oak-domain/lib/utils/assert");
|
||
const Exception_1 = require("../types/Exception");
|
||
const Exception_2 = require("oak-domain/lib/types/Exception");
|
||
const constants_1 = require("../constants");
|
||
const uuid_1 = require("oak-domain/lib/utils/uuid");
|
||
const types_1 = require("oak-domain/lib/types");
|
||
const Projection_1 = require("../types/Projection");
|
||
const wechatQrCode_1 = require("../aspects/wechatQrCode");
|
||
const BackendRuntimeContext_1 = require("oak-frontend-base/lib/context/BackendRuntimeContext");
|
||
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
||
const domain_1 = require("../utils/domain");
|
||
const user_1 = require("../utils/user");
|
||
const version_1 = require("oak-domain/lib/utils/version");
|
||
const validator_1 = require("oak-domain/lib/utils/validator");
|
||
/**
|
||
* general数据结构要求的后台上下文
|
||
*/
|
||
class BackendRuntimeContext extends BackendRuntimeContext_1.BackendRuntimeContext {
|
||
application;
|
||
token;
|
||
amIRoot;
|
||
amIReallyRoot;
|
||
rootMode;
|
||
userId;
|
||
platformManager;
|
||
appVersion;
|
||
applicationProjection = (0, lodash_1.cloneDeep)(Projection_1.applicationProjection);
|
||
async refineOpRecords() {
|
||
const isRoot = this.isRoot();
|
||
const isPlatformManager = this.platformManager;
|
||
for (const opRecord of this.opRecords) {
|
||
if (opRecord.a === 's') {
|
||
const { d } = opRecord;
|
||
for (const entity in d) {
|
||
if (entity === 'wechatQrCode') {
|
||
// todo 小程序码此时去微信服务器获得码数据
|
||
const wechatQrCodeListObj = d[entity];
|
||
for (const id in wechatQrCodeListObj) {
|
||
const wechatQrCodeData = wechatQrCodeListObj[id];
|
||
if (wechatQrCodeData.hasOwnProperty('buffer') &&
|
||
wechatQrCodeData.type === 'wechatMpWxaCode') {
|
||
const buffer = await (0, wechatQrCode_1.getMpUnlimitWxaCode)(id, this);
|
||
Object.assign(wechatQrCodeData, {
|
||
buffer,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
else if (entity === 'application') {
|
||
if (!(isRoot || isPlatformManager)) {
|
||
const rowDict = d[entity];
|
||
for (const id in rowDict) {
|
||
const { config } = rowDict[id];
|
||
if (config) {
|
||
// application中可能的保密信息
|
||
(0, lodash_1.unset)(config, 'appSecret');
|
||
(0, lodash_1.unset)(config, 'server.token');
|
||
(0, lodash_1.unset)(config, 'server.mode');
|
||
(0, lodash_1.unset)(config, 'server.encodingAESKey');
|
||
(0, lodash_1.unset)(config, 'wechat.appSecret');
|
||
(0, lodash_1.unset)(config, 'wechatNative.appSecret');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (['system', 'platform'].includes(entity)) {
|
||
if (!(isRoot || isPlatformManager)) {
|
||
const rowDict = d[entity];
|
||
for (const id in rowDict) {
|
||
const { config } = rowDict[id];
|
||
if (config) {
|
||
// server/platform中可能的保密信息
|
||
if (config.Account) {
|
||
for (const k in config.Account) {
|
||
if (config.Account[k] instanceof Array) {
|
||
config.Account[k]?.forEach((ele) => {
|
||
(0, lodash_1.unset)(ele, 'securityKey');
|
||
(0, lodash_1.unset)(ele, 'secretKey');
|
||
(0, lodash_1.unset)(ele, 'webApiKey');
|
||
(0, lodash_1.unset)(ele, 'accessKeySecret');
|
||
});
|
||
}
|
||
}
|
||
}
|
||
if (config.Sms) {
|
||
for (const k in config.Sms) {
|
||
if (config.Sms[k] instanceof Array) {
|
||
config.Sms[k]?.forEach((ele) => {
|
||
(0, lodash_1.unset)(ele, 'securityKey');
|
||
(0, lodash_1.unset)(ele, 'secretKey');
|
||
(0, lodash_1.unset)(ele, 'webApiKey');
|
||
(0, lodash_1.unset)(ele, 'accessKeySecret');
|
||
});
|
||
}
|
||
}
|
||
}
|
||
if (config.Emails) {
|
||
config.Emails?.forEach((ele) => {
|
||
(0, lodash_1.unset)(ele, 'password');
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (entity === 'user') {
|
||
for (const id in d[entity]) {
|
||
const userData = d[entity][id];
|
||
const { password, verifyPasswordAt } = userData;
|
||
if (password && verifyPasswordAt !== null) {
|
||
userData.password = (0, user_1.maskPassword)(password);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
async setPlatformManager(tokenValue, userId) {
|
||
if (tokenValue) {
|
||
// 前台传递
|
||
const result = await this.select('platform', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
userRelation$entity: {
|
||
user: {
|
||
token$user: {
|
||
$or: [
|
||
{
|
||
value: tokenValue,
|
||
},
|
||
{
|
||
oldValue: tokenValue,
|
||
},
|
||
],
|
||
}
|
||
},
|
||
relationId: {
|
||
$in: ["platform-owner" /* RelationId.Platform.Owner */, "platform-manager" /* RelationId.Platform.Manager */]
|
||
}
|
||
},
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
blockTrigger: true,
|
||
});
|
||
if (result.length > 0) {
|
||
this.platformManager = true;
|
||
}
|
||
else {
|
||
this.platformManager = false;
|
||
}
|
||
return;
|
||
}
|
||
if (userId) {
|
||
// 若是后台环境,用userId来查询处理
|
||
const result = await this.select('platform', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
userRelation$entity: {
|
||
userId,
|
||
relationId: {
|
||
$in: ["platform-owner" /* RelationId.Platform.Owner */, "platform-manager" /* RelationId.Platform.Manager */]
|
||
}
|
||
},
|
||
},
|
||
}, { dontCollect: true });
|
||
if (result.length > 0) {
|
||
this.platformManager = true;
|
||
}
|
||
else {
|
||
this.platformManager = false;
|
||
}
|
||
}
|
||
}
|
||
async setTokenValue(tokenValue, userId) {
|
||
if (tokenValue) {
|
||
// 前台传递
|
||
const result = await this.select('token', {
|
||
data: {
|
||
id: 1,
|
||
ableState: 1,
|
||
user: {
|
||
id: 1,
|
||
userState: 1,
|
||
isRoot: 1,
|
||
},
|
||
applicationId: 1,
|
||
userId: 1,
|
||
value: 1,
|
||
player: {
|
||
id: 1,
|
||
isRoot: 1,
|
||
},
|
||
playerId: 1,
|
||
},
|
||
filter: {
|
||
$or: [
|
||
{
|
||
value: tokenValue,
|
||
},
|
||
{
|
||
oldValue: tokenValue,
|
||
// refreshedAt: {
|
||
// $gte: Date.now() - 300 * 1000,
|
||
// },
|
||
},
|
||
],
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
blockTrigger: true,
|
||
});
|
||
if (result.length === 0) {
|
||
console.log(`构建BackendRuntimeContext对应tokenValue「${tokenValue}找不到相关的user`);
|
||
throw new Exception_1.OakTokenExpiredException();
|
||
}
|
||
const token = result[0];
|
||
if (token.ableState === 'disabled') {
|
||
console.log(`构建BackendRuntimeContext对应tokenValue「${tokenValue}已经被disable`);
|
||
throw new Exception_1.OakTokenExpiredException();
|
||
}
|
||
const { user, player } = token;
|
||
this.amIRoot = user?.isRoot;
|
||
this.amIReallyRoot = player?.isRoot;
|
||
this.token = token;
|
||
this.userId = token.userId;
|
||
return;
|
||
}
|
||
if (userId) {
|
||
// 若是后台环境,用userId来查询处理
|
||
const [user] = await this.select('user', {
|
||
data: {
|
||
id: 1,
|
||
isRoot: 1,
|
||
},
|
||
filter: { id: userId },
|
||
}, { dontCollect: true });
|
||
(0, assert_1.assert)(user, '初始化context时有userId但查询不到user');
|
||
this.amIRoot = user.isRoot;
|
||
this.amIReallyRoot = user.isRoot;
|
||
this.userId = userId;
|
||
}
|
||
}
|
||
async setApplication(appId) {
|
||
const result = await this.select('application', {
|
||
data: this.applicationProjection,
|
||
filter: {
|
||
id: appId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
blockTrigger: true,
|
||
});
|
||
(0, assert_1.assert)(result.length > 0, `构建BackendRuntimeContext对应appId「${appId}」找不到application`);
|
||
this.application = result[0];
|
||
}
|
||
async initialize(data, later) {
|
||
await super.initialize(data);
|
||
if (data) {
|
||
const closeRootMode = this.openRootMode();
|
||
try {
|
||
const { a: appId, t: tokenValue, rm, userId, v } = data;
|
||
this.appVersion = v;
|
||
const promises = []; // 这里需要并行,下面的await检查略过
|
||
if (appId) {
|
||
// @oak-ignore
|
||
promises.push(this.setApplication(appId));
|
||
}
|
||
if (tokenValue || userId) {
|
||
// @oak-ignore
|
||
promises.push(this.setTokenValue(tokenValue, userId));
|
||
// @oak-ignore
|
||
promises.push(this.setPlatformManager(tokenValue, userId));
|
||
}
|
||
if (promises.length > 0) {
|
||
await Promise.all(promises);
|
||
}
|
||
if (!rm) {
|
||
closeRootMode();
|
||
}
|
||
}
|
||
catch (err) {
|
||
closeRootMode();
|
||
throw err;
|
||
}
|
||
}
|
||
else {
|
||
// 否则是后台模式,默认用root
|
||
this.rootMode = true;
|
||
}
|
||
}
|
||
getApplicationId(allowNull) {
|
||
if (!allowNull) {
|
||
if (!this.application) {
|
||
throw new Exception_1.OakApplicationLoadingException();
|
||
}
|
||
return this.application.id;
|
||
}
|
||
return this.application?.id;
|
||
}
|
||
getSystemId(allowNull) {
|
||
if (!allowNull) {
|
||
if (!this.application) {
|
||
throw new Exception_1.OakApplicationLoadingException();
|
||
}
|
||
return this.application.systemId;
|
||
}
|
||
return this.application?.systemId;
|
||
}
|
||
getApplication(allowNull) {
|
||
if (!allowNull) {
|
||
if (!this.application) {
|
||
throw new Exception_1.OakApplicationLoadingException();
|
||
}
|
||
return this.application;
|
||
}
|
||
return this.application;
|
||
}
|
||
openRootMode() {
|
||
if (this.rootMode) {
|
||
return () => undefined;
|
||
}
|
||
this.rootMode = true;
|
||
return () => (this.rootMode = false);
|
||
}
|
||
getTokenValue(allowUnloggedIn) {
|
||
if (this.rootMode) {
|
||
return constants_1.ROOT_TOKEN_ID;
|
||
}
|
||
if (!this.token && !allowUnloggedIn) {
|
||
throw new Exception_2.OakUnloggedInException();
|
||
}
|
||
return this.token?.value;
|
||
}
|
||
getToken(allowUnloggedIn) {
|
||
if (!this.token && !allowUnloggedIn) {
|
||
throw new Exception_2.OakUnloggedInException();
|
||
}
|
||
if (this.token) {
|
||
const { userState } = this.token.user;
|
||
if (['disabled', 'merged'].includes(userState) &&
|
||
!this.isReallyRoot()) {
|
||
throw new Exception_1.OakUserDisabledException();
|
||
}
|
||
}
|
||
return this.token;
|
||
}
|
||
getCurrentUserId(allowUnloggedIn) {
|
||
if (this.userId) {
|
||
return this.userId;
|
||
}
|
||
const token = this.getToken(allowUnloggedIn);
|
||
return token?.userId;
|
||
}
|
||
setCurrentUserId(userId) {
|
||
(0, assert_1.assert)(this.isReallyRoot);
|
||
this.userId = userId;
|
||
}
|
||
async getSerializedData() {
|
||
const data = await super.getSerializedData();
|
||
// 后台物化上下文直接存userId;
|
||
return {
|
||
...data,
|
||
a: this.application?.id,
|
||
rm: this.rootMode,
|
||
userId: this.getCurrentUserId(true),
|
||
v: this.appVersion,
|
||
};
|
||
}
|
||
isRoot() {
|
||
if (this.rootMode) {
|
||
return true;
|
||
}
|
||
return !!this.amIRoot;
|
||
}
|
||
isReallyRoot() {
|
||
return !!this.amIReallyRoot;
|
||
}
|
||
async sendMessage(data) {
|
||
// @oak-ignore 这里直接return,依赖上层await,先跳过检查
|
||
return this.operate('message', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data,
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
}
|
||
allowUserUpdate() {
|
||
if (this.isReallyRoot()) {
|
||
return true;
|
||
}
|
||
const userInfo = this.token?.user;
|
||
if (userInfo) {
|
||
const { userState } = userInfo;
|
||
if (userState === 'disabled') {
|
||
throw new Exception_1.OakUserDisabledException('您的帐号已经被禁用,请联系客服');
|
||
}
|
||
else if (['merged'].includes(userState)) {
|
||
throw new Exception_1.OakTokenExpiredException('您的登录状态有异常,请重新登录 ');
|
||
}
|
||
else {
|
||
(0, assert_1.assert)(userState === 'normal' || userState === 'shadow');
|
||
}
|
||
return true;
|
||
}
|
||
throw new Exception_2.OakUnloggedInException('您尚未登录');
|
||
}
|
||
sortDomains(domains) {
|
||
return domains.sort((a, b) => {
|
||
const getPriority = (domain) => {
|
||
if (typeof domain.url !== 'string')
|
||
return 3;
|
||
const lowerItem = domain.url.toLowerCase();
|
||
// 1. 检查是否是localhost
|
||
if (lowerItem === 'localhost')
|
||
return 2;
|
||
// 2. 检查是否是IPv4或IPv6
|
||
if ((0, validator_1.isIP)(domain.url))
|
||
return 1;
|
||
// 3. 其他情况认为是域名
|
||
return 0;
|
||
};
|
||
return getPriority(a) - getPriority(b);
|
||
});
|
||
}
|
||
/**
|
||
* 获得当前系统的访问路径,根据application/system/domain之间的关联
|
||
* http://www.xxx.com/oak-api
|
||
*/
|
||
composeAccessPath() {
|
||
const application = this.getApplication();
|
||
const { system, domainId, domain } = application;
|
||
if (domainId) {
|
||
(0, assert_1.assert)(domain);
|
||
return (0, domain_1.composeServerUrl)(domain);
|
||
}
|
||
const { domain$system: domains } = system;
|
||
(0, assert_1.assert)(domains && domains.length > 0);
|
||
const findDomain = this.sortDomains(domains)?.[0];
|
||
return (0, domain_1.composeServerUrl)(findDomain);
|
||
}
|
||
async tryDeduceException(err) {
|
||
if (this.application && this.appVersion) {
|
||
const { soaVersion } = this.application;
|
||
if (soaVersion && (0, version_1.compareVersion)(this.appVersion, soaVersion) < 0) {
|
||
// 说明客户端可以升级
|
||
return new types_1.OakApplicationHasToUpgrade();
|
||
}
|
||
}
|
||
}
|
||
;
|
||
}
|
||
exports.BackendRuntimeContext = BackendRuntimeContext;
|
||
;
|
||
exports.default = BackendRuntimeContext;
|