2017-09-09 20:59:44 +08:00
|
|
|
|
process.env.VUE_ENV = 'server'
|
|
|
|
|
const isProd = process.env.NODE_ENV === 'production'
|
|
|
|
|
global.NODE_ENV = isProd
|
|
|
|
|
const useMicroCache = process.env.MICRO_CACHE !== 'false'
|
|
|
|
|
|
|
|
|
|
const fs = require('fs')
|
|
|
|
|
const path = require('path')
|
|
|
|
|
const favicon = require('serve-favicon')
|
|
|
|
|
const express = require('express')
|
|
|
|
|
const session = require('express-session');
|
|
|
|
|
const MongoStore = require('connect-mongo')(session);
|
|
|
|
|
const compression = require('compression')
|
|
|
|
|
const lurCache = require('lru-cache')
|
|
|
|
|
const ueditor = require("ueditor")
|
|
|
|
|
const logger = require('morgan')
|
|
|
|
|
const cookieParser = require('cookie-parser')
|
|
|
|
|
const bodyParser = require('body-parser')
|
|
|
|
|
const { createBundleRenderer } = require('vue-server-renderer')
|
2017-11-28 19:32:50 +08:00
|
|
|
|
const _ = require('lodash')
|
2017-09-09 20:59:44 +08:00
|
|
|
|
const resolve = file => path.resolve(__dirname, file)
|
|
|
|
|
|
|
|
|
|
const serverInfo =
|
|
|
|
|
`express/${require('express/package.json').version} ` +
|
|
|
|
|
`vue-server-renderer/${require('vue-server-renderer/package.json').version}`
|
|
|
|
|
|
|
|
|
|
const { service, settings, authSession, logUtil, siteFunc } = require('./utils');
|
|
|
|
|
const authUser = require('./utils/middleware/authUser');
|
|
|
|
|
const { AdminResource } = require('./server/lib/controller');
|
|
|
|
|
// 引入 api 路由
|
|
|
|
|
const routes = require('./server/routers/api')
|
|
|
|
|
const foreground = require('./server/routers/foreground')
|
|
|
|
|
const manage = require('./server/routers/manage');
|
|
|
|
|
const system = require('./server/routers/system');
|
|
|
|
|
|
|
|
|
|
const isCacheable = () => useMicroCache
|
|
|
|
|
const microCache = lurCache({
|
|
|
|
|
max: 100,
|
|
|
|
|
maxAge: 1000
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function createRenderer(bundle, template) {
|
|
|
|
|
// https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer
|
|
|
|
|
return createBundleRenderer(bundle, {
|
|
|
|
|
template,
|
|
|
|
|
|
|
|
|
|
// for component caching
|
|
|
|
|
cache: lurCache({
|
|
|
|
|
max: 1000,
|
|
|
|
|
maxAge: 1000 * 60 * 15
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
// this is only needed when vue-server-renderer is npm-linked
|
|
|
|
|
basedir: resolve('./dist'),
|
|
|
|
|
|
|
|
|
|
// recommended for performance
|
|
|
|
|
runInNewContext: false
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const app = express()
|
|
|
|
|
|
|
|
|
|
// 由 html-webpack-plugin 生成
|
|
|
|
|
let frontend
|
|
|
|
|
let backend
|
|
|
|
|
// 创建来自 webpack 生成的服务端包
|
|
|
|
|
let renderer
|
|
|
|
|
if (isProd) {
|
|
|
|
|
// 生产模式: 从 fs 创建服务器 HTML 渲染器和索引
|
|
|
|
|
const bundle = require('./dist/vue-ssr-bundle.json')
|
|
|
|
|
frontend = fs.readFileSync(resolve('./dist/server.html'), 'utf-8')
|
|
|
|
|
renderer = createRenderer(bundle, frontend)
|
|
|
|
|
} else {
|
|
|
|
|
// 开发模式: 设置带有热重新加载的 dev 服务器,并在文件更改时更新渲染器和索引 HTML
|
|
|
|
|
require('./build/setup-dev-server')(app, (bundle, _template) => {
|
|
|
|
|
frontend = _template.frontend
|
|
|
|
|
backend = _template.backend
|
|
|
|
|
renderer = createRenderer(bundle, frontend)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置静态文件缓存时间
|
|
|
|
|
const serve = (path, cache) => express.static(resolve(path), { maxAge: cache && isProd ? 60 * 60 * 24 * 30 : 0 })
|
|
|
|
|
|
|
|
|
|
// 引用 esj 模板引擎
|
|
|
|
|
app.set('views', path.join(__dirname, 'dist'))
|
|
|
|
|
app.engine('.html', require('ejs').__express)
|
|
|
|
|
app.set('view engine', 'ejs')
|
|
|
|
|
|
|
|
|
|
app.use(favicon('./favicon.ico'))
|
|
|
|
|
app.use(compression({ threshold: 0 }))
|
|
|
|
|
// 日志
|
|
|
|
|
app.use(logger('":method :url" :status :res[content-length] ":referrer" ":user-agent"'))
|
|
|
|
|
// body 解析中间件
|
|
|
|
|
// app.use(bodyParser.json())
|
|
|
|
|
app.use(bodyParser.urlencoded({ extended: true }))
|
|
|
|
|
// cookie 解析中间件
|
|
|
|
|
app.use(cookieParser(settings.session_secret));
|
|
|
|
|
// session配置
|
|
|
|
|
app.use(session({ //session持久化配置
|
|
|
|
|
secret: settings.encrypt_key,
|
|
|
|
|
// key: "kvkenskey",
|
|
|
|
|
cookie: {
|
|
|
|
|
maxAge: 1000 * 60 * 60 * 24 * 1
|
|
|
|
|
},
|
|
|
|
|
resave: false,
|
|
|
|
|
saveUninitialized: true,
|
|
|
|
|
store: new MongoStore({
|
|
|
|
|
db: "session",
|
|
|
|
|
host: "localhost",
|
|
|
|
|
port: 27017,
|
|
|
|
|
url: !isProd ? settings.URL : 'mongodb://' + settings.USERNAME + ':' + settings.PASSWORD + '@' + settings.HOST + ':' + settings.PORT + '/' + settings.DB + ''
|
|
|
|
|
})
|
|
|
|
|
}));
|
|
|
|
|
// 鉴权用户
|
|
|
|
|
app.use(authUser.auth);
|
|
|
|
|
// 初始化日志目录
|
|
|
|
|
logUtil.initPath();
|
|
|
|
|
// 设置 express 根目录
|
|
|
|
|
app.use(express.static(path.join(__dirname, 'dist')))
|
|
|
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
|
|
|
app.use('/server', serve('./dist/server', true))
|
|
|
|
|
app.use('/static', serve('./dist/static', true))
|
|
|
|
|
app.use('/manifest.json', serve('./manifest.json'))
|
|
|
|
|
app.use('/service-worker.js', serve('./dist/service-worker.js'))
|
|
|
|
|
// api 路由
|
|
|
|
|
app.use('/', foreground);
|
|
|
|
|
app.use('/api', routes);
|
|
|
|
|
app.use('/system', system);
|
|
|
|
|
|
|
|
|
|
// 前台路由, ssr 渲染
|
|
|
|
|
app.get(['/', '/page/:current(\\d+)?', '/:cate1?___:typeId?/:current(\\d+)?',
|
|
|
|
|
'/:cate0/:cate1?___:typeId?/:current(\\d+)?', '/search/:searchkey/:current(\\d+)?',
|
|
|
|
|
'/details/:id', '/users/:userPage', '/dr-admin', '/sitemap.html', '/tag/:tagName/:page(\\d+)?'], (req, res) => {
|
2017-11-06 17:53:48 +08:00
|
|
|
|
|
|
|
|
|
// 非正常登录用户禁止访问
|
|
|
|
|
if (req.originalUrl.indexOf('/users') == 0 && !req.session.logined) {
|
|
|
|
|
return res.redirect('/');
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-09 20:59:44 +08:00
|
|
|
|
if (req.originalUrl === '/dr-admin' && req.session.adminlogined) {
|
|
|
|
|
return res.redirect('/manage');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!renderer) {
|
|
|
|
|
return res.end('waiting for compilation... refresh in a moment.')
|
|
|
|
|
}
|
|
|
|
|
const s = Date.now()
|
|
|
|
|
|
|
|
|
|
res.setHeader("Content-Type", "text/html")
|
|
|
|
|
res.setHeader("Server", serverInfo)
|
|
|
|
|
|
|
|
|
|
const errorHandler = err => {
|
|
|
|
|
if (err && err.code === 404) {
|
|
|
|
|
res.status(404).end('404 | Page Not Found')
|
|
|
|
|
} else {
|
|
|
|
|
// Render Error Page or Redirect
|
|
|
|
|
res.status(500).end('Internal Error 500')
|
|
|
|
|
console.error(`error during render : ${req.url}`)
|
|
|
|
|
console.error(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cacheable = isCacheable(req)
|
|
|
|
|
if (cacheable) {
|
|
|
|
|
const hit = microCache.get(req.url)
|
|
|
|
|
|
|
|
|
|
if (hit) {
|
|
|
|
|
if (!isProd) {
|
|
|
|
|
console.log('cache hit!')
|
|
|
|
|
}
|
|
|
|
|
console.log(`whole request from cache: ${Date.now() - s}ms`)
|
|
|
|
|
return res.end(hit);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const context = {
|
|
|
|
|
title: '前端开发俱乐部',
|
|
|
|
|
description: '前端开发俱乐部',
|
|
|
|
|
keywords: 'doracms',
|
|
|
|
|
url: req.url,
|
|
|
|
|
cookies: req.cookies
|
|
|
|
|
}
|
|
|
|
|
renderer.renderToString(context, (err, html) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
return errorHandler(err)
|
|
|
|
|
}
|
|
|
|
|
res.end(html)
|
|
|
|
|
if (cacheable) {
|
|
|
|
|
microCache.set(req.url, html)
|
|
|
|
|
}
|
|
|
|
|
console.log(`whole request: ${Date.now() - s}ms`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 机器人抓取
|
|
|
|
|
app.get('/robots.txt', function (req, res, next) {
|
|
|
|
|
let stream = fs.createReadStream(path.join(__dirname, './robots.txt'), {
|
|
|
|
|
flags: 'r'
|
|
|
|
|
});
|
|
|
|
|
stream.pipe(res);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 集成ueditor
|
2017-11-28 19:32:50 +08:00
|
|
|
|
let qnParams = settings.openqn ? {
|
|
|
|
|
qn: {
|
|
|
|
|
accessKey: settings.accessKey,
|
|
|
|
|
secretKey: settings.secretKey,
|
|
|
|
|
bucket: settings.bucket,
|
|
|
|
|
origin: settings.origin
|
|
|
|
|
},
|
|
|
|
|
local: true
|
|
|
|
|
} : {};
|
|
|
|
|
app.use("/ueditor/ue", ueditor(path.join(__dirname, 'public'), config = qnParams, function (req, res, next) {
|
2017-09-09 20:59:44 +08:00
|
|
|
|
var imgDir = '/upload/images/ueditor/' //默认上传地址为图片
|
|
|
|
|
var ActionType = req.query.action;
|
|
|
|
|
if (ActionType === 'uploadimage' || ActionType === 'uploadfile' || ActionType === 'uploadvideo') {
|
|
|
|
|
var file_url = imgDir; //默认上传地址为图片
|
|
|
|
|
/*其他上传格式的地址*/
|
|
|
|
|
if (ActionType === 'uploadfile') {
|
|
|
|
|
file_url = '/upload/file/ueditor/'; //附件保存地址
|
|
|
|
|
}
|
|
|
|
|
if (ActionType === 'uploadvideo') {
|
|
|
|
|
file_url = '/upload/video/ueditor/'; //视频保存地址
|
|
|
|
|
}
|
|
|
|
|
res.ue_up(file_url); //你只要输入要保存的地址 。保存操作交给ueditor来做
|
|
|
|
|
res.setHeader('Content-Type', 'text/html');
|
|
|
|
|
}
|
|
|
|
|
//客户端发起图片列表请求
|
|
|
|
|
else if (ActionType === 'listimage') {
|
|
|
|
|
|
|
|
|
|
res.ue_list(imgDir); // 客户端会列出 dir_url 目录下的所有图片
|
|
|
|
|
}
|
|
|
|
|
// 客户端发起其它请求
|
|
|
|
|
else {
|
|
|
|
|
res.setHeader('Content-Type', 'application/json');
|
|
|
|
|
res.redirect('/ueditor/ueditor.config.json')
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
2017-11-28 19:32:50 +08:00
|
|
|
|
|
2017-09-09 20:59:44 +08:00
|
|
|
|
// 后台渲染
|
|
|
|
|
app.get('/manage', authSession, function (req, res) {
|
2017-11-28 19:32:50 +08:00
|
|
|
|
AdminResource.getAllResource(req, res).then((manageCates) => {
|
|
|
|
|
let adminPower = req.session.adminUserInfo.group.power;
|
|
|
|
|
let currentCates = JSON.stringify(siteFunc.renderNoPowerMenus(manageCates, adminPower));
|
2017-09-09 20:59:44 +08:00
|
|
|
|
if (isProd) {
|
|
|
|
|
res.render('admin.html', {
|
|
|
|
|
title: 'DoraCMS后台管理',
|
|
|
|
|
manageCates: currentCates
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
backend = backend.replace('__manageCates__', currentCates)
|
|
|
|
|
res.send(backend)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
app.use('/manage', manage);
|
|
|
|
|
|
|
|
|
|
// 404 页面
|
|
|
|
|
app.get('*', (req, res) => {
|
2017-11-28 19:32:50 +08:00
|
|
|
|
let Page404 = `
|
|
|
|
|
<div style="text-align:center">
|
|
|
|
|
<h3 style="width: 25%;font-size: 12rem;color: #409eff;margin: 0 auto;margin-top: 10%;">404</h3>
|
|
|
|
|
<div style="font-size: 15px;color: #878d99;">太调皮辣,不过我喜欢...哼哼 😏👽 <a href="/">返回首页</a></div>
|
|
|
|
|
</div>
|
|
|
|
|
`
|
|
|
|
|
res.send(Page404)
|
2017-09-09 20:59:44 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
app.use(function (req, res, next) {
|
|
|
|
|
var err = new Error(req.originalUrl + ' Not Found')
|
|
|
|
|
err.status = 404
|
|
|
|
|
next(err)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
app.use(function (err, req, res) {
|
|
|
|
|
if (err) logUtil.error(err, req);
|
|
|
|
|
res.status(err.status || 500)
|
|
|
|
|
res.send(err.message)
|
|
|
|
|
})
|
|
|
|
|
|
2017-11-28 19:32:50 +08:00
|
|
|
|
const port = process.env.PORT || settings.serverPort
|
2017-09-09 20:59:44 +08:00
|
|
|
|
app.listen(port, () => {
|
|
|
|
|
console.log(`server started at localhost:${port}`)
|
|
|
|
|
})
|