274 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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

/*
* Tencent is pleased to support the open source community by making MagicEditor available.
*
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { copySync, emptyDir, outputFileSync, readFileSync } from 'fs-extra';
import { cloneDeep } from 'lodash';
import { ActStatus, StaticPath, UiRuntimeJS } from '@src/config/config';
import ActService from '@src/service/act';
import PageService from '@src/service/page';
import type { ActBaseInfo, PageInfo, UiConfig } from '@src/typings';
import { configTransformDist, processTransConfig, serializeConfig } from '@src/utils/index';
import logger from '@src/utils/logger';
const actService = new ActService();
const pageService = new PageService();
type DivideConfig = {
pageName: string;
pageConfig: UiConfig;
};
type DivideConfigTrans = {
pageName: string;
pageConfigStr: string;
};
export default class PublishService {
/**
* 保存
* @param {ActBaseInfo} actInfo 活动信息
* @param {string} rootInfo uiconfig页面配置信息
* @returns {Res} 保存结果
*/
saveActInfo = async ({ actInfo, rootInfo }: { actInfo: ActBaseInfo; rootInfo: string }) => {
// 保存活动基础信息
await actService.update(actInfo);
try {
// 不是真正的json对象可能包含组件自定义code代码
// eslint-disable-next-line no-eval
const uiConfig = eval(`(${rootInfo})`);
// 按页面拆分 abtest等发布时处理
const divideConfigs = dividePageProcessor(uiConfig);
// 处理为保存所需格式
const pagesForSave = pageSaveProcessor(divideConfigs, actInfo);
// 保存到magic_ui_config表
await pageService.update(pagesForSave);
return { ret: 0, msg: '保存成功' };
} catch (error) {
logger.error(error);
throw new Error('保存活动失败');
}
};
/**
* 发布
* @param {number} actId 活动Id
* @param {string[]} publishPages 待发布页面
* @param {string} operator 发布人
* @returns {Res} 发布结果
*/
publish = async ({
actId,
publishPages,
operator,
}: {
actId: number;
publishPages: string[];
rootInfo: string;
operator: string;
}) => {
try {
// 查询活动基础信息
const actInfo = await actService.getActInfo(actId);
// 查询需要发布的页面
const pagesToPublish = await pageService.getPages(actId, publishPages);
let publishPagesArr: DivideConfigTrans[] = pagesToPublish.map((pageItem) => ({
pageName: pageItem.pageName,
pageConfigStr: pageItem.distCode,
}));
// 处理abtest页面
const abTestConfig: DivideConfigTrans[] = await getAbTestConfig(publishPages, actInfo.abTest, actId);
publishPagesArr = publishPagesArr.concat(abTestConfig);
// 发布
await publishToFiles(publishPagesArr);
// 更新页面状态
await pageService.update(pagesToPublish, true, operator);
// 确认页面是否全部发布
let actStatus = ActStatus.PART_PUBLISHED;
const singlePageCount = await pageService.getPagesCount(actId);
const abTestCount = actInfo.abTest.length;
const allPagesCount = singlePageCount + abTestCount;
if (allPagesCount === publishPages.length) {
actStatus = ActStatus.PUBLISHED;
}
await actService.publish(actInfo, actStatus);
return { ret: 0, msg: '发布成功' };
} catch (error) {
logger.error(error);
throw new Error('发布失败');
}
};
}
/**
* 配置信息拆分
* @param {UiConfig} uiConfig 按页面拆分后的配置数组每个都是完整的uiconfig可直接发布
* @returns {DivideConfig[]} 拆分后的页面数组
*/
const dividePageProcessor = (uiConfig: UiConfig): DivideConfig[] => {
const deployPages: DivideConfig[] = [];
// 按页面拆分uiConfig
for (let i = 0; i < uiConfig.items.length; i++) {
// 深复制一份
const pageConfig = cloneDeep(uiConfig);
pageConfig.items = [pageConfig.items[i]];
deployPages.push({
pageName: uiConfig.items[i].name,
pageConfig,
});
}
return deployPages;
};
/**
* 获取abtest包含的页面配置
* @param {ActBaseInfo.abTest} abTest abtest配置
* @param {string[]} publishPages 待发布页面
* @param {number} actId 活动ID
* @returns {DivideConfig[]} abtest拆分后的页面数组
*/
const getAbTestConfig = async (
publishPages: string[],
abTest: ActBaseInfo['abTest'],
actId: number,
): Promise<DivideConfigTrans[]> => {
const filterArr = filterIncludeAbTest(publishPages, abTest);
if (filterArr.length === 0) {
return [];
}
return await Promise.all(
filterArr.map(async (test) => {
let pagesToPublish: UiConfig;
await Promise.all(
test.pageList.map(async (testItem) => {
const [pageRecord] = await pageService.getPages(actId, [testItem.pageName]);
// 不是真正的json对象可能包含组件自定义code代码
// eslint-disable-next-line no-eval
const pageConfig = eval(`(${pageRecord.distCode})`);
if (!pagesToPublish) {
pagesToPublish = pageConfig;
} else {
pagesToPublish.items.push(pageConfig?.items[0]);
}
}),
);
// 将config对象序列化并转译
const srcCode = serializeConfig(pagesToPublish);
const distCode = configTransformDist(srcCode);
return {
pageName: test.name,
pageConfigStr: distCode,
};
}),
);
};
/**
* 过滤出发布页面包含的abtest页面
* @param {ActBaseInfo.abTest} abTest abtest配置
* @param {string[]} publishPages 待发布页面
* @returns {ActBaseInfo['abTest']} 过滤结果
*/
const filterIncludeAbTest = (publishPages: string[], abTest: ActBaseInfo['abTest']) => {
if (abTest.length === 0) {
return [];
}
const filterArr = [];
abTest.forEach((test) => {
if (publishPages.includes(test.name)) {
filterArr.push(test);
}
});
return filterArr;
};
/**
* 处理为保存所需格式
* @param {DivideConfig[]} divideConfigs 按页面拆分后的配置数组每个都是完整的uiconfig可直接发布
* @param {ActBaseInfo} actInfo 活动基本信息
* @returns {PageInfo[]} 保存所需的PageInfo数组
*/
const pageSaveProcessor = (divideConfigs: DivideConfig[], actInfo: ActBaseInfo): PageInfo[] =>
divideConfigs.map((divideItem) => {
const srcCode = serializeConfig(divideItem.pageConfig);
const distCode = configTransformDist(srcCode);
return {
pageTitle: divideItem.pageConfig.items[0].title || divideItem.pageConfig.items[0].name,
pageName: divideItem.pageName,
srcCode,
distCode,
actId: actInfo.actId,
id: divideItem.pageConfig.items[0].id,
};
});
/**
* 发布活动配置文件
* @param {DivideConfigTrans} pageConfig 发布页面的UiConfig活动配置
* @returns void
*/
const publishUiconfig = (pageConfig: DivideConfigTrans) => {
try {
const { pageName, pageConfigStr } = pageConfig;
const distJs = `${StaticPath.PUBLISH}/uiconfig_${pageName}.js`;
const code = processTransConfig(pageConfigStr);
outputFileSync(distJs, code);
logger.debug(`create ${distJs} success!`);
} catch (error) {
logger.error(error);
throw new Error('发布活动配置文件失败');
}
};
/**
* 发布活动html文件
* @param {DivideConfigTrans} pageConfig 发布页面的UiConfig活动配置
* @returns void
*/
const publishHtml = (pageConfig: DivideConfigTrans) => {
const { pageName } = pageConfig;
try {
// 复制html模板
const distHtml = `${StaticPath.PUBLISH}/${pageName}.html`;
const tmpHtml = `${StaticPath.STATIC}/vue3/runtime/page/index.html`;
copySync(tmpHtml, distHtml);
// 注入活动配置文件
const configScript = `<script type='module' src='./uiconfig_${pageName}.js'></script>\n\t${UiRuntimeJS}`;
const data = readFileSync(distHtml, 'utf-8');
const newData = data.replace(UiRuntimeJS, configScript);
outputFileSync(distHtml, newData, 'utf-8');
logger.debug(`create ${distHtml} success!`);
} catch (error) {
throw new Error('发布活动html文件失败');
}
};
/**
* 发布文件
* @param {DivideConfigTrans[]} publishPagesArr 每个发布页面的UiConfig
* @returns void
*/
const publishToFiles = async (publishPagesArr: DivideConfigTrans[]) => {
// 1. 文件夹清空并重新创建
await emptyDir(StaticPath.PUBLISH);
publishPagesArr.forEach((divideConfig) => {
// 2、发布uiconfig.js
publishUiconfig(divideConfig);
// 3、发布html模板
publishHtml(divideConfig);
});
};