Gowhich

Durban's Blog

最近使用koa2做项目测试开发,想整合下travis ci,网上资料也比较少,于是自己就整了个,做个记录。

先来看下travis.yml的配置

1
2
3
4
5
6
7
language: node_js
node_js:
- "6"
before_script:
- ./node_modules/.bin/knex migrate:latest --knexfile='./app/knexfile.js'
script:
- npm run test

因为是接口测试,所以首先需要做表创建等操作。

测试的命令:

1
NODE_ENV=production NODE_CONFIG_DIR='./app/config/' ./node_modules/.bin/mocha --require 'babel-polyfill' --compilers js:babel-register  ./app/test/**/*.js

主要是测试这里,使用了supertest,大概看下是如何调用的。

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
const request = require('supertest');
const should = require('should');
const index = require('../../index');

let app = request(index.listen());

describe('/api/persons', function() {
let personId;

it('POST /api/persons - create person success and respond with 200', function(done) {
app.post('/api/persons')
.send({
'firstName': 'Jennifer',
'lastName': 'Lawrence',
'age': 24
})
.expect(200)
.expect(function(res) {
(res.body.id > 0).should.be.true;
})
.end(function(err, res) {
if (err) {
return done(err);
}

let resJson = JSON.parse(res.text);
personId = resJson.id;

done();
})
});

it('GET /api/persons - fetch persons item', function(done) {
app.get('/api/persons')
.expect(200)
.expect(function(res) {
(res.body.length > 0).should.be.true;
})
.end(function(err, res) {
if (err) {
return done(err);
}

done();
})
});

it('GET /api/persons/:id - fetch a person', function(done) {
app.get(`/api/persons/${personId}`)
.expect(200)
.expect(function(res) {
(res.body.id == personId).should.be.true;
})
.end(function(err, res) {
if (err) {
return done(err);
}

done();
})
});

it('DELETE /api/persons/:id - delete a person', function(done) {
app.delete(`/api/persons/${personId}`)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}

done();
})
});

it('GET /api/persons/:id - fetch a person should 404', function(done) {
app.get(`/api/persons/${personId}`)
.expect(404)
.end(function(err, res) {
if (err) {
return done(err);
}

done();
})
});

});

这里主要注意的是

1
const index = require('../../index');

需要将koa实例暴漏出来,不然在做travis ci的集成后,启动了项目,测试的时候依然找不到具体访问地址。

来看下我的index.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
import Knex from 'knex';
import {
Model
} from 'objection';
import knexConfig from './knexfile';
import config from 'config';
import Koa from 'koa';
import koaLogger from 'koa-logger';
import bodyParser from 'koa-bodyparser';
import render from 'koa-ejs';
import co from 'co';
import koaStatic from "koa2-static"
import router from './router';

const path = require('path');

// initial knex
const knex = Knex(knexConfig.development);
Model.knex(knex);

// initial app
const app = new Koa();

// initial render
render(app, {
root: path.join(__dirname + '/view'),
layout: 'template',
viewExt: 'ejs',
cache: true,
debug: true
});
app.context.render = co.wrap(app.context.render);

// initial static

app.use(koaLogger())
.use(bodyParser())
.use(router.routes())
.use(koaStatic({
path: '/web',
root: __dirname + "/../static"
}));

module.exports = app;

需要注意的是这里的

1
module.exports = app;

暴漏出来,再supertest中才可以独立启动server测试。好的不明白的加群聊聊吧。

node.js 从 4.4.0 版本开始内置了 profiler, --prof 命令选项运行应用会在当前目录生成性能日志文件。

简单记录下使用方法

运行的时候加上 –prof 参数

1
node app.js --prof

运行后会在当前目录生成一个类似:isolate-0x1d1e1b0-v8-10041.log这样的文件

执行如下命令来分析程序的性能

1
node --prof-process isolate-0x1d1e1b0-v8-10041.log

具体解析分析的结果请看参考文章

参考:

诊断 node.js 应用 CPU 占用过高的问题

Easy profiling for Node.js Applications | Node.js

安装knexfile

1
npm install -g knex

然后在项目的根目录

1
knex init

将会产生knexfile.js,内容类似如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Update with your config settings.
module.exports = {
development: {
client: 'mysql',
connection: {
host: '127.0.0.1',
user: 'root',
password: '',
database: '<YOUR TEST DB NAME>',
charset: 'utf8'
}
},
staging: {
...
},
production: {
...
}
};

如果想要根据具体环境来执行具体配置,可以使用如下命令来指定环境

1
knex migrate:latest --env production

更多的使用可以参考Knex docs

创建Migration

1
knex migrate:make create_person

将会创建migrations目录并且将migration的文件放入文件夹中

默认的内容如下:

1
2
3
4
5
6
7
exports.up = function(knex, Promise){

}

