(转载请注明出处)
昨天啊呆同学又不好好做咖啡,请了假偷偷去面试了。下午回来时垂头丧气,一看就很不顺利的样子。
我问阿呆是不是又去找互联网工作受挫了。阿呆说,师傅你平时总是对我说学知识要广泛涉猎,不应该在一小块地方来回打转,更不要成为api复读机,只有见识广了,才能集各家之长,吸收优秀的思想。可是我去面试时,对方总会问一些api相关,比如小程序解析html片段所用到的方法,node进程守护使用的pm2的api,甚至最新框架版本号这些你认为很无聊的问题。我跟他们说这些问题都是可以网上秒查的,对方就很生气的问我不学这些还能学哪些东西。
我说我除了学前端相关,还学了c、java、ts、mysql、算法和网络相关。对方更生气了,说:你为什么不学node和mongodb,难道你认为这两个比java和mysql哪里差了吗,还是你觉得你前端知识很厉害,不需要再精进了。
阿呆越说越委屈,说一定是师傅你哪里教我错了。
我看着阿呆,等他心情平复之后说:你自己觉得你学了这么多,对你前端方面的技能有没有帮助?
阿呆说: 好像有一点儿。
我: 比如?
阿呆:我最近用node和express写接口时总觉得定义路由有点儿麻烦,每定义一个路由就要写一个路由方法, 像这样:
router.get('/getData', monitor.getMonitorData);
router.get('/deleteData', monitor.deleteMonitorData);
router.post('/errCatch', monitor.setMonitorData);
我在想如果像java的spring框架那样用注解的方式将路由定义在每一个controller上可不就省心多了, 像这样:
@RestController
@RequestMapping("/home")
public class IndexController {
@RequestMapping("/")
String get() {
//mapped to hostname:port/home/
return "Hello from get";
}
@RequestMapping("/index")
String index() {
//mapped to hostname:port/home/index/
return "Hello from index";
}
我: 有没有尝试一下?
阿呆:我试着用ts中的装饰器模仿了一下,我给你看看。
/* /decorator/controller.ts 这个是放在controller类上的注解。*/
import "reflect-metadata";
import {AppRouter} from "../AppRouter";
import {MetadataKey} from "./MetadataKey";
import { Methods } from './Methods';
import {NextFunction, Request, RequestHandler, Response} from "express";
/**
* @function 参数字段校验
* @param keys {String[]} 需要校验的字段
*/
export function bodyValidators(keys: string): RequestHandler {
return function (req: Request, res: Response, next: NextFunction) {
if (!req.body) {
res.status(422).send("Invalid request");
return;
}
for (let key of keys) {
if (!req.body[key]) {
res.status(422).send(`Missing property ${key}`);
return;
}
}
next();
}
}
/**
* @function 类注解
* @param routePrefix {String} 路由前缀
*/
export function controller(routePrefix: string) {
/**
* @function 目标拦截
* @param target {Function} 当前类的构造函数
*/
return function (target: Function) {
// 获取express.Router
const router = AppRouter.getInstance();
for (let key in target.prototype) {
// 获取构造函数上的原型成员
const routeHandler = target.prototype[key];
// 获取原型成员上的路由路径
const path = Reflect.getMetadata(MetadataKey.path, target.prototype, key);
// 获取原型成员上的方法
const method: Methods = Reflect.getMetadata(MetadataKey.method, target.prototype, key);
// 获取原型成员上的中间件
const middlewares = Reflect.getMetadata(MetadataKey.middleware, target.prototype, key) || [];
// 获取原型成员上的校验字段
const requireBodyProps = Reflect.getMetadata(MetadataKey.validator, target.prototype, key) || [];
// 生成校验器控件
const validator = bodyValidators(requireBodyProps);
if (path) { // 生成路由
router[method](
`${routePrefix}${path}`,
...middlewares,
validator,
routeHandler
);
}
}
}
}
/* AppRouter.ts */
import express from "express";
export class AppRouter {
private static instance: express.Router;
static getInstance (): express.Router {
if (!AppRouter.instance) {
AppRouter.instance = express.Router();
}
return AppRouter.instance;
}
}
/* MetadataKey */
export enum MetadataKey {
method = "method",
path = "path",
middleware = "middleware",
validator = "validator"
}
/* Methods */
export enum Methods {
get = 'get',
post = 'post',
patch = 'patch',
del = 'delete',
put = 'put'
}
/* /decorators/bodyValidator.ts */
import "reflect-metadata";
import {MetadataKey} from "./MetadataKey";
/**
* @function 定义校验控件
* @param keys {ArrayLike} 需要校验的字段
*/
export function bodyValidator(...keys: string[]) {
return function (target: any, key: string, desc: PropertyDescriptor) {
Reflect.defineMetadata(MetadataKey.validator, keys, target, key);
}
}
这个是放在controller类方法上的二级路由注解
/* routes.ts */
import "reflect-metadata";
import {RequestHandler} from "express";
import {Methods} from "./Methods";
import {MetadataKey} from "./MetadataKey";
interface RouteHandlerDescriptor extends PropertyDescriptor {
value?: RequestHandler
}
/**
* @function 路由绑定
* @param method {String} 请求方法
*/
function routeBinder(method: string) {
/**
* @function 路径获取
* @param path {String} 路由路径
*/
return function (path: string) {
/**
* @function 注解
* @param target {Object} 注解方法所在类的实例
* @param key {String} 注解方法名
* @param desc {RouteHandlerDescriptor} 注解方法中的描述符
*/
return function (target: any, key: string, desc: RouteHandlerDescriptor) {
// 定义路径(路径名,路径值, 所在类实例, 所在方法名称)
Reflect.defineMetadata(MetadataKey.path, path, target, key);
// 定义方法(方法名, 方法值, 所在类实例, 所在方法名称)
Reflect.defineMetadata(MetadataKey.method, method, target, key);
}
}
}
export const get = routeBinder(Methods.get);
export const put = routeBinder(Methods.put);
export const post = routeBinder(Methods.post);
export const del = routeBinder(Methods.del);
export const patch = routeBinder(Methods.patch);
/* use.ts中间件方法 */
import 'reflect-metadata';
import { RequestHandler } from 'express';
import { MetadataKey } from './MetadataKey';
/**
* @function 中间件注解
* @param middleware {RequestHandler} 加入中间件
*/
export function use(middleware: RequestHandler) {
/**
* @function 目标拦截
* @param target {Object} 注解目标所在类的实例
* @param key {String} 注解目标的名称
* @param desc {Object} 注解目标的属性描述符
*/
return function(target: any, key: string, desc: PropertyDescriptor) {
// 获取当前目标定义在当前获取的中间件前面的中间件列表
const middlewares = Reflect.getMetadata(MetadataKey.middleware, target, key) || [];
// 合并定义当前目标上的所有中间件
Reflect.defineMetadata(
MetadataKey.middleware,
[...middlewares, middleware],
target,
key
);
};
}
使用的时候是这样的
/* /middlewares/requireAuth.ts */
import {NextFunction, Request, Response} from "express";
export function requireAuth(req: Request, res: Response, next: NextFunction) {
if (req.session && req.session.loggedIn) {
next();
return
}
res.status(403);
res.send("Not Permitted")
}
/* /controllers/rootControllers.ts */
import {Request, Response, NextFunction} from "express";
import {controller, get, post, bodyValidator, use} from "../decorators";
import {requireAuth} from "../middlewares/requireAuth";
@controller("/root")
class RootControllers {
@get("") // /root
getRoot(req: Request, res: Response) {
if (req.session && req.session.loggedIn) {
res.send(` This is root router `);
}
}
@get('/protected') // /root/protected
@use(requireAuth)
getProtected(req: Request, res: Response) {
res.send('Welcome to protected route, logged in user');
}
@post('/login') // /root/login
@bodyValidator('email', 'password')
postLogin(req: Request, res: Response) {
const { email, password } = req.body;
if (email === 'hi@hi.com' && password === 'password') {
req.session = { loggedIn: true };
res.redirect('/');
} else {
res.send('Invalid email or password');
}
}
}
再将controller类引入到根文件就行了
import express from "express";
import bodyParser from "body-parser";
import cookieSession from "cookie-session";
import { AppRouter } from "./AppRouter";
import "./controllers/RootControllers";
const app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieSession({keys: ["laskdjf"]}));
app.use(AppRouter.getInstance());
app.listen(3000, () => {
console.log('Listening on port 3000');
});
师傅你觉得怎么样?阿呆问道。
我:你觉得呢?
阿呆:我觉得我还是安心做咖啡吧,互联网不适合我。
听到这句话我长舒了一口气,阿呆,你能这么想我很欣慰。
