Skip to content

Latest commit

 

History

History
302 lines (244 loc) · 13.8 KB

20180510-单院版本控制与特殊需求总结-谢瑶叶.md

File metadata and controls

302 lines (244 loc) · 13.8 KB

为何需要版本控制

单院项目是一个比较庞大的项目,其中接入了很多医院,每家医院都有自己习惯使用的那个版本(有些医院愿意升级,但有些医院习惯使用某个版本)。这就要求在这个项目中,所有版本要同时存在且不能相互干扰。为了达到这种需求,我们使用版本号来对文件进行区分,不同医院只要配置不同版本号,就能呈现相应版本的文件。版本控制的级别为页面级,URL不变。

特殊需求与版本控制的差别

单院有两种需求:基线需求和特殊需求。其中基线需求面向的是所有医院,是整个项目的基础,会不定时进行版本迭代(版本控制就是基于基线进行的)。而特殊需求针对的是单个医院,这种需求一般只有一家或两家医院有需要,那么我们就给有特殊需要的医院单独做这种需求,而不把特殊需求放到基线当中。(如果放到基线中会导致基线过于庞大,且难以维护)


版本控制的方法

一共三个步骤

  • 在配置文件中配置医院最高版本号
  • 以最高版本号为基准计算出该医院所要使用的所有页面的版本号
  • 通过计算出来的每个页面的版本号找到相应文件

医院配置文件(config)

上面提到过单院项目接入了很多医院,我们需要给每家医院配置不同版本号,这就是医院配置文件的作用。每家医院一个配置文件,文件格式是config_hosId.js(hosId是医院编号)。在这个文件中,有一个version参数,这就是版本号了。此处以红房子为例

version: {
    'appVersion': '2.7.0', 
    'physical/index':'230',
    'pat_bind_card': '224',
    'deposit/record': '250'
}

第一个参数appVersion是医院最高版本号,后面的计算都以此为标准。理想状态是:在配置文件中只需要配置一个appVersion,其他版本全靠计算得出。但实际使用下来有时候某些医院对具体页面也有要求,那么也将这种要求配置在文件中。上述代码appVersion下的三个参数就是具体页面的版本配置。这里配置的版本,优先级比计算要高。

计算所有版本号

首先我们需要一个文件,该文件记录了基线所有页面对应的所有版本号,文件名为config_version.js。

module.exports = {
    'registration/notice': '220',
    
    'registration/branch': '220, 223',
    // 科室选择
    'registration/departments': '220',
    // 科室医生列表
    'registration/department.details': '220, 230',
    
    'registration/outpatient.description': '220',

    'registration/doctor.details': '220, 222, 223, 225',
    
    'registration/common.details': '220',
    
    'registration/qrCode': '220'
}

当然不止这些,举个例子。配置的格式是:[模块名/文件名]:'版本号1, 版本号2...'。这个文件就是基线中所有页面和版本号的对应关系。

好,然后开始计算

function versionFactory(cfgVersion, appVersion, configVersion) {
    appVersion = appVersion && appVersion.replace(/\./g, '') || '220'; // 默认版本2.2.0

    for (var key in configVersion) {

        var Vs = configVersion[key].replace(/\s/g, '').split(',');

        cfgVersion[key] = cfgVersion[key] || floor(Vs, appVersion);
    }

    return cfgVersion;
}

/**
 * 版本查找
 * @param Vs, 版本数组
 * @param version, 当前医院版本
 * @returns {*}
 */
function floor(Vs, version) {
    var index = 0;

    Vs.sort(function (a, b) {
        return a - b
    }).map(function (v, i) {
        if (+version - v >= 0) index = i;
    });

    return Vs[index];
}
  • 第一步:统一版本号的写法:在配置文件中,我们将最高版本号appVersion写成x.x.x这种形式,但是实际上我们需要的是xxx这种形式(方便计算),这就是第一步要做的--转换格式。(获取不到appVersion时,默认220版本,也就是此项目中最低版本)
  • 第二步:遍历config_version,并获得每一个页面的版本数组。
  • 第三步:判断配置文件中对当前遍历到的页面是否有特殊配置(cfgVersion就是刚才的医院配置文件中的version参数),若有则优先特殊配置。若没有则根据最高版本号区当前版本数组中最接近但小于等于appVersion的那个版本。

计算结束后就得到了该医院所有页面的版本号。计算结果:

