如何基于接口文档生成模拟数据

前后端分离是目前主流的团队开发方式,有效的隔离不同技术领域开发人员的依赖,包括开发进度上的依赖和代码文件上的依赖。而做到这一点,全靠一个规则:依赖接口而不依赖实现。

那么,web领域前后端分离,通常都会选择基于http+json的方式,也就是大家说的restful类型的接口。在进入代码编写之前,团队会定义好所有依赖的rest接口,前后端开发人员遵照接口文档各自完成自己的工作,完美!

那么,在前后端开发进度不一致的情况下,可能会出现前端界面完成了但对应的后端服务还没有完成,怎么办?这个时候一般都是会让前端人员自己来提供一个模拟的响应数据,这种方案简单有效,瞬间前端人员就可以模拟出所需的服务接口响应。但是,问题在于,随着需求的变化,接口会发生变更,此时会影响前后端的代码,但往往会由于各种实际原因,之前的模拟接口响应的脚本并没有随着接口的变更而更新,对未来的测试留下了隐患~

怎么做最理想?几年前我在之前的团队推广过阿里的一个开源项目:RAP。虽然最终并没有大规模使用,但通过调研,RAP真心是个不错的解决方案,尤其是对国内的项目,本地化支持度非常高。该项目也一直在持续更新,如果有兴趣,我首先推荐尝试RAP。

不过这里,我们并不会采用RAP,理由可能有些牵强,仅仅是因为团队已经存在大量的接口文档在swagger标准上,并且组员也已经相当熟悉这个规范,他们不会接受突然切换到RAP上而带来的学习成本。

不过没关系,swagger作为rest文档界的事实标准,自然应该存在大量的模拟数据的解决方案,所以我花了几天的时间查阅了相关的资料(其实就是不停地google),找到了几种方案。

不过目前我能找到的,swagger下的对应解决方案都不像RAP那样开箱即用,多数都是需要自己完成组合才能投入使用的。这里我罗列一下相关的库:

最终,我决定基于 swagger-node + mockjs 来完成我们的根据swagger接口文档生成模拟数据的目的。之所以选择swagger-node,是因为它自身就容纳了很多方便的工具,例如:swagger editor:swagger.io在线版的编辑器,你可以直接跑在自己的服务器上,并且它是实时持久化到文件中且同步到mock server的。

安装和配置swagger-node非常的简单,只需要按照官方的步骤完成即可。装好后按照推荐的用法,我们需要先swagger project create一个项目出来,记得选择express作为服务端框架。swagger-node会按照预设为我们创建好一个标准规范的项目文件结构。

直接执行swagger project start就可以运行一个解析基于swagger接口标准规范文档并自动化创建对应服务的后端服务。该服务可以校验每次接口的请求,根据接口文档的要求来返回匹配的结果数据。

基本上完美,就是我们想要的。不过,美中不足的是,目前swagger-node根据接口文档返回的模拟数据是静态的,例如,整型只会返回1,字符串类型只会傻傻的返回Sample text。这显然无法满足我们的要求,唉,真可惜!

不过庆幸的是,我在该项目的issue中找到了一个很有价值的,提供了一个非常不错的解决方案。其实也很好理解,就是将原本swagger-node用来生成模拟数据的逻辑修改成符合我们要求的逻辑即可。同时该方案也让我关注到了mockjs项目,应该是个国人的项目,非常棒!其实国外也有不少同类型的项目,但说到本地化,还是我们自己国家的开发人员最贴心。并且看完文档后,发现它的扩展性也非常不错,简单直观,赞!

好的,一切就绪,开始改造吧!

先找到\你的项目\node_modules\swagger-express-mw\node_modules\swagger-node-runner\node_modules\swagger-tools,目录结构比较深~

swagger-tools提供了我们需要修改的中间件middleware\swagger-router.js。由于要使用到mockjs,所以需要将对应的文件放到lib文件夹下。

