摘要:此文已由作者张磊授权网易云社区发布。欢迎访问网易云社区,了解更多网易技术产品运营经验。前言在对蜂巢项目从迁移到的过程中,遇到的问题,以及在此过程中所使用的解决方案。三个头是一致的。文章来源网易云社区
此文已由作者张磊授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
前言
在对蜂巢项目从 nej + regularjs 迁移到 vue 的过程中,遇到的问题,以及在此过程中所使用的解决方案。
遇到的问题
父子页面通信
项目分为待重构的模块和已重构的模块,待重构的模块是使用 nej 和 regular ,重构的模块是 vue。页面是通过 iframe 引用重构的模块。
这里会涉及到几个问题。iframe 和父窗口数据交换问题、模态框、以及路由同步的问题。 这三个问题的解决方案都是使用了一个通信机制。这个通信机制对数据进行了序列化(在 ie 下,不序列化会遇到暗坑),所以函数是无法进行传递的,只可以传递值,然后通过 JSON.stringify 序列化成字符串,传递过去后再反序列化成相应类型。采用这种形式,在项目后期,对代码进行批量修改的时候查找也很方便,甚至可以将通信机制二次改造,而不需要改业务代码。
问题需要特殊说明的有:
模态框
由于 iframe 并不是铺满整个页面,在 iframe 内部实现模态框的时候,导致导航栏不会被覆盖掉。于是就可以看到页面的一部分变灰,但导航栏还是可以点击的。同时模态框的居中是相对于 iframe 的,所以看起来也不是特别居中。暂时解决方案,是使用通信机制传参调用父页面的模态框的逻辑。2. 路由同步
nej 和 vue 都有一套路由方案,但是路由的格式是不一致的,同时模块的命名方案也会不一致,再者 iframe 和 父页面路由的变更都需要通知到对方。
接下来讲通信机制如何解决问题的。
API
/**
* 发送信息 * @param {string} receiver 收件人 * @param {string} action 描述 * @param {*} [msg] 内容 * @param {function} [cb] 回执函数 * @param {boolean} [isTemp] 如果收件人不存在,通过设置这个参数来指定该消息是丢弃还是保存,当未来某时刻收件人存在的时候,会依次读取保存的信息,一般不需要指定。 */
send(receiver, action, msg, cb, isTemp) {
},
// Bridge.send("parent", "urlchange", "/module/list");
// Bridge.send("parent", "error", "网络出错");
action 可以在父子页面的 handles 对象里注册。eg:
// 父页面const handles = {
show() { toggle(true); }, hide() { toggle(false); }, hideModal: _u._$hideModal, alert(options) { _u._$alert(options); }, error(msg) { CloudUI.Toast.error(msg); }, confirm(options, cb) { _u._$confirm(Object.assign({ onok() { cb("sub", { msg: true, }); }, oncancel() { cb("sub", { msg: false, }); }, }, options)); },
}
// 子页面handles.urlchange = function urlchange(path) {
router.replace(path);
};
再来一些复杂的例子
同步调用
子页面向父页面传递数据
Bridge.send("parent", "urlchange", "/module/list");// 这里注意一点,为了以后方便,在 vue 模块内部使用的路由均是 vue 的,vue 路由向 nej 路由的转换在 /src/html/module/vue/map.js 进行配置,配置信息如下:// {// "/m/module/": "/module/list",// }
父页面向子页面传递数据
Bridge.send("sub", "urlchange", "/module/list");// 而在 nej 模块,写路由就可以随意点,可以直接写 vue 的路由,也可以让其进行转换,nej 的模块在后面均会丢弃,所以允许随意一点
异步调用
// send(receiver, action, msg, cb, isTemp)Bridge.send("parent", "confirm", { content: "所选快照正在维护中,创建可能需要等待较长时间,建议稍后再试。", okButton: "继续创建", cancelButton: "稍后再试", primaryButton: "cancelButton",
}, (err, status) => { if (status) { this.create();
} else { this.submitting = false; }
});// 其中第四个参数是回调函数 callback,模仿 nodejs 的实现,err 存在的时候就是失败,第二个参数是调用返回的 message。可以参见上面 handles.confirm。
路由
vue 以及 vue-router 支持的异步加载仅仅是组件级别的,而不是路由级别的,所以实现路由级别的异步加载就会绕一些。eg:
// 一般实现const Create = () => import("./create.vue");const List = () => import("./list.vue");
这种方案下,webpack 会对每一个路由进行打包,导致一个路由一个 chunk 的模式,前端加载负担过大。实际上,我们需要的粒度可能没有这么细,在这里使用一种 vue-router 官方的方案(滑到页面底部)。
// 优化实现const Create = () => import(/ webpackChunkName: "a" / "./create.vue");const List = () => import(/ webpackChunkName: "a" / "./list.vue");
这里是使用注释 / webpackChunkName: "a" / 来标明打入同一个 chunk a 中。唯一的坑点是使用注释。当然还有一种方案进行处理。eg:
// index.jsexport { default as create } from "./create.vue";export { default as list } from "./list.vue";// route.jsconst Create = () => import("./index.js").then((modules) => modules.create);
路由写在每个模块的下面,只存在一个文件。
eg:
// 建议- modules
- moduleA - routes.js
不建议在子目录放置路由,不清晰,完整的路由,可能需要打开多个文件,才能看到
// 不建议- modules
- moduleA - detail - routes.js - routes.js
建议方案会产生 routes.js 文件变的很庞大的问题,不方便查看。可参考如下写法,可以缓解此问题:
const routesA = [];const routesB = [];export default [
...routesA, ...routesB,
]
路由的划分问题
eg:
// 不建议const router = [
{ path: "/", component: () => import(/* webpackChunkName: "module" */ "./list.vue"), children: [ { path: "tab1", name: "module.list.tab1", // ... }, { path: "tab2", name: "module.list.tab2", // ... }, { path: "tab3", name: "module.list.tab3", // ... }, ], }, { path: "tab1/edit", name: "module.edit.tab1", // ... }
];
module 模块页,有三个 tab。三个 tab 头是一致的。 list.vue 的代码仅仅是实现了 3个 tab 一致的部分即头部。观察 tab1/edit 和 tab1,在逻辑层面上它们应该被放到一起,但是 path: "/" 所在的组件的 dom 中含有 3个 tab 的头,导致没有办法写在一起(写在一起的话同时会继承头部),权限控制更显麻烦。更好的做法是 path: "/" 这一层级不做任何 dom 相关的东西,写到每个 tab 内部。
// 建议const tab1 = { path: "tab1", // 权限控制 tab1 的准入 children: [
{ path: "list", name: "module.tab1.list", // ... }, { path: "edit", name: "module.tab1.edit", // ... }, ],
};
const router = [
{ path: "/", // 权限控制 module 的准入 children: [ tab1, { path: "tab2", name: "module.tab1.list", // ... }, { path: "tab3", name: "module.tab3.list", // ... }, ], }
];
当然可以根据权限控制进行调整,写法不是很固定。交互可能不太喜欢定义 path: "list", 但是第一种写法,相当于污染了整个路由的顶层,那后面必须定义多个顶层进行覆盖,由模块单入口路由变成了模块多入口路由。
openapi 和 webapi 数据转换
举例说明:在模块从 webapi 迁移到 openapi 的时候使用了一种方案,在数据获取层面对数据进行转换。即:
// openapiconst result1 = { Id: 1, Name: 2,
};// webapiconst result2 = { id: 1, name: 2,
};// transform(result1) 后的数据结构包含 result2 中有用的数据结构// 这个 transform 函数会将 openapi 的数据转换成 webapi 的,这样只需要改数据结构,让新老保持一致,再修改少量的业务逻辑即可完成接口迁移工作。
这个问题本身属于后端接口变更,与框架迁移属于并行任务,多带带拿来看并无关联,问题放在一起的时候,就变得棘手了。
在对 win 模块进行迁移的时候,在使用 vue 的时候希望接口方面使用 opeanpi 的数据,不进行数据转换。但是在使用老模块的时候,为了尽量少的动业务代码,对 opeanpi 的数据进行了转换,那就意味着两者的数据的并不一致,在使用上面提到的通信机制(调用父页面的模态框,需要传递数据)的时候,这就很致命了,意味着一方需要再做一次数据转换。目前代码是 vue 模块手工硬编码转换的,后面可以把这部分放到 nej ,可以借用其已有的接口的数据转换函数,对 vue 传递的数据进行二次转换。
另外还有一个问题,如果模块先进行框架迁移,后进行接口迁移,此时就面临两个方案。一个是使用 transform 函数对数据进行转换,另一种,推到重写。此时肯定更倾向于第一种方案,那么就需要对 transform 函数进行设计,让其更方便使用。这里简单设计了一种。
const source = { standard: { bandwidth: 1, ipChargeType: 2
}, instanceId: 6,
};
const rules = { standard: "NewStandard", instanceId: "InstanceId", "standard.bandwidth": ["InternetMaxBandwidth", "BizParam.InternetMaxBandwidth"], "standard.ipChargeType": "BizParam.NetworkChargeType",
};
const out = { NewStandard: { bandwidth: 1, ipChargeType: 2
}, InstanceId: 6, BizParam: { InternetMaxBandwidth: 1, NetworkChargeType: 2, }, InternetMaxBandwidth: 1,
};
it(transform(source, rules) is correct, () => { assert.deepEqual(transform(source, rules), Object.assign({}, source, out));
});
it(transform(source, rules, true) is correct, () => { assert.deepEqual(transform(out, rules, true), Object.assign({}, out, source));
});
静态资源
这里主要是 js 文件内引用的静态资源,该静态资源的路径需要用此语法进行设置
default: { logo: require(@/assets/images/logos/logo.png), // @是项目根路径},
这样这个静态文件就可以享受到 webpack 的处理,算出正确的路径,不然有可能出现显示不出来的情况。 另外静态资源不推荐写相对路径。eg: ../../../assets/images/logos/logo.png
接口
nej + regular 在代码里写了接口,在 vue 需要再次找到接口,重新写一遍,基本不可复用。但如果之前放置接口的地方稍显混乱,那么在找接口的时候,就需要一个个业务逻辑的看过去。试想可不可以
将接口按照模块放置在一起,称为 api 层,同时再划分出来 service 层。api 层通过 json 来描述一个接口的方方面面, service 层是从 api 层生成出来的,外加上对接口进行二次处理和对多个接口拼接。
然后就很好的抽象出一个独立的 api 层,和业务逻辑无关,仅和后端文档输出有关,同时 service 层又很好的保持一定的业务相关性。那么在换框架的时候,api 直接拿走即可,service 层仅需要稍许改动。另有文章介绍具体的实现。
这里可能会有接口数据缓存以及一定时间内只发一次请求的需求,那么想一下,需要在该层实现吗?还是需要更高的一层对数据处理的抽象,而不受限于仅对接口数据?又或者对 service 层的定义进行扩充,包含对数据处理的抽象?
网易云计算基础服务深度整合了 IaaS、PaaS 及容器技术,提供弹性计算、DevOps 工具链及微服务基础设施等服务,帮助企业解决 IT、架构及运维等问题,使企业更聚焦于业务,是新一代的云计算平台,点击可免费试用。
文章来源: 网易云社区
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/25269.html
摘要:本文将介绍网易云基础服务蜂巢实例迁移功能的实现,并探讨如何高效完成实例迁移任务。网易云基础服务蜂巢提供了负载监控阈值选项,在业务负载超过该阈值时,会暂停迁移操作,直到负载重新低于阈值。 欢迎访问网易云社区,了解更多网易技术产品运营经验。 我们把数据库里部分或全部 Schema和数据迁移到另一个实例的行为称为实例迁移,将导出数据的实例称为源实例,导入数据的实例称为目标实例。 根据迁移数...
摘要:最后,张晓龙透露未来网易云会在以下三个方面继续深耕研发高性能容器,跟进开源社区最新版本并适配,加大参与社区力度并反馈社区。文章来源网易云社区 欢迎访问网易云社区,了解更多网易技术产品运营经验。 10 月 15 日,聚焦 Kubernetes 中国行业应用与技术落地的首届中国 Kubernetes 用户大会(KEUC)在杭州成功举办。本次大会吸引了来自全球各地的技术精英齐聚一堂,共同探...
摘要:作为为应用平台即时通讯技术开发和部署提供便捷途径的云平台,网易云聚焦稳定性和易用性,通过强化私有云定制化的服务来满足不同用户的需求,而这正是网易云作为即时通讯领域的老兵的竞争优势所在。9月22日,2017中国移动互联网大会暨中国首席技术官大会在杭州举行。众多业界技术领袖参与了此次盛会,就互联网时代的IT技术研发挑战和应对策略进行了探讨。网易技术委员会资深专家委员徐杭生在会议上发表演讲,认为移...
摘要:在互联网的第三个时代,也就是下一个互联网十年里,云计算将成为这场大战的制胜关键所在。就在前不久,亚马逊旗下的云计算服务平台宣布正式在中国商用。的杀入对于中国的云计算市场也是一大不可忽视的力量。 互联网的第一个时代我们定义为PC互联网,互联网的第二个时代我们定义为移动互联网,而互联网的第三个时代我们则定义为万物联网。当前国内的互联网正处于第二个时代向第三个时代过渡期,而云计算则是支撑起万物联...
摘要:网易云信即时通讯云服务的产品优势网易云信涉足多元行业提升用户体验满足内外协同今年月,网易云信正式宣布通信与视频业务实现战略升级。 韩寒导演的影片《乘风破浪》中,有这样一个片段,在六一还坚持囤BB机有前途的时候,从事电脑编程和软件开发的小马则认为即时通讯才是未来的发展方向。看到这里,很多观影者都不谋而合地笑了,小马口中的即时通讯就是在1999年诞生的OICQ。影片中阿浪说的那句话没错,这个世...
阅读 1771·2021-11-15 11:37
阅读 3043·2021-11-04 16:05
阅读 1910·2021-10-27 14:18
阅读 2741·2021-08-12 13:30
阅读 2485·2019-08-29 14:18
阅读 2076·2019-08-29 13:07
阅读 2004·2019-08-27 10:54
阅读 2713·2019-08-26 12:15