Typescript爬虫实战(4) ---- 创建控制器和装饰器

为了将面向过程的代码改造成面向对象的代码。
将现有的代码进行改造。
首先将login的逻辑进行迁移:
创建LoginController.ts,并将代码迁移过来,先迁移具体逻辑,不管路由

class LoginController{
  home(req:Request,res:Response){
    const isLogin = req.session ? req.session.login : false
    if(isLogin){
      res.send(`
        <body>
          <a href='//www.greatytc.com/logout'>logout</a>
          <a href='/getData'>getData</a>
          <a href='/showData'>showData</a>
        </body>
      `)
    }else{
      const formHtml = `
      <body>
        <form method='POST' action='/login'>
          <input type='password' name='password'>
          <button>Submit</button>
        </form>
      </body>
      `
    res.send(formHtml)
    }
  }
}
// LoginController.ts
  • 创建一个路由的控制器,并通过原数据的方式,将路径保存在这个方法之上
//controller类的装饰器
function controller(target:any){
  for(let key in target.prototype){
//打印出绑定的路径
    console.log(Reflect.getMetadata('path',target.prototype,key))
    // ‘/’
  }
}
//路由的装饰器
function get(path:string){
  return function(target:any,key:string){
    Reflect.defineMetadata('path',path,target,key)
  }
}

@controller
class LoginController{
  @get('/')
  home()
  ...
}

在完成基础的装饰器之后,我们需要让LoginController通过装饰器实现路由的功能

  • 根据在原数据上的路径,如果路径存在,自动生成项目的路由
export function controller(target:any){
  for(let key in target.prototype){
    const path = Reflect.getMetadata('path',target.prototype,key)
    if(path){
      const handler = target.prototype[key]
      router.get(path,handler)
    }
  }
}
  • 为了能自动生成路由,只需要引入LoginController这个文件,就能执行,并生成路由
  • 将所有的get请求迁移过来
class LoginController{
  @get('/')
  home(req:Request,res:Response){
    const isLogin = req.session ? req.session.login : false
    if(isLogin){
      res.send(`
        <body>
          <a href='//www.greatytc.com/logout'>logout</a>
          <a href='/getData'>getData</a>
          <a href='/showData'>showData</a>
        </body>
      `)
    }else{
      const formHtml = `
      <body>
        <form method='POST' action='/login'>
          <input type='password' name='password'>
          <button>Submit</button>
        </form>
      </body>
      `
    res.send(formHtml)
    }
  }

  @get('/logOut')
  logOut(req:Request,res:Response){
    if(req.session){
      req.session.login = undefined
    }
    res.json(getResponseData(true));
  }
}

  • 接下来再迁移一下原有的post请求
//LoginController.ts
@post('/login')
  login(req:Request,res:Response){
    const isLogin = req.session ? req.session.login : false
    if(isLogin){
      res.json(getResponseData(false, '已经登陆过'));
    }else{
      if(req.body.password === '123' && req.session){
        req.session.login = true
        res.json(getResponseData(true));
      }else{
        res.json(getResponseData(false, 'login failed'));
      }
    }
  }

//decorator.ts
export function post(path:string){
  return function(target:any,key:string){
    Reflect.defineMetadata('path',path,target,key)
  }
}
  • 但是原有的装饰器已满足不了新的需求,因为原有的装饰器无法辨别出post和get请求,所以,需要在元数据上绑定一个请求方法
enum Method{
  get = 'get',
  post = 'post',
  put = 'put',
  del = 'delete'
}

export function controller(target:any){
  for(let key in target.prototype){
    const path = Reflect.getMetadata('path',target.prototype,key)
    //利用枚举类型对请求方法进行定义
    const method:Method = Reflect.getMetadata('method',target.prototype,key)
    const handler = target.prototype[key]
    if(path&&method&&handler){
  //往router上绑定方法
      router[method](path,handler)
    }
  }
}

export function post(path:string){
  return function(target:any,key:string){
    Reflect.defineMetadata('path',path,target,key)
    Reflect.defineMetadata('method','post',target,key)
  }
}
  • 同时也可以看到,生成请求方法装饰器的函数有大量内容冗余,可以利用工厂模式优化一代码
function methodFactory(type:string){
  return function(path:string){
    return function(target:any,key:string){
      Reflect.defineMetadata('path',path,target,key)
      Reflect.defineMetadata('method',type,target,key)
    }
  }
}

export const get = methodFactory('get')
export const post = methodFactory('post')
export const put = methodFactory('put')
  • 对使用了中间件的路由的装饰器
    • middleware类型:RequestHandler
//在方法的装饰器上绑定
export function controller(target:any){
  for(let key in target.prototype){
    const path = Reflect.getMetadata('path',target.prototype,key)
    const method:Method = Reflect.getMetadata('method',target.prototype,key)
    const middleware = Reflect.getMetadata('middleware',target.prototype,key)
    const handler = target.prototype[key]
    if(path&&method&&handler){
    //如果有中间件就使用中间件
      if(middleware){
        router[method](path,middleware,handler)
      }else{
        router[method](path,handler)
      }
    }
  }
}
//中间件的装饰器
export function use(middleware:RequestHandler){
  return function(target:any,key:string){
    Reflect.defineMetadata('middleware',middleware,target,key)
  }
}

优化项目结构

  • 将router从decorator中拆分出来
import {Router} from 'express';
export const router = Router()
  • 按职责对decorator进行进一步拆分
    • use.ts 处理中间件相关的装饰器
      import {RequestHandler} from 'express';
      
        export function use(middleware:RequestHandler){
        return function(target:any,key:string){
          Reflect.defineMetadata('middleware',middleware,target,key)
          }
        }
      
    • request.ts 处理请求相关的装饰器
enum Methods{
  get = 'get',
  post = 'post',
  put = 'put',
  del = 'delete'
}

function methodFactory(type:Methods){
  return function(path:string){
    return function(target:any,key:string){
      Reflect.defineMetadata('path',path,target,key)
      Reflect.defineMetadata('method',type,target,key)
    }
  }
}

export const get = methodFactory(Methods.get)
export const post = methodFactory(Methods.post)
export const put = methodFactory(Methods.put)
  • controller.ts 处理controller类的装饰器
import {Router,Request,Response, NextFunction,RequestHandler} from 'express';
import {router} from '../router'


enum Methods{
  get = 'get',
  post = 'post',
  put = 'put',
  del = 'delete'
}

export function controller(target:any){
  for(let key in target.prototype){
    const path = Reflect.getMetadata('path',target.prototype,key)
    const method:Methods = Reflect.getMetadata('method',target.prototype,key)
    const middleware = Reflect.getMetadata('middleware',target.prototype,key)
    const handler = target.prototype[key]
    if(path&&method&&handler){
      if(middleware){
        router[method](path,middleware,handler)
      }else{
        router[method](path,handler)
      }
      
    }
  }
}

最终项目目录:

├── data
│   └── course.json
├── package-lock.json
├── package.json
├── src
│   ├── analyzer.ts
│   ├── analyzerB.ts
│   ├── controller
│   │   ├── CrowllerController.ts
│   │   ├── LoginController.ts
│   │   └── decoratorOld.ts
│   ├── crowller.ts
│   ├── customer.d.ts
│   ├── decorator
│   │   ├── controller.ts
│   │   ├── index.ts
│   │   ├── request.ts
│   │   └── use.ts
│   ├── index.ts
│   ├── responseHandler.ts
│   ├── router.ts
│   └── routerOld.ts
└── tsconfig.json
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352