version: {
    appVersion:"2.7.0"
    base:"v2"
    commonSelect:"216"
    complaint/allergen:"220"
    complaint/editAllergen:"220"
    complaint/editPastHistory:"220"
    complaint/family:"220"
    complaint/familyHistory:"220"
    complaint/familySick:"220"
    complaint/index:"220"
    complaint/pastHistory:"220"
    complaint/selectAllergen:"220"
    complaint/selectSickness:"220"
    cost:"210"
}

以上也是举个例子,因为全部内容实在太多

通过版本号找到相应文件

serv_tools.js文件里有个loadView方法,这个方法可以根据版本号找到对应的文件。 具体代码如下:

//获取基线版本页面(不带版本号)的文件路径
    var baseFilePath = path.join.apply(null, [commonFileRootPath, 'client/views', module, fileName + '.html']);
    //获取带版本号的基线页面的文件路径
    var versionVal = versionObj[module + '/' + fileName];
    var versionFilePath = path.join.apply(null, [commonFileRootPath, 'client/views', module, fileName + '.' + versionVal + '.html']);

    var realFilePath = baseFilePath; // 不带版本号的页面
    var isVersionFilePathExist = fs.existsSync(versionFilePath);
    var isCustomFilePathExist = fs.existsSync(customFilePath);
    var isCustomClientFilePathExist = fs.existsSync(customClientFilePath);
    if (isVersionFilePathExist) { // 带版本号的页面
        realFilePath = versionFilePath;
    }
    if (isCustomFilePathExist) { // 特殊需求
        realFilePath = customFilePath;
    }
    if (isCustomClientFilePathExist) { // 终端个性化页面
        realFilePath = customClientFilePath;
    }

上面可以看到:优先级最高的是终端个性化页面,其次是特殊需求页面,然后是带版本的页面,最后是不带版本的基线页面(本项目最低版本220, 不带版本号就默认最低版本)。

前端路由router.模块名.js文件:

// 挂号须知
    'registration.notice': {
        url: '/index',
        templateUrl: 'loadView/registration/notice'
    }

上面依旧是举个例子。在加载某个页面时就调用的了loadView方法,然后获得了当前页面的文件路径

最后举个例子:

  • 假如reg模块index页面有三个版本,文件名分别为:index.html, index.223.html, index.225.html。
  • config_version.js中应添加如下配置: 'reg/index': '220, 223, 225'
  • 医院配置的版本号为225时,则找到index.225.html
  • 医院配置的版本号为224时,则找到index.224.html
  • 医院不配置版本号,或配置220时,通过计算本应该得到index.220.html,但是loadView方法中有判断该文件地址是否存在,不存在则无效,所以最终可以获得index.html

如此,版本控制就讲完了


特殊需求

特殊需求有两种:1.不新增模块,在原有模块上仅静态页面不同 2.新增一个特殊模块

不新增模块,在原有模块上仅静态页面不同

前面讲版本控制的时候loadView方法里优先级第二高的就是特殊需求的页面了,那么这个页面的文件路径在哪儿呢? 且看loadView方法的上半部分:

//个性化页面
    var ua = getReqUA(req);
    var customFileRootPath = path.join.call(null, path.resolve(commonFileRootPath, 'apps'), hosId, '/client/views/');
    //不带版本号的各终端通用页面
    var customFilePath = path.join.apply(null, [customFileRootPath, module, fileName + '.html']);
    //不带版本号的终端个性化页面(优先加载)
    var customClientFilePath;
    if (ua.isWx) {
        customClientFilePath = path.join.apply(null, [customFileRootPath, module, fileName + '.wx.html']);
    } else if (ua.isAli) {
        customClientFilePath = path.join.apply(null, [customFileRootPath, module, fileName + '.ali.html']);
    } else if (ua.isYuntai) {
        customClientFilePath = path.join.apply(null, [customFileRootPath, module, fileName + '.app.html']);
        if (ua.isAndroid) {
            customClientFilePath = path.join.apply(null, [customFileRootPath, module, fileName + '.app.android.html']);
        } else if (ua.isiPhone) {
            customClientFilePath = path.join.apply(null, [customFileRootPath, module, fileName + '.app.iphone.html']);
        }
    }

个性化页面,看到了吗?在项目中的apps文件夹下。

