Authored by 陈峰

commit

... ... @@ -21,6 +21,10 @@ export function createApp(context) {
const app = new Vue({
router,
store,
errorCaptured(e) {
console.log('errorCaptured', e);
return false;
},
render: h => h(App)
});
... ...
... ... @@ -2,7 +2,6 @@ const api = global.yoho.API;
const service = global.yoho.ServiceAPI;
const checkParams = require('../../utils/check-params');
const apiMaps = require('../../config/api-map');
const _ = require('lodash');
const NOT_FOUND_API_MAP = {
code: 400,
... ... @@ -13,30 +12,24 @@ const checkApiMap = url => {
};
const request = async({url, method, reqParams, context}) => {
const apiInfo = checkApiMap(url);
const {env, user} = context;
if (!apiInfo) {
return Promise.reject(NOT_FOUND_API_MAP);
}
try {
if (!apiInfo.service) {
Object.assign(reqParams, {
uid: {
uid: (user && user.uid) ? {
toString: () => {
return context.user.uid || 0;
},
sessionKey: context.user.sessionKey,
appSessionType: context.user.appSessionType
return user.uid;
},
sessionKey: user.sessionKey,
appSessionType: user.appSessionType
} : 1,
method: apiInfo.api,
sessionKey: context.user.sessionKey,
appVersion: context.user.appVersion
});
}
if (_.has(apiInfo, 'params.uid') &&
apiInfo.params.uid.require !== false &&
reqParams.uid === 0) { // 如果接口uid是必须的但是有没有传入uid则直接返回空对象
return Promise.resolve({});
}
const params = checkParams.getParams(reqParams, apiInfo);
const cache = method.toLowerCase() !== 'get' ? false : apiInfo.cache;
... ... @@ -45,8 +38,9 @@ const request = async({url, method, reqParams, context}) => {
cache: cache,
code: 200,
headers: {
'X-YOHO-IP': context.env.clientIp,
'X-Forwarded-For': context.env.clientIp
'X-YOHO-IP': env.clientIp,
'X-Forwarded-For': env.clientIp,
'User-Agent': 'yoho/nodejs'
}
});
} else {
... ... @@ -54,17 +48,12 @@ const request = async({url, method, reqParams, context}) => {
code: 200,
cache: cache,
headers: {
'X-YOHO-IP': context.env.clientIp,
'X-Forwarded-For': context.env.clientIp
'X-YOHO-IP': env.clientIp,
'X-Forwarded-For': env.clientIp,
'User-Agent': 'yoho/nodejs'
}
});
}
} catch (e) {
return Promise.reject({
code: 400,
message: `create api:${e}`
});
}
};
export const createApi = context => {
... ...
... ... @@ -52,4 +52,7 @@ router.onReady(() => {
app.$mount('#app');
});
router.onError(e => {
router.push({name: 'error.500'});
});
... ...
import {createApp} from './app';
import _ from 'lodash/core';
import {get} from 'lodash';
import {
SET_ENV,
INIT_ROUTE_CHANGE
} from 'store/yoho/types';
const sender = global.yoho.apmSender;
const logger = global.yoho.logger;
const catchError = (err, context) => {
logger.error(`[catchError], ${err}`);
setImmediate(() => {
try {
sender.addMessage({
measurement: 'error-report',
tags: {
app: 'yoho-app', // 应用名称
hostname: context.hostname,
type: 'server',
route: context.route, // 请求路由
uid: get(context, 'user.uid', 0),
udid: context.udid,
code: err.code || 500,
path: context.path,
url: encodeURIComponent(context.url),
ip: context.env.clientIp
},
fields: {
message: err.message,
stack: err.stack,
useragent: context.ua
}
});
} catch (error) {
logger.error(error);
}
});
};
export default context => {
return new Promise((resolve, reject) => {
const {app, router, store} = createApp(context);
const {url} = context;
const {url, env} = context;
const route = router.resolve(url).route;
// if (url !== route.fullPath) {
// return reject({code: 500, message: 'url not matched', url: route.fullPath});
// }
store.commit(SET_ENV, context.env);
store.commit(SET_ENV, env);
router.push(url);
router.onReady(() => {
try {
const matched = router.getMatchedComponents();
if (!matched.length) {
reject({code: 404});
if (matched.some(m => !m)) {
catchError(new Error('导航组件为空'), context);
router.push({name: 'error.500'});
return resolve(app);
}
const routes = [];
const rootRoute = _.find(router.options.routes, r => r.meta && r.meta.root);
if (rootRoute) {
routes.push({
name: rootRoute.name,
fullPath: rootRoute.path
});
}
if (route.name !== 'channel.home') {
routes.push({
name: route.name,
fullPath: route.fullPath
});
if (!matched.length) {
return reject({code: 404, message: ''});
}
store.commit(INIT_ROUTE_CHANGE, {routes});
Promise.all(matched.map(({asyncData}) =>
asyncData && asyncData({store, router: router.currentRoute})))
.then(() => {
context.state = store.state;
resolve(app);
}).catch((e) => {
reject({
code: 500,
message: e.stack || e.toString()
return resolve(app);
}).catch(e => {
catchError(e, context);
return resolve(app);
});
});
} catch (e) {
reject({
code: 500,
message: e.stack || e.toString()
router.onError(e => {
catchError(e, context);
router.push({name: 'error.500'});
return resolve(app);
});
}
}, reject);
});
};
... ...
<template>
<div class="err-404">
404
</div>
</template>
<script>
export default {
name: 'ErrorNotFound'
}
</script>
<style>
</style>
... ...
<template>
<div class="err-500">
500
<NotFound></NotFound>
</div>
</template>
<script>
export default {
name: 'Error',
created() {
console.log('400 created')
},
mounted() {
console.log('400 mounted')
},
components: {NotFound: () => import('./404')}
}
</script>
<style>
</style>
... ...
export default [{
path: '/error/404',
name: 'error.404',
component: () => import(/* webpackChunkName: "error" */ './404')
}, {
path: '/error/500',
name: 'error.500',
component: () => import(/* webpackChunkName: "error" */ './500')
}];
... ...
import ErrorPages from './error';
export default [...ErrorPages];
... ...
import Markfav from './markfav';
import Single from './single';
import Common from './common';
export default [...Markfav, ...Single];
export default [...Markfav, ...Single, ...Common];
... ...
... ... @@ -5,7 +5,7 @@ import routes from '../pages';
Vue.use(Router);
export function createRouter() {
return new Router({
const route = new Router({
mode: 'history',
routes,
scrollBehavior(to, from, savedPosition) {
... ... @@ -16,4 +16,13 @@ export function createRouter() {
}
}
});
route.beforeEach((to, from, next) => {
if (!to.matched.length) {
return next({name: 'error.404'});
}
next();
});
return route;
}
... ...
... ... @@ -66,9 +66,9 @@ exports.createApp = async(app) => {
app.use(userMiddleware);
app.use(ssrApiMiddleware);
app.use(ssrRouteMiddleware);
app.use(ssrRouteMiddleware.routers);
app.all('*', errorMiddleware.notFound); // 404
app.all('*', ssrRouteMiddleware.ssrRender); // 404
// YOHO 后置中间件
app.use(errorMiddleware.serverError);
... ...
/**
* 404 错误
* @return {[type]}
*/
const logger = global.yoho.logger;
exports.notFound = (req, res) => {
res.status(404);
if (req.xhr) {
return res.json({
code: 404,
message: '抱歉,页面不存在!'
});
}
return res.render('error/404', {
module: 'common',
page: 'error',
title: '页面不存在 | BLK | 潮流购物逛不停',
noLocalCSS: true
});
};
/**
* 服务器错误
* @return {[type]}
*/
exports.serverError = (err, req, res, next) => {
exports.serverError = (err, req, res, next) => { // eslint-disable-line
logger.error(`error at path: ${req.url}`);
logger.error(err);
logger.error(`${req.url},${err}`);
if (!res.headersSent) {
res.status(err.code || 500);
if (req.xhr) {
... ... @@ -40,12 +17,5 @@ exports.serverError = (err, req, res, next) => {
});
}
return res.render('error/500', {
module: 'common',
page: 'error',
title: '服务器错误 | BLK | 潮流购物逛不停',
noLocalCSS: true
});
}
next(err);
return res.send('服务器开小差了~');
};
... ...
... ... @@ -13,13 +13,13 @@ module.exports = async(req, res, next) => {
if (!apiInfo.service) {
baseParams = {
uid: {
uid: (req.user && req.user.uid) ? {
toString: () => {
return req.user.uid || 0;
},
sessionKey: req.user.sessionKey,
appSessionType: req.user.appSessionType
},
} : 0,
method: apiInfo.api
};
}
... ...
const fs = require('fs');
const path = require('path');
const url = require('url');
const sourceMap = require('source-map');
const _ = require('lodash');
const os = require('os');
const md5 = require('yoho-md5');
const pkg = require('../../package.json');
const routes = require('../../config/ssr-routes');
const redis = require('../../utils/redis');
const routeEncode = require('../../utils/route-encode');
const {createBundleRenderer} = require('vue-server-renderer');
const logger = global.yoho.logger;
const config = global.yoho.config;
const REG_STACK = /at ([^:]+):(\d+):(\d+)/;
const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
let renderer;
... ... @@ -41,11 +40,26 @@ const getContext = (req) => {
isiOS: req.yoho.isiOS,
isAndroid: req.yoho.isAndroid,
isYohoApp: req.yoho.isYohoApp,
clientIp: req.yoho.clientIp
}
clientIp: req.yoho.clientIp,
},
ua: req.get('user-agent'),
hostname: os.hostname(),
route: `[${req.method}]${_.get(req, 'route.path', '')}`, // 请求路由
udid: _.get(req, 'cookies.udid', 'yoho'),
path: `[${req.method}]${routeEncode.getRouter(req)}`,
};
};
const handlerError = (err = {}, req, res, next) => {
if (err.code === 404) {
return res.redirect('/error/404');
} else if (err.code === 500) {
return res.redirect('/error/500');
}
console.log(err)
return next(err);
};
const getCacheKey = (urlPath, cackeKey = '') => {
const urlObj = url.parse(urlPath);
... ... @@ -54,37 +68,11 @@ const getCacheKey = (urlPath, cackeKey = '') => {
.replace('$params', urlObj.query));
};
const parseError = async({stack = ''}) => {
try {
const splits = stack.split('\n');
const lastError = splits.map(str => {
const match = str.match(REG_STACK);
if (match) {
return {file: match[1], line: parseInt(match[2], 10), column: parseInt(match[3], 10)};
}
return false;
}).find(match => match);
if (lastError && lastError.file) {
const consumer = await new sourceMap.SourceMapConsumer(serverBundle.maps[lastError.file]);
const origin = consumer.originalPositionFor({
line: lastError.line,
column: 342
});
console.log(origin);
}
} catch (error) {
logger.error(error);
}
};
const render = (route) => {
return async(req, res, next) => {
try {
res.setHeader('X-YOHO-Version', pkg.version);
const ck = getCacheKey(req.url, route.cackeKey);
const ck = route.cackeKey ? getCacheKey(req.url, route.cackeKey) : void 0;
if (config.useCache && route.cache && ck) {
const html = await redis.getAsync(ck);
... ... @@ -100,20 +88,23 @@ const render = (route) => {
renderer.renderToString(context, (err, html) => {
if (err) {
parseError(err);
return next(err.message);
return handlerError(err, req, res, next);
}
if (config.useCache && route.cache && ck) {
redis.setex(ck, route.cacheTime || 60, html);
}
return res.send(html);
});
} catch (error) {
return next(error);
}
};
};
const devRender = (route) => {
return async(req, res, next) => {
try {
res.setHeader('X-YOHO-Version', pkg.version);
const ck = getCacheKey(req.url, route.cackeKey);
const ck = route.cackeKey ? getCacheKey(req.url, route.cackeKey) : void 0;
if (config.useCache && route.cache && ck) {
const html = await redis.getAsync(ck);
... ... @@ -132,19 +123,12 @@ const devRender = (route) => {
process.removeListener('message', event);
if (msg.action === 'ssr_request') {
if (msg.err) {
try {
const err = JSON.parse(msg.err);
let err = msg.err;
if (err.code === 404) {
return next();
}
return next(err);
} catch (e) {
return next({
code: 500,
message: msg.err
});
}
try {
err = JSON.parse(msg.err);
} catch (error) {} // eslint-disable-line
return handlerError(err, req, res, next);
}
if (config.useCache && route.cache && ck) {
redis.setex(ck, route.cacheTime || 60, msg.html);
... ... @@ -154,6 +138,9 @@ const devRender = (route) => {
};
process.on('message', event);
} catch (error) {
return next(error);
}
};
};
... ... @@ -165,4 +152,6 @@ _.each(routes, r => {
}
});
module.exports = router;
exports.ssrRender = isDev ? devRender({}) : render({});
exports.routers = router;
... ...
const _ = require('lodash');
const crypto = global.yoho.crypto;
function urlJoin(a, b) {
if (_.endsWith(a, '/') && _.startsWith(b, '/')) {
return a + b.substring(1, b.length);
} else if (!_.endsWith(a, '/') && !_.startsWith(b, '/')) {
return a + '/' + b;
} else {
return a + b;
}
}
function _encode(str) {
return encodeURIComponent(crypto.encryption(null, str));
}
const encode = _.memoize(_encode);
function getRouter(req) {
let route = req.route ? req.route.path : '';
let appPath = req.app.mountpath;
if (_.isArray(route) && route.length > 0) {
route = route[0];
}
let key = urlJoin(appPath, route.toString()); // route may be a regexp
if (key) {
return encode(key);
}
return '';
}
module.exports.md = function(req, res, next) {
function onRender() {
res.locals._router = getRouter(req);
}
res.on('beforeRender', onRender);
next();
};
module.exports.getRouter = getRouter;
... ...