Cmpage的微服务化
Cmpage前端是个典型的单页结构,每个业务模块从后端返回的是HTML片段,然后进行组装渲染。假设现在有3个团队分别开发了用户中心(UC),客户管理系统(CRM),人事考勤系统(HR),每个系统会有若干业务模块,系统内外的数据有关联,一般也都是用WebAPI的方式进行互相调用,但由于Cmpage的业务模块是高度配置型的,因此我们先不考虑WebAPI,而以公共类组成的框架项目为依托进行系统间相互调用(本方案耦合度较高,主要是基于开发成本的考虑,大团队或不差钱的或稳定性要求很高的项目还是采用现在流行的方案如SpringCloud等较好),因此可采用数据集中存放,应用分开部署,分别由不同的团队开发和运维,C#或Java版的可以建立框架项目,供业务子系统调用和解决方案集成,NodeJs版可以做成多个Npm模块(有待实现),主系统一般为UC,负责权限系统,其他业务系统通过框架公共类和方法来进行系统间调用(可采用 ServiceV1 和Proxy的方式来管理版本),这中间很多细节就不展开讨论了。
可以看出,整个系统要从大的单体系统拆分成多个可独立开发和运维的子系统,主要有三部分重要工作,1、网关和服务发现,2、框架项目的开发和维护,3、各业务子系统的开发和维护,这里我们主要看网关的实现。
服务如何发现
- 常规的方案需要有地方去注册服务、统一管理,但Cmpage的业务模块配置是集中存放于配置数据库中的,这自然就成了注册中心,而对外服务的接口可以写成带版本的公共服务类,放置于框架项目(也可以单独建立项目维护)供其他系统调用。
- Cmpage的前后端调用大部分都是通过统一URL接口,带上modulename参数来进行的,这样就可以方便地进行处理了, 在网关处配置业务模块所在地址就行了,然后通过简单的HTTP代理机制就可以实现该网关了
网关的实现
我们以NodeJS版的Cmpage为例来进行微服务网关的初步实现, 具体代码详见 https://gitee.com/defans/cmpage/blob/master/gate.js,说明如下:
- 安装http-proxy模块,npm install http-proxy;
- 配置业务模块和部署地址的对应关系,也可以从UC的接口取下列结构的JSON数据:
const moduleTargets = [{ modules:',DocuListLookup,DocuList,DcouRecArrive,DocuRecBill,DocuRecOrder,DocuRecOrderApply,DocuRecPick,', //列出了部分模块 target: 'http://10.9.35.41:8380' },{ modules:',ExamMarker,Exam,ExamRec,ExamStudent,Crontab,', //列出了部分模块 target: 'http://127.0.0.1:8380' } ]; function getTarget(modulename){ for(let mod of moduleTargets){ if(mod.modules.indexOf(','+modulename+',') >-1){ return {target: mod.target }; } } return { target: 'http://10.9.35.41:8380' }; }
3、这样可以根据配置进行简单的路由转发就行了
let server = http.createServer(function(req, res) { // 在这里可以自定义你的路由分发 let parms = querystring.parse(url.parse(req.url).query); console.log(parms); //var host = req.headers.host, ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; visitCnt ++; console.log("cnt: "+visitCnt+", cookie: " + req.headers.cookie); if(parms['modulename'] ){ proxy.web(req, res, getTarget(parms['modulename'])); }else{ proxy.web(req, res, { target: 'http://10.9.35.41:8380' }); } } );
Cmpage的适应性改造
由于网关代理转发的时候 req中是带有统一的cookie信息的,因此后端各业务系统如果能从同一个地方取Session信息的话,单点登录就实现了。因此session的配置改为 think-session-mysql 方式进行,大致如下:
- 增加NPM模块 think-session-mysql: npm install think-session-mysql
- 修改配置文件 src/config/adapter.js,增加相应配置, https://gitee.com/defans/cmpage/blob/master/src/config/adapter.js
- 在存放session信息的数据库中新建数据表:
CREATE TABLE `think_session` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `cookie` varchar(255) NOT NULL DEFAULT '', `data` text, `expire` bigint(11) NOT NULL DEFAULT '0', `maxage` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `cookie` (`cookie`), KEY `expire` (`expire`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
这样基本就配置好了,但运行过程中出现了一个错误,没去仔细深究,把 https://github.com/thinkjs/think-session-mysql/blob/master/index.js 文件中的第 69 行注释掉就行了,this.autoUpdate();,应该是session的有效期机制,但该插件获取配置信息有问题。
经测试,网关加两个系统是可以正常工作的,即两个系统可以通过代理网关结合成一个统一的系统提供给用户。