apps文件夹下放的是每个医院的特殊需求以及微信支付宝菜单配置等文件。目录结构为apps->hosId->client。在apps文件夹下的client和基线中的client目录结构是完全相同的。如果一个医院对基线的某个页面有特殊要求,比如reg模块的index页面要改,那就按照基线目录结构:client->view->reg->index.html,在apps文件夹下找到这个医院以hosId命名的文件,然后原原本本把基线的文件复制进去改一下就行。

  • 基线的目录:client->view->reg->index.html
  • 特殊需求目录结构:apps->999->client->view->reg->index.html

注意:特殊需求不能带版本号,要不然找不到

新增一个特殊模块

上面说到过特殊需求的文件路径跟基线是完全一样的,要添加一个新的特殊模块也是一样。 要点有两个:

  • 注册前端路由
  • 注册node端路由

前端路由怎么注册呢

'use strict';
import routes from './routes/index';
var configSpecial = require('../controllers/config/config_special');

export default function ($stateProvider, $urlRouterProvider) {
    //基线版路由
    for (let route in routes) {
        let routeOfModule = routes[route];
        for (let state in routeOfModule) {
            let flag1 = (state.split('.')); //用点号分割,表明是嵌套路由
            let flag2 = (state.split('/')); //用下划线分割,表明是非嵌套路由
            if (flag1.length < 2 && flag2.length < 2) {
                routeOfModule[state].url = '/hos' + routeOfModule[state].url;
            }
            $stateProvider.state(state, routeOfModule[state]);
        }
    }
    for (let key in configSpecial) {
        let specialRoutes = require('../apps/' + configSpecial[key] + '/client/router.js');
        for (let specialRoute in specialRoutes) {
            let specialRouteOfModule = specialRoutes[specialRoute];
            for (let specialState in specialRouteOfModule) {
                let flag1 = (specialState.split('.')); //用点号分割,表明是嵌套路由
                let flag2 = (specialState.split('/')); //用斜杠分割,表明是非嵌套路由,一般指兼容提供给app的链接
                if (flag1.length < 2 && flag2.length < 2) {
                    specialRouteOfModule[specialState].url = '/hos' + specialRouteOfModule[specialState].url;
                }
                $stateProvider.state(specialState, specialRouteOfModule[specialState]);
            }
        }
    }

    $urlRouterProvider.otherwise('/hos/home');
};

基线router.js文件中对基线路由和特殊路由都做了处理。我们可以看到特殊路由就在apps->hosId->client文件夹下。要注意的是我们要在configSpecial当中添加医院的hosId,不加上就注册不了路由:

module.exports = [
    999                            //红房子医院
];

注意:一般来说要在特殊需求的前端路由文件的模块名上加上hosId,以防和基线模块名冲突

var hosId = 999;
export default {
    'telereference': {
        url: '/telereference/' + 999,
        templateUrl: 'loadView/telereference/selectPatient',
        resolve: {
            loadAllNavController: ($q, $ocLazyLoad) => {
                return $q((resolve) => {
                    require.ensure([], () => {
                        let modules = require('../modules/telereference').default;

                        $ocLazyLoad.load({name: modules});

                        resolve(modules);
                    }, 'telereference');
                });
            }
        }
    }
};

node端路由的处理:

for (var hosId in config) {
        var routeFile = path.normalize(__dirname + '/../../apps/' + hosId + '/controllers/route_spe.js');
        if (fs.existsSync(routeFile)) {
            require(routeFile)(app);
        }
    }

同样,在基线中的route.js文件中有引入。具体路径为:apps->hosId->controllers/route_spe.js

ok, 到目前为止,版本控制和特殊需求都讲完了


目前版本控制的方式所带来的问题

  • 当一个页面有多种版本,且各个版本中相同的那部分代码出现bug时,就要改所有版本的文件(改需求同理)。最可怕的是假如这个页面还被某个医院设置为特殊需求时,还要去找到医院hosId对应文件夹下的那个文件去修改。
  • 由于每家医院版本不同,对应到的某个页面版本也不同,在改需求和改bug时首先要确定该页面的版本号才能找到对应的文件(不过我有快速找文件的方法,因为一般来说每个文件引用的controller都是不一样的,我就根据引用的controller来找,这是一种比较粗暴直接的方式)
  • 其实在单院中js文件也有不同版本,也是用文件名+版本号的方式做区分。当页面样式分版本时,我们可以只添加一个HTML文件,但是当页面逻辑也分版本时,我们就要添加一个HTML和一个js文件,如此一来,其实就冗余了。
  • 暂时想到这么多,以后遇到相应问题时再来补充