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;