目錄javascript
開發環境也能夠不用mock,直接提供後端server服務,這也是全棧開發和以前前端開發的不一樣之處。
能夠把node後端當成中間層,請求舊後端幷包裝後返回。css
項目使用webpack 1.15.0、vue 2.3.二、Node.js 6.9.2進行的開發。html
使用模板vue-element-admin-boilerplate中關於開發的思想1進行佈局,而後又豐富了它,包裝成分爲開發、生產模式的最外層的server.js。前端
使用vue-element-admin-boilerplate模板默認生成的開發服務器只是至關於當前項目中client文件夾內部client/build/dev-server.js
。vue
配置文件:.editorconfig、.eslintrc.js
項目介紹:readme.md。java
| - client 前端 | - dist 打包目錄,也是服務器的靜態資源路徑 | - node_modules | - server 後端服務器 | - static client靜態資源 | - package.json 工程文件 | - server.js 開發入口服務器
核心的部分有3個,最外層的server.js,client文件夾,server文件夾。
最外層的server.js是開發使用的。
client文件夾至關於原來的前端。
server文件夾至關於原來的後端。
node_modules先後端公用。
整體上:把先後端結合到一個項目中進行開發。node
最外層server.js使用了express框架。mysql
使用process.env.NODE_ENV(production爲生產模式)並同時定義了一個變量useWebpackDev(false爲生產模式)做爲標誌來判斷是server.js是處於開發環境仍是生產發佈環境。
只有在npm run build的時候會將process.env.NODE_ENV設置爲production(在client/build/build.js第4行)env.NODE_ENV = 'production'
,useWebpackDev是在server_config中設置的,而server_config又是依靠NODE_ENV來肯定是引用important.js(生成環境配置)仍是development.js,在important.js中useWebpackDev爲false(或undefined),在development.js中useWebpackDev爲true。
使用NODE_ENV以外再useWebpackDev能夠給定義當前是什麼環境多一些擴展性,由於:let env = process.env.NODE_ENV || 'development';
,因此能夠不用是非此即彼,即只能在development和production之間選擇。
important.js其實內容是相似development.js,只不過是存在雨服務器上,避免保密數據泄露在git裏。webpack
都是使用server.js提供服務,在生產環境也是當前的完整目錄結構,依然是使用當前server.js提供服務。ios
開發環境和生產環境其實都是同樣執行 node server.js
,只不過其實分別用了nodemon
或pm2
。
開發環境使用webpack大禮包:webpack、webpack-dev-middleware、webpack-hot-middleware。
生產環境(已經build過)直接對根目錄輸出index.html(剩下的交給vue-router)及static服務。最後都是監聽端口開啓服務。
npm run build
npm run build
命令主要是使用webpack打包以及一些複製粘貼工做(docker部署是另一碼事)。
本身造server並同時使用webpack的思路:
1.利用express本身造了一個server服務 1.5 可能但願在這裏提供一些路由服務(參考下面對路由劫持的分析) 2.app.use(require('connect-history-api-fallback')())包裝req.url 3.var compiler = webpack(webpackConfig)定義了讀取哪些怎麼讀取資源 4.dev-middleware讀取資源 5.hot-middleware提供熱刷新 5.5 可能但願在這裏提供一些路由服務(參考下面對路由劫持的分析)
參考文獻2
webpack-dev-server模塊內部在node_modules中包含webpack-dev-midlleware(把webpack生成的compiler變成express中間件的容器)。
The webpack-dev-server is a little Node.js Express server, which uses the webpack-dev-middleware to serve a webpack bundle. It also has a little runtime which is connected to the server via Sock.js.
webpack-dev-server提供了項目目錄文件的static服務,在模塊源文件第203行:
app.get("*", express.static(contentBase), serveIndex(contentBase));
好比通過測試發現,使用dev-server的rainvue項目在瀏覽器能訪問到根目錄的任何文件。
咱們本身的server使用的是express。
關於middleware中間件看註解3。能夠連續寫多個函數,即多個連續執行的中間件。app.use([path,] callback [, callback...])
根部若是同樣,就執行中間件,好比定義了path爲/abc 則/abc/ddd也執行該中間件
app.use('/', function(){})
和app.use(function(){})
沒有區別,'/' (root path)
是默認值。
Mounts the specified middleware function or functions at the specified path: the middleware function is executed when the base of the requested path matches path.
express的錯誤處理中間件,必須包含4個參數,這是區別於普通中間件的標誌。
app.use(function (err, req, res, next) { console.error(err.stack) res.status(500).send('Something broke!') })
express.static是express內置中間件,提供靜態資源服務。能夠同時有多個static。不存在代碼覆蓋關係,匹配到哪個就直接返回了,後面的也就不執行了。即中間件的本性:誰在前面誰生效。
//You can have more than one static directory per app: app.use(express.static('public')) app.use(express.static('uploads')) app.use(express.static('files'))
// 本項目屬於重複配置了static,能夠後期清理下代碼。 // server/index.js中 配置static服務路徑是在生產環境中的路徑 '/cms/getup/static' '/Users/liujunyang/henry/work/yktcms/server/dist/static' // server.js中進入useWebpackDev的if後,也配置了static。 '/cms/getup/static' './static' // else中也配置了static。 '/cms/getup/static' './dist/static'
功能:在單頁應用中,處理當刷新頁面或直接在地址欄訪問非根頁面的時候,返回404的bug。匹配非文件(路徑中不帶.)的get請求。
connect-history-api-fallback會 包裝 req.url。請求路徑若是符合匹配規則(如/或路徑如/dsfdsfd) 就會重寫爲/index.html(位於模塊文件第71行),不然(如/test.do)就保持原樣,最終都是進入next(),(模塊文件的50-75行),繼續執行後面的中間件。
... if (parsedUrl.pathname.indexOf('.') !== -1 && options.disableDotRule !== true) { logger( 'Not rewriting', req.method, req.url, 'because the path includes a dot (.) character.' ); return next(); } rewriteTarget = options.index || '/index.html'; logger('Rewriting', req.method, req.url, 'to', rewriteTarget); req.url = rewriteTarget; next();
1.生成的資源不是存在於文件夾,而是在內存中。
The webpack-dev-middleware is a small middleware for a connect-based middleware stack. It uses webpack to compile assets in-memory and serve them. When a compilation is running every request to the served webpack assets is blocked until we have a stable bundle. *譯:把資源編譯進內存,在編譯過程當中,全部請求都不能進行,直到得到穩定的bundle。*
2.html-webpack-plugin只是提供渲染生成文件,至於生成到哪是根據compiler,好比webpack-dev-middleware 提供的服務是在內存中,則yktcms開發中,不管是生成index.html仍是再生成別的文件,都是生成到內存中,在編輯器的文件目錄中是看不到的。
3.webpack-dev-middleware提供的服務是利用compiler的配置編譯在內存中的文件。
咱們使用了html-webpack-plugin,dev模式開發時index.html不用server.js從views文件夾中進行render。因此即便把views中的index.html刪除也不會影響dev模式開發。
new HtmlWebpackPlugin({ filename: 'index.html', template: './client/index.html', inject: true })
server在生產環境提供的views中的index.html是編譯時經過npm run build
生成的,並非手動建立的。
4.webpack-dev-middleware 中有send,若是執行send返回了請求,就不會走express以後的中間件了。
function processRequest() { ... // server content var content = context.fs.readFileSync(filename); content = shared.handleRangeHeaders(content, req, res); res.setHeader("Access-Control-Allow-Origin", "*"); // To support XHR, etc. res.setHeader("Content-Type", mime.lookup(filename) + "; charset=UTF-8"); res.setHeader("Content-Length", content.length); if(context.options.headers) { for(var name in context.options.headers) { res.setHeader(name, context.options.headers[name]); } } // Express automatically sets the statusCode to 200, but not all servers do (Koa). res.statusCode = res.statusCode || 200; if(res.send) res.send(content); else res.end(content); }
爲webpack提供熱刷新功能。
Webpack hot reloading using only webpack-dev-middleware. This allows you to add hot reloading into an existing server without webpack-dev-server.
使用方法的僞代碼:
Add the following plugins to the plugins array Add 'webpack-hot-middleware/client' into the entry array. app.use(require("webpack-hot-middleware")(compiler));
這裏說這個主要是介紹webpack-hot-middleware如何把 webpack-hot-middleware/client 加入entry。
entry單項若是是array,打包在一塊兒輸出最後一個。
webpack.dev.conf.js中的第9行:
baseWebpackConfig.entry[name] =['./client/build/dev-client'].concat(baseWebpackConfig.entry[name])
client/build/dev-client.js文件中的(require中省略了.js後綴) 'webpack-hot-middleware/client?reload=true’;實際上是 'webpack-hot-middleware/client.js?reload=true';
node-walk不是中間件,是一個遍歷文件夾的nodejs庫,只是遍歷,遍歷過程當中想 要作什麼處理的話給傳入相應的函數便可。
好比咱們是利用walk模塊遍歷mock文件夾的文件,讓指定的路由(url
)能返回訪問指定的文件(require(mod)
)
... var walk = require('walk') var walker = walk.walk('./client/mock', {followLinks: false}) ... ... walker.on('file', function (root, fileStat, next) { if (!/\.js$/.test(fileStat.name)) next() var filepath = path.join(root, fileStat.name) var url = filepath.replace('client/mock', '/mock').replace('.js', '') var mod = filepath.replace('client/mock', './client/mock') app.all(url, require(mod)) next() })
// 匹配的本地mock的url,帶.do後綴是爲了避免匹配connect-history-api-fallback以及vue-router的路由規則 ... if (useMock) { api = { ... article: '/mock/article/article.do', article_create: '/mock/article/create.do', ... } } ...
注意:walk必定不要忘記最後的next()。
可能到某一步就直接結束了,像過多層篩子,攔住了就不往下走了
1.若是符合放在前面的路由就直接結束。
2.connect-history-api-fallback。包裝req.url。
3.webpack-dev-middleware的處理。
若是在內存中(即compiler生成的文件,如app.js,如index.html或htmlplugin生成的一系列html)輸出req.url表明的文件,如/Users/liujunyang/henry/work/yktcms/dist/index.html。結束。 若是進入index.html,使用vue-router對當前路徑再次進行匹配,劫持了全部的router,結束。不符合vue-router規則的才繼續往下走next。
若是不在內存中,直接走next,也就不存在下面討論的vue-router的過濾規則的事。
4.其餘對文件的處理(能走到這的通常也就是文件了),好比上面介紹的walk的處理。
注意:在開發時,即useWebpackDev爲true時,每一個請求(不論文件仍是接口)都會通過層層(沒必要然是全部的)中間件處理,因此即便是切換vue裏面的路由其實也會把index.html自己再傳輸一遍。
而到線上以後,在輸出index.html以後,凡是vue-router能匹配的請求都交給vue-router了。
1.vue-router.js的674-763行的代碼是關於路徑的正則匹配:凡是路徑中帶點.的路由【除xxx.html外,對xxx.html的處理和不帶點的路徑是同樣的】,均不被vue-router匹配劫持。被匹配的就都會被vue-router處理(即劫持)。
2.在chrome的console中測試以下:
var PATH_REGEXP = new RegExp([ // Match escaped characters that would otherwise appear in future matches. // This allows the user to escape special characters that won't transform. '(\\\\.)', // Match Express-style parameters and un-named parameters with a prefix // and optional suffixes. Matches appear as: // // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' ].join('|'), 'g'); PATH_REGEXP /(\\.)|([\/.])?(?:(?:\:(\w+)(?:\(((?:\\.|[^\\()])+)\))?|\(((?:\\.|[^\\()])+)\))([+*?])?|(\*))/g PATH_REGEXP.test('http://localhost:20003/cms/getup/article-list2.dd') true PATH_REGEXP.exec('http://localhost:20003/cms/getup/article-list2.dd') null PATH_REGEXP.exec('http://localhost:20003/cms/getup/article-list2.') null PATH_REGEXP.test('http://localhost:20003/cms/getup/article-list2dd') false PATH_REGEXP.exec('http://localhost:20003/cms/getup/article-list2dd') [":20003", undefined, undefined, "20003", undefined, undefined, undefined, undefined] PATH_REGEXP.exec('http://localhost:20003/cms/getup/article-list2.html') [":20003", undefined, undefined, "20003", undefined, undefined, undefined, undefined]
3.好比:在地址欄手動訪問index.html,在webpack-dev-middleware輸出index.html後,因爲index.html頁面中有app.js,app.js中安排了vue-router,那麼接下來進入vue-router處理,因爲路徑 index.html
能被vue-router的規則匹配,根據app.js的中路由的處理,fallback進入了404頁面。
// fallback處理 routers.push({ path: '*', name: 'notfound', component: require('./views/notfound') })
可是若是有處理,就會進入相應的路由:
// 對路徑 index.html匹配組件進行處理 { path: '/index.html', name: 'perm', component: require('./views/perm') }
假設做爲webpack entry的那個包含有vue-router的js文件爲:main.js。
在html-webpack-plugin的規則中,inject爲true時main.js注入html文件,不然html文件沒有main.js文件。
1.第一個測試:
在htmlplugin中添加一個test.html,設置inject爲false。在地址欄輸入相應的pathto/test.html
,回車。
流程是這樣的: 由於test.html文件是htmlplugin中的,因此被編譯進了內存中,webpack-dev-middleware輸出test.html文件並經server返回給瀏覽器。 由於沒有main.js,因此沒有vue-router再對'pathto/test.html'進行處理,結束。 webpack-dev-middleware中處理的url爲:'/Users/liujunyang/henry/work/yktcms/dist/test.html'
2.第二個測試:
在htmlplugin中添加一個test2.html,設置inject爲true.在地址欄輸入相應的pathto/test2.html
,回車。
流程是這樣的: 由於test2.html文件是htmlplugin中的,因此被編譯進了內存中,webpack-dev-middleware輸出test2.html文件並經server返回給瀏覽器。 由於有main.js,因此加載並執行main.js。 main.js中有vue-router,'pathto/test2.html'能被vue-router匹配,可是根據main.js中的路由配置,並無對'pathto/test2.html'進行處理,就fallback進入了404頁面(這個流程和上面vue-router過濾規則中第3條對index.html的介紹是同樣的)。
3.第三個測試:
在地址欄輸入pathto/test.do
,回車(測試文件類型的路徑)。
流程是這樣的: 由於test.do不是htmlplugin中的,不在內存中,不會提早被webpack-dev-middleware輸出。 中間件直接next(也就沒有輸出index.html,更是根本沒vue-router什麼事)。 而後根據server有沒有提供路由服務或static服務,找到了就輸出,沒有找到的話就告知「Cannot GET /dd.do」
經過打包時npm run build
以後生成的文件直接提供index.html服務,提供static服務。
沒有connect-history-api-fallback 沒有webpack-dev-middleware 沒有webpack-hot-middleware 沒有開發環境須要的mock(開發環境也能夠不用mock,直接提供後端server服務,這也是全棧開發和以前前端開發的不一樣之處)。
git pull npm install npm run build pm2 restart cms pm2 list
TODO 詳解docker。
把webpack的配置文件放進了client端中,有歷史緣由(借鑑了vue-element-admin-boilerplate
模板),實際上是不該該的,應該放最外面。
涉及到的庫、模式、工具等在下面的解剖中再逐一慢慢涉及。如redis、sequelize等。
下面的解剖都是簡單解剖,詳細細節再新開筆記進行單獨介紹。本筆記着眼於整個項目的思路。
如何啓動開發環境 npm run dev
基本上就是最外層佈置了一個server。
根據環境判斷條件決定進入哪一個server。
開發環境須要devMiddleware。
生產環境因爲是webpack打包好的,直接提供打包好的文件服務。
... // server毛坯 const app = require('./server/index') ... // 什麼環境都須要的路由處理(放在了devMiddleware處理的前面) app.use(client_config.ROOT_ROUTER + '/api', cms_router); app.use(client_config.ROOT_ROUTER + '/preview', cms_router); app.use(client_config.ROOT_ROUTER + '/front', front_router); ... if (process.env.NODE_ENV !== 'production' && useWebpackDev) { ... // 開發環境 var compiler = webpack(webpackConfig) ... app.use(require('connect-history-api-fallback')()) ... app.use(devMiddleware) ... app.use(hotMiddleware) ... } else { ... // 生產環境 app.use(client_config.ROOT_ROUTER, function (req, res, next) { // 渲染cms頁面 res.render('index.html'); }); ... } ... app.listen(server_config.PORT)
就是一個vue2.0項目。
| - client 前端 | - build 開發、生產配置文件 | - config 配置文件 | - mock 模擬數據 | - src 前端vue開發 | - index.html vue項目的入口html文件
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui"> <meta name="format-detection" content="telephone=no" /> <meta name="keywords" content=""> <meta name="Description" content=""> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <link type="image/x-icon" rel="shortcut icon" href=""> <title></title> </head> <body> <div id="app"></div> <script src="/cms/getup/static/js/tinymce/tinymce.min.js"></script> <script src="/cms/getup/static/js/qrcode.min.js"></script> <!-- built files will be auto injected --> </body> </html>
client/src/main.js
import Vue from 'vue' ... import VueRouter from 'vue-router' import config from './config' import App from './App' ... Vue.use(VueRouter) ... const router = new VueRouter({ mode: 'history', base: config.ROOT_ROUTER, routes }) ... /* eslint-disable no-new */ new Vue({ el: '#app', template: '<App/>', router, components: {App} })
client/src/App.vue
<template> <transition> <router-view></router-view> </transition> </template>
client/src/router-config.js
/* eslint-disable */ ... const routers = [ { path: '/', component: require('./views/index') }, { path: '/article', name: 'article', component: require('./views/article/index'), children: [ { path: 'list', name: 'article-list', component: require('./views/article/list') }, { path: 'create', name: 'article-editor', component: require('./views/article/editor') }, ] }, ... ] routers.push({ path: '*', name: 'notfound', component: require('./views/notfound') }) export default routers
path: /client/src/api/request.js
使用axios和bluebird包裝了ajax請求。
使用方法:
// '/client/src/api/article.js' import request from './request' import Api from './api' export function createActivity (params) { return request.post(Api.activity_create, params) } ...
能夠算是一個node項目。
| - server 後端服務器 | - config 配置 | - controllers 處理業務邏輯 | - helpers 工具方法 | - models 數據庫結構 | - views 後端render的頁面 | - index.js 服務入口文件 | - routes-front.js 路由:非cms | - routes_cmsapi.js 路由:cms
毛坯server。
使用express。
'use strict'; const express = require('express'); ... // 造server const app = express(); // 模板引擎使用ejs app.set('views', path.join(__dirname, 'views')); app.engine('html', require('ejs').renderFile); app.set('view engine', 'ejs'); // 定義靜態資源static服務 app.use(client_config.ROOT_ROUTER + '/static', express.static(path.join(__dirname, 'dist/static'))); // 配置統一的header處理 app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", '*'); res.header("Access-Control-Allow-Headers", "User-Agent, Referer, Content-Type, Range, Content-Length, Authorization, Accept,X-Requested-With"); res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); res.header("Access-Control-Max-Age", 24 * 60 * 60);//預檢有效期 res.header("Access-Control-Expose-Headers", "Content-Range"); res.header("Access-Control-Allow-Credentials", true); if (req.method == "OPTIONS") return res.sendStatus(200); ... // 給每個請求的res對象定義了一個格式化函數備用 res.fmt = function (data) { //格式化返回結果 ... return res.json(resData); }; // 進入後續中間件處理 next(); }); ... // 輸出app在最外層server.js中繼續進行包裝。 module.exports = app; ...
舉2類例子:接口;渲染頁面。
1.接口類以請求文章列表爲例。
如某個ajax請求爲pathto/api/article/list
。
根據server.js第35行app.use(client_config.ROOT_ROUTER + '/api', cms_router);
,被cms_router路由處理,其中cms_router爲const cms_router = require('./server/routes_cmsapi');
,
cms_router路由中第74行router.get('/article/list', auth_validate, CmsArtileApiCtrl.articleList);
是對文章列表的處理。
其中 CmsArtileApiCtrl
是const CmsArtileApiCtrl = require('./controllers/cms/article');
,是對ArticleModel
的操做的包裝,因此放在controllers中,從數據庫獲取完數據後對接口進行返回:
... }).then(function (result) { return res.fmt({ data: { listData: result, count: count, pageNo: pageNo, pageSize: pageSize } }); }) ...
而ArticleModel
是const ArticleModel = require('../../models').ArticleModel;
,是用sequelize
定義的數據庫的用於文章的表。
2.渲染頁面類以某篇文章頁面爲例。
如在瀏覽器地址欄請求爲pathto/front/view/article/34
回車。
根據server.js第37行app.use(client_config.ROOT_ROUTER + '/front', front_router);
,被front_router路由處理,其中front_router爲const front_router = require('./server/routes-front');
,
front_router路由中第23行router.get('/view/article/:id', FrontArtileApiCtrl.frontViewArticle);
是對文章的處理。
其中FrontArtileApiCtrl
是const FrontArtileApiCtrl = require('./controllers/front/article');
,是另一個對ArticleModel
的操做的包裝,因此放在controllers中,是在構造函數的方法frontViewArticle
中把對應id的數據從數據庫獲取完數據後套ejs模板並進行redis緩存後返回瀏覽器(若是redis中存在的話則直接返回已有的文章)。
而ArticleModel
跟上面第一類例子相同是const ArticleModel = require('../../models').ArticleModel;
,是同一個用sequelize
定義的數據庫的用於文章的表。
對不一樣的接口請求配置不一樣的路由處理
... const express = require('express'); ... const router = express.Router(); ... const CmsArtileApiCtrl = require('./controllers/cms/article'); ... //獲取文章 router.get('/article', auth_validate, CmsArtileApiCtrl.article); //獲取文章列表 router.get('/article/list', auth_validate, CmsArtileApiCtrl.articleList); //建立文章 router.post('/article/create', auth_validate, CmsArtileApiCtrl.articleCreate); //刪除文章 router.post('/article/delete', auth_validate, CmsArtileApiCtrl.articleDelete); //編輯文章 router.post('/article/edit', auth_validate, CmsArtileApiCtrl.articleEdit); ... module.exports = router;
CmsArtileApiCtrl是一個構造函數,它的方法實際上是做爲router的中間件處理函數,進行的是數據庫操做。
... // 引用數據表實例 const ArticleModel = require('../../models').ArticleModel; ... const redisArticleUpdate = require('../../helpers/ArtAndActCacheUpdate').redisArticleUpdate; // 定義一個構造函數,它的方法實際上是做爲router的中間件處理函數 function Article() { } // 查詢文章列表並返回 注意req, res, next Article.prototype.articleList = function (req, res, next) { let where = {status: {'$ne': -1}}; ... let count = 0; Promise.all([ // 進行真正的數據庫操做 ArticleModel.findAll(pagination_query), ArticleModel.count(count_query) ]).then(function (result) { ... }).then(function (result) { return res.fmt({ data: { listData: result, count: count, pageNo: pageNo, pageSize: pageSize } }); }) } // 查詢某篇文章並返回 Article.prototype.article = function (req, res, next) { let where = {id: req.query.id || 0}; let query = { where: where } ArticleModel.findOne(query).then(function (result) { ... }).then(function (result) { return res.fmt({ status: 0, data: result }); }) } Article.prototype.articleCreate = function (req, res, next) { ... } Article.prototype.articleEdit = function (req, res, next) { ... } Article.prototype.articleDelete = function (req, res, next) { ... } ... // 返回構造函數的實例 module.exports = new Article();
依然是以本筆記中的文章的模型爲例。
下面是使用sequelize創建的數據表的定義。定義了title, content等字段。
path: /server/models/article.js
// 小寫 sequelize 的爲咱們建立的數據庫實例,大寫的 Sequelize 爲使用的sequelize庫模塊 const Sequelize = require('../helpers/mysql').Sequelize; const sequelize = require('../helpers/mysql').sequelize; // 文章表 var CmsArticle = sequelize.define('cms_article', { title: { type: Sequelize.TEXT, allowNull: false, comment: '標題' }, content: { type: Sequelize.TEXT, allowNull: false, comment: '內容' }, ... }, { 'createdAt': false, 'updatedAt': false }); // CmsArticle.sync({force: true}) module.exports = CmsArticle;
path: /server/helpers/mysql.js
'use strict'; const Sequelize = require('sequelize'); const mysql_config = require('../config/env').MYSQL[0]; const sequelize = new Sequelize(mysql_config.DATABASE, mysql_config.USER, mysql_config.PASSWORD, { host: mysql_config.HOST, dialect: 'mysql', //數據庫類型 pool: { max: 5, min: 0, idle: 10000 }, logging: false }); // 小寫 sequelize 的爲咱們建立的數據庫實例,大寫的 Sequelize 爲使用的sequelize庫模塊 exports.sequelize = sequelize; exports.Sequelize = Sequelize;
path: /server/views/article.html
<!DOCTYPE html> <html> <head> ... <title><%= navigator_title %></title> <style> <% include ./common/css-article.html %> .wrapper {background: #fff;} ... </style> ... </head> <body> <div class="wrapper"> ... <div><%- content %></div> ... </body> </html>
使用的ioredis
path: server/helpers/redis.js
| - client 前端 | - build 配置文件 | - build.js build時node調用的js | - config.js 一些公用配置 | - webpack.base.conf.js webpack基本配置 | - webpack.dev.conf.js webpack開發配置 | - webpack.prod.conf.js webpack生產配置 | - config | - dev.env.js 開發環境NODE_ENV | - index.js config模塊的對外輸出總接口 | - prod.env.js 生產環境NODE_ENV | - mock 模擬數據 | - article 文章相關 | - list.do.js 文章列表的假數據 | - src | - api 定義請求接口 | - api.js 定義接口的url(開發、生產) | - article.js 定義文章相關如何調用接口 | - request.js 包裝請求函數 | - assets 靜態資源 | - loading.gif | - components 存放定義的vue組件 | - Editor.vue 富文本編輯組件 | - Layout.vue 佈局組件 | - Search.vue 搜索框組件 | - Editor.vue 富文本編輯組件 | - helpers 存放公用js函數模塊 | - style 存放公用scss模塊(如mixin) | - _mixin.scss | - views 存放路由頁面 | - article 文章相關頁面 | - editor.vue 編輯文章 | - index.vue router-view模板 | - list.vue 文章列表 | - preview.vue 預覽文章 | - index.vue 根頁面 | - login.vue 登陸頁面 | - noauth.vue 無權限提示頁面 | - notfound.vue 404頁面 | - App.vue 根路由模板 | - auth.js 權限管理模塊 | - config.js 開發配置 | - main.js vue入口js | - nav-config.js 導航欄路由配置 | - router-config.js 路由配置 | - index.html vue項目的入口html文件 | - deploy 部署相關 | - dist 打包目錄,也是服務器的靜態資源路徑 | - logs 自動生成的日誌 | - node_modules | - scripts node腳本 | - job.js 生成字體、負責推送 | - server 後端服務器 | - config 配置 | - env 配置 | - development.js 開發配置 | - index.js 配置入口 | - production.js 生產配置 | - code.js 定義錯誤碼 | - controllers 處理業務邏輯 | - cms cms類 | - article.js 文章處理接口,利用model操做數據庫 | - auth.js 權限過濾 | - front front類 | - article.js | - helpers 工具方法 | - logger.js 日誌處理 | - mysql.js mysql客戶端 | - permCache.js 權限相關 | - redis.js redis | - redisCacheUpdate.js redis | - request.js 包裝請求 | - upload.js 七牛上傳 | - userinfo.js 用戶信息 | - webfont.js webfont | - models 數據庫結構 | - article.js 文章表 | - index.js 入口 | - profile.js 用戶數據 | - views 後端render的頁面 | - article.html 文章頁ejs模板 | - list.html 列表頁ejs模板 | - index.js 服務入口文件 | - routes-front.js 路由:非cms | - routes_cmsapi.js 路由:cms | - static client靜態資源 | - .bablerc bablerc | - .docerignore docerignore | - .editorconfig editorconfig | - .eslintignore eslintignore | - .eslintrc.js eslintrc | - .gitignore gitignore | - package.json 工程文件 | - readme.md readme | - server.js 開發服務器
並無使用webpack的dev-server,而是本身提供了一套server開發環境,另外提供了一套後端服務的server,同時提供了一套前端的vue開發成果↩
https://juejin.im/entry/574f95922e958a00683e402d
http://acgtofe.com/posts/2016/02/full-live-reload-for-express-with-webpack
https://github.com/bripkens/connect-history-api-fallback
https://github.com/nuysoft/Mock/wiki
http://fontawesome.io/icons/↩
If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.
To skip the rest of the middleware functions from a router middleware stack, call next('route') to pass control to the next route. NOTE: next('route') will work only in middleware functions that were loaded by using the app.METHOD() or router.METHOD() functions.↩