接下来就要修改代码了,使用你心爱的编辑器打开swagger-router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//找到getMockValue方法,大概在107行
var Mock = require('../lib/mock');
var getMockValue = function (version, schema) {
var type = _.isPlainObject(schema) ? schema.type : schema;
var xmock = _.isPlainObject(schema) ? schema['x-mock'] : schema;
var Random = Mock.Random;
var value;
var temp;
if(xmock){
var data = xmock.split("|");
type =data[0];
if(data.length>1)
temp = data[1];
}
if (!type) {
type = 'object';
}
switch (type) {
case 'guid':
value = Random.guid();break;
case 'id':
value = Random.id();break;
case 'step':
value = Random.increment(temp);break;
case 'title':
value = temp==='cn'?Random.ctitle():Random.title();break;
case 'text':
value = temp==='cn'?Random.cparagraph():Random.paragraph();break;
case 'color':
value = Random.hex();break;
case 'ip':
value = Random.ip();break;
case 'email':
value = Random.email(temp);break;
case 'firstname':
value = temp==='cn'?Random.cfirst():Random.first();break;
case 'lastname':
value = temp==='cn'?Random.clast():Random.last();break;
case 'cname':
value = Random.cname();break;
case 'name':
value = Random.name(temp === 'middle');break;
case 'url':
value = Random.url(temp);break;
case 'date':
value = Random.date(temp);break;
case 'time':
value = Random.time(temp);break;
case 'datetime':
value = Random.datetime(temp);break;
case 'array':
value = [];
for(var i = 0; i < temp && i < 20; i++){
value.push(getMockValue(version, _.isArray(schema.items) ? schema.items[0] : schema.items));
}
break;
case 'boolean':
if (version === '1.2' && !_.isUndefined(schema.defaultValue)) {
value = schema.defaultValue;
} else if (version === '2.0' && !_.isUndefined(schema.default)) {
value = schema.default;
} else if (_.isArray(schema.enum)) {
value = schema.enum[0];
} else {
value = Random.boolean(5, 5, true)||1;;
}
// Convert value if necessary
value = value === 'true' || value === true ? true : false;
break;
case 'file':
case 'File':
value = Random.image();break;
case 'integer':
if (version === '1.2' && !_.isUndefined(schema.defaultValue)) {
value = schema.defaultValue;
} else if (version === '2.0' && !_.isUndefined(schema.default)) {
value = schema.default;
} else if (_.isArray(schema.enum)) {
value = schema.enum[0];
} else {
value = Random.integer(1,1000)||1;
}
// Convert value if necessary
if (!_.isNumber(value)) {
value = parseInt(value, 10);
}
//TODO: Handle constraints and formats
break;
case 'object':
value = {};
_.each(schema.allOf, function (parentSchema) {
_.each(parentSchema.properties, function (property, propName) {
value[propName] = getMockValue(version, property);
});
});
_.each(schema.properties, function (property, propName) {
value[propName] = getMockValue(version, property);
});
break;
case 'number':
if (version === '1.2' && !_.isUndefined(schema.defaultValue)) {
value = schema.defaultValue;
} else if (version === '2.0' && !_.isUndefined(schema.default)) {
value = schema.default;
} else if (_.isArray(schema.enum)) {
value = schema.enum[0];
} else {
value = Random.float(0, 100, 0, 4);
}
// Convert value if necessary
if (!_.isNumber(value)) {
value = parseFloat(value);
}
// TODO: Handle constraints and formats
break;
case 'string':
if (version === '1.2' && !_.isUndefined(schema.defaultValue)) {
value = schema.defaultValue;
} else if (version === '2.0' && !_.isUndefined(schema.default)) {
value = schema.default;
} else if (_.isArray(schema.enum)) {
value = schema.enum[0];
} else {
if (schema.format === 'date') {
value = new Date().toISOString().split('T')[0];
} else if (schema.format === 'date-time') {
value = new Date().toISOString();
} else {
value = Random.string(0, 61);
}
}
break;
}
return value;
};

放心保存吧~~

接下来,我们在当前项目下直接运行swagger project start,哇哈,后台服务应该已经跑起来了。swagger-node会监控我们的接口定义文件(在你的项目\api\swagger下),每次变更,都会被实时映射到mock服务上。

上面的代码并不是非常难理解,我只是将mockjs官方提供的数据类型都简单的绑定到了swagger生成模拟数据的规则上。

使用起来也非常简单,特殊类型只需要增加对应的x-mock声明即可,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
swagger: "2.0"
info:
version: "0.0.3"
title: Hello World App
# during dev, should point to your local machine
host: localhost:10010
# basePath prefixes all resource paths
basePath: /
#
schemes:
# tip: remove http to make production-grade
- http
- https
# format of bodies a client can send (Content-Type)
consumes:
- application/json
# format of the responses to the client (Accepts)
produces:
- application/json
paths:
/hello:
# binds a127 app logic to a route
x-swagger-router-controller: hello_world
get:
description: Returns 'Hello' to the caller
# used as the method name of the controller
operationId: hello
parameters:
- name: name
in: query
description: The name of the person to whom to say hello
required: false
type: string
responses:
"200":
description: Success
schema:
type: object
required:
- total
- users
properties:
total:
description: 用户总数
type: integer
users:
description: 用户列表数据
type: array
x-mock: array|5
items:
$ref: "#/definitions/HelloWorldResponse"
# responses may fall through to errors
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
/swagger:
x-swagger-pipe: swagger_raw
# complex objects have schema definitions
definitions:
HelloWorldResponse:
required:
- id
- name
- message
properties:
id:
type: integer
x-mock: step|1
name:
type: string
x-mock: title|cn
message:
type: string
ErrorResponse:
required:
- message
properties:
message:
type: string

