一、为什么需要?
当有线上bug的时候,测试一般是看现象,开发是看接口和数据。但是由于线上没有相关的调试断点,开发定位问题的时候相对困难。其实环境的本质区别就是数据的不同,如果能把线上的接口请求数据,弄到本地,就可以快速定位问题。
二、开发用到的包
- koa
- koa-proxy
- browser-sync
三、实现思路
- 定义一个配置文件,配置对应的代理数据及相关的辅助数据
- 使用koa启动一个服务器处理请求和静态资源
- 使用browser-sync启动一个服务器处理资源监听和页面刷新
四、代码实现
1.配置文件
- config.json
{
"server": {
// 浏览器默认打开的页面资源
"opnPath": {
"local": "/public/index.html",
"build": "/product/demo/index.html"
},
// 资源监听数据的路径
"livereload": {
"watch": "./public/*"
},
// 代理配置
"proxy": [
{
"method": "post",
// 路由
"route": "/proxy/xxx",
// 传给 koa-proxy处理的参数
"options": {
"host": "https://xxx.com",
"url": "https://xxx.com/xxx",
"requestOptions": {
"strictSSL": false
}
}
}
]
}
}
2.服务器类
- serve.js
const path = require('path');
const http = require('http');
const Koa = require('koa');
const serve = require('koa-static');
const bodyParser = require('koa-bodyparser');
const koaProxy = require('koa-proxy');
const Router = require('@koa/router');
const browserSync = require('browser-sync');
class Server {
constructor(options) {
this.$options = options;
this.cwd = options.cwd || '';
this.port = options.port;
this.app = new Koa();
// 一些配置项
this.config = {};
this.routerStore = [];
this.proxy = koaProxy;
}
init () {
const currentPath = path.join(process.cwd(), this.cwd);
// 启动一个静态资源服务器,监听路径为 currentPath
this.app.use(serve(currentPath));
}
setConfig(options) {
Object.assign(this.config, options);
}
// 注册代理路由
registerRouter(method, route, middlewares) {
if (arguments.length !== 3) {
throw new Error('registerRouter params error.');
}
// 收集对应的路径、方法、中间件
this.routerStore.push({
path: route,
method,
middlewares
})
}
// 同步更新浏览器
browserSync(options) {
const liveReloadConfig = this.config.livereload || {};
const bs = browserSync.create();
bs.init(Object.assign({
https: this.$options.ssl,
open: 'external',
port: 12306,
notify: false,
proxy: options.proxy
}, liveReloadConfig.init));
// 监听对应的文件路径,有改变1s后 reload
bs.watch(liveReloadConfig.watch, {
interval: 1000
}).on('change', bs.reload);
}
start() {
this.init();
const router = new Router();
// 处理代理请求 router
const proxyRouter = new Router();
// 代理请求的收集器
const routerStore = this.routerStore;
const opnPath = this.config.opnPath || '';
routerStore.forEach(rs => {
/**
rs = {
path: rpath, //route
method,
middlewares
}
*/
let middlewares = rs.middlewares;
if (!Array.isArray(middlewares)) {
middlewares = [middlewares];
}
// koa-proxy 执行返回的函数名称为 proxy
const proxyFunc = middlewares.filter(fn => fn.name === 'proxy');
if (proxyFunc.length > 0) {
if (proxyFunc.length === middlewares.length) {
// router.post('/proxy/Trans/', **处理中间件**);
proxyRouter[rs.method](rs.path, ...middlewares);
} else {
throw new Error('不能同时存在proxy');
}
} else {
// 如果没有使用koa-proxy的正常处理对应的路由
router[rs.method](rs.path, ...middlewares);
}
})
// 代理路由
this.app.use(proxyRouter.routes()).use(proxyRouter.allowedMethods());
// body parser
this.app.use(bodyParser());
// 正常路由
this.app.use(router.routes()).use(router.allowedMethods());
const port = parseInt(this.port, 10);
let server = null;
let listenURL = '';
listenURL = `http://127.0.0.1:${port}`;
server = http.createServer(this.app.callback());
const opnURL = `${listenURL}${opnPath}`;
server.listen(port, () => {
console.log(`Server running at ${listenURL}`);
this.browserSync({ proxy: opnURL })
})
}
}
module.exports = Server;
3.入口文件
- index.js
const path = require('path');
const Server = require('./lib/server.js');
const config = require('./config.json')['server'];
function preServerProcess(config, context) {
// proxy为koa-proxy
const proxy = context.proxy;
// 获取当前的执行环境 【 local/build 】
const serverEnv = process.env.SERVER_ENV;
const cwd = process.cwd();
// 设置一些配置项
context.setConfig({
port: config.port,
// 默认打开的页面
opnPath: config.opnPath[serverEnv] || '',
// 自动刷新
livereload: {
watch: path.join(cwd, config.livereload.watch),
init: config.livereload.init || {}
}
});
// 处理代理配置
config.proxy.forEach((p) => {
// proxy(p.options) 返回一个middleware
context.registerRouter(p.method, p.route, proxy(p.options));
});
}
const arg = {
cwd: '',
port: 12345,
}
// 实例化koa服务器
const serve = new Server(arg);
// 进行一些预先的处理
preServerProcess(config, serve);
// 启动服务器
serve.start();
五、拓展
这个小的服务器因为功能是独立的,只负责资源的请求和监听,所以可以作为插件级别去处理