1. 程式人生 > >編寫 service

編寫 service

在實際應用中,Controller 一般不會自己產出資料,也不會包含複雜的邏輯,複雜的過程應抽象為業務邏輯層 Service

我們來新增一個 Service 抓取 Hacker News 的資料 ,如下:

// app/service/news.js
const Service = require('egg').Service;
class NewsService extends Service {
  async list(page = 1) {
    // read config
    const { serverUrl, pageSize } = this.config.news;

    // use build-in http client to GET hacker-news api
    const { data: idList } = await this.ctx.curl(`${serverUrl}/topstories.json`, {
      data: {
        orderBy: '"$key"',
        startAt: `"${pageSize * (page - 1)}"`,
        endAt: `"${pageSize * page - 1}"`,
      },
      dataType: 'json',
    });

    // parallel GET detail
    const newsList = await Promise.all(
      Object.keys(idList).map(key => {
        const url = `${serverUrl}/item/${idList[key]}.json`;
        return this.ctx.curl(url, { dataType: 'json' });
      })
    );
    return newsList.map(res => res.data);
  }
}
module.exports = NewsService;    

還需增加 app/service/news.js 中讀取到的配置:

// config/config.default.js
// 新增 news 的配置項
exports.news = {
  pageSize: 5,
  serverUrl: 'https://hacker-news.firebaseio.com/v0',
};

 編寫擴充套件

框架提供了一種快速擴充套件的方式,只需在 app/extend 目錄下提供擴充套件指令碼即可

在這裡,我們可以使用 View 外掛支援的 Helper 來實現:

npm i moment --save
// app/extend/helper.js
const moment = require('moment');
exports.relativeTime = time => moment(new Date(time * 1000)).fromNow();

 

在模板裡面使用:

<!-- app/view/news/list.tpl -->
{{ helper.relativeTime(item.time) }}

編寫 Middleware

假設有個需求:我們的新聞站點,禁止百度爬蟲訪問。

// app/middleware/robot.js
// options === app.config.robot
module.exports = (options, app) => {
  return async function robotMiddleware(ctx, next) {
    const source = ctx.get('user-agent') || '';
    const match = options.ua.some(ua => ua.test(source));
    if (match) {
      ctx.status = 403;
      ctx.message = 'Go away, robot.';
    } else {
      await next();
    }
  }
};

// config/config.default.js
// add middleware robot
exports.middleware = [
  'robot'
];
// robot's configurations
exports.robot = {
  ua: [
    /Baiduspider/i,
  ]
};

配置檔案

寫業務的時候,不可避免的需要有配置檔案,框架提供了強大的配置合併管理功能:

  • 支援按環境變數載入不同的配置檔案,如 config.local.js, config.prod.js 等等。
  • 應用/外掛/框架都可以配置自己的配置檔案,框架將按順序合併載入
  • 具體合併邏輯可參見配置檔案
  • // config/config.default.js
    exports.robot = {
      ua: [
        /curl/i,
        /Baiduspider/i,
      ],
    };
    
    // config/config.local.js
    // only read at development mode, will override default
    exports.robot = {
      ua: [
        /Baiduspider/i,
      ],
    };
    
    // app/service/some.js
    const Service = require('egg').Service;
    
    class SomeService extends Service {
      async list() {
        const rule = this.config.robot.ua;
      }
    }
    
    module.exports = SomeService;
  • 單元測試
  • 單元測試非常重要,框架也提供了 egg-bin 來幫開發者無痛的編寫測試。
  • 測試檔案應該放在專案根目錄下的 test 目錄下,並以 test.js 為字尾名,
  • 即 {app_root}/test/**/*.test.js
  •