增加了application和system中的相关数据结构,用于检查和控制应用升级

This commit is contained in:
Xu Chang 2025-01-26 16:14:29 +08:00
parent f8635876c6
commit 7021b0e548
128 changed files with 1133 additions and 127 deletions

View File

@ -89,6 +89,7 @@ export type AspectDict<ED extends EntityDict> = {
type: 'login' | 'changePassword' | 'confirm';
}, context: BackendRuntimeContext<ED>) => Promise<string>;
getApplication: (params: {
version: string;
type: AppType;
domain: string;
data: ED['application']['Projection'];

View File

@ -5,6 +5,7 @@ import { WebEnv } from 'oak-domain/lib/types/Environment';
import { File } from 'formidable';
import { BRC } from '../types/RuntimeCxt';
export declare function getApplication<ED extends EntityDict>(params: {
version: string;
type: AppType;
domain: string;
data: ED['application']['Projection'];

View File

@ -2,8 +2,10 @@ import { assert } from 'oak-domain/lib/utils/assert';
import { applicationProjection } from '../types/Projection';
import WechatSDK from 'oak-external-sdk/lib/WechatSDK';
import fs from 'fs';
import { cloneDeep } from 'oak-domain/lib/utils/lodash';
import { cloneDeep, unset } from 'oak-domain/lib/utils/lodash';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { compareVersion } from 'oak-domain/lib/utils/version';
import { OakApplicationHasToUpgrade } from 'oak-domain/lib/types/Exception';
async function getApplicationByDomain(context, options) {
const { data, type, domain } = options;
let applications = await context.select('application', {
@ -39,8 +41,26 @@ async function getApplicationByDomain(context, options) {
}
return applications;
}
function checkAppVersionSafe(application, version) {
const { dangerousVersions, warningVersions, system } = application;
const { oldestVersion, platform } = system;
const { oldestVersion: pfOldestVersion } = platform || {};
const oldest = pfOldestVersion || oldestVersion;
if (oldest) {
if (compareVersion(version, oldest) < 0) {
throw new OakApplicationHasToUpgrade();
}
}
if (dangerousVersions && dangerousVersions.includes(version)) {
throw new OakApplicationHasToUpgrade();
}
unset(application, 'dangerousVersions');
if (warningVersions) {
application.warningVersions = warningVersions.filter(ele => ele === version);
}
}
export async function getApplication(params, context) {
const { type, domain, data, appId } = params;
const { type, domain, data, appId, version } = params;
// 先找指定domain的应用如果不存在再找系统下面的domain 但无论怎么样都必须一项
const applications = await getApplicationByDomain(context, {
type,
@ -51,11 +71,13 @@ export async function getApplication(params, context) {
case 'wechatMp': {
assert(applications.length === 1, `微信小程序环境下,同一个系统必须存在唯一的【${type}】应用,域名「${domain}`);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id;
}
case 'native': {
assert(applications.length === 1, `APP环境下同一个系统必须存在唯一的【${type}】应用,域名「${domain}`);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id;
}
case 'wechatPublic': {
@ -68,15 +90,18 @@ export async function getApplication(params, context) {
});
assert(webApplications.length === 1, `微信公众号环境下, 可以未配置公众号但必须存在web应用域名「${domain}`);
const application = webApplications[0];
checkAppVersionSafe(application, version);
return application.id;
}
assert(applications.length === 1, `微信公众号环境下,同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名,域名「${domain}`);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id;
}
case 'web': {
assert(applications.length === 1, `web环境下同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名,域名「${domain}`);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id;
}
default: {

View File

@ -1,4 +1,18 @@
import { OakInputIllegalException } from 'oak-domain/lib/types';
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
import { isVersion } from 'oak-domain/lib/utils/version';
function checkVersion(data) {
const { dangerousVersions, warningVersions, soaVersion } = data;
if (dangerousVersions && dangerousVersions.find(ele => !isVersion(ele))) {
throw new OakInputIllegalException('application', ['dangerousVersions'], 'error::illegalVersionData');
}
if (warningVersions && warningVersions.find(ele => !isVersion(ele))) {
throw new OakInputIllegalException('application', ['warningVersions'], 'error::illegalVersionData');
}
if (soaVersion && !isVersion(soaVersion)) {
throw new OakInputIllegalException('application', ['soaVersion'], 'error::illegalVersionData');
}
}
const checkers = [
{
type: 'data',
@ -14,6 +28,7 @@ const checkers = [
};
if (data instanceof Array) {
data.forEach((ele) => {
checkVersion(ele);
checkAttributesNotNull('application', ele, [
'name',
'type',
@ -23,6 +38,7 @@ const checkers = [
});
}
else {
checkVersion(data);
checkAttributesNotNull('application', data, [
'name',
'type',
@ -33,5 +49,13 @@ const checkers = [
return;
},
},
{
type: 'data',
action: 'update',
entity: 'application',
checker(data) {
checkVersion(data);
}
}
];
export default checkers;

View File

@ -1,2 +1,2 @@
declare const checkers: (import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>>)[];
declare const checkers: (import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "system", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "platform", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>>)[];
export default checkers;

View File

@ -9,6 +9,8 @@ import wechatPublicTagChecker from './wechatPublicTag';
import messageChecker from './message';
import parasite from './parasite';
import applicationPassport from './applicationPassport';
import systems from './system';
import platforms from './platform';
const checkers = [
...mobileChecker,
...addressCheckers,
@ -21,5 +23,7 @@ const checkers = [
...messageChecker,
...parasite,
...applicationPassport,
...systems,
...platforms
];
export default checkers;

View File

@ -1,4 +1,6 @@
import { OakInputIllegalException } from 'oak-domain/lib/types';
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
import { isVersion } from 'oak-domain/lib/utils/version';
const checkers = [
{
type: 'data',
@ -15,11 +17,17 @@ const checkers = [
if (data instanceof Array) {
data.forEach((ele) => {
checkAttributesNotNull('platform', ele, ['name']);
if (ele.oldestVersion && !isVersion(ele.oldestVersion)) {
throw new OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData', 'oak-general-business');
}
setData(ele);
});
}
else {
checkAttributesNotNull('platform', data, ['name']);
if (data.oldestVersion && !isVersion(data.oldestVersion)) {
throw new OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData', 'oak-general-business');
}
setData(data);
}
return;

View File

@ -1,4 +1,6 @@
import { OakInputIllegalException } from 'oak-domain/lib/types';
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
import { isVersion } from 'oak-domain/lib/utils/version';
const checkers = [
{
type: 'data',
@ -20,15 +22,32 @@ const checkers = [
if (data instanceof Array) {
data.forEach((ele) => {
checkAttributesNotNull('system', ele, ['name', 'platformId']);
if (ele.oldestVersion && !isVersion(ele.oldestVersion)) {
throw new OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData', 'oak-general-business');
}
setData(ele);
});
}
else {
checkAttributesNotNull('system', data, ['name', 'platformId']);
if (data.oldestVersion && !isVersion(data.oldestVersion)) {
throw new OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData', 'oak-general-business');
}
setData(data);
}
return;
},
},
{
type: 'data',
action: 'update',
entity: 'system',
checker(data) {
const { oldestVersion } = data;
if (oldestVersion && !isVersion(oldestVersion)) {
throw new OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData');
}
}
}
];
export default checkers;

View File

@ -1,8 +1,11 @@
export default OakComponent({
isList: false,
entity: 'application',
formData({ data }) {
formData({ data, features }) {
const { dangerousVersions, warningVersions } = data;
return {
dv: dangerousVersions ? features.locales.t('whole', { count: dangerousVersions.length }) : features.locales.t('common::unset'),
wv: warningVersions ? features.locales.t('whole', { count: warningVersions.length }) : features.locales.t('common::unset'),
...data,
oakExecutable: this.tryExecute(),
};

View File

@ -0,0 +1,3 @@
{
"whole": "共%{count}项"
}

View File

@ -8,4 +8,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'application
tabValue: 'detail';
type: EntityDict['application']['Schema']['type'];
oakExecutable: boolean;
soaVersion?: string;
dv: string;
wv: string;
}>): React.JSX.Element | undefined;

View File

@ -2,12 +2,12 @@ import React, { useState } from 'react';
import { Row, Descriptions, Typography, Button, Modal, Space } from 'antd';
import ApplicationUpsert from '../upsert';
export default function Render(props) {
const { id, name, description, type, oakFullpath, oakExecutable, oakExecuting } = props.data;
const { id, name, description, type, oakFullpath, oakDirty, oakExecuting, dv, wv, soaVersion } = props.data;
const { t, clean, execute } = props.methods;
const [open, setOpen] = useState(false);
if (id && oakFullpath) {
return (<>
<Modal open={open} width={500} onCancel={() => {
<Modal destroyOnClose open={open} width={500} onCancel={() => {
clean();
setOpen(false);
}} footer={<Space>
@ -20,7 +20,7 @@ export default function Render(props) {
<Button type="primary" onClick={async () => {
await execute();
setOpen(false);
}} disabled={oakExecutable !== true || oakExecuting}>
}} disabled={!oakDirty || oakExecuting}>
{t('common::action.confirm')}
</Button>
</Space>}>
@ -41,6 +41,15 @@ export default function Render(props) {
<Descriptions.Item label={t('application:attr.type')}>
{t(`application:v.type.${type}`)}
</Descriptions.Item>
<Descriptions.Item label={t('application:attr.soaVersion')}>
{soaVersion || t('common::unset')}
</Descriptions.Item>
<Descriptions.Item label={t('application:attr.dangerousVersions')}>
{dv}
</Descriptions.Item>
<Descriptions.Item label={t('application:attr.warningVersions')}>
{wv}
</Descriptions.Item>
<Descriptions.Item span={2}>
<Row justify="end">
<Button type="primary" onClick={() => setOpen(true)}>

View File

@ -7,6 +7,9 @@ export default OakComponent({
config: 1,
description: 1,
type: 1,
dangerousVersions: 1,
warningVersions: 1,
soaVersion: 1,
systemId: 1,
domainId: 1,
},

View File

@ -6,6 +6,9 @@ export default OakComponent({
name: 1,
config: 1,
description: 1,
dangerousVersions: 1,
warningVersions: 1,
soaVersion: 1,
type: 1,
systemId: 1,
domainId: 1,

View File

@ -15,6 +15,9 @@ export default function Render(props: WebComponentProps<EntityDict, 'application
$$createAt$$: number;
domainId: string;
domains: EntityDict['domain']['Schema'][];
dangerousVersions: EntityDict['application']['OpSchema']['dangerousVersions'];
warningVersions: EntityDict['application']['OpSchema']['warningVersions'];
soaVersion: string;
}, {
confirm: () => void;
getDomains: (systemId: string) => Promise<void>;

View File

@ -1,10 +1,62 @@
import React from 'react';
import { Form, Select, Input } from 'antd';
import React, { useState, useRef } from 'react';
import { Form, Flex, Tag, Tooltip, Select, Input, theme } from 'antd';
import { PlusOutlined, CloseOutlined } from '@ant-design/icons';
function renderVersions(props) {
const { versions, onChange, t } = props;
const [inputVisible, setInputVisible] = useState(false);
const [inputValue, setInputValue] = useState('');
const inputRef = useRef(null);
const tagInputStyle = {
width: 64,
height: 22,
marginInlineEnd: 8,
verticalAlign: 'top',
};
const { token } = theme.useToken();
const tagPlusStyle = {
height: 22,
background: token.colorBgContainer,
borderStyle: 'dashed',
};
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
const handleInputConfirm = () => {
if (inputValue && !versions?.includes(inputValue)) {
onChange([...versions || [], inputValue]);
}
setInputVisible(false);
setInputValue('');
};
const handleClose = (removedTag) => {
const versions2 = versions.filter((tag) => tag !== removedTag);
onChange(versions2);
};
const showInput = () => {
setInputVisible(true);
};
return (<Flex gap="4px 0" wrap="wrap">
{(versions || []).map((tag, index) => {
const isLongTag = tag.length > 20;
const tagElem = (<Tag closeIcon={<CloseOutlined />} key={tag} onClose={() => handleClose(tag)}>
<span>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
</Tag>);
return isLongTag ? (<Tooltip title={tag} key={tag}>
{tagElem}
</Tooltip>) : (tagElem);
})}
{inputVisible ? (<Input ref={inputRef} type="text" size="small" style={tagInputStyle} value={inputValue} onChange={handleInputChange} onBlur={handleInputConfirm} onPressEnter={handleInputConfirm}/>) : (<Tag style={tagPlusStyle} icon={<PlusOutlined />} onClick={showInput}>
{t('common::action.add')}
</Tag>)}
</Flex>);
}
export default function Render(props) {
const { systemId, name, description, type, typeArr, $$createAt$$, domainId, domains, } = props.data;
const { systemId, name, description, type, typeArr, $$createAt$$, domainId, domains, dangerousVersions, warningVersions, soaVersion, } = props.data;
const { t, update, confirm, getDomains } = props.methods;
return (<Form colon={true} labelCol={{ span: 6 }} wrapperCol={{ span: 16 }}>
<Form.Item label="名称" required>
<Form.Item label={t('application:attr.name')} required>
<>
<Input onChange={(e) => {
update({
@ -13,7 +65,34 @@ export default function Render(props) {
}} value={name}/>
</>
</Form.Item>
<Form.Item label="描述">
<Form.Item label={t('application:attr.soaVersion')} required>
<>
<Input onChange={(e) => {
update({
soaVersion: e.target.value,
});
}} value={soaVersion}/>
</>
</Form.Item>
<Form.Item label={t('application:attr.dangerousVersions')}>
{renderVersions({
versions: dangerousVersions,
onChange: (v) => update({
dangerousVersions: v
}),
t,
})}
</Form.Item>
<Form.Item label={t('application:attr.warningVersions')}>
{renderVersions({
versions: warningVersions,
onChange: (v) => update({
warningVersions: v
}),
t,
})}
</Form.Item>
<Form.Item label={t('application:attr.description')}>
<>
<Input.TextArea onChange={(e) => {
update({
@ -22,7 +101,7 @@ export default function Render(props) {
}} value={description}/>
</>
</Form.Item>
<Form.Item label="应用类型" required>
<Form.Item label={t('application:attr.type')} required>
<>
<Select value={type} style={{ width: 120 }} disabled={$$createAt$$ > 1} options={typeArr.map((ele) => ({
label: t(`application:v.type.${ele.value}`),
@ -34,7 +113,7 @@ export default function Render(props) {
}}/>
</>
</Form.Item>
<Form.Item label="域名">
<Form.Item label={t('domain:name')}>
<>
<Select allowClear value={domainId} style={{ width: 120 }} options={domains?.map((ele) => ({
label: ele.url,

View File

@ -1,7 +1,7 @@
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "article", true, {
articleMenuId: string | undefined;
onChildEditArticleChange: (data: string) => void;
show: "preview" | "edit" | "doc";
show: "edit" | "doc" | "preview";
getBreadcrumbItemsByParent: (breadcrumbItems: string[]) => void;
breadcrumbItems: string[];
drawerOpen: boolean;

View File

@ -2,7 +2,7 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
onRemove: () => void;
onUpdateName: (name: string) => Promise<void>;
onChildEditArticleChange: (data: string) => void;
show: "preview" | "edit" | "doc";
show: "edit" | "doc" | "preview";
getBreadcrumbItemsByParent: (breadcrumbItems: string[]) => void;
breadItems: string[];
drawerOpen: boolean;

View File

@ -4,7 +4,7 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
entityId: string;
parentId: string | undefined;
onGrandChildEditArticleChange: (data: string) => void;
show: "preview" | "edit" | "doc";
show: "edit" | "doc" | "preview";
articleMenuId: string;
articleId: string;
getBreadcrumbItems: (breadcrumbItems: string[]) => void;

View File

@ -1,7 +1,7 @@
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "articleMenu", true, {
entity: string;
entityId: string;
show: "preview" | "edit" | "doc";
show: "edit" | "doc" | "preview";
articleMenuId: string;
articleId: string;
tocPosition: "none" | "left" | "right";

View File

@ -1,7 +1,7 @@
import { Style } from '../../../../types/Style';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../../oak-app-domain").EntityDict, keyof import("../../../../oak-app-domain").EntityDict, false, {
style: Style;
entity: "application" | "system" | "platform";
entity: "application" | "platform" | "system";
entityId: string;
name: string;
}>) => React.ReactElement;

View File

@ -1,7 +1,7 @@
import { Config } from '../../../types/Config';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, keyof import("../../../oak-app-domain").EntityDict, false, {
config: Config;
entity: "system" | "platform";
entity: "platform" | "system";
name: string;
entityId: string;
}>) => React.ReactElement;

View File

@ -15,16 +15,16 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
type?: ButtonProps['type'] | AmButtonProps['type'];
executeText?: string | undefined;
buttonProps?: (ButtonProps & {
color?: "default" | "success" | "primary" | "warning" | "danger" | undefined;
color?: "default" | "success" | "warning" | "primary" | "danger" | undefined;
fill?: "none" | "solid" | "outline" | undefined;
size?: "small" | "large" | "middle" | "mini" | undefined;
size?: "small" | "middle" | "large" | "mini" | undefined;
block?: boolean | undefined;
loading?: boolean | "auto" | undefined;
loadingText?: string | undefined;
loadingIcon?: import("react").ReactNode;
disabled?: boolean | undefined;
onClick?: ((event: import("react").MouseEvent<HTMLButtonElement, MouseEvent>) => unknown) | undefined;
type?: "button" | "reset" | "submit" | undefined;
type?: "button" | "submit" | "reset" | undefined;
shape?: "default" | "rounded" | "rectangular" | undefined;
children?: import("react").ReactNode;
} & Pick<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement>, "id" | "onMouseDown" | "onMouseUp" | "onTouchEnd" | "onTouchStart"> & {

View File

@ -4,6 +4,7 @@ import { WebComponentProps } from 'oak-frontend-base';
export default function Render(props: WebComponentProps<EntityDict, 'platform', false, {
name: string;
description: string;
oldestVersion: string;
style: EntityDict['system']['Schema']['style'];
}, {
confirm: () => void;

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Form, Input } from 'antd';
export default function Render(props) {
const { name, description, style } = props.data;
const { name, description, oldestVersion } = props.data;
const { t, update, navigateBack, confirm } = props.methods;
return (<Form colon={true} labelCol={{ span: 4 }} wrapperCol={{ span: 16 }}>
<Form.Item label="名称" required>
@ -21,6 +21,15 @@ export default function Render(props) {
});
}} value={description}/>
</>
</Form.Item>
</Form.Item>
<Form.Item label={t('platform:attr.oldestVersion')}>
<>
<Input onChange={(e) => {
update({
oldestVersion: e.target.value,
});
}} value={oldestVersion}/>
</>
</Form.Item>
</Form>);
}

View File

@ -7,4 +7,5 @@ export default function Render(props: WebComponentProps<EntityDict, 'platform',
oakId: string;
super: boolean;
oakExecutable: boolean;
oldestVersion: string;
}>): React.JSX.Element | null;

View File

@ -3,7 +3,7 @@ import { Row, Modal, Descriptions, Typography, Button, Space } from 'antd';
import SystemUpsert from '../upsert';
import Styles from './web.pc.module.less';
export default function Render(props) {
const { oakId, name, description, 'super': isSuper, oakFullpath, oakExecutable, oakExecuting } = props.data;
const { oakId, name, description, 'super': isSuper, oakFullpath, oakExecutable, oakExecuting, oldestVersion } = props.data;
const { t, execute, clean } = props.methods;
const [open, setOpen] = useState(false);
if (oakFullpath) {
@ -43,6 +43,9 @@ export default function Render(props) {
<Descriptions.Item label={t('system:attr.description')}>
{description}
</Descriptions.Item>
<Descriptions.Item label={t('system:attr.oldestVersion')}>
{oldestVersion || t('common::unset')}
</Descriptions.Item>
<Descriptions.Item label={t('system:attr.super')}>
{isSuper ? '是' : '否'}
</Descriptions.Item>

View File

@ -6,6 +6,7 @@ export default OakComponent({
name: 1,
config: 1,
description: 1,
oldestVersion: 1,
super: 1,
domain$system: {
$entity: 'domain',

View File

@ -6,6 +6,7 @@ export default OakComponent({
name: 1,
config: 1,
description: 1,
oldestVersion: 1,
super: 1,
},
formData({ data }) {

View File

@ -1,5 +0,0 @@
{
"tips": {
"isSuper": "超级系统属性可能影响程序的运行逻辑,请谨慎修改"
}
}

View File

@ -0,0 +1,6 @@
{
"tips": {
"isSuper": "超级系统属性可能影响程序的运行逻辑,请谨慎修改",
"oldestVersion": "系统能兼容的最低应用版本号格式为X.X.X"
}
}

View File

@ -5,4 +5,5 @@ export default function Render(props: WebComponentProps<EntityDict, 'system', fa
name: string;
description: string;
super: boolean;
oldestVersion: string;
}>): React.JSX.Element;

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Form, Switch, Input } from 'antd';
export default function Render(props) {
const { name, description, super: super2, } = props.data;
const { name, description, super: super2, oldestVersion, } = props.data;
const { t, update } = props.methods;
return (<Form colon={true} labelCol={{ span: 6 }} wrapperCol={{ span: 16 }}>
<Form.Item label={t('system:attr.name')} required>
@ -22,9 +22,18 @@ export default function Render(props) {
}} value={description}/>
</>
</Form.Item>
<Form.Item label={t('system:attr.oldestVersion')} tooltip={t('tips.oldestVersion')}>
<>
<Input onChange={(e) => {
update({
oldestVersion: e.target.value,
});
}} value={oldestVersion}/>
</>
</Form.Item>
<Form.Item label={t('system:attr.super')} required tooltip={t('tips.isSuper')}>
<>
<Switch checkedChildren={t('common::yes')} unCheckedChildren={t('common::no')} checked={super2} onChange={(checked) => {
<Switch checkedChildren={t('common::true')} unCheckedChildren={t('common::false')} checked={super2} onChange={(checked) => {
update({
super: checked,
});

View File

@ -2,6 +2,7 @@ import { RuntimeContext } from './RuntimeContext';
import { EntityDict } from '../oak-app-domain';
import { SerializedData } from './FrontendRuntimeContext';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { OakException } from 'oak-domain/lib/types/Exception';
import { BackendRuntimeContext as BRC } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
/**
* general数据结构要求的后台上下文
@ -14,15 +15,16 @@ export declare abstract class BackendRuntimeContext<ED extends EntityDict & Base
protected rootMode?: boolean;
private userId?;
protected platformManager?: boolean;
protected appVersion?: string;
protected applicationProjection: EntityDict['user']['Projection'];
refineOpRecords(): Promise<void>;
setPlatformManager(tokenValue?: string, userId?: string): Promise<void>;
setTokenValue(tokenValue?: string, userId?: string): Promise<void>;
setApplication(appId: string): Promise<void>;
initialize(data?: SerializedData, later?: boolean): Promise<void>;
getApplicationId(): ED["application"]["Schema"]["id"] | undefined;
getSystemId(): ED["application"]["Schema"]["systemId"] | undefined;
getApplication(): Partial<ED["application"]["Schema"]> | undefined;
getApplicationId<P extends true | undefined>(allowNull?: P): P extends undefined ? (string | undefined) : string;
getSystemId<P extends true | undefined>(allowNull?: P): P extends undefined ? (string | undefined) : string;
getApplication<P extends true | undefined>(allowNull?: P): P extends undefined ? (Partial<ED['application']['Schema']> | undefined) : Partial<ED['application']['Schema']>;
openRootMode(): () => void;
getTokenValue(allowUnloggedIn?: boolean): "oak-root-token" | ED["token"]["Schema"]["value"] | undefined;
getToken(allowUnloggedIn?: boolean): Partial<ED["token"]["Schema"]> | undefined;
@ -38,5 +40,6 @@ export declare abstract class BackendRuntimeContext<ED extends EntityDict & Base
* http://www.xxx.com/oak-api
*/
composeAccessPath(): string;
tryDeduceException(err: Error): Promise<OakException<any> | void>;
}
export default BackendRuntimeContext;

View File

@ -1,14 +1,16 @@
import { assert } from 'oak-domain/lib/utils/assert';
import { OakTokenExpiredException, OakUserDisabledException, } from '../types/Exception';
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';
/**
* general数据结构要求的后台上下文
*/
@ -20,6 +22,7 @@ export class BackendRuntimeContext extends BRC {
rootMode;
userId;
platformManager;
appVersion;
applicationProjection = cloneDeep(applicationProjection);
async refineOpRecords() {
const isRoot = this.isRoot();
@ -259,7 +262,8 @@ export class BackendRuntimeContext extends BRC {
if (data) {
const closeRootMode = this.openRootMode();
try {
const { a: appId, t: tokenValue, rm, userId } = data;
const { a: appId, t: tokenValue, rm, userId, v } = data;
this.appVersion = v;
const promises = [];
if (appId) {
promises.push(this.setApplication(appId));
@ -285,13 +289,31 @@ export class BackendRuntimeContext extends BRC {
this.rootMode = true;
}
}
getApplicationId() {
getApplicationId(allowNull) {
if (!allowNull) {
if (!this.application) {
throw new OakApplicationLoadingException();
}
return this.application.id;
}
return this.application?.id;
}
getSystemId() {
getSystemId(allowNull) {
if (!allowNull) {
if (!this.application) {
throw new OakApplicationLoadingException();
}
return this.application.systemId;
}
return this.application?.systemId;
}
getApplication() {
getApplication(allowNull) {
if (!allowNull) {
if (!this.application) {
throw new OakApplicationLoadingException();
}
return this.application;
}
return this.application;
}
openRootMode() {
@ -342,6 +364,7 @@ export class BackendRuntimeContext extends BRC {
a: this.application?.id,
rm: this.rootMode,
userId: this.getCurrentUserId(true),
v: this.appVersion,
};
}
isRoot() {
@ -394,6 +417,16 @@ export class BackendRuntimeContext extends BRC {
const [domain] = domains;
return composeServerUrl(domain);
}
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;

View File

@ -10,6 +10,7 @@ export interface SerializedData extends Fsd {
t?: string;
userId?: string;
rm?: boolean;
v?: string;
}
export declare abstract class FrontendRuntimeContext<ED extends EntityDict & BaseEntityDict> extends Frc<ED> implements RuntimeContext {
private application;

View File

@ -17,6 +17,11 @@ export class FrontendRuntimeContext extends Frc {
// appId必须要取到不能失败
const setInner = (resolve, reject) => {
try {
if (!this.application) {
// 有可能在系统初始化的时候调用this.application还没建立
resolve(undefined);
return;
}
const appId = this.application.getApplicationId();
assert(appId);
Object.assign(data, {
@ -42,6 +47,11 @@ export class FrontendRuntimeContext extends Frc {
const setTokenValue = async () => {
const setInner = (resolve, reject) => {
try {
if (!this.token) {
// 有可能在系统初始化的时候调用this.token还没建立
resolve(undefined);
return;
}
const tokenValue = this.token.getTokenValue();
if (tokenValue) {
Object.assign(data, {
@ -65,6 +75,9 @@ export class FrontendRuntimeContext extends Frc {
return new Promise((resolve, reject) => setInner(resolve, reject));
};
await setTokenValue();
Object.assign(data, {
v: this.application.getVersion(),
});
return data;
}
getApplicationId() {

View File

@ -1,5 +1,15 @@
// 本文件为自动编译产生,请勿直接修改
const i18ns = [
{
id: "c0ce3f84c8cd6f70457b7e57a8ac8b8f",
namespace: "oak-general-business-c-application-detail",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/application/detail",
data: {
"whole": "共%{count}项"
}
},
{
id: "2ebe552614a81e57fa1a1c21b5dc84f8",
namespace: "oak-general-business-c-application-panel",
@ -249,6 +259,19 @@ const i18ns = [
"login": "登录管理"
}
},
{
id: "819147f0564533e19aacf71c13d6f366",
namespace: "oak-general-business-c-system-upsert",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/system/upsert",
data: {
"tips": {
"isSuper": "超级系统属性可能影响程序的运行逻辑,请谨慎修改",
"oldestVersion": "系统能兼容的最低应用版本号格式为X.X.X"
}
}
},
{
id: "7bcbb4dbb525e9a575095102f673f7ba",
namespace: "oak-general-business-c-token-me",
@ -645,7 +668,8 @@ const i18ns = [
"mpHaveToSubscribe": "需要订阅小程序消息",
"userInfoLoading": "正在加载用户信息",
"applicationLoading": "应用正在初始化",
"uploadFailed": "上传失败"
"uploadFailed": "上传失败",
"illegalVersionData": "版本号必须是x.x.x的形式"
}
}
];

View File

@ -75,6 +75,7 @@ export type NativeConfig = {
port: string;
};
};
type Versions = string[];
export interface Schema extends EntityShape {
name: String<32>;
description?: Text;
@ -82,6 +83,10 @@ export interface Schema extends EntityShape {
system: System;
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style;
dangerousVersions: Versions;
warningVersions: Versions;
soaVersion: String<12>;
sessions?: Session[];
domain?: Domain;
}
export {};

View File

@ -12,6 +12,9 @@ const entityDesc = {
style: '样式',
sessions: '会话',
domain: '域名',
dangerousVersions: '强制升级版本',
warningVersions: '建议升级版本',
soaVersion: '最新发布版本'
},
v: {
type: {

View File

@ -7,6 +7,7 @@ export interface Schema extends EntityShape {
name: String<32>;
description?: Text;
config: Config;
oldestVersion?: String<32>;
style?: Style;
entity?: String<32>;
entityId?: String<64>;

View File

@ -10,6 +10,7 @@ export const entityDesc = {
style: '样式',
entity: '关联对象',
entityId: '关联对象id',
oldestVersion: '支持app最低版本'
},
r: {
owner: '拥有者',

View File

@ -10,6 +10,7 @@ export interface Schema extends EntityShape {
config: Config;
platform?: Platform;
folder?: String<16>;
oldestVersion?: String<32>;
super?: Boolean;
style?: Style;
entity?: String<32>;

View File

@ -13,6 +13,7 @@ export const entityDesc = {
style: '样式',
entity: '关联对象',
entityId: '关联对象id',
oldestVersion: '支持app最低版本'
},
},
}

View File

@ -4,16 +4,17 @@ import { Feature } from 'oak-frontend-base/es/types/Feature';
import { EntityDict } from '../oak-app-domain';
import { MediaType, MediaVideoDescription } from '../types/WeChat';
export declare class Application<ED extends EntityDict> extends Feature {
private version;
private applicationId?;
private application?;
private cache;
private storage;
private projection;
private sensitiveEntities;
constructor(cache: Cache<ED>, storage: LocalStorage);
constructor(cache: Cache<ED>, storage: LocalStorage, version: string);
private getApplicationFromCache;
private loadApplicationInfo;
initialize(domain: string, appId?: string | null, projection?: EntityDict['application']['Projection']): Promise<void>;
initialize(version: string, domain: string, appId?: string | null, projection?: EntityDict['application']['Projection']): Promise<void>;
getApplication(): Partial<ED["application"]["Schema"]>;
getApplicationId(allowUnInitialized?: boolean): string | undefined;
uploadWechatMedia(params: {
@ -23,4 +24,5 @@ export declare class Application<ED extends EntityDict> extends Feature {
isPermanent?: boolean;
description?: MediaVideoDescription;
}): Promise<any>;
getVersion(): string;
}

View File

@ -5,15 +5,17 @@ import { traverseProjection } from 'oak-domain/lib/utils/projection';
import { applicationProjection } from '../types/Projection';
import { OakApplicationLoadingException, } from '../types/Exception';
export class Application extends Feature {
version;
applicationId;
application;
cache;
storage;
projection;
sensitiveEntities = [];
constructor(cache, storage) {
constructor(cache, storage, version) {
super();
this.cache = cache;
this.version = version;
this.storage = storage;
this.projection = cloneDeep(applicationProjection);
// this.application做一层缓存有时候更新了一些相关的属性还是要更新的
@ -50,7 +52,7 @@ export class Application extends Feature {
this.application = data[0];
return this.application;
}
async loadApplicationInfo(domain) {
async loadApplicationInfo(version, domain) {
let applicationId;
let appType = 'web';
if (process.env.OAK_PLATFORM === 'wechatMp') {
@ -65,6 +67,7 @@ export class Application extends Feature {
}
}
const { result } = await this.cache.exec('getApplication', {
version,
type: appType,
domain,
data: this.projection,
@ -79,16 +82,17 @@ export class Application extends Feature {
// }
this.publish();
}
async initialize(domain, appId, projection) {
async initialize(version, domain, appId, projection) {
// const applicationId = await this.storage.load(LOCAL_STORAGE_KEYS.appId);
// this.applicationId = applicationId;
//接收外层注入的projection
this.version = version;
this.projection = merge(this.projection, projection);
if (process.env.NODE_ENV === 'development' && appId) {
// development环境下允许注入一个线上的appId
this.applicationId = appId;
}
return await this.loadApplicationInfo(domain);
return await this.loadApplicationInfo(version, domain);
}
getApplication() {
if (this.applicationId === undefined) {
@ -119,4 +123,7 @@ export class Application extends Feature {
const callBack = await this.cache.exec('uploadWechatMedia', formData);
return callBack.result;
}
getVersion() {
return this.version;
}
}

View File

@ -8,8 +8,9 @@ import { WechatMenu } from './wechatMenu';
import { WechatPublicTag } from './wechatPublicTag';
import { UserWechatPublicTag } from './userWechatPublicTag';
import Theme from './theme';
import { oakGetPackageJsonVersion } from '../utils/appVersion';
export function create(basicFeatures) {
const application = new Application(basicFeatures.cache, basicFeatures.localStorage);
const application = new Application(basicFeatures.cache, basicFeatures.localStorage, oakGetPackageJsonVersion());
const token = new Token(basicFeatures.cache, basicFeatures.localStorage, basicFeatures.environment, application);
const wechatMenu = new WechatMenu(basicFeatures.cache, basicFeatures.localStorage);
const wechatPublicTag = new WechatPublicTag(basicFeatures.cache, basicFeatures.localStorage);
@ -33,7 +34,7 @@ export function create(basicFeatures) {
};
}
export async function initialize(features, access, config, clazzes) {
await features.application.initialize(access.http.hostname, undefined, config?.applicationExtraProjection);
await features.application.initialize(oakGetPackageJsonVersion(), access.http.hostname, undefined, config?.applicationExtraProjection);
if (process.env.OAK_PLATFORM === 'web') {
features.wechatSdk.setLandingUrl(window.location.href);
}

View File

@ -22,5 +22,6 @@
"mpHaveToSubscribe": "需要订阅小程序消息",
"userInfoLoading": "正在加载用户信息",
"applicationLoading": "应用正在初始化",
"uploadFailed": "上传失败"
"uploadFailed": "上传失败",
"illegalVersionData": "版本号必须是x.x.x的形式"
}

View File

@ -28,6 +28,21 @@ export const desc = {
style: {
type: "object"
},
dangerousVersions: {
notNull: true,
type: "object"
},
warningVersions: {
notNull: true,
type: "object"
},
soaVersion: {
notNull: true,
type: "varchar",
params: {
length: 12
}
},
domainId: {
type: "ref",
ref: "domain"

View File

@ -75,6 +75,7 @@ export type NativeConfig = {
port: string;
};
};
type Versions = string[];
export type OpSchema = EntityShape & {
name: String<32>;
description?: Text | null;
@ -82,6 +83,9 @@ export type OpSchema = EntityShape & {
systemId: ForeignKey<"system">;
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style | null;
dangerousVersions: Versions;
warningVersions: Versions;
soaVersion: String<12>;
domainId?: ForeignKey<"domain"> | null;
} & {
[A in ExpressionKey]?: any;
@ -98,6 +102,9 @@ export type OpFilter = {
systemId: Q_StringValue;
config: JsonFilter<WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig>;
style: JsonFilter<Style>;
dangerousVersions: JsonFilter<Versions>;
warningVersions: JsonFilter<Versions>;
soaVersion: Q_StringValue;
domainId: Q_StringValue;
} & ExprOp<OpAttr | string>;
export type OpProjection = {
@ -113,6 +120,9 @@ export type OpProjection = {
systemId?: number;
config?: number | JsonProjection<WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig>;
style?: number | JsonProjection<Style>;
dangerousVersions?: number | JsonProjection<Versions>;
warningVersions?: number | JsonProjection<Versions>;
soaVersion?: number;
domainId?: number;
} & Partial<ExprOp<OpAttr | string>>;
export type OpSortAttr = Partial<{
@ -124,7 +134,11 @@ export type OpSortAttr = Partial<{
description: number;
type: number;
style: number;
dangerousVersions: number;
warningVersions: number;
soaVersion: number;
[k: string]: any;
} | ExprOp<OpAttr | string>>;
export type OpAction = OakMakeAction<GenericAction | string>;
export type OpUpdateAction = "update" | string;
export {};

View File

@ -8,7 +8,10 @@
"config": "设置",
"style": "样式",
"sessions": "会话",
"domain": "域名"
"domain": "域名",
"dangerousVersions": "强制升级版本",
"warningVersions": "建议升级版本",
"soaVersion": "最新发布版本"
},
"v": {
"type": {

View File

@ -15,6 +15,12 @@ export const desc = {
notNull: true,
type: "object"
},
oldestVersion: {
type: "varchar",
params: {
length: 32
}
},
style: {
type: "object"
},

View File

@ -9,6 +9,7 @@ export type OpSchema = EntityShape & {
name: String<32>;
description?: Text | null;
config: Config;
oldestVersion?: String<32> | null;
style?: Style | null;
entity?: String<32> | null;
entityId?: String<64> | null;
@ -24,6 +25,7 @@ export type OpFilter = {
name: Q_StringValue;
description: Q_StringValue;
config: JsonFilter<Config>;
oldestVersion: Q_StringValue;
style: JsonFilter<Style>;
entity: Q_StringValue;
entityId: Q_StringValue;
@ -38,6 +40,7 @@ export type OpProjection = {
name?: number;
description?: number;
config?: number | JsonProjection<Config>;
oldestVersion?: number;
style?: number | JsonProjection<Style>;
entity?: number;
entityId?: number;
@ -50,6 +53,7 @@ export type OpSortAttr = Partial<{
name: number;
description: number;
config: number;
oldestVersion: number;
style: number;
entity: number;
entityId: number;

View File

@ -6,7 +6,8 @@
"config": "设置",
"style": "样式",
"entity": "关联对象",
"entityId": "关联对象id"
"entityId": "关联对象id",
"oldestVersion": "支持app最低版本"
},
"r": {
"owner": "拥有者",

View File

@ -25,6 +25,12 @@ export const desc = {
length: 16
}
},
oldestVersion: {
type: "varchar",
params: {
length: 32
}
},
super: {
type: "boolean"
},

View File

@ -11,6 +11,7 @@ export type OpSchema = EntityShape & {
config: Config;
platformId?: ForeignKey<"platform"> | null;
folder?: String<16> | null;
oldestVersion?: String<32> | null;
super?: Boolean | null;
style?: Style | null;
entity?: String<32> | null;
@ -29,6 +30,7 @@ export type OpFilter = {
config: JsonFilter<Config>;
platformId: Q_StringValue;
folder: Q_StringValue;
oldestVersion: Q_StringValue;
super: Q_BooleanValue;
style: JsonFilter<Style>;
entity: Q_StringValue;
@ -46,6 +48,7 @@ export type OpProjection = {
config?: number | JsonProjection<Config>;
platformId?: number;
folder?: number;
oldestVersion?: number;
super?: number;
style?: number | JsonProjection<Style>;
entity?: number;
@ -60,6 +63,7 @@ export type OpSortAttr = Partial<{
description: number;
config: number;
folder: number;
oldestVersion: number;
super: number;
style: number;
entity: number;

View File

@ -9,6 +9,7 @@
"folder": "代码目录名",
"style": "样式",
"entity": "关联对象",
"entityId": "关联对象id"
"entityId": "关联对象id",
"oldestVersion": "支持app最低版本"
}
}

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -14,7 +14,7 @@ export declare function createToDo<ED extends EntityDict & BaseEntityDict, T ext
redirectTo: EntityDict['toDo']['OpSchema']['redirectTo'];
entity: any;
entityId: string;
}, userIds?: string[]): Promise<0 | 1>;
}, userIds?: string[]): Promise<1 | 0>;
/**
* todo例程entity对象上进行action操作时filtertodo完成
* entity的action的后trigger中调用

View File

@ -143,6 +143,9 @@ export const applicationProjection = {
type: 1,
systemId: 1,
style: 1,
dangerousVersions: 1,
warningVersions: 1,
soaVersion: 1,
description: 1,
system: {
id: 1,
@ -150,6 +153,7 @@ export const applicationProjection = {
config: 1,
platformId: 1,
style: 1,
oldestVersion: 1,
super: 1,
entity: 1,
entityId: 1,
@ -158,6 +162,7 @@ export const applicationProjection = {
config: 1,
style: 1,
entity: 1,
oldestVersion: 1,
entityId: 1,
},
domain$system: {

1
es/utils/appVersion.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare function oakGetPackageJsonVersion(): string;

4
es/utils/appVersion.js Normal file
View File

@ -0,0 +1,4 @@
// 命名为这个函数将在编译时被注入项目根目录的package.json中的version
export function oakGetPackageJsonVersion() {
return '1.0.0';
}

View File

@ -89,6 +89,7 @@ export type AspectDict<ED extends EntityDict> = {
type: 'login' | 'changePassword' | 'confirm';
}, context: BackendRuntimeContext<ED>) => Promise<string>;
getApplication: (params: {
version: string;
type: AppType;
domain: string;
data: ED['application']['Projection'];

View File

@ -5,6 +5,7 @@ import { WebEnv } from 'oak-domain/lib/types/Environment';
import { File } from 'formidable';
import { BRC } from '../types/RuntimeCxt';
export declare function getApplication<ED extends EntityDict>(params: {
version: string;
type: AppType;
domain: string;
data: ED['application']['Projection'];

View File

@ -8,6 +8,8 @@ const WechatSDK_1 = tslib_1.__importDefault(require("oak-external-sdk/lib/Wechat
const fs_1 = tslib_1.__importDefault(require("fs"));
const lodash_1 = require("oak-domain/lib/utils/lodash");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const version_1 = require("oak-domain/lib/utils/version");
const Exception_1 = require("oak-domain/lib/types/Exception");
async function getApplicationByDomain(context, options) {
const { data, type, domain } = options;
let applications = await context.select('application', {
@ -43,8 +45,26 @@ async function getApplicationByDomain(context, options) {
}
return applications;
}
function checkAppVersionSafe(application, version) {
const { dangerousVersions, warningVersions, system } = application;
const { oldestVersion, platform } = system;
const { oldestVersion: pfOldestVersion } = platform || {};
const oldest = pfOldestVersion || oldestVersion;
if (oldest) {
if ((0, version_1.compareVersion)(version, oldest) < 0) {
throw new Exception_1.OakApplicationHasToUpgrade();
}
}
if (dangerousVersions && dangerousVersions.includes(version)) {
throw new Exception_1.OakApplicationHasToUpgrade();
}
(0, lodash_1.unset)(application, 'dangerousVersions');
if (warningVersions) {
application.warningVersions = warningVersions.filter(ele => ele === version);
}
}
async function getApplication(params, context) {
const { type, domain, data, appId } = params;
const { type, domain, data, appId, version } = params;
// 先找指定domain的应用如果不存在再找系统下面的domain 但无论怎么样都必须一项
const applications = await getApplicationByDomain(context, {
type,
@ -55,11 +75,13 @@ async function getApplication(params, context) {
case 'wechatMp': {
(0, assert_1.assert)(applications.length === 1, `微信小程序环境下,同一个系统必须存在唯一的【${type}】应用,域名「${domain}`);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id;
}
case 'native': {
(0, assert_1.assert)(applications.length === 1, `APP环境下同一个系统必须存在唯一的【${type}】应用,域名「${domain}`);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id;
}
case 'wechatPublic': {
@ -72,15 +94,18 @@ async function getApplication(params, context) {
});
(0, assert_1.assert)(webApplications.length === 1, `微信公众号环境下, 可以未配置公众号但必须存在web应用域名「${domain}`);
const application = webApplications[0];
checkAppVersionSafe(application, version);
return application.id;
}
(0, assert_1.assert)(applications.length === 1, `微信公众号环境下,同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名,域名「${domain}`);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id;
}
case 'web': {
(0, assert_1.assert)(applications.length === 1, `web环境下同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名,域名「${domain}`);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id;
}
default: {

View File

@ -1,6 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("oak-domain/lib/types");
const validator_1 = require("oak-domain/lib/utils/validator");
const version_1 = require("oak-domain/lib/utils/version");
function checkVersion(data) {
const { dangerousVersions, warningVersions, soaVersion } = data;
if (dangerousVersions && dangerousVersions.find(ele => !(0, version_1.isVersion)(ele))) {
throw new types_1.OakInputIllegalException('application', ['dangerousVersions'], 'error::illegalVersionData');
}
if (warningVersions && warningVersions.find(ele => !(0, version_1.isVersion)(ele))) {
throw new types_1.OakInputIllegalException('application', ['warningVersions'], 'error::illegalVersionData');
}
if (soaVersion && !(0, version_1.isVersion)(soaVersion)) {
throw new types_1.OakInputIllegalException('application', ['soaVersion'], 'error::illegalVersionData');
}
}
const checkers = [
{
type: 'data',
@ -16,6 +30,7 @@ const checkers = [
};
if (data instanceof Array) {
data.forEach((ele) => {
checkVersion(ele);
(0, validator_1.checkAttributesNotNull)('application', ele, [
'name',
'type',
@ -25,6 +40,7 @@ const checkers = [
});
}
else {
checkVersion(data);
(0, validator_1.checkAttributesNotNull)('application', data, [
'name',
'type',
@ -35,5 +51,13 @@ const checkers = [
return;
},
},
{
type: 'data',
action: 'update',
entity: 'application',
checker(data) {
checkVersion(data);
}
}
];
exports.default = checkers;

View File

@ -1,2 +1,2 @@
declare const checkers: (import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>>)[];
declare const checkers: (import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "system", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "platform", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>>)[];
export default checkers;

View File

@ -12,6 +12,8 @@ const wechatPublicTag_1 = tslib_1.__importDefault(require("./wechatPublicTag"));
const message_1 = tslib_1.__importDefault(require("./message"));
const parasite_1 = tslib_1.__importDefault(require("./parasite"));
const applicationPassport_1 = tslib_1.__importDefault(require("./applicationPassport"));
const system_1 = tslib_1.__importDefault(require("./system"));
const platform_1 = tslib_1.__importDefault(require("./platform"));
const checkers = [
...mobile_1.default,
...address_1.default,
@ -24,5 +26,7 @@ const checkers = [
...message_1.default,
...parasite_1.default,
...applicationPassport_1.default,
...system_1.default,
...platform_1.default
];
exports.default = checkers;

View File

@ -1,6 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("oak-domain/lib/types");
const validator_1 = require("oak-domain/lib/utils/validator");
const version_1 = require("oak-domain/lib/utils/version");
const checkers = [
{
type: 'data',
@ -17,11 +19,17 @@ const checkers = [
if (data instanceof Array) {
data.forEach((ele) => {
(0, validator_1.checkAttributesNotNull)('platform', ele, ['name']);
if (ele.oldestVersion && !(0, version_1.isVersion)(ele.oldestVersion)) {
throw new types_1.OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData', 'oak-general-business');
}
setData(ele);
});
}
else {
(0, validator_1.checkAttributesNotNull)('platform', data, ['name']);
if (data.oldestVersion && !(0, version_1.isVersion)(data.oldestVersion)) {
throw new types_1.OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData', 'oak-general-business');
}
setData(data);
}
return;

View File

@ -1,6 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("oak-domain/lib/types");
const validator_1 = require("oak-domain/lib/utils/validator");
const version_1 = require("oak-domain/lib/utils/version");
const checkers = [
{
type: 'data',
@ -22,15 +24,32 @@ const checkers = [
if (data instanceof Array) {
data.forEach((ele) => {
(0, validator_1.checkAttributesNotNull)('system', ele, ['name', 'platformId']);
if (ele.oldestVersion && !(0, version_1.isVersion)(ele.oldestVersion)) {
throw new types_1.OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData', 'oak-general-business');
}
setData(ele);
});
}
else {
(0, validator_1.checkAttributesNotNull)('system', data, ['name', 'platformId']);
if (data.oldestVersion && !(0, version_1.isVersion)(data.oldestVersion)) {
throw new types_1.OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData', 'oak-general-business');
}
setData(data);
}
return;
},
},
{
type: 'data',
action: 'update',
entity: 'system',
checker(data) {
const { oldestVersion } = data;
if (oldestVersion && !(0, version_1.isVersion)(oldestVersion)) {
throw new types_1.OakInputIllegalException('system', ['oldestVersion'], 'error::illegalVersionData');
}
}
}
];
exports.default = checkers;

View File

@ -2,6 +2,7 @@ import { RuntimeContext } from './RuntimeContext';
import { EntityDict } from '../oak-app-domain';
import { SerializedData } from './FrontendRuntimeContext';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { OakException } from 'oak-domain/lib/types/Exception';
import { BackendRuntimeContext as BRC } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
/**
* general数据结构要求的后台上下文
@ -14,15 +15,16 @@ export declare abstract class BackendRuntimeContext<ED extends EntityDict & Base
protected rootMode?: boolean;
private userId?;
protected platformManager?: boolean;
protected appVersion?: string;
protected applicationProjection: EntityDict['user']['Projection'];
refineOpRecords(): Promise<void>;
setPlatformManager(tokenValue?: string, userId?: string): Promise<void>;
setTokenValue(tokenValue?: string, userId?: string): Promise<void>;
setApplication(appId: string): Promise<void>;
initialize(data?: SerializedData, later?: boolean): Promise<void>;
getApplicationId(): ED["application"]["Schema"]["id"] | undefined;
getSystemId(): ED["application"]["Schema"]["systemId"] | undefined;
getApplication(): Partial<ED["application"]["Schema"]> | undefined;
getApplicationId<P extends true | undefined>(allowNull?: P): P extends undefined ? (string | undefined) : string;
getSystemId<P extends true | undefined>(allowNull?: P): P extends undefined ? (string | undefined) : string;
getApplication<P extends true | undefined>(allowNull?: P): P extends undefined ? (Partial<ED['application']['Schema']> | undefined) : Partial<ED['application']['Schema']>;
openRootMode(): () => void;
getTokenValue(allowUnloggedIn?: boolean): "oak-root-token" | ED["token"]["Schema"]["value"] | undefined;
getToken(allowUnloggedIn?: boolean): Partial<ED["token"]["Schema"]> | undefined;
@ -38,5 +40,6 @@ export declare abstract class BackendRuntimeContext<ED extends EntityDict & Base
* http://www.xxx.com/oak-api
*/
composeAccessPath(): string;
tryDeduceException(err: Error): Promise<OakException<any> | void>;
}
export default BackendRuntimeContext;

View File

@ -6,12 +6,14 @@ 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");
/**
* general数据结构要求的后台上下文
*/
@ -23,6 +25,7 @@ class BackendRuntimeContext extends BackendRuntimeContext_1.BackendRuntimeContex
rootMode;
userId;
platformManager;
appVersion;
applicationProjection = (0, lodash_1.cloneDeep)(Projection_1.applicationProjection);
async refineOpRecords() {
const isRoot = this.isRoot();
@ -262,7 +265,8 @@ class BackendRuntimeContext extends BackendRuntimeContext_1.BackendRuntimeContex
if (data) {
const closeRootMode = this.openRootMode();
try {
const { a: appId, t: tokenValue, rm, userId } = data;
const { a: appId, t: tokenValue, rm, userId, v } = data;
this.appVersion = v;
const promises = [];
if (appId) {
promises.push(this.setApplication(appId));
@ -288,13 +292,31 @@ class BackendRuntimeContext extends BackendRuntimeContext_1.BackendRuntimeContex
this.rootMode = true;
}
}
getApplicationId() {
getApplicationId(allowNull) {
if (!allowNull) {
if (!this.application) {
throw new Exception_1.OakApplicationLoadingException();
}
return this.application.id;
}
return this.application?.id;
}
getSystemId() {
getSystemId(allowNull) {
if (!allowNull) {
if (!this.application) {
throw new Exception_1.OakApplicationLoadingException();
}
return this.application.systemId;
}
return this.application?.systemId;
}
getApplication() {
getApplication(allowNull) {
if (!allowNull) {
if (!this.application) {
throw new Exception_1.OakApplicationLoadingException();
}
return this.application;
}
return this.application;
}
openRootMode() {
@ -345,6 +367,7 @@ class BackendRuntimeContext extends BackendRuntimeContext_1.BackendRuntimeContex
a: this.application?.id,
rm: this.rootMode,
userId: this.getCurrentUserId(true),
v: this.appVersion,
};
}
isRoot() {
@ -397,6 +420,16 @@ class BackendRuntimeContext extends BackendRuntimeContext_1.BackendRuntimeContex
const [domain] = domains;
return (0, domain_1.composeServerUrl)(domain);
}
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;
;

View File

@ -10,6 +10,7 @@ export interface SerializedData extends Fsd {
t?: string;
userId?: string;
rm?: boolean;
v?: string;
}
export declare abstract class FrontendRuntimeContext<ED extends EntityDict & BaseEntityDict> extends Frc<ED> implements RuntimeContext {
private application;

View File

@ -20,6 +20,11 @@ class FrontendRuntimeContext extends FrontendRuntimeContext_1.FrontendRuntimeCon
// appId必须要取到不能失败
const setInner = (resolve, reject) => {
try {
if (!this.application) {
// 有可能在系统初始化的时候调用this.application还没建立
resolve(undefined);
return;
}
const appId = this.application.getApplicationId();
(0, assert_1.assert)(appId);
Object.assign(data, {
@ -45,6 +50,11 @@ class FrontendRuntimeContext extends FrontendRuntimeContext_1.FrontendRuntimeCon
const setTokenValue = async () => {
const setInner = (resolve, reject) => {
try {
if (!this.token) {
// 有可能在系统初始化的时候调用this.token还没建立
resolve(undefined);
return;
}
const tokenValue = this.token.getTokenValue();
if (tokenValue) {
Object.assign(data, {
@ -68,6 +78,9 @@ class FrontendRuntimeContext extends FrontendRuntimeContext_1.FrontendRuntimeCon
return new Promise((resolve, reject) => setInner(resolve, reject));
};
await setTokenValue();
Object.assign(data, {
v: this.application.getVersion(),
});
return data;
}
getApplicationId() {

View File

@ -2,6 +2,16 @@
// 本文件为自动编译产生,请勿直接修改
Object.defineProperty(exports, "__esModule", { value: true });
const i18ns = [
{
id: "c0ce3f84c8cd6f70457b7e57a8ac8b8f",
namespace: "oak-general-business-c-application-detail",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/application/detail",
data: {
"whole": "共%{count}项"
}
},
{
id: "2ebe552614a81e57fa1a1c21b5dc84f8",
namespace: "oak-general-business-c-application-panel",
@ -251,6 +261,19 @@ const i18ns = [
"login": "登录管理"
}
},
{
id: "819147f0564533e19aacf71c13d6f366",
namespace: "oak-general-business-c-system-upsert",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/system/upsert",
data: {
"tips": {
"isSuper": "超级系统属性可能影响程序的运行逻辑,请谨慎修改",
"oldestVersion": "系统能兼容的最低应用版本号格式为X.X.X"
}
}
},
{
id: "7bcbb4dbb525e9a575095102f673f7ba",
namespace: "oak-general-business-c-token-me",
@ -647,7 +670,8 @@ const i18ns = [
"mpHaveToSubscribe": "需要订阅小程序消息",
"userInfoLoading": "正在加载用户信息",
"applicationLoading": "应用正在初始化",
"uploadFailed": "上传失败"
"uploadFailed": "上传失败",
"illegalVersionData": "版本号必须是x.x.x的形式"
}
}
];

View File

@ -75,6 +75,7 @@ export type NativeConfig = {
port: string;
};
};
type Versions = string[];
export interface Schema extends EntityShape {
name: String<32>;
description?: Text;
@ -82,6 +83,10 @@ export interface Schema extends EntityShape {
system: System;
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style;
dangerousVersions: Versions;
warningVersions: Versions;
soaVersion: String<12>;
sessions?: Session[];
domain?: Domain;
}
export {};

View File

@ -14,6 +14,9 @@ const entityDesc = {
style: '样式',
sessions: '会话',
domain: '域名',
dangerousVersions: '强制升级版本',
warningVersions: '建议升级版本',
soaVersion: '最新发布版本'
},
v: {
type: {

View File

@ -7,6 +7,7 @@ export interface Schema extends EntityShape {
name: String<32>;
description?: Text;
config: Config;
oldestVersion?: String<32>;
style?: Style;
entity?: String<32>;
entityId?: String<64>;

View File

@ -13,6 +13,7 @@ exports.entityDesc = {
style: '样式',
entity: '关联对象',
entityId: '关联对象id',
oldestVersion: '支持app最低版本'
},
r: {
owner: '拥有者',

View File

@ -10,6 +10,7 @@ export interface Schema extends EntityShape {
config: Config;
platform?: Platform;
folder?: String<16>;
oldestVersion?: String<32>;
super?: Boolean;
style?: Style;
entity?: String<32>;

View File

@ -16,6 +16,7 @@ exports.entityDesc = {
style: '样式',
entity: '关联对象',
entityId: '关联对象id',
oldestVersion: '支持app最低版本'
},
},
}

View File

@ -4,16 +4,17 @@ import { Feature } from 'oak-frontend-base/es/types/Feature';
import { EntityDict } from '../oak-app-domain';
import { MediaType, MediaVideoDescription } from '../types/WeChat';
export declare class Application<ED extends EntityDict> extends Feature {
private version;
private applicationId?;
private application?;
private cache;
private storage;
private projection;
private sensitiveEntities;
constructor(cache: Cache<ED>, storage: LocalStorage);
constructor(cache: Cache<ED>, storage: LocalStorage, version: string);
private getApplicationFromCache;
private loadApplicationInfo;
initialize(domain: string, appId?: string | null, projection?: EntityDict['application']['Projection']): Promise<void>;
initialize(version: string, domain: string, appId?: string | null, projection?: EntityDict['application']['Projection']): Promise<void>;
getApplication(): Partial<ED["application"]["Schema"]>;
getApplicationId(allowUnInitialized?: boolean): string | undefined;
uploadWechatMedia(params: {
@ -23,4 +24,5 @@ export declare class Application<ED extends EntityDict> extends Feature {
isPermanent?: boolean;
description?: MediaVideoDescription;
}): Promise<any>;
getVersion(): string;
}

View File

@ -8,15 +8,17 @@ const projection_1 = require("oak-domain/lib/utils/projection");
const Projection_1 = require("../types/Projection");
const Exception_1 = require("../types/Exception");
class Application extends Feature_1.Feature {
version;
applicationId;
application;
cache;
storage;
projection;
sensitiveEntities = [];
constructor(cache, storage) {
constructor(cache, storage, version) {
super();
this.cache = cache;
this.version = version;
this.storage = storage;
this.projection = (0, lodash_1.cloneDeep)(Projection_1.applicationProjection);
// this.application做一层缓存有时候更新了一些相关的属性还是要更新的
@ -53,7 +55,7 @@ class Application extends Feature_1.Feature {
this.application = data[0];
return this.application;
}
async loadApplicationInfo(domain) {
async loadApplicationInfo(version, domain) {
let applicationId;
let appType = 'web';
if (process.env.OAK_PLATFORM === 'wechatMp') {
@ -68,6 +70,7 @@ class Application extends Feature_1.Feature {
}
}
const { result } = await this.cache.exec('getApplication', {
version,
type: appType,
domain,
data: this.projection,
@ -82,16 +85,17 @@ class Application extends Feature_1.Feature {
// }
this.publish();
}
async initialize(domain, appId, projection) {
async initialize(version, domain, appId, projection) {
// const applicationId = await this.storage.load(LOCAL_STORAGE_KEYS.appId);
// this.applicationId = applicationId;
//接收外层注入的projection
this.version = version;
this.projection = (0, lodash_1.merge)(this.projection, projection);
if (process.env.NODE_ENV === 'development' && appId) {
// development环境下允许注入一个线上的appId
this.applicationId = appId;
}
return await this.loadApplicationInfo(domain);
return await this.loadApplicationInfo(version, domain);
}
getApplication() {
if (this.applicationId === undefined) {
@ -122,5 +126,8 @@ class Application extends Feature_1.Feature {
const callBack = await this.cache.exec('uploadWechatMedia', formData);
return callBack.result;
}
getVersion() {
return this.version;
}
}
exports.Application = Application;

View File

@ -12,8 +12,9 @@ const wechatMenu_1 = require("./wechatMenu");
const wechatPublicTag_1 = require("./wechatPublicTag");
const userWechatPublicTag_1 = require("./userWechatPublicTag");
const theme_1 = tslib_1.__importDefault(require("./theme"));
const appVersion_1 = require("../utils/appVersion");
function create(basicFeatures) {
const application = new application_1.Application(basicFeatures.cache, basicFeatures.localStorage);
const application = new application_1.Application(basicFeatures.cache, basicFeatures.localStorage, (0, appVersion_1.oakGetPackageJsonVersion)());
const token = new token_1.Token(basicFeatures.cache, basicFeatures.localStorage, basicFeatures.environment, application);
const wechatMenu = new wechatMenu_1.WechatMenu(basicFeatures.cache, basicFeatures.localStorage);
const wechatPublicTag = new wechatPublicTag_1.WechatPublicTag(basicFeatures.cache, basicFeatures.localStorage);
@ -38,7 +39,7 @@ function create(basicFeatures) {
}
exports.create = create;
async function initialize(features, access, config, clazzes) {
await features.application.initialize(access.http.hostname, undefined, config?.applicationExtraProjection);
await features.application.initialize((0, appVersion_1.oakGetPackageJsonVersion)(), access.http.hostname, undefined, config?.applicationExtraProjection);
if (process.env.OAK_PLATFORM === 'web') {
features.wechatSdk.setLandingUrl(window.location.href);
}

View File

@ -22,5 +22,6 @@
"mpHaveToSubscribe": "需要订阅小程序消息",
"userInfoLoading": "正在加载用户信息",
"applicationLoading": "应用正在初始化",
"uploadFailed": "上传失败"
"uploadFailed": "上传失败",
"illegalVersionData": "版本号必须是x.x.x的形式"
}

View File

@ -31,6 +31,21 @@ exports.desc = {
style: {
type: "object"
},
dangerousVersions: {
notNull: true,
type: "object"
},
warningVersions: {
notNull: true,
type: "object"
},
soaVersion: {
notNull: true,
type: "varchar",
params: {
length: 12
}
},
domainId: {
type: "ref",
ref: "domain"

View File

@ -75,6 +75,7 @@ export type NativeConfig = {
port: string;
};
};
type Versions = string[];
export type OpSchema = EntityShape & {
name: String<32>;
description?: Text | null;
@ -82,6 +83,9 @@ export type OpSchema = EntityShape & {
systemId: ForeignKey<"system">;
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style | null;
dangerousVersions: Versions;
warningVersions: Versions;
soaVersion: String<12>;
domainId?: ForeignKey<"domain"> | null;
} & {
[A in ExpressionKey]?: any;
@ -98,6 +102,9 @@ export type OpFilter = {
systemId: Q_StringValue;
config: JsonFilter<WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig>;
style: JsonFilter<Style>;
dangerousVersions: JsonFilter<Versions>;
warningVersions: JsonFilter<Versions>;
soaVersion: Q_StringValue;
domainId: Q_StringValue;
} & ExprOp<OpAttr | string>;
export type OpProjection = {
@ -113,6 +120,9 @@ export type OpProjection = {
systemId?: number;
config?: number | JsonProjection<WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig>;
style?: number | JsonProjection<Style>;
dangerousVersions?: number | JsonProjection<Versions>;
warningVersions?: number | JsonProjection<Versions>;
soaVersion?: number;
domainId?: number;
} & Partial<ExprOp<OpAttr | string>>;
export type OpSortAttr = Partial<{
@ -124,7 +134,11 @@ export type OpSortAttr = Partial<{
description: number;
type: number;
style: number;
dangerousVersions: number;
warningVersions: number;
soaVersion: number;
[k: string]: any;
} | ExprOp<OpAttr | string>>;
export type OpAction = OakMakeAction<GenericAction | string>;
export type OpUpdateAction = "update" | string;
export {};

View File

@ -8,7 +8,10 @@
"config": "设置",
"style": "样式",
"sessions": "会话",
"domain": "域名"
"domain": "域名",
"dangerousVersions": "强制升级版本",
"warningVersions": "建议升级版本",
"soaVersion": "最新发布版本"
},
"v": {
"type": {

View File

@ -18,6 +18,12 @@ exports.desc = {
notNull: true,
type: "object"
},
oldestVersion: {
type: "varchar",
params: {
length: 32
}
},
style: {
type: "object"
},

View File

@ -9,6 +9,7 @@ export type OpSchema = EntityShape & {
name: String<32>;
description?: Text | null;
config: Config;
oldestVersion?: String<32> | null;
style?: Style | null;
entity?: String<32> | null;
entityId?: String<64> | null;
@ -24,6 +25,7 @@ export type OpFilter = {
name: Q_StringValue;
description: Q_StringValue;
config: JsonFilter<Config>;
oldestVersion: Q_StringValue;
style: JsonFilter<Style>;
entity: Q_StringValue;
entityId: Q_StringValue;
@ -38,6 +40,7 @@ export type OpProjection = {
name?: number;
description?: number;
config?: number | JsonProjection<Config>;
oldestVersion?: number;
style?: number | JsonProjection<Style>;
entity?: number;
entityId?: number;
@ -50,6 +53,7 @@ export type OpSortAttr = Partial<{
name: number;
description: number;
config: number;
oldestVersion: number;
style: number;
entity: number;
entityId: number;

View File

@ -6,7 +6,8 @@
"config": "设置",
"style": "样式",
"entity": "关联对象",
"entityId": "关联对象id"
"entityId": "关联对象id",
"oldestVersion": "支持app最低版本"
},
"r": {
"owner": "拥有者",

View File

@ -28,6 +28,12 @@ exports.desc = {
length: 16
}
},
oldestVersion: {
type: "varchar",
params: {
length: 32
}
},
super: {
type: "boolean"
},

View File

@ -11,6 +11,7 @@ export type OpSchema = EntityShape & {
config: Config;
platformId?: ForeignKey<"platform"> | null;
folder?: String<16> | null;
oldestVersion?: String<32> | null;
super?: Boolean | null;
style?: Style | null;
entity?: String<32> | null;
@ -29,6 +30,7 @@ export type OpFilter = {
config: JsonFilter<Config>;
platformId: Q_StringValue;
folder: Q_StringValue;
oldestVersion: Q_StringValue;
super: Q_BooleanValue;
style: JsonFilter<Style>;
entity: Q_StringValue;
@ -46,6 +48,7 @@ export type OpProjection = {
config?: number | JsonProjection<Config>;
platformId?: number;
folder?: number;
oldestVersion?: number;
super?: number;
style?: number | JsonProjection<Style>;
entity?: number;
@ -60,6 +63,7 @@ export type OpSortAttr = Partial<{
description: number;
config: number;
folder: number;
oldestVersion: number;
super: number;
style: number;
entity: number;

View File

@ -9,6 +9,7 @@
"folder": "代码目录名",
"style": "样式",
"entity": "关联对象",
"entityId": "关联对象id"
"entityId": "关联对象id",
"oldestVersion": "支持app最低版本"
}
}

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -146,6 +146,9 @@ exports.applicationProjection = {
type: 1,
systemId: 1,
style: 1,
dangerousVersions: 1,
warningVersions: 1,
soaVersion: 1,
description: 1,
system: {
id: 1,
@ -153,6 +156,7 @@ exports.applicationProjection = {
config: 1,
platformId: 1,
style: 1,
oldestVersion: 1,
super: 1,
entity: 1,
entityId: 1,
@ -161,6 +165,7 @@ exports.applicationProjection = {
config: 1,
style: 1,
entity: 1,
oldestVersion: 1,
entityId: 1,
},
domain$system: {

1
lib/utils/appVersion.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare function oakGetPackageJsonVersion(): string;

8
lib/utils/appVersion.js Normal file
View File

@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.oakGetPackageJsonVersion = void 0;
// 命名为这个函数将在编译时被注入项目根目录的package.json中的version
function oakGetPackageJsonVersion() {
return '1.0.0';
}
exports.oakGetPackageJsonVersion = oakGetPackageJsonVersion;

View File

@ -153,6 +153,7 @@ export type AspectDict<ED extends EntityDict> = {
) => Promise<string>;
getApplication: (
params: {
version: string;
type: AppType;
domain: string;
data: ED['application']['Projection'];

View File

@ -14,9 +14,11 @@ import WechatSDK, {
} from 'oak-external-sdk/lib/WechatSDK';
import fs from 'fs';
import { File } from 'formidable';
import { cloneDeep } from 'oak-domain/lib/utils/lodash';
import { cloneDeep, unset } from 'oak-domain/lib/utils/lodash';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { compareVersion } from 'oak-domain/lib/utils/version';
import { BRC } from '../types/RuntimeCxt';
import { OakApplicationHasToUpgrade } from 'oak-domain/lib/types/Exception';
async function getApplicationByDomain<ED extends EntityDict>(
context: BRC<ED>,
@ -70,8 +72,29 @@ async function getApplicationByDomain<ED extends EntityDict>(
return applications;
}
function checkAppVersionSafe<ED extends EntityDict>(application: Partial<ED['application']['Schema']>, version: string) {
const { dangerousVersions, warningVersions, system } = application;
const { oldestVersion, platform } = system!;
const { oldestVersion: pfOldestVersion } = platform || {};
const oldest = pfOldestVersion || oldestVersion;
if (oldest) {
if (compareVersion(version, oldest) < 0) {
throw new OakApplicationHasToUpgrade<ED>();
}
}
if (dangerousVersions && dangerousVersions.includes(version)) {
throw new OakApplicationHasToUpgrade<ED>();
}
unset(application, 'dangerousVersions');
if (warningVersions) {
application.warningVersions = warningVersions.filter(ele => ele === version);
}
}
export async function getApplication<ED extends EntityDict>(
params: {
version: string;
type: AppType;
domain: string;
data: ED['application']['Projection'];
@ -79,7 +102,7 @@ export async function getApplication<ED extends EntityDict>(
},
context: BRC<ED>
) {
const { type, domain, data, appId } = params;
const { type, domain, data, appId, version } = params;
// 先找指定domain的应用如果不存在再找系统下面的domain 但无论怎么样都必须一项
const applications = await getApplicationByDomain(context, {
@ -95,6 +118,7 @@ export async function getApplication<ED extends EntityDict>(
`微信小程序环境下,同一个系统必须存在唯一的【${type}】应用,域名「${domain}`
);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id as string;
}
case 'native': {
@ -103,6 +127,7 @@ export async function getApplication<ED extends EntityDict>(
`APP环境下同一个系统必须存在唯一的【${type}】应用,域名「${domain}`
);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id as string;
}
case 'wechatPublic': {
@ -119,6 +144,7 @@ export async function getApplication<ED extends EntityDict>(
`微信公众号环境下, 可以未配置公众号但必须存在web应用域名「${domain}`
);
const application = webApplications[0];
checkAppVersionSafe(application, version);
return application.id as string;
}
assert(
@ -126,6 +152,7 @@ export async function getApplication<ED extends EntityDict>(
`微信公众号环境下,同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名,域名「${domain}`
);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id as string;
}
case 'web': {
@ -135,6 +162,7 @@ export async function getApplication<ED extends EntityDict>(
);
const application = applications[0];
checkAppVersionSafe(application, version);
return application.id as string;
}
default: {

Some files were not shown because too many files have changed in this diff Show More