完工!

警告

这种结合mockjs和swagger-node的方法并不是很优雅,毕竟需要直接修改项目的依赖库。这会导致 每次创建新项目可能都会丢失之前的修改。不过我并没有找到swagger-tools这个项目的扩展方式,至少官方没有提供类似的hook供我们来使用。(逼急了哥,直接弄个docker镜像给你看 :-))

补充: swagger-node目前只支持单个swagger.yaml,并不支持多个独立的接口文件。只能要求我们在使用的时候将多个文件合并到一起,或者搭建多个项目,前面放一个nginx做反向代理。

译-在多个标签页之间共享sessionStorage

原文:Sharing sessionStorage between tabs for secure multi-tab authentication

译者得er瑟


昨天,就在昨天,前端一同事提了一个问题:我们的系统,用户重新开一个标签页,就要重新登录。我当时觉得这怎么可能?结果现场一测,还真是,好尴尬!

今天抽了点时间网上查了查,才发现原来一直以为很简单的sessionStorage,还真埋了这么一颗雷。不过国外前辈也提出了一个解决方案,不仅如此,文章还把浏览器端保存数据的场景分析的很透彻,所以斗胆翻译了一下。

阅读全文

小团队玩不转的测试

早在上一家公司,就为测试问题头疼过,那时候测试全靠人肉,还整出了黑盒白盒测试文档,还要对代码进行打点,还要人工去匹配打点数据是否执行…都是泪,都是泪,都是泪啊!

那个时候团队人数最多时有十二个,项目现在想想也不算大,按道理是可以分出来一部分人来专做测试工作的,只是当时无法说服领导成立测试小组,毕竟自己也没有自动化测试的经验。最后强迫别的部门的同事来帮我们测,除了心不甘请不愿外,测试的结果也不是特别的理想。

换了一家公司,依然是四人小团队,现在我开始琢磨如何自动化测试了。毕竟之前的不愉快精力,再加上我现在的岗位更多是解决团队的开发效率问题,所以必须得正视这个头疼的问题。

阅读全文

Git Hook帮你维护前端代码规范

只要不是一个人在战斗,你都一定会碰到很多工程问题。我们今天来说的,就是代码格式问题。这不是个什么有意思的话题,这个话题讲的就是条条框框,就是枯燥,就是没意思。但是,如果你的团队缺少代码格式规范的话,当你review组员的代码时,你就会感觉在吃屎,没错,不夸张!

我不怀疑团队组员的积极性,因为条条框框本来就不是程序员的调调,而且人类和机器的最大差别就是遵守规范的程度。所以,你不能要求你口头上说代码格式要怎样怎么,所有同事就会立刻写出符合要求的完美代码。

阅读全文

Open-Falcon初探

随着项目的开发一点一点的完成,离初版上线日期已经越来越近了,这样就涉及到各种运维问题,监控的意义就体现出来了。虽然项目最终会部署在云平台,而云平台自身会带监控套件,不过不够灵活,一些想要的指标和报警方式还是需要自己来实现。大概看了几款监控解决方案,对Open-Falcon特别有好感,虽然我不懂GO语言~

阅读全文

传统Web项目代码变更引起的浏览器缓存问题和解决思路

不确定文章的标题描述的是否已经足够明了,至少用类似的描述在gg中并没有定位到相关的文章。讨论这个话题的更多是围绕这前端工程化套件的用法的(例如webpack、grunt、gulp等),而这些工具对单入口页面SPA应用支持度非常的好。但现实往往不尽如意,面对传统的多入口web项目,前端又应该如何解决浏览器缓存旧版本代码的问题呢?

阅读全文

【转】调整虚拟机中Ubuntu Server屏幕分辨率

转自:http://blog.csdn.net/weilanxing/article/details/7664324

VMware中的Ubuntu Server的控制台窗口有点儿小,使用起来不太方便,要调整控制台的窗口大小,需要修改屏幕的分辨率,修改方法如下:

  1. 打开grub文件($vim /etc/default/grub), 修改参数GRUB_CMDLINE_LINUX的值,
    GRUB_CMDLINE_LINUX=”vga=0x317”, 参数值参考下图:

    1
    2
    3
    4
    5
    6
    | 640x480 800x600 1024x768 1280x1024
    ----|--------------------------------------
    256 | 0x301 0x303 0x305 0x307
    32k | 0x310 0x313 0x316 0x319
    64k | 0x311 0x314 0x317 0x31A
    16M | 0x312 0x315 0x318 0x31B
  2. $sudo update-grub

  3. $sudo reboot