oak-general-business/es/context/BackendRuntimeContext.js

461 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

import { assert } from 'oak-domain/lib/utils/assert';
import { OakApplicationLoadingException, OakTokenExpiredException, OakUserDisabledException, } from '../types/Exception';
import { OakUnloggedInException, } from 'oak-domain/lib/types/Exception';
import { ROOT_TOKEN_ID } from '../constants';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { OakApplicationHasToUpgrade } from 'oak-domain/lib/types';
import { applicationProjection } from '../types/Projection';
import { getMpUnlimitWxaCode } from '../aspects/wechatQrCode';
import { BackendRuntimeContext as BRC } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
import { cloneDeep, unset } from 'oak-domain/lib/utils/lodash';
import { composeServerUrl } from '../utils/domain';
import { maskPassword } from '../utils/user';
import { compareVersion } from 'oak-domain/lib/utils/version';
import { isIP } from 'oak-domain/lib/utils/validator';
/**
* general数据结构要求的后台上下文
*/
export class BackendRuntimeContext extends BRC {
application;
token;
amIRoot;
amIReallyRoot;
rootMode;
userId;
platformManager;
appVersion;
applicationProjection = cloneDeep(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 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中可能的保密信息
unset(config, 'appSecret');
unset(config, 'server.token');
unset(config, 'server.mode');
unset(config, 'server.encodingAESKey');
unset(config, 'wechat.appSecret');
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) => {
unset(ele, 'securityKey');
unset(ele, 'secretKey');
unset(ele, 'webApiKey');
unset(ele, 'accessKeySecret');
});
}
}
}
if (config.Sms) {
for (const k in config.Sms) {
if (config.Sms[k] instanceof Array) {
config.Sms[k]?.forEach((ele) => {
unset(ele, 'securityKey');
unset(ele, 'secretKey');
unset(ele, 'webApiKey');
unset(ele, 'accessKeySecret');
});
}
}
}
if (config.Emails) {
config.Emails?.forEach((ele) => {
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 = 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 OakTokenExpiredException();
}
const token = result[0];
if (token.ableState === 'disabled') {
console.log(`构建BackendRuntimeContext对应tokenValue「${tokenValue}已经被disable`);
throw new 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 });
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,
});
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 OakApplicationLoadingException();
}
return this.application.id;
}
return this.application?.id;
}
getSystemId(allowNull) {
if (!allowNull) {
if (!this.application) {
throw new OakApplicationLoadingException();
}
return this.application.systemId;
}
return this.application?.systemId;
}
getApplication(allowNull) {
if (!allowNull) {
if (!this.application) {
throw new 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 ROOT_TOKEN_ID;
}
if (!this.token && !allowUnloggedIn) {
throw new OakUnloggedInException();
}
return this.token?.value;
}
getToken(allowUnloggedIn) {
if (!this.token && !allowUnloggedIn) {
throw new OakUnloggedInException();
}
if (this.token) {
const { userState } = this.token.user;
if (['disabled', 'merged'].includes(userState) &&
!this.isReallyRoot()) {
throw new OakUserDisabledException();
}
}
return this.token;
}
getCurrentUserId(allowUnloggedIn) {
if (this.userId) {
return this.userId;
}
const token = this.getToken(allowUnloggedIn);
return token?.userId;
}
setCurrentUserId(userId) {
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 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 OakUserDisabledException('您的帐号已经被禁用,请联系客服');
}
else if (['merged'].includes(userState)) {
throw new OakTokenExpiredException('您的登录状态有异常,请重新登录 ');
}
else {
assert(userState === 'normal' || userState === 'shadow');
}
return true;
}
throw new 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 (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) {
assert(domain);
return composeServerUrl(domain);
}
const { domain$system: domains } = system;
assert(domains && domains.length > 0);
const findDomain = this.sortDomains(domains)?.[0];
return composeServerUrl(findDomain);
}
async tryDeduceException(err) {
if (this.application && this.appVersion) {
const { soaVersion } = this.application;
if (soaVersion && compareVersion(this.appVersion, soaVersion) < 0) {
// 说明客户端可以升级
return new OakApplicationHasToUpgrade();
}
}
}
;
}
;
export default BackendRuntimeContext;