奉上代码: node服务demo代码=》koa2-server项目代码;vue前端demo代码=》 vue-client项目代码。
如果git登不上可以换gitee=》koa2-server项目代码;vue前端demo代码=》 vue-client项目代码
本文借鉴于:用优雅的代码武装我们的koa2项目;对其中部分做修改整合而成。
之前我的路由总是手动注册的,大概是这样的:
//app.js
const Koa = require('koa');
const app = new Koa();
// 路由
const index = require('./routes/index');
const commom = require('./routes/commom'); // 通用服务
app.use(index.routes(), index.allowedMethods());
app.use(commom.routes(), commom.allowedMethods());
当时开发久了路由多了文件数量就庞大了,再像这样引入再use方式未免会显得繁琐拖沓。那有没有办法让这些文件自动被引入、自动被加载呢?
此时就需要一个好用的依赖了
npm install require-directory --save
在根目录下建一个文件夹core,以后一些公共的代码都存放在这里。
//core/init.js
const requireDirectory = require('require-directory');
const Router = require('koa-router');
class InitManager {
static initCore(app) {
// 把app.js中的koa实例传进来
InitManager.app = app;
InitManager.initLoadRouters();
}
static initLoadRouters() {
// 注意这里的路径是依赖于当前文件所在位置的
// module为固定参数,apiDirectory为路由文件所在的路径(支持嵌套目录下的文件),第三个参数中的visit为回调函数
// 那么根据我的项目目录就是这样的
const apiDirectory = `${
process.cwd()}/routes`
const modules = requireDirectory(module, apiDirectory, {
visit: whenLoadModule
});
function whenLoadModule(obj) {
if(obj instanceof Router) {
// FIXME 路由黑名单
const blackList = ['/image'];
const prefix = obj.opts.prefix;
if (!blackList.includes(prefix)) {
InitManager.app.use(obj.routes());
}
}
}
}
}
module.exports = InitManager;
// app.js
const Koa = require('koa');
const app = new Koa();
const InitManager = require('./core/init');
// ...中间件等其他操作
// 最后引入路由
InitManager.initCore(app);
可以说已经精简很多了,代码看起来是不是很爽,而且功能的实现照样没有问题。
有时候,在两种不同的环境下,我们需要做不同的处理,这时候就需要我们提前在全局中注入相应的参数。
首先在项目根目录中,创建config文件夹:
// config/config.js
module.exports = {
environment: 'dev'
}
//core/init.js的initManager类中增加如下内容
static loadConfig() {
const configPath = process.cwd() + '/config/config.js';
const config = require(configPath);
global.config = config;
}
// app.js
const Koa = require('koa');
const app = new Koa();
const InitManager = require('./core/init');
InitManager.loadConfig(); // 全局配置, 靠前引入,这样才能在用的时候有数据
// ...中间件等其他操作
// 最后引入路由
InitManager.initCore(app);
// 使用的时候
global.config.environment=== 'dev' ? dev : pro;
这样就通过全局的global变量中就可以取到当前的环境啦。
但是!!! 这样有个问题,你部署生产环境的时候要手动修改config.js里的environment配置(从代码上猜测的,笔者还没部署到生产环境)
统一的返回格式可以提高前端的处理效率,在组件化开发的今天,统一的返回格式也能让前端更好的封装组件。如果一个项目里都没有一个统一的返回格式,那这项目代码质量多半好不到哪里去。
先定义成功和失败的返回格式:
// middleware/response/response.js
/**
* response
* @param ctx
* @param data 数据
* @param code 错误码 || [错误码, 错误描述]
* @param message 错误描述
*/
exports.response = (ctx, data, code, message) => {
if (typeof code == 'object') {
message = code[1];
code = code[0];
}
ctx.body = {
code,
data,
message
};
};
/**
* response 成功
* @param ctx
* @param data 数据
* @param code 错误码 || [错误码, 错误描述]
* @param message 错误描述
*/
exports.success = (ctx, data, code = 1, message = '操作成功') => {
if (typeof code === 'string') {
message = code;
}
this.response(ctx, data, code, message);
};
/**
* response 异常
* @param ctx
* @param code 错误码 || [错误码, 错误描述]
* @param message 错误描述
*/
exports.error = (ctx, code = 0, data = '', message = '操作失败') => {
if (typeof code === 'object') {
message = code[1];
code = code[0];
}
ctx.errorLog(ctx, message, 0); // 记录异常日志
this.response(ctx, data, code, message);
};
// 导出模块
// middleware/response/index.js
const {
success, error } = require('./response');
module.exports = async (ctx, next) => {
ctx.success = success.bind(null, ctx);
ctx.error = error.bind(null, ctx);
await next();
};
// app.js
// ... 使用中间件
const response = require('./middleware/response');
app.use(response); // 返回体中间件
// ...
这样返回的时候直接使用就行了,格式统一,看起来舒服
ctx.error([0, '错误提示']);
// or
ctx.success(xxx);
在服务端api编写的过程中,异常处理是非常重要的一环,因为不可能每个函数返回的结果都是我们想要的。无论是语法的错误,还是业务逻辑上的错误,都需要让异常抛出,让问题以最直观的方式暴露,而不是直接忽略。关于编码风格,《代码大全》里面也强调过,在一个函数遇到异常时,最好的方式不是直接return false/null,而是让异常直接抛出。如果让异常直接抛出到前端会让人感觉这个后端真的菜。所以一定要封装。
设计异常处理中间件
// middleware/errorHandler.js
// 这里的工作是捕获异常生成返回的接口
const catchError = async (ctx, next) => {
try {
await next();
} catch (error) {
// console.log(error);
if (error.errno) {
// FIXME sql报错处理
ctx.error([0, 'sql查询报错'], error.sqlMessage);
} else if (error.code) {
ctx.error([error.code, error.msg]);
} else {
// 对于未知的异常,采用特别处理
ctx.errorLog(ctx, error, 'we made a mistake'); // 记录异常日志
ctx.error([-1, '未知的异常']);
}
}
};
module.exports = catchError;
到入口文件使用这个中间件
// app.js
// 统一错误异常处理
const errorHandler = require('./middleware/errorHandler');
app.use(errorHandler);
接着我们来以HttpException为例生成特定类型的异常:
// core/http-exception.js
class HttpException extends Error {
constructor(msg = '服务器异常', code = 400) {
super();
this.code = code;
this.msg = msg;
}
}
class ParamError extends HttpException {
constructor(msg) {
super();
this.code = 400;
this.msg = msg || '参数错误';
}
}
class NotFound extends HttpException {
constructor(msg) {
super();
this.msg = msg || '资源未找到';
this.code = 404;
}
}
class AuthFailed extends HttpException {
constructor(msg) {
super();
this.msg = msg || '授权失败';
this.code = 404;
}
}
class Forbidden extends HttpException {
constructor(msg) {
super();
this.msg = msg || '禁止访问';
this.code = 404;
}
}
module.exports = {
HttpException,
ParamError,
NotFound,
AuthFailed,
Forbidden
};
对于这种经常需要调用的错误处理的代码,有必要将它放到全局,不用每次都导入。
现在的init.js中是这样的:
const requireDirectory = require('require-directory');
const Router = require('koa-router');
class InitManager {
static initCore(app) {
// 把app.js中的koa实例传进来
InitManager.app = app;
InitManager.initLoadRouters();
InitManager.loadHttpException(); // 加入全局的Exception
}
static initLoadRouters() {
// 注意这里的路径是依赖于当前文件所在位置的
// 最好写成绝对路径
const apiDirectory = `${
process.cwd()}/routes`;
const modules = requireDirectory(module, apiDirectory, {
visit: whenLoadModule
});
function whenLoadModule(obj) {
if (obj instanceof Router) {
// FIXME 路由黑名单
const blackList = ['/image'];
const prefix = obj.opts.prefix;
if (!blackList.includes(prefix)) {
InitManager.app.use(obj.routes());
}
}
}
}
static loadConfig(path = '') {
const configPath = path || process.cwd() + '/config/config.js';
const config = require(configPath);
global.config = config;
}
static loadHttpException() {
const errors = require('./http-exception');
global.err = errors;
}
}
module.exports = InitManager;
这样就可以全局使用了
// 接口中
if (xxx) throw new global.err.ParamError(xxx);
但是!! 好像只能通过throw的方法来抛出错误,又因为是类,所以必须先实例化new下,后面看看能不能再优化下
这个我在之前系列(7)里专门写过了,这边就不写了,也可以按本文借鉴的那篇文章里介绍的那样。
不过还缺少了刷新token的使用,有空补下。
在开发的过程,当项目的目录越来越复杂的时候,包的引用路径也变得越来越麻烦。曾经就出现过这样的导入路径:
const Favor = require('../../../models/favor');
甚至还有比这个更加冗长的导入方式,作为一个有代码洁癖的程序员,实在让人看的非常不爽。其实通过绝对路径process.cwd()的方式也是可以解决这样一个问题的,但是当目录深到一定程度的时候,导入的代码也非常繁冗。那有没有更好的解决方式呢?
使用module-alias将路径别名就可以。
npm install module-alias --save
//package.json添加如下内容
"_moduleAliases": {
"@root": ".", // 根目录
"@models": "app/models",
},
重点: 然后在app.js引入这个库:
// 引入即可
require('module-alias/register');
现在引入代码就变成这样了:
const Favor = require('@models/favor');
不过这样用了别名之后在vscode里F12就找不到函数的定义位置了,有点小不足
上一篇:node从入门到放弃系列之(10)图形验证功能
下一篇:未完待续!!!