全棧開發的不同之處
開發環境也可以不用mock,直接提供后端server服務,這也是全棧開發和之前前端開發的不同之處。
可以把node后端當成中間層,請求舊后端並包裝后返回。
本項目框架簡介
項目使用webpack 1.15.0、vue 2.3.2、Node.js 6.9.2進行的開發。
使用模板vue-element-admin-boilerplate中關於開發的思想[1]進行布局,然后又豐富了它,包裝成分為開發、生產模式的最外層的server.js。
使用vue-element-admin-boilerplate模板默認生成的開發服務器只是相當於當前項目中client文件夾內部client/build/dev-server.js
。
配置文件:.editorconfig、.eslintrc.js
項目介紹:readme.md。
效果
思路拆解
文件夾結構
| - client 前端
| - dist 打包目錄,也是服務器的靜態資源路徑
| - node_modules
| - server 后端服務器
| - static client靜態資源
| - package.json 工程文件
| - server.js 開發入口服務器
layout各部分功能分配
核心的部分有3個,最外層的server.js,client文件夾,server文件夾。
最外層的server.js是開發使用的。
client文件夾相當於原來的前端。
server文件夾相當於原來的后端。
node_modules前后端公用。
總體上:把前后端結合到一個項目中進行開發。
最外層server.js使用了express框架。
開發環境vs生產環境
不同之處
使用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里。
相同之處
都是使用server.js提供服務,在生產環境也是當前的完整目錄結構,依然是使用當前server.js提供服務。
開發環境和生產環境其實都是一樣執行 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 可能希望在這里提供一些路由服務(參考下面對路由劫持的分析)
關於webpack-dev-server【我們項目沒有使用,而是自己開發了一個server】
參考文獻[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項目在瀏覽器能訪問到根目錄的任何文件。
關於express及其中間件
我們自己的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'
關於connect-history-api-fallback
功能:在單頁應用中,處理當刷新頁面或直接在地址欄訪問非根頁面的時候,返回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();
關於webpack-dev-middleware中間件容器
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-hot-middleware
為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的entry
這里說這個主要是介紹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';
關於walk
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了。
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服務,這也是全棧開發和之前前端開發的不同之處)。
部署
cms部署到雷
git pull
npm install
npm run build
pm2 restart cms
pm2 list
docker部署到雨
TODO 詳解docker。
簡單細節——解剖
概要
把webpack的配置文件放進了client端中,有歷史原因(借鑒了vue-element-admin-boilerplate
模板),其實是不應該的,應該放最外面。
涉及到的庫、模式、工具等在下面的解剖中再逐一慢慢涉及。如redis、sequelize等。
下面的解剖都是簡單解剖,詳細細節再新開筆記進行單獨介紹。本筆記着眼於整個項目的思路。
應用如何啟動
如何啟動開發環境 npm run dev
最外層server.js的解剖
基本上就是最外層布置了一個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)
client端的解剖
就是一個vue2.0項目。
layout
| - client 前端
| - build 開發、生產配置文件
| - config 配置文件
| - mock 模擬數據
| - src 前端vue開發
| - index.html vue項目的入口html文件
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>
入口js
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
request處理函數
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)
}
...
server端的解剖
可以算是一個node項目。
layout
| - server 后端服務器
| - config 配置
| - controllers 處理業務邏輯
| - helpers 工具方法
| - models 數據庫結構
| - views 后端render的頁面
| - index.js 服務入口文件
| - routes-front.js 路由:非cms
| - routes_cmsapi.js 路由:cms
server端index.js
毛坯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
定義的數據庫的用於文章的表。
路由配置 cms_router
對不同的接口請求配置不同的路由處理
...
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
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();
數據表 ArticleModel
依然是以本筆記中的文章的模型為例。
下面是使用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;
ejs模板
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>
redis處理
使用的ioredis
path: server/helpers/redis.js
完整layout
| - 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. ↩︎