exports.down = function(knex, Promise){

}

接下来我们在这里面实现具体的表信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
exports.up = function(knex, Promise){
return Promise.all([
knex.schema.createTable('person', function(table){
table.increments('id').primary();
table.integer('parentId').unsigned().references('id').inTable('person');
table.string('firstName');
table.string('fullName');
table.integer('age');
table.json('address');
})
]);
}

exports.down = function(knex, Promise){
return Promise.all([
knex.schema.dropTable('person')
]);
}

当应用migration的时候up被调用,当执行回滚的时候down被调用

在这些功能中,你可以使用Knex Schema functions

执行如下命令来使用新的migration

1
knex migrate:latest

更新数据库表

1
knex migrate:make update_person

内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
exports.up = function(knex, Promise) {
return Promise.all([
knex.schema.table('person', function(table){
table.string('twitter');
})
])
}

exports.down = function(knex, Promise) {
return Promise.all([
knex.schema.table('person', function(table){
table.dropColumn('twitter');
})
]);
}

回滚操作

回滚操作执行下面的命令

1
knex migrate:rollback

knex对于创建跟修改表变得很容易。

新建个文件

app.js

里面的内容如下:

1
2
console.log(__dirname + '/example.db');
console.log('example.db');

如果将app.js放在一个根目录下面

执行node app.js

分别输出如下内容:

1
2
/Users/durban/nodejs/koa-mysql-orm-model/example.db
example.db

我们建立一个文件夹app,app下建立一个app.js

里面的内容如下:

1
2
console.log(__dirname + '/example.db');
console.log('example.db');

执行node app.js

分别输出如下内容:

1
2
/Users/durban/nodejs/koa-mysql-orm-model/app/example.db
example.db

可见,__dirname追加了自身的目录路径,一般这样做的好处是,可以避免文件的混乱调用。

最近push代码到github的时候出现了问题

$ git push origin master

ssh: connect to host github.com port 22: Operation timed out

fatal: Could not read from remote repository.

Please make sure you have the correct access rights

and the repository exists.

经过google后找到了解决办法

先用如下命令进行测试

1
ssh -T -p 443 xx@xx

如果测试后出现下面类似的提示说明可以继续操作了

1
Hi userName! You've successfully authenticated, but GitHub does not provide shell access.

接下来编辑~/.ssh/config文件(没有则创建一个),然后加入下面的代码:

1
2
3
Host github.com 
Hostname ssh.github.com
Port 443

最后在进行测试下

1
ssh -T xx@xxx

如果有如下提示表示可以进行正常操作了

1
2
Warning: Permanently added the RSA host key for IP address '[192.30.253.122]:443' to the list of known hosts. 
Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.

如何在vim中搜索项目代码

这里使用的工具分别是Ag和Ack

Ag和Ack都是一个全局搜索工具,但是Ag会更快,比Ack和Grep都要快

通过网络搜索后:http://harttle.com/2015/12/21/vim-search.html

使用方式是用Ag来进行搜索,使用Ack用来展示结果。

现在来进行安装步骤总结

安装Ag

1
2
3
4
5
6
# OSX
brew install the_silver_searcher
# Archlinux
pacman -S the_silver_searcher
# Ubuntu
apt-get install silversearcher-ag

安装Ack.vim

在~/.vimrc中加入:

1
2
Plugin 'mileszs/ack.vim'
let g:ackprg = 'ag --nogroup --nocolor --column'

安装完之后需要重新启动vim,不然 光是 so ~/.vimrc 不起作用的,

Ack的基本操作

1
:Ack [options] {pattern} [{directories}]

常用快捷键如下:

? 帮助,显示所有快捷键

Enter/o 打开文件

O 打开文件并关闭Quickfix

go 预览文件,焦点仍然在Quickfix

t 新标签页打开文件

q 关闭Quickfix

我想到达到的效果

当访问 https://www.gowhich.com/blog/1 时301跳转到 https://blog.gowhich.com/2025/05/29/ubuntu和centos的时间更新操作.html

如果是一个两个的跳转 通过nginx倒是也可以实现

但是我有1124个贴子需要全部跳转,用nginx就实现不了了

而且这个跳转也不是直接对应id跳转,

比如 https://www.gowhich.com/blog/1 时301跳转到 https://blog.gowhich.com/blog/1

因为之前的文章都通过hexo重新生成了,带id不好分辨文章,所以新的文章的url就跟之前变化很大,需要一步一步调整所有贴子

还好不多,调整起来就是花费点时间,跳转倒是个比较大的问题


以上就是事情原因,解决方案就是使用Cloudflare的Workers路由

Cloudflare的Workers路由创建


Workers路由代码编辑

我的实现逻辑就是上面说的,代码如下

