alaska
Version:
Componentized and pluggable web framework for Node.js
550 lines (476 loc) • 14.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.OWNER = exports.AUTHENTICATED = exports.PUBLIC = exports.CLOSED = exports.PanicError = exports.NormalError = undefined;
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _mimeTypes = require('mime-types');
var _mimeTypes2 = _interopRequireDefault(_mimeTypes);
var _koa = require('koa');
var _koa2 = _interopRequireDefault(_koa);
var _koaQs = require('koa-qs');
var _koaQs2 = _interopRequireDefault(_koaQs);
var _statuses = require('statuses');
var _statuses2 = _interopRequireDefault(_statuses);
var _collie = require('collie');
var _collie2 = _interopRequireDefault(_collie);
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
var _depd = require('depd');
var _depd2 = _interopRequireDefault(_depd);
var _utils = require('./utils');
var utils = _interopRequireWildcard(_utils);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// $Flow
/* eslint new-cap:0 */
const debug = (0, _debug2.default)('alaska');
const deprecate = (0, _depd2.default)('alaska');
/**
* 一般错误
* @class {NormalError}
*/
class NormalError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
exports.NormalError = NormalError; /**
* 严重错误
* @class {PanicError}
*/
class PanicError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
exports.PanicError = PanicError; /**
* 接口关闭
* @type {number}
*/
const CLOSED = exports.CLOSED = 0;
/**
* 允许所有用户访问接口
*/
const PUBLIC = exports.PUBLIC = 1;
/**
* 允许已经认证的用户访问接口
*/
const AUTHENTICATED = exports.AUTHENTICATED = 2;
/**
* 允许资源所有者访问接口
*/
const OWNER = exports.OWNER = 3;
/**
* Alaska主类,通过import获取Alaska实例对象
* ```js
* import alaska from 'alaska';
* ```
*/
class Alaska {
/**
* 初始化一个新的Alaska实例对象
* @constructor
*/
constructor() {
this._callbackMode = false;
this._mounts = {};
this.locales = {};
this.services = {};
(0, _collie2.default)(this, 'launch');
(0, _collie2.default)(this, 'loadMiddlewares');
(0, _collie2.default)(this, 'registerService');
(0, _collie2.default)(this, 'listen');
}
/**
* 返回当前koa APP对象
* @returns {Koa}
*/
get app() {
if (!this._app) {
this._app = new _koa2.default();
this._app.env = this.getConfig('env');
this._app.proxy = this.getConfig('proxy');
this._app.subdomainOffset = this.getConfig('subdomainOffset');
(0, _koaQs2.default)(this._app);
}
return this._app;
}
/**
* 获取本Alaska实例中注册的Service
* @deprecated
* @param {string} id Service ID
* @return {Service|null}
*/
service(id) {
deprecate('service()');
return this.getService(id);
}
/**
* 获取本Alaska实例中注册的Service
* @since 0.12.0
* @param {string} id Service ID
* @return {Service}
*/
getService(id) {
if (id === 'main') {
return this.main;
}
let service = this.services[id];
if (!_lodash2.default.isObject(service)) {
this.panic(`Can not resolve '${id}' service!`);
}
return service;
}
/**
* 判断是否存在指定Service
* @param {string} id Service ID
* @returns {boolean}
*/
hasService(id) {
if (id === 'main' && this.main) {
return true;
}
return _lodash2.default.isObject(this.services[id]);
}
/**
* 注册新的Service
* @param {Service} service Service对象
*/
registerService(service) {
if (!this.main) {
this.main = service;
}
this.services[service.id] = service;
}
/**
* 获取当前Alaska实例的主Service配置
* @deprecated
* @param {string} key 配置名
* @param {*} [defaultValue] 默认值
* @returns {*}
*/
config(key, defaultValue) {
deprecate('config()');
return this.getConfig(key, defaultValue);
}
/**
* 获取当前Alaska实例的主Service配置
* @since 0.12.0
* @param {string} key 配置名
* @param {*} [defaultValue] 默认值
* @returns {*}
*/
getConfig(key, defaultValue) {
return this.main.getConfig(key, defaultValue);
}
/**
* 获取当前主配置的数据库链接
* @returns {mongoose.Connection | null}
*/
get db() {
return this.main.db;
}
/**
* 监听
*/
async listen() {
// $Flow
this.listen = utils.resolved;
const alaska = this;
debug('listen');
const mountKeys = Object.keys(this._mounts);
this.app.use(async (ctx, next) => {
const { hostname } = ctx;
for (let point of mountKeys) {
debug('test endpoint', point);
let service = alaska._mounts[point];
if (!service.domain || service.domain === hostname) {
if (ctx.path.startsWith(service.prefix)) {
ctx.service = service;
await service.routes(ctx, next);
return;
}
if (ctx.path + '/' === service.prefix) {
ctx.redirect(service.prefix);
return;
}
}
}
});
if (!this._callbackMode) {
this.app.listen(this.getConfig('port'));
}
}
/**
* 返回 http.createServer() 回调函数
* @returns {Function}
*/
callback() {
this._callbackMode = true;
return this.app.callback();
}
/**
* 加载APP中间件
*/
async loadMiddlewares() {
// $Flow
this.loadMiddlewares = utils.resolved;
// $Flow
const alaska = this;
const MAIN = this.main;
const { app } = this;
const locales = this.getConfig('locales');
const localeCookieKey = this.getConfig('localeCookieKey');
const localeQueryKey = this.getConfig('localeQueryKey');
const defaultLocale = MAIN.getConfig('defaultLocale');
// $Flow
app.use(async (ctx, next) => {
ctx.set('X-Powered-By', 'Alaska');
ctx.alaska = alaska;
ctx.main = MAIN;
ctx.service = MAIN;
ctx.panic = alaska.panic;
ctx.error = alaska.error;
/**
* 发送文件
* @param {string} filePath
* @param {Object} options
*/
ctx.sendfile = async function (filePath, options) {
options = options || {};
let trailingSlash = filePath[filePath.length - 1] === '/';
let { index } = options;
if (index && trailingSlash) filePath += index;
let maxage = options.maxage || options.maxAge || 0;
let hidden = options.hidden || false;
if (!hidden && utils.isHidden(filePath)) return;
let stats;
try {
stats = await utils.statAsync(filePath);
if (stats.isDirectory()) {
if (index) {
filePath += '/' + index;
stats = await utils.statAsync(filePath);
} else {
return;
}
}
} catch (err) {
let notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR'];
if (notfound.indexOf(err.code) > -1) return;
err.status = 500;
throw err;
}
ctx.set('Last-Modified', stats.mtime.toUTCString());
let lastModified = ctx.headers['if-modified-since'];
if (lastModified) {
try {
let date = new Date(lastModified);
if (date.getTime() === stats.mtime.getTime()) {
ctx.status = 304;
return;
}
} catch (e) {
console.error(e);
}
}
ctx.set('Content-Length', stats.size);
ctx.set('Cache-Control', 'max-age=' + (maxage / 1000 || 0));
ctx.type = options.type || _mimeTypes2.default.lookup(filePath);
ctx.body = _fs2.default.createReadStream(filePath);
};
//toJSON
{
let { toJSON } = ctx;
ctx.toJSON = function () {
let json = toJSON.call(ctx);
json.session = ctx.session || null;
json.state = ctx.state;
json.alaska = ctx.alaska.toJSON();
json.service = ctx.service.toJSON();
return json;
};
}
//切换语言
{
let locale = '';
if (localeQueryKey) {
if (ctx.query[localeQueryKey]) {
locale = ctx.query[localeQueryKey];
if (locales.indexOf(locale) > -1) {
ctx.cookies.set(localeCookieKey, locale, {
maxAge: 365 * 86400 * 1000
});
} else {
locale = '';
}
}
}
if (!locale) {
locale = ctx.cookies.get(localeCookieKey) || '';
}
if (!locale || locales.indexOf(locale) < 0) {
//没有cookie设置
//自动判断
locale = defaultLocale;
let languages = utils.parseAcceptLanguage(ctx.get('accept-language'));
for (let lang of languages) {
if (locales.indexOf(lang) > -1) {
locale = lang;
break;
}
}
}
if (locale) {
ctx.locale = locale;
ctx.state.locale = locale;
}
}
//translate
/**
* 翻译
* @param {string} message 原文
* @param {string} [locale] 目标语言
* @param {Object} [values] 翻译值
* @returns {string} 返回翻译结果
*/
ctx.t = (message, locale, values) => {
if (locale && typeof locale === 'object') {
values = locale;
locale = null;
}
if (!locale) {
// eslint-disable-next-line prefer-destructuring
locale = ctx.locale;
}
return ctx.service.t(message, locale, values);
};
ctx.state.t = ctx.t;
//config
ctx.state.c = (a, b, c) => ctx.service.getConfig(a, b, c);
//env
ctx.state.env = process.env.NODE_ENV || 'production';
/**
* 渲染并显示模板
* @param {string} template 模板文件
* @param {Object} [state] 模板变量
* @returns {Promise<string>} 返回渲染结果
*/
ctx.show = async function (template, state) {
ctx.body = await ctx.service.renderer.renderFile(template, Object.assign({}, ctx.state, state));
return ctx.body;
};
await next();
});
// $Flow
_lodash2.default.map(this.getConfig('middlewares', {}), (item, id) => {
if (!item.id) {
item.id = id;
}
return item;
}).sort((a, b) => a.sort < b.sort).forEach(item => {
let { id, fn, options } = item;
if (fn && typeof fn.default === 'function') {
fn = fn.default;
}
let name = id || (fn ? fn.name : 'unknown');
debug('middleware', name);
if (!_lodash2.default.isFunction(fn)) {
fn = this.modules.middlewares[id];
if (!fn) {
throw new PanicError(`Middleware '${name}' not found!`);
}
if (_lodash2.default.isObject(fn) && _lodash2.default.isFunction(fn.default)) {
fn = fn.default;
}
}
if (!options) {
options = {};
}
if (id) {
let defaultOptions = this.getConfig(id);
if (_lodash2.default.isObject(defaultOptions)) {
options = utils.merge(_lodash2.default.cloneDeep(defaultOptions), options);
}
}
if (typeof fn === 'function') {
app.use(fn(options));
} else {
throw new PanicError(`Middleware '${name}' is invalid!`);
}
});
}
/**
* 输出Alaska实例JSON调试信息
* @returns {Object}
*/
toJSON() {
return Object.keys(this.services).reduce((res, key) => {
res[key] = this.services[key].toJSON();
return res;
}, {});
}
/**
* 抛出严重错误,并输出调用栈
* @param {string|number|Error} message
* @param {string|number} [code]
*/
panic(message, code) {
if (!code && typeof message === 'number') {
let msg = _statuses2.default[message];
if (msg) {
code = message;
message = msg;
}
}
// $Flow 我们知道message为字符串,但是flow不知道
let error = new PanicError(message);
if (code) {
error.code = code;
}
console.error('Panic ' + error.stack);
throw error;
}
/**
* 抛出普通异常
* @param {string|number|Error} message
* @param {string|number} [code]
*/
error(message, code) {
if (!code && typeof message === 'number') {
let msg = _statuses2.default[message];
if (msg) {
code = message;
message = msg;
}
}
// $Flow 我们知道message为字符串,但是flow不知道
let error = new NormalError(message);
if (code) {
error.code = code;
}
throw error;
}
}
exports.default = new Alaska();
process.on('unhandledRejection', error => {
console.error('Unhandled Rejection:', error.stack || error);
let alaska = module.exports.defualt;
if (alaska && alaska.main && alaska.main.getConfig('unhandledRejectionExit') === false) {
return;
}
process.exit(1);
});
exports.utils = utils;
exports.Field = require('./field').default;
exports.Model = require('./model').default;
exports.Sled = require('./sled').default;
exports.Service = require('./service').default;
exports.Renderer = require('./renderer').default;
exports.Driver = require('./driver').default;