worker.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
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run "npm run dev" in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run "npm run deploy" to publish your worker
*
* Learn more at https://developers.cloudflare.com/workers/
*/
const dictData = {
1: "/2025/05/29/ubuntu和centos的时间更新操作.html",
2: "/2025/05/29/Yii%E4%B8%ADurlManager%E7%9A%84%E9%85%8D%E7%BD%AE.html",
}

addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});


async function handleRequest(request) {
const url = new URL(request.url);
const pathArr = url.pathname.split('/');
console.log(pathArr);
if (pathArr.length < 3) {
return fetch(request); // Response.redirect(request.url, 200);
}

let pathId = pathArr[2];

console.log(url.pathname);
if (url.pathname.match('/blog/view/id/*')) {
pathId = pathArr[4];
}

// 设置新域名
const newDomain = 'https://blog.gowhich.com'

const newPathName = dictData[pathId] || undefined;

// console.log(dictData);
console.log(newPathName);

if (newPathName == undefined) {
return fetch(request); // Response.redirect(request.url, 200);
}

// 保留路径和查询参数
const newUrl = newDomain + newPathName + url.search

console.log(newUrl);

// 返回 301 重定向
return Response.redirect(newUrl, 301);

// 无匹配则正常访问
return fetch(request);
}

编辑完之后点击部署就可以了

配置

点击创建好的路由,配置按照如下步骤


然后点击添加路由

x509: certificate signed by unknown authority 这个问题也许没有你想的那么复杂

起初是遇到了个问题

我就更新了个cdn的证书

结果golang的http库无法正常请求接口,php的file_get_contents也无法正常请求接口

报错信息

golang的http库反馈的是 x509: certificate signed by unknown authority

php的file_get_contents 反馈的是 error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed on line 1

经过多种的猜测,发现执行完这个命令后一切都恢复了正常

1
yum update ca-certificates -y

数据加密传输,这个目前我接触的几个方式,一个是密文传输,一个明文传输

密文传输,就是用密钥对数据加密,使用公钥对数据解密,传输的通道可以是https的也可以是http的。

明文传输,前提是建立一个安全的传输通道,这里使用证书对通道的安全做了防护,然后传输数据,使用的是明文。

比较专业的 可以后面慢慢分享,不过这里我就介绍下明文传输,如果是用nodejs建立安全通道

使用两个库,分别是urllib和request,这里的证书只介绍使用pfx文件

urllib库的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const urllibRequest = (url, method, data, pfx, pass) => {
return new Promise(function(resolve, reject) {
let options = {
data: data,
method: method,
pfx: pfx,
passphrase: pass,
rejectUnauthorized: false
}
urllib.request(url, options, function(err, data, res) {
if (err) {
return reject(err);
}
return resolve(data.toString());
});
});
}

request库的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const httpRequest = (url, method, data, pfx, pass) => {
return new Promise((resolve, reject) => {
let options = {
url: url,
method: method,
form: data,
headers: {
'Content-type': 'application/x-www-form-urlencoded'
},
agentOptions: {
pfx: pfx,
passphrase: pass,
rejectUnauthorized: false
}
};
request(options, function(err, httpResponse, data) {
if (err) {
return reject(err);
}
return resolve(data);
})
});
}

PM2启动配置文件参数比较全的一个。如下:

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
{
"name" : "node-app",
"cwd" : "/srv/node-app/current",
"args" : ["--toto=heya coco", "-d", "1"],
"script" : "bin/app.js",
"node_args" : ["--harmony", " --max-stack-size=102400000"],
"log_date_format" : "YYYY-MM-DD HH:mm Z",
"error_file" : "/var/log/node-app/node-app.stderr.log",
"out_file" : "log/node-app.stdout.log",
"pid_file" : "pids/node-geo-api.pid",
"instances" : 6, //or 0 => 'max'
"min_uptime" : "200s", // 200 seconds, defaults to 1000
"max_restarts" : 10, // defaults to 15
"max_memory_restart": "1M", // 1 megabytes, e.g.: "2G", "10M", "100K", 1024 the default unit is byte.
"cron_restart" : "1 0 * * *",
"watch" : false,
"ignore_watch" : ["[\\/\\\\]\\./", "node_modules"],
"merge_logs" : true,
"exec_interpreter" : "node",
"exec_mode" : "fork",
"autorestart" : false, // enable/disable automatic restart when an app crashes or exits
"vizion" : false, // enable/disable vizion features (versioning control)
// Default environment variables that will be injected in any environment and at any start
"env": {
"NODE_ENV": "production",
"AWESOME_SERVICE_API_TOKEN": "xxx"
}
"env_*" : {
"SPECIFIC_ENV" : true
}
}

比较有用的参数:

cron_restart:定时启动,解决重启能解决的问题

max_memory_restart:解决内容不够用的问题,不过会有风险【数据丢失】

0%